• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
57 struct 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 \*-------------------------------------------------------------------------*/
72 static struct clk *_gclk0;
73 static 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
playpaq_wm8510_calc_ssc_clock(struct snd_pcm_hw_params * params,struct snd_soc_dai * cpu_dai)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)
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 = %d, actual rate = %d\n",
121 		 rate, actual_rate);
122 
123 
124 	return cd;
125 }
126 #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
127 
128 
129 
playpaq_wm8510_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)130 static 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 = 12288000;
168 		mclk_div = WM8510_MCLKDIV_1;
169 		bclk = WM8510_BCLKDIV_8;
170 		break;
171 
172 	case 44100:
173 		pll_out = 11289600;
174 		mclk_div = WM8510_MCLKDIV_1;
175 		bclk = WM8510_BCLKDIV_8;
176 		break;
177 
178 	case 22050:
179 		pll_out = 11289600;
180 		mclk_div = WM8510_MCLKDIV_2;
181 		bclk = WM8510_BCLKDIV_8;
182 		break;
183 
184 	case 16000:
185 		pll_out = 12288000;
186 		mclk_div = WM8510_MCLKDIV_3;
187 		bclk = WM8510_BCLKDIV_8;
188 		break;
189 
190 	case 11025:
191 		pll_out = 11289600;
192 		mclk_div = WM8510_MCLKDIV_4;
193 		bclk = WM8510_BCLKDIV_8;
194 		break;
195 
196 	case 8000:
197 		pll_out = 12288000;
198 		mclk_div = WM8510_MCLKDIV_6;
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,
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 
293 static struct snd_soc_ops playpaq_wm8510_ops = {
294 	.hw_params = playpaq_wm8510_hw_params,
295 };
296 
297 
298 
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),
302 };
303 
304 
305 
306 static 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 
playpaq_wm8510_init(struct snd_soc_codec * codec)318 static 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 
353 static 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 
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,
368 	.num_links = 1,
369 };
370 
371 
372 
373 static struct wm8510_setup_data playpaq_wm8510_setup = {
374 	.i2c_bus = 0,
375 	.i2c_address = 0x1a,
376 };
377 
378 
379 
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,
384 };
385 
386 static struct platform_device *playpaq_snd_device;
387 
388 
playpaq_asoc_init(void)389 static 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 
455 err_device_add:
456 	if (playpaq_snd_device != NULL) {
457 		platform_device_put(playpaq_snd_device);
458 		playpaq_snd_device = NULL;
459 	}
460 err_device_alloc:
461 err_set_clk:
462 	if (_pll0 != NULL) {
463 		clk_put(_pll0);
464 		_pll0 = NULL;
465 	}
466 err_pll0:
467 	if (_gclk0 != NULL) {
468 		clk_put(_gclk0);
469 		_gclk0 = NULL;
470 	}
471 err_gclk0:
472 	ssc_free(ssc);
473 err_ssc:
474 	return ret;
475 }
476 
477 
playpaq_asoc_exit(void)478 static 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 
507 module_init(playpaq_asoc_init);
508 module_exit(playpaq_asoc_exit);
509 
510 MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
511 MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
512 MODULE_LICENSE("GPL");
513