This patch adds the ALSA I2S driver of the timberdale FPGA.

It has been submitted to kernel.org but not yet accepted. It has been
in previous versions of moblin.

Signed-off-by: Richard Röjfors <[email protected]>
---
diff -Naur linux-2.6.35.org/sound/drivers/Kconfig 
linux-2.6.35/sound/drivers/Kconfig
--- linux-2.6.35.org/sound/drivers/Kconfig      2010-08-01 22:11:14.000000000 
+0000
+++ linux-2.6.35/sound/drivers/Kconfig  2010-11-11 12:35:33.407408000 +0000
@@ -182,4 +182,14 @@
          The default time-out value in seconds for AC97 automatic
          power-save mode.  0 means to disable the power-save mode.
 
+config SND_TIMBERDALE_I2S
+       tristate "The timberdale FPGA I2S driver"
+       depends on MFD_TIMBERDALE && HAS_IOMEM
+       default y
+       help
+         Say Y here to enable driver for the I2S block found within the
+         Timberdale FPGA.
+         There is support for up to 8 I2S channels, in either transmitter
+         or receiver mode.
+
 endif  # SND_DRIVERS
diff -Naur linux-2.6.35.org/sound/drivers/Makefile 
linux-2.6.35/sound/drivers/Makefile
--- linux-2.6.35.org/sound/drivers/Makefile     2010-08-01 18:11:14.000000000 
-0400
+++ linux-2.6.35/sound/drivers/Makefile 2010-11-18 08:48:36.000000000 -0500
@@ -10,6 +10,7 @@
 snd-serial-u16550-objs := serial-u16550.o
 snd-virmidi-objs := virmidi.o
 snd-ml403-ac97cr-objs := ml403-ac97cr.o pcm-indirect2.o
+snd-timbi2s-objs := timbi2s.o
 
 # Toplevel Module Dependency
 obj-$(CONFIG_SND_DUMMY) += snd-dummy.o
@@ -19,5 +20,6 @@
 obj-$(CONFIG_SND_MTS64) += snd-mts64.o
 obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o
 obj-$(CONFIG_SND_ML403_AC97CR) += snd-ml403-ac97cr.o
+obj-$(CONFIG_SND_TIMBERDALE_I2S) += snd-timbi2s.o
 
 obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/
diff -Naur linux-2.6.35.org/sound/drivers/timbi2s.c 
linux-2.6.35/sound/drivers/timbi2s.c
--- linux-2.6.35.org/sound/drivers/timbi2s.c    1970-01-01 00:00:00.000000000 
+0000
+++ linux-2.6.35/sound/drivers/timbi2s.c        2010-11-11 12:32:05.751408000 
+0000
@@ -0,0 +1,763 @@
+/*
+ * timbi2s.c timberdale FPGA I2S driver
+ * Copyright (c) 2009 Intel Corporation
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Supports:
+ * Timberdale FPGA I2S
+ *
+ */
+
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/timbi2s.h>
+
+static int index = SNDRV_DEFAULT_IDX1;
+module_param(index, int, 0444);
+MODULE_PARM_DESC(index, "Index value for Timberdale I2S.");
+
+#define DRIVER_NAME "timb-i2s"
+
+#define MAX_BUSSES     10
+
+#define TIMBI2S_REG_VER        0x00
+#define TIMBI2S_REG_UIR        0x04
+
+#define TIMBI2S_BUS_PRESCALE   0x00
+#define TIMBI2S_BUS_ICLR       0x04
+#define TIMBI2S_BUS_IPR                0x08
+#define TIMBI2S_BUS_ISR                0x0c
+#define TIMBI2S_BUS_IER                0x10
+
+
+#define TIMBI2S_IRQ_TX_FULL            0x01
+#define TIMBI2S_IRQ_TX_ALMOST_FULL     0x02
+#define TIMBI2S_IRQ_TX_ALMOST_EMPTY    0x04
+#define TIMBI2S_IRQ_TX_EMPTY           0x08
+
+#define TIMBI2S_IRQ_RX_FULL            0x01
+#define TIMBI2S_IRQ_RX_ALMOST_FULL     0x02
+#define TIMBI2S_IRQ_RX_ALMOST_EMPTY    0x04
+#define TIMBI2S_IRQ_RX_NOT_EMPTY       0x08
+
+#define TIMBI2S_BUS_ICOR       0x14
+#define TIMBI2S_ICOR_TX_ENABLE 0x00000001
+#define TIMBI2S_ICOR_RX_ENABLE 0x00000002
+#define TIMBI2S_ICOR_LFIFO_RST 0x00000004
+#define TIMBI2S_ICOR_RFIFO_RST 0x00000008
+#define TIMBI2S_ICOR_FIFO_RST (TIMBI2S_ICOR_LFIFO_RST | TIMBI2S_ICOR_RFIFO_RST)
+#define TIMBI2S_ICOR_SOFT_RST  0x00000010
+#define TIMBI2S_ICOR_WORD_SEL_LEFT_SHIFT       8
+#define TIMBI2S_ICOR_WORD_SEL_LEFT_MASK                (0xff << 8)
+#define TIMBI2S_ICOR_WORD_SEL_RIGHT_SHIFT      16
+#define TIMBI2S_ICOR_WORD_SEL_RIGHT_MASK       (0xff << 16)
+#define TIMBI2S_ICOR_CLK_MASTER        0x10000000
+#define TIMBI2S_ICOR_RX_ID     0x20000000
+#define TIMBI2S_ICOR_TX_ID     0x40000000
+#define TIMBI2S_ICOR_WORD_SEL  0x80000000
+#define TIMBI2S_BUS_FIFO       0x18
+
+#define TIMBI2S_BUS_REG_AREA_SIZE      (TIMBI2S_BUS_FIFO - \
+       TIMBI2S_BUS_PRESCALE + 4)
+#define TIMBI2S_FIRST_BUS_AREA_OFS     0x08
+
+struct timbi2s_bus {
+       u32 flags;
+       u32 prescale;
+       struct snd_pcm  *pcm;
+       struct snd_card *card;
+       struct snd_pcm_substream *substream;
+       unsigned        buf_pos;
+       spinlock_t      lock; /* mutual exclusion */
+       u16             sample_rate;
+};
+
+#define BUS_RX         0x200
+#define BUS_MASTER     0x100
+#define BUS_INDEX_MASK 0xff
+#define BUS_INDEX(b) ((b)->flags & BUS_INDEX_MASK)
+#define BUS_IS_MASTER(b) ((b)->flags & BUS_MASTER)
+#define BUS_IS_RX(b) ((b)->flags & BUS_RX)
+
+#define SET_BUS_INDEX(b, id) ((b)->flags = ((b)->flags & ~BUS_INDEX_MASK) | id)
+#define SET_BUS_MASTER(b) ((b)->flags |= BUS_MASTER)
+#define SET_BUS_RX(b) ((b)->flags |= BUS_RX)
+
+#define TIMBI2S_BUS_OFFSET(bus) (TIMBI2S_FIRST_BUS_AREA_OFS + \
+       TIMBI2S_BUS_REG_AREA_SIZE * BUS_INDEX(bus))
+
+struct timbi2s {
+       void __iomem *membase;
+       int irq;
+       struct tasklet_struct   tasklet;
+       u32 main_clk;
+       unsigned num_busses;
+       struct timbi2s_bus busses[0];
+};
+
+#define BITS_PER_CHANNEL       16
+#define NUM_CHANNELS           2
+
+#define SAMPLE_SIZE    ((NUM_CHANNELS * BITS_PER_CHANNEL) / 8)
+#define NUM_PERIODS    32
+#define NUM_SAMPLES    256
+
+static struct snd_pcm_hardware timbi2s_rx_hw = {
+       .info                   = (SNDRV_PCM_INFO_MMAP
+                                 | SNDRV_PCM_INFO_MMAP_VALID
+                                 | SNDRV_PCM_INFO_INTERLEAVED),
+       .formats                = SNDRV_PCM_FMTBIT_S16_LE,
+       .rates                  = SNDRV_PCM_RATE_44100,
+       .rate_min               = 44100,
+       .rate_max               = 44100,
+       .channels_min           = 2, /* only stereo */
+       .channels_max           = 2,
+       .buffer_bytes_max       = NUM_PERIODS * SAMPLE_SIZE * NUM_SAMPLES,
+       .period_bytes_min       = SAMPLE_SIZE * NUM_SAMPLES,
+       .period_bytes_max       = SAMPLE_SIZE * NUM_SAMPLES,
+       .periods_min            = NUM_PERIODS,
+       .periods_max            = NUM_PERIODS,
+};
+
+static struct snd_pcm_hardware timbi2s_tx_hw = {
+       .info                   = (SNDRV_PCM_INFO_MMAP
+                                 | SNDRV_PCM_INFO_MMAP_VALID
+                                 | SNDRV_PCM_INFO_INTERLEAVED),
+       .formats                = SNDRV_PCM_FMTBIT_S16_LE,
+       .rates                  = SNDRV_PCM_RATE_44100,
+       .rate_min               = 44100,
+       .rate_max               = 44100,
+       .channels_min           = 2, /* only stereo */
+       .channels_max           = 2,
+       .buffer_bytes_max       = NUM_PERIODS * SAMPLE_SIZE * NUM_SAMPLES,
+       .period_bytes_min       = SAMPLE_SIZE * NUM_SAMPLES,
+       .period_bytes_max       = SAMPLE_SIZE * NUM_SAMPLES,
+       .periods_min            = NUM_PERIODS,
+       .periods_max            = NUM_PERIODS,
+};
+
+static inline void timbi2s_bus_write(struct timbi2s_bus *bus, u32 val, u32 reg)
+{
+       struct timbi2s *i2s = snd_pcm_chip(bus->card);
+
+       iowrite32(val, i2s->membase + TIMBI2S_BUS_OFFSET(bus) + reg);
+}
+
+static inline u32 timbi2s_bus_read(struct timbi2s_bus *bus, u32 reg)
+{
+       struct timbi2s *i2s = snd_pcm_chip(bus->card);
+
+       return ioread32(i2s->membase + TIMBI2S_BUS_OFFSET(bus) + reg);
+}
+
+static u32 timbi2s_calc_prescale(u32 main_clk, u32 sample_rate)
+{
+       u32 halfbit_rate = sample_rate * BITS_PER_CHANNEL * NUM_CHANNELS * 2;
+       return main_clk / halfbit_rate;
+}
+
+static int timbi2s_open(struct snd_pcm_substream *substream)
+{
+       struct timbi2s_bus *bus = snd_pcm_substream_chip(substream);
+       struct snd_card *card = bus->card;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       dev_dbg(snd_card_get_device_link(card),
+               "%s: Entry, substream: %p, bus: %d\n", __func__, substream,
+               BUS_INDEX(bus));
+
+       if (BUS_IS_RX(bus))
+               runtime->hw = timbi2s_rx_hw;
+       else
+               runtime->hw = timbi2s_tx_hw;
+
+       if (bus->sample_rate == 8000) {
+               runtime->hw.rates = SNDRV_PCM_RATE_8000;
+               runtime->hw.rate_min = 8000;
+               runtime->hw.rate_max = 8000;
+       }
+
+       bus->substream = substream;
+
+       return 0;
+}
+
+static int timbi2s_close(struct snd_pcm_substream *substream)
+{
+       struct timbi2s_bus *bus = snd_pcm_substream_chip(substream);
+       struct snd_card *card = bus->card;
+       dev_dbg(snd_card_get_device_link(card),
+               "%s: Entry, substream: %p, bus: %d\n", __func__, substream,
+               BUS_INDEX(bus));
+
+       bus->substream = NULL;
+
+       return 0;
+}
+
+static int timbi2s_hw_params(struct snd_pcm_substream *substream,
+               struct snd_pcm_hw_params *hw_params)
+{
+       struct timbi2s_bus *bus = snd_pcm_substream_chip(substream);
+       struct snd_card *card = bus->card;
+       struct timbi2s *i2s = snd_pcm_chip(card);
+       int err;
+
+       dev_dbg(snd_card_get_device_link(card),
+               "%s: Entry, substream: %p, bus: %d\n", __func__,
+               substream, BUS_INDEX(bus));
+
+       bus->prescale = timbi2s_calc_prescale(i2s->main_clk,
+                       params_rate(hw_params));
+
+       err = snd_pcm_lib_malloc_pages(substream,
+               params_buffer_bytes(hw_params));
+       if (err < 0)
+               return err;
+
+       dev_dbg(snd_card_get_device_link(card),
+               "%s: Rate: %d, format: %d\n", __func__, params_rate(hw_params),
+               params_format(hw_params));
+
+       return 0;
+}
+
+static int timbi2s_hw_free(struct snd_pcm_substream *substream)
+{
+       struct timbi2s_bus *bus = snd_pcm_substream_chip(substream);
+       struct snd_card *card = bus->card;
+       unsigned long flags;
+
+       dev_dbg(snd_card_get_device_link(card),
+               "%s: Entry, substream: %p\n", __func__, substream);
+
+       spin_lock_irqsave(&bus->lock, flags);
+       /* disable interrupts */
+       timbi2s_bus_write(bus, 0, TIMBI2S_BUS_IER);
+       spin_unlock_irqrestore(&bus->lock, flags);
+
+       /* disable TX and RX */
+       timbi2s_bus_write(bus, TIMBI2S_ICOR_FIFO_RST | TIMBI2S_ICOR_SOFT_RST,
+               TIMBI2S_BUS_ICOR);
+
+       return snd_pcm_lib_free_pages(substream);
+}
+
+static int timbi2s_prepare(struct snd_pcm_substream *substream)
+{
+       struct timbi2s_bus *bus = snd_pcm_substream_chip(substream);
+       struct snd_card *card = bus->card;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       u32 data;
+
+       dev_dbg(snd_card_get_device_link(card),
+               "%s: Entry, substream: %p, bus: %d, buffer: %d, period: %d\n",
+               __func__, substream,
+               BUS_INDEX(bus), (int)snd_pcm_lib_buffer_bytes(substream),
+               (int)snd_pcm_lib_period_bytes(substream));
+
+       if (runtime->dma_addr & 3 || runtime->buffer_size & 3) {
+               dev_err(snd_card_get_device_link(card),
+                       "%s: Only word aligned data allowed\n", __func__);
+               return -EINVAL;
+       }
+
+       if (runtime->channels != NUM_CHANNELS) {
+               dev_err(snd_card_get_device_link(card),
+                       "%s: Number of channels unsupported %d\n", __func__,
+                       runtime->channels);
+               return -EINVAL;
+       }
+
+       /* reset */
+       timbi2s_bus_write(bus, TIMBI2S_ICOR_FIFO_RST | TIMBI2S_ICOR_SOFT_RST,
+               TIMBI2S_BUS_ICOR);
+
+       /* only masters have prescaling, don't write if not needed */
+       if (BUS_IS_MASTER(bus))
+               timbi2s_bus_write(bus, bus->prescale, TIMBI2S_BUS_PRESCALE);
+
+       /* write word select */
+       data = ((BITS_PER_CHANNEL << TIMBI2S_ICOR_WORD_SEL_LEFT_SHIFT) &
+               TIMBI2S_ICOR_WORD_SEL_LEFT_MASK) |
+               ((BITS_PER_CHANNEL << TIMBI2S_ICOR_WORD_SEL_RIGHT_SHIFT) &
+               TIMBI2S_ICOR_WORD_SEL_RIGHT_MASK);
+       timbi2s_bus_write(bus, data, TIMBI2S_BUS_ICOR);
+
+       bus->buf_pos = 0;
+
+       return 0;
+}
+
+static int
+timbi2s_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       struct timbi2s_bus *bus = snd_pcm_substream_chip(substream);
+       struct snd_card *card = bus->card;
+       unsigned long flags;
+       u32 data;
+
+       dev_dbg(snd_card_get_device_link(card),
+               "%s: Entry, substream: %p, bus: %d, cmd: %d\n", __func__,
+               substream, BUS_INDEX(bus), cmd);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+               dev_dbg(snd_card_get_device_link(card),
+                       "%s: Got TRIGGER_START command\n", __func__);
+
+               /* start */
+               data = timbi2s_bus_read(bus, TIMBI2S_BUS_ICOR);
+               data |= TIMBI2S_ICOR_TX_ENABLE;
+               timbi2s_bus_write(bus, data, TIMBI2S_BUS_ICOR);
+
+               /* enable interrupts */
+               timbi2s_bus_write(bus, TIMBI2S_IRQ_TX_ALMOST_EMPTY,
+                       TIMBI2S_BUS_IER);
+               dev_dbg(snd_card_get_device_link(card),
+                       "%s: ISR: %x, ICOR: %x\n", __func__,
+                       timbi2s_bus_read(bus, TIMBI2S_BUS_ISR),
+                       timbi2s_bus_read(bus, TIMBI2S_BUS_ICOR));
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+               dev_dbg(snd_card_get_device_link(card),
+                       "%s: Got TRIGGER_STOP command\n", __func__);
+
+               spin_lock_irqsave(&bus->lock, flags);
+               /* disable interrupts */
+               timbi2s_bus_write(bus, 0, TIMBI2S_BUS_IER);
+               spin_unlock_irqrestore(&bus->lock, flags);
+
+               /* reset */
+               data = timbi2s_bus_read(bus, TIMBI2S_BUS_ICOR);
+               data &= ~TIMBI2S_ICOR_TX_ENABLE;
+
+               timbi2s_bus_write(bus, data, TIMBI2S_BUS_ICOR);
+               break;
+       default:
+               dev_dbg(snd_card_get_device_link(card),
+                       "%s: Got unsupported command\n", __func__);
+
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int
+timbi2s_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       struct timbi2s_bus *bus = snd_pcm_substream_chip(substream);
+       struct snd_card *card = bus->card;
+       unsigned long flags;
+
+       dev_dbg(snd_card_get_device_link(card),
+               "%s: Entry, substream: %p, bus: %d, cmd: %d\n", __func__,
+               substream, BUS_INDEX(bus), cmd);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+               dev_dbg(snd_card_get_device_link(card),
+                       "%s: Got TRIGGER_START command\n", __func__);
+
+               timbi2s_bus_write(bus, TIMBI2S_ICOR_RX_ENABLE |
+                       TIMBI2S_ICOR_FIFO_RST, TIMBI2S_BUS_ICOR);
+
+               timbi2s_bus_write(bus, TIMBI2S_IRQ_RX_ALMOST_FULL,
+                       TIMBI2S_BUS_IER);
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+               dev_dbg(snd_card_get_device_link(card),
+                       "%s: Got TRIGGER_STOP command\n", __func__);
+               /* disable interrupts */
+               spin_lock_irqsave(&bus->lock, flags);
+               timbi2s_bus_write(bus, 0, TIMBI2S_BUS_IER);
+               spin_unlock_irqrestore(&bus->lock, flags);
+               /* Stop RX */
+               timbi2s_bus_write(bus, 0, TIMBI2S_BUS_ICOR);
+               break;
+       default:
+               dev_dbg(snd_card_get_device_link(card),
+                       "%s: Got unsupported command\n", __func__);
+
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static snd_pcm_uframes_t
+timbi2s_pointer(struct snd_pcm_substream *substream)
+{
+       struct timbi2s_bus *bus = snd_pcm_substream_chip(substream);
+       struct snd_card *card = bus->card;
+       snd_pcm_uframes_t ret;
+
+       dev_dbg(snd_card_get_device_link(card),
+               "%s: Entry, substream: %p\n", __func__, substream);
+
+       ret = bytes_to_frames(substream->runtime, bus->buf_pos);
+       if (ret >= substream->runtime->buffer_size)
+               ret -= substream->runtime->buffer_size;
+
+       return ret;
+}
+
+static struct snd_pcm_ops timbi2s_playback_ops = {
+       .open           = timbi2s_open,
+       .close          = timbi2s_close,
+       .ioctl          = snd_pcm_lib_ioctl,
+       .hw_params      = timbi2s_hw_params,
+       .hw_free        = timbi2s_hw_free,
+       .prepare        = timbi2s_prepare,
+       .trigger        = timbi2s_playback_trigger,
+       .pointer        = timbi2s_pointer,
+};
+
+static struct snd_pcm_ops timbi2s_capture_ops = {
+       .open           = timbi2s_open,
+       .close          = timbi2s_close,
+       .ioctl          = snd_pcm_lib_ioctl,
+       .hw_params      = timbi2s_hw_params,
+       .hw_free        = timbi2s_hw_free,
+       .prepare        = timbi2s_prepare,
+       .trigger        = timbi2s_capture_trigger,
+       .pointer        = timbi2s_pointer,
+};
+
+static void timbi2s_irq_process_rx(struct timbi2s_bus *bus)
+{
+       struct snd_pcm_runtime *runtime = bus->substream->runtime;
+       u32 buffer_size = snd_pcm_lib_buffer_bytes(bus->substream);
+       u32 ipr = timbi2s_bus_read(bus, TIMBI2S_BUS_IPR);
+       int i;
+
+       dev_dbg(snd_card_get_device_link(bus->card),
+               "%s: Entry, bus: %d, IPR %x\n", __func__, BUS_INDEX(bus), ipr);
+
+       for (i = 0; i < NUM_SAMPLES; i++) {
+               *(u32 *)(runtime->dma_area + bus->buf_pos) =
+                       timbi2s_bus_read(bus, TIMBI2S_BUS_FIFO);
+               bus->buf_pos += SAMPLE_SIZE;
+               bus->buf_pos %= buffer_size;
+       }
+
+       timbi2s_bus_write(bus, ipr, TIMBI2S_BUS_ICLR);
+
+       /* inform ALSA that a period was received */
+       snd_pcm_period_elapsed(bus->substream);
+}
+
+static void timbi2s_irq_process_tx(struct timbi2s_bus *bus)
+{
+       struct snd_pcm_runtime *runtime = bus->substream->runtime;
+       u32 buffer_size = snd_pcm_lib_buffer_bytes(bus->substream);
+       u32 ipr = timbi2s_bus_read(bus, TIMBI2S_BUS_IPR);
+       int i;
+
+       dev_dbg(snd_card_get_device_link(bus->card),
+               "%s: Entry, bus: %d, IPR %x\n", __func__, BUS_INDEX(bus), ipr);
+
+       for (i = 0; i < NUM_SAMPLES; i++) {
+               timbi2s_bus_write(bus,
+                       *(u32 *)(runtime->dma_area + bus->buf_pos),
+                       TIMBI2S_BUS_FIFO);
+               bus->buf_pos += SAMPLE_SIZE;
+               bus->buf_pos %= buffer_size;
+       }
+
+       dev_dbg(snd_card_get_device_link(bus->card), "%s: ISR: %x, ICOR: %x\n",
+               __func__, timbi2s_bus_read(bus, TIMBI2S_BUS_ISR),
+               timbi2s_bus_read(bus, TIMBI2S_BUS_ICOR));
+
+       timbi2s_bus_write(bus, ipr, TIMBI2S_BUS_ICLR);
+
+       /* inform ALSA that a period was received */
+       snd_pcm_period_elapsed(bus->substream);
+}
+
+static void timbi2s_tasklet(unsigned long arg)
+{
+       struct snd_card *card = (struct snd_card *)arg;
+       struct timbi2s *i2s = snd_pcm_chip(card);
+       u32 uir;
+       unsigned i;
+
+       dev_dbg(snd_card_get_device_link(card), "%s: Entry, UIR %x\n",
+               __func__, uir);
+
+       while ((uir = ioread32(i2s->membase + TIMBI2S_REG_UIR)) != 0) {
+               for (i = 0; i < i2s->num_busses; i++)
+                       if (uir & (1 << i)) {
+                               struct timbi2s_bus *bus = i2s->busses + i;
+                               if (BUS_IS_RX(bus))
+                                       timbi2s_irq_process_rx(bus);
+                               else
+                                       timbi2s_irq_process_tx(bus);
+                       }
+       }
+
+       enable_irq(i2s->irq);
+}
+
+static irqreturn_t timbi2s_irq(int irq, void *devid)
+{
+       struct timbi2s *i2s = devid;
+
+       tasklet_schedule(&i2s->tasklet);
+       disable_irq_nosync(i2s->irq);
+
+       return IRQ_HANDLED;
+}
+
+static int timbi2s_setup_busses(struct snd_card *card,
+       struct platform_device *pdev)
+{
+       const struct timbi2s_platform_data *pdata = pdev->dev.platform_data;
+       unsigned i;
+
+       dev_dbg(&pdev->dev, "%s: Entry, no busses: %d, busses: %p\n", __func__,
+               pdata->num_busses, pdata->busses);
+
+       for (i = 0; i < pdata->num_busses; i++) {
+               const struct timbi2s_bus_data *bus_data = pdata->busses + i;
+               int                     capture = bus_data->rx;
+               int                     err;
+               u32                     ctl;
+               struct timbi2s          *i2s = snd_pcm_chip(card);
+               struct timbi2s_bus      *bus = i2s->busses + i;
+
+               dev_dbg(&pdev->dev, "%s: Setting up bus: %d\n", __func__, i);
+
+               SET_BUS_INDEX(bus, i);
+               bus->sample_rate = bus_data->sample_rate;
+               bus->card = card;
+               /* prescaling only applies to master busses, we use the
+                * knowledge of that to identify the direction later
+                * eg, bus->prescale != 0 -> master bus
+                */
+               if (capture)
+                       SET_BUS_RX(bus);
+
+               spin_lock_init(&bus->lock);
+
+               if (bus->sample_rate != 44100 && bus->sample_rate != 8000) {
+                       dev_err(&pdev->dev,
+                               "Unsupported bitrate: %d\n", bus->sample_rate);
+                       return -EINVAL;
+               }
+
+               dev_dbg(&pdev->dev, "%s: Will check HW direction on bus: %d\n",
+                       __func__, BUS_INDEX(bus));
+
+               /* check that the HW agrees with the direction */
+               ctl = timbi2s_bus_read(bus, TIMBI2S_BUS_ICOR);
+               if ((capture && !(ctl & TIMBI2S_ICOR_RX_ID)) ||
+                       (!capture && !(ctl & TIMBI2S_ICOR_TX_ID))) {
+                       dev_dbg(&pdev->dev,
+                               "HW and platform data disagree on direction\n");
+                       return -EINVAL;
+               }
+
+               dev_dbg(&pdev->dev, "%s: Will create PCM channel for bus: %d\n",
+                       __func__, BUS_INDEX(bus));
+               err = snd_pcm_new(card, bus_data->name ? bus_data->name :
+                       card->shortname, i, !capture, capture, &bus->pcm);
+               if (err) {
+                       dev_dbg(&pdev->dev, "%s, Failed to create pcm: %d\n",
+                               __func__, err);
+                       return err;
+               }
+
+               if (capture)
+                       snd_pcm_set_ops(bus->pcm, SNDRV_PCM_STREAM_CAPTURE,
+                               &timbi2s_capture_ops);
+               if (!capture)
+                       snd_pcm_set_ops(bus->pcm, SNDRV_PCM_STREAM_PLAYBACK,
+                               &timbi2s_playback_ops);
+
+               dev_dbg(&pdev->dev, "%s: Will preallocate buffers to bus: %d\n",
+                       __func__, BUS_INDEX(bus));
+
+               err = snd_pcm_lib_preallocate_pages_for_all(bus->pcm,
+                       SNDRV_DMA_TYPE_CONTINUOUS,
+                       snd_dma_continuous_data(GFP_KERNEL),
+                       NUM_SAMPLES * NUM_PERIODS * SAMPLE_SIZE * 2,
+                       NUM_SAMPLES * NUM_PERIODS * SAMPLE_SIZE * 2);
+               if (err) {
+                       dev_dbg(&pdev->dev, "%s, Failed to create pcm: %d\n",
+                               __func__, err);
+
+                       return err;
+               }
+
+               bus->pcm->private_data = bus;
+               bus->pcm->info_flags = 0;
+               strcpy(bus->pcm->name, card->shortname);
+               i2s->num_busses++;
+       }
+
+       return 0;
+}
+
+static int __devinit timbi2s_probe(struct platform_device *pdev)
+{
+       int err;
+       int irq;
+       struct timbi2s *i2s;
+       struct resource *iomem;
+       const struct timbi2s_platform_data *pdata = pdev->dev.platform_data;
+       struct snd_card *card;
+       u32 ver;
+
+       if (!pdata) {
+               err = -ENODEV;
+               goto out;
+       }
+
+       if (pdata->num_busses > MAX_BUSSES) {
+               err = -EINVAL;
+               goto out;
+       }
+
+       iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!iomem) {
+               err = -ENODEV;
+               goto out;
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               err = -ENODEV;
+               goto out;
+       }
+
+       err = snd_card_create(index, SNDRV_DEFAULT_STR1,
+               THIS_MODULE, sizeof(struct timbi2s) +
+               sizeof(struct timbi2s_bus) * pdata->num_busses, &card);
+       if (err)
+               goto out;
+
+       strcpy(card->driver, "Timberdale I2S");
+       strcpy(card->shortname, "Timberdale I2S");
+       sprintf(card->longname, "Timberdale I2S Driver");
+
+       snd_card_set_dev(card, &pdev->dev);
+
+       i2s = snd_pcm_chip(card);
+
+       if (!request_mem_region(iomem->start, resource_size(iomem),
+               DRIVER_NAME)) {
+               err = -EBUSY;
+               goto err_region;
+       }
+
+       i2s->membase = ioremap(iomem->start, resource_size(iomem));
+       if (!i2s->membase) {
+               err = -ENOMEM;
+               goto err_ioremap;
+       }
+
+       err = timbi2s_setup_busses(card, pdev);
+       if (err)
+               goto err_setup;
+
+       tasklet_init(&i2s->tasklet, timbi2s_tasklet, (unsigned long)card);
+       i2s->irq = irq;
+       i2s->main_clk = pdata->main_clk;
+
+       err = request_irq(irq, timbi2s_irq, 0, DRIVER_NAME, i2s);
+       if (err)
+               goto err_request_irq;
+
+       err = snd_card_register(card);
+       if (err)
+               goto err_register;
+
+       platform_set_drvdata(pdev, card);
+
+       ver = ioread32(i2s->membase + TIMBI2S_REG_VER);
+
+       printk(KERN_INFO
+               "Driver for Timberdale I2S (ver: %d.%d) successfully probed.\n",
+               ver >> 16 , ver & 0xffff);
+
+       return 0;
+
+err_register:
+       free_irq(irq, card);
+err_request_irq:
+err_setup:
+       iounmap(i2s->membase);
+err_ioremap:
+       release_mem_region(iomem->start, resource_size(iomem));
+err_region:
+       snd_card_free(card);
+out:
+       printk(KERN_ERR DRIVER_NAME": Failed to register: %d\n", err);
+
+       return err;
+}
+
+static int __devexit timbi2s_remove(struct platform_device *pdev)
+{
+       struct snd_card *card = platform_get_drvdata(pdev);
+       struct timbi2s *i2s = snd_pcm_chip(card);
+       struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+       tasklet_kill(&i2s->tasklet);
+       free_irq(i2s->irq, i2s);
+
+       iounmap(i2s->membase);
+       release_mem_region(iomem->start, resource_size(iomem));
+       snd_card_free(card);
+
+       platform_set_drvdata(pdev, 0);
+       return 0;
+}
+
+static struct platform_driver timbi2s_platform_driver = {
+       .driver = {
+               .name   = DRIVER_NAME,
+               .owner  = THIS_MODULE,
+       },
+       .probe          = timbi2s_probe,
+       .remove         = __devexit_p(timbi2s_remove),
+};
+
+/*--------------------------------------------------------------------------*/
+
+static int __init timbi2s_init(void)
+{
+       return platform_driver_register(&timbi2s_platform_driver);
+}
+
+static void __exit timbi2s_exit(void)
+{
+       platform_driver_unregister(&timbi2s_platform_driver);
+}
+
+module_init(timbi2s_init);
+module_exit(timbi2s_exit);
+
+MODULE_ALIAS("platform:"DRIVER_NAME);
+MODULE_DESCRIPTION("Timberdale I2S bus driver");
+MODULE_AUTHOR("Mocean Laboratories <[email protected]>");
+MODULE_LICENSE("GPL v2");
diff -Naur linux-2.6.35.org/include/sound/timbi2s.h 
linux-2.6.35/include/sound/timbi2s.h
--- linux-2.6.35.org/include/sound/timbi2s.h    1969-12-31 19:00:00.000000000 
-0500
+++ linux-2.6.35/include/sound/timbi2s.h        2010-11-16 09:16:39.000000000 
-0500
@@ -0,0 +1,33 @@
+/*
+ * timbi2s.h timberdale FPGA I2S platform data
+ * Copyright (c) 2009 Intel Corporation
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifndef __INCLUDE_SOUND_TIMBI2S_H
+#define __INCLUDE_SOUND_TIMBI2S_H
+
+struct timbi2s_bus_data {
+       u8      rx;
+       u16     sample_rate;
+       const char *name;
+};
+
+struct timbi2s_platform_data {
+       const struct timbi2s_bus_data   *busses;
+       int                             num_busses;
+       u32                             main_clk;
+};
+
+#endif

_______________________________________________
MeeGo-kernel mailing list
[email protected]
http://lists.meego.com/listinfo/meego-kernel

Reply via email to