diff options
author | blogic <blogic@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-10-05 10:12:53 +0000 |
---|---|---|
committer | blogic <blogic@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-10-05 10:12:53 +0000 |
commit | 5c105d9f3fd086aff195d3849dcf847d6b0bd927 (patch) | |
tree | 1229a11f725bfa58aa7c57a76898553bb5f6654a /target/linux/lantiq/files/drivers/spi | |
download | openwrt-5c105d9f3fd086aff195d3849dcf847d6b0bd927.tar.gz openwrt-5c105d9f3fd086aff195d3849dcf847d6b0bd927.zip |
branch Attitude Adjustment
git-svn-id: svn://svn.openwrt.org/openwrt/branches/attitude_adjustment@33625 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/lantiq/files/drivers/spi')
-rw-r--r-- | target/linux/lantiq/files/drivers/spi/spi-falcon.c | 483 | ||||
-rw-r--r-- | target/linux/lantiq/files/drivers/spi/spi-xway.c | 1070 | ||||
-rw-r--r-- | target/linux/lantiq/files/drivers/spi/spi_svip.c | 955 |
3 files changed, 2508 insertions, 0 deletions
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 index 000000000..447bbaaa3 --- /dev/null +++ b/target/linux/lantiq/files/drivers/spi/spi-falcon.c @@ -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"); diff --git a/target/linux/lantiq/files/drivers/spi/spi-xway.c b/target/linux/lantiq/files/drivers/spi/spi-xway.c new file mode 100644 index 000000000..be5c25b1f --- /dev/null +++ b/target/linux/lantiq/files/drivers/spi/spi-xway.c @@ -0,0 +1,1070 @@ +/* + * Lantiq SoC SPI controller + * + * Copyright (C) 2011 Daniel Schwierzeck <daniel.schwierzeck@googlemail.com> + * + * This program is free software; you can distribute it and/or modify it + * under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/completion.h> +#include <linux/spinlock.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/spi/spi.h> +#include <linux/spi/spi_bitbang.h> + +#include <lantiq_soc.h> +#include <lantiq_platform.h> + +#define LTQ_SPI_CLC 0x00 /* Clock control */ +#define LTQ_SPI_PISEL 0x04 /* Port input select */ +#define LTQ_SPI_ID 0x08 /* Identification */ +#define LTQ_SPI_CON 0x10 /* Control */ +#define LTQ_SPI_STAT 0x14 /* Status */ +#define LTQ_SPI_WHBSTATE 0x18 /* Write HW modified state */ +#define LTQ_SPI_TB 0x20 /* Transmit buffer */ +#define LTQ_SPI_RB 0x24 /* Receive buffer */ +#define LTQ_SPI_RXFCON 0x30 /* Receive FIFO control */ +#define LTQ_SPI_TXFCON 0x34 /* Transmit FIFO control */ +#define LTQ_SPI_FSTAT 0x38 /* FIFO status */ +#define LTQ_SPI_BRT 0x40 /* Baudrate timer */ +#define LTQ_SPI_BRSTAT 0x44 /* Baudrate timer status */ +#define LTQ_SPI_SFCON 0x60 /* Serial frame control */ +#define LTQ_SPI_SFSTAT 0x64 /* Serial frame status */ +#define LTQ_SPI_GPOCON 0x70 /* General purpose output control */ +#define LTQ_SPI_GPOSTAT 0x74 /* General purpose output status */ +#define LTQ_SPI_FGPO 0x78 /* Forced general purpose output */ +#define LTQ_SPI_RXREQ 0x80 /* Receive request */ +#define LTQ_SPI_RXCNT 0x84 /* Receive count */ +#define LTQ_SPI_DMACON 0xEC /* DMA control */ +#define LTQ_SPI_IRNEN 0xF4 /* Interrupt node enable */ +#define LTQ_SPI_IRNICR 0xF8 /* Interrupt node interrupt capture */ +#define LTQ_SPI_IRNCR 0xFC /* Interrupt node control */ + +#define LTQ_SPI_CLC_SMC_SHIFT 16 /* Clock divider for sleep mode */ +#define LTQ_SPI_CLC_SMC_MASK 0xFF +#define LTQ_SPI_CLC_RMC_SHIFT 8 /* Clock divider for normal run mode */ +#define LTQ_SPI_CLC_RMC_MASK 0xFF +#define LTQ_SPI_CLC_DISS BIT(1) /* Disable status bit */ +#define LTQ_SPI_CLC_DISR BIT(0) /* Disable request bit */ + +#define LTQ_SPI_ID_TXFS_SHIFT 24 /* Implemented TX FIFO size */ +#define LTQ_SPI_ID_TXFS_MASK 0x3F +#define LTQ_SPI_ID_RXFS_SHIFT 16 /* Implemented RX FIFO size */ +#define LTQ_SPI_ID_RXFS_MASK 0x3F +#define LTQ_SPI_ID_REV_MASK 0x1F /* Hardware revision number */ +#define LTQ_SPI_ID_CFG BIT(5) /* DMA interface support */ + +#define LTQ_SPI_CON_BM_SHIFT 16 /* Data width selection */ +#define LTQ_SPI_CON_BM_MASK 0x1F +#define LTQ_SPI_CON_EM BIT(24) /* Echo mode */ +#define LTQ_SPI_CON_IDLE BIT(23) /* Idle bit value */ +#define LTQ_SPI_CON_ENBV BIT(22) /* Enable byte valid control */ +#define LTQ_SPI_CON_RUEN BIT(12) /* Receive underflow error enable */ +#define LTQ_SPI_CON_TUEN BIT(11) /* Transmit underflow error enable */ +#define LTQ_SPI_CON_AEN BIT(10) /* Abort error enable */ +#define LTQ_SPI_CON_REN BIT(9) /* Receive overflow error enable */ +#define LTQ_SPI_CON_TEN BIT(8) /* Transmit overflow error enable */ +#define LTQ_SPI_CON_LB BIT(7) /* Loopback control */ +#define LTQ_SPI_CON_PO BIT(6) /* Clock polarity control */ +#define LTQ_SPI_CON_PH BIT(5) /* Clock phase control */ +#define LTQ_SPI_CON_HB BIT(4) /* Heading control */ +#define LTQ_SPI_CON_RXOFF BIT(1) /* Switch receiver off */ +#define LTQ_SPI_CON_TXOFF BIT(0) /* Switch transmitter off */ + +#define LTQ_SPI_STAT_RXBV_MASK 0x7 +#define LTQ_SPI_STAT_RXBV_SHIFT 28 +#define LTQ_SPI_STAT_BSY BIT(13) /* Busy flag */ +#define LTQ_SPI_STAT_RUE BIT(12) /* Receive underflow error flag */ +#define LTQ_SPI_STAT_TUE BIT(11) /* Transmit underflow error flag */ +#define LTQ_SPI_STAT_AE BIT(10) /* Abort error flag */ +#define LTQ_SPI_STAT_RE BIT(9) /* Receive error flag */ +#define LTQ_SPI_STAT_TE BIT(8) /* Transmit error flag */ +#define LTQ_SPI_STAT_MS BIT(1) /* Master/slave select bit */ +#define LTQ_SPI_STAT_EN BIT(0) /* Enable bit */ + +#define LTQ_SPI_WHBSTATE_SETTUE BIT(15) /* Set transmit underflow error flag */ +#define LTQ_SPI_WHBSTATE_SETAE BIT(14) /* Set abort error flag */ +#define LTQ_SPI_WHBSTATE_SETRE BIT(13) /* Set receive error flag */ +#define LTQ_SPI_WHBSTATE_SETTE BIT(12) /* Set transmit error flag */ +#define LTQ_SPI_WHBSTATE_CLRTUE BIT(11) /* Clear transmit underflow error flag */ +#define LTQ_SPI_WHBSTATE_CLRAE BIT(10) /* Clear abort error flag */ +#define LTQ_SPI_WHBSTATE_CLRRE BIT(9) /* Clear receive error flag */ +#define LTQ_SPI_WHBSTATE_CLRTE BIT(8) /* Clear transmit error flag */ +#define LTQ_SPI_WHBSTATE_SETME BIT(7) /* Set mode error flag */ +#define LTQ_SPI_WHBSTATE_CLRME BIT(6) /* Clear mode error flag */ +#define LTQ_SPI_WHBSTATE_SETRUE BIT(5) /* Set receive underflow error flag */ +#define LTQ_SPI_WHBSTATE_CLRRUE BIT(4) /* Clear receive underflow error flag */ +#define LTQ_SPI_WHBSTATE_SETMS BIT(3) /* Set master select bit */ +#define LTQ_SPI_WHBSTATE_CLRMS BIT(2) /* Clear master select bit */ +#define LTQ_SPI_WHBSTATE_SETEN BIT(1) /* Set enable bit (operational mode) */ +#define LTQ_SPI_WHBSTATE_CLREN BIT(0) /* Clear enable bit (config mode */ +#define LTQ_SPI_WHBSTATE_CLR_ERRORS 0x0F50 + +#define LTQ_SPI_RXFCON_RXFITL_SHIFT 8 /* FIFO interrupt trigger level */ +#define LTQ_SPI_RXFCON_RXFITL_MASK 0x3F +#define LTQ_SPI_RXFCON_RXFLU BIT(1) /* FIFO flush */ +#define LTQ_SPI_RXFCON_RXFEN BIT(0) /* FIFO enable */ + +#define LTQ_SPI_TXFCON_TXFITL_SHIFT 8 /* FIFO interrupt trigger level */ +#define LTQ_SPI_TXFCON_TXFITL_MASK 0x3F +#define LTQ_SPI_TXFCON_TXFLU BIT(1) /* FIFO flush */ +#define LTQ_SPI_TXFCON_TXFEN BIT(0) /* FIFO enable */ + +#define LTQ_SPI_FSTAT_RXFFL_MASK 0x3f +#define LTQ_SPI_FSTAT_RXFFL_SHIFT 0 +#define LTQ_SPI_FSTAT_TXFFL_MASK 0x3f +#define LTQ_SPI_FSTAT_TXFFL_SHIFT 8 + +#define LTQ_SPI_GPOCON_ISCSBN_SHIFT 8 +#define LTQ_SPI_GPOCON_INVOUTN_SHIFT 0 + +#define LTQ_SPI_FGPO_SETOUTN_SHIFT 8 +#define LTQ_SPI_FGPO_CLROUTN_SHIFT 0 + +#define LTQ_SPI_RXREQ_RXCNT_MASK 0xFFFF /* Receive count value */ +#define LTQ_SPI_RXCNT_TODO_MASK 0xFFFF /* Recevie to-do value */ + +#define LTQ_SPI_IRNEN_F BIT(3) /* Frame end interrupt request */ +#define LTQ_SPI_IRNEN_E BIT(2) /* Error end interrupt request */ +#define LTQ_SPI_IRNEN_T BIT(1) /* Transmit end interrupt request */ +#define LTQ_SPI_IRNEN_R BIT(0) /* Receive end interrupt request */ +#define LTQ_SPI_IRNEN_ALL 0xF + +/* Hard-wired GPIOs used by SPI controller */ +#define LTQ_SPI_GPIO_DI (ltq_is_ase()? 8 : 16) +#define LTQ_SPI_GPIO_DO (ltq_is_ase()? 9 : 17) +#define LTQ_SPI_GPIO_CLK (ltq_is_ase()? 10 : 18) + +struct ltq_spi { + struct spi_bitbang bitbang; + struct completion done; + spinlock_t lock; + + struct device *dev; + void __iomem *base; + struct clk *fpiclk; + struct clk *spiclk; + + int status; + int irq[3]; + + const u8 *tx; + u8 *rx; + u32 tx_cnt; + u32 rx_cnt; + u32 len; + struct spi_transfer *curr_transfer; + + u32 (*get_tx) (struct ltq_spi *); + + u16 txfs; + u16 rxfs; + unsigned dma_support:1; + unsigned cfg_mode:1; + +}; + +struct ltq_spi_controller_state { + void (*cs_activate) (struct spi_device *); + void (*cs_deactivate) (struct spi_device *); +}; + +struct ltq_spi_irq_map { + char *name; + irq_handler_t handler; +}; + +struct ltq_spi_cs_gpio_map { + unsigned gpio; + unsigned mux; +}; + +static inline struct ltq_spi *ltq_spi_to_hw(struct spi_device *spi) +{ + return spi_master_get_devdata(spi->master); +} + +static inline u32 ltq_spi_reg_read(struct ltq_spi *hw, u32 reg) +{ + return ioread32be(hw->base + reg); +} + +static inline void ltq_spi_reg_write(struct ltq_spi *hw, u32 val, u32 reg) +{ + iowrite32be(val, hw->base + reg); +} + +static inline void ltq_spi_reg_setbit(struct ltq_spi *hw, u32 bits, u32 reg) +{ + u32 val; + + val = ltq_spi_reg_read(hw, reg); + val |= bits; + ltq_spi_reg_write(hw, val, reg); +} + +static inline void ltq_spi_reg_clearbit(struct ltq_spi *hw, u32 bits, u32 reg) +{ + u32 val; + + val = ltq_spi_reg_read(hw, reg); + val &= ~bits; + ltq_spi_reg_write(hw, val, reg); +} + +static void ltq_spi_hw_enable(struct ltq_spi *hw) +{ + u32 clc; + + /* Power-up mdule */ + clk_enable(hw->spiclk); + + /* + * Set clock divider for run mode to 1 to + * run at same frequency as FPI bus + */ + clc = (1 << LTQ_SPI_CLC_RMC_SHIFT); + ltq_spi_reg_write(hw, clc, LTQ_SPI_CLC); +} + +static void ltq_spi_hw_disable(struct ltq_spi *hw) +{ + /* Set clock divider to 0 and set module disable bit */ + ltq_spi_reg_write(hw, LTQ_SPI_CLC_DISS, LTQ_SPI_CLC); + + /* Power-down mdule */ + clk_disable(hw->spiclk); +} + +static void ltq_spi_reset_fifos(struct ltq_spi *hw) +{ + u32 val; + + /* + * Enable and flush FIFOs. Set interrupt trigger level to + * half of FIFO count implemented in hardware. + */ + if (hw->txfs > 1) { + val = hw->txfs << (LTQ_SPI_TXFCON_TXFITL_SHIFT - 1); + val |= LTQ_SPI_TXFCON_TXFEN | LTQ_SPI_TXFCON_TXFLU; + ltq_spi_reg_write(hw, val, LTQ_SPI_TXFCON); + } + + if (hw->rxfs > 1) { + val = hw->rxfs << (LTQ_SPI_RXFCON_RXFITL_SHIFT - 1); + val |= LTQ_SPI_RXFCON_RXFEN | LTQ_SPI_RXFCON_RXFLU; + ltq_spi_reg_write(hw, val, LTQ_SPI_RXFCON); + } +} + +static inline int ltq_spi_wait_ready(struct ltq_spi *hw) +{ + u32 stat; + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(200); + + do { + stat = ltq_spi_reg_read(hw, LTQ_SPI_STAT); + if (!(stat & LTQ_SPI_STAT_BSY)) + return 0; + + cond_resched(); + } while (!time_after_eq(jiffies, timeout)); + + dev_err(hw->dev, "SPI wait ready timed out stat: %x\n", stat); + + return -ETIMEDOUT; +} + +static void ltq_spi_config_mode_set(struct ltq_spi *hw) +{ + if (hw->cfg_mode) + return; + + /* + * Putting the SPI module in config mode is only safe if no + * transfer is in progress as indicated by busy flag STATE.BSY. + */ + if (ltq_spi_wait_ready(hw)) { + ltq_spi_reset_fifos(hw); + hw->status = -ETIMEDOUT; + } + ltq_spi_reg_write(hw, LTQ_SPI_WHBSTATE_CLREN, LTQ_SPI_WHBSTATE); + + hw->cfg_mode = 1; +} + +static void ltq_spi_run_mode_set(struct ltq_spi *hw) +{ + if (!hw->cfg_mode) + return; + + ltq_spi_reg_write(hw, LTQ_SPI_WHBSTATE_SETEN, LTQ_SPI_WHBSTATE); + + hw->cfg_mode = 0; +} + +static u32 ltq_spi_tx_word_u8(struct ltq_spi *hw) +{ + const u8 *tx = hw->tx; + u32 data = *tx++; + + hw->tx_cnt++; + hw->tx++; + + return data; +} + +static u32 ltq_spi_tx_word_u16(struct ltq_spi *hw) +{ + const u16 *tx = (u16 *) hw->tx; + u32 data = *tx++; + + hw->tx_cnt += 2; + hw->tx += 2; + + return data; +} + +static u32 ltq_spi_tx_word_u32(struct ltq_spi *hw) +{ + const u32 *tx = (u32 *) hw->tx; + u32 data = *tx++; + + hw->tx_cnt += 4; + hw->tx += 4; + + return data; +} + +static void ltq_spi_bits_per_word_set(struct spi_device *spi) +{ + struct ltq_spi *hw = ltq_spi_to_hw(spi); + u32 bm; + u8 bits_per_word = spi->bits_per_word; + + /* + * Use either default value of SPI device or value + * from current transfer. + */ + if (hw->curr_transfer && hw->curr_transfer->bits_per_word) + bits_per_word = hw->curr_transfer->bits_per_word; + + if (bits_per_word <= 8) + hw->get_tx = ltq_spi_tx_word_u8; + else if (bits_per_word <= 16) + hw->get_tx = ltq_spi_tx_word_u16; + else if (bits_per_word <= 32) + hw->get_tx = ltq_spi_tx_word_u32; + + /* CON.BM value = bits_per_word - 1 */ + bm = (bits_per_word - 1) << LTQ_SPI_CON_BM_SHIFT; + + ltq_spi_reg_clearbit(hw, LTQ_SPI_CON_BM_MASK << + LTQ_SPI_CON_BM_SHIFT, LTQ_SPI_CON); + ltq_spi_reg_setbit(hw, bm, LTQ_SPI_CON); +} + +static void ltq_spi_speed_set(struct spi_device *spi) +{ + struct ltq_spi *hw = ltq_spi_to_hw(spi); + u32 br, max_speed_hz, spi_clk; + u32 speed_hz = spi->max_speed_hz; + + /* + * Use either default value of SPI device or value + * from current transfer. + */ + if (hw->curr_transfer && hw->curr_transfer->speed_hz) + speed_hz = hw->curr_transfer->speed_hz; + + /* + * SPI module clock is derived from FPI bus clock dependent on + * divider value in CLC.RMS which is always set to 1. + */ + spi_clk = clk_get_rate(hw->fpiclk); + + /* + * Maximum SPI clock frequency in master mode is half of + * SPI module clock frequency. Maximum reload value of + * baudrate generator BR is 2^16. + */ + max_speed_hz = spi_clk / 2; + if (speed_hz >= max_speed_hz) + br = 0; + else + br = (max_speed_hz / speed_hz) - 1; + + if (br > 0xFFFF) + br = 0xFFFF; + + ltq_spi_reg_write(hw, br, LTQ_SPI_BRT); +} + +static void ltq_spi_clockmode_set(struct spi_device *spi) +{ + struct ltq_spi *hw = ltq_spi_to_hw(spi); + u32 con; + + con = ltq_spi_reg_read(hw, LTQ_SPI_CON); + + /* + * SPI mode mapping in CON register: + * Mode CPOL CPHA CON.PO CON.PH + * 0 0 0 0 1 + * 1 0 1 0 0 + * 2 1 0 1 1 + * 3 1 1 1 0 + */ + if (spi->mode & SPI_CPHA) + con &= ~LTQ_SPI_CON_PH; + else + con |= LTQ_SPI_CON_PH; + + if (spi->mode & SPI_CPOL) + con |= LTQ_SPI_CON_PO; + else + con &= ~LTQ_SPI_CON_PO; + + /* Set heading control */ + if (spi->mode & SPI_LSB_FIRST) + con &= ~LTQ_SPI_CON_HB; + else + con |= LTQ_SPI_CON_HB; + + ltq_spi_reg_write(hw, con, LTQ_SPI_CON); +} + +static void ltq_spi_xmit_set(struct ltq_spi *hw, struct spi_transfer *t) +{ + u32 con; + + con = ltq_spi_reg_read(hw, LTQ_SPI_CON); + + if (t) { + if (t->tx_buf && t->rx_buf) { + con &= ~(LTQ_SPI_CON_TXOFF | LTQ_SPI_CON_RXOFF); + } else if (t->rx_buf) { + con &= ~LTQ_SPI_CON_RXOFF; + con |= LTQ_SPI_CON_TXOFF; + } else if (t->tx_buf) { + con &= ~LTQ_SPI_CON_TXOFF; + con |= LTQ_SPI_CON_RXOFF; + } + } else + con |= (LTQ_SPI_CON_TXOFF | LTQ_SPI_CON_RXOFF); + + ltq_spi_reg_write(hw, con, LTQ_SPI_CON); +} + +static void ltq_spi_gpio_cs_activate(struct spi_device *spi) +{ + struct ltq_spi_controller_data *cdata = spi->controller_data; + int val = spi->mode & SPI_CS_HIGH ? 1 : 0; + + gpio_set_value(cdata->gpio, val); +} + +static void ltq_spi_gpio_cs_deactivate(struct spi_device *spi) +{ + struct ltq_spi_controller_data *cdata = spi->controller_data; + int val = spi->mode & SPI_CS_HIGH ? 0 : 1; + + gpio_set_value(cdata->gpio, val); +} + +static void ltq_spi_internal_cs_activate(struct spi_device *spi) +{ + struct ltq_spi *hw = ltq_spi_to_hw(spi); + u32 fgpo; + + fgpo = (1 << (spi->chip_select + LTQ_SPI_FGPO_CLROUTN_SHIFT)); + ltq_spi_reg_setbit(hw, fgpo, LTQ_SPI_FGPO); +} + +static void ltq_spi_internal_cs_deactivate(struct spi_device *spi) +{ + struct ltq_spi *hw = ltq_spi_to_hw(spi); + u32 fgpo; + + fgpo = (1 << (spi->chip_select + LTQ_SPI_FGPO_SETOUTN_SHIFT)); + ltq_spi_reg_setbit(hw, fgpo, LTQ_SPI_FGPO); +} + +static void ltq_spi_chipselect(struct spi_device *spi, int cs) +{ + struct ltq_spi *hw = ltq_spi_to_hw(spi); + struct ltq_spi_controller_state *cstate = spi->controller_state; + + switch (cs) { + case BITBANG_CS_ACTIVE: + ltq_spi_bits_per_word_set(spi); + ltq_spi_speed_set(spi); + ltq_spi_clockmode_set(spi); + ltq_spi_run_mode_set(hw); + + cstate->cs_activate(spi); + break; + + case BITBANG_CS_INACTIVE: + cstate->cs_deactivate(spi); + + ltq_spi_config_mode_set(hw); + + break; + } +} + +static int ltq_spi_setup_transfer(struct spi_device *spi, + struct spi_transfer *t) +{ + struct ltq_spi *hw = ltq_spi_to_hw(spi); + u8 bits_per_word = spi->bits_per_word; + + hw->curr_transfer = t; + + if (t && t->bits_per_word) + bits_per_word = t->bits_per_word; + + if (bits_per_word > 32) + return -EINVAL; + + ltq_spi_config_mode_set(hw); + + return 0; +} + +static const struct ltq_spi_cs_gpio_map ltq_spi_cs[] = { + { 15, 2 }, + { 22, 2 }, + { 13, 1 }, + { 10, 1 }, + { 9, 1 }, + { 11, 3 }, +}; + +static const struct ltq_spi_cs_gpio_map ltq_spi_cs_ase[] = { + { 7, 2 }, + { 15, 1 }, + { 14, 1 }, +}; + +static int ltq_spi_setup(struct spi_device *spi) +{ + struct ltq_spi *hw = ltq_spi_to_hw(spi); + struct ltq_spi_controller_data *cdata = spi->controller_data; + struct ltq_spi_controller_state *cstate; + u32 gpocon, fgpo; + int ret; + + /* Set default word length to 8 if not set */ + if (!spi->bits_per_word) + spi->bits_per_word = 8; + + if (spi->bits_per_word > 32) + return -EINVAL; + + if (!spi->controller_state) { + cstate = kzalloc(sizeof(struct ltq_spi_controller_state), + GFP_KERNEL); + if (!cstate) + return -ENOMEM; + + spi->controller_state = cstate; + } else + return 0; + + /* + * Up to six GPIOs can be connected to the SPI module + * via GPIO alternate function to control the chip select lines. + * For more flexibility in board layout this driver can also control + * the CS lines via GPIO API. If GPIOs should be used, board setup code + * have to register the SPI device with struct ltq_spi_controller_data + * attached. + */ + if (cdata && cdata->gpio) { + ret = gpio_request(cdata->gpio, "spi-cs"); + if (ret) + return -EBUSY; + + ret = spi->mode & SPI_CS_HIGH ? 0 : 1; + gpio_direction_output(cdata->gpio, ret); + + cstate->cs_activate = ltq_spi_gpio_cs_activate; + cstate->cs_deactivate = ltq_spi_gpio_cs_deactivate; + } else { + struct ltq_spi_cs_gpio_map *cs_map = + ltq_is_ase() ? ltq_spi_cs_ase : ltq_spi_cs; + ret = ltq_gpio_request(&spi->dev, cs_map[spi->chip_select].gpio, + cs_map[spi->chip_select].mux, + 1, "spi-cs"); + if (ret) + return -EBUSY; + + gpocon = (1 << (spi->chip_select + + LTQ_SPI_GPOCON_ISCSBN_SHIFT)); + + if (spi->mode & SPI_CS_HIGH) + gpocon |= (1 << spi->chip_select); + + fgpo = (1 << (spi->chip_select + LTQ_SPI_FGPO_SETOUTN_SHIFT)); + + ltq_spi_reg_setbit(hw, gpocon, LTQ_SPI_GPOCON); + ltq_spi_reg_setbit(hw, fgpo, LTQ_SPI_FGPO); + + cstate->cs_activate = ltq_spi_internal_cs_activate; + cstate->cs_deactivate = ltq_spi_internal_cs_deactivate; + } + + return 0; +} + +static void ltq_spi_cleanup(struct spi_device *spi) +{ + struct ltq_spi_controller_data *cdata = spi->controller_data; + struct ltq_spi_controller_state *cstate = spi->controller_state; + unsigned gpio; + + if (cdata && cdata->gpio) + gpio = cdata->gpio; + else + gpio = ltq_is_ase() ? ltq_spi_cs_ase[spi->chip_select].gpio : + ltq_spi_cs[spi->chip_select].gpio; + + gpio_free(gpio); + kfree(cstate); +} + +static void ltq_spi_txfifo_write(struct ltq_spi *hw) +{ + u32 fstat, data; + u16 fifo_space; + + /* Determine how much FIFOs are free for TX data */ + fstat = ltq_spi_reg_read(hw, LTQ_SPI_FSTAT); + fifo_space = hw->txfs - ((fstat >> LTQ_SPI_FSTAT_TXFFL_SHIFT) & + LTQ_SPI_FSTAT_TXFFL_MASK); + + if (!fifo_space) + return; + + while (hw->tx_cnt < hw->len && fifo_space) { + data = hw->get_tx(hw); + ltq_spi_reg_write(hw, data, LTQ_SPI_TB); + fifo_space--; + } +} + +static void ltq_spi_rxfifo_read(struct ltq_spi *hw) +{ + u32 fstat, data, *rx32; + u16 fifo_fill; + u8 rxbv, shift, *rx8; + + /* Determine how much FIFOs are filled with RX data */ + fstat = ltq_spi_reg_read(hw, LTQ_SPI_FSTAT); + fifo_fill = ((fstat >> LTQ_SPI_FSTAT_RXFFL_SHIFT) + & LTQ_SPI_FSTAT_RXFFL_MASK); + + if (!fifo_fill) + return; + + /* + * The 32 bit FIFO is always used completely independent from the + * bits_per_word value. Thus four bytes have to be read at once + * per FIFO. + */ + rx32 = (u32 *) hw->rx; + while (hw->len - hw->rx_cnt >= 4 && fifo_fill) { + *rx32++ = ltq_spi_reg_read(hw, LTQ_SPI_RB); + hw->rx_cnt += 4; + hw->rx += 4; + fifo_fill--; + } + + /* + * If there are remaining bytes, read byte count from STAT.RXBV + * register and read the data byte-wise. + */ + while (fifo_fill && hw->rx_cnt < hw->len) { + rxbv = (ltq_spi_reg_read(hw, LTQ_SPI_STAT) >> + LTQ_SPI_STAT_RXBV_SHIFT) & LTQ_SPI_STAT_RXBV_MASK; + data = ltq_spi_reg_read(hw, LTQ_SPI_RB); + + shift = (rxbv - 1) * 8; + rx8 = hw->rx; + + while (rxbv) { + *rx8++ = (data >> shift) & 0xFF; + rxbv--; + shift -= 8; + hw->rx_cnt++; + hw->rx++; + } + + fifo_fill--; + } +} + +static void ltq_spi_rxreq_set(struct ltq_spi *hw) +{ + u32 rxreq, rxreq_max, rxtodo; + + rxtodo = ltq_spi_reg_read(hw, LTQ_SPI_RXCNT) & LTQ_SPI_RXCNT_TODO_MASK; + + /* + * In RX-only mode the serial clock is activated only after writing + * the expected amount of RX bytes into RXREQ register. + * To avoid receive overflows at high clocks it is better to request + * only the amount of bytes that fits into all FIFOs. This value + * depends on the FIFO size implemented in hardware. + */ + rxreq = hw->len - hw->rx_cnt; + rxreq_max = hw->rxfs << 2; + rxreq = min(rxreq_max, rxreq); + + if (!rxtodo && rxreq) + ltq_spi_reg_write(hw, rxreq, LTQ_SPI_RXREQ); +} + +static inline void ltq_spi_complete(struct ltq_spi *hw) +{ + complete(&hw->done); +} + +irqreturn_t ltq_spi_tx_irq(int irq, void *data) +{ + struct ltq_spi *hw = data; + unsigned long flags; + int completed = 0; + + spin_lock_irqsave(&hw->lock, flags); + + if (hw->tx_cnt < hw->len) + ltq_spi_txfifo_write(hw); + + if (hw->tx_cnt == hw->len) + completed = 1; + + spin_unlock_irqrestore(&hw->lock, flags); + + if (completed) + ltq_spi_complete(hw); + + return IRQ_HANDLED; +} + +irqreturn_t ltq_spi_rx_irq(int irq, void *data) +{ + struct ltq_spi *hw = data; + unsigned long flags; + int completed = 0; + + spin_lock_irqsave(&hw->lock, flags); + + if (hw->rx_cnt < hw->len) { + ltq_spi_rxfifo_read(hw); + + if (hw->tx && hw->tx_cnt < hw->len) + ltq_spi_txfifo_write(hw); + } + + if (hw->rx_cnt == hw->len) + completed = 1; + else if (!hw->tx) + ltq_spi_rxreq_set(hw); + + spin_unlock_irqrestore(&hw->lock, flags); + + if (completed) + ltq_spi_complete(hw); + + return IRQ_HANDLED; +} + +irqreturn_t ltq_spi_err_irq(int irq, void *data) +{ + struct ltq_spi *hw = data; + unsigned long flags; + + spin_lock_irqsave(&hw->lock, flags); + + /* Disable all interrupts */ + ltq_spi_reg_clearbit(hw, LTQ_SPI_IRNEN_ALL, LTQ_SPI_IRNEN); + + /* Clear all error flags */ + ltq_spi_reg_write(hw, LTQ_SPI_WHBSTATE_CLR_ERRORS, LTQ_SPI_WHBSTATE); + + /* Flush FIFOs */ + ltq_spi_reg_setbit(hw, LTQ_SPI_RXFCON_RXFLU, LTQ_SPI_RXFCON); + ltq_spi_reg_setbit(hw, LTQ_SPI_TXFCON_TXFLU, LTQ_SPI_TXFCON); + + hw->status = -EIO; + spin_unlock_irqrestore(&hw->lock, flags); + + ltq_spi_complete(hw); + + return IRQ_HANDLED; +} + +static int ltq_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) +{ + struct ltq_spi *hw = ltq_spi_to_hw(spi); + u32 irq_flags = 0; + + hw->tx = t->tx_buf; + hw->rx = t->rx_buf; + hw->len = t->len; + hw->tx_cnt = 0; + hw->rx_cnt = 0; + hw->status = 0; + INIT_COMPLETION(hw->done); + + ltq_spi_xmit_set(hw, t); + + /* Enable error interrupts */ + ltq_spi_reg_setbit(hw, LTQ_SPI_IRNEN_E, LTQ_SPI_IRNEN); + + if (hw->tx) { + /* Initially fill TX FIFO with as much data as possible */ + ltq_spi_txfifo_write(hw); + irq_flags |= LTQ_SPI_IRNEN_T; + + /* Always enable RX interrupt in Full Duplex mode */ + if (hw->rx) + irq_flags |= LTQ_SPI_IRNEN_R; + } else if (hw->rx) { + /* Start RX clock */ + ltq_spi_rxreq_set(hw); + + /* Enable RX interrupt to receive data from RX FIFOs */ + irq_flags |= LTQ_SPI_IRNEN_R; + } + + /* Enable TX or RX interrupts */ + ltq_spi_reg_setbit(hw, irq_flags, LTQ_SPI_IRNEN); + wait_for_completion_interruptible(&hw->done); + + /* Disable all interrupts */ + ltq_spi_reg_clearbit(hw, LTQ_SPI_IRNEN_ALL, LTQ_SPI_IRNEN); + + /* + * Return length of current transfer for bitbang utility code if + * no errors occured during transmission. + */ + if (!hw->status) + hw->status = hw->len; + + return hw->status; +} + +static const struct ltq_spi_irq_map ltq_spi_irqs[] = { + { "spi_tx", ltq_spi_tx_irq }, + { "spi_rx", ltq_spi_rx_irq }, + { "spi_err", ltq_spi_err_irq }, +}; + +static int __devinit +ltq_spi_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct resource *r; + struct ltq_spi *hw; + struct ltq_spi_platform_data *pdata = pdev->dev.platform_data; + int ret, i; + u32 data, id; + + master = spi_alloc_master(&pdev->dev, sizeof(struct ltq_spi)); + if (!master) { + dev_err(&pdev->dev, "spi_alloc_master\n"); + ret = -ENOMEM; + goto err; + } + + hw = spi_master_get_devdata(master); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "platform_get_resource\n"); + ret = -ENOENT; + goto err_master; + } + + r = devm_request_mem_region(&pdev->dev, r->start, resource_size(r), + pdev->name); + if (!r) { + dev_err(&pdev->dev, "devm_request_mem_region\n"); + ret = -ENXIO; + goto err_master; + } + + hw->base = devm_ioremap_nocache(&pdev->dev, r->start, resource_size(r)); + if (!hw->base) { + dev_err(&pdev->dev, "devm_ioremap_nocache\n"); + ret = -ENXIO; + goto err_master; + } + + hw->fpiclk = clk_get_fpi(); + if (IS_ERR(hw->fpiclk)) { + dev_err(&pdev->dev, "fpi clk\n"); + ret = PTR_ERR(hw->fpiclk); + goto err_master; + } + + hw->spiclk = clk_get(&pdev->dev, NULL); + if (IS_ERR(hw->spiclk)) { + dev_err(&pdev->dev, "spi clk\n"); + ret = PTR_ERR(hw->spiclk); + goto err_master; + } + + memset(hw->irq, 0, sizeof(hw->irq)); + for (i = 0; i < ARRAY_SIZE(ltq_spi_irqs); i++) { + ret = platform_get_irq_byname(pdev, ltq_spi_irqs[i].name); + if (0 > ret) { + dev_err(&pdev->dev, "platform_get_irq_byname\n"); + goto err_irq; + } + + hw->irq[i] = ret; + ret = request_irq(hw->irq[i], ltq_spi_irqs[i].handler, + 0, ltq_spi_irqs[i].name, hw); + if (ret) { + dev_err(&pdev->dev, "request_irq\n"); + goto err_irq; + } + } + + hw->bitbang.master = spi_master_get(master); + hw->bitbang.chipselect = ltq_spi_chipselect; + hw->bitbang.setup_transfer = ltq_spi_setup_transfer; + hw->bitbang.txrx_bufs = ltq_spi_txrx_bufs; + + master->bus_num = pdev->id; + master->num_chipselect = pdata->num_chipselect; + master->setup = ltq_spi_setup; + master->cleanup = ltq_spi_cleanup; + + hw->dev = &pdev->dev; + init_completion(&hw->done); + spin_lock_init(&hw->lock); + + /* Set GPIO alternate functions to SPI */ + ltq_gpio_request(&pdev->dev, LTQ_SPI_GPIO_DI, 2, 0, "spi-di"); + ltq_gpio_request(&pdev->dev, LTQ_SPI_GPIO_DO, 2, 1, "spi-do"); + ltq_gpio_request(&pdev->dev, LTQ_SPI_GPIO_CLK, 2, 1, "spi-clk"); + + ltq_spi_hw_enable(hw); + + /* Read module capabilities */ + id = ltq_spi_reg_read(hw, LTQ_SPI_ID); + hw->txfs = (id >> LTQ_SPI_ID_TXFS_SHIFT) & LTQ_SPI_ID_TXFS_MASK; + hw->rxfs = (id >> LTQ_SPI_ID_TXFS_SHIFT) & LTQ_SPI_ID_TXFS_MASK; + hw->dma_support = (id & LTQ_SPI_ID_CFG) ? 1 : 0; + + ltq_spi_config_mode_set(hw); + + /* Enable error checking, disable TX/RX, set idle value high */ + data = LTQ_SPI_CON_RUEN | LTQ_SPI_CON_AEN | + LTQ_SPI_CON_TEN | LTQ_SPI_CON_REN | + LTQ_SPI_CON_TXOFF | LTQ_SPI_CON_RXOFF | LTQ_SPI_CON_IDLE; + ltq_spi_reg_write(hw, data, LTQ_SPI_CON); + + /* Enable master mode and clear error flags */ + ltq_spi_reg_write(hw, LTQ_SPI_WHBSTATE_SETMS | + LTQ_SPI_WHBSTATE_CLR_ERRORS, LTQ_SPI_WHBSTATE); + + /* Reset GPIO/CS registers */ + ltq_spi_reg_write(hw, 0x0, LTQ_SPI_GPOCON); + ltq_spi_reg_write(hw, 0xFF00, LTQ_SPI_FGPO); + + /* Enable and flush FIFOs */ + ltq_spi_reset_fifos(hw); + + ret = spi_bitbang_start(&hw->bitbang); + if (ret) { + dev_err(&pdev->dev, "spi_bitbang_start\n"); + goto err_bitbang; + } + + platform_set_drvdata(pdev, hw); + + pr_info("Lantiq SoC SPI controller rev %u (TXFS %u, RXFS %u, DMA %u)\n", + id & LTQ_SPI_ID_REV_MASK, hw->txfs, hw->rxfs, hw->dma_support); + + return 0; + +err_bitbang: + ltq_spi_hw_disable(hw); + +err_irq: + clk_put(hw->fpiclk); + + for (; i > 0; i--) + free_irq(hw->irq[i], hw); + +err_master: + spi_master_put(master); + +err: + return ret; +} + +static int __devexit +ltq_spi_remove(struct platform_device *pdev) +{ + struct ltq_spi *hw = platform_get_drvdata(pdev); + int ret, i; + + ret = spi_bitbang_stop(&hw->bitbang); + if (ret) + return ret; + + platform_set_drvdata(pdev, NULL); + + ltq_spi_config_mode_set(hw); + ltq_spi_hw_disable(hw); + + for (i = 0; i < ARRAY_SIZE(hw->irq); i++) + if (0 < hw->irq[i]) + free_irq(hw->irq[i], hw); + + gpio_free(LTQ_SPI_GPIO_DI); + gpio_free(LTQ_SPI_GPIO_DO); + gpio_free(LTQ_SPI_GPIO_CLK); + + clk_put(hw->fpiclk); + spi_master_put(hw->bitbang.master); + + return 0; +} + +static struct platform_driver ltq_spi_driver = { + .probe = ltq_spi_probe, + .remove = __devexit_p(ltq_spi_remove), + .driver = { + .name = "ltq_spi", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(ltq_spi_driver); + +MODULE_DESCRIPTION("Lantiq SoC SPI controller driver"); +MODULE_AUTHOR("Daniel Schwierzeck <daniel.schwierzeck@googlemail.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ltq-spi"); diff --git a/target/linux/lantiq/files/drivers/spi/spi_svip.c b/target/linux/lantiq/files/drivers/spi/spi_svip.c new file mode 100644 index 000000000..ae25c20b3 --- /dev/null +++ b/target/linux/lantiq/files/drivers/spi/spi_svip.c @@ -0,0 +1,955 @@ +/************************************************************************ + * + * Copyright (c) 2008 + * Infineon Technologies AG + * St. Martin Strasse 53; 81669 Muenchen; Germany + * + * Inspired by Atmel AT32/AT91 SPI Controller driver + * Copyright (c) 2006 Atmel Corporation + * + * 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. + * + ************************************************************************/ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> + +#include <asm/io.h> + +#include <status_reg.h> +#include <base_reg.h> +#include <ssc_reg.h> +#include <sys0_reg.h> +#include <sys1_reg.h> + +#define SFRAME_SIZE 512 /* bytes */ +#define FIFO_HEADROOM 2 /* words */ + +#define SVIP_SSC_RFIFO_WORDS 8 + +enum svip_ssc_dir { + SSC_RXTX, + SSC_RX, + SSC_TX, + SSC_UNDEF +}; + +/* + * The core SPI transfer engine just talks to a register bank to set up + * DMA transfers; transfer queue progress is driven by IRQs. The clock + * framework provides the base clock, subdivided for each spi_device. + */ +struct svip_ssc_device { + struct svip_reg_ssc *regs; + enum svip_ssc_dir bus_dir; + struct spi_device *stay; + + u8 stopping; + struct list_head queue; + struct spi_transfer *current_transfer; + int remaining_bytes; + int rx_bytes; + int tx_bytes; + + char intname[4][16]; + + spinlock_t lock; +}; + +static int svip_ssc_setup(struct spi_device *spi); + +extern unsigned int ltq_get_fbs0_hz(void); + +static void cs_activate(struct svip_ssc_device *ssc_dev, struct spi_device *spi) +{ + ssc_dev->regs->whbgpostat = 0x0001 << spi->chip_select; /* activate the chip select */ +} + +static void cs_deactivate(struct svip_ssc_device *ssc_dev, struct spi_device *spi) +{ + ssc_dev->regs->whbgpostat = 0x0100 << spi->chip_select; /* deactivate the chip select */ +} + +/* + * "Normally" returns Byte Valid = 4. + * If the unaligned remainder of the packet is 3 bytes, these have to be + * transferred as a combination of a 16-bit and a 8-bit FPI transfer. For + * 2 or 1 remaining bytes a single 16-bit or 8-bit transfer will do. + */ +static int inline _estimate_bv(int byte_pos, int bytelen) +{ + int remainder = bytelen % 4; + + if (byte_pos < (bytelen - remainder)) + return 4; + + if (remainder == 3) + { + if (byte_pos == (bytelen - remainder)) + return 2; + else + return 1; + } + return remainder; +} + +/* + * Submit next transfer. + * lock is held, spi irq is blocked + */ +static void svip_ssc_next_xfer(struct spi_master *master, + struct spi_message *msg) +{ + struct svip_ssc_device *ssc_dev = spi_master_get_devdata(master); + struct spi_transfer *xfer; + unsigned char *buf_ptr; + + xfer = ssc_dev->current_transfer; + if (!xfer || ssc_dev->remaining_bytes == 0) { + if (xfer) + xfer = list_entry(xfer->transfer_list.next, + struct spi_transfer, transfer_list); + else + xfer = list_entry(msg->transfers.next, + struct spi_transfer, transfer_list); + ssc_dev->remaining_bytes = xfer->len; + ssc_dev->rx_bytes = 0; + ssc_dev->tx_bytes = 0; + ssc_dev->current_transfer = xfer; + ssc_dev->regs->sfcon = 0; /* reset Serial Framing */ + + /* enable and flush RX/TX FIFO */ + ssc_dev->regs->rxfcon = + SSC_RXFCON_RXFITL_VAL(SVIP_SSC_RFIFO_WORDS-FIFO_HEADROOM) | + SSC_RXFCON_RXFLU | /* Receive FIFO Flush */ + SSC_RXFCON_RXFEN; /* Receive FIFO Enable */ + + ssc_dev->regs->txfcon = + SSC_TXFCON_TXFITL_VAL(FIFO_HEADROOM) | + SSC_TXFCON_TXFLU | /* Transmit FIFO Flush */ + SSC_TXFCON_TXFEN; /* Transmit FIFO Enable */ + + asm("sync"); + + /* select mode RXTX, RX or TX */ + if (xfer->rx_buf && xfer->tx_buf) /* RX and TX */ + { + if (ssc_dev->bus_dir != SSC_RXTX) + { + ssc_dev->regs->mcon &= ~(SSC_MCON_RXOFF | SSC_MCON_TXOFF); + ssc_dev->bus_dir = SSC_RXTX; + ssc_dev->regs->irnen = SSC_IRNEN_T | SSC_IRNEN_F | SSC_IRNEN_E; + } + ssc_dev->regs->sfcon = + SSC_SFCON_PLEN_VAL(0) | + SSC_SFCON_DLEN_VAL(((xfer->len-1)%SFRAME_SIZE)*8+7) | + SSC_SFCON_STOP | + SSC_SFCON_ICLK_VAL(2) | + SSC_SFCON_IDAT_VAL(2) | + SSC_SFCON_IAEN | + SSC_SFCON_SFEN; + + } + else if (xfer->rx_buf) /* RX only */ + { + if (ssc_dev->bus_dir != SSC_RX) + { + ssc_dev->regs->mcon = + (ssc_dev->regs->mcon | SSC_MCON_TXOFF) & ~SSC_MCON_RXOFF; + + ssc_dev->bus_dir = SSC_RX; + + ssc_dev->regs->irnen = SSC_IRNEN_R | SSC_IRNEN_E; + } + /* Initiate clock generation for Rx-Only Transfer. In case of RX-only transfer, + * rx_bytes represents the number of already requested bytes. + */ + ssc_dev->rx_bytes = min(xfer->len, (unsigned)(SVIP_SSC_RFIFO_WORDS*4)); + ssc_dev->regs->rxreq = ssc_dev->rx_bytes; + } + else /* TX only */ + { + if (ssc_dev->bus_dir != SSC_TX) + { + ssc_dev->regs->mcon = + (ssc_dev->regs->mcon | SSC_MCON_RXOFF) & ~SSC_MCON_TXOFF; + + ssc_dev->bus_dir = SSC_TX; + + ssc_dev->regs->irnen = + SSC_IRNEN_T | SSC_IRNEN_F | SSC_IRNEN_E; + } + ssc_dev->regs->sfcon = + SSC_SFCON_PLEN_VAL(0) | + SSC_SFCON_DLEN_VAL(((xfer->len-1)%SFRAME_SIZE)*8+7) | + SSC_SFCON_STOP | + SSC_SFCON_ICLK_VAL(2) | + SSC_SFCON_IDAT_VAL(2) | + SSC_SFCON_IAEN | + SSC_SFCON_SFEN; + } + } + + if (xfer->tx_buf) + { + int outstanding; + int i; + int fstat = ssc_dev->regs->fstat; + int txffl = SSC_FSTAT_TXFFL_GET(fstat); + int rxffl = SSC_FSTAT_RXFFL_GET(fstat); + + outstanding = txffl; + + if (xfer->rx_buf) + { + outstanding += rxffl; + if (SSC_STATE_BSY_GET(ssc_dev->regs->state)) + outstanding++; + + while (rxffl) /* is 0 in TX-Only mode */ + { + unsigned int rb; + int rxbv = _estimate_bv(ssc_dev->rx_bytes, xfer->len); + rb = ssc_dev->regs->rb; + for (i=0; i<rxbv; i++) + { + ((unsigned char*)xfer->rx_buf)[ssc_dev->rx_bytes] = + (rb >> ((rxbv-i-1)*8)) & 0xFF; + + ssc_dev->rx_bytes++; + } + rxffl--; + outstanding--; + } + ssc_dev->remaining_bytes = xfer->len - ssc_dev->rx_bytes; + } + + /* for last Tx cycle set TxFifo threshold to 0 */ + if ((xfer->len - ssc_dev->tx_bytes) <= + (4*(SVIP_SSC_RFIFO_WORDS-1-outstanding))) + { + ssc_dev->regs->txfcon = SSC_TXFCON_TXFITL_VAL(0) | + SSC_TXFCON_TXFEN; + } + + while ((ssc_dev->tx_bytes < xfer->len) && + (outstanding < (SVIP_SSC_RFIFO_WORDS-1))) + { + unsigned int tb = 0; + int txbv = _estimate_bv(ssc_dev->tx_bytes, xfer->len); + + for (i=0; i<txbv; i++) + { + tb |= ((unsigned char*)xfer->tx_buf)[ssc_dev->tx_bytes] << + ((txbv-i-1)*8); + + ssc_dev->tx_bytes++; + } + switch(txbv) + { +#ifdef __BIG_ENDIAN + case 1: + *((unsigned char *)(&(ssc_dev->regs->tb))+3) = tb & 0xFF; + break; + case 2: + *((unsigned short *)(&(ssc_dev->regs->tb))+1) = tb & 0xFFFF; + break; +#else /* __LITTLE_ENDIAN */ + case 1: + *((unsigned char *)(&(ssc_dev->regs->tb))) = tb & 0xFF; + break; + case 2: + *((unsigned short *)(&(ssc_dev->regs->tb))) = tb & 0xFFFF; + break; +#endif + default: + ssc_dev->regs->tb = tb; + } + outstanding++; + } + } + else /* xfer->tx_buf == NULL -> RX only! */ + { + int j; + int rxffl = SSC_FSTAT_RXFFL_GET(ssc_dev->regs->fstat); + int rxbv = 0; + unsigned int rbuf; + + buf_ptr = (unsigned char*)xfer->rx_buf + + (xfer->len - ssc_dev->remaining_bytes); + + for (j = 0; j < rxffl; j++) + { + rxbv = SSC_STATE_RXBV_GET(ssc_dev->regs->state); + rbuf = ssc_dev->regs->rb; + + if (rxbv == 4) + { + *((unsigned int*)buf_ptr+j) = ntohl(rbuf); + } + else + { + int b; +#ifdef __BIG_ENDIAN + for (b = 0; b < rxbv; b++) + { + buf_ptr[4*j+b] = ((unsigned char*)(&rbuf))[4-rxbv+b]; + } +#else /* __LITTLE_ENDIAN */ + for (b = 0; b < rxbv; b++) + { + buf_ptr[4*j+b] = ((unsigned char*)(&rbuf))[rxbv-1-b]; + } +#endif + } + ssc_dev->remaining_bytes -= rxbv; + } + if ((ssc_dev->rx_bytes < xfer->len) && + !SSC_STATE_BSY_GET(ssc_dev->regs->state)) + { + int rxreq = min(xfer->len - ssc_dev->rx_bytes, + (unsigned)(SVIP_SSC_RFIFO_WORDS*4)); + + ssc_dev->rx_bytes += rxreq; + ssc_dev->regs->rxreq = rxreq; + } + + if (ssc_dev->remaining_bytes < 0) + { + printk("ssc_dev->remaining_bytes = %d! xfer->len = %d, " + "rxffl=%d, rxbv=%d\n", ssc_dev->remaining_bytes, xfer->len, + rxffl, rxbv); + + ssc_dev->remaining_bytes = 0; + } + } +} + +/* + * Submit next message. + * lock is held + */ +static void svip_ssc_next_message(struct spi_master *master) +{ + struct svip_ssc_device *ssc_dev = spi_master_get_devdata(master); + struct spi_message *msg; + struct spi_device *spi; + + BUG_ON(ssc_dev->current_transfer); + + msg = list_entry(ssc_dev->queue.next, struct spi_message, queue); + spi = msg->spi; + + dev_dbg(master->dev.parent, "start message %p on %p\n", msg, spi); + + /* select chip if it's not still active */ + if (ssc_dev->stay) { + if (ssc_dev->stay != spi) { + cs_deactivate(ssc_dev, ssc_dev->stay); + svip_ssc_setup(spi); + cs_activate(ssc_dev, spi); + } + ssc_dev->stay = NULL; + } + else { + svip_ssc_setup(spi); + cs_activate(ssc_dev, spi); + } + + svip_ssc_next_xfer(master, msg); +} + +/* + * Report message completion. + * lock is held + */ +static void +svip_ssc_msg_done(struct spi_master *master, struct svip_ssc_device *ssc_dev, + struct spi_message *msg, int status, int stay) +{ + if (!stay || status < 0) + cs_deactivate(ssc_dev, msg->spi); + else + ssc_dev->stay = msg->spi; + + list_del(&msg->queue); + msg->status = status; + + dev_dbg(master->dev.parent, + "xfer complete: %u bytes transferred\n", + msg->actual_length); + + spin_unlock(&ssc_dev->lock); + msg->complete(msg->context); + spin_lock(&ssc_dev->lock); + + ssc_dev->current_transfer = NULL; + + /* continue if needed */ + if (list_empty(&ssc_dev->queue) || ssc_dev->stopping) + ; /* TODO: disable hardware */ + else + svip_ssc_next_message(master); +} + +static irqreturn_t svip_ssc_eir_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = (struct platform_device*)dev_id; + struct spi_master *master = platform_get_drvdata(pdev); + struct svip_ssc_device *ssc_dev = spi_master_get_devdata(master); + + dev_err (&pdev->dev, "ERROR: errirq. STATE = 0x%0lx\n", + ssc_dev->regs->state); + return IRQ_HANDLED; +} + +static irqreturn_t svip_ssc_rir_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = (struct platform_device*)dev_id; + struct spi_master *master = platform_get_drvdata(pdev); + struct svip_ssc_device *ssc_dev = spi_master_get_devdata(master); + struct spi_message *msg; + struct spi_transfer *xfer; + + xfer = ssc_dev->current_transfer; + msg = list_entry(ssc_dev->queue.next, struct spi_message, queue); + + /* Tx and Rx Interrupts are fairly unpredictable. Just leave interrupt + * handler for spurious Interrupts! + */ + if (!xfer) { + dev_dbg(master->dev.parent, + "%s(%d): xfer = NULL\n", __FUNCTION__, irq); + goto out; + } + if ( !(xfer->rx_buf) ) { + dev_dbg(master->dev.parent, + "%s(%d): xfer->rx_buf = NULL\n", __FUNCTION__, irq); + goto out; + } + if (ssc_dev->remaining_bytes > 0) + { + /* + * Keep going, we still have data to send in + * the current transfer. + */ + svip_ssc_next_xfer(master, msg); + } + + if (ssc_dev->remaining_bytes == 0) + { + msg->actual_length += xfer->len; + + if (msg->transfers.prev == &xfer->transfer_list) { + /* report completed message */ + svip_ssc_msg_done(master, ssc_dev, msg, 0, + xfer->cs_change); + } + else { + if (xfer->cs_change) { + cs_deactivate(ssc_dev, msg->spi); + udelay(1); /* not nice in interrupt context */ + cs_activate(ssc_dev, msg->spi); + } + + /* Not done yet. Submit the next transfer. */ + svip_ssc_next_xfer(master, msg); + } + } +out: + return IRQ_HANDLED; +} + +static irqreturn_t svip_ssc_tir_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = (struct platform_device*)dev_id; + struct spi_master *master = platform_get_drvdata(pdev); + struct svip_ssc_device *ssc_dev = spi_master_get_devdata(master); + struct spi_message *msg; + struct spi_transfer *xfer; + int tx_remain; + + xfer = ssc_dev->current_transfer; + msg = list_entry(ssc_dev->queue.next, struct spi_message, queue); + + /* Tx and Rx Interrupts are fairly unpredictable. Just leave interrupt + * handler for spurious Interrupts! + */ + if (!xfer) { + dev_dbg(master->dev.parent, + "%s(%d): xfer = NULL\n", __FUNCTION__, irq); + goto out; + } + if ( !(xfer->tx_buf) ) { + dev_dbg(master->dev.parent, + "%s(%d): xfer->tx_buf = NULL\n", __FUNCTION__, irq); + goto out; + } + + if (ssc_dev->remaining_bytes > 0) + { + tx_remain = xfer->len - ssc_dev->tx_bytes; + if ( tx_remain == 0 ) + { + dev_dbg(master->dev.parent, + "%s(%d): tx_remain = 0\n", __FUNCTION__, irq); + } + else + /* + * Keep going, we still have data to send in + * the current transfer. + */ + svip_ssc_next_xfer(master, msg); + } +out: + return IRQ_HANDLED; +} + +static irqreturn_t svip_ssc_fir_handler(int irq, void *dev_id) +{ + struct platform_device *pdev = (struct platform_device*)dev_id; + struct spi_master *master = platform_get_drvdata(pdev); + struct svip_ssc_device *ssc_dev = spi_master_get_devdata(master); + struct spi_message *msg; + struct spi_transfer *xfer; + + xfer = ssc_dev->current_transfer; + msg = list_entry(ssc_dev->queue.next, struct spi_message, queue); + + /* Tx and Rx Interrupts are fairly unpredictable. Just leave interrupt + * handler for spurious Interrupts! + */ + if (!xfer) { + dev_dbg(master->dev.parent, + "%s(%d): xfer = NULL\n", __FUNCTION__, irq); + goto out; + } + if ( !(xfer->tx_buf) ) { + dev_dbg(master->dev.parent, + "%s(%d): xfer->tx_buf = NULL\n", __FUNCTION__, irq); + goto out; + } + + if (ssc_dev->remaining_bytes > 0) + { + int tx_remain = xfer->len - ssc_dev->tx_bytes; + + if (tx_remain == 0) + { + /* Frame interrupt gets raised _before_ last Rx interrupt */ + if (xfer->rx_buf) + { + svip_ssc_next_xfer(master, msg); + if (ssc_dev->remaining_bytes) + printk("expected RXTX transfer to be complete!\n"); + } + ssc_dev->remaining_bytes = 0; + } + else + { + ssc_dev->regs->sfcon = SSC_SFCON_PLEN_VAL(0) | + SSC_SFCON_DLEN_VAL(SFRAME_SIZE*8-1) | + SSC_SFCON_STOP | + SSC_SFCON_ICLK_VAL(2) | + SSC_SFCON_IDAT_VAL(2) | + SSC_SFCON_IAEN | + SSC_SFCON_SFEN; + } + } + + if (ssc_dev->remaining_bytes == 0) + { + msg->actual_length += xfer->len; + + if (msg->transfers.prev == &xfer->transfer_list) { + /* report completed message */ + svip_ssc_msg_done(master, ssc_dev, msg, 0, + xfer->cs_change); + } + else { + if (xfer->cs_change) { + cs_deactivate(ssc_dev, msg->spi); + udelay(1); /* not nice in interrupt context */ + cs_activate(ssc_dev, msg->spi); + } + + /* Not done yet. Submit the next transfer. */ + svip_ssc_next_xfer(master, msg); + } + } + +out: + return IRQ_HANDLED; +} + +/* the spi->mode bits understood by this driver: */ +#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST | SPI_LOOP) + +static int svip_ssc_setup(struct spi_device *spi) +{ + struct spi_master *master = spi->master; + struct svip_ssc_device *ssc_dev = spi_master_get_devdata(master); + unsigned int bits = spi->bits_per_word; + unsigned int br, sck_hz = spi->max_speed_hz; + unsigned long flags; + + if (ssc_dev->stopping) + return -ESHUTDOWN; + + if (spi->chip_select >= master->num_chipselect) { + dev_dbg(&spi->dev, + "setup: invalid chipselect %u (%u defined)\n", + spi->chip_select, master->num_chipselect); + return -EINVAL; + } + + if (bits == 0) + bits = 8; + if (bits != 8) { + dev_dbg(&spi->dev, + "setup: invalid bits_per_word %u (expect 8)\n", + bits); + return -EINVAL; + } + + if (spi->mode & ~MODEBITS) { + dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n", + spi->mode & ~MODEBITS); + return -EINVAL; + } + + /* Disable SSC */ + ssc_dev->regs->whbstate = SSC_WHBSTATE_CLREN; + + if (sck_hz == 0) + sck_hz = 10000; + + br = ltq_get_fbs0_hz()/(2 *sck_hz); + if (ltq_get_fbs0_hz()%(2 *sck_hz) == 0) + br = br -1; + ssc_dev->regs->br = br; + + /* set Control Register */ + ssc_dev->regs->mcon = SSC_MCON_ENBV | + SSC_MCON_RUEN | + SSC_MCON_TUEN | + SSC_MCON_AEN | + SSC_MCON_REN | + SSC_MCON_TEN | + (spi->mode & SPI_CPOL ? SSC_MCON_PO : 0) | /* Clock Polarity */ + (spi->mode & SPI_CPHA ? 0 : SSC_MCON_PH) | /* Tx on trailing edge */ + (spi->mode & SPI_LOOP ? SSC_MCON_LB : 0) | /* Loopback */ + (spi->mode & SPI_LSB_FIRST ? 0 : SSC_MCON_HB); /* MSB first */ + ssc_dev->bus_dir = SSC_UNDEF; + + /* Enable SSC */ + ssc_dev->regs->whbstate = SSC_WHBSTATE_SETEN; + asm("sync"); + + spin_lock_irqsave(&ssc_dev->lock, flags); + if (ssc_dev->stay == spi) + ssc_dev->stay = NULL; + cs_deactivate(ssc_dev, spi); + spin_unlock_irqrestore(&ssc_dev->lock, flags); + + dev_dbg(&spi->dev, + "setup: %u Hz bpw %u mode 0x%02x cs %u\n", + sck_hz, bits, spi->mode, spi->chip_select); + + return 0; +} + +static int svip_ssc_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct spi_master *master = spi->master; + struct svip_ssc_device *ssc_dev = spi_master_get_devdata(master); + struct spi_transfer *xfer; + unsigned long flags; + + dev_dbg(&spi->dev, "new message %p submitted\n", msg); + + if (unlikely(list_empty(&msg->transfers) + || !spi->max_speed_hz)) { + return -EINVAL; + } + + if (ssc_dev->stopping) + return -ESHUTDOWN; + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + if (!(xfer->tx_buf || xfer->rx_buf) || (xfer->len == 0)) { + dev_dbg(&spi->dev, "missing rx or tx buf\n"); + return -EINVAL; + } + + /* FIXME implement these protocol options!! */ + if (xfer->bits_per_word || xfer->speed_hz) { + dev_dbg(&spi->dev, "no protocol options yet\n"); + return -ENOPROTOOPT; + } + +#ifdef VERBOSE + dev_dbg(spi->dev, + " xfer %p: len %u tx %p/%08x rx %p/%08x\n", + xfer, xfer->len, + xfer->tx_buf, xfer->tx_dma, + xfer->rx_buf, xfer->rx_dma); +#endif + } + + msg->status = -EINPROGRESS; + msg->actual_length = 0; + + spin_lock_irqsave(&ssc_dev->lock, flags); + list_add_tail(&msg->queue, &ssc_dev->queue); + if (!ssc_dev->current_transfer) + { + /* start transmission machine, if not started yet */ + svip_ssc_next_message(master); + } + spin_unlock_irqrestore(&ssc_dev->lock, flags); + + return 0; +} + +static void svip_ssc_cleanup(struct spi_device *spi) +{ + struct svip_ssc_device *ssc_dev = spi_master_get_devdata(spi->master); + unsigned long flags; + + if (!spi->controller_state) + return; + + spin_lock_irqsave(&ssc_dev->lock, flags); + if (ssc_dev->stay == spi) { + ssc_dev->stay = NULL; + cs_deactivate(ssc_dev, spi); + } + spin_unlock_irqrestore(&ssc_dev->lock, flags); +} + +/*-------------------------------------------------------------------------*/ + +static int __init svip_ssc_probe(struct platform_device *pdev) +{ + int ret; + struct spi_master *master; + struct svip_ssc_device *ssc_dev; + struct resource *res_regs; + int irq; + + ret = -ENOMEM; + + /* setup spi core then atmel-specific driver state */ + master = spi_alloc_master(&pdev->dev, sizeof (*ssc_dev)); + if (!master) + { + dev_err (&pdev->dev, "ERROR: no memory for master spi\n"); + goto errout; + } + + ssc_dev = spi_master_get_devdata(master); + platform_set_drvdata(pdev, master); + + master->bus_num = pdev->id; + master->num_chipselect = 8; + master->mode_bits = MODEBITS; + master->setup = svip_ssc_setup; + master->transfer = svip_ssc_transfer; + master->cleanup = svip_ssc_cleanup; + + spin_lock_init(&ssc_dev->lock); + INIT_LIST_HEAD(&ssc_dev->queue); + + /* retrive register configration */ + res_regs = platform_get_resource_byname (pdev, IORESOURCE_MEM, "regs"); + if (NULL == res_regs) + { + dev_err (&pdev->dev, "ERROR: missed 'regs' resource\n"); + goto spierr; + } + + ssc_dev->regs = (struct svip_reg_ssc*)KSEG1ADDR(res_regs->start); + + irq = platform_get_irq_byname (pdev, "tx"); + if (irq < 0) + goto irqerr; + sprintf(ssc_dev->intname[0], "%s_tx", pdev->name); + ret = devm_request_irq(&pdev->dev, irq, svip_ssc_tir_handler, + IRQF_DISABLED, ssc_dev->intname[0], pdev); + if (ret != 0) + goto irqerr; + + irq = platform_get_irq_byname (pdev, "rx"); + if (irq < 0) + goto irqerr; + sprintf(ssc_dev->intname[1], "%s_rx", pdev->name); + ret = devm_request_irq(&pdev->dev, irq, svip_ssc_rir_handler, + IRQF_DISABLED, ssc_dev->intname[1], pdev); + if (ret != 0) + goto irqerr; + + irq = platform_get_irq_byname (pdev, "err"); + if (irq < 0) + goto irqerr; + sprintf(ssc_dev->intname[2], "%s_err", pdev->name); + ret = devm_request_irq(&pdev->dev, irq, svip_ssc_eir_handler, + IRQF_DISABLED, ssc_dev->intname[2], pdev); + if (ret != 0) + goto irqerr; + + irq = platform_get_irq_byname (pdev, "frm"); + if (irq < 0) + goto irqerr; + sprintf(ssc_dev->intname[3], "%s_frm", pdev->name); + ret = devm_request_irq(&pdev->dev, irq, svip_ssc_fir_handler, + IRQF_DISABLED, ssc_dev->intname[3], pdev); + if (ret != 0) + goto irqerr; + + /* + * Initialize the Hardware + */ + + /* Clear enable bit, i.e. put SSC into configuration mode */ + ssc_dev->regs->whbstate = SSC_WHBSTATE_CLREN; + /* enable SSC core to run at fpi clock */ + ssc_dev->regs->clc = SSC_CLC_RMC_VAL(1); + asm("sync"); + + /* GPIO CS */ + ssc_dev->regs->gpocon = SSC_GPOCON_ISCSBN_VAL(0xFF); + ssc_dev->regs->whbgpostat = SSC_WHBGPOSTAT_SETOUTN_VAL(0xFF); /* CS to high */ + + /* Set Master mode */ + ssc_dev->regs->whbstate = SSC_WHBSTATE_SETMS; + + /* enable and flush RX/TX FIFO */ + ssc_dev->regs->rxfcon = SSC_RXFCON_RXFITL_VAL(SVIP_SSC_RFIFO_WORDS-FIFO_HEADROOM) | + SSC_RXFCON_RXFLU | /* Receive FIFO Flush */ + SSC_RXFCON_RXFEN; /* Receive FIFO Enable */ + + ssc_dev->regs->txfcon = SSC_TXFCON_TXFITL_VAL(FIFO_HEADROOM) | + SSC_TXFCON_TXFLU | /* Transmit FIFO Flush */ + SSC_TXFCON_TXFEN; /* Transmit FIFO Enable */ + asm("sync"); + + /* enable IRQ */ + ssc_dev->regs->irnen = SSC_IRNEN_E; + + dev_info(&pdev->dev, "controller at 0x%08lx (irq %d)\n", + (unsigned long)ssc_dev->regs, platform_get_irq_byname (pdev, "rx")); + + ret = spi_register_master(master); + if (ret) + goto out_reset_hw; + + return 0; + +out_reset_hw: + +irqerr: + devm_free_irq (&pdev->dev, platform_get_irq_byname (pdev, "tx"), pdev); + devm_free_irq (&pdev->dev, platform_get_irq_byname (pdev, "rx"), pdev); + devm_free_irq (&pdev->dev, platform_get_irq_byname (pdev, "err"), pdev); + devm_free_irq (&pdev->dev, platform_get_irq_byname (pdev, "frm"), pdev); + +spierr: + + spi_master_put(master); + +errout: + return ret; +} + +static int __exit svip_ssc_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct svip_ssc_device *ssc_dev = spi_master_get_devdata(master); + struct spi_message *msg; + + /* reset the hardware and block queue progress */ + spin_lock_irq(&ssc_dev->lock); + ssc_dev->stopping = 1; + /* TODO: shutdown hardware */ + spin_unlock_irq(&ssc_dev->lock); + + /* Terminate remaining queued transfers */ + list_for_each_entry(msg, &ssc_dev->queue, queue) { + /* REVISIT unmapping the dma is a NOP on ARM and AVR32 + * but we shouldn't depend on that... + */ + msg->status = -ESHUTDOWN; + msg->complete(msg->context); + } + + devm_free_irq (&pdev->dev, platform_get_irq_byname (pdev, "tx"), pdev); + devm_free_irq (&pdev->dev, platform_get_irq_byname (pdev, "rx"), pdev); + devm_free_irq (&pdev->dev, platform_get_irq_byname (pdev, "err"), pdev); + devm_free_irq (&pdev->dev, platform_get_irq_byname (pdev, "frm"), pdev); + + spi_unregister_master(master); + platform_set_drvdata(pdev, NULL); + spi_master_put(master); + return 0; +} + +#ifdef CONFIG_PM +static int svip_ssc_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct svip_ssc_device *ssc_dev = spi_master_get_devdata(master); + + clk_disable(ssc_dev->clk); + return 0; +} + +static int svip_ssc_resume(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct svip_ssc_device *ssc_dev = spi_master_get_devdata(master); + + clk_enable(ssc_dev->clk); + return 0; +} +#endif + +static struct platform_driver svip_ssc_driver = { + .driver = { + .name = "ifx_ssc", + .owner = THIS_MODULE, + }, + .probe = svip_ssc_probe, +#ifdef CONFIG_PM + .suspend = svip_ssc_suspend, + .resume = svip_ssc_resume, +#endif + .remove = __exit_p(svip_ssc_remove) +}; + +int __init svip_ssc_init(void) +{ + return platform_driver_register(&svip_ssc_driver); +} + +void __exit svip_ssc_exit(void) +{ + platform_driver_unregister(&svip_ssc_driver); +} + +module_init(svip_ssc_init); +module_exit(svip_ssc_exit); + +MODULE_ALIAS("platform:ifx_ssc"); +MODULE_DESCRIPTION("Lantiq SSC Controller driver"); +MODULE_AUTHOR("Andreas Schmidt <andreas.schmidt@infineon.com>"); +MODULE_AUTHOR("Jevgenijs Grigorjevs <Jevgenijs.Grigorjevs@lantiq.com>"); +MODULE_LICENSE("GPL"); |