diff options
Diffstat (limited to 'src/soc/sifive/fu540/spi.c')
-rw-r--r-- | src/soc/sifive/fu540/spi.c | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/src/soc/sifive/fu540/spi.c b/src/soc/sifive/fu540/spi.c new file mode 100644 index 0000000000..6bf1700767 --- /dev/null +++ b/src/soc/sifive/fu540/spi.c @@ -0,0 +1,254 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2018 SiFive, Inc + * Copyright (C) 2019 HardenedLinux + * + * 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. + */ + +#include <arch/mmio.h> +#include <soc/spi.h> +#include <soc/clock.h> +#include <soc/addressmap.h> + +#include "spi_internal.h" + +static struct spi_ctrl *spictrls[] = { + (struct spi_ctrl *)FU540_QSPI0, + (struct spi_ctrl *)FU540_QSPI1, + (struct spi_ctrl *)FU540_QSPI2 +}; + + +/** + * Wait until SPI is ready for transmission and transmit byte. + */ +static void spi_tx(volatile struct spi_ctrl *spictrl, uint8_t in) +{ +#if __riscv_atomic + int32_t r; + do { + asm volatile ( + "amoor.w %0, %2, %1\n" + : "=r" (r), "+A" (spictrl->txdata.raw_bits) + : "r" (in) + ); + } while (r < 0); +#else + while ((int32_t) spictrl->txdata.raw_bits < 0) + ; + spictrl->txdata.data = in; +#endif +} + + +/** + * Wait until SPI receive queue has data and read byte. + */ +static uint8_t spi_rx(volatile struct spi_ctrl *spictrl) +{ + int32_t out; + while ((out = (int32_t) spictrl->rxdata.raw_bits) < 0) + ; + return (uint8_t) out; +} + +static int spi_xfer_(const struct spi_slave *slave, + const void *dout, size_t bytesout, + void *din, size_t bytesin) +{ + struct spi_ctrl *spictrl = spictrls[slave->bus]; + spi_reg_fmt fmt; + fmt.raw_bits = read32(&spictrl->fmt.raw_bits); + if (fmt.proto == FU540_SPI_PROTO_S) { + /* working in full-duplex mode + * receiving data needs to be triggered by sending data */ + while (bytesout || bytesin) { + uint8_t in, out = 0; + if (bytesout) { + out = *(uint8_t *)dout++; + bytesout--; + } + spi_tx(spictrl, out); + in = spi_rx(spictrl); + if (bytesin) { + *(uint8_t *)din++ = in; + bytesin--; + } + } + } else { + /* Working in half duplex + * send and receive can be done separately */ + if (dout && din) + return -1; + + if (dout) { + while (bytesout) { + spi_tx(spictrl, *(uint8_t *)dout++); + bytesout--; + } + } + + if (din) { + while (bytesin) { + *(uint8_t *)din++ = spi_rx(spictrl); + bytesin--; + } + } + } + return 0; +} + +static int spi_setup_(const struct spi_slave *slave) +{ + spi_reg_sckmode sckmode; + spi_reg_csmode csmode; + spi_reg_fmt fmt; + + if ((slave->bus > 2) || (slave->cs != 0)) + return -1; + + struct spi_ctrl *spictrl = spictrls[slave->bus]; + + write32(&spictrl->sckdiv, spi_min_clk_divisor(clock_get_tlclk_khz(), + 10000)); + + sckmode.raw_bits = 0; + sckmode.pha = FU540_SPI_PHA_LOW; + sckmode.pol = FU540_SPI_POL_LEADING; + write32(&spictrl->sckmode.raw_bits, sckmode.raw_bits); + + csmode.raw_bits = 0; + csmode.mode = FU540_SPI_CSMODE_AUTO; + write32(&spictrl->csmode.raw_bits, csmode.raw_bits); + + fmt.raw_bits = 0; + fmt.proto = FU540_SPI_PROTO_S; + fmt.endian = FU540_SPI_ENDIAN_BIG; + fmt.dir = 1; + fmt.len = 8; + write32(&spictrl->fmt.raw_bits, fmt.raw_bits); + + return 0; +} + +struct spi_ctrlr fu540_spi_ctrlr = { + .xfer = spi_xfer_, + .setup = spi_setup_, +}; + +const struct spi_ctrlr_buses spi_ctrlr_bus_map[] = { + { + .bus_start = 0, + .bus_end = 2, + .ctrlr = &fu540_spi_ctrlr, + } +}; + +const size_t spi_ctrlr_bus_map_count = ARRAY_SIZE(spi_ctrlr_bus_map); + +int fu540_spi_setup(unsigned int bus, unsigned int cs, + struct spi_slave *slave, + struct fu540_spi_config *config) +{ + spi_reg_sckmode sckmode; + spi_reg_csmode csmode; + spi_reg_fmt fmt; + + if ((bus > 2) || (cs != 0)) + return -1; + + if ((config->pha > 1) + || (config->pol > 1) + || (config->protocol > 2) + || (config->endianness > 1) + || (config->bits_per_frame > 8)) + return -1; + + slave->bus = bus; + slave->cs = cs; + slave->ctrlr = &fu540_spi_ctrlr; + + struct spi_ctrl *spictrl = spictrls[slave->bus]; + + write32(&spictrl->sckdiv, spi_min_clk_divisor(clock_get_tlclk_khz(), + config->freq / 1000)); + + sckmode.raw_bits = 0; + sckmode.pha = config->pha; + sckmode.pol = config->pol; + write32(&spictrl->sckmode.raw_bits, sckmode.raw_bits); + + csmode.raw_bits = 0; + csmode.mode = FU540_SPI_CSMODE_AUTO; + write32(&spictrl->csmode.raw_bits, csmode.raw_bits); + + fmt.raw_bits = 0; + fmt.proto = config->protocol; + fmt.endian = config->endianness; + fmt.dir = 1; + fmt.len = config->bits_per_frame; + write32(&spictrl->fmt.raw_bits, fmt.raw_bits); + + return 0; +} + +int fu540_spi_mmap( + const struct spi_slave *slave, + const struct fu540_spi_mmap_config *config) +{ + spi_reg_fctrl fctrl; + spi_reg_ffmt ffmt; + + if (slave->bus > 2) + return -1; + + if ((config->cmd_en > 1) + || (config->addr_len > 4) + || (config->pad_cnt > 15) + || (config->cmd_proto > 2) + || (config->addr_proto > 2) + || (config->data_proto > 2) + || (config->cmd_code > 255) + || (config->pad_code > 255)) + return -1; + + struct spi_ctrl *spictrl = spictrls[slave->bus]; + + /* disable direct memory-mapped spi flash mode */ + fctrl.raw_bits = 0; + fctrl.en = 0; + write32(&spictrl->fctrl.raw_bits, fctrl.raw_bits); + + /* reset spi flash chip */ + spi_tx(spictrl, 0x66); + spi_tx(spictrl, 0x99); + + /* Pass the information of the flash read operation to the spi + * controller */ + ffmt.raw_bits = 0; + ffmt.cmd_en = config->cmd_en; + ffmt.addr_len = config->addr_len; + ffmt.pad_cnt = config->pad_cnt; + ffmt.command_proto = config->cmd_proto; + ffmt.addr_proto = config->addr_proto; + ffmt.data_proto = config->data_proto; + ffmt.command_code = config->cmd_code; + ffmt.pad_code = config->pad_code; + write32(&spictrl->ffmt.raw_bits, ffmt.raw_bits); + + /* enable direct memory-mapped spi flash mode */ + fctrl.raw_bits = 0; + fctrl.en = 1; + write32(&spictrl->fctrl.raw_bits, fctrl.raw_bits); + + return 0; +} |