diff options
Diffstat (limited to 'target/linux/lantiq/patches/915-falcon-spi-flash.patch')
-rw-r--r-- | target/linux/lantiq/patches/915-falcon-spi-flash.patch | 497 |
1 files changed, 497 insertions, 0 deletions
diff --git a/target/linux/lantiq/patches/915-falcon-spi-flash.patch b/target/linux/lantiq/patches/915-falcon-spi-flash.patch new file mode 100644 index 0000000000..d24183e0d7 --- /dev/null +++ b/target/linux/lantiq/patches/915-falcon-spi-flash.patch @@ -0,0 +1,497 @@ +--- a/drivers/spi/Makefile ++++ b/drivers/spi/Makefile +@@ -22,6 +22,7 @@ obj-$(CONFIG_SPI_DESIGNWARE) += dw_spi. + obj-$(CONFIG_SPI_DW_PCI) += dw_spi_pci.o + obj-$(CONFIG_SPI_DW_MMIO) += dw_spi_mmio.o + obj-$(CONFIG_SPI_EP93XX) += ep93xx_spi.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 <linux/module.h> ++#include <linux/device.h> ++#include <linux/platform_device.h> ++#include <linux/spi/spi.h> ++#include <linux/delay.h> ++#include <linux/workqueue.h> ++ ++#include <lantiq.h> /* ebu_lock */ ++#include <falcon/ebu_reg.h> ++#include <falcon/sys1_reg.h> ++ ++#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_DLEN_OFFSET), ++ sfcmd); ++ len = 0; ++ val = 0; ++ priv->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_DLEN_OFFSET), sfcmd); ++ priv->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<<SFCMD_DLEN_OFFSET), sfcmd); ++ 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; ++ } ++ state = state_end; ++ break; ++ } ++ case state_end: ++ break; ++ } ++ } while (state != state_end); ++ ++ return 0; ++} ++ ++static int falcon_spi_setup(struct spi_device *spi) ++{ ++ struct device *dev = &spi->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 +@@ -169,6 +169,10 @@ config SPI_LM70_LLP + which interfaces to an LM70 temperature sensor using + a parallel port. + ++config SPI_FALCON ++ tristate "Falcon SPI controller support" ++ depends on SOC_LANTIQ_FALCON ++ + config SPI_MPC52xx + tristate "Freescale MPC52xx SPI (non-PSC) controller support" + depends on PPC_MPC52xx && SPI |