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