summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Patrick Mackinlay <pmackinlay@hotmail.com>2020-10-10 13:28:41 +0700
committer Patrick Mackinlay <pmackinlay@hotmail.com>2020-10-10 13:28:41 +0700
commite67a06058f57fd253c6e8c2a56b66d119f1cdd00 (patch)
tree21c9a4daf2c3a34621786fb6c30626695b057a09
parent29fc166c54cd63414b9f9e8c6be48ef6580a1f9a (diff)
ns32202: new device (wip)
-rw-r--r--scripts/src/machine.lua11
-rw-r--r--scripts/target/mame/mess.lua1
-rw-r--r--src/devices/machine/ns32202.cpp698
-rw-r--r--src/devices/machine/ns32202.h137
4 files changed, 847 insertions, 0 deletions
diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua
index 185b1a8164b..ac96f03e4cc 100644
--- a/scripts/src/machine.lua
+++ b/scripts/src/machine.lua
@@ -4568,3 +4568,14 @@ if (MACHINES["NS32081"]~=null) then
MAME_DIR .. "src/devices/machine/ns32081.h",
}
end
+
+---------------------------------------------------
+--
+--@src/devices/machine/ns32202.h,MACHINES["NS32202"] = true
+---------------------------------------------------
+if (MACHINES["NS32202"]~=null) then
+ files {
+ MAME_DIR .. "src/devices/machine/ns32202.cpp",
+ MAME_DIR .. "src/devices/machine/ns32202.h",
+ }
+end
diff --git a/scripts/target/mame/mess.lua b/scripts/target/mame/mess.lua
index 82f6bfc26ea..1401795fd94 100644
--- a/scripts/target/mame/mess.lua
+++ b/scripts/target/mame/mess.lua
@@ -781,6 +781,7 @@ MACHINES["WTL3132"] = true
MACHINES["CXD1185"] = true
MACHINES["BL_HANDHELDS_MENUCONTROL"] = true
MACHINES["NS32081"] = true
+MACHINES["NS32202"] = true
--------------------------------------------------
-- specify available bus cores
diff --git a/src/devices/machine/ns32202.cpp b/src/devices/machine/ns32202.cpp
new file mode 100644
index 00000000000..947bb6e38cd
--- /dev/null
+++ b/src/devices/machine/ns32202.cpp
@@ -0,0 +1,698 @@
+// license:BSD-3-Clause
+// copyright-holders:Patrick Mackinlay
+
+/*
+ * National Semiconductor NS32202 Interrupt Control Unit (ICU).
+ *
+ * Sources:
+ *
+ * http://bitsavers.org/components/national/_dataBooks/1989_National_Microprocessor_Databook_32000_NSC800.pdf
+ *
+ * TODO
+ * - timer/counter
+ */
+
+#include "emu.h"
+#include "ns32202.h"
+
+#define LOG_GENERAL (1U << 0)
+#define LOG_STATE (1U << 1)
+#define LOG_REGW (1U << 2)
+#define LOG_REGR (1U << 3)
+
+//#define VERBOSE (LOG_GENERAL|LOG_STATE|LOG_REGW|LOG_REGR)
+#include "logmacro.h"
+
+DEFINE_DEVICE_TYPE(NS32202, ns32202_device, "ns32202", "NS32202 Interrupt Control Unit")
+
+enum mctl_mask : u8
+{
+ MCTL_T16N8 = 0x01, // data bus mode
+ MCTL_NTAR = 0x02, // not auto-rotate mode
+ MCTL_FRZ = 0x08, // freeze interrupt pending
+ MCTL_CLKM = 0x10, // clock mode (square wave/pulsed)
+ MCTL_COUTM = 0x20, // cout mode (square wave/pulsed)
+ MCTL_COUTD = 0x40, // cout/scin input/output
+ MCTL_CFRZ = 0x80, // freeze counter readings
+};
+
+enum cctl_mask : u8
+{
+ CCTL_CDCRL = 0x01, // decrement l-counter
+ CCTL_CDCRH = 0x02, // decrement h-counter
+ CCTL_CRUNL = 0x04, // l-counter running
+ CCTL_CRUNH = 0x08, // h-counter running
+ CCTL_COUT0 = 0x10, // zero detect l-counter
+ CCTL_COUT1 = 0x20, // zero detect h-counter
+ CCTL_CFNPS = 0x40, // clock not prescaled
+ CCTL_CCON = 0x80, // counters concatenated
+};
+
+enum cictl_mask : u8
+{
+ CICTL_WENL = 0x01, // l-counter write enable
+ CICTL_CIEL = 0x02, // l-counter interrupt enable
+ CICTL_CIRL = 0x04, // l-counter interrupt request
+ CICTL_CERL = 0x08, // l-counter error flag
+ CICTL_WENH = 0x10, // h-counter write enable
+ CICTL_CIEH = 0x20, // h-counter interrupt enable
+ CICTL_CIRH = 0x40, // h-counter interrupt request
+ CICTL_CERH = 0x80, // h-counter error flag
+};
+
+ns32202_device::ns32202_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock)
+ : device_t(mconfig, NS32202, tag, owner, clock)
+ , m_out_int(*this)
+ , m_out_cout(*this)
+ , m_out_port(*this)
+ , m_line_state(0xffff)
+ , m_out_int_state(false)
+ , m_out_cout_state(false)
+{
+}
+
+void ns32202_device::device_start()
+{
+ m_out_int.resolve_safe();
+ m_out_cout.resolve_safe();
+ m_out_port.resolve_safe();
+
+ save_item(NAME(m_hvct));
+ save_item(NAME(m_eltg));
+ save_item(NAME(m_tpl));
+ save_item(NAME(m_ipnd));
+ save_item(NAME(m_isrv));
+ save_item(NAME(m_imsk));
+ save_item(NAME(m_csrc));
+ save_item(NAME(m_fprt));
+ save_item(NAME(m_mctl));
+ save_item(NAME(m_ocasn));
+ save_item(NAME(m_ciptr));
+ save_item(NAME(m_pdat));
+ save_item(NAME(m_ips));
+ save_item(NAME(m_pdir));
+ save_item(NAME(m_cctl));
+ save_item(NAME(m_cictl));
+ save_item(NAME(m_csv));
+ save_item(NAME(m_ccv));
+
+ save_item(NAME(m_isrv_count));
+
+ save_item(NAME(m_line_state));
+ save_item(NAME(m_out_int_state));
+ save_item(NAME(m_out_cout_state));
+
+ m_interrupt = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(ns32202_device::interrupt), this));
+ m_counter[0] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(ns32202_device::counter<0>), this));
+ m_counter[1] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(ns32202_device::counter<1>), this));
+}
+
+void ns32202_device::device_reset()
+{
+ m_eltg = 0xffff;
+ m_tpl = 0;
+ m_ipnd = 0;
+ m_isrv = 0;
+ m_imsk = 0xffff;
+ m_csrc = 0;
+ m_fprt = 0x0001;
+
+ m_mctl = MCTL_COUTD;
+ m_ocasn = 0;
+ m_ciptr = 0xff;
+ m_ips = 0xff;
+ m_pdir = 0xff;
+ m_cictl = 0;
+}
+
+void ns32202_device::set_int(bool int_state)
+{
+ if (int_state != m_out_int_state)
+ {
+ LOGMASKED(LOG_STATE, "int %s\n", int_state ? "asserted" : "cleared");
+
+ m_out_int_state = int_state;
+ m_out_int(!m_out_int_state);
+ }
+}
+
+void ns32202_device::set_cout(bool cout_state)
+{
+ if (cout_state != m_out_cout_state)
+ {
+ LOGMASKED(LOG_STATE, "cout %s\n", cout_state ? "asserted" : "cleared");
+
+ m_out_cout_state = cout_state;
+ m_out_cout(!m_out_cout_state);
+ }
+}
+
+template <unsigned ST1> void ns32202_device::map(address_map &map)
+{
+ map(0x00, 0x00).r(&ns32202_device::hvct_r<ST1, true>, "ns32202_device::hvct_r");
+ map(0x01, 0x01).rw(&ns32202_device::hvct_r<ST1, false>, "ns32202_device::svct_r", FUNC(ns32202_device::svct_w));
+ map(0x02, 0x02).rw(FUNC(ns32202_device::eltgl_r), FUNC(ns32202_device::eltgl_w));
+ map(0x03, 0x03).rw(FUNC(ns32202_device::eltgh_r), FUNC(ns32202_device::eltgh_w));
+ map(0x04, 0x04).rw(FUNC(ns32202_device::tpll_r), FUNC(ns32202_device::tpll_w));
+ map(0x05, 0x05).rw(FUNC(ns32202_device::tplh_r), FUNC(ns32202_device::tplh_w));
+ map(0x06, 0x06).rw(FUNC(ns32202_device::ipndl_r), FUNC(ns32202_device::ipndl_w));
+ map(0x07, 0x07).rw(FUNC(ns32202_device::ipndh_r), FUNC(ns32202_device::ipndh_w));
+ map(0x08, 0x08).rw(FUNC(ns32202_device::isrvl_r), FUNC(ns32202_device::isrvl_w));
+ map(0x09, 0x09).rw(FUNC(ns32202_device::isrvh_r), FUNC(ns32202_device::isrvh_w));
+ map(0x0a, 0x0a).rw(FUNC(ns32202_device::imskl_r), FUNC(ns32202_device::imskl_w));
+ map(0x0b, 0x0b).rw(FUNC(ns32202_device::imskh_r), FUNC(ns32202_device::imskh_w));
+ map(0x0c, 0x0c).rw(FUNC(ns32202_device::csrcl_r), FUNC(ns32202_device::csrcl_w));
+ map(0x0d, 0x0d).rw(FUNC(ns32202_device::csrch_r), FUNC(ns32202_device::csrch_w));
+ map(0x0e, 0x0e).rw(FUNC(ns32202_device::fprtl_r), FUNC(ns32202_device::fprtl_w));
+ map(0x0f, 0x0f).rw(FUNC(ns32202_device::fprth_r), FUNC(ns32202_device::fprth_w));
+ map(0x10, 0x10).rw(FUNC(ns32202_device::mctl_r), FUNC(ns32202_device::mctl_w));
+ map(0x11, 0x11).rw(FUNC(ns32202_device::ocasn_r), FUNC(ns32202_device::ocasn_w));
+ map(0x12, 0x12).rw(FUNC(ns32202_device::ciptr_r), FUNC(ns32202_device::ciptr_w));
+ map(0x13, 0x13).rw(FUNC(ns32202_device::pdat_r), FUNC(ns32202_device::pdat_w));
+ map(0x14, 0x14).rw(FUNC(ns32202_device::ips_r), FUNC(ns32202_device::ips_w));
+ map(0x15, 0x15).rw(FUNC(ns32202_device::pdir_r), FUNC(ns32202_device::pdir_w));
+ map(0x16, 0x16).rw(FUNC(ns32202_device::cctl_r), FUNC(ns32202_device::cctl_w));
+ map(0x17, 0x17).rw(FUNC(ns32202_device::cictl_r), FUNC(ns32202_device::cictl_w));
+ map(0x18, 0x18).rw(FUNC(ns32202_device::csvl_r<0>), FUNC(ns32202_device::csvl_w<0>));
+ map(0x19, 0x19).rw(FUNC(ns32202_device::csvh_r<0>), FUNC(ns32202_device::csvh_w<0>));
+ map(0x1a, 0x1a).rw(FUNC(ns32202_device::csvl_r<1>), FUNC(ns32202_device::csvl_w<1>));
+ map(0x1b, 0x1b).rw(FUNC(ns32202_device::csvh_r<1>), FUNC(ns32202_device::csvh_w<1>));
+ map(0x1c, 0x1c).rw(FUNC(ns32202_device::lccvl_r), FUNC(ns32202_device::lccvl_w));
+ map(0x1d, 0x1d).rw(FUNC(ns32202_device::lccvh_r), FUNC(ns32202_device::lccvh_w));
+ map(0x1e, 0x1e).rw(FUNC(ns32202_device::hccvl_r), FUNC(ns32202_device::hccvl_w));
+ map(0x1f, 0x1f).rw(FUNC(ns32202_device::hccvh_r), FUNC(ns32202_device::hccvh_w));
+}
+
+template void ns32202_device::map<0>(address_map &map);
+template void ns32202_device::map<1>(address_map &map);
+
+/*
+ * Set (and clear, for level-triggered interrupts) interrupt pending state
+ * based on edge/level/polarity configuration and previous/current line state,
+ * regardless of mask.
+ */
+template <unsigned Number> void ns32202_device::ir_w(int state)
+{
+ // ignore external interrupts assigned to counters
+ if (((m_cictl & CICTL_CIEL) && (m_ciptr & 15) == Number) ||
+ ((m_cictl & CICTL_CIEH) && (m_ciptr >> 4) == Number))
+ return;
+
+ u16 const mask = 1 << Number;
+
+ if (m_eltg & mask)
+ {
+ // level triggered
+ if (state == BIT(m_tpl, Number))
+ m_ipnd |= mask;
+ else
+ m_ipnd &= ~mask;
+ }
+ else
+ {
+ // edge triggered
+ if (bool(state) == BIT(m_tpl, Number) && bool(state) ^ BIT(m_line_state, Number))
+ m_ipnd |= mask;
+ }
+
+ // record input line state
+ if (state)
+ m_line_state |= mask;
+ else
+ m_line_state &= ~mask;
+
+ // datasheet states maximum 800ns
+ m_interrupt->adjust(attotime::from_nsec(600));
+}
+
+// instantiate all valid interrupt request templates
+template void ns32202_device::ir_w<0>(int state);
+template void ns32202_device::ir_w<1>(int state);
+template void ns32202_device::ir_w<2>(int state);
+template void ns32202_device::ir_w<3>(int state);
+template void ns32202_device::ir_w<4>(int state);
+template void ns32202_device::ir_w<5>(int state);
+template void ns32202_device::ir_w<6>(int state);
+template void ns32202_device::ir_w<7>(int state);
+template void ns32202_device::ir_w<8>(int state);
+template void ns32202_device::ir_w<9>(int state);
+template void ns32202_device::ir_w<10>(int state);
+template void ns32202_device::ir_w<11>(int state);
+template void ns32202_device::ir_w<12>(int state);
+template void ns32202_device::ir_w<13>(int state);
+template void ns32202_device::ir_w<14>(int state);
+template void ns32202_device::ir_w<15>(int state);
+
+/*
+ * Assert interrupt output if there are any unmasked pending interrupts; and
+ * - in auto-rotate mode and no interrupts are in-service; or
+ * - in fixed priority mode; and
+ * - no interrupts are in-service; or
+ * - unmasked pending interrupt has priority > in-service interrupt; or
+ * - unmasked pending cascade interrupt has priorty >= in-service interrupt
+ */
+void ns32202_device::interrupt(void *ptr, s32 param)
+{
+ // check for unmasked pending interrupts
+ if (!(m_ipnd & m_imsk))
+ return;
+
+ if (m_mctl & MCTL_NTAR)
+ {
+ // fixed priority mode
+ bool accept = false;
+
+ // check any interrupts in-service
+ if (m_isrv)
+ {
+ // check interrupts in descending priority order
+ u16 mask = m_fprt;
+ for (unsigned i = 0; i < 16; i++)
+ {
+ // check interrupt in-service
+ if (m_isrv & mask)
+ {
+ // check equal priority unmasked pending cascade interrupt
+ if ((m_csrc & mask) && (m_ipnd & mask) && !(m_imsk & mask))
+ {
+ LOGMASKED(LOG_STATE, "unmasked pending cascade in-service interrupt %d\n", 31 - count_leading_zeros(mask));
+ accept = true;
+ }
+
+ break;
+ }
+
+ // check unmasked pending interrupt
+ if ((m_ipnd & mask) && !(m_imsk & mask))
+ {
+ LOGMASKED(LOG_STATE, "unmasked pending interrupt %d\n", 31 - count_leading_zeros(mask));
+ accept = true;
+ break;
+ }
+
+ // rotate priority mask
+ mask = (mask << 1) | (mask >> 15);
+ }
+ }
+ else
+ accept = true;
+
+ if (!accept)
+ return;
+ }
+ else if (m_isrv)
+ return;
+
+ set_int(true);
+}
+
+u8 ns32202_device::interrupt_acknowledge(bool side_effects)
+{
+ side_effects &= !machine().side_effects_disabled();
+ u8 vector = m_hvct | 0x0f;
+
+ if ((m_ipnd & m_imsk) && m_fprt)
+ {
+ // find highest priority unmasked pending interrupt
+ u16 mask = m_fprt;
+ for (unsigned i = 0; i < 16; i++)
+ {
+ if ((m_ipnd & mask) && !(m_imsk & mask))
+ break;
+
+ // rotate priority mask
+ mask = (mask << 1) | (mask >> 15);
+ }
+
+ unsigned const number = 31 - count_leading_zeros(mask);
+ if (side_effects)
+ {
+ LOGMASKED(LOG_STATE, "acknowledge highest priority unmasked interrupt %d\n", number);
+
+ if (m_mctl & MCTL_NTAR)
+ {
+ if (m_csrc & mask)
+ m_isrv_count[number]++;
+ }
+ else
+ m_fprt = mask;
+
+ // mark interrupt in-service
+ m_isrv |= mask;
+
+ // clear interrupt pending
+ m_ipnd &= ~(mask);
+
+ // clear l-counter interrupt pending
+ if ((m_cictl & CICTL_CIEL) && (m_cictl & CICTL_CIRL) && BIT(mask, m_ciptr & 15))
+ m_cictl &= ~CICTL_CIRL;
+
+ // clear h-counter interrupt pending
+ if ((m_cictl & CICTL_CIEH) && (m_cictl & CICTL_CIRH) && BIT(mask, m_ciptr >> 4))
+ m_cictl &= ~CICTL_CIRH;
+
+ // clear interrupt output
+ set_int(false);
+ }
+
+ // compute acknowledge vector
+ if (m_csrc & mask)
+ vector = 0xf0 | number;
+ else
+ vector = m_hvct | number;
+ }
+ else if (side_effects)
+ {
+ if (m_fprt)
+ LOGMASKED(LOG_STATE, "acknowledge without unmasked interrupt pending\n");
+ else
+ LOGMASKED(LOG_STATE, "acknowledge with FPRT clear\n");
+
+ // clear pending edge for interrupt 15
+ if (!BIT(m_eltg, 15))
+ m_ipnd &= ~(1 << 15);
+
+ // clear first priority
+ if (!(m_mctl & MCTL_NTAR))
+ m_fprt = 0;
+ }
+
+ if (side_effects)
+ LOGMASKED(LOG_STATE, "acknowledge vector 0x%02x\n", vector);
+
+ return vector;
+}
+
+u8 ns32202_device::interrupt_return(bool side_effects)
+{
+ side_effects &= !machine().side_effects_disabled();
+ u8 vector = m_hvct | 0x0f;
+
+ // find highest priority in-service interrupt
+ if (m_isrv && m_fprt)
+ {
+ u16 mask = m_fprt;
+ for (unsigned i = 0; i < 16; i++)
+ {
+ if (m_isrv & mask)
+ break;
+
+ // rotate priority mask
+ mask = (mask << 1) | (mask >> 15);
+ }
+ unsigned const number = 31 - count_leading_zeros(mask);
+
+ if (side_effects)
+ {
+ LOGMASKED(LOG_STATE, "return highest priority in-service interrupt %d\n", number);
+
+ if (m_mctl & MCTL_NTAR)
+ {
+ if (m_csrc & mask)
+ {
+ m_isrv_count[number]--;
+
+ if (!m_isrv_count[number])
+ m_isrv &= ~mask;
+ }
+ else
+ // clear interrupt in-service
+ m_isrv &= ~mask;
+ }
+ else
+ {
+ // clear interrupt in-service
+ m_isrv &= ~mask;
+
+ // rotate priority mask
+ m_fprt = (m_fprt << 1) | (m_fprt >> 15);
+ }
+ }
+
+ // compute return vector
+ if (m_csrc & mask)
+ vector = 0xf0 | number;
+ else
+ vector = m_hvct | number;
+ }
+ else if (side_effects)
+ {
+ if (m_fprt)
+ LOGMASKED(LOG_STATE, "return without in-service interrupt\n");
+ else
+ LOGMASKED(LOG_STATE, "return with FPRT clear\n");
+
+ if (!(m_mctl & MCTL_NTAR))
+ // rotate priority mask
+ m_fprt = (m_fprt << 1) | (m_fprt >> 15);
+ }
+
+ if (side_effects)
+ LOGMASKED(LOG_STATE, "return vector 0x%02x\n", vector);
+
+ return vector;
+}
+
+/*
+ * Check for level-triggered interrupts which become pending due to change of
+ * edge/level or polarity registers.
+ */
+void ns32202_device::interrupt_update()
+{
+ // compute new pending state
+ u16 const ipnd = m_ipnd | (m_eltg & ~(m_line_state ^ m_tpl));
+
+ // update and assert if state changed
+ if (ipnd ^ m_ipnd)
+ {
+ m_ipnd = ipnd;
+ m_interrupt->adjust(attotime::zero);
+ }
+}
+
+// N=0 -> l-counter
+template <unsigned N> void ns32202_device::counter(void *buf, s32 param)
+{
+ u32 const scaled_clock = clock() / ((m_cctl & CCTL_CFNPS) ? 1 : 4);
+
+ // for now, assume this is the periodic timer triggered when we hit zero
+ // reload on cycle after zero
+ if (param)
+ {
+ u32 const ticks = (m_cctl & CCTL_CCON)
+ ? ((u32(m_csv[1]) << 16) | m_csv[0]) - ((u32(m_ccv[1]) << 16) | m_ccv[0])
+ : m_csv[N] - m_ccv[N];
+
+ // reload current value
+ if (m_cctl & CCTL_CCON)
+ {
+ m_ccv[0] = m_csv[0];
+ m_ccv[1] = m_csv[1];
+ }
+ else
+ m_ccv[N] = m_csv[N];
+
+ // reschedule counter
+ m_counter[N]->adjust(attotime::from_ticks(ticks, scaled_clock), 0);
+ }
+ else
+ {
+ // clear current value
+ if (m_cctl & CCTL_CCON)
+ {
+ m_ccv[0] = 0;
+ m_ccv[1] = 0;
+ }
+ else
+ m_ccv[N] = 0;
+
+ // schedule reload cycle
+ m_counter[N]->adjust(attotime::from_ticks(1, scaled_clock), 1);
+
+ // update cout
+ if (!(m_mctl & MCTL_COUTD) && (m_cctl & (CCTL_COUT0 << N)))
+ {
+ if (m_mctl & MCTL_COUTM)
+ {
+ set_cout(true);
+ set_cout(false);
+ }
+ else
+ set_cout(!m_out_cout_state);
+ }
+
+ // update port
+ if ((N == 1) && !(m_mctl & MCTL_T16N8) && (m_ocasn & 15))
+ {
+ // TODO: trigger interrupts if IPS != 0
+
+ u8 const mask = (m_ocasn & ~m_pdir) & 15;
+ if (m_mctl & MCTL_CLKM)
+ {
+ m_pdat &= ~mask;
+
+ m_out_port(0, m_ocasn & 15, mask);
+ m_out_port(0, 0, mask);
+ }
+ else
+ {
+ m_pdat ^= mask;
+
+ m_out_port(0, m_pdat, mask);
+ }
+ }
+
+ // interrupts
+ unsigned const shift = N ? 4 : 0;
+ if (m_cictl & (CICTL_CIEL << shift))
+ {
+ // check counter interrupt error
+ if (m_cictl & (CICTL_CIRL << shift))
+ m_cictl |= (CICTL_CERL << shift);
+
+ // set counter interrupt request
+ m_cictl |= (CICTL_CIRL << shift);
+
+ // raise interrupt
+ m_ipnd |= 1 << ((m_ciptr >> shift) & 15);
+ m_interrupt->adjust(attotime::zero);
+ }
+ }
+}
+
+template <unsigned ST1, bool SideEffects> u8 ns32202_device::hvct_r()
+{
+ if (!ST1)
+ return interrupt_acknowledge(SideEffects);
+ else
+ return interrupt_return(SideEffects);
+}
+
+void ns32202_device::eltgl_w(u8 data)
+{
+ m_eltg = (m_eltg & 0xff00) | data;
+
+ interrupt_update();
+}
+
+void ns32202_device::eltgh_w(u8 data)
+{
+ m_eltg = (u16(data) << 8) | u8(m_eltg);
+
+ interrupt_update();
+}
+
+void ns32202_device::tpll_w(u8 data)
+{
+ m_tpl = (m_tpl & 0xff00) | data;
+
+ interrupt_update();
+}
+
+void ns32202_device::tplh_w(u8 data)
+{
+ m_tpl = (u16(data) << 8) | u8(m_tpl);
+
+ interrupt_update();
+}
+
+void ns32202_device::csrcl_w(u8 data)
+{
+ m_csrc = (m_csrc & 0xff00) | data;
+
+ // clear in-service counters
+ for (unsigned i = 0; i < 8; i++)
+ if (!BIT(m_csrc, i))
+ m_isrv_count[i] = 0;
+}
+
+void ns32202_device::csrch_w(u8 data)
+{
+ m_csrc = (u16(data) << 8) | u8(m_csrc);
+
+ // clear in-service counters
+ for (unsigned i = 8; i < 16; i++)
+ if (!BIT(m_csrc, i))
+ m_isrv_count[i] = 0;
+}
+
+void ns32202_device::ipndl_w(u8 data)
+{
+ if (BIT(data, 6))
+ // clear all pending interrupts in register
+ m_ipnd &= 0xff00;
+ else if (BIT(data, 7))
+ {
+ // set interrupt
+ m_ipnd |= 1 << (data & 7);
+
+ m_interrupt->adjust(attotime::zero);
+ }
+ else
+ // clear interrupt
+ m_ipnd &= ~(1 << (data & 7));
+}
+
+void ns32202_device::ipndh_w(u8 data)
+{
+ if (BIT(data, 6))
+ // clear all pending interrupts in register
+ m_ipnd &= 0x00ff;
+ else if (BIT(data, 7))
+ {
+ // set interrupt
+ m_ipnd |= 256 << (data & 7);
+
+ m_interrupt->adjust(attotime::zero);
+ }
+ else
+ // clear interrupt
+ m_ipnd &= ~(256 << (data & 7));
+}
+
+void ns32202_device::fprtl_w(u8 data)
+{
+ m_fprt = 1 << (data & 15);
+}
+
+void ns32202_device::cctl_w(u8 data)
+{
+ // disable l-counter in concatenated mode
+ if ((data & CCTL_CCON) && m_counter[0]->enabled())
+ m_counter[0]->enable(false);
+
+ // compute scaled clock
+ u32 const scaled_clock = clock() / ((data & CCTL_CFNPS) ? 1 : 4);
+
+ // start/stop h-counter
+ if (!(m_cctl & CCTL_CRUNH) && (data & CCTL_CRUNH))
+ m_counter[1]->adjust(attotime::from_ticks(1, scaled_clock), 1);
+ else if ((m_cctl & CCTL_CRUNH) && !(data & CCTL_CRUNH))
+ m_counter[1]->enable(false);
+
+ if (!(data & CCTL_CRUNH) && (data & CCTL_CDCRH))
+ ; // TODO: decrement h-counter
+
+ // start/stop l-counter
+ if (!(data & CCTL_CCON))
+ {
+ if (!(m_cctl & CCTL_CRUNH) && (data & CCTL_CRUNH))
+ m_counter[0]->adjust(attotime::from_ticks(1, scaled_clock), 1);
+ else if ((m_cctl & CCTL_CRUNH) && !(data & CCTL_CRUNH))
+ m_counter[0]->enable(false);
+
+ if (!(data & CCTL_CRUNL) && (data & CCTL_CDCRL))
+ ; // TODO: decrement l-counter
+ }
+
+ m_cctl = data & ~(CCTL_CRUNH | CCTL_CRUNL);
+}
+
+void ns32202_device::cictl_w(u8 data)
+{
+ u8 const mask =
+ ((data & CICTL_WENL) ? (CICTL_CERL | CICTL_CIRL | CICTL_CIEL) : 0) |
+ ((data & CICTL_WENH) ? (CICTL_CERH | CICTL_CIRH | CICTL_CIEH) : 0);
+
+ m_cictl = (m_cictl & ~mask) | (data & mask);
+}
diff --git a/src/devices/machine/ns32202.h b/src/devices/machine/ns32202.h
new file mode 100644
index 00000000000..c6328e5fa38
--- /dev/null
+++ b/src/devices/machine/ns32202.h
@@ -0,0 +1,137 @@
+// license:BSD-3-Clause
+// copyright-holders:Patrick Mackinlay
+
+#ifndef MAME_MACHINE_NS32202_H
+#define MAME_MACHINE_NS32202_H
+
+#pragma once
+
+class ns32202_device : public device_t
+{
+public:
+ auto out_int() { return m_out_int.bind(); }
+ auto out_cout() { return m_out_cout.bind(); }
+ auto out_port() { return m_out_port.bind(); }
+
+ template <unsigned Number> void ir_w(int state);
+ template <unsigned ST1> void map(address_map &map);
+
+ ns32202_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock);
+
+protected:
+ // device_t overrides
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+ void set_int(bool int_state);
+ void set_cout(bool cout_state);
+
+ void interrupt(void *buf, s32 param);
+ u8 interrupt_acknowledge(bool side_effects);
+ u8 interrupt_return(bool side_effects);
+
+ void interrupt_update();
+
+ template <unsigned N> void counter(void *buf, s32 param);
+
+ template <unsigned ST1, bool SideEffects> u8 hvct_r();
+
+ u8 eltgl_r() { return u8(m_eltg); }
+ u8 eltgh_r() { return m_eltg >> 8; }
+ u8 tpll_r() { return u8(m_tpl); }
+ u8 tplh_r() { return m_tpl >> 8; }
+ u8 ipndl_r() { return u8(m_ipnd); }
+ u8 ipndh_r() { return m_ipnd >> 8; }
+ u8 isrvl_r() { return u8(m_isrv); }
+ u8 isrvh_r() { return m_isrv >> 8; }
+ u8 imskl_r() { return u8(m_imsk); }
+ u8 imskh_r() { return m_imsk >> 8; }
+ u8 csrcl_r() { return u8(m_csrc); }
+ u8 csrch_r() { return m_csrc >> 8; }
+ u8 fprtl_r() { return u8(m_fprt); }
+ u8 fprth_r() { return m_fprt >> 8; }
+ u8 mctl_r() { return m_mctl; }
+ u8 ocasn_r() { return m_ocasn; }
+ u8 ciptr_r() { return m_ciptr; }
+ u8 pdat_r() { return 0; }
+ u8 ips_r() { return m_ips; }
+ u8 pdir_r() { return m_pdir; }
+ u8 cctl_r() { return m_cctl; }
+ u8 cictl_r() { return m_cictl; }
+ template <unsigned N> u8 csvl_r() { return u8(m_csv[N]); }
+ template <unsigned N> u8 csvh_r() { return m_csv[N] >> 8; }
+ u8 lccvl_r() { return 0; }
+ u8 lccvh_r() { return 0; }
+ u8 hccvl_r() { return 0; }
+ u8 hccvh_r() { return 0; }
+
+ void svct_w(u8 data) { m_hvct = data & 0xf0; }
+
+ void eltgl_w(u8 data);
+ void eltgh_w(u8 data);
+ void tpll_w(u8 data);
+ void tplh_w(u8 data);
+ void ipndl_w(u8 data);
+ void ipndh_w(u8 data);
+ void isrvl_w(u8 data) { m_isrv = (m_isrv & 0xff00) | data; }
+ void isrvh_w(u8 data) { m_isrv = (u16(data) << 8) | u8(m_isrv); }
+ void imskl_w(u8 data) { m_imsk = (m_imsk & 0xff00) | data; m_interrupt->adjust(attotime::zero); }
+ void imskh_w(u8 data) { m_imsk = (u16(data) << 8) | u8(m_imsk); m_interrupt->adjust(attotime::zero); }
+ void csrcl_w(u8 data);
+ void csrch_w(u8 data);
+ void fprtl_w(u8 data);
+ void fprth_w(u8 data) {}
+
+ void mctl_w(u8 data) { m_mctl = data; }
+ void ocasn_w(u8 data) { m_ocasn = data; }
+ void ciptr_w(u8 data) { m_ciptr = data; }
+ void pdat_w(u8 data) {}
+ void ips_w(u8 data) { m_ips = data; }
+ void pdir_w(u8 data) { m_pdir = data; }
+ void cctl_w(u8 data);
+ void cictl_w(u8 data);
+
+ template <unsigned N> void csvl_w(u8 data) { m_csv[N] = (m_csv[N] & 0xff00) | data; }
+ template <unsigned N> void csvh_w(u8 data) { m_csv[N] = (u16(data) << 8) | u8(m_csv[N]); }
+ void lccvl_w(u8 data) {}
+ void lccvh_w(u8 data) {}
+ void hccvl_w(u8 data) {}
+ void hccvh_w(u8 data) {}
+
+private:
+ devcb_write_line m_out_int;
+ devcb_write_line m_out_cout;
+ devcb_write8 m_out_port;
+
+ emu_timer *m_interrupt;
+ emu_timer *m_counter[2];
+
+ u8 m_hvct; // hardware vector
+ u16 m_eltg; // edge/level triggering
+ u16 m_tpl; // triggering polarity
+ u16 m_ipnd; // interrupts pending
+ u16 m_isrv; // interrupts in-service
+ u16 m_imsk; // interrupt mask
+ u16 m_csrc; // cascaded source
+ u16 m_fprt; // first priority
+ u8 m_mctl; // mode control
+ u8 m_ocasn; // output clock assignment
+ u8 m_ciptr; // counter interrupt pointer
+ u8 m_pdat; // port data
+ u8 m_ips; // interrupt/port select
+ u8 m_pdir; // port direction
+ u8 m_cctl; // counter control
+ u8 m_cictl; // counter interrupt control
+ u16 m_csv[2]; // counter starting value
+ u16 m_ccv[2]; // counter current value
+
+ unsigned m_isrv_count[16];
+
+ u16 m_line_state;
+ bool m_out_int_state;
+ bool m_out_cout_state;
+};
+
+DECLARE_DEVICE_TYPE(NS32202, ns32202_device)
+
+#endif // MAME_MACHINE_NS32202_H