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
