Jelajahi Sumber

Beginnings of SDIO support for RP2040

(cherry picked from commit a3df9ef770f6649eeed2ad048c61667110c51aa3)
Petteri Aimonen 3 tahun lalu
induk
melakukan
57d818d9d7

+ 8 - 4
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp

@@ -126,10 +126,14 @@ void azplatform_init()
     gpio_conf(SCSI_IN_RST,    GPIO_FUNC_SIO, true, false, false, true, false);
 
     // SD card pins
-    gpio_conf(SD_SPI_SCK,     GPIO_FUNC_SPI, false,false, true,  true, true);
-    gpio_conf(SD_SPI_MOSI,    GPIO_FUNC_SPI, false,false, true,  true, true);
-    gpio_conf(SD_SPI_MISO,    GPIO_FUNC_SPI, false,false, false, true, true);
-    gpio_conf(SD_SPI_CS,      GPIO_FUNC_SIO, false,false, true,  true, true);
+    // Card is used in SDIO mode for main program, and in SPI mode for crash handler & bootloader.
+    //        pin             function       pup   pdown  out    state fast
+    gpio_conf(SD_SPI_SCK,     GPIO_FUNC_SPI, true, false, true,  true, true);
+    gpio_conf(SD_SPI_MOSI,    GPIO_FUNC_SPI, true, false, true,  true, true);
+    gpio_conf(SD_SPI_MISO,    GPIO_FUNC_SPI, true, false, false, true, true);
+    gpio_conf(SD_SPI_CS,      GPIO_FUNC_SIO, true, false, true,  true, true);
+    gpio_conf(SDIO_D1,        GPIO_FUNC_SIO, true, false, false, true, true);
+    gpio_conf(SDIO_D2,        GPIO_FUNC_SIO, true, false, false, true, true);
 
     // LED pin
     gpio_conf(LED_PIN,        GPIO_FUNC_SIO, false,false, true,  false, false);

+ 9 - 0
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.h

@@ -14,6 +14,7 @@ extern "C" {
 extern const char *g_azplatform_name;
 #define PLATFORM_NAME "ZuluSCSI RP2040"
 #define PLATFORM_REVISION "2.0"
+#define SD_USE_SDIO 1
 
 // Debug logging function, can be used to print to e.g. serial port.
 // May get called from interrupt handlers.
@@ -106,8 +107,16 @@ extern const uint32_t g_scsi_parity_lookup[256];
 
 // SD card driver for SdFat
 class SdSpiConfig;
+class SdioConfig;
 extern SdSpiConfig g_sd_spi_config;
+extern SdioConfig g_sd_sdio_config;
+
+#ifdef SD_USE_SDIO
+#define SD_CONFIG g_sd_sdio_config
+#define SD_CONFIG_CRASH g_sd_spi_config
+#else
 #define SD_CONFIG g_sd_spi_config
 #define SD_CONFIG_CRASH g_sd_spi_config
+#endif
 
 #endif

+ 180 - 0
lib/ZuluSCSI_platform_RP2040/rp2040_sdio.cpp

@@ -0,0 +1,180 @@
+// Implementation of SDIO communication for RP2040
+//
+// The RP2040 official work-in-progress code at
+// https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_sd_card
+// may be useful reference, but this is independent implementation.
+//
+// For official SDIO specifications, refer to:
+// https://www.sdcard.org/downloads/pls/
+// "SDIO Physical Layer Simplified Specification Version 8.00"
+
+#include "rp2040_sdio.h"
+#include "rp2040_sdio.pio.h"
+#include <hardware/pio.h>
+#include <hardware/gpio.h>
+#include <ZuluSCSI_platform.h>
+#include <ZuluSCSI_log.h>
+
+#define SDIO_PIO pio1
+#define SDIO_CMD_SM 0
+#define SDIO_DATA_SM 1
+
+static struct {
+    uint32_t pio_cmd_clk_offset;
+} g_sdio;
+
+// Table lookup for calculating CRC-7 checksum that is used in SDIO command packets.
+// Usage:
+//    uint8_t crc = 0;
+//    crc = crc7_table[crc ^ byte];
+//    .. repeat for every byte ..
+static const uint8_t crc7_table[256] = {
+	0x00, 0x12, 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e,	0x90, 0x82, 0xb4, 0xa6, 0xd8, 0xca, 0xfc, 0xee,
+	0x32, 0x20, 0x16, 0x04, 0x7a, 0x68, 0x5e, 0x4c,	0xa2, 0xb0, 0x86, 0x94, 0xea, 0xf8, 0xce, 0xdc,
+	0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a,	0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a,
+	0x56, 0x44, 0x72, 0x60, 0x1e, 0x0c, 0x3a, 0x28,	0xc6, 0xd4, 0xe2, 0xf0, 0x8e, 0x9c, 0xaa, 0xb8,
+	0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6,	0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26,
+	0xfa, 0xe8, 0xde, 0xcc, 0xb2, 0xa0, 0x96, 0x84,	0x6a, 0x78, 0x4e, 0x5c, 0x22, 0x30, 0x06, 0x14,
+	0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6, 0xc0, 0xd2,	0x3c, 0x2e, 0x18, 0x0a, 0x74, 0x66, 0x50, 0x42,
+	0x9e, 0x8c, 0xba, 0xa8, 0xd6, 0xc4, 0xf2, 0xe0,	0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x70,
+	0x82, 0x90, 0xa6, 0xb4, 0xca, 0xd8, 0xee, 0xfc,	0x12, 0x00, 0x36, 0x24, 0x5a, 0x48, 0x7e, 0x6c,
+	0xb0, 0xa2, 0x94, 0x86, 0xf8, 0xea, 0xdc, 0xce,	0x20, 0x32, 0x04, 0x16, 0x68, 0x7a, 0x4c, 0x5e,
+	0xe6, 0xf4, 0xc2, 0xd0, 0xae, 0xbc, 0x8a, 0x98,	0x76, 0x64, 0x52, 0x40, 0x3e, 0x2c, 0x1a, 0x08,
+	0xd4, 0xc6, 0xf0, 0xe2, 0x9c, 0x8e, 0xb8, 0xaa,	0x44, 0x56, 0x60, 0x72, 0x0c, 0x1e, 0x28, 0x3a,
+	0x4a, 0x58, 0x6e, 0x7c, 0x02, 0x10, 0x26, 0x34,	0xda, 0xc8, 0xfe, 0xec, 0x92, 0x80, 0xb6, 0xa4,
+	0x78, 0x6a, 0x5c, 0x4e, 0x30, 0x22, 0x14, 0x06,	0xe8, 0xfa, 0xcc, 0xde, 0xa0, 0xb2, 0x84, 0x96,
+	0x2e, 0x3c, 0x0a, 0x18, 0x66, 0x74, 0x42, 0x50,	0xbe, 0xac, 0x9a, 0x88, 0xf6, 0xe4, 0xd2, 0xc0,
+	0x1c, 0x0e, 0x38, 0x2a, 0x54, 0x46, 0x70, 0x62,	0x8c, 0x9e, 0xa8, 0xba, 0xc4, 0xd6, 0xe0, 0xf2
+};
+
+sdio_status_t rp2040_sdio_command_R1(uint8_t command, uint32_t arg, uint32_t *response)
+{
+    azdbg("Command: ", command, " arg ", arg);
+
+    // Format the arguments in the way expected by the PIO code.
+    uint32_t word0 =
+        (47 << 24) | // Number of bits in command minus one
+        ( 1 << 22) | // Transfer direction from host to card
+        (command << 16) | // Command byte
+        (((arg >> 24) & 0xFF) << 8) | // MSB byte of argument
+        (((arg >> 16) & 0xFF) << 0);
+    
+    uint32_t word1 =
+        (((arg >> 8) & 0xFF) << 24) |
+        (((arg >> 0) & 0xFF) << 16) | // LSB byte of argument
+        ( 1 << 8); // End bit
+
+    // Set number of bits in response minus one, or leave at 0 if no response expected
+    if (response)
+    {
+        word1 |= (47 << 0);
+    }
+
+    // Calculate checksum in the order that the bytes will be transmitted (big-endian)
+    uint8_t crc = 0;
+    crc = crc7_table[crc ^ ((word0 >> 16) & 0xFF)];
+    crc = crc7_table[crc ^ ((word0 >>  8) & 0xFF)];
+    crc = crc7_table[crc ^ ((word0 >>  0) & 0xFF)];
+    crc = crc7_table[crc ^ ((word1 >> 24) & 0xFF)];
+    crc = crc7_table[crc ^ ((word1 >> 16) & 0xFF)];
+    word1 |= crc << 8;
+    
+    // Transmit command
+    pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);
+    pio_sm_put(SDIO_PIO, SDIO_CMD_SM, word0);
+    pio_sm_put(SDIO_PIO, SDIO_CMD_SM, word1);
+
+    // Wait for response
+    uint32_t start = millis();
+    uint32_t wait_words = response ? 2 : 1;
+    while (pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM) < wait_words)
+    {
+        if ((uint32_t)(millis() - start) > 2)
+        {
+            azdbg("Timeout waiting for response in rp2040_sdio_command_R1(), ",
+                  "PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)g_sdio.pio_cmd_clk_offset,
+                  " RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM),
+                  " TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM));
+
+            // Reset the state machine program
+            pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);
+            pio_sm_exec(SDIO_PIO, SDIO_CMD_SM, pio_encode_jmp(g_sdio.pio_cmd_clk_offset));
+            return SDIO_ERR_RESPONSE_TIMEOUT;
+        }
+    }
+
+    delay(1);
+    azdbg("PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)g_sdio.pio_cmd_clk_offset,
+                  " RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM),
+                  " TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM));
+
+    if (response)
+    {
+        // Read out response packet
+        uint32_t resp0 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
+        uint32_t resp1 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
+        azdbg(resp0, " ", resp1);
+
+        // Calculate response checksum
+        crc = 0;
+        crc = crc7_table[crc ^ ((resp0 >> 24) & 0xFF)];
+        crc = crc7_table[crc ^ ((resp0 >> 16) & 0xFF)];
+        crc = crc7_table[crc ^ ((resp0 >>  8) & 0xFF)];
+        crc = crc7_table[crc ^ ((resp0 >>  0) & 0xFF)];
+        crc = crc7_table[crc ^ ((resp1 >>  8) & 0xFF)];
+
+        uint8_t actual_crc = ((resp1 >> 0) & 0xFE);
+        if (crc != actual_crc)
+        {
+            azdbg("CRC error in rp2040_sdio_command_R1(): calculated ", crc, " packet has ", actual_crc);
+            return SDIO_ERR_CRC;
+        }
+
+        *response = ((resp0 & 0xFFFFFF) << 8) | ((resp1 >> 8) & 0xFF);
+    }
+    else
+    {
+        // Read out dummy marker
+        pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
+    }
+
+    return SDIO_OK;
+}
+
+void rp2040_sdio_init()
+{
+    azdbg("rp2040_sdio_init()");
+
+    // Mark resources as being in use, unless it has been done already.
+    static bool resources_claimed = false;
+    if (!resources_claimed)
+    {
+        pio_sm_claim(SDIO_PIO, SDIO_CMD_SM);
+        pio_sm_claim(SDIO_PIO, SDIO_DATA_SM);
+        resources_claimed = true;
+    }
+
+    // Load PIO programs
+    pio_clear_instruction_memory(SDIO_PIO);
+
+    // Command & clock state machine
+    g_sdio.pio_cmd_clk_offset = pio_add_program(SDIO_PIO, &sdio_cmd_clk_program);
+    pio_sm_config cfg = sdio_cmd_clk_program_get_default_config(g_sdio.pio_cmd_clk_offset);
+    sm_config_set_out_pins(&cfg, SDIO_CMD, 1);
+    sm_config_set_in_pins(&cfg, SDIO_CMD);
+    sm_config_set_set_pins(&cfg, SDIO_CMD, 1);
+    sm_config_set_jmp_pin(&cfg, SDIO_CMD);
+    sm_config_set_sideset_pins(&cfg, SDIO_CLK);
+    sm_config_set_out_shift(&cfg, false, true, 32);
+    sm_config_set_in_shift(&cfg, false, true, 32);
+    sm_config_set_clkdiv_int_frac(&cfg, 5, 0);
+    sm_config_set_mov_status(&cfg, STATUS_TX_LESSTHAN, 2);
+
+    pio_sm_init(SDIO_PIO, SDIO_CMD_SM, g_sdio.pio_cmd_clk_offset, &cfg);
+    pio_sm_set_consecutive_pindirs(SDIO_PIO, SDIO_CMD_SM, SDIO_CLK, 1, true);
+    pio_sm_set_enabled(SDIO_PIO, SDIO_CMD_SM, true);
+
+    // Redirect GPIOs to PIO
+    gpio_set_function(SDIO_CMD, GPIO_FUNC_PIO1);
+    gpio_set_function(SDIO_CLK, GPIO_FUNC_PIO1);
+}

+ 34 - 0
lib/ZuluSCSI_platform_RP2040/rp2040_sdio.h

@@ -0,0 +1,34 @@
+// SD card access using SDIO for RP2040 platform.
+// This module contains the low-level SDIO bus implementation using
+// the PIO peripheral. The high-level commands are in sd_card_sdio.cpp.
+
+#pragma once
+#include <stdint.h>
+
+enum sdio_status_t {
+    SDIO_OK = 0,
+    SDIO_ERR_RESPONSE_TIMEOUT = 1, // Timed out waiting for response from card
+    SDIO_ERR_CRC = 2,              // Response CRC is wrong
+};
+
+// Execute a command that has 48-bit reply (response types R1, R3, R6 and R7)
+// If response is NULL, does not wait for reply.
+sdio_status_t rp2040_sdio_command_R1(uint8_t command, uint32_t arg, uint32_t *response);
+
+// Execute a command that has 136-bit reply (response type R2)
+sdio_status_t rp2040_sdio_command_R2(uint8_t command, uint32_t arg, uint32_t response[4]);
+
+// Start transferring data from SD card to memory buffer
+sdio_status_t rp2040_sdio_rx_start(uint8_t *buffer, uint32_t num_bytes);
+
+// Check if reception is complete
+bool rp2040_sdio_rx_poll();
+
+// Start transferring data from memory to SD card
+sdio_status_t rp2040_sdio_tx_start(const uint8_t *buffer, uint32_t num_bytes);
+
+// Check if transmission is complete
+bool rp2040_sdio_tx_poll();
+
+// (Re)initialize the SDIO interface
+void rp2040_sdio_init();

+ 119 - 0
lib/ZuluSCSI_platform_RP2040/rp2040_sdio.pio

@@ -0,0 +1,119 @@
+; RP2040 PIO program for implementing SD card access in SDIO mode
+; Run "pioasm rp2040_sdio.pio rp2040_sdio.pio.h" to regenerate the C header from this.
+
+; The RP2040 official work-in-progress code at
+; https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_sd_card
+; may be useful reference, but this is independent implementation.
+;
+; For official SDIO specifications, refer to:
+; https://www.sdcard.org/downloads/pls/
+; "SDIO Physical Layer Simplified Specification Version 8.00"
+
+; Clock settings
+; For 3.3V communication the available speeds are:
+; - Default speed: max. 25 MHz clock
+; - High speed:    max. 50 MHz clock
+;
+; From the default RP2040 clock speed of 125 MHz, the closest dividers
+; are 3 for 41.7 MHz and 5 for 25 MHz. The CPU can apply further divider
+; through state machine registers for the initial handshake.
+;
+; Because data is written on the falling edge and read on the rising
+; edge, it is preferrable to have a long 0 state and short 1 state.
+;.define CLOCK_DIVIDER 3
+.define CLOCK_DIVIDER 25
+.define D1 (CLOCK_DIVIDER/2 - 1)
+.define D0 ((CLOCK_DIVIDER + 1) / 2 - 1)
+.define SDIO_CLK_GPIO 18
+
+; State machine 0 is used to:
+; - generate continuous clock on SDIO_CLK
+; - send CMD packets
+; - receive response packets
+;
+; Pin mapping for this state machine:
+; - Sideset    : CLK
+; - IN/OUT/SET : CMD
+; - JMP_PIN    : CMD
+;
+; The commands to send are put on TX fifo and must have two words:
+; Word 0 bits 31-24: Number of bits in command minus one (usually 47)
+; Word 0 bits 23-00: First 24 bits of the command packet, shifted out MSB first
+; Word 1 bits 31-08: Last 24 bits of the command packet, shifted out MSB first
+; Word 1 bits 07-00: Number of bits in response minus one (usually 47), or 0 if no response
+;
+; The response is put on RX fifo, starting with the MSB.
+; Partial last word will be padded with zero bits at the top.
+;
+; The state machine EXECCTRL should be set so that STATUS indicates TX FIFO < 2
+; and that AUTOPULL and AUTOPUSH are enabled.
+
+.program sdio_cmd_clk
+    .side_set 1
+
+    mov OSR, NULL       side 1 [D1]    ; Make sure OSR is full of zeros to prevent autopull
+
+wait_cmd:
+    mov Y, !STATUS      side 0 [D0]    ; Check if TX FIFO has data
+    jmp !Y wait_cmd     side 1 [D1]
+
+load_cmd:
+    out NULL, 32        side 0 [D0]    ; Load first word (trigger autopull)
+    out X, 8            side 1 [D1]    ; Number of bits to send
+    set pins, 1         side 0 [D0]    ; Initial state of CMD is high
+    set pindirs, 1      side 1 [D1]    ; Set SDIO_CMD as output
+
+send_cmd:
+    out pins, 1         side 0 [D0]    ; Write output on falling edge of CLK
+    jmp X-- send_cmd    side 1 [D1]
+
+prep_resp:
+    set pindirs, 0      side 0 [D0]    ; Set SDIO_CMD as input
+    out X, 8            side 1 [D1]    ; Get number of bits in response
+    nop                 side 0 [D0]    ; For clock alignment
+    jmp !X resp_done    side 1 [D1]    ; Check if we expect a response
+
+wait_resp:
+    nop                  side 0 [D0]
+    jmp PIN wait_resp    side 1 [D1]    ; Loop until SDIO_CMD = 0
+
+    ; Note: input bits are read at the same time as we write CLK=0.
+    ; Because the host controls the clock, the read happens before
+    ; the card sees the falling clock edge. This gives maximum time
+    ; for the data bit to settle.
+read_resp:
+    in PINS, 1          side 0 [D0]    ; Read input data bit
+    jmp X-- read_resp   side 1 [D1]    ; Loop to receive all data bits
+
+resp_done:
+    push                side 0 [D0]    ; Push the remaining part of response
+
+; State machine 1 is used to send and receive data blocks.
+; Pin mapping for this state machine:
+; - IN / OUT: SDIO_D0-D3
+; - GPIO defined at beginning of this file: SDIO_CLK
+
+; Data reception program
+; This program will wait for initial start of block token and then
+; continuously receive data. The application can set limit of bytes
+; to receive by using DMA controller, and the final checksum will
+; fit in state machine RX FIFO.
+.program sdio_data_rx
+
+wait_start:
+    mov Y, !PINS                ; Read GPIOs (currently doesn't check for clock edge)
+    jmp !Y wait_start           ; Keep looping until we see all zeros start token
+
+.wrap_target
+    wait 1 gpio SDIO_CLK_GPIO   ; Wait for rising clock edge
+    in PINS, 4                  ; Read nibble
+.wrap
+
+; Data transmission program
+; This program will simply send nibbles out to pins, synchronous
+; to the clock signal. The application should prepend 0xF0 to the data
+; for the start of block token, and append the checksum.
+; The data should be padded to full 32 bits by 0xFF bytes.
+.program sdio_data_tx
+    wait 0 gpio SDIO_CLK_GPIO   ; Wait for falling clock edge
+    out PINS, 4                 ; Write nibble

+ 113 - 0
lib/ZuluSCSI_platform_RP2040/rp2040_sdio.pio.h

@@ -0,0 +1,113 @@
+// -------------------------------------------------- //
+// This file is autogenerated by pioasm; do not edit! //
+// -------------------------------------------------- //
+
+#pragma once
+
+#if !PICO_NO_HARDWARE
+#include "hardware/pio.h"
+#endif
+
+// ------------ //
+// sdio_cmd_clk //
+// ------------ //
+
+#define sdio_cmd_clk_wrap_target 0
+#define sdio_cmd_clk_wrap 17
+
+static const uint16_t sdio_cmd_clk_program_instructions[] = {
+            //     .wrap_target
+    0xbbe3, //  0: mov    osr, null       side 1 [11]
+    0xac4d, //  1: mov    y, !status      side 0 [12]
+    0x1b61, //  2: jmp    !y, 1           side 1 [11]
+    0x6c60, //  3: out    null, 32        side 0 [12]
+    0x7b28, //  4: out    x, 8            side 1 [11]
+    0xec01, //  5: set    pins, 1         side 0 [12]
+    0xfb81, //  6: set    pindirs, 1      side 1 [11]
+    0x6c01, //  7: out    pins, 1         side 0 [12]
+    0x1b47, //  8: jmp    x--, 7          side 1 [11]
+    0xec80, //  9: set    pindirs, 0      side 0 [12]
+    0x7b28, // 10: out    x, 8            side 1 [11]
+    0xac42, // 11: nop                    side 0 [12]
+    0x1b31, // 12: jmp    !x, 17          side 1 [11]
+    0xac42, // 13: nop                    side 0 [12]
+    0x1bcd, // 14: jmp    pin, 13         side 1 [11]
+    0x4c01, // 15: in     pins, 1         side 0 [12]
+    0x1b4f, // 16: jmp    x--, 15         side 1 [11]
+    0x8c20, // 17: push   block           side 0 [12]
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program sdio_cmd_clk_program = {
+    .instructions = sdio_cmd_clk_program_instructions,
+    .length = 18,
+    .origin = -1,
+};
+
+static inline pio_sm_config sdio_cmd_clk_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + sdio_cmd_clk_wrap_target, offset + sdio_cmd_clk_wrap);
+    sm_config_set_sideset(&c, 1, false, false);
+    return c;
+}
+#endif
+
+// ------------ //
+// sdio_data_rx //
+// ------------ //
+
+#define sdio_data_rx_wrap_target 2
+#define sdio_data_rx_wrap 3
+
+static const uint16_t sdio_data_rx_program_instructions[] = {
+    0xa048, //  0: mov    y, !pins                   
+    0x0060, //  1: jmp    !y, 0                      
+            //     .wrap_target
+    0x2092, //  2: wait   1 gpio, 18                 
+    0x4004, //  3: in     pins, 4                    
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program sdio_data_rx_program = {
+    .instructions = sdio_data_rx_program_instructions,
+    .length = 4,
+    .origin = -1,
+};
+
+static inline pio_sm_config sdio_data_rx_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + sdio_data_rx_wrap_target, offset + sdio_data_rx_wrap);
+    return c;
+}
+#endif
+
+// ------------ //
+// sdio_data_tx //
+// ------------ //
+
+#define sdio_data_tx_wrap_target 0
+#define sdio_data_tx_wrap 1
+
+static const uint16_t sdio_data_tx_program_instructions[] = {
+            //     .wrap_target
+    0x2012, //  0: wait   0 gpio, 18                 
+    0x6004, //  1: out    pins, 4                    
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program sdio_data_tx_program = {
+    .instructions = sdio_data_tx_program_instructions,
+    .length = 2,
+    .origin = -1,
+};
+
+static inline pio_sm_config sdio_data_tx_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + sdio_data_tx_wrap_target, offset + sdio_data_tx_wrap);
+    return c;
+}
+#endif
+

+ 249 - 0
lib/ZuluSCSI_platform_RP2040/sd_card_sdio.cpp

@@ -0,0 +1,249 @@
+// Driver for accessing SD card in SDIO mode on RP2040.
+
+#include "ZuluSCSI_platform.h"
+
+#ifdef SD_USE_SDIO
+
+#include "ZuluSCSI_log.h"
+#include "rp2040_sdio.h"
+#include <hardware/gpio.h>
+#include <SdFat.h>
+#include <SdCard/SdCardInfo.h>
+
+
+bool SdioCard::begin(SdioConfig sdioConfig)
+{
+    uint32_t reply;
+    
+    rp2040_sdio_init();
+    delay(1);
+    rp2040_sdio_command_R1(CMD0, 0, NULL); // GO_IDLE_STATE
+    rp2040_sdio_command_R1(CMD8, 0x1AA, &reply); // SEND_IF_COND
+    azdbg("Reply ", reply);
+    rp2040_sdio_command_R1(CMD0, 0, NULL); // GO_IDLE_STATE
+    rp2040_sdio_command_R1(CMD8, 0x1AA, &reply); // SEND_IF_COND
+    azdbg("Reply ", reply);
+
+    delay(100);
+    return false;
+}
+
+uint8_t SdioCard::errorCode() const
+{
+    return SD_CARD_ERROR_NONE;
+}
+
+uint32_t SdioCard::errorData() const
+{
+    return 0;
+}
+
+uint32_t SdioCard::errorLine() const
+{
+    return 0;
+}
+
+bool SdioCard::isBusy() 
+{
+    return (sio_hw->gpio_in & (1 << SDIO_D0)) == 0;
+}
+
+uint32_t SdioCard::kHzSdClk()
+{
+    return 0;
+}
+
+bool SdioCard::readCID(cid_t* cid)
+{
+    return true;
+}
+
+bool SdioCard::readCSD(csd_t* csd)
+{
+    return true;
+}
+
+bool SdioCard::readOCR(uint32_t* ocr)
+{
+    return true;
+}
+
+bool SdioCard::readData(uint8_t* dst)
+{
+    azlog("SdioCard::readData() called but not implemented!");
+    return false;
+}
+
+bool SdioCard::readStart(uint32_t sector)
+{
+    azlog("SdioCard::readStart() called but not implemented!");
+    return false;
+}
+
+bool SdioCard::readStop()
+{
+    azlog("SdioCard::readStop() called but not implemented!");
+    return false;
+}
+
+uint32_t SdioCard::sectorCount()
+{
+    // csd_t csd;
+    // sd_csd_get((uint8_t*)&csd);
+    // return sdCardCapacity(&csd);
+    return 0;
+}
+
+uint32_t SdioCard::status()
+{
+    // uint32_t status = 0;
+    // if (!checkReturnOk(sd_cardstatus_get(&status)))
+    //     return 0;
+    // else
+    //     return status;
+    return 0;
+}
+
+bool SdioCard::stopTransmission(bool blocking)
+{
+    return false;
+    // if (!checkReturnOk(sd_transfer_stop()))
+    //     return false;
+
+    // if (!blocking)
+    // {
+    //     return true;
+    // }
+    // else
+    // {
+    //     uint32_t end = millis() + 100;
+    //     while (millis() < end && isBusy())
+    //     {
+    //     }
+    //     if (isBusy())
+    //     {
+    //         azlog("SdioCard::stopTransmission() timeout");
+    //         return false;
+    //     }
+    //     else
+    //     {
+    //         return true;
+    //     }
+    // }
+}
+
+bool SdioCard::syncDevice()
+{
+    // if (sd_transfer_state_get() != SD_NO_TRANSFER)
+    // {
+    //     return stopTransmission(true);
+    // }
+    return true;
+}
+
+uint8_t SdioCard::type() const
+{
+    // if (g_sdio_card_type == SDIO_HIGH_CAPACITY_SD_CARD)
+    //     return SD_CARD_TYPE_SDHC;
+    // else if (g_sdio_card_type == SDIO_STD_CAPACITY_SD_CARD_V2_0)
+    //     return SD_CARD_TYPE_SD2;
+    // else
+    //     return SD_CARD_TYPE_SD1;
+}
+
+bool SdioCard::writeData(const uint8_t* src)
+{
+    azlog("SdioCard::writeData() called but not implemented!");
+    return false;
+}
+
+bool SdioCard::writeStart(uint32_t sector)
+{
+    azlog("SdioCard::writeStart() called but not implemented!");
+    return false;
+}
+
+bool SdioCard::writeStop()
+{
+    azlog("SdioCard::writeStop() called but not implemented!");
+    return false;
+}
+
+bool SdioCard::erase(uint32_t firstSector, uint32_t lastSector)
+{
+    // return checkReturnOk(sd_erase(firstSector * 512, lastSector * 512));
+}
+
+/* Writing and reading, with progress callback */
+
+// static sd_callback_t m_stream_callback;
+// static const uint8_t *m_stream_buffer;
+// static uint32_t m_stream_count;
+// static uint32_t m_stream_count_start;
+
+// void azplatform_set_sd_callback(sd_callback_t func, const uint8_t *buffer)
+// {
+//     m_stream_callback = func;
+//     m_stream_buffer = buffer;
+//     m_stream_count = 0;
+//     m_stream_count_start = 0;
+// }
+
+// static void sdio_callback(uint32_t complete)
+// {
+//     if (m_stream_callback)
+//     {
+//         m_stream_callback(m_stream_count_start + complete);
+//     }
+// }
+
+// static sdio_callback_t get_stream_callback(const uint8_t *buf, uint32_t count)
+// {
+//     m_stream_count_start = m_stream_count;
+
+//     if (m_stream_callback)
+//     {
+//         if (buf == m_stream_buffer + m_stream_count)
+//         {
+//             m_stream_count += count;
+//             return &sdio_callback;
+//         }
+//         else
+//         {
+//             azdbg("Stream buffer mismatch: ", (uint32_t)buf, " vs. ", (uint32_t)(m_stream_buffer + m_stream_count));
+//             return NULL;
+//         }
+//     }
+    
+//     return NULL;
+// }
+
+
+bool SdioCard::writeSector(uint32_t sector, const uint8_t* src)
+{
+    // return checkReturnOk(sd_block_write((uint32_t*)src, (uint64_t)sector * 512, 512,
+    //     get_stream_callback(src, 512)));
+}
+
+bool SdioCard::writeSectors(uint32_t sector, const uint8_t* src, size_t n)
+{
+    // return checkReturnOk(sd_multiblocks_write((uint32_t*)src, (uint64_t)sector * 512, 512, n,
+    //     get_stream_callback(src, n * 512)));
+}
+
+bool SdioCard::readSector(uint32_t sector, uint8_t* dst)
+{
+    // return checkReturnOk(sd_block_read((uint32_t*)dst, (uint64_t)sector * 512, 512,
+    //     get_stream_callback(dst, 512)));
+}
+
+bool SdioCard::readSectors(uint32_t sector, uint8_t* dst, size_t n)
+{
+    // return checkReturnOk(sd_multiblocks_read((uint32_t*)dst, (uint64_t)sector * 512, 512, n,
+    //     get_stream_callback(dst, n * 512)));
+}
+
+// SDIO configuration for main program
+SdioConfig g_sd_sdio_config(DMA_SDIO);
+
+#endif

+ 6 - 3
lib/ZuluSCSI_platform_RP2040/sd_card_spi.cpp

@@ -1,16 +1,21 @@
 // Driver and interface for accessing SD card in SPI mode
+// Normally this is only used for saving crash log in interrupt mode
 
 #include "ZuluSCSI_platform.h"
 #include "ZuluSCSI_log.h"
 #include <hardware/spi.h>
 #include <SdFat.h>
 
-#ifndef SD_USE_SDIO
 
 class RP2040SPIDriver : public SdSpiBaseClass
 {
 public:
     void begin(SdSpiConfig config) {
+        // Make sure pins are routed to SPI
+        gpio_set_function(SD_SPI_SCK,  GPIO_FUNC_SPI);
+        gpio_set_function(SD_SPI_MOSI, GPIO_FUNC_SPI);
+        gpio_set_function(SD_SPI_MISO, GPIO_FUNC_SPI);
+        gpio_set_function(SD_SPI_CS,   GPIO_FUNC_SIO);
     }
 
     void activate() {
@@ -102,5 +107,3 @@ void azplatform_set_sd_callback(sd_callback_t func, const uint8_t *buffer)
 {
     g_sd_spi_port.set_sd_callback(func, buffer);
 }
-
-#endif

+ 20 - 0
platformio.ini

@@ -61,3 +61,23 @@ build_flags =
      -DENABLE_DEDICATED_SPI=1
      -DHAS_SDIO_CLASS
      -DZULUSCSI_V1_1
+
+; ZuluSCSI v2.0 hardware platform, based on RP2040
+[env:ZuluSCSI_RP2040_v2_0]
+platform = raspberrypi
+framework = arduino
+board = ZuluSCSI_RP2040
+board_build.ldscript = lib/ZuluSCSI_platform_RP2040/rp2040.ld
+lib_deps =
+    SdFat=https://github.com/greiman/SdFat
+    minIni
+    ZuluSCSI_platform_RP2040
+    SCSI2SD
+build_flags =
+    -Os -Isrc -ggdb -g3
+    -Wall -Wno-sign-compare -Wno-ignored-qualifiers
+    -DSPI_DRIVER_SELECT=3
+    -DSD_CHIP_SELECT_MODE=2
+    -DENABLE_DEDICATED_SPI=1
+    -DHAS_SDIO_CLASS
+    -DUSE_ARDUINO=1