Skip to content
Open
3 changes: 3 additions & 0 deletions lib/BlueSCSI_platform_RP2MCU/BlueSCSI_platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,9 @@ void platform_late_init()

// Act as SCSI initiator
void platform_initiator_gpio_setup() {
#ifdef ENABLE_AUDIO_OUTPUT_SPDIF
audio_setup_release();
#endif
// pin function pup pdown out state fast
gpio_conf(SCSI_IN_IO, GPIO_FUNC_SIO, false, false, false, true, false);
gpio_conf(SCSI_IN_MSG, GPIO_FUNC_SIO, false, false, false, true, false);
Expand Down
13 changes: 13 additions & 0 deletions lib/BlueSCSI_platform_RP2MCU/audio_i2s.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,10 @@ bool audio_is_active() {
return !audio_idle;
}

bool audio_is_paused() {
return audio_paused;
}

bool audio_is_playing(uint8_t id) {
// return audio_playing;
return audio_owner == (id & S2S_CFG_TARGET_ID_BITS) && audio_playing;
Expand Down Expand Up @@ -924,5 +928,14 @@ void audio_set_file_position(uint8_t id, const CUETrackInfo *trackinfo, uint32_t
setup_playback(id, lba, 0, false);
}

#ifdef UNIT_TEST
/* Test accessor */
extern "C" void i2s_test_start(uint8_t owner)
{
audio_owner = owner;
audio_playing = true;
audio_idle = false;
}
#endif

#endif // ENABLE_AUDIO_OUTPUT_SPDIF
49 changes: 40 additions & 9 deletions lib/BlueSCSI_platform_RP2MCU/audio_spdif.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ static uint16_t wire_buf_b[WIRE_BUFFER_SIZE];
static uint8_t snd_stop_irqs = 0; // DMA IRQs seen since audio_stop() began

// tracking for audio playback
static uint8_t audio_owner; // SCSI ID or 0xFF when idle
static uint8_t audio_owner = 0xFF; // SCSI ID or 0xFF when idle
static volatile bool audio_paused = false;
static ImageBackingStore* audio_file;
static uint64_t fpos;
Expand Down Expand Up @@ -193,9 +193,14 @@ static uint8_t invert = 0; // biphase encode help: set if last wire bit was '1'
#ifndef SPDIF_PIO_INSTANCE
# ifdef BLUESCSI_MCU_RP20XX
# define SPDIF_PIO_INSTANCE pio0
# define SPDIF_PIO_SM 0
# else
# define SPDIF_PIO_INSTANCE pio2
# endif
#endif
#ifndef SPDIF_PIO_SM
# ifdef BLUESCSI_MCU_RP20XX
# define SPDIF_PIO_SM 0
# else
# define SPDIF_PIO_SM 1
# endif
#endif
Expand Down Expand Up @@ -392,11 +397,20 @@ bool audio_is_active() {
return audio_owner != 0xFF && g_scsi_settings.getSystem()->enableCDAudio;
}

bool audio_is_paused() {
return audio_paused;
}

bool audio_is_playing(uint8_t id) {
return audio_owner == (id & S2S_CFG_TARGET_ID_BITS);
}

void audio_setup() {
if (platform_is_initiator_mode_enabled()) {
// the SCSI host code owns the state machine, see audio_setup_release()
audio_setup_failed = true;
return;
}
if (!g_scsi_settings.getSystem()->enableCDAudio) {
logmsg("Audio setup skipped, this build does not support CD Audio");
audio_setup_failed = true;
Expand All @@ -416,10 +430,6 @@ void audio_setup() {
logmsg("BlueSCSI CD Audio Enabled - Connect DAC to BlueSCSI or use SPDIF on I2C SCL pin");
#endif

if (platform_set_smps_pwm(true)) {
logmsg("Regulator set to PWM mode for CD audio");
}

// Calculate clock divider, rounding up as necessary
double clkdiv = ((double)(g_bluescsi_timings->clk_hz) / (double)(5644800));

Expand Down Expand Up @@ -453,6 +463,10 @@ void audio_setup() {
already_claimed = true;
}

if (platform_set_smps_pwm(true)) {
logmsg("Regulator set to PWM mode for CD audio");
}

if (SPDIF_OUTPUT_PIN != GPIO_EXP_SPARE) {
gpio_put(GPIO_EXP_SPARE, true);
gpio_set_dir(GPIO_EXP_SPARE, false);
Expand All @@ -474,6 +488,24 @@ void audio_setup() {
#endif
}

// Initiator mode: the SCSI host code takes over PIO0 on RP2040. Put the state
// machine and the output pin back as a build without audio has them.
void audio_setup_release() {
audio_stop();
if (already_claimed) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_set_pins(&c, 0, 5); // PINCTRL reset value
pio_sm_init(SPDIF_PIO_UNIT, spdif_pio_sm, 0, &c);
gpio_put(SPDIF_OUTPUT_PIN, true);
gpio_set_dir(SPDIF_OUTPUT_PIN, false);
gpio_set_pulls(SPDIF_OUTPUT_PIN, true, false);
gpio_set_function(SPDIF_OUTPUT_PIN, GPIO_FUNC_I2C);
platform_set_smps_pwm(false);
already_claimed = false;
}
audio_setup_failed = true;
}

void audio_poll() {
if (!audio_is_active() || !g_scsi_settings.getSystem()->enableCDAudio || audio_setup_failed) return;
if (audio_paused) return;
Expand Down Expand Up @@ -649,6 +681,7 @@ void audio_stop(uint8_t id) {
if (audio_setup_failed) {
return;
}
if (audio_owner == 0xFF) return;
if (id != 0xFF && audio_owner != (id & S2S_CFG_TARGET_ID_BITS)) return;

// to help mute external hardware, send a bunch of '0' samples prior to
Expand All @@ -661,9 +694,7 @@ void audio_stop(uint8_t id) {
// and wait for them to shut down naturally
snd_stop_irqs = 0;
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();
while (!pio_sm_is_tx_fifo_empty(SPDIF_PIO_UNIT, spdif_pio_sm)) tight_loop_contents();
while (dma_channel_is_busy(SOUND_DMA_CHA) || !pio_sm_is_tx_fifo_empty(SPDIF_PIO_UNIT, spdif_pio_sm)) tight_loop_contents();
audio_stopping = false;

// idle the subsystem
Expand Down
7 changes: 7 additions & 0 deletions lib/BlueSCSI_platform_RP2MCU/audio_spdif.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* Copyright (C) 2023 saybur
* Copyright (c) 2026 Eric Helgeson <eric@bluescsi.com>
*
* 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
Expand Down Expand Up @@ -55,6 +56,12 @@ bool audio_is_active();
*/
void audio_setup();

/**
* Hands the PIO state machine and the output pin back when initiator mode is
* enabled. Audio stays off afterwards.
*/
void audio_setup_release();

/**
* Called from platform_poll() to fill sample buffer(s) if needed.
*/
Expand Down
7 changes: 6 additions & 1 deletion lib/BlueSCSI_platform_RP2MCU/scsi_accel_target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
// B: Lookup from g_scsi_parity_check_lookup and copy to scsi_read_parity PIO
// C: Addresses from scsi_accel_read PIO to lookup DMA READ_ADDR register
// D: From pacer to data state machine to trigger transfers
#ifdef BLUESCSI_NETWORK
#if defined(BLUESCSI_NETWORK) || defined(ENABLE_AUDIO_OUTPUT_SPDIF)
# define SCSI_DMA_CH_A 6
# define SCSI_DMA_CH_B 7
# define SCSI_DMA_CH_C 8
Expand All @@ -92,6 +92,11 @@
# define SCSI_DMA_CH_D 3
#endif

#ifdef ENABLE_AUDIO_OUTPUT_SPDIF
#define SCSI_DMA_USES(ch) ((ch) == SCSI_DMA_CH_A || (ch) == SCSI_DMA_CH_B || (ch) == SCSI_DMA_CH_C || (ch) == SCSI_DMA_CH_D)
static_assert(!SCSI_DMA_USES(SOUND_DMA_CHA) && !SCSI_DMA_USES(SOUND_DMA_CHB), "SCSI and S/PDIF audio share a DMA channel");
#endif

static struct {
uint8_t *app_buf; // Buffer provided by application
uint32_t app_bytes; // Bytes available in application buffer
Expand Down
15 changes: 8 additions & 7 deletions lib/BlueSCSI_platform_RP2MCU/sdio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#if defined(SD_USE_SDIO) && !defined(SD_USE_RP2350_SDIO)

#include "sdio.h"
#include "sdio_timeout.h"
#include "sdio_write_response.h"
#include <hardware/pio.h>
#include <hardware/dma.h>
Expand Down Expand Up @@ -86,8 +87,8 @@ static struct {
pio_sm_config pio_cfg_data_tx;

sdio_transfer_state_t transfer_state;
uint32_t transfer_start_time; // restarted on every completed block
uint32_t burst_start_time; // start of the whole multi-block transfer
volatile uint32_t transfer_start_time; // restarted on every completed block, from the DMA IRQ
volatile uint32_t burst_start_time; // start of the whole multi-block transfer
uint32_t *data_buf;
uint32_t blocks_done; // Number of blocks transferred so far
uint32_t total_blocks; // Total number of blocks to transfer
Expand Down Expand Up @@ -225,7 +226,7 @@ sdio_status_t receive_status_register(uint8_t* sds) {
{
// SD Spec says CMD6 transaction timeout is 100ms
// ACMD13 is likely similar
if ((uint32_t)(platform_millis() - g_sdio.transfer_start_time) > 100)
if (sdio_deadline_passed(&g_sdio.transfer_start_time, platform_millis, 100))
{

#ifdef SDIO_DEBUG
Expand Down Expand Up @@ -792,8 +793,8 @@ sdio_status_t rp2040_sdio_rx_poll(uint32_t *bytes_complete)
return SDIO_ERR_DATA_CRC;
}
}
else if ((uint32_t)(platform_millis() - g_sdio.transfer_start_time) > SDIO_BLOCK_TIMEOUT_MS ||
(uint32_t)(platform_millis() - g_sdio.burst_start_time) > SDIO_BURST_TIMEOUT_MS)
else if (sdio_deadline_passed(&g_sdio.transfer_start_time, platform_millis, SDIO_BLOCK_TIMEOUT_MS) ||
sdio_deadline_passed(&g_sdio.burst_start_time, platform_millis, SDIO_BURST_TIMEOUT_MS))
{
sdio_log_data_timeout("rp2040_sdio_rx_poll()", SDIO_DATA_SM, g_sdio.pio_data_rx_offset);
rp2040_sdio_stop();
Expand Down Expand Up @@ -1036,8 +1037,8 @@ sdio_status_t rp2040_sdio_tx_poll(uint32_t *bytes_complete)
rp2040_sdio_stop();
return g_sdio.wr_status;
}
else if ((uint32_t)(platform_millis() - g_sdio.transfer_start_time) > SDIO_BLOCK_TIMEOUT_MS ||
(uint32_t)(platform_millis() - g_sdio.burst_start_time) > SDIO_BURST_TIMEOUT_MS)
else if (sdio_deadline_passed(&g_sdio.transfer_start_time, platform_millis, SDIO_BLOCK_TIMEOUT_MS) ||
sdio_deadline_passed(&g_sdio.burst_start_time, platform_millis, SDIO_BURST_TIMEOUT_MS))
{
sdio_log_data_timeout("rp2040_sdio_tx_poll()", SDIO_CMD_SM, g_sdio.pio_data_tx_offset);

Expand Down
38 changes: 38 additions & 0 deletions lib/BlueSCSI_platform_RP2MCU/sdio_timeout.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* BlueSCSI - Copyright (c) 2026 Eric Helgeson <eric@bluescsi.com>
*
* BlueSCSI firmware is licensed under the GPL version 3 or any later version.
*
* https://www.gnu.org/licenses/gpl-3.0.html
* ----
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/

// Deadline check for a start time that an interrupt rewrites while the main
// loop polls. The start time is read before the clock, so an IRQ landing
// between the two reads can only make the block look younger. Reading the
// clock first lets a start time from the future wrap the subtraction to
// ~4.29e9 ms, which killed SD write bursts that were microseconds old.

#pragma once

#include <stdint.h>
#include <stdbool.h>

static inline bool sdio_deadline_passed(const volatile uint32_t *start_ms, uint32_t (*now_ms)(void), uint32_t limit_ms)
{
uint32_t start = *start_ms;
uint32_t now = now_ms();
return (uint32_t)(now - start) > limit_ms;
}
4 changes: 2 additions & 2 deletions src/BlueSCSI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ void save_logfile(bool always = false)
bool every_command = g_log_debug;
#ifdef ENABLE_AUDIO_OUTPUT
// An SD write can stall for 100-500 ms and CD audio buffers 46 ms,
// so batch the saves while a track plays.
if (audio_is_active()) every_command = false;
// so batch the saves while a track plays. A paused track reads nothing.
if (audio_is_active() && !audio_is_paused()) every_command = false;
#endif
bool buffer_filling = (loglen - prev_log_pos) > LOGBUFSIZE / 2;
if (always || every_command || buffer_filling || (LOG_SAVE_INTERVAL_MS > 0 && (uint32_t)(platform_millis() - prev_log_save) > LOG_SAVE_INTERVAL_MS))
Expand Down
5 changes: 5 additions & 0 deletions src/BlueSCSI_audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ bool audio_is_playing(uint8_t id);
*/
bool audio_is_active();

/**
* True while the active track is paused. Implemented by the platform backend.
*/
bool audio_is_paused();

/**
* Begins audio playback for a file.
*
Expand Down
Loading