Ver Fonte

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 há 2 anos atrás
pai
commit
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: