This is first pass at a mainline driver for the A20 on-chip codec. This patch 
will boot and install the ALSA SOC driver. But it won't play any music. 
Something is wrong in either the DMA or clock code. Anyone want to give this a 
try and see if you can figure out what is wrong? The patch sits on top of 
Emilio's DMA driver for the A20.
---
 arch/arm/boot/dts/sun7i-a20-cubietruck.dts |    4 
 arch/arm/boot/dts/sun7i-a20.dtsi           |  132 +++++
 drivers/clk/sunxi/clk-factors.c            |   12 
 drivers/clk/sunxi/clk-factors.h            |    4 
 drivers/clk/sunxi/clk-sunxi.c              |   56 ++
 drivers/dma/sun4i-dma.c                    |   81 +++
 sound/soc/Kconfig                          |    1 
 sound/soc/Makefile                         |    1 
 sound/soc/sunxi/Kconfig                    |    5 
 sound/soc/sunxi/Makefile                   |    2 
 sound/soc/sunxi/sunxi-codec.c              |  678 ++++++++++++++++++++++++++++
 11 files changed, 965 insertions(+), 11 deletions(-)
 create mode 100644 sound/soc/sunxi/Kconfig
 create mode 100644 sound/soc/sunxi/Makefile
 create mode 100644 sound/soc/sunxi/sunxi-codec.c

diff --git a/arch/arm/boot/dts/sun7i-a20-cubietruck.dts 
b/arch/arm/boot/dts/sun7i-a20-cubietruck.dts
index bed74a9..ed40c2e 100644
--- a/arch/arm/boot/dts/sun7i-a20-cubietruck.dts
+++ b/arch/arm/boot/dts/sun7i-a20-cubietruck.dts
@@ -137,6 +137,10 @@
                        status = "okay";
                };
 
+               codec@01c22c00 {
+                       status = "okay";
+               };
+
                ir0: ir@01c21800 {
                        pinctrl-names = "default";
                        pinctrl-0 = <&ir0_pins_a>;
diff --git a/arch/arm/boot/dts/sun7i-a20.dtsi b/arch/arm/boot/dts/sun7i-a20.dtsi
index 263eb79..222648f 100644
--- a/arch/arm/boot/dts/sun7i-a20.dtsi
+++ b/arch/arm/boot/dts/sun7i-a20.dtsi
@@ -91,6 +91,14 @@
                        clock-output-names = "pll1";
                };
 
+               pll2: clk@01c20008 {
+                       #clock-cells = <0>;
+                       compatible = "allwinner,sun7i-a20-pll2-clk";
+                       reg = <0x01c20008 0x4>;
+                       clocks = <&osc24M>;
+                       clock-output-names = "pll2";
+               };
+
                pll4: clk@01c20018 {
                        #clock-cells = <0>;
                        compatible = "allwinner,sun7i-a20-pll4-clk";
@@ -327,6 +335,30 @@
                        clock-output-names = "ir1";
                };
 
+               i2s0_clk: clk@01c200b8 {
+                       #clock-cells = <0>;
+                       compatible = "allwinner,sunxi-audio-clk";
+                       reg = <0x01c200b8 0x4>;
+                       clocks = <&pll2>;
+                       clock-output-names = "i2s0";
+               };
+
+               ac97_clk: clk@01c200bc {
+                       #clock-cells = <0>;
+                       compatible = "allwinner,sunxi-audio-clk";
+                       reg = <0x01c200bc 0x4>;
+                       clocks = <&pll2>;
+                       clock-output-names = "ac97";
+               };
+
+               spdif_clk: clk@01c200c0 {
+                       #clock-cells = <0>;
+                       compatible = "allwinner,sunxi-audio-clk";
+                       reg = <0x01c200c0 0x4>;
+                       clocks = <&pll2>;
+                       clock-output-names = "spdif";
+               };
+
                usb_clk: clk@01c200cc {
                        #clock-cells = <1>;
                        #reset-cells = <1>;
@@ -344,6 +376,30 @@
                        clock-output-names = "spi3";
                };
 
+               i2s1_clk: clk@01c200d8 {
+                       #clock-cells = <0>;
+                       compatible = "allwinner,sunxi-audio-clk";
+                       reg = <0x01c200d8 0x4>;
+                       clocks = <&pll2>;
+                       clock-output-names = "i2s1";
+               };
+
+               i2s2_clk: clk@01c200dc {
+                       #clock-cells = <0>;
+                       compatible = "allwinner,sunxi-audio-clk";
+                       reg = <0x01c200dc 0x4>;
+                       clocks = <&pll2>;
+                       clock-output-names = "i2s2";
+               };
+
+               codec_clk: clk@01c20140 {
+                       #clock-cells = <0>;
+                       compatible = "allwinner,sunxi-codec-clk";
+                       reg = <0x01c20140 0x4>;
+                       clocks = <&pll2>;
+                       clock-output-names = "codec";
+               };
+
                mbus_clk: clk@01c2015c {
                        #clock-cells = <0>;
                        compatible = "allwinner,sun4i-a10-mod0-clk";
@@ -807,6 +863,78 @@
                        clocks = <&osc24M>;
                        #pwm-cells = <3>;
                };
+               
+               spdif@01c21000 {
+                       compatible = "allwinner,sun7i-a20-spdif";
+                       reg = <0x01C21000 0x40>;
+                       interrupts = <0 13 4>;
+                       clocks = <&pll2>, <&apb0_gates 1>, <&spdif_clk>;
+                       clock-names = "pll2", "apb", "spdif";
+                       dmas = <&dma 0 2>, <&dma 0 2>;
+                       dma-names = "rx", "tx";
+                       status = "disabled";
+               };
+
+               ac97@01c21400 {
+                       compatible = "allwinner,sun7i-a20-ac97";
+                       reg = <0x01C21400 0x40>;
+                       interrupts = <0 14 4>;
+                       clocks = <&pll2>, <&apb0_gates 2>, <&ac97_clk>;
+                       clock-names = "pll2", "apb", "ac97";
+                       dmas = <&dma 0 5>, <&dma 0 5>;
+                       dma-names = "rx", "tx";
+                       status = "disabled";
+               };
+
+               i2s0: i2s@01c22000 {
+                       compatible = "allwinner,sun7i-a20-i2s";
+                       reg = <0x01C22000 0x40>;
+                       interrupts = <0 16 4>;
+                       clocks = <&pll2>, <&apb0_gates 3>, <&i2s0_clk>;
+                       clock-names = "pll2", "apb", "i2s";
+                       dmas = <&dma 0 3>, <&dma 0 3>;
+                       dma-names = "rx", "tx";
+                       status = "disabled";
+               };
+
+               i2s1: i2s@01c22400 {
+                       compatible = "allwinner,sun7i-a20-i2s";
+                       reg = <0x01C22400 0x40>;
+                       interrupts = <0 87 4>;
+                       clocks = <&pll2>, <&apb0_gates 4>, <&i2s1_clk>;
+                       clock-names = "pll2", "apb", "i2s";
+                       dmas = <&dma 0 4>, <&dma 0 4>;
+                       dma-names = "rx", "tx";
+                       status = "disabled";
+               };
+
+               i2s2: i2s@01c24400 {
+                       compatible = "allwinner,sun7i-a20-i2s";
+                       reg = <0x01C24400 0x40>;
+                       interrupts = <0 90 4>;
+                       clocks = <&pll2>, <&apb0_gates 8>, <&i2s2_clk>;
+                       clock-names = "pll2", "apb", "i2s";
+                       dmas = <&dma 0 6>, <&dma 0 6>;
+                       dma-names = "rx", "tx";
+                       status = "disabled";
+               };
+
+               codec: codec@01c22c00 {
+                       #sound-dai-cells = <0>;
+                       compatible = "allwinner,sun7i-a20-codec";
+                       reg = <0x01C22c00 0x40>;
+                       interrupts = <0 30 4>;
+                       clocks = <&pll2>, <&apb0_gates 0>, <&codec_clk>;
+                       clock-names = "pll2", "apb", "codec";
+                       dmas = <&dma 0 19>, <&dma 0 19>;
+                       dma-names = "rx", "tx";
+                       routing =
+                               "MIC_IN", "Microphone Jack",
+                               "Microphone Jack", "Mic Bias",
+                               "LINE_IN", "Line In Jack",
+                               "Headphone Jack", "HP_OUT";
+                       status = "disabled";
+               };
 
                ir0: ir@01c21800 {
                        compatible = "allwinner,sun7i-a20-ir";
@@ -851,8 +979,8 @@
                        reg-shift = <2>;
                        reg-io-width = <4>;
                        clocks = <&apb1_gates 16>;
-                       dmas = <&dma 0 8>, <&dma 0 8>;
-                       dma-names = "rx", "tx";
+                       /*dmas = <&dma 0 8>, <&dma 0 8>;
+                       dma-names = "rx", "tx"; */
                        status = "disabled";
                };
 
diff --git a/drivers/clk/sunxi/clk-factors.c b/drivers/clk/sunxi/clk-factors.c
index 399cf4d..a6264a7 100644
--- a/drivers/clk/sunxi/clk-factors.c
+++ b/drivers/clk/sunxi/clk-factors.c
@@ -62,11 +62,17 @@ static unsigned long clk_factors_recalc_rate(struct clk_hw 
*hw,
                p = FACTOR_GET(config->pshift, config->pwidth, reg);
 
        /* Calculate the rate */
-       if (config->n_from_one)
+       switch (config->rate_adjust) {
+       case N_FROM_ONE:
                rate = (parent_rate * (n + 1) * (k + 1) >> p) / (m + 1);
-       else
+               break;
+       case PLL2_DIV:
+               rate = parent_rate * n / k / m;
+               break;
+       default:
                rate = (parent_rate * n * (k + 1) >> p) / (m + 1);
-
+               break;
+       }
        return rate;
 }
 
diff --git a/drivers/clk/sunxi/clk-factors.h b/drivers/clk/sunxi/clk-factors.h
index 0484a48..2d63400 100644
--- a/drivers/clk/sunxi/clk-factors.h
+++ b/drivers/clk/sunxi/clk-factors.h
@@ -6,6 +6,8 @@
 
 #define SUNXI_FACTORS_NOT_APPLICABLE   (0)
 
+enum erate_adjust {NO_ADJUST = 0, N_FROM_ONE, PLL2_DIV};
+
 struct clk_factors_config {
        u8 nshift;
        u8 nwidth;
@@ -15,7 +17,7 @@ struct clk_factors_config {
        u8 mwidth;
        u8 pshift;
        u8 pwidth;
-       u8 n_from_one;
+       enum erate_adjust rate_adjust;
 };
 
 struct clk_factors {
diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c
index 1d16c0c..0f0de4d 100644
--- a/drivers/clk/sunxi/clk-sunxi.c
+++ b/drivers/clk/sunxi/clk-sunxi.c
@@ -212,6 +212,29 @@ static void sun8i_a23_get_pll1_factors(u32 *freq, u32 
parent_rate,
 }
 
 /**
+ * sun7i_get_pll2_factors()
+ * parent_rate is always 24Mhz
+ */
+
+static void sun7i_get_pll2_factors(u32 *freq, u32 parent_rate,
+                                  u8 *n, u8 *k, u8 *m, u8 *p)
+{
+       /* we were called to round the frequency, we can now return */
+       if (n == NULL)
+               return;
+       
+       if (*freq == 22579200) {
+               *n = 79;
+               *m = 21;  /* Pre */
+               *k = 4;   /* Post */
+       } else  {
+               *n = 86;
+               *m = 21;  /* Pre */
+               *k = 4;   /* Post */
+       }
+}
+
+/**
  * sun4i_get_pll5_factors() - calculates n, k factors for PLL5
  * PLL5 rate is calculated as follows
  * rate = parent_rate * n * (k + 1)
@@ -479,7 +502,17 @@ static struct clk_factors_config sun8i_a23_pll1_config = {
        .mwidth = 2,
        .pshift = 16,
        .pwidth = 2,
-       .n_from_one = 1,
+       .rate_adjust = N_FROM_ONE,
+};
+
+static struct clk_factors_config sun7i_pll2_config = {
+       .nshift = 8,
+       .nwidth = 7,
+       .kshift = 26,
+       .kwidth = 4,
+       .mshift = 0,
+       .mwidth = 5,
+       .rate_adjust = PLL2_DIV,
 };
 
 static struct clk_factors_config sun4i_pll5_config = {
@@ -494,7 +527,7 @@ static struct clk_factors_config sun6i_a31_pll6_config = {
        .nwidth = 5,
        .kshift = 4,
        .kwidth = 2,
-       .n_from_one = 1,
+       .rate_adjust = N_FROM_ONE,
 };
 
 static struct clk_factors_config sun4i_apb1_config = {
@@ -538,6 +571,12 @@ static const struct factors_data sun8i_a23_pll1_data 
__initconst = {
        .getter = sun8i_a23_get_pll1_factors,
 };
 
+static const struct factors_data sun7i_a20_pll2_data __initconst = {
+       .enable = 31,
+       .table = &sun7i_pll2_config,
+       .getter = sun7i_get_pll2_factors,
+};
+
 static const struct factors_data sun7i_a20_pll4_data __initconst = {
        .enable = 31,
        .table = &sun4i_pll5_config,
@@ -945,6 +984,10 @@ static const struct gates_data sun6i_a31_usb_gates_data 
__initconst = {
        .reset_mask = BIT(2) | BIT(1) | BIT(0),
 };
 
+static const struct gates_data sunxi_codec_data __initconst = {
+       .mask = {BIT(31)},
+};
+
 static void __init sunxi_gates_clk_setup(struct device_node *node,
                                         struct gates_data *data)
 {
@@ -975,17 +1018,19 @@ static void __init sunxi_gates_clk_setup(struct 
device_node *node,
        for_each_set_bit(i, data->mask, SUNXI_GATES_MAX_SIZE) {
                of_property_read_string_index(node, "clock-output-names",
                                              j, &clk_name);
-
                clk_data->clks[i] = clk_register_gate(NULL, clk_name,
                                                      clk_parent, 0,
                                                      reg + 4 * (i/32), i % 32,
                                                      0, &clk_lock);
                WARN_ON(IS_ERR(clk_data->clks[i]));
                clk_register_clkdev(clk_data->clks[i], clk_name, NULL);
-
                j++;
        }
 
+       /* if there is a single gate, copy it to entry[0] so there is no 
requirement for phandle arg */
+       if (j == 1)
+               clk_data->clks[0] = clk_data->clks[i];
+
        /* Adjust to the real max */
        clk_data->clk_num = i;
 
@@ -1195,12 +1240,12 @@ free_clkdata:
 }
 
 
-
 /* Matches for factors clocks */
 static const struct of_device_id clk_factors_match[] __initconst = {
        {.compatible = "allwinner,sun4i-a10-pll1-clk", .data = 
&sun4i_pll1_data,},
        {.compatible = "allwinner,sun6i-a31-pll1-clk", .data = 
&sun6i_a31_pll1_data,},
        {.compatible = "allwinner,sun8i-a23-pll1-clk", .data = 
&sun8i_a23_pll1_data,},
+       {.compatible = "allwinner,sun7i-a20-pll2-clk", .data = 
&sun7i_a20_pll2_data,},
        {.compatible = "allwinner,sun7i-a20-pll4-clk", .data = 
&sun7i_a20_pll4_data,},
        {.compatible = "allwinner,sun4i-a10-apb1-clk", .data = 
&sun4i_apb1_data,},
        {.compatible = "allwinner,sun4i-a10-mod0-clk", .data = 
&sun4i_mod0_data,},
@@ -1258,6 +1303,7 @@ static const struct of_device_id clk_gates_match[] 
__initconst = {
        {.compatible = "allwinner,sun4i-a10-usb-clk", .data = 
&sun4i_a10_usb_gates_data,},
        {.compatible = "allwinner,sun5i-a13-usb-clk", .data = 
&sun5i_a13_usb_gates_data,},
        {.compatible = "allwinner,sun6i-a31-usb-clk", .data = 
&sun6i_a31_usb_gates_data,},
+       {.compatible = "allwinner,sunxi-codec-clk", .data = &sunxi_codec_data,},
        {}
 };
 
diff --git a/drivers/dma/sun4i-dma.c b/drivers/dma/sun4i-dma.c
index 7b986bd..a23b176 100644
--- a/drivers/dma/sun4i-dma.c
+++ b/drivers/dma/sun4i-dma.c
@@ -637,6 +637,72 @@ sun4i_dma_prep_dma_memcpy(struct dma_chan *chan, 
dma_addr_t dest,
        return vchan_tx_prep(&vchan->vc, &contract->vd, flags);
 }
 
+static struct dma_async_tx_descriptor *sun4i_dma_prep_dma_cyclic(
+               struct dma_chan *chan, dma_addr_t buf, size_t len,
+               size_t period_len, enum dma_transfer_direction dir,
+               unsigned long flags, void *context) {
+       struct sun4i_dma_vchan *vchan = to_sun4i_dma_vchan(chan);
+       struct dma_slave_config *sconfig = &vchan->cfg;
+       struct sun4i_ddma_promise *promise;
+       struct sun4i_ddma_contract *contract;
+       dma_addr_t src, dest;
+
+       if (!is_slave_direction(dir)) {
+               dev_err(chan2dev(chan), "Invalid DMA direction\n");
+               return NULL;
+       }
+
+       contract = generate_ddma_contract();
+       if (!contract)
+               return NULL;
+
+       /* Figure out addresses */
+       if (dir == DMA_MEM_TO_DEV) {
+               src = buf;
+               dest = sconfig->dst_addr;
+       } else {
+               src = sconfig->src_addr;
+               dest = buf;
+       }
+
+       if (vchan->is_dedicated)
+               promise = generate_ddma_promise(chan, src, dest, len, sconfig);
+       else
+               promise = generate_ndma_promise(chan, src, dest, len, sconfig);
+
+       if (!promise) {
+               kfree(contract);
+               return NULL;
+       }
+
+       /* Figure out endpoints */
+       if (vchan->is_dedicated && dir == DMA_MEM_TO_DEV) {
+               promise->cfg |= DDMA_CFG_CONT_MODE | 
DDMA_CFG_SRC_DRQ_TYPE(DDMA_DRQ_TYPE_SDRAM) |
+                           DDMA_CFG_SRC_ADDR_MODE(DDMA_ADDR_MODE_LINEAR) |
+                           DDMA_CFG_DEST_DRQ_TYPE(vchan->endpoint) |
+                           DDMA_CFG_DEST_ADDR_MODE(DDMA_ADDR_MODE_IO);
+       } else if (!vchan->is_dedicated && dir == DMA_MEM_TO_DEV) {
+               promise->cfg |= NDMA_CFG_CONT_MODE | 
NDMA_CFG_SRC_DRQ_TYPE(NDMA_DRQ_TYPE_SDRAM) |
+                           NDMA_CFG_DEST_DRQ_TYPE(vchan->endpoint) |
+                           NDMA_CFG_DEST_FIXED_ADDR;
+       } else if (vchan->is_dedicated) {
+               promise->cfg |= DDMA_CFG_CONT_MODE | 
DDMA_CFG_SRC_DRQ_TYPE(vchan->endpoint) |
+                           DDMA_CFG_SRC_ADDR_MODE(DDMA_ADDR_MODE_IO) |
+                           DDMA_CFG_DEST_DRQ_TYPE(DDMA_DRQ_TYPE_SDRAM) |
+                           DDMA_CFG_DEST_ADDR_MODE(DDMA_ADDR_MODE_LINEAR);
+       } else {
+               promise->cfg |= NDMA_CFG_CONT_MODE | 
NDMA_CFG_SRC_DRQ_TYPE(vchan->endpoint) |
+                           NDMA_CFG_SRC_FIXED_ADDR |
+                           NDMA_CFG_DEST_DRQ_TYPE(NDMA_DRQ_TYPE_SDRAM);
+       }
+
+       /* Fill the contract with our only promise */
+       list_add_tail(&promise->list, &contract->demands);
+
+       /* And add it to the vchan */
+       return vchan_tx_prep(&vchan->vc, &contract->vd, flags);
+}
+
 static struct dma_async_tx_descriptor *
 sun4i_dma_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl,
                        unsigned int sg_len, enum dma_transfer_direction dir,
@@ -919,6 +985,19 @@ static void sun4i_ddma_tasklet(unsigned long data)
                execute_vchan_pending(priv, &priv->vchans[i]);
 }
 
+static int sun4i_dma_device_slave_caps(struct dma_chan *dchan,
+                                     struct dma_slave_caps *caps)
+{
+       caps->src_addr_widths = 32;
+       caps->dstn_addr_widths = 32;
+       caps->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
+       caps->cmd_pause = true;
+       caps->cmd_terminate = true;
+       caps->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
+
+       return 0;
+}
+
 static int sun4i_dma_probe(struct platform_device *pdev)
 {
        struct sun4i_ddma_dev *priv;
@@ -962,7 +1041,9 @@ static int sun4i_dma_probe(struct platform_device *pdev)
        priv->slave.device_issue_pending        = sun4i_dma_issue_pending;
        priv->slave.device_prep_slave_sg        = sun4i_dma_prep_slave_sg;
        priv->slave.device_prep_dma_memcpy      = sun4i_dma_prep_dma_memcpy;
+       priv->slave.device_prep_dma_cyclic      = sun4i_dma_prep_dma_cyclic;
        priv->slave.device_control              = sun4i_dma_control;
+       priv->slave.device_slave_caps           = sun4i_dma_device_slave_caps;
        priv->slave.chancnt                     = DDMA_NR_MAX_VCHANS;
 
        priv->slave.dev = &pdev->dev;
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 0060b31..8f08e4b 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -52,6 +52,7 @@ source "sound/soc/s6000/Kconfig"
 source "sound/soc/sh/Kconfig"
 source "sound/soc/sirf/Kconfig"
 source "sound/soc/spear/Kconfig"
+source "sound/soc/sunxi/Kconfig"
 source "sound/soc/tegra/Kconfig"
 source "sound/soc/txx9/Kconfig"
 source "sound/soc/ux500/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 5f1df02..50c7ab3 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_SND_SOC) += s6000/
 obj-$(CONFIG_SND_SOC)  += sh/
 obj-$(CONFIG_SND_SOC)  += sirf/
 obj-$(CONFIG_SND_SOC)  += spear/
+obj-$(CONFIG_SND_SOC)  += sunxi/
 obj-$(CONFIG_SND_SOC)  += tegra/
 obj-$(CONFIG_SND_SOC)  += txx9/
 obj-$(CONFIG_SND_SOC)  += ux500/
diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig
new file mode 100644
index 0000000..cb75a83
--- /dev/null
+++ b/sound/soc/sunxi/Kconfig
@@ -0,0 +1,5 @@
+config SND_SUNXI_SOC_CODEC
+       tristate "APB On-Chip sun4i and sun5i Codec"
+       select SND_SOC_GENERIC_DMAENGINE_PCM
+       default y
+
diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile
new file mode 100644
index 0000000..b8950d3
--- /dev/null
+++ b/sound/soc/sunxi/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_SND_SUNXI_SOC_CODEC) += sunxi-codec.o
+
diff --git a/sound/soc/sunxi/sunxi-codec.c b/sound/soc/sunxi/sunxi-codec.c
new file mode 100644
index 0000000..98601d4
--- /dev/null
+++ b/sound/soc/sunxi/sunxi-codec.c
@@ -0,0 +1,678 @@
+/*
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/of_address.h>
+#include <linux/clk.h>
+#include <linux/regmap.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/dmaengine_pcm.h>
+
+//Codec Register
+#define SUNXI_DAC_DPC                (0x00)
+#define SUNXI_DAC_FIFOC              (0x04)
+#define SUNXI_DAC_FIFOS              (0x08)
+#define SUNXI_DAC_TXDATA             (0x0c)
+#define SUNXI_DAC_ACTL               (0x10)
+#define SUNXI_DAC_TUNE               (0x14)
+#define SUNXI_DAC_DEBUG              (0x18)
+#define SUNXI_ADC_FIFOC              (0x1c)
+#define SUNXI_ADC_FIFOS              (0x20)
+#define SUNXI_ADC_RXDATA             (0x24)
+#define SUNXI_ADC_ACTL               (0x28)
+#define SUNXI_ADC_DEBUG              (0x2c)
+#define SUNXI_DAC_TXCNT              (0x30)
+#define SUNXI_ADC_RXCNT              (0x34)
+#define SUNXI_BIAS_CRT               (0x38)
+#define SUNXI_MIC_CRT                (0x3c)
+#define SUNXI_CODEC_REGS_NUM         (13)
+
+#define DAIFMT_16BITS             (16)
+#define DAIFMT_20BITS             (20)
+
+#define DAIFMT_BS_MASK            (~(1<<16))   //FIFO big small mode mask
+#define DAIFMT_BITS_MASK          (~(1<<5))            //FIFO Bits select 
mask,not used yet.
+#define SAMPLE_RATE_MASK          (~(7<<29))   //Sample Rate slect mask
+
+#define DAC_EN                    (31)
+#define DIGITAL_VOL               (12)
+//For CODEC OLD VERSION
+#define DAC_VERSION               (23)
+
+#define DAC_CHANNEL              (6)
+#define LAST_SE                   (26)
+#define TX_FIFO_MODE              (24)
+#define DRA_LEVEL                 (21)
+#define TX_TRI_LEVEL              (8)
+#define DAC_MODE                  (6)                  //not used yet
+#define TASR                      (5)                  //not used yet
+#define DAC_DRQ                   (4)
+#define DAC_FIFO_FLUSH            (0)
+
+#define VOLUME                    (0)
+#define PA_MUTE                   (6)
+#define MIXPAS                    (7)
+#define DACPAS                    (8)
+#define MIXEN                     (29)
+#define DACAEN_L                  (30)
+#define DACAEN_R                  (31)
+
+#define ADC_DIG_EN                (28)
+#define RX_FIFO_MODE              (24)
+#define RX_TRI_LEVEL              (8)
+#define ADC_MODE                  (7)
+#define RASR                      (6)
+#define ADC_DRQ                   (4)
+#define ADC_FIFO_FLUSH            (0)
+
+#define  ADC_LF_EN                (31)
+#define  ADC_RI_EN                (30)
+#define  ADC_EN                   (30)
+#define  MIC1_EN                  (29)
+#define  MIC2_EN                  (28)
+#define  VMIC_EN                  (27)
+#define  MIC_GAIN                 (25)
+#define  ADC_SELECT               (17)
+#define  PA_ENABLE                (4)
+#define  HP_DIRECT                (3)
+
+
+enum sunxi_device_id {SUN4A, SUN4I, SUN5I, SUN7I}; 
+
+struct sunxi_priv {
+       struct regmap *regmap;
+       int irq;
+       struct clk *clk_apb, *clk_pll2, *clk_module;
+
+       enum sunxi_device_id id;
+
+       struct snd_dmaengine_dai_dma_data playback_dma_data;
+       struct snd_dmaengine_dai_dma_data capture_dma_data;
+};
+
+static int codec_play_start(struct sunxi_priv *priv)
+{
+#ifdef JDS
+       if (gpio_pa_shutdown)
+               gpio_write_one_pin_value(gpio_pa_shutdown, 1, "audio_pa_ctrl");
+#endif
+       //flush TX FIFO
+       regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << 
DAC_FIFO_FLUSH, 0x1 << DAC_FIFO_FLUSH);
+       //enable dac drq
+       regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_DRQ, 0x1 
<< DAC_DRQ);
+       return 0;
+}
+
+static int codec_play_stop(struct sunxi_priv *priv)
+{
+       //pa mute
+#ifdef JDS
+       if (gpio_pa_shutdown)
+               gpio_write_one_pin_value(gpio_pa_shutdown, 0, "audio_pa_ctrl");
+#endif
+       regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << PA_MUTE, 0x0 << 
PA_MUTE);
+       mdelay(5);
+       //disable dac drq
+       regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_DRQ, 0x0 
<< DAC_DRQ);
+       //pa mute
+       regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << PA_MUTE, 0x0 << 
PA_MUTE);
+       regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACAEN_L, 0x0 
<< DACAEN_L);
+       regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACAEN_R, 0x0 
<< DACAEN_R);
+       return 0;
+}
+
+static int codec_capture_start(struct sunxi_priv *priv)
+{
+       //enable adc drq
+#ifdef JDS
+       if (gpio_pa_shutdown)
+               gpio_write_one_pin_value(gpio_pa_shutdown, 1, "audio_pa_ctrl");
+#endif
+       regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_DRQ, 0x1 
<< ADC_DRQ);
+       return 0;
+}
+
+static int codec_capture_stop(struct sunxi_priv *priv)
+{
+       //disable adc drq
+       regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_DRQ, 0x0 
<< ADC_DRQ);
+       //enable mic1 pa
+       regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x1 << MIC1_EN, 0x0 << 
MIC1_EN);
+       //enable VMIC
+       regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x1 << VMIC_EN, 0x0 << 
VMIC_EN);
+       if (priv->id == SUN7I) {
+               regmap_update_bits(priv->regmap, SUNXI_DAC_TUNE, 0x3 << 8, 0x0 
<< 8);
+       }
+       //enable adc digital
+       regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_DIG_EN, 
0x0 << ADC_DIG_EN);
+       //set RX FIFO mode
+       regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << RX_FIFO_MODE, 
0x0 << RX_FIFO_MODE);
+       //flush RX FIFO
+       regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << 
ADC_FIFO_FLUSH, 0x0 << ADC_FIFO_FLUSH);
+       //enable adc1 analog
+       regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x3 << ADC_EN, 0x0 << 
ADC_EN);
+       return 0;
+}
+
+static int sunxi_codec_trigger(struct snd_pcm_substream *substream, int cmd,
+       struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *codec_dai = rtd->codec_dai;
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct snd_soc_card *card = codec->card;
+       struct sunxi_priv *priv = snd_soc_card_get_drvdata(card);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+                       codec_capture_start(priv);
+               else
+                       codec_play_start(priv);
+               break;          break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+                       codec_capture_stop(priv);
+               else
+                       codec_play_stop(priv);
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int sunxi_codec_prepare(struct snd_pcm_substream *substream, struct 
snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *codec_dai = rtd->codec_dai;
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct snd_soc_card *card = codec->card;
+       struct sunxi_priv *priv = snd_soc_card_get_drvdata(card);
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK){
+               regmap_update_bits(priv->regmap, SUNXI_DAC_DPC, 0x1 << DAC_EN, 
0x1 << DAC_EN);
+               regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << 
DAC_FIFO_FLUSH, 0x1 << DAC_FIFO_FLUSH);
+               //set TX FIFO send drq level
+               regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0xf << 
TX_TRI_LEVEL, 0xf << TX_TRI_LEVEL);
+               if (substream->runtime->rate > 32000) {
+                       regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 
<< 28, 0x0 << 28);
+               } else {
+                       regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 
<< 28, 0x1 << 28);
+               }
+               //set TX FIFO MODE
+               regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << 
TX_FIFO_MODE, 0x1 << TX_FIFO_MODE);
+               //send last sample when dac fifo under run
+               regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << 
LAST_SE, 0x0 << LAST_SE);
+               //enable dac analog
+               regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << 
DACAEN_L, 0x1 << DACAEN_L);
+               regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << 
DACAEN_R, 0x1 << DACAEN_R);
+               //enable dac to pa
+               regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACPAS, 
0x1 << DACPAS);
+       } else {
+               //enable mic1 pa
+               regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x1 << 
MIC1_EN, 0x1 << MIC1_EN);
+               //mic1 gain 32dB
+               regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x3 << 25, 0x1 
<< 25);
+               //enable VMIC
+               regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x1 << 
VMIC_EN, 0x1 << VMIC_EN);
+
+               if (priv->id == SUN7I) {
+                       /* boost up record effect */
+                       regmap_update_bits(priv->regmap, SUNXI_DAC_TUNE, 0x3 << 
8, 0x1 << 8);
+               }
+
+               //enable adc digital
+               regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << 
ADC_DIG_EN, 0x1 << ADC_DIG_EN);
+               //set RX FIFO mode
+               regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << 
RX_FIFO_MODE, 0x1 << RX_FIFO_MODE);
+               //flush RX FIFO
+               regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << 
ADC_FIFO_FLUSH, 0x1 << ADC_FIFO_FLUSH);
+               //set RX FIFO rec drq level
+               regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0xf << 
RX_TRI_LEVEL, 0x7 << RX_TRI_LEVEL);
+               //enable adc1 analog
+               regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x3 << ADC_EN, 
0x3 << ADC_EN);
+       }
+       return 0;
+}
+
+static int sunxi_codec_hw_params(struct snd_pcm_substream *substream,
+       struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *codec_dai = rtd->codec_dai;
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct snd_soc_card *card = codec->card;
+       struct sunxi_priv *priv = snd_soc_card_get_drvdata(card);
+       unsigned int rate = params_rate(params);
+
+       switch (params_rate(params)) {
+       case 44100:
+       case 22050:
+       case 11025:
+       default:
+               clk_set_rate(priv->clk_pll2, 22579200);
+               clk_set_rate(priv->clk_module, 22579200);
+               break;
+       case 192000:
+       case 96000:
+       case 48000:
+       case 32000:
+       case 24000:
+       case 16000:
+       case 12000:
+       case 8000:
+               clk_set_rate(priv->clk_pll2, 24576000);
+               clk_set_rate(priv->clk_module, 24576000);
+               break;
+       }
+
+       switch (params_rate(params)) {
+       default:
+       case 44100:
+               rate = 0;
+               break;
+       case 22050:
+               rate = 2;
+               break;
+       case 11025:
+               rate = 4;
+               break;
+       case 192000:
+               rate = 6;
+               break;
+       case 96000:
+               rate = 7;
+               break;
+       case 48000:
+               rate = 0;
+               break;
+       case 32000:
+               rate = 1;
+               break;
+       case 24000:
+               rate = 2;
+               break;
+       case 16000:
+               rate = 3;
+               break;
+       case 12000:
+               rate = 4;
+               break;
+       case 8000:
+               rate = 5;
+               break;
+       }
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 7 << 29, rate 
<< 29);
+               if (substream->runtime->channels == 1)
+                       regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 1 << 
6, 1 << 6);
+               else
+                       regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 1 << 
6, 0 << 6);
+       } else  {
+               regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 7 << 29, rate 
<< 29);
+               if (substream->runtime->channels == 1)
+                       regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 1 << 
7, 1 << 7);
+               else
+                       regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 1 << 
7, 0 << 7);
+       }
+       return 0;
+}
+
+static const struct snd_kcontrol_new sun7i_dac_ctls[] = {
+       /*SUNXI_DAC_ACTL = 0x10,PAVOL*/
+       SOC_SINGLE("Master Playback Volume", SUNXI_DAC_ACTL, 0, 0x3f, 0), 
+       SOC_SINGLE("Playback Switch", SUNXI_DAC_ACTL, 6, 1, 0), //全局输出开关
+       SOC_SINGLE("FmL Switch", SUNXI_DAC_ACTL, 17, 1, 0), //Fm左开关
+       SOC_SINGLE("FmR Switch", SUNXI_DAC_ACTL, 16, 1, 0), //Fm右开关
+       SOC_SINGLE("LineL Switch", SUNXI_DAC_ACTL, 19, 1, 0), //Line左开关
+       SOC_SINGLE("LineR Switch", SUNXI_DAC_ACTL, 18, 1, 0), //Line右开关
+       SOC_SINGLE("Ldac Left Mixer", SUNXI_DAC_ACTL, 15, 1, 0), 
+       SOC_SINGLE("Rdac Right Mixer", SUNXI_DAC_ACTL, 14, 1, 0), 
+       SOC_SINGLE("Ldac Right Mixer", SUNXI_DAC_ACTL, 13, 1, 0), 
+       SOC_SINGLE("Mic Input Mux", SUNXI_DAC_ACTL, 9, 15, 0), //from bit 9 to 
bit 12.Mic(麦克风)输入静音
+       SOC_SINGLE("MIC output volume", SUNXI_DAC_ACTL, 20, 7, 0),
+       /*      FM Input to output mixer Gain Control
+       *       From -4.5db to 6db,1.5db/step,default is 0db
+       *       -4.5db:0x0,-3.0db:0x1,-1.5db:0x2,0db:0x3
+       *       1.5db:0x4,3.0db:0x5,4.5db:0x6,6db:0x7
+       */
+       SOC_SINGLE("Fm output Volume", SUNXI_DAC_ACTL, 23, 7, 0),
+       /*      Line-in gain stage to output mixer Gain Control
+       *       0:-1.5db,1:0db
+       */
+       SOC_SINGLE("Line output Volume", SUNXI_DAC_ACTL, 26, 1, 0),
+
+       SOC_SINGLE("Master Capture Mute", SUNXI_ADC_ACTL, 4, 1, 0), 
+       SOC_SINGLE("Right Capture Mute", SUNXI_ADC_ACTL, 31, 1, 0), 
+       SOC_SINGLE("Left Capture Mute", SUNXI_ADC_ACTL, 30, 1, 0), 
+       SOC_SINGLE("Linein Pre-AMP", SUNXI_ADC_ACTL, 13, 7, 0), 
+       SOC_SINGLE("LINEIN APM Volume", SUNXI_MIC_CRT, 13, 0x7, 0),
+       /* ADC Input Gain Control, capture volume
+       * 
000:-4.5db,001:-3db,010:-1.5db,011:0db,100:1.5db,101:3db,110:4.5db,111:6db
+       */
+       SOC_SINGLE("Capture Volume", SUNXI_ADC_ACTL, 20, 7, 0),
+       /*
+       *       MIC2 pre-amplifier Gain Control
+       *       00:0db,01:35db,10:38db,11:41db
+       */
+       SOC_SINGLE("MicL Volume", SUNXI_ADC_ACTL, 25, 3, 0), //mic左音量
+       SOC_SINGLE("MicR Volume", SUNXI_ADC_ACTL, 23, 3, 0), //mic右音量
+       SOC_SINGLE("Mic2 Boost", SUNXI_ADC_ACTL, 29, 1, 0), 
+       SOC_SINGLE("Mic1 Boost", SUNXI_ADC_ACTL, 28, 1, 0), 
+       SOC_SINGLE("Mic Power", SUNXI_ADC_ACTL, 27, 1, 0), 
+       SOC_SINGLE("ADC Input Mux", SUNXI_ADC_ACTL, 17, 7, 0), //ADC输入静音
+       SOC_SINGLE("Mic2 gain Volume", SUNXI_MIC_CRT, 26, 7, 0),
+       /*
+       *       MIC1 pre-amplifier Gain Control
+       *       00:0db,01:35db,10:38db,11:41db
+       */
+       SOC_SINGLE("Mic1 gain Volume", SUNXI_MIC_CRT, 29, 3, 0), 
+};
+
+
+static int sunxi_codec_dai_probe(struct snd_soc_dai *dai)
+{
+       struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai);
+       struct sunxi_priv *priv = snd_soc_card_get_drvdata(card);
+
+       snd_soc_dai_init_dma_data(dai, &priv->playback_dma_data, 
&priv->capture_dma_data);
+
+       return 0;
+}
+
+static int sunxi_codec_startup(struct snd_pcm_substream *substream,
+       struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *codec_dai = rtd->codec_dai;
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct snd_soc_card *card = codec->card;
+       struct sunxi_priv *priv = snd_soc_card_get_drvdata(card);
+       int ret;
+
+       ret = clk_prepare_enable(priv->clk_module);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static void sunxi_codec_shutdown(struct snd_pcm_substream *substream,
+       struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *codec_dai = rtd->codec_dai;
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct snd_soc_card *card = codec->card;
+       struct sunxi_priv *priv = snd_soc_card_get_drvdata(card);
+
+       clk_disable_unprepare(priv->clk_module);
+}
+
+static const struct snd_soc_dai_ops sunxi_codec_dai_ops = {
+       .startup = sunxi_codec_startup,
+       .shutdown = sunxi_codec_shutdown,
+       .trigger = sunxi_codec_trigger,
+       .hw_params = sunxi_codec_hw_params,
+       .prepare = sunxi_codec_prepare,
+};
+
+static struct snd_soc_dai_driver sunxi_codec_dai = {
+       .probe = sunxi_codec_dai_probe,
+       .playback = {
+               .channels_min = 1,
+               .channels_max = 2,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE,
+
+               .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | 
SNDRV_PCM_RATE_11025 |\
+                        SNDRV_PCM_RATE_22050| SNDRV_PCM_RATE_32000 | 
SNDRV_PCM_RATE_44100 |\
+                        SNDRV_PCM_RATE_48000 |SNDRV_PCM_RATE_96000 | 
SNDRV_PCM_RATE_192000 |\
+                        SNDRV_PCM_RATE_KNOT),
+               .rate_min = 8000,
+               .rate_max = 192000,
+       },
+       .ops = &sunxi_codec_dai_ops,
+};
+
+static const struct snd_soc_component_driver sunxi_codec_component = {
+       .name = "sunxi-codec",
+};
+
+static const struct regmap_config sunxi_codec_regmap_config = {
+       .reg_bits = 32,
+       .reg_stride = 4,
+       .val_bits = 32,
+       .max_register = SUNXI_MIC_CRT,
+};
+
+static const struct snd_soc_dapm_widget codec_dapm_widgets[] = {
+       SND_SOC_DAPM_OUTPUT("Mic Bias"),
+       SND_SOC_DAPM_OUTPUT("HP_OUT"),
+       SND_SOC_DAPM_INPUT("MIC_IN"),
+       SND_SOC_DAPM_INPUT("LINE_IN"),
+};
+
+static struct snd_soc_dai_link cdc_dai = {
+       .name = "cdc",
+       .stream_name = "CDC PCM",
+       .codec_dai_name = "sunxi-codec-dai",
+       .cpu_dai_name = "1c22c00.codec",
+       .codec_name = "1c22c00.codec",
+       .platform_name = "1c22c00.codec",
+       //.init = tegra_wm8903_init,
+       //.ops = &tegra_wm8903_ops,
+       .dai_fmt = SND_SOC_DAIFMT_I2S,
+};
+
+static struct snd_soc_card snd_soc_sunxi_codec = {
+       .name = "sunxi-codec",
+       .owner = THIS_MODULE,
+       .dai_link = &cdc_dai,
+       .num_links = 1,
+};
+
+static struct snd_soc_codec_driver dummy_codec = {
+       .controls = sun7i_dac_ctls,
+       .num_controls = ARRAY_SIZE(sun7i_dac_ctls),
+       .dapm_widgets = codec_dapm_widgets,
+       .num_dapm_widgets = ARRAY_SIZE(codec_dapm_widgets),
+};
+
+#define STUB_RATES     SNDRV_PCM_RATE_8000_192000
+#define STUB_FORMATS   (SNDRV_PCM_FMTBIT_S8 | \
+                       SNDRV_PCM_FMTBIT_U8 | \
+                       SNDRV_PCM_FMTBIT_S16_LE | \
+                       SNDRV_PCM_FMTBIT_U16_LE | \
+                       SNDRV_PCM_FMTBIT_S24_LE | \
+                       SNDRV_PCM_FMTBIT_U24_LE | \
+                       SNDRV_PCM_FMTBIT_S32_LE | \
+                       SNDRV_PCM_FMTBIT_U32_LE | \
+                       SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE)
+static struct snd_soc_dai_driver dummy_dai = {
+       .name = "sunxi-codec-dai",
+       .playback = {
+               .stream_name    = "Playback",
+               .channels_min   = 1,
+               .channels_max   = 2,
+               .rates          = STUB_RATES,
+               .formats        = STUB_FORMATS,
+       },
+       .capture = {
+               .stream_name    = "Capture",
+               .channels_min   = 1,
+               .channels_max   = 2,
+               .rates = STUB_RATES,
+               .formats = STUB_FORMATS,
+        },
+};
+
+static const struct of_device_id sunxi_codec_of_match[] = {
+       { .compatible = "allwinner,sun4i-a10a-codec", .data = (void *)SUN4A},
+       { .compatible = "allwinner,sun4i-a10-codec", .data = (void *)SUN4I},
+       { .compatible = "allwinner,sun5i-a13-codec", .data = (void *)SUN5I},
+       { .compatible = "allwinner,sun7i-a20-codec", .data = (void *)SUN7I},
+       {}
+};
+MODULE_DEVICE_TABLE(of, sunxi_codec_of_match);
+
+static int sunxi_codec_probe(struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct snd_soc_card *card = &snd_soc_sunxi_codec;
+       const struct of_device_id *of_id;
+       struct device *dev = &pdev->dev;
+       struct sunxi_priv *priv;
+       struct resource *res;
+       void __iomem *base;
+       int ret;
+
+       if (!of_device_is_available(np))
+               return -ENODEV;
+
+       of_id = of_match_device(sunxi_codec_of_match, dev);
+       if (!of_id)
+               return -EINVAL;
+
+       priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       card->dev = &pdev->dev;
+       platform_set_drvdata(pdev, card);
+       snd_soc_card_set_drvdata(card, priv);
+
+       priv->id = (int)of_id->data;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(base))
+               return PTR_ERR(base);
+
+       priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+                                           &sunxi_codec_regmap_config);
+       if (IS_ERR(priv->regmap))
+               return PTR_ERR(priv->regmap);
+
+       priv->irq = irq_of_parse_and_map(np, 0);
+       if (!priv->irq) {
+               dev_err(dev, "no irq for node %s\n", np->full_name);
+               return -ENXIO;
+       }
+
+       /* Clock */
+       priv->clk_apb = devm_clk_get(dev, "apb");
+       if (IS_ERR(priv->clk_apb)) {
+               dev_err(dev, "failed to get apb clock.\n");
+               return PTR_ERR(priv->clk_apb);
+       }
+       priv->clk_pll2 = devm_clk_get(dev, "pll2");
+       if (IS_ERR(priv->clk_pll2)) {
+               dev_err(dev, "failed to get pll2 clock.\n");
+               return PTR_ERR(priv->clk_pll2);
+       }
+       priv->clk_module = devm_clk_get(dev, "codec");
+       if (IS_ERR(priv->clk_module)) {
+               dev_err(dev, "failed to get codec clock.\n");
+               return PTR_ERR(priv->clk_module);
+       }
+
+       ret = clk_set_rate(priv->clk_pll2, 24576000);
+       if (ret) {
+               dev_err(dev, "set codec base clock rate failed!\n");
+               return ret;
+       }
+       if (clk_prepare_enable(priv->clk_pll2)) {
+               dev_err(dev, "try to enable clk_pll2 failed\n");
+               return -EINVAL;
+       }
+       if (clk_prepare_enable(priv->clk_apb)) {
+               dev_err(dev, "try to enable clk_apb failed\n");
+               return -EINVAL;
+       }
+
+       priv->playback_dma_data.addr = res->start + SUNXI_DAC_TXDATA;
+       priv->playback_dma_data.maxburst = 4;
+       priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+
+       priv->capture_dma_data.addr = res->start + SUNXI_ADC_RXDATA;
+       priv->capture_dma_data.maxburst = 4;
+       priv->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+
+       ret = snd_soc_register_codec(&pdev->dev, &dummy_codec, &dummy_dai, 1);
+
+       ret = devm_snd_soc_register_component(&pdev->dev, 
&sunxi_codec_component, &sunxi_codec_dai, 1);
+       if (ret)
+               goto err_clk_disable;
+
+       ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+       if (ret)
+               goto err_clk_disable;
+
+       ret = snd_soc_register_card(card);
+       if (ret) {
+               dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+               goto err_fini_utils;
+       }
+
+       ret = snd_soc_of_parse_audio_routing(card, "routing");
+       if (ret)
+               goto err;
+
+       return 0;
+
+err_fini_utils:
+err:
+err_clk_disable:
+       clk_disable_unprepare(priv->clk_module);
+       clk_disable_unprepare(priv->clk_apb);
+       clk_disable_unprepare(priv->clk_pll2);
+       return ret;
+}
+
+static int sunxi_codec_dev_remove(struct platform_device *pdev)
+{
+       struct sunxi_priv *priv = platform_get_drvdata(pdev);
+
+       clk_disable_unprepare(priv->clk_module);
+       clk_disable_unprepare(priv->clk_apb);
+       clk_disable_unprepare(priv->clk_pll2);
+
+       return 0;
+}
+
+static struct platform_driver sunxi_codec_driver = {
+       .driver = {
+               .name = "sunxi-codec",
+               .owner = THIS_MODULE,
+               .of_match_table = sunxi_codec_of_match,
+       },
+       .probe = sunxi_codec_probe,
+       .remove = sunxi_codec_dev_remove,
+};
+module_platform_driver(sunxi_codec_driver);
+
+MODULE_ALIAS("platform:sunxi-codec");
+MODULE_DESCRIPTION("sunxi CODEC ALSA codec driver");
+MODULE_AUTHOR("software");
+MODULE_LICENSE("GPL v2");
+

-- 
You received this message because you are subscribed to the Google Groups 
"linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to linux-sunxi+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to