diff --git a/hw/top_chip/data/pins_genesys2.xdc b/hw/top_chip/data/pins_genesys2.xdc index f2169a66..80539577 100644 --- a/hw/top_chip/data/pins_genesys2.xdc +++ b/hw/top_chip/data/pins_genesys2.xdc @@ -12,6 +12,7 @@ set_property -dict { PACKAGE_PIN AB25 IOSTANDARD LVCMOS33 } [get_ports { ftdi_r ## GPIO # Inputs +# User switches set_property -dict { PACKAGE_PIN G19 IOSTANDARD LVCMOS18 } [get_ports { gpio_i[0] }]; # SW0 (VADJ) set_property -dict { PACKAGE_PIN G25 IOSTANDARD LVCMOS18 } [get_ports { gpio_i[1] }]; # SW1 (VADJ) set_property -dict { PACKAGE_PIN H24 IOSTANDARD LVCMOS18 } [get_ports { gpio_i[2] }]; # SW2 (VADJ) @@ -20,11 +21,14 @@ set_property -dict { PACKAGE_PIN N19 IOSTANDARD LVCMOS18 } [get_ports { gpio_i set_property -dict { PACKAGE_PIN P19 IOSTANDARD LVCMOS18 } [get_ports { gpio_i[5] }]; # SW5 (VADJ) set_property -dict { PACKAGE_PIN P26 IOSTANDARD LVCMOS33 } [get_ports { gpio_i[6] }]; # SW6 (VCC3V3) set_property -dict { PACKAGE_PIN P27 IOSTANDARD LVCMOS33 } [get_ports { gpio_i[7] }]; # SW7 (VCC3V3) - -# Bootstrap pin, should be pulled down during boot to enter bootstrap mode. -set_property -dict { PACKAGE_PIN AB29 IOSTANDARD LVCMOS33 PULLTYPE PULLUP } [get_ports { gpio_i[8] }]; +# Bootstrap pin, should be pulled down during boot to enter bootstrap mode. +set_property -dict { PACKAGE_PIN AB29 IOSTANDARD LVCMOS33 PULLTYPE PULLUP } [get_ports { gpio_i[8] }]; # PROG_RXFN +# Micro SD card presence detect line +# TODO(elliotb): is this active-low or active-high? +set_property -dict { PACKAGE_PIN P28 IOSTANDARD LVCMOS33 } [get_ports { gpio_i[9] }]; # SD_CD # Outputs +# User LEDs set_property -dict { PACKAGE_PIN T28 IOSTANDARD LVCMOS33 } [get_ports { gpio_o[0] }]; # LED0 set_property -dict { PACKAGE_PIN V19 IOSTANDARD LVCMOS33 } [get_ports { gpio_o[1] }]; # LED1 set_property -dict { PACKAGE_PIN U30 IOSTANDARD LVCMOS33 } [get_ports { gpio_o[2] }]; # LED2 @@ -33,6 +37,8 @@ set_property -dict { PACKAGE_PIN V20 IOSTANDARD LVCMOS33 } [get_ports { gpio_o set_property -dict { PACKAGE_PIN V26 IOSTANDARD LVCMOS33 } [get_ports { gpio_o[5] }]; # LED5 set_property -dict { PACKAGE_PIN W24 IOSTANDARD LVCMOS33 } [get_ports { gpio_o[6] }]; # LED6 set_property -dict { PACKAGE_PIN W23 IOSTANDARD LVCMOS33 } [get_ports { gpio_o[7] }]; # LED7 +# Micro SD card power (VDD) control (active-low, ext. pull-up) +set_property -dict { PACKAGE_PIN AE24 IOSTANDARD LVCMOS33 } [get_ports { gpio_o[8] }]; # SD_RESET ## UART set_property -dict { PACKAGE_PIN Y20 IOSTANDARD LVCMOS33 } [get_ports { uart_rx_i }]; @@ -48,9 +54,17 @@ set_property -dict { PACKAGE_PIN Y23 IOSTANDARD LVCMOS33 } [get_ports { uart_t set_property -dict { PACKAGE_PIN T26 IOSTANDARD LVCMOS33 PULLTYPE PULLUP } [get_ports { i2c_scl_io }]; set_property -dict { PACKAGE_PIN T27 IOSTANDARD LVCMOS33 PULLTYPE PULLUP } [get_ports { i2c_sda_io }]; -## SPI (PMOD Header JD) +## SPI Device (PMOD Header JD) set_property -dict { PACKAGE_PIN W28 IOSTANDARD LVCMOS33 PULLTYPE PULLDOWN } [get_ports { spi_device_sd_o }]; set_property -dict { PACKAGE_PIN W27 IOSTANDARD LVCMOS33 PULLTYPE PULLDOWN } [get_ports { spi_device_sd_i }]; set_property -dict { PACKAGE_PIN W29 IOSTANDARD LVCMOS33 PULLTYPE PULLUP } [get_ports { spi_device_csb_i }]; set_property -dict { PACKAGE_PIN AD27 IOSTANDARD LVCMOS33 PULLTYPE PULLDOWN } [get_ports { spi_device_sck_i }]; set_property -dict { PACKAGE_PIN AD29 IOSTANDARD LVCMOS33 } [get_ports { spien }]; + +## SPI Host (MicroSD card slot) +set_property -dict { PACKAGE_PIN R28 IOSTANDARD LVCMOS33 } [get_ports { spi_host_sck_o }]; # SD_SCLK +set_property -dict { PACKAGE_PIN R26 IOSTANDARD LVCMOS33 } [get_ports { spi_host_sd_i }]; # SD_DAT0 +# set_property -dict { PACKAGE_PIN R30 IOSTANDARD LVCMOS33 } [get_ports { microsd_dat1 }]; # SD_DAT1 unused in SPI bus mode +# set_property -dict { PACKAGE_PIN P29 IOSTANDARD LVCMOS33 } [get_ports { microsd_dat2 }]; # SD_DAT2 unused in SPI bus mode +set_property -dict { PACKAGE_PIN T30 IOSTANDARD LVCMOS33 } [get_ports { spi_host_csb_o }]; # SD_DAT3 +set_property -dict { PACKAGE_PIN R29 IOSTANDARD LVCMOS33 } [get_ports { spi_host_sd_o }]; # SD_CMD diff --git a/hw/top_chip/dv/verilator/top_chip_verilator.core b/hw/top_chip/dv/verilator/top_chip_verilator.core index e5310efc..41b132fd 100644 --- a/hw/top_chip/dv/verilator/top_chip_verilator.core +++ b/hw/top_chip/dv/verilator/top_chip_verilator.core @@ -26,6 +26,7 @@ filesets: - lowrisc:dv:sw_test_status - lowrisc:dv:dv_test_status - lowrisc:sonata:i2cdpi + - lowrisc:sonata:spidevicedpi files: - dram_wrapper_sim.sv: { file_type: systemVerilogSource } diff --git a/hw/top_chip/dv/verilator/top_chip_verilator.sv b/hw/top_chip/dv/verilator/top_chip_verilator.sv index c56bf9d2..bdbec9b5 100644 --- a/hw/top_chip/dv/verilator/top_chip_verilator.sv +++ b/hw/top_chip/dv/verilator/top_chip_verilator.sv @@ -27,20 +27,24 @@ module top_chip_verilator (input logic clk_i, rst_ni); logic uart_rx; logic uart_tx; - // SPI signals + // SPI device signals logic spi_device_sck; logic spi_device_csb; logic [3:0] qspi_device_sdo; logic [3:0] qspi_device_sdo_en; logic spi_device_sdi; + // SPI host signals + logic spi_host_sck_output, spi_host_csb_output; + logic spi_host_sck_en_output, spi_host_csb_en_output; + logic spi_host_input; + logic [3:0] spi_host_sd_output; + logic [3:0] spi_host_sd_en_output; + // AXI signals top_pkg::axi_dram_req_t dram_req; top_pkg::axi_dram_resp_t dram_resp; - logic [3:0] spi_host_sd; - logic [3:0] spi_host_sd_en; - // CHERI Mocha top top_chip_system #( ) u_top_chip_system ( @@ -72,25 +76,22 @@ module top_chip_verilator (input logic clk_i, rst_ni); .spi_device_sd_i ({3'h0, spi_device_sdi}), // SPI MOSI = QSPI DQ0 .spi_device_tpm_csb_i ('0), - .spi_host_sck_o ( ), - .spi_host_sck_en_o ( ), - .spi_host_csb_o ( ), - .spi_host_csb_en_o ( ), - .spi_host_sd_o (spi_host_sd), - .spi_host_sd_en_o (spi_host_sd_en), - // Mapping output 0 to input 1 because legacy SPI does not allow - // bi-directional wires. - // This only works in standard mode where sd_o[0]=COPI and - // sd_i[1]=CIPO. - .spi_host_sd_i ({2'b0, spi_host_sd_en[0] ? spi_host_sd[0] : 1'b0, 1'b0}), + .spi_host_sck_o (spi_host_sck_output), + .spi_host_sck_en_o (spi_host_sck_en_output), + .spi_host_csb_o (spi_host_csb_output), + .spi_host_csb_en_o (spi_host_csb_en_output), + .spi_host_sd_o (spi_host_sd_output), + .spi_host_sd_en_o (spi_host_sd_en_output), + // Legacy SPI present in SD cards does not allow bi-directional wires. + // Work in standard mode where sd_o[0]=COPI and sd_i[1]=CIPO. + .spi_host_sd_i ({2'b0, spi_host_input, 1'b0}), // SPI CIPO = QSPI DQ1 .dram_req_o (dram_req), .dram_resp_i (dram_resp) ); // No support for dual or quad SPI in loopback mode right now. - logic unused_spi_host = (|spi_host_sd[3:2]) | spi_host_sd[0] | - (|spi_host_sd_en[3:2]) | spi_host_sd_en[0]; + logic unused_spi_host = |{spi_host_sd_output[3:1], spi_host_sd_en_output[3:1]}; // Virtual GPIO gpiodpi #( @@ -142,8 +143,25 @@ module top_chip_verilator (input logic clk_i, rst_ni); .spi_device_sck_o (spi_device_sck), .spi_device_csb_o (spi_device_csb), .spi_device_sdi_o (spi_device_sdi), - .spi_device_sdo_i (qspi_device_sdo[1]), // SPI MISO = QSPI DQ1 - .spi_device_sdo_en_i(qspi_device_sdo_en[1]) // SPI MISO = QSPI DQ1 + .spi_device_sdo_i (qspi_device_sdo[1]), // SPI CIPO = QSPI DQ1 + .spi_device_sdo_en_i(qspi_device_sdo_en[1]) // SPI CIPO = QSPI DQ1 + ); + + // Virtual SPI Device - model an SD card (in a limited way) + spidevicedpi #( + .ID ("microsd"), + .NDevices (1), + .DataW (1), + .OOB_InW (1), + .OOB_OutW (1) + ) u_spidevicedpi_microsd ( + .rst_ni, + .sck (spi_host_sck_en_output ? spi_host_sck_output : 1'b0), + .cs (spi_host_csb_en_output ? spi_host_csb_output : 1'b0), + .copi (spi_host_sd_en_output[0] ? spi_host_sd_output[0] : 1'b0), // SPI COPI = QSPI DQ0 + .cipo (spi_host_input), + .oob_in ( ), // not used + .oob_out( ) // TODO(elliotb): figure out how to drive gpio_inputs[9] here alongside gpiodpi ); `define DUT u_top_chip_system diff --git a/hw/top_chip/rtl/chip_mocha_genesys2.sv b/hw/top_chip/rtl/chip_mocha_genesys2.sv index 7b9dff63..9ec2b8f0 100644 --- a/hw/top_chip/rtl/chip_mocha_genesys2.sv +++ b/hw/top_chip/rtl/chip_mocha_genesys2.sv @@ -13,9 +13,9 @@ module chip_mocha_genesys2 #( input logic ext_rst_ni, input logic ftdi_rst_ni, - // GPIO - enough for the user switches and LEDs as a starting point - input logic [8:0] gpio_i, - output logic [7:0] gpio_o, + // GPIO + input logic [9:0] gpio_i, + output logic [8:0] gpio_o, // UART input logic uart_rx_i, @@ -25,13 +25,19 @@ module chip_mocha_genesys2 #( inout logic i2c_scl_io, inout logic i2c_sda_io, - // SPI + // SPI Device input logic spi_device_sck_i, input logic spi_device_csb_i, input logic spi_device_sd_i, output logic spi_device_sd_o, output logic spien, + // SPI Host + input logic spi_host_sck_o, + input logic spi_host_csb_o, + input logic spi_host_sd_i, + output logic spi_host_sd_o, + // DDR3 inout wire [31:0] ddr3_dq, inout wire [ 3:0] ddr3_dqs_n, @@ -78,9 +84,10 @@ module chip_mocha_genesys2 #( logic i2c_scl_en_output, i2c_sda_en_output; logic [3:0] qspi_device_sdo; logic [3:0] qspi_device_sdo_en; - - logic [3:0] spi_host_sd; - logic [3:0] spi_host_sd_en; + logic spi_host_sck_output, spi_host_csb_output; + logic spi_host_sck_en_output, spi_host_csb_en_output; + logic [3:0] spi_host_sd_output; + logic [3:0] spi_host_sd_en_output; // AXI signals // Tag controller to CDC FIFO, synchronous to u_top_chip_system.clkmgr_clocks.clk_main_infra @@ -154,7 +161,7 @@ module chip_mocha_genesys2 #( .rst_ni (rst_n_sync_50m), // GPIO - .gpio_i ({23'd0, gpio_i}), + .gpio_i (32'(gpio_i)), .gpio_o (gpio_outputs), .gpio_en_o (gpio_en_outputs), @@ -180,21 +187,17 @@ module chip_mocha_genesys2 #( .spi_device_csb_i (spi_device_csb_i), .spi_device_sd_o (qspi_device_sdo), .spi_device_sd_en_o (qspi_device_sdo_en), - .spi_device_sd_i ({3'h0, spi_device_sd_i}), // SPI MOSI = QSPI DQ0 + .spi_device_sd_i ({3'h0, spi_device_sd_i}), // SPI COPI = QSPI DQ0 .spi_device_tpm_csb_i ('0), // SPI host - .spi_host_sck_o ( ), - .spi_host_sck_en_o ( ), - .spi_host_csb_o ( ), - .spi_host_csb_en_o ( ), - .spi_host_sd_o (spi_host_sd), - .spi_host_sd_en_o (spi_host_sd_en), - // Mapping output 0 to input 1 because legacy SPI does not allow - // bi-directional wires. - // This only works in standard mode where sd_o[0]=COPI and - // sd_i[1]=CIPO. - .spi_host_sd_i ({2'b0, spi_host_sd_en[0] ? spi_host_sd[0] : 1'b0, 1'b0}), + .spi_host_sck_o (spi_host_sck_output), + .spi_host_sck_en_o (spi_host_sck_output_en), + .spi_host_csb_o (spi_host_csb_output), + .spi_host_csb_en_o (spi_host_csb_output_en), + .spi_host_sd_o (spi_host_sd_output), + .spi_host_sd_en_o (spi_host_sd_en_output), + .spi_host_sd_i ({2'b00, spi_host_sd_i, 1'b0}), // SPI CIPO = QSPI DQ1 // DRAM .dram_req_o (dram_req), @@ -203,7 +206,7 @@ module chip_mocha_genesys2 #( // GPIO tri-state output drivers // Instantiate for only the outputs connected to an FPGA pin - for (genvar ii = 0; ii < 8; ii++) begin : gen_gpio_o + for (genvar ii = 0; ii < $bits(gpio_o); ii++) begin : gen_gpio_o OBUFT obuft ( .I(gpio_outputs[ii]), .T(~gpio_en_outputs[ii]), @@ -225,13 +228,33 @@ module chip_mocha_genesys2 #( .O(i2c_sda_input) ); - // SPI tri-state output driver - OBUFT spi_obuft ( - .I(qspi_device_sdo[1]), // SPI MISO = QSPI DQ1 - .T(~qspi_device_sdo_en[1]), // SPI MISO = QSPI DQ1 + // SPI device tri-state output driver + OBUFT spi_device_obuft ( + .I(qspi_device_sdo[1]), // SPI CIPO = QSPI DQ1 + .T(~qspi_device_sdo_en[1]), // SPI CIPO = QSPI DQ1 .O(spi_device_sd_o) ); + // SPI host tri-state output drivers + OBUFT spi_host_sck_obuft ( + .I(spi_host_sck_output), + .T(~spi_host_sck_output_en), + .O(spi_host_sck_o) + ); + OBUFT spi_host_csb_obuft ( + .I(spi_host_csb_output), + .T(~spi_host_csb_output_en), + .O(spi_host_csb_o) + ); + // Legacy SPI present in SD cards does not allow bi-directional wires. + // Work in standard mode where sd_o[0]=COPI and sd_i[1]=CIPO. + // Other data outputs are unused. + OBUFT spi_host_sd_obuft ( + .I(spi_host_sd_output[0]), // SPI COPI = QSPI DQ0 + .T(~spi_host_sd_en_output[0]), // SPI COPI = QSPI DQ0 + .O(spi_host_sd_o) + ); + // Async AXI FIFO from tag controller to MIG axi_cdc #( .aw_chan_t (top_pkg::axi_dram_aw_chan_t), diff --git a/sw/device/lib/hal/spi_host.h b/sw/device/lib/hal/spi_host.h index 5c806e5a..0153cde4 100644 --- a/sw/device/lib/hal/spi_host.h +++ b/sw/device/lib/hal/spi_host.h @@ -15,17 +15,24 @@ #define SPI_HOST_CONTROL_SPIEN_MASK (1u << 31) #define SPI_HOST_CONTROL_OUTPUTEN_MASK (1u << 29) #define SPI_HOST_STATUS_REG (0x14) +#define SPI_HOST_STATUS_READY_MASK (1u << 31) +#define SPI_HOST_STATUS_ACTIVE_MASK (1u << 30) +#define SPI_HOST_STATUS_TXFULL_MASK (1u << 29) +#define SPI_HOST_STATUS_RXEMPTY_MASK (1u << 24) #define SPI_HOST_CONFIGOPTS_REG (0x18) #define SPI_HOST_CSID_REG (0x1C) #define SPI_HOST_COMMAND_REG (0x20) +#define SPI_HOST_COMMAND_CSAAT_OFFSET (0) #define SPI_HOST_COMMAND_DIRECTION_OFFSET (3) #define SPI_HOST_COMMAND_DIRECTION_RECEIVE (1 << SPI_HOST_COMMAND_DIRECTION_OFFSET) #define SPI_HOST_COMMAND_DIRECTION_TRANSMIT (2 << SPI_HOST_COMMAND_DIRECTION_OFFSET) #define SPI_HOST_COMMAND_DIRECTION_BIDIRECTIONAL (3 << SPI_HOST_COMMAND_DIRECTION_OFFSET) #define SPI_HOST_COMMAND_LEN_OFF (5) -#define SPI_HOST_COMMAND_LEN_MASK (0xFFFFF << SPI_HOST_COMMAND_LEN_OFF) +#define SPI_HOST_COMMAND_LEN_MAX (0xFFFFF) +#define SPI_HOST_COMMAND_LEN_MASK (SPI_HOST_COMMAND_LEN_MAX << SPI_HOST_COMMAND_LEN_OFF) #define SPI_HOST_RXDATA_REG (0x24) #define SPI_HOST_TXDATA_REG (0x28) +#define SPI_HOST_ERROR_STATUS_REG (0x30) typedef void *spi_host_t; diff --git a/sw/device/lib/runtime/CMakeLists.txt b/sw/device/lib/runtime/CMakeLists.txt index 8aa2bb42..98fc712f 100644 --- a/sw/device/lib/runtime/CMakeLists.txt +++ b/sw/device/lib/runtime/CMakeLists.txt @@ -2,7 +2,7 @@ # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 -set(SRCS print.c string.c) +set(SRCS print.c string.c sdcard.c) set(LIBS hal) diff --git a/sw/device/lib/runtime/sdcard.c b/sw/device/lib/runtime/sdcard.c new file mode 100644 index 00000000..858eb280 --- /dev/null +++ b/sw/device/lib/runtime/sdcard.c @@ -0,0 +1,486 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "hal/spi_host.h" +#include "hal/uart.h" +#include "hal/mmio.h" +#include "runtime/sdcard.h" +#include "runtime/print.h" +#include +// #include +#include + +// 'Private' function declarations +bool collected_data(spi_host_t spi, uint8_t *buf, uint32_t len, uart_t uart); +bool read_cid_csd(spi_host_t spi, uint8_t cmd, uint8_t *buf, uint32_t len, uart_t uart); +void read_card_data(spi_host_t spi, uint8_t data[], uint32_t len); +void wait_idle(spi_host_t spi); +void nonblocking_cycles(spi_host_t spi, uint32_t cycles, bool csaat); +void nonblocking_write(spi_host_t spi, const uint8_t data[], uint32_t len); + + +void deselect_card(spi_host_t spi) { + // Use a eight-cycle transaction with CSAAT unset to deassert the Chip-Select line. + // Using only one cycle seemed to screw-up the next transaction, so we use eight. + nonblocking_cycles(spi, 8, false); +} + + // Initialise the SD card ready for use. + bool init(spi_host_t spi, uart_t uart) { + // Every card tried seems to be more than capable of keeping up with 20Mbps. + const unsigned kSpiSpeed = 0u; + DEV_WRITE(spi + SPI_HOST_CONFIGOPTS_REG, + 0xFFFF & kSpiSpeed); + DEV_WRITE(spi + SPI_HOST_CONTROL_REG, + SPI_HOST_CONTROL_SPIEN_MASK); // keep output_en low for now to disable Chip-Select + + // Apparently we're required to send at least 74 SD CLK cycles with + // the device _not_ selected before talking to it. + nonblocking_cycles(spi, 74, false); + wait_idle(spi); + + DEV_WRITE(spi + SPI_HOST_CONTROL_REG, + (SPI_HOST_CONTROL_SPIEN_MASK | + SPI_HOST_CONTROL_OUTPUTEN_MASK)); // re-enable non-SCK outputs + + // Note that this is a very stripped-down card initialisation sequence + // that assumes SDHC version 2, so use a more recent microSD card. + do { + send_command(spi, SDCARD_CMD_GO_IDLE_STATE, 0u, uart); + } while (0x01 != get_response_R1(spi, uart)); + + send_command(spi, SDCARD_CMD_SEND_IF_COND, 0x1aau, uart); + get_response_R3(spi, uart); + + // Instruct the SD card whether to check CRC values on commands. + send_command(spi, SDCARD_CMD_CRC_ON_OFF, (uint32_t)SDCARD_CRC_ON, uart); + get_response_R1(spi, uart); + + // Read supported voltage range of the card. + send_command(spi, SDCARD_CMD_READ_OCR, 0, uart); + get_response_R3(spi, uart); + + do { + send_command(spi, SDCARD_CMD_APP_CMD, 0, uart); + (void)get_response_R1(spi, uart); + // Specify Host Capacity Support as 1. + send_command(spi, SDCARD_SD_SEND_OP_COND, 1u << 30, uart); + } while (0x01 & get_response_R1(spi, uart)); + + if (SDCARD_LOG_ON) { + uprintf(uart, "Setting block length to 0x%x", SDCARD_BLOCK_LEN); + } + + // Read card capacity information. + send_command(spi, SDCARD_CMD_READ_OCR, 0, uart); + get_response_R3(spi, uart); + + send_command(spi, SDCARD_CMD_SET_BLOCKLEN, SDCARD_BLOCK_LEN, uart); + uint8_t rd = get_response_R1(spi, uart); + if (SDCARD_LOG_ON) { + uprintf(uart, "Response: 0x%x", rd); + } + deselect_card(spi); + + return true; + } + + // Read Card Identification Data (CID). + bool read_cid(spi_host_t spi, uint8_t *buf, uint32_t len, uart_t uart) { + return read_cid_csd(spi, SDCARD_CMD_SEND_CID, buf, len, uart); + } + + // Read Card Specific Data (CSD). + bool read_csd(spi_host_t spi, uint8_t *buf, uint32_t len, uart_t uart) { + return read_cid_csd(spi, SDCARD_CMD_SEND_CSD, buf, len, uart); + } + + // Read a number of contiguous blocks from the SD card. + bool read_blocks(spi_host_t spi, uint32_t block, uint8_t *buf, uint32_t num_blocks, uart_t uart) { + const bool multi = num_blocks > 1u; + + bool ok = true; + for (uint32_t blk = 0u; blk < num_blocks; blk++) { + if (SDCARD_LOG_ON) { + uprintf(uart, "Reading block 0x%x\n", block + blk); + } + + if (multi) { + // Is this the first block of the read request? + if (!blk) { + send_command(spi, SDCARD_CMD_READ_MULTIPLE_BLOCK, block, uart); + (void)get_response_R1(spi, uart); + } + } else { + send_command(spi, SDCARD_CMD_READ_SINGLE_BLOCK, block + blk, uart); + (void)get_response_R1(spi, uart); + } + + if (!collected_data(spi, &buf[blk * SDCARD_BLOCK_LEN], SDCARD_BLOCK_LEN, uart)) { + ok = false; + break; + } + } + + if (multi) { + send_command(spi, SDCARD_CMD_STOP_TRANSMISSION, 0u, uart); + (void)get_response_R1b(spi, uart); + } + + deselect_card(spi); + return ok; + } + + // Write a number of contiguous blocks from the SD card. + bool write_blocks(spi_host_t spi, uint32_t block, uint8_t *buf, uint32_t num_blocks, uart_t uart) { + const bool multi = num_blocks > 1u; + uint8_t crc16[2]; + crc16[1] = crc16[0] = 0xffu; // CRC16 not required by default for SPI mode. + + bool ok = true; + for (uint32_t blk = 0u; blk < num_blocks; blk++) { + if (SDCARD_CRC_ON) { + // CRC16 bytes follow the data block. + uint16_t crc = calc_crc16(&buf[blk * SDCARD_BLOCK_LEN], SDCARD_BLOCK_LEN); + crc16[0] = (uint8_t)(crc >> 8); + crc16[1] = (uint8_t)crc; + } + if (SDCARD_LOG_ON) { + uprintf(uart, "Writing block 0x%x\n", block + blk); + } + + // Note: the Start Block Token differs between Multiple Block Write commands and + // the other data transfer commands, including the Single Block Write command. + uint8_t start_token; + if (multi) { + // Is this the first block of the read request? + if (!blk) { + send_command(spi, SDCARD_CMD_WRITE_MULTIPLE_BLOCK, block, uart); + (void)get_response_R1(spi, uart); + } + start_token = SDCARD_START_BLOCK_TOKEN_MW; + } else { + send_command(spi, SDCARD_CMD_WRITE_SINGLE_BLOCK, block + blk, uart); + (void)get_response_R1(spi, uart); + start_token = SDCARD_START_BLOCK_TOKEN; + } + + nonblocking_write(spi, &start_token, 1u); + nonblocking_write(spi, &buf[blk * SDCARD_BLOCK_LEN], SDCARD_BLOCK_LEN); + nonblocking_write(spi, crc16, sizeof(crc16)); + // Collect data_response and wait until the card is no longer busy. + if (5 != (0x1f & get_data_response_busy(spi))) { + // Data not accepted because of an error. + ok = false; + break; + } + } + + if (multi) { + const uint8_t stop_tran_token = (uint8_t)SDCARD_STOP_TRAN_TOKEN; + nonblocking_write(spi, &stop_tran_token, 1u); + // The card will hold the CIPO line low whilst busy, yielding repeated 0x00 bytes, + // but it seems to drop and raise the line at an arbitrary time with respect to + // the '8-clock counting' logic. + while (0x00 != get_response_byte(spi)); // Detect falling edge. + // Card will signal busy with zeros. + wait_not_busy(spi); + } + + deselect_card(spi); + return ok; + } + + // Send a command to the SD card with the supplied 32-bit argument. + void send_command(spi_host_t spi, uint8_t cmdCode, uint32_t arg, uart_t uart) { + uint8_t cmd[6]; + if (SDCARD_LOG_ON) { + uprintf(uart, "Sending command 0x%x", cmdCode); + } + + // Apparently we need to clock 8 times before sending the command. + // + // TODO: This may well be an issue with not aligning read data on the previous command? + // Without this the initialisation sequence gets stuck trying to specify HCS; the SD card + // does not become ready. + nonblocking_cycles(spi, 8u, true); + + cmd[0] = 0x40u | cmdCode; + cmd[1] = (uint8_t)(arg >> 24); + cmd[2] = (uint8_t)(arg >> 16); + cmd[3] = (uint8_t)(arg >> 8); + cmd[4] = (uint8_t)(arg >> 0); + // The final byte includes the CRC7 which _must_ be valid for two special commands, + // but normally in SPI mode CRC checking is OFF. + if (SDCARD_CRC_ON || cmdCode == SDCARD_CMD_GO_IDLE_STATE || cmdCode == SDCARD_CMD_SEND_IF_COND) { + cmd[5] = 1u | (calc_crc7(cmd, 5) << 1); + } else { + // No need to expend CPU times calculating the CRC7; it will be ignored. + cmd[5] = 0xffu; + } + nonblocking_write(spi, cmd, sizeof(cmd)); + } + + // Attempt to collect a single response byte from the device; if it is not driving the + // CIPO line we will read 0xff. + uint8_t get_response_byte(spi_host_t spi) { + uint8_t r; + read_card_data(spi, &r, 1u); + return r; + } + + // Get response type R1 from the SD card. + uint8_t get_response_R1(spi_host_t spi, uart_t uart) { + wait_idle(spi); + while (true) { + uint8_t rd1 = get_response_byte(spi); + // Whilst there is no response we read 0xff; an actual R1 response commences + // with a leading 0 bit (MSB). + if (!(rd1 & 0x80u)) { + if (SDCARD_LOG_ON) { + uprintf(uart, "R1 0x%x", rd1); + } + return rd1; + } + } + } + + // Wait until the SD card declares that it is no longer busy. + void wait_not_busy(spi_host_t spi) { + while (0x00 == get_response_byte(spi)); // Wait whilst device is busy. + } + + // Get response type R1b from the SD card. + uint8_t get_response_R1b(spi_host_t spi, uart_t uart) { + wait_idle(spi); + uint8_t rd1 = get_response_R1(spi, uart); + // Card may signal busy with zero bytes. + wait_not_busy(spi); + return rd1; + } + + // Get data_response after sending a block of write data to the SD card. + uint8_t get_data_response_busy(spi_host_t spi) { + uint8_t rd1; + wait_idle(spi); + do { + rd1 = get_response_byte(spi); + } while ((rd1 & 0x11u) != 0x01u); + wait_not_busy(spi); + return rd1; + } + + // Get response type R3 (5 bytes) from the SD card. + void get_response_R3(spi_host_t spi, uart_t uart) { + volatile uint8_t rd2; + (void)get_response_R1(spi, uart); + for (int r = 0; r < 4; ++r) { + // Wait until SPI Host hardware is ready for a command + while (!(DEV_READ(spi + SPI_HOST_STATUS_REG) & SPI_HOST_STATUS_READY_MASK)) { + } + // Program an RX command segment + DEV_WRITE(spi + SPI_HOST_COMMAND_REG, + ((1 << SPI_HOST_COMMAND_CSAAT_OFFSET) | SPI_HOST_COMMAND_DIRECTION_RECEIVE)); + wait_idle(spi); + while (DEV_READ(spi + SPI_HOST_STATUS_REG) & SPI_HOST_STATUS_RXEMPTY_MASK) { + } + rd2 = (uint8_t)(DEV_READ(spi + SPI_HOST_RXDATA_REG)); + } + // We need to ensure the FIFO reads occur, but we don't need the data presently. + rd2 = rd2; + } + + // Calculate the CRC7 value for a series of bytes (command/response); + // used to generate the CRC for a command or check that of a response if CRC_ON mode is used. + uint8_t calc_crc7(const uint8_t *data, uint32_t len) { + uint8_t crc = 0u; + while (len-- > 0u) { + uint8_t d = *data++; + for (unsigned b = 0u; b < 8u; b++) { + crc = (crc << 1) ^ (((crc ^ d) & 0x80u) ? 0x12u : 0u); + d <<= 1; + } + } + // 7 MSBs contain the CRC residual. + return crc >> 1; + } + + // Calculate the CRC16 value for a series of bytes (data blocks); + // used for generation or checking, if CRC_ON mode is used. + uint16_t calc_crc16(const uint8_t *data, uint32_t len) { + uint16_t crc = 0u; + while (len-- > 0u) { + uint16_t d = (uint16_t)*data++ << 8; + for (unsigned b = 0u; b < 8u; b++) { + crc = (crc << 1) ^ (((crc ^ d) & 0x8000u) ? 0x1021u : 0u); + d <<= 1; + } + } + return crc; + } + + // Collect an expect number of bytes into the given buffer, and return an indication of whether + // they were collected without error. + bool collected_data(spi_host_t spi, uint8_t *buf, uint32_t len, uart_t uart) { + uint8_t crc16[2]; + + // Collect the data, prepended with the Start Block Token and followed by the CRC16 bytes. + while (SDCARD_START_BLOCK_TOKEN != get_response_byte(spi)); + // For at least one test card we need to hold the COPI line high during the data read + // otherwise the data becomes corrupted; the card appears to be starting to accept a new + // command. + read_card_data(spi, buf, len); + read_card_data(spi, crc16, sizeof(crc16)); + + // Shall we validate the CRC16 of the received data block? + if (SDCARD_CRC_ON) { + uint16_t exp_crc16 = calc_crc16(buf, len); + uint16_t obs_crc16 = ((uint16_t)crc16[0] << 8) | crc16[1]; + if (SDCARD_LOG_ON) { + uprintf(uart, "Read block CRC 0x%x\n", obs_crc16); + uprintf(uart, "Calculated CRC 0x%x\n", exp_crc16); + } + if (obs_crc16 != exp_crc16) { + deselect_card(spi); + if (SDCARD_LOG_ON) { + uart_puts(uart, "CRC16 mismatch\n"); + } + return false; + } + } + return true; + } + + // Shared implementation for SDCARD_CMD_SEND_CID and SDCARD_CMD_SEND_CSD. + bool read_cid_csd(spi_host_t spi, uint8_t cmd, uint8_t *buf, uint32_t len, uart_t uart) { + send_command(spi, cmd, 0, uart); + (void)get_response_R1(spi, uart); + bool ok = collected_data(spi, buf, len, uart); + deselect_card(spi); + return ok; + } + + /* + * Receives `len` bytes and puts them in the `data` buffer, + * where `len` is at most `0x7ff`, being careful to keep COPI high by + * also transmitting repeated 0xff bytes. + * + * This method will block until the requested number of bytes has been seen. + * There is currently no timeout. + * + * Note that unlike the 'blocking_read' member function of the SPI object, + * this function intentionally keeps the COPI line high by supplying a 0xff + * byte for each byte read. This prevents COPI line dropping and being + * misinterpreted as the start of a command. + */ + void read_card_data(spi_host_t spi, uint8_t data[], uint32_t len) { + // assert((len-1) <= SPI_HOST_COMMAND_LEN_MAX); + wait_idle(spi); + // Do not attempt a zero-byte transfer; not supported by the controller. + if (len) { + // Wait until SPI Host hardware is ready for a command + while (!(DEV_READ(spi + SPI_HOST_STATUS_REG) & SPI_HOST_STATUS_READY_MASK)) { + } + // Program an RX command segment + DEV_WRITE(spi + SPI_HOST_COMMAND_REG, + ((1 << SPI_HOST_COMMAND_CSAAT_OFFSET) | SPI_HOST_COMMAND_DIRECTION_RECEIVE | + (((len-1) << SPI_HOST_COMMAND_LEN_OFF) & SPI_HOST_COMMAND_LEN_MASK))); + // Pull data from the RX FIFO as it becomes available + const uint8_t *end = data + len - 1; + while (data <= end) { + // Wait for data + while (DEV_READ(spi + SPI_HOST_STATUS_REG) & SPI_HOST_STATUS_RXEMPTY_MASK) { + } + // Read a 32-bit RX FIFO word and pack relevant bytes into the destination array + uint32_t data_word = DEV_READ(spi + SPI_HOST_RXDATA_REG); + for (uint32_t by = 0; (by < 4) && (data <= end); by++) { + *data++ = (uint8_t)(data_word); + data_word >>= 8; + } + } + } + } + + /* + * Poll the Status register until the Active flag has been cleared + */ + void wait_idle(spi_host_t spi) { + // One cycle of delay is needed to avoid a race condition when this + // function is called directly after writing a command segment. + __asm__("nop"); + while (SPI_HOST_STATUS_ACTIVE_MASK & DEV_READ(spi + SPI_HOST_STATUS_REG)) { + } + } + + /* + * Program a directionless segment (SCK and CS, but no data RX or TX) + * to be 'transmitted' by the SPI Host hardware. + * This is that same as transmitting all-ones due to the way the + * data output enable has been used in hardware. + * + * Note that length is specified in SCK cycles, rather than bytes. + * + * Leave the chip-select line asserted afterwards if `csaat` is set. + */ + void nonblocking_cycles(spi_host_t spi, uint32_t cycles, bool csaat) { + if (cycles) { + // Wait until SPI Host hardware is ready for a command + while (!(DEV_READ(spi + SPI_HOST_STATUS_REG) & SPI_HOST_STATUS_READY_MASK)) { + } + // Program a directionless command segment + DEV_WRITE(spi + SPI_HOST_COMMAND_REG, + ((csaat << SPI_HOST_COMMAND_CSAAT_OFFSET) | + (((cycles-1) << SPI_HOST_COMMAND_LEN_OFF) & SPI_HOST_COMMAND_LEN_MASK))); + } + } + + /* + * Program the transmission of `len` bytes starting with `data[0]` + * by the SPI Host hardware. + */ + void nonblocking_write(spi_host_t spi, const uint8_t data[], uint32_t len) { + // assert((len-1) <= SPI_HOST_COMMAND_LEN_MAX); + // Wait until SPI Host hardware is ready for a command + while (!(DEV_READ(spi + SPI_HOST_STATUS_REG) & SPI_HOST_STATUS_READY_MASK)) { + } + // Program a TX command segment. + // Doing this before providing TX data avoids the TX FIFO size being a hard limit. + DEV_WRITE(spi + SPI_HOST_COMMAND_REG, + ((1 << SPI_HOST_COMMAND_CSAAT_OFFSET) | SPI_HOST_COMMAND_DIRECTION_TRANSMIT | + (((len-1) << SPI_HOST_COMMAND_LEN_OFF) & SPI_HOST_COMMAND_LEN_MASK))); + // Load TX data using a fast full-word loop followed by a slower clean-up loop. + // The hope with the fast loop is + uint32_t by = 0; + uint32_t data_word; + if (3 < len) { + while (by < (len - 3u)) { + // Prepare a full 32-bit word of data + data_word = data[by]; + data_word |= data[by+1] << 8; + data_word |= data[by+2] << 16; + data_word |= data[by+3] << 24; + // Wait for TX FIFO space + while (DEV_READ(spi + SPI_HOST_STATUS_REG) & SPI_HOST_STATUS_TXFULL_MASK) { + } + // Write data to the SPI Host TX FIFO + DEV_WRITE(spi + SPI_HOST_TXDATA_REG, data_word); + by += 4; + } + } + if (by < len) { + // Prepare a partial word containing remaining data + data_word = data[by]; + if (len & 0x2) { + data_word |= data[by+1] << 8; + if (len & 0x1) { + data_word |= data[by+2] << 16; + } + } + // Wait for TX FIFO space + while (DEV_READ(spi + SPI_HOST_STATUS_REG) & SPI_HOST_STATUS_TXFULL_MASK) { + } + // Write data to the SPI Host TX FIFO + DEV_WRITE(spi + SPI_HOST_TXDATA_REG, data_word); + } + } diff --git a/sw/device/lib/runtime/sdcard.h b/sw/device/lib/runtime/sdcard.h new file mode 100644 index 00000000..f764998f --- /dev/null +++ b/sw/device/lib/runtime/sdcard.h @@ -0,0 +1,62 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "hal/mmio.h" +#include "hal/uart.h" +#include +// #include +#include + +// Compile-switches for sdcard-utils code. +// - Enable/disable CRC checking on SD Card traffic +#define SDCARD_CRC_ON true +// - Enable/disable logging over UART +#define SDCARD_LOG_ON true + +// Transfers in SPI mode are always in terms of 512-byte blocks. +#define SDCARD_BLOCK_LEN (512u) + +// SD command codes. (Section 7.3.1) +#define SDCARD_CMD_GO_IDLE_STATE (0u) +#define SDCARD_CMD_SEND_OP_COND (1u) +#define SDCARD_CMD_SEND_IF_COND (8u) +#define SDCARD_CMD_SEND_CSD (9u) +#define SDCARD_CMD_SEND_CID (10u) +#define SDCARD_CMD_STOP_TRANSMISSION (12u) +#define SDCARD_CMD_SET_BLOCKLEN (16u) +#define SDCARD_CMD_READ_SINGLE_BLOCK (17u) +#define SDCARD_CMD_READ_MULTIPLE_BLOCK (18u) +#define SDCARD_CMD_WRITE_SINGLE_BLOCK (24u) +#define SDCARD_CMD_WRITE_MULTIPLE_BLOCK (25u) +#define SDCARD_SD_SEND_OP_COND (41u) +#define SDCARD_CMD_APP_CMD (55u) +#define SDCARD_CMD_READ_OCR (58u) +#define SDCARD_CMD_CRC_ON_OFF (59u) + +// SD Control Tokens. (Section 7.3.3) +// Start Block Token precedes data block, for all but Multiple Block Write. +#define SDCARD_START_BLOCK_TOKEN (0xfeu) +// Start Block Token used for Multiple Block Write operations. +#define SDCARD_START_BLOCK_TOKEN_MW (0xfcu) +// Stop Transaction Token, for Multiple Block Writes. +#define SDCARD_STOP_TRAN_TOKEN (0xfdu) + +// 'Public' function declarations +void deselect_card(spi_host_t spi); +bool init(spi_host_t spi, uart_t uart); +bool read_cid(spi_host_t spi, uint8_t *buf, uint32_t len, uart_t uart); +bool read_csd(spi_host_t spi, uint8_t *buf, uint32_t len, uart_t uart); +bool read_blocks(spi_host_t spi, uint32_t block, uint8_t *buf, uint32_t num_blocks, uart_t uart); +bool write_blocks(spi_host_t spi, uint32_t block, uint8_t *buf, uint32_t num_blocks, uart_t uart); +void send_command(spi_host_t spi, uint8_t cmdCode, uint32_t arg, uart_t uart); +uint8_t get_response_byte(spi_host_t spi); +uint8_t get_response_R1(spi_host_t spi, uart_t uart); +void wait_not_busy(spi_host_t spi); +uint8_t get_response_R1b(spi_host_t spi, uart_t uart); +uint8_t get_data_response_busy(spi_host_t spi); +void get_response_R3(spi_host_t spi, uart_t uart); +uint8_t calc_crc7(const uint8_t *data, uint32_t len); +uint16_t calc_crc16(const uint8_t *data, uint32_t len); diff --git a/sw/device/tests/CMakeLists.txt b/sw/device/tests/CMakeLists.txt index 059ed967..45fb37d5 100644 --- a/sw/device/tests/CMakeLists.txt +++ b/sw/device/tests/CMakeLists.txt @@ -15,7 +15,8 @@ mocha_add_test(NAME plic_smoketest SOURCES plic/smoketest.c LIBRARIES ${LIBS} FP # Cannot currently run this on FPGA because the boot ROM expects the test to be provided on the SPI again. mocha_add_test(NAME rstmgr_software_reset SOURCES rstmgr/software_reset.c LIBRARIES ${LIBS}) mocha_add_test(NAME spi_device_smoketest SOURCES spi_device/smoketest.c LIBRARIES ${LIBS} FPGA) -mocha_add_test(NAME spi_host_smoketest SOURCES spi_host/smoketest.c LIBRARIES ${LIBS} FPGA) +# SPI Host currently has no working smoketest following switch from loopback to SD card connections +# mocha_add_test(NAME spi_host_smoketest SOURCES spi_host/smoketest.c LIBRARIES ${LIBS} FPGA) mocha_add_test(NAME tag_controller_smoketest SOURCES tag_controller/smoketest.c LIBRARIES ${LIBS} FPGA) mocha_add_test(NAME tag_controller_tag_test SOURCES tag_controller/tag_test.c LIBRARIES ${LIBS} FPGA) mocha_add_test(NAME timer_smoketest SOURCES timer/smoketest.c LIBRARIES ${LIBS} FPGA)