summaryrefslogtreecommitdiff
path: root/src/soc/sifive/fu540
diff options
context:
space:
mode:
Diffstat (limited to 'src/soc/sifive/fu540')
-rw-r--r--src/soc/sifive/fu540/Makefile.inc6
-rw-r--r--src/soc/sifive/fu540/include/soc/spi.h83
-rw-r--r--src/soc/sifive/fu540/media.c25
-rw-r--r--src/soc/sifive/fu540/spi.c254
-rw-r--r--src/soc/sifive/fu540/spi_internal.h242
5 files changed, 582 insertions, 28 deletions
diff --git a/src/soc/sifive/fu540/Makefile.inc b/src/soc/sifive/fu540/Makefile.inc
index 4f62f3ed62..3c97c08191 100644
--- a/src/soc/sifive/fu540/Makefile.inc
+++ b/src/soc/sifive/fu540/Makefile.inc
@@ -15,13 +15,13 @@ ifeq ($(CONFIG_SOC_SIFIVE_FU540),y)
bootblock-y += uart.c
bootblock-y += clint.c
-bootblock-y += media.c
+bootblock-y += spi.c
bootblock-y += bootblock.c
bootblock-y += clock.c
romstage-y += uart.c
romstage-y += clint.c
-romstage-y += media.c
+romstage-y += spi.c
romstage-y += sdram.c
romstage-y += cbmem.c
romstage-y += otp.c
@@ -29,7 +29,7 @@ romstage-y += clock.c
ramstage-y += uart.c
ramstage-y += clint.c
-ramstage-y += media.c
+ramstage-y += spi.c
ramstage-y += sdram.c
ramstage-y += cbmem.c
ramstage-y += otp.c
diff --git a/src/soc/sifive/fu540/include/soc/spi.h b/src/soc/sifive/fu540/include/soc/spi.h
new file mode 100644
index 0000000000..543f9b2035
--- /dev/null
+++ b/src/soc/sifive/fu540/include/soc/spi.h
@@ -0,0 +1,83 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2018 SiFive, 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.
+ */
+
+#ifndef __SOC_SIFIVE_HIFIVE_U_SPI_H__
+#define __SOC_SIFIVE_HIFIVE_U_SPI_H__
+#include <spi-generic.h>
+
+/* Data Pins: MOSI MISO */
+#define FU540_SPI_PROTO_S 0
+/* Data Pins: DQ0 DQ1 */
+#define FU540_SPI_PROTO_D 1
+/* Data Pins: DQ0 DQ1 DQ2 DQ3 */
+#define FU540_SPI_PROTO_Q 2
+
+/* send MSB first */
+#define FU540_SPI_ENDIAN_BIG 0
+/* send LSB first */
+#define FU540_SPI_ENDIAN_LITTLE 1
+
+/* inactive state of SCK is logical 0 */
+#define FU540_SPI_PHA_LOW 0
+/* inactive state of SCK is logical 1 */
+#define FU540_SPI_PHA_HIGH 1
+
+/* data is sampled on leading edge */
+#define FU540_SPI_POL_LEADING 0
+/* data is sampled on trailing edge */
+#define FU540_SPI_POL_TRAILING 1
+
+struct fu540_spi_config {
+ /* speed of spi interface */
+ unsigned int freq;
+ /* serial clock phase */
+ unsigned int pha;
+ /* serial clock polarity */
+ unsigned int pol;
+ unsigned int protocol;
+ unsigned int endianness;
+ /* up to 8bits */
+ unsigned int bits_per_frame;
+};
+
+/* more detailed spi configuration */
+int fu540_spi_setup(unsigned int bus, unsigned int cs, struct spi_slave *slave,
+ struct fu540_spi_config *config);
+
+/* This structure is used to describe the read command of SPI FLASH. */
+struct fu540_spi_mmap_config {
+ /* enable sending of command */
+ unsigned int cmd_en;
+ /* number of address bytes (0-4) */
+ unsigned int addr_len;
+ /* number of dummy cycles */
+ unsigned int pad_cnt;
+ /* protocol for transmitting command */
+ unsigned int cmd_proto;
+ /* protocol for transmitting address and padding */
+ unsigned int addr_proto;
+ /* protocol for receiving data bytes */
+ unsigned int data_proto;
+ /* value of command byte */
+ unsigned int cmd_code;
+ /* first 8 bits to transmit during dummy cycles */
+ unsigned int pad_code;
+};
+
+int fu540_spi_mmap(
+ const struct spi_slave *slave,
+ const struct fu540_spi_mmap_config *config);
+
+#endif /* __SOC_SIFIVE_HIFIVE_U_SPI_H__ */
diff --git a/src/soc/sifive/fu540/media.c b/src/soc/sifive/fu540/media.c
deleted file mode 100644
index 7b9ccb0e3c..0000000000
--- a/src/soc/sifive/fu540/media.c
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * This file is part of the coreboot project.
- *
- * Copyright (C) 2018 Jonathan Neuschäfer
- *
- * 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 <boot_device.h>
-
-/* At 0x20000000: A 256MiB long memory-mapped view of the flash at QSPI0 */
-static struct mem_region_device mdev =
- MEM_REGION_DEV_RO_INIT((void *)0x20000000, CONFIG_ROM_SIZE);
-
-const struct region_device *boot_device_ro(void)
-{
- return &mdev.rdev;
-}
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;
+}
diff --git a/src/soc/sifive/fu540/spi_internal.h b/src/soc/sifive/fu540/spi_internal.h
new file mode 100644
index 0000000000..97094c1d8c
--- /dev/null
+++ b/src/soc/sifive/fu540/spi_internal.h
@@ -0,0 +1,242 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2018 SiFive, 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.
+ */
+#ifndef __SOC_SIFIVE_HIFIVE_U_SPI_INTERNAL_H__
+#define __SOC_SIFIVE_HIFIVE_U_SPI_INTERNAL_H__
+
+#include <stdint.h>
+
+#define _ASSERT_SIZEOF(type, size) _Static_assert( \
+ sizeof(type) == (size), \
+ #type " must be " #size " bytes wide")
+
+#define FU540_SPI_CSMODE_AUTO 0
+#define FU540_SPI_CSMODE_HOLD 2
+#define FU540_SPI_CSMODE_OFF 3
+
+typedef union {
+ struct {
+ uint32_t pha : 1;
+ uint32_t pol : 1;
+ uint32_t reserved : 30;
+ };
+ uint32_t raw_bits;
+} spi_reg_sckmode;
+_ASSERT_SIZEOF(spi_reg_sckmode, 4);
+
+
+typedef union {
+ struct {
+ uint32_t mode : 2;
+ uint32_t reserved : 30;
+ };
+ uint32_t raw_bits;
+} spi_reg_csmode;
+_ASSERT_SIZEOF(spi_reg_csmode, 4);
+
+
+typedef union {
+ struct {
+ uint32_t cssck : 8;
+ uint32_t reserved0 : 8;
+ uint32_t sckcs : 8;
+ uint32_t reserved1 : 8;
+ };
+ uint32_t raw_bits;
+} spi_reg_delay0;
+_ASSERT_SIZEOF(spi_reg_delay0, 4);
+
+
+typedef union {
+ struct {
+ uint32_t intercs : 8;
+ uint32_t reserved0 : 8;
+ uint32_t interxfr : 8;
+ uint32_t reserved1 : 8;
+ };
+ uint32_t raw_bits;
+} spi_reg_delay1;
+_ASSERT_SIZEOF(spi_reg_delay1, 4);
+
+
+typedef union {
+ struct {
+ uint32_t proto : 2;
+ uint32_t endian : 1;
+ uint32_t dir : 1;
+ uint32_t reserved0 : 12;
+ uint32_t len : 4;
+ uint32_t reserved1 : 12;
+ };
+ uint32_t raw_bits;
+} spi_reg_fmt;
+_ASSERT_SIZEOF(spi_reg_fmt, 4);
+
+
+typedef union {
+ struct {
+ uint32_t data : 8;
+ uint32_t reserved : 23;
+ uint32_t full : 1;
+ };
+ uint32_t raw_bits;
+} spi_reg_txdata;
+_ASSERT_SIZEOF(spi_reg_txdata, 4);
+
+
+typedef union {
+ struct {
+ uint32_t data : 8;
+ uint32_t reserved : 23;
+ uint32_t empty : 1;
+ };
+ uint32_t raw_bits;
+} spi_reg_rxdata;
+_ASSERT_SIZEOF(spi_reg_rxdata, 4);
+
+
+typedef union {
+ struct {
+ uint32_t txmark : 3;
+ uint32_t reserved : 29;
+ };
+ uint32_t raw_bits;
+} spi_reg_txmark;
+_ASSERT_SIZEOF(spi_reg_txmark, 4);
+
+
+typedef union {
+ struct {
+ uint32_t rxmark : 3;
+ uint32_t reserved : 29;
+ };
+ uint32_t raw_bits;
+} spi_reg_rxmark;
+_ASSERT_SIZEOF(spi_reg_rxmark, 4);
+
+
+typedef union {
+ struct {
+ uint32_t en : 1;
+ uint32_t reserved : 31;
+ };
+ uint32_t raw_bits;
+} spi_reg_fctrl;
+_ASSERT_SIZEOF(spi_reg_fctrl, 4);
+
+
+typedef union {
+ struct {
+ uint32_t cmd_en : 1;
+ uint32_t addr_len : 3;
+ uint32_t pad_cnt : 4;
+ uint32_t command_proto : 2;
+ uint32_t addr_proto : 2;
+ uint32_t data_proto : 2;
+ uint32_t reserved : 2;
+ uint32_t command_code : 8;
+ uint32_t pad_code : 8;
+ };
+ uint32_t raw_bits;
+} spi_reg_ffmt;
+_ASSERT_SIZEOF(spi_reg_ffmt, 4);
+
+
+typedef union {
+ struct {
+ uint32_t txwm : 1;
+ uint32_t rxwm : 1;
+ uint32_t reserved : 30;
+ };
+ uint32_t raw_bits;
+} spi_reg_ie;
+typedef spi_reg_ie spi_reg_ip;
+_ASSERT_SIZEOF(spi_reg_ie, 4);
+_ASSERT_SIZEOF(spi_reg_ip, 4);
+
+#undef _ASSERT_SIZEOF
+
+
+/**
+ * SPI control register memory map.
+ *
+ * All functions take a pointer to a SPI device's control registers.
+ */
+struct spi_ctrl {
+ uint32_t sckdiv;
+ spi_reg_sckmode sckmode;
+ uint32_t reserved08;
+ uint32_t reserved0c;
+
+ uint32_t csid;
+ uint32_t csdef;
+ spi_reg_csmode csmode;
+ uint32_t reserved1c;
+
+ uint32_t reserved20;
+ uint32_t reserved24;
+ spi_reg_delay0 delay0;
+ spi_reg_delay1 delay1;
+
+ uint32_t reserved30;
+ uint32_t reserved34;
+ uint32_t reserved38;
+ uint32_t reserved3c;
+
+ spi_reg_fmt fmt;
+ uint32_t reserved44;
+ spi_reg_txdata txdata;
+ spi_reg_rxdata rxdata;
+
+ spi_reg_txmark txmark;
+ spi_reg_rxmark rxmark;
+ uint32_t reserved58;
+ uint32_t reserved5c;
+
+ spi_reg_fctrl fctrl;
+ spi_reg_ffmt ffmt;
+ uint32_t reserved68;
+ uint32_t reserved6c;
+
+ spi_reg_ie ie;
+ spi_reg_ip ip;
+};
+
+/**
+ * Get smallest clock divisor that divides input_khz to a quotient less than or
+ * equal to max_target_khz;
+ */
+static inline unsigned int
+spi_min_clk_divisor(unsigned int input_khz, unsigned int max_target_khz)
+{
+ // f_sck = f_in / (2 * (div + 1)) => div = (f_in / (2*f_sck)) - 1
+ //
+ // The nearest integer solution for div requires rounding up as to not
+ // exceed max_target_khz.
+ //
+ // div = ceil(f_in / (2*f_sck)) - 1
+ // = floor((f_in - 1 + 2*f_sck) / (2*f_sck)) - 1
+ //
+ // This should not overflow as long as (f_in - 1 + 2*f_sck) does not
+ // exceed 2^32 - 1, which is unlikely since we represent frequencies
+ // in kHz.
+ unsigned int quotient =
+ (input_khz + 2 * max_target_khz - 1) / (2 * max_target_khz);
+ // Avoid underflow
+ if (quotient == 0)
+ return 0;
+ return quotient - 1;
+}
+
+#endif /* __SOC_SIFIVE_HIFIVE_U_SPI_INTERNAL_H__ */