From 1468e0f5e10421f1217e5d95eaf10e9e2bd53d2d Mon Sep 17 00:00:00 2001 From: "Hsiang-Chia.Huang" Date: Tue, 27 Aug 2019 15:22:46 +0800 Subject: [PATCH] Add MULTI_WIRE mode and update spi driver. MULTI_WIRE_ALL _ _ CS |______________________________________________________| _ _ _ _ _ _ _ _ _ _ _ _ _ _ SCL _| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_ Phase ---cmd--|-addr--|-dummy-|-data---------------------------- DQ0 0 4 | 0 4 | 0 4 | 0 4 8 12... | | | DQ1 1 5 | 1 5 | 1 5 | 1 5 9 13... | | | DQ2 2 6 | 2 6 | 2 6 | 2 6 10 14... | | | DQ3 3 7 | 3 7 | 3 7 | 3 7 11 15... MULTI_WIRE_ADDR_DATA _ _ CS |______________________________________________________| _ _ _ _ _ _ _ _ _ _ _ _ _ _ SCL _| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_ Phase ------cmd------|-addr--|-dummy-|-data-------------------- DQ0 0 1 2 3 | 0 4 | 0 4 | 0 4 8 12... | | | DQ1 | 1 5 | 1 5 | 1 5 9 13... | | | DQ2 | 2 6 | 2 6 | 2 6 10 14... | | | DQ3 | 3 7 | 3 7 | 3 7 11 15... MULTI_WIRE_DATA_ONLY _ _ CS |______________________________________________________| _ _ _ _ _ _ _ _ _ _ _ _ _ _ SCL _| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_| |_ Phase ------cmd------|------addr-----|-----dummy-----|-data---- DQ0 0 1 2 3 | 0 1 2 3 | 0 1 2 3 | 0 4 | | | DQ1 | | | 1 5 | | | DQ2 | | | 2 6 | | | DQ3 | | | 3 7 --- metal/spi.h | 12 ++++ src/drivers/sifive_spi0.c | 125 +++++++++++++++++++++++++++++++++++++- 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/metal/spi.h b/metal/spi.h index 30271c45..7e4b04ae 100644 --- a/metal/spi.h +++ b/metal/spi.h @@ -21,6 +21,18 @@ struct metal_spi_config { unsigned int cs_active_high : 1; /*! @brief The chip select ID to activate for the SPI transfer */ unsigned int csid; + /*! @brief The spi command frame number (cycles = num * frame_len) */ + unsigned int cmd_num; + /*! @brief The spi address frame number */ + unsigned int addr_num; + /*! @brief The spi dummy frame number */ + unsigned int dummy_num; + /*! @brief The Dual/Quad spi mode selection.*/ + enum { + MULTI_WIRE_ALL, + MULTI_WIRE_DATA_ONLY, + MULTI_WIRE_ADDR_DATA + } multi_wire; }; struct metal_spi_vtable { diff --git a/src/drivers/sifive_spi0.c b/src/drivers/sifive_spi0.c index 4e6578cc..d4a503a2 100644 --- a/src/drivers/sifive_spi0.c +++ b/src/drivers/sifive_spi0.c @@ -63,10 +63,16 @@ static int configure_spi(struct __metal_driver_sifive_spi0 *spi, METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_SINGLE; break; case METAL_SPI_DUAL: - METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_DUAL; + if (config->multi_wire == MULTI_WIRE_ALL) + METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_DUAL; + else + METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_SINGLE; break; case METAL_SPI_QUAD: - METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_QUAD; + if (config->multi_wire == MULTI_WIRE_ALL) + METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_QUAD; + else + METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_SINGLE; break; default: /* Unsupported value */ @@ -131,6 +137,28 @@ static int configure_spi(struct __metal_driver_sifive_spi0 *spi, return 0; } +static void spi_mode_switch(struct __metal_driver_sifive_spi0 *spi, + struct metal_spi_config *config, + unsigned int trans_stage) { + long control_base = + __metal_driver_sifive_spi0_control_base((struct metal_spi *)spi); + + if (config->multi_wire == trans_stage) { + METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) &= ~(METAL_SPI_PROTO_MASK); + switch (config->protocol) { + case METAL_SPI_DUAL: + METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_DUAL; + break; + case METAL_SPI_QUAD: + METAL_SPI_REGW(METAL_SIFIVE_SPI0_FMT) |= METAL_SPI_PROTO_QUAD; + break; + default: + /* Unsupported value */ + return; + } + } +} + int __metal_driver_sifive_spi0_transfer(struct metal_spi *gspi, struct metal_spi_config *config, size_t len, char *tx_buf, @@ -138,6 +166,7 @@ int __metal_driver_sifive_spi0_transfer(struct metal_spi *gspi, struct __metal_driver_sifive_spi0 *spi = (void *)gspi; long control_base = __metal_driver_sifive_spi0_control_base(gspi); int rc = 0; + size_t i = 0; rc = configure_spi(spi, config); if (rc != 0) { @@ -153,7 +182,97 @@ int __metal_driver_sifive_spi0_transfer(struct metal_spi *gspi, /* Declare time_t variables to break out of infinite while loop */ time_t endwait; - for (size_t i = 0; i < len; i++) { + for (i = 0; i < config->cmd_num; i++) { + + while (METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXDATA) & METAL_SPI_TXDATA_FULL) + ; + + if (tx_buf) { + METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = tx_buf[i]; + } else { + METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = 0; + } + + endwait = metal_time() + METAL_SPI_RXDATA_TIMEOUT; + + while ((rxdata = METAL_SPI_REGW(METAL_SIFIVE_SPI0_RXDATA)) & + METAL_SPI_RXDATA_EMPTY) { + if (metal_time() > endwait) { + METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &= + ~(METAL_SPI_CSMODE_MASK); + + return 1; + } + } + + if (rx_buf) { + rx_buf[i] = (char)(rxdata & METAL_SPI_TXRXDATA_MASK); + } + } + + /* switch to Dual/Quad mode */ + spi_mode_switch(spi, config, MULTI_WIRE_ADDR_DATA); + + /* Send Addr data */ + for (; i < (config->cmd_num + config->addr_num); i++) { + + while (METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXDATA) & METAL_SPI_TXDATA_FULL) + ; + + if (tx_buf) { + METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = tx_buf[i]; + } else { + METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = 0; + } + + endwait = metal_time() + METAL_SPI_RXDATA_TIMEOUT; + + while ((rxdata = METAL_SPI_REGW(METAL_SIFIVE_SPI0_RXDATA)) & + METAL_SPI_RXDATA_EMPTY) { + if (metal_time() > endwait) { + METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &= + ~(METAL_SPI_CSMODE_MASK); + + return 1; + } + } + + if (rx_buf) { + rx_buf[i] = (char)(rxdata & METAL_SPI_TXRXDATA_MASK); + } + } + + /* Send Dummy data */ + for (; i < (config->cmd_num + config->addr_num + config->dummy_num); i++) { + + while (METAL_SPI_REGW(METAL_SIFIVE_SPI0_TXDATA) & METAL_SPI_TXDATA_FULL) + ; + + if (tx_buf) { + METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = tx_buf[i]; + } else { + METAL_SPI_REGB(METAL_SIFIVE_SPI0_TXDATA) = 0; + } + + endwait = metal_time() + METAL_SPI_RXDATA_TIMEOUT; + + while ((rxdata = METAL_SPI_REGW(METAL_SIFIVE_SPI0_RXDATA)) & + METAL_SPI_RXDATA_EMPTY) { + if (metal_time() > endwait) { + METAL_SPI_REGW(METAL_SIFIVE_SPI0_CSMODE) &= + ~(METAL_SPI_CSMODE_MASK); + return 1; + } + } + if (rx_buf) { + rx_buf[i] = (char)(rxdata & METAL_SPI_TXRXDATA_MASK); + } + } + + /* switch to Dual/Quad mode */ + spi_mode_switch(spi, config, MULTI_WIRE_DATA_ONLY); + + for (; i < len; i++) { /* Master send bytes to the slave */ /* Wait for TXFIFO to not be full */