summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/cpu/sh/sh_mtu.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/cpu/sh/sh_mtu.cpp')
-rw-r--r--src/devices/cpu/sh/sh_mtu.cpp529
1 files changed, 529 insertions, 0 deletions
diff --git a/src/devices/cpu/sh/sh_mtu.cpp b/src/devices/cpu/sh/sh_mtu.cpp
new file mode 100644
index 00000000000..532a47da5a8
--- /dev/null
+++ b/src/devices/cpu/sh/sh_mtu.cpp
@@ -0,0 +1,529 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert
+/***************************************************************************
+
+ sh_mtu.h
+
+ SH DMA controller
+
+***************************************************************************/
+
+#include "emu.h"
+#include "sh_mtu.h"
+#include "sh7042.h"
+
+#define V 1
+
+DEFINE_DEVICE_TYPE(SH_MTU, sh_mtu_device, "sh_mtu", "SH Multifuntion timer pulse unit")
+DEFINE_DEVICE_TYPE(SH_MTU_CHANNEL, sh_mtu_channel_device, "sh_mtu_channel", "SH Multifuntion timer pulse unit channel")
+
+sh_mtu_device::sh_mtu_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ device_t(mconfig, SH_MTU, tag, owner, clock),
+ m_cpu(*this, finder_base::DUMMY_TAG),
+ m_timer_channel(*this, "%u", 0)
+{
+}
+
+void sh_mtu_device::device_start()
+{
+ save_item(NAME(m_tstr));
+ save_item(NAME(m_tsyr));
+ save_item(NAME(m_toer));
+ save_item(NAME(m_tocr));
+ save_item(NAME(m_tgcr));
+ save_item(NAME(m_tcdr));
+ save_item(NAME(m_tddr));
+ save_item(NAME(m_tcnts));
+ save_item(NAME(m_tcbr));
+}
+
+void sh_mtu_device::device_reset()
+{
+ m_tstr = 0;
+ m_tsyr = 0;
+ m_toer = 0xc0;
+ m_tocr = 0;
+ m_tgcr = 0x80;
+ m_tcdr = 0xffff;
+ m_tddr = 0xffff;
+ m_tcnts = 0;
+ m_tcbr = 0xffff;
+}
+
+u8 sh_mtu_device::tstr_r()
+{
+ logerror("tstr_r\n");
+ return m_tstr;
+}
+
+void sh_mtu_device::tstr_w(u8 data)
+{
+ m_tstr = data;
+ logerror("tstr_w %02x\n", m_tstr);
+ // To generalize
+ m_timer_channel[0]->set_enable(BIT(m_tstr, 0));
+ m_timer_channel[1]->set_enable(BIT(m_tstr, 1));
+ m_timer_channel[2]->set_enable(BIT(m_tstr, 2));
+ m_timer_channel[3]->set_enable(BIT(m_tstr, 6));
+ m_timer_channel[4]->set_enable(BIT(m_tstr, 7));
+}
+
+u8 sh_mtu_device::tsyr_r()
+{
+ logerror("tsyr_r\n");
+ return m_tsyr;
+}
+
+void sh_mtu_device::tsyr_w(u8 data)
+{
+ m_tsyr = data;
+ logerror("tsyr_w %02x\n", m_tsyr);
+}
+
+u8 sh_mtu_device::toer_r()
+{
+ logerror("toer_r\n");
+ return m_toer;
+}
+
+void sh_mtu_device::toer_w(u8 data)
+{
+ m_toer = data;
+ logerror("toer_w %02x\n", m_toer);
+}
+
+u8 sh_mtu_device::tocr_r()
+{
+ logerror("tocr_r\n");
+ return m_tocr;
+}
+
+void sh_mtu_device::tocr_w(u8 data)
+{
+ m_tocr = data;
+ logerror("tocr_w %02x\n", m_tocr);
+}
+
+u8 sh_mtu_device::tgcr_r()
+{
+ logerror("tgcr_r\n");
+ return m_tgcr;
+}
+
+void sh_mtu_device::tgcr_w(u8 data)
+{
+ m_tgcr = data;
+ logerror("tgcr_w %02x\n", m_tgcr);
+}
+
+u16 sh_mtu_device::tcdr_r()
+{
+ logerror("tcdr_r\n");
+ return m_tcdr;
+}
+
+void sh_mtu_device::tcdr_w(offs_t, u16 data, u16 mem_mask)
+{
+ COMBINE_DATA(&m_tcdr);
+ logerror("tcdr_w %04x\n", m_tcdr);
+}
+
+u16 sh_mtu_device::tddr_r()
+{
+ logerror("tddr_r\n");
+ return m_tddr;
+}
+
+void sh_mtu_device::tddr_w(offs_t, u16 data, u16 mem_mask)
+{
+ COMBINE_DATA(&m_tddr);
+ logerror("tddr_w %04x\n", m_tddr);
+}
+
+u16 sh_mtu_device::tcnts_r()
+{
+ logerror("tcnts_r\n");
+ return m_tcnts;
+}
+
+void sh_mtu_device::tcnts_w(offs_t, u16 data, u16 mem_mask)
+{
+ COMBINE_DATA(&m_tcnts);
+ logerror("tcnts_w %04x\n", m_tcnts);
+}
+
+u16 sh_mtu_device::tcbr_r()
+{
+ logerror("tcbr_r\n");
+ return m_tcbr;
+}
+
+void sh_mtu_device::tcbr_w(offs_t, u16 data, u16 mem_mask)
+{
+ COMBINE_DATA(&m_tcbr);
+ logerror("tcbr_w %04x\n", m_tcbr);
+}
+
+sh_mtu_channel_device::sh_mtu_channel_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
+ device_t(mconfig, SH_MTU_CHANNEL, tag, owner, clock),
+ m_cpu(*this, finder_base::DUMMY_TAG),
+ m_intc(*this, finder_base::DUMMY_TAG),
+ m_chained_timer(*this, finder_base::DUMMY_TAG)
+{
+}
+
+void sh_mtu_channel_device::device_start()
+{
+ m_channel_active = false;
+ device_reset();
+
+ save_item(NAME(m_tgr_count));
+ save_item(NAME(m_tbr_count));
+ save_item(NAME(m_tgr_clearing));
+ save_item(NAME(m_tcr));
+ save_item(NAME(m_tmdr));
+ save_item(NAME(m_tior));
+ save_item(NAME(m_tier));
+ save_item(NAME(m_tsr));
+ save_item(NAME(m_clock_type));
+ save_item(NAME(m_clock_divider));
+ save_item(NAME(m_tcnt));
+ save_item(NAME(m_tgr));
+ save_item(NAME(m_last_clock_update));
+ save_item(NAME(m_event_time));
+ save_item(NAME(m_phase));
+ save_item(NAME(m_counter_cycle));
+ save_item(NAME(m_counter_incrementing));
+ save_item(NAME(m_channel_active));
+
+ m_tsr = 0xc0;
+}
+
+void sh_mtu_channel_device::device_reset()
+{
+ // Don't touch channel_active here, top level device handles it.
+
+ m_tbr_count = 0;
+ m_tgr_clearing = TGR_CLEAR_NONE;
+ m_tcr = 0;
+ m_tmdr = 0xc0;
+ m_tior = 0;
+ m_tier = 0x40 & m_tier_mask;
+ m_clock_type = DIV_1;
+ m_clock_divider = 0;
+ m_tcnt = 0;
+ std::fill(m_tgr.begin(), m_tgr.end(), 0xffff);
+ m_last_clock_update = 0;
+ m_event_time = 0;
+ m_phase = 0;
+ m_counter_cycle = 1;
+ m_counter_incrementing = true;
+}
+
+u8 sh_mtu_channel_device::tcr_r()
+{
+ return m_tcr;
+}
+
+void sh_mtu_channel_device::tcr_w(u8 data)
+{
+ update_counter();
+ m_tcr = data;
+ logerror("tcr_w %02x\n", m_tcr);
+ switch(m_tcr & 0x60) {
+ case 0x00:
+ m_tgr_clearing = TGR_CLEAR_NONE;
+ if(V>=1) logerror("No automatic tcnt clearing\n");
+ break;
+ case 0x20: case 0x40: {
+ m_tgr_clearing = m_tcr & 0x20 ? 0 : 1;
+ if(m_tgr_count > 2 && (m_tcr & 0x80))
+ m_tgr_clearing += 2;
+ if(V>=1) logerror("Auto-clear on tgr%c\n", 'a'+m_tgr_clearing);
+ break;
+ }
+ case 0x60:
+ m_tgr_clearing = TGR_CLEAR_EXT;
+ if(V>=1) logerror("External sync clear\n");
+ break;
+ }
+
+ int count_type = m_count_types[m_tcr & 7];
+ if(count_type >= DIV_1 && m_clock_type <= DIV_4) {
+ m_clock_type = DIV_1;
+ m_clock_divider = count_type - DIV_1;
+ if(V>=1) logerror("clock divider %d (%d)\n", m_clock_divider, 1 << m_clock_divider);
+ if(!m_clock_divider)
+ m_phase = 0;
+ else {
+ switch(m_tcr & 0x18) {
+ case 0x00:
+ m_phase = 0;
+ if(V>=1) logerror("Phase 0\n");
+ break;
+ case 0x08:
+ m_phase = 1 << (m_clock_divider-1);
+ if(V>=1) logerror("Phase 180\n");
+ break;
+ case 0x10: case 0x18:
+ m_phase = 0;
+ m_clock_divider--;
+ if(V>=1) logerror("Phase 0+180\n");
+ break;
+ }
+ }
+
+ } else if(count_type == CHAIN) {
+ m_clock_type = CHAIN;
+ m_clock_divider = 0;
+ m_phase = 0;
+ if(V>=1) logerror("chained timer\n");
+
+ } else if(count_type >= INPUT_A && count_type <= INPUT_D) {
+ m_clock_type = count_type;
+ m_clock_divider = 0;
+ m_phase = 0;
+ if(V>=1) logerror("counting input %c\n", 'a'+count_type-INPUT_A);
+ }
+ recalc_event();
+}
+
+u8 sh_mtu_channel_device::tmdr_r()
+{
+ return m_tmdr;
+}
+
+void sh_mtu_channel_device::tmdr_w(u8 data)
+{
+ m_tmdr = data;
+ logerror("tmdr_w %02x\n", m_tmdr);
+}
+
+u8 sh_mtu_channel_device::tior_r()
+{
+ return m_tior;
+}
+
+void sh_mtu_channel_device::tior_w(u8 data)
+{
+ m_tior = data;
+}
+
+u8 sh_mtu_channel_device::tier_r()
+{
+ return m_tier;
+}
+
+void sh_mtu_channel_device::tier_w(u8 data)
+{
+ update_counter();
+ m_tier = data;
+ if(0)
+ logerror("irq %c%c%c%c%c%c\n",
+ m_tier & IRQ_A ? 'a' : '.',
+ m_tier & IRQ_B ? 'b' : '.',
+ m_tier & IRQ_C ? 'c' : '.',
+ m_tier & IRQ_D ? 'd' : '.',
+ m_tier & IRQ_V ? 'v' : '.',
+ m_tier & IRQ_U ? 'u' : '.');
+ recalc_event();
+}
+
+u8 sh_mtu_channel_device::tsr_r()
+{
+ return m_tsr;
+}
+
+void sh_mtu_channel_device::tsr_w(u8 data)
+{
+ update_counter();
+ m_tsr = (data & 0x80) | (m_tsr & ~data & 0x7f);
+ recalc_event();
+}
+
+u16 sh_mtu_channel_device::tcnt_r()
+{
+ if(!machine().side_effects_disabled())
+ update_counter();
+ // Need to implement phase counting for the rotary controller on the psr540
+ if(m_tmdr & 0xf)
+ return 0;
+ return m_tcnt;
+}
+
+void sh_mtu_channel_device::tcnt_w(offs_t, u16 data, u16 mem_mask)
+{
+ update_counter();
+ COMBINE_DATA(&m_tcnt);
+ recalc_event();
+}
+
+u16 sh_mtu_channel_device::tgr_r(offs_t reg)
+{
+ return m_tgr[reg];
+}
+
+void sh_mtu_channel_device::tgr_w(offs_t reg, u16 data, u16 mem_mask)
+{
+ update_counter();
+ COMBINE_DATA(&m_tgr[reg]);
+ recalc_event();
+}
+
+u16 sh_mtu_channel_device::tgrc_r(offs_t reg)
+{
+ return tgr_r(reg + 2);
+}
+
+void sh_mtu_channel_device::tgrc_w(offs_t reg, u16 data, u16 mem_mask)
+{
+ tgr_w(reg + 2, data, mem_mask);
+}
+
+void sh_mtu_channel_device::set_enable(bool enable)
+{
+ update_counter();
+ m_channel_active = enable;
+ if(enable)
+ logerror("enabled\n");
+ else
+ logerror("disabled\n");
+ recalc_event();
+}
+
+u64 sh_mtu_channel_device::internal_update(u64 current_time)
+{
+ while(m_event_time && current_time >= m_event_time) {
+ update_counter(m_event_time);
+ recalc_event(m_event_time);
+ }
+
+ return m_event_time;
+}
+
+void sh_mtu_channel_device::recalc_event(u64 cur_time)
+{
+ if(!m_channel_active) {
+ m_event_time = 0;
+ return;
+ }
+
+ bool update_cpu = cur_time == 0;
+ u64 old_event_time = m_event_time;
+
+ if(m_clock_type != DIV_1) {
+ m_event_time = 0;
+ if(old_event_time && update_cpu)
+ m_cpu->internal_update();
+
+ return;
+ }
+
+ if(!cur_time)
+ cur_time = m_cpu->current_cycles();
+
+ if(m_counter_incrementing) {
+ u32 event_delay = 0xffffffff;
+ if(m_tgr_clearing >= 0)
+ m_counter_cycle = m_tgr[m_tgr_clearing] + 1;
+ else
+ m_counter_cycle = 0x10000;
+ if((m_tier & IRQ_V && m_interrupt[4] != -1) && (m_counter_cycle == 0x10000 || m_tcnt >= m_counter_cycle))
+ event_delay = 0x10000 - m_tcnt;
+
+ for(int i = 0; i < m_tgr_count; i++)
+ if(BIT(m_tier, i) && m_interrupt[i] != -1) {
+ u32 new_delay = 0xffffffff;
+ u16 cmp = m_tgr[i] + 1;
+ if(cmp > m_tcnt) {
+ if(m_tcnt >= m_counter_cycle || cmp <= m_counter_cycle)
+ new_delay = cmp - m_tcnt;
+ } else if(cmp <= m_counter_cycle) {
+ if(m_tcnt < m_counter_cycle)
+ new_delay = (m_counter_cycle - m_tcnt) + cmp;
+ else
+ new_delay = (0x10000 - m_tcnt) + cmp;
+ }
+
+ if(event_delay > new_delay)
+ event_delay = new_delay;
+ }
+ if(event_delay != 0xffffffff)
+ m_event_time = ((((cur_time + (1ULL << m_clock_divider) - m_phase) >> m_clock_divider) + event_delay - 1) << m_clock_divider) + m_phase;
+ else
+ m_event_time = 0;
+ } else {
+ logerror("decrementing counter\n");
+ exit(1);
+ }
+
+ if(old_event_time != m_event_time && update_cpu)
+ m_cpu->internal_update();
+}
+
+void sh_mtu_channel_device::update_counter(u64 cur_time)
+{
+ if(m_clock_type != DIV_1)
+ return;
+
+ if(!cur_time)
+ cur_time = m_cpu->current_cycles();
+
+ if(!m_channel_active) {
+ m_last_clock_update = cur_time;
+ return;
+ }
+
+ u64 base_time = m_last_clock_update;
+ m_last_clock_update = cur_time;
+ u64 new_time = cur_time;
+ if(m_clock_divider) {
+ base_time = (base_time + m_phase) >> m_clock_divider;
+ new_time = (new_time + m_phase) >> m_clock_divider;
+ }
+ if(new_time == base_time)
+ return;
+
+ if(m_counter_incrementing) {
+ u16 prev = m_tcnt;
+ u64 delta = new_time - base_time;
+ u64 tt = m_tcnt + delta;
+
+ if(prev >= m_counter_cycle) {
+ if(tt >= 0x10000)
+ m_tcnt = (tt - 0x10000) % m_counter_cycle;
+ else
+ m_tcnt = tt;
+ } else
+ m_tcnt = tt % m_counter_cycle;
+
+ for(int i = 0; i < m_tgr_count; i++) {
+ u16 cmp = m_tgr[i] + 1;
+ bool match = m_tcnt == cmp || (tt == cmp && tt == m_counter_cycle);
+ if(!match) {
+ // Need to do additional checks here for software that polls the flags with interrupts disabled, since recalc_event only schedules IRQ events.
+ if(prev >= m_counter_cycle)
+ match = (cmp > prev && tt >= cmp) || (cmp <= m_counter_cycle && m_tcnt < m_counter_cycle && (delta - (0x10000 - prev)) >= cmp);
+ else if(cmp <= m_counter_cycle)
+ match = delta >= m_counter_cycle || (prev < cmp && tt >= cmp) || (m_tcnt <= prev && m_tcnt >= cmp);
+
+ if(match && BIT(m_tier, i) && m_interrupt[i] != -1)
+ logerror("update_counter unexpected TGR %d IRQ\n", i);
+ }
+
+ if(match) {
+ m_tsr |= 1 << i;
+ if(BIT(m_tier, i) && m_interrupt[i] != -1)
+ m_intc->internal_interrupt(m_interrupt[i]);
+ }
+ }
+ if(tt >= 0x10000 && (m_counter_cycle == 0x10000 || prev >= m_counter_cycle)) {
+ m_tsr |= IRQ_V;
+ if(m_tier & IRQ_V && m_interrupt[4] != -1)
+ m_intc->internal_interrupt(m_interrupt[4]);
+ }
+ } else {
+ logerror("decrementing counter\n");
+ exit(1);
+ }
+}