On Tue, Oct 4, 2016 at 6:21 PM, Code Kipper codekipper@gmail.com wrote:
On 4 October 2016 at 11:46, Mylène Josserand mylene.josserand@free-electrons.com wrote:
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@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@csie.org
- Copyright 2016 Mylène Josserand mylene.josserand@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");
Does the A31s have the prcm for the codec?, as I was under the impression that it was the same as the A31 and I can't seem to find a datasheet for that SoC. You could also add H3 and A64 here.
Yes it does, which is why I made this driver.
You can find the datasheet for the A31s in Allwinner's Github repo:
https://github.com/allwinner-zh/documents
ChenYu
CK
+MODULE_AUTHOR("Chen-Yu Tsai wens@csie.org"); +MODULE_AUTHOR("Mylène Josserand mylene.josserand@free-electrons.com"); +MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sun8i-codec-analog");
2.9.3
linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel