Explorar o código

Unify ZuluSCSI v1.1, v1.1 ODE, v1.2

The three different board should be able to run the same firmware.
Regression testing is still needed for v1.1, v1.0, v1.0 mini.
Morio %!s(int64=2) %!d(string=hai) anos
pai
achega
ea7d6e5a30

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

@@ -24,7 +24,7 @@ jobs:
       - name: Build firmware
         run: |
           cd ZuluSCSI
-          pio run -ve ZuluSCSIv1_2
+          pio run -ve ZuluSCSIv1_1
     
       - name: Rename firmware files
         run: |

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

@@ -69,7 +69,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

@@ -198,7 +198,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;
 		default:
@@ -260,7 +260,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 =

+ 194 - 51
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.cpp

@@ -30,17 +30,22 @@
 #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
-#ifdef ZULUSCSI_HARDWARE_CONFIG
 #include "platform_hw_config.h"
-#endif
+
+
 
 /*************************/
 /* Timing functions      */
@@ -135,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()
@@ -187,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);
@@ -198,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);
 
@@ -219,76 +269,161 @@ 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
-#ifdef DIPSW1_PIN
     gpio_init(DIP_PORT, GPIO_MODE_IPD, 0, DIPSW1_PIN | DIPSW2_PIN | DIPSW3_PIN);
-#else
-    // Some boards do not have an Apple quirks dip switch
-    gpio_init(DIP_PORT, GPIO_MODE_IPD, 0, DIPSW2_PIN | DIPSW3_PIN);
-#endif
+    // 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
-#ifdef ZULUSCSI_HARDWARE_CONFIG
-    gpio_init(EJECT_BTN_PORT, GPIO_MODE_IPU, 0, EJECT_BTN_PIN);
-    gpio_init(USER_BTN_PORT,  GPIO_MODE_IPU, 0, USER_BTN_PIN);
-    hw_config_init_gpios();
-#else
-    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
+
     // 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)
 {
-    // Initialize usb for CDC serial output
-    usb_serial_init();
-
-    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;
     }
-#ifdef DIPSW1_PIN
-    if (gpio_input_bit_get(DIP_PORT, DIPSW1_PIN))
+    return false;
+}
+
+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;
     }
-#endif
+    logmsg(switch_name, " is ON: Disabling direct/raw mode");
+    return false;
+}
 
-#ifdef ZULUSCSI_HARDWARE_CONFIG
-    hw_config_init_state();
-#else
-    greenpak_load_firmware();
-#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);
 
+#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)
@@ -377,9 +512,6 @@ static void usb_log_poll()
 /*****************************************/
 /* Crash handlers                        */
 /*****************************************/
-
-extern SdFs SD;
-
 // Writes log data to the PB3 SWO pin
 void platform_log(const char *s)
 {
@@ -560,6 +692,9 @@ void platform_reset_watchdog()
 // 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();
 }
@@ -570,8 +705,16 @@ uint8_t platform_get_buttons()
     // and when button is pressed the pin goes low.
     uint8_t buttons = 0;
 #ifdef ZULUSCSI_HARDWARE_CONFIG
-    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;
+    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;

+ 18 - 13
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.h

@@ -43,26 +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"
-#   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
-#   include "ZuluSCSI_v1_1_gpio.h"
-#elif defined(ZULUSCSI_V1_2)
-#   define PLATFORM_NAME "ZuluSCSI v1.2"
-#   define PLATFORM_REVISION "1.2"
+#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 "platform_hw_config.h"
-#   include "ZuluSCSI_v1_2_gpio.h"
+#   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

+ 108 - 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,94 @@
 #define SD_SDIO_CMD_PORT  GPIOD
 #define SD_SDIO_CMD       GPIO_PIN_2
 
+// 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
+#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 +281,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 +293,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

+ 0 - 259
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_v1_2_gpio.h

@@ -1,259 +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/>.
-**/
-
-// GPIO definitions for ZuluSCSI v1.2
-
-#pragma once
-
-// 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 (unused for v1.2 - kept for compilation)
-#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
-#define SCSI_TIMER TIMER7
-#define SCSI_TIMER_RCU RCU_TIMER7
-#define SCSI_TIMER_OUT_PORT GPIOB
-#define SCSI_TIMER_OUT_PIN GPIO_PIN_1
-#define SCSI_TIMER_IN_PORT GPIOC
-#define SCSI_TIMER_IN_PIN GPIO_PIN_6
-#define SCSI_TIMER_DMA DMA1
-#define SCSI_TIMER_DMA_RCU RCU_DMA1
-#define SCSI_TIMER_DMACHA DMA_CH4
-#define SCSI_TIMER_DMACHB DMA_CH1
-#define SCSI_TIMER_DMACHA_IRQ DMA1_Channel4_IRQHandler
-#define SCSI_TIMER_DMACHA_IRQn DMA1_Channel4_IRQn
-#define SCSI_TIMER_DMACHB_IRQ DMA1_Channel1_IRQHandler
-#define SCSI_TIMER_DMACHB_IRQn DMA1_Channel1_IRQn
-
-// GreenPAK logic chip pins (unused for v1.2 - kept for compilation)
-#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_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 GPIO_PORT_SOURCE_GPIOD
-#define GREENPAK_PLD_IO2_EXTI_SOURCE_PIN  GPIO_PIN_SOURCE_3
-#define GREENPAK_IRQ  EXTI3_IRQHandler
-#define GREENPAK_IRQn EXTI3_IRQn
-
-// I2C for v1.2
-#define I2C_PORT GPIOB
-#define I2C_SCL GPIO_PIN_6
-#define I2C_SDA GPIO_PIN_7
-
-// 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  GPIOA
-#define SCSI_OUT_CD_PIN   GPIO_PIN_5
-#define SCSI_OUT_SEL_PORT GPIOA
-#define SCSI_OUT_SEL_PIN  GPIO_PIN_6
-#define SCSI_OUT_MSG_PORT GPIOA
-#define SCSI_OUT_MSG_PIN  GPIO_PIN_7
-#define SCSI_OUT_RST_PORT GPIOB
-#define SCSI_OUT_RST_PIN  GPIO_PIN_14
-#define SCSI_OUT_BSY_PORT GPIOB
-#define SCSI_OUT_BSY_PIN  GPIO_PIN_15
-#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 GPIOB
-#define SCSI_ATN_PIN  GPIO_PIN_12
-#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
-#define SCSI_EXMC_DMA DMA0
-#define SCSI_EXMC_DMA_RCU RCU_DMA0
-#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 GPIO_PORT_SOURCE_GPIOB
-// #define SCSI_SEL_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_11
-// #define SCSI_SEL_IRQ EXTI10_15_IRQHandler
-// #define SCSI_SEL_IRQn EXTI10_15_IRQn
-
-// SEL pin for 1.2
-#define SCSI_SEL_PORT GPIOD
-#define SCSI_SEL_PIN  GPIO_PIN_15
-#define SCSI_SEL_EXTI EXTI_15
-#define SCSI_SEL_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOD
-#define SCSI_SEL_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_15
-#define SCSI_SEL_IRQ EXTI10_15_IRQHandler
-#define SCSI_SEL_IRQn EXTI10_15_IRQn
-
-
-// BSY pin uses EXTI interrupt
-#define SCSI_BSY_PORT GPIOB
-#define SCSI_BSY_PIN  GPIO_PIN_10
-#define SCSI_BSY_EXTI EXTI_10
-#define SCSI_BSY_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB
-#define SCSI_BSY_EXTI_SOURCE_PIN  GPIO_PIN_SOURCE_10
-#define SCSI_BSY_IRQ  EXTI10_15_IRQHandler
-#define SCSI_BSY_IRQn EXTI10_15_IRQn
-
-// RST pin uses EXTI interrupt
-#define SCSI_RST_PORT GPIOB
-#define SCSI_RST_PIN  GPIO_PIN_13
-#define SCSI_RST_EXTI EXTI_13
-#define SCSI_RST_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB
-#define SCSI_RST_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_13
-#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 GPIOB
-#define SCSI_TERM_EN_PIN  GPIO_PIN_0
-
-// SPI/I2S Pins
-#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
-
-#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
-#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
-
-
-// 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_5
-// #define DIPSW3_PIN   GPIO_PIN_6
-
-// v1.2 DIP switch pins
-#define DIP_PORT     GPIOB
-#define DIPSW2_PIN   GPIO_PIN_5
-#define DIPSW3_PIN   GPIO_PIN_4
-// DIPSW DIRECT MODE replaces DIPSW1
-#define DIPSW_DIRECT_MODE_PORT  GPIOB
-#define DIPSW_DIRECT_MODE_PIN   GPIO_PIN_8
-// SCSI ID DIP swtich
-#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
-
-// Status LED pins
-#define LED_PORT     GPIOC
-#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)
-#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 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

+ 8 - 9
lib/ZuluSCSI_platform_GD32F205/platform_hw_config.cpp

@@ -22,7 +22,8 @@
 
 #ifdef ZULUSCSI_HARDWARE_CONFIG
 #include "platform_hw_config.h"
-#include "ZuluSCSI_config.h"
+#include <ZuluSCSI_config.h>
+#include <ZuluSCSI_log.h>
 
 HardwareConfig g_hw_config;
 
@@ -41,9 +42,9 @@ void hw_config_init_gpios()
     g_hw_config.init_gpios();
 }
 
-void hw_config_init_state()
+void hw_config_init_state(bool is_active)
 {
-    g_hw_config.init_state();
+    g_hw_config.init_state(is_active);
 }
 
 void HardwareConfig::init_gpios()
@@ -54,17 +55,15 @@ void HardwareConfig::init_gpios()
     // Device select BCD rotary dip switch
     gpio_init(DIPROT_DEVICE_SEL_BIT_PORT, GPIO_MODE_IPD, 0, DIPROT_DEVICE_SEL_BIT_PINS);
 
-    // Direct/Raw Mode Select
-    gpio_init(DIPSW_DIRECT_MODE_PORT, GPIO_MODE_IPD, 0, DIPSW_DIRECT_MODE_PIN);
-
     LED_EJECT_OFF();
     gpio_init(LED_EJECT_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, LED_EJECT_PIN);
 }
 
-void HardwareConfig::init_state()
+void HardwareConfig::init_state(bool is_active)
 {
-    m_is_active = RESET == gpio_input_bit_get(DIPSW_DIRECT_MODE_PORT, DIPSW_DIRECT_MODE_PIN);
+    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)
@@ -79,7 +78,7 @@ void HardwareConfig::init_state()
         m_device_type = S2S_CFG_OPTICAL;
     break;
     case 3:
-        m_device_type = S2S_CFG_MO;
+        m_device_type = S2S_CFG_REMOVABLE;
     break;
 
     default:

+ 2 - 2
lib/ZuluSCSI_platform_GD32F205/platform_hw_config.h

@@ -35,7 +35,7 @@ extern "C"
     S2S_CFG_TYPE hw_config_selected_device();
     bool hw_config_is_active();
     void hw_config_init_gpios();
-    void hw_config_init_state();
+    void hw_config_init_state(bool is_active);
     
 #ifdef __cplusplus
 }
@@ -49,7 +49,7 @@ public:
     void init_gpios();
 
     // Initialize device state settings
-    void init_state();
+    void init_state(bool is_active);
 
     // get the device type
     // @returns the device type

+ 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);
+     
 }
 
 /************************/

+ 2 - 13
platformio.ini

@@ -43,6 +43,7 @@ lib_deps =
 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 -Wall -Wno-sign-compare -ggdb -g3
@@ -79,21 +80,9 @@ build_flags =
      -DENABLE_DEDICATED_SPI=1
      -DPIO_USBFS_DEVICE_CDC
      -DHAS_SDIO_CLASS
+     -DENABLE_AUDIO_OUTPUT
      -DZULUSCSI_V1_1
 
-; ZuluSCSI V1.2 hardware platform, similar to V1.0 hardware but customized for removable devices.
-[env:ZuluSCSIv1_2]
-extends = env:ZuluSCSIv1_0
-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
-     -DHAS_SDIO_CLASS
-     -DZULUSCSI_V1_2
-
 ; ZuluSCSI RP2040 hardware platform, based on the Raspberry Pi foundation RP2040 microcontroller
 [env:ZuluSCSI_RP2040]
 platform = raspberrypi@1.9.0

+ 1 - 1
src/ZuluSCSI.cpp

@@ -469,7 +469,7 @@ bool findHDDImages()
         if (is_cd) type = S2S_CFG_OPTICAL;
         if (is_fd) type = S2S_CFG_FLOPPY_14MB;
         if (is_mo) type = S2S_CFG_MO;
-        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

+ 1 - 1
src/ZuluSCSI_config.h

@@ -85,7 +85,7 @@
 #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_FIXED     {"ZULUSCSI", "ZuluSCSI HDD",      PLATFORM_REVISION, "1.0"}
 #define APPLE_DRIVEINFO_REMOVABLE {"IOMEGA",   "BETA230",           PLATFORM_REVISION, "2.02"}
 #define APPLE_DRIVEINFO_OPTICAL   {"MATSHITA", "CD-ROM CR-8004A",   PLATFORM_REVISION, "2.0a"}
 #define APPLE_DRIVEINFO_FLOPPY    {"TEAC",     "FD235HS",           PLATFORM_REVISION, "1.44"}

+ 6 - 6
src/ZuluSCSI_disk.cpp

@@ -293,7 +293,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;
@@ -307,7 +307,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;
@@ -424,10 +424,10 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int
             logmsg("---- Configuring as magneto-optical");
             img.deviceType = S2S_CFG_MO;
         }
-        else if (type == S2S_CFG_REMOVEABLE)
+        else if (type == S2S_CFG_REMOVABLE)
         {
             logmsg("---- Configuring as removable drive");
-            img.deviceType = S2S_CFG_REMOVEABLE;
+            img.deviceType = S2S_CFG_REMOVABLE;
         }
         else if (type == S2S_CFG_SEQUENTIAL)
         {
@@ -649,7 +649,7 @@ static void scsiDiskLoadConfig(int target_idx, const char *section)
 
             strcpy(tmp, "RM0");
             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;
@@ -780,7 +780,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, "RM0");
                 break;
                 case S2S_CFG_MO: