summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/cpu/sh
diff options
context:
space:
mode:
author 987123879113 <63495610+987123879113@users.noreply.github.com>2023-09-22 13:56:24 +0900
committer GitHub <noreply@github.com>2023-09-22 14:56:24 +1000
commitf60fd23e3e10f140aa4a63ec1cf26cffd78f422a (patch)
treee203d59afaa7da25cf63748eac9501ed65108216 /src/devices/cpu/sh
parent85ce279c637a21d9a62627392d1636ece7ec360d (diff)
namco/namcos12.cpp: Emulated games with CDXA board. (#11558)
* machine/t10mmc.cpp: Added support for T10SBC_CMD_SEEK_10 command. * cpu/sh: Added SH7014 SoC. * machine/icd2061a.cpp: Emulated IC Designs 2061A programmable clock generator. * sound/lc78836m.cpp: Emulated Sanyo LC78836M audio DAC. * namco/namcos12_cdxa.cpp: Emulated Namco System 12 CDXA board. Systems promoted to working ----------------------------- Truck Kyosokyoku (Japan, TKK2/VER.A) [Windy Fairy] New working systems ----------------------------- Um Jammer Lammy NOW! (Japan, UL1/VER.A) [Phil Bennett, Eric Yockey, Windy Fairy]
Diffstat (limited to 'src/devices/cpu/sh')
-rw-r--r--src/devices/cpu/sh/sh7014.cpp201
-rw-r--r--src/devices/cpu/sh/sh7014.h83
-rw-r--r--src/devices/cpu/sh/sh7014_bsc.cpp148
-rw-r--r--src/devices/cpu/sh/sh7014_bsc.h62
-rw-r--r--src/devices/cpu/sh/sh7014_dmac.cpp400
-rw-r--r--src/devices/cpu/sh/sh7014_dmac.h173
-rw-r--r--src/devices/cpu/sh/sh7014_intc.cpp313
-rw-r--r--src/devices/cpu/sh/sh7014_intc.h147
-rw-r--r--src/devices/cpu/sh/sh7014_mtu.cpp571
-rw-r--r--src/devices/cpu/sh/sh7014_mtu.h211
-rw-r--r--src/devices/cpu/sh/sh7014_port.cpp243
-rw-r--r--src/devices/cpu/sh/sh7014_port.h98
-rw-r--r--src/devices/cpu/sh/sh7014_sci.cpp380
-rw-r--r--src/devices/cpu/sh/sh7014_sci.h135
14 files changed, 3165 insertions, 0 deletions
diff --git a/src/devices/cpu/sh/sh7014.cpp b/src/devices/cpu/sh/sh7014.cpp
new file mode 100644
index 00000000000..ba47606c399
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014.cpp
@@ -0,0 +1,201 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH-2 SH7014
+
+***************************************************************************/
+
+#include "emu.h"
+
+#include "sh7014.h"
+
+
+DEFINE_DEVICE_TYPE(SH2_SH7014, sh2_sh7014_device, "sh2_sh7014", "Hitachi SH-2 (SH7014)")
+
+sh2_sh7014_device::sh2_sh7014_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : sh2_device(mconfig, SH2_SH7014, tag, owner, clock, CPU_TYPE_SH2, address_map_constructor(FUNC(sh2_sh7014_device::sh7014_map), this), 32, 0xffffffff)
+ , m_sci(*this, "sci%u", 0u)
+ , m_bsc(*this, "bsc")
+ , m_dmac(*this, "dmac")
+ , m_intc(*this, "intc")
+ , m_mtu(*this, "mtu")
+ , m_port(*this, "io_port")
+ , m_sci_tx_cb(*this)
+{
+}
+
+void sh2_sh7014_device::device_start()
+{
+ sh2_device::device_start();
+
+ save_item(NAME(m_ccr));
+}
+
+void sh2_sh7014_device::device_reset()
+{
+ sh2_device::device_reset();
+
+ // CAC
+ m_ccr = 0;
+}
+
+void sh2_sh7014_device::device_add_mconfig(machine_config &config)
+{
+ SH7014_SCI(config, m_sci[0], DERIVED_CLOCK(1, 1), m_intc,
+ 0, // id
+ sh7014_intc_device::INT_VECTOR_SCI_ERI0,
+ sh7014_intc_device::INT_VECTOR_SCI_RXI0,
+ sh7014_intc_device::INT_VECTOR_SCI_TXI0,
+ sh7014_intc_device::INT_VECTOR_SCI_TEI0
+ );
+
+ SH7014_SCI(config, m_sci[1], DERIVED_CLOCK(1, 1), m_intc,
+ 1, // id
+ sh7014_intc_device::INT_VECTOR_SCI_ERI1,
+ sh7014_intc_device::INT_VECTOR_SCI_RXI1,
+ sh7014_intc_device::INT_VECTOR_SCI_TXI1,
+ sh7014_intc_device::INT_VECTOR_SCI_TEI1
+ );
+
+ SH7014_BSC(config, m_bsc);
+
+ SH7014_DMAC(config, m_dmac, DERIVED_CLOCK(1, 1), *this, m_intc);
+ m_dmac->set_notify_dma_source_callback(FUNC(sh2_sh7014_device::notify_dma_source));
+
+ SH7014_INTC(config, m_intc);
+ m_intc->set_irq_callback(FUNC(sh2_sh7014_device::set_irq));
+
+ SH7014_MTU(config, m_mtu, DERIVED_CLOCK(1, 1), m_intc);
+
+ SH7014_PORT(config, m_port);
+}
+
+void sh2_sh7014_device::sh7014_map(address_map &map)
+{
+ // SCI - Serial Communication Interface
+ map(0xffff81a0, 0xffff81af).m(m_sci[0], FUNC(sh7014_sci_device::map));
+ map(0xffff81b0, 0xffff81bf).m(m_sci[1], FUNC(sh7014_sci_device::map));
+
+ // MTU - Multifunction Timer Pulse Unit
+ map(0xffff8240, 0xffff82af).m(m_mtu, FUNC(sh7014_mtu_device::map));
+
+ // INTC - Interrupt Controller
+ map(0xffff8348, 0xffff835b).m(m_intc, FUNC(sh7014_intc_device::map));
+
+ // I/O - I/O Ports (DR registers)
+ // PFC - Pin Function Controller (IOR, CR registers)
+ // TODO: SH7016/SH7017 support additionally C and D ports in addition to the A, B, E, F that the SH7014 supports
+ map(0xffff8382, 0xffff8383).rw(m_port, FUNC(sh7014_port_device::padrl_r), FUNC(sh7014_port_device::padrl_w));
+ map(0xffff8386, 0xffff8387).rw(m_port, FUNC(sh7014_port_device::paiorl_r), FUNC(sh7014_port_device::paiorl_w));
+ map(0xffff838c, 0xffff838d).rw(m_port, FUNC(sh7014_port_device::pacrl1_r), FUNC(sh7014_port_device::pacrl1_w));
+ map(0xffff838e, 0xffff838f).rw(m_port, FUNC(sh7014_port_device::pacrl2_r), FUNC(sh7014_port_device::pacrl2_w));
+
+ map(0xffff8390, 0xffff8391).rw(m_port, FUNC(sh7014_port_device::pbdr_r), FUNC(sh7014_port_device::pbdr_w));
+ map(0xffff8394, 0xffff8395).rw(m_port, FUNC(sh7014_port_device::pbior_r), FUNC(sh7014_port_device::pbior_w));
+ map(0xffff8398, 0xffff8399).rw(m_port, FUNC(sh7014_port_device::pbcr1_r), FUNC(sh7014_port_device::pbcr1_w));
+ map(0xffff839a, 0xffff839b).rw(m_port, FUNC(sh7014_port_device::pbcr2_r), FUNC(sh7014_port_device::pbcr2_w));
+
+ map(0xffff83b0, 0xffff83b1).rw(m_port, FUNC(sh7014_port_device::pedr_r), FUNC(sh7014_port_device::pedr_w));
+ map(0xffff83b4, 0xffff83b5).rw(m_port, FUNC(sh7014_port_device::peior_r), FUNC(sh7014_port_device::peior_w));
+ map(0xffff83b8, 0xffff83b9).rw(m_port, FUNC(sh7014_port_device::pecr1_r), FUNC(sh7014_port_device::pecr1_w));
+ map(0xffff83ba, 0xffff83bb).rw(m_port, FUNC(sh7014_port_device::pecr2_r), FUNC(sh7014_port_device::pecr2_w));
+
+ map(0xffff83b3, 0xffff83b3).r(m_port, FUNC(sh7014_port_device::pfdr_r));
+
+ // TODO: CMT - Compare Match Timer
+ // 0xffff83d0 - 0xffff83df
+
+ // TODO: A/D - A/D Converter (High Speed, for SH7014)
+ // 0xffff83e0 - 0xffff83ff
+
+ // TODO: A/D - A/D Converter (Mid Speed, for SH7016/SH7017)
+ // 0xffff8420 - 0xffff8429
+
+ // TODO: WDT - Watchdog Timer
+ // 0xffff8610 - 0xffff8613
+
+ // TODO: Power-down state
+ // 0xffff8614
+
+ // BSC - Bus State Controller
+ map(0xffff8620, 0xffff8633).m(m_bsc, FUNC(sh7014_bsc_device::map));
+
+ // DMAC - Direct Memory Access Controller
+ map(0xffff86b0, 0xffff86df).m(m_dmac, FUNC(sh7014_dmac_device::map));
+
+ // CAC - Cache Memory
+ map(0xffff8740, 0xffff8741).rw(FUNC(sh2_sh7014_device::ccr_r), FUNC(sh2_sh7014_device::ccr_w));
+
+ // Cache space
+ map(0xfffff000, 0xffffffff).ram();
+}
+
+void sh2_sh7014_device::sh2_exception_internal(const char *message, int irqline, int vector)
+{
+ // IRQ was taken so clear it in the interrupt controller and pass it down
+ m_intc->set_interrupt(vector, CLEAR_LINE);
+ sh2_device::sh2_exception_internal(message, irqline, vector);
+}
+
+void sh2_sh7014_device::execute_set_input(int irqline, int state)
+{
+ /*
+ Flow for SH7014 IRQs:
+ sh2_sh7014_device::execute_set_input (for externally triggered IRQs)
+ -> sh7014_intc_device::set_input
+ -> sh2_sh7014_device::set_irq
+ -> sh2_device::execute_set_input (if not internal peripheral IRQ) OR DMA interception OR set sh2_device's internal IRQ flags
+ */
+ m_intc->set_input(irqline, state);
+}
+
+void sh2_sh7014_device::set_irq(int vector, int level, bool is_internal)
+{
+ if (!is_internal) {
+ sh2_device::execute_set_input(vector, ASSERT_LINE);
+ return;
+ }
+
+ // SH7014's DMA controller can be configured to trigger based on various
+ // on-board peripheral IRQs, so on-board peripheral IRQs must go through here
+ if (m_dmac->is_dma_activated(vector)) {
+ m_intc->set_interrupt(vector, CLEAR_LINE);
+ return;
+ }
+
+ m_sh2_state->internal_irq_level = level;
+ m_internal_irq_vector = vector;
+ m_test_irq = 1;
+}
+
+void sh2_sh7014_device::notify_dma_source(uint32_t source)
+{
+ if (source == sh7014_dmac_channel_device::RS_SCI_TXI0)
+ m_sci[0]->set_dma_source_tx(true);
+ else if (source == sh7014_dmac_channel_device::RS_SCI_TXI1)
+ m_sci[1]->set_dma_source_tx(true);
+ else if (source == sh7014_dmac_channel_device::RS_SCI_RXI0)
+ m_sci[0]->set_dma_source_rx(true);
+ else if (source == sh7014_dmac_channel_device::RS_SCI_RXI1)
+ m_sci[1]->set_dma_source_rx(true);
+}
+
+///////
+// CAC
+
+uint16_t sh2_sh7014_device::ccr_r()
+{
+ // bits 15-5 are undefined
+ return m_ccr & 0x1f;
+}
+
+void sh2_sh7014_device::ccr_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ // bit 0 - CECS0 CS0 Space Cache Enable
+ // bit 1 - CECS1 CS1 Space Cache Enable
+ // bit 2 - CECS2 CS2 Space Cache Enable
+ // bit 3 - CECS3 CS3 Space Cache Enable
+ // bit 4 - CEDRAM DRAM Space Cache Enable
+ COMBINE_DATA(&m_ccr);
+}
diff --git a/src/devices/cpu/sh/sh7014.h b/src/devices/cpu/sh/sh7014.h
new file mode 100644
index 00000000000..873581404d1
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014.h
@@ -0,0 +1,83 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH-2 SH7014
+
+***************************************************************************/
+
+#ifndef MAME_CPU_SH_SH7014_H
+#define MAME_CPU_SH_SH7014_H
+
+#pragma once
+
+#include "sh2.h"
+#include "sh7014_bsc.h"
+#include "sh7014_dmac.h"
+#include "sh7014_intc.h"
+#include "sh7014_mtu.h"
+#include "sh7014_port.h"
+#include "sh7014_sci.h"
+
+class sh2_sh7014_device : public sh2_device
+{
+public:
+ sh2_sh7014_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ template<int Sci> auto sci_tx_w() {
+ return m_sci[Sci].lookup()->write_sci_tx();
+ }
+
+ template<int Sci> void sci_set_external_clock_period(const attotime &period) {
+ m_sci[Sci].lookup()->set_external_clock_period(period);
+ }
+
+ template<int Sci> void sci_set_send_full_data_transmit_on_sync_hack(bool enabled) {
+ m_sci[Sci].lookup()->set_send_full_data_transmit_on_sync_hack(enabled);
+ }
+
+ auto read_porta() { return m_port.lookup()->port_a_read_callback(); }
+ auto write_porta() { return m_port.lookup()->port_a_write_callback(); }
+
+ auto read_portb() { return m_port.lookup()->port_b_read_callback(); }
+ auto write_portb() { return m_port.lookup()->port_b_write_callback(); }
+
+ auto read_porte() { return m_port.lookup()->port_e_read_callback(); }
+ auto write_porte() { return m_port.lookup()->port_e_write_callback(); }
+
+ auto read_portf() { return m_port.lookup()->port_f_read_callback(); }
+
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+ virtual void device_add_mconfig(machine_config &config) override;
+
+ virtual void execute_set_input(int inputnum, int state) override;
+
+ virtual void sh2_exception_internal(const char *message, int irqline, int vector) override;
+
+private:
+ void sh7014_map(address_map &map);
+
+ void set_irq(int vector, int level, bool is_internal);
+
+ void notify_dma_source(uint32_t source);
+
+ uint16_t ccr_r();
+ void ccr_w(offs_t offset, uint16_t dat, uint16_t mem_mask = ~0);
+
+ required_device_array<sh7014_sci_device, 2> m_sci;
+ required_device<sh7014_bsc_device> m_bsc;
+ required_device<sh7014_dmac_device> m_dmac;
+ required_device<sh7014_intc_device> m_intc;
+ required_device<sh7014_mtu_device> m_mtu;
+ required_device<sh7014_port_device> m_port;
+
+ devcb_write_line::array<2> m_sci_tx_cb;
+
+ uint16_t m_ccr;
+};
+
+DECLARE_DEVICE_TYPE(SH2_SH7014, sh2_sh7014_device)
+
+#endif // MAME_CPU_SH_SH7014_H
diff --git a/src/devices/cpu/sh/sh7014_bsc.cpp b/src/devices/cpu/sh/sh7014_bsc.cpp
new file mode 100644
index 00000000000..467bfdd1f46
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_bsc.cpp
@@ -0,0 +1,148 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 Bus State Controller
+
+***************************************************************************/
+
+#include "emu.h"
+#include "sh7014_bsc.h"
+
+// #define VERBOSE (LOG_GENERAL)
+
+#include "logmacro.h"
+
+
+DEFINE_DEVICE_TYPE(SH7014_BSC, sh7014_bsc_device, "sh7014bsc", "SH7014 Bus State Controller")
+
+
+sh7014_bsc_device::sh7014_bsc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, SH7014_BSC, tag, owner, clock)
+{
+}
+
+void sh7014_bsc_device::device_start()
+{
+ save_item(NAME(m_bcr1));
+ save_item(NAME(m_bcr2));
+ save_item(NAME(m_wcr1));
+ save_item(NAME(m_wcr2));
+ save_item(NAME(m_dcr));
+ save_item(NAME(m_rtcsr));
+ save_item(NAME(m_rtcnt));
+ save_item(NAME(m_rtcor));
+}
+
+void sh7014_bsc_device::device_reset()
+{
+ m_bcr1 = 0;
+ m_bcr2 = 0xffff;
+ m_wcr1 = 0xffff;
+ m_wcr2 = 0x000f;
+ m_dcr = 0;
+ m_rtcsr = 0;
+ m_rtcnt = 0;
+ m_rtcor = 0;
+}
+
+void sh7014_bsc_device::map(address_map &map)
+{
+ map(0x00, 0x01).rw(FUNC(sh7014_bsc_device::bcr1_r), FUNC(sh7014_bsc_device::bcr1_w));
+ map(0x02, 0x03).rw(FUNC(sh7014_bsc_device::bcr2_r), FUNC(sh7014_bsc_device::bcr2_w));
+ map(0x04, 0x05).rw(FUNC(sh7014_bsc_device::wcr1_r), FUNC(sh7014_bsc_device::wcr1_w));;
+ map(0x06, 0x07).rw(FUNC(sh7014_bsc_device::wcr2_r), FUNC(sh7014_bsc_device::wcr2_w));
+ map(0x0a, 0x0b).rw(FUNC(sh7014_bsc_device::dcr_r), FUNC(sh7014_bsc_device::dcr_w));
+ map(0x0c, 0x0d).rw(FUNC(sh7014_bsc_device::rtcsr_r), FUNC(sh7014_bsc_device::rtcsr_w));
+ map(0x0e, 0x0f).rw(FUNC(sh7014_bsc_device::rtcnt_r), FUNC(sh7014_bsc_device::rtcnt_w));
+ map(0x10, 0x11).rw(FUNC(sh7014_bsc_device::rtcor_r), FUNC(sh7014_bsc_device::rtcor_w));
+}
+
+///
+
+uint16_t sh7014_bsc_device::bcr1_r()
+{
+ // bit 0 [R/W] CS0 Space Size Specification (A0SZ)
+ // bit 1 [R/W] CS1 Space Size Specification (A1SZ)
+ // bit 2 [R/W] CS2 Space Size Specification (A2SZ)
+ // bit 3 [R/W] CS3 Space Size Specification (A3SZ)
+ // bit 8 [R/W] Multiplex I/O Enable (IOE)
+ // bit 13 [R] Always returns 1
+ // Everything else is read-only and will return 0
+ return (m_bcr1 & 0x10f) | (1 << 13);
+}
+
+void sh7014_bsc_device::bcr1_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_bcr1);
+}
+
+uint16_t sh7014_bsc_device::bcr2_r()
+{
+ return m_bcr2;
+}
+
+void sh7014_bsc_device::bcr2_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_bcr2);
+}
+
+uint16_t sh7014_bsc_device::wcr1_r()
+{
+ return m_wcr1;
+}
+
+void sh7014_bsc_device::wcr1_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_wcr1);
+}
+
+uint16_t sh7014_bsc_device::wcr2_r()
+{
+ return m_wcr2 & 0x3ff;
+}
+
+void sh7014_bsc_device::wcr2_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_wcr2);
+}
+
+uint16_t sh7014_bsc_device::dcr_r()
+{
+ return m_dcr & ~0x48;
+}
+
+void sh7014_bsc_device::dcr_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_dcr);
+}
+
+uint16_t sh7014_bsc_device::rtcsr_r()
+{
+ return m_rtcsr & 0x7f;
+}
+
+void sh7014_bsc_device::rtcsr_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_rtcsr);
+}
+
+uint16_t sh7014_bsc_device::rtcnt_r()
+{
+ return m_rtcnt & 0xff;
+}
+
+void sh7014_bsc_device::rtcnt_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_rtcnt);
+}
+
+uint16_t sh7014_bsc_device::rtcor_r()
+{
+ return m_rtcor & 0xff;
+}
+
+void sh7014_bsc_device::rtcor_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_rtcor);
+}
diff --git a/src/devices/cpu/sh/sh7014_bsc.h b/src/devices/cpu/sh/sh7014_bsc.h
new file mode 100644
index 00000000000..97f4b0c879c
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_bsc.h
@@ -0,0 +1,62 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 Bus State Controller
+
+***************************************************************************/
+
+#ifndef MAME_CPU_SH_SH7014_BSC_H
+#define MAME_CPU_SH_SH7014_BSC_H
+
+#pragma once
+
+
+class sh7014_bsc_device : public device_t
+{
+public:
+ sh7014_bsc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
+
+ void map(address_map &map);
+
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+private:
+ uint16_t bcr1_r();
+ void bcr1_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t bcr2_r();
+ void bcr2_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t wcr1_r();
+ void wcr1_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t wcr2_r();
+ void wcr2_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t dcr_r();
+ void dcr_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t rtcsr_r();
+ void rtcsr_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t rtcor_r();
+ void rtcor_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t rtcnt_r();
+ void rtcnt_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t m_bcr1, m_bcr2;
+ uint16_t m_wcr1, m_wcr2;
+ uint16_t m_dcr;
+ uint16_t m_rtcsr;
+ uint16_t m_rtcnt;
+ uint16_t m_rtcor;
+};
+
+
+DECLARE_DEVICE_TYPE(SH7014_BSC, sh7014_bsc_device)
+
+#endif // MAME_CPU_SH_SH7014_BSC_H
diff --git a/src/devices/cpu/sh/sh7014_dmac.cpp b/src/devices/cpu/sh/sh7014_dmac.cpp
new file mode 100644
index 00000000000..5dcb02304e6
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_dmac.cpp
@@ -0,0 +1,400 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 Direct Memory Access Controller
+
+ TODO list (not comprehensive):
+ - Channel priority
+ - External dual and single address modes
+ - DREQ, DACK, DRAK pins
+
+***************************************************************************/
+
+#include "emu.h"
+#include "sh7014_dmac.h"
+
+#define LOG_TX (1U << 1)
+
+// #define VERBOSE (LOG_GENERAL | LOG_TX)
+
+#include "logmacro.h"
+
+
+DEFINE_DEVICE_TYPE(SH7014_DMAC, sh7014_dmac_device, "sh7014dmac", "SH7014 Direct Memory Access Controller")
+DEFINE_DEVICE_TYPE(SH7014_DMAC_CHANNEL, sh7014_dmac_channel_device, "sh7014dmacchan", "SH7014 Direct Memory Access Controller Channel")
+
+
+sh7014_dmac_device::sh7014_dmac_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, SH7014_DMAC, tag, owner, clock)
+ , m_cpu(*this, finder_base::DUMMY_TAG)
+ , m_intc(*this, finder_base::DUMMY_TAG)
+ , m_chan(*this, "ch%u", 0u)
+{
+}
+
+void sh7014_dmac_device::device_start()
+{
+ save_item(NAME(m_dmaor));
+}
+
+void sh7014_dmac_device::device_reset()
+{
+ m_dmaor = 0;
+}
+
+void sh7014_dmac_device::device_add_mconfig(machine_config &config)
+{
+ SH7014_DMAC_CHANNEL(config, m_chan[0], DERIVED_CLOCK(1, 1), *this, m_cpu, m_intc,
+ 0, // channel
+ sh7014_intc_device::INT_VECTOR_DMA_CH0
+ );
+
+ SH7014_DMAC_CHANNEL(config, m_chan[1], DERIVED_CLOCK(1, 1), *this, m_cpu, m_intc,
+ 1, // channel
+ sh7014_intc_device::INT_VECTOR_DMA_CH1
+ );
+}
+
+void sh7014_dmac_device::map(address_map &map)
+{
+ map(0x00, 0x01).rw(FUNC(sh7014_dmac_device::dmaor_r), FUNC(sh7014_dmac_device::dmaor_w));
+ map(0x10, 0x1f).m(m_chan[0], FUNC(sh7014_dmac_channel_device::map));
+ map(0x20, 0x2f).m(m_chan[1], FUNC(sh7014_dmac_channel_device::map));
+}
+
+///
+
+uint16_t sh7014_dmac_device::dmaor_r()
+{
+ return m_dmaor & 7;
+}
+
+void sh7014_dmac_device::dmaor_w(uint16_t data)
+{
+ m_dmaor = (data & ~6) | (m_dmaor & data & 6);
+ m_chan[0]->dma_check();
+ m_chan[1]->dma_check();
+}
+
+bool sh7014_dmac_device::is_transfer_allowed()
+{
+ const bool is_enabled = (m_dmaor & DMAOR_DME) != 0;
+ const bool has_address_error = (m_dmaor & DMAOR_AE) != 0;
+ const bool has_nmi_flag = (m_dmaor & DMAOR_NMIF) != 0;
+ return is_enabled && !has_nmi_flag && !has_address_error;
+}
+
+int sh7014_dmac_device::is_dma_activated(int vector)
+{
+ int activated = 0;
+
+ if (!is_transfer_allowed())
+ return 0;
+
+ if (m_chan[0]->is_dma_activated(vector))
+ activated |= 1;
+ if (m_chan[1]->is_dma_activated(vector))
+ activated |= 2;
+
+ return activated;
+}
+
+
+//////////////////
+
+
+void sh7014_dmac_channel_device::map(address_map &map)
+{
+ map(0x00, 0x03).rw(FUNC(sh7014_dmac_channel_device::sar_r), FUNC(sh7014_dmac_channel_device::sar_w));
+ map(0x04, 0x07).rw(FUNC(sh7014_dmac_channel_device::dar_r), FUNC(sh7014_dmac_channel_device::dar_w));
+ map(0x08, 0x0b).rw(FUNC(sh7014_dmac_channel_device::dmatcr_r), FUNC(sh7014_dmac_channel_device::dmatcr_w));
+ map(0x0c, 0x0f).rw(FUNC(sh7014_dmac_channel_device::chcr_r), FUNC(sh7014_dmac_channel_device::chcr_w));
+}
+
+sh7014_dmac_channel_device::sh7014_dmac_channel_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, SH7014_DMAC_CHANNEL, tag, owner, clock)
+ , m_dmac(*this, finder_base::DUMMY_TAG)
+ , m_cpu(*this, finder_base::DUMMY_TAG)
+ , m_intc(*this, finder_base::DUMMY_TAG)
+ , m_notify_dma_source_cb(*this)
+{
+}
+
+void sh7014_dmac_channel_device::device_start()
+{
+ save_item(NAME(m_sar));
+ save_item(NAME(m_dar));
+ save_item(NAME(m_dmatcr));
+ save_item(NAME(m_chcr));
+
+ save_item(NAME(m_dma_timer_active));
+ save_item(NAME(m_active_dma_addr_mode_source));
+ save_item(NAME(m_active_dma_addr_mode_dest));
+ save_item(NAME(m_active_dma_unit_size));
+ save_item(NAME(m_active_dma_is_burst));
+
+ save_item(NAME(m_selected_resource));
+ save_item(NAME(m_request_source_type));
+ save_item(NAME(m_expected_irq_vector));
+
+ m_dma_current_active_timer = timer_alloc(FUNC(sh7014_dmac_channel_device::dma_timer_callback), this);
+}
+
+void sh7014_dmac_channel_device::device_reset()
+{
+ m_sar = m_dar = 0;
+ m_dmatcr = 0;
+ m_chcr = 0;
+
+ m_dma_timer_active = false;
+ m_active_dma_addr_mode_source = 0;
+ m_active_dma_addr_mode_dest = 0;
+ m_active_dma_unit_size = 1;
+ m_active_dma_is_burst = false;
+
+ m_selected_resource = RS_EXTERNAL_DUAL_ADDR;
+ m_request_source_type = RS_TYPE_EXTERNAL_DUAL;
+ m_expected_irq_vector = -1;
+
+ m_dma_current_active_timer->adjust(attotime::never);
+}
+
+///
+
+bool sh7014_dmac_channel_device::is_enabled()
+{
+ return (m_chcr & CHCR_DE) != 0;
+}
+
+uint32_t sh7014_dmac_channel_device::sar_r()
+{
+ return m_sar;
+}
+
+void sh7014_dmac_channel_device::sar_w(offs_t offset, uint32_t data, uint32_t mem_mask)
+{
+ COMBINE_DATA(&m_sar);
+}
+
+uint32_t sh7014_dmac_channel_device::dar_r()
+{
+ return m_dar;
+}
+
+void sh7014_dmac_channel_device::dar_w(offs_t offset, uint32_t data, uint32_t mem_mask)
+{
+ COMBINE_DATA(&m_dar);
+}
+
+uint32_t sh7014_dmac_channel_device::dmatcr_r()
+{
+ return m_dmatcr & 0xffff;
+}
+
+void sh7014_dmac_channel_device::dmatcr_w(offs_t offset, uint32_t data, uint32_t mem_mask)
+{
+ COMBINE_DATA(&m_dmatcr);
+ m_dmatcr &= 0xffff;
+}
+
+uint32_t sh7014_dmac_channel_device::chcr_r()
+{
+ return m_chcr & 0xbff7f;
+}
+
+void sh7014_dmac_channel_device::chcr_w(offs_t offset, uint32_t data, uint32_t mem_mask)
+{
+ COMBINE_DATA(&m_chcr);
+
+ m_selected_resource = BIT(m_chcr, 8, 4);
+ m_expected_irq_vector = -1;
+
+ switch (m_selected_resource) {
+ case RS_MTU_TGI0A:
+ m_request_source_type = RS_TYPE_INTERNAL;
+ m_expected_irq_vector = sh7014_intc_device::INT_VECTOR_MTU_TGI0A;
+ break;
+ case RS_MTU_TGI1A:
+ m_request_source_type = RS_TYPE_INTERNAL;
+ m_expected_irq_vector = sh7014_intc_device::INT_VECTOR_MTU_TGI1A;
+ break;
+ case RS_MTU_TGI2A:
+ m_request_source_type = RS_TYPE_INTERNAL;
+ m_expected_irq_vector = sh7014_intc_device::INT_VECTOR_MTU_TGI2A;
+ break;
+ case RS_AD:
+ m_request_source_type = RS_TYPE_INTERNAL;
+ m_expected_irq_vector = sh7014_intc_device::INT_VECTOR_AD;
+ break;
+ case RS_SCI_TXI0:
+ m_request_source_type = RS_TYPE_INTERNAL;
+ m_expected_irq_vector = sh7014_intc_device::INT_VECTOR_SCI_TXI0;
+ break;
+ case RS_SCI_RXI0:
+ m_request_source_type = RS_TYPE_INTERNAL;
+ m_expected_irq_vector = sh7014_intc_device::INT_VECTOR_SCI_RXI0;
+ break;
+ case RS_SCI_TXI1:
+ m_request_source_type = RS_TYPE_INTERNAL;
+ m_expected_irq_vector = sh7014_intc_device::INT_VECTOR_SCI_TXI1;
+ break;
+ case RS_SCI_RXI1:
+ m_request_source_type = RS_TYPE_INTERNAL;
+ m_expected_irq_vector = sh7014_intc_device::INT_VECTOR_SCI_RXI1;
+ break;
+
+ case RS_AUTO_REQUEST:
+ m_request_source_type = RS_TYPE_AUTO;
+ break;
+
+ case RS_EXTERNAL_DUAL_ADDR:
+ m_request_source_type = RS_TYPE_EXTERNAL_DUAL;
+ break;
+
+ case RS_EXTERNAL_SINGLE_ADDR_TO_DEV:
+ case RS_EXTERNAL_SINGLE_DEV_TO_ADDR:
+ m_request_source_type = RS_TYPE_EXTERNAL_SINGLE;
+ break;
+
+ default:
+ m_request_source_type = RS_TYPE_PROHIBITED;
+ break;
+ }
+
+ dma_check();
+}
+
+bool sh7014_dmac_channel_device::is_dma_activated(int vector)
+{
+ if (m_request_source_type != RS_TYPE_INTERNAL || m_expected_irq_vector == -1 || vector != m_expected_irq_vector)
+ return false;
+
+ if (!m_dma_timer_active)
+ dma_check();
+
+ if (!m_dma_timer_active)
+ return false;
+
+ m_dma_current_active_timer->adjust(attotime::from_ticks(2, clock()));
+
+ return true;
+}
+
+TIMER_CALLBACK_MEMBER( sh7014_dmac_channel_device::dma_timer_callback )
+{
+ if (!m_dma_timer_active)
+ return;
+
+ if (!m_dmac->is_transfer_allowed()) {
+ LOG("SH7014: DMA %d transfer aborted\n", m_channel_id);
+ m_dma_timer_active = false;
+ return;
+ }
+
+ LOGMASKED(LOG_TX, "DMAC ch %d remaining %08x size %d | source %08x dest %08x\n", m_channel_id, m_dmatcr, m_active_dma_unit_size, m_sar, m_dar);
+
+ if (m_active_dma_addr_mode_source == DMA_ADDR_MODE_DEC)
+ m_sar -= m_active_dma_unit_size;
+
+ if (m_active_dma_addr_mode_dest == DMA_ADDR_MODE_DEC)
+ m_dar -= m_active_dma_unit_size;
+
+ if (m_request_source_type == RS_TYPE_INTERNAL)
+ m_notify_dma_source_cb(m_selected_resource);
+
+ switch (m_active_dma_unit_size) {
+ case 1:
+ m_cpu->m_program->write_byte(
+ m_dar,
+ m_cpu->m_program->read_byte(m_sar)
+ );
+ break;
+ case 2:
+ m_cpu->m_program->write_word(
+ m_dar,
+ m_cpu->m_program->read_word(m_sar)
+ );
+ break;
+ case 4:
+ m_cpu->m_program->write_dword(
+ m_dar,
+ m_cpu->m_program->read_dword(m_sar)
+ );
+ break;
+ }
+
+ if (m_active_dma_addr_mode_source == DMA_ADDR_MODE_INC)
+ m_sar += m_active_dma_unit_size;
+
+ if (m_active_dma_addr_mode_dest == DMA_ADDR_MODE_INC)
+ m_dar += m_active_dma_unit_size;
+
+ m_dmatcr--;
+
+ if (m_dmatcr <= 0) {
+ LOGMASKED(LOG_TX, "SH7014: DMA %d complete\n", m_channel_id);
+
+ m_dmatcr = 0;
+ m_chcr |= CHCR_TE; // transfer end
+ m_dma_timer_active = false;
+
+ if (m_active_dma_is_burst)
+ m_cpu->resume(SUSPEND_REASON_HALT);
+
+ const auto interrupt_enable = (m_chcr & CHCR_IE) != 0;
+ if (interrupt_enable)
+ m_intc->set_interrupt(m_vector, ASSERT_LINE);
+ } else {
+ // schedule next DMA callback
+ // Internal source transfers in burst mode end on last transfer, otherwise should end after the first transfer when burst mode is off
+ // TODO: DREQ and DACK are needed for external sources instead of being handled like an auto requests
+ if (m_request_source_type != RS_TYPE_INTERNAL || (m_request_source_type == RS_TYPE_INTERNAL && m_active_dma_is_burst))
+ m_dma_current_active_timer->adjust(attotime::from_ticks(2, clock()));
+ }
+}
+
+void sh7014_dmac_channel_device::dma_check()
+{
+ if (!m_dmac->is_transfer_allowed()) {
+ if (m_dma_timer_active) {
+ LOG("SH7014: DMA %d cancelled in-flight\n", m_channel_id);
+ m_dma_current_active_timer->adjust(attotime::never);
+ m_dma_timer_active = false;
+ }
+
+ return;
+ }
+
+ if (m_dma_timer_active)
+ return;
+
+ m_active_dma_addr_mode_dest = BIT(m_chcr, 14, 2); // DM
+ m_active_dma_addr_mode_source = BIT(m_chcr, 12, 2); // SM
+ m_active_dma_unit_size = 1 << BIT(m_chcr, 3, 2); // TS
+ m_active_dma_is_burst = (m_chcr & CHCR_TM) != 0;
+
+ if (m_active_dma_addr_mode_dest == DMA_ADDR_MODE_PROHIBITED || m_active_dma_addr_mode_source == DMA_ADDR_MODE_PROHIBITED || m_active_dma_unit_size > 4) {
+ LOG("SH7014: DMA %d: bad increment values (%d, %d, %d, %04x)\n", m_channel_id, m_active_dma_addr_mode_dest, m_active_dma_addr_mode_source, m_active_dma_unit_size, m_chcr);
+ return;
+ }
+
+ if (m_dmatcr == 0)
+ m_dmatcr = 0x10000;
+
+ m_dma_timer_active = true;
+
+ LOG("SH7014: DMA %d start %x, %x, %x, %04x, %d, %d, %d, %d\n", m_channel_id, m_sar, m_dar, m_dmatcr, m_chcr, m_active_dma_addr_mode_source, m_active_dma_addr_mode_dest, m_active_dma_unit_size, m_active_dma_is_burst);
+
+ if (m_active_dma_unit_size > 1) {
+ const int mask = m_active_dma_unit_size - 1;
+ m_sar &= ~mask;
+ m_dar &= ~mask;
+ }
+
+ if (m_active_dma_is_burst)
+ m_cpu->suspend(SUSPEND_REASON_HALT, 1);
+
+ if (m_request_source_type != RS_TYPE_INTERNAL || (m_request_source_type == RS_TYPE_INTERNAL && m_active_dma_is_burst))
+ m_dma_current_active_timer->adjust(attotime::from_ticks(2, clock()));
+}
diff --git a/src/devices/cpu/sh/sh7014_dmac.h b/src/devices/cpu/sh/sh7014_dmac.h
new file mode 100644
index 00000000000..d9c92918222
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_dmac.h
@@ -0,0 +1,173 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 Direct Memory Access Controller
+
+***************************************************************************/
+
+#ifndef MAME_CPU_SH_SH7014_DMAC_H
+#define MAME_CPU_SH_SH7014_DMAC_H
+
+#pragma once
+
+#include "sh2.h"
+#include "sh7014_intc.h"
+
+DECLARE_DEVICE_TYPE(SH7014_DMAC, sh7014_dmac_device)
+DECLARE_DEVICE_TYPE(SH7014_DMAC_CHANNEL, sh7014_dmac_channel_device)
+
+class sh7014_dmac_device;
+
+class sh7014_dmac_channel_device : public device_t
+{
+public:
+ enum {
+ RS_EXTERNAL_DUAL_ADDR = 0, // External request, dual address mode
+ RS_EXTERNAL_SINGLE_ADDR_TO_DEV = 2, // External request, single address mode. External address space -> external device
+ RS_EXTERNAL_SINGLE_DEV_TO_ADDR = 3, // External request, single address mode. External device -> external address space
+ RS_AUTO_REQUEST = 4,
+ RS_MTU_TGI0A = 6,
+ RS_MTU_TGI1A = 7,
+ RS_MTU_TGI2A = 8,
+ RS_AD = 11,
+ RS_SCI_TXI0 = 12,
+ RS_SCI_RXI0 = 13,
+ RS_SCI_TXI1 = 14,
+ RS_SCI_RXI1 = 15,
+ };
+
+ sh7014_dmac_channel_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ template<typename T, typename U, typename V> sh7014_dmac_channel_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, T &&dmac, U &&cpu, V &&intc, int chan_id, int vector)
+ : sh7014_dmac_channel_device(mconfig, tag, owner, clock)
+ {
+ m_dmac.set_tag(std::forward<T>(dmac));
+ m_cpu.set_tag(std::forward<U>(cpu));
+ m_intc.set_tag(std::forward<V>(intc));
+ m_channel_id = chan_id;
+ m_vector = vector;
+ }
+
+ auto notify_dma_source_callback() { return m_notify_dma_source_cb.bind(); }
+
+ void map(address_map &map);
+
+ void dma_check();
+
+ bool is_dma_activated(int vector);
+
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+private:
+ enum {
+ RS_TYPE_EXTERNAL_DUAL = 0,
+ RS_TYPE_EXTERNAL_SINGLE,
+ RS_TYPE_AUTO,
+ RS_TYPE_INTERNAL,
+ RS_TYPE_PROHIBITED,
+ };
+
+ enum {
+ DMA_ADDR_MODE_FIXED = 0,
+ DMA_ADDR_MODE_INC,
+ DMA_ADDR_MODE_DEC,
+ DMA_ADDR_MODE_PROHIBITED,
+ };
+
+ enum {
+ CHCR_DE = 1 << 0,
+ CHCR_TE = 1 << 1,
+ CHCR_IE = 1 << 2,
+ CHCR_TM = 1 << 5,
+ };
+
+ bool is_enabled();
+
+ uint32_t sar_r();
+ void sar_w(offs_t offset, uint32_t data, uint32_t mem_mask = ~0);
+
+ uint32_t dar_r();
+ void dar_w(offs_t offset, uint32_t data, uint32_t mem_mask = ~0);
+
+ uint32_t dmatcr_r();
+ void dmatcr_w(offs_t offset, uint32_t data, uint32_t mem_mask = ~0);
+
+ uint32_t chcr_r();
+ void chcr_w(offs_t offset, uint32_t data, uint32_t mem_mask = ~0);
+
+ TIMER_CALLBACK_MEMBER( dma_timer_callback );
+
+ required_device<sh7014_dmac_device> m_dmac;
+ required_device<sh2_device> m_cpu;
+ required_device<sh7014_intc_device> m_intc;
+
+ emu_timer *m_dma_current_active_timer;
+
+ devcb_write32 m_notify_dma_source_cb;
+
+ uint32_t m_channel_id;
+ uint32_t m_vector;
+
+ uint32_t m_chcr;
+ uint32_t m_sar;
+ uint32_t m_dar;
+ int32_t m_dmatcr;
+
+ bool m_dma_timer_active;
+ uint32_t m_active_dma_addr_mode_source;
+ uint32_t m_active_dma_addr_mode_dest;
+ uint32_t m_active_dma_unit_size;
+ bool m_active_dma_is_burst;
+
+ int32_t m_request_source_type, m_selected_resource;
+ int32_t m_expected_irq_vector;
+};
+
+class sh7014_dmac_device : public device_t
+{
+public:
+ sh7014_dmac_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ template<typename T, typename U> sh7014_dmac_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, T &&cpu, U &&intc)
+ : sh7014_dmac_device(mconfig, tag, owner, clock)
+ {
+ m_cpu.set_tag(std::forward<T>(cpu));
+ m_intc.set_tag(std::forward<U>(intc));
+ }
+
+ template <typename... T> void set_notify_dma_source_callback(T &&... args) {
+ m_chan[0].lookup()->notify_dma_source_callback().set(std::forward<T>(args)...);
+ m_chan[1].lookup()->notify_dma_source_callback().set(std::forward<T>(args)...);
+ }
+
+ void map(address_map &map);
+
+ int is_dma_activated(int vector);
+ bool is_transfer_allowed();
+
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+ virtual void device_add_mconfig(machine_config &config) override;
+
+private:
+ enum {
+ DMAOR_DME = 1 << 0,
+ DMAOR_NMIF = 1 << 1,
+ DMAOR_AE = 1 << 2,
+ };
+
+ void dmaor_w(uint16_t data);
+ uint16_t dmaor_r();
+
+ required_device<sh2_device> m_cpu;
+ required_device<sh7014_intc_device> m_intc;
+ required_device_array<sh7014_dmac_channel_device, 2> m_chan;
+
+ uint16_t m_dmaor;
+};
+
+#endif // MAME_CPU_SH_SH7014_DMAC_H
diff --git a/src/devices/cpu/sh/sh7014_intc.cpp b/src/devices/cpu/sh/sh7014_intc.cpp
new file mode 100644
index 00000000000..c6f1ea521ad
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_intc.cpp
@@ -0,0 +1,313 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 Interrupt Controller
+
+***************************************************************************/
+
+#include "emu.h"
+#include "sh7014_intc.h"
+
+// #define VERBOSE (LOG_GENERAL)
+
+#include "logmacro.h"
+
+
+DEFINE_DEVICE_TYPE(SH7014_INTC, sh7014_intc_device, "sh7014intc", "SH7014 Interrupt Controller")
+
+
+sh7014_intc_device::sh7014_intc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, SH7014_INTC, tag, owner, clock)
+ , m_set_irq_cb(*this)
+{
+}
+
+void sh7014_intc_device::device_start()
+{
+ m_set_irq_cb.resolve();
+
+ save_item(NAME(m_ipra));
+ save_item(NAME(m_iprb));
+ save_item(NAME(m_iprc));
+ save_item(NAME(m_iprd));
+ save_item(NAME(m_ipre));
+ save_item(NAME(m_iprf));
+ save_item(NAME(m_iprg));
+ save_item(NAME(m_iprh));
+ save_item(NAME(m_icr));
+ save_item(NAME(m_isr));
+ save_item(NAME(m_irq_type));
+ save_item(NAME(m_nmi_input));
+ save_item(NAME(m_pending_irqs));
+ save_item(NAME(m_irq_levels));
+}
+
+void sh7014_intc_device::device_reset()
+{
+ m_ipra = 0;
+ m_iprb = 0;
+ m_iprc = 0;
+ m_iprd = 0;
+ m_ipre = 0;
+ m_iprf = 0;
+ m_iprg = 0;
+ m_iprh = 0;
+ m_icr = m_isr = 0;
+ m_nmi_input = false;
+
+ std::fill(std::begin(m_irq_type), std::end(m_irq_type), IRQ_LEVEL);
+ std::fill(std::begin(m_irq_levels), std::end(m_irq_levels), 0);
+ std::fill(std::begin(m_pending_irqs), std::end(m_pending_irqs), 0);
+
+ m_irq_levels[INT_VECTOR_NMI] = 16; // fixed value
+}
+
+void sh7014_intc_device::map(address_map &map)
+{
+ map(0x00, 0x01).rw(FUNC(sh7014_intc_device::ipra_r), FUNC(sh7014_intc_device::ipra_w));
+ map(0x02, 0x03).rw(FUNC(sh7014_intc_device::iprb_r), FUNC(sh7014_intc_device::iprb_w));
+ map(0x04, 0x05).rw(FUNC(sh7014_intc_device::iprc_r), FUNC(sh7014_intc_device::iprc_w));
+ map(0x06, 0x07).rw(FUNC(sh7014_intc_device::iprd_r), FUNC(sh7014_intc_device::iprd_w));
+ map(0x08, 0x09).rw(FUNC(sh7014_intc_device::ipre_r), FUNC(sh7014_intc_device::ipre_w));
+ map(0x0a, 0x0b).rw(FUNC(sh7014_intc_device::iprf_r), FUNC(sh7014_intc_device::iprf_w));
+ map(0x0c, 0x0d).rw(FUNC(sh7014_intc_device::iprg_r), FUNC(sh7014_intc_device::iprg_w));
+ map(0x0e, 0x0f).rw(FUNC(sh7014_intc_device::iprh_r), FUNC(sh7014_intc_device::iprh_w));
+ map(0x10, 0x11).rw(FUNC(sh7014_intc_device::icr_r), FUNC(sh7014_intc_device::icr_w));
+ map(0x12, 0x13).rw(FUNC(sh7014_intc_device::isr_r), FUNC(sh7014_intc_device::isr_w));
+}
+
+///
+
+uint16_t sh7014_intc_device::ipra_r()
+{
+ return m_ipra;
+}
+
+void sh7014_intc_device::ipra_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_ipra);
+
+ m_irq_levels[INT_VECTOR_IRQ0] = BIT(m_ipra, 12, 4);
+ m_irq_levels[INT_VECTOR_IRQ1] = BIT(m_ipra, 8, 4);
+ m_irq_levels[INT_VECTOR_IRQ2] = BIT(m_ipra, 4, 4);
+ m_irq_levels[INT_VECTOR_IRQ3] = BIT(m_ipra, 0, 4);
+
+ update_irq_state();
+}
+
+uint16_t sh7014_intc_device::iprb_r()
+{
+ return m_iprb & 0xff00;
+}
+
+void sh7014_intc_device::iprb_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_iprb);
+
+ m_irq_levels[INT_VECTOR_IRQ6] = BIT(m_iprb, 4, 4);
+ m_irq_levels[INT_VECTOR_IRQ7] = BIT(m_iprb, 0, 4);
+
+ update_irq_state();
+}
+
+uint16_t sh7014_intc_device::iprc_r()
+{
+ return m_iprc;
+}
+
+void sh7014_intc_device::iprc_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_iprc);
+
+ m_irq_levels[INT_VECTOR_DMA_CH0] = BIT(m_iprc, 12, 4);
+ m_irq_levels[INT_VECTOR_DMA_CH1] = BIT(m_iprc, 8, 4);
+
+ update_irq_state();
+}
+
+uint16_t sh7014_intc_device::iprd_r()
+{
+ return m_iprd;
+}
+
+void sh7014_intc_device::iprd_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_iprd);
+
+ m_irq_levels[INT_VECTOR_MTU_TGI0A] = m_irq_levels[INT_VECTOR_MTU_TGI0B] = m_irq_levels[INT_VECTOR_MTU_TGI0C] = m_irq_levels[INT_VECTOR_MTU_TGI0D] = BIT(m_iprd, 12, 4);
+ m_irq_levels[INT_VECTOR_MTU_TGI0V] = BIT(m_iprd, 8, 4);
+ m_irq_levels[INT_VECTOR_MTU_TGI1A] = m_irq_levels[INT_VECTOR_MTU_TGI1B] = BIT(m_iprd, 4, 4);
+ m_irq_levels[INT_VECTOR_MTU_TGI1V] = m_irq_levels[INT_VECTOR_MTU_TGI1U] = BIT(m_iprd, 0, 4);
+
+ update_irq_state();
+}
+
+uint16_t sh7014_intc_device::ipre_r()
+{
+ return m_ipre & 0xff00;
+}
+
+void sh7014_intc_device::ipre_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_ipre);
+
+ m_irq_levels[INT_VECTOR_MTU_TGI2A] = m_irq_levels[INT_VECTOR_MTU_TGI2B] = BIT(m_ipre, 12, 4);
+ m_irq_levels[INT_VECTOR_MTU_TGI2V] = m_irq_levels[INT_VECTOR_MTU_TGI2U] = BIT(m_ipre, 8, 4);
+
+ update_irq_state();
+}
+
+uint16_t sh7014_intc_device::iprf_r()
+{
+ return m_iprf & 0xff;
+}
+
+void sh7014_intc_device::iprf_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_iprf);
+
+ m_irq_levels[INT_VECTOR_SCI_ERI0] = m_irq_levels[INT_VECTOR_SCI_RXI0] = m_irq_levels[INT_VECTOR_SCI_TXI0] = m_irq_levels[INT_VECTOR_SCI_TEI0] = BIT(m_iprf, 4, 4);
+ m_irq_levels[INT_VECTOR_SCI_ERI1] = m_irq_levels[INT_VECTOR_SCI_RXI1] = m_irq_levels[INT_VECTOR_SCI_TXI1] = m_irq_levels[INT_VECTOR_SCI_TEI1] = BIT(m_iprf, 0, 4);
+
+ update_irq_state();
+}
+
+uint16_t sh7014_intc_device::iprg_r()
+{
+ return m_iprg & 0xf0ff;
+}
+
+void sh7014_intc_device::iprg_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_iprg);
+
+ m_irq_levels[INT_VECTOR_AD] = BIT(m_iprg, 12, 4);
+ m_irq_levels[INT_VECTOR_CMT_CH0] = BIT(m_iprg, 4, 4);
+ m_irq_levels[INT_VECTOR_CMT_CH1] = BIT(m_iprg, 0, 4);
+
+ update_irq_state();
+}
+
+uint16_t sh7014_intc_device::iprh_r()
+{
+ return m_iprh & 0xf000;
+}
+
+void sh7014_intc_device::iprh_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_iprh);
+
+ m_irq_levels[INT_VECTOR_WDT] = m_irq_levels[INT_VECTOR_BSC] = BIT(m_iprh, 12, 4);
+
+ update_irq_state();
+}
+
+uint16_t sh7014_intc_device::icr_r()
+{
+ uint16_t r = m_icr & 0x01f3;
+
+ if (m_nmi_input == ((m_icr & ICR_NMIE) ? ASSERT_LINE : CLEAR_LINE))
+ r |= ICR_NMIL;
+
+ return r;
+}
+
+void sh7014_intc_device::icr_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ COMBINE_DATA(&m_icr);
+
+ for (int i = 0; i < 8; i++)
+ m_irq_type[i] = BIT(m_icr, 7 - i);
+}
+
+uint16_t sh7014_intc_device::isr_r()
+{
+ return m_isr & 0xf3;
+}
+
+void sh7014_intc_device::isr_w(offs_t offset, uint16_t data, uint16_t mem_mask)
+{
+ // ICR will be set to 1 for edge detection, and it's only possible to
+ // clear flags in ISR for IRQs set to edge detection
+ auto old = m_isr;
+
+ COMBINE_DATA(&m_isr);
+
+ m_isr = (old & ~m_icr) | (old & m_isr & m_icr);
+
+ update_irq_state();
+}
+
+void sh7014_intc_device::set_interrupt(int vector, int state)
+{
+ if (vector != -1) {
+ if (state == ASSERT_LINE)
+ m_pending_irqs[vector >> 5] |= 1 << (vector & 31);
+ else if (state == CLEAR_LINE)
+ m_pending_irqs[vector >> 5] &= ~(1 << (vector & 31));
+
+ if (vector == INT_VECTOR_NMI) {
+ m_nmi_input = state == ASSERT_LINE;
+ } else if ((vector >= INT_VECTOR_IRQ0 && vector <= INT_VECTOR_IRQ3) || vector == INT_VECTOR_IRQ6 || vector == INT_VECTOR_IRQ7) {
+ const int irq = vector - INT_VECTOR_IRQ0;
+
+ if (state == ASSERT_LINE) {
+ if (m_irq_type[irq] == IRQ_LEVEL)
+ m_isr |= 1 << (7 - irq);
+ } else if (state == CLEAR_LINE) {
+ if (m_irq_type[irq] == IRQ_LEVEL)
+ m_isr &= ~(1 << (7 - irq));
+ }
+ }
+ }
+
+ update_irq_state();
+}
+
+void sh7014_intc_device::update_irq_state()
+{
+ int cur_vector = 0;
+ int cur_level = -1;
+
+ // isr has the bits in reverse order
+ m_pending_irqs[INT_VECTOR_IRQ0 >> 5] &= ~0xff;
+ m_pending_irqs[INT_VECTOR_IRQ0 >> 5] |= BIT(m_isr, 7)
+ | (BIT(m_isr, 6) << 1)
+ | (BIT(m_isr, 5) << 2)
+ | (BIT(m_isr, 4) << 3)
+ | (BIT(m_isr, 1) << 6)
+ | (BIT(m_isr, 0) << 7);
+
+ for (int i = 0; i < MAX_VECTORS / 32; i++) {
+ if (!m_pending_irqs[i])
+ continue;
+
+ for (int j = 0; j < 32; j++) {
+ if (BIT(m_pending_irqs[i], j)) {
+ const int vector = i * 32 + j;
+ const int level = m_irq_levels[vector];
+
+ if (level > cur_level) {
+ cur_vector = vector;
+ cur_level = level;
+ }
+ }
+ }
+ }
+
+ if (cur_vector == INT_VECTOR_NMI)
+ m_set_irq_cb(INPUT_LINE_NMI, cur_level, false);
+ else if ((cur_vector >= INT_VECTOR_IRQ0 && cur_vector <= INT_VECTOR_IRQ3) || cur_vector == INT_VECTOR_IRQ6 || cur_vector == INT_VECTOR_IRQ7)
+ m_set_irq_cb(cur_vector - INT_VECTOR_IRQ0, cur_level, false);
+ else if (cur_vector > INT_VECTOR_IRQ7)
+ m_set_irq_cb(cur_vector, cur_level, true);
+}
+
+void sh7014_intc_device::set_input(int inputnum, int state)
+{
+ if (inputnum == INPUT_LINE_NMI)
+ set_interrupt(INT_VECTOR_NMI, state);
+ else if ((inputnum >= INPUT_LINE_IRQ0 && inputnum <= INPUT_LINE_IRQ3) || inputnum == INPUT_LINE_IRQ6 || inputnum == INPUT_LINE_IRQ7)
+ set_interrupt(INT_VECTOR_IRQ0 + (inputnum - INPUT_LINE_IRQ0), state);
+}
diff --git a/src/devices/cpu/sh/sh7014_intc.h b/src/devices/cpu/sh/sh7014_intc.h
new file mode 100644
index 00000000000..d8d71f6dbc3
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_intc.h
@@ -0,0 +1,147 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 Interrupt Controller
+
+***************************************************************************/
+
+#ifndef MAME_CPU_SH_SH7014_INTC_H
+#define MAME_CPU_SH_SH7014_INTC_H
+
+#pragma once
+
+
+class sh7014_intc_device : public device_t
+{
+public:
+ enum {
+ INT_VECTOR_NMI = 11,
+
+ INT_VECTOR_IRQ0 = 64,
+ INT_VECTOR_IRQ1 = 65,
+ INT_VECTOR_IRQ2 = 66,
+ INT_VECTOR_IRQ3 = 67,
+ // 68 and 69 are reserved
+ INT_VECTOR_IRQ6 = 70,
+ INT_VECTOR_IRQ7 = 71,
+
+ INT_VECTOR_DMA_CH0 = 72,
+ INT_VECTOR_DMA_CH1 = 76,
+
+ INT_VECTOR_MTU_TGI0A = 88,
+ INT_VECTOR_MTU_TGI0B = 89,
+ INT_VECTOR_MTU_TGI0C = 90,
+ INT_VECTOR_MTU_TGI0D = 91,
+ INT_VECTOR_MTU_TGI0V = 92,
+
+ INT_VECTOR_MTU_TGI1A = 96,
+ INT_VECTOR_MTU_TGI1B = 97,
+ INT_VECTOR_MTU_TGI1V = 100,
+ INT_VECTOR_MTU_TGI1U = 101,
+
+ INT_VECTOR_MTU_TGI2A = 104,
+ INT_VECTOR_MTU_TGI2B = 105,
+ INT_VECTOR_MTU_TGI2V = 108,
+ INT_VECTOR_MTU_TGI2U = 109,
+
+ INT_VECTOR_SCI_ERI0 = 128,
+ INT_VECTOR_SCI_RXI0 = 129,
+ INT_VECTOR_SCI_TXI0 = 130,
+ INT_VECTOR_SCI_TEI0 = 131,
+
+ INT_VECTOR_SCI_ERI1 = 132,
+ INT_VECTOR_SCI_RXI1 = 133,
+ INT_VECTOR_SCI_TXI1 = 134,
+ INT_VECTOR_SCI_TEI1 = 135,
+
+ INT_VECTOR_AD = 136,
+
+ INT_VECTOR_CMT_CH0 = 144,
+ INT_VECTOR_CMT_CH1 = 148,
+
+ INT_VECTOR_WDT = 152,
+
+ INT_VECTOR_BSC = 153,
+ };
+
+ typedef device_delegate<void (int vector, int level, bool is_internal)> set_irq_delegate;
+
+ sh7014_intc_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
+
+ template <typename... T> void set_irq_callback(T &&... args) { m_set_irq_cb.set(std::forward<T>(args)...); }
+
+ void map(address_map &map);
+
+ void set_input(int inputnum, int state);
+
+ void set_interrupt(int vector, int state);
+
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+private:
+ enum {
+ IRQ_LEVEL,
+ IRQ_EDGE,
+ };
+
+ enum {
+ MAX_VECTORS = 256,
+ };
+
+ enum {
+ ICR_NMIE = 1 << 8,
+ ICR_NMIL = 1 << 15,
+ };
+
+ uint16_t ipra_r();
+ void ipra_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t iprb_r();
+ void iprb_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t iprc_r();
+ void iprc_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t iprd_r();
+ void iprd_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t ipre_r();
+ void ipre_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t iprf_r();
+ void iprf_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t iprg_r();
+ void iprg_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t iprh_r();
+ void iprh_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t icr_r();
+ void icr_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ uint16_t isr_r();
+ void isr_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0);
+
+ void update_irq_state();
+
+ set_irq_delegate m_set_irq_cb;
+
+ uint16_t m_ipra, m_iprb, m_iprc, m_iprd, m_ipre, m_iprf, m_iprg, m_iprh;
+ uint16_t m_icr, m_isr;
+
+ int8_t m_irq_levels[MAX_VECTORS];
+
+ uint32_t m_irq_type[8];
+ bool m_nmi_input;
+
+ uint32_t m_pending_irqs[MAX_VECTORS/32];
+};
+
+
+DECLARE_DEVICE_TYPE(SH7014_INTC, sh7014_intc_device)
+
+#endif // MAME_CPU_SH_SH7014_INTC_H
diff --git a/src/devices/cpu/sh/sh7014_mtu.cpp b/src/devices/cpu/sh/sh7014_mtu.cpp
new file mode 100644
index 00000000000..1f1f2ddf879
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_mtu.cpp
@@ -0,0 +1,571 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 Multifunction Timer Pulse Unit
+
+ TODO list (not comprehensive):
+ - Synchronized operation
+ - Cascade connection operation
+ - External clocks
+ - Timer modes (PWM mode, phase counting mode (+ decrementing counter mode), etc)
+
+***************************************************************************/
+
+#include "emu.h"
+
+#include "sh7014_mtu.h"
+
+// #define VERBOSE (LOG_GENERAL)
+
+#include "logmacro.h"
+
+
+DEFINE_DEVICE_TYPE(SH7014_MTU, sh7014_mtu_device, "sh7014mtu", "SH7014 Multifunction Timer Pulse Unit")
+DEFINE_DEVICE_TYPE(SH7014_MTU_CHANNEL, sh7014_mtu_channel_device, "sh7014mtuchan", "SH7014 Multifunction Timer Pulse Unit Channel")
+
+sh7014_mtu_device::sh7014_mtu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, SH7014_MTU, tag, owner, clock)
+ , m_intc(*this, finder_base::DUMMY_TAG)
+ , m_chan(*this, "ch%u", 0u)
+{
+}
+
+void sh7014_mtu_device::device_start()
+{
+ save_item(NAME(m_tsyr));
+}
+
+void sh7014_mtu_device::device_reset()
+{
+ m_tsyr = 0;
+}
+
+void sh7014_mtu_device::device_add_mconfig(machine_config &config)
+{
+ SH7014_MTU_CHANNEL(config, m_chan[0], DERIVED_CLOCK(1, 1), m_intc,
+ 0, // channel
+ sh7014_intc_device::INT_VECTOR_MTU_TGI0A,
+ sh7014_intc_device::INT_VECTOR_MTU_TGI0B,
+ sh7014_intc_device::INT_VECTOR_MTU_TGI0C,
+ sh7014_intc_device::INT_VECTOR_MTU_TGI0D,
+ sh7014_intc_device::INT_VECTOR_MTU_TGI0V,
+ -1
+ );
+
+ SH7014_MTU_CHANNEL(config, m_chan[1], DERIVED_CLOCK(1, 1), m_intc,
+ 1, // channel
+ sh7014_intc_device::INT_VECTOR_MTU_TGI1A,
+ sh7014_intc_device::INT_VECTOR_MTU_TGI1B,
+ -1,
+ -1,
+ sh7014_intc_device::INT_VECTOR_MTU_TGI1V,
+ sh7014_intc_device::INT_VECTOR_MTU_TGI1U
+ );
+
+ SH7014_MTU_CHANNEL(config, m_chan[2], DERIVED_CLOCK(1, 1), m_intc,
+ 2, // channel
+ sh7014_intc_device::INT_VECTOR_MTU_TGI2A,
+ sh7014_intc_device::INT_VECTOR_MTU_TGI2B,
+ -1,
+ -1,
+ sh7014_intc_device::INT_VECTOR_MTU_TGI2V,
+ sh7014_intc_device::INT_VECTOR_MTU_TGI2U
+ );
+}
+
+void sh7014_mtu_device::map(address_map &map)
+{
+ map(0x00, 0x00).rw(FUNC(sh7014_mtu_device::tstr_r), FUNC(sh7014_mtu_device::tstr_w));
+ map(0x01, 0x01).rw(FUNC(sh7014_mtu_device::tsyr_r), FUNC(sh7014_mtu_device::tsyr_w));
+
+ map(0x20, 0x3f).m(m_chan[0], FUNC(sh7014_mtu_channel_device::map_chan0));
+ map(0x40, 0x5f).m(m_chan[1], FUNC(sh7014_mtu_channel_device::map_chan1_2));
+ map(0x60, 0x7f).m(m_chan[2], FUNC(sh7014_mtu_channel_device::map_chan1_2));
+}
+
+///
+
+uint8_t sh7014_mtu_device::tstr_r()
+{
+ return m_chan[0]->is_enabled()
+ | (m_chan[1]->is_enabled() << 1)
+ | (m_chan[2]->is_enabled() << 2);
+}
+
+void sh7014_mtu_device::tstr_w(uint8_t data)
+{
+ LOG("%s: tstr_w %02x\n", machine().describe_context().c_str(), data);
+
+ m_chan[0]->set_enable((data & TSTR_CST0) != 0);
+ m_chan[1]->set_enable((data & TSTR_CST1) != 0);
+ m_chan[2]->set_enable((data & TSTR_CST2) != 0);
+}
+
+uint8_t sh7014_mtu_device::tsyr_r()
+{
+ return m_tsyr & (TSYR_SYNC0 | TSYR_SYNC1 | TSYR_SYNC2);
+}
+
+void sh7014_mtu_device::tsyr_w(uint8_t data)
+{
+ LOG("%s: tsyr_w %02x\n", machine().describe_context().c_str(), data);
+
+ m_tsyr = data;
+}
+
+
+//////////////////
+
+sh7014_mtu_channel_device::sh7014_mtu_channel_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, SH7014_MTU_CHANNEL, tag, owner, clock)
+ , m_intc(*this, finder_base::DUMMY_TAG)
+ , m_timer(nullptr)
+{
+}
+
+void sh7014_mtu_channel_device::device_start()
+{
+ save_item(NAME(m_tcr));
+ save_item(NAME(m_tmdr));
+ save_item(NAME(m_tiorh));
+ save_item(NAME(m_tiorl));
+ save_item(NAME(m_tier));
+ save_item(NAME(m_tsr));
+ save_item(NAME(m_tcnt));
+ save_item(NAME(m_tgr));
+
+ save_item(NAME(m_last_clock_update));
+ save_item(NAME(m_clock_type));
+ save_item(NAME(m_clock_divider));
+ save_item(NAME(m_channel_active));
+ save_item(NAME(m_phase));
+ save_item(NAME(m_counter_cycle));
+ save_item(NAME(m_tgr_clearing));
+
+ m_timer = timer_alloc(FUNC(sh7014_mtu_channel_device::timer_callback), this);
+}
+
+void sh7014_mtu_channel_device::device_reset()
+{
+ m_tcr = 0;
+ m_tmdr = 0;
+ m_tier = 0;
+ m_tsr = TSR_TCFD;
+ m_tiorh = m_tiorl = 0;
+ std::fill(std::begin(m_tgr), std::end(m_tgr), 0);
+
+ m_tcnt = 0;
+ m_last_clock_update = 0;
+ m_clock_type = INPUT_INTERNAL;
+ m_clock_divider = DIV_1;
+ m_channel_active = false;
+ m_phase = 0;
+ m_counter_cycle = 0x10000;
+ m_tgr_clearing = TGR_CLEAR_NONE;
+
+ m_timer->adjust(attotime::never);
+}
+
+void sh7014_mtu_channel_device::map_chan0(address_map &map)
+{
+ map(0x00, 0x00).rw(FUNC(sh7014_mtu_channel_device::tcr_r), FUNC(sh7014_mtu_channel_device::tcr_w));
+ map(0x01, 0x01).rw(FUNC(sh7014_mtu_channel_device::tmdr_r), FUNC(sh7014_mtu_channel_device::tmdr_w));
+ map(0x02, 0x02).rw(FUNC(sh7014_mtu_channel_device::tiorh_r), FUNC(sh7014_mtu_channel_device::tiorh_w));
+ map(0x03, 0x03).rw(FUNC(sh7014_mtu_channel_device::tiorl_r), FUNC(sh7014_mtu_channel_device::tiorl_w));
+ map(0x04, 0x04).rw(FUNC(sh7014_mtu_channel_device::tier_r), FUNC(sh7014_mtu_channel_device::tier_w));
+ map(0x05, 0x05).rw(FUNC(sh7014_mtu_channel_device::tsr_r), FUNC(sh7014_mtu_channel_device::tsr_w));
+ map(0x06, 0x07).rw(FUNC(sh7014_mtu_channel_device::tcnt_r), FUNC(sh7014_mtu_channel_device::tcnt_w));
+ map(0x08, 0x09).rw(FUNC(sh7014_mtu_channel_device::tgra_r), FUNC(sh7014_mtu_channel_device::tgra_w));
+ map(0x0a, 0x0b).rw(FUNC(sh7014_mtu_channel_device::tgrb_r), FUNC(sh7014_mtu_channel_device::tgrb_w));
+ map(0x0c, 0x0d).rw(FUNC(sh7014_mtu_channel_device::tgrc_r), FUNC(sh7014_mtu_channel_device::tgrc_w));
+ map(0x0e, 0x0f).rw(FUNC(sh7014_mtu_channel_device::tgrd_r), FUNC(sh7014_mtu_channel_device::tgrd_w));
+}
+
+void sh7014_mtu_channel_device::map_chan1_2(address_map &map)
+{
+ map(0x00, 0x00).rw(FUNC(sh7014_mtu_channel_device::tcr_r), FUNC(sh7014_mtu_channel_device::tcr_w));
+ map(0x01, 0x01).rw(FUNC(sh7014_mtu_channel_device::tmdr_r), FUNC(sh7014_mtu_channel_device::tmdr_w));
+ map(0x02, 0x02).rw(FUNC(sh7014_mtu_channel_device::tiorh_r), FUNC(sh7014_mtu_channel_device::tiorh_w));
+ map(0x04, 0x04).rw(FUNC(sh7014_mtu_channel_device::tier_r), FUNC(sh7014_mtu_channel_device::tier_w));
+ map(0x05, 0x05).rw(FUNC(sh7014_mtu_channel_device::tsr_r), FUNC(sh7014_mtu_channel_device::tsr_w));
+ map(0x06, 0x07).rw(FUNC(sh7014_mtu_channel_device::tcnt_r), FUNC(sh7014_mtu_channel_device::tcnt_w));
+ map(0x08, 0x09).rw(FUNC(sh7014_mtu_channel_device::tgra_r), FUNC(sh7014_mtu_channel_device::tgra_w));
+ map(0x0a, 0x0b).rw(FUNC(sh7014_mtu_channel_device::tgrb_r), FUNC(sh7014_mtu_channel_device::tgrb_w));
+}
+
+///
+
+TIMER_CALLBACK_MEMBER( sh7014_mtu_channel_device::timer_callback )
+{
+ update_counter();
+ schedule_next_event();
+}
+
+void sh7014_mtu_channel_device::set_enable(bool enabled)
+{
+ update_counter();
+
+ m_channel_active = enabled;
+
+ schedule_next_event();
+}
+
+uint8_t sh7014_mtu_channel_device::tcr_r()
+{
+ uint8_t r = m_tcr;
+
+ if (m_channel_id > 0)
+ r &= 0x7f;
+
+ return r;
+}
+
+void sh7014_mtu_channel_device::tcr_w(uint8_t data)
+{
+ LOG("%s: tcr_w<%d> %02x\n", machine().describe_context().c_str(), m_channel_id, data);
+
+ update_counter();
+
+ m_tcr = data;
+
+ constexpr uint32_t divider_type[3][8] = {
+ {INPUT_INTERNAL, INPUT_INTERNAL, INPUT_INTERNAL, INPUT_INTERNAL, INPUT_A, INPUT_B, INPUT_C, INPUT_D},
+ {INPUT_INTERNAL, INPUT_INTERNAL, INPUT_INTERNAL, INPUT_INTERNAL, INPUT_A, INPUT_B, INPUT_INTERNAL, INPUT_TCNT2},
+ {INPUT_INTERNAL, INPUT_INTERNAL, INPUT_INTERNAL, INPUT_INTERNAL, INPUT_A, INPUT_B, INPUT_C, INPUT_INTERNAL},
+ };
+
+ const int prescaler = BIT(m_tcr, 0, 3);
+
+ m_clock_type = divider_type[m_channel_id][prescaler];
+
+ if (m_clock_type == INPUT_INTERNAL) {
+ constexpr int32_t dividers[3][8] = {
+ {DIV_1, DIV_4, DIV_16, DIV_64, -1, -1, -1, -1},
+ {DIV_1, DIV_4, DIV_16, DIV_64, -1, -1, DIV_256, -1},
+ {DIV_1, DIV_4, DIV_16, DIV_64, -1, -1, -1, DIV_1024},
+ };
+
+ m_clock_divider = dividers[m_channel_id][prescaler];
+ } else {
+ m_clock_divider = DIV_1;
+ }
+
+ const int clock_edge = BIT(m_tcr, 3, 2);
+ if (m_clock_divider < DIV_4) {
+ m_phase = 0;
+ } else {
+ switch (clock_edge) {
+ case 0:
+ m_phase = 0;
+ break;
+ case 1:
+ m_phase = 1 << (m_clock_divider - 1);
+ break;
+ case 2:
+ // If count on both rising and falling edges, input clock frequency becomes 1/2
+ m_phase = 0;
+ m_clock_divider--;
+ break;
+ }
+ }
+
+ const int counting_clear = BIT(m_tcr, 5, m_channel_id == 0 ? 3 : 2);
+ if (counting_clear == 3 || (m_channel_id == 0 && counting_clear == 7))
+ m_tgr_clearing = TGR_CLEAR_SYNC;
+ else if (counting_clear >= 1 && counting_clear <= 2)
+ m_tgr_clearing = counting_clear - 1; // TGRA, TGRB
+ else if (m_channel_id == 0 && counting_clear >= 5 && counting_clear <= 6)
+ m_tgr_clearing = counting_clear - 3; // TGRC, TGRD (ch 0 only)
+ else
+ m_tgr_clearing = TGR_CLEAR_NONE; // 0, 4, and anything else
+
+ schedule_next_event();
+}
+
+uint8_t sh7014_mtu_channel_device::tmdr_r()
+{
+ uint8_t r = m_tmdr;
+
+ if (m_channel_id > 0)
+ r &= 0x0f;
+
+ return r | 0xc0;
+}
+
+void sh7014_mtu_channel_device::tmdr_w(uint8_t data)
+{
+ LOG("%s: tmdr_w<%d> %02x\n", machine().describe_context().c_str(), m_channel_id, data);
+
+ m_tmdr = data;
+}
+
+uint8_t sh7014_mtu_channel_device::tiorh_r()
+{
+ return m_tiorh;
+}
+
+void sh7014_mtu_channel_device::tiorh_w(uint8_t data)
+{
+ LOG("%s: tiorh_w<%d> %02x\n", machine().describe_context().c_str(), m_channel_id, data);
+
+ const bool trga_is_output_compare_register = BIT(data, 3) == 0;
+ const bool trga_was_output_compare_register = BIT(m_tiorh, 3) == 0;
+ if (trga_is_output_compare_register) {
+ const auto tgra_mode = BIT(data, 2);
+ const auto old_tgra_mode = BIT(m_tiorh, 2);
+
+ if ((tgra_mode != old_tgra_mode) || (trga_is_output_compare_register != trga_was_output_compare_register)) {
+ const auto initial_output = tgra_mode ? TSR_TGFA : 0;
+ m_tsr = (m_tsr & ~TSR_TGFA) | initial_output;
+ }
+ }
+
+ const bool trgb_is_output_compare_register = BIT(data, 7) == 0;
+ const bool trgb_was_output_compare_register = BIT(m_tiorh, 7) == 0;
+ if (trgb_is_output_compare_register) {
+ const auto tgrb_mode = BIT(data, 6);
+ const auto old_tgrb_mode = BIT(m_tiorh, 6);
+
+ if ((tgrb_mode != old_tgrb_mode) || (trgb_is_output_compare_register != trgb_was_output_compare_register)) {
+ const auto initial_output = tgrb_mode ? TSR_TGFB : 0;
+ m_tsr = (m_tsr & ~TSR_TGFB) | initial_output;
+ }
+ }
+
+ m_tiorh = data;
+}
+
+uint8_t sh7014_mtu_channel_device::tiorl_r()
+{
+ if (m_channel_id > 0)
+ return 0;
+
+ return m_tiorl;
+}
+
+void sh7014_mtu_channel_device::tiorl_w(uint8_t data)
+{
+ LOG("%s: tiorl_w<%d> %02x\n", machine().describe_context().c_str(), m_channel_id, data);
+
+ if (m_channel_id > 0)
+ return;
+
+ const bool trgc_is_output_compare_register = BIT(data, 3) == 0;
+ const bool trgc_was_output_compare_register = BIT(m_tiorl, 3) == 0;
+ if (trgc_is_output_compare_register) {
+ const auto tgrc_mode = BIT(data, 2);
+ const auto old_tgrc_mode = BIT(m_tiorl, 2);
+
+ if ((tgrc_mode != old_tgrc_mode) || (trgc_is_output_compare_register != trgc_was_output_compare_register)) {
+ const auto initial_output = tgrc_mode ? TSR_TGFC : 0;
+ m_tsr = (m_tsr & ~TSR_TGFC) | initial_output;
+ }
+ }
+
+ const bool trgd_is_output_compare_register = BIT(data, 7) == 0;
+ const bool trgd_was_output_compare_register = BIT(m_tiorl, 7) == 0;
+ if (trgd_is_output_compare_register) {
+ const auto tgrd_mode = BIT(data, 6);
+ const auto old_tgrd_mode = BIT(m_tiorl, 6);
+
+ if ((tgrd_mode != old_tgrd_mode) || (trgd_is_output_compare_register != trgd_was_output_compare_register)) {
+ const auto initial_output = tgrd_mode ? TSR_TGFD : 0;
+ m_tsr = (m_tsr & ~TSR_TGFD) | initial_output;
+ }
+ }
+
+ m_tiorl = data;
+}
+
+uint8_t sh7014_mtu_channel_device::tier_r()
+{
+ return m_tier | 0x40;
+}
+
+void sh7014_mtu_channel_device::tier_w(uint8_t data)
+{
+ LOG("%s: tier_w<%d> %02x\n", machine().describe_context().c_str(), m_channel_id, data);
+
+ m_tier = data;
+}
+
+uint8_t sh7014_mtu_channel_device::tsr_r()
+{
+ uint8_t r;
+
+ if (m_channel_id == 0)
+ r = (m_tsr & (TSR_TGFA | TSR_TGFB | TSR_TGFC | TSR_TGFD | TSR_TCFV)) | TSR_TCFD;
+ else
+ r = m_tsr & (TSR_TGFA | TSR_TGFB | TSR_TCFV | TSR_TCFU | TSR_TCFD);
+
+ return r | (1 << 6);
+}
+
+void sh7014_mtu_channel_device::tsr_w(uint8_t data)
+{
+ LOG("%s: tsr_w<%d> %02x\n", machine().describe_context().c_str(), m_channel_id, data);
+
+ if (m_channel_id == 0) {
+ const uint8_t mask = TSR_TGFA | TSR_TGFB | TSR_TGFC | TSR_TGFD | TSR_TCFV;
+ m_tsr = (data & ~mask) | (m_tsr & data & mask);
+ } else {
+ const uint8_t mask = TSR_TGFA | TSR_TGFB | TSR_TCFV | TSR_TCFU;
+ m_tsr = (data & ~mask) | (m_tsr & data & mask) | (m_tsr & TSR_TCFD);
+ }
+}
+
+uint16_t sh7014_mtu_channel_device::tcnt_r()
+{
+ update_counter();
+ return m_tcnt;
+}
+
+void sh7014_mtu_channel_device::tcnt_w(uint16_t data)
+{
+ LOG("%s: tcnt_w<%d> %04x -> %04x\n", machine().describe_context().c_str(), m_channel_id, m_tcnt, data);
+
+ m_tcnt = data;
+}
+
+uint16_t sh7014_mtu_channel_device::tgra_r()
+{
+ return m_tgr[0];
+}
+
+void sh7014_mtu_channel_device::tgra_w(uint16_t data)
+{
+ LOG("%s: tgra_w<%d> %04x -> %04x\n", machine().describe_context().c_str(), m_channel_id, m_tgr[0], data);
+
+ m_tgr[0] = data;
+}
+
+uint16_t sh7014_mtu_channel_device::tgrb_r()
+{
+ return m_tgr[1];
+}
+
+void sh7014_mtu_channel_device::tgrb_w(uint16_t data)
+{
+ LOG("%s: tgrb_w<%d> %04x -> %04x\n", machine().describe_context().c_str(), m_channel_id, m_tgr[1], data);
+
+ m_tgr[1] = data;
+}
+
+uint16_t sh7014_mtu_channel_device::tgrc_r()
+{
+ return m_tgr[2];
+}
+
+void sh7014_mtu_channel_device::tgrc_w(uint16_t data)
+{
+ LOG("%s: tgrc_w<%d> %04x -> %04x\n", machine().describe_context().c_str(), m_channel_id, m_tgr[2], data);
+
+ m_tgr[2] = data;
+}
+
+uint16_t sh7014_mtu_channel_device::tgrd_r()
+{
+ return m_tgr[3];
+}
+
+void sh7014_mtu_channel_device::tgrd_w(uint16_t data)
+{
+ LOG("%s: tgrd_w<%d> %04x -> %04x\n", machine().describe_context().c_str(), m_channel_id, m_tgr[3], data);
+
+ m_tgr[3] = data;
+}
+
+void sh7014_mtu_channel_device::update_counter()
+{
+ if (m_clock_type != INPUT_INTERNAL)
+ return;
+
+ uint64_t cur_time = machine().time().as_ticks(clock());
+
+ if (!m_channel_active) {
+ m_last_clock_update = cur_time;
+ return;
+ }
+
+ if (m_last_clock_update == cur_time)
+ return;
+
+ uint64_t base_time = (m_last_clock_update + m_phase) >> m_clock_divider;
+ uint64_t new_time = (cur_time + m_phase) >> m_clock_divider;
+ int tt = m_tcnt + (new_time - base_time);
+ m_tcnt = tt % m_counter_cycle;
+
+ for (int i = 0; i < m_tgr_count; i++) {
+ if (!BIT(m_tsr, i) && BIT(m_tier, i) && tt >= m_tgr[i]) {
+ m_tsr |= 1 << i;
+ m_intc->set_interrupt(m_vectors[i], ASSERT_LINE);
+ }
+ }
+
+ if (!(m_tsr & TSR_TCFV) && tt >= 0x10000) {
+ // Overflowed
+ m_tsr |= TSR_TCFV;
+
+ if (m_tier & TIER_TCIEV)
+ m_intc->set_interrupt(m_vectors[VECTOR_TCIV], ASSERT_LINE);
+ } else if (m_channel_id == 0 && !(m_tsr & TSR_TCFU) && tt < 0) {
+ // Underflowed, only usable on channel 0
+ m_tsr |= TSR_TCFU;
+
+ if (m_tier & TIER_TCIEU)
+ m_intc->set_interrupt(m_vectors[VECTOR_TCIU], ASSERT_LINE);
+ }
+
+ m_last_clock_update = cur_time;
+}
+
+void sh7014_mtu_channel_device::schedule_next_event()
+{
+ m_timer->adjust(attotime::never);
+
+ if (!m_channel_active || m_clock_type != INPUT_INTERNAL) {
+ return;
+ }
+
+ uint32_t event_delay = 0xffffffff;
+ if (m_tgr_clearing >= 0 && m_tgr[m_tgr_clearing]) {
+ m_counter_cycle = m_tgr[m_tgr_clearing];
+ } else {
+ m_counter_cycle = 0x10000;
+
+ if (m_tier & TIER_TCIEV) {
+ // Try to schedule next event for when the overflow should happen if overflow interrupt is enabled
+ event_delay = m_counter_cycle - m_tcnt;
+
+ if (event_delay == 0)
+ event_delay = m_counter_cycle;
+ }
+ }
+
+ // If one of the comparison register interrupts is enabled then set the next event time to be when the next interrupt should happen
+ for (int i = 0; i < m_tgr_count; i++) {
+ if (BIT(m_tier, i)) {
+ uint32_t new_delay = 0xffffffff;
+
+ if (m_tgr[i] > m_tcnt) {
+ if (m_tcnt >= m_counter_cycle || m_tgr[i] <= m_counter_cycle)
+ new_delay = m_tgr[i] - m_tcnt;
+ } else if (m_tgr[i] <= m_counter_cycle) {
+ if (m_tcnt < m_counter_cycle)
+ new_delay = (m_counter_cycle - m_tcnt) + m_tgr[i];
+ else
+ new_delay = (0x10000 - m_tcnt) + m_tgr[i];
+ }
+
+ if (event_delay > new_delay)
+ event_delay = new_delay;
+ }
+ }
+
+ if (event_delay != 0xffffffff) {
+ const uint32_t next_event = (((((1ULL << m_clock_divider) - m_phase) >> m_clock_divider) + event_delay - 1) << m_clock_divider) + m_phase;
+ m_timer->adjust(attotime::from_ticks(next_event, clock()));
+
+ }
+}
diff --git a/src/devices/cpu/sh/sh7014_mtu.h b/src/devices/cpu/sh/sh7014_mtu.h
new file mode 100644
index 00000000000..c9c9e784390
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_mtu.h
@@ -0,0 +1,211 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 Multifunction Timer Pulse Unit
+
+***************************************************************************/
+
+#ifndef MAME_CPU_SH_SH7014_MTU_H
+#define MAME_CPU_SH_SH7014_MTU_H
+
+#pragma once
+
+#include "sh7014_intc.h"
+
+DECLARE_DEVICE_TYPE(SH7014_MTU, sh7014_mtu_device)
+DECLARE_DEVICE_TYPE(SH7014_MTU_CHANNEL, sh7014_mtu_channel_device)
+
+
+class sh7014_mtu_device : public device_t
+{
+public:
+ sh7014_mtu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ template<typename T> sh7014_mtu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, T &&intc)
+ : sh7014_mtu_device(mconfig, tag, owner, clock)
+ {
+ m_intc.set_tag(std::forward<T>(intc));
+ }
+
+ void map(address_map &map);
+
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+ virtual void device_add_mconfig(machine_config &config) override;
+
+private:
+ enum {
+ TSTR_CST0 = 1 << 0,
+ TSTR_CST1 = 1 << 1,
+ TSTR_CST2 = 1 << 2,
+
+ TSYR_SYNC0 = 1 << 0,
+ TSYR_SYNC1 = 1 << 1,
+ TSYR_SYNC2 = 1 << 2,
+ };
+
+ uint8_t tstr_r();
+ void tstr_w(uint8_t data);
+
+ uint8_t tsyr_r();
+ void tsyr_w(uint8_t data);
+
+ required_device<sh7014_intc_device> m_intc;
+ required_device_array<sh7014_mtu_channel_device, 3> m_chan;
+
+ uint8_t m_tsyr;
+};
+
+
+class sh7014_mtu_channel_device : public device_t
+{
+public:
+ sh7014_mtu_channel_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ template<typename T> sh7014_mtu_channel_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, T &&intc, int chan_id, int32_t vector_tgia, int32_t vector_tgib, int32_t vector_tgic, int32_t vector_tgid, int32_t vector_tgiv, int32_t vector_tgiu)
+ : sh7014_mtu_channel_device(mconfig, tag, owner, clock)
+ {
+ m_intc.set_tag(std::forward<T>(intc));
+ m_channel_id = chan_id;
+ m_vectors[VECTOR_TGIA] = vector_tgia;
+ m_vectors[VECTOR_TGIB] = vector_tgib;
+ m_vectors[VECTOR_TGIC] = vector_tgic;
+ m_vectors[VECTOR_TGID] = vector_tgid;
+ m_vectors[VECTOR_TCIV] = vector_tgiv;
+ m_vectors[VECTOR_TCIU] = vector_tgiu;
+ m_tgr_count = chan_id == 0 ? 4 : 2;
+ }
+
+ void map_chan0(address_map &map);
+ void map_chan1_2(address_map &map);
+
+ void set_enable(bool enabled);
+ bool is_enabled() { return m_channel_active; }
+
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+private:
+ enum {
+ INPUT_INTERNAL = 0,
+ INPUT_A,
+ INPUT_B,
+ INPUT_C,
+ INPUT_D,
+ INPUT_TCNT2
+ };
+
+ enum {
+ DIV_1 = 0,
+ DIV_2,
+ DIV_4,
+ DIV_8,
+ DIV_16,
+ DIV_32,
+ DIV_64,
+ DIV_128,
+ DIV_256,
+ DIV_512,
+ DIV_1024
+ };
+
+ enum {
+ TGR_CLEAR_NONE = -1,
+ TGR_CLEAR_SYNC = -2
+ };
+
+ enum {
+ VECTOR_TGIA = 0,
+ VECTOR_TGIB = 1,
+ VECTOR_TGIC = 2,
+ VECTOR_TGID = 3,
+ VECTOR_TCIV = 4,
+ VECTOR_TCIU = 5
+ };
+
+ enum {
+
+ TSR_TGFA = 1 << 0,
+ TSR_TGFB = 1 << 1,
+ TSR_TGFC = 1 << 2, // ch 0 only
+ TSR_TGFD = 1 << 3, // ch 0 only
+ TSR_TCFV = 1 << 4,
+ TSR_TCFU = 1 << 5, // ch 1/2 only
+ TSR_TCFD = 1 << 7, // ch 1/2 only
+
+ TIER_TGIEA = 1 << 0,
+ TIER_TGIEB = 1 << 1,
+ TIER_TGIEC = 1 << 2, // ch 0 only
+ TIER_TGIED = 1 << 3, // ch 0 only
+ TIER_TCIEV = 1 << 4,
+ TIER_TCIEU = 1 << 5, // ch 1/2 only
+ TIER_TTGE = 1 << 7,
+ };
+
+ uint8_t tcr_r();
+ void tcr_w(uint8_t data);
+
+ uint8_t tmdr_r();
+ void tmdr_w(uint8_t data);
+
+ uint8_t tiorh_r();
+ void tiorh_w(uint8_t data);
+
+ uint8_t tiorl_r();
+ void tiorl_w(uint8_t data);
+
+ uint8_t tier_r();
+ void tier_w(uint8_t data);
+
+ uint8_t tsr_r();
+ void tsr_w(uint8_t data);
+
+ uint16_t tcnt_r();
+ void tcnt_w(uint16_t data);
+
+ uint16_t tgra_r();
+ void tgra_w(uint16_t data);
+
+ uint16_t tgrb_r();
+ void tgrb_w(uint16_t data);
+
+ uint16_t tgrc_r();
+ void tgrc_w(uint16_t data);
+
+ uint16_t tgrd_r();
+ void tgrd_w(uint16_t data);
+
+ void update_counter();
+ void schedule_next_event();
+
+ TIMER_CALLBACK_MEMBER( timer_callback );
+
+ required_device<sh7014_intc_device> m_intc;
+
+ emu_timer *m_timer;
+
+ uint32_t m_channel_id;
+ int32_t m_vectors[6];
+ uint32_t m_tgr_count;
+
+ uint8_t m_tcr;
+ uint8_t m_tmdr;
+ uint8_t m_tiorh, m_tiorl;
+ uint8_t m_tier;
+ uint8_t m_tsr;
+ uint16_t m_tgr[4];
+ uint16_t m_tcnt;
+
+ uint64_t m_last_clock_update;
+ uint32_t m_clock_type, m_clock_divider;
+ uint32_t m_phase;
+ uint32_t m_counter_cycle;
+ int32_t m_tgr_clearing;
+
+ bool m_channel_active;
+};
+
+#endif // MAME_CPU_SH_SH7014_MTU_H
diff --git a/src/devices/cpu/sh/sh7014_port.cpp b/src/devices/cpu/sh/sh7014_port.cpp
new file mode 100644
index 00000000000..96ed0701648
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_port.cpp
@@ -0,0 +1,243 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 I/O Port
+
+ TODO list (not comprehensive):
+ - Special functions specified by control registers are not implemented
+
+***************************************************************************/
+
+#include "emu.h"
+#include "sh7014_port.h"
+
+#define LOG_READ (1U << 1)
+#define LOG_WRITE (1U << 2)
+
+// #define VERBOSE (LOG_GENERAL | LOG_READ | LOG_WRITE)
+
+#include "logmacro.h"
+
+
+DEFINE_DEVICE_TYPE(SH7014_PORT, sh7014_port_device, "sh7014port", "SH7014 I/O Port")
+
+
+sh7014_port_device::sh7014_port_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, SH7014_PORT, tag, owner, clock)
+ , m_port_a_read_cb(*this, 0)
+ , m_port_a_write_cb(*this)
+ , m_port_b_read_cb(*this, 0)
+ , m_port_b_write_cb(*this)
+ , m_port_e_read_cb(*this, 0)
+ , m_port_e_write_cb(*this)
+ , m_port_f_read_cb(*this, 0)
+{
+}
+
+void sh7014_port_device::device_start()
+{
+ save_item(NAME(m_padr));
+ save_item(NAME(m_paior));
+ save_item(NAME(m_pacr1));
+ save_item(NAME(m_pacr2));
+
+ save_item(NAME(m_pbdr));
+ save_item(NAME(m_pbior));
+ save_item(NAME(m_pbcr1));
+ save_item(NAME(m_pbcr2));
+
+ save_item(NAME(m_pedr));
+ save_item(NAME(m_peior));
+ save_item(NAME(m_pecr1));
+ save_item(NAME(m_pecr2));
+
+ save_item(NAME(m_pfdr));
+}
+
+void sh7014_port_device::device_reset()
+{
+ m_padr = 0;
+ m_paior = 0;
+ m_pacr1 = m_pacr2 = 0;
+
+ m_pbdr = 0;
+ m_pbior = 0;
+ m_pbcr1 = m_pbcr2 = 0;
+
+ m_pedr = 0;
+ m_peior = 0;
+ m_pecr1 = m_pecr2 = 0;
+
+ m_pfdr = 0;
+}
+
+///
+
+uint16_t sh7014_port_device::padrl_r()
+{
+ uint16_t pins = m_port_a_read_cb();
+ uint16_t r = ((m_padr & m_paior) | (pins & ~m_paior)) & 0x83ff;
+ LOGMASKED(LOG_READ, "padrl_r %04x (pins: %04x %04x)\n", r, m_paior, pins);
+ return r;
+}
+
+void sh7014_port_device::padrl_w(uint16_t data)
+{
+ m_padr = data & 0x83ff;
+ LOGMASKED(LOG_WRITE, "padrl_w %04x\n", m_padr);
+
+ if (m_paior)
+ m_port_a_write_cb(m_padr & m_paior);
+}
+
+uint16_t sh7014_port_device::paiorl_r()
+{
+ LOGMASKED(LOG_READ, "paiorl_r %04x\n", m_paior);
+ return m_paior;
+}
+
+void sh7014_port_device::paiorl_w(uint16_t data)
+{
+ m_paior = data & 0x83ff;
+ LOGMASKED(LOG_WRITE, "paiorl_w %04x\n", m_paior);
+}
+
+uint16_t sh7014_port_device::pacrl1_r()
+{
+ LOGMASKED(LOG_READ, "m_pacr1 %04x\n", m_pacr1);
+ return m_pacr1;
+}
+
+void sh7014_port_device::pacrl1_w(uint16_t data)
+{
+ m_pacr1 = data & 0x400f;
+ LOGMASKED(LOG_WRITE, "pacrl1_w %04x\n", m_pacr1);
+}
+
+uint16_t sh7014_port_device::pacrl2_r()
+{
+ LOGMASKED(LOG_READ, "pacrl2_r %04x\n", m_pacr2);
+ return m_pacr2;
+}
+
+void sh7014_port_device::pacrl2_w(uint16_t data)
+{
+ m_pacr2 = data & 0xfd75;
+ LOGMASKED(LOG_WRITE, "pacrl2_w %04x\n", m_pacr2);
+}
+
+uint16_t sh7014_port_device::pbdr_r()
+{
+ uint16_t pins = m_port_b_read_cb();
+ uint16_t r = ((m_pbdr & m_pbior) | (pins & ~m_pbior)) & 0x3ff;
+ LOGMASKED(LOG_READ, "pbdr_r %04x (pins: %04x %04x)\n", r, m_pbior, pins);
+ return r;
+}
+
+void sh7014_port_device::pbdr_w(uint16_t data)
+{
+ m_pbdr = data;
+ LOGMASKED(LOG_WRITE, "pbdr_w %04x\n", m_pbdr);
+
+ if (m_pbior)
+ m_port_b_write_cb(m_pbdr & m_pbior);
+}
+
+uint16_t sh7014_port_device::pbior_r()
+{
+ LOGMASKED(LOG_READ, "pbior_r %04x\n", m_pbior);
+ return m_pbior;
+}
+
+void sh7014_port_device::pbior_w(uint16_t data)
+{
+ // For SH7014 specifically, bits 0 and 1 are read only
+ m_pbior = data & 0x3fc;
+ LOGMASKED(LOG_WRITE, "pbior_w %04x\n", m_pbior);
+}
+
+uint16_t sh7014_port_device::pbcr1_r()
+{
+ LOGMASKED(LOG_READ, "pbcr1_r %04x\n", m_pbcr1);
+ return m_pbcr1;
+}
+
+void sh7014_port_device::pbcr1_w(uint16_t data)
+{
+ m_pbcr1 = data & 0xf;
+ LOGMASKED(LOG_WRITE, "pbcr1_w %04x\n", m_pbcr1);
+}
+
+uint16_t sh7014_port_device::pbcr2_r()
+{
+ LOGMASKED(LOG_READ, "pbcr2_r %04x\n", m_pbcr2);
+ return m_pbcr2;
+}
+
+void sh7014_port_device::pbcr2_w(uint16_t data)
+{
+ m_pbcr2 = data & 0xfff5;
+ LOGMASKED(LOG_WRITE, "pbcr2_w %04x\n", m_pbcr2);
+}
+
+uint16_t sh7014_port_device::pedr_r()
+{
+ uint16_t pins = m_port_e_read_cb();
+ uint16_t r = ((m_pedr & m_peior) | (pins & ~m_peior));
+ LOGMASKED(LOG_READ, "pedr_r %04x (pins: %04x %04x)\n", r, m_peior, pins);
+ return r;
+}
+
+void sh7014_port_device::pedr_w(uint16_t data)
+{
+ m_pedr = data;
+ LOGMASKED(LOG_WRITE, "pedr_w %04x\n", m_pedr);
+
+ if (m_peior)
+ m_port_e_write_cb(m_pedr & m_peior);
+}
+
+uint16_t sh7014_port_device::peior_r()
+{
+ LOGMASKED(LOG_READ, "peior_r %04x\n", m_peior);
+ return m_peior;
+}
+
+void sh7014_port_device::peior_w(uint16_t data)
+{
+ m_peior = data;
+ LOGMASKED(LOG_WRITE, "peior_w %04x\n", m_peior);
+}
+
+uint16_t sh7014_port_device::pecr1_r()
+{
+ LOGMASKED(LOG_READ, "pecr1_r %04x\n", m_pecr1);
+ return m_pecr1;
+}
+
+void sh7014_port_device::pecr1_w(uint16_t data)
+{
+ m_pecr1 = data & 0xf000;
+ LOGMASKED(LOG_WRITE, "pecr1_w %04x\n", m_pecr1);
+}
+
+uint16_t sh7014_port_device::pecr2_r()
+{
+ LOGMASKED(LOG_READ, "pecr2_r %04x\n", m_pecr2);
+ return m_pecr2;
+}
+
+void sh7014_port_device::pecr2_w(uint16_t data)
+{
+ m_pecr2 = data & 0x55ff;
+ LOGMASKED(LOG_WRITE, "pecr2_w %04x\n", m_pecr2);
+}
+
+
+uint8_t sh7014_port_device::pfdr_r()
+{
+ auto r = m_port_f_read_cb();
+ LOGMASKED(LOG_READ, "pfdr_r %02x\n", r);
+ return r;
+}
diff --git a/src/devices/cpu/sh/sh7014_port.h b/src/devices/cpu/sh/sh7014_port.h
new file mode 100644
index 00000000000..675812ebbaa
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_port.h
@@ -0,0 +1,98 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 Bus State Controller
+
+***************************************************************************/
+
+#ifndef MAME_CPU_SH_SH7014_PORT_H
+#define MAME_CPU_SH_SH7014_PORT_H
+
+#pragma once
+
+
+class sh7014_port_device : public device_t
+{
+public:
+ sh7014_port_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0);
+
+ auto port_a_read_callback() { return m_port_a_read_cb.bind(); }
+ auto port_a_write_callback() { return m_port_a_write_cb.bind(); }
+
+ auto port_b_read_callback() { return m_port_b_read_cb.bind(); }
+ auto port_b_write_callback() { return m_port_b_write_cb.bind(); }
+
+ auto port_e_read_callback() { return m_port_e_read_cb.bind(); }
+ auto port_e_write_callback() { return m_port_e_write_cb.bind(); }
+
+ auto port_f_read_callback() { return m_port_f_read_cb.bind(); }
+
+ // port A
+ uint16_t padrl_r();
+ void padrl_w(uint16_t data);
+
+ uint16_t paiorl_r();
+ void paiorl_w(uint16_t data);
+
+ uint16_t pacrl1_r();
+ void pacrl1_w(uint16_t data);
+
+ uint16_t pacrl2_r();
+ void pacrl2_w(uint16_t data);
+
+ // port b
+ uint16_t pbdr_r();
+ void pbdr_w(uint16_t data);
+
+ uint16_t pbior_r();
+ void pbior_w(uint16_t data);
+
+ uint16_t pbcr1_r();
+ void pbcr1_w(uint16_t data);
+
+ uint16_t pbcr2_r();
+ void pbcr2_w(uint16_t data);
+
+ // port e
+ uint16_t pedr_r();
+ void pedr_w(uint16_t data);
+
+ uint16_t peior_r();
+ void peior_w(uint16_t data);
+
+ uint16_t pecr1_r();
+ void pecr1_w(uint16_t data);
+
+ uint16_t pecr2_r();
+ void pecr2_w(uint16_t data);
+
+ // port f
+ uint8_t pfdr_r();
+
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+private:
+ devcb_read16 m_port_a_read_cb;
+ devcb_write16 m_port_a_write_cb;
+
+ devcb_read16 m_port_b_read_cb;
+ devcb_write16 m_port_b_write_cb;
+
+ devcb_read16 m_port_e_read_cb;
+ devcb_write16 m_port_e_write_cb;
+
+ devcb_read16 m_port_f_read_cb;
+
+ uint16_t m_padr, m_paior, m_pacr1, m_pacr2;
+ uint16_t m_pbdr, m_pbior, m_pbcr1, m_pbcr2;
+ uint16_t m_pedr, m_peior, m_pecr1, m_pecr2;
+ uint8_t m_pfdr;
+};
+
+
+DECLARE_DEVICE_TYPE(SH7014_PORT, sh7014_port_device)
+
+#endif // MAME_CPU_SH_SH7014_PORT_H
diff --git a/src/devices/cpu/sh/sh7014_sci.cpp b/src/devices/cpu/sh/sh7014_sci.cpp
new file mode 100644
index 00000000000..ffa19247382
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_sci.cpp
@@ -0,0 +1,380 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 SCI Controller
+
+ TODO list (not comprehensive):
+ - RX is untested
+ - Multiprocessor bit is not handled at all
+
+***************************************************************************/
+
+#include "emu.h"
+#include "sh7014_sci.h"
+
+#define LOG_REGISTERS (1U << 1)
+#define LOG_TXRX (1U << 2)
+#define LOG_CLOCK (1U << 3)
+
+// #define VERBOSE (LOG_GENERAL | LOG_REGISTERS | LOG_TXRX | LOG_CLOCK)
+
+#include "logmacro.h"
+
+DEFINE_DEVICE_TYPE(SH7014_SCI, sh7014_sci_device, "sh7014sci", "SH7014 SCI Controller")
+
+sh7014_sci_device::sh7014_sci_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : device_t(mconfig, SH7014_SCI, tag, owner, clock)
+ , device_serial_interface(mconfig, *this)
+ , m_intc(*this, finder_base::DUMMY_TAG)
+ , m_sci_tx_cb(*this)
+ , m_hack_set_full_data_transmit_on_sync(false)
+{
+ m_external_clock_period = attotime::never;
+}
+
+void sh7014_sci_device::device_start()
+{
+ save_item(NAME(m_smr));
+ save_item(NAME(m_brr));
+ save_item(NAME(m_scr));
+ save_item(NAME(m_tdr));
+ save_item(NAME(m_ssr));
+ save_item(NAME(m_rdr));
+ save_item(NAME(m_is_dma_source_tx));
+ save_item(NAME(m_is_dma_source_rx));
+ save_item(NAME(m_clock_speed));
+ save_item(NAME(m_external_clock_period));
+}
+
+void sh7014_sci_device::device_reset()
+{
+ m_smr = 0;
+ m_brr = 0xff;
+ m_scr = 0;
+ m_tdr = 0xff;
+ m_ssr = SSR_TDRE | SSR_TEND;
+ m_rdr = 0;
+ m_is_dma_source_tx = m_is_dma_source_rx = false;
+ m_clock_speed = attotime::never;
+
+ update_data_format();
+ update_clock();
+}
+
+void sh7014_sci_device::map(address_map &map)
+{
+ map(0x00, 0x00).rw(FUNC(sh7014_sci_device::smr_r), FUNC(sh7014_sci_device::smr_w));
+ map(0x01, 0x01).rw(FUNC(sh7014_sci_device::brr_r), FUNC(sh7014_sci_device::brr_w));
+ map(0x02, 0x02).rw(FUNC(sh7014_sci_device::scr_r), FUNC(sh7014_sci_device::scr_w));
+ map(0x03, 0x03).rw(FUNC(sh7014_sci_device::tdr_r), FUNC(sh7014_sci_device::tdr_w));
+ map(0x04, 0x04).rw(FUNC(sh7014_sci_device::ssr_r), FUNC(sh7014_sci_device::ssr_w));
+ map(0x05, 0x05).r(FUNC(sh7014_sci_device::rdr_r));
+}
+
+///
+
+void sh7014_sci_device::set_send_full_data_transmit_on_sync_hack(bool enabled)
+{
+ // Synchronous clock mode forces a fixed 8-bit transmissions with no start, stop, parity, or multiprocessor bits.
+ // This flag makes it so that all 8 bits in the transmit register will be transferred at the start of a transmission
+ // instead of transmitting 1 bit at a time when in synchronous clock mode, allowing the transmit clock to be set to
+ // 1/8th of its normal speed.
+ m_hack_set_full_data_transmit_on_sync = enabled;
+}
+
+void sh7014_sci_device::set_external_clock_period(const attotime &period)
+{
+ m_external_clock_period = period;
+
+ // Update clock again if it's being used
+ if (m_scr & SCR_CKE1)
+ update_clock();
+}
+
+uint8_t sh7014_sci_device::smr_r()
+{
+ return m_smr;
+}
+
+void sh7014_sci_device::smr_w(uint8_t data)
+{
+ LOGMASKED(LOG_REGISTERS, "smr_w %02x %s %c%c%c%s /%d\n", data,
+ data & SMR_CA ? "sync" : "async",
+ data & SMR_CHR ? '7' : '8',
+ data & SMR_PE ? data & SMR_OE ? 'o' : 'e' : 'n',
+ data & SMR_STOP ? '2' : '1',
+ data & SMR_MP ? " mp" : "",
+ 1 << (2 * (data & SMR_CKS)));
+
+ bool do_clock_update = (data & (SMR_CKS | SMR_CA)) != (m_smr & (SMR_CKS | SMR_CA));
+ bool do_format_update = false;
+
+ if (!(data & SMR_CA))
+ do_format_update = (data & ~SMR_CKS) != (m_smr & ~SMR_CKS);
+ else
+ do_format_update = (data & SMR_CA) != (m_smr & SMR_CA);
+
+ m_smr = data;
+
+ if (do_format_update)
+ update_data_format();
+
+ if (do_clock_update)
+ update_clock();
+}
+
+uint8_t sh7014_sci_device::scr_r()
+{
+ return m_scr;
+}
+
+void sh7014_sci_device::scr_w(uint8_t data)
+{
+ const auto old = m_scr;
+
+ LOGMASKED(LOG_REGISTERS, "scr_w %02x%s%s%s%s%s%s clk=%d\n", data,
+ (data & SCR_TIE) ? " txi" : "",
+ (data & SCR_RIE) ? " rxi" : "",
+ (data & SCR_TE) ? " tx" : "",
+ (data & SCR_RE) ? " rx" : "",
+ (data & SCR_MPIE) ? " mpi" : "",
+ (data & SCR_TEIE) ? " tei" : "",
+ data & SCR_CKE);
+
+ if ((m_scr & SCR_TE) && !(data & SCR_TE))
+ m_ssr |= SSR_TEND | SSR_TDRE;
+
+ if ((m_scr & SCR_TIE) && !(data & SCR_TIE))
+ m_intc->set_interrupt(m_txi_int, CLEAR_LINE);
+
+ if ((m_scr & SCR_TEIE) && !(data & SCR_TEIE))
+ m_intc->set_interrupt(m_tei_int, CLEAR_LINE);
+
+ if ((m_scr & SCR_RIE) && !(data & SCR_RIE)) {
+ m_intc->set_interrupt(m_rxi_int, CLEAR_LINE);
+ m_intc->set_interrupt(m_eri_int, CLEAR_LINE);
+ }
+
+ m_scr = data;
+
+ if ((data & SCR_CKE) != (old & SCR_CKE))
+ update_clock();
+}
+
+uint8_t sh7014_sci_device::ssr_r()
+{
+ LOGMASKED(LOG_REGISTERS, "ssr_r %02x\n", m_ssr);
+ return m_ssr;
+}
+
+void sh7014_sci_device::ssr_w(uint8_t data)
+{
+ const auto old = m_ssr;
+ bool do_tx_update = false;
+
+ m_ssr = (data & (m_ssr & (SSR_TDRE | SSR_RDRF | SSR_ORER | SSR_FER | SSR_PER)))
+ | (m_ssr & (SSR_TEND | SSR_MPB))
+ | (data & SSR_MPBT);
+
+ if (!(m_scr & SCR_TE)) {
+ m_ssr |= SSR_TEND | SSR_TDRE;
+ } else if ((old & SSR_TDRE) || !(m_scr & SSR_TDRE)) {
+ do_tx_update = true;
+ m_ssr &= ~(SSR_TEND | SSR_TDRE);
+ }
+
+ if ((old & (SSR_ORER | SSR_FER | SSR_PER)) && !(data & (SSR_ORER | SSR_FER | SSR_PER)))
+ m_intc->set_interrupt(m_eri_int, CLEAR_LINE);
+
+ LOGMASKED(LOG_REGISTERS, "ssr_w %02x | %02x -> %02x\n", data, old, m_ssr);
+
+ if (do_tx_update)
+ update_tx_state();
+}
+
+uint8_t sh7014_sci_device::brr_r()
+{
+ return m_brr;
+}
+
+void sh7014_sci_device::brr_w(uint8_t data)
+{
+ LOGMASKED(LOG_REGISTERS, "brr_w %02x\n", data);
+ m_brr = data;
+ update_clock();
+}
+
+uint8_t sh7014_sci_device::tdr_r()
+{
+ LOGMASKED(LOG_TXRX, "tdr_r %02x\n", m_tdr);
+ return m_tdr;
+}
+
+void sh7014_sci_device::tdr_w(uint8_t data)
+{
+ LOGMASKED(LOG_TXRX, "tdr_w%s %02x %d %d\n", m_is_dma_source_tx ? " (dma)" : "", data, is_transmit_register_empty(), (m_scr & SCR_TE) != 0);
+
+ m_tdr = data;
+
+ if (!(m_scr & SCR_TE))
+ return;
+
+ if (m_is_dma_source_tx) {
+ // Normally this would happen with a write to SCR but DMAs can only write to TDR,
+ // so these flags are handled here as a special case for DMA writes only
+ m_ssr &= ~(SSR_TEND | SSR_TDRE);
+ m_is_dma_source_tx = false;
+
+ update_tx_state();
+ }
+}
+
+void sh7014_sci_device::tra_callback()
+{
+ if (!is_transmit_register_empty())
+ m_sci_tx_cb(transmit_register_get_data_bit());
+
+ if ((m_smr & SMR_CA) && m_hack_set_full_data_transmit_on_sync) {
+ while (!is_transmit_register_empty())
+ m_sci_tx_cb(transmit_register_get_data_bit());
+ }
+}
+
+void sh7014_sci_device::tra_complete()
+{
+ if (!(m_ssr & SSR_TDRE)) {
+ update_tx_state();
+ return;
+ }
+
+ LOGMASKED(LOG_TXRX, "transmit ended\n");
+
+ m_ssr |= SSR_TEND;
+ if (m_scr & SCR_TEIE)
+ m_intc->set_interrupt(m_tei_int, ASSERT_LINE);
+}
+
+void sh7014_sci_device::update_tx_state()
+{
+ if (!(m_scr & SCR_TE) || (m_ssr & SSR_TDRE) || !is_transmit_register_empty())
+ return;
+
+ LOGMASKED(LOG_TXRX, "transmitting %02x\n", m_tdr);
+
+ transmit_register_setup(m_tdr);
+
+ m_ssr = (m_ssr & ~SSR_TEND) | SSR_TDRE;
+
+ if (m_scr & SCR_TIE)
+ m_intc->set_interrupt(m_txi_int, ASSERT_LINE);
+}
+
+uint8_t sh7014_sci_device::rdr_r()
+{
+ auto r = m_rdr;
+
+ LOGMASKED(LOG_TXRX, "rdr_r%s %02x\n", m_is_dma_source_rx ? " (dma)" : "", m_rdr);
+
+ // DMA reads cause RDRF to be cleared
+ if (m_is_dma_source_rx)
+ m_ssr &= ~SSR_RDRF;
+
+ return r;
+}
+
+void sh7014_sci_device::rcv_complete()
+{
+ receive_register_extract();
+
+ if (!(m_scr & SCR_RE))
+ return;
+
+ if (is_receive_framing_error())
+ m_ssr |= SSR_FER;
+ if (is_receive_parity_error())
+ m_ssr |= SSR_PER;
+
+ if (!(m_ssr & SSR_RDRF)) {
+ m_rdr = get_received_char();
+ m_ssr |= SSR_RDRF;
+
+ if (m_scr & SCR_RIE)
+ m_intc->set_interrupt(m_rxi_int, ASSERT_LINE);
+
+ if ((m_ssr & SSR_FER) | (m_ssr & SSR_PER) || (m_ssr & SSR_ORER))
+ m_intc->set_interrupt(m_eri_int, ASSERT_LINE);
+ } else {
+ m_ssr |= SSR_ORER;
+ }
+}
+
+void sh7014_sci_device::update_data_format()
+{
+ if (m_smr & SMR_CA) {
+ // Synchronous clock is a fixed 8-bit data length transmission
+ set_data_frame(0, 8, PARITY_NONE, STOP_BITS_0);
+ return;
+ }
+
+ // Async
+ set_data_frame(
+ 1,
+ (m_smr & SMR_CHR) ? 8 : 7,
+ (m_smr & SMR_MP) ? PARITY_NONE : ((m_smr & SMR_PE) ? PARITY_ODD : PARITY_EVEN), // Multiprocessor mode does not use parity
+ (m_smr & SMR_STOP) ? STOP_BITS_1 : STOP_BITS_2
+ );
+}
+
+void sh7014_sci_device::update_clock()
+{
+ auto clock_mode = INTERNAL_SYNC_OUT;
+ if (m_smr & SMR_CA) {
+ if (m_scr & SCR_CKE1)
+ clock_mode = EXTERNAL_SYNC;
+ else
+ clock_mode = INTERNAL_SYNC_OUT;
+ } else {
+ if (m_scr & SCR_CKE1)
+ clock_mode = EXTERNAL_ASYNC;
+ else if (m_scr & SCR_CKE0)
+ clock_mode = INTERNAL_ASYNC_OUT;
+ else
+ clock_mode = INTERNAL_ASYNC;
+ }
+
+ if (clock_mode == EXTERNAL_ASYNC && !m_external_clock_period.is_never())
+ clock_mode = EXTERNAL_RATE_ASYNC;
+ else if (clock_mode == EXTERNAL_SYNC && !m_external_clock_period.is_never())
+ clock_mode = EXTERNAL_RATE_SYNC;
+
+ auto clock_speed = attotime::zero;
+ switch (clock_mode) {
+ case INTERNAL_ASYNC:
+ case INTERNAL_ASYNC_OUT:
+ case INTERNAL_SYNC_OUT:
+ {
+ int divider = (1 << (2 * (m_smr & SMR_CKS))) * (m_brr + 1);
+ clock_speed = attotime::from_ticks(divider, clock());
+ }
+ break;
+
+ case EXTERNAL_ASYNC:
+ case EXTERNAL_RATE_ASYNC:
+ case EXTERNAL_SYNC:
+ case EXTERNAL_RATE_SYNC:
+ clock_speed = m_external_clock_period;
+ break;
+ }
+
+ if (clock_speed != m_clock_speed && !clock_speed.is_never()) {
+ LOGMASKED(LOG_CLOCK, "Changing SCI ch %d rate set to %lf (%d %04x %04x)\n", m_channel_id, clock_speed.as_hz(), clock_mode, m_brr, m_smr);
+
+ if (m_hack_set_full_data_transmit_on_sync && (m_smr & SMR_CA))
+ set_rate(clock_speed * 8);
+ else
+ set_rate(clock_speed);
+
+ m_clock_speed = clock_speed;
+ }
+}
diff --git a/src/devices/cpu/sh/sh7014_sci.h b/src/devices/cpu/sh/sh7014_sci.h
new file mode 100644
index 00000000000..cc713bc737c
--- /dev/null
+++ b/src/devices/cpu/sh/sh7014_sci.h
@@ -0,0 +1,135 @@
+// license:BSD-3-Clause
+// copyright-holders:windyfairy
+/***************************************************************************
+
+ SH7014 SCI Controller
+
+***************************************************************************/
+
+#ifndef MAME_CPU_SH_SH7014_SCI_H
+#define MAME_CPU_SH_SH7014_SCI_H
+
+#pragma once
+
+#include "sh2.h"
+#include "sh7014_intc.h"
+#include "diserial.h"
+
+class sh7014_sci_device : public device_t, public device_serial_interface
+{
+public:
+ sh7014_sci_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
+
+ template<typename T> sh7014_sci_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, T &&intc, int channel_id, int eri, int rxi, int txi, int tei)
+ : sh7014_sci_device(mconfig, tag, owner, clock)
+ {
+ m_intc.set_tag(std::forward<T>(intc));
+ m_channel_id = channel_id;
+ m_eri_int = eri;
+ m_rxi_int = rxi;
+ m_txi_int = txi;
+ m_tei_int = tei;
+ }
+
+ auto write_sci_tx() { return m_sci_tx_cb.bind(); }
+
+ void set_dma_source_tx(bool val) { m_is_dma_source_tx = val; }
+ void set_dma_source_rx(bool val) { m_is_dma_source_rx = val; }
+
+ void set_external_clock_period(const attotime &_period);
+ void set_send_full_data_transmit_on_sync_hack(bool enabled);
+
+ void map(address_map &map);
+
+protected:
+ virtual void device_start() override;
+ virtual void device_reset() override;
+
+ virtual void rcv_complete() override;
+
+ virtual void tra_callback() override;
+ virtual void tra_complete() override;
+
+private:
+ enum : uint32_t {
+ INTERNAL_ASYNC = 0,
+ INTERNAL_ASYNC_OUT,
+ EXTERNAL_ASYNC,
+ EXTERNAL_RATE_ASYNC,
+ INTERNAL_SYNC_OUT,
+ EXTERNAL_SYNC,
+ EXTERNAL_RATE_SYNC
+ };
+
+ enum {
+ SMR_CA = 1 << 7,
+ SMR_CHR = 1 << 6,
+ SMR_PE = 1 << 5,
+ SMR_OE = 1 << 4,
+ SMR_STOP = 1 << 3,
+ SMR_MP = 1 << 2,
+ SMR_CKS = (1 << 1) | (1 << 0),
+
+ SCR_TIE = 1 << 7,
+ SCR_RIE = 1 << 6,
+ SCR_TE = 1 << 5,
+ SCR_RE = 1 << 4,
+ SCR_MPIE = 1 << 3,
+ SCR_TEIE = 1 << 2,
+ SCR_CKE = (1 << 1) | (1 << 0),
+ SCR_CKE1 = 1 << 1,
+ SCR_CKE0 = 1 << 0,
+
+ SSR_TDRE = 1 << 7,
+ SSR_RDRF = 1 << 6,
+ SSR_ORER = 1 << 5,
+ SSR_FER = 1 << 4,
+ SSR_PER = 1 << 3,
+ SSR_TEND = 1 << 2,
+ SSR_MPB = 1 << 1,
+ SSR_MPBT = 1 << 0
+ };
+
+ uint8_t smr_r();
+ void smr_w(uint8_t data);
+
+ uint8_t brr_r();
+ void brr_w(uint8_t data);
+
+ uint8_t scr_r();
+ void scr_w(uint8_t data);
+
+ uint8_t tdr_r();
+ void tdr_w(uint8_t data);
+
+ uint8_t ssr_r();
+ void ssr_w(uint8_t data);
+
+ uint8_t rdr_r();
+
+ void update_tx_state();
+ void update_clock();
+ void update_data_format();
+
+ required_device<sh7014_intc_device> m_intc;
+
+ devcb_write_line m_sci_tx_cb;
+
+ int m_channel_id, m_eri_int, m_rxi_int, m_txi_int, m_tei_int;
+ bool m_hack_set_full_data_transmit_on_sync;
+
+ uint8_t m_smr;
+ uint8_t m_brr;
+ uint8_t m_scr;
+ uint8_t m_tdr;
+ uint8_t m_ssr;
+ uint8_t m_rdr;
+
+ attotime m_external_clock_period, m_clock_speed;
+ bool m_is_dma_source_tx, m_is_dma_source_rx;
+};
+
+
+DECLARE_DEVICE_TYPE(SH7014_SCI, sh7014_sci_device)
+
+#endif // MAME_CPU_SH_SH7014_SCI_H