Basic timer implementation
This commit is contained in:
parent
ad652c47ed
commit
ad316162f3
|
@ -10,6 +10,7 @@
|
|||
#include "gpu.h"
|
||||
#include "interrupt_controller.h"
|
||||
#include "pad.h"
|
||||
#include "timers.h"
|
||||
#include <cstdio>
|
||||
Log_SetChannel(Bus);
|
||||
|
||||
|
@ -25,7 +26,8 @@ Bus::Bus() = default;
|
|||
|
||||
Bus::~Bus() = default;
|
||||
|
||||
bool Bus::Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, Pad* pad)
|
||||
bool Bus::Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom,
|
||||
Pad* pad, Timers* timers)
|
||||
{
|
||||
if (!LoadBIOS())
|
||||
return false;
|
||||
|
@ -36,6 +38,7 @@ bool Bus::Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_co
|
|||
m_gpu = gpu;
|
||||
m_cdrom = cdrom;
|
||||
m_pad = pad;
|
||||
m_timers = timers;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -274,6 +277,20 @@ bool Bus::DoWriteInterruptController(MemoryAccessSize size, u32 offset, u32 valu
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Bus::DoReadTimers(MemoryAccessSize size, u32 offset, u32& value)
|
||||
{
|
||||
FixupUnalignedWordAccessW32(offset, value);
|
||||
value = m_timers->ReadRegister(offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bus::DoWriteTimers(MemoryAccessSize size, u32 offset, u32 value)
|
||||
{
|
||||
FixupUnalignedWordAccessW32(offset, value);
|
||||
m_timers->WriteRegister(offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bus::ReadSPU(MemoryAccessSize size, u32 offset, u32& value)
|
||||
{
|
||||
if (offset == 0x1AE)
|
||||
|
|
|
@ -16,6 +16,7 @@ class InterruptController;
|
|||
class GPU;
|
||||
class CDROM;
|
||||
class Pad;
|
||||
class Timers;
|
||||
class System;
|
||||
|
||||
class Bus
|
||||
|
@ -24,7 +25,7 @@ public:
|
|||
Bus();
|
||||
~Bus();
|
||||
|
||||
bool Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, Pad* pad);
|
||||
bool Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, Pad* pad, Timers* timers);
|
||||
void Reset();
|
||||
bool DoState(StateWrapper& sw);
|
||||
|
||||
|
@ -50,6 +51,9 @@ private:
|
|||
static constexpr u32 DMA_BASE = 0x1F801080;
|
||||
static constexpr u32 DMA_SIZE = 0x80;
|
||||
static constexpr u32 DMA_MASK = DMA_SIZE - 1;
|
||||
static constexpr u32 TIMERS_BASE = 0x1F801100;
|
||||
static constexpr u32 TIMERS_SIZE = 0x40;
|
||||
static constexpr u32 TIMERS_MASK = TIMERS_SIZE - 1;
|
||||
static constexpr u32 CDROM_BASE = 0x1F801800;
|
||||
static constexpr u32 CDROM_SIZE = 0x04;
|
||||
static constexpr u32 CDROM_MASK = CDROM_SIZE - 1;
|
||||
|
@ -94,6 +98,9 @@ private:
|
|||
bool DoReadDMA(MemoryAccessSize size, u32 offset, u32& value);
|
||||
bool DoWriteDMA(MemoryAccessSize size, u32 offset, u32 value);
|
||||
|
||||
bool DoReadTimers(MemoryAccessSize size, u32 offset, u32& value);
|
||||
bool DoWriteTimers(MemoryAccessSize size, u32 offset, u32 value);
|
||||
|
||||
bool ReadSPU(MemoryAccessSize size, u32 offset, u32& value);
|
||||
bool WriteSPU(MemoryAccessSize size, u32 offset, u32 value);
|
||||
|
||||
|
@ -103,6 +110,7 @@ private:
|
|||
GPU* m_gpu = nullptr;
|
||||
CDROM* m_cdrom = nullptr;
|
||||
Pad* m_pad = nullptr;
|
||||
Timers* m_timers = nullptr;
|
||||
|
||||
std::array<u8, 2097152> m_ram{}; // 2MB RAM
|
||||
std::array<u8, 524288> m_bios{}; // 512K BIOS ROM
|
||||
|
|
|
@ -108,6 +108,15 @@ bool Bus::DispatchAccess(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddres
|
|||
return (type == MemoryAccessType::Read) ? DoReadDMA(size, bus_address & DMA_MASK, value) :
|
||||
DoWriteDMA(size, bus_address & DMA_MASK, value);
|
||||
}
|
||||
else if (bus_address < TIMERS_BASE)
|
||||
{
|
||||
return DoInvalidAccess(type, size, cpu_address, bus_address, value);
|
||||
}
|
||||
else if (bus_address < (TIMERS_BASE + TIMERS_SIZE))
|
||||
{
|
||||
return (type == MemoryAccessType::Read) ? DoReadTimers(size, bus_address & TIMERS_MASK, value) :
|
||||
DoWriteTimers(size, bus_address & TIMERS_MASK, value);
|
||||
}
|
||||
else if (bus_address < CDROM_BASE)
|
||||
{
|
||||
return DoInvalidAccess(type, size, cpu_address, bus_address, value);
|
||||
|
|
|
@ -304,6 +304,17 @@ void CDROM::ExecuteTestCommand(u8 subcommand)
|
|||
return;
|
||||
}
|
||||
|
||||
case 0x22:
|
||||
{
|
||||
Log_DebugPrintf("Get CDROM region ID string");
|
||||
static constexpr u8 response[] = {'f', 'o', 'r', ' ', 'U', '/', 'C'};
|
||||
m_response_fifo.PushRange(response, countof(response));
|
||||
m_param_fifo.Clear();
|
||||
SetInterrupt(Interrupt::INT3);
|
||||
UpdateStatusRegister();
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
Log_ErrorPrintf("Unknown test command 0x%02X", subcommand);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "interrupt_controller.h"
|
||||
#include "stb_image_write.h"
|
||||
#include "system.h"
|
||||
#include "timers.h"
|
||||
Log_SetChannel(GPU);
|
||||
|
||||
bool GPU::DUMP_CPU_TO_VRAM_COPIES = false;
|
||||
|
@ -16,11 +17,12 @@ GPU::GPU() = default;
|
|||
|
||||
GPU::~GPU() = default;
|
||||
|
||||
bool GPU::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller)
|
||||
bool GPU::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, Timers* timers)
|
||||
{
|
||||
m_system = system;
|
||||
m_dma = dma;
|
||||
m_interrupt_controller = interrupt_controller;
|
||||
m_timers = timers;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -254,12 +256,14 @@ void GPU::UpdateCRTCConfig()
|
|||
void GPU::UpdateSliceTicks()
|
||||
{
|
||||
// the next event is at the end of the next scanline
|
||||
// const TickCount ticks_until_next_event = m_crtc_state.ticks_per_scanline - m_crtc_state.current_tick_in_scanline;
|
||||
|
||||
#if 1
|
||||
const TickCount ticks_until_next_event = m_crtc_state.ticks_per_scanline - m_crtc_state.current_tick_in_scanline;
|
||||
#else
|
||||
// or at vblank. this will depend on the timer config..
|
||||
const TickCount ticks_until_next_event =
|
||||
((m_crtc_state.total_scanlines_per_frame - m_crtc_state.current_scanline) * m_crtc_state.ticks_per_scanline) -
|
||||
m_crtc_state.current_tick_in_scanline;
|
||||
#endif
|
||||
|
||||
// convert to master clock, rounding up as we want to overshoot not undershoot
|
||||
const TickCount system_ticks = (ticks_until_next_event * 7 + 10) / 11;
|
||||
|
@ -279,13 +283,22 @@ void GPU::Execute(TickCount ticks)
|
|||
{
|
||||
m_crtc_state.current_tick_in_scanline -= m_crtc_state.ticks_per_scanline;
|
||||
m_crtc_state.current_scanline++;
|
||||
if (m_timers->IsUsingExternalClock(HBLANK_TIMER_INDEX))
|
||||
m_timers->AddTicks(HBLANK_TIMER_INDEX, 1);
|
||||
|
||||
const bool old_vblank = m_crtc_state.in_vblank;
|
||||
m_crtc_state.in_vblank = m_crtc_state.current_scanline >= m_crtc_state.visible_vertical_resolution;
|
||||
if (m_crtc_state.in_vblank && !old_vblank)
|
||||
const bool new_vblank = m_crtc_state.current_scanline >= m_crtc_state.visible_vertical_resolution;
|
||||
if (new_vblank != old_vblank)
|
||||
{
|
||||
Log_DebugPrintf("Now in v-blank");
|
||||
m_interrupt_controller->InterruptRequest(InterruptController::IRQ::VBLANK);
|
||||
m_crtc_state.in_vblank = new_vblank;
|
||||
|
||||
if (!old_vblank)
|
||||
{
|
||||
Log_DebugPrintf("Now in v-blank");
|
||||
m_interrupt_controller->InterruptRequest(InterruptController::IRQ::VBLANK);
|
||||
}
|
||||
|
||||
m_timers->SetGate(HBLANK_TIMER_INDEX, new_vblank);
|
||||
}
|
||||
|
||||
// past the end of vblank?
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include "common/bitfield.h"
|
||||
#include "timers.h"
|
||||
#include "types.h"
|
||||
#include <array>
|
||||
#include <deque>
|
||||
|
@ -10,6 +11,7 @@ class StateWrapper;
|
|||
class System;
|
||||
class DMA;
|
||||
class InterruptController;
|
||||
class Timers;
|
||||
|
||||
class GPU
|
||||
{
|
||||
|
@ -17,7 +19,7 @@ public:
|
|||
GPU();
|
||||
virtual ~GPU();
|
||||
|
||||
virtual bool Initialize(System* system, DMA* dma, InterruptController* interrupt_controller);
|
||||
virtual bool Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, Timers* timers);
|
||||
virtual void Reset();
|
||||
virtual bool DoState(StateWrapper& sw);
|
||||
|
||||
|
@ -39,6 +41,8 @@ protected:
|
|||
static constexpr u32 VRAM_SIZE = VRAM_WIDTH * VRAM_HEIGHT * sizeof(u16);
|
||||
static constexpr u32 TEXTURE_PAGE_WIDTH = 256;
|
||||
static constexpr u32 TEXTURE_PAGE_HEIGHT = 256;
|
||||
static constexpr u32 DOT_TIMER_INDEX = 0;
|
||||
static constexpr u32 HBLANK_TIMER_INDEX = 1;
|
||||
|
||||
static constexpr s32 S11ToS32(u32 value)
|
||||
{
|
||||
|
@ -184,6 +188,7 @@ protected:
|
|||
System* m_system = nullptr;
|
||||
DMA* m_dma = nullptr;
|
||||
InterruptController* m_interrupt_controller = nullptr;
|
||||
Timers* m_timers = nullptr;
|
||||
|
||||
union GPUSTAT
|
||||
{
|
||||
|
|
|
@ -12,9 +12,9 @@ GPU_HW_OpenGL::~GPU_HW_OpenGL()
|
|||
DestroyFramebuffer();
|
||||
}
|
||||
|
||||
bool GPU_HW_OpenGL::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller)
|
||||
bool GPU_HW_OpenGL::Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, Timers* timers)
|
||||
{
|
||||
if (!GPU_HW::Initialize(system, dma, interrupt_controller))
|
||||
if (!GPU_HW::Initialize(system, dma, interrupt_controller, timers))
|
||||
return false;
|
||||
|
||||
CreateFramebuffer();
|
||||
|
|
|
@ -13,7 +13,7 @@ public:
|
|||
GPU_HW_OpenGL();
|
||||
~GPU_HW_OpenGL() override;
|
||||
|
||||
bool Initialize(System* system, DMA* dma, InterruptController* interrupt_controller) override;
|
||||
bool Initialize(System* system, DMA* dma, InterruptController* interrupt_controller, Timers* timers) override;
|
||||
void Reset() override;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
<ClCompile Include="pad.cpp" />
|
||||
<ClCompile Include="pad_device.cpp" />
|
||||
<ClCompile Include="system.cpp" />
|
||||
<ClCompile Include="timers.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="bus.h" />
|
||||
|
@ -70,6 +71,7 @@
|
|||
<ClInclude Include="pad_device.h" />
|
||||
<ClInclude Include="save_state_version.h" />
|
||||
<ClInclude Include="system.h" />
|
||||
<ClInclude Include="timers.h" />
|
||||
<ClInclude Include="types.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<ClCompile Include="pad.cpp" />
|
||||
<ClCompile Include="pad_device.cpp" />
|
||||
<ClCompile Include="digital_controller.cpp" />
|
||||
<ClCompile Include="timers.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="types.h" />
|
||||
|
@ -37,6 +38,7 @@
|
|||
<ClInclude Include="pad.h" />
|
||||
<ClInclude Include="pad_device.h" />
|
||||
<ClInclude Include="digital_controller.h" />
|
||||
<ClInclude Include="timers.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="cpu_core.inl" />
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "interrupt_controller.h"
|
||||
#include "pad.h"
|
||||
#include "pad_device.h"
|
||||
#include "timers.h"
|
||||
|
||||
System::System(HostInterface* host_interface) : m_host_interface(host_interface)
|
||||
{
|
||||
|
@ -20,6 +21,7 @@ System::System(HostInterface* host_interface) : m_host_interface(host_interface)
|
|||
m_gpu = GPU::CreateHardwareOpenGLRenderer();
|
||||
m_cdrom = std::make_unique<CDROM>();
|
||||
m_pad = std::make_unique<Pad>();
|
||||
m_timers = std::make_unique<Timers>();
|
||||
}
|
||||
|
||||
System::~System() = default;
|
||||
|
@ -30,7 +32,7 @@ bool System::Initialize()
|
|||
return false;
|
||||
|
||||
if (!m_bus->Initialize(m_cpu.get(), m_dma.get(), m_interrupt_controller.get(), m_gpu.get(), m_cdrom.get(),
|
||||
m_pad.get()))
|
||||
m_pad.get(), m_timers.get()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -41,7 +43,7 @@ bool System::Initialize()
|
|||
if (!m_interrupt_controller->Initialize(m_cpu.get()))
|
||||
return false;
|
||||
|
||||
if (!m_gpu->Initialize(this, m_dma.get(), m_interrupt_controller.get()))
|
||||
if (!m_gpu->Initialize(this, m_dma.get(), m_interrupt_controller.get(), m_timers.get()))
|
||||
return false;
|
||||
|
||||
if (!m_cdrom->Initialize(m_dma.get(), m_interrupt_controller.get()))
|
||||
|
@ -50,6 +52,9 @@ bool System::Initialize()
|
|||
if (!m_pad->Initialize(m_interrupt_controller.get()))
|
||||
return false;
|
||||
|
||||
if (!m_timers->Initialize(m_interrupt_controller.get()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -76,6 +81,9 @@ bool System::DoState(StateWrapper& sw)
|
|||
if (!sw.DoMarker("Pad") || !m_pad->DoState(sw))
|
||||
return false;
|
||||
|
||||
if (!sw.DoMarker("Timers") || !m_timers->DoState(sw))
|
||||
return false;
|
||||
|
||||
return !sw.HasError();
|
||||
}
|
||||
|
||||
|
@ -90,6 +98,7 @@ void System::Reset()
|
|||
m_gpu->Reset();
|
||||
m_cdrom->Reset();
|
||||
m_pad->Reset();
|
||||
m_timers->Reset();
|
||||
m_frame_number = 1;
|
||||
}
|
||||
|
||||
|
@ -113,7 +122,9 @@ void System::RunFrame()
|
|||
const TickCount pending_ticks = m_cpu->Execute();
|
||||
|
||||
// run pending ticks from CPU for other components
|
||||
m_gpu->Execute(pending_ticks);
|
||||
m_gpu->Execute(pending_ticks * 3);
|
||||
|
||||
m_timers->AddTicks(2, m_timers->IsUsingExternalClock(2) ? (pending_ticks / 8) : pending_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ class GPU;
|
|||
class CDROM;
|
||||
class Pad;
|
||||
class PadDevice;
|
||||
class Timers;
|
||||
|
||||
class System
|
||||
{
|
||||
|
@ -60,5 +61,6 @@ private:
|
|||
std::unique_ptr<GPU> m_gpu;
|
||||
std::unique_ptr<CDROM> m_cdrom;
|
||||
std::unique_ptr<Pad> m_pad;
|
||||
std::unique_ptr<Timers> m_timers;
|
||||
u32 m_frame_number = 1;
|
||||
};
|
||||
|
|
|
@ -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