// license:BSD-3-Clause // copyright-holders:Ryan Holtz /****************************************************************************** * * Sony PlayStation 2 IOP timer device skeleton * * To Do: * Everything * */ #include "emu.h" #include "ioptimer.h" DEFINE_DEVICE_TYPE(SONYIOP_TIMER, iop_timer_device, "ioptimer", "PlayStation 2 IOP timer") iop_timer_device::iop_timer_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, SONYIOP_TIMER, tag, owner, clock) , m_compare_timer(nullptr) , m_overflow_timer(nullptr) , m_last_update_time(attotime::zero) , m_elapsed_time(attotime::zero) , m_zero_return(false) , m_count(0) , m_compare(0) , m_gate_enable(false) , m_gate_mode(GATM_LOW) , m_cmp_int_enabled(false) , m_cmp_int(false) , m_ovf_int_enabled(false) , m_ovf_int(false) , m_ienable(false) , m_int_cb(*this) { } void iop_timer_device::device_start() { m_int_cb.resolve_safe(); if (!m_compare_timer) m_compare_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(iop_timer_device::compare), this)); if (!m_overflow_timer) m_overflow_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(iop_timer_device::overflow), this)); } void iop_timer_device::device_reset() { m_ctrl = 0; m_last_update_time = attotime::zero; m_elapsed_time = attotime::zero; m_zero_return = false; m_count = 0; m_compare = 0; m_gate_enable = false; m_gate_mode = GATM_LOW; m_cmp_int_enabled = false; m_cmp_int = false; m_ovf_int_enabled = false; m_ovf_int = false; m_ienable = false; m_compare_timer->adjust(attotime::never); m_overflow_timer->adjust(attotime::never); } void iop_timer_device::update_gate() { // TODO } void iop_timer_device::update_interrupts() { if (!m_int_cb.isnull()) { bool interrupt = m_ienable && ((m_ovf_int && m_ovf_int_enabled) || (m_cmp_int && m_cmp_int_enabled)); m_int_cb(interrupt); } } void iop_timer_device::update_compare_timer() { // TODO: Compare timer functionality is a total guess if (!m_ienable || !m_cmp_int_enabled) m_compare_timer->adjust(attotime::never); m_compare_timer->adjust(attotime::from_ticks(m_compare - m_count, clock())); } void iop_timer_device::update_overflow_timer() { // TODO: Overflow timer functionality is not used yet, total guess /* uint32_t ticks = 0; if (m_zero_return) { if (m_compare > m_count) ticks = m_compare - m_count; else ticks = (uint32_t)(0x100000000ULL - (m_count - m_compare)); } else { ticks = (uint32_t)(0x100000000ULL - m_count); } */ m_overflow_timer->adjust(attotime::never); } void iop_timer_device::update_count() { attotime curr_time = machine().scheduler().time(); attotime time_delta = curr_time - m_last_update_time; m_last_update_time = curr_time; m_elapsed_time += time_delta; uint32_t ticks = (uint32_t)m_elapsed_time.as_ticks(clock()); if (ticks > 0) { m_elapsed_time -= attotime::from_ticks(ticks, clock()); m_count += ticks; } } void iop_timer_device::set_ctrl(uint32_t data) { static const char *gatm_names[4] = { "low?", "reset+rising?", "reset+falling?", "reset+both?" }; logerror("%s: set_ctrl: GATE=%d, GATM=%s, ZRET=%d\n", machine().describe_context(), data & CTRL_GATE, gatm_names[(data & CTRL_GATM) >> 1], (data & CTRL_ZRET) >> 3); logerror("%s: CMPE=%d, OVFE=%d, REP_INT=%d, TOG_INT=%d\n", machine().describe_context(), (data & CTRL_CMPE) >> 4, (data & CTRL_OVFE) >> 5, (data & CTRL_REPI) >> 6, (data & CTRL_TOGI) >> 7); const uint32_t old = m_ctrl; m_ctrl = data | CTRL_INTE; const uint32_t changed = old ^ data; if (!changed) return; m_gate_enable = data & CTRL_GATE; m_gate_mode = (timer_gate_mode)((data & CTRL_GATM) >> 1); m_zero_return = (data & CTRL_ZRET) >> 3; m_cmp_int_enabled = (data & CTRL_CMPE) >> 4; m_ovf_int_enabled = (data & CTRL_OVFE) >> 5; m_repeat_int = (data & CTRL_REPI) >> 6; m_toggle_int = (data & CTRL_TOGI) >> 7; m_ienable = 1; m_count = 0; if (changed & CTRL_CMPE) { if (data & CTRL_CMPE) update_compare_timer(); else m_compare_timer->adjust(attotime::never); } if (changed & CTRL_OVFE) { if (data & CTRL_OVFE) update_overflow_timer(); else m_overflow_timer->adjust(attotime::never); } if (changed & CTRL_INTE) { update_interrupts(); } } TIMER_CALLBACK_MEMBER(iop_timer_device::compare) { if (m_zero_return) { m_count = 0; // Guess update_compare_timer(); } else { m_compare_timer->adjust(attotime::never); } if (m_cmp_int_enabled) m_cmp_int = 1; update_interrupts(); } TIMER_CALLBACK_MEMBER(iop_timer_device::overflow) { if (m_ovf_int_enabled) m_ovf_int = 1; update_interrupts(); } READ32_MEMBER(iop_timer_device::read) { uint32_t ret = 0; switch (offset) { case 0x00: { //const uint32_t old = m_count; update_count(); ret = m_count; //if (old != m_count) //logerror("%s: IOP timer read: COUNT (%08x & %08x)\n", machine().describe_context(), ret, mem_mask); break; } case 0x01: ret = m_ctrl | (m_ovf_int ? CTRL_OVFF : 0) | (m_cmp_int ? CTRL_CMPF : 0); logerror("%s: IOP timer read: MODE (%08x & %08x)\n", machine().describe_context(), ret, mem_mask); break; case 0x02: ret = m_compare; logerror("%s: IOP timer read: COMPARE (%08x & %08x)\n", machine().describe_context(), ret, mem_mask); break; default: break; } return ret; } WRITE32_MEMBER(iop_timer_device::write) { switch (offset) { case 0x00: m_count = data; logerror("%s: IOP timer write: COUNT = %08x & %08x\n", machine().describe_context(), data, mem_mask); update_compare_timer(); update_overflow_timer(); break; case 0x01: set_ctrl(data); break; case 0x02: { logerror("%s: IOP timer write: COMPARE = %08x & %08x\n", machine().describe_context(), data, mem_mask); if (m_compare == data) break; m_compare = data; if (!m_toggle_int) m_ienable = true; update_compare_timer(); break; } default: break; } }