diff options
Diffstat (limited to 'sound/sh/aica.c')
-rw-r--r-- | sound/sh/aica.c | 675 |
1 files changed, 675 insertions, 0 deletions
diff --git a/sound/sh/aica.c b/sound/sh/aica.c new file mode 100644 index 000000000000..97bb86a58622 --- /dev/null +++ b/sound/sh/aica.c | |||
@@ -0,0 +1,675 @@ | |||
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 | |||
68 | static struct spu_work_holder { | ||
69 | struct work_struct spu_dma_work; | ||
70 | void *sspointer; | ||
71 | } spu_working; | ||
72 | |||
73 | static struct workqueue_struct *aica_queue; | ||
74 | |||
75 | /* Simple platform device */ | ||
76 | static struct platform_device *pd; | ||
77 | static struct resource aica_memory_space[2] = { | ||
78 | { | ||
79 | .name = "AICA ARM CONTROL", | ||
80 | .start = ARM_RESET_REGISTER, | ||
81 | .flags = IORESOURCE_MEM, | ||
82 | .end = ARM_RESET_REGISTER + 3, | ||
83 | }, | ||
84 | { | ||
85 | .name = "AICA Sound RAM", | ||
86 | .start = SPU_MEMORY_BASE, | ||
87 | .flags = IORESOURCE_MEM, | ||
88 | .end = SPU_MEMORY_BASE + 0x200000 - 1, | ||
89 | }, | ||
90 | }; | ||
91 | |||
92 | /* SPU specific functions */ | ||
93 | /* spu_write_wait - wait for G2-SH FIFO to clear */ | ||
94 | static void spu_write_wait(void) | ||
95 | { | ||
96 | int time_count; | ||
97 | time_count = 0; | ||
98 | while (1) { | ||
99 | if (!(readl(G2_FIFO) & 0x11)) | ||
100 | break; | ||
101 | /* To ensure hardware failure doesn't wedge kernel */ | ||
102 | time_count++; | ||
103 | if (time_count > 0x10000) | ||
104 | { | ||
105 | snd_printk("WARNING: G2 FIFO appears to be blocked.\n"); | ||
106 | break; | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | |||
111 | /* spu_memset - write to memory in SPU address space */ | ||
112 | static void spu_memset(u32 toi, u32 what, int length) | ||
113 | { | ||
114 | int i; | ||
115 | snd_assert(length % 4 == 0, return); | ||
116 | for (i = 0; i < length; i++) { | ||
117 | if (!(i % 8)) | ||
118 | spu_write_wait(); | ||
119 | writel(what, toi + SPU_MEMORY_BASE); | ||
120 | toi++; | ||
121 | } | ||
122 | } | ||
123 | |||
124 | /* spu_memload - write to SPU address space */ | ||
125 | static void spu_memload(u32 toi, void *from, int length) | ||
126 | { | ||
127 | u32 *froml = from; | ||
128 | u32 __iomem *to = (u32 __iomem *) (SPU_MEMORY_BASE + toi); | ||
129 | int i; | ||
130 | u32 val; | ||
131 | length = DIV_ROUND_UP(length, 4); | ||
132 | spu_write_wait(); | ||
133 | for (i = 0; i < length; i++) { | ||
134 | if (!(i % 8)) | ||
135 | spu_write_wait(); | ||
136 | val = *froml; | ||
137 | writel(val, to); | ||
138 | froml++; | ||
139 | to++; | ||
140 | } | ||
141 | } | ||
142 | |||
143 | /* spu_disable - set spu registers to stop sound output */ | ||
144 | static void spu_disable(void) | ||
145 | { | ||
146 | int i; | ||
147 | u32 regval; | ||
148 | spu_write_wait(); | ||
149 | regval = readl(ARM_RESET_REGISTER); | ||
150 | regval |= 1; | ||
151 | spu_write_wait(); | ||
152 | writel(regval, ARM_RESET_REGISTER); | ||
153 | for (i = 0; i < 64; i++) { | ||
154 | spu_write_wait(); | ||
155 | regval = readl(SPU_REGISTER_BASE + (i * 0x80)); | ||
156 | regval = (regval & ~0x4000) | 0x8000; | ||
157 | spu_write_wait(); | ||
158 | writel(regval, SPU_REGISTER_BASE + (i * 0x80)); | ||
159 | } | ||
160 | } | ||
161 | |||
162 | /* spu_enable - set spu registers to enable sound output */ | ||
163 | static void spu_enable(void) | ||
164 | { | ||
165 | u32 regval = readl(ARM_RESET_REGISTER); | ||
166 | regval &= ~1; | ||
167 | spu_write_wait(); | ||
168 | writel(regval, ARM_RESET_REGISTER); | ||
169 | } | ||
170 | |||
171 | /* | ||
172 | * Halt the sound processor, clear the memory, | ||
173 | * load some default ARM7 code, and then restart ARM7 | ||
174 | */ | ||
175 | static void spu_reset(void) | ||
176 | { | ||
177 | spu_disable(); | ||
178 | spu_memset(0, 0, 0x200000 / 4); | ||
179 | /* Put ARM7 in endless loop */ | ||
180 | ctrl_outl(0xea000002, SPU_MEMORY_BASE); | ||
181 | spu_enable(); | ||
182 | } | ||
183 | |||
184 | /* aica_chn_start - write to spu to start playback */ | ||
185 | static void aica_chn_start(void) | ||
186 | { | ||
187 | spu_write_wait(); | ||
188 | writel(AICA_CMD_KICK | AICA_CMD_START, (u32 *) AICA_CONTROL_POINT); | ||
189 | } | ||
190 | |||
191 | /* aica_chn_halt - write to spu to halt playback */ | ||
192 | static void aica_chn_halt(void) | ||
193 | { | ||
194 | spu_write_wait(); | ||
195 | writel(AICA_CMD_KICK | AICA_CMD_STOP, (u32 *) AICA_CONTROL_POINT); | ||
196 | } | ||
197 | |||
198 | /* ALSA code below */ | ||
199 | static struct snd_pcm_hardware snd_pcm_aica_playback_hw = { | ||
200 | .info = (SNDRV_PCM_INFO_NONINTERLEAVED), | ||
201 | .formats = | ||
202 | (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | | ||
203 | SNDRV_PCM_FMTBIT_IMA_ADPCM), | ||
204 | .rates = SNDRV_PCM_RATE_8000_48000, | ||
205 | .rate_min = 8000, | ||
206 | .rate_max = 48000, | ||
207 | .channels_min = 1, | ||
208 | .channels_max = 2, | ||
209 | .buffer_bytes_max = AICA_BUFFER_SIZE, | ||
210 | .period_bytes_min = AICA_PERIOD_SIZE, | ||
211 | .period_bytes_max = AICA_PERIOD_SIZE, | ||
212 | .periods_min = AICA_PERIOD_NUMBER, | ||
213 | .periods_max = AICA_PERIOD_NUMBER, | ||
214 | }; | ||
215 | |||
216 | static int aica_dma_transfer(int channels, int buffer_size, | ||
217 | struct snd_pcm_substream *substream) | ||
218 | { | ||
219 | int q, err, period_offset; | ||
220 | struct snd_card_aica *dreamcastcard; | ||
221 | struct snd_pcm_runtime *runtime; | ||
222 | err = 0; | ||
223 | dreamcastcard = substream->pcm->private_data; | ||
224 | period_offset = dreamcastcard->clicks; | ||
225 | period_offset %= (AICA_PERIOD_NUMBER / channels); | ||
226 | runtime = substream->runtime; | ||
227 | for (q = 0; q < channels; q++) { | ||
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 | break; | ||
239 | dma_wait_for_completion(AICA_DMA_CHANNEL); | ||
240 | } | ||
241 | return err; | ||
242 | } | ||
243 | |||
244 | static void startup_aica(struct snd_card_aica *dreamcastcard) | ||
245 | { | ||
246 | spu_memload(AICA_CHANNEL0_CONTROL_OFFSET, | ||
247 | dreamcastcard->channel, | ||
248 | sizeof(struct aica_channel)); | ||
249 | aica_chn_start(); | ||
250 | } | ||
251 | |||
252 | static void run_spu_dma(struct work_struct *work) | ||
253 | { | ||
254 | int buffer_size; | ||
255 | struct snd_pcm_substream *substream; | ||
256 | struct snd_pcm_runtime *runtime; | ||
257 | struct snd_card_aica *dreamcastcard; | ||
258 | struct spu_work_holder *holder = container_of(work, struct spu_work_holder, spu_dma_work); | ||
259 | substream = holder-> sspointer; | ||
260 | dreamcastcard = substream->pcm->private_data; | ||
261 | runtime = substream->runtime; | ||
262 | if (unlikely(dreamcastcard->dma_check == 0)) { | ||
263 | buffer_size = frames_to_bytes(runtime, runtime->buffer_size); | ||
264 | if (runtime->channels > 1) | ||
265 | dreamcastcard->channel->flags |= 0x01; | ||
266 | aica_dma_transfer(runtime->channels, buffer_size, substream); | ||
267 | startup_aica(dreamcastcard); | ||
268 | dreamcastcard->clicks = | ||
269 | buffer_size / (AICA_PERIOD_SIZE * runtime->channels); | ||
270 | return; | ||
271 | } else { | ||
272 | aica_dma_transfer(runtime->channels, | ||
273 | AICA_PERIOD_SIZE * runtime->channels, | ||
274 | substream); | ||
275 | snd_pcm_period_elapsed(dreamcastcard->substream); | ||
276 | dreamcastcard->clicks++; | ||
277 | if (unlikely(dreamcastcard->clicks >= AICA_PERIOD_NUMBER)) | ||
278 | { | ||
279 | dreamcastcard->clicks %= AICA_PERIOD_NUMBER; | ||
280 | } | ||
281 | mod_timer(&dreamcastcard->timer, jiffies + 1); | ||
282 | } | ||
283 | } | ||
284 | |||
285 | static void aica_period_elapsed(unsigned long timer_var) | ||
286 | { | ||
287 | /*timer function - so cannot sleep */ | ||
288 | int play_period; | ||
289 | struct snd_pcm_runtime *runtime; | ||
290 | struct snd_pcm_substream *substream; | ||
291 | struct snd_card_aica *dreamcastcard; | ||
292 | substream = (struct snd_pcm_substream *)timer_var; | ||
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(&(dreamcastcard->timer), 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 | queue_work(aica_queue, &(spu_working.spu_dma_work)); | ||
311 | } | ||
312 | |||
313 | static void spu_begin_dma(struct snd_pcm_substream *substream) | ||
314 | { | ||
315 | /* Must be atomic */ | ||
316 | struct snd_card_aica *dreamcastcard; | ||
317 | struct snd_pcm_runtime *runtime; | ||
318 | runtime = substream->runtime; | ||
319 | dreamcastcard = substream->pcm->private_data; | ||
320 | /* Use queue to do the heavy lifting */ | ||
321 | spu_working.sspointer = substream; | ||
322 | INIT_WORK(&(spu_working.spu_dma_work), run_spu_dma); | ||
323 | queue_work(aica_queue, &(spu_working.spu_dma_work)); | ||
324 | /* Timer may already be running */ | ||
325 | if (unlikely(dreamcastcard->timer.data)) { | ||
326 | mod_timer(&dreamcastcard->timer, jiffies + 4); | ||
327 | return; | ||
328 | } | ||
329 | init_timer(&(dreamcastcard->timer)); | ||
330 | dreamcastcard->timer.data = (unsigned long)substream; | ||
331 | dreamcastcard->timer.function = aica_period_elapsed; | ||
332 | dreamcastcard->timer.expires = jiffies + 4; | ||
333 | add_timer(&(dreamcastcard->timer)); | ||
334 | } | ||
335 | |||
336 | static int snd_aicapcm_pcm_open(struct snd_pcm_substream | ||
337 | *substream) | ||
338 | { | ||
339 | struct snd_pcm_runtime *runtime; | ||
340 | struct aica_channel *channel; | ||
341 | struct snd_card_aica *dreamcastcard; | ||
342 | if (!enable) | ||
343 | return -ENOENT; | ||
344 | dreamcastcard = substream->pcm->private_data; | ||
345 | channel = kmalloc(sizeof(struct aica_channel), GFP_KERNEL); | ||
346 | if (!channel) | ||
347 | return -ENOMEM; | ||
348 | /* set defaults for channel */ | ||
349 | channel->sfmt = SM_8BIT; | ||
350 | channel->cmd = AICA_CMD_START; | ||
351 | channel->vol = dreamcastcard->master_volume; | ||
352 | channel->pan = 0x80; | ||
353 | channel->pos = 0; | ||
354 | channel->flags = 0; /* default to mono */ | ||
355 | dreamcastcard->channel = channel; | ||
356 | runtime = substream->runtime; | ||
357 | runtime->hw = snd_pcm_aica_playback_hw; | ||
358 | spu_enable(); | ||
359 | dreamcastcard->clicks = 0; | ||
360 | dreamcastcard->current_period = 0; | ||
361 | dreamcastcard->dma_check = 0; | ||
362 | return 0; | ||
363 | } | ||
364 | |||
365 | static int snd_aicapcm_pcm_close(struct snd_pcm_substream | ||
366 | *substream) | ||
367 | { | ||
368 | struct snd_card_aica *dreamcastcard = substream->pcm->private_data; | ||
369 | del_timer(&dreamcastcard->timer); | ||
370 | kfree(dreamcastcard->channel); | ||
371 | spu_disable(); | ||
372 | return 0; | ||
373 | } | ||
374 | |||
375 | static int snd_aicapcm_pcm_hw_free(struct snd_pcm_substream | ||
376 | *substream) | ||
377 | { | ||
378 | /* Free the DMA buffer */ | ||
379 | return snd_pcm_lib_free_pages(substream); | ||
380 | } | ||
381 | |||
382 | static int snd_aicapcm_pcm_hw_params(struct snd_pcm_substream | ||
383 | *substream, struct snd_pcm_hw_params | ||
384 | *hw_params) | ||
385 | { | ||
386 | /* Allocate a DMA buffer using ALSA built-ins */ | ||
387 | return | ||
388 | snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); | ||
389 | } | ||
390 | |||
391 | static int snd_aicapcm_pcm_prepare(struct snd_pcm_substream | ||
392 | *substream) | ||
393 | { | ||
394 | struct snd_card_aica *dreamcastcard = substream->pcm->private_data; | ||
395 | if ((substream->runtime)->format == SNDRV_PCM_FORMAT_S16_LE) | ||
396 | dreamcastcard->channel->sfmt = SM_16BIT; | ||
397 | dreamcastcard->channel->freq = substream->runtime->rate; | ||
398 | dreamcastcard->substream = substream; | ||
399 | return 0; | ||
400 | } | ||
401 | |||
402 | static int snd_aicapcm_pcm_trigger(struct snd_pcm_substream | ||
403 | *substream, int cmd) | ||
404 | { | ||
405 | struct snd_card_aica *dreamcastcard; | ||
406 | switch (cmd) { | ||
407 | case SNDRV_PCM_TRIGGER_START: | ||
408 | spu_begin_dma(substream); | ||
409 | break; | ||
410 | case SNDRV_PCM_TRIGGER_STOP: | ||
411 | dreamcastcard = substream->pcm->private_data; | ||
412 | if (dreamcastcard->timer.data) | ||
413 | del_timer(&dreamcastcard->timer); | ||
414 | aica_chn_halt(); | ||
415 | break; | ||
416 | default: | ||
417 | return -EINVAL; | ||
418 | } | ||
419 | return 0; | ||
420 | } | ||
421 | |||
422 | static unsigned long snd_aicapcm_pcm_pointer(struct snd_pcm_substream | ||
423 | *substream) | ||
424 | { | ||
425 | return readl(AICA_CONTROL_CHANNEL_SAMPLE_NUMBER); | ||
426 | } | ||
427 | |||
428 | static struct snd_pcm_ops snd_aicapcm_playback_ops = { | ||
429 | .open = snd_aicapcm_pcm_open, | ||
430 | .close = snd_aicapcm_pcm_close, | ||
431 | .ioctl = snd_pcm_lib_ioctl, | ||
432 | .hw_params = snd_aicapcm_pcm_hw_params, | ||
433 | .hw_free = snd_aicapcm_pcm_hw_free, | ||
434 | .prepare = snd_aicapcm_pcm_prepare, | ||
435 | .trigger = snd_aicapcm_pcm_trigger, | ||
436 | .pointer = snd_aicapcm_pcm_pointer, | ||
437 | }; | ||
438 | |||
439 | /* TO DO: set up to handle more than one pcm instance */ | ||
440 | static int __init snd_aicapcmchip(struct snd_card_aica | ||
441 | *dreamcastcard, int pcm_index) | ||
442 | { | ||
443 | struct snd_pcm *pcm; | ||
444 | int err; | ||
445 | /* AICA has no capture ability */ | ||
446 | err = | ||
447 | snd_pcm_new(dreamcastcard->card, "AICA PCM", pcm_index, 1, 0, &pcm); | ||
448 | if (unlikely(err < 0)) | ||
449 | return err; | ||
450 | pcm->private_data = dreamcastcard; | ||
451 | strcpy(pcm->name, "AICA PCM"); | ||
452 | snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, | ||
453 | &snd_aicapcm_playback_ops); | ||
454 | /* Allocate the DMA buffers */ | ||
455 | err = | ||
456 | snd_pcm_lib_preallocate_pages_for_all(pcm, | ||
457 | SNDRV_DMA_TYPE_CONTINUOUS, | ||
458 | snd_dma_continuous_data | ||
459 | (GFP_KERNEL), | ||
460 | AICA_BUFFER_SIZE, | ||
461 | AICA_BUFFER_SIZE); | ||
462 | return err; | ||
463 | } | ||
464 | |||
465 | /* Mixer controls */ | ||
466 | static int aica_pcmswitch_info(struct snd_kcontrol *kcontrol, | ||
467 | struct snd_ctl_elem_info *uinfo) | ||
468 | { | ||
469 | uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; | ||
470 | uinfo->count = 1; | ||
471 | uinfo->value.integer.min = 0; | ||
472 | uinfo->value.integer.max = 1; | ||
473 | return 0; | ||
474 | } | ||
475 | |||
476 | static int aica_pcmswitch_get(struct snd_kcontrol *kcontrol, | ||
477 | struct snd_ctl_elem_value *ucontrol) | ||
478 | { | ||
479 | ucontrol->value.integer.value[0] = 1; /* TO DO: Fix me */ | ||
480 | return 0; | ||
481 | } | ||
482 | |||
483 | static int aica_pcmswitch_put(struct snd_kcontrol *kcontrol, | ||
484 | struct snd_ctl_elem_value *ucontrol) | ||
485 | { | ||
486 | if (ucontrol->value.integer.value[0] == 1) | ||
487 | return 0; /* TO DO: Fix me */ | ||
488 | else | ||
489 | aica_chn_halt(); | ||
490 | return 0; | ||
491 | } | ||
492 | |||
493 | static int aica_pcmvolume_info(struct snd_kcontrol *kcontrol, | ||
494 | struct snd_ctl_elem_info *uinfo) | ||
495 | { | ||
496 | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | ||
497 | uinfo->count = 1; | ||
498 | uinfo->value.integer.min = 0; | ||
499 | uinfo->value.integer.max = 0xFF; | ||
500 | return 0; | ||
501 | } | ||
502 | |||
503 | static int aica_pcmvolume_get(struct snd_kcontrol *kcontrol, | ||
504 | struct snd_ctl_elem_value *ucontrol) | ||
505 | { | ||
506 | struct snd_card_aica *dreamcastcard; | ||
507 | dreamcastcard = kcontrol->private_data; | ||
508 | if (unlikely(!dreamcastcard->channel)) | ||
509 | return -ETXTBSY; /* we've not yet been set up */ | ||
510 | ucontrol->value.integer.value[0] = dreamcastcard->channel->vol; | ||
511 | return 0; | ||
512 | } | ||
513 | |||
514 | static int aica_pcmvolume_put(struct snd_kcontrol *kcontrol, | ||
515 | struct snd_ctl_elem_value *ucontrol) | ||
516 | { | ||
517 | struct snd_card_aica *dreamcastcard; | ||
518 | dreamcastcard = kcontrol->private_data; | ||
519 | if (unlikely(!dreamcastcard->channel)) | ||
520 | return -ETXTBSY; | ||
521 | if (unlikely(dreamcastcard->channel->vol == | ||
522 | ucontrol->value.integer.value[0])) | ||
523 | return 0; | ||
524 | dreamcastcard->channel->vol = ucontrol->value.integer.value[0]; | ||
525 | dreamcastcard->master_volume = ucontrol->value.integer.value[0]; | ||
526 | spu_memload(AICA_CHANNEL0_CONTROL_OFFSET, | ||
527 | dreamcastcard->channel, | ||
528 | sizeof(struct aica_channel)); | ||
529 | |||
530 | return 1; | ||
531 | } | ||
532 | |||
533 | static struct snd_kcontrol_new snd_aica_pcmswitch_control __devinitdata = { | ||
534 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
535 | .name = "PCM Playback Switch", | ||
536 | .index = 0, | ||
537 | .info = aica_pcmswitch_info, | ||
538 | .get = aica_pcmswitch_get, | ||
539 | .put = aica_pcmswitch_put | ||
540 | }; | ||
541 | |||
542 | static struct snd_kcontrol_new snd_aica_pcmvolume_control __devinitdata = { | ||
543 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
544 | .name = "PCM Playback Volume", | ||
545 | .index = 0, | ||
546 | .info = aica_pcmvolume_info, | ||
547 | .get = aica_pcmvolume_get, | ||
548 | .put = aica_pcmvolume_put | ||
549 | }; | ||
550 | |||
551 | static int load_aica_firmware(void) | ||
552 | { | ||
553 | int err; | ||
554 | const struct firmware *fw_entry; | ||
555 | spu_reset(); | ||
556 | err = request_firmware(&fw_entry, "aica_firmware.bin", &pd->dev); | ||
557 | if (unlikely(err)) | ||
558 | return err; | ||
559 | /* write firware into memory */ | ||
560 | spu_disable(); | ||
561 | spu_memload(0, fw_entry->data, fw_entry->size); | ||
562 | spu_enable(); | ||
563 | release_firmware(fw_entry); | ||
564 | return err; | ||
565 | } | ||
566 | |||
567 | static int __devinit add_aicamixer_controls(struct snd_card_aica | ||
568 | *dreamcastcard) | ||
569 | { | ||
570 | int err; | ||
571 | err = snd_ctl_add | ||
572 | (dreamcastcard->card, | ||
573 | snd_ctl_new1(&snd_aica_pcmvolume_control, dreamcastcard)); | ||
574 | if (unlikely(err < 0)) | ||
575 | return err; | ||
576 | err = snd_ctl_add | ||
577 | (dreamcastcard->card, | ||
578 | snd_ctl_new1(&snd_aica_pcmswitch_control, dreamcastcard)); | ||
579 | if (unlikely(err < 0)) | ||
580 | return err; | ||
581 | return 0; | ||
582 | } | ||
583 | |||
584 | static int snd_aica_remove(struct platform_device *devptr) | ||
585 | { | ||
586 | struct snd_card_aica *dreamcastcard; | ||
587 | dreamcastcard = platform_get_drvdata(devptr); | ||
588 | if (unlikely(!dreamcastcard)) | ||
589 | return -ENODEV; | ||
590 | snd_card_free(dreamcastcard->card); | ||
591 | kfree(dreamcastcard); | ||
592 | platform_set_drvdata(devptr, NULL); | ||
593 | return 0; | ||
594 | } | ||
595 | |||
596 | static int __init snd_aica_probe(struct platform_device *devptr) | ||
597 | { | ||
598 | int err; | ||
599 | struct snd_card_aica *dreamcastcard; | ||
600 | dreamcastcard = kmalloc(sizeof(struct snd_card_aica), GFP_KERNEL); | ||
601 | if (unlikely(!dreamcastcard)) | ||
602 | return -ENOMEM; | ||
603 | dreamcastcard->card = | ||
604 | snd_card_new(index, SND_AICA_DRIVER, THIS_MODULE, 0); | ||
605 | if (unlikely(!dreamcastcard->card)) { | ||
606 | kfree(dreamcastcard); | ||
607 | return -ENODEV; | ||
608 | } | ||
609 | strcpy(dreamcastcard->card->driver, "snd_aica"); | ||
610 | strcpy(dreamcastcard->card->shortname, SND_AICA_DRIVER); | ||
611 | strcpy(dreamcastcard->card->longname, | ||
612 | "Yamaha AICA Super Intelligent Sound Processor for SEGA Dreamcast"); | ||
613 | /* Load the PCM 'chip' */ | ||
614 | err = snd_aicapcmchip(dreamcastcard, 0); | ||
615 | if (unlikely(err < 0)) | ||
616 | goto freedreamcast; | ||
617 | snd_card_set_dev(dreamcastcard->card, &devptr->dev); | ||
618 | dreamcastcard->timer.data = 0; | ||
619 | dreamcastcard->channel = NULL; | ||
620 | /* Add basic controls */ | ||
621 | err = add_aicamixer_controls(dreamcastcard); | ||
622 | if (unlikely(err < 0)) | ||
623 | goto freedreamcast; | ||
624 | /* Register the card with ALSA subsystem */ | ||
625 | err = snd_card_register(dreamcastcard->card); | ||
626 | if (unlikely(err < 0)) | ||
627 | goto freedreamcast; | ||
628 | platform_set_drvdata(devptr, dreamcastcard); | ||
629 | aica_queue = create_workqueue(CARD_NAME); | ||
630 | if (unlikely(!aica_queue)) | ||
631 | goto freedreamcast; | ||
632 | snd_printk | ||
633 | ("ALSA Driver for Yamaha AICA Super Intelligent Sound Processor\n"); | ||
634 | return 0; | ||
635 | freedreamcast: | ||
636 | snd_card_free(dreamcastcard->card); | ||
637 | kfree(dreamcastcard); | ||
638 | return err; | ||
639 | } | ||
640 | |||
641 | static struct platform_driver snd_aica_driver = { | ||
642 | .probe = snd_aica_probe, | ||
643 | .remove = snd_aica_remove, | ||
644 | .driver = { | ||
645 | .name = SND_AICA_DRIVER}, | ||
646 | }; | ||
647 | |||
648 | static int __init aica_init(void) | ||
649 | { | ||
650 | int err; | ||
651 | err = platform_driver_register(&snd_aica_driver); | ||
652 | if (unlikely(err < 0)) | ||
653 | return err; | ||
654 | pd = platform_device_register_simple(SND_AICA_DRIVER, -1, | ||
655 | aica_memory_space, 2); | ||
656 | if (unlikely(IS_ERR(pd))) { | ||
657 | platform_driver_unregister(&snd_aica_driver); | ||
658 | return PTR_ERR(pd); | ||
659 | } | ||
660 | /* Load the firmware */ | ||
661 | return load_aica_firmware(); | ||
662 | } | ||
663 | |||
664 | static void __exit aica_exit(void) | ||
665 | { | ||
666 | /* Destroy the aica kernel thread */ | ||
667 | destroy_workqueue(aica_queue); | ||
668 | platform_device_unregister(pd); | ||
669 | platform_driver_unregister(&snd_aica_driver); | ||
670 | /* Kill any sound still playing and reset ARM7 to safe state */ | ||
671 | spu_reset(); | ||
672 | } | ||
673 | |||
674 | module_init(aica_init); | ||
675 | module_exit(aica_exit); | ||