384 lines
13 KiB
Diff
384 lines
13 KiB
Diff
|
From 1fb1a22f82be9d731068a52a90b9770e5019758a 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 263/464] 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
|
||
|
|