Commit | Line | Data |
---|---|---|
4a161d23 ML |
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, | |
dee89c4d MB |
163 | struct snd_pcm_hw_params *params, |
164 | struct snd_soc_dai *dai) | |
4a161d23 ML |
165 | { |
166 | /* FIXME */ | |
167 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | |
168 | unsigned long r, stat; | |
169 | int chans, stype = SUBSTREAM_TYPE(substream); | |
170 | ||
171 | chans = params_channels(params); | |
172 | ||
173 | r = au_readl(AC97_CFG(pscdata)); | |
174 | stat = au_readl(AC97_STAT(pscdata)); | |
175 | ||
176 | /* already active? */ | |
177 | if (stat & (PSC_AC97STAT_TB | PSC_AC97STAT_RB)) { | |
178 | /* reject parameters not currently set up */ | |
179 | if ((PSC_AC97CFG_GET_LEN(r) != params->msbits) || | |
180 | (pscdata->rate != params_rate(params))) | |
181 | return -EINVAL; | |
182 | } else { | |
183 | /* disable AC97 device controller first */ | |
184 | au_writel(r & ~PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); | |
185 | au_sync(); | |
186 | ||
187 | /* set sample bitdepth: REG[24:21]=(BITS-2)/2 */ | |
188 | r &= ~PSC_AC97CFG_LEN_MASK; | |
189 | r |= PSC_AC97CFG_SET_LEN(params->msbits); | |
190 | ||
191 | /* channels: enable slots for front L/R channel */ | |
192 | if (stype == PCM_TX) { | |
193 | r &= ~PSC_AC97CFG_TXSLOT_MASK; | |
194 | r |= PSC_AC97CFG_TXSLOT_ENA(3); | |
195 | r |= PSC_AC97CFG_TXSLOT_ENA(4); | |
196 | } else { | |
197 | r &= ~PSC_AC97CFG_RXSLOT_MASK; | |
198 | r |= PSC_AC97CFG_RXSLOT_ENA(3); | |
199 | r |= PSC_AC97CFG_RXSLOT_ENA(4); | |
200 | } | |
201 | ||
202 | /* finally enable the AC97 controller again */ | |
203 | au_writel(r | PSC_AC97CFG_DE_ENABLE, AC97_CFG(pscdata)); | |
204 | au_sync(); | |
205 | ||
206 | pscdata->cfg = r; | |
207 | pscdata->rate = params_rate(params); | |
208 | } | |
209 | ||
210 | return 0; | |
211 | } | |
212 | ||
213 | static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream, | |
dee89c4d | 214 | int cmd, struct snd_soc_dai *dai) |
4a161d23 ML |
215 | { |
216 | /* FIXME */ | |
217 | struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata; | |
218 | int ret, stype = SUBSTREAM_TYPE(substream); | |
219 | ||
220 | ret = 0; | |
221 | ||
222 | switch (cmd) { | |
223 | case SNDRV_PCM_TRIGGER_START: | |
224 | case SNDRV_PCM_TRIGGER_RESUME: | |
225 | au_writel(AC97PCR_START(stype), AC97_PCR(pscdata)); | |
226 | au_sync(); | |
227 | break; | |
228 | case SNDRV_PCM_TRIGGER_STOP: | |
229 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
230 | au_writel(AC97PCR_STOP(stype), AC97_PCR(pscdata)); | |
231 | au_sync(); | |
232 | break; | |
233 | default: | |
234 | ret = -EINVAL; | |
235 | } | |
236 | return ret; | |
237 | } | |
238 | ||
239 | static int au1xpsc_ac97_probe(struct platform_device *pdev, | |
240 | struct snd_soc_dai *dai) | |
241 | { | |
242 | int ret; | |
243 | struct resource *r; | |
244 | unsigned long sel; | |
245 | ||
246 | if (au1xpsc_ac97_workdata) | |
247 | return -EBUSY; | |
248 | ||
249 | au1xpsc_ac97_workdata = | |
250 | kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL); | |
251 | if (!au1xpsc_ac97_workdata) | |
252 | return -ENOMEM; | |
253 | ||
254 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
255 | if (!r) { | |
256 | ret = -ENODEV; | |
257 | goto out0; | |
258 | } | |
259 | ||
260 | ret = -EBUSY; | |
261 | au1xpsc_ac97_workdata->ioarea = | |
262 | request_mem_region(r->start, r->end - r->start + 1, | |
263 | "au1xpsc_ac97"); | |
264 | if (!au1xpsc_ac97_workdata->ioarea) | |
265 | goto out0; | |
266 | ||
267 | au1xpsc_ac97_workdata->mmio = ioremap(r->start, 0xffff); | |
268 | if (!au1xpsc_ac97_workdata->mmio) | |
269 | goto out1; | |
270 | ||
271 | /* configuration: max dma trigger threshold, enable ac97 */ | |
272 | au1xpsc_ac97_workdata->cfg = PSC_AC97CFG_RT_FIFO8 | | |
273 | PSC_AC97CFG_TT_FIFO8 | | |
274 | PSC_AC97CFG_DE_ENABLE; | |
275 | ||
276 | /* preserve PSC clock source set up by platform (dev.platform_data | |
277 | * is already occupied by soc layer) | |
278 | */ | |
279 | sel = au_readl(PSC_SEL(au1xpsc_ac97_workdata)) & PSC_SEL_CLK_MASK; | |
280 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata)); | |
281 | au_sync(); | |
282 | au_writel(0, PSC_SEL(au1xpsc_ac97_workdata)); | |
283 | au_sync(); | |
284 | au_writel(PSC_SEL_PS_AC97MODE | sel, PSC_SEL(au1xpsc_ac97_workdata)); | |
285 | au_sync(); | |
286 | /* next up: cold reset. Dont check for PSC-ready now since | |
287 | * there may not be any codec clock yet. | |
288 | */ | |
289 | ||
290 | return 0; | |
291 | ||
292 | out1: | |
293 | release_resource(au1xpsc_ac97_workdata->ioarea); | |
294 | kfree(au1xpsc_ac97_workdata->ioarea); | |
295 | out0: | |
296 | kfree(au1xpsc_ac97_workdata); | |
297 | au1xpsc_ac97_workdata = NULL; | |
298 | return ret; | |
299 | } | |
300 | ||
301 | static void au1xpsc_ac97_remove(struct platform_device *pdev, | |
302 | struct snd_soc_dai *dai) | |
303 | { | |
304 | /* disable PSC completely */ | |
305 | au_writel(0, AC97_CFG(au1xpsc_ac97_workdata)); | |
306 | au_sync(); | |
307 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata)); | |
308 | au_sync(); | |
309 | ||
310 | iounmap(au1xpsc_ac97_workdata->mmio); | |
311 | release_resource(au1xpsc_ac97_workdata->ioarea); | |
312 | kfree(au1xpsc_ac97_workdata->ioarea); | |
313 | kfree(au1xpsc_ac97_workdata); | |
314 | au1xpsc_ac97_workdata = NULL; | |
315 | } | |
316 | ||
317 | static int au1xpsc_ac97_suspend(struct platform_device *pdev, | |
318 | struct snd_soc_dai *dai) | |
319 | { | |
320 | /* save interesting registers and disable PSC */ | |
321 | au1xpsc_ac97_workdata->pm[0] = | |
322 | au_readl(PSC_SEL(au1xpsc_ac97_workdata)); | |
323 | ||
324 | au_writel(0, AC97_CFG(au1xpsc_ac97_workdata)); | |
325 | au_sync(); | |
326 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata)); | |
327 | au_sync(); | |
328 | ||
329 | return 0; | |
330 | } | |
331 | ||
332 | static int au1xpsc_ac97_resume(struct platform_device *pdev, | |
333 | struct snd_soc_dai *dai) | |
334 | { | |
335 | /* restore PSC clock config */ | |
336 | au_writel(au1xpsc_ac97_workdata->pm[0] | PSC_SEL_PS_AC97MODE, | |
337 | PSC_SEL(au1xpsc_ac97_workdata)); | |
338 | au_sync(); | |
339 | ||
340 | /* after this point the ac97 core will cold-reset the codec. | |
341 | * During cold-reset the PSC is reinitialized and the last | |
342 | * configuration set up in hw_params() is restored. | |
343 | */ | |
344 | return 0; | |
345 | } | |
346 | ||
347 | struct snd_soc_dai au1xpsc_ac97_dai = { | |
348 | .name = "au1xpsc_ac97", | |
3ba9e10a | 349 | .ac97_control = 1, |
4a161d23 ML |
350 | .probe = au1xpsc_ac97_probe, |
351 | .remove = au1xpsc_ac97_remove, | |
352 | .suspend = au1xpsc_ac97_suspend, | |
353 | .resume = au1xpsc_ac97_resume, | |
354 | .playback = { | |
355 | .rates = AC97_RATES, | |
356 | .formats = AC97_FMTS, | |
357 | .channels_min = 2, | |
358 | .channels_max = 2, | |
359 | }, | |
360 | .capture = { | |
361 | .rates = AC97_RATES, | |
362 | .formats = AC97_FMTS, | |
363 | .channels_min = 2, | |
364 | .channels_max = 2, | |
365 | }, | |
366 | .ops = { | |
367 | .trigger = au1xpsc_ac97_trigger, | |
368 | .hw_params = au1xpsc_ac97_hw_params, | |
369 | }, | |
370 | }; | |
371 | EXPORT_SYMBOL_GPL(au1xpsc_ac97_dai); | |
372 | ||
373 | static int __init au1xpsc_ac97_init(void) | |
374 | { | |
375 | au1xpsc_ac97_workdata = NULL; | |
376 | return 0; | |
377 | } | |
378 | ||
379 | static void __exit au1xpsc_ac97_exit(void) | |
380 | { | |
381 | } | |
382 | ||
383 | module_init(au1xpsc_ac97_init); | |
384 | module_exit(au1xpsc_ac97_exit); | |
385 | ||
386 | MODULE_LICENSE("GPL"); | |
387 | MODULE_DESCRIPTION("Au12x0/Au1550 PSC AC97 ALSA ASoC audio driver"); | |
388 | MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>"); |