build/patch/kernel/archive/sunxi-6.4/patches.megous/sound-soc-sun8i-codec-Add-support-for-digital-part-of-the-AC100.patch

384 lines
13 KiB
Diff

From 5525888f685ef5ff168bba53d3c0189ddb560530 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20Jirman?= <megi@xff.cz>
Date: Sun, 9 Feb 2020 17:58:59 +0100
Subject: [PATCH 261/469] sound: soc: sun8i-codec: Add support for digital part
of the AC100 codec
X-Power AC100 codec has almost the same digital part as the A64 codec.
The major difference is that registers are spaced differently. A64
has codec register address stride of 4 bytes, while AC100 has addresses
packed together.
We use a custom regmap/regmap_bus to translate register addresses from
the A64 regmap to the correct registers in the AC100's MFD regmap.
We also need to power the codec, so AC100's regulators are also enabled
by the codec driver.
Signed-off-by: Ondrej Jirman <megi@xff.cz>
---
sound/soc/sunxi/Kconfig | 4 +-
sound/soc/sunxi/sun8i-codec.c | 222 ++++++++++++++++++++++++++++++++--
2 files changed, 218 insertions(+), 8 deletions(-)
diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig
index 6a17e49535bd..c61a44bf5198 100644
--- a/sound/soc/sunxi/Kconfig
+++ b/sound/soc/sunxi/Kconfig
@@ -16,9 +16,11 @@ config SND_SUN8I_CODEC
depends on MACH_SUN8I || (ARM64 && ARCH_SUNXI) || COMPILE_TEST
depends on COMMON_CLK
select REGMAP_MMIO
+ select MFD_AC100
help
This option enables the digital part of the internal audio codec for
- Allwinner sun8i SoC (and particularly A33).
+ Allwinner sun8i SoC (and particularly A33). It also supports digital
+ part of X-Powers AC100.
Say Y or M if you want to add sun8i digital audio codec support.
diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c
index 4ea97030d5d2..5a5997c29a92 100644
--- a/sound/soc/sunxi/sun8i-codec.c
+++ b/sound/soc/sunxi/sun8i-codec.c
@@ -18,7 +18,9 @@
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#include <linux/log2.h>
+#include <linux/mfd/ac100.h>
#include <sound/jack.h>
#include <sound/pcm_params.h>
@@ -207,6 +209,8 @@ struct sun8i_codec_quirks {
bool lrck_inversion : 1;
};
+#define AC100_NUM_SUPPLIES 4
+
struct sun8i_codec {
struct regmap *regmap;
struct snd_soc_card *card;
@@ -221,6 +225,9 @@ struct sun8i_codec {
int jack_type;
unsigned int sysclk_rate;
int sysclk_refcnt;
+
+ struct regmap *ac100_regmap;
+ struct regulator_bulk_data supplies[AC100_NUM_SUPPLIES];
};
static struct snd_soc_dai_driver sun8i_codec_dais[];
@@ -624,6 +631,7 @@ static int sun8i_codec_hw_params(struct snd_pcm_substream *substream,
SUN8I_AIF_CLK_CTRL_BCLK_DIV_MASK,
bclk_div << SUN8I_AIF_CLK_CTRL_BCLK_DIV);
+ if (!scodec->ac100_regmap) {
/*
* SYSCLK rate
*
@@ -645,6 +653,7 @@ static int sun8i_codec_hw_params(struct snd_pcm_substream *substream,
if (!aif->open_streams)
scodec->sysclk_refcnt++;
scodec->sysclk_rate = sysclk_rate;
+ }
aif->lrck_div_order = lrck_div_order;
aif->sample_rate = sample_rate;
@@ -663,8 +672,11 @@ static int sun8i_codec_hw_free(struct snd_pcm_substream *substream,
if (aif->open_streams != BIT(substream->stream))
goto done;
- clk_rate_exclusive_put(scodec->clk_module);
- scodec->sysclk_refcnt--;
+ if (!scodec->ac100_regmap) {
+ clk_rate_exclusive_put(scodec->clk_module);
+ scodec->sysclk_refcnt--;
+ }
+
aif->lrck_div_order = 0;
aif->sample_rate = 0;
@@ -937,8 +949,6 @@ static const struct snd_kcontrol_new sun8i_dac_mixer_controls[] = {
static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = {
/* System Clocks */
- SND_SOC_DAPM_CLOCK_SUPPLY("mod"),
-
SND_SOC_DAPM_SUPPLY("AIF1CLK",
SUN8I_SYSCLK_CTL,
SUN8I_SYSCLK_CTL_AIF1CLK_ENA, 0, NULL, 0),
@@ -1099,8 +1109,6 @@ static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = {
static const struct snd_soc_dapm_route sun8i_codec_dapm_routes[] = {
/* Clock Routes */
- { "AIF1CLK", NULL, "mod" },
-
{ "SYSCLK", NULL, "AIF1CLK" },
{ "CLK AIF1", NULL, "AIF1CLK" },
@@ -1270,6 +1278,14 @@ static const struct snd_soc_dapm_route sun8i_codec_legacy_routes[] = {
{ "AIF1 Slot 0 Right", NULL, "DACR" },
};
+static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets_sun8i[] = {
+ SND_SOC_DAPM_CLOCK_SUPPLY("mod"),
+};
+
+static const struct snd_soc_dapm_route sun8i_codec_dapm_routes_sun8i[] = {
+ { "AIF1CLK", NULL, "mod" },
+};
+
static struct snd_soc_jack_pin sun8i_codec_jack_pins[] = {
{
.pin = "Headphone Jack",
@@ -1334,12 +1350,29 @@ static int sun8i_codec_jack_init(struct sun8i_codec *scodec)
return 0;
}
+static int ac100_codec_component_probe(struct snd_soc_component *component);
+
static int sun8i_codec_component_probe(struct snd_soc_component *component)
{
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
struct sun8i_codec *scodec = snd_soc_component_get_drvdata(component);
int ret;
+ if (scodec->ac100_regmap)
+ return ac100_codec_component_probe(component);
+
+ ret = snd_soc_dapm_new_controls(dapm,
+ sun8i_codec_dapm_widgets_sun8i,
+ ARRAY_SIZE(sun8i_codec_dapm_widgets_sun8i));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_add_routes(dapm,
+ sun8i_codec_dapm_routes_sun8i,
+ ARRAY_SIZE(sun8i_codec_dapm_routes_sun8i));
+ if (ret)
+ return ret;
+
scodec->card = component->card;
/* Add widgets for backward compatibility with old device trees. */
@@ -1442,7 +1475,7 @@ static bool sun8i_codec_volatile_reg(struct device *dev, unsigned int reg)
return reg == SUN8I_HMIC_STS;
}
-static const struct regmap_config sun8i_codec_regmap_config = {
+static struct regmap_config sun8i_codec_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
@@ -1555,12 +1588,175 @@ static irqreturn_t sun8i_codec_jack_irq(int irq, void *dev_id)
return IRQ_HANDLED;
}
+/* AC100 Codec Support (digital parts) */
+
+static int sun8i_codec_ac100_regmap_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct sun8i_codec *scodec = context;
+ int ret;
+
+ ret = regmap_read(scodec->ac100_regmap, reg / 4, val);
+ if (ret == 0)
+ pr_err("R%02x => %04x\n", reg / 4, *val);
+
+ return ret;
+}
+
+static int sun8i_codec_ac100_regmap_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct sun8i_codec *scodec = context;
+
+ pr_err("W%02x <= %04x\n", reg / 4, val);
+
+ return regmap_write(scodec->ac100_regmap, reg / 4, val);
+}
+
+static struct regmap_bus sun8i_codec_ac100_regmap_bus = {
+ .reg_write = sun8i_codec_ac100_regmap_write,
+ .reg_read = sun8i_codec_ac100_regmap_read,
+};
+
+static const char *const ac100_supply_names[AC100_NUM_SUPPLIES] = {
+ "LDOIN",
+ "AVCC",
+ "VDDIO1",
+ "VDDIO2",
+};
+
+#define AC100_SYSCLK_CTRL_PLLCLK_ENA_OFF 15
+#define AC100_SYSCLK_CTRL_PLLCLK_ENA_MASK BIT(15)
+#define AC100_SYSCLK_CTRL_PLLCLK_ENA_DISABLED 0
+#define AC100_SYSCLK_CTRL_PLLCLK_ENA_ENABLED BIT(15)
+#define AC100_SYSCLK_CTRL_PLLCLK_SRC_OFF 12
+#define AC100_SYSCLK_CTRL_PLLCLK_SRC_MASK GENMASK(13, 12)
+#define AC100_SYSCLK_CTRL_PLLCLK_SRC_MCLK1 (0x0 << 12)
+#define AC100_SYSCLK_CTRL_PLLCLK_SRC_MCLK2 (0x1 << 12)
+#define AC100_SYSCLK_CTRL_PLLCLK_SRC_BCLK1 (0x2 << 12)
+#define AC100_SYSCLK_CTRL_PLLCLK_SRC_BCLK2 (0x3 << 12)
+#define AC100_SYSCLK_CTRL_I2S1CLK_ENA_OFF 11
+#define AC100_SYSCLK_CTRL_I2S1CLK_ENA_MASK BIT(11)
+#define AC100_SYSCLK_CTRL_I2S1CLK_ENA_DISABLED 0
+#define AC100_SYSCLK_CTRL_I2S1CLK_ENA_ENABLED BIT(11)
+#define AC100_SYSCLK_CTRL_I2S1CLK_SRC_OFF 8
+#define AC100_SYSCLK_CTRL_I2S1CLK_SRC_MASK GENMASK(9, 8)
+#define AC100_SYSCLK_CTRL_I2S1CLK_SRC_MCLK1 (0x0 << 8)
+#define AC100_SYSCLK_CTRL_I2S1CLK_SRC_MCLK2 (0x1 << 8)
+#define AC100_SYSCLK_CTRL_I2S1CLK_SRC_PLL (0x2 << 8)
+#define AC100_SYSCLK_CTRL_I2S2CLK_ENA_OFF 7
+#define AC100_SYSCLK_CTRL_I2S2CLK_ENA_MASK BIT(7)
+#define AC100_SYSCLK_CTRL_I2S2CLK_ENA_DISABLED 0
+#define AC100_SYSCLK_CTRL_I2S2CLK_ENA_ENABLED BIT(7)
+#define AC100_SYSCLK_CTRL_I2S2CLK_SRC_OFF 4
+#define AC100_SYSCLK_CTRL_I2S2CLK_SRC_MASK GENMASK(5, 4)
+#define AC100_SYSCLK_CTRL_I2S2CLK_SRC_MCLK1 (0x0 << 4)
+#define AC100_SYSCLK_CTRL_I2S2CLK_SRC_MCLK2 (0x1 << 4)
+#define AC100_SYSCLK_CTRL_I2S2CLK_SRC_PLL (0x2 << 4)
+#define AC100_SYSCLK_CTRL_SYSCLK_ENA_OFF 3
+#define AC100_SYSCLK_CTRL_SYSCLK_ENA_MASK BIT(3)
+#define AC100_SYSCLK_CTRL_SYSCLK_ENA_DISABLED 0
+#define AC100_SYSCLK_CTRL_SYSCLK_ENA_ENABLED BIT(3)
+#define AC100_SYSCLK_CTRL_SYSCLK_SRC_OFF 0
+#define AC100_SYSCLK_CTRL_SYSCLK_SRC_MASK BIT(0)
+#define AC100_SYSCLK_CTRL_SYSCLK_SRC_I2S1CLK 0
+#define AC100_SYSCLK_CTRL_SYSCLK_SRC_I2S2CLK BIT(0)
+
+
+static int ac100_codec_component_probe(struct snd_soc_component *component)
+{
+ struct sun8i_codec *scodec = snd_soc_component_get_drvdata(component);
+
+ // The system clock(SYSCLK) of AC100 must be 512*fs(fs=48KHz or 44.1KHz)
+
+ // Source clocks from the SoC
+
+ regmap_update_bits(scodec->ac100_regmap, AC100_SYSCLK_CTRL,
+ AC100_SYSCLK_CTRL_I2S1CLK_SRC_MASK,
+ AC100_SYSCLK_CTRL_I2S1CLK_SRC_MCLK1);
+ regmap_update_bits(scodec->ac100_regmap, AC100_SYSCLK_CTRL,
+ AC100_SYSCLK_CTRL_I2S2CLK_SRC_MASK,
+ AC100_SYSCLK_CTRL_I2S2CLK_SRC_MCLK1);
+ regmap_update_bits(scodec->ac100_regmap, AC100_SYSCLK_CTRL,
+ AC100_SYSCLK_CTRL_SYSCLK_SRC_MASK,
+ AC100_SYSCLK_CTRL_SYSCLK_SRC_I2S1CLK);
+
+ /* Program the default sample rate. */
+ sun8i_codec_update_sample_rate(scodec);
+
+ return 0;
+}
+
+static int sun8i_codec_probe_ac100(struct platform_device *pdev)
+{
+ struct ac100_dev *ac100 = dev_get_drvdata(pdev->dev.parent);
+ struct device* dev = &pdev->dev;
+ struct sun8i_codec *scodec;
+ int ret, i;
+
+ scodec = devm_kzalloc(dev, sizeof(*scodec), GFP_KERNEL);
+ if (!scodec)
+ return -ENOMEM;
+
+ scodec->quirks = of_device_get_match_data(&pdev->dev);
+ scodec->ac100_regmap = ac100->regmap;
+
+ platform_set_drvdata(pdev, scodec);
+
+ // caching is done by the MFD regmap
+ sun8i_codec_regmap_config.cache_type = REGCACHE_NONE;
+
+ // we need to create a custom regmap_bus that will map reads/writes to the MFD regmap
+ scodec->regmap = __regmap_lockdep_wrapper(__devm_regmap_init,
+ "ac100-regmap-codec", dev,
+ &sun8i_codec_ac100_regmap_bus, scodec,
+ &sun8i_codec_regmap_config);
+ if (IS_ERR(scodec->regmap)) {
+ dev_err(dev, "Failed to create our regmap\n");
+ return PTR_ERR(scodec->regmap);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(scodec->supplies); i++)
+ scodec->supplies[i].supply = ac100_supply_names[i];
+
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(scodec->supplies),
+ scodec->supplies);
+ if (ret != 0) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(scodec->supplies),
+ scodec->supplies);
+ if (ret != 0) {
+ dev_err(dev, "Failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_component(dev, &sun8i_soc_component,
+ sun8i_codec_dais,
+ ARRAY_SIZE(sun8i_codec_dais));
+ if (ret) {
+ dev_err(dev, "Failed to register codec\n");
+ goto err_disable_reg;
+ }
+
+ return ret;
+
+err_disable_reg:
+ regulator_bulk_disable(ARRAY_SIZE(scodec->supplies),
+ scodec->supplies);
+ return ret;
+}
+
static int sun8i_codec_probe(struct platform_device *pdev)
{
struct sun8i_codec *scodec;
void __iomem *base;
int ret;
+ if (of_device_is_compatible(pdev->dev.of_node, "x-powers,ac100-codec"))
+ return sun8i_codec_probe_ac100(pdev);
+
scodec = devm_kzalloc(&pdev->dev, sizeof(*scodec), GFP_KERNEL);
if (!scodec)
return -ENOMEM;
@@ -1642,6 +1838,14 @@ static int sun8i_codec_probe(struct platform_device *pdev)
static void sun8i_codec_remove(struct platform_device *pdev)
{
+ struct sun8i_codec *scodec = dev_get_drvdata(&pdev->dev);
+
+ if (scodec->ac100_regmap) {
+ regulator_bulk_disable(ARRAY_SIZE(scodec->supplies),
+ scodec->supplies);
+ return;
+ }
+
pm_runtime_disable(&pdev->dev);
if (!pm_runtime_status_suspended(&pdev->dev))
sun8i_codec_runtime_suspend(&pdev->dev);
@@ -1658,9 +1862,13 @@ static const struct sun8i_codec_quirks sun50i_a64_quirks = {
.jack_detection = true,
};
+static const struct sun8i_codec_quirks ac100_quirks = {
+};
+
static const struct of_device_id sun8i_codec_of_match[] = {
{ .compatible = "allwinner,sun8i-a33-codec", .data = &sun8i_a33_quirks },
{ .compatible = "allwinner,sun50i-a64-codec", .data = &sun50i_a64_quirks },
+ { .compatible = "x-powers,ac100-codec", .data = &ac100_quirks },
{}
};
MODULE_DEVICE_TABLE(of, sun8i_codec_of_match);
--
2.34.1