| /* |
| * 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 Yrjola <juha.yrjola@nokia.com> |
| * |
| * Copyright (C) 2005 David Brownell (ported to 2.6 SPI interface) |
| * Copyright (C) 2006 Nokia |
| * |
| * Many updates by Imre Deak <imre.deak@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 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/kernel.h> |
| #include <linux/init.h> |
| #include <linux/delay.h> |
| #include <linux/platform_device.h> |
| #include <linux/workqueue.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 0x0001 |
| #define UWIRE_READ_RISING_EDGE 0x0000 |
| #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; |
| }; |
| |
| struct uwire_state { |
| unsigned bits_per_word; |
| unsigned div1_idx; |
| }; |
| |
| /* 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_set_clk1_div(int div1_idx) |
| { |
| u16 w; |
| |
| w = uwire_read_reg(UWIRE_SR3); |
| w &= ~(0x03 << 1); |
| w |= div1_idx << 1; |
| uwire_write_reg(UWIRE_SR3, w); |
| } |
| |
| static void uwire_chipselect(struct spi_device *spi, int value) |
| { |
| struct uwire_state *ust = spi->controller_state; |
| u16 w; |
| int old_cs; |
| |
| |
| BUG_ON(wait_uwire_csr_flag(CSRB, 0, 0)); |
| |
| w = uwire_read_reg(UWIRE_CSR); |
| old_cs = (w >> 10) & 0x03; |
| if (value == BITBANG_CS_INACTIVE || old_cs != spi->chip_select) { |
| /* Deselect this CS, or the previous CS */ |
| w &= ~CS_CMD; |
| uwire_write_reg(UWIRE_CSR, w); |
| } |
| /* activate specfied chipselect */ |
| if (value == BITBANG_CS_ACTIVE) { |
| uwire_set_clk1_div(ust->div1_idx); |
| /* 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; |
| uwire_write_reg(UWIRE_CSR, w); |
| } |
| } |
| |
| static int uwire_txrx(struct spi_device *spi, struct spi_transfer *t) |
| { |
| struct uwire_state *ust = spi->controller_state; |
| unsigned len = t->len; |
| unsigned bits = ust->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 bit 15 is first sent; we byteswap multibyte words |
| * (msb-first) on the way out from memory. |
| */ |
| val = *buf++; |
| if (bits > 8) { |
| bytes = 2; |
| val |= *buf++ << 8; |
| } else |
| bytes = 1; |
| val <<= 16 - bits; |
| |
| #ifdef VERBOSE |
| pr_debug("%s: write-%d =%04x\n", |
| spi->dev.bus_id, bits, val); |
| #endif |
| if (wait_uwire_csr_flag(CSRB, 0, 0)) |
| goto eio; |
| |
| uwire_write_reg(UWIRE_TDR, val); |
| |
| /* start write */ |
| val = START | w | (bits << 5); |
| |
| 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 (bits > 8) { |
| bytes = 2; |
| } else |
| 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 bit 0 is last received; multibyte words will |
| * be properly byteswapped on the way to memory. |
| */ |
| val = uwire_read_reg(UWIRE_RDR); |
| val &= (1 << bits) - 1; |
| *buf++ = (u8) val; |
| if (bytes == 2) |
| *buf++ = val >> 8; |
| status += bytes; |
| #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_transfer(struct spi_device *spi, struct spi_transfer *t) |
| { |
| struct uwire_state *ust = spi->controller_state; |
| struct uwire_spi *uwire; |
| unsigned flags = 0; |
| unsigned bits; |
| unsigned hz; |
| unsigned long rate; |
| int div1_idx; |
| int div1; |
| int div2; |
| 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; |
| } |
| |
| bits = spi->bits_per_word; |
| if (t != NULL && t->bits_per_word) |
| bits = t->bits_per_word; |
| if (!bits) |
| bits = 8; |
| |
| if (bits > 16) { |
| pr_debug("%s: wordsize %d?\n", spi->dev.bus_id, bits); |
| status = -ENODEV; |
| goto done; |
| } |
| ust->bits_per_word = bits; |
| |
| /* 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_RISING_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); |
| |
| hz = spi->max_speed_hz; |
| if (t != NULL && t->speed_hz) |
| hz = t->speed_hz; |
| |
| if (!hz) { |
| pr_debug("%s: zero speed?\n", spi->dev.bus_id); |
| status = -EINVAL; |
| goto done; |
| } |
| |
| /* F_INT = mpu_xor_clk / DIV1 */ |
| for (div1_idx = 0; div1_idx < 4; div1_idx++) { |
| switch (div1_idx) { |
| case 0: |
| div1 = 2; |
| break; |
| case 1: |
| div1 = 4; |
| break; |
| case 2: |
| div1 = 7; |
| break; |
| default: |
| case 3: |
| div1 = 10; |
| break; |
| } |
| div2 = (rate / div1 + hz - 1) / hz; |
| if (div2 <= 8) |
| break; |
| } |
| if (div1_idx == 4) { |
| pr_debug("%s: lowest clock %ld, need %d\n", |
| spi->dev.bus_id, rate / 10 / 8, hz); |
| status = -EDOM; |
| goto done; |
| } |
| |
| /* we have to cache this and reset in uwire_chipselect as this is a |
| * global parameter and another uwire device can change it under |
| * us */ |
| ust->div1_idx = div1_idx; |
| uwire_set_clk1_div(div1_idx); |
| |
| rate /= div1; |
| |
| switch (div2) { |
| case 0: |
| case 1: |
| case 2: |
| flags |= UWIRE_FREQ_DIV_2; |
| rate /= 2; |
| break; |
| case 3: |
| case 4: |
| flags |= UWIRE_FREQ_DIV_4; |
| rate /= 4; |
| break; |
| case 5: |
| case 6: |
| case 7: |
| case 8: |
| flags |= UWIRE_FREQ_DIV_8; |
| rate /= 8; |
| break; |
| } |
| omap_uwire_configure_mode(spi->chip_select, flags); |
| pr_debug("%s: uwire flags %02x, armxor %lu KHz, SCK %lu KHz\n", |
| __FUNCTION__, flags, |
| clk_get_rate(uwire->ck) / 1000, |
| rate / 1000); |
| status = 0; |
| done: |
| return status; |
| } |
| |
| static int uwire_setup(struct spi_device *spi) |
| { |
| struct uwire_state *ust = spi->controller_state; |
| |
| if (ust == NULL) { |
| ust = kzalloc(sizeof(*ust), GFP_KERNEL); |
| if (ust == NULL) |
| return -ENOMEM; |
| spi->controller_state = ust; |
| } |
| |
| return uwire_setup_transfer(spi, NULL); |
| } |
| |
| static void uwire_cleanup(const struct spi_device *spi) |
| { |
| kfree(spi->controller_state); |
| } |
| |
| 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, "armxor_ck"); |
| if (!uwire->ck || IS_ERR(uwire->ck)) { |
| dev_dbg(&pdev->dev, "no mpu_xor_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; |
| master->cleanup = uwire_cleanup; |
| |
| uwire->bitbang.master = master; |
| uwire->bitbang.chipselect = uwire_chipselect; |
| uwire->bitbang.setup_transfer = uwire_setup_transfer; |
| 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"); |
| |