[PATCH v1 3/3] ASoC: zx-tdm: add zte's tdm controller driver

Shawn Guo shawnguo at kernel.org
Tue Feb 14 05:31:25 PST 2017


On Mon, Feb 13, 2017 at 04:17:33PM +0800, Baoyou Xie wrote:
> This patch adds tdm controller driver for zte's SoC family.
> 
> Signed-off-by: Baoyou Xie <baoyou.xie at linaro.org>

The patch looks rather pretty to me.  I have only a few small comments
below.

> ---
>  sound/soc/zte/Kconfig  |   8 +
>  sound/soc/zte/Makefile |   1 +
>  sound/soc/zte/zx-tdm.c | 466 +++++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 475 insertions(+)
>  create mode 100644 sound/soc/zte/zx-tdm.c
> 
> diff --git a/sound/soc/zte/Kconfig b/sound/soc/zte/Kconfig
> index 6d8a90d..75f67a5 100644
> --- a/sound/soc/zte/Kconfig
> +++ b/sound/soc/zte/Kconfig
> @@ -15,3 +15,11 @@ config ZX_I2S
>  	help
>  	  Say Y or M if you want to add support for codecs attached to the
>  	  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
> diff --git a/sound/soc/zte/Makefile b/sound/soc/zte/Makefile
> index 77768f5..1fc841a 100644
> --- a/sound/soc/zte/Makefile
> +++ b/sound/soc/zte/Makefile
> @@ -1,2 +1,3 @@
>  obj-$(CONFIG_ZX_SPDIF)	+= zx-spdif.o
>  obj-$(CONFIG_ZX_I2S)	+= zx-i2s.o
> +obj-$(CONFIG_ZX_TDM)	+= zx-tdm.o
> diff --git a/sound/soc/zte/zx-tdm.c b/sound/soc/zte/zx-tdm.c
> new file mode 100644
> index 0000000..72e5136
> --- /dev/null
> +++ b/sound/soc/zte/zx-tdm.c
> @@ -0,0 +1,466 @@
> +/*
> + * ZTE's TDM driver
> + *
> + * Copyright (C) 2017 ZTE Ltd
> + *
> + * Author: Baoyou Xie <baoyou.xie at 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

Please consistently use space instead of tab right after #define.

> +#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)

((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)

Ditto

> +#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				*regmap;

Variable name 'regmap' is usually used with 'struct regmap *' type.

> +	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->regmap + reg);
> +}
> +
> +static inline void zx_tdm_writel(struct zx_tdm_info *tdm, u16 reg, u32 val)
> +{
> +	writel_relaxed(val, tdm->regmap + 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);

Unnecessary parenthesis.

> +		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);

Unnecessary parenthesis.

> +	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_config;
> +	unsigned int dma_mask;
> +	struct resource *res;
> +	void __iomem *regmap;

Do not name it regmap.

> +	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, "tdm_wclk");
> +	if (IS_ERR(zx_tdm->dai_wclk)) {
> +		dev_err(&pdev->dev, "Fail to get tdm_wclk\n");
> +		return PTR_ERR(zx_tdm->dai_wclk);
> +	}
> +
> +	zx_tdm->dai_pclk = devm_clk_get(&pdev->dev, "tdm_pclk");
> +	if (IS_ERR(zx_tdm->dai_pclk)) {
> +		dev_err(&pdev->dev, "Fail to get tdm_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->regmap = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(zx_tdm->regmap)) {
> +		dev_err(&pdev->dev, "ioremap failed!\n");

As function devm_ioremap_resource() already has error messages for all
error conditions, we can save our own here.

> +		return PTR_ERR(zx_tdm->regmap);
> +	}
> +
> +	ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
> +				"zte,tdm-dma-sysctrl", 3, 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];
> +	dma_config = out_args.args[2];
> +	regmap = syscon_node_to_regmap(out_args.np);
> +	if (IS_ERR(regmap)) {
> +		of_node_put(out_args.np);
> +		return PTR_ERR(regmap);
> +	}
> +
> +	regmap_update_bits(regmap, dma_reg_offset, dma_mask, dma_config);
> +	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,zx-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,
> +	},
> +};
> +

Drop this newline.

Shawn

> +module_platform_driver(tdm_driver);
> +
> +MODULE_AUTHOR("Baoyou Xie <baoyou.xie at linaro.org>");
> +MODULE_DESCRIPTION("ZTE TDM DAI driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.7.4
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel



More information about the linux-arm-kernel mailing list