All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] alsa: Add support for timberdale I2S block
@ 2009-10-26 13:02 Richard Röjfors
  2009-10-26 13:09 ` Mark Brown
  0 siblings, 1 reply; 7+ messages in thread
From: Richard Röjfors @ 2009-10-26 13:02 UTC (permalink / raw)
  To: alsa-devel; +Cc: Takashi Iwai, Andrew Morton, alsa-devel

This driver adds support for the I2S block of the timberdale FPGA.

The timberdale is a FPGA found on intel development boards for
In-Vehicle Infotainment.

The block has support for up to 8 I2S channels, can be clocked
from either the FPGA or the device side.

This driver introduces support for this block, by exposing each
I2S channel as an ALSA PCM channel.

Signed-off-by: Richard Röjfors <richard.rojfors@mocean-labs.com>
---
diff --git a/include/sound/timbi2s.h b/include/sound/timbi2s.h
new file mode 100644
index 0000000..4ab810c
--- /dev/null
+++ b/include/sound/timbi2s.h
@@ -0,0 +1,32 @@
+/*
+ * 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;
+};
+
+struct timbi2s_platform_data {
+	const struct timbi2s_bus_data	*busses;
+	int				num_busses;
+	u32				main_clk;
+};
+
+#endif
diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig
index 84714a6..54ad4e7 100644
--- a/sound/drivers/Kconfig
+++ b/sound/drivers/Kconfig
@@ -182,4 +182,17 @@ config SND_AC97_POWER_SAVE_DEFAULT
 	  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.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-timbi2s.
+
 endif	# SND_DRIVERS
diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile
index d4a07f9..dea2eed 100644
--- a/sound/drivers/Makefile
+++ b/sound/drivers/Makefile
@@ -10,6 +10,7 @@ snd-portman2x4-objs := portman2x4.o
 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_MTPAV) += snd-mtpav.o
 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 --git a/sound/drivers/timbi2s.c b/sound/drivers/timbi2s.c
new file mode 100644
index 0000000..a9c34c2
--- /dev/null
+++ b/sound/drivers/timbi2s.c
@@ -0,0 +1,755 @@
+/*
+ * 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>
+
+#define DRIVER_NAME "timb-i2s"
+
+#define MAX_BUSSES	8
+
+#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		0x10
+#define TIMBI2S_IRQ_RX_ALMOST_FULL	0x20
+#define TIMBI2S_IRQ_RX_ALMOST_EMPTY	0x40
+#define TIMBI2S_IRQ_RX_NOT_EMPTY	0x80
+
+#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_8000,
+	.rate_min		= 8000,
+	.rate_max		= 8000,
+	.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;
+		if (bus->sample_rate == 8000) {
+			runtime->hw.rates = SNDRV_PCM_RATE_8000;
+			runtime->hw.rate_min = 8000;
+			runtime->hw.rate_max = 8000;
+		}
+	} else
+		runtime->hw = timbi2s_tx_hw;
+
+	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 = ioread32(i2s->membase + TIMBI2S_REG_UIR);
+	unsigned i;
+
+	dev_dbg(snd_card_get_device_link(card), "%s: Entry, UIR %x\n",
+		__func__, uir);
+
+	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++) {
+		int			capture = pdata->busses[i].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 = pdata->busses[i].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, 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(SNDRV_DEFAULT_IDX1, 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 <info@mocean-labs.com>");
+MODULE_LICENSE("GPL v2");

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH] alsa: Add support for timberdale I2S block
  2009-10-26 13:02 [PATCH] alsa: Add support for timberdale I2S block Richard Röjfors
@ 2009-10-26 13:09 ` Mark Brown
  2009-10-30 11:53   ` Takashi Iwai
  2010-02-01 15:20   ` [PATCH] alsa: Add support for timberdale I2S block Richard Röjfors
  0 siblings, 2 replies; 7+ messages in thread
From: Mark Brown @ 2009-10-26 13:09 UTC (permalink / raw)
  To: Richard R?jfors; +Cc: Takashi Iwai, alsa-devel, Andrew Morton, alsa-devel

On Mon, Oct 26, 2009 at 02:02:57PM +0100, Richard R?jfors wrote:
> This driver adds support for the I2S block of the timberdale FPGA.
> 
> The timberdale is a FPGA found on intel development boards for
> In-Vehicle Infotainment.
> 
> The block has support for up to 8 I2S channels, can be clocked
> from either the FPGA or the device side.
> 
> This driver introduces support for this block, by exposing each
> I2S channel as an ALSA PCM channel.
> 
> Signed-off-by: Richard R?jfors <richard.rojfors@mocean-labs.com>

Without having investigated in any great detail I'd really expect this
driver to be done within ASoC if it's producing generic I2S output.  If
it's producing I2S out then presumably system designers will be able to
attach whatever CODECs they desire and if those CODECs require any soft
control then there will be a need to interoperate with CODEC drivers.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] alsa: Add support for timberdale I2S block
  2009-10-26 13:09 ` Mark Brown
@ 2009-10-30 11:53   ` Takashi Iwai
  2009-11-02 18:26     ` ASoC updates for 2.6.33 Mark Brown
  2010-02-01 15:20   ` [PATCH] alsa: Add support for timberdale I2S block Richard Röjfors
  1 sibling, 1 reply; 7+ messages in thread
From: Takashi Iwai @ 2009-10-30 11:53 UTC (permalink / raw)
  To: Mark Brown; +Cc: Richard R?jfors, alsa-devel, Andrew Morton, alsa-devel

At Mon, 26 Oct 2009 13:09:31 +0000,
Mark Brown wrote:
> 
> On Mon, Oct 26, 2009 at 02:02:57PM +0100, Richard R?jfors wrote:
> > This driver adds support for the I2S block of the timberdale FPGA.
> > 
> > The timberdale is a FPGA found on intel development boards for
> > In-Vehicle Infotainment.
> > 
> > The block has support for up to 8 I2S channels, can be clocked
> > from either the FPGA or the device side.
> > 
> > This driver introduces support for this block, by exposing each
> > I2S channel as an ALSA PCM channel.
> > 
> > Signed-off-by: Richard R?jfors <richard.rojfors@mocean-labs.com>
> 
> Without having investigated in any great detail I'd really expect this
> driver to be done within ASoC if it's producing generic I2S output.  If
> it's producing I2S out then presumably system designers will be able to
> attach whatever CODECs they desire and if those CODECs require any soft
> control then there will be a need to interoperate with CODEC drivers.

I don't mind to keep non-ASoC driver if Richard is reachable :)
But I agree with Mark basically.  This kind of device would match better
with ASoC in general.


Richard, if you are going to rewrite it for ASoC, I'll postpone this
patch.  If you would like only this version, I can apply it.


thanks,

Takashi

^ permalink raw reply	[flat|nested] 7+ messages in thread

* ASoC updates for 2.6.33
  2009-10-30 11:53   ` Takashi Iwai
@ 2009-11-02 18:26     ` Mark Brown
  2009-11-03  7:02       ` Takashi Iwai
  0 siblings, 1 reply; 7+ messages in thread
From: Mark Brown @ 2009-11-02 18:26 UTC (permalink / raw)
  To: alsa-devel; +Cc: alsa-devel

The following changes since commit 98078bf90495729e59edbec088d00b9d98f4cc0f:
  Mark Brown (1):
        Merge branch 'for-2.6.32' into for-2.6.33

are available in the git repository at:

  git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git for-2.6.33

Eero Nurkkala (2):
      ASoC: remove io_mutex
      ASoC: refactor snd_soc_update_bits()

Manuel Lauss (1):
      ASoC: au1x: convert to platform drivers.

Neil Jones (1):
      ASoC: Add support for the WM8727 DAC.

Peter Ujfalusi (1):
      ASoC: TWL4030: Make sure, that the codec is powered on startup

 sound/soc/au1x/dbdma2.c    |  117 ++++++++++++++++++++++-----
 sound/soc/au1x/psc-ac97.c  |  194 +++++++++++++++++++++++++++-----------------
 sound/soc/au1x/psc-i2s.c   |  189 +++++++++++++++++++++++++++----------------
 sound/soc/au1x/psc.h       |    7 ++-
 sound/soc/codecs/Kconfig   |    4 +
 sound/soc/codecs/Makefile  |    2 +
 sound/soc/codecs/twl4030.c |    1 +
 sound/soc/codecs/wm8727.c  |  143 ++++++++++++++++++++++++++++++++
 sound/soc/codecs/wm8727.h  |   21 +++++
 sound/soc/soc-core.c       |   41 +++++++---
 10 files changed, 545 insertions(+), 174 deletions(-)
 create mode 100644 sound/soc/codecs/wm8727.c
 create mode 100644 sound/soc/codecs/wm8727.h

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: ASoC updates for 2.6.33
  2009-11-02 18:26     ` ASoC updates for 2.6.33 Mark Brown
@ 2009-11-03  7:02       ` Takashi Iwai
  0 siblings, 0 replies; 7+ messages in thread
From: Takashi Iwai @ 2009-11-03  7:02 UTC (permalink / raw)
  To: alsa-devel; +Cc: alsa-devel

At Mon, 2 Nov 2009 18:26:37 +0000,
Mark Brown wrote:
> 
> The following changes since commit 98078bf90495729e59edbec088d00b9d98f4cc0f:
>   Mark Brown (1):
>         Merge branch 'for-2.6.32' into for-2.6.33
> 
> are available in the git repository at:
> 
>   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git for-2.6.33

Pulled now.  Thanks.


Takashi


> Eero Nurkkala (2):
>       ASoC: remove io_mutex
>       ASoC: refactor snd_soc_update_bits()
> 
> Manuel Lauss (1):
>       ASoC: au1x: convert to platform drivers.
> 
> Neil Jones (1):
>       ASoC: Add support for the WM8727 DAC.
> 
> Peter Ujfalusi (1):
>       ASoC: TWL4030: Make sure, that the codec is powered on startup
> 
>  sound/soc/au1x/dbdma2.c    |  117 ++++++++++++++++++++++-----
>  sound/soc/au1x/psc-ac97.c  |  194 +++++++++++++++++++++++++++-----------------
>  sound/soc/au1x/psc-i2s.c   |  189 +++++++++++++++++++++++++++----------------
>  sound/soc/au1x/psc.h       |    7 ++-
>  sound/soc/codecs/Kconfig   |    4 +
>  sound/soc/codecs/Makefile  |    2 +
>  sound/soc/codecs/twl4030.c |    1 +
>  sound/soc/codecs/wm8727.c  |  143 ++++++++++++++++++++++++++++++++
>  sound/soc/codecs/wm8727.h  |   21 +++++
>  sound/soc/soc-core.c       |   41 +++++++---
>  10 files changed, 545 insertions(+), 174 deletions(-)
>  create mode 100644 sound/soc/codecs/wm8727.c
>  create mode 100644 sound/soc/codecs/wm8727.h
> 

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] alsa: Add support for timberdale I2S block
  2009-10-26 13:09 ` Mark Brown
  2009-10-30 11:53   ` Takashi Iwai
@ 2010-02-01 15:20   ` Richard Röjfors
  2010-02-01 16:46     ` Mark Brown
  1 sibling, 1 reply; 7+ messages in thread
From: Richard Röjfors @ 2010-02-01 15:20 UTC (permalink / raw)
  To: Mark Brown; +Cc: Takashi Iwai, alsa-devel, alsa-devel

Mark Brown wrote:
> On Mon, Oct 26, 2009 at 02:02:57PM +0100, Richard R?jfors wrote:
>> This driver adds support for the I2S block of the timberdale FPGA.
>>
>> The timberdale is a FPGA found on intel development boards for
>> In-Vehicle Infotainment.
>>
>> The block has support for up to 8 I2S channels, can be clocked
>> from either the FPGA or the device side.
>>
>> This driver introduces support for this block, by exposing each
>> I2S channel as an ALSA PCM channel.
>>
>> Signed-off-by: Richard R?jfors <richard.rojfors@mocean-labs.com>
> 
> Without having investigated in any great detail I'd really expect this
> driver to be done within ASoC if it's producing generic I2S output.  If
> it's producing I2S out then presumably system designers will be able to
> attach whatever CODECs they desire and if those CODECs require any soft
> control then there will be a need to interoperate with CODEC drivers.

This thing produces raw I2S/captures raw I2S as you say. It's used to
connect to bluetooth chips and radio DSP:s. So there are no CODEC:s
involved. Does it make sense to do it within ASoC?

--Richard

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] alsa: Add support for timberdale I2S block
  2010-02-01 15:20   ` [PATCH] alsa: Add support for timberdale I2S block Richard Röjfors
@ 2010-02-01 16:46     ` Mark Brown
  0 siblings, 0 replies; 7+ messages in thread
From: Mark Brown @ 2010-02-01 16:46 UTC (permalink / raw)
  To: Richard Röjfors; +Cc: Takashi Iwai, alsa-devel

On Mon, Feb 01, 2010 at 04:20:38PM +0100, Richard Röjfors wrote:
> Mark Brown wrote:

> > Without having investigated in any great detail I'd really expect this
> > driver to be done within ASoC if it's producing generic I2S output.  If
> > it's producing I2S out then presumably system designers will be able to
> > attach whatever CODECs they desire and if those CODECs require any soft
> > control then there will be a need to interoperate with CODEC drivers.

> This thing produces raw I2S/captures raw I2S as you say. It's used to
> connect to bluetooth chips and radio DSP:s. So there are no CODEC:s
> involved. Does it make sense to do it within ASoC?

Presumably there's nothing intrinsic in the hardware which makes this
specific to bluetooth and other radios?  If it's just vanilla I2S out
I'd be shocked if nobody ever decided to hook it up to a CODEC for some
reason or other, even if that CODEC is just being used to hook up to an
analogue baseband that's been selected for some reason.

In any case, due to mobile phones things like the bluetooth are getting
represented in ASoC and it's trivial to provide a stub for them where
they aren't so it seems sensible to have the option to hook the device
up in ASoC.

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2010-02-01 16:46 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-10-26 13:02 [PATCH] alsa: Add support for timberdale I2S block Richard Röjfors
2009-10-26 13:09 ` Mark Brown
2009-10-30 11:53   ` Takashi Iwai
2009-11-02 18:26     ` ASoC updates for 2.6.33 Mark Brown
2009-11-03  7:02       ` Takashi Iwai
2010-02-01 15:20   ` [PATCH] alsa: Add support for timberdale I2S block Richard Röjfors
2010-02-01 16:46     ` Mark Brown

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.