mirror of https://github.com/stenzek/duckstation
Basic timer implementation
parent
ad652c47ed
commit
ad316162f3
@ -0,0 +1,203 @@
|
||||
#include "timers.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "common/state_wrapper.h"
|
||||
#include "interrupt_controller.h"
|
||||
Log_SetChannel(Timers);
|
||||
|
||||
Timers::Timers() = default;
|
||||
|
||||
Timers::~Timers() = default;
|
||||
|
||||
bool Timers::Initialize(InterruptController* interrupt_controller)
|
||||
{
|
||||
m_interrupt_controller = interrupt_controller;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Timers::Reset()
|
||||
{
|
||||
for (CounterState& cs : m_states)
|
||||
{
|
||||
cs.mode.bits = 0;
|
||||
cs.counter = 0;
|
||||
cs.target = 0;
|
||||
cs.gate = false;
|
||||
cs.external_counting_enabled = false;
|
||||
cs.counting_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Timers::DoState(StateWrapper& sw)
|
||||
{
|
||||
for (CounterState& cs : m_states)
|
||||
{
|
||||
sw.Do(&cs.mode.bits);
|
||||
sw.Do(&cs.counter);
|
||||
sw.Do(&cs.target);
|
||||
sw.Do(&cs.gate);
|
||||
sw.Do(&cs.external_counting_enabled);
|
||||
sw.Do(&cs.counting_enabled);
|
||||
}
|
||||
|
||||
return !sw.HasError();
|
||||
}
|
||||
|
||||
void Timers::SetGate(u32 timer, bool state)
|
||||
{
|
||||
CounterState& cs = m_states[timer];
|
||||
if (cs.gate == state)
|
||||
return;
|
||||
|
||||
cs.gate = state;
|
||||
|
||||
if (cs.mode.sync_enable)
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
switch (cs.mode.sync_mode)
|
||||
{
|
||||
case SyncMode::ResetOnGate:
|
||||
case SyncMode::ResetAndRunOnGate:
|
||||
cs.counter = 0;
|
||||
break;
|
||||
|
||||
case SyncMode::FreeRunOnGate:
|
||||
cs.mode.sync_enable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCountingEnabled(cs);
|
||||
}
|
||||
}
|
||||
|
||||
void Timers::AddTicks(u32 timer, u32 count)
|
||||
{
|
||||
CounterState& cs = m_states[timer];
|
||||
cs.counter += count;
|
||||
|
||||
const u32 reset_value = cs.mode.reset_at_target ? cs.target : u32(0xFFFF);
|
||||
if (cs.counter < reset_value)
|
||||
return;
|
||||
|
||||
const bool old_intr = cs.mode.interrupt_request;
|
||||
|
||||
if (cs.counter >= cs.target)
|
||||
cs.mode.reached_target = true;
|
||||
if (cs.counter >= u32(0xFFFF))
|
||||
cs.mode.reached_overflow = true;
|
||||
|
||||
// TODO: Non-repeat mode.
|
||||
const bool target_intr = cs.mode.reached_target & cs.mode.irq_at_target;
|
||||
const bool overflow_intr = cs.mode.reached_overflow & cs.mode.irq_on_overflow;
|
||||
const bool new_intr = target_intr | overflow_intr;
|
||||
if (!old_intr && new_intr)
|
||||
{
|
||||
m_interrupt_controller->InterruptRequest(
|
||||
static_cast<InterruptController::IRQ>(static_cast<u32>(InterruptController::IRQ::TMR0) + timer));
|
||||
}
|
||||
|
||||
if (reset_value > 0)
|
||||
cs.counter = cs.counter % reset_value;
|
||||
else
|
||||
cs.counter = 0;
|
||||
}
|
||||
|
||||
u32 Timers::ReadRegister(u32 offset)
|
||||
{
|
||||
const u32 timer_index = (offset >> 4) & u32(0x03);
|
||||
const u32 port_offset = offset & u32(0x0F);
|
||||
|
||||
CounterState& cs = m_states[timer_index];
|
||||
|
||||
switch (port_offset)
|
||||
{
|
||||
case 0x00:
|
||||
return cs.counter;
|
||||
|
||||
case 0x04:
|
||||
{
|
||||
const u32 bits = cs.mode.bits;
|
||||
cs.mode.reached_overflow = false;
|
||||
cs.mode.reached_target = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
return cs.target;
|
||||
|
||||
default:
|
||||
Log_ErrorPrintf("Read unknown register in timer %u (offset 0x%02X)", offset);
|
||||
return UINT32_C(0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
void Timers::WriteRegister(u32 offset, u32 value)
|
||||
{
|
||||
const u32 timer_index = (offset >> 4) & u32(0x03);
|
||||
const u32 port_offset = offset & u32(0x0F);
|
||||
|
||||
CounterState& cs = m_states[timer_index];
|
||||
|
||||
switch (port_offset)
|
||||
{
|
||||
case 0x00:
|
||||
Log_DebugPrintf("Timer %u write counter %u", timer_index, value);
|
||||
cs.counter = value & u32(0xFFFF);
|
||||
break;
|
||||
|
||||
case 0x04:
|
||||
{
|
||||
Log_DebugPrintf("Timer %u write mode register 0x%04X", timer_index, value);
|
||||
cs.mode.bits = value & u32(0x1FFF);
|
||||
cs.use_external_clock = (cs.mode.clock_source & (timer_index == 2 ? 2 : 1)) != 0;
|
||||
cs.counter = 0;
|
||||
UpdateCountingEnabled(cs);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
Log_DebugPrintf("Timer %u write target 0x%04X", timer_index, ZeroExtend32(Truncate16(value)));
|
||||
cs.target = value & u32(0xFFFF);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log_ErrorPrintf("Write unknown register in timer %u (offset 0x%02X, value 0x%X)", offset, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Timers::UpdateCountingEnabled(CounterState& cs)
|
||||
{
|
||||
if (cs.mode.sync_enable)
|
||||
{
|
||||
switch (cs.mode.sync_mode)
|
||||
{
|
||||
case SyncMode::PauseOnGate:
|
||||
case SyncMode::FreeRunOnGate:
|
||||
cs.counting_enabled = !cs.gate;
|
||||
break;
|
||||
|
||||
case SyncMode::ResetOnGate:
|
||||
cs.counting_enabled = true;
|
||||
break;
|
||||
|
||||
case SyncMode::ResetAndRunOnGate:
|
||||
cs.counting_enabled = cs.gate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cs.counting_enabled = true;
|
||||
}
|
||||
|
||||
cs.external_counting_enabled = cs.use_external_clock && cs.counting_enabled;
|
||||
}
|
||||
|
||||
void Timers::UpdateDowncount() {}
|
||||
|
||||
u32 Timers::GetSystemTicksForTimerTicks(u32 timer) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
#include "common/bitfield.h"
|
||||
#include "types.h"
|
||||
#include <array>
|
||||
|
||||
class StateWrapper;
|
||||
|
||||
class InterruptController;
|
||||
|
||||
class Timers
|
||||
{
|
||||
public:
|
||||
Timers();
|
||||
~Timers();
|
||||
|
||||
bool Initialize(InterruptController* interrupt_controller);
|
||||
void Reset();
|
||||
bool DoState(StateWrapper& sw);
|
||||
|
||||
void SetGate(u32 timer, bool state);
|
||||
|
||||
// dot clock/hblank/sysclk div 8
|
||||
bool IsUsingExternalClock(u32 timer) const { return m_states[timer].external_counting_enabled; }
|
||||
void AddTicks(u32 timer, u32 ticks);
|
||||
|
||||
u32 ReadRegister(u32 offset);
|
||||
void WriteRegister(u32 offset, u32 value);
|
||||
|
||||
private:
|
||||
static constexpr u32 NUM_TIMERS = 3;
|
||||
|
||||
enum class SyncMode : u8
|
||||
{
|
||||
PauseOnGate = 0,
|
||||
ResetOnGate = 1,
|
||||
ResetAndRunOnGate = 2,
|
||||
FreeRunOnGate = 3
|
||||
};
|
||||
|
||||
union CounterMode
|
||||
{
|
||||
u32 bits;
|
||||
|
||||
BitField<u32, bool, 0, 1> sync_enable;
|
||||
BitField<u32, SyncMode, 1, 2> sync_mode;
|
||||
BitField<u32, bool, 3, 1> reset_at_target;
|
||||
BitField<u32, bool, 4, 1> irq_at_target;
|
||||
BitField<u32, bool, 5, 1> irq_on_overflow;
|
||||
BitField<u32, bool, 6, 1> irq_repeat;
|
||||
BitField<u32, bool, 7, 1> irq_pulse;
|
||||
BitField<u32, u8, 8, 2> clock_source;
|
||||
BitField<u32, bool, 10, 1> interrupt_request;
|
||||
BitField<u32, bool, 11, 1> reached_target;
|
||||
BitField<u32, bool, 12, 1> reached_overflow;
|
||||
};
|
||||
|
||||
struct CounterState
|
||||
{
|
||||
CounterMode mode;
|
||||
u32 counter;
|
||||
u32 target;
|
||||
bool gate;
|
||||
bool use_external_clock;
|
||||
bool external_counting_enabled;
|
||||
bool counting_enabled;
|
||||
};
|
||||
|
||||
void UpdateCountingEnabled(CounterState& cs);
|
||||
|
||||
void UpdateDowncount();
|
||||
u32 GetSystemTicksForTimerTicks(u32 timer) const;
|
||||
|
||||
InterruptController* m_interrupt_controller = nullptr;
|
||||
|
||||
std::array<CounterState, NUM_TIMERS> m_states{};
|
||||
};
|
||||
Loading…
Reference in New Issue