openwrt-owl/target/linux/lantiq/patches/915-falcon-spi-flash.patch

498 lines
12 KiB
Diff

--- 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 *)&reg)[idx])
+#define reg_w32_table(val, reg, idx) reg_w32(val, &((uint32_t *)&reg)[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