All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jose Abreu <Jose.Abreu@synopsys.com>
To: linux-snps-arc@lists.infradead.org, linux-kernel@vger.kernel.org,
	dri-devel@lists.freedesktop.org, alsa-devel@alsa-project.org,
	devicetree@vger.kernel.org
Cc: airlied@linux.ie, lgirdwood@gmail.com, broonie@kernel.org,
	perex@perex.cz, tiwai@suse.com,
	laurent.pinchart+renesas@ideasonboard.com,
	wsa+renesas@sang-engineering.com, lars@metafoo.de,
	ville.syrjala@linux.intel.com,
	nariman@opensource.wolfsonmicro.com, alexander.deucher@amd.com,
	Maruthi.Bayyavarapu@amd.com, buyitian@gmail.com, tixy@linaro.org,
	yitian.bu@tangramtek.com, Alexey.Brodkin@synopsys.com,
	robh+dt@kernel.org, pawel.moll@arm.com, mark.rutland@arm.com,
	ijc+devicetree@hellion.org.uk, galak@codeaurora.org,
	Vineet.Gupta1@synopsys.com, CARLOS.PALMINHA@synopsys.com,
	Jose Abreu <Jose.Abreu@synopsys.com>
Subject: [PATCH 2/3 v2] ASoC: dwc: Add I2S HDMI audio support
Date: Mon, 28 Mar 2016 15:36:10 +0100	[thread overview]
Message-ID: <538be366488bf0d7633d702f2d0bab16707b7a47.1459174494.git.joabreu@synopsys.com> (raw)
In-Reply-To: <cover.1459174494.git.joabreu@synopsys.com>
In-Reply-To: <cover.1459174494.git.joabreu@synopsys.com>

HDMI audio support was added to the AXS board using an
I2S cpu driver and a custom platform driver.

The platform driver supports two channels @ 16 bits with
rates 32k, 44.1k and 48k. ALSA Simple audio card is used to
glue the cpu, platform and codec driver (adv7511).

Signed-off-by: Jose Abreu <joabreu@synopsys.com>
---

No changes v1 -> v2.

 sound/soc/dwc/Kconfig          |   1 +
 sound/soc/dwc/designware_i2s.c | 385 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 373 insertions(+), 13 deletions(-)

diff --git a/sound/soc/dwc/Kconfig b/sound/soc/dwc/Kconfig
index d50e085..bc3fae7 100644
--- a/sound/soc/dwc/Kconfig
+++ b/sound/soc/dwc/Kconfig
@@ -2,6 +2,7 @@ config SND_DESIGNWARE_I2S
 	tristate "Synopsys I2S Device Driver"
 	depends on CLKDEV_LOOKUP
 	select SND_SOC_GENERIC_DMAENGINE_PCM
+	select SND_SIMPLE_CARD
 	help
 	 Say Y or M if you want to add support for I2S driver for
 	 Synopsys desigwnware I2S device. The device supports upto
diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c
index bff258d..0f2f588 100644
--- a/sound/soc/dwc/designware_i2s.c
+++ b/sound/soc/dwc/designware_i2s.c
@@ -84,11 +84,37 @@
 #define MAX_CHANNEL_NUM		8
 #define MIN_CHANNEL_NUM		2
 
+/* FPGA Version Info */
+#define FPGA_VER_INFO	0xE0011230
+#define FPGA_VER_27M	0x000FBED9
+
+/* PLL registers addresses */
+#define PLL_IDIV_ADDR	0xE00100A0
+#define PLL_FBDIV_ADDR	0xE00100A4
+#define PLL_ODIV0_ADDR	0xE00100A8
+#define PLL_ODIV1_ADDR	0xE00100AC
+
+/* PCM definitions */
+#define BUFFER_BYTES_MAX	384000
+#define PERIOD_BYTES_MIN	2048
+#define PERIODS_MIN		8
+
 union dw_i2s_snd_dma_data {
 	struct i2s_dma_data pd;
 	struct snd_dmaengine_dai_dma_data dt;
 };
 
+struct dw_pcm_binfo {
+	struct snd_pcm_substream *stream;
+	unsigned char *dma_base;
+	unsigned char *dma_pointer;
+	unsigned int period_size_frames;
+	unsigned int size;
+	snd_pcm_uframes_t period_pointer;
+	unsigned int total_periods;
+	unsigned int current_period;
+};
+
 struct dw_i2s_dev {
 	void __iomem *i2s_base;
 	struct clk *clk;
@@ -100,14 +126,103 @@ struct dw_i2s_dev {
 	struct device *dev;
 	u32 ccr;
 	u32 xfer_resolution;
+	u32 xfer_bytes;
+	u32 fifo_th;
 
 	/* data related to DMA transfers b/w i2s and DMAC */
 	union dw_i2s_snd_dma_data play_dma_data;
 	union dw_i2s_snd_dma_data capture_dma_data;
 	struct i2s_clk_config_data config;
 	int (*i2s_clk_cfg)(struct i2s_clk_config_data *config);
+	struct dw_pcm_binfo binfo;
+};
+
+struct dw_i2s_pll {
+	unsigned int rate;
+	unsigned int data_width;
+	unsigned int idiv;
+	unsigned int fbdiv;
+	unsigned int odiv0;
+	unsigned int odiv1;
+};
+
+static const struct dw_i2s_pll dw_i2s_pll_cfg_27m[] = {
+	/* 27Mhz */
+	{ 32000, 16, 0x104, 0x451, 0x10E38, 0x2000 },
+	{ 44100, 16, 0x104, 0x596, 0x10D35, 0x2000 },
+	{ 48000, 16, 0x208, 0xA28, 0x10B2C, 0x2000 },
+	{ 0, 0, 0, 0, 0, 0 },
 };
 
+static const struct dw_i2s_pll dw_i2s_pll_cfg_28m[] = {
+	/* 28.224Mhz */
+	{ 32000, 16, 0x82, 0x105, 0x107DF, 0x2000 },
+	{ 44100, 16, 0x28A, 0x1, 0x10001, 0x2000 },
+	{ 48000, 16, 0xA28, 0x187, 0x10042, 0x2000 },
+	{ 0, 0, 0, 0, 0, 0 },
+};
+
+static const struct snd_pcm_hardware dw_pcm_hw = {
+	.info       = SNDRV_PCM_INFO_INTERLEAVED |
+			SNDRV_PCM_INFO_MMAP |
+			SNDRV_PCM_INFO_MMAP_VALID |
+			SNDRV_PCM_INFO_BLOCK_TRANSFER,
+	.rates      = SNDRV_PCM_RATE_32000 |
+			SNDRV_PCM_RATE_44100 |
+			SNDRV_PCM_RATE_48000,
+	.rate_min   = 32000,
+	.rate_max   = 48000,
+	.formats    = SNDRV_PCM_FMTBIT_S16_LE,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = BUFFER_BYTES_MAX,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
+	.periods_min      = PERIODS_MIN,
+	.periods_max      = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+};
+
+static int dw_pcm_transfer(u32 *lsample, u32 *rsample, int bytes, int buf_size,
+		struct dw_pcm_binfo *bi)
+{
+	struct snd_pcm_runtime *rt = NULL;
+	int i;
+
+	if (!bi)
+		return -EINVAL;
+
+	rt = bi->stream->runtime;
+
+	for (i = 0; i < buf_size; i++) {
+		if (bi->stream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			memcpy(&lsample[i], bi->dma_pointer, bytes);
+			bi->dma_pointer += bytes;
+			memcpy(&rsample[i], bi->dma_pointer, bytes);
+			bi->dma_pointer += bytes;
+		} else {
+			memcpy(bi->dma_pointer, &lsample[i], bytes);
+			bi->dma_pointer += bytes;
+			memcpy(bi->dma_pointer, &rsample[i], bytes);
+			bi->dma_pointer += bytes;
+		}
+	}
+	bi->period_pointer += bytes_to_frames(rt, bytes * 2 * buf_size);
+
+	if (bi->period_pointer >=
+			(bi->period_size_frames * bi->current_period)) {
+		bi->current_period++;
+		if (bi->current_period > bi->total_periods) {
+			bi->dma_pointer = bi->dma_base;
+			bi->period_pointer = 0;
+			bi->current_period = 1;
+		}
+
+		snd_pcm_period_elapsed(bi->stream);
+	}
+
+	return 0;
+}
+
 static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val)
 {
 	writel(val, io_base + reg);
@@ -144,20 +259,94 @@ static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream)
 	}
 }
 
+static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
+{
+	struct dw_i2s_dev *dev = dev_id;
+	u32 isr[4], sleft[dev->fifo_th], sright[dev->fifo_th];
+	int i, j;
+
+	for (i = 0; i < 4; i++)
+		isr[i] = i2s_read_reg(dev->i2s_base, ISR(i));
+
+	for (i = 0; i < 4; i++) {
+		/* Copy only to/from first two channels.
+		 *  TODO: Remaining channels
+		 */
+		if ((isr[i] & 0x10) && (i == 0) && (dev->binfo.stream->stream ==
+					SNDRV_PCM_STREAM_PLAYBACK)) {
+			/* TXFE - TX FIFO is empty */
+			dw_pcm_transfer(sleft, sright, dev->xfer_bytes,
+					dev->fifo_th, &dev->binfo);
+
+			for (j = 0; j < dev->fifo_th; j++) {
+				i2s_write_reg(dev->i2s_base, LRBR_LTHR(i),
+						sleft[j]);
+				i2s_write_reg(dev->i2s_base, RRBR_RTHR(i),
+						sright[j]);
+			}
+		} else if ((isr[i] & 0x1) && (i == 0) &&
+					(dev->binfo.stream->stream ==
+					SNDRV_PCM_STREAM_CAPTURE)) {
+			/* RSFE - RX FIFO is full */
+			for (j = 0; j < dev->fifo_th; j++) {
+				sleft[j] = i2s_read_reg(dev->i2s_base,
+						LRBR_LTHR(i));
+				sright[j] = i2s_read_reg(dev->i2s_base,
+						RRBR_RTHR(i));
+			}
+
+			dw_pcm_transfer(sleft, sright, dev->xfer_bytes,
+					dev->fifo_th, &dev->binfo);
+		}
+	}
+
+	i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK);
+	i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE);
+
+	return IRQ_HANDLED;
+}
+
+static int i2s_pll_cfg(struct i2s_clk_config_data *config)
+{
+	const struct dw_i2s_pll *pll_cfg;
+	u32 rate = config->sample_rate;
+	u32 data_width = config->data_width;
+	int i;
+
+	if (readl((void *)FPGA_VER_INFO) <= FPGA_VER_27M)
+		pll_cfg = dw_i2s_pll_cfg_27m;
+	else
+		pll_cfg = dw_i2s_pll_cfg_28m;
+
+	for (i = 0; pll_cfg[i].rate != 0; i++) {
+		if ((pll_cfg[i].rate == rate) &&
+				(pll_cfg[i].data_width == data_width)) {
+			writel(pll_cfg[i].idiv, (void *)PLL_IDIV_ADDR);
+			writel(pll_cfg[i].fbdiv, (void *)PLL_FBDIV_ADDR);
+			writel(pll_cfg[i].odiv0, (void *)PLL_ODIV0_ADDR);
+			writel(pll_cfg[i].odiv1, (void *)PLL_ODIV1_ADDR);
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
 static void i2s_start(struct dw_i2s_dev *dev,
 		      struct snd_pcm_substream *substream)
 {
+	struct i2s_clk_config_data *config = &dev->config;
 	u32 i, irq;
 	i2s_write_reg(dev->i2s_base, IER, 1);
 
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
-		for (i = 0; i < 4; i++) {
+		for (i = 0; i < (config->chan_nr / 2); i++) {
 			irq = i2s_read_reg(dev->i2s_base, IMR(i));
 			i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30);
 		}
 		i2s_write_reg(dev->i2s_base, ITER, 1);
 	} else {
-		for (i = 0; i < 4; i++) {
+		for (i = 0; i < (config->chan_nr / 2); i++) {
 			irq = i2s_read_reg(dev->i2s_base, IMR(i));
 			i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03);
 		}
@@ -195,6 +384,139 @@ static void i2s_stop(struct dw_i2s_dev *dev,
 	}
 }
 
+static int dw_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+	snd_soc_set_runtime_hwparams(substream, &dw_pcm_hw);
+	snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
+
+	rt->hw.rate_min = 32000;
+	rt->hw.rate_max = 48000;
+
+	dev->binfo.stream = substream;
+	rt->private_data = &dev->binfo;
+	return 0;
+}
+
+static int dw_pcm_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static int dw_pcm_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct dw_pcm_binfo *bi = rt->private_data;
+	int ret;
+
+	ret = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+			params_buffer_bytes(hw_params));
+	if (ret < 0)
+		return ret;
+
+	memset(rt->dma_area, 0, params_buffer_bytes(hw_params));
+	bi->dma_base = rt->dma_area;
+	bi->dma_pointer = bi->dma_base;
+
+	return 0;
+}
+
+static int dw_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	int ret;
+
+	ret = snd_pcm_lib_free_vmalloc_buffer(substream);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int dw_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct dw_pcm_binfo *bi = rt->private_data;
+	u32 buffer_size_frames = 0;
+
+	bi->period_size_frames = bytes_to_frames(rt,
+			snd_pcm_lib_period_bytes(substream));
+	bi->size = snd_pcm_lib_buffer_bytes(substream);
+	buffer_size_frames = bytes_to_frames(rt, bi->size);
+	bi->total_periods = buffer_size_frames / bi->period_size_frames;
+	bi->current_period = 1;
+
+	if ((buffer_size_frames % bi->period_size_frames) != 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int dw_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t dw_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct dw_pcm_binfo *bi = rt->private_data;
+
+	return bi->period_pointer;
+}
+
+static struct snd_pcm_ops dw_pcm_ops = {
+	.open      = dw_pcm_open,
+	.close     = dw_pcm_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.hw_params = dw_pcm_hw_params,
+	.hw_free   = dw_pcm_hw_free,
+	.prepare   = dw_pcm_prepare,
+	.trigger   = dw_pcm_trigger,
+	.pointer   = dw_pcm_pointer,
+	.page      = snd_pcm_lib_get_vmalloc_page,
+	.mmap      = snd_pcm_lib_mmap_vmalloc,
+};
+
+static int dw_pcm_new(struct snd_soc_pcm_runtime *runtime)
+{
+	struct snd_pcm *pcm = runtime->pcm;
+	int ret;
+
+	ret =  snd_pcm_lib_preallocate_pages_for_all(pcm,
+			SNDRV_DMA_TYPE_DEV,
+			snd_dma_continuous_data(GFP_KERNEL),
+			dw_pcm_hw.buffer_bytes_max,
+			dw_pcm_hw.buffer_bytes_max);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void dw_pcm_free(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static struct snd_soc_platform_driver dw_pcm_soc_platform = {
+	.pcm_new  = dw_pcm_new,
+	.pcm_free = dw_pcm_free,
+	.ops = &dw_pcm_ops,
+};
+
 static int dw_i2s_startup(struct snd_pcm_substream *substream,
 		struct snd_soc_dai *cpu_dai)
 {
@@ -231,14 +553,16 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
 		if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
 			i2s_write_reg(dev->i2s_base, TCR(ch_reg),
 				      dev->xfer_resolution);
-			i2s_write_reg(dev->i2s_base, TFCR(ch_reg), 0x02);
+			i2s_write_reg(dev->i2s_base, TFCR(ch_reg),
+				      dev->fifo_th - 1);
 			irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg));
 			i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x30);
 			i2s_write_reg(dev->i2s_base, TER(ch_reg), 1);
 		} else {
 			i2s_write_reg(dev->i2s_base, RCR(ch_reg),
 				      dev->xfer_resolution);
-			i2s_write_reg(dev->i2s_base, RFCR(ch_reg), 0x07);
+			i2s_write_reg(dev->i2s_base, RFCR(ch_reg),
+				      dev->fifo_th - 1);
 			irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg));
 			i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x03);
 			i2s_write_reg(dev->i2s_base, RER(ch_reg), 1);
@@ -259,22 +583,25 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
 		config->data_width = 16;
 		dev->ccr = 0x00;
 		dev->xfer_resolution = 0x02;
+		dev->xfer_bytes = 0x02;
 		break;
 
 	case SNDRV_PCM_FORMAT_S24_LE:
 		config->data_width = 24;
 		dev->ccr = 0x08;
 		dev->xfer_resolution = 0x04;
+		dev->xfer_bytes = 0x03;
 		break;
 
 	case SNDRV_PCM_FORMAT_S32_LE:
 		config->data_width = 32;
 		dev->ccr = 0x10;
 		dev->xfer_resolution = 0x05;
+		dev->xfer_bytes = 0x04;
 		break;
 
 	default:
-		dev_err(dev->dev, "designware-i2s: unsuppted PCM fmt");
+		dev_err(dev->dev, "designware-i2s: unsupported PCM fmt");
 		return -EINVAL;
 	}
 
@@ -316,6 +643,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
 			}
 		}
 	}
+
 	return 0;
 }
 
@@ -498,6 +826,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev,
 	 */
 	u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1);
 	u32 comp2 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp2);
+	u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
 	u32 idx;
 
 	if (dev->capability & DWC_I2S_RECORD &&
@@ -536,6 +865,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev,
 		dev->capability |= DW_I2S_SLAVE;
 	}
 
+	dev->fifo_th = fifo_depth / 2;
 	return 0;
 }
 
@@ -620,7 +950,7 @@ static int dw_i2s_probe(struct platform_device *pdev)
 	const struct i2s_platform_data *pdata = pdev->dev.platform_data;
 	struct dw_i2s_dev *dev;
 	struct resource *res;
-	int ret;
+	int ret, irq_number;
 	struct snd_soc_dai_driver *dw_i2s_dai;
 	const char *clk_id;
 
@@ -643,6 +973,19 @@ static int dw_i2s_probe(struct platform_device *pdev)
 	if (IS_ERR(dev->i2s_base))
 		return PTR_ERR(dev->i2s_base);
 
+	irq_number = platform_get_irq(pdev, 0);
+	if (irq_number <= 0) {
+		dev_err(&pdev->dev, "get irq fail\n");
+		return -EINVAL;
+	}
+
+	ret = devm_request_irq(&pdev->dev, irq_number, i2s_irq_handler,
+			IRQF_SHARED, "dw_i2s_irq_handler", dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "request irq fail\n");
+		return ret;
+	}
+
 	dev->dev = &pdev->dev;
 
 	dev->i2s_reg_comp1 = I2S_COMP_PARAM_1;
@@ -671,14 +1014,22 @@ static int dw_i2s_probe(struct platform_device *pdev)
 				return -ENODEV;
 			}
 		}
-		dev->clk = devm_clk_get(&pdev->dev, clk_id);
 
-		if (IS_ERR(dev->clk))
-			return PTR_ERR(dev->clk);
+		if (dev->i2s_clk_cfg ||
+				of_get_property(pdev->dev.of_node, "clocks", NULL)) {
+			dev->clk = devm_clk_get(&pdev->dev, clk_id);
+
+			if (IS_ERR(dev->clk))
+				return PTR_ERR(dev->clk);
 
-		ret = clk_prepare_enable(dev->clk);
-		if (ret < 0)
-			return ret;
+			ret = clk_prepare_enable(dev->clk);
+			if (ret < 0)
+				return ret;
+		} else {
+			/* Use internal PLL config */
+			dev->i2s_clk_cfg = i2s_pll_cfg;
+			dev->clk = NULL;
+		}
 	}
 
 	dev_set_drvdata(&pdev->dev, dev);
@@ -690,7 +1041,14 @@ static int dw_i2s_probe(struct platform_device *pdev)
 	}
 
 	if (!pdata) {
-		ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+		if (of_get_property(pdev->dev.of_node, "dmas", NULL)) {
+			/* Using DMA */
+			ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+		} else {
+			ret = snd_soc_register_platform(&pdev->dev,
+					&dw_pcm_soc_platform);
+		}
+
 		if (ret) {
 			dev_err(&pdev->dev,
 				"Could not register PCM: %d\n", ret);
@@ -714,6 +1072,7 @@ static int dw_i2s_remove(struct platform_device *pdev)
 		clk_disable_unprepare(dev->clk);
 
 	pm_runtime_disable(&pdev->dev);
+	snd_soc_unregister_platform(&pdev->dev);
 	return 0;
 }
 
-- 
1.9.1

WARNING: multiple messages have this Message-ID (diff)
From: Jose Abreu <Jose.Abreu-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
To: linux-snps-arc-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW@public.gmane.org,
	alsa-devel-K7yf7f+aM1XWsZ/bQMPhNw@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Cc: airlied-cv59FeDIM0c@public.gmane.org,
	lgirdwood-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org,
	broonie-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
	perex-/Fr2/VpizcU@public.gmane.org,
	tiwai-IBi9RG/b67k@public.gmane.org,
	laurent.pinchart+renesas-ryLnwIuWjnjg/C1BVhZhaw@public.gmane.org,
	wsa+renesas-jBu1N2QxHDJrcw3mvpCnnVaTQe2KTcn/@public.gmane.org,
	lars-Qo5EllUWu/uELgA04lAiVw@public.gmane.org,
	ville.syrjala-VuQAYsv1563Yd54FQh9/CA@public.gmane.org,
	nariman-yzvPICuk2AATkU/dhu1WVueM+bqZidxxQQ4Iyu8u01E@public.gmane.org,
	alexander.deucher-5C7GfCeVMHo@public.gmane.org,
	Maruthi.Bayyavarapu-5C7GfCeVMHo@public.gmane.org,
	buyitian-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org,
	tixy-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
	yitian.bu-sf4bEIuTEZ8/jFmSBShxMA@public.gmane.org,
	Alexey.Brodkin-HKixBCOQz3hWk0Htik3J/w@public.gmane.org,
	robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
	pawel.moll-5wv7dgnIgG8@public.gmane.org,
	mark.rutland-5wv7dgnIgG8@public.gmane.org,
	ijc+devicetree-KcIKpvwj1kUDXYZnReoRVg@public.gmane.org,
	galak-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org,
	Vineet.Gupta1-HKixBCOQz3hWk0Htik3J/w@public.gmane.org,
	CARLOS.PALMINHA-HKixBCOQz3hWk0Htik3J/w@public.gmane.org,
	Jose Abreu <Jose.Abreu-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
Subject: [PATCH 2/3 v2] ASoC: dwc: Add I2S HDMI audio support
Date: Mon, 28 Mar 2016 15:36:10 +0100	[thread overview]
Message-ID: <538be366488bf0d7633d702f2d0bab16707b7a47.1459174494.git.joabreu@synopsys.com> (raw)
In-Reply-To: <cover.1459174494.git.joabreu-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
In-Reply-To: <cover.1459174494.git.joabreu-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>

HDMI audio support was added to the AXS board using an
I2S cpu driver and a custom platform driver.

The platform driver supports two channels @ 16 bits with
rates 32k, 44.1k and 48k. ALSA Simple audio card is used to
glue the cpu, platform and codec driver (adv7511).

Signed-off-by: Jose Abreu <joabreu-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>
---

No changes v1 -> v2.

 sound/soc/dwc/Kconfig          |   1 +
 sound/soc/dwc/designware_i2s.c | 385 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 373 insertions(+), 13 deletions(-)

diff --git a/sound/soc/dwc/Kconfig b/sound/soc/dwc/Kconfig
index d50e085..bc3fae7 100644
--- a/sound/soc/dwc/Kconfig
+++ b/sound/soc/dwc/Kconfig
@@ -2,6 +2,7 @@ config SND_DESIGNWARE_I2S
 	tristate "Synopsys I2S Device Driver"
 	depends on CLKDEV_LOOKUP
 	select SND_SOC_GENERIC_DMAENGINE_PCM
+	select SND_SIMPLE_CARD
 	help
 	 Say Y or M if you want to add support for I2S driver for
 	 Synopsys desigwnware I2S device. The device supports upto
diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c
index bff258d..0f2f588 100644
--- a/sound/soc/dwc/designware_i2s.c
+++ b/sound/soc/dwc/designware_i2s.c
@@ -84,11 +84,37 @@
 #define MAX_CHANNEL_NUM		8
 #define MIN_CHANNEL_NUM		2
 
+/* FPGA Version Info */
+#define FPGA_VER_INFO	0xE0011230
+#define FPGA_VER_27M	0x000FBED9
+
+/* PLL registers addresses */
+#define PLL_IDIV_ADDR	0xE00100A0
+#define PLL_FBDIV_ADDR	0xE00100A4
+#define PLL_ODIV0_ADDR	0xE00100A8
+#define PLL_ODIV1_ADDR	0xE00100AC
+
+/* PCM definitions */
+#define BUFFER_BYTES_MAX	384000
+#define PERIOD_BYTES_MIN	2048
+#define PERIODS_MIN		8
+
 union dw_i2s_snd_dma_data {
 	struct i2s_dma_data pd;
 	struct snd_dmaengine_dai_dma_data dt;
 };
 
+struct dw_pcm_binfo {
+	struct snd_pcm_substream *stream;
+	unsigned char *dma_base;
+	unsigned char *dma_pointer;
+	unsigned int period_size_frames;
+	unsigned int size;
+	snd_pcm_uframes_t period_pointer;
+	unsigned int total_periods;
+	unsigned int current_period;
+};
+
 struct dw_i2s_dev {
 	void __iomem *i2s_base;
 	struct clk *clk;
@@ -100,14 +126,103 @@ struct dw_i2s_dev {
 	struct device *dev;
 	u32 ccr;
 	u32 xfer_resolution;
+	u32 xfer_bytes;
+	u32 fifo_th;
 
 	/* data related to DMA transfers b/w i2s and DMAC */
 	union dw_i2s_snd_dma_data play_dma_data;
 	union dw_i2s_snd_dma_data capture_dma_data;
 	struct i2s_clk_config_data config;
 	int (*i2s_clk_cfg)(struct i2s_clk_config_data *config);
+	struct dw_pcm_binfo binfo;
+};
+
+struct dw_i2s_pll {
+	unsigned int rate;
+	unsigned int data_width;
+	unsigned int idiv;
+	unsigned int fbdiv;
+	unsigned int odiv0;
+	unsigned int odiv1;
+};
+
+static const struct dw_i2s_pll dw_i2s_pll_cfg_27m[] = {
+	/* 27Mhz */
+	{ 32000, 16, 0x104, 0x451, 0x10E38, 0x2000 },
+	{ 44100, 16, 0x104, 0x596, 0x10D35, 0x2000 },
+	{ 48000, 16, 0x208, 0xA28, 0x10B2C, 0x2000 },
+	{ 0, 0, 0, 0, 0, 0 },
 };
 
+static const struct dw_i2s_pll dw_i2s_pll_cfg_28m[] = {
+	/* 28.224Mhz */
+	{ 32000, 16, 0x82, 0x105, 0x107DF, 0x2000 },
+	{ 44100, 16, 0x28A, 0x1, 0x10001, 0x2000 },
+	{ 48000, 16, 0xA28, 0x187, 0x10042, 0x2000 },
+	{ 0, 0, 0, 0, 0, 0 },
+};
+
+static const struct snd_pcm_hardware dw_pcm_hw = {
+	.info       = SNDRV_PCM_INFO_INTERLEAVED |
+			SNDRV_PCM_INFO_MMAP |
+			SNDRV_PCM_INFO_MMAP_VALID |
+			SNDRV_PCM_INFO_BLOCK_TRANSFER,
+	.rates      = SNDRV_PCM_RATE_32000 |
+			SNDRV_PCM_RATE_44100 |
+			SNDRV_PCM_RATE_48000,
+	.rate_min   = 32000,
+	.rate_max   = 48000,
+	.formats    = SNDRV_PCM_FMTBIT_S16_LE,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = BUFFER_BYTES_MAX,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
+	.periods_min      = PERIODS_MIN,
+	.periods_max      = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+};
+
+static int dw_pcm_transfer(u32 *lsample, u32 *rsample, int bytes, int buf_size,
+		struct dw_pcm_binfo *bi)
+{
+	struct snd_pcm_runtime *rt = NULL;
+	int i;
+
+	if (!bi)
+		return -EINVAL;
+
+	rt = bi->stream->runtime;
+
+	for (i = 0; i < buf_size; i++) {
+		if (bi->stream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			memcpy(&lsample[i], bi->dma_pointer, bytes);
+			bi->dma_pointer += bytes;
+			memcpy(&rsample[i], bi->dma_pointer, bytes);
+			bi->dma_pointer += bytes;
+		} else {
+			memcpy(bi->dma_pointer, &lsample[i], bytes);
+			bi->dma_pointer += bytes;
+			memcpy(bi->dma_pointer, &rsample[i], bytes);
+			bi->dma_pointer += bytes;
+		}
+	}
+	bi->period_pointer += bytes_to_frames(rt, bytes * 2 * buf_size);
+
+	if (bi->period_pointer >=
+			(bi->period_size_frames * bi->current_period)) {
+		bi->current_period++;
+		if (bi->current_period > bi->total_periods) {
+			bi->dma_pointer = bi->dma_base;
+			bi->period_pointer = 0;
+			bi->current_period = 1;
+		}
+
+		snd_pcm_period_elapsed(bi->stream);
+	}
+
+	return 0;
+}
+
 static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val)
 {
 	writel(val, io_base + reg);
@@ -144,20 +259,94 @@ static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream)
 	}
 }
 
+static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
+{
+	struct dw_i2s_dev *dev = dev_id;
+	u32 isr[4], sleft[dev->fifo_th], sright[dev->fifo_th];
+	int i, j;
+
+	for (i = 0; i < 4; i++)
+		isr[i] = i2s_read_reg(dev->i2s_base, ISR(i));
+
+	for (i = 0; i < 4; i++) {
+		/* Copy only to/from first two channels.
+		 *  TODO: Remaining channels
+		 */
+		if ((isr[i] & 0x10) && (i == 0) && (dev->binfo.stream->stream ==
+					SNDRV_PCM_STREAM_PLAYBACK)) {
+			/* TXFE - TX FIFO is empty */
+			dw_pcm_transfer(sleft, sright, dev->xfer_bytes,
+					dev->fifo_th, &dev->binfo);
+
+			for (j = 0; j < dev->fifo_th; j++) {
+				i2s_write_reg(dev->i2s_base, LRBR_LTHR(i),
+						sleft[j]);
+				i2s_write_reg(dev->i2s_base, RRBR_RTHR(i),
+						sright[j]);
+			}
+		} else if ((isr[i] & 0x1) && (i == 0) &&
+					(dev->binfo.stream->stream ==
+					SNDRV_PCM_STREAM_CAPTURE)) {
+			/* RSFE - RX FIFO is full */
+			for (j = 0; j < dev->fifo_th; j++) {
+				sleft[j] = i2s_read_reg(dev->i2s_base,
+						LRBR_LTHR(i));
+				sright[j] = i2s_read_reg(dev->i2s_base,
+						RRBR_RTHR(i));
+			}
+
+			dw_pcm_transfer(sleft, sright, dev->xfer_bytes,
+					dev->fifo_th, &dev->binfo);
+		}
+	}
+
+	i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK);
+	i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE);
+
+	return IRQ_HANDLED;
+}
+
+static int i2s_pll_cfg(struct i2s_clk_config_data *config)
+{
+	const struct dw_i2s_pll *pll_cfg;
+	u32 rate = config->sample_rate;
+	u32 data_width = config->data_width;
+	int i;
+
+	if (readl((void *)FPGA_VER_INFO) <= FPGA_VER_27M)
+		pll_cfg = dw_i2s_pll_cfg_27m;
+	else
+		pll_cfg = dw_i2s_pll_cfg_28m;
+
+	for (i = 0; pll_cfg[i].rate != 0; i++) {
+		if ((pll_cfg[i].rate == rate) &&
+				(pll_cfg[i].data_width == data_width)) {
+			writel(pll_cfg[i].idiv, (void *)PLL_IDIV_ADDR);
+			writel(pll_cfg[i].fbdiv, (void *)PLL_FBDIV_ADDR);
+			writel(pll_cfg[i].odiv0, (void *)PLL_ODIV0_ADDR);
+			writel(pll_cfg[i].odiv1, (void *)PLL_ODIV1_ADDR);
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
 static void i2s_start(struct dw_i2s_dev *dev,
 		      struct snd_pcm_substream *substream)
 {
+	struct i2s_clk_config_data *config = &dev->config;
 	u32 i, irq;
 	i2s_write_reg(dev->i2s_base, IER, 1);
 
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
-		for (i = 0; i < 4; i++) {
+		for (i = 0; i < (config->chan_nr / 2); i++) {
 			irq = i2s_read_reg(dev->i2s_base, IMR(i));
 			i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30);
 		}
 		i2s_write_reg(dev->i2s_base, ITER, 1);
 	} else {
-		for (i = 0; i < 4; i++) {
+		for (i = 0; i < (config->chan_nr / 2); i++) {
 			irq = i2s_read_reg(dev->i2s_base, IMR(i));
 			i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03);
 		}
@@ -195,6 +384,139 @@ static void i2s_stop(struct dw_i2s_dev *dev,
 	}
 }
 
+static int dw_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+	snd_soc_set_runtime_hwparams(substream, &dw_pcm_hw);
+	snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
+
+	rt->hw.rate_min = 32000;
+	rt->hw.rate_max = 48000;
+
+	dev->binfo.stream = substream;
+	rt->private_data = &dev->binfo;
+	return 0;
+}
+
+static int dw_pcm_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static int dw_pcm_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct dw_pcm_binfo *bi = rt->private_data;
+	int ret;
+
+	ret = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+			params_buffer_bytes(hw_params));
+	if (ret < 0)
+		return ret;
+
+	memset(rt->dma_area, 0, params_buffer_bytes(hw_params));
+	bi->dma_base = rt->dma_area;
+	bi->dma_pointer = bi->dma_base;
+
+	return 0;
+}
+
+static int dw_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	int ret;
+
+	ret = snd_pcm_lib_free_vmalloc_buffer(substream);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int dw_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct dw_pcm_binfo *bi = rt->private_data;
+	u32 buffer_size_frames = 0;
+
+	bi->period_size_frames = bytes_to_frames(rt,
+			snd_pcm_lib_period_bytes(substream));
+	bi->size = snd_pcm_lib_buffer_bytes(substream);
+	buffer_size_frames = bytes_to_frames(rt, bi->size);
+	bi->total_periods = buffer_size_frames / bi->period_size_frames;
+	bi->current_period = 1;
+
+	if ((buffer_size_frames % bi->period_size_frames) != 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int dw_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t dw_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct dw_pcm_binfo *bi = rt->private_data;
+
+	return bi->period_pointer;
+}
+
+static struct snd_pcm_ops dw_pcm_ops = {
+	.open      = dw_pcm_open,
+	.close     = dw_pcm_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.hw_params = dw_pcm_hw_params,
+	.hw_free   = dw_pcm_hw_free,
+	.prepare   = dw_pcm_prepare,
+	.trigger   = dw_pcm_trigger,
+	.pointer   = dw_pcm_pointer,
+	.page      = snd_pcm_lib_get_vmalloc_page,
+	.mmap      = snd_pcm_lib_mmap_vmalloc,
+};
+
+static int dw_pcm_new(struct snd_soc_pcm_runtime *runtime)
+{
+	struct snd_pcm *pcm = runtime->pcm;
+	int ret;
+
+	ret =  snd_pcm_lib_preallocate_pages_for_all(pcm,
+			SNDRV_DMA_TYPE_DEV,
+			snd_dma_continuous_data(GFP_KERNEL),
+			dw_pcm_hw.buffer_bytes_max,
+			dw_pcm_hw.buffer_bytes_max);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void dw_pcm_free(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static struct snd_soc_platform_driver dw_pcm_soc_platform = {
+	.pcm_new  = dw_pcm_new,
+	.pcm_free = dw_pcm_free,
+	.ops = &dw_pcm_ops,
+};
+
 static int dw_i2s_startup(struct snd_pcm_substream *substream,
 		struct snd_soc_dai *cpu_dai)
 {
@@ -231,14 +553,16 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
 		if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
 			i2s_write_reg(dev->i2s_base, TCR(ch_reg),
 				      dev->xfer_resolution);
-			i2s_write_reg(dev->i2s_base, TFCR(ch_reg), 0x02);
+			i2s_write_reg(dev->i2s_base, TFCR(ch_reg),
+				      dev->fifo_th - 1);
 			irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg));
 			i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x30);
 			i2s_write_reg(dev->i2s_base, TER(ch_reg), 1);
 		} else {
 			i2s_write_reg(dev->i2s_base, RCR(ch_reg),
 				      dev->xfer_resolution);
-			i2s_write_reg(dev->i2s_base, RFCR(ch_reg), 0x07);
+			i2s_write_reg(dev->i2s_base, RFCR(ch_reg),
+				      dev->fifo_th - 1);
 			irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg));
 			i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x03);
 			i2s_write_reg(dev->i2s_base, RER(ch_reg), 1);
@@ -259,22 +583,25 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
 		config->data_width = 16;
 		dev->ccr = 0x00;
 		dev->xfer_resolution = 0x02;
+		dev->xfer_bytes = 0x02;
 		break;
 
 	case SNDRV_PCM_FORMAT_S24_LE:
 		config->data_width = 24;
 		dev->ccr = 0x08;
 		dev->xfer_resolution = 0x04;
+		dev->xfer_bytes = 0x03;
 		break;
 
 	case SNDRV_PCM_FORMAT_S32_LE:
 		config->data_width = 32;
 		dev->ccr = 0x10;
 		dev->xfer_resolution = 0x05;
+		dev->xfer_bytes = 0x04;
 		break;
 
 	default:
-		dev_err(dev->dev, "designware-i2s: unsuppted PCM fmt");
+		dev_err(dev->dev, "designware-i2s: unsupported PCM fmt");
 		return -EINVAL;
 	}
 
@@ -316,6 +643,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
 			}
 		}
 	}
+
 	return 0;
 }
 
@@ -498,6 +826,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev,
 	 */
 	u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1);
 	u32 comp2 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp2);
+	u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
 	u32 idx;
 
 	if (dev->capability & DWC_I2S_RECORD &&
@@ -536,6 +865,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev,
 		dev->capability |= DW_I2S_SLAVE;
 	}
 
+	dev->fifo_th = fifo_depth / 2;
 	return 0;
 }
 
@@ -620,7 +950,7 @@ static int dw_i2s_probe(struct platform_device *pdev)
 	const struct i2s_platform_data *pdata = pdev->dev.platform_data;
 	struct dw_i2s_dev *dev;
 	struct resource *res;
-	int ret;
+	int ret, irq_number;
 	struct snd_soc_dai_driver *dw_i2s_dai;
 	const char *clk_id;
 
@@ -643,6 +973,19 @@ static int dw_i2s_probe(struct platform_device *pdev)
 	if (IS_ERR(dev->i2s_base))
 		return PTR_ERR(dev->i2s_base);
 
+	irq_number = platform_get_irq(pdev, 0);
+	if (irq_number <= 0) {
+		dev_err(&pdev->dev, "get irq fail\n");
+		return -EINVAL;
+	}
+
+	ret = devm_request_irq(&pdev->dev, irq_number, i2s_irq_handler,
+			IRQF_SHARED, "dw_i2s_irq_handler", dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "request irq fail\n");
+		return ret;
+	}
+
 	dev->dev = &pdev->dev;
 
 	dev->i2s_reg_comp1 = I2S_COMP_PARAM_1;
@@ -671,14 +1014,22 @@ static int dw_i2s_probe(struct platform_device *pdev)
 				return -ENODEV;
 			}
 		}
-		dev->clk = devm_clk_get(&pdev->dev, clk_id);
 
-		if (IS_ERR(dev->clk))
-			return PTR_ERR(dev->clk);
+		if (dev->i2s_clk_cfg ||
+				of_get_property(pdev->dev.of_node, "clocks", NULL)) {
+			dev->clk = devm_clk_get(&pdev->dev, clk_id);
+
+			if (IS_ERR(dev->clk))
+				return PTR_ERR(dev->clk);
 
-		ret = clk_prepare_enable(dev->clk);
-		if (ret < 0)
-			return ret;
+			ret = clk_prepare_enable(dev->clk);
+			if (ret < 0)
+				return ret;
+		} else {
+			/* Use internal PLL config */
+			dev->i2s_clk_cfg = i2s_pll_cfg;
+			dev->clk = NULL;
+		}
 	}
 
 	dev_set_drvdata(&pdev->dev, dev);
@@ -690,7 +1041,14 @@ static int dw_i2s_probe(struct platform_device *pdev)
 	}
 
 	if (!pdata) {
-		ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+		if (of_get_property(pdev->dev.of_node, "dmas", NULL)) {
+			/* Using DMA */
+			ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+		} else {
+			ret = snd_soc_register_platform(&pdev->dev,
+					&dw_pcm_soc_platform);
+		}
+
 		if (ret) {
 			dev_err(&pdev->dev,
 				"Could not register PCM: %d\n", ret);
@@ -714,6 +1072,7 @@ static int dw_i2s_remove(struct platform_device *pdev)
 		clk_disable_unprepare(dev->clk);
 
 	pm_runtime_disable(&pdev->dev);
+	snd_soc_unregister_platform(&pdev->dev);
 	return 0;
 }
 
-- 
1.9.1


--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

WARNING: multiple messages have this Message-ID (diff)
From: Jose.Abreu@synopsys.com (Jose Abreu)
To: linux-snps-arc@lists.infradead.org
Subject: [PATCH 2/3 v2] ASoC: dwc: Add I2S HDMI audio support
Date: Mon, 28 Mar 2016 15:36:10 +0100	[thread overview]
Message-ID: <538be366488bf0d7633d702f2d0bab16707b7a47.1459174494.git.joabreu@synopsys.com> (raw)
In-Reply-To: <cover.1459174494.git.joabreu@synopsys.com>

HDMI audio support was added to the AXS board using an
I2S cpu driver and a custom platform driver.

The platform driver supports two channels @ 16 bits with
rates 32k, 44.1k and 48k. ALSA Simple audio card is used to
glue the cpu, platform and codec driver (adv7511).

Signed-off-by: Jose Abreu <joabreu at synopsys.com>
---

No changes v1 -> v2.

 sound/soc/dwc/Kconfig          |   1 +
 sound/soc/dwc/designware_i2s.c | 385 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 373 insertions(+), 13 deletions(-)

diff --git a/sound/soc/dwc/Kconfig b/sound/soc/dwc/Kconfig
index d50e085..bc3fae7 100644
--- a/sound/soc/dwc/Kconfig
+++ b/sound/soc/dwc/Kconfig
@@ -2,6 +2,7 @@ config SND_DESIGNWARE_I2S
 	tristate "Synopsys I2S Device Driver"
 	depends on CLKDEV_LOOKUP
 	select SND_SOC_GENERIC_DMAENGINE_PCM
+	select SND_SIMPLE_CARD
 	help
 	 Say Y or M if you want to add support for I2S driver for
 	 Synopsys desigwnware I2S device. The device supports upto
diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c
index bff258d..0f2f588 100644
--- a/sound/soc/dwc/designware_i2s.c
+++ b/sound/soc/dwc/designware_i2s.c
@@ -84,11 +84,37 @@
 #define MAX_CHANNEL_NUM		8
 #define MIN_CHANNEL_NUM		2
 
+/* FPGA Version Info */
+#define FPGA_VER_INFO	0xE0011230
+#define FPGA_VER_27M	0x000FBED9
+
+/* PLL registers addresses */
+#define PLL_IDIV_ADDR	0xE00100A0
+#define PLL_FBDIV_ADDR	0xE00100A4
+#define PLL_ODIV0_ADDR	0xE00100A8
+#define PLL_ODIV1_ADDR	0xE00100AC
+
+/* PCM definitions */
+#define BUFFER_BYTES_MAX	384000
+#define PERIOD_BYTES_MIN	2048
+#define PERIODS_MIN		8
+
 union dw_i2s_snd_dma_data {
 	struct i2s_dma_data pd;
 	struct snd_dmaengine_dai_dma_data dt;
 };
 
+struct dw_pcm_binfo {
+	struct snd_pcm_substream *stream;
+	unsigned char *dma_base;
+	unsigned char *dma_pointer;
+	unsigned int period_size_frames;
+	unsigned int size;
+	snd_pcm_uframes_t period_pointer;
+	unsigned int total_periods;
+	unsigned int current_period;
+};
+
 struct dw_i2s_dev {
 	void __iomem *i2s_base;
 	struct clk *clk;
@@ -100,14 +126,103 @@ struct dw_i2s_dev {
 	struct device *dev;
 	u32 ccr;
 	u32 xfer_resolution;
+	u32 xfer_bytes;
+	u32 fifo_th;
 
 	/* data related to DMA transfers b/w i2s and DMAC */
 	union dw_i2s_snd_dma_data play_dma_data;
 	union dw_i2s_snd_dma_data capture_dma_data;
 	struct i2s_clk_config_data config;
 	int (*i2s_clk_cfg)(struct i2s_clk_config_data *config);
+	struct dw_pcm_binfo binfo;
+};
+
+struct dw_i2s_pll {
+	unsigned int rate;
+	unsigned int data_width;
+	unsigned int idiv;
+	unsigned int fbdiv;
+	unsigned int odiv0;
+	unsigned int odiv1;
+};
+
+static const struct dw_i2s_pll dw_i2s_pll_cfg_27m[] = {
+	/* 27Mhz */
+	{ 32000, 16, 0x104, 0x451, 0x10E38, 0x2000 },
+	{ 44100, 16, 0x104, 0x596, 0x10D35, 0x2000 },
+	{ 48000, 16, 0x208, 0xA28, 0x10B2C, 0x2000 },
+	{ 0, 0, 0, 0, 0, 0 },
 };
 
+static const struct dw_i2s_pll dw_i2s_pll_cfg_28m[] = {
+	/* 28.224Mhz */
+	{ 32000, 16, 0x82, 0x105, 0x107DF, 0x2000 },
+	{ 44100, 16, 0x28A, 0x1, 0x10001, 0x2000 },
+	{ 48000, 16, 0xA28, 0x187, 0x10042, 0x2000 },
+	{ 0, 0, 0, 0, 0, 0 },
+};
+
+static const struct snd_pcm_hardware dw_pcm_hw = {
+	.info       = SNDRV_PCM_INFO_INTERLEAVED |
+			SNDRV_PCM_INFO_MMAP |
+			SNDRV_PCM_INFO_MMAP_VALID |
+			SNDRV_PCM_INFO_BLOCK_TRANSFER,
+	.rates      = SNDRV_PCM_RATE_32000 |
+			SNDRV_PCM_RATE_44100 |
+			SNDRV_PCM_RATE_48000,
+	.rate_min   = 32000,
+	.rate_max   = 48000,
+	.formats    = SNDRV_PCM_FMTBIT_S16_LE,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = BUFFER_BYTES_MAX,
+	.period_bytes_min = PERIOD_BYTES_MIN,
+	.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
+	.periods_min      = PERIODS_MIN,
+	.periods_max      = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
+};
+
+static int dw_pcm_transfer(u32 *lsample, u32 *rsample, int bytes, int buf_size,
+		struct dw_pcm_binfo *bi)
+{
+	struct snd_pcm_runtime *rt = NULL;
+	int i;
+
+	if (!bi)
+		return -EINVAL;
+
+	rt = bi->stream->runtime;
+
+	for (i = 0; i < buf_size; i++) {
+		if (bi->stream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			memcpy(&lsample[i], bi->dma_pointer, bytes);
+			bi->dma_pointer += bytes;
+			memcpy(&rsample[i], bi->dma_pointer, bytes);
+			bi->dma_pointer += bytes;
+		} else {
+			memcpy(bi->dma_pointer, &lsample[i], bytes);
+			bi->dma_pointer += bytes;
+			memcpy(bi->dma_pointer, &rsample[i], bytes);
+			bi->dma_pointer += bytes;
+		}
+	}
+	bi->period_pointer += bytes_to_frames(rt, bytes * 2 * buf_size);
+
+	if (bi->period_pointer >=
+			(bi->period_size_frames * bi->current_period)) {
+		bi->current_period++;
+		if (bi->current_period > bi->total_periods) {
+			bi->dma_pointer = bi->dma_base;
+			bi->period_pointer = 0;
+			bi->current_period = 1;
+		}
+
+		snd_pcm_period_elapsed(bi->stream);
+	}
+
+	return 0;
+}
+
 static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val)
 {
 	writel(val, io_base + reg);
@@ -144,20 +259,94 @@ static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream)
 	}
 }
 
+static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
+{
+	struct dw_i2s_dev *dev = dev_id;
+	u32 isr[4], sleft[dev->fifo_th], sright[dev->fifo_th];
+	int i, j;
+
+	for (i = 0; i < 4; i++)
+		isr[i] = i2s_read_reg(dev->i2s_base, ISR(i));
+
+	for (i = 0; i < 4; i++) {
+		/* Copy only to/from first two channels.
+		 *  TODO: Remaining channels
+		 */
+		if ((isr[i] & 0x10) && (i == 0) && (dev->binfo.stream->stream ==
+					SNDRV_PCM_STREAM_PLAYBACK)) {
+			/* TXFE - TX FIFO is empty */
+			dw_pcm_transfer(sleft, sright, dev->xfer_bytes,
+					dev->fifo_th, &dev->binfo);
+
+			for (j = 0; j < dev->fifo_th; j++) {
+				i2s_write_reg(dev->i2s_base, LRBR_LTHR(i),
+						sleft[j]);
+				i2s_write_reg(dev->i2s_base, RRBR_RTHR(i),
+						sright[j]);
+			}
+		} else if ((isr[i] & 0x1) && (i == 0) &&
+					(dev->binfo.stream->stream ==
+					SNDRV_PCM_STREAM_CAPTURE)) {
+			/* RSFE - RX FIFO is full */
+			for (j = 0; j < dev->fifo_th; j++) {
+				sleft[j] = i2s_read_reg(dev->i2s_base,
+						LRBR_LTHR(i));
+				sright[j] = i2s_read_reg(dev->i2s_base,
+						RRBR_RTHR(i));
+			}
+
+			dw_pcm_transfer(sleft, sright, dev->xfer_bytes,
+					dev->fifo_th, &dev->binfo);
+		}
+	}
+
+	i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK);
+	i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE);
+
+	return IRQ_HANDLED;
+}
+
+static int i2s_pll_cfg(struct i2s_clk_config_data *config)
+{
+	const struct dw_i2s_pll *pll_cfg;
+	u32 rate = config->sample_rate;
+	u32 data_width = config->data_width;
+	int i;
+
+	if (readl((void *)FPGA_VER_INFO) <= FPGA_VER_27M)
+		pll_cfg = dw_i2s_pll_cfg_27m;
+	else
+		pll_cfg = dw_i2s_pll_cfg_28m;
+
+	for (i = 0; pll_cfg[i].rate != 0; i++) {
+		if ((pll_cfg[i].rate == rate) &&
+				(pll_cfg[i].data_width == data_width)) {
+			writel(pll_cfg[i].idiv, (void *)PLL_IDIV_ADDR);
+			writel(pll_cfg[i].fbdiv, (void *)PLL_FBDIV_ADDR);
+			writel(pll_cfg[i].odiv0, (void *)PLL_ODIV0_ADDR);
+			writel(pll_cfg[i].odiv1, (void *)PLL_ODIV1_ADDR);
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
 static void i2s_start(struct dw_i2s_dev *dev,
 		      struct snd_pcm_substream *substream)
 {
+	struct i2s_clk_config_data *config = &dev->config;
 	u32 i, irq;
 	i2s_write_reg(dev->i2s_base, IER, 1);
 
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
-		for (i = 0; i < 4; i++) {
+		for (i = 0; i < (config->chan_nr / 2); i++) {
 			irq = i2s_read_reg(dev->i2s_base, IMR(i));
 			i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30);
 		}
 		i2s_write_reg(dev->i2s_base, ITER, 1);
 	} else {
-		for (i = 0; i < 4; i++) {
+		for (i = 0; i < (config->chan_nr / 2); i++) {
 			irq = i2s_read_reg(dev->i2s_base, IMR(i));
 			i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03);
 		}
@@ -195,6 +384,139 @@ static void i2s_stop(struct dw_i2s_dev *dev,
 	}
 }
 
+static int dw_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+	snd_soc_set_runtime_hwparams(substream, &dw_pcm_hw);
+	snd_pcm_hw_constraint_integer(rt, SNDRV_PCM_HW_PARAM_PERIODS);
+
+	rt->hw.rate_min = 32000;
+	rt->hw.rate_max = 48000;
+
+	dev->binfo.stream = substream;
+	rt->private_data = &dev->binfo;
+	return 0;
+}
+
+static int dw_pcm_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static int dw_pcm_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct dw_pcm_binfo *bi = rt->private_data;
+	int ret;
+
+	ret = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+			params_buffer_bytes(hw_params));
+	if (ret < 0)
+		return ret;
+
+	memset(rt->dma_area, 0, params_buffer_bytes(hw_params));
+	bi->dma_base = rt->dma_area;
+	bi->dma_pointer = bi->dma_base;
+
+	return 0;
+}
+
+static int dw_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	int ret;
+
+	ret = snd_pcm_lib_free_vmalloc_buffer(substream);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int dw_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct dw_pcm_binfo *bi = rt->private_data;
+	u32 buffer_size_frames = 0;
+
+	bi->period_size_frames = bytes_to_frames(rt,
+			snd_pcm_lib_period_bytes(substream));
+	bi->size = snd_pcm_lib_buffer_bytes(substream);
+	buffer_size_frames = bytes_to_frames(rt, bi->size);
+	bi->total_periods = buffer_size_frames / bi->period_size_frames;
+	bi->current_period = 1;
+
+	if ((buffer_size_frames % bi->period_size_frames) != 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int dw_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t dw_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *rt = substream->runtime;
+	struct dw_pcm_binfo *bi = rt->private_data;
+
+	return bi->period_pointer;
+}
+
+static struct snd_pcm_ops dw_pcm_ops = {
+	.open      = dw_pcm_open,
+	.close     = dw_pcm_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.hw_params = dw_pcm_hw_params,
+	.hw_free   = dw_pcm_hw_free,
+	.prepare   = dw_pcm_prepare,
+	.trigger   = dw_pcm_trigger,
+	.pointer   = dw_pcm_pointer,
+	.page      = snd_pcm_lib_get_vmalloc_page,
+	.mmap      = snd_pcm_lib_mmap_vmalloc,
+};
+
+static int dw_pcm_new(struct snd_soc_pcm_runtime *runtime)
+{
+	struct snd_pcm *pcm = runtime->pcm;
+	int ret;
+
+	ret =  snd_pcm_lib_preallocate_pages_for_all(pcm,
+			SNDRV_DMA_TYPE_DEV,
+			snd_dma_continuous_data(GFP_KERNEL),
+			dw_pcm_hw.buffer_bytes_max,
+			dw_pcm_hw.buffer_bytes_max);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void dw_pcm_free(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static struct snd_soc_platform_driver dw_pcm_soc_platform = {
+	.pcm_new  = dw_pcm_new,
+	.pcm_free = dw_pcm_free,
+	.ops = &dw_pcm_ops,
+};
+
 static int dw_i2s_startup(struct snd_pcm_substream *substream,
 		struct snd_soc_dai *cpu_dai)
 {
@@ -231,14 +553,16 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
 		if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
 			i2s_write_reg(dev->i2s_base, TCR(ch_reg),
 				      dev->xfer_resolution);
-			i2s_write_reg(dev->i2s_base, TFCR(ch_reg), 0x02);
+			i2s_write_reg(dev->i2s_base, TFCR(ch_reg),
+				      dev->fifo_th - 1);
 			irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg));
 			i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x30);
 			i2s_write_reg(dev->i2s_base, TER(ch_reg), 1);
 		} else {
 			i2s_write_reg(dev->i2s_base, RCR(ch_reg),
 				      dev->xfer_resolution);
-			i2s_write_reg(dev->i2s_base, RFCR(ch_reg), 0x07);
+			i2s_write_reg(dev->i2s_base, RFCR(ch_reg),
+				      dev->fifo_th - 1);
 			irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg));
 			i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x03);
 			i2s_write_reg(dev->i2s_base, RER(ch_reg), 1);
@@ -259,22 +583,25 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
 		config->data_width = 16;
 		dev->ccr = 0x00;
 		dev->xfer_resolution = 0x02;
+		dev->xfer_bytes = 0x02;
 		break;
 
 	case SNDRV_PCM_FORMAT_S24_LE:
 		config->data_width = 24;
 		dev->ccr = 0x08;
 		dev->xfer_resolution = 0x04;
+		dev->xfer_bytes = 0x03;
 		break;
 
 	case SNDRV_PCM_FORMAT_S32_LE:
 		config->data_width = 32;
 		dev->ccr = 0x10;
 		dev->xfer_resolution = 0x05;
+		dev->xfer_bytes = 0x04;
 		break;
 
 	default:
-		dev_err(dev->dev, "designware-i2s: unsuppted PCM fmt");
+		dev_err(dev->dev, "designware-i2s: unsupported PCM fmt");
 		return -EINVAL;
 	}
 
@@ -316,6 +643,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
 			}
 		}
 	}
+
 	return 0;
 }
 
@@ -498,6 +826,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev,
 	 */
 	u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1);
 	u32 comp2 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp2);
+	u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
 	u32 idx;
 
 	if (dev->capability & DWC_I2S_RECORD &&
@@ -536,6 +865,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev,
 		dev->capability |= DW_I2S_SLAVE;
 	}
 
+	dev->fifo_th = fifo_depth / 2;
 	return 0;
 }
 
@@ -620,7 +950,7 @@ static int dw_i2s_probe(struct platform_device *pdev)
 	const struct i2s_platform_data *pdata = pdev->dev.platform_data;
 	struct dw_i2s_dev *dev;
 	struct resource *res;
-	int ret;
+	int ret, irq_number;
 	struct snd_soc_dai_driver *dw_i2s_dai;
 	const char *clk_id;
 
@@ -643,6 +973,19 @@ static int dw_i2s_probe(struct platform_device *pdev)
 	if (IS_ERR(dev->i2s_base))
 		return PTR_ERR(dev->i2s_base);
 
+	irq_number = platform_get_irq(pdev, 0);
+	if (irq_number <= 0) {
+		dev_err(&pdev->dev, "get irq fail\n");
+		return -EINVAL;
+	}
+
+	ret = devm_request_irq(&pdev->dev, irq_number, i2s_irq_handler,
+			IRQF_SHARED, "dw_i2s_irq_handler", dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "request irq fail\n");
+		return ret;
+	}
+
 	dev->dev = &pdev->dev;
 
 	dev->i2s_reg_comp1 = I2S_COMP_PARAM_1;
@@ -671,14 +1014,22 @@ static int dw_i2s_probe(struct platform_device *pdev)
 				return -ENODEV;
 			}
 		}
-		dev->clk = devm_clk_get(&pdev->dev, clk_id);
 
-		if (IS_ERR(dev->clk))
-			return PTR_ERR(dev->clk);
+		if (dev->i2s_clk_cfg ||
+				of_get_property(pdev->dev.of_node, "clocks", NULL)) {
+			dev->clk = devm_clk_get(&pdev->dev, clk_id);
+
+			if (IS_ERR(dev->clk))
+				return PTR_ERR(dev->clk);
 
-		ret = clk_prepare_enable(dev->clk);
-		if (ret < 0)
-			return ret;
+			ret = clk_prepare_enable(dev->clk);
+			if (ret < 0)
+				return ret;
+		} else {
+			/* Use internal PLL config */
+			dev->i2s_clk_cfg = i2s_pll_cfg;
+			dev->clk = NULL;
+		}
 	}
 
 	dev_set_drvdata(&pdev->dev, dev);
@@ -690,7 +1041,14 @@ static int dw_i2s_probe(struct platform_device *pdev)
 	}
 
 	if (!pdata) {
-		ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+		if (of_get_property(pdev->dev.of_node, "dmas", NULL)) {
+			/* Using DMA */
+			ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+		} else {
+			ret = snd_soc_register_platform(&pdev->dev,
+					&dw_pcm_soc_platform);
+		}
+
 		if (ret) {
 			dev_err(&pdev->dev,
 				"Could not register PCM: %d\n", ret);
@@ -714,6 +1072,7 @@ static int dw_i2s_remove(struct platform_device *pdev)
 		clk_disable_unprepare(dev->clk);
 
 	pm_runtime_disable(&pdev->dev);
+	snd_soc_unregister_platform(&pdev->dev);
 	return 0;
 }
 
-- 
1.9.1

  parent reply	other threads:[~2016-03-28 14:37 UTC|newest]

Thread overview: 66+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-03-28 14:36 [PATCH 0/3 v2] Add I2S/ADV7511 audio support for ARC AXS10x boards Jose Abreu
2016-03-28 14:36 ` Jose Abreu
2016-03-28 14:36 ` Jose Abreu
2016-03-28 14:36 ` [PATCH 1/3 v2] drm/i2c/adv7511: Add audio support Jose Abreu
2016-03-28 14:36   ` Jose Abreu
2016-03-29  8:05   ` Archit Taneja
2016-03-29  8:05     ` Archit Taneja
2016-03-29  8:05     ` Archit Taneja
2016-03-29 10:52     ` Jose Abreu
2016-03-29 10:52       ` Jose Abreu
2016-03-29 10:52       ` Jose Abreu
2016-03-29 17:03       ` Archit Taneja
2016-03-29 17:03         ` Archit Taneja
2016-03-29 17:03         ` Archit Taneja
2016-03-31 12:57         ` Jose Abreu
2016-03-31 12:57           ` Jose Abreu
2016-03-31 12:57           ` Jose Abreu
2016-03-30  9:58   ` Emil Velikov
2016-03-30  9:58     ` Emil Velikov
2016-03-30  9:58     ` Emil Velikov
2016-04-01 17:10   ` Laurent Pinchart
2016-04-01 17:10     ` Laurent Pinchart
2016-04-01 17:10     ` Laurent Pinchart
2016-04-04  9:05     ` Jose Abreu
2016-04-04  9:05       ` Jose Abreu
2016-04-04  9:05       ` Jose Abreu
2016-04-04 21:41       ` Laurent Pinchart
2016-04-04 21:41         ` Laurent Pinchart
2016-04-04 21:41         ` Laurent Pinchart
2016-04-05 11:00         ` Jose Abreu
2016-04-05 11:00           ` Jose Abreu
2016-04-05 11:00           ` Jose Abreu
2016-04-05 16:03           ` Laurent Pinchart
2016-04-05 16:03             ` Laurent Pinchart
2016-04-05 16:03             ` Laurent Pinchart
2016-04-03  5:05   ` kbuild test robot
2016-04-03  5:05     ` kbuild test robot
2016-04-03  5:05     ` kbuild test robot
2016-03-28 14:36 ` Jose Abreu [this message]
2016-03-28 14:36   ` [PATCH 2/3 v2] ASoC: dwc: Add I2S HDMI " Jose Abreu
2016-03-28 14:36   ` Jose Abreu
2016-03-28 15:35   ` Alexey Brodkin
2016-03-28 15:35     ` Alexey Brodkin
2016-03-28 15:35     ` Alexey Brodkin
2016-03-28 16:07     ` Jose Abreu
2016-03-28 16:07       ` Jose Abreu
2016-03-28 16:07       ` Jose Abreu
2016-03-29 17:31   ` Mark Brown
2016-03-29 17:31     ` Mark Brown
2016-03-29 17:31     ` Mark Brown
2016-03-29 18:03     ` Jose Abreu
2016-03-29 18:22       ` Mark Brown
2016-03-29 18:22         ` Mark Brown
2016-03-29 18:22         ` Mark Brown
2016-03-31  9:37         ` Jose Abreu
2016-03-31  9:37           ` Jose Abreu
2016-03-31  9:37           ` Jose Abreu
2016-03-31 16:56           ` Mark Brown
2016-03-31 16:56             ` Mark Brown
2016-03-31 16:56             ` Mark Brown
2016-03-28 14:36 ` [PATCH 3/3 v2] arc: axs10x: Add support for Designware I2S on DT Jose Abreu
2016-03-28 14:36   ` Jose Abreu
2016-03-28 14:36   ` Jose Abreu
2016-03-29 17:00 ` [PATCH 0/3 v2] Add I2S/ADV7511 audio support for ARC AXS10x boards Mark Brown
2016-03-29 17:00   ` Mark Brown
2016-03-29 17:00   ` Mark Brown

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=538be366488bf0d7633d702f2d0bab16707b7a47.1459174494.git.joabreu@synopsys.com \
    --to=jose.abreu@synopsys.com \
    --cc=Alexey.Brodkin@synopsys.com \
    --cc=CARLOS.PALMINHA@synopsys.com \
    --cc=Maruthi.Bayyavarapu@amd.com \
    --cc=Vineet.Gupta1@synopsys.com \
    --cc=airlied@linux.ie \
    --cc=alexander.deucher@amd.com \
    --cc=alsa-devel@alsa-project.org \
    --cc=broonie@kernel.org \
    --cc=buyitian@gmail.com \
    --cc=devicetree@vger.kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=galak@codeaurora.org \
    --cc=ijc+devicetree@hellion.org.uk \
    --cc=lars@metafoo.de \
    --cc=laurent.pinchart+renesas@ideasonboard.com \
    --cc=lgirdwood@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-snps-arc@lists.infradead.org \
    --cc=mark.rutland@arm.com \
    --cc=nariman@opensource.wolfsonmicro.com \
    --cc=pawel.moll@arm.com \
    --cc=perex@perex.cz \
    --cc=robh+dt@kernel.org \
    --cc=tiwai@suse.com \
    --cc=tixy@linaro.org \
    --cc=ville.syrjala@linux.intel.com \
    --cc=wsa+renesas@sang-engineering.com \
    --cc=yitian.bu@tangramtek.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.