[PATCH 04/14] ASoC: Add sun8i analog codec driver

Mylène Josserand mylene.josserand at free-electrons.com
Tue Oct 4 02:46:17 PDT 2016


Add the analog part of the sun8i (A33) codec driver. This driver
implements all the analog part of the codec using PRCM registers.

The read/write regmap functions must be handled in a custom way as
the PRCM register behaves as "mailbox" register.

Signed-off-by: Mylène Josserand <mylene.josserand at free-electrons.com>
---
 sound/soc/sunxi/Kconfig              |   7 +
 sound/soc/sunxi/Makefile             |   1 +
 sound/soc/sunxi/sun8i-codec-analog.c | 304 +++++++++++++++++++++++++++++++++++
 3 files changed, 312 insertions(+)
 create mode 100644 sound/soc/sunxi/sun8i-codec-analog.c

diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig
index dd23682..7aee95a 100644
--- a/sound/soc/sunxi/Kconfig
+++ b/sound/soc/sunxi/Kconfig
@@ -26,4 +26,11 @@ config SND_SUN4I_SPDIF
 	help
 	  Say Y or M to add support for the S/PDIF audio block in the Allwinner
 	  A10 and affiliated SoCs.
+
+config SND_SUN8I_CODEC_ANALOG
+	tristate "Allwinner SUN8I analog codec"
+	select REGMAP_MMIO
+        help
+	  Say Y or M if you want to add sun8i analog audiocodec support
+
 endmenu
diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile
index 604c7b84..241c0df 100644
--- a/sound/soc/sunxi/Makefile
+++ b/sound/soc/sunxi/Makefile
@@ -1,3 +1,4 @@
 obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o
 obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o
 obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o
+obj-$(CONFIG_SND_SUN8I_CODEC_ANALOG) += sun8i-codec-analog.o
diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c
new file mode 100644
index 0000000..be3d540
--- /dev/null
+++ b/sound/soc/sunxi/sun8i-codec-analog.c
@@ -0,0 +1,304 @@
+/*
+ * This driver supports the analog controls for the internal codec
+ * found in Allwinner's A31s, A33 and A23 SoCs.
+ *
+ * Copyright 2016 Chen-Yu Tsai <wens at csie.org>
+ * Copyright 2016 Mylène Josserand <mylene.josserand at free-electrons.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/module.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+
+/* Codec analog control register offsets and bit fields */
+#define SUN8I_ADDA_HP_VOLC				0x00
+#define SUN8I_ADDA_HP_VOLC_PA_CLK_GATE			7
+#define SUN8I_ADDA_HP_VOLC_HP_VOL			0
+#define SUN8I_ADDA_LOMIXSC				0x01
+#define SUN8I_ADDA_LOMIXSC_MIC1			6
+#define SUN8I_ADDA_LOMIXSC_MIC2			5
+#define SUN8I_ADDA_LOMIXSC_PHONE			4
+#define SUN8I_ADDA_LOMIXSC_PHONEN			3
+#define SUN8I_ADDA_LOMIXSC_LINEINL			2
+#define SUN8I_ADDA_LOMIXSC_DACL			1
+#define SUN8I_ADDA_LOMIXSC_DACR			0
+#define SUN8I_ADDA_ROMIXSC				0x02
+#define SUN8I_ADDA_ROMIXSC_MIC1			6
+#define SUN8I_ADDA_ROMIXSC_MIC2			5
+#define SUN8I_ADDA_ROMIXSC_PHONE			4
+#define SUN8I_ADDA_ROMIXSC_PHONEP			3
+#define SUN8I_ADDA_ROMIXSC_LINEINR			2
+#define SUN8I_ADDA_ROMIXSC_DACR			1
+#define SUN8I_ADDA_ROMIXSC_DACL			0
+#define SUN8I_ADDA_DAC_PA_SRC				0x03
+#define SUN8I_ADDA_DAC_PA_SRC_DACAREN			7
+#define SUN8I_ADDA_DAC_PA_SRC_DACALEN			6
+#define SUN8I_ADDA_DAC_PA_SRC_RMIXEN			5
+#define SUN8I_ADDA_DAC_PA_SRC_LMIXEN			4
+#define SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE		3
+#define SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE		2
+#define SUN8I_ADDA_DAC_PA_SRC_RHPIS			1
+#define SUN8I_ADDA_DAC_PA_SRC_LHPIS			0
+#define SUN8I_ADDA_PHONEIN_GCTRL			0x04
+#define SUN8I_ADDA_PHONEIN_GCTRL_PHONEPG		4
+#define SUN8I_ADDA_PHONEIN_GCTRL_PHONENG		0
+#define SUN8I_ADDA_LINEIN_GCTRL			0x05
+#define SUN8I_ADDA_LINEIN_GCTRL_LINEING		4
+#define SUN8I_ADDA_LINEIN_GCTRL_PHONEG			0
+#define SUN8I_ADDA_MICIN_GCTRL				0x06
+#define SUN8I_ADDA_MICIN_GCTRL_MIC1G			4
+#define SUN8I_ADDA_MICIN_GCTRL_MIC2G			0
+#define SUN8I_ADDA_PAEN_HP_CTRL			0x07
+#define SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN			7
+#define SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC		5
+#define SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN		4
+#define SUN8I_ADDA_PAEN_HP_CTRL_PA_ANTI_POP_CTRL	2
+#define SUN8I_ADDA_PAEN_HP_CTRL_LTRNMUTE		1
+#define SUN8I_ADDA_PAEN_HP_CTRL_RTLNMUTE		0
+#define SUN8I_ADDA_PHONEOUT_CTRL			0x08
+#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTG		5
+#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTEN		4
+#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTS3		3
+#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTS2		2
+#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTS1		1
+#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTS0		0
+#define SUN8I_ADDA_PHONE_GAIN_CTRL			0x09
+#define SUN8I_ADDA_PHONE_GAIN_CTRL_PHONEPREG		0
+#define SUN8I_ADDA_MIC2G_CTRL				0x0a
+#define SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN		7
+#define SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST		4
+#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL			0x0b
+#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN	7
+#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN	6
+#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIAS_MODE	5
+#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN	3
+#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST	0
+#define SUN8I_ADDA_PA_ANTI_POP_CTRL			0x0e
+#define SUN8I_ADDA_ADC_AP_EN				0x0f
+
+/* Analog control register access bits */
+#define ADDA_PR				0x0 /* PRCM base + 0x1c0 */
+#define ADDA_PR_RESET				BIT(28)
+#define ADDA_PR_WRITE				BIT(24)
+#define ADDA_PR_ADDR_SHIFT			16
+#define ADDA_PR_ADDR_MASK			GENMASK(4, 0)
+#define ADDA_PR_DATA_IN_SHIFT			8
+#define ADDA_PR_DATA_IN_MASK			GENMASK(7, 0)
+#define ADDA_PR_DATA_OUT_SHIFT			0
+#define ADDA_PR_DATA_OUT_MASK			GENMASK(7, 0)
+
+/* regmap access bits */
+static int adda_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	void __iomem *base = context;
+	u32 tmp;
+
+	tmp = readl(base);
+
+	/* De-assert reset */
+	writel(tmp | ADDA_PR_RESET, base);
+
+	tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT);
+	tmp |= reg << ADDA_PR_ADDR_SHIFT;
+	writel(tmp, base);
+
+	*val = readl(base) & ADDA_PR_DATA_OUT_MASK;
+
+	return 0;
+}
+
+static int adda_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+	void __iomem *base = context;
+	u32 tmp;
+
+	tmp = readl(base);
+	/* De-assert reset */
+	writel(tmp | ADDA_PR_RESET, base);
+
+	/* Write the address */
+	tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT);
+	tmp |= reg << ADDA_PR_ADDR_SHIFT;
+	writel(tmp, base);
+
+	/* Write the value */
+	tmp &= ~(ADDA_PR_DATA_IN_MASK << ADDA_PR_DATA_IN_SHIFT);
+	tmp |= val << ADDA_PR_DATA_IN_SHIFT;
+	writel(tmp, base);
+
+	/* Indicate that the previous value must be written */
+	writel(readl(base) | ADDA_PR_WRITE, base);
+
+	/* Reset the write bit */
+	writel(readl(base) & ~(ADDA_PR_WRITE), base);
+
+	return 0;
+}
+
+struct regmap_config adda_pr_regmap_cfg = {
+	.name		= "adda-pr",
+	.reg_bits	= 5,
+	.reg_stride	= 1,
+	.val_bits	= 8,
+	.reg_read	= adda_reg_read,
+	.reg_write	= adda_reg_write,
+	.fast_io	= true,
+	.max_register	= 24,
+};
+
+static DECLARE_TLV_DB_SCALE(sun8i_codec_headphone_volume_scale, -6300, 100, 1);
+
+static const struct snd_kcontrol_new sun8i_analog_widgets[] = {
+	SOC_SINGLE_TLV("Headphone Volume", SUN8I_ADDA_HP_VOLC,
+		       SUN8I_ADDA_HP_VOLC_HP_VOL, 0x3F, 0,
+		       sun8i_codec_headphone_volume_scale),
+
+	/* Playback Switch */
+	SOC_DOUBLE("DAC Playback Switch", SUN8I_ADDA_DAC_PA_SRC,
+		   SUN8I_ADDA_DAC_PA_SRC_DACALEN, SUN8I_ADDA_DAC_PA_SRC_DACAREN,
+		   1, 0),
+
+	SOC_DOUBLE("Headphone Playback Switch", SUN8I_ADDA_DAC_PA_SRC,
+		   SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE,
+		   SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE, 1, 0),
+};
+
+/* headphone controls */
+static const char * const sun8i_codec_hp_src_enum_text[] = {
+	"DAC", "Mixer",
+};
+
+static SOC_ENUM_DOUBLE_DECL(sun8i_codec_hp_src_enum,
+			    SUN8I_ADDA_DAC_PA_SRC,
+			    SUN8I_ADDA_DAC_PA_SRC_LHPIS,
+			    SUN8I_ADDA_DAC_PA_SRC_RHPIS,
+			    sun8i_codec_hp_src_enum_text);
+
+static const struct snd_kcontrol_new sun8i_codec_hp_src[] = {
+	SOC_DAPM_ENUM("Headphone Source Playback Route",
+		      sun8i_codec_hp_src_enum),
+};
+
+static const struct snd_kcontrol_new sun8i_codec_mixer_controls[] = {
+	SOC_DAPM_SINGLE("DAC Left Playback Switch",
+			SUN8I_ADDA_LOMIXSC,
+			SUN8I_ADDA_LOMIXSC_DACL, 1, 0),
+	SOC_DAPM_SINGLE("DAC Right Playback Switch",
+			SUN8I_ADDA_ROMIXSC,
+			SUN8I_ADDA_ROMIXSC_DACR, 1, 0),
+	SOC_DAPM_SINGLE("DAC Reversed Left Playback Switch",
+			SUN8I_ADDA_LOMIXSC,
+			SUN8I_ADDA_LOMIXSC_DACR, 1, 0),
+	SOC_DAPM_SINGLE("DAC Reversed Right Playback Switch",
+			SUN8I_ADDA_ROMIXSC,
+			SUN8I_ADDA_ROMIXSC_DACL, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget sun8i_codec_analog_dapm_widgets[] = {
+	/* Mixers */
+	SOC_MIXER_ARRAY("Left Mixer", SUN8I_ADDA_DAC_PA_SRC,
+			SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0,
+			sun8i_codec_mixer_controls),
+	SOC_MIXER_ARRAY("Right Mixer", SUN8I_ADDA_DAC_PA_SRC,
+			SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0,
+			sun8i_codec_mixer_controls),
+
+	/* Headphone output path */
+	SND_SOC_DAPM_MUX("Headphone Source Playback Route",
+			 SND_SOC_NOPM, 0, 0, sun8i_codec_hp_src),
+	SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL,
+			     SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0),
+
+	/* Headphone outputs */
+	SND_SOC_DAPM_OUTPUT("HP"),
+
+};
+
+static const struct snd_soc_dapm_route sun8i_codec_analog_dapm_routes[] = {
+	/* Left Mixer Routes */
+	{ "Left Mixer", "DAC Playback Switch", "Left DAC" },
+	{ "Left Mixer", "DAC Reversed Left Playback Switch", "Right DAC" },
+
+	/* Right Mixer Routes */
+	{ "Right Mixer", "DAC Playback Switch", "Right DAC" },
+	{ "Right Mixer", "DAC Reversed Right Playback Switch", "Left DAC" },
+
+	/* Headphone Routes */
+	{ "Headphone Source Playback Route", "DAC", "Left DAC" },
+	{ "Headphone Source Playback Route", "DAC", "Right DAC" },
+	{ "Headphone Source Playback Route", "Mixer", "Left Mixer" },
+	{ "Headphone Source Playback Route", "Mixer", "Right Mixer" },
+	{ "Headphone Amp", NULL, "Headphone Source Playback Route" },
+	{ "HP", NULL, "Headphone Amp" },
+};
+
+static const struct snd_soc_component_driver sun8i_codec_analog_cmpnt_drv = {
+	.name			= "sun8i-codec-analog",
+	.controls		= sun8i_analog_widgets,
+	.num_controls		= ARRAY_SIZE(sun8i_analog_widgets),
+	.dapm_widgets		= sun8i_codec_analog_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(sun8i_codec_analog_dapm_widgets),
+	.dapm_routes		= sun8i_codec_analog_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(sun8i_codec_analog_dapm_routes),
+};
+
+static const struct of_device_id sun8i_codec_analog_of_match[] = {
+	{ .compatible = "allwinner,sun8i-codec-analog", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match);
+
+static int sun8i_codec_analog_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct regmap *regmap;
+	void __iomem *base;
+
+	/* Get PRCM resources and registers */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(base)) {
+		dev_err(&pdev->dev, "Failed to map PRCM registers\n");
+		return PTR_ERR(base);
+	}
+
+	regmap = devm_regmap_init(&pdev->dev, NULL, base, &adda_pr_regmap_cfg);
+	if (IS_ERR(regmap)) {
+		dev_err(&pdev->dev, "Regmap initialisation failed\n");
+		return PTR_ERR(regmap);
+	}
+
+	return devm_snd_soc_register_component(&pdev->dev,
+					       &sun8i_codec_analog_cmpnt_drv,
+					       NULL, 0);
+}
+
+static struct platform_driver sun8i_codec_analog_driver = {
+	.driver = {
+		.name = "sun8i-codec-analog",
+		.of_match_table = sun8i_codec_analog_of_match,
+	},
+	.probe = sun8i_codec_analog_probe,
+};
+module_platform_driver(sun8i_codec_analog_driver);
+
+MODULE_DESCRIPTION("Allwinner A31s/A33/A23 codec analog controls driver");
+MODULE_AUTHOR("Chen-Yu Tsai <wens at csie.org>");
+MODULE_AUTHOR("Mylène Josserand <mylene.josserand at free-electrons.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sun8i-codec-analog");
-- 
2.9.3




More information about the linux-arm-kernel mailing list