blob: f0b28091e970c00344ecc6543267f4b689cbbc28 [file] [log] [blame]
// SPI SD card storage interface
#include "storage.hpp"
#include "pico/time.h"
#include "pico/binary_info.h"
#include "hardware/clocks.h"
#include "engine/engine.hpp"
#include "config.h"
#include "spi.pio.h"
#define SD_TIMEOUT 10
static PIO sd_pio = pio1;
static int sd_sm = 0;
static bool sd_io_initialised = false;
static uint32_t card_size_blocks = 0;
static bool is_hcs = false;
static void spi_write(const uint8_t *buf, size_t len) {
size_t tx_remain = len, rx_remain = len;
auto txfifo = (io_rw_8 *) &sd_pio->txf[sd_sm];
auto rxfifo = (io_rw_8 *) &sd_pio->rxf[sd_sm];
while (tx_remain || rx_remain) {
if (tx_remain && !pio_sm_is_tx_fifo_full(sd_pio, sd_sm)) {
*txfifo = *buf++;
--tx_remain;
}
if (rx_remain && !pio_sm_is_rx_fifo_empty(sd_pio, sd_sm)) {
(void) *rxfifo;
--rx_remain;
}
}
}
static void spi_read(uint8_t *buf, size_t len) {
size_t rx_remain = len;
auto txfifo = (io_rw_8 *) &sd_pio->txf[sd_sm];
auto rxfifo = (io_rw_8 *) &sd_pio->rxf[sd_sm];
// assume FIFO is empty
*txfifo = 0xFF;
*txfifo = 0xFF;
*txfifo = 0xFF;
*txfifo = 0xFF;
while(rx_remain) {
if (!pio_sm_is_rx_fifo_empty(sd_pio, sd_sm)) {
*buf++ = *rxfifo;
if(--rx_remain > 3)
*txfifo = 0xFF;
}
}
}
static uint8_t spi_transfer_byte(uint8_t b) {
while(pio_sm_is_tx_fifo_full(sd_pio, sd_sm));
*(io_rw_8 *)&sd_pio->txf[sd_sm] = b;
while(pio_sm_is_rx_fifo_empty(sd_pio, sd_sm));
return *(io_rw_8 *)&sd_pio->rxf[sd_sm];
}
static bool sd_wait_ready() {
absolute_time_t timeout_time = make_timeout_time_ms(SD_TIMEOUT);
while(spi_transfer_byte(0xFF) != 0xFF) {
if(absolute_time_diff_us(get_absolute_time(), timeout_time) <= 0)
return false;
}
return true;
}
static bool sd_begin() {
gpio_put(SD_CS, 0);
// wait for ready
if(!sd_wait_ready()) {
gpio_put(SD_CS, 1);
return false;
}
return true;
}
static void sd_end() {
gpio_put(SD_CS, 1);
spi_transfer_byte(0xFF);
}
static void sd_write_command(uint8_t cmd, uint32_t param, uint8_t crc) {
uint8_t buf[]{
uint8_t(0x40 | cmd),
uint8_t(param >> 24),
uint8_t(param >> 16),
uint8_t(param >> 8),
uint8_t(param),
crc
};
spi_write(buf, sizeof(buf));
}
uint8_t sd_read_response() {
uint8_t ret = 0;
int attempt = 0;
while(((ret = spi_transfer_byte(0xFF)) & 0x80) && (attempt++ < 8));
return ret;
}
static uint8_t sd_command1(uint8_t cmd, uint32_t param, uint8_t crc = 1) {
if(!sd_begin())
return 0xFF;
sd_write_command(cmd, param, crc);
uint8_t res = sd_read_response();
sd_end();
return res;
}
static uint8_t sd_command3_7(uint8_t cmd, uint32_t param, uint32_t &res_data, uint8_t crc = 1) {
if(!sd_begin())
return 0xFF;
sd_write_command(cmd, param, crc);
uint8_t res = sd_read_response();
// no error bits
if((res & 0xFE) == 0) {
spi_read((uint8_t *)&res_data, 4);
res_data = __builtin_bswap32(res_data);
}
sd_end();
return res;
}
static bool sd_read_block(uint8_t *buffer, int len) {
// wait for start token
absolute_time_t timeout_time = make_timeout_time_ms(SD_TIMEOUT * 10);
while(spi_transfer_byte(0xFF) != 0xFE) {
if(absolute_time_diff_us(get_absolute_time(), timeout_time) <= 0)
return false;
}
spi_read(buffer, len);
// crc
spi_transfer_byte(0xFF);
spi_transfer_byte(0xFF);
return true;
}
static uint8_t sd_command_read_block(uint8_t cmd, uint32_t addr, uint8_t *buffer) {
if(!sd_begin())
return 0xFF;
sd_write_command(cmd, addr, 1);
uint8_t res = sd_read_response();
if(res == 0) {
int len = 512;
if(cmd == 9 || cmd == 10) // CSD/CID are 16 bytes
len = 16;
else if(cmd == 6) // SWITCH
len = 64;
if(!sd_read_block(buffer, len))
return 0xFF;
}
sd_end();
return res;
}
static uint8_t sd_command_read_block_multiple(uint8_t cmd, uint32_t addr, uint8_t *buffer, int count, uint32_t &read) {
if(!sd_begin())
return 0xFF;
sd_write_command(cmd, addr, 1);
uint8_t res = sd_read_response();
if(res == 0) {
// likely only used with CMD 18
const int len = 512;
while(count--) {
if(!sd_read_block(buffer, len))
return 0xFF;
read += len;
buffer += len;
}
// now CMD12 to end
sd_write_command(12, 0, 1); // STOP_TRANSMISSION
spi_transfer_byte(0xFF); // stuff byte
sd_read_response();
}
sd_end();
return res;
}
static uint8_t sd_command_write_block(uint8_t cmd, uint32_t addr, const uint8_t *buffer) {
if(!sd_begin())
return 0xFF;
sd_write_command(cmd, addr, 1);
uint8_t res = sd_read_response();
if(res == 0) {
if(!sd_wait_ready()) {
sd_end();
return 0xFF;
}
spi_transfer_byte(0xFE); // start token (different for CMD25)
int len = 512;
for(int i = 0; i < len; i++)
spi_transfer_byte(*buffer++);
// crc
spi_transfer_byte(0xFF);
spi_transfer_byte(0xFF);
auto dataRes = spi_transfer_byte(0xFF) & 0x1F;
// check accepted
if(dataRes != 0x5) {
sd_end();
return 0xFF;
}
// wait for not busy
while(spi_transfer_byte(0xFF) == 0);
}
sd_end();
return res;
}
bool storage_init() {
bi_decl_if_func_used(bi_4pins_with_names(SD_MISO, "SD RX", SD_MOSI, "SD TX", SD_SCK, "SD SCK", SD_CS, "SD CS"));
// this will be called again it it fails
if(!sd_io_initialised) {
int base = 0;
#if SD_MOSI >= 32 || SD_MISO >= 32 || SD_SCK >= 32
// assumes anything else using this PIO can also deal with the base
static_assert(SD_MOSI >= 16 && SD_MISO >= 16 && SD_SCK >= 16);
pio_set_gpio_base(sd_pio, 16);
base = 16;
#endif
uint offset = pio_add_program(sd_pio, &spi_cpha0_program);
sd_sm = pio_claim_unused_sm(sd_pio, true);
pio_sm_config c = spi_cpha0_program_get_default_config(offset);
sm_config_set_out_pins(&c, SD_MOSI, 1);
sm_config_set_in_pins(&c, SD_MISO);
sm_config_set_sideset_pins(&c, SD_SCK);
sm_config_set_out_shift(&c, false, true, 8);
sm_config_set_in_shift(&c, false, true, 8);
// MOSI, SCK output are low, MISO is input
pio_sm_set_pins_with_mask64(sd_pio, sd_sm, 0, (1ull << SD_SCK) | (1ull << SD_MOSI));
pio_sm_set_pindirs_with_mask64(sd_pio, sd_sm, (1ull << SD_SCK) | (1ull << SD_MOSI), (1ull << SD_SCK) | (1ull << SD_MOSI) | (1ull << SD_MISO));
pio_gpio_init(sd_pio, SD_MOSI);
pio_gpio_init(sd_pio, SD_MISO);
pio_gpio_init(sd_pio, SD_SCK);
gpio_pull_up(SD_MISO);
// SPI is synchronous, so bypass input synchroniser to reduce input delay.
hw_set_bits(&sd_pio->input_sync_bypass, 1u << (SD_MISO - base));
pio_sm_init(sd_pio, sd_sm, offset, &c);
pio_sm_set_enabled(sd_pio, sd_sm, true);
// CS
gpio_init(SD_CS);
gpio_set_dir(SD_CS, GPIO_OUT);
gpio_put(SD_CS, 1);
sd_io_initialised = true;
}
// go slow for init
pio_sm_set_clkdiv(sd_pio, sd_sm, 250);
uint8_t buf[]{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
spi_write(buf, sizeof(buf));
// send cmd0
uint8_t res = 0xFF;
for(int retry = 0; retry < 16 && res == 0xFF; retry++)
res = sd_command1(0, 0, 0x95); // GO_IDLE_STATE
if(res != 0x1) {
blit::debugf("CMD0 failed (res %X)\n", res);
return false;
}
// check voltage range / check if SDv2
bool is_v2 = true;
uint32_t data;
res = sd_command3_7(8, 0x1AA, data, 0x87); // SEND_IF_COND
if(res == 5) {
// not supported, old card
is_v2 = false;
} else if(res != 1) {
blit::debugf("CMD8 failed (res %X)\n", res);
return false;
} else if(data != 0x1AA) {
blit::debugf("CMD8 returned unexpected %X!\n", data);
return false;
}
// init
while(res != 0) {
res = sd_command1(55, 0, 0x65); // APP_CMD
if(res == 0x1)
res = sd_command1(41, is_v2 ? 0x40000000 : 0, is_v2 ? 0x77 : 0xE5); // APP_SEND_OP_COND
else if(res != 0xFF) {
blit::debugf("CMD55 failed (res %X)\n", res);
return false;
}
if(res > 1 && res != 0xFF) {
blit::debugf("ACMD41 failed (res %X)\n", res);
return false;
}
}
// defaults, but make sure
sd_command1(59, 0); // CRC_ON_OFF
sd_command1(16, 512); // SET_BLOCKLEN
// read OCR
res = sd_command3_7(58, 0, data); // READ_OCR
if(res != 0) {
blit::debugf("CMD58 failed (res %X)\n", res);
return false;
}
is_hcs = false;
if((data & 0xC0000000) == 0xC0000000)
is_hcs = true;
// read CSD
uint8_t csd[16];
res = sd_command_read_block(9, 0, csd); // SEND_CSD
if(res != 0) {
blit::debugf("CMD9 failed (res %X)\n", res);
return false;
}
// v1
if((csd[0] >> 6) == 0) {
int c_size = ((csd[6] & 0x3) << 10) | (csd[7] << 2) | (csd[8] >> 6);
int c_size_mult = ((csd[9] & 0x3) << 1) | (csd[10] >> 7);
int readBlLen = csd[5] & 0xF;
uint32_t num_blocks = uint32_t(c_size + 1) * (2 << (c_size_mult + 1));
uint32_t size_bytes = num_blocks * (2 << (readBlLen - 1));
card_size_blocks = size_bytes / 512;
} else { // v2
// measured in 512k blocks
card_size_blocks = (((int32_t(csd[7] & 0x3F) << 16) | (uint32_t(csd[8]) << 8) | csd[9]) + 1) * 1024;
}
blit::debugf("Detected %s card, size %i blocks\n", is_v2 ? (is_hcs ? "SDHC" : "SDv2") : "SDv1", card_size_blocks);
// set speed (PIO program is 2 cycles/clock)
// according to the SD specs high speed in SPI mode is "Same as SD mode", helpful.
#if SD_SPI_OVERCLOCK
// these are too fast, but usually okay and the best we can do with a 125MHz clock
// 75MHz is definitely not okay (from 150MHz clock on RP2350)
int clkdiv = std::ceil(clock_get_hz(clk_sys) / (31250000.0f * 2.0f));
int clkdiv_high_speed = std::ceil(clock_get_hz(clk_sys) / (62500000.0f * 2.0f));
#else
int clkdiv = std::ceil(clock_get_hz(clk_sys) / (25000000.0f * 2.0f));
int clkdiv_high_speed = std::ceil(clock_get_hz(clk_sys) / (50000000.0f * 2.0f));
#endif
// attempt high speed
uint8_t switch_res[64];
if(sd_command_read_block(6, 0x80FFFFF1, switch_res) == 0) { // SWITCH
if((switch_res[16] & 0xF) == 1)
clkdiv = clkdiv_high_speed; // successful switch, use high speed
}
pio_sm_set_clkdiv(sd_pio, sd_sm, clkdiv);
pio_sm_restart(sd_pio, sd_sm);
return true;
}
bool is_storage_available() {
return card_size_blocks != 0;
}
void get_storage_size(uint16_t &block_size, uint32_t &num_blocks) {
block_size = 512;
num_blocks = card_size_blocks;
}
int32_t storage_read(uint32_t sector, uint32_t offset, void *buffer, uint32_t size_bytes) {
// offset should be 0 (block size == msc buffer size)
if(!is_hcs)
sector *= 512;
auto blocks = size_bytes / 512;
if(blocks == 1) {
if(sd_command_read_block(17, sector, (uint8_t *)buffer) != 0) // READ_SINGLE_BLOCK
return 0;
return size_bytes;
} else {
uint32_t read = 0;
sd_command_read_block_multiple(18, sector, (uint8_t *)buffer, blocks, read);
return read;
}
return size_bytes;
}
int32_t storage_write(uint32_t sector, uint32_t offset, const uint8_t *buffer, uint32_t size_bytes) {
// offset should be 0
if(!is_hcs)
sector *= 512;
auto blocks = size_bytes / 512;
int32_t written = 0;
// TODO: multi block writes
while(blocks--) {
if(sd_command_write_block(24, sector, (uint8_t *)buffer + written) != 0) // WRITE_SINGLE_BLOCK
break;
written += 512;
if(!is_hcs)
sector += 512;
else
sector++;
}
return written;
}