diff options
-rw-r--r-- | src/mainboard/sifive/hifive-unleashed/Makefile.inc | 3 | ||||
-rw-r--r-- | src/mainboard/sifive/hifive-unleashed/media.c (renamed from src/soc/sifive/fu540/media.c) | 24 | ||||
-rw-r--r-- | src/soc/sifive/fu540/Makefile.inc | 6 | ||||
-rw-r--r-- | src/soc/sifive/fu540/include/soc/spi.h | 83 | ||||
-rw-r--r-- | src/soc/sifive/fu540/spi.c | 254 | ||||
-rw-r--r-- | src/soc/sifive/fu540/spi_internal.h | 242 |
6 files changed, 609 insertions, 3 deletions
diff --git a/src/mainboard/sifive/hifive-unleashed/Makefile.inc b/src/mainboard/sifive/hifive-unleashed/Makefile.inc index 207898e973..8ce266f034 100644 --- a/src/mainboard/sifive/hifive-unleashed/Makefile.inc +++ b/src/mainboard/sifive/hifive-unleashed/Makefile.inc @@ -12,12 +12,15 @@ # GNU General Public License for more details. bootblock-y += memlayout.ld +bootblock-y += media.c romstage-y += memlayout.ld romstage-y += romstage.c +romstage-y += media.c ramstage-y += memlayout.ld ramstage-y += fixup_fdt.c +ramstage-y += media.c DTB=$(obj)/hifive-unleashed.dtb diff --git a/src/soc/sifive/fu540/media.c b/src/mainboard/sifive/hifive-unleashed/media.c index 7b9ccb0e3c..b0198a7abe 100644 --- a/src/soc/sifive/fu540/media.c +++ b/src/mainboard/sifive/hifive-unleashed/media.c @@ -2,6 +2,7 @@ * This file is part of the coreboot project. * * Copyright (C) 2018 Jonathan Neuschäfer + * 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 @@ -14,6 +15,7 @@ */ #include <boot_device.h> +#include <soc/spi.h> /* At 0x20000000: A 256MiB long memory-mapped view of the flash at QSPI0 */ static struct mem_region_device mdev = @@ -23,3 +25,25 @@ const struct region_device *boot_device_ro(void) { return &mdev.rdev; } + +const static struct fu540_spi_mmap_config spi_mmap_config = { + .cmd_en = 1, + .addr_len = 4, + .pad_cnt = 6, + .cmd_proto = FU540_SPI_PROTO_S, + .addr_proto = FU540_SPI_PROTO_Q, + .data_proto = FU540_SPI_PROTO_Q, + .cmd_code = 0xec, + .pad_code = 0 +}; + +void boot_device_init(void) +{ + struct spi_slave slave; + + /* initialize spi controller */ + spi_setup_slave(0, 0, &slave); + + /* map flash to memory space */ + fu540_spi_mmap(&slave, &spi_mmap_config); +} 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/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__ */ |