From c4874cde098758e0f4c6a32f9a3defa7b02f0939 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 215/463] 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 | 216 ++++++++++++++++++++++++++++++++--
 2 files changed, 212 insertions(+), 8 deletions(-)

diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig
index 7b2b3bcb062e8..f87b061f59b2a 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 c6550f0b32041..2e4581f371a74 100644
--- a/sound/soc/sunxi/sun8i-codec.c
+++ b/sound/soc/sunxi/sun8i-codec.c
@@ -21,7 +21,9 @@
 #include <linux/of.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>
@@ -194,6 +196,50 @@
 				 SNDRV_PCM_RATE_192000    |\
 				 SNDRV_PCM_RATE_KNOT)
 
+#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 const char *const ac100_supply_names[] = {
+	"LDOIN",
+	"AVCC",
+	"VDDIO1",
+	"VDDIO2",
+};
+
 enum {
 	SUN8I_CODEC_AIF1,
 	SUN8I_CODEC_AIF2,
@@ -242,6 +288,9 @@ struct sun8i_codec {
 	int				last_hmic_irq;
 	unsigned int			sysclk_rate;
 	int				sysclk_refcnt;
+
+	struct regmap			*ac100_regmap;
+	struct regulator_bulk_data	supplies[ARRAY_SIZE(ac100_supply_names)];
 };
 
 static struct snd_soc_dai_driver sun8i_codec_dais[];
@@ -645,6 +694,10 @@ 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);
 
+	/* TODO: Implement clk driver for AC100 codec system clock */
+	if (scodec->ac100_regmap)
+		goto update_sample_rate;
+
 	/*
 	 * SYSCLK rate
 	 *
@@ -667,6 +720,7 @@ static int sun8i_codec_hw_params(struct snd_pcm_substream *substream,
 		scodec->sysclk_refcnt++;
 	scodec->sysclk_rate = sysclk_rate;
 
+update_sample_rate:
 	aif->lrck_div_order = lrck_div_order;
 	aif->sample_rate = sample_rate;
 	aif->open_streams |= BIT(substream->stream);
@@ -684,8 +738,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;
 
@@ -958,8 +1015,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),
@@ -1120,8 +1175,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" },
@@ -1291,6 +1344,16 @@ 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 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);
@@ -1299,6 +1362,21 @@ static int sun8i_codec_component_probe(struct snd_soc_component *component)
 
 	scodec->component = component;
 
+	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;
+
 	/* Add widgets for backward compatibility with old device trees. */
 	if (scodec->quirks->legacy_widgets) {
 		ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_legacy_widgets,
@@ -1640,7 +1718,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,
@@ -1650,12 +1728,124 @@ static const struct regmap_config sun8i_codec_regmap_config = {
 	.cache_type	= REGCACHE_FLAT,
 };
 
+/* 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;
+
+	return regmap_read(scodec->ac100_regmap, reg / 4, val);
+}
+
+static int sun8i_codec_ac100_regmap_write(void *context,
+					  unsigned int reg, unsigned int val)
+{
+	struct sun8i_codec *scodec = context;
+
+	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 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. */
+	return sun8i_codec_update_sample_rate(scodec);
+}
+
+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, so disable the mapped regmap
+	 * cache.
+	 */
+	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))
+		return dev_err_probe(dev, PTR_ERR(scodec->regmap),
+				     "Failed to create our regmap\n");
+
+	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)
+		return dev_err_probe(dev, ret, "Failed to request supplies\n");
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(scodec->supplies),
+				    scodec->supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to enable supplies\n");
+
+	ret = devm_snd_soc_register_component(dev, &sun8i_soc_component,
+					      sun8i_codec_dais,
+					      ARRAY_SIZE(sun8i_codec_dais));
+	if (ret) {
+		dev_err_probe(dev, ret, "Failed to register codec\n");
+		goto err_disable_reg;
+	}
+
+	return 0;
+
+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;
@@ -1728,6 +1918,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);
@@ -1744,9 +1942,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.46.0

