mirror of https://github.com/PCSX2/pcsx2.git
811 lines
22 KiB
C++
811 lines
22 KiB
C++
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
// Note on INTC usage: All counters code is always called from inside the context of an
|
|
// event test, so instead of using the iopTestIntc we just set the 0x1070 flags directly.
|
|
// The EventText function will pick it up.
|
|
|
|
#include "IopCounters.h"
|
|
#include "R3000A.h"
|
|
#include "Common.h"
|
|
#include "SPU2/spu2.h"
|
|
#include "DEV9/DEV9.h"
|
|
#include "USB/USB.h"
|
|
#include "IopHw.h"
|
|
#include "IopDma.h"
|
|
#include "CDVD/CDVD.h"
|
|
|
|
#include <math.h>
|
|
|
|
/* Config.PsxType == 1: PAL:
|
|
VBlank interlaced 50.00 Hz
|
|
VBlank non-interlaced 49.76 Hz
|
|
HBlank 15.625 KHz
|
|
Config.PsxType == 0: NSTC
|
|
VBlank interlaced 59.94 Hz
|
|
VBlank non-interlaced 59.82 Hz
|
|
HBlank 15.73426573 KHz */
|
|
|
|
// Misc IOP Clocks
|
|
// FIXME: this divider is actually 2.73 (36864000 / 13500000), but not sure what uses it, so this'll do, we should maybe change things to float.
|
|
#define PSXPIXEL 3
|
|
#define PSXSOUNDCLK ((int)(48000))
|
|
|
|
psxCounter psxCounters[NUM_COUNTERS];
|
|
s32 psxNextDeltaCounter;
|
|
u32 psxNextStartCounter;
|
|
|
|
bool hBlanking = false;
|
|
bool vBlanking = false;
|
|
|
|
// flags when the gate is off or counter disabled. (do not count)
|
|
#define IOPCNT_STOPPED (0x10000000ul)
|
|
|
|
// used to disable targets until after an overflow
|
|
#define IOPCNT_FUTURE_TARGET (0x1000000000ULL)
|
|
#define IOPCNT_MODE_WRITE_MSK 0x63FF
|
|
#define IOPCNT_MODE_FLAG_MSK 0x1C00
|
|
|
|
#define IOPCNT_GATE_CNT_LOW 0 // Counts only when signal is low (V/H RENDER).
|
|
#define IOPCNT_GATE_CLR_END 1 // Counts continuously, clears at end of BLANK.
|
|
#define IOPCNT_GATE_CNT_HIGH_ZERO_OFF 2 // Starts at beginning of next BLANK, counts only during BLANK, returns zero any other time.
|
|
#define IOPCNT_GATE_START_AT_END 3 // Starts at end of next BLANK, continuous count, no clear.
|
|
|
|
// Use an arbitrary value to flag HBLANK counters.
|
|
// These counters will be counted by the hblank gates coming from the EE,
|
|
// which ensures they stay 100% in sync with the EE's hblank counters.
|
|
#define PSXHBLANK 0x2001
|
|
|
|
static bool psxRcntCanCount(int cntidx)
|
|
{
|
|
if (psxCounters[cntidx].mode.stopped)
|
|
return false;
|
|
|
|
if (!(psxCounters[cntidx].mode.gateEnable))
|
|
return true;
|
|
|
|
const u32 gateMode = psxCounters[cntidx].mode.gateMode;
|
|
|
|
if (cntidx == 2 || cntidx == 4 || cntidx == 5)
|
|
{
|
|
// Gates being enabled on these counters forces it to disable the counter if being on or off depends on a gate being on or off.
|
|
return (gateMode & 1);
|
|
}
|
|
|
|
const bool blanking = cntidx == 0 ? hBlanking : vBlanking;
|
|
|
|
// Stop counting if Gate mode 0 (only count when rendering) and blanking or Gate mode 2 (only count when blanking) when not blanking
|
|
if ((gateMode == IOPCNT_GATE_CNT_LOW && blanking == true) || (gateMode == IOPCNT_GATE_CNT_HIGH_ZERO_OFF && blanking == false))
|
|
return false;
|
|
|
|
// All other cases allow counting.
|
|
return true;
|
|
}
|
|
|
|
static void psxRcntSync(int cntidx)
|
|
{
|
|
if ((psxCounters[cntidx].currentIrqMode.repeatInterrupt) && !(psxCounters[cntidx].currentIrqMode.toggleInterrupt))
|
|
{ //Repeat IRQ mode Pulsed, resets a few cycles after the interrupt, this should do.
|
|
psxCounters[cntidx].mode.intrEnable = true;
|
|
}
|
|
|
|
if (psxRcntCanCount(cntidx) && psxCounters[cntidx].rate != PSXHBLANK)
|
|
{
|
|
const u32 change = (psxRegs.cycle - psxCounters[cntidx].startCycle) / psxCounters[cntidx].rate;
|
|
if (change > 0)
|
|
{
|
|
psxCounters[cntidx].count += change;
|
|
psxCounters[cntidx].startCycle += change * psxCounters[cntidx].rate;
|
|
|
|
psxCounters[cntidx].startCycle &= ~(psxCounters[cntidx].rate - 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (psxCounters[cntidx].mode.gateEnable && psxCounters[cntidx].mode.gateMode == IOPCNT_GATE_CNT_HIGH_ZERO_OFF)
|
|
psxCounters[cntidx].count = 0;
|
|
|
|
psxCounters[cntidx].startCycle = psxRegs.cycle;
|
|
}
|
|
}
|
|
|
|
static void _rcntSet(int cntidx)
|
|
{
|
|
u64 overflowCap = (cntidx >= 3) ? 0x100000000ULL : 0x10000;
|
|
u64 c;
|
|
|
|
const psxCounter& counter = psxCounters[cntidx];
|
|
|
|
// psxNextCounter is relative to the psxRegs.cycle when rcntUpdate() was last called.
|
|
// However, the current _rcntSet could be called at any cycle count, so we need to take
|
|
// that into account. Adding the difference from that cycle count to the current one
|
|
// will do the trick!
|
|
|
|
if (counter.rate == PSXHBLANK || !psxRcntCanCount(cntidx))
|
|
return;
|
|
|
|
// check for special cases where the overflow or target has just passed
|
|
// (we probably missed it because we're doing/checking other things)
|
|
if (counter.count > overflowCap || counter.count > counter.target)
|
|
{
|
|
psxNextDeltaCounter = 4;
|
|
return;
|
|
}
|
|
|
|
c = (u64)((overflowCap - counter.count) * counter.rate) - (psxRegs.cycle - counter.startCycle);
|
|
c += psxRegs.cycle - psxNextStartCounter; // adjust for time passed since last rcntUpdate();
|
|
|
|
if (c < (u64)psxNextDeltaCounter)
|
|
{
|
|
psxNextDeltaCounter = (u32)c;
|
|
psxSetNextBranch(psxNextStartCounter, psxNextDeltaCounter); //Need to update on counter resets/target changes
|
|
}
|
|
|
|
if (counter.target & IOPCNT_FUTURE_TARGET)
|
|
return;
|
|
|
|
c = (s64)((counter.target - counter.count) * counter.rate) - (psxRegs.cycle - counter.startCycle);
|
|
c += psxRegs.cycle - psxNextStartCounter; // adjust for time passed since last rcntUpdate();
|
|
|
|
if (c < (u64)psxNextDeltaCounter)
|
|
{
|
|
psxNextDeltaCounter = (u32)c;
|
|
psxSetNextBranch(psxNextStartCounter, psxNextDeltaCounter); //Need to update on counter resets/target changes
|
|
}
|
|
}
|
|
|
|
|
|
void psxRcntInit()
|
|
{
|
|
int i;
|
|
|
|
std::memset(psxCounters, 0, sizeof(psxCounters));
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
psxCounters[i].rate = 1;
|
|
psxCounters[i].mode.intrEnable = true;
|
|
psxCounters[i].target = IOPCNT_FUTURE_TARGET;
|
|
psxCounters[i].currentIrqMode.repeatInterrupt = false;
|
|
psxCounters[i].currentIrqMode.toggleInterrupt = false;
|
|
}
|
|
for (i = 3; i < 6; i++)
|
|
{
|
|
psxCounters[i].rate = 1;
|
|
psxCounters[i].mode.intrEnable = true;
|
|
psxCounters[i].target = IOPCNT_FUTURE_TARGET;
|
|
psxCounters[i].currentIrqMode.repeatInterrupt = false;
|
|
psxCounters[i].currentIrqMode.toggleInterrupt = false;
|
|
}
|
|
|
|
psxCounters[0].interrupt = 0x10;
|
|
psxCounters[1].interrupt = 0x20;
|
|
psxCounters[2].interrupt = 0x40;
|
|
|
|
psxCounters[3].interrupt = 0x04000;
|
|
psxCounters[4].interrupt = 0x08000;
|
|
psxCounters[5].interrupt = 0x10000;
|
|
|
|
psxCounters[6].rate = 768;
|
|
psxCounters[6].deltaCycles = psxCounters[6].rate;
|
|
psxCounters[6].mode.modeval = 0x8;
|
|
|
|
psxCounters[7].rate = PSXCLK / 1000;
|
|
psxCounters[7].deltaCycles = psxCounters[7].rate;
|
|
psxCounters[7].mode.modeval = 0x8;
|
|
|
|
for (i = 0; i < 8; i++)
|
|
psxCounters[i].startCycle = psxRegs.cycle;
|
|
|
|
// Tell the IOP to branch ASAP, so that timers can get
|
|
// configured properly.
|
|
psxNextDeltaCounter = 1;
|
|
psxNextStartCounter = psxRegs.cycle;
|
|
}
|
|
|
|
static void _rcntFireInterrupt(int i, bool isOverflow)
|
|
{
|
|
bool updateIntr = psxCounters[i].currentIrqMode.repeatInterrupt;
|
|
|
|
if (psxCounters[i].mode.intrEnable)
|
|
{
|
|
bool already_set = isOverflow ? psxCounters[i].mode.overflowFlag : psxCounters[i].mode.targetFlag;
|
|
if (updateIntr || !already_set)
|
|
{
|
|
// IRQ fired
|
|
//DevCon.Warning("Counter %d %s IRQ Fired count %x target %x psx Cycle %d", i, isOverflow ? "Overflow" : "Target", psxCounters[i].count, psxCounters[i].target, psxRegs.cycle);
|
|
psxHu32(0x1070) |= psxCounters[i].interrupt;
|
|
iopTestIntc();
|
|
}
|
|
|
|
updateIntr |= psxCounters[i].currentIrqMode.toggleInterrupt;
|
|
}
|
|
|
|
if (updateIntr)
|
|
{
|
|
if (psxCounters[i].currentIrqMode.toggleInterrupt)
|
|
{
|
|
// Toggle mode
|
|
psxCounters[i].mode.intrEnable ^= true; // Interrupt flag inverted
|
|
}
|
|
else
|
|
{
|
|
psxCounters[i].mode.intrEnable = false; // Interrupt flag set low
|
|
}
|
|
}
|
|
}
|
|
|
|
static void _rcntTestTarget(int i)
|
|
{
|
|
if (psxCounters[i].count < psxCounters[i].target)
|
|
return;
|
|
|
|
PSXCNT_LOG("IOP Counter[%d] target 0x%I64x >= 0x%I64x (mode: %x)",
|
|
i, psxCounters[i].count, psxCounters[i].target, psxCounters[i].mode.modeval);
|
|
|
|
if (psxCounters[i].mode.targetIntr)
|
|
{
|
|
// Target interrupt
|
|
_rcntFireInterrupt(i, false);
|
|
}
|
|
|
|
psxCounters[i].mode.targetFlag = true;
|
|
|
|
if (psxCounters[i].mode.zeroReturn)
|
|
{
|
|
// Reset on target
|
|
psxCounters[i].count -= psxCounters[i].target;
|
|
}
|
|
else
|
|
psxCounters[i].target |= IOPCNT_FUTURE_TARGET;
|
|
}
|
|
|
|
|
|
static __fi void _rcntTestOverflow(int i)
|
|
{
|
|
u64 maxTarget = (i < 3) ? 0xffff : 0xfffffffful;
|
|
if (psxCounters[i].count <= maxTarget)
|
|
return;
|
|
|
|
PSXCNT_LOG("IOP Counter[%d] overflow 0x%I64x >= 0x%I64x (mode: %x)",
|
|
i, psxCounters[i].count, maxTarget, psxCounters[i].mode.modeval);
|
|
|
|
if (psxCounters[i].mode.overflIntr)
|
|
{
|
|
// Overflow interrupt
|
|
_rcntFireInterrupt(i, true);
|
|
}
|
|
|
|
psxCounters[i].mode.overflowFlag = true;
|
|
|
|
// Update count.
|
|
// Count wraps around back to zero, while the target is restored (if not in one shot mode).
|
|
// (high bit of the target gets set by rcntWtarget when the target is behind
|
|
// the counter value, and thus should not be flagged until after an overflow)
|
|
psxCounters[i].count -= maxTarget + 1;
|
|
psxCounters[i].target &= maxTarget;
|
|
}
|
|
|
|
/*
|
|
Gate:
|
|
TM_NO_GATE 000
|
|
TM_GATE_ON_Count 001
|
|
TM_GATE_ON_ClearStart 011
|
|
TM_GATE_ON_Clear_OFF_Start 101
|
|
TM_GATE_ON_Start 111
|
|
|
|
= means counting
|
|
- means not counting
|
|
|
|
V-blank ----+ +----------------------------+ +------
|
|
| | | |
|
|
| | | |
|
|
+----+ +----+
|
|
TM_NO_GATE:
|
|
|
|
0================================>============
|
|
|
|
TM_GATE_ON_Count:
|
|
|
|
<---->===========================><---->======
|
|
|
|
TM_GATE_ON_ClearStart:
|
|
|
|
=====>0================================>0=====
|
|
|
|
TM_GATE_ON_Clear_OFF_Start:
|
|
|
|
0====>0-------------------------->0====>0-----
|
|
|
|
TM_GATE_ON_Start:
|
|
|
|
<---->===========================>============
|
|
*/
|
|
|
|
static void _psxCheckStartGate(int i)
|
|
{
|
|
if (!(psxCounters[i].mode.gateEnable))
|
|
return; // Ignore Gate
|
|
|
|
switch (psxCounters[i].mode.gateMode)
|
|
{
|
|
case 0x0: // GATE_ON_count - count while gate signal is low (RENDER)
|
|
|
|
// get the current count at the time of stoppage:
|
|
psxCounters[i].count = (i < 3) ?
|
|
psxRcntRcount16(i) :
|
|
psxRcntRcount32(i);
|
|
|
|
// Not strictly necessary.
|
|
psxCounters[i].startCycle = psxRegs.cycle & ~(psxCounters[i].rate - 1);
|
|
break;
|
|
|
|
case 0x1: // GATE_ON_ClearStart - Counts constantly, clears on Blank END
|
|
// do nothing!
|
|
break;
|
|
|
|
case 0x2: // GATE_ON_Clear_OFF_Start - Counts only when Blanking, clears on both ends, starts counting on next Blank Start.
|
|
psxRcntSync(i);
|
|
psxCounters[i].mode.stopped = false;
|
|
psxCounters[i].count = 0;
|
|
psxCounters[i].target &= ~IOPCNT_FUTURE_TARGET;
|
|
break;
|
|
|
|
case 0x3: //GATE_ON_Start - Starts counting when the next Blank Ends, no clear.
|
|
// do nothing!
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void _psxCheckEndGate(int i)
|
|
{
|
|
if (!(psxCounters[i].mode.gateEnable))
|
|
return; // Ignore Gate
|
|
|
|
// NOTE: Starting and stopping of modes 0 and 2 are checked in psxRcntCanCount(), only need to update the start cycle and counts.
|
|
switch (psxCounters[i].mode.gateMode)
|
|
{
|
|
case 0x0: // GATE_ON_count - count while gate signal is low (RENDER)
|
|
psxCounters[i].startCycle = psxRegs.cycle & ~(psxCounters[i].rate - 1);
|
|
break;
|
|
|
|
case 0x1: // GATE_ON_ClearStart - Counts constantly, clears on Blank END
|
|
psxRcntSync(i);
|
|
psxCounters[i].count = 0;
|
|
psxCounters[i].target &= ~IOPCNT_FUTURE_TARGET;
|
|
break;
|
|
|
|
case 0x2: // GATE_ON_Clear_OFF_Start - Counts only when Blanking, clears on both ends, starts counting on next Blank Start.
|
|
// No point in updating the count, since we're gonna clear it.
|
|
psxRcntSync(i);
|
|
psxCounters[i].count = 0;
|
|
psxCounters[i].target &= ~IOPCNT_FUTURE_TARGET;
|
|
break; // do not set the counter
|
|
|
|
case 0x3: // GATE_ON_Start - Starts counting when the next Blank Ends, no clear.
|
|
if (psxCounters[i].mode.stopped)
|
|
{
|
|
psxCounters[i].startCycle = psxRegs.cycle & ~(psxCounters[i].rate - 1);
|
|
psxCounters[i].mode.stopped = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void psxHBlankStart()
|
|
{
|
|
// AlternateSource/scanline counters for Gates 1 and 3.
|
|
// We count them here so that they stay nicely synced with the EE's hsync.
|
|
if ((psxCounters[1].rate == PSXHBLANK) && psxRcntCanCount(1))
|
|
{
|
|
psxCounters[1].count++;
|
|
_rcntTestOverflow(1);
|
|
_rcntTestTarget(1);
|
|
}
|
|
|
|
if ((psxCounters[3].rate == PSXHBLANK) && psxRcntCanCount(3))
|
|
{
|
|
psxCounters[3].count++;
|
|
_rcntTestOverflow(3);
|
|
_rcntTestTarget(3);
|
|
}
|
|
|
|
_psxCheckStartGate(0);
|
|
|
|
hBlanking = true;
|
|
|
|
_rcntSet(0);
|
|
}
|
|
|
|
void psxHBlankEnd()
|
|
{
|
|
_psxCheckEndGate(0);
|
|
|
|
hBlanking = false;
|
|
|
|
_rcntSet(0);
|
|
}
|
|
|
|
void psxVBlankStart()
|
|
{
|
|
cdvdVsync();
|
|
iopIntcIrq(0);
|
|
|
|
_psxCheckStartGate(1);
|
|
_psxCheckStartGate(3);
|
|
|
|
vBlanking = true;
|
|
|
|
_rcntSet(1);
|
|
_rcntSet(3);
|
|
}
|
|
|
|
void psxVBlankEnd()
|
|
{
|
|
iopIntcIrq(11);
|
|
|
|
_psxCheckEndGate(1);
|
|
_psxCheckEndGate(3);
|
|
|
|
vBlanking = false;
|
|
|
|
_rcntSet(1);
|
|
_rcntSet(3);
|
|
}
|
|
|
|
void psxRcntUpdate()
|
|
{
|
|
int i;
|
|
|
|
psxNextDeltaCounter = 0x7fffffff;
|
|
psxNextStartCounter = psxRegs.cycle;
|
|
|
|
for (i = 0; i < 6; i++)
|
|
{
|
|
// don't count disabled or hblank counters...
|
|
// We can't check the ALTSOURCE flag because the PSXCLOCK source *should*
|
|
// be counted here.
|
|
|
|
psxRcntSync(i);
|
|
|
|
if (psxCounters[i].rate == PSXHBLANK)
|
|
continue;
|
|
|
|
if (!psxRcntCanCount(i))
|
|
continue;
|
|
|
|
_rcntTestOverflow(i);
|
|
_rcntTestTarget(i);
|
|
}
|
|
|
|
const u32 spu2_delta = (psxRegs.cycle - lClocks) % 768;
|
|
psxCounters[6].startCycle = psxRegs.cycle - spu2_delta;
|
|
psxCounters[6].deltaCycles = psxCounters[6].rate;
|
|
SPU2async();
|
|
psxNextDeltaCounter = psxCounters[6].deltaCycles;
|
|
|
|
DEV9async(1);
|
|
const s32 diffusb = psxRegs.cycle - psxCounters[7].startCycle;
|
|
s32 cusb = psxCounters[7].deltaCycles;
|
|
|
|
if (diffusb >= psxCounters[7].deltaCycles)
|
|
{
|
|
USBasync(diffusb);
|
|
psxCounters[7].startCycle += psxCounters[7].rate * (diffusb / psxCounters[7].rate);
|
|
psxCounters[7].deltaCycles = psxCounters[7].rate;
|
|
}
|
|
else
|
|
cusb -= diffusb;
|
|
|
|
if (cusb < psxNextDeltaCounter)
|
|
psxNextDeltaCounter = cusb;
|
|
|
|
for (i = 0; i < 6; i++)
|
|
_rcntSet(i);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void psxRcntWcount16(int index, u16 value)
|
|
{
|
|
pxAssert(index < 3);
|
|
PSXCNT_LOG("16bit IOP Counter[%d] writeCount16 = %x", index, value);
|
|
|
|
psxRcntSync(index);
|
|
|
|
psxCounters[index].count = value & 0xffff;
|
|
|
|
psxCounters[index].target &= 0xffff;
|
|
|
|
if (psxCounters[index].count > psxCounters[index].target)
|
|
{
|
|
// Count already higher than Target
|
|
//DevCon.Warning("32bit Count already higher than target");
|
|
psxCounters[index].target |= IOPCNT_FUTURE_TARGET;
|
|
}
|
|
|
|
_rcntSet(index);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void psxRcntWcount32(int index, u32 value)
|
|
{
|
|
pxAssert(index >= 3 && index < 6);
|
|
PSXCNT_LOG("32bit IOP Counter[%d] writeCount32 = %x", index, value);
|
|
|
|
psxRcntSync(index);
|
|
|
|
psxCounters[index].count = value;
|
|
|
|
psxCounters[index].target &= 0xffffffff;
|
|
|
|
if (psxCounters[index].count > psxCounters[index].target)
|
|
{
|
|
// Count already higher than Target
|
|
//DevCon.Warning("32bit Count already higher than target");
|
|
psxCounters[index].target |= IOPCNT_FUTURE_TARGET;
|
|
}
|
|
|
|
_rcntSet(index);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
__fi void psxRcntWmode16(int index, u32 value)
|
|
{
|
|
int irqmode = 0;
|
|
PSXCNT_LOG("16bit IOP Counter[%d] writeMode = 0x%04X", index, value);
|
|
|
|
pxAssume(index >= 0 && index < 3);
|
|
psxCounter& counter = psxCounters[index];
|
|
psxCounterMode oldMode = counter.mode;
|
|
|
|
counter.mode.modeval = (value & IOPCNT_MODE_WRITE_MSK) | (counter.mode.modeval & IOPCNT_MODE_FLAG_MSK); // Write new value, preserve flags
|
|
|
|
if (!((oldMode.targetFlag || oldMode.overflowFlag) && (oldMode.targetIntr || oldMode.overflIntr)))
|
|
psxRcntSetNewIntrMode(index);
|
|
|
|
if (counter.mode.repeatIntr != counter.currentIrqMode.repeatInterrupt || counter.mode.toggleIntr != counter.currentIrqMode.toggleInterrupt)
|
|
DevCon.Warning("Write to psxCounter[%d] mode old repeat %d new %d old toggle %d new %d", index, counter.currentIrqMode.repeatInterrupt, counter.mode.repeatIntr, counter.currentIrqMode.toggleInterrupt, counter.mode.toggleIntr);
|
|
|
|
if (value & (1 << 4))
|
|
{
|
|
irqmode += 1;
|
|
}
|
|
if (value & (1 << 5))
|
|
{
|
|
irqmode += 2;
|
|
}
|
|
if (value & (1 << 7))
|
|
{
|
|
PSXCNT_LOG("16 Counter %d Toggle IRQ on %s", index, (irqmode & 3) == 1 ? "Target" : ((irqmode & 3) == 2 ? "Overflow" : "Target and Overflow"));
|
|
}
|
|
else
|
|
{
|
|
PSXCNT_LOG("16 Counter %d Pulsed IRQ on %s", index, (irqmode & 3) == 1 ? "Target" : ((irqmode & 3) == 2 ? "Overflow" : "Target and Overflow"));
|
|
}
|
|
if (!(value & (1 << 6)))
|
|
{
|
|
PSXCNT_LOG("16 Counter %d One Shot", index);
|
|
}
|
|
else
|
|
{
|
|
PSXCNT_LOG("16 Counter %d Repeat", index);
|
|
}
|
|
if (index == 2)
|
|
{
|
|
if (counter.mode.t2Prescale)
|
|
psxCounters[2].rate = 8;
|
|
else
|
|
psxCounters[2].rate = 1;
|
|
}
|
|
else
|
|
{
|
|
// Counters 0 and 1 can select PIXEL or HSYNC as an alternate source:
|
|
counter.rate = 1;
|
|
|
|
if (counter.mode.extSignal)
|
|
counter.rate = (index == 0) ? PSXPIXEL : PSXHBLANK;
|
|
|
|
if (counter.rate == PSXPIXEL)
|
|
Console.Warning("PSX Pixel clock set to time 0, sync may be incorrect");
|
|
|
|
if (counter.mode.gateEnable)
|
|
{
|
|
// If set to gate mode 2 or 3, the counting starts at the start and end of the next blank respectively depending on which counter.
|
|
if (counter.mode.gateMode >= IOPCNT_GATE_CNT_HIGH_ZERO_OFF)
|
|
counter.mode.stopped = true;
|
|
|
|
PSXCNT_LOG("IOP Counter[%d] Gate Check set, value = 0x%04X", index, value);
|
|
}
|
|
}
|
|
|
|
// Current counter *always* resets on mode write.
|
|
counter.count = 0;
|
|
counter.startCycle = psxRegs.cycle & ~(counter.rate - 1);
|
|
counter.target &= 0xffff;
|
|
|
|
_rcntSet(index);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
__fi void psxRcntWmode32(int index, u32 value)
|
|
{
|
|
PSXCNT_LOG("32bit IOP Counter[%d] writeMode = 0x%04x", index, value);
|
|
int irqmode = 0;
|
|
pxAssume(index >= 3 && index < 6);
|
|
psxCounter& counter = psxCounters[index];
|
|
psxCounterMode oldMode = counter.mode;
|
|
|
|
counter.mode.modeval = (value & IOPCNT_MODE_WRITE_MSK) | (counter.mode.modeval & IOPCNT_MODE_FLAG_MSK); // Write new value, preserve flags
|
|
|
|
if (!((oldMode.targetFlag || oldMode.overflowFlag) && (oldMode.targetIntr || oldMode.overflIntr)))
|
|
psxRcntSetNewIntrMode(index);
|
|
|
|
if (counter.mode.repeatIntr != counter.currentIrqMode.repeatInterrupt || counter.mode.toggleIntr != counter.currentIrqMode.toggleInterrupt)
|
|
DevCon.Warning("Write to psxCounter[%d] mode old repeat %d new %d old toggle %d new %d", index, counter.currentIrqMode.repeatInterrupt, counter.mode.repeatIntr, counter.currentIrqMode.toggleInterrupt, counter.mode.toggleIntr);
|
|
|
|
if (value & (1 << 4))
|
|
{
|
|
irqmode += 1;
|
|
}
|
|
if (value & (1 << 5))
|
|
{
|
|
irqmode += 2;
|
|
}
|
|
if (value & (1 << 7))
|
|
{
|
|
PSXCNT_LOG("32 Counter %d Toggle IRQ on %s", index, (irqmode & 3) == 1 ? "Target" : ((irqmode & 3) == 2 ? "Overflow" : "Target and Overflow"));
|
|
}
|
|
else
|
|
{
|
|
PSXCNT_LOG("32 Counter %d Pulsed IRQ on %s", index, (irqmode & 3) == 1 ? "Target" : ((irqmode & 3) == 2 ? "Overflow" : "Target and Overflow"));
|
|
}
|
|
if (!(value & (1 << 6)))
|
|
{
|
|
PSXCNT_LOG("32 Counter %d One Shot", index);
|
|
}
|
|
else
|
|
{
|
|
PSXCNT_LOG("32 Counter %d Repeat", index);
|
|
}
|
|
if (index == 3)
|
|
{
|
|
// Counter 3 has the HBlank as an alternate source.
|
|
counter.rate = 1;
|
|
|
|
if (counter.mode.extSignal)
|
|
counter.rate = PSXHBLANK;
|
|
|
|
if (counter.mode.gateEnable)
|
|
{
|
|
PSXCNT_LOG("IOP Counter[3] Gate Check set, value = %x", value);
|
|
// If set to gate mode 2 or 3, the counting starts at the start and end of the next blank respectively depending on which counter.
|
|
if (counter.mode.gateMode >= IOPCNT_GATE_CNT_HIGH_ZERO_OFF)
|
|
counter.mode.stopped = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (counter.mode.t4_5Prescale)
|
|
{
|
|
case 0x1:
|
|
counter.rate = 8;
|
|
break;
|
|
case 0x2:
|
|
counter.rate = 16;
|
|
break;
|
|
case 0x3:
|
|
counter.rate = 256;
|
|
break;
|
|
default:
|
|
counter.rate = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Current counter *always* resets on mode write.
|
|
counter.count = 0;
|
|
counter.startCycle = psxRegs.cycle & ~(counter.rate - 1);
|
|
counter.target &= 0xffffffff;
|
|
_rcntSet(index);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
void psxRcntWtarget16(int index, u32 value)
|
|
{
|
|
pxAssert(index < 3);
|
|
PSXCNT_LOG("IOP Counter[%d] writeTarget16 = %lx", index, value);
|
|
psxCounters[index].target = value & 0xffff;
|
|
|
|
psxRcntSync(index);
|
|
|
|
// protect the target from an early arrival.
|
|
// if the target is behind the current count, then set the target overflow
|
|
// flag, so that the target won't be active until after the next overflow.
|
|
|
|
if (psxCounters[index].target <= psxCounters[index].count)
|
|
psxCounters[index].target |= IOPCNT_FUTURE_TARGET;
|
|
|
|
_rcntSet(index);
|
|
}
|
|
|
|
void psxRcntWtarget32(int index, u32 value)
|
|
{
|
|
pxAssert(index >= 3 && index < 6);
|
|
PSXCNT_LOG("IOP Counter[%d] writeTarget32 = %lx mode %x", index, value, psxCounters[index].mode.modeval);
|
|
|
|
psxCounters[index].target = value;
|
|
|
|
psxRcntSync(index);
|
|
// protect the target from an early arrival.
|
|
// if the target is behind the current count, then set the target overflow
|
|
// flag, so that the target won't be active until after the next overflow.
|
|
|
|
if (psxCounters[index].target <= psxCounters[index].count)
|
|
psxCounters[index].target |= IOPCNT_FUTURE_TARGET;
|
|
|
|
_rcntSet(index);
|
|
}
|
|
|
|
u16 psxRcntRcount16(int index)
|
|
{
|
|
psxRcntSync(index);
|
|
const u32 retval = (u32)psxCounters[index].count;
|
|
|
|
pxAssert(index < 3);
|
|
|
|
PSXCNT_LOG("IOP Counter[%d] readCount16 = %lx", index, (u16)retval);
|
|
|
|
return (u16)retval;
|
|
}
|
|
|
|
u32 psxRcntRcount32(int index)
|
|
{
|
|
psxRcntSync(index);
|
|
|
|
const u32 retval = (u32)psxCounters[index].count;
|
|
|
|
pxAssert(index >= 3 && index < 6);
|
|
|
|
PSXCNT_LOG("IOP Counter[%d] readCount32 = %lx", index, retval);
|
|
|
|
return retval;
|
|
}
|
|
|
|
void psxRcntSetNewIntrMode(int index)
|
|
{
|
|
psxCounters[index].mode.targetFlag = false;
|
|
psxCounters[index].mode.overflowFlag = false;
|
|
psxCounters[index].mode.intrEnable = true;
|
|
|
|
if (psxCounters[index].mode.repeatIntr != psxCounters[index].currentIrqMode.repeatInterrupt || psxCounters[index].mode.toggleIntr != psxCounters[index].currentIrqMode.toggleInterrupt)
|
|
DevCon.Warning("Updating psxCounter[%d] mode old repeat %d new %d old toggle %d new %d", index, psxCounters[index].mode.repeatIntr, psxCounters[index].currentIrqMode.repeatInterrupt, psxCounters[index].mode.toggleIntr, psxCounters[index].currentIrqMode.toggleInterrupt);
|
|
|
|
psxCounters[index].currentIrqMode.repeatInterrupt = psxCounters[index].mode.repeatIntr;
|
|
psxCounters[index].currentIrqMode.toggleInterrupt = psxCounters[index].mode.toggleIntr;
|
|
}
|
|
|
|
bool SaveStateBase::psxRcntFreeze()
|
|
{
|
|
if (!FreezeTag("iopCounters"))
|
|
return false;
|
|
|
|
Freeze(psxCounters);
|
|
Freeze(psxNextDeltaCounter);
|
|
Freeze(psxNextStartCounter);
|
|
Freeze(hBlanking);
|
|
Freeze(vBlanking);
|
|
|
|
if (!IsOkay())
|
|
return false;
|
|
|
|
if (IsLoading())
|
|
psxRcntUpdate();
|
|
|
|
return true;
|
|
}
|