From 9b0e8a8239f334a40d1637204a0f58ab2efc0061 Mon Sep 17 00:00:00 2001
From: Stephen Graf <stephen.graf@gmail.com>
Date: Wed, 26 Mar 2025 17:06:51 +0000
Subject: Sound for H616, H618 Allwinner SOCs

The Linux kernel now (v6.13) provides an analog codec
for H616 SoCs in sun41.codec.c. We will use it.

 arch/arm64/boot/dts/allwinner/sun50i-h616-orangepi-zero.dtsi
 arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi include/sound/soc-dai.h
 sound/soc/Kconfig sound/soc/Makefile sound/soc/soc-core.c
 sound/soc/sunxi_v2/Kconfig sound/soc/sunxi_v2/Makefile
 sound/soc/sunxi_v2/drv_hdmi.h sound/soc/sunxi_v2/snd_sunxi_ahub.c
 sound/soc/sunxi_v2/snd_sunxi_ahub.h sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c
 sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h sound/soc/sunxi_v2/snd_sunxi_common.c
 sound/soc/sunxi_v2/snd_sunxi_common.h sound/soc/sunxi_v2/snd_sunxi_log.h
 sound/soc/sunxi_v2/snd_sunxi_mach.c sound/soc/sunxi_v2/snd_sunxi_mach.h
 sound/soc/sunxi_v2/snd_sunxi_mach_utils.c
 sound/soc/sunxi_v2/snd_sunxi_mach_utils.h

Signed-off-by: Stephen Graf <stephen.graf@gmail.com>
---
 .../allwinner/sun50i-h616-orangepi-zero.dtsi  |   12 +
 .../arm64/boot/dts/allwinner/sun50i-h616.dtsi |   60 +-
 include/sound/soc-dai.h                       |   13 +
 sound/soc/Kconfig                             |    1 +
 sound/soc/Makefile                            |    1 +
 sound/soc/soc-core.c                          |   25 +
 sound/soc/sunxi_v2/Kconfig                    |   45 +
 sound/soc/sunxi_v2/Makefile                   |   11 +
 sound/soc/sunxi_v2/drv_hdmi.h                 |   63 +
 sound/soc/sunxi_v2/snd_sunxi_ahub.c           | 1475 +++++++++++++++++
 sound/soc/sunxi_v2/snd_sunxi_ahub.h           |   74 +
 sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c       |  532 ++++++
 sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h       |  294 ++++
 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           |   20 +
 sound/soc/sunxi_v2/snd_sunxi_mach_utils.c     |  422 +++++
 sound/soc/sunxi_v2/snd_sunxi_mach_utils.h     |  116 ++
 20 files changed, 4005 insertions(+), 1 deletion(-)
 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 60f643abb3eb..109c33c6558f 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h616-orangepi-zero.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-orangepi-zero.dtsi
@@ -110,6 +110,18 @@ &de {
 	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 307a8678e0eb..8de963bee2ac 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
@@ -678,7 +678,8 @@ gic: interrupt-controller@3021000 {
 		};
 
 		iommu: iommu@30f0000 {
-			compatible = "allwinner,sun50i-h616-iommu";
+			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>;
@@ -1045,6 +1046,63 @@ codec: codec@5096000 {
 			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>;
+			};
+		};
+
 		usbotg: usb@5100000 {
 			compatible = "allwinner,sun50i-h616-musb",
 				     "allwinner,sun8i-h3-musb";
diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h
index a11501752637..eb1eb56a4a6d 100644
--- a/include/sound/soc-dai.h
+++ b/include/sound/soc-dai.h
@@ -413,6 +413,15 @@ struct snd_soc_dai_driver {
 	struct snd_soc_dobj dobj;
 	const 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);
+	/* 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;
@@ -423,6 +432,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 5efba76abb31..b1b4693ca49b 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -117,6 +117,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 08baaa11d813..ca53a0853e0f 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -70,6 +70,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 3c6d8aef4130..e81e3b32569c 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -2632,6 +2632,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);
 
@@ -2660,6 +2661,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_v2/Kconfig b/sound/soc/sunxi_v2/Kconfig
new file mode 100644
index 000000000000..e5d4aff7e6ca
--- /dev/null
+++ b/sound/soc/sunxi_v2/Kconfig
@@ -0,0 +1,45 @@
+# common
+config SND_SOC_SUNXI_MACH
+	tristate
+
+# ahub dam
+config SND_SOC_SUNXI_AHUB_DAM
+	tristate
+
+config SND_SOC_SUNXI_INTERNALCODEC
+	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_SUN4I_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..6d4a847a928f
--- /dev/null
+++ b/sound/soc/sunxi_v2/snd_sunxi_ahub.c
@@ -0,0 +1,1475 @@
+/*
+ * 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), &reg_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", &regulator_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 void 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");
+}
+
+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..cd4be46b6ebf
--- /dev/null
+++ b/sound/soc/sunxi_v2/snd_sunxi_ahub.h
@@ -0,0 +1,74 @@
+/* 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;
+};
+
+int sunxi_loopback_debug_get(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol);
+int sunxi_loopback_debug_set(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol);
+int __init sunxi_ahub_dev_init(void);
+void __exit sunxi_ahub_dev_exit(void);
+
+#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..f3c7bb3c7cd2
--- /dev/null
+++ b/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.c
@@ -0,0 +1,532 @@
+/*
+ * 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, &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 void 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");
+}
+
+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..436742d87e8f
--- /dev/null
+++ b/sound/soc/sunxi_v2/snd_sunxi_ahub_dam.h
@@ -0,0 +1,294 @@
+/* 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);
+
+int __init sunxi_ahub_dam_dev_init(void);
+void __exit sunxi_ahub_dam_dev_exit(void);
+
+#endif /* __SND_SUNXI_AHUB_DAM_H */
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..74d276a2da92
--- /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->id);
+	struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->id);
+	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 void asoc_simple_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	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..6f9ea8ccf8b5
--- /dev/null
+++ b/sound/soc/sunxi_v2/snd_sunxi_mach.h
@@ -0,0 +1,20 @@
+/* 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"
+
+int __init sunxi_soundcard_machine_dev_init(void);
+void __exit sunxi_soundcard_machine_dev_exit(void);
+
+#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

