diff options
Diffstat (limited to 'sound/soc/au1x/psc-ac97.c')
-rw-r--r-- | sound/soc/au1x/psc-ac97.c | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c new file mode 100644 index 000000000000..57facbad6825 --- /dev/null +++ b/sound/soc/au1x/psc-ac97.c | |||
@@ -0,0 +1,387 @@ | |||
1 | /* | ||
2 | * Au12x0/Au1550 PSC ALSA ASoC audio support. | ||
3 | * | ||
4 | * (c) 2007-2008 MSC Vertriebsges.m.b.H., | ||
5 | * Manuel Lauss <mano@roarinelk.homelinux.net> | ||
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> | ||
22 | #include <linux/suspend.h> | ||
23 | #include <sound/core.h> | ||
24 | #include <sound/pcm.h> | ||
25 | #include <sound/initval.h> | ||
26 | #include <sound/soc.h> | ||
27 | #include <asm/mach-au1x00/au1000.h> | ||
28 | #include <asm/mach-au1x00/au1xxx_psc.h> | ||
29 | |||
30 | #include "psc.h" | ||
31 | |||
32 | #define AC97_DIR \ | ||
33 | (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) | ||
34 | |||
35 | #define AC97_RATES \ | ||
36 | SNDRV_PCM_RATE_8000_48000 | ||
37 | |||
38 | #define AC97_FMTS \ | ||
39 | (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3BE) | ||
40 | |||
41 | #define AC97PCR_START(stype) \ | ||
42 | ((stype) == PCM_TX ? PSC_AC97PCR_TS : PSC_AC97PCR_RS) | ||
43 | #define AC97PCR_STOP(stype) \ | ||
44 | ((stype) == PCM_TX ? PSC_AC97PCR_TP : PSC_AC97PCR_RP) | ||
45 | #define AC97PCR_CLRFIFO(stype) \ | ||
46 | ((stype) == PCM_TX ? PSC_AC97PCR_TC : PSC_AC97PCR_RC) | ||
47 | |||
48 | /* instance data. There can be only one, MacLeod!!!! */ | ||
49 | static struct au1xpsc_audio_data *au1xpsc_ac97_workdata; | ||
50 | |||
51 | /* AC97 controller reads codec register */ | ||
52 | static unsigned short au1xpsc_ac97_read(struct snd_ac97 *ac97, | ||
53 | unsigned short reg) | ||
54 | { | ||
55 | /* FIXME */ | ||
56 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | ||
57 | unsigned short data, tmo; | ||
58 | |||
59 | au_writel(PSC_AC97CDC_RD | PSC_AC97CDC_INDX(reg), AC97_CDC(pscdata)); | ||
60 | au_sync(); | ||
61 | |||
62 | tmo = 1000; | ||
63 | while ((!(au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)) && --tmo) | ||
64 | udelay(2); | ||
65 | |||
66 | if (!tmo) | ||
67 | data = 0xffff; | ||
68 | else | ||
69 | data = au_readl(AC97_CDC(pscdata)) & 0xffff; | ||
70 | |||
71 | au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata)); | ||
72 | au_sync(); | ||
73 | |||
74 | return data; | ||
75 | } | ||
76 | |||
77 | /* AC97 controller writes to codec register */ | ||
78 | static void au1xpsc_ac97_write(struct snd_ac97 *ac97, unsigned short reg, | ||
79 | unsigned short val) | ||
80 | { | ||
81 | /* FIXME */ | ||
82 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | ||
83 | unsigned int tmo; | ||
84 | |||
85 | au_writel(PSC_AC97CDC_INDX(reg) | (val & 0xffff), AC97_CDC(pscdata)); | ||
86 | au_sync(); | ||
87 | tmo = 1000; | ||
88 | while ((!(au_readl(AC97_EVNT(pscdata)) & PSC_AC97EVNT_CD)) && --tmo) | ||
89 | au_sync(); | ||
90 | |||
91 | au_writel(PSC_AC97EVNT_CD, AC97_EVNT(pscdata)); | ||
92 | au_sync(); | ||
93 | } | ||
94 | |||
95 | /* AC97 controller asserts a warm reset */ | ||
96 | static void au1xpsc_ac97_warm_reset(struct snd_ac97 *ac97) | ||
97 | { | ||
98 | /* FIXME */ | ||
99 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | ||
100 | |||
101 | au_writel(PSC_AC97RST_SNC, AC97_RST(pscdata)); | ||
102 | au_sync(); | ||
103 | msleep(10); | ||
104 | au_writel(0, AC97_RST(pscdata)); | ||
105 | au_sync(); | ||
106 | } | ||
107 | |||
108 | static void au1xpsc_ac97_cold_reset(struct snd_ac97 *ac97) | ||
109 | { | ||
110 | /* FIXME */ | ||
111 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | ||
112 | int i; | ||
113 | |||
114 | /* disable PSC during cold reset */ | ||
115 | au_writel(0, AC97_CFG(au1xpsc_ac97_workdata)); | ||
116 | au_sync(); | ||
117 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(pscdata)); | ||
118 | au_sync(); | ||
119 | |||
120 | /* issue cold reset */ | ||
121 | au_writel(PSC_AC97RST_RST, AC97_RST(pscdata)); | ||
122 | au_sync(); | ||
123 | msleep(500); | ||
124 | au_writel(0, AC97_RST(pscdata)); | ||
125 | au_sync(); | ||
126 | |||
127 | /* enable PSC */ | ||
128 | au_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata)); | ||
129 | au_sync(); | ||
130 | |||
131 | /* wait for PSC to indicate it's ready */ | ||
132 | i = 100000; | ||
133 | while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_SR)) && (--i)) | ||
134 | au_sync(); | ||
135 | |||
136 | if (i == 0) { | ||
137 | printk(KERN_ERR "au1xpsc-ac97: PSC not ready!\n"); | ||
138 | return; | ||
139 | } | ||
140 | |||
141 | /* enable the ac97 function */ | ||
142 | au_writel(pscdata->cfg | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); | ||
143 | au_sync(); | ||
144 | |||
145 | /* wait for AC97 core to become ready */ | ||
146 | i = 100000; | ||
147 | while (!((au_readl(AC97_STAT(pscdata)) & PSC_AC97STAT_DR)) && (--i)) | ||
148 | au_sync(); | ||
149 | if (i == 0) | ||
150 | printk(KERN_ERR "au1xpsc-ac97: AC97 ctrl not ready\n"); | ||
151 | } | ||
152 | |||
153 | /* AC97 controller operations */ | ||
154 | struct snd_ac97_bus_ops soc_ac97_ops = { | ||
155 | .read = au1xpsc_ac97_read, | ||
156 | .write = au1xpsc_ac97_write, | ||
157 | .reset = au1xpsc_ac97_cold_reset, | ||
158 | .warm_reset = au1xpsc_ac97_warm_reset, | ||
159 | }; | ||
160 | EXPORT_SYMBOL_GPL(soc_ac97_ops); | ||
161 | |||
162 | static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream, | ||
163 | struct snd_pcm_hw_params *params) | ||
164 | { | ||
165 | /* FIXME */ | ||
166 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | ||
167 | unsigned long r, stat; | ||
168 | int chans, stype = SUBSTREAM_TYPE(substream); | ||
169 | |||
170 | chans = params_channels(params); | ||
171 | |||
172 | r = au_readl(AC97_CFG(pscdata)); | ||
173 | stat = au_readl(AC97_STAT(pscdata)); | ||
174 | |||
175 | /* already active? */ | ||
176 | if (stat & (PSC_AC97STAT_TB | PSC_AC97STAT_RB)) { | ||
177 | /* reject parameters not currently set up */ | ||
178 | if ((PSC_AC97CFG_GET_LEN(r) != params->msbits) || | ||
179 | (pscdata->rate != params_rate(params))) | ||
180 | return -EINVAL; | ||
181 | } else { | ||
182 | /* disable AC97 device controller first */ | ||
183 | au_writel(r & ~PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); | ||
184 | au_sync(); | ||
185 | |||
186 | /* set sample bitdepth: REG[24:21]=(BITS-2)/2 */ | ||
187 | r &= ~PSC_AC97CFG_LEN_MASK; | ||
188 | r |= PSC_AC97CFG_SET_LEN(params->msbits); | ||
189 | |||
190 | /* channels: enable slots for front L/R channel */ | ||
191 | if (stype == PCM_TX) { | ||
192 | r &= ~PSC_AC97CFG_TXSLOT_MASK; | ||
193 | r |= PSC_AC97CFG_TXSLOT_ENA(3); | ||
194 | r |= PSC_AC97CFG_TXSLOT_ENA(4); | ||
195 | } else { | ||
196 | r &= ~PSC_AC97CFG_RXSLOT_MASK; | ||
197 | r |= PSC_AC97CFG_RXSLOT_ENA(3); | ||
198 | r |= PSC_AC97CFG_RXSLOT_ENA(4); | ||
199 | } | ||
200 | |||
201 | /* finally enable the AC97 controller again */ | ||
202 | au_writel(r | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); | ||
203 | au_sync(); | ||
204 | |||
205 | pscdata->cfg = r; | ||
206 | pscdata->rate = params_rate(params); | ||
207 | } | ||
208 | |||
209 | return 0; | ||
210 | } | ||
211 | |||
212 | static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream, | ||
213 | int cmd) | ||
214 | { | ||
215 | /* FIXME */ | ||
216 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | ||
217 | int ret, stype = SUBSTREAM_TYPE(substream); | ||
218 | |||
219 | ret = 0; | ||
220 | |||
221 | switch (cmd) { | ||
222 | case SNDRV_PCM_TRIGGER_START: | ||
223 | case SNDRV_PCM_TRIGGER_RESUME: | ||
224 | au_writel(AC97PCR_START(stype), AC97_PCR(pscdata)); | ||
225 | au_sync(); | ||
226 | break; | ||
227 | case SNDRV_PCM_TRIGGER_STOP: | ||
228 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
229 | au_writel(AC97PCR_STOP(stype), AC97_PCR(pscdata)); | ||
230 | au_sync(); | ||
231 | break; | ||
232 | default: | ||
233 | ret = -EINVAL; | ||
234 | } | ||
235 | return ret; | ||
236 | } | ||
237 | |||
238 | static int au1xpsc_ac97_probe(struct platform_device *pdev, | ||
239 | struct snd_soc_dai *dai) | ||
240 | { | ||
241 | int ret; | ||
242 | struct resource *r; | ||
243 | unsigned long sel; | ||
244 | |||
245 | if (au1xpsc_ac97_workdata) | ||
246 | return -EBUSY; | ||
247 | |||
248 | au1xpsc_ac97_workdata = | ||
249 | kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL); | ||
250 | if (!au1xpsc_ac97_workdata) | ||
251 | return -ENOMEM; | ||
252 | |||
253 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
254 | if (!r) { | ||
255 | ret = -ENODEV; | ||
256 | goto out0; | ||
257 | } | ||
258 | |||
259 | ret = -EBUSY; | ||
260 | au1xpsc_ac97_workdata->ioarea = | ||
261 | request_mem_region(r->start, r->end - r->start + 1, | ||
262 | "au1xpsc_ac97"); | ||
263 | if (!au1xpsc_ac97_workdata->ioarea) | ||
264 | goto out0; | ||
265 | |||
266 | au1xpsc_ac97_workdata->mmio = ioremap(r->start, 0xffff); | ||
267 | if (!au1xpsc_ac97_workdata->mmio) | ||
268 | goto out1; | ||
269 | |||
270 | /* configuration: max dma trigger threshold, enable ac97 */ | ||
271 | au1xpsc_ac97_workdata->cfg = PSC_AC97CFG_RT_FIFO8 | | ||
272 | PSC_AC97CFG_TT_FIFO8 | | ||
273 | PSC_AC97CFG_DE_ENABLE; | ||
274 | |||
275 | /* preserve PSC clock source set up by platform (dev.platform_data | ||
276 | * is already occupied by soc layer) | ||
277 | */ | ||
278 | sel = au_readl(PSC_SEL(au1xpsc_ac97_workdata)) & PSC_SEL_CLK_MASK; | ||
279 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata)); | ||
280 | au_sync(); | ||
281 | au_writel(0, PSC_SEL(au1xpsc_ac97_workdata)); | ||
282 | au_sync(); | ||
283 | au_writel(PSC_SEL_PS_AC97MODE | sel, PSC_SEL(au1xpsc_ac97_workdata)); | ||
284 | au_sync(); | ||
285 | /* next up: cold reset. Dont check for PSC-ready now since | ||
286 | * there may not be any codec clock yet. | ||
287 | */ | ||
288 | |||
289 | return 0; | ||
290 | |||
291 | out1: | ||
292 | release_resource(au1xpsc_ac97_workdata->ioarea); | ||
293 | kfree(au1xpsc_ac97_workdata->ioarea); | ||
294 | out0: | ||
295 | kfree(au1xpsc_ac97_workdata); | ||
296 | au1xpsc_ac97_workdata = NULL; | ||
297 | return ret; | ||
298 | } | ||
299 | |||
300 | static void au1xpsc_ac97_remove(struct platform_device *pdev, | ||
301 | struct snd_soc_dai *dai) | ||
302 | { | ||
303 | /* disable PSC completely */ | ||
304 | au_writel(0, AC97_CFG(au1xpsc_ac97_workdata)); | ||
305 | au_sync(); | ||
306 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata)); | ||
307 | au_sync(); | ||
308 | |||
309 | iounmap(au1xpsc_ac97_workdata->mmio); | ||
310 | release_resource(au1xpsc_ac97_workdata->ioarea); | ||
311 | kfree(au1xpsc_ac97_workdata->ioarea); | ||
312 | kfree(au1xpsc_ac97_workdata); | ||
313 | au1xpsc_ac97_workdata = NULL; | ||
314 | } | ||
315 | |||
316 | static int au1xpsc_ac97_suspend(struct platform_device *pdev, | ||
317 | struct snd_soc_dai *dai) | ||
318 | { | ||
319 | /* save interesting registers and disable PSC */ | ||
320 | au1xpsc_ac97_workdata->pm[0] = | ||
321 | au_readl(PSC_SEL(au1xpsc_ac97_workdata)); | ||
322 | |||
323 | au_writel(0, AC97_CFG(au1xpsc_ac97_workdata)); | ||
324 | au_sync(); | ||
325 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata)); | ||
326 | au_sync(); | ||
327 | |||
328 | return 0; | ||
329 | } | ||
330 | |||
331 | static int au1xpsc_ac97_resume(struct platform_device *pdev, | ||
332 | struct snd_soc_dai *dai) | ||
333 | { | ||
334 | /* restore PSC clock config */ | ||
335 | au_writel(au1xpsc_ac97_workdata->pm[0] | PSC_SEL_PS_AC97MODE, | ||
336 | PSC_SEL(au1xpsc_ac97_workdata)); | ||
337 | au_sync(); | ||
338 | |||
339 | /* after this point the ac97 core will cold-reset the codec. | ||
340 | * During cold-reset the PSC is reinitialized and the last | ||
341 | * configuration set up in hw_params() is restored. | ||
342 | */ | ||
343 | return 0; | ||
344 | } | ||
345 | |||
346 | struct snd_soc_dai au1xpsc_ac97_dai = { | ||
347 | .name = "au1xpsc_ac97", | ||
348 | .type = SND_SOC_DAI_AC97, | ||
349 | .probe = au1xpsc_ac97_probe, | ||
350 | .remove = au1xpsc_ac97_remove, | ||
351 | .suspend = au1xpsc_ac97_suspend, | ||
352 | .resume = au1xpsc_ac97_resume, | ||
353 | .playback = { | ||
354 | .rates = AC97_RATES, | ||
355 | .formats = AC97_FMTS, | ||
356 | .channels_min = 2, | ||
357 | .channels_max = 2, | ||
358 | }, | ||
359 | .capture = { | ||
360 | .rates = AC97_RATES, | ||
361 | .formats = AC97_FMTS, | ||
362 | .channels_min = 2, | ||
363 | .channels_max = 2, | ||
364 | }, | ||
365 | .ops = { | ||
366 | .trigger = au1xpsc_ac97_trigger, | ||
367 | .hw_params = au1xpsc_ac97_hw_params, | ||
368 | }, | ||
369 | }; | ||
370 | EXPORT_SYMBOL_GPL(au1xpsc_ac97_dai); | ||
371 | |||
372 | static int __init au1xpsc_ac97_init(void) | ||
373 | { | ||
374 | au1xpsc_ac97_workdata = NULL; | ||
375 | return 0; | ||
376 | } | ||
377 | |||
378 | static void __exit au1xpsc_ac97_exit(void) | ||
379 | { | ||
380 | } | ||
381 | |||
382 | module_init(au1xpsc_ac97_init); | ||
383 | module_exit(au1xpsc_ac97_exit); | ||
384 | |||
385 | MODULE_LICENSE("GPL"); | ||
386 | MODULE_DESCRIPTION("Au12x0/Au1550 PSC AC97 ALSA ASoC audio driver"); | ||
387 | MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>"); | ||