Add rp2 PIO and DMA fast SD card driver
The builtin micropython SD card driver using the default micropython SPI implementation suffered from very low read speeds. Add an optimized SD card driver using rp2 PIO and DMA. Currently read only, write support to be added later. Signed-off-by: Matthias Blankertz <matthias@blankertz.org>
This commit is contained in:
@@ -15,3 +15,5 @@ set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)
|
||||
|
||||
set(GEN_PINS_BOARD_CSV "${CMAKE_CURRENT_LIST_DIR}/pins.csv")
|
||||
set(GEN_PINS_CSV_ARG --board-csv "${GEN_PINS_BOARD_CSV}")
|
||||
|
||||
add_link_options("-Wl,--print-memory-usage")
|
||||
|
||||
@@ -8,7 +8,7 @@ set -eu
|
||||
make -C mpy-cross -j "$(nproc)"
|
||||
make -C ports/rp2 BOARD=TONBERRY_RPI_PICO_W BOARD_DIR="$TOPDIR"/boards/RPI_PICO_W clean
|
||||
make -C ports/rp2 BOARD=TONBERRY_RPI_PICO_W BOARD_DIR="$TOPDIR"/boards/RPI_PICO_W \
|
||||
USER_C_MODULES="$TOPDIR"/src/audiocore/micropython.cmake -j "$(nproc)"
|
||||
USER_C_MODULES="$TOPDIR"/src/micropython.cmake -j "$(nproc)"
|
||||
)
|
||||
|
||||
echo "Output in lib/micropython/ports/rp2/build-TONBERRY_RPI_PICO_W/firmware.uf2"
|
||||
|
||||
2
software/src/micropython.cmake
Normal file
2
software/src/micropython.cmake
Normal file
@@ -0,0 +1,2 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/audiocore/micropython.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/rp2_sd/micropython.cmake)
|
||||
19
software/src/rp2_sd/micropython.cmake
Normal file
19
software/src/rp2_sd/micropython.cmake
Normal file
@@ -0,0 +1,19 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
add_library(usermod_rp2_sd INTERFACE)
|
||||
|
||||
pico_generate_pio_header(usermod_rp2_sd ${CMAKE_CURRENT_LIST_DIR}/sd_spi_pio.pio)
|
||||
|
||||
target_sources(usermod_rp2_sd INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/module.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/sd.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/sd_spi.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sd_spi_pio.pio.h
|
||||
)
|
||||
|
||||
target_include_directories(usermod_rp2_sd INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(usermod INTERFACE usermod_rp2_sd)
|
||||
117
software/src/rp2_sd/module.c
Normal file
117
software/src/rp2_sd/module.c
Normal file
@@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
#include "py/obj.h"
|
||||
#include "sd.h"
|
||||
|
||||
// Include MicroPython API.
|
||||
#include "py/mperrno.h"
|
||||
#include "py/runtime.h"
|
||||
|
||||
// This module is RP2 specific
|
||||
#include "mphalport.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
const mp_obj_type_t sdcard_type;
|
||||
struct sdcard_obj {
|
||||
mp_obj_base_t base;
|
||||
struct sd_context sd_context;
|
||||
};
|
||||
|
||||
static void sdcard_init(struct sdcard_obj *obj, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
|
||||
{
|
||||
enum { ARG_mosi, ARG_miso, ARG_sck, ARG_ss, ARG_baudrate };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{MP_QSTR_mosi, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
|
||||
{MP_QSTR_miso, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
|
||||
{MP_QSTR_sck, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
|
||||
{MP_QSTR_ss, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
|
||||
{MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 15000000}},
|
||||
};
|
||||
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
const mp_hal_pin_obj_t pin_mosi = mp_hal_get_pin_obj(args[ARG_mosi].u_obj);
|
||||
const mp_hal_pin_obj_t pin_miso = mp_hal_get_pin_obj(args[ARG_miso].u_obj);
|
||||
const mp_hal_pin_obj_t pin_sck = mp_hal_get_pin_obj(args[ARG_sck].u_obj);
|
||||
const mp_hal_pin_obj_t pin_ss = mp_hal_get_pin_obj(args[ARG_ss].u_obj);
|
||||
const unsigned baudrate = args[ARG_baudrate].u_int;
|
||||
if (!sd_init(&obj->sd_context, pin_mosi, pin_miso, pin_sck, pin_ss, baudrate)) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("sd_init() failed"));
|
||||
}
|
||||
}
|
||||
|
||||
static mp_obj_t sdcard_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args)
|
||||
{
|
||||
struct sdcard_obj *sdcard = mp_obj_malloc(struct sdcard_obj, &sdcard_type);
|
||||
mp_map_t kw_args;
|
||||
mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
|
||||
sdcard_init(sdcard, n_args, args, &kw_args);
|
||||
return MP_OBJ_FROM_PTR(sdcard);
|
||||
}
|
||||
|
||||
static mp_obj_t sdcard_deinit(mp_obj_t self_obj)
|
||||
{
|
||||
struct sdcard_obj *self = MP_OBJ_TO_PTR(self_obj);
|
||||
if (!sd_deinit(&self->sd_context))
|
||||
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("sd_deinit() failed"));
|
||||
return mp_const_none;
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_1(sdcard_deinit_obj, sdcard_deinit);
|
||||
|
||||
static mp_obj_t sdcard_readblocks(mp_obj_t self_obj, mp_obj_t block_obj, mp_obj_t buf_obj)
|
||||
{
|
||||
struct sdcard_obj *self = MP_OBJ_TO_PTR(self_obj);
|
||||
const int start_block = mp_obj_get_int(block_obj);
|
||||
mp_buffer_info_t bufinfo;
|
||||
if (!mp_get_buffer(buf_obj, &bufinfo, MP_BUFFER_WRITE))
|
||||
mp_raise_ValueError("Not a write buffer");
|
||||
if (bufinfo.len % 512 != 0)
|
||||
mp_raise_ValueError("Buffer length is invalid");
|
||||
const int nblocks = bufinfo.len / 512;
|
||||
for (int block = 0; block < nblocks; block++) {
|
||||
// TODO: Implement CMD18 read multiple blocks
|
||||
if (!sd_readblock(&self->sd_context, start_block + block, bufinfo.buf + block * 512))
|
||||
mp_raise_OSError(MP_EIO);
|
||||
}
|
||||
return mp_const_none;
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_3(sdcard_readblocks_obj, sdcard_readblocks);
|
||||
|
||||
static mp_obj_t sdcard_ioctl(mp_obj_t self_obj, mp_obj_t op_obj, mp_obj_t arg_obj)
|
||||
{
|
||||
struct sdcard_obj *self = MP_OBJ_TO_PTR(self_obj);
|
||||
int op = mp_obj_get_int(op_obj);
|
||||
switch (op) {
|
||||
case 4:
|
||||
return mp_obj_new_int(self->sd_context.blocks);
|
||||
case 5:
|
||||
return mp_obj_new_int(512);
|
||||
default:
|
||||
return mp_const_none;
|
||||
}
|
||||
};
|
||||
static MP_DEFINE_CONST_FUN_OBJ_3(sdcard_ioctl_obj, sdcard_ioctl);
|
||||
|
||||
static const mp_rom_map_elem_t sdcard_locals_dict_table[] = {
|
||||
{MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&sdcard_deinit_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&sdcard_ioctl_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&sdcard_readblocks_obj)},
|
||||
};
|
||||
static MP_DEFINE_CONST_DICT(sdcard_locals_dict, sdcard_locals_dict_table);
|
||||
|
||||
MP_DEFINE_CONST_OBJ_TYPE(sdcard_type, MP_QSTR_SDCard, MP_TYPE_FLAG_NONE, locals_dict, &sdcard_locals_dict, make_new,
|
||||
&sdcard_make_new);
|
||||
|
||||
static const mp_rom_map_elem_t rp2_sd_module_globals_table[] = {
|
||||
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_rp2_sd)},
|
||||
{MP_ROM_QSTR(MP_QSTR_SDCard), MP_ROM_PTR(&sdcard_type)},
|
||||
};
|
||||
static MP_DEFINE_CONST_DICT(rp2_sd_module_globals, rp2_sd_module_globals_table);
|
||||
|
||||
const mp_obj_module_t rp2_sd_cmodule = {
|
||||
.base = {&mp_type_module},
|
||||
.globals = (mp_obj_dict_t *)&rp2_sd_module_globals,
|
||||
};
|
||||
MP_REGISTER_MODULE(MP_QSTR_rp2_sd, rp2_sd_cmodule);
|
||||
267
software/src/rp2_sd/sd.c
Normal file
267
software/src/rp2_sd/sd.c
Normal file
@@ -0,0 +1,267 @@
|
||||
#include "sd.h"
|
||||
#include "sd_spi.h"
|
||||
#include "sd_util.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
// #define SD_DEBUG
|
||||
|
||||
#define SD_R1_ILLEGAL_COMMAND (1 << 2)
|
||||
|
||||
static bool sd_acmd(const uint8_t cmd, const uint32_t arg, unsigned resplen, uint8_t res[static resplen])
|
||||
{
|
||||
if (!sd_cmd(55, 0, 1, res))
|
||||
return false;
|
||||
if ((res[0] & 0x7e) != 0x00)
|
||||
return false;
|
||||
|
||||
return sd_cmd(cmd, arg, resplen, res);
|
||||
}
|
||||
|
||||
static bool sd_early_init(void)
|
||||
{
|
||||
uint8_t buf;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
if (sd_cmd(0, 0, 1, &buf)) {
|
||||
#ifdef SD_DEBUG
|
||||
printf("CMD0 resp %02hhx\n", buf);
|
||||
#endif
|
||||
if (buf == 0x01) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#ifdef SD_DEBUG
|
||||
printf("CMD0 timeout, try again...\n");
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool sd_check_interface_condition(void)
|
||||
{
|
||||
uint8_t buf[5];
|
||||
if (sd_cmd(8, 0x000001AA, 5, buf)) {
|
||||
if ((buf[3] & 0xf) != 0x1 || buf[4] != 0xAA) {
|
||||
printf("sd_init: check interface condition failed\n");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (buf[0] & SD_R1_ILLEGAL_COMMAND) {
|
||||
printf("sd_init: check interface condition returned illegal command - old card?\n");
|
||||
} else {
|
||||
printf("sd_init: check interface condition failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sd_send_op_cond(void)
|
||||
{
|
||||
uint8_t buf[1];
|
||||
bool use_acmd = true;
|
||||
for (int timeout = 0; timeout < 500; ++timeout) {
|
||||
bool result = false;
|
||||
if (use_acmd)
|
||||
result = sd_acmd(41, 0x40000000, 1, buf);
|
||||
else
|
||||
result = sd_cmd(1, 0x40000000, 1, buf);
|
||||
if (!result) {
|
||||
if (use_acmd && buf[0] & SD_R1_ILLEGAL_COMMAND) {
|
||||
#ifdef SD_DEBUG
|
||||
printf("sd_init: card does not understand ACMD41, try CMD1...\n");
|
||||
#endif
|
||||
continue;
|
||||
} else if (buf[0] != 0x01) {
|
||||
printf("sd_init: send_op_cond failed\n");
|
||||
return false;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (buf[0] == 0x00) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
printf("sd_init: send_op_cond: timeout waiting for !idle\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool sd_read_ocr(uint32_t *const ocr)
|
||||
{
|
||||
uint8_t buf[5];
|
||||
if (!sd_cmd(58, 0, 5, buf))
|
||||
return false;
|
||||
*ocr = buf[1] << 24 | buf[2] << 16 | buf[3] << 8 | buf[4];
|
||||
return true;
|
||||
}
|
||||
|
||||
static void sd_dump_cid [[maybe_unused]] (void)
|
||||
{
|
||||
uint8_t buf[16];
|
||||
if (sd_cmd_read(10, 0, 16, buf)) {
|
||||
const uint8_t crc = sd_crc7(15, buf);
|
||||
const uint8_t card_crc = buf[15] >> 1;
|
||||
if (card_crc != crc) {
|
||||
printf("CRC mismatch: Got %02hhx, expected %02hhx\n", card_crc, crc);
|
||||
// Some cheap SD cards always report CRC=0, don't fail in that case
|
||||
if (card_crc != 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t mid = buf[0];
|
||||
char oid[2], pnm[5];
|
||||
memcpy(oid, buf + 1, 2);
|
||||
memcpy(pnm, buf + 3, 5);
|
||||
uint8_t prv = buf[8];
|
||||
uint32_t psn = buf[9] << 24 | buf[10] << 16 | buf[11] << 8 | buf[12];
|
||||
int mdt_year = 2000 + ((buf[13] & 0xf) << 4 | (buf[14] & 0xf0) >> 4);
|
||||
int mdt_month = buf[14] & 0x0f;
|
||||
printf("CID: mid: %02hhx, oid: %.2s, pnm: %.5s, prv: %02hhx, psn: %08" PRIx32 ", mdt_year: %d, mdt_month: %d\n",
|
||||
mid, oid, pnm, prv, psn, mdt_year, mdt_month);
|
||||
}
|
||||
}
|
||||
|
||||
static bool sd_read_csd(struct sd_context *sd_context)
|
||||
{
|
||||
uint8_t buf[16];
|
||||
if (sd_cmd_read(9, 0, 16, buf)) {
|
||||
const uint8_t crc = sd_crc7(15, buf);
|
||||
const uint8_t card_crc = buf[15] >> 1;
|
||||
if (card_crc != crc) {
|
||||
printf("CRC mismatch: Got %02hhx, expected %02hhx\n", card_crc, crc);
|
||||
// Some cheap SD cards always report CRC=0, don't fail in that case
|
||||
if (card_crc != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const unsigned csd_ver = buf[0] >> 6;
|
||||
unsigned blocksize [[maybe_unused]] = 0;
|
||||
unsigned blocks = 0;
|
||||
unsigned version [[maybe_unused]] = 0;
|
||||
switch (csd_ver) {
|
||||
case 0: {
|
||||
if (sd_context->sdhc_sdxc) {
|
||||
printf("sd_init: Got CSD v1.0 but card is SDHC/SDXC?\n");
|
||||
return false;
|
||||
}
|
||||
const unsigned read_bl_len = buf[5] & 0xf;
|
||||
if (read_bl_len < 9 || read_bl_len > 11) {
|
||||
printf("Invalid read_bl_len in CSD 1.0\n");
|
||||
return false;
|
||||
}
|
||||
blocksize = 1 << (buf[5] & 0xf);
|
||||
const unsigned c_size_mult = (buf[9] & 0x1) << 2 | (buf[10] & 0xc0) >> 6;
|
||||
const unsigned c_size = (buf[6] & 0x3) << 10 | (buf[7] << 2) | (buf[8] & 0xc0) >> 6;
|
||||
blocks = (c_size + 1) * (1 << (c_size_mult + 2));
|
||||
version = 1;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
blocksize = 512;
|
||||
const unsigned c_size = (buf[7] & 0x3f) << 16 | buf[8] << 8 | buf[9];
|
||||
blocks = (c_size + 1) * 1024;
|
||||
version = 2;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
printf("sd_init: Got CSD v3.0, but SDUC does not support SPI.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sd_context->blocks = blocks;
|
||||
#ifdef SD_DEBUG
|
||||
printf("CSD version %u.0, blocksize %u, blocks %u, capacity %llu MiB\n", version, blocksize, blocks,
|
||||
((uint64_t)blocksize * blocks) / (1024 * 1024));
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sd_init(struct sd_context *sd_context, int mosi, int miso, int sck, int ss, int rate)
|
||||
{
|
||||
if (!sd_spi_init(mosi, miso, sck, ss)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sd_early_init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sd_check_interface_condition()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t ocr;
|
||||
if (!sd_read_ocr(&ocr)) {
|
||||
printf("sd_init: read OCR failed\n");
|
||||
return false;
|
||||
}
|
||||
if ((ocr & 0x00380000) != 0x00380000) {
|
||||
printf("sd_init: unsupported card voltage range\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sd_send_op_cond())
|
||||
return false;
|
||||
|
||||
sd_spi_set_bitrate(rate);
|
||||
|
||||
if (!sd_read_ocr(&ocr)) {
|
||||
printf("sd_init: read OCR failed\n");
|
||||
return false;
|
||||
}
|
||||
if (!(ocr & (1 << 31))) {
|
||||
printf("sd_init: card not powered up but !idle?\n");
|
||||
return false;
|
||||
}
|
||||
sd_context->sdhc_sdxc = (ocr & (1 << 30));
|
||||
|
||||
if (!sd_read_csd(sd_context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef SD_DEBUG
|
||||
sd_dump_cid();
|
||||
#endif
|
||||
|
||||
sd_context->initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sd_deinit(struct sd_context *sd_context)
|
||||
{
|
||||
if (!sd_spi_deinit())
|
||||
return false;
|
||||
sd_context->initialized = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sd_readblock(struct sd_context *sd_context, size_t sector_num, uint8_t buffer[static 512])
|
||||
{
|
||||
if (!sd_context->initialized || sector_num >= sd_context->blocks)
|
||||
return false;
|
||||
|
||||
return sd_cmd_read(17, sector_num, 512, buffer);
|
||||
}
|
||||
|
||||
bool sd_readblock_start(struct sd_context *sd_context, size_t sector_num, uint8_t buffer[static 512])
|
||||
{
|
||||
if (!sd_context->initialized || sector_num >= sd_context->blocks)
|
||||
return false;
|
||||
return sd_cmd_read_start(17, sector_num, 512, buffer);
|
||||
}
|
||||
|
||||
bool sd_readblock_complete(struct sd_context *sd_context)
|
||||
{
|
||||
if (!sd_context->initialized)
|
||||
return false;
|
||||
sd_cmd_read_complete();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sd_readblock_is_complete(struct sd_context *sd_context) { return sd_cmd_read_is_complete(); }
|
||||
21
software/src/rp2_sd/sd.h
Normal file
21
software/src/rp2_sd/sd.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct sd_context {
|
||||
size_t blocks;
|
||||
bool initialized;
|
||||
bool old_card;
|
||||
bool sdhc_sdxc;
|
||||
};
|
||||
|
||||
bool sd_init(struct sd_context *context, int mosi, int miso, int sck, int ss, int rate);
|
||||
bool sd_deinit(struct sd_context *sd_context);
|
||||
|
||||
bool sd_readblock(struct sd_context *context, size_t sector_num, uint8_t buffer[static 512]);
|
||||
|
||||
bool sd_readblock_start(struct sd_context *context, size_t sector_num, uint8_t buffer[static 512]);
|
||||
bool sd_readblock_complete(struct sd_context *context);
|
||||
bool sd_readblock_is_complete(struct sd_context *context);
|
||||
293
software/src/rp2_sd/sd_spi.c
Normal file
293
software/src/rp2_sd/sd_spi.c
Normal file
@@ -0,0 +1,293 @@
|
||||
#include "hardware/pio.h"
|
||||
#ifdef NDEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
|
||||
#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 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 void sd_spi_read_dma(uint8_t wrdata, uint8_t *data, size_t len)
|
||||
{
|
||||
assert(sd_spi_context.sd_dma_context.state == DMA_IDLE);
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
sd_spi_read_dma(0xff, data, datalen);
|
||||
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[1];
|
||||
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);
|
||||
}
|
||||
27
software/src/rp2_sd/sd_spi.h
Normal file
27
software/src/rp2_sd/sd_spi.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define SD_MISO 4
|
||||
#define SD_SCK 2
|
||||
#define SD_MOSI 3
|
||||
#define SD_CS 5
|
||||
|
||||
#define SD_PIO pio0
|
||||
|
||||
#define SD_INIT_BITRATE 400000
|
||||
#define SD_BITRATE 15000000
|
||||
|
||||
bool sd_cmd(const uint8_t cmd, const uint32_t arg, unsigned resplen, uint8_t resp[static resplen]);
|
||||
bool sd_cmd_read(uint8_t cmd, uint32_t arg, unsigned datalen, uint8_t data[static datalen]);
|
||||
|
||||
bool sd_spi_init(int mosi, int miso, int sck, int ss);
|
||||
bool sd_spi_deinit(void);
|
||||
void sd_spi_set_bitrate(const int rate);
|
||||
|
||||
void sd_spi_dbg_clk(const int div, const int frac);
|
||||
|
||||
bool sd_cmd_read_start(uint8_t cmd, uint32_t arg, unsigned datalen, uint8_t data[static datalen]);
|
||||
bool sd_cmd_read_complete(void);
|
||||
bool sd_cmd_read_is_complete(void);
|
||||
61
software/src/rp2_sd/sd_spi_pio.pio
Normal file
61
software/src/rp2_sd/sd_spi_pio.pio
Normal file
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
.program sd_spi_pio
|
||||
.side_set 1
|
||||
|
||||
// data - MISO MOSI
|
||||
// sideset - SCK
|
||||
|
||||
// normal SPI
|
||||
normal:
|
||||
.wrap_target
|
||||
out pins, 1 side 0 [1]
|
||||
in pins, 1 side 1 [1]
|
||||
.wrap
|
||||
// Special "wait for token" repeated read SPI
|
||||
wait_loop:
|
||||
pull block side 0 [0]
|
||||
mov osr, x side 0 [0]
|
||||
set y, 8 side 0 [0]
|
||||
read_loop:
|
||||
out pins, 1 side 0 [1]
|
||||
in pins, 1 side 1 [0]
|
||||
jmp y--, read_loop side 1 [0]
|
||||
mov isr, y side 0 [0]
|
||||
jmp x != y, wait_done side 0 [0]
|
||||
set y, 0 side 0 [0]
|
||||
mov y, isr side 0 [0]
|
||||
jmp wait_loop side 0 [0]
|
||||
wait_done:
|
||||
push block side 0 [0]
|
||||
jmp normal side 0 [0]
|
||||
|
||||
% c-sdk {
|
||||
#include "hardware/clocks.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
static inline void sd_spi_pio_program_init(PIO pio, uint sm, uint offset, uint mosi, uint miso, uint sck, uint bitrate) {
|
||||
pio_gpio_init(pio, mosi);
|
||||
pio_gpio_init(pio, miso);
|
||||
pio_gpio_init(pio, sck);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, mosi, 1, true);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, miso, 1, false);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, sck, 1, true);
|
||||
|
||||
pio_sm_config c = sd_spi_pio_program_get_default_config(offset);
|
||||
sm_config_set_out_pins(&c, mosi, 1);
|
||||
sm_config_set_in_pins(&c, miso);
|
||||
sm_config_set_sideset_pins(&c, sck);
|
||||
sm_config_set_out_shift(&c, false, true, 8);
|
||||
sm_config_set_in_shift(&c, false, true, 8);
|
||||
|
||||
const unsigned pio_freq = bitrate*4;
|
||||
const float div = clock_get_hz(clk_sys) / (float)pio_freq;
|
||||
// for some reason, small clkdiv values (even integer ones) cause issues
|
||||
sm_config_set_clkdiv(&c, div < 2.5f ? 2.5f : div);
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
}
|
||||
%}
|
||||
30
software/src/rp2_sd/sd_util.h
Normal file
30
software/src/rp2_sd/sd_util.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
inline static uint8_t sd_crc7(size_t len, const uint8_t data[const static len])
|
||||
{
|
||||
const uint8_t poly = 0b1001;
|
||||
uint8_t crc = 0;
|
||||
for (size_t pos = 0; pos < len; ++pos) {
|
||||
crc ^= data[pos];
|
||||
for (int bit = 0; bit < 8; ++bit) {
|
||||
crc = (crc << 1) ^ ((crc & 0x80) ? (poly << 1) : 0);
|
||||
}
|
||||
}
|
||||
return crc >> 1;
|
||||
}
|
||||
|
||||
/* inline static uint16_t sd_crc16(size_t len, const uint8_t data[const static len]) */
|
||||
/* { */
|
||||
/* const uint16_t poly = 0b1000000100001; */
|
||||
/* uint16_t crc = 0; */
|
||||
/* for (size_t pos = 0; pos < len; ++pos) { */
|
||||
/* crc ^= data[pos] << 8; */
|
||||
/* for (int bit = 0; bit < 8; ++bit) { */
|
||||
/* crc = (crc << 1) ^ ((crc & 0x8000) ? poly : 0); */
|
||||
/* } */
|
||||
/* } */
|
||||
/* return crc; */
|
||||
/* } */
|
||||
Reference in New Issue
Block a user