diff options
Diffstat (limited to 'src/devices/cpu/sh/sh_mtu.cpp')
-rw-r--r-- | src/devices/cpu/sh/sh_mtu.cpp | 529 |
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); + } +} |