11448f8acSLauri Kasanen // SPDX-License-Identifier: GPL-2.0
21448f8acSLauri Kasanen /*
31448f8acSLauri Kasanen * Sound driver for Nintendo 64.
41448f8acSLauri Kasanen *
51448f8acSLauri Kasanen * Copyright 2021 Lauri Kasanen
61448f8acSLauri Kasanen */
71448f8acSLauri Kasanen
81448f8acSLauri Kasanen #include <linux/dma-mapping.h>
91448f8acSLauri Kasanen #include <linux/init.h>
101448f8acSLauri Kasanen #include <linux/interrupt.h>
111448f8acSLauri Kasanen #include <linux/io.h>
121448f8acSLauri Kasanen #include <linux/log2.h>
131448f8acSLauri Kasanen #include <linux/module.h>
141448f8acSLauri Kasanen #include <linux/platform_device.h>
151448f8acSLauri Kasanen #include <linux/spinlock.h>
161448f8acSLauri Kasanen
171448f8acSLauri Kasanen #include <sound/control.h>
181448f8acSLauri Kasanen #include <sound/core.h>
191448f8acSLauri Kasanen #include <sound/initval.h>
201448f8acSLauri Kasanen #include <sound/pcm.h>
211448f8acSLauri Kasanen #include <sound/pcm_params.h>
221448f8acSLauri Kasanen
231448f8acSLauri Kasanen MODULE_AUTHOR("Lauri Kasanen <cand@gmx.com>");
241448f8acSLauri Kasanen MODULE_DESCRIPTION("N64 Audio");
251448f8acSLauri Kasanen MODULE_LICENSE("GPL");
261448f8acSLauri Kasanen
271448f8acSLauri Kasanen #define AI_NTSC_DACRATE 48681812
281448f8acSLauri Kasanen #define AI_STATUS_BUSY (1 << 30)
291448f8acSLauri Kasanen #define AI_STATUS_FULL (1 << 31)
301448f8acSLauri Kasanen
311448f8acSLauri Kasanen #define AI_ADDR_REG 0
321448f8acSLauri Kasanen #define AI_LEN_REG 1
331448f8acSLauri Kasanen #define AI_CONTROL_REG 2
341448f8acSLauri Kasanen #define AI_STATUS_REG 3
351448f8acSLauri Kasanen #define AI_RATE_REG 4
361448f8acSLauri Kasanen #define AI_BITCLOCK_REG 5
371448f8acSLauri Kasanen
381448f8acSLauri Kasanen #define MI_INTR_REG 2
391448f8acSLauri Kasanen #define MI_MASK_REG 3
401448f8acSLauri Kasanen
411448f8acSLauri Kasanen #define MI_INTR_AI 0x04
421448f8acSLauri Kasanen
431448f8acSLauri Kasanen #define MI_MASK_CLR_AI 0x0010
441448f8acSLauri Kasanen #define MI_MASK_SET_AI 0x0020
451448f8acSLauri Kasanen
461448f8acSLauri Kasanen
471448f8acSLauri Kasanen struct n64audio {
481448f8acSLauri Kasanen u32 __iomem *ai_reg_base;
491448f8acSLauri Kasanen u32 __iomem *mi_reg_base;
501448f8acSLauri Kasanen
511448f8acSLauri Kasanen void *ring_base;
521448f8acSLauri Kasanen dma_addr_t ring_base_dma;
531448f8acSLauri Kasanen
541448f8acSLauri Kasanen struct snd_card *card;
551448f8acSLauri Kasanen
561448f8acSLauri Kasanen struct {
571448f8acSLauri Kasanen struct snd_pcm_substream *substream;
581448f8acSLauri Kasanen int pos, nextpos;
591448f8acSLauri Kasanen u32 writesize;
601448f8acSLauri Kasanen u32 bufsize;
611448f8acSLauri Kasanen spinlock_t lock;
621448f8acSLauri Kasanen } chan;
631448f8acSLauri Kasanen };
641448f8acSLauri Kasanen
n64audio_write_reg(struct n64audio * priv,const u8 reg,const u32 value)651448f8acSLauri Kasanen static void n64audio_write_reg(struct n64audio *priv, const u8 reg, const u32 value)
661448f8acSLauri Kasanen {
671448f8acSLauri Kasanen writel(value, priv->ai_reg_base + reg);
681448f8acSLauri Kasanen }
691448f8acSLauri Kasanen
n64mi_write_reg(struct n64audio * priv,const u8 reg,const u32 value)701448f8acSLauri Kasanen static void n64mi_write_reg(struct n64audio *priv, const u8 reg, const u32 value)
711448f8acSLauri Kasanen {
721448f8acSLauri Kasanen writel(value, priv->mi_reg_base + reg);
731448f8acSLauri Kasanen }
741448f8acSLauri Kasanen
n64mi_read_reg(struct n64audio * priv,const u8 reg)751448f8acSLauri Kasanen static u32 n64mi_read_reg(struct n64audio *priv, const u8 reg)
761448f8acSLauri Kasanen {
771448f8acSLauri Kasanen return readl(priv->mi_reg_base + reg);
781448f8acSLauri Kasanen }
791448f8acSLauri Kasanen
n64audio_push(struct n64audio * priv)801448f8acSLauri Kasanen static void n64audio_push(struct n64audio *priv)
811448f8acSLauri Kasanen {
821448f8acSLauri Kasanen struct snd_pcm_runtime *runtime = priv->chan.substream->runtime;
831448f8acSLauri Kasanen unsigned long flags;
841448f8acSLauri Kasanen u32 count;
851448f8acSLauri Kasanen
861448f8acSLauri Kasanen spin_lock_irqsave(&priv->chan.lock, flags);
871448f8acSLauri Kasanen
881448f8acSLauri Kasanen count = priv->chan.writesize;
891448f8acSLauri Kasanen
901448f8acSLauri Kasanen memcpy(priv->ring_base + priv->chan.nextpos,
911448f8acSLauri Kasanen runtime->dma_area + priv->chan.nextpos, count);
921448f8acSLauri Kasanen
931448f8acSLauri Kasanen /*
941448f8acSLauri Kasanen * The hw registers are double-buffered, and the IRQ fires essentially
951448f8acSLauri Kasanen * one period behind. The core only allows one period's distance, so we
961448f8acSLauri Kasanen * keep a private DMA buffer to afford two.
971448f8acSLauri Kasanen */
981448f8acSLauri Kasanen n64audio_write_reg(priv, AI_ADDR_REG, priv->ring_base_dma + priv->chan.nextpos);
991448f8acSLauri Kasanen barrier();
1001448f8acSLauri Kasanen n64audio_write_reg(priv, AI_LEN_REG, count);
1011448f8acSLauri Kasanen
1021448f8acSLauri Kasanen priv->chan.nextpos += count;
1031448f8acSLauri Kasanen priv->chan.nextpos %= priv->chan.bufsize;
1041448f8acSLauri Kasanen
1051448f8acSLauri Kasanen runtime->delay = runtime->period_size;
1061448f8acSLauri Kasanen
1071448f8acSLauri Kasanen spin_unlock_irqrestore(&priv->chan.lock, flags);
1081448f8acSLauri Kasanen }
1091448f8acSLauri Kasanen
n64audio_isr(int irq,void * dev_id)1101448f8acSLauri Kasanen static irqreturn_t n64audio_isr(int irq, void *dev_id)
1111448f8acSLauri Kasanen {
1121448f8acSLauri Kasanen struct n64audio *priv = dev_id;
1131448f8acSLauri Kasanen const u32 intrs = n64mi_read_reg(priv, MI_INTR_REG);
1141448f8acSLauri Kasanen unsigned long flags;
1151448f8acSLauri Kasanen
1161448f8acSLauri Kasanen // Check it's ours
1171448f8acSLauri Kasanen if (!(intrs & MI_INTR_AI))
1181448f8acSLauri Kasanen return IRQ_NONE;
1191448f8acSLauri Kasanen
1201448f8acSLauri Kasanen n64audio_write_reg(priv, AI_STATUS_REG, 1);
1211448f8acSLauri Kasanen
1221448f8acSLauri Kasanen if (priv->chan.substream && snd_pcm_running(priv->chan.substream)) {
1231448f8acSLauri Kasanen spin_lock_irqsave(&priv->chan.lock, flags);
1241448f8acSLauri Kasanen
1251448f8acSLauri Kasanen priv->chan.pos = priv->chan.nextpos;
1261448f8acSLauri Kasanen
1271448f8acSLauri Kasanen spin_unlock_irqrestore(&priv->chan.lock, flags);
1281448f8acSLauri Kasanen
1291448f8acSLauri Kasanen snd_pcm_period_elapsed(priv->chan.substream);
1301448f8acSLauri Kasanen if (priv->chan.substream && snd_pcm_running(priv->chan.substream))
1311448f8acSLauri Kasanen n64audio_push(priv);
1321448f8acSLauri Kasanen }
1331448f8acSLauri Kasanen
1341448f8acSLauri Kasanen return IRQ_HANDLED;
1351448f8acSLauri Kasanen }
1361448f8acSLauri Kasanen
1371448f8acSLauri Kasanen static const struct snd_pcm_hardware n64audio_pcm_hw = {
1381448f8acSLauri Kasanen .info = (SNDRV_PCM_INFO_MMAP |
1391448f8acSLauri Kasanen SNDRV_PCM_INFO_MMAP_VALID |
1401448f8acSLauri Kasanen SNDRV_PCM_INFO_INTERLEAVED |
1411448f8acSLauri Kasanen SNDRV_PCM_INFO_BLOCK_TRANSFER),
1421448f8acSLauri Kasanen .formats = SNDRV_PCM_FMTBIT_S16_BE,
1431448f8acSLauri Kasanen .rates = SNDRV_PCM_RATE_8000_48000,
1441448f8acSLauri Kasanen .rate_min = 8000,
1451448f8acSLauri Kasanen .rate_max = 48000,
1461448f8acSLauri Kasanen .channels_min = 2,
1471448f8acSLauri Kasanen .channels_max = 2,
1481448f8acSLauri Kasanen .buffer_bytes_max = 32768,
1491448f8acSLauri Kasanen .period_bytes_min = 1024,
1501448f8acSLauri Kasanen .period_bytes_max = 32768,
1511448f8acSLauri Kasanen .periods_min = 3,
1521448f8acSLauri Kasanen // 3 periods lets the double-buffering hw read one buffer behind safely
1531448f8acSLauri Kasanen .periods_max = 128,
1541448f8acSLauri Kasanen };
1551448f8acSLauri Kasanen
hw_rule_period_size(struct snd_pcm_hw_params * params,struct snd_pcm_hw_rule * rule)1561448f8acSLauri Kasanen static int hw_rule_period_size(struct snd_pcm_hw_params *params,
1571448f8acSLauri Kasanen struct snd_pcm_hw_rule *rule)
1581448f8acSLauri Kasanen {
1591448f8acSLauri Kasanen struct snd_interval *c = hw_param_interval(params,
1601448f8acSLauri Kasanen SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
1611448f8acSLauri Kasanen int changed = 0;
1621448f8acSLauri Kasanen
1631448f8acSLauri Kasanen /*
1641448f8acSLauri Kasanen * The DMA unit has errata on (start + len) & 0x3fff == 0x2000.
1651448f8acSLauri Kasanen * This constraint makes sure that the period size is not a power of two,
1661448f8acSLauri Kasanen * which combined with dma_alloc_coherent aligning the buffer to the largest
1671448f8acSLauri Kasanen * PoT <= size guarantees it won't be hit.
1681448f8acSLauri Kasanen */
1691448f8acSLauri Kasanen
1701448f8acSLauri Kasanen if (is_power_of_2(c->min)) {
1711448f8acSLauri Kasanen c->min += 2;
1721448f8acSLauri Kasanen changed = 1;
1731448f8acSLauri Kasanen }
1741448f8acSLauri Kasanen if (is_power_of_2(c->max)) {
1751448f8acSLauri Kasanen c->max -= 2;
1761448f8acSLauri Kasanen changed = 1;
1771448f8acSLauri Kasanen }
1781448f8acSLauri Kasanen if (snd_interval_checkempty(c)) {
1791448f8acSLauri Kasanen c->empty = 1;
1801448f8acSLauri Kasanen return -EINVAL;
1811448f8acSLauri Kasanen }
1821448f8acSLauri Kasanen
1831448f8acSLauri Kasanen return changed;
1841448f8acSLauri Kasanen }
1851448f8acSLauri Kasanen
n64audio_pcm_open(struct snd_pcm_substream * substream)1861448f8acSLauri Kasanen static int n64audio_pcm_open(struct snd_pcm_substream *substream)
1871448f8acSLauri Kasanen {
1881448f8acSLauri Kasanen struct snd_pcm_runtime *runtime = substream->runtime;
1891448f8acSLauri Kasanen int err;
1901448f8acSLauri Kasanen
1911448f8acSLauri Kasanen runtime->hw = n64audio_pcm_hw;
1921448f8acSLauri Kasanen err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
1931448f8acSLauri Kasanen if (err < 0)
1941448f8acSLauri Kasanen return err;
1951448f8acSLauri Kasanen
1961448f8acSLauri Kasanen err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2);
1971448f8acSLauri Kasanen if (err < 0)
1981448f8acSLauri Kasanen return err;
1991448f8acSLauri Kasanen
2001448f8acSLauri Kasanen err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
2011448f8acSLauri Kasanen hw_rule_period_size, NULL, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
2021448f8acSLauri Kasanen if (err < 0)
2031448f8acSLauri Kasanen return err;
2041448f8acSLauri Kasanen
2051448f8acSLauri Kasanen return 0;
2061448f8acSLauri Kasanen }
2071448f8acSLauri Kasanen
n64audio_pcm_prepare(struct snd_pcm_substream * substream)2081448f8acSLauri Kasanen static int n64audio_pcm_prepare(struct snd_pcm_substream *substream)
2091448f8acSLauri Kasanen {
2101448f8acSLauri Kasanen struct snd_pcm_runtime *runtime = substream->runtime;
2111448f8acSLauri Kasanen struct n64audio *priv = substream->pcm->private_data;
2121448f8acSLauri Kasanen u32 rate;
2131448f8acSLauri Kasanen
2141448f8acSLauri Kasanen rate = ((2 * AI_NTSC_DACRATE / runtime->rate) + 1) / 2 - 1;
2151448f8acSLauri Kasanen
2161448f8acSLauri Kasanen n64audio_write_reg(priv, AI_RATE_REG, rate);
2171448f8acSLauri Kasanen
2181448f8acSLauri Kasanen rate /= 66;
2191448f8acSLauri Kasanen if (rate > 16)
2201448f8acSLauri Kasanen rate = 16;
2211448f8acSLauri Kasanen n64audio_write_reg(priv, AI_BITCLOCK_REG, rate - 1);
2221448f8acSLauri Kasanen
2231448f8acSLauri Kasanen spin_lock_irq(&priv->chan.lock);
2241448f8acSLauri Kasanen
2251448f8acSLauri Kasanen /* Setup the pseudo-dma transfer pointers. */
2261448f8acSLauri Kasanen priv->chan.pos = 0;
2271448f8acSLauri Kasanen priv->chan.nextpos = 0;
2281448f8acSLauri Kasanen priv->chan.substream = substream;
2291448f8acSLauri Kasanen priv->chan.writesize = snd_pcm_lib_period_bytes(substream);
2301448f8acSLauri Kasanen priv->chan.bufsize = snd_pcm_lib_buffer_bytes(substream);
2311448f8acSLauri Kasanen
2321448f8acSLauri Kasanen spin_unlock_irq(&priv->chan.lock);
2331448f8acSLauri Kasanen return 0;
2341448f8acSLauri Kasanen }
2351448f8acSLauri Kasanen
n64audio_pcm_trigger(struct snd_pcm_substream * substream,int cmd)2361448f8acSLauri Kasanen static int n64audio_pcm_trigger(struct snd_pcm_substream *substream,
2371448f8acSLauri Kasanen int cmd)
2381448f8acSLauri Kasanen {
2391448f8acSLauri Kasanen struct n64audio *priv = substream->pcm->private_data;
2401448f8acSLauri Kasanen
2411448f8acSLauri Kasanen switch (cmd) {
2421448f8acSLauri Kasanen case SNDRV_PCM_TRIGGER_START:
2431448f8acSLauri Kasanen n64audio_push(substream->pcm->private_data);
2441448f8acSLauri Kasanen n64audio_write_reg(priv, AI_CONTROL_REG, 1);
2451448f8acSLauri Kasanen n64mi_write_reg(priv, MI_MASK_REG, MI_MASK_SET_AI);
2461448f8acSLauri Kasanen break;
2471448f8acSLauri Kasanen case SNDRV_PCM_TRIGGER_STOP:
2481448f8acSLauri Kasanen n64audio_write_reg(priv, AI_CONTROL_REG, 0);
2491448f8acSLauri Kasanen n64mi_write_reg(priv, MI_MASK_REG, MI_MASK_CLR_AI);
2501448f8acSLauri Kasanen break;
2511448f8acSLauri Kasanen default:
2521448f8acSLauri Kasanen return -EINVAL;
2531448f8acSLauri Kasanen }
2541448f8acSLauri Kasanen return 0;
2551448f8acSLauri Kasanen }
2561448f8acSLauri Kasanen
n64audio_pcm_pointer(struct snd_pcm_substream * substream)2571448f8acSLauri Kasanen static snd_pcm_uframes_t n64audio_pcm_pointer(struct snd_pcm_substream *substream)
2581448f8acSLauri Kasanen {
2591448f8acSLauri Kasanen struct n64audio *priv = substream->pcm->private_data;
2601448f8acSLauri Kasanen
2611448f8acSLauri Kasanen return bytes_to_frames(substream->runtime,
2621448f8acSLauri Kasanen priv->chan.pos);
2631448f8acSLauri Kasanen }
2641448f8acSLauri Kasanen
n64audio_pcm_close(struct snd_pcm_substream * substream)2651448f8acSLauri Kasanen static int n64audio_pcm_close(struct snd_pcm_substream *substream)
2661448f8acSLauri Kasanen {
2671448f8acSLauri Kasanen struct n64audio *priv = substream->pcm->private_data;
2681448f8acSLauri Kasanen
2691448f8acSLauri Kasanen priv->chan.substream = NULL;
2701448f8acSLauri Kasanen
2711448f8acSLauri Kasanen return 0;
2721448f8acSLauri Kasanen }
2731448f8acSLauri Kasanen
2741448f8acSLauri Kasanen static const struct snd_pcm_ops n64audio_pcm_ops = {
2751448f8acSLauri Kasanen .open = n64audio_pcm_open,
2761448f8acSLauri Kasanen .prepare = n64audio_pcm_prepare,
2771448f8acSLauri Kasanen .trigger = n64audio_pcm_trigger,
2781448f8acSLauri Kasanen .pointer = n64audio_pcm_pointer,
2791448f8acSLauri Kasanen .close = n64audio_pcm_close,
2801448f8acSLauri Kasanen };
2811448f8acSLauri Kasanen
2821448f8acSLauri Kasanen /*
2831448f8acSLauri Kasanen * The target device is embedded and RAM-constrained. We save RAM
2841448f8acSLauri Kasanen * by initializing in __init code that gets dropped late in boot.
2851448f8acSLauri Kasanen * For the same reason there is no module or unloading support.
2861448f8acSLauri Kasanen */
n64audio_probe(struct platform_device * pdev)2871448f8acSLauri Kasanen static int __init n64audio_probe(struct platform_device *pdev)
2881448f8acSLauri Kasanen {
2891448f8acSLauri Kasanen struct snd_card *card;
2901448f8acSLauri Kasanen struct snd_pcm *pcm;
2911448f8acSLauri Kasanen struct n64audio *priv;
292*a544684bSMeng Tang int err, irq;
2931448f8acSLauri Kasanen
2941448f8acSLauri Kasanen err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1,
2951448f8acSLauri Kasanen SNDRV_DEFAULT_STR1,
2961448f8acSLauri Kasanen THIS_MODULE, sizeof(*priv), &card);
2971448f8acSLauri Kasanen if (err < 0)
2981448f8acSLauri Kasanen return err;
2991448f8acSLauri Kasanen
3001448f8acSLauri Kasanen priv = card->private_data;
3011448f8acSLauri Kasanen
3021448f8acSLauri Kasanen spin_lock_init(&priv->chan.lock);
3031448f8acSLauri Kasanen
3041448f8acSLauri Kasanen priv->card = card;
3051448f8acSLauri Kasanen
3061448f8acSLauri Kasanen priv->ring_base = dma_alloc_coherent(card->dev, 32 * 1024, &priv->ring_base_dma,
3071448f8acSLauri Kasanen GFP_DMA|GFP_KERNEL);
3081448f8acSLauri Kasanen if (!priv->ring_base) {
3091448f8acSLauri Kasanen err = -ENOMEM;
3101448f8acSLauri Kasanen goto fail_card;
3111448f8acSLauri Kasanen }
3121448f8acSLauri Kasanen
3131448f8acSLauri Kasanen priv->mi_reg_base = devm_platform_ioremap_resource(pdev, 0);
314c88fb897SWei Yongjun if (IS_ERR(priv->mi_reg_base)) {
315c88fb897SWei Yongjun err = PTR_ERR(priv->mi_reg_base);
3161448f8acSLauri Kasanen goto fail_dma_alloc;
3171448f8acSLauri Kasanen }
3181448f8acSLauri Kasanen
3191448f8acSLauri Kasanen priv->ai_reg_base = devm_platform_ioremap_resource(pdev, 1);
320c88fb897SWei Yongjun if (IS_ERR(priv->ai_reg_base)) {
321c88fb897SWei Yongjun err = PTR_ERR(priv->ai_reg_base);
3221448f8acSLauri Kasanen goto fail_dma_alloc;
3231448f8acSLauri Kasanen }
3241448f8acSLauri Kasanen
3251448f8acSLauri Kasanen err = snd_pcm_new(card, "N64 Audio", 0, 1, 0, &pcm);
3261448f8acSLauri Kasanen if (err < 0)
3271448f8acSLauri Kasanen goto fail_dma_alloc;
3281448f8acSLauri Kasanen
3291448f8acSLauri Kasanen pcm->private_data = priv;
3301448f8acSLauri Kasanen strcpy(pcm->name, "N64 Audio");
3311448f8acSLauri Kasanen
3321448f8acSLauri Kasanen snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &n64audio_pcm_ops);
3331448f8acSLauri Kasanen snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, card->dev, 0, 0);
3341448f8acSLauri Kasanen
3351448f8acSLauri Kasanen strcpy(card->driver, "N64 Audio");
3361448f8acSLauri Kasanen strcpy(card->shortname, "N64 Audio");
3371448f8acSLauri Kasanen strcpy(card->longname, "N64 Audio");
3381448f8acSLauri Kasanen
339*a544684bSMeng Tang irq = platform_get_irq(pdev, 0);
340*a544684bSMeng Tang if (irq < 0) {
341be471fe3SYang Yingliang err = -EINVAL;
342be471fe3SYang Yingliang goto fail_dma_alloc;
343be471fe3SYang Yingliang }
344*a544684bSMeng Tang if (devm_request_irq(&pdev->dev, irq, n64audio_isr,
3451448f8acSLauri Kasanen IRQF_SHARED, "N64 Audio", priv)) {
3461448f8acSLauri Kasanen err = -EBUSY;
3471448f8acSLauri Kasanen goto fail_dma_alloc;
3481448f8acSLauri Kasanen }
3491448f8acSLauri Kasanen
3501448f8acSLauri Kasanen err = snd_card_register(card);
3511448f8acSLauri Kasanen if (err < 0)
3521448f8acSLauri Kasanen goto fail_dma_alloc;
3531448f8acSLauri Kasanen
3541448f8acSLauri Kasanen return 0;
3551448f8acSLauri Kasanen
3561448f8acSLauri Kasanen fail_dma_alloc:
3571448f8acSLauri Kasanen dma_free_coherent(card->dev, 32 * 1024, priv->ring_base, priv->ring_base_dma);
3581448f8acSLauri Kasanen
3591448f8acSLauri Kasanen fail_card:
3601448f8acSLauri Kasanen snd_card_free(card);
3611448f8acSLauri Kasanen return err;
3621448f8acSLauri Kasanen }
3631448f8acSLauri Kasanen
3641448f8acSLauri Kasanen static struct platform_driver n64audio_driver = {
3651448f8acSLauri Kasanen .driver = {
3661448f8acSLauri Kasanen .name = "n64audio",
3671448f8acSLauri Kasanen },
3681448f8acSLauri Kasanen };
3691448f8acSLauri Kasanen
n64audio_init(void)3701448f8acSLauri Kasanen static int __init n64audio_init(void)
3711448f8acSLauri Kasanen {
3721448f8acSLauri Kasanen return platform_driver_probe(&n64audio_driver, n64audio_probe);
3731448f8acSLauri Kasanen }
3741448f8acSLauri Kasanen
3751448f8acSLauri Kasanen module_init(n64audio_init);
376