--- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_SPI_BITBANG) += spi_bitbang.o obj-$(CONFIG_SPI_AU1550) += au1550_spi.o obj-$(CONFIG_SPI_BUTTERFLY) += spi_butterfly.o +obj-$(CONFIG_SPI_FALCON) += spi_falcon.o obj-$(CONFIG_SPI_GPIO) += spi_gpio.o obj-$(CONFIG_SPI_GPIO_OLD) += spi_gpio_old.o obj-$(CONFIG_SPI_IMX) += spi_imx.o --- /dev/null +++ b/drivers/spi/spi_falcon.c @@ -0,0 +1,471 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include /* ebu_lock */ +#include +#include + +#define DRV_NAME "falcon_spi" + +#define FALCON_SPI_XFER_BEGIN (1 << 0) +#define FALCON_SPI_XFER_END (1 << 1) + +/* mapping for access macros */ +#define reg_r32(reg) __raw_readl(reg) +#define reg_w32(val, reg) __raw_writel(val, reg) +#define reg_w32_mask(clear, set, reg) reg_w32((reg_r32(reg) \ + & ~(clear)) | (set), reg) +#define reg_r32_table(reg, idx) reg_r32(&((uint32_t *)®)[idx]) +#define reg_w32_table(val, reg, idx) reg_w32(val, &((uint32_t *)®)[idx]) + +#define ebu (priv->ebu_membase) +#define sys1 (priv->sys1_membase) + +struct falcon_spi { + u32 sfcmd; /* for caching of opcode, direction, ... */ + + struct spi_master *master; + + struct gpon_reg_ebu __iomem *ebu_membase; + struct gpon_reg_sys1 __iomem *sys1_membase; +}; + +int falcon_spi_xfer(struct spi_device *spi, + struct spi_transfer *t, + unsigned long flags) +{ + struct device *dev = &spi->dev; + struct falcon_spi *priv = spi_master_get_devdata(spi->master); + const u8 *txp = t->tx_buf; + u8 *rxp = t->rx_buf; + unsigned int bytelen = ((8 * t->len + 7) / 8); + unsigned int len, alen, dumlen; + u32 val; + enum { + state_init, + state_command_prepare, + state_write, + state_read, + state_disable_cs, + state_end + } state = state_init; + + do { + switch (state) { + case state_init: /* detect phase of upper layer sequence */ + { + /* initial write ? */ + if (flags & FALCON_SPI_XFER_BEGIN) { + if (!txp) { + dev_err(dev, + "BEGIN without tx data!\n"); + return -1; + } + /* + * Prepare the parts of the sfcmd register, + * which should not + * change during a sequence! + * Only exception are the length fields, + * especially alen and dumlen. + */ + + priv->sfcmd = ((spi->chip_select + << SFCMD_CS_OFFSET) + & SFCMD_CS_MASK); + priv->sfcmd |= SFCMD_KEEP_CS_KEEP_SELECTED; + priv->sfcmd |= *txp; + txp++; + bytelen--; + if (bytelen) { + /* more data: + * maybe address and/or dummy */ + state = state_command_prepare; + break; + } else { + dev_dbg(dev, "write cmd %02X\n", + priv->sfcmd & SFCMD_OPC_MASK); + } + } + /* continued write ? */ + if (txp && bytelen) { + state = state_write; + break; + } + /* read data? */ + if (rxp && bytelen) { + state = state_read; + break; + } + /* end of sequence? */ + if (flags & FALCON_SPI_XFER_END) + state = state_disable_cs; + else + state = state_end; + break; + } + case state_command_prepare: /* collect tx data for + address and dummy phase */ + { + /* txp is valid, already checked */ + val = 0; + alen = 0; + dumlen = 0; + while (bytelen > 0) { + if (alen < 3) { + val = (val<<8)|(*txp++); + alen++; + } else if ((dumlen < 15) && (*txp == 0)) { + /* + * assume dummy bytes are set to 0 + * from upper layer + */ + dumlen++; + txp++; + } else + break; + bytelen--; + } + priv->sfcmd &= ~(SFCMD_ALEN_MASK | SFCMD_DUMLEN_MASK); + priv->sfcmd |= (alen << SFCMD_ALEN_OFFSET) | + (dumlen << SFCMD_DUMLEN_OFFSET); + if (alen > 0) + ebu_w32(val, sfaddr); + + dev_dbg(dev, "write cmd %02X, alen=%d " + "(addr=%06X) dumlen=%d\n", + priv->sfcmd & SFCMD_OPC_MASK, + alen, val, dumlen); + + if (bytelen > 0) { + /* continue with write */ + state = state_write; + } else if (flags & FALCON_SPI_XFER_END) { + /* end of sequence? */ + state = state_disable_cs; + } else { + /* go to end and expect another + * call (read or write) */ + state = state_end; + } + break; + } + case state_write: + { + /* txp still valid */ + priv->sfcmd |= SFCMD_DIR_WRITE; + len = 0; + val = 0; + do { + if (bytelen--) + val |= (*txp++) << (8 * len++); + if ((flags & FALCON_SPI_XFER_END) + && (bytelen == 0)) { + priv->sfcmd &= + ~SFCMD_KEEP_CS_KEEP_SELECTED; + } + if ((len == 4) || (bytelen == 0)) { + ebu_w32(val, sfdata); + ebu_w32(priv->sfcmd + | (len<sfcmd &= ~(SFCMD_ALEN_MASK + | SFCMD_DUMLEN_MASK); + } + } while (bytelen); + state = state_end; + break; + } + case state_read: + { + /* read data */ + priv->sfcmd &= ~SFCMD_DIR_WRITE; + do { + if ((flags & FALCON_SPI_XFER_END) + && (bytelen <= 4)) { + priv->sfcmd &= + ~SFCMD_KEEP_CS_KEEP_SELECTED; + } + len = (bytelen > 4) ? 4 : bytelen; + bytelen -= len; + ebu_w32(priv->sfcmd + |(len<sfcmd &= ~(SFCMD_ALEN_MASK + | SFCMD_DUMLEN_MASK); + do { + val = ebu_r32(sfstat); + if (val & SFSTAT_CMD_ERR) { + /* reset error status */ + dev_err(dev, "SFSTAT: CMD_ERR " + "(%x)\n", val); + ebu_w32(SFSTAT_CMD_ERR, sfstat); + return -1; + } + } while (val & SFSTAT_CMD_PEND); + val = ebu_r32(sfdata); + do { + *rxp = (val & 0xFF); + rxp++; + val >>= 8; + len--; + } while (len); + } while (bytelen); + state = state_end; + break; + } + case state_disable_cs: + { + priv->sfcmd &= ~SFCMD_KEEP_CS_KEEP_SELECTED; + ebu_w32(priv->sfcmd | (0<dev; + struct falcon_spi *priv = spi_master_get_devdata(spi->master); + const u32 ebuclk = 100*1000*1000; + unsigned int i; + unsigned long flags; + + dev_dbg(dev, "setup\n"); + + if (spi->master->bus_num > 0 || spi->chip_select > 0) + return -ENODEV; + + spin_lock_irqsave(&ebu_lock, flags); + + if (ebuclk < spi->max_speed_hz) { + /* set EBU clock to 100 MHz */ + sys1_w32_mask(0, EBUCC_EBUDIV_SELF100, ebucc); + i = 1; /* divider */ + } else { + /* set EBU clock to 50 MHz */ + sys1_w32_mask(EBUCC_EBUDIV_SELF100, 0, ebucc); + + /* search for suitable divider */ + for (i = 1; i < 7; i++) { + if (ebuclk / i <= spi->max_speed_hz) + break; + } + } + + /* setup period of serial clock */ + ebu_w32_mask(SFTIME_SCKF_POS_MASK + | SFTIME_SCKR_POS_MASK + | SFTIME_SCK_PER_MASK, + (i << SFTIME_SCKR_POS_OFFSET) + | (i << (SFTIME_SCK_PER_OFFSET + 1)), + sftime); + + /* set some bits of unused_wd, to not trigger HOLD/WP + * signals on non QUAD flashes */ + ebu_w32((SFIO_UNUSED_WD_MASK & (0x8|0x4)), sfio); + + ebu_w32(BUSRCON0_AGEN_SERIAL_FLASH | BUSRCON0_PORTW_8_BIT_MUX, + busrcon0); + ebu_w32(BUSWCON0_AGEN_SERIAL_FLASH, buswcon0); + /* set address wrap around to maximum for 24-bit addresses */ + ebu_w32_mask(SFCON_DEV_SIZE_MASK, SFCON_DEV_SIZE_A23_0, sfcon); + + spin_unlock_irqrestore(&ebu_lock, flags); + + return 0; +} + +static int falcon_spi_transfer(struct spi_device *spi, struct spi_message *m) +{ + struct falcon_spi *priv = spi_master_get_devdata(spi->master); + struct spi_transfer *t; + unsigned long spi_flags; + unsigned long flags; + int ret = 0; + + priv->sfcmd = 0; + m->actual_length = 0; + + spi_flags = FALCON_SPI_XFER_BEGIN; + list_for_each_entry(t, &m->transfers, transfer_list) { + if (list_is_last(&t->transfer_list, &m->transfers)) + spi_flags |= FALCON_SPI_XFER_END; + + spin_lock_irqsave(&ebu_lock, flags); + ret = falcon_spi_xfer(spi, t, spi_flags); + spin_unlock_irqrestore(&ebu_lock, flags); + + if (ret) + break; + + m->actual_length += t->len; + + if (t->delay_usecs || t->cs_change) + BUG(); + + spi_flags = 0; + } + + m->status = ret; + m->complete(m->context); + + return 0; +} + +static void falcon_spi_cleanup(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + + dev_dbg(dev, "cleanup\n"); +} + +static int __devinit falcon_spi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct falcon_spi *priv; + struct spi_master *master; + struct resource *memres_ebu, *memres_sys1; + int ret; + + dev_dbg(dev, "probing\n"); + + memres_ebu = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ebu"); + memres_sys1 = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "sys1"); + + if (!memres_ebu || !memres_sys1) { + dev_err(dev, "no resources\n"); + return -ENODEV; + } + + master = spi_alloc_master(&pdev->dev, sizeof(*priv)); + if (!master) { + dev_err(dev, "no memory for spi_master\n"); + return -ENOMEM; + } + + priv = spi_master_get_devdata(master); + + priv->ebu_membase = ioremap_nocache(memres_ebu->start & ~KSEG1, + resource_size(memres_ebu)); + + if (!priv->ebu_membase) { + dev_err(dev, "can't map ebu memory\n"); + + ret = -ENOMEM; + goto free_master; + } + + priv->sys1_membase = ioremap_nocache(memres_sys1->start & ~KSEG1, + resource_size(memres_sys1)); + + if (!priv->sys1_membase) { + dev_err(dev, "can't map sys1 memory\n"); + + ret = -ENOMEM; + goto unmap_ebu; + } + + priv->master = master; + + master->mode_bits = SPI_MODE_3; + master->num_chipselect = 1; + master->bus_num = 0; + + master->setup = falcon_spi_setup; + master->transfer = falcon_spi_transfer; + master->cleanup = falcon_spi_cleanup; + + platform_set_drvdata(pdev, priv); + + ret = spi_register_master(master); + if (ret) + goto unmap_sys1; + + return 0; + +unmap_sys1: + iounmap(priv->sys1_membase); + +unmap_ebu: + iounmap(priv->ebu_membase); + +free_master: + spi_master_put(master); + + return ret; +} + +static int __devexit falcon_spi_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct falcon_spi *priv = platform_get_drvdata(pdev); + + dev_dbg(dev, "removed\n"); + + spi_unregister_master(priv->master); + + iounmap(priv->sys1_membase); + iounmap(priv->ebu_membase); + + return 0; +} + +static struct platform_driver falcon_spi_driver = { + .probe = falcon_spi_probe, + .remove = __devexit_p(falcon_spi_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE + } +}; + +static int __init falcon_spi_init(void) +{ + return platform_driver_register(&falcon_spi_driver); +} + +static void __exit falcon_spi_exit(void) +{ + platform_driver_unregister(&falcon_spi_driver); +} + +module_init(falcon_spi_init); +module_exit(falcon_spi_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Lantiq Falcon SPI controller driver"); --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -142,6 +142,10 @@ which interfaces to an LM70 temperature sensor using a parallel port. +config SPI_FALCON + tristate "Falcon SPI controller support" + depends on SOC_FALCON + config SPI_MPC52xx_PSC tristate "Freescale MPC52xx PSC SPI controller" depends on PPC_MPC52xx && EXPERIMENTAL