]> pilppa.org Git - linux-2.6-omap-h63xx.git/commitdiff
ARM: OMAP: Add SPI master driver for OMAP1 uWire controller
authorJuha Yrjola <juha.yrjola@nokia.com>
Wed, 8 Feb 2006 17:04:12 +0000 (19:04 +0200)
committerJuha Yrjola <juha.yrjola@nokia.com>
Wed, 8 Feb 2006 17:04:12 +0000 (19:04 +0200)
The converted driver originated from David Brownell, with some
modifications by Imre.

Signed-off-by: Imre Deak <imre.deak@nokia.com>
Signed-off-by: Juha Yrjölä <juha.yrjola@nokia.com>
arch/arm/plat-omap/devices.c
drivers/spi/Kconfig
drivers/spi/Makefile
drivers/spi/omap_uwire.c [new file with mode: 0644]

index 572f86d2ce5a30a2ac86725867bac7168d700567..756b3d913f9b28abe617e54629a1e24b0689728d 100644 (file)
@@ -297,6 +297,55 @@ static void __init omap_init_mmc(void)
 static inline void omap_init_mmc(void) {}
 #endif
 
+/*-------------------------------------------------------------------------*/
+
+/* Numbering for the SPI-capable controllers when used for SPI:
+ * spi         = 1
+ * uwire       = 2
+ * mmc1..2     = 3..4
+ * mcbsp1..3   = 5..7
+ */
+
+#if defined(CONFIG_SPI_OMAP_UWIRE) || defined(CONFIG_SPI_OMAP_UWIRE_MODULE)
+
+#define        OMAP_UWIRE_BASE         0xfffb3000
+
+static struct resource uwire_resources[] = {
+       {
+               .start          = OMAP_UWIRE_BASE,
+               .end            = OMAP_UWIRE_BASE + 0x20,
+               .flags          = IORESOURCE_MEM,
+       },
+};
+
+static struct platform_device omap_uwire_device = {
+       .name      = "omap_uwire",
+       .id          = -1,
+       .dev = {
+               .release        = omap_nop_release,
+       },
+       .num_resources  = ARRAY_SIZE(uwire_resources),
+       .resource       = uwire_resources,
+};
+
+static void omap_init_uwire(void)
+{
+       /* FIXME define and use a boot tag; not all boards will be hooking
+        * up devices to the microwire controller, and multi-board configs
+        * mean that CONFIG_SPI_OMAP_UWIRE may be configured anyway...
+        */
+
+       /* board-specific code must configure chipselects (only a few
+        * are normally used) and SCLK/SDI/SDO (each has two choices).
+        */
+       (void) platform_device_register(&omap_uwire_device);
+}
+#else
+static inline void omap_init_uwire(void) {}
+#endif
+
+/*-------------------------------------------------------------------------*/
+
 #if    defined(CONFIG_OMAP_WATCHDOG) || defined(CONFIG_OMAP_WATCHDOG_MODULE)
 
 #ifdef CONFIG_ARCH_OMAP24XX
@@ -430,6 +479,7 @@ static int __init omap_init_devices(void)
        omap_init_i2c();
        omap_init_kp();
        omap_init_mmc();
+       omap_init_uwire();
        omap_init_wdt();
        omap_init_rng();
 
index fad44fa763bc17f061a36c898862f9e89508fbc5..6ca4eec3be5ce617b04ff3d06c73e25632fbfdec 100644 (file)
@@ -85,6 +85,13 @@ config SPI_BUTTERFLY
          inexpensive battery powered microcontroller evaluation board.
          This same cable can be used to flash new firmware.
 
+config SPI_OMAP_UWIRE
+       tristate "OMAP MicroWire"
+       depends on SPI_MASTER && ARCH_OMAP
+       select SPI_BITBANG
+       help
+         This hooks up to the MicroWire controller on OMAP chips.
+
 config SPI_OMAP24XX
        bool "McSPI driver for OMAP24xx"
        depends on SPI_MASTER && ARCH_OMAP24XX
index 46e860fecd873d050fe9f61896635af633ef4aba..ba27054f8b4a99a247e9bfe3cbfa9c999ab43b5c 100644 (file)
@@ -14,6 +14,7 @@ obj-$(CONFIG_SPI_MASTER)              += spi.o
 obj-$(CONFIG_SPI_BITBANG)              += spi_bitbang.o
 obj-$(CONFIG_SPI_BUTTERFLY)            += spi_butterfly.o
 obj-$(CONFIG_SPI_OMAP24XX)             += omap2_mcspi.o
+obj-$(CONFIG_SPI_OMAP_UWIRE)           += omap_uwire.o
 #      ... add above this line ...
 
 # SPI protocol drivers (device/link on bus)
diff --git a/drivers/spi/omap_uwire.c b/drivers/spi/omap_uwire.c
new file mode 100644 (file)
index 0000000..93eb7d8
--- /dev/null
@@ -0,0 +1,481 @@
+/*
+ * omap_uwire.c -- MicroWire interface driver for OMAP
+ *
+ * Copyright 2003 MontaVista Software Inc. <source@mvista.com>
+ *
+ * Ported to 2.6 OMAP uwire interface.
+ * Copyright (C) 2004 Texas Instruments.
+ *
+ * Generalization patches by Juha Yrjölä <juha.yrjola@nokia.com>
+ *
+ * Copyright (C) 2005 David Brownell (ported to 2.6 SPI interface)
+ *
+ * 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 SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+
+#include <asm/system.h>
+#include <asm/irq.h>
+#include <asm/hardware.h>
+#include <asm/io.h>
+#include <asm/mach-types.h>
+
+#include <asm/arch/mux.h>
+#include <asm/arch/omap730.h>  /* OMAP730_IO_CONF registers */
+
+
+/* FIXME address is now a platform device resource,
+ * and irqs should show there too...
+ */
+#define UWIRE_BASE_PHYS                0xFFFB3000
+#define UWIRE_BASE             ((void *__iomem)IO_ADDRESS(UWIRE_BASE_PHYS))
+
+/* uWire Registers: */
+#define UWIRE_IO_SIZE 0x20
+#define UWIRE_TDR     0x00
+#define UWIRE_RDR     0x00
+#define UWIRE_CSR     0x01
+#define UWIRE_SR1     0x02
+#define UWIRE_SR2     0x03
+#define UWIRE_SR3     0x04
+#define UWIRE_SR4     0x05
+#define UWIRE_SR5     0x06
+
+/* CSR bits */
+#define        RDRB    (1 << 15)
+#define        CSRB    (1 << 14)
+#define        START   (1 << 13)
+#define        CS_CMD  (1 << 12)
+
+/* SR1 or SR2 bits */
+#define UWIRE_READ_FALLING_EDGE                0x0000
+#define UWIRE_READ_RISING_EDGE         0x0001
+#define UWIRE_WRITE_FALLING_EDGE       0x0000
+#define UWIRE_WRITE_RISING_EDGE                0x0002
+#define UWIRE_CS_ACTIVE_LOW            0x0000
+#define UWIRE_CS_ACTIVE_HIGH           0x0004
+#define UWIRE_FREQ_DIV_2               0x0000
+#define UWIRE_FREQ_DIV_4               0x0008
+#define UWIRE_FREQ_DIV_8               0x0010
+#define UWIRE_CHK_READY                        0x0020
+#define UWIRE_CLK_INVERTED             0x0040
+
+
+struct uwire_spi {
+       struct spi_bitbang      bitbang;
+       struct clk              *ck;
+};
+
+/* REVISIT compile time constant for idx_shift? */
+static unsigned int uwire_idx_shift;
+
+static inline void uwire_write_reg(int idx, u16 val)
+{
+       __raw_writew(val, UWIRE_BASE + (idx << uwire_idx_shift));
+}
+
+static inline u16 uwire_read_reg(int idx)
+{
+       return __raw_readw(UWIRE_BASE + (idx << uwire_idx_shift));
+}
+
+static inline void omap_uwire_configure_mode(u8 cs, unsigned long flags)
+{
+       u16     w, val = 0;
+       int     shift, reg;
+
+       if (flags & UWIRE_CLK_INVERTED)
+               val ^= 0x03;
+       val = flags & 0x3f;
+       if (cs & 1)
+               shift = 6;
+       else
+               shift = 0;
+       if (cs <= 1)
+               reg = UWIRE_SR1;
+       else
+               reg = UWIRE_SR2;
+
+       w = uwire_read_reg(reg);
+       w &= ~(0x3f << shift);
+       w |= val << shift;
+       uwire_write_reg(reg, w);
+}
+
+static int wait_uwire_csr_flag(u16 mask, u16 val, int might_not_catch)
+{
+       u16 w;
+       int c = 0;
+       unsigned long max_jiffies = jiffies + HZ;
+
+       for (;;) {
+               w = uwire_read_reg(UWIRE_CSR);
+               if ((w & mask) == val)
+                       break;
+               if (time_after(jiffies, max_jiffies)) {
+                       printk(KERN_ERR "%s: timeout. reg=%#06x "
+                                       "mask=%#06x val=%#06x\n",
+                              __FUNCTION__, w, mask, val);
+                       return -1;
+               }
+               c++;
+               if (might_not_catch && c > 64)
+                       break;
+       }
+       return 0;
+}
+
+static void uwire_chipselect(struct spi_device *spi, int value)
+{
+       u16     w;
+
+       BUG_ON(wait_uwire_csr_flag(CSRB, 0, 0));
+
+       /* activate/deactivate specfied chipselect */
+       if (value == BITBANG_CS_ACTIVE) {
+               /* invert clock? */
+               if (spi->mode & SPI_CPOL)
+                       uwire_write_reg(UWIRE_SR4, 1);
+               else
+                       uwire_write_reg(UWIRE_SR4, 0);
+
+               w = spi->chip_select << 10;
+               w |= CS_CMD;
+       } else
+               w = 0;
+       uwire_write_reg(UWIRE_CSR, w);
+}
+
+static int uwire_txrx(struct spi_device *spi, struct spi_transfer *t)
+{
+       unsigned        len = t->len;
+       unsigned        bits = spi->bits_per_word;
+       unsigned        bytes;
+       u16             val, w;
+       int             status = 0;;
+
+       if (!t->tx_buf && !t->rx_buf)
+               return 0;
+
+       /* Microwire doesn't read and write concurrently */
+       if (t->tx_buf && t->rx_buf)
+               return -EPERM;
+
+       w = spi->chip_select << 10;
+       w |= CS_CMD;
+
+       if (t->tx_buf) {
+               const u8        *buf = t->tx_buf;
+
+               /* NOTE:  DMA could be used for TX transfers */
+
+               /* write one or two bytes at a time */
+               while (len >= 1) {
+                       /* tx is msb-aligned */
+                       val = *buf++;
+                       if (len > 1 && (!bits || bits > 8)) {
+                               if (!bits)
+                                       bits = 16;
+                               bytes = 2;
+                               val |= *buf++ << 8;
+                       } else {
+                               if (!bits || bits > 8)
+                                       bits = 8;
+                               bytes = 1;
+                       }
+                       val <<= 16 - bits;
+
+#ifdef VERBOSE
+                       pr_debug("%s: write-%d =%04x\n",
+                                       spi->dev.bus_id, bits, val);
+#endif
+                       uwire_write_reg(UWIRE_TDR, val);
+
+                       /* start write */
+                       val = START | w | (bits << 5);
+                       if (wait_uwire_csr_flag(CSRB, 0, 0))
+                               goto eio;
+
+                       uwire_write_reg(UWIRE_CSR, val);
+                       len -= bytes;
+
+                       /* Wait till write actually starts.
+                        * This is needed with MPU clock 60+ MHz.
+                        * REVISIT: we may not have time to catch it...
+                        */
+                       if (wait_uwire_csr_flag(CSRB, CSRB, 1))
+                               goto eio;
+
+                       status += bytes;
+               }
+
+               /* REVISIT:  save this for later to get more i/o overlap */
+               if (wait_uwire_csr_flag(CSRB, 0, 0))
+                       goto eio;
+
+       } else if (t->rx_buf) {
+               u8              *buf = t->rx_buf;
+
+               /* read one or two bytes at a time */
+               while (len) {
+                       if (len > 1 && (!bits || bits > 8)) {
+                               if (!bits)
+                                       bits = 16;
+                               bytes = 2;
+                       } else {
+                               if (!bits || bits > 8)
+                                       bits = 8;
+                               bytes = 1;
+                       }
+
+                       /* start read */
+                       val = START | w | (bits << 0);
+                       uwire_write_reg(UWIRE_CSR, val);
+                       len -= bytes;
+
+                       /* Wait till read actually starts */
+                       (void) wait_uwire_csr_flag(CSRB, CSRB, 1);
+
+                       if (wait_uwire_csr_flag(RDRB | CSRB,
+                                               RDRB, 0))
+                               goto eio;
+
+                       /* rx is lsb-aligned */
+                       val = uwire_read_reg(UWIRE_RDR);
+                       val &= (1 << bits) - 1;
+                       *buf++ = (u8) val;
+                       if (bytes == 2)
+                               *buf++ = val >> 8;
+                       status += len;
+#ifdef VERBOSE
+                       pr_debug("%s: read-%d =%04x\n",
+                                       spi->dev.bus_id, bits, val);
+#endif
+
+               }
+       }
+       return status;
+eio:
+       return -EIO;
+}
+
+static int uwire_setup(struct spi_device *spi)
+{
+       struct uwire_spi        *uwire;
+       unsigned                flags = 0;
+       unsigned long           rate;
+       u16                     div1;
+       int                     status;
+
+       uwire = spi_master_get_devdata(spi->master);
+
+       if (spi->chip_select > 3) {
+               pr_debug("%s: cs%d?\n", spi->dev.bus_id, spi->chip_select);
+               status = -ENODEV;
+               goto done;
+       }
+
+       if (spi->bits_per_word > 16) {
+               pr_debug("%s: wordsize %d?\n", spi->dev.bus_id,
+                               spi->bits_per_word);
+               status = -ENODEV;
+               goto done;
+       }
+
+       /* mode 0..3, clock inverted separately;
+        * standard nCS signaling;
+        * don't treat DI=high as "not ready"
+        */
+       if (spi->mode & SPI_CS_HIGH)
+               flags |= UWIRE_CS_ACTIVE_HIGH;
+
+       if (spi->mode & SPI_CPOL)
+               flags |= UWIRE_CLK_INVERTED;
+
+       switch (spi->mode & (SPI_CPOL | SPI_CPHA)) {
+       case SPI_MODE_0:
+       case SPI_MODE_3:
+               flags |= UWIRE_WRITE_FALLING_EDGE | UWIRE_READ_FALLING_EDGE;
+               break;
+       case SPI_MODE_1:
+       case SPI_MODE_2:
+               flags |= UWIRE_WRITE_FALLING_EDGE | UWIRE_READ_RISING_EDGE;
+               break;
+       }
+
+       /* assume it's already enabled */
+       rate = clk_get_rate(uwire->ck);
+
+       /* F_INT = mpu_per_clk / DIV1 */
+       div1 = (uwire_read_reg(UWIRE_SR3) >> 1) & 0x3;
+       switch (div1) {
+       case 0: rate /= 2; break;
+       case 1: rate /= 4; break;
+       case 2: rate /= 7; break;
+       case 3: rate /= 10; break;
+       }
+
+       /* SCLK = F_INT / DIV2 */
+       rate >>= 1;
+       if (rate <= spi->max_speed_hz)
+               flags |= UWIRE_FREQ_DIV_2;
+       else {
+               rate >>= 1;
+               if (rate <= spi->max_speed_hz)
+                       flags |= UWIRE_FREQ_DIV_4;
+               else {
+                       rate >>= 1;
+                       if (rate <= spi->max_speed_hz)
+                               flags |= UWIRE_FREQ_DIV_8;
+                       else {
+                               /* REVISIT:  we could change DIV2 */
+                               pr_debug("%s: lowest clock %ld, need %d, "
+                                               "div1 %d\n",
+                                       spi->dev.bus_id, rate,
+                                       spi->max_speed_hz, div1);
+                               status = -EDOM;
+                               goto done;
+                       }
+               }
+       }
+       omap_uwire_configure_mode(spi->chip_select, flags);
+       pr_debug("%s: uwire flags %02x, armper %lu KHz, SCK %lu KHz\n",
+                       __FUNCTION__, flags,
+                       clk_get_rate(uwire->ck) / 1000,
+                       rate / 1000);
+       status = 0;
+done:
+       return status;
+}
+
+static void uwire_off(struct uwire_spi *uwire)
+{
+       uwire_write_reg(UWIRE_SR3, 0);
+       clk_disable(uwire->ck);
+       clk_put(uwire->ck);
+       spi_master_put(uwire->bitbang.master);
+}
+
+static int uwire_probe(struct platform_device *pdev)
+{
+       struct spi_master       *master;
+       struct uwire_spi        *uwire;
+       int                     status;
+
+       master = spi_alloc_master(&pdev->dev, sizeof *uwire);
+       if (!master)
+               return -ENODEV;
+
+       uwire = spi_master_get_devdata(master);
+       dev_set_drvdata(&pdev->dev, uwire);
+
+       uwire->ck = clk_get(&pdev->dev, "armper_ck");
+       if (!uwire->ck || IS_ERR(uwire->ck)) {
+               dev_dbg(&pdev->dev, "no mpu_per_clk ?\n");
+               spi_master_put(master);
+               return -ENODEV;
+       }
+       clk_enable(uwire->ck);
+
+       if (cpu_is_omap730())
+               uwire_idx_shift = 1;
+       else
+               uwire_idx_shift = 2;
+
+       uwire_write_reg(UWIRE_SR3, 1);
+
+       master->bus_num = 2;    /* "official" */
+       master->num_chipselect = 4;
+       master->setup = uwire_setup;
+
+       uwire->bitbang.master = master;
+       uwire->bitbang.chipselect = uwire_chipselect;
+       uwire->bitbang.txrx_bufs = uwire_txrx;
+
+       status = spi_bitbang_start(&uwire->bitbang);
+       if (status < 0)
+               uwire_off(uwire);
+       return status;
+}
+
+static int uwire_remove(struct platform_device *pdev)
+{
+       struct uwire_spi        *uwire = dev_get_drvdata(&pdev->dev);
+       int                     status;
+
+       // FIXME remove all child devices, somewhere ...
+
+       status = spi_bitbang_stop(&uwire->bitbang);
+       uwire_off(uwire);
+       return status;
+}
+
+static struct platform_driver uwire_driver = {
+       .driver = {
+               .name           = "omap_uwire",
+               .bus            = &platform_bus_type,
+               .owner          = THIS_MODULE,
+       },
+       .probe          = uwire_probe,
+       .remove         = uwire_remove,
+       // suspend ... unuse ck
+       // resume ... use ck
+};
+
+static int __init omap_uwire_init(void)
+{
+       /* FIXME move these into the relevant board init code. also, include
+        * H3 support; it uses tsc2101 like H2 (on a different chipselect).
+        */
+
+       if (machine_is_omap_h2()) {
+               /* defaults: W21 SDO, U18 SDI, V19 SCL */
+               omap_cfg_reg(N14_1610_UWIRE_CS0);
+               omap_cfg_reg(N15_1610_UWIRE_CS1);
+       }
+       if (machine_is_omap_perseus2()) {
+               /* configure pins: MPU_UW_nSCS1, MPU_UW_SDO, MPU_UW_SCLK */
+               int val = omap_readl(OMAP730_IO_CONF_9) & ~0x00EEE000;
+               omap_writel(val | 0x00AAA000, OMAP730_IO_CONF_9);
+       }
+
+       return platform_driver_register(&uwire_driver);
+}
+
+static void __exit omap_uwire_exit(void)
+{
+       platform_driver_unregister(&uwire_driver);
+}
+
+subsys_initcall(omap_uwire_init);
+module_exit(omap_uwire_exit);
+
+MODULE_LICENSE("GPL");