diff options
author | Hung-Te Lin <hungte@chromium.org> | 2013-06-26 20:29:06 +0800 |
---|---|---|
committer | Stefan Reinauer <stefan.reinauer@coreboot.org> | 2013-07-10 23:17:39 +0200 |
commit | 864420766ad85c8ed0dd98aefd8f527aeb506aa5 (patch) | |
tree | 7ea24b882fa020fbf83a4e933d9e28f702d8a5bc | |
parent | cab3621446542fadf67e9406c4ae39fb63a0536f (diff) |
armv7/exynos5420: Add output ability and half-duplex mode in SPI driver.
The SPI driver (exynos_spi_rx_tx) was implemented with only "read" ability and
only full-duplex mode. To communicate with devices like ChromeOS EC, we need
both output (tx) and half-duplex (searching frame header) features.
This commit adds a spi_rx_tx that can handle all cases we need.
Change-Id: I6aba3839eb0711d49c143dc0620245c0dfe782d8
Signed-off-by: Hung-Te Lin <hungte@chromium.org>
Signed-off-by: Gabe Black <gabeblack@chromium.org>
Reviewed-on: http://review.coreboot.org/3713
Tested-by: build bot (Jenkins)
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
-rw-r--r-- | src/cpu/samsung/exynos5420/spi.c | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/src/cpu/samsung/exynos5420/spi.c b/src/cpu/samsung/exynos5420/spi.c index 6637aad9e8..dddbec5dfc 100644 --- a/src/cpu/samsung/exynos5420/spi.c +++ b/src/cpu/samsung/exynos5420/spi.c @@ -27,6 +27,8 @@ #include "cpu.h" #include "spi.h" +#define EXYNOS_SPI_MAX_TRANSFER_BYTES (65535) + #if defined(CONFIG_DEBUG_SPI) && CONFIG_DEBUG_SPI # define DEBUG_SPI(x,...) printk(BIOS_DEBUG, "EXYNOS_SPI: " x) #else @@ -122,6 +124,119 @@ static inline void exynos_spi_flush_fifo(struct exynos_spi *regs) setbits_le32(®s->ch_cfg, SPI_RX_CH_ON | SPI_TX_CH_ON); } +static void exynos_spi_request_bytes(struct exynos_spi *regs, int count, + int width) +{ + uint32_t mode_word = SPI_MODE_CH_WIDTH_WORD | SPI_MODE_BUS_WIDTH_WORD, + swap_word = (SPI_TX_SWAP_EN | SPI_RX_SWAP_EN | + SPI_TX_BYTE_SWAP | SPI_RX_BYTE_SWAP | + SPI_TX_HWORD_SWAP | SPI_RX_HWORD_SWAP); + + /* For word address we need to swap bytes */ + if (width == sizeof(uint32_t)) { + setbits_le32(®s->mode_cfg, mode_word); + setbits_le32(®s->swap_cfg, swap_word); + count /= width; + } else { + /* Select byte access and clear the swap configuration */ + clrbits_le32(®s->mode_cfg, mode_word); + writel(0, ®s->swap_cfg); + } + + exynos_spi_soft_reset(regs); + + if (count) { + ASSERT(count < (1 << 16)); + writel(count | SPI_PACKET_CNT_EN, ®s->pkt_cnt); + } else { + writel(0, ®s->pkt_cnt); + } +} + +int spi_rx_tx(struct spi_slave *slave, uint8_t *rxp, int rx_bytes, + const uint8_t *txp, int tx_bytes); +int spi_rx_tx(struct spi_slave *slave, uint8_t *rxp, int rx_bytes, + const uint8_t *txp, int tx_bytes) +{ + struct exynos_spi_slave *espi = to_exynos_spi(slave); + struct exynos_spi *regs = espi->regs; + + int step; + int todo = MAX(rx_bytes, tx_bytes); + int wait_for_frame_header = espi->half_duplex; + + ASSERT(todo < EXYNOS_SPI_MAX_TRANSFER_BYTES); + + /* Select transfer mode. */ + if (espi->half_duplex) { + step = 1; + } else if ((rx_bytes | tx_bytes | (uintptr_t)rxp |(uintptr_t)txp) & 3) { + printk(BIOS_CRIT, "%s: WARNING: tranfer mode decreased to 1B\n", + __func__); + step = 1; + } else { + step = sizeof(uint32_t); + } + + exynos_spi_request_bytes(regs, espi->half_duplex ? 0 : todo, step); + + /* Note: Some device, like ChromeOS EC, tries to work in half-duplex + * mode and sends a large amount of data (larger than FIFO size). + * Printing lots of debug messages or doing extra delay in the loop + * below may cause rx buffer to overflow and getting unexpected data + * error. + */ + while (rx_bytes || tx_bytes) { + int temp; + uint32_t spi_sts = readl(®s->spi_sts); + int rx_lvl = (spi_sts >> SPI_RX_LVL_OFFSET) & SPI_FIFO_LVL_MASK, + tx_lvl = (spi_sts >> SPI_TX_LVL_OFFSET) & SPI_FIFO_LVL_MASK; + int min_tx = ((tx_bytes || !espi->half_duplex) ? + (espi->fifo_size / 2) : 1); + + // TODO(hungte) Abort if timeout happens in half-duplex mode. + + /* + * Don't completely fill the txfifo, since we don't want our + * rxfifo to overflow, and it may already contain data. + */ + while (tx_lvl < min_tx) { + if (tx_bytes) { + if (step == sizeof(uint32_t)) { + temp = *((uint32_t *)txp); + txp += sizeof(uint32_t); + } else { + temp = *txp++; + } + tx_bytes -= step; + } else { + temp = -1; + } + writel(temp, ®s->tx_data); + tx_lvl += step; + } + + while ((rx_lvl >= step) && rx_bytes) { + temp = readl(®s->rx_data); + rx_lvl -= step; + if (wait_for_frame_header) { + if ((temp & 0xff) == espi->frame_header) { + wait_for_frame_header = 0; + } + break; /* Restart the outer loop. */ + } + if (step == sizeof(uint32_t)) { + *((uint32_t *)rxp) = temp; + rxp += sizeof(uint32_t); + } else { + *rxp++ = temp; + } + rx_bytes -= step; + } + } + return 0; +} + static void exynos_spi_rx_tx(struct exynos_spi *regs, int todo, void *dinp, void const *doutp, int i) { |