diff options
Diffstat (limited to 'sound/soc/atmel/playpaq_wm8510.c')
-rw-r--r-- | sound/soc/atmel/playpaq_wm8510.c | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/sound/soc/atmel/playpaq_wm8510.c b/sound/soc/atmel/playpaq_wm8510.c new file mode 100644 index 000000000000..5b07cf7ea4e7 --- /dev/null +++ b/sound/soc/atmel/playpaq_wm8510.c | |||
@@ -0,0 +1,513 @@ | |||
1 | /* sound/soc/at32/playpaq_wm8510.c | ||
2 | * ASoC machine driver for PlayPaq using WM8510 codec | ||
3 | * | ||
4 | * Copyright (C) 2008 Long Range Systems | ||
5 | * Geoffrey Wossum <gwossum@acm.org> | ||
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 | * This code is largely inspired by sound/soc/at91/eti_b1_wm8731.c | ||
12 | * | ||
13 | * NOTE: If you don't have the AT32 enhanced portmux configured (which | ||
14 | * isn't currently in the mainline or Atmel patched kernel), you will | ||
15 | * need to set the MCLK pin (PA30) to peripheral A in your board initialization | ||
16 | * code. Something like: | ||
17 | * at32_select_periph(GPIO_PIN_PA(30), GPIO_PERIPH_A, 0); | ||
18 | * | ||
19 | */ | ||
20 | |||
21 | /* #define DEBUG */ | ||
22 | |||
23 | #include <linux/module.h> | ||
24 | #include <linux/moduleparam.h> | ||
25 | #include <linux/version.h> | ||
26 | #include <linux/kernel.h> | ||
27 | #include <linux/errno.h> | ||
28 | #include <linux/clk.h> | ||
29 | #include <linux/timer.h> | ||
30 | #include <linux/interrupt.h> | ||
31 | #include <linux/platform_device.h> | ||
32 | |||
33 | #include <sound/core.h> | ||
34 | #include <sound/pcm.h> | ||
35 | #include <sound/pcm_params.h> | ||
36 | #include <sound/soc.h> | ||
37 | #include <sound/soc-dapm.h> | ||
38 | |||
39 | #include <mach/at32ap700x.h> | ||
40 | #include <mach/portmux.h> | ||
41 | |||
42 | #include "../codecs/wm8510.h" | ||
43 | #include "atmel-pcm.h" | ||
44 | #include "atmel_ssc_dai.h" | ||
45 | |||
46 | |||
47 | /*-------------------------------------------------------------------------*\ | ||
48 | * constants | ||
49 | \*-------------------------------------------------------------------------*/ | ||
50 | #define MCLK_PIN GPIO_PIN_PA(30) | ||
51 | #define MCLK_PERIPH GPIO_PERIPH_A | ||
52 | |||
53 | |||
54 | /*-------------------------------------------------------------------------*\ | ||
55 | * data types | ||
56 | \*-------------------------------------------------------------------------*/ | ||
57 | /* SSC clocking data */ | ||
58 | struct ssc_clock_data { | ||
59 | /* CMR div */ | ||
60 | unsigned int cmr_div; | ||
61 | |||
62 | /* Frame period (as needed by xCMR.PERIOD) */ | ||
63 | unsigned int period; | ||
64 | |||
65 | /* The SSC clock rate these settings where calculated for */ | ||
66 | unsigned long ssc_rate; | ||
67 | }; | ||
68 | |||
69 | |||
70 | /*-------------------------------------------------------------------------*\ | ||
71 | * module data | ||
72 | \*-------------------------------------------------------------------------*/ | ||
73 | static struct clk *_gclk0; | ||
74 | static struct clk *_pll0; | ||
75 | |||
76 | #define CODEC_CLK (_gclk0) | ||
77 | |||
78 | |||
79 | /*-------------------------------------------------------------------------*\ | ||
80 | * Sound SOC operations | ||
81 | \*-------------------------------------------------------------------------*/ | ||
82 | #if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE | ||
83 | static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock( | ||
84 | struct snd_pcm_hw_params *params, | ||
85 | struct snd_soc_dai *cpu_dai) | ||
86 | { | ||
87 | struct at32_ssc_info *ssc_p = cpu_dai->private_data; | ||
88 | struct ssc_device *ssc = ssc_p->ssc; | ||
89 | struct ssc_clock_data cd; | ||
90 | unsigned int rate, width_bits, channels; | ||
91 | unsigned int bitrate, ssc_div; | ||
92 | unsigned actual_rate; | ||
93 | |||
94 | |||
95 | /* | ||
96 | * Figure out required bitrate | ||
97 | */ | ||
98 | rate = params_rate(params); | ||
99 | channels = params_channels(params); | ||
100 | width_bits = snd_pcm_format_physical_width(params_format(params)); | ||
101 | bitrate = rate * width_bits * channels; | ||
102 | |||
103 | |||
104 | /* | ||
105 | * Figure out required SSC divider and period for required bitrate | ||
106 | */ | ||
107 | cd.ssc_rate = clk_get_rate(ssc->clk); | ||
108 | ssc_div = cd.ssc_rate / bitrate; | ||
109 | cd.cmr_div = ssc_div / 2; | ||
110 | if (ssc_div & 1) { | ||
111 | /* round cmr_div up */ | ||
112 | cd.cmr_div++; | ||
113 | } | ||
114 | cd.period = width_bits - 1; | ||
115 | |||
116 | |||
117 | /* | ||
118 | * Find actual rate, compare to requested rate | ||
119 | */ | ||
120 | actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1)); | ||
121 | pr_debug("playpaq_wm8510: Request rate = %d, actual rate = %d\n", | ||
122 | rate, actual_rate); | ||
123 | |||
124 | |||
125 | return cd; | ||
126 | } | ||
127 | #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ | ||
128 | |||
129 | |||
130 | |||
131 | static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream, | ||
132 | struct snd_pcm_hw_params *params) | ||
133 | { | ||
134 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
135 | struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; | ||
136 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
137 | struct at32_ssc_info *ssc_p = cpu_dai->private_data; | ||
138 | struct ssc_device *ssc = ssc_p->ssc; | ||
139 | unsigned int pll_out = 0, bclk = 0, mclk_div = 0; | ||
140 | int ret; | ||
141 | |||
142 | |||
143 | /* Due to difficulties with getting the correct clocks from the AT32's | ||
144 | * PLL0, we're going to let the CODEC be in charge of all the clocks | ||
145 | */ | ||
146 | #if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE | ||
147 | const unsigned int fmt = (SND_SOC_DAIFMT_I2S | | ||
148 | SND_SOC_DAIFMT_NB_NF | | ||
149 | SND_SOC_DAIFMT_CBM_CFM); | ||
150 | #else | ||
151 | struct ssc_clock_data cd; | ||
152 | const unsigned int fmt = (SND_SOC_DAIFMT_I2S | | ||
153 | SND_SOC_DAIFMT_NB_NF | | ||
154 | SND_SOC_DAIFMT_CBS_CFS); | ||
155 | #endif | ||
156 | |||
157 | if (ssc == NULL) { | ||
158 | pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n"); | ||
159 | return -EINVAL; | ||
160 | } | ||
161 | |||
162 | |||
163 | /* | ||
164 | * Figure out PLL and BCLK dividers for WM8510 | ||
165 | */ | ||
166 | switch (params_rate(params)) { | ||
167 | case 48000: | ||
168 | pll_out = 12288000; | ||
169 | mclk_div = WM8510_MCLKDIV_1; | ||
170 | bclk = WM8510_BCLKDIV_8; | ||
171 | break; | ||
172 | |||
173 | case 44100: | ||
174 | pll_out = 11289600; | ||
175 | mclk_div = WM8510_MCLKDIV_1; | ||
176 | bclk = WM8510_BCLKDIV_8; | ||
177 | break; | ||
178 | |||
179 | case 22050: | ||
180 | pll_out = 11289600; | ||
181 | mclk_div = WM8510_MCLKDIV_2; | ||
182 | bclk = WM8510_BCLKDIV_8; | ||
183 | break; | ||
184 | |||
185 | case 16000: | ||
186 | pll_out = 12288000; | ||
187 | mclk_div = WM8510_MCLKDIV_3; | ||
188 | bclk = WM8510_BCLKDIV_8; | ||
189 | break; | ||
190 | |||
191 | case 11025: | ||
192 | pll_out = 11289600; | ||
193 | mclk_div = WM8510_MCLKDIV_4; | ||
194 | bclk = WM8510_BCLKDIV_8; | ||
195 | break; | ||
196 | |||
197 | case 8000: | ||
198 | pll_out = 12288000; | ||
199 | mclk_div = WM8510_MCLKDIV_6; | ||
200 | bclk = WM8510_BCLKDIV_8; | ||
201 | break; | ||
202 | |||
203 | default: | ||
204 | pr_warning("playpaq_wm8510: Unsupported sample rate %d\n", | ||
205 | params_rate(params)); | ||
206 | return -EINVAL; | ||
207 | } | ||
208 | |||
209 | |||
210 | /* | ||
211 | * set CPU and CODEC DAI configuration | ||
212 | */ | ||
213 | ret = snd_soc_dai_set_fmt(codec_dai, fmt); | ||
214 | if (ret < 0) { | ||
215 | pr_warning("playpaq_wm8510: " | ||
216 | "Failed to set CODEC DAI format (%d)\n", | ||
217 | ret); | ||
218 | return ret; | ||
219 | } | ||
220 | ret = snd_soc_dai_set_fmt(cpu_dai, fmt); | ||
221 | if (ret < 0) { | ||
222 | pr_warning("playpaq_wm8510: " | ||
223 | "Failed to set CPU DAI format (%d)\n", | ||
224 | ret); | ||
225 | return ret; | ||
226 | } | ||
227 | |||
228 | |||
229 | /* | ||
230 | * Set CPU clock configuration | ||
231 | */ | ||
232 | #if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE | ||
233 | cd = playpaq_wm8510_calc_ssc_clock(params, cpu_dai); | ||
234 | pr_debug("playpaq_wm8510: cmr_div = %d, period = %d\n", | ||
235 | cd.cmr_div, cd.period); | ||
236 | ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div); | ||
237 | if (ret < 0) { | ||
238 | pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n", | ||
239 | ret); | ||
240 | return ret; | ||
241 | } | ||
242 | ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD, | ||
243 | cd.period); | ||
244 | if (ret < 0) { | ||
245 | pr_warning("playpaq_wm8510: " | ||
246 | "Failed to set CPU transmit period (%d)\n", | ||
247 | ret); | ||
248 | return ret; | ||
249 | } | ||
250 | #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ | ||
251 | |||
252 | |||
253 | /* | ||
254 | * Set CODEC clock configuration | ||
255 | */ | ||
256 | pr_debug("playpaq_wm8510: " | ||
257 | "pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n", | ||
258 | clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div); | ||
259 | |||
260 | |||
261 | #if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE | ||
262 | ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk); | ||
263 | if (ret < 0) { | ||
264 | pr_warning | ||
265 | ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n", | ||
266 | ret); | ||
267 | return ret; | ||
268 | } | ||
269 | #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ | ||
270 | |||
271 | |||
272 | ret = snd_soc_dai_set_pll(codec_dai, 0, | ||
273 | clk_get_rate(CODEC_CLK), pll_out); | ||
274 | if (ret < 0) { | ||
275 | pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n", | ||
276 | ret); | ||
277 | return ret; | ||
278 | } | ||
279 | |||
280 | |||
281 | ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div); | ||
282 | if (ret < 0) { | ||
283 | pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n", | ||
284 | ret); | ||
285 | return ret; | ||
286 | } | ||
287 | |||
288 | |||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | |||
293 | |||
294 | static struct snd_soc_ops playpaq_wm8510_ops = { | ||
295 | .hw_params = playpaq_wm8510_hw_params, | ||
296 | }; | ||
297 | |||
298 | |||
299 | |||
300 | static const struct snd_soc_dapm_widget playpaq_dapm_widgets[] = { | ||
301 | SND_SOC_DAPM_MIC("Int Mic", NULL), | ||
302 | SND_SOC_DAPM_SPK("Ext Spk", NULL), | ||
303 | }; | ||
304 | |||
305 | |||
306 | |||
307 | static const struct snd_soc_dapm_route intercon[] = { | ||
308 | /* speaker connected to SPKOUT */ | ||
309 | {"Ext Spk", NULL, "SPKOUTP"}, | ||
310 | {"Ext Spk", NULL, "SPKOUTN"}, | ||
311 | |||
312 | {"Mic Bias", NULL, "Int Mic"}, | ||
313 | {"MICN", NULL, "Mic Bias"}, | ||
314 | {"MICP", NULL, "Mic Bias"}, | ||
315 | }; | ||
316 | |||
317 | |||
318 | |||
319 | static int playpaq_wm8510_init(struct snd_soc_codec *codec) | ||
320 | { | ||
321 | int i; | ||
322 | |||
323 | /* | ||
324 | * Add DAPM widgets | ||
325 | */ | ||
326 | for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++) | ||
327 | snd_soc_dapm_new_control(codec, &playpaq_dapm_widgets[i]); | ||
328 | |||
329 | |||
330 | |||
331 | /* | ||
332 | * Setup audio path interconnects | ||
333 | */ | ||
334 | snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); | ||
335 | |||
336 | |||
337 | |||
338 | /* always connected pins */ | ||
339 | snd_soc_dapm_enable_pin(codec, "Int Mic"); | ||
340 | snd_soc_dapm_enable_pin(codec, "Ext Spk"); | ||
341 | snd_soc_dapm_sync(codec); | ||
342 | |||
343 | |||
344 | |||
345 | /* Make CSB show PLL rate */ | ||
346 | snd_soc_dai_set_clkdiv(codec->dai, WM8510_OPCLKDIV, | ||
347 | WM8510_OPCLKDIV_1 | 4); | ||
348 | |||
349 | return 0; | ||
350 | } | ||
351 | |||
352 | |||
353 | |||
354 | static struct snd_soc_dai_link playpaq_wm8510_dai = { | ||
355 | .name = "WM8510", | ||
356 | .stream_name = "WM8510 PCM", | ||
357 | .cpu_dai = &at32_ssc_dai[0], | ||
358 | .codec_dai = &wm8510_dai, | ||
359 | .init = playpaq_wm8510_init, | ||
360 | .ops = &playpaq_wm8510_ops, | ||
361 | }; | ||
362 | |||
363 | |||
364 | |||
365 | static struct snd_soc_machine snd_soc_machine_playpaq = { | ||
366 | .name = "LRS_PlayPaq_WM8510", | ||
367 | .dai_link = &playpaq_wm8510_dai, | ||
368 | .num_links = 1, | ||
369 | }; | ||
370 | |||
371 | |||
372 | |||
373 | static struct wm8510_setup_data playpaq_wm8510_setup = { | ||
374 | .i2c_bus = 0, | ||
375 | .i2c_address = 0x1a, | ||
376 | }; | ||
377 | |||
378 | |||
379 | |||
380 | static struct snd_soc_device playpaq_wm8510_snd_devdata = { | ||
381 | .machine = &snd_soc_machine_playpaq, | ||
382 | .platform = &at32_soc_platform, | ||
383 | .codec_dev = &soc_codec_dev_wm8510, | ||
384 | .codec_data = &playpaq_wm8510_setup, | ||
385 | }; | ||
386 | |||
387 | static struct platform_device *playpaq_snd_device; | ||
388 | |||
389 | |||
390 | static int __init playpaq_asoc_init(void) | ||
391 | { | ||
392 | int ret = 0; | ||
393 | struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data; | ||
394 | struct ssc_device *ssc = NULL; | ||
395 | |||
396 | |||
397 | /* | ||
398 | * Request SSC device | ||
399 | */ | ||
400 | ssc = ssc_request(0); | ||
401 | if (IS_ERR(ssc)) { | ||
402 | ret = PTR_ERR(ssc); | ||
403 | goto err_ssc; | ||
404 | } | ||
405 | ssc_p->ssc = ssc; | ||
406 | |||
407 | |||
408 | /* | ||
409 | * Configure MCLK for WM8510 | ||
410 | */ | ||
411 | _gclk0 = clk_get(NULL, "gclk0"); | ||
412 | if (IS_ERR(_gclk0)) { | ||
413 | _gclk0 = NULL; | ||
414 | goto err_gclk0; | ||
415 | } | ||
416 | _pll0 = clk_get(NULL, "pll0"); | ||
417 | if (IS_ERR(_pll0)) { | ||
418 | _pll0 = NULL; | ||
419 | goto err_pll0; | ||
420 | } | ||
421 | if (clk_set_parent(_gclk0, _pll0)) { | ||
422 | pr_warning("snd-soc-playpaq: " | ||
423 | "Failed to set PLL0 as parent for DAC clock\n"); | ||
424 | goto err_set_clk; | ||
425 | } | ||
426 | clk_set_rate(CODEC_CLK, 12000000); | ||
427 | clk_enable(CODEC_CLK); | ||
428 | |||
429 | #if defined CONFIG_AT32_ENHANCED_PORTMUX | ||
430 | at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0); | ||
431 | #endif | ||
432 | |||
433 | |||
434 | /* | ||
435 | * Create and register platform device | ||
436 | */ | ||
437 | playpaq_snd_device = platform_device_alloc("soc-audio", 0); | ||
438 | if (playpaq_snd_device == NULL) { | ||
439 | ret = -ENOMEM; | ||
440 | goto err_device_alloc; | ||
441 | } | ||
442 | |||
443 | platform_set_drvdata(playpaq_snd_device, &playpaq_wm8510_snd_devdata); | ||
444 | playpaq_wm8510_snd_devdata.dev = &playpaq_snd_device->dev; | ||
445 | |||
446 | ret = platform_device_add(playpaq_snd_device); | ||
447 | if (ret) { | ||
448 | pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n", | ||
449 | ret); | ||
450 | goto err_device_add; | ||
451 | } | ||
452 | |||
453 | return 0; | ||
454 | |||
455 | |||
456 | err_device_add: | ||
457 | if (playpaq_snd_device != NULL) { | ||
458 | platform_device_put(playpaq_snd_device); | ||
459 | playpaq_snd_device = NULL; | ||
460 | } | ||
461 | err_device_alloc: | ||
462 | err_set_clk: | ||
463 | if (_pll0 != NULL) { | ||
464 | clk_put(_pll0); | ||
465 | _pll0 = NULL; | ||
466 | } | ||
467 | err_pll0: | ||
468 | if (_gclk0 != NULL) { | ||
469 | clk_put(_gclk0); | ||
470 | _gclk0 = NULL; | ||
471 | } | ||
472 | err_gclk0: | ||
473 | ssc_free(ssc); | ||
474 | err_ssc: | ||
475 | return ret; | ||
476 | } | ||
477 | |||
478 | |||
479 | static void __exit playpaq_asoc_exit(void) | ||
480 | { | ||
481 | struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data; | ||
482 | struct ssc_device *ssc; | ||
483 | |||
484 | if (ssc_p != NULL) { | ||
485 | ssc = ssc_p->ssc; | ||
486 | if (ssc != NULL) | ||
487 | ssc_free(ssc); | ||
488 | ssc_p->ssc = NULL; | ||
489 | } | ||
490 | |||
491 | if (_gclk0 != NULL) { | ||
492 | clk_put(_gclk0); | ||
493 | _gclk0 = NULL; | ||
494 | } | ||
495 | if (_pll0 != NULL) { | ||
496 | clk_put(_pll0); | ||
497 | _pll0 = NULL; | ||
498 | } | ||
499 | |||
500 | #if defined CONFIG_AT32_ENHANCED_PORTMUX | ||
501 | at32_free_pin(MCLK_PIN); | ||
502 | #endif | ||
503 | |||
504 | platform_device_unregister(playpaq_snd_device); | ||
505 | playpaq_snd_device = NULL; | ||
506 | } | ||
507 | |||
508 | module_init(playpaq_asoc_init); | ||
509 | module_exit(playpaq_asoc_exit); | ||
510 | |||
511 | MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>"); | ||
512 | MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq"); | ||
513 | MODULE_LICENSE("GPL"); | ||