mirror of https://github.com/mgba-emu/mgba.git
DS: Merge GBA and DS timers
This commit is contained in:
parent
e0ae2e8906
commit
d620a8c38c
|
@ -10,8 +10,8 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/internal/arm/arm.h>
|
||||
#include <mgba/core/log.h>
|
||||
#include <mgba/core/timing.h>
|
||||
|
||||
#include <mgba/internal/ds/memory.h>
|
||||
#include <mgba/internal/ds/timer.h>
|
||||
|
@ -46,6 +46,7 @@ enum DSIRQ {
|
|||
DS_IRQ_WIFI = 0x18,
|
||||
};
|
||||
|
||||
struct ARMCore;
|
||||
struct DS;
|
||||
struct Patch;
|
||||
struct VFile;
|
||||
|
@ -62,15 +63,19 @@ struct DS {
|
|||
struct DSVideo video;
|
||||
int timersEnabled7;
|
||||
int timersEnabled9;
|
||||
struct DSTimer timers7[4];
|
||||
struct DSTimer timers9[4];
|
||||
struct GBATimer timers7[4];
|
||||
struct GBATimer timers9[4];
|
||||
|
||||
struct mCoreSync* sync;
|
||||
struct mTiming timing7;
|
||||
struct mTiming timing9;
|
||||
|
||||
struct ARMDebugger* debugger;
|
||||
|
||||
int springIRQ7;
|
||||
int springIRQ9;
|
||||
bool cpuBlocked;
|
||||
bool earlyExit;
|
||||
|
||||
uint32_t bios7Checksum;
|
||||
uint32_t bios9Checksum;
|
||||
|
|
|
@ -10,29 +10,12 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
DECL_BITFIELD(DSTimerFlags, uint32_t);
|
||||
DECL_BITS(DSTimerFlags, PrescaleBits, 0, 4);
|
||||
DECL_BIT(DSTimerFlags, CountUp, 4);
|
||||
DECL_BIT(DSTimerFlags, DoIrq, 5);
|
||||
DECL_BIT(DSTimerFlags, Enable, 6);
|
||||
#include <mgba/internal/gba/timer.h>
|
||||
|
||||
struct DSTimer {
|
||||
uint16_t reload;
|
||||
uint16_t oldReload;
|
||||
int32_t lastEvent;
|
||||
int32_t nextEvent;
|
||||
int32_t overflowInterval;
|
||||
DSTimerFlags flags;
|
||||
};
|
||||
|
||||
// TODO: Merge back into GBATimer
|
||||
struct ARMCore;
|
||||
void DSTimerUpdateRegister(struct DSTimer* timer, struct ARMCore* cpu, uint16_t* io);
|
||||
void DSTimerWriteTMCNT_LO(struct DSTimer* timer, uint16_t reload);
|
||||
void DSTimerWriteTMCNT_HI(struct DSTimer* timer, struct ARMCore* cpu, uint16_t* io, uint16_t control);
|
||||
|
||||
struct DS;
|
||||
int32_t DSTimersProcessEvents(struct DS* ds, int32_t cycles);
|
||||
void DSTimerInit(struct DS* ds);
|
||||
void DSTimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, uint16_t control);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ DECL_BIT(GBATimerFlags, CountUp, 4);
|
|||
DECL_BIT(GBATimerFlags, DoIrq, 5);
|
||||
DECL_BIT(GBATimerFlags, Enable, 6);
|
||||
|
||||
struct GBA;
|
||||
struct GBATimer {
|
||||
uint16_t reload;
|
||||
uint16_t oldReload;
|
||||
|
@ -28,10 +27,16 @@ struct GBATimer {
|
|||
GBATimerFlags flags;
|
||||
};
|
||||
|
||||
struct ARMCore;
|
||||
struct GBA;
|
||||
void GBATimerInit(struct GBA* gba);
|
||||
void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload);
|
||||
void GBATimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, uint16_t control);
|
||||
|
||||
void GBATimerUpdateRegister(struct GBA* gba, int timer);
|
||||
void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t value);
|
||||
void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t value);
|
||||
void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, int32_t skew);
|
||||
void GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate);
|
||||
void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
|
|
54
src/ds/ds.c
54
src/ds/ds.c
|
@ -80,10 +80,7 @@ static void DSInit(void* cpu, struct mCPUComponent* component) {
|
|||
|
||||
ds->springIRQ7 = 0;
|
||||
ds->springIRQ9 = 0;
|
||||
ds->timersEnabled7 = 0;
|
||||
ds->timersEnabled9 = 0;
|
||||
memset(ds->timers7, 0, sizeof(ds->timers7));
|
||||
memset(ds->timers9, 0, sizeof(ds->timers9));
|
||||
DSTimerInit(ds);
|
||||
ds->keySource = NULL;
|
||||
ds->rtcSource = NULL;
|
||||
ds->rumble = NULL;
|
||||
|
@ -91,6 +88,9 @@ static void DSInit(void* cpu, struct mCPUComponent* component) {
|
|||
ds->romVf = NULL;
|
||||
|
||||
ds->keyCallback = NULL;
|
||||
|
||||
mTimingInit(&ds->timing7, &ds->arm7->cycles, &ds->arm7->nextEvent);
|
||||
mTimingInit(&ds->timing9, &ds->arm9->cycles, &ds->arm9->nextEvent);
|
||||
}
|
||||
|
||||
void DSUnloadROM(struct DS* ds) {
|
||||
|
@ -103,6 +103,8 @@ void DSUnloadROM(struct DS* ds) {
|
|||
void DSDestroy(struct DS* ds) {
|
||||
DSUnloadROM(ds);
|
||||
DSMemoryDeinit(ds);
|
||||
mTimingDeinit(&ds->timing7);
|
||||
mTimingDeinit(&ds->timing9);
|
||||
}
|
||||
|
||||
void DS7InterruptHandlerInit(struct ARMInterruptHandler* irqh) {
|
||||
|
@ -140,6 +142,7 @@ void DS7Reset(struct ARMCore* cpu) {
|
|||
cpu->gprs[ARM_SP] = DS7_SP_BASE;
|
||||
|
||||
struct DS* ds = (struct DS*) cpu->master;
|
||||
mTimingClear(&ds->timing7);
|
||||
DSMemoryReset(ds);
|
||||
DS7IOInit(ds);
|
||||
|
||||
|
@ -171,6 +174,7 @@ void DS9Reset(struct ARMCore* cpu) {
|
|||
cpu->gprs[ARM_SP] = DS9_SP_BASE;
|
||||
|
||||
struct DS* ds = (struct DS*) cpu->master;
|
||||
mTimingClear(&ds->timing9);
|
||||
DS9IOInit(ds);
|
||||
|
||||
struct DSCartridge* header = ds->romVf->map(ds->romVf, sizeof(*header), MAP_READ);
|
||||
|
@ -200,25 +204,37 @@ static void DSProcessEvents(struct ARMCore* cpu) {
|
|||
ds->springIRQ7 = 0;
|
||||
}
|
||||
|
||||
int32_t cycles = cpu->nextEvent;
|
||||
int32_t nextEvent = INT_MAX;
|
||||
int32_t testEvent;
|
||||
int32_t nextEvent = cpu->nextEvent;
|
||||
while (cpu->cycles >= nextEvent) {
|
||||
int32_t cycles = cpu->cycles;
|
||||
|
||||
cpu->cycles = 0;
|
||||
cpu->nextEvent = INT_MAX;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (cycles < 0) {
|
||||
mLOG(DS, FATAL, "Negative cycles passed: %i", cycles);
|
||||
}
|
||||
if (cycles < 0) {
|
||||
mLOG(DS, FATAL, "Negative cycles passed: %i", cycles);
|
||||
}
|
||||
#endif
|
||||
nextEvent = cycles;
|
||||
do {
|
||||
nextEvent = mTimingTick(&ds->timing7, nextEvent);
|
||||
} while (ds->cpuBlocked);
|
||||
|
||||
testEvent = DSTimersProcessEvents(ds, cycles);
|
||||
if (testEvent < nextEvent) {
|
||||
nextEvent = testEvent;
|
||||
}
|
||||
cpu->nextEvent = nextEvent;
|
||||
|
||||
cpu->cycles -= cycles;
|
||||
cpu->nextEvent = nextEvent;
|
||||
|
||||
if (cpu->halted) {
|
||||
cpu->cycles = cpu->nextEvent;
|
||||
if (ds->earlyExit) {
|
||||
ds->earlyExit = false;
|
||||
break;
|
||||
}
|
||||
if (cpu->halted) {
|
||||
cpu->cycles = nextEvent;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
else if (nextEvent < 0) {
|
||||
mLOG(DS, FATAL, "Negative cycles will pass: %i", nextEvent);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
32
src/ds/io.c
32
src/ds/io.c
|
@ -26,41 +26,33 @@ void DS7IOWrite(struct DS* ds, uint32_t address, uint16_t value) {
|
|||
switch (address) {
|
||||
// Timers
|
||||
case DS7_REG_TM0CNT_LO:
|
||||
DSTimerWriteTMCNT_LO(&ds->timers7[0], value);
|
||||
GBATimerWriteTMCNT_LO(&ds->timers7[0], value);
|
||||
return;
|
||||
case DS7_REG_TM1CNT_LO:
|
||||
DSTimerWriteTMCNT_LO(&ds->timers7[1], value);
|
||||
GBATimerWriteTMCNT_LO(&ds->timers7[1], value);
|
||||
return;
|
||||
case DS7_REG_TM2CNT_LO:
|
||||
DSTimerWriteTMCNT_LO(&ds->timers7[2], value);
|
||||
GBATimerWriteTMCNT_LO(&ds->timers7[2], value);
|
||||
return;
|
||||
case DS7_REG_TM3CNT_LO:
|
||||
DSTimerWriteTMCNT_LO(&ds->timers7[3], value);
|
||||
GBATimerWriteTMCNT_LO(&ds->timers7[3], value);
|
||||
return;
|
||||
|
||||
case DS7_REG_TM0CNT_HI:
|
||||
value &= 0x00C7;
|
||||
DSTimerWriteTMCNT_HI(&ds->timers7[0], ds->arm7, &ds->memory.io7[(address - 2) >> 1], value);
|
||||
ds->timersEnabled7 &= ~1;
|
||||
ds->timersEnabled7 |= DSTimerFlagsGetEnable(ds->timers7[0].flags);
|
||||
DSTimerWriteTMCNT_HI(&ds->timers7[0], &ds->timing7, ds->arm7, &ds->memory.io7[DS7_REG_TM0CNT_LO >> 1], value);
|
||||
break;
|
||||
case DS7_REG_TM1CNT_HI:
|
||||
value &= 0x00C7;
|
||||
DSTimerWriteTMCNT_HI(&ds->timers7[1], ds->arm7, &ds->memory.io7[(address - 2) >> 1], value);
|
||||
ds->timersEnabled7 &= ~2;
|
||||
ds->timersEnabled7 |= DSTimerFlagsGetEnable(ds->timers7[1].flags) << 1;
|
||||
DSTimerWriteTMCNT_HI(&ds->timers7[1], &ds->timing7, ds->arm7, &ds->memory.io7[DS7_REG_TM1CNT_LO >> 1], value);
|
||||
break;
|
||||
case DS7_REG_TM2CNT_HI:
|
||||
value &= 0x00C7;
|
||||
DSTimerWriteTMCNT_HI(&ds->timers7[2], ds->arm7, &ds->memory.io7[(address - 2) >> 1], value);
|
||||
ds->timersEnabled7 &= ~4;
|
||||
ds->timersEnabled7 |= DSTimerFlagsGetEnable(ds->timers7[2].flags) << 2;
|
||||
DSTimerWriteTMCNT_HI(&ds->timers7[2], &ds->timing7, ds->arm7, &ds->memory.io7[DS7_REG_TM2CNT_LO >> 1], value);
|
||||
break;
|
||||
case DS7_REG_TM3CNT_HI:
|
||||
value &= 0x00C7;
|
||||
DSTimerWriteTMCNT_HI(&ds->timers7[3], ds->arm7, &ds->memory.io7[(address - 2) >> 1], value);
|
||||
ds->timersEnabled7 &= ~8;
|
||||
ds->timersEnabled7 |= DSTimerFlagsGetEnable(ds->timers7[3].flags) << 3;
|
||||
DSTimerWriteTMCNT_HI(&ds->timers7[3], &ds->timing7, ds->arm7, &ds->memory.io7[DS7_REG_TM3CNT_LO >> 1], value);
|
||||
break;
|
||||
|
||||
case DS7_REG_IPCSYNC:
|
||||
|
@ -113,16 +105,16 @@ void DS7IOWrite32(struct DS* ds, uint32_t address, uint32_t value) {
|
|||
uint16_t DS7IORead(struct DS* ds, uint32_t address) {
|
||||
switch (address) {
|
||||
case DS7_REG_TM0CNT_LO:
|
||||
DSTimerUpdateRegister(&ds->timers7[0], ds->arm7, &ds->memory.io7[address >> 1]);
|
||||
GBATimerUpdateRegisterInternal(&ds->timers7[0], &ds->timing7, ds->arm7, &ds->memory.io7[address >> 1], 0);
|
||||
break;
|
||||
case DS7_REG_TM1CNT_LO:
|
||||
DSTimerUpdateRegister(&ds->timers7[1], ds->arm7, &ds->memory.io7[address >> 1]);
|
||||
GBATimerUpdateRegisterInternal(&ds->timers7[1], &ds->timing7, ds->arm7, &ds->memory.io7[address >> 1], 0);
|
||||
break;
|
||||
case DS7_REG_TM2CNT_LO:
|
||||
DSTimerUpdateRegister(&ds->timers7[2], ds->arm7, &ds->memory.io7[address >> 1]);
|
||||
GBATimerUpdateRegisterInternal(&ds->timers7[2], &ds->timing7, ds->arm7, &ds->memory.io7[address >> 1], 0);
|
||||
break;
|
||||
case DS7_REG_TM3CNT_LO:
|
||||
DSTimerUpdateRegister(&ds->timers7[3], ds->arm7, &ds->memory.io7[address >> 1]);
|
||||
GBATimerUpdateRegisterInternal(&ds->timers7[3], &ds->timing7, ds->arm7, &ds->memory.io7[address >> 1], 0);
|
||||
break;
|
||||
|
||||
case DS7_REG_TM0CNT_HI:
|
||||
|
|
161
src/ds/timer.c
161
src/ds/timer.c
|
@ -8,103 +8,84 @@
|
|||
#include <mgba/internal/arm/arm.h>
|
||||
#include <mgba/internal/ds/ds.h>
|
||||
|
||||
void DSTimerUpdateRegister(struct DSTimer* timer, struct ARMCore* cpu, uint16_t* io) {
|
||||
if (DSTimerFlagsIsEnable(timer->flags) && !DSTimerFlagsIsCountUp(timer->flags)) {
|
||||
// Reading this takes two cycles (1N+1I), so let's remove them preemptively
|
||||
*io = timer->oldReload + ((cpu->cycles - timer->lastEvent - 2) >> DSTimerFlagsGetPrescaleBits(timer->flags));
|
||||
static void DS7TimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct DS* ds = context;
|
||||
struct GBATimer* timer = &ds->timers7[0];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
DSRaiseIRQ(ds->arm7, ds->memory.io7, DS_IRQ_TIMER0);
|
||||
}
|
||||
GBATimerUpdate(timing, &ds->timers7[0], &ds->memory.io7[DS7_REG_TM0CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &ds->timers7[1], &ds->memory.io7[DS7_REG_TM1CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
void DSTimerWriteTMCNT_LO(struct DSTimer* timer, uint16_t reload) {
|
||||
timer->reload = reload;
|
||||
timer->overflowInterval = (0x10000 - timer->reload) << DSTimerFlagsGetPrescaleBits(timer->flags);
|
||||
static void DS7TimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct DS* ds = context;
|
||||
struct GBATimer* timer = &ds->timers7[1];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
DSRaiseIRQ(ds->arm7, ds->memory.io7, DS_IRQ_TIMER1);
|
||||
}
|
||||
GBATimerUpdate(timing, &ds->timers7[1], &ds->memory.io7[DS7_REG_TM1CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &ds->timers7[2], &ds->memory.io7[DS7_REG_TM2CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
void DSTimerWriteTMCNT_HI(struct DSTimer* timer, struct ARMCore* cpu, uint16_t* io, uint16_t control) {
|
||||
DSTimerUpdateRegister(timer, cpu, io);
|
||||
|
||||
unsigned oldPrescale = DSTimerFlagsGetPrescaleBits(timer->flags);
|
||||
switch (control & 0x0003) {
|
||||
case 0x0000:
|
||||
timer->flags = DSTimerFlagsSetPrescaleBits(timer->flags, 0);
|
||||
break;
|
||||
case 0x0001:
|
||||
timer->flags = DSTimerFlagsSetPrescaleBits(timer->flags, 6);
|
||||
break;
|
||||
case 0x0002:
|
||||
timer->flags = DSTimerFlagsSetPrescaleBits(timer->flags, 8);
|
||||
break;
|
||||
case 0x0003:
|
||||
timer->flags = DSTimerFlagsSetPrescaleBits(timer->flags, 10);
|
||||
break;
|
||||
}
|
||||
timer->flags = DSTimerFlagsTestFillCountUp(timer->flags, control & 0x0004);
|
||||
timer->flags = DSTimerFlagsTestFillDoIrq(timer->flags, control & 0x0040);
|
||||
timer->overflowInterval = (0x10000 - timer->reload) << DSTimerFlagsGetPrescaleBits(timer->flags);
|
||||
bool wasEnabled = DSTimerFlagsIsEnable(timer->flags);
|
||||
timer->flags = DSTimerFlagsTestFillEnable(timer->flags, control & 0x0080);
|
||||
if (!wasEnabled && DSTimerFlagsIsEnable(timer->flags)) {
|
||||
if (!DSTimerFlagsIsCountUp(timer->flags)) {
|
||||
timer->nextEvent = cpu->cycles + timer->overflowInterval;
|
||||
} else {
|
||||
timer->nextEvent = INT_MAX;
|
||||
}
|
||||
*io = timer->reload;
|
||||
timer->oldReload = timer->reload;
|
||||
timer->lastEvent = cpu->cycles;
|
||||
} else if (wasEnabled && !DSTimerFlagsIsEnable(timer->flags)) {
|
||||
if (!DSTimerFlagsIsCountUp(timer->flags)) {
|
||||
*io = timer->oldReload + ((cpu->cycles - timer->lastEvent) >> oldPrescale);
|
||||
}
|
||||
} else if (DSTimerFlagsGetPrescaleBits(timer->flags) != oldPrescale && !DSTimerFlagsIsCountUp(timer->flags)) {
|
||||
// FIXME: this might be before present
|
||||
timer->nextEvent = timer->lastEvent + timer->overflowInterval;
|
||||
}
|
||||
|
||||
if (timer->nextEvent < cpu->nextEvent) {
|
||||
cpu->nextEvent = timer->nextEvent;
|
||||
static void DS7TimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct DS* ds = context;
|
||||
struct GBATimer* timer = &ds->timers7[2];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
DSRaiseIRQ(ds->arm7, ds->memory.io7, DS_IRQ_TIMER2);
|
||||
}
|
||||
GBATimerUpdate(timing, &ds->timers7[2], &ds->memory.io7[DS7_REG_TM2CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &ds->timers7[3], &ds->memory.io7[DS7_REG_TM3CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
int32_t DSTimersProcessEvents(struct DS* ds, int32_t cycles) {
|
||||
int32_t nextEvent = INT_MAX;
|
||||
if (!ds->timersEnabled7) {
|
||||
return nextEvent;
|
||||
static void DS7TimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct DS* ds = context;
|
||||
struct GBATimer* timer = &ds->timers7[3];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
DSRaiseIRQ(ds->arm7, ds->memory.io7, DS_IRQ_TIMER3);
|
||||
}
|
||||
|
||||
struct DSTimer* timer;
|
||||
struct DSTimer* nextTimer;
|
||||
|
||||
int t;
|
||||
for (t = 0; t < 4; ++t) {
|
||||
timer = &ds->timers7[t];
|
||||
if (DSTimerFlagsIsEnable(timer->flags)) {
|
||||
timer->nextEvent -= cycles;
|
||||
timer->lastEvent -= cycles;
|
||||
while (timer->nextEvent <= 0) {
|
||||
timer->lastEvent = timer->nextEvent;
|
||||
timer->nextEvent += timer->overflowInterval;
|
||||
ds->memory.io7[(DS7_REG_TM0CNT_LO + (t << 2)) >> 1] = timer->reload;
|
||||
timer->oldReload = timer->reload;
|
||||
|
||||
if (DSTimerFlagsIsDoIrq(timer->flags)) {
|
||||
DSRaiseIRQ(ds->arm7, ds->memory.io7, DS_IRQ_TIMER0);
|
||||
}
|
||||
|
||||
if (t == 3) {
|
||||
break;
|
||||
}
|
||||
|
||||
nextTimer = &ds->timers7[t + 1];
|
||||
if (DSTimerFlagsIsCountUp(nextTimer->flags)) {
|
||||
++ds->memory.io7[(DS7_REG_TM1CNT_LO + (t << 2)) >> 1];
|
||||
if (!ds->memory.io7[(DS7_REG_TM1CNT_LO + (t << 2)) >> 1]) {
|
||||
nextTimer->nextEvent = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
nextEvent = timer->nextEvent;
|
||||
}
|
||||
}
|
||||
return nextEvent;
|
||||
GBATimerUpdate(timing, &ds->timers7[3], &ds->memory.io7[DS7_REG_TM3CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
void DSTimerInit(struct DS* ds) {
|
||||
memset(ds->timers7, 0, sizeof(ds->timers7));
|
||||
ds->timers7[0].event.name = "DS7 Timer 0";
|
||||
ds->timers7[0].event.callback = DS7TimerUpdate0;
|
||||
ds->timers7[0].event.context = ds;
|
||||
ds->timers7[0].event.priority = 0x20;
|
||||
ds->timers7[1].event.name = "DS7 Timer 1";
|
||||
ds->timers7[1].event.callback = DS7TimerUpdate1;
|
||||
ds->timers7[1].event.context = ds;
|
||||
ds->timers7[1].event.priority = 0x21;
|
||||
ds->timers7[2].event.name = "DS7 Timer 2";
|
||||
ds->timers7[2].event.callback = DS7TimerUpdate2;
|
||||
ds->timers7[2].event.context = ds;
|
||||
ds->timers7[2].event.priority = 0x22;
|
||||
ds->timers7[3].event.name = "DS7 Timer 3";
|
||||
ds->timers7[3].event.callback = DS7TimerUpdate3;
|
||||
ds->timers7[3].event.context = ds;
|
||||
ds->timers7[3].event.priority = 0x23;
|
||||
|
||||
memset(ds->timers9, 0, sizeof(ds->timers9));
|
||||
ds->timers9[0].event.name = "DS9 Timer 0";
|
||||
ds->timers9[0].event.callback = NULL;
|
||||
ds->timers9[0].event.context = ds;
|
||||
ds->timers9[0].event.priority = 0x20;
|
||||
ds->timers9[1].event.name = "DS9 Timer 1";
|
||||
ds->timers9[1].event.callback = NULL;
|
||||
ds->timers9[1].event.context = ds;
|
||||
ds->timers9[1].event.priority = 0x21;
|
||||
ds->timers9[2].event.name = "DS9 Timer 2";
|
||||
ds->timers9[2].event.callback = NULL;
|
||||
ds->timers9[2].event.context = ds;
|
||||
ds->timers9[2].event.priority = 0x22;
|
||||
ds->timers9[3].event.name = "DS9 Timer 3";
|
||||
ds->timers9[3].event.callback = NULL;
|
||||
ds->timers9[3].event.context = ds;
|
||||
ds->timers9[3].event.priority = 0x23;
|
||||
}
|
||||
|
||||
void DSTimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, uint16_t value) {
|
||||
GBATimerUpdateRegisterInternal(timer, timing, cpu, io, 0);
|
||||
GBATimerWriteTMCNT_HI(timer, timing, cpu, io, value);
|
||||
}
|
||||
|
|
20
src/gba/io.c
20
src/gba/io.c
|
@ -487,33 +487,37 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
|
||||
// Timers
|
||||
case REG_TM0CNT_LO:
|
||||
GBATimerWriteTMCNT_LO(gba, 0, value);
|
||||
GBATimerWriteTMCNT_LO(&gba->timers[0], value);
|
||||
return;
|
||||
case REG_TM1CNT_LO:
|
||||
GBATimerWriteTMCNT_LO(gba, 1, value);
|
||||
GBATimerWriteTMCNT_LO(&gba->timers[1], value);
|
||||
return;
|
||||
case REG_TM2CNT_LO:
|
||||
GBATimerWriteTMCNT_LO(gba, 2, value);
|
||||
GBATimerWriteTMCNT_LO(&gba->timers[2], value);
|
||||
return;
|
||||
case REG_TM3CNT_LO:
|
||||
GBATimerWriteTMCNT_LO(gba, 3, value);
|
||||
GBATimerWriteTMCNT_LO(&gba->timers[3], value);
|
||||
return;
|
||||
|
||||
case REG_TM0CNT_HI:
|
||||
value &= 0x00C7;
|
||||
GBATimerWriteTMCNT_HI(gba, 0, value);
|
||||
GBATimerUpdateRegister(gba, 0);
|
||||
GBATimerWriteTMCNT_HI(&gba->timers[0], &gba->timing, gba->cpu, &gba->memory.io[REG_TM0CNT_LO >> 1], value);
|
||||
break;
|
||||
case REG_TM1CNT_HI:
|
||||
value &= 0x00C7;
|
||||
GBATimerWriteTMCNT_HI(gba, 1, value);
|
||||
GBATimerUpdateRegister(gba, 1);
|
||||
GBATimerWriteTMCNT_HI(&gba->timers[1], &gba->timing, gba->cpu, &gba->memory.io[REG_TM1CNT_LO >> 1], value);
|
||||
break;
|
||||
case REG_TM2CNT_HI:
|
||||
value &= 0x00C7;
|
||||
GBATimerWriteTMCNT_HI(gba, 2, value);
|
||||
GBATimerUpdateRegister(gba, 2);
|
||||
GBATimerWriteTMCNT_HI(&gba->timers[2], &gba->timing, gba->cpu, &gba->memory.io[REG_TM2CNT_LO >> 1], value);
|
||||
break;
|
||||
case REG_TM3CNT_HI:
|
||||
value &= 0x00C7;
|
||||
GBATimerWriteTMCNT_HI(gba, 3, value);
|
||||
GBATimerUpdateRegister(gba, 3);
|
||||
GBATimerWriteTMCNT_HI(&gba->timers[3], &gba->timing, gba->cpu, &gba->memory.io[REG_TM3CNT_LO >> 1], value);
|
||||
break;
|
||||
|
||||
// SIO
|
||||
|
|
151
src/gba/timer.c
151
src/gba/timer.c
|
@ -8,36 +8,33 @@
|
|||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/io.h>
|
||||
|
||||
static void GBATimerUpdate(struct mTiming* timing, struct GBA* gba, int timerId, uint32_t cyclesLate) {
|
||||
struct GBATimer* timer = &gba->timers[timerId];
|
||||
gba->memory.io[(REG_TM0CNT_LO >> 1) + (timerId << 1)] = timer->reload;
|
||||
static void GBATimerUpdateAudio(struct GBA* gba, int timerId, uint32_t cyclesLate) {
|
||||
if (!gba->audio.enable) {
|
||||
return;
|
||||
}
|
||||
if ((gba->audio.chALeft || gba->audio.chARight) && gba->audio.chATimer == timerId) {
|
||||
GBAAudioSampleFIFO(&gba->audio, 0, cyclesLate);
|
||||
}
|
||||
|
||||
if ((gba->audio.chBLeft || gba->audio.chBRight) && gba->audio.chBTimer == timerId) {
|
||||
GBAAudioSampleFIFO(&gba->audio, 1, cyclesLate);
|
||||
}
|
||||
}
|
||||
|
||||
void GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate) {
|
||||
if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled?
|
||||
++*io;
|
||||
if (!*io && GBATimerFlagsIsEnable(nextTimer->flags)) {
|
||||
mTimingSchedule(timing, &nextTimer->event, -cyclesLate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate) {
|
||||
*io = timer->reload;
|
||||
timer->oldReload = timer->reload;
|
||||
timer->lastEvent = timing->masterCycles - cyclesLate;
|
||||
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER0 + timerId);
|
||||
}
|
||||
|
||||
if (gba->audio.enable && timerId < 2) {
|
||||
if ((gba->audio.chALeft || gba->audio.chARight) && gba->audio.chATimer == timerId) {
|
||||
GBAAudioSampleFIFO(&gba->audio, 0, cyclesLate);
|
||||
}
|
||||
|
||||
if ((gba->audio.chBLeft || gba->audio.chBRight) && gba->audio.chBTimer == timerId) {
|
||||
GBAAudioSampleFIFO(&gba->audio, 1, cyclesLate);
|
||||
}
|
||||
}
|
||||
|
||||
if (timerId < 4) {
|
||||
struct GBATimer* nextTimer = &gba->timers[timerId + 1];
|
||||
if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled?
|
||||
++gba->memory.io[(REG_TM1CNT_LO >> 1) + (timerId << 1)];
|
||||
if (!gba->memory.io[(REG_TM1CNT_LO >> 1) + (timerId << 1)] && GBATimerFlagsIsEnable(nextTimer->flags)) {
|
||||
mTimingSchedule(timing, &nextTimer->event, -cyclesLate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!GBATimerFlagsIsCountUp(timer->flags)) {
|
||||
uint32_t nextEvent = timer->overflowInterval - cyclesLate;
|
||||
mTimingSchedule(timing, &timer->event, nextEvent);
|
||||
|
@ -45,19 +42,44 @@ static void GBATimerUpdate(struct mTiming* timing, struct GBA* gba, int timerId,
|
|||
}
|
||||
|
||||
static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
GBATimerUpdate(timing, context, 0, cyclesLate);
|
||||
struct GBA* gba = context;
|
||||
struct GBATimer* timer = &gba->timers[0];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER0);
|
||||
}
|
||||
GBATimerUpdateAudio(gba, 0, cyclesLate);
|
||||
GBATimerUpdate(timing, &gba->timers[0], &gba->memory.io[REG_TM0CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
GBATimerUpdate(timing, context, 1, cyclesLate);
|
||||
struct GBA* gba = context;
|
||||
struct GBATimer* timer = &gba->timers[1];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER1);
|
||||
}
|
||||
GBATimerUpdateAudio(gba, 1, cyclesLate);
|
||||
GBATimerUpdate(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
GBATimerUpdate(timing, context, 2, cyclesLate);
|
||||
struct GBA* gba = context;
|
||||
struct GBATimer* timer = &gba->timers[2];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER2);
|
||||
}
|
||||
GBATimerUpdate(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
GBATimerUpdate(timing, context, 3, cyclesLate);
|
||||
struct GBA* gba = context;
|
||||
struct GBATimer* timer = &gba->timers[3];
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER3);
|
||||
}
|
||||
GBATimerUpdate(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate);
|
||||
}
|
||||
|
||||
void GBATimerInit(struct GBA* gba) {
|
||||
|
@ -87,56 +109,57 @@ void GBATimerUpdateRegister(struct GBA* gba, int timer) {
|
|||
if (gba->memory.lastPrefetchedPc >= (uint32_t) gba->cpu->gprs[ARM_PC]) {
|
||||
prefetchSkew = (gba->memory.lastPrefetchedPc - gba->cpu->gprs[ARM_PC]) * (gba->cpu->memory.activeSeqCycles16 + 1) / WORD_SIZE_THUMB;
|
||||
}
|
||||
// Reading this takes two cycles (1N+1I), so let's remove them preemptively
|
||||
int32_t diff = gba->cpu->cycles - (currentTimer->lastEvent - gba->timing.masterCycles);
|
||||
gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((diff - 2 + prefetchSkew) >> GBATimerFlagsGetPrescaleBits(currentTimer->flags));
|
||||
GBATimerUpdateRegisterInternal(currentTimer, &gba->timing, gba->cpu, &gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1], prefetchSkew);
|
||||
}
|
||||
}
|
||||
|
||||
void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t reload) {
|
||||
gba->timers[timer].reload = reload;
|
||||
gba->timers[timer].overflowInterval = (0x10000 - gba->timers[timer].reload) << GBATimerFlagsGetPrescaleBits(gba->timers[timer].flags);
|
||||
void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, int32_t skew) {
|
||||
// Reading this takes two cycles (1N+1I), so let's remove them preemptively
|
||||
int32_t diff = cpu->cycles - (timer->lastEvent - timing->masterCycles);
|
||||
*io = timer->oldReload + ((diff - 2 + skew) >> GBATimerFlagsGetPrescaleBits(timer->flags));
|
||||
}
|
||||
|
||||
void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {
|
||||
struct GBATimer* currentTimer = &gba->timers[timer];
|
||||
GBATimerUpdateRegister(gba, timer);
|
||||
void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload) {
|
||||
timer->reload = reload;
|
||||
timer->overflowInterval = (0x10000 - timer->reload) << GBATimerFlagsGetPrescaleBits(timer->flags);
|
||||
}
|
||||
|
||||
unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(currentTimer->flags);
|
||||
void GBATimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, struct ARMCore* cpu, uint16_t* io, uint16_t control) {
|
||||
unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(timer->flags);
|
||||
switch (control & 0x0003) {
|
||||
case 0x0000:
|
||||
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 0);
|
||||
timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, 0);
|
||||
break;
|
||||
case 0x0001:
|
||||
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 6);
|
||||
timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, 6);
|
||||
break;
|
||||
case 0x0002:
|
||||
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 8);
|
||||
timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, 8);
|
||||
break;
|
||||
case 0x0003:
|
||||
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 10);
|
||||
timer->flags = GBATimerFlagsSetPrescaleBits(timer->flags, 10);
|
||||
break;
|
||||
}
|
||||
currentTimer->flags = GBATimerFlagsTestFillCountUp(currentTimer->flags, timer > 0 && (control & 0x0004));
|
||||
currentTimer->flags = GBATimerFlagsTestFillDoIrq(currentTimer->flags, control & 0x0040);
|
||||
currentTimer->overflowInterval = (0x10000 - currentTimer->reload) << GBATimerFlagsGetPrescaleBits(currentTimer->flags);
|
||||
bool wasEnabled = GBATimerFlagsIsEnable(currentTimer->flags);
|
||||
currentTimer->flags = GBATimerFlagsTestFillEnable(currentTimer->flags, control & 0x0080);
|
||||
if (!wasEnabled && GBATimerFlagsIsEnable(currentTimer->flags)) {
|
||||
mTimingDeschedule(&gba->timing, ¤tTimer->event);
|
||||
if (!GBATimerFlagsIsCountUp(currentTimer->flags)) {
|
||||
mTimingSchedule(&gba->timing, ¤tTimer->event, currentTimer->overflowInterval);
|
||||
timer->flags = GBATimerFlagsTestFillCountUp(timer->flags, timer > 0 && (control & 0x0004));
|
||||
timer->flags = GBATimerFlagsTestFillDoIrq(timer->flags, control & 0x0040);
|
||||
timer->overflowInterval = (0x10000 - timer->reload) << GBATimerFlagsGetPrescaleBits(timer->flags);
|
||||
bool wasEnabled = GBATimerFlagsIsEnable(timer->flags);
|
||||
timer->flags = GBATimerFlagsTestFillEnable(timer->flags, control & 0x0080);
|
||||
if (!wasEnabled && GBATimerFlagsIsEnable(timer->flags)) {
|
||||
mTimingDeschedule(timing, &timer->event);
|
||||
if (!GBATimerFlagsIsCountUp(timer->flags)) {
|
||||
mTimingSchedule(timing, &timer->event, timer->overflowInterval);
|
||||
}
|
||||
gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->reload;
|
||||
currentTimer->oldReload = currentTimer->reload;
|
||||
currentTimer->lastEvent = gba->timing.masterCycles + gba->cpu->cycles;
|
||||
} else if (wasEnabled && !GBATimerFlagsIsEnable(currentTimer->flags)) {
|
||||
mTimingDeschedule(&gba->timing, ¤tTimer->event);
|
||||
if (!GBATimerFlagsIsCountUp(currentTimer->flags)) {
|
||||
gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((gba->cpu->cycles - currentTimer->lastEvent) >> oldPrescale);
|
||||
*io = timer->reload;
|
||||
timer->oldReload = timer->reload;
|
||||
timer->lastEvent = timing->masterCycles + cpu->cycles;
|
||||
} else if (wasEnabled && !GBATimerFlagsIsEnable(timer->flags)) {
|
||||
mTimingDeschedule(timing, &timer->event);
|
||||
if (!GBATimerFlagsIsCountUp(timer->flags)) {
|
||||
*io = timer->oldReload + ((cpu->cycles - timer->lastEvent) >> oldPrescale);
|
||||
}
|
||||
} else if (GBATimerFlagsIsEnable(currentTimer->flags) && GBATimerFlagsGetPrescaleBits(currentTimer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
|
||||
mTimingDeschedule(&gba->timing, ¤tTimer->event);
|
||||
mTimingSchedule(&gba->timing, ¤tTimer->event, currentTimer->overflowInterval - currentTimer->lastEvent);
|
||||
} else if (GBATimerFlagsIsEnable(timer->flags) && GBATimerFlagsGetPrescaleBits(timer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(timer->flags)) {
|
||||
mTimingDeschedule(timing, &timer->event);
|
||||
mTimingSchedule(timing, &timer->event, timer->overflowInterval - timer->lastEvent);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue