]>
Commit | Line | Data |
---|---|---|
2cb7cef9 BS |
1 | From: Juergen Beisert <jbe@pengutronix.de> |
2 | Subject: RFC: MPC5200 PSC AC97 driver | |
3 | ||
4 | if someone is interested: Here the full patch to get sound support for | |
5 | MPC5200b and a current 2.6.25 kernel. | |
6 | ||
7 | Acked-by: Olaf Hering <olh@novell.com> | |
8 | ||
9 | --- | |
10 | arch/powerpc/include/asm/mpc52xx_psc.h | 4 | |
11 | arch/powerpc/kernel/prom_init.c | 3 | |
12 | sound/ppc/Kconfig | 15 | |
13 | sound/ppc/Makefile | 2 | |
14 | sound/ppc/mpc52xx_ac97.c | 807 +++++++++++++++++++++++++++++++++ | |
15 | 5 files changed, 831 insertions(+) | |
16 | ||
17 | --- a/arch/powerpc/include/asm/mpc52xx_psc.h | |
18 | +++ b/arch/powerpc/include/asm/mpc52xx_psc.h | |
19 | @@ -28,6 +28,10 @@ | |
20 | #define MPC52xx_PSC_MAXNUM 6 | |
21 | ||
22 | /* Programmable Serial Controller (PSC) status register bits */ | |
23 | +#define MPC52xx_PSC_SR_UNEX_RX 0x0001 | |
24 | +#define MPC52xx_PSC_SR_DATA_VAL 0x0002 | |
25 | +#define MPC52xx_PSC_SR_DATA_OVR 0x0004 | |
26 | +#define MPC52xx_PSC_SR_CMDSEND 0x0008 | |
27 | #define MPC52xx_PSC_SR_CDE 0x0080 | |
28 | #define MPC52xx_PSC_SR_RXRDY 0x0100 | |
29 | #define MPC52xx_PSC_SR_RXFULL 0x0200 | |
30 | --- a/arch/powerpc/kernel/prom_init.c | |
31 | +++ b/arch/powerpc/kernel/prom_init.c | |
32 | @@ -2189,6 +2189,7 @@ static void __init fixup_device_tree_efi | |
33 | ||
34 | static void __init fixup_device_tree_efika(void) | |
35 | { | |
36 | + int sound_cell[1] = { 1 }; | |
37 | int sound_irq[3] = { 2, 2, 0 }; | |
38 | int bcomm_irq[3*16] = { 3,0,0, 3,1,0, 3,2,0, 3,3,0, | |
39 | 3,4,0, 3,5,0, 3,6,0, 3,7,0, | |
40 | @@ -2244,6 +2245,8 @@ static void __init fixup_device_tree_efi | |
41 | prom_printf("Adding sound interrupts property\n"); | |
42 | prom_setprop(node, "/builtin/sound", "interrupts", | |
43 | sound_irq, sizeof(sound_irq)); | |
44 | + prom_setprop(node, "/builtin/sound", "cell-index", | |
45 | + sound_cell, sizeof(sound_cell)); | |
46 | } | |
47 | } | |
48 | ||
49 | --- a/sound/ppc/Kconfig | |
50 | +++ b/sound/ppc/Kconfig | |
51 | @@ -48,4 +48,19 @@ config SND_PS3_DEFAULT_START_DELAY | |
52 | depends on SND_PS3 | |
53 | default "2000" | |
54 | ||
55 | + | |
56 | +# ALSA ppc drivers | |
57 | + | |
58 | +menu "ALSA PPC devices" | |
59 | + depends on SND!=n && PPC | |
60 | + | |
61 | +config SND_PPC_MPC52xx_AC97 | |
62 | + tristate "Freescale MPC52xx AC97 interface support" | |
63 | + depends on SND && PPC_MPC52xx | |
64 | + select SND_AC97_CODEC | |
65 | + help | |
66 | + Say Y or M if you want to support any AC97 codec attached to | |
67 | + the Freescqle MPC52xx AC97 interface. | |
68 | +endmenu | |
69 | + | |
70 | endif # SND_PPC | |
71 | --- a/sound/ppc/Makefile | |
72 | +++ b/sound/ppc/Makefile | |
73 | @@ -4,7 +4,9 @@ | |
74 | # | |
75 | ||
76 | snd-powermac-objs := powermac.o pmac.o awacs.o burgundy.o daca.o tumbler.o keywest.o beep.o | |
77 | +snd-mpc52xx-ac97-objs := mpc52xx_ac97.o | |
78 | ||
79 | # Toplevel Module Dependency | |
80 | obj-$(CONFIG_SND_POWERMAC) += snd-powermac.o | |
81 | obj-$(CONFIG_SND_PS3) += snd_ps3.o | |
82 | +obj-$(CONFIG_SND_PPC_MPC52xx_AC97) += snd-mpc52xx-ac97.o | |
83 | --- /dev/null | |
84 | +++ b/sound/ppc/mpc52xx_ac97.c | |
85 | @@ -0,0 +1,807 @@ | |
86 | +/* | |
87 | + * Driver for the PSC of the Freescale MPC52xx configured as AC97 interface | |
88 | + * | |
89 | + * | |
90 | + * Copyright (C) 2006 Sylvain Munaut <tnt@246tNt.com> | |
91 | + * | |
92 | + * This file is licensed under the terms of the GNU General Public License | |
93 | + * version 2. This program is licensed "as is" without any warranty of any | |
94 | + * kind, whether express or implied. | |
95 | + */ | |
96 | +#include <linux/kernel.h> | |
97 | +#include <linux/module.h> | |
98 | +#include <linux/interrupt.h> | |
99 | +#include <linux/spinlock.h> | |
100 | + | |
101 | +#include <sound/core.h> | |
102 | +#include <sound/initval.h> | |
103 | +#include <sound/pcm.h> | |
104 | +#include <sound/pcm_params.h> | |
105 | +#include <sound/ac97_codec.h> | |
106 | + | |
107 | +#include <asm/of_platform.h> | |
108 | +#include <linux/dma-mapping.h> | |
109 | +#include <asm/mpc52xx_psc.h> | |
110 | + | |
111 | +#include <sysdev/bestcomm/bestcomm.h> | |
112 | +#include <sysdev/bestcomm/gen_bd.h> | |
113 | + | |
114 | + | |
115 | +#define DRV_NAME "mpc52xx-psc-ac97" | |
116 | + | |
117 | + | |
118 | +/* ======================================================================== */ | |
119 | +/* Structs / Defines */ | |
120 | +/* ======================================================================== */ | |
121 | + | |
122 | +/* Private structure */ | |
123 | +struct mpc52xx_ac97_priv { | |
124 | + struct device *dev; | |
125 | + resource_size_t mem_start; | |
126 | + resource_size_t mem_len; | |
127 | + int irq; | |
128 | + struct mpc52xx_psc __iomem *psc; | |
129 | + struct mpc52xx_psc_fifo __iomem *fifo; | |
130 | + | |
131 | + struct bcom_task *tsk_tx; | |
132 | + spinlock_t dma_lock; | |
133 | + | |
134 | + struct snd_card *card; | |
135 | + struct snd_pcm *pcm; | |
136 | + struct snd_ac97 *ac97; | |
137 | + | |
138 | + struct snd_pcm_substream *substream_playback; | |
139 | + | |
140 | + int period_byte_size; | |
141 | + u32 period_start, period_end, period_next_p; | |
142 | +}; | |
143 | + | |
144 | +/* Register bit definition (AC97 mode specific) */ | |
145 | +#define PSC_AC97_SLOT_BIT(n) (1<<(12-n)) | |
146 | +#define PSC_AC97_SLOTS_XMIT_SHIFT 16 | |
147 | +#define PSC_AC97_SLOTS_RECV_SHIFT 0 | |
148 | + | |
149 | +/* Bestcomm options */ | |
150 | +#define AC97_TX_NUM_BD 32 | |
151 | +#define AC97_RX_NUM_BD 32 | |
152 | + | |
153 | +static int mpc52xx_ac97_tx_fill(struct mpc52xx_ac97_priv *priv) | |
154 | +{ | |
155 | + struct snd_pcm_runtime *rt; | |
156 | + | |
157 | + u32 dma_data_ptr; | |
158 | + | |
159 | + rt = priv->substream_playback->runtime; | |
160 | + | |
161 | + dma_data_ptr = virt_to_phys(rt->dma_area); | |
162 | + | |
163 | + priv->period_byte_size = frames_to_bytes(rt, rt->period_size); | |
164 | + priv->period_start = dma_data_ptr; | |
165 | + priv->period_end = dma_data_ptr + priv->period_byte_size * rt->periods; | |
166 | + priv->period_next_p = dma_data_ptr; | |
167 | + | |
168 | + spin_lock(&priv->dma_lock); | |
169 | + while (!bcom_queue_full(priv->tsk_tx)) { | |
170 | + struct bcom_gen_bd *bd; | |
171 | + | |
172 | + /* Submit a new one */ | |
173 | + bd = (struct bcom_gen_bd *) bcom_prepare_next_buffer(priv->tsk_tx); | |
174 | + bd->status = priv->period_byte_size; | |
175 | + bd->buf_pa = priv->period_next_p; | |
176 | + bcom_submit_next_buffer(priv->tsk_tx, NULL); | |
177 | + | |
178 | + /* Next pointer */ | |
179 | + priv->period_next_p += priv->period_byte_size; | |
180 | + if (priv->period_next_p >= priv->period_end) | |
181 | + priv->period_next_p = priv->period_start; | |
182 | + } | |
183 | + spin_unlock(&priv->dma_lock); | |
184 | + | |
185 | + return 0; | |
186 | +} | |
187 | + | |
188 | + | |
189 | +/* ======================================================================== */ | |
190 | +/* ISR routine */ | |
191 | +/* ======================================================================== */ | |
192 | + | |
193 | +static irqreturn_t mpc52xx_ac97_tx_irq(int irq, void *dev_id) | |
194 | +{ | |
195 | + struct mpc52xx_ac97_priv *priv = dev_id; | |
196 | + struct snd_pcm_runtime *rt; | |
197 | + struct bcom_gen_bd *bd; | |
198 | + | |
199 | + rt = priv->substream_playback->runtime; | |
200 | + | |
201 | + if (!bcom_buffer_done(priv->tsk_tx)) { | |
202 | + dev_dbg(priv->dev, "tx mismatch? Check correct output PSC\n"); | |
203 | + bcom_disable(priv->tsk_tx); | |
204 | + } | |
205 | + | |
206 | + spin_lock(&priv->dma_lock); | |
207 | + while (bcom_buffer_done(priv->tsk_tx)) { | |
208 | + /* Get the buffer back */ | |
209 | + bcom_retrieve_buffer(priv->tsk_tx, NULL, NULL); | |
210 | + | |
211 | + /* Submit a new one */ | |
212 | + bd = (struct bcom_gen_bd *) bcom_prepare_next_buffer(priv->tsk_tx); | |
213 | + bd->status = priv->period_byte_size; | |
214 | + bd->buf_pa = priv->period_next_p; | |
215 | + bcom_submit_next_buffer(priv->tsk_tx, NULL); | |
216 | + bcom_enable(priv->tsk_tx); | |
217 | + | |
218 | + /* Next pointer */ | |
219 | + priv->period_next_p += priv->period_byte_size; | |
220 | + if (priv->period_next_p >= priv->period_end) | |
221 | + priv->period_next_p = priv->period_start; | |
222 | + } | |
223 | + spin_unlock(&priv->dma_lock); | |
224 | + | |
225 | + snd_pcm_period_elapsed(priv->substream_playback); | |
226 | + | |
227 | + return IRQ_HANDLED; | |
228 | +} | |
229 | + | |
230 | + | |
231 | +static irqreturn_t mpc52xx_ac97_irq(int irq, void *dev_id) | |
232 | +{ | |
233 | + struct mpc52xx_ac97_priv *priv = dev_id; | |
234 | + | |
235 | + static int icnt = 0; | |
236 | + | |
237 | +#if 1 | |
238 | + /* Anti Crash during dev ;) */ | |
239 | + if ((icnt++) > 5000) | |
240 | + out_be16(&priv->psc->mpc52xx_psc_imr, 0); | |
241 | +#endif | |
242 | + | |
243 | + /* Print statuts */ | |
244 | + dev_dbg(priv->dev, "isr: %04x", in_be16(&priv->psc->mpc52xx_psc_imr)); | |
245 | + out_8(&priv->psc->command,MPC52xx_PSC_RST_ERR_STAT); | |
246 | + | |
247 | + return IRQ_HANDLED; | |
248 | +} | |
249 | + | |
250 | +/* ======================================================================== */ | |
251 | +/* PCM interface */ | |
252 | +/* ======================================================================== */ | |
253 | + | |
254 | +/* HW desc */ | |
255 | + | |
256 | +static struct snd_pcm_hardware mpc52xx_ac97_hw = { | |
257 | + .info = SNDRV_PCM_INFO_INTERLEAVED | | |
258 | + SNDRV_PCM_INFO_MMAP | | |
259 | + SNDRV_PCM_INFO_MMAP_VALID, | |
260 | + .formats = SNDRV_PCM_FMTBIT_S32_BE, | |
261 | + .rates = SNDRV_PCM_RATE_8000_48000, | |
262 | + .rate_min = 8000, | |
263 | + .rate_max = 48000, | |
264 | + .channels_min = 1, | |
265 | + .channels_max = 2, /* Support for more ? */ | |
266 | + .buffer_bytes_max = 1024*1024, | |
267 | + .period_bytes_min = 512, | |
268 | + .period_bytes_max = 16*1024, | |
269 | + .periods_min = 8, | |
270 | + .periods_max = 1024, | |
271 | + .fifo_size = 512, | |
272 | +}; | |
273 | + | |
274 | + | |
275 | +/* Playback */ | |
276 | + | |
277 | +static int mpc52xx_ac97_playback_open(struct snd_pcm_substream *substream) | |
278 | +{ | |
279 | + struct mpc52xx_ac97_priv *priv = substream->pcm->private_data; | |
280 | + | |
281 | + dev_dbg(priv->dev, "mpc52xx_ac97_playback_open(%p)\n", substream); | |
282 | + | |
283 | + substream->runtime->hw = mpc52xx_ac97_hw; | |
284 | + | |
285 | + priv->substream_playback = substream; | |
286 | + | |
287 | + return 0; /* FIXME */ | |
288 | +} | |
289 | + | |
290 | +static int mpc52xx_ac97_playback_close(struct snd_pcm_substream *substream) | |
291 | +{ | |
292 | + struct mpc52xx_ac97_priv *priv = substream->pcm->private_data; | |
293 | + dev_dbg(priv->dev, "mpc52xx_ac97_playback_close(%p)\n", substream); | |
294 | + priv->substream_playback = NULL; | |
295 | + return 0; /* FIXME */ | |
296 | +} | |
297 | + | |
298 | +static int mpc52xx_ac97_playback_prepare(struct snd_pcm_substream *substream) | |
299 | +{ | |
300 | + struct mpc52xx_ac97_priv *priv = substream->pcm->private_data; | |
301 | + | |
302 | + dev_dbg(priv->dev, "mpc52xx_ac97_playback_prepare(%p)\n", substream); | |
303 | + | |
304 | + /* FIXME, need a spinlock to protect access */ | |
305 | + if (substream->runtime->channels == 1) | |
306 | + out_be32(&priv->psc->ac97_slots, 0x01000000); | |
307 | + else | |
308 | + out_be32(&priv->psc->ac97_slots, 0x03000000); | |
309 | + | |
310 | + snd_ac97_set_rate(priv->ac97, AC97_PCM_FRONT_DAC_RATE, | |
311 | + substream->runtime->rate); | |
312 | + | |
313 | + return 0; /* FIXME */ | |
314 | +} | |
315 | + | |
316 | + | |
317 | +/* Capture */ | |
318 | + | |
319 | +static int mpc52xx_ac97_capture_open(struct snd_pcm_substream *substream) | |
320 | +{ | |
321 | +/* struct mpc52xx_ac97_priv *priv = substream->pcm->private_data; */ | |
322 | + return 0; /* FIXME */ | |
323 | +} | |
324 | + | |
325 | +static int mpc52xx_ac97_capture_close(struct snd_pcm_substream *substream) | |
326 | +{ | |
327 | +/* struct mpc52xx_ac97_priv *priv = substream->pcm->private_data; */ | |
328 | + return 0; /* FIXME */ | |
329 | +} | |
330 | + | |
331 | +static int | |
332 | +mpc52xx_ac97_capture_prepare(struct snd_pcm_substream *substream) | |
333 | +{ | |
334 | +/* struct mpc52xx_ac97_priv *priv = substream->pcm->private_data; */ | |
335 | + return 0; /* FIXME */ | |
336 | +} | |
337 | + | |
338 | + | |
339 | +/* Common */ | |
340 | + | |
341 | +static int mpc52xx_ac97_hw_params(struct snd_pcm_substream *substream, | |
342 | + struct snd_pcm_hw_params *params) | |
343 | +{ | |
344 | + struct mpc52xx_ac97_priv *priv = substream->pcm->private_data; | |
345 | + int rv; | |
346 | + | |
347 | + dev_dbg(priv->dev, "mpc52xx_ac97_hw_params(%p)\n", substream); | |
348 | + | |
349 | + rv = snd_pcm_lib_malloc_pages(substream, | |
350 | + params_buffer_bytes(params)); | |
351 | + if (rv < 0) { | |
352 | + printk(KERN_ERR "hw params failes\n"); /* FIXME */ | |
353 | + return rv; | |
354 | + } | |
355 | + | |
356 | + dev_dbg(priv->dev, "%d %d %d\n", params_buffer_bytes(params), | |
357 | + params_period_bytes(params), params_periods(params)); | |
358 | + | |
359 | + return 0; | |
360 | +} | |
361 | + | |
362 | +static int mpc52xx_ac97_hw_free(struct snd_pcm_substream *substream) | |
363 | +{ | |
364 | + struct mpc52xx_ac97_priv *priv = substream->pcm->private_data; | |
365 | + | |
366 | + dev_dbg(priv->dev, "mpc52xx_ac97_hw_free(%p)\n", substream); | |
367 | + | |
368 | + return snd_pcm_lib_free_pages(substream); | |
369 | +} | |
370 | + | |
371 | +static int mpc52xx_ac97_trigger(struct snd_pcm_substream *substream, int cmd) | |
372 | +{ | |
373 | + struct mpc52xx_ac97_priv *priv = substream->pcm->private_data; | |
374 | + int rv = 0; | |
375 | + | |
376 | + dev_dbg(priv->dev, "mpc52xx_ac97_trigger(%p,%d)\n", substream, cmd); | |
377 | + | |
378 | + switch (cmd) { | |
379 | + case SNDRV_PCM_TRIGGER_START: | |
380 | + /* Enable TX taks */ | |
381 | + bcom_gen_bd_tx_reset(priv->tsk_tx); | |
382 | + mpc52xx_ac97_tx_fill(priv); | |
383 | + bcom_enable(priv->tsk_tx); | |
384 | +/* | |
385 | + out_be16(&priv->psc->mpc52xx_psc_imr, 0x0800); // 0x0100 | |
386 | + out_be16(&priv->psc->mpc52xx_psc_imr, 0x0100); // 0x0100 | |
387 | +*/ | |
388 | + /* FIXME: Shouldn't we check for overrun too ? */ | |
389 | + /* also, shouldn't we just activate TX here ? */ | |
390 | + | |
391 | + break; | |
392 | + | |
393 | + case SNDRV_PCM_TRIGGER_STOP: | |
394 | + /* Disable TX task */ | |
395 | + bcom_disable(priv->tsk_tx); | |
396 | + out_be16(&priv->psc->mpc52xx_psc_imr, 0x0000); // 0x0100 | |
397 | + | |
398 | + break; | |
399 | + | |
400 | + default: | |
401 | + rv = -EINVAL; | |
402 | + } | |
403 | + | |
404 | + /* FIXME */ | |
405 | + return rv; | |
406 | +} | |
407 | + | |
408 | +static snd_pcm_uframes_t mpc52xx_ac97_pointer(struct snd_pcm_substream *substream) | |
409 | +{ | |
410 | + struct snd_pcm_runtime *runtime = substream->runtime; | |
411 | + struct mpc52xx_ac97_priv *priv = substream->pcm->private_data; | |
412 | + u32 count; | |
413 | + | |
414 | + count = priv->tsk_tx->bd[priv->tsk_tx->outdex].data[0] - priv->period_start; | |
415 | + | |
416 | + return bytes_to_frames(runtime, count); | |
417 | +} | |
418 | + | |
419 | + | |
420 | +/* Ops */ | |
421 | + | |
422 | +static struct snd_pcm_ops mpc52xx_ac97_playback_ops = { | |
423 | + .open = mpc52xx_ac97_playback_open, | |
424 | + .close = mpc52xx_ac97_playback_close, | |
425 | + .ioctl = snd_pcm_lib_ioctl, | |
426 | + .hw_params = mpc52xx_ac97_hw_params, | |
427 | + .hw_free = mpc52xx_ac97_hw_free, | |
428 | + .prepare = mpc52xx_ac97_playback_prepare, | |
429 | + .trigger = mpc52xx_ac97_trigger, | |
430 | + .pointer = mpc52xx_ac97_pointer, | |
431 | +}; | |
432 | + | |
433 | +static struct snd_pcm_ops mpc52xx_ac97_capture_ops = { | |
434 | + .open = mpc52xx_ac97_capture_open, | |
435 | + .close = mpc52xx_ac97_capture_close, | |
436 | + .ioctl = snd_pcm_lib_ioctl, | |
437 | + .hw_params = mpc52xx_ac97_hw_params, | |
438 | + .hw_free = mpc52xx_ac97_hw_free, | |
439 | + .prepare = mpc52xx_ac97_capture_prepare, | |
440 | + .trigger = mpc52xx_ac97_trigger, | |
441 | + .pointer = mpc52xx_ac97_pointer, | |
442 | +}; | |
443 | + | |
444 | + | |
445 | +/* ======================================================================== */ | |
446 | +/* AC97 Bus interface */ | |
447 | +/* ======================================================================== */ | |
448 | + | |
449 | +static unsigned short mpc52xx_ac97_bus_read(struct snd_ac97 *ac97, | |
450 | + unsigned short reg) | |
451 | +{ | |
452 | + struct mpc52xx_ac97_priv *priv = ac97->private_data; | |
453 | + int timeout; | |
454 | + unsigned int val; | |
455 | + | |
456 | + /* Wait for it to be ready */ | |
457 | + timeout = 1000; | |
458 | + while ((--timeout) && (in_be16(&priv->psc->mpc52xx_psc_status) & | |
459 | + MPC52xx_PSC_SR_CMDSEND) ) | |
460 | + udelay(10); | |
461 | + | |
462 | + if (!timeout) { | |
463 | + printk(KERN_ERR DRV_NAME ": timeout on ac97 bus (rdy)\n"); | |
464 | + return 0xffff; | |
465 | + } | |
466 | + | |
467 | + /* Do the read */ | |
468 | + out_be32(&priv->psc->ac97_cmd, (1<<31) | ((reg & 0x7f) << 24)); | |
469 | + | |
470 | + /* Wait for the answer */ | |
471 | + timeout = 1000; | |
472 | + while ((--timeout) && !(in_be16(&priv->psc->mpc52xx_psc_status) & | |
473 | + MPC52xx_PSC_SR_DATA_VAL) ) | |
474 | + udelay(10); | |
475 | + | |
476 | + if (!timeout) { | |
477 | + printk(KERN_ERR DRV_NAME ": timeout on ac97 read (val)\n"); | |
478 | + return 0xffff; | |
479 | + } | |
480 | + | |
481 | + /* Get the data */ | |
482 | + val = in_be32(&priv->psc->ac97_data); | |
483 | + if ( ((val>>24) & 0x7f) != reg ) { | |
484 | + printk(KERN_ERR DRV_NAME ": reg echo error on ac97 read\n"); | |
485 | + return 0xffff; | |
486 | + } | |
487 | + val = (val >> 8) & 0xffff; | |
488 | + | |
489 | + return (unsigned short) val; | |
490 | +} | |
491 | + | |
492 | +static void mpc52xx_ac97_bus_write(struct snd_ac97 *ac97, | |
493 | + unsigned short reg, unsigned short val) | |
494 | +{ | |
495 | + struct mpc52xx_ac97_priv *priv = ac97->private_data; | |
496 | + int timeout; | |
497 | + | |
498 | + /* Wait for it to be ready */ | |
499 | + timeout = 1000; | |
500 | + while ((--timeout) && (in_be16(&priv->psc->mpc52xx_psc_status) & | |
501 | + MPC52xx_PSC_SR_CMDSEND) ) | |
502 | + udelay(10); | |
503 | + | |
504 | + if (!timeout) { | |
505 | + printk(KERN_ERR DRV_NAME ": timeout on ac97 write\n"); | |
506 | + return; | |
507 | + } | |
508 | + | |
509 | + /* Write data */ | |
510 | + out_be32(&priv->psc->ac97_cmd, ((reg & 0x7f) << 24) | (val << 8)); | |
511 | +} | |
512 | + | |
513 | +static void mpc52xx_ac97_bus_reset(struct snd_ac97 *ac97) | |
514 | +{ | |
515 | + struct mpc52xx_ac97_priv *priv = ac97->private_data; | |
516 | + | |
517 | + dev_dbg(priv->dev, "ac97 codec reset\n"); | |
518 | + | |
519 | + /* Do a cold reset */ | |
520 | + /* | |
521 | + * Note: This could interfere with some external AC97 mixers, as it | |
522 | + * could switch them into test mode, when SYNC or SDATA_OUT are not | |
523 | + * low while RES is low! | |
524 | + */ | |
525 | + out_8(&priv->psc->op1, 0x02); | |
526 | + udelay(10); | |
527 | + out_8(&priv->psc->op0, 0x02); | |
528 | + udelay(50); | |
529 | + | |
530 | + /* PSC recover from cold reset (cfr user manual, not sure if useful) */ | |
531 | + out_be32(&priv->psc->sicr, in_be32(&priv->psc->sicr)); | |
532 | +} | |
533 | + | |
534 | + | |
535 | +static struct snd_ac97_bus_ops mpc52xx_ac97_bus_ops = { | |
536 | + .read = mpc52xx_ac97_bus_read, | |
537 | + .write = mpc52xx_ac97_bus_write, | |
538 | + .reset = mpc52xx_ac97_bus_reset, | |
539 | +}; | |
540 | + | |
541 | + | |
542 | +/* ======================================================================== */ | |
543 | +/* Sound driver setup */ | |
544 | +/* ======================================================================== */ | |
545 | + | |
546 | +static int mpc52xx_ac97_setup_pcm(struct mpc52xx_ac97_priv *priv) | |
547 | +{ | |
548 | + int rv; | |
549 | + | |
550 | + rv = snd_pcm_new(priv->card, DRV_NAME "-pcm", 0, 1, 1, &priv->pcm); | |
551 | + if (rv) { | |
552 | + dev_dbg(priv->dev, "%s: snd_pcm_new failed\n", DRV_NAME); | |
553 | + return rv; | |
554 | + } | |
555 | + | |
556 | + rv = snd_pcm_lib_preallocate_pages_for_all(priv->pcm, | |
557 | + SNDRV_DMA_TYPE_CONTINUOUS, snd_dma_continuous_data(GFP_KERNEL), | |
558 | + 128*1024, 128*1024); | |
559 | + if (rv) { | |
560 | + dev_dbg(priv->dev, | |
561 | + "%s: snd_pcm_lib_preallocate_pages_for_all failed\n", | |
562 | + DRV_NAME); | |
563 | + return rv; | |
564 | + } | |
565 | + | |
566 | + snd_pcm_set_ops(priv->pcm, SNDRV_PCM_STREAM_PLAYBACK, | |
567 | + &mpc52xx_ac97_playback_ops); | |
568 | + snd_pcm_set_ops(priv->pcm, SNDRV_PCM_STREAM_CAPTURE, | |
569 | + &mpc52xx_ac97_capture_ops); | |
570 | + | |
571 | + priv->pcm->private_data = priv; | |
572 | + priv->pcm->info_flags = 0; | |
573 | + | |
574 | + strcpy(priv->pcm->name, "Freescale MPC52xx PSC-AC97 PCM"); | |
575 | + | |
576 | + return 0; | |
577 | +} | |
578 | + | |
579 | +static int mpc52xx_ac97_setup_mixer(struct mpc52xx_ac97_priv *priv) | |
580 | +{ | |
581 | + struct snd_ac97_bus *ac97_bus; | |
582 | + struct snd_ac97_template ac97_template; | |
583 | + int rv; | |
584 | + | |
585 | + rv = snd_ac97_bus(priv->card, 0, &mpc52xx_ac97_bus_ops, NULL, &ac97_bus); | |
586 | + if (rv) { | |
587 | + printk(KERN_ERR DRV_NAME ": snd_ac97_bus failed\n"); | |
588 | + return rv; | |
589 | + } | |
590 | + | |
591 | + memset(&ac97_template, 0, sizeof(struct snd_ac97_template)); | |
592 | + ac97_template.private_data = priv; | |
593 | + | |
594 | + rv = snd_ac97_mixer(ac97_bus, &ac97_template, &priv->ac97); | |
595 | + if (rv) { | |
596 | + printk(KERN_ERR DRV_NAME ": snd_ac97_mixer failed\n"); | |
597 | + return rv; | |
598 | + } | |
599 | + | |
600 | + return 0; | |
601 | +} | |
602 | + | |
603 | +static int mpc52xx_ac97_hwinit(struct mpc52xx_ac97_priv *priv) | |
604 | +{ | |
605 | + /* Reset everything first by safety */ | |
606 | + out_8(&priv->psc->command,MPC52xx_PSC_RST_RX); | |
607 | + out_8(&priv->psc->command,MPC52xx_PSC_RST_TX); | |
608 | + out_8(&priv->psc->command,MPC52xx_PSC_RST_ERR_STAT); | |
609 | + | |
610 | + /* Do a cold reset of codec */ | |
611 | + /* | |
612 | + * Note: This could interfere with some external AC97 mixers, as it | |
613 | + * could switch them into test mode, when SYNC or SDATA_OUT are not | |
614 | + * low while RES is low! | |
615 | + */ | |
616 | + out_8(&priv->psc->op1, 0x02); | |
617 | + udelay(10); | |
618 | + out_8(&priv->psc->op0, 0x02); | |
619 | + udelay(50); | |
620 | + | |
621 | + /* Configure AC97 enhanced mode */ | |
622 | + out_be32(&priv->psc->sicr, 0x03010000); | |
623 | + | |
624 | + /* No slots active */ | |
625 | + out_be32(&priv->psc->ac97_slots, 0x00000000); | |
626 | + | |
627 | + /* No IRQ */ | |
628 | + out_be16(&priv->psc->mpc52xx_psc_imr, 0x0000); | |
629 | + | |
630 | + /* FIFO levels */ | |
631 | + out_8(&priv->fifo->rfcntl, 0x07); | |
632 | + out_8(&priv->fifo->tfcntl, 0x07); | |
633 | + out_be16(&priv->fifo->rfalarm, 0x80); | |
634 | + out_be16(&priv->fifo->tfalarm, 0x80); | |
635 | + | |
636 | + /* Go */ | |
637 | + out_8(&priv->psc->command,MPC52xx_PSC_TX_ENABLE); | |
638 | + out_8(&priv->psc->command,MPC52xx_PSC_RX_ENABLE); | |
639 | + | |
640 | + return 0; | |
641 | +} | |
642 | + | |
643 | +static int mpc52xx_ac97_hwshutdown(struct mpc52xx_ac97_priv *priv) | |
644 | +{ | |
645 | + /* No IRQ */ | |
646 | + out_be16(&priv->psc->mpc52xx_psc_imr, 0x0000); | |
647 | + | |
648 | + /* Disable TB & RX */ | |
649 | + out_8(&priv->psc->command,MPC52xx_PSC_RST_RX); | |
650 | + out_8(&priv->psc->command,MPC52xx_PSC_RST_TX); | |
651 | + | |
652 | + /* FIXME : Reset or put codec in low power ? */ | |
653 | + | |
654 | + return 0; | |
655 | +} | |
656 | + | |
657 | +/* ======================================================================== */ | |
658 | +/* OF Platform Driver */ | |
659 | +/* ======================================================================== */ | |
660 | + | |
661 | +static int __devinit | |
662 | +mpc52xx_ac97_probe(struct of_device *op, const struct of_device_id *match) | |
663 | +{ | |
664 | + struct device_node *dn = op->node; | |
665 | + struct mpc52xx_ac97_priv *priv; | |
666 | + struct snd_card *card; | |
667 | + struct resource res; | |
668 | + int tx_initiator; | |
669 | + int rv; | |
670 | + const unsigned int *devno; | |
671 | + | |
672 | + dev_dbg(&op->dev, "probing MPC52xx PSC AC97 driver\n"); | |
673 | + | |
674 | + /* Get card structure */ | |
675 | + rv = -ENOMEM; | |
676 | + card = snd_card_new(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, | |
677 | + THIS_MODULE, sizeof(struct mpc52xx_ac97_priv)); | |
678 | + if (!card) | |
679 | + goto err_early; | |
680 | + | |
681 | + priv = card->private_data; | |
682 | + | |
683 | + /* Init our private structure */ | |
684 | + priv->card = card; | |
685 | + priv->dev = &op->dev; | |
686 | + | |
687 | + /* Get resources (mem,irq,...) */ | |
688 | + rv = of_address_to_resource(dn, 0, &res); | |
689 | + if (rv) | |
690 | + goto err_early; | |
691 | + | |
692 | + priv->mem_start = res.start; | |
693 | + priv->mem_len = res.end - res.start + 1; | |
694 | + | |
695 | + if (!request_mem_region(priv->mem_start, priv->mem_len, DRV_NAME)) { | |
696 | + dev_err(&op->dev, "%s: request_mem_region failed\n", DRV_NAME); | |
697 | + rv = -EBUSY; | |
698 | + goto err_early; | |
699 | + } | |
700 | + | |
701 | + priv->psc = ioremap(priv->mem_start, priv->mem_len); | |
702 | + if (!priv->psc) { | |
703 | + dev_err(&op->dev, "%s: ioremap failed\n", DRV_NAME); | |
704 | + rv = -ENOMEM; | |
705 | + goto err_iomap; | |
706 | + } | |
707 | + /* the fifo starts right after psc ends */ | |
708 | + priv->fifo = (struct mpc52xx_psc_fifo*)&priv->psc[1]; /* FIXME */ | |
709 | + | |
710 | + priv->irq = irq_of_parse_and_map(dn, 0); | |
711 | + if (priv->irq == NO_IRQ) { | |
712 | + dev_err(&op->dev, "%s: irq_of_parse_and_map failed\n", | |
713 | + DRV_NAME); | |
714 | + rv = -EBUSY; | |
715 | + goto err_irqmap; | |
716 | + } | |
717 | + | |
718 | + /* Setup Bestcomm tasks */ | |
719 | + spin_lock_init(&priv->dma_lock); | |
720 | + | |
721 | + /* | |
722 | + * PSC1 or PSC2 can be configured for AC97 usage. Select the right | |
723 | + * channel, to let the BCOMM unit does its job correctly. | |
724 | + */ | |
725 | + devno = of_get_property(dn, "cell-index", NULL); | |
726 | + switch (*devno) { | |
727 | + case 0: /* PSC1 */ | |
728 | + tx_initiator = 14; | |
729 | + break; | |
730 | + case 1: /* PSC2 */ | |
731 | + tx_initiator = 12; | |
732 | + break; | |
733 | + default: | |
734 | + dev_dbg(priv->dev, "Unknown PSC unit for AC97 usage!\n"); | |
735 | + rv = -ENODEV; | |
736 | + goto err_irq; | |
737 | + } | |
738 | + | |
739 | + priv->tsk_tx = bcom_gen_bd_tx_init(AC97_TX_NUM_BD, | |
740 | + priv->mem_start + sizeof(struct mpc52xx_psc) + | |
741 | + offsetof(struct mpc52xx_psc_fifo, tfdata), | |
742 | + tx_initiator, | |
743 | + 2); /* ipr : FIXME */ | |
744 | + if (!priv->tsk_tx) { | |
745 | + dev_err(&op->dev, "%s: bcom_gen_bd_tx_init failed\n", | |
746 | + DRV_NAME); | |
747 | + rv = -ENOMEM; | |
748 | + goto err_bcomm; | |
749 | + } | |
750 | + | |
751 | + /* Low level HW Init */ | |
752 | + mpc52xx_ac97_hwinit(priv); | |
753 | + | |
754 | + /* Request IRQ now that we're 'stable' */ | |
755 | + rv = request_irq(priv->irq, mpc52xx_ac97_irq, 0, DRV_NAME, priv); | |
756 | + if (rv < 0) { | |
757 | + dev_err(&op->dev, "%s: request_irq failed\n", DRV_NAME); | |
758 | + goto err_irqreq; | |
759 | + } | |
760 | + | |
761 | + rv = request_irq(bcom_get_task_irq(priv->tsk_tx), | |
762 | + mpc52xx_ac97_tx_irq, 0, DRV_NAME "_tx", priv); | |
763 | + if (rv < 0) { | |
764 | + dev_err(&op->dev, "%s: request_irq failed\n", DRV_NAME); | |
765 | + goto err_txirqreq; | |
766 | + } | |
767 | + | |
768 | + /* Prepare sound stuff */ | |
769 | + rv = mpc52xx_ac97_setup_mixer(priv); | |
770 | + if (rv) | |
771 | + goto err_late; | |
772 | + | |
773 | + rv = mpc52xx_ac97_setup_pcm(priv); | |
774 | + if (rv) | |
775 | + goto err_late; | |
776 | + | |
777 | + /* Finally register the card */ | |
778 | + snprintf(card->shortname, sizeof(card->shortname), DRV_NAME); | |
779 | + snprintf(card->longname, sizeof(card->longname), | |
780 | + "Freescale MPC52xx PSC-AC97 (%s)", card->mixername); | |
781 | + | |
782 | + rv = snd_card_register(card); | |
783 | + if (rv) { | |
784 | + dev_err(&op->dev, "%s: snd_card_register failed\n", DRV_NAME); | |
785 | + goto err_late; | |
786 | + } | |
787 | + | |
788 | + dev_set_drvdata(&op->dev, priv); | |
789 | + | |
790 | + return 0; | |
791 | + | |
792 | +err_late: | |
793 | + free_irq(bcom_get_task_irq(priv->tsk_tx), priv); | |
794 | +err_txirqreq: | |
795 | + free_irq(priv->irq, priv); | |
796 | +err_irqreq: | |
797 | + bcom_gen_bd_tx_release(priv->tsk_tx); | |
798 | +err_bcomm: | |
799 | + mpc52xx_ac97_hwshutdown(priv); | |
800 | +err_irq: | |
801 | + irq_dispose_mapping(priv->irq); | |
802 | +err_irqmap: | |
803 | + iounmap(priv->psc); | |
804 | +err_iomap: | |
805 | + release_mem_region(priv->mem_start, priv->mem_len); | |
806 | +err_early: | |
807 | + if (card) | |
808 | + snd_card_free(card); | |
809 | + return rv; | |
810 | +} | |
811 | + | |
812 | +static int mpc52xx_ac97_remove(struct of_device *op) | |
813 | +{ | |
814 | + struct mpc52xx_ac97_priv *priv; | |
815 | + | |
816 | + dev_dbg(&op->dev, "removing MPC52xx PSC AC97 driver\n"); | |
817 | + | |
818 | + priv = dev_get_drvdata(&op->dev); | |
819 | + if (priv) { | |
820 | + /* Sound subsys shutdown */ | |
821 | + snd_card_free(priv->card); | |
822 | + | |
823 | + /* Low level HW shutdown */ | |
824 | + mpc52xx_ac97_hwshutdown(priv); | |
825 | + | |
826 | + /* Release bestcomm tasks */ | |
827 | + free_irq(bcom_get_task_irq(priv->tsk_tx), priv); | |
828 | + bcom_gen_bd_tx_release(priv->tsk_tx); | |
829 | + | |
830 | + /* Release resources */ | |
831 | + iounmap(priv->psc); | |
832 | + free_irq(priv->irq, priv); | |
833 | + irq_dispose_mapping(priv->irq); | |
834 | + release_mem_region(priv->mem_start, priv->mem_len); | |
835 | + } | |
836 | + | |
837 | + dev_set_drvdata(&op->dev, NULL); | |
838 | + | |
839 | + return 0; | |
840 | +} | |
841 | + | |
842 | + | |
843 | +static struct of_device_id mpc52xx_ac97_of_match[] = { | |
844 | + { | |
845 | + .type = "sound", | |
846 | + .compatible = "mpc5200b-psc-ac97", /* B only for now */ | |
847 | + }, { } | |
848 | +}; | |
849 | +MODULE_DEVICE_TABLE(of, mpc52xx_ac97_of_match); | |
850 | +static struct of_platform_driver mpc52xx_ac97_of_driver = { | |
851 | + .owner = THIS_MODULE, | |
852 | + .name = DRV_NAME, | |
853 | + .match_table = mpc52xx_ac97_of_match, | |
854 | + .probe = mpc52xx_ac97_probe, | |
855 | + .remove = mpc52xx_ac97_remove, | |
856 | + .driver = { | |
857 | + .name = DRV_NAME, | |
858 | + }, | |
859 | +}; | |
860 | + | |
861 | +/* ======================================================================== */ | |
862 | +/* Module */ | |
863 | +/* ======================================================================== */ | |
864 | + | |
865 | +static int __init mpc52xx_ac97_init(void) | |
866 | +{ | |
867 | + int rv; | |
868 | + | |
869 | + printk(KERN_INFO "Sound: MPC52xx PSC AC97 driver\n"); | |
870 | + | |
871 | + rv = of_register_platform_driver(&mpc52xx_ac97_of_driver); | |
872 | + if (rv) { | |
873 | + printk(KERN_ERR DRV_NAME ": " | |
874 | + "of_register_platform_driver failed (%i)\n", rv); | |
875 | + return rv; | |
876 | + } | |
877 | + | |
878 | + return 0; | |
879 | +} | |
880 | + | |
881 | +static void __exit mpc52xx_ac97_exit(void) | |
882 | +{ | |
883 | + of_unregister_platform_driver(&mpc52xx_ac97_of_driver); | |
884 | +} | |
885 | + | |
886 | +module_init(mpc52xx_ac97_init); | |
887 | +module_exit(mpc52xx_ac97_exit); | |
888 | + | |
889 | +MODULE_AUTHOR("Sylvain Munaut <tnt@246tNt.com>"); | |
890 | +MODULE_DESCRIPTION(DRV_NAME ": Freescale MPC52xx PSC AC97 driver"); | |
891 | +MODULE_LICENSE("GPL"); | |
892 | + |