• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-R7000-V1.0.7.12_1.2.5/components/opensource/linux/linux-2.6.36/sound/soc/atmel/
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>
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>
36#include <sound/soc-dapm.h>
37
38#include <mach/at32ap700x.h>
39#include <mach/portmux.h>
40
41#include "../codecs/wm8510.h"
42#include "atmel-pcm.h"
43#include "atmel_ssc_dai.h"
44
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,
84	struct snd_soc_dai *cpu_dai)
85{
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;
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));
120	pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n",
121		 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;
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;
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:
167		pll_out = 24576000;
168		mclk_div = WM8510_MCLKDIV_2;
169		bclk = WM8510_BCLKDIV_8;
170		break;
171
172	case 44100:
173		pll_out = 22579200;
174		mclk_div = WM8510_MCLKDIV_2;
175		bclk = WM8510_BCLKDIV_8;
176		break;
177
178	case 22050:
179		pll_out = 22579200;
180		mclk_div = WM8510_MCLKDIV_4;
181		bclk = WM8510_BCLKDIV_8;
182		break;
183
184	case 16000:
185		pll_out = 24576000;
186		mclk_div = WM8510_MCLKDIV_6;
187		bclk = WM8510_BCLKDIV_8;
188		break;
189
190	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
202	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	 */
212	ret = snd_soc_dai_set_fmt(codec_dai, fmt);
213	if (ret < 0) {
214		pr_warning("playpaq_wm8510: "
215			   "Failed to set CODEC DAI format (%d)\n",
216			   ret);
217		return ret;
218	}
219	ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
220	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);
235	ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div);
236	if (ret < 0) {
237		pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",
238			   ret);
239		return ret;
240	}
241	ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD,
242					  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
261	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);
262	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
271	ret = snd_soc_dai_set_pll(codec_dai, 0, 0,
272					 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
280	ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);
281	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
306static const struct snd_soc_dapm_route intercon[] = {
307	/* 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"},
314};
315
316
317
318static int playpaq_wm8510_init(struct snd_soc_codec *codec)
319{
320	int i;
321
322	/*
323	 * Add DAPM widgets
324	 */
325	for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++)
326		snd_soc_dapm_new_control(codec, &playpaq_dapm_widgets[i]);
327
328
329
330	/*
331	 * Setup audio path interconnects
332	 */
333	snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
334
335
336
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);
341
342
343
344	/* Make CSB show PLL rate */
345	snd_soc_dai_set_clkdiv(codec->dai, WM8510_OPCLKDIV,
346				       WM8510_OPCLKDIV_1 | 4);
347
348	return 0;
349}
350
351
352
353static struct snd_soc_dai_link playpaq_wm8510_dai = {
354	.name = "WM8510",
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,
360};
361
362
363
364static struct snd_soc_card snd_soc_playpaq = {
365	.name = "LRS_PlayPaq_WM8510",
366	.platform = &at32_soc_platform,
367	.dai_link = &playpaq_wm8510_dai,
368	.num_links = 1,
369};
370
371
372
373static struct wm8510_setup_data playpaq_wm8510_setup = {
374	.i2c_bus = 0,
375	.i2c_address = 0x1a,
376};
377
378
379
380static 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,
384};
385
386static struct platform_device *playpaq_snd_device;
387
388
389static int __init playpaq_asoc_init(void)
390{
391	int ret = 0;
392	struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data;
393	struct ssc_device *ssc = NULL;
394
395
396	/*
397	 * Request SSC device
398	 */
399	ssc = ssc_request(0);
400	if (IS_ERR(ssc)) {
401		ret = PTR_ERR(ssc);
402		goto err_ssc;
403	}
404	ssc_p->ssc = ssc;
405
406
407	/*
408	 * Configure MCLK for WM8510
409	 */
410	_gclk0 = clk_get(NULL, "gclk0");
411	if (IS_ERR(_gclk0)) {
412		_gclk0 = NULL;
413		goto err_gclk0;
414	}
415	_pll0 = clk_get(NULL, "pll0");
416	if (IS_ERR(_pll0)) {
417		_pll0 = NULL;
418		goto err_pll0;
419	}
420	if (clk_set_parent(_gclk0, _pll0)) {
421		pr_warning("snd-soc-playpaq: "
422			   "Failed to set PLL0 as parent for DAC clock\n");
423		goto err_set_clk;
424	}
425	clk_set_rate(CODEC_CLK, 12000000);
426	clk_enable(CODEC_CLK);
427
428#if defined CONFIG_AT32_ENHANCED_PORTMUX
429	at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0);
430#endif
431
432
433	/*
434	 * Create and register platform device
435	 */
436	playpaq_snd_device = platform_device_alloc("soc-audio", 0);
437	if (playpaq_snd_device == NULL) {
438		ret = -ENOMEM;
439		goto err_device_alloc;
440	}
441
442	platform_set_drvdata(playpaq_snd_device, &playpaq_wm8510_snd_devdata);
443	playpaq_wm8510_snd_devdata.dev = &playpaq_snd_device->dev;
444
445	ret = platform_device_add(playpaq_snd_device);
446	if (ret) {
447		pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",
448			   ret);
449		goto err_device_add;
450	}
451
452	return 0;
453
454
455err_device_add:
456	if (playpaq_snd_device != NULL) {
457		platform_device_put(playpaq_snd_device);
458		playpaq_snd_device = NULL;
459	}
460err_device_alloc:
461err_set_clk:
462	if (_pll0 != NULL) {
463		clk_put(_pll0);
464		_pll0 = NULL;
465	}
466err_pll0:
467	if (_gclk0 != NULL) {
468		clk_put(_gclk0);
469		_gclk0 = NULL;
470	}
471err_gclk0:
472	ssc_free(ssc);
473err_ssc:
474	return ret;
475}
476
477
478static void __exit playpaq_asoc_exit(void)
479{
480	struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data;
481	struct ssc_device *ssc;
482
483	if (ssc_p != NULL) {
484		ssc = ssc_p->ssc;
485		if (ssc != NULL)
486			ssc_free(ssc);
487		ssc_p->ssc = NULL;
488	}
489
490	if (_gclk0 != NULL) {
491		clk_put(_gclk0);
492		_gclk0 = NULL;
493	}
494	if (_pll0 != NULL) {
495		clk_put(_pll0);
496		_pll0 = NULL;
497	}
498
499#if defined CONFIG_AT32_ENHANCED_PORTMUX
500	at32_free_pin(MCLK_PIN);
501#endif
502
503	platform_device_unregister(playpaq_snd_device);
504	playpaq_snd_device = NULL;
505}
506
507module_init(playpaq_asoc_init);
508module_exit(playpaq_asoc_exit);
509
510MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
511MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
512MODULE_LICENSE("GPL");
513