Эх сурвалжийг харах

Merge remote-tracking branches 'asoc/topic/wm8960', 'asoc/topic/wm8978' and 'asoc/topic/zte-tdm' into asoc-next

Mark Brown 8 жил өмнө
parent
commit
20d5c84bef

+ 30 - 0
Documentation/devicetree/bindings/sound/zte,tdm.txt

@@ -0,0 +1,30 @@
+ZTE TDM DAI driver
+
+Required properties:
+
+- compatible : should be one of the following.
+       * zte,zx296718-tdm
+- reg : physical base address of the controller and length of memory mapped
+    region.
+- clocks : Pairs of phandle and specifier referencing the controller's clocks.
+- clock-names: "wclk" for the wclk.
+               "pclk" for the pclk.
+-#clock-cells: should be 1.
+- zte,tdm-dma-sysctrl : Reference to the sysctrl controller controlling
+    the dma. includes:
+	phandle of sysctrl.
+	register offset in sysctrl for control dma.
+	mask of the register that be written to sysctrl.
+
+Example:
+
+	tdm: tdm@1487000 {
+		compatible = "zte,zx296718-tdm";
+		reg = <0x01487000 0x1000>;
+		clocks = <&audiocrm AUDIO_TDM_WCLK>, <&audiocrm AUDIO_TDM_PCLK>;
+		clock-names = "wclk", "pclk";
+		#clock-cells = <1>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&tdm_global_pin>;
+		zte,tdm-dma-sysctrl = <&sysctrl 0x10c 4>;
+	};

+ 146 - 49
sound/soc/codecs/wm8960.c

@@ -604,12 +604,150 @@ static const int bclk_divs[] = {
 	120, 160, 220, 240, 320, 320, 320
 	120, 160, 220, 240, 320, 320, 320
 };
 };
 
 
+/**
+ * wm8960_configure_sysclk - checks if there is a sysclk frequency available
+ *	The sysclk must be chosen such that:
+ *		- sysclk     = MCLK / sysclk_divs
+ *		- lrclk      = sysclk / dac_divs
+ *		- 10 * bclk  = sysclk / bclk_divs
+ *
+ *	If we cannot find an exact match for (sysclk, lrclk, bclk)
+ *	triplet, we relax the bclk such that bclk is chosen as the
+ *	closest available frequency greater than expected bclk.
+ *
+ * @wm8960_priv: wm8960 codec private data
+ * @mclk: MCLK used to derive sysclk
+ * @sysclk_idx: sysclk_divs index for found sysclk
+ * @dac_idx: dac_divs index for found lrclk
+ * @bclk_idx: bclk_divs index for found bclk
+ *
+ * Returns:
+ *  -1, in case no sysclk frequency available found
+ * >=0, in case we could derive bclk and lrclk from sysclk using
+ *      (@sysclk_idx, @dac_idx, @bclk_idx) dividers
+ */
+static
+int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk,
+			    int *sysclk_idx, int *dac_idx, int *bclk_idx)
+{
+	int sysclk, bclk, lrclk;
+	int i, j, k;
+	int diff, closest = mclk;
+
+	/* marker for no match */
+	*bclk_idx = -1;
+
+	bclk = wm8960->bclk;
+	lrclk = wm8960->lrclk;
+
+	/* check if the sysclk frequency is available. */
+	for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
+		if (sysclk_divs[i] == -1)
+			continue;
+		sysclk = mclk / sysclk_divs[i];
+		for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
+			if (sysclk != dac_divs[j] * lrclk)
+				continue;
+			for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
+				diff = sysclk - bclk * bclk_divs[k] / 10;
+				if (diff == 0) {
+					*sysclk_idx = i;
+					*dac_idx = j;
+					*bclk_idx = k;
+					break;
+				}
+				if (diff > 0 && closest > diff) {
+					*sysclk_idx = i;
+					*dac_idx = j;
+					*bclk_idx = k;
+					closest = diff;
+				}
+			}
+			if (k != ARRAY_SIZE(bclk_divs))
+				break;
+		}
+		if (j != ARRAY_SIZE(dac_divs))
+			break;
+	}
+	return *bclk_idx;
+}
+
+/**
+ * wm8960_configure_pll - checks if there is a PLL out frequency available
+ *	The PLL out frequency must be chosen such that:
+ *		- sysclk      = lrclk * dac_divs
+ *		- freq_out    = sysclk * sysclk_divs
+ *		- 10 * sysclk = bclk * bclk_divs
+ *
+ * 	If we cannot find an exact match for (sysclk, lrclk, bclk)
+ * 	triplet, we relax the bclk such that bclk is chosen as the
+ * 	closest available frequency greater than expected bclk.
+ *
+ * @codec: codec structure
+ * @freq_in: input frequency used to derive freq out via PLL
+ * @sysclk_idx: sysclk_divs index for found sysclk
+ * @dac_idx: dac_divs index for found lrclk
+ * @bclk_idx: bclk_divs index for found bclk
+ *
+ * Returns:
+ * < 0, in case no PLL frequency out available was found
+ * >=0, in case we could derive bclk, lrclk, sysclk from PLL out using
+ *      (@sysclk_idx, @dac_idx, @bclk_idx) dividers
+ */
+static
+int wm8960_configure_pll(struct snd_soc_codec *codec, int freq_in,
+			 int *sysclk_idx, int *dac_idx, int *bclk_idx)
+{
+	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+	int sysclk, bclk, lrclk, freq_out;
+	int diff, closest, best_freq_out;
+	int i, j, k;
+
+	bclk = wm8960->bclk;
+	lrclk = wm8960->lrclk;
+	closest = freq_in;
+
+	best_freq_out = -EINVAL;
+	*sysclk_idx = *dac_idx = *bclk_idx = -1;
+
+	for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
+		if (sysclk_divs[i] == -1)
+			continue;
+		for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
+			sysclk = lrclk * dac_divs[j];
+			freq_out = sysclk * sysclk_divs[i];
+
+			for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
+				if (!is_pll_freq_available(freq_in, freq_out))
+					continue;
+
+				diff = sysclk - bclk * bclk_divs[k] / 10;
+				if (diff == 0) {
+					*sysclk_idx = i;
+					*dac_idx = j;
+					*bclk_idx = k;
+					return freq_out;
+				}
+				if (diff > 0 && closest > diff) {
+					*sysclk_idx = i;
+					*dac_idx = j;
+					*bclk_idx = k;
+					closest = diff;
+					best_freq_out = freq_out;
+				}
+			}
+		}
+	}
+
+	return best_freq_out;
+}
 static int wm8960_configure_clocking(struct snd_soc_codec *codec)
 static int wm8960_configure_clocking(struct snd_soc_codec *codec)
 {
 {
 	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
 	struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
-	int sysclk, bclk, lrclk, freq_out, freq_in;
+	int freq_out, freq_in;
 	u16 iface1 = snd_soc_read(codec, WM8960_IFACE1);
 	u16 iface1 = snd_soc_read(codec, WM8960_IFACE1);
 	int i, j, k;
 	int i, j, k;
+	int ret;
 
 
 	if (!(iface1 & (1<<6))) {
 	if (!(iface1 & (1<<6))) {
 		dev_dbg(codec->dev,
 		dev_dbg(codec->dev,
@@ -623,8 +761,6 @@ static int wm8960_configure_clocking(struct snd_soc_codec *codec)
 	}
 	}
 
 
 	freq_in = wm8960->freq_in;
 	freq_in = wm8960->freq_in;
-	bclk = wm8960->bclk;
-	lrclk = wm8960->lrclk;
 	/*
 	/*
 	 * If it's sysclk auto mode, check if the MCLK can provide sysclk or
 	 * If it's sysclk auto mode, check if the MCLK can provide sysclk or
 	 * not. If MCLK can provide sysclk, using MCLK to provide sysclk
 	 * not. If MCLK can provide sysclk, using MCLK to provide sysclk
@@ -643,60 +779,21 @@ static int wm8960_configure_clocking(struct snd_soc_codec *codec)
 	}
 	}
 
 
 	if (wm8960->clk_id != WM8960_SYSCLK_PLL) {
 	if (wm8960->clk_id != WM8960_SYSCLK_PLL) {
-		/* check if the sysclk frequency is available. */
-		for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
-			if (sysclk_divs[i] == -1)
-				continue;
-			sysclk = freq_out / sysclk_divs[i];
-			for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
-				if (sysclk != dac_divs[j] * lrclk)
-					continue;
-				for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k)
-					if (sysclk == bclk * bclk_divs[k] / 10)
-						break;
-				if (k != ARRAY_SIZE(bclk_divs))
-					break;
-			}
-			if (j != ARRAY_SIZE(dac_divs))
-				break;
-		}
-
-		if (i != ARRAY_SIZE(sysclk_divs)) {
+		ret = wm8960_configure_sysclk(wm8960, freq_out, &i, &j, &k);
+		if (ret >= 0) {
 			goto configure_clock;
 			goto configure_clock;
 		} else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) {
 		} else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) {
 			dev_err(codec->dev, "failed to configure clock\n");
 			dev_err(codec->dev, "failed to configure clock\n");
 			return -EINVAL;
 			return -EINVAL;
 		}
 		}
 	}
 	}
-	/* get a available pll out frequency and set pll */
-	for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
-		if (sysclk_divs[i] == -1)
-			continue;
-		for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
-			sysclk = lrclk * dac_divs[j];
-			freq_out = sysclk * sysclk_divs[i];
 
 
-			for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
-				if (sysclk == bclk * bclk_divs[k] / 10 &&
-				    is_pll_freq_available(freq_in, freq_out)) {
-					wm8960_set_pll(codec,
-						       freq_in, freq_out);
-					break;
-				} else {
-					continue;
-				}
-			}
-			if (k != ARRAY_SIZE(bclk_divs))
-				break;
-		}
-		if (j != ARRAY_SIZE(dac_divs))
-			break;
-	}
-
-	if (i == ARRAY_SIZE(sysclk_divs)) {
-		dev_err(codec->dev, "failed to configure clock\n");
-		return -EINVAL;
+	freq_out = wm8960_configure_pll(codec, freq_in, &i, &j, &k);
+	if (freq_out < 0) {
+		dev_err(codec->dev, "failed to configure clock via PLL\n");
+		return freq_out;
 	}
 	}
+	wm8960_set_pll(codec, freq_in, freq_out);
 
 
 configure_clock:
 configure_clock:
 	/* configure sysclk clock */
 	/* configure sysclk clock */

+ 7 - 0
sound/soc/codecs/wm8978.c

@@ -1071,9 +1071,16 @@ static const struct i2c_device_id wm8978_i2c_id[] = {
 };
 };
 MODULE_DEVICE_TABLE(i2c, wm8978_i2c_id);
 MODULE_DEVICE_TABLE(i2c, wm8978_i2c_id);
 
 
+static const struct of_device_id wm8978_of_match[] = {
+	{ .compatible = "wlf,wm8978", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, wm8978_of_match);
+
 static struct i2c_driver wm8978_i2c_driver = {
 static struct i2c_driver wm8978_i2c_driver = {
 	.driver = {
 	.driver = {
 		.name = "wm8978",
 		.name = "wm8978",
+		.of_match_table = wm8978_of_match,
 	},
 	},
 	.probe =    wm8978_i2c_probe,
 	.probe =    wm8978_i2c_probe,
 	.remove =   wm8978_i2c_remove,
 	.remove =   wm8978_i2c_remove,

+ 8 - 0
sound/soc/zte/Kconfig

@@ -15,3 +15,11 @@ config ZX_I2S
 	help
 	help
 	  Say Y or M if you want to add support for codecs attached to the
 	  Say Y or M if you want to add support for codecs attached to the
 	  ZTE ZX I2S interface
 	  ZTE ZX I2S interface
+
+config ZX_TDM
+        tristate "ZTE ZX TDM Driver Support"
+        depends on COMMON_CLK
+        select SND_SOC_GENERIC_DMAENGINE_PCM
+        help
+          Say Y or M if you want to add support for codecs attached to the
+          ZTE ZX TDM interface

+ 1 - 0
sound/soc/zte/Makefile

@@ -1,2 +1,3 @@
 obj-$(CONFIG_ZX_SPDIF)	+= zx-spdif.o
 obj-$(CONFIG_ZX_SPDIF)	+= zx-spdif.o
 obj-$(CONFIG_ZX_I2S)	+= zx-i2s.o
 obj-$(CONFIG_ZX_I2S)	+= zx-i2s.o
+obj-$(CONFIG_ZX_TDM)	+= zx-tdm.o

+ 461 - 0
sound/soc/zte/zx-tdm.c

@@ -0,0 +1,461 @@
+/*
+ * ZTE's TDM driver
+ *
+ * Copyright (C) 2017 ZTE Ltd
+ *
+ * Author: Baoyou Xie <baoyou.xie@linaro.org>
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#define	REG_TIMING_CTRL		0x04
+#define	REG_TX_FIFO_CTRL	0x0C
+#define	REG_RX_FIFO_CTRL	0x10
+#define REG_INT_EN		0x1C
+#define REG_INT_STATUS		0x20
+#define REG_DATABUF		0x24
+#define REG_TS_MASK0		0x44
+#define REG_PROCESS_CTRL	0x54
+
+#define FIFO_CTRL_TX_RST	BIT(0)
+#define FIFO_CTRL_RX_RST	BIT(0)
+#define DEAGULT_FIFO_THRES	GENMASK(4, 2)
+
+#define FIFO_CTRL_TX_DMA_EN	BIT(1)
+#define FIFO_CTRL_RX_DMA_EN	BIT(1)
+
+#define TX_FIFO_RST_MASK	BIT(0)
+#define RX_FIFO_RST_MASK	BIT(0)
+
+#define FIFOCTRL_TX_FIFO_RST	BIT(0)
+#define FIFOCTRL_RX_FIFO_RST	BIT(0)
+
+#define TXTH_MASK		GENMASK(5, 2)
+#define RXTH_MASK		GENMASK(5, 2)
+
+#define FIFOCTRL_THRESHOLD(x)	((x) << 2)
+
+#define TIMING_MS_MASK		BIT(1)
+/*
+ * 00: 8 clk cycles every timeslot
+ * 01: 16 clk cycles every timeslot
+ * 10: 32 clk cycles every timeslot
+ */
+#define TIMING_SYNC_WIDTH_MASK	GENMASK(6, 5)
+#define TIMING_WIDTH_SHIFT      5
+#define TIMING_DEFAULT_WIDTH    0
+#define TIMING_TS_WIDTH(x)	((x) << TIMING_WIDTH_SHIFT)
+#define TIMING_WIDTH_FACTOR     8
+
+#define TIMING_MASTER_MODE	BIT(21)
+#define TIMING_LSB_FIRST	BIT(20)
+#define TIMING_TS_NUM(x)	(((x) - 1) << 7)
+#define TIMING_CLK_SEL_MASK	GENMASK(2, 0)
+#define TIMING_CLK_SEL_DEF	BIT(2)
+
+#define PROCESS_TX_EN		BIT(0)
+#define PROCESS_RX_EN		BIT(1)
+#define PROCESS_TDM_EN		BIT(2)
+#define PROCESS_DISABLE_ALL	0
+
+#define INT_DISABLE_ALL		0
+#define INT_STATUS_MASK		GENMASK(6, 0)
+
+struct zx_tdm_info {
+	struct snd_dmaengine_dai_dma_data	dma_playback;
+	struct snd_dmaengine_dai_dma_data	dma_capture;
+	resource_size_t				phy_addr;
+	void __iomem				*regbase;
+	struct clk				*dai_wclk;
+	struct clk				*dai_pclk;
+	int					master;
+	struct device				*dev;
+};
+
+static inline u32 zx_tdm_readl(struct zx_tdm_info *tdm, u16 reg)
+{
+	return readl_relaxed(tdm->regbase + reg);
+}
+
+static inline void zx_tdm_writel(struct zx_tdm_info *tdm, u16 reg, u32 val)
+{
+	writel_relaxed(val, tdm->regbase + reg);
+}
+
+static void zx_tdm_tx_en(struct zx_tdm_info *tdm, bool on)
+{
+	unsigned long val;
+
+	val = zx_tdm_readl(tdm, REG_PROCESS_CTRL);
+	if (on)
+		val |= PROCESS_TX_EN | PROCESS_TDM_EN;
+	else
+		val &= ~(PROCESS_TX_EN | PROCESS_TDM_EN);
+	zx_tdm_writel(tdm, REG_PROCESS_CTRL, val);
+}
+
+static void zx_tdm_rx_en(struct zx_tdm_info *tdm, bool on)
+{
+	unsigned long val;
+
+	val = zx_tdm_readl(tdm, REG_PROCESS_CTRL);
+	if (on)
+		val |= PROCESS_RX_EN | PROCESS_TDM_EN;
+	else
+		val &= ~(PROCESS_RX_EN | PROCESS_TDM_EN);
+	zx_tdm_writel(tdm, REG_PROCESS_CTRL, val);
+}
+
+static void zx_tdm_tx_dma_en(struct zx_tdm_info *tdm, bool on)
+{
+	unsigned long val;
+
+	val = zx_tdm_readl(tdm, REG_TX_FIFO_CTRL);
+	val |= FIFO_CTRL_TX_RST | DEAGULT_FIFO_THRES;
+	if (on)
+		val |= FIFO_CTRL_TX_DMA_EN;
+	else
+		val &= ~FIFO_CTRL_TX_DMA_EN;
+	zx_tdm_writel(tdm, REG_TX_FIFO_CTRL, val);
+}
+
+static void zx_tdm_rx_dma_en(struct zx_tdm_info *tdm, bool on)
+{
+	unsigned long val;
+
+	val = zx_tdm_readl(tdm, REG_RX_FIFO_CTRL);
+	val |= FIFO_CTRL_RX_RST | DEAGULT_FIFO_THRES;
+	if (on)
+		val |= FIFO_CTRL_RX_DMA_EN;
+	else
+		val &= ~FIFO_CTRL_RX_DMA_EN;
+	zx_tdm_writel(tdm, REG_RX_FIFO_CTRL, val);
+}
+
+#define ZX_TDM_RATES	(SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000)
+
+#define ZX_TDM_FMTBIT \
+	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_MU_LAW | \
+	SNDRV_PCM_FORMAT_A_LAW)
+
+static int zx_tdm_dai_probe(struct snd_soc_dai *dai)
+{
+	struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev);
+
+	snd_soc_dai_set_drvdata(dai, zx_tdm);
+	zx_tdm->dma_playback.addr = zx_tdm->phy_addr + REG_DATABUF;
+	zx_tdm->dma_playback.maxburst = 16;
+	zx_tdm->dma_capture.addr = zx_tdm->phy_addr + REG_DATABUF;
+	zx_tdm->dma_capture.maxburst = 16;
+	snd_soc_dai_init_dma_data(dai, &zx_tdm->dma_playback,
+				  &zx_tdm->dma_capture);
+	return 0;
+}
+
+static int zx_tdm_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+	struct zx_tdm_info *tdm = snd_soc_dai_get_drvdata(cpu_dai);
+	unsigned long val;
+
+	val = zx_tdm_readl(tdm, REG_TIMING_CTRL);
+	val &= ~(TIMING_SYNC_WIDTH_MASK | TIMING_MS_MASK);
+	val |= TIMING_DEFAULT_WIDTH << TIMING_WIDTH_SHIFT;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		tdm->master = 1;
+		val |= TIMING_MASTER_MODE;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		tdm->master = 0;
+		val &= ~TIMING_MASTER_MODE;
+		break;
+	default:
+		dev_err(cpu_dai->dev, "Unknown master/slave format\n");
+		return -EINVAL;
+	}
+
+
+	zx_tdm_writel(tdm, REG_TIMING_CTRL, val);
+
+	return 0;
+}
+
+static int zx_tdm_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *socdai)
+{
+	struct zx_tdm_info *tdm = snd_soc_dai_get_drvdata(socdai);
+	struct snd_dmaengine_dai_dma_data *dma_data;
+	unsigned int ts_width = TIMING_DEFAULT_WIDTH;
+	unsigned int ch_num = 32;
+	unsigned int mask = 0;
+	unsigned int ret = 0;
+	unsigned long val;
+
+	dma_data = snd_soc_dai_get_dma_data(socdai, substream);
+	dma_data->addr_width = ch_num >> 3;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_MU_LAW:
+	case SNDRV_PCM_FORMAT_A_LAW:
+	case SNDRV_PCM_FORMAT_S16_LE:
+		ts_width = 1;
+		break;
+	default:
+		ts_width = 0;
+		dev_err(socdai->dev, "Unknown data format\n");
+		return -EINVAL;
+	}
+
+	val = zx_tdm_readl(tdm, REG_TIMING_CTRL);
+	val |= TIMING_TS_WIDTH(ts_width) | TIMING_TS_NUM(1);
+	zx_tdm_writel(tdm, REG_TIMING_CTRL, val);
+	zx_tdm_writel(tdm, REG_TS_MASK0, mask);
+
+	if (tdm->master)
+		ret = clk_set_rate(tdm->dai_wclk,
+			params_rate(params) * TIMING_WIDTH_FACTOR * ch_num);
+
+	return ret;
+}
+
+static int zx_tdm_trigger(struct snd_pcm_substream *substream, int cmd,
+			  struct snd_soc_dai *dai)
+{
+	int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+	struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev);
+	unsigned int val;
+	int ret = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		if (capture) {
+			val = zx_tdm_readl(zx_tdm, REG_RX_FIFO_CTRL);
+			val |= FIFOCTRL_RX_FIFO_RST;
+			zx_tdm_writel(zx_tdm, REG_RX_FIFO_CTRL, val);
+
+			zx_tdm_rx_dma_en(zx_tdm, true);
+		} else {
+			val = zx_tdm_readl(zx_tdm, REG_TX_FIFO_CTRL);
+			val |= FIFOCTRL_TX_FIFO_RST;
+			zx_tdm_writel(zx_tdm, REG_TX_FIFO_CTRL, val);
+
+			zx_tdm_tx_dma_en(zx_tdm, true);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (capture)
+			zx_tdm_rx_en(zx_tdm, true);
+		else
+			zx_tdm_tx_en(zx_tdm, true);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		if (capture)
+			zx_tdm_rx_dma_en(zx_tdm, false);
+		else
+			zx_tdm_tx_dma_en(zx_tdm, false);
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (capture)
+			zx_tdm_rx_en(zx_tdm, false);
+		else
+			zx_tdm_tx_en(zx_tdm, false);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int zx_tdm_startup(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev);
+	int ret;
+
+	ret = clk_prepare_enable(zx_tdm->dai_wclk);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(zx_tdm->dai_pclk);
+	if (ret) {
+		clk_disable_unprepare(zx_tdm->dai_wclk);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void zx_tdm_shutdown(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct zx_tdm_info *zx_tdm = dev_get_drvdata(dai->dev);
+
+	clk_disable_unprepare(zx_tdm->dai_pclk);
+	clk_disable_unprepare(zx_tdm->dai_wclk);
+}
+
+static struct snd_soc_dai_ops zx_tdm_dai_ops = {
+	.trigger	= zx_tdm_trigger,
+	.hw_params	= zx_tdm_hw_params,
+	.set_fmt	= zx_tdm_set_fmt,
+	.startup	= zx_tdm_startup,
+	.shutdown	= zx_tdm_shutdown,
+};
+
+static const struct snd_soc_component_driver zx_tdm_component = {
+	.name			= "zx-tdm",
+};
+
+static void zx_tdm_init_state(struct zx_tdm_info *tdm)
+{
+	unsigned int val;
+
+	zx_tdm_writel(tdm, REG_PROCESS_CTRL, PROCESS_DISABLE_ALL);
+
+	val = zx_tdm_readl(tdm, REG_TIMING_CTRL);
+	val |= TIMING_LSB_FIRST;
+	val &= ~TIMING_CLK_SEL_MASK;
+	val |= TIMING_CLK_SEL_DEF;
+	zx_tdm_writel(tdm, REG_TIMING_CTRL, val);
+
+	zx_tdm_writel(tdm, REG_INT_EN, INT_DISABLE_ALL);
+	/*
+	 * write INT_STATUS register to clear it.
+	 */
+	zx_tdm_writel(tdm, REG_INT_STATUS, INT_STATUS_MASK);
+	zx_tdm_writel(tdm, REG_RX_FIFO_CTRL, FIFOCTRL_RX_FIFO_RST);
+	zx_tdm_writel(tdm, REG_TX_FIFO_CTRL, FIFOCTRL_TX_FIFO_RST);
+
+	val = zx_tdm_readl(tdm, REG_RX_FIFO_CTRL);
+	val &= ~(RXTH_MASK | RX_FIFO_RST_MASK);
+	val |= FIFOCTRL_THRESHOLD(8);
+	zx_tdm_writel(tdm, REG_RX_FIFO_CTRL, val);
+
+	val = zx_tdm_readl(tdm, REG_TX_FIFO_CTRL);
+	val &= ~(TXTH_MASK | TX_FIFO_RST_MASK);
+	val |= FIFOCTRL_THRESHOLD(8);
+	zx_tdm_writel(tdm, REG_TX_FIFO_CTRL, val);
+}
+
+static struct snd_soc_dai_driver zx_tdm_dai = {
+	.name	= "zx-tdm-dai",
+	.id	= 0,
+	.probe	= zx_tdm_dai_probe,
+	.playback   = {
+		.channels_min	= 1,
+		.channels_max	= 4,
+		.rates		= ZX_TDM_RATES,
+		.formats	= ZX_TDM_FMTBIT,
+	},
+	.capture = {
+		.channels_min	= 1,
+		.channels_max	= 4,
+		.rates		= ZX_TDM_RATES,
+		.formats	= ZX_TDM_FMTBIT,
+	},
+	.ops	= &zx_tdm_dai_ops,
+};
+
+static int zx_tdm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct of_phandle_args out_args;
+	unsigned int dma_reg_offset;
+	struct zx_tdm_info *zx_tdm;
+	unsigned int dma_mask;
+	struct resource *res;
+	struct regmap *regmap_sysctrl;
+	int ret;
+
+	zx_tdm = devm_kzalloc(&pdev->dev, sizeof(*zx_tdm), GFP_KERNEL);
+	if (!zx_tdm)
+		return -ENOMEM;
+
+	zx_tdm->dev = dev;
+
+	zx_tdm->dai_wclk = devm_clk_get(&pdev->dev, "wclk");
+	if (IS_ERR(zx_tdm->dai_wclk)) {
+		dev_err(&pdev->dev, "Fail to get wclk\n");
+		return PTR_ERR(zx_tdm->dai_wclk);
+	}
+
+	zx_tdm->dai_pclk = devm_clk_get(&pdev->dev, "pclk");
+	if (IS_ERR(zx_tdm->dai_pclk)) {
+		dev_err(&pdev->dev, "Fail to get pclk\n");
+		return PTR_ERR(zx_tdm->dai_pclk);
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	zx_tdm->phy_addr = res->start;
+	zx_tdm->regbase = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(zx_tdm->regbase))
+		return PTR_ERR(zx_tdm->regbase);
+
+	ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
+				"zte,tdm-dma-sysctrl", 2, 0, &out_args);
+	if (ret) {
+		dev_err(&pdev->dev, "Fail to get zte,tdm-dma-sysctrl\n");
+		return ret;
+	}
+
+	dma_reg_offset = out_args.args[0];
+	dma_mask = out_args.args[1];
+	regmap_sysctrl = syscon_node_to_regmap(out_args.np);
+	if (IS_ERR(regmap_sysctrl)) {
+		of_node_put(out_args.np);
+		return PTR_ERR(regmap_sysctrl);
+	}
+
+	regmap_update_bits(regmap_sysctrl, dma_reg_offset, dma_mask, dma_mask);
+	of_node_put(out_args.np);
+
+	zx_tdm_init_state(zx_tdm);
+	platform_set_drvdata(pdev, zx_tdm);
+
+	ret = devm_snd_soc_register_component(&pdev->dev, &zx_tdm_component,
+						&zx_tdm_dai, 1);
+	if (ret) {
+		dev_err(&pdev->dev, "Register DAI failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+	if (ret)
+		dev_err(&pdev->dev, "Register platform PCM failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id zx_tdm_dt_ids[] = {
+	{ .compatible = "zte,zx296718-tdm", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, zx_tdm_dt_ids);
+
+static struct platform_driver tdm_driver = {
+	.probe = zx_tdm_probe,
+	.driver = {
+		.name = "zx-tdm",
+		.of_match_table = zx_tdm_dt_ids,
+	},
+};
+module_platform_driver(tdm_driver);
+
+MODULE_AUTHOR("Baoyou Xie <baoyou.xie@linaro.org>");
+MODULE_DESCRIPTION("ZTE TDM DAI driver");
+MODULE_LICENSE("GPL v2");