소스 검색

Merge branch 'main' into pico-daynaport

Morio 2 년 전
부모
커밋
a306ae9a72

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

@@ -7,7 +7,7 @@ on:
 
 jobs:
   build_firmware:
-    name: RHC-internal
+    name: RHC-internal-Z4
     runs-on: self-hosted
 #    name: Build firmware on Ubuntu 20.04
 #    runs-on: ubuntu-20.04
@@ -26,7 +26,7 @@ jobs:
       - name: Build firmware
         run: |
           cd ZuluSCSI
-          pio run -j 8 -ve ZuluSCSI_Pico_DaynaPORT
+          pio run -v -j8
     
       - name: Rename firmware files
         run: |

+ 1 - 1
lib/SCSI2SD/include/scsi2sd.h

@@ -73,7 +73,7 @@ typedef enum
 typedef enum
 {
 	S2S_CFG_FIXED,
-	S2S_CFG_REMOVEABLE,
+	S2S_CFG_REMOVABLE,
 	S2S_CFG_OPTICAL,
 	S2S_CFG_FLOPPY_14MB,
 	S2S_CFG_MO,

+ 2 - 2
lib/SCSI2SD/src/firmware/inquiry.c

@@ -201,7 +201,7 @@ void s2s_scsiInquiry()
 			break;
 
 		case S2S_CFG_FLOPPY_14MB:
-		case S2S_CFG_REMOVEABLE:
+		case S2S_CFG_REMOVABLE:
 			scsiDev.data[1] |= 0x80; // Removable bit.
 			break;
 
@@ -268,7 +268,7 @@ uint8_t getDeviceTypeQualifier()
 		break;
 
 	case S2S_CFG_FLOPPY_14MB:
-	case S2S_CFG_REMOVEABLE:
+	case S2S_CFG_REMOVABLE:
 		return 0;
 		break;
 

+ 1 - 1
lib/SCSI2SD/src/firmware/mode.c

@@ -272,7 +272,7 @@ static void doModeSense(
 	switch (scsiDev.target->cfg->deviceType)
 	{
 	case S2S_CFG_FIXED:
-	case S2S_CFG_REMOVEABLE:
+	case S2S_CFG_REMOVABLE:
 		mediumType = 0; // We should support various floppy types here!
 		// Contains cache bits (0) and a Write-Protect bit.
 		deviceSpecificParam =

+ 244 - 28
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.cpp

@@ -24,15 +24,28 @@
 #include "gd32f20x_fmc.h"
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_config.h"
+#include "usbd_conf.h"
+#include "usb_serial.h"
 #include "greenpak.h"
 #include <SdFat.h>
 #include <scsi.h>
 #include <assert.h>
+#include <audio.h>
+#include <ZuluSCSI_audio.h>
+
+extern SdFs SD;
 
 extern "C" {
 
 const char *g_platform_name = PLATFORM_NAME;
 static bool g_enable_apple_quirks = false;
+bool g_direct_mode = false;
+ZuluSCSIVersion_t g_zuluscsi_version = ZSVersion_unknown;
+
+// hw_config.cpp c functions
+#include "platform_hw_config.h"
+
+
 
 /*************************/
 /* Timing functions      */
@@ -127,6 +140,47 @@ void SysTick_Handle_PreEmptively()
 /* GPIO init   */
 /***************/
 
+#ifdef PLATFORM_VERSION_1_1_PLUS
+static void init_audio_gpio()
+{
+        gpio_pin_remap1_config(GPIO_PCF5, GPIO_PCF5_SPI1_IO_REMAP1, ENABLE);
+        gpio_pin_remap1_config(GPIO_PCF5, GPIO_PCF5_SPI1_NSCK_REMAP1, ENABLE);
+        gpio_pin_remap1_config(GPIO_PCF4, GPIO_PCF4_SPI1_SCK_PD3_REMAP, ENABLE);
+        gpio_init(I2S_CK_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, I2S_CK_PIN);
+        gpio_init(I2S_SD_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, I2S_SD_PIN);
+        gpio_init(I2S_WS_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, I2S_WS_PIN);
+}
+#endif
+
+// Method of determining whichi scsi board is being used
+static ZuluSCSIVersion_t get_zuluscsi_version()
+{
+#ifdef DIGITAL_VERSION_DETECT_PORT
+    bool pull_down;
+    bool pull_up;
+
+    gpio_init(DIGITAL_VERSION_DETECT_PORT, GPIO_MODE_IPU, 0, DIGITAL_VERSION_DETECT_PIN);
+    delay_us(10);
+    pull_up  = SET == gpio_input_bit_get(DIGITAL_VERSION_DETECT_PORT, DIGITAL_VERSION_DETECT_PIN);
+
+    gpio_init(DIGITAL_VERSION_DETECT_PORT, GPIO_MODE_IPD, 0, DIGITAL_VERSION_DETECT_PIN);
+    delay_us(10);
+    pull_down = RESET ==  gpio_input_bit_get(DIGITAL_VERSION_DETECT_PORT, DIGITAL_VERSION_DETECT_PIN);
+
+    if (pull_up && pull_down)
+        return ZSVersion_v1_1;
+    if (pull_down && !pull_up)
+        return ZSVersion_v1_1_ODE;
+    if (pull_up && !pull_down)
+    {
+        return ZSVersion_v1_2;
+    }
+#endif // DIGITAL_DETECT_VERSION
+
+    return ZSVersion_unknown;
+}
+
+
 // Initialize SPI and GPIO configuration
 // Clock has already been initialized by system_gd32f20x.c
 void platform_init()
@@ -179,6 +233,11 @@ void platform_init()
     // SCSI pins.
     // Initialize open drain outputs to high.
     SCSI_RELEASE_OUTPUTS();
+
+    // determine the ZulusSCSI board version
+    g_zuluscsi_version = get_zuluscsi_version();
+
+    // Init SCSI pins GPIOs
     gpio_init(SCSI_OUT_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SCSI_OUT_DATA_MASK | SCSI_OUT_REQ);
     gpio_init(SCSI_OUT_IO_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SCSI_OUT_IO_PIN);
     gpio_init(SCSI_OUT_CD_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, SCSI_OUT_CD_PIN);
@@ -190,7 +249,6 @@ void platform_init()
     gpio_init(SCSI_IN_PORT, GPIO_MODE_IN_FLOATING, 0, SCSI_IN_MASK);
     gpio_init(SCSI_ATN_PORT, GPIO_MODE_IN_FLOATING, 0, SCSI_ATN_PIN);
     gpio_init(SCSI_BSY_PORT, GPIO_MODE_IN_FLOATING, 0, SCSI_BSY_PIN);
-    gpio_init(SCSI_SEL_PORT, GPIO_MODE_IN_FLOATING, 0, SCSI_SEL_PIN);
     gpio_init(SCSI_ACK_PORT, GPIO_MODE_IN_FLOATING, 0, SCSI_ACK_PIN);
     gpio_init(SCSI_RST_PORT, GPIO_MODE_IN_FLOATING, 0, SCSI_RST_PIN);
 
@@ -211,57 +269,163 @@ void platform_init()
     gpio_init(SD_SDIO_CMD_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, SD_SDIO_CMD);
 #endif
 
+#ifdef PLATFORM_VERSION_1_1_PLUS
+    if (g_zuluscsi_version == ZSVersion_v1_1)
+    {
+        // SCSI Select
+        gpio_init(SCSI_SEL_PORT, GPIO_MODE_IN_FLOATING, 0, SCSI_SEL_PIN);
+
+        // DIP switches
+        gpio_init(DIP_PORT, GPIO_MODE_IPD, 0, DIPSW1_PIN | DIPSW2_PIN | DIPSW3_PIN);
+        gpio_init(EJECT_1_PORT, GPIO_MODE_IPU, 0, EJECT_1_PIN);
+        gpio_init(EJECT_2_PORT, GPIO_MODE_IPU, 0, EJECT_2_PIN);
+
+    }
+    else if (g_zuluscsi_version == ZSVersion_v1_1_ODE)
+    {
+        // SCSI Select
+        gpio_init(SCSI_ODE_SEL_PORT, GPIO_MODE_IN_FLOATING, 0, SCSI_ODE_SEL_PIN);
+        // DIP switches
+        gpio_init(ODE_DIP_PORT, GPIO_MODE_IPD, 0, ODE_DIPSW1_PIN | ODE_DIPSW2_PIN | ODE_DIPSW3_PIN);
+        // Buttons
+        gpio_init(EJECT_BTN_PORT, GPIO_MODE_IPU, 0, EJECT_BTN_PIN);
+        gpio_init(USER_BTN_PORT, GPIO_MODE_IPU, 0, USER_BTN_PIN);
+        init_audio_gpio();
+        g_audio_enabled = true;
+    }
+    else if (g_zuluscsi_version == ZSVersion_v1_2)
+    {
+        // SCSI Select
+        gpio_init(SCSI_ODE_SEL_PORT, GPIO_MODE_IN_FLOATING, 0, SCSI_ODE_SEL_PIN);
+        // General settings DIP switch
+        gpio_init(V1_2_DIPSW_TERM_PORT, GPIO_MODE_IPD, 0, V1_2_DIPSW_TERM_PIN);
+        gpio_init(V1_2_DIPSW_DBG_PORT, GPIO_MODE_IPD, 0, V1_2_DIPSW_DBG_PIN);
+        gpio_init(V1_2_DIPSW_QUIRKS_PORT, GPIO_MODE_IPD, 0, V1_2_DIPSW_QUIRKS_PIN);
+        // Direct/Raw Mode Select
+        gpio_init(V1_2_DIPSW_DIRECT_MODE_PORT, GPIO_MODE_IPD, 0, V1_2_DIPSW_DIRECT_MODE_PIN);
+        // SCSI ID dip switch
+        gpio_init(DIPSW_SCSI_ID_BIT_PORT, GPIO_MODE_IPD, 0, DIPSW_SCSI_ID_BIT_PINS);
+        // Device select BCD rotary DIP switch
+        gpio_init(DIPROT_DEVICE_SEL_BIT_PORT, GPIO_MODE_IPD, 0, DIPROT_DEVICE_SEL_BIT_PINS);
+
+        // Buttons
+        gpio_init(EJECT_BTN_PORT, GPIO_MODE_IPU, 0, EJECT_BTN_PIN);
+        gpio_init(USER_BTN_PORT,  GPIO_MODE_IPU, 0, USER_BTN_PIN);
+
+        LED_EJECT_OFF();
+        gpio_init(LED_EJECT_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, LED_EJECT_PIN);
+    }
+#else
+    // SCSI Select
+    gpio_init(SCSI_SEL_PORT, GPIO_MODE_IN_FLOATING, 0, SCSI_SEL_PIN);
     // DIP switches
     gpio_init(DIP_PORT, GPIO_MODE_IPD, 0, DIPSW1_PIN | DIPSW2_PIN | DIPSW3_PIN);
+    // Ejection buttons
+    gpio_init(EJECT_1_PORT, GPIO_MODE_IPU, 0, EJECT_1_PIN);
+    gpio_init(EJECT_2_PORT, GPIO_MODE_IPU, 0, EJECT_2_PIN);
 
+#endif // PLATFORM_VERSION_1_1_PLUS
     // LED pins
     gpio_bit_set(LED_PORT, LED_PINS);
     gpio_init(LED_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, LED_PINS);
 
-    // Ejection buttons
-    gpio_init(EJECT_1_PORT, GPIO_MODE_IPU, 0, EJECT_1_PIN);
-    gpio_init(EJECT_2_PORT, GPIO_MODE_IPU, 0, EJECT_2_PIN);
 
     // SWO trace pin on PB3
     gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3);
 }
 
-void platform_late_init()
+static void set_termination(uint32_t port, uint32_t pin, const char *switch_name)
 {
-    logmsg("Platform: ", g_platform_name);
-    logmsg("FW Version: ", g_log_firmwareversion);
-    
-#ifdef ZULUSCSI_V1_0_mini
-    logmsg("DIPSW3 is ON: Enabling SCSI termination");
-#else
-    if (gpio_input_bit_get(DIP_PORT, DIPSW3_PIN))
+    if (gpio_input_bit_get(port, pin))
     {
-        logmsg("DIPSW3 is ON: Enabling SCSI termination");
+        logmsg(switch_name, " is ON: Enabling SCSI termination");
         gpio_bit_reset(SCSI_TERM_EN_PORT, SCSI_TERM_EN_PIN);
     }
     else
     {
-        logmsg("DIPSW3 is OFF: SCSI termination disabled");
+        logmsg(switch_name, "is OFF: SCSI termination disabled");
     }
-#endif // ZULUSCSI_V1_0_mini
+}
 
-    if (gpio_input_bit_get(DIP_PORT, DIPSW2_PIN))
+static bool get_debug(uint32_t port, uint32_t pin, const char *switch_name)
+{
+    if (gpio_input_bit_get(port, pin))
     {
-        logmsg("DIPSW2 is ON: enabling debug messages");
-        g_log_debug = true;
+        logmsg(switch_name, " is ON: enabling debug messages");
+        return true;
     }
-    else
+    return false;
+}
+
+static bool get_quirks(uint32_t port, uint32_t pin, const char *switch_name)
+{
+    if (gpio_input_bit_get(port, pin))
     {
-        g_log_debug = false;
+        logmsg(switch_name, " is ON: enabling Apple quirks by default");
+        return true;
     }
+    return false;
+}
 
-    if (gpio_input_bit_get(DIP_PORT, DIPSW1_PIN))
+#ifdef PLATFORM_VERSION_1_1_PLUS
+static bool get_direct_mode(uint32_t port, uint32_t pin, const char *switch_name)
+{
+    if (!gpio_input_bit_get(port, pin))
     {
-        logmsg("DIPSW1 is ON: enabling Apple quirks by default");
-        g_enable_apple_quirks = true;
+        logmsg(switch_name, " is OFF: Enabling direct/raw mode");
+        return true;
     }
+    logmsg(switch_name, " is ON: Disabling direct/raw mode");
+    return false;
+}
+#endif
+
+void platform_late_init()
+{
+    // Initialize usb for CDC serial output
+    usb_serial_init();
+
+    logmsg("Platform: ", g_platform_name);
+    logmsg("FW Version: ", g_log_firmwareversion);
 
-    greenpak_load_firmware();
+#ifdef PLATFORM_VERSION_1_1_PLUS
+    if (ZSVersion_v1_1 == g_zuluscsi_version)
+    {
+        logmsg("Board Version: ZuluSCSI v1.1 Standard Edition");
+        set_termination(DIP_PORT, DIPSW3_PIN, "DIPSW3");
+        g_log_debug = get_debug(DIP_PORT, DIPSW2_PIN, "DIPSW2");
+        g_enable_apple_quirks = get_quirks(DIP_PORT, DIPSW1_PIN, "DIPSW1");
+        greenpak_load_firmware();
+    }
+    else if (ZSVersion_v1_1_ODE == g_zuluscsi_version)
+    {
+        logmsg("Board Version: ZuluSCSI v1.1 ODE");
+        logmsg("ODE - Optical Drive Emulator");
+        set_termination(ODE_DIP_PORT, ODE_DIPSW3_PIN, "DIPSW3");
+        g_log_debug = get_debug(ODE_DIP_PORT, ODE_DIPSW2_PIN, "DIPSW2");
+        g_enable_apple_quirks = get_quirks(ODE_DIP_PORT, ODE_DIPSW1_PIN, "DIPSW1");
+        audio_setup();
+    }
+    else if (ZSVersion_v1_2 == g_zuluscsi_version)
+    {
+        logmsg("Board Version: ZuluSCSI v1.2");
+        hw_config_init_gpios();
+        set_termination(V1_2_DIPSW_TERM_PORT, V1_2_DIPSW_TERM_PIN, "DIPSW4");
+        g_log_debug = get_debug(V1_2_DIPSW_DBG_PORT, V1_2_DIPSW_DBG_PIN, "DIPSW3");
+        g_direct_mode = get_direct_mode(V1_2_DIPSW_DIRECT_MODE_PORT, V1_2_DIPSW_DIRECT_MODE_PIN, "DIPSW2");
+        g_enable_apple_quirks = get_quirks(V1_2_DIPSW_QUIRKS_PORT, V1_2_DIPSW_QUIRKS_PIN, "DIPSW1");
+        hw_config_init_state(g_direct_mode);
+    }
+#else // PLATFORM_VERSION_1_1_PLUS - ZuluSCSI v1.0 and v1.0 minis gpio config
+    #ifdef ZULUSCSI_V1_0_mini
+        logmsg("SCSI termination is always on");
+    #elif defined(ZULUSCSI_V1_0)
+        set_termination(DIP_PORT, DIPSW3_PIN, "DIPSW3");
+        g_log_debug = get_debug(DIP_PORT, DIPSW2_PIN, "DIPSW2");
+        g_enable_apple_quirks = get_quirks(DIP_PORT, DIPSW1_PIN, "DIPSW1");
+    #endif // ZULUSCSI_V1_0_mini
+#endif // PLATFORM_VERSION_1_1_PLUS
+    
 }
 
 void platform_disable_led(void)
@@ -319,11 +483,37 @@ static void adc_poll()
 }
 
 /*****************************************/
-/* Crash handlers                        */
+/* Debug logging and watchdog            */
 /*****************************************/
 
-extern SdFs SD;
+// 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.
 
+static void usb_log_poll()
+{
+    static uint32_t logpos = 0;
+
+    if (usb_serial_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_DATA_PACKET_SIZE) len = USB_CDC_DATA_PACKET_SIZE;
+
+        // Update log position by the actual number of bytes sent
+        // If USB CDC buffer is full, this may be 0
+        usb_serial_send((uint8_t*)data, len);
+        logpos -= available - len;
+    }
+}
+
+/*****************************************/
+/* Crash handlers                        */
+/*****************************************/
 // Writes log data to the PB3 SWO pin
 void platform_log(const char *s)
 {
@@ -337,6 +527,10 @@ void platform_log(const char *s)
 
 void platform_emergency_log_save()
 {
+#ifdef ZULUSCSI_HARDWARE_CONFIG
+    if (g_hw_config.is_active())
+        return;
+#endif
     platform_set_sd_callback(NULL, NULL);
 
     SD.begin(SD_CONFIG_CRASH);
@@ -390,11 +584,12 @@ void show_hardfault(uint32_t *sp)
         logmsg("STACK ", (uint32_t)p, ":    ", p[0], " ", p[1], " ", p[2], " ", p[3]);
         p += 4;
     }
-
+ 
     platform_emergency_log_save();
 
     while (1)
     {
+        usb_log_poll();
         // Flash the crash address on the LED
         // Short pulse means 0, long pulse means 1
         int base_delay = 1000;
@@ -467,6 +662,7 @@ void __assert_func(const char *file, int line, const char *func, const char *exp
 
     while(1)
     {
+        usb_log_poll();
         LED_OFF();
         for (int j = 0; j < 1000; j++) delay_ns(100000);
         LED_ON();
@@ -488,13 +684,21 @@ void platform_reset_watchdog()
     // 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;
+
+    // USB log is polled here also to make sure any log messages in fault states
+    // get passed to USB.
+    usb_log_poll();
 }
 
 // Poll function that is called every few milliseconds.
 // Can be left empty or used for platform-specific processing.
 void platform_poll()
 {
+#ifdef ENABLE_AUDIO_OUTPUT
+    audio_poll();
+#endif
     adc_poll();
+    usb_log_poll();
 }
 
 uint8_t platform_get_buttons()
@@ -502,9 +706,21 @@ uint8_t platform_get_buttons()
     // Buttons are active low: internal pull-up is enabled,
     // and when button is pressed the pin goes low.
     uint8_t buttons = 0;
+#ifdef PLATFORM_VERSION_1_1_PLUS
+    if (g_zuluscsi_version == ZSVersion_v1_1_ODE || g_zuluscsi_version == ZSVersion_v1_2)
+    {
+        if (!gpio_input_bit_get(EJECT_BTN_PORT, EJECT_BTN_PIN))   buttons |= 1;
+        if (!gpio_input_bit_get(USER_BTN_PORT, USER_BTN_PIN))   buttons |= 4;
+    }
+    else
+    {
+        if (!gpio_input_bit_get(EJECT_1_PORT, EJECT_1_PIN))   buttons |= 1;
+        if (!gpio_input_bit_get(EJECT_2_PORT, EJECT_2_PIN))   buttons |= 2;
+    }
+#else
     if (!gpio_input_bit_get(EJECT_1_PORT, EJECT_1_PIN))   buttons |= 1;
     if (!gpio_input_bit_get(EJECT_2_PORT, EJECT_2_PIN))   buttons |= 2;
-
+#endif
     // Simple debouncing logic: handle button releases after 100 ms delay.
     static uint32_t debounce;
     static uint8_t buttons_debounced = 0;

+ 18 - 4
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.h

@@ -27,7 +27,6 @@
 #include <gd32f20x.h>
 #include <gd32f20x_gpio.h>
 #include <scsi2sd.h>
-#include "ZuluSCSI_config.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -44,16 +43,31 @@ extern const char *g_platform_name;
 #   define PLATFORM_REVISION "1.0"
 #   define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_ASYNC_50
 #   include "ZuluSCSI_v1_0_gpio.h"
-#elif defined(ZULUSCSI_V1_1)
-#   define PLATFORM_NAME "ZuluSCSI v1.1"
-#   define PLATFORM_REVISION "1.1"
+#else
+#   define PLATFORM_NAME "ZuluSCSI v1.1+"
+#   define PLATFORM_REVISION "1.1+"
 #   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_VERSION_1_1_PLUS
+#   define ZULUSCSI_HARDWARE_CONFIG
 #   include "ZuluSCSI_v1_1_gpio.h"
 #endif
 
+#include "platform_hw_config.h"
+
+enum ZuluSCSIVersion_t
+{
+    ZSVersion_unknown,
+    ZSVersion_v1_1,
+    ZSVersion_v1_1_ODE,
+    ZSVersion_v1_2
+
+};
+
+extern enum ZuluSCSIVersion_t g_zuluscsi_version;
+
 #ifndef PLATFORM_VDD_WARNING_LIMIT_mV
 #define PLATFORM_VDD_WARNING_LIMIT_mV 2800
 #endif

+ 5 - 0
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_v1_0_gpio.h

@@ -93,6 +93,11 @@
 #define SCSI_SEL_IRQ EXTI10_15_IRQHandler
 #define SCSI_SEL_IRQn EXTI10_15_IRQn
 
+// Unused place holders for compiling
+#define SCSI_ODE_SEL_PORT   SCSI_SEL_PORT
+#define SCSI_ODE_SEL_PIN    SCSI_SEL_PIN
+
+
 // BSY pin uses EXTI interrupt
 #define SCSI_BSY_PORT GPIOB
 #define SCSI_BSY_PIN  GPIO_PIN_10

+ 116 - 0
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_v1_1_gpio.h

@@ -77,6 +77,13 @@
 #define GREENPAK_IRQ  EXTI3_IRQHandler
 #define GREENPAK_IRQn EXTI3_IRQn
 
+
+// I2C for ODE and v1.2
+#define ODE_I2C_PORT GPIOB
+#define ODE_I2C_SCL GPIO_PIN_6
+#define ODE_I2C_SDA GPIO_PIN_7
+
+
 // SCSI input data port
 #define SCSI_IN_PORT  GPIOE
 #define SCSI_IN_DB7   GPIO_PIN_15
@@ -136,6 +143,15 @@
 #define SCSI_SEL_IRQ EXTI10_15_IRQHandler
 #define SCSI_SEL_IRQn EXTI10_15_IRQn
 
+// SEL pin for ODE and v1.2
+#define SCSI_ODE_SEL_PORT GPIOD
+#define SCSI_ODE_SEL_PIN  GPIO_PIN_15
+#define SCSI_ODE_SEL_EXTI EXTI_15
+#define SCSI_ODE_SEL_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOD
+#define SCSI_ODE_SEL_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_15
+#define SCSI_ODE_SEL_IRQ EXTI10_15_IRQHandler
+#define SCSI_ODE_SEL_IRQn EXTI10_15_IRQn
+
 // BSY pin uses EXTI interrupt
 #define SCSI_BSY_PORT GPIOB
 #define SCSI_BSY_PIN  GPIO_PIN_10
@@ -170,12 +186,102 @@
 #define SD_SDIO_CMD_PORT  GPIOD
 #define SD_SDIO_CMD       GPIO_PIN_2
 
+// V1.2 SD Card write protect and card detect
+#define SD_WP_PORT GPIOE
+#define SD_WP_PIN  GPIO_PIN_2
+#define SD_CD_PORT GPIOE
+#define SD_CD_PIN  GPIO_PIN_3
+
+// v1.2 has a strong pull up, the ODE has strong pull down, v1.1 vanilla test for a floating pin
+#define DIGITAL_VERSION_DETECT_PORT  GPIOA
+#define DIGITAL_VERSION_DETECT_PIN   GPIO_PIN_15
+
+// v1.2 and future boards detect version via voltage level
+// v1.2: 2.5V
+// TODO get ADC version detection working
+#define ADC_VERSION_DETECT_PORT GPIOC
+#define ADC_VERSION_DETECT_PIN  GPIO_PIN_0
+#define ADC_VERSION_DETECT_CHANNEL ADC_CHANNEL_10
+#define ADC_VERSION_DETECT_V1_2_LIMIT_LOW 0
+#define ADC_VERSION_DETECT_V1_2_LIMIT_HIGH 4096*1
+
+// SPI Pins: v1.2
+#define SPI_CS_PORT     GPIOB
+#define SPI_CS_PIN      GPIO_PIN_9
+#define SPI_MISO_PORT   GPIOC
+#define SPI_MISO_PIN    GPIO_PIN_2
+#define SPI_MOSI_PORT   GPIOC
+#define SPI_MOSI_PIN    GPIO_PIN_3
+#define SPI_CK_PORT     GPIOD
+#define SPI_CK_PIN      GPIO_PIN_3
+// I2S pins: ODE and v1.2
+#define I2S_SD_PORT     SPI_MOSI_PORT
+#define I2S_SD_PIN      SPI_MOSI_PIN
+#define I2S_WS_PORT     SPI_CS_PORT
+#define I2S_WS_PIN      SPI_CS_PIN
+#define I2S_CK_PORT     SPI_CK_PORT
+#define I2S_CK_PIN      SPI_CK_PIN
+
+// SPI/I2S DMA - ODE and v1.2
+#define SPI_DMA         DMA0
+#define SPI_DMA_CH      DMA_CH4
+#define SPI_RCU_DMA     RCU_DMA0
+#define SPI_I2S_SPI     SPI1
+#define SPI_RCU_I2S_SPI RCU_SPI1
+#define SPI_IRQHandler  DMA0_Channel4_IRQHandler
+#define SPI_DMA_IRQn    DMA0_Channel4_IRQn
+
 // DIP switches
 #define DIP_PORT     GPIOB
 #define DIPSW1_PIN   GPIO_PIN_4
 #define DIPSW2_PIN   GPIO_PIN_5
 #define DIPSW3_PIN   GPIO_PIN_6
 
+// ODE DIP switch pins
+#define ODE_DIP_PORT     GPIOB
+#define ODE_DIPSW1_PIN   GPIO_PIN_8
+#define ODE_DIPSW2_PIN   GPIO_PIN_5
+#define ODE_DIPSW3_PIN   GPIO_PIN_4
+
+// v1.2 DIP switch pins
+#define V1_2_DIPSW_TERM_PORT        GPIOB
+#define V1_2_DIPSW_TERM_PIN         GPIO_PIN_4
+#define V1_2_DIPSW_DBG_PORT         GPIOB
+#define V1_2_DIPSW_DBG_PIN          GPIO_PIN_5
+#define V1_2_DIPSW_QUIRKS_PORT      GPIOE
+#define V1_2_DIPSW_QUIRKS_PIN       GPIO_PIN_0
+#define V1_2_DIPSW_DIRECT_MODE_PORT GPIOB
+#define V1_2_DIPSW_DIRECT_MODE_PIN  GPIO_PIN_8
+// SCSI ID DIP switch
+#define DIPSW_SCSI_ID_BIT_PORT  GPIOC
+#define DIPSW_SCSI_ID_BIT1_PIN  GPIO_PIN_13
+#define DIPSW_SCSI_ID_BIT2_PIN  GPIO_PIN_14
+#define DIPSW_SCSI_ID_BIT3_PIN  GPIO_PIN_15
+#define DIPSW_SCSI_ID_BIT_PINS  (DIPSW_SCSI_ID_BIT1_PIN | DIPSW_SCSI_ID_BIT2_PIN | DIPSW_SCSI_ID_BIT3_PIN)
+#define DIPSW_SCSI_ID_BIT_SHIFT 13
+// Rotary DIP switch
+#define DIPROT_DEVICE_SEL_BIT_PORT  GPIOE
+#define DIPROT_DEVICE_SEL_BIT1_PIN  GPIO_PIN_5
+#define DIPROT_DEVICE_SEL_BIT2_PIN  GPIO_PIN_6
+#define DIPROT_DEVICE_SEL_BIT_PINS  (DIPROT_DEVICE_SEL_BIT1_PIN | DIPROT_DEVICE_SEL_BIT2_PIN)
+#define DIPROT_DEVICE_SEL_BIT_SHIFT 5
+
+// ODE I2S Audio
+#define ODE_I2S_CK_PORT GPIOD
+#define ODE_I2S_CK_PIN  GPIO_PIN_3
+#define ODE_I2S_SD_PORT GPIOC
+#define ODE_I2S_SD_PIN  GPIO_PIN_3
+#define ODE_I2S_WS_PORT GPIOB
+#define ODE_I2S_WS_PIN  GPIO_PIN_9
+#define ODE_DMA         DMA0
+#define ODE_DMA_CH      DMA_CH4
+#define ODE_RCU_DMA     RCU_DMA0
+#define ODE_I2S_SPI     SPI1
+#define ODE_RCU_I2S_SPI RCU_SPI1
+#define ODE_IRQHandler  DMA0_Channel4_IRQHandler
+#define ODE_DMA_IRQn    DMA0_Channel4_IRQn
+
+
 // Status LED pins
 #define LED_PORT     GPIOC
 #define LED_I_PIN    GPIO_PIN_4
@@ -183,6 +289,10 @@
 #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)
+#define LED_EJECT_PORT  GPIOA
+#define LED_EJECT_PIN   GPIO_PIN_1
+#define LED_EJECT_ON()  gpio_bit_reset(LED_EJECT_PORT, LED_EJECT_PIN)
+#define LED_EJECT_OFF() gpio_bit_set(LED_EJECT_PORT, LED_EJECT_PIN)
 
 // Ejection buttons are available on expansion header J303.
 // PE5 = channel 1, PE6 = channel 2
@@ -191,3 +301,9 @@
 #define EJECT_1_PIN     GPIO_PIN_5
 #define EJECT_2_PORT    GPIOE
 #define EJECT_2_PIN     GPIO_PIN_6
+
+// Ejection button is on GPIO PA3 and USER button is on GPIO PA2
+#define EJECT_BTN_PORT  GPIOA
+#define EJECT_BTN_PIN   GPIO_PIN_3
+#define USER_BTN_PORT   GPIOA
+#define USER_BTN_PIN    GPIO_PIN_2

+ 414 - 0
lib/ZuluSCSI_platform_GD32F205/audio.cpp

@@ -0,0 +1,414 @@
+/** 
+ * Copyright (C) 2023 saybur
+ * 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/>.
+**/
+#ifdef ENABLE_AUDIO_OUTPUT
+#include "audio.h"
+#include "ZuluSCSI_platform.h"
+#include "ZuluSCSI_audio.h"
+#include "ZuluSCSI_v1_1_gpio.h"
+#include "ZuluSCSI_log.h"
+
+extern "C" 
+{
+    #include "gd32f20x_rcu.h" 
+    #include "gd32f20x_dma.h"
+    #include "gd32f20x_misc.h"
+}
+
+bool g_audio_enabled = false;
+bool g_audio_stopped = true;
+
+// some chonky buffers to store audio samples
+static uint8_t sample_circ_buf[AUDIO_BUFFER_SIZE] __attribute__((aligned(4)));
+
+// tracking for the state for the circular buffer, A first half and B second half
+enum bufstate { STALE, FILLING, READY };
+static volatile bufstate sbufst_a = STALE;
+static volatile bufstate sbufst_b = STALE;
+static uint8_t sbufswap = 0;
+
+// tracking for audio playback
+static uint8_t audio_owner; // SCSI ID or 0xFF when idle
+static volatile bool audio_paused = false;
+static ImageBackingStore* audio_file;
+static volatile uint64_t fpos;
+static volatile uint32_t fleft;
+extern bool g_audio_stopped;
+
+
+// historical playback status information
+static audio_status_code audio_last_status[8] = {ASC_NO_STATUS};
+
+// volume information for targets
+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
+};
+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
+};
+
+bool audio_is_active() {
+    return audio_owner != 0xFF;
+}
+
+
+bool audio_is_playing(uint8_t id) {
+    return audio_owner == (id & 7);
+}
+
+
+
+static void audio_start_dma()
+{  
+        dma_channel_disable(ODE_DMA, ODE_DMA_CH);
+        dma_interrupt_flag_clear(ODE_DMA, ODE_DMA_CH, DMA_INT_FLAG_HTF);
+        dma_interrupt_flag_clear(ODE_DMA, ODE_DMA_CH, DMA_INT_FLAG_FTF);
+        dma_interrupt_enable(ODE_DMA, ODE_DMA_CH, DMA_INT_HTF | DMA_INT_FTF);
+        dma_transfer_number_config(ODE_DMA, ODE_DMA_CH, AUDIO_BUFFER_SIZE / 2); // convert to 16bit transfer count
+        spi_enable(ODE_I2S_SPI);
+        dma_channel_enable(ODE_DMA, ODE_DMA_CH);
+
+}
+extern "C"
+{
+    void ODE_IRQHandler() 
+    {
+        if ( SET == dma_interrupt_flag_get(ODE_DMA, ODE_DMA_CH, DMA_INT_FLAG_HTF))
+        {
+            dma_interrupt_flag_clear(ODE_DMA, ODE_DMA_CH, DMA_INT_FLAG_HTF);
+            sbufst_a = STALE;
+        }
+        if (SET == dma_interrupt_flag_get(ODE_DMA, ODE_DMA_CH, DMA_INT_FLAG_FTF))
+        {
+            dma_interrupt_flag_clear(ODE_DMA, ODE_DMA_CH, DMA_INT_FLAG_FTF);
+            sbufst_b = STALE;
+        } 
+    }
+}
+
+void audio_setup() 
+{
+    // Setup clocks  
+    rcu_periph_clock_enable(ODE_RCU_I2S_SPI);
+    rcu_periph_clock_enable(ODE_RCU_DMA);
+
+    // Install NVIC
+    nvic_irq_enable(ODE_DMA_IRQn, 3, 0);
+
+    // DMA setup
+    dma_parameter_struct dma_init_struct;
+    dma_struct_para_init(&dma_init_struct);
+
+    dma_deinit(ODE_DMA, ODE_DMA_CH);
+    dma_init_struct.periph_addr  = (uint32_t)&SPI_DATA(ODE_I2S_SPI);
+    dma_init_struct.memory_addr  = (uint32_t)sample_circ_buf;
+    dma_init_struct.direction    = DMA_MEMORY_TO_PERIPHERAL;
+    dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT;
+    dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
+    dma_init_struct.priority     = DMA_PRIORITY_LOW;
+    dma_init_struct.number       = AUDIO_BUFFER_SIZE / 2; // 8 bit to 16 bit conversion length
+    dma_init_struct.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;
+    dma_init_struct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;
+    dma_init(ODE_DMA, ODE_DMA_CH, &dma_init_struct);
+    /* configure DMA mode */
+    dma_circulation_enable(ODE_DMA, ODE_DMA_CH);
+    dma_memory_to_memory_disable(ODE_DMA, ODE_DMA_CH);
+
+    /* configure I2S1 */
+    i2s_disable(ODE_I2S_SPI);
+    spi_i2s_deinit(ODE_I2S_SPI);
+    i2s_init(ODE_I2S_SPI, I2S_MODE_MASTERTX, I2S_STD_PHILLIPS, I2S_CKPL_LOW);
+    i2s_psc_config(ODE_I2S_SPI, I2S_AUDIOSAMPLE_44K, I2S_FRAMEFORMAT_DT16B_CH16B, I2S_MCKOUT_DISABLE);
+    spi_dma_enable(ODE_I2S_SPI, SPI_DMA_TRANSMIT);
+    i2s_enable(ODE_I2S_SPI);
+
+}
+
+/*
+ * Takes in a buffer with interleaved 16bit samples and adjusts their volume
+*/
+static void audio_adjust(uint8_t owner, int16_t* buffer, size_t length)
+{
+    uint8_t volume[2]; 
+    uint16_t packed_volume = volumes[owner & 7];
+    volume[0] = packed_volume >> 8;
+    volume[1] = packed_volume & 0xFF;
+
+    // enable or disable based on the channel information for both output
+    // ports, where the high byte and mask control the right channel, and
+    // the low control the left channel    
+    uint16_t chn = channels[owner & 7] & AUDIO_CHANNEL_ENABLE_MASK;
+    if (!(chn >> 8))
+    {
+        volume[0] = 0;
+    } 
+    if (!(chn & 0xFF))
+    {
+        volume[1] = 0;
+    }
+
+    for (int i = 0; i < length; i++)
+    {
+        // linear volume
+        buffer[i] = (int16_t)(( ((int32_t)buffer[i]) * volume[i & 0x1]) >> 8); 
+    }
+
+}
+
+void audio_poll() 
+{
+    if(!g_audio_enabled)
+        return;
+
+    if ((!g_audio_stopped) && 1 == (0x1 & platform_get_buttons()))
+    {
+        audio_stop(audio_owner);
+    }
+    if (!audio_is_active()) return;
+    if (audio_paused) return;
+    if (fleft == 0 && sbufst_a == STALE && sbufst_b == STALE) {
+        // out of data and ready to stop
+        audio_stop(audio_owner);
+        return;
+    } else if (fleft == 0) {
+        // out of data to read but still working on remainder
+        return;
+    } else if (!audio_file->isOpen()) {
+        // closed elsewhere, maybe disk ejected?
+        dbgmsg("------ Playback stop due to closed file");
+        audio_stop(audio_owner);
+        return;
+    }
+
+    if ( STALE  == sbufst_a || STALE == sbufst_b )
+    {
+        uint8_t* audiobuf;
+        if (sbufst_a == STALE) {
+            sbufst_a = FILLING;
+            audiobuf = sample_circ_buf;
+        } 
+        
+        if (sbufst_b == STALE) {
+            sbufst_b = FILLING;
+            audiobuf = sample_circ_buf + AUDIO_BUFFER_HALF_SIZE;
+        }
+
+        platform_set_sd_callback(NULL, NULL);
+ 
+        uint16_t toRead = AUDIO_BUFFER_HALF_SIZE;
+        if (fleft < toRead) toRead = fleft;
+        if (audio_file->position() != fpos) {
+            // should be uncommon due to SCSI command restrictions on devices
+            // playing audio; if this is showing up in logs a different approach
+            // will be needed to avoid seek performance issues on FAT32 vols
+            dbgmsg("------ Audio seek required on ", audio_owner);
+            if (!audio_file->seek(fpos)) {
+                logmsg("Audio error, unable to seek to ", fpos, ", ID:", audio_owner);
+            }
+        }
+        ssize_t read_length = audio_file->read(audiobuf, AUDIO_BUFFER_HALF_SIZE);
+        
+        if ( read_length < AUDIO_BUFFER_HALF_SIZE )
+        {
+            if ( read_length < 0)
+            {
+                logmsg("Playback file half buffer size read error: ", read_length);    
+                return;
+            }
+            else
+            {
+                // pad buffer with zeros
+                memset(audiobuf + read_length, 0, AUDIO_BUFFER_HALF_SIZE - read_length);
+            }
+        }
+        audio_adjust(audio_owner, (int16_t*) audiobuf, AUDIO_BUFFER_HALF_SIZE / 2);
+    
+        fpos += toRead;
+        fleft -= toRead;
+
+        if (sbufst_a == FILLING) {
+            sbufst_a = READY;
+        } else if (sbufst_b == FILLING) {
+            sbufst_b = READY;
+        }
+    }
+}
+
+bool audio_play(uint8_t owner, ImageBackingStore* img, uint64_t start, uint64_t end, bool swap)
+{
+    if (audio_is_active()) audio_stop(audio_owner);
+
+
+    // verify audio file is present and inputs are (somewhat) sane
+    if (owner == 0xFF) 
+    {
+        logmsg("Illegal audio owner");
+        return false;
+    }
+    if (start >= end) 
+    {
+        logmsg("Invalid range for audio (", start, ":", end, ")");
+        return false;
+    }
+
+    audio_file = img;
+    if (!audio_file->isOpen()) {
+        logmsg("File not open for audio playback, ", owner);
+        return false;
+    }
+    uint64_t len = audio_file->size();
+    if (start > len) 
+    {
+        logmsg("File playback request start (", start, ":", len, ") outside file bounds");
+        return false;
+    }
+    // truncate playback end to end of file
+    // we will not consider this to be an error at the moment
+    if (end > len) 
+    {
+        dbgmsg("------ Truncate audio play request end ", end, " to file size ", len);
+        end = len;
+    }
+    fleft = end - start;
+    if (fleft <= AUDIO_BUFFER_SIZE)
+    {
+        logmsg("File playback request (", start, ":", end, ") too short");
+        return false;
+    }
+
+    // read in initial sample buffers
+    if (!audio_file->seek(start))
+    {
+        logmsg("Sample file failed start seek to ", start);
+        return false;
+    }
+    ssize_t read_length = audio_file->read(sample_circ_buf, AUDIO_BUFFER_SIZE);
+    if ( read_length < AUDIO_BUFFER_SIZE)
+    {
+        if ( read_length < 0)
+        {
+            logmsg("Playback file read error: ", read_length);    
+            return false;
+        }
+        else
+        {
+            // pad buffer with zeros
+            memset(sample_circ_buf + read_length, 0, AUDIO_BUFFER_SIZE - read_length);
+        }
+
+    }
+    audio_adjust(owner, (int16_t*)sample_circ_buf, AUDIO_BUFFER_SIZE / 2);
+ 
+    // prepare initial tracking state
+    fpos = audio_file->position();
+    fleft -= AUDIO_BUFFER_SIZE;
+    sbufswap = swap;
+    sbufst_a = READY;
+    sbufst_b = READY;
+    audio_owner = owner & 7;
+    audio_last_status[audio_owner] = ASC_PLAYING;
+    audio_paused = false;
+    g_audio_stopped = false;
+    audio_start_dma();
+
+    return true;
+}
+
+bool audio_set_paused(uint8_t id, bool paused)
+{
+    if (audio_owner != (id & 7)) return false;
+    else if (audio_paused && paused) return false;
+    else if (!audio_paused && !paused) return false;
+
+
+
+    if (paused) 
+    {
+        // Turn off interrupts to stop flagging new audio samples to load
+        dma_interrupt_disable(ODE_DMA, ODE_DMA_CH, DMA_INT_FTF | DMA_INT_HTF);
+        dma_interrupt_flag_clear(ODE_DMA, ODE_DMA_CH, DMA_INT_FLAG_HTF);
+        dma_interrupt_flag_clear(ODE_DMA, ODE_DMA_CH, DMA_INT_FLAG_FTF);
+        // Set status for both left and right channel to stop audio polling
+        // from loading new samples
+        sbufst_a = READY;
+        sbufst_b = READY;
+        // Let the DMA continue to run but set audio out to 0s
+        memset(sample_circ_buf, 0, AUDIO_BUFFER_SIZE);
+        audio_last_status[audio_owner] = ASC_PAUSED;
+    } 
+    else
+    {
+        // Enable audio polling and DMA interrupts to load and play new samples
+        sbufst_a = STALE;
+        sbufst_b = STALE;
+        dma_interrupt_flag_clear(ODE_DMA, ODE_DMA_CH, DMA_INT_FLAG_HTF);
+        dma_interrupt_flag_clear(ODE_DMA, ODE_DMA_CH, DMA_INT_FLAG_FTF);
+        dma_interrupt_enable(ODE_DMA, ODE_DMA_CH, DMA_INT_FTF | DMA_INT_HTF);
+        audio_last_status[audio_owner] = ASC_PLAYING;
+    }
+    return true;
+}
+
+void audio_stop(uint8_t id)
+{
+    if (audio_owner != (id & 7)) return;
+
+    spi_disable(ODE_I2S_SPI);
+    dma_channel_disable(ODE_DMA, ODE_DMA_CH);
+
+    // idle the subsystem
+    audio_last_status[audio_owner] = ASC_COMPLETED;
+    audio_paused = false;
+    g_audio_stopped = true;
+    audio_owner = 0xFF;
+}
+
+audio_status_code audio_get_status_code(uint8_t id)
+{
+    audio_status_code tmp = audio_last_status[id & 7];
+    if (tmp == ASC_COMPLETED || tmp == ASC_ERRORED) {
+        audio_last_status[id & 7] = ASC_NO_STATUS;
+    }
+    return tmp;
+}
+
+uint16_t audio_get_volume(uint8_t id)
+{
+    return volumes[id & 7];
+}
+
+void audio_set_volume(uint8_t id, uint16_t vol)
+{
+    volumes[id & 7] = vol;
+}
+
+uint16_t audio_get_channel(uint8_t id) {
+    return channels[id & 7];
+}
+
+void audio_set_channel(uint8_t id, uint16_t chn) {
+    channels[id & 7] = chn;
+}
+
+
+#endif // ENABLE_AUDIO_OUTPUT

+ 61 - 0
lib/ZuluSCSI_platform_GD32F205/audio.h

@@ -0,0 +1,61 @@
+/** 
+ * Copyright (C) 2023 saybur
+ * 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
+#ifdef ENABLE_AUDIO_OUTPUT
+
+extern bool g_audio_enabled;
+extern bool g_ode_audio_stopped;
+
+// size of the a circular audio sample buffer, in bytes
+// these must be divisible by 1024
+#define AUDIO_BUFFER_SIZE 16384
+// #define AUDIO_BUFFER_SIZE 8192 // ~46.44ms 
+// # define AUDIO_BUFFER_SIZE 4096 // reduce memory usage
+#define AUDIO_BUFFER_HALF_SIZE AUDIO_BUFFER_SIZE / 2
+
+/**
+ * Handler for I2S DMA interrupts
+ */
+void ODE_IRQHandler();
+
+
+/**
+ * Indicates if the audio subsystem is actively streaming, including if it is
+ * sending silent data during sample stall events.
+ *
+ * \return true if audio streaming is active, false otherwise.
+ */
+bool audio_is_active();
+
+/**
+ * Initializes the audio subsystem. Should be called only once, toward the end
+ * of platform_late_init().
+ */
+void audio_setup();
+
+/**
+ * Called from platform_poll() to fill sample buffer(s) if needed.
+ */
+void audio_poll();
+#endif //ENABLE_AUDIO_OUTPUT

+ 519 - 0
lib/ZuluSCSI_platform_GD32F205/gd32_cdc_acm_core.c

@@ -0,0 +1,519 @@
+/*!
+    \file    cdc_acm_core.c
+    \brief   CDC ACM driver
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+    \version 2020-12-10, V3.0.1, firmware for GD32F20x
+    \version 2020-12-14, V3.0.2, firmware for GD32F20x
+*/
+
+/*
+    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"
+#include "usbd_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 */
+const usb_desc_dev gd32_cdc_dev_desc =
+{
+    .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 */
+const usb_cdc_desc_config_set gd32_cdc_config_desc = 
+{
+    .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 const usb_desc_LANGID usbd_language_id_desc = 
+{
+    .header = 
+     {
+         .bLength         = sizeof(usb_desc_LANGID), 
+         .bDescriptorType = USB_DESCTYPE_STR,
+     },
+    .wLANGID              = ENG_LANGID
+};
+
+/* USB manufacture string */
+static const usb_desc_str manufacturer_string = 
+{
+    .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 const usb_desc_str product_string = 
+{
+    .header = 
+     {
+         .bLength         = USB_STRING_LEN(8), 
+         .bDescriptorType = USB_DESCTYPE_STR,
+     },
+    .unicode_string = {'Z', 'u', 'l', 'u', 'S', 'C', 'S', 'I'}
+};
+
+/* USBD serial string */
+static usb_desc_str serial_string = 
+{
+    .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_acm_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_acm_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 = 0U;
+        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 gd32_usb_cdc_handler cdc_handler;
+
+    /* 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      deinitialize 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:
+        /* 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 = &udev->dev.transc_in[0];
+        transc->remain_len = req->wLength;
+        transc->xfer_buf = cdc->cmd;
+        break;
+
+    case GET_LINE_CODING:
+        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 = &udev->dev.transc_out[0];
+        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_acm_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_GD32F205/gd32_cdc_acm_core.h

@@ -0,0 +1,66 @@
+/*!
+    \file    cdc_acm_core.h
+    \brief   the header file of CDC ACM driver
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+*/
+
+/*
+    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 __CDC_ACM_CORE_H
+#define __CDC_ACM_CORE_H
+
+#include "usbd_enum.h"
+#include "usb_cdc.h"
+
+#define USB_CDC_RX_LEN      64
+
+typedef struct {
+    uint8_t packet_sent;
+    uint8_t packet_receive;
+
+    uint8_t data[USB_CDC_RX_LEN];
+    uint8_t cmd[USB_CDC_CMD_PACKET_SIZE];
+
+    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 /* __CDC_ACM_CORE_H */

+ 98 - 0
lib/ZuluSCSI_platform_GD32F205/platform_hw_config.cpp

@@ -0,0 +1,98 @@
+/** 
+ * 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 "ZuluSCSI_platform.h"
+
+#ifdef ZULUSCSI_HARDWARE_CONFIG
+#include "platform_hw_config.h"
+#include <ZuluSCSI_config.h>
+#include <ZuluSCSI_log.h>
+
+HardwareConfig g_hw_config;
+
+S2S_CFG_TYPE hw_config_selected_device()
+{
+    return g_hw_config.device_type();
+};
+
+bool hw_config_is_active()
+{
+    return g_hw_config.is_active();
+}
+
+void hw_config_init_gpios()
+{
+    g_hw_config.init_gpios();
+}
+
+void hw_config_init_state(bool is_active)
+{
+    g_hw_config.init_state(is_active);
+}
+
+void HardwareConfig::init_gpios()
+{
+    // SCSI ID dip switch
+    gpio_init(DIPSW_SCSI_ID_BIT_PORT, GPIO_MODE_IPD, 0, DIPSW_SCSI_ID_BIT_PINS);
+    
+    // Device select BCD rotary dip switch
+    gpio_init(DIPROT_DEVICE_SEL_BIT_PORT, GPIO_MODE_IPD, 0, DIPROT_DEVICE_SEL_BIT_PINS);
+
+    LED_EJECT_OFF();
+    gpio_init(LED_EJECT_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, LED_EJECT_PIN);
+}
+
+void HardwareConfig::init_state(bool is_active)
+{
+    m_is_active = is_active;
+    m_scsi_id = (gpio_input_port_get(DIPSW_SCSI_ID_BIT_PORT) & DIPSW_SCSI_ID_BIT_PINS) >> DIPSW_SCSI_ID_BIT_SHIFT;
+    logmsg("SCSI ID set via DIP switch to ", m_scsi_id);
+    
+    uint8_t rotary_select = (gpio_input_port_get(DIPROT_DEVICE_SEL_BIT_PORT) & DIPROT_DEVICE_SEL_BIT_PINS) >> DIPROT_DEVICE_SEL_BIT_SHIFT;
+    switch (rotary_select)
+    {
+    case 0:
+        m_device_type = S2S_CFG_FIXED;
+    break;
+    case 1:
+        m_device_type = S2S_CFG_SEQUENTIAL;
+    break;
+    case 2:
+        m_device_type = S2S_CFG_OPTICAL;
+    break;
+    case 3:
+        m_device_type = S2S_CFG_REMOVABLE;
+    break;
+
+    default:
+        m_device_type = S2S_CFG_FIXED;
+    }
+    
+    if (m_device_type == S2S_CFG_OPTICAL)
+    {
+        m_blocksize = DEFAULT_BLOCKSIZE_OPTICAL;
+    }
+    else
+    {
+        m_blocksize = RAW_FALLBACK_BLOCKSIZE;
+    }
+}
+
+#endif // ZULUSCSI_HARDWARE_CONFIG

+ 73 - 0
lib/ZuluSCSI_platform_GD32F205/platform_hw_config.h

@@ -0,0 +1,73 @@
+/** 
+ * 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/>.
+**/
+
+/**
+ * Configuration of the ZuluSCSI set by hardware rotary and standard DIP switches
+ * Settings include SCSI ID, device type, and if hardware config is active 
+*/
+
+#pragma once
+#include <scsi2sd.h>
+
+// C wrappers
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+    S2S_CFG_TYPE hw_config_selected_device();
+    bool hw_config_is_active();
+    void hw_config_init_gpios();
+    void hw_config_init_state(bool is_active);
+    
+#ifdef __cplusplus
+}
+#endif
+
+#ifdef __cplusplus
+class HardwareConfig
+{
+public:
+    // Initialize GPIOs
+    void init_gpios();
+
+    // Initialize device state settings
+    void init_state(bool is_active);
+
+    // get the device type
+    // @returns the device type
+    const S2S_CFG_TYPE& device_type() const {return m_device_type;}
+    const uint8_t& scsi_id()    const {return m_scsi_id;}
+    const bool& is_active()     const {return m_is_active;}
+    const int blocksize()       const {return m_blocksize;}
+
+protected:
+    S2S_CFG_TYPE m_device_type;
+    uint8_t m_scsi_id;
+    bool m_is_active;
+    int m_blocksize;
+};
+
+
+// global hardware configuration
+extern HardwareConfig g_hw_config;
+
+#endif // __cplusplus
+

+ 6 - 1
lib/ZuluSCSI_platform_GD32F205/scsiPhy.cpp

@@ -85,7 +85,7 @@ volatile uint8_t g_scsi_ctrl_bsy;
 
 static void scsi_bsy_deassert_interrupt()
 {
-    if (SCSI_IN(SEL) && !SCSI_IN(BSY))
+    if (!SCSI_IN(BSY) && (((g_zuluscsi_version == ZSVersion_v1_1_ODE || g_zuluscsi_version == ZSVersion_v1_2) && SCSI_IN(ODE_SEL)) || SCSI_IN(SEL)) )
     {
         uint8_t sel_bits = SCSI_IN_DATA();
         int sel_id = -1;
@@ -125,7 +125,12 @@ extern "C" bool scsiStatusSEL()
         SCSI_OUT(BSY, 1);
     }
 
+    if (g_zuluscsi_version == ZSVersion_v1_1_ODE || g_zuluscsi_version == ZSVersion_v1_2)
+    {
+        return SCSI_IN(ODE_SEL);
+    }
     return SCSI_IN(SEL);
+     
 }
 
 /************************/

+ 126 - 0
lib/ZuluSCSI_platform_GD32F205/usb_conf.h

@@ -0,0 +1,126 @@
+/*!
+    \file    usb_conf.h
+    \brief   USB core driver basic configuration
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+*/
+
+/*
+    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 __USB_CONF_H
+#define __USB_CONF_H
+
+#include "stdlib.h"
+#include "gd32f20x.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.
+*******************************************************************************/
+
+#ifdef USE_USB_FS
+    #define USB_FS_CORE
+#endif
+
+/*******************************************************************************
+ *                      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.
+*******************************************************************************/
+
+#ifdef USB_FS_CORE
+    #define RX_FIFO_FS_SIZE                         128U
+    #define USB_RX_FIFO_FS_SIZE                     RX_FIFO_FS_SIZE
+    #define TX0_FIFO_FS_SIZE                        64U
+    #define TX1_FIFO_FS_SIZE                        128U
+    #define TX2_FIFO_FS_SIZE                        0U
+    #define TX3_FIFO_FS_SIZE                        0U
+#endif /* USB_FS_CORE */
+
+#define USB_SOF_OUTPUT              1U
+#define USB_LOW_POWER               0U
+
+/* if uncomment it, need jump to USB JP */
+//#define VBUS_SENSING_ENABLED
+
+//#define USE_HOST_MODE
+#define USE_DEVICE_MODE
+//#define USE_OTG_MODE
+
+#ifndef USE_DEVICE_MODE
+    #ifndef USE_HOST_MODE
+        #error  "USE_DEVICE_MODE or USE_HOST_MODE should be defined!"
+    #endif
+#endif
+
+#define __ALIGN_BEGIN
+#define __ALIGN_END
+
+/* __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 */

+ 80 - 0
lib/ZuluSCSI_platform_GD32F205/usb_serial.cpp

@@ -0,0 +1,80 @@
+/** 
+ * 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 "ZuluSCSI_platform.h"
+#include "usb_serial.h"
+
+extern "C" 
+{
+#include <gd32_cdc_acm_core.h>
+#include <drv_usb_hw.h>
+#include <usbd_core.h>
+#include <drv_usbd_int.h>
+usb_core_driver cdc_acm;
+}
+
+
+void usb_serial_init(void)
+{
+    // set USB full speed prescaler and turn on USB clock
+    rcu_usbfs_trng_clock_config(RCU_CKUSB_CKPLL_DIV2_5);
+    rcu_periph_clock_enable(RCU_USBFS);
+    usbd_init(&cdc_acm, USB_CORE_ENUM_FS, &gd32_cdc_desc, &gd32_cdc_class);
+
+    // USB full speed Interrupt config
+    nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
+    nvic_irq_enable((uint8_t)USBFS_IRQn, 2U, 0U);
+}
+
+
+bool usb_serial_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_serial_send(uint8_t *data, uint32_t length)
+{
+    gd32_cdc_acm_data_send(&cdc_acm, data, length);
+}
+
+void USBFS_IRQHandler(void)
+{
+    usbd_isr(&cdc_acm);
+}
+
+void usb_udelay (const uint32_t usec)
+{
+    delay_us(usec);
+}
+
+
+void usb_mdelay (const uint32_t msec)
+{
+    delay(msec);
+}

+ 35 - 0
lib/ZuluSCSI_platform_GD32F205/usb_serial.h

@@ -0,0 +1,35 @@
+/** 
+ * 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
+
+// init USB full speed interface
+void usb_serial_init(void);
+// check if the USB serial interface is ready to send data
+bool usb_serial_ready(void);
+// Send data of the USB serial interface
+void usb_serial_send(uint8_t *data, uint32_t length);
+
+extern "C"
+{
+    // The USB full speed IRQ handler
+    void USBFS_IRQHandler(void);
+}

+ 62 - 0
lib/ZuluSCSI_platform_GD32F205/usbd_conf.h

@@ -0,0 +1,62 @@
+/*!
+    \file    usbd_conf.h
+    \brief   the header file of USB device configuration
+
+    \version 2020-07-28, V3.0.0, firmware for GD32F20x
+*/
+
+/*
+    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 __USBD_CONF_H
+#define __USBD_CONF_H
+
+#include "usb_conf.h"
+
+#define USBD_CFG_MAX_NUM                    1U
+#define USBD_ITF_MAX_NUM                    1U
+
+#define CDC_COM_INTERFACE                   0U
+
+#define USB_STR_DESC_MAX_SIZE               255U
+
+#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                    4U
+
+#define USB_CDC_CMD_PACKET_SIZE             8U    /* Control Endpoint Packet size */
+
+#define APP_RX_DATA_SIZE                    2048U /* 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. */
+#define USB_CDC_DATA_PACKET_SIZE            64U   /* Endpoint IN & OUT Packet size */
+#define CDC_IN_FRAME_INTERVAL               5U   /* Number of frames between IN transfers */
+
+#endif /* __USBD_CONF_H */

+ 2 - 0
lib/ZuluSCSI_platform_GD32F205/zuluscsi_gd32f205_btldr.ld

@@ -54,12 +54,14 @@ SECTIONS
     *(*DMA1_Channel1_IRQHandler*)
     *(*EXTI3_IRQHandler*)
     *(*EXTI10_15_IRQHandler*)
+    *(*USBFS_IRQHandler*)
   }
 
   DMA1_Channel1_IRQHandler = 0;
   DMA1_Channel4_IRQHandler = 0;
   EXTI10_15_IRQHandler = 0;
   EXTI3_IRQHandler = 0;
+  USBFS_IRQHandler = 0;
 
   /* The startup code goes first into FLASH */
   .isr_vector :

+ 12 - 5
platformio.ini

@@ -1,7 +1,7 @@
 ; PlatformIO Project Configuration File https://docs.platformio.org/page/projectconf.html
 
 [platformio]
-default_envs = ZuluSCSIv1_0, ZuluSCSIv1_0_mini, ZuluSCSIv1_1, ZuluSCSI_RP2040, ZuluSCSI_RP2040_Audio, ZuluSCSI_Pico, ZuluSCSI_BS2
+default_envs = ZuluSCSIv1_0, ZuluSCSIv1_0_mini, ZuluSCSIv1_1_plus, ZuluSCSI_RP2040, ZuluSCSI_RP2040_Audio, ZuluSCSI_Pico, ZuluSCSI_Pico_DaynaPORT, ZuluSCSI_BS2
 
 ; Example platform to serve as a base for porting efforts
 [env:template]
@@ -39,17 +39,21 @@ lib_deps =
     ZuluSCSI_platform_GD32F205
     SCSI2SD
     CUEParser
+    GD32F20x_usbfs_library
 upload_protocol = stlink
 platform_packages = platformio/toolchain-gccarmnoneeabi@1.100301.220327
     framework-spl-gd32@https://github.com/CommunityGD32Cores/gd32-pio-spl-package.git
+debug_tool = cmsis-dap
 extra_scripts = src/build_bootloader.py
-debug_build_flags = -Os -ggdb -g3
+debug_build_flags = 
+     -Os -Wall -Wno-sign-compare -ggdb -g3
 build_flags = 
      -Os -Wall -Wno-sign-compare -ggdb -g3 -Isrc
      -D__SYSTEM_CLOCK_120M_PLL_IRC8M=120000000
      -DSPI_DRIVER_SELECT=3
      -DSD_CHIP_SELECT_MODE=2
      -DENABLE_DEDICATED_SPI=1
+     -DPIO_USBFS_DEVICE_CDC
      -DZULUSCSI_V1_0
 
 ; ZuluSCSI V1.0 mini hardware platform with GD32F205 CPU.
@@ -61,11 +65,12 @@ build_flags =
      -DSPI_DRIVER_SELECT=3
      -DSD_CHIP_SELECT_MODE=2
      -DENABLE_DEDICATED_SPI=1
+     -DPIO_USBFS_DEVICE_CDC
      -DZULUSCSI_V1_0
      -DZULUSCSI_V1_0_mini
 
-; ZuluSCSI V1.1 hardware platform, similar to V1.0 but with improved performance.
-[env:ZuluSCSIv1_1]
+; ZuluSCSI V1.1+ hardware platforms, this support v1.1, v1.1 ODE, and vl.2
+[env:ZuluSCSIv1_1_plus]
 extends = env:ZuluSCSIv1_0
 build_flags = 
      -Os -Wall -Wno-sign-compare -ggdb -g3 -Isrc
@@ -73,8 +78,10 @@ build_flags =
      -DSPI_DRIVER_SELECT=3
      -DSD_CHIP_SELECT_MODE=2
      -DENABLE_DEDICATED_SPI=1
+     -DPIO_USBFS_DEVICE_CDC
      -DHAS_SDIO_CLASS
-     -DZULUSCSI_V1_1
+     -DENABLE_AUDIO_OUTPUT
+     -DZULUSCSI_V1_1_plus
 
 ; ZuluSCSI RP2040 hardware platform, based on the Raspberry Pi foundation RP2040 microcontroller
 [env:ZuluSCSI_RP2040]

+ 71 - 23
src/ZuluSCSI.cpp

@@ -68,17 +68,22 @@ static bool g_sdcard_present;
 /************************************/
 
 #define BLINK_STATUS_OK 1
-#define BLINK_ERROR_NO_IMAGES  3 
+#define BLINK_ERROR_NO_IMAGES  3
+#define BLINK_DIRECT_MODE      4
 #define BLINK_ERROR_NO_SD_CARD 5
 
 void blinkStatus(int count)
 {
+  uint8_t blink_delay = 250;
+  if (count == BLINK_DIRECT_MODE)
+    blink_delay = 100;
+
   for (int i = 0; i < count; i++)
   {
     LED_ON();
-    delay(250);
+    delay(blink_delay);
     LED_OFF();
-    delay(250);
+    delay(blink_delay);
   }
 }
 
@@ -98,6 +103,12 @@ extern "C" void s2s_ledOff()
 
 void save_logfile(bool always = false)
 {
+#ifdef ZULUSCSI_HARDWARE_CONFIG
+  // Disable logging to the SD card when in direct mode
+  if (g_hw_config.is_active())
+    return;
+#endif
+
   static uint32_t prev_log_pos = 0;
   static uint32_t prev_log_len = 0;
   static uint32_t prev_log_save = 0;
@@ -120,6 +131,12 @@ void save_logfile(bool always = false)
 
 void init_logfile()
 {
+#ifdef ZULUSCSI_HARDWARE_CONFIG
+  // Disable logging to the SD card when in direct mode
+  if (g_hw_config.is_active())
+    return;
+#endif
+
   static bool first_open_after_boot = true;
 
   bool truncate = first_open_after_boot;
@@ -294,6 +311,12 @@ bool createImage(const char *cmd_filename, char imgname[MAX_FILE_PATH + 1])
 // Iterate over the root path in the SD card looking for candidate image files.
 bool findHDDImages()
 {
+#ifdef ZULUSCSI_HARDWARE_CONFIG
+  if (g_hw_config.is_active())
+  {
+    return false;
+  }
+#endif // ZULUSCSI_HARDWARE_CONFIG
   char imgdir[MAX_FILE_PATH];
   ini_gets("SCSI", "Dir", "/", imgdir, sizeof(imgdir), CONFIGFILE);
   int dirindex = 0;
@@ -404,7 +427,7 @@ bool findHDDImages()
         if (is_cd)
         {
           // Use 2048 as the default sector size for CD-ROMs
-          blk = 2048;
+          blk = DEFAULT_BLOCKSIZE_OPTICAL;
         }
 
         // Parse SCSI device ID
@@ -471,7 +494,7 @@ bool findHDDImages()
 #ifdef ZULUSCSI_NETWORK
         if (is_ne) type = S2S_CFG_NETWORK;
 #endif // ZULUSCSI_NETWORK
-        if (is_re) type = S2S_CFG_REMOVEABLE;
+        if (is_re) type = S2S_CFG_REMOVABLE;
         if (is_tp) type = S2S_CFG_SEQUENTIAL;
 
         // Open the image file
@@ -588,10 +611,17 @@ static bool mountSDCard()
 
 static void reinitSCSI()
 {
+#if defined(ZULUSCSI_HARDWARE_CONFIG)
+  if (!g_hw_config.is_active() && ini_getbool("SCSI", "Debug", 0, CONFIGFILE))
+  {
+    g_log_debug = true;
+  }
+#else
   if (ini_getbool("SCSI", "Debug", 0, CONFIGFILE))
   {
     g_log_debug = true;
   }
+#endif
 
 #ifdef PLATFORM_HAS_INITIATOR_MODE
   if (platform_is_initiator_mode_enabled())
@@ -609,26 +639,44 @@ static void reinitSCSI()
 #endif
 
   scsiDiskResetImages();
-  readSCSIDeviceConfig();
-  findHDDImages();
-
-  // Error if there are 0 image files
-  if (scsiDiskCheckAnyImagesConfigured())
-  {
-    // Ok, there is an image, turn LED on for the time it takes to perform init
-    LED_ON();
-    delay(100);
+#if defined(ZULUSCSI_HARDWARE_CONFIG)
+  if (g_hw_config.is_active())
+  {
+    bool success;
+    logmsg("Direct/Raw mode enabled, using hardware switches for configuration");
+    success = scsiDiskOpenHDDImage(g_hw_config.scsi_id(), "RAW:0:0xFFFFFFFF", g_hw_config.scsi_id(), 0,
+                                   g_hw_config.blocksize(), g_hw_config.device_type());
+    if (success)
+    {
+      blinkStatus(BLINK_STATUS_OK);
+    }
+    delay(250);
+    blinkStatus(BLINK_DIRECT_MODE);
   }
   else
-  {
-#if RAW_FALLBACK_ENABLE
-    logmsg("No images found, enabling RAW fallback partition");
-    scsiDiskOpenHDDImage(RAW_FALLBACK_SCSI_ID, "RAW:0:0xFFFFFFFF", RAW_FALLBACK_SCSI_ID, 0,
-                         RAW_FALLBACK_BLOCKSIZE);
-#else
-    logmsg("No valid image files found!");
-#endif
-    blinkStatus(BLINK_ERROR_NO_IMAGES);
+#endif // ZULUSCSI_HARDWARE_CONFIG
+  { 
+    readSCSIDeviceConfig();
+    findHDDImages();
+
+    // Error if there are 0 image files
+    if (scsiDiskCheckAnyImagesConfigured())
+    {
+      // Ok, there is an image, turn LED on for the time it takes to perform init
+      LED_ON();
+      delay(100);
+    }
+    else
+    {
+  #ifdef RAW_FALLBACK_ENABLE
+      logmsg("No images found, enabling RAW fallback partition");
+      scsiDiskOpenHDDImage(RAW_FALLBACK_SCSI_ID, "RAW:0:0xFFFFFFFF", RAW_FALLBACK_SCSI_ID, 0,
+                          RAW_FALLBACK_BLOCKSIZE);
+  #else
+      logmsg("No valid image files found!");
+  #endif // RAW_FALLBACK_ENABLE
+      blinkStatus(BLINK_ERROR_NO_IMAGES);
+    }
   }
 
   scsiPhyReset();

+ 9 - 2
src/ZuluSCSI_config.h

@@ -28,8 +28,8 @@
 #include <ZuluSCSI_platform.h>
 
 // Use variables for version number
-#define FW_VER_NUM      "23.10.05"
-#define FW_VER_SUFFIX   "wifi-dev"
+#define FW_VER_NUM      "23.10.19"
+#define FW_VER_SUFFIX   "dev"
 #define ZULU_FW_VERSION FW_VER_NUM "-" FW_VER_SUFFIX
 
 // Configuration and log file paths
@@ -83,6 +83,9 @@
 #define DRIVEINFO_NETWORK   {"Dayna",    "SCSI/Link",       "2.0f", ""}
 #define DRIVEINFO_TAPE      {"ZULUSCSI", "TAPE",      PLATFORM_REVISION, ""}
 
+// Default optical drive blocksize
+#define DEFAULT_BLOCKSIZE_OPTICAL 2048
+
 // Default SCSI drive information when Apple quirks are enabled
 #define APPLE_DRIVEINFO_FIXED     {"CDC",      "ZuluSCSI HDD",      PLATFORM_REVISION, "1.0"}
 #define APPLE_DRIVEINFO_REMOVABLE {"IOMEGA",   "BETA230",           PLATFORM_REVISION, "2.02"}
@@ -101,3 +104,7 @@
 #ifndef PREFETCH_BUFFER_SIZE
 #define PREFETCH_BUFFER_SIZE 8192
 #endif
+
+// Masks for buttons
+#define EJECT_BTN_MASK (1|2)
+#define USER_BTN_MASK  (4)

+ 31 - 26
src/ZuluSCSI_disk.cpp

@@ -295,7 +295,7 @@ static void setDefaultDriveInfo(int target_idx)
         switch (img.deviceType)
         {
             case S2S_CFG_FIXED:         driveinfo = apl_driveinfo_fixed; break;
-            case S2S_CFG_REMOVEABLE:    driveinfo = apl_driveinfo_removable; break;
+            case S2S_CFG_REMOVABLE:    driveinfo = apl_driveinfo_removable; break;
             case S2S_CFG_OPTICAL:       driveinfo = apl_driveinfo_optical; break;
             case S2S_CFG_FLOPPY_14MB:   driveinfo = apl_driveinfo_floppy; break;
             case S2S_CFG_MO:            driveinfo = apl_driveinfo_magopt; break;
@@ -310,7 +310,7 @@ static void setDefaultDriveInfo(int target_idx)
         switch (img.deviceType)
         {
             case S2S_CFG_FIXED:         driveinfo = driveinfo_fixed; break;
-            case S2S_CFG_REMOVEABLE:    driveinfo = driveinfo_removable; break;
+            case S2S_CFG_REMOVABLE:    driveinfo = driveinfo_removable; break;
             case S2S_CFG_OPTICAL:       driveinfo = driveinfo_optical; break;
             case S2S_CFG_FLOPPY_14MB:   driveinfo = driveinfo_floppy; break;
             case S2S_CFG_MO:            driveinfo = driveinfo_magopt; break;
@@ -397,33 +397,38 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int
                 return false;
             }
 
-            uint32_t sector_begin = 0, sector_end = 0;
-            if (img.file.isRom())
-            {
-                // ROM is always contiguous, no need to log
-            }
-            else if (img.file.contiguousRange(&sector_begin, &sector_end))
-            {
-                dbgmsg("---- Image file is contiguous, SD card sectors ", (int)sector_begin, " to ", (int)sector_end);
-            }
-            else
-            {
-                logmsg("---- WARNING: file ", filename, " is not contiguous. This will increase read latency.");
-            }
+        uint32_t sector_begin = 0, sector_end = 0;
+        if (img.file.isRom())
+        {
+            // ROM is always contiguous, no need to log
+        }
+        else if (img.file.contiguousRange(&sector_begin, &sector_end))
+        {
+            dbgmsg("---- Image file is contiguous, SD card sectors ", (int)sector_begin, " to ", (int)sector_end);
+        }
+        else
+        {
+            logmsg("---- WARNING: file ", filename, " is not contiguous. This will increase read latency.");
+        }
+
+        if (type == S2S_CFG_FIXED)
+        {
+            logmsg("---- Configuring as disk drive drive");
+            img.deviceType = S2S_CFG_FIXED;
         }
-        if (type == S2S_CFG_OPTICAL)
+        else if (type == S2S_CFG_OPTICAL)
         {
-            logmsg("---- Configuring as CD-ROM drive based on image name");
+            logmsg("---- Configuring as CD-ROM drive");
             img.deviceType = S2S_CFG_OPTICAL;
         }
         else if (type == S2S_CFG_FLOPPY_14MB)
         {
-            logmsg("---- Configuring as floppy drive based on image name");
+            logmsg("---- Configuring as floppy drive");
             img.deviceType = S2S_CFG_FLOPPY_14MB;
         }
         else if (type == S2S_CFG_MO)
         {
-            logmsg("---- Configuring as magneto-optical based on image name");
+            logmsg("---- Configuring as magneto-optical");
             img.deviceType = S2S_CFG_MO;
         }
 #ifdef ZULUSCSI_NETWORK
@@ -433,14 +438,14 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int
             img.deviceType = S2S_CFG_NETWORK;
         }
 #endif // ZULUSCSI_NETWORK
-        else if (type == S2S_CFG_REMOVEABLE)
+        else if (type == S2S_CFG_REMOVABLE)
         {
-            logmsg("---- Configuring as removable drive based on image name");
-            img.deviceType = S2S_CFG_REMOVEABLE;
+            logmsg("---- Configuring as removable drive");
+            img.deviceType = S2S_CFG_REMOVABLE;
         }
         else if (type == S2S_CFG_SEQUENTIAL)
         {
-            logmsg("---- Configuring as tape drive based on image name");
+            logmsg("---- Configuring as tape drive");
             img.deviceType = S2S_CFG_SEQUENTIAL;
         }
 
@@ -658,7 +663,7 @@ static void scsiDiskLoadConfig(int target_idx, const char *section)
 
             strcpy(tmp, "RE0");
             tmp[2] += target_idx;
-            scsiDiskCheckDir(tmp, target_idx, &img, S2S_CFG_REMOVEABLE, "removable");
+            scsiDiskCheckDir(tmp, target_idx, &img, S2S_CFG_REMOVABLE, "removable");
 
             strcpy(tmp, "MO0");
             tmp[2] += target_idx;
@@ -789,7 +794,7 @@ int scsiDiskGetNextImageName(image_config_t &img, char *buf, size_t buflen)
                 case S2S_CFG_OPTICAL:
                     strcpy(dirname, "CD0");
                 break;
-                case S2S_CFG_REMOVEABLE:
+                case S2S_CFG_REMOVABLE:
                     strcpy(dirname, "RE0");
                 break;
                 case S2S_CFG_MO:
@@ -940,7 +945,7 @@ uint8_t diskEjectButtonUpdate(bool immediate)
 {
     // treat '1' to '0' transitions as eject actions
     static uint8_t previous = 0x00;
-    uint8_t bitmask = platform_get_buttons();
+    uint8_t bitmask = platform_get_buttons() & EJECT_BTN_MASK;
     uint8_t ejectors = (previous ^ bitmask) & previous;
     previous = bitmask;