]> err.no Git - linux-2.6/blob - sound/soc/at32/playpaq_wm8510.c
[SCSI] ibmvfc: Driver version 1.0.2
[linux-2.6] / sound / soc / at32 / playpaq_wm8510.c
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/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>
32
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>
38
39 #include <asm/arch/at32ap700x.h>
40 #include <asm/arch/portmux.h>
41
42 #include "../codecs/wm8510.h"
43 #include "at32-pcm.h"
44 #include "at32-ssc.h"
45
46
47 /*-------------------------------------------------------------------------*\
48  * constants
49 \*-------------------------------------------------------------------------*/
50 #define MCLK_PIN                GPIO_PIN_PA(30)
51 #define MCLK_PERIPH             GPIO_PERIPH_A
52
53
54 /*-------------------------------------------------------------------------*\
55  * data types
56 \*-------------------------------------------------------------------------*/
57 /* SSC clocking data */
58 struct ssc_clock_data {
59         /* CMR div */
60         unsigned int cmr_div;
61
62         /* Frame period (as needed by xCMR.PERIOD) */
63         unsigned int period;
64
65         /* The SSC clock rate these settings where calculated for */
66         unsigned long ssc_rate;
67 };
68
69
70 /*-------------------------------------------------------------------------*\
71  * module data
72 \*-------------------------------------------------------------------------*/
73 static struct clk *_gclk0;
74 static struct clk *_pll0;
75
76 #define CODEC_CLK (_gclk0)
77
78
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_dai *cpu_dai)
86 {
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;
92         unsigned actual_rate;
93
94
95         /*
96          * Figure out required bitrate
97          */
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;
102
103
104         /*
105          * Figure out required SSC divider and period for required bitrate
106          */
107         cd.ssc_rate = clk_get_rate(ssc->clk);
108         ssc_div = cd.ssc_rate / bitrate;
109         cd.cmr_div = ssc_div / 2;
110         if (ssc_div & 1) {
111                 /* round cmr_div up */
112                 cd.cmr_div++;
113         }
114         cd.period = width_bits - 1;
115
116
117         /*
118          * Find actual rate, compare to requested rate
119          */
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",
122                  rate, actual_rate);
123
124
125         return cd;
126 }
127 #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
128
129
130
131 static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream,
132                                     struct snd_pcm_hw_params *params)
133 {
134         struct snd_soc_pcm_runtime *rtd = substream->private_data;
135         struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
136         struct snd_soc_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;
140         int ret;
141
142
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
145          */
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);
150 #else
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);
155 #endif
156
157         if (ssc == NULL) {
158                 pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n");
159                 return -EINVAL;
160         }
161
162
163         /*
164          * Figure out PLL and BCLK dividers for WM8510
165          */
166         switch (params_rate(params)) {
167         case 48000:
168                 pll_out = 12288000;
169                 mclk_div = WM8510_MCLKDIV_1;
170                 bclk = WM8510_BCLKDIV_8;
171                 break;
172
173         case 44100:
174                 pll_out = 11289600;
175                 mclk_div = WM8510_MCLKDIV_1;
176                 bclk = WM8510_BCLKDIV_8;
177                 break;
178
179         case 22050:
180                 pll_out = 11289600;
181                 mclk_div = WM8510_MCLKDIV_2;
182                 bclk = WM8510_BCLKDIV_8;
183                 break;
184
185         case 16000:
186                 pll_out = 12288000;
187                 mclk_div = WM8510_MCLKDIV_3;
188                 bclk = WM8510_BCLKDIV_8;
189                 break;
190
191         case 11025:
192                 pll_out = 11289600;
193                 mclk_div = WM8510_MCLKDIV_4;
194                 bclk = WM8510_BCLKDIV_8;
195                 break;
196
197         case 8000:
198                 pll_out = 12288000;
199                 mclk_div = WM8510_MCLKDIV_6;
200                 bclk = WM8510_BCLKDIV_8;
201                 break;
202
203         default:
204                 pr_warning("playpaq_wm8510: Unsupported sample rate %d\n",
205                            params_rate(params));
206                 return -EINVAL;
207         }
208
209
210         /*
211          * set CPU and CODEC DAI configuration
212          */
213         ret = snd_soc_dai_set_fmt(codec_dai, fmt);
214         if (ret < 0) {
215                 pr_warning("playpaq_wm8510: "
216                            "Failed to set CODEC DAI format (%d)\n",
217                            ret);
218                 return ret;
219         }
220         ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
221         if (ret < 0) {
222                 pr_warning("playpaq_wm8510: "
223                            "Failed to set CPU DAI format (%d)\n",
224                            ret);
225                 return ret;
226         }
227
228
229         /*
230          * Set CPU clock configuration
231          */
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 = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div);
237         if (ret < 0) {
238                 pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",
239                            ret);
240                 return ret;
241         }
242         ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD,
243                                           cd.period);
244         if (ret < 0) {
245                 pr_warning("playpaq_wm8510: "
246                            "Failed to set CPU transmit period (%d)\n",
247                            ret);
248                 return ret;
249         }
250 #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
251
252
253         /*
254          * Set CODEC clock configuration
255          */
256         pr_debug("playpaq_wm8510: "
257                  "pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n",
258                  clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div);
259
260
261 #if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
262         ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);
263         if (ret < 0) {
264                 pr_warning
265                     ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n",
266                      ret);
267                 return ret;
268         }
269 #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
270
271
272         ret = snd_soc_dai_set_pll(codec_dai, 0,
273                                          clk_get_rate(CODEC_CLK), pll_out);
274         if (ret < 0) {
275                 pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n",
276                            ret);
277                 return ret;
278         }
279
280
281         ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);
282         if (ret < 0) {
283                 pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n",
284                            ret);
285                 return ret;
286         }
287
288
289         return 0;
290 }
291
292
293
294 static struct snd_soc_ops playpaq_wm8510_ops = {
295         .hw_params = playpaq_wm8510_hw_params,
296 };
297
298
299
300 static const struct snd_soc_dapm_widget playpaq_dapm_widgets[] = {
301         SND_SOC_DAPM_MIC("Int Mic", NULL),
302         SND_SOC_DAPM_SPK("Ext Spk", NULL),
303 };
304
305
306
307 static const char *intercon[][3] = {
308         /* speaker connected to SPKOUT */
309         {"Ext Spk", NULL, "SPKOUTP"},
310         {"Ext Spk", NULL, "SPKOUTN"},
311
312         {"Mic Bias", NULL, "Int Mic"},
313         {"MICN", NULL, "Mic Bias"},
314         {"MICP", NULL, "Mic Bias"},
315
316         /* Terminator */
317         {NULL, NULL, NULL},
318 };
319
320
321
322 static int playpaq_wm8510_init(struct snd_soc_codec *codec)
323 {
324         int i;
325
326         /*
327          * Add DAPM widgets
328          */
329         for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++)
330                 snd_soc_dapm_new_control(codec, &playpaq_dapm_widgets[i]);
331
332
333
334         /*
335          * Setup audio path interconnects
336          */
337         for (i = 0; intercon[i][0] != NULL; i++) {
338                 snd_soc_dapm_connect_input(codec,
339                                            intercon[i][0],
340                                            intercon[i][1], intercon[i][2]);
341         }
342
343
344         /* always connected pins */
345         snd_soc_dapm_enable_pin(codec, "Int Mic");
346         snd_soc_dapm_enable_pin(codec, "Ext Spk");
347         snd_soc_dapm_sync(codec);
348
349
350
351         /* Make CSB show PLL rate */
352         snd_soc_dai_set_clkdiv(codec->dai, WM8510_OPCLKDIV,
353                                        WM8510_OPCLKDIV_1 | 4);
354
355         return 0;
356 }
357
358
359
360 static struct snd_soc_dai_link playpaq_wm8510_dai = {
361         .name = "WM8510",
362         .stream_name = "WM8510 PCM",
363         .cpu_dai = &at32_ssc_dai[0],
364         .codec_dai = &wm8510_dai,
365         .init = playpaq_wm8510_init,
366         .ops = &playpaq_wm8510_ops,
367 };
368
369
370
371 static struct snd_soc_machine snd_soc_machine_playpaq = {
372         .name = "LRS_PlayPaq_WM8510",
373         .dai_link = &playpaq_wm8510_dai,
374         .num_links = 1,
375 };
376
377
378
379 static struct wm8510_setup_data playpaq_wm8510_setup = {
380         .i2c_address = 0x1a,
381 };
382
383
384
385 static struct snd_soc_device playpaq_wm8510_snd_devdata = {
386         .machine = &snd_soc_machine_playpaq,
387         .platform = &at32_soc_platform,
388         .codec_dev = &soc_codec_dev_wm8510,
389         .codec_data = &playpaq_wm8510_setup,
390 };
391
392 static struct platform_device *playpaq_snd_device;
393
394
395 static int __init playpaq_asoc_init(void)
396 {
397         int ret = 0;
398         struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data;
399         struct ssc_device *ssc = NULL;
400
401
402         /*
403          * Request SSC device
404          */
405         ssc = ssc_request(0);
406         if (IS_ERR(ssc)) {
407                 ret = PTR_ERR(ssc);
408                 ssc = NULL;
409                 goto err_ssc;
410         }
411         ssc_p->ssc = ssc;
412
413
414         /*
415          * Configure MCLK for WM8510
416          */
417         _gclk0 = clk_get(NULL, "gclk0");
418         if (IS_ERR(_gclk0)) {
419                 _gclk0 = NULL;
420                 goto err_gclk0;
421         }
422         _pll0 = clk_get(NULL, "pll0");
423         if (IS_ERR(_pll0)) {
424                 _pll0 = NULL;
425                 goto err_pll0;
426         }
427         if (clk_set_parent(_gclk0, _pll0)) {
428                 pr_warning("snd-soc-playpaq: "
429                            "Failed to set PLL0 as parent for DAC clock\n");
430                 goto err_set_clk;
431         }
432         clk_set_rate(CODEC_CLK, 12000000);
433         clk_enable(CODEC_CLK);
434
435 #if defined CONFIG_AT32_ENHANCED_PORTMUX
436         at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0);
437 #endif
438
439
440         /*
441          * Create and register platform device
442          */
443         playpaq_snd_device = platform_device_alloc("soc-audio", 0);
444         if (playpaq_snd_device == NULL) {
445                 ret = -ENOMEM;
446                 goto err_device_alloc;
447         }
448
449         platform_set_drvdata(playpaq_snd_device, &playpaq_wm8510_snd_devdata);
450         playpaq_wm8510_snd_devdata.dev = &playpaq_snd_device->dev;
451
452         ret = platform_device_add(playpaq_snd_device);
453         if (ret) {
454                 pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",
455                            ret);
456                 goto err_device_add;
457         }
458
459         return 0;
460
461
462 err_device_add:
463         if (playpaq_snd_device != NULL) {
464                 platform_device_put(playpaq_snd_device);
465                 playpaq_snd_device = NULL;
466         }
467 err_device_alloc:
468 err_set_clk:
469         if (_pll0 != NULL) {
470                 clk_put(_pll0);
471                 _pll0 = NULL;
472         }
473 err_pll0:
474         if (_gclk0 != NULL) {
475                 clk_put(_gclk0);
476                 _gclk0 = NULL;
477         }
478 err_gclk0:
479         if (ssc != NULL) {
480                 ssc_free(ssc);
481                 ssc = NULL;
482         }
483 err_ssc:
484         return ret;
485 }
486
487
488 static void __exit playpaq_asoc_exit(void)
489 {
490         struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data;
491         struct ssc_device *ssc;
492
493         if (ssc_p != NULL) {
494                 ssc = ssc_p->ssc;
495                 if (ssc != NULL)
496                         ssc_free(ssc);
497                 ssc_p->ssc = NULL;
498         }
499
500         if (_gclk0 != NULL) {
501                 clk_put(_gclk0);
502                 _gclk0 = NULL;
503         }
504         if (_pll0 != NULL) {
505                 clk_put(_pll0);
506                 _pll0 = NULL;
507         }
508
509 #if defined CONFIG_AT32_ENHANCED_PORTMUX
510         at32_free_pin(MCLK_PIN);
511 #endif
512
513         platform_device_unregister(playpaq_snd_device);
514         playpaq_snd_device = NULL;
515 }
516
517 module_init(playpaq_asoc_init);
518 module_exit(playpaq_asoc_exit);
519
520 MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
521 MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
522 MODULE_LICENSE("GPL");