diff options
Diffstat (limited to 'target/linux/lantiq/files-3.3/drivers/spi/spi-falcon.c')
| -rw-r--r-- | target/linux/lantiq/files-3.3/drivers/spi/spi-falcon.c | 483 | 
1 files changed, 483 insertions, 0 deletions
diff --git a/target/linux/lantiq/files-3.3/drivers/spi/spi-falcon.c b/target/linux/lantiq/files-3.3/drivers/spi/spi-falcon.c new file mode 100644 index 000000000..447bbaaa3 --- /dev/null +++ b/target/linux/lantiq/files-3.3/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");  | 
