1 /* sound/soc/at32/playpaq_wm8510.c
2 * ASoC machine driver for PlayPaq using WM8510 codec
4 * Copyright (C) 2008 Long Range Systems
5 * Geoffrey Wossum <gwossum@acm.org>
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.
11 * This code is largely inspired by sound/soc/at91/eti_b1_wm8731.c
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);
23 #include <linux/module.h>
24 #include <linux/moduleparam.h>
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>
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>
38 #include <mach/at32ap700x.h>
39 #include <mach/portmux.h>
41 #include "../codecs/wm8510.h"
42 #include "atmel-pcm.h"
43 #include "atmel_ssc_dai.h"
46 /*-------------------------------------------------------------------------*\
48 \*-------------------------------------------------------------------------*/
49 #define MCLK_PIN GPIO_PIN_PA(30)
50 #define MCLK_PERIPH GPIO_PERIPH_A
53 /*-------------------------------------------------------------------------*\
55 \*-------------------------------------------------------------------------*/
56 /* SSC clocking data */
57 struct ssc_clock_data {
61 /* Frame period (as needed by xCMR.PERIOD) */
64 /* The SSC clock rate these settings where calculated for */
65 unsigned long ssc_rate;
69 /*-------------------------------------------------------------------------*\
71 \*-------------------------------------------------------------------------*/
72 static struct clk *_gclk0;
73 static struct clk *_pll0;
75 #define CODEC_CLK (_gclk0)
78 /*-------------------------------------------------------------------------*\
79 * Sound SOC operations
80 \*-------------------------------------------------------------------------*/
81 #if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
82 static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock(
83 struct snd_pcm_hw_params *params,
84 struct snd_soc_dai *cpu_dai)
86 struct at32_ssc_info *ssc_p = cpu_dai->private_data;
87 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;
95 * Figure out required bitrate
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;
104 * Figure out required SSC divider and period for required bitrate
106 cd.ssc_rate = clk_get_rate(ssc->clk);
107 ssc_div = cd.ssc_rate / bitrate;
108 cd.cmr_div = ssc_div / 2;
110 /* round cmr_div up */
113 cd.period = width_bits - 1;
117 * Find actual rate, compare to requested rate
119 actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1));
120 pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n",
126 #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
130 static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream,
131 struct snd_pcm_hw_params *params)
133 struct snd_soc_pcm_runtime *rtd = substream->private_data;
134 struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
135 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
136 struct at32_ssc_info *ssc_p = cpu_dai->private_data;
137 struct ssc_device *ssc = ssc_p->ssc;
138 unsigned int pll_out = 0, bclk = 0, mclk_div = 0;
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
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);
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);
157 pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n");
163 * Figure out PLL and BCLK dividers for WM8510
165 switch (params_rate(params)) {
168 mclk_div = WM8510_MCLKDIV_2;
169 bclk = WM8510_BCLKDIV_8;
174 mclk_div = WM8510_MCLKDIV_2;
175 bclk = WM8510_BCLKDIV_8;
180 mclk_div = WM8510_MCLKDIV_4;
181 bclk = WM8510_BCLKDIV_8;
186 mclk_div = WM8510_MCLKDIV_6;
187 bclk = WM8510_BCLKDIV_8;
192 mclk_div = WM8510_MCLKDIV_8;
193 bclk = WM8510_BCLKDIV_8;
198 mclk_div = WM8510_MCLKDIV_12;
199 bclk = WM8510_BCLKDIV_8;
203 pr_warning("playpaq_wm8510: Unsupported sample rate %d\n",
204 params_rate(params));
210 * set CPU and CODEC DAI configuration
212 ret = snd_soc_dai_set_fmt(codec_dai, fmt);
214 pr_warning("playpaq_wm8510: "
215 "Failed to set CODEC DAI format (%d)\n",
219 ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
221 pr_warning("playpaq_wm8510: "
222 "Failed to set CPU DAI format (%d)\n",
229 * Set CPU clock configuration
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);
235 ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div);
237 pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",
241 ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD,
244 pr_warning("playpaq_wm8510: "
245 "Failed to set CPU transmit period (%d)\n",
249 #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
253 * Set CODEC clock configuration
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);
260 #if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
261 ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);
264 ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n",
268 #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
271 ret = snd_soc_dai_set_pll(codec_dai, 0,
272 clk_get_rate(CODEC_CLK), pll_out);
274 pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n",
280 ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);
282 pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n",
293 static struct snd_soc_ops playpaq_wm8510_ops = {
294 .hw_params = playpaq_wm8510_hw_params,
299 static 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),
306 static const struct snd_soc_dapm_route intercon[] = {
307 /* speaker connected to SPKOUT */
308 {"Ext Spk", NULL, "SPKOUTP"},
309 {"Ext Spk", NULL, "SPKOUTN"},
311 {"Mic Bias", NULL, "Int Mic"},
312 {"MICN", NULL, "Mic Bias"},
313 {"MICP", NULL, "Mic Bias"},
318 static int playpaq_wm8510_init(struct snd_soc_codec *codec)
325 for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++)
326 snd_soc_dapm_new_control(codec, &playpaq_dapm_widgets[i]);
331 * Setup audio path interconnects
333 snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
337 /* always connected pins */
338 snd_soc_dapm_enable_pin(codec, "Int Mic");
339 snd_soc_dapm_enable_pin(codec, "Ext Spk");
340 snd_soc_dapm_sync(codec);
344 /* Make CSB show PLL rate */
345 snd_soc_dai_set_clkdiv(codec->dai, WM8510_OPCLKDIV,
346 WM8510_OPCLKDIV_1 | 4);
353 static struct snd_soc_dai_link playpaq_wm8510_dai = {
355 .stream_name = "WM8510 PCM",
356 .cpu_dai = &at32_ssc_dai[0],
357 .codec_dai = &wm8510_dai,
358 .init = playpaq_wm8510_init,
359 .ops = &playpaq_wm8510_ops,
364 static struct snd_soc_card snd_soc_playpaq = {
365 .name = "LRS_PlayPaq_WM8510",
366 .platform = &at32_soc_platform,
367 .dai_link = &playpaq_wm8510_dai,
373 static struct wm8510_setup_data playpaq_wm8510_setup = {
380 static struct snd_soc_device playpaq_wm8510_snd_devdata = {
381 .card = &snd_soc_playpaq,
382 .codec_dev = &soc_codec_dev_wm8510,
383 .codec_data = &playpaq_wm8510_setup,
386 static struct platform_device *playpaq_snd_device;
389 static int __init playpaq_asoc_init(void)
392 struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data;
393 struct ssc_device *ssc = NULL;
399 ssc = ssc_request(0);
408 * Configure MCLK for WM8510
410 _gclk0 = clk_get(NULL, "gclk0");
411 if (IS_ERR(_gclk0)) {
415 _pll0 = clk_get(NULL, "pll0");
420 if (clk_set_parent(_gclk0, _pll0)) {
421 pr_warning("snd-soc-playpaq: "
422 "Failed to set PLL0 as parent for DAC clock\n");
425 clk_set_rate(CODEC_CLK, 12000000);
426 clk_enable(CODEC_CLK);
428 #if defined CONFIG_AT32_ENHANCED_PORTMUX
429 at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0);
434 * Create and register platform device
436 playpaq_snd_device = platform_device_alloc("soc-audio", 0);
437 if (playpaq_snd_device == NULL) {
439 goto err_device_alloc;
442 platform_set_drvdata(playpaq_snd_device, &playpaq_wm8510_snd_devdata);
443 playpaq_wm8510_snd_devdata.dev = &playpaq_snd_device->dev;
445 ret = platform_device_add(playpaq_snd_device);
447 pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",
456 if (playpaq_snd_device != NULL) {
457 platform_device_put(playpaq_snd_device);
458 playpaq_snd_device = NULL;
467 if (_gclk0 != NULL) {
478 static void __exit playpaq_asoc_exit(void)
480 struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data;
481 struct ssc_device *ssc;
490 if (_gclk0 != NULL) {
499 #if defined CONFIG_AT32_ENHANCED_PORTMUX
500 at32_free_pin(MCLK_PIN);
503 platform_device_unregister(playpaq_snd_device);
504 playpaq_snd_device = NULL;
507 module_init(playpaq_asoc_init);
508 module_exit(playpaq_asoc_exit);
510 MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
511 MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
512 MODULE_LICENSE("GPL");