blob: aede7e74ec34b09ea56e5a9ffdef4ccd4e04272b [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>
Geoffrey Wossum9aaca962008-06-05 13:49:34 +010025#include <linux/kernel.h>
26#include <linux/errno.h>
27#include <linux/clk.h>
28#include <linux/timer.h>
29#include <linux/interrupt.h>
30#include <linux/platform_device.h>
31
32#include <sound/core.h>
33#include <sound/pcm.h>
34#include <sound/pcm_params.h>
35#include <sound/soc.h>
36#include <sound/soc-dapm.h>
37
Russell Kinga09e64f2008-08-05 16:14:15 +010038#include <mach/at32ap700x.h>
39#include <mach/portmux.h>
Geoffrey Wossum9aaca962008-06-05 13:49:34 +010040
41#include "../codecs/wm8510.h"
Sedji Gaouaou6c742502008-10-03 16:57:50 +020042#include "atmel-pcm.h"
43#include "atmel_ssc_dai.h"
Geoffrey Wossum9aaca962008-06-05 13:49:34 +010044
45
46/*-------------------------------------------------------------------------*\
47 * constants
48\*-------------------------------------------------------------------------*/
49#define MCLK_PIN GPIO_PIN_PA(30)
50#define MCLK_PERIPH GPIO_PERIPH_A
51
52
53/*-------------------------------------------------------------------------*\
54 * data types
55\*-------------------------------------------------------------------------*/
56/* SSC clocking data */
57struct ssc_clock_data {
58 /* CMR div */
59 unsigned int cmr_div;
60
61 /* Frame period (as needed by xCMR.PERIOD) */
62 unsigned int period;
63
64 /* The SSC clock rate these settings where calculated for */
65 unsigned long ssc_rate;
66};
67
68
69/*-------------------------------------------------------------------------*\
70 * module data
71\*-------------------------------------------------------------------------*/
72static struct clk *_gclk0;
73static struct clk *_pll0;
74
75#define CODEC_CLK (_gclk0)
76
77
78/*-------------------------------------------------------------------------*\
79 * Sound SOC operations
80\*-------------------------------------------------------------------------*/
81#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
82static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock(
83 struct snd_pcm_hw_params *params,
Liam Girdwood453ba202008-07-07 16:07:29 +010084 struct snd_soc_dai *cpu_dai)
Geoffrey Wossum9aaca962008-06-05 13:49:34 +010085{
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +000086 struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +010087 struct ssc_device *ssc = ssc_p->ssc;
88 struct ssc_clock_data cd;
89 unsigned int rate, width_bits, channels;
90 unsigned int bitrate, ssc_div;
91 unsigned actual_rate;
92
93
94 /*
95 * Figure out required bitrate
96 */
97 rate = params_rate(params);
98 channels = params_channels(params);
99 width_bits = snd_pcm_format_physical_width(params_format(params));
100 bitrate = rate * width_bits * channels;
101
102
103 /*
104 * Figure out required SSC divider and period for required bitrate
105 */
106 cd.ssc_rate = clk_get_rate(ssc->clk);
107 ssc_div = cd.ssc_rate / bitrate;
108 cd.cmr_div = ssc_div / 2;
109 if (ssc_div & 1) {
110 /* round cmr_div up */
111 cd.cmr_div++;
112 }
113 cd.period = width_bits - 1;
114
115
116 /*
117 * Find actual rate, compare to requested rate
118 */
119 actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1));
Roel Kluin449bd542009-05-27 17:08:39 -0700120 pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n",
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100121 rate, actual_rate);
122
123
124 return cd;
125}
126#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
127
128
129
130static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream,
131 struct snd_pcm_hw_params *params)
132{
133 struct snd_soc_pcm_runtime *rtd = substream->private_data;
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000134 struct snd_soc_dai *codec_dai = rtd->codec_dai;
135 struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
136 struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100137 struct ssc_device *ssc = ssc_p->ssc;
138 unsigned int pll_out = 0, bclk = 0, mclk_div = 0;
139 int ret;
140
141
142 /* Due to difficulties with getting the correct clocks from the AT32's
143 * PLL0, we're going to let the CODEC be in charge of all the clocks
144 */
145#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
146 const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
147 SND_SOC_DAIFMT_NB_NF |
148 SND_SOC_DAIFMT_CBM_CFM);
149#else
150 struct ssc_clock_data cd;
151 const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
152 SND_SOC_DAIFMT_NB_NF |
153 SND_SOC_DAIFMT_CBS_CFS);
154#endif
155
156 if (ssc == NULL) {
157 pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n");
158 return -EINVAL;
159 }
160
161
162 /*
163 * Figure out PLL and BCLK dividers for WM8510
164 */
165 switch (params_rate(params)) {
166 case 48000:
Jonas Andersson86027ae2009-03-04 08:24:26 +0100167 pll_out = 24576000;
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100168 mclk_div = WM8510_MCLKDIV_2;
169 bclk = WM8510_BCLKDIV_8;
170 break;
171
Jonas Andersson86027ae2009-03-04 08:24:26 +0100172 case 44100:
173 pll_out = 22579200;
174 mclk_div = WM8510_MCLKDIV_2;
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100175 bclk = WM8510_BCLKDIV_8;
176 break;
177
Jonas Andersson86027ae2009-03-04 08:24:26 +0100178 case 22050:
179 pll_out = 22579200;
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100180 mclk_div = WM8510_MCLKDIV_4;
181 bclk = WM8510_BCLKDIV_8;
182 break;
183
Jonas Andersson86027ae2009-03-04 08:24:26 +0100184 case 16000:
185 pll_out = 24576000;
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100186 mclk_div = WM8510_MCLKDIV_6;
187 bclk = WM8510_BCLKDIV_8;
188 break;
189
Jonas Andersson86027ae2009-03-04 08:24:26 +0100190 case 11025:
191 pll_out = 22579200;
192 mclk_div = WM8510_MCLKDIV_8;
193 bclk = WM8510_BCLKDIV_8;
194 break;
195
196 case 8000:
197 pll_out = 24576000;
198 mclk_div = WM8510_MCLKDIV_12;
199 bclk = WM8510_BCLKDIV_8;
200 break;
201
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100202 default:
203 pr_warning("playpaq_wm8510: Unsupported sample rate %d\n",
204 params_rate(params));
205 return -EINVAL;
206 }
207
208
209 /*
210 * set CPU and CODEC DAI configuration
211 */
Liam Girdwood64105cf2008-07-08 13:19:18 +0100212 ret = snd_soc_dai_set_fmt(codec_dai, fmt);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100213 if (ret < 0) {
214 pr_warning("playpaq_wm8510: "
215 "Failed to set CODEC DAI format (%d)\n",
216 ret);
217 return ret;
218 }
Liam Girdwood64105cf2008-07-08 13:19:18 +0100219 ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100220 if (ret < 0) {
221 pr_warning("playpaq_wm8510: "
222 "Failed to set CPU DAI format (%d)\n",
223 ret);
224 return ret;
225 }
226
227
228 /*
229 * Set CPU clock configuration
230 */
231#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
232 cd = playpaq_wm8510_calc_ssc_clock(params, cpu_dai);
233 pr_debug("playpaq_wm8510: cmr_div = %d, period = %d\n",
234 cd.cmr_div, cd.period);
Liam Girdwood64105cf2008-07-08 13:19:18 +0100235 ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100236 if (ret < 0) {
237 pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",
238 ret);
239 return ret;
240 }
Liam Girdwood64105cf2008-07-08 13:19:18 +0100241 ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD,
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100242 cd.period);
243 if (ret < 0) {
244 pr_warning("playpaq_wm8510: "
245 "Failed to set CPU transmit period (%d)\n",
246 ret);
247 return ret;
248 }
249#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
250
251
252 /*
253 * Set CODEC clock configuration
254 */
255 pr_debug("playpaq_wm8510: "
256 "pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n",
257 clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div);
258
259
260#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
Liam Girdwood64105cf2008-07-08 13:19:18 +0100261 ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100262 if (ret < 0) {
263 pr_warning
264 ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n",
265 ret);
266 return ret;
267 }
268#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
269
270
Mark Brown85488032009-09-05 18:52:16 +0100271 ret = snd_soc_dai_set_pll(codec_dai, 0, 0,
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100272 clk_get_rate(CODEC_CLK), pll_out);
273 if (ret < 0) {
274 pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n",
275 ret);
276 return ret;
277 }
278
279
Liam Girdwood64105cf2008-07-08 13:19:18 +0100280 ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100281 if (ret < 0) {
282 pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n",
283 ret);
284 return ret;
285 }
286
287
288 return 0;
289}
290
291
292
293static struct snd_soc_ops playpaq_wm8510_ops = {
294 .hw_params = playpaq_wm8510_hw_params,
295};
296
297
298
299static const struct snd_soc_dapm_widget playpaq_dapm_widgets[] = {
300 SND_SOC_DAPM_MIC("Int Mic", NULL),
301 SND_SOC_DAPM_SPK("Ext Spk", NULL),
302};
303
304
305
Mark Browncdbdd162008-10-16 11:00:06 +0100306static const struct snd_soc_dapm_route intercon[] = {
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100307 /* speaker connected to SPKOUT */
308 {"Ext Spk", NULL, "SPKOUTP"},
309 {"Ext Spk", NULL, "SPKOUTN"},
310
311 {"Mic Bias", NULL, "Int Mic"},
312 {"MICN", NULL, "Mic Bias"},
313 {"MICP", NULL, "Mic Bias"},
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100314};
315
316
317
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000318static int playpaq_wm8510_init(struct snd_soc_pcm_runtime *rtd)
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100319{
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000320 struct snd_soc_codec *codec = rtd->codec;
Liam Girdwoodce6120c2010-11-05 15:53:46 +0200321 struct snd_soc_dapm_context *dapm = &codec->dapm;
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100322 int i;
323
324 /*
325 * Add DAPM widgets
326 */
327 for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++)
Liam Girdwoodce6120c2010-11-05 15:53:46 +0200328 snd_soc_dapm_new_control(dapm, &playpaq_dapm_widgets[i]);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100329
330
331
332 /*
333 * Setup audio path interconnects
334 */
Liam Girdwoodce6120c2010-11-05 15:53:46 +0200335 snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
Mark Browncdbdd162008-10-16 11:00:06 +0100336
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100337
338
Liam Girdwood6f484132008-07-08 13:21:52 +0100339 /* always connected pins */
Liam Girdwoodce6120c2010-11-05 15:53:46 +0200340 snd_soc_dapm_enable_pin(dapm, "Int Mic");
341 snd_soc_dapm_enable_pin(dapm, "Ext Spk");
342 snd_soc_dapm_sync(dapm);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100343
344
345
346 /* Make CSB show PLL rate */
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000347 snd_soc_dai_set_clkdiv(rtd->codec_dai, WM8510_OPCLKDIV,
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100348 WM8510_OPCLKDIV_1 | 4);
349
350 return 0;
351}
352
353
354
355static struct snd_soc_dai_link playpaq_wm8510_dai = {
356 .name = "WM8510",
357 .stream_name = "WM8510 PCM",
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000358 .cpu_dai_name= "atmel-ssc-dai.0",
359 .platform_name = "atmel-pcm-audio",
360 .codec_name = "wm8510-codec.0-0x1a",
361 .codec_dai_name = "wm8510-hifi",
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100362 .init = playpaq_wm8510_init,
363 .ops = &playpaq_wm8510_ops,
364};
365
366
367
Mark Brown87506542008-11-18 20:50:34 +0000368static struct snd_soc_card snd_soc_playpaq = {
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100369 .name = "LRS_PlayPaq_WM8510",
370 .dai_link = &playpaq_wm8510_dai,
371 .num_links = 1,
372};
373
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100374static struct platform_device *playpaq_snd_device;
375
376
377static int __init playpaq_asoc_init(void)
378{
379 int ret = 0;
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100380
381 /*
382 * Configure MCLK for WM8510
383 */
384 _gclk0 = clk_get(NULL, "gclk0");
385 if (IS_ERR(_gclk0)) {
386 _gclk0 = NULL;
387 goto err_gclk0;
388 }
389 _pll0 = clk_get(NULL, "pll0");
390 if (IS_ERR(_pll0)) {
391 _pll0 = NULL;
392 goto err_pll0;
393 }
394 if (clk_set_parent(_gclk0, _pll0)) {
395 pr_warning("snd-soc-playpaq: "
396 "Failed to set PLL0 as parent for DAC clock\n");
397 goto err_set_clk;
398 }
399 clk_set_rate(CODEC_CLK, 12000000);
400 clk_enable(CODEC_CLK);
401
402#if defined CONFIG_AT32_ENHANCED_PORTMUX
403 at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0);
404#endif
405
406
407 /*
408 * Create and register platform device
409 */
410 playpaq_snd_device = platform_device_alloc("soc-audio", 0);
411 if (playpaq_snd_device == NULL) {
412 ret = -ENOMEM;
413 goto err_device_alloc;
414 }
415
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000416 platform_set_drvdata(playpaq_snd_device, &snd_soc_playpaq);
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100417
418 ret = platform_device_add(playpaq_snd_device);
419 if (ret) {
420 pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",
421 ret);
422 goto err_device_add;
423 }
424
425 return 0;
426
427
428err_device_add:
429 if (playpaq_snd_device != NULL) {
430 platform_device_put(playpaq_snd_device);
431 playpaq_snd_device = NULL;
432 }
433err_device_alloc:
434err_set_clk:
435 if (_pll0 != NULL) {
436 clk_put(_pll0);
437 _pll0 = NULL;
438 }
439err_pll0:
440 if (_gclk0 != NULL) {
441 clk_put(_gclk0);
442 _gclk0 = NULL;
443 }
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100444 return ret;
445}
446
447
448static void __exit playpaq_asoc_exit(void)
449{
Geoffrey Wossum9aaca962008-06-05 13:49:34 +0100450 if (_gclk0 != NULL) {
451 clk_put(_gclk0);
452 _gclk0 = NULL;
453 }
454 if (_pll0 != NULL) {
455 clk_put(_pll0);
456 _pll0 = NULL;
457 }
458
459#if defined CONFIG_AT32_ENHANCED_PORTMUX
460 at32_free_pin(MCLK_PIN);
461#endif
462
463 platform_device_unregister(playpaq_snd_device);
464 playpaq_snd_device = NULL;
465}
466
467module_init(playpaq_asoc_init);
468module_exit(playpaq_asoc_exit);
469
470MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
471MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
472MODULE_LICENSE("GPL");