summaryrefslogtreecommitdiff
path: root/src/soc/sifive/fu540/spi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/soc/sifive/fu540/spi.c')
-rw-r--r--src/soc/sifive/fu540/spi.c254
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;
+}