mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-12-21 11:27:43 +02:00
498 lines
12 KiB
Diff
498 lines
12 KiB
Diff
|
--- a/drivers/spi/Makefile
|
||
|
+++ b/drivers/spi/Makefile
|
||
|
@@ -55,6 +55,7 @@ obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci.
|
||
|
obj-$(CONFIG_SPI_SH_MSIOF) += spi_sh_msiof.o
|
||
|
obj-$(CONFIG_SPI_STMP3XXX) += spi_stmp.o
|
||
|
obj-$(CONFIG_SPI_NUC900) += spi_nuc900.o
|
||
|
+obj-$(CONFIG_SPI_FALCON) += spi_falcon.o
|
||
|
|
||
|
# special build for s3c24xx spi driver with fiq support
|
||
|
spi_s3c24xx_hw-y := spi_s3c24xx.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
|
||
|
@@ -210,6 +210,10 @@ config SPI_MPC52xx
|
||
|
This drivers supports the MPC52xx SPI controller in master SPI
|
||
|
mode.
|
||
|
|
||
|
+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
|