// license:BSD-3-Clause // copyright-holders:Lee Ward, Dirk Best, Curt Coder /************************************************************************* Memotech MTX 500, MTX 512 and RS 128 **************************************************************************/ #include "emu.h" #include "formats/imageutl.h" #include "includes/mtx.h" #include "cpu/z80/z80.h" #include "imagedev/cassette.h" #include "machine/ram.h" #include "imagedev/snapquik.h" #include "bus/centronics/ctronics.h" #include "machine/z80ctc.h" #include "machine/z80dart.h" #include "video/tms9928a.h" #include "sound/sn76496.h" /*************************************************************************** READ/WRITE HANDLERS ***************************************************************************/ /*------------------------------------------------- mtx_strobe_r - centronics strobe -------------------------------------------------*/ READ8_MEMBER(mtx_state::mtx_strobe_r) { /* set STROBE low */ m_centronics->write_strobe(false); return 0xff; } /*------------------------------------------------- mtx_subpage_w - rom2 subpages -------------------------------------------------*/ /* The original ROM card supported 4 8KB ROM chips. These appeared in ROM slot 2 in subpages 0 to 3. The subpage register starts as 0, but is changed by attempting to write to 0x0000-0x1fff whilst in RELCPMH=0 mode (ie. attempting to write to the OS ROM). Videowalls could use a later ROM card with 4 32KB ROMs. These also appeared in ROM slot 2 in subpages 0 to 15. */ WRITE8_MEMBER(mtx_state::mtx_subpage_w) { if (m_extrom->exists()) { if ((data * 0x2000) < m_extrom->get_rom_size()) membank("rommap_bank1")->configure_entry(2, m_extrom->get_rom_base() + 0x2000 * data); else membank("rommap_bank1")->configure_entry(2, memregion("user2")->base() + 0x4000); membank("rommap_bank1")->set_entry(2); } } /*------------------------------------------------- mtx_bankswitch_w - bankswitch -------------------------------------------------*/ /* There are two memory models on the MTX, the standard one and a CBM mode. In standard mode, the memory map is defined as: 0x0000 - 0x1fff OSROM 0x2000 - 0x3fff Paged ROM 0x4000 - 0x7fff Paged RAM 0x8000 - 0xbfff Paged RAM 0xc000 - 0xffff RAM Banks are selected by output port 0. Bits 0-3 define the RAM page and bits 4-6 the ROM page. CBM mode is selected by bit 7 of output port 0. ROM is replaced by RAM in this mode. */ void mtx_state::bankswitch(uint8_t data) { /* bit description 0 P0 1 P1 2 P2 3 P3 4 R0 5 R1 6 R2 7 RELCPMH */ address_space &program = m_maincpu->space(AS_PROGRAM); uint8_t cbm_mode = (data >> 7) & 0x01; uint8_t rom_page = (data >> 4) & 0x07; uint8_t ram_page = (data >> 0) & 0x0f; if (cbm_mode) { /* ram based memory map */ program.install_readwrite_bank(0x0000, 0x3fff, "rammap_bank1"); program.install_readwrite_bank(0x4000, 0x7fff, "rammap_bank2"); program.install_readwrite_bank(0x8000, 0xbfff, "rammap_bank3"); /* set ram bank, for invalid pages a nop-handler will be installed */ switch (ram_page) { case 0: if (ram_page * 0xc000 + 0x10000 <= m_ram->size()) membank("rammap_bank1")->set_entry(ram_page); else program.nop_readwrite(0x0000, 0x3fff); if (ram_page * 0xc000 + 0xc000 <= m_ram->size()) membank("rammap_bank2")->set_entry(ram_page); else program.nop_readwrite(0x4000, 0x7fff); if (ram_page * 0xc000 + 0x8000 <= m_ram->size()) membank("rammap_bank3")->set_entry(ram_page); else program.nop_readwrite(0x8000, 0xbfff); break; default: if (ram_page * 0xc000 + 0x8000 <= m_ram->size()) membank("rammap_bank1")->set_entry(ram_page); else program.nop_readwrite(0x0000, 0x3fff); if (ram_page * 0xc000 + 0xc000 <= m_ram->size()) membank("rammap_bank2")->set_entry(ram_page); else program.nop_readwrite(0x4000, 0x7fff); if (ram_page * 0xc000 + 0x10000 <= m_ram->size()) membank("rammap_bank3")->set_entry(ram_page); else program.nop_readwrite(0x8000, 0xbfff); break; } } else { /* rom based memory map */ program.install_rom(0x0000, 0x1fff, memregion("user1")->base()); program.install_write_handler(0x0000, 0x1fff, write8_delegate(FUNC(mtx_state::mtx_subpage_w), this)); program.install_read_bank(0x2000, 0x3fff, "rommap_bank1"); program.unmap_write(0x2000, 0x3fff); program.install_readwrite_bank(0x4000, 0x7fff, "rommap_bank2"); program.install_readwrite_bank(0x8000, 0xbfff, "rommap_bank3"); /* set rom bank (switches between basic and assembler rom or cartridges) */ membank("rommap_bank1")->set_entry(rom_page); /* set ram bank, for invalid pages a nop-handler will be installed */ if (ram_page * 0x8000 + 0xc000 <= m_ram->size()) membank("rommap_bank2")->set_entry(ram_page); else program.nop_readwrite(0x4000, 0x7fff); if (ram_page * 0x8000 + 0x8000 <= m_ram->size()) membank("rommap_bank3")->set_entry(ram_page); else program.nop_readwrite(0x8000, 0xbfff); } } WRITE8_MEMBER(mtx_state::mtx_bankswitch_w) { bankswitch(data); } /*------------------------------------------------- mtx_sound_strobe_r - sound strobe -------------------------------------------------*/ READ8_MEMBER(mtx_state::mtx_sound_strobe_r) { m_sn->write(space, 0, m_sound_latch); return 0xff; } /*------------------------------------------------- mtx_sound_latch_w - sound latch write -------------------------------------------------*/ WRITE8_MEMBER(mtx_state::mtx_sound_latch_w) { m_sound_latch = data; } /*------------------------------------------------- mtx_cst_w - cassette write -------------------------------------------------*/ WRITE8_MEMBER(mtx_state::mtx_cst_w) { m_cassette->output( BIT(data, 0) ? -1 : 1); } /*------------------------------------------------- mtx_cst_motor_w - cassette motor -------------------------------------------------*/ WRITE8_MEMBER(mtx_state::mtx_cst_motor_w) { m_cassette->change_state(data ? CASSETTE_MOTOR_ENABLED : CASSETTE_MOTOR_DISABLED, CASSETTE_MASK_MOTOR); } /*------------------------------------------------- mtx_prt_r - centronics status -------------------------------------------------*/ WRITE_LINE_MEMBER(mtx_state::write_centronics_busy) { m_centronics_busy = state; } WRITE_LINE_MEMBER(mtx_state::write_centronics_fault) { m_centronics_fault = state; } WRITE_LINE_MEMBER(mtx_state::write_centronics_perror) { m_centronics_perror = state; } WRITE_LINE_MEMBER(mtx_state::write_centronics_select) { m_centronics_select = state; } READ8_MEMBER(mtx_state::mtx_prt_r) { /* bit description 0 BUSY 1 ERROR 2 PE 3 SLCT 4 5 6 7 */ uint8_t data = 0; /* reset STROBE to high */ m_centronics->write_strobe( true); data |= m_centronics_busy << 0; data |= m_centronics_fault << 1; data |= m_centronics_perror << 2; data |= m_centronics_select << 3; return data; } /*------------------------------------------------- mtx_sense_w - keyboard sense write -------------------------------------------------*/ WRITE8_MEMBER(mtx_state::mtx_sense_w) { m_key_sense = data; } /*------------------------------------------------- mtx_key_lo_r - keyboard low read -------------------------------------------------*/ READ8_MEMBER(mtx_state::mtx_key_lo_r) { uint8_t data = 0xff; if (!(m_key_sense & 0x01)) data &= ioport("ROW0")->read(); if (!(m_key_sense & 0x02)) data &= ioport("ROW1")->read(); if (!(m_key_sense & 0x04)) data &= ioport("ROW2")->read(); if (!(m_key_sense & 0x08)) data &= ioport("ROW3")->read(); if (!(m_key_sense & 0x10)) data &= ioport("ROW4")->read(); if (!(m_key_sense & 0x20)) data &= ioport("ROW5")->read(); if (!(m_key_sense & 0x40)) data &= ioport("ROW6")->read(); if (!(m_key_sense & 0x80)) data &= ioport("ROW7")->read(); return data; } /*------------------------------------------------- mtx_key_lo_r - keyboard high read -------------------------------------------------*/ READ8_MEMBER(mtx_state::mtx_key_hi_r) { uint8_t data = ioport("country_code")->read(); if (!(m_key_sense & 0x01)) data &= ioport("ROW0")->read() >> 8; if (!(m_key_sense & 0x02)) data &= ioport("ROW1")->read() >> 8; if (!(m_key_sense & 0x04)) data &= ioport("ROW2")->read() >> 8; if (!(m_key_sense & 0x08)) data &= ioport("ROW3")->read() >> 8; if (!(m_key_sense & 0x10)) data &= ioport("ROW4")->read() >> 8; if (!(m_key_sense & 0x20)) data &= ioport("ROW5")->read() >> 8; if (!(m_key_sense & 0x40)) data &= ioport("ROW6")->read() >> 8; if (!(m_key_sense & 0x80)) data &= ioport("ROW7")->read() >> 8; return data; } /*------------------------------------------------- hrx_address_w - HRX video RAM address -------------------------------------------------*/ WRITE8_MEMBER(mtx_state::hrx_address_w) { if (offset) { /* bit description 0 A8 1 A9 2 A10 3 4 5 attribute memory write enable 6 ASCII memory write enable 7 cycle (0=read/1=write) */ } else { /* bit description 0 A0 1 A1 2 A2 3 A3 4 A4 5 A5 6 A6 7 A7 */ } } /*------------------------------------------------- hrx_data_r - HRX data read -------------------------------------------------*/ READ8_MEMBER(mtx_state::hrx_data_r) { return 0; } /*------------------------------------------------- hrx_data_w - HRX data write -------------------------------------------------*/ WRITE8_MEMBER(mtx_state::hrx_data_w) { } /*------------------------------------------------- hrx_attr_r - HRX attribute read -------------------------------------------------*/ READ8_MEMBER(mtx_state::hrx_attr_r) { return 0; } /*------------------------------------------------- hrx_attr_r - HRX attribute write -------------------------------------------------*/ WRITE8_MEMBER(mtx_state::hrx_attr_w) { /* bit description 0 1 2 3 4 5 6 7 */ } /*************************************************************************** EXTENSION BOARD ROMS ***************************************************************************/ DEVICE_IMAGE_LOAD_MEMBER( mtx_state, extrom_load ) { uint32_t size = m_extrom->common_get_size("rom"); if (size > 0x80000) { image.seterror(IMAGE_ERROR_UNSPECIFIED, "Unsupported rom size"); return image_init_result::FAIL; } m_extrom->rom_alloc(size, GENERIC_ROM8_WIDTH, ENDIANNESS_LITTLE); m_extrom->common_load_rom(m_extrom->get_rom_base(), size, "rom"); return image_init_result::PASS; } /*************************************************************************** ROMPAK ROMS ***************************************************************************/ DEVICE_IMAGE_LOAD_MEMBER( mtx_state, rompak_load ) { uint32_t size = m_rompak->common_get_size("rom"); if (size > 0x2000) { image.seterror(IMAGE_ERROR_UNSPECIFIED, "Unsupported cartridge size"); return image_init_result::FAIL; } m_rompak->rom_alloc(size, GENERIC_ROM8_WIDTH, ENDIANNESS_LITTLE); m_rompak->common_load_rom(m_rompak->get_rom_base(), size, "rom"); return image_init_result::PASS; } /*************************************************************************** SNAPSHOT ***************************************************************************/ // this only works for some of the files, nothing which tries to load // more data from tape. todo: tapes which autorun after loading SNAPSHOT_LOAD_MEMBER( mtx_state, mtx ) { address_space &program = m_maincpu->space(AS_PROGRAM); void *ptr; uint8_t header[18]; // read header image.fread(&header, sizeof(header)); // verify first byte if (header[0] != 0xff) { image.seterror(IMAGE_ERROR_INVALIDIMAGE, nullptr); return image_init_result::FAIL; } // get tape name char tape_name[16]; memcpy(&tape_name, &header[1], 15); tape_name[15] = '\0'; image.message("Loading '%s'", tape_name); // start of system variables area uint16_t system_variables_base = pick_integer_le(header, 16, 2); // write system variables uint16_t system_variables_size = 0; if (system_variables_base != 0) { ptr = program.get_write_ptr(system_variables_base); system_variables_size = 0xfb4b - system_variables_base; image.fread(ptr, system_variables_size); } // write actual image data uint16_t data_size = snapshot_size - 18 - system_variables_size; ptr = program.get_write_ptr(0x4000); image.fread(ptr, 0x4000); // if we cross the page boundary, get a new write pointer and write the rest if (data_size > 0x4000) { ptr = program.get_write_ptr(0x8000); image.fread(ptr, 0x4000); } logerror("snapshot name = '%s', system_size = 0x%04x, data_size = 0x%04x\n", tape_name, system_variables_size, data_size); return image_init_result::PASS; } /*************************************************************************** MACHINE INITIALIZATION ***************************************************************************/ /*------------------------------------------------- MACHINE_START( mtx512 ) -------------------------------------------------*/ MACHINE_START_MEMBER(mtx_state, mtx512) { address_space &program = m_maincpu->space(AS_PROGRAM); /* setup banks for rom based memory map */ program.install_read_bank(0x2000, 0x3fff, "rommap_bank1"); program.install_readwrite_bank(0x4000, 0x7fff, "rommap_bank2"); program.install_readwrite_bank(0x8000, 0xbfff, "rommap_bank3"); membank("rommap_bank1")->configure_entries(0, 8, memregion("user2")->base(), 0x2000); if (m_extrom->exists()) membank("rommap_bank1")->configure_entry(2, m_extrom->get_rom_base()); if (m_rompak->exists()) membank("rommap_bank1")->configure_entry(7, m_rompak->get_rom_base()); membank("rommap_bank2")->configure_entries(0, 16, m_ram->pointer() + 0x8000, 0x8000); membank("rommap_bank3")->configure_entries(0, 16, m_ram->pointer() + 0x4000, 0x8000); /* setup banks for ram based memory map */ program.install_readwrite_bank(0x0000, 0x3fff, "rammap_bank1"); program.install_readwrite_bank(0x4000, 0x7fff, "rammap_bank2"); program.install_readwrite_bank(0x8000, 0xbfff, "rammap_bank3"); membank("rammap_bank1")->configure_entry(0, m_ram->pointer() + 0xc000); membank("rammap_bank1")->configure_entries(1, 15, m_ram->pointer() + 0x10000, 0xc000); membank("rammap_bank2")->configure_entry(0, m_ram->pointer() + 0x8000); membank("rammap_bank2")->configure_entries(1, 15, m_ram->pointer() + 0x14000, 0xc000); membank("rammap_bank3")->configure_entry(0, m_ram->pointer() + 0x4000); membank("rammap_bank3")->configure_entries(1, 15, m_ram->pointer() + 0x18000, 0xc000); /* install 4000h bytes common block */ program.install_ram(0xc000, 0xffff, m_ram->pointer()); } MACHINE_RESET_MEMBER(mtx_state, mtx512) { /* bank switching */ bankswitch(0); }