Commit | Line | Data |
---|---|---|
4a161d23 ML |
1 | /* |
2 | * Au12x0/Au1550 PSC ALSA ASoC audio support. | |
3 | * | |
cdc65fbe ML |
4 | * (c) 2007-2009 MSC Vertriebsges.m.b.H., |
5 | * Manuel Lauss <manuel.lauss@gmail.com> | |
4a161d23 ML |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | * | |
11 | * Au1xxx-PSC AC97 glue. | |
12 | * | |
13 | * NOTE: all of these drivers can only work with a SINGLE instance | |
14 | * of a PSC. Multiple independent audio devices are impossible | |
15 | * with ASoC v1. | |
16 | */ | |
17 | ||
18 | #include <linux/init.h> | |
19 | #include <linux/module.h> | |
20 | #include <linux/device.h> | |
21 | #include <linux/delay.h> | |
cdc65fbe | 22 | #include <linux/mutex.h> |
4a161d23 ML |
23 | #include <linux/suspend.h> |
24 | #include <sound/core.h> | |
25 | #include <sound/pcm.h> | |
26 | #include <sound/initval.h> | |
27 | #include <sound/soc.h> | |
28 | #include <asm/mach-au1x00/au1000.h> | |
29 | #include <asm/mach-au1x00/au1xxx_psc.h> | |
30 | ||
31 | #include "psc.h" | |
32 | ||
cdc65fbe ML |
33 | /* how often to retry failed codec register reads/writes */ |
34 | #define AC97_RW_RETRIES 5 | |
35 | ||
4a161d23 ML |
36 | #define AC97_DIR \ |
37 | (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) | |
38 | ||
39 | #define AC97_RATES \ | |
40 | SNDRV_PCM_RATE_8000_48000 | |
41 | ||
42 | #define AC97_FMTS \ | |
43 | (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3BE) | |
44 | ||
45 | #define AC97PCR_START(stype) \ | |
46 | ((stype) == PCM_TX ? PSC_AC97PCR_TS : PSC_AC97PCR_RS) | |
47 | #define AC97PCR_STOP(stype) \ | |
48 | ((stype) == PCM_TX ? PSC_AC97PCR_TP : PSC_AC97PCR_RP) | |
49 | #define AC97PCR_CLRFIFO(stype) \ | |
50 | ((stype) == PCM_TX ? PSC_AC97PCR_TC : PSC_AC97PCR_RC) | |
51 | ||
cdc65fbe ML |
52 | #define AC97STAT_BUSY(stype) \ |
53 | ((stype) == PCM_TX ? PSC_AC97STAT_TB : PSC_AC97STAT_RB) | |
54 | ||
4a161d23 ML |
55 | /* instance data. There can be only one, MacLeod!!!! */ |
56 | static struct au1xpsc_audio_data *au1xpsc_ac97_workdata; | |
57 | ||
58 | /* AC97 controller reads codec register */ | |
59 | static unsigned short au1xpsc_ac97_read(struct snd_ac97 *ac97, | |
60 | unsigned short reg) | |
61 | { | |
62 | /* FIXME */ | |
63 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | |
e697cd41 ML |
64 | unsigned short retry, tmo; |
65 | unsigned long data; | |
4a161d23 | 66 | |
cdc65fbe | 67 | au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata)); |
4a161d23 ML |
68 | au_sync(); |
69 | ||
cdc65fbe ML |
70 | retry = AC97_RW_RETRIES; |
71 | do { | |
72 | mutex_lock(&pscdata->lock); | |
73 | ||
74 | au_writel(PSC_AC97CDC_RD | PSC_AC97CDC_INDX(reg), | |
75 | AC97_CDC(pscdata)); | |
76 | au_sync(); | |
77 | ||
8d567b6b ML |
78 | tmo = 20; |
79 | do { | |
80 | udelay(21); | |
81 | if (au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD) | |
82 | break; | |
83 | } while (--tmo); | |
4a161d23 | 84 | |
e697cd41 | 85 | data = au_readl(AC97_CDC(pscdata)); |
4a161d23 | 86 | |
cdc65fbe ML |
87 | au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata)); |
88 | au_sync(); | |
89 | ||
90 | mutex_unlock(&pscdata->lock); | |
e697cd41 ML |
91 | |
92 | if (reg != ((data >> 16) & 0x7f)) | |
93 | tmo = 1; /* wrong register, try again */ | |
94 | ||
cdc65fbe | 95 | } while (--retry && !tmo); |
4a161d23 | 96 | |
e697cd41 | 97 | return retry ? data & 0xffff : 0xffff; |
4a161d23 ML |
98 | } |
99 | ||
100 | /* AC97 controller writes to codec register */ | |
101 | static void au1xpsc_ac97_write(struct snd_ac97 *ac97, unsigned short reg, | |
102 | unsigned short val) | |
103 | { | |
104 | /* FIXME */ | |
105 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | |
cdc65fbe | 106 | unsigned int tmo, retry; |
4a161d23 | 107 | |
cdc65fbe | 108 | au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata)); |
4a161d23 | 109 | au_sync(); |
cdc65fbe ML |
110 | |
111 | retry = AC97_RW_RETRIES; | |
112 | do { | |
113 | mutex_lock(&pscdata->lock); | |
114 | ||
115 | au_writel(PSC_AC97CDC_INDX(reg) | (val & 0xffff), | |
116 | AC97_CDC(pscdata)); | |
4a161d23 ML |
117 | au_sync(); |
118 | ||
8d567b6b ML |
119 | tmo = 20; |
120 | do { | |
121 | udelay(21); | |
122 | if (au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD) | |
123 | break; | |
124 | } while (--tmo); | |
cdc65fbe ML |
125 | |
126 | au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata)); | |
127 | au_sync(); | |
128 | ||
129 | mutex_unlock(&pscdata->lock); | |
130 | } while (--retry && !tmo); | |
4a161d23 ML |
131 | } |
132 | ||
133 | /* AC97 controller asserts a warm reset */ | |
134 | static void au1xpsc_ac97_warm_reset(struct snd_ac97 *ac97) | |
135 | { | |
136 | /* FIXME */ | |
137 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | |
138 | ||
139 | au_writel(PSC_AC97RST_SNC, AC97_RST(pscdata)); | |
140 | au_sync(); | |
141 | msleep(10); | |
142 | au_writel(0, AC97_RST(pscdata)); | |
143 | au_sync(); | |
144 | } | |
145 | ||
146 | static void au1xpsc_ac97_cold_reset(struct snd_ac97 *ac97) | |
147 | { | |
148 | /* FIXME */ | |
149 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | |
150 | int i; | |
151 | ||
152 | /* disable PSC during cold reset */ | |
153 | au_writel(0, AC97_CFG(au1xpsc_ac97_workdata)); | |
154 | au_sync(); | |
155 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(pscdata)); | |
156 | au_sync(); | |
157 | ||
158 | /* issue cold reset */ | |
159 | au_writel(PSC_AC97RST_RST, AC97_RST(pscdata)); | |
160 | au_sync(); | |
161 | msleep(500); | |
162 | au_writel(0, AC97_RST(pscdata)); | |
163 | au_sync(); | |
164 | ||
165 | /* enable PSC */ | |
166 | au_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata)); | |
167 | au_sync(); | |
168 | ||
169 | /* wait for PSC to indicate it's ready */ | |
cdc65fbe | 170 | i = 1000; |
4a161d23 | 171 | while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_SR)) && (--i)) |
cdc65fbe | 172 | msleep(1); |
4a161d23 ML |
173 | |
174 | if (i == 0) { | |
175 | printk(KERN_ERR "au1xpsc-ac97: PSC not ready!\n"); | |
176 | return; | |
177 | } | |
178 | ||
179 | /* enable the ac97 function */ | |
180 | au_writel(pscdata->cfg | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); | |
181 | au_sync(); | |
182 | ||
183 | /* wait for AC97 core to become ready */ | |
cdc65fbe | 184 | i = 1000; |
4a161d23 | 185 | while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && (--i)) |
cdc65fbe | 186 | msleep(1); |
4a161d23 ML |
187 | if (i == 0) |
188 | printk(KERN_ERR "au1xpsc-ac97: AC97 ctrl not ready\n"); | |
189 | } | |
190 | ||
191 | /* AC97 controller operations */ | |
192 | struct snd_ac97_bus_ops soc_ac97_ops = { | |
193 | .read = au1xpsc_ac97_read, | |
194 | .write = au1xpsc_ac97_write, | |
195 | .reset = au1xpsc_ac97_cold_reset, | |
196 | .warm_reset = au1xpsc_ac97_warm_reset, | |
197 | }; | |
198 | EXPORT_SYMBOL_GPL(soc_ac97_ops); | |
199 | ||
200 | static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream, | |
dee89c4d MB |
201 | struct snd_pcm_hw_params *params, |
202 | struct snd_soc_dai *dai) | |
4a161d23 ML |
203 | { |
204 | /* FIXME */ | |
205 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | |
cdc65fbe | 206 | unsigned long r, ro, stat; |
8d567b6b | 207 | int chans, t, stype = SUBSTREAM_TYPE(substream); |
4a161d23 ML |
208 | |
209 | chans = params_channels(params); | |
210 | ||
cdc65fbe | 211 | r = ro = au_readl(AC97_CFG(pscdata)); |
4a161d23 ML |
212 | stat = au_readl(AC97_STAT(pscdata)); |
213 | ||
214 | /* already active? */ | |
215 | if (stat & (PSC_AC97STAT_TB | PSC_AC97STAT_RB)) { | |
216 | /* reject parameters not currently set up */ | |
217 | if ((PSC_AC97CFG_GET_LEN(r) != params->msbits) || | |
218 | (pscdata->rate != params_rate(params))) | |
219 | return -EINVAL; | |
220 | } else { | |
4a161d23 ML |
221 | |
222 | /* set sample bitdepth: REG[24:21]=(BITS-2)/2 */ | |
223 | r &= ~PSC_AC97CFG_LEN_MASK; | |
224 | r |= PSC_AC97CFG_SET_LEN(params->msbits); | |
225 | ||
226 | /* channels: enable slots for front L/R channel */ | |
227 | if (stype == PCM_TX) { | |
228 | r &= ~PSC_AC97CFG_TXSLOT_MASK; | |
229 | r |= PSC_AC97CFG_TXSLOT_ENA(3); | |
230 | r |= PSC_AC97CFG_TXSLOT_ENA(4); | |
231 | } else { | |
232 | r &= ~PSC_AC97CFG_RXSLOT_MASK; | |
233 | r |= PSC_AC97CFG_RXSLOT_ENA(3); | |
234 | r |= PSC_AC97CFG_RXSLOT_ENA(4); | |
235 | } | |
236 | ||
cdc65fbe ML |
237 | /* do we need to poke the hardware? */ |
238 | if (!(r ^ ro)) | |
239 | goto out; | |
240 | ||
241 | /* ac97 engine is about to be disabled */ | |
242 | mutex_lock(&pscdata->lock); | |
243 | ||
244 | /* disable AC97 device controller first... */ | |
245 | au_writel(r & ~PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); | |
246 | au_sync(); | |
247 | ||
248 | /* ...wait for it... */ | |
8d567b6b ML |
249 | t = 100; |
250 | while ((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR) && --t) | |
251 | msleep(1); | |
252 | ||
253 | if (!t) | |
254 | printk(KERN_ERR "PSC-AC97: can't disable!\n"); | |
cdc65fbe ML |
255 | |
256 | /* ...write config... */ | |
257 | au_writel(r, AC97_CFG(pscdata)); | |
258 | au_sync(); | |
259 | ||
260 | /* ...enable the AC97 controller again... */ | |
4a161d23 ML |
261 | au_writel(r | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); |
262 | au_sync(); | |
263 | ||
cdc65fbe | 264 | /* ...and wait for ready bit */ |
8d567b6b ML |
265 | t = 100; |
266 | while ((!(au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && --t) | |
267 | msleep(1); | |
268 | ||
269 | if (!t) | |
270 | printk(KERN_ERR "PSC-AC97: can't enable!\n"); | |
cdc65fbe ML |
271 | |
272 | mutex_unlock(&pscdata->lock); | |
273 | ||
4a161d23 ML |
274 | pscdata->cfg = r; |
275 | pscdata->rate = params_rate(params); | |
276 | } | |
277 | ||
cdc65fbe | 278 | out: |
4a161d23 ML |
279 | return 0; |
280 | } | |
281 | ||
282 | static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream, | |
dee89c4d | 283 | int cmd, struct snd_soc_dai *dai) |
4a161d23 ML |
284 | { |
285 | /* FIXME */ | |
286 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | |
287 | int ret, stype = SUBSTREAM_TYPE(substream); | |
288 | ||
289 | ret = 0; | |
290 | ||
291 | switch (cmd) { | |
292 | case SNDRV_PCM_TRIGGER_START: | |
293 | case SNDRV_PCM_TRIGGER_RESUME: | |
cdc65fbe ML |
294 | au_writel(AC97PCR_CLRFIFO(stype), AC97_PCR(pscdata)); |
295 | au_sync(); | |
4a161d23 ML |
296 | au_writel(AC97PCR_START(stype), AC97_PCR(pscdata)); |
297 | au_sync(); | |
298 | break; | |
299 | case SNDRV_PCM_TRIGGER_STOP: | |
300 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
301 | au_writel(AC97PCR_STOP(stype), AC97_PCR(pscdata)); | |
302 | au_sync(); | |
cdc65fbe ML |
303 | |
304 | while (au_readl(AC97_STAT(pscdata)) & AC97STAT_BUSY(stype)) | |
305 | asm volatile ("nop"); | |
306 | ||
307 | au_writel(AC97PCR_CLRFIFO(stype), AC97_PCR(pscdata)); | |
308 | au_sync(); | |
309 | ||
4a161d23 ML |
310 | break; |
311 | default: | |
312 | ret = -EINVAL; | |
313 | } | |
314 | return ret; | |
315 | } | |
316 | ||
317 | static int au1xpsc_ac97_probe(struct platform_device *pdev, | |
318 | struct snd_soc_dai *dai) | |
319 | { | |
320 | int ret; | |
321 | struct resource *r; | |
322 | unsigned long sel; | |
323 | ||
324 | if (au1xpsc_ac97_workdata) | |
325 | return -EBUSY; | |
326 | ||
327 | au1xpsc_ac97_workdata = | |
328 | kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL); | |
329 | if (!au1xpsc_ac97_workdata) | |
330 | return -ENOMEM; | |
331 | ||
cdc65fbe ML |
332 | mutex_init(&au1xpsc_ac97_workdata->lock); |
333 | ||
4a161d23 ML |
334 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
335 | if (!r) { | |
336 | ret = -ENODEV; | |
337 | goto out0; | |
338 | } | |
339 | ||
340 | ret = -EBUSY; | |
341 | au1xpsc_ac97_workdata->ioarea = | |
342 | request_mem_region(r->start, r->end - r->start + 1, | |
343 | "au1xpsc_ac97"); | |
344 | if (!au1xpsc_ac97_workdata->ioarea) | |
345 | goto out0; | |
346 | ||
347 | au1xpsc_ac97_workdata->mmio = ioremap(r->start, 0xffff); | |
348 | if (!au1xpsc_ac97_workdata->mmio) | |
349 | goto out1; | |
350 | ||
351 | /* configuration: max dma trigger threshold, enable ac97 */ | |
cdc65fbe ML |
352 | au1xpsc_ac97_workdata->cfg = PSC_AC97CFG_RT_FIFO8 | |
353 | PSC_AC97CFG_TT_FIFO8 | | |
354 | PSC_AC97CFG_DE_ENABLE; | |
4a161d23 ML |
355 | |
356 | /* preserve PSC clock source set up by platform (dev.platform_data | |
357 | * is already occupied by soc layer) | |
358 | */ | |
359 | sel = au_readl(PSC_SEL(au1xpsc_ac97_workdata)) & PSC_SEL_CLK_MASK; | |
360 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata)); | |
361 | au_sync(); | |
362 | au_writel(0, PSC_SEL(au1xpsc_ac97_workdata)); | |
363 | au_sync(); | |
364 | au_writel(PSC_SEL_PS_AC97MODE | sel, PSC_SEL(au1xpsc_ac97_workdata)); | |
365 | au_sync(); | |
366 | /* next up: cold reset. Dont check for PSC-ready now since | |
367 | * there may not be any codec clock yet. | |
368 | */ | |
369 | ||
370 | return 0; | |
371 | ||
372 | out1: | |
373 | release_resource(au1xpsc_ac97_workdata->ioarea); | |
374 | kfree(au1xpsc_ac97_workdata->ioarea); | |
375 | out0: | |
376 | kfree(au1xpsc_ac97_workdata); | |
377 | au1xpsc_ac97_workdata = NULL; | |
378 | return ret; | |
379 | } | |
380 | ||
381 | static void au1xpsc_ac97_remove(struct platform_device *pdev, | |
382 | struct snd_soc_dai *dai) | |
383 | { | |
384 | /* disable PSC completely */ | |
385 | au_writel(0, AC97_CFG(au1xpsc_ac97_workdata)); | |
386 | au_sync(); | |
387 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata)); | |
388 | au_sync(); | |
389 | ||
390 | iounmap(au1xpsc_ac97_workdata->mmio); | |
391 | release_resource(au1xpsc_ac97_workdata->ioarea); | |
392 | kfree(au1xpsc_ac97_workdata->ioarea); | |
393 | kfree(au1xpsc_ac97_workdata); | |
394 | au1xpsc_ac97_workdata = NULL; | |
395 | } | |
396 | ||
dc7d7b83 | 397 | static int au1xpsc_ac97_suspend(struct snd_soc_dai *dai) |
4a161d23 ML |
398 | { |
399 | /* save interesting registers and disable PSC */ | |
400 | au1xpsc_ac97_workdata->pm[0] = | |
401 | au_readl(PSC_SEL(au1xpsc_ac97_workdata)); | |
402 | ||
403 | au_writel(0, AC97_CFG(au1xpsc_ac97_workdata)); | |
404 | au_sync(); | |
405 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata)); | |
406 | au_sync(); | |
407 | ||
408 | return 0; | |
409 | } | |
410 | ||
dc7d7b83 | 411 | static int au1xpsc_ac97_resume(struct snd_soc_dai *dai) |
4a161d23 ML |
412 | { |
413 | /* restore PSC clock config */ | |
414 | au_writel(au1xpsc_ac97_workdata->pm[0] | PSC_SEL_PS_AC97MODE, | |
415 | PSC_SEL(au1xpsc_ac97_workdata)); | |
416 | au_sync(); | |
417 | ||
418 | /* after this point the ac97 core will cold-reset the codec. | |
419 | * During cold-reset the PSC is reinitialized and the last | |
420 | * configuration set up in hw_params() is restored. | |
421 | */ | |
422 | return 0; | |
423 | } | |
424 | ||
6335d055 EM |
425 | static struct snd_soc_dai_ops au1xpsc_ac97_dai_ops = { |
426 | .trigger = au1xpsc_ac97_trigger, | |
427 | .hw_params = au1xpsc_ac97_hw_params, | |
428 | }; | |
429 | ||
4a161d23 ML |
430 | struct snd_soc_dai au1xpsc_ac97_dai = { |
431 | .name = "au1xpsc_ac97", | |
3ba9e10a | 432 | .ac97_control = 1, |
4a161d23 ML |
433 | .probe = au1xpsc_ac97_probe, |
434 | .remove = au1xpsc_ac97_remove, | |
435 | .suspend = au1xpsc_ac97_suspend, | |
436 | .resume = au1xpsc_ac97_resume, | |
437 | .playback = { | |
438 | .rates = AC97_RATES, | |
439 | .formats = AC97_FMTS, | |
440 | .channels_min = 2, | |
441 | .channels_max = 2, | |
442 | }, | |
443 | .capture = { | |
444 | .rates = AC97_RATES, | |
445 | .formats = AC97_FMTS, | |
446 | .channels_min = 2, | |
447 | .channels_max = 2, | |
448 | }, | |
6335d055 | 449 | .ops = &au1xpsc_ac97_dai_ops, |
4a161d23 ML |
450 | }; |
451 | EXPORT_SYMBOL_GPL(au1xpsc_ac97_dai); | |
452 | ||
453 | static int __init au1xpsc_ac97_init(void) | |
454 | { | |
455 | au1xpsc_ac97_workdata = NULL; | |
3f4b783c | 456 | return snd_soc_register_dai(&au1xpsc_ac97_dai); |
4a161d23 ML |
457 | } |
458 | ||
459 | static void __exit au1xpsc_ac97_exit(void) | |
460 | { | |
3f4b783c | 461 | snd_soc_unregister_dai(&au1xpsc_ac97_dai); |
4a161d23 ML |
462 | } |
463 | ||
464 | module_init(au1xpsc_ac97_init); | |
465 | module_exit(au1xpsc_ac97_exit); | |
466 | ||
467 | MODULE_LICENSE("GPL"); | |
468 | MODULE_DESCRIPTION("Au12x0/Au1550 PSC AC97 ALSA ASoC audio driver"); | |
cdc65fbe | 469 | MODULE_AUTHOR("Manuel Lauss <manuel.lauss@gmail.com>"); |