Explorar el Código

Add platform support for upcoming ZuluSCSI Blaster

*    Set ZuluSCSI Blaster default clock rate to 200.4MHz
        For ZuluSCSI Blaster (RP2350B), valid SpeedGrade modes are:
     - A: the same as TurboMax 250MHz (not compatible with CD-ROM audio)
     - B: 175MHz (CD-ROM audio) slower than default
     - C: 155.25MHz (CD-ROM audio) slower than B

*   Add ability to set SCSI read RP2MCU PIO timings. The changes affect the RP2MCUs' SCSI PIO state machine timings.
    Specifically the synchronous read pacer PIO state machine timings are no longer calculated from the SCSI bus write PIO state machine timings.

    With this ability the default 125MHz system clock of the RP2040 can now do Ultra SCSI (Fast-20) though the actual throughput is around 10.2 MB/s for reads from the device. This is about 0.4MB/s faster than what it's original default Fast-10 speed was.

*    The bootloader now checks for target name as prefix in ZuluSCSI*.bin
    update file e.g."ZuluSCSI_RP2040", or "ZuluSCSI_Pico".

    The ZuluSCSI-FW.zip package now matches firmware filename length along with
    the above prefix check so
    "ZuluSCSI_Pico_2025-02-21_e4be9ed.bin"
    and
    "ZuluSCSI_Pico_2_2025-02-21_e4be9ed.bin"
    are unique,
    but "ZuluSCSI_Pico_2025-02-27_af32f89.bin" matches the first string.

    Because zuluscsi*.bin files are not longer blindly used by the
    bootloader. The main code warns if there are zuluscsi*.bin files
    on the SD card that were not used as updates by the bootloader.

*    Fix Win9x CD Playback and TOC LBA addresses

    These are fixes taken from https://github.com/ZuluIDE/ZuluIDE-firmware/pull/180
    They fix an issue with incorrectly reported LBA addresses in TOC reading. It also fixes an issue where the Win9x CD Player was skipping tracks after a track transition.

*    Enable I2S audio output for ZuluSCSI 2350B
*    Differentiate between I2S and SP/DIF files

    - Modified ZuluIDE I2S code to work with ZuluSCSI
    - Added a Max Volume feature that can set a percentage of the max volume from 0 - 100 to quiet down the CD Audio DAC.
    - Created and redefined the following defines in platformio.ini.
     + ENABLE_AUDIO_OUTPUT - general audio code
     + ENABLE_AUDIO_OUTPUT_I2S - specific code to I2S interfaces
     + ENABLE_AUDIO_OUTPUT_SPDIF - specific code to SP/DIP interfaces

*    Fix ordering of speed grades. The order of the speed grade text and enum didn't match so speed grade, so A wasn't matching the enum SPEED_GRADE_A, etc.

*    Multi-bin CD Audio game playback. Tested Quake 2 with a multi-bin/cue files directory in Windows 98SE, aong with testing single bin/cue files in Mac OS running Descent. Both used a SCSI ID 0 hard drive with a CD-ROM drive on another SCSI ID. Benchmarking the hard drive with CD audio playing on the Mac caused some audio popping, but actual game play did not stutter

*    Properly move code and constants to SRAM

    For the RP2350B, moved the code and constants that should be run in SRAM into the `EXCLUDE FILE` for .text and .rodata. Created a map file to make sure the code and constants were actually put into SRAM by the .ld scripts. Verified that moving both the .text and .rodata actually resulted in a speed increase in linux using dd. Got around ~10.8MB/s
    from 10.4MB/s.

    Added a comments in the rp mcu's linker scripts (.ld files) on how to move the code back to Flash for debugging and added a notes in the `platformio.ini` file for creating .map files and to look at the linker scripts to get debugging to work.

*   Move voltage monitoring channel to 8 for RP2350B, since the RP2350B uses a different ADC channel for monitoring temperature     than the RP2350A or RP2040.
Morio hace 8 meses
padre
commit
1614613c62
Se han modificado 47 ficheros con 2092 adiciones y 398 borrados
  1. 1 1
      boards/zuluscsi_blaster.json
  2. 10 6
      lib/ZipParser/zip_parser.cpp
  3. 3 2
      lib/ZipParser/zip_parser.h
  4. 101 0
      lib/ZuluI2S/ZuluI2S.cpp
  5. 51 0
      lib/ZuluI2S/ZuluI2S.h
  6. 66 0
      lib/ZuluI2S/zulu_pio_i2s.pio
  7. 61 0
      lib/ZuluI2S/zulu_pio_i2s.pio.h
  8. 1 1
      lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.cpp
  9. 6 6
      lib/ZuluSCSI_platform_GD32F205/audio_i2s.cpp
  10. 2 2
      lib/ZuluSCSI_platform_GD32F205/audio_i2s.h
  11. 67 31
      lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform.cpp
  12. 3 3
      lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform.h
  13. 8 8
      lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_config.h
  14. 75 59
      lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_gpio_Blaster.h
  15. 1 1
      lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_gpio_Pico.h
  16. 1 1
      lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_gpio_Pico_2.h
  17. 1 1
      lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_gpio_RP2040.h
  18. 1 1
      lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_msc.cpp
  19. 764 0
      lib/ZuluSCSI_platform_RP2MCU/audio_i2s.cpp
  20. 56 0
      lib/ZuluSCSI_platform_RP2MCU/audio_i2s.h
  21. 10 10
      lib/ZuluSCSI_platform_RP2MCU/audio_spdif.cpp
  22. 2 2
      lib/ZuluSCSI_platform_RP2MCU/audio_spdif.h
  23. 15 5
      lib/ZuluSCSI_platform_RP2MCU/custom_timings.cpp
  24. 1 1
      lib/ZuluSCSI_platform_RP2MCU/library.json
  25. 2 0
      lib/ZuluSCSI_platform_RP2MCU/rp2040-template.ld
  26. 18 24
      lib/ZuluSCSI_platform_RP2MCU/rp23xx-template.ld
  27. 3 14
      lib/ZuluSCSI_platform_RP2MCU/run_pioasm.sh
  28. 2 2
      lib/ZuluSCSI_platform_RP2MCU/scsi2sd_timings.c
  29. 1 1
      lib/ZuluSCSI_platform_RP2MCU/scsiHostPhy.cpp
  30. 5 1
      lib/ZuluSCSI_platform_RP2MCU/scsiPhy.cpp
  31. 52 39
      lib/ZuluSCSI_platform_RP2MCU/scsi_accel_target.cpp
  32. 5 1
      lib/ZuluSCSI_platform_RP2MCU/sd_card_sdio.cpp
  33. 19 6
      lib/ZuluSCSI_platform_RP2MCU/sdio.cpp
  34. 363 61
      lib/ZuluSCSI_platform_RP2MCU/timings_RP2MCU.c
  35. 34 11
      lib/ZuluSCSI_platform_RP2MCU/timings_RP2MCU.h
  36. 24 15
      platformio.ini
  37. 40 8
      src/ZuluSCSI.cpp
  38. 29 3
      src/ZuluSCSI_audio.h
  39. 2 2
      src/ZuluSCSI_bootloader.cpp
  40. 50 33
      src/ZuluSCSI_cdrom.cpp
  41. 2 3
      src/ZuluSCSI_config.h
  42. 71 3
      src/ZuluSCSI_disk.cpp
  43. 5 0
      src/ZuluSCSI_disk.h
  44. 9 9
      src/ZuluSCSI_settings.cpp
  45. 3 1
      src/ZuluSCSI_settings.h
  46. 4 5
      zuluscsi.ini
  47. 42 15
      zuluscsi_timings.ini

+ 1 - 1
boards/zuluscsi_rp2350A.json → boards/zuluscsi_blaster.json

@@ -32,7 +32,7 @@
     "frameworks": [
         "arduino"
     ],
-    "name": "ZuluSCSI RP2350A",
+    "name": "ZuluSCSI Blaster",
     "upload": {
         "maximum_ram_size": 524288,
         "maximum_size": 16777216,

+ 10 - 6
lib/ZipParser/zip_parser.cpp

@@ -20,6 +20,7 @@
 **/
 
 #include "zip_parser.h"
+#include <ctype.h>
 
 #define ZIP_PARSER_METHOD_DEFLATE_BYTE 0x08
 #define ZIP_PARSER_METHOD_UNCOMPRESSED_BYTE 0x00
@@ -32,10 +33,10 @@ namespace zipparser
         filename_len = 0;
         Reset();
     }
-    Parser::Parser(char const *filename, const size_t length)
+    Parser::Parser(char const *filename, const size_t length, const size_t target_total_length)
     {
         Reset();
-        SetMatchingFilename(filename, length);
+        SetMatchingFilename(filename, length, target_total_length);
     }
 
     void Parser::Reset()
@@ -46,7 +47,7 @@ namespace zipparser
         crc = 0;
     }
 
-    void Parser::SetMatchingFilename(char const *filename, const size_t length)
+    void Parser::SetMatchingFilename(char const *filename, const size_t length, const size_t target_total_length)
     {
         if (filename[0] == '\0')
             filename_len = 0;
@@ -54,6 +55,7 @@ namespace zipparser
         {
             this->filename = filename;
             filename_len = length;
+            target_zip_filename_len = target_total_length;
         }
     }
 
@@ -198,15 +200,17 @@ namespace zipparser
                 break;
                 case parsing_target::filename:
                     if (position <= current_zip_filename_len - 1)
-                    {    
-                        if (matching && position < filename_len && filename[position] != buf[idx])
+                    {
+                        // make sure zipped filename is the correct length
+                        if (current_zip_filename_len != target_zip_filename_len)
+                            matching = false; 
+                        if (matching && position < filename_len && tolower(filename[position]) != tolower(buf[idx]))
                             matching = false;
                         if (position == filename_len - 1 && matching)
                             filename_match = true;
                         if (position == current_zip_filename_len -1)
                         {
                             target = parsing_target::extra_field;
-                            matching = true;
                             position = 0;
                             if (extra_field_len == 0)
                             {

+ 3 - 2
lib/ZipParser/zip_parser.h

@@ -33,8 +33,8 @@ namespace zipparser
     {
         public:
             Parser();
-            Parser(char const *filename, const size_t length);
-            void SetMatchingFilename(char const *filename, const size_t length);
+            Parser(char const *filename, const size_t length, const size_t target_total_length);
+            void SetMatchingFilename(char const *filename, const size_t length, const size_t target_total_length);
             void Reset();
             static const int32_t PARSE_ERROR = -1;
             static const int32_t PARSE_CENTRAL_DIR = -2;
@@ -52,6 +52,7 @@ namespace zipparser
             char const *filename;
             size_t filename_len;
             size_t current_zip_filename_len;
+            size_t target_zip_filename_len;
             size_t extra_field_len;
             uint32_t compressed_data_size;
             uint32_t uncompressed_data_size;

+ 101 - 0
lib/ZuluI2S/ZuluI2S.cpp

@@ -0,0 +1,101 @@
+/*
+    I2SIn and I2SOut for Raspberry Pi Pico
+    Implements one or more I2S interfaces using DMA
+
+    Copyright (c) 2022 Earle F. Philhower, III <earlephilhower@yahoo.com>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+#include <Arduino.h>
+#include "ZuluI2S.h"
+#include "zulu_pio_i2s.pio.h"
+#include <pico/stdlib.h>
+
+
+I2S::I2S() {
+    _running = false;
+    _div_int = 48;
+    _div_frac = 0;
+    _bps = 16;
+    _pio = pio0_hw;
+    _sm = 1;
+    _pinBCLK = 26;
+    _pinDOUT = 28;
+}
+
+I2S::~I2S() {
+    end();
+}
+
+bool I2S::setBCLK(pin_size_t pin) {
+    if (_running || (pin > 28)) {
+        return false;
+    }
+    _pinBCLK = pin;
+    return true;
+}
+
+bool I2S::setDATA(pin_size_t pin) {
+    if (_running || (pin > 29)) {
+        return false;
+    }
+    _pinDOUT = pin;
+    return true;
+}
+
+bool I2S::setBitsPerSample(int bps) {
+    if (_running || ((bps != 8) && (bps != 16) && (bps != 24) && (bps != 32))) {
+        return false;
+    }
+    _bps = bps;
+    return true;
+}
+
+volatile void *I2S::getPioFIFOAddr()
+{
+    return (volatile void *)&_pio->txf[_sm];
+}
+
+bool I2S::setDivider(uint16_t div_int, uint8_t div_frac) {
+    _div_int = div_int;
+    _div_frac = div_frac;
+    return true;
+}
+
+uint I2S::getPioDreq() {
+    return pio_get_dreq(_pio, _sm, true);
+}
+
+bool I2S::begin(PIO pio, uint sm) {
+    if (_running)
+        return true;
+    _pio = pio;
+    _sm = sm;
+    _running = true;
+    int off = 0;
+    pio_sm_claim(_pio, _sm);
+    off = pio_add_program(_pio, &pio_i2s_out_program);
+    pio_i2s_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps);
+    pio_sm_set_clkdiv_int_frac(_pio, _sm, _div_int, _div_frac);
+    pio_sm_set_enabled(_pio, _sm, true);
+    return true;
+}
+
+void I2S::end() {
+    if (_running) {
+        pio_sm_set_enabled(_pio, _sm, false);
+        _running = false;
+    }
+}

+ 51 - 0
lib/ZuluI2S/ZuluI2S.h

@@ -0,0 +1,51 @@
+/*
+    I2SIn and I2SOut for Raspberry Pi Pico
+    Implements one or more I2S interfaces using DMA
+
+    Copyright (c) 2022 Earle F. Philhower, III <earlephilhower@yahoo.com>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#pragma once
+
+class I2S {
+public:
+    I2S();
+    virtual ~I2S();
+
+    bool setBCLK(pin_size_t pin);
+    bool setDATA(pin_size_t pin);
+    bool setBitsPerSample(int bps);
+
+    bool setDivider(uint16_t div_int, uint8_t div_frac);
+    uint getPioDreq();
+    volatile void *getPioFIFOAddr();
+
+    bool begin(PIO pio, uint sm);
+    void end();
+
+private:
+    pin_size_t _pinBCLK;
+    pin_size_t _pinDOUT;
+    uint16_t _div_int;
+    uint8_t _div_frac;
+    int _bps;
+    bool _running;
+
+    PIOProgram *_i2s;
+    PIO _pio;
+    int _sm;
+};

+ 66 - 0
lib/ZuluI2S/zulu_pio_i2s.pio

@@ -0,0 +1,66 @@
+; pio_i2s for the Raspberry Pi Pico RP2040
+;
+; Based loosely off of the MicroPython I2S code in
+; https://github.com/micropython/micropython/blob/master/ports/rp2/machine_i2s.c
+;
+; Copyright (c) 2022 Earle F. Philhower, III <earlephilhower@yahoo.com>
+;
+; This library is free software; you can redistribute it and/or
+; modify it under the terms of the GNU Lesser General Public
+; License as published by the Free Software Foundation; either
+; version 2.1 of the License, or (at your option) any later version.
+;
+; This library 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
+; Lesser General Public License for more details.
+;
+; You should have received a copy of the GNU Lesser General Public
+; License along with this library; if not, write to the Free Software
+; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+.program pio_i2s_out
+.side_set 2   ; 0 = bclk, 1=wclk
+
+; The C code should place (number of bits/sample - 2) in Y and
+; also update the SHIFTCTRL to be 24 or 32 as appropriate
+
+;                           +----- WCLK
+;                           |+---- BCLK
+    mov x, y         side 0b01
+left:
+    out pins, 1      side 0b00
+    jmp x--, left    side 0b01
+    out pins, 1      side 0b10 ; Last bit of left has WCLK change per I2S spec
+
+    mov x, y         side 0b11
+right:
+    out pins, 1      side 0b10
+    jmp x--, right   side 0b11
+    out pins, 1      side 0b00 ; Last bit of right also has WCLK change
+    ; Loop back to beginning...
+
+% c-sdk {
+
+static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits) {
+    pio_gpio_init(pio, data_pin);
+    pio_gpio_init(pio, clock_pin_base);
+    pio_gpio_init(pio, clock_pin_base + 1);
+
+    pio_sm_config sm_config =  pio_i2s_out_program_get_default_config(offset);
+
+    sm_config_set_out_pins(&sm_config, data_pin, 1);
+    sm_config_set_sideset_pins(&sm_config, clock_pin_base);
+    sm_config_set_out_shift(&sm_config, false, true, (bits <= 16) ? 2 * bits : bits);
+    sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
+
+    pio_sm_init(pio, sm, offset, &sm_config);
+
+    uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
+    pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
+    pio_sm_set_pins(pio, sm, 0); // clear pins
+
+    pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
+}
+
+%}

+ 61 - 0
lib/ZuluI2S/zulu_pio_i2s.pio.h

@@ -0,0 +1,61 @@
+// -------------------------------------------------- //
+// This file is autogenerated by pioasm; do not edit! //
+// -------------------------------------------------- //
+
+#pragma once
+
+#if !PICO_NO_HARDWARE
+#include "hardware/pio.h"
+#endif
+
+// ----------- //
+// pio_i2s_out //
+// ----------- //
+
+#define pio_i2s_out_wrap_target 0
+#define pio_i2s_out_wrap 7
+
+static const uint16_t pio_i2s_out_program_instructions[] = {
+            //     .wrap_target
+    0xa822, //  0: mov    x, y            side 1     
+    0x6001, //  1: out    pins, 1         side 0     
+    0x0841, //  2: jmp    x--, 1          side 1     
+    0x7001, //  3: out    pins, 1         side 2     
+    0xb822, //  4: mov    x, y            side 3     
+    0x7001, //  5: out    pins, 1         side 2     
+    0x1845, //  6: jmp    x--, 5          side 3     
+    0x6001, //  7: out    pins, 1         side 0     
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program pio_i2s_out_program = {
+    .instructions = pio_i2s_out_program_instructions,
+    .length = 8,
+    .origin = -1,
+};
+
+static inline pio_sm_config pio_i2s_out_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + pio_i2s_out_wrap_target, offset + pio_i2s_out_wrap);
+    sm_config_set_sideset(&c, 2, false, false);
+    return c;
+}
+
+static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits) {
+    pio_gpio_init(pio, data_pin);
+    pio_gpio_init(pio, clock_pin_base);
+    pio_gpio_init(pio, clock_pin_base + 1);
+    pio_sm_config sm_config =  pio_i2s_out_program_get_default_config(offset);
+    sm_config_set_out_pins(&sm_config, data_pin, 1);
+    sm_config_set_sideset_pins(&sm_config, clock_pin_base);
+    sm_config_set_out_shift(&sm_config, false, true, (bits <= 16) ? 2 * bits : bits);
+    sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
+    pio_sm_init(pio, sm, offset, &sm_config);
+    uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
+    pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
+    pio_sm_set_pins(pio, sm, 0); // clear pins
+    pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
+}
+
+#endif

+ 1 - 1
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.cpp

@@ -32,7 +32,7 @@
 #include <SdFat.h>
 #include <scsi.h>
 #include <assert.h>
-#include <audio.h>
+#include <audio_i2s.h>
 #include <ZuluSCSI_audio.h>
 #include <ZuluSCSI_settings.h>
 

+ 6 - 6
lib/ZuluSCSI_platform_GD32F205/audio.cpp → lib/ZuluSCSI_platform_GD32F205/audio_i2s.cpp

@@ -19,8 +19,8 @@
  * 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"
+#ifdef ENABLE_AUDIO_OUTPUT_I2S
+#include "audio_i2s.h"
 #include "ZuluSCSI_platform.h"
 #include "ZuluSCSI_audio.h"
 #include "ZuluSCSI_v1_1_gpio.h"
@@ -255,7 +255,7 @@ void audio_poll()
     }
 }
 
-bool audio_play(uint8_t owner, ImageBackingStore* img, uint64_t start, uint64_t end, bool swap)
+bool audio_play(uint8_t owner, image_config_t* img, uint64_t start, uint64_t end, bool swap)
 {
     if (audio_is_active()) audio_stop(audio_owner);
 
@@ -272,7 +272,7 @@ bool audio_play(uint8_t owner, ImageBackingStore* img, uint64_t start, uint64_t
         return false;
     }
 
-    audio_file = img;
+    audio_file = &img->file;
     if (!audio_file->isOpen()) {
         logmsg("File not open for audio playback, ", owner);
         return false;
@@ -416,9 +416,9 @@ uint64_t audio_get_file_position()
     return fpos;
 }
 
-void audio_set_file_position(uint32_t lba)
+void audio_set_file_position(uint8_t id, uint32_t lba)
 {
     fpos = 2352 * (uint64_t)lba;
 }
 
-#endif // ENABLE_AUDIO_OUTPUT
+#endif // ENABLE_AUDIO_OUTPUT_I2S

+ 2 - 2
lib/ZuluSCSI_platform_GD32F205/audio.h → lib/ZuluSCSI_platform_GD32F205/audio_i2s.h

@@ -22,7 +22,7 @@
 
 
 #pragma once
-#ifdef ENABLE_AUDIO_OUTPUT
+#ifdef ENABLE_AUDIO_OUTPUT_I2S
 
 extern bool g_audio_enabled;
 extern bool g_ode_audio_stopped;
@@ -58,4 +58,4 @@ void audio_setup();
  * Called from platform_poll() to fill sample buffer(s) if needed.
  */
 void audio_poll();
-#endif //ENABLE_AUDIO_OUTPUT
+#endif //ENABLE_AUDIO_OUTPUT_I2S

+ 67 - 31
lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform.cpp

@@ -58,9 +58,11 @@ extern "C" {
 #include "ZuluSCSI_platform_msc.h"
 #endif
 
-#ifdef ENABLE_AUDIO_OUTPUT
-#  include "audio.h"
-#endif // ENABLE_AUDIO_OUTPUT
+#ifdef ENABLE_AUDIO_OUTPUT_SPDIF
+#  include "audio_spdif.h"
+#elif defined(ENABLE_AUDIO_OUTPUT_I2S)
+#  include "audio_i2s.h"
+#endif // ENABLE_AUDIO_OUTPUT_SPDIF
 
 extern bool g_rawdrive_active;
 
@@ -161,9 +163,15 @@ bool platform_reclock(zuluscsi_speed_grade_t speed_grade)
 
         if (do_reclock)
         {
+#ifdef  ENABLE_AUDIO_OUTPUT
+            if (g_zuluscsi_timings->audio.audio_clocked)
+                logmsg("Reclocking with these settings are compatible with CD audio playback");
+            else
+                logmsg("Reclocking with these settings may cause audio playback to be too fast or slow ");
+#endif
             logmsg("Initial Clock set to ", (int) platform_sys_clock_in_hz(), "Hz");
-            logmsg("Attempting reclock the MCU to ",(int) g_zuluscsi_timings->clk_hz, "Hz");
-            logmsg("Attempting to set SDIO clock to ", (int)((g_zuluscsi_timings->clk_hz / g_zuluscsi_timings->sdio.clk_div_pio + (5 * MHZ / 10)) / MHZ) , "MHz");
+            logmsg("Reclocking the MCU to ",(int) g_zuluscsi_timings->clk_hz, "Hz");
+            logmsg("Setting the SDIO clock to ", (int)((g_zuluscsi_timings->clk_hz / g_zuluscsi_timings->sdio.clk_div_pio + (5 * MHZ / 10)) / MHZ) , "MHz");
             usb_log_poll();
             reclock();
             logmsg("After reclocking, system reports clock set to ", (int) platform_sys_clock_in_hz(), "Hz");
@@ -266,7 +274,7 @@ void platform_init()
         termination = !gpio_get(DIP_TERM);
 
     }
-# else
+# elif defined(ZULUSCSI_V2_0)
     pin_setup_state_t dip_state = read_setup_ack_pin();
     if (dip_state == SETUP_UNDETERMINED)
     {
@@ -285,6 +293,11 @@ void platform_init()
     // dbglog DIP switch works in any case, as it does not have bus hold.
     dbglog = !gpio_get(DIP_DBGLOG);
     g_log_debug = dbglog;
+# else
+    g_scsi_initiator = !gpio_get(DIP_INITIATOR);
+    termination = !gpio_get(DIP_TERM);
+    dbglog = !gpio_get(DIP_DBGLOG);
+    g_log_debug = dbglog;
 # endif
 #else
     delay(10);
@@ -331,18 +344,6 @@ void platform_init()
     logmsg ("SCSI termination is handled by a hardware jumper");
 #endif  // HAS_DIP_SWITCHES
 
-#ifdef ENABLE_AUDIO_OUTPUT
-    logmsg("SP/DIF audio to expansion header enabled");
-    logmsg("Reclocking MCU for audio timings");
-    if (platform_reclock(SPEED_GRADE_AUDIO))
-    {
-        logmsg("Reclocked for Audio Ouput finished");
-    }
-    else
-    {
-        logmsg("Audio Output timings not found");
-    }
-#endif // ENABLE_AUDIO_OUTPUT
 
     // Get flash chip size
     uint8_t cmd_read_jedec_id[4] = {0x9f, 0, 0, 0};
@@ -366,7 +367,7 @@ void platform_init()
     // LED pin
     gpio_conf(LED_PIN,        GPIO_FUNC_SIO, false,false, true,  false, false);
 
-#ifndef ENABLE_AUDIO_OUTPUT
+#ifndef ENABLE_AUDIO_OUTPUT_SPDIF
 #ifdef GPIO_I2C_SDA
     // I2C pins
     //        pin             function       pup   pdown  out    state fast
@@ -378,7 +379,7 @@ void platform_init()
     gpio_conf(GPIO_EXP_AUDIO, GPIO_FUNC_SPI, true,false, false,  true, true);
     gpio_conf(GPIO_EXP_SPARE, GPIO_FUNC_SIO, true,false, false,  true, false);
     // configuration of corresponding SPI unit occurs in audio_setup()
-#endif  // ENABLE_AUDIO_OUTPUT
+#endif  // ENABLE_AUDIO_OUTPUT_SPDIF
 
 #ifdef GPIO_USB_POWER
     gpio_conf(GPIO_USB_POWER, GPIO_FUNC_SIO, false, false, false,  false, false);
@@ -451,15 +452,29 @@ void platform_late_init()
         gpio_conf(SCSI_IN_ATN,    GPIO_FUNC_SIO, true, false, false, true, false);
         gpio_conf(SCSI_IN_RST,    GPIO_FUNC_SIO, true, false, false, true, false);
 
-#ifndef PIO_FRAMEWORK_ARDUINO_NO_USB
-    Serial.begin();
+#ifdef ENABLE_AUDIO_OUTPUT_I2S
+    logmsg("I2S audio to expansion header enabled");
+    if (!platform_reclock(SPEED_GRADE_AUDIO_I2S))
+    {
+        logmsg("Audio output timings not found");
+    }
 #endif
-
+#ifdef ENABLE_AUDIO_OUTPUT_SPDIF
+    logmsg("S/PDIF audio to expansion header enabled");
+    if (platform_reclock(SPEED_GRADE_AUDIO_SPDIF))
+    {
+        logmsg("Reclocked for Audio Ouput at ", (int) platform_sys_clock_in_hz(), "Hz");
+    }
+    else
+    {
+        logmsg("Audio Output timings not found");
+    }
+#endif // ENABLE_AUDIO_OUTPUT_SPDIF
 
 #ifdef ENABLE_AUDIO_OUTPUT
         // one-time control setup for DMA channels and second core
         audio_setup();
-#endif // ENABLE_AUDIO_OUTPUT
+#endif // ENABLE_AUDIO_OUTPUT_SPDIF
     }
     else
     {
@@ -482,6 +497,9 @@ void platform_late_init()
         gpio_conf(SCSI_OUT_ATN,   GPIO_FUNC_SIO, false,false, true,  true, true);
 #endif  // PLATFORM_HAS_INITIATOR_MODE
     }
+#ifndef PIO_FRAMEWORK_ARDUINO_NO_USB
+    Serial.begin();
+#endif
     scsi_accel_rp2040_init();
 }
 
@@ -731,13 +749,17 @@ static void adc_poll()
         adc_init();
         adc_set_temp_sensor_enabled(true);
         adc_set_clkdiv(65535); // Lowest samplerate, about 2 kHz
+#ifdef ZULUSCSI_BLASTER
+        adc_select_input(8);
+#else
         adc_select_input(4);
+#endif
         adc_fifo_setup(true, false, 0, false, false);
         adc_run(true);
         initialized = true;
     }
 
-#ifdef ENABLE_AUDIO_OUTPUT
+#ifdef ENABLE_AUDIO_OUTPUT_SPDIF
     /*
     * If ADC sample reads are done, either via direct reading, FIFO, or DMA,
     * at the same time a SPI DMA write begins, it appears that the first
@@ -746,7 +768,7 @@ static void adc_poll()
     * is playing.
     */
    if (audio_is_active()) return;
-#endif  // ENABLE_AUDIO_OUTPUT
+#endif  // ENABLE_AUDIO_OUTPUT_SPDIF
 
     int adc_value_max = 0;
     while (!adc_fifo_is_empty())
@@ -900,9 +922,9 @@ void platform_poll()
     usb_log_poll();
     adc_poll();
 
-#ifdef ENABLE_AUDIO_OUTPUT
+#if defined(ENABLE_AUDIO_OUTPUT_SPDIF) || defined(ENABLE_AUDIO_OUTPUT_I2S)
     audio_poll();
-#endif // ENABLE_AUDIO_OUTPUT
+#endif // ENABLE_AUDIO_OUTPUT_SPDIF
 }
 
 void platform_reset_mcu()
@@ -914,14 +936,14 @@ uint8_t platform_get_buttons()
 {
     uint8_t buttons = 0;
 
-#if defined(ENABLE_AUDIO_OUTPUT)
+#if defined(ENABLE_AUDIO_OUTPUT_SPDIF)
     // pulled to VCC via resistor, sinking when pressed
     if (!gpio_get(GPIO_EXP_SPARE)) buttons |= 1;
 #elif defined(GPIO_I2C_SDA)
     // SDA = button 1, SCL = button 2
     if (!gpio_get(GPIO_I2C_SDA)) buttons |= 1;
     if (!gpio_get(GPIO_I2C_SCL)) buttons |= 2;
-#endif // defined(ENABLE_AUDIO_OUTPUT)
+#endif // defined(ENABLE_AUDIO_OUTPUT_SPDIF)
 
     // Simple debouncing logic: handle button releases after 100 ms delay.
     static uint32_t debounce;
@@ -1022,7 +1044,20 @@ bool platform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count
  */
 
 #define PARITY(n) ((1 ^ (n) ^ ((n)>>1) ^ ((n)>>2) ^ ((n)>>3) ^ ((n)>>4) ^ ((n)>>5) ^ ((n)>>6) ^ ((n)>>7)) & 1)
-#define X(n) (\
+#ifdef ZULUSCSI_BLASTER
+# define X(n) (\
+    ((n & 0x01) ? 0 : (1 << 0)) | \
+    ((n & 0x02) ? 0 : (1 << 1)) | \
+    ((n & 0x04) ? 0 : (1 << 2)) | \
+    ((n & 0x08) ? 0 : (1 << 3)) | \
+    ((n & 0x10) ? 0 : (1 << 4)) | \
+    ((n & 0x20) ? 0 : (1 << 5)) | \
+    ((n & 0x40) ? 0 : (1 << 6)) | \
+    ((n & 0x80) ? 0 : (1 << 7)) | \
+    (PARITY(n)  ? 0 : (1 << 8)) \
+)
+#else
+# define X(n) (\
     ((n & 0x01) ? 0 : (1 << SCSI_IO_DB0)) | \
     ((n & 0x02) ? 0 : (1 << SCSI_IO_DB1)) | \
     ((n & 0x04) ? 0 : (1 << SCSI_IO_DB2)) | \
@@ -1033,6 +1068,7 @@ bool platform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count
     ((n & 0x80) ? 0 : (1 << SCSI_IO_DB7)) | \
     (PARITY(n)  ? 0 : (1 << SCSI_IO_DBP)) \
 )
+#endif
 
 const uint16_t g_scsi_parity_lookup[256] __attribute__((aligned(512), section(".scratch_x.parity"))) =
 {

+ 3 - 3
lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform.h

@@ -38,9 +38,9 @@
 #elif defined(ZULUSCSI_BS2)
 // BS2 hardware variant, using Raspberry Pico board on a carrier PCB
 #include "ZuluSCSI_platform_gpio_BS2.h"
-#elif defined(ZULUSCSI_RP2350A)
-// RP2350A variant, using mcu chip directly
-#include "ZuluSCSI_platform_gpio_RP2350A.h"
+#elif defined(ZULUSCSI_BLASTER)
+// RP2350B variant, using mcu chip directly
+#include "ZuluSCSI_platform_gpio_Blaster.h"
 #else
 // Normal RP2040 variant, using RP2040 chip directly
 #include "ZuluSCSI_platform_gpio_RP2040.h"

+ 8 - 8
lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_config.h

@@ -31,7 +31,7 @@
 # define PLATFORM_HAS_INITIATOR_MODE 1
 # define DISABLE_SWO
 # define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_20
-# define PLATFORM_DEFAULT_SCSI_SPEED_SETTING 10
+# define PLATFORM_DEFAULT_SCSI_SPEED_SETTING 20
 #elif defined(ZULUSCSI_PICO_2)
 # ifdef ZULUSCSI_PICO_2_DAYNAPORT
 #   define PLATFORM_NAME "ZuluSCSI Pico 2 DaynaPORT"
@@ -39,16 +39,16 @@
 #   define PLATFORM_NAME "ZuluSCSI Pico 2"
 # endif
 # define PLATFORM_PID "Pico 2"
-# define PLATFORM_REVISION "2.0"
+# define PLATFORM_REVISION "2.3A"
 # define PLATFORM_HAS_INITIATOR_MODE 1
 # define DISABLE_SWO
 # define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_20
 # define PLATFORM_DEFAULT_SCSI_SPEED_SETTING 20
 
-#elif defined(ZULUSCSI_RP2350A)
-# define PLATFORM_NAME "ZuluSCSI RP2350A"
-# define PLATFORM_PID "RP2350A"
-# define PLATFORM_REVISION "2.0"
+#elif defined(ZULUSCSI_BLASTER)
+# define PLATFORM_NAME "ZuluSCSI Blaster"
+# define PLATFORM_PID "Blaster"
+# define PLATFORM_REVISION "2.3B"
 # define PLATFORM_HAS_INITIATOR_MODE 1
 # define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_20
 # define PLATFORM_DEFAULT_SCSI_SPEED_SETTING 20
@@ -57,14 +57,14 @@
 # define PLATFORM_PID "BS2"
 # define PLATFORM_REVISION "1.0"
 # define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_20
-# define PLATFORM_DEFAULT_SCSI_SPEED_SETTING 10
+# define PLATFORM_DEFAULT_SCSI_SPEED_SETTING 20
 #else
 # define PLATFORM_NAME "ZuluSCSI RP2040"
 # define PLATFORM_PID "RP2040"
 # define PLATFORM_REVISION "2.0"
 # define PLATFORM_HAS_INITIATOR_MODE 1
 # define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_20
-# define PLATFORM_DEFAULT_SCSI_SPEED_SETTING 10
+# define PLATFORM_DEFAULT_SCSI_SPEED_SETTING 20
 #endif
 
 #define PLATFORM_OPTIMAL_MIN_SD_WRITE_SIZE 32768

+ 75 - 59
lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_gpio_RP2350A.h → lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_gpio_Blaster.h

@@ -28,96 +28,112 @@
 // SCSI data input/output port.
 // The data bus uses external bidirectional buffer, with
 // direction controlled by DATA_DIR pin.
-#define SCSI_IO_DB0  0
-#define SCSI_IO_DB1  1
-#define SCSI_IO_DB2  2
-#define SCSI_IO_DB3  3
-#define SCSI_IO_DB4  4
-#define SCSI_IO_DB5  5
-#define SCSI_IO_DB6  6
-#define SCSI_IO_DB7  7
-#define SCSI_IO_DBP  8
-#define SCSI_IO_DATA_MASK 0x1FF
-#define SCSI_IO_SHIFT 0
+#define SCSI_IO_DB0  12
+#define SCSI_IO_DB1  13
+#define SCSI_IO_DB2  14
+#define SCSI_IO_DB3  15
+#define SCSI_IO_DB4  16
+#define SCSI_IO_DB5  17
+#define SCSI_IO_DB6  18
+#define SCSI_IO_DB7  19
+#define SCSI_IO_DBP  20
+#define SCSI_IO_DATA_MASK 0x1FF000
+#define SCSI_IO_SHIFT 12
 
 // Data direction control
-#define SCSI_DATA_DIR 17
+#define SCSI_DATA_DIR 22
 
 // SCSI output status lines
-#define SCSI_OUT_IO   12
-#define SCSI_OUT_CD   11
-#define SCSI_OUT_MSG  13
-#define SCSI_OUT_RST  28
-#define SCSI_OUT_BSY  26
-#define SCSI_OUT_REQ  9
-#define SCSI_OUT_SEL  24
+#define SCSI_OUT_IO   7
+#define SCSI_OUT_CD   23
+#define SCSI_OUT_MSG  26
+#define SCSI_OUT_RST  47
+#define SCSI_OUT_BSY  45
+#define SCSI_OUT_REQ  21
+#define SCSI_OUT_SEL  44
 
 // SCSI input status signals
-#define SCSI_IN_SEL  11
-#define SCSI_IN_ACK  10
-#define SCSI_IN_ATN  29
-#define SCSI_IN_BSY  13
-#define SCSI_IN_RST  27
+#define SCSI_IN_SEL  23
+#define SCSI_IN_ACK  27
+#define SCSI_IN_ATN  6
+#define SCSI_IN_BSY  26
+#define SCSI_IN_RST  46
 
 // Status line outputs for initiator mode
-#define SCSI_OUT_ACK  10
-#define SCSI_OUT_ATN  29
+#define SCSI_OUT_ACK  27
+#define SCSI_OUT_ATN  6
 
 // Status line inputs for initiator mode
-#define SCSI_IN_IO    12
-#define SCSI_IN_CD    11
-#define SCSI_IN_MSG   13
-#define SCSI_IN_REQ   9
+#define SCSI_IN_IO    7
+#define SCSI_IN_CD    23
+#define SCSI_IN_MSG   26
+#define SCSI_IN_REQ   21
 
 // Status LED pins
-#define LED_PIN      25
+#define LED_PIN      33
 
 // SD card pins in SDIO mode
-#define SDIO_CLK 18
-#define SDIO_CMD 19
-#define SDIO_D0  20
-#define SDIO_D1  21
-#define SDIO_D2  22
-#define SDIO_D3  23
+#define SDIO_CLK 34
+#define SDIO_CMD 35
+#define SDIO_D0  36
+#define SDIO_D1  37
+#define SDIO_D2  38
+#define SDIO_D3  39
 
 // SD card pins in SPI mode
 #define SD_SPI       spi0
-#define SD_SPI_SCK   18
-#define SD_SPI_MOSI  19
-#define SD_SPI_MISO  20
-#define SD_SPI_CS    23
+#define SD_SPI_SCK   SDIO_CLK
+#define SD_SPI_MOSI  SDIO_CMD
+#define SD_SPI_MISO  SDIO_D0
+#define SD_SPI_CS    SDIO_D3
 
-#ifndef ENABLE_AUDIO_OUTPUT
+#ifndef ENABLE_AUDIO_OUTPUT_SPDIF
     // IO expander I2C
-    #define GPIO_I2C_SDA 14
-    #define GPIO_I2C_SCL 15
+    #define GPIO_I2C_SDA 30
+    #define GPIO_I2C_SCL 31
 #else
     // IO expander I2C pins being used as SPI for audio
     #define AUDIO_SPI      spi1
-    #define GPIO_EXP_SPARE 14
-    #define GPIO_EXP_AUDIO 15
+    #define GPIO_EXP_SPARE 30
+    #define GPIO_EXP_AUDIO 31
+#endif
+
+#ifdef ENABLE_AUDIO_OUTPUT_I2S
+    #define GPIO_I2S_BCLK 8
+    #define GPIO_I2S_WS   9
+    #define GPIO_I2S_DOUT 10
+    #define I2S_DMA_IRQ_NUM DMA_IRQ_2
 #endif
 
-// DIP switch pins
-#define HAS_DIP_SWITCHES
-#define DIP_INITIATOR 10
-#define DIP_DBGLOG 16
-#define DIP_TERM 9
 
 // Other pins
-#define SWO_PIN 16
+#define SWO_PIN 32
+
+// DIP switch pins
+#define HAS_DIP_SWITCHES
+#define DIP_INITIATOR   SCSI_OUT_ACK
+#define DIP_DBGLOG      SWO_PIN
+#define DIP_TERM        SCSI_OUT_REQ
 
 // Below are GPIO access definitions that are used from scsiPhy.cpp.
 
 // Write a single SCSI pin.
 // Example use: SCSI_OUT(ATN, 1) sets SCSI_ATN to low (active) state.
 #define SCSI_OUT(pin, state) \
-    *(state ? &sio_hw->gpio_clr : &sio_hw->gpio_set) = 1 << (SCSI_OUT_ ## pin)
+    ((SCSI_OUT_ ## pin) > 31 ? \
+        *(state ? &sio_hw->gpio_hi_clr : &sio_hw->gpio_hi_set) = 1 << ((SCSI_OUT_ ## pin) - 32) \
+    : \
+        *(state ? &sio_hw->gpio_clr : &sio_hw->gpio_set) = 1 << (SCSI_OUT_ ## pin) \
+    )
 
 // Read a single SCSI pin.
 // Example use: SCSI_IN(ATN), returns 1 for active low state.
 #define SCSI_IN(pin) \
-    ((sio_hw->gpio_in & (1 << (SCSI_IN_ ## pin))) ? 0 : 1)
+    ((SCSI_IN_ ## pin) > 31 ? \
+        ((sio_hw->gpio_hi_in & (1 << ((SCSI_IN_ ## pin) - 32))) ? 0 : 1) \
+    : \
+        ((sio_hw->gpio_in & (1 << (SCSI_IN_ ## pin))) ? 0 : 1) \
+    )
 
 // Set pin directions for initiator vs. target mode
 #define SCSI_ENABLE_INITIATOR() \
@@ -141,7 +157,7 @@
 // Write SCSI data bus, also sets REQ to inactive.
 #define SCSI_OUT_DATA(data) \
     gpio_put_masked(SCSI_IO_DATA_MASK | (1 << SCSI_OUT_REQ), \
-                    g_scsi_parity_lookup[(uint8_t)(data)] | (1 << SCSI_OUT_REQ)), \
+                    (g_scsi_parity_lookup[(uint8_t)(data)] << SCSI_IO_SHIFT) | (1 << SCSI_OUT_REQ)), \
     SCSI_ENABLE_DATA_OUT()
 
 // Release SCSI data bus and REQ signal
@@ -157,10 +173,10 @@
     sio_hw->gpio_set = (1 << SCSI_OUT_IO) | \
                        (1 << SCSI_OUT_CD) | \
                        (1 << SCSI_OUT_MSG) | \
-                       (1 << SCSI_OUT_RST) | \
-                       (1 << SCSI_OUT_BSY) | \
-                       (1 << SCSI_OUT_REQ) | \
-                       (1 << SCSI_OUT_SEL)
+                       (1 << SCSI_OUT_REQ), \
+    sio_hw->gpio_hi_set =   (1 << (SCSI_OUT_RST - 32)) | \
+                            (1 << (SCSI_OUT_BSY - 32)) | \
+                            (1 << (SCSI_OUT_SEL - 32))
 
 // Read SCSI data bus
 #define SCSI_IN_DATA() \

+ 1 - 1
lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_gpio_Pico.h

@@ -87,7 +87,7 @@
 #define SD_SPI_MISO  12
 #define SD_SPI_CS    15
 
-#ifndef ENABLE_AUDIO_OUTPUT
+#ifndef ENABLE_AUDIO_OUTPUT_SPDIF
     // No spare pins for I2C
     // IO expander I2C
     // #define GPIO_I2C_SDA 14

+ 1 - 1
lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_gpio_Pico_2.h

@@ -87,7 +87,7 @@
 #define SD_SPI_MISO  12
 #define SD_SPI_CS    15
 
-#ifndef ENABLE_AUDIO_OUTPUT
+#ifndef ENABLE_AUDIO_OUTPUT_SPDIF
     // No spare pins for I2C
     // IO expander I2C
     // #define GPIO_I2C_SDA 14

+ 1 - 1
lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_gpio_RP2040.h

@@ -87,7 +87,7 @@
 #define SD_SPI_MISO  20
 #define SD_SPI_CS    23
 
-#ifndef ENABLE_AUDIO_OUTPUT
+#ifndef ENABLE_AUDIO_OUTPUT_SPDIF
     // IO expander I2C
     #define GPIO_I2C_SDA 14
     #define GPIO_I2C_SCL 15

+ 1 - 1
lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform_msc.cpp

@@ -231,7 +231,7 @@ extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8],
 
   const char vid[] = "ZuluSCSI";
   const char pid[] = PLATFORM_PID; 
-  const char rev[] = "1.0";
+  const char rev[] = PLATFORM_REVISION;
 
   memcpy(vendor_id, vid, tu_min32(strlen(vid), 8));
   memcpy(product_id, pid, tu_min32(strlen(pid), 16));

+ 764 - 0
lib/ZuluSCSI_platform_RP2MCU/audio_i2s.cpp

@@ -0,0 +1,764 @@
+/**
+ * Copyright (C) 2023 saybur
+ * Copyright (C) 2024 Rabbit Hole Computing LLC
+ *
+ * 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_I2S
+
+#include <SdFat.h>
+#include <stdbool.h>
+#include <hardware/dma.h>
+#include <hardware/irq.h>
+#include <hardware/pio.h>
+#include <pico/multicore.h>
+#include "audio_i2s.h"
+#include <CUEParser.h>
+#include "timings_RP2MCU.h"
+#include "ZuluSCSI_audio.h"
+// #include "ZuluIDE_config.h"
+#include "ZuluSCSI_log.h"
+#include "ZuluSCSI_platform.h"
+// #include "ide_imagefile.h"
+// #include "ide_atapi.h"
+#include <ZuluI2S.h>
+
+
+extern SdFs SD;
+
+I2S i2s;
+
+static FsFile audio_parent;
+static FsFile audio_file;
+static FsFile *cuesheet_file;
+static CUEParser * g_cue_parser = nullptr;
+static char g_cuesheet[4096];
+// True is using the same filenames for the bin/cue, false if using a directory with multiple bin/wav files
+static bool single_bin_file = false;
+// DMA configuration info
+static dma_channel_config snd_dma_a_cfg;
+static dma_channel_config snd_dma_b_cfg;
+
+// some chonky buffers to store audio samples,
+// output and sample buffers are the same memory
+#define AUDIO_OUT_BUFFER_SIZE (AUDIO_BUFFER_SIZE / 4)
+static uint32_t out_len_a = AUDIO_OUT_BUFFER_SIZE;
+static uint32_t out_len_b = AUDIO_OUT_BUFFER_SIZE;
+static uint32_t * out_len = &out_len_a;
+static uint32_t output_buf_a[AUDIO_OUT_BUFFER_SIZE];
+static uint32_t output_buf_b[AUDIO_OUT_BUFFER_SIZE];
+
+static uint8_t *sample_buf_a = (uint8_t*) output_buf_a;
+static uint8_t *sample_buf_b = (uint8_t*) output_buf_b;
+
+// tracking for the state of the above buffers
+enum bufstate { STALE, FILLING, PROCESSING, READY };
+static volatile bufstate sbufst_a = STALE;
+static volatile bufstate sbufst_b = STALE;
+enum bufselect { A, B };
+static bufselect sbufsel = A;
+
+
+// tracking for audio playback
+static uint8_t audio_owner; // SCSI ID or 0xFF when idle
+static bool audio_idle = true;
+static bool audio_playing = false;
+static volatile bool audio_paused = false;
+static uint64_t fpos;
+static uint32_t fleft;
+static uint64_t gap_length = 0;
+static bool last_track_reached = false;
+static bool within_gap = false;
+static uint32_t gap_read = 0;
+static CUETrackInfo current_track = {0};
+
+// historical playback status information
+static audio_status_code audio_last_status[8] = {ASC_NO_STATUS, ASC_NO_STATUS, ASC_NO_STATUS, ASC_NO_STATUS,
+                                                 ASC_NO_STATUS, ASC_NO_STATUS, ASC_NO_STATUS, ASC_NO_STATUS};
+// volume information for targets
+static volatile uint16_t volumes[8] = {
+    DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH,
+    DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH, DEFAULT_VOLUME_LEVEL_2CH
+};
+
+static volatile uint16_t channel[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
+};
+
+// mechanism for cleanly stopping DMA units
+static volatile bool audio_stopping = false;
+
+/*
+ * I2S format is directly compatible to CD 16-bit audio with left and right channels
+ * The only encoding needed is adjusting the volume and muting if one of the channels
+ * is disabled.
+ */
+static void snd_encode(int16_t* samples, int16_t* output_buf, uint16_t len) {
+    uint8_t vol_r = 0, vol_l = 0;
+    vol_l = (uint8_t)volumes[audio_owner];
+    vol_r = (uint8_t)(volumes[audio_owner] >> 8);
+    uint16_t chn = channel[audio_owner] & AUDIO_CHANNEL_ENABLE_MASK;
+    if (!(chn >> 8))   vol_r = 0;   // right
+    if (!(chn & 0xFF)) vol_l = 0; // left
+    int16_t temp = 0;
+    for (uint16_t i = 0; i < len; i++ )
+    {
+        if (samples == nullptr)
+            output_buf[i] = 0;
+        else
+        {
+            if (i % 2 == 0)
+            {
+                temp = output_buf[i+1];
+                output_buf[i+1] = (int16_t)(((int64_t)samples[i]) * (vol_l) * g_scsi_settings.getSystem()->maxVolume / 25500) ;
+            }
+            else
+            {
+                output_buf[i-1] = (int16_t)(((int64_t)temp) * (vol_r) * g_scsi_settings.getSystem()->maxVolume / 25500);
+            }
+        }
+    }
+}
+
+// functions for passing to Core1
+static void snd_process_a() {
+    snd_encode((int16_t *)(sample_buf_a), (int16_t*)(output_buf_a), AUDIO_BUFFER_SIZE/2);
+    sbufst_a = READY;
+}
+static void snd_process_b() {
+    snd_encode((int16_t *)sample_buf_b, (int16_t*)(output_buf_b), AUDIO_BUFFER_SIZE/2);
+    sbufst_b = READY;
+}
+
+
+
+/**********************************************************************************************
+ * Sets up playback via side effect for last_track_reached, within_gap, fpos and fleft, gap_read
+ * \param start - start of playback in lba
+ * \param length - length of playback in lba
+ * \param continued - true if updating values while audio is being played
+ *                  - false if setting up for the first time
+ **********************************************************************************************/
+static bool setup_playback(uint8_t id, uint32_t start, uint32_t length, bool continued)
+{
+    static uint32_t last_length = 0;
+    static uint32_t last_start = 0;
+    static uint8_t last_track_number = 0;
+
+    if (!continued)
+    {
+        last_start = start;
+        last_length = length;
+        last_track_number = 0;
+    }
+
+    // read in the first track and report errors
+    const CUETrackInfo *find_track_info;
+
+    // Init globals
+    within_gap = false;
+    last_track_reached = false;
+    gap_length = 0;
+    gap_read = 0;
+
+    uint64_t file_size = 0;
+    CUETrackInfo track_info = {0};
+    uint32_t start_of_next_track = 0;
+    int file_index = -1;
+
+    g_cue_parser->restart();
+
+    while ((find_track_info = g_cue_parser->next_track(file_size)) != nullptr )
+    {
+
+        if (!single_bin_file)
+        {
+            // opening the file for getting file size
+            if (find_track_info->file_index != file_index)
+            {
+                if (!(audio_parent.isDir() && audio_file.open(&audio_parent, find_track_info->filename, O_RDONLY)))
+                {
+                    dbgmsg("------ Audio playback - could not open the next track's bin file: ", find_track_info->filename);
+                    audio_file.close();
+                    return false;
+                }
+                file_index = find_track_info->file_index;
+            }
+        }
+        file_size = audio_file.size();
+
+
+        if (continued)
+        {
+            // looking up the next track
+            if (find_track_info->track_number < last_track_number + 1)
+                continue;
+            if (find_track_info->track_number == last_track_number + 1)
+            {
+                // set start to the new track because the last track has finished
+                start = find_track_info->track_start;
+            }
+        }
+
+        if (start < find_track_info->track_start)
+        {
+            // start began in the last track, stop looping
+            start_of_next_track = find_track_info->track_start;
+            break;
+        }
+
+        track_info = *find_track_info;
+
+    }
+
+    if (!single_bin_file)
+    {
+        if (!(audio_parent.isDir() && audio_file.open(&audio_parent, track_info.filename, O_RDONLY)))
+        {
+            dbgmsg("------ Audio playback - could not open the current track's bin file: ", track_info.filename);
+            audio_file.close();
+            return false;
+        }
+    }
+
+    if (find_track_info == nullptr)
+    {
+        // if the loop completed without breaking
+        last_track_reached = true;
+        if (track_info.track_number == 0)
+        {
+            dbgmsg("------ Audio continued playback could not find specified track");
+            return false;
+        }
+    }
+
+    // test if the current or new audio file is open or can be opened
+    if (single_bin_file && !audio_file.isOpen())
+    {
+        dbgmsg("------ Audio playback - CD's bin file is not open");
+        return false;
+    }
+
+    if (track_info.track_mode != CUETrack_AUDIO)
+    {
+        dbgmsg("------ Audio playback - track not CD Audio");
+        return false;
+    }
+
+    if (continued)
+    {
+        // adjust length for new track
+        length = last_length - (start - last_start);
+        last_length = length;
+        last_start = start;
+    }
+    last_track_number = track_info.track_number;
+
+    //  find the offset within the current audio file
+    uint64_t offset = track_info.file_offset;
+    if (start >= track_info.data_start)
+    {
+        // add to the offset the current playback position
+        offset += (start - track_info.data_start) * (uint64_t)track_info.sector_length;
+    }
+    else if (track_info.unstored_pregap_length != 0 && start >= track_info.data_start - track_info.unstored_pregap_length)
+    {
+        // Start is within the pregap position, offset is not increased due to no file data is being played
+        gap_length = (start - track_info.data_start) *(uint64_t) track_info.sector_length;
+        // offset += 0;
+        within_gap = true;
+        gap_read = 0;
+    }
+    else
+    {
+        // Get data from stored pregap (INDEX 0), which is in the file before trackinfo.file_offset.
+        uint32_t seek_back = (track_info.data_start - start) * track_info.sector_length;
+        if (seek_back > offset)
+        {
+            logmsg("WARNING: Host attempted CD read at sector ", start, "+", length,
+                    " pregap request ", (int)seek_back, " exceeded available ", (int)offset, " for track ", track_info.track_number,
+                    " (possible .cue file issue)");
+            offset = 0;
+            return false;
+        }
+        else
+        {
+            offset -= seek_back;
+        }
+    }
+
+    if (start_of_next_track != 0)
+    {
+        // There is a next track
+        if (start + length < start_of_next_track)
+        {
+            // playback ends before the next track
+            if (within_gap)
+                // adjust length unplayed file data within gap
+                fleft = (length - track_info.unstored_pregap_length) * (uint64_t)track_info.sector_length;
+            else
+                fleft = length * (uint64_t)track_info.sector_length;
+
+            last_track_reached = true;
+        }
+        else
+        {
+            // playback continues after this track
+            if (within_gap)
+                fleft = (start_of_next_track - track_info.data_start) * (uint64_t)track_info.sector_length;
+            else
+                fleft = (start_of_next_track - start) * (uint64_t)track_info.sector_length;
+            last_track_reached = false;
+        }
+    }
+    else
+    {
+        // if playback is with current bin file and there are no more tracks
+        volatile uint64_t size_of_playback;
+        volatile uint32_t start_lba = start;
+        size_of_playback = (start_lba + length - track_info.data_start) * (uint64_t)track_info.sector_length ;
+        volatile uint64_t last_track_byte_length = audio_file.size() - track_info.file_offset;
+        if (size_of_playback <= last_track_byte_length)
+        {
+            if (within_gap)
+                fleft = (length - (track_info.data_start - start)) * track_info.sector_length;
+            else
+                fleft = length *  track_info.sector_length;
+            last_track_reached = true;
+        }
+        else
+        {
+            dbgmsg("------ Audio playback - length ", (int) length ,", beyond the last file in cue ");
+            return false;
+        }
+    }
+    current_track = track_info;
+    fpos = offset;
+    return true;
+}
+
+// Allows execution on Core1 via function pointers. Each function can take
+// no parameters and should return nothing, operating via side-effects only.
+static void core1_handler() {
+    while (1) {
+        void (*function)() = (void (*)()) multicore_fifo_pop_blocking();
+        (*function)();
+    }
+}
+
+/* ------------------------------------------------------------------------ */
+/* ---------- VISIBLE FUNCTIONS ------------------------------------------- */
+/* ------------------------------------------------------------------------ */
+extern "C"
+{
+void audio_dma_irq() {
+    // Using dma irq raw register access, because the 2.1.0 pico-sdk function seem to cause issues
+    if (dma_hw->intr & (1 << SOUND_DMA_CHA)) {
+        dma_hw->ints2 = (1 << SOUND_DMA_CHA);
+        sbufst_a = STALE;
+        if (audio_stopping) {
+            channel_config_set_chain_to(&snd_dma_a_cfg, SOUND_DMA_CHA);
+        }
+        dma_channel_configure(SOUND_DMA_CHA,
+                &snd_dma_a_cfg,
+                i2s.getPioFIFOAddr(),
+                output_buf_a,
+                out_len_a / 4,
+                false);
+    } else if (dma_hw->intr & (1 << SOUND_DMA_CHB)) {
+        dma_hw->ints2 = (1 <<  SOUND_DMA_CHB);
+        sbufst_b = STALE;
+        if (audio_stopping) {
+            channel_config_set_chain_to(&snd_dma_b_cfg, SOUND_DMA_CHB);
+        }
+        dma_channel_configure(SOUND_DMA_CHB,
+                &snd_dma_b_cfg,
+                i2s.getPioFIFOAddr(),
+                output_buf_b,
+                out_len_b / 4,
+                false);
+    }
+}
+}
+bool audio_is_active() {
+    return !audio_idle;
+}
+
+bool audio_is_playing(uint8_t id) {
+//    return audio_playing;
+    return audio_owner == (id & 7) && audio_playing;
+}
+
+void audio_setup() {
+    // setup GPIOs
+    pio_gpio_init(I2S_PIO_HW, GPIO_I2S_BCLK);
+    pio_gpio_init(I2S_PIO_HW, GPIO_I2S_WS);
+    pio_gpio_init(I2S_PIO_HW, GPIO_I2S_DOUT);
+
+    // setup Arduino-Pico I2S library
+    i2s.setBCLK(GPIO_I2S_BCLK);
+    i2s.setDATA(GPIO_I2S_DOUT);
+    i2s.setBitsPerSample(16);
+    i2s.setDivider(g_zuluscsi_timings->audio.clk_div_pio, 0);
+    i2s.begin(I2S_PIO_HW, I2S_PIO_SM);
+    dma_channel_claim(SOUND_DMA_CHA);
+	dma_channel_claim(SOUND_DMA_CHB);
+
+    irq_set_exclusive_handler(I2S_DMA_IRQ_NUM, audio_dma_irq);
+    irq_set_enabled(I2S_DMA_IRQ_NUM, true);
+    irq_clear(I2S_DMA_IRQ_NUM);
+
+    logmsg("Starting Core1 for audio");
+    multicore_launch_core1(core1_handler);
+}
+
+
+void audio_poll() {
+    if (audio_idle) return;
+
+    static bool set_pause_buf = true;
+    if (audio_paused)
+    {
+        if (set_pause_buf)
+        {
+            memset(output_buf_a, 0, sizeof(output_buf_a));
+            memset(output_buf_b, 0, sizeof(output_buf_b));
+        }
+        set_pause_buf = false;
+        return;
+    }
+    set_pause_buf = true;
+
+
+    if (last_track_reached && fleft == 0 && sbufst_a == STALE && sbufst_b == STALE) {
+        // out of data and ready to stop
+        audio_stop(audio_owner);
+        return;
+    } else if (last_track_reached && 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 (fleft == 0)
+    {
+        if (!setup_playback(audio_owner, 0, 0, true))
+        {
+            dbgmsg("------ Playback stopped because of error loading next track");
+            audio_stop(audio_owner);
+            return;
+        }
+    }
+
+    // are new audio samples needed from the memory card?
+    uint8_t* audiobuf;
+    if (sbufst_a == STALE) {
+        sbufst_a = FILLING;
+        audiobuf = sample_buf_a;
+        out_len = &out_len_a;
+    } else if (sbufst_b == STALE) {
+        sbufst_b = FILLING;
+        audiobuf = sample_buf_b;
+        out_len = &out_len_b;
+    } else {
+        // no data needed this time
+        return;
+    }
+
+
+    platform_set_sd_callback(NULL, NULL);
+    uint16_t toRead = AUDIO_BUFFER_SIZE;
+    uint16_t gap_to_read = AUDIO_BUFFER_SIZE;
+    if (within_gap)
+    {
+        if (gap_length < gap_to_read) gap_to_read = gap_length;
+        memset(audiobuf, 0, AUDIO_BUFFER_SIZE);
+        gap_read += gap_to_read;
+        *out_len = gap_to_read;
+        if (gap_read >= gap_length)
+        {
+            within_gap = false;
+            gap_read = 0;
+            gap_length = 0;
+        }
+    }
+    else
+    {
+        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");
+            if (!audio_file.seek(fpos)) {
+                logmsg("------ Audio error, unable to seek to ", fpos);
+            }
+        }
+        if (audio_file.read(audiobuf, toRead) != toRead) {
+            logmsg("------ Audio sample data read error");
+        }
+        *out_len = toRead;
+        fpos += toRead;
+        fleft -= toRead;
+    }
+
+
+    if (sbufst_a == FILLING) {
+        sbufst_a = PROCESSING;
+        multicore_fifo_push_blocking((uintptr_t) &snd_process_a);
+    } else if (sbufst_b == FILLING) {
+        sbufst_b = PROCESSING;
+        multicore_fifo_push_blocking((uintptr_t) &snd_process_b);
+    }
+}
+
+bool audio_play(uint8_t owner, image_config_t* img, uint32_t start, uint32_t length, bool swap) {
+    // Per Annex C terminate playback immediately if already in progress on
+    // the current target. Non-current targets may also get their audio
+    // interrupted later due to hardware limitations
+    // stop any existing playback first
+     if (!audio_idle)
+        audio_stop(audio_owner);
+
+    if(!img->cuesheetfile && img->cuesheetfile.isOpen())
+    {
+        logmsg("Error attempting to play CD Audio with no cue/bin image(s)");
+        return false;
+    }
+
+    if (img->bin_container.isOpen() && img->bin_container.isDir())
+    {
+        audio_parent.close();
+        audio_file.close();
+        audio_parent = img->bin_container;
+        single_bin_file = false;
+    }
+    else if (img->bin_container.isOpen())
+    {
+        audio_parent.close();
+        audio_file.close();
+        audio_file = img->bin_container;
+        single_bin_file = true;
+    }
+    else
+        return false;
+
+    if (&img->cuesheetfile != cuesheet_file)
+    {
+        cuesheet_file = &img->cuesheetfile;
+        cuesheet_file->seek(0);
+        cuesheet_file->read(g_cuesheet, sizeof(g_cuesheet));
+        delete g_cue_parser;
+        g_cue_parser = new CUEParser(g_cuesheet);
+    }
+    // dbgmsg("Request to play ('", file, "':", start, ":", end, ")");
+
+    // verify audio file is present and inputs are (somewhat) sane
+    platform_set_sd_callback(NULL, NULL);
+
+    // truncate playback end to end of file
+    // we will not consider this to be an error at the moment
+    // \todo reimplement
+    // if (end > len) {
+    //     dbgmsg("------ Truncate audio play request end ", end, " to file size ", len);
+    //     end = len;
+    //
+    audio_owner = owner;
+    if(!setup_playback(owner, start, length, false))
+        return false;
+
+    if (length == 0)
+    {
+        audio_last_status[owner] = ASC_NO_STATUS;
+        audio_paused = false;
+        audio_playing = false;
+        audio_idle = true;
+        return true;
+    }
+
+    audio_last_status[owner] = ASC_PLAYING;
+    audio_paused = false;
+    audio_playing = true;
+    audio_idle = false;
+
+    // read in initial sample buffers
+    if (within_gap)
+    {
+        sbufst_a = READY;
+        sbufst_b = READY;
+        memset(output_buf_a, 0, sizeof(output_buf_a));
+        memset(output_buf_b, 0, sizeof(output_buf_b));
+    }
+    else
+    {
+        sbufst_a = STALE;
+        sbufst_b = STALE;
+        sbufsel = B;
+        audio_poll();
+        sbufsel = A;
+        audio_poll();
+    }
+    // setup the two DMA units to hand-off to each other
+    // to maintain a stable bitstream these need to run without interruption
+	snd_dma_a_cfg = dma_channel_get_default_config(SOUND_DMA_CHA);
+	channel_config_set_transfer_data_size(&snd_dma_a_cfg, DMA_SIZE_32);
+	channel_config_set_dreq(&snd_dma_a_cfg, i2s.getPioDreq());
+	channel_config_set_read_increment(&snd_dma_a_cfg, true);
+	channel_config_set_chain_to(&snd_dma_a_cfg, SOUND_DMA_CHB);
+    channel_config_set_high_priority(&snd_dma_a_cfg, true);
+	dma_channel_configure(SOUND_DMA_CHA, &snd_dma_a_cfg, i2s.getPioFIFOAddr(),
+			output_buf_a, AUDIO_OUT_BUFFER_SIZE, false);
+    hw_set_bits(&dma_hw->inte2, 1 << SOUND_DMA_CHA );
+    // dma_irqn_set_channel_enabled(I2S_DMA_IRQ_NUM, SOUND_DMA_CHA, true);
+    // dma_channel_set_irq0_enabled(SOUND_DMA_CHA, true);
+	snd_dma_b_cfg = dma_channel_get_default_config(SOUND_DMA_CHB);
+	channel_config_set_transfer_data_size(&snd_dma_b_cfg, DMA_SIZE_32);
+	channel_config_set_dreq(&snd_dma_b_cfg, i2s.getPioDreq());
+	channel_config_set_read_increment(&snd_dma_b_cfg, true);
+	channel_config_set_chain_to(&snd_dma_b_cfg, SOUND_DMA_CHA);
+    channel_config_set_high_priority(&snd_dma_b_cfg, true);
+	dma_channel_configure(SOUND_DMA_CHB, &snd_dma_b_cfg, i2s.getPioFIFOAddr(),
+			output_buf_b, AUDIO_OUT_BUFFER_SIZE, false);
+    hw_set_bits(&dma_hw->inte2, 1 << SOUND_DMA_CHB );
+    // dma_irqn_set_channel_enabled(I2S_DMA_IRQ_NUM, SOUND_DMA_CHB, true);
+    // dma_channel_set_irq0_enabled(SOUND_DMA_CHB, true);
+    // ready to go
+    dma_channel_start(SOUND_DMA_CHA);
+    return true;
+}
+
+bool audio_set_paused(uint8_t id, bool paused) {
+    if (audio_idle) return false;
+    else if (audio_paused && paused) return false;
+    else if (!audio_paused && !paused) return false;
+
+    audio_paused = paused;
+
+    if (paused) {
+        audio_last_status[id] = ASC_PAUSED;
+    } else {
+        audio_last_status[id] = ASC_PLAYING;
+    }
+    return true;
+}
+
+void audio_stop(uint8_t id) {
+    if (audio_idle || (id & 7) != audio_owner) return;
+
+    memset(&current_track, 0, sizeof(current_track));
+    memset(output_buf_a, 0, sizeof(output_buf_a));
+    memset(output_buf_b, 0, sizeof(output_buf_b));
+
+    // then indicate that the streams should no longer chain to one another
+    // and wait for them to shut down naturally
+    audio_stopping = true;
+    while (dma_channel_is_busy(SOUND_DMA_CHA)) tight_loop_contents();
+    while (dma_channel_is_busy(SOUND_DMA_CHB)) tight_loop_contents();
+    // \todo check if I2S pio is done
+    // The way to check is the I2S pio is done would be to check
+    // if the fifo is empty and the PIO's program counter is at the first instruction
+    // while (spi_is_busy(AUDIO_SPI)) tight_loop_contents();
+    audio_stopping = false;
+    dma_channel_abort(SOUND_DMA_CHA);
+    dma_channel_abort(SOUND_DMA_CHB);
+    // idle the subsystem
+    audio_last_status[id] = ASC_COMPLETED;
+    audio_paused = false;
+    audio_playing = false;
+    audio_idle = true;
+    audio_file.close();
+}
+
+audio_status_code audio_get_status_code(uint8_t id) {
+    audio_status_code tmp = audio_last_status[id];
+    if (tmp == ASC_COMPLETED || tmp == ASC_ERRORED) {
+        audio_last_status[id] = ASC_NO_STATUS;
+    }
+    return tmp;
+}
+
+uint16_t audio_get_volume(uint8_t id) {
+    return volumes[id];
+}
+
+void audio_set_volume(uint8_t id, uint16_t vol)
+{
+    volumes[id] = vol;
+}
+
+uint16_t audio_get_channel(uint8_t id) {
+    return channel[id];
+}
+
+void audio_set_channel(uint8_t id, uint16_t chn) {
+    channel[id] = chn;
+}
+
+uint32_t audio_get_lba_position()
+{
+    if (current_track.track_number != 0 && audio_file.isOpen())
+    {
+        // We need the file position from the start of the track,
+        // current_track.file_offset equivalent to data_start (index 1 in cue file)
+        // index0_offset is the adjustment to current_track.file_offset
+        // to make it equivalent to current_track.track_start (index 0 in cue file)
+        uint64_t index0_offset = (current_track.data_start -  current_track.track_start) * current_track.sector_length;
+        return current_track.track_start + (audio_file.position() - (current_track.file_offset - index0_offset)) / current_track.sector_length;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
+void audio_set_cue_parser(CUEParser *cue_parser, FsFile* file)
+{
+    g_cue_parser = cue_parser;
+    if (file != nullptr)
+    {
+        char filename[MAX_FILE_PATH] = {0};
+        if (file->isFile())
+        {
+            file->getName(filename, sizeof(filename));
+            audio_file.open(filename, O_RDONLY);
+            single_bin_file = true;
+        }
+        else if (file->isDir())
+        {
+            file->getName(filename, sizeof(filename));
+            audio_parent.open(filename, O_RDONLY);
+            single_bin_file = false;
+        }
+    }
+}
+
+uint64_t audio_get_file_position()
+{
+    return fpos;
+}
+
+void audio_set_file_position(uint8_t id, uint32_t lba)
+{
+    setup_playback(id, lba, 0, false);
+}
+
+
+#endif // ENABLE_AUDIO_OUTPUT_SPDIF

+ 56 - 0
lib/ZuluSCSI_platform_RP2MCU/audio_i2s.h

@@ -0,0 +1,56 @@
+/**
+ * Copyright (C) 2023 saybur
+ * Copyright (C) 2024 Rabbit Hole Computing LLC
+ * 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_I2S
+
+#include <stdint.h>
+
+
+// i2s PIO settings
+#define I2S_PIO_HW pio2_hw
+#define I2S_PIO_SM 0
+
+// audio subsystem DMA channels
+#define SOUND_DMA_CHA 6
+#define SOUND_DMA_CHB 7
+
+// size of the two audio sample buffers, in bytes
+// #define AUDIO_BUFFER_SIZE 8192 // reduce memory usage
+#define AUDIO_BUFFER_SIZE 2352 * 12
+/**
+ * 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();
+
+
+extern "C" void audio_dma_irq();
+#endif // ENABLE_AUDIO_OUTPUT_SPDIF

+ 10 - 10
lib/ZuluSCSI_platform_RP2MCU/audio.cpp → lib/ZuluSCSI_platform_RP2MCU/audio_spdif.cpp

@@ -15,7 +15,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 **/
 
-#ifdef ENABLE_AUDIO_OUTPUT
+#ifdef ENABLE_AUDIO_OUTPUT_SPDIF
 
 #include <SdFat.h>
 #include <stdbool.h>
@@ -23,7 +23,7 @@
 #include <hardware/irq.h>
 #include <hardware/spi.h>
 #include <pico/multicore.h>
-#include "audio.h"
+#include "audio_spdif.h"
 #include "ZuluSCSI_audio.h"
 #include "ZuluSCSI_config.h"
 #include "ZuluSCSI_log.h"
@@ -32,7 +32,7 @@
 extern SdFs SD;
 
 // Table with the number of '1' bits for each index.
-// Used for SP/DIF parity calculations.
+// Used for S/PDIF parity calculations.
 // Placed in SRAM5 for the second core to use with reduced contention.
 const uint8_t snd_parity[256] __attribute__((aligned(256), section(".scratch_y.snd_parity"))) = {
     0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 
@@ -99,7 +99,7 @@ const uint16_t biphase[256] __attribute__((aligned(512), section(".scratch_y.bip
     0xCCAA, 0xB355, 0xD355, 0xACAA, 0xCB55, 0xB4AA, 0xD4AA, 0xAB55,
     0xCD55, 0xB2AA, 0xD2AA, 0xAD55, 0xCAAA, 0xB555, 0xD555, 0xAAAA };
 /*
- * Biphase frame headers for SP/DIF, including the special bit framing
+ * Biphase frame headers for S/PDIF, including the special bit framing
  * errors used to detect (sub)frame start conditions. See above table
  * for details.
  */
@@ -159,7 +159,7 @@ static uint8_t invert = 0; // biphase encode help: set if last wire bit was '1'
 
 /*
  * Translates 16-bit stereo sound samples to biphase wire patterns for the
- * SPI peripheral. Produces 8 patterns (128 bits, or 1 SP/DIF frame) per pair
+ * SPI peripheral. Produces 8 patterns (128 bits, or 1 S/PDIF frame) per pair
  * of input samples. Provided length is the total number of sample bytes present,
  * _twice_ the number of samples (little-endian order assumed)
  * 
@@ -355,7 +355,7 @@ bool audio_is_playing(uint8_t id) {
 }
 
 void audio_setup() {
-    // setup SPI to blast SP/DIF data over the TX pin
+    // setup SPI to blast S/PDIF data over the TX pin
     spi_set_baudrate(AUDIO_SPI, 5644800); // will be slightly wrong, ~0.03% slow
     hw_write_masked(&spi_get_hw(AUDIO_SPI)->cr0,
             0x1F, // TI mode with 16 bits
@@ -425,7 +425,7 @@ void audio_poll() {
     }
 }
 
-bool audio_play(uint8_t owner, ImageBackingStore* img, uint64_t start, uint64_t end, bool swap) {
+bool audio_play(uint8_t owner, image_config_t* img, uint64_t start, uint64_t end, bool swap) {
     // stop any existing playback first
     if (audio_is_active()) audio_stop(audio_owner);
 
@@ -441,7 +441,7 @@ bool audio_play(uint8_t owner, ImageBackingStore* img, uint64_t start, uint64_t
         return false;
     }
     platform_set_sd_callback(NULL, NULL);
-    audio_file = img;
+    audio_file = &img->file;
     if (!audio_file->isOpen()) {
         logmsg("File not open for audio playback, ", owner);
         return false;
@@ -590,9 +590,9 @@ uint64_t audio_get_file_position()
     return fpos;
 }
 
-void audio_set_file_position(uint32_t lba)
+void audio_set_file_position(uint8_t id, uint32_t lba)
 {
     fpos = 2352 * (uint64_t)lba;
 
 }
-#endif // ENABLE_AUDIO_OUTPUT
+#endif // ENABLE_AUDIO_OUTPUT_SPDIF

+ 2 - 2
lib/ZuluSCSI_platform_RP2MCU/audio.h → lib/ZuluSCSI_platform_RP2MCU/audio_spdif.h

@@ -16,7 +16,7 @@
 **/
 
 #pragma once
-#ifdef ENABLE_AUDIO_OUTPUT
+#ifdef ENABLE_AUDIO_OUTPUT_SPDIF
 
 #include <stdint.h>
 
@@ -60,4 +60,4 @@ void audio_setup();
  */
 void audio_poll();
 
-#endif // ENABLE_AUDIO_OUTPUT
+#endif // ENABLE_AUDIO_OUTPUT_SPDIF

+ 15 - 5
lib/ZuluSCSI_platform_RP2MCU/custom_timings.cpp

@@ -45,6 +45,7 @@ bool CustomTimings::set_timings_from_file()
     const char scsi_10_section[] = "scsi_10";
     const char scsi_5_section[] = "scsi_5";
     const char sdio_section[] = "sdio";
+    const char audio_section[] = "audio";
 
     // pll
     int32_t vco = ini_getl(pll_section, "vco_freq_hz", g_zuluscsi_timings->pll.vco_freq, CUSTOM_TIMINGS_FILE);
@@ -53,9 +54,9 @@ bool CustomTimings::set_timings_from_file()
 
     if (vco > 0 && post_div1 > 0 && post_div2 > 0)
     {
-        if (vco / post_div1 / post_div2 > 250000000)
+        if (vco / post_div1 / post_div2 > 252000000)
         {
-            logmsg("Reclocking over 250MHz with the PLL settings is not allowed using ", CUSTOM_TIMINGS_FILE);
+            logmsg("Reclocking over 252MHz with the PLL settings is not allowed using ", CUSTOM_TIMINGS_FILE);
             return false;
         }
     }
@@ -94,20 +95,26 @@ bool CustomTimings::set_timings_from_file()
     // scsi 20
     g_zuluscsi_timings->scsi_20.delay0 = ini_getl(scsi_20_section, "delay0_cc", g_zuluscsi_timings->scsi_20.delay0, CUSTOM_TIMINGS_FILE);
     g_zuluscsi_timings->scsi_20.delay1 = ini_getl(scsi_20_section, "delay1_cc", g_zuluscsi_timings->scsi_20.delay1, CUSTOM_TIMINGS_FILE);
-    g_zuluscsi_timings->scsi_20.total_delay_adjust = ini_getl(scsi_20_section, "total_delay_adjust_cc", g_zuluscsi_timings->scsi_20.total_delay_adjust, CUSTOM_TIMINGS_FILE);
+    g_zuluscsi_timings->scsi_20.total_period_adjust = ini_getl(scsi_20_section, "total_period_adjust_cc", g_zuluscsi_timings->scsi_20.total_period_adjust, CUSTOM_TIMINGS_FILE);
     g_zuluscsi_timings->scsi_20.max_sync = ini_getl(scsi_20_section, "max_sync", g_zuluscsi_timings->scsi_20.max_sync, CUSTOM_TIMINGS_FILE);
+    g_zuluscsi_timings->scsi_20.rdelay1 = ini_getl(scsi_20_section, "read_delay1_cc", g_zuluscsi_timings->scsi_20.rdelay1, CUSTOM_TIMINGS_FILE);
+    g_zuluscsi_timings->scsi_20.rtotal_period_adjust = ini_getl(scsi_20_section, "read_total_period_adjust_cc", g_zuluscsi_timings->scsi_20.rtotal_period_adjust, CUSTOM_TIMINGS_FILE);
 
     // scsi 10
     g_zuluscsi_timings->scsi_10.delay0 = ini_getl(scsi_10_section, "delay0_cc", g_zuluscsi_timings->scsi_10.delay0, CUSTOM_TIMINGS_FILE);
     g_zuluscsi_timings->scsi_10.delay1 = ini_getl(scsi_10_section, "delay1_cc", g_zuluscsi_timings->scsi_10.delay1, CUSTOM_TIMINGS_FILE);
-    g_zuluscsi_timings->scsi_10.total_delay_adjust = ini_getl(scsi_10_section, "total_delay_adjust_cc", g_zuluscsi_timings->scsi_10.total_delay_adjust, CUSTOM_TIMINGS_FILE);
+    g_zuluscsi_timings->scsi_10.total_period_adjust = ini_getl(scsi_10_section, "total_period_adjust_cc", g_zuluscsi_timings->scsi_10.total_period_adjust, CUSTOM_TIMINGS_FILE);
     g_zuluscsi_timings->scsi_10.max_sync = ini_getl(scsi_10_section, "max_sync", g_zuluscsi_timings->scsi_10.max_sync, CUSTOM_TIMINGS_FILE);
+    g_zuluscsi_timings->scsi_10.rdelay1 = ini_getl(scsi_10_section, "read_delay1_cc", g_zuluscsi_timings->scsi_10.rdelay1, CUSTOM_TIMINGS_FILE);
+    g_zuluscsi_timings->scsi_10.rtotal_period_adjust = ini_getl(scsi_10_section, "read_total_period_adjust_cc", g_zuluscsi_timings->scsi_10.rtotal_period_adjust, CUSTOM_TIMINGS_FILE);
 
     // scsi 5
     g_zuluscsi_timings->scsi_5.delay0 = ini_getl(scsi_5_section, "delay0_cc", g_zuluscsi_timings->scsi_5.delay0, CUSTOM_TIMINGS_FILE);
     g_zuluscsi_timings->scsi_5.delay1 = ini_getl(scsi_5_section, "delay1_cc", g_zuluscsi_timings->scsi_5.delay1, CUSTOM_TIMINGS_FILE);
-    g_zuluscsi_timings->scsi_5.total_delay_adjust = ini_getl(scsi_5_section, "total_delay_adjust_cc", g_zuluscsi_timings->scsi_5.total_delay_adjust, CUSTOM_TIMINGS_FILE);
+    g_zuluscsi_timings->scsi_5.total_period_adjust = ini_getl(scsi_5_section, "total_period_adjust_cc", g_zuluscsi_timings->scsi_5.total_period_adjust, CUSTOM_TIMINGS_FILE);
     g_zuluscsi_timings->scsi_5.max_sync = ini_getl(scsi_5_section, "max_sync", g_zuluscsi_timings->scsi_5.max_sync, CUSTOM_TIMINGS_FILE);
+    g_zuluscsi_timings->scsi_5.rdelay1 = ini_getl(scsi_5_section, "read_delay1_cc", g_zuluscsi_timings->scsi_5.rdelay1, CUSTOM_TIMINGS_FILE);
+    g_zuluscsi_timings->scsi_5.rtotal_period_adjust = ini_getl(scsi_5_section, "read_total_period_adjust_cc", g_zuluscsi_timings->scsi_5.rtotal_period_adjust, CUSTOM_TIMINGS_FILE);
 
     // sdio
     g_zuluscsi_timings->sdio.clk_div_pio = ini_getl(sdio_section, "clk_div_pio", g_zuluscsi_timings->sdio.clk_div_pio, CUSTOM_TIMINGS_FILE);
@@ -115,5 +122,8 @@ bool CustomTimings::set_timings_from_file()
     g_zuluscsi_timings->sdio.delay0 = ini_getl(sdio_section, "delay0", g_zuluscsi_timings->sdio.delay0, CUSTOM_TIMINGS_FILE);
     g_zuluscsi_timings->sdio.delay1 = ini_getl(sdio_section, "delay1", g_zuluscsi_timings->sdio.delay1, CUSTOM_TIMINGS_FILE);
 
+    // audio
+    g_zuluscsi_timings->audio.clk_div_pio = ini_getl(audio_section, "clk_div_pio", g_zuluscsi_timings->audio.clk_div_pio, CUSTOM_TIMINGS_FILE);
+    g_zuluscsi_timings->audio.audio_clocked = ini_getbool(audio_section, "clk_for_audio", g_zuluscsi_timings->audio.audio_clocked, CUSTOM_TIMINGS_FILE);
     return true;
 }

+ 1 - 1
lib/ZuluSCSI_platform_RP2MCU/library.json

@@ -9,6 +9,6 @@
     "platforms": "*",
     "dependencies":
     {
-        "CUEParser": "https://github.com/rabbitholecomputing/CUEParser"
+        "CUEParser": "https://github.com/rabbitholecomputing/CUEParser#v2025.02.25"
     }
 }

+ 2 - 0
lib/ZuluSCSI_platform_RP2MCU/rp2040-template.ld

@@ -97,6 +97,8 @@ SECTIONS
         /* RP2040 breakpoints in RAM code don't always work very well
          * because the boot routine tends to overwrite them.
          * Uncommenting this line puts all code in flash.
+         * You may have to delete "firmware.elf" for the next build
+         * to use this linker file's changes
          */
         /* *(.text .text*) */
     } > FLASH

+ 18 - 24
lib/ZuluSCSI_platform_RP2MCU/rp23xx-template.ld

@@ -73,7 +73,18 @@ SECTIONS
          * FLASH ... we will include any thing excluded here in .data below by default */
         *(.init)
         *libgcc.a:cmse_nonsecure_call.o
-        *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*)
+
+/* =============================================================== */
+    /* Exclude as much code from flash as possible as the RP2350 series has twice as much SRAM */
+        *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a: *libZuluIDE_platform_RPMCU.a:  *libSdFat.a: *libSCSI2SD.a: *CUEParser.a: *minIni.a: *.cpp.o) .text*)
+
+    /* ---------------------------------------------------------
+    Uncomment the EXCLUDE_FILE line below and comment the EXCLUDE_FILE line above for debug to work properly,
+    the initial break point to main seems to get clobbered when everything is in SRAM.
+    You may have to delete the "firmware.elf" file so the next build links with newly modified linker script
+    ------------------------------------------------------------*/
+        /* *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .text*) */
+/* =============================================================== */
         *(.fini)
         /* Pull all c'tors into .text */
         *crtbegin.o(.ctors)
@@ -112,28 +123,6 @@ SECTIONS
         *(.eh_frame*)
         . = ALIGN(4);
 
-               /* Put only non-timecritical code in flash
-         * This includes e.g. floating point math routines.
-         */
-        .pio/build/$project_name/src/ZuluSCSI_log.cpp.o(.text .text*)
-        .pio/build/$project_name/src/ZuluSCSI_log_trace.cpp.o(.text .text*)
-        .pio/build/$project_name/src/ZuluSCSI_settings.cpp.o(.text .text*)
-        .pio/build/$project_name/src/QuirksCheck.cpp.o(.text .text*)
-        *libZuluSCSI_platform_RP2350.a:ZuluSCSI_platform.cpp.o(.text .text*)
-        *libm*:(.text .text*)
-        *libc*:(.text .text*)
-        *libgcc*:*df*(.text .text*)
-        *USB*(.text .text*)
-        *SPI*(.text .text*)
-        *Spi*(.text .text*)
-        *spi*(.text .text*)
-        *stdc*:(.text .text*)
-        *supc*:(.text .text*)
-        *nosys*:(.text .text*)
-        *libc*:*printf*(.text .text*)
-        *libc*:*toa*(.text .text*)
-        *libminIni.a:(.text .text*)
-        *libCUEParser.a:(.text .text*)
     } > FLASH
 
     /* Note the boot2 section is optional, and should be discarded if there is
@@ -158,7 +147,12 @@ SECTIONS
         "ERROR: Pico second stage bootloader must be no more than 256 bytes in size")
 
     .rodata : {
-        *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*)
+        /* Exclude as many constants as possible from flash as corresponding to the code that has been removed from flash
+            to keep the MCU from having to hit flash while it is executing in SRAM */
+        *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:  *libZuluIDE_platform_RPMCU.a:  *libSdFat.a: *libSCSI2SD.a: *CUEParser.a: *minIni.a: *.cpp.o) .rodata*)
+/* Uncomment below and comment above lines for debugging */
+        /* *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a:) .rodata*) */
+
         *(.srodata*)
         . = ALIGN(4);
         *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))

+ 3 - 14
lib/ZuluSCSI_platform_RP2MCU/run_pioasm.sh

@@ -1,17 +1,6 @@
 #!/bin/bash
 
 # This script regenerates the .pio.h files from .pio
-pioasm sdio_RP2040.pio sdio_RP2040.pio.h
-pioasm sdio_Pico.pio sdio_Pico.pio.h
-pioasm sdio.RP2350.pio sdio.RP2350.pio.h
-pioasm sdio.Pico_2.pio sdio.Pico_2.h
-
-pioasm scsi_accel_target_RP2040.pio scsi_accel_target_RP2040.pio.h
-pioasm scsi_accel_target_Pico.pio scsi_accel_target_Pico.pio.h
-pioasm scsi_accel_target_RP2350A.pio scsi_accel_target_RP2350A.pio.h
-pioasm scsi_accel_target_Pico_2.pio scsi_accel_target_Pico_2.pio.h
-
-pioasm scsi_accel_host_RP2040.pio scsi_accel_host_RP2040.pio.h
-pioasm scsi_accel_host_Pico.pio scsi_accel_host_Pico.pio.h
-pioasm scsi_accel_host_RP2350A.pio scsi_accel_host_RP2350A.pio.h
-pioasm scsi_accel_host_Pico_2.pio scsi_accel_host_Pico_2.pio.h
+pioasm sdio_RP2MCU.pio sdio_RP2MCU.pio.h
+pioasm scsi_accel_target_RP2MCU.pio scsi_accel_target_RP2MCU.pio.h
+pioasm scsi_accel_host_RP2MCU.pio scsi_accel_host_RP2MCU.pio.h

+ 2 - 2
lib/ZuluSCSI_platform_RP2MCU/scsi2sd_timings.c

@@ -20,11 +20,11 @@
 **/
 #include "timings.h"
 #if defined(ZULUSCSI_MCU_RP23XX)
-uint8_t g_max_sync_20_period = 18;
+uint8_t g_max_sync_20_period = 12;
 uint8_t g_max_sync_10_period = 25;
 uint8_t g_max_sync_5_period  = 50; 
 #elif defined(ZULUSCSI_MCU_RP20XX)
-uint8_t g_max_sync_20_period = 25;
+uint8_t g_max_sync_20_period = 12;
 uint8_t g_max_sync_10_period = 25;
 uint8_t g_max_sync_5_period  = 50; 
 #endif

+ 1 - 1
lib/ZuluSCSI_platform_RP2MCU/scsiHostPhy.cpp

@@ -212,7 +212,7 @@ static inline uint8_t scsiHostReadOneByte(int* parityError)
     SCSIHOST_WAIT_INACTIVE(REQ);
     SCSI_OUT(ACK, 0);
 
-    if (parityError && r != (g_scsi_parity_lookup[r & 0xFF] ^ SCSI_IO_DATA_MASK))
+    if (parityError && r != (g_scsi_parity_lookup[r & 0xFF] ^ (SCSI_IO_DATA_MASK >> SCSI_IO_SHIFT)))
     {
         logmsg("Parity error in scsiReadOneByte(): ", (uint32_t)r);
         *parityError = 1;

+ 5 - 1
lib/ZuluSCSI_platform_RP2MCU/scsiPhy.cpp

@@ -149,7 +149,11 @@ static void scsiPhyIRQ(uint gpio, uint32_t events)
     {
         // Note BSY / SEL interrupts only when we are not driving OUT_BSY low ourselves.
         // The BSY input pin may be shared with other signals.
+#if SCSI_OUT_BSY > 31
+        if (sio_hw->gpio_hi_out & (1 << (SCSI_OUT_BSY - 32)))
+#else
         if (sio_hw->gpio_out & (1 << SCSI_OUT_BSY))
+#endif
         {
             scsi_bsy_deassert_interrupt();
         }
@@ -374,7 +378,7 @@ static inline uint8_t scsiReadOneByte(int* parityError)
     SCSI_OUT(REQ, 0);
     SCSI_WAIT_INACTIVE(ACK);
 
-    if (parityError && r != (g_scsi_parity_lookup[r & 0xFF] ^ SCSI_IO_DATA_MASK))
+    if (parityError && r != (g_scsi_parity_lookup[r & 0xFF] ^ (SCSI_IO_DATA_MASK >> SCSI_IO_SHIFT)))
     {
         logmsg("Parity error in scsiReadOneByte(): ", (uint32_t)r);
         *parityError = 1;

+ 52 - 39
lib/ZuluSCSI_platform_RP2MCU/scsi_accel_target.cpp

@@ -41,9 +41,9 @@
 #include <hardware/sync.h>
 #include <pico/multicore.h>
 
-#ifdef ENABLE_AUDIO_OUTPUT
-#include <audio.h>
-#endif // ENABLE_AUDIO_OUTPUT
+#ifdef ENABLE_AUDIO_OUTPUT_SPDIF
+#include "audio_spdif.h"
+#endif // ENABLE_AUDIO_OUTPUT_SPDIF
 
 #include "scsi_accel_target_RP2MCU.pio.h"
 
@@ -893,10 +893,10 @@ static int pio_add_scsi_sync_write_program()
 
 static void scsi_dma_irq()
 {
-#ifndef ENABLE_AUDIO_OUTPUT
+#ifndef ENABLE_AUDIO_OUTPUT_SPDIF
     dma_hw->ints0 = (1 << SCSI_DMA_CH_A);
 #else
-    // see audio.h for whats going on here
+    // see audio_spdif.h for whats going on here
     if (dma_hw->intr & (1 << SCSI_DMA_CH_A)) {
         dma_hw->ints0 = (1 << SCSI_DMA_CH_A);
     } else {
@@ -941,16 +941,16 @@ static void scsidma_config_gpio()
         pio_sm_set_consecutive_pindirs(SCSI_DMA_PIO, SCSI_DATA_SM, SCSI_IO_DB0, 9, true);
         pio_sm_set_consecutive_pindirs(SCSI_DMA_PIO, SCSI_DATA_SM, SCSI_OUT_REQ, 1, true);
 
-        iobank0_hw->io[SCSI_IO_DB0].ctrl  = GPIO_FUNC_PIO0;
-        iobank0_hw->io[SCSI_IO_DB1].ctrl  = GPIO_FUNC_PIO0;
-        iobank0_hw->io[SCSI_IO_DB2].ctrl  = GPIO_FUNC_PIO0;
-        iobank0_hw->io[SCSI_IO_DB3].ctrl  = GPIO_FUNC_PIO0;
-        iobank0_hw->io[SCSI_IO_DB4].ctrl  = GPIO_FUNC_PIO0;
-        iobank0_hw->io[SCSI_IO_DB5].ctrl  = GPIO_FUNC_PIO0;
-        iobank0_hw->io[SCSI_IO_DB6].ctrl  = GPIO_FUNC_PIO0;
-        iobank0_hw->io[SCSI_IO_DB7].ctrl  = GPIO_FUNC_PIO0;
-        iobank0_hw->io[SCSI_IO_DBP].ctrl  = GPIO_FUNC_PIO0;
-        iobank0_hw->io[SCSI_OUT_REQ].ctrl = GPIO_FUNC_PIO0;
+        io_bank0_hw->io[SCSI_IO_DB0].ctrl  = GPIO_FUNC_PIO0;
+        io_bank0_hw->io[SCSI_IO_DB1].ctrl  = GPIO_FUNC_PIO0;
+        io_bank0_hw->io[SCSI_IO_DB2].ctrl  = GPIO_FUNC_PIO0;
+        io_bank0_hw->io[SCSI_IO_DB3].ctrl  = GPIO_FUNC_PIO0;
+        io_bank0_hw->io[SCSI_IO_DB4].ctrl  = GPIO_FUNC_PIO0;
+        io_bank0_hw->io[SCSI_IO_DB5].ctrl  = GPIO_FUNC_PIO0;
+        io_bank0_hw->io[SCSI_IO_DB6].ctrl  = GPIO_FUNC_PIO0;
+        io_bank0_hw->io[SCSI_IO_DB7].ctrl  = GPIO_FUNC_PIO0;
+        io_bank0_hw->io[SCSI_IO_DBP].ctrl  = GPIO_FUNC_PIO0;
+        io_bank0_hw->io[SCSI_OUT_REQ].ctrl = GPIO_FUNC_PIO0;
     }
     else if (g_scsi_dma_state == SCSIDMA_READ)
     {
@@ -970,16 +970,16 @@ static void scsidma_config_gpio()
             pio_sm_set_consecutive_pindirs(SCSI_DMA_PIO, SCSI_SYNC_SM, SCSI_OUT_REQ, 1, true);
         }
 
-        iobank0_hw->io[SCSI_IO_DB0].ctrl  = GPIO_FUNC_SIO;
-        iobank0_hw->io[SCSI_IO_DB1].ctrl  = GPIO_FUNC_SIO;
-        iobank0_hw->io[SCSI_IO_DB2].ctrl  = GPIO_FUNC_SIO;
-        iobank0_hw->io[SCSI_IO_DB3].ctrl  = GPIO_FUNC_SIO;
-        iobank0_hw->io[SCSI_IO_DB4].ctrl  = GPIO_FUNC_SIO;
-        iobank0_hw->io[SCSI_IO_DB5].ctrl  = GPIO_FUNC_SIO;
-        iobank0_hw->io[SCSI_IO_DB6].ctrl  = GPIO_FUNC_SIO;
-        iobank0_hw->io[SCSI_IO_DB7].ctrl  = GPIO_FUNC_SIO;
-        iobank0_hw->io[SCSI_IO_DBP].ctrl  = GPIO_FUNC_SIO;
-        iobank0_hw->io[SCSI_OUT_REQ].ctrl = GPIO_FUNC_PIO0;
+        io_bank0_hw->io[SCSI_IO_DB0].ctrl  = GPIO_FUNC_SIO;
+        io_bank0_hw->io[SCSI_IO_DB1].ctrl  = GPIO_FUNC_SIO;
+        io_bank0_hw->io[SCSI_IO_DB2].ctrl  = GPIO_FUNC_SIO;
+        io_bank0_hw->io[SCSI_IO_DB3].ctrl  = GPIO_FUNC_SIO;
+        io_bank0_hw->io[SCSI_IO_DB4].ctrl  = GPIO_FUNC_SIO;
+        io_bank0_hw->io[SCSI_IO_DB5].ctrl  = GPIO_FUNC_SIO;
+        io_bank0_hw->io[SCSI_IO_DB6].ctrl  = GPIO_FUNC_SIO;
+        io_bank0_hw->io[SCSI_IO_DB7].ctrl  = GPIO_FUNC_SIO;
+        io_bank0_hw->io[SCSI_IO_DBP].ctrl  = GPIO_FUNC_SIO;
+        io_bank0_hw->io[SCSI_OUT_REQ].ctrl = GPIO_FUNC_PIO0;
     }
 }
 
@@ -1230,43 +1230,56 @@ bool scsi_accel_rp2040_setSyncMode(int syncOffset, int syncPeriod)
             // delay2: Delay from REQ deassert to data write (negation period)
             // see timings.c for delay periods in clock cycles
             int delay0, delay1, delay2;
+
+            // setup timings for sync_read_pacer
+            // total period = rdelay0 + redelay1
+            // rdelay0: wait for transfer period (total_period +  rtotal_period_adjust - rdelay1)
+            // rdelay1: req assert period
+            int rdelay1;
+
             uint32_t up_rounder = g_zuluscsi_timings->scsi.clk_period_ps / 2 + 1;
             uint32_t delay_in_ps = (syncPeriod * 4) * 1000;
-            // This is the delay in clock cycles rounded up
-            int totalDelay = (delay_in_ps + up_rounder) / g_zuluscsi_timings->scsi.clk_period_ps;
-
+            // This is the period in clock cycles rounded up
+            int totalPeriod = (delay_in_ps + up_rounder) / g_zuluscsi_timings->scsi.clk_period_ps;
+            int rtotalPeriod = totalPeriod;
             if (syncPeriod < 25)
             {
                 // Fast-20 SCSI timing: 15 ns assertion period
                 // The hardware rise and fall time require some extra delay,
                 // These delays are in addition to the 1 cycle that the PIO takes to execute the instruction
-                totalDelay += g_zuluscsi_timings->scsi_20.total_delay_adjust;
+                totalPeriod += g_zuluscsi_timings->scsi_20.total_period_adjust;
                 delay0 = g_zuluscsi_timings->scsi_20.delay0; //Data setup time, should be min 11.5ns according to the spec for FAST-20
                 delay1 = g_zuluscsi_timings->scsi_20.delay1; //pulse width, should be min 15ns according to the spec for FAST-20
-                delay2 = totalDelay - delay0 - delay1 - 3;  //Data hold time, should be min 16.5ns according to the spec for FAST-20
+                delay2 = totalPeriod - delay0 - delay1 - 3;  //Data hold time, should be min 16.5ns according to the spec for FAST-20
                 if (delay2 < 0) delay2 = 0;
                 if (delay2 > 15) delay2 = 15;
+                rdelay1 = g_zuluscsi_timings->scsi_20.rdelay1;
+                rtotalPeriod += g_zuluscsi_timings->scsi_20.rtotal_period_adjust;
             }
             else if (syncPeriod < 50 )
             {
                 // Fast-10 SCSI timing: 30 ns assertion period, 25 ns skew delay
                 // The hardware rise and fall time require some extra delay,
-                totalDelay += g_zuluscsi_timings->scsi_10.total_delay_adjust;
+                totalPeriod += g_zuluscsi_timings->scsi_10.total_period_adjust;
                 delay0 = g_zuluscsi_timings->scsi_10.delay0; // 4;
                 delay1 = g_zuluscsi_timings->scsi_10.delay1; // 6;
-                delay2 = totalDelay - delay0 - delay1 - 3;
+                delay2 = totalPeriod - delay0 - delay1 - 3;
                 if (delay2 < 0) delay2 = 0;
                 if (delay2 > 15) delay2 = 15;
+                rdelay1 = g_zuluscsi_timings->scsi_10.rdelay1;
+                rtotalPeriod += g_zuluscsi_timings->scsi_10.rtotal_period_adjust;
             }
             else
             {
                 // Slow SCSI timing: 90 ns assertion period, 55 ns skew delay
-                totalDelay += g_zuluscsi_timings->scsi_5.total_delay_adjust;
+                totalPeriod += g_zuluscsi_timings->scsi_5.total_period_adjust;
                 delay0 = g_zuluscsi_timings->scsi_5.delay0;
                 delay1 = g_zuluscsi_timings->scsi_5.delay1;
-                delay2 = totalDelay - delay0 - delay1 - 3;
+                delay2 = totalPeriod - delay0 - delay1 - 3;
                 if (delay2 < 0) delay2 = 0;
                 if (delay2 > 15) delay2 = 15;
+                rdelay1 = g_zuluscsi_timings->scsi_5.rdelay1;
+                rtotalPeriod += g_zuluscsi_timings->scsi_5.rtotal_period_adjust;
             }
 
             // Patch the delay values into the instructions in scsi_sync_write.
@@ -1279,11 +1292,11 @@ bool scsi_accel_rp2040_setSyncMode(int syncOffset, int syncPeriod)
             SCSI_DMA_PIO->instr_mem[g_scsi_dma.pio_offset_sync_write + 2] = instr2;
 
             // And similar patching for scsi_sync_read_pacer
-            int rdelay2 = totalDelay - delay1 - 2;
-            if (rdelay2 > 15) rdelay2 = 15;
-            if (rdelay2 < 5) rdelay2 = 5;
-            uint16_t rinstr0 = scsi_sync_read_pacer_program_instructions[0] | pio_encode_delay(rdelay2);
-            uint16_t rinstr1 = (scsi_sync_read_pacer_program_instructions[1] + g_scsi_dma.pio_offset_sync_read_pacer) | pio_encode_delay(delay1);
+            int rdelay0 = rtotalPeriod - rdelay1 - 2;
+            if (rdelay0 > 15) rdelay0 = 15;
+            if (rdelay0 < 0) rdelay0 = 0;
+            uint16_t rinstr0 = scsi_sync_read_pacer_program_instructions[0] | pio_encode_delay(rdelay0);
+            uint16_t rinstr1 = (scsi_sync_read_pacer_program_instructions[1] + g_scsi_dma.pio_offset_sync_read_pacer) | pio_encode_delay(rdelay1);
             SCSI_DMA_PIO->instr_mem[g_scsi_dma.pio_offset_sync_read_pacer + 0] = rinstr0;
             SCSI_DMA_PIO->instr_mem[g_scsi_dma.pio_offset_sync_read_pacer + 1] = rinstr1;
         }

+ 5 - 1
lib/ZuluSCSI_platform_RP2MCU/sd_card_sdio.cpp

@@ -207,7 +207,11 @@ uint32_t SdioCard::errorLine() const
 
 bool SdioCard::isBusy() 
 {
-    return (sio_hw->gpio_in & (1 << SDIO_D0)) == 0;
+#if SDIO_D0 > 31
+    return 0 == (sio_hw->gpio_hi_in & (1 << (SDIO_D0 - 32)));
+#else
+    return 0 == (sio_hw->gpio_in & (1 << SDIO_D0));
+#endif
 }
 
 uint32_t SdioCard::kHzSdClk()

+ 19 - 6
lib/ZuluSCSI_platform_RP2MCU/sdio.cpp

@@ -47,6 +47,14 @@
 #define SDIO_DMA_CH 4
 #define SDIO_DMA_CHB 5
 
+// If the highest SD pin is beyond the first 32 GPIOs, 
+// set the base GPIO to 16 to use GPIOs 16-47
+#if SDIO_D3 > 31
+# define SDIO_GPIO_BASE_HIGH
+# define SDIO_BASE_OFFSET 16
+#else
+# define SDIO_BASE_OFFSET 0
+#endif
 // Maximum number of 512 byte blocks to transfer in one request
 #define SDIO_MAX_BLOCKS 256
 
@@ -784,6 +792,10 @@ sdio_status_t rp2040_sdio_stop()
 
 void rp2040_sdio_init(int clock_divider)
 {
+    #ifdef SDIO_GPIO_BASE_HIGH
+        pio_set_gpio_base(SDIO_PIO, 16);
+    #endif
+
     // Mark resources as being in use, unless it has been done already.
     static bool resources_claimed = false;
     if (!resources_claimed)
@@ -846,7 +858,7 @@ void rp2040_sdio_init(int clock_divider)
         sdio_data_rx_program.pio_version };
     memcpy(temp_program_instr, sdio_data_rx_program_instructions, sizeof(sdio_data_rx_program_instructions));
     // wait 1 gpio SDIO_CLK_GPIO  [0]; [CLKDIV-1]
-    uint16_t instr = pio_encode_wait_gpio(true, SDIO_CLK) | pio_encode_delay(g_zuluscsi_timings->sdio.clk_div_pio - 1);
+    uint16_t instr = pio_encode_wait_gpio(true, SDIO_CLK - SDIO_BASE_OFFSET) | pio_encode_delay(g_zuluscsi_timings->sdio.clk_div_pio - 1);
     temp_program_instr[2] = instr;
     // in PINS, 4                 [0]; [CLKDIV-2]
     instr = sdio_data_rx_program_instructions[3] | pio_encode_delay(g_zuluscsi_timings->sdio.clk_div_pio - 2);
@@ -869,10 +881,10 @@ void rp2040_sdio_init(int clock_divider)
         sdio_data_tx_program.pio_version };
     memcpy(temp_program_instr, sdio_data_tx_program_instructions, sizeof(sdio_data_tx_program_instructions));
     // wait 0 gpio SDIO_CLK_GPIO  
-    instr = pio_encode_wait_gpio(false, SDIO_CLK);
+    instr = pio_encode_wait_gpio(false, SDIO_CLK - SDIO_BASE_OFFSET);
     temp_program_instr[0] = instr;
     // wait 1 gpio SDIO_CLK_GPIO;  [0]; [CLKDIV + D1 - 1];
-    instr = pio_encode_wait_gpio(true, SDIO_CLK) | pio_encode_delay(g_zuluscsi_timings->sdio.clk_div_pio + g_zuluscsi_timings->sdio.delay1 - 1);
+    instr = pio_encode_wait_gpio(true, SDIO_CLK - SDIO_BASE_OFFSET) | pio_encode_delay(g_zuluscsi_timings->sdio.clk_div_pio + g_zuluscsi_timings->sdio.delay1 - 1);
     temp_program_instr[1] = instr;
     
     for (uint8_t i = 2; i < sizeof(sdio_data_tx_program_instructions) / sizeof(sdio_data_tx_program_instructions[0]); i++)
@@ -894,8 +906,9 @@ void rp2040_sdio_init(int clock_divider)
     // This reduces input delay.
     // Because the CLK is driven synchronously to CPU clock,
     // there should be no metastability problems.
-    SDIO_PIO->input_sync_bypass |= (1 << SDIO_CLK) | (1 << SDIO_CMD)
-                                 | (1 << SDIO_D0) | (1 << SDIO_D1) | (1 << SDIO_D2) | (1 << SDIO_D3);
+    SDIO_PIO->input_sync_bypass |= (1 << (SDIO_CLK - SDIO_BASE_OFFSET)) | (1 << (SDIO_CMD - SDIO_BASE_OFFSET))
+                                 | (1 << (SDIO_D0 - SDIO_BASE_OFFSET)) | (1 << (SDIO_D1 - SDIO_BASE_OFFSET)) 
+                                 | (1 << (SDIO_D2 - SDIO_BASE_OFFSET)) | (1 << (SDIO_D3 - SDIO_BASE_OFFSET));
 
     // Redirect GPIOs to PIO
     gpio_set_function(SDIO_CMD, GPIO_FUNC_PIO1);
@@ -909,7 +922,7 @@ void rp2040_sdio_init(int clock_divider)
     irq_set_exclusive_handler(DMA_IRQ_1, rp2040_sdio_tx_irq);
     irq_set_enabled(DMA_IRQ_1, true);
 #if 0
-#ifndef ENABLE_AUDIO_OUTPUT
+#ifndef ENABLE_AUDIO_OUTPUT_SPDIF
     irq_set_exclusive_handler(DMA_IRQ_1, rp2040_sdio_tx_irq);
 #else
     // seem to hit assertion in _exclusive_handler call due to DMA_IRQ_0 being shared?

+ 363 - 61
lib/ZuluSCSI_platform_RP2MCU/timings_RP2MCU.c

@@ -24,6 +24,7 @@
 
 
 static zuluscsi_timings_t  predefined_timings[]  = {
+    // predefined_timings[0] - 125000000
     {
         .clk_hz = 125000000,
 
@@ -43,18 +44,21 @@ static zuluscsi_timings_t  predefined_timings[]  = {
 
         .scsi_20 =
         {
-            .delay0 = 4,
-            .delay1 = 6,
-            .total_delay_adjust = -1,
-            .max_sync = 25,
-
+            .delay0 = 3 - 1,
+            .delay1 = 3 - 1,
+            .total_period_adjust = -1,
+            .rdelay1 = 3 - 1,
+            .rtotal_period_adjust = -1,
+            .max_sync = 12,
         },
 
         .scsi_10 =
         {
-            .delay0 = 4,
-            .delay1 = 6,
-            .total_delay_adjust = -1,
+            .delay0 = 5 - 1,
+            .delay1 = 7 - 1,
+            .total_period_adjust = -1,
+            .rdelay1 = 6,
+            .rtotal_period_adjust = -1,
             .max_sync = 25,
         },
 
@@ -62,18 +66,27 @@ static zuluscsi_timings_t  predefined_timings[]  = {
         {
             .delay0 = 10 - 1,
             .delay1 = 15 - 1,
-            .total_delay_adjust = -1,
+            .total_period_adjust = -1,
+            .rdelay1 = 15 - 1,
+            .rtotal_period_adjust = -1,
             .max_sync = 50,
         },
 
         .sdio =
         {
             .clk_div_1mhz = 25, // = 125MHz clk / clk_div_pio
-            .clk_div_pio = 5,
+            .clk_div_pio = 4,
             .delay0 = 3 - 1, // subtract one for the instruction delay
-            .delay1 = 2 - 1  // clk_div_pio - delay0 and subtract one for the instruction delay
+            .delay1 = 1 - 1  // clk_div_pio - delay0 and subtract one for the instruction delay
+        },
+
+        .audio =
+        {
+            .clk_div_pio = 48,
+            .audio_clocked = false,
         }
     },
+    // predefined_timings[1] - 133000000
     {
         .clk_hz = 133000000,
 
@@ -93,18 +106,21 @@ static zuluscsi_timings_t  predefined_timings[]  = {
 
         .scsi_20 =
         {
-            .delay0 = 4,
-            .delay1 = 6,
-            .total_delay_adjust = -1,
-            .max_sync = 25,
-
+            .delay0 = 3 - 1,
+            .delay1 = 3 - 1,
+            .total_period_adjust = -1,
+            .rdelay1 = 3 - 1,
+            .rtotal_period_adjust = -1,
+            .max_sync = 12,
         },
 
         .scsi_10 =
         {
-            .delay0 = 4,
-            .delay1 = 6,
-            .total_delay_adjust = -1,
+            .delay0 = 5 - 1,
+            .delay1 = 7 - 1,
+            .total_period_adjust = -1,
+            .rdelay1 = 7 - 1,
+            .rtotal_period_adjust = 0,
             .max_sync = 25,
         },
 
@@ -112,7 +128,9 @@ static zuluscsi_timings_t  predefined_timings[]  = {
         {
             .delay0 = 10 - 1,
             .delay1 = 15 - 1,
-            .total_delay_adjust = -1,
+            .total_period_adjust = -1,
+            .rdelay1 = 15 - 1,
+            .rtotal_period_adjust = 0,
             .max_sync = 50,
         },
 
@@ -122,8 +140,15 @@ static zuluscsi_timings_t  predefined_timings[]  = {
             .clk_div_pio = 5,
             .delay0 = 3 - 1, // subtract one for the instruction delay
             .delay1 = 2 - 1  // clk_div_pio - delay0 and subtract one for the instruction delay
+        },
+
+        .audio =
+        {
+            .clk_div_pio = 48,
+            .audio_clocked = false,
         }
     },
+    // predefined_timings[2] - 135428571 - RP2040 Audio DAC Attack S/PDIF clocks
     {
         .clk_hz = 135428571,
 
@@ -143,18 +168,21 @@ static zuluscsi_timings_t  predefined_timings[]  = {
 
         .scsi_20 =
         {
-            .delay0 = 4,
-            .delay1 = 6,
-            .total_delay_adjust = -1,
-            .max_sync = 25,
-
+            .delay0 = 3 - 1,
+            .delay1 = 3 - 1,
+            .total_period_adjust = -1,
+            .rdelay1 = 3 - 1,
+            .rtotal_period_adjust = -1,
+            .max_sync = 12,
         },
 
         .scsi_10 =
         {
-            .delay0 = 4,
-            .delay1 = 6,
-            .total_delay_adjust = -1,
+            .delay0 = 5 - 1,
+            .delay1 = 7 - 1,
+            .total_period_adjust = -1,
+            .rdelay1 = 7 - 1,
+            .rtotal_period_adjust = -1,
             .max_sync = 25,
         },
 
@@ -162,7 +190,9 @@ static zuluscsi_timings_t  predefined_timings[]  = {
         {
             .delay0 = 10 - 1,
             .delay1 = 15 - 1,
-            .total_delay_adjust = -1,
+            .total_period_adjust = -1,
+            .rdelay1 = 15 - 1,
+            .rtotal_period_adjust = -1,
             .max_sync = 50,
         },
 
@@ -172,8 +202,18 @@ static zuluscsi_timings_t  predefined_timings[]  = {
             .clk_div_pio = 5,
             .delay0 = 3 - 1, // subtract one for the instruction delay
             .delay1 = 2 - 1  // clk_div_pio - delay0 and subtract one for the instruction delay
+        },
+
+        .audio =
+        {
+            // 44.1KHz to the nearest integer with a sys clk of 135.43MHz and 2 x 16-bit samples with the pio clock running 2x I2S clock
+            // 135.43Mhz / 16 / 2 / 2 / 44.1KHz = 47.98 ~= 48
+            .clk_div_pio = 48,
+            .audio_clocked = true,
         }
+
     },
+    // predefined_timings[3] - 150000000
     {
         .clk_hz = 150000000,
 
@@ -193,10 +233,12 @@ static zuluscsi_timings_t  predefined_timings[]  = {
 
         .scsi_20 =
         {
-            .delay0 = 3 - 1,
-            .delay1 = 4 - 1,
-            .total_delay_adjust = 0,
-            .max_sync = 18,
+            .delay0 = 2 - 1,
+            .delay1 = 3 - 1,
+            .total_period_adjust = 0,
+            .rdelay1 = 3 - 1,
+            .rtotal_period_adjust = -1,
+            .max_sync = 12,
 
         },
 
@@ -204,7 +246,9 @@ static zuluscsi_timings_t  predefined_timings[]  = {
         {
             .delay0 = 4 - 1,
             .delay1 = 5 - 1,
-            .total_delay_adjust = 0,
+            .total_period_adjust = 0,
+            .rdelay1 = 5 - 1,
+            .rtotal_period_adjust = 0,
             .max_sync = 25,
 
         },
@@ -213,19 +257,28 @@ static zuluscsi_timings_t  predefined_timings[]  = {
         {
             .delay0 = 10 - 1,
             .delay1 = 15, // should be 18 - 1 but max currently is 15
-            .total_delay_adjust = 0,
+            .total_period_adjust = 0,
+            .rdelay1 = 15,
+            .rtotal_period_adjust = 0,
             .max_sync = 50,
 
         },
 
         .sdio =
         {
-            .clk_div_1mhz = 30, // = 150MHz clk / clk_div_pio
+            .clk_div_1mhz = 30,// = 150MHz clk / clk_div_pio
             .clk_div_pio = 5,
             .delay0 = 3 - 1, // subtract one for the instruction delay
             .delay1 = 2 - 1  // clk_div_pio - delay0 and subtract one for the instruction delay
+        },
+
+        .audio =
+        {
+            .clk_div_pio = 54,
+            .audio_clocked = false,
         }
     },
+    // predefined_timings[4] - 250000000
     {
         .clk_hz = 250000000,
 
@@ -247,7 +300,9 @@ static zuluscsi_timings_t  predefined_timings[]  = {
         {
             .delay0 = 3 - 1,
             .delay1 = 5 - 1,
-            .total_delay_adjust = 1,
+            .total_period_adjust = 1,
+            .rdelay1 = 5 - 1,
+            .rtotal_period_adjust = -1,
             .max_sync = 12,
 
         },
@@ -255,8 +310,10 @@ static zuluscsi_timings_t  predefined_timings[]  = {
         .scsi_10 =
         {
             .delay0 = 6 - 1,
-            .delay1 = 9 - 1,
-            .total_delay_adjust = 1,
+            .delay1 = 8 - 1,
+            .total_period_adjust = 0,
+            .rdelay1 = 8 - 1,
+            .rtotal_period_adjust = 0,
             .max_sync = 25,
         },
 
@@ -264,28 +321,232 @@ static zuluscsi_timings_t  predefined_timings[]  = {
         {
             .delay0 = 15, // maxed out should be 16
             .delay1 = 15, // maxed out should be 30
-            .total_delay_adjust = 1,
+            .total_period_adjust = 1,
+            .rdelay1 = 15,
+            .rtotal_period_adjust = 1,
             .max_sync = 50,
         },
         .sdio =
         {
-            .clk_div_1mhz = 30, // set by trail and error
+            .clk_div_1mhz = 30,// set by trail and error
             .clk_div_pio = 5, // SDIO at 50MHz
             .delay0 = 4 - 1, // subtract one for the instruction delay
             .delay1 = 1 - 1  // clk_div_pio - delay0 and subtract one for the instruction delay
         }
     },
+    // predefined_timings[5] - 155250000 - Default clocks for Blaster I2S Audio
+    {
+        .clk_hz = 155250000,
+
+        .pll =
+        {
+            .refdiv = 3,
+            .vco_freq = 1242000000,
+            .post_div1 = 4,
+            .post_div2 = 2,
+        },
+
+        .scsi =
+        {
+            .req_delay = 10,
+            .clk_period_ps = 6441
+        },
+
+        .scsi_20 =
+        {
+            .delay0 = 2 - 1,
+            .delay1 = 3 - 1,
+            .total_period_adjust = 0,
+            .rdelay1 = 3 - 1,
+            .rtotal_period_adjust = -1,
+            .max_sync = 12,
+
+        },
+
+        .scsi_10 =
+        {
+            .delay0 = 4 - 1,
+            .delay1 = 5 - 1,
+            .total_period_adjust = 0,
+            .rdelay1 = 5 - 1,
+            .rtotal_period_adjust = 0,
+            .max_sync = 25,
+
+        },
+
+        .scsi_5 =
+        {
+            .delay0 = 10 - 1,
+            .delay1 = 15, // should be 18 - 1 but max currently is 15
+            .total_period_adjust = 0,
+            .rdelay1 = 15,
+            .rtotal_period_adjust = 0,
+            .max_sync = 50,
+
+        },
+
+        .sdio =
+        {
+            .clk_div_1mhz = 26, // = 155.25MHz clk / clk_div_pio
+            .clk_div_pio = 6,
+            .delay0 = 4 - 1, // subtract one for the instruction delay
+            .delay1 = 2 - 1  // clk_div_pio - delay0 and subtract one for the instruction delay
+        },
+
+        .audio =
+        {
+            // 44.1KHz to the nearest integer with a sys clk of 155.25Mhz and 2 x 16-bit samples with the pio clock running 2x I2S clock
+            // 155.25Mhz / 16 / 2 / 2 / 44.1KHz = 55.01 ~= 55
+            .clk_div_pio = 55,
+            .audio_clocked = true,
+        }
+    },
+    // predefined_timings[6] - 175000000 - Alternate clocking for I2S Audio
+    {
+        // predefined_timings[6] - Clocking for I2S Audio at 175MHz system clock
+        .clk_hz = 175000000,
+
+        .pll =
+        {
+            .refdiv = 2,
+            .vco_freq = 1050000000,
+            .post_div1 = 6,
+            .post_div2 = 1,
+        },
+
+        .scsi =
+        {
+            .req_delay = 10,
+            .clk_period_ps = 5714
+        },
+
+        .scsi_20 =
+        {
+            .delay0 = 3 - 1,
+            .delay1 = 4 - 1,
+            .total_period_adjust = -1,
+            .rdelay1 = 3 - 1,
+            .rtotal_period_adjust = -1,
+            .max_sync = 12,
+        },
+
+        .scsi_10 =
+        {
+            .delay0 = 4 - 1,
+            .delay1 = 6 - 1,
+            .total_period_adjust = 0,
+            .rdelay1 = 6 - 1,
+            .rtotal_period_adjust = 0,
+            .max_sync = 25,
+        },
+
+        .scsi_5 =
+        {
+            .delay0 = 4 - 1,
+            .delay1 = 10 - 1,
+            .total_period_adjust = 0,
+            .rdelay1 = 10 - 1,
+            .rtotal_period_adjust = 0,
+            .max_sync = 50,
+
+        },
+
+        .sdio =
+        {
+            .clk_div_1mhz = 30,
+            .clk_div_pio = 5,
+            .delay0 = 4 - 1, // subtract one for the instruction delay
+            .delay1 = 1 - 1  // clk_div_pio - delay0 and subtract one for the instruction delay
+        },
+
+        .audio =
+        {
+            // Divider for 44.1KHz to the nearest integer with a sys clk divided by 2 x 16-bit samples with the pio clock running 2x I2S clock
+            // 175Mhz / 16 / 2 / 2 / 44.1KHz ~= 62
+            .clk_div_pio = 62,
+            .audio_clocked = true,
+        }
+    },
+    // predefined_timings[7] - 200400000 - Alternate clocking for I2S Audio
+    {
+        .clk_hz = 200400000,
+
+        .pll =
+        {
+            .refdiv = 2,
+            .vco_freq = 1002000000,
+            .post_div1 = 5,
+            .post_div2 = 1,
+        },
+
+        .scsi =
+        {
+            .req_delay = 12,
+            .clk_period_ps = 4990
+        },
+
+        .scsi_20 =
+        {
+            .delay0 = 3 - 1,
+            .delay1 = 4 - 1,
+            .total_period_adjust = 1,
+            .rdelay1 = 5 - 1,
+            .rtotal_period_adjust = -1,
+            .max_sync = 12,
+
+        },
+
+        .scsi_10 =
+        {
+            .delay0 = 5 - 1,
+            .delay1 = 7 - 1,
+            .total_period_adjust = 0,
+            .rdelay1 = 7- 1,
+            .rtotal_period_adjust = 0,
+            .max_sync = 25,
+
+        },
+
+        .scsi_5 =
+        {
+            .delay0 = 5 - 1,
+            .delay1 = 11 - 1,
+            .total_period_adjust = 0,
+            .rdelay1 = 11 - 1,
+            .rtotal_period_adjust = 0,
+            .max_sync = 50,
+
+        },
+
+        .sdio =
+        {
+            .clk_div_1mhz = 30,
+            .clk_div_pio = 5,
+            .delay0 = 4 - 1, // subtract one for the instruction delay
+            .delay1 = 1 - 1  // clk_div_pio - delay0 and subtract one for the instruction delay
+        },
+
+        .audio =
+        {
+            // Divider for 44.1KHz to the nearest integer with a sys clk divided by 2 x 16-bit samples with the pio clock running 2x I2S clock
+            // 200.4Mhz / 16 / 2 / 2 / 44.1KHz = 71.003 ~= 71
+            .clk_div_pio = 71,
+            .audio_clocked = true,
+        }
+    },
 };
-    zuluscsi_timings_t  current_timings;
+static zuluscsi_timings_t  current_timings;
 
-#ifdef ENABLE_AUDIO_OUTPUT
-    zuluscsi_timings_t *g_zuluscsi_timings = &predefined_timings[2];
+#ifdef ENABLE_AUDIO_OUTPUT_SPDIF
+zuluscsi_timings_t *g_zuluscsi_timings = &predefined_timings[2];
+#elif defined(ENABLE_AUDIO_OUTPUT_I2S)
+zuluscsi_timings_t *g_zuluscsi_timings = &predefined_timings[7];
 #elif defined(ZULUSCSI_MCU_RP23XX)
-    zuluscsi_timings_t *g_zuluscsi_timings = &predefined_timings[3];
+zuluscsi_timings_t *g_zuluscsi_timings = &predefined_timings[3];
 #elif defined(ZULUSCSI_PICO)
-    zuluscsi_timings_t *g_zuluscsi_timings = &predefined_timings[1];
+zuluscsi_timings_t *g_zuluscsi_timings = &predefined_timings[1];
 #else
-    zuluscsi_timings_t *g_zuluscsi_timings = &predefined_timings[0];
+zuluscsi_timings_t *g_zuluscsi_timings = &predefined_timings[0];
 #endif
 
 
@@ -295,24 +556,65 @@ bool set_timings(zuluscsi_speed_grade_t speed_grade)
 
     switch (speed_grade)
     {
-        case SPEED_GRADE_MAX:
-        case SPEED_GRADE_A:
-            timings_index = 4;
-            break;
-        case SPEED_GRADE_B:
-            timings_index = 3;
-            break;
-        case SPEED_GRADE_C:
-            timings_index  = 1;
-            break;
-        case SPEED_GRADE_AUDIO:
-            timings_index = 2;
+#ifdef ENABLE_AUDIO_OUTPUT_I2S
+    case SPEED_GRADE_MAX:
+    case SPEED_GRADE_A:
+        timings_index = 4;
+        break;
+    case SPEED_GRADE_B:
+        timings_index = 6;
+        break;
+    case SPEED_GRADE_C:
+        timings_index = 5;
+        break;
+    case SPEED_GRADE_AUDIO_SPDIF:
+        timings_index = 2;
+        break;
+    case SPEED_GRADE_AUDIO_I2S:
+        timings_index = 7;
+        break;
+#elif defined(ZULUSCSI_MCU_RP23XX)
+    case SPEED_GRADE_MAX:
+    case SPEED_GRADE_A:
+        timings_index = 4;
+        break;
+    case SPEED_GRADE_B:
+        timings_index = 7;
+        break;
+    case SPEED_GRADE_C:
+        timings_index  = 6;
+        break;
+    case SPEED_GRADE_AUDIO_SPDIF:
+        timings_index = 2;
+        break;
+    case SPEED_GRADE_AUDIO_I2S:
+        timings_index = 5;
+        break;
+#else
+case SPEED_GRADE_MAX:
+    case SPEED_GRADE_A:
+        timings_index = 4;
+        break;
+    case SPEED_GRADE_B:
+        timings_index = 3;
+        break;
+    case SPEED_GRADE_C:
+        timings_index  = 1;
+        break;
+    case SPEED_GRADE_AUDIO_SPDIF:
+        timings_index = 2;
+        break;
+    case SPEED_GRADE_AUDIO_I2S:
+        timings_index = 5;
+        break;
+#endif
+        default:
             break;
-    }   
+    }
     if (speed_grade != SPEED_GRADE_DEFAULT && speed_grade != SPEED_GRADE_CUSTOM)
     {
         g_zuluscsi_timings = &current_timings;
-        memcpy(g_zuluscsi_timings, &predefined_timings[timings_index], sizeof(current_timings));
+        memcpy(g_zuluscsi_timings, &predefined_timings[timings_index], sizeof(*g_zuluscsi_timings));
         g_max_sync_10_period = g_zuluscsi_timings->scsi_10.max_sync;
         g_max_sync_20_period = g_zuluscsi_timings->scsi_20.max_sync;
         g_max_sync_5_period = g_zuluscsi_timings->scsi_5.max_sync;

+ 34 - 11
lib/ZuluSCSI_platform_RP2MCU/timings_RP2MCU.h

@@ -55,21 +55,28 @@ typedef struct
         uint32_t clk_period_ps;
     } scsi;
 
-
-    // delay0: Data Setup Time - Delay from data write to REQ assertion
+    // delayX: Writing to SCSI bus signaling delays
+    // delay0: Receive hold time - Delay from data write to REQ assertion
     // delay1  Transmit Assertion time from REQ assert to REQ deassert (req pulse) 
-    // delay2: Negation period - (total_delay - d0 - d1): total_delay spec is the sync value * 4 in ns width)
-    // both values are in clock cycles minus 1 for the pio instruction delay
-    // delay0 spec: Ultra(20):  11.5ns  Fast(10): 23ns  SCSI-1(5): 23ns
-    // delay1 spec: Ultra(20):  16.5ns  Fast(10): 33ns  SCSI-1(5): 53ns 
-    // delay2 spec: Ultra(20):  15ns    Fast(10): 30ns  SCSI-1(5): 80ns 
-    // total_delay_adjust is manual adjustment value, when checked with a scope
+    // delay2: Negation period - (total_period - d0 - d1): total_period spec is the sync value * 4 in ns width)
+    // rdelayX: Reading from the SCSI bus delay adjustments
+    // rtotal_period_adjust: adjustment to total delay for rdelay0 calculation
+    // rdelay0: total_period + rtotal_period_adjust - rdelay1
+    // rdelay1: Transmit Assertion time from REQ assert to REQ deassert
+    // all values are in clock cycles minus 1 for the pio instruction delay
+    // delay0 spec:  Ultra(20):  11.5ns  Fast(10): 25ns  SCSI-1(5): 25ns
+    // delay1 spec:  Ultra(20):  15ns    Fast(10): 30ns  SCSI-1(5): 80ns
+    // delay2 spec:  Ultra(20):  15ns    Fast(10): 30ns  SCSI-1(5): 80ns
+    // rdelay1 spec: Ultra(20):  15ns    Fast(10): 30ns  SCSI-1(5): 80ns
+    // total_period_adjust is manual adjustment value, when checked with a scope
     // Max sync - the minimum sync period ("max" clock rate) that is supported at this clock rate, the number is 1/4 the actual value in ns
     struct
     {
         uint8_t delay0;
         uint8_t delay1;
-        int16_t total_delay_adjust;
+        uint8_t rtotal_period_adjust;
+        uint8_t rdelay1;
+        int16_t total_period_adjust;
         uint8_t max_sync;
     } scsi_20;
 
@@ -77,7 +84,9 @@ typedef struct
     {
         uint8_t delay0;
         uint8_t delay1;
-        int16_t total_delay_adjust;
+        uint8_t rtotal_period_adjust;
+        uint8_t rdelay1;
+        int16_t total_period_adjust;
         uint8_t max_sync;
     } scsi_10;
 
@@ -85,7 +94,9 @@ typedef struct
     {
         uint8_t delay0;
         uint8_t delay1;
-        int16_t total_delay_adjust;
+        uint8_t rtotal_period_adjust;
+        uint8_t rdelay1;
+        int16_t total_period_adjust;
         uint8_t max_sync;
     } scsi_5;
 
@@ -107,6 +118,18 @@ typedef struct
         uint8_t delay1; // clk_div_pio - delay0 and subtract one for the instruction delay
     } sdio;
 
+    struct
+    {
+        // Divider for 44.1KHz to the nearest integer with a sys clk frequency divided by 2 x 16-bit samples with the pio clock running 2x I2S clock
+        // Example sys clock frequency of 155.25Mhz would be 155.25MHz/ 16 / 2 / 2 / 44.1KHz = 55.006 ~= 55
+        uint8_t clk_div_pio;
+        // True if the clock rate is close enough to support audio playback without much error
+        // Currently this has been decided to be within 0.02% from what the ZuluSCSI plays back compared to 44.1KHz
+        // For the example above of 155.25MHz uses a pio state machine divider of 55
+        // 155.25MHz / 55 / 16 / 2 / 2 = 41.1051KHz so |41.1051KHz - 44.1KHz| / 55.1KHz = 0.011%
+        bool audio_clocked;
+    } audio;
+
 } zuluscsi_timings_t;
 
 extern  zuluscsi_timings_t *g_zuluscsi_timings;

+ 24 - 15
platformio.ini

@@ -1,7 +1,7 @@
 ; PlatformIO Project Configuration File https://docs.platformio.org/page/projectconf.html
 
 [platformio]
-default_envs = ZuluSCSIv1_0, ZuluSCSIv1_0_mini, ZuluSCSIv1_1_plus, ZuluSCSI_RP2040, ZuluSCSI_RP2040_Audio, ZuluSCSI_Pico, ZuluSCSI_Pico_DaynaPORT, ZuluSCSI_BS2, ZuluSCSI_Pico_2, ZuluSCSI_Pico_2_DaynaPORT
+default_envs = ZuluSCSIv1_0, ZuluSCSIv1_0_mini, ZuluSCSIv1_1_plus, ZuluSCSI_RP2040, ZuluSCSI_RP2040_Audio, ZuluSCSI_Pico, ZuluSCSI_Pico_DaynaPORT, ZuluSCSI_BS2, ZuluSCSI_Pico_2, ZuluSCSI_Pico_2_DaynaPORT, ZuluSCSI_Blaster
 
 [env]
 build_flags =
@@ -26,7 +26,7 @@ lib_deps =
     minIni
     ZuluSCSI_platform_template
     SCSI2SD
-    CUEParser=https://github.com/rabbitholecomputing/CUEParser
+    CUEParser=https://github.com/rabbitholecomputing/CUEParser#v2025.02.25
 
 ; ZuluSCSI V1.0 hardware platform with GD32F205 CPU.
 [env:ZuluSCSIv1_0]
@@ -44,7 +44,7 @@ lib_deps =
     minIni
     ZuluSCSI_platform_GD32F205
     SCSI2SD
-    CUEParser=https://github.com/rabbitholecomputing/CUEParser
+    CUEParser=https://github.com/rabbitholecomputing/CUEParser#v2025.02.25
     GD32F20x_usbfs_library
 upload_protocol = stlink
 platform_packages = platformio/toolchain-gccarmnoneeabi@1.100301.220327
@@ -97,6 +97,7 @@ build_flags =
      -DPIO_USBFS_DEVICE_CDC
      -DHAS_SDIO_CLASS
      -DENABLE_AUDIO_OUTPUT
+     -DENABLE_AUDIO_OUTPUT_I2S
      -DZULUSCSI_V1_1_plus
      -DPLATFORM_MASS_STORAGE
      -DSDFAT_NOARDUINO
@@ -104,8 +105,10 @@ build_flags =
      -DLOGBUFSIZE=8192
 
 ; ZuluSCSI settings shared among Raspberry Pi microcontroller like the RP2040 and RP2350
+; Note: getting the code to break on main, check the comments in the rp2040-template.ld or rp23xx-template.ld
+;       They should instruct you on the changes needed to move code out of SRAM and back to flash
 [env:ZuluSCSI_RP2MCU]
-platform = https://github.com/maxgerhardt/platform-raspberrypi.git#19e30129fb1428b823be585c787dcb4ac0d9014c
+platform = https://github.com/maxgerhardt/platform-raspberrypi.git#39b90392af50585e429941bd2561a91949a2ba46
 platform_packages =
     framework-arduinopico@https://github.com/rabbitholecomputing/arduino-pico.git#v4.3.0-DaynaPORT
 extra_scripts =
@@ -118,7 +121,8 @@ lib_deps =
     SdFat=https://github.com/rabbitholecomputing/SdFat#2.2.3-gpt
     minIni
     SCSI2SD
-    CUEParser=https://github.com/rabbitholecomputing/CUEParser
+    ; When updating git tag for CUEParser here, also update lib\ZuluSCSI_platform_RP2MCU\library.json
+    CUEParser=https://github.com/rabbitholecomputing/CUEParser#v2025.02.25
     ZuluSCSI_platform_RP2MCU
 upload_protocol = cmsis-dap
 debug_tool = cmsis-dap
@@ -170,6 +174,7 @@ extends = env:ZuluSCSI_RP2040
 build_flags =
     ${env:ZuluSCSI_RP2040.build_flags}
     -DENABLE_AUDIO_OUTPUT
+    -DENABLE_AUDIO_OUTPUT_SPDIF
     -DLOGBUFSIZE=8192
 
 ; Variant of RP2040 platform, based on Raspberry Pico board and a carrier PCB
@@ -278,7 +283,7 @@ lib_deps =
     minIni
     ZuluSCSI_platform_GD32F450
     SCSI2SD
-    CUEParser=https://github.com/rabbitholecomputing/CUEParser
+    CUEParser=https://github.com/rabbitholecomputing/CUEParser#v2025.02.25
 upload_protocol = stlink
 platform_packages = 
     toolchain-gccarmnoneeabi@1.90201.191206
@@ -303,21 +308,25 @@ build_flags =
 
 ;========================================
 ; ZuluSCSI RP2350 hardware platform, based on the Raspberry Pi foundation RP2350 microcontroller
-[env:ZuluSCSI_RP2350A]
+[env:ZuluSCSI_Blaster]
 extends = env:ZuluSCSI_RP2MCU
-board = zuluscsi_RP2350A
-; How much flash in bytes the bootloader and main app will be allocated
-; It is used as the starting point for a ROM image saved in flash
-; Changing this will cause issues with boards that already have a ROM drive in flash
-program_flash_allocation = 360448
+board = zuluscsi_blaster
+program_flash_allocation = 589824
 linker_script_template = lib/ZuluSCSI_platform_RP2MCU/rp23xx-template.ld
 ldscript_bootloader = lib/ZuluSCSI_platform_RP2MCU/rp23xx_btldr.ld
+lib_deps =
+    ${env:ZuluSCSI_RP2MCU.lib_deps}
+    ZuluI2S
 build_flags =
     ${env:ZuluSCSI_RP2MCU.build_flags}
-    -DZULUSCSI_RP2350A
+    -DZULUSCSI_BLASTER
     -DZULUSCSI_MCU_RP23XX
-    -DROMDRIVE_OFFSET=${env:ZuluSCSI_RP2350A.program_flash_allocation}
-
+    -DENABLE_AUDIO_OUTPUT
+    -DENABLE_AUDIO_OUTPUT_I2S
+    -DROMDRIVE_OFFSET=${env:ZuluSCSI_Blaster.program_flash_allocation}
+; Use the -Wl line below as a "build_flags" entry if you wish to make a map file
+; to check memory locations of the compiled code.
+;    -Wl,-Map="${platformio.build_dir}/${this.__env__}/firmware.map"
 
 [env:ZuluSCSI_Pico_2_DaynaPORT]
 extends = env:ZuluSCSI_RP2MCU

+ 40 - 8
src/ZuluSCSI.cpp

@@ -850,6 +850,33 @@ static void reinitSCSI()
 
 }
 
+// Alert user that update bin file not used
+static void check_for_unused_update_files()
+{
+  FsFile root = SD.open("/");
+  FsFile file;
+  char filename[MAX_FILE_PATH + 1];
+  bool bin_files_found = false;
+  while (file.openNext(&root, O_RDONLY))
+  {
+    if (!file.isDir())
+    {
+      size_t filename_len = file.getName(filename, sizeof(filename));
+      if (strncasecmp(filename, "zuluscsi", sizeof("zuluscsi" - 1)) == 0 &&
+          strncasecmp(filename + filename_len - 4, ".bin", 4) == 0)
+      {
+        bin_files_found = true;
+        logmsg("Firmware update file \"", filename, "\" does not contain the board model string \"", FIRMWARE_NAME_PREFIX, "\"");
+      }
+    }
+  }
+  if (bin_files_found)
+  {
+    logmsg("Please use the ", FIRMWARE_PREFIX ,"*.zip firmware bundle, or the proper .bin or .uf2 file to update the firmware.");
+    logmsg("See http://zuluscsi.com/manual for more information");
+  }
+}
+
 // Update firmware by unzipping the firmware package
 static void firmware_update()
 {
@@ -878,8 +905,10 @@ static void firmware_update()
   }
 
   logmsg("Found firmware package ", name);
-
-  zipparser::Parser parser = zipparser::Parser(FIRMWARE_NAME_PREFIX, sizeof(FIRMWARE_NAME_PREFIX) - 1);
+  // example fixed length at the end of the filename
+  const uint32_t postfix_filename_length = sizeof("_2025-02-21_e4be9ed.bin") - 1;
+  const uint32_t target_filename_length = sizeof(FIRMWARE_NAME_PREFIX) - 1 + postfix_filename_length;
+  zipparser::Parser parser = zipparser::Parser(FIRMWARE_NAME_PREFIX, sizeof(FIRMWARE_NAME_PREFIX) - 1, target_filename_length);
   uint8_t buf[512];
   int32_t parsed_length;
   int bytes_read = 0;
@@ -897,13 +926,14 @@ static void firmware_update()
       }
       else
       {
-        // seek to start of compressed data in matching file
+        // seek to start of data in matching file
         file.seekSet(file.position() - (sizeof(buf) - parsed_length));
         break;
       }
     }
     if (parsed_length < 0)
     {
+      logmsg("Filename character length of ", (int)target_filename_length , " with a prefix of ", FIRMWARE_NAME_PREFIX, " not found in ", name);
       file.close();
       root.close();
       return;
@@ -916,7 +946,10 @@ static void firmware_update()
 
     logmsg("Unzipping matching firmware with prefix: ", FIRMWARE_NAME_PREFIX);
     FsFile target_firmware;
-    target_firmware.open(&root, "ZuluSCSI.bin", O_BINARY | O_WRONLY | O_CREAT | O_TRUNC);
+    char firmware_name[64] = {0};
+    memcpy(firmware_name, FIRMWARE_NAME_PREFIX, sizeof(FIRMWARE_NAME_PREFIX) - 1);
+    memcpy(firmware_name + sizeof(FIRMWARE_NAME_PREFIX) - 1, ".bin", sizeof(".bin"));
+    target_firmware.open(&root, firmware_name, O_BINARY | O_WRONLY | O_CREAT | O_TRUNC);
     uint32_t position = 0;
     while ((bytes_read = file.read(buf, sizeof(buf))) > 0)
     {
@@ -945,11 +978,9 @@ static void firmware_update()
     {
       target_firmware.close();
       logmsg("Error reading firmware package file");
-      root.remove("ZuluSCSI.bin");
+      root.remove(firmware_name);
     }
   }
-  else
-    logmsg("Updater did not find matching file in package: ", name);
   file.close();
   root.close();
 }
@@ -1007,10 +1038,11 @@ static void zuluscsi_setup_sd_card(bool wait_for_card = true)
       logmsg("Continuing without SD card");
     }
   }
-
+  check_for_unused_update_files();
   firmware_update();
 
 
+
   if (g_sdcard_present)
   {
 

+ 29 - 3
src/ZuluSCSI_audio.h

@@ -22,7 +22,7 @@
 #pragma once
 
 #include <stdint.h>
-#include "ImageBackingStore.h"
+#include "ZuluSCSI_disk.h"
 
 /*
  * Starting volume level for audio output, with 0 being muted and 255 being
@@ -69,6 +69,7 @@ enum audio_status_code {
  */
 bool audio_is_playing(uint8_t id);
 
+#if defined(ENABLE_AUDIO_OUTPUT) && !defined(ZULUSCSI_BLASTER)
 /**
  * Begins audio playback for a file.
  *
@@ -79,7 +80,23 @@ bool audio_is_playing(uint8_t id);
  * \param swap   If false, little-endian sample order, otherwise big-endian.
  * \return       True if successful, false otherwise.
  */
-bool audio_play(uint8_t owner, ImageBackingStore* img, uint64_t start, uint64_t end, bool swap);
+bool audio_play(uint8_t owner, image_config_t* img, uint64_t start, uint64_t end, bool swap);
+
+#elif defined(ENABLE_AUDIO_OUTPUT_I2S) && defined(ZULUSCSI_BLASTER)
+/**
+ * Begins audio playback for a file.
+ *
+ * \param owner  The SCSI ID that initiated this playback operation.
+ * \param img    Pointer to the image container that can load PCM samples to play.
+ * \param start  LBA offset within file where playback will begin, inclusive.
+ * \param length LBA length of play .
+ * \param swap   If false, little-endian sample order, otherwise big-endian.
+ * \return       True if successful, false otherwise.
+ */
+bool audio_play(uint8_t owner, image_config_t* img, uint32_t start, uint32_t length, bool swap);
+
+#endif
+
 
 /**
  * Pauses audio playback. This may be delayed slightly to allow sample buffers
@@ -154,8 +171,17 @@ void audio_set_channel(uint8_t id, uint16_t chn);
 */
 uint64_t audio_get_file_position();
 
+#ifdef ZULUSCSI_BLASTER
+/**
+ * Gets the LBA position in the audio image
+ * 
+ * \return LBA position in the audio image
+*/
+uint32_t audio_get_lba_position();
+#endif
+
 /**
  * Sets the playback position in the audio image via the lba
  * 
 */
-void audio_set_file_position(uint32_t lba);
+void audio_set_file_position(uint8_t id, uint32_t lba);

+ 2 - 2
src/ZuluSCSI_bootloader.cpp

@@ -43,8 +43,8 @@ bool find_firmware_image(FsFile &file, char name[MAX_FILE_PATH + 1])
 
         int namelen = file.getName(name, MAX_FILE_PATH);
 
-        if (namelen >= 11 &&
-            strncasecmp(name, "zuluscsi", 8) == 0 &&
+        if (namelen >= sizeof(FIRMWARE_NAME_PREFIX) + 3 &&
+            strncasecmp(name, FIRMWARE_NAME_PREFIX, sizeof(FIRMWARE_NAME_PREFIX) - 1) == 0 &&
             strncasecmp(name + namelen - 3, "bin", 3) == 0)
         {
             root.close();

+ 50 - 33
src/ZuluSCSI_cdrom.cpp

@@ -824,7 +824,7 @@ void doReadHeader(bool MSF, uint32_t lba, uint16_t allocationLength)
 {
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
 
-#if ENABLE_AUDIO_OUTPUT
+#ifdef ENABLE_AUDIO_OUTPUT
     // terminate audio playback if active on this target (Annex C)
     audio_stop(img.scsiId & 7);
 #endif
@@ -1358,7 +1358,11 @@ void cdromGetAudioPlaybackStatus(uint8_t *status, uint32_t *current_lba, bool cu
             *status = (uint8_t) audio_get_status_code(target);
         }
     }
+# ifdef ZULUSCSI_BLASTER
+    *current_lba = audio_get_lba_position();
+# else
     *current_lba = audio_get_file_position() / 2352;
+# endif
 #else
     if (status) *status = 0; // audio status code for 'unsupported/invalid' and not-playing indicator
 #endif
@@ -1367,7 +1371,7 @@ void cdromGetAudioPlaybackStatus(uint8_t *status, uint32_t *current_lba, bool cu
 
 static void doPlayAudio(uint32_t lba, uint32_t length)
 {
-#ifdef ENABLE_AUDIO_OUTPUT
+#if defined(ENABLE_AUDIO_OUTPUT) && !defined(ZULUSCSI_BLASTER)
     dbgmsg("------ CD-ROM Play Audio request at ", lba, " for ", length, " sectors");
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
     uint8_t target_id = img.scsiId & 7;
@@ -1382,7 +1386,7 @@ static void doPlayAudio(uint32_t lba, uint32_t length)
 
     if (length == 0)
     {
-        audio_set_file_position(lba);
+        audio_set_file_position(target_id, lba);
         scsiDev.status = 0;
         scsiDev.phase = STATUS;
         return;
@@ -1417,7 +1421,7 @@ static void doPlayAudio(uint32_t lba, uint32_t length)
 
         // playback request appears to be sane, so perform it
         // see earlier note for context on the block length below
-        if (!audio_play(target_id, &(img.file), offset,
+        if (!audio_play(target_id, &img, offset,
                 offset + length * trackinfo.sector_length, false))
         {
             // Underlying data/media error? Fake a disk scratch, which should
@@ -1433,6 +1437,41 @@ static void doPlayAudio(uint32_t lba, uint32_t length)
     }
     else
     {
+        // virtual drive supports audio, just not with this disk image
+        dbgmsg("---- Request to play audio on non-audio image");
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        scsiDev.target->sense.asc = 0x6400; // ILLEGAL MODE FOR THIS TRACK
+        scsiDev.phase = STATUS;
+    }
+#elif defined(ENABLE_AUDIO_OUTPUT_I2S) && defined(ZULUSCSI_BLASTER)
+    dbgmsg("------ CD-ROM Play Audio request at ", lba, " for ", length, " sectors");
+    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+    uint8_t target_id = img.scsiId & 7;
+
+    // if transfer length is zero no audio playback happens.
+    // don't treat as an error per SCSI-2; handle via short-circuit
+
+    if (lba == 0xFFFFFFFF)
+    {
+        // request to start playback from 'current position'
+        lba = audio_get_lba_position();
+    }
+    if (audio_play(target_id, &img, lba, length , false))
+    {
+        scsiDev.status = 0;
+        scsiDev.phase = STATUS;
+    }
+    else
+    {
+        // // Underlying data/media error? Fake a disk scratch, which should
+        // // be a condition most CD-DA players are expecting
+        // scsiDev.status = CHECK_CONDITION;
+        // scsiDev.target->sense.code = MEDIUM_ERROR;
+        // scsiDev.target->sense.asc = 0x1106; // CIRC UNRECOVERED ERROR
+        // scsiDev.phase = STATUS;
+        // return;
+        
         // virtual drive supports audio, just not with this disk image
         dbgmsg("---- Request to play audio on non-audio image");
         scsiDev.status = CHECK_CONDITION;
@@ -1524,7 +1563,7 @@ static void doReadCD(uint32_t lba, uint32_t length, uint8_t sector_type,
 {
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
 
-#if ENABLE_AUDIO_OUTPUT
+#ifdef ENABLE_AUDIO_OUTPUT 
     // terminate audio playback if active on this target (Annex C)
     audio_stop(img.scsiId & 7);
 #endif
@@ -1580,7 +1619,7 @@ static void doReadCD(uint32_t lba, uint32_t length, uint8_t sector_type,
     {
         offset = trackinfo.file_offset + trackinfo.sector_length * ((int64_t)lba - trackinfo.data_start);
         dbgmsg("------ Read CD: ", (int)length, " sectors starting at ", (int)lba,
-            ", track number ", trackinfo.track_number, ", sector size ", (int)trackinfo.sector_length,
+            ", track number ", trackinfo.track_number, ", track mode ", (int) trackinfo.track_mode,", sector size ", (int)trackinfo.sector_length,
             ", main channel ", main_channel, ", sub channel ", sub_channel,
             ", data offset in file ", (int)offset);
     }
@@ -1928,29 +1967,9 @@ static void doReadSubchannel(bool time, bool subq, uint8_t parameter, uint8_t tr
 static bool doReadCapacity(uint32_t lba, uint8_t pmi)
 {
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
-
-    CUEParser parser;
-    if (!loadCueSheet(img, parser))
+    uint32_t capacity = img.get_capacity_lba();
+    if (capacity > 0)
     {
-        // basic image, let the disk handler resolve
-        return false;
-    }
-
-    // find the last track on the disk
-    CUETrackInfo lasttrack = {0};
-    const CUETrackInfo *trackinfo;
-    uint64_t prev_capacity = 0;
-    while ((trackinfo = parser.next_track(prev_capacity)) != NULL)
-    {
-        lasttrack = *trackinfo;
-        cdromSelectBinFileForTrack(img, trackinfo);
-        prev_capacity = img.file.size();
-    }
-
-    uint32_t capacity = 0;
-    if (lasttrack.track_number != 0)
-    {
-        capacity = getLeadOutLBA(&lasttrack);
         capacity--; // shift to last addressable LBA
         if (pmi && lba && lba > capacity)
         {
@@ -1962,7 +1981,7 @@ static bool doReadCapacity(uint32_t lba, uint8_t pmi)
     }
     else
     {
-        logmsg("WARNING: unable to find capacity, no cue file found for ID ", img.scsiId);
+        logmsg("WARNING: unable to find capacity of device ID ", (int) 7 & img.scsiId);
     }
 
     scsiDev.data[0] = capacity >> 24;
@@ -2002,7 +2021,7 @@ extern "C" int scsiCDRomCommand()
     // Start/stop command
     if (command == 0x1B)
     {
-#if ENABLE_AUDIO_OUTPUT
+#ifdef ENABLE_AUDIO_OUTPUT
         // terminate audio playback if active on this target (MMC-1 Annex C)
         audio_stop(img.scsiId & 7);
 #endif
@@ -2186,7 +2205,7 @@ extern "C" int scsiCDRomCommand()
         {
             // request to start playback from 'current position'
 #ifdef ENABLE_AUDIO_OUTPUT
-            lba = audio_get_file_position() / AUDIO_CD_SECTOR_LEN;
+            lba = 0xFFFFFFFF;
 #endif
         }
 
@@ -2349,8 +2368,6 @@ extern "C" int scsiCDRomCommand()
         // SEEK
         // implement Annex C termination requirement and pass to disk handler
         doStopAudio();
-        // this may need more specific handling, the Win9x player appears to
-        // expect a pickup move to the given LBA
         commandHandled = 0;
     }
     else if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_APPLE

+ 2 - 3
src/ZuluSCSI_config.h

@@ -27,13 +27,12 @@
 #include <ZuluSCSI_platform_config.h>
 
 // Use variables for version number
-#define FW_VER_NUM      "25.02.21"
-#define FW_VER_SUFFIX   "release"
+#define FW_VER_NUM      "25.03.04"
+#define FW_VER_SUFFIX   "beta1"
 
 #define DEF_STRINGFY(DEF) STRINGFY(DEF)
 #define STRINGFY(STR) #STR
 #define FIRMWARE_NAME_PREFIX DEF_STRINGFY(BUILD_ENV)
-
 #define ZULU_FW_VERSION FW_VER_NUM "-" FW_VER_SUFFIX
 #define INQUIRY_NAME  PLATFORM_NAME " v" ZULU_FW_VERSION
 #define TOOLBOX_API 0

+ 71 - 3
src/ZuluSCSI_disk.cpp

@@ -32,7 +32,7 @@
 #include "ZuluSCSI_config.h"
 #include "ZuluSCSI_settings.h"
 #ifdef ENABLE_AUDIO_OUTPUT
-#include "ZuluSCSI_audio.h"
+#  include "ZuluSCSI_audio.h"
 #endif
 #include "ZuluSCSI_cdrom.h"
 #include "ImageBackingStore.h"
@@ -181,6 +181,52 @@ void image_config_t::clear()
     *this = empty;
 }
 
+uint32_t image_config_t::get_capacity_lba()
+{
+    if (bin_container.isOpen() && cuesheetfile.isOpen())
+    {
+        size_t halfbufsize = sizeof(scsiDev.data) / 2;
+        char *cuebuf = (char*)&scsiDev.data[halfbufsize];
+        cuesheetfile.seekSet(0);
+        int len = cuesheetfile.read(cuebuf, halfbufsize);
+        if (len == 0)
+            return 0;
+        CUEParser parser(cuebuf);
+        CUETrackInfo const *track;
+        CUETrackInfo last_track = {0};
+        if (bin_container.isDir())
+        {
+            FsFile bin_file;
+            uint64_t prev_capacity = 0;
+            // Find last track
+            while((track = parser.next_track(prev_capacity)) != nullptr)
+            {
+                last_track = *track;
+                if (!bin_file.open(&bin_container, track->filename, O_RDONLY | O_BINARY))
+                {
+                    dbgmsg("Unable to open cue/multi-bin image file \"", track->filename, "\" to determine total capacity");
+                    return 0;
+                }
+                prev_capacity = bin_file.size();
+                bin_file.close();
+            }
+            if (last_track.track_number != 0)
+                return last_track.data_start + prev_capacity / last_track.sector_length;
+            else
+                return 0;
+        }
+        else
+        {
+            // Single bin file
+            track = parser.next_track();
+            return track->data_start + bin_container.size() / track->sector_length;
+        }
+    }
+    else
+        return file.size() / bytesPerSector;
+
+}
+
 void scsiDiskCloseSDCardImages()
 {
     for (int i = 0; i < S2S_MAX_TARGETS; i++)
@@ -347,6 +393,7 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_lun, in
 {
     image_config_t &img = g_DiskImages[target_idx];
     img.cuesheetfile.close();
+    img.bin_container.close();
     img.cdrom_binfile_index = -1;
     scsiDiskSetImageConfig(target_idx);
     img.file = ImageBackingStore(filename, blocksize);
@@ -487,6 +534,14 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_lun, in
                     logmsg("---- Failed to parse cue sheet, using as plain binary image");
                     img.cuesheetfile.close();
                 }
+                else
+                {
+                    // Set bin container to single bin file
+                    img.bin_container.open(filename);
+                    // If bin container is a directory close the file
+                    if (img.bin_container.isDir())
+                        img.bin_container.close();
+                }
             }
             else
             {
@@ -511,7 +566,11 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_lun, in
                 }
             }
 
-            if (!valid)
+            if (valid)
+            {
+                img.bin_container.open(foldername);
+            }
+            else
             {
                 logmsg("No valid .cue sheet found in folder '", foldername, "'");
                 img.cuesheetfile.close();
@@ -1527,7 +1586,7 @@ static void doSeek(uint32_t lba)
 {
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
     uint32_t bytesPerSector = scsiDev.target->liveCfg.bytesPerSector;
-    uint32_t capacity = img.file.size() / bytesPerSector;
+    uint32_t capacity = img.get_capacity_lba();
 
     if (lba >= capacity)
     {
@@ -1545,6 +1604,15 @@ static void doSeek(uint32_t lba)
         }
         else
         {
+#ifdef ENABLE_AUDIO_OUTPUT
+            if (scsiDev.target->cfg->deviceType == S2S_CFG_OPTICAL)
+            {
+                // Uses audio play with a length of 0. CD audio won't actually play,
+                // but Read Subchannel will report the proper LBA location 
+                if (!audio_play(scsiDev.target->targetId, &img, lba, 0, false))
+                    dbgmsg("Failed to seek to audio track lba position ", (int) lba);
+            }
+#endif
             s2s_delay_us(10);
         }
     }

+ 5 - 0
src/ZuluSCSI_disk.h

@@ -86,6 +86,9 @@ struct image_config_t: public S2S_TargetCfg
     // Cue sheet file for CD-ROM images
     FsFile cuesheetfile;
 
+    // the bin file for the cue sheet, the directory for multi bin files, or closed if neither
+    FsFile bin_container;
+
     // Right-align vendor / product type strings
     // Standard SCSI uses left alignment
     int rightAlignStrings;
@@ -102,6 +105,8 @@ struct image_config_t: public S2S_TargetCfg
     // Clear any image state to zeros
     void clear();
 
+    uint32_t get_capacity_lba();
+
 private:
     // There should be only one global instance of this struct per device, so make copy constructor private.
     image_config_t(const image_config_t&) = default;

+ 9 - 9
src/ZuluSCSI_settings.cpp

@@ -38,15 +38,16 @@ const char *systemPresetName[] = {"", "Mac", "MacPlus", "MPC3000", "MegaSTE", "X
 const char *devicePresetName[] = {"", "ST32430N"};
 
 // must be in the same order as zuluscsi_speed_grade_t in ZuluSCSI_settings.h
-const char * const speed_grade_strings[7] =
+const char * const speed_grade_strings[] =
 {
     "Default",
     "TurboMax",
     "Custom",
-    "Audio",
-    "TurboA",
-    "TurboB",
-    "TurboC",
+    "AudioSPDIF",
+    "AudioI2S",
+    "A",
+    "B",
+    "C",
 };
 
 // Helper function for case-insensitive string compare
@@ -309,6 +310,7 @@ scsi_system_settings_t *ZuluSCSISettings::initSystem(const char *presetName)
     cfgSys.enableParity = true;
     cfgSys.useFATAllocSize = false;
     cfgSys.enableCDAudio = false;
+    cfgSys.maxVolume = 100;
     cfgSys.enableUSBMassStorage = false;
     cfgSys.usbMassStorageWaitPeriod = 1000;
     cfgSys.usbMassStoragePresentImages = false;
@@ -408,6 +410,7 @@ scsi_system_settings_t *ZuluSCSISettings::initSystem(const char *presetName)
     cfgSys.enableParity =  ini_getbool("SCSI", "EnableParity", cfgSys.enableParity, CONFIGFILE);
     cfgSys.useFATAllocSize = ini_getbool("SCSI", "UseFATAllocSize", cfgSys.useFATAllocSize, CONFIGFILE);
     cfgSys.enableCDAudio = ini_getbool("SCSI", "EnableCDAudio", cfgSys.enableCDAudio, CONFIGFILE);
+    cfgSys.maxVolume =  ini_getl("SCSI", "MaxVolume", cfgSys.maxVolume, CONFIGFILE);
 
     cfgSys.enableUSBMassStorage = ini_getbool("SCSI", "EnableUSBMassStorage", cfgSys.enableUSBMassStorage, CONFIGFILE);
     cfgSys.usbMassStorageWaitPeriod = ini_getl("SCSI", "USBMassStorageWaitPeriod", cfgSys.usbMassStorageWaitPeriod, CONFIGFILE);
@@ -526,10 +529,6 @@ zuluscsi_speed_grade_t ZuluSCSISettings::stringToSpeedGrade(const char *speed_gr
 {
     zuluscsi_speed_grade_t grade = zuluscsi_speed_grade_t::SPEED_GRADE_DEFAULT;
 
-#ifdef ENABLE_AUDIO_OUTPUT
-    logmsg("Audio output enabled, reclocking isn't possible");
-    return SPEED_GRADE_DEFAULT;
-#endif
     bool found_speed_grade = false;
     // search the list of speed grade strings for a matching target
     for (uint8_t i = 0; i < sizeof(speed_grade_strings)/sizeof(speed_grade_strings[0]); i++)
@@ -546,6 +545,7 @@ zuluscsi_speed_grade_t ZuluSCSISettings::stringToSpeedGrade(const char *speed_gr
       logmsg("Setting \"", speed_grade_target, "\" does not match any known speed grade, using default");
       grade = SPEED_GRADE_DEFAULT;
     }
+
     return grade;
 }
 

+ 3 - 1
src/ZuluSCSI_settings.h

@@ -26,7 +26,8 @@ typedef enum
     SPEED_GRADE_DEFAULT = 0,
     SPEED_GRADE_MAX,
     SPEED_GRADE_CUSTOM,
-    SPEED_GRADE_AUDIO,
+    SPEED_GRADE_AUDIO_SPDIF,
+    SPEED_GRADE_AUDIO_I2S,
     SPEED_GRADE_A,
     SPEED_GRADE_B,
     SPEED_GRADE_C,
@@ -80,6 +81,7 @@ typedef struct __attribute__((__packed__)) scsi_system_settings_t
     bool enableParity;
     bool useFATAllocSize;
     bool enableCDAudio;
+    uint8_t maxVolume;
     bool enableUSBMassStorage;
     uint16_t usbMassStorageWaitPeriod;
     bool usbMassStoragePresentImages;

+ 4 - 5
zuluscsi.ini

@@ -31,11 +31,9 @@
 #InitPreDelay = 0  # How many milliseconds to delay before the SCSI interface is initialized
 #InitPostDelay = 0 # How many milliseconds to delay after the SCSI interface is initialized
 
-# Will attempt to reclock the board if the board supports both reclocking and the choosen speed
-# SpeedGrade = Default # TurboMax - max speed, TurboA - mode A, TurboC, etc
-# When reclocking the board MaxSyncSpeed may need to be increased, see manual for details
-# MaxSyncSpeed = 10 # Set to 5, 10, or 20 to enable synchronous SCSI mode, 0 to disable
-
+# Will reclock the board if the board supports both reclocking and the chosen speed
+# SpeedGrade = Default # TurboMax - max speed, A, B, C, etc
+# MaxSyncSpeed = 20 # Set to 5, 10, or 20 to enable synchronous SCSI mode, 0 to disable
 
 # ROM settings
 #DisableROMDrive = 1 # Disable the ROM drive if it has been loaded to flash
@@ -53,6 +51,7 @@
 #InitiatorMSCStatusInterval = 5000 # Periodically report access status to log
 
 #EnableCDAudio = 0 # 1: Enable CD audio - an external I2S DAC on the v1.2 is required
+#MaxVolume = 100 # Set the percentage of the volume from 1 to 100 (default)
 
 # Settings that can be specified either per-device or for all devices.
 

+ 42 - 15
zuluscsi_timings.ini

@@ -4,7 +4,7 @@ disable = False # True: disable custom settings - default false: use the setting
 # extends_speed_grade lets you choose a built in timing setting as a template
 # this is the same value as would be set as SpeedGrade in zuluscsi.ini
 # this allows the ability to use a minimum of the below settings in your zuluscsi_timings.ini file
-extends_speed_grade = Default # Default, TurboMax, TurboA, TurboB, etc
+extends_speed_grade = Default # Default, TurboMax, A, B, etc
 
 # boot_with_sync value setting starts the board at an assumed sync value
 # boot_with_offset value setting starts the board at an assumed offset value
@@ -35,22 +35,35 @@ pd2 = 1 # Post divider 2
 clk_period_ps = 4000 # 1 / clk_hz * 10^12
 # Delay from data setup to REQ assertion.
 # deskew delay + cable skew delay = 55 ns minimum
-req_delay_cc = 14 # 55ns / clk_period_ps * 1000 rounded up
+req_delay_cc = 14 # 55ns / (clk_period_ps * 1000 rounded up)
 
 # The next settings are for different SCSI synchronous clock speeds
+    # delayX: Writing to SCSI bus transfer period timings
     # delay0: Data Setup Time - Delay from data write to REQ assertion
     # delay1: Transmit Assertion time from REQ assert to REQ deassert (req pulse)
     # delay2: Negation period - this value is calculated by the firmware: (total_delay - d0 - d1)
     # total_delay spec is the sync value * 4 in ns
+    # all values are in clock cycles minus 1 for the pio instruction delay
+    # except for total_period_adjust which is in clock cycles
 
-    # Delay0 and Delay1 values are in clock cycles minus 1 for the pio instruction delay
-    #  SCSI clock:  Ultra(20)     Fast(10)  SCSI-1(5)
-    # delay0 spec:     11.5ns         23ns       23ns
-    # delay1 spec:     16.5ns         33ns       53ns
-    # delay2 spec:   min 15ns     min 30ns   min 80ns
-    # total_delay_adjust is manual adjustment value, when checked with a scope
+    # rdelayX: Reading from the SCSI bus period timings
+    # rtotal_period_adjust: adjustment to total delay for rdelay0 calculation
+    # rdelay0: total_period + rtotal_period_adjust - rdelay1
+    # rdelay1: Transmit Assertion time from REQ assert to REQ deassert
+    # all values are in clock cycles minus 1 for the pio instruction delay,
+    # except for rtotal_period_adjust which is in clock cycles
 
-    # sync value is what the SCSI controller sends to the device to negotiate it' sync speed
+    # delay0 spec:  Ultra(20):  11.5ns  Fast(10): 25ns  SCSI-1(5): 25ns
+    # delay1 spec:  Ultra(20):  15ns    Fast(10): 30ns  SCSI-1(5): 80ns
+    # delay2 spec:  Ultra(20):  15ns    Fast(10): 30ns  SCSI-1(5): 80ns
+    # rdelay1 spec: Ultra(20):  15ns    Fast(10): 30ns  SCSI-1(5): 80ns
+
+    # delay0 spec:  Ultra(20):  11.5ns  Fast(10): 25ns  SCSI-1(5): 25ns
+    # delay1 spec:  Ultra(20):  15ns    Fast(10): 30ns  SCSI-1(5): 80ns
+    # delay2 spec:  Ultra(20):  15ns    Fast(10): 30ns  SCSI-1(5): 80ns
+    # rdelay1 spec: Ultra(20):  15ns    Fast(10): 30ns  SCSI-1(5): 80ns
+
+    # sync value is what the SCSI controller sends to the device to negotiate it's sync speed
     # These are the fastest sync values for each synchronous clock speed
     #   12 (48ns)  is for Fast20 21MB/s
     #   25 (100ns) is for Fast10 10MB/s
@@ -61,12 +74,14 @@ req_delay_cc = 14 # 55ns / clk_period_ps * 1000 rounded up
     # Though the ZuluSCSI generally isn't able to achieve that, it can get close on reads
 
 # the maximum value that delay0_cc and delay1_cc can be set to is 15
-    # due to the way the Programmable IO on the RP2 series chips work
+# due to the way the Programmable IO on the RP2 series chips work
 [scsi_20]
 # These are the Ultra (Fast 20) synchronous SCSI settings, SCSI running at 20MHz
-delay0_cc = 2 # 3 - 1 - delay 0 in clock cycles minus 1
-delay1_cc = 4 # 5 - 1 - delay 1 in clock cycles minus 1
-total_delay_adjust_cc = 1 # adjustment to the total sync period in clock cylces
+delay0_cc = 2 # 3 - 1 # delay0 in clock cycles minus 1
+delay1_cc = 4 # 5 - 1 # delay1 in clock cycles minus 1
+total_period_adjust_cc = 1 # adjustment to the total sync period in clock cycles
+read_delay1_cc = 4 # 5 - 1 # read delay 1 in clock cycles minus 1
+read_total_period_adjust_cc = -1 # adjustment to the total read sync period in clock cycles
 max_sync = 12 # the max sync supported by the board in Fast20, total sync periods in ns / 4
 
 [scsi_10]
@@ -74,13 +89,17 @@ max_sync = 12 # the max sync supported by the board in Fast20, total sync period
 delay0_cc = 5 # 6 - 1
 delay1_cc = 8 # 9 - 1
 total_delay_adjust_cc = 1
+read_delay1_cc = 8 # 9 - 1
+read_total_period_adjust_cc = 0
 max_sync = 25
 
 [scsi_5]
 # These are the SCSI-1 (Fast 5) synchronous SCSI settings, SCSI runnings at 5MHz
 delay0_cc = 15 # maxed out, should probably be around 16
 delay1_cc = 15 # maxed out, should probably be around be 30
-total_delay_adjust_cc = 1
+total_delay_adjust_cc = 0
+read_delay1_cc = 15 # maxed out, should probably be around be 30
+read_total_period_adjust_cc = 0
 max_sync = 50
 
 
@@ -88,7 +107,7 @@ max_sync = 50
 # These settings determine the clock setting and duty cycle of the SDIO clock
 clk_div_pio = 5 # clk_hz / clk_div_pio should be between 25MHz and 50MHz
 # SDIO first communicates to the SD card at 1MHz, clk_div_1mhz divides the SDIO clock down to 1MHz
-clk_div_1mhz = 50 # this should be the SDIO clock speed in MHz, but for some SDIO clockspeeds using a smaller value worked
+clk_div_1mhz = 30 # this should be the SDIO clock speed in MHz, but for some SDIO clockspeeds using a smaller value worked
 # delay0 and delay1 determine the duty cycle of the SDIO clock
 # their total should equal clk_div_pio. but they both have 1 subtracted from
 # them to accommodate the clock cycle a PIO instruction takes
@@ -96,4 +115,12 @@ clk_div_1mhz = 50 # this should be the SDIO clock speed in MHz, but for some SDI
 delay0 = 3 # 4 - 1
 delay1 = 0 # 1 - 1
 
+[audio]
+# Divider for 44.1KHz to the nearest integer with a sys clk frequency divided by 2 x 16-bit samples with the pio clock running 2x I2S clock
+# Example sys clock frequency of 155.25Mhz would be 155.25MHz/ 16 / 2 / 2 / 44.1KHz = 55.006 ~= 55
+clk_div_pio = 55
+# True: the I2S device is close to outputting 44.1KHz CD audio. False: System Clock speed is not conducive for CD audio
+clk_for_audio = false # If true, it will skip over a warning about the system clock not being clocked to an audio compatible frequency
+
+
 # Please post your findings to https://github.com/ZuluSCSI/ZuluSCSI-firmware/discussions