diff options
Diffstat (limited to 'drivers/spi/ep93xx_spi.c')
-rw-r--r-- | drivers/spi/ep93xx_spi.c | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/drivers/spi/ep93xx_spi.c b/drivers/spi/ep93xx_spi.c new file mode 100644 index 0000000..235557e --- /dev/null +++ b/drivers/spi/ep93xx_spi.c @@ -0,0 +1,274 @@ +/* + * SPI Driver for EP93xx + * + * Copyright (C) 2013 Sergey Kostanabev <sergey.kostanbaev <at> fairwaves.ru> + * + * Inspired form linux kernel driver and atmel uboot driver + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <spi.h> +#include <malloc.h> + +#include <asm/io.h> + +#include <asm/arch/ep93xx.h> + + +#define BIT(x) (1<<(x)) +#define SSPBASE SPI_BASE + +#define SSPCR0 0x0000 +#define SSPCR0_MODE_SHIFT 6 +#define SSPCR0_SCR_SHIFT 8 +#define SSPCR0_SPH BIT(7) +#define SSPCR0_SPO BIT(6) +#define SSPCR0_FRF_SPI 0 +#define SSPCR0_DSS_8BIT 7 + +#define SSPCR1 0x0004 +#define SSPCR1_RIE BIT(0) +#define SSPCR1_TIE BIT(1) +#define SSPCR1_RORIE BIT(2) +#define SSPCR1_LBM BIT(3) +#define SSPCR1_SSE BIT(4) +#define SSPCR1_MS BIT(5) +#define SSPCR1_SOD BIT(6) + +#define SSPDR 0x0008 + +#define SSPSR 0x000c +#define SSPSR_TFE BIT(0) +#define SSPSR_TNF BIT(1) +#define SSPSR_RNE BIT(2) +#define SSPSR_RFF BIT(3) +#define SSPSR_BSY BIT(4) +#define SSPCPSR 0x0010 + +#define SSPIIR 0x0014 +#define SSPIIR_RIS BIT(0) +#define SSPIIR_TIS BIT(1) +#define SSPIIR_RORIS BIT(2) +#define SSPICR SSPIIR + +#define SSPCLOCK 14745600 +#define SSP_MAX_RATE (SSPCLOCK / 2) +#define SSP_MIN_RATE (SSPCLOCK / (254 * 256)) + +/* timeout in milliseconds */ +#define SPI_TIMEOUT 5 +/* maximum depth of RX/TX FIFO */ +#define SPI_FIFO_SIZE 8 + +struct ep93xx_spi_slave { + struct spi_slave slave; + + unsigned sspcr0; + unsigned sspcpsr; +}; + +static inline struct ep93xx_spi_slave *to_ep93xx_spi(struct spi_slave *slave) +{ + return container_of(slave, struct ep93xx_spi_slave, slave); +} + +void spi_init() +{ +} + +static inline void ep93xx_spi_write_u8(u16 reg, u8 value) +{ + writel(value, (unsigned int *)(SSPBASE + reg)); +} + +static inline u8 ep93xx_spi_read_u8(u16 reg) +{ + return readl((unsigned int *)(SSPBASE + reg)); +} + +static inline void ep93xx_spi_write_u16(u16 reg, u16 value) +{ + writel(value, (unsigned int *)(SSPBASE + reg)); +} + +static inline u16 ep93xx_spi_read_u16(u16 reg) +{ + return (u16)readl((unsigned int *)(SSPBASE + reg)); +} + +static int ep93xx_spi_init_hw(unsigned int rate, unsigned int mode, + struct ep93xx_spi_slave *slave) +{ + unsigned cpsr, scr; + + if (rate > SSP_MAX_RATE) + rate = SSP_MAX_RATE; + + if (rate < SSP_MIN_RATE) + return -1; + + /* Calculate divisors so that we can get speed according the + * following formula: + * rate = spi_clock_rate / (cpsr * (1 + scr)) + * + * cpsr must be even number and starts from 2, scr can be any number + * between 0 and 255. + */ + for (cpsr = 2; cpsr <= 254; cpsr += 2) { + for (scr = 0; scr <= 255; scr++) { + if ((SSPCLOCK / (cpsr * (scr + 1))) <= rate) { + /* Set CHPA and CPOL, SPI format and 8bit */ + unsigned sspcr0 = (scr << SSPCR0_SCR_SHIFT) | + SSPCR0_FRF_SPI | SSPCR0_DSS_8BIT; + if (mode & SPI_CPHA) + sspcr0 |= SSPCR0_SPH; + if (mode & SPI_CPOL) + sspcr0 |= SSPCR0_SPO; + + slave->sspcr0 = sspcr0; + slave->sspcpsr = cpsr; + return 0; + } + } + } + + return -1; +} + +void spi_set_speed(struct spi_slave *slave, unsigned int hz) +{ + struct ep93xx_spi_slave *as = to_ep93xx_spi(slave); + + unsigned int mode = 0; + if (as->sspcr0 & SSPCR0_SPH) + mode |= SPI_CPHA; + if (as->sspcr0 & SSPCR0_SPO) + mode |= SPI_CPOL; + + ep93xx_spi_init_hw(hz, mode, as); +} + +struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs, + unsigned int max_hz, unsigned int mode) +{ + struct ep93xx_spi_slave *as; + + if (!spi_cs_is_valid(bus, cs)) + return NULL; + + as = spi_alloc_slave(struct ep93xx_spi_slave, bus, cs); + if (!as) + return NULL; + + if (ep93xx_spi_init_hw(max_hz, mode, as)) { + free(as); + return NULL; + } + + return &as->slave; +} + +void spi_free_slave(struct spi_slave *slave) +{ + struct ep93xx_spi_slave *as = to_ep93xx_spi(slave); + + free(as); +} + +int spi_claim_bus(struct spi_slave *slave) +{ + struct ep93xx_spi_slave *as = to_ep93xx_spi(slave); + + /* Enable the SPI hardware */ + ep93xx_spi_write_u8(SSPCR1, SSPCR1_SSE); + + + ep93xx_spi_write_u8(SSPCPSR, as->sspcpsr); + ep93xx_spi_write_u16(SSPCR0, as->sspcr0); + + debug("Select CS:%d SSPCPSR=%02x SSPCR0=%04x\n", + slave->cs, as->sspcpsr, as->sspcr0); + return 0; +} + +void spi_release_bus(struct spi_slave *slave) +{ + /* Disable the SPI hardware */ + ep93xx_spi_write_u8(SSPCR1, 0); +} + +int spi_xfer(struct spi_slave *slave, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + unsigned int len_tx; + unsigned int len_rx; + unsigned int len; + u32 status; + const u8 *txp = dout; + u8 *rxp = din; + u8 value; + + debug("spi_xfer: slave %u:%u dout %p din %p bitlen %u\n", + slave->bus, slave->cs, (uint *)dout, (uint *)din, bitlen); + + + if (bitlen == 0) + /* Finish any previously submitted transfers */ + goto out; + + if (bitlen % 8) { + /* Errors always terminate an ongoing transfer */ + flags |= SPI_XFER_END; + goto out; + } + + len = bitlen / 8; + + + if (flags & SPI_XFER_BEGIN) { + /* Empty RX FIFO */ + while ((ep93xx_spi_read_u8(SSPSR) & SSPSR_RNE)) + ep93xx_spi_read_u8(SSPDR); + + spi_cs_activate(slave); + } + + for (len_tx = 0, len_rx = 0; len_rx < len; ) { + status = ep93xx_spi_read_u8(SSPSR); + + if ((len_tx < len) && (status & SSPSR_TNF)) { + if (txp) + value = *txp++; + else + value = 0xff; + + ep93xx_spi_write_u8(SSPDR, value); + len_tx++; + } + + if (status & SSPSR_RNE) { + value = ep93xx_spi_read_u8(SSPDR); + + if (rxp) + *rxp++ = value; + len_rx++; + } + } + +out: + if (flags & SPI_XFER_END) { + /* + * Wait until the transfer is completely done before + * we deactivate CS. + */ + do { + status = ep93xx_spi_read_u8(SSPSR); + } while (status & SSPSR_BSY); + + spi_cs_deactivate(slave); + } + + return 0; +} |