Implemented PTIMER alarm interrupt of NV2A + fixed a bug in timer_init

This fixes DOAU showing the dirty disk error in PAL50 mode
This commit is contained in:
ergo720 2023-03-28 00:02:34 +02:00
parent ccd77fcf4d
commit 709108b045
6 changed files with 60 additions and 15 deletions

View File

@ -42,8 +42,8 @@
#include "core\hle\DSOUND\DirectSound\DirectSoundGlobal.hpp"
static uint64_t last_qpc; // last time when QPC was called
static uint64_t exec_time; // total execution time in us since the emulation started
static std::atomic_uint64_t last_qpc; // last time when QPC was called
static std::atomic_uint64_t exec_time; // total execution time in us since the emulation started
static uint64_t pit_last; // last time when the pit time was updated
static uint64_t pit_last_qpc; // last QPC time of the pit
// The frequency of the high resolution clock of the host, and the start time
@ -53,8 +53,8 @@ int64_t HostQPCFrequency, HostQPCStartTime;
void timer_init()
{
QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER *>(&HostQPCFrequency));
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&last_qpc));
pit_last_qpc = last_qpc;
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&HostQPCStartTime));
pit_last_qpc = last_qpc = HostQPCStartTime;
pit_last = get_now();
// Synchronize xbox system time with host time
@ -140,11 +140,12 @@ uint64_t get_now()
static uint64_t get_next(uint64_t now)
{
std::array<uint64_t, 4> next;
std::array<uint64_t, 5> next;
next[0] = g_NV2A->vblank_next(now);
next[1] = pit_next(now);
next[2] = g_USB0->m_HostController->OHCI_next(now);
next[3] = dsound_next(now);
next[1] = g_NV2A->ptimer_next(now);
next[2] = pit_next(now);
next[3] = g_USB0->m_HostController->OHCI_next(now);
next[4] = dsound_next(now);
return *std::min_element(next.begin(), next.end());
}

View File

@ -82,6 +82,7 @@ DEVICE_WRITE32(PRAMDAC)
} else {
d->pramdac.core_clock_freq = (NV2A_CRYSTAL_FREQ * n)
/ (1 << p) / m;
d->ptimer_period = ((uint64_t(d->ptimer.alarm_time >> 5) * SCALE_S_IN_US) / d->pramdac.core_clock_freq);
}
break;

View File

@ -35,17 +35,13 @@
#include "common\util\CxbxUtil.h"
#define NANOSECONDS_PER_SECOND 1000000000
/* PTIMER - time measurement and time-based alarms */
static uint64_t ptimer_get_clock(NV2AState * d)
static uint64_t ptimer_get_clock(NV2AState *d)
{
// Get time in nanoseconds
uint64_t time = std::chrono::duration<uint64_t, std::nano>(std::chrono::steady_clock::now().time_since_epoch()).count();
return Muldiv64(Muldiv64(time,
return Muldiv64(Muldiv64(get_now(),
(uint32_t)d->pramdac.core_clock_freq, // TODO : Research how this can be updated to accept uint64_t
NANOSECONDS_PER_SECOND), // Was CLOCKS_PER_SEC
SCALE_S_IN_US), // Was CLOCKS_PER_SEC
d->ptimer.denominator,
d->ptimer.numerator);
}
@ -91,6 +87,13 @@ DEVICE_WRITE32(PTIMER)
break;
case NV_PTIMER_INTR_EN_0:
d->ptimer.enabled_interrupts = value;
if (d->ptimer.enabled_interrupts & NV_PTIMER_INTR_EN_0_ALARM) {
d->ptimer_last = get_now();
d->ptimer_active = true;
}
else if ((d->ptimer.enabled_interrupts & NV_PTIMER_INTR_EN_0_ALARM) == 0) {
d->ptimer_active = false;
}
update_irq(d);
break;
case NV_PTIMER_DENOMINATOR:
@ -101,6 +104,7 @@ DEVICE_WRITE32(PTIMER)
break;
case NV_PTIMER_ALARM_0:
d->ptimer.alarm_time = value;
d->ptimer_period = ((uint64_t(d->ptimer.alarm_time >> 5) * SCALE_S_IN_US) / d->pramdac.core_clock_freq);
break;
default:
//DEVICE_WRITE32_REG(ptimer); // Was : DEBUG_WRITE32_UNHANDLED(PTIMER);

View File

@ -130,6 +130,14 @@ static void update_irq(NV2AState *d)
d->pmc.pending_interrupts &= ~NV_PMC_INTR_0_PVIDEO;
}
/* PTIMER */
if (d->ptimer.pending_interrupts & d->ptimer.enabled_interrupts) {
d->pmc.pending_interrupts |= NV_PMC_INTR_0_PTIMER;
}
else {
d->pmc.pending_interrupts &= ~NV_PMC_INTR_0_PTIMER;
}
/* TODO : PBUS * /
if (d->pbus.pending_interrupts & d->pbus.enabled_interrupts) {
d->pmc.pending_interrupts |= NV_PMC_INTR_0_PBUS;
@ -1401,3 +1409,30 @@ uint64_t NV2ADevice::vblank_next(uint64_t now)
return m_nv2a_state->vblank_last + vblank_period - now; // time remaining until next vblank
}
uint64_t NV2ADevice::ptimer_next(uint64_t now)
{
// Test case: Dead or Alive Ultimate uses this when in PAL50 mode only
if (m_nv2a_state->ptimer_active) {
const uint64_t ptimer_period = m_nv2a_state->ptimer_period;
uint64_t next = m_nv2a_state->ptimer_last + ptimer_period;
if (now >= next) {
if (!m_nv2a_state->exiting) [[likely]] {
m_nv2a_state->ptimer.pending_interrupts |= NV_PTIMER_INTR_0_ALARM;
update_irq(m_nv2a_state);
// trigger the gpu interrupt if it was asserted in update_irq
if (g_bEnableAllInterrupts && HalSystemInterrupts[3].IsPending() && EmuInterruptList[3] && EmuInterruptList[3]->Connected) {
HalSystemInterrupts[3].Trigger(EmuInterruptList[3]);
}
}
m_nv2a_state->ptimer_last = get_now();
return ptimer_period;
}
return m_nv2a_state->ptimer_last + ptimer_period - now; // time remaining until next ptimer interrupt
}
return -1;
}

View File

@ -110,6 +110,7 @@ public:
static int GetFrameHeight(NV2AState *d);
uint64_t vblank_next(uint64_t now);
uint64_t ptimer_next(uint64_t now);
private:
NV2AState *m_nv2a_state;

View File

@ -363,6 +363,9 @@ typedef struct NV2AState {
// qemu_irq irq;
bool exiting;
bool enable_overlay = false;
bool ptimer_active = false;
uint64_t ptimer_last;
uint64_t ptimer_period;
// VGACommonState vga;
// GraphicHwOps hw_ops;