[lantiq] cleanup patches
[openwrt/svn-archive/archive.git] / target / linux / lantiq / files / drivers / spi / spi-falcon.c
diff --git a/target/linux/lantiq/files/drivers/spi/spi-falcon.c b/target/linux/lantiq/files/drivers/spi/spi-falcon.c
new file mode 100644 (file)
index 0000000..447bbaa
--- /dev/null
@@ -0,0 +1,483 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License version 2 as published
+ *  by the Free Software Foundation.
+ *
+ *  Copyright (C) 2010 Thomas Langer <thomas.langer@lantiq.com>
+ */
+
+#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_soc.h>
+
+#define DRV_NAME                       "falcon_spi"
+
+#define FALCON_SPI_XFER_BEGIN          (1 << 0)
+#define FALCON_SPI_XFER_END            (1 << 1)
+
+/* Bus Read Configuration Register0 */
+#define LTQ_BUSRCON0   0x00000010
+/* Bus Write Configuration Register0 */
+#define LTQ_BUSWCON0   0x00000018
+/* Serial Flash Configuration Register */
+#define LTQ_SFCON      0x00000080
+/* Serial Flash Time Register */
+#define LTQ_SFTIME     0x00000084
+/* Serial Flash Status Register */
+#define LTQ_SFSTAT     0x00000088
+/* Serial Flash Command Register */
+#define LTQ_SFCMD      0x0000008C
+/* Serial Flash Address Register */
+#define LTQ_SFADDR     0x00000090
+/* Serial Flash Data Register */
+#define LTQ_SFDATA     0x00000094
+/* Serial Flash I/O Control Register */
+#define LTQ_SFIO       0x00000098
+/* EBU Clock Control Register */
+#define LTQ_EBUCC      0x000000C4
+
+/* Dummy Phase Length */
+#define SFCMD_DUMLEN_OFFSET    16
+#define SFCMD_DUMLEN_MASK      0x000F0000
+/* Chip Select */
+#define SFCMD_CS_OFFSET                24
+#define SFCMD_CS_MASK          0x07000000
+/* field offset */
+#define SFCMD_ALEN_OFFSET      20
+#define SFCMD_ALEN_MASK                0x00700000
+/* SCK Rise-edge Position */
+#define SFTIME_SCKR_POS_OFFSET 8
+#define SFTIME_SCKR_POS_MASK   0x00000F00
+/* SCK Period */
+#define SFTIME_SCK_PER_OFFSET  0
+#define SFTIME_SCK_PER_MASK    0x0000000F
+/* SCK Fall-edge Position */
+#define SFTIME_SCKF_POS_OFFSET 12
+#define SFTIME_SCKF_POS_MASK   0x0000F000
+/* Device Size */
+#define SFCON_DEV_SIZE_A23_0   0x03000000
+#define SFCON_DEV_SIZE_MASK    0x0F000000
+/* Read Data Position */
+#define SFTIME_RD_POS_MASK     0x000F0000
+/* Data Output */
+#define SFIO_UNUSED_WD_MASK    0x0000000F
+/* Command Opcode mask */
+#define SFCMD_OPC_MASK         0x000000FF
+/* dlen bytes of data to write */
+#define SFCMD_DIR_WRITE                0x00000100
+/* Data Length offset */
+#define SFCMD_DLEN_OFFSET      9
+/* Command Error */
+#define SFSTAT_CMD_ERR         0x20000000
+/* Access Command Pending */
+#define SFSTAT_CMD_PEND                0x00400000
+/* Frequency set to 100MHz. */
+#define EBUCC_EBUDIV_SELF100   0x00000001
+/* Serial Flash */
+#define BUSRCON0_AGEN_SERIAL_FLASH     0xF0000000
+/* 8-bit multiplexed */
+#define BUSRCON0_PORTW_8_BIT_MUX       0x00000000
+/* Serial Flash */
+#define BUSWCON0_AGEN_SERIAL_FLASH     0xF0000000
+/* Chip Select after opcode */
+#define SFCMD_KEEP_CS_KEEP_SELECTED    0x00008000
+
+struct falcon_spi {
+       u32 sfcmd; /* for caching of opcode, direction, ... */
+       struct spi_master *master;
+};
+
+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;
+               }
+               /* collect tx data for address and dummy phase */
+               case state_command_prepare:
+               {
+                       /* 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)
+                               ltq_ebu_w32(val, LTQ_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)) {
+                                       ltq_ebu_w32(val, LTQ_SFDATA);
+                                       ltq_ebu_w32(priv->sfcmd
+                                               | (len<<SFCMD_DLEN_OFFSET),
+                                               LTQ_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;
+                               ltq_ebu_w32(priv->sfcmd
+                                       |(len<<SFCMD_DLEN_OFFSET), LTQ_SFCMD);
+                               priv->sfcmd &= ~(SFCMD_ALEN_MASK
+                                                | SFCMD_DUMLEN_MASK);
+                               do {
+                                       val = ltq_ebu_r32(LTQ_SFSTAT);
+                                       if (val & SFSTAT_CMD_ERR) {
+                                               /* reset error status */
+                                               dev_err(dev, "SFSTAT: CMD_ERR "
+                                                       "(%x)\n", val);
+                                               ltq_ebu_w32(SFSTAT_CMD_ERR,
+                                                       LTQ_SFSTAT);
+                                               return -1;
+                                       }
+                               } while (val & SFSTAT_CMD_PEND);
+                               val = ltq_ebu_r32(LTQ_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;
+                       ltq_ebu_w32(priv->sfcmd | (0 << SFCMD_DLEN_OFFSET),
+                               LTQ_SFCMD);
+                       val = ltq_ebu_r32(LTQ_SFSTAT);
+                       if (val & SFSTAT_CMD_ERR) {
+                               /* reset error status */
+                               dev_err(dev, "SFSTAT: CMD_ERR (%x)\n", val);
+                               ltq_ebu_w32(SFSTAT_CMD_ERR, LTQ_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;
+       const u32 ebuclk = 100000000;
+       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 */
+               ltq_sys1_w32_mask(0, EBUCC_EBUDIV_SELF100, LTQ_EBUCC);
+               i = 1; /* divider */
+       } else {
+               /* set EBU clock to 50 MHz */
+               ltq_sys1_w32_mask(EBUCC_EBUDIV_SELF100, 0, LTQ_EBUCC);
+
+               /* search for suitable divider */
+               for (i = 1; i < 7; i++) {
+                       if (ebuclk / i <= spi->max_speed_hz)
+                               break;
+               }
+       }
+
+       /* setup period of serial clock */
+       ltq_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)),
+                    LTQ_SFTIME);
+
+       /*
+        * set some bits of unused_wd, to not trigger HOLD/WP
+        * signals on non QUAD flashes
+        */
+       ltq_ebu_w32((SFIO_UNUSED_WD_MASK & (0x8 | 0x4)), LTQ_SFIO);
+
+       ltq_ebu_w32(BUSRCON0_AGEN_SERIAL_FLASH | BUSRCON0_PORTW_8_BIT_MUX,
+               LTQ_BUSRCON0);
+       ltq_ebu_w32(BUSWCON0_AGEN_SERIAL_FLASH, LTQ_BUSWCON0);
+       /* set address wrap around to maximum for 24-bit addresses */
+       ltq_ebu_w32_mask(SFCON_DEV_SIZE_MASK, SFCON_DEV_SIZE_A23_0, LTQ_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;
+       int ret;
+
+       dev_dbg(dev, "probing\n");
+
+       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->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)
+               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);
+
+       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");