1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * |
4 | * Copyright Adrian McMenamin 2005, 2006, 2007 |
5 | * <adrian@mcmen.demon.co.uk> |
6 | * Requires firmware (BSD licenced) available from: |
7 | * http://linuxdc.cvs.sourceforge.net/linuxdc/linux-sh-dc/sound/oss/aica/firmware/ |
8 | * or the maintainer |
9 | */ |
10 | |
11 | #include <linux/init.h> |
12 | #include <linux/jiffies.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/time.h> |
15 | #include <linux/wait.h> |
16 | #include <linux/module.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/firmware.h> |
19 | #include <linux/timer.h> |
20 | #include <linux/delay.h> |
21 | #include <linux/workqueue.h> |
22 | #include <linux/io.h> |
23 | #include <sound/core.h> |
24 | #include <sound/control.h> |
25 | #include <sound/pcm.h> |
26 | #include <sound/initval.h> |
27 | #include <sound/info.h> |
28 | #include <asm/dma.h> |
29 | #include <mach/sysasic.h> |
30 | #include "aica.h" |
31 | |
32 | MODULE_AUTHOR("Adrian McMenamin <adrian@mcmen.demon.co.uk>" ); |
33 | MODULE_DESCRIPTION("Dreamcast AICA sound (pcm) driver" ); |
34 | MODULE_LICENSE("GPL" ); |
35 | MODULE_FIRMWARE("aica_firmware.bin" ); |
36 | |
37 | /* module parameters */ |
38 | #define CARD_NAME "AICA" |
39 | static int index = -1; |
40 | static char *id; |
41 | static bool enable = 1; |
42 | module_param(index, int, 0444); |
43 | MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard." ); |
44 | module_param(id, charp, 0444); |
45 | MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard." ); |
46 | module_param(enable, bool, 0644); |
47 | MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard." ); |
48 | |
49 | /* Simple platform device */ |
50 | static struct platform_device *pd; |
51 | static struct resource aica_memory_space[2] = { |
52 | { |
53 | .name = "AICA ARM CONTROL" , |
54 | .start = ARM_RESET_REGISTER, |
55 | .flags = IORESOURCE_MEM, |
56 | .end = ARM_RESET_REGISTER + 3, |
57 | }, |
58 | { |
59 | .name = "AICA Sound RAM" , |
60 | .start = SPU_MEMORY_BASE, |
61 | .flags = IORESOURCE_MEM, |
62 | .end = SPU_MEMORY_BASE + 0x200000 - 1, |
63 | }, |
64 | }; |
65 | |
66 | /* SPU specific functions */ |
67 | /* spu_write_wait - wait for G2-SH FIFO to clear */ |
68 | static void spu_write_wait(void) |
69 | { |
70 | int time_count; |
71 | time_count = 0; |
72 | while (1) { |
73 | if (!(readl(G2_FIFO) & 0x11)) |
74 | break; |
75 | /* To ensure hardware failure doesn't wedge kernel */ |
76 | time_count++; |
77 | if (time_count > 0x10000) { |
78 | snd_printk |
79 | ("WARNING: G2 FIFO appears to be blocked.\n" ); |
80 | break; |
81 | } |
82 | } |
83 | } |
84 | |
85 | /* spu_memset - write to memory in SPU address space */ |
86 | static void spu_memset(u32 toi, u32 what, int length) |
87 | { |
88 | int i; |
89 | unsigned long flags; |
90 | if (snd_BUG_ON(length % 4)) |
91 | return; |
92 | for (i = 0; i < length; i++) { |
93 | if (!(i % 8)) |
94 | spu_write_wait(); |
95 | local_irq_save(flags); |
96 | writel(val: what, addr: toi + SPU_MEMORY_BASE); |
97 | local_irq_restore(flags); |
98 | toi++; |
99 | } |
100 | } |
101 | |
102 | /* spu_memload - write to SPU address space */ |
103 | static void spu_memload(u32 toi, const void *from, int length) |
104 | { |
105 | unsigned long flags; |
106 | const u32 *froml = from; |
107 | u32 __iomem *to = (u32 __iomem *) (SPU_MEMORY_BASE + toi); |
108 | int i; |
109 | u32 val; |
110 | length = DIV_ROUND_UP(length, 4); |
111 | spu_write_wait(); |
112 | for (i = 0; i < length; i++) { |
113 | if (!(i % 8)) |
114 | spu_write_wait(); |
115 | val = *froml; |
116 | local_irq_save(flags); |
117 | writel(val, addr: to); |
118 | local_irq_restore(flags); |
119 | froml++; |
120 | to++; |
121 | } |
122 | } |
123 | |
124 | /* spu_disable - set spu registers to stop sound output */ |
125 | static void spu_disable(void) |
126 | { |
127 | int i; |
128 | unsigned long flags; |
129 | u32 regval; |
130 | spu_write_wait(); |
131 | regval = readl(ARM_RESET_REGISTER); |
132 | regval |= 1; |
133 | spu_write_wait(); |
134 | local_irq_save(flags); |
135 | writel(val: regval, ARM_RESET_REGISTER); |
136 | local_irq_restore(flags); |
137 | for (i = 0; i < 64; i++) { |
138 | spu_write_wait(); |
139 | regval = readl(SPU_REGISTER_BASE + (i * 0x80)); |
140 | regval = (regval & ~0x4000) | 0x8000; |
141 | spu_write_wait(); |
142 | local_irq_save(flags); |
143 | writel(val: regval, SPU_REGISTER_BASE + (i * 0x80)); |
144 | local_irq_restore(flags); |
145 | } |
146 | } |
147 | |
148 | /* spu_enable - set spu registers to enable sound output */ |
149 | static void spu_enable(void) |
150 | { |
151 | unsigned long flags; |
152 | u32 regval = readl(ARM_RESET_REGISTER); |
153 | regval &= ~1; |
154 | spu_write_wait(); |
155 | local_irq_save(flags); |
156 | writel(val: regval, ARM_RESET_REGISTER); |
157 | local_irq_restore(flags); |
158 | } |
159 | |
160 | /* |
161 | * Halt the sound processor, clear the memory, |
162 | * load some default ARM7 code, and then restart ARM7 |
163 | */ |
164 | static void spu_reset(void) |
165 | { |
166 | unsigned long flags; |
167 | spu_disable(); |
168 | spu_memset(toi: 0, what: 0, length: 0x200000 / 4); |
169 | /* Put ARM7 in endless loop */ |
170 | local_irq_save(flags); |
171 | __raw_writel(val: 0xea000002, SPU_MEMORY_BASE); |
172 | local_irq_restore(flags); |
173 | spu_enable(); |
174 | } |
175 | |
176 | /* aica_chn_start - write to spu to start playback */ |
177 | static void aica_chn_start(void) |
178 | { |
179 | unsigned long flags; |
180 | spu_write_wait(); |
181 | local_irq_save(flags); |
182 | writel(AICA_CMD_KICK | AICA_CMD_START, addr: (u32 *) AICA_CONTROL_POINT); |
183 | local_irq_restore(flags); |
184 | } |
185 | |
186 | /* aica_chn_halt - write to spu to halt playback */ |
187 | static void aica_chn_halt(void) |
188 | { |
189 | unsigned long flags; |
190 | spu_write_wait(); |
191 | local_irq_save(flags); |
192 | writel(AICA_CMD_KICK | AICA_CMD_STOP, addr: (u32 *) AICA_CONTROL_POINT); |
193 | local_irq_restore(flags); |
194 | } |
195 | |
196 | /* ALSA code below */ |
197 | static const struct snd_pcm_hardware snd_pcm_aica_playback_hw = { |
198 | .info = (SNDRV_PCM_INFO_NONINTERLEAVED), |
199 | .formats = |
200 | (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | |
201 | SNDRV_PCM_FMTBIT_IMA_ADPCM), |
202 | .rates = SNDRV_PCM_RATE_8000_48000, |
203 | .rate_min = 8000, |
204 | .rate_max = 48000, |
205 | .channels_min = 1, |
206 | .channels_max = 2, |
207 | .buffer_bytes_max = AICA_BUFFER_SIZE, |
208 | .period_bytes_min = AICA_PERIOD_SIZE, |
209 | .period_bytes_max = AICA_PERIOD_SIZE, |
210 | .periods_min = AICA_PERIOD_NUMBER, |
211 | .periods_max = AICA_PERIOD_NUMBER, |
212 | }; |
213 | |
214 | static int aica_dma_transfer(int channels, int buffer_size, |
215 | struct snd_pcm_substream *substream) |
216 | { |
217 | int q, err, period_offset; |
218 | struct snd_card_aica *dreamcastcard; |
219 | struct snd_pcm_runtime *runtime; |
220 | unsigned long flags; |
221 | err = 0; |
222 | dreamcastcard = substream->pcm->private_data; |
223 | period_offset = dreamcastcard->clicks; |
224 | period_offset %= (AICA_PERIOD_NUMBER / channels); |
225 | runtime = substream->runtime; |
226 | for (q = 0; q < channels; q++) { |
227 | local_irq_save(flags); |
228 | err = dma_xfer(AICA_DMA_CHANNEL, |
229 | (unsigned long) (runtime->dma_area + |
230 | (AICA_BUFFER_SIZE * q) / |
231 | channels + |
232 | AICA_PERIOD_SIZE * |
233 | period_offset), |
234 | AICA_CHANNEL0_OFFSET + q * CHANNEL_OFFSET + |
235 | AICA_PERIOD_SIZE * period_offset, |
236 | buffer_size / channels, AICA_DMA_MODE); |
237 | if (unlikely(err < 0)) { |
238 | local_irq_restore(flags); |
239 | break; |
240 | } |
241 | dma_wait_for_completion(AICA_DMA_CHANNEL); |
242 | local_irq_restore(flags); |
243 | } |
244 | return err; |
245 | } |
246 | |
247 | static void startup_aica(struct snd_card_aica *dreamcastcard) |
248 | { |
249 | spu_memload(AICA_CHANNEL0_CONTROL_OFFSET, |
250 | from: dreamcastcard->channel, length: sizeof(struct aica_channel)); |
251 | aica_chn_start(); |
252 | } |
253 | |
254 | static void run_spu_dma(struct work_struct *work) |
255 | { |
256 | int buffer_size; |
257 | struct snd_pcm_runtime *runtime; |
258 | struct snd_card_aica *dreamcastcard; |
259 | dreamcastcard = |
260 | container_of(work, struct snd_card_aica, spu_dma_work); |
261 | runtime = dreamcastcard->substream->runtime; |
262 | if (unlikely(dreamcastcard->dma_check == 0)) { |
263 | buffer_size = |
264 | frames_to_bytes(runtime, size: runtime->buffer_size); |
265 | if (runtime->channels > 1) |
266 | dreamcastcard->channel->flags |= 0x01; |
267 | aica_dma_transfer(channels: runtime->channels, buffer_size, |
268 | substream: dreamcastcard->substream); |
269 | startup_aica(dreamcastcard); |
270 | dreamcastcard->clicks = |
271 | buffer_size / (AICA_PERIOD_SIZE * runtime->channels); |
272 | return; |
273 | } else { |
274 | aica_dma_transfer(channels: runtime->channels, |
275 | AICA_PERIOD_SIZE * runtime->channels, |
276 | substream: dreamcastcard->substream); |
277 | snd_pcm_period_elapsed(substream: dreamcastcard->substream); |
278 | dreamcastcard->clicks++; |
279 | if (unlikely(dreamcastcard->clicks >= AICA_PERIOD_NUMBER)) |
280 | dreamcastcard->clicks %= AICA_PERIOD_NUMBER; |
281 | mod_timer(timer: &dreamcastcard->timer, expires: jiffies + 1); |
282 | } |
283 | } |
284 | |
285 | static void aica_period_elapsed(struct timer_list *t) |
286 | { |
287 | struct snd_card_aica *dreamcastcard = from_timer(dreamcastcard, |
288 | t, timer); |
289 | struct snd_pcm_substream *substream = dreamcastcard->substream; |
290 | /*timer function - so cannot sleep */ |
291 | int play_period; |
292 | struct snd_pcm_runtime *runtime; |
293 | runtime = substream->runtime; |
294 | dreamcastcard = substream->pcm->private_data; |
295 | /* Have we played out an additional period? */ |
296 | play_period = |
297 | frames_to_bytes(runtime, |
298 | readl |
299 | (AICA_CONTROL_CHANNEL_SAMPLE_NUMBER)) / |
300 | AICA_PERIOD_SIZE; |
301 | if (play_period == dreamcastcard->current_period) { |
302 | /* reschedule the timer */ |
303 | mod_timer(timer: &(dreamcastcard->timer), expires: jiffies + 1); |
304 | return; |
305 | } |
306 | if (runtime->channels > 1) |
307 | dreamcastcard->current_period = play_period; |
308 | if (unlikely(dreamcastcard->dma_check == 0)) |
309 | dreamcastcard->dma_check = 1; |
310 | schedule_work(work: &(dreamcastcard->spu_dma_work)); |
311 | } |
312 | |
313 | static void spu_begin_dma(struct snd_pcm_substream *substream) |
314 | { |
315 | struct snd_card_aica *dreamcastcard; |
316 | struct snd_pcm_runtime *runtime; |
317 | runtime = substream->runtime; |
318 | dreamcastcard = substream->pcm->private_data; |
319 | /*get the queue to do the work */ |
320 | schedule_work(work: &(dreamcastcard->spu_dma_work)); |
321 | mod_timer(timer: &dreamcastcard->timer, expires: jiffies + 4); |
322 | } |
323 | |
324 | static int snd_aicapcm_pcm_open(struct snd_pcm_substream |
325 | *substream) |
326 | { |
327 | struct snd_pcm_runtime *runtime; |
328 | struct aica_channel *channel; |
329 | struct snd_card_aica *dreamcastcard; |
330 | if (!enable) |
331 | return -ENOENT; |
332 | dreamcastcard = substream->pcm->private_data; |
333 | channel = kmalloc(size: sizeof(struct aica_channel), GFP_KERNEL); |
334 | if (!channel) |
335 | return -ENOMEM; |
336 | /* set defaults for channel */ |
337 | channel->sfmt = SM_8BIT; |
338 | channel->cmd = AICA_CMD_START; |
339 | channel->vol = dreamcastcard->master_volume; |
340 | channel->pan = 0x80; |
341 | channel->pos = 0; |
342 | channel->flags = 0; /* default to mono */ |
343 | dreamcastcard->channel = channel; |
344 | runtime = substream->runtime; |
345 | runtime->hw = snd_pcm_aica_playback_hw; |
346 | spu_enable(); |
347 | dreamcastcard->clicks = 0; |
348 | dreamcastcard->current_period = 0; |
349 | dreamcastcard->dma_check = 0; |
350 | return 0; |
351 | } |
352 | |
353 | static int snd_aicapcm_pcm_close(struct snd_pcm_substream |
354 | *substream) |
355 | { |
356 | struct snd_card_aica *dreamcastcard = substream->pcm->private_data; |
357 | flush_work(work: &(dreamcastcard->spu_dma_work)); |
358 | del_timer(timer: &dreamcastcard->timer); |
359 | dreamcastcard->substream = NULL; |
360 | kfree(objp: dreamcastcard->channel); |
361 | spu_disable(); |
362 | return 0; |
363 | } |
364 | |
365 | static int snd_aicapcm_pcm_prepare(struct snd_pcm_substream |
366 | *substream) |
367 | { |
368 | struct snd_card_aica *dreamcastcard = substream->pcm->private_data; |
369 | if ((substream->runtime)->format == SNDRV_PCM_FORMAT_S16_LE) |
370 | dreamcastcard->channel->sfmt = SM_16BIT; |
371 | dreamcastcard->channel->freq = substream->runtime->rate; |
372 | dreamcastcard->substream = substream; |
373 | return 0; |
374 | } |
375 | |
376 | static int snd_aicapcm_pcm_trigger(struct snd_pcm_substream |
377 | *substream, int cmd) |
378 | { |
379 | switch (cmd) { |
380 | case SNDRV_PCM_TRIGGER_START: |
381 | spu_begin_dma(substream); |
382 | break; |
383 | case SNDRV_PCM_TRIGGER_STOP: |
384 | aica_chn_halt(); |
385 | break; |
386 | default: |
387 | return -EINVAL; |
388 | } |
389 | return 0; |
390 | } |
391 | |
392 | static unsigned long snd_aicapcm_pcm_pointer(struct snd_pcm_substream |
393 | *substream) |
394 | { |
395 | return readl(AICA_CONTROL_CHANNEL_SAMPLE_NUMBER); |
396 | } |
397 | |
398 | static const struct snd_pcm_ops snd_aicapcm_playback_ops = { |
399 | .open = snd_aicapcm_pcm_open, |
400 | .close = snd_aicapcm_pcm_close, |
401 | .prepare = snd_aicapcm_pcm_prepare, |
402 | .trigger = snd_aicapcm_pcm_trigger, |
403 | .pointer = snd_aicapcm_pcm_pointer, |
404 | }; |
405 | |
406 | /* TO DO: set up to handle more than one pcm instance */ |
407 | static int __init snd_aicapcmchip(struct snd_card_aica |
408 | *dreamcastcard, int pcm_index) |
409 | { |
410 | struct snd_pcm *pcm; |
411 | int err; |
412 | /* AICA has no capture ability */ |
413 | err = |
414 | snd_pcm_new(card: dreamcastcard->card, id: "AICA PCM" , device: pcm_index, playback_count: 1, capture_count: 0, |
415 | rpcm: &pcm); |
416 | if (unlikely(err < 0)) |
417 | return err; |
418 | pcm->private_data = dreamcastcard; |
419 | strcpy(p: pcm->name, q: "AICA PCM" ); |
420 | snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_PLAYBACK, |
421 | ops: &snd_aicapcm_playback_ops); |
422 | /* Allocate the DMA buffers */ |
423 | snd_pcm_set_managed_buffer_all(pcm, |
424 | SNDRV_DMA_TYPE_CONTINUOUS, |
425 | NULL, |
426 | AICA_BUFFER_SIZE, |
427 | AICA_BUFFER_SIZE); |
428 | return 0; |
429 | } |
430 | |
431 | /* Mixer controls */ |
432 | #define aica_pcmswitch_info snd_ctl_boolean_mono_info |
433 | |
434 | static int aica_pcmswitch_get(struct snd_kcontrol *kcontrol, |
435 | struct snd_ctl_elem_value *ucontrol) |
436 | { |
437 | ucontrol->value.integer.value[0] = 1; /* TO DO: Fix me */ |
438 | return 0; |
439 | } |
440 | |
441 | static int aica_pcmswitch_put(struct snd_kcontrol *kcontrol, |
442 | struct snd_ctl_elem_value *ucontrol) |
443 | { |
444 | if (ucontrol->value.integer.value[0] == 1) |
445 | return 0; /* TO DO: Fix me */ |
446 | else |
447 | aica_chn_halt(); |
448 | return 0; |
449 | } |
450 | |
451 | static int aica_pcmvolume_info(struct snd_kcontrol *kcontrol, |
452 | struct snd_ctl_elem_info *uinfo) |
453 | { |
454 | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
455 | uinfo->count = 1; |
456 | uinfo->value.integer.min = 0; |
457 | uinfo->value.integer.max = 0xFF; |
458 | return 0; |
459 | } |
460 | |
461 | static int aica_pcmvolume_get(struct snd_kcontrol *kcontrol, |
462 | struct snd_ctl_elem_value *ucontrol) |
463 | { |
464 | struct snd_card_aica *dreamcastcard; |
465 | dreamcastcard = kcontrol->private_data; |
466 | if (unlikely(!dreamcastcard->channel)) |
467 | return -ETXTBSY; /* we've not yet been set up */ |
468 | ucontrol->value.integer.value[0] = dreamcastcard->channel->vol; |
469 | return 0; |
470 | } |
471 | |
472 | static int aica_pcmvolume_put(struct snd_kcontrol *kcontrol, |
473 | struct snd_ctl_elem_value *ucontrol) |
474 | { |
475 | struct snd_card_aica *dreamcastcard; |
476 | unsigned int vol; |
477 | dreamcastcard = kcontrol->private_data; |
478 | if (unlikely(!dreamcastcard->channel)) |
479 | return -ETXTBSY; |
480 | vol = ucontrol->value.integer.value[0]; |
481 | if (vol > 0xff) |
482 | return -EINVAL; |
483 | if (unlikely(dreamcastcard->channel->vol == vol)) |
484 | return 0; |
485 | dreamcastcard->channel->vol = ucontrol->value.integer.value[0]; |
486 | dreamcastcard->master_volume = ucontrol->value.integer.value[0]; |
487 | spu_memload(AICA_CHANNEL0_CONTROL_OFFSET, |
488 | from: dreamcastcard->channel, length: sizeof(struct aica_channel)); |
489 | return 1; |
490 | } |
491 | |
492 | static const struct snd_kcontrol_new snd_aica_pcmswitch_control = { |
493 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
494 | .name = "PCM Playback Switch" , |
495 | .index = 0, |
496 | .info = aica_pcmswitch_info, |
497 | .get = aica_pcmswitch_get, |
498 | .put = aica_pcmswitch_put |
499 | }; |
500 | |
501 | static const struct snd_kcontrol_new snd_aica_pcmvolume_control = { |
502 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
503 | .name = "PCM Playback Volume" , |
504 | .index = 0, |
505 | .info = aica_pcmvolume_info, |
506 | .get = aica_pcmvolume_get, |
507 | .put = aica_pcmvolume_put |
508 | }; |
509 | |
510 | static int load_aica_firmware(void) |
511 | { |
512 | int err; |
513 | const struct firmware *fw_entry; |
514 | spu_reset(); |
515 | err = request_firmware(fw: &fw_entry, name: "aica_firmware.bin" , device: &pd->dev); |
516 | if (unlikely(err)) |
517 | return err; |
518 | /* write firmware into memory */ |
519 | spu_disable(); |
520 | spu_memload(toi: 0, from: fw_entry->data, length: fw_entry->size); |
521 | spu_enable(); |
522 | release_firmware(fw: fw_entry); |
523 | return err; |
524 | } |
525 | |
526 | static int add_aicamixer_controls(struct snd_card_aica *dreamcastcard) |
527 | { |
528 | int err; |
529 | err = snd_ctl_add |
530 | (card: dreamcastcard->card, |
531 | kcontrol: snd_ctl_new1(kcontrolnew: &snd_aica_pcmvolume_control, private_data: dreamcastcard)); |
532 | if (unlikely(err < 0)) |
533 | return err; |
534 | err = snd_ctl_add |
535 | (card: dreamcastcard->card, |
536 | kcontrol: snd_ctl_new1(kcontrolnew: &snd_aica_pcmswitch_control, private_data: dreamcastcard)); |
537 | if (unlikely(err < 0)) |
538 | return err; |
539 | return 0; |
540 | } |
541 | |
542 | static void snd_aica_remove(struct platform_device *devptr) |
543 | { |
544 | struct snd_card_aica *dreamcastcard; |
545 | dreamcastcard = platform_get_drvdata(pdev: devptr); |
546 | snd_card_free(card: dreamcastcard->card); |
547 | kfree(objp: dreamcastcard); |
548 | } |
549 | |
550 | static int snd_aica_probe(struct platform_device *devptr) |
551 | { |
552 | int err; |
553 | struct snd_card_aica *dreamcastcard; |
554 | dreamcastcard = kzalloc(size: sizeof(struct snd_card_aica), GFP_KERNEL); |
555 | if (unlikely(!dreamcastcard)) |
556 | return -ENOMEM; |
557 | err = snd_card_new(parent: &devptr->dev, idx: index, SND_AICA_DRIVER, |
558 | THIS_MODULE, extra_size: 0, card_ret: &dreamcastcard->card); |
559 | if (unlikely(err < 0)) { |
560 | kfree(objp: dreamcastcard); |
561 | return err; |
562 | } |
563 | strcpy(p: dreamcastcard->card->driver, q: "snd_aica" ); |
564 | strcpy(p: dreamcastcard->card->shortname, SND_AICA_DRIVER); |
565 | strcpy(p: dreamcastcard->card->longname, |
566 | q: "Yamaha AICA Super Intelligent Sound Processor for SEGA Dreamcast" ); |
567 | /* Prepare to use the queue */ |
568 | INIT_WORK(&(dreamcastcard->spu_dma_work), run_spu_dma); |
569 | timer_setup(&dreamcastcard->timer, aica_period_elapsed, 0); |
570 | /* Load the PCM 'chip' */ |
571 | err = snd_aicapcmchip(dreamcastcard, pcm_index: 0); |
572 | if (unlikely(err < 0)) |
573 | goto freedreamcast; |
574 | /* Add basic controls */ |
575 | err = add_aicamixer_controls(dreamcastcard); |
576 | if (unlikely(err < 0)) |
577 | goto freedreamcast; |
578 | /* Register the card with ALSA subsystem */ |
579 | err = snd_card_register(card: dreamcastcard->card); |
580 | if (unlikely(err < 0)) |
581 | goto freedreamcast; |
582 | platform_set_drvdata(pdev: devptr, data: dreamcastcard); |
583 | snd_printk |
584 | ("ALSA Driver for Yamaha AICA Super Intelligent Sound Processor\n" ); |
585 | return 0; |
586 | freedreamcast: |
587 | snd_card_free(card: dreamcastcard->card); |
588 | kfree(objp: dreamcastcard); |
589 | return err; |
590 | } |
591 | |
592 | static struct platform_driver snd_aica_driver = { |
593 | .probe = snd_aica_probe, |
594 | .remove_new = snd_aica_remove, |
595 | .driver = { |
596 | .name = SND_AICA_DRIVER, |
597 | }, |
598 | }; |
599 | |
600 | static int __init aica_init(void) |
601 | { |
602 | int err; |
603 | err = platform_driver_register(&snd_aica_driver); |
604 | if (unlikely(err < 0)) |
605 | return err; |
606 | pd = platform_device_register_simple(SND_AICA_DRIVER, id: -1, |
607 | res: aica_memory_space, num: 2); |
608 | if (IS_ERR(ptr: pd)) { |
609 | platform_driver_unregister(&snd_aica_driver); |
610 | return PTR_ERR(ptr: pd); |
611 | } |
612 | /* Load the firmware */ |
613 | return load_aica_firmware(); |
614 | } |
615 | |
616 | static void __exit aica_exit(void) |
617 | { |
618 | platform_device_unregister(pd); |
619 | platform_driver_unregister(&snd_aica_driver); |
620 | /* Kill any sound still playing and reset ARM7 to safe state */ |
621 | spu_reset(); |
622 | } |
623 | |
624 | module_init(aica_init); |
625 | module_exit(aica_exit); |
626 | |