1 From 382d2274cfd8fc22064a33681e45668cfb6f91ad Mon Sep 17 00:00:00 2001
2 From: Lars-Peter Clausen <lars@metafoo.de>
3 Date: Sun, 1 Aug 2010 21:14:09 +0200
4 Subject: [PATCH] ASoC: Add JZ4740 codec driver
6 This patch adds support for the JZ4740 internal codec.
8 Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
9 Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
10 Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
12 sound/soc/codecs/Kconfig | 4 +
13 sound/soc/codecs/Makefile | 2 +
14 sound/soc/codecs/jz4740.c | 511 +++++++++++++++++++++++++++++++++++++++++++++
15 sound/soc/codecs/jz4740.h | 20 ++
16 4 files changed, 537 insertions(+), 0 deletions(-)
17 create mode 100644 sound/soc/codecs/jz4740.c
18 create mode 100644 sound/soc/codecs/jz4740.h
20 --- a/sound/soc/codecs/Kconfig
21 +++ b/sound/soc/codecs/Kconfig
22 @@ -23,6 +23,7 @@ config SND_SOC_ALL_CODECS
23 select SND_SOC_AK4671 if I2C
24 select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
25 select SND_SOC_CS4270 if I2C
26 + select SND_SOC_JZ4740 if SOC_JZ4740
27 select SND_SOC_MAX9877 if I2C
28 select SND_SOC_DA7210 if I2C
29 select SND_SOC_PCM3008
30 @@ -138,6 +139,9 @@ config SND_SOC_CS4270_VD33_ERRATA
31 config SND_SOC_CX20442
34 +config SND_SOC_JZ4740_CODEC
40 --- a/sound/soc/codecs/Makefile
41 +++ b/sound/soc/codecs/Makefile
42 @@ -56,6 +56,7 @@ snd-soc-wm9705-objs := wm9705.o
43 snd-soc-wm9712-objs := wm9712.o
44 snd-soc-wm9713-objs := wm9713.o
45 snd-soc-wm-hubs-objs := wm_hubs.o
46 +snd-soc-jz4740-codec-objs := jz4740.o
49 snd-soc-max9877-objs := max9877.o
50 @@ -78,6 +79,7 @@ obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-
51 obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
52 obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
53 obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
54 +obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
55 obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
56 obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
57 obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
59 +++ b/sound/soc/codecs/jz4740.c
62 + * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
64 + * This program is free software; you can redistribute it and/or modify
65 + * it under the terms of the GNU General Public License version 2 as
66 + * published by the Free Software Foundation.
68 + * You should have received a copy of the GNU General Public License along
69 + * with this program; if not, write to the Free Software Foundation, Inc.,
70 + * 675 Mass Ave, Cambridge, MA 02139, USA.
74 +#include <linux/kernel.h>
75 +#include <linux/module.h>
76 +#include <linux/platform_device.h>
77 +#include <linux/slab.h>
79 +#include <linux/delay.h>
81 +#include <sound/core.h>
82 +#include <sound/pcm.h>
83 +#include <sound/pcm_params.h>
84 +#include <sound/initval.h>
85 +#include <sound/soc-dapm.h>
86 +#include <sound/soc.h>
88 +#define JZ4740_REG_CODEC_1 0x0
89 +#define JZ4740_REG_CODEC_2 0x1
91 +#define JZ4740_CODEC_1_LINE_ENABLE BIT(29)
92 +#define JZ4740_CODEC_1_MIC_ENABLE BIT(28)
93 +#define JZ4740_CODEC_1_SW1_ENABLE BIT(27)
94 +#define JZ4740_CODEC_1_ADC_ENABLE BIT(26)
95 +#define JZ4740_CODEC_1_SW2_ENABLE BIT(25)
96 +#define JZ4740_CODEC_1_DAC_ENABLE BIT(24)
97 +#define JZ4740_CODEC_1_VREF_DISABLE BIT(20)
98 +#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19)
99 +#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18)
100 +#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17)
101 +#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16)
102 +#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14)
103 +#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13)
104 +#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12)
105 +#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10))
106 +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9)
107 +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8)
108 +#define JZ4740_CODEC_1_SUSPEND BIT(1)
109 +#define JZ4740_CODEC_1_RESET BIT(0)
111 +#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29
112 +#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28
113 +#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27
114 +#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26
115 +#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25
116 +#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24
117 +#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14
118 +#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8
120 +#define JZ4740_CODEC_2_INPUT_VOLUME_MASK 0x1f0000
121 +#define JZ4740_CODEC_2_SAMPLE_RATE_MASK 0x000f00
122 +#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK 0x000030
123 +#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK 0x000003
125 +#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET 16
126 +#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET 8
127 +#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET 4
128 +#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET 0
130 +static const uint32_t jz4740_codec_regs[] = {
131 + 0x021b2302, 0x00170803,
134 +struct jz4740_codec {
135 + void __iomem *base;
136 + struct resource *mem;
138 + uint32_t reg_cache[2];
139 + struct snd_soc_codec codec;
142 +static inline struct jz4740_codec *codec_to_jz4740(struct snd_soc_codec *codec)
144 + return container_of(codec, struct jz4740_codec, codec);
147 +static unsigned int jz4740_codec_read(struct snd_soc_codec *codec,
150 + struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec);
151 + return readl(jz4740_codec->base + (reg << 2));
154 +static int jz4740_codec_write(struct snd_soc_codec *codec, unsigned int reg,
157 + struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec);
159 + jz4740_codec->reg_cache[reg] = val;
160 + writel(val, jz4740_codec->base + (reg << 2));
165 +static const struct snd_kcontrol_new jz4740_codec_controls[] = {
166 + SOC_SINGLE("Master Playback Volume", JZ4740_REG_CODEC_2,
167 + JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0),
168 + SOC_SINGLE("Master Capture Volume", JZ4740_REG_CODEC_2,
169 + JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0),
170 + SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1,
171 + JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1),
172 + SOC_SINGLE("Mic Capture Volume", JZ4740_REG_CODEC_2,
173 + JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0),
176 +static const struct snd_kcontrol_new jz4740_codec_output_controls[] = {
177 + SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1,
178 + JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0),
179 + SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1,
180 + JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0),
183 +static const struct snd_kcontrol_new jz4740_codec_input_controls[] = {
184 + SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1,
185 + JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0),
186 + SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1,
187 + JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0),
190 +static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = {
191 + SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1,
192 + JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0),
193 + SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1,
194 + JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0),
196 + SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1,
197 + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1,
198 + jz4740_codec_output_controls,
199 + ARRAY_SIZE(jz4740_codec_output_controls)),
201 + SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0,
202 + jz4740_codec_input_controls,
203 + ARRAY_SIZE(jz4740_codec_input_controls)),
204 + SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0),
206 + SND_SOC_DAPM_OUTPUT("LOUT"),
207 + SND_SOC_DAPM_OUTPUT("ROUT"),
209 + SND_SOC_DAPM_INPUT("MIC"),
210 + SND_SOC_DAPM_INPUT("LIN"),
211 + SND_SOC_DAPM_INPUT("RIN"),
214 +static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = {
215 + {"Line Input", NULL, "LIN"},
216 + {"Line Input", NULL, "RIN"},
218 + {"Input Mixer", "Line Capture Switch", "Line Input"},
219 + {"Input Mixer", "Mic Capture Switch", "MIC"},
221 + {"ADC", NULL, "Input Mixer"},
223 + {"Output Mixer", "Bypass Switch", "Input Mixer"},
224 + {"Output Mixer", "DAC Switch", "DAC"},
226 + {"LOUT", NULL, "Output Mixer"},
227 + {"ROUT", NULL, "Output Mixer"},
230 +static int jz4740_codec_hw_params(struct snd_pcm_substream *substream,
231 + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
234 + struct snd_soc_pcm_runtime *rtd = substream->private_data;
235 + struct snd_soc_device *socdev = rtd->socdev;
236 + struct snd_soc_codec *codec = socdev->card->codec;
238 + switch (params_rate(params)) {
270 + val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET;
272 + snd_soc_update_bits(codec, JZ4740_REG_CODEC_2,
273 + JZ4740_CODEC_2_SAMPLE_RATE_MASK, val);
278 +static struct snd_soc_dai_ops jz4740_codec_dai_ops = {
279 + .hw_params = jz4740_codec_hw_params,
282 +struct snd_soc_dai jz4740_codec_dai = {
285 + .stream_name = "Playback",
288 + .rates = SNDRV_PCM_RATE_8000_48000,
289 + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
292 + .stream_name = "Capture",
295 + .rates = SNDRV_PCM_RATE_8000_48000,
296 + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
298 + .ops = &jz4740_codec_dai_ops,
299 + .symmetric_rates = 1,
301 +EXPORT_SYMBOL_GPL(jz4740_codec_dai);
303 +static void jz4740_codec_wakeup(struct snd_soc_codec *codec)
306 + uint32_t *cache = codec->reg_cache;
308 + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
309 + JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET);
312 + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
313 + JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0);
315 + for (i = 0; i < ARRAY_SIZE(jz4740_codec_regs); ++i)
316 + jz4740_codec_write(codec, i, cache[i]);
319 +static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec,
320 + enum snd_soc_bias_level level)
323 + unsigned int value;
326 + case SND_SOC_BIAS_ON:
328 + case SND_SOC_BIAS_PREPARE:
329 + mask = JZ4740_CODEC_1_VREF_DISABLE |
330 + JZ4740_CODEC_1_VREF_AMP_DISABLE |
331 + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
334 + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
336 + case SND_SOC_BIAS_STANDBY:
337 + /* The only way to clear the suspend flag is to reset the codec */
338 + if (codec->bias_level == SND_SOC_BIAS_OFF)
339 + jz4740_codec_wakeup(codec);
341 + mask = JZ4740_CODEC_1_VREF_DISABLE |
342 + JZ4740_CODEC_1_VREF_AMP_DISABLE |
343 + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
344 + value = JZ4740_CODEC_1_VREF_DISABLE |
345 + JZ4740_CODEC_1_VREF_AMP_DISABLE |
346 + JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
348 + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
350 + case SND_SOC_BIAS_OFF:
351 + mask = JZ4740_CODEC_1_SUSPEND;
352 + value = JZ4740_CODEC_1_SUSPEND;
354 + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
360 + codec->bias_level = level;
365 +static struct snd_soc_codec *jz4740_codec_codec;
367 +static int jz4740_codec_dev_probe(struct platform_device *pdev)
370 + struct snd_soc_device *socdev = platform_get_drvdata(pdev);
371 + struct snd_soc_codec *codec = jz4740_codec_codec;
375 + socdev->card->codec = codec;
377 + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
379 + dev_err(&pdev->dev, "Failed to create pcms: %d\n", ret);
383 + snd_soc_add_controls(codec, jz4740_codec_controls,
384 + ARRAY_SIZE(jz4740_codec_controls));
386 + snd_soc_dapm_new_controls(codec, jz4740_codec_dapm_widgets,
387 + ARRAY_SIZE(jz4740_codec_dapm_widgets));
389 + snd_soc_dapm_add_routes(codec, jz4740_codec_dapm_routes,
390 + ARRAY_SIZE(jz4740_codec_dapm_routes));
392 + snd_soc_dapm_new_widgets(codec);
397 +static int jz4740_codec_dev_remove(struct platform_device *pdev)
399 + struct snd_soc_device *socdev = platform_get_drvdata(pdev);
401 + snd_soc_free_pcms(socdev);
402 + snd_soc_dapm_free(socdev);
407 +#ifdef CONFIG_PM_SLEEP
409 +static int jz4740_codec_suspend(struct platform_device *pdev, pm_message_t state)
411 + struct snd_soc_device *socdev = platform_get_drvdata(pdev);
412 + struct snd_soc_codec *codec = socdev->card->codec;
414 + return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF);
417 +static int jz4740_codec_resume(struct platform_device *pdev)
419 + struct snd_soc_device *socdev = platform_get_drvdata(pdev);
420 + struct snd_soc_codec *codec = socdev->card->codec;
422 + return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
426 +#define jz4740_codec_suspend NULL
427 +#define jz4740_codec_resume NULL
430 +struct snd_soc_codec_device soc_codec_dev_jz4740_codec = {
431 + .probe = jz4740_codec_dev_probe,
432 + .remove = jz4740_codec_dev_remove,
433 + .suspend = jz4740_codec_suspend,
434 + .resume = jz4740_codec_resume,
436 +EXPORT_SYMBOL_GPL(soc_codec_dev_jz4740_codec);
438 +static int __devinit jz4740_codec_probe(struct platform_device *pdev)
441 + struct jz4740_codec *jz4740_codec;
442 + struct snd_soc_codec *codec;
443 + struct resource *mem;
445 + jz4740_codec = kzalloc(sizeof(*jz4740_codec), GFP_KERNEL);
449 + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
451 + dev_err(&pdev->dev, "Failed to get mmio memory resource\n");
453 + goto err_free_codec;
456 + mem = request_mem_region(mem->start, resource_size(mem), pdev->name);
458 + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
460 + goto err_free_codec;
463 + jz4740_codec->base = ioremap(mem->start, resource_size(mem));
464 + if (!jz4740_codec->base) {
465 + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
467 + goto err_release_mem_region;
469 + jz4740_codec->mem = mem;
471 + jz4740_codec_dai.dev = &pdev->dev;
473 + codec = &jz4740_codec->codec;
475 + codec->dev = &pdev->dev;
476 + codec->name = "jz4740";
477 + codec->owner = THIS_MODULE;
479 + codec->read = jz4740_codec_read;
480 + codec->write = jz4740_codec_write;
481 + codec->set_bias_level = jz4740_codec_set_bias_level;
482 + codec->bias_level = SND_SOC_BIAS_OFF;
484 + codec->dai = &jz4740_codec_dai;
485 + codec->num_dai = 1;
487 + codec->reg_cache = jz4740_codec->reg_cache;
488 + codec->reg_cache_size = 2;
489 + memcpy(codec->reg_cache, jz4740_codec_regs, sizeof(jz4740_codec_regs));
491 + mutex_init(&codec->mutex);
492 + INIT_LIST_HEAD(&codec->dapm_widgets);
493 + INIT_LIST_HEAD(&codec->dapm_paths);
495 + jz4740_codec_codec = codec;
497 + snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
498 + JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE);
500 + platform_set_drvdata(pdev, jz4740_codec);
502 + ret = snd_soc_register_codec(codec);
504 + dev_err(&pdev->dev, "Failed to register codec\n");
508 + ret = snd_soc_register_dai(&jz4740_codec_dai);
510 + dev_err(&pdev->dev, "Failed to register codec dai\n");
511 + goto err_unregister_codec;
514 + jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
518 +err_unregister_codec:
519 + snd_soc_unregister_codec(codec);
521 + iounmap(jz4740_codec->base);
522 +err_release_mem_region:
523 + release_mem_region(mem->start, resource_size(mem));
525 + kfree(jz4740_codec);
530 +static int __devexit jz4740_codec_remove(struct platform_device *pdev)
532 + struct jz4740_codec *jz4740_codec = platform_get_drvdata(pdev);
533 + struct resource *mem = jz4740_codec->mem;
535 + snd_soc_unregister_dai(&jz4740_codec_dai);
536 + snd_soc_unregister_codec(&jz4740_codec->codec);
538 + iounmap(jz4740_codec->base);
539 + release_mem_region(mem->start, resource_size(mem));
541 + platform_set_drvdata(pdev, NULL);
542 + kfree(jz4740_codec);
547 +static struct platform_driver jz4740_codec_driver = {
548 + .probe = jz4740_codec_probe,
549 + .remove = __devexit_p(jz4740_codec_remove),
551 + .name = "jz4740-codec",
552 + .owner = THIS_MODULE,
556 +static int __init jz4740_codec_init(void)
558 + return platform_driver_register(&jz4740_codec_driver);
560 +module_init(jz4740_codec_init);
562 +static void __exit jz4740_codec_exit(void)
564 + platform_driver_unregister(&jz4740_codec_driver);
566 +module_exit(jz4740_codec_exit);
568 +MODULE_DESCRIPTION("JZ4740 SoC internal codec driver");
569 +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
570 +MODULE_LICENSE("GPL v2");
571 +MODULE_ALIAS("platform:jz4740-codec");
573 +++ b/sound/soc/codecs/jz4740.h
576 + * Copyright (C) 2009, Lars-Peter Clausen <lars@metafoo.de>
578 + * This program is free software; you can redistribute it and/or modify
579 + * it under the terms of the GNU General Public License version 2 as
580 + * published by the Free Software Foundation.
582 + * You should have received a copy of the GNU General Public License along
583 + * with this program; if not, write to the Free Software Foundation, Inc.,
584 + * 675 Mass Ave, Cambridge, MA 02139, USA.
588 +#ifndef __SND_SOC_CODECS_JZ4740_CODEC_H__
589 +#define __SND_SOC_CODECS_JZ4740_CODEC_H__
591 +extern struct snd_soc_dai jz4740_codec_dai;
592 +extern struct snd_soc_codec_device soc_codec_dev_jz4740_codec;