blob: 3f326219f1ec182dba666bcf01d5c12e4d258377 [file] [log] [blame]
Geoffrey Wossum9aaca962008-06-05 13:49:34 +01001/* 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
Russell Kinga09e64f2008-08-05 16:14:15 +010039#include <mach/at32ap700x.h>
40#include <mach/portmux.h>
Geoffrey Wossum9aaca962008-06-05 13:49:34 +010041
42#include "../codecs/wm8510.h"
43#include "at32-pcm.h"
44#include "at32-ssc.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 */
58struct 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\*-------------------------------------------------------------------------*/
73static struct clk *_gclk0;
74static 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
83static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock(
84 struct snd_pcm_hw_params *params,
Liam Girdwood453ba202008-07-07 16:07:29 +010085 struct snd_soc_dai *cpu_dai)
Geoffrey Wossum9aaca962008-06-05 13:49:34 +010086{
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
131static 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;
Liam Girdwood453ba202008-07-07 16:07:29 +0100135 struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
136 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100137 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 */
Liam Girdwood64105cf2008-07-08 13:19:18 +0100213 ret = snd_soc_dai_set_fmt(codec_dai, fmt);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100214 if (ret < 0) {
215 pr_warning("playpaq_wm8510: "
216 "Failed to set CODEC DAI format (%d)\n",
217 ret);
218 return ret;
219 }
Liam Girdwood64105cf2008-07-08 13:19:18 +0100220 ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100221 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);
Liam Girdwood64105cf2008-07-08 13:19:18 +0100236 ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100237 if (ret < 0) {
238 pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",
239 ret);
240 return ret;
241 }
Liam Girdwood64105cf2008-07-08 13:19:18 +0100242 ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD,
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100243 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
Liam Girdwood64105cf2008-07-08 13:19:18 +0100262 ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100263 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
Liam Girdwood64105cf2008-07-08 13:19:18 +0100272 ret = snd_soc_dai_set_pll(codec_dai, 0,
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100273 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
Liam Girdwood64105cf2008-07-08 13:19:18 +0100281 ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100282 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
294static struct snd_soc_ops playpaq_wm8510_ops = {
295 .hw_params = playpaq_wm8510_hw_params,
296};
297
298
299
300static 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
307static const char *intercon[][3] = {
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 /* Terminator */
317 {NULL, NULL, NULL},
318};
319
320
321
322static int playpaq_wm8510_init(struct snd_soc_codec *codec)
323{
324 int i;
325
326 /*
327 * Add DAPM widgets
328 */
329 for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++)
330 snd_soc_dapm_new_control(codec, &playpaq_dapm_widgets[i]);
331
332
333
334 /*
335 * Setup audio path interconnects
336 */
337 for (i = 0; intercon[i][0] != NULL; i++) {
338 snd_soc_dapm_connect_input(codec,
339 intercon[i][0],
340 intercon[i][1], intercon[i][2]);
341 }
342
343
Liam Girdwood6f484132008-07-08 13:21:52 +0100344 /* always connected pins */
345 snd_soc_dapm_enable_pin(codec, "Int Mic");
346 snd_soc_dapm_enable_pin(codec, "Ext Spk");
347 snd_soc_dapm_sync(codec);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100348
349
350
351 /* Make CSB show PLL rate */
Liam Girdwood6f484132008-07-08 13:21:52 +0100352 snd_soc_dai_set_clkdiv(codec->dai, WM8510_OPCLKDIV,
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100353 WM8510_OPCLKDIV_1 | 4);
354
355 return 0;
356}
357
358
359
360static struct snd_soc_dai_link playpaq_wm8510_dai = {
361 .name = "WM8510",
362 .stream_name = "WM8510 PCM",
363 .cpu_dai = &at32_ssc_dai[0],
364 .codec_dai = &wm8510_dai,
365 .init = playpaq_wm8510_init,
366 .ops = &playpaq_wm8510_ops,
367};
368
369
370
371static struct snd_soc_machine snd_soc_machine_playpaq = {
372 .name = "LRS_PlayPaq_WM8510",
373 .dai_link = &playpaq_wm8510_dai,
374 .num_links = 1,
375};
376
377
378
379static struct wm8510_setup_data playpaq_wm8510_setup = {
380 .i2c_address = 0x1a,
381};
382
383
384
385static struct snd_soc_device playpaq_wm8510_snd_devdata = {
386 .machine = &snd_soc_machine_playpaq,
387 .platform = &at32_soc_platform,
388 .codec_dev = &soc_codec_dev_wm8510,
389 .codec_data = &playpaq_wm8510_setup,
390};
391
392static struct platform_device *playpaq_snd_device;
393
394
395static int __init playpaq_asoc_init(void)
396{
397 int ret = 0;
398 struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data;
399 struct ssc_device *ssc = NULL;
400
401
402 /*
403 * Request SSC device
404 */
405 ssc = ssc_request(0);
406 if (IS_ERR(ssc)) {
407 ret = PTR_ERR(ssc);
408 ssc = NULL;
409 goto err_ssc;
410 }
411 ssc_p->ssc = ssc;
412
413
414 /*
415 * Configure MCLK for WM8510
416 */
417 _gclk0 = clk_get(NULL, "gclk0");
418 if (IS_ERR(_gclk0)) {
419 _gclk0 = NULL;
420 goto err_gclk0;
421 }
422 _pll0 = clk_get(NULL, "pll0");
423 if (IS_ERR(_pll0)) {
424 _pll0 = NULL;
425 goto err_pll0;
426 }
427 if (clk_set_parent(_gclk0, _pll0)) {
428 pr_warning("snd-soc-playpaq: "
429 "Failed to set PLL0 as parent for DAC clock\n");
430 goto err_set_clk;
431 }
432 clk_set_rate(CODEC_CLK, 12000000);
433 clk_enable(CODEC_CLK);
434
435#if defined CONFIG_AT32_ENHANCED_PORTMUX
436 at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0);
437#endif
438
439
440 /*
441 * Create and register platform device
442 */
443 playpaq_snd_device = platform_device_alloc("soc-audio", 0);
444 if (playpaq_snd_device == NULL) {
445 ret = -ENOMEM;
446 goto err_device_alloc;
447 }
448
449 platform_set_drvdata(playpaq_snd_device, &playpaq_wm8510_snd_devdata);
450 playpaq_wm8510_snd_devdata.dev = &playpaq_snd_device->dev;
451
452 ret = platform_device_add(playpaq_snd_device);
453 if (ret) {
454 pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",
455 ret);
456 goto err_device_add;
457 }
458
459 return 0;
460
461
462err_device_add:
463 if (playpaq_snd_device != NULL) {
464 platform_device_put(playpaq_snd_device);
465 playpaq_snd_device = NULL;
466 }
467err_device_alloc:
468err_set_clk:
469 if (_pll0 != NULL) {
470 clk_put(_pll0);
471 _pll0 = NULL;
472 }
473err_pll0:
474 if (_gclk0 != NULL) {
475 clk_put(_gclk0);
476 _gclk0 = NULL;
477 }
478err_gclk0:
479 if (ssc != NULL) {
480 ssc_free(ssc);
481 ssc = NULL;
482 }
483err_ssc:
484 return ret;
485}
486
487
488static void __exit playpaq_asoc_exit(void)
489{
490 struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data;
491 struct ssc_device *ssc;
492
493 if (ssc_p != NULL) {
494 ssc = ssc_p->ssc;
495 if (ssc != NULL)
496 ssc_free(ssc);
497 ssc_p->ssc = NULL;
498 }
499
500 if (_gclk0 != NULL) {
501 clk_put(_gclk0);
502 _gclk0 = NULL;
503 }
504 if (_pll0 != NULL) {
505 clk_put(_pll0);
506 _pll0 = NULL;
507 }
508
509#if defined CONFIG_AT32_ENHANCED_PORTMUX
510 at32_free_pin(MCLK_PIN);
511#endif
512
513 platform_device_unregister(playpaq_snd_device);
514 playpaq_snd_device = NULL;
515}
516
517module_init(playpaq_asoc_init);
518module_exit(playpaq_asoc_exit);
519
520MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
521MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
522MODULE_LICENSE("GPL");