384 lines
13 KiB
Diff
384 lines
13 KiB
Diff
From 58fcfd05a8b2bd0a31453dcf4e4dd3ce759a658f Mon Sep 17 00:00:00 2001
|
|
From: Ondrej Jirman <megous@megous.com>
|
|
Date: Sun, 9 Feb 2020 17:58:59 +0100
|
|
Subject: [PATCH 402/478] 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 150ab2e3de76..47f14ce6de5b 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 68cf5faaa10d..3bcc7457e8cf 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.35.3
|
|
|