diff options
Diffstat (limited to 'drivers/spi')
| -rw-r--r-- | drivers/spi/Makefile | 1 | ||||
| -rw-r--r-- | drivers/spi/ti_qspi.c | 311 | 
2 files changed, 312 insertions, 0 deletions
| diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 91d24cea5..e5941b09f 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -38,6 +38,7 @@ COBJS-$(CONFIG_FDT_SPI) += fdt_spi.o  COBJS-$(CONFIG_TEGRA20_SFLASH) += tegra20_sflash.o  COBJS-$(CONFIG_TEGRA20_SLINK) += tegra20_slink.o  COBJS-$(CONFIG_TEGRA114_SPI) += tegra114_spi.o +COBJS-$(CONFIG_TI_QSPI) += ti_qspi.o  COBJS-$(CONFIG_XILINX_SPI) += xilinx_spi.o  COBJS-$(CONFIG_ZYNQ_SPI) += zynq_spi.o diff --git a/drivers/spi/ti_qspi.c b/drivers/spi/ti_qspi.c new file mode 100644 index 000000000..5a5b48276 --- /dev/null +++ b/drivers/spi/ti_qspi.c @@ -0,0 +1,311 @@ +/* + * TI QSPI driver + * + * Copyright (C) 2013, Texas Instruments, Incorporated + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <asm/io.h> +#include <asm/arch/omap.h> +#include <malloc.h> +#include <spi.h> + +/* ti qpsi register bit masks */ +#define QSPI_TIMEOUT                    2000000 +#define QSPI_FCLK                       192000000 +/* clock control */ +#define QSPI_CLK_EN                     (1 << 31) +#define QSPI_CLK_DIV_MAX                0xffff +/* command */ +#define QSPI_EN_CS(n)                   (n << 28) +#define QSPI_WLEN(n)                    ((n-1) << 19) +#define QSPI_3_PIN                      (1 << 18) +#define QSPI_RD_SNGL                    (1 << 16) +#define QSPI_WR_SNGL                    (2 << 16) +#define QSPI_INVAL                      (4 << 16) +#define QSPI_RD_QUAD                    (7 << 16) +/* device control */ +#define QSPI_DD(m, n)                   (m << (3 + n*8)) +#define QSPI_CKPHA(n)                   (1 << (2 + n*8)) +#define QSPI_CSPOL(n)                   (1 << (1 + n*8)) +#define QSPI_CKPOL(n)                   (1 << (n*8)) +/* status */ +#define QSPI_WC                         (1 << 1) +#define QSPI_BUSY                       (1 << 0) +#define QSPI_WC_BUSY                    (QSPI_WC | QSPI_BUSY) +#define QSPI_XFER_DONE                  QSPI_WC +#define MM_SWITCH                       0x01 +#define MEM_CS                          0x100 +#define MEM_CS_UNSELECT                 0xfffff0ff +#define MMAP_START_ADDR                 0x5c000000 +#define CORE_CTRL_IO                    0x4a002558 + +#define QSPI_CMD_READ                   (0x3 << 0) +#define QSPI_CMD_READ_QUAD              (0x6b << 0) +#define QSPI_CMD_READ_FAST              (0x0b << 0) +#define QSPI_SETUP0_NUM_A_BYTES         (0x2 << 8) +#define QSPI_SETUP0_NUM_D_BYTES_NO_BITS (0x0 << 10) +#define QSPI_SETUP0_NUM_D_BYTES_8_BITS  (0x1 << 10) +#define QSPI_SETUP0_READ_NORMAL         (0x0 << 12) +#define QSPI_SETUP0_READ_QUAD           (0x3 << 12) +#define QSPI_CMD_WRITE                  (0x2 << 16) +#define QSPI_NUM_DUMMY_BITS             (0x0 << 24) + +/* ti qspi register set */ +struct ti_qspi_regs { +	u32 pid; +	u32 pad0[3]; +	u32 sysconfig; +	u32 pad1[3]; +	u32 int_stat_raw; +	u32 int_stat_en; +	u32 int_en_set; +	u32 int_en_ctlr; +	u32 intc_eoi; +	u32 pad2[3]; +	u32 clk_ctrl; +	u32 dc; +	u32 cmd; +	u32 status; +	u32 data; +	u32 setup0; +	u32 setup1; +	u32 setup2; +	u32 setup3; +	u32 memswitch; +	u32 data1; +	u32 data2; +	u32 data3; +}; + +/* ti qspi slave */ +struct ti_qspi_slave { +	struct spi_slave slave; +	struct ti_qspi_regs *base; +	unsigned int mode; +	u32 cmd; +	u32 dc; +}; + +static inline struct ti_qspi_slave *to_ti_qspi_slave(struct spi_slave *slave) +{ +	return container_of(slave, struct ti_qspi_slave, slave); +} + +static void ti_spi_setup_spi_register(struct ti_qspi_slave *qslave) +{ +	struct spi_slave *slave = &qslave->slave; +	u32 memval = 0; + +	slave->memory_map = (void *)MMAP_START_ADDR; + +	memval |= QSPI_CMD_READ | QSPI_SETUP0_NUM_A_BYTES | +			QSPI_SETUP0_NUM_D_BYTES_NO_BITS | +			QSPI_SETUP0_READ_NORMAL | QSPI_CMD_WRITE | +			QSPI_NUM_DUMMY_BITS; + +	writel(memval, &qslave->base->setup0); +} + +static void ti_spi_set_speed(struct spi_slave *slave, uint hz) +{ +	struct ti_qspi_slave *qslave = to_ti_qspi_slave(slave); +	uint clk_div; + +	debug("ti_spi_set_speed: hz: %d, clock divider %d\n", hz, clk_div); + +	if (!hz) +		clk_div = 0; +	else +		clk_div = (QSPI_FCLK / hz) - 1; + +	/* disable SCLK */ +	writel(readl(&qslave->base->clk_ctrl) & ~QSPI_CLK_EN, +	       &qslave->base->clk_ctrl); + +	/* assign clk_div values */ +	if (clk_div < 0) +		clk_div = 0; +	else if (clk_div > QSPI_CLK_DIV_MAX) +		clk_div = QSPI_CLK_DIV_MAX; + +	/* enable SCLK */ +	writel(QSPI_CLK_EN | clk_div, &qslave->base->clk_ctrl); +} + +int spi_cs_is_valid(unsigned int bus, unsigned int cs) +{ +	return 1; +} + +void spi_cs_activate(struct spi_slave *slave) +{ +	/* CS handled in xfer */ +	return; +} + +void spi_cs_deactivate(struct spi_slave *slave) +{ +	struct ti_qspi_slave *qslave = to_ti_qspi_slave(slave); + +	debug("spi_cs_deactivate: 0x%08x\n", (u32)slave); + +	writel(qslave->cmd | QSPI_INVAL, &qslave->base->cmd); +} + +void spi_init(void) +{ +	/* nothing to do */ +} + +struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, +				  unsigned int max_hz, unsigned int mode) +{ +	struct ti_qspi_slave *qslave; + +	qslave = spi_alloc_slave(struct ti_qspi_slave, bus, cs); +	if (!qslave) { +		printf("SPI_error: Fail to allocate ti_qspi_slave\n"); +		return NULL; +	} + +	qslave->base = (struct ti_qspi_regs *)QSPI_BASE; +	qslave->mode = mode; + +	ti_spi_set_speed(&qslave->slave, max_hz); + +#ifdef CONFIG_TI_SPI_MMAP +	ti_spi_setup_spi_register(qslave); +#endif + +	return &qslave->slave; +} + +void spi_free_slave(struct spi_slave *slave) +{ +	struct ti_qspi_slave *qslave = to_ti_qspi_slave(slave); +	free(qslave); +} + +int spi_claim_bus(struct spi_slave *slave) +{ +	struct ti_qspi_slave *qslave = to_ti_qspi_slave(slave); + +	debug("spi_claim_bus: bus:%i cs:%i\n", slave->bus, slave->cs); + +	qslave->dc = 0; +	if (qslave->mode & SPI_CPHA) +		qslave->dc |= QSPI_CKPHA(slave->cs); +	if (qslave->mode & SPI_CPOL) +		qslave->dc |= QSPI_CKPOL(slave->cs); +	if (qslave->mode & SPI_CS_HIGH) +		qslave->dc |= QSPI_CSPOL(slave->cs); + +	writel(qslave->dc, &qslave->base->dc); +	writel(0, &qslave->base->cmd); +	writel(0, &qslave->base->data); + +	return 0; +} + +void spi_release_bus(struct spi_slave *slave) +{ +	struct ti_qspi_slave *qslave = to_ti_qspi_slave(slave); + +	debug("spi_release_bus: bus:%i cs:%i\n", slave->bus, slave->cs); + +	writel(0, &qslave->base->dc); +	writel(0, &qslave->base->cmd); +	writel(0, &qslave->base->data); +} + +int spi_xfer(struct spi_slave *slave, unsigned int bitlen, const void *dout, +	     void *din, unsigned long flags) +{ +	struct ti_qspi_slave *qslave = to_ti_qspi_slave(slave); +	uint words = bitlen >> 3; /* fixed 8-bit word length */ +	const uchar *txp = dout; +	uchar *rxp = din; +	uint status; +	int timeout, val; + +	debug("spi_xfer: bus:%i cs:%i bitlen:%i words:%i flags:%lx\n", +	      slave->bus, slave->cs, bitlen, words, flags); + +	/* Setup mmap flags */ +	if (flags & SPI_XFER_MMAP) { +		writel(MM_SWITCH, &qslave->base->memswitch); +		val = readl(CORE_CTRL_IO); +		val |= MEM_CS; +		writel(val, CORE_CTRL_IO); +		return 0; +	} else if (flags & SPI_XFER_MMAP_END) { +		writel(~MM_SWITCH, &qslave->base->memswitch); +		val = readl(CORE_CTRL_IO); +		val &= MEM_CS_UNSELECT; +		writel(val, CORE_CTRL_IO); +		return 0; +	} + +	if (bitlen == 0) +		return -1; + +	if (bitlen % 8) { +		debug("spi_xfer: Non byte aligned SPI transfer\n"); +		return -1; +	} + +	/* Setup command reg */ +	qslave->cmd = 0; +	qslave->cmd |= QSPI_WLEN(8); +	qslave->cmd |= QSPI_EN_CS(slave->cs); +	if (flags & SPI_3WIRE) +		qslave->cmd |= QSPI_3_PIN; +	qslave->cmd |= 0xfff; + +	while (words--) { +		if (txp) { +			debug("tx cmd %08x dc %08x data %02x\n", +			      qslave->cmd | QSPI_WR_SNGL, qslave->dc, *txp); +			writel(*txp++, &qslave->base->data); +			writel(qslave->cmd | QSPI_WR_SNGL, +			       &qslave->base->cmd); +			status = readl(&qslave->base->status); +			timeout = QSPI_TIMEOUT; +			while ((status & QSPI_WC_BUSY) != QSPI_XFER_DONE) { +				if (--timeout < 0) { +					printf("spi_xfer: TX timeout!\n"); +					return -1; +				} +				status = readl(&qslave->base->status); +			} +			debug("tx done, status %08x\n", status); +		} +		if (rxp) { +			qslave->cmd |= QSPI_RD_SNGL; +			debug("rx cmd %08x dc %08x\n", +			      qslave->cmd, qslave->dc); +			writel(qslave->cmd, &qslave->base->cmd); +			status = readl(&qslave->base->status); +			timeout = QSPI_TIMEOUT; +			while ((status & QSPI_WC_BUSY) != QSPI_XFER_DONE) { +				if (--timeout < 0) { +					printf("spi_xfer: RX timeout!\n"); +					return -1; +				} +				status = readl(&qslave->base->status); +			} +			*rxp++ = readl(&qslave->base->data); +			debug("rx done, status %08x, read %02x\n", +			      status, *(rxp-1)); +		} +	} + +	/* Terminate frame */ +	if (flags & SPI_XFER_END) +		spi_cs_deactivate(slave); + +	return 0; +} |