Browse Source

Initial implementation of synchronous mode for RP2040.

Not fully performance optimized yet.
Petteri Aimonen 3 years ago
parent
commit
193c61d600

+ 5 - 0
lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.h

@@ -14,8 +14,13 @@ extern "C" {
 extern const char *g_azplatform_name;
 #define PLATFORM_NAME "ZuluSCSI RP2040"
 #define PLATFORM_REVISION "2.0"
+#define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_10
 #define SD_USE_SDIO 1
 
+// NOTE: The driver supports synchronous speeds higher than 10MB/s, but this
+// has not been tested due to lack of fast enough SCSI adapter.
+// #define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_TURBO
+
 // Debug logging function, can be used to print to e.g. serial port.
 // May get called from interrupt handlers.
 void azplatform_log(const char *s);

+ 2 - 2
lib/ZuluSCSI_platform_RP2040/rp2040_sdio.cpp

@@ -19,8 +19,8 @@
 #define SDIO_PIO pio1
 #define SDIO_CMD_SM 0
 #define SDIO_DATA_SM 1
-#define SDIO_DMA_CH 1
-#define SDIO_DMA_CHB 2
+#define SDIO_DMA_CH 2
+#define SDIO_DMA_CHB 3
 
 // Maximum number of 512 byte blocks to transfer in one request
 #define SDIO_MAX_BLOCKS 256

+ 10 - 0
lib/ZuluSCSI_platform_RP2040/scsiPhy.cpp

@@ -166,6 +166,16 @@ extern "C" uint32_t scsiEnterPhaseImmediate(int phase)
         g_scsi_phase = (SCSI_PHASE)phase;
         scsiLogPhaseChange(phase);
 
+        // Select between synchronous vs. asynchronous SCSI writes
+        if (g_scsi_phase == DATA_IN && scsiDev.target->syncOffset > 0)
+        {
+            scsi_accel_rp2040_setWriteMode(scsiDev.target->syncOffset, scsiDev.target->syncPeriod);
+        }
+        else
+        {
+            scsi_accel_rp2040_setWriteMode(0, 0);
+        }
+
         if (phase < 0)
         {
             // Other communication on bus or reset state

+ 23 - 1
lib/ZuluSCSI_platform_RP2040/scsi_accel.pio

@@ -27,6 +27,7 @@
     wait 0 gpio ACK             side 0  ; Assert REQ, wait for ACK low
 
 ; Read from SCSI bus using asynchronous handshake.
+; Also works for synchronous mode down to 50 ns transfer period.
 ; Data is returned as 16-bit words that contain the 8 data bits + 1 parity bit.
 ; Number of bytes to receive minus 1 should be written to TX fifo.
 ; Number of bytes to receive must be divisible by 2.
@@ -41,6 +42,27 @@ start:
     wait 0 gpio ACK             side 0  ; Assert REQ, wait for ACK low
     in pins, 9                  side 1  ; Deassert REQ, read GPIO
     in null, 7                  side 1  ; Padding bits
-    push iffull block           side 1  ; Put data to RX FIFO
     jmp x-- start               side 1  ; Decrement byte count and jump to start
 
+; Data state machine for synchronous writes.
+; Takes the lowest 9 bits of each 16 bit word and writes them to bus with REQ pulse.
+; The delay times will be rewritten by C code to match the negotiated SCSI sync speed.
+;
+; Shifts one bit to ISR per every byte transmitted. This is used to control the transfer
+; pace, the RX fifo acts as a counter to keep track of unacknowledged bytes. The C code
+; can set the syncOffset by changing autopush threshold, e.g. threshold 3 = 12 bytes offset.
+.program scsi_sync_write
+    .side_set 1
+
+    out pins, 9     [0]         side 1  ; Write data and parity bit, wait for deskew delay
+    out null, 7     [0]         side 0  ; Assert REQ, wait for assert time
+    in null, 1      [0]         side 1  ; Deassert REQ, wait for transfer period, wait for space in ACK buffer
+
+; Data pacing state machine for synchronous writes.
+; Takes one bit from ISR on every falling edge of ACK.
+; The C code should set autopull threshold to match scsi_sync_write autopush threshold.
+; System DMA will then move words from scsi_sync_write RX fifo to scsi_sync_write_pacer TX fifo.
+.program scsi_sync_write_pacer
+    wait 1 gpio ACK
+    wait 0 gpio ACK   ; Wait for falling edge on ACK
+    out null, 1       ; Let scsi_sync_write send one more byte

+ 61 - 4
lib/ZuluSCSI_platform_RP2040/scsi_accel.pio.h

@@ -45,7 +45,7 @@ static inline pio_sm_config scsi_accel_async_write_program_get_default_config(ui
 // --------------------- //
 
 #define scsi_accel_async_read_wrap_target 0
-#define scsi_accel_async_read_wrap 7
+#define scsi_accel_async_read_wrap 6
 
 static const uint16_t scsi_accel_async_read_program_instructions[] = {
             //     .wrap_target
@@ -55,15 +55,14 @@ static const uint16_t scsi_accel_async_read_program_instructions[] = {
     0x200a, //  3: wait   0 gpio, 10      side 0     
     0x5009, //  4: in     pins, 9         side 1     
     0x5067, //  5: in     null, 7         side 1     
-    0x9060, //  6: push   iffull block    side 1     
-    0x1042, //  7: jmp    x--, 2          side 1     
+    0x1042, //  6: jmp    x--, 2          side 1
             //     .wrap
 };
 
 #if !PICO_NO_HARDWARE
 static const struct pio_program scsi_accel_async_read_program = {
     .instructions = scsi_accel_async_read_program_instructions,
-    .length = 8,
+    .length = 7,
     .origin = -1,
 };
 
@@ -75,3 +74,61 @@ static inline pio_sm_config scsi_accel_async_read_program_get_default_config(uin
 }
 #endif
 
+// --------------- //
+// scsi_sync_write //
+// --------------- //
+
+#define scsi_sync_write_wrap_target 0
+#define scsi_sync_write_wrap 2
+
+static const uint16_t scsi_sync_write_program_instructions[] = {
+            //     .wrap_target
+    0x7009, //  0: out    pins, 9         side 1
+    0x6067, //  1: out    null, 7         side 0
+    0x5061, //  2: in     null, 1         side 1
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program scsi_sync_write_program = {
+    .instructions = scsi_sync_write_program_instructions,
+    .length = 3,
+    .origin = -1,
+};
+
+static inline pio_sm_config scsi_sync_write_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + scsi_sync_write_wrap_target, offset + scsi_sync_write_wrap);
+    sm_config_set_sideset(&c, 1, false, false);
+    return c;
+}
+#endif
+
+// --------------------- //
+// scsi_sync_write_pacer //
+// --------------------- //
+
+#define scsi_sync_write_pacer_wrap_target 0
+#define scsi_sync_write_pacer_wrap 2
+
+static const uint16_t scsi_sync_write_pacer_program_instructions[] = {
+            //     .wrap_target
+    0x208a, //  0: wait   1 gpio, 10
+    0x200a, //  1: wait   0 gpio, 10
+    0x6061, //  2: out    null, 1
+            //     .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program scsi_sync_write_pacer_program = {
+    .instructions = scsi_sync_write_pacer_program_instructions,
+    .length = 3,
+    .origin = -1,
+};
+
+static inline pio_sm_config scsi_sync_write_pacer_program_get_default_config(uint offset) {
+    pio_sm_config c = pio_get_default_sm_config();
+    sm_config_set_wrap(&c, offset + scsi_sync_write_pacer_wrap_target, offset + scsi_sync_write_pacer_wrap);
+    return c;
+}
+#endif

+ 217 - 18
lib/ZuluSCSI_platform_RP2040/scsi_accel_rp2040.cpp

@@ -18,6 +18,8 @@
 #define SCSI_DMA_PIO pio0
 #define SCSI_DMA_SM 0
 #define SCSI_DMA_CH 0
+#define SCSI_DMA_SYNC_SM 1
+#define SCSI_DMA_SYNC_CH 1
 
 enum scsidma_buf_sel_t { SCSIBUF_NONE = 0, SCSIBUF_A = 1, SCSIBUF_B = 2 };
 
@@ -30,14 +32,25 @@ static struct {
     uint8_t *next_app_buf; // Next buffer from application after current one finishes
     uint32_t next_app_bytes; // Bytes in next buffer
 
+    // Synchronous mode?
+    int syncOffset;
+    int syncPeriod;
+    int syncOffsetDivider; // Autopush/autopull threshold for the write pacer state machine
+    int syncOffsetPreload; // Number of items to preload in the RX fifo of scsi_sync_write
+
     // PIO configurations
     uint32_t pio_offset_async_write;
     uint32_t pio_offset_async_read;
+    uint32_t pio_offset_sync_write_pacer;
+    uint32_t pio_offset_sync_write;
     pio_sm_config pio_cfg_async_write;
     pio_sm_config pio_cfg_async_read;
+    pio_sm_config pio_cfg_sync_write_pacer;
+    pio_sm_config pio_cfg_sync_write;
 
     // DMA configurations
-    dma_channel_config dma_write_config;
+    dma_channel_config dma_write_config; // Data from RAM to first state machine
+    dma_channel_config dma_write_pacer_config; // In synchronous mode only, transfer between state machines
 
     // We use two DMA buffers alternatively
     // The buffer contains the data bytes with parity added.
@@ -154,15 +167,50 @@ static void start_dma_write()
     g_scsi_dma.dma_countA = refill_dmabuf(g_scsi_dma.dma_bufA);
     g_scsi_dma.dma_countB = refill_dmabuf(g_scsi_dma.dma_bufB);
     
-    // Start DMA from buffer A
-    g_scsi_dma.dma_current_buf = SCSIBUF_A;
-    dma_channel_configure(SCSI_DMA_CH,
-        &g_scsi_dma.dma_write_config,
-        &SCSI_DMA_PIO->txf[SCSI_DMA_SM],
-        g_scsi_dma.dma_bufA,
-        g_scsi_dma.dma_countA,
-        true
-    );
+    if (g_scsi_dma.syncOffset == 0)
+    {
+        // Asynchronous mode
+        // Start DMA from buffer A
+        g_scsi_dma.dma_current_buf = SCSIBUF_A;
+        dma_channel_configure(SCSI_DMA_CH,
+            &g_scsi_dma.dma_write_config,
+            &SCSI_DMA_PIO->txf[SCSI_DMA_SM],
+            g_scsi_dma.dma_bufA,
+            g_scsi_dma.dma_countA,
+            true
+        );
+
+        // Enable state machine
+        pio_sm_set_enabled(SCSI_DMA_PIO, SCSI_DMA_SM, true);
+    }
+    else
+    {
+        // Synchronous mode
+
+        // Start DMA transfer to move dummy bits to write pacer
+        dma_channel_configure(SCSI_DMA_SYNC_CH,
+            &g_scsi_dma.dma_write_pacer_config,
+            &SCSI_DMA_PIO->txf[SCSI_DMA_SYNC_CH],
+            &SCSI_DMA_PIO->rxf[SCSI_DMA_SM],
+            0xFFFFFFFF,
+            true
+        );
+
+        // Start DMA transfer to move data from buffer A to data writer
+        g_scsi_dma.dma_current_buf = SCSIBUF_A;
+        dma_channel_configure(SCSI_DMA_CH,
+            &g_scsi_dma.dma_write_config,
+            &SCSI_DMA_PIO->txf[SCSI_DMA_SM],
+            g_scsi_dma.dma_bufA,
+            g_scsi_dma.dma_countA,
+            true
+        );
+
+        // Enable state machines
+        pio_sm_set_enabled(SCSI_DMA_PIO, SCSI_DMA_SYNC_SM, true);
+        pio_sm_set_enabled(SCSI_DMA_PIO, SCSI_DMA_SM, true);
+    }
+
 }
 
 static void scsi_dma_write_irq()
@@ -282,9 +330,40 @@ void scsi_accel_rp2040_startWrite(const uint8_t* data, uint32_t count, volatile
     if (must_reconfig_gpio)
     {
         SCSI_ENABLE_DATA_OUT();
-        pio_sm_init(SCSI_DMA_PIO, SCSI_DMA_SM, g_scsi_dma.pio_offset_async_write, &g_scsi_dma.pio_cfg_async_write);
-        scsidma_config_gpio();
-        pio_sm_set_enabled(SCSI_DMA_PIO, SCSI_DMA_SM, true);
+
+        if (g_scsi_dma.syncOffset == 0)
+        {
+            // Asynchronous write
+            pio_sm_init(SCSI_DMA_PIO, SCSI_DMA_SM, g_scsi_dma.pio_offset_async_write, &g_scsi_dma.pio_cfg_async_write);
+            scsidma_config_gpio();
+        }
+        else
+        {
+            // Synchronous write
+            // First state machine writes data to SCSI bus and dummy bits to its RX fifo.
+            // Second state machine empties the dummy bits every time ACK is received, to control the transmit pace.
+            pio_sm_init(SCSI_DMA_PIO, SCSI_DMA_SM, g_scsi_dma.pio_offset_sync_write, &g_scsi_dma.pio_cfg_sync_write);
+            pio_sm_init(SCSI_DMA_PIO, SCSI_DMA_SYNC_SM, g_scsi_dma.pio_offset_sync_write_pacer, &g_scsi_dma.pio_cfg_sync_write_pacer);
+            scsidma_config_gpio();
+
+            // Prefill RX fifo to set the syncOffset
+            for (int i = 0; i < g_scsi_dma.syncOffsetPreload; i++)
+            {
+                pio_sm_exec(SCSI_DMA_PIO, SCSI_DMA_SM,
+                    pio_encode_push(false, false) | pio_encode_sideset(1, 1));
+            }
+
+            // Fill the pacer TX fifo
+            // DMA should start transferring only after ACK pulses are received
+            for (int i = 0; i < 4; i++)
+            {
+                pio_sm_put(SCSI_DMA_PIO, SCSI_DMA_SYNC_SM, 0);
+            }
+
+            // Fill the pacer OSR
+            pio_sm_exec(SCSI_DMA_PIO, SCSI_DMA_SYNC_SM,
+                pio_encode_mov(pio_osr, pio_null));
+        }
         
         dma_channel_set_irq0_enabled(SCSI_DMA_CH, true);
         irq_set_exclusive_handler(DMA_IRQ_0, scsi_dma_write_irq);
@@ -326,23 +405,30 @@ bool scsi_accel_rp2040_isWriteFinished(const uint8_t* data)
 void scsi_accel_rp2040_stopWrite(volatile int *resetFlag)
 {
     // Wait for TX fifo to be empty and ACK to go high
+    // For synchronous writes wait for all ACKs to be received also
     uint32_t start = millis();
-    while ((!pio_sm_is_tx_fifo_empty(SCSI_DMA_PIO, SCSI_DMA_SM) || SCSI_IN(ACK)) && !*resetFlag)
+    while ((!pio_sm_is_tx_fifo_empty(SCSI_DMA_PIO, SCSI_DMA_SM)
+            || pio_sm_get_rx_fifo_level(SCSI_DMA_PIO, SCSI_DMA_SM) > g_scsi_dma.syncOffsetPreload
+            || SCSI_IN(ACK)) && !*resetFlag)
     {
         if ((uint32_t)(millis() - start) > 5000)
         {
-            azlog("scsi_accel_rp2040_stopWrite() timeout");
+            azlog("scsi_accel_rp2040_stopWrite() timeout, FIFO levels ",
+                (int)pio_sm_get_tx_fifo_level(SCSI_DMA_PIO, SCSI_DMA_SM), " ",
+                (int)pio_sm_get_rx_fifo_level(SCSI_DMA_PIO, SCSI_DMA_SM));
             *resetFlag = 1;
             break;
         }
     }
 
     dma_channel_abort(SCSI_DMA_CH);
+    dma_channel_abort(SCSI_DMA_SYNC_CH);
     dma_channel_set_irq0_enabled(SCSI_DMA_CH, false);
     g_scsi_dma_state = SCSIDMA_IDLE;
     SCSI_RELEASE_DATA_REQ();
     scsidma_config_gpio();
     pio_sm_set_enabled(SCSI_DMA_PIO, SCSI_DMA_SM, false);
+    pio_sm_set_enabled(SCSI_DMA_PIO, SCSI_DMA_SYNC_SM, false);
 }
 
 void scsi_accel_rp2040_finishWrite(volatile int *resetFlag)
@@ -352,7 +438,11 @@ void scsi_accel_rp2040_finishWrite(volatile int *resetFlag)
     {
         if ((uint32_t)(millis() - start) > 5000)
         {
-            azlog("scsi_accel_rp2040_finishWrite() timeout");
+            azlog("scsi_accel_rp2040_finishWrite() timeout,"
+             " state: ", (int)g_scsi_dma_state, " ", (int)g_scsi_dma.dma_current_buf, " ", (int)g_scsi_dma.dma_countA, " ", (int)g_scsi_dma.dma_countB,
+             " PIO PC: ", (int)pio_sm_get_pc(SCSI_DMA_PIO, SCSI_DMA_SM), " ", (int)pio_sm_get_pc(SCSI_DMA_PIO, SCSI_DMA_SYNC_SM),
+             " PIO FIFO: ", (int)pio_sm_get_tx_fifo_level(SCSI_DMA_PIO, SCSI_DMA_SM), " ", (int)pio_sm_get_tx_fifo_level(SCSI_DMA_PIO, SCSI_DMA_SYNC_SM),
+             " DMA counts: ", dma_hw->ch[SCSI_DMA_CH].al2_transfer_count, " ", dma_hw->ch[SCSI_DMA_SYNC_CH].al2_transfer_count);
             *resetFlag = 1;
             break;
         }
@@ -446,14 +536,27 @@ void scsi_accel_rp2040_init()
     sm_config_set_fifo_join(&g_scsi_dma.pio_cfg_async_write, PIO_FIFO_JOIN_TX);
     sm_config_set_out_shift(&g_scsi_dma.pio_cfg_async_write, true, false, 32);
 
-    // Asynchronous SCSI read
+    // Asynchronous / synchronous SCSI read
     g_scsi_dma.pio_offset_async_read = pio_add_program(SCSI_DMA_PIO, &scsi_accel_async_read_program);
     g_scsi_dma.pio_cfg_async_read = scsi_accel_async_read_program_get_default_config(g_scsi_dma.pio_offset_async_read);
     sm_config_set_in_pins(&g_scsi_dma.pio_cfg_async_read, SCSI_IO_DB0);
     sm_config_set_sideset_pins(&g_scsi_dma.pio_cfg_async_read, SCSI_OUT_REQ);
-    sm_config_set_out_shift(&g_scsi_dma.pio_cfg_async_write, true, false, 32);
+    sm_config_set_out_shift(&g_scsi_dma.pio_cfg_async_read, true, false, 32);
     sm_config_set_in_shift(&g_scsi_dma.pio_cfg_async_read, true, true, 32);
 
+    // Synchronous SCSI write pacer / ACK handler
+    g_scsi_dma.pio_offset_sync_write_pacer = pio_add_program(SCSI_DMA_PIO, &scsi_sync_write_pacer_program);
+    g_scsi_dma.pio_cfg_sync_write_pacer = scsi_sync_write_pacer_program_get_default_config(g_scsi_dma.pio_offset_sync_write_pacer);
+    sm_config_set_out_shift(&g_scsi_dma.pio_cfg_sync_write_pacer, true, true, 1);
+
+    // Synchronous SCSI data writer
+    g_scsi_dma.pio_offset_sync_write = pio_add_program(SCSI_DMA_PIO, &scsi_sync_write_program);
+    g_scsi_dma.pio_cfg_sync_write = scsi_sync_write_program_get_default_config(g_scsi_dma.pio_offset_sync_write);
+    sm_config_set_out_pins(&g_scsi_dma.pio_cfg_sync_write, SCSI_IO_DB0, 9);
+    sm_config_set_sideset_pins(&g_scsi_dma.pio_cfg_sync_write, SCSI_OUT_REQ);
+    sm_config_set_out_shift(&g_scsi_dma.pio_cfg_sync_write, true, true, 32);
+    sm_config_set_in_shift(&g_scsi_dma.pio_cfg_sync_write, true, true, 1);
+
     // Create DMA channel configuration so it can be applied quickly later
     dma_channel_config cfg = dma_channel_get_default_config(SCSI_DMA_CH);
     channel_config_set_transfer_data_size(&cfg, DMA_SIZE_32);
@@ -461,4 +564,100 @@ void scsi_accel_rp2040_init()
     channel_config_set_write_increment(&cfg, false);
     channel_config_set_dreq(&cfg, pio_get_dreq(SCSI_DMA_PIO, SCSI_DMA_SM, true));
     g_scsi_dma.dma_write_config = cfg;
+
+    // In synchronous mode a second DMA channel is used to transfer dummy bits
+    // from first state machine to second one.
+    cfg = dma_channel_get_default_config(SCSI_DMA_SYNC_CH);
+    channel_config_set_transfer_data_size(&cfg, DMA_SIZE_32);
+    channel_config_set_read_increment(&cfg, false);
+    channel_config_set_write_increment(&cfg, false);
+    channel_config_set_dreq(&cfg, pio_get_dreq(SCSI_DMA_PIO, SCSI_DMA_SYNC_SM, true));
+    g_scsi_dma.dma_write_pacer_config = cfg;
 }
+
+void scsi_accel_rp2040_setWriteMode(int syncOffset, int syncPeriod)
+{
+    if (syncOffset != g_scsi_dma.syncOffset || syncPeriod != g_scsi_dma.syncPeriod)
+    {
+        g_scsi_dma.syncOffset = syncOffset;
+        g_scsi_dma.syncPeriod = syncPeriod;
+
+        if (syncOffset > 0)
+        {
+            // Set up offset amount to PIO state machine configs.
+            // The RX fifo of scsi_sync_write has 4 slots.
+            // We can preload it with 0-3 items and set the autopush threshold 1, 2, 4 ... 32
+            // to act as a divider. This allows offsets 1 to 128 bytes.
+            // SCSI2SD code currently only uses offsets up to 15.
+            if (syncOffset <= 4)
+            {
+                g_scsi_dma.syncOffsetDivider = 1;
+                g_scsi_dma.syncOffsetPreload = 5 - syncOffset;
+            }
+            else if (syncOffset <= 8)
+            {
+                g_scsi_dma.syncOffsetDivider = 2;
+                g_scsi_dma.syncOffsetPreload = 5 - syncOffset / 2;
+            }
+            else if (syncOffset <= 16)
+            {
+                g_scsi_dma.syncOffsetDivider = 4;
+                g_scsi_dma.syncOffsetPreload = 5 - syncOffset / 4;
+            }
+            else
+            {
+                g_scsi_dma.syncOffsetDivider = 4;
+                g_scsi_dma.syncOffsetPreload = 0;
+            }
+
+            // To properly detect when all bytes have been ACKed,
+            // we need at least one vacant slot in the FIFO.
+            if (g_scsi_dma.syncOffsetPreload > 3)
+                g_scsi_dma.syncOffsetPreload = 3;
+
+            sm_config_set_out_shift(&g_scsi_dma.pio_cfg_sync_write_pacer, true, true, g_scsi_dma.syncOffsetDivider);
+            sm_config_set_in_shift(&g_scsi_dma.pio_cfg_sync_write, true, true, g_scsi_dma.syncOffsetDivider);
+
+            // Set up the timing parameters to PIO program
+            // The scsi_sync_write PIO program consists of three instructions.
+            // The delays are in clock cycles, each taking 8 ns.
+            // delay0: Delay from data write to REQ assertion
+            // delay1: Delay from REQ assert to REQ deassert
+            // delay2: Delay from REQ deassert to data write
+            int delay0, delay1, delay2;
+            int totalDelay = syncPeriod * 4 / 8;
+
+            if (syncPeriod <= 25)
+            {
+                // Fast SCSI timing: 30 ns assertion period, 25 ns skew delay
+                // The hardware rise and fall time require some extra delay,
+                // the values below are tuned based on oscilloscope measurements.
+                delay0 = 3;
+                delay1 = 5;
+                delay2 = totalDelay - delay0 - delay1 - 3;
+                if (delay2 < 0) delay2 = 0;
+                if (delay2 > 15) delay2 = 15;
+            }
+            else
+            {
+                // Slow SCSI timing: 90 ns assertion period, 55 ns skew delay
+                delay0 = 6;
+                delay1 = 12;
+                delay2 = totalDelay - delay0 - delay1 - 3;
+                if (delay2 < 0) delay2 = 0;
+                if (delay2 > 15) delay2 = 15;
+            }
+
+            // Patch the delay values into the instructions.
+            // The code in scsi_accel.pio must have delay set to 0 for this to work correctly.
+            uint16_t instr0 = scsi_sync_write_program_instructions[0] | pio_encode_delay(delay0);
+            uint16_t instr1 = scsi_sync_write_program_instructions[1] | pio_encode_delay(delay1);
+            uint16_t instr2 = scsi_sync_write_program_instructions[2] | pio_encode_delay(delay2);
+
+            SCSI_DMA_PIO->instr_mem[g_scsi_dma.pio_offset_sync_write + 0] = instr0;
+            SCSI_DMA_PIO->instr_mem[g_scsi_dma.pio_offset_sync_write + 1] = instr1;
+            SCSI_DMA_PIO->instr_mem[g_scsi_dma.pio_offset_sync_write + 2] = instr2;
+        }
+    }
+
+}

+ 8 - 1
lib/ZuluSCSI_platform_RP2040/scsi_accel_rp2040.h

@@ -6,6 +6,11 @@
 
 void scsi_accel_rp2040_init();
 
+// Set SCSI access mode for write requests.
+// Setting syncOffset = 0 enables asynchronous SCSI.
+// Setting syncOffset > 0 enables synchronous SCSI.
+void scsi_accel_rp2040_setWriteMode(int syncOffset, int syncPeriod);
+
 void scsi_accel_rp2040_startWrite(const uint8_t* data, uint32_t count, volatile int *resetFlag);
 void scsi_accel_rp2040_stopWrite(volatile int *resetFlag);
 void scsi_accel_rp2040_finishWrite(volatile int *resetFlag);
@@ -14,4 +19,6 @@ void scsi_accel_rp2040_finishWrite(volatile int *resetFlag);
 // If data is NULL, checks if all writes have completed.
 bool scsi_accel_rp2040_isWriteFinished(const uint8_t* data);
 
-void scsi_accel_rp2040_read(uint8_t *buf, uint32_t count, int *parityError, volatile int *resetFlag);
+// Read data from SCSI bus.
+// Works for both asynchronous and synchronous modes.
+void scsi_accel_rp2040_read(uint8_t *buf, uint32_t count, int *parityError, volatile int *resetFlag);