From 95576bc41f438c3be5be3bea23384951dd572148 Mon Sep 17 00:00:00 2001
From: Ondrej Jirman <megous@megous.com>
Date: Sun, 9 Feb 2020 17:58:59 +0100
Subject: [PATCH 398/467] 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 <megous@megous.com>
---
 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 150ab2e3d..47f14ce6d 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 68cf5faaa..3bcc7457e 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. */
@@ -1443,7 +1476,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,
@@ -1556,12 +1589,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;
@@ -1643,6 +1839,14 @@ static int sun8i_codec_probe(struct platform_device *pdev)
 
 static int 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 0;
+	}
+
 	pm_runtime_disable(&pdev->dev);
 	if (!pm_runtime_status_suspended(&pdev->dev))
 		sun8i_codec_runtime_suspend(&pdev->dev);
@@ -1661,9 +1865,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.0

