Merge branch 'x86-rdrand-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[deliverable/linux.git] / sound / soc / atmel / playpaq_wm8510.c
CommitLineData
9aaca968
GW
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>
9aaca968
GW
25#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>
9aaca968 36
a09e64fb
RK
37#include <mach/at32ap700x.h>
38#include <mach/portmux.h>
9aaca968
GW
39
40#include "../codecs/wm8510.h"
6c742509
SG
41#include "atmel-pcm.h"
42#include "atmel_ssc_dai.h"
9aaca968
GW
43
44
45/*-------------------------------------------------------------------------*\
46 * constants
47\*-------------------------------------------------------------------------*/
48#define MCLK_PIN GPIO_PIN_PA(30)
49#define MCLK_PERIPH GPIO_PERIPH_A
50
51
52/*-------------------------------------------------------------------------*\
53 * data types
54\*-------------------------------------------------------------------------*/
55/* SSC clocking data */
56struct ssc_clock_data {
57 /* CMR div */
58 unsigned int cmr_div;
59
60 /* Frame period (as needed by xCMR.PERIOD) */
61 unsigned int period;
62
63 /* The SSC clock rate these settings where calculated for */
64 unsigned long ssc_rate;
65};
66
67
68/*-------------------------------------------------------------------------*\
69 * module data
70\*-------------------------------------------------------------------------*/
71static struct clk *_gclk0;
72static struct clk *_pll0;
73
74#define CODEC_CLK (_gclk0)
75
76
77/*-------------------------------------------------------------------------*\
78 * Sound SOC operations
79\*-------------------------------------------------------------------------*/
80#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
81static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock(
82 struct snd_pcm_hw_params *params,
453ba20b 83 struct snd_soc_dai *cpu_dai)
9aaca968 84{
f0fba2ad 85 struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai);
9aaca968
GW
86 struct ssc_device *ssc = ssc_p->ssc;
87 struct ssc_clock_data cd;
88 unsigned int rate, width_bits, channels;
89 unsigned int bitrate, ssc_div;
90 unsigned actual_rate;
91
92
93 /*
94 * Figure out required bitrate
95 */
96 rate = params_rate(params);
97 channels = params_channels(params);
98 width_bits = snd_pcm_format_physical_width(params_format(params));
99 bitrate = rate * width_bits * channels;
100
101
102 /*
103 * Figure out required SSC divider and period for required bitrate
104 */
105 cd.ssc_rate = clk_get_rate(ssc->clk);
106 ssc_div = cd.ssc_rate / bitrate;
107 cd.cmr_div = ssc_div / 2;
108 if (ssc_div & 1) {
109 /* round cmr_div up */
110 cd.cmr_div++;
111 }
112 cd.period = width_bits - 1;
113
114
115 /*
116 * Find actual rate, compare to requested rate
117 */
118 actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1));
449bd54d 119 pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n",
9aaca968
GW
120 rate, actual_rate);
121
122
123 return cd;
124}
125#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
126
127
128
129static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream,
130 struct snd_pcm_hw_params *params)
131{
132 struct snd_soc_pcm_runtime *rtd = substream->private_data;
f0fba2ad
LG
133 struct snd_soc_dai *codec_dai = rtd->codec_dai;
134 struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
135 struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai);
9aaca968
GW
136 struct ssc_device *ssc = ssc_p->ssc;
137 unsigned int pll_out = 0, bclk = 0, mclk_div = 0;
138 int ret;
139
140
141 /* Due to difficulties with getting the correct clocks from the AT32's
142 * PLL0, we're going to let the CODEC be in charge of all the clocks
143 */
144#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
145 const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
146 SND_SOC_DAIFMT_NB_NF |
147 SND_SOC_DAIFMT_CBM_CFM);
148#else
149 struct ssc_clock_data cd;
150 const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
151 SND_SOC_DAIFMT_NB_NF |
152 SND_SOC_DAIFMT_CBS_CFS);
153#endif
154
155 if (ssc == NULL) {
156 pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n");
157 return -EINVAL;
158 }
159
160
161 /*
162 * Figure out PLL and BCLK dividers for WM8510
163 */
164 switch (params_rate(params)) {
165 case 48000:
86027ae7
JA
166 pll_out = 24576000;
167 mclk_div = WM8510_MCLKDIV_2;
9aaca968
GW
168 bclk = WM8510_BCLKDIV_8;
169 break;
170
171 case 44100:
86027ae7
JA
172 pll_out = 22579200;
173 mclk_div = WM8510_MCLKDIV_2;
9aaca968
GW
174 bclk = WM8510_BCLKDIV_8;
175 break;
176
177 case 22050:
86027ae7
JA
178 pll_out = 22579200;
179 mclk_div = WM8510_MCLKDIV_4;
9aaca968
GW
180 bclk = WM8510_BCLKDIV_8;
181 break;
182
183 case 16000:
86027ae7
JA
184 pll_out = 24576000;
185 mclk_div = WM8510_MCLKDIV_6;
9aaca968
GW
186 bclk = WM8510_BCLKDIV_8;
187 break;
188
189 case 11025:
86027ae7
JA
190 pll_out = 22579200;
191 mclk_div = WM8510_MCLKDIV_8;
9aaca968
GW
192 bclk = WM8510_BCLKDIV_8;
193 break;
194
195 case 8000:
86027ae7
JA
196 pll_out = 24576000;
197 mclk_div = WM8510_MCLKDIV_12;
9aaca968
GW
198 bclk = WM8510_BCLKDIV_8;
199 break;
200
201 default:
202 pr_warning("playpaq_wm8510: Unsupported sample rate %d\n",
203 params_rate(params));
204 return -EINVAL;
205 }
206
207
208 /*
209 * set CPU and CODEC DAI configuration
210 */
64105cfd 211 ret = snd_soc_dai_set_fmt(codec_dai, fmt);
9aaca968
GW
212 if (ret < 0) {
213 pr_warning("playpaq_wm8510: "
214 "Failed to set CODEC DAI format (%d)\n",
215 ret);
216 return ret;
217 }
64105cfd 218 ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
9aaca968
GW
219 if (ret < 0) {
220 pr_warning("playpaq_wm8510: "
221 "Failed to set CPU DAI format (%d)\n",
222 ret);
223 return ret;
224 }
225
226
227 /*
228 * Set CPU clock configuration
229 */
230#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
231 cd = playpaq_wm8510_calc_ssc_clock(params, cpu_dai);
232 pr_debug("playpaq_wm8510: cmr_div = %d, period = %d\n",
233 cd.cmr_div, cd.period);
64105cfd 234 ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div);
9aaca968
GW
235 if (ret < 0) {
236 pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",
237 ret);
238 return ret;
239 }
64105cfd 240 ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD,
9aaca968
GW
241 cd.period);
242 if (ret < 0) {
243 pr_warning("playpaq_wm8510: "
244 "Failed to set CPU transmit period (%d)\n",
245 ret);
246 return ret;
247 }
248#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
249
250
251 /*
252 * Set CODEC clock configuration
253 */
254 pr_debug("playpaq_wm8510: "
255 "pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n",
256 clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div);
257
258
259#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
64105cfd 260 ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);
9aaca968
GW
261 if (ret < 0) {
262 pr_warning
263 ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n",
264 ret);
265 return ret;
266 }
267#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
268
269
85488037 270 ret = snd_soc_dai_set_pll(codec_dai, 0, 0,
9aaca968
GW
271 clk_get_rate(CODEC_CLK), pll_out);
272 if (ret < 0) {
273 pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n",
274 ret);
275 return ret;
276 }
277
278
64105cfd 279 ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);
9aaca968
GW
280 if (ret < 0) {
281 pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n",
282 ret);
283 return ret;
284 }
285
286
287 return 0;
288}
289
290
291
292static struct snd_soc_ops playpaq_wm8510_ops = {
293 .hw_params = playpaq_wm8510_hw_params,
294};
295
296
297
298static const struct snd_soc_dapm_widget playpaq_dapm_widgets[] = {
299 SND_SOC_DAPM_MIC("Int Mic", NULL),
300 SND_SOC_DAPM_SPK("Ext Spk", NULL),
301};
302
303
304
cdbdd167 305static const struct snd_soc_dapm_route intercon[] = {
9aaca968
GW
306 /* speaker connected to SPKOUT */
307 {"Ext Spk", NULL, "SPKOUTP"},
308 {"Ext Spk", NULL, "SPKOUTN"},
309
310 {"Mic Bias", NULL, "Int Mic"},
311 {"MICN", NULL, "Mic Bias"},
312 {"MICP", NULL, "Mic Bias"},
9aaca968
GW
313};
314
315
316
f0fba2ad 317static int playpaq_wm8510_init(struct snd_soc_pcm_runtime *rtd)
9aaca968 318{
f0fba2ad 319 struct snd_soc_codec *codec = rtd->codec;
ce6120cc 320 struct snd_soc_dapm_context *dapm = &codec->dapm;
9aaca968
GW
321 int i;
322
323 /*
324 * Add DAPM widgets
325 */
326 for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++)
ce6120cc 327 snd_soc_dapm_new_control(dapm, &playpaq_dapm_widgets[i]);
9aaca968
GW
328
329
330
331 /*
332 * Setup audio path interconnects
333 */
ce6120cc 334 snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
cdbdd167 335
9aaca968
GW
336
337
6f48413d 338 /* always connected pins */
ce6120cc
LG
339 snd_soc_dapm_enable_pin(dapm, "Int Mic");
340 snd_soc_dapm_enable_pin(dapm, "Ext Spk");
341 snd_soc_dapm_sync(dapm);
9aaca968
GW
342
343
344
345 /* Make CSB show PLL rate */
f0fba2ad 346 snd_soc_dai_set_clkdiv(rtd->codec_dai, WM8510_OPCLKDIV,
9aaca968
GW
347 WM8510_OPCLKDIV_1 | 4);
348
349 return 0;
350}
351
352
353
354static struct snd_soc_dai_link playpaq_wm8510_dai = {
355 .name = "WM8510",
356 .stream_name = "WM8510 PCM",
f0fba2ad
LG
357 .cpu_dai_name= "atmel-ssc-dai.0",
358 .platform_name = "atmel-pcm-audio",
359 .codec_name = "wm8510-codec.0-0x1a",
360 .codec_dai_name = "wm8510-hifi",
9aaca968
GW
361 .init = playpaq_wm8510_init,
362 .ops = &playpaq_wm8510_ops,
363};
364
365
366
87506549 367static struct snd_soc_card snd_soc_playpaq = {
9aaca968
GW
368 .name = "LRS_PlayPaq_WM8510",
369 .dai_link = &playpaq_wm8510_dai,
370 .num_links = 1,
371};
372
9aaca968
GW
373static struct platform_device *playpaq_snd_device;
374
375
376static int __init playpaq_asoc_init(void)
377{
378 int ret = 0;
9aaca968
GW
379
380 /*
381 * Configure MCLK for WM8510
382 */
383 _gclk0 = clk_get(NULL, "gclk0");
384 if (IS_ERR(_gclk0)) {
385 _gclk0 = NULL;
386 goto err_gclk0;
387 }
388 _pll0 = clk_get(NULL, "pll0");
389 if (IS_ERR(_pll0)) {
390 _pll0 = NULL;
391 goto err_pll0;
392 }
393 if (clk_set_parent(_gclk0, _pll0)) {
394 pr_warning("snd-soc-playpaq: "
395 "Failed to set PLL0 as parent for DAC clock\n");
396 goto err_set_clk;
397 }
398 clk_set_rate(CODEC_CLK, 12000000);
399 clk_enable(CODEC_CLK);
400
401#if defined CONFIG_AT32_ENHANCED_PORTMUX
402 at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0);
403#endif
404
405
406 /*
407 * Create and register platform device
408 */
409 playpaq_snd_device = platform_device_alloc("soc-audio", 0);
410 if (playpaq_snd_device == NULL) {
411 ret = -ENOMEM;
412 goto err_device_alloc;
413 }
414
f0fba2ad 415 platform_set_drvdata(playpaq_snd_device, &snd_soc_playpaq);
9aaca968
GW
416
417 ret = platform_device_add(playpaq_snd_device);
418 if (ret) {
419 pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",
420 ret);
421 goto err_device_add;
422 }
423
424 return 0;
425
426
427err_device_add:
428 if (playpaq_snd_device != NULL) {
429 platform_device_put(playpaq_snd_device);
430 playpaq_snd_device = NULL;
431 }
432err_device_alloc:
433err_set_clk:
434 if (_pll0 != NULL) {
435 clk_put(_pll0);
436 _pll0 = NULL;
437 }
438err_pll0:
439 if (_gclk0 != NULL) {
440 clk_put(_gclk0);
441 _gclk0 = NULL;
442 }
9aaca968
GW
443 return ret;
444}
445
446
447static void __exit playpaq_asoc_exit(void)
448{
9aaca968
GW
449 if (_gclk0 != NULL) {
450 clk_put(_gclk0);
451 _gclk0 = NULL;
452 }
453 if (_pll0 != NULL) {
454 clk_put(_pll0);
455 _pll0 = NULL;
456 }
457
458#if defined CONFIG_AT32_ENHANCED_PORTMUX
459 at32_free_pin(MCLK_PIN);
460#endif
461
462 platform_device_unregister(playpaq_snd_device);
463 playpaq_snd_device = NULL;
464}
465
466module_init(playpaq_asoc_init);
467module_exit(playpaq_asoc_exit);
468
469MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
470MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
471MODULE_LICENSE("GPL");
This page took 0.198922 seconds and 5 git commands to generate.