]>
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 I2S 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 | * NOTE: so far only PSC slave mode (bit- and frameclock) is supported. | |
17 | */ | |
18 | ||
19 | #include <linux/init.h> | |
20 | #include <linux/module.h> | |
21 | #include <linux/suspend.h> | |
22 | #include <sound/core.h> | |
23 | #include <sound/pcm.h> | |
24 | #include <sound/initval.h> | |
25 | #include <sound/soc.h> | |
26 | #include <asm/mach-au1x00/au1000.h> | |
27 | #include <asm/mach-au1x00/au1xxx_psc.h> | |
28 | ||
29 | #include "psc.h" | |
30 | ||
31 | /* supported I2S DAI hardware formats */ | |
32 | #define AU1XPSC_I2S_DAIFMT \ | |
33 | (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | \ | |
34 | SND_SOC_DAIFMT_NB_NF) | |
35 | ||
36 | /* supported I2S direction */ | |
37 | #define AU1XPSC_I2S_DIR \ | |
38 | (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) | |
39 | ||
40 | #define AU1XPSC_I2S_RATES \ | |
41 | SNDRV_PCM_RATE_8000_192000 | |
42 | ||
43 | #define AU1XPSC_I2S_FMTS \ | |
44 | (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) | |
45 | ||
46 | #define I2SSTAT_BUSY(stype) \ | |
47 | ((stype) == PCM_TX ? PSC_I2SSTAT_TB : PSC_I2SSTAT_RB) | |
48 | #define I2SPCR_START(stype) \ | |
49 | ((stype) == PCM_TX ? PSC_I2SPCR_TS : PSC_I2SPCR_RS) | |
50 | #define I2SPCR_STOP(stype) \ | |
51 | ((stype) == PCM_TX ? PSC_I2SPCR_TP : PSC_I2SPCR_RP) | |
52 | #define I2SPCR_CLRFIFO(stype) \ | |
53 | ((stype) == PCM_TX ? PSC_I2SPCR_TC : PSC_I2SPCR_RC) | |
54 | ||
55 | ||
56 | /* instance data. There can be only one, MacLeod!!!! */ | |
57 | static struct au1xpsc_audio_data *au1xpsc_i2s_workdata; | |
58 | ||
59 | static int au1xpsc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, | |
60 | unsigned int fmt) | |
61 | { | |
62 | struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata; | |
63 | unsigned long ct; | |
64 | int ret; | |
65 | ||
66 | ret = -EINVAL; | |
67 | ||
68 | ct = pscdata->cfg; | |
69 | ||
70 | ct &= ~(PSC_I2SCFG_XM | PSC_I2SCFG_MLJ); /* left-justified */ | |
71 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | |
72 | case SND_SOC_DAIFMT_I2S: | |
73 | ct |= PSC_I2SCFG_XM; /* enable I2S mode */ | |
74 | break; | |
75 | case SND_SOC_DAIFMT_MSB: | |
76 | break; | |
77 | case SND_SOC_DAIFMT_LSB: | |
78 | ct |= PSC_I2SCFG_MLJ; /* LSB (right-) justified */ | |
79 | break; | |
80 | default: | |
81 | goto out; | |
82 | } | |
83 | ||
84 | ct &= ~(PSC_I2SCFG_BI | PSC_I2SCFG_WI); /* IB-IF */ | |
85 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | |
86 | case SND_SOC_DAIFMT_NB_NF: | |
87 | ct |= PSC_I2SCFG_BI | PSC_I2SCFG_WI; | |
88 | break; | |
89 | case SND_SOC_DAIFMT_NB_IF: | |
90 | ct |= PSC_I2SCFG_BI; | |
91 | break; | |
92 | case SND_SOC_DAIFMT_IB_NF: | |
93 | ct |= PSC_I2SCFG_WI; | |
94 | break; | |
95 | case SND_SOC_DAIFMT_IB_IF: | |
96 | break; | |
97 | default: | |
98 | goto out; | |
99 | } | |
100 | ||
101 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | |
102 | case SND_SOC_DAIFMT_CBM_CFM: /* CODEC master */ | |
103 | ct |= PSC_I2SCFG_MS; /* PSC I2S slave mode */ | |
104 | break; | |
105 | case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */ | |
106 | ct &= ~PSC_I2SCFG_MS; /* PSC I2S Master mode */ | |
107 | break; | |
108 | default: | |
109 | goto out; | |
110 | } | |
111 | ||
112 | pscdata->cfg = ct; | |
113 | ret = 0; | |
114 | out: | |
115 | return ret; | |
116 | } | |
117 | ||
118 | static int au1xpsc_i2s_hw_params(struct snd_pcm_substream *substream, | |
dee89c4d MB |
119 | struct snd_pcm_hw_params *params, |
120 | struct snd_soc_dai *dai) | |
4a161d23 ML |
121 | { |
122 | struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata; | |
123 | ||
124 | int cfgbits; | |
125 | unsigned long stat; | |
126 | ||
127 | /* check if the PSC is already streaming data */ | |
128 | stat = au_readl(I2S_STAT(pscdata)); | |
129 | if (stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB)) { | |
130 | /* reject parameters not currently set up in hardware */ | |
131 | cfgbits = au_readl(I2S_CFG(pscdata)); | |
132 | if ((PSC_I2SCFG_GET_LEN(cfgbits) != params->msbits) || | |
133 | (params_rate(params) != pscdata->rate)) | |
134 | return -EINVAL; | |
135 | } else { | |
136 | /* set sample bitdepth */ | |
137 | pscdata->cfg &= ~(0x1f << 4); | |
138 | pscdata->cfg |= PSC_I2SCFG_SET_LEN(params->msbits); | |
139 | /* remember current rate for other stream */ | |
140 | pscdata->rate = params_rate(params); | |
141 | } | |
142 | return 0; | |
143 | } | |
144 | ||
145 | /* Configure PSC late: on my devel systems the codec is I2S master and | |
146 | * supplies the i2sbitclock __AND__ i2sMclk (!) to the PSC unit. ASoC | |
147 | * uses aggressive PM and switches the codec off when it is not in use | |
148 | * which also means the PSC unit doesn't get any clocks and is therefore | |
149 | * dead. That's why this chunk here gets called from the trigger callback | |
150 | * because I can be reasonably certain the codec is driving the clocks. | |
151 | */ | |
152 | static int au1xpsc_i2s_configure(struct au1xpsc_audio_data *pscdata) | |
153 | { | |
154 | unsigned long tmo; | |
155 | ||
156 | /* bring PSC out of sleep, and configure I2S unit */ | |
157 | au_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata)); | |
158 | au_sync(); | |
159 | ||
160 | tmo = 1000000; | |
161 | while (!(au_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_SR) && tmo) | |
162 | tmo--; | |
163 | ||
164 | if (!tmo) | |
165 | goto psc_err; | |
166 | ||
167 | au_writel(0, I2S_CFG(pscdata)); | |
168 | au_sync(); | |
169 | au_writel(pscdata->cfg | PSC_I2SCFG_DE_ENABLE, I2S_CFG(pscdata)); | |
170 | au_sync(); | |
171 | ||
172 | /* wait for I2S controller to become ready */ | |
173 | tmo = 1000000; | |
174 | while (!(au_readl(I2S_STAT(pscdata)) & PSC_I2SSTAT_DR) && tmo) | |
175 | tmo--; | |
176 | ||
177 | if (tmo) | |
178 | return 0; | |
179 | ||
180 | psc_err: | |
181 | au_writel(0, I2S_CFG(pscdata)); | |
182 | au_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata)); | |
183 | au_sync(); | |
184 | return -ETIMEDOUT; | |
185 | } | |
186 | ||
187 | static int au1xpsc_i2s_start(struct au1xpsc_audio_data *pscdata, int stype) | |
188 | { | |
189 | unsigned long tmo, stat; | |
190 | int ret; | |
191 | ||
192 | ret = 0; | |
193 | ||
194 | /* if both TX and RX are idle, configure the PSC */ | |
195 | stat = au_readl(I2S_STAT(pscdata)); | |
196 | if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) { | |
197 | ret = au1xpsc_i2s_configure(pscdata); | |
198 | if (ret) | |
199 | goto out; | |
200 | } | |
201 | ||
202 | au_writel(I2SPCR_CLRFIFO(stype), I2S_PCR(pscdata)); | |
203 | au_sync(); | |
204 | au_writel(I2SPCR_START(stype), I2S_PCR(pscdata)); | |
205 | au_sync(); | |
206 | ||
207 | /* wait for start confirmation */ | |
208 | tmo = 1000000; | |
209 | while (!(au_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo) | |
210 | tmo--; | |
211 | ||
212 | if (!tmo) { | |
213 | au_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata)); | |
214 | au_sync(); | |
215 | ret = -ETIMEDOUT; | |
216 | } | |
217 | out: | |
218 | return ret; | |
219 | } | |
220 | ||
221 | static int au1xpsc_i2s_stop(struct au1xpsc_audio_data *pscdata, int stype) | |
222 | { | |
223 | unsigned long tmo, stat; | |
224 | ||
225 | au_writel(I2SPCR_STOP(stype), I2S_PCR(pscdata)); | |
226 | au_sync(); | |
227 | ||
228 | /* wait for stop confirmation */ | |
229 | tmo = 1000000; | |
230 | while ((au_readl(I2S_STAT(pscdata)) & I2SSTAT_BUSY(stype)) && tmo) | |
231 | tmo--; | |
232 | ||
233 | /* if both TX and RX are idle, disable PSC */ | |
234 | stat = au_readl(I2S_STAT(pscdata)); | |
2b30a55d | 235 | if (!(stat & (PSC_I2SSTAT_TB | PSC_I2SSTAT_RB))) { |
4a161d23 ML |
236 | au_writel(0, I2S_CFG(pscdata)); |
237 | au_sync(); | |
238 | au_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata)); | |
239 | au_sync(); | |
240 | } | |
241 | return 0; | |
242 | } | |
243 | ||
dee89c4d MB |
244 | static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, |
245 | struct snd_soc_dai *dai) | |
4a161d23 ML |
246 | { |
247 | struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata; | |
248 | int ret, stype = SUBSTREAM_TYPE(substream); | |
249 | ||
250 | switch (cmd) { | |
251 | case SNDRV_PCM_TRIGGER_START: | |
252 | case SNDRV_PCM_TRIGGER_RESUME: | |
253 | ret = au1xpsc_i2s_start(pscdata, stype); | |
254 | break; | |
255 | case SNDRV_PCM_TRIGGER_STOP: | |
256 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
257 | ret = au1xpsc_i2s_stop(pscdata, stype); | |
258 | break; | |
259 | default: | |
260 | ret = -EINVAL; | |
261 | } | |
262 | return ret; | |
263 | } | |
264 | ||
265 | static int au1xpsc_i2s_probe(struct platform_device *pdev, | |
266 | struct snd_soc_dai *dai) | |
267 | { | |
268 | struct resource *r; | |
269 | unsigned long sel; | |
270 | int ret; | |
271 | ||
272 | if (au1xpsc_i2s_workdata) | |
273 | return -EBUSY; | |
274 | ||
275 | au1xpsc_i2s_workdata = | |
276 | kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL); | |
277 | if (!au1xpsc_i2s_workdata) | |
278 | return -ENOMEM; | |
279 | ||
280 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
281 | if (!r) { | |
282 | ret = -ENODEV; | |
283 | goto out0; | |
284 | } | |
285 | ||
286 | ret = -EBUSY; | |
287 | au1xpsc_i2s_workdata->ioarea = | |
288 | request_mem_region(r->start, r->end - r->start + 1, | |
289 | "au1xpsc_i2s"); | |
290 | if (!au1xpsc_i2s_workdata->ioarea) | |
291 | goto out0; | |
292 | ||
293 | au1xpsc_i2s_workdata->mmio = ioremap(r->start, 0xffff); | |
294 | if (!au1xpsc_i2s_workdata->mmio) | |
295 | goto out1; | |
296 | ||
297 | /* preserve PSC clock source set up by platform (dev.platform_data | |
298 | * is already occupied by soc layer) | |
299 | */ | |
300 | sel = au_readl(PSC_SEL(au1xpsc_i2s_workdata)) & PSC_SEL_CLK_MASK; | |
301 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata)); | |
302 | au_sync(); | |
303 | au_writel(PSC_SEL_PS_I2SMODE | sel, PSC_SEL(au1xpsc_i2s_workdata)); | |
304 | au_writel(0, I2S_CFG(au1xpsc_i2s_workdata)); | |
305 | au_sync(); | |
306 | ||
307 | /* preconfigure: set max rx/tx fifo depths */ | |
308 | au1xpsc_i2s_workdata->cfg |= | |
309 | PSC_I2SCFG_RT_FIFO8 | PSC_I2SCFG_TT_FIFO8; | |
310 | ||
311 | /* don't wait for I2S core to become ready now; clocks may not | |
312 | * be running yet; depending on clock input for PSC a wait might | |
313 | * time out. | |
314 | */ | |
315 | ||
316 | return 0; | |
317 | ||
318 | out1: | |
319 | release_resource(au1xpsc_i2s_workdata->ioarea); | |
320 | kfree(au1xpsc_i2s_workdata->ioarea); | |
321 | out0: | |
322 | kfree(au1xpsc_i2s_workdata); | |
323 | au1xpsc_i2s_workdata = NULL; | |
324 | return ret; | |
325 | } | |
326 | ||
327 | static void au1xpsc_i2s_remove(struct platform_device *pdev, | |
328 | struct snd_soc_dai *dai) | |
329 | { | |
330 | au_writel(0, I2S_CFG(au1xpsc_i2s_workdata)); | |
331 | au_sync(); | |
332 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata)); | |
333 | au_sync(); | |
334 | ||
335 | iounmap(au1xpsc_i2s_workdata->mmio); | |
336 | release_resource(au1xpsc_i2s_workdata->ioarea); | |
337 | kfree(au1xpsc_i2s_workdata->ioarea); | |
338 | kfree(au1xpsc_i2s_workdata); | |
339 | au1xpsc_i2s_workdata = NULL; | |
340 | } | |
341 | ||
dc7d7b83 | 342 | static int au1xpsc_i2s_suspend(struct snd_soc_dai *cpu_dai) |
4a161d23 ML |
343 | { |
344 | /* save interesting register and disable PSC */ | |
345 | au1xpsc_i2s_workdata->pm[0] = | |
346 | au_readl(PSC_SEL(au1xpsc_i2s_workdata)); | |
347 | ||
348 | au_writel(0, I2S_CFG(au1xpsc_i2s_workdata)); | |
349 | au_sync(); | |
350 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata)); | |
351 | au_sync(); | |
352 | ||
353 | return 0; | |
354 | } | |
355 | ||
dc7d7b83 | 356 | static int au1xpsc_i2s_resume(struct snd_soc_dai *cpu_dai) |
4a161d23 ML |
357 | { |
358 | /* select I2S mode and PSC clock */ | |
359 | au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata)); | |
360 | au_sync(); | |
361 | au_writel(0, PSC_SEL(au1xpsc_i2s_workdata)); | |
362 | au_sync(); | |
363 | au_writel(au1xpsc_i2s_workdata->pm[0], | |
364 | PSC_SEL(au1xpsc_i2s_workdata)); | |
365 | au_sync(); | |
366 | ||
367 | return 0; | |
368 | } | |
369 | ||
6335d055 EM |
370 | static struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = { |
371 | .trigger = au1xpsc_i2s_trigger, | |
372 | .hw_params = au1xpsc_i2s_hw_params, | |
373 | .set_fmt = au1xpsc_i2s_set_fmt, | |
374 | }; | |
375 | ||
4a161d23 ML |
376 | struct snd_soc_dai au1xpsc_i2s_dai = { |
377 | .name = "au1xpsc_i2s", | |
4a161d23 ML |
378 | .probe = au1xpsc_i2s_probe, |
379 | .remove = au1xpsc_i2s_remove, | |
380 | .suspend = au1xpsc_i2s_suspend, | |
381 | .resume = au1xpsc_i2s_resume, | |
382 | .playback = { | |
383 | .rates = AU1XPSC_I2S_RATES, | |
384 | .formats = AU1XPSC_I2S_FMTS, | |
385 | .channels_min = 2, | |
386 | .channels_max = 8, /* 2 without external help */ | |
387 | }, | |
388 | .capture = { | |
389 | .rates = AU1XPSC_I2S_RATES, | |
390 | .formats = AU1XPSC_I2S_FMTS, | |
391 | .channels_min = 2, | |
392 | .channels_max = 8, /* 2 without external help */ | |
393 | }, | |
6335d055 | 394 | .ops = &au1xpsc_i2s_dai_ops, |
4a161d23 ML |
395 | }; |
396 | EXPORT_SYMBOL(au1xpsc_i2s_dai); | |
397 | ||
398 | static int __init au1xpsc_i2s_init(void) | |
399 | { | |
400 | au1xpsc_i2s_workdata = NULL; | |
3f4b783c | 401 | return snd_soc_register_dai(&au1xpsc_i2s_dai); |
4a161d23 ML |
402 | } |
403 | ||
404 | static void __exit au1xpsc_i2s_exit(void) | |
405 | { | |
3f4b783c | 406 | snd_soc_unregister_dai(&au1xpsc_i2s_dai); |
4a161d23 ML |
407 | } |
408 | ||
409 | module_init(au1xpsc_i2s_init); | |
410 | module_exit(au1xpsc_i2s_exit); | |
411 | ||
412 | MODULE_LICENSE("GPL"); | |
413 | MODULE_DESCRIPTION("Au12x0/Au1550 PSC I2S ALSA ASoC audio driver"); | |
414 | MODULE_AUTHOR("Manuel Lauss <mano@roarinelk.homelinux.net>"); |