// license:BSD-3-Clause // copyright-holders:Nathan Woods /********************************************************************* cococart.cpp CoCo/Dragon cartridge slot - typically used for "Program Paks" (which are simple ROMs) but in practice is the main extensibility mechanism for CoCo hardware CoCo and Dragon pinout listing --- ------- --- ------- 1 -12V 21 A2 2 +12V 22 A3 3 HALT 23 A4 4 NMI 24 A5 5 RESET 25 A6 6 EIN 26 A7 7 QIN 27 A8 8 CART 28 A9 9 +5V 29 A10 10 D0 30 A11 11 D1 31 A12 12 D2 32 CTS 13 D3 33 GND 14 D4 34 GND 15 D5 35 SND 16 D6 36 SCS 17 D7 37 A13 18 R/!W 38 A14 19 A0 39 A15 20 A1 40 SLENB Notes: CTS - ROM read $C000-$FEFF ($FDFF on CoCo 3) SCS - Spare Chip Select: IO space between $FF40-5F *********************************************************************/ #include "emu.h" #include "cococart.h" #include "formats/rpk.h" #include "coco_dcmodem.h" #include "coco_fdc.h" #include "coco_gmc.h" #include "coco_ide.h" #include "coco_max.h" #include "coco_midi.h" #include "coco_multi.h" #include "coco_orch90.h" #include "coco_pak.h" #include "coco_psg.h" #include "coco_ram.h" #include "coco_rs232.h" #include "coco_ssc.h" #include "coco_stecomp.h" #include "coco_sym12.h" #include "coco_wpk.h" #include "coco_wpk2p.h" #include "dragon_amtor.h" #include "dragon_claw.h" #include "dragon_fdc.h" #include "dragon_jcbsnd.h" #include "dragon_jcbspch.h" #include "dragon_msx2.h" #include "dragon_serial.h" #include "dragon_sprites.h" /*************************************************************************** PARAMETERS ***************************************************************************/ //#define LOG_GENERAL (1U << 0) //defined in logmacro.h already #define LOG_CART (1U << 1) // shows cart line changes #define LOG_NMI (1U << 2) // shows switch changes #define LOG_HALT (1U << 3) // shows switch changes // #define VERBOSE (LOG_CART) #include "logmacro.h" #define LOGCART(...) LOGMASKED(LOG_CART, __VA_ARGS__) #define LOGNMI(...) LOGMASKED(LOG_NMI, __VA_ARGS__) #define LOGHALT(...) LOGMASKED(LOG_HALT, __VA_ARGS__) /*************************************************************************** CONSTANTS ***************************************************************************/ // definitions of RPK PCBs in layout.xml static const char *coco_rpk_pcbdefs[] = { "standard", "paged16k", nullptr }; // ...and their mappings to "default card slots" static const char *coco_rpk_cardslottypes[] = { "pak", "banked_16k" }; //************************************************************************** // GLOBAL VARIABLES //************************************************************************** DEFINE_DEVICE_TYPE(COCOCART_SLOT, cococart_slot_device, "cococart_slot", "CoCo Cartridge Slot") //************************************************************************** // LIVE DEVICE //************************************************************************** ALLOW_SAVE_TYPE(cococart_slot_device::line_value); //------------------------------------------------- // cococart_slot_device - constructor //------------------------------------------------- cococart_slot_device::cococart_slot_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : device_t(mconfig, COCOCART_SLOT, tag, owner, clock), device_single_card_slot_interface(mconfig, *this), device_cartrom_image_interface(mconfig, *this), m_cart_callback(*this), m_nmi_callback(*this), m_halt_callback(*this), m_cart(nullptr) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void cococart_slot_device::device_start() { for(int i=0; i < TIMER_POOL; i++ ) { m_cart_line.timer[i] = timer_alloc(FUNC(cococart_slot_device::cart_line_timer_tick), this); m_nmi_line.timer[i] = timer_alloc(FUNC(cococart_slot_device::nmi_line_timer_tick), this); m_halt_line.timer[i] = timer_alloc(FUNC(cococart_slot_device::halt_line_timer_tick), this); } m_cart_line.timer_index = 0; m_cart_line.delay = 0; m_cart_line.value = line_value::CLEAR; m_cart_line.line = 0; m_cart_line.q_count = 0; m_cart_callback.resolve(); m_cart_line.callback = &m_cart_callback; m_nmi_line.timer_index = 0; m_nmi_line.delay = 0; m_nmi_line.value = line_value::CLEAR; m_nmi_line.line = 0; m_nmi_line.q_count = 0; m_nmi_callback.resolve(); m_nmi_line.callback = &m_nmi_callback; m_halt_line.timer_index = 0; m_halt_line.delay = 0; m_halt_line.value = line_value::CLEAR; m_halt_line.line = 0; m_halt_line.q_count = 0; m_halt_callback.resolve(); m_halt_line.callback = &m_halt_callback; m_cart = get_card_device(); save_item(STRUCT_MEMBER(m_cart_line, timer_index)); save_item(STRUCT_MEMBER(m_cart_line, delay)); save_item(STRUCT_MEMBER(m_cart_line, value)); save_item(STRUCT_MEMBER(m_cart_line, line)); save_item(STRUCT_MEMBER(m_cart_line, q_count)); save_item(STRUCT_MEMBER(m_nmi_line, timer_index)); save_item(STRUCT_MEMBER(m_nmi_line, delay)); save_item(STRUCT_MEMBER(m_nmi_line, value)); save_item(STRUCT_MEMBER(m_nmi_line, line)); save_item(STRUCT_MEMBER(m_nmi_line, q_count)); save_item(STRUCT_MEMBER(m_halt_line, timer_index)); save_item(STRUCT_MEMBER(m_halt_line, delay)); save_item(STRUCT_MEMBER(m_halt_line, value)); save_item(STRUCT_MEMBER(m_halt_line, line)); save_item(STRUCT_MEMBER(m_halt_line, q_count)); } //------------------------------------------------- // cart_line_timer_tick - update the output // value for the cart's line to PIA1 CB1 //------------------------------------------------- TIMER_CALLBACK_MEMBER(cococart_slot_device::cart_line_timer_tick) { set_line(line::CART, m_cart_line, (line_value) param); } //------------------------------------------------- // nmi_line_timer_tick - update the output // value sent to the CPU's NMI line //------------------------------------------------- TIMER_CALLBACK_MEMBER(cococart_slot_device::nmi_line_timer_tick) { set_line(line::NMI, m_nmi_line, (line_value) param); } //------------------------------------------------- // halt_line_timer_tick - update the output // value sent to the CPU's HALT line //------------------------------------------------- TIMER_CALLBACK_MEMBER(cococart_slot_device::halt_line_timer_tick) { set_line(line::HALT, m_halt_line, (line_value) param); } //------------------------------------------------- // cts_read //------------------------------------------------- u8 cococart_slot_device::cts_read(offs_t offset) { u8 result = 0x00; if (m_cart) result = m_cart->cts_read(offset); return result; } //------------------------------------------------- // cts_write //------------------------------------------------- void cococart_slot_device::cts_write(offs_t offset, u8 data) { if (m_cart) m_cart->cts_write(offset, data); } //------------------------------------------------- // scs_read //------------------------------------------------- u8 cococart_slot_device::scs_read(offs_t offset) { u8 result = 0x00; if (m_cart) result = m_cart->scs_read(offset); return result; } //------------------------------------------------- // scs_write //------------------------------------------------- void cococart_slot_device::scs_write(offs_t offset, u8 data) { if (m_cart) m_cart->scs_write(offset, data); } //------------------------------------------------- // line_value_string //------------------------------------------------- const char *cococart_slot_device::line_value_string(line_value value) { const char *s; switch(value) { case line_value::CLEAR: s = "CLEAR"; break; case line_value::ASSERT: s = "ASSERT"; break; case line_value::Q: s = "Q"; break; default: throw false && "Invalid value"; } return s; } //------------------------------------------------- // set_line //------------------------------------------------- void cococart_slot_device::set_line(line ln, coco_cartridge_line &line, cococart_slot_device::line_value value) { if ((line.value != value) || (value == line_value::Q)) { line.value = value; switch (ln) { case line::CART: LOGCART( "set_line: CART, value: %s\n", line_value_string(value)); break; case line::NMI: LOGNMI( "set_line: NMI, value: %s\n", line_value_string(value)); break; case line::HALT: LOGHALT( "set_line: HALT, value: %s\n", line_value_string(value)); break; case line::SOUND_ENABLE: break; } // engage in a bit of gymnastics for this odious 'Q' value switch(line.value) { case line_value::CLEAR: line.line = 0x00; line.q_count = 0; break; case line_value::ASSERT: line.line = 0x01; line.q_count = 0; break; case line_value::Q: line.line = line.line ? 0x00 : 0x01; if (line.q_count++ < 4) set_line_timer(line, value); break; } /* invoke the callback, if present */ if (!(*line.callback).isnull()) (*line.callback)(line.line); } } //------------------------------------------------- // set_line_timer //------------------------------------------------- void cococart_slot_device::set_line_timer(coco_cartridge_line &line, cococart_slot_device::line_value value) { // calculate delay; delay dependant on cycles per second attotime delay = (line.delay != 0) ? clocks_to_attotime(line.delay) : attotime::zero; line.timer[line.timer_index]->adjust(delay, (int) value); line.timer_index = (line.timer_index + 1) % TIMER_POOL; } //------------------------------------------------- // twiddle_line_if_q //------------------------------------------------- void cococart_slot_device::twiddle_line_if_q(coco_cartridge_line &line) { if (line.value == line_value::Q) { line.q_count = 0; set_line_timer(line, line_value::Q); } } //------------------------------------------------- // twiddle_q_lines - hack to support twiddling the // Q line //------------------------------------------------- void cococart_slot_device::twiddle_q_lines() { twiddle_line_if_q(m_cart_line); twiddle_line_if_q(m_nmi_line); twiddle_line_if_q(m_halt_line); } //------------------------------------------------- // set_line_value //------------------------------------------------- void cococart_slot_device::set_line_value(cococart_slot_device::line which, cococart_slot_device::line_value value) { switch (which) { case cococart_slot_device::line::CART: set_line_timer(m_cart_line, value); break; case cococart_slot_device::line::NMI: set_line_timer(m_nmi_line, value); break; case cococart_slot_device::line::HALT: set_line_timer(m_halt_line, value); break; case cococart_slot_device::line::SOUND_ENABLE: if (m_cart) m_cart->set_sound_enable(value != cococart_slot_device::line_value::CLEAR); break; } } //------------------------------------------------- // set_line_delay //------------------------------------------------- void cococart_slot_device::set_line_delay(cococart_slot_device::line which, int cycles) { switch (which) { case cococart_slot_device::line::CART: m_cart_line.delay = cycles; break; case cococart_slot_device::line::NMI: m_nmi_line.delay = cycles; break; case cococart_slot_device::line::HALT: m_halt_line.delay = cycles; break; default: throw false; } } //------------------------------------------------- // get_line_value //------------------------------------------------- cococart_slot_device::line_value cococart_slot_device::get_line_value(cococart_slot_device::line which) const { line_value result; switch (which) { case cococart_slot_device::line::CART: result = m_cart_line.value; break; case cococart_slot_device::line::NMI: result = m_nmi_line.value; break; case cococart_slot_device::line::HALT: result = m_halt_line.value; break; default: result = line_value::CLEAR; break; } return result; } //------------------------------------------------- // get_cart_base //------------------------------------------------- u8 *cococart_slot_device::get_cart_base() { if (m_cart != nullptr) return m_cart->get_cart_base(); return nullptr; } //------------------------------------------------- // get_cart_size //------------------------------------------------- u32 cococart_slot_device::get_cart_size() { if (m_cart != nullptr) return m_cart->get_cart_size(); return 0x8000; } //------------------------------------------------- // set_cart_base_update //------------------------------------------------- void cococart_slot_device::set_cart_base_update(cococart_base_update_delegate update) { if (m_cart) m_cart->set_cart_base_update(update); } //------------------------------------------------- // read_coco_rpk //------------------------------------------------- static std::error_condition read_coco_rpk(std::unique_ptr &&stream, rpk_file::ptr &result) { // sanity checks static_assert(std::size(coco_rpk_pcbdefs) - 1 == std::size(coco_rpk_cardslottypes)); // set up the RPK reader rpk_reader reader(coco_rpk_pcbdefs, false); // and read the RPK file return reader.read(std::move(stream), result); } //------------------------------------------------- // read_coco_rpk //------------------------------------------------- static std::error_condition read_coco_rpk(std::unique_ptr &&stream, u8 *mem, offs_t cart_length, offs_t &actual_length) { actual_length = 0; // open the RPK rpk_file::ptr file; std::error_condition err = read_coco_rpk(std::move(stream), file); if (err) return err; // for now, we are just going to load all sockets into the contiguous block of memory // that cartridges use offs_t pos = 0; for (const rpk_socket &socket : file->sockets()) { // only ROM supported for now; if we see anything else it should have been caught in the RPK code assert(socket.type() == rpk_socket::socket_type::ROM); // read all bytes std::vector contents; err = socket.read_file(contents); if (err) return err; // copy the bytes offs_t size = (offs_t) std::min(contents.size(), (size_t)cart_length - pos); memcpy(&mem[pos], &contents[0], size); pos += size; } // we're done! actual_length = pos; return std::error_condition(); } //------------------------------------------------- // call_load //------------------------------------------------- image_init_result cococart_slot_device::call_load() { if (m_cart) { memory_region *cart_mem = m_cart->get_cart_memregion(); u8 *base = cart_mem->base(); offs_t read_length, cart_length = cart_mem->bytes(); if (loaded_through_softlist()) { // loaded through softlist read_length = get_software_region_length("rom"); memcpy(base, get_software_region("rom"), read_length); } else if (is_filetype("rpk")) { // RPK file util::core_file::ptr proxy; std::error_condition err = util::core_file::open_proxy(image_core_file(), proxy); if (!err) err = read_coco_rpk(std::move(proxy), base, cart_length, read_length); if (err) return image_init_result::FAIL; } else { // conventional ROM image read_length = fread(base, cart_length); } while (read_length < cart_length) { offs_t len = std::min(read_length, cart_length - read_length); memcpy(base + read_length, base, len); read_length += len; } } return image_init_result::PASS; } //------------------------------------------------- // get_default_card_software //------------------------------------------------- std::string cococart_slot_device::get_default_card_software(get_default_card_software_hook &hook) const { // this is the default for anything not in an RPK file int pcb_type = 0; // is this an RPK? if (hook.is_filetype("rpk")) { // RPK file rpk_file::ptr file; util::core_file::ptr proxy; std::error_condition err = util::core_file::open_proxy(*hook.image_file(), proxy); if (!err) err = read_coco_rpk(std::move(proxy), file); if (!err) pcb_type = file->pcb_type(); } // lookup the default slot return software_get_default_slot(coco_rpk_cardslottypes[pcb_type]); } //************************************************************************** // DEVICE COCO CART INTERFACE - Implemented by devices that plug into // CoCo cartridge slots //************************************************************************** template class device_finder; template class device_finder; //------------------------------------------------- // device_cococart_interface - constructor //------------------------------------------------- device_cococart_interface::device_cococart_interface(const machine_config &mconfig, device_t &device) : device_interface(device, "cococart") , m_owning_slot(nullptr) , m_host(nullptr) { } //------------------------------------------------- // ~device_cococart_interface - destructor //------------------------------------------------- device_cococart_interface::~device_cococart_interface() { } //------------------------------------------------- // interface_config_complete //------------------------------------------------- void device_cococart_interface::interface_config_complete() { m_owning_slot = dynamic_cast(device().owner()); m_host = m_owning_slot ? dynamic_cast(m_owning_slot->owner()) : nullptr; } //------------------------------------------------- // interface_pre_start //------------------------------------------------- void device_cococart_interface::interface_pre_start() { if (!m_owning_slot) throw emu_fatalerror("Expected device().owner() to be of type cococart_slot_device"); if (!m_host) throw emu_fatalerror("Expected m_owning_slot->owner() to be of type device_cococart_host_interface"); } //------------------------------------------------- // cts_read - Signifies a read where the CTS pin // on the cartridge slot was asserted ($C000-FFEF) //------------------------------------------------- u8 device_cococart_interface::cts_read(offs_t offset) { return 0x00; } //------------------------------------------------- // cts_write - Signifies a write where the CTS pin // on the cartridge slot was asserted ($C000-FFEF) //------------------------------------------------- void device_cococart_interface::cts_write(offs_t offset, u8 data) { } //------------------------------------------------- // scs_read - Signifies a read where the SCS pin // on the cartridge slot was asserted ($FF40-5F) //------------------------------------------------- u8 device_cococart_interface::scs_read(offs_t offset) { return 0x00; } //------------------------------------------------- // scs_write - Signifies a write where the SCS pin // on the cartridge slot was asserted ($FF40-5F) //------------------------------------------------- void device_cococart_interface::scs_write(offs_t offset, u8 data) { } //------------------------------------------------- // set_sound_enable //------------------------------------------------- void device_cococart_interface::set_sound_enable(bool sound_enable) { } //------------------------------------------------- // get_cart_base //------------------------------------------------- u8 *device_cococart_interface::get_cart_base() { return nullptr; } //------------------------------------------------- // get_cart_size //------------------------------------------------- u32 device_cococart_interface::get_cart_size() { return 0x8000; } //------------------------------------------------- // set_cart_base_update //------------------------------------------------- void device_cococart_interface::set_cart_base_update(cococart_base_update_delegate update) { m_update = update; } //------------------------------------------------- // cart_base_changed //------------------------------------------------- void device_cococart_interface::cart_base_changed(void) { if (!m_update.isnull()) m_update(get_cart_base()); else { // propagate up to host interface device_cococart_interface *host = dynamic_cast(m_host); host->cart_base_changed(); } } /*------------------------------------------------- get_cart_memregion -------------------------------------------------*/ memory_region *device_cococart_interface::get_cart_memregion() { return 0; } //------------------------------------------------- // cartridge_space //------------------------------------------------- address_space &device_cococart_interface::cartridge_space() { return host().cartridge_space(); } //------------------------------------------------- // set_line_value //------------------------------------------------- void device_cococart_interface::set_line_value(cococart_slot_device::line line, cococart_slot_device::line_value value) { owning_slot().set_line_value(line, value); } //------------------------------------------------- // coco_cart_add_basic_devices //------------------------------------------------- void coco_cart_add_basic_devices(device_slot_interface &device) { // basic devices, on both the main slot and the Multi-Pak interface device.option_add_internal("banked_16k", COCO_PAK_BANKED); device.option_add_internal("pak", COCO_PAK); device.option_add("ccpsg", COCO_PSG); device.option_add("dcmodem", COCO_DCMODEM); device.option_add("gmc", COCO_PAK_GMC); device.option_add("ide", COCO_IDE); device.option_add("max", COCO_PAK_MAX); device.option_add("midi", COCO_MIDI); device.option_add("orch90", COCO_ORCH90); device.option_add("ram", COCO_PAK_RAM); device.option_add("rs232", COCO_RS232); device.option_add("ssc", COCO_SSC); device.option_add("ssfm", DRAGON_MSX2); device.option_add("stecomp", COCO_STEREO_COMPOSER); device.option_add("sym12", COCO_SYM12); device.option_add("wpk", COCO_WPK); device.option_add("wpk2", COCO_WPK2); device.option_add("wpkrs", COCO_WPKRS); device.option_add("wpk2p", COCO_WPK2P); } //------------------------------------------------- // coco_cart_add_fdcs //------------------------------------------------- void coco_cart_add_fdcs(device_slot_interface &device) { // FDCs are optional because if they are on a Multi-Pak interface, they must // be on Slot 4 device.option_add("cc2hdb1", COCO2_HDB1); device.option_add("cc3hdb1", COCO3_HDB1); device.option_add("cd6809_fdc", CD6809_FDC); device.option_add("cp450_fdc", CP450_FDC); device.option_add("fdc", COCO_FDC); device.option_add("fdcv11", COCO_FDC_V11); } //------------------------------------------------- // coco_cart_add_multi_pak //------------------------------------------------- void coco_cart_add_multi_pak(device_slot_interface &device) { // and the Multi-Pak itself is optional because they cannot be daisy chained device.option_add("multi", COCO_MULTIPAK); } //------------------------------------------------- // dragon_cart_add_basic_devices //------------------------------------------------- void dragon_cart_add_basic_devices(device_slot_interface &device) { device.option_add_internal("amtor", DRAGON_AMTOR); device.option_add("ccpsg", COCO_PSG); device.option_add("claw", DRAGON_CLAW); device.option_add("gmc", COCO_PAK_GMC); device.option_add("jcbsnd", DRAGON_JCBSND); device.option_add("jcbspch", DRAGON_JCBSPCH); device.option_add("max", COCO_PAK_MAX); device.option_add("midi", DRAGON_MIDI); device.option_add("orch90", COCO_ORCH90); device.option_add("pak", COCO_PAK); device.option_add("serial", DRAGON_SERIAL); device.option_add("ram", COCO_PAK_RAM); device.option_add("sprites", DRAGON_SPRITES); device.option_add("ssc", COCO_SSC); device.option_add("ssfm", DRAGON_MSX2); device.option_add("stecomp", COCO_STEREO_COMPOSER); device.option_add("sym12", COCO_SYM12); device.option_add("wpk2p", COCO_WPK2P); } //------------------------------------------------- // dragon_cart_add_fdcs //------------------------------------------------- void dragon_cart_add_fdcs(device_slot_interface &device) { device.option_add("dragon_fdc", DRAGON_FDC); device.option_add("premier_fdc", PREMIER_FDC); device.option_add("sdtandy_fdc", SDTANDY_FDC); } //------------------------------------------------- // dragon_cart_add_multi_pak //------------------------------------------------- void dragon_cart_add_multi_pak(device_slot_interface &device) { device.option_add("multi", DRAGON_MULTIPAK); }