[PATCH 04/14] ASoC: Add sun8i analog codec driver
Code Kipper
codekipper at gmail.com
Tue Oct 4 03:21:18 PDT 2016
On 4 October 2016 at 11:46, Mylène Josserand
<mylene.josserand at 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 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");
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 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
>
>
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
More information about the linux-arm-kernel
mailing list