Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/sound/soc/atmel/playpaq_wm8510.c
10817 views
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 <[email protected]>
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
37
#include <mach/at32ap700x.h>
38
#include <mach/portmux.h>
39
40
#include "../codecs/wm8510.h"
41
#include "atmel-pcm.h"
42
#include "atmel_ssc_dai.h"
43
44
45
/*-------------------------------------------------------------------------*\
46
* constants
47
\*-------------------------------------------------------------------------*/
48
#define MCLK_PIN GPIO_PIN_PA(30)
49
#define MCLK_PERIPH GPIO_PERIPH_A
50
51
52
/*-------------------------------------------------------------------------*\
53
* data types
54
\*-------------------------------------------------------------------------*/
55
/* SSC clocking data */
56
struct ssc_clock_data {
57
/* CMR div */
58
unsigned int cmr_div;
59
60
/* Frame period (as needed by xCMR.PERIOD) */
61
unsigned int period;
62
63
/* The SSC clock rate these settings where calculated for */
64
unsigned long ssc_rate;
65
};
66
67
68
/*-------------------------------------------------------------------------*\
69
* module data
70
\*-------------------------------------------------------------------------*/
71
static struct clk *_gclk0;
72
static struct clk *_pll0;
73
74
#define CODEC_CLK (_gclk0)
75
76
77
/*-------------------------------------------------------------------------*\
78
* Sound SOC operations
79
\*-------------------------------------------------------------------------*/
80
#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
81
static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock(
82
struct snd_pcm_hw_params *params,
83
struct snd_soc_dai *cpu_dai)
84
{
85
struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai);
86
struct ssc_device *ssc = ssc_p->ssc;
87
struct ssc_clock_data cd;
88
unsigned int rate, width_bits, channels;
89
unsigned int bitrate, ssc_div;
90
unsigned actual_rate;
91
92
93
/*
94
* Figure out required bitrate
95
*/
96
rate = params_rate(params);
97
channels = params_channels(params);
98
width_bits = snd_pcm_format_physical_width(params_format(params));
99
bitrate = rate * width_bits * channels;
100
101
102
/*
103
* Figure out required SSC divider and period for required bitrate
104
*/
105
cd.ssc_rate = clk_get_rate(ssc->clk);
106
ssc_div = cd.ssc_rate / bitrate;
107
cd.cmr_div = ssc_div / 2;
108
if (ssc_div & 1) {
109
/* round cmr_div up */
110
cd.cmr_div++;
111
}
112
cd.period = width_bits - 1;
113
114
115
/*
116
* Find actual rate, compare to requested rate
117
*/
118
actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1));
119
pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n",
120
rate, actual_rate);
121
122
123
return cd;
124
}
125
#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
126
127
128
129
static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream,
130
struct snd_pcm_hw_params *params)
131
{
132
struct snd_soc_pcm_runtime *rtd = substream->private_data;
133
struct snd_soc_dai *codec_dai = rtd->codec_dai;
134
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
135
struct at32_ssc_info *ssc_p = snd_soc_dai_get_drvdata(cpu_dai);
136
struct ssc_device *ssc = ssc_p->ssc;
137
unsigned int pll_out = 0, bclk = 0, mclk_div = 0;
138
int ret;
139
140
141
/* Due to difficulties with getting the correct clocks from the AT32's
142
* PLL0, we're going to let the CODEC be in charge of all the clocks
143
*/
144
#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
145
const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
146
SND_SOC_DAIFMT_NB_NF |
147
SND_SOC_DAIFMT_CBM_CFM);
148
#else
149
struct ssc_clock_data cd;
150
const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
151
SND_SOC_DAIFMT_NB_NF |
152
SND_SOC_DAIFMT_CBS_CFS);
153
#endif
154
155
if (ssc == NULL) {
156
pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n");
157
return -EINVAL;
158
}
159
160
161
/*
162
* Figure out PLL and BCLK dividers for WM8510
163
*/
164
switch (params_rate(params)) {
165
case 48000:
166
pll_out = 24576000;
167
mclk_div = WM8510_MCLKDIV_2;
168
bclk = WM8510_BCLKDIV_8;
169
break;
170
171
case 44100:
172
pll_out = 22579200;
173
mclk_div = WM8510_MCLKDIV_2;
174
bclk = WM8510_BCLKDIV_8;
175
break;
176
177
case 22050:
178
pll_out = 22579200;
179
mclk_div = WM8510_MCLKDIV_4;
180
bclk = WM8510_BCLKDIV_8;
181
break;
182
183
case 16000:
184
pll_out = 24576000;
185
mclk_div = WM8510_MCLKDIV_6;
186
bclk = WM8510_BCLKDIV_8;
187
break;
188
189
case 11025:
190
pll_out = 22579200;
191
mclk_div = WM8510_MCLKDIV_8;
192
bclk = WM8510_BCLKDIV_8;
193
break;
194
195
case 8000:
196
pll_out = 24576000;
197
mclk_div = WM8510_MCLKDIV_12;
198
bclk = WM8510_BCLKDIV_8;
199
break;
200
201
default:
202
pr_warning("playpaq_wm8510: Unsupported sample rate %d\n",
203
params_rate(params));
204
return -EINVAL;
205
}
206
207
208
/*
209
* set CPU and CODEC DAI configuration
210
*/
211
ret = snd_soc_dai_set_fmt(codec_dai, fmt);
212
if (ret < 0) {
213
pr_warning("playpaq_wm8510: "
214
"Failed to set CODEC DAI format (%d)\n",
215
ret);
216
return ret;
217
}
218
ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
219
if (ret < 0) {
220
pr_warning("playpaq_wm8510: "
221
"Failed to set CPU DAI format (%d)\n",
222
ret);
223
return ret;
224
}
225
226
227
/*
228
* Set CPU clock configuration
229
*/
230
#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
231
cd = playpaq_wm8510_calc_ssc_clock(params, cpu_dai);
232
pr_debug("playpaq_wm8510: cmr_div = %d, period = %d\n",
233
cd.cmr_div, cd.period);
234
ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div);
235
if (ret < 0) {
236
pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",
237
ret);
238
return ret;
239
}
240
ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD,
241
cd.period);
242
if (ret < 0) {
243
pr_warning("playpaq_wm8510: "
244
"Failed to set CPU transmit period (%d)\n",
245
ret);
246
return ret;
247
}
248
#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
249
250
251
/*
252
* Set CODEC clock configuration
253
*/
254
pr_debug("playpaq_wm8510: "
255
"pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n",
256
clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div);
257
258
259
#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
260
ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);
261
if (ret < 0) {
262
pr_warning
263
("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n",
264
ret);
265
return ret;
266
}
267
#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
268
269
270
ret = snd_soc_dai_set_pll(codec_dai, 0, 0,
271
clk_get_rate(CODEC_CLK), pll_out);
272
if (ret < 0) {
273
pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n",
274
ret);
275
return ret;
276
}
277
278
279
ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);
280
if (ret < 0) {
281
pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n",
282
ret);
283
return ret;
284
}
285
286
287
return 0;
288
}
289
290
291
292
static struct snd_soc_ops playpaq_wm8510_ops = {
293
.hw_params = playpaq_wm8510_hw_params,
294
};
295
296
297
298
static const struct snd_soc_dapm_widget playpaq_dapm_widgets[] = {
299
SND_SOC_DAPM_MIC("Int Mic", NULL),
300
SND_SOC_DAPM_SPK("Ext Spk", NULL),
301
};
302
303
304
305
static const struct snd_soc_dapm_route intercon[] = {
306
/* speaker connected to SPKOUT */
307
{"Ext Spk", NULL, "SPKOUTP"},
308
{"Ext Spk", NULL, "SPKOUTN"},
309
310
{"Mic Bias", NULL, "Int Mic"},
311
{"MICN", NULL, "Mic Bias"},
312
{"MICP", NULL, "Mic Bias"},
313
};
314
315
316
317
static int playpaq_wm8510_init(struct snd_soc_pcm_runtime *rtd)
318
{
319
struct snd_soc_codec *codec = rtd->codec;
320
struct snd_soc_dapm_context *dapm = &codec->dapm;
321
int i;
322
323
/*
324
* Add DAPM widgets
325
*/
326
for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++)
327
snd_soc_dapm_new_control(dapm, &playpaq_dapm_widgets[i]);
328
329
330
331
/*
332
* Setup audio path interconnects
333
*/
334
snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
335
336
337
338
/* always connected pins */
339
snd_soc_dapm_enable_pin(dapm, "Int Mic");
340
snd_soc_dapm_enable_pin(dapm, "Ext Spk");
341
snd_soc_dapm_sync(dapm);
342
343
344
345
/* Make CSB show PLL rate */
346
snd_soc_dai_set_clkdiv(rtd->codec_dai, WM8510_OPCLKDIV,
347
WM8510_OPCLKDIV_1 | 4);
348
349
return 0;
350
}
351
352
353
354
static struct snd_soc_dai_link playpaq_wm8510_dai = {
355
.name = "WM8510",
356
.stream_name = "WM8510 PCM",
357
.cpu_dai_name= "atmel-ssc-dai.0",
358
.platform_name = "atmel-pcm-audio",
359
.codec_name = "wm8510-codec.0-0x1a",
360
.codec_dai_name = "wm8510-hifi",
361
.init = playpaq_wm8510_init,
362
.ops = &playpaq_wm8510_ops,
363
};
364
365
366
367
static struct snd_soc_card snd_soc_playpaq = {
368
.name = "LRS_PlayPaq_WM8510",
369
.dai_link = &playpaq_wm8510_dai,
370
.num_links = 1,
371
};
372
373
static struct platform_device *playpaq_snd_device;
374
375
376
static int __init playpaq_asoc_init(void)
377
{
378
int ret = 0;
379
380
/*
381
* Configure MCLK for WM8510
382
*/
383
_gclk0 = clk_get(NULL, "gclk0");
384
if (IS_ERR(_gclk0)) {
385
_gclk0 = NULL;
386
goto err_gclk0;
387
}
388
_pll0 = clk_get(NULL, "pll0");
389
if (IS_ERR(_pll0)) {
390
_pll0 = NULL;
391
goto err_pll0;
392
}
393
if (clk_set_parent(_gclk0, _pll0)) {
394
pr_warning("snd-soc-playpaq: "
395
"Failed to set PLL0 as parent for DAC clock\n");
396
goto err_set_clk;
397
}
398
clk_set_rate(CODEC_CLK, 12000000);
399
clk_enable(CODEC_CLK);
400
401
#if defined CONFIG_AT32_ENHANCED_PORTMUX
402
at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0);
403
#endif
404
405
406
/*
407
* Create and register platform device
408
*/
409
playpaq_snd_device = platform_device_alloc("soc-audio", 0);
410
if (playpaq_snd_device == NULL) {
411
ret = -ENOMEM;
412
goto err_device_alloc;
413
}
414
415
platform_set_drvdata(playpaq_snd_device, &snd_soc_playpaq);
416
417
ret = platform_device_add(playpaq_snd_device);
418
if (ret) {
419
pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",
420
ret);
421
goto err_device_add;
422
}
423
424
return 0;
425
426
427
err_device_add:
428
if (playpaq_snd_device != NULL) {
429
platform_device_put(playpaq_snd_device);
430
playpaq_snd_device = NULL;
431
}
432
err_device_alloc:
433
err_set_clk:
434
if (_pll0 != NULL) {
435
clk_put(_pll0);
436
_pll0 = NULL;
437
}
438
err_pll0:
439
if (_gclk0 != NULL) {
440
clk_put(_gclk0);
441
_gclk0 = NULL;
442
}
443
return ret;
444
}
445
446
447
static void __exit playpaq_asoc_exit(void)
448
{
449
if (_gclk0 != NULL) {
450
clk_put(_gclk0);
451
_gclk0 = NULL;
452
}
453
if (_pll0 != NULL) {
454
clk_put(_pll0);
455
_pll0 = NULL;
456
}
457
458
#if defined CONFIG_AT32_ENHANCED_PORTMUX
459
at32_free_pin(MCLK_PIN);
460
#endif
461
462
platform_device_unregister(playpaq_snd_device);
463
playpaq_snd_device = NULL;
464
}
465
466
module_init(playpaq_asoc_init);
467
module_exit(playpaq_asoc_exit);
468
469
MODULE_AUTHOR("Geoffrey Wossum <[email protected]>");
470
MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
471
MODULE_LICENSE("GPL");
472
473