From 017320a50ad85c0638e449544d2546ae3fc6cd72 Mon Sep 17 00:00:00 2001 From: Juha Yrjola Date: Wed, 8 Feb 2006 19:04:12 +0200 Subject: [PATCH] ARM: OMAP: Add SPI master driver for OMAP1 uWire controller MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The converted driver originated from David Brownell, with some modifications by Imre. Signed-off-by: Imre Deak Signed-off-by: Juha Yrjölä --- arch/arm/plat-omap/devices.c | 50 ++++ drivers/spi/Kconfig | 7 + drivers/spi/Makefile | 1 + drivers/spi/omap_uwire.c | 481 +++++++++++++++++++++++++++++++++++ 4 files changed, 539 insertions(+) create mode 100644 drivers/spi/omap_uwire.c diff --git a/arch/arm/plat-omap/devices.c b/arch/arm/plat-omap/devices.c index 572f86d2ce5..756b3d913f9 100644 --- a/arch/arm/plat-omap/devices.c +++ b/arch/arm/plat-omap/devices.c @@ -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(); diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index fad44fa763b..6ca4eec3be5 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -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 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 46e860fecd8..ba27054f8b4 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -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 index 00000000000..93eb7d8683f --- /dev/null +++ b/drivers/spi/omap_uwire.c @@ -0,0 +1,481 @@ +/* + * omap_uwire.c -- MicroWire interface driver for OMAP + * + * Copyright 2003 MontaVista Software Inc. + * + * Ported to 2.6 OMAP uwire interface. + * Copyright (C) 2004 Texas Instruments. + * + * Generalization patches by Juha Yrjölä + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include /* 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"); -- 2.41.1