| /* | |
| * 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. | |
| */ | |
| #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/slab.h> | |
| #include <linux/device.h> | |
| #include <linux/spi/spi.h> | |
| #include <linux/spi/spi_bitbang.h> | |
| #include <linux/module.h> | |
| #include <linux/io.h> | |
| #include <mach/hardware.h> | |
| #include <asm/mach-types.h> | |
| #include <mach/mux.h> | |
| #include <mach/omap7xx.h> /* OMAP7XX_IO_CONF registers */ | |
| /* FIXME address is now a platform device resource, | |
| * and irqs should show there too... | |
| */ | |
| #define UWIRE_BASE_PHYS 0xFFFB3000 | |
| /* 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 div1_idx; | |
| }; | |
| /* REVISIT compile time constant for idx_shift? */ | |
| /* | |
| * Or, put it in a structure which is used throughout the driver; | |
| * that avoids having to issue two loads for each bit of static data. | |
| */ | |
| static unsigned int uwire_idx_shift; | |
| static void __iomem *uwire_base; | |
| 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", | |
| __func__, 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) | |
| { | |
| unsigned len = t->len; | |
| unsigned bits = t->bits_per_word; | |
| unsigned bytes; | |
| u16 val, w; | |
| int status = 0; | |
| if (!t->tx_buf && !t->rx_buf) | |
| return 0; | |
| 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", | |
| dev_name(&spi->dev), 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", | |
| dev_name(&spi->dev), 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 hz; | |
| unsigned long rate; | |
| int div1_idx; | |
| int div1; | |
| int div2; | |
| int status; | |
| uwire = spi_master_get_devdata(spi->master); | |
| /* 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_RISING_EDGE; | |
| break; | |
| case SPI_MODE_1: | |
| case SPI_MODE_2: | |
| flags |= UWIRE_WRITE_RISING_EDGE | UWIRE_READ_FALLING_EDGE; | |
| break; | |
| } | |
| /* assume it's already enabled */ | |
| rate = clk_get_rate(uwire->ck); | |
| if (t != NULL) | |
| hz = t->speed_hz; | |
| else | |
| hz = spi->max_speed_hz; | |
| if (!hz) { | |
| pr_debug("%s: zero speed?\n", dev_name(&spi->dev)); | |
| 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", | |
| dev_name(&spi->dev), 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", | |
| __func__, 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(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); | |
| 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); | |
| uwire_base = devm_ioremap(&pdev->dev, UWIRE_BASE_PHYS, UWIRE_IO_SIZE); | |
| if (!uwire_base) { | |
| dev_dbg(&pdev->dev, "can't ioremap UWIRE\n"); | |
| spi_master_put(master); | |
| return -ENOMEM; | |
| } | |
| platform_set_drvdata(pdev, uwire); | |
| uwire->ck = devm_clk_get(&pdev->dev, "fck"); | |
| if (IS_ERR(uwire->ck)) { | |
| status = PTR_ERR(uwire->ck); | |
| dev_dbg(&pdev->dev, "no functional clock?\n"); | |
| spi_master_put(master); | |
| return status; | |
| } | |
| clk_enable(uwire->ck); | |
| if (cpu_is_omap7xx()) | |
| uwire_idx_shift = 1; | |
| else | |
| uwire_idx_shift = 2; | |
| uwire_write_reg(UWIRE_SR3, 1); | |
| /* the spi->mode bits understood by this driver: */ | |
| master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; | |
| master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 16); | |
| master->flags = SPI_MASTER_HALF_DUPLEX; | |
| 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 = platform_get_drvdata(pdev); | |
| // FIXME remove all child devices, somewhere ... | |
| spi_bitbang_stop(&uwire->bitbang); | |
| uwire_off(uwire); | |
| return 0; | |
| } | |
| /* work with hotplug and coldplug */ | |
| MODULE_ALIAS("platform:omap_uwire"); | |
| static struct platform_driver uwire_driver = { | |
| .driver = { | |
| .name = "omap_uwire", | |
| }, | |
| .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(OMAP7XX_IO_CONF_9) & ~0x00EEE000; | |
| omap_writel(val | 0x00AAA000, OMAP7XX_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"); | |