From c6b72abd3a66b4e9c1aac9eec1cd0b2fe44d1beb Mon Sep 17 00:00:00 2001 From: Robin Sergeant Date: Sun, 13 Oct 2024 23:42:54 +0100 Subject: rm/rm380z.cpp: Split RM480Z out to its own driver with MQ2 floppy drive support (#12576) --- src/mame/mame.lst | 6 +- src/mame/rm/rm380z.cpp | 115 ++------------- src/mame/rm/rm380z.h | 36 +---- src/mame/rm/rm380z_m.cpp | 4 - src/mame/rm/rm380z_v.cpp | 34 ----- src/mame/rm/rm480z.cpp | 372 +++++++++++++++++++++++++++++++++++++++++++++++ src/mame/rm/rm480z.h | 153 +++++++++++++++++++ src/mame/rm/rm480z_m.cpp | 249 +++++++++++++++++++++++++++++++ src/mame/rm/rm480z_v.cpp | 260 +++++++++++++++++++++++++++++++++ src/mame/rm/rm_mq2.cpp | 256 ++++++++++++++++++++++++++++++++ src/mame/rm/rm_mq2.h | 53 +++++++ 11 files changed, 1364 insertions(+), 174 deletions(-) create mode 100644 src/mame/rm/rm480z.cpp create mode 100644 src/mame/rm/rm480z.h create mode 100644 src/mame/rm/rm480z_m.cpp create mode 100644 src/mame/rm/rm480z_v.cpp create mode 100644 src/mame/rm/rm_mq2.cpp create mode 100644 src/mame/rm/rm_mq2.h diff --git a/src/mame/mame.lst b/src/mame/mame.lst index 6bf4b69c4cf..04f9cdd9fdc 100644 --- a/src/mame/mame.lst +++ b/src/mame/mame.lst @@ -38796,8 +38796,10 @@ rm380zf // 1978 RM380z with COS 4.0/F firmware rm380zfhrg // 1978 RM380z with COS 4.0/F and HRG rm380z34d // 1978 RM380z with COS 3.4/F firmware and casette rm380z34e // 1978 RM380z with COS 3.4/M firmware and casette -rm480z // 1981 RM480z -rm480za // 1981 RM480z + +@source:rm/rm480z.cpp +rm480z // 1981 RM480z with ROS 2.2 +rm480za // 1981 RM480z with ROS 1.2 @source:rm/rmnimbus.cpp nimbus // RM Nimbus 186 diff --git a/src/mame/rm/rm380z.cpp b/src/mame/rm/rm380z.cpp index b0816a175ba..ed8955b31ab 100644 --- a/src/mame/rm/rm380z.cpp +++ b/src/mame/rm/rm380z.cpp @@ -11,6 +11,9 @@ Driver started on 22/12/2011 Stefano Bodrato, 09/12/2016 - skeleton for cassette support True tape samples are needed to continue ! +Robin Sergeant, 2024 - h/w scrolling, HRG, sound, 8" disk support +and various bug fixes. + === From the Firmware Manual: @@ -37,39 +40,16 @@ COS 4.2 A /M = 5.25 inch single density floppy Ver 4.2 can have either floppy type -*** RM480Z *** -RML 40-Character LINK 480Z V1.0 -RML 80-Character LINK 480Z V1.0 -RML 40-Character LINK 480Z V1.1 A -RML 40-Character LINK 480Z V1.1 B -RML 80-Character LINK 480Z V1.1 A -RML 80-Character LINK 480Z V1.1 B -RML 80-Character LINK 480Z V1.2 A -RML 80-Character LINK 480Z V1.2 B -RML 80-Character LINK 480Z V1.2 C -RML 80-Character LINK 480Z V1.2 D -RML 80-Character LINK 480Z V2.2 B - -V1.0 refers to ROS 1.0 -V1.1 refers to ROS 1.1 -V1.2 refers to ROS 1.2 -V2.2 refers to ROS 2.2 - Monitor commands: -B - Boot CP/M (COS /F, COS /M, ROS 1.2, ROS 2.2) +B - Boot CP/M (COS /F, COS /M) X - Boot CP/M from another drive (as above) -N - Boot network (ROS) -T - Enter terminal mode (ROS) -L - Load program from cassette (COS /C, ROS) +L - Load program from cassette (COS /C) D - Dump memory to cassette (as above) C - Continue program at restart address (as above) J - Go to address -O - Select printer option (and cassette speed for COS /C, ROS) +O - Select printer option (and cassette speed for COS /C) M - Enable HRG board as memory (COS 3.4, COS 4.0) W - Select 40 or 80 characters per line (All 80-column machines) -R - Start ROM BASIC (ROS 1.1, ROS 1.2, ROS 2.2) -Ctrl+Shift+8 - Break and return to current OS (ROS) -Ctrl+Shift+9 - Break and return to front panel (ROS) Ctrl+F - Enter Front Panel (=the debugger) Ctrl+T - Enter Typewriter mode Ctrl+S - Autopaging on @@ -77,14 +57,13 @@ Ctrl+Q - Autopaging off Ctrl+A - Toggle autopaging Graphics characters: These are low-res (2x3 TRS80-style) from 80-BF, repeated at C0-FF. -80-BF will be low-intensity, except for a RM480Z if using a colour monitor. +80-BF will be low-intensity. The characters 00-1F have one set for COS 3.4 and COS 4.0, and a different set for the others. COS 4.0 and 4.2 allow one to redefine the 80-FF character range, and to have attributes. ROS 2.2 allows an alternate character set. Sound: RM380Z has a connector for a speaker -RM480Z has the speaker fitted === @@ -117,8 +96,8 @@ PAGE SEL bit in PORT0 set to 1: FB00-FBFF - Memory-mapped ports (FBFC-FBFF) FC00-FFFF - RAM -Video resolution (not confirmed): -80x24 - 6 pixels wide (5 + spacing), 10 pixels high (9 + spacing) = 480x240 +Video resolution: +80x24 - 8 pixels wide, 10 pixels high = 640x240 Video input clock is 16MHz According to the manuals, VDU-1 chargen is Texas 74LS262. @@ -136,10 +115,7 @@ Notes on COS 4.0 disassembly: TODO: -- Properly implement dimming and graphic chars (>0x80) -- Understand why any write to disk command fails with "bad sector" -- Understand why ctrl-U (blinking cursor) in COS 4.0 stops keyboard input from working -- Get a reliable ROM dump and charset ROM dump +- Make cassette interface work. Attempt to register save state entry after state registration is closed! @@ -190,29 +166,6 @@ void rm380z_state::rm380z_io(address_map &map) map(0xe8, 0xff).rw(FUNC(rm380z_state::rm380z_porthi_r), FUNC(rm380z_state::rm380z_porthi_w)); } -void rm480z_state::rm480z_mem(address_map &map) -{ - map(0x0000, 0xe7ff).ram(); - map(0xe800, 0xf7ff).rom().region(RM380Z_MAINCPU_TAG, 0); - map(0xf800, 0xffff).ram(); -} - -void rm480z_state::rm480z_io(address_map &map) -{ - //map(0x00, 0x17).ram(); // videoram - //map(0x18, 0x18).mirror(0xff00); // control port 0 - //map(0x19, 0x19).mirror(0xff00); // control port 1 - //map(0x1a, 0x1a).mirror(0xff00); // control port 2 - //map(0x1b, 0x1b).mirror(0xff00); // control port 3 (DAC) // option - //map(0x1d, 0x1d).mirror(0xff00); // control port 5 (USERIO) // option - //map(0x20, 0x23).mirror(0xff00); // system CTC - 0=SIO4&cassin, 1=SIO2&cassio, 2=keybd int, 3=50Hz int for repeat key - //map(0x24, 0x27).mirror(0xff00); // system SIO - 0=chA network data, 1=chB SIO4 data, 2=ChA control, 3=ChB control - //map(0x28, 0x29).mirror(0xff02); // am9511/am9512 maths chip // option - //map(0x2c, 0x2f).mirror(0xff00); // z80ctc IEEE int, Maths int, RTC, RTC // option - //map(0x30, 0x37).mirror(0xff00); // IEEE chip // option - //map(0x38, 0x3b).mirror(0xff00); // Hi-res graphics option -} - INPUT_PORTS_START( rm380z ) // PORT_START("additional_chars") @@ -318,30 +271,6 @@ void rm380z_state_cos40_hrg::rm380zhrg(machine_config &config) m_palette->set_init(FUNC(rm380z_state_cos40_hrg::palette_init)).set_entries(19); } -void rm480z_state::rm480z(machine_config &config) -{ - /* basic machine hardware */ - Z80(config, m_maincpu, 16_MHz_XTAL / 4); - m_maincpu->set_addrmap(AS_PROGRAM, &rm480z_state::rm480z_mem); - m_maincpu->set_addrmap(AS_IO, &rm480z_state::rm480z_io); - - /* video hardware */ -// screen_device &screen(SCREEN(config, "screen", SCREEN_TYPE_RASTER)); -// screen.set_refresh_hz(50); -// screen.set_vblank_time(ATTOSECONDS_IN_USEC(0)); -// screen.set_size((RM380Z_SCREENCOLS*(RM380Z_CHDIMX+1)), (RM380Z_SCREENROWS*(RM380Z_CHDIMY+1))); -// screen.set_visarea(0, (RM380Z_SCREENCOLS*(RM380Z_CHDIMX+1))-1, 0, (RM380Z_SCREENROWS*(RM380Z_CHDIMY+1))-1); -// screen.set_screen_update(FUNC(rm380z_state::screen_update_rm480z)); -// screen.set_palette("palette"); - -// PALETTE(config, "palette", palette_device::MONOCHROME); - - /* keyboard */ -// generic_keyboard_device &keyboard(GENERIC_KEYBOARD(config, "keyboard", 0)); -// keyboard.set_keyboard_callback(FUNC(rm380z_state::keyboard_put)); -} - - /* ROM definitions */ ROM_START( rm380z34d ) // COS 3.4D/F @@ -390,28 +319,6 @@ ROM_START( rm380zfhrg ) // COS 4.0B/F ROM_LOAD( "c-gen-22.bin", 0x0000, 0x0800, CRC(1b67127f) SHA1(289a919871d30c5e832d22244bcac1dcfd544baa) ) ROM_END -// RM480Z is quite different, might be better off in its own driver -ROM_START( rm480z ) - ROM_REGION( 0x10000, RM380Z_MAINCPU_TAG, 0 ) - ROM_LOAD( "fv2.0_0_12099_19.2.86.ic83", 0x0000, 0x4000, CRC(a0f02d8a) SHA1(1c063b842699dc0ad85a5a5f337f2864497f9c0f) ) - ROM_LOAD( "fv2.0_1_12100_27.2.86.ic93", 0x4000, 0x4000, CRC(2a93ca6e) SHA1(7fdd772d4251dbf951a687d184ed787cfe21212b) ) - ROM_REGION( 0x2000, "chargen", 0 ) - ROM_LOAD( "cg06_12098_28.2.86.ic98", 0x0000, 0x2000, CRC(15d40f7e) SHA1(a7266357eb9be849f77a97ff3013b236c0af8289) ) -ROM_END - -ROM_START( rm480za ) - ROM_REGION( 0x10000, RM380Z_MAINCPU_TAG, 0 ) - ROM_LOAD( "ros_1.2b.ls", 0x0000, 0x2000, CRC(37e93287) SHA1(c96d4b7eedadb0fb8e3732b6ba3e898e123c393f) ) - ROM_LOAD( "bir_5.4b_0.lt", 0x2000, 0x4000, CRC(64759792) SHA1(ffce5e59788cd492937e85b368b71e8a5a2aaa9c) ) - ROM_LOAD( "bir_5.4b_1.lu", 0x6000, 0x4000, CRC(adce35df) SHA1(dc20df0fcfefc5e2682b47e7ce454043dc593cd8) ) - ROM_LOAD( "bir_5.4b_2.lv", 0xa000, 0x4000, CRC(5a455fc8) SHA1(04c5f17120ee085c013e600b77ca63cbb138aa4d) ) - ROM_REGION( 0x10000, "user1", 0 ) // bioses for fdc board versions - ROM_LOAD( "idc3-1i.rom", 0x0000, 0x2000, CRC(39e2cdf0) SHA1(ba523af357b61bbe6192727139850f36597d79f1) ) - ROM_LOAD( "idc5-1j.rom", 0x2000, 0x2000, CRC(d2ac27e2) SHA1(12d3966e0096c9bfb98135e15c3ddb37920cce15) ) - ROM_REGION( 0x2000, "chargen", 0 ) - ROM_LOAD( "cg06.lq", 0x0000, 0x2000, BAD_DUMP CRC(15d40f7e) SHA1(a7266357eb9be849f77a97ff3013b236c0af8289) ) // chip is marked CG05, might not be the same, so marked as bad -ROM_END - /* Driver */ // YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS @@ -421,5 +328,3 @@ COMP(1978, rm380zf, 0, 0, rm380zf, rm380z, rm380z_state_cos4 COMP(1978, rm380zfhrg, rm380zf, 0, rm380zfhrg, rm380zhrg, rm380z_state_cos40_hrg, driver_device::empty_init, "Research Machines", "RM-380Z, COS 4.0B/F with HRG", 0) COMP(1978, rm380z34d, rm380z, 0, rm380z34d, rm380z, rm380z_state_cos34, driver_device::empty_init, "Research Machines", "RM-380Z, COS 3.4D/F", MACHINE_NO_SOUND_HW) COMP(1978, rm380z34e, rm380z, 0, rm380z34e, rm380z, rm380z_state_cos34, driver_device::empty_init, "Research Machines", "RM-380Z, COS 3.4E/M", MACHINE_NO_SOUND_HW) -COMP(1981, rm480z, rm380z, 0, rm480z, rm380z, rm480z_state, driver_device::empty_init, "Research Machines", "LINK RM-480Z (set 1)", MACHINE_IS_SKELETON) -COMP(1981, rm480za, rm380z, 0, rm480za, rm380z, rm480z_state, driver_device::empty_init, "Research Machines", "LINK RM-480Z (set 2)", MACHINE_IS_SKELETON) diff --git a/src/mame/rm/rm380z.h b/src/mame/rm/rm380z.h index 837df37397e..8f217506d78 100644 --- a/src/mame/rm/rm380z.h +++ b/src/mame/rm/rm380z.h @@ -93,12 +93,12 @@ protected: uint8_t m_fbfe = 0; required_device m_maincpu; - optional_device m_screen; - optional_device m_palette; - optional_device m_messram; - optional_device m_fdc; - optional_device m_floppy0; - optional_device m_floppy1; + required_device m_screen; + required_device m_palette; + required_device m_messram; + required_device m_fdc; + required_device m_floppy0; + required_device m_floppy1; }; // COS 3.4 machine with cassette and VDU-40 display @@ -199,7 +199,7 @@ protected: uint8_t m_fbfd = 0; required_region_ptr m_chargen; - optional_device m_speaker; + required_device m_speaker; private: void config_videomode(); @@ -265,26 +265,4 @@ private: required_ioport m_io_display_type; }; -// partially implemented non working RM480z -class rm480z_state : public rm380z_state_cos40 -{ -public: - rm480z_state(const machine_config &mconfig, device_type type, const char *tag) : - rm380z_state_cos40(mconfig, type, tag) - { - } - - void rm480z(machine_config &config); - void rm480za(machine_config &config) { rm480z(config); } - -protected: - void machine_reset() override ATTR_COLD; - - void update_screen(bitmap_ind16 &bitmap) const override; - -private: - void rm480z_io(address_map &map) ATTR_COLD; - void rm480z_mem(address_map &map) ATTR_COLD; -}; - #endif // MAME_RM_RM380Z_H diff --git a/src/mame/rm/rm380z_m.cpp b/src/mame/rm/rm380z_m.cpp index aea491862e3..d829eb161e6 100644 --- a/src/mame/rm/rm380z_m.cpp +++ b/src/mame/rm/rm380z_m.cpp @@ -371,10 +371,6 @@ void rm380z_state_cos40_hrg::machine_reset() memset(m_hrg_scratchpad, 0, sizeof(m_hrg_scratchpad)); } -void rm480z_state::machine_reset() -{ -} - void rm380z_state::config_memory_map() { address_space &program = m_maincpu->space(AS_PROGRAM); diff --git a/src/mame/rm/rm380z_v.cpp b/src/mame/rm/rm380z_v.cpp index 152b5138896..55632f7f777 100644 --- a/src/mame/rm/rm380z_v.cpp +++ b/src/mame/rm/rm380z_v.cpp @@ -428,37 +428,3 @@ void rm380z_state_cos34::update_screen(bitmap_ind16 &bitmap) const } } } - -// only partially implemented and non working -void rm480z_state::update_screen(bitmap_ind16 &bitmap) const -{ - uint16_t sy = 0, ma = 0; - - for (uint8_t y = 0; y < RM380Z_SCREENROWS; y++) - { - for (uint8_t ra = 0; ra < 11; ra++) - { - uint16_t *p = &bitmap.pix(sy++); - - for (uint16_t x = ma; x < ma + 64; x++) - { - uint8_t gfx = 0; - if (ra < 10) - { - const uint8_t chr = m_vram.get_char(y, x); - gfx = m_chargen[(chr << 4) | ra]; - } - - // Display a scanline of a character - *p++ = BIT(gfx, 7); - *p++ = BIT(gfx, 6); - *p++ = BIT(gfx, 5); - *p++ = BIT(gfx, 4); - *p++ = BIT(gfx, 3); - *p++ = BIT(gfx, 2); - *p++ = BIT(gfx, 1); - *p++ = BIT(gfx, 0); - } - } - } -} diff --git a/src/mame/rm/rm480z.cpp b/src/mame/rm/rm480z.cpp new file mode 100644 index 00000000000..b5275ea635a --- /dev/null +++ b/src/mame/rm/rm480z.cpp @@ -0,0 +1,372 @@ +// license:BSD-3-Clause +// copyright-holders:Robin Sergeant +/* + +Research Machines 480Z (aka "RML 480Z" or "Link 480Z") +Microcomputer produced by Research Machines Limited, Oxford, UK +1982-1985 + +=== + +From the Firmware Manual: + +List of known firmware revisions by sign-on message: + +RML 40-Character LINK 480Z V1.0 +RML 80-Character LINK 480Z V1.0 +RML 40-Character LINK 480Z V1.1 A +RML 40-Character LINK 480Z V1.1 B +RML 80-Character LINK 480Z V1.1 A +RML 80-Character LINK 480Z V1.1 B +RML 80-Character LINK 480Z V1.2 A +RML 80-Character LINK 480Z V1.2 B +RML 80-Character LINK 480Z V1.2 C +RML 80-Character LINK 480Z V1.2 D +RML 80-Character LINK 480Z V2.2 B + +V1.0 refers to ROS 1.0 (Mk1 harwdare) +V1.1 refers to ROS 1.1 +V1.2 refers to ROS 1.2 +V2.2 refers to ROS 2.2 (Mk2 hardware) + +The Mk1 hardware uses 4 x 8K EEPROMs, whereas Mk2 has 2 x 16K +(memory maps differ slighly due to these differences). + +Monitor commands: +B - Boot CP/M (ROS 1.2, ROS 2.2) +X - Boot CP/M from another drive (as above) +N - Boot network +T - Enter terminal mode +L - Load program from cassette +D - Dump memory to cassette +C - Continue program at restart address +J - Go to address +O - Select printer option and cassette speed +W - Select 40 or 80 characters per line (All 80-column machines) +R - Start ROM BASIC (ROS 1.1, ROS 1.2, ROS 2.2) +Ctrl+Shift+8 - Break and return to current OS +Ctrl+Shift+9 - Break and return to front panel +Ctrl+F - Enter Front Panel (=the debugger) +Ctrl+T - Enter Typewriter mode +Ctrl+S - Autopaging on +Ctrl+Q - Autopaging off +Ctrl+A - Toggle autopaging + +Graphics characters: These are low-res (2x3 TRS80-style) from 80-BF, repeated at C0-FF. +80-BF will be low-intensity, except if using a colour monitor. +The characters 00-1F have one set for COS 3.4 and COS 4.0, and a different set for the others. +ROS 2.2 allows an alternate character set. + +Sound: +RM480Z has the speaker fitted + +=== + +4 different memory maps are used (see memory map functions for layout): + +Page 0 is used for start-up and some ROS functions +Page 1 is designed for running CP/M +Page 2 is used to run BIR (Basic In Rom) +Page 3 contains only RAM (no known uses) + +Up to 256K of RAM can be used with bank switching +The 64K address space is divided into 4 banks, each of which can be configured to +address a particular 16K chunk of physical RAM. + +Page selection and bank switching is performed by writes to control ports. + +Video resolution: +80x24 - 8 pixels wide, 10 pixels high = 640x240 +Video input clock is 16MHz + +Additional HRG video modes: + +640 x 192 (1 bit per pixel, monochrome) +320 x 192 (2 bits per pixel, colour) +160 x 96 (4 bits per pixel, colour) + +HRG occupies the top portion of the screen, with 4 lines of video (text) output below. +The video (text) display is drawn over any HRG output (text can overlay graphics). + +=== + +Interrupt driven keyboard, with repeat key (non ASCII scan codes). + +No built in FDC, but external floppy drives can be connected via RS232 port. + +Could also network boot if connected to a CHAIN LAN, where a 380Z would act as a file server. + +Programs could also be loaded from Cassette and rompacks. + +=== + +TODO: + +- Add cassette support +- Add rompack support (parallel port EEPROM cartridges) +- Add support for option hardware (if software supporting it is available). +- Save states + +*/ + +#include "emu.h" +#include "rm480z.h" +#include "rm_mq2.h" +#include "machine/clock.h" +#include "speaker.h" +#include "screen.h" +#include "utf8.h" + +void rm480z_state::rm480z_MK1_mem(address_map &map) +{ + map(0x0000, 0x3fff).bankrw(m_bank[0]); + map(0x4000, 0x7fff).bankrw(m_bank[1]); + map(0x8000, 0xbfff).bankrw(m_bank[2]); + map(0xc000, 0xffff).bankrw(m_bank[3]); + + map(0x0000, 0xf7ff).view(m_view); + // page 0 (for start-up) + m_view[0](0x0000, 0x07ff).rom().region("ros", 0x0000); + m_view[0](0x0800, 0x17ff).rom().region("bir0", 0x0800); + m_view[0](0x1800, 0x1fff).rom().region("bir1", 0x1800); + m_view[0](0x3800, 0x3fff).rom().region("ros", 0x1800); + m_view[0](0xe800, 0xf7ff).rom().region("ros", 0x0800); + // page 1 (for running CP/M) + m_view[1](0xe800, 0xf7ff).rom().region("ros", 0x0800); + //page 2 (for running BIR) + m_view[2](0x9800, 0x9fff).rom().region("bir1", 0x1800); + m_view[2](0xa000, 0xbfff).rom().region("bir2", 0x0000); + m_view[2](0xc000, 0xd7ff).rom().region("bir1", 0x0000); + m_view[2](0xd800, 0xdfff).rom().region("bir0", 0x1800); + m_view[2](0xe000, 0xe7ff).rom().region("bir0", 0x0000); + m_view[2](0xe800, 0xf7ff).rom().region("ros", 0x0800); +} + +void rm480z_state::rm480z_MK2_mem(address_map &map) +{ + map(0x0000, 0x3fff).bankrw(m_bank[0]); + map(0x4000, 0x7fff).bankrw(m_bank[1]); + map(0x8000, 0xbfff).bankrw(m_bank[2]); + map(0xc000, 0xffff).bankrw(m_bank[3]); + + map(0x0000, 0xf7ff).view(m_view); + // page 0 (for start-up) + m_view[0](0x0000, 0x07ff).rom().region("rom0", 0x0000); + m_view[0](0x0800, 0x1fff).rom().region("rom1", 0x0800); + m_view[0](0x7800, 0x7fff).rom().region("rom0", 0x3800); + m_view[0](0xe800, 0xf7ff).rom().region("rom0", 0x2800); + // page 1 (for running CP/M) + m_view[1](0xe800, 0xf7ff).rom().region("rom0", 0x2800); + //page 2 (for running BIR) + m_view[2](0x9800, 0xbfff).rom().region("rom1", 0x1800); + m_view[2](0xc000, 0xc7ff).rom().region("rom1", 0x0000); + m_view[2](0xc800, 0xe7ff).rom().region("rom0", 0x0800); + m_view[2](0xe800, 0xf7ff).rom().region("rom0", 0x2800); +} + +void rm480z_state::rm480z_io(address_map &map) +{ + map(0x00, 0x17).select(0x7f00).rw(FUNC(rm480z_state::videoram_read), FUNC(rm480z_state::videoram_write)); + map(0x18, 0x1d).select(0xff00).rw(FUNC(rm480z_state::status_port_read), FUNC(rm480z_state::control_port_write)); + map(0x20, 0x23).mirror(0xff00).rw(m_ctc, FUNC(z80ctc_device::read), FUNC(z80ctc_device::write)); + map(0x24, 0x27).mirror(0xff00).rw(m_sio, FUNC(z80sio_device::cd_ba_r), FUNC(z80sio_device::cd_ba_w)); + map(0x38, 0x3b).mirror(0xff00).rw(FUNC(rm480z_state::hrg_port_read), FUNC(rm480z_state::hrg_port_write)); + //map(0x28, 0x29).mirror(0xff02); // am9511/am9512 maths chip // option + //map(0x2c, 0x2f).mirror(0xff00); // z80ctc IEEE int, Maths int, RTC, RTC // option + //map(0x30, 0x37).mirror(0xff00); // IEEE chip // option +} + +INPUT_PORTS_START( rm480z ) + PORT_START("kbrow.0") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("m M") PORT_CODE(KEYCODE_M) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME(", <") PORT_CODE(KEYCODE_COMMA) + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("i I") PORT_CODE(KEYCODE_I) + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("9 )") PORT_CODE(KEYCODE_9) + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("k K") PORT_CODE(KEYCODE_K) + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("l L") PORT_CODE(KEYCODE_L) + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("0 _") PORT_CODE(KEYCODE_0) + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("o O") PORT_CODE(KEYCODE_O) + + PORT_START("kbrow.1") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("x X") PORT_CODE(KEYCODE_X) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("c C") PORT_CODE(KEYCODE_C) + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("w W") PORT_CODE(KEYCODE_W) + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("3 #") PORT_CODE(KEYCODE_3) + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("s S") PORT_CODE(KEYCODE_S) + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("d D") PORT_CODE(KEYCODE_D) + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("4 $") PORT_CODE(KEYCODE_4) + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("e E") PORT_CODE(KEYCODE_E) + + PORT_START("kbrow.2") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("v V") PORT_CODE(KEYCODE_V) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("b B") PORT_CODE(KEYCODE_B) + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("r R") PORT_CODE(KEYCODE_R) + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("5 %") PORT_CODE(KEYCODE_5) + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("f F") PORT_CODE(KEYCODE_F) + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("g G") PORT_CODE(KEYCODE_G) + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("6 &") PORT_CODE(KEYCODE_6) + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("t T") PORT_CODE(KEYCODE_T) + + PORT_START("kbrow.3") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Shift") PORT_CODE(KEYCODE_LSHIFT) PORT_CODE(KEYCODE_RSHIFT) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Repeat") PORT_CODE(KEYCODE_RALT) + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("[ {") PORT_CODE(KEYCODE_OPENBRACE) + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("\\ |") PORT_CODE(KEYCODE_BACKSLASH) PORT_CODE(KEYCODE_BACKSLASH2) + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("] }") PORT_CODE(KEYCODE_CLOSEBRACE) + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Line Feed") PORT_CODE(KEYCODE_MENU) + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Delete") PORT_CODE(KEYCODE_BACKSPACE) + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Carriage Return") PORT_CODE(KEYCODE_ENTER) + + PORT_START("kbrow.4") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Space") PORT_CODE(KEYCODE_SPACE) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("n N") PORT_CODE(KEYCODE_N) + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("y Y") PORT_CODE(KEYCODE_Y) + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("7 '") PORT_CODE(KEYCODE_7) + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("h H") PORT_CODE(KEYCODE_H) + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("j J") PORT_CODE(KEYCODE_J) + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("8 (") PORT_CODE(KEYCODE_8) + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("u U") PORT_CODE(KEYCODE_U) + + PORT_START("kbrow.5") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Control") PORT_CODE(KEYCODE_LCONTROL) PORT_CODE(KEYCODE_RCONTROL) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("z Z") PORT_CODE(KEYCODE_Z) + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("q Q") PORT_CODE(KEYCODE_Q) + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("1 !") PORT_CODE(KEYCODE_1) + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Caps Lock") PORT_CODE(KEYCODE_CAPSLOCK) PORT_TOGGLE + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("a A") PORT_CODE(KEYCODE_A) + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("2 \"") PORT_CODE(KEYCODE_2) + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("Escape") PORT_CODE(KEYCODE_ESC) + + PORT_START("kbrow.6") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("F3") PORT_CODE(KEYCODE_F3) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME(UTF8_DOWN) PORT_CODE(KEYCODE_DOWN) + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME(UTF8_RIGHT) PORT_CODE(KEYCODE_RIGHT) + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME(UTF8_UP) PORT_CODE(KEYCODE_UP) + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("F4") PORT_CODE(KEYCODE_F4) + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME(UTF8_LEFT) PORT_CODE(KEYCODE_LEFT) + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("F2") PORT_CODE(KEYCODE_F2) + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("F1") PORT_CODE(KEYCODE_F1) + + PORT_START("kbrow.7") + PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME(". >") PORT_CODE(KEYCODE_STOP) + PORT_BIT(0x02, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("/ ?") PORT_CODE(KEYCODE_SLASH) + PORT_BIT(0x04, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("p P") PORT_CODE(KEYCODE_P) + PORT_BIT(0x08, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("- =") PORT_CODE(KEYCODE_MINUS) + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("; +") PORT_CODE(KEYCODE_QUOTE) + PORT_BIT(0x20, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME(": *") PORT_CODE(KEYCODE_COLON) + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("^ ~") PORT_CODE(KEYCODE_EQUALS) + PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("@ `") PORT_CODE(KEYCODE_ASTERISK) + + PORT_START("display_type") + PORT_CONFNAME( 0x01, 0x00, "Monitor" ) PORT_CHANGED_MEMBER(DEVICE_SELF, rm480z_state, monitor_changed, 0) + PORT_CONFSETTING( 0x00, "TTL RGB Colour Monitor" ) + PORT_CONFSETTING( 0x01, "Monochrome b/w Monitor" ) +INPUT_PORTS_END + +uint32_t rm480z_state::screen_update_rm480z(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) +{ + // blank screen + bitmap.fill(0); + + update_screen(bitmap); + + return 0; +} + +static void rm480z_default_rs232_devices(device_slot_interface &device) +{ + device.option_add("rm_mq2", RM_MQ2); +} + +static const z80_daisy_config daisy_chain[] = +{ + { "sio" }, + { "ctc" }, + { nullptr } +}; + +void rm480z_state::rm480z(machine_config &config) +{ + /* basic machine hardware */ + Z80(config, m_maincpu, 16_MHz_XTAL / 4); + m_maincpu->set_addrmap(AS_PROGRAM, &rm480z_state::rm480z_MK2_mem); + m_maincpu->set_addrmap(AS_IO, &rm480z_state::rm480z_io); + m_maincpu->set_daisy_config(daisy_chain); + + Z80SIO(config, m_sio, 16_MHz_XTAL / 4); + m_sio->out_int_callback().set_inputline(m_maincpu, INPUT_LINE_IRQ0); + m_sio->out_txdb_callback().set(m_rs232, FUNC(rs232_port_device::write_txd)); + m_sio->out_dtrb_callback().set(m_rs232, FUNC(rs232_port_device::write_dtr)); + m_sio->out_rtsb_callback().set(m_rs232, FUNC(rs232_port_device::write_rts)); + + // rs232 port for floppy drive + RS232_PORT(config, m_rs232, rm480z_default_rs232_devices, "rm_mq2"); + m_rs232->rxd_handler().set(m_sio, FUNC(z80sio_device::rxb_w)); + m_rs232->dcd_handler().set(m_sio, FUNC(z80sio_device::dcdb_w)); + m_rs232->dsr_handler().set(m_sio, FUNC(z80sio_device::syncb_w)); + m_rs232->cts_handler().set(m_sio, FUNC(z80sio_device::ctsb_w)); + + Z80CTC(config, m_ctc, 16_MHz_XTAL / 4); + m_ctc->intr_callback().set_inputline(m_maincpu, INPUT_LINE_IRQ0); + m_ctc->zc_callback<0>().set(m_sio, FUNC(z80sio_device::rxtxcb_w)); + + CLOCK(config, "ctc_clock", 16_MHz_XTAL / 8).signal_handler().set(m_ctc, FUNC(z80ctc_device::trg0)); + + /* video hardware */ + PALETTE(config, m_palette).set_init(FUNC(rm480z_state::palette_init)).set_entries(19); + SCREEN(config, m_screen, SCREEN_TYPE_RASTER); + m_screen->set_screen_update(FUNC(rm480z_state::screen_update_rm480z)); + m_screen->set_palette(m_palette); + m_screen->set_raw(16_MHz_XTAL, 1024, 0, 640, 312, 0, 240); + m_screen->register_vblank_callback(vblank_state_delegate(&rm480z_state::vblank_callback, this)); + + // keyboard is clocked by 500 kHz oscillator divided to give a 15625 Hz scan rate + TIMER(config, "kbd_scan").configure_periodic(FUNC(rm480z_state::kbd_scan), attotime::from_hz(500_kHz_XTAL / 32)); + + SPEAKER(config, "mono").front_center(); + SPEAKER_SOUND(config, m_speaker).add_route(ALL_OUTPUTS, "mono", 0.80); + + /* RAM configurations */ + RAM(config, RAM_TAG).set_default_size("256K"); +} + +void rm480z_state::rm480za(machine_config &config) +{ + rm480z(config); + m_maincpu->set_addrmap(AS_PROGRAM, &rm480z_state::rm480z_MK1_mem); +} + +/* ROM definitions */ + +ROM_START( rm480z ) + ROM_REGION( 0x4000, "rom0", 0 ) + ROM_LOAD( "fv2.0_0_12099_19.2.86.ic83", 0x0000, 0x4000, CRC(a0f02d8a) SHA1(1c063b842699dc0ad85a5a5f337f2864497f9c0f) ) + ROM_REGION( 0x4000, "rom1", 0 ) + ROM_LOAD( "fv2.0_1_12100_27.2.86.ic93", 0x0000, 0x4000, CRC(2a93ca6e) SHA1(7fdd772d4251dbf951a687d184ed787cfe21212b) ) + ROM_REGION( 0x2000, "chargen", 0 ) + ROM_LOAD( "cg06_12098_28.2.86.ic98", 0x0000, 0x2000, CRC(15d40f7e) SHA1(a7266357eb9be849f77a97ff3013b236c0af8289) ) +ROM_END + +ROM_START( rm480za ) + ROM_REGION( 0x2000, "ros", 0 ) + ROM_DEFAULT_BIOS("1.2d") + ROM_SYSTEM_BIOS(0, "1.2b", "ROS 1.2B") + ROMX_LOAD( "ros_1.2b.ls", 0x0000, 0x2000, CRC(37e93287) SHA1(c96d4b7eedadb0fb8e3732b6ba3e898e123c393f), ROM_BIOS(0) ) + ROM_SYSTEM_BIOS(1, "1.2d", "ROS 1.2D") + ROMX_LOAD( "ros-1-2d.bin", 0x0000, 0x2000, CRC(3fe61618) SHA1(ee4d70694489ab7f123e59d73b304d6d5fcd8a81), ROM_BIOS(1) ) + + ROM_REGION( 0x2000, "bir0", 0 ) + ROM_LOAD( "bir5-4-0.bin", 0x0000, 0x2000, CRC(51875c95) SHA1(96bb058512a0f21634e629229effc6b36d0f0a7a) ) + ROM_REGION( 0x2000, "bir1", 0 ) + ROM_LOAD( "bir5-4-1.bin", 0x0000, 0x2000, CRC(63959245) SHA1(2e42453ce281fd6cc2de176ff98f0a326d3ae8a8) ) + ROM_REGION( 0x2000, "bir2", 0 ) + ROM_LOAD( "bir5-4-2.bin", 0x0000, 0x2000, CRC(d3eb07cf) SHA1(9e576e8d2ae571319dc6c1cb035f13cf56abf690) ) + + ROM_REGION( 0x2000, "chargen", 0 ) + ROM_LOAD( "cg06.lq", 0x0000, 0x2000, BAD_DUMP CRC(15d40f7e) SHA1(a7266357eb9be849f77a97ff3013b236c0af8289) ) // chip is marked CG05, might not be the same, so marked as bad +ROM_END + +/* Driver */ +// YEAR NAME PARENT COMPAT MACHINE INPUT CLASS INIT COMPANY FULLNAME FLAGS +COMP(1981, rm480z, 0, 0, rm480z, rm480z, rm480z_state, driver_device::empty_init, "Research Machines", "LINK RM-480Z (set 1)", 0) +COMP(1981, rm480za, rm480z, 0, rm480za, rm480z, rm480z_state, driver_device::empty_init, "Research Machines", "LINK RM-480Z (set 2)", 0) diff --git a/src/mame/rm/rm480z.h b/src/mame/rm/rm480z.h new file mode 100644 index 00000000000..d70a79b13e3 --- /dev/null +++ b/src/mame/rm/rm480z.h @@ -0,0 +1,153 @@ +// license:BSD-3-Clause +// copyright-holders:Robin Sergeant + +/* + +Research Machines RM 480Z + +*/ + +#ifndef MAME_RM_RM480Z_H +#define MAME_RM_RM480Z_H + +#pragma once + +#include "bus/rs232/rs232.h" +#include "cpu/z80/z80.h" +#include "machine/timer.h" +#include "machine/ram.h" +#include "machine/z80ctc.h" +#include "machine/z80daisy.h" +#include "machine/z80sio.h" +#include "sound/spkrdev.h" + +#include "emupal.h" + +// +// +// + +#define RM480Z_MAINCPU_TAG "maincpu" + +// +// +// + +class rm480z_state : public driver_device +{ +public: + rm480z_state(const machine_config &mconfig, device_type type, const char *tag) : + driver_device(mconfig, type, tag), + m_view(*this, "view"), + m_maincpu(*this, RM480Z_MAINCPU_TAG), + m_screen(*this, "screen"), + m_palette(*this, "palette"), + m_ram(*this, RAM_TAG), + m_bank(*this, "bank%u", 1U), + m_ctc(*this, "ctc"), + m_sio(*this, "sio"), + m_rs232(*this, "rs232"), + m_speaker(*this, "speaker"), + m_io_kbrow(*this, "kbrow.%u", 0U), + m_io_display_type(*this, "display_type"), + m_chargen(*this, "chargen") + { + } + + DECLARE_INPUT_CHANGED_MEMBER(monitor_changed); + + void rm480z(machine_config &config); + void rm480za(machine_config &config); + +private: + + template + class rm480z_vram + { + public: + void set_char(int row, int col, uint8_t data) { m_chars[row][col] = data; } + void reset() { memset(m_chars, 0x80, sizeof(m_chars)); } + + uint8_t get_char(int row, int col) const { return m_chars[row][col]; } + + private: + uint8_t m_chars[ROWS][COLS]; + }; + + enum class hrg_display_mode : uint8_t + { + none = 0, + high = 1, + medium_0 = 2, + medium_1 = 3, + extra_high = 4 + }; + + static inline constexpr int RM480Z_SCREENROWS = 24; + static inline constexpr int RM480Z_SCREENCOLS = 80; + static inline constexpr int RM480Z_VIDEOMODE_40COL = 0x01; + static inline constexpr int RM480Z_VIDEOMODE_80COL = 0x02; + static inline constexpr int RM480Z_HRG_RAM_SIZE = 16384; // 16k + static inline constexpr int RM480Z_HRG_SCRATCHPAD_SIZE = 16; + + memory_view m_view; + + bool m_kbd_reset = true; // cleared by firmware when it is ready to receive keyboard interrupts + bool m_kbd_ready = false; // set when new key code is available (on key press or release) + uint8_t m_kbd_code = 0; // scan code of last keyboard event (bit 7 set on key release) + uint8_t m_kbd_scan_pos = 0; // current keyboard matrix scanning position + uint8_t m_kbd_state[8] = {0}; // keyboard matrix state (bits set when keys held down). + + rm480z_vram m_vram; + int m_videomode = RM480Z_VIDEOMODE_80COL; + bool m_alt_char_set = false; // replace teletext characters with inverse ASCII characters + + uint8_t m_hrg_ram[RM480Z_HRG_RAM_SIZE]; + uint8_t m_hrg_scratchpad[RM480Z_HRG_SCRATCHPAD_SIZE]; + hrg_display_mode m_hrg_display_mode = hrg_display_mode::none; + uint8_t m_hrg_port0 = 0; // HRG port 0 value (various uses) + uint8_t m_hrg_port1 = 0; // HRG port 1 value (various uses) + bool m_hrg_mem_open = false; // when HRG memory is open the display in blanked (used when clearing screen) + bool m_video_inhibit = false; // inhibits text output on a colour display + bool m_hrg_inhibit = false; // inhibits graphics output on a monocrhome display + + required_device m_maincpu; + required_device m_screen; + required_device m_palette; + required_device m_ram; + required_memory_bank_array<4> m_bank; + required_device m_ctc; + required_device m_sio; + required_device m_rs232; + required_device m_speaker; + required_ioport_array<8> m_io_kbrow; + required_ioport m_io_display_type; + required_region_ptr m_chargen; + + TIMER_DEVICE_CALLBACK_MEMBER(kbd_scan); + void vblank_callback(screen_device &screen, bool vblank_state); + + void rm480z_io(address_map &map) ATTR_COLD; + void rm480z_MK1_mem(address_map &map) ATTR_COLD; + void rm480z_MK2_mem(address_map &map) ATTR_COLD; + + uint8_t status_port_read(offs_t offset); + void control_port_write(offs_t offset, uint8_t data); + uint8_t videoram_read(offs_t offset); + void videoram_write(offs_t offset, uint8_t data); + uint8_t hrg_port_read(offs_t offset); + void hrg_port_write(offs_t offset, uint8_t data); + + void machine_reset() override ATTR_COLD; + uint32_t screen_update_rm480z(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); + void update_screen(bitmap_ind16 &bitmap) const; + void putChar(int charnum, int x, int y, bitmap_ind16 &bitmap, bool bMonochrome) const; + int calculate_hrg_vram_index() const; + void palette_init(palette_device &palette); + void change_palette(int index, uint8_t value); + void draw_extra_high_res_graphics(bitmap_ind16 &bitmap) const; + void draw_high_res_graphics(bitmap_ind16 &bitmap) const; + void draw_medium_res_graphics(bitmap_ind16 &bitmap) const; +}; + +#endif // MAME_RM_RM480Z_H diff --git a/src/mame/rm/rm480z_m.cpp b/src/mame/rm/rm480z_m.cpp new file mode 100644 index 00000000000..a78bb95da15 --- /dev/null +++ b/src/mame/rm/rm480z_m.cpp @@ -0,0 +1,249 @@ +// license:BSD-3-Clause +// copyright-holders:Robin Sergeant + +/* + +RM 480Z machine + +*/ + +#include "emu.h" +#include "rm480z.h" + +TIMER_DEVICE_CALLBACK_MEMBER(rm480z_state::kbd_scan) +{ + if (!m_kbd_reset && !m_kbd_ready) + { + const int row = m_kbd_scan_pos >> 3; + const int col = m_kbd_scan_pos & 0x07; + const uint8_t new_val = m_io_kbrow[row]->read(); + const uint8_t delta = new_val ^ m_kbd_state[row]; + const uint8_t mask = 1 << col; + + if (delta & mask) + { + m_kbd_code = m_kbd_scan_pos; + if ((new_val & mask) == 0) + { + m_kbd_code |= 0x80; // key release + } + m_kbd_ready = true; + m_kbd_state[row] ^= mask; + m_ctc->trg2(0); + m_ctc->trg2(1); // trigger keyboard interrupt + } + + m_kbd_scan_pos++; + m_kbd_scan_pos &= 0x3f; // reset position after scanning 64 keys + } +} + +void rm480z_state::vblank_callback(screen_device &screen, bool vblank_state) +{ + // used as 50 Hz clock for keyboard repeat timer + m_ctc->trg3(vblank_state); +} + +void rm480z_state::control_port_write(offs_t offset, uint8_t data) +{ + switch (offset & 0xff) + { + case 0: + { + const int bank = (offset & 0x0300) >> 8; + uint8_t* ram = m_ram->pointer(); + m_bank[bank]->set_base(ram + 0x4000 * (data & 0x0f)); + break; + } + case 1: + { + const int page = data & 0x03; + if (page == 3) + { + // page 3 is all RAM (no ROM) + m_view.disable(); + } + else + { + m_view.select(page); + } + m_kbd_reset = !BIT(data, 6); + break; + } + case 2: + m_speaker->level_w(BIT(data, 5)); + m_alt_char_set = BIT(data, 6); + m_videomode = BIT(data, 7) ? RM480Z_VIDEOMODE_80COL : RM480Z_VIDEOMODE_40COL; + break; + case 3: + // D/A Converter output + break; + case 5: + // USER I/O output port + break; + } +} + +uint8_t rm480z_state::status_port_read(offs_t offset) +{ + uint8_t ret_val = 0; + + switch (offset & 0xff) + { + case 0: + // DIL switches (were used to set a network address) + break; + case 1: + // bit 0 is low during line blank + if (!m_screen->hblank()) + { + ret_val |= 0x01; + } + // bit 1 is high during frame blank + if (m_screen->vblank()) + { + ret_val |= 0x02; + } + // bit 3 is low when new key waiting to be read from kbd + if (!m_kbd_ready) + { + ret_val |= 0x04; + } + break; + case 2: + // second status port (bits relate to unemulated features) + break; + case 3: + ret_val = m_kbd_code; + m_kbd_ready = false; + break; + case 5: + // USER I/O input port + break; + } + + return ret_val; +} + +uint8_t rm480z_state::hrg_port_read(offs_t offset) +{ + uint8_t ret_val = 0; + + switch (offset) + { + case 0: + // write only port + break; + case 1: + // write only port + break; + case 2: + // bit 0 is high during line blank + if (m_screen->hblank()) + { + ret_val |= 0x01; + } + // bit 1 is high during frame blank + if (m_screen->vblank()) + { + ret_val |= 0x02; + } + // bit 6 is low when screen memory is open + if (!m_hrg_mem_open) + { + ret_val |= 0x40; + } + break; + case 3: + // read HRG memory (addressed using ports 0 and 1) + const int index = calculate_hrg_vram_index(); + if (index >= 0) + { + ret_val = m_hrg_ram[index]; + } + break; + } + + return ret_val; +} + +void rm480z_state::hrg_port_write(offs_t offset, uint8_t data) +{ + switch (offset) + { + case 0: + m_hrg_port0 = data; + break; + case 1: + m_hrg_port1 = data; + break; + case 2: + if (BIT(data, 7)) + { + change_palette(data & 0x0f, 255 - m_hrg_port0); + } + else + { + switch ((data >> 4) & 0x03) + { + case 0x00: + m_hrg_display_mode = hrg_display_mode::extra_high; + break; + case 0x01: + m_hrg_display_mode = hrg_display_mode::high; + break; + case 0x03: + if (BIT(data, 3)) + { + m_hrg_display_mode = hrg_display_mode::medium_1; + } + else + { + m_hrg_display_mode = hrg_display_mode::medium_0; + } + break; + default: + m_hrg_display_mode = hrg_display_mode::none; + break; + } + } + m_hrg_inhibit = !BIT(data, 1); + m_video_inhibit = !BIT(data, 2); + m_hrg_mem_open = !BIT(data, 6); + break; + case 3: + const int index = calculate_hrg_vram_index(); + if (index >= 0) + { + m_hrg_ram[index] = data; + } + break; + } +} + +void rm480z_state::machine_reset() +{ + uint8_t* mem = m_ram->pointer(); + + m_vram.reset(); + memset(m_hrg_ram, 0, sizeof(m_hrg_ram)); + m_hrg_display_mode = hrg_display_mode::none; + m_hrg_port0 = 0; + m_hrg_port1 = 0; + m_hrg_mem_open = false; + m_video_inhibit = false; + m_hrg_inhibit = false; + + m_kbd_reset = true; + m_kbd_ready = false; + m_kbd_code = 0; + m_kbd_scan_pos = 0; + memset(m_kbd_state, 0, sizeof(m_kbd_scan_pos)); + + m_view.select(0); + + m_bank[0]->set_base(mem); + m_bank[1]->set_base(mem + 0x4000); + m_bank[2]->set_base(mem + 0x8000); + m_bank[3]->set_base(mem + 0xc000); +} diff --git a/src/mame/rm/rm480z_v.cpp b/src/mame/rm/rm480z_v.cpp new file mode 100644 index 00000000000..6a3ec3b7118 --- /dev/null +++ b/src/mame/rm/rm480z_v.cpp @@ -0,0 +1,260 @@ +// license:BSD-3-Clause +// copyright-holders:Robin Sergeant + +/* + +RM 480Z video code + +*/ + +#include "emu.h" +#include "rm480z.h" + +INPUT_CHANGED_MEMBER(rm480z_state::monitor_changed) +{ + // re-calculate HRG palette values from scratchpad + for (int c=0; c < RM480Z_HRG_SCRATCHPAD_SIZE; c++) + { + change_palette(c, m_hrg_scratchpad[c]); + } +} + +void rm480z_state::change_palette(int index, uint8_t value) +{ + rgb_t new_colour; + + if (m_io_display_type->read()) + { + // value is intensity for a b/w monochrome display + new_colour = rgb_t(value, value, value); + } + else + { + // for colour displays only bits 4, 6 and 7 are used. + uint8_t red = BIT(value, 6) << 2; + uint8_t green = BIT(value, 7) << 1; + uint8_t blue = BIT(value, 4); + new_colour = raw_to_rgb_converter::standard_rgb_decoder<1, 1, 1, 2, 1, 0>(red | green | blue); + } + + m_palette->set_pen_color(index + 3, new_colour); + m_hrg_scratchpad[index] = value; +} + +void rm480z_state::palette_init(palette_device &palette) +{ + // text display palette (black, grey (dim), and white) + palette.set_pen_color(0, rgb_t::black()); + palette.set_pen_color(1, rgb_t(0xc0, 0xc0, 0xc0)); + palette.set_pen_color(2, rgb_t::white()); + + // HRG palette (initialise to all black) + for (int c = 3; c < 19; c++) + { + palette.set_pen_color(c, rgb_t::black()); + } +} + +int rm480z_state::calculate_hrg_vram_index() const +{ + int index = -1; + + if (m_hrg_port0 < 192) + { + // 15K of pixel data, 80 bytes per row + if (m_hrg_port1 < 80) + { + index = (m_hrg_port0 * 80) + m_hrg_port1; + } + } + else + { + // 1K of user defined character data, 8 bytes per character grouped into 16 byte blocks + if ((m_hrg_port1 >= 64) && (m_hrg_port1 <= 79)) + { + index = 15'360 + ((m_hrg_port0 - 192) * 16) + (m_hrg_port1 - 64); + } + } + + return index; +} + +uint8_t rm480z_state::videoram_read(offs_t offset) +{ + uint8_t data = 0; + const int row = offset & 0xff; + const int col = offset >> 8; + + if (col < RM480Z_SCREENCOLS) + { + data = m_vram.get_char(row, col); + } + + return data; +} + +void rm480z_state::videoram_write(offs_t offset, uint8_t data) +{ + const int row = offset & 0xff; + const int col = offset >> 8; + + if (col < RM480Z_SCREENCOLS) + { + m_vram.set_char(row, col, data); + } +} + +void rm480z_state::putChar(int charnum, int x, int y, bitmap_ind16 &bitmap, bool bMonochrome) const +{ + const int pw = (m_videomode == RM480Z_VIDEOMODE_40COL) ? 2 : 1; + const int ph = 1; + bool attrDim = false; + bool attrRev = false; + + if (charnum > 128) + { + if (m_alt_char_set) + { + charnum -= 128; + attrDim = true; + attrRev = true; + } + else if (charnum < 0xc0) + { + attrDim = true; + } + } + + int data_pos = charnum * 16; + + for (int r = 0; r < 10; r++, data_pos++) + { + uint8_t data = m_chargen[data_pos]; + + for (int c = 0; c < 8; c++, data <<= 1) + { + uint8_t pixel_value; + if (attrRev) + { + pixel_value = BIT(data, 7) ? 0 : 2; + } + else + { + pixel_value = BIT(data, 7) ? 2 : 0; + } + if (attrDim && pixel_value && bMonochrome) + { + // NB only monochrome monitors support dimmed (grey) text + pixel_value = 1; + } + if (pixel_value) + { + bitmap.plot_box((x * 8 + c) * pw, y * 10 + r, pw, ph, pixel_value); + } + } + } +} + +void rm480z_state::draw_extra_high_res_graphics(bitmap_ind16 &bitmap) const +{ + const int pw = 1; + const int ph = 1; + + // (1-bit per pixel, 8 pixels per byte) + for (int y = 0; y < 192; y++) + { + for (int x = 0; x < 640; x+= 8) + { + int index = (y * 80) + (x / 8); + uint8_t data = m_hrg_ram[index]; + for (int c = 0; c < 8; c++, data <<= 1) + { + uint8_t pixel_value = data & 0x80 ? 2 : 0; + if (pixel_value) + { + bitmap.plot_box((x + c)*pw, y*ph, pw, ph, pixel_value); + } + } + } + } +} + +void rm480z_state::draw_high_res_graphics(bitmap_ind16 &bitmap) const +{ + const int pw = 2; + const int ph = 1; + + // (2-bits per pixel, 4 pixels per byte) + for (int y = 0; y < 192; y++) + { + for (int x = 0; x < 320; x+= 4) + { + int index = (y * 80) + (x / 4); + uint8_t data = m_hrg_ram[index]; + for (int c = 0; c < 4; c++, data >>= 2) + { + bitmap.plot_box((x + c)*pw, y*ph, pw, ph, (data & 0x03) + 3); + } + } + } +} + +void rm480z_state::draw_medium_res_graphics(bitmap_ind16 &bitmap) const +{ + const int page = (m_hrg_display_mode == hrg_display_mode::medium_0) ? 0 : 1; + const int pw = 4; + const int ph = 2; + + // (4-bits per pixel, 2 pixels per byte) + for (int y = 0; y < 96; y++) + { + for (int x = 0; x < 160; x+= 2) + { + int index = (((y * 2) + page) * 80) + (x / 2); + uint8_t data = m_hrg_ram[index]; + for (int c = 0; c < 2; c++, data >>= 2) + { + uint8_t pixel_value = (((data >> 2) & 0x0c) | (data & 0x03)) +3; + bitmap.plot_box((x + c)*pw, y*ph, pw, ph, pixel_value); + } + } + } +} + +void rm480z_state::update_screen(bitmap_ind16 &bitmap) const +{ + const int ncols = (m_videomode == RM480Z_VIDEOMODE_40COL) ? 40 : 80; + const bool bMonochrome = m_io_display_type->read(); + + if (!m_hrg_mem_open && (!m_hrg_inhibit || !bMonochrome)) + { + switch (m_hrg_display_mode) + { + case hrg_display_mode::extra_high: + draw_extra_high_res_graphics(bitmap); + break; + case hrg_display_mode::high: + draw_high_res_graphics(bitmap); + break; + case hrg_display_mode::medium_0: + case hrg_display_mode::medium_1: + draw_medium_res_graphics(bitmap); + break; + case hrg_display_mode::none: + // don't display HRG + break; + } + } + + if (!m_video_inhibit || bMonochrome) + { + for (int row = 0; row < RM480Z_SCREENROWS; row++) + { + for (int col = 0; col < ncols; col++) + { + uint8_t curch = m_vram.get_char(row, col); + putChar(curch, col, row, bitmap, bMonochrome); + } + } + } +} diff --git a/src/mame/rm/rm_mq2.cpp b/src/mame/rm/rm_mq2.cpp new file mode 100644 index 00000000000..0d7fd5c9c3d --- /dev/null +++ b/src/mame/rm/rm_mq2.cpp @@ -0,0 +1,256 @@ +// license:BSD-3-Clause +// copyright-holders:Robin Sergeant +/********************************************************************** + + Research Machines MQ2 dual floppy drive + +An RS232 serial communication protocol is used for communcation between the MQ2 and 480Z. + +Initial requests are sent to the drive at 9600 baud, and the drive's response to this upgrades +the connection to 125000 baud. The RTS output line is used by the 480Z to reset the baud rate +back to 9600 baud prior to issuing a new request. When this line is high an NMI is generated +to achieve this. + +On first boot the 480Z requests disk handling code which is downloaded from the MQ2 and used +to supplement the 480Z firmware (hence most of the disk handling code is actually contained +in the MQ2 itself). + +The RS232 cable is connected as follows: + +480Z MQ2 +RxD <-- TxD +TxD --> RxD +DCD <-- RTS +RTS --> DCD +CTS <-- DTR +DTR --> CTS +GND --- GND + +Interrupt mode 1 is used and only the FDC generates interrupts. + +**********************************************************************/ + +#include "emu.h" +#include "rm_mq2.h" +#include "machine/clock.h" + +//************************************************************************** +// DEVICE DEFINITIONS +//************************************************************************** + +DEFINE_DEVICE_TYPE(RM_MQ2, rmMQ2_device, "rm_mq2", "Research Machines MQ2") + + +//------------------------------------------------- +// ROM( c1551 ) +//------------------------------------------------- + +ROM_START( rmMQ2 ) + ROM_REGION( 0x2000, "idc_rom", 0 ) + ROM_LOAD( "idc3-1i.rom", 0x0000, 0x2000, CRC(39e2cdf0) SHA1(ba523af357b61bbe6192727139850f36597d79f1) ) +ROM_END + + +//------------------------------------------------- +// rom_region - device-specific ROM region +//------------------------------------------------- + +const tiny_rom_entry *rmMQ2_device::device_rom_region() const +{ + return ROM_NAME( rmMQ2 ); +} + +//------------------------------------------------- +// ADDRESS_MAP( rmMQ2_mem ) +//------------------------------------------------- + +void rmMQ2_device::rmMQ2_mem(address_map &map) +{ + map(0x0000, 0x1fff).rom().region("idc_rom", 0); + map(0xe000, 0xe7ff).mirror(0x1800).ram(); // 2K RAM mirrored at 0xe800, 0xf000 and 0xf800 +} + +//------------------------------------------------- +// ADDRESS_MAP( rmMQ2_io ) +//------------------------------------------------- + +void rmMQ2_device::rmMQ2_io(address_map &map) +{ + map(0x00, 0x03).mirror(0xff00).r(FUNC(rmMQ2_device::fdc_read)).w(m_fdc, FUNC(fd1793_device::write)); + map(0x04, 0x04).mirror(0xff00).r(FUNC(rmMQ2_device::drive_status_r)); + map(0x07, 0x07).mirror(0xff00).r(FUNC(rmMQ2_device::status_r)); + map(0x0d, 0x0d).mirror(0xff00).w(FUNC(rmMQ2_device::port1_w)); + map(0x0e, 0x0e).mirror(0xff00).w(FUNC(rmMQ2_device::port0_w)); + map(0xe0, 0xe3).mirror(0xff00).rw(m_ctc, FUNC(z80ctc_device::read), FUNC(z80ctc_device::write)); + map(0xe4, 0xe7).mirror(0xff00).rw(m_sio, FUNC(z80sio_device::ba_cd_r), FUNC(z80sio_device::ba_cd_w)); +} + +//------------------------------------------------- +// device_add_mconfig - add device configuration +//------------------------------------------------- + +static void rmMQ2_floppies(device_slot_interface &device) +{ + device.option_add("525sd", FLOPPY_525_SD); + device.option_add("525dd", FLOPPY_525_DD); + device.option_add("525qd", FLOPPY_525_QD); +} + +void rmMQ2_device::device_add_mconfig(machine_config &config) +{ + Z80(config, m_maincpu, 8_MHz_XTAL / 2); + m_maincpu->set_addrmap(AS_PROGRAM, &rmMQ2_device::rmMQ2_mem); + m_maincpu->set_addrmap(AS_IO, &rmMQ2_device::rmMQ2_io); + + Z80SIO(config, m_sio, 8_MHz_XTAL / 2); + m_sio->out_txdb_callback().set(FUNC(rmMQ2_device::output_rxd)); + m_sio->out_dtrb_callback().set(FUNC(rmMQ2_device::output_cts)); + m_sio->out_rtsb_callback().set(FUNC(rmMQ2_device::output_dcd)); + + Z80CTC(config, m_ctc, 8_MHz_XTAL / 2); + m_ctc->zc_callback<0>().set(m_sio, FUNC(z80sio_device::rxtxcb_w)); + + CLOCK(config, "ctc_clock", 8_MHz_XTAL / 4).signal_handler().set(m_ctc, FUNC(z80ctc_device::trg0)); + + FD1793(config, m_fdc, 8_MHz_XTAL / 8); + m_fdc->intrq_wr_callback().set(FUNC(rmMQ2_device::fdc_intrq_w)); + m_fdc->drq_wr_callback().set(FUNC(rmMQ2_device::fdc_drq_w)); + FLOPPY_CONNECTOR(config, m_floppy0, rmMQ2_floppies, "525qd", floppy_image_device::default_mfm_floppy_formats); + FLOPPY_CONNECTOR(config, m_floppy1, rmMQ2_floppies, "525qd", floppy_image_device::default_mfm_floppy_formats); +} + +//------------------------------------------------- +// c1551_device - constructor +//------------------------------------------------- + +rmMQ2_device::rmMQ2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : + device_t(mconfig, RM_MQ2, tag, owner, clock), + device_rs232_port_interface(mconfig, *this), + m_maincpu(*this, "maincpu"), + m_ctc(*this, "ctc"), + m_sio(*this, "sio"), + m_fdc(*this, "wd1793"), + m_floppy0(*this, "wd1793:0"), + m_floppy1(*this, "wd1793:1") +{ +} + +void rmMQ2_device::device_start() +{ +} + +void rmMQ2_device::input_txd(int state) +{ + if (started()) + { + m_sio->rxb_w(state); + } +} + +void rmMQ2_device::input_dtr(int state) +{ + if (started()) + { + m_sio->ctsb_w(state); + } +} + +void rmMQ2_device::input_rts(int state) +{ + if (started()) + { + m_sio->dcdb_w(state); + // give firmware ~50ms to finish initialisation before allowing first NMI + if (m_maincpu->total_cycles() > 200'000) + { + // NMI generated to reset baud rate to 9600 + m_maincpu->set_input_line(INPUT_LINE_NMI, state ? ASSERT_LINE : CLEAR_LINE); + } + } +} + +void rmMQ2_device::port0_w(uint8_t data) +{ + floppy_image_device *floppy = nullptr; + const int drive_no = ~data & 0x0f; + + switch (drive_no) + { + case 1: + floppy = m_floppy0->get_device(); + break; + case 2: + floppy = m_floppy1->get_device(); + break; + default: + // ignore as only two drives supported + break; + } + + if (floppy) + { + m_fdc->set_floppy(floppy); + // motor should be controlled by bit 4, but it changes state too frequently so keep on instead + floppy->mon_w(0); + floppy->ss_w(BIT(data, 5)); + } +} + +void rmMQ2_device::port1_w(uint8_t data) +{ + // bit 1 is set for DD (MFM encoding), and cleared for SD (FM encoding) + m_fdc->dden_w(!BIT(data, 1)); + m_8inch_sel = BIT(data, 0); +} + +uint8_t rmMQ2_device::status_r() +{ + if (!m_fdc->intrq_r() && !m_fdc->drq_r()) + { + /* this port is read by the firmware to syncronize access to the FDC. If + neither the DRQ or INTRQ lines are active it suspends the CPU. */ + m_maincpu->set_input_line(Z80_INPUT_LINE_WAIT, ASSERT_LINE); + } + return 0; +} + +uint8_t rmMQ2_device::drive_status_r() +{ + // return high bits to indicate that the drives are ok + return 0xff; +} + +void rmMQ2_device::fdc_intrq_w(int state) +{ + if (state) + { + m_maincpu->set_input_line(Z80_INPUT_LINE_WAIT, CLEAR_LINE); + } + m_maincpu->set_input_line(INPUT_LINE_IRQ0, state); +} + +void rmMQ2_device::fdc_drq_w(int state) +{ + if (state) + { + m_maincpu->set_input_line(Z80_INPUT_LINE_WAIT, CLEAR_LINE); + } +} + +uint8_t rmMQ2_device::fdc_read(offs_t offset) +{ + uint8_t data = m_fdc->read(offset); + + /* When the 8INCH line is high the Index Pulse FDC input is connected to pin 4 of the + floppy connector, which is unused by 5.25" drives. This fact is used by the firmware + to detect the drive type being used, i.e. it sets 8INCH high and checks to see if the IP + is received within a set time. We must therefore ensure that the INDEX bit of the + FDC status byte remains low during this detection period to prevent 8" drive parameters + from being mistakenly used. */ + if ((offset == 0) && m_8inch_sel) + { + data &= 0xfd; + } + + return data; +} diff --git a/src/mame/rm/rm_mq2.h b/src/mame/rm/rm_mq2.h new file mode 100644 index 00000000000..eabdec503b3 --- /dev/null +++ b/src/mame/rm/rm_mq2.h @@ -0,0 +1,53 @@ +// license:BSD-3-Clause +// copyright-holders:Robin Sergeant + +#ifndef MAME_BUS_RM_MQ2_H +#define MAME_BUS_RM_MQ2_H + +#pragma once + +#include "bus/rs232/rs232.h" +#include "cpu/z80/z80.h" +#include "imagedev/floppy.h" +#include "machine/wd_fdc.h" +#include "machine/z80ctc.h" +#include "machine/z80daisy.h" +#include "machine/z80sio.h" + +class rmMQ2_device : public device_t, public device_rs232_port_interface +{ +public: + rmMQ2_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + +private: + bool m_8inch_sel = false; + + required_device m_maincpu; + required_device m_ctc; + required_device m_sio; + required_device m_fdc; + required_device m_floppy0; + required_device m_floppy1; + + const tiny_rom_entry *device_rom_region() const override ATTR_COLD; + void device_add_mconfig(machine_config &config) override ATTR_COLD; + void device_start() override ATTR_COLD; + + void input_txd(int state) override; + void input_rts(int state) override; + void input_dtr(int state) override; + + void rmMQ2_mem(address_map &map) ATTR_COLD; + void rmMQ2_io(address_map &map) ATTR_COLD; + void port0_w(uint8_t data); + void port1_w(uint8_t data); + uint8_t status_r(); + uint8_t drive_status_r(); + void fdc_intrq_w(int state); + void fdc_drq_w(int state); + uint8_t fdc_read(offs_t offset); +}; + +DECLARE_DEVICE_TYPE(RM_MQ2, rmMQ2_device) + +#endif // MAME_BUS_RM_MQ2_H -- cgit v1.2.3