diff options
Diffstat (limited to 'src/soc/samsung')
-rw-r--r-- | src/soc/samsung/exynos5420/spi.c | 326 | ||||
-rw-r--r-- | src/soc/samsung/exynos5420/spi.h | 6 |
2 files changed, 129 insertions, 203 deletions
diff --git a/src/soc/samsung/exynos5420/spi.c b/src/soc/samsung/exynos5420/spi.c index a2bbbb35b7..c8661e1852 100644 --- a/src/soc/samsung/exynos5420/spi.c +++ b/src/soc/samsung/exynos5420/spi.c @@ -23,6 +23,7 @@ #include <stdlib.h> #include <assert.h> #include <spi_flash.h> +#include <string.h> #include "cpu.h" #include "spi.h" @@ -38,9 +39,7 @@ struct exynos_spi_slave { struct spi_slave slave; struct exynos_spi *regs; - unsigned int fifo_size; - uint8_t half_duplex; - uint8_t frame_header; /* header byte to detect in half-duplex mode. */ + int initialized; }; /* TODO(hungte) Move the SPI param list to per-board configuration, probably @@ -55,17 +54,12 @@ static struct exynos_spi_slave exynos_spi_slaves[3] = { { .slave = { .bus = 1, .rw = SPI_READ_FLAG, }, .regs = (void *)EXYNOS5_SPI1_BASE, - .fifo_size = 64, - .half_duplex = 0, }, // SPI 2 { .slave = { .bus = 2, .rw = SPI_READ_FLAG | SPI_WRITE_FLAG, }, .regs = (void *)EXYNOS5_SPI2_BASE, - .fifo_size = 64, - .half_duplex = 1, - .frame_header = 0xec, }, }; @@ -74,15 +68,69 @@ static inline struct exynos_spi_slave *to_exynos_spi(struct spi_slave *slave) return container_of(slave, struct exynos_spi_slave, slave); } +static void spi_sw_reset(struct exynos_spi *regs, int word) +{ + const uint32_t orig_mode_cfg = readl(®s->mode_cfg); + uint32_t mode_cfg = orig_mode_cfg; + const uint32_t orig_swap_cfg = readl(®s->swap_cfg); + uint32_t swap_cfg = orig_swap_cfg; + + mode_cfg &= ~(SPI_MODE_CH_WIDTH_MASK | SPI_MODE_BUS_WIDTH_MASK); + if (word) { + mode_cfg |= SPI_MODE_CH_WIDTH_WORD | SPI_MODE_BUS_WIDTH_WORD; + swap_cfg |= SPI_RX_SWAP_EN | + SPI_RX_BYTE_SWAP | + SPI_RX_HWORD_SWAP | + SPI_TX_SWAP_EN | + SPI_TX_BYTE_SWAP | + SPI_TX_HWORD_SWAP; + } else { + mode_cfg |= SPI_MODE_CH_WIDTH_BYTE | SPI_MODE_BUS_WIDTH_BYTE; + swap_cfg = 0; + } + + if (mode_cfg != orig_mode_cfg) + writel(mode_cfg, ®s->mode_cfg); + if (swap_cfg != orig_swap_cfg) + writel(swap_cfg, ®s->swap_cfg); + + clrbits_le32(®s->ch_cfg, SPI_RX_CH_ON | SPI_TX_CH_ON); + setbits_le32(®s->ch_cfg, SPI_CH_RST); + clrbits_le32(®s->ch_cfg, SPI_CH_RST); + setbits_le32(®s->ch_cfg, SPI_RX_CH_ON | SPI_TX_CH_ON); +} + void spi_init(void) { - printk(BIOS_INFO, "Exynos SPI driver initiated.\n"); +} + +static void exynos_spi_init(struct exynos_spi *regs) +{ + // Set FB_CLK_SEL. + writel(SPI_FB_DELAY_180, ®s->fb_clk); + // CPOL: Active high. + clrbits_le32(®s->ch_cfg, SPI_CH_CPOL_L); + + // Clear rx and tx channel if set priveously. + clrbits_le32(®s->ch_cfg, SPI_RX_CH_ON | SPI_TX_CH_ON); + + setbits_le32(®s->swap_cfg, + SPI_RX_SWAP_EN | SPI_RX_BYTE_SWAP | SPI_RX_HWORD_SWAP); + clrbits_le32(®s->ch_cfg, SPI_CH_HS_EN); + + // Do a soft reset, which will also enable both channels. + spi_sw_reset(regs, 1); } struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs) { ASSERT(bus >= 0 && bus < 3); - return &(exynos_spi_slaves[bus].slave); + struct exynos_spi_slave *eslave = &exynos_spi_slaves[bus]; + if (!eslave->initialized) { + exynos_spi_init(eslave->regs); + eslave->initialized = 1; + } + return &eslave->slave; } int spi_cs_is_valid(unsigned int bus, unsigned int cs) @@ -103,237 +151,111 @@ void spi_cs_deactivate(struct spi_slave *slave) setbits_le32(®s->cs_reg, SPI_SLAVE_SIG_INACT); } -static inline void exynos_spi_soft_reset(struct exynos_spi *regs) +int spi_claim_bus(struct spi_slave *slave) { - /* The soft reset clears only FIFO and status register. - * All special function registers are not changed. */ - setbits_le32(®s->ch_cfg, SPI_CH_RST); - clrbits_le32(®s->ch_cfg, SPI_CH_RST); + spi_cs_activate(slave); + return 0; } -static inline void exynos_spi_flush_fifo(struct exynos_spi *regs) +static void spi_transfer(struct exynos_spi *regs, void *in, const void *out, + u32 size) { - /* - * Flush spi tx, rx fifos and reset the SPI controller - * and clear rx/tx channel - */ - clrbits_le32(®s->ch_cfg, SPI_RX_CH_ON | SPI_TX_CH_ON); - clrbits_le32(®s->ch_cfg, SPI_CH_HS_EN); - exynos_spi_soft_reset(regs); - setbits_le32(®s->ch_cfg, SPI_RX_CH_ON | SPI_TX_CH_ON); -} + u8 *inb = in; + const u8 *outb = out; -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); - } + int width = (size % 4) ? 1 : 4; - exynos_spi_soft_reset(regs); + while (size) { + int packets = size / width; + // The packet count field is 16 bits wide. + packets = MIN(packets, (1 << 16) - 1); - if (count) { - ASSERT(count < (1 << 16)); - writel(count | SPI_PACKET_CNT_EN, ®s->pkt_cnt); - } else { - writel(0, ®s->pkt_cnt); - } -} + int out_bytes, in_bytes; + out_bytes = in_bytes = packets * width; -static 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: transfer mode decreased to 1B\n", - __func__); - step = 1; - } else { - step = sizeof(uint32_t); - } + spi_sw_reset(regs, width == 4); + writel(packets | SPI_PACKET_CNT_EN, ®s->pkt_cnt); + + while (out_bytes || in_bytes) { + uint32_t spi_sts = readl(®s->spi_sts); + int rx_lvl = ((spi_sts >> 15) & 0x1ff); + int tx_lvl = ((spi_sts >> 6) & 0x1ff); - 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++; + if (tx_lvl < 32 && tx_lvl < out_bytes) { + uint32_t data = 0xffffffff; + + if (outb) { + memcpy(&data, outb, width); + outb += width; } - tx_bytes -= step; - } else { - temp = -1; + writel(data, ®s->tx_data); + + out_bytes -= width; } - 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; + if (rx_lvl >= width) { + uint32_t data = readl(®s->rx_data); + + if (inb) { + memcpy(inb, &data, width); + inb += width; } - break; /* Restart the outer loop. */ - } - if (step == sizeof(uint32_t)) { - *((uint32_t *)rxp) = temp; - rxp += sizeof(uint32_t); - } else { - *rxp++ = temp; + + in_bytes -= width; } - rx_bytes -= step; } + + size -= packets * width; } - return 0; } -int spi_claim_bus(struct spi_slave *slave) +int spi_xfer(struct spi_slave *slave, const void *dout, unsigned int bytes_out, + void *din, unsigned int bytes_in) { - struct exynos_spi_slave *espi = to_exynos_spi(slave); - struct exynos_spi *regs = espi->regs; + struct exynos_spi *regs = to_exynos_spi(slave)->regs; - exynos_spi_flush_fifo(regs); + if (bytes_out && bytes_in) { + u32 min_size = MIN(bytes_out, bytes_in); - // Select Active High Clock, Format A (SCP 30.2.1.8). - clrbits_le32(®s->ch_cfg, SPI_CH_CPOL_L | SPI_CH_CPHA_B); + spi_transfer(regs, din, dout, min_size); - // Set FeedBack Clock Selection. - writel(SPI_FB_DELAY_180, ®s->fb_clk); + bytes_out -= min_size; + bytes_in -= min_size; - // HIGH speed is required for Tx/Rx to work in 50MHz (SCP 30.2.1.6). - if (espi->half_duplex) { - clrbits_le32(®s->ch_cfg, SPI_CH_HS_EN); - printk(BIOS_DEBUG, "%s: LOW speed.\n", __func__); - } else { - setbits_le32(®s->ch_cfg, SPI_CH_HS_EN); - printk(BIOS_DEBUG, "%s: HIGH speed.\n", __func__); + din = (uint8_t *)din + min_size; + dout = (const uint8_t *)dout + min_size; } + + if (bytes_in) + spi_transfer(regs, din, NULL, bytes_in); + else if (bytes_out) + spi_transfer(regs, NULL, dout, bytes_out); + return 0; } -int spi_xfer(struct spi_slave *slave, const void *dout, unsigned int out_bytes, - void *din, unsigned int in_bytes) +void spi_release_bus(struct spi_slave *slave) { - uint8_t *out_ptr = (uint8_t *)dout, *in_ptr = (uint8_t *)din; - int offset, todo, len; - int ret = 0; - - len = MAX(out_bytes, in_bytes); - - /* - * Exynos SPI limits each transfer to (2^16-1=65535) bytes. To keep - * things simple (especially for word-width transfer mode), allow a - * maximum of (2^16-4=65532) bytes. We could allow more in word mode, - * but the performance difference is small. - */ - spi_cs_activate(slave); - for (offset = 0; !ret && (offset < len); offset += todo) { - todo = min(len - offset, (1 << 16) - 4); - ret = spi_rx_tx(slave, in_ptr, MIN(in_bytes, todo), out_ptr, - MIN(out_bytes, todo)); - // Adjust remaining bytes and pointers. - if (in_bytes >= todo) { - in_bytes -= todo; - in_ptr += todo; - } else { - in_bytes = 0; - in_ptr = NULL; - } - if (out_bytes >= todo) { - out_bytes -= todo; - out_ptr += todo; - } else { - out_bytes = 0; - out_ptr = NULL; - } - } spi_cs_deactivate(slave); - - return ret; } static int exynos_spi_read(struct spi_slave *slave, void *dest, uint32_t len, uint32_t off) { struct exynos_spi *regs = to_exynos_spi(slave)->regs; - int rv; + u32 command; + spi_claim_bus(slave); - // TODO(hungte) Merge the "read address" command into spi_xfer calls - // (full-duplex mode). - - spi_cs_activate(slave); - - // Specify read address (in word-width mode). + // Send address. ASSERT(off < (1 << 24)); - exynos_spi_request_bytes(regs, sizeof(off), sizeof(off)); - writel(htonl((SF_READ_DATA_CMD << 24) | off), ®s->tx_data); - while (!(readl(®s->spi_sts) & SPI_ST_TX_DONE)) { - /* Wait for TX done */ - } - - // Now, safe to transfer. - rv = spi_xfer(slave, NULL, 0, dest, len * 8); - spi_cs_deactivate(slave); + command = htonl(SF_READ_DATA_CMD << 24 | off); + spi_transfer(regs, NULL, &command, sizeof(command)); - return (rv == 0) ? len : -1; -} + // Read the data. + spi_transfer(regs, dest, NULL, len); + spi_release_bus(slave); -void spi_release_bus(struct spi_slave *slave) -{ - struct exynos_spi *regs = to_exynos_spi(slave)->regs; - /* Reset swap mode to make sure no one relying on default values (Ex, - * payload or kernel) will go wrong. */ - clrbits_le32(®s->mode_cfg, (SPI_MODE_CH_WIDTH_WORD | - SPI_MODE_BUS_WIDTH_WORD)); - writel(0, ®s->swap_cfg); - exynos_spi_flush_fifo(regs); + return len; } // SPI as CBFS media. diff --git a/src/soc/samsung/exynos5420/spi.h b/src/soc/samsung/exynos5420/spi.h index 20c2adb087..78cca6f416 100644 --- a/src/soc/samsung/exynos5420/spi.h +++ b/src/soc/samsung/exynos5420/spi.h @@ -57,8 +57,12 @@ check_member(exynos_spi, fb_clk, 0x2c); #define SPI_TX_CH_ON (1 << 0) /* SPI_MODECFG */ -#define SPI_MODE_CH_WIDTH_WORD (0x2 << 29) +#define SPI_MODE_BUS_WIDTH_BYTE (0x0 << 17) #define SPI_MODE_BUS_WIDTH_WORD (0x2 << 17) +#define SPI_MODE_BUS_WIDTH_MASK (0x3 << 17) +#define SPI_MODE_CH_WIDTH_BYTE (0x0 << 29) +#define SPI_MODE_CH_WIDTH_WORD (0x2 << 29) +#define SPI_MODE_CH_WIDTH_MASK (0x3 << 29) /* SPI_CSREG */ #define SPI_SLAVE_SIG_INACT (1 << 0) |