diff mbox

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

Message ID db0f628732e8b52b36b8204f146573926957e654.1475571575.git.mylene.josserand@free-electrons.com (mailing list archive)
State New, archived
Headers show

Commit Message

Mylene JOSSERAND Oct. 4, 2016, 9:46 a.m. UTC
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

Comments

Code Kipper Oct. 4, 2016, 10:21 a.m. UTC | #1
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.
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
Chen-Yu Tsai Oct. 4, 2016, 10:56 a.m. UTC | #2
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
diff mbox

Patch

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");
+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");