* [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 related [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 related [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).