ASoC: Add driver for s6000 I2S interface
authorDaniel Glöckner <dg@emlix.com>
Sat, 28 Mar 2009 18:47:01 +0000 (19:47 +0100)
committerMark Brown <broonie@opensource.wolfsonmicro.com>
Sat, 4 Apr 2009 14:28:22 +0000 (15:28 +0100)
This patch adds a driver for the I2S interface found on Stretch s6000
family processors.

Signed-off-by: Daniel Glöckner <dg@emlix.com>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
sound/soc/Kconfig
sound/soc/Makefile
sound/soc/s6000/Kconfig [new file with mode: 0644]
sound/soc/s6000/Makefile [new file with mode: 0644]
sound/soc/s6000/s6000-i2s.c [new file with mode: 0644]
sound/soc/s6000/s6000-i2s.h [new file with mode: 0644]
sound/soc/s6000/s6000-pcm.c [new file with mode: 0644]
sound/soc/s6000/s6000-pcm.h [new file with mode: 0644]

index 3d2bb6f..3304f9d 100644 (file)
@@ -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 codecs
index 0237879..8943a14 100644 (file)
@@ -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/Kconfig
new file mode 100644 (file)
index 0000000..4bfc8bc
--- /dev/null
@@ -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
+       tristate
diff --git a/sound/soc/s6000/Makefile b/sound/soc/s6000/Makefile
new file mode 100644 (file)
index 0000000..df15f87
--- /dev/null
@@ -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.o
diff --git a/sound/soc/s6000/s6000-i2s.c b/sound/soc/s6000/s6000-i2s.c
new file mode 100644 (file)
index 0000000..dcc7904
--- /dev/null
@@ -0,0 +1,629 @@
+/*
+ * ALSA SoC I2S Audio Layer for the Stretch S6000 family
+ *
+ * Author:      Daniel Gloeckner, <dg@emlix.com>
+ * Copyright:   (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * 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.h
new file mode 100644 (file)
index 0000000..2375fdf
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * ALSA SoC I2S Audio Layer for the Stretch s6000 family
+ *
+ * Author:      Daniel Gloeckner, <dg@emlix.com>
+ * Copyright:   (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * 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;
+};
+#endif
diff --git a/sound/soc/s6000/s6000-pcm.c b/sound/soc/s6000/s6000-pcm.c
new file mode 100644 (file)
index 0000000..83b8028
--- /dev/null
@@ -0,0 +1,497 @@
+/*
+ * ALSA PCM interface for the Stetch s6000 family
+ *
+ * Author:      Daniel Gloeckner, <dg@emlix.com>
+ * Copyright:   (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * 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.h
new file mode 100644 (file)
index 0000000..96f23f6
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * ALSA PCM interface for the Stretch s6000 family
+ *
+ * Author:      Daniel Gloeckner, <dg@emlix.com>
+ * Copyright:   (C) 2009 emlix GmbH <info@emlix.com>
+ *
+ * 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