summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/a2bus/grappler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/bus/a2bus/grappler.cpp')
-rw-r--r--src/devices/bus/a2bus/grappler.cpp1057
1 files changed, 1057 insertions, 0 deletions
diff --git a/src/devices/bus/a2bus/grappler.cpp b/src/devices/bus/a2bus/grappler.cpp
new file mode 100644
index 00000000000..3b14db16c48
--- /dev/null
+++ b/src/devices/bus/a2bus/grappler.cpp
@@ -0,0 +1,1057 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+#include "emu.h"
+#include "grappler.h"
+
+//#define VERBOSE 1
+//#define LOG_OUTPUT_FUNC osd_printf_info
+#include "logmacro.h"
+
+namespace {
+
+ROM_START(grappler)
+ ROM_REGION(0x0800, "rom", 0)
+ ROM_LOAD( "eps-1_c1981.u6", 0x0000, 0x0800, CRC(862773cb) SHA1(791ebae64a7fad8f42bdfaec36b9e2d34f12ded8) )
+ROM_END
+
+
+ROM_START(grapplerplus)
+ // TODO: add other revisions - 3.3 is known to exist
+
+ ROM_DEFAULT_BIOS("v32")
+ ROM_SYSTEM_BIOS(0, "v30", "ROM 3.0")
+ ROM_SYSTEM_BIOS(1, "v32", "ROM 3.2")
+
+ ROM_REGION(0x1000, "rom", 0)
+ ROMX_LOAD( "3.0.u9", 0x0000, 0x1000, CRC(17cf5e02) SHA1(9b01a9b1cf7752987e03f9b0285eb9329d63b777), ROM_BIOS(0) )
+ ROMX_LOAD( "3.2.u9", 0x0000, 0x1000, CRC(6f88b70c) SHA1(433ae61a0553ee9c1628ea5b6376dac848c04cad), ROM_BIOS(1) )
+ROM_END
+
+
+ROM_START(bufgrapplerplus)
+ // TODO: confirm contents
+ // despite having the same version string, this differs from the Grapper+ 3.2.u9 ROM
+
+ // 9433B-0141
+ // 90ROM00002
+ // GI8603CEY
+ // TAIWAN
+ ROM_REGION(0x1000, "rom", 0)
+ ROM_LOAD( "90rom00002.u18", 0x0000, 0x1000, CRC(cd07c7ef) SHA1(6c2f1375b5df6bb65dfca1444c064661242fef1a) )
+
+ // S 8450
+ // SCN8048A
+ // C6N40 B
+ // 95ROM08048
+ ROM_REGION(0x0400, "mcu", 0)
+ ROM_LOAD( "95rom08048.u10", 0x0000, 0x0400, CRC(55990f67) SHA1(f0907cf02d8c5dbf1bfacb0d626f2231142f0ff7) )
+ROM_END
+
+
+INPUT_PORTS_START(grapplerplus)
+ PORT_START("S1")
+ PORT_DIPNAME(0x07, 0x00, "Printer Type") PORT_DIPLOCATION("S1:4,3,2")
+ PORT_DIPSETTING( 0x00, "Epson Series")
+ PORT_DIPSETTING( 0x01, "NEC 8023/C. Itoh 8510/DMP 85")
+ PORT_DIPSETTING( 0x02, "Star Gemini")
+ PORT_DIPSETTING( 0x03, "Anadex Printers")
+ PORT_DIPSETTING( 0x04, "Okidata 82A, 83A, 92, 93, 84")
+ PORT_DIPSETTING( 0x06, "Okidata 84 w/o Step II Graphics")
+ PORT_DIPSETTING( 0x05, "Apple Dot Matrix")
+ PORT_DIPSETTING( 0x07, "invalid")
+ PORT_DIPNAME(0x08, 0x08, "Most Significant Bit") PORT_DIPLOCATION("S1:1") PORT_CHANGED_MEMBER(DEVICE_SELF, a2bus_grapplerplus_device_base, sw_msb, 0)
+ PORT_DIPSETTING( 0x08, "Software Control")
+ PORT_DIPSETTING( 0x00, "Not Transmitted")
+INPUT_PORTS_END
+
+
+INPUT_PORTS_START(bufgrapplerplus)
+ PORT_INCLUDE(grapplerplus)
+
+ PORT_START("CNF")
+ PORT_CONFNAME(0xff, 0x00, "RAM Size")
+ PORT_CONFSETTING( 0xfc, "16K (2 chips)")
+ PORT_CONFSETTING( 0xf0, "32K (4 chips)")
+ PORT_CONFSETTING( 0x00, "64K (8 chips)")
+INPUT_PORTS_END
+
+} // anonymous namespace
+
+
+
+DEFINE_DEVICE_TYPE(A2BUS_GRAPPLER, a2bus_grappler_device, "a2grappler", "Orange Micro Grappler Printer Interface")
+DEFINE_DEVICE_TYPE(A2BUS_GRAPPLERPLUS, a2bus_grapplerplus_device, "a2grapplerplus", "Orange Micro Grappler+ Printer Interface")
+DEFINE_DEVICE_TYPE(A2BUS_BUFGRAPPLERPLUS, a2bus_buf_grapplerplus_device, "a2bufgrapplerplus", "Orange Micro Buffered Grappler+ Printer Interface")
+DEFINE_DEVICE_TYPE(A2BUS_BUFGRAPPLERPLUSA, a2bus_buf_grapplerplus_reva_device, "a2bufgrapplerplusa", "Orange Micro Buffered Grappler+ (rev A) Printer Interface")
+
+
+
+//==============================================================
+// Grappler base
+//==============================================================
+
+a2bus_grappler_device_base::a2bus_grappler_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) :
+ device_t(mconfig, type, tag, owner, clock),
+ device_a2bus_card_interface(mconfig, *this),
+ m_printer_conn(*this, "prn"),
+ m_printer_out(*this, "prn_out"),
+ m_rom(*this, "rom"),
+ m_rom_bank(0x0000U),
+ m_busy_in(1U),
+ m_pe_in(1U),
+ m_slct_in(1U)
+{
+}
+
+
+
+//--------------------------------------------------
+// device_a2bus_card_interface implementation
+//--------------------------------------------------
+
+u8 a2bus_grappler_device_base::read_c800(u16 offset)
+{
+ return m_rom[(offset & 0x07ffU) | m_rom_bank];
+}
+
+
+
+//--------------------------------------------------
+// device_t implementation
+//--------------------------------------------------
+
+void a2bus_grappler_device_base::device_add_mconfig(machine_config &config)
+{
+ CENTRONICS(config, m_printer_conn, centronics_devices, "printer");
+ m_printer_conn->busy_handler().set(FUNC(a2bus_grappler_device_base::busy_w));
+ m_printer_conn->perror_handler().set(FUNC(a2bus_grappler_device_base::pe_w));
+ m_printer_conn->select_handler().set(FUNC(a2bus_grappler_device_base::slct_w));
+
+ OUTPUT_LATCH(config, m_printer_out);
+ m_printer_conn->set_output_latch(*m_printer_out);
+}
+
+
+void a2bus_grappler_device_base::device_start()
+{
+ save_item(NAME(m_rom_bank));
+ save_item(NAME(m_busy_in));
+ save_item(NAME(m_pe_in));
+ save_item(NAME(m_slct_in));
+}
+
+
+
+//--------------------------------------------------
+// printer status inputs
+//--------------------------------------------------
+
+void a2bus_grappler_device_base::set_rom_bank(u16 rom_bank)
+{
+ if (m_rom_bank != rom_bank)
+ LOG("Select ROM bank %04X\n", rom_bank);
+ m_rom_bank = rom_bank;
+}
+
+
+
+//--------------------------------------------------
+// printer status inputs
+//--------------------------------------------------
+
+WRITE_LINE_MEMBER(a2bus_grappler_device_base::busy_w)
+{
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_grappler_device_base::set_busy_in), this), state ? 1 : 0);
+}
+
+
+WRITE_LINE_MEMBER(a2bus_grappler_device_base::pe_w)
+{
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_grappler_device_base::set_pe_in), this), state ? 1 : 0);
+}
+
+
+WRITE_LINE_MEMBER(a2bus_grappler_device_base::slct_w)
+{
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_grappler_device_base::set_slct_in), this), state ? 1 : 0);
+}
+
+
+
+//--------------------------------------------------
+// synchronised printer status inputs
+//--------------------------------------------------
+
+void a2bus_grappler_device_base::set_busy_in(void *ptr, s32 param)
+{
+ if (u32(param) != m_busy_in)
+ {
+ LOG("BUSY=%d\n", param);
+ m_busy_in = u8(u32(param));
+ }
+}
+
+
+void a2bus_grappler_device_base::set_pe_in(void *ptr, s32 param)
+{
+ if (u32(param) != m_pe_in)
+ {
+ LOG("PAPER EMPTY=%d\n", param);
+ m_pe_in = u8(u32(param));
+ }
+}
+
+
+void a2bus_grappler_device_base::set_slct_in(void *ptr, s32 param)
+{
+ if (u32(param) != m_slct_in)
+ {
+ LOG("SELECT=%d\n", param);
+ m_slct_in = u8(u32(param));
+ }
+}
+
+
+
+//==============================================================
+// Grappler implementation
+//==============================================================
+
+a2bus_grappler_device::a2bus_grappler_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) :
+ a2bus_grappler_device_base(mconfig, A2BUS_GRAPPLER, tag, owner, clock),
+ m_strobe(1U),
+ m_ack_latch(1U),
+ m_ack_in(1U)
+{
+}
+
+
+
+//--------------------------------------------------
+// device_a2bus_card_interface implementation
+//--------------------------------------------------
+
+u8 a2bus_grappler_device::read_c0nx(u8 offset)
+{
+ LOG("Read C0n%01X\n", offset);
+
+ if (BIT(offset, 1)) // A1 - assert strobe
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_grappler_device::set_strobe), this), 0);
+ else if (BIT(offset, 2)) // A2 - release strobe
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_grappler_device::set_strobe), this), 1);
+
+ if (BIT(offset, 0)) // A0 - printer status
+ {
+ return
+ 0xf0U | // TODO: actually open bus
+ (busy_in() << 3) |
+ (pe_in() << 2) |
+ (slct_in() << 1) |
+ m_ack_latch;
+ }
+ else
+ {
+ return 0xffU; // TODO: actually open bus
+ }
+}
+
+
+void a2bus_grappler_device::write_c0nx(u8 offset, u8 data)
+{
+ LOG("Write C0n%01X=%02X\n", offset, data);
+
+ if (BIT(offset, 0)) // A0 - write data
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_grappler_device::set_data), this), int(unsigned(data)));
+
+ if (BIT(offset, 1)) // A1 - assert strobe
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_grappler_device::set_strobe), this), 0);
+ else if (BIT(offset, 2)) // A2 - release strobe
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_grappler_device::set_strobe), this), 1);
+}
+
+
+u8 a2bus_grappler_device::read_cnxx(u8 offset)
+{
+ return m_rom[offset | (slotno() << 8)];
+}
+
+
+void a2bus_grappler_device::write_cnxx(u8 offset, u8 data)
+{
+ LOG("Write Cn%02X=%02X (bus conflict)\n", offset, data);
+}
+
+
+
+//--------------------------------------------------
+// device_t implementation
+//--------------------------------------------------
+
+tiny_rom_entry const *a2bus_grappler_device::device_rom_region() const
+{
+ return ROM_NAME(grappler);
+}
+
+
+void a2bus_grappler_device::device_add_mconfig(machine_config &config)
+{
+ a2bus_grappler_device_base::device_add_mconfig(config);
+
+ m_printer_conn->ack_handler().set(FUNC(a2bus_grappler_device::ack_w));
+}
+
+
+void a2bus_grappler_device::device_start()
+{
+ a2bus_grappler_device_base::device_start();
+
+ save_item(NAME(m_strobe));
+ save_item(NAME(m_ack_latch));
+ save_item(NAME(m_ack_in));
+}
+
+
+void a2bus_grappler_device::device_reset()
+{
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_grappler_device::set_strobe), this), 1);
+}
+
+
+
+//--------------------------------------------------
+// printer status inputs
+//--------------------------------------------------
+
+WRITE_LINE_MEMBER(a2bus_grappler_device::ack_w)
+{
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_grappler_device::set_ack_in), this), state ? 1 : 0);
+}
+
+
+
+//--------------------------------------------------
+// synchronised signals
+//--------------------------------------------------
+
+void a2bus_grappler_device::set_data(void *ptr, s32 param)
+{
+ LOG("Output data %02X\n", u8(u32(param)));
+ m_printer_out->write(u8(u32(param)));
+}
+
+
+void a2bus_grappler_device::set_strobe(void *ptr, s32 param)
+{
+ LOG("Output /STROBE=%d\n", param);
+ m_printer_conn->write_strobe(param);
+ if (m_strobe && !param)
+ {
+ if (m_ack_in)
+ {
+ if (!m_ack_latch)
+ LOG("Clearing acknowledge latch\n");
+ else
+ LOG("Previous data not acknowledged\n");
+ m_ack_latch = 1U;
+ }
+ else
+ {
+ LOG("/ACK asserted, not clearing acknowledge latch\n");
+ }
+ }
+ m_strobe = u8(u32(param));
+}
+
+
+void a2bus_grappler_device::set_ack_in(void *ptr, s32 param)
+{
+ if (u32(param) != m_ack_in)
+ {
+ LOG("/ACK=%d\n", param);
+ m_ack_in = u8(u32(param));
+ if (!param)
+ {
+ if (m_ack_latch)
+ LOG("Set acknowledge latch\n");
+ else
+ LOG("No data written since previous acknowledge\n");
+ m_ack_latch = 0U;
+ }
+ else if (!m_strobe)
+ {
+ LOG("Clearing acknowledge latch\n");
+ m_ack_latch = 1U;
+ }
+ }
+}
+
+
+
+//==============================================================
+// Grappler+ base
+//==============================================================
+
+a2bus_grapplerplus_device_base::a2bus_grapplerplus_device_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) :
+ a2bus_grappler_device_base(mconfig, type, tag, owner, clock),
+ m_s1(*this, "S1"),
+ m_ack_latch(1U),
+ m_ack_in(1U)
+{
+}
+
+
+
+//--------------------------------------------------
+// device_t implementation
+//--------------------------------------------------
+
+ioport_constructor a2bus_grapplerplus_device_base::device_input_ports() const
+{
+ return INPUT_PORTS_NAME(grapplerplus);
+}
+
+
+void a2bus_grapplerplus_device_base::device_start()
+{
+ a2bus_grappler_device_base::device_start();
+
+ save_item(NAME(m_ack_latch));
+ save_item(NAME(m_ack_in));
+}
+
+
+void a2bus_grapplerplus_device_base::device_reset()
+{
+ m_ack_latch = 1U;
+}
+
+
+
+//--------------------------------------------------
+// device_a2bus_card_interface implementation
+//--------------------------------------------------
+
+void a2bus_grapplerplus_device_base::write_c0nx(u8 offset, u8 data)
+{
+ LOG("Write C0n%01X=%02X\n", offset, data);
+
+ if (!(offset & 0x03U)) // !A0 && !A1 - write data
+ {
+ // latch output data
+ LOG("Latch data %02X\n", data);
+ data_latched(data);
+
+ // clearing the ACK latch will acknowledge an interrupt
+ if (m_ack_in)
+ {
+ if (m_ack_latch)
+ LOG("Clearing acknowledge latch\n");
+ else
+ LOG("Previous data not acknowledged\n");
+ m_ack_latch = 0U;
+ ack_latch_cleared();
+ }
+ else
+ {
+ LOG("/ACK asserted, not clearing acknowledge latch\n");
+ }
+ }
+
+ if (BIT(offset, 0)) // A0 - select high ROM bank
+ set_rom_bank(0x0800U);
+}
+
+
+u8 a2bus_grapplerplus_device_base::read_cnxx(u8 offset)
+{
+ if (!machine().side_effects_disabled())
+ set_rom_bank(0x0000U);
+ return m_rom[(!m_ack_latch && BIT(offset, 7)) ? (offset & 0xbfU) : offset];
+}
+
+
+void a2bus_grapplerplus_device_base::write_cnxx(u8 offset, u8 data)
+{
+ LOG("Write Cn%02X=%02X (bus conflict)\n", offset, data);
+
+ set_rom_bank(0x0000U);
+}
+
+
+
+//--------------------------------------------------
+// ACK latch set input
+//--------------------------------------------------
+
+WRITE_LINE_MEMBER(a2bus_grapplerplus_device_base::ack_w)
+{
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_grapplerplus_device_base::set_ack_in), this), state ? 1 : 0);
+}
+
+
+
+//--------------------------------------------------
+// synchronised printer status inputs
+//--------------------------------------------------
+
+void a2bus_grapplerplus_device_base::set_ack_in(void *ptr, s32 param)
+{
+ if (u32(param) != m_ack_in)
+ {
+ LOG("/ACK=%d\n", param);
+ m_ack_in = u8(u32(param));
+ if (!param)
+ {
+ if (!m_ack_latch)
+ LOG("Set acknowledge latch\n");
+ else
+ LOG("No data written since previous acknowledge\n");
+ m_ack_latch = 1U;
+ ack_latch_set();
+ }
+ }
+}
+
+
+
+//==============================================================
+// Grappler+ implementation
+//==============================================================
+
+a2bus_grapplerplus_device::a2bus_grapplerplus_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) :
+ a2bus_grapplerplus_device_base(mconfig, A2BUS_GRAPPLERPLUS, tag, owner, clock),
+ m_strobe_timer(nullptr),
+ m_data_latch(0xffU),
+ m_irq_disable(1U),
+ m_irq(0x00U),
+ m_next_strobe(1U)
+{
+}
+
+
+
+//--------------------------------------------------
+// DIP switch handlers
+//--------------------------------------------------
+
+INPUT_CHANGED_MEMBER(a2bus_grapplerplus_device::sw_msb)
+{
+ if (BIT(m_data_latch, 7))
+ m_printer_out->write(m_data_latch & (BIT(m_s1->read(), 3) ? 0xffU : 0x7fU));
+}
+
+
+
+//--------------------------------------------------
+// device_a2bus_card_interface implementation
+//--------------------------------------------------
+
+u8 a2bus_grapplerplus_device::read_c0nx(u8 offset)
+{
+ return
+ m_irq |
+ ((m_s1->read() & 0x07U) << 4) |
+ (busy_in() << 3) |
+ (pe_in() << 2) |
+ (slct_in() << 1) |
+ ack_latch();
+}
+
+
+void a2bus_grapplerplus_device::write_c0nx(u8 offset, u8 data)
+{
+ a2bus_grapplerplus_device_base::write_c0nx(offset, data);
+
+ if (BIT(offset, 1)) // A1 - disable interrupt
+ {
+ if (!m_irq_disable)
+ LOG("Disable interrupt request\n");
+ else
+ LOG("Interrupt request already disabled\n");
+ m_irq_disable = 1U;
+ if (m_irq)
+ {
+ assert(ack_latch());
+ LOG("Releasing slot IRQ\n");
+ m_irq = 0x00U;
+ lower_slot_irq();
+ }
+ }
+ else if (BIT(offset, 2)) // A2 - enable interrupt
+ {
+ if (m_irq_disable)
+ LOG("Enable interrupt request\n");
+ else
+ LOG("Interrupt request already enabled\n");
+ m_irq_disable = 0U;
+ if (ack_latch() && !m_irq)
+ {
+ LOG("Asserting slot IRQ\n");
+ m_irq = 0x80U;
+ raise_slot_irq();
+ }
+ }
+}
+
+
+
+//--------------------------------------------------
+// device_t implementation
+//--------------------------------------------------
+
+tiny_rom_entry const *a2bus_grapplerplus_device::device_rom_region() const
+{
+ return ROM_NAME(grapplerplus);
+}
+
+
+void a2bus_grapplerplus_device::device_add_mconfig(machine_config &config)
+{
+ a2bus_grapplerplus_device_base::device_add_mconfig(config);
+
+ m_printer_conn->ack_handler().set(FUNC(a2bus_grapplerplus_device::ack_w));
+}
+
+
+void a2bus_grapplerplus_device::device_start()
+{
+ a2bus_grapplerplus_device_base::device_start();
+
+ m_strobe_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(a2bus_grapplerplus_device::update_strobe), this));
+
+ m_next_strobe = 1U;
+
+ save_item(NAME(m_data_latch));
+ save_item(NAME(m_irq_disable));
+ save_item(NAME(m_irq));
+ save_item(NAME(m_next_strobe));
+
+ m_strobe_timer->adjust(attotime::from_ticks(7, clock()));
+}
+
+
+void a2bus_grapplerplus_device::device_reset()
+{
+ a2bus_grapplerplus_device_base::device_reset();
+
+ m_irq_disable = 1U;
+ if (m_irq)
+ {
+ m_irq = 0x00U;
+ lower_slot_irq();
+ }
+}
+
+
+
+//--------------------------------------------------
+// a2bus_grapplerplus_device_base implementation
+//--------------------------------------------------
+
+void a2bus_grapplerplus_device::data_latched(u8 data)
+{
+ // remember MSB can be forced low by DIP switch
+ m_data_latch = data;
+ m_printer_out->write(m_data_latch & (BIT(m_s1->read(), 3) ? 0xffU : 0x7fU));
+
+ // generate strobe pulse after one clock cycle
+ m_next_strobe = 0U;
+ if (!m_strobe_timer->enabled())
+ {
+ LOG("Start strobe timer\n");
+ m_strobe_timer->adjust(attotime::from_ticks(7, clock()));
+ }
+}
+
+
+void a2bus_grapplerplus_device::ack_latch_set()
+{
+ if (!m_irq_disable && !m_irq)
+ {
+ LOG("Asserting slot IRQ\n");
+ m_irq = 0x80U;
+ raise_slot_irq();
+ }
+}
+
+
+void a2bus_grapplerplus_device::ack_latch_cleared()
+{
+ if (m_irq)
+ {
+ assert(!m_irq_disable);
+ LOG("Releasing slot IRQ\n");
+ m_irq = 0x00U;
+ lower_slot_irq();
+ }
+}
+
+
+
+//--------------------------------------------------
+// timer handlers
+//--------------------------------------------------
+
+TIMER_CALLBACK_MEMBER(a2bus_grapplerplus_device::update_strobe)
+{
+ LOG("Output /STROBE=%u\n", m_next_strobe);
+ m_printer_conn->write_strobe(m_next_strobe);
+ if (!m_next_strobe)
+ {
+ LOG("Start strobe timer\n");
+ m_next_strobe = 1U;
+ m_strobe_timer->adjust(attotime::from_ticks(7, clock()));
+ }
+}
+
+
+
+//==============================================================
+// Buffered Grappler+ implementation
+//==============================================================
+
+a2bus_buf_grapplerplus_device::a2bus_buf_grapplerplus_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) :
+ a2bus_buf_grapplerplus_device(mconfig, A2BUS_BUFGRAPPLERPLUS, tag, owner, clock)
+{
+}
+
+
+a2bus_buf_grapplerplus_device::a2bus_buf_grapplerplus_device(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) :
+ a2bus_grapplerplus_device_base(mconfig, type, tag, owner, clock),
+ m_mcu(*this, "mcu"),
+ m_ram(),
+ m_ram_row(0xff00),
+ m_ram_mask(0x00U),
+ m_mcu_p2(0xffU),
+ m_data_latch(0xffU),
+ m_ibusy(1U),
+ m_buf_ack_latch(1U),
+ m_buf_ack_in(1U)
+{
+}
+
+
+
+//--------------------------------------------------
+// device_a2bus_card_interface implementation
+//--------------------------------------------------
+
+u8 a2bus_buf_grapplerplus_device::read_c0nx(u8 offset)
+{
+ return
+ ((m_s1->read() & 0x0fU) << 4) |
+ (m_ibusy << 3) |
+ (pe_in() << 2) |
+ (slct_in() << 1) |
+ ack_latch();
+}
+
+
+
+//--------------------------------------------------
+// device_t implementation
+//--------------------------------------------------
+
+tiny_rom_entry const *a2bus_buf_grapplerplus_device::device_rom_region() const
+{
+ return ROM_NAME(bufgrapplerplus);
+}
+
+
+void a2bus_buf_grapplerplus_device::device_add_mconfig(machine_config &config)
+{
+ // 1982 schematics show MCU driven by 7M clock
+ device_add_mconfig(config, DERIVED_CLOCK(1, 1));
+}
+
+
+ioport_constructor a2bus_buf_grapplerplus_device::device_input_ports() const
+{
+ return INPUT_PORTS_NAME(bufgrapplerplus);
+}
+
+
+void a2bus_buf_grapplerplus_device::device_start()
+{
+ a2bus_grapplerplus_device_base::device_start();
+
+ m_ram = std::make_unique<u8 []>(0x10000);
+
+ save_pointer(NAME(m_ram), 0x10000);
+ save_item(NAME(m_ram_row));
+ save_item(NAME(m_ram_mask));
+ save_item(NAME(m_mcu_p2));
+ save_item(NAME(m_data_latch));
+ save_item(NAME(m_ibusy));
+ save_item(NAME(m_buf_ack_latch));
+ save_item(NAME(m_buf_ack_in));
+}
+
+
+void a2bus_buf_grapplerplus_device::device_reset()
+{
+ // The MCU is not reset when /RST is asserted. Instead, /RST is
+ // connected to P27. Pressing the reset key(s) momentarily will not
+ // clear the print buffer - the host Apple II can be reset without
+ // interrupting a long print job. Holding the reset key(s) for two
+ // seconds clears the buffer. Holding the reset key(s) on initial
+ // boot enters the RAM test (results are printed). MAME doesn't
+ // currently cater for devices that don't reset their children on
+ // reset, and Apple II slots don't currently expose the /RST signal
+ // directly.
+
+ a2bus_grapplerplus_device_base::device_reset();
+
+ // should only do this on initial reset, but we get away with it here because the MCU is automatically reset even if it shouldn't be
+ m_ram_mask = ioport("CNF")->read();
+}
+
+
+
+//--------------------------------------------------
+// helpers
+//--------------------------------------------------
+
+template <typename T>
+void a2bus_buf_grapplerplus_device::device_add_mconfig(machine_config &config, T &&mcu_clock)
+{
+ a2bus_grapplerplus_device_base::device_add_mconfig(config);
+
+ m_printer_conn->ack_handler().set(FUNC(a2bus_buf_grapplerplus_device::buf_ack_w));
+
+ // P22 is tied high, pulling it low is used for some factory test mode
+ I8048(config, m_mcu, std::forward<T>(mcu_clock));
+ m_mcu->set_addrmap(AS_IO, &a2bus_buf_grapplerplus_device::mcu_io);
+ m_mcu->p2_out_cb().set(FUNC(a2bus_buf_grapplerplus_device::mcu_p2_w));
+ m_mcu->t0_in_cb().set([this] () { return busy_in(); });
+ m_mcu->t1_in_cb().set([this] () { return m_buf_ack_latch; });
+ m_mcu->bus_in_cb().set(FUNC(a2bus_buf_grapplerplus_device::mcu_bus_r));
+ m_mcu->bus_out_cb().set(FUNC(a2bus_buf_grapplerplus_device::mcu_bus_w));
+}
+
+
+
+//--------------------------------------------------
+// a2bus_grapplerplus_device_base implementation
+//--------------------------------------------------
+
+void a2bus_buf_grapplerplus_device::data_latched(u8 data)
+{
+ // IBUSY is exposed on C0nX
+ if (!m_ibusy)
+ LOG("Setting IBUSY\n");
+ else
+ LOG("IBUSY already set\n");
+ m_ibusy = 1U;
+
+ // these signals cross executable device domains
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_buf_grapplerplus_device::set_buf_data), this), int(unsigned(data)));
+ m_mcu->set_input_line(MCS48_INPUT_IRQ, ASSERT_LINE);
+}
+
+
+
+//--------------------------------------------------
+// printer status inputs
+//--------------------------------------------------
+
+DECLARE_WRITE_LINE_MEMBER(a2bus_buf_grapplerplus_device::buf_ack_w)
+{
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_buf_grapplerplus_device::set_buf_ack_in), this), state ? 1 : 0);
+}
+
+
+
+//--------------------------------------------------
+// MCU I/O handlers
+//--------------------------------------------------
+
+void a2bus_buf_grapplerplus_device::mcu_io(address_map &map)
+{
+ map(0x00, 0xff).nopr(); // read to put the BUS lines in high-impedance state before a real read using INS
+}
+
+
+void a2bus_buf_grapplerplus_device::mcu_p2_w(u8 data)
+{
+ // check for changed bits
+ u8 const diff(data ^ m_mcu_p2);
+ m_mcu_p2 = data;
+
+ // P20 enables /CAS on /RD or /WR
+ if (BIT(diff, 0))
+ LOG("RAM EN=%u\n", BIT(data, 0));
+
+ // row address strobe
+ if (BIT(diff, 1))
+ {
+ if (!BIT(data, 1))
+ {
+ LOG("Row address %02X\n", m_mcu->p1_r());
+ m_ram_row = u16(m_mcu->p1_r()) << 8;
+ }
+ else
+ {
+ LOG("Released /RAS\n");
+ }
+ }
+
+ // P23 is the /IOEN signal
+ if (BIT(diff, 3))
+ LOG("/IOEN=%u\n", BIT(data, 3));
+
+ // P24 allows fast DRAM refresh using ALE for /RAS
+ if (BIT(diff, 4))
+ LOG(BIT(data, 4) ? "Start DRAM refresh\n" : "End DRAM refresh\n");
+
+ // P25 is the strobe output and clears the acknowledge latch
+ if (BIT(diff, 5))
+ {
+ if (BIT(data, 5))
+ {
+ if (m_buf_ack_in)
+ {
+ if (m_buf_ack_latch)
+ LOG("T1 already set\n");
+ else
+ LOG("Setting T1\n");
+ m_buf_ack_latch = 1U;
+ }
+ else
+ {
+ LOG("/ACK asserted, not setting T1\n");
+ }
+ }
+ LOG("Output /STROBE=%u\n", BIT(data, 5));
+ m_printer_conn->write_strobe(BIT(data, 5));
+ }
+}
+
+
+u8 a2bus_buf_grapplerplus_device::mcu_bus_r()
+{
+ u8 result(0xffU);
+
+ // RAM EN enables RAM
+ if (BIT(m_mcu_p2, 0))
+ {
+ if (!BIT(m_mcu_p2, 1))
+ {
+ u16 const addr(m_ram_row | u16(m_mcu->p1_r()));
+ LOG("Read RAM @%04X=%02X\n", addr, m_ram[addr]);
+ result &= m_ram[addr] | m_ram_mask;
+ }
+ else
+ {
+ LOG("/RD asserted /CAS while /RAS not asserted\n");
+ }
+ }
+
+ // /IOEN enables the data latch, pulses /IACK and clears IBUSY on /RD
+ if (!BIT(m_mcu_p2, 3))
+ {
+ LOG("Read data latch %02X\n", m_data_latch);
+ result &= m_data_latch;
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(a2bus_buf_grapplerplus_device::clear_ibusy), this));
+ m_mcu->set_input_line(MCS48_INPUT_IRQ, CLEAR_LINE);
+ ack_w(0);
+ ack_w(1);
+ }
+
+ return result;
+}
+
+
+void a2bus_buf_grapplerplus_device::mcu_bus_w(u8 data)
+{
+ // RAM EN enables RAM
+ if (BIT(m_mcu_p2, 0))
+ {
+ if (!BIT(m_mcu_p2, 1))
+ {
+ u16 const addr(m_ram_row | u16(m_mcu->p1_r()));
+ LOG("Wrote RAM @%04X=%02X\n", addr, data);
+ m_ram[addr] = data | m_ram_mask;
+ }
+ else
+ {
+ LOG("/WR asserted /CAS while /RAS not asserted\n");
+ }
+ }
+
+ // /IOEN enables output latch
+ if (!BIT(m_mcu_p2, 3))
+ {
+ LOG("Output data %02X\n", data);
+ m_printer_out->write(data);
+ }
+}
+
+
+
+//--------------------------------------------------
+// synchronised signals
+//--------------------------------------------------
+
+void a2bus_buf_grapplerplus_device::set_buf_data(void *ptr, s32 param)
+{
+ m_data_latch = u8(u32(param));
+}
+
+
+void a2bus_buf_grapplerplus_device::set_buf_ack_in(void *ptr, s32 param)
+{
+ if (u32(param) != m_buf_ack_in)
+ {
+ LOG("/ACK=%d\n", param);
+ m_buf_ack_in = u8(u32(param));
+ if (!param)
+ {
+ if (m_buf_ack_latch)
+ LOG("Clearing T1\n");
+ else
+ LOG("No data output since previous acknowledge\n");
+ m_buf_ack_latch = 0U;
+ }
+ }
+}
+
+
+void a2bus_buf_grapplerplus_device::clear_ibusy(void *ptr, s32 param)
+{
+ if (m_ibusy)
+ {
+ LOG("Clearing IBUSY\n");
+ m_ibusy = 0U;
+ }
+ else
+ {
+ LOG("IBUSY already clear\n");
+ }
+}
+
+
+
+//==============================================================
+// Buffered Grappler+ rev A implementation
+//==============================================================
+
+a2bus_buf_grapplerplus_reva_device::a2bus_buf_grapplerplus_reva_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) :
+ a2bus_buf_grapplerplus_device(mconfig, A2BUS_BUFGRAPPLERPLUSA, tag, owner, clock)
+{
+}
+
+
+
+//--------------------------------------------------
+// device_t implementation
+//--------------------------------------------------
+
+void a2bus_buf_grapplerplus_reva_device::device_add_mconfig(machine_config &config)
+{
+ // boards with 6 MHz clock crystal for MCU have been seen with both UVEPROM and mask ROM parts
+ // ORANGE MICRO INC., 1983
+ // ASSY NO, 72 BGP 00001 REV A
+ // PART NO. 95PCB00003
+ a2bus_buf_grapplerplus_device::device_add_mconfig(config, 6_MHz_XTAL);
+}