Files
tonberry-pico/software/modules/rp2_sd/sd_spi.c
Matthias Blankertz 7f8282315e
All checks were successful
Build RPi Pico firmware image / Build-Firmware (push) Successful in 5m29s
Check code formatting / Check-C-Format (push) Successful in 9s
Check code formatting / Check-Python-Flake8 (push) Successful in 9s
Check code formatting / Check-Bash-Shellcheck (push) Successful in 4s
Run unit tests on host / Run-Unit-Tests (push) Successful in 8s
Restructure sources
The python and C modules that are supposed to be built into the firmware
image (i.e. those that are in manifest.py or in USER_C_MODULES) have
been moved to the software/modules directory.

The software/src directory should now only contain python scripts and
other files that should be installed to the Picos flash filesystem. The
idea is that these should be those scripts that implement the
application behaviour, as these are the ones that a user who does not
want to build the whole firmware themself wants to modify.
2025-04-01 22:05:30 +02:00

294 lines
12 KiB
C

#include "hardware/pio.h"
#include "sd_spi.h"
#include "sd_util.h"
#include "sd_spi_pio.pio.h"
#include "hardware/dma.h"
#include <hardware/gpio.h>
#include <hardware/spi.h>
#include <hardware/sync.h>
#include <pico/time.h>
#include <string.h>
struct sd_dma_context {
uint8_t *read_buf;
size_t len;
uint8_t crc_buf[2];
uint8_t read_token_buf;
uint8_t wrdata;
_Atomic enum { DMA_READ_TOKEN, DMA_READ, DMA_IDLE } state;
};
struct sd_spi_context {
struct sd_dma_context sd_dma_context;
int spi_sm;
unsigned spi_offset;
int spi_dma_rd, spi_dma_wr, spi_dma_rd_crc;
dma_channel_config spi_dma_rd_cfg, spi_dma_wr_cfg, spi_dma_rd_crc_cfg;
int mosi, miso, sck, ss;
bool initialized;
};
/* We only realistically need one context, so reduce overhead by statically allocating it here */
static struct sd_spi_context sd_spi_context = {};
static void __time_critical_func(sd_spi_write_blocking)(const uint8_t *data, size_t len)
{
if (len == 0)
return;
pio_sm_put(SD_PIO, sd_spi_context.spi_sm, data[0] << 24);
for (size_t i = 1; i < len; ++i) {
pio_sm_put(SD_PIO, sd_spi_context.spi_sm, data[i] << 24);
pio_sm_get_blocking(SD_PIO, sd_spi_context.spi_sm);
}
pio_sm_get_blocking(SD_PIO, sd_spi_context.spi_sm);
assert(pio_sm_is_tx_fifo_empty(SD_PIO, sd_spi_context.spi_sm));
assert(pio_sm_is_rx_fifo_empty(SD_PIO, sd_spi_context.spi_sm));
}
static void __time_critical_func(sd_spi_read_blocking)(uint8_t wrdata, uint8_t *data, size_t len)
{
if (len == 0)
return;
pio_sm_put(SD_PIO, sd_spi_context.spi_sm, wrdata << 24);
for (size_t i = 0; i < len - 1; ++i) {
pio_sm_put(SD_PIO, sd_spi_context.spi_sm, wrdata << 24);
data[i] = pio_sm_get_blocking(SD_PIO, sd_spi_context.spi_sm);
}
data[len - 1] = pio_sm_get_blocking(SD_PIO, sd_spi_context.spi_sm);
assert(pio_sm_is_tx_fifo_empty(SD_PIO, sd_spi_context.spi_sm));
assert(pio_sm_is_rx_fifo_empty(SD_PIO, sd_spi_context.spi_sm));
}
static void __time_critical_func(sd_spi_dma_isr)(void)
{
if (dma_channel_get_irq0_status(sd_spi_context.spi_dma_rd)) {
dma_channel_acknowledge_irq0(sd_spi_context.spi_dma_rd);
if (sd_spi_context.sd_dma_context.state == DMA_READ_TOKEN) {
if (sd_spi_context.sd_dma_context.read_token_buf != 0xff) {
if (sd_spi_context.sd_dma_context.read_token_buf == 0xfe) {
channel_config_set_chain_to(&sd_spi_context.spi_dma_rd_cfg, sd_spi_context.spi_dma_rd_crc);
channel_config_set_irq_quiet(&sd_spi_context.spi_dma_rd_cfg, true);
dma_channel_configure(sd_spi_context.spi_dma_rd, &sd_spi_context.spi_dma_rd_cfg,
sd_spi_context.sd_dma_context.read_buf, &SD_PIO->rxf[sd_spi_context.spi_sm],
sd_spi_context.sd_dma_context.len, false);
dma_channel_configure(sd_spi_context.spi_dma_wr, &sd_spi_context.spi_dma_wr_cfg,
&SD_PIO->txf[sd_spi_context.spi_sm], &sd_spi_context.sd_dma_context.wrdata,
sd_spi_context.sd_dma_context.len + 2, false);
dma_channel_configure(sd_spi_context.spi_dma_rd_crc, &sd_spi_context.spi_dma_rd_crc_cfg,
sd_spi_context.sd_dma_context.crc_buf, &SD_PIO->rxf[sd_spi_context.spi_sm], 2,
false);
dma_start_channel_mask((1 << sd_spi_context.spi_dma_rd) | (1 << sd_spi_context.spi_dma_wr));
sd_spi_context.sd_dma_context.state = DMA_READ;
} else {
// Bad read token, abort transfer
sd_spi_context.sd_dma_context.state = DMA_IDLE;
}
} else {
// try again
dma_channel_configure(sd_spi_context.spi_dma_rd, &sd_spi_context.spi_dma_rd_cfg,
&sd_spi_context.sd_dma_context.read_token_buf,
&SD_PIO->rxf[sd_spi_context.spi_sm], 1, false);
dma_channel_configure(sd_spi_context.spi_dma_wr, &sd_spi_context.spi_dma_wr_cfg,
&SD_PIO->txf[sd_spi_context.spi_sm], &sd_spi_context.sd_dma_context.wrdata, 1,
false);
dma_start_channel_mask((1 << sd_spi_context.spi_dma_rd) | (1 << sd_spi_context.spi_dma_wr));
}
}
}
if (dma_channel_get_irq0_status(sd_spi_context.spi_dma_rd_crc)) {
dma_channel_acknowledge_irq0(sd_spi_context.spi_dma_rd_crc);
assert(sd_spi_context.sd_dma_context.state == DMA_READ);
sd_spi_context.sd_dma_context.state = DMA_IDLE;
}
}
void sd_spi_wait_complete(void)
{
while (sd_spi_context.sd_dma_context.state != DMA_IDLE)
__wfi();
}
bool sd_cmd_read_is_complete(void) { return sd_spi_context.sd_dma_context.state == DMA_IDLE; }
static bool sd_spi_read_dma(uint8_t wrdata, uint8_t *data, size_t len)
{
if (sd_spi_context.sd_dma_context.state != DMA_IDLE)
return false;
channel_config_set_chain_to(&sd_spi_context.spi_dma_rd_cfg, sd_spi_context.spi_dma_rd);
channel_config_set_irq_quiet(&sd_spi_context.spi_dma_rd_cfg, false);
dma_channel_configure(sd_spi_context.spi_dma_rd, &sd_spi_context.spi_dma_rd_cfg,
&sd_spi_context.sd_dma_context.read_token_buf, &SD_PIO->rxf[sd_spi_context.spi_sm], 1, false);
dma_channel_configure(sd_spi_context.spi_dma_wr, &sd_spi_context.spi_dma_wr_cfg,
&SD_PIO->txf[sd_spi_context.spi_sm], &sd_spi_context.sd_dma_context.wrdata, 1, false);
sd_spi_context.sd_dma_context.state = DMA_READ_TOKEN;
sd_spi_context.sd_dma_context.len = len;
sd_spi_context.sd_dma_context.read_buf = data;
sd_spi_context.sd_dma_context.wrdata = wrdata;
dma_start_channel_mask((1 << sd_spi_context.spi_dma_rd) | (1 << sd_spi_context.spi_dma_wr));
return true;
}
static void sd_spi_cmd_send(const uint8_t cmd, const uint32_t arg)
{
uint8_t buf[6] = {0x40 | cmd, arg >> 24, arg >> 16, arg >> 8, arg, 0};
buf[5] = sd_crc7(5, buf) << 1 | 1;
gpio_put(sd_spi_context.ss, false);
// Write command, argument and CRC
sd_spi_write_blocking(buf, 6);
}
bool sd_cmd(const uint8_t cmd, const uint32_t arg, unsigned resplen, uint8_t resp[static resplen])
{
sd_spi_cmd_send(cmd, arg);
resp[0] = 0;
// Read up to 8 garbage bytes (0xff), followed by R1 (MSB is zero)
bool got_r1 = false;
for (int timeout = 0; timeout < 8; ++timeout) {
sd_spi_read_blocking(0xff, resp, 1);
if (!(resp[0] & 0x80)) {
got_r1 = true;
break;
}
}
if (got_r1 && (resp[0] & 0x7e) == 0) {
// read rest of response if R1 does not indicate an error
sd_spi_read_blocking(0xff, resp + 1, resplen - 1);
}
gpio_put(sd_spi_context.ss, true);
const uint8_t buf = 0xff;
// Ensure 8 SPI clock cycles after CS deasserted
sd_spi_write_blocking(&buf, 1);
return got_r1 && (resp[0] & 0x7e) == 0;
}
bool sd_cmd_read(uint8_t cmd, uint32_t arg, unsigned datalen, uint8_t data[static datalen])
{
if (!sd_cmd_read_start(cmd, arg, datalen, data))
return false;
return sd_cmd_read_complete();
}
bool sd_cmd_read_start(uint8_t cmd, uint32_t arg, unsigned datalen, uint8_t data[static datalen])
{
uint8_t buf[2];
sd_spi_cmd_send(cmd, arg);
// Read up to 8 garbage bytes (0xff), followed by R1 (MSB is zero)
bool got_r1 = false;
for (int timeout = 0; timeout < 8; ++timeout) {
sd_spi_read_blocking(0xff, buf, 1);
if (!(buf[0] & 0x80)) {
got_r1 = true;
break;
}
}
if (!got_r1 || buf[0] != 0x00)
goto abort;
if (!sd_spi_read_dma(0xff, data, datalen))
goto abort;
return true;
abort:
gpio_put(sd_spi_context.ss, true);
sd_spi_read_blocking(0xff, buf, 1);
return false;
}
bool sd_cmd_read_complete(void)
{
uint8_t buf;
sd_spi_wait_complete();
gpio_put(sd_spi_context.ss, true);
sd_spi_read_blocking(0xff, &buf, 1);
return (sd_spi_context.sd_dma_context.read_token_buf == 0xfe);
}
bool sd_spi_init(int mosi, int miso, int sck, int ss)
{
if (sd_spi_context.initialized)
return false;
if (!pio_can_add_program(SD_PIO, &sd_spi_pio_program))
return false;
sd_spi_context.spi_sm = pio_claim_unused_sm(SD_PIO, false);
if (sd_spi_context.spi_sm == -1)
return false;
sd_spi_context.spi_offset = pio_add_program(SD_PIO, &sd_spi_pio_program);
sd_spi_context.mosi = mosi;
sd_spi_context.miso = miso;
sd_spi_context.sck = sck;
sd_spi_context.ss = ss;
sd_spi_pio_program_init(SD_PIO, sd_spi_context.spi_sm, sd_spi_context.spi_offset, sd_spi_context.mosi,
sd_spi_context.miso, sd_spi_context.sck, SD_INIT_BITRATE);
pio_sm_set_enabled(SD_PIO, sd_spi_context.spi_sm, true);
gpio_init(sd_spi_context.ss);
gpio_set_dir(sd_spi_context.ss, true);
sd_spi_context.spi_dma_rd = dma_claim_unused_channel(false);
sd_spi_context.spi_dma_rd_crc = dma_claim_unused_channel(false);
sd_spi_context.spi_dma_wr = dma_claim_unused_channel(false);
if (sd_spi_context.spi_dma_rd == -1 || sd_spi_context.spi_dma_rd_crc == -1 || sd_spi_context.spi_dma_wr == -1)
return false;
sd_spi_context.spi_dma_rd_cfg = dma_channel_get_default_config(sd_spi_context.spi_dma_rd);
channel_config_set_read_increment(&sd_spi_context.spi_dma_rd_cfg, false);
channel_config_set_write_increment(&sd_spi_context.spi_dma_rd_cfg, true);
channel_config_set_dreq(&sd_spi_context.spi_dma_rd_cfg, pio_get_dreq(SD_PIO, sd_spi_context.spi_sm, false));
channel_config_set_transfer_data_size(&sd_spi_context.spi_dma_rd_cfg, DMA_SIZE_8);
sd_spi_context.spi_dma_rd_crc_cfg = dma_channel_get_default_config(sd_spi_context.spi_dma_rd_crc);
channel_config_set_read_increment(&sd_spi_context.spi_dma_rd_crc_cfg, false);
channel_config_set_dreq(&sd_spi_context.spi_dma_rd_crc_cfg, pio_get_dreq(SD_PIO, sd_spi_context.spi_sm, false));
channel_config_set_transfer_data_size(&sd_spi_context.spi_dma_rd_crc_cfg, DMA_SIZE_8);
sd_spi_context.spi_dma_wr_cfg = dma_channel_get_default_config(sd_spi_context.spi_dma_wr);
channel_config_set_read_increment(&sd_spi_context.spi_dma_wr_cfg, false);
channel_config_set_dreq(&sd_spi_context.spi_dma_wr_cfg, pio_get_dreq(SD_PIO, sd_spi_context.spi_sm, true));
channel_config_set_transfer_data_size(&sd_spi_context.spi_dma_wr_cfg, DMA_SIZE_8);
sd_spi_context.sd_dma_context.state = DMA_IDLE;
irq_add_shared_handler(DMA_IRQ_0, &sd_spi_dma_isr, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
dma_channel_set_irq0_enabled(sd_spi_context.spi_dma_rd, true);
dma_channel_set_irq0_enabled(sd_spi_context.spi_dma_rd_crc, true);
irq_set_enabled(DMA_IRQ_0, true);
gpio_put(sd_spi_context.ss, true);
uint8_t buf[16];
memset(buf, 0xff, 16);
// Ensure at least 74 SPI clock cycles without CS asserted
sd_spi_write_blocking(buf, 10);
sd_spi_context.initialized = true;
return true;
}
bool sd_spi_deinit(void)
{
if (!sd_spi_context.initialized)
return false;
if (sd_spi_context.sd_dma_context.state != DMA_IDLE)
return false;
dma_channel_set_irq0_enabled(sd_spi_context.spi_dma_rd, false);
dma_channel_set_irq0_enabled(sd_spi_context.spi_dma_rd_crc, false);
irq_remove_handler(DMA_IRQ_0, &sd_spi_dma_isr);
dma_channel_unclaim(sd_spi_context.spi_dma_rd);
dma_channel_unclaim(sd_spi_context.spi_dma_rd_crc);
dma_channel_unclaim(sd_spi_context.spi_dma_wr);
pio_remove_program(SD_PIO, &sd_spi_pio_program, sd_spi_context.spi_offset);
pio_sm_unclaim(SD_PIO, sd_spi_context.spi_sm);
sd_spi_context.initialized = false;
return true;
}
void sd_spi_set_bitrate(const int rate)
{
pio_sm_set_enabled(SD_PIO, sd_spi_context.spi_sm, false);
sd_spi_pio_program_init(SD_PIO, sd_spi_context.spi_sm, sd_spi_context.spi_offset, sd_spi_context.mosi,
sd_spi_context.miso, sd_spi_context.sck, rate);
pio_sm_set_enabled(SD_PIO, sd_spi_context.spi_sm, true);
}