// license:BSD-3-Clause // copyright-holders:AJR /************************************************************************** MDT 60 was designed by Zenith for use with Morrow's Micro-Decision computers as a low-cost terminal with limited features. It appears to be a stripped-down version of some previously designed terminal, with the auxiliary port omitted from the Morrow version even though the schematics published in its service manual include it. These schematics also include the same keyboard used by the Z-29, though the actual keyboard found with one unit was different. **************************************************************************/ #include "emu.h" #include "bus/rs232/rs232.h" #include "bus/z29_kbd/keyboard.h" #include "cpu/m6502/m6502.h" #include "machine/i8251.h" #include "video/mc6845.h" #include "screen.h" class mdt60_state : public driver_device { public: mdt60_state(const machine_config &mconfig, device_type type, const char *tag) : driver_device(mconfig, type, tag) , m_maincpu(*this, "maincpu") , m_keyboard(*this, "keyboard") , m_crtc(*this, "crtc") , m_uart(*this, "uart%u", 0U) , m_charram(*this, "charram") , m_charrom(*this, "charrom") , m_attrram(*this, "attrram") , m_dip0(*this, "DIP0") , m_baud_timer(nullptr) , m_keyin(false) , m_output_reg(0x3f) , m_timer_output(false) { } void mdt60(machine_config &mconfig); protected: virtual void machine_start() override; virtual void machine_reset() override; private: MC6845_UPDATE_ROW(update_row); MC6845_ON_UPDATE_ADDR_CHANGED(update_cb); TIMER_CALLBACK_MEMBER(baud_timer); DECLARE_WRITE_LINE_MEMBER(keyin_w); u8 dip0_r(offs_t offset); void reg_w(u8 data); void mem_map(address_map &map); required_device m_maincpu; required_device m_keyboard; required_device m_crtc; optional_device_array m_uart; required_shared_ptr m_charram; required_region_ptr m_charrom; required_shared_ptr m_attrram; required_ioport m_dip0; emu_timer *m_baud_timer; bool m_keyin; u8 m_output_reg; bool m_timer_output; }; void mdt60_state::machine_start() { m_baud_timer = timer_alloc(FUNC(mdt60_state::baud_timer), this); m_baud_timer->adjust(attotime::zero); save_item(NAME(m_keyin)); save_item(NAME(m_output_reg)); save_item(NAME(m_timer_output)); } void mdt60_state::machine_reset() { reg_w(0); } TIMER_CALLBACK_MEMBER(mdt60_state::baud_timer) { m_timer_output = !m_timer_output; // LS352 multiplexer has inverting outputs m_uart[0]->write_rxc(!m_timer_output); m_uart[0]->write_txc(!m_timer_output); constexpr auto time_base = 16.5888_MHz_XTAL / 9; if ((m_output_reg & 0x06) == 0x00) m_baud_timer->adjust(attotime::from_ticks(m_timer_output ? 2 : 4, time_base)); else if ((m_output_reg & 0x06) == 0x06) m_baud_timer->adjust(attotime::from_ticks(48, time_base)); else m_baud_timer->adjust(attotime::from_ticks(3 * (m_output_reg & 0x06), time_base)); } MC6845_UPDATE_ROW(mdt60_state::update_row) { u32 *pix = &bitmap.pix(y); for (unsigned x = 0; x < x_count; x++) { u8 cdata = m_charram[(ma + x) & 0x7ff]; u8 adata = m_attrram[(ma + x) & 0x3ff] >> (BIT(ma + x, 10) ? 4 : 0); u16 dots = m_charrom[cdata << 4 | ra] << 1 | BIT(adata, 3); if (x == cursor_x) dots = ~dots; if (BIT(adata, 2)) dots = ~dots; if (BIT(adata, 0) && (ra & 0xb) == 0xa) dots = ~dots; rgb_t fg = BIT(adata, 1) ? rgb_t::white() : rgb_t(0xc0, 0xc0, 0xc0); rgb_t bg = rgb_t::black(); if (BIT(m_output_reg, 5)) std::swap(fg, bg); for (int n = 8; n >= 0; n--) *pix++ = BIT(dots, n) ? fg : bg; } } MC6845_ON_UPDATE_ADDR_CHANGED(mdt60_state::update_cb) { } WRITE_LINE_MEMBER(mdt60_state::keyin_w) { m_keyin = state; m_maincpu->set_input_line(m6502_device::IRQ_LINE, state ? ASSERT_LINE : CLEAR_LINE); } u8 mdt60_state::dip0_r(offs_t offset) { u8 buffer = 0xff; if (!BIT(offset, 0)) buffer &= m_dip0->read() >> 1 | 0x80; if (!BIT(offset, 1)) buffer &= m_dip0->read() | 0xfe; if (!m_keyin) buffer &= 0x7f; return buffer; } void mdt60_state::reg_w(u8 data) { if (BIT(data, 0) != BIT(m_output_reg, 0)) m_keyboard->keyout_w(BIT(data, 0)); // Bits 1–2 & 3–4 select 1 of 4 baud rates for each UART; bit 5 selects inverse video m_output_reg = data & 0x3f; } void mdt60_state::mem_map(address_map &map) { map(0x0000, 0x03ff).mirror(0x1800).ram(); map(0x0400, 0x07ff).mirror(0x1800).ram().share("attrram"); map(0x2000, 0x27ff).mirror(0x1800).ram().share("charram"); map(0x4000, 0x4000).mirror(0x1ffe).rw(m_crtc, FUNC(r6545_1_device::status_r), FUNC(r6545_1_device::address_w)); map(0x4001, 0x4001).mirror(0x1ffe).rw(m_crtc, FUNC(r6545_1_device::register_r), FUNC(r6545_1_device::register_w)); map(0x6000, 0x6001).mirror(0x1ffe).rw(m_uart[0], FUNC(i8251_device::read), FUNC(i8251_device::write)); //map(0x8000, 0x8001).mirror(0x1ffe).rw(m_uart[1], FUNC(i8251_device::read), FUNC(i8251_device::write)); map(0xa000, 0xa003).mirror(0x1ffc).r(FUNC(mdt60_state::dip0_r)); map(0xa000, 0xa000).mirror(0x1fff).w(FUNC(mdt60_state::reg_w)); //map(0xc000, 0xc000).mirror(0x1fff).portr("DIP1"); map(0xe000, 0xefff).mirror(0x1000).rom().region("coderom", 0); } static INPUT_PORTS_START(mdt60) PORT_START("DIP0") PORT_DIPNAME(0x03, 0x01, "Baud Rate") PORT_DIPLOCATION("SW1:1,2") PORT_DIPSETTING(0x03, "300") PORT_DIPSETTING(0x02, "1200") PORT_DIPSETTING(0x01, "9600") PORT_DIPSETTING(0x00, "19200") PORT_DIPNAME(0x0c, 0x00, "Parity") PORT_DIPLOCATION("SW1:3,4") PORT_DIPSETTING(0x04, "Odd") PORT_DIPSETTING(0x0c, "Even") PORT_DIPSETTING(0x00, "Mark") PORT_DIPSETTING(0x08, "Space") PORT_DIPNAME(0x70, 0x00, "Character Set") PORT_DIPLOCATION("SW1:5,6,7") PORT_DIPSETTING(0x00, "U.S.") PORT_DIPSETTING(0x10, "U.K.") PORT_DIPSETTING(0x20, "French") PORT_DIPSETTING(0x30, "German") PORT_DIPSETTING(0x40, "Spanish") PORT_DIPSETTING(0x50, "Danish/Norwegian") PORT_DIPSETTING(0x60, "Swedish/Finnish") PORT_DIPSETTING(0x70, "Italian") PORT_DIPNAME(0x80, 0x80, "Function Key Sequence Type") PORT_DIPLOCATION("SW1:8") PORT_DIPSETTING(0x80, "Morrow (FS)") PORT_DIPSETTING(0x00, "TeleVideo (SOH...CR)") PORT_START("DIP1") PORT_BIT(0xff, 0xff, IPT_UNUSED) // SW2 is unpopulated (as is LS244 at U20) INPUT_PORTS_END // XTAL frequency is specified as 16.589 MHz on actual parts as well as in MDT 60 schematics. // This has been assumed to be a lower-precision specification of the common 16.5888 MHz value. void mdt60_state::mdt60(machine_config &config) { M6512(config, m_maincpu, 16.5888_MHz_XTAL / 9); // R6512AP m_maincpu->set_addrmap(AS_PROGRAM, &mdt60_state::mem_map); I8251(config, m_uart[0], 16.5888_MHz_XTAL / 9); // TMP8251AP (U19) m_uart[0]->rxrdy_handler().set_inputline(m_maincpu, m6512_device::NMI_LINE); m_uart[0]->txd_handler().set("comm", FUNC(rs232_port_device::write_txd)); m_uart[0]->dtr_handler().set("comm", FUNC(rs232_port_device::write_dtr)); m_uart[0]->rts_handler().set("comm", FUNC(rs232_port_device::write_rts)); // UART 1 (at U28) is unpopulated and not used by code Z29_KEYBOARD(config, m_keyboard, z29_keyboards, "md"); m_keyboard->keyin_callback().set(FUNC(mdt60_state::keyin_w)).invert(); // Reset output (pin 2) is not connected screen_device &screen(SCREEN(config, "screen", SCREEN_TYPE_RASTER)); screen.set_color(rgb_t::amber()); screen.set_raw(16.5888_MHz_XTAL, 918, 0, 720, 301, 0, 288); screen.set_screen_update(m_crtc, FUNC(r6545_1_device::screen_update)); R6545_1(config, m_crtc, 16.5888_MHz_XTAL / 9); // R6545-1AP m_crtc->set_screen("screen"); m_crtc->set_show_border_area(false); m_crtc->set_char_width(9); m_crtc->set_update_row_callback(FUNC(mdt60_state::update_row)); m_crtc->set_on_update_addr_change_callback(FUNC(mdt60_state::update_cb)); // LPEN is tied to GND (code uses this to determine CRTC type) rs232_port_device &comm(RS232_PORT(config, "comm", default_rs232_devices, "loopback")); // Service Manual suggests shorting pins with a bent paper clip if no DB-25 loopback connector is available ;-) comm.rxd_handler().set(m_uart[0], FUNC(i8251_device::write_rxd)); comm.dsr_handler().set(m_uart[0], FUNC(i8251_device::write_dsr)); comm.cts_handler().set(m_uart[0], FUNC(i8251_device::write_cts)); } ROM_START(mdt60) ROM_REGION(0x1000, "coderom", 0) // "ROM REV. 1.7 -- Copyright (C) 1983 by Morrow Designs, Inc." ROM_LOAD("vb-rom_1.7_bcfd.u9", 0x0000, 0x1000, CRC(3902f7b3) SHA1(ee0ce7f68efe20efe1ab8a44888e276f08fe2d07)) ROM_REGION(0x1000, "charrom", 0) ROM_LOAD("char_gen_b207.u6", 0x0000, 0x1000, CRC(b19432da) SHA1(fa73641c08b778f19a17676a7f4074f69b7b55dd)) ROM_END SYST(1983, mdt60, 0, 0, mdt60, mdt60, mdt60_state, empty_init, "Morrow Designs", "MDT 60 Video Display Terminal", MACHINE_SUPPORTS_SAVE)