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/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>
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>
39 #include <asm/arch/at32ap700x.h>
40 #include <asm/arch/portmux.h>
42 #include "../codecs/wm8510.h"
47 /*-------------------------------------------------------------------------*\
49 \*-------------------------------------------------------------------------*/
50 #define MCLK_PIN GPIO_PIN_PA(30)
51 #define MCLK_PERIPH GPIO_PERIPH_A
54 /*-------------------------------------------------------------------------*\
56 \*-------------------------------------------------------------------------*/
57 /* SSC clocking data */
58 struct ssc_clock_data
{
62 /* Frame period (as needed by xCMR.PERIOD) */
65 /* The SSC clock rate these settings where calculated for */
66 unsigned long ssc_rate
;
70 /*-------------------------------------------------------------------------*\
72 \*-------------------------------------------------------------------------*/
73 static struct clk
*_gclk0
;
74 static struct clk
*_pll0
;
76 #define CODEC_CLK (_gclk0)
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_cpu_dai
*cpu_dai
)
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
;
96 * Figure out required bitrate
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
;
105 * Figure out required SSC divider and period for required bitrate
107 cd
.ssc_rate
= clk_get_rate(ssc
->clk
);
108 ssc_div
= cd
.ssc_rate
/ bitrate
;
109 cd
.cmr_div
= ssc_div
/ 2;
111 /* round cmr_div up */
114 cd
.period
= width_bits
- 1;
118 * Find actual rate, compare to requested rate
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",
127 #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
131 static int playpaq_wm8510_hw_params(struct snd_pcm_substream
*substream
,
132 struct snd_pcm_hw_params
*params
)
134 struct snd_soc_pcm_runtime
*rtd
= substream
->private_data
;
135 struct snd_soc_codec_dai
*codec_dai
= rtd
->dai
->codec_dai
;
136 struct snd_soc_cpu_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;
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
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
);
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
);
158 pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n");
164 * Figure out PLL and BCLK dividers for WM8510
166 switch (params_rate(params
)) {
169 mclk_div
= WM8510_MCLKDIV_1
;
170 bclk
= WM8510_BCLKDIV_8
;
175 mclk_div
= WM8510_MCLKDIV_1
;
176 bclk
= WM8510_BCLKDIV_8
;
181 mclk_div
= WM8510_MCLKDIV_2
;
182 bclk
= WM8510_BCLKDIV_8
;
187 mclk_div
= WM8510_MCLKDIV_3
;
188 bclk
= WM8510_BCLKDIV_8
;
193 mclk_div
= WM8510_MCLKDIV_4
;
194 bclk
= WM8510_BCLKDIV_8
;
199 mclk_div
= WM8510_MCLKDIV_6
;
200 bclk
= WM8510_BCLKDIV_8
;
204 pr_warning("playpaq_wm8510: Unsupported sample rate %d\n",
205 params_rate(params
));
211 * set CPU and CODEC DAI configuration
213 ret
= codec_dai
->dai_ops
.set_fmt(codec_dai
, fmt
);
215 pr_warning("playpaq_wm8510: "
216 "Failed to set CODEC DAI format (%d)\n",
220 ret
= cpu_dai
->dai_ops
.set_fmt(cpu_dai
, fmt
);
222 pr_warning("playpaq_wm8510: "
223 "Failed to set CPU DAI format (%d)\n",
230 * Set CPU clock configuration
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
= cpu_dai
->dai_ops
.set_clkdiv(cpu_dai
,
237 AT32_SSC_CMR_DIV
, cd
.cmr_div
);
239 pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",
243 ret
= cpu_dai
->dai_ops
.set_clkdiv(cpu_dai
, AT32_SSC_TCMR_PERIOD
,
246 pr_warning("playpaq_wm8510: "
247 "Failed to set CPU transmit period (%d)\n",
251 #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
255 * Set CODEC clock configuration
257 pr_debug("playpaq_wm8510: "
258 "pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n",
259 clk_get_rate(CODEC_CLK
), pll_out
, bclk
, mclk_div
);
262 #if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
263 ret
= codec_dai
->dai_ops
.set_clkdiv(codec_dai
, WM8510_BCLKDIV
, bclk
);
266 ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n",
270 #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
273 ret
= codec_dai
->dai_ops
.set_pll(codec_dai
, 0,
274 clk_get_rate(CODEC_CLK
), pll_out
);
276 pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n",
282 ret
= codec_dai
->dai_ops
.set_clkdiv(codec_dai
,
283 WM8510_MCLKDIV
, mclk_div
);
285 pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n",
296 static struct snd_soc_ops playpaq_wm8510_ops
= {
297 .hw_params
= playpaq_wm8510_hw_params
,
302 static const struct snd_soc_dapm_widget playpaq_dapm_widgets
[] = {
303 SND_SOC_DAPM_MIC("Int Mic", NULL
),
304 SND_SOC_DAPM_SPK("Ext Spk", NULL
),
309 static const char *intercon
[][3] = {
310 /* speaker connected to SPKOUT */
311 {"Ext Spk", NULL
, "SPKOUTP"},
312 {"Ext Spk", NULL
, "SPKOUTN"},
314 {"Mic Bias", NULL
, "Int Mic"},
315 {"MICN", NULL
, "Mic Bias"},
316 {"MICP", NULL
, "Mic Bias"},
324 static int playpaq_wm8510_init(struct snd_soc_codec
*codec
)
331 for (i
= 0; i
< ARRAY_SIZE(playpaq_dapm_widgets
); i
++)
332 snd_soc_dapm_new_control(codec
, &playpaq_dapm_widgets
[i
]);
337 * Setup audio path interconnects
339 for (i
= 0; intercon
[i
][0] != NULL
; i
++) {
340 snd_soc_dapm_connect_input(codec
,
342 intercon
[i
][1], intercon
[i
][2]);
346 /* always connected endpoints */
347 snd_soc_dapm_set_endpoint(codec
, "Int Mic", 1);
348 snd_soc_dapm_set_endpoint(codec
, "Ext Spk", 1);
349 snd_soc_dapm_sync_endpoints(codec
);
353 /* Make CSB show PLL rate */
354 codec
->dai
->dai_ops
.set_clkdiv(codec
->dai
, WM8510_OPCLKDIV
,
355 WM8510_OPCLKDIV_1
| 4);
362 static struct snd_soc_dai_link playpaq_wm8510_dai
= {
364 .stream_name
= "WM8510 PCM",
365 .cpu_dai
= &at32_ssc_dai
[0],
366 .codec_dai
= &wm8510_dai
,
367 .init
= playpaq_wm8510_init
,
368 .ops
= &playpaq_wm8510_ops
,
373 static struct snd_soc_machine snd_soc_machine_playpaq
= {
374 .name
= "LRS_PlayPaq_WM8510",
375 .dai_link
= &playpaq_wm8510_dai
,
381 static struct wm8510_setup_data playpaq_wm8510_setup
= {
387 static struct snd_soc_device playpaq_wm8510_snd_devdata
= {
388 .machine
= &snd_soc_machine_playpaq
,
389 .platform
= &at32_soc_platform
,
390 .codec_dev
= &soc_codec_dev_wm8510
,
391 .codec_data
= &playpaq_wm8510_setup
,
394 static struct platform_device
*playpaq_snd_device
;
397 static int __init
playpaq_asoc_init(void)
400 struct at32_ssc_info
*ssc_p
= playpaq_wm8510_dai
.cpu_dai
->private_data
;
401 struct ssc_device
*ssc
= NULL
;
407 ssc
= ssc_request(0);
417 * Configure MCLK for WM8510
419 _gclk0
= clk_get(NULL
, "gclk0");
420 if (IS_ERR(_gclk0
)) {
424 _pll0
= clk_get(NULL
, "pll0");
429 if (clk_set_parent(_gclk0
, _pll0
)) {
430 pr_warning("snd-soc-playpaq: "
431 "Failed to set PLL0 as parent for DAC clock\n");
434 clk_set_rate(CODEC_CLK
, 12000000);
435 clk_enable(CODEC_CLK
);
437 #if defined CONFIG_AT32_ENHANCED_PORTMUX
438 at32_select_periph(MCLK_PIN
, MCLK_PERIPH
, 0);
443 * Create and register platform device
445 playpaq_snd_device
= platform_device_alloc("soc-audio", 0);
446 if (playpaq_snd_device
== NULL
) {
448 goto err_device_alloc
;
451 platform_set_drvdata(playpaq_snd_device
, &playpaq_wm8510_snd_devdata
);
452 playpaq_wm8510_snd_devdata
.dev
= &playpaq_snd_device
->dev
;
454 ret
= platform_device_add(playpaq_snd_device
);
456 pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",
465 if (playpaq_snd_device
!= NULL
) {
466 platform_device_put(playpaq_snd_device
);
467 playpaq_snd_device
= NULL
;
476 if (_gclk0
!= NULL
) {
490 static void __exit
playpaq_asoc_exit(void)
492 struct at32_ssc_info
*ssc_p
= playpaq_wm8510_dai
.cpu_dai
->private_data
;
493 struct ssc_device
*ssc
;
502 if (_gclk0
!= NULL
) {
511 #if defined CONFIG_AT32_ENHANCED_PORTMUX
512 at32_free_pin(MCLK_PIN
);
515 platform_device_unregister(playpaq_snd_device
);
516 playpaq_snd_device
= NULL
;
519 module_init(playpaq_asoc_init
);
520 module_exit(playpaq_asoc_exit
);
522 MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
523 MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
524 MODULE_LICENSE("GPL");