[Letux-kernel] [RFC v2 1/5] ASoC: codecs: Add jz4780-codec driver

H. Nikolaus Schaller hns at goldelico.com
Sun May 2 11:16:18 CEST 2021


The jz4780 SoC has a built-in audio codec.

This driver is based on the old 3.18 kernel by Paul Burton.

I have modified the code to compile on v5.12.

FIXME:
* use REGCACHE_FLAT but that needs volatile/readable defined
* remove, suspend
* provide YAML
* add more controls for additional codec features

Signed-off-by: Paul Burton <paul.burton at imgtec.com>
Co-Authored-by: Paul Burton <paul.burton at imgtec.com>
Signed-off-by: H. Nikolaus Schaller <hns at goldelico.com>
---
 sound/soc/codecs/Kconfig  |  13 +
 sound/soc/codecs/Makefile |   2 +
 sound/soc/codecs/jz4780.c | 635 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 650 insertions(+)
 create mode 100644 sound/soc/codecs/jz4780.c

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index cf5e6074ff1c0..043450db8a3b4 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -106,6 +106,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_JZ4725B_CODEC
 	imply SND_SOC_JZ4760_CODEC
 	imply SND_SOC_JZ4770_CODEC
+	imply SND_SOC_JZ4780_CODEC
 	imply SND_SOC_LM4857
 	imply SND_SOC_LM49453
 	imply SND_SOC_LOCHNAGAR_SC
@@ -760,6 +761,18 @@ config SND_SOC_JZ4770_CODEC
 	  This driver can also be built as a module. If so, the module
 	  will be called snd-soc-jz4770-codec.
 
+config SND_SOC_JZ4780_CODEC
+	depends on MACH_INGENIC || COMPILE_TEST
+	depends on OF
+	select REGMAP
+	tristate "Ingenic JZ4780 internal CODEC"
+	help
+	  Enable support for the internal CODEC found in the JZ4780 SoC
+	  from Ingenic.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called snd-soc-jz4780-codec.
+
 config SND_SOC_L3
 	tristate
 
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index edcbcf5633c46..9aa99bf7b55a9 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -108,6 +108,7 @@ snd-soc-jz4740-codec-objs := jz4740.o
 snd-soc-jz4725b-codec-objs := jz4725b.o
 snd-soc-jz4760-codec-objs := jz4760.o
 snd-soc-jz4770-codec-objs := jz4770.o
+snd-soc-jz4780-codec-objs := jz4780.o
 snd-soc-l3-objs := l3.o
 snd-soc-lm4857-objs := lm4857.o
 snd-soc-lm49453-objs := lm49453.o
@@ -428,6 +429,7 @@ obj-$(CONFIG_SND_SOC_JZ4740_CODEC)	+= snd-soc-jz4740-codec.o
 obj-$(CONFIG_SND_SOC_JZ4725B_CODEC)	+= snd-soc-jz4725b-codec.o
 obj-$(CONFIG_SND_SOC_JZ4760_CODEC)      += snd-soc-jz4760-codec.o
 obj-$(CONFIG_SND_SOC_JZ4770_CODEC)	+= snd-soc-jz4770-codec.o
+obj-$(CONFIG_SND_SOC_JZ4780_CODEC)	+= snd-soc-jz4780-codec.o
 obj-$(CONFIG_SND_SOC_L3)	+= snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_LM4857)	+= snd-soc-lm4857.o
 obj-$(CONFIG_SND_SOC_LM49453)   += snd-soc-lm49453.o
diff --git a/sound/soc/codecs/jz4780.c b/sound/soc/codecs/jz4780.c
new file mode 100644
index 0000000000000..c0be29214a94b
--- /dev/null
+++ b/sound/soc/codecs/jz4780.c
@@ -0,0 +1,635 @@
+/*
+ * Ingenic JZ4780 ASoC codec driver
+ *
+ * Copyright (c) 2013 Imagination Technologies
+ * Author: Paul Burton <paul.burton at imgtec.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define	REG_SR			0x00
+#define	REG_SR2			0x01
+#define	REG_MR			0x07
+#define	REG_AICR_DAC		0x08
+#define	REG_AICR_ADC		0x09
+#define	REG_CR_LO		0x0B
+#define	REG_CR_HP		0x0D
+#define	REG_CR_DMIC		0x10
+#define	REG_CR_MIC1		0x11
+#define	REG_CR_MIC2		0x12
+#define	REG_CR_LI1		0x13
+#define	REG_CR_LI2		0x14
+#define	REG_CR_DAC		0x17
+#define	REG_CR_ADC		0x18
+#define	REG_CR_MIX		0x19
+#define	REG_DR_MIX		0x1A
+#define	REG_CR_VIC		0x1B
+#define	REG_CR_CK		0x1C
+#define	REG_FCR_DAC		0x1D
+#define	REG_FCR_ADC		0x20
+#define	REG_CR_TIMER_MSB	0x21
+#define	REG_CR_TIMER_LSB	0x22
+#define	REG_ICR			0x23
+#define	REG_IMR			0x24
+#define	REG_IFR			0x25
+#define	REG_IMR2		0x26
+#define	REG_IFR2		0x27
+#define	REG_GCR_HPL		0x28
+#define	REG_GCR_HPR		0x29
+#define	REG_GCR_LIBYL		0x2A
+#define	REG_GCR_LIBYR		0x2B
+#define	REG_GCR_DACL		0x2C
+#define	REG_GCR_DACR		0x2D
+#define	REG_GCR_MIC1		0x2E
+#define	REG_GCR_MIC2		0x2F
+#define	REG_GCR_ADCL		0x30
+#define	REG_GCR_ADCR		0x31
+#define	REG_GCR_MIXDACL		0x34
+#define	REG_GCR_MIXDACR		0x35
+#define	REG_GCR_MIXADCL		0x36
+#define	REG_GCR_MIXADCR		0x37
+#define	REG_CR_ADC_AGC		0x3A
+#define	REG_DR_ADC_AGC		0x3B
+
+#define	REG_DMIXER		0x100
+#define	REG_DMIX_0		REG_DMIXER | 0x0
+#define	REG_DMIX_1		REG_DMIXER | 0x1
+#define	REG_DMIX_2		REG_DMIXER | 0x2
+#define	REG_DMIX_3		REG_DMIXER | 0x3
+
+#define REG_RGADW_OFF				0
+#define REG_RGDATA_OFF				4
+#define REG_RGADW_RGADDR_SHIFT			8
+#define REG_RGADW_RGADDR_MASK			0x7F
+#define REG_RGADW_RGDIN_MASK			0xFF
+#define REG_RGADW_RGWR				BIT(16)
+#define REG_RGADW_ICRST				BIT(31)
+
+#define REG_CR_MIC1_MICSTEREO_MASK		BIT(7)
+#define REG_CR_MIC1_MICSTEREO_MONO		0
+#define REG_CR_MIC1_MICSTEREO_STEREO		1
+#define REG_CR_MIC1_MIC1_SEL_MASK		BIT(1)
+#define REG_CR_MIC1_MIC1_SEL_AIP2		1
+
+#define REG_CR_ADC_ADC_LEFT_ONLY_MASK		BIT(5)
+#define REG_CR_ADC_ADC_LEFT_ONLY_RCH_ACTIVE	0
+#define REG_CR_ADC_ADC_LEFT_ONLY_RCH_INACTIVE	1
+#define REG_CR_ADC_ADC_IN_SEL_MASK		0x3
+#define REG_CR_ADC_ADC_IN_SEL_AIN1		0
+
+#define REG_CR_DAC_DAC_MUTE_MASK		BIT(7)
+#define REG_CR_DAC_DAC_MUTE_SHIFT		7
+#define REG_CR_DAC_DAC_MUTE_INACTIVE		0
+#define REG_CR_DAC_DAC_MUTE_SOFT_MUTE		1
+
+#define REG_AICR_DAC_DAC_ADWL_MASK		0xC0
+#define REG_AICR_DAC_DAC_ADWL_SHIFT		6
+#define REG_AICR_DAC_AUDIOIF_MASK		0x3
+#define REG_AICR_DAC_AUDIOIF_I2S		0x3
+
+#define REG_AICR_ADC_ADC_ADWL_MASK		0xC0
+#define REG_AICR_ADC_ADC_ADWL_SHIFT		6
+#define REG_AICR_ADC_AUDIOIF_MASK		0x3
+#define REG_AICR_ADC_AUDIOIF_I2S		0x3
+
+#define REG_FCR_DAC_MASK			0xF
+
+#define REG_CR_VIC_SB_MASK			BIT(0)
+#define REG_CR_VIC_SB_NORMAL_MODE		0
+#define REG_CR_VIC_SB_POWERDOWN_MODE		1
+#define REG_CR_VIC_SB_SLEEP_MASK		BIT(1)
+#define REG_CR_VIC_SB_SLEEP_NORMAL_MODE		0
+#define REG_CR_VIC_SB_SLEEP_SLEEP_MODE		0x2
+
+#define REG_CR_MIX_MIX_EN_MASK			BIT(7)
+#define REG_CR_MIX_MIX_EN_OFF			0
+#define REG_CR_MIX_MIX_EN_ENABLED		1
+#define REG_CR_MIX_MIX_LOAD_MASK		BIT(6)
+#define REG_CR_MIX_MIX_LOAD_READ		0
+#define REG_CR_MIX_MIX_LOAD_WRITE		0x40
+#define REG_CR_MIX_DAC_MIX_MASK			0x3
+
+
+static struct reg_default jz4780_codec_reg_defaults[] = {
+	{ REG_AICR_DAC,		0xd3 },
+	{ REG_AICR_ADC,		0xd3 },
+	{ REG_CR_LO,		0x90 },
+	{ REG_CR_HP,		0x90 },
+	{ REG_CR_MIC1,		0xb0 },
+	{ REG_CR_MIC2,		0x30 },
+	{ REG_CR_LI1,		0x10 },
+	{ REG_CR_LI2,		0x10 },
+	{ REG_CR_DAC,		0x90 },
+	{ REG_CR_ADC,		0x90 },
+	{ REG_CR_VIC,		0x03 },
+	{ REG_IMR,		0xff },
+	{ REG_IMR2,		0xff },
+	{ REG_GCR_HPL,		0x06 },
+	{ REG_GCR_HPR,		0x06 },
+	{ REG_GCR_LIBYL,	0x06 },
+	{ REG_GCR_LIBYR,	0x06 },
+};
+
+struct jz4780_codec {
+	void __iomem *base;
+	struct device *dev;
+	struct clk *clk;
+	struct regmap *regmap;
+};
+
+static int jz4780_codec_io_wait(struct jz4780_codec *codec)
+{
+	u32 reg;
+
+	return readl_poll_timeout(codec->base + REG_RGADW_OFF, reg,
+				  !(reg & REG_RGADW_RGWR),
+				  1000, 1 * USEC_PER_SEC);
+}
+
+static int jz4780_codec_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	struct jz4780_codec *jzc = context;
+	int ret;
+
+	clk_enable(jzc->clk);
+
+	ret = jz4780_codec_io_wait(jzc);
+	if (ret)
+		goto out;
+
+	writel((reg & REG_RGADW_RGADDR_MASK) << REG_RGADW_RGADDR_SHIFT,
+		jzc->base + REG_RGADW_OFF);
+	*val = readl(jzc->base + REG_RGDATA_OFF) & REG_RGADW_RGDIN_MASK;
+
+out:
+	clk_disable(jzc->clk);
+	return ret;
+}
+
+static int jz4780_codec_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+	struct jz4780_codec *jzc = context;
+	int ret;
+
+	clk_enable(jzc->clk);
+
+	ret = jz4780_codec_io_wait(jzc);
+	if (ret)
+		goto out;
+
+	writel(REG_RGADW_RGWR | ((reg & REG_RGADW_RGADDR_MASK)
+		<< REG_RGADW_RGADDR_SHIFT)
+		| (val & REG_RGADW_RGDIN_MASK),
+		jzc->base + REG_RGADW_OFF);
+
+	ret = jz4780_codec_io_wait(jzc);
+
+out:
+	clk_disable(jzc->clk);
+	return ret;
+}
+
+static const char * const mic1_input_mux_text[] = {
+	"AIP1", "AIP2",
+};
+
+static const struct soc_enum mic1_input_enum =
+	SOC_ENUM_SINGLE(REG_CR_MIC1, 0, 2, mic1_input_mux_text);
+
+static const struct snd_kcontrol_new mic1_input_mux =
+	SOC_DAPM_ENUM("ADC Capture Route", mic1_input_enum);
+
+static const struct snd_kcontrol_new jz4780_codec_controls[] = {
+	SOC_DOUBLE_R("Master Capture Volume", REG_GCR_ADCL, REG_GCR_ADCR,
+		0, 63, 0),
+
+	SOC_SINGLE("Mic Capture Volume", REG_GCR_MIC1, 0, 7, 0),
+	SOC_SINGLE("Mic Capture Switch", REG_CR_ADC, 7, 1, 1),
+
+	SOC_DOUBLE_R("Master Playback Volume", REG_GCR_DACL, REG_GCR_DACR,
+		0, 31, 1),
+	SOC_SINGLE("Master Playback Switch", REG_CR_DAC, 7, 1, 1),
+
+	SOC_DOUBLE_R("Headphone Playback Volume", REG_GCR_HPL, REG_GCR_HPR,
+		0, 31, 1),
+	SOC_SINGLE("Headphone Playback Switch", REG_CR_HP, 7, 1, 1),
+};
+
+static const struct snd_soc_dapm_widget jz4780_codec_dapm_widgets[] = {
+	SND_SOC_DAPM_SUPPLY("ADC_SUPPLY", REG_CR_ADC, 4, 1, NULL, 0),
+	SND_SOC_DAPM_ADC("ADC", "Capture", REG_AICR_ADC, 4, 1),
+
+	SND_SOC_DAPM_SUPPLY("DAC_SUPPLY", REG_CR_DAC, 4, 1, NULL, 0),
+	SND_SOC_DAPM_DAC("DAC", "Playback", REG_AICR_DAC, 4, 1),
+
+	SND_SOC_DAPM_MUX("ADC Capture Route", REG_CR_MIC1, 4, 1, &mic1_input_mux),
+
+	SND_SOC_DAPM_MIXER("Headphones", REG_CR_HP, 4, 1, NULL, 0),
+
+	SND_SOC_DAPM_MICBIAS("Mic Bias", REG_CR_MIC1, 5, 1),
+
+	SND_SOC_DAPM_INPUT("AIP1"),
+	SND_SOC_DAPM_INPUT("AIP2"),
+	SND_SOC_DAPM_INPUT("AIP3"),
+
+	SND_SOC_DAPM_OUTPUT("SYSCLK"),
+
+	SND_SOC_DAPM_OUTPUT("AOHPL"),
+	SND_SOC_DAPM_OUTPUT("AOHPR"),
+};
+
+static const struct snd_soc_dapm_route jz4780_codec_dapm_routes[] = {
+	{"ADC", NULL, "ADC_SUPPLY"},
+	{"ADC", NULL, "ADC Capture Route"},
+
+	{"ADC Capture Route", "AIP1", "AIP1"},
+	{"ADC Capture Route", "AIP2", "AIP2"},
+
+	{"DAC", NULL, "DAC_SUPPLY"},
+	{"Headphones", NULL, "DAC"},
+	{"AOHPL", NULL, "Headphones"},
+	{"AOHPR", NULL, "Headphones"},
+
+	/* SYSCLK output from the codec to the AIC is required to keep the
+	 * DMA transfer going during playback when all audible outputs have
+	 * been disabled.
+	 */
+	{"SYSCLK", NULL, "DAC" },
+};
+
+static int jz4780_codec_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	uint32_t val;
+	struct jz4780_codec *jzc = snd_soc_component_get_drvdata(dai->component);
+	struct regmap *regmap = jzc->regmap;
+
+	switch (params_rate(params)) {
+	case 8000:
+		val = 0;
+		break;
+	case 11025:
+		val = 1;
+		break;
+	case 12000:
+		val = 2;
+		break;
+	case 16000:
+		val = 3;
+		break;
+	case 22050:
+		val = 4;
+		break;
+	case 24000:
+		val = 5;
+		break;
+	case 32000:
+		val = 6;
+		break;
+	case 44100:
+		val = 7;
+		break;
+	case 48000:
+		val = 8;
+		break;
+	case 88200:
+		val = 9;
+		break;
+	case 96000:
+		val = 10;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+/* REMOVEME if works
+int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned int reg,
+				unsigned int mask, unsigned int value);
+=>
+int regmap_update_bits(struct regmap *map, unsigned int reg,
+		       unsigned int mask, unsigned int val);
+*/
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		regmap_update_bits(regmap, REG_FCR_DAC, REG_FCR_DAC_MASK, val);
+	else
+		regmap_update_bits(regmap, REG_FCR_ADC, REG_FCR_DAC_MASK, val);
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16:
+		val = 0;
+		break;
+	case SNDRV_PCM_FORMAT_S18_3LE:
+		val = 1;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		val = 2;
+		break;
+	case SNDRV_PCM_FORMAT_S24:
+		val = 3;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		regmap_update_bits(regmap, REG_AICR_DAC,
+				    REG_AICR_DAC_DAC_ADWL_MASK,
+				    val << REG_AICR_DAC_DAC_ADWL_SHIFT);
+	} else {
+		regmap_update_bits(regmap, REG_AICR_ADC,
+				    REG_AICR_DAC_DAC_ADWL_MASK,
+				    val << REG_AICR_DAC_DAC_ADWL_SHIFT);
+
+		regmap_update_bits(regmap, REG_CR_MIC1,
+				    REG_CR_MIC1_MICSTEREO_MASK,
+				    REG_CR_MIC1_MICSTEREO_MONO);
+		regmap_update_bits(regmap, REG_CR_ADC,
+				    REG_CR_ADC_ADC_LEFT_ONLY_MASK,
+				    REG_CR_ADC_ADC_LEFT_ONLY_RCH_INACTIVE);
+	}
+
+	return 0;
+}
+
+static int jz4780_codec_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_component *codec = dai->component;
+	struct jz4780_codec *jzc = snd_soc_component_get_drvdata(codec);
+	struct regmap *regmap = jzc->regmap;
+
+	regmap_update_bits(regmap, REG_CR_DAC, REG_CR_DAC_DAC_MUTE_MASK,
+			    (!!mute) << REG_CR_DAC_DAC_MUTE_SHIFT);
+	return 0;
+}
+
+static struct snd_soc_dai_ops jz4780_codec_dai_ops = {
+	.hw_params = jz4780_codec_hw_params,
+//	.digital_mute = jz4780_codec_mute,
+};
+
+static struct snd_soc_dai_driver jz4780_codec_dai = {
+	.name = "jz4780-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S18_3LE |
+			SNDRV_PCM_FMTBIT_S20_3LE |
+			SNDRV_PCM_FMTBIT_S24_LE,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S18_3LE |
+			SNDRV_PCM_FMTBIT_S20_3LE |
+			SNDRV_PCM_FMTBIT_S24_LE,
+	},
+	.ops = &jz4780_codec_dai_ops,
+};
+
+static int jz4780_codec_set_bias_level(struct snd_soc_component *codec,
+	enum snd_soc_bias_level level)
+{
+	struct jz4780_codec *jzc = snd_soc_component_get_drvdata(codec);
+	struct regmap *regmap = jzc->regmap;
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		regmap_update_bits(regmap, REG_CR_VIC, REG_CR_VIC_SB_SLEEP_MASK,
+				    REG_CR_VIC_SB_SLEEP_NORMAL_MODE);
+		/* From manual, max time to power up after sleep */
+		msleep(900);
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		regmap_update_bits(regmap, REG_CR_VIC, REG_CR_VIC_SB_MASK,
+				    REG_CR_VIC_SB_NORMAL_MODE);
+		regmap_update_bits(regmap, REG_CR_VIC, REG_CR_VIC_SB_SLEEP_MASK,
+				    REG_CR_VIC_SB_SLEEP_NORMAL_MODE);
+		/* From manual, max time to power up after sleep */
+		msleep(900);
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		regmap_update_bits(regmap, REG_CR_VIC, REG_CR_VIC_SB_SLEEP_MASK,
+				    REG_CR_VIC_SB_SLEEP_SLEEP_MODE);
+		break;
+	case SND_SOC_BIAS_OFF:
+		regmap_update_bits(regmap, REG_CR_VIC, REG_CR_VIC_SB_MASK,
+				    REG_CR_VIC_SB_POWERDOWN_MODE);
+		break;
+	default:
+		break;
+	}
+
+	codec->dapm.bias_level = level;
+
+	return 0;
+}
+
+static int jz4780_codec_dev_probe(struct snd_soc_component *codec)
+{
+	struct jz4780_codec *jzc = snd_soc_component_get_drvdata(codec);
+	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(codec);
+	struct regmap *regmap = jzc->regmap;
+
+	/* reset */
+	writel(REG_RGADW_ICRST, jzc->base);
+	udelay(2);
+	writel(0, jzc->base);
+
+	/*
+	 * avoid
+	 * [    6.978427] codec 100200a4.audio-codec can not start from non-off bias with idle_bias_off==1
+	*/
+	dapm->idle_bias_off = 0;
+
+	jz4780_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+	/* select I2S */
+
+	regmap_update_bits(regmap, REG_AICR_DAC, REG_AICR_DAC_AUDIOIF_MASK,
+			    REG_AICR_DAC_AUDIOIF_I2S);
+	regmap_update_bits(regmap, REG_AICR_ADC, REG_AICR_ADC_AUDIOIF_MASK,
+			    REG_AICR_ADC_AUDIOIF_I2S);
+
+	/* select AIP2 input */
+	regmap_update_bits(regmap, REG_CR_MIC1, REG_CR_MIC1_MIC1_SEL_MASK,
+			    REG_CR_MIC1_MIC1_SEL_AIP2);
+	regmap_update_bits(regmap, REG_CR_ADC, REG_CR_ADC_ADC_IN_SEL_MASK,
+			    REG_CR_ADC_ADC_IN_SEL_AIN1);
+
+	/* disable mixer */
+	regmap_update_bits(regmap, REG_CR_MIX, REG_CR_MIX_MIX_EN_MASK,
+			    REG_CR_MIX_MIX_EN_OFF);
+
+	return 0;
+}
+
+static int jz4780_codec_dev_remove(struct snd_soc_component *codec)
+{
+	jz4780_codec_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int jz4780_codec_suspend(struct snd_soc_component *codec, pm_message_t state)
+{
+	return jz4780_codec_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+static int jz4780_codec_resume(struct snd_soc_component *codec)
+{
+	return jz4780_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+}
+
+#else
+#define jz4780_codec_suspend NULL
+#define jz4780_codec_resume NULL
+#endif
+
+static struct snd_soc_component_driver soc_codec_dev_jz4780_codec = {
+	.probe = jz4780_codec_dev_probe,
+/* FIXME
+	.remove = jz4780_codec_dev_remove,
+	.suspend = jz4780_codec_suspend,
+*/
+	.resume = jz4780_codec_resume,
+	.set_bias_level = jz4780_codec_set_bias_level,
+/* FIXME
+	.reg_cache_default = jz4780_codec_reg_defaults,
+	.reg_word_size = sizeof(uint8_t),
+	.reg_cache_size	= 0x40,
+*/
+	.controls = jz4780_codec_controls,
+	.num_controls = ARRAY_SIZE(jz4780_codec_controls),
+	.dapm_widgets = jz4780_codec_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(jz4780_codec_dapm_widgets),
+	.dapm_routes = jz4780_codec_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(jz4780_codec_dapm_routes),
+};
+
+static const struct of_device_id jz4780_of_matches[] = {
+	{ .compatible = "ingenic,jz4780-codec", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, jz4780_of_matches);
+
+static struct regmap_config jz4780_codec_regmap_config = {
+	.reg_bits = 7,
+	.val_bits = 8,
+
+	.reg_read = jz4780_codec_reg_read,
+	.reg_write = jz4780_codec_reg_write,
+
+	.max_register = REG_DR_ADC_AGC,
+/* FIXME
+	.volatile_reg = jz4780_codec_volatile,
+	.readable_reg = jz4780_codec_readable,
+	.writeable_reg = jz4780_codec_writeable,
+*/
+	.reg_defaults = jz4780_codec_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(jz4780_codec_reg_defaults),
+/* FIXME: REGCACHE_FLAT needs volatile/readable defined */
+	.cache_type = REGCACHE_NONE,
+};
+
+static int jz4780_codec_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	int ret;
+	struct jz4780_codec *codec;
+
+	codec = devm_kzalloc(dev, sizeof(*codec), GFP_KERNEL);
+	if (!codec)
+		return -ENOMEM;
+
+	codec->dev = dev;
+
+	codec->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(codec->base))
+		return -PTR_ERR(codec->base);
+
+	codec->clk = devm_clk_get(&pdev->dev, "i2s");
+	if (IS_ERR(codec->clk))
+		return PTR_ERR(codec->clk);
+
+	clk_prepare(codec->clk);
+
+	codec->regmap = devm_regmap_init(dev, NULL, codec,
+					&jz4780_codec_regmap_config);
+	if (IS_ERR(codec->regmap))
+		return PTR_ERR(codec->regmap);
+
+	platform_set_drvdata(pdev, codec);
+
+	ret = devm_snd_soc_register_component(dev,
+			&soc_codec_dev_jz4780_codec, &jz4780_codec_dai, 1);
+	if (ret)
+		goto out_err_register;
+
+	return 0;
+
+out_err_register:
+	clk_unprepare(codec->clk);
+
+	return ret;
+}
+
+static int jz4780_codec_remove(struct platform_device *pdev)
+{
+	struct jz4780_codec *codec = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_component(&pdev->dev);
+
+	clk_unprepare(codec->clk);
+
+	platform_set_drvdata(pdev, NULL);
+	kfree(codec);
+
+	return 0;
+}
+
+static struct platform_driver jz4780_codec_driver = {
+	.probe = jz4780_codec_probe,
+	.remove = jz4780_codec_remove,
+	.driver = {
+		.name = "jz4780-codec",
+		.of_match_table = jz4780_of_matches
+	},
+};
+
+module_platform_driver(jz4780_codec_driver);
+
+MODULE_AUTHOR("Paul Burton <paul.burton at imgtec.com>");
+MODULE_DESCRIPTION("Ingenic JZ4780 ASoC codec driver");
+MODULE_LICENSE("GPL");
-- 
2.26.2



More information about the Letux-kernel mailing list