Skip to content

Commit 1448f8a

Browse files
clbrtsbogend
authored andcommitted
sound: Add n64 driver
This adds support for the Nintendo 64 console's sound. Signed-off-by: Lauri Kasanen <[email protected]> Reviewed-by: Takashi Iwai <[email protected]> Signed-off-by: Thomas Bogendoerfer <[email protected]>
1 parent 42b2099 commit 1448f8a

File tree

3 files changed

+380
-0
lines changed

3 files changed

+380
-0
lines changed

sound/mips/Kconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,12 @@ config SND_SGI_HAL2
2424
help
2525
Sound support for the SGI Indy and Indigo2 Workstation.
2626

27+
config SND_N64
28+
bool "N64 Audio"
29+
depends on MACH_NINTENDO64 && SND=y
30+
select SND_PCM
31+
help
32+
Sound support for the N64.
33+
2734
endif # SND_MIPS
2835

sound/mips/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ snd-sgi-hal2-objs := hal2.o
99
# Toplevel Module Dependency
1010
obj-$(CONFIG_SND_SGI_O2) += snd-sgi-o2.o
1111
obj-$(CONFIG_SND_SGI_HAL2) += snd-sgi-hal2.o
12+
obj-$(CONFIG_SND_N64) += snd-n64.o

sound/mips/snd-n64.c

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

0 commit comments

Comments
 (0)