diff --git a/src/mame/ensoniq/esq5505.cpp b/src/mame/ensoniq/esq5505.cpp index 766bfa24cafb0..3ef54fbf7426d 100644 --- a/src/mame/ensoniq/esq5505.cpp +++ b/src/mame/ensoniq/esq5505.cpp @@ -178,6 +178,7 @@ #include "sound/esqpump.h" #include "emupal.h" #include "speaker.h" +#include "vfxcart.h" #include #include @@ -223,6 +224,7 @@ class esq5505_state : public driver_device , m_pump(*this, "pump") , m_fdc(*this, "wd1772") , m_floppy_connector(*this, "wd1772:0") + , m_cart(*this, "cart") , m_panel(*this, "panel") , m_dmac(*this, "mc68450") , m_mdout(*this, "mdout") @@ -258,6 +260,7 @@ class esq5505_state : public driver_device required_device m_pump; optional_device m_fdc; optional_device m_floppy_connector; + optional_device m_cart; required_device m_panel; optional_device m_dmac; required_device m_mdout; @@ -283,7 +286,18 @@ class esq5505_state : public driver_device int m_system_type = 0; uint8_t m_duart_io = 0; - + bool m_otis_irq = false; + bool m_floppy_dskchg = false; + bool m_docirq = false; + bool m_floppy_is_loaded = false; + bool m_floppy_is_active = false; + emu_timer *m_motor_on_timer; + emu_timer *m_dskchg_reset_timer; + + TIMER_CALLBACK_MEMBER(floppy_motor_on); + TIMER_CALLBACK_MEMBER(floppy_dskchg_reset); + + static void floppy_drives(device_slot_interface &device); static void floppy_formats(format_registration &fr); void eps_map(address_map &map) ATTR_COLD; @@ -294,15 +308,160 @@ class esq5505_state : public driver_device void cpu_space_map(address_map &map) ATTR_COLD; void eps_cpu_space_map(address_map &map) ATTR_COLD; + void update_floppy_inputs(); + void floppy_loaded(bool loaded); + void floppy_load(floppy_image_device *floppy); + void floppy_unload(floppy_image_device *floppy); + void cartridge_loaded(bool loaded); + void cartridge_load(ensoniq_vfx_cartridge *cart); + void cartridge_unload(ensoniq_vfx_cartridge *cart); + + void update_docirq_to_maincpu(); + void otis_irq(int irq); + uint16_t m_analog_values[8]; }; +void esq5505_state::cartridge_loaded(bool loaded) +{ + LOG("Cartridge %s\n", loaded ? "Inserted" : "Ejected"); + int state = loaded ? CLEAR_LINE : ASSERT_LINE; + + // On VFX and later, DUART input bit 1 is 0 for cartridge present. + LOG("ip1 -> %d\n", state); + m_duart->ip1_w(state); +} + +void esq5505_state::cartridge_load(ensoniq_vfx_cartridge *cart) +{ + (void) cart; + cartridge_loaded(true); +} + +void esq5505_state::cartridge_unload(ensoniq_vfx_cartridge *cart) +{ + (void) cart; + cartridge_loaded(false); +} + +void esq5505_state::floppy_drives(device_slot_interface &device) +{ + device.option_add_internal("35dd", FLOPPY_35_DD); +} + void esq5505_state::floppy_formats(format_registration &fr) { fr.add_mfm_containers(); fr.add(FLOPPY_ESQIMG_FORMAT); } +TIMER_CALLBACK_MEMBER(esq5505_state::floppy_motor_on) +{ + bool motor_on = param; + if (m_floppy_connector) + { + floppy_image_device *floppy = m_floppy_connector->get_device(); + if (floppy) + { + floppy->mon_w(motor_on ? CLEAR_LINE : ASSERT_LINE); // active low + m_panel->set_floppy_active(motor_on); + } + } +} + +TIMER_CALLBACK_MEMBER(esq5505_state::floppy_dskchg_reset) +{ + m_floppy_dskchg = !m_floppy_is_loaded; + LOG("Resetting floppy_dskchg -> %s\n", m_floppy_dskchg ? "true" : "false"); + update_docirq_to_maincpu(); +} + +void esq5505_state::update_floppy_inputs() +{ + // update the "Disk Ready" input + int state = (m_floppy_is_active && m_floppy_is_loaded) ? ASSERT_LINE : CLEAR_LINE; +#if VERBOSE + static int prev_state = 0; + if (prev_state != state) LOG("ip0 -> %d\n", state); + prev_state = state; +#endif + m_duart->ip0_w(state); + + // Also update the DOC IRQ in case there's a pending disk change to handle. + update_docirq_to_maincpu(); +} + +void esq5505_state::floppy_loaded(bool loaded) +{ + if (m_floppy_connector) + { + m_floppy_is_loaded = loaded; + if (!loaded) + { + // Only set m_floppy_dskchg; it will be reset a short time after + // the disk has been enabled while m_floppy_dskchg is true. + m_floppy_dskchg = true; + } + + LOG("Floppy %s\n", loaded ? "Inserted" : "Ejected"); + update_floppy_inputs(); + } + else + { + LOG("\n", loaded); + } +} + +void esq5505_state::floppy_load(floppy_image_device *floppy) +{ + (void) floppy; + floppy_loaded(true); +} + +void esq5505_state::floppy_unload(floppy_image_device *floppy) +{ + (void) floppy; + floppy_loaded(false); +} + +void esq5505_state::update_docirq_to_maincpu() +{ + bool floppy_dskchg_irq = m_floppy_is_active && m_floppy_dskchg; + if (floppy_dskchg_irq) LOG("docirq (m68k_irq1) due to disk change = %d\n", floppy_dskchg_irq); + if (floppy_dskchg_irq && m_floppy_is_loaded) + { + // The drives that Ensoniq use only _pulse_ DSKCHG for a brief time, when a disk is in the drive. + // schedule a reset. + LOG("Scheduling DSKCHG reset\n"); + m_dskchg_reset_timer->adjust(attotime::from_nsec(500)); + } + bool v = m_otis_irq || floppy_dskchg_irq; + if (v != m_docirq) + { + LOG("docirq (m68k_irq1) -> %d\n", v); + m_maincpu->set_input_line(M68K_IRQ_1, v); + m_docirq = v; + } + else + { + // LOG("m68k irq1 -- %d\n", v); + } +} + +void esq5505_state::otis_irq(int irq) +{ + if (irq != m_otis_irq) + { + // LOG("otis_irq -> %d\n", irq); + m_otis_irq = irq; + update_docirq_to_maincpu(); + } + else + { + // LOG("otis_irq -- %d\n", irq); + } +} + void esq5505_state::cpu_space_map(address_map &map) { map(0xfffff0, 0xffffff).m(m_maincpu, FUNC(m68000_base_device::autovectors_map)); @@ -317,11 +476,59 @@ void esq5505_state::eps_cpu_space_map(address_map &map) void esq5505_state::machine_start() { + LOG("machine_start()\n"); + if (m_floppy_connector) { + floppy_image_device *floppy = m_floppy_connector->get_device(); + if (floppy) { + floppy->setup_load_cb(floppy_image_device::load_cb(&esq5505_state::floppy_load, this)); + floppy->setup_unload_cb(floppy_image_device::unload_cb(&esq5505_state::floppy_unload, this)); + + m_motor_on_timer = timer_alloc(FUNC(esq5505_state::floppy_motor_on), this); + m_dskchg_reset_timer = timer_alloc(FUNC(esq5505_state::floppy_dskchg_reset), this); + + // Set DSKCHG according to whether there is a floppy in the drive. + if (floppy->exists()) { + LOG("\nFloppy Drive has Floppy '%s'\n", floppy->filename()); + m_floppy_dskchg = false; + } else { + LOG("\nFloppy Drive has No Floppy\n"); + m_floppy_dskchg = true; + } + } else { + LOG("\nFloppy Drive has No Image Device!\n"); + } + } else { + LOG("\nNo Floppy Drive\n"); + } + if (m_cart) { + m_cart->setup_load_cb(ensoniq_vfx_cartridge::load_cb(&esq5505_state::cartridge_load, this)); + m_cart->setup_unload_cb(ensoniq_vfx_cartridge::unload_cb(&esq5505_state::cartridge_unload, this)); + + if (m_cart->exists()) { + LOG("\nCartridge Slot has Cartridge '%s'\n", m_cart->filename()); + } else { + LOG("\nCartridge Slot has No Cartridge\n"); + } + } } void esq5505_state::machine_reset() { - floppy_image_device *floppy = m_floppy_connector ? m_floppy_connector->get_device() : nullptr; + // Check our image devices for load status. + if (m_floppy_connector) { + floppy_image_device *floppy = m_floppy_connector->get_device(); + if (floppy && floppy->exists()) { + floppy_load(floppy); + } else { + floppy_unload(floppy); + } + } + + if (m_cart && m_cart->exists()) { + cartridge_load(m_cart); + } else { + cartridge_unload(m_cart); + } // Default analog values: all values are 10 bits, left-justified within 16 bits. m_analog_values[0] = 0x7fc0; // pitch mod: start in the center @@ -332,27 +539,6 @@ void esq5505_state::machine_reset() m_analog_values[5] = 0xffc0; // Volume control: full on. m_analog_values[6] = 0x7fc0; // Battery voltage: something reasonable. m_analog_values[7] = 0x5540; // vRef to check battery. - - // on VFX, bit 0 is 1 for 'cartridge present'. - // on VFX-SD and later, bit 0 is2 1 for floppy present, bit 1 is 1 for cartridge present - if (strcmp(machine().system().name, "vfx") == 0) - { - // todo: handle VFX cart-in when we support cartridges - m_duart->ip0_w(ASSERT_LINE); - } - else - { - m_duart->ip1_w(CLEAR_LINE); - - if (floppy) - { - m_duart->ip0_w(CLEAR_LINE); - } - else - { - m_duart->ip0_w(ASSERT_LINE); - } - } } uint16_t esq5505_state::lower_r(offs_t offset) @@ -396,6 +582,7 @@ void esq5505_state::vfx_map(address_map &map) map(0x200000, 0x20001f).rw("otis", FUNC(es5505_device::read), FUNC(es5505_device::write)); map(0x280000, 0x28001f).rw(m_duart, FUNC(mc68681_device::read), FUNC(mc68681_device::write)).umask16(0x00ff); map(0x260000, 0x2601ff).rw(m_esp, FUNC(es5510_device::host_r), FUNC(es5510_device::host_w)).umask16(0x00ff); + map(0x2e0000, 0x2fffff).rw(m_cart, FUNC(ensoniq_vfx_cartridge::read), FUNC(ensoniq_vfx_cartridge::write)).umask16(0x00ff); map(0xc00000, 0xc1ffff).rom().region("osrom", 0); map(0xff0000, 0xffffff).ram().share("osram"); } @@ -407,6 +594,7 @@ void esq5505_state::vfxsd_map(address_map &map) map(0x280000, 0x28001f).rw(m_duart, FUNC(mc68681_device::read), FUNC(mc68681_device::write)).umask16(0x00ff); map(0x260000, 0x2601ff).rw(m_esp, FUNC(es5510_device::host_r), FUNC(es5510_device::host_w)).umask16(0x00ff); map(0x2c0000, 0x2c0007).rw(m_fdc, FUNC(wd1772_device::read), FUNC(wd1772_device::write)).umask16(0x00ff); + map(0x2e0000, 0x2fffff).rw(m_cart, FUNC(ensoniq_vfx_cartridge::read), FUNC(ensoniq_vfx_cartridge::write)).umask16(0x00ff); map(0x330000, 0x37ffff).ram().share("seqram"); map(0xc00000, 0xc3ffff).rom().region("osrom", 0); map(0xff0000, 0xffffff).ram().share("osram"); @@ -469,7 +657,7 @@ void esq5505_state::duart_output(uint8_t data) VFX-SD & SD-1 (32): bits 0/1/2 = analog sel bit 3 = SSEL (disk side) - bit 4 = DSEL (drive select?) + bit 4 = DSEL (Drive Delect and floppy Motor On) bit 6 = ESPHALT bit 7 = SACK (?) */ @@ -499,7 +687,23 @@ void esq5505_state::duart_output(uint8_t data) } else { - floppy->ss_w(((data & 8)>>3)^1); + floppy->ss_w(((data & 8) >> 3) ^ 1); // bit 3, inverted -> floppy + m_floppy_is_active = (data & 16) != 0; // bit 4 is used to activate the floppy: + if (m_floppy_is_active) + { + // immediately assert DISK SELECT (active low) + floppy->ds_w(CLEAR_LINE); + // but schedule a delayed MOTOR ON, since the keyboard constantly pulses this after a file has been read. + m_motor_on_timer->adjust(attotime::from_usec(100), 1); + } + else + { + // immediately deassert DISK SELECT (active low) + floppy->ds_w(ASSERT_LINE); + // but schedule a slightly delayed MOTOR OFF, since the keyboard sometimes seems to pulse this. + m_motor_on_timer->adjust(attotime::from_usec(50), 0); + } + update_floppy_inputs(); } } @@ -594,7 +798,7 @@ void esq5505_state::common(machine_config &config) m_otis->set_region0("waverom"); /* Bank 0 */ m_otis->set_region1("waverom2"); /* Bank 1 */ m_otis->set_channels(4); /* channels */ - m_otis->irq_cb().set_inputline(m_maincpu, M68K_IRQ_1); + m_otis->irq_cb().set(FUNC(esq5505_state::otis_irq)); m_otis->read_port_cb().set(FUNC(esq5505_state::analog_r)); /* ADC */ m_otis->add_route(0, "pump", 1.0, 0); m_otis->add_route(1, "pump", 1.0, 1); @@ -610,6 +814,8 @@ void esq5505_state::vfx(machine_config &config, int panel_type) { common(config); + ENSONIQ_VFX_CARTRIDGE(config, m_cart); + ESQPANEL2X40_VFX(config, m_panel, panel_type); m_panel->write_tx().set(m_duart, FUNC(mc68681_device::rx_b_w)); m_panel->write_analog().set(FUNC(esq5505_state::analog_w)); @@ -630,10 +836,7 @@ void esq5505_state::eps(machine_config &config) m_panel->write_analog().set(FUNC(esq5505_state::analog_w)); WD1772(config, m_fdc, 8_MHz_XTAL); - FLOPPY_CONNECTOR(config, m_floppy_connector); - m_floppy_connector->option_add("35dd", FLOPPY_35_DD); - m_floppy_connector->set_default_option("35dd"); - m_floppy_connector->set_formats(esq5505_state::floppy_formats); + FLOPPY_CONNECTOR(config, m_floppy_connector, esq5505_state::floppy_drives, "35dd", esq5505_state::floppy_formats, true);//.enable_sound(true); HD63450(config, m_dmac, 10_MHz_XTAL); // MC68450 compatible m_dmac->set_cpu_tag(m_maincpu); @@ -656,10 +859,7 @@ void esq5505_state::vfxsd(machine_config &config, int panel_type) m_pump->add_route(3, "aux", 1.0, 1); WD1772(config, m_fdc, 8000000); - FLOPPY_CONNECTOR(config, m_floppy_connector); - m_floppy_connector->option_add("35dd", FLOPPY_35_DD); - m_floppy_connector->set_default_option("35dd"); - m_floppy_connector->set_formats(esq5505_state::floppy_formats); + FLOPPY_CONNECTOR(config, m_floppy_connector, esq5505_state::floppy_drives, "35dd", esq5505_state::floppy_formats, true).enable_sound(true); } void esq5505_state::sd1(machine_config &config, int panel_type) @@ -724,7 +924,7 @@ void esq5505_state::common32(machine_config &config) m_otis->set_region0("waverom"); /* Bank 0 */ m_otis->set_region1("waverom2"); /* Bank 1 */ m_otis->set_channels(4); /* channels */ - m_otis->irq_cb().set_inputline(m_maincpu, M68K_IRQ_1); + m_otis->irq_cb().set(FUNC(esq5505_state::otis_irq)); m_otis->read_port_cb().set(FUNC(esq5505_state::analog_r)); /* ADC */ m_otis->add_route(0, "pump", 1.0, 0); m_otis->add_route(1, "pump", 1.0, 1); diff --git a/src/mame/ensoniq/vfxcart.cpp b/src/mame/ensoniq/vfxcart.cpp new file mode 100644 index 0000000000000..cf18eaef224d8 --- /dev/null +++ b/src/mame/ensoniq/vfxcart.cpp @@ -0,0 +1,153 @@ +// license:BSD-3-Clause +// copyright-holders:Christian Brunschen +#include "emu.h" +#include "vfxcart.h" + +#define VERBOSE 0 +#include "logmacro.h" + +DEFINE_DEVICE_TYPE(ENSONIQ_VFX_CARTRIDGE, ensoniq_vfx_cartridge, "ensoniq_vfx_cartridge", "Ensoniq VFX family Cartridge") + +ensoniq_vfx_cartridge::ensoniq_vfx_cartridge( + const machine_config &mconfig, + const char *tag, + device_t *owner, + u32 clock ) + : device_t(mconfig, ENSONIQ_VFX_CARTRIDGE, tag, owner, clock) + , device_image_interface(mconfig, *this) +{} + +ensoniq_vfx_cartridge::~ensoniq_vfx_cartridge() {} + +u8 ensoniq_vfx_cartridge::read(offs_t offset) { + m_state = state::IDLE; + auto v = m_storage[offset & MASK]; + if (offset > 0x7f00) LOG("R %04x -> %02x\r\n", offset, v); + return v; +} + +void ensoniq_vfx_cartridge::write(offs_t offset, u8 data) { + if (!m_is_writable) { + if (offset > 0x7f00) LOG("!W %04x (%02x)\r\n", offset, data); + return; + } + + // The Ensoniq VFX family use the common EEPROM software write protection scheme + // to protect their EEPROM cartridges from accidental writes. + // But they do it in a simplistic way: + // Each byte is written individually using a separate Enable Write Protection + // command sequence. + // This means that in a real EEPROM, writing each byte takes an additional + // T_BLC + T_WC ~= 5ms - for each byte. Writing an entire cartridge thus will take + // approximately 32000 * 5ms = 160 seconds = 3 minutes. + // However, to determine when they can write the next byte, the VFX family also + // read the just-written byte back: The EEPROM's "Toggle Bit Polling" + // and "/DATA Polling" facilities will keep returning something other than the written + // byte until the byte has been successfully written. + // Here, we can actually immediately write any written bytes to storage, and use + // the client's reading of the value to signal the end of the write cycle. + switch(m_state) { + case state::IDLE: + if (offset == 0x5555 && data == 0xaa) + m_state = state::CMD1; + else + m_state = state::IDLE; + break; + case state::CMD1: + if (offset == 0x2aaa && data == 0x55) + m_state = state::CMD2; + else + m_state = state::IDLE; + break; + case state::CMD2: + if (offset == 0x5555 && data == 0xa0) + m_state = state::WR; + else + m_state = state::IDLE; + break; + + case state::WR: + if (offset > 0x7f00) LOG("W %04x : %02x\r\n", offset, data); + m_storage[offset] = data; + break; + } +} + +void ensoniq_vfx_cartridge::setup_load_cb(load_cb cb) +{ + m_load_cb = cb; + if (!m_load_cb.isnull() && is_loaded()) { + m_load_cb(this); + } +} + +void ensoniq_vfx_cartridge::setup_unload_cb(unload_cb cb) +{ + m_unload_cb = cb; + if (!m_unload_cb.isnull() && !is_loaded()) { + m_unload_cb(this); + } +} + +std::pair ensoniq_vfx_cartridge::call_load() { + std::fill(std::begin(m_storage), std::end(m_storage), 0); + + LOG("Loading cartridge data from '%s'\r\n", filename()); + auto n = fread(&m_storage[0], m_storage.size()); + fseek(0, SEEK_END); + if (n < SIZE || ftell() != SIZE) { + return std::make_pair(image_error::INVALIDLENGTH, "Invalid size, must be 32kiB"); + } + + // If the file is read-only, treat it as a non-writable ROM file, even if it has an EEPROM filetype. + // This is to prevent users from loading a readonly EEPROM cartridge file, + // making changes to it, and quitting MAME expecting the image to have been written back, + // which could not happen because the file was read-only. + m_is_writable = !is_readonly() && (filetype() == "eeprom" || filetype() == "sc32"); // Ensoniq StorCart-32 + LOG("- loaded %d bytes, cartridge is %s\r\n", n, m_is_writable ? "Writable" : "Read-Only"); + + if (!m_load_cb.isnull()) { + m_load_cb(this); + } + + return std::make_pair(std::error_condition(), std::string()); +} + +std::pair ensoniq_vfx_cartridge::call_create(int format_type, util::option_resolution *format_options) { + LOG("Creating empty cartridge data in '%s'\r\n", filename()); + std::fill(std::begin(m_storage), std::end(m_storage), 0); + m_storage[0x7ffe] = 0x05; + m_storage[0x7fff] = 0x01; + fseek(0, SEEK_SET); + fwrite(&m_storage[0], m_storage.size()); + + // By definition, if we create a cartridge image, is kind of has to be a writable one: a completely empty + // read-only cartridge makes no sense! + // This allows users to create a cartridge image with a ".rom" or ".cart" extension, write to it _once_, + // in this session, until it is unloaded; and whenever in the future they load it again, it will be + // detected as ROM (_not_ EEPROM) by its file extension. + m_is_writable = true; + + // Creating a cartridge also loads it. + if (!m_load_cb.isnull()) { + m_load_cb(this); + } + + return std::make_pair(std::error_condition(), std::string()); +} + +void ensoniq_vfx_cartridge::call_unload() { + // If the current file is writable and is a writeable (EEPROM) file, write the data back + LOG("Unloading cartridge '%s'\r\n", filename()); + if (!is_readonly() && m_is_writable) { + LOG("Writing cartridge data to '%s'\r\n", filename()); + fseek(0, SEEK_SET); + fwrite(&m_storage[0], m_storage.size()); + } + + if (!m_unload_cb.isnull()) { + m_unload_cb(this); + } + + std::fill(std::begin(m_storage), std::end(m_storage), 0); +} diff --git a/src/mame/ensoniq/vfxcart.h b/src/mame/ensoniq/vfxcart.h new file mode 100644 index 0000000000000..4a3d3e10f0fdc --- /dev/null +++ b/src/mame/ensoniq/vfxcart.h @@ -0,0 +1,72 @@ +// license:BSD-3-Clause +// copyright-holders:Christian Brunschen +#ifndef MAME_ENSONIQ_VFXCART_H +#define MAME_ENSONIQ_VFXCART_H + +#pragma once + +#include "diimage.h" + +class ensoniq_vfx_cartridge: + public device_t, + public device_image_interface +{ +public: + typedef delegate load_cb; + typedef delegate unload_cb; + + ensoniq_vfx_cartridge( + const machine_config &mconfig, + const char *tag, + device_t *owner, + u32 clock = 0); + + virtual ~ensoniq_vfx_cartridge(); + + u8 read(offs_t offset); + void write(offs_t offset, u8 data); + virtual const char *filename() const { return device_image_interface::filename(); } + + void setup_load_cb(load_cb cb); + void setup_unload_cb(unload_cb cb); + + // device_image_interface overrides + virtual std::pair call_load() override; + virtual std::pair call_create(int format_type, util::option_resolution *format_options) override; + virtual void call_unload() override; + + virtual bool is_readable() const noexcept override { return true; } + virtual bool is_writeable() const noexcept override { return true; } + virtual bool is_creatable() const noexcept override { return true; } + virtual bool is_reset_on_load() const noexcept override { return false; } + virtual char const *file_extensions() const noexcept override { return "rom,cart,eeprom,sc32"; } + virtual char const *image_type_name() const noexcept override { return "cartridge"; } + virtual char const *image_brief_type_name() const noexcept override { return "cart"; } + +protected: + virtual void device_start() override ATTR_COLD {} + +private: + + static constexpr uint32_t SIZE = 32 * 1024; + static constexpr uint32_t MASK = SIZE - 1; + + enum class state : int { + IDLE, + CMD1, + CMD2, + WR + }; + state m_state = state::IDLE; + std::array m_storage; + bool m_is_writable; + + load_cb m_load_cb; + unload_cb m_unload_cb; +}; + + +// device type declaration +DECLARE_DEVICE_TYPE(ENSONIQ_VFX_CARTRIDGE, ensoniq_vfx_cartridge) + +#endif // MAME_ENSONIQ_VFXCART_H