Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add platform support for CD-ROM eject buttons #226

Merged
merged 5 commits into from May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Expand Up @@ -107,6 +107,27 @@ ZuluSCSI RP2040 DIP switch settings are:
- TERMINATION: Enable SCSI termination
- BOOTLOADER: Enable built-in USB bootloader, this DIP switch MUST remain off during normal operation.

Physical eject button for CDROM
-------------------------------
CD-ROM drives can be configured to eject when a physical button is pressed.
If multiple image files are configured with `IMG0`..`IMG9` config settings, ejection will switch between them.
Two separate buttons are supported and they can eject different drives.

[SCSI1]
Type=2 # CDROM drive
IMG0 = img0.iso
IMG1 = ...
EjectButton = 1

On GD32-based ZuluSCSI models (V1.0 and V1.1), buttons are connected to J303 12-pin expansion header.
Button 1 is connected between `PE5` and `GND`, and button 2 is connected between `PE6` and `GND`.
Pin locations are also shown in [this image](docs/ZuluSCSI_v1_1_buttons.jpg).

On RP2040-based ZuluSCSI models, buttons are connected to the I2C pins.
Button 1 is connected between `SDA` and `GND` and button 2 is connected between `SCL` and `GND`.
On full-size models, the pins are available on expansion header J303 ([image](docs/ZuluSCSI_RP2040_buttons.jpg)).
On compact model, pins are available on 4-pin I2C header J305 ([image](docs/ZuluSCSI_RP2040_compact_buttons.jpg)).

SCSI initiator mode
-------------------
The RP2040 model supports SCSI initiator mode for reading SCSI drives.
Expand Down
Binary file added docs/ZuluSCSI_RP2040_buttons.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ZuluSCSI_RP2040_compact_buttons.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ZuluSCSI_v1_1_buttons.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 25 additions & 1 deletion lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.cpp
Expand Up @@ -218,6 +218,10 @@ void platform_init()
gpio_bit_set(LED_PORT, LED_PINS);
gpio_init(LED_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, LED_PINS);

// Ejection buttons
gpio_init(EJECT_1_PORT, GPIO_MODE_IPU, 0, EJECT_1_PIN);
gpio_init(EJECT_2_PORT, GPIO_MODE_IPU, 0, EJECT_2_PIN);

// SWO trace pin on PB3
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3);
}
Expand Down Expand Up @@ -495,7 +499,27 @@ void platform_poll()

uint8_t platform_get_buttons()
{
return 0;
// Buttons are active low: internal pull-up is enabled,
// and when button is pressed the pin goes low.
uint8_t buttons = 0;
if (!gpio_input_bit_get(EJECT_1_PORT, EJECT_1_PIN)) buttons |= 1;
if (!gpio_input_bit_get(EJECT_2_PORT, EJECT_2_PIN)) buttons |= 2;

// Simple debouncing logic: handle button releases after 100 ms delay.
static uint32_t debounce;
static uint8_t buttons_debounced = 0;

if (buttons != 0)
{
buttons_debounced = buttons;
debounce = millis();
}
else if ((uint32_t)(millis() - debounce) > 100)
{
buttons_debounced = 0;
}

return buttons_debounced;
}

/***********************/
Expand Down
8 changes: 8 additions & 0 deletions lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_v1_0_gpio.h
Expand Up @@ -138,3 +138,11 @@
#define LED_PINS (LED_I_PIN | LED_E_PIN)
#define LED_ON() gpio_bit_reset(LED_PORT, LED_PINS)
#define LED_OFF() gpio_bit_set(LED_PORT, LED_PINS)

// Ejection buttons are available on expansion header J303.
// PE5 = channel 1, PE6 = channel 2
// Connect button between GPIO and GND pin.
#define EJECT_1_PORT GPIOE
#define EJECT_1_PIN GPIO_PIN_5
#define EJECT_2_PORT GPIOE
#define EJECT_2_PIN GPIO_PIN_6
10 changes: 9 additions & 1 deletion lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_v1_1_gpio.h
Expand Up @@ -182,4 +182,12 @@
#define LED_E_PIN GPIO_PIN_5
#define LED_PINS (LED_I_PIN | LED_E_PIN)
#define LED_ON() gpio_bit_reset(LED_PORT, LED_PINS)
#define LED_OFF() gpio_bit_set(LED_PORT, LED_PINS)
#define LED_OFF() gpio_bit_set(LED_PORT, LED_PINS)

// Ejection buttons are available on expansion header J303.
// PE5 = channel 1, PE6 = channel 2
// Connect button between GPIO and GND pin.
#define EJECT_1_PORT GPIOE
#define EJECT_1_PIN GPIO_PIN_5
#define EJECT_2_PORT GPIOE
#define EJECT_2_PIN GPIO_PIN_6
30 changes: 24 additions & 6 deletions lib/ZuluSCSI_platform_RP2040/ZuluSCSI_platform.cpp
Expand Up @@ -621,14 +621,32 @@ void platform_poll()

uint8_t platform_get_buttons()
{
#ifdef ENABLE_AUDIO_OUTPUT
uint8_t pins = 0x00;
uint8_t buttons = 0;

#if defined(ENABLE_AUDIO_OUTPUT)
// pulled to VCC via resistor, sinking when pressed
if (!gpio_get(GPIO_EXP_SPARE)) pins |= 0x01;
return pins;
#else
return 0;
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

// Simple debouncing logic: handle button releases after 100 ms delay.
static uint32_t debounce;
static uint8_t buttons_debounced = 0;

if (buttons != 0)
{
buttons_debounced = buttons;
debounce = millis();
}
else if ((uint32_t)(millis() - debounce) > 100)
{
buttons_debounced = 0;
}

return buttons_debounced;
}

/*****************************************/
Expand Down
21 changes: 17 additions & 4 deletions src/ZuluSCSI_cdrom.cpp
Expand Up @@ -817,9 +817,22 @@ void cdromPerformEject(image_config_t &img)
// terminate audio playback if active on this target (MMC-1 Annex C)
audio_stop(target);
#endif
dbgmsg("------ CDROM open tray on ID ", (int)target);
img.ejected = true;
img.cdrom_events = 3; // Media removal
if (!img.ejected)
{
dbgmsg("------ CDROM open tray on ID ", (int)target);
img.ejected = true;
img.cdrom_events = 3; // Media removal
}
else
{
dbgmsg("------ CDROM close tray on ID ", (int)target);
if (!cdromSwitchNextImage(img))
{
// Reinsert the single image
img.ejected = false;
img.cdrom_events = 2; // New media
}
}
}

// Reinsert any ejected CDROMs on reboot
Expand Down Expand Up @@ -904,7 +917,7 @@ static void doGetEventStatusNotification(bool immed)
scsiDev.phase = DATA_IN;
img.cdrom_events = 0;

if (img.ejected)
if (img.ejected && img.reinsert_after_eject)
{
// We are now reporting to host that the drive is open.
// Simulate a "close" for next time the host polls.
Expand Down
24 changes: 19 additions & 5 deletions src/ZuluSCSI_disk.cpp
Expand Up @@ -174,6 +174,8 @@ void scsiDiskCloseSDCardImages()
{
g_DiskImages[i].file.close();
}

g_DiskImages[i].cuesheetfile.close();
}
}

Expand Down Expand Up @@ -326,6 +328,7 @@ static void setDefaultDriveInfo(int target_idx)
bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int scsi_lun, int blocksize, S2S_CFG_TYPE type)
{
image_config_t &img = g_DiskImages[target_idx];
img.cuesheetfile.close();
img.file = ImageBackingStore(filename, blocksize);

if (img.file.isOpen())
Expand Down Expand Up @@ -478,6 +481,7 @@ static void scsiDiskLoadConfig(int target_idx, const char *section)
img.rightAlignStrings = ini_getbool(section, "RightAlignStrings", 0, CONFIGFILE);
img.prefetchbytes = ini_getl(section, "PrefetchBytes", img.prefetchbytes, CONFIGFILE);
img.reinsert_on_inquiry = ini_getbool(section, "ReinsertCDOnInquiry", 1, CONFIGFILE);
img.reinsert_after_eject = ini_getbool(section, "ReinsertAfterEject", 1, CONFIGFILE);
img.ejectButton = ini_getl(section, "EjectButton", 0, CONFIGFILE);

char tmp[32];
Expand Down Expand Up @@ -559,18 +563,25 @@ image_config_t &scsiDiskGetImageConfig(int target_idx)

static void diskEjectAction(uint8_t buttonId)
{
logmsg("Eject button pressed for channel ", buttonId);
bool found = false;
for (uint8_t i = 0; i < S2S_MAX_TARGETS; i++)
{
image_config_t img = g_DiskImages[i];
image_config_t &img = g_DiskImages[i];
if (img.ejectButton == buttonId)
{
if (img.deviceType == S2S_CFG_OPTICAL)
{
found = true;
logmsg("Eject button ", (int)buttonId, " pressed, passing to CD drive SCSI", (int)i);
cdromPerformEject(img);
}
}
}

if (!found)
{
logmsg("Eject button ", (int)buttonId, " pressed, but no drives with EjectButton=", (int)buttonId, " setting found!");
}
}

uint8_t diskEjectButtonUpdate(bool immediate)
Expand Down Expand Up @@ -897,9 +908,12 @@ static int doTestUnitReady()
scsiDev.target->sense.asc = MEDIUM_NOT_PRESENT;
scsiDev.phase = STATUS;

// We are now reporting to host that the drive is open.
// Simulate a "close" for next time the host polls.
cdromSwitchNextImage(img);
if (img.reinsert_after_eject)
{
// We are now reporting to host that the drive is open.
// Simulate a "close" for next time the host polls.
cdromSwitchNextImage(img);
}
}
else if (unlikely(!(blockDev.state & DISK_PRESENT)))
{
Expand Down
7 changes: 6 additions & 1 deletion src/ZuluSCSI_disk.h
Expand Up @@ -42,12 +42,17 @@ extern "C" {
// Extended configuration stored alongside the normal SCSI2SD target information
struct image_config_t: public S2S_TargetCfg
{
// There should be only one global instance of this struct per device, so disallow copy constructor.
image_config_t() = default;
image_config_t(const image_config_t&) = delete;

ImageBackingStore file;

// For CD-ROM drive ejection
bool ejected;
uint8_t cdrom_events;
bool reinsert_on_inquiry;
bool reinsert_on_inquiry; // Reinsert on Inquiry command (to reinsert automatically after boot)
bool reinsert_after_eject; // Reinsert next image after ejection

// selects a physical button channel that will cause an eject action
// default option of '0' disables this functionality
Expand Down
2 changes: 2 additions & 0 deletions zuluscsi.ini
Expand Up @@ -41,6 +41,8 @@
#RightAlignStrings = 0 # Right-align SCSI vendor / product strings, defaults on if Quirks = 1
#PrefetchBytes = 8192 # Maximum number of bytes to prefetch after a read request, 0 to disable
#ReinsertCDOnInquiry = 1 # Reinsert any ejected CD-ROM image on Inquiry command
#ReinsertAfterEject = 1 # Reinsert next CD image after eject, if multiple images configured.
#EjectButton = 0 # Enable eject by button 1 or 2, or set 0 to disable

# Settings can be overridden for individual devices.
#[SCSI2]
Expand Down