mirror of https://github.com/armbian/build.git
5453 lines
178 KiB
Diff
5453 lines
178 KiB
Diff
From 6e527d62e8e118474abae0058e7df10e98afb4ce Mon Sep 17 00:00:00 2001
|
|
From: Stephen Graf <stephen.graf@gmail.com>
|
|
Date: Thu, 9 May 2024 20:59:34 -0700
|
|
Subject: Sound for H616, H618 Allwinner SOCs
|
|
|
|
Signed-off-by: Stephen Graf <stephen.graf@gmail.com>
|
|
---
|
|
.../allwinner/sun50i-h616-orangepi-zero.dtsi | 18 +
|
|
.../arm64/boot/dts/allwinner/sun50i-h616.dtsi | 83 +
|
|
drivers/clk/sunxi-ng/ccu-sun50i-h616.c | 33 +-
|
|
include/sound/soc-dai.h | 13 +
|
|
sound/soc/Kconfig | 1 +
|
|
sound/soc/Makefile | 1 +
|
|
sound/soc/soc-core.c | 25 +
|
|
sound/soc/sunxi/Kconfig | 8 +
|
|
sound/soc/sunxi/Makefile | 1 +
|
|
sound/soc/sunxi/sun50iw9-codec.c | 1093 ++++++++++++
|
|
sound/soc/sunxi_v2/Kconfig | 48 +
|
|
sound/soc/sunxi_v2/Makefile | 11 +
|
|
sound/soc/sunxi_v2/drv_hdmi.h | 63 +
|
|
sound/soc/sunxi_v2/snd_sunxi_ahub.c | 1477 +++++++++++++++++
|
|
sound/soc/sunxi_v2/snd_sunxi_ahub.h | 67 +
|
|
sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c | 534 ++++++
|
|
sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h | 291 ++++
|
|
sound/soc/sunxi_v2/snd_sunxi_common.c | 267 +++
|
|
sound/soc/sunxi_v2/snd_sunxi_common.h | 67 +
|
|
sound/soc/sunxi_v2/snd_sunxi_log.h | 29 +
|
|
sound/soc/sunxi_v2/snd_sunxi_mach.c | 479 ++++++
|
|
sound/soc/sunxi_v2/snd_sunxi_mach.h | 17 +
|
|
sound/soc/sunxi_v2/snd_sunxi_mach_utils.c | 422 +++++
|
|
sound/soc/sunxi_v2/snd_sunxi_mach_utils.h | 116 ++
|
|
24 files changed, 5147 insertions(+), 17 deletions(-)
|
|
create mode 100644 sound/soc/sunxi/sun50iw9-codec.c
|
|
create mode 100644 sound/soc/sunxi_v2/Kconfig
|
|
create mode 100644 sound/soc/sunxi_v2/Makefile
|
|
create mode 100644 sound/soc/sunxi_v2/drv_hdmi.h
|
|
create mode 100644 sound/soc/sunxi_v2/snd_sunxi_ahub.c
|
|
create mode 100644 sound/soc/sunxi_v2/snd_sunxi_ahub.h
|
|
create mode 100644 sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c
|
|
create mode 100644 sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h
|
|
create mode 100644 sound/soc/sunxi_v2/snd_sunxi_common.c
|
|
create mode 100644 sound/soc/sunxi_v2/snd_sunxi_common.h
|
|
create mode 100644 sound/soc/sunxi_v2/snd_sunxi_log.h
|
|
create mode 100644 sound/soc/sunxi_v2/snd_sunxi_mach.c
|
|
create mode 100644 sound/soc/sunxi_v2/snd_sunxi_mach.h
|
|
create mode 100644 sound/soc/sunxi_v2/snd_sunxi_mach_utils.c
|
|
create mode 100644 sound/soc/sunxi_v2/snd_sunxi_mach_utils.h
|
|
|
|
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616-orangepi-zero.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616-orangepi-zero.dtsi
|
|
index ce3dc6d9cd66..23553f2249c2 100644
|
|
--- a/arch/arm64/boot/dts/allwinner/sun50i-h616-orangepi-zero.dtsi
|
|
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-orangepi-zero.dtsi
|
|
@@ -105,6 +105,24 @@ &de {
|
|
status = "okay";
|
|
};
|
|
|
|
+&codec {
|
|
+ allwinner,audio-routing =
|
|
+ "Line Out", "LINEOUT";
|
|
+ status = "okay";
|
|
+};
|
|
+
|
|
+&ahub_dam_plat {
|
|
+ status = "okay";
|
|
+};
|
|
+
|
|
+&ahub1_plat {
|
|
+ status = "okay";
|
|
+};
|
|
+
|
|
+&ahub1_mach {
|
|
+ status = "okay";
|
|
+};
|
|
+
|
|
&ehci1 {
|
|
status = "okay";
|
|
};
|
|
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
|
|
index b7c9d4b02751..f4ff1833e5fe 100644
|
|
--- a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
|
|
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
|
|
@@ -182,6 +182,78 @@ dma: dma-controller@3002000 {
|
|
#dma-cells = <1>;
|
|
};
|
|
|
|
+ codec: codec@05096000 {
|
|
+ #sound-dai-cells = <0>;
|
|
+ compatible = "allwinner,sun50i-h616-codec";
|
|
+ reg = <0x05096000 0x31c>;
|
|
+ interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH>;
|
|
+ clocks = <&ccu CLK_BUS_AUDIO_CODEC>,
|
|
+ <&ccu CLK_AUDIO_CODEC_1X>,
|
|
+ <&ccu CLK_AUDIO_CODEC_4X>;
|
|
+ clock-names = "apb", "audio-codec-1x", "audio-codec-4x";
|
|
+ resets = <&ccu RST_BUS_AUDIO_CODEC>;
|
|
+ dmas = <&dma 6>;
|
|
+ dma-names = "tx";
|
|
+ status = "disabled";
|
|
+ };
|
|
+
|
|
+ ahub_dam_plat:ahub_dam_plat@5097000 {
|
|
+ #sound-dai-cells = <0>;
|
|
+ /* sound card without pcm for hardware mix setting */
|
|
+ compatible = "allwinner,sunxi-snd-plat-ahub_dam";
|
|
+ reg = <0x05097000 0x1000>;
|
|
+ resets = <&ccu RST_BUS_AUDIO_HUB>;
|
|
+ clocks = <&ccu CLK_AUDIO_CODEC_1X>,
|
|
+ <&ccu CLK_AUDIO_CODEC_4X>,
|
|
+ <&ccu CLK_AUDIO_HUB>,
|
|
+ <&ccu CLK_BUS_AUDIO_HUB>;
|
|
+ clock-names = "clk_pll_audio",
|
|
+ "clk_pll_audio_4x",
|
|
+ "clk_audio_hub",
|
|
+ "clk_bus_audio_hub";
|
|
+ status = "disabled";
|
|
+ };
|
|
+
|
|
+ ahub1_plat:ahub1_plat {
|
|
+ #sound-dai-cells = <0>;
|
|
+ compatible = "allwinner,sunxi-snd-plat-ahub";
|
|
+ apb_num = <1>; /* for dma port 4 */
|
|
+ dmas = <&dma 4>, <&dma 4>;
|
|
+ dma-names = "tx", "rx";
|
|
+ playback_cma = <128>;
|
|
+ capture_cma = <128>;
|
|
+ tx_fifo_size = <128>;
|
|
+ rx_fifo_size = <128>;
|
|
+
|
|
+ tdm_num = <1>;
|
|
+ tx_pin = <0>;
|
|
+ rx_pin = <0>;
|
|
+ status = "disabled";
|
|
+ };
|
|
+
|
|
+ ahub1_mach:ahub1_mach {
|
|
+ compatible = "allwinner,sunxi-snd-mach";
|
|
+ soundcard-mach,name = "HDMI";
|
|
+
|
|
+ soundcard-mach,format = "i2s";
|
|
+ soundcard-mach,frame-master = <&ahub1_cpu>;
|
|
+ soundcard-mach,bitclock-master = <&ahub1_cpu>;
|
|
+ /* soundcard-mach,frame-inversion; */
|
|
+ /* soundcard-mach,bitclock-inversion; */
|
|
+ soundcard-mach,slot-num = <2>;
|
|
+ soundcard-mach,slot-width = <32>;
|
|
+ status = "disabled";
|
|
+ ahub1_cpu: soundcard-mach,cpu {
|
|
+ sound-dai = <&ahub1_plat>;
|
|
+ soundcard-mach,pll-fs = <4>;
|
|
+ soundcard-mach,mclk-fs = <0>;
|
|
+ };
|
|
+
|
|
+ ahub1_codec: soundcard-mach,codec {
|
|
+ sound-dai = <&hdmi>;
|
|
+ };
|
|
+ };
|
|
+
|
|
gpu: gpu@1800000 {
|
|
compatible = "allwinner,sun50i-h616-mali",
|
|
"arm,mali-bifrost";
|
|
@@ -475,6 +547,17 @@ gic: interrupt-controller@3021000 {
|
|
#interrupt-cells = <3>;
|
|
};
|
|
|
|
+ iommu: iommu@30f0000 {
|
|
+ compatible = "allwinner,sun50i-h616-iommu",
|
|
+ "allwinner,sun50i-h6-iommu";
|
|
+ reg = <0x030f0000 0x10000>;
|
|
+ interrupts = <GIC_SPI 61 IRQ_TYPE_LEVEL_HIGH>;
|
|
+ clocks = <&ccu CLK_BUS_IOMMU>;
|
|
+ resets = <&ccu RST_BUS_IOMMU>;
|
|
+ #iommu-cells = <1>;
|
|
+ status = "okay";
|
|
+ };
|
|
+
|
|
mmc0: mmc@4020000 {
|
|
compatible = "allwinner,sun50i-h616-mmc",
|
|
"allwinner,sun50i-a100-mmc";
|
|
diff --git a/drivers/clk/sunxi-ng/ccu-sun50i-h616.c b/drivers/clk/sunxi-ng/ccu-sun50i-h616.c
|
|
index 21e918582aa5..ab2628596b36 100644
|
|
--- a/drivers/clk/sunxi-ng/ccu-sun50i-h616.c
|
|
+++ b/drivers/clk/sunxi-ng/ccu-sun50i-h616.c
|
|
@@ -215,20 +215,22 @@ static struct ccu_nkmp pll_de_clk = {
|
|
},
|
|
};
|
|
|
|
-/*
|
|
- * TODO: Determine SDM settings for the audio PLL. The manual suggests
|
|
- * PLL_FACTOR_N=16, PLL_POST_DIV_P=2, OUTPUT_DIV=2, pattern=0xe000c49b
|
|
- * for 24.576 MHz, and PLL_FACTOR_N=22, PLL_POST_DIV_P=3, OUTPUT_DIV=2,
|
|
- * pattern=0xe001288c for 22.5792 MHz.
|
|
- * This clashes with our fixed PLL_POST_DIV_P.
|
|
- */
|
|
#define SUN50I_H616_PLL_AUDIO_REG 0x078
|
|
+
|
|
+static struct ccu_sdm_setting pll_audio_sdm_table[] = {
|
|
+ { .rate = 90316800, .pattern = 0xc001288d, .m = 3, .n = 22 },
|
|
+ { .rate = 98304000, .pattern = 0xc001eb85, .m = 5, .n = 40 },
|
|
+};
|
|
+
|
|
static struct ccu_nm pll_audio_hs_clk = {
|
|
.enable = BIT(31),
|
|
.lock = BIT(28),
|
|
.n = _SUNXI_CCU_MULT_MIN(8, 8, 12),
|
|
- .m = _SUNXI_CCU_DIV(1, 1), /* input divider */
|
|
+ .m = _SUNXI_CCU_DIV(16, 6),
|
|
+ .sdm = _SUNXI_CCU_SDM(pll_audio_sdm_table,
|
|
+ BIT(24), 0x178, BIT(31)),
|
|
.common = {
|
|
+ .features = CCU_FEATURE_SIGMA_DELTA_MOD,
|
|
.reg = 0x078,
|
|
.hw.init = CLK_HW_INIT("pll-audio-hs", "osc24M",
|
|
&ccu_nm_ops,
|
|
@@ -688,13 +690,13 @@ static const struct clk_hw *clk_parent_pll_audio[] = {
|
|
*/
|
|
static CLK_FIXED_FACTOR_HWS(pll_audio_1x_clk, "pll-audio-1x",
|
|
clk_parent_pll_audio,
|
|
- 96, 1, CLK_SET_RATE_PARENT);
|
|
+ 4, 1, CLK_SET_RATE_PARENT);
|
|
static CLK_FIXED_FACTOR_HWS(pll_audio_2x_clk, "pll-audio-2x",
|
|
clk_parent_pll_audio,
|
|
- 48, 1, CLK_SET_RATE_PARENT);
|
|
+ 2, 1, CLK_SET_RATE_PARENT);
|
|
static CLK_FIXED_FACTOR_HWS(pll_audio_4x_clk, "pll-audio-4x",
|
|
clk_parent_pll_audio,
|
|
- 24, 1, CLK_SET_RATE_PARENT);
|
|
+ 1, 1, CLK_SET_RATE_PARENT);
|
|
|
|
static const struct clk_hw *pll_periph0_parents[] = {
|
|
&pll_periph0_clk.common.hw
|
|
@@ -1130,13 +1132,10 @@ static int sun50i_h616_ccu_probe(struct platform_device *pdev)
|
|
writel(val, reg + usb2_clk_regs[i]);
|
|
}
|
|
|
|
- /*
|
|
- * Force the post-divider of pll-audio to 12 and the output divider
|
|
- * of it to 2, so 24576000 and 22579200 rates can be set exactly.
|
|
- */
|
|
val = readl(reg + SUN50I_H616_PLL_AUDIO_REG);
|
|
- val &= ~(GENMASK(21, 16) | BIT(0));
|
|
- writel(val | (11 << 16) | BIT(0), reg + SUN50I_H616_PLL_AUDIO_REG);
|
|
+ val &= ~BIT(1);
|
|
+ val |= BIT(0);
|
|
+ writel(val, reg + SUN50I_H616_PLL_AUDIO_REG);
|
|
|
|
/*
|
|
* First clock parent (osc32K) is unusable for CEC. But since there
|
|
diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h
|
|
index adcd8719d343..778751096c23 100644
|
|
--- a/include/sound/soc-dai.h
|
|
+++ b/include/sound/soc-dai.h
|
|
@@ -415,6 +415,15 @@ struct snd_soc_dai_driver {
|
|
struct snd_soc_dobj dobj;
|
|
struct of_phandle_args *dai_args;
|
|
|
|
+ /* DAI driver callbacks */
|
|
+ int (*probe)(struct snd_soc_dai *dai);
|
|
+ int (*remove)(struct snd_soc_dai *dai);
|
|
+ /* compress dai */
|
|
+ int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
|
|
+ /* Optional Callback used at pcm creation*/
|
|
+ int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
|
|
+ struct snd_soc_dai *dai);
|
|
+
|
|
/* ops */
|
|
const struct snd_soc_dai_ops *ops;
|
|
const struct snd_soc_cdai_ops *cops;
|
|
@@ -425,6 +434,10 @@ struct snd_soc_dai_driver {
|
|
unsigned int symmetric_rate:1;
|
|
unsigned int symmetric_channels:1;
|
|
unsigned int symmetric_sample_bits:1;
|
|
+
|
|
+ /* probe ordering - for components with runtime dependencies */
|
|
+ int probe_order;
|
|
+ int remove_order;
|
|
};
|
|
|
|
/* for Playback/Capture */
|
|
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
|
|
index 439fa631c342..7dc3967ccb27 100644
|
|
--- a/sound/soc/Kconfig
|
|
+++ b/sound/soc/Kconfig
|
|
@@ -108,6 +108,7 @@ source "sound/soc/starfive/Kconfig"
|
|
source "sound/soc/sti/Kconfig"
|
|
source "sound/soc/stm/Kconfig"
|
|
source "sound/soc/sunxi/Kconfig"
|
|
+source "sound/soc/sunxi_v2/Kconfig"
|
|
source "sound/soc/tegra/Kconfig"
|
|
source "sound/soc/ti/Kconfig"
|
|
source "sound/soc/uniphier/Kconfig"
|
|
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
|
|
index 8376fdb217ed..79b48e8b3657 100644
|
|
--- a/sound/soc/Makefile
|
|
+++ b/sound/soc/Makefile
|
|
@@ -65,6 +65,7 @@ obj-$(CONFIG_SND_SOC) += starfive/
|
|
obj-$(CONFIG_SND_SOC) += sti/
|
|
obj-$(CONFIG_SND_SOC) += stm/
|
|
obj-$(CONFIG_SND_SOC) += sunxi/
|
|
+obj-$(CONFIG_SND_SOC) += sunxi_v2/
|
|
obj-$(CONFIG_SND_SOC) += tegra/
|
|
obj-$(CONFIG_SND_SOC) += ti/
|
|
obj-$(CONFIG_SND_SOC) += uniphier/
|
|
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
|
|
index e65fe3a7c3e4..f43402e66020 100644
|
|
--- a/sound/soc/soc-core.c
|
|
+++ b/sound/soc/soc-core.c
|
|
@@ -2523,6 +2523,7 @@ struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component,
|
|
{
|
|
struct device *dev = component->dev;
|
|
struct snd_soc_dai *dai;
|
|
+ struct snd_soc_dai_ops *ops; /* REMOVE ME */
|
|
|
|
lockdep_assert_held(&client_mutex);
|
|
|
|
@@ -2551,6 +2552,30 @@ struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component,
|
|
if (!dai->name)
|
|
return NULL;
|
|
|
|
+ /* REMOVE ME */
|
|
+ if (dai_drv->probe ||
|
|
+ dai_drv->remove ||
|
|
+ dai_drv->compress_new ||
|
|
+ dai_drv->pcm_new ||
|
|
+ dai_drv->probe_order ||
|
|
+ dai_drv->remove_order) {
|
|
+
|
|
+ ops = devm_kzalloc(dev, sizeof(struct snd_soc_dai_ops), GFP_KERNEL);
|
|
+ if (!ops)
|
|
+ return NULL;
|
|
+ if (dai_drv->ops)
|
|
+ memcpy(ops, dai_drv->ops, sizeof(struct snd_soc_dai_ops));
|
|
+
|
|
+ ops->probe = dai_drv->probe;
|
|
+ ops->remove = dai_drv->remove;
|
|
+ ops->compress_new = dai_drv->compress_new;
|
|
+ ops->pcm_new = dai_drv->pcm_new;
|
|
+ ops->probe_order = dai_drv->probe_order;
|
|
+ ops->remove_order = dai_drv->remove_order;
|
|
+
|
|
+ dai_drv->ops = ops;
|
|
+ }
|
|
+
|
|
dai->component = component;
|
|
dai->dev = dev;
|
|
dai->driver = dai_drv;
|
|
diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig
|
|
index 753c38c5d554..0f6579ea7143 100644
|
|
--- a/sound/soc/sunxi/Kconfig
|
|
+++ b/sound/soc/sunxi/Kconfig
|
|
@@ -10,6 +10,14 @@ config SND_SUN4I_CODEC
|
|
Select Y or M to add support for the Codec embedded in the Allwinner
|
|
A10 and affiliated SoCs.
|
|
|
|
+config SND_SUN50IW9_CODEC
|
|
+ tristate "Allwinner H616 Codec Support"
|
|
+ select SND_SOC_GENERIC_DMAENGINE_PCM
|
|
+ select REGMAP_MMIO
|
|
+ help
|
|
+ Select Y or M to add support for the Codec embedded in the Allwinner
|
|
+ H616 and affiliated SoCs.
|
|
+
|
|
config SND_SUN8I_CODEC
|
|
tristate "Allwinner SUN8I audio codec"
|
|
depends on OF
|
|
diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile
|
|
index b0b976a3d069..f72d94f3004c 100644
|
|
--- a/sound/soc/sunxi/Makefile
|
|
+++ b/sound/soc/sunxi/Makefile
|
|
@@ -1,6 +1,7 @@
|
|
# SPDX-License-Identifier: GPL-2.0
|
|
obj-$(CONFIG_SND_SUN8I_CODEC_ANALOG) += sun8i-codec-analog.o
|
|
obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o
|
|
+obj-$(CONFIG_SND_SUN50IW9_CODEC) += sun50iw9-codec.o
|
|
obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o
|
|
obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o
|
|
obj-$(CONFIG_SND_SUN50I_CODEC_ANALOG) += sun50i-codec-analog.o
|
|
diff --git a/sound/soc/sunxi/sun50iw9-codec.c b/sound/soc/sunxi/sun50iw9-codec.c
|
|
new file mode 100644
|
|
index 000000000000..38b1d3824c20
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi/sun50iw9-codec.c
|
|
@@ -0,0 +1,1093 @@
|
|
+// SPDX-License-Identifier: GPL-2.0-or-later
|
|
+/*
|
|
+ * Copyright 2014 Emilio López <emilio@elopez.com.ar>
|
|
+ * Copyright 2014 Jon Smirl <jonsmirl@gmail.com>
|
|
+ * Copyright 2015 Maxime Ripard <maxime.ripard@free-electrons.com>
|
|
+ * Copyright 2015 Adam Sampson <ats@offog.org>
|
|
+ * Copyright 2016 Chen-Yu Tsai <wens@csie.org>
|
|
+ * Copyright 2021 gryzun <gryzun_an@rambler.ru>
|
|
+ *
|
|
+ * Based on the Allwinner SDK driver, released under the GPL.
|
|
+ */
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_address.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/reset.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
+
|
|
+#include <sound/core.h>
|
|
+#include <sound/pcm.h>
|
|
+#include <sound/pcm_params.h>
|
|
+#include <sound/soc.h>
|
|
+#include <sound/tlv.h>
|
|
+#include <sound/initval.h>
|
|
+#include <sound/dmaengine_pcm.h>
|
|
+
|
|
+#define SUNXI_DAC_DPC (0x00)
|
|
+#define SUNXI_DAC_DPC_EN_DA (31)
|
|
+#define SUNXI_DAC_DPC_DVOL (12)
|
|
+#define SUNXI_DAC_DPC_HPF_EN (18)
|
|
+
|
|
+#define SUNXI_DAC_FIFOC (0x10)
|
|
+#define SUNXI_DAC_FIFOC_DAC_FS (29)
|
|
+#define SUNXI_DAC_FIFOC_TX_FIFO_MODE (24)
|
|
+#define SUNXI_DAC_FIFOC_DRQ_CLR_CNT (21)
|
|
+#define SUNXI_DAC_FIFOC_MONO_EN (6)
|
|
+#define SUNXI_DAC_FIFOC_TX_SAMPLE_BITS (5)
|
|
+#define SUNXI_DAC_FIFOC_DAC_DRQ_EN (4)
|
|
+#define SUNXI_DAC_FIFOC_FIFO_FLUSH (0)
|
|
+
|
|
+#define SUNXI_DAC_FIFO_STA (0x14)
|
|
+#define SUNXI_DAC_TXE_INT (3)
|
|
+#define SUNXI_DAC_TXU_INT (2)
|
|
+#define SUNXI_DAC_TXO_INT (1)
|
|
+#define SUNXI_DAC_TXDATA (0x20)
|
|
+#define SUNXI_DAC_CNT (0x24)
|
|
+#define SUNXI_DAC_DG_REG (0x28)
|
|
+#define SUNXI_DAC_DAP_CTL (0xf0)
|
|
+
|
|
+#define SUNXI_DAC_AC_DAC_REG (0x310)
|
|
+#define SUNXI_DAC_LEN (15)
|
|
+#define SUNXI_DAC_REN (14)
|
|
+#define SUNXI_LINEOUTL_EN (13)
|
|
+#define SUNXI_LMUTE (12)
|
|
+#define SUNXI_LINEOUTR_EN (11)
|
|
+#define SUNXI_RMUTE (10)
|
|
+#define SUNXI_RSWITCH (9)
|
|
+#define SUNXI_RAMPEN (8)
|
|
+#define SUNXI_LINEOUTL_SEL (6)
|
|
+#define SUNXI_LINEOUTR_SEL (5)
|
|
+#define SUNXI_LINEOUT_VOL (0)
|
|
+
|
|
+#define SUNXI_DAC_AC_MIXER_REG (0x314)
|
|
+#define SUNXI_LMIX_LDAC (21)
|
|
+#define SUNXI_LMIX_RDAC (20)
|
|
+#define SUNXI_RMIX_RDAC (17)
|
|
+#define SUNXI_RMIX_LDAC (16)
|
|
+#define SUNXI_LMIXEN (11)
|
|
+#define SUNXI_RMIXEN (10)
|
|
+
|
|
+#define SUNXI_DAC_AC_RAMP_REG (0x31c)
|
|
+#define SUNXI_RAMP_STEP (4)
|
|
+#define SUNXI_RDEN (0)
|
|
+
|
|
+#define LABEL(constant) \
|
|
+ { \
|
|
+#constant, constant, 0 \
|
|
+ }
|
|
+#define LABEL_END \
|
|
+ { \
|
|
+ NULL, 0, -1 \
|
|
+ }
|
|
+
|
|
+struct audiocodec_reg_label
|
|
+{
|
|
+ const char *name;
|
|
+ const unsigned int address;
|
|
+ int value;
|
|
+};
|
|
+
|
|
+static struct audiocodec_reg_label reg_labels[] = {
|
|
+ LABEL(SUNXI_DAC_DPC),
|
|
+ LABEL(SUNXI_DAC_FIFOC),
|
|
+ LABEL(SUNXI_DAC_FIFO_STA),
|
|
+ LABEL(SUNXI_DAC_CNT),
|
|
+ LABEL(SUNXI_DAC_DG_REG),
|
|
+ LABEL(SUNXI_DAC_DAP_CTL),
|
|
+ LABEL(SUNXI_DAC_AC_DAC_REG),
|
|
+ LABEL(SUNXI_DAC_AC_MIXER_REG),
|
|
+ LABEL(SUNXI_DAC_AC_RAMP_REG),
|
|
+ LABEL_END,
|
|
+};
|
|
+
|
|
+struct regmap *codec_regmap_debug = NULL;
|
|
+
|
|
+struct sun50i_h616_codec
|
|
+{
|
|
+ unsigned char *name;
|
|
+ struct device *dev;
|
|
+ struct regmap *regmap;
|
|
+ struct clk *clk_apb;
|
|
+ struct clk *clk_module;
|
|
+ struct reset_control *rst;
|
|
+ struct gpio_desc *gpio_pa;
|
|
+
|
|
+ /* ADC_FIFOC register is at different offset on different SoCs */
|
|
+ struct regmap_field *reg_adc_fifoc;
|
|
+
|
|
+ struct snd_dmaengine_dai_dma_data playback_dma_data;
|
|
+};
|
|
+
|
|
+static void sun50i_h616_codec_start_playback(struct sun50i_h616_codec *scodec)
|
|
+{
|
|
+ /* Flush TX FIFO */
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC,
|
|
+ BIT(SUNXI_DAC_FIFOC_FIFO_FLUSH),
|
|
+ BIT(SUNXI_DAC_FIFOC_FIFO_FLUSH));
|
|
+
|
|
+ /* Enable DAC DRQ */
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC,
|
|
+ BIT(SUNXI_DAC_FIFOC_DAC_DRQ_EN),
|
|
+ BIT(SUNXI_DAC_FIFOC_DAC_DRQ_EN));
|
|
+}
|
|
+
|
|
+static void sun50i_h616_codec_stop_playback(struct sun50i_h616_codec *scodec)
|
|
+{
|
|
+ /* Disable DAC DRQ */
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC,
|
|
+ BIT(SUNXI_DAC_FIFOC_DAC_DRQ_EN),
|
|
+ 0);
|
|
+}
|
|
+
|
|
+static int sun50i_h616_codec_trigger(struct snd_pcm_substream *substream, int cmd,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
+ struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(rtd->card);
|
|
+
|
|
+ switch (cmd)
|
|
+ {
|
|
+ case SNDRV_PCM_TRIGGER_START:
|
|
+ case SNDRV_PCM_TRIGGER_RESUME:
|
|
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
+ regmap_update_bits(scodec->regmap,
|
|
+ SUNXI_DAC_FIFOC,
|
|
+ (0x1 << SUNXI_DAC_FIFOC_DAC_DRQ_EN),
|
|
+ (0x1 << SUNXI_DAC_FIFOC_DAC_DRQ_EN));
|
|
+ break;
|
|
+ case SNDRV_PCM_TRIGGER_STOP:
|
|
+ case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
+ regmap_update_bits(scodec->regmap,
|
|
+ SUNXI_DAC_FIFOC,
|
|
+ (0x1 << SUNXI_DAC_FIFOC_DAC_DRQ_EN),
|
|
+ (0x0 << SUNXI_DAC_FIFOC_DAC_DRQ_EN));
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sun50i_h616_codec_prepare(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
+ struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(rtd->card);
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC,
|
|
+ (0x1 << SUNXI_DAC_FIFOC_FIFO_FLUSH),
|
|
+ (0x1 << SUNXI_DAC_FIFOC_FIFO_FLUSH));
|
|
+ regmap_write(scodec->regmap, SUNXI_DAC_FIFO_STA,
|
|
+ (0x1 << SUNXI_DAC_TXE_INT | 1 << SUNXI_DAC_TXU_INT | 0x1 << SUNXI_DAC_TXO_INT));
|
|
+ regmap_write(scodec->regmap, SUNXI_DAC_CNT, 0);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static unsigned long sun50i_h616_codec_get_mod_freq(struct snd_pcm_hw_params *params)
|
|
+{
|
|
+ unsigned int rate = params_rate(params);
|
|
+
|
|
+ switch (rate)
|
|
+ {
|
|
+ case 176400:
|
|
+ case 88200:
|
|
+ case 44100:
|
|
+ case 33075:
|
|
+ case 22050:
|
|
+ case 14700:
|
|
+ case 11025:
|
|
+ case 7350:
|
|
+ return 22579200;
|
|
+
|
|
+ case 192000:
|
|
+ case 96000:
|
|
+ case 48000:
|
|
+ case 32000:
|
|
+ case 24000:
|
|
+ case 16000:
|
|
+ case 12000:
|
|
+ case 8000:
|
|
+ return 24576000;
|
|
+
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int sun50i_h616_codec_get_hw_rate(struct snd_pcm_hw_params *params)
|
|
+{
|
|
+ unsigned int rate = params_rate(params);
|
|
+
|
|
+ switch (rate)
|
|
+ {
|
|
+ case 192000:
|
|
+ case 176400:
|
|
+ return 6;
|
|
+
|
|
+ case 96000:
|
|
+ case 88200:
|
|
+ return 7;
|
|
+
|
|
+ case 48000:
|
|
+ case 44100:
|
|
+ return 0;
|
|
+
|
|
+ case 32000:
|
|
+ case 33075:
|
|
+ return 1;
|
|
+
|
|
+ case 24000:
|
|
+ case 22050:
|
|
+ return 2;
|
|
+
|
|
+ case 16000:
|
|
+ case 14700:
|
|
+ return 3;
|
|
+
|
|
+ case 12000:
|
|
+ case 11025:
|
|
+ return 4;
|
|
+
|
|
+ case 8000:
|
|
+ case 7350:
|
|
+ return 5;
|
|
+
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int sun50i_h616_codec_hw_params_playback(struct sun50i_h616_codec *scodec,
|
|
+ struct snd_pcm_hw_params *params,
|
|
+ unsigned int hwrate)
|
|
+{
|
|
+ u32 val;
|
|
+
|
|
+ /* Set DAC sample rate */
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC,
|
|
+ 7 << SUNXI_DAC_FIFOC_DAC_FS,
|
|
+ hwrate << SUNXI_DAC_FIFOC_DAC_FS);
|
|
+
|
|
+ /* Set the number of channels we want to use */
|
|
+ if (params_channels(params) == 1)
|
|
+ val = BIT(SUNXI_DAC_FIFOC_MONO_EN);
|
|
+ else
|
|
+ val = 0;
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC,
|
|
+ BIT(SUNXI_DAC_FIFOC_MONO_EN),
|
|
+ val);
|
|
+
|
|
+ /* Set the number of sample bits to either 16 or 24 bits */
|
|
+ if (hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min == 32)
|
|
+ {
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC,
|
|
+ BIT(SUNXI_DAC_FIFOC_TX_SAMPLE_BITS),
|
|
+ BIT(SUNXI_DAC_FIFOC_TX_SAMPLE_BITS));
|
|
+
|
|
+ /* Set TX FIFO mode to padding the LSBs with 0 */
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC,
|
|
+ BIT(SUNXI_DAC_FIFOC_TX_FIFO_MODE),
|
|
+ 0);
|
|
+
|
|
+ scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC,
|
|
+ BIT(SUNXI_DAC_FIFOC_TX_SAMPLE_BITS),
|
|
+ 0);
|
|
+
|
|
+ /* Set TX FIFO mode to repeat the MSB */
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC,
|
|
+ BIT(SUNXI_DAC_FIFOC_TX_FIFO_MODE),
|
|
+ BIT(SUNXI_DAC_FIFOC_TX_FIFO_MODE));
|
|
+
|
|
+ scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+struct sample_rate
|
|
+{
|
|
+ unsigned int samplerate;
|
|
+ unsigned int rate_bit;
|
|
+};
|
|
+
|
|
+static const struct sample_rate sample_rate_conv[] = {
|
|
+ {44100, 0},
|
|
+ {48000, 0},
|
|
+ {8000, 5},
|
|
+ {32000, 1},
|
|
+ {22050, 2},
|
|
+ {24000, 2},
|
|
+ {16000, 3},
|
|
+ {11025, 4},
|
|
+ {12000, 4},
|
|
+ {192000, 6},
|
|
+ {96000, 7},
|
|
+};
|
|
+
|
|
+static int sun50i_h616_codec_hw_params(struct snd_pcm_substream *substream,
|
|
+ struct snd_pcm_hw_params *params,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
+ struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(rtd->card);
|
|
+ unsigned long clk_freq;
|
|
+ int ret, hwrate;
|
|
+ int i;
|
|
+
|
|
+ clk_freq = sun50i_h616_codec_get_mod_freq(params);
|
|
+ if (!clk_freq)
|
|
+ return -EINVAL;
|
|
+
|
|
+ ret = clk_set_rate(scodec->clk_module, clk_freq * 2);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ hwrate = sun50i_h616_codec_get_hw_rate(params);
|
|
+ if (hwrate < 0)
|
|
+ return hwrate;
|
|
+
|
|
+ switch (params_format(params))
|
|
+ {
|
|
+ case SNDRV_PCM_FORMAT_S16_LE:
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
+ {
|
|
+ regmap_update_bits(scodec->regmap,
|
|
+ SUNXI_DAC_FIFOC,
|
|
+ (0x3 << SUNXI_DAC_FIFOC_TX_FIFO_MODE),
|
|
+ (0x3 << SUNXI_DAC_FIFOC_TX_FIFO_MODE));
|
|
+ regmap_update_bits(scodec->regmap,
|
|
+ SUNXI_DAC_FIFOC,
|
|
+ (0x1 << SUNXI_DAC_FIFOC_TX_SAMPLE_BITS),
|
|
+ (0x0 << SUNXI_DAC_FIFOC_TX_SAMPLE_BITS));
|
|
+ }
|
|
+ break;
|
|
+ case SNDRV_PCM_FORMAT_S24_LE:
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
+ {
|
|
+ regmap_update_bits(scodec->regmap,
|
|
+ SUNXI_DAC_FIFOC,
|
|
+ (0x3 << SUNXI_DAC_FIFOC_TX_FIFO_MODE),
|
|
+ (0x0 << SUNXI_DAC_FIFOC_TX_FIFO_MODE));
|
|
+ regmap_update_bits(scodec->regmap,
|
|
+ SUNXI_DAC_FIFOC,
|
|
+ (0x1 << SUNXI_DAC_FIFOC_TX_SAMPLE_BITS),
|
|
+ (0x1 << SUNXI_DAC_FIFOC_TX_SAMPLE_BITS));
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(sample_rate_conv); i++)
|
|
+ {
|
|
+ if (sample_rate_conv[i].samplerate == params_rate(params))
|
|
+ {
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
+ {
|
|
+ regmap_update_bits(scodec->regmap,
|
|
+ SUNXI_DAC_FIFOC,
|
|
+ (0x7 << SUNXI_DAC_FIFOC_DAC_FS),
|
|
+ (sample_rate_conv[i].rate_bit << SUNXI_DAC_FIFOC_DAC_FS));
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
+ {
|
|
+ switch (params_channels(params))
|
|
+ {
|
|
+ case 1:
|
|
+ regmap_update_bits(scodec->regmap,
|
|
+ SUNXI_DAC_FIFOC,
|
|
+ (0x1 << SUNXI_DAC_FIFOC_MONO_EN),
|
|
+ (0x1 << SUNXI_DAC_FIFOC_MONO_EN));
|
|
+ break;
|
|
+ case 2:
|
|
+ regmap_update_bits(scodec->regmap,
|
|
+ SUNXI_DAC_FIFOC,
|
|
+ (0x1 << SUNXI_DAC_FIFOC_MONO_EN),
|
|
+ (0x0 << SUNXI_DAC_FIFOC_MONO_EN));
|
|
+ break;
|
|
+ default:
|
|
+ pr_err("[%s] Playback cannot support %d channels.\n",
|
|
+ __func__, params_channels(params));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static unsigned int sun50i_h616_codec_src_rates[] = {
|
|
+ 8000, 11025, 12000, 16000, 22050, 24000, 32000,
|
|
+ 44100, 48000, 96000, 192000};
|
|
+
|
|
+static struct snd_pcm_hw_constraint_list sun50i_h616_codec_constraints = {
|
|
+ .count = ARRAY_SIZE(sun50i_h616_codec_src_rates),
|
|
+ .list = sun50i_h616_codec_src_rates,
|
|
+};
|
|
+
|
|
+static int sun50i_h616_codec_startup(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
+ struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(rtd->card);
|
|
+
|
|
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
|
|
+ SNDRV_PCM_HW_PARAM_RATE, &sun50i_h616_codec_constraints);
|
|
+
|
|
+ /*
|
|
+ * Stop issuing DRQ when we have room for less than 16 samples
|
|
+ * in our TX FIFO
|
|
+ */
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_FIFOC,
|
|
+ 3 << SUNXI_DAC_FIFOC_DRQ_CLR_CNT,
|
|
+ 3 << SUNXI_DAC_FIFOC_DRQ_CLR_CNT);
|
|
+
|
|
+ return clk_prepare_enable(scodec->clk_module);
|
|
+}
|
|
+
|
|
+static void sun50i_h616_codec_shutdown(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
+ struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(rtd->card);
|
|
+
|
|
+ clk_disable_unprepare(scodec->clk_module);
|
|
+}
|
|
+
|
|
+static const struct snd_soc_dai_ops sun50i_h616_codec_dai_ops = {
|
|
+ .startup = sun50i_h616_codec_startup,
|
|
+ .shutdown = sun50i_h616_codec_shutdown,
|
|
+ .trigger = sun50i_h616_codec_trigger,
|
|
+ .hw_params = sun50i_h616_codec_hw_params,
|
|
+ .prepare = sun50i_h616_codec_prepare,
|
|
+};
|
|
+
|
|
+static struct snd_soc_dai_driver sun50i_h616_codec_dai = {
|
|
+ .name = "Codec",
|
|
+ .ops = &sun50i_h616_codec_dai_ops,
|
|
+ .playback = {
|
|
+ .stream_name = "Codec Playback",
|
|
+ .channels_min = 1,
|
|
+ .channels_max = 2,
|
|
+ .rate_min = 8000,
|
|
+ .rate_max = 192000,
|
|
+ .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT,
|
|
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
|
|
+ SNDRV_PCM_FMTBIT_S24_LE,
|
|
+ .sig_bits = 24,
|
|
+ },
|
|
+};
|
|
+
|
|
+static int sunxi_lineout_event(struct snd_soc_dapm_widget *w,
|
|
+ struct snd_kcontrol *k, int event)
|
|
+{
|
|
+ struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(w->dapm->card);
|
|
+
|
|
+ switch (event)
|
|
+ {
|
|
+ case SND_SOC_DAPM_POST_PMU:
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_RAMP_REG,
|
|
+ (0x1 << SUNXI_RDEN), (0x1 << SUNXI_RDEN));
|
|
+ msleep(25);
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG,
|
|
+ (0x1 << SUNXI_LINEOUTL_EN) | (0x1 << SUNXI_LINEOUTR_EN),
|
|
+ (0x1 << SUNXI_LINEOUTL_EN) | (0x1 << SUNXI_LINEOUTR_EN));
|
|
+ break;
|
|
+ case SND_SOC_DAPM_PRE_PMD:
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_RAMP_REG,
|
|
+ (0x1 << SUNXI_RDEN), (0x0 << SUNXI_RDEN));
|
|
+ msleep(25);
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG,
|
|
+ (0x1 << SUNXI_LINEOUTL_EN) | (0x1 << SUNXI_LINEOUTR_EN),
|
|
+ (0x0 << SUNXI_LINEOUTL_EN) | (0x0 << SUNXI_LINEOUTR_EN));
|
|
+
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const DECLARE_TLV_DB_SCALE(digital_tlv, 0, -116, -7424);
|
|
+static const DECLARE_TLV_DB_SCALE(linein_to_l_r_mix_vol_tlv, -450, 150, 0);
|
|
+static const DECLARE_TLV_DB_SCALE(fmin_to_l_r_mix_vol_tlv, -450, 150, 0);
|
|
+
|
|
+static const unsigned int lineout_tlv[] = {
|
|
+ TLV_DB_RANGE_HEAD(2),
|
|
+ 0,
|
|
+ 0,
|
|
+ TLV_DB_SCALE_ITEM(0, 0, 1),
|
|
+ 1,
|
|
+ 31,
|
|
+ TLV_DB_SCALE_ITEM(-4350, 150, 1),
|
|
+};
|
|
+
|
|
+/*lineoutL mux select */
|
|
+const char *const left_lineoutl_text[] = {
|
|
+ "LOMixer",
|
|
+ "LROMixer",
|
|
+};
|
|
+
|
|
+static const struct soc_enum left_lineout_enum =
|
|
+ SOC_ENUM_SINGLE(SUNXI_DAC_AC_DAC_REG, SUNXI_LINEOUTL_SEL,
|
|
+ ARRAY_SIZE(left_lineoutl_text), left_lineoutl_text);
|
|
+
|
|
+static const struct snd_kcontrol_new left_lineout_mux =
|
|
+ SOC_DAPM_ENUM("Left LINEOUT Mux", left_lineout_enum);
|
|
+
|
|
+/*lineoutR mux select */
|
|
+const char *const right_lineoutr_text[] = {
|
|
+ "ROMixer",
|
|
+ "LROMixer",
|
|
+};
|
|
+
|
|
+static const struct soc_enum right_lineout_enum =
|
|
+ SOC_ENUM_SINGLE(SUNXI_DAC_AC_DAC_REG, SUNXI_LINEOUTR_SEL,
|
|
+ ARRAY_SIZE(right_lineoutr_text), right_lineoutr_text);
|
|
+
|
|
+static const struct snd_kcontrol_new right_lineout_mux =
|
|
+ SOC_DAPM_ENUM("Right LINEOUT Mux", right_lineout_enum);
|
|
+
|
|
+static const struct snd_kcontrol_new sun50i_h616_codec_codec_controls[] = {
|
|
+
|
|
+ SOC_SINGLE_TLV("digital volume", SUNXI_DAC_DPC,
|
|
+ SUNXI_DAC_DPC_DVOL, 0x3F, 0, digital_tlv),
|
|
+
|
|
+ SOC_SINGLE_TLV("LINEOUT volume", SUNXI_DAC_AC_DAC_REG,
|
|
+ SUNXI_LINEOUT_VOL, 0x1F, 0, lineout_tlv),
|
|
+};
|
|
+
|
|
+static const struct snd_kcontrol_new left_output_mixer[] = {
|
|
+ SOC_DAPM_SINGLE("DACL Switch", SUNXI_DAC_AC_MIXER_REG, SUNXI_LMIX_LDAC, 1, 0),
|
|
+ SOC_DAPM_SINGLE("DACR Switch", SUNXI_DAC_AC_MIXER_REG, SUNXI_LMIX_RDAC, 1, 0),
|
|
+};
|
|
+
|
|
+static const struct snd_kcontrol_new right_output_mixer[] = {
|
|
+ SOC_DAPM_SINGLE("DACL Switch", SUNXI_DAC_AC_MIXER_REG, SUNXI_RMIX_LDAC, 1, 0),
|
|
+ SOC_DAPM_SINGLE("DACR Switch", SUNXI_DAC_AC_MIXER_REG, SUNXI_RMIX_RDAC, 1, 0),
|
|
+};
|
|
+
|
|
+static const struct snd_soc_dapm_widget sun50i_h616_codec_codec_widgets[] = {
|
|
+
|
|
+ /* Digital parts of the DACs */
|
|
+ SND_SOC_DAPM_SUPPLY("DAC Enable", SUNXI_DAC_DPC,
|
|
+ SUNXI_DAC_DPC_EN_DA, 0, NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_AIF_IN_E("DACL", "Codec Playback", 0, SUNXI_DAC_AC_DAC_REG, SUNXI_DAC_LEN, 0,
|
|
+ NULL, 0),
|
|
+ SND_SOC_DAPM_AIF_IN_E("DACR", "Codec Playback", 0, SUNXI_DAC_AC_DAC_REG, SUNXI_DAC_REN, 0,
|
|
+ NULL, 0),
|
|
+
|
|
+ SND_SOC_DAPM_MIXER("Left Output Mixer", SUNXI_DAC_AC_MIXER_REG, SUNXI_LMIXEN, 0,
|
|
+ left_output_mixer, ARRAY_SIZE(left_output_mixer)),
|
|
+ SND_SOC_DAPM_MIXER("Right Output Mixer", SUNXI_DAC_AC_MIXER_REG, SUNXI_RMIXEN, 0,
|
|
+ right_output_mixer, ARRAY_SIZE(right_output_mixer)),
|
|
+
|
|
+ SND_SOC_DAPM_MUX("Left LINEOUT Mux", SND_SOC_NOPM,
|
|
+ 0, 0, &left_lineout_mux),
|
|
+ SND_SOC_DAPM_MUX("Right LINEOUT Mux", SND_SOC_NOPM,
|
|
+ 0, 0, &right_lineout_mux),
|
|
+
|
|
+ SND_SOC_DAPM_OUTPUT("LINEOUTL"),
|
|
+ SND_SOC_DAPM_OUTPUT("LINEOUTR"),
|
|
+
|
|
+ SND_SOC_DAPM_LINE("LINEOUT", sunxi_lineout_event),
|
|
+};
|
|
+
|
|
+static const struct snd_soc_component_driver sun50i_h616_codec_codec = {
|
|
+ .controls = sun50i_h616_codec_codec_controls,
|
|
+ .num_controls = ARRAY_SIZE(sun50i_h616_codec_codec_controls),
|
|
+ .dapm_widgets = sun50i_h616_codec_codec_widgets,
|
|
+ .num_dapm_widgets = ARRAY_SIZE(sun50i_h616_codec_codec_widgets),
|
|
+ .idle_bias_on = 1,
|
|
+ .use_pmdown_time = 1,
|
|
+ .endianness = 1,
|
|
+};
|
|
+
|
|
+static const struct snd_soc_component_driver sun50i_h616_codec_component = {
|
|
+ .name = "sun50i_h616-codec",
|
|
+ .legacy_dai_naming = 1,
|
|
+#ifdef CONFIG_DEBUG_FS
|
|
+ .debugfs_prefix = "cpu",
|
|
+#endif
|
|
+};
|
|
+
|
|
+#define SUN50IW9_CODEC_RATES (SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT)
|
|
+#define SUN50IW9_CODEC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
|
|
+
|
|
+static int sun50i_h616_codec_dai_probe(struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai);
|
|
+ struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(card);
|
|
+
|
|
+ snd_soc_dai_init_dma_data(dai, &scodec->playback_dma_data,
|
|
+ NULL);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct snd_soc_dai_driver dummy_cpu_dai = {
|
|
+ .name = "sun50i_h616-codec-cpu-dai",
|
|
+ .probe = sun50i_h616_codec_dai_probe,
|
|
+ .playback = {
|
|
+ .stream_name = "Playback",
|
|
+ .channels_min = 1,
|
|
+ .channels_max = 2,
|
|
+ .rates = SUN50IW9_CODEC_RATES,
|
|
+ .formats = SUN50IW9_CODEC_FORMATS,
|
|
+ .sig_bits = 24,
|
|
+ },
|
|
+};
|
|
+
|
|
+static struct snd_soc_dai_link *sun50i_h616_codec_create_link(struct device *dev,
|
|
+ int *num_links)
|
|
+{
|
|
+ struct snd_soc_dai_link *link = devm_kzalloc(dev, sizeof(*link),
|
|
+ GFP_KERNEL);
|
|
+ struct snd_soc_dai_link_component *dlc = devm_kzalloc(dev,
|
|
+ 3 * sizeof(*dlc), GFP_KERNEL);
|
|
+ if (!link || !dlc)
|
|
+ return NULL;
|
|
+
|
|
+ link->cpus = &dlc[0];
|
|
+ link->codecs = &dlc[1];
|
|
+ link->platforms = &dlc[2];
|
|
+
|
|
+ link->num_cpus = 1;
|
|
+ link->num_codecs = 1;
|
|
+ link->num_platforms = 1;
|
|
+
|
|
+ link->name = "cdc";
|
|
+ link->stream_name = "CDC PCM";
|
|
+ link->codecs->dai_name = "Codec";
|
|
+ link->cpus->dai_name = dev_name(dev);
|
|
+ link->codecs->name = dev_name(dev);
|
|
+ link->platforms->name = dev_name(dev);
|
|
+ link->dai_fmt = SND_SOC_DAIFMT_I2S;
|
|
+ link->playback_only = true;
|
|
+ link->capture_only = false;
|
|
+
|
|
+ *num_links = 1;
|
|
+
|
|
+ return link;
|
|
+};
|
|
+
|
|
+static int sun50i_h616_codec_spk_event(struct snd_soc_dapm_widget *w,
|
|
+ struct snd_kcontrol *k, int event)
|
|
+{
|
|
+ struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(w->dapm->card);
|
|
+
|
|
+ gpiod_set_value_cansleep(scodec->gpio_pa,
|
|
+ !!SND_SOC_DAPM_EVENT_ON(event));
|
|
+
|
|
+ if (SND_SOC_DAPM_EVENT_ON(event))
|
|
+ {
|
|
+ /*
|
|
+ * Need a delay to wait for DAC to push the data. 700ms seems
|
|
+ * to be the best compromise not to feel this delay while
|
|
+ * playing a sound.
|
|
+ */
|
|
+ msleep(700);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct snd_soc_dapm_widget sun6i_codec_card_dapm_widgets[] = {
|
|
+ SND_SOC_DAPM_LINE("Line Out", NULL),
|
|
+ SND_SOC_DAPM_SPK("Speaker", sun50i_h616_codec_spk_event),
|
|
+};
|
|
+
|
|
+/* Connect digital side enables to analog side widgets */
|
|
+static const struct snd_soc_dapm_route sun8i_codec_card_routes[] = {
|
|
+ /* DAC Routes */
|
|
+ {"DACR", NULL, "DAC Enable"},
|
|
+ {"DACL", NULL, "DAC Enable"},
|
|
+
|
|
+ {"Left Output Mixer", "DACR Switch", "DACR"},
|
|
+ {"Left Output Mixer", "DACL Switch", "DACL"},
|
|
+
|
|
+ {"Right Output Mixer", "DACL Switch", "DACL"},
|
|
+ {"Right Output Mixer", "DACR Switch", "DACR"},
|
|
+
|
|
+ {"Left LINEOUT Mux", "LOMixer", "Left Output Mixer"},
|
|
+ {"Left LINEOUT Mux", "LROMixer", "Right Output Mixer"},
|
|
+ {"Right LINEOUT Mux", "ROMixer", "Right Output Mixer"},
|
|
+ {"Right LINEOUT Mux", "LROMixer", "Left Output Mixer"},
|
|
+
|
|
+ {"LINEOUTL", NULL, "Left LINEOUT Mux"},
|
|
+ {"LINEOUTR", NULL, "Right LINEOUT Mux"},
|
|
+
|
|
+ {"LINEOUT", NULL, "LINEOUTL"},
|
|
+ {"LINEOUT", NULL, "LINEOUTR"},
|
|
+
|
|
+ {"Speaker", NULL, "LINEOUTL"},
|
|
+ {"Speaker", NULL, "LINEOUTR"},
|
|
+};
|
|
+
|
|
+static const struct snd_kcontrol_new sunxi_card_controls[] = {
|
|
+ SOC_DAPM_PIN_SWITCH("LINEOUT"),
|
|
+};
|
|
+
|
|
+static struct snd_soc_card *sun50i_h616_codec_create_card(struct device *dev)
|
|
+{
|
|
+ struct snd_soc_card *card;
|
|
+ int ret;
|
|
+
|
|
+ card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
|
|
+ if (!card)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ card->dai_link = sun50i_h616_codec_create_link(dev, &card->num_links);
|
|
+ if (!card->dai_link)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ card->dev = dev;
|
|
+ card->owner = THIS_MODULE;
|
|
+ card->name = "audiocodec";
|
|
+ card->controls = sunxi_card_controls;
|
|
+ card->num_controls = ARRAY_SIZE(sunxi_card_controls),
|
|
+ card->dapm_widgets = sun6i_codec_card_dapm_widgets;
|
|
+ card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets);
|
|
+ card->dapm_routes = sun8i_codec_card_routes;
|
|
+ card->num_dapm_routes = ARRAY_SIZE(sun8i_codec_card_routes);
|
|
+ card->fully_routed = true;
|
|
+
|
|
+ ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing");
|
|
+ if (ret)
|
|
+ dev_warn(dev, "failed to parse audio-routing: %d\n", ret);
|
|
+
|
|
+ return card;
|
|
+};
|
|
+
|
|
+static const struct regmap_config sun50i_h616_codec_regmap_config = {
|
|
+ .reg_bits = 32,
|
|
+ .reg_stride = 4,
|
|
+ .val_bits = 32,
|
|
+ .max_register = SUNXI_DAC_AC_RAMP_REG,
|
|
+ .cache_type = REGCACHE_NONE,
|
|
+};
|
|
+
|
|
+struct sun50i_h616_codec_quirks
|
|
+{
|
|
+ const struct regmap_config *regmap_config;
|
|
+ const struct snd_soc_component_driver *codec;
|
|
+ struct snd_soc_card *(*create_card)(struct device *dev);
|
|
+ struct reg_field reg_adc_fifoc; /* used for regmap_field */
|
|
+ unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */
|
|
+ unsigned int reg_adc_rxdata; /* RX FIFO offset for DMA config */
|
|
+ bool has_reset;
|
|
+};
|
|
+
|
|
+static const struct sun50i_h616_codec_quirks sun50i_h616_codec_quirks = {
|
|
+ .regmap_config = &sun50i_h616_codec_regmap_config,
|
|
+ .codec = &sun50i_h616_codec_codec,
|
|
+ .create_card = sun50i_h616_codec_create_card,
|
|
+ .reg_dac_txdata = SUNXI_DAC_TXDATA,
|
|
+ .has_reset = true,
|
|
+};
|
|
+
|
|
+static const struct of_device_id sun50i_h616_codec_of_match[] = {
|
|
+ {
|
|
+ .compatible = "allwinner,sun50i-h616-codec",
|
|
+ .data = &sun50i_h616_codec_quirks,
|
|
+ },
|
|
+ {}};
|
|
+MODULE_DEVICE_TABLE(of, sun50i_h616_codec_of_match);
|
|
+
|
|
+static ssize_t show_audio_reg(struct device *dev,
|
|
+ struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ int count = 0, i = 0;
|
|
+ unsigned int reg_val;
|
|
+ unsigned int size = ARRAY_SIZE(reg_labels);
|
|
+
|
|
+ count += sprintf(buf, "dump audiocodec reg:\n");
|
|
+
|
|
+ while ((i < size) && (reg_labels[i].name != NULL))
|
|
+ {
|
|
+ regmap_read(codec_regmap_debug,
|
|
+ reg_labels[i].address, ®_val);
|
|
+ count += sprintf(buf + count, "%-20s [0x%03x]: 0x%-10x save_val:0x%x\n",
|
|
+ reg_labels[i].name, (reg_labels[i].address),
|
|
+ reg_val, reg_labels[i].value);
|
|
+ i++;
|
|
+ }
|
|
+
|
|
+ return count;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(audio_reg, 0644, show_audio_reg, NULL);
|
|
+
|
|
+static struct attribute *audio_debug_attrs[] = {
|
|
+ &dev_attr_audio_reg.attr,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+static struct attribute_group audio_debug_attr_group = {
|
|
+ .name = "audio_reg_debug",
|
|
+ .attrs = audio_debug_attrs,
|
|
+};
|
|
+
|
|
+static void sunxi_codec_init(struct sun50i_h616_codec *scodec)
|
|
+{
|
|
+ /* Disable DRC function for playback */
|
|
+ regmap_write(scodec->regmap, SUNXI_DAC_DAP_CTL, 0);
|
|
+
|
|
+ /* Enable HPF(high passed filter) */
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_DPC,
|
|
+ (0x1 << SUNXI_DAC_DPC_HPF_EN), (0x1 << SUNXI_DAC_DPC_HPF_EN));
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG,
|
|
+ (0x1f << SUNXI_LINEOUT_VOL),
|
|
+ (0x1a << SUNXI_LINEOUT_VOL));
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_DPC,
|
|
+ (0x3f << SUNXI_DAC_DPC_DVOL), (0 << SUNXI_DAC_DPC_DVOL));
|
|
+
|
|
+ /* Mixer to channel LINEOUT MUTE control init */
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG,
|
|
+ (0x1 << SUNXI_LMUTE), (0x1 << SUNXI_LMUTE));
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG,
|
|
+ (0x1 << SUNXI_RMUTE), (0x1 << SUNXI_RMUTE));
|
|
+
|
|
+ /* ramp func about */
|
|
+ if (0)
|
|
+ {
|
|
+ /* Not used the ramp func cause there is the MUTE to avoid pop noise */
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG,
|
|
+ (0x1 << SUNXI_RSWITCH), (0x1 << SUNXI_RSWITCH));
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG,
|
|
+ (0x1 << SUNXI_RAMPEN), (0x0 << SUNXI_RAMPEN));
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_RAMP_REG,
|
|
+ (0x7 << SUNXI_RAMP_STEP), (0x0 << SUNXI_RAMP_STEP));
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_RAMP_REG,
|
|
+ (0x1 << SUNXI_RDEN), (0x0 << SUNXI_RDEN));
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ /* If no MUTE to avoid pop, just use the ramp func to avoid it */
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG,
|
|
+ (0x1 << SUNXI_RSWITCH), (0x0 << SUNXI_RSWITCH));
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_DAC_REG,
|
|
+ (0x1 << SUNXI_RAMPEN), (0x1 << SUNXI_RAMPEN));
|
|
+
|
|
+ regmap_update_bits(scodec->regmap, SUNXI_DAC_AC_RAMP_REG,
|
|
+ (0x7 << SUNXI_RAMP_STEP), (0x1 << SUNXI_RAMP_STEP));
|
|
+ }
|
|
+}
|
|
+
|
|
+static int sun50i_h616_codec_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct snd_soc_card *card;
|
|
+ struct sun50i_h616_codec *scodec;
|
|
+ const struct sun50i_h616_codec_quirks *quirks;
|
|
+ struct resource *res;
|
|
+ void __iomem *base;
|
|
+ int ret;
|
|
+
|
|
+ scodec = devm_kzalloc(&pdev->dev, sizeof(struct sun50i_h616_codec), GFP_KERNEL);
|
|
+ if (!scodec)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ scodec->dev = &pdev->dev;
|
|
+
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
+ base = devm_ioremap_resource(&pdev->dev, res);
|
|
+ if (IS_ERR(base))
|
|
+ return PTR_ERR(base);
|
|
+
|
|
+ quirks = of_device_get_match_data(&pdev->dev);
|
|
+ if (quirks == NULL)
|
|
+ {
|
|
+ dev_err(&pdev->dev, "Failed to determine the quirks to use\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base,
|
|
+ quirks->regmap_config);
|
|
+ if (IS_ERR(scodec->regmap))
|
|
+ {
|
|
+ dev_err(&pdev->dev, "Failed to create our regmap\n");
|
|
+ return PTR_ERR(scodec->regmap);
|
|
+ }
|
|
+
|
|
+ /* Get the clocks from the DT */
|
|
+ scodec->clk_apb = devm_clk_get(&pdev->dev, "apb");
|
|
+ if (IS_ERR(scodec->clk_apb))
|
|
+ {
|
|
+ dev_err(&pdev->dev, "Failed to get the APB clock\n");
|
|
+ return PTR_ERR(scodec->clk_apb);
|
|
+ }
|
|
+
|
|
+ scodec->clk_module = devm_clk_get(&pdev->dev, "audio-codec-1x");
|
|
+ if (IS_ERR(scodec->clk_module))
|
|
+ {
|
|
+ dev_err(&pdev->dev, "Failed to get the codec module clock\n");
|
|
+ return PTR_ERR(scodec->clk_module);
|
|
+ }
|
|
+
|
|
+ if (quirks->has_reset)
|
|
+ {
|
|
+ scodec->rst = devm_reset_control_get_exclusive(&pdev->dev,
|
|
+ NULL);
|
|
+ if (IS_ERR(scodec->rst))
|
|
+ {
|
|
+ dev_err(&pdev->dev, "Failed to get reset control\n");
|
|
+ return PTR_ERR(scodec->rst);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa",
|
|
+ GPIOD_OUT_LOW);
|
|
+ if (IS_ERR(scodec->gpio_pa))
|
|
+ {
|
|
+ ret = PTR_ERR(scodec->gpio_pa);
|
|
+ if (ret != -EPROBE_DEFER)
|
|
+ dev_err(&pdev->dev, "Failed to get pa gpio: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Enable the bus clock */
|
|
+ if (clk_prepare_enable(scodec->clk_apb))
|
|
+ {
|
|
+ dev_err(&pdev->dev, "Failed to enable the APB clock\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* Deassert the reset control */
|
|
+ if (scodec->rst)
|
|
+ {
|
|
+ ret = reset_control_deassert(scodec->rst);
|
|
+ if (ret)
|
|
+ {
|
|
+ dev_err(&pdev->dev,
|
|
+ "Failed to deassert the reset control\n");
|
|
+ goto err_clk_disable;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* DMA configuration for TX FIFO */
|
|
+ scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata;
|
|
+ scodec->playback_dma_data.maxburst = 8;
|
|
+ scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
|
+
|
|
+ ret = devm_snd_soc_register_component(&pdev->dev, quirks->codec,
|
|
+ &sun50i_h616_codec_dai, 1);
|
|
+ if (ret)
|
|
+ {
|
|
+ dev_err(&pdev->dev, "Failed to register our codec\n");
|
|
+ goto err_assert_reset;
|
|
+ }
|
|
+
|
|
+ ret = devm_snd_soc_register_component(&pdev->dev,
|
|
+ &sun50i_h616_codec_component,
|
|
+ &dummy_cpu_dai, 1);
|
|
+ if (ret)
|
|
+ {
|
|
+ dev_err(&pdev->dev, "Failed to register our DAI\n");
|
|
+ goto err_assert_reset;
|
|
+ }
|
|
+
|
|
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
|
|
+ if (ret)
|
|
+ {
|
|
+ dev_err(&pdev->dev, "Failed to register against DMAEngine\n");
|
|
+ goto err_assert_reset;
|
|
+ }
|
|
+
|
|
+ card = quirks->create_card(&pdev->dev);
|
|
+ if (IS_ERR(card))
|
|
+ {
|
|
+ ret = PTR_ERR(card);
|
|
+ dev_err(&pdev->dev, "Failed to create our card\n");
|
|
+ goto err_assert_reset;
|
|
+ }
|
|
+
|
|
+ snd_soc_card_set_drvdata(card, scodec);
|
|
+
|
|
+ codec_regmap_debug = scodec->regmap;
|
|
+
|
|
+ ret = snd_soc_register_card(card);
|
|
+ if (ret)
|
|
+ {
|
|
+ dev_err(&pdev->dev, "Failed to register our card\n");
|
|
+ goto err_assert_reset;
|
|
+ }
|
|
+
|
|
+ ret = sysfs_create_group(&pdev->dev.kobj, &audio_debug_attr_group);
|
|
+ if (ret)
|
|
+ dev_warn(&pdev->dev, "failed to create attr group\n");
|
|
+
|
|
+ sunxi_codec_init(scodec);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_assert_reset:
|
|
+ if (scodec->rst)
|
|
+ reset_control_assert(scodec->rst);
|
|
+err_clk_disable:
|
|
+ clk_disable_unprepare(scodec->clk_apb);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int sun50i_h616_codec_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
|
|
+ struct sun50i_h616_codec *scodec = snd_soc_card_get_drvdata(card);
|
|
+
|
|
+ snd_soc_unregister_card(card);
|
|
+ if (scodec->rst)
|
|
+ reset_control_assert(scodec->rst);
|
|
+ clk_disable_unprepare(scodec->clk_apb);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct platform_driver sun50i_h616_codec_driver = {
|
|
+ .driver = {
|
|
+ .name = "sun50i-h616-codec",
|
|
+ .of_match_table = sun50i_h616_codec_of_match,
|
|
+ },
|
|
+ .probe = sun50i_h616_codec_probe,
|
|
+ .remove = sun50i_h616_codec_remove,
|
|
+};
|
|
+module_platform_driver(sun50i_h616_codec_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("Allwinner H616 codec driver");
|
|
+MODULE_AUTHOR("Emilio López <emilio@elopez.com.ar>");
|
|
+MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
|
|
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
|
|
+MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
|
|
+MODULE_AUTHOR("Leeboby <leeboby@aliyun.com>");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/sound/soc/sunxi_v2/Kconfig b/sound/soc/sunxi_v2/Kconfig
|
|
new file mode 100644
|
|
index 000000000000..37fc579ba9db
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/Kconfig
|
|
@@ -0,0 +1,48 @@
|
|
+# common
|
|
+config SND_SOC_SUNXI_MACH
|
|
+ tristate
|
|
+
|
|
+# ahub dam
|
|
+config SND_SOC_SUNXI_AHUB_DAM
|
|
+ tristate
|
|
+
|
|
+config SND_SOC_SUNXI_INTERNALCODEC
|
|
+ tristate
|
|
+
|
|
+config SND_SOC_SUNXI_SUN50IW9_CODEC
|
|
+ tristate
|
|
+
|
|
+# menu select
|
|
+menu "Allwinner SoC Audio support V2"
|
|
+ depends on ARCH_SUNXI
|
|
+
|
|
+# aaudio
|
|
+config SND_SOC_SUNXI_AAUDIO
|
|
+ tristate "Allwinner AAUDIO support"
|
|
+ select REGMAP_MMIO
|
|
+ select SND_SOC_GENERIC_DMAENGINE_PCM
|
|
+ select SND_SOC_SUNXI_MACH
|
|
+ select SND_SOC_SUNXI_INTERNALCODEC
|
|
+ select SND_SOC_SUNXI_SUN50IW9_CODEC
|
|
+ depends on ARCH_SUNXI
|
|
+ help
|
|
+ Select Y or M to support analog-audio Module in the Allwinner SoCs.
|
|
+
|
|
+# ahub
|
|
+config SND_SOC_SUNXI_AHUB
|
|
+ tristate "Allwinner AHUB Support"
|
|
+ select REGMAP_MMIO
|
|
+ select SND_SOC_GENERIC_DMAENGINE_PCM
|
|
+ select SND_SOC_SUNXI_MACH
|
|
+ select SND_SOC_SUNXI_AHUB_DAM
|
|
+ depends on ARCH_SUNXI
|
|
+ help
|
|
+ Select Y or M to support audio-hub Module in Allwinner SoCs.
|
|
+
|
|
+config SND_SOC_SUNXI_DEBUG
|
|
+ tristate "Components Debug"
|
|
+ depends on SND_SOC_SUNXI_COMPONENTS
|
|
+ help
|
|
+ Select Y or M to support debug components.
|
|
+
|
|
+endmenu
|
|
diff --git a/sound/soc/sunxi_v2/Makefile b/sound/soc/sunxi_v2/Makefile
|
|
new file mode 100644
|
|
index 000000000000..c7c2ef8f9fe9
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/Makefile
|
|
@@ -0,0 +1,11 @@
|
|
+# platform -> ahub
|
|
+snd_soc_sunxi_ahub_dam-objs += snd_sunxi_ahub_dam.o
|
|
+obj-$(CONFIG_SND_SOC_SUNXI_AHUB_DAM) += snd_soc_sunxi_ahub_dam.o
|
|
+
|
|
+snd_soc_sunxi_ahub-objs += snd_sunxi_ahub.o
|
|
+obj-$(CONFIG_SND_SOC_SUNXI_AHUB) += snd_soc_sunxi_ahub.o
|
|
+
|
|
+# common -> machine (note: Finally compile, save system startup time)
|
|
+snd_soc_sunxi_machine-objs += snd_sunxi_mach.o
|
|
+snd_soc_sunxi_machine-objs += snd_sunxi_mach_utils.o
|
|
+obj-$(CONFIG_SND_SOC_SUNXI_MACH) += snd_soc_sunxi_machine.o
|
|
diff --git a/sound/soc/sunxi_v2/drv_hdmi.h b/sound/soc/sunxi_v2/drv_hdmi.h
|
|
new file mode 100644
|
|
index 000000000000..2e05489b01e1
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/drv_hdmi.h
|
|
@@ -0,0 +1,63 @@
|
|
+/*
|
|
+ * Allwinner SoCs hdmi driver.
|
|
+ *
|
|
+ * Copyright (C) 2016 Allwinner.
|
|
+ *
|
|
+ * This file is licensed under the terms of the GNU General Public
|
|
+ * License version 2. This program is licensed "as is" without any
|
|
+ * warranty of any kind, whether express or implied.
|
|
+ */
|
|
+
|
|
+#ifndef __DRV_HDMI_H__
|
|
+#define __DRV_HDMI_H__
|
|
+
|
|
+typedef struct {
|
|
+ __u8 hw_intf; /* 0:iis 1:spdif 2:pcm */
|
|
+ __u16 fs_between; /* fs */
|
|
+ __u32 sample_rate; /*sample rate*/
|
|
+ __u8 clk_edge; /* 0:*/
|
|
+ __u8 ch0_en; /* 1 */
|
|
+ __u8 ch1_en; /* 0 */
|
|
+ __u8 ch2_en; /* 0 */
|
|
+ __u8 ch3_en; /* 0 */
|
|
+ __u8 word_length; /* 32 */
|
|
+ __u8 shift_ctl; /* 0 */
|
|
+ __u8 dir_ctl; /* 0 */
|
|
+ __u8 ws_pol;
|
|
+ __u8 just_pol;
|
|
+ __u8 channel_num;
|
|
+ __u8 data_raw;
|
|
+ __u8 sample_bit;
|
|
+ __u8 ca; /* channel allocation */
|
|
+} hdmi_audio_t;
|
|
+
|
|
+typedef struct {
|
|
+ __s32 (*hdmi_audio_enable)(__u8 mode, __u8 channel);
|
|
+ __s32 (*hdmi_set_audio_para)(hdmi_audio_t *audio_para);
|
|
+ __s32 (*hdmi_is_playback)(void);
|
|
+} __audio_hdmi_func;
|
|
+
|
|
+enum hdmi_hpd_status {
|
|
+ STATUE_CLOSE = 0,
|
|
+ STATUE_OPEN = 1,
|
|
+};
|
|
+
|
|
+void audio_set_hdmi_func(__audio_hdmi_func *hdmi_func);
|
|
+#if defined(CONFIG_SND_SUNXI_SOC_AUDIOHUB_INTERFACE)
|
|
+void audio_set_muti_hdmi_func(__audio_hdmi_func *hdmi_func);
|
|
+#endif
|
|
+
|
|
+/******************** SND_HDMI for sunxi_v2 begain ***************************/
|
|
+#if IS_ENABLED(CONFIG_HDMI2_DISP2_SUNXI)
|
|
+extern int snd_hdmi_get_func(__audio_hdmi_func *hdmi_func);
|
|
+#else
|
|
+static inline int snd_hdmi_get_func(__audio_hdmi_func *hdmi_func)
|
|
+{
|
|
+ pr_err("HDMI Audio API is disable\n");
|
|
+
|
|
+ return -1;
|
|
+}
|
|
+#endif
|
|
+/******************** SND_HDMI for sunxi_v2 end ******************************/
|
|
+
|
|
+#endif
|
|
diff --git a/sound/soc/sunxi_v2/snd_sunxi_ahub.c b/sound/soc/sunxi_v2/snd_sunxi_ahub.c
|
|
new file mode 100644
|
|
index 000000000000..8a1065e9183e
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/snd_sunxi_ahub.c
|
|
@@ -0,0 +1,1477 @@
|
|
+/*
|
|
+ * sound\soc\sunxi\snd_sunxi_ahub.c
|
|
+ * (C) Copyright 2021-2025
|
|
+ * AllWinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
+ * Dby <dby@allwinnertech.com>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of
|
|
+ * the License, or (at your option) any later version.
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/reset.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/pinctrl/consumer.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/of_address.h>
|
|
+#include <linux/of_gpio.h>
|
|
+#include <sound/soc.h>
|
|
+#include <sound/pcm_params.h>
|
|
+#include <sound/dmaengine_pcm.h>
|
|
+
|
|
+#include "snd_sunxi_log.h"
|
|
+#include "snd_sunxi_ahub.h"
|
|
+
|
|
+#define HLOG "AHUB"
|
|
+#define DRV_NAME "sunxi-snd-plat-ahub"
|
|
+
|
|
+static int sunxi_ahub_dai_startup(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai);
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int apb_num, tdm_num;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ apb_num = ahub_info->dts_info.apb_num;
|
|
+ tdm_num = ahub_info->dts_info.tdm_num;
|
|
+
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ snd_soc_dai_set_dma_data(dai, substream,
|
|
+ &ahub_info->playback_dma_param);
|
|
+ } else {
|
|
+ snd_soc_dai_set_dma_data(dai, substream,
|
|
+ &ahub_info->capture_dma_param);
|
|
+ }
|
|
+
|
|
+ /* APBIF & I2S of RST and GAT */
|
|
+ if (tdm_num > 3 || apb_num > 2) {
|
|
+ SND_LOG_ERR(HLOG, "unspport tdm num or apbif num\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_RST,
|
|
+ 0x1 << (APBIF_TXDIF0_RST - apb_num),
|
|
+ 0x1 << (APBIF_TXDIF0_RST - apb_num));
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_GAT,
|
|
+ 0x1 << (APBIF_TXDIF0_GAT - apb_num),
|
|
+ 0x1 << (APBIF_TXDIF0_GAT - apb_num));
|
|
+ } else {
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_RST,
|
|
+ 0x1 << (APBIF_RXDIF0_RST - apb_num),
|
|
+ 0x1 << (APBIF_RXDIF0_RST - apb_num));
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_GAT,
|
|
+ 0x1 << (APBIF_RXDIF0_GAT - apb_num),
|
|
+ 0x1 << (APBIF_RXDIF0_GAT - apb_num));
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dai_set_pll(struct snd_soc_dai *dai,
|
|
+ int pll_id, int source,
|
|
+ unsigned int freq_in, unsigned int freq_out)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai);
|
|
+ struct sunxi_ahub_clk_info *clk_info = NULL;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "stream -> %s, freq_in ->%u, freq_out ->%u\n",
|
|
+ pll_id ? "IN" : "OUT", freq_in, freq_out);
|
|
+
|
|
+ if (IS_ERR_OR_NULL(ahub_info)) {
|
|
+ SND_LOG_ERR(HLOG, "ahub_info is null.\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ clk_info = &ahub_info->clk_info;
|
|
+
|
|
+ if (freq_in > 24576000) {
|
|
+ //if (clk_set_parent(clk_info->clk_module, clk_info->clk_pllx4)) {
|
|
+ // SND_LOG_ERR(HLOG, "set parent of clk_module to pllx4 failed\n");
|
|
+ // return -EINVAL;
|
|
+ //}
|
|
+
|
|
+ if (clk_set_rate(clk_info->clk_pll, freq_in)) {
|
|
+ SND_LOG_ERR(HLOG, "freq : %u pllx4 clk unsupport\n", freq_in);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ } else {
|
|
+ //if (clk_set_parent(clk_info->clk_module, clk_info->clk_pll)) {
|
|
+ // SND_LOG_ERR(HLOG, "set parent of clk_module to pll failed\n");
|
|
+ // return -EINVAL;
|
|
+ //}
|
|
+ if (clk_set_rate(clk_info->clk_pll, freq_in)) {
|
|
+ SND_LOG_ERR(HLOG, "freq : %u pll clk unsupport\n", freq_in);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+ if (clk_set_rate(clk_info->clk_module, freq_out / 2)) {
|
|
+ SND_LOG_ERR(HLOG, "freq : %u module clk unsupport\n", freq_out);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ ahub_info->pllclk_freq = freq_in;
|
|
+ ahub_info->mclk_freq = freq_out;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
|
|
+ unsigned int freq, int dir)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai);
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int tdm_num;
|
|
+ unsigned int mclk_ratio, mclk_ratio_map;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (IS_ERR_OR_NULL(ahub_info)) {
|
|
+ SND_LOG_ERR(HLOG, "ahub_info is null.\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ tdm_num = ahub_info->dts_info.tdm_num;
|
|
+
|
|
+ if (freq == 0) {
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num),
|
|
+ 0x1 << I2S_CLKD_MCLK, 0x0 << I2S_CLKD_MCLK);
|
|
+ SND_LOG_DEBUG(HLOG, "mclk freq: 0\n");
|
|
+ return 0;
|
|
+ }
|
|
+ if (ahub_info->pllclk_freq == 0) {
|
|
+ SND_LOG_ERR(HLOG, "pllclk freq is invalid\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ mclk_ratio = ahub_info->pllclk_freq / freq;
|
|
+
|
|
+ switch (mclk_ratio) {
|
|
+ case 1:
|
|
+ mclk_ratio_map = 1;
|
|
+ break;
|
|
+ case 2:
|
|
+ mclk_ratio_map = 2;
|
|
+ break;
|
|
+ case 4:
|
|
+ mclk_ratio_map = 3;
|
|
+ break;
|
|
+ case 6:
|
|
+ mclk_ratio_map = 4;
|
|
+ break;
|
|
+ case 8:
|
|
+ mclk_ratio_map = 5;
|
|
+ break;
|
|
+ case 12:
|
|
+ mclk_ratio_map = 6;
|
|
+ break;
|
|
+ case 16:
|
|
+ mclk_ratio_map = 7;
|
|
+ break;
|
|
+ case 24:
|
|
+ mclk_ratio_map = 8;
|
|
+ break;
|
|
+ case 32:
|
|
+ mclk_ratio_map = 9;
|
|
+ break;
|
|
+ case 48:
|
|
+ mclk_ratio_map = 10;
|
|
+ break;
|
|
+ case 64:
|
|
+ mclk_ratio_map = 11;
|
|
+ break;
|
|
+ case 96:
|
|
+ mclk_ratio_map = 12;
|
|
+ break;
|
|
+ case 128:
|
|
+ mclk_ratio_map = 13;
|
|
+ break;
|
|
+ case 176:
|
|
+ mclk_ratio_map = 14;
|
|
+ break;
|
|
+ case 192:
|
|
+ mclk_ratio_map = 15;
|
|
+ break;
|
|
+ default:
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num),
|
|
+ 0x1 << I2S_CLKD_MCLK, 0x0 << I2S_CLKD_MCLK);
|
|
+ SND_LOG_ERR(HLOG, "mclk freq div unsupport\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num),
|
|
+ 0xf << I2S_CLKD_MCLKDIV,
|
|
+ mclk_ratio_map << I2S_CLKD_MCLKDIV);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num),
|
|
+ 0x1 << I2S_CLKD_MCLK, 0x1 << I2S_CLKD_MCLK);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dai_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai);
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int tdm_num;
|
|
+ unsigned int bclk_ratio;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (IS_ERR_OR_NULL(ahub_info)) {
|
|
+ SND_LOG_ERR(HLOG, "ahub_info is null.\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ tdm_num = ahub_info->dts_info.tdm_num;
|
|
+
|
|
+ /* ratio -> cpudai pllclk / pcm rate */
|
|
+ switch (ratio) {
|
|
+ case 1:
|
|
+ bclk_ratio = 1;
|
|
+ break;
|
|
+ case 2:
|
|
+ bclk_ratio = 2;
|
|
+ break;
|
|
+ case 4:
|
|
+ bclk_ratio = 3;
|
|
+ break;
|
|
+ case 6:
|
|
+ bclk_ratio = 4;
|
|
+ break;
|
|
+ case 8:
|
|
+ bclk_ratio = 5;
|
|
+ break;
|
|
+ case 12:
|
|
+ bclk_ratio = 6;
|
|
+ break;
|
|
+ case 16:
|
|
+ bclk_ratio = 7;
|
|
+ break;
|
|
+ case 24:
|
|
+ bclk_ratio = 8;
|
|
+ break;
|
|
+ case 32:
|
|
+ bclk_ratio = 9;
|
|
+ break;
|
|
+ case 48:
|
|
+ bclk_ratio = 10;
|
|
+ break;
|
|
+ case 64:
|
|
+ bclk_ratio = 11;
|
|
+ break;
|
|
+ case 96:
|
|
+ bclk_ratio = 12;
|
|
+ break;
|
|
+ case 128:
|
|
+ bclk_ratio = 13;
|
|
+ break;
|
|
+ case 176:
|
|
+ bclk_ratio = 14;
|
|
+ break;
|
|
+ case 192:
|
|
+ bclk_ratio = 15;
|
|
+ break;
|
|
+ default:
|
|
+ SND_LOG_ERR(HLOG, "bclk freq div unsupport\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CLKD(tdm_num),
|
|
+ 0xf << I2S_CLKD_BCLKDIV,
|
|
+ (bclk_ratio - 2) << I2S_CLKD_BCLKDIV);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai);
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int tdm_num, tx_pin, rx_pin;
|
|
+ unsigned int mode, offset;
|
|
+ unsigned int lrck_polarity, brck_polarity;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ ahub_info->fmt = fmt;
|
|
+
|
|
+ if (IS_ERR_OR_NULL(ahub_info)) {
|
|
+ SND_LOG_ERR(HLOG, "ahub_info is null.\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ tdm_num = ahub_info->dts_info.tdm_num;
|
|
+ tx_pin = ahub_info->dts_info.tx_pin;
|
|
+ rx_pin = ahub_info->dts_info.rx_pin;
|
|
+
|
|
+ /* set TDM format */
|
|
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
+ case SND_SOC_DAIFMT_I2S:
|
|
+ mode = 1;
|
|
+ offset = 1;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_RIGHT_J:
|
|
+ mode = 2;
|
|
+ offset = 0;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_LEFT_J:
|
|
+ mode = 1;
|
|
+ offset = 0;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_DSP_A:
|
|
+ mode = 0;
|
|
+ offset = 1;
|
|
+ /* L data MSB after FRM LRC (short frame) */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num),
|
|
+ 0x1 << I2S_FMT0_LRCK_WIDTH,
|
|
+ 0x0 << I2S_FMT0_LRCK_WIDTH);
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_DSP_B:
|
|
+ mode = 0;
|
|
+ offset = 0;
|
|
+ /* L data MSB during FRM LRC (long frame) */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num),
|
|
+ 0x1 << I2S_FMT0_LRCK_WIDTH,
|
|
+ 0x1 << I2S_FMT0_LRCK_WIDTH);
|
|
+ break;
|
|
+ default:
|
|
+ SND_LOG_ERR(HLOG, "format setting failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x3 << I2S_CTL_MODE, mode << I2S_CTL_MODE);
|
|
+ /* regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, tx_pin),
|
|
+ * 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET);
|
|
+ */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 0),
|
|
+ 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 1),
|
|
+ 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 2),
|
|
+ 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, 3),
|
|
+ 0x3 << I2S_OUT_OFFSET, offset << I2S_OUT_OFFSET);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_IN_SLOT(tdm_num),
|
|
+ 0x3 << I2S_IN_OFFSET, offset << I2S_IN_OFFSET);
|
|
+
|
|
+ /* set lrck & bclk polarity */
|
|
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
+ case SND_SOC_DAIFMT_NB_NF:
|
|
+ lrck_polarity = 0;
|
|
+ brck_polarity = 0;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_NB_IF:
|
|
+ lrck_polarity = 1;
|
|
+ brck_polarity = 0;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_IB_NF:
|
|
+ lrck_polarity = 0;
|
|
+ brck_polarity = 1;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_IB_IF:
|
|
+ lrck_polarity = 1;
|
|
+ brck_polarity = 1;
|
|
+ break;
|
|
+ default:
|
|
+ SND_LOG_ERR(HLOG, "invert clk setting failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ if (((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) ||
|
|
+ ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_B))
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num),
|
|
+ 0x1 << I2S_FMT0_LRCK_POLARITY,
|
|
+ (lrck_polarity^1) << I2S_FMT0_LRCK_POLARITY);
|
|
+ else
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num),
|
|
+ 0x1 << I2S_FMT0_LRCK_POLARITY,
|
|
+ lrck_polarity << I2S_FMT0_LRCK_POLARITY);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num),
|
|
+ 0x1 << I2S_FMT0_BCLK_POLARITY,
|
|
+ brck_polarity << I2S_FMT0_BCLK_POLARITY);
|
|
+
|
|
+ /* set master/slave */
|
|
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
+ case SND_SOC_DAIFMT_CBM_CFM:
|
|
+ /* lrck & bclk dir output */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << I2S_CTL_CLK_OUT, 0x0 << I2S_CTL_CLK_OUT);
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_CBS_CFS:
|
|
+ /* lrck & bclk dir input */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << I2S_CTL_CLK_OUT, 0x1 << I2S_CTL_CLK_OUT);
|
|
+ break;
|
|
+ default:
|
|
+ SND_LOG_ERR(HLOG, "unknown master/slave format\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dai_set_tdm_slot(struct snd_soc_dai *dai,
|
|
+ unsigned int tx_mask, unsigned int rx_mask,
|
|
+ int slots, int slot_width)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai);
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int tdm_num, tx_pin, rx_pin;
|
|
+ unsigned int slot_width_map, lrck_width_map;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (IS_ERR_OR_NULL(ahub_info)) {
|
|
+ SND_LOG_ERR(HLOG, "ahub_info is null\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ tdm_num = ahub_info->dts_info.tdm_num;
|
|
+ tx_pin = ahub_info->dts_info.tx_pin;
|
|
+ rx_pin = ahub_info->dts_info.rx_pin;
|
|
+
|
|
+ switch (slot_width) {
|
|
+ case 8:
|
|
+ slot_width_map = 1;
|
|
+ break;
|
|
+ case 12:
|
|
+ slot_width_map = 2;
|
|
+ break;
|
|
+ case 16:
|
|
+ slot_width_map = 3;
|
|
+ break;
|
|
+ case 20:
|
|
+ slot_width_map = 4;
|
|
+ break;
|
|
+ case 24:
|
|
+ slot_width_map = 5;
|
|
+ break;
|
|
+ case 28:
|
|
+ slot_width_map = 6;
|
|
+ break;
|
|
+ case 32:
|
|
+ slot_width_map = 7;
|
|
+ break;
|
|
+ default:
|
|
+ SND_LOG_ERR(HLOG, "unknown slot width\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num),
|
|
+ 0x7 << I2S_FMT0_SW, slot_width_map << I2S_FMT0_SW);
|
|
+
|
|
+ /* bclk num of per channel
|
|
+ * I2S/RIGHT_J/LEFT_J -> lrck long total is lrck_width_map * 2
|
|
+ * DSP_A/DAP_B -> lrck long total is lrck_width_map * 1
|
|
+ */
|
|
+ switch (ahub_info->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
+ case SND_SOC_DAIFMT_I2S:
|
|
+ case SND_SOC_DAIFMT_RIGHT_J:
|
|
+ case SND_SOC_DAIFMT_LEFT_J:
|
|
+ slots /= 2;
|
|
+ break;
|
|
+ case SND_SOC_DAIFMT_DSP_A:
|
|
+ case SND_SOC_DAIFMT_DSP_B:
|
|
+ break;
|
|
+ default:
|
|
+ SND_LOG_ERR(HLOG, "unsupoort format\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ lrck_width_map = slots * slot_width - 1;
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num),
|
|
+ 0x3ff << I2S_FMT0_LRCK_PERIOD,
|
|
+ lrck_width_map << I2S_FMT0_LRCK_PERIOD);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dai_hw_params(struct snd_pcm_substream *substream,
|
|
+ struct snd_pcm_hw_params *params,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai);
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int apb_num, tdm_num, tx_pin, rx_pin;
|
|
+ unsigned int channels;
|
|
+ unsigned int channels_en[16] = {
|
|
+ 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff,
|
|
+ 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff
|
|
+ };
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (IS_ERR_OR_NULL(ahub_info)) {
|
|
+ SND_LOG_ERR(HLOG, "ahub_info is null.\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ apb_num = ahub_info->dts_info.apb_num;
|
|
+ tdm_num = ahub_info->dts_info.tdm_num;
|
|
+ tx_pin = ahub_info->dts_info.tx_pin;
|
|
+ rx_pin = ahub_info->dts_info.rx_pin;
|
|
+
|
|
+ /* configure DMA */
|
|
+ switch (params_physical_width(params)) {
|
|
+ case 16:
|
|
+ ahub_info->playback_dma_param.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
|
+ ahub_info->capture_dma_param.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
|
+ break;
|
|
+ case 24:
|
|
+ case 32:
|
|
+ ahub_info->playback_dma_param.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
+ ahub_info->capture_dma_param.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
+ break;
|
|
+ default:
|
|
+ dev_err(dai->dev, "Unsupported physical sample width: %d\n",
|
|
+ params_physical_width(params));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* set bits */
|
|
+ switch (params_format(params)) {
|
|
+ case SNDRV_PCM_FORMAT_S16_LE:
|
|
+ /* apbifn bits */
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ regmap_update_bits(regmap,
|
|
+ SUNXI_AHUB_APBIF_TX_CTL(apb_num),
|
|
+ 0x7 << APBIF_TX_WS,
|
|
+ 0x3 << APBIF_TX_WS);
|
|
+ regmap_update_bits(regmap,
|
|
+ SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num),
|
|
+ 0x1 << APBIF_TX_TXIM,
|
|
+ 0x1 << APBIF_TX_TXIM);
|
|
+ } else {
|
|
+ regmap_update_bits(regmap,
|
|
+ SUNXI_AHUB_APBIF_RX_CTL(apb_num),
|
|
+ 0x7 << APBIF_RX_WS,
|
|
+ 0x3 << APBIF_RX_WS);
|
|
+ regmap_update_bits(regmap,
|
|
+ SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num),
|
|
+ 0x3 << APBIF_RX_RXOM,
|
|
+ 0x1 << APBIF_RX_RXOM);
|
|
+ }
|
|
+
|
|
+ regmap_update_bits(regmap,
|
|
+ SUNXI_AHUB_I2S_FMT0(tdm_num),
|
|
+ 0x7 << I2S_FMT0_SR,
|
|
+ 0x3 << I2S_FMT0_SR);
|
|
+ break;
|
|
+ case SNDRV_PCM_FORMAT_S20_3LE:
|
|
+ case SNDRV_PCM_FORMAT_S24_LE:
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num),
|
|
+ 0x7 << APBIF_TX_WS, 0x5 << APBIF_TX_WS);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num),
|
|
+ 0x1 << APBIF_TX_TXIM, 0x1 << APBIF_TX_TXIM);
|
|
+ } else {
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num),
|
|
+ 0x7 << APBIF_RX_WS, 0x5 << APBIF_RX_WS);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num),
|
|
+ 0x3 << APBIF_RX_RXOM, 0x1 << APBIF_RX_RXOM);
|
|
+ }
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num),
|
|
+ 0x7 << I2S_FMT0_SR, 0x5 << I2S_FMT0_SR);
|
|
+ break;
|
|
+ case SNDRV_PCM_FORMAT_S32_LE:
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num),
|
|
+ 0x7 << APBIF_TX_WS, 0x7 << APBIF_TX_WS);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num),
|
|
+ 0x1 << APBIF_TX_TXIM, 0x1 << APBIF_TX_TXIM);
|
|
+ } else {
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num),
|
|
+ 0x7 << APBIF_RX_WS, 0x7 << APBIF_RX_WS);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num),
|
|
+ 0x3 << APBIF_RX_RXOM, 0x1 << APBIF_RX_RXOM);
|
|
+ }
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_FMT0(tdm_num),
|
|
+ 0x7 << I2S_FMT0_SR, 0x7 << I2S_FMT0_SR);
|
|
+ break;
|
|
+ default:
|
|
+ SND_LOG_ERR(HLOG, "unrecognized format bits\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* set channels */
|
|
+ channels = params_channels(params);
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ /* apbifn channels */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num),
|
|
+ 0xf << APBIF_TX_CHAN_NUM,
|
|
+ (channels - 1) << APBIF_TX_CHAN_NUM);
|
|
+ /* tdmn channels */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CHCFG(tdm_num),
|
|
+ 0xf << I2S_CHCFG_TX_CHANNUM,
|
|
+ (channels - 1) << I2S_CHCFG_TX_CHANNUM);
|
|
+
|
|
+ regmap_update_bits(regmap,
|
|
+ SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, tx_pin),
|
|
+ 0xf << I2S_OUT_SLOT_NUM,
|
|
+ (channels - 1) << I2S_OUT_SLOT_NUM);
|
|
+ regmap_update_bits(regmap,
|
|
+ SUNXI_AHUB_I2S_OUT_SLOT(tdm_num, tx_pin),
|
|
+ 0xffff << I2S_OUT_SLOT_EN,
|
|
+ channels_en[channels - 1] << I2S_OUT_SLOT_EN);
|
|
+ } else {
|
|
+ /* apbifn channels */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num),
|
|
+ 0xf << APBIF_RX_CHAN_NUM,
|
|
+ (channels - 1) << APBIF_RX_CHAN_NUM);
|
|
+ /* tdmn channels */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CHCFG(tdm_num),
|
|
+ 0xf << I2S_CHCFG_RX_CHANNUM,
|
|
+ (channels - 1) << I2S_CHCFG_RX_CHANNUM);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_IN_SLOT(tdm_num),
|
|
+ 0xf << I2S_IN_SLOT_NUM,
|
|
+ (channels - 1) << I2S_IN_SLOT_NUM);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void sunxi_ahub_dai_tx_route(struct sunxi_ahub_info *ahub_info,
|
|
+ bool enable)
|
|
+{
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int tdm_num, tx_pin;
|
|
+ unsigned int apb_num;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "%s\n", enable ? "on" : "off");
|
|
+
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ tdm_num = ahub_info->dts_info.tdm_num;
|
|
+ tx_pin = ahub_info->dts_info.tx_pin;
|
|
+ apb_num = ahub_info->dts_info.apb_num;
|
|
+
|
|
+ if (enable)
|
|
+ goto tx_route_enable;
|
|
+ else
|
|
+ goto tx_route_disable;
|
|
+
|
|
+tx_route_enable:
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << (I2S_CTL_SDO0_EN + tx_pin),
|
|
+ 0x1 << (I2S_CTL_SDO0_EN + tx_pin));
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << I2S_CTL_TXEN, 0x1 << I2S_CTL_TXEN);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << I2S_CTL_OUT_MUTE, 0x0 << I2S_CTL_OUT_MUTE);
|
|
+ /* start apbif tx */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num),
|
|
+ 0x1 << APBIF_TX_START, 0x1 << APBIF_TX_START);
|
|
+ /* enable tx drq */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_IRQ_CTL(apb_num),
|
|
+ 0x1 << APBIF_TX_DRQ, 0x1 << APBIF_TX_DRQ);
|
|
+ return;
|
|
+
|
|
+tx_route_disable:
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << I2S_CTL_OUT_MUTE, 0x1 << I2S_CTL_OUT_MUTE);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << I2S_CTL_TXEN, 0x0 << I2S_CTL_TXEN);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << (I2S_CTL_SDO0_EN + tx_pin),
|
|
+ 0x0 << (I2S_CTL_SDO0_EN + tx_pin));
|
|
+ /* stop apbif tx */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_CTL(apb_num),
|
|
+ 0x1 << APBIF_TX_START, 0x0 << APBIF_TX_START);
|
|
+ /* disable tx drq */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TX_IRQ_CTL(apb_num),
|
|
+ 0x1 << APBIF_TX_DRQ, 0x0 << APBIF_TX_DRQ);
|
|
+ return;
|
|
+}
|
|
+
|
|
+static void sunxi_ahub_dai_rx_route(struct sunxi_ahub_info *ahub_info,
|
|
+ bool enable)
|
|
+{
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int tdm_num, rx_pin;
|
|
+ unsigned int apb_num;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "%s\n", enable ? "on" : "off");
|
|
+
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ tdm_num = ahub_info->dts_info.tdm_num;
|
|
+ rx_pin = ahub_info->dts_info.rx_pin;
|
|
+ apb_num = ahub_info->dts_info.apb_num;
|
|
+
|
|
+ if (enable)
|
|
+ goto rx_route_enable;
|
|
+ else
|
|
+ goto rx_route_disable;
|
|
+
|
|
+rx_route_enable:
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << (I2S_CTL_SDI0_EN + rx_pin),
|
|
+ 0x1 << (I2S_CTL_SDI0_EN + rx_pin));
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << I2S_CTL_RXEN, 0x1 << I2S_CTL_RXEN);
|
|
+ /* start apbif rx */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num),
|
|
+ 0x1 << APBIF_RX_START, 0x1 << APBIF_RX_START);
|
|
+ /* enable rx drq */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_IRQ_CTL(apb_num),
|
|
+ 0x1 << APBIF_RX_DRQ, 0x1 << APBIF_RX_DRQ);
|
|
+ return;
|
|
+
|
|
+rx_route_disable:
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << I2S_CTL_RXEN, 0x0 << I2S_CTL_RXEN);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << (I2S_CTL_SDI0_EN + rx_pin),
|
|
+ 0x0 << (I2S_CTL_SDI0_EN + rx_pin));
|
|
+ /* stop apbif rx */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_CTL(apb_num),
|
|
+ 0x1 << APBIF_RX_START, 0x0 << APBIF_RX_START);
|
|
+ /* disable rx drq */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RX_IRQ_CTL(apb_num),
|
|
+ 0x1 << APBIF_RX_DRQ, 0x0 << APBIF_RX_DRQ);
|
|
+ return;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dai_trigger(struct snd_pcm_substream *substream,
|
|
+ int cmd,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai);
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (IS_ERR_OR_NULL(ahub_info)) {
|
|
+ SND_LOG_ERR(HLOG, "ahub_info is null.\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ switch (cmd) {
|
|
+ case SNDRV_PCM_TRIGGER_START:
|
|
+ case SNDRV_PCM_TRIGGER_RESUME:
|
|
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ sunxi_ahub_dai_tx_route(ahub_info, true);
|
|
+ } else {
|
|
+ sunxi_ahub_dai_rx_route(ahub_info, true);
|
|
+ }
|
|
+ break;
|
|
+ case SNDRV_PCM_TRIGGER_STOP:
|
|
+ case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ sunxi_ahub_dai_tx_route(ahub_info, false);
|
|
+ } else {
|
|
+ sunxi_ahub_dai_rx_route(ahub_info, false);
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dai_prepare(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai);
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int apb_num;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (IS_ERR_OR_NULL(ahub_info)) {
|
|
+ SND_LOG_ERR(HLOG, "ahub_info is null.\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ apb_num = ahub_info->dts_info.apb_num;
|
|
+
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ /* clear txfifo */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num),
|
|
+ 0x1 << APBIF_TX_FTX, 0x1 << APBIF_TX_FTX);
|
|
+ /* clear tx o/u irq */
|
|
+ regmap_write(regmap, SUNXI_AHUB_APBIF_TX_IRQ_STA(apb_num),
|
|
+ (0x1 << APBIF_TX_OV_PEND) | (0x1 << APBIF_TX_EM_PEND));
|
|
+ /* clear tx fifo cnt */
|
|
+ regmap_write(regmap, SUNXI_AHUB_APBIF_TXFIFO_CNT(apb_num), 0);
|
|
+ } else {
|
|
+ /* clear rxfifo */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num),
|
|
+ 0x1 << APBIF_RX_FRX, 0x1 << APBIF_RX_FRX);
|
|
+ /* clear rx o/u irq */
|
|
+ regmap_write(regmap, SUNXI_AHUB_APBIF_RX_IRQ_STA(apb_num),
|
|
+ (0x1 << APBIF_RX_UV_PEND) | (0x1 << APBIF_RX_AV_PEND));
|
|
+ /* clear rx fifo cnt */
|
|
+ regmap_write(regmap, SUNXI_AHUB_APBIF_RXFIFO_CNT(apb_num), 0);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void sunxi_ahub_dai_shutdown(struct snd_pcm_substream *substream,
|
|
+ struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai);
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int apb_num, tdm_num;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ apb_num = ahub_info->dts_info.apb_num;
|
|
+ tdm_num = ahub_info->dts_info.tdm_num;
|
|
+
|
|
+ /* APBIF & I2S of RST and GAT */
|
|
+ if (tdm_num > 3 || apb_num > 2) {
|
|
+ SND_LOG_ERR(HLOG, "unspport tdm num or apbif num\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_RST,
|
|
+ 0x1 << (APBIF_TXDIF0_RST - apb_num),
|
|
+ 0x0 << (APBIF_TXDIF0_RST - apb_num));
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_GAT,
|
|
+ 0x1 << (APBIF_TXDIF0_GAT - apb_num),
|
|
+ 0x0 << (APBIF_TXDIF0_GAT - apb_num));
|
|
+ } else {
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_RST,
|
|
+ 0x1 << (APBIF_RXDIF0_RST - apb_num),
|
|
+ 0x0 << (APBIF_RXDIF0_RST - apb_num));
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_GAT,
|
|
+ 0x1 << (APBIF_RXDIF0_GAT - apb_num),
|
|
+ 0x0 << (APBIF_RXDIF0_GAT - apb_num));
|
|
+ }
|
|
+}
|
|
+
|
|
+static const struct snd_soc_dai_ops sunxi_ahub_dai_ops = {
|
|
+ /* call by machine */
|
|
+ .set_pll = sunxi_ahub_dai_set_pll, // set pllclk
|
|
+ .set_sysclk = sunxi_ahub_dai_set_sysclk, // set mclk
|
|
+ .set_bclk_ratio = sunxi_ahub_dai_set_bclk_ratio,// set bclk freq
|
|
+ .set_tdm_slot = sunxi_ahub_dai_set_tdm_slot, // set slot num and width
|
|
+ .set_fmt = sunxi_ahub_dai_set_fmt, // set tdm fmt
|
|
+ /* call by asoc */
|
|
+ .startup = sunxi_ahub_dai_startup,
|
|
+ .hw_params = sunxi_ahub_dai_hw_params, // set hardware params
|
|
+ .prepare = sunxi_ahub_dai_prepare, // clean irq and fifo
|
|
+ .trigger = sunxi_ahub_dai_trigger, // set drq
|
|
+ .shutdown = sunxi_ahub_dai_shutdown,
|
|
+};
|
|
+
|
|
+static void snd_soc_sunxi_ahub_init(struct sunxi_ahub_info *ahub_info)
|
|
+{
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int apb_num, tdm_num, tx_pin, rx_pin;
|
|
+ unsigned int reg_val = 0;
|
|
+ unsigned int rx_pin_map = 0;
|
|
+ unsigned int tdm_to_apb = 0;
|
|
+ unsigned int apb_to_tdm = 0;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ apb_num = ahub_info->dts_info.apb_num;
|
|
+ tdm_num = ahub_info->dts_info.tdm_num;
|
|
+ tx_pin = ahub_info->dts_info.tx_pin;
|
|
+ rx_pin = ahub_info->dts_info.rx_pin;
|
|
+
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << I2S_CTL_GEN, 0x1 << I2S_CTL_GEN);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_RST,
|
|
+ 0x1 << (I2S0_RST - tdm_num),
|
|
+ 0x1 << (I2S0_RST - tdm_num));
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_GAT,
|
|
+ 0x1 << (I2S0_GAT - tdm_num),
|
|
+ 0x1 << (I2S0_GAT - tdm_num));
|
|
+
|
|
+ /* tdm tx channels map */
|
|
+ regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP0(tdm_num, tx_pin), 0x76543210);
|
|
+ regmap_write(regmap, SUNXI_AHUB_I2S_OUT_CHMAP1(tdm_num, tx_pin), 0xFEDCBA98);
|
|
+
|
|
+ /* tdm rx channels map */
|
|
+ rx_pin_map = (rx_pin << 4) | (rx_pin << 12) | (rx_pin << 20) | (rx_pin << 28);
|
|
+ reg_val = 0x03020100 | rx_pin_map;
|
|
+ regmap_write(regmap, SUNXI_AHUB_I2S_IN_CHMAP0(tdm_num), reg_val);
|
|
+ reg_val = 0x07060504 | rx_pin_map;
|
|
+ regmap_write(regmap, SUNXI_AHUB_I2S_IN_CHMAP1(tdm_num), reg_val);
|
|
+ reg_val = 0x0B0A0908 | rx_pin_map;
|
|
+ regmap_write(regmap, SUNXI_AHUB_I2S_IN_CHMAP2(tdm_num), reg_val);
|
|
+ reg_val = 0x0F0E0D0C | rx_pin_map;
|
|
+ regmap_write(regmap, SUNXI_AHUB_I2S_IN_CHMAP3(tdm_num), reg_val);
|
|
+
|
|
+ /* tdm tx & rx data fmt
|
|
+ * 1. MSB first
|
|
+ * 2. transfer 0 after each sample in each slot
|
|
+ * 3. linear PCM
|
|
+ */
|
|
+ regmap_write(regmap, SUNXI_AHUB_I2S_FMT1(tdm_num), 0x30);
|
|
+
|
|
+ /* apbif tx & rx data fmt
|
|
+ * 1. MSB first
|
|
+ * 2. trigger level tx -> 0x20, rx -> 0x40
|
|
+ */
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num),
|
|
+ 0x1 << APBIF_TX_TXIM, 0x0 << APBIF_TX_TXIM);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_TXFIFO_CTL(apb_num),
|
|
+ 0x3f << APBIF_TX_LEVEL, 0x20 << APBIF_TX_LEVEL);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num),
|
|
+ 0x3 << APBIF_RX_RXOM, 0x0 << APBIF_RX_RXOM);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_APBIF_RXFIFO_CTL(apb_num),
|
|
+ 0x7f << APBIF_RX_LEVEL, 0x40 << APBIF_RX_LEVEL);
|
|
+
|
|
+ /* apbif <-> tdm */
|
|
+ switch (tdm_num)
|
|
+ {
|
|
+ case 0:
|
|
+ tdm_to_apb = APBIF_RX_I2S0_TXDIF;
|
|
+ break;
|
|
+ case 1:
|
|
+ tdm_to_apb = APBIF_RX_I2S1_TXDIF;
|
|
+ break;
|
|
+ case 2:
|
|
+ tdm_to_apb = APBIF_RX_I2S2_TXDIF;
|
|
+ break;
|
|
+ case 3:
|
|
+ tdm_to_apb = APBIF_RX_I2S3_TXDIF;
|
|
+ break;
|
|
+ default:
|
|
+ SND_LOG_ERR(HLOG, "unspport tdm num\n");
|
|
+ return;
|
|
+ }
|
|
+ regmap_write(regmap, SUNXI_AHUB_APBIF_RXFIFO_CONT(apb_num), 0x1 << tdm_to_apb);
|
|
+
|
|
+ switch (apb_num)
|
|
+ {
|
|
+ case 0:
|
|
+ apb_to_tdm = I2S_RX_APBIF_TXDIF0;
|
|
+ break;
|
|
+ case 1:
|
|
+ apb_to_tdm = I2S_RX_APBIF_TXDIF1;
|
|
+ break;
|
|
+ case 2:
|
|
+ apb_to_tdm = I2S_RX_APBIF_TXDIF2;
|
|
+ break;
|
|
+ default:
|
|
+ SND_LOG_ERR(HLOG, "unspport apb num\n");
|
|
+ return;
|
|
+ }
|
|
+ regmap_write(regmap, SUNXI_AHUB_I2S_RXCONT(tdm_num), 0x1 << apb_to_tdm);
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dai_probe(struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai);
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ /* pcm_new will using the dma_param about the cma and fifo params. */
|
|
+ snd_soc_dai_init_dma_data(dai,
|
|
+ &ahub_info->playback_dma_param,
|
|
+ &ahub_info->capture_dma_param);
|
|
+
|
|
+ snd_soc_sunxi_ahub_init(ahub_info);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dai_remove(struct snd_soc_dai *dai)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_dai_get_drvdata(dai);
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int tdm_num;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ tdm_num = ahub_info->dts_info.tdm_num;
|
|
+
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_I2S_CTL(tdm_num),
|
|
+ 0x1 << I2S_CTL_GEN, 0x0 << I2S_CTL_GEN);
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_RST,
|
|
+ 0x1 << (I2S0_RST - tdm_num),
|
|
+ 0x0 << (I2S0_RST - tdm_num));
|
|
+ regmap_update_bits(regmap, SUNXI_AHUB_GAT,
|
|
+ 0x1 << (I2S0_GAT - tdm_num),
|
|
+ 0x0 << (I2S0_GAT - tdm_num));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct snd_soc_dai_driver sunxi_ahub_dai = {
|
|
+ .name = "ahub_plat",
|
|
+ .probe = sunxi_ahub_dai_probe,
|
|
+ .remove = sunxi_ahub_dai_remove,
|
|
+ .playback = {
|
|
+ .stream_name = "Playback",
|
|
+ .channels_min = 1,
|
|
+ .channels_max = 2,
|
|
+ .rates = SNDRV_PCM_RATE_8000_192000
|
|
+ | SNDRV_PCM_RATE_KNOT,
|
|
+ .formats = SNDRV_PCM_FMTBIT_S16_LE
|
|
+ | SNDRV_PCM_FMTBIT_S24_LE
|
|
+ | SNDRV_PCM_FMTBIT_S32_LE,
|
|
+ },
|
|
+ .capture = {
|
|
+ .stream_name = "Capture",
|
|
+ .channels_min = 1,
|
|
+ .channels_max = 2,
|
|
+ .rates = SNDRV_PCM_RATE_8000_192000
|
|
+ | SNDRV_PCM_RATE_KNOT,
|
|
+ .formats = SNDRV_PCM_FMTBIT_S16_LE
|
|
+ | SNDRV_PCM_FMTBIT_S24_LE
|
|
+ | SNDRV_PCM_FMTBIT_S32_LE,
|
|
+ },
|
|
+ .ops = &sunxi_ahub_dai_ops,
|
|
+};
|
|
+
|
|
+static int sunxi_ahub_probe(struct snd_soc_component *component)
|
|
+{
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_suspend(struct snd_soc_component *component)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_component_get_drvdata(component);
|
|
+ struct regmap *regmap = NULL;
|
|
+ unsigned int apb_num, tdm_num;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ regmap = ahub_info->mem_info.regmap;
|
|
+ apb_num = ahub_info->dts_info.apb_num;
|
|
+ tdm_num = ahub_info->dts_info.tdm_num;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_resume(struct snd_soc_component *component)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_component_get_drvdata(component);
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ snd_soc_sunxi_ahub_init(ahub_info);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int sunxi_loopback_debug_get(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ unsigned int reg_val;
|
|
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_component_get_drvdata(component);
|
|
+ struct sunxi_ahub_mem_info *mem_info = &ahub_info->mem_info;
|
|
+ struct sunxi_ahub_dts_info *dts_info = &ahub_info->dts_info;
|
|
+
|
|
+ regmap_read(mem_info->regmap, SUNXI_AHUB_I2S_CTL(dts_info->tdm_num), ®_val);
|
|
+ ucontrol->value.integer.value[0] = ((reg_val & (1 << I2S_CTL_LOOP0)) ? 1 : 0);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int sunxi_loopback_debug_set(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
|
+ struct sunxi_ahub_info *ahub_info = snd_soc_component_get_drvdata(component);
|
|
+ struct sunxi_ahub_mem_info *mem_info = &ahub_info->mem_info;
|
|
+ struct sunxi_ahub_dts_info *dts_info = &ahub_info->dts_info;
|
|
+
|
|
+ switch (ucontrol->value.integer.value[0]) {
|
|
+ case 0:
|
|
+ regmap_update_bits(mem_info->regmap,
|
|
+ SUNXI_AHUB_I2S_CTL(dts_info->tdm_num),
|
|
+ 1 << I2S_CTL_LOOP0, 0 << I2S_CTL_LOOP0);
|
|
+ break;
|
|
+ case 1:
|
|
+ regmap_update_bits(mem_info->regmap,
|
|
+ SUNXI_AHUB_I2S_CTL(dts_info->tdm_num),
|
|
+ 1 << I2S_CTL_LOOP0, 1 << I2S_CTL_LOOP0);
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new sunxi_ahub_controls[] = {
|
|
+ SOC_SINGLE_EXT("loopback debug", SND_SOC_NOPM, 0, 1, 0,
|
|
+ sunxi_loopback_debug_get, sunxi_loopback_debug_set),
|
|
+};
|
|
+
|
|
+static struct snd_soc_component_driver sunxi_ahub_component = {
|
|
+ .name = DRV_NAME,
|
|
+ .probe = sunxi_ahub_probe,
|
|
+ .suspend = sunxi_ahub_suspend,
|
|
+ .resume = sunxi_ahub_resume,
|
|
+ .controls = sunxi_ahub_controls,
|
|
+ .num_controls = ARRAY_SIZE(sunxi_ahub_controls),
|
|
+};
|
|
+
|
|
+/*******************************************************************************
|
|
+ * for kernel source
|
|
+ ******************************************************************************/
|
|
+static int snd_soc_sunxi_ahub_pin_init(struct platform_device *pdev,
|
|
+ struct device_node *np,
|
|
+ struct sunxi_ahub_pinctl_info *pin_info)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (of_property_read_bool(np, "pinctrl_used")) {
|
|
+ pin_info->pinctrl_used = 1;
|
|
+ } else {
|
|
+ pin_info->pinctrl_used = 0;
|
|
+ SND_LOG_DEBUG(HLOG, "unused pinctrl\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ pin_info->pinctrl = devm_pinctrl_get(&pdev->dev);
|
|
+ if (IS_ERR_OR_NULL(pin_info->pinctrl)) {
|
|
+ SND_LOG_ERR(HLOG, "pinctrl get failed\n");
|
|
+ ret = -EINVAL;
|
|
+ return ret;
|
|
+ }
|
|
+ pin_info->pinstate = pinctrl_lookup_state(pin_info->pinctrl,
|
|
+ PINCTRL_STATE_DEFAULT);
|
|
+ if (IS_ERR_OR_NULL(pin_info->pinstate)) {
|
|
+ SND_LOG_ERR(HLOG, "pinctrl default state get fail\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_loopup_pinstate;
|
|
+ }
|
|
+ pin_info->pinstate_sleep = pinctrl_lookup_state(pin_info->pinctrl,
|
|
+ PINCTRL_STATE_SLEEP);
|
|
+ if (IS_ERR_OR_NULL(pin_info->pinstate_sleep)) {
|
|
+ SND_LOG_ERR(HLOG, "pinctrl sleep state get failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_loopup_pin_sleep;
|
|
+ }
|
|
+ ret = pinctrl_select_state(pin_info->pinctrl, pin_info->pinstate);
|
|
+ if (ret < 0) {
|
|
+ SND_LOG_ERR(HLOG, "daudio set pinctrl default state fail\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_pinctrl_select_default;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_pinctrl_select_default:
|
|
+err_loopup_pin_sleep:
|
|
+err_loopup_pinstate:
|
|
+ devm_pinctrl_put(pin_info->pinctrl);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int snd_soc_sunxi_ahub_dts_params_init(struct platform_device *pdev,
|
|
+ struct device_node *np,
|
|
+ struct sunxi_ahub_dts_info *dts_info)
|
|
+{
|
|
+ int ret = 0;
|
|
+ unsigned int temp_val = 0;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ /* get tdm fmt of apb_num & tdm_num & tx/rx_pin */
|
|
+ ret = of_property_read_u32(np, "apb_num", &temp_val);
|
|
+ if (ret < 0) {
|
|
+ SND_LOG_WARN(HLOG, "apb_num config missing\n");
|
|
+ dts_info->apb_num = 0;
|
|
+ } else {
|
|
+ if (temp_val > 2) { /* APBIFn (n = 0~2) */
|
|
+ dts_info->apb_num = 0;
|
|
+ SND_LOG_WARN(HLOG, "apb_num config invalid\n");
|
|
+ } else {
|
|
+ dts_info->apb_num = temp_val;
|
|
+ }
|
|
+ }
|
|
+ ret = of_property_read_u32(np, "tdm_num", &temp_val);
|
|
+ if (ret < 0) {
|
|
+ SND_LOG_WARN(HLOG, "tdm_num config missing\n");
|
|
+ dts_info->tdm_num = 0;
|
|
+ } else {
|
|
+ if (temp_val > 3) { /* I2Sn (n = 0~3) */
|
|
+ dts_info->tdm_num = 0;
|
|
+ SND_LOG_WARN(HLOG, "tdm_num config invalid\n");
|
|
+ } else {
|
|
+ dts_info->tdm_num = temp_val;
|
|
+ }
|
|
+ }
|
|
+ ret = of_property_read_u32(np, "tx_pin", &temp_val);
|
|
+ if (ret < 0) {
|
|
+ SND_LOG_WARN(HLOG, "tx_pin config missing\n");
|
|
+ dts_info->tx_pin = 0;
|
|
+ } else {
|
|
+ if (temp_val > 3) { /* I2S_DOUTn (n = 0~3) */
|
|
+ dts_info->tx_pin = 0;
|
|
+ SND_LOG_WARN(HLOG, "tx_pin config invalid\n");
|
|
+ } else {
|
|
+ dts_info->tx_pin = temp_val;
|
|
+ }
|
|
+ }
|
|
+ ret = of_property_read_u32(np, "rx_pin", &temp_val);
|
|
+ if (ret < 0) {
|
|
+ SND_LOG_WARN(HLOG, "rx_pin config missing\n");
|
|
+ dts_info->rx_pin = 0;
|
|
+ } else {
|
|
+ if (temp_val > 3) { /* I2S_DINTn (n = 0~3) */
|
|
+ dts_info->rx_pin = 0;
|
|
+ SND_LOG_WARN(HLOG, "rx_pin config invalid\n");
|
|
+ } else {
|
|
+ dts_info->rx_pin = temp_val;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "playback_cma : %lu\n", dts_info->playback_cma);
|
|
+ SND_LOG_DEBUG(HLOG, "capture_cma : %lu\n", dts_info->capture_cma);
|
|
+ SND_LOG_DEBUG(HLOG, "tx_fifo_size : %lu\n", dts_info->playback_fifo_size);
|
|
+ SND_LOG_DEBUG(HLOG, "rx_fifo_size : %lu\n", dts_info->capture_fifo_size);
|
|
+ SND_LOG_DEBUG(HLOG, "apb_num : %u\n", dts_info->apb_num);
|
|
+ SND_LOG_DEBUG(HLOG, "tdm_num : %u\n", dts_info->tdm_num);
|
|
+ SND_LOG_DEBUG(HLOG, "tx_pin : %u\n", dts_info->tx_pin);
|
|
+ SND_LOG_DEBUG(HLOG, "rx_pin : %u\n", dts_info->rx_pin);
|
|
+
|
|
+ return 0;
|
|
+};
|
|
+
|
|
+static int snd_soc_sunxi_ahub_regulator_init(struct platform_device *pdev,
|
|
+ struct device_node *np,
|
|
+ struct sunxi_ahub_regulator_info *regulator_info)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ regulator_info->regulator_name = NULL;
|
|
+ if (of_property_read_string(np, "ahub_regulator", ®ulator_info->regulator_name)) {
|
|
+ SND_LOG_DEBUG(HLOG, "regulator missing\n");
|
|
+ regulator_info->regulator = NULL;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ regulator_info->regulator = regulator_get(NULL, regulator_info->regulator_name);
|
|
+ if (IS_ERR_OR_NULL(regulator_info->regulator)) {
|
|
+ SND_LOG_ERR(HLOG, "get duaido vcc-pin failed\n");
|
|
+ ret = -EFAULT;
|
|
+ goto err_regulator_get;
|
|
+ }
|
|
+ ret = regulator_set_voltage(regulator_info->regulator, 3300000, 3300000);
|
|
+ if (ret < 0) {
|
|
+ SND_LOG_ERR(HLOG, "set duaido voltage failed\n");
|
|
+ ret = -EFAULT;
|
|
+ goto err_regulator_set_vol;
|
|
+ }
|
|
+ ret = regulator_enable(regulator_info->regulator);
|
|
+ if (ret < 0) {
|
|
+ SND_LOG_ERR(HLOG, "enable duaido vcc-pin failed\n");
|
|
+ ret = -EFAULT;
|
|
+ goto err_regulator_enable;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_regulator_enable:
|
|
+err_regulator_set_vol:
|
|
+ if (regulator_info->regulator)
|
|
+ regulator_put(regulator_info->regulator);
|
|
+err_regulator_get:
|
|
+ return ret;
|
|
+};
|
|
+
|
|
+static void snd_soc_sunxi_dma_params_init(struct sunxi_ahub_info *ahub_info)
|
|
+{
|
|
+ struct resource *res = ahub_info->mem_info.res;
|
|
+ struct sunxi_ahub_dts_info *dts_info = &ahub_info->dts_info;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ ahub_info->playback_dma_param.addr =
|
|
+ res->start + SUNXI_AHUB_APBIF_TXFIFO(dts_info->apb_num);
|
|
+ ahub_info->playback_dma_param.maxburst = 8;
|
|
+ //ahub_info->playback_dma_param.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
+
|
|
+ ahub_info->capture_dma_param.addr =
|
|
+ res->start + SUNXI_AHUB_APBIF_RXFIFO(dts_info->apb_num);
|
|
+ ahub_info->capture_dma_param.maxburst = 8;
|
|
+ //ahub_info->capture_dma_param.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
+};
|
|
+
|
|
+static int sunxi_ahub_dev_probe(struct platform_device *pdev)
|
|
+{
|
|
+ int ret;
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+ struct sunxi_ahub_info *ahub_info = NULL;
|
|
+ struct sunxi_ahub_mem_info *mem_info = NULL;
|
|
+ struct sunxi_ahub_clk_info *clk_info = NULL;
|
|
+ struct sunxi_ahub_pinctl_info *pin_info = NULL;
|
|
+ struct sunxi_ahub_dts_info *dts_info = NULL;
|
|
+ struct sunxi_ahub_regulator_info *regulator_info = NULL;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ ahub_info = devm_kzalloc(&pdev->dev,
|
|
+ sizeof(struct sunxi_ahub_info),
|
|
+ GFP_KERNEL);
|
|
+ if (IS_ERR_OR_NULL(ahub_info)) {
|
|
+ SND_LOG_ERR(HLOG, "alloc sunxi_ahub_info failed\n");
|
|
+ ret = -ENOMEM;
|
|
+ goto err_devm_malloc_sunxi_daudio;
|
|
+ }
|
|
+ dev_set_drvdata(&pdev->dev, ahub_info);
|
|
+ ahub_info->dev = &pdev->dev;
|
|
+ mem_info = &ahub_info->mem_info;
|
|
+ clk_info = &ahub_info->clk_info;
|
|
+ pin_info = &ahub_info->pin_info;
|
|
+ dts_info = &ahub_info->dts_info;
|
|
+ regulator_info = &ahub_info->regulator_info;
|
|
+
|
|
+ ret = snd_soc_sunxi_ahub_mem_get(mem_info);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "remap get failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_snd_soc_sunxi_ahub_mem_get;
|
|
+ }
|
|
+
|
|
+ ret = snd_soc_sunxi_ahub_clk_get(clk_info);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "clk get failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_snd_soc_sunxi_ahub_clk_get;
|
|
+ }
|
|
+
|
|
+ ret = snd_soc_sunxi_ahub_dts_params_init(pdev, np, dts_info);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "dts init failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_snd_soc_sunxi_ahub_dts_params_init;
|
|
+ }
|
|
+
|
|
+ ret = snd_soc_sunxi_ahub_pin_init(pdev, np, pin_info);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "pinctrl init failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_snd_soc_sunxi_ahub_pin_init;
|
|
+ }
|
|
+
|
|
+ ret = snd_soc_sunxi_ahub_regulator_init(pdev, np, regulator_info);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "regulator_info init failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_snd_soc_sunxi_ahub_regulator_init;
|
|
+ }
|
|
+
|
|
+ snd_soc_sunxi_dma_params_init(ahub_info);
|
|
+
|
|
+ ret = snd_soc_register_component(&pdev->dev,
|
|
+ &sunxi_ahub_component,
|
|
+ &sunxi_ahub_dai, 1);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "component register failed\n");
|
|
+ ret = -ENOMEM;
|
|
+ goto err_snd_soc_register_component;
|
|
+ }
|
|
+
|
|
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "register ASoC platform failed\n");
|
|
+ ret = -ENOMEM;
|
|
+ goto err_snd_soc_sunxi_dma_platform_register;
|
|
+ }
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "register ahub platform success\n");
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_snd_soc_sunxi_dma_platform_register:
|
|
+ snd_soc_unregister_component(&pdev->dev);
|
|
+err_snd_soc_register_component:
|
|
+err_snd_soc_sunxi_ahub_regulator_init:
|
|
+err_snd_soc_sunxi_ahub_dts_params_init:
|
|
+err_snd_soc_sunxi_ahub_pin_init:
|
|
+err_snd_soc_sunxi_ahub_clk_get:
|
|
+err_snd_soc_sunxi_ahub_mem_get:
|
|
+ devm_kfree(&pdev->dev, ahub_info);
|
|
+err_devm_malloc_sunxi_daudio:
|
|
+ of_node_put(np);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dev_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct sunxi_ahub_info *ahub_info = dev_get_drvdata(&pdev->dev);
|
|
+ struct sunxi_ahub_pinctl_info *pin_info = &ahub_info->pin_info;
|
|
+ struct sunxi_ahub_regulator_info *regulator_info = &ahub_info->regulator_info;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ snd_soc_unregister_component(&pdev->dev);
|
|
+
|
|
+ if (regulator_info->regulator) {
|
|
+ if (!IS_ERR_OR_NULL(regulator_info->regulator)) {
|
|
+ regulator_disable(regulator_info->regulator);
|
|
+ regulator_put(regulator_info->regulator);
|
|
+ }
|
|
+ }
|
|
+ if (pin_info->pinctrl_used) {
|
|
+ devm_pinctrl_put(pin_info->pinctrl);
|
|
+ }
|
|
+
|
|
+ devm_kfree(&pdev->dev, ahub_info);
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "unregister ahub platform success\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id sunxi_ahub_of_match[] = {
|
|
+ { .compatible = "allwinner," DRV_NAME, },
|
|
+ {},
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, sunxi_ahub_of_match);
|
|
+
|
|
+static struct platform_driver sunxi_ahub_driver = {
|
|
+ .driver = {
|
|
+ .name = DRV_NAME,
|
|
+ .owner = THIS_MODULE,
|
|
+ .of_match_table = sunxi_ahub_of_match,
|
|
+ },
|
|
+ .probe = sunxi_ahub_dev_probe,
|
|
+ .remove = sunxi_ahub_dev_remove,
|
|
+};
|
|
+
|
|
+int __init sunxi_ahub_dev_init(void)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = platform_driver_register(&sunxi_ahub_driver);
|
|
+ if (ret != 0) {
|
|
+ SND_LOG_ERR(HLOG, "platform driver register failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+void __exit sunxi_ahub_dev_exit(void)
|
|
+{
|
|
+ platform_driver_unregister(&sunxi_ahub_driver);
|
|
+}
|
|
+
|
|
+late_initcall(sunxi_ahub_dev_init);
|
|
+module_exit(sunxi_ahub_dev_exit);
|
|
+
|
|
+MODULE_AUTHOR("Dby@allwinnertech.com");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DESCRIPTION("sunxi soundcard platform of ahub");
|
|
diff --git a/sound/soc/sunxi_v2/snd_sunxi_ahub.h b/sound/soc/sunxi_v2/snd_sunxi_ahub.h
|
|
new file mode 100644
|
|
index 000000000000..b3c1cc592844
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/snd_sunxi_ahub.h
|
|
@@ -0,0 +1,67 @@
|
|
+/* sound\soc\sunxi\snd_sunxi_ahub.h
|
|
+ * (C) Copyright 2021-2025
|
|
+ * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
+ * Dby <dby@allwinnertech.com>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of
|
|
+ * the License, or (at your option) any later version.
|
|
+ */
|
|
+
|
|
+#ifndef __SND_SUNXI_AHUB_H
|
|
+#define __SND_SUNXI_AHUB_H
|
|
+
|
|
+#include "snd_sunxi_ahub_dam.h"
|
|
+
|
|
+struct sunxi_ahub_pinctl_info {
|
|
+ struct pinctrl *pinctrl;
|
|
+ struct pinctrl_state *pinstate;
|
|
+ struct pinctrl_state *pinstate_sleep;
|
|
+
|
|
+ bool pinctrl_used;
|
|
+};
|
|
+
|
|
+struct sunxi_ahub_dts_info {
|
|
+ unsigned int dai_type;
|
|
+ unsigned int apb_num;
|
|
+ unsigned int tdm_num;
|
|
+ unsigned int tx_pin;
|
|
+ unsigned int rx_pin;
|
|
+
|
|
+ /* value must be (2^n)Kbyte */
|
|
+ size_t playback_cma;
|
|
+ size_t playback_fifo_size;
|
|
+ size_t capture_cma;
|
|
+ size_t capture_fifo_size;
|
|
+};
|
|
+
|
|
+struct sunxi_ahub_regulator_info {
|
|
+ struct regulator *regulator;
|
|
+ const char *regulator_name;
|
|
+};
|
|
+
|
|
+struct sunxi_ahub_info {
|
|
+ struct device *dev;
|
|
+
|
|
+ struct sunxi_ahub_mem_info mem_info;
|
|
+ struct sunxi_ahub_clk_info clk_info;
|
|
+ struct sunxi_ahub_pinctl_info pin_info;
|
|
+ struct sunxi_ahub_dts_info dts_info;
|
|
+ struct sunxi_ahub_regulator_info regulator_info;
|
|
+
|
|
+ //struct sunxi_dma_params playback_dma_param;
|
|
+ //struct sunxi_dma_params capture_dma_param;
|
|
+ struct snd_dmaengine_dai_dma_data playback_dma_param;
|
|
+ struct snd_dmaengine_dai_dma_data capture_dma_param;
|
|
+
|
|
+ /* for Hardware param setting */
|
|
+ unsigned int fmt;
|
|
+ unsigned int pllclk_freq;
|
|
+ unsigned int moduleclk_freq;
|
|
+ unsigned int mclk_freq;
|
|
+ unsigned int lrck_freq;
|
|
+ unsigned int bclk_freq;
|
|
+};
|
|
+
|
|
+#endif /* __SND_SUNXI_AHUB_H */
|
|
diff --git a/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c b/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c
|
|
new file mode 100644
|
|
index 000000000000..1fcc8aefd50c
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c
|
|
@@ -0,0 +1,534 @@
|
|
+/*
|
|
+ * sound\soc\sunxi\snd_sunxi_ahub_dam.c
|
|
+ * (C) Copyright 2021-2025
|
|
+ * AllWinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
+ * Dby <dby@allwinnertech.com>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of
|
|
+ * the License, or (at your option) any later version.
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <sound/soc.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/reset.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/of_address.h>
|
|
+#include <linux/of_gpio.h>
|
|
+#include <sound/soc.h>
|
|
+
|
|
+#include "snd_sunxi_log.h"
|
|
+#include "snd_sunxi_ahub_dam.h"
|
|
+
|
|
+#define HLOG "AHUB_DAM"
|
|
+#define DRV_NAME "sunxi-snd-plat-ahub_dam"
|
|
+
|
|
+static struct resource g_res;
|
|
+struct sunxi_ahub_mem_info g_mem_info = {
|
|
+ .res = &g_res,
|
|
+};
|
|
+static struct sunxi_ahub_clk_info g_clk_info;
|
|
+static struct regmap_config g_regmap_config = {
|
|
+ .reg_bits = 32,
|
|
+ .reg_stride = 4,
|
|
+ .val_bits = 32,
|
|
+ .max_register = SUNXI_AHUB_MAX_REG,
|
|
+ .cache_type = REGCACHE_NONE,
|
|
+};
|
|
+
|
|
+static struct snd_soc_dai_driver sunxi_ahub_dam_dai = {
|
|
+ .name = "ahub_dam",
|
|
+};
|
|
+
|
|
+static int sunxi_ahub_dam_probe(struct snd_soc_component *component)
|
|
+{
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dam_suspend(struct snd_soc_component *component)
|
|
+{
|
|
+ struct sunxi_ahub_clk_info *clk_info = &g_clk_info;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ clk_disable_unprepare(clk_info->clk_module);
|
|
+ clk_disable_unprepare(clk_info->clk_pll);
|
|
+ //clk_disable_unprepare(clk_info->clk_pllx4);
|
|
+ clk_disable_unprepare(clk_info->clk_bus);
|
|
+ reset_control_assert(clk_info->clk_rst);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dam_resume(struct snd_soc_component *component)
|
|
+{
|
|
+ struct sunxi_ahub_clk_info *clk_info = &g_clk_info;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (reset_control_deassert(clk_info->clk_rst)) {
|
|
+ SND_LOG_ERR(HLOG, "clk rst deassert failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ if (clk_prepare_enable(clk_info->clk_bus)) {
|
|
+ SND_LOG_ERR(HLOG, "clk bus enable failed\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+ if (clk_prepare_enable(clk_info->clk_pll)) {
|
|
+ SND_LOG_ERR(HLOG, "clk_pll enable failed\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+ //if (clk_prepare_enable(clk_info->clk_pllx4)) {
|
|
+ // SND_LOG_ERR(HLOG, "clk_pllx4 enable failed\n");
|
|
+ // return -EBUSY;
|
|
+ //}
|
|
+ if (clk_prepare_enable(clk_info->clk_module)) {
|
|
+ SND_LOG_ERR(HLOG, "clk_module enable failed\n");
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+struct str_conv {
|
|
+ char *str;
|
|
+ unsigned int reg;
|
|
+};
|
|
+static struct str_conv ahub_mux_name[] = {
|
|
+ {"APBIF0 Src Select", SUNXI_AHUB_APBIF_RXFIFO_CONT(0)},
|
|
+ {"APBIF1 Src Select", SUNXI_AHUB_APBIF_RXFIFO_CONT(1)},
|
|
+ {"APBIF2 Src Select", SUNXI_AHUB_APBIF_RXFIFO_CONT(2)},
|
|
+ {"I2S0 Src Select", SUNXI_AHUB_I2S_RXCONT(0)},
|
|
+ {"I2S1 Src Select", SUNXI_AHUB_I2S_RXCONT(1)},
|
|
+ {"I2S2 Src Select", SUNXI_AHUB_I2S_RXCONT(2)},
|
|
+ {"I2S3 Src Select", SUNXI_AHUB_I2S_RXCONT(3)},
|
|
+ {"DAM0C0 Src Select", SUNXI_AHUB_DAM_RX0_SRC(0)},
|
|
+ {"DAM0C1 Src Select", SUNXI_AHUB_DAM_RX1_SRC(0)},
|
|
+ {"DAM0C2 Src Select", SUNXI_AHUB_DAM_RX2_SRC(0)},
|
|
+ {"DAM1C0 Src Select", SUNXI_AHUB_DAM_RX0_SRC(1)},
|
|
+ {"DAM1C1 Src Select", SUNXI_AHUB_DAM_RX1_SRC(1)},
|
|
+ {"DAM1C2 Src Select", SUNXI_AHUB_DAM_RX2_SRC(1)},
|
|
+};
|
|
+static const char *ahub_mux_text[] = {
|
|
+ "NONE",
|
|
+ "APBIF_TXDIF0",
|
|
+ "APBIF_TXDIF1",
|
|
+ "APBIF_TXDIF2",
|
|
+ "I2S0_TXDIF",
|
|
+ "I2S1_TXDIF",
|
|
+ "I2S2_TXDIF",
|
|
+ "I2S3_TXDIF",
|
|
+ "DAM0_TXDIF",
|
|
+ "DAM1_TXDIF",
|
|
+};
|
|
+static const unsigned int ahub_mux_values[] = {
|
|
+ 0,
|
|
+ 1 << I2S_RX_APBIF_TXDIF0,
|
|
+ 1 << I2S_RX_APBIF_TXDIF1,
|
|
+ 1 << I2S_RX_APBIF_TXDIF2,
|
|
+ 1 << I2S_RX_I2S0_TXDIF,
|
|
+ 1 << I2S_RX_I2S1_TXDIF,
|
|
+ 1 << I2S_RX_I2S2_TXDIF,
|
|
+ 1 << I2S_RX_I2S3_TXDIF,
|
|
+ 1 << I2S_RX_DAM0_TXDIF,
|
|
+ 1 << I2S_RX_DAM1_TXDIF,
|
|
+};
|
|
+static SOC_ENUM_SINGLE_EXT_DECL(ahub_mux, ahub_mux_text);
|
|
+
|
|
+static int sunxi_ahub_mux_get(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ int i;
|
|
+ unsigned int reg_val;
|
|
+ unsigned int src_reg;
|
|
+ struct regmap *regmap = g_mem_info.regmap;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(ahub_mux_name); i++) {
|
|
+ if (!strncmp(ahub_mux_name[i].str, kcontrol->id.name,
|
|
+ strlen(ahub_mux_name[i].str))) {
|
|
+ src_reg = ahub_mux_name[i].reg;
|
|
+ regmap_read(regmap, src_reg, ®_val);
|
|
+ reg_val &= 0xffffc000;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 1; i < ARRAY_SIZE(ahub_mux_values); i++) {
|
|
+ if (reg_val & ahub_mux_values[i]) {
|
|
+ ucontrol->value.integer.value[0] = i;
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+ ucontrol->value.integer.value[0] = 0;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_mux_set(struct snd_kcontrol *kcontrol,
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
+{
|
|
+ int i;
|
|
+ unsigned int src_reg, src_regbit;
|
|
+ struct regmap *regmap = g_mem_info.regmap;
|
|
+
|
|
+ if (ucontrol->value.integer.value[0] > ARRAY_SIZE(ahub_mux_name))
|
|
+ return -EINVAL;
|
|
+
|
|
+ src_regbit = ahub_mux_values[ucontrol->value.integer.value[0]];
|
|
+ for (i = 0; i < ARRAY_SIZE(ahub_mux_name); i++) {
|
|
+ if (!strncmp(ahub_mux_name[i].str, kcontrol->id.name,
|
|
+ strlen(ahub_mux_name[i].str))) {
|
|
+ src_reg = ahub_mux_name[i].reg;
|
|
+ regmap_update_bits(regmap, src_reg, 0xffffc000, src_regbit);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct snd_kcontrol_new sunxi_ahub_dam_controls[] = {
|
|
+ SOC_ENUM_EXT("APBIF0 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+ SOC_ENUM_EXT("APBIF1 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+ SOC_ENUM_EXT("APBIF2 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+ SOC_ENUM_EXT("I2S0 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+ SOC_ENUM_EXT("I2S1 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+ SOC_ENUM_EXT("I2S2 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+ SOC_ENUM_EXT("I2S3 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+ SOC_ENUM_EXT("DAM0C0 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+ SOC_ENUM_EXT("DAM0C1 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+ SOC_ENUM_EXT("DAM0C2 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+ SOC_ENUM_EXT("DAM1C0 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+ SOC_ENUM_EXT("DAM1C1 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+ SOC_ENUM_EXT("DAM1C2 Src Select", ahub_mux, sunxi_ahub_mux_get, sunxi_ahub_mux_set),
|
|
+};
|
|
+
|
|
+static struct snd_soc_component_driver sunxi_ahub_dam_dev = {
|
|
+ .name = DRV_NAME,
|
|
+ .probe = sunxi_ahub_dam_probe,
|
|
+ .suspend = sunxi_ahub_dam_suspend,
|
|
+ .resume = sunxi_ahub_dam_resume,
|
|
+ .controls = sunxi_ahub_dam_controls,
|
|
+ .num_controls = ARRAY_SIZE(sunxi_ahub_dam_controls),
|
|
+};
|
|
+
|
|
+/*******************************************************************************
|
|
+ * for kernel source
|
|
+ ******************************************************************************/
|
|
+int snd_soc_sunxi_ahub_mem_get(struct sunxi_ahub_mem_info *mem_info)
|
|
+{
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (IS_ERR_OR_NULL(g_mem_info.regmap)) {
|
|
+ SND_LOG_ERR(HLOG, "regmap is invalid\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ if (IS_ERR_OR_NULL(g_mem_info.res)) {
|
|
+ SND_LOG_ERR(HLOG, "res is invalid\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ mem_info->regmap = g_mem_info.regmap;
|
|
+ mem_info->res = g_mem_info.res;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(snd_soc_sunxi_ahub_mem_get);
|
|
+
|
|
+int snd_soc_sunxi_ahub_clk_get(struct sunxi_ahub_clk_info *clk_info)
|
|
+{
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (IS_ERR_OR_NULL(g_clk_info.clk_pll)) {
|
|
+ SND_LOG_ERR(HLOG, "clk_pll is invalid\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ //if (IS_ERR_OR_NULL(g_clk_info.clk_pllx4)) {
|
|
+ // SND_LOG_ERR(HLOG, "clk_pllx4 is invalid\n");
|
|
+ // return -EINVAL;
|
|
+ //}
|
|
+ if (IS_ERR_OR_NULL(g_clk_info.clk_module)) {
|
|
+ SND_LOG_ERR(HLOG, "clk_module is invalid\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ clk_info->clk_pll = g_clk_info.clk_pll;
|
|
+ //clk_info->clk_pllx4 = g_clk_info.clk_pllx4;
|
|
+ clk_info->clk_module = g_clk_info.clk_module;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(snd_soc_sunxi_ahub_clk_get);
|
|
+
|
|
+static int snd_soc_sunxi_ahub_mem_init(struct platform_device *pdev,
|
|
+ struct device_node *np,
|
|
+ struct sunxi_ahub_mem_info *mem_info)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ ret = of_address_to_resource(np, 0, mem_info->res);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "parse device node resource failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_of_addr_to_resource;
|
|
+ }
|
|
+
|
|
+ mem_info->memregion = devm_request_mem_region(&pdev->dev,
|
|
+ mem_info->res->start,
|
|
+ resource_size(mem_info->res),
|
|
+ DRV_NAME);
|
|
+ if (IS_ERR_OR_NULL(mem_info->memregion)) {
|
|
+ SND_LOG_ERR(HLOG, "memory region already claimed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_devm_request_region;
|
|
+ }
|
|
+
|
|
+ mem_info->membase = devm_ioremap(&pdev->dev,
|
|
+ mem_info->memregion->start,
|
|
+ resource_size(mem_info->memregion));
|
|
+ if (IS_ERR_OR_NULL(mem_info->membase)) {
|
|
+ SND_LOG_ERR(HLOG, "ioremap failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_devm_ioremap;
|
|
+ }
|
|
+
|
|
+ mem_info->regmap = devm_regmap_init_mmio(&pdev->dev,
|
|
+ mem_info->membase,
|
|
+ &g_regmap_config);
|
|
+ if (IS_ERR_OR_NULL(mem_info->regmap)) {
|
|
+ SND_LOG_ERR(HLOG, "regmap init failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_devm_regmap_init;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_devm_regmap_init:
|
|
+ devm_iounmap(&pdev->dev, mem_info->membase);
|
|
+err_devm_ioremap:
|
|
+ devm_release_mem_region(&pdev->dev, mem_info->memregion->start,
|
|
+ resource_size(mem_info->memregion));
|
|
+err_devm_request_region:
|
|
+err_of_addr_to_resource:
|
|
+ return ret;
|
|
+};
|
|
+
|
|
+static int snd_soc_sunxi_ahub_clk_init(struct platform_device *pdev,
|
|
+ struct device_node *np,
|
|
+ struct sunxi_ahub_clk_info *clk_info)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ /* deassert rst clk */
|
|
+ clk_info->clk_rst = devm_reset_control_get(&pdev->dev, NULL);
|
|
+ if (IS_ERR_OR_NULL(clk_info->clk_rst)) {
|
|
+ SND_LOG_ERR(HLOG, "clk rst get failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_rst_clk;
|
|
+ }
|
|
+ if (reset_control_deassert(clk_info->clk_rst)) {
|
|
+ SND_LOG_ERR(HLOG, "deassert reset clk failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_rst_clk;
|
|
+ }
|
|
+
|
|
+ /* enable ahub bus clk */
|
|
+ clk_info->clk_bus = of_clk_get_by_name(np, "clk_bus_audio_hub");
|
|
+ if (IS_ERR_OR_NULL(clk_info->clk_bus)) {
|
|
+ SND_LOG_ERR(HLOG, "clk bus get failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_bus_clk;
|
|
+ }
|
|
+ if (clk_prepare_enable(clk_info->clk_bus)) {
|
|
+ SND_LOG_ERR(HLOG, "ahub clk bus enable failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_bus_clk;
|
|
+ }
|
|
+
|
|
+ /* get clk of ahub */
|
|
+ clk_info->clk_module = of_clk_get_by_name(np, "clk_audio_hub");
|
|
+ if (IS_ERR_OR_NULL(clk_info->clk_module)) {
|
|
+ SND_LOG_ERR(HLOG, "clk module get failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_module_clk;
|
|
+ }
|
|
+ clk_info->clk_pll = of_clk_get_by_name(np, "clk_pll_audio");
|
|
+ if (IS_ERR_OR_NULL(clk_info->clk_pll)) {
|
|
+ SND_LOG_ERR(HLOG, "clk pll get failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_pll_clk;
|
|
+ }
|
|
+ //clk_info->clk_pllx4 = of_clk_get_by_name(np, "clk_pll_audio_4x");
|
|
+ //if (IS_ERR_OR_NULL(clk_info->clk_pllx4)) {
|
|
+ // SND_LOG_ERR(HLOG, "clk pllx4 get failed\n");
|
|
+ // ret = -EBUSY;
|
|
+ // goto err_pllx4_clk;
|
|
+ //}
|
|
+
|
|
+ /* set ahub clk parent */
|
|
+ //if (clk_set_parent(clk_info->clk_module, clk_info->clk_pllx4)) {
|
|
+ // SND_LOG_ERR(HLOG, "set parent of clk_module to pllx4 failed\n");
|
|
+ // ret = -EINVAL;
|
|
+ // goto err_set_parent_clk;
|
|
+ //}
|
|
+
|
|
+ /* enable clk of ahub */
|
|
+ if (clk_prepare_enable(clk_info->clk_pll)) {
|
|
+ SND_LOG_ERR(HLOG, "clk_pll enable failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_pll_clk_enable;
|
|
+ }
|
|
+ //if (clk_prepare_enable(clk_info->clk_pllx4)) {
|
|
+ // SND_LOG_ERR(HLOG, "clk_pllx4 enable failed\n");
|
|
+ // ret = -EBUSY;
|
|
+ // goto err_pllx4_clk_enable;
|
|
+ //}
|
|
+ if (clk_prepare_enable(clk_info->clk_module)) {
|
|
+ SND_LOG_ERR(HLOG, "clk_module enable failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_module_clk_enable;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_module_clk_enable:
|
|
+// clk_disable_unprepare(clk_info->clk_pllx4);
|
|
+//err_pllx4_clk_enable:
|
|
+ clk_disable_unprepare(clk_info->clk_pll);
|
|
+err_pll_clk_enable:
|
|
+//err_set_parent_clk:
|
|
+// clk_put(clk_info->clk_pllx4);
|
|
+//err_pllx4_clk:
|
|
+// clk_put(clk_info->clk_pll);
|
|
+err_pll_clk:
|
|
+ clk_put(clk_info->clk_module);
|
|
+err_module_clk:
|
|
+ clk_disable_unprepare(clk_info->clk_bus);
|
|
+ clk_put(clk_info->clk_bus);
|
|
+err_bus_clk:
|
|
+ reset_control_assert(clk_info->clk_rst);
|
|
+err_rst_clk:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dam_dev_probe(struct platform_device *pdev)
|
|
+{
|
|
+ int ret;
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ ret = snd_soc_sunxi_ahub_mem_init(pdev, np, &g_mem_info);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "remap init failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_snd_soc_sunxi_ahub_mem_init;
|
|
+ }
|
|
+
|
|
+ ret = snd_soc_sunxi_ahub_clk_init(pdev, np, &g_clk_info);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "clk init failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_snd_soc_sunxi_ahub_clk_init;
|
|
+ }
|
|
+
|
|
+ ret = snd_soc_register_component(&pdev->dev,
|
|
+ &sunxi_ahub_dam_dev,
|
|
+ &sunxi_ahub_dam_dai, 1);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "component register failed\n");
|
|
+ ret = -ENOMEM;
|
|
+ goto err_snd_soc_register_component;
|
|
+ }
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "register ahub_dam platform success\n");
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_snd_soc_register_component:
|
|
+err_snd_soc_sunxi_ahub_clk_init:
|
|
+err_snd_soc_sunxi_ahub_mem_init:
|
|
+ of_node_put(np);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int sunxi_ahub_dam_dev_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct sunxi_ahub_mem_info *mem_info = &g_mem_info;
|
|
+ struct sunxi_ahub_clk_info *clk_info = &g_clk_info;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ snd_soc_unregister_component(&pdev->dev);
|
|
+
|
|
+ devm_iounmap(&pdev->dev, mem_info->membase);
|
|
+ devm_release_mem_region(&pdev->dev, mem_info->memregion->start,
|
|
+ resource_size(mem_info->memregion));
|
|
+
|
|
+ clk_disable_unprepare(clk_info->clk_module);
|
|
+ clk_put(clk_info->clk_module);
|
|
+ clk_disable_unprepare(clk_info->clk_pll);
|
|
+ clk_put(clk_info->clk_pll);
|
|
+ //clk_disable_unprepare(clk_info->clk_pllx4);
|
|
+ //clk_put(clk_info->clk_pllx4);
|
|
+ clk_disable_unprepare(clk_info->clk_bus);
|
|
+ clk_put(clk_info->clk_bus);
|
|
+ reset_control_assert(clk_info->clk_rst);
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "unregister ahub_dam platform success\n");
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id sunxi_ahub_dam_of_match[] = {
|
|
+ { .compatible = "allwinner," DRV_NAME, },
|
|
+ {},
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, sunxi_ahub_dam_of_match);
|
|
+
|
|
+static struct platform_driver sunxi_ahub_dam_driver = {
|
|
+ .driver = {
|
|
+ .name = DRV_NAME,
|
|
+ .owner = THIS_MODULE,
|
|
+ .of_match_table = sunxi_ahub_dam_of_match,
|
|
+ },
|
|
+ .probe = sunxi_ahub_dam_dev_probe,
|
|
+ .remove = sunxi_ahub_dam_dev_remove,
|
|
+};
|
|
+
|
|
+int __init sunxi_ahub_dam_dev_init(void)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = platform_driver_register(&sunxi_ahub_dam_driver);
|
|
+ if (ret != 0) {
|
|
+ SND_LOG_ERR(HLOG, "platform driver register failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+void __exit sunxi_ahub_dam_dev_exit(void)
|
|
+{
|
|
+ platform_driver_unregister(&sunxi_ahub_dam_driver);
|
|
+}
|
|
+
|
|
+late_initcall(sunxi_ahub_dam_dev_init);
|
|
+module_exit(sunxi_ahub_dam_dev_exit);
|
|
+
|
|
+MODULE_AUTHOR("Dby@allwinnertech.com");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DESCRIPTION("sunxi soundcard platform of ahub_dam");
|
|
diff --git a/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h b/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h
|
|
new file mode 100644
|
|
index 000000000000..b7679bf545e9
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h
|
|
@@ -0,0 +1,291 @@
|
|
+/* sound\soc\sunxi\snd_sunxi_ahub_dam.h
|
|
+ * (C) Copyright 2021-2025
|
|
+ * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
+ * Dby <dby@allwinnertech.com>
|
|
+ *
|
|
+ * some simple description for this code
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of
|
|
+ * the License, or (at your option) any later version.
|
|
+ *
|
|
+ */
|
|
+#ifndef __SND_SUNXI_AHUB_DAM_H
|
|
+#define __SND_SUNXI_AHUB_DAM_H
|
|
+
|
|
+/* SUNXI Audio Hub registers list */
|
|
+#define SUNXI_AHUB_CTL 0x00
|
|
+#define SUNXI_AHUB_VER 0x04
|
|
+#define SUNXI_AHUB_RST 0x08
|
|
+#define SUNXI_AHUB_GAT 0x0c
|
|
+
|
|
+#define SUNXI_AHUB_APBIF_TX_CTL(n) (0x10 + ((n) * 0x30))
|
|
+#define SUNXI_AHUB_APBIF_TX_IRQ_CTL(n) (0x14 + ((n) * 0x30))
|
|
+#define SUNXI_AHUB_APBIF_TX_IRQ_STA(n) (0x18 + ((n) * 0x30))
|
|
+
|
|
+#define SUNXI_AHUB_APBIF_TXFIFO_CTL(n) (0x20 + ((n) * 0x30))
|
|
+#define SUNXI_AHUB_APBIF_TXFIFO_STA(n) (0x24 + ((n) * 0x30))
|
|
+
|
|
+#define SUNXI_AHUB_APBIF_TXFIFO(n) (0x30 + ((n) * 0x30))
|
|
+#define SUNXI_AHUB_APBIF_TXFIFO_CNT(n) (0x34 + ((n) * 0x30))
|
|
+
|
|
+#define SUNXI_AHUB_APBIF_RX_CTL(n) (0x100 + ((n) * 0x30))
|
|
+#define SUNXI_AHUB_APBIF_RX_IRQ_CTL(n) (0x104 + ((n) * 0x30))
|
|
+#define SUNXI_AHUB_APBIF_RX_IRQ_STA(n) (0x108 + ((n) * 0x30))
|
|
+
|
|
+#define SUNXI_AHUB_APBIF_RXFIFO_CTL(n) (0x110 + ((n) * 0x30))
|
|
+#define SUNXI_AHUB_APBIF_RXFIFO_STA(n) (0x114 + ((n) * 0x30))
|
|
+#define SUNXI_AHUB_APBIF_RXFIFO_CONT(n) (0x118 + ((n) * 0x30))
|
|
+
|
|
+#define SUNXI_AHUB_APBIF_RXFIFO(n) (0x120 + ((n) * 0x30))
|
|
+#define SUNXI_AHUB_APBIF_RXFIFO_CNT(n) (0x124 + ((n) * 0x30))
|
|
+
|
|
+#define SUNXI_AHUB_I2S_CTL(n) (0x200 + ((n) << 8))
|
|
+#define SUNXI_AHUB_I2S_FMT0(n) (0x204 + ((n) << 8))
|
|
+#define SUNXI_AHUB_I2S_FMT1(n) (0x208 + ((n) << 8))
|
|
+#define SUNXI_AHUB_I2S_CLKD(n) (0x20c + ((n) << 8))
|
|
+
|
|
+#define SUNXI_AHUB_I2S_RXCONT(n) (0x220 + ((n) << 8))
|
|
+#define SUNXI_AHUB_I2S_CHCFG(n) (0x224 + ((n) << 8))
|
|
+#define SUNXI_AHUB_I2S_IRQ_CTL(n) (0x228 + ((n) << 8))
|
|
+#define SUNXI_AHUB_I2S_IRQ_STA(n) (0x22C + ((n) << 8))
|
|
+#define SUNXI_AHUB_I2S_OUT_SLOT(n, m) (0x230 + ((n) << 8) + ((m) << 4))
|
|
+#define SUNXI_AHUB_I2S_OUT_CHMAP0(n, m) (0x234 + ((n) << 8) + ((m) << 4))
|
|
+#define SUNXI_AHUB_I2S_OUT_CHMAP1(n, m) (0x238 + ((n) << 8) + ((m) << 4))
|
|
+
|
|
+#define SUNXI_AHUB_I2S_IN_SLOT(n) (0x270 + ((n) << 8))
|
|
+#define SUNXI_AHUB_I2S_IN_CHMAP0(n) (0x274 + ((n) << 8))
|
|
+#define SUNXI_AHUB_I2S_IN_CHMAP1(n) (0x278 + ((n) << 8))
|
|
+#define SUNXI_AHUB_I2S_IN_CHMAP2(n) (0x27C + ((n) << 8))
|
|
+#define SUNXI_AHUB_I2S_IN_CHMAP3(n) (0x280 + ((n) << 8))
|
|
+
|
|
+#define SUNXI_AHUB_DAM_CTL(n) (0xA00 + ((n) << 7))
|
|
+
|
|
+#define SUNXI_AHUB_DAM_RX0_SRC(n) (0xA10 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_RX1_SRC(n) (0xA14 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_RX2_SRC(n) (0xA18 + ((n) << 7))
|
|
+
|
|
+#define SUNXI_AHUB_DAM_MIX_CTL0(n) (0xA30 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_MIX_CTL1(n) (0xA34 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_MIX_CTL2(n) (0xA38 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_MIX_CTL3(n) (0xA3C + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_MIX_CTL4(n) (0xA40 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_MIX_CTL5(n) (0xA44 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_MIX_CTL6(n) (0xA48 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_MIX_CTL7(n) (0xA4C + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_GAIN_CTL0(n) (0xA50 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_GAIN_CTL1(n) (0xA54 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_GAIN_CTL2(n) (0xA58 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_GAIN_CTL3(n) (0xA5C + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_GAIN_CTL4(n) (0xA60 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_GAIN_CTL5(n) (0xA64 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_GAIN_CTL6(n) (0xA68 + ((n) << 7))
|
|
+#define SUNXI_AHUB_DAM_GAIN_CTL7(n) (0xA6C + ((n) << 7))
|
|
+
|
|
+#define SUNXI_AHUB_MAX_REG SUNXI_AHUB_DAM_GAIN_CTL7(1)
|
|
+
|
|
+/* SUNXI_AHUB_CTL */
|
|
+#define HDMI_SRC_SEL 0x04
|
|
+
|
|
+/* SUNXI_AHUB_RST */
|
|
+#define APBIF_TXDIF0_RST 31
|
|
+#define APBIF_TXDIF1_RST 30
|
|
+#define APBIF_TXDIF2_RST 29
|
|
+#define APBIF_RXDIF0_RST 27
|
|
+#define APBIF_RXDIF1_RST 26
|
|
+#define APBIF_RXDIF2_RST 25
|
|
+#define I2S0_RST 23
|
|
+#define I2S1_RST 22
|
|
+#define I2S2_RST 21
|
|
+#define I2S3_RST 20
|
|
+#define DAM0_RST 15
|
|
+#define DAM1_RST 14
|
|
+
|
|
+/* SUNXI_AHUB_GAT */
|
|
+#define APBIF_TXDIF0_GAT 31
|
|
+#define APBIF_TXDIF1_GAT 30
|
|
+#define APBIF_TXDIF2_GAT 29
|
|
+#define APBIF_RXDIF0_GAT 27
|
|
+#define APBIF_RXDIF1_GAT 26
|
|
+#define APBIF_RXDIF2_GAT 25
|
|
+#define I2S0_GAT 23
|
|
+#define I2S1_GAT 22
|
|
+#define I2S2_GAT 21
|
|
+#define I2S3_GAT 20
|
|
+#define DAM0_GAT 15
|
|
+#define DAM1_GAT 14
|
|
+
|
|
+/* SUNXI_AHUB_APBIF_TX_CTL */
|
|
+#define APBIF_TX_WS 16
|
|
+#define APBIF_TX_CHAN_NUM 8
|
|
+#define APBIF_TX_START 4
|
|
+
|
|
+/* SUNXI_AHUB_APBIF_TX_IRQ_CTL */
|
|
+#define APBIF_TX_DRQ 3
|
|
+#define APBIF_TX_OVEN 1
|
|
+#define APBIF_TX_EMEN 0
|
|
+
|
|
+/* SUNXI_AHUB_APBIF_TX_IRQ_STA */
|
|
+#define APBIF_TX_OV_PEND 2
|
|
+#define APBIF_TX_EM_PEND 0
|
|
+
|
|
+/* SUNXI_AHUB_APBIF_TXFIFO_CTL */
|
|
+#define APBIF_TX_FTX 12
|
|
+#define APBIF_TX_LEVEL 4
|
|
+#define APBIF_TX_TXIM 0
|
|
+
|
|
+/* SUNXI_AHUB_APBIF_TXFIFO_STA */
|
|
+#define APBIF_TX_EMPTY 8
|
|
+#define APBIF_TX_EMCNT 0
|
|
+
|
|
+/* SUNXI_AHUB_APBIF_RX_CTL */
|
|
+#define APBIF_RX_WS 16
|
|
+#define APBIF_RX_CHAN_NUM 8
|
|
+#define APBIF_RX_START 4
|
|
+
|
|
+/* SUNXI_AHUB_APBIF_RX_IRQ_CTL */
|
|
+#define APBIF_RX_DRQ 3
|
|
+#define APBIF_RX_UVEN 2
|
|
+#define APBIF_RX_AVEN 0
|
|
+
|
|
+/* SUNXI_AHUB_APBIF_RX_IRQ_STA */
|
|
+#define APBIF_RX_UV_PEND 2
|
|
+#define APBIF_RX_AV_PEND 0
|
|
+
|
|
+/* SUNXI_AHUB_APBIF_RXFIFO_CTL */
|
|
+#define APBIF_RX_FRX 12
|
|
+#define APBIF_RX_LEVEL 4
|
|
+#define APBIF_RX_RXOM 0
|
|
+
|
|
+/* SUNXI_AHUB_APBIF_RXFIFO_STA */
|
|
+#define APBIF_RX_AVAIL 8
|
|
+#define APBIF_RX_AVCNT 0
|
|
+
|
|
+/* SUNXI_AHUB_APBIF_RXFIFO_CONT */
|
|
+#define APBIF_RX_APBIF_TXDIF0 31
|
|
+#define APBIF_RX_APBIF_TXDIF1 30
|
|
+#define APBIF_RX_APBIF_TXDIF2 29
|
|
+#define APBIF_RX_I2S0_TXDIF 27
|
|
+#define APBIF_RX_I2S1_TXDIF 26
|
|
+#define APBIF_RX_I2S2_TXDIF 25
|
|
+#define APBIF_RX_I2S3_TXDIF 23
|
|
+#define APBIF_RX_DAM0_TXDIF 19
|
|
+#define APBIF_RX_DAM1_TXDIF 15
|
|
+
|
|
+/* SUNXI_AHUB_I2S_CTL */
|
|
+#define I2S_CTL_LOOP3 23
|
|
+#define I2S_CTL_LOOP2 22
|
|
+#define I2S_CTL_LOOP1 21
|
|
+#define I2S_CTL_LOOP0 20
|
|
+#define I2S_CTL_SDI3_EN 15
|
|
+#define I2S_CTL_SDI2_EN 14
|
|
+#define I2S_CTL_SDI1_EN 13
|
|
+#define I2S_CTL_SDI0_EN 12
|
|
+#define I2S_CTL_CLK_OUT 18
|
|
+#define I2S_CTL_SDO3_EN 11
|
|
+#define I2S_CTL_SDO2_EN 10
|
|
+#define I2S_CTL_SDO1_EN 9
|
|
+#define I2S_CTL_SDO0_EN 8
|
|
+#define I2S_CTL_OUT_MUTE 6
|
|
+#define I2S_CTL_MODE 4
|
|
+#define I2S_CTL_TXEN 2
|
|
+#define I2S_CTL_RXEN 1
|
|
+#define I2S_CTL_GEN 0
|
|
+
|
|
+/* SUNXI_AHUB_I2S_FMT0 */
|
|
+#define I2S_FMT0_LRCK_WIDTH 30
|
|
+#define I2S_FMT0_LRCK_POLARITY 19
|
|
+#define I2S_FMT0_LRCK_PERIOD 8
|
|
+#define I2S_FMT0_BCLK_POLARITY 7
|
|
+#define I2S_FMT0_SR 4
|
|
+#define I2S_FMT0_EDGE 3
|
|
+#define I2S_FMT0_SW 0
|
|
+
|
|
+/* SUNXI_AHUB_I2S_FMT1 */
|
|
+#define I2S_FMT1_RX_LSB 7
|
|
+#define I2S_FMT1_TX_LSB 6
|
|
+#define I2S_FMT1_EXT 4
|
|
+#define I2S_FMT1_RX_PDM 2
|
|
+#define I2S_FMT1_TX_PDM 0
|
|
+
|
|
+/* SUNXI_AHUB_I2S_CLKD */
|
|
+#define I2S_CLKD_MCLK 8
|
|
+#define I2S_CLKD_BCLKDIV 4
|
|
+#define I2S_CLKD_MCLKDIV 0
|
|
+
|
|
+/* SUNXI_AHUB_I2S_RXCONT */
|
|
+#define I2S_RX_APBIF_TXDIF0 31
|
|
+#define I2S_RX_APBIF_TXDIF1 30
|
|
+#define I2S_RX_APBIF_TXDIF2 29
|
|
+#define I2S_RX_I2S0_TXDIF 27
|
|
+#define I2S_RX_I2S1_TXDIF 26
|
|
+#define I2S_RX_I2S2_TXDIF 25
|
|
+#define I2S_RX_I2S3_TXDIF 23
|
|
+#define I2S_RX_DAM0_TXDIF 19
|
|
+#define I2S_RX_DAM1_TXDIF 15
|
|
+
|
|
+/* SUNXI_AHUB_I2S_CHCFG */
|
|
+#define I2S_CHCFG_HIZ 9
|
|
+#define I2S_CHCFG_TX_STATE 8
|
|
+#define I2S_CHCFG_RX_CHANNUM 4
|
|
+#define I2S_CHCFG_TX_CHANNUM 0
|
|
+
|
|
+/* SUNXI_AHUB_I2S_IRQ_CTL */
|
|
+#define I2S_IRQ_RXOV_EN 1
|
|
+#define I2S_IRQ_TXUV_EN 0
|
|
+
|
|
+/* SUNXI_AHUB_I2S_IRQ_STA */
|
|
+#define I2S_IRQ_RXOV_PEND 1
|
|
+#define I2S_IRQ_TXUV_PEND 0
|
|
+
|
|
+/* SUNXI_AHUB_I2S_OUT_SLOT */
|
|
+#define I2S_OUT_OFFSET 20
|
|
+#define I2S_OUT_SLOT_NUM 16
|
|
+#define I2S_OUT_SLOT_EN 0
|
|
+
|
|
+/* SUNXI_AHUB_I2S_IN_SLOT */
|
|
+#define I2S_IN_OFFSET 20
|
|
+#define I2S_IN_SLOT_NUM 16
|
|
+
|
|
+/* SUNXI_AHUB_DAM_CTL */
|
|
+#define DAM_CTL_RX2_NUM 24
|
|
+#define DAM_CTL_RX1_NUM 20
|
|
+#define DAM_CTL_RX0_NUM 16
|
|
+#define DAM_CTL_TX_NUM 8
|
|
+#define DAM_CTL_RX2EN 6
|
|
+#define DAM_CTL_RX1EN 5
|
|
+#define DAM_CTL_RX0EN 4
|
|
+#define DAM_CTL_TXEN 0
|
|
+
|
|
+/* SUNXI_AHUB_DAM_RX##chan##_SRC */
|
|
+#define DAM_RX_APBIF_TXDIF0 31
|
|
+#define DAM_RX_APBIF_TXDIF1 30
|
|
+#define DAM_RX_APBIF_TXDIF2 29
|
|
+#define DAM_RX_I2S0_TXDIF 27
|
|
+#define DAM_RX_I2S1_TXDIF 26
|
|
+#define DAM_RX_I2S2_TXDIF 25
|
|
+#define DAM_RX_I2S3_TXDIF 23
|
|
+#define DAM_RX_DAM0_TXDIF 19
|
|
+#define DAM_RX_DAM1_TXDIF 15
|
|
+
|
|
+struct sunxi_ahub_mem_info {
|
|
+ char *dev_name;
|
|
+ struct resource *res;
|
|
+ void __iomem *membase;
|
|
+ struct resource *memregion;
|
|
+ struct regmap *regmap;
|
|
+};
|
|
+
|
|
+struct sunxi_ahub_clk_info {
|
|
+ struct clk *clk_pll;
|
|
+ struct clk *clk_pllx4;
|
|
+ struct clk *clk_module;
|
|
+ struct clk *clk_bus;
|
|
+ struct reset_control *clk_rst;
|
|
+};
|
|
+
|
|
+extern int snd_soc_sunxi_ahub_mem_get(struct sunxi_ahub_mem_info *mem_info);
|
|
+extern int snd_soc_sunxi_ahub_clk_get(struct sunxi_ahub_clk_info *clk_info);
|
|
+
|
|
+#endif /* __SND_SUNXI_AHUB_DAM_H */
|
|
\ No newline at end of file
|
|
diff --git a/sound/soc/sunxi_v2/snd_sunxi_common.c b/sound/soc/sunxi_v2/snd_sunxi_common.c
|
|
new file mode 100644
|
|
index 000000000000..410ab75aea5a
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/snd_sunxi_common.c
|
|
@@ -0,0 +1,267 @@
|
|
+/*
|
|
+ * sound\soc\sunxi\snd_sunxi_common.c
|
|
+ * (C) Copyright 2021-2025
|
|
+ * AllWinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
+ * Dby <dby@allwinnertech.com>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of
|
|
+ * the License, or (at your option) any later version.
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+#include <linux/of.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/reset.h>
|
|
+#include <linux/device.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <linux/of_address.h>
|
|
+#include <linux/of_gpio.h>
|
|
+
|
|
+#include "snd_sunxi_log.h"
|
|
+#include "snd_sunxi_common.h"
|
|
+
|
|
+#define HLOG "COMMON"
|
|
+
|
|
+/* for regmap */
|
|
+int snd_sunxi_mem_init(struct platform_device *pdev,
|
|
+ struct sunxi_mem_info *mem_info)
|
|
+{
|
|
+ int ret = 0;
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ ret = of_address_to_resource(np, 0, mem_info->res);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "parse device node resource failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_of_addr_to_resource;
|
|
+ }
|
|
+
|
|
+ mem_info->memregion = devm_request_mem_region(&pdev->dev,
|
|
+ mem_info->res->start,
|
|
+ resource_size(mem_info->res),
|
|
+ mem_info->dev_name);
|
|
+ if (IS_ERR_OR_NULL(mem_info->memregion)) {
|
|
+ SND_LOG_ERR(HLOG, "memory region already claimed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_devm_request_region;
|
|
+ }
|
|
+
|
|
+ mem_info->membase = devm_ioremap(&pdev->dev,
|
|
+ mem_info->memregion->start,
|
|
+ resource_size(mem_info->memregion));
|
|
+ if (IS_ERR_OR_NULL(mem_info->membase)) {
|
|
+ SND_LOG_ERR(HLOG, "ioremap failed\n");
|
|
+ ret = -EBUSY;
|
|
+ goto err_devm_ioremap;
|
|
+ }
|
|
+
|
|
+ mem_info->regmap = devm_regmap_init_mmio(&pdev->dev,
|
|
+ mem_info->membase,
|
|
+ mem_info->regmap_config);
|
|
+ if (IS_ERR_OR_NULL(mem_info->regmap)) {
|
|
+ SND_LOG_ERR(HLOG, "regmap init failed\n");
|
|
+ ret = -EINVAL;
|
|
+ goto err_devm_regmap_init;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_devm_regmap_init:
|
|
+ devm_iounmap(&pdev->dev, mem_info->membase);
|
|
+err_devm_ioremap:
|
|
+ devm_release_mem_region(&pdev->dev, mem_info->memregion->start,
|
|
+ resource_size(mem_info->memregion));
|
|
+err_devm_request_region:
|
|
+err_of_addr_to_resource:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+void snd_sunxi_mem_exit(struct platform_device *pdev,
|
|
+ struct sunxi_mem_info *mem_info)
|
|
+{
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ devm_iounmap(&pdev->dev, mem_info->membase);
|
|
+ devm_release_mem_region(&pdev->dev, mem_info->memregion->start,
|
|
+ resource_size(mem_info->memregion));
|
|
+}
|
|
+
|
|
+/* for reg labels */
|
|
+int snd_sunxi_save_reg(struct regmap *regmap, struct reg_label *reg_labels)
|
|
+{
|
|
+ int i = 0;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ while (reg_labels[i].name != NULL) {
|
|
+ regmap_read(regmap,
|
|
+ reg_labels[i].address, &(reg_labels[i].value));
|
|
+ i++;
|
|
+ }
|
|
+
|
|
+ return i;
|
|
+}
|
|
+
|
|
+int snd_sunxi_echo_reg(struct regmap *regmap, struct reg_label *reg_labels)
|
|
+{
|
|
+ int i = 0;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ while (reg_labels[i].name != NULL) {
|
|
+ regmap_write(regmap,
|
|
+ reg_labels[i].address, reg_labels[i].value);
|
|
+ i++;
|
|
+ }
|
|
+
|
|
+ return i;
|
|
+}
|
|
+
|
|
+/* for pa config */
|
|
+struct pa_config *snd_sunxi_pa_pin_init(struct platform_device *pdev,
|
|
+ u32 *pa_pin_max)
|
|
+{
|
|
+ int ret, i;
|
|
+ u32 pin_max;
|
|
+ u32 gpio_tmp;
|
|
+ u32 temp_val;
|
|
+ char str[20] = {0};
|
|
+ struct pa_config *pa_cfg;
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ *pa_pin_max = 0;
|
|
+ ret = of_property_read_u32(np, "pa_pin_max", &temp_val);
|
|
+ if (ret < 0) {
|
|
+ SND_LOG_WARN(HLOG, "pa_pin_max get failed, default 0\n");
|
|
+ return NULL;
|
|
+ } else {
|
|
+ pin_max = temp_val;
|
|
+ }
|
|
+
|
|
+ pa_cfg = kzalloc(sizeof(struct pa_config) * pin_max, GFP_KERNEL);
|
|
+ if (!pa_cfg) {
|
|
+ SND_LOG_ERR(HLOG, "can't pa_config memory\n");
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < pin_max; i++) {
|
|
+ sprintf(str, "pa_pin_%d", i);
|
|
+ ret = of_get_named_gpio(np, str, 0);
|
|
+ if (ret < 0) {
|
|
+ SND_LOG_ERR(HLOG, "pa_pin_%u get failed\n", i);
|
|
+ pa_cfg[i].used = 0;
|
|
+ continue;
|
|
+ }
|
|
+ gpio_tmp = ret;
|
|
+ if (!gpio_is_valid(gpio_tmp)) {
|
|
+ SND_LOG_ERR(HLOG, "pa_pin_%u (%u) is invalid\n",
|
|
+ i, gpio_tmp);
|
|
+ pa_cfg[i].used = 0;
|
|
+ continue;
|
|
+ }
|
|
+ ret = devm_gpio_request(&pdev->dev, gpio_tmp, str);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "pa_pin_%u (%u) request failed\n",
|
|
+ i, gpio_tmp);
|
|
+ pa_cfg[i].used = 0;
|
|
+ continue;
|
|
+ }
|
|
+ pa_cfg[i].used = 1;
|
|
+ pa_cfg[i].pin = gpio_tmp;
|
|
+
|
|
+ sprintf(str, "pa_pin_level_%d", i);
|
|
+ ret = of_property_read_u32(np, str, &temp_val);
|
|
+ if (ret < 0) {
|
|
+ SND_LOG_WARN(HLOG, "%s get failed, default low\n", str);
|
|
+ pa_cfg[i].level = 0;
|
|
+ } else {
|
|
+ if (temp_val > 0)
|
|
+ pa_cfg[i].level = 1;
|
|
+ }
|
|
+ sprintf(str, "pa_pin_msleep_%d", i);
|
|
+ ret = of_property_read_u32(np, str, &temp_val);
|
|
+ if (ret < 0) {
|
|
+ SND_LOG_WARN(HLOG, "%s get failed, default 0\n", str);
|
|
+ pa_cfg[i].msleep = 0;
|
|
+ } else {
|
|
+ pa_cfg[i].msleep = temp_val;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ *pa_pin_max = pin_max;
|
|
+ snd_sunxi_pa_pin_disable(pa_cfg, pin_max);
|
|
+
|
|
+ return pa_cfg;
|
|
+}
|
|
+
|
|
+void snd_sunxi_pa_pin_exit(struct platform_device *pdev,
|
|
+ struct pa_config *pa_cfg, u32 pa_pin_max)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ snd_sunxi_pa_pin_disable(pa_cfg, pa_pin_max);
|
|
+
|
|
+ for (i = 0; i < pa_pin_max; i++) {
|
|
+ if (!pa_cfg[i].used)
|
|
+ continue;
|
|
+
|
|
+ gpio_free(pa_cfg[i].pin);
|
|
+ }
|
|
+
|
|
+ if (pa_cfg)
|
|
+ kfree(pa_cfg);
|
|
+}
|
|
+
|
|
+int snd_sunxi_pa_pin_enable(struct pa_config *pa_cfg, u32 pa_pin_max)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (pa_pin_max < 1) {
|
|
+ SND_LOG_DEBUG(HLOG, "no pa pin config\n");
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < pa_pin_max; i++) {
|
|
+ if (!pa_cfg[i].used)
|
|
+ continue;
|
|
+
|
|
+ gpio_direction_output(pa_cfg[i].pin, 1);
|
|
+ gpio_set_value(pa_cfg[i].pin, pa_cfg[i].level);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void snd_sunxi_pa_pin_disable(struct pa_config *pa_cfg, u32 pa_pin_max)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (pa_pin_max < 1) {
|
|
+ SND_LOG_DEBUG(HLOG, "no pa pin config\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < pa_pin_max; i++) {
|
|
+ if (!pa_cfg[i].used)
|
|
+ continue;
|
|
+
|
|
+ gpio_direction_output(pa_cfg[i].pin, 1);
|
|
+ gpio_set_value(pa_cfg[i].pin, !pa_cfg[i].level);
|
|
+ }
|
|
+}
|
|
diff --git a/sound/soc/sunxi_v2/snd_sunxi_common.h b/sound/soc/sunxi_v2/snd_sunxi_common.h
|
|
new file mode 100644
|
|
index 000000000000..7b88d20c25e0
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/snd_sunxi_common.h
|
|
@@ -0,0 +1,67 @@
|
|
+/* sound\soc\sunxi\snd_sunxi_common.h
|
|
+ * (C) Copyright 2021-2025
|
|
+ * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
+ * Dby <dby@allwinnertech.com>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of
|
|
+ * the License, or (at your option) any later version.
|
|
+ */
|
|
+
|
|
+#ifndef __SND_SUNXI_COMMON_H
|
|
+#define __SND_SUNXI_COMMON_H
|
|
+
|
|
+/* for regmap */
|
|
+struct sunxi_mem_info {
|
|
+ char *dev_name;
|
|
+ struct resource *res;
|
|
+ struct regmap_config *regmap_config;
|
|
+
|
|
+ void __iomem *membase;
|
|
+ struct resource *memregion;
|
|
+ struct regmap *regmap;
|
|
+};
|
|
+
|
|
+int snd_sunxi_mem_init(struct platform_device *pdev,
|
|
+ struct sunxi_mem_info *mem_info);
|
|
+void snd_sunxi_mem_exit(struct platform_device *pdev,
|
|
+ struct sunxi_mem_info *mem_info);
|
|
+
|
|
+/* for reg debug */
|
|
+#define REG_LABEL(constant) {#constant, constant, 0}
|
|
+#define REG_LABEL_END {NULL, 0, 0}
|
|
+
|
|
+struct reg_label {
|
|
+ const char *name;
|
|
+ const unsigned int address;
|
|
+ unsigned int value;
|
|
+};
|
|
+
|
|
+/* EX:
|
|
+ * static struct reg_label reg_labels[] = {
|
|
+ * REG_LABEL(SUNXI_REG_0),
|
|
+ * REG_LABEL(SUNXI_REG_1),
|
|
+ * REG_LABEL(SUNXI_REG_n),
|
|
+ * REG_LABEL_END,
|
|
+ * };
|
|
+ */
|
|
+int snd_sunxi_save_reg(struct regmap *regmap, struct reg_label *reg_labels);
|
|
+int snd_sunxi_echo_reg(struct regmap *regmap, struct reg_label *reg_labels);
|
|
+
|
|
+/* for pa config */
|
|
+struct pa_config {
|
|
+ u32 pin;
|
|
+ u32 msleep;
|
|
+ bool used;
|
|
+ bool level;
|
|
+};
|
|
+
|
|
+struct pa_config *snd_sunxi_pa_pin_init(struct platform_device *pdev,
|
|
+ u32 *pa_pin_max);
|
|
+void snd_sunxi_pa_pin_exit(struct platform_device *pdev,
|
|
+ struct pa_config *pa_cfg, u32 pa_pin_max);
|
|
+int snd_sunxi_pa_pin_enable(struct pa_config *pa_cfg, u32 pa_pin_max);
|
|
+void snd_sunxi_pa_pin_disable(struct pa_config *pa_cfg, u32 pa_pin_max);
|
|
+
|
|
+#endif /* __SND_SUNXI_COMMON_H */
|
|
\ No newline at end of file
|
|
diff --git a/sound/soc/sunxi_v2/snd_sunxi_log.h b/sound/soc/sunxi_v2/snd_sunxi_log.h
|
|
new file mode 100644
|
|
index 000000000000..89ad9fe71936
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/snd_sunxi_log.h
|
|
@@ -0,0 +1,29 @@
|
|
+/*
|
|
+ * sound\soc\sunxi\snd_sunxi_log.h
|
|
+ * (C) Copyright 2021-2025
|
|
+ * allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
+ * Dby <dby@allwinnertech.com>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of
|
|
+ * the License, or (at your option) any later version.
|
|
+ */
|
|
+
|
|
+#ifndef __SND_SUNXI_LOG_H
|
|
+#define __SND_SUNXI_LOG_H
|
|
+#include <linux/kernel.h>
|
|
+
|
|
+#define SND_LOG_ERR(head, fmt, arg...) \
|
|
+ pr_err("[sound %4d][" head " %s] " fmt, __LINE__, __func__, ##arg)
|
|
+
|
|
+#define SND_LOG_WARN(head, fmt, arg...) \
|
|
+ pr_warn("[sound %4d][" head " %s] " fmt, __LINE__, __func__, ##arg)
|
|
+
|
|
+#define SND_LOG_INFO(head, fmt, arg...) \
|
|
+ pr_info("[sound %4d][" head " %s] " fmt, __LINE__, __func__, ##arg)
|
|
+
|
|
+#define SND_LOG_DEBUG(head, fmt, arg...) \
|
|
+ pr_debug("[sound %4d][" head " %s] " fmt, __LINE__, __func__, ##arg)
|
|
+
|
|
+#endif /* __SND_SUNXI_LOG_H */
|
|
diff --git a/sound/soc/sunxi_v2/snd_sunxi_mach.c b/sound/soc/sunxi_v2/snd_sunxi_mach.c
|
|
new file mode 100644
|
|
index 000000000000..27449ad6b843
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/snd_sunxi_mach.c
|
|
@@ -0,0 +1,479 @@
|
|
+/*
|
|
+ * sound\soc\sunxi\snd_sunxi_mach.c
|
|
+ * (C) Copyright 2021-2025
|
|
+ * AllWinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
+ * Dby <dby@allwinnertech.com>
|
|
+ *
|
|
+ * based on ${LINUX}/sound/soc/generic/simple-card.c
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of
|
|
+ * the License, or (at your option) any later version.
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <sound/soc.h>
|
|
+
|
|
+#include "snd_sunxi_log.h"
|
|
+#include "snd_sunxi_mach.h"
|
|
+
|
|
+#define HLOG "MACH"
|
|
+#define DAI "sound-dai"
|
|
+#define CELL "#sound-dai-cells"
|
|
+#define PREFIX "soundcard-mach,"
|
|
+
|
|
+#define DRV_NAME "sunxi-snd-mach"
|
|
+
|
|
+static void asoc_simple_shutdown(struct snd_pcm_substream *substream)
|
|
+{
|
|
+}
|
|
+
|
|
+static int asoc_simple_startup(struct snd_pcm_substream *substream)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int asoc_simple_hw_params(struct snd_pcm_substream *substream,
|
|
+ struct snd_pcm_hw_params *params)
|
|
+{
|
|
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
|
+
|
|
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
|
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
|
+
|
|
+ struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(rtd->card);
|
|
+ struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, rtd->num);
|
|
+ struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num);
|
|
+ struct asoc_simple_dai *dais = priv->dais;
|
|
+ unsigned int mclk;
|
|
+ unsigned int cpu_pll_clk, codec_pll_clk;
|
|
+ unsigned int cpu_bclk_ratio, codec_bclk_ratio;
|
|
+ unsigned int freq_point;
|
|
+ int cpu_clk_div, codec_clk_div;
|
|
+ int ret = 0;
|
|
+
|
|
+ switch (params_rate(params)) {
|
|
+ case 8000:
|
|
+ case 12000:
|
|
+ case 16000:
|
|
+ case 24000:
|
|
+ case 32000:
|
|
+ case 48000:
|
|
+ case 64000:
|
|
+ case 96000:
|
|
+ case 192000:
|
|
+ freq_point = 24576000;
|
|
+ break;
|
|
+ case 11025:
|
|
+ case 22050:
|
|
+ case 44100:
|
|
+ case 88200:
|
|
+ case 176400:
|
|
+ freq_point = 22579200;
|
|
+ break;
|
|
+ default:
|
|
+ SND_LOG_ERR(HLOG, "Invalid rate %d\n", params_rate(params));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ /* for cpudai pll clk */
|
|
+ cpu_pll_clk = freq_point * dai_props->cpu_pll_fs;
|
|
+ codec_pll_clk = freq_point * dai_props->codec_pll_fs;
|
|
+ cpu_clk_div = cpu_pll_clk / params_rate(params);
|
|
+ codec_clk_div = codec_pll_clk / params_rate(params);
|
|
+ SND_LOG_DEBUG(HLOG, "freq point : %u\n", freq_point);
|
|
+ SND_LOG_DEBUG(HLOG, "cpu pllclk : %u\n", cpu_pll_clk);
|
|
+ SND_LOG_DEBUG(HLOG, "codec pllclk : %u\n", codec_pll_clk);
|
|
+ SND_LOG_DEBUG(HLOG, "cpu clk_div : %u\n", cpu_clk_div);
|
|
+ SND_LOG_DEBUG(HLOG, "codec clk_div: %u\n", codec_clk_div);
|
|
+
|
|
+ if (cpu_dai->driver->ops->set_pll) {
|
|
+ ret = snd_soc_dai_set_pll(cpu_dai, substream->stream, 0,
|
|
+ cpu_pll_clk, cpu_pll_clk);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "cpu_dai set pllclk failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+ if (codec_dai->driver->ops->set_pll) {
|
|
+ ret = snd_soc_dai_set_pll(codec_dai, substream->stream, 0,
|
|
+ codec_pll_clk, codec_pll_clk);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "codec_dai set pllclk failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (cpu_dai->driver->ops->set_clkdiv) {
|
|
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, 0, cpu_clk_div);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "cpu_dai set clk_div failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+ if (codec_dai->driver->ops->set_clkdiv) {
|
|
+ ret = snd_soc_dai_set_clkdiv(codec_dai, 0, codec_clk_div);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "cadec_dai set clk_div failed.\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* use for tdm only */
|
|
+ if (!(dais->slots && dais->slot_width))
|
|
+ return 0;
|
|
+
|
|
+ /* for cpudai & codecdai mclk */
|
|
+ if (dai_props->mclk_fp)
|
|
+ mclk = (freq_point >> 1) * dai_props->mclk_fs;
|
|
+ else
|
|
+ mclk = params_rate(params) * dai_props->mclk_fs;
|
|
+ cpu_bclk_ratio = cpu_pll_clk / (params_rate(params) * dais->slot_width * dais->slots);
|
|
+ codec_bclk_ratio = codec_pll_clk / (params_rate(params) * dais->slot_width * dais->slots);
|
|
+ SND_LOG_DEBUG(HLOG, "mclk : %u\n", mclk);
|
|
+ SND_LOG_DEBUG(HLOG, "cpu_bclk_ratio : %u\n", cpu_bclk_ratio);
|
|
+ SND_LOG_DEBUG(HLOG, "codec_bclk_ratio: %u\n", codec_bclk_ratio);
|
|
+
|
|
+ if (cpu_dai->driver->ops->set_sysclk) {
|
|
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "cpu_dai set sysclk(mclk) failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+ if (codec_dai->driver->ops->set_sysclk) {
|
|
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, SND_SOC_CLOCK_IN);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "cadec_dai set sysclk(mclk) failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (cpu_dai->driver->ops->set_bclk_ratio) {
|
|
+ ret = snd_soc_dai_set_bclk_ratio(cpu_dai, cpu_bclk_ratio);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "cpu_dai set bclk failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+ if (codec_dai->driver->ops->set_bclk_ratio) {
|
|
+ ret = snd_soc_dai_set_bclk_ratio(codec_dai, codec_bclk_ratio);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "codec_dai set bclk failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (cpu_dai->driver->ops->set_fmt) {
|
|
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_link->dai_fmt);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "cpu dai set fmt failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+ if (codec_dai->driver->ops->set_fmt) {
|
|
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_link->dai_fmt);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "codec dai set fmt failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (cpu_dai->driver->ops->set_tdm_slot) {
|
|
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, dais->slots, dais->slot_width);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "cpu dai set tdm slot failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+ if (codec_dai->driver->ops->set_tdm_slot) {
|
|
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, dais->slots, dais->slot_width);
|
|
+ if (ret) {
|
|
+ SND_LOG_ERR(HLOG, "codec dai set tdm slot failed\n");
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct snd_soc_ops simple_ops = {
|
|
+ .startup = asoc_simple_startup,
|
|
+ .shutdown = asoc_simple_shutdown,
|
|
+ .hw_params = asoc_simple_hw_params,
|
|
+};
|
|
+
|
|
+static int asoc_simple_dai_init(struct snd_soc_pcm_runtime *rtd)
|
|
+{
|
|
+ int i;
|
|
+ struct snd_soc_card *card = rtd->card;
|
|
+ struct snd_soc_dapm_context *dapm = &card->dapm;
|
|
+
|
|
+ const struct snd_kcontrol_new *controls = card->controls;
|
|
+
|
|
+ for (i = 0; i < card->num_controls; i++)
|
|
+ if (controls[i].info == snd_soc_dapm_info_pin_switch)
|
|
+ snd_soc_dapm_disable_pin(dapm,
|
|
+ (const char *)controls[i].private_value);
|
|
+
|
|
+ if (card->num_controls)
|
|
+ snd_soc_dapm_sync(dapm);
|
|
+
|
|
+ /* snd_soc_dai_set_sysclk(); */
|
|
+ /* snd_soc_dai_set_tdm_slot(); */
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int simple_dai_link_of(struct device_node *node,
|
|
+ struct asoc_simple_priv *priv)
|
|
+{
|
|
+ struct device *dev = simple_priv_to_dev(priv);
|
|
+ struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, 0);
|
|
+ struct simple_dai_props *dai_props = simple_priv_to_props(priv, 0);
|
|
+ struct device_node *top_np = NULL;
|
|
+ struct device_node *cpu = NULL;
|
|
+ struct device_node *plat = NULL;
|
|
+ struct device_node *codec = NULL;
|
|
+ char prop[128];
|
|
+ char *prefix = "";
|
|
+ int ret, single_cpu;
|
|
+
|
|
+ prefix = PREFIX;
|
|
+ top_np = node;
|
|
+
|
|
+ snprintf(prop, sizeof(prop), "%scpu", prefix);
|
|
+ cpu = of_get_child_by_name(top_np, prop);
|
|
+ if (!cpu) {
|
|
+ ret = -EINVAL;
|
|
+ SND_LOG_ERR(HLOG, "Can't find %s DT node\n", prop);
|
|
+ goto dai_link_of_err;
|
|
+ }
|
|
+ snprintf(prop, sizeof(prop), "%splat", prefix);
|
|
+ plat = of_get_child_by_name(top_np, prop);
|
|
+
|
|
+ snprintf(prop, sizeof(prop), "%scodec", prefix);
|
|
+ codec = of_get_child_by_name(top_np, prop);
|
|
+ if (!codec) {
|
|
+ ret = -EINVAL;
|
|
+ SND_LOG_ERR(HLOG, "Can't find %s DT node\n", prop);
|
|
+ goto dai_link_of_err;
|
|
+ }
|
|
+
|
|
+ ret = asoc_simple_parse_daifmt(top_np, codec, prefix, &dai_link->dai_fmt);
|
|
+ if (ret < 0)
|
|
+ goto dai_link_of_err;
|
|
+ /* sunxi: parse stream direction
|
|
+ * ex1)
|
|
+ * top_node {
|
|
+ * PREFIXplayback-only;
|
|
+ * }
|
|
+ * ex2)
|
|
+ * top_node {
|
|
+ * PREFIXcapture-only;
|
|
+ * }
|
|
+ */
|
|
+ ret = asoc_simple_parse_daistream(top_np, prefix, dai_link);
|
|
+ if (ret < 0)
|
|
+ goto dai_link_of_err;
|
|
+ /* sunxi: parse slot-num & slot-width
|
|
+ * ex)
|
|
+ * top_node {
|
|
+ * PREFIXplayslot-num = <x>;
|
|
+ * PREFIXplayslot-width = <x>;
|
|
+ * }
|
|
+ */
|
|
+ ret = asoc_simple_parse_tdm_slot(top_np, prefix, priv->dais);
|
|
+ if (ret < 0)
|
|
+ goto dai_link_of_err;
|
|
+
|
|
+ ret = asoc_simple_parse_cpu(cpu, dai_link, DAI, CELL, &single_cpu);
|
|
+ if (ret < 0)
|
|
+ goto dai_link_of_err;
|
|
+ ret = asoc_simple_parse_codec(codec, dai_link, DAI, CELL);
|
|
+ if (ret < 0) {
|
|
+ if (ret == -EPROBE_DEFER)
|
|
+ goto dai_link_of_err;
|
|
+ dai_link->codecs->name = "snd-soc-dummy";
|
|
+ dai_link->codecs->dai_name = "snd-soc-dummy-dai";
|
|
+ /* dai_link->codecs->name = "sunxi-dummy-codec"; */
|
|
+ /* dai_link->codecs->dai_name = "sunxi-dummy-codec-dai"; */
|
|
+ SND_LOG_DEBUG(HLOG, "use dummy codec for simple card.\n");
|
|
+ }
|
|
+ ret = asoc_simple_parse_platform(plat, dai_link, DAI, CELL);
|
|
+ if (ret < 0)
|
|
+ goto dai_link_of_err;
|
|
+
|
|
+ /* sunxi: parse pll-fs & mclk-fs
|
|
+ * ex)
|
|
+ * top_node {
|
|
+ * PREFIXcpu {
|
|
+ * PREFIXpll-fs = <x>;
|
|
+ * PREFIXmclk-fs = <x>;
|
|
+ * }
|
|
+ * }
|
|
+ */
|
|
+ ret = asoc_simple_parse_tdm_clk(cpu, codec, prefix, dai_props);
|
|
+ if (ret < 0)
|
|
+ goto dai_link_of_err;
|
|
+
|
|
+ ret = asoc_simple_set_dailink_name(dev, dai_link,
|
|
+ "%s-%s",
|
|
+ dai_link->cpus->dai_name,
|
|
+ dai_link->codecs->dai_name);
|
|
+ if (ret < 0)
|
|
+ goto dai_link_of_err;
|
|
+
|
|
+ dai_link->ops = &simple_ops;
|
|
+ dai_link->init = asoc_simple_dai_init;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "name : %s\n", dai_link->stream_name);
|
|
+ SND_LOG_DEBUG(HLOG, "format : %x\n", dai_link->dai_fmt);
|
|
+ SND_LOG_DEBUG(HLOG, "cpu : %s\n", dai_link->cpus->name);
|
|
+ SND_LOG_DEBUG(HLOG, "codec : %s\n", dai_link->codecs->name);
|
|
+
|
|
+ asoc_simple_canonicalize_cpu(dai_link, single_cpu);
|
|
+ asoc_simple_canonicalize_platform(dai_link);
|
|
+
|
|
+dai_link_of_err:
|
|
+ of_node_put(cpu);
|
|
+ of_node_put(plat);
|
|
+ of_node_put(codec);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int simple_parse_of(struct asoc_simple_priv *priv)
|
|
+{
|
|
+ int ret;
|
|
+ struct device *dev = simple_priv_to_dev(priv);
|
|
+ struct snd_soc_card *card = simple_priv_to_card(priv);
|
|
+ struct device_node *top_np = dev->of_node;
|
|
+
|
|
+ SND_LOG_DEBUG(HLOG, "\n");
|
|
+
|
|
+ if (!top_np)
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* DAPM widgets */
|
|
+ ret = asoc_simple_parse_widgets(card, PREFIX);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* DAPM routes */
|
|
+ ret = asoc_simple_parse_routing(card, PREFIX);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* DAPM pin_switches */
|
|
+ ret = asoc_simple_parse_pin_switches(card, PREFIX);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ /* For single DAI link & old style of DT node */
|
|
+ ret = simple_dai_link_of(top_np, priv);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = asoc_simple_parse_card_name(card, PREFIX);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int simple_soc_probe(struct snd_soc_card *card)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int asoc_simple_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct device_node *top_np = dev->of_node;
|
|
+ struct asoc_simple_priv *priv;
|
|
+ struct snd_soc_card *card;
|
|
+ int ret;
|
|
+
|
|
+ /* Allocate the private data and the DAI link array */
|
|
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
+ if (!priv)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ card = simple_priv_to_card(priv);
|
|
+ card->owner = THIS_MODULE;
|
|
+ card->dev = dev;
|
|
+ card->probe = simple_soc_probe;
|
|
+
|
|
+ ret = asoc_simple_init_priv(priv);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ if (top_np && of_device_is_available(top_np)) {
|
|
+ ret = simple_parse_of(priv);
|
|
+ if (ret < 0) {
|
|
+ if (ret != -EPROBE_DEFER)
|
|
+ SND_LOG_ERR(HLOG, "parse error %d\n", ret);
|
|
+ goto err;
|
|
+ }
|
|
+ } else {
|
|
+ SND_LOG_ERR(HLOG, "simple card dts available\n");
|
|
+ }
|
|
+
|
|
+ snd_soc_card_set_drvdata(card, priv);
|
|
+
|
|
+ /* asoc_simple_debug_info(priv); */
|
|
+ ret = devm_snd_soc_register_card(dev, card);
|
|
+ if (ret >= 0)
|
|
+ return ret;
|
|
+err:
|
|
+ asoc_simple_clean_reference(card);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int asoc_simple_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
|
|
+
|
|
+ return asoc_simple_clean_reference(card);
|
|
+}
|
|
+
|
|
+static const struct of_device_id snd_soc_sunxi_of_match[] = {
|
|
+ { .compatible = "allwinner," DRV_NAME, },
|
|
+ {},
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, snd_soc_sunxi_of_match);
|
|
+
|
|
+static struct platform_driver sunxi_soundcard_machine_driver = {
|
|
+ .driver = {
|
|
+ .name = DRV_NAME,
|
|
+ .pm = &snd_soc_pm_ops,
|
|
+ .of_match_table = snd_soc_sunxi_of_match,
|
|
+ },
|
|
+ .probe = asoc_simple_probe,
|
|
+ .remove = asoc_simple_remove,
|
|
+};
|
|
+
|
|
+int __init sunxi_soundcard_machine_dev_init(void)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = platform_driver_register(&sunxi_soundcard_machine_driver);
|
|
+ if (ret != 0) {
|
|
+ SND_LOG_ERR(HLOG, "platform driver register failed\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+void __exit sunxi_soundcard_machine_dev_exit(void)
|
|
+{
|
|
+ platform_driver_unregister(&sunxi_soundcard_machine_driver);
|
|
+}
|
|
+
|
|
+late_initcall(sunxi_soundcard_machine_dev_init);
|
|
+module_exit(sunxi_soundcard_machine_dev_exit);
|
|
+
|
|
+MODULE_AUTHOR("Dby@allwinnertech.com");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DESCRIPTION("sunxi soundcard machine");
|
|
diff --git a/sound/soc/sunxi_v2/snd_sunxi_mach.h b/sound/soc/sunxi_v2/snd_sunxi_mach.h
|
|
new file mode 100644
|
|
index 000000000000..ab429c8841ab
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/snd_sunxi_mach.h
|
|
@@ -0,0 +1,17 @@
|
|
+/* sound\soc\sunxi\snd_sunxi_mach.h
|
|
+ * (C) Copyright 2021-2025
|
|
+ * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
+ * Dby <dby@allwinnertech.com>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of
|
|
+ * the License, or (at your option) any later version.
|
|
+ */
|
|
+
|
|
+#ifndef __SND_SUNXI_MACH_H
|
|
+#define __SND_SUNXI_MACH_H
|
|
+
|
|
+#include "snd_sunxi_mach_utils.h"
|
|
+
|
|
+#endif /* __SND_SUNXI_MACH_H */
|
|
diff --git a/sound/soc/sunxi_v2/snd_sunxi_mach_utils.c b/sound/soc/sunxi_v2/snd_sunxi_mach_utils.c
|
|
new file mode 100644
|
|
index 000000000000..15f474e5cbeb
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/snd_sunxi_mach_utils.c
|
|
@@ -0,0 +1,422 @@
|
|
+/*
|
|
+ * sound\soc\sunxi\snd_sunxi_mach_utils.c
|
|
+ * (C) Copyright 2021-2025
|
|
+ * AllWinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
+ * Dby <dby@allwinnertech.com>
|
|
+ *
|
|
+ * based on ${LINUX}/sound/soc/generic/simple-card.c
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of
|
|
+ * the License, or (at your option) any later version.
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/of.h>
|
|
+#include <sound/soc.h>
|
|
+
|
|
+#include "snd_sunxi_log.h"
|
|
+#include "snd_sunxi_mach_utils.h"
|
|
+
|
|
+#define HLOG "mach_utils"
|
|
+
|
|
+int asoc_simple_clean_reference(struct snd_soc_card *card)
|
|
+{
|
|
+ struct snd_soc_dai_link *dai_link;
|
|
+ int i;
|
|
+
|
|
+ for_each_card_prelinks(card, i, dai_link) {
|
|
+ of_node_put(dai_link->cpus->of_node);
|
|
+ of_node_put(dai_link->codecs->of_node);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int asoc_simple_init_priv(struct asoc_simple_priv *priv)
|
|
+{
|
|
+ struct snd_soc_card *card = simple_priv_to_card(priv);
|
|
+ struct device *dev = simple_priv_to_dev(priv);
|
|
+ struct snd_soc_dai_link *dai_link;
|
|
+ struct simple_dai_props *dai_props;
|
|
+ struct asoc_simple_dai *dais;
|
|
+ struct snd_soc_codec_conf *cconf = NULL;
|
|
+
|
|
+ dai_props = devm_kcalloc(dev, 1, sizeof(*dai_props), GFP_KERNEL);
|
|
+ dai_link = devm_kcalloc(dev, 1, sizeof(*dai_link), GFP_KERNEL);
|
|
+ dais = devm_kcalloc(dev, 1, sizeof(*dais), GFP_KERNEL);
|
|
+ if (!dai_props || !dai_link || !dais)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ /*
|
|
+ if (li->conf) {
|
|
+ cconf = devm_kcalloc(dev, li->conf, sizeof(*cconf), GFP_KERNEL);
|
|
+ if (!cconf)
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ */
|
|
+
|
|
+ /*
|
|
+ * Use snd_soc_dai_link_component instead of legacy style
|
|
+ * It is codec only. but cpu/platform will be supported in the future.
|
|
+ * see
|
|
+ * soc-core.c :: snd_soc_init_multicodec()
|
|
+ *
|
|
+ * "platform" might be removed
|
|
+ * see
|
|
+ * simple-card-utils.c :: asoc_simple_canonicalize_platform()
|
|
+ */
|
|
+ dai_link->cpus = &dai_props->cpus;
|
|
+ dai_link->num_cpus = 1;
|
|
+ dai_link->codecs = &dai_props->codecs;
|
|
+ dai_link->num_codecs = 1;
|
|
+ dai_link->platforms = &dai_props->platforms;
|
|
+ dai_link->num_platforms = 1;
|
|
+
|
|
+ priv->dai_props = dai_props;
|
|
+ priv->dai_link = dai_link;
|
|
+ priv->dais = dais;
|
|
+ priv->codec_conf = cconf;
|
|
+
|
|
+ card->dai_link = priv->dai_link;
|
|
+ card->num_links = 1;
|
|
+ card->codec_conf = cconf;
|
|
+ card->num_configs = 0;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int asoc_simple_parse_widgets(struct snd_soc_card *card, char *prefix)
|
|
+{
|
|
+ struct device_node *node = card->dev->of_node;
|
|
+ char prop[128];
|
|
+
|
|
+ if (!prefix)
|
|
+ prefix = "";
|
|
+
|
|
+ snprintf(prop, sizeof(prop), "%s%s", prefix, "widgets");
|
|
+
|
|
+ if (of_property_read_bool(node, prop))
|
|
+ return snd_soc_of_parse_audio_simple_widgets(card, prop);
|
|
+
|
|
+ /* no widgets is not error */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int asoc_simple_parse_routing(struct snd_soc_card *card, char *prefix)
|
|
+{
|
|
+ struct device_node *node = card->dev->of_node;
|
|
+ char prop[128];
|
|
+
|
|
+ if (!prefix)
|
|
+ prefix = "";
|
|
+
|
|
+ snprintf(prop, sizeof(prop), "%s%s", prefix, "routing");
|
|
+
|
|
+ if (!of_property_read_bool(node, prop))
|
|
+ return 0;
|
|
+
|
|
+ return snd_soc_of_parse_audio_routing(card, prop);
|
|
+}
|
|
+
|
|
+int asoc_simple_parse_pin_switches(struct snd_soc_card *card, char *prefix)
|
|
+{
|
|
+ const unsigned int nb_controls_max = 16;
|
|
+ const char **strings, *control_name;
|
|
+ struct snd_kcontrol_new *controls;
|
|
+ struct device *dev = card->dev;
|
|
+ unsigned int i, nb_controls;
|
|
+ char prop[128];
|
|
+ int ret;
|
|
+
|
|
+ if (!prefix)
|
|
+ prefix = "";
|
|
+
|
|
+ snprintf(prop, sizeof(prop), "%s%s", prefix, "pin-switches");
|
|
+
|
|
+ if (!of_property_read_bool(dev->of_node, prop))
|
|
+ return 0;
|
|
+
|
|
+ strings = devm_kcalloc(dev, nb_controls_max,
|
|
+ sizeof(*strings), GFP_KERNEL);
|
|
+ if (!strings)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ret = of_property_read_string_array(dev->of_node, prop,
|
|
+ strings, nb_controls_max);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ nb_controls = (unsigned int)ret;
|
|
+
|
|
+ controls = devm_kcalloc(dev, nb_controls,
|
|
+ sizeof(*controls), GFP_KERNEL);
|
|
+ if (!controls)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ for (i = 0; i < nb_controls; i++) {
|
|
+ control_name = devm_kasprintf(dev, GFP_KERNEL,
|
|
+ "%s Switch", strings[i]);
|
|
+ if (!control_name)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ controls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
|
|
+ controls[i].name = control_name;
|
|
+ controls[i].info = snd_soc_dapm_info_pin_switch;
|
|
+ controls[i].get = snd_soc_dapm_get_pin_switch;
|
|
+ controls[i].put = snd_soc_dapm_put_pin_switch;
|
|
+ controls[i].private_value = (unsigned long)strings[i];
|
|
+ }
|
|
+
|
|
+ card->controls = controls;
|
|
+ card->num_controls = nb_controls;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int asoc_simple_parse_daifmt(struct device_node *node,
|
|
+ struct device_node *codec,
|
|
+ char *prefix,
|
|
+ unsigned int *retfmt)
|
|
+{
|
|
+ struct device_node *bitclkmaster = NULL;
|
|
+ struct device_node *framemaster = NULL;
|
|
+ unsigned int daifmt;
|
|
+
|
|
+ daifmt = snd_soc_daifmt_parse_format(node, prefix);
|
|
+
|
|
+ snd_soc_daifmt_parse_clock_provider_as_phandle(node, prefix, &bitclkmaster, &framemaster);
|
|
+ if (!bitclkmaster && !framemaster) {
|
|
+ /*
|
|
+ * No dai-link level and master setting was not found from
|
|
+ * sound node level, revert back to legacy DT parsing and
|
|
+ * take the settings from codec node.
|
|
+ */
|
|
+ SND_LOG_DEBUG(HLOG, "Revert to legacy daifmt parsing\n");
|
|
+
|
|
+ daifmt |= snd_soc_daifmt_parse_clock_provider_as_flag(codec, NULL);
|
|
+ } else {
|
|
+ daifmt |= snd_soc_daifmt_clock_provider_from_bitmap(
|
|
+ ((codec == bitclkmaster) << 4) | (codec == framemaster));
|
|
+ }
|
|
+
|
|
+ of_node_put(bitclkmaster);
|
|
+ of_node_put(framemaster);
|
|
+
|
|
+ *retfmt = daifmt;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int asoc_simple_parse_daistream(struct device_node *node, char *prefix,
|
|
+ struct snd_soc_dai_link *dai_link)
|
|
+{
|
|
+ char prop[128];
|
|
+
|
|
+ if (!prefix)
|
|
+ prefix = "";
|
|
+
|
|
+ /* check "[prefix]playback-only" */
|
|
+ snprintf(prop, sizeof(prop), "%splayback-only", prefix);
|
|
+ if (of_property_read_bool(node, prop))
|
|
+ dai_link->playback_only = 1;
|
|
+
|
|
+ /* check "[prefix]capture-only" */
|
|
+ snprintf(prop, sizeof(prop), "%scapture-only", prefix);
|
|
+ if (of_property_read_bool(node, prop))
|
|
+ dai_link->capture_only = 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int asoc_simple_parse_tdm_slot(struct device_node *node, char *prefix,
|
|
+ struct asoc_simple_dai *dais)
|
|
+{
|
|
+ int ret;
|
|
+ char prop[128];
|
|
+ unsigned int val;
|
|
+
|
|
+ if (!prefix)
|
|
+ prefix = "";
|
|
+
|
|
+ snprintf(prop, sizeof(prop), "%sslot-num", prefix);
|
|
+ ret = of_property_read_u32(node, prop, &val);
|
|
+ if (!ret)
|
|
+ dais->slots = val;
|
|
+
|
|
+ snprintf(prop, sizeof(prop), "%sslot-width", prefix);
|
|
+ ret = of_property_read_u32(node, prop, &val);
|
|
+ if (!ret)
|
|
+ dais->slot_width = val;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int asoc_simple_parse_tdm_clk(struct device_node *cpu,
|
|
+ struct device_node *codec,
|
|
+ char *prefix,
|
|
+ struct simple_dai_props *dai_props)
|
|
+{
|
|
+ int ret;
|
|
+ char prop[128];
|
|
+ unsigned int val;
|
|
+
|
|
+ if (!prefix)
|
|
+ prefix = "";
|
|
+
|
|
+ snprintf(prop, sizeof(prop), "%spll-fs", prefix);
|
|
+ ret = of_property_read_u32(cpu, prop, &val);
|
|
+ if (ret)
|
|
+ dai_props->cpu_pll_fs = 1; /* default sysclk 24.576 or 22.5792MHz * 1 */
|
|
+ else
|
|
+ dai_props->cpu_pll_fs = val;
|
|
+
|
|
+ ret = of_property_read_u32(codec, prop, &val);
|
|
+ if (ret)
|
|
+ dai_props->codec_pll_fs = 1; /* default sysclk 24.576 or 22.5792MHz * 1 */
|
|
+ else
|
|
+ dai_props->codec_pll_fs = val;
|
|
+
|
|
+ snprintf(prop, sizeof(prop), "%smclk-fp", prefix);
|
|
+ dai_props->mclk_fp = of_property_read_bool(cpu, prop);
|
|
+
|
|
+ snprintf(prop, sizeof(prop), "%smclk-fs", prefix);
|
|
+ ret = of_property_read_u32(cpu, prop, &val);
|
|
+ if (ret)
|
|
+ dai_props->mclk_fs = 0; /* default mclk 0Hz(un output) */
|
|
+ else
|
|
+ dai_props->mclk_fs = val;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int asoc_simple_parse_card_name(struct snd_soc_card *card,
|
|
+ char *prefix)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ if (!prefix)
|
|
+ prefix = "";
|
|
+
|
|
+ /* Parse the card name from DT */
|
|
+ ret = snd_soc_of_parse_card_name(card, "label");
|
|
+ if (ret < 0 || !card->name) {
|
|
+ char prop[128];
|
|
+
|
|
+ snprintf(prop, sizeof(prop), "%sname", prefix);
|
|
+ ret = snd_soc_of_parse_card_name(card, prop);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (!card->name && card->dai_link)
|
|
+ card->name = card->dai_link->name;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int asoc_simple_parse_dai(struct device_node *node,
|
|
+ struct snd_soc_dai_link_component *dlc,
|
|
+ const char *list_name, const char *cells_name,
|
|
+ int *is_single_link)
|
|
+{
|
|
+ struct of_phandle_args args;
|
|
+ int ret;
|
|
+
|
|
+ if (!node)
|
|
+ return 0;
|
|
+
|
|
+ /*
|
|
+ * Get node via "sound-dai = <&phandle port>"
|
|
+ * it will be used as xxx_of_node on soc_bind_dai_link()
|
|
+ */
|
|
+ ret = of_parse_phandle_with_args(node, list_name, cells_name, 0, &args);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /*
|
|
+ * FIXME
|
|
+ *
|
|
+ * Here, dlc->dai_name is pointer to CPU/Codec DAI name.
|
|
+ * If user unbinded CPU or Codec driver, but not for Sound Card,
|
|
+ * dlc->dai_name is keeping unbinded CPU or Codec
|
|
+ * driver's pointer.
|
|
+ *
|
|
+ * If user re-bind CPU or Codec driver again, ALSA SoC will try
|
|
+ * to rebind Card via snd_soc_try_rebind_card(), but because of
|
|
+ * above reason, it might can't bind Sound Card.
|
|
+ * Because Sound Card is pointing to released dai_name pointer.
|
|
+ *
|
|
+ * To avoid this rebind Card issue,
|
|
+ * 1) It needs to alloc memory to keep dai_name eventhough
|
|
+ * CPU or Codec driver was unbinded, or
|
|
+ * 2) user need to rebind Sound Card everytime
|
|
+ * if he unbinded CPU or Codec.
|
|
+ */
|
|
+ ret = snd_soc_of_get_dai_name(node, &dlc->dai_name, 0);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ dlc->of_node = args.np;
|
|
+
|
|
+ if (is_single_link)
|
|
+ *is_single_link = !args.args_count;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int asoc_simple_set_dailink_name(struct device *dev,
|
|
+ struct snd_soc_dai_link *dai_link,
|
|
+ const char *fmt, ...)
|
|
+{
|
|
+ va_list ap;
|
|
+ char *name = NULL;
|
|
+ int ret = -ENOMEM;
|
|
+
|
|
+ va_start(ap, fmt);
|
|
+ name = devm_kvasprintf(dev, GFP_KERNEL, fmt, ap);
|
|
+ va_end(ap);
|
|
+
|
|
+ if (name) {
|
|
+ ret = 0;
|
|
+
|
|
+ dai_link->name = name;
|
|
+ dai_link->stream_name = name;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+void asoc_simple_canonicalize_platform(struct snd_soc_dai_link *dai_link)
|
|
+{
|
|
+ /* Assumes platform == cpu */
|
|
+ if (!dai_link->platforms->of_node)
|
|
+ dai_link->platforms->of_node = dai_link->cpus->of_node;
|
|
+
|
|
+ /*
|
|
+ * DPCM BE can be no platform.
|
|
+ * Alloced memory will be waste, but not leak.
|
|
+ */
|
|
+ if (!dai_link->platforms->of_node)
|
|
+ dai_link->num_platforms = 0;
|
|
+}
|
|
+
|
|
+void asoc_simple_canonicalize_cpu(struct snd_soc_dai_link *dai_link,
|
|
+ int is_single_links)
|
|
+{
|
|
+ /*
|
|
+ * In soc_bind_dai_link() will check cpu name after
|
|
+ * of_node matching if dai_link has cpu_dai_name.
|
|
+ * but, it will never match if name was created by
|
|
+ * fmt_single_name() remove cpu_dai_name if cpu_args
|
|
+ * was 0. See:
|
|
+ * fmt_single_name()
|
|
+ * fmt_multiple_name()
|
|
+ */
|
|
+ if (is_single_links)
|
|
+ dai_link->cpus->dai_name = NULL;
|
|
+}
|
|
+
|
|
+MODULE_AUTHOR("Dby@allwinnertech.com");
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_DESCRIPTION("sunxi soundcard machine utils");
|
|
diff --git a/sound/soc/sunxi_v2/snd_sunxi_mach_utils.h b/sound/soc/sunxi_v2/snd_sunxi_mach_utils.h
|
|
new file mode 100644
|
|
index 000000000000..a9cffa0d859b
|
|
--- /dev/null
|
|
+++ b/sound/soc/sunxi_v2/snd_sunxi_mach_utils.h
|
|
@@ -0,0 +1,116 @@
|
|
+/* sound\soc\sunxi\snd_sunxi_mach_utils.h
|
|
+ * (C) Copyright 2021-2025
|
|
+ * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
+ * Dby <dby@allwinnertech.com>
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or
|
|
+ * modify it under the terms of the GNU General Public License as
|
|
+ * published by the Free Software Foundation; either version 2 of
|
|
+ * the License, or (at your option) any later version.
|
|
+ */
|
|
+
|
|
+#ifndef __SND_SUNXI_MACH_UTILS_H
|
|
+#define __SND_SUNXI_MACH_UTILS_H
|
|
+
|
|
+#define simple_priv_to_card(priv) (&(priv)->snd_card)
|
|
+#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i))
|
|
+#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev)
|
|
+#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i))
|
|
+
|
|
+#define asoc_simple_parse_cpu(node, dai_link, \
|
|
+ list_name, cells_name, is_single_link) \
|
|
+ asoc_simple_parse_dai(node, dai_link->cpus, \
|
|
+ list_name, cells_name, is_single_link)
|
|
+
|
|
+#define asoc_simple_parse_codec(node, dai_link, \
|
|
+ list_name, cells_name) \
|
|
+ asoc_simple_parse_dai(node, dai_link->codecs, \
|
|
+ list_name, cells_name, NULL)
|
|
+
|
|
+#define asoc_simple_parse_platform(node, dai_link, \
|
|
+ list_name, cells_name) \
|
|
+ asoc_simple_parse_dai(node, dai_link->platforms, \
|
|
+ list_name, cells_name, NULL)
|
|
+
|
|
+struct asoc_simple_dai {
|
|
+ const char *name;
|
|
+ unsigned int sysclk;
|
|
+ int clk_direction;
|
|
+ int slots;
|
|
+ int slot_width;
|
|
+ unsigned int tx_slot_mask;
|
|
+ unsigned int rx_slot_mask;
|
|
+ struct clk *clk;
|
|
+};
|
|
+
|
|
+struct asoc_simple_data {
|
|
+ u32 convert_rate;
|
|
+ u32 convert_channels;
|
|
+};
|
|
+
|
|
+struct asoc_simple_jack {
|
|
+ struct snd_soc_jack jack;
|
|
+ struct snd_soc_jack_pin pin;
|
|
+ struct snd_soc_jack_gpio gpio;
|
|
+};
|
|
+
|
|
+struct asoc_simple_priv {
|
|
+ struct snd_soc_card snd_card;
|
|
+ struct simple_dai_props {
|
|
+ struct asoc_simple_dai *cpu_dai;
|
|
+ struct asoc_simple_dai *codec_dai;
|
|
+ struct snd_soc_dai_link_component cpus; /* single cpu */
|
|
+ struct snd_soc_dai_link_component codecs; /* single codec */
|
|
+ struct snd_soc_dai_link_component platforms;
|
|
+ struct asoc_simple_data adata;
|
|
+ struct snd_soc_codec_conf *codec_conf;
|
|
+ bool mclk_fp;
|
|
+ unsigned int mclk_fs;
|
|
+ unsigned int cpu_pll_fs;
|
|
+ unsigned int codec_pll_fs;
|
|
+ } *dai_props;
|
|
+ struct asoc_simple_jack hp_jack;
|
|
+ struct asoc_simple_jack mic_jack;
|
|
+ struct snd_soc_dai_link *dai_link;
|
|
+ struct asoc_simple_dai *dais;
|
|
+ struct snd_soc_codec_conf *codec_conf;
|
|
+ struct gpio_desc *pa_gpio;
|
|
+};
|
|
+
|
|
+int asoc_simple_clean_reference(struct snd_soc_card *card);
|
|
+int asoc_simple_init_priv(struct asoc_simple_priv *priv);
|
|
+
|
|
+int asoc_simple_parse_widgets(struct snd_soc_card *card, char *prefix);
|
|
+int asoc_simple_parse_routing(struct snd_soc_card *card, char *prefix);
|
|
+int asoc_simple_parse_pin_switches(struct snd_soc_card *card, char *prefix);
|
|
+
|
|
+int asoc_simple_parse_daistream(struct device_node *node,
|
|
+ char *prefix,
|
|
+ struct snd_soc_dai_link *dai_link);
|
|
+int asoc_simple_parse_daifmt(struct device_node *node,
|
|
+ struct device_node *codec,
|
|
+ char *prefix,
|
|
+ unsigned int *retfmt);
|
|
+int asoc_simple_parse_tdm_slot(struct device_node *node,
|
|
+ char *prefix,
|
|
+ struct asoc_simple_dai *dais);
|
|
+int asoc_simple_parse_tdm_clk(struct device_node *cpu,
|
|
+ struct device_node *codec,
|
|
+ char *prefix,
|
|
+ struct simple_dai_props *dai_props);
|
|
+
|
|
+int asoc_simple_parse_card_name(struct snd_soc_card *card, char *prefix);
|
|
+int asoc_simple_parse_dai(struct device_node *node,
|
|
+ struct snd_soc_dai_link_component *dlc,
|
|
+ const char *list_name,
|
|
+ const char *cells_name,
|
|
+ int *is_single_link);
|
|
+
|
|
+int asoc_simple_set_dailink_name(struct device *dev,
|
|
+ struct snd_soc_dai_link *dai_link,
|
|
+ const char *fmt, ...);
|
|
+void asoc_simple_canonicalize_platform(struct snd_soc_dai_link *dai_link);
|
|
+void asoc_simple_canonicalize_cpu(struct snd_soc_dai_link *dai_link,
|
|
+ int is_single_links);
|
|
+
|
|
+#endif /* __SND_SUNXI_MACH_UTILS_H */
|
|
--
|
|
2.35.3
|
|
|