[patch 1/2] alsa: ASoC driver for s6000 I2S interface

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

 



This patch adds a driver for the I2S interface found on Stretch s6000family processors.
Signed-off-by: Daniel Glöckner <dg@xxxxxxxxx>--- sound/soc/Kconfig           |    1 + sound/soc/Makefile          |    1 + sound/soc/s6000/Kconfig     |   10 + sound/soc/s6000/Makefile    |    6 + sound/soc/s6000/s6000-i2s.c |  421 +++++++++++++++++++++++++++++++++++ sound/soc/s6000/s6000-i2s.h |  167 ++++++++++++++ sound/soc/s6000/s6000-pcm.c |  518 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/s6000/s6000-pcm.h |   41 ++++ 8 files changed, 1165 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 ef025c6..56e5198 100644--- a/sound/soc/Kconfig+++ b/sound/soc/Kconfig@@ -31,6 +31,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 86a9b1f..6b33d80 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..5612ed2--- /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 && SND_SOC+	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..719a3c4--- /dev/null+++ b/sound/soc/s6000/s6000-i2s.c@@ -0,0 +1,421 @@+/*+ * 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 <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"++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 __devinit s6000_i2s_probe(struct platform_device *pdev,+				     struct snd_soc_dai *dai)+{+	struct s6000_i2s_dev *dev;+	struct resource *scbmem, *sifmem, *region, *dma_in, *dma_out;+	struct s6000_snd_platform_data *pdata;+	u8 __iomem *mmio;+	int ret;++	pdata = pdev->dev.platform_data;++	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;+	}++	dma_in = platform_get_resource(pdev, IORESOURCE_DMA, 0);+	if (!dma_in) {+		dev_err(&pdev->dev, "no dma resource?\n");+		ret = -ENODEV;+		goto err_release_sif;+	}++	region = request_mem_region(dma_in->start,+				    dma_in->end - dma_in->start + 1,+				    pdev->name);+	if (!region) {+		dev_err(&pdev->dev, "I2S DMA region already claimed\n");+		ret = -EBUSY;+		goto err_release_sif;+	}++	dma_out = 0;+	if (pdata->lines_out) {+		if (pdata->lines_in) {+			dma_out = platform_get_resource(pdev, IORESOURCE_DMA,+							1);+			if (!dma_out) {+				dev_err(&pdev->dev,+					"no second dma resource?\n");+				ret = -ENODEV;+				goto err_release_dma1;+			}++			region = request_mem_region(dma_out->start,+						    dma_out->end+						     - dma_out->start + 1,+						    pdev->name);+			if (!region) {+				dev_err(&pdev->dev,+					"I2S DMA region already claimed\n");+				ret = -EBUSY;+				goto err_release_dma1;+			}+		} else {+			dma_out = dma_in;+			dma_in = 0;+		}+	}++	dev = kzalloc(sizeof(struct s6000_i2s_dev), GFP_KERNEL);+	if (!dev) {+		ret = -ENOMEM;+		goto err_release_dma2;+	}++	dai->private_data = dev;+	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->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;+		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};++		dev->lines_in = (dev->lines_in != 0);+		dev->lines_out = (dev->lines_out != 0);+		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;++		cfg[dev->channel_in] = S6_I2S_IN;+		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]);+	}++	dev->dma_params.dma_in = dma_in ? dma_in->start : 0;+	dev->dma_params.dma_out = dma_out ? dma_out->start : 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;++	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);++	return 0;++err_release_dev:+	kfree(dev);+err_release_dma2:+	if (dma_out)+		release_mem_region(dma_out->start,+				   dma_out->end - dma_out->start + 1);+err_release_dma1:+	if (dma_in)+		release_mem_region(dma_in->start,+				   dma_in->end - dma_in->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 snd_soc_dai *dai)+{+	struct s6000_i2s_dev *dev = dai->private_data;+	struct resource *region;+	void __iomem *mmio = dev->scbbase;++	s6000_i2s_stop_channel(dev, 0);+	s6000_i2s_stop_channel(dev, 1);+	kfree(dev);+	dai->private_data = 0;++	s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);++	region = platform_get_resource(pdev, IORESOURCE_DMA, 0);+	release_region(region->start, region->end - region->start + 1);++	if (dev->lines_in && dev->lines_out) {+		region = platform_get_resource(pdev, IORESOURCE_DMA, 1);+		release_region(region->start, region->end - region->start + 1);+	}++	region = platform_get_resource(pdev, IORESOURCE_MEM, 0);+	release_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);+}++#define S6000_I2S_RATES	(SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT | \+			 SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000)+#define S6000_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)++struct snd_soc_dai s6000_i2s_dai = {+	.name = "s6000-i2s",+	.id = 0,+	.probe = s6000_i2s_probe,+	.remove = __devexit_p(s6000_i2s_remove),+	.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 = {+		.set_fmt = s6000_i2s_set_dai_fmt,+		.set_clkdiv = s6000_i2s_set_clkdiv,+		.hw_params = s6000_i2s_hw_params,+	}+}+EXPORT_SYMBOL_GPL(s6000_i2s_dai);++static int __init s6000_i2s_init(void)+{+	return snd_soc_register_dai(&s6000_i2s_dai);+}+module_init(s6000_i2s_init);++static void __exit s6000_i2s_exit(void)+{+	snd_soc_unregister_dai(&s6000_i2s_dai);+}+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..4918f95--- /dev/null+++ b/sound/soc/s6000/s6000-i2s.h@@ -0,0 +1,167 @@+/*+ * 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++#include <linux/io.h>+#include "s6000-pcm.h"++extern struct snd_soc_dai s6000_i2s_dai;++struct s6000_i2s_dev {+	dma_addr_t sifbase;+	u8 __iomem *scbbase;+	int wide;+	int channel_in;+	int channel_out;+	int lines_in;+	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 inline 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 inline 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 inline 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 inline 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 inline 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;+}+#endifdiff --git a/sound/soc/s6000/s6000-pcm.c b/sound/soc/s6000/s6000-pcm.cnew file mode 100644index 0000000..90b546c--- /dev/null+++ b/sound/soc/s6000/s6000-pcm.c@@ -0,0 +1,518 @@+/*+ * 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-i2s.h"+#include "s6000-pcm.h"++#define S6000_PCM_DEBUG 0+#if S6000_PCM_DEBUG+#define DPRINTK(x...) printk(KERN_DEBUG x)+#else+#define DPRINTK(x...)+#endif++#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_KNOT |+		  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 snd_pcm_substream *playback, *capture;+	struct s6000_runtime_data *prtd;+	unsigned int i2s_errors;+	int i, ret = IRQ_NONE;+	u32 channel[2] = {+		[SNDRV_PCM_STREAM_PLAYBACK] = params->dma_out,+		[SNDRV_PCM_STREAM_CAPTURE] = params->dma_in+	};++	i2s_errors = s6000_i2s_int_sources(runtime->dai->cpu_dai->private_data);+	playback = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;+	capture = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;++	if (unlikely(i2s_errors & S6_I2S_INT_ALIGNMENT)) {+		printk(KERN_ERR "s6000-i2s: Alignment Error\n");+		ret = IRQ_HANDLED;+	}++	if (unlikely(i2s_errors & S6_I2S_INT_UNDERRUN) &&+	    playback->runtime &&+	    snd_pcm_running(playback)) {+		printk(KERN_WARNING "s6000-i2s: Tx Underrun\n");+		s6000_i2s_start(playback);++		prtd = playback->runtime->private_data;+		spin_lock(&prtd->lock);+		s6000_pcm_enqueue_dma(playback);+		/* a second descriptor will be queued below */+		spin_unlock(&prtd->lock);++		ret = IRQ_HANDLED;+	}++	for (i = 0; i < 2; ++i) {+		struct snd_pcm_substream *substream = pcm->streams[i].substream;+		unsigned int pending;++		if (!channel[i])+			continue;++		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);+				DPRINTK("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);++		}+	}++	if (unlikely(i2s_errors & S6_I2S_INT_OVERRUN) &&+	    capture->runtime &&+	    snd_pcm_running(capture)) {+		printk(KERN_WARNING "s6000-i2s: Rx Overrun\n");++		prtd = capture->runtime->private_data;+		spin_lock(&prtd->lock);+		s6000_pcm_enqueue_dma(capture);+		spin_unlock(&prtd->lock);++		s6000_i2s_start(capture);+		ret = IRQ_HANDLED;+	}++	return ret;+}++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;++	spin_lock_irqsave(&prtd->lock, flags);++	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)+		s6dmac_enable_chan(DMA_MASK_DMAC(par->dma_out),+				   DMA_INDEX_CHNL(par->dma_out),+				   1, 0, 1, 0, 0, 0, 0, 4, -1, 0, 0, 1);+	else+		s6dmac_enable_chan(DMA_MASK_DMAC(par->dma_in),+				   DMA_INDEX_CHNL(par->dma_in),+				   1, 0, 0, 1, 0, 0, 0, 4, -1, 0, 0, 1);++	s6000_pcm_enqueue_dma(substream);+	s6000_pcm_enqueue_dma(substream);++	spin_unlock_irqrestore(&prtd->lock, flags);++	return 0;+}++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)+{+	int ret = 0;+	switch (cmd) {+	case SNDRV_PCM_TRIGGER_START:+	case SNDRV_PCM_TRIGGER_RESUME:+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {+			s6000_i2s_start(substream);+			ret = s6000_pcm_start(substream);+		} else {+			ret = s6000_pcm_start(substream);+			s6000_i2s_start(substream);+		}+		break;+	case SNDRV_PCM_TRIGGER_STOP:+	case SNDRV_PCM_TRIGGER_SUSPEND:+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:+		s6000_i2s_stop(substream);+		ret = s6000_pcm_stop(substream);+		break;+	default:+		ret = -EINVAL;+	}++	return ret;+}++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);+}++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..8d7305f--- /dev/null+++ b/sound/soc/s6000/s6000-pcm.h@@ -0,0 +1,41 @@+/*+ * 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 s6000_pcm_dma_params {+	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;+};++struct s6000_snd_platform_data {+	int lines_in;+	int lines_out;+	int channel_in;+	int channel_out;+	int wide;+	int same_rate;+};++int s6000_pcm_start(struct snd_pcm_substream *substream);+int s6000_pcm_stop(struct snd_pcm_substream *substream);+extern struct snd_soc_platform s6000_soc_platform;++#endif-- 1.6.2.107.ge47ee
_______________________________________________Alsa-devel mailing listAlsa-devel@xxxxxxxxxxxxxxxxxxxx://mailman.alsa-project.org/mailman/listinfo/alsa-devel

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

  Powered by Linux