1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Sound driver for Nintendo 64.
4 *
5 * Copyright 2021 Lauri Kasanen
6 */
7
8#include <linux/dma-mapping.h>
9#include <linux/init.h>
10#include <linux/interrupt.h>
11#include <linux/io.h>
12#include <linux/log2.h>
13#include <linux/module.h>
14#include <linux/platform_device.h>
15#include <linux/spinlock.h>
16#include <linux/string.h>
17
18#include <sound/control.h>
19#include <sound/core.h>
20#include <sound/initval.h>
21#include <sound/pcm.h>
22#include <sound/pcm_params.h>
23
24MODULE_AUTHOR("Lauri Kasanen <cand@gmx.com>");
25MODULE_DESCRIPTION("N64 Audio");
26MODULE_LICENSE("GPL");
27
28#define AI_NTSC_DACRATE 48681812
29#define AI_STATUS_BUSY (1 << 30)
30#define AI_STATUS_FULL (1 << 31)
31
32#define AI_ADDR_REG 0
33#define AI_LEN_REG 1
34#define AI_CONTROL_REG 2
35#define AI_STATUS_REG 3
36#define AI_RATE_REG 4
37#define AI_BITCLOCK_REG 5
38
39#define MI_INTR_REG 2
40#define MI_MASK_REG 3
41
42#define MI_INTR_AI 0x04
43
44#define MI_MASK_CLR_AI 0x0010
45#define MI_MASK_SET_AI 0x0020
46
47
48struct n64audio {
49 u32 __iomem *ai_reg_base;
50 u32 __iomem *mi_reg_base;
51
52 void *ring_base;
53 dma_addr_t ring_base_dma;
54
55 struct snd_card *card;
56
57 struct {
58 struct snd_pcm_substream *substream;
59 int pos, nextpos;
60 u32 writesize;
61 u32 bufsize;
62 spinlock_t lock;
63 } chan;
64};
65
66static void n64audio_write_reg(struct n64audio *priv, const u8 reg, const u32 value)
67{
68 writel(val: value, addr: priv->ai_reg_base + reg);
69}
70
71static void n64mi_write_reg(struct n64audio *priv, const u8 reg, const u32 value)
72{
73 writel(val: value, addr: priv->mi_reg_base + reg);
74}
75
76static u32 n64mi_read_reg(struct n64audio *priv, const u8 reg)
77{
78 return readl(addr: priv->mi_reg_base + reg);
79}
80
81static void n64audio_push(struct n64audio *priv)
82{
83 struct snd_pcm_runtime *runtime = priv->chan.substream->runtime;
84 u32 count;
85
86 guard(spinlock_irqsave)(l: &priv->chan.lock);
87
88 count = priv->chan.writesize;
89
90 memcpy(priv->ring_base + priv->chan.nextpos,
91 runtime->dma_area + priv->chan.nextpos, count);
92
93 /*
94 * The hw registers are double-buffered, and the IRQ fires essentially
95 * one period behind. The core only allows one period's distance, so we
96 * keep a private DMA buffer to afford two.
97 */
98 n64audio_write_reg(priv, AI_ADDR_REG, value: priv->ring_base_dma + priv->chan.nextpos);
99 barrier();
100 n64audio_write_reg(priv, AI_LEN_REG, value: count);
101
102 priv->chan.nextpos += count;
103 priv->chan.nextpos %= priv->chan.bufsize;
104
105 runtime->delay = runtime->period_size;
106}
107
108static irqreturn_t n64audio_isr(int irq, void *dev_id)
109{
110 struct n64audio *priv = dev_id;
111 const u32 intrs = n64mi_read_reg(priv, MI_INTR_REG);
112
113 // Check it's ours
114 if (!(intrs & MI_INTR_AI))
115 return IRQ_NONE;
116
117 n64audio_write_reg(priv, AI_STATUS_REG, value: 1);
118
119 if (priv->chan.substream && snd_pcm_running(substream: priv->chan.substream)) {
120 scoped_guard(spinlock_irqsave, &priv->chan.lock) {
121 priv->chan.pos = priv->chan.nextpos;
122 }
123
124 snd_pcm_period_elapsed(substream: priv->chan.substream);
125 if (priv->chan.substream && snd_pcm_running(substream: priv->chan.substream))
126 n64audio_push(priv);
127 }
128
129 return IRQ_HANDLED;
130}
131
132static const struct snd_pcm_hardware n64audio_pcm_hw = {
133 .info = (SNDRV_PCM_INFO_MMAP |
134 SNDRV_PCM_INFO_MMAP_VALID |
135 SNDRV_PCM_INFO_INTERLEAVED |
136 SNDRV_PCM_INFO_BLOCK_TRANSFER),
137 .formats = SNDRV_PCM_FMTBIT_S16_BE,
138 .rates = SNDRV_PCM_RATE_8000_48000,
139 .rate_min = 8000,
140 .rate_max = 48000,
141 .channels_min = 2,
142 .channels_max = 2,
143 .buffer_bytes_max = 32768,
144 .period_bytes_min = 1024,
145 .period_bytes_max = 32768,
146 .periods_min = 3,
147 // 3 periods lets the double-buffering hw read one buffer behind safely
148 .periods_max = 128,
149};
150
151static int hw_rule_period_size(struct snd_pcm_hw_params *params,
152 struct snd_pcm_hw_rule *rule)
153{
154 struct snd_interval *c = hw_param_interval(params,
155 SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
156 int changed = 0;
157
158 /*
159 * The DMA unit has errata on (start + len) & 0x3fff == 0x2000.
160 * This constraint makes sure that the period size is not a power of two,
161 * which combined with dma_alloc_coherent aligning the buffer to the largest
162 * PoT <= size guarantees it won't be hit.
163 */
164
165 if (is_power_of_2(n: c->min)) {
166 c->min += 2;
167 changed = 1;
168 }
169 if (is_power_of_2(n: c->max)) {
170 c->max -= 2;
171 changed = 1;
172 }
173 if (snd_interval_checkempty(i: c)) {
174 c->empty = 1;
175 return -EINVAL;
176 }
177
178 return changed;
179}
180
181static int n64audio_pcm_open(struct snd_pcm_substream *substream)
182{
183 struct snd_pcm_runtime *runtime = substream->runtime;
184 int err;
185
186 runtime->hw = n64audio_pcm_hw;
187 err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
188 if (err < 0)
189 return err;
190
191 err = snd_pcm_hw_constraint_step(runtime, cond: 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, step: 2);
192 if (err < 0)
193 return err;
194
195 err = snd_pcm_hw_rule_add(runtime, cond: 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
196 func: hw_rule_period_size, NULL, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1);
197 if (err < 0)
198 return err;
199
200 return 0;
201}
202
203static int n64audio_pcm_prepare(struct snd_pcm_substream *substream)
204{
205 struct snd_pcm_runtime *runtime = substream->runtime;
206 struct n64audio *priv = substream->pcm->private_data;
207 u32 rate;
208
209 rate = ((2 * AI_NTSC_DACRATE / runtime->rate) + 1) / 2 - 1;
210
211 n64audio_write_reg(priv, AI_RATE_REG, value: rate);
212
213 rate /= 66;
214 if (rate > 16)
215 rate = 16;
216 n64audio_write_reg(priv, AI_BITCLOCK_REG, value: rate - 1);
217
218 guard(spinlock_irq)(l: &priv->chan.lock);
219
220 /* Setup the pseudo-dma transfer pointers. */
221 priv->chan.pos = 0;
222 priv->chan.nextpos = 0;
223 priv->chan.substream = substream;
224 priv->chan.writesize = snd_pcm_lib_period_bytes(substream);
225 priv->chan.bufsize = snd_pcm_lib_buffer_bytes(substream);
226
227 return 0;
228}
229
230static int n64audio_pcm_trigger(struct snd_pcm_substream *substream,
231 int cmd)
232{
233 struct n64audio *priv = substream->pcm->private_data;
234
235 switch (cmd) {
236 case SNDRV_PCM_TRIGGER_START:
237 n64audio_push(priv: substream->pcm->private_data);
238 n64audio_write_reg(priv, AI_CONTROL_REG, value: 1);
239 n64mi_write_reg(priv, MI_MASK_REG, MI_MASK_SET_AI);
240 break;
241 case SNDRV_PCM_TRIGGER_STOP:
242 n64audio_write_reg(priv, AI_CONTROL_REG, value: 0);
243 n64mi_write_reg(priv, MI_MASK_REG, MI_MASK_CLR_AI);
244 break;
245 default:
246 return -EINVAL;
247 }
248 return 0;
249}
250
251static snd_pcm_uframes_t n64audio_pcm_pointer(struct snd_pcm_substream *substream)
252{
253 struct n64audio *priv = substream->pcm->private_data;
254
255 return bytes_to_frames(runtime: substream->runtime,
256 size: priv->chan.pos);
257}
258
259static int n64audio_pcm_close(struct snd_pcm_substream *substream)
260{
261 struct n64audio *priv = substream->pcm->private_data;
262
263 priv->chan.substream = NULL;
264
265 return 0;
266}
267
268static const struct snd_pcm_ops n64audio_pcm_ops = {
269 .open = n64audio_pcm_open,
270 .prepare = n64audio_pcm_prepare,
271 .trigger = n64audio_pcm_trigger,
272 .pointer = n64audio_pcm_pointer,
273 .close = n64audio_pcm_close,
274};
275
276/*
277 * The target device is embedded and RAM-constrained. We save RAM
278 * by initializing in __init code that gets dropped late in boot.
279 * For the same reason there is no module or unloading support.
280 */
281static int __init n64audio_probe(struct platform_device *pdev)
282{
283 struct snd_card *card;
284 struct snd_pcm *pcm;
285 struct n64audio *priv;
286 int err, irq;
287
288 err = snd_card_new(parent: &pdev->dev, SNDRV_DEFAULT_IDX1,
289 SNDRV_DEFAULT_STR1,
290 THIS_MODULE, extra_size: sizeof(*priv), card_ret: &card);
291 if (err < 0)
292 return err;
293
294 priv = card->private_data;
295
296 spin_lock_init(&priv->chan.lock);
297
298 priv->card = card;
299
300 priv->ring_base = dma_alloc_coherent(dev: card->dev, size: 32 * 1024, dma_handle: &priv->ring_base_dma,
301 GFP_DMA|GFP_KERNEL);
302 if (!priv->ring_base) {
303 err = -ENOMEM;
304 goto fail_card;
305 }
306
307 priv->mi_reg_base = devm_platform_ioremap_resource(pdev, index: 0);
308 if (IS_ERR(ptr: priv->mi_reg_base)) {
309 err = PTR_ERR(ptr: priv->mi_reg_base);
310 goto fail_dma_alloc;
311 }
312
313 priv->ai_reg_base = devm_platform_ioremap_resource(pdev, index: 1);
314 if (IS_ERR(ptr: priv->ai_reg_base)) {
315 err = PTR_ERR(ptr: priv->ai_reg_base);
316 goto fail_dma_alloc;
317 }
318
319 err = snd_pcm_new(card, id: "N64 Audio", device: 0, playback_count: 1, capture_count: 0, rpcm: &pcm);
320 if (err < 0)
321 goto fail_dma_alloc;
322
323 pcm->private_data = priv;
324 strscpy(pcm->name, "N64 Audio");
325
326 snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_PLAYBACK, ops: &n64audio_pcm_ops);
327 snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, data: card->dev, size: 0, max: 0);
328
329 strscpy(card->driver, "N64 Audio");
330 strscpy(card->shortname, "N64 Audio");
331 strscpy(card->longname, "N64 Audio");
332
333 irq = platform_get_irq(pdev, 0);
334 if (irq < 0) {
335 err = -EINVAL;
336 goto fail_dma_alloc;
337 }
338 if (devm_request_irq(dev: &pdev->dev, irq, handler: n64audio_isr,
339 IRQF_SHARED, devname: "N64 Audio", dev_id: priv)) {
340 err = -EBUSY;
341 goto fail_dma_alloc;
342 }
343
344 err = snd_card_register(card);
345 if (err < 0)
346 goto fail_dma_alloc;
347
348 return 0;
349
350fail_dma_alloc:
351 dma_free_coherent(dev: card->dev, size: 32 * 1024, cpu_addr: priv->ring_base, dma_handle: priv->ring_base_dma);
352
353fail_card:
354 snd_card_free(card);
355 return err;
356}
357
358static struct platform_driver n64audio_driver = {
359 .driver = {
360 .name = "n64audio",
361 },
362};
363
364static int __init n64audio_init(void)
365{
366 return platform_driver_probe(&n64audio_driver, n64audio_probe);
367}
368
369module_init(n64audio_init);
370

source code of linux/sound/mips/snd-n64.c