Browse Source

Merge branch 'main' into pico-network

Morio 2 years ago
parent
commit
316d497082
53 changed files with 9398 additions and 739 deletions
  1. 1 1
      .github/workflows/firmware_build.yml
  2. 1 1
      .gitignore
  3. 865 0
      lib/ZuluSCSI_platform_GD32F450/ZuluSCSI_platform.cpp
  4. 201 0
      lib/ZuluSCSI_platform_GD32F450/ZuluSCSI_platform.h
  5. 207 0
      lib/ZuluSCSI_platform_GD32F450/ZuluSCSI_v1_4_gpio.h
  6. 28 0
      lib/ZuluSCSI_platform_GD32F450/bsp.h
  7. 521 0
      lib/ZuluSCSI_platform_GD32F450/gd32_cdc_acm_core.c
  8. 66 0
      lib/ZuluSCSI_platform_GD32F450/gd32_cdc_acm_core.h
  9. 2540 0
      lib/ZuluSCSI_platform_GD32F450/gd32_sdio_sdcard.c
  10. 262 0
      lib/ZuluSCSI_platform_GD32F450/gd32_sdio_sdcard.h
  11. 216 0
      lib/ZuluSCSI_platform_GD32F450/greenpak.cpp
  12. 34 0
      lib/ZuluSCSI_platform_GD32F450/greenpak.h
  13. 46 0
      lib/ZuluSCSI_platform_GD32F450/greenpak_fw.h
  14. 37 0
      lib/ZuluSCSI_platform_GD32F450/scsi2sd_time.h
  15. 626 0
      lib/ZuluSCSI_platform_GD32F450/scsiPhy.cpp
  16. 90 0
      lib/ZuluSCSI_platform_GD32F450/scsiPhy.h
  17. 173 0
      lib/ZuluSCSI_platform_GD32F450/scsi_accel_asm.cpp
  18. 32 0
      lib/ZuluSCSI_platform_GD32F450/scsi_accel_asm.h
  19. 787 0
      lib/ZuluSCSI_platform_GD32F450/scsi_accel_dma.cpp
  20. 48 0
      lib/ZuluSCSI_platform_GD32F450/scsi_accel_dma.h
  21. 388 0
      lib/ZuluSCSI_platform_GD32F450/scsi_accel_greenpak.cpp
  22. 32 0
      lib/ZuluSCSI_platform_GD32F450/scsi_accel_greenpak.h
  23. 540 0
      lib/ZuluSCSI_platform_GD32F450/scsi_accel_sync.cpp
  24. 38 0
      lib/ZuluSCSI_platform_GD32F450/scsi_accel_sync.h
  25. 345 0
      lib/ZuluSCSI_platform_GD32F450/sd_card_sdio.cpp
  26. 192 0
      lib/ZuluSCSI_platform_GD32F450/usb_conf.h
  27. 157 0
      lib/ZuluSCSI_platform_GD32F450/usb_hs.cpp
  28. 40 0
      lib/ZuluSCSI_platform_GD32F450/usb_hs.h
  29. 75 0
      lib/ZuluSCSI_platform_GD32F450/usbd_conf.h
  30. 175 0
      lib/ZuluSCSI_platform_GD32F450/zuluscsi_gd32f450.ld
  31. 185 0
      lib/ZuluSCSI_platform_GD32F450/zuluscsi_gd32f450_btldr.ld
  32. 81 52
      lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp
  33. 5 6
      lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_gpio_Pico.h
  34. 2 2
      lib/ZuluSCSI_platform_RP2040/audio.cpp
  35. 0 1
      lib/ZuluSCSI_platform_RP2040/scsi_accel_host.cpp
  36. 1 3
      lib/ZuluSCSI_platform_RP2040/scsi_accel_target.cpp
  37. 0 124
      lib/ZuluSCSI_platform_RP2040/scsi_accel_target_BS2.pio
  38. 0 225
      lib/ZuluSCSI_platform_RP2040/scsi_accel_target_BS2.pio.h
  39. 1 3
      lib/ZuluSCSI_platform_RP2040/sdio.cpp
  40. 0 165
      lib/ZuluSCSI_platform_RP2040/sdio_BS2.pio
  41. 0 121
      lib/ZuluSCSI_platform_RP2040/sdio_BS2.pio.h
  42. 35 12
      platformio.ini
  43. 8 2
      src/ImageBackingStore.cpp
  44. 25 0
      src/ImageBackingStore.h
  45. 118 0
      src/QuirksCheck.cpp
  46. 35 0
      src/QuirksCheck.h
  47. 4 5
      src/ZuluSCSI.cpp
  48. 3 1
      src/ZuluSCSI_audio.h
  49. 18 1
      src/ZuluSCSI_bootloader.cpp
  50. 1 1
      src/ZuluSCSI_config.h
  51. 110 13
      src/ZuluSCSI_disk.cpp
  52. 1 0
      src/ZuluSCSI_main.cpp
  53. 2 0
      zuluscsi.ini

+ 1 - 1
.github/workflows/firmware_build.yml

@@ -24,7 +24,7 @@ jobs:
       - name: Build firmware
       - name: Build firmware
         run: |
         run: |
           cd ZuluSCSI
           cd ZuluSCSI
-          pio run -v -e ZuluSCSI_Pico
+          pio run -v
     
     
       - name: Rename firmware files
       - name: Rename firmware files
         run: |
         run: |

+ 1 - 1
.gitignore

@@ -1,2 +1,2 @@
 .pio
 .pio
-.vscode/
+.vscode

+ 865 - 0
lib/ZuluSCSI_platform_GD32F450/ZuluSCSI_platform.cpp

@@ -0,0 +1,865 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#include "ZuluSCSI_platform.h"
+#include "gd32f4xx_sdio.h"
+#include "gd32f4xx_fmc.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_config.h"
+#include "usb_hs.h"
+#include "usbd_conf.h"
+#include "greenpak.h"
+#include <SdFat.h>
+#include <scsi.h>
+#include <assert.h>
+
+extern "C" {
+
+const char *g_platform_name = PLATFORM_NAME;
+static bool g_enable_apple_quirks = false;
+
+/*************************/
+/* Timing functions      */
+/*************************/
+
+static volatile uint32_t g_millisecond_counter;
+static volatile uint32_t g_watchdog_timeout;
+static uint32_t g_ns_to_cycles; // Q0.32 fixed point format
+
+static void watchdog_handler(uint32_t *sp);
+
+unsigned long millis()
+{
+    return g_millisecond_counter;
+}
+
+void delay(unsigned long ms)
+{
+    uint32_t start = g_millisecond_counter;
+    while ((uint32_t)(g_millisecond_counter - start) < ms);
+}
+
+void delay_ns(unsigned long ns)
+{
+    uint32_t CNT_start = DWT->CYCCNT;
+    if (ns <= 50) return; // Approximate call overhead
+    ns -= 50;
+
+    uint32_t cycles = ((uint64_t)ns * g_ns_to_cycles) >> 32;
+    while ((uint32_t)(DWT->CYCCNT - CNT_start) < cycles);
+}
+
+void SysTick_Handler_inner(uint32_t *sp)
+{
+    g_millisecond_counter++;
+
+    if (g_watchdog_timeout > 0)
+    {
+        g_watchdog_timeout--;
+
+        const uint32_t busreset_time = WATCHDOG_CRASH_TIMEOUT - WATCHDOG_BUS_RESET_TIMEOUT;
+        if (g_watchdog_timeout <= busreset_time)
+        {
+            if (!scsiDev.resetFlag)
+            {
+                logmsg("WATCHDOG TIMEOUT at PC ", sp[6], " LR ", sp[5], " attempting bus reset");
+                scsiDev.resetFlag = 1;
+            }
+
+            if (g_watchdog_timeout == 0)
+            {
+                watchdog_handler(sp);
+            }
+        }
+    }
+}
+
+__attribute__((interrupt, naked))
+void SysTick_Handler(void)
+{
+    // Take note of stack pointer so that we can print debug
+    // info in watchdog handler.
+    asm("mrs r0, msp\n"
+        "b SysTick_Handler_inner": : : "r0");
+}
+
+// This function is called by scsiPhy.cpp.
+// It resets the systick counter to give 1 millisecond of uninterrupted transfer time.
+// The total number of skips is kept track of to keep the correct time on average.
+void SysTick_Handle_PreEmptively()
+{
+    static int skipped_clocks = 0;
+
+    __disable_irq();
+    uint32_t loadval = SysTick->LOAD;
+    skipped_clocks += loadval - SysTick->VAL;
+    SysTick->VAL = 0;
+
+    if (skipped_clocks > loadval)
+    {
+        // We have skipped enough ticks that it is time to fake a call
+        // to SysTick interrupt handler.
+        skipped_clocks -= loadval;
+        uint32_t stack_frame[8] = {0};
+        stack_frame[6] = (uint32_t)__builtin_return_address(0);
+        SysTick_Handler_inner(stack_frame);
+    }
+    __enable_irq();
+}
+
+/***************/
+/* GPIO init   */
+/***************/
+
+// Initialize SPI and GPIO configuration
+// Clock has already been initialized by system_gd32f20x.c
+void platform_init()
+{
+
+    SystemCoreClockUpdate();
+    nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
+    // Enable SysTick to drive millis()
+    g_millisecond_counter = 0;
+    SysTick_Config(SystemCoreClock / 1000U);
+    nvic_irq_enable(SysTick_IRQn, 0x00U, 0x00U);
+    //NVIC_SetPriority(SysTick_IRQn, 0x00U);
+    //NVIC_EnableIRQ(SysTick_IRQn);
+
+    
+
+    // Enable DWT counter to drive delay_ns()
+    g_ns_to_cycles = ((uint64_t)SystemCoreClock << 32) / 1000000000;
+    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
+    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
+
+    // Enable debug output on SWO pin
+    DBG_CTL0 |= DBG_CTL0_TRACE_IOEN;
+    //TODO figure out if this code needs to execute - TPI_ACPR == 99 at the if statement below
+    //if (TPI->ACPR == 0)
+    {
+        CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
+        TPI->ACPR = SystemCoreClock / 2000000 - 1; // 2 Mbps baudrate for SWO
+        // TPI->ACPR = SystemCoreClock / 30000000 - 1; // 30 Mbps baudrate for SWO
+        TPI->SPPR = 2;
+        TPI->FFCR = 0x100; // TPIU packet framing disabled
+        // DWT->CTRL |= (1 << DWT_CTRL_EXCTRCENA_Pos);
+        // DWT->CTRL |= (1 << DWT_CTRL_CYCTAP_Pos)
+        //             | (15 << DWT_CTRL_POSTPRESET_Pos)
+        //             | (1 << DWT_CTRL_PCSAMPLENA_Pos)
+        //             | (3 << DWT_CTRL_SYNCTAP_Pos)
+        //             | (1 << DWT_CTRL_CYCCNTENA_Pos);
+        ITM->LAR = 0xC5ACCE55;
+        ITM->TCR = (1 << ITM_TCR_DWTENA_Pos)
+                    | (1 << ITM_TCR_SYNCENA_Pos)
+                    | (1 << ITM_TCR_ITMENA_Pos);
+        ITM->TER = 0xFFFFFFFF; // Enable all stimulus ports
+    }
+
+    // Enable needed clocks for GPIO
+    rcu_periph_clock_enable(RCU_GPIOA);
+    rcu_periph_clock_enable(RCU_GPIOB);
+    rcu_periph_clock_enable(RCU_GPIOC);
+    rcu_periph_clock_enable(RCU_GPIOD);
+    rcu_periph_clock_enable(RCU_GPIOE);
+    rcu_periph_clock_enable(RCU_GPIOF);
+    rcu_periph_clock_enable(RCU_GPIOG);
+
+    // Switch to SWD debug port (disable JTAG) to release PB4 as GPIO
+    gpio_mode_set(GPIOB, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_4);
+
+    // SCSI pins.
+    // Initialize open drain outputs to high.
+    SCSI_RELEASE_OUTPUTS();
+    gpio_mode_set(SCSI_OUT_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SCSI_OUT_DATA_MASK | SCSI_OUT_REQ);
+    gpio_mode_set(SCSI_OUT_IO_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,   SCSI_OUT_IO_PIN);
+    gpio_mode_set(SCSI_OUT_CD_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,   SCSI_OUT_CD_PIN);
+    gpio_mode_set(SCSI_OUT_SEL_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,  SCSI_OUT_SEL_PIN);
+    gpio_mode_set(SCSI_OUT_MSG_PORT,  GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SCSI_OUT_MSG_PIN);
+    gpio_mode_set(SCSI_OUT_RST_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,  SCSI_OUT_RST_PIN);
+    gpio_mode_set(SCSI_OUT_BSY_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE,  SCSI_OUT_BSY_PIN);
+    
+    gpio_output_options_set(SCSI_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ,     SCSI_OUT_DATA_MASK | SCSI_OUT_REQ);
+    gpio_output_options_set(SCSI_OUT_IO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ,  SCSI_OUT_IO_PIN);
+    gpio_output_options_set(SCSI_OUT_CD_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ,  SCSI_OUT_CD_PIN);
+    gpio_output_options_set(SCSI_OUT_SEL_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SCSI_OUT_SEL_PIN);
+    gpio_output_options_set(SCSI_OUT_MSG_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SCSI_OUT_MSG_PIN);
+    gpio_output_options_set(SCSI_OUT_RST_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SCSI_OUT_RST_PIN);
+    gpio_output_options_set(SCSI_OUT_BSY_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, SCSI_OUT_BSY_PIN);
+
+
+    gpio_mode_set(SCSI_IN_PORT,  GPIO_MODE_INPUT, GPIO_PUPD_NONE,   SCSI_IN_MASK);
+    gpio_mode_set(SCSI_ATN_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE,   SCSI_ATN_PIN);
+    gpio_mode_set(SCSI_BSY_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE,   SCSI_BSY_PIN);
+    gpio_mode_set(SCSI_SEL_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE,   SCSI_SEL_PIN);
+    gpio_mode_set(SCSI_ACK_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE,   SCSI_ACK_PIN);
+    gpio_mode_set(SCSI_RST_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE,   SCSI_RST_PIN);
+
+
+    // Terminator enable
+    gpio_bit_set(SCSI_TERM_EN_PORT, SCSI_TERM_EN_PIN);
+    gpio_mode_set(SCSI_TERM_EN_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SCSI_TERM_EN_PIN);
+    gpio_output_options_set(SCSI_TERM_EN_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, SCSI_TERM_EN_PIN);
+
+    // SD card pins using SDIO
+    gpio_mode_set(SD_SDIO_DATA_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SD_SDIO_D0 | SD_SDIO_D1 | SD_SDIO_D2 | SD_SDIO_D3);
+    gpio_output_options_set(SD_SDIO_DATA_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SD_SDIO_D0 | SD_SDIO_D1 | SD_SDIO_D2 | SD_SDIO_D3);
+    gpio_af_set(SD_SDIO_DATA_PORT, GPIO_AF_12, SD_SDIO_D0 | SD_SDIO_D1 | SD_SDIO_D2 | SD_SDIO_D3);
+
+    gpio_mode_set(SD_SDIO_CLK_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SD_SDIO_CLK);
+    gpio_output_options_set(SD_SDIO_CLK_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SD_SDIO_CLK);
+    gpio_af_set(SD_SDIO_CLK_PORT, GPIO_AF_12, SD_SDIO_CLK);
+
+    gpio_mode_set(SD_SDIO_CMD_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SD_SDIO_CMD);
+    gpio_output_options_set(SD_SDIO_CMD_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SD_SDIO_CMD);
+    gpio_af_set(SD_SDIO_CMD_PORT, GPIO_AF_12, SD_SDIO_CMD);
+
+    // DIP switches
+    gpio_mode_set(DIP_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, DIPSW1_PIN | DIPSW2_PIN | DIPSW3_PIN);
+
+    // LED pins
+    gpio_bit_set(LED_PORT, LED_PINS);
+    gpio_mode_set(LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, LED_PINS);
+    gpio_output_options_set(LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, LED_PINS);
+
+    // SWO trace pin on PB3  
+    gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_3);
+    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3);
+    gpio_af_set(GPIOB, GPIO_AF_0, GPIO_PIN_3);  
+}
+
+void platform_late_init()
+{
+    logmsg("Platform: ", g_platform_name);
+    logmsg("FW Version: ", g_log_firmwareversion);
+    
+    
+
+    if (gpio_input_bit_get(DIP_PORT, DIPSW3_PIN))
+    {
+        logmsg("DIPSW3 is ON: Enabling SCSI termination");
+        gpio_bit_reset(SCSI_TERM_EN_PORT, SCSI_TERM_EN_PIN);
+    }
+    else
+    {
+        logmsg("DIPSW3 is OFF: SCSI termination disabled");
+    }
+
+    if (gpio_input_bit_get(DIP_PORT, DIPSW2_PIN))
+    {
+        logmsg("DIPSW2 is ON: enabling debug messages");
+        g_log_debug = true;
+    }
+    else
+    {
+        g_log_debug = false;
+    }
+
+    if (gpio_input_bit_get(DIP_PORT, DIPSW1_PIN))
+    {
+        logmsg("DIPSW1 is ON: enabling Apple quirks by default");
+        g_enable_apple_quirks = true;
+    }
+    usb_hs_init();
+    greenpak_load_firmware();
+}
+
+void platform_disable_led(void)
+{   
+    gpio_mode_set(LED_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, LED_PINS);
+    logmsg("Disabling status LED");
+}
+
+/*****************************************/
+/* Supply voltage monitor                */
+/*****************************************/
+
+// Use ADC to implement supply voltage monitoring for the +3.0V rail.
+// This works by sampling the Vrefint, which has
+// a voltage of 1.2 V, allowing to calculate the VDD voltage.
+static void adc_poll()
+{
+#if PLATFORM_VDD_WARNING_LIMIT_mV > 0
+    static bool initialized = false;
+    static int lowest_vdd_seen = PLATFORM_VDD_WARNING_LIMIT_mV;
+
+    if (!initialized)
+    {
+        rcu_periph_clock_enable(RCU_ADC0);
+        adc_enable(ADC0);
+        adc_calibration_enable(ADC0);
+        adc_channel_16_to_18(ADC_TEMP_VREF_CHANNEL_SWITCH, ENABLE);
+        adc_inserted_channel_config(ADC0, 0, ADC_CHANNEL_17, ADC_SAMPLETIME_144);
+        //TODO can these be safely removed
+        /*
+        adc_external_trigger_source_config(ADC0, ADC_INSERTED_CHANNEL, ADC0_1_2_EXTTRIG_INSERTED_NONE);
+        adc_external_trigger_config(ADC0, ADC_INSERTED_CHANNEL, ENABLE);
+        */
+        adc_software_trigger_enable(ADC0, ADC_INSERTED_CHANNEL);
+        initialized = true;
+    }
+
+    // Read previous result and start new one
+    int adc_value = ADC_IDATA0(ADC0);
+    adc_software_trigger_enable(ADC0, ADC_INSERTED_CHANNEL);
+
+    // adc_value = 1200mV * 4096 / Vdd
+    // => Vdd = 1200mV * 4096 / adc_value
+    // To avoid wasting time on division, compare against
+    // limit directly.
+    const int limit = (1200 * 4096) / PLATFORM_VDD_WARNING_LIMIT_mV;
+    if (adc_value > limit)
+    {
+        // Warn once, and then again if we detect even a lower drop.
+        int vdd_mV = (1200 * 4096) / adc_value;
+        if (vdd_mV < lowest_vdd_seen)
+        {
+            logmsg("WARNING: Detected supply voltage drop to ", vdd_mV, "mV. Verify power supply is adequate.");
+            lowest_vdd_seen = vdd_mV - 50; // Small hysteresis to avoid excessive warnings
+        }
+    }
+#endif
+}
+
+/*****************************************/
+/* Debug logging and watchdog            */
+/*****************************************/
+
+// Send log data to USB UART if USB is connected.
+// Data is retrieved from the shared log ring buffer and
+// this function sends as much as fits in USB CDC buffer.
+//
+// This is normally called by platform_reset_watchdog() in
+// the normal polling loop. If code hangs, the watchdog_callback()
+// also starts calling this after 2 seconds.
+// This ensures that log messages get passed even if code hangs,
+// but does not unnecessarily delay normal execution.
+static void usb_log_poll()
+{
+    static uint32_t logpos = 0;
+
+    if (usb_hs_ready())
+    {
+        // Retrieve pointer to log start and determine number of bytes available.
+        uint32_t available = 0;
+        const char *data = log_get_buffer(&logpos, &available);
+        // Limit to CDC packet size
+        uint32_t len = available;
+        if (len == 0) return;
+        if (len > USB_CDC_EP_IN_WORKING_SIZE) len = USB_CDC_EP_IN_WORKING_SIZE;
+
+        // Update log position by the actual number of bytes sent
+        // If USB CDC buffer is full, this may be 0
+        usb_hs_send((uint8_t*)data, len);
+        logpos -= available - len;
+    }
+}
+
+
+
+
+/*****************************************/
+/* Crash handlers                        */
+/*****************************************/
+
+extern SdFs SD;
+
+// Writes log data to the PB3 SWO pin
+void platform_log(const char *s)
+{
+    while (*s)
+    {
+        // Write to SWO pin
+        while (ITM->PORT[0].u32 == 0);
+        ITM->PORT[0].u8 = *s++;
+    }
+}
+
+void platform_emergency_log_save()
+{
+    platform_set_sd_callback(NULL, NULL);
+
+    SD.begin(SD_CONFIG_CRASH);
+    FsFile crashfile = SD.open(CRASHFILE, O_WRONLY | O_CREAT | O_TRUNC);
+
+    if (!crashfile.isOpen())
+    {
+        // Try to reinitialize
+        int max_retry = 10;
+        while (max_retry-- > 0 && !SD.begin(SD_CONFIG_CRASH));
+
+        crashfile = SD.open(CRASHFILE, O_WRONLY | O_CREAT | O_TRUNC);
+    }
+
+    uint32_t startpos = 0;
+    crashfile.write(log_get_buffer(&startpos));
+    crashfile.write(log_get_buffer(&startpos));
+    crashfile.flush();
+    crashfile.close();
+}
+
+extern uint32_t _estack;
+
+__attribute__((noinline))
+void show_hardfault(uint32_t *sp)
+{
+    uint32_t pc = sp[6];
+    uint32_t lr = sp[5];
+    uint32_t cfsr = SCB->CFSR;
+    
+    logmsg("--------------");
+    logmsg("CRASH!");
+    logmsg("Platform: ", g_platform_name);
+    logmsg("FW Version: ", g_log_firmwareversion);
+    logmsg("scsiDev.cdb: ", bytearray(scsiDev.cdb, 12));
+    logmsg("scsiDev.phase: ", (int)scsiDev.phase);
+    logmsg("CFSR: ", cfsr);
+    logmsg("SP: ", (uint32_t)sp);
+    logmsg("PC: ", pc);
+    logmsg("LR: ", lr);
+    logmsg("R0: ", sp[0]);
+    logmsg("R1: ", sp[1]);
+    logmsg("R2: ", sp[2]);
+    logmsg("R3: ", sp[3]);
+
+    uint32_t *p = (uint32_t*)((uint32_t)sp & ~3);
+    for (int i = 0; i < 8; i++)
+    {
+        if (p == &_estack) break; // End of stack
+        
+        logmsg("STACK ", (uint32_t)p, ":    ", p[0], " ", p[1], " ", p[2], " ", p[3]);
+        p += 4;
+    }
+
+    platform_emergency_log_save();
+
+    while (1)
+    {
+        // Flash the crash address on the LED
+        // Short pulse means 0, long pulse means 1
+        int base_delay = 1000;
+        for (int i = 31; i >= 0; i--)
+        {
+            LED_OFF();
+            for (int j = 0; j < base_delay; j++) delay_ns(100000);
+            
+            int delay = (pc & (1 << i)) ? (3 * base_delay) : base_delay;
+            LED_ON();
+            for (int j = 0; j < delay; j++) delay_ns(100000);
+            LED_OFF();
+        }
+
+        for (int j = 0; j < base_delay * 10; j++) delay_ns(100000);
+    }
+}
+
+__attribute__((naked, interrupt))
+void HardFault_Handler(void)
+{
+    // Copies stack pointer into first argument
+    asm("mrs r0, msp\n"
+        "b show_hardfault": : : "r0");
+}
+
+__attribute__((naked, interrupt))
+void MemManage_Handler(void)
+{
+    asm("mrs r0, msp\n"
+        "b show_hardfault": : : "r0");
+}
+
+__attribute__((naked, interrupt))
+void BusFault_Handler(void)
+{
+    asm("mrs r0, msp\n"
+        "b show_hardfault": : : "r0");
+}
+
+__attribute__((naked, interrupt))
+void UsageFault_Handler(void)
+{
+    asm("mrs r0, msp\n"
+        "b show_hardfault": : : "r0");
+}
+
+void __assert_func(const char *file, int line, const char *func, const char *expr)
+{
+    uint32_t dummy = 0;
+
+    logmsg("--------------");
+    logmsg("ASSERT FAILED!");
+    logmsg("Platform: ", g_platform_name);
+    logmsg("FW Version: ", g_log_firmwareversion);
+    logmsg("scsiDev.cdb: ", bytearray(scsiDev.cdb, 12));
+    logmsg("scsiDev.phase: ", (int)scsiDev.phase);
+    logmsg("Assert failed: ", file , ":", line, " in ", func, ":", expr);
+
+    uint32_t *p = (uint32_t*)((uint32_t)&dummy & ~3);
+    for (int i = 0; i < 8; i++)
+    {
+        if (p == &_estack) break; // End of stack
+
+        logmsg("STACK ", (uint32_t)p, ":    ", p[0], " ", p[1], " ", p[2], " ", p[3]);
+        p += 4;
+    }
+
+    platform_emergency_log_save();
+
+    while(1)
+    {
+        LED_OFF();
+        for (int j = 0; j < 1000; j++) delay_ns(100000);
+        LED_ON();
+        for (int j = 0; j < 1000; j++) delay_ns(100000);
+    }
+}
+
+} /* extern "C" */
+
+static void watchdog_handler(uint32_t *sp)
+{
+    logmsg("-------------- WATCHDOG TIMEOUT");
+    show_hardfault(sp);
+}
+
+void platform_reset_watchdog()
+{
+    // This uses a software watchdog based on systick timer interrupt.
+    // It gives us opportunity to collect better debug info than the
+    // full hardware reset that would be caused by hardware watchdog.
+    g_watchdog_timeout = WATCHDOG_CRASH_TIMEOUT;
+}
+
+// Poll function that is called every few milliseconds.
+// Can be left empty or used for platform-specific processing.
+void platform_poll()
+{
+    // adc_poll();
+    usb_log_poll();
+}
+
+uint8_t platform_get_buttons()
+{
+    return 0;
+}
+
+
+/***********************/
+/* Flash reprogramming */
+/***********************/
+#define SECTOR_NUMBER_TO_ID_ERROR 0xFFFFFFFF
+
+static uint32_t sector_number_to_id(uint32_t sector_number)
+{
+    if(11 >= sector_number){
+        return CTL_SN(sector_number);
+    }else if(23 >= sector_number){
+        return CTL_SN(sector_number + 4);
+    }else if(27 >= sector_number){
+        return CTL_SN(sector_number - 12);
+    }
+    return SECTOR_NUMBER_TO_ID_ERROR;
+}
+
+static bool erase_flash_sector(uint32_t sector)
+{
+    fmc_unlock();
+    fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_OPERR | FMC_FLAG_WPERR | FMC_FLAG_PGMERR | FMC_FLAG_PGSERR);
+    uint32_t sector_id = sector_number_to_id(sector);
+    if (sector_id == SECTOR_NUMBER_TO_ID_ERROR)
+    {
+        logmsg("Sector ", (int) sector, " does not exist");
+        return false;
+    }
+
+    if (FMC_READY != fmc_sector_erase(sector_id))
+    {
+        logmsg("Failed flash failed to erase sector, ", (int) sector);
+        LED_OFF();
+        return false;
+    }
+    fmc_lock();
+    return true;
+}
+
+static bool write_flash(uint32_t offset, uint32_t length, uint8_t buffer[PLATFORM_FLASH_WRITE_BUFFER_SIZE])
+{
+        
+    fmc_unlock();
+    fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_OPERR | FMC_FLAG_WPERR | FMC_FLAG_PGMERR | FMC_FLAG_PGSERR);
+    fmc_state_enum status;
+    uint32_t *buf32 = (uint32_t*)buffer;
+    uint32_t memory_address = FLASH_BASE + offset;
+    uint32_t num_words = length / 4;
+    if (length % 4 == 0)
+    {
+        for (int i = 0; i < num_words; i++)
+        {
+            status = fmc_word_program(memory_address, buf32[i]);
+            if (status != FMC_READY)
+            {
+                logmsg("Flash write failed at address: ", memory_address, " with code ", (int)status);
+                return false;
+            }
+            memory_address += 4;   
+        }
+    }
+    else
+    {
+       logmsg("Firmware size expected to be word (4byte) aligned");
+    }
+
+    fmc_lock();
+    memory_address = FLASH_BASE + offset;
+    for (int i = 0; i < num_words; i++)
+    {
+        uint32_t expected = buf32[i];
+        uint32_t actual = *(volatile uint32_t*)(memory_address);
+        if (actual != expected)
+        {
+            logmsg("Flash word verify failed memory address ", memory_address, " got ", actual, " expected ", expected);
+            return false;
+        }
+        memory_address += 4;
+    }
+
+    return true;
+}
+
+// the size of the main code without the bootloader
+static uint32_t firmware_size(FsFile &file)
+{
+    uint32_t fwsize = file.size();
+    if (fwsize <=  PLATFORM_BOOTLOADER_SIZE )
+    {
+        logmsg("Firmware file size too small: ", fwsize, " bootloader fits in the first : ", PLATFORM_BOOTLOADER_SIZE, " bytes");
+        return false;
+    }
+    return fwsize - PLATFORM_BOOTLOADER_SIZE;
+}
+
+bool platform_firmware_erase(FsFile &file)
+{
+    uint32_t bootloader_sector_index = 0;
+    uint32_t bootloader_sector_byte_count = 0;
+    const uint32_t map_length = sizeof(platform_flash_sector_map)/sizeof(platform_flash_sector_map[0]);
+    // Find at which sector the bootloader ends so it isn't overwritten
+    for(;;)
+    {
+        if (bootloader_sector_index < map_length)
+        {
+            bootloader_sector_byte_count += platform_flash_sector_map[bootloader_sector_index];
+            if (bootloader_sector_byte_count < PLATFORM_BOOTLOADER_SIZE)
+            {
+                bootloader_sector_index++;
+            }
+            else
+            {
+                break;
+            }    
+        }
+        else
+        {
+            logmsg("Bootloader does not fit in flash");
+            return false;
+        }
+                
+    }
+    
+    // find the last sector the mainline firmware ends
+    uint32_t fwsize = firmware_size(file);
+    uint32_t firmware_sector_start = bootloader_sector_index + 1;
+    uint32_t last_sector_index = firmware_sector_start;
+    uint32_t last_sector_byte_count = 0;
+    for(;;)
+    {
+        if (last_sector_index < map_length)
+        {
+            last_sector_byte_count += platform_flash_sector_map[last_sector_index];
+            if (fwsize > last_sector_byte_count)
+            {
+                last_sector_index++;
+            }
+            else
+            {
+                break;
+            }
+        }
+        else
+        {
+            logmsg("Firmware too large: ", (int) fwsize, 
+                    " space left after the bootloader ",  last_sector_byte_count,
+                    " total flash size ", (int)PLATFORM_FLASH_TOTAL_SIZE);
+            return false;
+        }
+    }
+
+    // Erase the sectors the mainline firmware will be written to
+    for (int i = firmware_sector_start; i <= last_sector_index; i++)
+    {
+        if (i % 2 == 0)
+        {
+            LED_ON();
+        }
+        else
+        {
+            LED_OFF();
+        }
+
+        if (!erase_flash_sector(i))
+        {
+            logmsg("Flash failed to erase sector ", i);
+            return false;
+        }
+        
+    }
+    LED_OFF();
+    return true;
+}
+
+bool platform_firmware_program(FsFile &file)
+{
+    // write the mainline firmware to flash
+    int32_t bytes_read = 0;
+    uint32_t address_offset = PLATFORM_BOOTLOADER_SIZE;
+    
+    // Make sure the buffer is aligned to word boundary
+    static uint32_t buffer32[PLATFORM_FLASH_WRITE_BUFFER_SIZE / 4];
+    uint8_t *buffer = (uint8_t*)buffer32;
+
+    if (!file.seek(PLATFORM_BOOTLOADER_SIZE))
+    {
+        logmsg("Seek failed");
+        return false;
+    }
+
+    dbgmsg("Writing flash at firmware offset ", address_offset, " data ", bytearray(buffer, 4));
+
+    for(;;)
+    {
+        if ((address_offset - PLATFORM_BOOTLOADER_SIZE) / PLATFORM_FLASH_WRITE_BUFFER_SIZE % 2)
+        {
+            LED_ON();
+        }
+        else
+        {
+            LED_OFF();
+        }
+        
+        bytes_read = file.read(buffer, PLATFORM_FLASH_WRITE_BUFFER_SIZE);
+        if ( bytes_read < 0)
+        {
+            logmsg("Firmware file read failed, error code ", (int) bytes_read);
+            return false;
+        }
+        if (!write_flash(address_offset, bytes_read, buffer))
+        {
+            logmsg("Failed to write flash at offset: ", address_offset, " bytes read: ",(int) bytes_read);
+            return false;
+        }
+        
+        // check the mainline firmware is valid
+        if (address_offset == PLATFORM_BOOTLOADER_SIZE)
+        {
+            if (buffer[3] != 0x20 || buffer[7] != 0x08)
+            {
+                logmsg("Invalid firmware file, starts with: ", bytearray(buffer, 16));
+                return false;
+            }
+
+        }
+
+        if (bytes_read < PLATFORM_FLASH_WRITE_BUFFER_SIZE)
+        {
+            break;
+        }
+        address_offset += bytes_read;
+        
+    }
+    LED_OFF();
+    return true;
+}
+
+void platform_boot_to_main_firmware()
+{
+    uint32_t *mainprogram_start = (uint32_t*)(0x08000000 + PLATFORM_BOOTLOADER_SIZE);
+    SCB->VTOR = (uint32_t)mainprogram_start;
+  
+    __asm__(
+        "msr msp, %0\n\t"
+        "bx %1" : : "r" (mainprogram_start[0]),
+                    "r" (mainprogram_start[1]) : "memory");
+}
+
+/**************************************/
+/* SCSI configuration based on DIPSW1 */
+/**************************************/
+
+void platform_config_hook(S2S_TargetCfg *config)
+{
+    // Enable Apple quirks by dip switch
+    if (g_enable_apple_quirks)
+    {
+        if (config->quirks == S2S_CFG_QUIRKS_NONE)
+        {
+            config->quirks = S2S_CFG_QUIRKS_APPLE;
+        }
+    }
+}
+
+/**********************************************/
+/* Mapping from data bytes to GPIO BOP values */
+/**********************************************/
+
+#define PARITY(n) ((1 ^ (n) ^ ((n)>>1) ^ ((n)>>2) ^ ((n)>>3) ^ ((n)>>4) ^ ((n)>>5) ^ ((n)>>6) ^ ((n)>>7)) & 1)
+#define X(n) (\
+    ((n & 0x01) ? (SCSI_OUT_DB0 << 16) : SCSI_OUT_DB0) | \
+    ((n & 0x02) ? (SCSI_OUT_DB1 << 16) : SCSI_OUT_DB1) | \
+    ((n & 0x04) ? (SCSI_OUT_DB2 << 16) : SCSI_OUT_DB2) | \
+    ((n & 0x08) ? (SCSI_OUT_DB3 << 16) : SCSI_OUT_DB3) | \
+    ((n & 0x10) ? (SCSI_OUT_DB4 << 16) : SCSI_OUT_DB4) | \
+    ((n & 0x20) ? (SCSI_OUT_DB5 << 16) : SCSI_OUT_DB5) | \
+    ((n & 0x40) ? (SCSI_OUT_DB6 << 16) : SCSI_OUT_DB6) | \
+    ((n & 0x80) ? (SCSI_OUT_DB7 << 16) : SCSI_OUT_DB7) | \
+    (PARITY(n)  ? (SCSI_OUT_DBP << 16) : SCSI_OUT_DBP) | \
+    (SCSI_OUT_REQ) \
+)
+    
+const uint32_t g_scsi_out_byte_to_bop[256] =
+{
+    X(0x00), X(0x01), X(0x02), X(0x03), X(0x04), X(0x05), X(0x06), X(0x07), X(0x08), X(0x09), X(0x0a), X(0x0b), X(0x0c), X(0x0d), X(0x0e), X(0x0f),
+    X(0x10), X(0x11), X(0x12), X(0x13), X(0x14), X(0x15), X(0x16), X(0x17), X(0x18), X(0x19), X(0x1a), X(0x1b), X(0x1c), X(0x1d), X(0x1e), X(0x1f),
+    X(0x20), X(0x21), X(0x22), X(0x23), X(0x24), X(0x25), X(0x26), X(0x27), X(0x28), X(0x29), X(0x2a), X(0x2b), X(0x2c), X(0x2d), X(0x2e), X(0x2f),
+    X(0x30), X(0x31), X(0x32), X(0x33), X(0x34), X(0x35), X(0x36), X(0x37), X(0x38), X(0x39), X(0x3a), X(0x3b), X(0x3c), X(0x3d), X(0x3e), X(0x3f),
+    X(0x40), X(0x41), X(0x42), X(0x43), X(0x44), X(0x45), X(0x46), X(0x47), X(0x48), X(0x49), X(0x4a), X(0x4b), X(0x4c), X(0x4d), X(0x4e), X(0x4f),
+    X(0x50), X(0x51), X(0x52), X(0x53), X(0x54), X(0x55), X(0x56), X(0x57), X(0x58), X(0x59), X(0x5a), X(0x5b), X(0x5c), X(0x5d), X(0x5e), X(0x5f),
+    X(0x60), X(0x61), X(0x62), X(0x63), X(0x64), X(0x65), X(0x66), X(0x67), X(0x68), X(0x69), X(0x6a), X(0x6b), X(0x6c), X(0x6d), X(0x6e), X(0x6f),
+    X(0x70), X(0x71), X(0x72), X(0x73), X(0x74), X(0x75), X(0x76), X(0x77), X(0x78), X(0x79), X(0x7a), X(0x7b), X(0x7c), X(0x7d), X(0x7e), X(0x7f),
+    X(0x80), X(0x81), X(0x82), X(0x83), X(0x84), X(0x85), X(0x86), X(0x87), X(0x88), X(0x89), X(0x8a), X(0x8b), X(0x8c), X(0x8d), X(0x8e), X(0x8f),
+    X(0x90), X(0x91), X(0x92), X(0x93), X(0x94), X(0x95), X(0x96), X(0x97), X(0x98), X(0x99), X(0x9a), X(0x9b), X(0x9c), X(0x9d), X(0x9e), X(0x9f),
+    X(0xa0), X(0xa1), X(0xa2), X(0xa3), X(0xa4), X(0xa5), X(0xa6), X(0xa7), X(0xa8), X(0xa9), X(0xaa), X(0xab), X(0xac), X(0xad), X(0xae), X(0xaf),
+    X(0xb0), X(0xb1), X(0xb2), X(0xb3), X(0xb4), X(0xb5), X(0xb6), X(0xb7), X(0xb8), X(0xb9), X(0xba), X(0xbb), X(0xbc), X(0xbd), X(0xbe), X(0xbf),
+    X(0xc0), X(0xc1), X(0xc2), X(0xc3), X(0xc4), X(0xc5), X(0xc6), X(0xc7), X(0xc8), X(0xc9), X(0xca), X(0xcb), X(0xcc), X(0xcd), X(0xce), X(0xcf),
+    X(0xd0), X(0xd1), X(0xd2), X(0xd3), X(0xd4), X(0xd5), X(0xd6), X(0xd7), X(0xd8), X(0xd9), X(0xda), X(0xdb), X(0xdc), X(0xdd), X(0xde), X(0xdf),
+    X(0xe0), X(0xe1), X(0xe2), X(0xe3), X(0xe4), X(0xe5), X(0xe6), X(0xe7), X(0xe8), X(0xe9), X(0xea), X(0xeb), X(0xec), X(0xed), X(0xee), X(0xef),
+    X(0xf0), X(0xf1), X(0xf2), X(0xf3), X(0xf4), X(0xf5), X(0xf6), X(0xf7), X(0xf8), X(0xf9), X(0xfa), X(0xfb), X(0xfc), X(0xfd), X(0xfe), X(0xff)
+};
+
+#undef X

+ 201 - 0
lib/ZuluSCSI_platform_GD32F450/ZuluSCSI_platform.h

@@ -0,0 +1,201 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+// Platform-specific definitions for ZuluSCSI.
+// Can be customized for different microcontrollers, this file is for GD32F205VCT6.
+
+#pragma once
+
+#include <gd32f4xx.h>
+#include <gd32f4xx_gpio.h>
+#include <scsi2sd.h>
+#include "ZuluSCSI_config.h"
+
+
+#ifdef __cplusplus
+#include <SdFat.h>
+
+extern "C" {
+#endif
+
+extern const char *g_platform_name;
+
+
+#if defined(ZULUSCSI_V1_4)
+#   define PLATFORM_NAME "ZuluSCSI v1.4"
+#   define PLATFORM_REVISION "1.4"
+#   define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_10
+#   define PLATFORM_OPTIMAL_MIN_SD_WRITE_SIZE 4096
+#   define PLATFORM_OPTIMAL_MAX_SD_WRITE_SIZE 65536
+#   define PLATFORM_OPTIMAL_LAST_SD_WRITE_SIZE 8192
+#   define PLATFORM_FLASH_SECTOR_ERASE
+#   include "ZuluSCSI_v1_4_gpio.h"
+#endif
+
+#ifndef PLATFORM_VDD_WARNING_LIMIT_mV
+#define PLATFORM_VDD_WARNING_LIMIT_mV 2800
+#endif
+
+// Debug logging functions
+void platform_log(const char *s);
+
+// Minimal millis() implementation as GD32F205 does not
+// have an Arduino core yet.
+unsigned long millis(void);
+void delay(unsigned long ms);
+
+// Precise nanosecond delays
+// Works in interrupt context also, max delay 500 000 ns, min delay about 500 ns
+void delay_ns(unsigned long ns);
+
+static inline void delay_us(unsigned long us)
+{
+    if (us > 0)
+    {
+        delay_ns(us * 1000);
+    }
+}
+
+// Approximate fast delay
+static inline void delay_100ns()
+{
+    asm volatile ("nop \n nop \n nop \n nop \n nop");
+}
+
+// Initialize SPI and GPIO configuration
+void platform_init();
+
+// Initialization for main application, not used for bootloader
+void platform_late_init();
+
+// Hooks
+void platform_end_of_loop_hook(void);
+
+// Disable the status LED
+void platform_disable_led(void);
+
+// Setup soft watchdog
+void platform_reset_watchdog();
+
+// Poll function that is called every few milliseconds.
+// The SD card is free to access during this time, and pauses up to
+// few milliseconds shouldn't disturb SCSI communication.
+void platform_poll();
+
+// Returns the state of any platform-specific buttons.
+// The returned value should be a mask for buttons 1-8 in bits 0-7 respectively,
+// where '1' is a button pressed and '0' is a button released.
+// Debouncing logic is left up to the specific implementation.
+// This function should return without significantly delay.
+uint8_t platform_get_buttons();
+
+// Reinitialize SD card connection and save log from interrupt context.
+// This can be used in crash handlers.
+void platform_emergency_log_save();
+
+// Set callback that will be called during data transfer to/from SD card.
+// This can be used to implement simultaneous transfer to SCSI bus.
+typedef void (*sd_callback_t)(uint32_t bytes_complete);
+void platform_set_sd_callback(sd_callback_t func, const uint8_t *buffer);
+
+// This function is called by scsiPhy.cpp.
+// It resets the systick counter to give 1 millisecond of uninterrupted transfer time.
+// The total number of skips is kept track of to keep the correct time on average.
+void SysTick_Handle_PreEmptively();
+
+// Reprogram firmware in main program area.
+#define PLATFORM_BOOTLOADER_SIZE 32768
+#define PLATFORM_FLASH_TOTAL_SIZE (512 * 1024)
+
+// must be a factor of each sector map size
+#define PLATFORM_FLASH_WRITE_BUFFER_SIZE 2048
+// From GD32F4xx user manual
+const uint32_t platform_flash_sector_map[] =
+    {
+         16 * 1024,
+         16 * 1024,
+         16 * 1024,
+         16 * 1024,
+         64 * 1024,
+        128 * 1024,
+        128 * 1024, 
+        128 * 1024
+    };
+
+void platform_boot_to_main_firmware();
+
+// Configuration customizations based on DIP switch settings
+// When DIPSW1 is on, Apple quirks are enabled by default.
+void platform_config_hook(S2S_TargetCfg *config);
+#define PLATFORM_CONFIG_HOOK(cfg) platform_config_hook(cfg)
+
+// Write a single SCSI pin.
+// Example use: SCSI_OUT(ATN, 1) sets SCSI_ATN to low (active) state.
+#define SCSI_OUT(pin, state) \
+    GPIO_BOP(SCSI_OUT_ ## pin ## _PORT) = (SCSI_OUT_ ## pin ## _PIN) << (state ? 16 : 0)
+
+// Read a single SCSI pin.
+// Example use: SCSI_IN(ATN), returns 1 for active low state.
+#define SCSI_IN(pin) \
+    ((GPIO_ISTAT(SCSI_ ## pin ## _PORT) & (SCSI_ ## pin ## _PIN)) ? 0 : 1)
+
+// Write SCSI data bus, also sets REQ to inactive.
+extern const uint32_t g_scsi_out_byte_to_bop[256];
+#define SCSI_OUT_DATA(data) \
+    GPIO_BOP(SCSI_OUT_PORT) = g_scsi_out_byte_to_bop[(uint8_t)(data)]
+
+// Release SCSI data bus and REQ signal
+#define SCSI_RELEASE_DATA_REQ() \
+    GPIO_BOP(SCSI_OUT_PORT) = SCSI_OUT_DATA_MASK | SCSI_OUT_REQ
+
+// Release all SCSI outputs
+#define SCSI_RELEASE_OUTPUTS() \
+    GPIO_BOP(SCSI_OUT_PORT) = SCSI_OUT_DATA_MASK | SCSI_OUT_REQ, \
+    GPIO_BOP(SCSI_OUT_IO_PORT)  = SCSI_OUT_IO_PIN, \
+    GPIO_BOP(SCSI_OUT_CD_PORT)  = SCSI_OUT_CD_PIN, \
+    GPIO_BOP(SCSI_OUT_SEL_PORT) = SCSI_OUT_SEL_PIN, \
+    GPIO_BOP(SCSI_OUT_MSG_PORT) = SCSI_OUT_MSG_PIN, \
+    GPIO_BOP(SCSI_OUT_RST_PORT) = SCSI_OUT_RST_PIN, \
+    GPIO_BOP(SCSI_OUT_BSY_PORT) = SCSI_OUT_BSY_PIN
+
+// Read SCSI data bus
+#define SCSI_IN_DATA(data) \
+    (((~GPIO_ISTAT(SCSI_IN_PORT)) & SCSI_IN_MASK) >> SCSI_IN_SHIFT)
+
+#ifdef __cplusplus
+}
+
+bool platform_firmware_erase(FsFile &file);
+bool platform_firmware_program(FsFile &file);
+// SD card driver for SdFat
+
+// SDIO interface, ZuluSCSI v1.4
+class SdioConfig;
+extern SdioConfig g_sd_sdio_config;
+extern SdioConfig g_sd_sdio_config_crash;
+#define SD_CONFIG g_sd_sdio_config
+#define SD_CONFIG_CRASH g_sd_sdio_config_crash
+
+// Check if a DMA request for SD card read has completed.
+// This is used to optimize the timing of data transfers on SCSI bus.
+bool check_sd_read_done();
+
+#endif

+ 207 - 0
lib/ZuluSCSI_platform_GD32F450/ZuluSCSI_v1_4_gpio.h

@@ -0,0 +1,207 @@
+// GPIO definitions for ZuluSCSI v1.4
+
+#pragma once
+
+#include "gd32f4xx_syscfg.h"
+
+// SCSI data output port.
+// The output data is written using BSRR mechanism, so all data pins must be on same GPIO port.
+// The output pins are open-drain in hardware, using separate buffer chips for driving.
+#define SCSI_OUT_PORT GPIOD
+#define SCSI_OUT_DB7  GPIO_PIN_9
+#define SCSI_OUT_DB6  GPIO_PIN_10
+#define SCSI_OUT_DB5  GPIO_PIN_11
+#define SCSI_OUT_DB4  GPIO_PIN_12
+#define SCSI_OUT_DB3  GPIO_PIN_13
+#define SCSI_OUT_DB2  GPIO_PIN_14
+#define SCSI_OUT_DB1  GPIO_PIN_0
+#define SCSI_OUT_DB0  GPIO_PIN_1
+#define SCSI_OUT_DBP  GPIO_PIN_8
+#define SCSI_OUT_REQ  GPIO_PIN_4
+#define SCSI_OUT_DATA_MASK (SCSI_OUT_DB0 | SCSI_OUT_DB1 | SCSI_OUT_DB2 | SCSI_OUT_DB3 | SCSI_OUT_DB4 | SCSI_OUT_DB5 | SCSI_OUT_DB6 | SCSI_OUT_DB7 | SCSI_OUT_DBP)
+#define SCSI_OUT_REQ_IDX 4
+
+// Control signals to optional PLD device
+#define SCSI_OUT_PLD1 GPIO_PIN_15
+#define SCSI_OUT_PLD2 GPIO_PIN_3
+#define SCSI_OUT_PLD3 GPIO_PIN_5
+#define SCSI_OUT_PLD4 GPIO_PIN_7
+
+// Control signals for timer based DMA acceleration
+// TIMER0_CH1 triggers DMACHA
+// TIMER0_CH3 triggers DMACHB
+#define SCSI_TIMER TIMER0
+#define SCSI_TIMER_RCU RCU_TIMER0
+#define SCSI_TIMER_OUT_PORT GPIOB
+#define SCSI_TIMER_OUT_PIN GPIO_PIN_15
+#define SCSI_TIMER_OUT_AF GPIO_AF_1
+#define SCSI_TIMER_IN_PORT GPIOA
+#define SCSI_TIMER_IN_PIN GPIO_PIN_8
+#define SCSI_TIMER_IN_AF GPIO_AF_1
+#define SCSI_TIMER_DMA DMA1
+#define SCSI_TIMER_DMA_RCU RCU_DMA1
+#define SCSI_TIMER_DMACHA DMA_CH2
+#define SCSI_TIMER_DMACHA_SUB_PERIPH DMA_SUBPERI6
+#define SCSI_TIMER_DMACHB DMA_CH4
+#define SCSI_TIMER_DMACHB_SUB_PERIPH DMA_SUBPERI6
+#define SCSI_TIMER_DMACHA_IRQ DMA1_Channel2_IRQHandler
+#define SCSI_TIMER_DMACHA_IRQn DMA1_Channel2_IRQn
+#define SCSI_TIMER_DMACHB_IRQ DMA1_Channel4_IRQHandler
+#define SCSI_TIMER_DMACHB_IRQn DMA1_Channel4_IRQn
+
+// GreenPAK logic chip pins
+#define GREENPAK_I2C_ADDR 0x10
+#define GREENPAK_I2C_PORT GPIOB
+#define GREENPAK_I2C_SCL GPIO_PIN_8
+#define GREENPAK_I2C_SDA GPIO_PIN_9
+#define GREENPAK_PLD_PORT GPIOD
+#define GREENPAK_PLD_IO1 GPIO_PIN_15
+#define GREENPAK_PLD_IO2 GPIO_PIN_3
+#define GREENPAK_PLD_IO3 GPIO_PIN_5
+#define GREENPAK_PLD_IO4 GPIO_PIN_7
+#define GREENPAK_PLD_IO2_EXTI EXTI_3
+#define GREENPAK_PLD_IO2_EXTI_SOURCE_PORT EXTI_SOURCE_GPIOD
+#define GREENPAK_PLD_IO2_EXTI_SOURCE_PIN  EXTI_SOURCE_PIN3
+#define GREENPAK_IRQ  EXTI3_IRQHandler
+#define GREENPAK_IRQn EXTI3_IRQn
+
+// SCSI input data port
+#define SCSI_IN_PORT  GPIOE
+#define SCSI_IN_DB7   GPIO_PIN_15
+#define SCSI_IN_DB6   GPIO_PIN_14
+#define SCSI_IN_DB5   GPIO_PIN_13
+#define SCSI_IN_DB4   GPIO_PIN_12
+#define SCSI_IN_DB3   GPIO_PIN_11
+#define SCSI_IN_DB2   GPIO_PIN_10
+#define SCSI_IN_DB1   GPIO_PIN_9
+#define SCSI_IN_DB0   GPIO_PIN_8
+#define SCSI_IN_DBP   GPIO_PIN_7
+#define SCSI_IN_MASK  (SCSI_IN_DB7|SCSI_IN_DB6|SCSI_IN_DB5|SCSI_IN_DB4|SCSI_IN_DB3|SCSI_IN_DB2|SCSI_IN_DB1|SCSI_IN_DB0|SCSI_IN_DBP)
+#define SCSI_IN_SHIFT 8
+
+// SCSI output status lines
+#define SCSI_OUT_IO_PORT  GPIOA
+#define SCSI_OUT_IO_PIN   GPIO_PIN_4
+#define SCSI_OUT_CD_PORT  GPIOF
+#define SCSI_OUT_CD_PIN   GPIO_PIN_12
+#define SCSI_OUT_SEL_PORT GPIOA
+#define SCSI_OUT_SEL_PIN  GPIO_PIN_6
+#define SCSI_OUT_MSG_PORT GPIOG
+#define SCSI_OUT_MSG_PIN  GPIO_PIN_1
+#define SCSI_OUT_RST_PORT GPIOB
+#define SCSI_OUT_RST_PIN  GPIO_PIN_14
+#define SCSI_OUT_BSY_PORT GPIOG
+#define SCSI_OUT_BSY_PIN  GPIO_PIN_0
+#define SCSI_OUT_REQ_PORT SCSI_OUT_PORT
+#define SCSI_OUT_REQ_PIN  SCSI_OUT_REQ
+
+// SCSI input status signals
+#define SCSI_ACK_PORT GPIOA
+#define SCSI_ACK_PIN  GPIO_PIN_0
+#define SCSI_ATN_PORT GPIOF
+#define SCSI_ATN_PIN  GPIO_PIN_14
+#define SCSI_IN_ACK_IDX 0
+
+// Extra signals used with EXMC for synchronous mode
+#define SCSI_IN_ACK_EXMC_NWAIT_PORT GPIOD
+#define SCSI_IN_ACK_EXMC_NWAIT_PIN  GPIO_PIN_6
+#define SCSI_OUT_REQ_EXMC_NOE_PORT  GPIOD
+#define SCSI_OUT_REQ_EXMC_NOE_PIN   GPIO_PIN_4
+#define SCSI_OUT_REQ_EXMC_NOE_IDX   4
+#define SCSI_EXMC_DATA_SHIFT 5
+// DMA1 is the only controller that allows memory to memory transfers
+#define SCSI_EXMC_DMA DMA1  
+#define SCSI_EXMC_DMA_RCU RCU_DMA1
+#define SCSI_EXMC_DMACH DMA_CH0
+#define SCSI_SYNC_TIMER TIMER1
+#define SCSI_SYNC_TIMER_RCU RCU_TIMER1
+
+// SEL pin uses EXTI interrupt
+#define SCSI_SEL_PORT GPIOB
+#define SCSI_SEL_PIN  GPIO_PIN_11
+#define SCSI_SEL_EXTI EXTI_11
+#define SCSI_SEL_EXTI_SOURCE_PORT EXTI_SOURCE_GPIOB
+#define SCSI_SEL_EXTI_SOURCE_PIN EXTI_SOURCE_PIN11
+#define SCSI_SEL_IRQ EXTI10_15_IRQHandler
+#define SCSI_SEL_IRQn EXTI10_15_IRQn
+
+// BSY pin uses EXTI interrupt
+#define SCSI_BSY_PORT GPIOF
+#define SCSI_BSY_PIN  GPIO_PIN_13
+#define SCSI_BSY_EXTI EXTI_13
+#define SCSI_BSY_EXTI_SOURCE_PORT EXTI_SOURCE_GPIOF
+#define SCSI_BSY_EXTI_SOURCE_PIN  EXTI_SOURCE_PIN13
+#define SCSI_BSY_IRQ  EXTI10_15_IRQHandler
+#define SCSI_BSY_IRQn EXTI10_15_IRQn
+
+// RST pin uses EXTI interrupt
+#define SCSI_RST_PORT GPIOF
+#define SCSI_RST_PIN  GPIO_PIN_15
+#define SCSI_RST_EXTI EXTI_15
+#define SCSI_RST_EXTI_SOURCE_PORT EXTI_SOURCE_GPIOF
+#define SCSI_RST_EXTI_SOURCE_PIN EXTI_SOURCE_PIN15
+#define SCSI_RST_IRQ  EXTI10_15_IRQHandler
+#define SCSI_RST_IRQn EXTI10_15_IRQn
+
+// Terminator enable/disable config, active low
+#define SCSI_TERM_EN_PORT GPIOA
+#define SCSI_TERM_EN_PIN  GPIO_PIN_10
+
+// SD card pins
+#define SD_USE_SDIO 1
+#define SD_SDIO_DATA_PORT GPIOC
+#define SD_SDIO_D0        GPIO_PIN_8
+#define SD_SDIO_D1        GPIO_PIN_9
+#define SD_SDIO_D2        GPIO_PIN_10
+#define SD_SDIO_D3        GPIO_PIN_11
+#define SD_SDIO_CLK_PORT  GPIOC
+#define SD_SDIO_CLK       GPIO_PIN_12
+#define SD_SDIO_CMD_PORT  GPIOD
+#define SD_SDIO_CMD       GPIO_PIN_2
+
+// DIP switches
+#define DIP_PORT     GPIOB
+#define DIPSW1_PIN   GPIO_PIN_4
+#define DIPSW2_PIN   GPIO_PIN_6
+#define DIPSW3_PIN   GPIO_PIN_7
+
+// Status LED pins
+#define LED_PORT     GPIOF
+#define LED_I_PIN    GPIO_PIN_4
+#define LED_E_PIN    GPIO_PIN_5
+#define LED_PINS     (LED_I_PIN | LED_E_PIN)
+#define LED_ON()     gpio_bit_reset(LED_PORT, LED_PINS)
+#define LED_OFF()    gpio_bit_set(LED_PORT, LED_PINS)
+
+// User LEDs
+#define USER_LED_PORT       GPIOC
+#define USER_LED_1_PIN      GPIO_PIN_14
+#define USER_LED_2_PIN      GPIO_PIN_15
+#define USER_LED_1_ON()     gpio_bit_reset(USER_LED_PORT, USER_LED_1_PIN);
+#define USER_LED_1_OFF()    gpio_bit_set(USER_LED_PORT, USER_LED_1_PIN);
+#define USER_LED_2_ON()     gpio_bit_reset(USER_LED_PORT, USER_LED_2_PIN);
+#define USER_LED_2_OFF()    gpio_bit_set(USER_LED_PORT, USER_LED_2_PIN);
+
+// USB HS
+#define USB_HS_ULPI_STP_PORT GPIOC
+#define USB_HS_ULPI_STP_PIN  GPIO_PIN_0
+#define USB_HS_ULPI_CLK_PORT GPIOA
+#define USB_HS_ULPI_CLK_PIN  GPIO_PIN_5
+#define USB_HS_ULPI_NXT_PORT GPIOC
+#define USB_HS_ULPI_NXT_PIN  GPIO_PIN_3 
+#define USB_HS_ULPI_DIR_PORT GPIOC
+#define USB_HS_ULPI_DIR_PIN  GPIO_PIN_2
+
+
+#define USB_HS_ULPI_D0_PORT GPIOA
+#define USB_HS_ULPI_D0_PIN  GPIO_PIN_3 
+#define USB_HS_ULPI_D1_TO_7_PORT GPIOB
+#define USB_HS_ULPI_D1_TO_7_PIN   GPIO_PIN_0  \
+                                | GPIO_PIN_1  \
+                                | GPIO_PIN_10 \
+                                | GPIO_PIN_2  \
+                                | GPIO_PIN_12 \
+                                | GPIO_PIN_13 \
+                                | GPIO_PIN_5
+#define USB_HS_ULPI_AF 10
+

+ 28 - 0
lib/ZuluSCSI_platform_GD32F450/bsp.h

@@ -0,0 +1,28 @@
+/** 
+ * SCSI2SD V6 - Copyright (C) 2016 Michael McMaster <michael@codesrc.com>
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * This file is licensed under the GPL version 3 or any later version.  
+ * It is derived from bsp.h in SCSI2SD V6.
+ *  
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+// Dummy file for SCSI2SD.
+
+#pragma once
+
+#define S2S_DMA_ALIGN

+ 521 - 0
lib/ZuluSCSI_platform_GD32F450/gd32_cdc_acm_core.c

@@ -0,0 +1,521 @@
+/*!
+    \file    cdc_acm_core.c
+    \brief   CDC ACM driver
+
+    \version 2020-08-01, V3.0.0, firmware for GD32F4xx
+    \version 2020-12-07, V3.0.1, firmware for GD32F4xx
+*/
+
+/*
+    Copyright (c) 2020, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this 
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice, 
+       this list of conditions and the following disclaimer in the documentation 
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors 
+       may be used to endorse or promote products derived from this software without 
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
+OF SUCH DAMAGE.
+*/
+
+#include "gd32_cdc_acm_core.h"
+
+#define USBD_VID                          0x28E9U
+#define USBD_PID                          0x018AU
+
+/* note:it should use the C99 standard when compiling the below codes */
+/* USB standard device descriptor */
+__ALIGN_BEGIN const usb_desc_dev gd32_cdc_dev_desc __ALIGN_END =
+{
+    .header = 
+     {
+         .bLength          = USB_DEV_DESC_LEN, 
+         .bDescriptorType  = USB_DESCTYPE_DEV,
+     },
+    .bcdUSB                = 0x0200U,
+    .bDeviceClass          = USB_CLASS_CDC,
+    .bDeviceSubClass       = 0x00U,
+    .bDeviceProtocol       = 0x00U,
+    .bMaxPacketSize0       = USB_FS_EP0_MAX_LEN,
+    .idVendor              = USBD_VID,
+    .idProduct             = USBD_PID,
+    .bcdDevice             = 0x0100U,
+    .iManufacturer         = STR_IDX_MFC,
+    .iProduct              = STR_IDX_PRODUCT,
+    .iSerialNumber         = STR_IDX_SERIAL,
+    .bNumberConfigurations = USBD_CFG_MAX_NUM,
+};
+
+/* USB device configuration descriptor */
+__ALIGN_BEGIN const usb_cdc_desc_config_set gd32_cdc_config_desc __ALIGN_END = 
+{
+    .config = 
+    {
+        .header = 
+         {
+             .bLength         = sizeof(usb_desc_config), 
+             .bDescriptorType = USB_DESCTYPE_CONFIG,
+         },
+        .wTotalLength         = USB_CDC_ACM_CONFIG_DESC_SIZE,
+        .bNumInterfaces       = 0x02U,
+        .bConfigurationValue  = 0x01U,
+        .iConfiguration       = 0x00U,
+        .bmAttributes         = 0x80U,
+        .bMaxPower            = 0x32U
+    },
+
+    .cmd_itf = 
+    {
+        .header = 
+         {
+             .bLength         = sizeof(usb_desc_itf), 
+             .bDescriptorType = USB_DESCTYPE_ITF 
+         },
+        .bInterfaceNumber     = 0x00U,
+        .bAlternateSetting    = 0x00U,
+        .bNumEndpoints        = 0x01U,
+        .bInterfaceClass      = USB_CLASS_CDC,
+        .bInterfaceSubClass   = USB_CDC_SUBCLASS_ACM,
+        .bInterfaceProtocol   = USB_CDC_PROTOCOL_AT,
+        .iInterface           = 0x00U
+    },
+
+    .cdc_header = 
+    {
+        .header =
+         {
+            .bLength         = sizeof(usb_desc_header_func), 
+            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
+         },
+        .bDescriptorSubtype  = 0x00U,
+        .bcdCDC              = 0x0110U
+    },
+
+    .cdc_call_managment = 
+    {
+        .header = 
+         {
+            .bLength         = sizeof(usb_desc_call_managment_func), 
+            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
+         },
+        .bDescriptorSubtype  = 0x01U,
+        .bmCapabilities      = 0x00U,
+        .bDataInterface      = 0x01U
+    },
+
+    .cdc_acm = 
+    {
+        .header = 
+         {
+            .bLength         = sizeof(usb_desc_acm_func), 
+            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
+         },
+        .bDescriptorSubtype  = 0x02U,
+        .bmCapabilities      = 0x02U,
+    },
+
+    .cdc_union = 
+    {
+        .header = 
+         {
+            .bLength         = sizeof(usb_desc_union_func), 
+            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
+         },
+        .bDescriptorSubtype  = 0x06U,
+        .bMasterInterface    = 0x00U,
+        .bSlaveInterface0    = 0x01U,
+    },
+
+    .cdc_cmd_endpoint = 
+    {
+        .header = 
+         {
+            .bLength         = sizeof(usb_desc_ep), 
+            .bDescriptorType = USB_DESCTYPE_EP,
+         },
+        .bEndpointAddress    = CDC_CMD_EP,
+        .bmAttributes        = USB_EP_ATTR_INT,
+        .wMaxPacketSize      = USB_CDC_CMD_PACKET_SIZE,
+        .bInterval           = 0x0AU
+    },
+
+    .cdc_data_interface = 
+    {
+        .header = 
+         {
+            .bLength         = sizeof(usb_desc_itf), 
+            .bDescriptorType = USB_DESCTYPE_ITF,
+         },
+        .bInterfaceNumber    = 0x01U,
+        .bAlternateSetting   = 0x00U,
+        .bNumEndpoints       = 0x02U,
+        .bInterfaceClass     = USB_CLASS_DATA,
+        .bInterfaceSubClass  = 0x00U,
+        .bInterfaceProtocol  = USB_CDC_PROTOCOL_NONE,
+        .iInterface          = 0x00U
+    },
+
+    .cdc_out_endpoint = 
+    {
+        .header = 
+         {
+             .bLength         = sizeof(usb_desc_ep), 
+             .bDescriptorType = USB_DESCTYPE_EP, 
+         },
+        .bEndpointAddress     = CDC_DATA_OUT_EP,
+        .bmAttributes         = USB_EP_ATTR_BULK,
+        .wMaxPacketSize       = USB_CDC_DATA_PACKET_SIZE,
+        .bInterval            = 0x00U
+    },
+
+    .cdc_in_endpoint = 
+    {
+        .header = 
+         {
+             .bLength         = sizeof(usb_desc_ep), 
+             .bDescriptorType = USB_DESCTYPE_EP 
+         },
+        .bEndpointAddress     = CDC_DATA_IN_EP,
+        .bmAttributes         = USB_EP_ATTR_BULK,
+        .wMaxPacketSize       = USB_CDC_DATA_PACKET_SIZE,
+        .bInterval            = 0x00U
+    }
+};
+
+/* USB language ID Descriptor */
+static __ALIGN_BEGIN const usb_desc_LANGID usbd_language_id_desc __ALIGN_END = 
+{
+    .header = 
+     {
+         .bLength         = sizeof(usb_desc_LANGID), 
+         .bDescriptorType = USB_DESCTYPE_STR,
+     },
+    .wLANGID              = ENG_LANGID
+};
+
+/* USB manufacture string */
+static __ALIGN_BEGIN const usb_desc_str manufacturer_string __ALIGN_END = 
+{
+    .header = 
+     {
+         .bLength         = USB_STRING_LEN(19), 
+         .bDescriptorType = USB_DESCTYPE_STR,
+     },
+    .unicode_string = {'R', 'a', 'b', 'b', 'i', 't', 'H', 'o', 'l','e','C','o','m','p','u','t','i','n','g'}
+};
+
+/* USB product string */
+static __ALIGN_BEGIN const usb_desc_str product_string __ALIGN_END = 
+{
+    .header = 
+     {
+         .bLength         = USB_STRING_LEN(11), 
+         .bDescriptorType = USB_DESCTYPE_STR,
+     },
+    .unicode_string = {'Z', 'u', 'l', 'u', 'S', 'C', 'S', 'I', '-', 'F', '4'}
+};
+
+/* USBD serial string */
+static __ALIGN_BEGIN usb_desc_str serial_string __ALIGN_END = 
+{
+    .header = 
+     {
+         .bLength         = USB_STRING_LEN(12), 
+         .bDescriptorType = USB_DESCTYPE_STR,
+     }
+};
+
+/* USB string descriptor set */
+void *const gd32_usbd_cdc_strings[] = 
+{
+    [STR_IDX_LANGID]  = (uint8_t *)&usbd_language_id_desc,
+    [STR_IDX_MFC]     = (uint8_t *)&manufacturer_string,
+    [STR_IDX_PRODUCT] = (uint8_t *)&product_string,
+    [STR_IDX_SERIAL]  = (uint8_t *)&serial_string
+};
+
+usb_desc gd32_cdc_desc = 
+{
+    .dev_desc    = (uint8_t *)&gd32_cdc_dev_desc,
+    .config_desc = (uint8_t *)&gd32_cdc_config_desc,
+    .strings     = gd32_usbd_cdc_strings
+};
+
+/* local function prototypes ('static') */
+static uint8_t cdc_acm_init   (usb_dev *udev, uint8_t config_index);
+static uint8_t cdc_acm_deinit (usb_dev *udev, uint8_t config_index);
+static uint8_t cdc_acm_req    (usb_dev *udev, usb_req *req);
+static uint8_t cdc_ctlx_out   (usb_dev *udev);
+static uint8_t cdc_acm_in     (usb_dev *udev, uint8_t ep_num);
+static uint8_t cdc_acm_out    (usb_dev *udev, uint8_t ep_num);
+
+/* USB CDC device class callbacks structure */
+usb_class_core gd32_cdc_class =
+{
+    .command   = NO_CMD,
+    .alter_set = 0U,
+
+    .init      = cdc_acm_init,
+    .deinit    = cdc_acm_deinit,
+
+    .req_proc  = cdc_acm_req,
+    .ctlx_out  = cdc_ctlx_out,
+
+    .data_in   = cdc_acm_in,
+    .data_out  = cdc_acm_out
+};
+
+/*!
+    \brief      check CDC ACM is ready for data transfer
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     0 if CDC is ready, 5 else
+*/
+uint8_t gd32_cdc_acm_check_ready(usb_dev *udev)
+{
+    if (udev->dev.class_data[CDC_COM_INTERFACE] != NULL) {
+        gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+
+        if ((1U == cdc->packet_sent)) {
+            return 1U;
+        }
+    }
+
+    return 0U;
+}
+
+/*!
+    \brief      send CDC ACM data
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     USB device operation status
+*/
+void gd32_cdc_acm_data_send(usb_dev *udev, uint8_t *data, uint32_t length)
+{
+    gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+    if (cdc->packet_sent == 1)
+    {
+        cdc->packet_sent = 0;
+        usbd_ep_send (udev, CDC_DATA_IN_EP, data, length);
+    }
+    
+
+}
+
+/*!
+    \brief      receive CDC ACM data
+    \param[in]  udev: pointer to USB device instance
+    \param[out] none
+    \retval     USB device operation status
+*/
+void gd32_cdc_acm_data_receive (usb_dev *udev)
+{
+    gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+    usbd_ep_recev(udev, CDC_DATA_OUT_EP, (uint8_t*)(cdc->data), USB_CDC_DATA_PACKET_SIZE);
+}
+
+/*!
+    \brief      initialize the CDC ACM device
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  config_index: configuration index
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t cdc_acm_init (usb_dev *udev, uint8_t config_index)
+{
+    static __ALIGN_BEGIN gd32_usb_cdc_handler cdc_handler __ALIGN_END;
+
+    /* initialize the data TX endpoint */
+    usbd_ep_setup (udev, &(gd32_cdc_config_desc.cdc_in_endpoint));
+
+    /* initialize the data RX endpoint */
+    usbd_ep_setup (udev, &(gd32_cdc_config_desc.cdc_out_endpoint));
+
+    /* initialize the command TX endpoint */
+    usbd_ep_setup (udev, &(gd32_cdc_config_desc.cdc_cmd_endpoint));
+
+    /* initialize CDC handler structure */
+    cdc_handler.packet_receive = 1U;
+    cdc_handler.packet_sent = 1U;
+    cdc_handler.receive_length = 0U;
+
+    cdc_handler.line_coding = (acm_line){
+        .dwDTERate   = 115200,
+        .bCharFormat = 0,
+        .bParityType = 0,
+        .bDataBits   = 0x08
+    };
+
+    udev->dev.class_data[CDC_COM_INTERFACE] = (void *)&cdc_handler;
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      de-initialize the CDC ACM device
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  config_index: configuration index
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t cdc_acm_deinit (usb_dev *udev, uint8_t config_index)
+{
+    /* deinitialize the data TX/RX endpoint */
+    usbd_ep_clear (udev, CDC_DATA_IN_EP);
+    usbd_ep_clear (udev, CDC_DATA_OUT_EP);
+
+    /* deinitialize the command TX endpoint */
+    usbd_ep_clear (udev, CDC_CMD_EP);
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      handle the CDC ACM class-specific requests
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  req: device class-specific request
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t cdc_acm_req (usb_dev *udev, usb_req *req)
+{
+    gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+
+    usb_transc *transc = NULL;
+
+    switch (req->bRequest) {
+    case SEND_ENCAPSULATED_COMMAND:
+        /* no operation for this driver */
+        break;
+
+    case GET_ENCAPSULATED_RESPONSE:
+        /* no operation for this driver */
+        break;
+
+    case SET_COMM_FEATURE:
+        /* no operation for this driver */
+        break;
+
+    case GET_COMM_FEATURE:
+        /* no operation for this driver */
+        break;
+
+    case CLEAR_COMM_FEATURE:
+        /* no operation for this driver */
+        break;
+
+    case SET_LINE_CODING:
+        transc = &udev->dev.transc_out[0];
+        /* set the value of the current command to be processed */
+        udev->dev.class_core->alter_set = req->bRequest;
+
+        /* enable EP0 prepare to receive command data packet */
+        transc->remain_len = req->wLength;
+        transc->xfer_buf = cdc->cmd;
+        break;
+
+    case GET_LINE_CODING:
+        transc = &udev->dev.transc_in[0];
+
+        cdc->cmd[0] = (uint8_t)(cdc->line_coding.dwDTERate);
+        cdc->cmd[1] = (uint8_t)(cdc->line_coding.dwDTERate >> 8);
+        cdc->cmd[2] = (uint8_t)(cdc->line_coding.dwDTERate >> 16);
+        cdc->cmd[3] = (uint8_t)(cdc->line_coding.dwDTERate >> 24);
+        cdc->cmd[4] = cdc->line_coding.bCharFormat;
+        cdc->cmd[5] = cdc->line_coding.bParityType;
+        cdc->cmd[6] = cdc->line_coding.bDataBits;
+
+        transc->xfer_buf = cdc->cmd;
+        transc->remain_len = 7U;
+        break;
+
+    case SET_CONTROL_LINE_STATE:
+        /* no operation for this driver */
+        break;
+
+    case SEND_BREAK:
+        /* no operation for this driver */
+        break;
+
+    default:
+        break;
+    }
+
+    return USBD_OK;
+}
+
+static uint8_t cdc_ctlx_out (usb_dev *udev)
+{
+    gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+
+    if (udev->dev.class_core->alter_set != NO_CMD) {
+        /* process the command data */
+        cdc->line_coding.dwDTERate = (uint32_t)((uint32_t)cdc->cmd[0] | 
+                                               ((uint32_t)cdc->cmd[1] << 8U) | 
+                                               ((uint32_t)cdc->cmd[2] << 16U) | 
+                                               ((uint32_t)cdc->cmd[3] << 24U));
+
+        cdc->line_coding.bCharFormat = cdc->cmd[4];
+        cdc->line_coding.bParityType = cdc->cmd[5];
+        cdc->line_coding.bDataBits = cdc->cmd[6];
+
+        udev->dev.class_core->alter_set = NO_CMD;
+    }
+
+    return USBD_OK;
+}
+
+
+/*!
+    \brief      handle CDC ACM data
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: endpoint identifier
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t cdc_acm_in (usb_dev *udev, uint8_t ep_num)
+{
+    usb_transc *transc = &udev->dev.transc_in[EP_ID(ep_num)];
+
+    gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+
+    if ((0U == transc->xfer_len % transc->max_len) && (0U != transc->xfer_len)) {
+        usbd_ep_send (udev, ep_num, NULL, 0U);
+    } else {
+        cdc->packet_sent = 1U;
+    }
+
+    return USBD_OK;
+}
+
+/*!
+    \brief      handle CDC ACM data
+    \param[in]  udev: pointer to USB device instance
+    \param[in]  ep_num: endpoint identifier
+    \param[out] none
+    \retval     USB device operation status
+*/
+static uint8_t cdc_acm_out (usb_dev *udev, uint8_t ep_num)
+{
+    gd32_usb_cdc_handler *cdc = (gd32_usb_cdc_handler *)udev->dev.class_data[CDC_COM_INTERFACE];
+
+    cdc->packet_receive = 1U;
+    cdc->receive_length = ((usb_core_driver *)udev)->dev.transc_out[ep_num].xfer_count;
+
+    return USBD_OK;
+}

+ 66 - 0
lib/ZuluSCSI_platform_GD32F450/gd32_cdc_acm_core.h

@@ -0,0 +1,66 @@
+/*!
+    \file    cdc_acm_core.h
+    \brief   the header file of cdc acm driver
+
+    \version 2020-08-01, V3.0.0, firmware for GD32F4xx
+*/
+
+/*
+    Copyright (c) 2020, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this 
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice, 
+       this list of conditions and the following disclaimer in the documentation 
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors 
+       may be used to endorse or promote products derived from this software without 
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
+OF SUCH DAMAGE.
+*/
+
+#ifndef __GD32_CDC_ACM_CORE_H
+#define __GD32_CDC_ACM_CORE_H
+
+#include "usbd_enum.h"
+#include "usb_cdc.h"
+
+#define USB_CDC_RX_LEN      USB_CDC_DATA_PACKET_SIZE
+
+typedef struct {
+    uint8_t data[USB_CDC_RX_LEN];
+    uint8_t cmd[USB_CDC_CMD_PACKET_SIZE];
+
+    uint8_t packet_sent;
+    uint8_t packet_receive;
+
+    uint32_t receive_length;
+
+    acm_line line_coding;
+} gd32_usb_cdc_handler;
+
+extern usb_desc gd32_cdc_desc;
+extern usb_class_core gd32_cdc_class;
+
+/* function declarations */
+/* check CDC ACM is ready for data transfer */
+uint8_t gd32_cdc_acm_check_ready(usb_dev *udev);
+/* send CDC ACM data */
+void gd32_cdc_acm_data_send(usb_dev *udev, uint8_t* data, uint32_t length);
+/* receive CDC ACM data */
+void gd32_cdc_acm_data_receive(usb_dev *udev);
+
+#endif /* __GD32_CDC_ACM_CORE_H */

+ 2540 - 0
lib/ZuluSCSI_platform_GD32F450/gd32_sdio_sdcard.c

@@ -0,0 +1,2540 @@
+/*!
+    \file    sdcard.c
+    \brief   SD card driver
+
+    \version 2016-08-15, V1.0.0, firmware for GD32F4xx
+    \version 2018-12-12, V2.0.0, firmware for GD32F4xx
+    \version 2020-09-30, V2.1.0, firmware for GD32F4xx
+    \version 2022-03-09, V3.0.0, firmware for GD32F4xx
+*/
+
+/*
+    Copyright (c) 2022, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#include "gd32_sdio_sdcard.h"
+#include "gd32f4xx_sdio.h"
+#include "gd32f4xx_dma.h"
+#include <stddef.h>
+
+/* card status of R1 definitions */
+#define SD_R1_OUT_OF_RANGE                  BIT(31)                   /* command's argument was out of the allowed range */
+#define SD_R1_ADDRESS_ERROR                 BIT(30)                   /* misaligned address which did not match the block length */
+#define SD_R1_BLOCK_LEN_ERROR               BIT(29)                   /* transferred block length is not allowed */
+#define SD_R1_ERASE_SEQ_ERROR               BIT(28)                   /* an error in the sequence of erase commands occurred */
+#define SD_R1_ERASE_PARAM                   BIT(27)                   /* an invalid selection of write-blocks for erase occurred */
+#define SD_R1_WP_VIOLATION                  BIT(26)                   /* the host attempts to write to a protected block or to the temporary or permanent write protected card */
+#define SD_R1_CARD_IS_LOCKED                BIT(25)                   /* the card is locked by the host */
+#define SD_R1_LOCK_UNLOCK_FAILED            BIT(24)                   /* a sequence or password error has been detected in lock/unlock card command */
+#define SD_R1_COM_CRC_ERROR                 BIT(23)                   /* CRC check of the previous command failed */
+#define SD_R1_ILLEGAL_COMMAND               BIT(22)                   /* command not legal for the card state */
+#define SD_R1_CARD_ECC_FAILED               BIT(21)                   /* card internal ECC was applied but failed to correct the data */
+#define SD_R1_CC_ERROR                      BIT(20)                   /* internal card controller error */
+#define SD_R1_GENERAL_UNKNOWN_ERROR         BIT(19)                   /* a general or an unknown error occurred during the operation */
+#define SD_R1_CSD_OVERWRITE                 BIT(16)                   /* read only section of the CSD does not match or attempt to reverse the copy or permanent WP bits */
+#define SD_R1_WP_ERASE_SKIP                 BIT(15)                   /* partial address space was erased */
+#define SD_R1_CARD_ECC_DISABLED             BIT(14)                   /* command has been executed without using the internal ECC */
+#define SD_R1_ERASE_RESET                   BIT(13)                   /* an erase sequence was cleared before executing */
+#define SD_R1_READY_FOR_DATA                BIT(8)                    /* correspond to buffer empty signaling on the bus */
+#define SD_R1_APP_CMD                       BIT(5)                    /* card will expect ACMD */
+#define SD_R1_AKE_SEQ_ERROR                 BIT(3)                    /* error in the sequence of the authentication process */
+#define SD_R1_ERROR_BITS                    (uint32_t)0xFDF9E008      /* all the R1 error bits */
+
+/* card status of R6 definitions */
+#define SD_R6_COM_CRC_ERROR                 BIT(15)                   /* CRC check of the previous command failed */
+#define SD_R6_ILLEGAL_COMMAND               BIT(14)                   /* command not legal for the card state */
+#define SD_R6_GENERAL_UNKNOWN_ERROR         BIT(13)                   /* a general or an unknown error occurred during the operation */
+
+/* card state */
+#define SD_CARDSTATE_IDLE                   ((uint8_t)0x00)           /* card is in idle state */
+#define SD_CARDSTATE_READY                  ((uint8_t)0x01)           /* card is in ready state */
+#define SD_CARDSTATE_IDENTIFICAT            ((uint8_t)0x02)           /* card is in identificat state */
+#define SD_CARDSTATE_STANDBY                ((uint8_t)0x03)           /* card is in standby state */
+#define SD_CARDSTATE_TRANSFER               ((uint8_t)0x04)           /* card is in transfer state */
+#define SD_CARDSTATE_DATA                   ((uint8_t)0x05)           /* card is in data sending state */
+#define SD_CARDSTATE_RECEIVING              ((uint8_t)0x06)           /* card is in receiving state */
+#define SD_CARDSTATE_PROGRAMMING            ((uint8_t)0x07)           /* card is in programming state */
+#define SD_CARDSTATE_DISCONNECT             ((uint8_t)0x08)           /* card is in disconnect state */
+#define SD_CARDSTATE_LOCKED                 ((uint32_t)0x02000000)    /* card is in locked state */
+
+#define SD_CHECK_PATTERN                    ((uint32_t)0x000001AA)    /* check pattern for CMD8 */
+#define SD_VOLTAGE_WINDOW                   ((uint32_t)0x80100000)    /* host 3.3V request in ACMD41 */
+
+/* parameters for ACMD41(voltage validation) */
+#define SD_HIGH_CAPACITY                    ((uint32_t)0x40000000)    /* high capacity SD memory card */
+#define SD_STD_CAPACITY                     ((uint32_t)0x00000000)    /* standard capacity SD memory card */
+
+/* SD bus width, check SCR register */
+#define SD_BUS_WIDTH_4BIT                   ((uint32_t)0x00040000)    /* 4-bit width bus mode */
+#define SD_BUS_WIDTH_1BIT                   ((uint32_t)0x00010000)    /* 1-bit width bus mode */
+
+/* masks for SCR register */
+#define SD_MASK_0_7BITS                     ((uint32_t)0x000000FF)    /* mask [7:0] bits */
+#define SD_MASK_8_15BITS                    ((uint32_t)0x0000FF00)    /* mask [15:8] bits */
+#define SD_MASK_16_23BITS                   ((uint32_t)0x00FF0000)    /* mask [23:16] bits */
+#define SD_MASK_24_31BITS                   ((uint32_t)0xFF000000)    /* mask [31:24] bits */
+
+#define SDIO_FIFO_ADDR                      ((uint32_t)0x40012C80)    /* address of SDIO_FIFO */
+#define SD_FIFOHALF_WORDS                   ((uint32_t)0x00000008)    /* words of FIFO half full/empty */
+#define SD_FIFOHALF_BYTES                   ((uint32_t)0x00000020)    /* bytes of FIFO half full/empty */
+
+#define SD_DATATIMEOUT                      ((uint32_t)0xFFFFFFFF)    /* DSM data timeout */
+#define SD_MAX_VOLT_VALIDATION              ((uint32_t)0x0000FFFF)    /* the maximum times of voltage validation */
+#define SD_MAX_DATA_LENGTH                  ((uint32_t)0x01FFFFFF)    /* the maximum length of data */
+#define SD_ALLZERO                          ((uint32_t)0x00000000)    /* all zero */
+#define SD_RCA_SHIFT                        ((uint8_t)0x10)           /* RCA shift bits */
+#define SD_CLK_DIV_INIT                     ((uint16_t)0x0076)        /* SD clock division in initialization phase */
+#define SD_CLK_DIV_TRANS                    ((uint16_t)0x0002)        /* SD clock division in transmission phase */
+
+#define SDIO_MASK_INTC_FLAGS                ((uint32_t)0x00C007FF)    /* mask flags of SDIO_INTC */
+
+uint32_t sd_scr[2] = {0, 0};                                          /* content of SCR register */
+
+static sdio_card_type_enum cardtype = SDIO_STD_CAPACITY_SD_CARD_V1_1; /* SD card type */
+static uint32_t sd_csd[4] = {0, 0, 0, 0};                             /* content of CSD register */
+static uint32_t sd_cid[4] = {0, 0, 0, 0};                             /* content of CID register */
+static uint16_t sd_rca = 0;                                           /* RCA of SD card */
+static uint32_t transmode = SD_POLLING_MODE;
+static uint32_t totalnumber_bytes = 0, stopcondition = 0;
+static __IO sd_error_enum transerror = SD_OK;
+static __IO uint32_t transend = 0, number_bytes = 0;
+
+/* check if the command sent error occurs */
+static sd_error_enum cmdsent_error_check(void);
+/* check if error occurs for R1 response */
+static sd_error_enum r1_error_check(uint8_t cmdindex);
+/* check if error type for R1 response */
+static sd_error_enum r1_error_type_check(uint32_t resp);
+/* check if error occurs for R2 response */
+static sd_error_enum r2_error_check(void);
+/* check if error occurs for R3 response */
+static sd_error_enum r3_error_check(void);
+/* check if error occurs for R6 response */
+static sd_error_enum r6_error_check(uint8_t cmdindex, uint16_t *prca);
+/* check if error occurs for R7 response */
+static sd_error_enum r7_error_check(void);
+
+/* get the state which the card is in */
+static sd_error_enum sd_card_state_get(uint8_t *pcardstate);
+/* configure the bus width mode */
+static sd_error_enum sd_bus_width_config(uint32_t buswidth);
+/* get the SCR of corresponding card */
+static sd_error_enum sd_scr_get(uint16_t rca, uint32_t *pscr);
+/* get the data block size */
+static uint32_t sd_datablocksize_get(uint16_t bytesnumber);
+
+/* configure the GPIO of SDIO interface */
+static void gpio_config(void);
+/* configure the RCU of SDIO and DMA */
+static void rcu_config(void);
+/* configure the DMA for SDIO transfer request */
+static void dma_transfer_config(uint32_t *srcbuf, uint32_t bufsize);
+/* configure the DMA for SDIO reveive request */
+static void dma_receive_config(uint32_t *dstbuf, uint32_t bufsize);
+
+unsigned long millis(void);
+
+/*!
+    \brief      initialize the SD card and make it in standby state
+    \param[in]  none
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_init(void)
+{
+    sd_error_enum status = SD_OK;
+    /* configure the RCU and GPIO, deinitialize the SDIO */
+    rcu_config();
+    gpio_config();
+    sdio_deinit();
+
+    /* configure the clock and work voltage */
+    status = sd_power_on();
+    if(SD_OK != status) {
+        return status;
+    }
+
+    /* initialize the card and get CID and CSD of the card */
+    status = sd_card_init();
+    if(SD_OK != status) {
+        return status;
+    }
+
+    /* configure the SDIO peripheral */
+    sdio_clock_config(SDIO_SDIOCLKEDGE_RISING, SDIO_CLOCKBYPASS_DISABLE, SDIO_CLOCKPWRSAVE_DISABLE, SD_CLK_DIV_TRANS);
+    sdio_bus_mode_set(SDIO_BUSMODE_1BIT);
+    sdio_hardware_clock_disable();
+
+    return status;
+}
+
+/*!
+    \brief      initialize the card and get CID and CSD of the card
+    \param[in]  none
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_card_init(void)
+{
+    sd_error_enum status = SD_OK;
+    uint16_t temp_rca = 0x01;
+
+    if(SDIO_POWER_OFF == sdio_power_state_get()) {
+        status = SD_OPERATION_IMPROPER;
+        return status;
+    }
+
+    /* the card is not I/O only card */
+    if(SDIO_SECURE_DIGITAL_IO_CARD != cardtype) {
+        /* send CMD2(SD_CMD_ALL_SEND_CID) to get the CID numbers */
+        sdio_command_response_config(SD_CMD_ALL_SEND_CID, (uint32_t)0x0, SDIO_RESPONSETYPE_LONG);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+        /* check if some error occurs */
+        status = r2_error_check();
+        if(SD_OK != status) {
+            return status;
+        }
+
+        /* store the CID numbers */
+        sd_cid[0] = sdio_response_get(SDIO_RESPONSE0);
+        sd_cid[1] = sdio_response_get(SDIO_RESPONSE1);
+        sd_cid[2] = sdio_response_get(SDIO_RESPONSE2);
+        sd_cid[3] = sdio_response_get(SDIO_RESPONSE3);
+    }
+
+    /* the card is SD memory card or the I/O card has the memory portion */
+    if((SDIO_STD_CAPACITY_SD_CARD_V1_1 == cardtype) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == cardtype) ||
+            (SDIO_HIGH_CAPACITY_SD_CARD == cardtype) || (SDIO_SECURE_DIGITAL_IO_COMBO_CARD == cardtype)) {
+        /* send CMD3(SEND_RELATIVE_ADDR) to ask the card to publish a new relative address (RCA) */
+        sdio_command_response_config(SD_CMD_SEND_RELATIVE_ADDR, (uint32_t)0x0, SDIO_RESPONSETYPE_SHORT);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+        /* check if some error occurs */
+        status = r6_error_check(SD_CMD_SEND_RELATIVE_ADDR, &temp_rca);
+        if(SD_OK != status) {
+            return status;
+        }
+    }
+
+    if(SDIO_SECURE_DIGITAL_IO_CARD != cardtype) {
+        /* the card is not I/O only card */
+        sd_rca = temp_rca;
+
+        /* send CMD9(SEND_CSD) to get the addressed card's card-specific data (CSD) */
+        sdio_command_response_config(SD_CMD_SEND_CSD, (uint32_t)(temp_rca << SD_RCA_SHIFT), SDIO_RESPONSETYPE_LONG);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+        /* check if some error occurs */
+        status = r2_error_check();
+        if(SD_OK != status) {
+            return status;
+        }
+
+        /* store the card-specific data (CSD) */
+        sd_csd[0] = sdio_response_get(SDIO_RESPONSE0);
+        sd_csd[1] = sdio_response_get(SDIO_RESPONSE1);
+        sd_csd[2] = sdio_response_get(SDIO_RESPONSE2);
+        sd_csd[3] = sdio_response_get(SDIO_RESPONSE3);
+    }
+    return status;
+}
+
+/*!
+    \brief      configure the clock and the work voltage, and get the card type
+    \param[in]  none
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_power_on(void)
+{
+    sd_error_enum status = SD_OK;
+    uint32_t sdcardtype = SD_STD_CAPACITY, response = 0, count = 0;
+    uint8_t busyflag = 0;
+
+    /* configure the SDIO peripheral */
+    sdio_clock_config(SDIO_SDIOCLKEDGE_RISING, SDIO_CLOCKBYPASS_DISABLE, SDIO_CLOCKPWRSAVE_DISABLE, SD_CLK_DIV_INIT);
+    sdio_bus_mode_set(SDIO_BUSMODE_1BIT);
+    sdio_hardware_clock_disable();
+    sdio_power_state_set(SDIO_POWER_ON);
+    /* enable SDIO_CLK clock output */
+    sdio_clock_enable();
+
+    /* send CMD0(GO_IDLE_STATE) to reset the card */
+    sdio_command_response_config(SD_CMD_GO_IDLE_STATE, (uint32_t)0x0, SDIO_RESPONSETYPE_NO);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    /* enable the CSM */
+    sdio_csm_enable();
+
+    /* check if command sent error occurs */
+    status = cmdsent_error_check();
+    if(SD_OK != status) {
+        return status;
+    }
+
+    /* send CMD8(SEND_IF_COND) to get SD memory card interface condition */
+    sdio_command_response_config(SD_CMD_SEND_IF_COND, SD_CHECK_PATTERN, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+
+    if(SD_OK == r7_error_check()) {
+        /* SD Card 2.0 */
+        cardtype = SDIO_STD_CAPACITY_SD_CARD_V2_0;
+        sdcardtype = SD_HIGH_CAPACITY;
+    }
+
+    /* send CMD55(APP_CMD) to indicate next command is application specific command */
+    sdio_command_response_config(SD_CMD_APP_CMD, (uint32_t)0x0, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+
+    if(SD_OK == r1_error_check(SD_CMD_APP_CMD)) {
+        /* SD memory card */
+        while((!busyflag) && (count < SD_MAX_VOLT_VALIDATION)) {
+            /* send CMD55(APP_CMD) to indicate next command is application specific command */
+            sdio_command_response_config(SD_CMD_APP_CMD, (uint32_t)0x0, SDIO_RESPONSETYPE_SHORT);
+            sdio_wait_type_set(SDIO_WAITTYPE_NO);
+            sdio_csm_enable();
+            /* check if some error occurs */
+            status = r1_error_check(SD_CMD_APP_CMD);
+            if(SD_OK != status) {
+                return status;
+            }
+
+            /* send ACMD41(SD_SEND_OP_COND) to get host capacity support information (HCS) and OCR content */
+            sdio_command_response_config(SD_APPCMD_SD_SEND_OP_COND, (SD_VOLTAGE_WINDOW | sdcardtype), SDIO_RESPONSETYPE_SHORT);
+            sdio_wait_type_set(SDIO_WAITTYPE_NO);
+            sdio_csm_enable();
+            /* check if some error occurs */
+            status = r3_error_check();
+            if(SD_OK != status) {
+                return status;
+            }
+            /* get the response and check card power up status bit(busy) */
+            response = sdio_response_get(SDIO_RESPONSE0);
+            busyflag = (uint8_t)((response >> 31) & (uint32_t)0x01);
+            ++count;
+        }
+        if(count >= SD_MAX_VOLT_VALIDATION) {
+            status = SD_VOLTRANGE_INVALID;
+            return status;
+        }
+        if(response &= SD_HIGH_CAPACITY) {
+            /* SDHC card */
+            cardtype = SDIO_HIGH_CAPACITY_SD_CARD;
+        }
+    }
+    return status;
+}
+
+/*!
+    \brief      close the power of SDIO
+    \param[in]  none
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_power_off(void)
+{
+    sd_error_enum status = SD_OK;
+    sdio_power_state_set(SDIO_POWER_OFF);
+    return status;
+}
+
+/*!
+    \brief      configure the bus mode
+    \param[in]  busmode: the bus mode
+      \arg        SDIO_BUSMODE_1BIT: 1-bit SDIO card bus mode
+      \arg        SDIO_BUSMODE_4BIT: 4-bit SDIO card bus mode
+      \arg        SDIO_BUSMODE_8BIT: 8-bit SDIO card bus mode (MMC only)
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_bus_mode_config(uint32_t busmode)
+{
+    sd_error_enum status = SD_OK;
+    if(SDIO_MULTIMEDIA_CARD == cardtype) {
+        /* MMC card doesn't support this function */
+        status = SD_FUNCTION_UNSUPPORTED;
+        return status;
+    } else if((SDIO_STD_CAPACITY_SD_CARD_V1_1 == cardtype) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == cardtype) ||
+              (SDIO_HIGH_CAPACITY_SD_CARD == cardtype)) {
+        if(SDIO_BUSMODE_8BIT == busmode) {
+            /* 8 bit bus mode doesn't support */
+            status = SD_FUNCTION_UNSUPPORTED;
+            return status;
+        } else if(SDIO_BUSMODE_4BIT == busmode) {
+            /* configure SD bus width and the SDIO */
+            status = sd_bus_width_config(SD_BUS_WIDTH_4BIT);
+            if(SD_OK == status) {
+                sdio_clock_config(SDIO_SDIOCLKEDGE_RISING, SDIO_CLOCKBYPASS_DISABLE,
+                                  SDIO_CLOCKPWRSAVE_DISABLE, SD_CLK_DIV_TRANS);
+                sdio_bus_mode_set(busmode);
+                sdio_hardware_clock_disable();
+            }
+        } else if(SDIO_BUSMODE_1BIT == busmode) {
+            /* configure SD bus width and the SDIO */
+            status = sd_bus_width_config(SD_BUS_WIDTH_1BIT);
+            if(SD_OK == status) {
+                sdio_clock_config(SDIO_SDIOCLKEDGE_RISING, SDIO_CLOCKBYPASS_DISABLE,
+                                  SDIO_CLOCKPWRSAVE_DISABLE, SD_CLK_DIV_TRANS);
+                sdio_bus_mode_set(busmode);
+                sdio_hardware_clock_disable();
+            }
+        } else {
+            status = SD_PARAMETER_INVALID;
+        }
+    }
+    return status;
+}
+
+/*!
+    \brief      configure the mode of transmission
+    \param[in]  txmode: transfer mode
+      \arg        SD_DMA_MODE: DMA mode
+      \arg        SD_POLLING_MODE: polling mode
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_transfer_mode_config(uint32_t txmode)
+{
+    sd_error_enum status = SD_OK;
+    /* set the transfer mode */
+    if((SD_DMA_MODE == txmode) || (SD_POLLING_MODE == txmode)) {
+        transmode = txmode;
+    } else {
+        status = SD_PARAMETER_INVALID;
+    }
+    return status;
+}
+
+/*!
+    \brief      read a block data into a buffer from the specified address of a card
+    \param[out] preadbuffer: a pointer that store a block read data
+    \param[in]  readaddr: the read data address
+    \param[in]  blocksize: the data block size
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_block_read(uint32_t *preadbuffer, uint64_t readaddr, uint16_t blocksize, sdio_callback_t callback)
+{
+    /* initialize the variables */
+    sd_error_enum status = SD_OK;
+    uint32_t count = 0, align = 0, datablksize = SDIO_DATABLOCKSIZE_1BYTE, *ptempbuff = preadbuffer;
+
+    if(NULL == preadbuffer) {
+        status = SD_PARAMETER_INVALID;
+        return status;
+    }
+
+    transerror = SD_OK;
+    transend = 0;
+    totalnumber_bytes = 0;
+    /* clear all DSM configuration */
+    sdio_data_config(0, 0, SDIO_DATABLOCKSIZE_1BYTE);
+    sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOCARD);
+    sdio_dsm_disable();
+    sdio_dma_disable();
+
+    /* check whether the card is locked */
+    if(sdio_response_get(SDIO_RESPONSE0) & SD_CARDSTATE_LOCKED) {
+        status = SD_LOCK_UNLOCK_FAILED;
+        return status;
+    }
+
+    /* blocksize is fixed in 512B for SDHC card */
+    if(SDIO_HIGH_CAPACITY_SD_CARD == cardtype) {
+        blocksize = 512;
+        readaddr /= 512;
+    }
+
+    align = blocksize & (blocksize - 1);
+    if((blocksize > 0) && (blocksize <= 2048) && (0 == align)) {
+        datablksize = sd_datablocksize_get(blocksize);
+        /* send CMD16(SET_BLOCKLEN) to set the block length */
+        sdio_command_response_config(SD_CMD_SET_BLOCKLEN, (uint32_t)blocksize, SDIO_RESPONSETYPE_SHORT);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+
+        /* check if some error occurs */
+        status = r1_error_check(SD_CMD_SET_BLOCKLEN);
+        if(SD_OK != status) {
+            return status;
+        }
+    } else {
+        status = SD_PARAMETER_INVALID;
+        return status;
+    }
+
+    stopcondition = 0;
+    totalnumber_bytes = blocksize;
+
+    /* configure SDIO data transmission */
+    sdio_data_config(SD_DATATIMEOUT, totalnumber_bytes, datablksize);
+    sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOSDIO);
+    sdio_dsm_enable();
+
+    /* send CMD17(READ_SINGLE_BLOCK) to read a block */
+    sdio_command_response_config(SD_CMD_READ_SINGLE_BLOCK, (uint32_t)readaddr, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_READ_SINGLE_BLOCK);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    if(SD_POLLING_MODE == transmode) {
+        /* polling mode */
+        while(!sdio_flag_get(SDIO_FLAG_DTCRCERR | SDIO_FLAG_DTTMOUT | SDIO_FLAG_RXORE | SDIO_FLAG_DTBLKEND | SDIO_FLAG_STBITE)) {
+            if(RESET != sdio_flag_get(SDIO_FLAG_RFH)) {
+                /* at least 8 words can be read in the FIFO */
+                for(count = 0; count < SD_FIFOHALF_WORDS; count++) {
+                    *(ptempbuff + count) = sdio_data_read();
+                }
+                ptempbuff += SD_FIFOHALF_WORDS;
+            }
+        }
+
+        /* whether some error occurs and return it */
+        if(RESET != sdio_flag_get(SDIO_FLAG_DTCRCERR)) {
+            status = SD_DATA_CRC_ERROR;
+            sdio_flag_clear(SDIO_FLAG_DTCRCERR);
+            return status;
+        } else if(RESET != sdio_flag_get(SDIO_FLAG_DTTMOUT)) {
+            status = SD_DATA_TIMEOUT;
+            sdio_flag_clear(SDIO_FLAG_DTTMOUT);
+            return status;
+        } else if(RESET != sdio_flag_get(SDIO_FLAG_RXORE)) {
+            status = SD_RX_OVERRUN_ERROR;
+            sdio_flag_clear(SDIO_FLAG_RXORE);
+            return status;
+        } else if(RESET != sdio_flag_get(SDIO_FLAG_STBITE)) {
+            status = SD_START_BIT_ERROR;
+            sdio_flag_clear(SDIO_FLAG_STBITE);
+            return status;
+        }
+        while(RESET != sdio_flag_get(SDIO_FLAG_RXDTVAL)) {
+            *ptempbuff = sdio_data_read();
+            ++ptempbuff;
+        }
+        /* clear the SDIO_INTC flags */
+        sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+    } else if(SD_DMA_MODE == transmode) {
+        /* DMA mode */
+        /* enable the SDIO corresponding interrupts and DMA function */
+        sdio_interrupt_enable(SDIO_INT_CCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_RXORE | SDIO_INT_DTEND | SDIO_INT_STBITE);
+        sdio_dma_enable();
+        dma_receive_config(preadbuffer, blocksize);
+        uint32_t start = millis();
+        while((RESET == dma_flag_get(DMA1, DMA_CH3, DMA_FLAG_FTF))) {
+            if((uint32_t)(millis() - start) > 1000) {
+                return SD_ERROR;
+            }
+            if (callback)
+            {
+                // Note: GD32F4 DMA counts down from 0xFFFF when DMA flow control mode is "peripheral"
+                uint32_t complete = (0xFFFF - DMA_CHCNT(DMA1, DMA_CH3) * 4);
+                if (complete <= blocksize)
+                {
+                    callback(complete);
+                }
+            }
+        }
+    } else {
+        status = SD_PARAMETER_INVALID;
+    }
+    return status;
+}
+
+/*!
+    \brief      read multiple blocks data into a buffer from the specified address of a card
+    \param[out] preadbuffer: a pointer that store multiple blocks read data
+    \param[in]  readaddr: the read data address
+    \param[in]  blocksize: the data block size
+    \param[in]  blocksnumber: number of blocks that will be read
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_multiblocks_read(uint32_t *preadbuffer, uint64_t readaddr, uint16_t blocksize, uint32_t blocksnumber, sdio_callback_t callback)
+{
+    /* initialize the variables */
+    sd_error_enum status = SD_OK;
+    uint32_t count = 0, align = 0, datablksize = SDIO_DATABLOCKSIZE_1BYTE, *ptempbuff = preadbuffer;
+
+    if(NULL == preadbuffer) {
+        status = SD_PARAMETER_INVALID;
+        return status;
+    }
+
+    transerror = SD_OK;
+    transend = 0;
+    totalnumber_bytes = 0;
+    /* clear all DSM configuration */
+    sdio_data_config(0, 0, SDIO_DATABLOCKSIZE_1BYTE);
+    sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOCARD);
+    sdio_dsm_disable();
+    sdio_dma_disable();
+
+    /* check whether the card is locked */
+    if(sdio_response_get(SDIO_RESPONSE0) & SD_CARDSTATE_LOCKED) {
+        status = SD_LOCK_UNLOCK_FAILED;
+        return status;
+    }
+
+    /* blocksize is fixed in 512B for SDHC card */
+    if(SDIO_HIGH_CAPACITY_SD_CARD == cardtype) {
+        blocksize = 512;
+        readaddr /= 512;
+    }
+
+    align = blocksize & (blocksize - 1);
+    if((blocksize > 0) && (blocksize <= 2048) && (0 == align)) {
+        datablksize = sd_datablocksize_get(blocksize);
+        /* send CMD16(SET_BLOCKLEN) to set the block length */
+        sdio_command_response_config(SD_CMD_SET_BLOCKLEN, (uint32_t)blocksize, SDIO_RESPONSETYPE_SHORT);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+
+        /* check if some error occurs */
+        status = r1_error_check(SD_CMD_SET_BLOCKLEN);
+        if(SD_OK != status) {
+            return status;
+        }
+    } else {
+        status = SD_PARAMETER_INVALID;
+        return status;
+    }
+
+    if(blocksnumber >= 1) {
+        if(blocksnumber * blocksize > SD_MAX_DATA_LENGTH) {
+            /* exceeds the maximum length */
+            status = SD_PARAMETER_INVALID;
+            return status;
+        }
+
+        stopcondition = 1;
+        totalnumber_bytes = blocksnumber * blocksize;
+
+        /* configure the SDIO data transmission */
+        sdio_data_config(SD_DATATIMEOUT, totalnumber_bytes, datablksize);
+        sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOSDIO);
+        sdio_dsm_enable();
+
+        /* send CMD18(READ_MULTIPLE_BLOCK) to read multiple blocks */
+        sdio_command_response_config(SD_CMD_READ_MULTIPLE_BLOCK, readaddr, SDIO_RESPONSETYPE_SHORT);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+        /* check if some error occurs */
+        status = r1_error_check(SD_CMD_READ_MULTIPLE_BLOCK);
+        if(SD_OK != status) {
+            return status;
+        }
+
+        if(SD_POLLING_MODE == transmode) {
+            /* polling mode */
+            while(!sdio_flag_get(SDIO_FLAG_DTCRCERR | SDIO_FLAG_DTTMOUT | SDIO_FLAG_RXORE | SDIO_FLAG_DTEND | SDIO_FLAG_STBITE)) {
+                if(RESET != sdio_flag_get(SDIO_FLAG_RFH)) {
+                    /* at least 8 words can be read in the FIFO */
+                    for(count = 0; count < SD_FIFOHALF_WORDS; count++) {
+                        *(ptempbuff + count) = sdio_data_read();
+                    }
+                    ptempbuff += SD_FIFOHALF_WORDS;
+                }
+            }
+
+            /* whether some error occurs and return it */
+            if(RESET != sdio_flag_get(SDIO_FLAG_DTCRCERR)) {
+                status = SD_DATA_CRC_ERROR;
+                sdio_flag_clear(SDIO_FLAG_DTCRCERR);
+                return status;
+            } else if(RESET != sdio_flag_get(SDIO_FLAG_DTTMOUT)) {
+                status = SD_DATA_TIMEOUT;
+                sdio_flag_clear(SDIO_FLAG_DTTMOUT);
+                return status;
+            } else if(RESET != sdio_flag_get(SDIO_FLAG_RXORE)) {
+                status = SD_RX_OVERRUN_ERROR;
+                sdio_flag_clear(SDIO_FLAG_RXORE);
+                return status;
+            } else if(RESET != sdio_flag_get(SDIO_FLAG_STBITE)) {
+                status = SD_START_BIT_ERROR;
+                sdio_flag_clear(SDIO_FLAG_STBITE);
+                return status;
+            }
+            while(RESET != sdio_flag_get(SDIO_FLAG_RXDTVAL)) {
+                *ptempbuff = sdio_data_read();
+                ++ptempbuff;
+            }
+
+            if(RESET != sdio_flag_get(SDIO_FLAG_DTEND)) {
+                if((SDIO_STD_CAPACITY_SD_CARD_V1_1 == cardtype) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == cardtype) ||
+                        (SDIO_HIGH_CAPACITY_SD_CARD == cardtype)) {
+                    /* send CMD12(STOP_TRANSMISSION) to stop transmission */
+                    sdio_command_response_config(SD_CMD_STOP_TRANSMISSION, (uint32_t)0x0, SDIO_RESPONSETYPE_SHORT);
+                    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+                    sdio_csm_enable();
+                    /* check if some error occurs */
+                    status = r1_error_check(SD_CMD_STOP_TRANSMISSION);
+                    if(SD_OK != status) {
+                        return status;
+                    }
+                }
+            }
+            sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+        } else if(SD_DMA_MODE == transmode) {
+            /* DMA mode */
+            /* enable the SDIO corresponding interrupts and DMA function */
+            sdio_interrupt_enable(SDIO_INT_DTCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_RXORE | SDIO_INT_DTEND | SDIO_INT_STBITE);
+            sdio_dma_enable();
+            dma_receive_config(preadbuffer, totalnumber_bytes);
+            uint32_t start = millis();
+            while((RESET == dma_flag_get(DMA1, DMA_CH3, DMA_FLAG_FTF))) {
+                if((uint32_t)(millis() - start) > 1000) {
+                    return SD_ERROR;
+                }
+                if (callback)
+                {
+                    // Note: GD32F4 DMA counts down from 0xFFFF when DMA flow control mode is "peripheral"
+                    uint32_t complete = (0xFFFF - DMA_CHCNT(DMA1, DMA_CH3) * 4);
+                    if (complete <= blocksize)
+                    {
+                        callback(complete);
+                    }
+                }
+            }
+            while((0 == transend) && (SD_OK == transerror)) {
+                if (callback)
+                {
+                    callback(totalnumber_bytes);
+                }
+            }
+            if(SD_OK != transerror) {
+                return transerror;
+            }
+        } else {
+            status = SD_PARAMETER_INVALID;
+        }
+    }
+    return status;
+}
+
+/*!
+    \brief      write a block data to the specified address of a card
+    \param[in]  pwritebuffer: a pointer that store a block data to be transferred
+    \param[in]  writeaddr: the read data address
+    \param[in]  blocksize: the data block size
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_block_write(uint32_t *pwritebuffer, uint64_t writeaddr, uint16_t blocksize, sdio_callback_t callback)
+{
+    /* initialize the variables */
+    sd_error_enum status = SD_OK;
+    uint8_t cardstate = 0;
+    uint32_t count = 0, align = 0, datablksize = SDIO_DATABLOCKSIZE_1BYTE, *ptempbuff = pwritebuffer;
+    uint32_t transbytes = 0, restwords = 0, response = 0;
+
+    if(NULL == pwritebuffer) {
+        status = SD_PARAMETER_INVALID;
+        return status;
+    }
+
+    transerror = SD_OK;
+    transend = 0;
+    totalnumber_bytes = 0;
+    /* clear all DSM configuration */
+    sdio_data_config(0, 0, SDIO_DATABLOCKSIZE_1BYTE);
+    sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOCARD);
+    sdio_dsm_disable();
+    sdio_dma_disable();
+
+    /* check whether the card is locked */
+    if(sdio_response_get(SDIO_RESPONSE0) & SD_CARDSTATE_LOCKED) {
+        status = SD_LOCK_UNLOCK_FAILED;
+        return status;
+    }
+
+    /* blocksize is fixed in 512B for SDHC card */
+    if(SDIO_HIGH_CAPACITY_SD_CARD == cardtype) {
+        blocksize = 512;
+        writeaddr /= 512;
+    }
+
+    align = blocksize & (blocksize - 1);
+    if((blocksize > 0) && (blocksize <= 2048) && (0 == align)) {
+        datablksize = sd_datablocksize_get(blocksize);
+        /* send CMD16(SET_BLOCKLEN) to set the block length */
+        sdio_command_response_config(SD_CMD_SET_BLOCKLEN, (uint32_t)blocksize, SDIO_RESPONSETYPE_SHORT);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+
+        /* check if some error occurs */
+        status = r1_error_check(SD_CMD_SET_BLOCKLEN);
+        if(SD_OK != status) {
+            return status;
+        }
+    } else {
+        status = SD_PARAMETER_INVALID;
+        return status;
+    }
+
+    /* send CMD13(SEND_STATUS), addressed card sends its status registers */
+    sdio_command_response_config(SD_CMD_SEND_STATUS, (uint32_t)sd_rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_SEND_STATUS);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    response = sdio_response_get(SDIO_RESPONSE0);
+
+    uint32_t start = millis();
+    while((0 == (response & SD_R1_READY_FOR_DATA))) {
+        /* continue to send CMD13 to polling the state of card until buffer empty or timeout */
+        if((uint32_t)(millis() - start) > 1000) {
+            return SD_ERROR;
+        }
+
+        if (callback)
+        {
+            callback(0);
+        }
+        /* send CMD13(SEND_STATUS), addressed card sends its status registers */
+        sdio_command_response_config(SD_CMD_SEND_STATUS, (uint32_t)sd_rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+        /* check if some error occurs */
+        status = r1_error_check(SD_CMD_SEND_STATUS);
+        if(SD_OK != status) {
+            return status;
+        }
+        response = sdio_response_get(SDIO_RESPONSE0);
+    }
+
+    /* send CMD24(WRITE_BLOCK) to write a block */
+    sdio_command_response_config(SD_CMD_WRITE_BLOCK, writeaddr, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_WRITE_BLOCK);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    stopcondition = 0;
+    totalnumber_bytes = blocksize;
+
+    /* configure the SDIO data transmission */
+    sdio_data_config(SD_DATATIMEOUT, totalnumber_bytes, datablksize);
+    sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOCARD);
+    sdio_dsm_enable();
+
+    if(SD_POLLING_MODE == transmode) {
+        /* polling mode */
+        while(!sdio_flag_get(SDIO_FLAG_DTCRCERR | SDIO_FLAG_DTTMOUT | SDIO_FLAG_TXURE | SDIO_FLAG_DTBLKEND | SDIO_FLAG_STBITE)) {
+            if(RESET != sdio_flag_get(SDIO_FLAG_TFH)) {
+                /* at least 8 words can be written into the FIFO */
+                if((totalnumber_bytes - transbytes) < SD_FIFOHALF_BYTES) {
+                    restwords = (totalnumber_bytes - transbytes) / 4 + (((totalnumber_bytes - transbytes) % 4 == 0) ? 0 : 1);
+                    for(count = 0; count < restwords; count++) {
+                        sdio_data_write(*ptempbuff);
+                        ++ptempbuff;
+                        transbytes += 4;
+                    }
+                } else {
+                    for(count = 0; count < SD_FIFOHALF_WORDS; count++) {
+                        sdio_data_write(*(ptempbuff + count));
+                    }
+                    /* 8 words(32 bytes) has been transferred */
+                    ptempbuff += SD_FIFOHALF_WORDS;
+                    transbytes += SD_FIFOHALF_BYTES;
+                }
+            }
+        }
+
+        /* whether some error occurs and return it */
+        if(RESET != sdio_flag_get(SDIO_FLAG_DTCRCERR)) {
+            status = SD_DATA_CRC_ERROR;
+            sdio_flag_clear(SDIO_FLAG_DTCRCERR);
+            return status;
+        } else if(RESET != sdio_flag_get(SDIO_FLAG_DTTMOUT)) {
+            status = SD_DATA_TIMEOUT;
+            sdio_flag_clear(SDIO_FLAG_DTTMOUT);
+            return status;
+        } else if(RESET != sdio_flag_get(SDIO_FLAG_TXURE)) {
+            status = SD_TX_UNDERRUN_ERROR;
+            sdio_flag_clear(SDIO_FLAG_TXURE);
+            return status;
+        } else if(RESET != sdio_flag_get(SDIO_FLAG_STBITE)) {
+            status = SD_START_BIT_ERROR;
+            sdio_flag_clear(SDIO_FLAG_STBITE);
+            return status;
+        }
+    } else if(SD_DMA_MODE == transmode) {
+        /* DMA mode */
+        /* enable the SDIO corresponding interrupts and DMA */
+        sdio_interrupt_enable(SDIO_INT_DTCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_TXURE | SDIO_INT_DTEND | SDIO_INT_STBITE);
+        dma_transfer_config(pwritebuffer, blocksize);
+        sdio_dma_enable();
+
+        uint32_t start = millis();
+        while((RESET == dma_flag_get(DMA1, DMA_CH3, DMA_FLAG_FTF))) {
+            if((uint32_t)(millis() - start) > 1000) {
+                return SD_ERROR;
+            }
+            if (callback)
+            {
+                // Note: GD32F4 DMA counts down from 0xFFFF when DMA flow control mode is "peripheral"
+                uint32_t complete = (0xFFFF - DMA_CHCNT(DMA1, DMA_CH3) * 4);
+                if (complete <= blocksize)
+                {
+                    callback(complete);
+                }
+            }
+        }
+        while((0 == transend) && (SD_OK == transerror)) {
+            if (callback)
+            {
+                callback(blocksize);
+            }
+        }
+
+        if(SD_OK != transerror) {
+            return transerror;
+        }
+    } else {
+        status = SD_PARAMETER_INVALID;
+        return status;
+    }
+
+    /* clear the SDIO_INTC flags */
+    sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+    /* get the card state and wait the card is out of programming and receiving state */
+    status = sd_card_state_get(&cardstate);
+    while((SD_OK == status) && ((SD_CARDSTATE_PROGRAMMING == cardstate) || (SD_CARDSTATE_RECEIVING == cardstate))) {
+        if (callback)
+        {
+            callback(blocksize);
+        }
+        status = sd_card_state_get(&cardstate);
+    }
+    return status;
+}
+
+/*!
+    \brief      write multiple blocks data to the specified address of a card
+    \param[in]  pwritebuffer: a pointer that store multiple blocks data to be transferred
+    \param[in]  writeaddr: the read data address
+    \param[in]  blocksize: the data block size
+    \param[in]  blocksnumber: number of blocks that will be written
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_multiblocks_write(uint32_t *pwritebuffer, uint64_t writeaddr, uint16_t blocksize, uint32_t blocksnumber, sdio_callback_t callback)
+{
+    /* initialize the variables */
+    sd_error_enum status = SD_OK;
+    uint8_t cardstate = 0;
+    uint32_t count = 0, align = 0, datablksize = SDIO_DATABLOCKSIZE_1BYTE, *ptempbuff = pwritebuffer;
+    uint32_t transbytes = 0, restwords = 0;
+
+    if(NULL == pwritebuffer) {
+        status = SD_PARAMETER_INVALID;
+        return status;
+    }
+
+    transerror = SD_OK;
+    transend = 0;
+    totalnumber_bytes = 0;
+    /* clear all DSM configuration */
+    sdio_data_config(0, 0, SDIO_DATABLOCKSIZE_1BYTE);
+    sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOCARD);
+    sdio_dsm_disable();
+    sdio_dma_disable();
+
+    /* check whether the card is locked */
+    if(sdio_response_get(SDIO_RESPONSE0) & SD_CARDSTATE_LOCKED) {
+        status = SD_LOCK_UNLOCK_FAILED;
+        return status;
+    }
+
+    /* blocksize is fixed in 512B for SDHC card */
+    if(SDIO_HIGH_CAPACITY_SD_CARD == cardtype) {
+        blocksize = 512;
+        writeaddr /= 512;
+    }
+
+    align = blocksize & (blocksize - 1);
+    if((blocksize > 0) && (blocksize <= 2048) && (0 == align)) {
+        datablksize = sd_datablocksize_get(blocksize);
+        /* send CMD16(SET_BLOCKLEN) to set the block length */
+        sdio_command_response_config(SD_CMD_SET_BLOCKLEN, (uint32_t)blocksize, SDIO_RESPONSETYPE_SHORT);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+
+        /* check if some error occurs */
+        status = r1_error_check(SD_CMD_SET_BLOCKLEN);
+        if(SD_OK != status) {
+            return status;
+        }
+    } else {
+        status = SD_PARAMETER_INVALID;
+        return status;
+    }
+
+    /* send CMD13(SEND_STATUS), addressed card sends its status registers */
+    sdio_command_response_config(SD_CMD_SEND_STATUS, (uint32_t)sd_rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_SEND_STATUS);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    if(blocksnumber >= 1) {
+        if(blocksnumber * blocksize > SD_MAX_DATA_LENGTH) {
+            status = SD_PARAMETER_INVALID;
+            return status;
+        }
+
+        if((SDIO_STD_CAPACITY_SD_CARD_V1_1 == cardtype) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == cardtype) ||
+                (SDIO_HIGH_CAPACITY_SD_CARD == cardtype)) {
+            /* send CMD55(APP_CMD) to indicate next command is application specific command */
+            sdio_command_response_config(SD_CMD_APP_CMD, (uint32_t)sd_rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
+            sdio_wait_type_set(SDIO_WAITTYPE_NO);
+            sdio_csm_enable();
+            /* check if some error occurs */
+            status = r1_error_check(SD_CMD_APP_CMD);
+            if(SD_OK != status) {
+                return status;
+            }
+
+            /* send ACMD23(SET_WR_BLK_ERASE_COUNT) to set the number of write blocks to be preerased before writing */
+            sdio_command_response_config(SD_APPCMD_SET_WR_BLK_ERASE_COUNT, blocksnumber, SDIO_RESPONSETYPE_SHORT);
+            sdio_wait_type_set(SDIO_WAITTYPE_NO);
+            sdio_csm_enable();
+            /* check if some error occurs */
+            status = r1_error_check(SD_APPCMD_SET_WR_BLK_ERASE_COUNT);
+            if(SD_OK != status) {
+                return status;
+            }
+        }
+        /* send CMD25(WRITE_MULTIPLE_BLOCK) to continuously write blocks of data */
+        sdio_command_response_config(SD_CMD_WRITE_MULTIPLE_BLOCK, writeaddr, SDIO_RESPONSETYPE_SHORT);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+        /* check if some error occurs */
+        status = r1_error_check(SD_CMD_WRITE_MULTIPLE_BLOCK);
+        if(SD_OK != status) {
+            return status;
+        }
+
+        stopcondition = 1;
+        totalnumber_bytes = blocksnumber * blocksize;
+
+        /* configure the SDIO data transmission */
+        sdio_data_config(SD_DATATIMEOUT, totalnumber_bytes, datablksize);
+        sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOCARD);
+        sdio_dsm_enable();
+
+        if(SD_POLLING_MODE == transmode) {
+            /* polling mode */
+            while(!sdio_flag_get(SDIO_FLAG_DTCRCERR | SDIO_FLAG_DTTMOUT | SDIO_FLAG_TXURE | SDIO_FLAG_DTEND | SDIO_FLAG_STBITE)) {
+                if(RESET != sdio_flag_get(SDIO_FLAG_TFH)) {
+                    /* at least 8 words can be written into the FIFO */
+                    if(!((totalnumber_bytes - transbytes) < SD_FIFOHALF_BYTES)) {
+                        for(count = 0; count < SD_FIFOHALF_WORDS; count++) {
+                            sdio_data_write(*(ptempbuff + count));
+                        }
+                        /* 8 words(32 bytes) has been transferred */
+                        ptempbuff += SD_FIFOHALF_WORDS;
+                        transbytes += SD_FIFOHALF_BYTES;
+                    } else {
+                        restwords = (totalnumber_bytes - transbytes) / 4 + (((totalnumber_bytes - transbytes) % 4 == 0) ? 0 : 1);
+                        for(count = 0; count < restwords; count++) {
+                            sdio_data_write(*ptempbuff);
+                            ++ptempbuff;
+                            transbytes += 4;
+                        }
+                    }
+                }
+            }
+
+            /* whether some error occurs and return it */
+            if(RESET != sdio_flag_get(SDIO_FLAG_DTCRCERR)) {
+                status = SD_DATA_CRC_ERROR;
+                sdio_flag_clear(SDIO_FLAG_DTCRCERR);
+                return status;
+            } else if(RESET != sdio_flag_get(SDIO_FLAG_DTTMOUT)) {
+                status = SD_DATA_TIMEOUT;
+                sdio_flag_clear(SDIO_FLAG_DTTMOUT);
+                return status;
+            } else if(RESET != sdio_flag_get(SDIO_FLAG_TXURE)) {
+                status = SD_TX_UNDERRUN_ERROR;
+                sdio_flag_clear(SDIO_FLAG_TXURE);
+                return status;
+            } else if(RESET != sdio_flag_get(SDIO_FLAG_STBITE)) {
+                status = SD_START_BIT_ERROR;
+                sdio_flag_clear(SDIO_FLAG_STBITE);
+                return status;
+            }
+
+            if(RESET != sdio_flag_get(SDIO_FLAG_DTEND)) {
+                if((SDIO_STD_CAPACITY_SD_CARD_V1_1 == cardtype) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == cardtype) ||
+                        (SDIO_HIGH_CAPACITY_SD_CARD == cardtype)) {
+                    /* send CMD12(STOP_TRANSMISSION) to stop transmission */
+                    sdio_command_response_config(SD_CMD_STOP_TRANSMISSION, (uint32_t)0x0, SDIO_RESPONSETYPE_SHORT);
+                    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+                    sdio_csm_enable();
+                    /* check if some error occurs */
+                    status = r1_error_check(SD_CMD_STOP_TRANSMISSION);
+                    if(SD_OK != status) {
+                        return status;
+                    }
+                }
+            }
+            sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+        } else if(SD_DMA_MODE == transmode) {
+            /* DMA mode */
+            /* enable SDIO corresponding interrupts and DMA */
+            sdio_interrupt_enable(SDIO_INT_DTCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_TXURE | SDIO_INT_DTEND | SDIO_INT_STBITE);
+            sdio_dma_enable();
+            dma_transfer_config(pwritebuffer, totalnumber_bytes);
+
+            uint32_t start = millis();
+            while((RESET == dma_flag_get(DMA1, DMA_CH3, DMA_FLAG_FTF))) {
+                if((uint32_t)(millis() - start) > 1000) {
+                    return SD_ERROR;
+                }
+                if (callback)
+                {
+                    // Note: GD32F4 DMA counts down from 0xFFFF when DMA flow control mode is "peripheral"
+                    uint32_t complete = (0xFFFF - DMA_CHCNT(DMA1, DMA_CH3) * 4);
+                    if (complete <= blocksize)
+                    {
+                        callback(complete);
+                    }
+                }
+            }
+            while((0 == transend) && (SD_OK == transerror)) {
+                if (callback)
+                {
+                    callback(totalnumber_bytes);
+                }
+            }
+            if(SD_OK != transerror) {
+                return transerror;
+            }
+        } else {
+            status = SD_PARAMETER_INVALID;
+            return status;
+        }
+    }
+
+    /* clear the SDIO_INTC flags */
+    sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+    /* get the card state and wait the card is out of programming and receiving state */
+    status = sd_card_state_get(&cardstate);
+    while((SD_OK == status) && ((SD_CARDSTATE_PROGRAMMING == cardstate) || (SD_CARDSTATE_RECEIVING == cardstate))) {
+        if (callback)
+        {
+            callback(totalnumber_bytes);
+        }
+        status = sd_card_state_get(&cardstate);
+    }
+    return status;
+}
+
+/*!
+    \brief      erase a continuous area of a card
+    \param[in]  startaddr: the start address
+    \param[in]  endaddr: the end address
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_erase(uint64_t startaddr, uint64_t endaddr)
+{
+    /* initialize the variables */
+    sd_error_enum status = SD_OK;
+    uint32_t count = 0, clkdiv = 0;
+    __IO uint32_t delay = 0;
+    uint8_t cardstate = 0, tempbyte = 0;
+    uint16_t tempccc = 0;
+
+    /* get the card command classes from CSD */
+    tempbyte = (uint8_t)((sd_csd[1] & SD_MASK_24_31BITS) >> 24);
+    tempccc = (uint16_t)((uint16_t)tempbyte << 4);
+    tempbyte = (uint8_t)((sd_csd[1] & SD_MASK_16_23BITS) >> 16);
+    tempccc |= (uint16_t)((uint16_t)(tempbyte & 0xF0) >> 4);
+    if(0 == (tempccc & SD_CCC_ERASE)) {
+        /* don't support the erase command */
+        status = SD_FUNCTION_UNSUPPORTED;
+        return status;
+    }
+    clkdiv = (SDIO_CLKCTL & SDIO_CLKCTL_DIV);
+    clkdiv += ((SDIO_CLKCTL & SDIO_CLKCTL_DIV8) >> 31) * 256;
+    clkdiv += 2;
+    delay = 168000 / clkdiv;
+
+    /* check whether the card is locked */
+    if(sdio_response_get(SDIO_RESPONSE0) & SD_CARDSTATE_LOCKED) {
+        status = SD_LOCK_UNLOCK_FAILED;
+        return(status);
+    }
+
+    /* blocksize is fixed in 512B for SDHC card */
+    if(SDIO_HIGH_CAPACITY_SD_CARD == cardtype) {
+        startaddr /= 512;
+        endaddr /= 512;
+    }
+
+    if((SDIO_STD_CAPACITY_SD_CARD_V1_1 == cardtype) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == cardtype) ||
+            (SDIO_HIGH_CAPACITY_SD_CARD == cardtype)) {
+        /* send CMD32(ERASE_WR_BLK_START) to set the address of the first write block to be erased */
+        sdio_command_response_config(SD_CMD_ERASE_WR_BLK_START, startaddr, SDIO_RESPONSETYPE_SHORT);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+        /* check if some error occurs */
+        status = r1_error_check(SD_CMD_ERASE_WR_BLK_START);
+        if(SD_OK != status) {
+            return status;
+        }
+
+        /* send CMD33(ERASE_WR_BLK_END) to set the address of the last write block of the continuous range to be erased */
+        sdio_command_response_config(SD_CMD_ERASE_WR_BLK_END, endaddr, SDIO_RESPONSETYPE_SHORT);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+        /* check if some error occurs */
+        status = r1_error_check(SD_CMD_ERASE_WR_BLK_END);
+        if(SD_OK != status) {
+            return status;
+        }
+    }
+
+    /* send CMD38(ERASE) to set the address of the first write block to be erased */
+    sdio_command_response_config(SD_CMD_ERASE, (uint32_t)0x0, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_ERASE);
+    if(SD_OK != status) {
+        return status;
+    }
+    /* loop until the counter is reach to the calculated time */
+    for(count = 0; count < delay; count++) {
+    }
+    /* get the card state and wait the card is out of programming and receiving state */
+    status = sd_card_state_get(&cardstate);
+    while((SD_OK == status) && ((SD_CARDSTATE_PROGRAMMING == cardstate) || (SD_CARDSTATE_RECEIVING == cardstate))) {
+        status = sd_card_state_get(&cardstate);
+    }
+    return status;
+}
+
+/*!
+    \brief      process all the interrupts which the corresponding flags are set
+    \param[in]  none
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_interrupts_process(void)
+{
+    transerror = SD_OK;
+    if(RESET != sdio_interrupt_flag_get(SDIO_INT_FLAG_DTEND)) {
+        /* send CMD12 to stop data transfer in multiple blocks operation */
+        if(1 == stopcondition) {
+            transerror = sd_transfer_stop();
+        } else {
+            transerror = SD_OK;
+        }
+        sdio_interrupt_flag_clear(SDIO_INT_DTEND);
+        /* disable all the interrupts */
+        sdio_interrupt_disable(SDIO_INT_DTCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_DTEND | SDIO_INT_STBITE |
+                               SDIO_INT_TFH | SDIO_INT_RFH | SDIO_INT_TXURE | SDIO_INT_RXORE);
+        transend = 1;
+        number_bytes = 0;
+        return transerror;
+    }
+
+    if(RESET != sdio_interrupt_flag_get(SDIO_INT_FLAG_DTCRCERR)) {
+        sdio_interrupt_flag_clear(SDIO_INT_DTCRCERR);
+        /* disable all the interrupts */
+        sdio_interrupt_disable(SDIO_INT_DTCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_DTEND | SDIO_INT_STBITE |
+                               SDIO_INT_TFH | SDIO_INT_RFH | SDIO_INT_TXURE | SDIO_INT_RXORE);
+        number_bytes = 0;
+        transerror = SD_DATA_CRC_ERROR;
+        return transerror;
+    }
+
+    if(RESET != sdio_interrupt_flag_get(SDIO_INT_FLAG_DTTMOUT)) {
+        sdio_interrupt_flag_clear(SDIO_INT_DTTMOUT);
+        /* disable all the interrupts */
+        sdio_interrupt_disable(SDIO_INT_DTCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_DTEND | SDIO_INT_STBITE |
+                               SDIO_INT_TFH | SDIO_INT_RFH | SDIO_INT_TXURE | SDIO_INT_RXORE);
+        number_bytes = 0;
+        transerror = SD_DATA_TIMEOUT;
+        return transerror;
+    }
+
+    if(RESET != sdio_interrupt_flag_get(SDIO_INT_FLAG_STBITE)) {
+        sdio_interrupt_flag_clear(SDIO_INT_STBITE);
+        /* disable all the interrupts */
+        sdio_interrupt_disable(SDIO_INT_DTCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_DTEND | SDIO_INT_STBITE |
+                               SDIO_INT_TFH | SDIO_INT_RFH | SDIO_INT_TXURE | SDIO_INT_RXORE);
+        number_bytes = 0;
+        transerror = SD_START_BIT_ERROR;
+        return transerror;
+    }
+
+    if(RESET != sdio_interrupt_flag_get(SDIO_INT_FLAG_TXURE)) {
+        sdio_interrupt_flag_clear(SDIO_INT_TXURE);
+        /* disable all the interrupts */
+        sdio_interrupt_disable(SDIO_INT_DTCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_DTEND | SDIO_INT_STBITE |
+                               SDIO_INT_TFH | SDIO_INT_RFH | SDIO_INT_TXURE | SDIO_INT_RXORE);
+        number_bytes = 0;
+        transerror = SD_TX_UNDERRUN_ERROR;
+        return transerror;
+    }
+
+    if(RESET != sdio_interrupt_flag_get(SDIO_INT_FLAG_RXORE)) {
+        sdio_interrupt_flag_clear(SDIO_INT_RXORE);
+        /* disable all the interrupts */
+        sdio_interrupt_disable(SDIO_INT_DTCRCERR | SDIO_INT_DTTMOUT | SDIO_INT_DTEND | SDIO_INT_STBITE |
+                               SDIO_INT_TFH | SDIO_INT_RFH | SDIO_INT_TXURE | SDIO_INT_RXORE);
+        number_bytes = 0;
+        transerror = SD_RX_OVERRUN_ERROR;
+        return transerror;
+    }
+    return transerror;
+}
+
+/*!
+    \brief      select or deselect a card
+    \param[in]  cardrca: the RCA of a card
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_card_select_deselect(uint16_t cardrca)
+{
+    sd_error_enum status = SD_OK;
+    /* send CMD7(SELECT/DESELECT_CARD) to select or deselect the card */
+    sdio_command_response_config(SD_CMD_SELECT_DESELECT_CARD, (uint32_t)(cardrca << SD_RCA_SHIFT), SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+
+    status = r1_error_check(SD_CMD_SELECT_DESELECT_CARD);
+    return status;
+}
+
+/*!
+    \brief      get the card status whose response format R1 contains a 32-bit field
+    \param[in]  none
+    \param[out] pcardstatus: a pointer that store card status
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_cardstatus_get(uint32_t *pcardstatus)
+{
+    sd_error_enum status = SD_OK;
+    if(NULL == pcardstatus) {
+        status = SD_PARAMETER_INVALID;
+        return status;
+    }
+
+    /* send CMD13(SEND_STATUS), addressed card sends its status register */
+    sdio_command_response_config(SD_CMD_SEND_STATUS, (uint32_t)sd_rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_SEND_STATUS);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    *pcardstatus = sdio_response_get(SDIO_RESPONSE0);
+    return status;
+}
+
+/*!
+    \brief      get the SD status, the size of the SD status is one data block of 512 bit
+    \param[in]  none
+    \param[out] psdstatus: a pointer that store SD card status
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_sdstatus_get(uint32_t *psdstatus)
+{
+    sd_error_enum status = SD_OK;
+    uint32_t count = 0;
+
+    /* check whether the card is locked */
+    if(sdio_response_get(SDIO_RESPONSE0) & SD_CARDSTATE_LOCKED) {
+        status = SD_LOCK_UNLOCK_FAILED;
+        return(status);
+    }
+
+    /* send CMD16(SET_BLOCKLEN) to set the block length */
+    sdio_command_response_config(SD_CMD_SET_BLOCKLEN, (uint32_t)64, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_SET_BLOCKLEN);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    /* send CMD55(APP_CMD) to indicate next command is application specific command */
+    sdio_command_response_config(SD_CMD_APP_CMD, (uint32_t)sd_rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_APP_CMD);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    /* configure the SDIO data transmission */
+    sdio_data_config(SD_DATATIMEOUT, (uint32_t)64, SDIO_DATABLOCKSIZE_64BYTES);
+    sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOSDIO);
+    sdio_dsm_enable();
+
+    /* send ACMD13(SD_STATUS) to get the SD status */
+    sdio_command_response_config(SD_APPCMD_SD_STATUS, (uint32_t)0x0, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_APPCMD_SD_STATUS);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    while(!sdio_flag_get(SDIO_FLAG_DTCRCERR | SDIO_FLAG_DTTMOUT | SDIO_FLAG_RXORE | SDIO_FLAG_DTBLKEND | SDIO_FLAG_STBITE)) {
+        if(RESET != sdio_flag_get(SDIO_FLAG_RFH)) {
+            for(count = 0; count < SD_FIFOHALF_WORDS; count++) {
+                *(psdstatus + count) = sdio_data_read();
+            }
+            psdstatus += SD_FIFOHALF_WORDS;
+        }
+    }
+
+    /* whether some error occurs and return it */
+    if(RESET != sdio_flag_get(SDIO_FLAG_DTCRCERR)) {
+        status = SD_DATA_CRC_ERROR;
+        sdio_flag_clear(SDIO_FLAG_DTCRCERR);
+        return status;
+    } else if(RESET != sdio_flag_get(SDIO_FLAG_DTTMOUT)) {
+        status = SD_DATA_TIMEOUT;
+        sdio_flag_clear(SDIO_FLAG_DTTMOUT);
+        return status;
+    } else if(RESET != sdio_flag_get(SDIO_FLAG_RXORE)) {
+        status = SD_RX_OVERRUN_ERROR;
+        sdio_flag_clear(SDIO_FLAG_RXORE);
+        return status;
+    } else if(RESET != sdio_flag_get(SDIO_FLAG_STBITE)) {
+        status = SD_START_BIT_ERROR;
+        sdio_flag_clear(SDIO_FLAG_STBITE);
+        return status;
+    }
+    while(RESET != sdio_flag_get(SDIO_FLAG_RXDTVAL)) {
+        *psdstatus = sdio_data_read();
+        ++psdstatus;
+    }
+
+    /* clear the SDIO_INTC flags */
+    sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+    psdstatus -= 16;
+    for(count = 0; count < 16; count++) {
+        psdstatus[count] = ((psdstatus[count] & SD_MASK_0_7BITS) << 24) | ((psdstatus[count] & SD_MASK_8_15BITS) << 8) |
+                           ((psdstatus[count] & SD_MASK_16_23BITS) >> 8) | ((psdstatus[count] & SD_MASK_24_31BITS) >> 24);
+    }
+    return status;
+}
+
+/*!
+    \brief      stop an ongoing data transfer
+    \param[in]  none
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_transfer_stop(void)
+{
+    sd_error_enum status = SD_OK;
+    /* send CMD12(STOP_TRANSMISSION) to stop transmission */
+    sdio_command_response_config(SD_CMD_STOP_TRANSMISSION, (uint32_t)0x0, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_STOP_TRANSMISSION);
+    return status;
+}
+
+/*!
+    \brief      lock or unlock a card
+    \param[in]  lockstate: the lock state
+      \arg        SD_LOCK: lock the SD card
+      \arg        SD_UNLOCK: unlock the SD card
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_lock_unlock(uint8_t lockstate)
+{
+    sd_error_enum status = SD_OK;
+    uint8_t cardstate = 0, tempbyte = 0;
+    uint32_t pwd1 = 0, pwd2 = 0, response = 0;
+    __IO uint32_t timeout = 0;
+    uint16_t tempccc = 0;
+
+    /* get the card command classes from CSD */
+    tempbyte = (uint8_t)((sd_csd[1] & SD_MASK_24_31BITS) >> 24);
+    tempccc = (uint16_t)((uint16_t)tempbyte << 4);
+    tempbyte = (uint8_t)((sd_csd[1] & SD_MASK_16_23BITS) >> 16);
+    tempccc |= (uint16_t)((uint16_t)(tempbyte & 0xF0) >> 4);
+
+    if(0 == (tempccc & SD_CCC_LOCK_CARD)) {
+        /* don't support the lock command */
+        status = SD_FUNCTION_UNSUPPORTED;
+        return status;
+    }
+    /* password pattern */
+    pwd1 = (0x01020600 | lockstate);
+    pwd2 = 0x03040506;
+
+    /* clear all DSM configuration */
+    sdio_data_config(0, 0, SDIO_DATABLOCKSIZE_1BYTE);
+    sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOCARD);
+    sdio_dsm_disable();
+    sdio_dma_disable();
+
+    /* send CMD16(SET_BLOCKLEN) to set the block length */
+    sdio_command_response_config(SD_CMD_SET_BLOCKLEN, (uint32_t)8, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_SET_BLOCKLEN);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    /* send CMD13(SEND_STATUS), addressed card sends its status register */
+    sdio_command_response_config(SD_CMD_SEND_STATUS, (uint32_t)sd_rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_SEND_STATUS);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    response = sdio_response_get(SDIO_RESPONSE0);
+    timeout = 100000;
+    while((0 == (response & SD_R1_READY_FOR_DATA)) && (timeout > 0)) {
+        /* continue to send CMD13 to polling the state of card until buffer empty or timeout */
+        --timeout;
+        /* send CMD13(SEND_STATUS), addressed card sends its status registers */
+        sdio_command_response_config(SD_CMD_SEND_STATUS, (uint32_t)sd_rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
+        sdio_wait_type_set(SDIO_WAITTYPE_NO);
+        sdio_csm_enable();
+        /* check if some error occurs */
+        status = r1_error_check(SD_CMD_SEND_STATUS);
+        if(SD_OK != status) {
+            return status;
+        }
+        response = sdio_response_get(SDIO_RESPONSE0);
+    }
+    if(0 == timeout) {
+        return SD_ERROR;
+    }
+
+    /* send CMD42(LOCK_UNLOCK) to set/reset the password or lock/unlock the card */
+    sdio_command_response_config(SD_CMD_LOCK_UNLOCK, (uint32_t)0x0, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_LOCK_UNLOCK);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    response = sdio_response_get(SDIO_RESPONSE0);
+
+    /* configure the SDIO data transmission */
+    sdio_data_config(SD_DATATIMEOUT, (uint32_t)8, SDIO_DATABLOCKSIZE_8BYTES);
+    sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOCARD);
+    sdio_dsm_enable();
+
+    /* write password pattern */
+    sdio_data_write(pwd1);
+    sdio_data_write(pwd2);
+
+    /* whether some error occurs and return it */
+    if(RESET != sdio_flag_get(SDIO_FLAG_DTCRCERR)) {
+        status = SD_DATA_CRC_ERROR;
+        sdio_flag_clear(SDIO_FLAG_DTCRCERR);
+        return status;
+    } else if(RESET != sdio_flag_get(SDIO_FLAG_DTTMOUT)) {
+        status = SD_DATA_TIMEOUT;
+        sdio_flag_clear(SDIO_FLAG_DTTMOUT);
+        return status;
+    } else if(RESET != sdio_flag_get(SDIO_FLAG_TXURE)) {
+        status = SD_TX_UNDERRUN_ERROR;
+        sdio_flag_clear(SDIO_FLAG_TXURE);
+        return status;
+    } else if(RESET != sdio_flag_get(SDIO_FLAG_STBITE)) {
+        status = SD_START_BIT_ERROR;
+        sdio_flag_clear(SDIO_FLAG_STBITE);
+        return status;
+    }
+
+    /* clear the SDIO_INTC flags */
+    sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+    /* get the card state and wait the card is out of programming and receiving state */
+    status = sd_card_state_get(&cardstate);
+    while((SD_OK == status) && ((SD_CARDSTATE_PROGRAMMING == cardstate) || (SD_CARDSTATE_RECEIVING == cardstate))) {
+        status = sd_card_state_get(&cardstate);
+    }
+    return status;
+}
+
+/*!
+    \brief      get the data transfer state
+    \param[in]  none
+    \param[out] none
+    \retval     sd_error_enum
+*/
+sd_transfer_state_enum sd_transfer_state_get(void)
+{
+    sd_transfer_state_enum transtate = SD_NO_TRANSFER;
+    if(RESET != sdio_flag_get(SDIO_FLAG_TXRUN | SDIO_FLAG_RXRUN)) {
+        transtate = SD_TRANSFER_IN_PROGRESS;
+    }
+    return transtate;
+}
+
+/*!
+    \brief      get SD card capacity
+    \param[in]  none
+    \param[out] none
+    \retval     capacity of the card(KB)
+*/
+uint32_t sd_card_capacity_get(void)
+{
+    uint8_t tempbyte = 0, devicesize_mult = 0, readblklen = 0;
+    uint32_t capacity = 0, devicesize = 0;
+    if((SDIO_STD_CAPACITY_SD_CARD_V1_1 == cardtype) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == cardtype)) {
+        /* calculate the c_size(device size) */
+        tempbyte = (uint8_t)((sd_csd[1] & SD_MASK_8_15BITS) >> 8);
+        devicesize |= (uint32_t)((uint32_t)(tempbyte & 0x03) << 10);
+        tempbyte = (uint8_t)(sd_csd[1] & SD_MASK_0_7BITS);
+        devicesize |= (uint32_t)((uint32_t)tempbyte << 2);
+        tempbyte = (uint8_t)((sd_csd[2] & SD_MASK_24_31BITS) >> 24);
+        devicesize |= (uint32_t)((uint32_t)(tempbyte & 0xC0) >> 6);
+
+        /* calculate the c_size_mult(device size multiplier) */
+        tempbyte = (uint8_t)((sd_csd[2] & SD_MASK_16_23BITS) >> 16);
+        devicesize_mult = (tempbyte & 0x03) << 1;
+        tempbyte = (uint8_t)((sd_csd[2] & SD_MASK_8_15BITS) >> 8);
+        devicesize_mult |= (tempbyte & 0x80) >> 7;
+
+        /* calculate the read_bl_len */
+        tempbyte = (uint8_t)((sd_csd[1] & SD_MASK_16_23BITS) >> 16);
+        readblklen = tempbyte & 0x0F;
+
+        /* capacity = BLOCKNR*BLOCK_LEN, BLOCKNR = (C_SIZE+1)*MULT, MULT = 2^(C_SIZE_MULT+2), BLOCK_LEN = 2^READ_BL_LEN */
+        capacity = (devicesize + 1) * (1 << (devicesize_mult + 2));
+        capacity *= (1 << readblklen);
+
+        /* change the unit of capacity to KByte */
+        capacity /= 1024;
+    } else if(SDIO_HIGH_CAPACITY_SD_CARD == cardtype) {
+        /* calculate the c_size */
+        tempbyte = (uint8_t)(sd_csd[1] & SD_MASK_0_7BITS);
+        devicesize = (uint32_t)((uint32_t)(tempbyte & 0x3F) << 16);
+        tempbyte = (uint8_t)((sd_csd[2] & SD_MASK_24_31BITS) >> 24);
+        devicesize |= (uint32_t)((uint32_t)tempbyte << 8);
+        tempbyte = (uint8_t)((sd_csd[2] & SD_MASK_16_23BITS) >> 16);
+        devicesize |= (uint32_t)tempbyte;
+
+        /* capacity = (c_size+1)*512KByte */
+        capacity = (devicesize + 1) * 512;
+    }
+    return capacity;
+}
+
+sd_error_enum sd_card_information_get_short(sdio_card_type_enum *card_type, uint16_t *card_rca)
+{
+    *card_type = cardtype;
+    *card_rca = sd_rca;
+    return SD_OK;
+}
+
+/*!
+    \brief      get the detailed information of the SD card based on received CID and CSD
+    \param[in]  none
+    \param[out] pcardinfo: a pointer that store the detailed card information
+    \retval     sd_error_enum
+*/
+sd_error_enum sd_card_information_get(sd_card_info_struct *pcardinfo)
+{
+    sd_error_enum status = SD_OK;
+    uint8_t tempbyte = 0;
+
+    if(NULL == pcardinfo) {
+        status = SD_PARAMETER_INVALID;
+        return status;
+    }
+
+    /* store the card type and RCA */
+    pcardinfo->card_type = cardtype;
+    pcardinfo->card_rca = sd_rca;
+
+    /* CID byte 0 */
+    tempbyte = (uint8_t)((sd_cid[0] & SD_MASK_24_31BITS) >> 24);
+    pcardinfo->card_cid.mid = tempbyte;
+
+    /* CID byte 1 */
+    tempbyte = (uint8_t)((sd_cid[0] & SD_MASK_16_23BITS) >> 16);
+    pcardinfo->card_cid.oid = (uint16_t)((uint16_t)tempbyte << 8);
+
+    /* CID byte 2 */
+    tempbyte = (uint8_t)((sd_cid[0] & SD_MASK_8_15BITS) >> 8);
+    pcardinfo->card_cid.oid |= (uint16_t)tempbyte;
+
+    /* CID byte 3 */
+    tempbyte = (uint8_t)(sd_cid[0] & SD_MASK_0_7BITS);
+    pcardinfo->card_cid.pnm0 = (uint32_t)((uint32_t)tempbyte << 24);
+
+    /* CID byte 4 */
+    tempbyte = (uint8_t)((sd_cid[1] & SD_MASK_24_31BITS) >> 24);
+    pcardinfo->card_cid.pnm0 |= (uint32_t)((uint32_t)tempbyte << 16);
+
+    /* CID byte 5 */
+    tempbyte = (uint8_t)((sd_cid[1] & SD_MASK_16_23BITS) >> 16);
+    pcardinfo->card_cid.pnm0 |= (uint32_t)((uint32_t)tempbyte << 8);
+
+    /* CID byte 6 */
+    tempbyte = (uint8_t)((sd_cid[1] & SD_MASK_8_15BITS) >> 8);
+    pcardinfo->card_cid.pnm0 |= (uint32_t)(tempbyte);
+
+    /* CID byte 7 */
+    tempbyte = (uint8_t)(sd_cid[1] & SD_MASK_0_7BITS);
+    pcardinfo->card_cid.pnm1 = tempbyte;
+
+    /* CID byte 8 */
+    tempbyte = (uint8_t)((sd_cid[2] & SD_MASK_24_31BITS) >> 24);
+    pcardinfo->card_cid.prv = tempbyte;
+
+    /* CID byte 9 */
+    tempbyte = (uint8_t)((sd_cid[2] & SD_MASK_16_23BITS) >> 16);
+    pcardinfo->card_cid.psn = (uint32_t)((uint32_t)tempbyte << 24);
+
+    /* CID byte 10 */
+    tempbyte = (uint8_t)((sd_cid[2] & SD_MASK_8_15BITS) >> 8);
+    pcardinfo->card_cid.psn |= (uint32_t)((uint32_t)tempbyte << 16);
+
+    /* CID byte 11 */
+    tempbyte = (uint8_t)(sd_cid[2] & SD_MASK_0_7BITS);
+    pcardinfo->card_cid.psn |= (uint32_t)tempbyte;
+
+    /* CID byte 12 */
+    tempbyte = (uint8_t)((sd_cid[3] & SD_MASK_24_31BITS) >> 24);
+    pcardinfo->card_cid.psn |= (uint32_t)tempbyte;
+
+    /* CID byte 13 */
+    tempbyte = (uint8_t)((sd_cid[3] & SD_MASK_16_23BITS) >> 16);
+    pcardinfo->card_cid.mdt = (uint16_t)((uint16_t)(tempbyte & 0x0F) << 8);
+
+    /* CID byte 14 */
+    tempbyte = (uint8_t)((sd_cid[3] & SD_MASK_8_15BITS) >> 8);
+    pcardinfo->card_cid.mdt |= (uint16_t)tempbyte;
+
+    /* CID byte 15 */
+    tempbyte = (uint8_t)(sd_cid[3] & SD_MASK_0_7BITS);
+    pcardinfo->card_cid.cid_crc = (tempbyte & 0xFE) >> 1;
+
+    /* CSD byte 0 */
+    tempbyte = (uint8_t)((sd_csd[0] & SD_MASK_24_31BITS) >> 24);
+    pcardinfo->card_csd.csd_struct = (tempbyte & 0xC0) >> 6;
+
+    /* CSD byte 1 */
+    tempbyte = (uint8_t)((sd_csd[0] & SD_MASK_16_23BITS) >> 16);
+    pcardinfo->card_csd.taac = tempbyte;
+
+    /* CSD byte 2 */
+    tempbyte = (uint8_t)((sd_csd[0] & SD_MASK_8_15BITS) >> 8);
+    pcardinfo->card_csd.nsac = tempbyte;
+
+    /* CSD byte 3 */
+    tempbyte = (uint8_t)(sd_csd[0] & SD_MASK_0_7BITS);
+    pcardinfo->card_csd.tran_speed = tempbyte;
+
+    /* CSD byte 4 */
+    tempbyte = (uint8_t)((sd_csd[1] & SD_MASK_24_31BITS) >> 24);
+    pcardinfo->card_csd.ccc = (uint16_t)((uint16_t)tempbyte << 4);
+
+    /* CSD byte 5 */
+    tempbyte = (uint8_t)((sd_csd[1] & SD_MASK_16_23BITS) >> 16);
+    pcardinfo->card_csd.ccc |= (uint16_t)((uint16_t)(tempbyte & 0xF0) >> 4);
+    pcardinfo->card_csd.read_bl_len = tempbyte & 0x0F;
+
+    /* CSD byte 6 */
+    tempbyte = (uint8_t)((sd_csd[1] & SD_MASK_8_15BITS) >> 8);
+    pcardinfo->card_csd.read_bl_partial = (tempbyte & 0x80) >> 7;
+    pcardinfo->card_csd.write_blk_misalign = (tempbyte & 0x40) >> 6;
+    pcardinfo->card_csd.read_blk_misalign = (tempbyte & 0x20) >> 5;
+    pcardinfo->card_csd.dsp_imp = (tempbyte & 0x10) >> 4;
+
+    if((SDIO_STD_CAPACITY_SD_CARD_V1_1 == cardtype) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == cardtype)) {
+        /* card is SDSC card, CSD version 1.0 */
+        pcardinfo->card_csd.c_size = (uint32_t)((uint32_t)(tempbyte & 0x03) << 10);
+
+        /* CSD byte 7 */
+        tempbyte = (uint8_t)(sd_csd[1] & SD_MASK_0_7BITS);
+        pcardinfo->card_csd.c_size |= (uint32_t)((uint32_t)tempbyte << 2);
+
+        /* CSD byte 8 */
+        tempbyte = (uint8_t)((sd_csd[2] & SD_MASK_24_31BITS) >> 24);
+        pcardinfo->card_csd.c_size |= (uint32_t)((uint32_t)(tempbyte & 0xC0) >> 6);
+        pcardinfo->card_csd.vdd_r_curr_min = (tempbyte & 0x38) >> 3;
+        pcardinfo->card_csd.vdd_r_curr_max = tempbyte & 0x07;
+
+        /* CSD byte 9 */
+        tempbyte = (uint8_t)((sd_csd[2] & SD_MASK_16_23BITS) >> 16);
+        pcardinfo->card_csd.vdd_w_curr_min = (tempbyte & 0xE0) >> 5;
+        pcardinfo->card_csd.vdd_w_curr_max = (tempbyte & 0x1C) >> 2;
+        pcardinfo->card_csd.c_size_mult = (tempbyte & 0x03) << 1;
+
+        /* CSD byte 10 */
+        tempbyte = (uint8_t)((sd_csd[2] & SD_MASK_8_15BITS) >> 8);
+        pcardinfo->card_csd.c_size_mult |= (tempbyte & 0x80) >> 7;
+
+        /* calculate the card block size and capacity */
+        pcardinfo->card_blocksize = 1 << (pcardinfo->card_csd.read_bl_len);
+        pcardinfo->card_capacity = pcardinfo->card_csd.c_size + 1;
+        pcardinfo->card_capacity *= (1 << (pcardinfo->card_csd.c_size_mult + 2));
+        pcardinfo->card_capacity *= pcardinfo->card_blocksize;
+    } else if(SDIO_HIGH_CAPACITY_SD_CARD == cardtype) {
+        /* card is SDHC card, CSD version 2.0 */
+        /* CSD byte 7 */
+        tempbyte = (uint8_t)(sd_csd[1] & SD_MASK_0_7BITS);
+        pcardinfo->card_csd.c_size = (uint32_t)((uint32_t)(tempbyte & 0x3F) << 16);
+
+        /* CSD byte 8 */
+        tempbyte = (uint8_t)((sd_csd[2] & SD_MASK_24_31BITS) >> 24);
+        pcardinfo->card_csd.c_size |= (uint32_t)((uint32_t)tempbyte << 8);
+
+        /* CSD byte 9 */
+        tempbyte = (uint8_t)((sd_csd[2] & SD_MASK_16_23BITS) >> 16);
+        pcardinfo->card_csd.c_size |= (uint32_t)tempbyte;
+
+        /* calculate the card block size and capacity */
+        pcardinfo->card_blocksize = 512;
+        pcardinfo->card_capacity = (pcardinfo->card_csd.c_size + 1) * 512 * 1024;
+    }
+
+    pcardinfo->card_csd.erase_blk_en = (tempbyte & 0x40) >> 6;
+    pcardinfo->card_csd.sector_size = (tempbyte & 0x3F) << 1;
+
+    /* CSD byte 11 */
+    tempbyte = (uint8_t)(sd_csd[2] & SD_MASK_0_7BITS);
+    pcardinfo->card_csd.sector_size |= (tempbyte & 0x80) >> 7;
+    pcardinfo->card_csd.wp_grp_size = (tempbyte & 0x7F);
+
+    /* CSD byte 12 */
+    tempbyte = (uint8_t)((sd_csd[3] & SD_MASK_24_31BITS) >> 24);
+    pcardinfo->card_csd.wp_grp_enable = (tempbyte & 0x80) >> 7;
+    pcardinfo->card_csd.r2w_factor = (tempbyte & 0x1C) >> 2;
+    pcardinfo->card_csd.write_bl_len = (tempbyte & 0x03) << 2;
+
+    /* CSD byte 13 */
+    tempbyte = (uint8_t)((sd_csd[3] & SD_MASK_16_23BITS) >> 16);
+    pcardinfo->card_csd.write_bl_len |= (tempbyte & 0xC0) >> 6;
+    pcardinfo->card_csd.write_bl_partial = (tempbyte & 0x20) >> 5;
+
+    /* CSD byte 14 */
+    tempbyte = (uint8_t)((sd_csd[3] & SD_MASK_8_15BITS) >> 8);
+    pcardinfo->card_csd.file_format_grp = (tempbyte & 0x80) >> 7;
+    pcardinfo->card_csd.copy_flag = (tempbyte & 0x40) >> 6;
+    pcardinfo->card_csd.perm_write_protect = (tempbyte & 0x20) >> 5;
+    pcardinfo->card_csd.tmp_write_protect = (tempbyte & 0x10) >> 4;
+    pcardinfo->card_csd.file_format = (tempbyte & 0x0C) >> 2;
+
+    /* CSD byte 15 */
+    tempbyte = (uint8_t)(sd_csd[3] & SD_MASK_0_7BITS);
+    pcardinfo->card_csd.csd_crc = (tempbyte & 0xFE) >> 1;
+
+    return status;
+}
+
+void sd_cid_get(uint8_t *cid)
+{
+    // SdFat expects the data in big endian format.
+    for (int i = 0; i < 16; i++)
+    {
+        cid[i] = (sd_cid[i / 4] >> (24 - (i % 4) * 8)) & 0xFF;
+    }
+}
+
+void sd_csd_get(uint8_t *csd)
+{
+    for (int i = 0; i < 16; i++)
+    {
+        csd[i] = (sd_csd[i / 4] >> (24 - (i % 4) * 8)) & 0xFF;
+    }
+}
+
+/*!
+    \brief      check if the command sent error occurs
+    \param[in]  none
+    \param[out] none
+    \retval     sd_error_enum
+*/
+static sd_error_enum cmdsent_error_check(void)
+{
+    sd_error_enum status = SD_OK;
+    __IO uint32_t timeout = 100000;
+    /* check command sent flag */
+    while((RESET == sdio_flag_get(SDIO_FLAG_CMDSEND)) && (timeout > 0)) {
+        --timeout;
+    }
+    /* command response is timeout */
+    if(0 == timeout) {
+        status = SD_CMD_RESP_TIMEOUT;
+        return status;
+    }
+    /* if the command is sent, clear the SDIO_INTC flags */
+    sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+    return status;
+}
+
+/*!
+    \brief      check if error type for R1 response
+    \param[in]  resp: content of response
+    \param[out] none
+    \retval     sd_error_enum
+*/
+static sd_error_enum r1_error_type_check(uint32_t resp)
+{
+    sd_error_enum status = SD_ERROR;
+    /* check which error occurs */
+    if(resp & SD_R1_OUT_OF_RANGE) {
+        status = SD_OUT_OF_RANGE;
+    } else if(resp & SD_R1_ADDRESS_ERROR) {
+        status = SD_ADDRESS_ERROR;
+    } else if(resp & SD_R1_BLOCK_LEN_ERROR) {
+        status = SD_BLOCK_LEN_ERROR;
+    } else if(resp & SD_R1_ERASE_SEQ_ERROR) {
+        status = SD_ERASE_SEQ_ERROR;
+    } else if(resp & SD_R1_ERASE_PARAM) {
+        status = SD_ERASE_PARAM;
+    } else if(resp & SD_R1_WP_VIOLATION) {
+        status = SD_WP_VIOLATION;
+    } else if(resp & SD_R1_LOCK_UNLOCK_FAILED) {
+        status = SD_LOCK_UNLOCK_FAILED;
+    } else if(resp & SD_R1_COM_CRC_ERROR) {
+        status = SD_COM_CRC_ERROR;
+    } else if(resp & SD_R1_ILLEGAL_COMMAND) {
+        status = SD_ILLEGAL_COMMAND;
+    } else if(resp & SD_R1_CARD_ECC_FAILED) {
+        status = SD_CARD_ECC_FAILED;
+    } else if(resp & SD_R1_CC_ERROR) {
+        status = SD_CC_ERROR;
+    } else if(resp & SD_R1_GENERAL_UNKNOWN_ERROR) {
+        status = SD_GENERAL_UNKNOWN_ERROR;
+    } else if(resp & SD_R1_CSD_OVERWRITE) {
+        status = SD_CSD_OVERWRITE;
+    } else if(resp & SD_R1_WP_ERASE_SKIP) {
+        status = SD_WP_ERASE_SKIP;
+    } else if(resp & SD_R1_CARD_ECC_DISABLED) {
+        status = SD_CARD_ECC_DISABLED;
+    } else if(resp & SD_R1_ERASE_RESET) {
+        status = SD_ERASE_RESET;
+    } else if(resp & SD_R1_AKE_SEQ_ERROR) {
+        status = SD_AKE_SEQ_ERROR;
+    }
+    return status;
+}
+
+/*!
+    \brief      check if error occurs for R1 response
+    \param[in]  cmdindex: the index of command
+    \param[out] none
+    \retval     sd_error_enum
+*/
+static sd_error_enum r1_error_check(uint8_t cmdindex)
+{
+    sd_error_enum status = SD_OK;
+    uint32_t reg_status = 0, resp_r1 = 0;
+
+    /* store the content of SDIO_STAT */
+    reg_status = SDIO_STAT;
+    while(!(reg_status & (SDIO_FLAG_CCRCERR | SDIO_FLAG_CMDTMOUT | SDIO_FLAG_CMDRECV))) {
+        reg_status = SDIO_STAT;
+    }
+    /* check whether an error or timeout occurs or command response received */
+    if(reg_status & SDIO_FLAG_CCRCERR) {
+        status = SD_CMD_CRC_ERROR;
+        sdio_flag_clear(SDIO_FLAG_CCRCERR);
+        return status;
+    } else if(reg_status & SDIO_FLAG_CMDTMOUT) {
+        status = SD_CMD_RESP_TIMEOUT;
+        sdio_flag_clear(SDIO_FLAG_CMDTMOUT);
+        return status;
+    }
+
+    /* check whether the last response command index is the desired one */
+    if(sdio_command_index_get() != cmdindex) {
+        status = SD_ILLEGAL_COMMAND;
+        return status;
+    }
+    /* clear all the SDIO_INTC flags */
+    sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+    /* get the SDIO response register 0 for checking */
+    resp_r1 = sdio_response_get(SDIO_RESPONSE0);
+    if(SD_ALLZERO == (resp_r1 & SD_R1_ERROR_BITS)) {
+        /* no error occurs, return SD_OK */
+        status = SD_OK;
+        return status;
+    }
+
+    /* if some error occurs, return the error type */
+    status = r1_error_type_check(resp_r1);
+    return status;
+}
+
+/*!
+    \brief      check if error occurs for R2 response
+    \param[in]  none
+    \param[out] none
+    \retval     sd_error_enum
+*/
+static sd_error_enum r2_error_check(void)
+{
+    sd_error_enum status = SD_OK;
+    uint32_t reg_status = 0;
+
+    /* store the content of SDIO_STAT */
+    reg_status = SDIO_STAT;
+    while(!(reg_status & (SDIO_FLAG_CCRCERR | SDIO_FLAG_CMDTMOUT | SDIO_FLAG_CMDRECV))) {
+        reg_status = SDIO_STAT;
+    }
+    /* check whether an error or timeout occurs or command response received */
+    if(reg_status & SDIO_FLAG_CCRCERR) {
+        status = SD_CMD_CRC_ERROR;
+        sdio_flag_clear(SDIO_FLAG_CCRCERR);
+        return status;
+    } else if(reg_status & SDIO_FLAG_CMDTMOUT) {
+        status = SD_CMD_RESP_TIMEOUT;
+        sdio_flag_clear(SDIO_FLAG_CMDTMOUT);
+        return status;
+    }
+    /* clear all the SDIO_INTC flags */
+    sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+    return status;
+}
+
+/*!
+    \brief      check if error occurs for R3 response
+    \param[in]  none
+    \param[out] none
+    \retval     sd_error_enum
+*/
+static sd_error_enum r3_error_check(void)
+{
+    sd_error_enum status = SD_OK;
+    uint32_t reg_status = 0;
+
+    /* store the content of SDIO_STAT */
+    reg_status = SDIO_STAT;
+    while(!(reg_status & (SDIO_FLAG_CCRCERR | SDIO_FLAG_CMDTMOUT | SDIO_FLAG_CMDRECV))) {
+        reg_status = SDIO_STAT;
+    }
+    if(reg_status & SDIO_FLAG_CMDTMOUT) {
+        status = SD_CMD_RESP_TIMEOUT;
+        sdio_flag_clear(SDIO_FLAG_CMDTMOUT);
+        return status;
+    }
+    /* clear all the SDIO_INTC flags */
+    sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+    return status;
+}
+
+/*!
+    \brief      check if error occurs for R6 response
+    \param[in]  cmdindex: the index of command
+    \param[out] prca: a pointer that store the RCA of card
+    \retval     sd_error_enum
+*/
+static sd_error_enum r6_error_check(uint8_t cmdindex, uint16_t *prca)
+{
+    sd_error_enum status = SD_OK;
+    uint32_t reg_status = 0, response = 0;
+
+    /* store the content of SDIO_STAT */
+    reg_status = SDIO_STAT;
+    while(!(reg_status & (SDIO_FLAG_CCRCERR | SDIO_FLAG_CMDTMOUT | SDIO_FLAG_CMDRECV))) {
+        reg_status = SDIO_STAT;
+    }
+    /* check whether an error or timeout occurs or command response received */
+    if(reg_status & SDIO_FLAG_CCRCERR) {
+        status = SD_CMD_CRC_ERROR;
+        sdio_flag_clear(SDIO_FLAG_CCRCERR);
+        return status;
+    } else if(reg_status & SDIO_FLAG_CMDTMOUT) {
+        status = SD_CMD_RESP_TIMEOUT;
+        sdio_flag_clear(SDIO_FLAG_CMDTMOUT);
+        return status;
+    }
+
+    /* check whether the last response command index is the desired one */
+    if(sdio_command_index_get() != cmdindex) {
+        status = SD_ILLEGAL_COMMAND;
+        return status;
+    }
+    /* clear all the SDIO_INTC flags */
+    sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+    /* get the SDIO response register 0 for checking */
+    response = sdio_response_get(SDIO_RESPONSE0);
+
+    if(SD_ALLZERO == (response & (SD_R6_COM_CRC_ERROR | SD_R6_ILLEGAL_COMMAND | SD_R6_GENERAL_UNKNOWN_ERROR))) {
+        *prca = (uint16_t)(response >> 16);
+        return status;
+    }
+    /* if some error occurs, return the error type */
+    if(response & SD_R6_COM_CRC_ERROR) {
+        status = SD_COM_CRC_ERROR;
+    } else if(response & SD_R6_ILLEGAL_COMMAND) {
+        status = SD_ILLEGAL_COMMAND;
+    } else if(response & SD_R6_GENERAL_UNKNOWN_ERROR) {
+        status = SD_GENERAL_UNKNOWN_ERROR;
+    }
+    return status;
+}
+
+/*!
+    \brief      check if error occurs for R7 response
+    \param[in]  none
+    \param[out] none
+    \retval     sd_error_enum
+*/
+static sd_error_enum r7_error_check(void)
+{
+    sd_error_enum status = SD_ERROR;
+    uint32_t reg_status = 0;
+    __IO uint32_t timeout = 100000;
+
+    /* store the content of SDIO_STAT */
+    reg_status = SDIO_STAT;
+    while(!(reg_status & (SDIO_FLAG_CCRCERR | SDIO_FLAG_CMDTMOUT | SDIO_FLAG_CMDRECV)) && (timeout > 0)) {
+        reg_status = SDIO_STAT;
+        --timeout;
+    }
+
+    /* check the flags */
+    if((reg_status & SDIO_FLAG_CMDTMOUT) || (0 == timeout)) {
+        status = SD_CMD_RESP_TIMEOUT;
+        sdio_flag_clear(SDIO_FLAG_CMDTMOUT);
+        return status;
+    }
+    if(reg_status & SDIO_FLAG_CMDRECV) {
+        status = SD_OK;
+        sdio_flag_clear(SDIO_FLAG_CMDRECV);
+        return status;
+    }
+    return status;
+}
+
+/*!
+    \brief      get the state which the card is in
+    \param[in]  none
+    \param[out] pcardstate: a pointer that store the card state
+      \arg        SD_CARDSTATE_IDLE: card is in idle state
+      \arg        SD_CARDSTATE_READY: card is in ready state
+      \arg        SD_CARDSTATE_IDENTIFICAT: card is in identification state
+      \arg        SD_CARDSTATE_STANDBY: card is in standby state
+      \arg        SD_CARDSTATE_TRANSFER: card is in transfer state
+      \arg        SD_CARDSTATE_DATA: card is in data state
+      \arg        SD_CARDSTATE_RECEIVING: card is in receiving state
+      \arg        SD_CARDSTATE_PROGRAMMING: card is in programming state
+      \arg        SD_CARDSTATE_DISCONNECT: card is in disconnect state
+      \arg        SD_CARDSTATE_LOCKED: card is in locked state
+    \retval     sd_error_enum
+*/
+static sd_error_enum sd_card_state_get(uint8_t *pcardstate)
+{
+    sd_error_enum status = SD_OK;
+    __IO uint32_t reg_status = 0, response = 0;
+
+    /* send CMD13(SEND_STATUS), addressed card sends its status register */
+    sdio_command_response_config(SD_CMD_SEND_STATUS, (uint32_t)sd_rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+
+    /* store the content of SDIO_STAT */
+    reg_status = SDIO_STAT;
+    while(!(reg_status & (SDIO_FLAG_CCRCERR | SDIO_FLAG_CMDTMOUT | SDIO_FLAG_CMDRECV))) {
+        reg_status = SDIO_STAT;
+    }
+    /* check whether an error or timeout occurs or command response received */
+    if(reg_status & SDIO_FLAG_CCRCERR) {
+        status = SD_CMD_CRC_ERROR;
+        sdio_flag_clear(SDIO_FLAG_CCRCERR);
+        return status;
+    } else if(reg_status & SDIO_FLAG_CMDTMOUT) {
+        status = SD_CMD_RESP_TIMEOUT;
+        sdio_flag_clear(SDIO_FLAG_CMDTMOUT);
+        return status;
+    }
+
+    /* command response received, store the response command index */
+    reg_status = (uint32_t)sdio_command_index_get();
+    if(reg_status != (uint32_t)SD_CMD_SEND_STATUS) {
+        status = SD_ILLEGAL_COMMAND;
+        return status;
+    }
+    /* clear all the SDIO_INTC flags */
+    sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+    /* get the SDIO response register 0 for checking */
+    response = sdio_response_get(SDIO_RESPONSE0);
+    *pcardstate = (uint8_t)((response >> 9) & 0x0000000F);
+
+    if(SD_ALLZERO == (response & SD_R1_ERROR_BITS)) {
+        /* no error occurs, return SD_OK */
+        status = SD_OK;
+        return status;
+    }
+
+    /* if some error occurs, return the error type */
+    status = r1_error_type_check(response);
+    return status;
+}
+
+/*!
+    \brief      configure the bus width mode
+    \param[in]  buswidth: the bus width
+      \arg        SD_BUS_WIDTH_1BIT: 1-bit bus width
+      \arg        SD_BUS_WIDTH_4BIT: 4-bit bus width
+    \param[out] none
+    \retval     sd_error_enum
+*/
+static sd_error_enum sd_bus_width_config(uint32_t buswidth)
+{
+    sd_error_enum status = SD_OK;
+    /* check whether the card is locked */
+    if(sdio_response_get(SDIO_RESPONSE0) & SD_CARDSTATE_LOCKED) {
+        status = SD_LOCK_UNLOCK_FAILED;
+        return status;
+    }
+    /* get the SCR register */
+    status = sd_scr_get(sd_rca, sd_scr);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    if(SD_BUS_WIDTH_1BIT == buswidth) {
+        if(SD_ALLZERO != (sd_scr[1] & buswidth)) {
+            /* send CMD55(APP_CMD) to indicate next command is application specific command */
+            sdio_command_response_config(SD_CMD_APP_CMD, (uint32_t)sd_rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
+            sdio_wait_type_set(SDIO_WAITTYPE_NO);
+            sdio_csm_enable();
+            /* check if some error occurs */
+            status = r1_error_check(SD_CMD_APP_CMD);
+            if(SD_OK != status) {
+                return status;
+            }
+
+            /* send ACMD6(SET_BUS_WIDTH) to define the data bus width */
+            sdio_command_response_config(SD_APPCMD_SET_BUS_WIDTH, (uint32_t)0x0, SDIO_RESPONSETYPE_SHORT);
+            sdio_wait_type_set(SDIO_WAITTYPE_NO);
+            sdio_csm_enable();
+            /* check if some error occurs */
+            status = r1_error_check(SD_APPCMD_SET_BUS_WIDTH);
+            if(SD_OK != status) {
+                return status;
+            }
+        } else {
+            status = SD_OPERATION_IMPROPER;
+        }
+        return status;
+    } else if(SD_BUS_WIDTH_4BIT == buswidth) {
+        if(SD_ALLZERO != (sd_scr[1] & buswidth)) {
+            /* send CMD55(APP_CMD) to indicate next command is application specific command */
+            sdio_command_response_config(SD_CMD_APP_CMD, (uint32_t)sd_rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
+            sdio_wait_type_set(SDIO_WAITTYPE_NO);
+            sdio_csm_enable();
+            /* check if some error occurs */
+            status = r1_error_check(SD_CMD_APP_CMD);
+            if(SD_OK != status) {
+                return status;
+            }
+
+            /* send ACMD6(SET_BUS_WIDTH) to define the data bus width */
+            sdio_command_response_config(SD_APPCMD_SET_BUS_WIDTH, (uint32_t)0x2, SDIO_RESPONSETYPE_SHORT);
+            sdio_wait_type_set(SDIO_WAITTYPE_NO);
+            sdio_csm_enable();
+            /* check if some error occurs */
+            status = r1_error_check(SD_APPCMD_SET_BUS_WIDTH);
+            if(SD_OK != status) {
+                return status;
+            }
+        } else {
+            status = SD_OPERATION_IMPROPER;
+        }
+        return status;
+    } else {
+        status = SD_PARAMETER_INVALID;
+        return status;
+    }
+}
+
+/*!
+    \brief      get the SCR of corresponding card
+    \param[in]  rca: RCA of a card
+    \param[out] pscr: a pointer that store the SCR content
+    \retval     sd_error_enum
+*/
+static sd_error_enum sd_scr_get(uint16_t rca, uint32_t *pscr)
+{
+    sd_error_enum status = SD_OK;
+    uint32_t temp_scr[2] = {0, 0}, idx_scr = 0;
+    /* send CMD16(SET_BLOCKLEN) to set block length */
+    sdio_command_response_config(SD_CMD_SET_BLOCKLEN, (uint32_t)8, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_SET_BLOCKLEN);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    /* send CMD55(APP_CMD) to indicate next command is application specific command */
+    sdio_command_response_config(SD_CMD_APP_CMD, (uint32_t)rca << SD_RCA_SHIFT, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_CMD_APP_CMD);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    /* configure SDIO data */
+    sdio_data_config(SD_DATATIMEOUT, (uint32_t)8, SDIO_DATABLOCKSIZE_8BYTES);
+    sdio_data_transfer_config(SDIO_TRANSMODE_BLOCK, SDIO_TRANSDIRECTION_TOSDIO);
+    sdio_dsm_enable();
+
+    /* send ACMD51(SEND_SCR) to read the SD configuration register */
+    sdio_command_response_config(SD_APPCMD_SEND_SCR, (uint32_t)0x0, SDIO_RESPONSETYPE_SHORT);
+    sdio_wait_type_set(SDIO_WAITTYPE_NO);
+    sdio_csm_enable();
+    /* check if some error occurs */
+    status = r1_error_check(SD_APPCMD_SEND_SCR);
+    if(SD_OK != status) {
+        return status;
+    }
+
+    /* store the received SCR */
+    while(!sdio_flag_get(SDIO_FLAG_DTCRCERR | SDIO_FLAG_DTTMOUT | SDIO_FLAG_RXORE | SDIO_FLAG_DTBLKEND | SDIO_FLAG_STBITE)) {
+        if(RESET != sdio_flag_get(SDIO_FLAG_RXDTVAL)) {
+            *(temp_scr + idx_scr) = sdio_data_read();
+            ++idx_scr;
+        }
+    }
+
+    /* check whether some error occurs */
+    if(RESET != sdio_flag_get(SDIO_FLAG_DTCRCERR)) {
+        status = SD_DATA_CRC_ERROR;
+        sdio_flag_clear(SDIO_FLAG_DTCRCERR);
+        return status;
+    } else if(RESET != sdio_flag_get(SDIO_FLAG_DTTMOUT)) {
+        status = SD_DATA_TIMEOUT;
+        sdio_flag_clear(SDIO_FLAG_DTTMOUT);
+        return status;
+    } else if(RESET != sdio_flag_get(SDIO_FLAG_RXORE)) {
+        status = SD_RX_OVERRUN_ERROR;
+        sdio_flag_clear(SDIO_FLAG_RXORE);
+        return status;
+    } else if(RESET != sdio_flag_get(SDIO_FLAG_STBITE)) {
+        status = SD_START_BIT_ERROR;
+        sdio_flag_clear(SDIO_FLAG_STBITE);
+        return status;
+    }
+
+    /* clear all the SDIO_INTC flags */
+    sdio_flag_clear(SDIO_MASK_INTC_FLAGS);
+    /* readjust the temp SCR value */
+    *(pscr) = ((temp_scr[1] & SD_MASK_0_7BITS) << 24) | ((temp_scr[1] & SD_MASK_8_15BITS) << 8) |
+              ((temp_scr[1] & SD_MASK_16_23BITS) >> 8) | ((temp_scr[1] & SD_MASK_24_31BITS) >> 24);
+    *(pscr + 1) = ((temp_scr[0] & SD_MASK_0_7BITS) << 24) | ((temp_scr[0] & SD_MASK_8_15BITS) << 8) |
+                  ((temp_scr[0] & SD_MASK_16_23BITS) >> 8) | ((temp_scr[0] & SD_MASK_24_31BITS) >> 24);
+    return status;
+}
+
+/*!
+    \brief      get the data block size
+    \param[in]  bytesnumber: the number of bytes
+    \param[out] none
+    \retval     data block size
+      \arg        SDIO_DATABLOCKSIZE_1BYTE: block size = 1 byte
+      \arg        SDIO_DATABLOCKSIZE_2BYTES: block size = 2 bytes
+      \arg        SDIO_DATABLOCKSIZE_4BYTES: block size = 4 bytes
+      \arg        SDIO_DATABLOCKSIZE_8BYTES: block size = 8 bytes
+      \arg        SDIO_DATABLOCKSIZE_16BYTES: block size = 16 bytes
+      \arg        SDIO_DATABLOCKSIZE_32BYTES: block size = 32 bytes
+      \arg        SDIO_DATABLOCKSIZE_64BYTES: block size = 64 bytes
+      \arg        SDIO_DATABLOCKSIZE_128BYTES: block size = 128 bytes
+      \arg        SDIO_DATABLOCKSIZE_256BYTES: block size = 256 bytes
+      \arg        SDIO_DATABLOCKSIZE_512BYTES: block size = 512 bytes
+      \arg        SDIO_DATABLOCKSIZE_1024BYTES: block size = 1024 bytes
+      \arg        SDIO_DATABLOCKSIZE_2048BYTES: block size = 2048 bytes
+      \arg        SDIO_DATABLOCKSIZE_4096BYTES: block size = 4096 bytes
+      \arg        SDIO_DATABLOCKSIZE_8192BYTES: block size = 8192 bytes
+      \arg        SDIO_DATABLOCKSIZE_16384BYTES: block size = 16384 bytes
+*/
+static uint32_t sd_datablocksize_get(uint16_t bytesnumber)
+{
+    uint8_t exp_val = 0;
+    /* calculate the exponent of 2 */
+    while(1 != bytesnumber) {
+        bytesnumber >>= 1;
+        ++exp_val;
+    }
+    return DATACTL_BLKSZ(exp_val);
+}
+
+/*!
+    \brief      configure the GPIO of SDIO interface
+    \param[in]  none
+    \param[out] none
+    \retval     none
+*/
+static void gpio_config(void)
+{
+    /* configure the SDIO_DAT0(PC8), SDIO_DAT1(PC9), SDIO_DAT2(PC10), SDIO_DAT3(PC11), SDIO_CLK(PC12) and SDIO_CMD(PD2) */
+    gpio_af_set(GPIOC, GPIO_AF_12, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12);
+    gpio_af_set(GPIOD, GPIO_AF_12, GPIO_PIN_2);
+
+    gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11);
+    gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_25MHZ, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11);
+
+    gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_12);
+    gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_25MHZ, GPIO_PIN_12);
+
+    gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_2);
+    gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_25MHZ, GPIO_PIN_2);
+
+}
+
+/*!
+    \brief      configure the RCU of SDIO and DMA
+    \param[in]  none
+    \param[out] none
+    \retval     none
+*/
+static void rcu_config(void)
+{
+    rcu_periph_clock_enable(RCU_GPIOC);
+    rcu_periph_clock_enable(RCU_GPIOD);
+
+    rcu_periph_clock_enable(RCU_SDIO);
+    rcu_periph_clock_enable(RCU_DMA1);
+}
+
+/*!
+    \brief      configure the DMA1 channel 3 for transferring data
+    \param[in]  srcbuf: a pointer point to a buffer which will be transferred
+    \param[in]  bufsize: the size of buffer(not used in flow controller is peripheral)
+    \param[out] none
+    \retval     none
+*/
+static void dma_transfer_config(uint32_t *srcbuf, uint32_t bufsize)
+{
+    dma_multi_data_parameter_struct dma_struct;
+    /* clear all the interrupt flags */
+    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_FEE);
+    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_SDE);
+    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_TAE);
+    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_HTF);
+    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_FTF);
+    dma_channel_disable(DMA1, DMA_CH3);
+    dma_deinit(DMA1, DMA_CH3);
+
+    /* configure the DMA1 channel 3 */
+    dma_struct.periph_addr = (uint32_t)SDIO_FIFO_ADDR;
+    dma_struct.memory0_addr = (uint32_t)srcbuf;
+    dma_struct.direction = DMA_MEMORY_TO_PERIPH;
+    dma_struct.number = 0;
+    dma_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
+    dma_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
+    dma_struct.periph_width = DMA_PERIPH_WIDTH_32BIT;
+    dma_struct.memory_width = DMA_MEMORY_WIDTH_32BIT;
+    dma_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
+    dma_struct.periph_burst_width = DMA_PERIPH_BURST_4_BEAT;
+    dma_struct.memory_burst_width = DMA_MEMORY_BURST_4_BEAT;
+    dma_struct.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
+    dma_struct.critical_value = DMA_FIFO_4_WORD;
+    dma_multi_data_mode_init(DMA1, DMA_CH3, &dma_struct);
+
+    dma_flow_controller_config(DMA1, DMA_CH3, DMA_FLOW_CONTROLLER_PERI);
+    dma_channel_subperipheral_select(DMA1, DMA_CH3, DMA_SUBPERI4);
+    dma_channel_enable(DMA1, DMA_CH3);
+}
+
+/*!
+    \brief      configure the DMA1 channel 3 for receiving data
+    \param[in]  dstbuf: a pointer point to a buffer which will receive data
+    \param[in]  bufsize: the size of buffer(not used in flow controller is peripheral)
+    \param[out] none
+    \retval     none
+*/
+static void dma_receive_config(uint32_t *dstbuf, uint32_t bufsize)
+{
+    dma_multi_data_parameter_struct dma_struct;
+    /* clear all the interrupt flags */
+    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_FEE);
+    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_SDE);
+    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_TAE);
+    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_HTF);
+    dma_flag_clear(DMA1, DMA_CH3, DMA_FLAG_FTF);
+    dma_channel_disable(DMA1, DMA_CH3);
+    dma_deinit(DMA1, DMA_CH3);
+
+    /* configure the DMA1 channel 3 */
+    dma_struct.periph_addr = (uint32_t)SDIO_FIFO_ADDR;
+    dma_struct.memory0_addr = (uint32_t)dstbuf;
+    dma_struct.direction = DMA_PERIPH_TO_MEMORY;
+    dma_struct.number = bufsize/4;
+    dma_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
+    dma_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
+    dma_struct.periph_width = DMA_PERIPH_WIDTH_32BIT;
+    dma_struct.memory_width = DMA_MEMORY_WIDTH_32BIT;
+    dma_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
+    dma_struct.periph_burst_width = DMA_PERIPH_BURST_4_BEAT;
+    dma_struct.memory_burst_width = DMA_MEMORY_BURST_4_BEAT;
+    dma_struct.critical_value = DMA_FIFO_4_WORD;
+    dma_struct.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
+    dma_multi_data_mode_init(DMA1, DMA_CH3, &dma_struct);
+
+    dma_flow_controller_config(DMA1, DMA_CH3, DMA_FLOW_CONTROLLER_PERI);
+    dma_channel_subperipheral_select(DMA1, DMA_CH3, DMA_SUBPERI4);
+    dma_channel_enable(DMA1, DMA_CH3);
+}

+ 262 - 0
lib/ZuluSCSI_platform_GD32F450/gd32_sdio_sdcard.h

@@ -0,0 +1,262 @@
+/*!
+    \file    sdcard.h
+    \brief   the header file of SD card driver
+
+    \version 2016-08-15, V1.0.0, firmware for GD32F4xx
+    \version 2018-12-12, V2.0.0, firmware for GD32F4xx
+    \version 2020-09-30, V2.1.0, firmware for GD32F4xx
+    \version 2022-03-09, V3.0.0, firmware for GD32F4xx
+*/
+
+/*
+    Copyright (c) 2022, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice,
+       this list of conditions and the following disclaimer in the documentation
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors
+       may be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+#ifndef SDCARD_H
+#define SDCARD_H
+
+#include "gd32f4xx.h"
+
+/* SD memory card bus commands index */
+#define SD_CMD_GO_IDLE_STATE                  ((uint8_t)0)   /* CMD0, GO_IDLE_STATE */
+#define SD_CMD_ALL_SEND_CID                   ((uint8_t)2)   /* CMD2, ALL_SEND_CID */
+#define SD_CMD_SEND_RELATIVE_ADDR             ((uint8_t)3)   /* CMD3, SEND_RELATIVE_ADDR */
+#define SD_CMD_SET_DSR                        ((uint8_t)4)   /* CMD4, SET_DSR */
+#define SD_CMD_SWITCH_FUNC                    ((uint8_t)6)   /* CMD6, SWITCH_FUNC */
+#define SD_CMD_SELECT_DESELECT_CARD           ((uint8_t)7)   /* CMD7, SELECT_DESELECT_CARD */
+#define SD_CMD_SEND_IF_COND                   ((uint8_t)8)   /* CMD8, SEND_IF_COND */
+#define SD_CMD_SEND_CSD                       ((uint8_t)9)   /* CMD9, SEND_CSD */
+#define SD_CMD_SEND_CID                       ((uint8_t)10)  /* CMD10, SEND_CID */
+#define SD_CMD_STOP_TRANSMISSION              ((uint8_t)12)  /* CMD12, STOP_TRANSMISSION */
+#define SD_CMD_SEND_STATUS                    ((uint8_t)13)  /* CMD13, SEND_STATUS */
+#define SD_CMD_GO_INACTIVE_STATE              ((uint8_t)15)  /* CMD15, GO_INACTIVE_STATE */
+#define SD_CMD_SET_BLOCKLEN                   ((uint8_t)16)  /* CMD16, SET_BLOCKLEN */
+#define SD_CMD_READ_SINGLE_BLOCK              ((uint8_t)17)  /* CMD17, READ_SINGLE_BLOCK */
+#define SD_CMD_READ_MULTIPLE_BLOCK            ((uint8_t)18)  /* CMD18, READ_MULTIPLE_BLOCK */
+#define SD_CMD_WRITE_BLOCK                    ((uint8_t)24)  /* CMD24, WRITE_BLOCK */
+#define SD_CMD_WRITE_MULTIPLE_BLOCK           ((uint8_t)25)  /* CMD25, WRITE_MULTIPLE_BLOCK */
+#define SD_CMD_PROG_CSD                       ((uint8_t)27)  /* CMD27, PROG_CSD */
+#define SD_CMD_SET_WRITE_PROT                 ((uint8_t)28)  /* CMD28, SET_WRITE_PROT */
+#define SD_CMD_CLR_WRITE_PROT                 ((uint8_t)29)  /* CMD29, CLR_WRITE_PROT */
+#define SD_CMD_SEND_WRITE_PROT                ((uint8_t)30)  /* CMD30, SEND_WRITE_PROT */
+#define SD_CMD_ERASE_WR_BLK_START             ((uint8_t)32)  /* CMD32, ERASE_WR_BLK_START */
+#define SD_CMD_ERASE_WR_BLK_END               ((uint8_t)33)  /* CMD33, ERASE_WR_BLK_END */
+#define SD_CMD_ERASE                          ((uint8_t)38)  /* CMD38, ERASE */
+#define SD_CMD_LOCK_UNLOCK                    ((uint8_t)42)  /* CMD42, LOCK_UNLOCK */
+#define SD_CMD_APP_CMD                        ((uint8_t)55)  /* CMD55, APP_CMD */
+#define SD_CMD_GEN_CMD                        ((uint8_t)56)  /* CMD56, GEN_CMD */
+
+/* SD memory card application specific commands index */
+#define SD_APPCMD_SET_BUS_WIDTH               ((uint8_t)6)   /* ACMD6, SET_BUS_WIDTH */
+#define SD_APPCMD_SD_STATUS                   ((uint8_t)13)  /* ACMD13, SD_STATUS */
+#define SD_APPCMD_SEND_NUM_WR_BLOCKS          ((uint8_t)22)  /* ACMD22, SEND_NUM_WR_BLOCKS */
+#define SD_APPCMD_SET_WR_BLK_ERASE_COUNT      ((uint8_t)23)  /* ACMD23, SET_WR_BLK_ERASE_COUNT */
+#define SD_APPCMD_SD_SEND_OP_COND             ((uint8_t)41)  /* ACMD41, SD_SEND_OP_COND */
+#define SD_APPCMD_SET_CLR_CARD_DETECT         ((uint8_t)42)  /* ACMD42, SET_CLR_CARD_DETECT */
+#define SD_APPCMD_SEND_SCR                    ((uint8_t)51)  /* ACMD51, SEND_SCR */
+
+/* card command class */
+#define SD_CCC_SWITCH                          BIT(10)       /* class 10 */
+#define SD_CCC_IO_MODE                         BIT(9)        /* class 9 */
+#define SD_CCC_APPLICATION_SPECIFIC            BIT(8)        /* class 8 */
+#define SD_CCC_LOCK_CARD                       BIT(7)        /* class 7 */
+#define SD_CCC_WRITE_PROTECTION                BIT(6)        /* class 6 */
+#define SD_CCC_ERASE                           BIT(5)        /* class 5 */
+#define SD_CCC_BLOCK_WRITE                     BIT(4)        /* class 4 */
+#define SD_CCC_BLOCK_READ                      BIT(2)        /* class 2 */
+#define SD_CCC_BASIC                           BIT(0)        /* class 0 */
+
+/* SD card data transmission mode */
+#define SD_DMA_MODE                           ((uint32_t)0x00000000) /* DMA mode */
+#define SD_POLLING_MODE                       ((uint32_t)0x00000001) /* polling mode */
+
+/* lock unlock status */
+#define SD_LOCK                               ((uint8_t)0x05)        /* lock the SD card */
+#define SD_UNLOCK                             ((uint8_t)0x02)        /* unlock the SD card */
+
+/* supported memory cards types */
+typedef enum {
+    SDIO_STD_CAPACITY_SD_CARD_V1_1 = 0,   /* standard capacity SD card version 1.1 */
+    SDIO_STD_CAPACITY_SD_CARD_V2_0,       /* standard capacity SD card version 2.0 */
+    SDIO_HIGH_CAPACITY_SD_CARD,           /* high capacity SD card */
+    SDIO_SECURE_DIGITAL_IO_CARD,          /* secure digital IO card */
+    SDIO_SECURE_DIGITAL_IO_COMBO_CARD,    /* secure digital IO combo card */
+    SDIO_MULTIMEDIA_CARD,                 /* multimedia card */
+    SDIO_HIGH_CAPACITY_MULTIMEDIA_CARD,   /* high capacity multimedia card */
+    SDIO_HIGH_SPEED_MULTIMEDIA_CARD       /* high speed multimedia card */
+} sdio_card_type_enum;
+
+/* card identification (CID) register */
+typedef struct {
+    __IO uint8_t mid;                     /* manufacturer ID */
+    __IO uint16_t oid;                    /* OEM/application ID */
+    __IO uint32_t pnm0;                   /* product name */
+    __IO uint8_t pnm1;                    /* product name */
+    __IO uint8_t prv;                     /* product revision */
+    __IO uint32_t psn;                    /* product serial number */
+    __IO uint16_t mdt;                    /* manufacturing date */
+    __IO uint8_t cid_crc;                 /* CID CRC7 checksum */
+} sd_cid_struct;
+
+/* CSD register (CSD version 1.0 and 2.0) */
+typedef struct {
+    __IO uint8_t csd_struct;              /* CSD struct */
+    __IO uint8_t taac;                    /* data read access-time */
+    __IO uint8_t nsac;                    /* data read access-time in CLK cycles */
+    __IO uint8_t tran_speed;              /* max. data transfer rate */
+    __IO uint16_t ccc;                    /* card command classes */
+    __IO uint8_t read_bl_len;             /* max. read data block length */
+    __IO uint8_t read_bl_partial;         /* partial blocks for read allowed */
+    __IO uint8_t write_blk_misalign;      /* write block misalignment */
+    __IO uint8_t read_blk_misalign;       /* read block misalignment */
+    __IO uint8_t dsp_imp;                 /* DSR implemented */
+    __IO uint32_t c_size;                 /* device size, 12 bits in CSD version 1.0, 22 bits in CSD version 2.0 */
+    __IO uint8_t vdd_r_curr_min;          /* max. read current @VDD min, CSD version 1.0 */
+    __IO uint8_t vdd_r_curr_max;          /* max. read current @VDD max, CSD version 1.0 */
+    __IO uint8_t vdd_w_curr_min;          /* max. write current @VDD min, CSD version 1.0 */
+    __IO uint8_t vdd_w_curr_max;          /* max. write current @VDD max, CSD version 1.0 */
+    __IO uint8_t c_size_mult;             /* device size multiplier, CSD version 1.0 */
+    __IO uint8_t erase_blk_en;            /* erase single block enable */
+    __IO uint8_t sector_size;             /* erase sector size */
+    __IO uint8_t wp_grp_size;             /* write protect group size */
+    __IO uint8_t wp_grp_enable;           /* write protect group enable */
+    __IO uint8_t r2w_factor;              /* write speed factor */
+    __IO uint8_t write_bl_len;            /* max. write data block length */
+    __IO uint8_t write_bl_partial;        /* partial blocks for write allowed */
+    __IO uint8_t file_format_grp;         /* file format group */
+    __IO uint8_t copy_flag;               /* copy flag (OTP) */
+    __IO uint8_t perm_write_protect;      /* permanent write protection */
+    __IO uint8_t tmp_write_protect;       /* temporary write protection */
+    __IO uint8_t file_format;             /* file format */
+    __IO uint8_t csd_crc;                 /* CSD CRC checksum */
+} sd_csd_struct;
+
+/* information of card */
+typedef struct {
+    sd_cid_struct card_cid;               /* CID register */
+    sd_csd_struct card_csd;               /* CSD register */
+    sdio_card_type_enum card_type;        /* card tpye */
+    uint32_t card_capacity;               /* card capacity */
+    uint32_t card_blocksize;              /* card block size */
+    uint16_t card_rca;                    /* card relative card address */
+} sd_card_info_struct;
+
+/* SD error flags */
+typedef enum {
+    SD_OUT_OF_RANGE = 0,                  /* command's argument was out of range */
+    SD_ADDRESS_ERROR,                     /* misaligned address which did not match the block length */
+    SD_BLOCK_LEN_ERROR,                   /* transferred block length is not allowed for the card or the number of transferred bytes does not match the block length */
+    SD_ERASE_SEQ_ERROR,                   /* an error in the sequence of erase command occurs */
+    SD_ERASE_PARAM,                       /* an invalid selection of write-blocks for erase occurred */
+    SD_WP_VIOLATION,                      /* attempt to program a write protect block or permanent write protected card */
+    SD_LOCK_UNLOCK_FAILED,                /* sequence or password error has been detected in lock/unlock card command */
+    SD_COM_CRC_ERROR,                     /* CRC check of the previous command failed */
+    SD_ILLEGAL_COMMAND,                   /* command not legal for the card state */
+    SD_CARD_ECC_FAILED,                   /* card internal ECC was applied but failed to correct the data */
+    SD_CC_ERROR,                          /* internal card controller error */
+    SD_GENERAL_UNKNOWN_ERROR,             /* general or unknown error occurred during the operation */
+    SD_CSD_OVERWRITE,                     /* read only section of the CSD does not match the card content or an attempt to reverse the copy or permanent WP bits was made */
+    SD_WP_ERASE_SKIP,                     /* only partial address space was erased or the temporary or permanent write protected card was erased */
+    SD_CARD_ECC_DISABLED,                 /* command has been executed without using internal ECC */
+    SD_ERASE_RESET,                       /* erase sequence was cleared before executing because an out of erase sequence command was received */
+    SD_AKE_SEQ_ERROR,                     /* error in the sequence of the authentication process */
+
+    SD_CMD_CRC_ERROR,                     /* command response received (CRC check failed) */
+    SD_DATA_CRC_ERROR,                    /* data block sent/received (CRC check failed) */
+    SD_CMD_RESP_TIMEOUT,                  /* command response timeout */
+    SD_DATA_TIMEOUT,                      /* data timeout */
+    SD_TX_UNDERRUN_ERROR,                 /* transmit FIFO underrun error occurs */
+    SD_RX_OVERRUN_ERROR,                  /* received FIFO overrun error occurs */
+    SD_START_BIT_ERROR,                   /* start bit error in the bus */
+
+    SD_VOLTRANGE_INVALID,                 /* the voltage range is invalid */
+    SD_PARAMETER_INVALID,                 /* the parameter is invalid */
+    SD_OPERATION_IMPROPER,                /* the operation is improper */
+    SD_FUNCTION_UNSUPPORTED,              /* the function is unsupported */
+    SD_ERROR,                             /* an error occurred */
+    SD_OK                                 /* no error occurred */
+} sd_error_enum;
+
+typedef enum {
+    SD_NO_TRANSFER = 0,                     /* no data transfer is acting */
+    SD_TRANSFER_IN_PROGRESS                 /* data transfer is in progress */
+} sd_transfer_state_enum;
+
+extern uint32_t sd_scr[2];                /* SD card SCR */
+
+/* function declarations */
+/* initialize the SD card and make it in standby state */
+sd_error_enum sd_init(void);
+/* initialize the card and get CID and CSD of the card */
+sd_error_enum sd_card_init(void);
+/* configure the clock and the work voltage, and get the card type */
+sd_error_enum sd_power_on(void);
+/* close the power of SDIO */
+sd_error_enum sd_power_off(void);
+
+/* configure the bus mode */
+sd_error_enum sd_bus_mode_config(uint32_t busmode);
+/* configure the mode of transmission */
+sd_error_enum sd_transfer_mode_config(uint32_t txmode);
+
+typedef void (*sdio_callback_t)(uint32_t bytes_done);
+/* read a block data into a buffer from the specified address of a card */
+sd_error_enum sd_block_read(uint32_t *preadbuffer, uint64_t readaddr, uint16_t blocksize, sdio_callback_t callback);
+/* read multiple blocks data into a buffer from the specified address of a card */
+sd_error_enum sd_multiblocks_read(uint32_t *preadbuffer, uint64_t readaddr, uint16_t blocksize, uint32_t blocksnumber, sdio_callback_t callback);
+/* write a block data to the specified address of a card */
+sd_error_enum sd_block_write(uint32_t *pwritebuffer, uint64_t writeaddr, uint16_t blocksize, sdio_callback_t callback);
+/* write multiple blocks data to the specified address of a card */
+sd_error_enum sd_multiblocks_write(uint32_t *pwritebuffer, uint64_t writeaddr, uint16_t blocksize, uint32_t blocksnumber, sdio_callback_t callback);
+/* erase a continuous area of a card */
+sd_error_enum sd_erase(uint64_t startaddr, uint64_t endaddr);
+/* process all the interrupts which the corresponding flags are set */
+sd_error_enum sd_interrupts_process(void);
+
+/* select or deselect a card */
+sd_error_enum sd_card_select_deselect(uint16_t cardrca);
+/* get the card status whose response format R1 contains a 32-bit field */
+sd_error_enum sd_cardstatus_get(uint32_t *pcardstatus);
+/* get the SD status, the size of the SD status is one data block of 512 bit */
+sd_error_enum sd_sdstatus_get(uint32_t *psdstatus);
+/* stop an ongoing data transfer */
+sd_error_enum sd_transfer_stop(void);
+/* lock or unlock a card */
+sd_error_enum sd_lock_unlock(uint8_t lockstate);
+
+/* get the data transfer state */
+sd_transfer_state_enum sd_transfer_state_get(void);
+/* get SD card capacity(KB) */
+uint32_t sd_card_capacity_get(void);
+/* get the detailed information of the SD card based on received CID and CSD */
+sd_error_enum sd_card_information_get(sd_card_info_struct *pcardinfo);
+sd_error_enum sd_card_information_get_short(sdio_card_type_enum *card_type, uint16_t *card_rca);
+
+/* Get card information in raw format */
+void sd_cid_get(uint8_t *cid);
+void sd_csd_get(uint8_t *csd);
+
+#endif /* SDCARD_H */

+ 216 - 0
lib/ZuluSCSI_platform_GD32F450/greenpak.cpp

@@ -0,0 +1,216 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+// I2C communication with GreenPAK.
+// This uses bitbanging for I2C so that internal GPIO pull-up can be used
+// and to avoid the bugs that are present in STM32F2 I2C peripheral,
+// it is uncertain if the same bugs apply to GD32F2.
+
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+#include "greenpak.h"
+#include "greenpak_fw.h"
+#include <gd32f4xx_gpio.h>
+
+#ifndef GREENPAK_I2C_PORT
+
+bool greenpak_write(uint16_t regaddr, const uint8_t *data, int length) { return false; }
+bool greenpak_read(uint16_t regaddr, uint8_t *data, int length) { return false; }
+bool greenpak_load_firmware() { return false; }
+bool greenpak_is_ready() { return false; }
+
+#else
+
+bool g_greenpak_is_ready;
+
+// SCL is driven as push-pull, SDA is driven as IPU / OUT_OD
+#define I2C_SCL_HI() GPIO_BOP(GREENPAK_I2C_PORT) = GREENPAK_I2C_SCL
+#define I2C_SCL_LO() GPIO_BC(GREENPAK_I2C_PORT) = GREENPAK_I2C_SCL
+#define I2C_SDA_HI() gpio_mode_set(GREENPAK_I2C_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GREENPAK_I2C_SDA)
+#define I2C_SDA_LO() gpio_mode_set(GREENPAK_I2C_PORT, GPIO_MODE_OUTPUT, GPIO_OSPEED_2MHZ, GREENPAK_I2C_SDA), gpio_output_options_set(GREENPAK_I2C_PORT,GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GREENPAK_I2C_SDA), GPIO_BC(GREENPAK_I2C_PORT) = GREENPAK_I2C_SDA
+
+#define I2C_SDA_READ() (GPIO_ISTAT(GREENPAK_I2C_PORT) & GREENPAK_I2C_SDA)
+#define I2C_DELAY() delay_ns(10000);
+
+static void greenpak_gpio_init()
+{
+    gpio_bit_set(GREENPAK_I2C_PORT, GREENPAK_I2C_SCL | GREENPAK_I2C_SDA);
+
+    gpio_mode_set(GREENPAK_I2C_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GREENPAK_I2C_SDA);
+    
+    gpio_mode_set(GREENPAK_I2C_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GREENPAK_I2C_SCL);
+    gpio_output_options_set(GREENPAK_I2C_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GREENPAK_I2C_SCL);
+
+    // Data bits used for communication
+    uint32_t greenpak_io = GREENPAK_PLD_IO1 | GREENPAK_PLD_IO2 | GREENPAK_PLD_IO3;
+    gpio_bit_reset(SCSI_OUT_PORT, greenpak_io);
+    gpio_mode_set(GREENPAK_PLD_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, greenpak_io);
+    gpio_output_options_set(GREENPAK_PLD_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, greenpak_io);
+    
+}
+
+static void i2c_writebit(bool bit)
+{
+    if (bit)
+        I2C_SDA_HI();
+    else
+        I2C_SDA_LO();
+
+    I2C_DELAY();
+    I2C_SCL_HI();
+    I2C_DELAY();
+    I2C_SCL_LO();
+}
+
+static bool i2c_readbit()
+{
+    I2C_SDA_HI(); // Pull-up
+    I2C_DELAY();
+    I2C_SCL_HI();
+    I2C_DELAY();
+    bool result = I2C_SDA_READ();
+    I2C_SCL_LO();
+    return result;
+}
+
+// Write byte to I2C bus, return ACK bit status
+static bool i2c_writebyte(uint8_t byte)
+{
+    for (int i = 0; i < 8; i++)
+    {
+        i2c_writebit(byte & (0x80 >> i));
+    }
+    return !i2c_readbit();
+}
+
+// Read byte from I2C bus
+static uint8_t i2c_readbyte(bool ack)
+{
+    uint8_t result = 0;
+    for (int i = 0; i < 8; i++)
+    {
+        result |= i2c_readbit() << (7 - i);
+    }
+
+    i2c_writebyte(!ack);
+    return result;
+}
+
+static bool i2c_start(uint8_t device_addr)
+{
+    // Initial signal state
+    I2C_SCL_HI();
+    I2C_SDA_HI();
+    I2C_DELAY();
+
+    // Start condition
+    I2C_SDA_LO();
+    I2C_DELAY();
+    
+    I2C_SCL_LO();
+    I2C_DELAY();
+
+    // Device address
+    return i2c_writebyte(device_addr);
+}
+
+static void i2c_stop()
+{
+    I2C_SDA_LO();
+    I2C_DELAY();
+    I2C_SCL_HI();
+    I2C_DELAY();
+    I2C_SDA_HI();
+    I2C_DELAY();
+}
+
+bool greenpak_write(uint16_t regaddr, const uint8_t *data, int length)
+{
+    bool status = true;
+    uint16_t blockaddr = regaddr >> 8;
+    status &= i2c_start(GREENPAK_I2C_ADDR | (blockaddr << 1));
+    status &= i2c_writebyte(regaddr & 0xFF);
+    
+    for (int i = 0; i < length; i++)
+    {
+        status &= i2c_writebyte(data[i]);
+    }
+
+    i2c_stop();
+    return status;
+}
+
+bool greenpak_read(uint16_t regaddr, uint8_t *data, int length)
+{
+    bool status = true;
+    uint16_t blockaddr = (regaddr >> 8) & 7;
+    status &= i2c_start(GREENPAK_I2C_ADDR | (blockaddr << 1));
+    status &= i2c_writebyte(regaddr & 0xFF);
+
+    status &= i2c_start(GREENPAK_I2C_ADDR | (blockaddr << 1) | 1);
+    
+    for (int i = 0; i < length; i++)
+    {
+        data[i] = i2c_readbyte(i < length - 1);
+    }
+
+    i2c_stop();
+    return status;
+}
+
+bool greenpak_load_firmware()
+{
+    uint8_t dummy;
+    greenpak_gpio_init();
+
+    if (!greenpak_read(0, &dummy, 1))
+    {
+        logmsg("Optional GreenPAK not detected");
+        return false;
+    }
+    else
+    {
+        logmsg("Optional GreenPAK detected, loading firmware");
+    }
+
+    if (!greenpak_write(0, g_greenpak_fw, sizeof(g_greenpak_fw)))
+    {
+        logmsg("GreenPAK firmware loading failed");
+        return false;
+    }
+    else
+    {
+        logmsg("GreenPAK firmware successfully loaded");
+        LED_ON();
+        delay(10);
+        LED_OFF();
+        delay(100);
+        g_greenpak_is_ready = true;
+        return true;
+    }
+}
+
+bool greenpak_is_ready()
+{
+    return g_greenpak_is_ready;
+}
+
+#endif

+ 34 - 0
lib/ZuluSCSI_platform_GD32F450/greenpak.h

@@ -0,0 +1,34 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+// External GreenPAK SLG46824 programmable logic can optionally be used to
+// accelerate SCSI communications. This module contains code to load firmware
+// to the GreenPAK through I2C.
+
+#pragma once
+
+#include <stdint.h>
+
+bool greenpak_write(uint16_t regaddr, const uint8_t *data, int length);
+bool greenpak_read(uint16_t regaddr, uint8_t *data, int length);
+
+bool greenpak_load_firmware();
+bool greenpak_is_ready();

+ 46 - 0
lib/ZuluSCSI_platform_GD32F450/greenpak_fw.h

@@ -0,0 +1,46 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+
+const uint8_t g_greenpak_fw[] = {
+  0xc4, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0xc2, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x08,
+  0x13, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x30, 0x70, 0x00, 0x30, 0x20, 0x30, 0x30, 0x00, 0x00, 0x30, 0x30,
+  0x30, 0x00, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x14, 0x22, 0x30, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0xd7, 0x14, 0x20, 0x00, 0x01, 0x00, 0x00,
+  0x00, 0x02, 0x01, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x02, 0x01,
+  0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x02,
+  0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0xa5
+};

+ 37 - 0
lib/ZuluSCSI_platform_GD32F450/scsi2sd_time.h

@@ -0,0 +1,37 @@
+/** 
+ * SCSI2SD V6 - Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * This file is licensed under the GPL version 3 or any later version.  
+ * It is derived from time.h in SCSI2SD V6.
+ *  
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+// Timing functions for SCSI2SD.
+// This file is derived from time.h in SCSI2SD-V6.
+
+#pragma once
+
+#include <stdint.h>
+#include "ZuluSCSI_platform.h"
+
+#define s2s_getTime_ms() millis()
+#define s2s_elapsedTime_ms(since) ((uint32_t)(millis() - (since)))
+#define s2s_delay_ms(x) delay_ns(x * 1000000)
+#define s2s_delay_us(x) delay_ns(x * 1000)
+#define s2s_delay_ns(x) delay_ns(x)
+

+ 626 - 0
lib/ZuluSCSI_platform_GD32F450/scsiPhy.cpp

@@ -0,0 +1,626 @@
+/** 
+ * SCSI2SD V6 - Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * This file is licensed under the GPL version 3 or any later version.  
+ * It is derived from scsiPhy.c in SCSI2SD V6.
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+// Implements the low level interface to SCSI bus
+// Partially derived from scsiPhy.c from SCSI2SD-V6
+
+#include "scsiPhy.h"
+#include "ZuluSCSI_platform.h"
+#include "scsi_accel_asm.h"
+#include "scsi_accel_dma.h"
+#include "scsi_accel_greenpak.h"
+#include "scsi_accel_sync.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_log_trace.h"
+#include "ZuluSCSI_config.h"
+#include <minIni.h>
+
+#include <scsi2sd.h>
+extern "C" {
+#include <scsi.h>
+#include <scsi2sd_time.h>
+}
+
+// Acceleration mode in use
+static enum {
+    PHY_MODE_BEST_AVAILABLE = 0,
+    PHY_MODE_PIO = 1,
+    PHY_MODE_DMA_TIMER = 2,
+    PHY_MODE_GREENPAK_PIO = 3,
+    PHY_MODE_GREENPAK_DMA = 4
+} g_scsi_phy_mode;
+static const char *g_scsi_phy_mode_names[] = {
+    "Unknown", "PIO", "DMA_TIMER", "GREENPAK_PIO", "GREENPAK_DMA"
+};
+
+// State of polling write request
+static struct {
+    const uint8_t *data;
+    uint32_t count;
+    bool use_sync_mode;
+} g_scsi_writereq;
+
+static void init_irqs();
+
+/***********************/
+/* SCSI status signals */
+/***********************/
+
+extern "C" bool scsiStatusATN()
+{
+    return SCSI_IN(ATN);
+}
+
+extern "C" bool scsiStatusBSY()
+{
+    return SCSI_IN(BSY);
+}
+
+/************************/
+/* SCSI selection logic */
+/************************/
+
+volatile uint8_t g_scsi_sts_selection;
+volatile uint8_t g_scsi_ctrl_bsy;
+
+static void scsi_bsy_deassert_interrupt()
+{
+    if (SCSI_IN(SEL) && !SCSI_IN(BSY))
+    {
+        uint8_t sel_bits = SCSI_IN_DATA();
+        int sel_id = -1;
+        for (int i = 0; i < S2S_MAX_TARGETS; i++)
+        {
+            if (scsiDev.targets[i].targetId <= 7 && scsiDev.targets[i].cfg)
+            {
+                if (sel_bits & (1 << scsiDev.targets[i].targetId))
+                {
+                    sel_id = scsiDev.targets[i].targetId;
+                    break;
+                }
+            }
+        }
+
+        if (sel_id >= 0)
+        {
+            uint8_t atn_flag = SCSI_IN(ATN) ? SCSI_STS_SELECTION_ATN : 0;
+            g_scsi_sts_selection = SCSI_STS_SELECTION_SUCCEEDED | atn_flag | sel_id;
+        }
+
+        // selFlag is required for Philips P2000C which releases it after 600ns
+        // without waiting for BSY.
+        // Also required for some early Mac Plus roms
+        scsiDev.selFlag = *SCSI_STS_SELECTED;
+    }
+}
+
+extern "C" bool scsiStatusSEL()
+{
+    if (g_scsi_ctrl_bsy)
+    {
+        // We don't have direct register access to BSY bit like SCSI2SD scsi.c expects.
+        // Instead update the state here.
+        // Releasing happens with bus release.
+        g_scsi_ctrl_bsy = 0;
+        SCSI_OUT(BSY, 1);
+    }
+
+    return SCSI_IN(SEL);
+}
+
+/************************/
+/* SCSI bus reset logic */
+/************************/
+
+static void scsi_rst_assert_interrupt()
+{
+    bool rst1 = SCSI_IN(RST);
+    delay_ns(500);
+    bool rst2 = SCSI_IN(RST);
+
+    if (rst1 && rst2)
+    {
+        dbgmsg("BUS RESET");
+        scsiDev.resetFlag = 1;
+    }
+}
+
+static void selectPhyMode()
+{
+    int oldmode = g_scsi_phy_mode;
+
+    int default_mode = PHY_MODE_BEST_AVAILABLE;
+
+    // Read overriding setting from configuration file
+    int wanted_mode = ini_getl("SCSI", "PhyMode", default_mode, CONFIGFILE);
+
+    // Default: software GPIO bitbang, available on all revisions
+    g_scsi_phy_mode = PHY_MODE_PIO;
+    
+    // Timer based DMA bitbang, available on V1.1, 2.8 MB/s
+#ifdef SCSI_ACCEL_DMA_AVAILABLE
+    if (wanted_mode == PHY_MODE_BEST_AVAILABLE || wanted_mode == PHY_MODE_DMA_TIMER)
+    {
+        g_scsi_phy_mode = PHY_MODE_DMA_TIMER;
+    }
+#endif
+
+    // GreenPAK with software write, available on V1.1 with extra chip, 3.5 MB/s
+    if (wanted_mode == PHY_MODE_BEST_AVAILABLE || wanted_mode == PHY_MODE_GREENPAK_PIO)
+    {
+        if (greenpak_is_ready())
+        {
+            g_scsi_phy_mode = PHY_MODE_GREENPAK_PIO;
+        }
+    }
+
+    // GreenPAK with DMA write, available on V1.1 with extra chip
+#ifdef SCSI_ACCEL_DMA_AVAILABLE
+    if (wanted_mode == PHY_MODE_BEST_AVAILABLE || wanted_mode == PHY_MODE_GREENPAK_DMA)
+    {
+        if (greenpak_is_ready())
+        {
+            g_scsi_phy_mode = PHY_MODE_GREENPAK_DMA;
+        }
+    }
+#endif
+
+    if (g_scsi_phy_mode != oldmode)
+    {
+        logmsg("SCSI PHY operating mode: ", g_scsi_phy_mode_names[g_scsi_phy_mode]);
+    }
+}
+
+extern "C" void scsiPhyReset(void)
+{
+    SCSI_RELEASE_OUTPUTS();
+    scsi_accel_dma_stopWrite();
+
+    g_scsi_sts_selection = 0;
+    g_scsi_ctrl_bsy = 0;
+    g_scsi_writereq.count = 0;
+    init_irqs();
+
+#ifdef SCSI_SYNC_MODE_AVAILABLE
+    scsi_accel_sync_init();
+#endif
+
+    selectPhyMode();
+
+    if (g_scsi_phy_mode == PHY_MODE_DMA_TIMER)
+    {
+        scsi_accel_timer_dma_init();
+    }
+    else if (g_scsi_phy_mode == PHY_MODE_GREENPAK_DMA)
+    {
+        scsi_accel_greenpak_dma_init();
+    }
+}
+
+/************************/
+/* SCSI bus phase logic */
+/************************/
+
+static SCSI_PHASE g_scsi_phase;
+
+extern "C" void scsiEnterPhase(int phase)
+{
+    int delay = scsiEnterPhaseImmediate(phase);
+    if (delay > 0)
+    {
+        s2s_delay_ns(delay);
+    }
+}
+
+// Change state and return nanosecond delay to wait
+extern "C" uint32_t scsiEnterPhaseImmediate(int phase)
+{
+    if (phase != g_scsi_phase)
+    {
+        // ANSI INCITS 362-2002 SPI-3 10.7.1:
+        // Phase changes are not allowed while REQ or ACK is asserted.
+        while (likely(!scsiDev.resetFlag) && SCSI_IN(ACK)) {}
+
+        if (scsiDev.compatMode < COMPAT_SCSI2 && (phase == DATA_IN || phase == DATA_OUT))
+        {
+            // Akai S1000/S3000 seems to need extra delay before changing to data phase
+            // after a command. The code in ZuluSCSI_disk.cpp tries to do this while waiting
+            // for SD card, to avoid any extra latency.
+            s2s_delay_ns(400000);
+        }
+
+        int oldphase = g_scsi_phase;
+        g_scsi_phase = (SCSI_PHASE)phase;
+        scsiLogPhaseChange(phase);
+        
+        if (phase < 0)
+        {
+            // Other communication on bus or reset state
+            SCSI_RELEASE_OUTPUTS();
+            return 0;
+        }
+        else
+        {
+            SCSI_OUT(MSG, phase & __scsiphase_msg);
+            SCSI_OUT(CD,  phase & __scsiphase_cd);
+            SCSI_OUT(IO,  phase & __scsiphase_io);
+        
+            int delayNs = 400; // Bus settle delay
+            if ((oldphase & __scsiphase_io) != (phase & __scsiphase_io))
+            {
+                delayNs += 400; // Data release delay
+            }
+
+            if (scsiDev.compatMode < COMPAT_SCSI2)
+            {
+                // EMU EMAX needs 100uS ! 10uS is not enough.
+                delayNs += 100000;
+            }
+
+            return delayNs;
+        }
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+// Release all signals
+void scsiEnterBusFree(void)
+{
+    g_scsi_phase = BUS_FREE;
+    g_scsi_sts_selection = 0;
+    g_scsi_ctrl_bsy = 0;
+    scsiDev.cdbLen = 0;
+    
+    SCSI_RELEASE_OUTPUTS();
+}
+
+/********************/
+/* Transmit to host */
+/********************/
+
+#define SCSI_WAIT_ACTIVE(pin) \
+  if (!SCSI_IN(pin)) { \
+    if (!SCSI_IN(pin)) { \
+      while(!SCSI_IN(pin) && !scsiDev.resetFlag); \
+    } \
+  }
+
+#define SCSI_WAIT_INACTIVE(pin) \
+  if (SCSI_IN(pin)) { \
+    if (SCSI_IN(pin)) { \
+      while(SCSI_IN(pin) && !scsiDev.resetFlag); \
+    } \
+  }
+
+static inline void scsiWriteOneByte(uint8_t value)
+{
+    SCSI_OUT_DATA(value);
+    delay_100ns(); // DB setup time before REQ
+    SCSI_OUT(REQ, 1);
+    SCSI_WAIT_ACTIVE(ACK);
+    SCSI_RELEASE_DATA_REQ(); // Release data and REQ
+    SCSI_WAIT_INACTIVE(ACK);
+}
+
+extern "C" void scsiWriteByte(uint8_t value)
+{
+    scsiLogDataIn(&value, 1);
+    scsiWriteOneByte(value);
+}
+
+extern "C" void scsiWrite(const uint8_t* data, uint32_t count)
+{
+    scsiStartWrite(data, count);
+    scsiFinishWrite();
+}
+
+extern "C" void scsiStartWrite(const uint8_t* data, uint32_t count)
+{
+    scsiLogDataIn(data, count);
+
+    g_scsi_writereq.use_sync_mode = (g_scsi_phase == DATA_IN && scsiDev.target->syncOffset > 0);
+
+    if (g_scsi_phy_mode == PHY_MODE_PIO
+        || g_scsi_phy_mode == PHY_MODE_GREENPAK_PIO
+        || g_scsi_writereq.use_sync_mode)
+    {
+        // Software based bit-banging.
+        // Write requests are queued and then executed in isWriteFinished() callback.
+        // This allows better parallelism with SD card transfers.
+        
+        if (g_scsi_writereq.count)
+        {
+            if (data == g_scsi_writereq.data + g_scsi_writereq.count)
+            {
+                // Combine with previous one
+                g_scsi_writereq.count += count;
+                return;
+            }
+            else
+            {
+                // Actually execute previous request
+                scsiFinishWrite();
+            }
+        }
+
+        g_scsi_writereq.data = data;
+        g_scsi_writereq.count = count;
+    }
+    else if (g_scsi_phy_mode == PHY_MODE_DMA_TIMER || g_scsi_phy_mode == PHY_MODE_GREENPAK_DMA)
+    {
+        // Accelerated writes using DMA and timers
+        scsi_accel_dma_startWrite(data, count, &scsiDev.resetFlag);
+    }
+    else
+    {
+        logmsg("Unknown SCSI PHY mode: ", (int)g_scsi_phy_mode);
+    }
+}
+
+static void processPollingWrite(uint32_t count)
+{
+    if (count > g_scsi_writereq.count)
+        count = g_scsi_writereq.count;
+    
+    const uint8_t *data = g_scsi_writereq.data;
+    uint32_t count_words = count / 4;
+
+    if (g_scsi_writereq.use_sync_mode)
+    {
+        // Synchronous mode transfer
+        scsi_accel_sync_send(data, count, &scsiDev.resetFlag);
+    }
+    else if (count_words * 4 == count)
+    {
+        if (g_scsi_phy_mode == PHY_MODE_GREENPAK_PIO)
+        {
+            // GreenPAK PIO accelerated asynchronous transfer
+            scsi_accel_greenpak_send((const uint32_t*)data, count_words, &scsiDev.resetFlag);
+        }
+        else
+        {
+            // Assembler optimized asynchronous transfer
+            scsi_accel_asm_send((const uint32_t*)data, count_words, &scsiDev.resetFlag);
+        }
+    }
+    else
+    {
+        // Use simple loop for unaligned transfers
+        for (uint32_t i = 0; i < count; i++)
+        {
+            if (scsiDev.resetFlag) break;
+            scsiWriteOneByte(data[i]);
+        }
+    }
+
+    g_scsi_writereq.count -= count;
+    if (g_scsi_writereq.count)
+    {
+        g_scsi_writereq.data += count;
+    }
+    else
+    {
+        g_scsi_writereq.data = NULL;
+    }
+}
+
+static bool isPollingWriteFinished(const uint8_t *data)
+{
+    if (g_scsi_writereq.count)
+    {
+        if (data == NULL)
+        {
+            return false;
+        }
+        else if (data >= g_scsi_writereq.data &&
+            data < g_scsi_writereq.data + g_scsi_writereq.count)
+        {
+            return false;
+        }
+    }
+    return true;
+}
+
+extern "C" bool scsiIsWriteFinished(const uint8_t *data)
+{
+    // Check if there is still a polling transfer in progress
+    if (!isPollingWriteFinished(data) && !check_sd_read_done())
+    {
+        // Process the transfer piece-by-piece while waiting
+        // for SD card to react.
+        int max_count = g_scsi_writereq.count / 8;
+        
+        // Always transfer whole sectors without pause to avoid problems with some SCSI hosts.
+        int bytesPerSector = 512;
+        if (scsiDev.target)
+        {
+            bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
+        }
+        if (max_count % bytesPerSector != 0) max_count -= (max_count % bytesPerSector);
+        if (max_count < bytesPerSector) max_count = bytesPerSector;
+        
+        // Avoid SysTick interrupt pauses during the transfer
+        SysTick_Handle_PreEmptively();
+
+        processPollingWrite(max_count);
+        return isPollingWriteFinished(data);
+    }
+    
+    if (g_scsi_phy_mode == PHY_MODE_DMA_TIMER || g_scsi_phy_mode == PHY_MODE_GREENPAK_DMA)
+    {
+        return scsi_accel_dma_isWriteFinished(data);
+    }
+    else
+    {
+        return true;
+    }
+}
+
+extern "C" void scsiFinishWrite()
+{
+    if (g_scsi_writereq.count)
+    {
+        // Finish previously started polling write request.
+        processPollingWrite(g_scsi_writereq.count);
+    }
+
+    if (g_scsi_phy_mode == PHY_MODE_DMA_TIMER || g_scsi_phy_mode == PHY_MODE_GREENPAK_DMA)
+    {
+        scsi_accel_dma_finishWrite(&scsiDev.resetFlag);
+    }
+}
+
+/*********************/
+/* Receive from host */
+/*********************/
+
+static inline uint8_t scsiReadOneByte(void)
+{
+    SCSI_OUT(REQ, 1);
+    SCSI_WAIT_ACTIVE(ACK);
+    delay_100ns();
+    uint8_t r = SCSI_IN_DATA();
+    SCSI_OUT(REQ, 0);
+    SCSI_WAIT_INACTIVE(ACK);
+
+    return r;
+}
+
+extern "C" uint8_t scsiReadByte(void)
+{
+    uint8_t r = scsiReadOneByte();
+    scsiLogDataOut(&r, 1);
+    return r;
+}
+
+extern "C" void scsiRead(uint8_t* data, uint32_t count, int* parityError)
+{
+    *parityError = 0;
+
+    uint32_t count_words = count / 4;
+    bool use_greenpak = (g_scsi_phy_mode == PHY_MODE_GREENPAK_DMA || g_scsi_phy_mode == PHY_MODE_GREENPAK_PIO);
+
+    SysTick_Handle_PreEmptively();
+
+    if (g_scsi_phase == DATA_OUT && scsiDev.target->syncOffset > 0)
+    {
+        // Synchronous data transfer
+        scsi_accel_sync_recv(data, count, parityError, &scsiDev.resetFlag);
+    }
+    else if (count_words * 4 == count && count_words >= 2 && use_greenpak)
+    {
+        // GreenPAK accelerated receive can handle a multiple of 4 bytes with minimum of 8 bytes.
+        scsi_accel_greenpak_recv((uint32_t*)data, count_words, &scsiDev.resetFlag);
+    }
+    else if (count_words * 4 == count && count_words >= 1)
+    {
+        // Optimized ASM subroutine can handle multiple of 4 bytes with minimum of 4 bytes.
+        scsi_accel_asm_recv((uint32_t*)data, count_words, &scsiDev.resetFlag);
+    }
+    else
+    {
+        // Use a simple loop for short and unaligned transfers
+        for (uint32_t i = 0; i < count; i++)
+        {
+            if (scsiDev.resetFlag) break;
+
+            data[i] = scsiReadOneByte();
+        }
+    }
+
+    scsiLogDataOut(data, count);
+}
+
+/**********************/
+/* Interrupt handlers */
+/**********************/
+
+extern "C"
+void SCSI_RST_IRQ (void)
+{
+    if (exti_interrupt_flag_get(SCSI_RST_EXTI))
+    {
+        exti_interrupt_flag_clear(SCSI_RST_EXTI);
+        scsi_rst_assert_interrupt();
+    }
+
+    if (exti_interrupt_flag_get(SCSI_BSY_EXTI))
+    {
+        exti_interrupt_flag_clear(SCSI_BSY_EXTI);
+        scsi_bsy_deassert_interrupt();
+    }
+
+    if (exti_interrupt_flag_get(SCSI_SEL_EXTI))
+    {
+        // Check BSY line status when SEL goes active.
+        // This is needed to handle SCSI-1 hosts that use the single initiator mode.
+        // The host will just assert the SEL directly, without asserting BSY first.
+        exti_interrupt_flag_clear(SCSI_SEL_EXTI);
+        scsi_bsy_deassert_interrupt();
+    }
+}
+
+#if SCSI_RST_IRQn != SCSI_BSY_IRQn
+extern "C"
+void SCSI_BSY_IRQ (void)
+{
+    SCSI_RST_IRQ();
+}
+#endif
+
+#if (SCSI_SEL_IRQn != SCSI_RST_IRQn) && (SCSI_SEL_IRQn != SCSI_BSY_IRQn)
+extern "C"
+void SCSI_SEL_IRQ (void)
+{
+    SCSI_RST_IRQ();
+}
+#endif
+
+static void init_irqs()
+{
+    // Falling edge of RST pin
+    gpio_mode_set(SCSI_RST_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SCSI_RST_PIN);
+    syscfg_exti_line_config(SCSI_RST_EXTI_SOURCE_PORT, SCSI_RST_EXTI_SOURCE_PIN);
+    exti_init(SCSI_RST_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
+    NVIC_SetPriority(SCSI_RST_IRQn, 1);
+    NVIC_EnableIRQ(SCSI_RST_IRQn);
+
+    // Rising edge of BSY pin
+    gpio_mode_set(SCSI_BSY_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SCSI_BSY_PIN);
+    syscfg_exti_line_config(SCSI_BSY_EXTI_SOURCE_PORT, SCSI_BSY_EXTI_SOURCE_PIN);
+    exti_init(SCSI_BSY_EXTI, EXTI_INTERRUPT, EXTI_TRIG_RISING);
+    NVIC_SetPriority(SCSI_BSY_IRQn, 1);
+    NVIC_EnableIRQ(SCSI_BSY_IRQn);
+
+    // Falling edge of SEL pin
+    gpio_mode_set(SCSI_SEL_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SCSI_SEL_PIN);
+    syscfg_exti_line_config(SCSI_SEL_EXTI_SOURCE_PORT, SCSI_SEL_EXTI_SOURCE_PIN);
+    exti_init(SCSI_SEL_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
+    NVIC_SetPriority(SCSI_SEL_IRQn, 1);
+    NVIC_EnableIRQ(SCSI_SEL_IRQn);
+}
+
+

+ 90 - 0
lib/ZuluSCSI_platform_GD32F450/scsiPhy.h

@@ -0,0 +1,90 @@
+/** 
+ * SCSI2SD V6 - Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * This file is licensed under the GPL version 3 or any later version.  
+ * It is derived from scsiPhy.h in SCSI2SD V6.
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+// Interface to SCSI physical interface.
+// This file is derived from scsiPhy.h in SCSI2SD-V6.
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Read SCSI status signals
+bool scsiStatusATN();
+bool scsiStatusBSY();
+bool scsiStatusSEL();
+
+// Parity not yet implemented
+#define scsiParityError() 0
+
+// Get SCSI selection status.
+// This is latched by interrupt when BSY is deasserted while SEL is asserted.
+// Lowest 3 bits are the selected target id.
+// Highest bits are status information.
+#define SCSI_STS_SELECTION_SUCCEEDED 0x40
+#define SCSI_STS_SELECTION_ATN 0x80
+extern volatile uint8_t g_scsi_sts_selection;
+#define SCSI_STS_SELECTED (&g_scsi_sts_selection)
+extern volatile uint8_t g_scsi_ctrl_bsy;
+#define SCSI_CTRL_BSY (&g_scsi_ctrl_bsy)
+
+// Called when SCSI RST signal has been asserted, should release bus.
+void scsiPhyReset(void);
+
+// Change MSG / CD / IO signal states and wait for necessary transition time.
+// Phase argument is one of SCSI_PHASE enum values.
+void scsiEnterPhase(int phase);
+
+// Change state and return nanosecond delay to wait
+uint32_t scsiEnterPhaseImmediate(int phase);
+
+// Release all signals
+void scsiEnterBusFree(void);
+
+// Blocking data transfer
+void scsiWrite(const uint8_t* data, uint32_t count);
+void scsiRead(uint8_t* data, uint32_t count, int* parityError);
+void scsiWriteByte(uint8_t value);
+uint8_t scsiReadByte(void);
+
+// Non-blocking data transfer.
+// Depending on platform support the start() function may block.
+// The start function can be called multiple times, it may internally
+// either combine transfers or block until previous transfer completes.
+void scsiStartWrite(const uint8_t* data, uint32_t count);
+void scsiFinishWrite();
+
+// Query whether the data at pointer has already been read, i.e. buffer can be reused.
+// If data is NULL, checks if all writes have completed.
+bool scsiIsWriteFinished(const uint8_t *data);
+
+
+#define s2s_getScsiRateKBs() 0
+
+#ifdef __cplusplus
+}
+#endif

+ 173 - 0
lib/ZuluSCSI_platform_GD32F450/scsi_accel_asm.cpp

@@ -0,0 +1,173 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#include "scsi_accel_asm.h"
+#include "ZuluSCSI_platform.h"
+
+// Optimized ASM blocks for the SCSI communication subroutine
+
+// Take 8 bits from d and format them for writing
+// d is name of data operand, b is bit offset, x is unique label
+#define ASM_LOAD_DATA(d, b, x) \
+"    load_data1_" x "_%=: \n" \
+"        ubfx    %[tmp1], %[" d "], #" b ", #8 \n" \
+"        ldr     %[tmp1], [%[byte_lookup], %[tmp1], lsl #2] \n"
+
+// Write data to SCSI port and set REQ high
+#define ASM_SEND_DATA(x) \
+"    send_data" x "_%=: \n" \
+"        str     %[tmp1], [%[out_port_bop]] \n"
+
+// Read data from SCSI port, set REQ high, store data to d at bit offset b
+#define ASM_RECV_DATA(d, b, x) \
+"    recv_data" x "_%=: \n" \
+"        ldr     %[tmp1], [%[in_port_istat]] \n" \
+"        mov     %[tmp2], %[req_high_bop] \n" \
+"        str     %[tmp2], [%[out_port_bop]] \n" \
+"        ubfx    %[tmp1], %[tmp1], %[data_in_shift], #8 \n" \
+"        bfi     %[" d "], %[tmp1], #" b ", #8 \n"
+
+// Wait for ACK to be high, set REQ low, wait ACK low
+#define ASM_HANDSHAKE(x) \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        str     %[tmp2], [%[req_pin_bb]] \n" \
+"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        str     %[tmp2], [%[req_pin_bb]] \n" \
+"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        str     %[tmp2], [%[req_pin_bb]] \n" \
+"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
+"    wait_ack_inactive" x "_%=: \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        str     %[tmp2], [%[req_pin_bb]] \n" \
+"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
+"        ldr     %[tmp2], [%[reset_flag]] \n" \
+"        cbnz    %[tmp2], req_is_low_now" x "_%= \n" \
+"        b.n     wait_ack_inactive" x "_%= \n" \
+"    req_is_low_now" x "_%=: \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
+"    wait_ack_active" x "_%=: \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], over_ack_active" x "_%= \n" \
+"        ldr     %[tmp2], [%[reset_flag]] \n" \
+"        cbnz    %[tmp2], over_ack_active" x "_%= \n" \
+"        b.n     wait_ack_active" x "_%= \n" \
+"    over_ack_active" x "_%=: \n" \
+
+// Send bytes to SCSI bus using the asynchronous handshake mechanism
+// Takes 4 bytes at a time for sending from buf.
+void scsi_accel_asm_send(const uint32_t *buf, uint32_t num_words, volatile int *resetFlag)
+{
+    volatile uint32_t *out_port_bop = (volatile uint32_t*)&GPIO_BOP(SCSI_OUT_PORT);
+    const uint32_t *byte_lookup = g_scsi_out_byte_to_bop;
+    uint32_t ack_pin_bb = PERIPH_BB_BASE + (((uint32_t)&GPIO_ISTAT(SCSI_ACK_PORT)) - APB1_BUS_BASE) * 32 + SCSI_IN_ACK_IDX * 4;
+    uint32_t req_pin_bb = PERIPH_BB_BASE + (((uint32_t)out_port_bop) - APB1_BUS_BASE) * 32 + (SCSI_OUT_REQ_IDX + 16) * 4;
+    register uint32_t tmp1 = 0;
+    register uint32_t tmp2 = 0;
+    register uint32_t data = 0;
+
+    asm volatile (
+    "   ldr      %[data], [%[buf]], #4 \n" \
+        ASM_LOAD_DATA("data", "0", "first")
+
+    "inner_loop_%=: \n" \
+        ASM_SEND_DATA("0")
+        ASM_LOAD_DATA("data", "8", "8")
+        ASM_HANDSHAKE("0")
+        
+        ASM_SEND_DATA("8")
+        ASM_LOAD_DATA("data", "16", "16")
+        ASM_HANDSHAKE("8")
+
+        ASM_SEND_DATA("16")
+        ASM_LOAD_DATA("data", "24", "24")
+        ASM_HANDSHAKE("16")
+
+        ASM_SEND_DATA("24")
+    "   ldr      %[data], [%[buf]], #4 \n" \
+        ASM_LOAD_DATA("data", "0", "0")
+        ASM_HANDSHAKE("24")
+
+    "   subs     %[num_words], %[num_words], #1 \n" \
+    "   bne     inner_loop_%= \n"
+    : /* Output */ [tmp1] "+l" (tmp1), [tmp2] "+l" (tmp2), [data] "+r" (data),
+                   [buf] "+r" (buf), [num_words] "+r" (num_words)
+    : /* Input */ [ack_pin_bb] "r" (ack_pin_bb),
+                  [req_pin_bb] "r" (req_pin_bb),
+                  [out_port_bop] "r"(out_port_bop),
+                  [byte_lookup] "r" (byte_lookup),
+                  [reset_flag] "r" (resetFlag)
+    : /* Clobber */ );
+
+    SCSI_RELEASE_DATA_REQ();
+}
+
+// Read bytes from SCSI bus using asynchronous handshake mechanism
+// Takes 4 bytes at a time.
+void scsi_accel_asm_recv(uint32_t *buf, uint32_t num_words, volatile int *resetFlag)
+{
+    volatile uint32_t *out_port_bop = (volatile uint32_t*)&GPIO_BOP(SCSI_OUT_PORT);
+    volatile uint32_t *in_port_istat = (volatile uint32_t*)&GPIO_ISTAT(SCSI_IN_PORT);
+    uint32_t ack_pin_bb = PERIPH_BB_BASE + (((uint32_t)&GPIO_ISTAT(SCSI_ACK_PORT)) - APB1_BUS_BASE) * 32 + SCSI_IN_ACK_IDX * 4;
+    uint32_t req_pin_bb = PERIPH_BB_BASE + (((uint32_t)out_port_bop) - APB1_BUS_BASE) * 32 + (SCSI_OUT_REQ_IDX + 16) * 4;
+    register uint32_t tmp1 = 0;
+    register uint32_t tmp2 = 0;
+    register uint32_t data = 0;
+
+    asm volatile (
+    "inner_loop_%=: \n" \
+        ASM_HANDSHAKE("0")
+        ASM_RECV_DATA("data", "0", "0")
+        
+        ASM_HANDSHAKE("8")
+        ASM_RECV_DATA("data", "8", "8")
+        
+        ASM_HANDSHAKE("16")
+        ASM_RECV_DATA("data", "16", "16")
+        
+        ASM_HANDSHAKE("24")
+        ASM_RECV_DATA("data", "24", "24")
+
+    "   mvn      %[data], %[data] \n" \
+    "   str      %[data], [%[buf]], #4 \n" \
+    "   subs     %[num_words], %[num_words], #1 \n" \
+    "   bne     inner_loop_%= \n"
+    : /* Output */ [tmp1] "+l" (tmp1), [tmp2] "+l" (tmp2), [data] "+r" (data),
+                   [buf] "+r" (buf), [num_words] "+r" (num_words)
+    : /* Input */ [ack_pin_bb] "r" (ack_pin_bb),
+                  [req_pin_bb] "r" (req_pin_bb),
+                  [out_port_bop] "r"(out_port_bop),
+                  [in_port_istat] "r" (in_port_istat),
+                  [reset_flag] "r" (resetFlag),
+                  [data_in_shift] "I" (SCSI_IN_SHIFT),
+                  [req_high_bop] "I" (SCSI_OUT_REQ)
+    : /* Clobber */ );
+
+    SCSI_RELEASE_DATA_REQ();
+}

+ 32 - 0
lib/ZuluSCSI_platform_GD32F450/scsi_accel_asm.h

@@ -0,0 +1,32 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+// SCSI subroutines using hand-optimized assembler
+
+#pragma once
+
+#include <stdint.h>
+
+/*!< Peripheral base address in the bit-band region for a cortex M4 */
+#define PERIPH_BB_BASE        ((uint32_t)0x42000000)    
+
+void scsi_accel_asm_send(const uint32_t *buf, uint32_t num_words, volatile int *resetFlag);
+void scsi_accel_asm_recv(uint32_t *buf, uint32_t num_words, volatile int *resetFlag);

+ 787 - 0
lib/ZuluSCSI_platform_GD32F450/scsi_accel_dma.cpp

@@ -0,0 +1,787 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#include "scsi_accel_dma.h"
+#include <ZuluSCSI_log.h>
+#include <gd32f4xx_timer.h>
+#include <gd32f4xx_rcu.h>
+#include <gd32f4xx_gpio.h>
+#include <assert.h>
+#include <string.h>
+
+#ifndef SCSI_ACCEL_DMA_AVAILABLE
+
+void scsi_accel_timer_dma_init() {}
+void scsi_accel_greenpak_dma_init() {}
+void scsi_accel_dma_startWrite(const uint8_t* data, uint32_t count, volatile int *resetFlag) {}
+void scsi_accel_dma_stopWrite() {}
+void scsi_accel_dma_finishWrite(volatile int *resetFlag) {}
+bool scsi_accel_dma_isWriteFinished(const uint8_t* data) { return true; }
+
+
+#else
+
+static void greenpak_refill_dmabuf();
+static void greenpak_start_dma();
+static void greenpak_stop_dma();
+
+enum greenpak_state_t { GREENPAK_IO1_LOW = 0, GREENPAK_IO1_HIGH, GREENPAK_STOP};
+
+#define DMA_BUF_SIZE 256
+#define DMA_BUF_MASK (DMA_BUF_SIZE - 1)
+static struct {
+    uint8_t *app_buf; // Buffer provided by application
+    uint32_t dma_buf[DMA_BUF_SIZE]; // Buffer of data formatted for GPIO BOP register
+    uint32_t dma_idx; // Write index to DMA buffer
+    uint32_t dma_fillto; // Point up to which DMA buffer is available for refilling
+    uint32_t timer_buf; // Control value for timer SWEVG register
+    uint32_t bytes_app; // Bytes available in application buffer
+    uint32_t bytes_dma; // Bytes (words) written so far to DMA buffer
+    uint32_t scheduled_dma; // Bytes (words) that DMA data count was last set to
+    greenpak_state_t greenpak_state; // Toggle signal state for greenpak
+    bool incomplete_buf; // Did not have enough data to fill DMA buffer
+
+    uint8_t *next_app_buf; // Next buffer from application after current one finishes
+    uint32_t next_app_bytes; // Bytes in next buffer
+} g_scsi_dma;
+
+enum scsidma_state_t { SCSIDMA_IDLE = 0, SCSIDMA_WRITE };
+static volatile scsidma_state_t g_scsi_dma_state;
+static bool g_scsi_dma_use_greenpak;
+
+void scsi_accel_timer_dma_init()
+{
+    g_scsi_dma_state = SCSIDMA_IDLE;
+    g_scsi_dma_use_greenpak = false;
+    rcu_periph_clock_enable(SCSI_TIMER_RCU);
+    rcu_periph_clock_enable(SCSI_TIMER_DMA_RCU);
+
+    // DMA Channel A: data copy
+    // GPIO DMA copies data from memory buffer to GPIO BOP register.
+    // The memory buffer is filled by interrupt routine.
+    dma_multi_data_parameter_struct gpio_dma_config =
+    {
+        .periph_addr = (uint32_t)&GPIO_BOP(SCSI_OUT_PORT),
+        .periph_width = DMA_PERIPH_WIDTH_32BIT,
+        .periph_inc = DMA_PERIPH_INCREASE_DISABLE,
+        .memory0_addr = 0, // Filled before transfer
+        .memory_width = DMA_MEMORY_WIDTH_32BIT,
+        .memory_inc = DMA_MEMORY_INCREASE_ENABLE,
+        .memory_burst_width = DMA_MEMORY_BURST_SINGLE,
+        .periph_burst_width = DMA_PERIPH_BURST_SINGLE,
+        .critical_value = DMA_FIFO_1_WORD,
+        .circular_mode = DMA_CIRCULAR_MODE_ENABLE,
+        .direction = DMA_MEMORY_TO_PERIPH,
+        .number = DMA_BUF_SIZE,
+        .priority = DMA_PRIORITY_ULTRA_HIGH
+    };
+
+    dma_multi_data_mode_init(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, &gpio_dma_config);
+    dma_channel_subperipheral_select(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, SCSI_TIMER_DMACHA_SUB_PERIPH);
+    NVIC_SetPriority(SCSI_TIMER_DMACHA_IRQn, 1);
+    NVIC_EnableIRQ(SCSI_TIMER_DMACHA_IRQn);
+
+    // DMA Channel B: timer update
+    // Timer DMA causes update event to restart timer after
+    // GPIO DMA operation is done.
+    dma_multi_data_parameter_struct timer_dma_config =
+    {
+        .periph_addr = (uint32_t)&TIMER_SWEVG(SCSI_TIMER),
+        .periph_width = DMA_PERIPH_WIDTH_32BIT,
+        .periph_inc = DMA_PERIPH_INCREASE_DISABLE,
+        .memory0_addr = (uint32_t)&g_scsi_dma.timer_buf,
+        .memory_width = DMA_MEMORY_WIDTH_32BIT,
+        .memory_inc = DMA_PERIPH_INCREASE_DISABLE,
+        .memory_burst_width = DMA_MEMORY_BURST_SINGLE,
+        .periph_burst_width = DMA_PERIPH_BURST_SINGLE,
+        .critical_value = DMA_FIFO_1_WORD,
+        .circular_mode = DMA_CIRCULAR_MODE_DISABLE,
+        .direction = DMA_MEMORY_TO_PERIPH,
+        .number = DMA_BUF_SIZE,
+        .priority = DMA_PRIORITY_HIGH
+
+    };
+    dma_multi_data_mode_init(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, &timer_dma_config);
+    dma_channel_subperipheral_select(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, SCSI_TIMER_DMACHB_SUB_PERIPH);
+    NVIC_SetPriority(SCSI_TIMER_DMACHB_IRQn, 2);
+    NVIC_EnableIRQ(SCSI_TIMER_DMACHB_IRQn);
+
+    g_scsi_dma.timer_buf = TIMER_SWEVG_UPG;
+
+    // Timer is used to toggle the request signal based on external trigger input.
+    // OUT_REQ is driven by timer output.
+    // 1. On timer update event, REQ is set low.
+    // 2. When ACK goes low, timer counts and OUT_REQ is set high.
+    //    Simultaneously a DMA request is triggered to write next data to GPIO.
+    // 3. When ACK goes high, a DMA request is triggered to cause timer update event.
+    //    The DMA request priority is set so that 2. always completes before it.
+    TIMER_CTL0(SCSI_TIMER) = 0;
+    TIMER_SMCFG(SCSI_TIMER) = TIMER_SLAVE_MODE_EXTERNAL0 | TIMER_SMCFG_TRGSEL_CI0F_ED;
+    TIMER_CAR(SCSI_TIMER) = 65535;
+    TIMER_PSC(SCSI_TIMER) = 0;
+    TIMER_DMAINTEN(SCSI_TIMER) = 0;
+    TIMER_CHCTL0(SCSI_TIMER) = 0x6001; // CH0 as input, CH1 as DMA trigger
+    TIMER_CHCTL1(SCSI_TIMER) = 0x6074; // CH2 as fast PWM output, CH3 as DMA trigger
+    TIMER_CHCTL2(SCSI_TIMER) = TIMER_CHCTL2_CH2NEN;
+    TIMER_CCHP(SCSI_TIMER) = TIMER_CCHP_POEN;
+    TIMER_CH1CV(SCSI_TIMER) = 1; // Copy data when ACK goes low
+    TIMER_CH2CV(SCSI_TIMER) = 1; // REQ is low until ACK goes low
+    TIMER_CH3CV(SCSI_TIMER) = 2; // Reset timer after ACK goes high & previous DMA is complete
+    gpio_mode_set(SCSI_TIMER_IN_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SCSI_TIMER_IN_PIN);
+    gpio_af_set(SCSI_TIMER_IN_PORT, SCSI_TIMER_IN_AF, SCSI_TIMER_IN_PIN);   
+    scsi_accel_dma_stopWrite();
+}
+
+// Select whether OUT_REQ is connected to timer or GPIO port
+static void scsi_dma_gpio_config(bool enable)
+{
+    if (enable)
+    {
+        //gpio_init(SCSI_OUT_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, SCSI_OUT_REQ);
+
+        gpio_mode_set(SCSI_OUT_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, SCSI_OUT_REQ);
+        
+
+
+        if (g_scsi_dma_use_greenpak)
+        {
+            GPIO_BC(SCSI_OUT_PORT) = GREENPAK_PLD_IO1;
+            GPIO_BOP(SCSI_OUT_PORT) = GREENPAK_PLD_IO2;
+        }
+        else
+        {
+            gpio_mode_set(SCSI_TIMER_OUT_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SCSI_TIMER_OUT_PIN);
+            // @TODO determine if the output should be set to 200MHZ instead of 50MHZ
+            gpio_output_options_set(SCSI_TIMER_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SCSI_TIMER_OUT_PIN);
+            gpio_af_set(SCSI_TIMER_OUT_PORT, SCSI_TIMER_OUT_AF, SCSI_TIMER_OUT_PIN);
+        }
+    }
+    else
+    {
+        GPIO_BC(SCSI_OUT_PORT) = GREENPAK_PLD_IO2;
+        gpio_mode_set(SCSI_TIMER_OUT_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SCSI_TIMER_OUT_PIN);
+        gpio_output_options_set(SCSI_TIMER_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SCSI_TIMER_OUT_PIN);
+        
+        gpio_mode_set(SCSI_OUT_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SCSI_OUT_REQ);
+        gpio_output_options_set(SCSI_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SCSI_OUT_REQ);
+    }
+}
+
+// Convert input bytes into BOP values in the DMA buffer
+static void refill_dmabuf()
+{
+    if (g_scsi_dma_use_greenpak)
+    {
+        greenpak_refill_dmabuf();
+        return;
+    }
+
+    // Check how many bytes we have available from the application
+    uint32_t count = g_scsi_dma.bytes_app - g_scsi_dma.bytes_dma;
+    
+    // Check amount of free space in DMA buffer
+    uint32_t max = g_scsi_dma.dma_fillto - g_scsi_dma.dma_idx;
+    if (count > max) count = max;
+    if (count == 0) return;
+
+    uint8_t *src = g_scsi_dma.app_buf + g_scsi_dma.bytes_dma;
+    uint32_t *dst = g_scsi_dma.dma_buf;
+    uint32_t pos = g_scsi_dma.dma_idx;
+    uint32_t end = pos + count;
+    g_scsi_dma.dma_idx = end;
+    g_scsi_dma.bytes_dma += count;
+
+    while (pos + 4 <= end)
+    {
+        uint32_t input = *(uint32_t*)src;
+        src += 4;
+
+        dst[(pos++) & DMA_BUF_MASK] = g_scsi_out_byte_to_bop[(input >> 0) & 0xFF];
+        dst[(pos++) & DMA_BUF_MASK] = g_scsi_out_byte_to_bop[(input >> 8) & 0xFF];
+        dst[(pos++) & DMA_BUF_MASK] = g_scsi_out_byte_to_bop[(input >> 16) & 0xFF];
+        dst[(pos++) & DMA_BUF_MASK] = g_scsi_out_byte_to_bop[(input >> 24) & 0xFF];
+    }
+
+    while (pos < end)
+    {
+        dst[(pos++) & DMA_BUF_MASK] = g_scsi_out_byte_to_bop[*src++];
+    }
+
+    if (end < g_scsi_dma.dma_fillto)
+    {
+        // Partial buffer fill, this will get refilled from interrupt if we
+        // get more data. Set next byte to an invalid parity value so that
+        // any race conditions will get caught as parity error.
+        dst[pos & DMA_BUF_MASK] = g_scsi_out_byte_to_bop[0] ^ SCSI_OUT_DBP;
+    }
+}
+
+// Start DMA transfer
+static void start_dma()
+{
+    if (g_scsi_dma_use_greenpak)
+    {
+        greenpak_start_dma();
+        return;
+    }
+
+    // Disable channels while configuring
+    dma_channel_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA);
+    dma_channel_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB);
+    TIMER_CTL0(SCSI_TIMER) = 0;
+
+    // Set new buffer address and size
+    // CHA / Data channel is in circular mode and always has DMA_BUF_SIZE buffer size.
+    // CHB / Update channel limits the number of data.
+    dma_memory_address_config(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_MEMORY_0, (uint32_t)g_scsi_dma.dma_buf);
+    dma_transfer_number_config(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_BUF_SIZE);
+   
+    uint32_t dma_to_schedule = g_scsi_dma.bytes_app - g_scsi_dma.scheduled_dma;
+
+    dma_transfer_number_config(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, dma_to_schedule);
+    g_scsi_dma.scheduled_dma += dma_to_schedule;
+    
+    // Clear pending DMA events
+    TIMER_DMAINTEN(SCSI_TIMER) = 0;
+    TIMER_DMAINTEN(SCSI_TIMER) = TIMER_DMAINTEN_CH1DEN | TIMER_DMAINTEN_CH3DEN;
+
+    // Clear and enable interrupt
+    dma_interrupt_flag_clear(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_FLAG_HTF | DMA_FLAG_FTF | DMA_FLAG_FEE);
+    dma_interrupt_flag_clear(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, DMA_FLAG_HTF | DMA_FLAG_FTF | DMA_FLAG_FEE);
+    dma_interrupt_enable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_CHXCTL_FTFIE | DMA_CHXCTL_HTFIE);
+    dma_interrupt_enable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, DMA_CHXCTL_FTFIE);
+
+    // Enable channels
+    dma_channel_enable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA);
+    dma_channel_enable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB);
+
+    // Make sure REQ is initially high
+    TIMER_CNT(SCSI_TIMER) = 16;
+    TIMER_CHCTL1(SCSI_TIMER) = 0x6050;
+    TIMER_CHCTL1(SCSI_TIMER) = 0x6074;
+
+    // Enable timer
+    timer_enable(SCSI_TIMER);
+
+    // Generate first events
+    TIMER_SWEVG(SCSI_TIMER) = TIMER_SWEVG_CH1G;
+    TIMER_SWEVG(SCSI_TIMER) = TIMER_SWEVG_CH3G;
+}
+
+// Stop DMA transfer
+static void stop_dma()
+{
+    greenpak_stop_dma();
+
+    dma_channel_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA);
+    // DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA) &= ~DMA_CHXCTL_CHEN;
+    dma_channel_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB);
+    // DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB) &= ~DMA_CHXCTL_CHEN;
+    dma_interrupt_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_CHXCTL_FTFIE | DMA_CHXCTL_HTFIE);
+    // DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA) &= ~(DMA_CHXCTL_FTFIE | DMA_CHXCTL_HTFIE);
+    dma_interrupt_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, DMA_CHXCTL_FTFIE);
+    //DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB) &= ~DMA_CHXCTL_FTFIE;
+
+    // Wait for ACK of the last byte
+    volatile int timeout = 10000;
+    while (TIMER_CNT(SCSI_TIMER) < 2 && timeout > 0)
+    {
+        timeout--;
+    }
+
+    timer_disable(SCSI_TIMER);
+    g_scsi_dma_state = SCSIDMA_IDLE;
+    SCSI_RELEASE_DATA_REQ();
+
+}
+
+static void check_dma_next_buffer()
+{
+    // Check if we are at the end of the application buffer
+    if (g_scsi_dma.next_app_buf && g_scsi_dma.bytes_dma == g_scsi_dma.bytes_app)
+    {
+        // Switch to next buffer
+        assert(g_scsi_dma.scheduled_dma == g_scsi_dma.bytes_app);
+        g_scsi_dma.app_buf = g_scsi_dma.next_app_buf;
+        g_scsi_dma.bytes_app = g_scsi_dma.next_app_bytes;
+        g_scsi_dma.bytes_dma = 0;
+        g_scsi_dma.scheduled_dma = 0;
+        g_scsi_dma.next_app_buf = 0;
+        g_scsi_dma.next_app_bytes = 0;
+        refill_dmabuf();
+    }
+}
+
+// Convert new data from application buffer to DMA buffer
+extern "C" void SCSI_TIMER_DMACHA_IRQ()
+{
+    // dbgmsg("DMA irq A, counts: ", DMA_CHCNT(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA), " ",
+    //             DMA_CHCNT(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB), " ",
+    //             TIMER_CNT(SCSI_TIMER));
+
+ 
+   uint32_t intf0 = DMA_INTF0(SCSI_TIMER_DMA);
+    uint32_t intf1 = DMA_INTF1(SCSI_TIMER_DMA);
+
+    if (dma_interrupt_flag_get(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_FLAG_HTF))
+    {
+        if (dma_interrupt_flag_get(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_FLAG_FTF))
+        {
+            logmsg("ERROR: SCSI DMA overrun: intf0 :", intf0,
+               " intf1: ", intf1,
+               " bytes_app: ", g_scsi_dma.bytes_app,
+               " bytes_dma: ", g_scsi_dma.bytes_dma,
+               " dma_idx: ", g_scsi_dma.dma_idx,
+               " sched_dma: ", g_scsi_dma.scheduled_dma);
+            stop_dma();
+            return;
+        }
+
+        dma_interrupt_flag_clear(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_FLAG_HTF);
+        g_scsi_dma.dma_fillto += DMA_BUF_SIZE / 2;
+    }
+    else if (dma_interrupt_flag_get(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_FLAG_FTF))
+    {
+        dma_interrupt_flag_clear(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_FLAG_FTF);
+        g_scsi_dma.dma_fillto += DMA_BUF_SIZE / 2;
+    }
+
+    if (!g_scsi_dma.incomplete_buf)
+    {
+        // Fill DMA buffer with data from current application buffer
+        refill_dmabuf();
+
+        check_dma_next_buffer();
+    }
+
+    if (g_scsi_dma.dma_idx < g_scsi_dma.dma_fillto)
+    {
+        // We weren't able to fill the DMA buffer completely during the interrupt.
+        // This can cause DMA to prefetch words that have not yet been written.
+        // The DMA CHA must be restarted in CHB interrupt handler.
+        g_scsi_dma.incomplete_buf = true;
+    }
+}
+
+// Check if enough data is available to continue DMA transfer
+extern "C" void SCSI_TIMER_DMACHB_IRQ()
+{
+    if (dma_interrupt_flag_get(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, DMA_FLAG_FTF))
+    {
+        dma_interrupt_flag_clear(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, DMA_FLAG_FTF);
+
+        if (g_scsi_dma.bytes_app > g_scsi_dma.scheduled_dma)
+        {
+            if (g_scsi_dma.incomplete_buf)
+            {
+                // Previous request didn't have a complete buffer worth of data.
+                // The multiword DMA has already loaded next bytes from RAM, so we need
+                // to reinitialize DMA channel A.
+                __disable_irq();
+                dma_channel_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA);
+                dma_memory_address_config(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_MEMORY_0, (uint32_t)g_scsi_dma.dma_buf);
+                dma_transfer_number_config(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_BUF_SIZE);
+
+                g_scsi_dma.bytes_dma = g_scsi_dma.scheduled_dma;
+                g_scsi_dma.dma_idx = 0;
+                g_scsi_dma.dma_fillto = DMA_BUF_SIZE;
+                g_scsi_dma.incomplete_buf = false;
+                refill_dmabuf();
+
+                // Enable channel and generate event to transfer first byte
+                dma_interrupt_flag_clear(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, DMA_FLAG_HTF | DMA_FLAG_FTF | DMA_FLAG_FEE);
+                dma_channel_enable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA);
+                TIMER_SWEVG(SCSI_TIMER) = TIMER_SWEVG_CH1G;
+                __enable_irq();
+            }
+
+            // Verify the first byte of the new data has been written to outputs
+            // It may have been updated after the DMA write occurred.
+/*  @TODO Is this still needed?
+            __disable_irq();
+            uint32_t first_data_idx = g_scsi_dma.dma_idx - (g_scsi_dma.bytes_dma - g_scsi_dma.scheduled_dma);
+            uint32_t first_data = g_scsi_dma.dma_buf[first_data_idx & DMA_BUF_MASK];
+            GPIO_BOP(SCSI_OUT_PORT) = first_data;
+            __enable_irq();
+*/
+            // Update the total number of bytes available for DMA
+            uint32_t dma_to_schedule = g_scsi_dma.bytes_app - g_scsi_dma.scheduled_dma;
+            dma_channel_disable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB);
+            dma_transfer_number_config(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, dma_to_schedule);
+            dma_channel_enable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB);
+            g_scsi_dma.scheduled_dma += dma_to_schedule;
+        }
+        else
+        {
+            // No more data available
+            stop_dma();
+        }
+    }
+
+
+
+    // dbgmsg("DMA irq B, counts: ", DMA_CHCNT(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA), " ",
+    //             DMA_CHCNT(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB), " ",
+    //             TIMER_CNT(SCSI_TIMER));
+    if (dma_interrupt_flag_get(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, DMA_FLAG_FTF))
+    {
+        dma_interrupt_flag_clear(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB, DMA_FLAG_FTF);
+
+        if (g_scsi_dma.bytes_app > g_scsi_dma.scheduled_dma)
+        {
+            if (g_scsi_dma.dma_idx < g_scsi_dma.dma_fillto)
+            {
+                // Previous request didn't have a complete buffer worth of data.
+                // Refill the buffer and ensure that the first byte of the new data gets
+                // written to outputs.
+                __disable_irq();
+                refill_dmabuf();
+                __enable_irq();
+            }
+
+
+
+            // Update the total number of bytes available for DMA
+            uint32_t dma_to_schedule = g_scsi_dma.bytes_app - g_scsi_dma.scheduled_dma;
+            DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB) &= ~DMA_CHXCTL_CHEN;
+            DMA_CHCNT(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB) = dma_to_schedule;
+            DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB) |= DMA_CHXCTL_CHEN;
+            g_scsi_dma.scheduled_dma += dma_to_schedule;
+        }
+        else
+        {
+            // No more data available
+            stop_dma();
+        }
+    }
+}
+
+void scsi_accel_dma_startWrite(const uint8_t* data, uint32_t count, volatile int *resetFlag)
+{
+    __disable_irq();
+    if (g_scsi_dma_state == SCSIDMA_WRITE)
+    {
+        if (!g_scsi_dma.next_app_buf && data == g_scsi_dma.app_buf + g_scsi_dma.bytes_app)
+        {
+            // Combine with currently running request
+            g_scsi_dma.bytes_app += count;
+            count = 0;
+        }
+        else if (data == g_scsi_dma.next_app_buf + g_scsi_dma.next_app_bytes)
+        {
+            // Combine with queued request
+            g_scsi_dma.next_app_bytes += count;
+            count = 0;
+        }
+        else if (!g_scsi_dma.next_app_buf)
+        {
+            // Add as queued request
+            g_scsi_dma.next_app_buf = (uint8_t*)data;
+            g_scsi_dma.next_app_bytes = count;
+            count = 0;
+        }
+    }
+    __enable_irq();
+
+    // Check if the request was combined
+    if (count == 0) return;
+
+    if (g_scsi_dma_state != SCSIDMA_IDLE)
+    {
+        // Wait for previous request to finish
+        scsi_accel_dma_finishWrite(resetFlag);
+        if (*resetFlag)
+        {
+            return;
+        }
+    }
+
+    // dbgmsg("Starting DMA write of ", (int)count, " bytes");
+    scsi_dma_gpio_config(true);
+    g_scsi_dma_state = SCSIDMA_WRITE;
+    g_scsi_dma.app_buf = (uint8_t*)data;
+    g_scsi_dma.dma_idx = 0;
+    g_scsi_dma.dma_fillto = DMA_BUF_SIZE;
+    g_scsi_dma.bytes_app = count;
+    g_scsi_dma.bytes_dma = 0;
+    g_scsi_dma.scheduled_dma = 0;
+    g_scsi_dma.incomplete_buf = false;
+    g_scsi_dma.next_app_buf = NULL;
+    g_scsi_dma.next_app_bytes = 0;
+    g_scsi_dma.greenpak_state = GREENPAK_IO1_LOW;
+    refill_dmabuf();
+    start_dma();
+}
+
+bool scsi_accel_dma_isWriteFinished(const uint8_t* data)
+{
+    // Check if everything has completed
+    if (g_scsi_dma_state == SCSIDMA_IDLE)
+    {
+        return true;
+    }
+
+    if (!data)
+        return false;
+    
+    // Check if this data item is still in queue.
+    __disable_irq();
+    bool finished = true;
+    if (data >= g_scsi_dma.app_buf + g_scsi_dma.bytes_dma &&
+        data < g_scsi_dma.app_buf + g_scsi_dma.bytes_app)
+    {
+        finished = false; // In current transfer
+    }
+    else if (data >= g_scsi_dma.next_app_buf &&
+             data < g_scsi_dma.next_app_buf + g_scsi_dma.next_app_bytes)
+    {
+        finished = false; // In queued transfer
+    }
+    __enable_irq();
+
+    return finished;
+}
+
+void scsi_accel_dma_stopWrite()
+{
+    stop_dma();
+    scsi_dma_gpio_config(false);
+}
+
+void scsi_accel_dma_finishWrite(volatile int *resetFlag)
+{
+    uint32_t start = millis();
+    while (g_scsi_dma_state != SCSIDMA_IDLE && !*resetFlag)
+    {
+        if ((uint32_t)(millis() - start) > 5000)
+        {
+            logmsg("scsi_accel_dma_finishWrite() timeout, DMA counts ",
+                dma_transfer_number_get(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA), " ",
+                dma_transfer_number_get(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB), " ",
+                TIMER_CNT(SCSI_TIMER));
+            *resetFlag = 1;
+            break;
+        }
+    }
+
+    scsi_accel_dma_stopWrite();
+}
+
+/************************************************/
+/* Functions using external GreenPAK logic chip */
+/************************************************/
+// @TODO properly handle greenpak DMA
+void scsi_accel_greenpak_dma_init()
+{
+    g_scsi_dma_state = SCSIDMA_IDLE;
+    g_scsi_dma_use_greenpak = true;
+    rcu_periph_clock_enable(SCSI_TIMER_RCU);
+    rcu_periph_clock_enable(SCSI_TIMER_DMA_RCU);
+
+    // DMA Channel A: data copy
+    // GPIO DMA copies data from memory buffer to GPIO BOP register.
+    // The memory buffer is filled by interrupt routine.
+ /* @TODO replace with gd32F4xx dma code 
+    dma_parameter_struct gpio_dma_config =
+    {
+        .periph_addr = (uint32_t)&GPIO_BOP(SCSI_OUT_PORT),
+        .periph_width = DMA_PERIPHERAL_WIDTH_32BIT,
+        .memory_addr = (uint32_t)g_scsi_dma.dma_buf,
+        .memory_width = DMA_MEMORY_WIDTH_32BIT,
+        .number = DMA_BUF_SIZE,
+        .priority = DMA_PRIORITY_ULTRA_HIGH,
+        .periph_inc = DMA_PERIPH_INCREASE_DISABLE,
+        .memory_inc = DMA_MEMORY_INCREASE_ENABLE,
+        .direction = DMA_MEMORY_TO_PERIPHERAL
+    };
+    dma_init(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA, &gpio_dma_config);
+    dma_circulation_enable(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA);
+    */
+    NVIC_SetPriority(SCSI_TIMER_DMACHA_IRQn, 2);
+    NVIC_EnableIRQ(SCSI_TIMER_DMACHA_IRQn);
+    NVIC_DisableIRQ(SCSI_TIMER_DMACHB_IRQn);
+
+    // EXTI channel is used to trigger when we reach end of the transfer.
+    // Because the main DMA is circular and transfer size may not be even
+    // multiple of it, we cannot trigger the end at the DMA interrupt.
+    syscfg_exti_line_config(GREENPAK_PLD_IO2_EXTI_SOURCE_PORT, GREENPAK_PLD_IO2_EXTI_SOURCE_PIN);
+    exti_init(GREENPAK_PLD_IO2_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
+    exti_interrupt_flag_clear(GREENPAK_PLD_IO2_EXTI);
+    exti_interrupt_disable(GREENPAK_PLD_IO2_EXTI);
+    NVIC_SetPriority(GREENPAK_IRQn, 1);
+    NVIC_EnableIRQ(GREENPAK_IRQn);
+
+    
+    // Timer is used to trigger DMA requests
+    // OUT_REQ is driven by timer output.
+    // 1. On timer update event, REQ is set low.
+    // 2. When ACK goes low, timer counts and OUT_REQ is set high.
+    //    Simultaneously a DMA request is triggered to write next data to GPIO.
+    // 3. When ACK goes high, a DMA request is triggered to cause timer update event.
+    //    The DMA request priority is set so that 2. always completes before it.
+    TIMER_CTL0(SCSI_TIMER) = 0;
+    TIMER_SMCFG(SCSI_TIMER) = TIMER_SLAVE_MODE_EXTERNAL0 | TIMER_SMCFG_TRGSEL_CI0F_ED;
+    TIMER_CAR(SCSI_TIMER) = 1;
+    TIMER_PSC(SCSI_TIMER) = 0;
+    TIMER_DMAINTEN(SCSI_TIMER) = 0;
+    TIMER_CHCTL0(SCSI_TIMER) = 0x6001; // CH0 as input, CH1 as DMA trigger
+    TIMER_CHCTL1(SCSI_TIMER) = 0;
+    TIMER_CHCTL2(SCSI_TIMER) = 0;
+    TIMER_CCHP(SCSI_TIMER) = 0;
+    TIMER_CH1CV(SCSI_TIMER) = 1; // Copy data when ACK goes low
+    //TODO figure out if this needs to be set to an alternate fucntion like a timer
+    gpio_mode_set(SCSI_TIMER_IN_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SCSI_TIMER_IN_PIN);
+}
+
+extern const uint32_t g_scsi_out_byte_to_bop_pld1hi[256];
+extern const uint32_t g_scsi_out_byte_to_bop_pld1lo[256];
+
+static void greenpak_refill_dmabuf()
+{
+    if (g_scsi_dma.greenpak_state == GREENPAK_STOP)
+    {
+        // Wait for previous DMA block to end first
+        return;
+    }
+
+    // Check how many bytes we have available from the application
+    uint32_t count = g_scsi_dma.bytes_app - g_scsi_dma.bytes_dma;
+    
+    // Check amount of free space in DMA buffer
+    uint32_t max = g_scsi_dma.dma_fillto - g_scsi_dma.dma_idx;
+    if (count > max) count = max;
+
+    uint8_t *src = g_scsi_dma.app_buf + g_scsi_dma.bytes_dma;
+    uint32_t *dst = g_scsi_dma.dma_buf;
+    uint32_t pos = g_scsi_dma.dma_idx;
+    uint32_t end = pos + count;
+    g_scsi_dma.dma_idx = end;
+    g_scsi_dma.bytes_dma += count;
+    g_scsi_dma.scheduled_dma = g_scsi_dma.bytes_dma;
+
+    if (pos < end && g_scsi_dma.greenpak_state == GREENPAK_IO1_HIGH)
+    {
+        // Fix alignment so that main loop begins with PLD1HI
+        dst[(pos++) & DMA_BUF_MASK] = g_scsi_out_byte_to_bop_pld1lo[*src++];
+        g_scsi_dma.greenpak_state = GREENPAK_IO1_LOW;
+    }
+
+    while (pos + 4 <= end)
+    {
+        uint32_t input = *(uint32_t*)src;
+        src += 4;
+
+        dst[(pos++) & DMA_BUF_MASK] = g_scsi_out_byte_to_bop_pld1hi[(input >> 0) & 0xFF];
+        dst[(pos++) & DMA_BUF_MASK] = g_scsi_out_byte_to_bop_pld1lo[(input >> 8) & 0xFF];
+        dst[(pos++) & DMA_BUF_MASK] = g_scsi_out_byte_to_bop_pld1hi[(input >> 16) & 0xFF];
+        dst[(pos++) & DMA_BUF_MASK] = g_scsi_out_byte_to_bop_pld1lo[(input >> 24) & 0xFF];
+    }
+
+    while (pos < end)
+    {
+        if (g_scsi_dma.greenpak_state == GREENPAK_IO1_HIGH)
+        {
+            dst[(pos++) & DMA_BUF_MASK] = g_scsi_out_byte_to_bop_pld1lo[*src++];
+            g_scsi_dma.greenpak_state = GREENPAK_IO1_LOW;
+        }
+        else
+        {
+            dst[(pos++) & DMA_BUF_MASK] = g_scsi_out_byte_to_bop_pld1hi[*src++];
+            g_scsi_dma.greenpak_state = GREENPAK_IO1_HIGH;
+        }
+    }
+
+    uint32_t remain = g_scsi_dma.dma_fillto - g_scsi_dma.dma_idx;
+    if (!g_scsi_dma.next_app_buf && remain > 0)
+    {
+        // Mark the end of transfer by turning PD2 off
+        dst[(pos++) & DMA_BUF_MASK] = (GREENPAK_PLD_IO2 << 16) | (GREENPAK_PLD_IO1 << 16);
+        g_scsi_dma.dma_idx = pos;
+        g_scsi_dma.greenpak_state = GREENPAK_STOP;
+    }
+}
+
+extern "C" void GREENPAK_IRQ()
+{
+    if (EXTI_PD & GREENPAK_PLD_IO2_EXTI)
+    {
+        EXTI_PD = GREENPAK_PLD_IO2_EXTI;
+
+        if (g_scsi_dma.bytes_app > g_scsi_dma.bytes_dma || g_scsi_dma.next_app_buf)
+        {
+            assert(g_scsi_dma.greenpak_state == GREENPAK_STOP);
+            g_scsi_dma.greenpak_state = GREENPAK_IO1_LOW;
+            // More data is available
+            check_dma_next_buffer();
+            refill_dmabuf();
+            
+            // Continue transferring
+            GPIO_BOP(SCSI_OUT_PORT) = GREENPAK_PLD_IO2;
+            TIMER_SWEVG(SCSI_TIMER) = TIMER_SWEVG_CH1G;
+        }
+        else
+        {
+            stop_dma();
+        }
+    }
+}
+
+static void greenpak_start_dma()
+{
+    //TODO rewrite with GD32F4xx DMA methods
+    /*
+    // Disable channels while configuring
+    DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA) &= ~DMA_CHXCTL_CHEN;
+    DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHB) &= ~DMA_CHXCTL_CHEN;
+    TIMER_CTL0(SCSI_TIMER) = 0;
+
+    // Set buffer address and size
+    DMA_CHMADDR(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA) = (uint32_t)g_scsi_dma.dma_buf;
+    DMA_CHCNT(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA) = DMA_BUF_SIZE;
+
+    // Clear pending DMA events
+    TIMER_DMAINTEN(SCSI_TIMER) = 0;
+    TIMER_DMAINTEN(SCSI_TIMER) = TIMER_DMAINTEN_CH1DEN | TIMER_DMAINTEN_CH3DEN;
+
+    // Clear and enable interrupt
+    DMA_INTC(SCSI_TIMER_DMA) = DMA_FLAG_ADD(DMA_FLAG_HTF | DMA_FLAG_FTF | DMA_FLAG_ERR, SCSI_TIMER_DMACHA);
+    DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA) |= DMA_CHXCTL_FTFIE | DMA_CHXCTL_HTFIE;
+    exti_interrupt_flag_clear(GREENPAK_PLD_IO2_EXTI);
+    exti_interrupt_enable(GREENPAK_PLD_IO2_EXTI);
+
+    // Enable channels
+    DMA_CHCTL(SCSI_TIMER_DMA, SCSI_TIMER_DMACHA) |= DMA_CHXCTL_CHEN;
+    
+    // Enable timer
+    TIMER_CNT(SCSI_TIMER) = 0;
+    TIMER_CTL0(SCSI_TIMER) |= TIMER_CTL0_CEN;
+
+    // Generate first event
+    TIMER_SWEVG(SCSI_TIMER) = TIMER_SWEVG_CH1G;
+    */
+}
+
+static void greenpak_stop_dma()
+{
+    exti_interrupt_disable(GREENPAK_PLD_IO2_EXTI);
+}
+
+#endif

+ 48 - 0
lib/ZuluSCSI_platform_GD32F450/scsi_accel_dma.h

@@ -0,0 +1,48 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+// SCSI subroutines that use hardware DMA for transfer in the background.
+// Uses either GD32 timer or external GreenPAK to implement REQ pin toggling.
+
+#pragma once
+
+#include <stdint.h>
+#include "ZuluSCSI_platform.h"
+#include "greenpak.h"
+
+#ifdef SCSI_TIMER
+#define SCSI_ACCEL_DMA_AVAILABLE 1
+#endif
+
+/* Initialization function decides whether to use timer or GreenPAK */
+void scsi_accel_greenpak_dma_init();
+void scsi_accel_timer_dma_init();
+
+/* Common functions */
+void scsi_accel_dma_startWrite(const uint8_t* data, uint32_t count, volatile int *resetFlag);
+void scsi_accel_dma_stopWrite();
+void scsi_accel_dma_finishWrite(volatile int *resetFlag);
+
+// Query whether the data at pointer has already been read, i.e. buffer can be reused.
+// If data is NULL, checks if all writes have completed.
+bool scsi_accel_dma_isWriteFinished(const uint8_t* data);
+
+

+ 388 - 0
lib/ZuluSCSI_platform_GD32F450/scsi_accel_greenpak.cpp

@@ -0,0 +1,388 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#include "scsi_accel_greenpak.h"
+#include "ZuluSCSI_platform.h"
+#include <ZuluSCSI_log.h>
+#include <assert.h>
+
+#ifndef GREENPAK_PLD_IO1
+
+void scsi_accel_greenpak_send(const uint32_t *buf, uint32_t num_words, volatile int *resetFlag)
+{
+    assert(false);
+}
+
+void scsi_accel_greenpak_recv(uint32_t *buf, uint32_t num_words, volatile int *resetFlag)
+{
+    assert(false);
+}
+
+#else
+
+/*********************************************************/
+/* Optimized writes to SCSI bus in GREENPAK_PIO mode     */
+/*********************************************************/
+
+extern const uint32_t g_scsi_out_byte_to_bop_pld1hi[256];
+extern const uint32_t g_scsi_out_byte_to_bop_pld1lo[256];
+
+// Optimized ASM blocks for the SCSI communication subroutine
+
+// Take 8 bits from d and format them for writing
+// d is name of data operand, b is bit offset, x is unique label
+#define ASM_LOAD_DATA_PLD1HI(d, b, x) \
+"    load_data1_" x "_%=: \n" \
+"        ubfx    %[tmp1], %[" d "], #" b ", #8 \n" \
+"        ldr     %[tmp1], [%[byte_lookup_pld1hi], %[tmp1], lsl #2] \n"
+
+#define ASM_LOAD_DATA_PLD1LO(d, b, x) \
+"    load_data1_" x "_%=: \n" \
+"        ubfx    %[tmp1], %[" d "], #" b ", #8 \n" \
+"        ldr     %[tmp1], [%[byte_lookup_pld1lo], %[tmp1], lsl #2] \n"
+
+// Write data to SCSI port
+#define ASM_SEND_DATA(x) \
+"    send_data" x "_%=: \n" \
+"        str     %[tmp1], [%[out_port_bop]] \n"
+
+// Wait for ACK to be low or REQ to be high.
+// The external logic will set REQ low when PLD_IO1 is toggled and
+// back high as soon as ACK goes low. New data can be written to
+// GPIO as soon as ACK goes low. If an interrupt happens in the
+// middle, we may miss the ACK pulse and should check REQ.
+#define ASM_WAIT_DONE(x) \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], wait_done_" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], wait_done_" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], wait_done_" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], wait_done_" x "_%= \n" \
+"    wait_req_inactive_" x "_%=: \n" \
+"        ldr     %[tmp2], [%[req_pin_bb]] \n" \
+"        cbnz    %[tmp2], wait_done_" x "_%= \n" \
+"        ldr     %[tmp2], [%[reset_flag]] \n" \
+"        cbnz    %[tmp2], wait_done_" x "_%= \n" \
+"        b.n     wait_req_inactive_" x "_%= \n" \
+"    wait_done_" x "_%=: \n"
+
+// Send bytes to SCSI bus using the asynchronous handshake mechanism
+// Takes 4 bytes at a time for sending from buf.
+void scsi_accel_greenpak_send(const uint32_t *buf, uint32_t num_words, volatile int *resetFlag)
+{
+    volatile uint32_t *out_port_bop = (volatile uint32_t*)&GPIO_BOP(SCSI_OUT_PORT);
+    uint32_t ack_pin_bb = PERIPH_BB_BASE + (((uint32_t)&GPIO_ISTAT(SCSI_ACK_PORT)) - APB1_BUS_BASE) * 32 + SCSI_IN_ACK_IDX * 4;
+    uint32_t req_pin_bb = PERIPH_BB_BASE + (((uint32_t)&GPIO_ISTAT(SCSI_OUT_PORT)) - APB1_BUS_BASE) * 32 + SCSI_OUT_REQ_IDX * 4;
+    register uint32_t tmp1 = 0;
+    register uint32_t tmp2 = 0;
+    register uint32_t data = 0;
+
+    // Set REQ pin as input and PLD_IO2 high to enable logic
+    GPIO_BC(SCSI_OUT_PORT) = GREENPAK_PLD_IO1;
+    gpio_mode_set(SCSI_OUT_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, SCSI_OUT_REQ);
+    GPIO_BOP(SCSI_OUT_PORT) = GREENPAK_PLD_IO2;
+
+    asm volatile (
+    "   ldr      %[data], [%[buf]], #4 \n" \
+        ASM_LOAD_DATA_PLD1HI("data", "0", "first")
+        
+    "inner_loop_%=: \n" \
+        ASM_SEND_DATA("0")
+        ASM_LOAD_DATA_PLD1LO("data", "8", "8")
+        ASM_WAIT_DONE("0")
+        
+        ASM_SEND_DATA("8")
+        ASM_LOAD_DATA_PLD1HI("data", "16", "16")
+        ASM_WAIT_DONE("8")
+
+        ASM_SEND_DATA("16")
+        ASM_LOAD_DATA_PLD1LO("data", "24", "24")
+        ASM_WAIT_DONE("16")
+
+        ASM_SEND_DATA("24")
+    "   ldr      %[data], [%[buf]], #4 \n" \
+        ASM_LOAD_DATA_PLD1HI("data", "0", "0")
+        ASM_WAIT_DONE("24")
+
+    "   subs     %[num_words], %[num_words], #1 \n" \
+    "   bne     inner_loop_%= \n"
+    : /* Output */ [tmp1] "+l" (tmp1), [tmp2] "+l" (tmp2), [data] "+r" (data),
+                   [buf] "+r" (buf), [num_words] "+r" (num_words)
+    : /* Input */ [ack_pin_bb] "r" (ack_pin_bb),
+                  [req_pin_bb] "r" (req_pin_bb),
+                  [out_port_bop] "r"(out_port_bop),
+                  [byte_lookup_pld1hi] "r" (g_scsi_out_byte_to_bop_pld1hi),
+                  [byte_lookup_pld1lo] "r" (g_scsi_out_byte_to_bop_pld1lo),
+                  [reset_flag] "r" (resetFlag)
+    : /* Clobber */ );
+
+    SCSI_RELEASE_DATA_REQ();
+
+    // Disable external logic and set REQ pin as output
+    GPIO_BC(SCSI_OUT_PORT) = GREENPAK_PLD_IO2;
+    gpio_mode_set(SCSI_OUT_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE , SCSI_OUT_REQ);
+    gpio_output_options_set(SCSI_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SCSI_OUT_REQ);
+}
+
+/**********************************************/
+/* Mapping from data bytes to GPIO BOP values */
+/**********************************************/
+
+#define PARITY(n) ((1 ^ (n) ^ ((n)>>1) ^ ((n)>>2) ^ ((n)>>3) ^ ((n)>>4) ^ ((n)>>5) ^ ((n)>>6) ^ ((n)>>7)) & 1)
+#define X(n) (\
+    ((n & 0x01) ? (SCSI_OUT_DB0 << 16) : SCSI_OUT_DB0) | \
+    ((n & 0x02) ? (SCSI_OUT_DB1 << 16) : SCSI_OUT_DB1) | \
+    ((n & 0x04) ? (SCSI_OUT_DB2 << 16) : SCSI_OUT_DB2) | \
+    ((n & 0x08) ? (SCSI_OUT_DB3 << 16) : SCSI_OUT_DB3) | \
+    ((n & 0x10) ? (SCSI_OUT_DB4 << 16) : SCSI_OUT_DB4) | \
+    ((n & 0x20) ? (SCSI_OUT_DB5 << 16) : SCSI_OUT_DB5) | \
+    ((n & 0x40) ? (SCSI_OUT_DB6 << 16) : SCSI_OUT_DB6) | \
+    ((n & 0x80) ? (SCSI_OUT_DB7 << 16) : SCSI_OUT_DB7) | \
+    (PARITY(n)  ? (SCSI_OUT_DBP << 16) : SCSI_OUT_DBP) | \
+    (GREENPAK_PLD_IO1) \
+)
+    
+const uint32_t g_scsi_out_byte_to_bop_pld1hi[256] =
+{
+    X(0x00), X(0x01), X(0x02), X(0x03), X(0x04), X(0x05), X(0x06), X(0x07), X(0x08), X(0x09), X(0x0a), X(0x0b), X(0x0c), X(0x0d), X(0x0e), X(0x0f),
+    X(0x10), X(0x11), X(0x12), X(0x13), X(0x14), X(0x15), X(0x16), X(0x17), X(0x18), X(0x19), X(0x1a), X(0x1b), X(0x1c), X(0x1d), X(0x1e), X(0x1f),
+    X(0x20), X(0x21), X(0x22), X(0x23), X(0x24), X(0x25), X(0x26), X(0x27), X(0x28), X(0x29), X(0x2a), X(0x2b), X(0x2c), X(0x2d), X(0x2e), X(0x2f),
+    X(0x30), X(0x31), X(0x32), X(0x33), X(0x34), X(0x35), X(0x36), X(0x37), X(0x38), X(0x39), X(0x3a), X(0x3b), X(0x3c), X(0x3d), X(0x3e), X(0x3f),
+    X(0x40), X(0x41), X(0x42), X(0x43), X(0x44), X(0x45), X(0x46), X(0x47), X(0x48), X(0x49), X(0x4a), X(0x4b), X(0x4c), X(0x4d), X(0x4e), X(0x4f),
+    X(0x50), X(0x51), X(0x52), X(0x53), X(0x54), X(0x55), X(0x56), X(0x57), X(0x58), X(0x59), X(0x5a), X(0x5b), X(0x5c), X(0x5d), X(0x5e), X(0x5f),
+    X(0x60), X(0x61), X(0x62), X(0x63), X(0x64), X(0x65), X(0x66), X(0x67), X(0x68), X(0x69), X(0x6a), X(0x6b), X(0x6c), X(0x6d), X(0x6e), X(0x6f),
+    X(0x70), X(0x71), X(0x72), X(0x73), X(0x74), X(0x75), X(0x76), X(0x77), X(0x78), X(0x79), X(0x7a), X(0x7b), X(0x7c), X(0x7d), X(0x7e), X(0x7f),
+    X(0x80), X(0x81), X(0x82), X(0x83), X(0x84), X(0x85), X(0x86), X(0x87), X(0x88), X(0x89), X(0x8a), X(0x8b), X(0x8c), X(0x8d), X(0x8e), X(0x8f),
+    X(0x90), X(0x91), X(0x92), X(0x93), X(0x94), X(0x95), X(0x96), X(0x97), X(0x98), X(0x99), X(0x9a), X(0x9b), X(0x9c), X(0x9d), X(0x9e), X(0x9f),
+    X(0xa0), X(0xa1), X(0xa2), X(0xa3), X(0xa4), X(0xa5), X(0xa6), X(0xa7), X(0xa8), X(0xa9), X(0xaa), X(0xab), X(0xac), X(0xad), X(0xae), X(0xaf),
+    X(0xb0), X(0xb1), X(0xb2), X(0xb3), X(0xb4), X(0xb5), X(0xb6), X(0xb7), X(0xb8), X(0xb9), X(0xba), X(0xbb), X(0xbc), X(0xbd), X(0xbe), X(0xbf),
+    X(0xc0), X(0xc1), X(0xc2), X(0xc3), X(0xc4), X(0xc5), X(0xc6), X(0xc7), X(0xc8), X(0xc9), X(0xca), X(0xcb), X(0xcc), X(0xcd), X(0xce), X(0xcf),
+    X(0xd0), X(0xd1), X(0xd2), X(0xd3), X(0xd4), X(0xd5), X(0xd6), X(0xd7), X(0xd8), X(0xd9), X(0xda), X(0xdb), X(0xdc), X(0xdd), X(0xde), X(0xdf),
+    X(0xe0), X(0xe1), X(0xe2), X(0xe3), X(0xe4), X(0xe5), X(0xe6), X(0xe7), X(0xe8), X(0xe9), X(0xea), X(0xeb), X(0xec), X(0xed), X(0xee), X(0xef),
+    X(0xf0), X(0xf1), X(0xf2), X(0xf3), X(0xf4), X(0xf5), X(0xf6), X(0xf7), X(0xf8), X(0xf9), X(0xfa), X(0xfb), X(0xfc), X(0xfd), X(0xfe), X(0xff)
+};
+
+#undef X
+
+#define X(n) (\
+    ((n & 0x01) ? (SCSI_OUT_DB0 << 16) : SCSI_OUT_DB0) | \
+    ((n & 0x02) ? (SCSI_OUT_DB1 << 16) : SCSI_OUT_DB1) | \
+    ((n & 0x04) ? (SCSI_OUT_DB2 << 16) : SCSI_OUT_DB2) | \
+    ((n & 0x08) ? (SCSI_OUT_DB3 << 16) : SCSI_OUT_DB3) | \
+    ((n & 0x10) ? (SCSI_OUT_DB4 << 16) : SCSI_OUT_DB4) | \
+    ((n & 0x20) ? (SCSI_OUT_DB5 << 16) : SCSI_OUT_DB5) | \
+    ((n & 0x40) ? (SCSI_OUT_DB6 << 16) : SCSI_OUT_DB6) | \
+    ((n & 0x80) ? (SCSI_OUT_DB7 << 16) : SCSI_OUT_DB7) | \
+    (PARITY(n)  ? (SCSI_OUT_DBP << 16) : SCSI_OUT_DBP) | \
+    (GREENPAK_PLD_IO1 << 16) \
+)
+    
+const uint32_t g_scsi_out_byte_to_bop_pld1lo[256] =
+{
+    X(0x00), X(0x01), X(0x02), X(0x03), X(0x04), X(0x05), X(0x06), X(0x07), X(0x08), X(0x09), X(0x0a), X(0x0b), X(0x0c), X(0x0d), X(0x0e), X(0x0f),
+    X(0x10), X(0x11), X(0x12), X(0x13), X(0x14), X(0x15), X(0x16), X(0x17), X(0x18), X(0x19), X(0x1a), X(0x1b), X(0x1c), X(0x1d), X(0x1e), X(0x1f),
+    X(0x20), X(0x21), X(0x22), X(0x23), X(0x24), X(0x25), X(0x26), X(0x27), X(0x28), X(0x29), X(0x2a), X(0x2b), X(0x2c), X(0x2d), X(0x2e), X(0x2f),
+    X(0x30), X(0x31), X(0x32), X(0x33), X(0x34), X(0x35), X(0x36), X(0x37), X(0x38), X(0x39), X(0x3a), X(0x3b), X(0x3c), X(0x3d), X(0x3e), X(0x3f),
+    X(0x40), X(0x41), X(0x42), X(0x43), X(0x44), X(0x45), X(0x46), X(0x47), X(0x48), X(0x49), X(0x4a), X(0x4b), X(0x4c), X(0x4d), X(0x4e), X(0x4f),
+    X(0x50), X(0x51), X(0x52), X(0x53), X(0x54), X(0x55), X(0x56), X(0x57), X(0x58), X(0x59), X(0x5a), X(0x5b), X(0x5c), X(0x5d), X(0x5e), X(0x5f),
+    X(0x60), X(0x61), X(0x62), X(0x63), X(0x64), X(0x65), X(0x66), X(0x67), X(0x68), X(0x69), X(0x6a), X(0x6b), X(0x6c), X(0x6d), X(0x6e), X(0x6f),
+    X(0x70), X(0x71), X(0x72), X(0x73), X(0x74), X(0x75), X(0x76), X(0x77), X(0x78), X(0x79), X(0x7a), X(0x7b), X(0x7c), X(0x7d), X(0x7e), X(0x7f),
+    X(0x80), X(0x81), X(0x82), X(0x83), X(0x84), X(0x85), X(0x86), X(0x87), X(0x88), X(0x89), X(0x8a), X(0x8b), X(0x8c), X(0x8d), X(0x8e), X(0x8f),
+    X(0x90), X(0x91), X(0x92), X(0x93), X(0x94), X(0x95), X(0x96), X(0x97), X(0x98), X(0x99), X(0x9a), X(0x9b), X(0x9c), X(0x9d), X(0x9e), X(0x9f),
+    X(0xa0), X(0xa1), X(0xa2), X(0xa3), X(0xa4), X(0xa5), X(0xa6), X(0xa7), X(0xa8), X(0xa9), X(0xaa), X(0xab), X(0xac), X(0xad), X(0xae), X(0xaf),
+    X(0xb0), X(0xb1), X(0xb2), X(0xb3), X(0xb4), X(0xb5), X(0xb6), X(0xb7), X(0xb8), X(0xb9), X(0xba), X(0xbb), X(0xbc), X(0xbd), X(0xbe), X(0xbf),
+    X(0xc0), X(0xc1), X(0xc2), X(0xc3), X(0xc4), X(0xc5), X(0xc6), X(0xc7), X(0xc8), X(0xc9), X(0xca), X(0xcb), X(0xcc), X(0xcd), X(0xce), X(0xcf),
+    X(0xd0), X(0xd1), X(0xd2), X(0xd3), X(0xd4), X(0xd5), X(0xd6), X(0xd7), X(0xd8), X(0xd9), X(0xda), X(0xdb), X(0xdc), X(0xdd), X(0xde), X(0xdf),
+    X(0xe0), X(0xe1), X(0xe2), X(0xe3), X(0xe4), X(0xe5), X(0xe6), X(0xe7), X(0xe8), X(0xe9), X(0xea), X(0xeb), X(0xec), X(0xed), X(0xee), X(0xef),
+    X(0xf0), X(0xf1), X(0xf2), X(0xf3), X(0xf4), X(0xf5), X(0xf6), X(0xf7), X(0xf8), X(0xf9), X(0xfa), X(0xfb), X(0xfc), X(0xfd), X(0xfe), X(0xff)
+};
+
+#undef X
+
+/*********************************************************/
+/* Optimized reads from SCSI bus in GREENPAK_PIO mode    */
+/*********************************************************/
+
+// Wait for ACK to go high and back low.
+// This indicates that there is a byte ready to be read
+// If interrupt occurs in middle, we may miss ACK going high.
+// In that case, verify that REQ is already low.
+#define ASM_WAIT_ACK(x) \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbnz    %[tmp2], ack_is_high_" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbnz    %[tmp2], ack_is_high_" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbnz    %[tmp2], ack_is_high_" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbnz    %[tmp2], ack_is_high_" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbnz    %[tmp2], ack_is_high_" x "_%= \n" \
+"    wait_req_low_" x "_%=: \n" \
+"        cpsid   i \n" \
+"        ldr     %[tmp2], [%[req_pin_bb]] \n" \
+"        cbz     %[tmp2], ack_is_high_" x "_%= \n" \
+"        ldr     %[tmp2], [%[reset_flag]] \n" \
+"        cbnz    %[tmp2], ack_is_high_" x "_%= \n" \
+"        cpsie   i \n" \
+"        b.n     wait_req_low_" x "_%= \n" \
+"    ack_is_high_" x "_%=: \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], ack_is_low_" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], ack_is_low_" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], ack_is_low_" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], ack_is_low_" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], ack_is_low_" x "_%= \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], ack_is_low_" x "_%= \n" \
+"    wait_ack_low_" x "_%=: \n" \
+"        cpsid   i \n" \
+"        ldr     %[tmp2], [%[ack_pin_bb]] \n" \
+"        cbz     %[tmp2], ack_is_low_" x "_%= \n" \
+"        ldr     %[tmp2], [%[reset_flag]] \n" \
+"        cbnz    %[tmp2], ack_is_low_" x "_%= \n" \
+"        cpsie   i \n" \
+"        b.n     wait_ack_low_" x "_%= \n" \
+"    ack_is_low_" x "_%=: \n"
+
+// Prepare for reception of data by loading the next PLD_IO1 value
+// and disabling interrupts.
+#define ASM_PREP_RECV(l) \
+"        mov    %[tmp1], %[" l "] \n" \
+"        cpsid  i \n"
+
+// Read GPIO bus, take the data byte and toggle PLD_IO1.
+// Note that the PLD_IO1 write is done first to reduce latency, but
+// the istat value that is read by next instruction is still the old
+// one due to IO port delays. Interrupts must be disabled for this
+// sequence to work correctly.
+//
+// d is the name of register where data is to be stored
+// b is the bit offset to store the byte at
+// x is unique label
+#define ASM_RECV_DATA(d, b, x) \
+"    read_data_" x "_%=: \n" \
+"        str    %[tmp1], [%[out_port_bop]] \n" \
+"        ldr    %[tmp1], [%[in_port_istat]] \n" \
+"        ubfx   %[tmp1], %[tmp1], %[data_in_shift], #8 \n" \
+"        bfi    %[" d "], %[tmp1], #" b ", #8 \n" \
+"        cpsie  i \n"
+
+// Read bytes from SCSI bus using asynchronous handshake mechanism
+// Takes 4 bytes at a time.
+void scsi_accel_greenpak_recv(uint32_t *buf, uint32_t num_words, volatile int *resetFlag)
+{
+    volatile uint32_t *out_port_bop = (volatile uint32_t*)&GPIO_BOP(SCSI_OUT_PORT);
+    volatile uint32_t *in_port_istat = (volatile uint32_t*)&GPIO_ISTAT(SCSI_IN_PORT);
+    uint32_t ack_pin_bb = PERIPH_BB_BASE + (((uint32_t)&GPIO_ISTAT(SCSI_ACK_PORT)) - APB1_BUS_BASE) * 32 + SCSI_IN_ACK_IDX * 4;
+    uint32_t req_pin_bb = PERIPH_BB_BASE + (((uint32_t)&GPIO_ISTAT(SCSI_OUT_PORT)) - APB1_BUS_BASE) * 32 + SCSI_OUT_REQ_IDX * 4;
+    register uint32_t tmp1 = 0;
+    register uint32_t tmp2 = 0;
+    register uint32_t data = 0;
+
+    // Last word requires special handling so that hardware doesn't issue new REQ pulse.
+    assert(num_words >= 2);
+    num_words -= 1;
+
+    // Set PLD_IO3 high to enable read from SCSI bus
+    GPIO_BOP(SCSI_OUT_PORT) = GREENPAK_PLD_IO3;
+
+    // Make sure that the previous access has fully completed.
+    // E.g. Macintosh can hold ACK low for long time after last byte of block.
+    while (SCSI_IN(ACK) && !*resetFlag);
+
+    // Set REQ pin as input and PLD_IO2 high to enable logic
+    GPIO_BC(SCSI_OUT_PORT) = GREENPAK_PLD_IO1;
+    gpio_mode_set(SCSI_OUT_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, SCSI_OUT_REQ);
+    GPIO_BOP(SCSI_OUT_PORT) = GREENPAK_PLD_IO2;
+
+    asm volatile (
+    "inner_loop_%=: \n"
+        ASM_PREP_RECV("pld1_hi")
+        ASM_WAIT_ACK("0")
+        ASM_RECV_DATA("data", "0", "0")
+        
+        ASM_PREP_RECV("pld1_lo")
+        ASM_WAIT_ACK("8")
+        ASM_RECV_DATA("data", "8", "8")
+        
+        ASM_PREP_RECV("pld1_hi")
+        ASM_WAIT_ACK("16")
+        ASM_RECV_DATA("data", "16", "16")
+        
+        ASM_PREP_RECV("pld1_lo")
+        ASM_WAIT_ACK("24")
+        ASM_RECV_DATA("data", "24", "24")
+
+    "   mvn      %[data], %[data] \n"
+    "   str      %[data], [%[buf]], #4 \n"
+    "   subs     %[num_words], %[num_words], #1 \n"
+    "   bne     inner_loop_%= \n"
+
+    // Process last word separately to avoid issuing extra REQ pulse at end.
+    "recv_last_word_%=: \n"
+        ASM_PREP_RECV("pld1_hi")
+        ASM_WAIT_ACK("0b")
+        ASM_RECV_DATA("data", "0", "0b")
+        
+        ASM_PREP_RECV("pld1_lo")
+        ASM_WAIT_ACK("8b")
+        ASM_RECV_DATA("data", "8", "8b")
+        
+        ASM_PREP_RECV("pld1_hi")
+        ASM_WAIT_ACK("16b")
+        ASM_RECV_DATA("data", "16", "16b")
+        
+        ASM_PREP_RECV("pld1_hi")
+        ASM_WAIT_ACK("24b")
+        ASM_RECV_DATA("data", "24", "24b")
+
+    "   mvn      %[data], %[data] \n"
+    "   str      %[data], [%[buf]], #4 \n"
+
+    : /* Output */ [tmp1] "+l" (tmp1), [tmp2] "+l" (tmp2), [data] "+r" (data),
+                   [buf] "+r" (buf), [num_words] "+r" (num_words)
+    : /* Input */ [ack_pin_bb] "r" (ack_pin_bb),
+                  [req_pin_bb] "r" (req_pin_bb),
+                  [out_port_bop] "r"(out_port_bop),
+                  [in_port_istat] "r" (in_port_istat),
+                  [reset_flag] "r" (resetFlag),
+                  [data_in_shift] "I" (SCSI_IN_SHIFT),
+                  [pld1_lo] "I" (SCSI_OUT_PLD1 << 16),
+                  [pld1_hi] "I" (SCSI_OUT_PLD1)
+    : /* Clobber */ );
+
+    SCSI_RELEASE_DATA_REQ();
+
+    // Disable external logic and set REQ pin as output
+    GPIO_BC(SCSI_OUT_PORT) = GREENPAK_PLD_IO2;
+    gpio_mode_set(SCSI_OUT_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, SCSI_OUT_REQ);
+    gpio_output_options_set(SCSI_OUT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SCSI_OUT_REQ);
+    GPIO_BC(SCSI_OUT_PORT) = GREENPAK_PLD_IO3;
+}
+
+#endif

+ 32 - 0
lib/ZuluSCSI_platform_GD32F450/scsi_accel_greenpak.h

@@ -0,0 +1,32 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+// SCSI subroutines using external GreenPAK logic chip for acceleration
+
+#pragma once
+
+#include <stdint.h>
+#include "greenpak.h"
+/*!< Peripheral base address in the bit-band region for a cortex M4 */
+#define PERIPH_BB_BASE        ((uint32_t)0x42000000)    
+
+void scsi_accel_greenpak_send(const uint32_t *buf, uint32_t num_words, volatile int *resetFlag);
+void scsi_accel_greenpak_recv(uint32_t *buf, uint32_t num_words, volatile int *resetFlag);

+ 540 - 0
lib/ZuluSCSI_platform_GD32F450/scsi_accel_sync.cpp

@@ -0,0 +1,540 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+/* Synchronous mode SCSI implementation.
+ *
+ * In synchronous mode, the handshake mechanism is not used. Instead
+ * either end of the communication will just send a bunch of bytes
+ * and only afterwards checks that the number of acknowledgement
+ * pulses matches.
+ * 
+ * The receiving end should latch in the data at the falling edge of
+ * the request pulse (on either REQ or ACK pin). We use the GD32 EXMC
+ * peripheral to implement this latching with the NWAIT pin when
+ * reading data from the host. NOE is used to generate the REQ pulses.
+ * 
+ * Writing data to the host is simpler, as we can just write it out
+ * from the GPIO port at our own pace. A timer is used for generating
+ * the output pulses on REQ pin.
+ */
+
+#include "scsi_accel_sync.h"
+#include <ZuluSCSI_log.h>
+#include <gd32f4xx_exmc.h>
+#include <scsi.h>
+
+#ifndef SCSI_SYNC_MODE_AVAILABLE
+
+void scsi_accel_sync_init() {}
+
+void scsi_accel_sync_recv(uint8_t *data, uint32_t count, int* parityError, volatile int *resetFlag) {}
+void scsi_accel_sync_send(const uint8_t* data, uint32_t count, volatile int *resetFlag) {}
+
+#else
+
+/********************************/
+/* Transfer from host to device */
+/********************************/
+
+#define SYNC_DMA_BUFSIZE 512
+static uint32_t g_sync_dma_buf[SYNC_DMA_BUFSIZE];
+
+void scsi_accel_sync_init()
+{
+    rcu_periph_clock_enable(RCU_EXMC);
+    rcu_periph_clock_enable(SCSI_EXMC_DMA_RCU);
+    rcu_periph_clock_enable(SCSI_SYNC_TIMER_RCU);
+
+    exmc_norsram_timing_parameter_struct timing_param = {
+        .asyn_access_mode = EXMC_ACCESS_MODE_A,
+        .syn_data_latency = EXMC_DATALAT_2_CLK,
+        .syn_clk_division = EXMC_SYN_CLOCK_RATIO_2_CLK,
+        .bus_latency = 1,
+        .asyn_data_setuptime = 2,
+        .asyn_address_holdtime = 2,
+        .asyn_address_setuptime = 16
+    };
+
+    exmc_norsram_parameter_struct sram_param = {
+        .norsram_region = EXMC_BANK0_NORSRAM_REGION0,
+        .write_mode = EXMC_ASYN_WRITE,
+        .extended_mode = DISABLE,
+        .asyn_wait = ENABLE,
+        .nwait_signal = ENABLE,
+        .memory_write = DISABLE,
+        .nwait_config = EXMC_NWAIT_CONFIG_DURING,
+        .wrap_burst_mode = DISABLE,
+        .nwait_polarity = EXMC_NWAIT_POLARITY_HIGH,
+        .burst_mode = DISABLE,
+        .databus_width = EXMC_NOR_DATABUS_WIDTH_16B,
+        .memory_type = EXMC_MEMORY_TYPE_SRAM,
+        .address_data_mux = DISABLE,
+        .read_write_timing = &timing_param
+    };
+
+    EXMC_SNCTL(EXMC_BANK0_NORSRAM_REGION0) &= ~EXMC_SNCTL_NRBKEN;
+    exmc_norsram_init(&sram_param);
+
+    // DMA used to transfer data from EXMC to RAM
+    // DMA is used so that if data transfer fails, we can at least abort by resetting CPU.
+    // Accessing EXMC from the CPU directly hangs it totally if ACK pulses are not received.
+    // TODO: Figure out why DMA does not work correctly with EXMC on GD32F450
+    // dma_single_data_parameter_struct exmc_dma_config =
+    // {
+    //     .periph_addr = EXMC_NOR_PSRAM,
+    //     .periph_inc = DMA_PERIPH_INCREASE_DISABLE,
+    //     .memory0_addr = (uint32_t)g_sync_dma_buf, 
+    //     .memory_inc = DMA_MEMORY_INCREASE_ENABLE,
+    //     .periph_memory_width = DMA_PERIPH_WIDTH_16BIT,
+    //     .circular_mode = DMA_CIRCULAR_MODE_DISABLE,
+    //     .direction = DMA_MEMORY_TO_MEMORY,
+    //     .number = 0, // Filled before transfer
+    //     .priority = DMA_PRIORITY_MEDIUM
+    // };
+    // dma_single_data_mode_init(SCSI_EXMC_DMA, SCSI_EXMC_DMACH, &exmc_dma_config);
+    gpio_mode_set(SCSI_IN_ACK_EXMC_NWAIT_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SCSI_IN_ACK_EXMC_NWAIT_PIN);
+    gpio_af_set(SCSI_IN_ACK_EXMC_NWAIT_PORT, GPIO_AF_12, SCSI_IN_ACK_EXMC_NWAIT_PIN);
+
+    // TIMER1 CH0 port and pin enable
+    gpio_mode_set(SCSI_ACK_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SCSI_ACK_PIN);
+    gpio_af_set(SCSI_ACK_PORT, GPIO_AF_1, SCSI_ACK_PIN);
+    // TIMER1 is used to count ACK pulses
+    TIMER_CTL0(SCSI_SYNC_TIMER) = 0;
+    TIMER_SMCFG(SCSI_SYNC_TIMER) = TIMER_SLAVE_MODE_EXTERNAL0 | TIMER_SMCFG_TRGSEL_CI0FE0;
+    TIMER_CAR(SCSI_SYNC_TIMER) = 65535;
+    TIMER_PSC(SCSI_SYNC_TIMER) = 0;
+    TIMER_CHCTL0(SCSI_SYNC_TIMER) = 0x0001; // CH0 as input
+}
+
+void scsi_accel_sync_recv(uint8_t *data, uint32_t count, int* parityError, volatile int *resetFlag)
+{
+    // Enable EXMC to drive REQ from EXMC_NOE pin
+    EXMC_SNCTL(EXMC_BANK0_NORSRAM_REGION0) |= EXMC_SNCTL_NRBKEN;
+
+    // save GPIO registers to restore after method is done
+    uint32_t oldmode_gpio_ctl = GPIO_CTL(SCSI_OUT_REQ_EXMC_NOE_PORT);
+    uint32_t oldmode_gpio_pud = GPIO_PUD(SCSI_OUT_REQ_EXMC_NOE_PORT);
+    uint32_t oldmode_gpio_ospd = GPIO_OSPD(SCSI_OUT_REQ_EXMC_NOE_PORT);
+    uint32_t oldmode_gpio_omode = GPIO_OMODE(SCSI_OUT_REQ_EXMC_NOE_PORT);
+    uint32_t oldmode_gpio_af = GPIO_AFSEL0(SCSI_OUT_REQ_EXMC_NOE_PORT);
+
+    // @TODO figure out ouput speed should be set to 200MHz
+    gpio_af_set(SCSI_OUT_REQ_EXMC_NOE_PORT, GPIO_AF_12, SCSI_OUT_REQ_EXMC_NOE_PIN);
+    gpio_output_options_set(SCSI_OUT_REQ_EXMC_NOE_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, SCSI_OUT_REQ_EXMC_NOE_PIN);
+    gpio_mode_set(SCSI_OUT_REQ_EXMC_NOE_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, SCSI_OUT_REQ_EXMC_NOE_PIN);
+    
+    while (count > 0)
+    {
+        uint32_t blocksize = (count > SYNC_DMA_BUFSIZE * 2) ? (SYNC_DMA_BUFSIZE * 2) : count;
+        count -= blocksize;
+
+        // dma_memory_address_config(SCSI_EXMC_DMA, SCSI_EXMC_DMACH, 0, (uint32_t)g_sync_dma_buf);
+        // dma_transfer_number_config(SCSI_EXMC_DMA, SCSI_EXMC_DMACH, blocksize);
+        // dma_channel_enable(SCSI_EXMC_DMA, SCSI_EXMC_DMACH);
+
+        uint16_t *src = (uint16_t*)g_sync_dma_buf;
+        uint8_t *dst = data;
+        uint8_t *end = data + blocksize;
+        uint32_t start = millis();
+        while (dst < end)
+        {
+            // Read from EXMC and write to internal RAM
+            // Note that this will hang the CPU if host does not send ACK pulses.
+            uint16_t word = *(uint16_t*)EXMC_NOR_PSRAM;
+            *dst++ = (~word) >> SCSI_EXMC_DATA_SHIFT;
+            
+            // TODO: Figure out why DMA does not work correctly with EXMC on GD32F450
+            // uint32_t remain = DMA_CHCNT(SCSI_EXMC_DMA, SCSI_EXMC_DMACH);
+            // while (dst < end - remain)
+            // {
+            //     *dst++ = ~(*src++) >> SCSI_EXMC_DATA_SHIFT;
+            // }
+            // if ((uint32_t)(millis() - start) > 500 || *resetFlag)
+            // {
+            //     // We are in a pinch here: without ACK pulses coming, the EXMC and DMA peripherals
+            //     // are locked up. The only way out is a whole system reset.
+            //     logmsg("SCSI Synchronous read timeout: resetting system");
+            //     NVIC_SystemReset();
+            // }
+        }
+        // dma_channel_disable(SCSI_EXMC_DMA, SCSI_EXMC_DMACH);
+        data = end;
+    }
+
+    // Set SCSI data IN pins back to input mode
+    gpio_mode_set(SCSI_IN_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, SCSI_IN_MASK);
+    EXMC_SNCTL(EXMC_BANK0_NORSRAM_REGION0) &= ~EXMC_SNCTL_NRBKEN;
+    GPIO_CTL(SCSI_OUT_REQ_EXMC_NOE_PORT) = oldmode_gpio_ctl;
+    GPIO_OSPD(SCSI_OUT_REQ_EXMC_NOE_PORT) = oldmode_gpio_ospd;
+    GPIO_OMODE(SCSI_OUT_REQ_EXMC_NOE_PORT) = oldmode_gpio_omode;
+    GPIO_PUD(SCSI_OUT_REQ_EXMC_NOE_PORT) = oldmode_gpio_pud;
+    GPIO_AFSEL0(SCSI_OUT_REQ_EXMC_NOE_PORT) = oldmode_gpio_af;
+
+
+
+}
+
+/********************************/
+/* Transfer from device to host */
+/********************************/
+
+// Simple delay, about 20 ns.
+// This is less likely to get optimized away by CPU pipeline than nop
+#define ASM_DELAY()  \
+"   ldr     %[tmp2], [%[reset_flag]] \n"
+
+// Take 8 bits from d and format them for writing
+// d is name of data operand, b is bit offset
+#define ASM_LOAD_DATA(b) \
+"        ubfx    %[tmp1], %[data], #" b ", #8 \n" \
+"        ldr     %[tmp1], [%[byte_lookup], %[tmp1], lsl #2] \n"
+
+// Write data to SCSI port and set REQ high
+#define ASM_SEND_DATA() \
+"        str     %[tmp1], [%[out_port_bop]] \n"
+
+// Set REQ low
+#define ASM_SET_REQ_LOW() \
+"        mov     %[tmp2], %[bop_req_low] \n" \
+"        str     %[tmp2], [%[out_port_bop]] \n"
+
+// Wait for ACK_TIMER - n to be less than num_bytes
+#define ASM_WAIT_ACK_TIMER(n) \
+    "wait_acks_" n "_%=: \n" \
+        "   ldr     %[tmp2], [%[ack_timer]] \n" \
+        "   sub     %[tmp2], # " n " \n" \
+        "   cmp     %[tmp2], %[num_bytes] \n" \
+        "   ble     got_acks_" n "_%= \n" \
+        "   ldr     %[tmp2], [%[reset_flag]] \n" \
+        "   cmp     %[tmp2], #0 \n" \
+        "   bne     all_done_%= \n" \
+        "   b       wait_acks_" n "_%= \n" \
+    "got_acks_" n "_%=: \n"
+
+// Send 4 bytes
+#define ASM_SEND_4BYTES() \
+ASM_LOAD_DATA("0") \
+ASM_SEND_DATA() \
+ASM_DELAY1() \
+ASM_SET_REQ_LOW() \
+ASM_DELAY2() \
+ASM_LOAD_DATA("8") \
+ASM_SEND_DATA() \
+ASM_DELAY1() \
+ASM_SET_REQ_LOW() \
+ASM_DELAY2() \
+ASM_LOAD_DATA("16") \
+ASM_SEND_DATA() \
+ASM_DELAY1() \
+ASM_SET_REQ_LOW() \
+ASM_DELAY2() \
+ASM_LOAD_DATA("24") \
+ASM_SEND_DATA() \
+ASM_DELAY1() \
+ASM_SET_REQ_LOW()
+
+// Send 1 byte, wait for ACK_TIMER to be less than num_bytes + n and send 3 bytes more
+// This interleaving minimizes the delay caused by WAIT_ACK_TIMER.
+#define ASM_SEND_4BYTES_WAIT(n) \
+ASM_LOAD_DATA("0") \
+ASM_SEND_DATA() \
+ASM_DELAY2() \
+ASM_LOAD_DATA("8") \
+ASM_SET_REQ_LOW() \
+ASM_DELAY2() \
+"   ldr     %[tmp2], [%[ack_timer]] \n" \
+"   sub     %[tmp2], # " n " \n" \
+ASM_SEND_DATA() \
+"   cmp     %[tmp2], %[num_bytes] \n" \
+"   ble     got_acks_" n "_%= \n" \
+ASM_WAIT_ACK_TIMER(n) \
+ASM_DELAY2() \
+ASM_SET_REQ_LOW() \
+ASM_DELAY2() \
+ASM_LOAD_DATA("16") \
+ASM_SEND_DATA() \
+ASM_DELAY1() \
+ASM_SET_REQ_LOW() \
+ASM_DELAY2() \
+ASM_LOAD_DATA("24") \
+ASM_SEND_DATA() \
+ASM_DELAY1() \
+ASM_SET_REQ_LOW() \
+
+// Specialized routine for settings:
+// <=100 ns period, >=15 outstanding REQs
+static void sync_send_100ns_15off(const uint8_t *buf, uint32_t num_bytes, volatile int *resetFlag)
+{
+    volatile uint32_t *out_port_bop = (volatile uint32_t*)&GPIO_BOP(SCSI_OUT_PORT);
+    volatile uint32_t *ack_timer = &TIMER_CNT(SCSI_SYNC_TIMER);
+    const uint32_t *byte_lookup = g_scsi_out_byte_to_bop;
+    register uint32_t tmp1 = 0;
+    register uint32_t tmp2 = 0;
+    register uint32_t data = 0;
+
+// Delay 1 is typically longest and delay 2 shortest.
+// Tuning these is just trial and error.
+#define ASM_DELAY1() ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY()
+#define ASM_DELAY2() ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY()
+
+    asm volatile (
+    "main_loop_%=: \n"
+        "   subs  %[num_bytes], %[num_bytes], #16 \n"
+        "   bmi     last_bytes_%= \n"
+
+       /* At each point make sure there is at most 15 bytes in flight */
+        "   ldr   %[data], [%[buf]], #4 \n"
+        ASM_SEND_4BYTES_WAIT("26")
+        ASM_DELAY2()
+        "   ldr   %[data], [%[buf]], #4 \n"
+        ASM_SEND_4BYTES_WAIT("22")
+        ASM_DELAY2()
+        "   ldr   %[data], [%[buf]], #4 \n"
+        ASM_SEND_4BYTES_WAIT("18")
+        ASM_DELAY2()
+        "   ldr   %[data], [%[buf]], #4 \n"
+        ASM_SEND_4BYTES_WAIT("14")
+        ASM_DELAY2()
+
+        "   cbz   %[num_bytes], all_done_%= \n"
+        "   b     main_loop_%= \n"
+
+    "last_bytes_%=: \n"
+        "   add  %[num_bytes], %[num_bytes], #16 \n"
+    "last_bytes_loop_%=: \n"
+        "   ldrb    %[data], [%[buf]], #1 \n"
+        ASM_LOAD_DATA("0")
+
+        ASM_WAIT_ACK_TIMER("15")
+        ASM_SEND_DATA()
+        ASM_DELAY1()
+        ASM_SET_REQ_LOW()
+
+        "   subs %[num_bytes], %[num_bytes], #1 \n"
+        "   bne  last_bytes_loop_%= \n"
+    "all_done_%=: \n"
+        ASM_DELAY()
+
+    : /* Output */ [tmp1] "+l" (tmp1), [tmp2] "+l" (tmp2), [data] "+r" (data),
+                   [buf] "+r" (buf), [num_bytes] "+r" (num_bytes)
+    : /* Input */ [ack_timer] "r" (ack_timer),
+                  [bop_req_low] "I" (SCSI_OUT_REQ << 16),
+                  [out_port_bop] "r"(out_port_bop),
+                  [byte_lookup] "r" (byte_lookup),
+                  [reset_flag] "r" (resetFlag)
+    : /* Clobber */);
+
+#undef ASM_DELAY1
+#undef ASM_DELAY2
+
+    SCSI_RELEASE_DATA_REQ();
+}
+
+// Specialized routine for settings:
+// <=200 ns period, >=15 outstanding REQs
+static void sync_send_200ns_15off(const uint8_t *buf, uint32_t num_bytes, volatile int *resetFlag)
+{
+    volatile uint32_t *out_port_bop = (volatile uint32_t*)&GPIO_BOP(SCSI_OUT_PORT);
+    volatile uint32_t *ack_timer = &TIMER_CNT(SCSI_SYNC_TIMER);
+    const uint32_t *byte_lookup = g_scsi_out_byte_to_bop;
+    register uint32_t tmp1 = 0;
+    register uint32_t tmp2 = 0;
+    register uint32_t data = 0;
+
+#define ASM_DELAY1() ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY()
+#define ASM_DELAY2() ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY()
+
+    asm volatile (
+    "main_loop_%=: \n"
+        "   subs  %[num_bytes], %[num_bytes], #16 \n"
+        "   bmi     last_bytes_%= \n"
+
+        /* At each point make sure there is at most 15 bytes in flight */
+        "   ldr   %[data], [%[buf]], #4 \n"
+        ASM_SEND_4BYTES_WAIT("26")
+        ASM_DELAY2()
+        "   ldr   %[data], [%[buf]], #4 \n"
+        ASM_SEND_4BYTES_WAIT("22")
+        ASM_DELAY2()
+        "   ldr   %[data], [%[buf]], #4 \n"
+        ASM_SEND_4BYTES_WAIT("18")
+        ASM_DELAY2()
+        "   ldr   %[data], [%[buf]], #4 \n"
+        ASM_SEND_4BYTES_WAIT("14")
+
+        "   cbz   %[num_bytes], all_done_%= \n"
+        "   b     main_loop_%= \n"
+
+    "last_bytes_%=: \n"
+        "   add  %[num_bytes], %[num_bytes], #16 \n"
+    "last_bytes_loop_%=: \n"
+        "   ldrb    %[data], [%[buf]], #1 \n"
+        ASM_LOAD_DATA("0")
+
+        ASM_WAIT_ACK_TIMER("15")
+        ASM_SEND_DATA()
+        ASM_DELAY1()
+        ASM_SET_REQ_LOW()
+        ASM_DELAY2()
+
+        "   subs %[num_bytes], %[num_bytes], #1 \n"
+        "   bne  last_bytes_loop_%= \n"
+    "all_done_%=: \n"
+        ASM_DELAY1()
+
+    : /* Output */ [tmp1] "+l" (tmp1), [tmp2] "+l" (tmp2), [data] "+r" (data),
+                   [buf] "+r" (buf), [num_bytes] "+r" (num_bytes)
+    : /* Input */ [ack_timer] "r" (ack_timer),
+                  [bop_req_low] "I" (SCSI_OUT_REQ << 16),
+                  [out_port_bop] "r"(out_port_bop),
+                  [byte_lookup] "r" (byte_lookup),
+                  [reset_flag] "r" (resetFlag)
+    : /* Clobber */);
+
+#undef ASM_DELAY1
+#undef ASM_DELAY2
+
+    SCSI_RELEASE_DATA_REQ();
+}
+
+// Specialized routine for settings:
+// <=260 ns period, >=7 outstanding REQs
+static void sync_send_260ns_7off(const uint8_t *buf, uint32_t num_bytes, volatile int *resetFlag)
+{
+    volatile uint32_t *out_port_bop = (volatile uint32_t*)&GPIO_BOP(SCSI_OUT_PORT);
+    volatile uint32_t *ack_timer = &TIMER_CNT(SCSI_SYNC_TIMER);
+    const uint32_t *byte_lookup = g_scsi_out_byte_to_bop;
+    register uint32_t tmp1 = 0;
+    register uint32_t tmp2 = 0;
+    register uint32_t data = 0;
+
+#define ASM_DELAY1() ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY()
+#define ASM_DELAY2() ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY() \
+                     ASM_DELAY() ASM_DELAY() ASM_DELAY() ASM_DELAY()
+
+    asm volatile (
+    "main_loop_%=: \n"
+        "   subs  %[num_bytes], %[num_bytes], #4 \n"
+        "   bmi     last_bytes_%= \n"
+
+        /* At each point make sure there is at most 3 bytes in flight */
+        "   ldr   %[data], [%[buf]], #4 \n"
+        ASM_SEND_4BYTES_WAIT("7")
+        ASM_DELAY2()
+        "   cbz   %[num_bytes], all_done_%= \n"
+        "   b     main_loop_%= \n"
+
+    "last_bytes_%=: \n"
+        "   add  %[num_bytes], %[num_bytes], #4 \n"
+    "last_bytes_loop_%=: \n"
+        "   ldrb    %[data], [%[buf]], #1 \n"
+        ASM_LOAD_DATA("0")
+
+        ASM_WAIT_ACK_TIMER("5")
+        ASM_SEND_DATA()
+        ASM_DELAY1()
+        ASM_SET_REQ_LOW()
+        ASM_DELAY2()
+
+        "   subs %[num_bytes], %[num_bytes], #1 \n"
+        "   bne  last_bytes_loop_%= \n"
+    "all_done_%=: \n"
+        ASM_DELAY1()
+
+    : /* Output */ [tmp1] "+l" (tmp1), [tmp2] "+l" (tmp2), [data] "+r" (data),
+                   [buf] "+r" (buf), [num_bytes] "+r" (num_bytes)
+    : /* Input */ [ack_timer] "r" (ack_timer),
+                  [bop_req_low] "I" (SCSI_OUT_REQ << 16),
+                  [out_port_bop] "r"(out_port_bop),
+                  [byte_lookup] "r" (byte_lookup),
+                  [reset_flag] "r" (resetFlag)
+    : /* Clobber */);
+
+#undef ASM_DELAY1
+#undef ASM_DELAY2
+
+    SCSI_RELEASE_DATA_REQ();
+}
+
+void scsi_accel_sync_send(const uint8_t* data, uint32_t count, volatile int *resetFlag)
+{
+    // Timer counts down from the initial number of bytes.
+    TIMER_CNT(SCSI_SYNC_TIMER) = count;
+    TIMER_CTL0(SCSI_SYNC_TIMER) = TIMER_CTL0_CEN | TIMER_CTL0_DIR;
+
+    int syncOffset = scsiDev.target->syncOffset;
+    int syncPeriod = scsiDev.target->syncPeriod;
+
+    if (syncOffset >= 15 && syncPeriod <= 25)
+    {
+        sync_send_100ns_15off(data, count, resetFlag);
+    }
+    else if (syncOffset >= 15 && syncPeriod <= 50)
+    {
+        sync_send_200ns_15off(data, count, resetFlag);
+    }
+    else if (syncOffset >= 7 && syncPeriod <= 65)
+    {
+        sync_send_260ns_7off(data, count, resetFlag);
+    }
+    else
+    {
+        dbgmsg("No optimized routine for syncOffset=", syncOffset, " syndPeriod=", syncPeriod, ", using fallback");
+        while (count-- > 0)
+        {
+            while (TIMER_CNT(SCSI_SYNC_TIMER) > count + syncOffset && !*resetFlag);
+
+            SCSI_OUT_DATA(*data++);
+            delay_ns(syncPeriod * 2);
+            SCSI_OUT(REQ, 1);
+            delay_ns(syncPeriod * 2);
+        }
+        delay_ns(syncPeriod * 2);
+        SCSI_RELEASE_DATA_REQ();
+    }
+
+    while (TIMER_CNT(SCSI_SYNC_TIMER) > 0 && !*resetFlag);
+
+    if (*resetFlag)
+    {
+        dbgmsg("Bus reset during sync transfer, total ", (int)count,
+              " bytes, remaining ACK count ", (int)TIMER_CNT(SCSI_SYNC_TIMER));
+    }
+
+    TIMER_CTL0(SCSI_SYNC_TIMER) = 0;
+}
+
+
+#endif

+ 38 - 0
lib/ZuluSCSI_platform_GD32F450/scsi_accel_sync.h

@@ -0,0 +1,38 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+// SCSI subroutines that implement synchronous mode SCSI.
+// Uses DMA for data transfer, EXMC for data input and
+// GD32 timer for the REQ pin toggling.
+
+#pragma once
+
+#include <stdint.h>
+#include "ZuluSCSI_platform.h"
+
+#ifdef SCSI_IN_ACK_EXMC_NWAIT_PORT
+#define SCSI_SYNC_MODE_AVAILABLE
+#endif
+
+void scsi_accel_sync_init();
+
+void scsi_accel_sync_recv(uint8_t *data, uint32_t count, int* parityError, volatile int *resetFlag);
+void scsi_accel_sync_send(const uint8_t* data, uint32_t count, volatile int *resetFlag);

+ 345 - 0
lib/ZuluSCSI_platform_GD32F450/sd_card_sdio.cpp

@@ -0,0 +1,345 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+// Driver and interface for accessing SD card in SDIO mode
+// Used on ZuluSCSI v1.4.
+
+#include "ZuluSCSI_platform.h"
+
+#ifdef SD_USE_SDIO
+
+
+extern "C"
+{
+#include "gd32f4xx_sdio.h"
+#include "gd32f4xx_dma.h"
+#include "gd32_sdio_sdcard.h"
+}
+#include "ZuluSCSI_log.h"
+#include <SdFat.h>
+
+static sd_error_enum g_sdio_error = SD_OK;
+static int g_sdio_error_line = 0;
+static uint32_t g_sdio_card_status;
+static uint32_t g_sdio_clk_kHz;
+static sdio_card_type_enum g_sdio_card_type;
+static uint16_t g_sdio_card_rca;
+static uint32_t g_sdio_sector_count;
+
+#define checkReturnOk(call) ((g_sdio_error = (call)) == SD_OK ? true : logSDError(__LINE__))
+static bool logSDError(int line)
+{
+    g_sdio_error_line = line;
+    logmsg("SDIO SD card error on line ", line, ", error code ", (int)g_sdio_error);
+    return false;
+}
+
+bool SdioCard::begin(SdioConfig sdioConfig)
+{
+    rcu_periph_clock_enable(RCU_SDIO);
+    rcu_periph_clock_enable(RCU_DMA1);
+    nvic_irq_enable(SDIO_IRQn, 0, 0);
+
+    g_sdio_error = sd_init();
+    if (g_sdio_error != SD_OK)
+    {
+        // Don't spam the log when main program polls for card insertion.
+        dbgmsg("sd_init() failed: ", (int)g_sdio_error);
+        return false;
+    }
+
+    return checkReturnOk(sd_card_information_get_short(&g_sdio_card_type, &g_sdio_card_rca))
+        && checkReturnOk(sd_card_select_deselect(g_sdio_card_rca))
+        && checkReturnOk(sd_cardstatus_get(&g_sdio_card_status))
+        && checkReturnOk(sd_bus_mode_config(SDIO_BUSMODE_4BIT))
+        && checkReturnOk(sd_transfer_mode_config(sdioConfig.useDma() ? SD_DMA_MODE : SD_POLLING_MODE))
+        && (g_sdio_sector_count = sectorCount());
+}
+
+uint8_t SdioCard::errorCode() const
+{
+    if (g_sdio_error == SD_OK)
+        return SD_CARD_ERROR_NONE;
+    else
+        return 0x80 + g_sdio_error;
+}
+
+uint32_t SdioCard::errorData() const
+{
+    return 0;
+}
+
+uint32_t SdioCard::errorLine() const
+{
+    return g_sdio_error_line;
+}
+
+bool SdioCard::isBusy() 
+{
+    return (GPIO_ISTAT(SD_SDIO_DATA_PORT) & SD_SDIO_D0) == 0;
+}
+
+uint32_t SdioCard::kHzSdClk()
+{
+    return g_sdio_clk_kHz;
+}
+
+bool SdioCard::readCID(cid_t* cid)
+{
+    sd_cid_get((uint8_t*)cid);
+    return true;
+}
+
+bool SdioCard::readCSD(csd_t* csd)
+{
+    sd_csd_get((uint8_t*)csd);
+    return true;
+}
+
+bool SdioCard::readOCR(uint32_t* ocr)
+{
+    // SDIO mode does not have CMD58, but main program uses this to
+    // poll for card presence. Return status register instead.
+    return sd_cardstatus_get(ocr) == SD_OK;
+}
+
+bool SdioCard::readData(uint8_t* dst)
+{
+    logmsg("SdioCard::readData() called but not implemented!");
+    return false;
+}
+
+bool SdioCard::readStart(uint32_t sector)
+{
+    logmsg("SdioCard::readStart() called but not implemented!");
+    return false;
+}
+
+bool SdioCard::readStop()
+{
+    logmsg("SdioCard::readStop() called but not implemented!");
+    return false;
+}
+
+uint32_t SdioCard::sectorCount()
+{
+    csd_t csd;
+    sd_csd_get((uint8_t*)&csd);
+    return csd.capacity();
+}
+
+uint32_t SdioCard::status()
+{
+    uint32_t status = 0;
+    if (!checkReturnOk(sd_cardstatus_get(&status)))
+        return 0;
+    else
+        return status;
+}
+
+bool SdioCard::stopTransmission(bool blocking)
+{
+    if (!checkReturnOk(sd_transfer_stop()))
+        return false;
+
+    if (!blocking)
+    {
+        return true;
+    }
+    else
+    {
+        uint32_t end = millis() + 100;
+        while (millis() < end && isBusy())
+        {
+        }
+        if (isBusy())
+        {
+            logmsg("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)
+{
+    logmsg("SdioCard::writeData() called but not implemented!");
+    return false;
+}
+
+bool SdioCard::writeStart(uint32_t sector)
+{
+    logmsg("SdioCard::writeStart() called but not implemented!");
+    return false;
+}
+
+bool SdioCard::writeStop()
+{
+    logmsg("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));
+}
+
+bool SdioCard::cardCMD6(uint32_t arg, uint8_t* status) {
+    logmsg("SdioCard::cardCMD6() not implemented");
+    return false;
+}
+
+bool SdioCard::readSCR(scr_t* scr) {
+    logmsg("SdioCard::readSCR() not implemented");
+    return false;
+}
+
+/* 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 platform_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, const char *accesstype, uint32_t sector)
+{
+    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
+        {
+            dbgmsg("SD card ", accesstype, "(", (int)sector,
+                  ") slow transfer, buffer", (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, "writeSector", sector)));
+}
+
+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, "writeSectors", sector)));
+}
+
+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, "readSector", sector)));
+}
+
+bool SdioCard::readSectors(uint32_t sector, uint8_t* dst, size_t n)
+{
+    if (sector + n >= g_sdio_sector_count)
+    {
+        // sd_multiblocks_read() seems to have trouble reading the very last sector
+        for (int i = 0; i < n; i++)
+        {
+            if (!readSector(sector + i, dst + i * 512))
+            {
+                logmsg("End of drive read failed at ", sector, " + ", i);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    return checkReturnOk(sd_multiblocks_read((uint32_t*)dst, (uint64_t)sector * 512, 512, n,
+        get_stream_callback(dst, n * 512, "readSectors", sector)));
+}
+
+// Check if a DMA request for SD card read has completed.
+// This is used to optimize the timing of data transfers on SCSI bus.
+bool check_sd_read_done()
+{
+    return (DMA_CHCTL(DMA1, DMA_CH3) & DMA_CHXCTL_CHEN)
+        && (DMA_INTF0(DMA1) & DMA_FLAG_ADD(DMA_FLAG_FTF, DMA_CH3));
+}
+
+// These functions are not used for SDIO mode but are needed to avoid build error.
+void sdCsInit(SdCsPin_t pin) {}
+void sdCsWrite(SdCsPin_t pin, bool level) {}
+
+// Interrupt handler for SDIO
+extern "C" void SDIO_IRQHandler(void)
+{
+    sd_interrupts_process();
+}
+
+// SDIO configuration for main program
+SdioConfig g_sd_sdio_config(DMA_SDIO);
+
+// SDIO configuration in crash
+SdioConfig g_sd_sdio_config_crash(0);
+
+#endif

+ 192 - 0
lib/ZuluSCSI_platform_GD32F450/usb_conf.h

@@ -0,0 +1,192 @@
+/*!
+    \file    usb_conf.h
+    \brief   USB core driver basic configuration
+
+    \version 2020-08-01, V3.0.0, firmware for GD32F4xx
+    \version 2022-03-09, V3.1.0, firmware for GD32F4xx
+    \version 2022-06-30, V3.2.0, firmware for GD32F4xx
+*/
+
+/*
+    Copyright (c) 2022, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this 
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice, 
+       this list of conditions and the following disclaimer in the documentation 
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors 
+       may be used to endorse or promote products derived from this software without 
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USB_CONF_H
+#define __USB_CONF_H
+
+#include <stdlib.h>
+#include "gd32f4xx.h"
+
+
+/* USB Core and PHY interface configuration */
+
+/****************** USB FS PHY CONFIGURATION *******************************
+ *  The USB FS Core supports one on-chip Full Speed PHY.
+ *  The USE_EMBEDDED_PHY symbol is defined in the project compiler preprocessor
+ *  when FS core is used.
+*******************************************************************************/
+#define USE_USB_HS
+#define USE_ULPI_PHY
+
+#ifdef USE_USB_FS
+    #define USB_FS_CORE
+#endif /* USE_USB_FS */
+
+#ifdef USE_USB_HS
+    #define USB_HS_CORE
+#endif /* USE_USB_HS */
+
+/*******************************************************************************
+ *                      FIFO Size Configuration in Device mode
+ *
+ *  (i) Receive data FIFO size = RAM for setup packets + 
+ *                   OUT endpoint control information +
+ *                   data OUT packets + miscellaneous
+ *      Space = ONE 32-bits words
+ *      --> RAM for setup packets = 10 spaces
+ *          (n is the nbr of CTRL EPs the device core supports)
+ *      --> OUT EP CTRL info = 1 space
+ *          (one space for status information written to the FIFO along with each 
+ *          received packet)
+ *      --> Data OUT packets = (Largest Packet Size / 4) + 1 spaces 
+ *          (MINIMUM to receive packets)
+ *      --> OR data OUT packets  = at least 2* (Largest Packet Size / 4) + 1 spaces 
+ *          (if high-bandwidth EP is enabled or multiple isochronous EPs)
+ *      --> Miscellaneous = 1 space per OUT EP
+ *          (one space for transfer complete status information also pushed to the 
+ *          FIFO with each endpoint's last packet)
+ *
+ *  (ii) MINIMUM RAM space required for each IN EP Tx FIFO = MAX packet size for 
+ *       that particular IN EP. More space allocated in the IN EP Tx FIFO results
+ *       in a better performance on the USB and can hide latencies on the AHB.
+ *
+ *  (iii) TXn min size = 16 words. (n:Transmit FIFO index)
+ *
+ *  (iv) When a TxFIFO is not used, the Configuration should be as follows:
+ *       case 1: n > m and Txn is not used (n,m:Transmit FIFO indexes)
+ *       --> Txm can use the space allocated for Txn.
+ *       case 2: n < m and Txn is not used (n,m:Transmit FIFO indexes)
+ *       --> Txn should be configured with the minimum space of 16 words
+ *
+ *  (v) The FIFO is used optimally when used TxFIFOs are allocated in the top 
+ *      of the FIFO.Ex: use EP1 and EP2 as IN instead of EP1 and EP3 as IN ones.
+ *
+ *  (vi) In HS case12 FIFO locations should be reserved for internal DMA registers
+ *       so total FIFO size should be 1012 Only instead of 1024
+*******************************************************************************/
+
+#ifdef USB_FS_CORE
+    #define RX_FIFO_FS_SIZE                         128
+    #define TX0_FIFO_FS_SIZE                        64
+    #define TX1_FIFO_FS_SIZE                        64
+    #define TX2_FIFO_FS_SIZE                        64
+    #define TX3_FIFO_FS_SIZE                        0
+
+    #define USBFS_SOF_OUTPUT                        0
+    #define USBFS_LOW_POWER                         0
+#endif /* USB_FS_CORE */
+
+#ifdef USB_HS_CORE
+    #define RX_FIFO_HS_SIZE                          512
+    #define TX0_FIFO_HS_SIZE                         128
+    #define TX1_FIFO_HS_SIZE                         128
+    #define TX2_FIFO_HS_SIZE                         128
+    #define TX3_FIFO_HS_SIZE                         0
+    #define TX4_FIFO_HS_SIZE                         0
+    #define TX5_FIFO_HS_SIZE                         0
+
+    #ifdef USE_ULPI_PHY
+        #define USB_ULPI_PHY_ENABLED
+    #endif
+
+    #ifdef USE_EMBEDDED_PHY
+        #define USB_EMBEDDED_PHY_ENABLED
+    #endif
+
+    #define USB_HS_INTERNAL_DMA_ENABLED
+//    #define USB_HS_DEDICATED_EP1_ENABLED
+
+    #define USB_SOF_OUTPUT                        0
+    #define USB_LOW_POWER                         0
+#endif /* USB_HS_CORE */
+
+//#define VBUS_SENSING_ENABLED
+
+//#define USE_HOST_MODE
+#define USE_DEVICE_MODE
+//#define USE_OTG_MODE
+
+#ifndef USB_FS_CORE
+    #ifndef USB_HS_CORE
+        #error  "USB_HS_CORE or USB_FS_CORE should be defined!"
+    #endif
+#endif
+
+#ifndef USE_DEVICE_MODE
+    #ifndef USE_HOST_MODE
+        #error  "USE_DEVICE_MODE or USE_HOST_MODE should be defined!"
+    #endif
+#endif
+
+#ifndef USE_USB_HS
+    #ifndef USE_USB_FS
+        #error  "USE_USB_HS or USE_USB_FS should be defined!"
+    #endif
+#endif
+
+/* In HS mode and when the DMA is used, all variables and data structures dealing
+   with the DMA during the transaction process should be 4-bytes aligned */
+
+#ifdef USB_HS_INTERNAL_DMA_ENABLED
+    #if defined (__GNUC__)         /* GNU Compiler */
+        #define __ALIGN_END __attribute__ ((aligned (4)))
+        #define __ALIGN_BEGIN
+    #else
+        #define __ALIGN_END
+
+        #if defined (__CC_ARM)     /* ARM Compiler */
+            #define __ALIGN_BEGIN __align(4)  
+        #elif defined (__ICCARM__) /* IAR Compiler */
+            #define __ALIGN_BEGIN 
+        #elif defined (__TASKING__)/* TASKING Compiler */
+            #define __ALIGN_BEGIN __align(4) 
+        #endif /* __CC_ARM */  
+    #endif /* __GNUC__ */ 
+#else
+    #define __ALIGN_BEGIN
+    #define __ALIGN_END
+#endif /* USB_OTG_HS_INTERNAL_DMA_ENABLED */
+
+/* __packed keyword used to decrease the data type alignment to 1-byte */
+#if defined (__GNUC__)       /* GNU Compiler */
+    #ifndef __packed
+        #define __packed __attribute__ ((__packed__))
+    #endif
+#elif defined (__TASKING__)    /* TASKING Compiler */
+    #define __packed __unaligned
+#endif /* __CC_ARM */
+
+#endif /* __USB_CONF_H */

+ 157 - 0
lib/ZuluSCSI_platform_GD32F450/usb_hs.cpp

@@ -0,0 +1,157 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+
+#include "usb_hs.h"
+#include "ZuluSCSI_v1_4_gpio.h"
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_log.h"
+
+extern "C" 
+{
+#include <gd32_cdc_acm_core.h>
+#include <gd32f4xx_gpio.h>
+#include <drv_usb_hw.h>
+#include <drv_usb_core.h>
+#include <drv_usbd_int.h>
+usb_core_driver cdc_acm;
+}
+
+
+void usb_hs_init(void)
+{
+    // USB HS clock
+    rcu_periph_clock_enable(RCU_SYSCFG);
+    rcu_periph_clock_enable(RCU_USBHSULPI);
+    rcu_periph_clock_enable(RCU_USBHS);
+
+    // usb_clock_active(&cdc_acm);
+
+    // USB HS pins    
+    gpio_mode_set(USB_HS_ULPI_CLK_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, USB_HS_ULPI_CLK_PIN);
+    gpio_mode_set(USB_HS_ULPI_DIR_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, USB_HS_ULPI_DIR_PIN);
+    gpio_mode_set(USB_HS_ULPI_NXT_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, USB_HS_ULPI_NXT_PIN);
+    gpio_mode_set(USB_HS_ULPI_STP_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, USB_HS_ULPI_STP_PIN);
+    gpio_mode_set(USB_HS_ULPI_D0_PORT,  GPIO_MODE_AF, GPIO_PUPD_NONE, USB_HS_ULPI_D0_PIN);
+    gpio_mode_set(USB_HS_ULPI_D1_TO_7_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, USB_HS_ULPI_D1_TO_7_PIN);
+    
+    gpio_output_options_set(USB_HS_ULPI_CLK_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, USB_HS_ULPI_CLK_PIN);
+    gpio_output_options_set(USB_HS_ULPI_DIR_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, USB_HS_ULPI_DIR_PIN);
+    gpio_output_options_set(USB_HS_ULPI_NXT_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, USB_HS_ULPI_NXT_PIN);
+    gpio_output_options_set(USB_HS_ULPI_STP_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, USB_HS_ULPI_STP_PIN);
+    gpio_output_options_set(USB_HS_ULPI_D0_PORT,  GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, USB_HS_ULPI_D0_PIN);
+    gpio_output_options_set(USB_HS_ULPI_D1_TO_7_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_200MHZ, USB_HS_ULPI_D1_TO_7_PIN);
+
+    gpio_af_set(USB_HS_ULPI_CLK_PORT, USB_HS_ULPI_AF, USB_HS_ULPI_CLK_PIN);
+    gpio_af_set(USB_HS_ULPI_DIR_PORT, USB_HS_ULPI_AF, USB_HS_ULPI_DIR_PIN);
+    gpio_af_set(USB_HS_ULPI_NXT_PORT, USB_HS_ULPI_AF, USB_HS_ULPI_NXT_PIN);
+    gpio_af_set(USB_HS_ULPI_STP_PORT, USB_HS_ULPI_AF, USB_HS_ULPI_STP_PIN);
+    gpio_af_set(USB_HS_ULPI_D0_PORT,  USB_HS_ULPI_AF, USB_HS_ULPI_D0_PIN);
+    gpio_af_set(USB_HS_ULPI_D1_TO_7_PORT, USB_HS_ULPI_AF, USB_HS_ULPI_D1_TO_7_PIN);
+
+    
+    usbd_init(&cdc_acm, USB_CORE_ENUM_HS, &gd32_cdc_desc, &gd32_cdc_class);
+
+    // USB HS Interrupt config
+    nvic_irq_enable((uint8_t)USBHS_IRQn, 0U, 0U);
+    
+#ifdef USB_HS_DEDICATED_EP1_ENABLED
+    nvic_irq_enable(USBHS_EP1_Out_IRQn, 3, 0);
+    nvic_irq_enable(USBHS_EP1_In_IRQn, 3, 0);
+#endif /* USB_HS_DEDICATED_EP1_ENABLED */
+
+    // USB HS wake functionality 
+    /* enable the power module clock */
+    // rcu_periph_clock_enable(RCU_PMU);
+
+    
+    /* USB wakeup EXTI line configuration */
+    // exti_interrupt_flag_clear(EXTI_18);
+    // exti_init(EXTI_18, EXTI_INTERRUPT, EXTI_TRIG_RISING);
+    // exti_interrupt_enable(EXTI_18);
+
+    // nvic_irq_enable((uint8_t)USBFS_WKUP_IRQn, 1U, 0U);
+
+}
+
+bool usb_hs_ready(void)
+{
+    if (USBD_CONFIGURED == cdc_acm.dev.cur_status) 
+    {
+        if (1U == gd32_cdc_acm_check_ready(&cdc_acm)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void usb_hs_send(uint8_t *data, uint32_t length)
+{
+    gd32_cdc_acm_data_send(&cdc_acm, data, length);
+}
+
+extern "C" {
+void usb_udelay (const uint32_t usec) 
+{
+    delay_us(usec);
+}
+
+void usb_mdelay(const uint32_t msec)
+{
+    delay(msec);
+}
+
+
+
+void USBHS_IRQHandler(void)
+{
+    usbd_isr(&cdc_acm);
+}
+
+#ifdef USB_HS_DEDICATED_EP1_ENABLED
+
+/*!
+    \brief      this function handles EP1_IN Handler
+    \param[in]  none
+    \param[out] none
+    \retval     none
+*/
+__attribute__((interrupt, naked))
+void USBHS_EP1_In_IRQHandler(void)
+{
+    usbd_int_dedicated_ep1in (&cdc_acm);
+}
+
+/*!
+    \brief      this function handles EP1_OUT Handler
+    \param[in]  none
+    \param[out] none
+    \retval     none
+*/
+__attribute__((interrupt, naked))
+void USBHS_EP1_Out_IRQHandler(void)
+{
+    usbd_int_dedicated_ep1out (&cdc_acm);
+}
+
+#endif /* USB_HS_DEDICATED_EP1_ENABLED */
+
+}

+ 40 - 0
lib/ZuluSCSI_platform_GD32F450/usb_hs.h

@@ -0,0 +1,40 @@
+/** 
+ * ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
+ * 
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#pragma once
+
+#include <gd32f4xx.h>
+
+void usb_hs_init(void);
+bool usb_hs_ready(void);
+void usb_hs_send(uint8_t *data, uint32_t length);
+
+extern "C"
+{
+// Interrupt handlers
+void USBHS_IRQHandler(void);
+#ifdef USB_HS_DEDICATED_EP1_ENABLED
+/* this function handles EP1_IN IRQ Handler */
+void USBHS_EP1_In_IRQHandler(void);
+/* this function handles EP1_OUT IRQ Handler */
+void USBHS_EP1_Out_IRQHandler(void);
+#endif /* USB_HS_DEDICATED_EP1_ENABLED */
+}

+ 75 - 0
lib/ZuluSCSI_platform_GD32F450/usbd_conf.h

@@ -0,0 +1,75 @@
+/*!
+    \file    usbd_conf.h
+    \brief   the header file of USB device configuration
+
+    \version 2020-08-01, V3.0.0, firmware for GD32F4xx
+    \version 2022-03-09, V3.1.0, firmware for GD32F4xx
+    \version 2022-06-30, V3.2.0, firmware for GD32F4xx
+*/
+
+/*
+    Copyright (c) 2022, GigaDevice Semiconductor Inc.
+
+    Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice, this 
+       list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright notice, 
+       this list of conditions and the following disclaimer in the documentation 
+       and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holder nor the names of its contributors 
+       may be used to endorse or promote products derived from this software without 
+       specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
+OF SUCH DAMAGE.
+*/
+
+#ifndef __USBD_CONF_H
+#define __USBD_CONF_H
+
+#include "usb_conf.h"
+
+#define USBD_CFG_MAX_NUM                    1
+#define USBD_ITF_MAX_NUM                    1
+
+#define CDC_COM_INTERFACE                   0
+
+#define USB_STR_DESC_MAX_SIZE               255
+
+#define CDC_DATA_IN_EP                      EP1_IN  /* EP1 for data IN */
+#define CDC_DATA_OUT_EP                     EP3_OUT /* EP3 for data OUT */
+#define CDC_CMD_EP                          EP2_IN  /* EP2 for CDC commands */
+
+#define USB_STRING_COUNT                    4
+
+#define USB_CDC_CMD_PACKET_SIZE             8    /* Control Endpoint Packet size */
+
+#define APP_RX_DATA_SIZE                    2048 /* Total size of IN buffer: 
+                                                    APP_RX_DATA_SIZE*8 / MAX_BAUDARATE * 1000 should be > CDC_IN_FRAME_INTERVAL*8 */
+
+/* CDC endpoints parameters: you can fine tune these values depending on the needed baud rate and performance */
+#ifdef USE_USB_HS
+    #ifdef USE_ULPI_PHY
+        #define USB_CDC_DATA_PACKET_SIZE        512  /* Endpoint IN & OUT Packet size */
+        #define USB_CDC_EP_IN_WORKING_SIZE      64   /* Sending a packet to host USB_CDC_DATA_PACKET_SIZE doesn't work, this value does*/
+        #define CDC_IN_FRAME_INTERVAL           40   /* Number of micro-frames between IN transfers */
+    #else
+        #define USB_CDC_DATA_PACKET_SIZE        64   /* Endpoint IN & OUT Packet size */
+        #define CDC_IN_FRAME_INTERVAL           5    /* Number of frames between IN transfers */
+    #endif
+#else
+    #define USB_CDC_DATA_PACKET_SIZE            64   /* Endpoint IN & OUT Packet size */
+    #define CDC_IN_FRAME_INTERVAL               5    /* Number of frames between IN transfers */
+#endif /* USE_USB_HS */
+
+#endif /* __USBD_CONF_H */

+ 175 - 0
lib/ZuluSCSI_platform_GD32F450/zuluscsi_gd32f450.ld

@@ -0,0 +1,175 @@
+/*
+ *
+ * Customized linker script for combining bootloader & main program
+ * PlatformIO default linker script template for STM32 F1/F2/F3/F4/F7/L0/L1/L4
+ *
+ */
+
+/* Entry Point */
+ENTRY(Reset_Handler)
+
+/* Highest address of the user mode stack */ 
+_estack = 0x20030000;    /* end of RAM */
+/* Generate a link error if heap and stack don't fit into RAM */
+_Min_Heap_Size = 0x200;      /* required amount of heap  */
+_Min_Stack_Size = 0x400;     /* required amount of stack */
+
+/* Specify the memory areas */
+MEMORY
+{
+    RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 192K
+    CCRAM (xrw)    : ORIGIN = 0x10000000, LENGTH = 64K
+    FLASH (rx)     : ORIGIN = 0x08000000, LENGTH = 512K
+}
+
+/* Define output sections */
+SECTIONS
+{
+  /* If bootloader is included, it goes in first 32 kB */
+  .text.bootloader : ALIGN(16) SUBALIGN(16)
+  {
+    KEEP(*(.text.btldr*))
+    . = ALIGN(32768);
+    CHECK_BOOTLOADER_SIZE = 1 / (. <= 32768);
+  } >FLASH
+  
+  /* The startup code goes first into FLASH */
+  .isr_vector :
+  {
+    . = ALIGN(4);
+    KEEP(*(.isr_vector)) /* Startup code */
+    . = ALIGN(4);
+  } >FLASH
+
+  /* The program code and other data goes into FLASH */
+  .text :
+  {
+    . = ALIGN(4);
+    *(.text)           /* .text sections (code) */
+    *(.text*)          /* .text* sections (code) */
+    *(.glue_7)         /* glue arm to thumb code */
+    *(.glue_7t)        /* glue thumb to arm code */
+    *(.eh_frame)
+
+    KEEP (*(.init))
+    KEEP (*(.fini))
+
+    . = ALIGN(4);
+    _etext = .;        /* define a global symbols at end of code */
+  } >FLASH
+
+  /* Constant data goes into FLASH */
+  .rodata :
+  {
+    . = ALIGN(4);
+    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
+    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
+    . = ALIGN(4);
+  } >FLASH
+
+  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
+  .ARM : {
+    __exidx_start = .;
+    *(.ARM.exidx*)
+    __exidx_end = .;
+  } >FLASH
+
+  .preinit_array     :
+  {
+    PROVIDE_HIDDEN (__preinit_array_start = .);
+    KEEP (*(.preinit_array*))
+    PROVIDE_HIDDEN (__preinit_array_end = .);
+  } >FLASH
+  .init_array :
+  {
+    PROVIDE_HIDDEN (__init_array_start = .);
+    KEEP (*(SORT(.init_array.*)))
+    KEEP (*(.init_array*))
+    PROVIDE_HIDDEN (__init_array_end = .);
+  } >FLASH
+  .fini_array :
+  {
+    PROVIDE_HIDDEN (__fini_array_start = .);
+    KEEP (*(SORT(.fini_array.*)))
+    KEEP (*(.fini_array*))
+    PROVIDE_HIDDEN (__fini_array_end = .);
+  } >FLASH
+
+  /* used by the startup to initialize data */
+  _sidata = LOADADDR(.data);
+
+  /* Initialized data sections goes into RAM, load LMA copy after code */
+  .data : 
+  {
+    . = ALIGN(4);
+    _sdata = .;        /* create a global symbol at data start */
+    *(.data)           /* .data sections */
+    *(.data*)          /* .data* sections */
+
+    . = ALIGN(4);
+    _edata = .;        /* define a global symbol at data end */
+  } >RAM AT> FLASH
+  
+  /* Uninitialized data section */
+  . = ALIGN(4);
+  .bss :
+  {
+    /* This is used by the startup in order to initialize the .bss secion */
+    _sbss = .;         /* define a global symbol at bss start */
+    __bss_start__ = _sbss;
+    *(.bss)
+    *(.bss*)
+    *(COMMON)
+
+    . = ALIGN(4);
+    _ebss = .;         /* define a global symbol at bss end */
+    __bss_end__ = _ebss;
+  } >RAM
+
+  /* User_heap_stack section, used to check that there is enough RAM left */
+  ._user_heap_stack :
+  {
+    . = ALIGN(4);
+    PROVIDE ( end = . );
+    PROVIDE ( _end = . );
+    . = . + _Min_Heap_Size;
+    . = . + _Min_Stack_Size;
+    . = ALIGN(4);
+  } >RAM
+
+  /* uninitialized CCRAM objects (like, buffers) */
+  .ccram_bss :
+  {
+    __ccram_start_bss__ = .; /* define a global symbol at ccram start */
+    KEEP(*(.ccram_bss))
+    KEEP(*(.ccram_bss*))
+    . = ALIGN(4);
+    __ccram_end_bss__ = .; /* define a global symbol at end of *used* CCRAM (BSS) */
+  } >CCRAM
+
+  /* initialized CCRAM objects (like, initialized variables) */
+  _si_ccram_data = LOADADDR(.ccram_data);
+  .ccram_data :
+  {
+    . = ALIGN(4);
+    _ccram_start_data = .;    /* create a global symbol at data start */
+    *(.ccram_bss)             /* .data sections */
+    *(.ccram_bss*)            /* .data* sections */
+
+    . = ALIGN(4);
+    _ccram_end_data = .;      /* define a global symbol at data end */
+  } >CCRAM AT> FLASH
+  
+
+  /* Remove information from the standard libraries */
+  /DISCARD/ :
+  {
+    libc.a ( * )
+    libm.a ( * )
+    libgcc.a ( * )
+  }
+
+  .ARM.attributes 0 : { *(.ARM.attributes) }
+}
+
+

+ 185 - 0
lib/ZuluSCSI_platform_GD32F450/zuluscsi_gd32f450_btldr.ld

@@ -0,0 +1,185 @@
+/*
+ *
+ * Customized linker script for building bootloader
+ *
+ */
+
+/* Entry Point */
+ENTRY(Reset_Handler)
+
+/* Highest address of the user mode stack */ 
+_estack = 0x20030000;    /* end of RAM */
+/* Generate a link error if heap and stack don't fit into RAM */
+_Min_Heap_Size = 0x200;      /* required amount of heap  */
+_Min_Stack_Size = 0x400;     /* required amount of stack */
+
+/* Specify the memory areas */
+MEMORY
+{
+    RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 192K
+    CCRAM (xrw)    : ORIGIN = 0x10000000, LENGTH = 64K
+    FLASH (rx)     : ORIGIN = 0x08000000, LENGTH = 512K
+}
+
+/* Define output sections */
+SECTIONS
+{
+  /* Discard interrupt vectors that are not needed for bootloader.
+   * This way unnecessary code doesn't get pulled in.
+   */
+  /DISCARD/ :
+  {
+    *(*USBHS_IRQHandler*)
+    *(*DMA1_Channel4_IRQHandler*)
+    *(*DMA1_Channel2_IRQHandler*)
+    *(*EXTI3_IRQHandler*)
+    *(*EXTI10_15_IRQHandler*)
+  }
+
+
+  USBHS_IRQHandler = 0;
+  DMA1_Channel2_IRQHandler = 0;
+  DMA1_Channel4_IRQHandler = 0;
+  EXTI10_15_IRQHandler = 0;
+  EXTI3_IRQHandler = 0;
+
+  /* The startup code goes first into FLASH */
+  .isr_vector :
+  {
+    . = ALIGN(4);
+    KEEP(*(.isr_vector)) /* Startup code */
+    . = ALIGN(4);
+  } >FLASH
+
+  /* The program code and other data goes into FLASH */
+  .text :
+  {
+    . = ALIGN(4);
+    *(.text)           /* .text sections (code) */
+    *(.text*)          /* .text* sections (code) */
+    *(.glue_7)         /* glue arm to thumb code */
+    *(.glue_7t)        /* glue thumb to arm code */
+    *(.eh_frame)
+
+    KEEP (*(.init))
+    KEEP (*(.fini))
+
+    . = ALIGN(4);
+    _etext = .;        /* define a global symbols at end of code */
+  } >FLASH
+
+  /* Constant data goes into FLASH */
+  .rodata :
+  {
+    . = ALIGN(4);
+    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
+    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
+    . = ALIGN(4);
+  } >FLASH
+
+  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
+  .ARM : {
+    __exidx_start = .;
+    *(.ARM.exidx*)
+    __exidx_end = .;
+  } >FLASH
+
+  .preinit_array     :
+  {
+    PROVIDE_HIDDEN (__preinit_array_start = .);
+    KEEP (*(.preinit_array*))
+    PROVIDE_HIDDEN (__preinit_array_end = .);
+  } >FLASH
+  .init_array :
+  {
+    PROVIDE_HIDDEN (__init_array_start = .);
+    KEEP (*(SORT(.init_array.*)))
+    KEEP (*(.init_array*))
+    PROVIDE_HIDDEN (__init_array_end = .);
+  } >FLASH
+  .fini_array :
+  {
+    PROVIDE_HIDDEN (__fini_array_start = .);
+    KEEP (*(SORT(.fini_array.*)))
+    KEEP (*(.fini_array*))
+    PROVIDE_HIDDEN (__fini_array_end = .);
+  } >FLASH
+
+  /* used by the startup to initialize data */
+  _sidata = LOADADDR(.data);
+
+  /* Initialized data sections goes into RAM, load LMA copy after code */
+  .data : 
+  {
+    . = ALIGN(4);
+    _sdata = .;        /* create a global symbol at data start */
+    *(.data)           /* .data sections */
+    *(.data*)          /* .data* sections */
+
+    . = ALIGN(4);
+    _edata = .;        /* define a global symbol at data end */
+  } >RAM AT> FLASH
+  
+  /* Uninitialized data section */
+  . = ALIGN(4);
+  .bss :
+  {
+    /* This is used by the startup in order to initialize the .bss secion */
+    _sbss = .;         /* define a global symbol at bss start */
+    __bss_start__ = _sbss;
+    *(.bss)
+    *(.bss*)
+    *(COMMON)
+
+    . = ALIGN(4);
+    _ebss = .;         /* define a global symbol at bss end */
+    __bss_end__ = _ebss;
+  } >RAM
+
+  /* User_heap_stack section, used to check that there is enough RAM left */
+  ._user_heap_stack :
+  {
+    . = ALIGN(4);
+    PROVIDE ( end = . );
+    PROVIDE ( _end = . );
+    . = . + _Min_Heap_Size;
+    . = . + _Min_Stack_Size;
+    . = ALIGN(4);
+  } >RAM
+
+  /* uninitialized CCRAM objects (like, buffers) */
+  .ccram_bss :
+  {
+    __ccram_start_bss__ = .; /* define a global symbol at ccram start */
+    KEEP(*(.ccram_bss))
+    KEEP(*(.ccram_bss*))
+    . = ALIGN(4);
+    __ccram_end_bss__ = .; /* define a global symbol at end of *used* CCRAM (BSS) */
+  } >CCRAM
+
+  /* initialized CCRAM objects (like, initialized variables) */
+  _si_ccram_data = LOADADDR(.ccram_data);
+  .ccram_data :
+  {
+    . = ALIGN(4);
+    _ccram_start_data = .;    /* create a global symbol at data start */
+    *(.ccram_bss)             /* .data sections */
+    *(.ccram_bss*)            /* .data* sections */
+
+    . = ALIGN(4);
+    _ccram_end_data = .;      /* define a global symbol at data end */
+  } >CCRAM AT> FLASH
+  
+
+  /* Remove information from the standard libraries */
+  /DISCARD/ :
+  {
+    libc.a ( * )
+    libm.a ( * )
+    libgcc.a ( * )
+  }
+
+  .ARM.attributes 0 : { *(.ARM.attributes) }
+}
+
+

+ 81 - 52
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp

@@ -124,6 +124,49 @@ static void reclock_for_audio() {
 }
 }
 #endif  // ENABLE_AUDIO_OUT
 #endif  // ENABLE_AUDIO_OUT
 
 
+#ifdef HAS_DIP_SWITCHES
+enum pin_setup_state_t  {SETUP_FALSE, SETUP_TRUE, SETUP_UNDETERMINED};
+static pin_setup_state_t read_setup_ack_pin()
+{
+    /* Revision 2022d of the RP2040 hardware has problems reading initiator DIP switch setting.
+     * The 74LVT245 hold current is keeping the GPIO_ACK state too strongly.
+     * Detect this condition by toggling the pin up and down and seeing if it sticks.
+     * 
+     * Revision 2023b and 2023c of the Pico boards have issues reading TERM and DEBUG DIP switch
+     * settings. GPIO_ACK is externally pulled down to ground for later revisions.
+     * If the state is detected as undetermined then the board is the 2023b or 2023c revision.
+     */
+
+    // Strong output high, then pulldown
+    //        pin             function       pup   pdown   out    state  fast
+    gpio_conf(SCSI_IN_ACK,  GPIO_FUNC_SIO, false, false, true,  true,  false);
+    gpio_conf(SCSI_IN_ACK,  GPIO_FUNC_SIO, false, true,  false, true,  false);
+    delay(1);
+    bool ack_state1 = gpio_get(SCSI_IN_ACK);
+    
+    // Strong output low, then pullup
+    //        pin             function       pup   pdown   out    state  fast
+    gpio_conf(SCSI_IN_ACK,  GPIO_FUNC_SIO, false, false, true,  false, false);
+    gpio_conf(SCSI_IN_ACK,  GPIO_FUNC_SIO, true,  false, false, false, false);
+    delay(1);
+    bool ack_state2 = gpio_get(SCSI_IN_ACK);
+
+    if (ack_state1 == ack_state2)
+    {
+        // Ok, was able to read the state directly
+        return !ack_state1 ? SETUP_TRUE : SETUP_FALSE;
+    }
+
+    // Enable OUT_BSY for a short time.
+    // If in target mode, this will force GPIO_ACK high.
+    gpio_put(SCSI_OUT_BSY, 0);
+    delay_100ns();
+    gpio_put(SCSI_OUT_BSY, 1);
+
+    return SETUP_UNDETERMINED;
+}
+#endif
+
 void platform_init()
 void platform_init()
 {
 {
     // Make sure second core is stopped
     // Make sure second core is stopped
@@ -146,11 +189,26 @@ void platform_init()
     gpio_conf(DIP_INITIATOR,  GPIO_FUNC_SIO, false, false, false, false, false);
     gpio_conf(DIP_INITIATOR,  GPIO_FUNC_SIO, false, false, false, false, false);
     gpio_conf(DIP_DBGLOG,     GPIO_FUNC_SIO, false, false, false, false, false);
     gpio_conf(DIP_DBGLOG,     GPIO_FUNC_SIO, false, false, false, false, false);
     gpio_conf(DIP_TERM,       GPIO_FUNC_SIO, false, false, false, false, false);
     gpio_conf(DIP_TERM,       GPIO_FUNC_SIO, false, false, false, false, false);
-
     delay(10); // 10 ms delay to let pull-ups do their work
     delay(10); // 10 ms delay to let pull-ups do their work
-
-    bool dbglog = !gpio_get(DIP_DBGLOG);
-    bool termination = !gpio_get(DIP_TERM);
+    bool working_dip = true;
+    bool dbglog = false;
+    bool termination = false;
+# ifdef ZULUSCSI_PICO
+    // Initiator dip setting works on all rev 2023b, 2023c, and newer rev Pico boards
+    g_scsi_initiator = !gpio_get(DIP_INITIATOR);
+    
+    working_dip = SETUP_UNDETERMINED != read_setup_ack_pin();    
+    if (working_dip)
+    {
+        dbglog = !gpio_get(DIP_DBGLOG);
+        termination = !gpio_get(DIP_TERM);
+        
+    }
+# else
+    g_scsi_initiator = SETUP_TRUE == read_setup_ack_pin();
+    dbglog = !gpio_get(DIP_DBGLOG);
+    termination = !gpio_get(DIP_TERM);
+# endif
 #else
 #else
     delay(10);
     delay(10);
 #endif // HAS_DIP_SWITCHES
 #endif // HAS_DIP_SWITCHES
@@ -169,16 +227,26 @@ void platform_init()
     logmsg("FW Version: ", g_log_firmwareversion);
     logmsg("FW Version: ", g_log_firmwareversion);
 
 
 #ifdef HAS_DIP_SWITCHES
 #ifdef HAS_DIP_SWITCHES
-    logmsg("DIP switch settings: debug log ", (int)dbglog, ", termination ", (int)termination);
-    g_log_debug = dbglog;
+    if (working_dip)
+    {       
+        logmsg("DIP switch settings: debug log ", (int)dbglog, ", termination ", (int)termination);
+        g_log_debug = dbglog;
 
 
-    if (termination)
-    {
-        logmsg("SCSI termination is enabled");
+        if (termination)
+        {
+            logmsg("SCSI termination is enabled");
+        }
+        else
+        {
+            logmsg("NOTE: SCSI termination is disabled");
+        }
     }
     }
     else
     else
     {
     {
-        logmsg("NOTE: SCSI termination is disabled");
+        logmsg("SCSI termination is determined by the DIP switch labeled \"TERM\"");
+        logmsg("Debug logging can only be enabled via INI file \"DEBUG=1\" under [SCSI] in zuluscsi.ini");
+        logmsg("-- DEBUG DIP switch setting is ignored on ZuluSCSI Pico FS Rev. 2023b and 2023c boards");
+        g_log_debug = false;
     }
     }
 #else
 #else
     g_log_debug = false;
     g_log_debug = false;
@@ -228,59 +296,18 @@ void platform_init()
 #endif  // ENABLE_AUDIO_OUTPUT
 #endif  // ENABLE_AUDIO_OUTPUT
 }
 }
 
 
-#ifdef HAS_DIP_SWITCHES
-static bool read_initiator_dip_switch()
-{
-    /* Revision 2022d hardware has problems reading initiator DIP switch setting.
-     * The 74LVT245 hold current is keeping the GPIO_ACK state too strongly.
-     * Detect this condition by toggling the pin up and down and seeing if it sticks.
-     */
-
-    // Strong output high, then pulldown
-    //        pin             function       pup   pdown   out    state  fast
-    gpio_conf(DIP_INITIATOR,  GPIO_FUNC_SIO, false, false, true,  true,  false);
-    gpio_conf(DIP_INITIATOR,  GPIO_FUNC_SIO, false, true,  false, true,  false);
-    delay(1);
-    bool initiator_state1 = gpio_get(DIP_INITIATOR);
-    
-    // Strong output low, then pullup
-    //        pin             function       pup   pdown   out    state  fast
-    gpio_conf(DIP_INITIATOR,  GPIO_FUNC_SIO, false, false, true,  false, false);
-    gpio_conf(DIP_INITIATOR,  GPIO_FUNC_SIO, true,  false, false, false, false);
-    delay(1);
-    bool initiator_state2 = gpio_get(DIP_INITIATOR);
-
-    if (initiator_state1 == initiator_state2)
-    {
-        // Ok, was able to read the state directly
-        return !initiator_state1;
-    }
-
-    // Enable OUT_BSY for a short time.
-    // If in target mode, this will force GPIO_ACK high.
-    gpio_put(SCSI_OUT_BSY, 0);
-    delay_100ns();
-    gpio_put(SCSI_OUT_BSY, 1);
-
-    return !gpio_get(DIP_INITIATOR);
-}
-#endif // HAS_DIP_SWITCHES
-
 // late_init() only runs in main application, SCSI not needed in bootloader
 // late_init() only runs in main application, SCSI not needed in bootloader
 void platform_late_init()
 void platform_late_init()
 {
 {
 #if defined(HAS_DIP_SWITCHES) && defined(PLATFORM_HAS_INITIATOR_MODE)
 #if defined(HAS_DIP_SWITCHES) && defined(PLATFORM_HAS_INITIATOR_MODE)
-    if (read_initiator_dip_switch())
+    if (g_scsi_initiator == true)
     {
     {
-        g_scsi_initiator = true;
         logmsg("SCSI initiator mode selected by DIP switch, expecting SCSI disks on the bus");
         logmsg("SCSI initiator mode selected by DIP switch, expecting SCSI disks on the bus");
     }
     }
     else
     else
     {
     {
-        g_scsi_initiator = false;
         logmsg("SCSI target/disk mode selected by DIP switch, acting as a SCSI disk");
         logmsg("SCSI target/disk mode selected by DIP switch, acting as a SCSI disk");
     }
     }
-
 #else
 #else
     g_scsi_initiator = false;
     g_scsi_initiator = false;
     logmsg("SCSI target/disk mode, acting as a SCSI disk");
     logmsg("SCSI target/disk mode, acting as a SCSI disk");
@@ -353,6 +380,8 @@ void platform_late_init()
         gpio_conf(SCSI_IN_REQ,    GPIO_FUNC_SIO, true ,false, false, true, false);
         gpio_conf(SCSI_IN_REQ,    GPIO_FUNC_SIO, true ,false, false, true, false);
         gpio_conf(SCSI_IN_BSY,    GPIO_FUNC_SIO, true, false, false, true, false);
         gpio_conf(SCSI_IN_BSY,    GPIO_FUNC_SIO, true, false, false, true, false);
         gpio_conf(SCSI_IN_RST,    GPIO_FUNC_SIO, true, false, false, true, false);
         gpio_conf(SCSI_IN_RST,    GPIO_FUNC_SIO, true, false, false, true, false);
+        // Reinitialize OUT_RST to output mode. On RP Pico variant the pin is shared with IN_RST.
+        gpio_conf(SCSI_OUT_RST,   GPIO_FUNC_SIO, false, false, true,  true, true);
         gpio_conf(SCSI_OUT_SEL,   GPIO_FUNC_SIO, false,false, true,  true, true);
         gpio_conf(SCSI_OUT_SEL,   GPIO_FUNC_SIO, false,false, true,  true, true);
         gpio_conf(SCSI_OUT_ACK,   GPIO_FUNC_SIO, false,false, true,  true, true);
         gpio_conf(SCSI_OUT_ACK,   GPIO_FUNC_SIO, false,false, true,  true, true);
         gpio_conf(SCSI_OUT_ATN,   GPIO_FUNC_SIO, false,false, true,  true, true);
         gpio_conf(SCSI_OUT_ATN,   GPIO_FUNC_SIO, false,false, true,  true, true);

+ 5 - 6
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform_gpio_Pico.h

@@ -95,11 +95,10 @@
 #define SD_SPI_CS    15
 #define SD_SPI_CS    15
 
 
 #ifndef ENABLE_AUDIO_OUTPUT
 #ifndef ENABLE_AUDIO_OUTPUT
-# ifdef GPIO_I2C_SDA
+    // No spare pins for I2C
     // IO expander I2C
     // IO expander I2C
-    #define GPIO_I2C_SDA 14
-    #define GPIO_I2C_SCL 15
-# endif // GPIO_I2C_SDA
+    // #define GPIO_I2C_SDA 14
+    // #define GPIO_I2C_SCL 15
 #else
 #else
     // IO expander I2C pins being used as SPI for audio
     // IO expander I2C pins being used as SPI for audio
     #define AUDIO_SPI      spi1
     #define AUDIO_SPI      spi1
@@ -110,8 +109,8 @@
 // DIP switch pins
 // DIP switch pins
 #define HAS_DIP_SWITCHES
 #define HAS_DIP_SWITCHES
 #define DIP_INITIATOR 28
 #define DIP_INITIATOR 28
-#define DIP_DBGLOG 20
-#define DIP_TERM 18
+#define DIP_DBGLOG 17
+#define DIP_TERM 22
 
 
 // Other pins
 // Other pins
 #define SWO_PIN 16
 #define SWO_PIN 16

+ 2 - 2
lib/ZuluSCSI_platform_RP2040/audio.cpp

@@ -142,8 +142,8 @@ static audio_status_code audio_last_status[8] = {ASC_NO_STATUS};
 
 
 // volume information for targets
 // volume information for targets
 static volatile uint16_t volumes[8] = {
 static volatile uint16_t volumes[8] = {
-    DEFAULT_VOLUME_LEVEL, DEFAULT_VOLUME_LEVEL, DEFAULT_VOLUME_LEVEL, DEFAULT_VOLUME_LEVEL,
-    DEFAULT_VOLUME_LEVEL, DEFAULT_VOLUME_LEVEL, DEFAULT_VOLUME_LEVEL, DEFAULT_VOLUME_LEVEL
+    DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH,
+    DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH
 };
 };
 static volatile uint16_t channels[8] = {
 static volatile uint16_t channels[8] = {
     AUDIO_CHANNEL_ENABLE_MASK, AUDIO_CHANNEL_ENABLE_MASK, AUDIO_CHANNEL_ENABLE_MASK, AUDIO_CHANNEL_ENABLE_MASK,
     AUDIO_CHANNEL_ENABLE_MASK, AUDIO_CHANNEL_ENABLE_MASK, AUDIO_CHANNEL_ENABLE_MASK, AUDIO_CHANNEL_ENABLE_MASK,

+ 0 - 1
lib/ZuluSCSI_platform_RP2040/scsi_accel_host.cpp

@@ -31,7 +31,6 @@
 #include <hardware/sync.h>
 #include <hardware/sync.h>
 
 
 #ifdef PLATFORM_HAS_INITIATOR_MODE
 #ifdef PLATFORM_HAS_INITIATOR_MODE
-
 #ifdef ZULUSCSI_PICO
 #ifdef ZULUSCSI_PICO
 #include "scsi_accel_host_Pico.pio.h"
 #include "scsi_accel_host_Pico.pio.h"
 #else
 #else

+ 1 - 3
lib/ZuluSCSI_platform_RP2040/scsi_accel_target.cpp

@@ -49,10 +49,8 @@
 #include <multicore.h>
 #include <multicore.h>
 #endif // ZULUSCSI_NETWORK
 #endif // ZULUSCSI_NETWORK
 
 
-#ifdef ZULUSCSI_PICO
+#if defined(ZULUSCSI_PICO) || defined(ZULUSCSI_BS2)
 #include "scsi_accel_target_Pico.pio.h"
 #include "scsi_accel_target_Pico.pio.h"
-#elif defined(ZULUSCSI_BS2)
-#include "scsi_accel_target_BS2.pio.h"
 #else
 #else
 #include "scsi_accel_target_RP2040.pio.h"
 #include "scsi_accel_target_RP2040.pio.h"
 #endif // ZULUSCSI_PICO
 #endif // ZULUSCSI_PICO

+ 0 - 124
lib/ZuluSCSI_platform_RP2040/scsi_accel_target_BS2.pio

@@ -1,124 +0,0 @@
-; ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
-;
-; ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
-;
-; https://www.gnu.org/licenses/gpl-3.0.html
-; ----
-; This program is free software: you can redistribute it and/or modify
-; it under the terms of the GNU General Public License as published by
-; the Free Software Foundation, either version 3 of the License, or
-; (at your option) any later version. 
-;
-; This program is distributed in the hope that it will be useful,
-; but WITHOUT ANY WARRANTY; without even the implied warranty of
-; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-; GNU General Public License for more details. 
-;
-; You should have received a copy of the GNU General Public License
-; along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-
-; RP2040 PIO program for accelerating SCSI communication
-; Run "pioasm scsi_accel.pio scsi_accel.pio.h" to regenerate the C header from this.
-; GPIO mapping:
-; - 0-7: DB0-DB7
-; -   8: DBP
-; Side set is REQ pin
-
-.define REQ 17
-.define ACK 26
-
-; Delay from data setup to REQ assertion.
-; deskew delay + cable skew delay = 55 ns minimum
-; One clock cycle is 8 ns => delay 7 clocks
-.define REQ_DLY 7
-
-; Adds parity to data that is to be written to SCSI
-; This works by generating addresses for DMA to fetch data from.
-; Register X should be initialized to the base address of the lookup table.
-.program scsi_parity
-    pull block
-    in NULL, 1
-    in OSR, 8
-    in X, 23
-
-; Write to SCSI bus using asynchronous handshake.
-; Data is written as 32-bit words that contain the 8 data bits + 1 parity bit.
-; 23 bits in each word are discarded.
-; Number of bytes to send must be multiple of 2.
-.program scsi_accel_async_write
-    .side_set 1
-
-    pull ifempty block          side 1  ; Get data from TX FIFO
-    out pins, 9                 side 1  ; Write data and parity bit
-    out null, 23 [REQ_DLY-2]    side 1  ; Discard unused bits, wait for data preset time
-    wait 1 gpio ACK             side 1  ; Wait for ACK to be inactive
-    wait 0 gpio ACK             side 0  ; Assert REQ, wait for ACK low
-
-; Read from SCSI bus using sync or async handshake.
-; Data is returned as 32-bit words:
-; - bit  0: always zero
-; - bits 1-8: data byte
-; - bit  9: parity bit
-; - bits 10-31: lookup table address
-; Lookup table address should be loaded into register Y.
-; One dummy word should be written to TX fifo for every byte to receive.
-.program scsi_accel_read
-    .side_set 1
-
-    pull block                  side 1  ; Pull from TX fifo for counting bytes and pacing sync mode
-    wait 1 gpio ACK             side 1  ; Wait for ACK high
-    in null, 1                  side 0  ; Zero bit because lookup table entries are 16-bit
-    wait 0 gpio ACK             side 0  ; Assert REQ, wait for ACK low
-    in pins, 9                  side 1  ; Deassert REQ, read GPIO
-    in y, 22                    side 1  ; Copy parity lookup table address
-
-; Data state machine for synchronous writes.
-; Takes the lowest 9 bits of each 32 bit word and writes them to bus with REQ pulse.
-; The delay times will be rewritten by C code to match the negotiated SCSI sync speed.
-;
-; Shifts one bit to ISR per every byte transmitted. This is used to control the transfer
-; pace, the RX fifo acts as a counter to keep track of unacknowledged bytes. The C code
-; can set the syncOffset by changing autopush threshold, e.g. threshold 3 = 12 bytes offset.
-.program scsi_sync_write
-    .side_set 1
-
-    out pins, 9      [0]        side 1  ; Write data and parity bit, wait for deskew delay
-    out null, 23     [0]        side 0  ; Assert REQ, wait for assert time
-    in null, 1       [0]        side 1  ; Deassert REQ, wait for transfer period, wait for space in ACK buffer
-
-; Data pacing state machine for synchronous writes.
-; Takes one bit from ISR on every falling edge of ACK.
-; The C code should set autopull threshold to match scsi_sync_write autopush threshold.
-; System DMA will then move words from scsi_sync_write RX fifo to scsi_sync_write_pacer TX fifo.
-.program scsi_sync_write_pacer
-    wait 1 gpio ACK
-    wait 0 gpio ACK   ; Wait for falling edge on ACK
-    out null, 1       ; Let scsi_sync_write send one more byte
-
-; Data pacing state machine for synchronous reads.
-; The delay times will be rewritten by C code to match the negotiated SCSI sync speed.
-; Number of bytes to receive minus one should be loaded into register X.
-; In synchronous mode this generates the REQ pulses and dummy words.
-; In asynchronous mode it just generates dummy words to feed to scsi_accel_read.
-.program scsi_sync_read_pacer
-    .side_set 1
-
-start:
-    push block      [0]      side 1  ; Send dummy word to scsi_accel_read, wait for transfer period
-    jmp x-- start   [0]      side 0  ; Assert REQ, wait for assert time
-
-finish:
-    jmp finish      [0]      side 1
-
-; Parity checker for reads from SCSI bus.
-; Receives 16-bit words from g_scsi_parity_check_lookup
-; Bottom 8 bits are the data byte, which is passed to output FIFO
-; The 9th bit is parity valid bit, which is 1 for valid and 0 for parity error.
-.program scsi_read_parity
-parity_valid:
-    out isr, 8                ; Take the 8 data bits for passing to RX fifo
-    push block                ; Push the data to RX fifo
-    out x, 24                 ; Take the parity valid bit, and the rest of 32-bit word
-    jmp x-- parity_valid      ; If parity valid bit is 1, repeat from start
-    irq set 0                 ; Parity error, set interrupt flag

+ 0 - 225
lib/ZuluSCSI_platform_RP2040/scsi_accel_target_BS2.pio.h

@@ -1,225 +0,0 @@
-// -------------------------------------------------- //
-// This file is autogenerated by pioasm; do not edit! //
-// -------------------------------------------------- //
-
-#pragma once
-
-#if !PICO_NO_HARDWARE
-#include "hardware/pio.h"
-#endif
-
-// ----------- //
-// scsi_parity //
-// ----------- //
-
-#define scsi_parity_wrap_target 0
-#define scsi_parity_wrap 3
-
-static const uint16_t scsi_parity_program_instructions[] = {
-            //     .wrap_target
-    0x80a0, //  0: pull   block                      
-    0x4061, //  1: in     null, 1                    
-    0x40e8, //  2: in     osr, 8                     
-    0x4037, //  3: in     x, 23                      
-            //     .wrap
-};
-
-#if !PICO_NO_HARDWARE
-static const struct pio_program scsi_parity_program = {
-    .instructions = scsi_parity_program_instructions,
-    .length = 4,
-    .origin = -1,
-};
-
-static inline pio_sm_config scsi_parity_program_get_default_config(uint offset) {
-    pio_sm_config c = pio_get_default_sm_config();
-    sm_config_set_wrap(&c, offset + scsi_parity_wrap_target, offset + scsi_parity_wrap);
-    return c;
-}
-#endif
-
-// ---------------------- //
-// scsi_accel_async_write //
-// ---------------------- //
-
-#define scsi_accel_async_write_wrap_target 0
-#define scsi_accel_async_write_wrap 4
-
-static const uint16_t scsi_accel_async_write_program_instructions[] = {
-            //     .wrap_target
-    0x90e0, //  0: pull   ifempty block   side 1     
-    0x7009, //  1: out    pins, 9         side 1     
-    0x7577, //  2: out    null, 23        side 1 [5] 
-    0x309a, //  3: wait   1 gpio, 26      side 1     
-    0x201a, //  4: wait   0 gpio, 26      side 0     
-            //     .wrap
-};
-
-#if !PICO_NO_HARDWARE
-static const struct pio_program scsi_accel_async_write_program = {
-    .instructions = scsi_accel_async_write_program_instructions,
-    .length = 5,
-    .origin = -1,
-};
-
-static inline pio_sm_config scsi_accel_async_write_program_get_default_config(uint offset) {
-    pio_sm_config c = pio_get_default_sm_config();
-    sm_config_set_wrap(&c, offset + scsi_accel_async_write_wrap_target, offset + scsi_accel_async_write_wrap);
-    sm_config_set_sideset(&c, 1, false, false);
-    return c;
-}
-#endif
-
-// --------------- //
-// scsi_accel_read //
-// --------------- //
-
-#define scsi_accel_read_wrap_target 0
-#define scsi_accel_read_wrap 5
-
-static const uint16_t scsi_accel_read_program_instructions[] = {
-            //     .wrap_target
-    0x90a0, //  0: pull   block           side 1     
-    0x309a, //  1: wait   1 gpio, 26      side 1     
-    0x4061, //  2: in     null, 1         side 0     
-    0x201a, //  3: wait   0 gpio, 26      side 0     
-    0x5009, //  4: in     pins, 9         side 1     
-    0x5056, //  5: in     y, 22           side 1     
-            //     .wrap
-};
-
-#if !PICO_NO_HARDWARE
-static const struct pio_program scsi_accel_read_program = {
-    .instructions = scsi_accel_read_program_instructions,
-    .length = 6,
-    .origin = -1,
-};
-
-static inline pio_sm_config scsi_accel_read_program_get_default_config(uint offset) {
-    pio_sm_config c = pio_get_default_sm_config();
-    sm_config_set_wrap(&c, offset + scsi_accel_read_wrap_target, offset + scsi_accel_read_wrap);
-    sm_config_set_sideset(&c, 1, false, false);
-    return c;
-}
-#endif
-
-// --------------- //
-// scsi_sync_write //
-// --------------- //
-
-#define scsi_sync_write_wrap_target 0
-#define scsi_sync_write_wrap 2
-
-static const uint16_t scsi_sync_write_program_instructions[] = {
-            //     .wrap_target
-    0x7009, //  0: out    pins, 9         side 1     
-    0x6077, //  1: out    null, 23        side 0     
-    0x5061, //  2: in     null, 1         side 1     
-            //     .wrap
-};
-
-#if !PICO_NO_HARDWARE
-static const struct pio_program scsi_sync_write_program = {
-    .instructions = scsi_sync_write_program_instructions,
-    .length = 3,
-    .origin = -1,
-};
-
-static inline pio_sm_config scsi_sync_write_program_get_default_config(uint offset) {
-    pio_sm_config c = pio_get_default_sm_config();
-    sm_config_set_wrap(&c, offset + scsi_sync_write_wrap_target, offset + scsi_sync_write_wrap);
-    sm_config_set_sideset(&c, 1, false, false);
-    return c;
-}
-#endif
-
-// --------------------- //
-// scsi_sync_write_pacer //
-// --------------------- //
-
-#define scsi_sync_write_pacer_wrap_target 0
-#define scsi_sync_write_pacer_wrap 2
-
-static const uint16_t scsi_sync_write_pacer_program_instructions[] = {
-            //     .wrap_target
-    0x209a, //  0: wait   1 gpio, 26                 
-    0x201a, //  1: wait   0 gpio, 26                 
-    0x6061, //  2: out    null, 1                    
-            //     .wrap
-};
-
-#if !PICO_NO_HARDWARE
-static const struct pio_program scsi_sync_write_pacer_program = {
-    .instructions = scsi_sync_write_pacer_program_instructions,
-    .length = 3,
-    .origin = -1,
-};
-
-static inline pio_sm_config scsi_sync_write_pacer_program_get_default_config(uint offset) {
-    pio_sm_config c = pio_get_default_sm_config();
-    sm_config_set_wrap(&c, offset + scsi_sync_write_pacer_wrap_target, offset + scsi_sync_write_pacer_wrap);
-    return c;
-}
-#endif
-
-// -------------------- //
-// scsi_sync_read_pacer //
-// -------------------- //
-
-#define scsi_sync_read_pacer_wrap_target 0
-#define scsi_sync_read_pacer_wrap 2
-
-static const uint16_t scsi_sync_read_pacer_program_instructions[] = {
-            //     .wrap_target
-    0x9020, //  0: push   block           side 1     
-    0x0040, //  1: jmp    x--, 0          side 0     
-    0x1002, //  2: jmp    2               side 1     
-            //     .wrap
-};
-
-#if !PICO_NO_HARDWARE
-static const struct pio_program scsi_sync_read_pacer_program = {
-    .instructions = scsi_sync_read_pacer_program_instructions,
-    .length = 3,
-    .origin = -1,
-};
-
-static inline pio_sm_config scsi_sync_read_pacer_program_get_default_config(uint offset) {
-    pio_sm_config c = pio_get_default_sm_config();
-    sm_config_set_wrap(&c, offset + scsi_sync_read_pacer_wrap_target, offset + scsi_sync_read_pacer_wrap);
-    sm_config_set_sideset(&c, 1, false, false);
-    return c;
-}
-#endif
-
-// ---------------- //
-// scsi_read_parity //
-// ---------------- //
-
-#define scsi_read_parity_wrap_target 0
-#define scsi_read_parity_wrap 4
-
-static const uint16_t scsi_read_parity_program_instructions[] = {
-            //     .wrap_target
-    0x60c8, //  0: out    isr, 8                     
-    0x8020, //  1: push   block                      
-    0x6038, //  2: out    x, 24                      
-    0x0040, //  3: jmp    x--, 0                     
-    0xc000, //  4: irq    nowait 0                   
-            //     .wrap
-};
-
-#if !PICO_NO_HARDWARE
-static const struct pio_program scsi_read_parity_program = {
-    .instructions = scsi_read_parity_program_instructions,
-    .length = 5,
-    .origin = -1,
-};
-
-static inline pio_sm_config scsi_read_parity_program_get_default_config(uint offset) {
-    pio_sm_config c = pio_get_default_sm_config();
-    sm_config_set_wrap(&c, offset + scsi_read_parity_wrap_target, offset + scsi_read_parity_wrap);
-    return c;
-}
-#endif
-

+ 1 - 3
lib/ZuluSCSI_platform_RP2040/sdio.cpp

@@ -36,10 +36,8 @@
 #include <ZuluSCSI_platform.h>
 #include <ZuluSCSI_platform.h>
 #include <ZuluSCSI_log.h>
 #include <ZuluSCSI_log.h>
 
 
-#ifdef ZULUSCSI_PICO
+#if defined(ZULUSCSI_PICO) || defined(ZULUSCSI_BS2)
 #include "sdio_Pico.pio.h"
 #include "sdio_Pico.pio.h"
-#elif defined(ZULUSCSI_BS2)
-#include "sdio_BS2.pio.h"
 #else
 #else
 #include "sdio_RP2040.pio.h"
 #include "sdio_RP2040.pio.h"
 #endif
 #endif

+ 0 - 165
lib/ZuluSCSI_platform_RP2040/sdio_BS2.pio

@@ -1,165 +0,0 @@
-; ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
-; 
-; ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
-; 
-; https://www.gnu.org/licenses/gpl-3.0.html
-; ----
-; This program is free software: you can redistribute it and/or modify
-; it under the terms of the GNU General Public License as published by
-; the Free Software Foundation, either version 3 of the License, or
-; (at your option) any later version. 
-; 
-; This program is distributed in the hope that it will be useful,
-; but WITHOUT ANY WARRANTY; without even the implied warranty of
-; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-; GNU General Public License for more details. 
-; 
-; You should have received a copy of the GNU General Public License
-; along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-; 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 CLKDIV 3
-.define CLKDIV 5
-.define D0 ((CLKDIV + 1) / 2 - 1)
-.define D1 (CLKDIV/2 - 1)
-.define SDIO_CLK_GPIO 10
-
-; 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
-; receive a data block. The application must set number of nibbles
-; to receive minus 1 to Y register before running this program.
-.program sdio_data_rx
-
-wait_start:
-    mov X, Y                               ; Reinitialize number of nibbles to receive
-    wait 0 pin 0                           ; Wait for zero state on D0
-    wait 1 gpio SDIO_CLK_GPIO  [CLKDIV-1]  ; Wait for rising edge and then whole clock cycle
-
-rx_data:
-    in PINS, 4                 [CLKDIV-2]  ; Read nibble
-    jmp X--, rx_data
-
-; Data transmission program
-;
-; Before running this program, pindirs should be set as output
-; and register X should be initialized with the number of nibbles
-; to send minus 1 (typically 8 + 1024 + 16 + 1 - 1 = 1048)
-; and register Y with the number of response bits minus 1 (typically 31).
-;
-; Words written to TX FIFO must be:
-; - Word 0: start token 0xFFFFFFF0
-; - Word 1-128: transmitted data (512 bytes)
-; - Word 129-130: CRC checksum
-; - Word 131: end token 0xFFFFFFFF
-;
-; After the card reports idle status, RX FIFO will get a word that
-; contains the D0 line response from card.
-
-.program sdio_data_tx
-    wait 0 gpio SDIO_CLK_GPIO  
-    wait 1 gpio SDIO_CLK_GPIO  [CLKDIV + D1 - 1]; Synchronize so that write occurs on falling edge
-
-tx_loop:
-    out PINS, 4                [D0]    ; Write nibble and wait for whole clock cycle
-    jmp X-- tx_loop            [D1]
-
-    set pindirs, 0x00          [D0]    ; Set data bus as input
-
-.wrap_target
-response_loop:
-    in PINS, 1                 [D1]    ; Read D0 on rising edge
-    jmp Y--, response_loop     [D0]
-
-wait_idle:
-    wait 1 pin 0               [D1]    ; Wait for card to indicate idle condition
-    push                       [D0]    ; Push the response token
-.wrap

+ 0 - 121
lib/ZuluSCSI_platform_RP2040/sdio_BS2.pio.h

@@ -1,121 +0,0 @@
-// -------------------------------------------------- //
-// 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
-    0xb1e3, //  0: mov    osr, null       side 1 [1] 
-    0xa24d, //  1: mov    y, !status      side 0 [2] 
-    0x1161, //  2: jmp    !y, 1           side 1 [1] 
-    0x6260, //  3: out    null, 32        side 0 [2] 
-    0x7128, //  4: out    x, 8            side 1 [1] 
-    0xe201, //  5: set    pins, 1         side 0 [2] 
-    0xf181, //  6: set    pindirs, 1      side 1 [1] 
-    0x6201, //  7: out    pins, 1         side 0 [2] 
-    0x1147, //  8: jmp    x--, 7          side 1 [1] 
-    0xe280, //  9: set    pindirs, 0      side 0 [2] 
-    0x7128, // 10: out    x, 8            side 1 [1] 
-    0xa242, // 11: nop                    side 0 [2] 
-    0x1131, // 12: jmp    !x, 17          side 1 [1] 
-    0xa242, // 13: nop                    side 0 [2] 
-    0x11cd, // 14: jmp    pin, 13         side 1 [1] 
-    0x4201, // 15: in     pins, 1         side 0 [2] 
-    0x114f, // 16: jmp    x--, 15         side 1 [1] 
-    0x8220, // 17: push   block           side 0 [2] 
-            //     .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 0
-#define sdio_data_rx_wrap 4
-
-static const uint16_t sdio_data_rx_program_instructions[] = {
-            //     .wrap_target
-    0xa022, //  0: mov    x, y                       
-    0x2020, //  1: wait   0 pin, 0                   
-    0x248a, //  2: wait   1 gpio, 10             [4] 
-    0x4304, //  3: in     pins, 4                [3] 
-    0x0043, //  4: jmp    x--, 3                     
-            //     .wrap
-};
-
-#if !PICO_NO_HARDWARE
-static const struct pio_program sdio_data_rx_program = {
-    .instructions = sdio_data_rx_program_instructions,
-    .length = 5,
-    .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 5
-#define sdio_data_tx_wrap 8
-
-static const uint16_t sdio_data_tx_program_instructions[] = {
-    0x200a, //  0: wait   0 gpio, 10                 
-    0x258a, //  1: wait   1 gpio, 10             [5] 
-    0x6204, //  2: out    pins, 4                [2] 
-    0x0142, //  3: jmp    x--, 2                 [1] 
-    0xe280, //  4: set    pindirs, 0             [2] 
-            //     .wrap_target
-    0x4101, //  5: in     pins, 1                [1] 
-    0x0285, //  6: jmp    y--, 5                 [2] 
-    0x21a0, //  7: wait   1 pin, 0               [1] 
-    0x8220, //  8: push   block                  [2] 
-            //     .wrap
-};
-
-#if !PICO_NO_HARDWARE
-static const struct pio_program sdio_data_tx_program = {
-    .instructions = sdio_data_tx_program_instructions,
-    .length = 9,
-    .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
-

+ 35 - 12
platformio.ini

@@ -154,18 +154,7 @@ build_flags =
 ; Variant of RP2040 platform, based on Raspberry Pico board and a carrier PCB
 ; Variant of RP2040 platform, based on Raspberry Pico board and a carrier PCB
 ; Differs in pinout from ZuluSCSI_RP2040 platform, but shares most of the code.
 ; Differs in pinout from ZuluSCSI_RP2040 platform, but shares most of the code.
 [env:ZuluSCSI_BS2]
 [env:ZuluSCSI_BS2]
-platform = raspberrypi@1.9.0
-framework = arduino
-board = ZuluSCSI_RP2040
-extra_scripts = src/build_bootloader.py
-board_build.ldscript = lib/ZuluSCSI_platform_RP2040/rp2040.ld
-ldscript_bootloader = lib/ZuluSCSI_platform_RP2040/rp2040_btldr.ld
-lib_deps =
-    SdFat=https://github.com/rabbitholecomputing/SdFat#2.2.0-gpt
-    minIni
-    ZuluSCSI_platform_RP2040
-    SCSI2SD
-    CUEParser
+extends = env:ZuluSCSI_RP2040
 build_flags =
 build_flags =
     -O2 -Isrc -ggdb -g3
     -O2 -Isrc -ggdb -g3
     -Wall -Wno-sign-compare -Wno-ignored-qualifiers
     -Wall -Wno-sign-compare -Wno-ignored-qualifiers
@@ -175,3 +164,37 @@ build_flags =
     -DHAS_SDIO_CLASS
     -DHAS_SDIO_CLASS
     -DUSE_ARDUINO=1
     -DUSE_ARDUINO=1
     -DZULUSCSI_BS2
     -DZULUSCSI_BS2
+
+; ZuluSCSI F4 hardware platform with GD32F450ZET6 CPU.
+[env:ZuluSCSIv1_4]
+platform = https://github.com/CommunityGD32Cores/platform-gd32.git
+board = genericGD32F450ZE
+board_build.mcu = gd32f450zet6
+board_build.core = gd32
+board_build.ldscript = lib/ZuluSCSI_platform_GD32F450/zuluscsi_gd32f450.ld
+ldscript_bootloader = lib/ZuluSCSI_platform_GD32F450/zuluscsi_gd32f450_btldr.ld
+framework = spl
+lib_compat_mode = off
+lib_deps =
+    GD32F4xx_usbfs_library
+    SdFat_NoArduino
+    minIni
+    ZuluSCSI_platform_GD32F450
+    SCSI2SD
+    CUEParser
+upload_protocol = stlink
+platform_packages = 
+    toolchain-gccarmnoneeabi@1.90201.191206
+    framework-spl-gd32@https://github.com/CommunityGD32Cores/gd32-pio-spl-package.git
+extra_scripts = src/build_bootloader.py
+debug_tool = cmsis-dap
+debug_build_flags = -Os -ggdb -g3
+build_flags = 
+     -Os -Wall -Wno-sign-compare -ggdb -g3 -Isrc
+     -D__SYSTEM_CLOCK_200M_PLL_IRC16M=200000000
+     -DSPI_DRIVER_SELECT=3
+     -DSD_CHIP_SELECT_MODE=2
+     -DENABLE_DEDICATED_SPI=1
+     -DHAS_SDIO_CLASS
+     -DPIO_USBFS_DEVICE_CDC
+     -DZULUSCSI_V1_4

+ 8 - 2
src/ImageBackingStore.cpp

@@ -1,8 +1,8 @@
 /**
 /**
- * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
+ * ZuluSCSI™ - Copyright (c) 2022-2023 Rabbit Hole Computing™
+ * Portions - Copyright (C) 2023 Eric Helgeson
  *
  *
  * This file is licensed under the GPL version 3 or any later version. 
  * This file is licensed under the GPL version 3 or any later version. 
- * It is derived from disk.c in SCSI2SD V6
  *
  *
  * https://www.gnu.org/licenses/gpl-3.0.html
  * https://www.gnu.org/licenses/gpl-3.0.html
  * ----
  * ----
@@ -145,6 +145,12 @@ bool ImageBackingStore::isRom()
     return m_isrom;
     return m_isrom;
 }
 }
 
 
+
+bool ImageBackingStore::isRaw()
+{
+    return m_israw;
+}
+
 bool ImageBackingStore::close()
 bool ImageBackingStore::close()
 {
 {
     if (m_israw)
     if (m_israw)

+ 25 - 0
src/ImageBackingStore.h

@@ -1,3 +1,25 @@
+/**
+ * Portions - Copyright (C) 2023 Eric Helgeson
+ * ZuluSCSI™ - Copyright (c) 2022-2023 Rabbit Hole Computing™
+ *
+ * This file is licensed under the GPL version 3 or any later version. 
+ *
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
 /* Access layer to image files associated with a SCSI device.
 /* Access layer to image files associated with a SCSI device.
  * Currently supported image storage modes:
  * Currently supported image storage modes:
  *
  *
@@ -49,6 +71,9 @@ public:
     // Is this internal ROM drive in microcontroller flash?
     // Is this internal ROM drive in microcontroller flash?
     bool isRom();
     bool isRom();
 
 
+    // Is this backed by raw passthrough
+    bool isRaw();
+
     // Close the image so that .isOpen() will return false.
     // Close the image so that .isOpen() will return false.
     bool close();
     bool close();
 
 

+ 118 - 0
src/QuirksCheck.cpp

@@ -0,0 +1,118 @@
+/** 
+ * Copyright (C) 2023 Eric Helgeson
+ * Portions ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
+ * 
+ * This file is licensed under the GPL version 3 or any later version. 
+ * It is derived from BlueSCSI_platform_config_hook.cpp in BluSCSI-v2
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ---- 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#include <minIni.h>
+#include "ZuluSCSI_disk.h"
+#include "ZuluSCSI_log.h"
+#include "QuirksCheck.h"
+#include <assert.h>
+#include <stdint.h>
+
+
+static bool isValidMacintoshImage(image_config_t *img)
+{
+    bool result = true;
+    const char apple_magic[2] = {0x45, 0x52};
+    const char block_size[2]  = {0x02, 0x00};  // 512 BE == 2
+    const char lido_sig[4] = {'C', 'M', 'S', '_' };
+    uint8_t tmp[SD_SECTOR_SIZE];
+    // Check for Apple Magic
+    img->file.seek(0); 
+    img->file.read(tmp, SD_SECTOR_SIZE);
+    if (memcmp(apple_magic, tmp, 2) != 0)
+    {
+        dbgmsg("---- Apple magic not found.");
+        result = false;
+    }
+    // Check HFS Block size is 512
+    if (memcmp(block_size, &tmp[2], 2) != 0)
+    {
+        dbgmsg("---- Block size not 512", block_size);
+        result = false;
+    }
+    uint8_t *mac_driver = &tmp[MACINTOSH_SCSI_DRIVER_OFFSET];
+    uint32_t driver_offset_blocks = mac_driver[0] << 24 | 
+                                    mac_driver[1] << 16 |
+                                    mac_driver[2] << 8  |  
+                                    mac_driver[3];
+    // Find size of SCSI Driver partition
+    uint8_t *mac_driver_size = &tmp[MACINTOSH_SCSI_DRIVER_SIZE_OFFSET];
+    uint32_t driver_size_blocks = mac_driver_size[0] << 8 | mac_driver_size[1];
+    // SCSI Driver sanity checks
+    if((driver_size_blocks * MACINTOSH_BLOCK_SIZE) > MACINTOSH_SCSI_DRIVER_MAX_SIZE ||
+        (driver_offset_blocks * MACINTOSH_BLOCK_SIZE) > img->file.size())
+    {
+        dbgmsg("---- Invalid Macintosh SCSI Driver partition detected.");
+        result = false;
+    }
+    // Contains Lido Driver - driver causes issues on a Mac Plus and is generally slower than the Apple 4.3 or FWB.
+    // Also causes compatibility issues with other drivers.   
+    // Mac block sizes should be the same size SD sector sizes for raw seeks, and reads to work 
+    assert(MACINTOSH_BLOCK_SIZE == SD_SECTOR_SIZE);
+    img->file.seek(driver_offset_blocks * MACINTOSH_BLOCK_SIZE); 
+    img->file.read(tmp, SD_SECTOR_SIZE);
+    uint8_t* lido_driver = &tmp[LIDO_SIG_OFFSET];
+    if(memcmp(lido_sig, lido_driver, 4) == 0)
+    {
+        logmsg("---- WARNING: This drive contains the LIDO driver and may cause issues.");
+    }
+
+    return result;
+}
+
+// Called from ZuluSCSI_disk after image is initalized.
+static void macQuirksSanityCheck(image_config_t *img)
+{
+
+    if(ini_getbool("SCSI", "DisableMacSanityCheck", false, CONFIGFILE))
+    {
+        dbgmsg("---- Skipping Mac sanity check due to DisableMacSanityCheck");
+        return;
+    }
+
+    if(img->deviceType == S2S_CFG_FIXED)
+    {
+        if(!isValidMacintoshImage(img))
+        {
+            logmsg("---- WARNING: This image does not appear to be a valid Macintosh disk image.");
+        }
+        else
+        {
+            dbgmsg("---- Valid Macintosh disk image detected.");
+        }
+    }
+    
+    // Macintosh hosts reserve ID 7, so warn the user this configuration wont work
+    if((img->scsiId & S2S_CFG_TARGET_ID_BITS) == 7)
+    {
+        logmsg("---- WARNING: Quirks set to Apple so can not use SCSI ID 7!");
+    }
+}
+
+void quirksCheck(image_config_t *img)
+{
+    if (img->quirks == S2S_CFG_QUIRKS_APPLE)
+    {
+        macQuirksSanityCheck(img);
+    }
+}

+ 35 - 0
src/QuirksCheck.h

@@ -0,0 +1,35 @@
+
+/** 
+ * Copyright (C) 2023 Eric Helgeson
+ * Portions ZuluSCSI™ - Copyright (c) 2023 Rabbit Hole Computing™
+ * 
+ * This file is licensed under the GPL version 3 or any later version. 
+ * It is derived from BlueSCSI_platform_config_hook.h in BluSCSI-v2
+ * 
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ---- 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#pragma once
+#include "ZuluSCSI_disk.h"
+
+void quirksCheck(image_config_t *img);
+
+// Macintosh Device image constants
+#define MACINTOSH_SCSI_DRIVER_OFFSET 18
+#define MACINTOSH_SCSI_DRIVER_SIZE_OFFSET MACINTOSH_SCSI_DRIVER_OFFSET + 4
+#define MACINTOSH_BLOCK_SIZE 512
+#define MACINTOSH_SCSI_DRIVER_MAX_SIZE 64 * MACINTOSH_BLOCK_SIZE // 32768
+#define LIDO_SIG_OFFSET 24

+ 4 - 5
src/ZuluSCSI.cpp

@@ -1,6 +1,6 @@
 /*  
 /*  
- *  ZuluSCSI
- *  Copyright (c) 2022 Rabbit Hole Computing
+ *  ZuluSCSI
+ *  Copyright (c) 2022-2023 Rabbit Hole Computing
  * 
  * 
  * This project is based on BlueSCSI:
  * This project is based on BlueSCSI:
  *
  *
@@ -298,7 +298,7 @@ bool findHDDImages()
   ini_gets("SCSI", "Dir", "/", imgdir, sizeof(imgdir), CONFIGFILE);
   ini_gets("SCSI", "Dir", "/", imgdir, sizeof(imgdir), CONFIGFILE);
   int dirindex = 0;
   int dirindex = 0;
 
 
-  logmsg("Finding HDD images in directory ", imgdir, ":");
+  logmsg("Finding images in directory ", imgdir, ":");
 
 
   SdFile root;
   SdFile root;
   root.open(imgdir);
   root.open(imgdir);
@@ -329,7 +329,7 @@ bool findHDDImages()
 
 
       if (imgdir[0] != '\0')
       if (imgdir[0] != '\0')
       {
       {
-        logmsg("Finding HDD images in additional directory Dir", (int)dirindex, " = \"", imgdir, "\":");
+        logmsg("Finding images in additional directory Dir", (int)dirindex, " = \"", imgdir, "\":");
         root.open(imgdir);
         root.open(imgdir);
         if (!root.isOpen())
         if (!root.isOpen())
         {
         {
@@ -636,7 +636,6 @@ static void reinitSCSI()
 #endif // ZULUSCSI_NETWORK
 #endif // ZULUSCSI_NETWORK
   
   
 }
 }
-
 extern "C" void zuluscsi_setup(void)
 extern "C" void zuluscsi_setup(void)
 {
 {
   platform_init();
   platform_init();

+ 3 - 1
src/ZuluSCSI_audio.h

@@ -33,7 +33,9 @@
  * This implementation uses the high byte for output port 1 and the low byte
  * This implementation uses the high byte for output port 1 and the low byte
  * for port 0. The two values are averaged to determine final volume level.
  * for port 0. The two values are averaged to determine final volume level.
  */
  */
-#define DEFAULT_VOLUME_LEVEL 0x3F3F
+#define DEFAULT_VOLUME_LEVEL 0x3F
+#define DEFAULT_VOLUME_LEVEL_2CH DEFAULT_VOLUME_LEVEL << 8 | DEFAULT_VOLUME_LEVEL
+
 /*
 /*
  * Defines the 'enable' masks for the two audio output ports of each device.
  * Defines the 'enable' masks for the two audio output ports of each device.
  * If this mask is matched with audio_get_channel() the relevant port will
  * If this mask is matched with audio_get_channel() the relevant port will

+ 18 - 1
src/ZuluSCSI_bootloader.cpp

@@ -24,8 +24,8 @@
 #include <ZuluSCSI_platform.h>
 #include <ZuluSCSI_platform.h>
 #include "ZuluSCSI_config.h"
 #include "ZuluSCSI_config.h"
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_log.h"
-#include <SdFat.h>
 #include <string.h>
 #include <string.h>
+#include <SdFat.h>
 
 
 #ifdef PLATFORM_BOOTLOADER_SIZE
 #ifdef PLATFORM_BOOTLOADER_SIZE
 
 
@@ -59,6 +59,7 @@ bool find_firmware_image(FsFile &file, char name[MAX_FILE_PATH + 1])
     return false;
     return false;
 }
 }
 
 
+#ifndef PLATFORM_FLASH_SECTOR_ERASE
 bool program_firmware(FsFile &file)
 bool program_firmware(FsFile &file)
 {
 {
     uint32_t filesize = file.size();
     uint32_t filesize = file.size();
@@ -103,6 +104,22 @@ bool program_firmware(FsFile &file)
 
 
     return true;
     return true;
 }
 }
+#else // PLATFORM_FLASH_SECTOR_ERASE
+bool program_firmware(FsFile &file)
+{
+    if (!platform_firmware_erase(file))
+    {
+        return false;
+    }
+    if (!platform_firmware_program(file))
+    {
+        return false;
+    }
+    return true;
+    
+}
+
+#endif // PLATFORM_FLASH_SECTOR_ERASE
 
 
 static bool mountSDCard()
 static bool mountSDCard()
 {
 {

+ 1 - 1
src/ZuluSCSI_config.h

@@ -28,7 +28,7 @@
 #include <ZuluSCSI_platform.h>
 #include <ZuluSCSI_platform.h>
 
 
 // Use variables for version number
 // Use variables for version number
-#define FW_VER_NUM      "23.09.07"
+#define FW_VER_NUM      "23.09.21"
 #define FW_VER_SUFFIX   "devel"
 #define FW_VER_SUFFIX   "devel"
 #define ZULU_FW_VERSION FW_VER_NUM "-" FW_VER_SUFFIX
 #define ZULU_FW_VERSION FW_VER_NUM "-" FW_VER_SUFFIX
 
 

+ 110 - 13
src/ZuluSCSI_disk.cpp

@@ -1,8 +1,8 @@
 /** 
 /** 
  * SCSI2SD V6 - Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
  * SCSI2SD V6 - Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
- * Copyright (C) 2014 Doug Brown <doug@downtowndougbrown.com
- * ZuluSCSI™ - Copyright (c) 2022 Rabbit Hole Computing™
- * Copyright (c) 2023 joshua stein <jcs@jcs.org>
+ * Portions Copyright (C) 2014 Doug Brown <doug@downtowndougbrown.com>
+ * Portions Copyright (C) 2023 Eric Helgeson
+ * ZuluSCSI™ - Copyright (c) 2022-2023 Rabbit Hole Computing™
  *  
  *  
  * This file is licensed under the GPL version 3 or any later version. 
  * This file is licensed under the GPL version 3 or any later version. 
  * It is derived from disk.c in SCSI2SD V6
  * It is derived from disk.c in SCSI2SD V6
@@ -31,9 +31,13 @@
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_config.h"
 #include "ZuluSCSI_config.h"
 #include "ZuluSCSI_presets.h"
 #include "ZuluSCSI_presets.h"
+#ifdef ENABLE_AUDIO_OUTPUT
+#include "ZuluSCSI_audio.h"
+#endif
 #include "ZuluSCSI_cdrom.h"
 #include "ZuluSCSI_cdrom.h"
 #include "ImageBackingStore.h"
 #include "ImageBackingStore.h"
 #include "ROMDrive.h"
 #include "ROMDrive.h"
+#include "QuirksCheck.h"
 #include <minIni.h>
 #include <minIni.h>
 #include <string.h>
 #include <string.h>
 #include <strings.h>
 #include <strings.h>
@@ -441,11 +445,12 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int
 #ifdef PLATFORM_CONFIG_HOOK
 #ifdef PLATFORM_CONFIG_HOOK
         PLATFORM_CONFIG_HOOK(&img);
         PLATFORM_CONFIG_HOOK(&img);
 #endif
 #endif
+        quirksCheck(&img);
 
 
         if (img.name_from_image) 
         if (img.name_from_image) 
         { 
         { 
             setNameFromImage(img, filename); 
             setNameFromImage(img, filename); 
-            logmsg("Vendor / product id set from image file name");
+            logmsg("---- Vendor / product id set from image file name");
         }
         }
 
 
         setDefaultDriveInfo(target_idx);
         setDefaultDriveInfo(target_idx);
@@ -542,6 +547,12 @@ bool scsiDiskFilenameValid(const char* name)
             }
             }
         }
         }
     }
     }
+    // Check first character
+    if (!isalnum(name[0]))
+    {
+        // ignore files that don't start with a letter or a number
+        return false;
+    }
     return true;
     return true;
 }
 }
 
 
@@ -569,6 +580,23 @@ static void scsiDiskConfigDefaults(int target_idx)
     memset(img.serial, 0, sizeof(img.serial));
     memset(img.serial, 0, sizeof(img.serial));
 }
 }
 
 
+static void scsiDiskCheckDir(char * dir_name, int target_idx, image_config_t* img, S2S_CFG_TYPE type, const char* type_name)
+{
+    if (SD.exists(dir_name))
+    {
+        if (img->image_directory)
+        {
+            logmsg("-- Already found an image directory, skipping '", dir_name, "'");
+        }
+        else
+        {
+            img->deviceType = type;
+            img->image_directory = true;
+            logmsg("SCSI", target_idx, " searching default ", type_name, " image directory '", dir_name, "'");
+        }
+    }
+}
+
 // Load values for target configuration from given section if they exist.
 // Load values for target configuration from given section if they exist.
 // Otherwise keep current settings.
 // Otherwise keep current settings.
 static void scsiDiskLoadConfig(int target_idx, const char *section)
 static void scsiDiskLoadConfig(int target_idx, const char *section)
@@ -585,6 +613,11 @@ static void scsiDiskLoadConfig(int target_idx, const char *section)
     img.reinsert_on_inquiry = ini_getbool(section, "ReinsertCDOnInquiry", img.reinsert_on_inquiry, CONFIGFILE);
     img.reinsert_on_inquiry = ini_getbool(section, "ReinsertCDOnInquiry", img.reinsert_on_inquiry, CONFIGFILE);
     img.reinsert_after_eject = ini_getbool(section, "ReinsertAfterEject", img.reinsert_after_eject, CONFIGFILE);
     img.reinsert_after_eject = ini_getbool(section, "ReinsertAfterEject", img.reinsert_after_eject, CONFIGFILE);
     img.ejectButton = ini_getl(section, "EjectButton", 0, CONFIGFILE);
     img.ejectButton = ini_getl(section, "EjectButton", 0, CONFIGFILE);
+#ifdef ENABLE_AUDIO_OUTPUT
+    uint16_t vol = ini_getl(section, "CDAVolume", DEFAULT_VOLUME_LEVEL, CONFIGFILE) & 0xFF;
+    // Set volume on both channels
+    audio_set_volume(target_idx, (vol << 8) | vol);
+#endif
 
 
     char tmp[32];
     char tmp[32];
     memset(tmp, 0, sizeof(tmp));
     memset(tmp, 0, sizeof(tmp));
@@ -608,9 +641,35 @@ static void scsiDiskLoadConfig(int target_idx, const char *section)
         ini_gets(section, "ImgDir", "", tmp, sizeof(tmp), CONFIGFILE);
         ini_gets(section, "ImgDir", "", tmp, sizeof(tmp), CONFIGFILE);
         if (tmp[0])
         if (tmp[0])
         {
         {
-            logmsg("-- SCSI", target_idx, " using image directory \'", tmp, "'");
+            logmsg("SCSI", target_idx, " using image directory '", tmp, "'");
             img.image_directory = true;
             img.image_directory = true;
         }
         }
+        else
+        {
+            strcpy(tmp, "HD0");
+            tmp[2] += target_idx;
+            scsiDiskCheckDir(tmp, target_idx, &img, S2S_CFG_FIXED, "disk");
+
+            strcpy(tmp, "CD0");
+            tmp[2] += target_idx;
+            scsiDiskCheckDir(tmp, target_idx, &img, S2S_CFG_OPTICAL, "optical");
+
+            strcpy(tmp, "RM0");
+            tmp[2] += target_idx;
+            scsiDiskCheckDir(tmp, target_idx, &img, S2S_CFG_REMOVEABLE, "removable");
+
+            strcpy(tmp, "MO0");
+            tmp[2] += target_idx;
+            scsiDiskCheckDir(tmp, target_idx, &img, S2S_CFG_MO, "magneto-optical");
+
+            strcpy(tmp, "TP0");
+            tmp[2] += target_idx;
+            scsiDiskCheckDir(tmp, target_idx, &img, S2S_CFG_SEQUENTIAL, "tape");
+
+            strcpy(tmp, "FP0");
+            tmp[2] += target_idx;
+            scsiDiskCheckDir(tmp, target_idx, &img, S2S_CFG_FLOPPY_14MB, "floppy");
+        }
     }
     }
 }
 }
 
 
@@ -624,12 +683,13 @@ static int findNextImageAfter(image_config_t &img,
     FsFile dir;
     FsFile dir;
     if (dirname[0] == '\0')
     if (dirname[0] == '\0')
     {
     {
-        logmsg("Image directory name invalid for ID", (img.scsiId & 7));
+        logmsg("Image directory name invalid for ID", (img.scsiId & S2S_CFG_TARGET_ID_BITS));
         return 0;
         return 0;
     }
     }
     if (!dir.open(dirname))
     if (!dir.open(dirname))
     {
     {
         logmsg("Image directory '", dirname, "' couldn't be opened");
         logmsg("Image directory '", dirname, "' couldn't be opened");
+        return 0;
     }
     }
     if (!dir.isDir())
     if (!dir.isDir())
     {
     {
@@ -637,6 +697,12 @@ static int findNextImageAfter(image_config_t &img,
         dir.close();
         dir.close();
         return 0;
         return 0;
     }
     }
+    if (dir.isHidden())
+    {
+        logmsg("Image directory '", dirname, "' is hidden, skipping");
+        dir.close();
+        return 0;
+    }
 
 
     char first_name[MAX_FILE_PATH] = {'\0'};
     char first_name[MAX_FILE_PATH] = {'\0'};
     char candidate_name[MAX_FILE_PATH] = {'\0'};
     char candidate_name[MAX_FILE_PATH] = {'\0'};
@@ -646,10 +712,14 @@ static int findNextImageAfter(image_config_t &img,
         if (file.isDir()) continue;
         if (file.isDir()) continue;
         if (!file.getName(buf, MAX_FILE_PATH))
         if (!file.getName(buf, MAX_FILE_PATH))
         {
         {
-            logmsg("Image directory '", dirname, "'had invalid file");
+            logmsg("Image directory '", dirname, "' had invalid file");
             continue;
             continue;
         }
         }
         if (!scsiDiskFilenameValid(buf)) continue;
         if (!scsiDiskFilenameValid(buf)) continue;
+        if (file.isHidden()) {
+            logmsg("Image '", dirname, "/", buf, "' is hidden, skipping file");
+            continue;
+        }
 
 
         // keep track of the first item to allow wrapping
         // keep track of the first item to allow wrapping
         // without having to iterate again
         // without having to iterate again
@@ -686,13 +756,14 @@ static int findNextImageAfter(image_config_t &img,
     else
     else
     {
     {
         logmsg("Image directory '", dirname, "' was empty");
         logmsg("Image directory '", dirname, "' was empty");
+        img.image_directory = false;
         return 0;
         return 0;
     }
     }
 }
 }
 
 
 int scsiDiskGetNextImageName(image_config_t &img, char *buf, size_t buflen)
 int scsiDiskGetNextImageName(image_config_t &img, char *buf, size_t buflen)
 {
 {
-    int target_idx = img.scsiId & 7;
+    int target_idx = img.scsiId & S2S_CFG_TARGET_ID_BITS;
 
 
     char section[6] = "SCSI0";
     char section[6] = "SCSI0";
     section[4] = '0' + target_idx;
     section[4] = '0' + target_idx;
@@ -708,10 +779,36 @@ int scsiDiskGetNextImageName(image_config_t &img, char *buf, size_t buflen)
         int dirlen = ini_gets(section, key, "", dirname, sizeof(dirname), CONFIGFILE);
         int dirlen = ini_gets(section, key, "", dirname, sizeof(dirname), CONFIGFILE);
         if (!dirlen)
         if (!dirlen)
         {
         {
-            // If image_directory set but ImageDir is not, could be used to
-            // indicate an image directory configured via folder structure.
-            // Not implemented, so treat this as equivalent to missing ImageDir
-            return 0;
+            switch (img.deviceType)
+            {
+                case S2S_CFG_FIXED:
+                    strcpy(dirname ,"HD0");
+                    break;
+                case S2S_CFG_OPTICAL:
+                    strcpy(dirname, "CD0");
+                break;
+                case S2S_CFG_REMOVEABLE:
+                    strcpy(dirname, "RM0");
+                break;
+                case S2S_CFG_MO:
+                    strcpy(dirname, "MO0");
+                break;
+                case S2S_CFG_SEQUENTIAL:
+                    strcpy(dirname ,"TP0");
+                break;
+                case S2S_CFG_FLOPPY_14MB:
+                    strcpy(dirname, "FP0");
+                break;
+                default:
+                    dbgmsg("No matching device type for default directory found");
+                    return 0;
+            }
+            dirname[2] += target_idx;
+            if (!SD.exists(dirname))
+            {
+                dbgmsg("Default image directory, ", dirname, " does not exist");
+                return 0;
+            }
         }
         }
 
 
         // find the next filename
         // find the next filename
@@ -790,7 +887,7 @@ void scsiDiskLoadConfig(int target_idx)
     if (scsiDiskGetNextImageName(img, filename, sizeof(filename)))
     if (scsiDiskGetNextImageName(img, filename, sizeof(filename)))
     {
     {
         int blocksize = (img.deviceType == S2S_CFG_OPTICAL) ? 2048 : 512;
         int blocksize = (img.deviceType == S2S_CFG_OPTICAL) ? 2048 : 512;
-        logmsg("-- Opening '", filename, "' for id:", target_idx, ", specified in " CONFIGFILE);
+        logmsg("-- Opening '", filename, "' for id: ", target_idx);
         scsiDiskOpenHDDImage(target_idx, filename, target_idx, 0, blocksize);
         scsiDiskOpenHDDImage(target_idx, filename, target_idx, 0, blocksize);
     }
     }
 }
 }

+ 1 - 0
src/ZuluSCSI_main.cpp

@@ -59,6 +59,7 @@ extern "C" void loop(void)
 #else
 #else
 int main(void)
 int main(void)
 {
 {
+    
     zuluscsi_setup();
     zuluscsi_setup();
     while (1)
     while (1)
     {
     {

+ 2 - 0
zuluscsi.ini

@@ -45,6 +45,8 @@
 #ReinsertCDOnInquiry = 1 # Reinsert any ejected CD-ROM image on Inquiry command
 #ReinsertCDOnInquiry = 1 # Reinsert any ejected CD-ROM image on Inquiry command
 #ReinsertAfterEject = 1 # Reinsert next CD image after eject, if multiple images configured.
 #ReinsertAfterEject = 1 # Reinsert next CD image after eject, if multiple images configured.
 #EjectButton = 0 # Enable eject by button 1 or 2, or set 0 to disable
 #EjectButton = 0 # Enable eject by button 1 or 2, or set 0 to disable
+#CDAVolume = 63 # Change CD Audio default volume. Maximum 255.
+#DisableMacSanityCheck = 0 # Disable sanity warnings for Mac disk drives. Default is 0 - enable checks
 
 
 # Settings can be overridden for individual devices.
 # Settings can be overridden for individual devices.
 #[SCSI2]
 #[SCSI2]