Patrick Rudolph has submitted this change and it was merged. ( https://review.coreboot.org/c/coreboot/+/33055 )
Change subject: soc/sifive/fu540: add code for spi and map flash to memory spaces ......................................................................
soc/sifive/fu540: add code for spi and map flash to memory spaces
SiFive's ZSBL has initialized flash, but only 16MB of space is available.
1. add code for spi 2. add code to map flash to memory spaces
Change-Id: I106688c65ac7dd70be7479dc4691797b700682d9 Signed-off-by: Xiang Wang merle@hardenedlinux.org Reviewed-on: https://review.coreboot.org/c/coreboot/+/33055 Tested-by: build bot (Jenkins) no-reply@coreboot.org Reviewed-by: Patrick Rudolph siro@das-labor.org --- M src/mainboard/sifive/hifive-unleashed/Makefile.inc R src/mainboard/sifive/hifive-unleashed/media.c M src/soc/sifive/fu540/Makefile.inc A src/soc/sifive/fu540/include/soc/spi.h A src/soc/sifive/fu540/spi.c A src/soc/sifive/fu540/spi_internal.h 6 files changed, 609 insertions(+), 3 deletions(-)
Approvals: build bot (Jenkins): Verified Patrick Rudolph: Looks good to me, approved
diff --git a/src/mainboard/sifive/hifive-unleashed/Makefile.inc b/src/mainboard/sifive/hifive-unleashed/Makefile.inc index 207898e..8ce266f 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 similarity index 62% rename from src/soc/sifive/fu540/media.c rename to src/mainboard/sifive/hifive-unleashed/media.c index 7b9ccb0..b0198a7 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 @@ { 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 4f62f3e..3c97c08 100644 --- a/src/soc/sifive/fu540/Makefile.inc +++ b/src/soc/sifive/fu540/Makefile.inc @@ -15,13 +15,13 @@
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 @@
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 0000000..543f9b2 --- /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 0000000..6bf1700 --- /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 0000000..97094c1 --- /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__ */