linux-spi.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] OMAP: Add OMAP24XX Multichannel SPI controller driver
@ 2007-06-19  6:23 Trilok Soni
       [not found] ` <5d5443650706182323k4cc99707v8ad8dc5cff24ca3e-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 7+ messages in thread
From: Trilok Soni @ 2007-06-19  6:23 UTC (permalink / raw)
  To: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f
  Cc: juha.yrjola-EmnPodGKVbzby3iVrkZq2A,
	samuel.ortiz-EmnPodGKVbzby3iVrkZq2A, Tony Lindgren,
	dbrownell-Rn4VEauK+AKRv+LV9MX5uipxlwaOVQ5f

[-- Attachment #1: Type: text/plain, Size: 18 bytes --]

-- 
--Trilok Soni

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-OMAP-Add-OMAP24XX-Multichannel-SPI-controller-driver.patch --]
[-- Type: text/x-patch; name=0001-OMAP-Add-OMAP24XX-Multichannel-SPI-controller-driver.patch; charset=ANSI_X3.4-1968, Size: 29128 bytes --]

From a1b74e254ae6ccb9675ddc0fc41d5d11c345b023 Mon Sep 17 00:00:00 2001
From: Samuel Ortiz <samuel.ortiz@solidboot.com>
Date: Sat, 26 May 2007 19:19:19 +0530
Subject: [PATCH] OMAP: Add OMAP24XX Multichannel SPI controller driver

- Add OMAP24XX Multichannel SPI controller driver. This
  driver is tested very well under OMAP GIT tree with
  N800 - Nokia Internet Tablet.

Signed-off-by: Trilok Soni <soni.trilok@gmail.com>
---
 drivers/spi/Kconfig       |    5 +
 drivers/spi/Makefile      |    1 +
 drivers/spi/omap2_mcspi.c | 1048 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1054 insertions(+), 0 deletions(-)
 create mode 100644 drivers/spi/omap2_mcspi.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 5e3f748..dfd51b4 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -133,6 +133,11 @@ config SPI_OMAP_UWIRE
 	help
 	  This hooks up to the MicroWire controller on OMAP1 chips.
 
+config SPI_OMAP24XX
+	tristate "McSPI driver for OMAP24xx"
+	depends on SPI_MASTER && ARCH_OMAP24XX
+	help
+	  SPI master controller for OMAP24xx McSPI modules.
 
 config SPI_PXA2XX
 	tristate "PXA2xx SSP SPI master"
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 5788d86..4e52f93 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_SPI_BUTTERFLY)		+= spi_butterfly.o
 obj-$(CONFIG_SPI_IMX)			+= spi_imx.o
 obj-$(CONFIG_SPI_PXA2XX)		+= pxa2xx_spi.o
 obj-$(CONFIG_SPI_OMAP_UWIRE)		+= omap_uwire.o
+obj-$(CONFIG_SPI_OMAP24XX)		+= omap2_mcspi.o
 obj-$(CONFIG_SPI_MPC52xx_PSC)		+= mpc52xx_psc_spi.o
 obj-$(CONFIG_SPI_MPC83xx)		+= spi_mpc83xx.o
 obj-$(CONFIG_SPI_S3C24XX_GPIO)		+= spi_s3c24xx_gpio.o
diff --git a/drivers/spi/omap2_mcspi.c b/drivers/spi/omap2_mcspi.c
new file mode 100644
index 0000000..314d194
--- /dev/null
+++ b/drivers/spi/omap2_mcspi.c
@@ -0,0 +1,1048 @@
+/*
+ * OMAP2 McSPI controller driver
+ *
+ * Copyright (C) 2005, 2006 Nokia Corporation
+ * Author:	Samuel Ortiz <samuel.ortiz@nokia.com> and
+ *		Juha Yrjölä <juha.yrjola@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+
+#include <linux/spi/spi.h>
+
+#include <asm/io.h>
+#include <asm/arch/dma.h>
+#include <asm/arch/clock.h>
+
+#define OMAP2_MCSPI_MAX_FREQ		48000000
+
+#define OMAP2_MCSPI_REVISION		0x00
+#define OMAP2_MCSPI_SYSCONFIG		0x10
+#define OMAP2_MCSPI_SYSSTATUS		0x14
+#define OMAP2_MCSPI_IRQSTATUS		0x18
+#define OMAP2_MCSPI_IRQENABLE		0x1c
+#define OMAP2_MCSPI_WAKEUPENABLE	0x20
+#define OMAP2_MCSPI_SYST		0x24
+#define OMAP2_MCSPI_MODULCTRL		0x28
+
+/* per-channel banks, 0x14 bytes each, first is: */
+#define OMAP2_MCSPI_CHCONF0		0x2c
+#define OMAP2_MCSPI_CHSTAT0		0x30
+#define OMAP2_MCSPI_CHCTRL0		0x34
+#define OMAP2_MCSPI_TX0			0x38
+#define OMAP2_MCSPI_RX0			0x3c
+
+/* per-register bitmasks: */
+
+#define OMAP2_MCSPI_SYSCONFIG_AUTOIDLE	(1 << 0)
+#define OMAP2_MCSPI_SYSCONFIG_SOFTRESET	(1 << 1)
+
+#define OMAP2_MCSPI_SYSSTATUS_RESETDONE	(1 << 0)
+
+#define OMAP2_MCSPI_MODULCTRL_SINGLE	(1 << 0)
+#define OMAP2_MCSPI_MODULCTRL_MS	(1 << 2)
+#define OMAP2_MCSPI_MODULCTRL_STEST	(1 << 3)
+
+#define OMAP2_MCSPI_CHCONF_PHA		(1 << 0)
+#define OMAP2_MCSPI_CHCONF_POL		(1 << 1)
+#define OMAP2_MCSPI_CHCONF_CLKD_MASK	(0x0f << 2)
+#define OMAP2_MCSPI_CHCONF_EPOL		(1 << 6)
+#define OMAP2_MCSPI_CHCONF_WL_MASK	(0x1f << 7)
+#define OMAP2_MCSPI_CHCONF_TRM_RX_ONLY	(0x01 << 12)
+#define OMAP2_MCSPI_CHCONF_TRM_TX_ONLY	(0x02 << 12)
+#define OMAP2_MCSPI_CHCONF_TRM_MASK	(0x03 << 12)
+#define OMAP2_MCSPI_CHCONF_DMAW		(1 << 14)
+#define OMAP2_MCSPI_CHCONF_DMAR		(1 << 15)
+#define OMAP2_MCSPI_CHCONF_DPE0		(1 << 16)
+#define OMAP2_MCSPI_CHCONF_DPE1		(1 << 17)
+#define OMAP2_MCSPI_CHCONF_IS		(1 << 18)
+#define OMAP2_MCSPI_CHCONF_TURBO	(1 << 19)
+#define OMAP2_MCSPI_CHCONF_FORCE	(1 << 20)
+
+#define OMAP2_MCSPI_CHSTAT_RXS		(1 << 0)
+#define OMAP2_MCSPI_CHSTAT_TXS		(1 << 1)
+#define OMAP2_MCSPI_CHSTAT_EOT		(1 << 2)
+
+#define OMAP2_MCSPI_CHCTRL_EN		(1 << 0)
+
+
+/* We have 2 DMA channels per CS, one for RX and one for TX */
+struct omap2_mcspi_dma {
+	int dma_tx_channel;
+	int dma_rx_channel;
+
+	int dma_tx_sync_dev;
+	int dma_rx_sync_dev;
+
+	struct completion dma_tx_completion;
+	struct completion dma_rx_completion;
+};
+
+struct omap2_mcspi {
+	struct work_struct	work;
+	spinlock_t		lock;
+	struct list_head	msg_queue;
+	struct spi_master	*master;
+	struct clk		*ick;
+	struct clk		*fck;
+	/* Virtual base address of the controller */
+	unsigned long		base;
+	/* SPI1 has 4 channels, while SPI2 has 2 */
+	struct omap2_mcspi_dma	*dma_channels;
+};
+
+struct omap2_mcspi_cs {
+	u8 transmit_mode;
+	int word_len;
+};
+
+static struct workqueue_struct * omap2_mcspi_wq;
+
+#define MOD_REG_BIT(val, mask, set) do { \
+	if (set) \
+		val |= mask; \
+	else \
+		val &= ~mask; \
+} while(0)
+
+static inline void mcspi_write_reg(struct spi_master *master,
+		int idx, u32 val)
+{
+	struct omap2_mcspi *mcspi = spi_master_get_devdata(master);
+
+	__raw_writel(val, mcspi->base + idx);
+}
+
+static inline u32 mcspi_read_reg(struct spi_master *master,
+		int idx)
+{
+	struct omap2_mcspi *mcspi = spi_master_get_devdata(master);
+
+	return __raw_readl(mcspi->base + idx);
+}
+
+static inline void mcspi_write_cs_reg(const struct spi_device *spi,
+		int idx, u32 val)
+{
+	struct omap2_mcspi *mcspi = spi_master_get_devdata(spi->master);
+
+	__raw_writel(val, mcspi->base + spi->chip_select * 0x14 + idx);
+}
+
+static inline u32 mcspi_read_cs_reg(const struct spi_device *spi,
+		int idx)
+{
+	struct omap2_mcspi *mcspi = spi_master_get_devdata(spi->master);
+
+	return __raw_readl(mcspi->base + spi->chip_select * 0x14 + idx);
+}
+
+static void omap2_mcspi_set_dma_req(const struct spi_device *spi,
+		int is_read, int enable)
+{
+	u32 l, rw;
+
+	l = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
+
+	if (is_read) /* 1 is read, 0 write */
+		rw = OMAP2_MCSPI_CHCONF_DMAR;
+	else
+		rw = OMAP2_MCSPI_CHCONF_DMAW;
+
+	MOD_REG_BIT(l, rw, enable);
+	mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCONF0, l);
+}
+
+static void omap2_mcspi_set_enable(const struct spi_device *spi, int enable)
+{
+	u32 l;
+
+	l = enable ? OMAP2_MCSPI_CHCTRL_EN : 0;
+	mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCTRL0, l);
+}
+
+static void omap2_mcspi_force_cs(struct spi_device *spi, int cs_active)
+{
+	u32 l;
+
+	l = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
+	MOD_REG_BIT(l, OMAP2_MCSPI_CHCONF_FORCE, cs_active);
+	mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCONF0, l);
+}
+
+static void omap2_mcspi_set_master_mode(struct spi_master *master)
+{
+	u32 l;
+
+	/* setup when switching from (reset default) slave mode
+	 * to single-channel master mode
+	 */
+	l = mcspi_read_reg(master, OMAP2_MCSPI_MODULCTRL);
+	MOD_REG_BIT(l, OMAP2_MCSPI_MODULCTRL_STEST, 0);
+	MOD_REG_BIT(l, OMAP2_MCSPI_MODULCTRL_MS, 0);
+	MOD_REG_BIT(l, OMAP2_MCSPI_MODULCTRL_SINGLE, 1);
+	mcspi_write_reg(master, OMAP2_MCSPI_MODULCTRL, l);
+}
+
+static void
+omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
+{
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_cs	*cs = spi->controller_state;
+	struct omap2_mcspi_dma  *mcspi_dma;
+	unsigned int		count, c;
+	unsigned long		base, tx_reg, rx_reg;
+	int			word_len, data_type, element_count;
+	u8			* rx;
+	const u8		* tx;
+	u32			l;
+
+	mcspi = spi_master_get_devdata(spi->master);
+	mcspi_dma = &mcspi->dma_channels[spi->chip_select];
+
+	count = xfer->len;
+	c = count;
+	word_len = cs->word_len;
+
+	l = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
+	l &= ~OMAP2_MCSPI_CHCONF_TRM_MASK;
+	if (xfer->tx_buf == NULL)
+		l |= OMAP2_MCSPI_CHCONF_TRM_RX_ONLY;
+	else if (xfer->rx_buf == NULL)
+		l |= OMAP2_MCSPI_CHCONF_TRM_TX_ONLY;
+	mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCONF0, l);
+
+	omap2_mcspi_set_enable(spi, 1);
+
+	base = io_v2p(mcspi->base) + spi->chip_select * 0x14;
+	tx_reg = base + OMAP2_MCSPI_TX0;
+	rx_reg = base + OMAP2_MCSPI_RX0;
+	rx = xfer->rx_buf;
+	tx = xfer->tx_buf;
+
+	if (word_len <= 8) {
+		data_type = OMAP_DMA_DATA_TYPE_S8;
+		element_count = count;
+	} else if (word_len <= 16) {
+		data_type = OMAP_DMA_DATA_TYPE_S16;
+		element_count = count >> 1;
+	} else if (word_len <= 32) {
+		data_type = OMAP_DMA_DATA_TYPE_S32;
+		element_count = count >> 2;
+	} else
+		goto out;
+
+	/* RX_ONLY mode needs dummy data in TX reg */
+	if (tx == NULL)
+		__raw_writel(0, mcspi->base
+					+ spi->chip_select * 0x14
+					+ OMAP2_MCSPI_TX0);
+
+	if (tx != NULL) {
+		xfer->tx_dma = dma_map_single(&spi->dev, (void *) tx, count,
+				DMA_TO_DEVICE);
+		if (dma_mapping_error(xfer->tx_dma)) {
+			dev_err(&spi->dev, "dma TX %d bytes error\n", count);
+			return;
+		}
+
+		omap_set_dma_transfer_params(mcspi_dma->dma_tx_channel,
+				data_type, element_count, 1,
+				OMAP_DMA_SYNC_ELEMENT,
+				mcspi_dma->dma_tx_sync_dev, 0);
+
+		omap_set_dma_dest_params(mcspi_dma->dma_tx_channel, 0,
+				OMAP_DMA_AMODE_CONSTANT,
+				tx_reg, 0, 0);
+
+		omap_set_dma_src_params(mcspi_dma->dma_tx_channel, 0,
+				OMAP_DMA_AMODE_POST_INC,
+				xfer->tx_dma, 0, 0);
+	}
+
+	if (rx != NULL) {
+		xfer->rx_dma = dma_map_single(&spi->dev, rx, count,
+				DMA_FROM_DEVICE);
+		if (dma_mapping_error(xfer->rx_dma)) {
+			dev_err(&spi->dev, "dma RX %d bytes error\n", count);
+			if (tx != NULL)
+				dma_unmap_single(NULL, xfer->tx_dma,
+						count, DMA_TO_DEVICE);
+			goto out;
+		}
+
+		omap_set_dma_transfer_params(mcspi_dma->dma_rx_channel,
+				data_type, element_count, 1,
+				OMAP_DMA_SYNC_ELEMENT,
+				mcspi_dma->dma_rx_sync_dev, 1);
+
+		omap_set_dma_src_params(mcspi_dma->dma_rx_channel, 0,
+				OMAP_DMA_AMODE_CONSTANT,
+				rx_reg, 0, 0);
+
+		omap_set_dma_dest_params(mcspi_dma->dma_rx_channel, 0,
+				OMAP_DMA_AMODE_POST_INC,
+				xfer->rx_dma, 0, 0);
+	}
+
+	if (tx != NULL) {
+		omap_start_dma(mcspi_dma->dma_tx_channel);
+		omap2_mcspi_set_dma_req(spi, 0, 1);
+	}
+
+	if (rx != NULL) {
+		omap_start_dma(mcspi_dma->dma_rx_channel);
+		omap2_mcspi_set_dma_req(spi, 1, 1);
+	}
+
+	if (tx != NULL) {
+		wait_for_completion(&mcspi_dma->dma_tx_completion);
+		dma_unmap_single(NULL, xfer->tx_dma, count, DMA_TO_DEVICE);
+	}
+
+	if (rx != NULL) {
+		wait_for_completion(&mcspi_dma->dma_rx_completion);
+		dma_unmap_single(NULL, xfer->rx_dma, count, DMA_FROM_DEVICE);
+	}
+out:
+	omap2_mcspi_set_enable(spi, 0);
+}
+
+static int mcspi_wait_for_reg_bit(unsigned long reg, unsigned long bit)
+{
+	unsigned long timeout;
+
+	timeout = jiffies + msecs_to_jiffies(1000);
+	while (!(__raw_readl(reg) & bit)) {
+		if (time_after(jiffies, timeout))
+			return -1;
+	}
+	return 0;
+}
+
+static void
+omap2_mcspi_txrx_pio(struct spi_device *spi, struct spi_transfer *xfer)
+{
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_cs	*cs = spi->controller_state;
+	unsigned int		count, c;
+	u32			l;
+	unsigned long		base, tx_reg, rx_reg, chstat_reg;
+	int			word_len;
+
+	mcspi = spi_master_get_devdata(spi->master);
+	count = xfer->len;
+	c = count;
+	word_len = cs->word_len;
+
+	l = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
+	l &= ~OMAP2_MCSPI_CHCONF_TRM_MASK;
+	if (xfer->tx_buf == NULL)
+		l |= OMAP2_MCSPI_CHCONF_TRM_RX_ONLY;
+	else if (xfer->rx_buf == NULL)
+		l |= OMAP2_MCSPI_CHCONF_TRM_TX_ONLY;
+	mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCONF0, l);
+
+	omap2_mcspi_set_enable(spi, 1);
+
+	/* We store the pre-calculated register addresses on stack to speed
+	 * up the transfer loop. */
+	base = mcspi->base + spi->chip_select * 0x14;
+	tx_reg		= base + OMAP2_MCSPI_TX0;
+	rx_reg		= base + OMAP2_MCSPI_RX0;
+	chstat_reg	= base + OMAP2_MCSPI_CHSTAT0;
+
+	/* RX_ONLY mode needs dummy data in TX reg.   If we were using
+	 * TURBO mode (double buffered) we'd need to disable the channel
+	 * before reading the penultimate word ... so TURBO wouldn't be an
+	 * option except for the last transfer, else if cs_change is set.
+	 */
+	if (xfer->tx_buf == NULL)
+		__raw_writel(0, tx_reg);
+
+	if (word_len <= 8) {
+		u8		*rx;
+		const u8	*tx;
+
+		rx = xfer->rx_buf;
+		tx = xfer->tx_buf;
+
+		while (c--) {
+			if (tx != NULL) {
+				if (mcspi_wait_for_reg_bit(chstat_reg,
+						OMAP2_MCSPI_CHSTAT_TXS) < 0) {
+					dev_err(&spi->dev, "TXS timed out\n");
+					goto out;
+				}
+#ifdef VERBOSE
+				dev_dbg(&spi->dev, "write-%d %02x\n",
+						word_len, *tx);
+#endif
+				__raw_writel(*tx++, tx_reg);
+			}
+			if (rx != NULL) {
+				if (mcspi_wait_for_reg_bit(chstat_reg,
+						OMAP2_MCSPI_CHSTAT_RXS) < 0) {
+					dev_err(&spi->dev, "RXS timed out\n");
+					goto out;
+				}
+				*rx++ = __raw_readl(rx_reg);
+#ifdef VERBOSE
+				dev_dbg(&spi->dev, "read-%d %02x\n",
+						word_len, *(rx - 1));
+#endif
+			}
+		}
+	} else if (word_len <= 16) {
+		u16		*rx;
+		const u16	*tx;
+
+		rx = xfer->rx_buf;
+		tx = xfer->tx_buf;
+		c >>= 1;
+		while (c--) {
+			if (tx != NULL) {
+				if (mcspi_wait_for_reg_bit(chstat_reg,
+						OMAP2_MCSPI_CHSTAT_TXS) < 0) {
+					dev_err(&spi->dev, "TXS timed out\n");
+					goto out;
+				}
+#ifdef VERBOSE
+				dev_dbg(&spi->dev, "write-%d %04x\n",
+						word_len, *tx);
+#endif
+				__raw_writel(*tx++, tx_reg);
+			}
+			if (rx != NULL) {
+				if (mcspi_wait_for_reg_bit(chstat_reg,
+						OMAP2_MCSPI_CHSTAT_RXS) < 0) {
+					dev_err(&spi->dev, "RXS timed out\n");
+					goto out;
+				}
+				*rx++ = __raw_readl(rx_reg);
+#ifdef VERBOSE
+				dev_dbg(&spi->dev, "read-%d %04x\n",
+						word_len, *(rx - 1));
+#endif
+			}
+		}
+	} else if (word_len <= 32) {
+		u32		*rx;
+		const u32	*tx;
+
+		rx = xfer->rx_buf;
+		tx = xfer->tx_buf;
+		c >>= 2;
+		while (c--) {
+			if (tx != NULL) {
+				if (mcspi_wait_for_reg_bit(chstat_reg,
+						OMAP2_MCSPI_CHSTAT_TXS) < 0) {
+					dev_err(&spi->dev, "TXS timed out\n");
+					goto out;
+				}
+#ifdef VERBOSE
+				dev_dbg(&spi->dev, "write-%d %04x\n",
+						word_len, *tx);
+#endif
+				__raw_writel(*tx++, tx_reg);
+			}
+			if (rx != NULL) {
+				if (mcspi_wait_for_reg_bit(chstat_reg,
+						OMAP2_MCSPI_CHSTAT_RXS) < 0) {
+					dev_err(&spi->dev, "RXS timed out\n");
+					goto out;
+				}
+				*rx++ = __raw_readl(rx_reg);
+#ifdef VERBOSE
+				dev_dbg(&spi->dev, "read-%d %04x\n",
+						word_len, *(rx - 1));
+#endif
+			}
+		}
+	}
+
+	/* for TX_ONLY mode, be sure all words have shifted out */
+	if (xfer->rx_buf == NULL) {
+		if (mcspi_wait_for_reg_bit(chstat_reg,
+				OMAP2_MCSPI_CHSTAT_TXS) < 0) {
+			dev_err(&spi->dev, "TXS timed out\n");
+			goto out;
+		}
+		if (mcspi_wait_for_reg_bit(chstat_reg,
+				OMAP2_MCSPI_CHSTAT_EOT) < 0)
+			dev_err(&spi->dev, "EOT timed out\n");
+out:
+		omap2_mcspi_set_enable(spi, 0);
+	}
+}
+
+/* called only when no transfer is active to this device */
+static int omap2_mcspi_setup_transfer(struct spi_device *spi,
+		struct spi_transfer *t)
+{
+	struct omap2_mcspi_cs *cs = spi->controller_state;
+	struct omap2_mcspi *mcspi;
+	u32 l = 0, div = 0;
+	u8 word_len = spi->bits_per_word;
+
+	mcspi = spi_master_get_devdata(spi->master);
+
+	if (t != NULL && t->bits_per_word)
+		word_len = t->bits_per_word;
+
+	cs->word_len = word_len;
+
+	if (spi->max_speed_hz) {
+		while (div <= 15 && (OMAP2_MCSPI_MAX_FREQ / (1 << div))
+					> spi->max_speed_hz)
+			div++;
+	} else
+		div = 15;
+
+	l = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
+
+	/* standard 4-wire master mode:  SCK, MOSI/out, MISO/in, nCS
+	 * REVISIT: this controller could support SPI_3WIRE mode.
+	 */
+	l &= ~(OMAP2_MCSPI_CHCONF_IS|OMAP2_MCSPI_CHCONF_DPE1);
+	l |= OMAP2_MCSPI_CHCONF_DPE0;
+
+	/* wordlength */
+	l &= ~OMAP2_MCSPI_CHCONF_WL_MASK;
+	l |= (word_len - 1) << 7;
+
+	/* set chipselect polarity; manage with FORCE */
+	if (!(spi->mode & SPI_CS_HIGH))
+		l |= OMAP2_MCSPI_CHCONF_EPOL;	/* active-low; normal */
+	else
+		l &= ~OMAP2_MCSPI_CHCONF_EPOL;
+
+	/* set clock divisor */
+	l &= ~OMAP2_MCSPI_CHCONF_CLKD_MASK;
+	l |= div << 2;
+
+	/* set SPI mode 0..3 */
+	if (spi->mode & SPI_CPOL)
+		l |= OMAP2_MCSPI_CHCONF_POL;
+	else
+		l &= ~OMAP2_MCSPI_CHCONF_POL;
+	if (spi->mode & SPI_CPHA)
+		l |= OMAP2_MCSPI_CHCONF_PHA;
+	else
+		l &= ~OMAP2_MCSPI_CHCONF_PHA;
+
+	mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCONF0, l);
+
+	dev_dbg(&spi->dev, "setup: speed %d, sample %s edge, clk %s\n",
+			OMAP2_MCSPI_MAX_FREQ / (1 << div),
+			(spi->mode & SPI_CPHA) ? "trailing" : "leading",
+			(spi->mode & SPI_CPOL) ? "inverted" : "normal");
+
+	return 0;
+}
+
+static void omap2_mcspi_dma_rx_callback(int lch, u16 ch_status, void *data)
+{
+	struct spi_device	*spi = data;
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_dma	*mcspi_dma;
+
+	mcspi = spi_master_get_devdata(spi->master);
+	mcspi_dma = &(mcspi->dma_channels[spi->chip_select]);
+
+	complete(&mcspi_dma->dma_rx_completion);
+
+	/* We must disable the DMA RX request */
+	omap2_mcspi_set_dma_req(spi, 1, 0);
+}
+
+static void omap2_mcspi_dma_tx_callback(int lch, u16 ch_status, void *data)
+{
+	struct spi_device	*spi = data;
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_dma	*mcspi_dma;
+
+	mcspi = spi_master_get_devdata(spi->master);
+	mcspi_dma = &(mcspi->dma_channels[spi->chip_select]);
+
+	complete(&mcspi_dma->dma_tx_completion);
+
+	/* We must disable the DMA TX request */
+	omap2_mcspi_set_dma_req(spi, 0, 0);
+}
+
+static int omap2_mcspi_request_dma(struct spi_device *spi)
+{
+	struct spi_master	*master = spi->master;
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_dma	*mcspi_dma;
+
+	mcspi = spi_master_get_devdata(master);
+	mcspi_dma = mcspi->dma_channels + spi->chip_select;
+
+	if (omap_request_dma(mcspi_dma->dma_rx_sync_dev, "McSPI RX",
+			omap2_mcspi_dma_rx_callback, spi,
+			&mcspi_dma->dma_rx_channel)) {
+		dev_err(&spi->dev, "no RX DMA channel for McSPI\n");
+		return -EAGAIN;
+	}
+
+	if (omap_request_dma(mcspi_dma->dma_tx_sync_dev, "McSPI TX",
+			omap2_mcspi_dma_tx_callback, spi,
+			&mcspi_dma->dma_tx_channel)) {
+		omap_free_dma(mcspi_dma->dma_rx_channel);
+		mcspi_dma->dma_rx_channel = -1;
+		dev_err(&spi->dev, "no TX DMA channel for McSPI\n");
+		return -EAGAIN;
+	}
+
+	init_completion(&mcspi_dma->dma_rx_completion);
+	init_completion(&mcspi_dma->dma_tx_completion);
+
+	return 0;
+}
+
+/* the spi->mode bits understood by this driver: */
+#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH)
+
+static int omap2_mcspi_setup(struct spi_device *spi)
+{
+	int			ret;
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_dma	*mcspi_dma;
+	struct omap2_mcspi_cs	*cs = spi->controller_state;
+
+	if (spi->mode & ~MODEBITS) {
+		dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n",
+			spi->mode & ~MODEBITS);
+		return -EINVAL;
+	}
+
+	if (spi->bits_per_word == 0)
+		spi->bits_per_word = 8;
+	else if (spi->bits_per_word < 4 || spi->bits_per_word > 32) {
+		dev_dbg(&spi->dev, "setup: unsupported %d bit words\n",
+			spi->bits_per_word);
+		return -EINVAL;
+	}
+
+	mcspi = spi_master_get_devdata(spi->master);
+	mcspi_dma = &mcspi->dma_channels[spi->chip_select];
+
+	if (!cs) {
+		cs = kzalloc(sizeof *cs, GFP_KERNEL);
+		if (!cs)
+			return -ENOMEM;
+		spi->controller_state = cs;
+	}
+
+	if (mcspi_dma->dma_rx_channel == -1
+			|| mcspi_dma->dma_tx_channel == -1) {
+		ret = omap2_mcspi_request_dma(spi);
+		if (ret < 0)
+			return ret;
+	}
+
+	clk_enable(mcspi->ick);
+	clk_enable(mcspi->fck);
+	ret =  omap2_mcspi_setup_transfer(spi, NULL);
+	clk_disable(mcspi->fck);
+	clk_disable(mcspi->ick);
+
+	return ret;
+}
+
+static void omap2_mcspi_cleanup(struct spi_device *spi)
+{
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_dma	*mcspi_dma;
+
+	mcspi = spi_master_get_devdata(spi->master);
+	mcspi_dma = &mcspi->dma_channels[spi->chip_select];
+
+	if (spi->controller_state != NULL)
+		kfree(spi->controller_state);
+
+	if (mcspi_dma->dma_rx_channel != -1) {
+		omap_free_dma(mcspi_dma->dma_rx_channel);
+		mcspi_dma->dma_rx_channel = -1;
+	}
+	if (mcspi_dma->dma_tx_channel != -1) {
+		omap_free_dma(mcspi_dma->dma_tx_channel);
+		mcspi_dma->dma_tx_channel = -1;
+	}
+}
+
+static void omap2_mcspi_work(struct work_struct *work)
+{
+	struct omap2_mcspi	*mcspi;
+
+	mcspi = container_of(work, struct omap2_mcspi, work);
+	spin_lock_irq(&mcspi->lock);
+
+	clk_enable(mcspi->ick);
+	clk_enable(mcspi->fck);
+
+	/* We only enable one channel at a time -- the one whose message is
+	 * at the head of the queue -- although this controller would gladly
+	 * arbitrate among multiple channels.  This corresponds to "single
+	 * channel" master mode.  As a side effect, we need to manage the
+	 * chipselect with the FORCE bit ... CS != channel enable.
+	 */
+	while (!list_empty(&mcspi->msg_queue)) {
+		struct spi_message		*m;
+		struct spi_device		*spi;
+		struct spi_transfer		*t = NULL;
+		int				cs_active = 0;
+		struct omap2_mcspi_device_config *conf;
+		struct omap2_mcspi_cs		*cs;
+		int				par_override = 0;
+		int status = 0;
+
+		m = container_of(mcspi->msg_queue.next, struct spi_message,
+				 queue);
+
+		list_del_init(&m->queue);
+		spin_unlock_irq(&mcspi->lock);
+
+		spi = m->spi;
+		conf = spi->controller_data;
+		cs = spi->controller_state;
+
+		list_for_each_entry(t, &m->transfers, transfer_list) {
+			if (t->tx_buf == NULL && t->rx_buf == NULL && t->len) {
+				status = -EINVAL;
+				break;
+			}
+			if (par_override || t->speed_hz || t->bits_per_word) {
+				par_override = 1;
+				status = omap2_mcspi_setup_transfer(spi, t);
+				if (status < 0)
+					break;
+				if (!t->speed_hz && !t->bits_per_word)
+					par_override = 0;
+			}
+
+			if (!cs_active) {
+				omap2_mcspi_force_cs(spi, 1);
+				cs_active = 1;
+			}
+
+			if (m->is_dma_mapped
+					&& (t->tx_dma != 0 || t->rx_dma != 0))
+				omap2_mcspi_txrx_dma(spi, t);
+			else
+				omap2_mcspi_txrx_pio(spi, t);
+
+			if (t->delay_usecs)
+				udelay(t->delay_usecs);
+
+			/* this ignores the "leave it on after last xfer" hint */
+			if (t->cs_change) {
+				omap2_mcspi_force_cs(spi, 0);
+				cs_active = 0;
+			}
+		}
+
+		/* Restore defaults if they were overriden */
+		if (par_override) {
+			par_override = 0;
+			status = omap2_mcspi_setup_transfer(spi, NULL);
+		}
+
+		if (cs_active)
+			omap2_mcspi_force_cs(spi, 0);
+
+		m->status = status;
+		m->complete(m->context);
+
+		spin_lock_irq(&mcspi->lock);
+	}
+
+	clk_disable(mcspi->fck);
+	clk_disable(mcspi->ick);
+
+	spin_unlock_irq(&mcspi->lock);
+}
+
+static int omap2_mcspi_transfer(struct spi_device *spi, struct spi_message *m)
+{
+	struct omap2_mcspi	*mcspi;
+	unsigned long		flags;
+	struct spi_transfer	*t;
+
+	m->actual_length = 0;
+	m->status = 0;
+
+	/* reject invalid messages and transfers */
+	if (list_empty(&m->transfers) || !m->complete)
+		return -EINVAL;
+	list_for_each_entry(t, &m->transfers, transfer_list) {
+		if (t->speed_hz > OMAP2_MCSPI_MAX_FREQ
+				|| (t->len && !(t->rx_buf || t->tx_buf))
+				|| (t->bits_per_word &&
+					(  t->bits_per_word < 4
+					|| t->bits_per_word > 32))) {
+			dev_dbg(&spi->dev, "transfer: %d Hz, %d %s%s, %d bpw\n",
+					t->speed_hz,
+					t->len,
+					t->rx_buf ? "rx" : "",
+					t->tx_buf ? "tx" : "",
+					t->bits_per_word);
+			return -EINVAL;
+		}
+		if (t->speed_hz && t->speed_hz < OMAP2_MCSPI_MAX_FREQ/(1<<16)) {
+			dev_dbg(&spi->dev, "%d Hz max exceeds %d\n",
+					t->speed_hz,
+					t->speed_hz < OMAP2_MCSPI_MAX_FREQ/(1<<16));
+			return -EINVAL;
+		}
+
+		/* REVISIT move dma mapping to here */
+	}
+
+	mcspi = spi_master_get_devdata(spi->master);
+
+	spin_lock_irqsave(&mcspi->lock, flags);
+	list_add_tail(&m->queue, &mcspi->msg_queue);
+	spin_unlock_irqrestore(&mcspi->lock, flags);
+
+	queue_work(omap2_mcspi_wq, &mcspi->work);
+
+	return 0;
+}
+
+static int __init omap2_mcspi_reset(struct omap2_mcspi *mcspi)
+{
+	struct spi_master	*master = mcspi->master;
+	u32			tmp;
+
+	clk_enable(mcspi->ick);
+	clk_enable(mcspi->fck);
+
+	mcspi_write_reg(master, OMAP2_MCSPI_SYSCONFIG,
+			OMAP2_MCSPI_SYSCONFIG_SOFTRESET);
+	do {
+		tmp = mcspi_read_reg(master, OMAP2_MCSPI_SYSSTATUS);
+	} while (!(tmp & OMAP2_MCSPI_SYSSTATUS_RESETDONE));
+
+	mcspi_write_reg(master, OMAP2_MCSPI_SYSCONFIG,
+			// (3 << 8) | (2 << 3) |
+			OMAP2_MCSPI_SYSCONFIG_AUTOIDLE);
+
+	omap2_mcspi_set_master_mode(master);
+
+	clk_disable(mcspi->fck);
+	clk_disable(mcspi->ick);
+	return 0;
+}
+
+static u8 __initdata spi1_rxdma_id [] = {
+	OMAP24XX_DMA_SPI1_RX0,
+	OMAP24XX_DMA_SPI1_RX1,
+	OMAP24XX_DMA_SPI1_RX2,
+	OMAP24XX_DMA_SPI1_RX3,
+};
+
+static u8 __initdata spi1_txdma_id [] = {
+	OMAP24XX_DMA_SPI1_TX0,
+	OMAP24XX_DMA_SPI1_TX1,
+	OMAP24XX_DMA_SPI1_TX2,
+	OMAP24XX_DMA_SPI1_TX3,
+};
+
+static u8 __initdata spi2_rxdma_id[] = {
+	OMAP24XX_DMA_SPI2_RX0,
+	OMAP24XX_DMA_SPI2_RX1,
+};
+
+static u8 __initdata spi2_txdma_id[] = {
+	OMAP24XX_DMA_SPI2_TX0,
+	OMAP24XX_DMA_SPI2_TX1,
+};
+
+static int __init omap2_mcspi_probe(struct platform_device *pdev)
+{
+	struct spi_master	*master;
+	struct omap2_mcspi	*mcspi;
+	struct resource		*r;
+	int			status = 0, i;
+	const u8		*rxdma_id, *txdma_id;
+	unsigned		num_chipselect;
+
+	switch (pdev->id) {
+	case 1:
+		rxdma_id = spi1_rxdma_id;
+		txdma_id = spi1_txdma_id;
+		num_chipselect = 4;
+		break;
+	case 2:
+		rxdma_id = spi2_rxdma_id;
+		txdma_id = spi2_txdma_id;
+		num_chipselect = 2;
+		break;
+	/* REVISIT omap2430 has a third McSPI ... */
+	default:
+		return -EINVAL;
+	}
+
+	master = spi_alloc_master(&pdev->dev, sizeof *mcspi);
+	if (master == NULL) {
+		dev_dbg(&pdev->dev, "master allocation failed\n");
+		return -ENOMEM;
+	}
+
+	if (pdev->id != -1)
+		master->bus_num = pdev->id;
+
+	master->setup = omap2_mcspi_setup;
+	master->transfer = omap2_mcspi_transfer;
+	master->cleanup = omap2_mcspi_cleanup;
+	master->num_chipselect = num_chipselect;
+
+	dev_set_drvdata(&pdev->dev, master);
+
+	mcspi = spi_master_get_devdata(master);
+	mcspi->master = master;
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (r == NULL) {
+		status = -ENODEV;
+		goto err1;
+	}
+	if (!request_mem_region(r->start, (r->end - r->start) + 1,
+			pdev->dev.bus_id)) {
+		status = -EBUSY;
+		goto err1;
+	}
+
+	mcspi->base = io_p2v(r->start);
+
+	INIT_WORK(&mcspi->work, omap2_mcspi_work);
+
+	spin_lock_init(&mcspi->lock);
+	INIT_LIST_HEAD(&mcspi->msg_queue);
+
+	mcspi->ick = clk_get(&pdev->dev, "mcspi_ick");
+	if (IS_ERR(mcspi->ick)) {
+		dev_dbg(&pdev->dev, "can't get mcspi_ick\n");
+		status = PTR_ERR(mcspi->ick);
+		goto err1a;
+	}
+	mcspi->fck = clk_get(&pdev->dev, "mcspi_fck");
+	if (IS_ERR(mcspi->fck)) {
+		dev_dbg(&pdev->dev, "can't get mcspi_fck\n");
+		status = PTR_ERR(mcspi->fck);
+		goto err2;
+	}
+
+	mcspi->dma_channels = kcalloc(master->num_chipselect,
+			sizeof(struct omap2_mcspi_dma),
+			GFP_KERNEL);
+
+	if (mcspi->dma_channels == NULL)
+		goto err3;
+
+	for (i = 0; i < num_chipselect; i++) {
+		mcspi->dma_channels[i].dma_rx_channel = -1;
+		mcspi->dma_channels[i].dma_rx_sync_dev = rxdma_id[i];
+		mcspi->dma_channels[i].dma_tx_channel = -1;
+		mcspi->dma_channels[i].dma_tx_sync_dev = txdma_id[i];
+	}
+
+	if (omap2_mcspi_reset(mcspi) < 0)
+		goto err4;
+
+	status = spi_register_master(master);
+	if (status < 0)
+		goto err4;
+
+	return status;
+
+err4:
+	kfree(mcspi->dma_channels);
+err3:
+	clk_put(mcspi->fck);
+err2:
+	clk_put(mcspi->ick);
+err1a:
+	release_mem_region(r->start, (r->end - r->start) + 1);
+err1:
+	spi_master_put(master);
+	return status;
+}
+
+static int __exit omap2_mcspi_remove(struct platform_device *pdev)
+{
+	struct spi_master	*master;
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_dma	*dma_channels;
+	struct resource		*r;
+
+	master = dev_get_drvdata(&pdev->dev);
+	mcspi = spi_master_get_devdata(master);
+	dma_channels = mcspi->dma_channels;
+
+	clk_put(mcspi->fck);
+	clk_put(mcspi->ick);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(r->start, (r->end - r->start) + 1);
+
+	spi_unregister_master(master);
+	kfree(dma_channels);
+
+	return 0;
+}
+
+static struct platform_driver omap2_mcspi_driver = {
+	.driver = {
+		.name =		"omap2_mcspi",
+		.owner =	THIS_MODULE,
+	},
+	.remove =	__exit_p(omap2_mcspi_remove),
+};
+
+
+static int __init omap2_mcspi_init(void)
+{
+	omap2_mcspi_wq = create_workqueue(omap2_mcspi_driver.driver.name);
+	if (omap2_mcspi_wq == NULL)
+		return -1;
+	return platform_driver_probe(&omap2_mcspi_driver, omap2_mcspi_probe);
+}
+subsys_initcall(omap2_mcspi_init);
+
+static void __exit omap2_mcspi_exit(void)
+{
+	platform_driver_unregister(&omap2_mcspi_driver);
+
+	destroy_workqueue(omap2_mcspi_wq);
+}
+module_exit(omap2_mcspi_exit);
+
+MODULE_LICENSE("GPL");
-- 
1.5.0


[-- Attachment #3: Type: text/plain, Size: 286 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/

[-- Attachment #4: Type: text/plain, Size: 210 bytes --]

_______________________________________________
spi-devel-general mailing list
spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
https://lists.sourceforge.net/lists/listinfo/spi-devel-general

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

* Re: [PATCH] OMAP: Add OMAP24XX Multichannel SPI controller driver
       [not found] ` <5d5443650706182323k4cc99707v8ad8dc5cff24ca3e-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2007-06-20 23:30   ` David Brownell
       [not found]     ` <200706201630.04647.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
  0 siblings, 1 reply; 7+ messages in thread
From: David Brownell @ 2007-06-20 23:30 UTC (permalink / raw)
  To: Trilok Soni
  Cc: juha.yrjola-EmnPodGKVbzby3iVrkZq2A,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
	Tony Lindgren, samuel.ortiz-EmnPodGKVbzby3iVrkZq2A

That's pretty much the latest from omap-git, right?

If so, please try the appended patch, which includes various
bugfixes and cleanups I've had in my queue for a while.

- Dave


==============
More updates:

  Bugfixes:
    - Address "last RX-only PIO read" issue noted by Imre
    - Track number of bytes transferred, and return the total
    - Report short transfers as I/O errors
    - Use DMA automatically on larger transfers (vs virtually never)

  Cleanups:
    - Move code from PIO and DMA per-transfer code to work loop:
	    * device enable/disable
	    * RX_ONLY tx data init
	    * channel config updates
    - Move DMA mapping from work loop to pre-submit logic
    - Queue work before dropping the queue lock
    - Use "void __iomem *" for pio addresses, not "unsigned long"
    - Compute register bank base address just once
    - A few more whitespace tweaks

Those cleanups save more code space, mostly on the critical I/O path.

The DMA stuff is a bugfix, since the previous interpretation of the API
was contrary to spec and intention.  Needing a heuristic is annoying,
but seems inescapable.

---
 drivers/spi/omap2_mcspi.c |  232 +++++++++++++++++++++++++---------------------
 1 file changed, 131 insertions(+), 101 deletions(-)

--- h4.orig/drivers/spi/omap2_mcspi.c	2007-05-31 17:01:08.000000000 -0700
+++ h4/drivers/spi/omap2_mcspi.c	2007-05-31 22:58:39.000000000 -0700
@@ -38,6 +38,7 @@
 #include <asm/arch/dma.h>
 #include <asm/arch/clock.h>
 
+
 #define OMAP2_MCSPI_MAX_FREQ		48000000
 
 #define OMAP2_MCSPI_REVISION		0x00
@@ -102,6 +103,12 @@ struct omap2_mcspi_dma {
 	struct completion dma_rx_completion;
 };
 
+/* use PIO for small transfers, avoiding DMA setup/teardown overhead and
+ * cache operations; better heuristics consider wordsize and bitrate.
+ */
+#define DMA_MIN_BYTES			8
+
+
 struct omap2_mcspi {
 	struct work_struct	work;
 	spinlock_t		lock;
@@ -110,17 +117,17 @@ struct omap2_mcspi {
 	struct clk		*ick;
 	struct clk		*fck;
 	/* Virtual base address of the controller */
-	unsigned long		base;
+	void __iomem		*base;
 	/* SPI1 has 4 channels, while SPI2 has 2 */
 	struct omap2_mcspi_dma	*dma_channels;
 };
 
 struct omap2_mcspi_cs {
-	u8 transmit_mode;
-	int word_len;
+	void __iomem		*base;
+	int			word_len;
 };
 
-static struct workqueue_struct * omap2_mcspi_wq;
+static struct workqueue_struct *omap2_mcspi_wq;
 
 #define MOD_REG_BIT(val, mask, set) do { \
 	if (set) \
@@ -137,8 +144,7 @@ static inline void mcspi_write_reg(struc
 	__raw_writel(val, mcspi->base + idx);
 }
 
-static inline u32 mcspi_read_reg(struct spi_master *master,
-		int idx)
+static inline u32 mcspi_read_reg(struct spi_master *master, int idx)
 {
 	struct omap2_mcspi *mcspi = spi_master_get_devdata(master);
 
@@ -148,17 +154,16 @@ static inline u32 mcspi_read_reg(struct 
 static inline void mcspi_write_cs_reg(const struct spi_device *spi,
 		int idx, u32 val)
 {
-	struct omap2_mcspi *mcspi = spi_master_get_devdata(spi->master);
+	struct omap2_mcspi_cs	*cs = spi->controller_state;
 
-	__raw_writel(val, mcspi->base + spi->chip_select * 0x14 + idx);
+	__raw_writel(val, cs->base +  idx);
 }
 
-static inline u32 mcspi_read_cs_reg(const struct spi_device *spi,
-		int idx)
+static inline u32 mcspi_read_cs_reg(const struct spi_device *spi, int idx)
 {
-	struct omap2_mcspi *mcspi = spi_master_get_devdata(spi->master);
+	struct omap2_mcspi_cs	*cs = spi->controller_state;
 
-	return __raw_readl(mcspi->base + spi->chip_select * 0x14 + idx);
+	return __raw_readl(cs->base + idx);
 }
 
 static void omap2_mcspi_set_dma_req(const struct spi_device *spi,
@@ -208,7 +213,7 @@ static void omap2_mcspi_set_master_mode(
 	mcspi_write_reg(master, OMAP2_MCSPI_MODULCTRL, l);
 }
 
-static void
+static unsigned
 omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
 {
 	struct omap2_mcspi	*mcspi;
@@ -219,7 +224,6 @@ omap2_mcspi_txrx_dma(struct spi_device *
 	int			word_len, data_type, element_count;
 	u8			* rx;
 	const u8		* tx;
-	u32			l;
 
 	mcspi = spi_master_get_devdata(spi->master);
 	mcspi_dma = &mcspi->dma_channels[spi->chip_select];
@@ -228,17 +232,7 @@ omap2_mcspi_txrx_dma(struct spi_device *
 	c = count;
 	word_len = cs->word_len;
 
-	l = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
-	l &= ~OMAP2_MCSPI_CHCONF_TRM_MASK;
-	if (xfer->tx_buf == NULL)
-		l |= OMAP2_MCSPI_CHCONF_TRM_RX_ONLY;
-	else if (xfer->rx_buf == NULL)
-		l |= OMAP2_MCSPI_CHCONF_TRM_TX_ONLY;
-	mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCONF0, l);
-
-	omap2_mcspi_set_enable(spi, 1);
-
-	base = io_v2p(mcspi->base) + spi->chip_select * 0x14;
+	base = (unsigned long) io_v2p(cs->base);
 	tx_reg = base + OMAP2_MCSPI_TX0;
 	rx_reg = base + OMAP2_MCSPI_RX0;
 	rx = xfer->rx_buf;
@@ -250,26 +244,12 @@ omap2_mcspi_txrx_dma(struct spi_device *
 	} else if (word_len <= 16) {
 		data_type = OMAP_DMA_DATA_TYPE_S16;
 		element_count = count >> 1;
-	} else if (word_len <= 32) {
+	} else /* word_len <= 32 */ {
 		data_type = OMAP_DMA_DATA_TYPE_S32;
 		element_count = count >> 2;
-	} else
-		goto out;
-
-	/* RX_ONLY mode needs dummy data in TX reg */
-	if (tx == NULL)
-		__raw_writel(0, mcspi->base
-					+ spi->chip_select * 0x14
-					+ OMAP2_MCSPI_TX0);
+	}
 
 	if (tx != NULL) {
-		xfer->tx_dma = dma_map_single(&spi->dev, (void *) tx, count,
-				DMA_TO_DEVICE);
-		if (dma_mapping_error(xfer->tx_dma)) {
-			dev_err(&spi->dev, "dma TX %d bytes error\n", count);
-			return;
-		}
-
 		omap_set_dma_transfer_params(mcspi_dma->dma_tx_channel,
 				data_type, element_count, 1,
 				OMAP_DMA_SYNC_ELEMENT,
@@ -285,16 +265,6 @@ omap2_mcspi_txrx_dma(struct spi_device *
 	}
 
 	if (rx != NULL) {
-		xfer->rx_dma = dma_map_single(&spi->dev, rx, count,
-				DMA_FROM_DEVICE);
-		if (dma_mapping_error(xfer->rx_dma)) {
-			dev_err(&spi->dev, "dma RX %d bytes error\n", count);
-			if (tx != NULL)
-				dma_unmap_single(NULL, xfer->tx_dma,
-						count, DMA_TO_DEVICE);
-			goto out;
-		}
-
 		omap_set_dma_transfer_params(mcspi_dma->dma_rx_channel,
 				data_type, element_count, 1,
 				OMAP_DMA_SYNC_ELEMENT,
@@ -328,11 +298,10 @@ omap2_mcspi_txrx_dma(struct spi_device *
 		wait_for_completion(&mcspi_dma->dma_rx_completion);
 		dma_unmap_single(NULL, xfer->rx_dma, count, DMA_FROM_DEVICE);
 	}
-out:
-	omap2_mcspi_set_enable(spi, 0);
+	return count;
 }
 
-static int mcspi_wait_for_reg_bit(unsigned long reg, unsigned long bit)
+static int mcspi_wait_for_reg_bit(void __iomem *reg, unsigned long bit)
 {
 	unsigned long timeout;
 
@@ -344,14 +313,17 @@ static int mcspi_wait_for_reg_bit(unsign
 	return 0;
 }
 
-static void
+static unsigned
 omap2_mcspi_txrx_pio(struct spi_device *spi, struct spi_transfer *xfer)
 {
 	struct omap2_mcspi	*mcspi;
 	struct omap2_mcspi_cs	*cs = spi->controller_state;
 	unsigned int		count, c;
 	u32			l;
-	unsigned long		base, tx_reg, rx_reg, chstat_reg;
+	void __iomem		*base = cs->base;
+	void __iomem		*tx_reg;
+	void __iomem		*rx_reg;
+	void __iomem		*chstat_reg;
 	int			word_len;
 
 	mcspi = spi_master_get_devdata(spi->master);
@@ -361,29 +333,13 @@ omap2_mcspi_txrx_pio(struct spi_device *
 
 	l = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
 	l &= ~OMAP2_MCSPI_CHCONF_TRM_MASK;
-	if (xfer->tx_buf == NULL)
-		l |= OMAP2_MCSPI_CHCONF_TRM_RX_ONLY;
-	else if (xfer->rx_buf == NULL)
-		l |= OMAP2_MCSPI_CHCONF_TRM_TX_ONLY;
-	mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCONF0, l);
-
-	omap2_mcspi_set_enable(spi, 1);
 
 	/* We store the pre-calculated register addresses on stack to speed
 	 * up the transfer loop. */
-	base = mcspi->base + spi->chip_select * 0x14;
 	tx_reg		= base + OMAP2_MCSPI_TX0;
 	rx_reg		= base + OMAP2_MCSPI_RX0;
 	chstat_reg	= base + OMAP2_MCSPI_CHSTAT0;
 
-	/* RX_ONLY mode needs dummy data in TX reg.   If we were using
-	 * TURBO mode (double buffered) we'd need to disable the channel
-	 * before reading the penultimate word ... so TURBO wouldn't be an
-	 * option except for the last transfer, else if cs_change is set.
-	 */
-	if (xfer->tx_buf == NULL)
-		__raw_writel(0, tx_reg);
-
 	if (word_len <= 8) {
 		u8		*rx;
 		const u8	*tx;
@@ -391,7 +347,7 @@ omap2_mcspi_txrx_pio(struct spi_device *
 		rx = xfer->rx_buf;
 		tx = xfer->tx_buf;
 
-		while (c--) {
+		do {
 			if (tx != NULL) {
 				if (mcspi_wait_for_reg_bit(chstat_reg,
 						OMAP2_MCSPI_CHSTAT_TXS) < 0) {
@@ -410,21 +366,27 @@ omap2_mcspi_txrx_pio(struct spi_device *
 					dev_err(&spi->dev, "RXS timed out\n");
 					goto out;
 				}
+				/* prevent last RX_ONLY read from triggering
+				 * more word i/o: switch to rx+tx
+				 */
+				if (c == 0 && tx == NULL)
+					mcspi_write_cs_reg(spi,
+							OMAP2_MCSPI_CHCONF0, l);
 				*rx++ = __raw_readl(rx_reg);
 #ifdef VERBOSE
 				dev_dbg(&spi->dev, "read-%d %02x\n",
 						word_len, *(rx - 1));
 #endif
 			}
-		}
+			c -= 1;
+		} while (c);
 	} else if (word_len <= 16) {
 		u16		*rx;
 		const u16	*tx;
 
 		rx = xfer->rx_buf;
 		tx = xfer->tx_buf;
-		c >>= 1;
-		while (c--) {
+		do {
 			if (tx != NULL) {
 				if (mcspi_wait_for_reg_bit(chstat_reg,
 						OMAP2_MCSPI_CHSTAT_TXS) < 0) {
@@ -443,21 +405,27 @@ omap2_mcspi_txrx_pio(struct spi_device *
 					dev_err(&spi->dev, "RXS timed out\n");
 					goto out;
 				}
+				/* prevent last RX_ONLY read from triggering
+				 * more word i/o: switch to rx+tx
+				 */
+				if (c == 0 && tx == NULL)
+					mcspi_write_cs_reg(spi,
+							OMAP2_MCSPI_CHCONF0, l);
 				*rx++ = __raw_readl(rx_reg);
 #ifdef VERBOSE
 				dev_dbg(&spi->dev, "read-%d %04x\n",
 						word_len, *(rx - 1));
 #endif
 			}
-		}
+			c -= 2;
+		} while (c);
 	} else if (word_len <= 32) {
 		u32		*rx;
 		const u32	*tx;
 
 		rx = xfer->rx_buf;
 		tx = xfer->tx_buf;
-		c >>= 2;
-		while (c--) {
+		do {
 			if (tx != NULL) {
 				if (mcspi_wait_for_reg_bit(chstat_reg,
 						OMAP2_MCSPI_CHSTAT_TXS) < 0) {
@@ -476,13 +444,20 @@ omap2_mcspi_txrx_pio(struct spi_device *
 					dev_err(&spi->dev, "RXS timed out\n");
 					goto out;
 				}
+				/* prevent last RX_ONLY read from triggering
+				 * more word i/o: switch to rx+tx
+				 */
+				if (c == 0 && tx == NULL)
+					mcspi_write_cs_reg(spi,
+							OMAP2_MCSPI_CHCONF0, l);
 				*rx++ = __raw_readl(rx_reg);
 #ifdef VERBOSE
 				dev_dbg(&spi->dev, "read-%d %04x\n",
 						word_len, *(rx - 1));
 #endif
 			}
-		}
+			c -= 4;
+		} while (c);
 	}
 
 	/* for TX_ONLY mode, be sure all words have shifted out */
@@ -490,14 +465,12 @@ omap2_mcspi_txrx_pio(struct spi_device *
 		if (mcspi_wait_for_reg_bit(chstat_reg,
 				OMAP2_MCSPI_CHSTAT_TXS) < 0) {
 			dev_err(&spi->dev, "TXS timed out\n");
-			goto out;
-		}
-		if (mcspi_wait_for_reg_bit(chstat_reg,
+		} else if (mcspi_wait_for_reg_bit(chstat_reg,
 				OMAP2_MCSPI_CHSTAT_EOT) < 0)
 			dev_err(&spi->dev, "EOT timed out\n");
-out:
-		omap2_mcspi_set_enable(spi, 0);
 	}
+out:
+	return count - c;
 }
 
 /* called only when no transfer is active to this device */
@@ -657,6 +630,7 @@ static int omap2_mcspi_setup(struct spi_
 		cs = kzalloc(sizeof *cs, GFP_KERNEL);
 		if (!cs)
 			return -ENOMEM;
+		cs->base = mcspi->base + spi->chip_select * 0x14;
 		spi->controller_state = cs;
 	}
 
@@ -721,7 +695,8 @@ static void omap2_mcspi_work(struct work
 		struct omap2_mcspi_device_config *conf;
 		struct omap2_mcspi_cs		*cs;
 		int				par_override = 0;
-		int status = 0;
+		int				status = 0;
+		u32				chconf;
 
 		m = container_of(mcspi->msg_queue.next, struct spi_message,
 				 queue);
@@ -733,6 +708,7 @@ static void omap2_mcspi_work(struct work
 		conf = spi->controller_data;
 		cs = spi->controller_state;
 
+		omap2_mcspi_set_enable(spi, 1);
 		list_for_each_entry(t, &m->transfers, transfer_list) {
 			if (t->tx_buf == NULL && t->rx_buf == NULL && t->len) {
 				status = -EINVAL;
@@ -752,16 +728,37 @@ static void omap2_mcspi_work(struct work
 				cs_active = 1;
 			}
 
-			if (m->is_dma_mapped
-					&& (t->tx_dma != 0 || t->rx_dma != 0))
-				omap2_mcspi_txrx_dma(spi, t);
-			else
-				omap2_mcspi_txrx_pio(spi, t);
+			chconf = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
+			chconf &= ~OMAP2_MCSPI_CHCONF_TRM_MASK;
+			if (t->tx_buf == NULL)
+				chconf |= OMAP2_MCSPI_CHCONF_TRM_RX_ONLY;
+			else if (t->rx_buf == NULL)
+				chconf |= OMAP2_MCSPI_CHCONF_TRM_TX_ONLY;
+			mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCONF0, chconf);
+
+			if (t->len) {
+				unsigned	count;
+
+				/* RX_ONLY mode needs dummy data in TX reg */
+				if (t->tx_buf == NULL)
+					__raw_writel(0, cs->base + OMAP2_MCSPI_TX0);
+
+				if (m->is_dma_mapped || t->len >= DMA_MIN_BYTES)
+					count = omap2_mcspi_txrx_dma(spi, t);
+				else
+					count = omap2_mcspi_txrx_pio(spi, t);
+				m->actual_length += count;
+
+				if (count != t->len) {
+					status = -EIO;
+					break;
+				}
+			}
 
 			if (t->delay_usecs)
 				udelay(t->delay_usecs);
 
-			/* this ignores the "leave it on after last xfer" hint */
+			/* ignore the "leave it on after last xfer" hint */
 			if (t->cs_change) {
 				omap2_mcspi_force_cs(spi, 0);
 				cs_active = 0;
@@ -777,6 +774,8 @@ static void omap2_mcspi_work(struct work
 		if (cs_active)
 			omap2_mcspi_force_cs(spi, 0);
 
+		omap2_mcspi_set_enable(spi, 0);
+
 		m->status = status;
 		m->complete(m->context);
 
@@ -802,36 +801,67 @@ static int omap2_mcspi_transfer(struct s
 	if (list_empty(&m->transfers) || !m->complete)
 		return -EINVAL;
 	list_for_each_entry(t, &m->transfers, transfer_list) {
+		const void	*tx_buf = t->tx_buf;
+		void		*rx_buf = t->rx_buf;
+		unsigned	len = t->len;
+
 		if (t->speed_hz > OMAP2_MCSPI_MAX_FREQ
-				|| (t->len && !(t->rx_buf || t->tx_buf))
+				|| (len && !(rx_buf || tx_buf))
 				|| (t->bits_per_word &&
 					(  t->bits_per_word < 4
 					|| t->bits_per_word > 32))) {
 			dev_dbg(&spi->dev, "transfer: %d Hz, %d %s%s, %d bpw\n",
 					t->speed_hz,
-					t->len,
-					t->rx_buf ? "rx" : "",
-					t->tx_buf ? "tx" : "",
+					len,
+					tx_buf ? "tx" : "",
+					rx_buf ? "rx" : "",
 					t->bits_per_word);
 			return -EINVAL;
 		}
 		if (t->speed_hz && t->speed_hz < OMAP2_MCSPI_MAX_FREQ/(1<<16)) {
 			dev_dbg(&spi->dev, "%d Hz max exceeds %d\n",
 					t->speed_hz,
-					t->speed_hz < OMAP2_MCSPI_MAX_FREQ/(1<<16));
+					OMAP2_MCSPI_MAX_FREQ/(1<<16));
 			return -EINVAL;
 		}
 
-		/* REVISIT move dma mapping to here */
+		if (m->is_dma_mapped || len < DMA_MIN_BYTES)
+			continue;
+
+		/* Do DMA mapping "early" for better error reporting and
+		 * dcache use.  Note that if dma_unmap_single() ever starts
+		 * to do real work on ARM, we'd need to clean up mappings
+		 * for previous transfers on *ALL* exits of this loop...
+		 */
+		if (tx_buf != NULL) {
+			t->tx_dma = dma_map_single(&spi->dev, (void *) tx_buf,
+					len, DMA_TO_DEVICE);
+			if (dma_mapping_error(t->tx_dma)) {
+				dev_dbg(&spi->dev, "dma %cX %d bytes error\n",
+						'T', len);
+				return -EINVAL;
+			}
+		}
+		if (rx_buf != NULL) {
+			t->rx_dma = dma_map_single(&spi->dev, rx_buf, t->len,
+					DMA_FROM_DEVICE);
+			if (dma_mapping_error(t->rx_dma)) {
+				dev_dbg(&spi->dev, "dma %cX %d bytes error\n",
+						'R', len);
+				if (tx_buf != NULL)
+					dma_unmap_single(NULL, t->tx_dma,
+							len, DMA_TO_DEVICE);
+				return -EINVAL;
+			}
+		}
 	}
 
 	mcspi = spi_master_get_devdata(spi->master);
 
 	spin_lock_irqsave(&mcspi->lock, flags);
 	list_add_tail(&m->queue, &mcspi->msg_queue);
-	spin_unlock_irqrestore(&mcspi->lock, flags);
-
 	queue_work(omap2_mcspi_wq, &mcspi->work);
+	spin_unlock_irqrestore(&mcspi->lock, flags);
 
 	return 0;
 }
@@ -940,7 +970,7 @@ static int __init omap2_mcspi_probe(stru
 		goto err1;
 	}
 
-	mcspi->base = io_p2v(r->start);
+	mcspi->base = (void __iomem *) io_p2v(r->start);
 
 	INIT_WORK(&mcspi->work, omap2_mcspi_work);
 

-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/

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

* Re: [PATCH] OMAP: Add OMAP24XX Multichannel SPI controller driver
       [not found]     ` <200706201630.04647.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
@ 2007-06-21  7:37       ` Trilok Soni
       [not found]         ` <5d5443650706210037k6562d50frc7fc2f261a2e9352-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 7+ messages in thread
From: Trilok Soni @ 2007-06-21  7:37 UTC (permalink / raw)
  To: David Brownell
  Cc: juha.yrjola-EmnPodGKVbzby3iVrkZq2A,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
	Tony Lindgren, samuel.ortiz-EmnPodGKVbzby3iVrkZq2A

[-- Attachment #1: Type: text/plain, Size: 616 bytes --]

On 6/21/07, David Brownell <david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org> wrote:
> That's pretty much the latest from omap-git, right?
>
> If so, please try the appended patch, which includes various
> bugfixes and cleanups I've had in my queue for a while.
>

Thanx David. Yes it is from latest omap-git. Tony may like to apply
this patch to omap-git, instead of waiting it to come from upstream.

I have appended your patch with mine and attached that patch, it is
now build tested only, someone need to help in testing this patch as
other team here took my H4 for some high-priority work :(

-- 
--Trilok Soni

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-OMAP-Add-OMAP24XX-Multichannel-SPI-controller-driver.patch --]
[-- Type: text/x-patch; name=0001-OMAP-Add-OMAP24XX-Multichannel-SPI-controller-driver.patch; charset=ANSI_X3.4-1968, Size: 29902 bytes --]

From e651a074a923abe61cde15c2b0a2ee97aee1899c Mon Sep 17 00:00:00 2001
From: Samuel Ortiz <samuel.ortiz@solidboot.com>
Date: Thu, 21 Jun 2007 18:36:53 +0530
Subject: [PATCH] OMAP: Add OMAP24XX Multichannel SPI controller driver

- Add OMAP24XX Multichannel SPI controller driver. This
  driver is tested very well under OMAP GIT tree with
  N800 - Nokia Internet Tablet.

Signed-off-by: Trilok Soni <soni.trilok@gmail.com>
---
 drivers/spi/Kconfig       |    5 +
 drivers/spi/Makefile      |    1 +
 drivers/spi/omap2_mcspi.c | 1078 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1084 insertions(+), 0 deletions(-)
 create mode 100644 drivers/spi/omap2_mcspi.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 5e3f748..dfd51b4 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -133,6 +133,11 @@ config SPI_OMAP_UWIRE
 	help
 	  This hooks up to the MicroWire controller on OMAP1 chips.
 
+config SPI_OMAP24XX
+	tristate "McSPI driver for OMAP24xx"
+	depends on SPI_MASTER && ARCH_OMAP24XX
+	help
+	  SPI master controller for OMAP24xx McSPI modules.
 
 config SPI_PXA2XX
 	tristate "PXA2xx SSP SPI master"
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 5788d86..4e52f93 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_SPI_BUTTERFLY)		+= spi_butterfly.o
 obj-$(CONFIG_SPI_IMX)			+= spi_imx.o
 obj-$(CONFIG_SPI_PXA2XX)		+= pxa2xx_spi.o
 obj-$(CONFIG_SPI_OMAP_UWIRE)		+= omap_uwire.o
+obj-$(CONFIG_SPI_OMAP24XX)		+= omap2_mcspi.o
 obj-$(CONFIG_SPI_MPC52xx_PSC)		+= mpc52xx_psc_spi.o
 obj-$(CONFIG_SPI_MPC83xx)		+= spi_mpc83xx.o
 obj-$(CONFIG_SPI_S3C24XX_GPIO)		+= spi_s3c24xx_gpio.o
diff --git a/drivers/spi/omap2_mcspi.c b/drivers/spi/omap2_mcspi.c
new file mode 100644
index 0000000..dbff420
--- /dev/null
+++ b/drivers/spi/omap2_mcspi.c
@@ -0,0 +1,1078 @@
+/*
+ * OMAP2 McSPI controller driver
+ *
+ * Copyright (C) 2005, 2006 Nokia Corporation
+ * Author:	Samuel Ortiz <samuel.ortiz@nokia.com> and
+ *		Juha Yrjölä <juha.yrjola@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+
+#include <linux/spi/spi.h>
+
+#include <asm/io.h>
+#include <asm/arch/dma.h>
+#include <asm/arch/clock.h>
+
+
+#define OMAP2_MCSPI_MAX_FREQ		48000000
+
+#define OMAP2_MCSPI_REVISION		0x00
+#define OMAP2_MCSPI_SYSCONFIG		0x10
+#define OMAP2_MCSPI_SYSSTATUS		0x14
+#define OMAP2_MCSPI_IRQSTATUS		0x18
+#define OMAP2_MCSPI_IRQENABLE		0x1c
+#define OMAP2_MCSPI_WAKEUPENABLE	0x20
+#define OMAP2_MCSPI_SYST		0x24
+#define OMAP2_MCSPI_MODULCTRL		0x28
+
+/* per-channel banks, 0x14 bytes each, first is: */
+#define OMAP2_MCSPI_CHCONF0		0x2c
+#define OMAP2_MCSPI_CHSTAT0		0x30
+#define OMAP2_MCSPI_CHCTRL0		0x34
+#define OMAP2_MCSPI_TX0			0x38
+#define OMAP2_MCSPI_RX0			0x3c
+
+/* per-register bitmasks: */
+
+#define OMAP2_MCSPI_SYSCONFIG_AUTOIDLE	(1 << 0)
+#define OMAP2_MCSPI_SYSCONFIG_SOFTRESET	(1 << 1)
+
+#define OMAP2_MCSPI_SYSSTATUS_RESETDONE	(1 << 0)
+
+#define OMAP2_MCSPI_MODULCTRL_SINGLE	(1 << 0)
+#define OMAP2_MCSPI_MODULCTRL_MS	(1 << 2)
+#define OMAP2_MCSPI_MODULCTRL_STEST	(1 << 3)
+
+#define OMAP2_MCSPI_CHCONF_PHA		(1 << 0)
+#define OMAP2_MCSPI_CHCONF_POL		(1 << 1)
+#define OMAP2_MCSPI_CHCONF_CLKD_MASK	(0x0f << 2)
+#define OMAP2_MCSPI_CHCONF_EPOL		(1 << 6)
+#define OMAP2_MCSPI_CHCONF_WL_MASK	(0x1f << 7)
+#define OMAP2_MCSPI_CHCONF_TRM_RX_ONLY	(0x01 << 12)
+#define OMAP2_MCSPI_CHCONF_TRM_TX_ONLY	(0x02 << 12)
+#define OMAP2_MCSPI_CHCONF_TRM_MASK	(0x03 << 12)
+#define OMAP2_MCSPI_CHCONF_DMAW		(1 << 14)
+#define OMAP2_MCSPI_CHCONF_DMAR		(1 << 15)
+#define OMAP2_MCSPI_CHCONF_DPE0		(1 << 16)
+#define OMAP2_MCSPI_CHCONF_DPE1		(1 << 17)
+#define OMAP2_MCSPI_CHCONF_IS		(1 << 18)
+#define OMAP2_MCSPI_CHCONF_TURBO	(1 << 19)
+#define OMAP2_MCSPI_CHCONF_FORCE	(1 << 20)
+
+#define OMAP2_MCSPI_CHSTAT_RXS		(1 << 0)
+#define OMAP2_MCSPI_CHSTAT_TXS		(1 << 1)
+#define OMAP2_MCSPI_CHSTAT_EOT		(1 << 2)
+
+#define OMAP2_MCSPI_CHCTRL_EN		(1 << 0)
+
+
+/* We have 2 DMA channels per CS, one for RX and one for TX */
+struct omap2_mcspi_dma {
+	int dma_tx_channel;
+	int dma_rx_channel;
+
+	int dma_tx_sync_dev;
+	int dma_rx_sync_dev;
+
+	struct completion dma_tx_completion;
+	struct completion dma_rx_completion;
+};
+
+/* use PIO for small transfers, avoiding DMA setup/teardown overhead and
+ * cache operations; better heuristics consider wordsize and bitrate.
+ */
+#define DMA_MIN_BYTES			8
+
+
+struct omap2_mcspi {
+	struct work_struct	work;
+	spinlock_t		lock;
+	struct list_head	msg_queue;
+	struct spi_master	*master;
+	struct clk		*ick;
+	struct clk		*fck;
+	/* Virtual base address of the controller */
+	void __iomem		*base;
+	/* SPI1 has 4 channels, while SPI2 has 2 */
+	struct omap2_mcspi_dma	*dma_channels;
+};
+
+struct omap2_mcspi_cs {
+	void __iomem		*base;
+	int			word_len;
+};
+
+static struct workqueue_struct *omap2_mcspi_wq;
+
+#define MOD_REG_BIT(val, mask, set) do { \
+	if (set) \
+		val |= mask; \
+	else \
+		val &= ~mask; \
+} while(0)
+
+static inline void mcspi_write_reg(struct spi_master *master,
+		int idx, u32 val)
+{
+	struct omap2_mcspi *mcspi = spi_master_get_devdata(master);
+
+	__raw_writel(val, mcspi->base + idx);
+}
+
+static inline u32 mcspi_read_reg(struct spi_master *master, int idx)
+{
+	struct omap2_mcspi *mcspi = spi_master_get_devdata(master);
+
+	return __raw_readl(mcspi->base + idx);
+}
+
+static inline void mcspi_write_cs_reg(const struct spi_device *spi,
+		int idx, u32 val)
+{
+	struct omap2_mcspi_cs	*cs = spi->controller_state;
+
+	__raw_writel(val, cs->base +  idx);
+}
+
+static inline u32 mcspi_read_cs_reg(const struct spi_device *spi, int idx)
+{
+	struct omap2_mcspi_cs	*cs = spi->controller_state;
+
+	return __raw_readl(cs->base + idx);
+}
+
+static void omap2_mcspi_set_dma_req(const struct spi_device *spi,
+		int is_read, int enable)
+{
+	u32 l, rw;
+
+	l = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
+
+	if (is_read) /* 1 is read, 0 write */
+		rw = OMAP2_MCSPI_CHCONF_DMAR;
+	else
+		rw = OMAP2_MCSPI_CHCONF_DMAW;
+
+	MOD_REG_BIT(l, rw, enable);
+	mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCONF0, l);
+}
+
+static void omap2_mcspi_set_enable(const struct spi_device *spi, int enable)
+{
+	u32 l;
+
+	l = enable ? OMAP2_MCSPI_CHCTRL_EN : 0;
+	mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCTRL0, l);
+}
+
+static void omap2_mcspi_force_cs(struct spi_device *spi, int cs_active)
+{
+	u32 l;
+
+	l = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
+	MOD_REG_BIT(l, OMAP2_MCSPI_CHCONF_FORCE, cs_active);
+	mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCONF0, l);
+}
+
+static void omap2_mcspi_set_master_mode(struct spi_master *master)
+{
+	u32 l;
+
+	/* setup when switching from (reset default) slave mode
+	 * to single-channel master mode
+	 */
+	l = mcspi_read_reg(master, OMAP2_MCSPI_MODULCTRL);
+	MOD_REG_BIT(l, OMAP2_MCSPI_MODULCTRL_STEST, 0);
+	MOD_REG_BIT(l, OMAP2_MCSPI_MODULCTRL_MS, 0);
+	MOD_REG_BIT(l, OMAP2_MCSPI_MODULCTRL_SINGLE, 1);
+	mcspi_write_reg(master, OMAP2_MCSPI_MODULCTRL, l);
+}
+
+static unsigned
+omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer)
+{
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_cs	*cs = spi->controller_state;
+	struct omap2_mcspi_dma  *mcspi_dma;
+	unsigned int		count, c;
+	unsigned long		base, tx_reg, rx_reg;
+	int			word_len, data_type, element_count;
+	u8			* rx;
+	const u8		* tx;
+
+	mcspi = spi_master_get_devdata(spi->master);
+	mcspi_dma = &mcspi->dma_channels[spi->chip_select];
+
+	count = xfer->len;
+	c = count;
+	word_len = cs->word_len;
+
+	base = (unsigned long) io_v2p(cs->base);
+	tx_reg = base + OMAP2_MCSPI_TX0;
+	rx_reg = base + OMAP2_MCSPI_RX0;
+	rx = xfer->rx_buf;
+	tx = xfer->tx_buf;
+
+	if (word_len <= 8) {
+		data_type = OMAP_DMA_DATA_TYPE_S8;
+		element_count = count;
+	} else if (word_len <= 16) {
+		data_type = OMAP_DMA_DATA_TYPE_S16;
+		element_count = count >> 1;
+	} else /* word_len <= 32 */ {
+		data_type = OMAP_DMA_DATA_TYPE_S32;
+		element_count = count >> 2;
+	}
+
+	if (tx != NULL) {
+		omap_set_dma_transfer_params(mcspi_dma->dma_tx_channel,
+				data_type, element_count, 1,
+				OMAP_DMA_SYNC_ELEMENT,
+				mcspi_dma->dma_tx_sync_dev, 0);
+
+		omap_set_dma_dest_params(mcspi_dma->dma_tx_channel, 0,
+				OMAP_DMA_AMODE_CONSTANT,
+				tx_reg, 0, 0);
+
+		omap_set_dma_src_params(mcspi_dma->dma_tx_channel, 0,
+				OMAP_DMA_AMODE_POST_INC,
+				xfer->tx_dma, 0, 0);
+	}
+
+	if (rx != NULL) {
+		omap_set_dma_transfer_params(mcspi_dma->dma_rx_channel,
+				data_type, element_count, 1,
+				OMAP_DMA_SYNC_ELEMENT,
+				mcspi_dma->dma_rx_sync_dev, 1);
+
+		omap_set_dma_src_params(mcspi_dma->dma_rx_channel, 0,
+				OMAP_DMA_AMODE_CONSTANT,
+				rx_reg, 0, 0);
+
+		omap_set_dma_dest_params(mcspi_dma->dma_rx_channel, 0,
+				OMAP_DMA_AMODE_POST_INC,
+				xfer->rx_dma, 0, 0);
+	}
+
+	if (tx != NULL) {
+		omap_start_dma(mcspi_dma->dma_tx_channel);
+		omap2_mcspi_set_dma_req(spi, 0, 1);
+	}
+
+	if (rx != NULL) {
+		omap_start_dma(mcspi_dma->dma_rx_channel);
+		omap2_mcspi_set_dma_req(spi, 1, 1);
+	}
+
+	if (tx != NULL) {
+		wait_for_completion(&mcspi_dma->dma_tx_completion);
+		dma_unmap_single(NULL, xfer->tx_dma, count, DMA_TO_DEVICE);
+	}
+
+	if (rx != NULL) {
+		wait_for_completion(&mcspi_dma->dma_rx_completion);
+		dma_unmap_single(NULL, xfer->rx_dma, count, DMA_FROM_DEVICE);
+	}
+	return count;
+}
+
+static int mcspi_wait_for_reg_bit(void __iomem *reg, unsigned long bit)
+{
+	unsigned long timeout;
+
+	timeout = jiffies + msecs_to_jiffies(1000);
+	while (!(__raw_readl(reg) & bit)) {
+		if (time_after(jiffies, timeout))
+			return -1;
+	}
+	return 0;
+}
+
+static unsigned
+omap2_mcspi_txrx_pio(struct spi_device *spi, struct spi_transfer *xfer)
+{
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_cs	*cs = spi->controller_state;
+	unsigned int		count, c;
+	u32			l;
+	void __iomem		*base = cs->base;
+	void __iomem		*tx_reg;
+	void __iomem		*rx_reg;
+	void __iomem		*chstat_reg;
+	int			word_len;
+
+	mcspi = spi_master_get_devdata(spi->master);
+	count = xfer->len;
+	c = count;
+	word_len = cs->word_len;
+
+	l = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
+	l &= ~OMAP2_MCSPI_CHCONF_TRM_MASK;
+
+	/* We store the pre-calculated register addresses on stack to speed
+	 * up the transfer loop. */
+	tx_reg		= base + OMAP2_MCSPI_TX0;
+	rx_reg		= base + OMAP2_MCSPI_RX0;
+	chstat_reg	= base + OMAP2_MCSPI_CHSTAT0;
+
+	if (word_len <= 8) {
+		u8		*rx;
+		const u8	*tx;
+
+		rx = xfer->rx_buf;
+		tx = xfer->tx_buf;
+
+		do {
+			if (tx != NULL) {
+				if (mcspi_wait_for_reg_bit(chstat_reg,
+						OMAP2_MCSPI_CHSTAT_TXS) < 0) {
+					dev_err(&spi->dev, "TXS timed out\n");
+					goto out;
+				}
+#ifdef VERBOSE
+				dev_dbg(&spi->dev, "write-%d %02x\n",
+						word_len, *tx);
+#endif
+				__raw_writel(*tx++, tx_reg);
+			}
+			if (rx != NULL) {
+				if (mcspi_wait_for_reg_bit(chstat_reg,
+						OMAP2_MCSPI_CHSTAT_RXS) < 0) {
+					dev_err(&spi->dev, "RXS timed out\n");
+					goto out;
+				}
+				/* prevent last RX_ONLY read from triggering
+				 * more word i/o: switch to rx+tx
+				 */
+				if (c == 0 && tx == NULL)
+					mcspi_write_cs_reg(spi,
+							OMAP2_MCSPI_CHCONF0, l);
+				*rx++ = __raw_readl(rx_reg);
+#ifdef VERBOSE
+				dev_dbg(&spi->dev, "read-%d %02x\n",
+						word_len, *(rx - 1));
+#endif
+			}
+			c -= 1;
+		} while (c);
+	} else if (word_len <= 16) {
+		u16		*rx;
+		const u16	*tx;
+
+		rx = xfer->rx_buf;
+		tx = xfer->tx_buf;
+		do {
+			if (tx != NULL) {
+				if (mcspi_wait_for_reg_bit(chstat_reg,
+						OMAP2_MCSPI_CHSTAT_TXS) < 0) {
+					dev_err(&spi->dev, "TXS timed out\n");
+					goto out;
+				}
+#ifdef VERBOSE
+				dev_dbg(&spi->dev, "write-%d %04x\n",
+						word_len, *tx);
+#endif
+				__raw_writel(*tx++, tx_reg);
+			}
+			if (rx != NULL) {
+				if (mcspi_wait_for_reg_bit(chstat_reg,
+						OMAP2_MCSPI_CHSTAT_RXS) < 0) {
+					dev_err(&spi->dev, "RXS timed out\n");
+					goto out;
+				}
+				/* prevent last RX_ONLY read from triggering
+				 * more word i/o: switch to rx+tx
+				 */
+				if (c == 0 && tx == NULL)
+					mcspi_write_cs_reg(spi,
+							OMAP2_MCSPI_CHCONF0, l);
+				*rx++ = __raw_readl(rx_reg);
+#ifdef VERBOSE
+				dev_dbg(&spi->dev, "read-%d %04x\n",
+						word_len, *(rx - 1));
+#endif
+			}
+			c -= 2;
+		} while (c);
+	} else if (word_len <= 32) {
+		u32		*rx;
+		const u32	*tx;
+
+		rx = xfer->rx_buf;
+		tx = xfer->tx_buf;
+		do {
+			if (tx != NULL) {
+				if (mcspi_wait_for_reg_bit(chstat_reg,
+						OMAP2_MCSPI_CHSTAT_TXS) < 0) {
+					dev_err(&spi->dev, "TXS timed out\n");
+					goto out;
+				}
+#ifdef VERBOSE
+				dev_dbg(&spi->dev, "write-%d %04x\n",
+						word_len, *tx);
+#endif
+				__raw_writel(*tx++, tx_reg);
+			}
+			if (rx != NULL) {
+				if (mcspi_wait_for_reg_bit(chstat_reg,
+						OMAP2_MCSPI_CHSTAT_RXS) < 0) {
+					dev_err(&spi->dev, "RXS timed out\n");
+					goto out;
+				}
+				/* prevent last RX_ONLY read from triggering
+				 * more word i/o: switch to rx+tx
+				 */
+				if (c == 0 && tx == NULL)
+					mcspi_write_cs_reg(spi,
+							OMAP2_MCSPI_CHCONF0, l);
+				*rx++ = __raw_readl(rx_reg);
+#ifdef VERBOSE
+				dev_dbg(&spi->dev, "read-%d %04x\n",
+						word_len, *(rx - 1));
+#endif
+			}
+			c -= 4;
+		} while (c);
+	}
+
+	/* for TX_ONLY mode, be sure all words have shifted out */
+	if (xfer->rx_buf == NULL) {
+		if (mcspi_wait_for_reg_bit(chstat_reg,
+				OMAP2_MCSPI_CHSTAT_TXS) < 0) {
+			dev_err(&spi->dev, "TXS timed out\n");
+		} else if (mcspi_wait_for_reg_bit(chstat_reg,
+				OMAP2_MCSPI_CHSTAT_EOT) < 0)
+			dev_err(&spi->dev, "EOT timed out\n");
+	}
+out:
+	return count - c;
+}
+
+/* called only when no transfer is active to this device */
+static int omap2_mcspi_setup_transfer(struct spi_device *spi,
+		struct spi_transfer *t)
+{
+	struct omap2_mcspi_cs *cs = spi->controller_state;
+	struct omap2_mcspi *mcspi;
+	u32 l = 0, div = 0;
+	u8 word_len = spi->bits_per_word;
+
+	mcspi = spi_master_get_devdata(spi->master);
+
+	if (t != NULL && t->bits_per_word)
+		word_len = t->bits_per_word;
+
+	cs->word_len = word_len;
+
+	if (spi->max_speed_hz) {
+		while (div <= 15 && (OMAP2_MCSPI_MAX_FREQ / (1 << div))
+					> spi->max_speed_hz)
+			div++;
+	} else
+		div = 15;
+
+	l = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
+
+	/* standard 4-wire master mode:  SCK, MOSI/out, MISO/in, nCS
+	 * REVISIT: this controller could support SPI_3WIRE mode.
+	 */
+	l &= ~(OMAP2_MCSPI_CHCONF_IS|OMAP2_MCSPI_CHCONF_DPE1);
+	l |= OMAP2_MCSPI_CHCONF_DPE0;
+
+	/* wordlength */
+	l &= ~OMAP2_MCSPI_CHCONF_WL_MASK;
+	l |= (word_len - 1) << 7;
+
+	/* set chipselect polarity; manage with FORCE */
+	if (!(spi->mode & SPI_CS_HIGH))
+		l |= OMAP2_MCSPI_CHCONF_EPOL;	/* active-low; normal */
+	else
+		l &= ~OMAP2_MCSPI_CHCONF_EPOL;
+
+	/* set clock divisor */
+	l &= ~OMAP2_MCSPI_CHCONF_CLKD_MASK;
+	l |= div << 2;
+
+	/* set SPI mode 0..3 */
+	if (spi->mode & SPI_CPOL)
+		l |= OMAP2_MCSPI_CHCONF_POL;
+	else
+		l &= ~OMAP2_MCSPI_CHCONF_POL;
+	if (spi->mode & SPI_CPHA)
+		l |= OMAP2_MCSPI_CHCONF_PHA;
+	else
+		l &= ~OMAP2_MCSPI_CHCONF_PHA;
+
+	mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCONF0, l);
+
+	dev_dbg(&spi->dev, "setup: speed %d, sample %s edge, clk %s\n",
+			OMAP2_MCSPI_MAX_FREQ / (1 << div),
+			(spi->mode & SPI_CPHA) ? "trailing" : "leading",
+			(spi->mode & SPI_CPOL) ? "inverted" : "normal");
+
+	return 0;
+}
+
+static void omap2_mcspi_dma_rx_callback(int lch, u16 ch_status, void *data)
+{
+	struct spi_device	*spi = data;
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_dma	*mcspi_dma;
+
+	mcspi = spi_master_get_devdata(spi->master);
+	mcspi_dma = &(mcspi->dma_channels[spi->chip_select]);
+
+	complete(&mcspi_dma->dma_rx_completion);
+
+	/* We must disable the DMA RX request */
+	omap2_mcspi_set_dma_req(spi, 1, 0);
+}
+
+static void omap2_mcspi_dma_tx_callback(int lch, u16 ch_status, void *data)
+{
+	struct spi_device	*spi = data;
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_dma	*mcspi_dma;
+
+	mcspi = spi_master_get_devdata(spi->master);
+	mcspi_dma = &(mcspi->dma_channels[spi->chip_select]);
+
+	complete(&mcspi_dma->dma_tx_completion);
+
+	/* We must disable the DMA TX request */
+	omap2_mcspi_set_dma_req(spi, 0, 0);
+}
+
+static int omap2_mcspi_request_dma(struct spi_device *spi)
+{
+	struct spi_master	*master = spi->master;
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_dma	*mcspi_dma;
+
+	mcspi = spi_master_get_devdata(master);
+	mcspi_dma = mcspi->dma_channels + spi->chip_select;
+
+	if (omap_request_dma(mcspi_dma->dma_rx_sync_dev, "McSPI RX",
+			omap2_mcspi_dma_rx_callback, spi,
+			&mcspi_dma->dma_rx_channel)) {
+		dev_err(&spi->dev, "no RX DMA channel for McSPI\n");
+		return -EAGAIN;
+	}
+
+	if (omap_request_dma(mcspi_dma->dma_tx_sync_dev, "McSPI TX",
+			omap2_mcspi_dma_tx_callback, spi,
+			&mcspi_dma->dma_tx_channel)) {
+		omap_free_dma(mcspi_dma->dma_rx_channel);
+		mcspi_dma->dma_rx_channel = -1;
+		dev_err(&spi->dev, "no TX DMA channel for McSPI\n");
+		return -EAGAIN;
+	}
+
+	init_completion(&mcspi_dma->dma_rx_completion);
+	init_completion(&mcspi_dma->dma_tx_completion);
+
+	return 0;
+}
+
+/* the spi->mode bits understood by this driver: */
+#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH)
+
+static int omap2_mcspi_setup(struct spi_device *spi)
+{
+	int			ret;
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_dma	*mcspi_dma;
+	struct omap2_mcspi_cs	*cs = spi->controller_state;
+
+	if (spi->mode & ~MODEBITS) {
+		dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n",
+			spi->mode & ~MODEBITS);
+		return -EINVAL;
+	}
+
+	if (spi->bits_per_word == 0)
+		spi->bits_per_word = 8;
+	else if (spi->bits_per_word < 4 || spi->bits_per_word > 32) {
+		dev_dbg(&spi->dev, "setup: unsupported %d bit words\n",
+			spi->bits_per_word);
+		return -EINVAL;
+	}
+
+	mcspi = spi_master_get_devdata(spi->master);
+	mcspi_dma = &mcspi->dma_channels[spi->chip_select];
+
+	if (!cs) {
+		cs = kzalloc(sizeof *cs, GFP_KERNEL);
+		if (!cs)
+			return -ENOMEM;
+		cs->base = mcspi->base + spi->chip_select * 0x14;
+		spi->controller_state = cs;
+	}
+
+	if (mcspi_dma->dma_rx_channel == -1
+			|| mcspi_dma->dma_tx_channel == -1) {
+		ret = omap2_mcspi_request_dma(spi);
+		if (ret < 0)
+			return ret;
+	}
+
+	clk_enable(mcspi->ick);
+	clk_enable(mcspi->fck);
+	ret =  omap2_mcspi_setup_transfer(spi, NULL);
+	clk_disable(mcspi->fck);
+	clk_disable(mcspi->ick);
+
+	return ret;
+}
+
+static void omap2_mcspi_cleanup(struct spi_device *spi)
+{
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_dma	*mcspi_dma;
+
+	mcspi = spi_master_get_devdata(spi->master);
+	mcspi_dma = &mcspi->dma_channels[spi->chip_select];
+
+	if (spi->controller_state != NULL)
+		kfree(spi->controller_state);
+
+	if (mcspi_dma->dma_rx_channel != -1) {
+		omap_free_dma(mcspi_dma->dma_rx_channel);
+		mcspi_dma->dma_rx_channel = -1;
+	}
+	if (mcspi_dma->dma_tx_channel != -1) {
+		omap_free_dma(mcspi_dma->dma_tx_channel);
+		mcspi_dma->dma_tx_channel = -1;
+	}
+}
+
+static void omap2_mcspi_work(struct work_struct *work)
+{
+	struct omap2_mcspi	*mcspi;
+
+	mcspi = container_of(work, struct omap2_mcspi, work);
+	spin_lock_irq(&mcspi->lock);
+
+	clk_enable(mcspi->ick);
+	clk_enable(mcspi->fck);
+
+	/* We only enable one channel at a time -- the one whose message is
+	 * at the head of the queue -- although this controller would gladly
+	 * arbitrate among multiple channels.  This corresponds to "single
+	 * channel" master mode.  As a side effect, we need to manage the
+	 * chipselect with the FORCE bit ... CS != channel enable.
+	 */
+	while (!list_empty(&mcspi->msg_queue)) {
+		struct spi_message		*m;
+		struct spi_device		*spi;
+		struct spi_transfer		*t = NULL;
+		int				cs_active = 0;
+		struct omap2_mcspi_device_config *conf;
+		struct omap2_mcspi_cs		*cs;
+		int				par_override = 0;
+		int				status = 0;
+		u32				chconf;
+
+		m = container_of(mcspi->msg_queue.next, struct spi_message,
+				 queue);
+
+		list_del_init(&m->queue);
+		spin_unlock_irq(&mcspi->lock);
+
+		spi = m->spi;
+		conf = spi->controller_data;
+		cs = spi->controller_state;
+
+		omap2_mcspi_set_enable(spi, 1);
+		list_for_each_entry(t, &m->transfers, transfer_list) {
+			if (t->tx_buf == NULL && t->rx_buf == NULL && t->len) {
+				status = -EINVAL;
+				break;
+			}
+			if (par_override || t->speed_hz || t->bits_per_word) {
+				par_override = 1;
+				status = omap2_mcspi_setup_transfer(spi, t);
+				if (status < 0)
+					break;
+				if (!t->speed_hz && !t->bits_per_word)
+					par_override = 0;
+			}
+
+			if (!cs_active) {
+				omap2_mcspi_force_cs(spi, 1);
+				cs_active = 1;
+			}
+
+			chconf = mcspi_read_cs_reg(spi, OMAP2_MCSPI_CHCONF0);
+			chconf &= ~OMAP2_MCSPI_CHCONF_TRM_MASK;
+			if (t->tx_buf == NULL)
+				chconf |= OMAP2_MCSPI_CHCONF_TRM_RX_ONLY;
+			else if (t->rx_buf == NULL)
+				chconf |= OMAP2_MCSPI_CHCONF_TRM_TX_ONLY;
+			mcspi_write_cs_reg(spi, OMAP2_MCSPI_CHCONF0, chconf);
+
+			if (t->len) {
+				unsigned	count;
+
+				/* RX_ONLY mode needs dummy data in TX reg */
+				if (t->tx_buf == NULL)
+					__raw_writel(0, cs->base + OMAP2_MCSPI_TX0);
+
+				if (m->is_dma_mapped || t->len >= DMA_MIN_BYTES)
+					count = omap2_mcspi_txrx_dma(spi, t);
+				else
+					count = omap2_mcspi_txrx_pio(spi, t);
+				m->actual_length += count;
+
+				if (count != t->len) {
+					status = -EIO;
+					break;
+				}
+			}
+
+			if (t->delay_usecs)
+				udelay(t->delay_usecs);
+
+			/* ignore the "leave it on after last xfer" hint */
+			if (t->cs_change) {
+				omap2_mcspi_force_cs(spi, 0);
+				cs_active = 0;
+			}
+		}
+
+		/* Restore defaults if they were overriden */
+		if (par_override) {
+			par_override = 0;
+			status = omap2_mcspi_setup_transfer(spi, NULL);
+		}
+
+		if (cs_active)
+			omap2_mcspi_force_cs(spi, 0);
+
+		omap2_mcspi_set_enable(spi, 0);
+
+		m->status = status;
+		m->complete(m->context);
+
+		spin_lock_irq(&mcspi->lock);
+	}
+
+	clk_disable(mcspi->fck);
+	clk_disable(mcspi->ick);
+
+	spin_unlock_irq(&mcspi->lock);
+}
+
+static int omap2_mcspi_transfer(struct spi_device *spi, struct spi_message *m)
+{
+	struct omap2_mcspi	*mcspi;
+	unsigned long		flags;
+	struct spi_transfer	*t;
+
+	m->actual_length = 0;
+	m->status = 0;
+
+	/* reject invalid messages and transfers */
+	if (list_empty(&m->transfers) || !m->complete)
+		return -EINVAL;
+	list_for_each_entry(t, &m->transfers, transfer_list) {
+		const void	*tx_buf = t->tx_buf;
+		void		*rx_buf = t->rx_buf;
+		unsigned	len = t->len;
+
+		if (t->speed_hz > OMAP2_MCSPI_MAX_FREQ
+				|| (len && !(rx_buf || tx_buf))
+				|| (t->bits_per_word &&
+					(  t->bits_per_word < 4
+					|| t->bits_per_word > 32))) {
+			dev_dbg(&spi->dev, "transfer: %d Hz, %d %s%s, %d bpw\n",
+					t->speed_hz,
+					len,
+					tx_buf ? "tx" : "",
+					rx_buf ? "rx" : "",
+					t->bits_per_word);
+			return -EINVAL;
+		}
+		if (t->speed_hz && t->speed_hz < OMAP2_MCSPI_MAX_FREQ/(1<<16)) {
+			dev_dbg(&spi->dev, "%d Hz max exceeds %d\n",
+					t->speed_hz,
+					OMAP2_MCSPI_MAX_FREQ/(1<<16));
+			return -EINVAL;
+		}
+
+		if (m->is_dma_mapped || len < DMA_MIN_BYTES)
+			continue;
+
+		/* Do DMA mapping "early" for better error reporting and
+		 * dcache use.  Note that if dma_unmap_single() ever starts
+		 * to do real work on ARM, we'd need to clean up mappings
+		 * for previous transfers on *ALL* exits of this loop...
+		 */
+		if (tx_buf != NULL) {
+			t->tx_dma = dma_map_single(&spi->dev, (void *) tx_buf,
+					len, DMA_TO_DEVICE);
+			if (dma_mapping_error(t->tx_dma)) {
+				dev_dbg(&spi->dev, "dma %cX %d bytes error\n",
+						'T', len);
+				return -EINVAL;
+			}
+		}
+		if (rx_buf != NULL) {
+			t->rx_dma = dma_map_single(&spi->dev, rx_buf, t->len,
+					DMA_FROM_DEVICE);
+			if (dma_mapping_error(t->rx_dma)) {
+				dev_dbg(&spi->dev, "dma %cX %d bytes error\n",
+						'R', len);
+				if (tx_buf != NULL)
+					dma_unmap_single(NULL, t->tx_dma,
+							len, DMA_TO_DEVICE);
+				return -EINVAL;
+			}
+		}
+	}
+
+	mcspi = spi_master_get_devdata(spi->master);
+
+	spin_lock_irqsave(&mcspi->lock, flags);
+	list_add_tail(&m->queue, &mcspi->msg_queue);
+	queue_work(omap2_mcspi_wq, &mcspi->work);
+	spin_unlock_irqrestore(&mcspi->lock, flags);
+
+	return 0;
+}
+
+static int __init omap2_mcspi_reset(struct omap2_mcspi *mcspi)
+{
+	struct spi_master	*master = mcspi->master;
+	u32			tmp;
+
+	clk_enable(mcspi->ick);
+	clk_enable(mcspi->fck);
+
+	mcspi_write_reg(master, OMAP2_MCSPI_SYSCONFIG,
+			OMAP2_MCSPI_SYSCONFIG_SOFTRESET);
+	do {
+		tmp = mcspi_read_reg(master, OMAP2_MCSPI_SYSSTATUS);
+	} while (!(tmp & OMAP2_MCSPI_SYSSTATUS_RESETDONE));
+
+	mcspi_write_reg(master, OMAP2_MCSPI_SYSCONFIG,
+			// (3 << 8) | (2 << 3) |
+			OMAP2_MCSPI_SYSCONFIG_AUTOIDLE);
+
+	omap2_mcspi_set_master_mode(master);
+
+	clk_disable(mcspi->fck);
+	clk_disable(mcspi->ick);
+	return 0;
+}
+
+static u8 __initdata spi1_rxdma_id [] = {
+	OMAP24XX_DMA_SPI1_RX0,
+	OMAP24XX_DMA_SPI1_RX1,
+	OMAP24XX_DMA_SPI1_RX2,
+	OMAP24XX_DMA_SPI1_RX3,
+};
+
+static u8 __initdata spi1_txdma_id [] = {
+	OMAP24XX_DMA_SPI1_TX0,
+	OMAP24XX_DMA_SPI1_TX1,
+	OMAP24XX_DMA_SPI1_TX2,
+	OMAP24XX_DMA_SPI1_TX3,
+};
+
+static u8 __initdata spi2_rxdma_id[] = {
+	OMAP24XX_DMA_SPI2_RX0,
+	OMAP24XX_DMA_SPI2_RX1,
+};
+
+static u8 __initdata spi2_txdma_id[] = {
+	OMAP24XX_DMA_SPI2_TX0,
+	OMAP24XX_DMA_SPI2_TX1,
+};
+
+static int __init omap2_mcspi_probe(struct platform_device *pdev)
+{
+	struct spi_master	*master;
+	struct omap2_mcspi	*mcspi;
+	struct resource		*r;
+	int			status = 0, i;
+	const u8		*rxdma_id, *txdma_id;
+	unsigned		num_chipselect;
+
+	switch (pdev->id) {
+	case 1:
+		rxdma_id = spi1_rxdma_id;
+		txdma_id = spi1_txdma_id;
+		num_chipselect = 4;
+		break;
+	case 2:
+		rxdma_id = spi2_rxdma_id;
+		txdma_id = spi2_txdma_id;
+		num_chipselect = 2;
+		break;
+	/* REVISIT omap2430 has a third McSPI ... */
+	default:
+		return -EINVAL;
+	}
+
+	master = spi_alloc_master(&pdev->dev, sizeof *mcspi);
+	if (master == NULL) {
+		dev_dbg(&pdev->dev, "master allocation failed\n");
+		return -ENOMEM;
+	}
+
+	if (pdev->id != -1)
+		master->bus_num = pdev->id;
+
+	master->setup = omap2_mcspi_setup;
+	master->transfer = omap2_mcspi_transfer;
+	master->cleanup = omap2_mcspi_cleanup;
+	master->num_chipselect = num_chipselect;
+
+	dev_set_drvdata(&pdev->dev, master);
+
+	mcspi = spi_master_get_devdata(master);
+	mcspi->master = master;
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (r == NULL) {
+		status = -ENODEV;
+		goto err1;
+	}
+	if (!request_mem_region(r->start, (r->end - r->start) + 1,
+			pdev->dev.bus_id)) {
+		status = -EBUSY;
+		goto err1;
+	}
+
+	mcspi->base = (void __iomem *) io_p2v(r->start);
+
+	INIT_WORK(&mcspi->work, omap2_mcspi_work);
+
+	spin_lock_init(&mcspi->lock);
+	INIT_LIST_HEAD(&mcspi->msg_queue);
+
+	mcspi->ick = clk_get(&pdev->dev, "mcspi_ick");
+	if (IS_ERR(mcspi->ick)) {
+		dev_dbg(&pdev->dev, "can't get mcspi_ick\n");
+		status = PTR_ERR(mcspi->ick);
+		goto err1a;
+	}
+	mcspi->fck = clk_get(&pdev->dev, "mcspi_fck");
+	if (IS_ERR(mcspi->fck)) {
+		dev_dbg(&pdev->dev, "can't get mcspi_fck\n");
+		status = PTR_ERR(mcspi->fck);
+		goto err2;
+	}
+
+	mcspi->dma_channels = kcalloc(master->num_chipselect,
+			sizeof(struct omap2_mcspi_dma),
+			GFP_KERNEL);
+
+	if (mcspi->dma_channels == NULL)
+		goto err3;
+
+	for (i = 0; i < num_chipselect; i++) {
+		mcspi->dma_channels[i].dma_rx_channel = -1;
+		mcspi->dma_channels[i].dma_rx_sync_dev = rxdma_id[i];
+		mcspi->dma_channels[i].dma_tx_channel = -1;
+		mcspi->dma_channels[i].dma_tx_sync_dev = txdma_id[i];
+	}
+
+	if (omap2_mcspi_reset(mcspi) < 0)
+		goto err4;
+
+	status = spi_register_master(master);
+	if (status < 0)
+		goto err4;
+
+	return status;
+
+err4:
+	kfree(mcspi->dma_channels);
+err3:
+	clk_put(mcspi->fck);
+err2:
+	clk_put(mcspi->ick);
+err1a:
+	release_mem_region(r->start, (r->end - r->start) + 1);
+err1:
+	spi_master_put(master);
+	return status;
+}
+
+static int __exit omap2_mcspi_remove(struct platform_device *pdev)
+{
+	struct spi_master	*master;
+	struct omap2_mcspi	*mcspi;
+	struct omap2_mcspi_dma	*dma_channels;
+	struct resource		*r;
+
+	master = dev_get_drvdata(&pdev->dev);
+	mcspi = spi_master_get_devdata(master);
+	dma_channels = mcspi->dma_channels;
+
+	clk_put(mcspi->fck);
+	clk_put(mcspi->ick);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(r->start, (r->end - r->start) + 1);
+
+	spi_unregister_master(master);
+	kfree(dma_channels);
+
+	return 0;
+}
+
+static struct platform_driver omap2_mcspi_driver = {
+	.driver = {
+		.name =		"omap2_mcspi",
+		.owner =	THIS_MODULE,
+	},
+	.remove =	__exit_p(omap2_mcspi_remove),
+};
+
+
+static int __init omap2_mcspi_init(void)
+{
+	omap2_mcspi_wq = create_workqueue(omap2_mcspi_driver.driver.name);
+	if (omap2_mcspi_wq == NULL)
+		return -1;
+	return platform_driver_probe(&omap2_mcspi_driver, omap2_mcspi_probe);
+}
+subsys_initcall(omap2_mcspi_init);
+
+static void __exit omap2_mcspi_exit(void)
+{
+	platform_driver_unregister(&omap2_mcspi_driver);
+
+	destroy_workqueue(omap2_mcspi_wq);
+}
+module_exit(omap2_mcspi_exit);
+
+MODULE_LICENSE("GPL");
-- 
1.5.0


[-- Attachment #3: Type: text/plain, Size: 286 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/

[-- Attachment #4: Type: text/plain, Size: 210 bytes --]

_______________________________________________
spi-devel-general mailing list
spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
https://lists.sourceforge.net/lists/listinfo/spi-devel-general

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

* Re: [PATCH] OMAP: Add OMAP24XX Multichannel SPI controller driver
       [not found]         ` <5d5443650706210037k6562d50frc7fc2f261a2e9352-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2007-06-29  9:36           ` Trilok Soni
       [not found]             ` <5d5443650706290236g14da1c09hc023d12954674f71-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 7+ messages in thread
From: Trilok Soni @ 2007-06-29  9:36 UTC (permalink / raw)
  To: David Brownell
  Cc: juha.yrjola-EmnPodGKVbzby3iVrkZq2A,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
	Tony Lindgren, imre.deak-EmnPodGKVbzby3iVrkZq2A,
	samuel.ortiz-EmnPodGKVbzby3iVrkZq2A

On 6/21/07, Trilok Soni <soni.trilok-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> On 6/21/07, David Brownell <david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org> wrote:
> > That's pretty much the latest from omap-git, right?
> >
> > If so, please try the appended patch, which includes various
> > bugfixes and cleanups I've had in my queue for a while.
> >
>
> Thanx David. Yes it is from latest omap-git. Tony may like to apply
> this patch to omap-git, instead of waiting it to come from upstream.
>
> I have appended your patch with mine and attached that patch, it is
> now build tested only, someone need to help in testing this patch as
> other team here took my H4 for some high-priority work :(

No updates on testing this driver?

-- 
--Trilok Soni

-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/

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

* Re: [PATCH] OMAP: Add OMAP24XX Multichannel SPI controller driver
       [not found]             ` <5d5443650706290236g14da1c09hc023d12954674f71-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2007-06-29 19:36               ` David Brownell
       [not found]                 ` <200706291236.11069.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
  0 siblings, 1 reply; 7+ messages in thread
From: David Brownell @ 2007-06-29 19:36 UTC (permalink / raw)
  To: Trilok Soni
  Cc: juha.yrjola-EmnPodGKVbzby3iVrkZq2A,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
	Tony Lindgren, imre.deak-EmnPodGKVbzby3iVrkZq2A,
	samuel.ortiz-EmnPodGKVbzby3iVrkZq2A

On Friday 29 June 2007, Trilok Soni wrote:
> On 6/21/07, Trilok Soni <soni.trilok-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> > On 6/21/07, David Brownell <david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org> wrote:
> > > That's pretty much the latest from omap-git, right?
> > >
> > > If so, please try the appended patch, which includes various
> > > bugfixes and cleanups I've had in my queue for a while.
> > >
> >
> > Thanx David. Yes it is from latest omap-git. Tony may like to apply
> > this patch to omap-git, instead of waiting it to come from upstream.
> >
> > I have appended your patch with mine and attached that patch, it is
> > now build tested only, someone need to help in testing this patch as
> > other team here took my H4 for some high-priority work :(
> 
> No updates on testing this driver?

The omap-git version plus my patch works for me ... ;)

-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/

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

* Re: [PATCH] OMAP: Add OMAP24XX Multichannel SPI controller driver
       [not found]                 ` <200706291236.11069.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
@ 2007-07-02  5:10                   ` Trilok Soni
       [not found]                     ` <5d5443650707012210w32f578c4k78f6bd76f9a8ec6-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 7+ messages in thread
From: Trilok Soni @ 2007-07-02  5:10 UTC (permalink / raw)
  To: David Brownell
  Cc: juha.yrjola-EmnPodGKVbzby3iVrkZq2A,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
	Tony Lindgren, imre.deak-EmnPodGKVbzby3iVrkZq2A,
	samuel.ortiz-EmnPodGKVbzby3iVrkZq2A

On 6/30/07, David Brownell <david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org> wrote:
> On Friday 29 June 2007, Trilok Soni wrote:
> > On 6/21/07, Trilok Soni <soni.trilok-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> > > On 6/21/07, David Brownell <david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org> wrote:
> > > > That's pretty much the latest from omap-git, right?
> > > >
> > > > If so, please try the appended patch, which includes various
> > > > bugfixes and cleanups I've had in my queue for a while.
> > > >
> > >
> > > Thanx David. Yes it is from latest omap-git. Tony may like to apply
> > > this patch to omap-git, instead of waiting it to come from upstream.
> > >
> > > I have appended your patch with mine and attached that patch, it is
> > > now build tested only, someone need to help in testing this patch as
> > > other team here took my H4 for some high-priority work :(
> >
> > No updates on testing this driver?
>
> The omap-git version plus my patch works for me ... ;)
>

The patch submitted by me is omap-git version + your patch. So, it is
OK to take that patch for mainline inclusion.


-- 
--Trilok Soni

-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/

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

* Re: [PATCH] OMAP: Add OMAP24XX Multichannel SPI controller driver
       [not found]                     ` <5d5443650707012210w32f578c4k78f6bd76f9a8ec6-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2007-07-02  6:10                       ` David Brownell
  0 siblings, 0 replies; 7+ messages in thread
From: David Brownell @ 2007-07-02  6:10 UTC (permalink / raw)
  To: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f
  Cc: juha.yrjola-EmnPodGKVbzby3iVrkZq2A, Tony Lindgren,
	imre.deak-EmnPodGKVbzby3iVrkZq2A,
	samuel.ortiz-EmnPodGKVbzby3iVrkZq2A

On Sunday 01 July 2007, Trilok Soni wrote:
> On 6/30/07, David Brownell <david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org> wrote:
> > On Friday 29 June 2007, Trilok Soni wrote:

> > > No updates on testing this driver?
> >
> > The omap-git version plus my patch works for me ... ;)
> >
> 
> The patch submitted by me is omap-git version + your patch. So, it is
> OK to take that patch for mainline inclusion.

I'll send it upstream then.  I added Juha's signed-off-by since
that's found in the OMAP-GIT, and since I know that Andrew likes
to see the authors sign off on such stuff.

- Dave


-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/

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

end of thread, other threads:[~2007-07-02  6:10 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2007-06-19  6:23 [PATCH] OMAP: Add OMAP24XX Multichannel SPI controller driver Trilok Soni
     [not found] ` <5d5443650706182323k4cc99707v8ad8dc5cff24ca3e-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2007-06-20 23:30   ` David Brownell
     [not found]     ` <200706201630.04647.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2007-06-21  7:37       ` Trilok Soni
     [not found]         ` <5d5443650706210037k6562d50frc7fc2f261a2e9352-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2007-06-29  9:36           ` Trilok Soni
     [not found]             ` <5d5443650706290236g14da1c09hc023d12954674f71-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2007-06-29 19:36               ` David Brownell
     [not found]                 ` <200706291236.11069.david-b-yBeKhBN/0LDR7s880joybQ@public.gmane.org>
2007-07-02  5:10                   ` Trilok Soni
     [not found]                     ` <5d5443650707012210w32f578c4k78f6bd76f9a8ec6-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2007-07-02  6:10                       ` David Brownell

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).