[PATCH 01/14] ASoC: Add driver for s6000 I2S interface

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Daniel Glöckner <dg@xxxxxxxxx>
This patch adds a driver for the I2S interface found on Stretch s6000family processors.
Signed-off-by: Daniel Glöckner <dg@xxxxxxxxx>Signed-off-by: Mark Brown <broonie@xxxxxxxxxxxxxxxxxxxxxxxxxxx>--- sound/soc/Kconfig           |    1 + sound/soc/Makefile          |    1 + sound/soc/s6000/Kconfig     |   10 + sound/soc/s6000/Makefile    |    6 + sound/soc/s6000/s6000-i2s.c |  629 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/s6000/s6000-i2s.h |   25 ++ sound/soc/s6000/s6000-pcm.c |  497 ++++++++++++++++++++++++++++++++++ sound/soc/s6000/s6000-pcm.h |   35 +++ 8 files changed, 1204 insertions(+), 0 deletions(-) create mode 100644 sound/soc/s6000/Kconfig create mode 100644 sound/soc/s6000/Makefile create mode 100644 sound/soc/s6000/s6000-i2s.c create mode 100644 sound/soc/s6000/s6000-i2s.h create mode 100644 sound/soc/s6000/s6000-pcm.c create mode 100644 sound/soc/s6000/s6000-pcm.h
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfigindex 3d2bb6f..3304f9d 100644--- a/sound/soc/Kconfig+++ b/sound/soc/Kconfig@@ -32,6 +32,7 @@ source "sound/soc/fsl/Kconfig" source "sound/soc/omap/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/s3c24xx/Kconfig"+source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig"  # Supported codecsdiff --git a/sound/soc/Makefile b/sound/soc/Makefileindex 0237879..8943a14 100644--- a/sound/soc/Makefile+++ b/sound/soc/Makefile@@ -10,4 +10,5 @@ obj-$(CONFIG_SND_SOC)	+= fsl/ obj-$(CONFIG_SND_SOC)	+= omap/ obj-$(CONFIG_SND_SOC)	+= pxa/ obj-$(CONFIG_SND_SOC)	+= s3c24xx/+obj-$(CONFIG_SND_SOC)	+= s6000/ obj-$(CONFIG_SND_SOC)	+= sh/diff --git a/sound/soc/s6000/Kconfig b/sound/soc/s6000/Kconfignew file mode 100644index 0000000..4bfc8bc--- /dev/null+++ b/sound/soc/s6000/Kconfig@@ -0,0 +1,10 @@+config SND_S6000_SOC+	tristate "SoC Audio for the Stretch s6000 family"+	depends on XTENSA_VARIANT_S6000+	help+	  Say Y or M if you want to add support for codecs attached to+	  s6000 family chips. You will also need to select the platform+	  to support below.++config SND_S6000_SOC_I2S+	tristatediff --git a/sound/soc/s6000/Makefile b/sound/soc/s6000/Makefilenew file mode 100644index 0000000..df15f87--- /dev/null+++ b/sound/soc/s6000/Makefile@@ -0,0 +1,6 @@+# s6000 Platform Support+snd-soc-s6000-objs := s6000-pcm.o+snd-soc-s6000-i2s-objs := s6000-i2s.o++obj-$(CONFIG_SND_S6000_SOC) += snd-soc-s6000.o+obj-$(CONFIG_SND_S6000_SOC_I2S) += snd-soc-s6000-i2s.odiff --git a/sound/soc/s6000/s6000-i2s.c b/sound/soc/s6000/s6000-i2s.cnew file mode 100644index 0000000..dcc7904--- /dev/null+++ b/sound/soc/s6000/s6000-i2s.c@@ -0,0 +1,629 @@+/*+ * ALSA SoC I2S Audio Layer for the Stretch S6000 family+ *+ * Author:      Daniel Gloeckner, <dg@xxxxxxxxx>+ * Copyright:   (C) 2009 emlix GmbH <info@xxxxxxxxx>+ *+ * This program is free software; you can redistribute it and/or modify+ * it under the terms of the GNU General Public License version 2 as+ * published by the Free Software Foundation.+ */++#include <linux/init.h>+#include <linux/module.h>+#include <linux/device.h>+#include <linux/delay.h>+#include <linux/clk.h>+#include <linux/interrupt.h>+#include <linux/io.h>++#include <sound/core.h>+#include <sound/pcm.h>+#include <sound/pcm_params.h>+#include <sound/initval.h>+#include <sound/soc.h>++#include "s6000-i2s.h"+#include "s6000-pcm.h"++struct s6000_i2s_dev {+	dma_addr_t sifbase;+	u8 __iomem *scbbase;+	unsigned int wide;+	unsigned int channel_in;+	unsigned int channel_out;+	unsigned int lines_in;+	unsigned int lines_out;+	struct s6000_pcm_dma_params dma_params;+};++#define S6_I2S_INTERRUPT_STATUS	0x00+#define   S6_I2S_INT_OVERRUN	1+#define   S6_I2S_INT_UNDERRUN	2+#define   S6_I2S_INT_ALIGNMENT	4+#define S6_I2S_INTERRUPT_ENABLE	0x04+#define S6_I2S_INTERRUPT_RAW	0x08+#define S6_I2S_INTERRUPT_CLEAR	0x0C+#define S6_I2S_INTERRUPT_SET	0x10+#define S6_I2S_MODE		0x20+#define   S6_I2S_DUAL		0+#define   S6_I2S_WIDE		1+#define S6_I2S_TX_DEFAULT	0x24+#define S6_I2S_DATA_CFG(c)	(0x40 + 0x10 * (c))+#define   S6_I2S_IN		0+#define   S6_I2S_OUT		1+#define   S6_I2S_UNUSED		2+#define S6_I2S_INTERFACE_CFG(c)	(0x44 + 0x10 * (c))+#define   S6_I2S_DIV_MASK	0x001fff+#define   S6_I2S_16BIT		0x000000+#define   S6_I2S_20BIT		0x002000+#define   S6_I2S_24BIT		0x004000+#define   S6_I2S_32BIT		0x006000+#define   S6_I2S_BITS_MASK	0x006000+#define   S6_I2S_MEM_16BIT	0x000000+#define   S6_I2S_MEM_32BIT	0x008000+#define   S6_I2S_MEM_MASK	0x008000+#define   S6_I2S_CHANNELS_SHIFT	16+#define   S6_I2S_CHANNELS_MASK	0x030000+#define   S6_I2S_SCK_IN		0x000000+#define   S6_I2S_SCK_OUT	0x040000+#define   S6_I2S_SCK_DIR	0x040000+#define   S6_I2S_WS_IN		0x000000+#define   S6_I2S_WS_OUT		0x080000+#define   S6_I2S_WS_DIR		0x080000+#define   S6_I2S_LEFT_FIRST	0x000000+#define   S6_I2S_RIGHT_FIRST	0x100000+#define   S6_I2S_FIRST		0x100000+#define   S6_I2S_CUR_SCK	0x200000+#define   S6_I2S_CUR_WS		0x400000+#define S6_I2S_ENABLE(c)	(0x48 + 0x10 * (c))+#define   S6_I2S_DISABLE_IF	0x02+#define   S6_I2S_ENABLE_IF	0x03+#define   S6_I2S_IS_BUSY	0x04+#define   S6_I2S_DMA_ACTIVE	0x08+#define   S6_I2S_IS_ENABLED	0x10++#define S6_I2S_NUM_LINES	4++#define S6_I2S_SIF_PORT0	0x0000000+#define S6_I2S_SIF_PORT1	0x0000080 /* docs say 0x0000010 */++static inline void s6_i2s_write_reg(struct s6000_i2s_dev *dev, int reg, u32 val)+{+	writel(val, dev->scbbase + reg);+}++static inline u32 s6_i2s_read_reg(struct s6000_i2s_dev *dev, int reg)+{+	return readl(dev->scbbase + reg);+}++static inline void s6_i2s_mod_reg(struct s6000_i2s_dev *dev, int reg,+				  u32 mask, u32 val)+{+	val ^= s6_i2s_read_reg(dev, reg) & ~mask;+	s6_i2s_write_reg(dev, reg, val);+}++static void s6000_i2s_start_channel(struct s6000_i2s_dev *dev, int channel)+{+	int i, j, cur, prev;++	/*+	 * Wait for WCLK to toggle 5 times before enabling the channel+	 * s6000 Family Datasheet 3.6.4:+	 *   "At least two cycles of WS must occur between commands+	 *    to disable or enable the interface"+	 */+	j = 0;+	prev = ~S6_I2S_CUR_WS;+	for (i = 1000000; --i && j < 6; ) {+		cur = s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(channel))+		       & S6_I2S_CUR_WS;+		if (prev != cur) {+			prev = cur;+			j++;+		}+	}+	if (j < 6)+		printk(KERN_WARNING "s6000-i2s: timeout waiting for WCLK\n");++	s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_ENABLE_IF);+}++static void s6000_i2s_stop_channel(struct s6000_i2s_dev *dev, int channel)+{+	s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_DISABLE_IF);+}++static void s6000_i2s_start(struct snd_pcm_substream *substream)+{+	struct snd_soc_pcm_runtime *rtd = substream->private_data;+	struct s6000_i2s_dev *dev = rtd->dai->cpu_dai->private_data;+	int channel;++	channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?+			dev->channel_out : dev->channel_in;++	s6000_i2s_start_channel(dev, channel);+}++static void s6000_i2s_stop(struct snd_pcm_substream *substream)+{+	struct snd_soc_pcm_runtime *rtd = substream->private_data;+	struct s6000_i2s_dev *dev = rtd->dai->cpu_dai->private_data;+	int channel;++	channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?+			dev->channel_out : dev->channel_in;++	s6000_i2s_stop_channel(dev, channel);+}++static int s6000_i2s_trigger(struct snd_pcm_substream *substream, int cmd,+			     int after)+{+	switch (cmd) {+	case SNDRV_PCM_TRIGGER_START:+	case SNDRV_PCM_TRIGGER_RESUME:+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:+		if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) ^ !after)+			s6000_i2s_start(substream);+		break;+	case SNDRV_PCM_TRIGGER_STOP:+	case SNDRV_PCM_TRIGGER_SUSPEND:+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:+		if (!after)+			s6000_i2s_stop(substream);+	}+	return 0;+}++static unsigned int s6000_i2s_int_sources(struct s6000_i2s_dev *dev)+{+	unsigned int pending;+	pending = s6_i2s_read_reg(dev, S6_I2S_INTERRUPT_RAW);+	pending &= S6_I2S_INT_ALIGNMENT |+		   S6_I2S_INT_UNDERRUN |+		   S6_I2S_INT_OVERRUN;+	s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR, pending);++	return pending;+}++static unsigned int s6000_i2s_check_xrun(struct snd_soc_dai *cpu_dai)+{+	struct s6000_i2s_dev *dev = cpu_dai->private_data;+	unsigned int errors;+	unsigned int ret;++	errors = s6000_i2s_int_sources(dev);+	if (likely(!errors))+		return 0;++	ret = 0;+	if (errors & S6_I2S_INT_ALIGNMENT)+		printk(KERN_ERR "s6000-i2s: WCLK misaligned\n");+	if (errors & S6_I2S_INT_UNDERRUN)+		ret |= 1 << SNDRV_PCM_STREAM_PLAYBACK;+	if (errors & S6_I2S_INT_OVERRUN)+		ret |= 1 << SNDRV_PCM_STREAM_CAPTURE;+	return ret;+}++static void s6000_i2s_wait_disabled(struct s6000_i2s_dev *dev)+{+	int channel;+	int n = 50;+	for (channel = 0; channel < 2; channel++) {+		while (--n >= 0) {+			int v = s6_i2s_read_reg(dev, S6_I2S_ENABLE(channel));+			if ((v & S6_I2S_IS_ENABLED)+			    || !(v & (S6_I2S_DMA_ACTIVE | S6_I2S_IS_BUSY)))+				break;+			udelay(20);+		}+	}+	if (n < 0)+		printk(KERN_WARNING "s6000-i2s: timeout disabling interfaces");+}++static int s6000_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,+				   unsigned int fmt)+{+	struct s6000_i2s_dev *dev = cpu_dai->private_data;+	u32 w;++	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {+	case SND_SOC_DAIFMT_CBM_CFM:+		w = S6_I2S_SCK_IN | S6_I2S_WS_IN;+		break;+	case SND_SOC_DAIFMT_CBS_CFM:+		w = S6_I2S_SCK_OUT | S6_I2S_WS_IN;+		break;+	case SND_SOC_DAIFMT_CBM_CFS:+		w = S6_I2S_SCK_IN | S6_I2S_WS_OUT;+		break;+	case SND_SOC_DAIFMT_CBS_CFS:+		w = S6_I2S_SCK_OUT | S6_I2S_WS_OUT;+		break;+	default:+		return -EINVAL;+	}++	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {+	case SND_SOC_DAIFMT_IB_IF:+		w |= S6_I2S_LEFT_FIRST;+		break;+	case SND_SOC_DAIFMT_IB_NF:+		w |= S6_I2S_RIGHT_FIRST;+		break;+	default:+		return -EINVAL;+	}++	s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(0),+		       S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w);+	s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(1),+		       S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w);++	return 0;+}++static int s6000_i2s_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)+{+	struct s6000_i2s_dev *dev = dai->private_data;++	if (!div || (div & 1) || div > (S6_I2S_DIV_MASK + 1) * 2)+		return -EINVAL;++	s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(div_id),+		       S6_I2S_DIV_MASK, div / 2 - 1);+	return 0;+}++static int s6000_i2s_hw_params(struct snd_pcm_substream *substream,+			       struct snd_pcm_hw_params *params,+			       struct snd_soc_dai *dai)+{+	struct s6000_i2s_dev *dev = dai->private_data;+	int interf;+	u32 w = 0;++	if (dev->wide)+		interf = 0;+	else {+		w |= (((params_channels(params) - 2) / 2)+		      << S6_I2S_CHANNELS_SHIFT) & S6_I2S_CHANNELS_MASK;+		interf = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)+				? dev->channel_out : dev->channel_in;+	}++	switch (params_format(params)) {+	case SNDRV_PCM_FORMAT_S16_LE:+		w |= S6_I2S_16BIT | S6_I2S_MEM_16BIT;+		break;+	case SNDRV_PCM_FORMAT_S32_LE:+		w |= S6_I2S_32BIT | S6_I2S_MEM_32BIT;+		break;+	default:+		printk(KERN_WARNING "s6000-i2s: unsupported PCM format %x\n",+		       params_format(params));+		return -EINVAL;+	}++	if (s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(interf))+	     & S6_I2S_IS_ENABLED) {+		printk(KERN_ERR "s6000-i2s: interface already enabled\n");+		return -EBUSY;+	}++	s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(interf),+		       S6_I2S_CHANNELS_MASK|S6_I2S_MEM_MASK|S6_I2S_BITS_MASK,+		       w);++	return 0;+}++static int s6000_i2s_dai_probe(struct platform_device *pdev,+			       struct snd_soc_dai *dai)+{+	struct s6000_i2s_dev *dev = dai->private_data;+	struct s6000_snd_platform_data *pdata = pdev->dev.platform_data;++	if (!pdata)+		return -EINVAL;++	dev->wide = pdata->wide;+	dev->channel_in = pdata->channel_in;+	dev->channel_out = pdata->channel_out;+	dev->lines_in = pdata->lines_in;+	dev->lines_out = pdata->lines_out;++	s6_i2s_write_reg(dev, S6_I2S_MODE,+			 dev->wide ? S6_I2S_WIDE : S6_I2S_DUAL);++	if (dev->wide) {+		int i;++		if (dev->lines_in + dev->lines_out > S6_I2S_NUM_LINES)+			return -EINVAL;++		dev->channel_in = 0;+		dev->channel_out = 1;+		dai->capture.channels_min = 2 * dev->lines_in;+		dai->capture.channels_max = dai->capture.channels_min;+		dai->playback.channels_min = 2 * dev->lines_out;+		dai->playback.channels_max = dai->playback.channels_min;++		for (i = 0; i < dev->lines_out; i++)+			s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_OUT);++		for (; i < S6_I2S_NUM_LINES - dev->lines_in; i++)+			s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i),+					 S6_I2S_UNUSED);++		for (; i < S6_I2S_NUM_LINES; i++)+			s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_IN);+	} else {+		unsigned int cfg[2] = {S6_I2S_UNUSED, S6_I2S_UNUSED};++		if (dev->lines_in > 1 || dev->lines_out > 1)+			return -EINVAL;++		dai->capture.channels_min = 2 * dev->lines_in;+		dai->capture.channels_max = 8 * dev->lines_in;+		dai->playback.channels_min = 2 * dev->lines_out;+		dai->playback.channels_max = 8 * dev->lines_out;++		if (dev->lines_in)+			cfg[dev->channel_in] = S6_I2S_IN;+		if (dev->lines_out)+			cfg[dev->channel_out] = S6_I2S_OUT;++		s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(0), cfg[0]);+		s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(1), cfg[1]);+	}++	if (dev->lines_out) {+		if (dev->lines_in) {+			if (!dev->dma_params.dma_out)+				return -ENODEV;+		} else {+			dev->dma_params.dma_out = dev->dma_params.dma_in;+			dev->dma_params.dma_in = 0;+		}+	}+	dev->dma_params.sif_in = dev->sifbase + (dev->channel_in ?+					S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0);+	dev->dma_params.sif_out = dev->sifbase + (dev->channel_out ?+					S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0);+	dev->dma_params.same_rate = pdata->same_rate | pdata->wide;+	return 0;+}++#define S6000_I2S_RATES	(SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \+			 SNDRV_PCM_RATE_8000_192000)+#define S6000_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)++static struct snd_soc_dai_ops s6000_i2s_dai_ops = {+	.set_fmt = s6000_i2s_set_dai_fmt,+	.set_clkdiv = s6000_i2s_set_clkdiv,+	.hw_params = s6000_i2s_hw_params,+};++struct snd_soc_dai s6000_i2s_dai = {+	.name = "s6000-i2s",+	.id = 0,+	.probe = s6000_i2s_dai_probe,+	.playback = {+		.channels_min = 2,+		.channels_max = 8,+		.formats = S6000_I2S_FORMATS,+		.rates = S6000_I2S_RATES,+		.rate_min = 0,+		.rate_max = 1562500,+	},+	.capture = {+		.channels_min = 2,+		.channels_max = 8,+		.formats = S6000_I2S_FORMATS,+		.rates = S6000_I2S_RATES,+		.rate_min = 0,+		.rate_max = 1562500,+	},+	.ops = &s6000_i2s_dai_ops,+}+EXPORT_SYMBOL_GPL(s6000_i2s_dai);++static int __devinit s6000_i2s_probe(struct platform_device *pdev)+{+	struct s6000_i2s_dev *dev;+	struct resource *scbmem, *sifmem, *region, *dma1, *dma2;+	u8 __iomem *mmio;+	int ret;++	scbmem = platform_get_resource(pdev, IORESOURCE_MEM, 0);+	if (!scbmem) {+		dev_err(&pdev->dev, "no mem resource?\n");+		ret = -ENODEV;+		goto err_release_none;+	}++	region = request_mem_region(scbmem->start,+				    scbmem->end - scbmem->start + 1,+				    pdev->name);+	if (!region) {+		dev_err(&pdev->dev, "I2S SCB region already claimed\n");+		ret = -EBUSY;+		goto err_release_none;+	}++	mmio = ioremap(scbmem->start, scbmem->end - scbmem->start + 1);+	if (!mmio) {+		dev_err(&pdev->dev, "can't ioremap SCB region\n");+		ret = -ENOMEM;+		goto err_release_scb;+	}++	sifmem = platform_get_resource(pdev, IORESOURCE_MEM, 1);+	if (!sifmem) {+		dev_err(&pdev->dev, "no second mem resource?\n");+		ret = -ENODEV;+		goto err_release_map;+	}++	region = request_mem_region(sifmem->start,+				    sifmem->end - sifmem->start + 1,+				    pdev->name);+	if (!region) {+		dev_err(&pdev->dev, "I2S SIF region already claimed\n");+		ret = -EBUSY;+		goto err_release_map;+	}++	dma1 = platform_get_resource(pdev, IORESOURCE_DMA, 0);+	if (!dma1) {+		dev_err(&pdev->dev, "no dma resource?\n");+		ret = -ENODEV;+		goto err_release_sif;+	}++	region = request_mem_region(dma1->start, dma1->end - dma1->start + 1,+				    pdev->name);+	if (!region) {+		dev_err(&pdev->dev, "I2S DMA region already claimed\n");+		ret = -EBUSY;+		goto err_release_sif;+	}++	dma2 = platform_get_resource(pdev, IORESOURCE_DMA, 1);+	if (dma2) {+		region = request_mem_region(dma2->start,+					    dma2->end - dma2->start + 1,+					    pdev->name);+		if (!region) {+			dev_err(&pdev->dev,+				"I2S DMA region already claimed\n");+			ret = -EBUSY;+			goto err_release_dma1;+		}+	}++	dev = kzalloc(sizeof(struct s6000_i2s_dev), GFP_KERNEL);+	if (!dev) {+		ret = -ENOMEM;+		goto err_release_dma2;+	}++	s6000_i2s_dai.dev = &pdev->dev;+	s6000_i2s_dai.private_data = dev;+	s6000_i2s_dai.dma_data = &dev->dma_params;++	dev->sifbase = sifmem->start;+	dev->scbbase = mmio;++	s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);+	s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR,+			 S6_I2S_INT_ALIGNMENT |+			 S6_I2S_INT_UNDERRUN |+			 S6_I2S_INT_OVERRUN);++	s6000_i2s_stop_channel(dev, 0);+	s6000_i2s_stop_channel(dev, 1);+	s6000_i2s_wait_disabled(dev);++	dev->dma_params.check_xrun = s6000_i2s_check_xrun;+	dev->dma_params.trigger = s6000_i2s_trigger;+	dev->dma_params.dma_in = dma1->start;+	dev->dma_params.dma_out = dma2 ? dma2->start : 0;+	dev->dma_params.irq = platform_get_irq(pdev, 0);+	if (dev->dma_params.irq < 0) {+		dev_err(&pdev->dev, "no irq resource?\n");+		ret = -ENODEV;+		goto err_release_dev;+	}++	s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE,+			 S6_I2S_INT_ALIGNMENT |+			 S6_I2S_INT_UNDERRUN |+			 S6_I2S_INT_OVERRUN);++	ret = snd_soc_register_dai(&s6000_i2s_dai);+	if (ret)+		goto err_release_dev;++	return 0;++err_release_dev:+	kfree(dev);+err_release_dma2:+	if (dma2)+		release_mem_region(dma2->start, dma2->end - dma2->start + 1);+err_release_dma1:+	release_mem_region(dma1->start, dma1->end - dma1->start + 1);+err_release_sif:+	release_mem_region(sifmem->start, (sifmem->end - sifmem->start) + 1);+err_release_map:+	iounmap(mmio);+err_release_scb:+	release_mem_region(scbmem->start, (scbmem->end - scbmem->start) + 1);+err_release_none:+	return ret;+}++static void __devexit s6000_i2s_remove(struct platform_device *pdev)+{+	struct s6000_i2s_dev *dev = s6000_i2s_dai.private_data;+	struct resource *region;+	void __iomem *mmio = dev->scbbase;++	snd_soc_unregister_dai(&s6000_i2s_dai);++	s6000_i2s_stop_channel(dev, 0);+	s6000_i2s_stop_channel(dev, 1);++	s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);+	s6000_i2s_dai.private_data = 0;+	kfree(dev);++	region = platform_get_resource(pdev, IORESOURCE_DMA, 0);+	release_mem_region(region->start, region->end - region->start + 1);++	region = platform_get_resource(pdev, IORESOURCE_DMA, 1);+	if (region)+		release_mem_region(region->start,+				   region->end - region->start + 1);++	region = platform_get_resource(pdev, IORESOURCE_MEM, 0);+	release_mem_region(region->start, (region->end - region->start) + 1);++	iounmap(mmio);+	region = platform_get_resource(pdev, IORESOURCE_IO, 0);+	release_mem_region(region->start, (region->end - region->start) + 1);+}++static struct platform_driver s6000_i2s_driver = {+	.probe  = s6000_i2s_probe,+	.remove = __devexit_p(s6000_i2s_remove),+	.driver = {+		.name   = "s6000-i2s",+		.owner  = THIS_MODULE,+	},+};++static int __init s6000_i2s_init(void)+{+	return platform_driver_register(&s6000_i2s_driver);+}+module_init(s6000_i2s_init);++static void __exit s6000_i2s_exit(void)+{+	platform_driver_unregister(&s6000_i2s_driver);+}+module_exit(s6000_i2s_exit);++MODULE_AUTHOR("Daniel Gloeckner");+MODULE_DESCRIPTION("Stretch s6000 family I2S SoC Interface");+MODULE_LICENSE("GPL");diff --git a/sound/soc/s6000/s6000-i2s.h b/sound/soc/s6000/s6000-i2s.hnew file mode 100644index 0000000..2375fdf--- /dev/null+++ b/sound/soc/s6000/s6000-i2s.h@@ -0,0 +1,25 @@+/*+ * ALSA SoC I2S Audio Layer for the Stretch s6000 family+ *+ * Author:      Daniel Gloeckner, <dg@xxxxxxxxx>+ * Copyright:   (C) 2009 emlix GmbH <info@xxxxxxxxx>+ *+ * This program is free software; you can redistribute it and/or modify+ * it under the terms of the GNU General Public License version 2 as+ * published by the Free Software Foundation.+ */++#ifndef _S6000_I2S_H+#define _S6000_I2S_H++extern struct snd_soc_dai s6000_i2s_dai;++struct s6000_snd_platform_data {+	int lines_in;+	int lines_out;+	int channel_in;+	int channel_out;+	int wide;+	int same_rate;+};+#endifdiff --git a/sound/soc/s6000/s6000-pcm.c b/sound/soc/s6000/s6000-pcm.cnew file mode 100644index 0000000..83b8028--- /dev/null+++ b/sound/soc/s6000/s6000-pcm.c@@ -0,0 +1,497 @@+/*+ * ALSA PCM interface for the Stetch s6000 family+ *+ * Author:      Daniel Gloeckner, <dg@xxxxxxxxx>+ * Copyright:   (C) 2009 emlix GmbH <info@xxxxxxxxx>+ *+ * This program is free software; you can redistribute it and/or modify+ * it under the terms of the GNU General Public License version 2 as+ * published by the Free Software Foundation.+ */++#include <linux/module.h>+#include <linux/init.h>+#include <linux/platform_device.h>+#include <linux/slab.h>+#include <linux/dma-mapping.h>+#include <linux/interrupt.h>++#include <sound/core.h>+#include <sound/pcm.h>+#include <sound/pcm_params.h>+#include <sound/soc.h>++#include <asm/dma.h>+#include <variant/dmac.h>++#include "s6000-pcm.h"++#define S6_PCM_PREALLOCATE_SIZE (96 * 1024)+#define S6_PCM_PREALLOCATE_MAX  (2048 * 1024)++static struct snd_pcm_hardware s6000_pcm_hardware = {+	.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |+		 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |+		 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_JOINT_DUPLEX),+	.formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE),+	.rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \+		  SNDRV_PCM_RATE_8000_192000),+	.rate_min = 0,+	.rate_max = 1562500,+	.channels_min = 2,+	.channels_max = 8,+	.buffer_bytes_max = 0x7ffffff0,+	.period_bytes_min = 16,+	.period_bytes_max = 0xfffff0,+	.periods_min = 2,+	.periods_max = 1024, /* no limit */+	.fifo_size = 0,+};++struct s6000_runtime_data {+	spinlock_t lock;+	int period;		/* current DMA period */+};++static void s6000_pcm_enqueue_dma(struct snd_pcm_substream *substream)+{+	struct snd_pcm_runtime *runtime = substream->runtime;+	struct s6000_runtime_data *prtd = runtime->private_data;+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;+	struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;+	int channel;+	unsigned int period_size;+	unsigned int dma_offset;+	dma_addr_t dma_pos;+	dma_addr_t src, dst;++	period_size = snd_pcm_lib_period_bytes(substream);+	dma_offset = prtd->period * period_size;+	dma_pos = runtime->dma_addr + dma_offset;++	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {+		src = dma_pos;+		dst = par->sif_out;+		channel = par->dma_out;+	} else {+		src = par->sif_in;+		dst = dma_pos;+		channel = par->dma_in;+	}++	if (!s6dmac_channel_enabled(DMA_MASK_DMAC(channel),+				    DMA_INDEX_CHNL(channel)))+		return;++	if (s6dmac_fifo_full(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel))) {+		printk(KERN_ERR "s6000-pcm: fifo full\n");+		return;+	}++	BUG_ON(period_size & 15);+	s6dmac_put_fifo(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel),+			src, dst, period_size);++	prtd->period++;+	if (unlikely(prtd->period >= runtime->periods))+		prtd->period = 0;+}++static irqreturn_t s6000_pcm_irq(int irq, void *data)+{+	struct snd_pcm *pcm = data;+	struct snd_soc_pcm_runtime *runtime = pcm->private_data;+	struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data;+	struct s6000_runtime_data *prtd;+	unsigned int has_xrun;+	int i, ret = IRQ_NONE;+	u32 channel[2] = {+		[SNDRV_PCM_STREAM_PLAYBACK] = params->dma_out,+		[SNDRV_PCM_STREAM_CAPTURE] = params->dma_in+	};++	has_xrun = params->check_xrun(runtime->dai->cpu_dai);++	for (i = 0; i < ARRAY_SIZE(channel); ++i) {+		struct snd_pcm_substream *substream = pcm->streams[i].substream;+		unsigned int pending;++		if (!channel[i])+			continue;++		if (unlikely(has_xrun & (1 << i)) &&+		    substream->runtime &&+		    snd_pcm_running(substream)) {+			dev_dbg(pcm->dev, "xrun\n");+			snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);+			ret = IRQ_HANDLED;+		}++		pending = s6dmac_int_sources(DMA_MASK_DMAC(channel[i]),+					     DMA_INDEX_CHNL(channel[i]));++		if (pending & 1) {+			ret = IRQ_HANDLED;+			if (likely(substream->runtime &&+				   snd_pcm_running(substream))) {+				snd_pcm_period_elapsed(substream);+				dev_dbg(pcm->dev, "period elapsed %x %x\n",+				       s6dmac_cur_src(DMA_MASK_DMAC(channel[i]),+						   DMA_INDEX_CHNL(channel[i])),+				       s6dmac_cur_dst(DMA_MASK_DMAC(channel[i]),+						   DMA_INDEX_CHNL(channel[i])));+				prtd = substream->runtime->private_data;+				spin_lock(&prtd->lock);+				s6000_pcm_enqueue_dma(substream);+				spin_unlock(&prtd->lock);+			}+		}++		if (unlikely(pending & ~7)) {+			if (pending & (1 << 3))+				printk(KERN_WARNING+				       "s6000-pcm: DMA %x Underflow\n",+				       channel[i]);+			if (pending & (1 << 4))+				printk(KERN_WARNING+				       "s6000-pcm: DMA %x Overflow\n",+				       channel[i]);+			if (pending & 0x1e0)+				printk(KERN_WARNING+				       "s6000-pcm: DMA %x Master Error "+				       "(mask %x)\n",+				       channel[i], pending >> 5);++		}+	}++	return ret;+}++static int s6000_pcm_start(struct snd_pcm_substream *substream)+{+	struct s6000_runtime_data *prtd = substream->runtime->private_data;+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;+	struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;+	unsigned long flags;+	int srcinc;+	u32 dma;++	spin_lock_irqsave(&prtd->lock, flags);++	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {+		srcinc = 1;+		dma = par->dma_out;+	} else {+		srcinc = 0;+		dma = par->dma_in;+	}+	s6dmac_enable_chan(DMA_MASK_DMAC(dma), DMA_INDEX_CHNL(dma),+			   1 /* priority 1 (0 is max) */,+			   0 /* peripheral requests w/o xfer length mode */,+			   srcinc /* source address increment */,+			   srcinc^1 /* destination address increment */,+			   0 /* chunksize 0 (skip impossible on this dma) */,+			   0 /* source skip after chunk (impossible) */,+			   0 /* destination skip after chunk (impossible) */,+			   4 /* 16 byte burst size */,+			   -1 /* don't conserve bandwidth */,+			   0 /* low watermark irq descriptor theshold */,+			   0 /* disable hardware timestamps */,+			   1 /* enable channel */);++	s6000_pcm_enqueue_dma(substream);+	s6000_pcm_enqueue_dma(substream);++	spin_unlock_irqrestore(&prtd->lock, flags);++	return 0;+}++static int s6000_pcm_stop(struct snd_pcm_substream *substream)+{+	struct s6000_runtime_data *prtd = substream->runtime->private_data;+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;+	struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;+	unsigned long flags;+	u32 channel;++	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)+		channel = par->dma_out;+	else+		channel = par->dma_in;++	s6dmac_set_terminal_count(DMA_MASK_DMAC(channel),+				  DMA_INDEX_CHNL(channel), 0);++	spin_lock_irqsave(&prtd->lock, flags);++	s6dmac_disable_chan(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel));++	spin_unlock_irqrestore(&prtd->lock, flags);++	return 0;+}++static int s6000_pcm_trigger(struct snd_pcm_substream *substream, int cmd)+{+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;+	struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;+	int ret;++	ret = par->trigger(substream, cmd, 0);+	if (ret < 0)+		return ret;++	switch (cmd) {+	case SNDRV_PCM_TRIGGER_START:+	case SNDRV_PCM_TRIGGER_RESUME:+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:+		ret = s6000_pcm_start(substream);+		break;+	case SNDRV_PCM_TRIGGER_STOP:+	case SNDRV_PCM_TRIGGER_SUSPEND:+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:+		ret = s6000_pcm_stop(substream);+		break;+	default:+		ret = -EINVAL;+	}+	if (ret < 0)+		return ret;++	return par->trigger(substream, cmd, 1);+}++static int s6000_pcm_prepare(struct snd_pcm_substream *substream)+{+	struct s6000_runtime_data *prtd = substream->runtime->private_data;++	prtd->period = 0;++	return 0;+}++static snd_pcm_uframes_t s6000_pcm_pointer(struct snd_pcm_substream *substream)+{+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;+	struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;+	struct snd_pcm_runtime *runtime = substream->runtime;+	struct s6000_runtime_data *prtd = runtime->private_data;+	unsigned long flags;+	unsigned int offset;+	dma_addr_t count;++	spin_lock_irqsave(&prtd->lock, flags);++	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)+		count = s6dmac_cur_src(DMA_MASK_DMAC(par->dma_out),+				       DMA_INDEX_CHNL(par->dma_out));+	else+		count = s6dmac_cur_dst(DMA_MASK_DMAC(par->dma_in),+				       DMA_INDEX_CHNL(par->dma_in));++	count -= runtime->dma_addr;++	spin_unlock_irqrestore(&prtd->lock, flags);++	offset = bytes_to_frames(runtime, count);+	if (unlikely(offset >= runtime->buffer_size))+		offset = 0;++	return offset;+}++static int s6000_pcm_open(struct snd_pcm_substream *substream)+{+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;+	struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;+	struct snd_pcm_runtime *runtime = substream->runtime;+	struct s6000_runtime_data *prtd;+	int ret;++	snd_soc_set_runtime_hwparams(substream, &s6000_pcm_hardware);++	ret = snd_pcm_hw_constraint_step(runtime, 0,+					 SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 16);+	if (ret < 0)+		return ret;+	ret = snd_pcm_hw_constraint_step(runtime, 0,+					 SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16);+	if (ret < 0)+		return ret;+	ret = snd_pcm_hw_constraint_integer(runtime,+					    SNDRV_PCM_HW_PARAM_PERIODS);+	if (ret < 0)+		return ret;++	if (par->same_rate) {+		int rate;+		spin_lock(&par->lock); /* needed? */+		rate = par->rate;+		spin_unlock(&par->lock);+		if (rate != -1) {+			ret = snd_pcm_hw_constraint_minmax(runtime,+							SNDRV_PCM_HW_PARAM_RATE,+							rate, rate);+			if (ret < 0)+				return ret;+		}+	}++	prtd = kzalloc(sizeof(struct s6000_runtime_data), GFP_KERNEL);+	if (prtd == NULL)+		return -ENOMEM;++	spin_lock_init(&prtd->lock);++	runtime->private_data = prtd;++	return 0;+}++static int s6000_pcm_close(struct snd_pcm_substream *substream)+{+	struct snd_pcm_runtime *runtime = substream->runtime;+	struct s6000_runtime_data *prtd = runtime->private_data;++	kfree(prtd);++	return 0;+}++static int s6000_pcm_hw_params(struct snd_pcm_substream *substream,+				 struct snd_pcm_hw_params *hw_params)+{+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;+	struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;+	int ret;+	ret = snd_pcm_lib_malloc_pages(substream,+				       params_buffer_bytes(hw_params));+	if (ret < 0) {+		printk(KERN_WARNING "s6000-pcm: allocation of memory failed\n");+		return ret;+	}++	if (par->same_rate) {+		spin_lock(&par->lock);+		if (par->rate == -1 ||+		    !(par->in_use & ~(1 << substream->stream))) {+			par->rate = params_rate(hw_params);+			par->in_use |= 1 << substream->stream;+		} else if (params_rate(hw_params) != par->rate) {+			snd_pcm_lib_free_pages(substream);+			par->in_use &= ~(1 << substream->stream);+			ret = -EBUSY;+		}+		spin_unlock(&par->lock);+	}+	return ret;+}++static int s6000_pcm_hw_free(struct snd_pcm_substream *substream)+{+	struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;+	struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;++	spin_lock(&par->lock);+	par->in_use &= ~(1 << substream->stream);+	if (!par->in_use)+		par->rate = -1;+	spin_unlock(&par->lock);++	return snd_pcm_lib_free_pages(substream);+}++static struct snd_pcm_ops s6000_pcm_ops = {+	.open = 	s6000_pcm_open,+	.close = 	s6000_pcm_close,+	.ioctl = 	snd_pcm_lib_ioctl,+	.hw_params = 	s6000_pcm_hw_params,+	.hw_free = 	s6000_pcm_hw_free,+	.trigger =	s6000_pcm_trigger,+	.prepare = 	s6000_pcm_prepare,+	.pointer = 	s6000_pcm_pointer,+};++static void s6000_pcm_free(struct snd_pcm *pcm)+{+	struct snd_soc_pcm_runtime *runtime = pcm->private_data;+	struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data;++	free_irq(params->irq, pcm);+	snd_pcm_lib_preallocate_free_for_all(pcm);+}++static u64 s6000_pcm_dmamask = DMA_32BIT_MASK;++static int s6000_pcm_new(struct snd_card *card,+			 struct snd_soc_dai *dai, struct snd_pcm *pcm)+{+	struct snd_soc_pcm_runtime *runtime = pcm->private_data;+	struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data;+	int res;++	if (!card->dev->dma_mask)+		card->dev->dma_mask = &s6000_pcm_dmamask;+	if (!card->dev->coherent_dma_mask)+		card->dev->coherent_dma_mask = DMA_32BIT_MASK;++	if (params->dma_in) {+		s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_in),+				    DMA_INDEX_CHNL(params->dma_in));+		s6dmac_int_sources(DMA_MASK_DMAC(params->dma_in),+				   DMA_INDEX_CHNL(params->dma_in));+	}++	if (params->dma_out) {+		s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_out),+				    DMA_INDEX_CHNL(params->dma_out));+		s6dmac_int_sources(DMA_MASK_DMAC(params->dma_out),+				   DMA_INDEX_CHNL(params->dma_out));+	}++	res = request_irq(params->irq, s6000_pcm_irq, IRQF_SHARED,+			  s6000_soc_platform.name, pcm);+	if (res) {+		printk(KERN_ERR "s6000-pcm couldn't get IRQ\n");+		return res;+	}++	res = snd_pcm_lib_preallocate_pages_for_all(pcm,+						    SNDRV_DMA_TYPE_DEV,+						    card->dev,+						    S6_PCM_PREALLOCATE_SIZE,+						    S6_PCM_PREALLOCATE_MAX);+	if (res)+		printk(KERN_WARNING "s6000-pcm: preallocation failed\n");++	spin_lock_init(&params->lock);+	params->in_use = 0;+	params->rate = -1;+	return 0;+}++struct snd_soc_platform s6000_soc_platform = {+	.name = 	"s6000-audio",+	.pcm_ops = 	&s6000_pcm_ops,+	.pcm_new = 	s6000_pcm_new,+	.pcm_free = 	s6000_pcm_free,+};+EXPORT_SYMBOL_GPL(s6000_soc_platform);++static int __init s6000_pcm_init(void)+{+	return snd_soc_register_platform(&s6000_soc_platform);+}+module_init(s6000_pcm_init);++static void __exit s6000_pcm_exit(void)+{+	snd_soc_unregister_platform(&s6000_soc_platform);+}+module_exit(s6000_pcm_exit);++MODULE_AUTHOR("Daniel Gloeckner");+MODULE_DESCRIPTION("Stretch s6000 family PCM DMA module");+MODULE_LICENSE("GPL");diff --git a/sound/soc/s6000/s6000-pcm.h b/sound/soc/s6000/s6000-pcm.hnew file mode 100644index 0000000..96f23f6--- /dev/null+++ b/sound/soc/s6000/s6000-pcm.h@@ -0,0 +1,35 @@+/*+ * ALSA PCM interface for the Stretch s6000 family+ *+ * Author:      Daniel Gloeckner, <dg@xxxxxxxxx>+ * Copyright:   (C) 2009 emlix GmbH <info@xxxxxxxxx>+ *+ * This program is free software; you can redistribute it and/or modify+ * it under the terms of the GNU General Public License version 2 as+ * published by the Free Software Foundation.+ */++#ifndef _S6000_PCM_H+#define _S6000_PCM_H++struct snd_soc_dai;+struct snd_pcm_substream;++struct s6000_pcm_dma_params {+	unsigned int (*check_xrun)(struct snd_soc_dai *cpu_dai);+	int (*trigger)(struct snd_pcm_substream *substream, int cmd, int after);+	dma_addr_t sif_in;+	dma_addr_t sif_out;+	u32 dma_in;+	u32 dma_out;+	int irq;+	int same_rate;++	spinlock_t lock;+	int in_use;+	int rate;+};++extern struct snd_soc_platform s6000_soc_platform;++#endif-- 1.6.2.2
_______________________________________________Alsa-devel mailing listAlsa-devel@xxxxxxxxxxxxxxxxxxxx://mailman.alsa-project.org/mailman/listinfo/alsa-devel

[Index of Archives]     [ALSA User]     [Linux Audio Users]     [Kernel Archive]     [Asterisk PBX]     [Photo Sharing]     [Linux Sound]     [Video 4 Linux]     [Gimp]     [Yosemite News]

  Powered by Linux