diff options
author | Hung-Te Lin <hungte@chromium.org> | 2013-09-27 12:45:45 +0800 |
---|---|---|
committer | Isaac Christensen <isaac.christensen@se-eng.com> | 2014-08-26 17:55:18 +0200 |
commit | 22d0ca0ceb802675cdcab1472b8477066f729373 (patch) | |
tree | 79e2e38a2c6b34125f48b05cfd7f9ef3c88c833d /src/soc/samsung/exynos5420/spi.c | |
parent | b123e0d3345554d7e93361bb4511a53bc95d41a1 (diff) | |
download | coreboot-22d0ca0ceb802675cdcab1472b8477066f729373.tar.xz |
armv7: Move Exynos from 'cpu' to 'soc'.
The Exynos family and most ARM products are SoC, not just CPU.
We used to put ARM code in src/cpu to avoid polluting the code base for what was
essentially an experiment at the time. Now that it's past the experimental phase
and we're going to see more SoCs (including intel/baytrail) in coreboot.
Change-Id: I5ea1f822664244edf5f77087bc8018d7c535f81c
Reviewed-on: https://chromium-review.googlesource.com/170891
Tested-by: Hung-Te Lin <hungte@chromium.org>
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
Reviewed-by: Ronald Minnich <rminnich@chromium.org>
Commit-Queue: Hung-Te Lin <hungte@chromium.org>
(cherry picked from commit c8bb8fe0b20be37465f93c738d80e7e43033670a)
Signed-off-by: Isaac Christensen <isaac.christensen@se-eng.com>
Reviewed-on: http://review.coreboot.org/6739
Tested-by: build bot (Jenkins)
Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
Diffstat (limited to 'src/soc/samsung/exynos5420/spi.c')
-rw-r--r-- | src/soc/samsung/exynos5420/spi.c | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/src/soc/samsung/exynos5420/spi.c b/src/soc/samsung/exynos5420/spi.c new file mode 100644 index 0000000000..c6c08e925c --- /dev/null +++ b/src/soc/samsung/exynos5420/spi.c @@ -0,0 +1,411 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2011 Samsung Electronics + * Copyright 2013 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <console/console.h> +#include <arch/io.h> +#include <stdlib.h> +#include <assert.h> +#include <spi_flash.h> + +#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 +# define DEBUG_SPI(x,...) +#endif + +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. */ +}; + +/* TODO(hungte) Move the SPI param list to per-board configuration, probably + * Kconfig or mainboard.c */ +static struct exynos_spi_slave exynos_spi_slaves[3] = { + // SPI 0 + { + .slave = { .bus = 0, }, + .regs = (void *)EXYNOS5_SPI0_BASE, + }, + // SPI 1 + { + .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, + }, +}; + +static inline struct exynos_spi_slave *to_exynos_spi(struct spi_slave *slave) +{ + return container_of(slave, struct exynos_spi_slave, slave); +} + +void spi_init(void) +{ + printk(BIOS_INFO, "Exynos SPI driver initiated.\n"); +} + +struct spi_slave *spi_setup_slave(unsigned int bus, unsigned int cs) +{ + ASSERT(bus >= 0 && bus < 3); + return &(exynos_spi_slaves[bus].slave); +} + +int spi_cs_is_valid(unsigned int bus, unsigned int cs) +{ + return bus > 0 && bus < 3; +} + +void spi_cs_activate(struct spi_slave *slave) +{ + struct exynos_spi *regs = to_exynos_spi(slave)->regs; + // TODO(hungte) Add some delay if too many transactions happen at once. + clrbits_le32(®s->cs_reg, SPI_SLAVE_SIG_INACT); +} + +void spi_cs_deactivate(struct spi_slave *slave) +{ + struct exynos_spi *regs = to_exynos_spi(slave)->regs; + setbits_le32(®s->cs_reg, SPI_SLAVE_SIG_INACT); +} + +static inline void exynos_spi_soft_reset(struct exynos_spi *regs) +{ + /* 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); +} + +static inline void exynos_spi_flush_fifo(struct exynos_spi *regs) +{ + /* + * 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); +} + +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); + } +} + +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: 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; +} + +int spi_claim_bus(struct spi_slave *slave) +{ + struct exynos_spi_slave *espi = to_exynos_spi(slave); + struct exynos_spi *regs = espi->regs; + + exynos_spi_flush_fifo(regs); + + // Select Active High Clock, Format A (SCP 30.2.1.8). + clrbits_le32(®s->ch_cfg, SPI_CH_CPOL_L | SPI_CH_CPHA_B); + + // Set FeedBack Clock Selection. + writel(SPI_FB_DELAY_180, ®s->fb_clk); + + // 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__); + } + return 0; +} + +int spi_xfer(struct spi_slave *slave, const void *dout, unsigned int out_bytes, + void *din, unsigned int in_bytes) +{ + 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; + + // 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). + 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); + + return (rv == 0) ? len : -1; +} + +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); +} + +// SPI as CBFS media. +struct exynos_spi_media { + struct spi_slave *slave; + struct cbfs_simple_buffer buffer; +}; + +static int exynos_spi_cbfs_open(struct cbfs_media *media) +{ + struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; + DEBUG_SPI("exynos_spi_cbfs_open\n"); + return spi_claim_bus(spi->slave); +} + +static int exynos_spi_cbfs_close(struct cbfs_media *media) +{ + struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; + DEBUG_SPI("exynos_spi_cbfs_close\n"); + spi_release_bus(spi->slave); + return 0; +} + +static size_t exynos_spi_cbfs_read(struct cbfs_media *media, void *dest, + size_t offset, size_t count) +{ + struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; + int bytes; + DEBUG_SPI("exynos_spi_cbfs_read(%u)\n", count); + bytes = exynos_spi_read(spi->slave, dest, count, offset); + return bytes; +} + +static void *exynos_spi_cbfs_map(struct cbfs_media *media, size_t offset, + size_t count) +{ + struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; + DEBUG_SPI("exynos_spi_cbfs_map\n"); + // exynos: spi_rx_tx may work in 4 byte-width-transmission mode and + // requires buffer memory address to be aligned. + if (count % 4) + count += 4 - (count % 4); + return cbfs_simple_buffer_map(&spi->buffer, media, offset, count); +} + +static void *exynos_spi_cbfs_unmap(struct cbfs_media *media, + const void *address) +{ + struct exynos_spi_media *spi = (struct exynos_spi_media*)media->context; + DEBUG_SPI("exynos_spi_cbfs_unmap\n"); + return cbfs_simple_buffer_unmap(&spi->buffer, address); +} + +int initialize_exynos_spi_cbfs_media(struct cbfs_media *media, + void *buffer_address, + size_t buffer_size) +{ + // TODO Replace static variable to support multiple streams. + static struct exynos_spi_media context; + static struct exynos_spi_slave *eslave = &exynos_spi_slaves[1]; + DEBUG_SPI("initialize_exynos_spi_cbfs_media\n"); + + context.slave = &eslave->slave; + context.buffer.allocated = context.buffer.last_allocate = 0; + context.buffer.buffer = buffer_address; + context.buffer.size = buffer_size; + media->context = (void*)&context; + media->open = exynos_spi_cbfs_open; + media->close = exynos_spi_cbfs_close; + media->read = exynos_spi_cbfs_read; + media->map = exynos_spi_cbfs_map; + media->unmap = exynos_spi_cbfs_unmap; + + return 0; +} |