aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSongjun Wu <songjun.wu@atmel.com>2015-10-08 06:13:31 -0400
committerMark Brown <broonie@kernel.org>2015-10-22 12:48:33 -0400
commite0a25b6d18624140905d79775f9e1b05c12502f5 (patch)
treefdeb7072b08b213cda925299354274d2189ea758
parent6ff33f3902c3b1c5d0db6b1e2c70b6d76fba357f (diff)
ASoC: atmel-classd: add the Audio Class D Amplifier
Add driver for the digital imput to PWM output stereo class D amplifier. It comes with filter, digitally controlled gain, an equalizer and a dmphase filter. Signed-off-by: Songjun Wu <songjun.wu@atmel.com> Signed-off-by: Mark Brown <broonie@kernel.org>
-rw-r--r--sound/soc/atmel/Kconfig9
-rw-r--r--sound/soc/atmel/Makefile2
-rw-r--r--sound/soc/atmel/atmel-classd.c679
-rw-r--r--sound/soc/atmel/atmel-classd.h120
4 files changed, 810 insertions, 0 deletions
diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig
index 1489cd461aec..2d30464b81ce 100644
--- a/sound/soc/atmel/Kconfig
+++ b/sound/soc/atmel/Kconfig
@@ -59,4 +59,13 @@ config SND_AT91_SOC_SAM9X5_WM8731
59 help 59 help
60 Say Y if you want to add support for audio SoC on an 60 Say Y if you want to add support for audio SoC on an
61 at91sam9x5 based board that is using WM8731 codec. 61 at91sam9x5 based board that is using WM8731 codec.
62
63config SND_ATMEL_SOC_CLASSD
64 tristate "Atmel ASoC driver for boards using CLASSD"
65 depends on ARCH_AT91 || COMPILE_TEST
66 select SND_ATMEL_SOC_DMA
67 select REGMAP_MMIO
68 help
69 Say Y if you want to add support for Atmel ASoC driver for boards using
70 CLASSD.
62endif 71endif
diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile
index b327e5cc8de3..f6f7db428216 100644
--- a/sound/soc/atmel/Makefile
+++ b/sound/soc/atmel/Makefile
@@ -11,7 +11,9 @@ obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o
11snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o 11snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o
12snd-atmel-soc-wm8904-objs := atmel_wm8904.o 12snd-atmel-soc-wm8904-objs := atmel_wm8904.o
13snd-soc-sam9x5-wm8731-objs := sam9x5_wm8731.o 13snd-soc-sam9x5-wm8731-objs := sam9x5_wm8731.o
14snd-atmel-soc-classd-objs := atmel-classd.o
14 15
15obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o 16obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o
16obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o 17obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o
17obj-$(CONFIG_SND_AT91_SOC_SAM9X5_WM8731) += snd-soc-sam9x5-wm8731.o 18obj-$(CONFIG_SND_AT91_SOC_SAM9X5_WM8731) += snd-soc-sam9x5-wm8731.o
19obj-$(CONFIG_SND_ATMEL_SOC_CLASSD) += snd-atmel-soc-classd.o
diff --git a/sound/soc/atmel/atmel-classd.c b/sound/soc/atmel/atmel-classd.c
new file mode 100644
index 000000000000..8276675730ef
--- /dev/null
+++ b/sound/soc/atmel/atmel-classd.c
@@ -0,0 +1,679 @@
1/* Atmel ALSA SoC Audio Class D Amplifier (CLASSD) driver
2 *
3 * Copyright (C) 2015 Atmel
4 *
5 * Author: Songjun Wu <songjun.wu@atmel.com>
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 or later
9 * as published by the Free Software Foundation.
10 */
11
12#include <linux/of.h>
13#include <linux/clk.h>
14#include <linux/module.h>
15#include <linux/platform_device.h>
16#include <linux/regmap.h>
17#include <sound/core.h>
18#include <sound/dmaengine_pcm.h>
19#include <sound/pcm_params.h>
20#include <sound/tlv.h>
21#include "atmel-classd.h"
22
23struct atmel_classd_pdata {
24 bool non_overlap_enable;
25 int non_overlap_time;
26 int pwm_type;
27 const char *card_name;
28};
29
30struct atmel_classd {
31 dma_addr_t phy_base;
32 struct regmap *regmap;
33 struct clk *pclk;
34 struct clk *gclk;
35 struct clk *aclk;
36 int irq;
37 const struct atmel_classd_pdata *pdata;
38};
39
40#ifdef CONFIG_OF
41static const struct of_device_id atmel_classd_of_match[] = {
42 {
43 .compatible = "atmel,sama5d2-classd",
44 }, {
45 /* sentinel */
46 }
47};
48MODULE_DEVICE_TABLE(of, atmel_classd_of_match);
49
50static struct atmel_classd_pdata *atmel_classd_dt_init(struct device *dev)
51{
52 struct device_node *np = dev->of_node;
53 struct atmel_classd_pdata *pdata;
54 const char *pwm_type;
55 int ret;
56
57 if (!np) {
58 dev_err(dev, "device node not found\n");
59 return ERR_PTR(-EINVAL);
60 }
61
62 pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
63 if (!pdata)
64 return ERR_PTR(-ENOMEM);
65
66 ret = of_property_read_string(np, "atmel,pwm-type", &pwm_type);
67 if ((ret == 0) && (strcmp(pwm_type, "diff") == 0))
68 pdata->pwm_type = CLASSD_MR_PWMTYP_DIFF;
69 else
70 pdata->pwm_type = CLASSD_MR_PWMTYP_SINGLE;
71
72 ret = of_property_read_u32(np,
73 "atmel,non-overlap-time", &pdata->non_overlap_time);
74 if (ret)
75 pdata->non_overlap_enable = false;
76 else
77 pdata->non_overlap_enable = true;
78
79 ret = of_property_read_string(np, "atmel,model", &pdata->card_name);
80 if (ret)
81 pdata->card_name = "CLASSD";
82
83 return pdata;
84}
85#else
86static inline struct atmel_classd_pdata *
87atmel_classd_dt_init(struct device *dev)
88{
89 return ERR_PTR(-EINVAL);
90}
91#endif
92
93#define ATMEL_CLASSD_RATES (SNDRV_PCM_RATE_8000 \
94 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 \
95 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 \
96 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 \
97 | SNDRV_PCM_RATE_96000)
98
99static const struct snd_pcm_hardware atmel_classd_hw = {
100 .info = SNDRV_PCM_INFO_MMAP
101 | SNDRV_PCM_INFO_MMAP_VALID
102 | SNDRV_PCM_INFO_INTERLEAVED
103 | SNDRV_PCM_INFO_RESUME
104 | SNDRV_PCM_INFO_PAUSE,
105 .formats = (SNDRV_PCM_FMTBIT_S16_LE),
106 .rates = ATMEL_CLASSD_RATES,
107 .rate_min = 8000,
108 .rate_max = 96000,
109 .channels_min = 2,
110 .channels_max = 2,
111 .buffer_bytes_max = 64 * 1024,
112 .period_bytes_min = 256,
113 .period_bytes_max = 32 * 1024,
114 .periods_min = 2,
115 .periods_max = 256,
116};
117
118#define ATMEL_CLASSD_PREALLOC_BUF_SIZE (64 * 1024)
119
120/* cpu dai component */
121static int atmel_classd_cpu_dai_startup(struct snd_pcm_substream *substream,
122 struct snd_soc_dai *cpu_dai)
123{
124 struct snd_soc_pcm_runtime *rtd = substream->private_data;
125 struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
126
127 regmap_write(dd->regmap, CLASSD_THR, 0x0);
128
129 return clk_prepare_enable(dd->pclk);
130}
131
132static void atmel_classd_cpu_dai_shutdown(struct snd_pcm_substream *substream,
133 struct snd_soc_dai *cpu_dai)
134{
135 struct snd_soc_pcm_runtime *rtd = substream->private_data;
136 struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
137
138 clk_disable_unprepare(dd->pclk);
139}
140
141static const struct snd_soc_dai_ops atmel_classd_cpu_dai_ops = {
142 .startup = atmel_classd_cpu_dai_startup,
143 .shutdown = atmel_classd_cpu_dai_shutdown,
144};
145
146static struct snd_soc_dai_driver atmel_classd_cpu_dai = {
147 .playback = {
148 .channels_min = 2,
149 .channels_max = 2,
150 .rates = ATMEL_CLASSD_RATES,
151 .formats = SNDRV_PCM_FMTBIT_S16_LE,},
152 .ops = &atmel_classd_cpu_dai_ops,
153};
154
155static const struct snd_soc_component_driver atmel_classd_cpu_dai_component = {
156 .name = "atmel-classd",
157};
158
159/* platform */
160static int
161atmel_classd_platform_configure_dma(struct snd_pcm_substream *substream,
162 struct snd_pcm_hw_params *params,
163 struct dma_slave_config *slave_config)
164{
165 struct snd_soc_pcm_runtime *rtd = substream->private_data;
166 struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
167
168 if (params_physical_width(params) != 16) {
169 dev_err(rtd->platform->dev,
170 "only supports 16-bit audio data\n");
171 return -EINVAL;
172 }
173
174 slave_config->direction = DMA_MEM_TO_DEV;
175 slave_config->dst_addr = dd->phy_base + CLASSD_THR;
176 slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
177 slave_config->dst_maxburst = 1;
178 slave_config->src_maxburst = 1;
179 slave_config->device_fc = false;
180
181 return 0;
182}
183
184static const struct snd_dmaengine_pcm_config
185atmel_classd_dmaengine_pcm_config = {
186 .prepare_slave_config = atmel_classd_platform_configure_dma,
187 .pcm_hardware = &atmel_classd_hw,
188 .prealloc_buffer_size = ATMEL_CLASSD_PREALLOC_BUF_SIZE,
189};
190
191/* codec */
192static const char * const mono_mode_text[] = {
193 "mix", "sat", "left", "right"
194};
195
196static SOC_ENUM_SINGLE_DECL(classd_mono_mode_enum,
197 CLASSD_INTPMR, CLASSD_INTPMR_MONO_MODE_SHIFT,
198 mono_mode_text);
199
200static const char * const eqcfg_text[] = {
201 "Treble-12dB", "Treble-6dB",
202 "Medium-8dB", "Medium-3dB",
203 "Bass-12dB", "Bass-6dB",
204 "0 dB",
205 "Bass+6dB", "Bass+12dB",
206 "Medium+3dB", "Medium+8dB",
207 "Treble+6dB", "Treble+12dB",
208};
209
210static const unsigned int eqcfg_value[] = {
211 CLASSD_INTPMR_EQCFG_T_CUT_12, CLASSD_INTPMR_EQCFG_T_CUT_6,
212 CLASSD_INTPMR_EQCFG_M_CUT_8, CLASSD_INTPMR_EQCFG_M_CUT_3,
213 CLASSD_INTPMR_EQCFG_B_CUT_12, CLASSD_INTPMR_EQCFG_B_CUT_6,
214 CLASSD_INTPMR_EQCFG_FLAT,
215 CLASSD_INTPMR_EQCFG_B_BOOST_6, CLASSD_INTPMR_EQCFG_B_BOOST_12,
216 CLASSD_INTPMR_EQCFG_M_BOOST_3, CLASSD_INTPMR_EQCFG_M_BOOST_8,
217 CLASSD_INTPMR_EQCFG_T_BOOST_6, CLASSD_INTPMR_EQCFG_T_BOOST_12,
218};
219
220static SOC_VALUE_ENUM_SINGLE_DECL(classd_eqcfg_enum,
221 CLASSD_INTPMR, CLASSD_INTPMR_EQCFG_SHIFT, 0xf,
222 eqcfg_text, eqcfg_value);
223
224static const DECLARE_TLV_DB_SCALE(classd_digital_tlv, -7800, 100, 1);
225
226static const struct snd_kcontrol_new atmel_classd_snd_controls[] = {
227SOC_DOUBLE_TLV("Playback Volume", CLASSD_INTPMR,
228 CLASSD_INTPMR_ATTL_SHIFT, CLASSD_INTPMR_ATTR_SHIFT,
229 78, 1, classd_digital_tlv),
230
231SOC_SINGLE("Deemphasis Switch", CLASSD_INTPMR,
232 CLASSD_INTPMR_DEEMP_SHIFT, 1, 0),
233
234SOC_SINGLE("Mono Switch", CLASSD_INTPMR, CLASSD_INTPMR_MONO_SHIFT, 1, 0),
235
236SOC_SINGLE("Swap Switch", CLASSD_INTPMR, CLASSD_INTPMR_SWAP_SHIFT, 1, 0),
237
238SOC_ENUM("Mono Mode", classd_mono_mode_enum),
239
240SOC_ENUM("EQ", classd_eqcfg_enum),
241};
242
243static const char * const pwm_type[] = {
244 "Single ended", "Differential"
245};
246
247static int atmel_classd_codec_probe(struct snd_soc_codec *codec)
248{
249 struct snd_soc_card *card = snd_soc_codec_get_drvdata(codec);
250 struct atmel_classd *dd = snd_soc_card_get_drvdata(card);
251 const struct atmel_classd_pdata *pdata = dd->pdata;
252 u32 mask, val;
253
254 mask = CLASSD_MR_PWMTYP_MASK;
255 val = pdata->pwm_type << CLASSD_MR_PWMTYP_SHIFT;
256
257 mask |= CLASSD_MR_NON_OVERLAP_MASK;
258 if (pdata->non_overlap_enable) {
259 val |= (CLASSD_MR_NON_OVERLAP_EN
260 << CLASSD_MR_NON_OVERLAP_SHIFT);
261
262 mask |= CLASSD_MR_NOVR_VAL_MASK;
263 switch (pdata->non_overlap_time) {
264 case 5:
265 val |= (CLASSD_MR_NOVR_VAL_5NS
266 << CLASSD_MR_NOVR_VAL_SHIFT);
267 break;
268 case 10:
269 val |= (CLASSD_MR_NOVR_VAL_10NS
270 << CLASSD_MR_NOVR_VAL_SHIFT);
271 break;
272 case 15:
273 val |= (CLASSD_MR_NOVR_VAL_15NS
274 << CLASSD_MR_NOVR_VAL_SHIFT);
275 break;
276 case 20:
277 val |= (CLASSD_MR_NOVR_VAL_20NS
278 << CLASSD_MR_NOVR_VAL_SHIFT);
279 break;
280 default:
281 val |= (CLASSD_MR_NOVR_VAL_10NS
282 << CLASSD_MR_NOVR_VAL_SHIFT);
283 dev_warn(codec->dev,
284 "non-overlapping value %d is invalid, the default value 10 is specified\n",
285 pdata->non_overlap_time);
286 break;
287 }
288 }
289
290 snd_soc_update_bits(codec, CLASSD_MR, mask, val);
291
292 dev_info(codec->dev,
293 "PWM modulation type is %s, non-overlapping is %s\n",
294 pwm_type[pdata->pwm_type],
295 pdata->non_overlap_enable?"enabled":"disabled");
296
297 return 0;
298}
299
300static struct regmap *atmel_classd_codec_get_remap(struct device *dev)
301{
302 return dev_get_regmap(dev, NULL);
303}
304
305static struct snd_soc_codec_driver soc_codec_dev_classd = {
306 .probe = atmel_classd_codec_probe,
307 .controls = atmel_classd_snd_controls,
308 .num_controls = ARRAY_SIZE(atmel_classd_snd_controls),
309 .get_regmap = atmel_classd_codec_get_remap,
310};
311
312/* codec dai component */
313static int atmel_classd_codec_dai_startup(struct snd_pcm_substream *substream,
314 struct snd_soc_dai *codec_dai)
315{
316 struct snd_soc_pcm_runtime *rtd = substream->private_data;
317 struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
318 int ret;
319
320 ret = clk_prepare_enable(dd->aclk);
321 if (ret)
322 return ret;
323
324 return clk_prepare_enable(dd->gclk);
325}
326
327static int atmel_classd_codec_dai_digital_mute(struct snd_soc_dai *codec_dai,
328 int mute)
329{
330 struct snd_soc_codec *codec = codec_dai->codec;
331 u32 mask, val;
332
333 mask = CLASSD_MR_LMUTE_MASK | CLASSD_MR_RMUTE_MASK;
334
335 if (mute)
336 val = mask;
337 else
338 val = 0;
339
340 snd_soc_update_bits(codec, CLASSD_MR, mask, val);
341
342 return 0;
343}
344
345#define CLASSD_ACLK_RATE_11M2896_MPY_8 (112896 * 100 * 8)
346#define CLASSD_ACLK_RATE_12M288_MPY_8 (12228 * 1000 * 8)
347
348static struct {
349 int rate;
350 int sample_rate;
351 int dsp_clk;
352 unsigned long aclk_rate;
353} const sample_rates[] = {
354 { 8000, CLASSD_INTPMR_FRAME_8K,
355 CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_ACLK_RATE_12M288_MPY_8 },
356 { 16000, CLASSD_INTPMR_FRAME_16K,
357 CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_ACLK_RATE_12M288_MPY_8 },
358 { 32000, CLASSD_INTPMR_FRAME_32K,
359 CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_ACLK_RATE_12M288_MPY_8 },
360 { 48000, CLASSD_INTPMR_FRAME_48K,
361 CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_ACLK_RATE_12M288_MPY_8 },
362 { 96000, CLASSD_INTPMR_FRAME_96K,
363 CLASSD_INTPMR_DSP_CLK_FREQ_12M288, CLASSD_ACLK_RATE_12M288_MPY_8 },
364 { 22050, CLASSD_INTPMR_FRAME_22K,
365 CLASSD_INTPMR_DSP_CLK_FREQ_11M2896, CLASSD_ACLK_RATE_11M2896_MPY_8 },
366 { 44100, CLASSD_INTPMR_FRAME_44K,
367 CLASSD_INTPMR_DSP_CLK_FREQ_11M2896, CLASSD_ACLK_RATE_11M2896_MPY_8 },
368 { 88200, CLASSD_INTPMR_FRAME_88K,
369 CLASSD_INTPMR_DSP_CLK_FREQ_11M2896, CLASSD_ACLK_RATE_11M2896_MPY_8 },
370};
371
372static int
373atmel_classd_codec_dai_hw_params(struct snd_pcm_substream *substream,
374 struct snd_pcm_hw_params *params,
375 struct snd_soc_dai *codec_dai)
376{
377 struct snd_soc_pcm_runtime *rtd = substream->private_data;
378 struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
379 struct snd_soc_codec *codec = codec_dai->codec;
380 int fs;
381 int i, best, best_val, cur_val, ret;
382 u32 mask, val;
383
384 fs = params_rate(params);
385
386 best = 0;
387 best_val = abs(fs - sample_rates[0].rate);
388 for (i = 1; i < ARRAY_SIZE(sample_rates); i++) {
389 /* Closest match */
390 cur_val = abs(fs - sample_rates[i].rate);
391 if (cur_val < best_val) {
392 best = i;
393 best_val = cur_val;
394 }
395 }
396
397 dev_dbg(codec->dev,
398 "Selected SAMPLE_RATE of %dHz, ACLK_RATE of %ldHz\n",
399 sample_rates[best].rate, sample_rates[best].aclk_rate);
400
401 clk_disable_unprepare(dd->gclk);
402 clk_disable_unprepare(dd->aclk);
403
404 ret = clk_set_rate(dd->aclk, sample_rates[best].aclk_rate);
405 if (ret)
406 return ret;
407
408 mask = CLASSD_INTPMR_DSP_CLK_FREQ_MASK | CLASSD_INTPMR_FRAME_MASK;
409 val = (sample_rates[best].dsp_clk << CLASSD_INTPMR_DSP_CLK_FREQ_SHIFT)
410 | (sample_rates[best].sample_rate << CLASSD_INTPMR_FRAME_SHIFT);
411
412 snd_soc_update_bits(codec, CLASSD_INTPMR, mask, val);
413
414 ret = clk_prepare_enable(dd->aclk);
415 if (ret)
416 return ret;
417
418 return clk_prepare_enable(dd->gclk);
419}
420
421static void
422atmel_classd_codec_dai_shutdown(struct snd_pcm_substream *substream,
423 struct snd_soc_dai *codec_dai)
424{
425 struct snd_soc_pcm_runtime *rtd = substream->private_data;
426 struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
427
428 clk_disable_unprepare(dd->gclk);
429 clk_disable_unprepare(dd->aclk);
430}
431
432static int atmel_classd_codec_dai_prepare(struct snd_pcm_substream *substream,
433 struct snd_soc_dai *codec_dai)
434{
435 struct snd_soc_codec *codec = codec_dai->codec;
436
437 snd_soc_update_bits(codec, CLASSD_MR,
438 CLASSD_MR_LEN_MASK | CLASSD_MR_REN_MASK,
439 (CLASSD_MR_LEN_DIS << CLASSD_MR_LEN_SHIFT)
440 |(CLASSD_MR_REN_DIS << CLASSD_MR_REN_SHIFT));
441
442 return 0;
443}
444
445static int atmel_classd_codec_dai_trigger(struct snd_pcm_substream *substream,
446 int cmd, struct snd_soc_dai *codec_dai)
447{
448 struct snd_soc_codec *codec = codec_dai->codec;
449 u32 mask, val;
450
451 mask = CLASSD_MR_LEN_MASK | CLASSD_MR_REN_MASK;
452
453 switch (cmd) {
454 case SNDRV_PCM_TRIGGER_START:
455 case SNDRV_PCM_TRIGGER_RESUME:
456 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
457 val = mask;
458 break;
459 case SNDRV_PCM_TRIGGER_STOP:
460 case SNDRV_PCM_TRIGGER_SUSPEND:
461 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
462 val = (CLASSD_MR_LEN_DIS << CLASSD_MR_LEN_SHIFT)
463 | (CLASSD_MR_REN_DIS << CLASSD_MR_REN_SHIFT);
464 break;
465 default:
466 return -EINVAL;
467 }
468
469 snd_soc_update_bits(codec, CLASSD_MR, mask, val);
470
471 return 0;
472}
473
474static const struct snd_soc_dai_ops atmel_classd_codec_dai_ops = {
475 .digital_mute = atmel_classd_codec_dai_digital_mute,
476 .startup = atmel_classd_codec_dai_startup,
477 .shutdown = atmel_classd_codec_dai_shutdown,
478 .hw_params = atmel_classd_codec_dai_hw_params,
479 .prepare = atmel_classd_codec_dai_prepare,
480 .trigger = atmel_classd_codec_dai_trigger,
481};
482
483#define ATMEL_CLASSD_CODEC_DAI_NAME "atmel-classd-hifi"
484
485static struct snd_soc_dai_driver atmel_classd_codec_dai = {
486 .name = ATMEL_CLASSD_CODEC_DAI_NAME,
487 .playback = {
488 .stream_name = "Playback",
489 .channels_min = 2,
490 .channels_max = 2,
491 .rates = ATMEL_CLASSD_RATES,
492 .formats = SNDRV_PCM_FMTBIT_S16_LE,
493 },
494 .ops = &atmel_classd_codec_dai_ops,
495};
496
497/* ASoC sound card */
498static int atmel_classd_asoc_card_init(struct device *dev,
499 struct snd_soc_card *card)
500{
501 struct snd_soc_dai_link *dai_link;
502 struct atmel_classd *dd = snd_soc_card_get_drvdata(card);
503
504 dai_link = devm_kzalloc(dev, sizeof(*dai_link), GFP_KERNEL);
505 if (!dai_link)
506 return -ENOMEM;
507
508 dai_link->name = "CLASSD";
509 dai_link->stream_name = "CLASSD PCM";
510 dai_link->codec_dai_name = ATMEL_CLASSD_CODEC_DAI_NAME;
511 dai_link->cpu_dai_name = dev_name(dev);
512 dai_link->codec_name = dev_name(dev);
513 dai_link->platform_name = dev_name(dev);
514
515 card->dai_link = dai_link;
516 card->num_links = 1;
517 card->name = dd->pdata->card_name;
518 card->dev = dev;
519
520 return 0;
521};
522
523/* regmap configuration */
524static const struct reg_default atmel_classd_reg_defaults[] = {
525 { CLASSD_INTPMR, 0x00301212 },
526};
527
528#define ATMEL_CLASSD_REG_MAX 0xE4
529static const struct regmap_config atmel_classd_regmap_config = {
530 .reg_bits = 32,
531 .reg_stride = 4,
532 .val_bits = 32,
533 .max_register = ATMEL_CLASSD_REG_MAX,
534
535 .cache_type = REGCACHE_FLAT,
536 .reg_defaults = atmel_classd_reg_defaults,
537 .num_reg_defaults = ARRAY_SIZE(atmel_classd_reg_defaults),
538};
539
540static int atmel_classd_probe(struct platform_device *pdev)
541{
542 struct device *dev = &pdev->dev;
543 struct atmel_classd *dd;
544 struct resource *res;
545 void __iomem *io_base;
546 const struct atmel_classd_pdata *pdata;
547 struct snd_soc_card *card;
548 int ret;
549
550 pdata = dev_get_platdata(dev);
551 if (!pdata) {
552 pdata = atmel_classd_dt_init(dev);
553 if (IS_ERR(pdata))
554 return PTR_ERR(pdata);
555 }
556
557 dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL);
558 if (!dd)
559 return -ENOMEM;
560
561 dd->pdata = pdata;
562
563 dd->irq = platform_get_irq(pdev, 0);
564 if (dd->irq < 0) {
565 ret = dd->irq;
566 dev_err(dev, "failed to could not get irq: %d\n", ret);
567 return ret;
568 }
569
570 dd->pclk = devm_clk_get(dev, "pclk");
571 if (IS_ERR(dd->pclk)) {
572 ret = PTR_ERR(dd->pclk);
573 dev_err(dev, "failed to get peripheral clock: %d\n", ret);
574 return ret;
575 }
576
577 dd->gclk = devm_clk_get(dev, "gclk");
578 if (IS_ERR(dd->gclk)) {
579 ret = PTR_ERR(dd->gclk);
580 dev_err(dev, "failed to get GCK clock: %d\n", ret);
581 return ret;
582 }
583
584 dd->aclk = devm_clk_get(dev, "aclk");
585 if (IS_ERR(dd->aclk)) {
586 ret = PTR_ERR(dd->aclk);
587 dev_err(dev, "failed to get audio clock: %d\n", ret);
588 return ret;
589 }
590
591 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
592 if (!res) {
593 dev_err(dev, "no memory resource\n");
594 return -ENXIO;
595 }
596
597 io_base = devm_ioremap_resource(dev, res);
598 if (IS_ERR(io_base)) {
599 ret = PTR_ERR(io_base);
600 dev_err(dev, "failed to remap register memory: %d\n", ret);
601 return ret;
602 }
603
604 dd->phy_base = res->start;
605
606 dd->regmap = devm_regmap_init_mmio(dev, io_base,
607 &atmel_classd_regmap_config);
608 if (IS_ERR(dd->regmap)) {
609 ret = PTR_ERR(dd->regmap);
610 dev_err(dev, "failed to init register map: %d\n", ret);
611 return ret;
612 }
613
614 ret = devm_snd_soc_register_component(dev,
615 &atmel_classd_cpu_dai_component,
616 &atmel_classd_cpu_dai, 1);
617 if (ret) {
618 dev_err(dev, "could not register CPU DAI: %d\n", ret);
619 return ret;
620 }
621
622 ret = devm_snd_dmaengine_pcm_register(dev,
623 &atmel_classd_dmaengine_pcm_config,
624 0);
625 if (ret) {
626 dev_err(dev, "could not register platform: %d\n", ret);
627 return ret;
628 }
629
630 ret = snd_soc_register_codec(dev, &soc_codec_dev_classd,
631 &atmel_classd_codec_dai, 1);
632 if (ret) {
633 dev_err(dev, "could not register codec: %d\n", ret);
634 return ret;
635 }
636
637 /* register sound card */
638 card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
639 if (!card)
640 return -ENOMEM;
641
642 snd_soc_card_set_drvdata(card, dd);
643 platform_set_drvdata(pdev, card);
644
645 ret = atmel_classd_asoc_card_init(dev, card);
646 if (ret) {
647 dev_err(dev, "failed to init sound card\n");
648 return ret;
649 }
650
651 ret = devm_snd_soc_register_card(dev, card);
652 if (ret) {
653 dev_err(dev, "failed to register sound card: %d\n", ret);
654 return ret;
655 }
656
657 return 0;
658}
659
660static int atmel_classd_remove(struct platform_device *pdev)
661{
662 snd_soc_unregister_codec(&pdev->dev);
663 return 0;
664}
665
666static struct platform_driver atmel_classd_driver = {
667 .driver = {
668 .name = "atmel-classd",
669 .of_match_table = of_match_ptr(atmel_classd_of_match),
670 .pm = &snd_soc_pm_ops,
671 },
672 .probe = atmel_classd_probe,
673 .remove = atmel_classd_remove,
674};
675module_platform_driver(atmel_classd_driver);
676
677MODULE_DESCRIPTION("Atmel ClassD driver under ALSA SoC architecture");
678MODULE_AUTHOR("Songjun Wu <songjun.wu@atmel.com>");
679MODULE_LICENSE("GPL");
diff --git a/sound/soc/atmel/atmel-classd.h b/sound/soc/atmel/atmel-classd.h
new file mode 100644
index 000000000000..73f8fdd1ca83
--- /dev/null
+++ b/sound/soc/atmel/atmel-classd.h
@@ -0,0 +1,120 @@
1#ifndef __ATMEL_CLASSD_H_
2#define __ATMEL_CLASSD_H_
3
4#define CLASSD_CR 0x00000000
5#define CLASSD_CR_RESET 0x1
6
7#define CLASSD_MR 0x00000004
8
9#define CLASSD_MR_LEN_DIS 0x0
10#define CLASSD_MR_LEN_EN 0x1
11#define CLASSD_MR_LEN_MASK (0x1 << 0)
12#define CLASSD_MR_LEN_SHIFT (0)
13
14#define CLASSD_MR_LMUTE_DIS 0x0
15#define CLASSD_MR_LMUTE_EN 0x1
16#define CLASSD_MR_LMUTE_SHIFT (0x1)
17#define CLASSD_MR_LMUTE_MASK (0x1 << 1)
18
19#define CLASSD_MR_REN_DIS 0x0
20#define CLASSD_MR_REN_EN 0x1
21#define CLASSD_MR_REN_MASK (0x1 << 4)
22#define CLASSD_MR_REN_SHIFT (4)
23
24#define CLASSD_MR_RMUTE_DIS 0x0
25#define CLASSD_MR_RMUTE_EN 0x1
26#define CLASSD_MR_RMUTE_SHIFT (0x5)
27#define CLASSD_MR_RMUTE_MASK (0x1 << 5)
28
29#define CLASSD_MR_PWMTYP_SINGLE 0x0
30#define CLASSD_MR_PWMTYP_DIFF 0x1
31#define CLASSD_MR_PWMTYP_MASK (0x1 << 8)
32#define CLASSD_MR_PWMTYP_SHIFT (8)
33
34#define CLASSD_MR_NON_OVERLAP_DIS 0x0
35#define CLASSD_MR_NON_OVERLAP_EN 0x1
36#define CLASSD_MR_NON_OVERLAP_MASK (0x1 << 16)
37#define CLASSD_MR_NON_OVERLAP_SHIFT (16)
38
39#define CLASSD_MR_NOVR_VAL_5NS 0x0
40#define CLASSD_MR_NOVR_VAL_10NS 0x1
41#define CLASSD_MR_NOVR_VAL_15NS 0x2
42#define CLASSD_MR_NOVR_VAL_20NS 0x3
43#define CLASSD_MR_NOVR_VAL_MASK (0x3 << 20)
44#define CLASSD_MR_NOVR_VAL_SHIFT (20)
45
46#define CLASSD_INTPMR 0x00000008
47
48#define CLASSD_INTPMR_ATTL_MASK (0x3f << 0)
49#define CLASSD_INTPMR_ATTL_SHIFT (0)
50#define CLASSD_INTPMR_ATTR_MASK (0x3f << 8)
51#define CLASSD_INTPMR_ATTR_SHIFT (8)
52
53#define CLASSD_INTPMR_DSP_CLK_FREQ_12M288 0x0
54#define CLASSD_INTPMR_DSP_CLK_FREQ_11M2896 0x1
55#define CLASSD_INTPMR_DSP_CLK_FREQ_MASK (0x1 << 16)
56#define CLASSD_INTPMR_DSP_CLK_FREQ_SHIFT (16)
57
58#define CLASSD_INTPMR_DEEMP_DIS 0x0
59#define CLASSD_INTPMR_DEEMP_EN 0x1
60#define CLASSD_INTPMR_DEEMP_MASK (0x1 << 18)
61#define CLASSD_INTPMR_DEEMP_SHIFT (18)
62
63#define CLASSD_INTPMR_SWAP_LEFT_ON_LSB 0x0
64#define CLASSD_INTPMR_SWAP_RIGHT_ON_LSB 0x1
65#define CLASSD_INTPMR_SWAP_MASK (0x1 << 19)
66#define CLASSD_INTPMR_SWAP_SHIFT (19)
67
68#define CLASSD_INTPMR_FRAME_8K 0x0
69#define CLASSD_INTPMR_FRAME_16K 0x1
70#define CLASSD_INTPMR_FRAME_32K 0x2
71#define CLASSD_INTPMR_FRAME_48K 0x3
72#define CLASSD_INTPMR_FRAME_96K 0x4
73#define CLASSD_INTPMR_FRAME_22K 0x5
74#define CLASSD_INTPMR_FRAME_44K 0x6
75#define CLASSD_INTPMR_FRAME_88K 0x7
76#define CLASSD_INTPMR_FRAME_MASK (0x7 << 20)
77#define CLASSD_INTPMR_FRAME_SHIFT (20)
78
79#define CLASSD_INTPMR_EQCFG_FLAT 0x0
80#define CLASSD_INTPMR_EQCFG_B_BOOST_12 0x1
81#define CLASSD_INTPMR_EQCFG_B_BOOST_6 0x2
82#define CLASSD_INTPMR_EQCFG_B_CUT_12 0x3
83#define CLASSD_INTPMR_EQCFG_B_CUT_6 0x4
84#define CLASSD_INTPMR_EQCFG_M_BOOST_3 0x5
85#define CLASSD_INTPMR_EQCFG_M_BOOST_8 0x6
86#define CLASSD_INTPMR_EQCFG_M_CUT_3 0x7
87#define CLASSD_INTPMR_EQCFG_M_CUT_8 0x8
88#define CLASSD_INTPMR_EQCFG_T_BOOST_12 0x9
89#define CLASSD_INTPMR_EQCFG_T_BOOST_6 0xa
90#define CLASSD_INTPMR_EQCFG_T_CUT_12 0xb
91#define CLASSD_INTPMR_EQCFG_T_CUT_6 0xc
92#define CLASSD_INTPMR_EQCFG_SHIFT (24)
93
94#define CLASSD_INTPMR_MONO_DIS 0x0
95#define CLASSD_INTPMR_MONO_EN 0x1
96#define CLASSD_INTPMR_MONO_MASK (0x1 << 28)
97#define CLASSD_INTPMR_MONO_SHIFT (28)
98
99#define CLASSD_INTPMR_MONO_MODE_MIX 0x0
100#define CLASSD_INTPMR_MONO_MODE_SAT 0x1
101#define CLASSD_INTPMR_MONO_MODE_LEFT 0x2
102#define CLASSD_INTPMR_MONO_MODE_RIGHT 0x3
103#define CLASSD_INTPMR_MONO_MODE_MASK (0x3 << 29)
104#define CLASSD_INTPMR_MONO_MODE_SHIFT (29)
105
106#define CLASSD_INTSR 0x0000000c
107
108#define CLASSD_THR 0x00000010
109
110#define CLASSD_IER 0x00000014
111
112#define CLASSD_IDR 0x00000018
113
114#define CLASSD_IMR 0x0000001c
115
116#define CLASSD_ISR 0x00000020
117
118#define CLASSD_WPMR 0x000000e4
119
120#endif