GBA: Convert timers to mTiming

This commit is contained in:
Jeffrey Pfau 2016-09-23 22:56:45 -07:00
parent e423cd45e5
commit a506f6cd9d
6 changed files with 189 additions and 252 deletions

View File

@ -64,6 +64,7 @@ void GBTimerDivReset(struct GBTimer* timer) {
timer->p->memory.io[REG_DIV] = 0;
timer->internalDiv = 0;
timer->nextDiv = GB_DMG_DIV_PERIOD;
mTimingDeschedule(&timer->p->timing, &timer->event);
mTimingSchedule(&timer->p->timing, &timer->event, timer->nextDiv);
}

View File

@ -18,6 +18,7 @@
#include "gba/rr/rr.h"
#include "gba/serialize.h"
#include "gba/sio.h"
#include "gba/timer.h"
#include "gba/vfame.h"
#include "util/crc32.h"
@ -39,7 +40,6 @@ static const size_t GBA_MB_MAGIC_OFFSET = 0xC0;
static void GBAInit(void* cpu, struct mCPUComponent* component);
static void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh);
static void GBAProcessEvents(struct ARMCore* cpu);
static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles);
static void GBAHitStub(struct ARMCore* cpu, uint32_t opcode);
static void GBAIllegal(struct ARMCore* cpu, uint32_t opcode);
static void GBABreakpoint(struct ARMCore* cpu, int immediate);
@ -80,9 +80,6 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) {
gba->sio.p = gba;
GBASIOInit(&gba->sio);
gba->timersEnabled = 0;
memset(gba->timers, 0, sizeof(gba->timers));
gba->springIRQ = 0;
gba->keySource = 0;
gba->rotationSource = 0;
@ -114,6 +111,8 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) {
gba->pristineRom = 0;
gba->pristineRomSize = 0;
gba->yankedRomSize = 0;
mTimingInit(&gba->timing, &gba->cpu->cycles);
}
void GBAUnloadROM(struct GBA* gba) {
@ -156,6 +155,7 @@ void GBADestroy(struct GBA* gba) {
GBAAudioDeinit(&gba->audio);
GBASIODeinit(&gba->sio);
gba->rr = 0;
mTimingDeinit(&gba->timing);
}
void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh) {
@ -192,12 +192,10 @@ void GBAReset(struct ARMCore* cpu) {
GBAVideoReset(&gba->video);
GBAAudioReset(&gba->audio);
GBAIOInit(gba);
GBATimerInit(gba);
GBASIOReset(&gba->sio);
gba->timersEnabled = 0;
memset(gba->timers, 0, sizeof(gba->timers));
gba->lastJump = 0;
gba->haltPending = false;
gba->idleDetectionStep = 0;
@ -239,12 +237,21 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
int32_t cycles = cpu->nextEvent;
int32_t nextEvent = INT_MAX;
int32_t testEvent;
cpu->cycles -= cycles;
#ifndef NDEBUG
if (cycles < 0) {
mLOG(GBA, FATAL, "Negative cycles passed: %i", cycles);
}
#endif
mTimingTick(&gba->timing, cycles);
testEvent = mTimingNextEvent(&gba->timing);
if (testEvent < nextEvent) {
nextEvent = testEvent;
}
testEvent = GBAVideoProcessEvents(&gba->video, cycles);
if (testEvent < nextEvent) {
#ifndef NDEBUG
@ -265,16 +272,6 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
nextEvent = testEvent;
}
testEvent = GBATimersProcessEvents(gba, cycles);
if (testEvent < nextEvent) {
#ifndef NDEBUG
if (testEvent == 0) {
mLOG(GBA, ERROR, "Timers requiring 0 cycles");
}
#endif
nextEvent = testEvent;
}
testEvent = GBAMemoryRunDMAs(gba, cycles);
if (testEvent < nextEvent) {
#ifndef NDEBUG
@ -290,7 +287,6 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
nextEvent = testEvent;
}
cpu->cycles -= cycles;
cpu->nextEvent = nextEvent;
if (cpu->halted) {
@ -310,145 +306,6 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
} while (cpu->cycles >= cpu->nextEvent);
}
static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) {
int32_t nextEvent = INT_MAX;
if (gba->timersEnabled) {
struct GBATimer* timer;
struct GBATimer* nextTimer;
timer = &gba->timers[0];
if (GBATimerFlagsIsEnable(timer->flags) && timer->nextEvent != INT_MAX) {
timer->nextEvent -= cycles;
timer->lastEvent -= cycles;
while (timer->nextEvent <= 0) {
timer->lastEvent = timer->nextEvent;
timer->nextEvent += timer->overflowInterval;
gba->memory.io[REG_TM0CNT_LO >> 1] = timer->reload;
timer->oldReload = timer->reload;
if (GBATimerFlagsIsDoIrq(timer->flags)) {
GBARaiseIRQ(gba, IRQ_TIMER0);
}
if (gba->audio.enable) {
if ((gba->audio.chALeft || gba->audio.chARight) && gba->audio.chATimer == 0) {
GBAAudioSampleFIFO(&gba->audio, 0, timer->lastEvent);
}
if ((gba->audio.chBLeft || gba->audio.chBRight) && gba->audio.chBTimer == 0) {
GBAAudioSampleFIFO(&gba->audio, 1, timer->lastEvent);
}
}
nextTimer = &gba->timers[1];
if (GBATimerFlagsIsCountUp(nextTimer->flags)) {
++gba->memory.io[REG_TM1CNT_LO >> 1];
if (!gba->memory.io[REG_TM1CNT_LO >> 1]) {
nextTimer->nextEvent = cycles;
}
}
}
nextEvent = timer->nextEvent;
}
timer = &gba->timers[1];
if (GBATimerFlagsIsEnable(timer->flags) && timer->nextEvent != INT_MAX) {
timer->nextEvent -= cycles;
timer->lastEvent -= cycles;
if (timer->nextEvent <= 0) {
timer->lastEvent = timer->nextEvent;
timer->nextEvent += timer->overflowInterval;
gba->memory.io[REG_TM1CNT_LO >> 1] = timer->reload;
timer->oldReload = timer->reload;
if (GBATimerFlagsIsDoIrq(timer->flags)) {
GBARaiseIRQ(gba, IRQ_TIMER1);
}
if (gba->audio.enable) {
if ((gba->audio.chALeft || gba->audio.chARight) && gba->audio.chATimer == 1) {
GBAAudioSampleFIFO(&gba->audio, 0, timer->lastEvent);
}
if ((gba->audio.chBLeft || gba->audio.chBRight) && gba->audio.chBTimer == 1) {
GBAAudioSampleFIFO(&gba->audio, 1, timer->lastEvent);
}
}
if (GBATimerFlagsIsCountUp(timer->flags)) {
timer->nextEvent = INT_MAX;
}
nextTimer = &gba->timers[2];
if (GBATimerFlagsIsCountUp(nextTimer->flags)) {
++gba->memory.io[REG_TM2CNT_LO >> 1];
if (!gba->memory.io[REG_TM2CNT_LO >> 1]) {
nextTimer->nextEvent = cycles;
}
}
}
if (timer->nextEvent < nextEvent) {
nextEvent = timer->nextEvent;
}
}
timer = &gba->timers[2];
if (GBATimerFlagsIsEnable(timer->flags) && timer->nextEvent != INT_MAX) {
timer->nextEvent -= cycles;
timer->lastEvent -= cycles;
if (timer->nextEvent <= 0) {
timer->lastEvent = timer->nextEvent;
timer->nextEvent += timer->overflowInterval;
gba->memory.io[REG_TM2CNT_LO >> 1] = timer->reload;
timer->oldReload = timer->reload;
if (GBATimerFlagsIsDoIrq(timer->flags)) {
GBARaiseIRQ(gba, IRQ_TIMER2);
}
if (GBATimerFlagsIsCountUp(timer->flags)) {
timer->nextEvent = INT_MAX;
}
nextTimer = &gba->timers[3];
if (GBATimerFlagsIsCountUp(nextTimer->flags)) {
++gba->memory.io[REG_TM3CNT_LO >> 1];
if (!gba->memory.io[REG_TM3CNT_LO >> 1]) {
nextTimer->nextEvent = cycles;
}
}
}
if (timer->nextEvent < nextEvent) {
nextEvent = timer->nextEvent;
}
}
timer = &gba->timers[3];
if (GBATimerFlagsIsEnable(timer->flags) && timer->nextEvent != INT_MAX) {
timer->nextEvent -= cycles;
timer->lastEvent -= cycles;
if (timer->nextEvent <= 0) {
timer->lastEvent = timer->nextEvent;
timer->nextEvent += timer->overflowInterval;
gba->memory.io[REG_TM3CNT_LO >> 1] = timer->reload;
timer->oldReload = timer->reload;
if (GBATimerFlagsIsDoIrq(timer->flags)) {
GBARaiseIRQ(gba, IRQ_TIMER3);
}
if (GBATimerFlagsIsCountUp(timer->flags)) {
timer->nextEvent = INT_MAX;
}
}
if (timer->nextEvent < nextEvent) {
nextEvent = timer->nextEvent;
}
}
}
return nextEvent;
}
void GBAAttachDebugger(struct GBA* gba, struct mDebugger* debugger) {
gba->debugger = (struct ARMDebugger*) debugger->platform;
gba->debugger->setSoftwareBreakpoint = _setSoftwareBreakpoint;
@ -580,72 +437,6 @@ void GBAApplyPatch(struct GBA* gba, struct Patch* patch) {
gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize);
}
void GBATimerUpdateRegister(struct GBA* gba, int timer) {
struct GBATimer* currentTimer = &gba->timers[timer];
if (GBATimerFlagsIsEnable(currentTimer->flags) && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
int32_t prefetchSkew = 0;
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
gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((gba->cpu->cycles - currentTimer->lastEvent - 2 + prefetchSkew) >> GBATimerFlagsGetPrescaleBits(currentTimer->flags));
}
}
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 GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {
struct GBATimer* currentTimer = &gba->timers[timer];
GBATimerUpdateRegister(gba, timer);
unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(currentTimer->flags);
switch (control & 0x0003) {
case 0x0000:
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 0);
break;
case 0x0001:
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 6);
break;
case 0x0002:
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 8);
break;
case 0x0003:
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->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)) {
if (!GBATimerFlagsIsCountUp(currentTimer->flags)) {
currentTimer->nextEvent = gba->cpu->cycles + currentTimer->overflowInterval;
} else {
currentTimer->nextEvent = INT_MAX;
}
gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->reload;
currentTimer->oldReload = currentTimer->reload;
currentTimer->lastEvent = gba->cpu->cycles;
gba->timersEnabled |= 1 << timer;
} else if (wasEnabled && !GBATimerFlagsIsEnable(currentTimer->flags)) {
if (!GBATimerFlagsIsCountUp(currentTimer->flags)) {
gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((gba->cpu->cycles - currentTimer->lastEvent) >> oldPrescale);
}
gba->timersEnabled &= ~(1 << timer);
} else if (GBATimerFlagsGetPrescaleBits(currentTimer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
// FIXME: this might be before present
currentTimer->nextEvent = currentTimer->lastEvent + currentTimer->overflowInterval;
}
if (currentTimer->nextEvent < gba->cpu->nextEvent) {
gba->cpu->nextEvent = currentTimer->nextEvent;
}
};
void GBAWriteIE(struct GBA* gba, uint16_t value) {
if (value & (1 << IRQ_KEYPAD)) {
mLOG(GBA, STUB, "Keypad interrupts not implemented");

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2015 Jeffrey Pfau
/* Copyright (c) 2013-2016 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -10,12 +10,14 @@
#include "arm/arm.h"
#include "core/log.h"
#include "core/timing.h"
#include "gba/interface.h"
#include "gba/memory.h"
#include "gba/video.h"
#include "gba/audio.h"
#include "gba/sio.h"
#include "gba/timer.h"
#define GBA_ARM7TDMI_FREQUENCY 0x1000000U
@ -55,25 +57,10 @@ struct VFile;
mLOG_DECLARE_CATEGORY(GBA);
mLOG_DECLARE_CATEGORY(GBA_DEBUG);
DECL_BITFIELD(GBATimerFlags, uint32_t);
DECL_BITS(GBATimerFlags, PrescaleBits, 0, 4);
DECL_BIT(GBATimerFlags, CountUp, 4);
DECL_BIT(GBATimerFlags, DoIrq, 5);
DECL_BIT(GBATimerFlags, Enable, 6);
DECL_BITFIELD(GBADebugFlags, uint16_t);
DECL_BITS(GBADebugFlags, Level, 0, 3);
DECL_BIT(GBADebugFlags, Send, 8);
struct GBATimer {
uint16_t reload;
uint16_t oldReload;
int32_t lastEvent;
int32_t nextEvent;
int32_t overflowInterval;
GBATimerFlags flags;
};
struct GBA {
struct mCPUComponent d;
@ -84,13 +71,13 @@ struct GBA {
struct GBASIO sio;
struct mCoreSync* sync;
struct mTiming timing;
struct ARMDebugger* debugger;
uint32_t bus;
int performingDMA;
int timersEnabled;
struct GBATimer timers[4];
int springIRQ;
@ -153,10 +140,6 @@ void GBADestroy(struct GBA* gba);
void GBAReset(struct ARMCore* cpu);
void GBASkipBIOS(struct GBA* gba);
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 GBAWriteIE(struct GBA* gba, uint16_t value);
void GBAWriteIME(struct GBA* gba, uint16_t value);
void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq);

View File

@ -912,7 +912,6 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) {
STORE_16(gba->timers[i].reload, 0, &state->timers[i].reload);
STORE_16(gba->timers[i].oldReload, 0, &state->timers[i].oldReload);
STORE_32(gba->timers[i].lastEvent, 0, &state->timers[i].lastEvent);
STORE_32(gba->timers[i].nextEvent, 0, &state->timers[i].nextEvent);
STORE_32(gba->timers[i].overflowInterval, 0, &state->timers[i].overflowInterval);
STORE_32(gba->timers[i].flags, 0, &state->timers[i].flags);
STORE_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource);
@ -936,7 +935,6 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) {
}
}
gba->timersEnabled = 0;
for (i = 0; i < 4; ++i) {
LOAD_16(gba->timers[i].reload, 0, &state->timers[i].reload);
LOAD_16(gba->timers[i].oldReload, 0, &state->timers[i].oldReload);
@ -945,10 +943,8 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) {
if (i > 0 && GBATimerFlagsIsCountUp(gba->timers[i].flags)) {
// Overwrite invalid values in savestate
gba->timers[i].lastEvent = 0;
gba->timers[i].nextEvent = INT_MAX;
} else {
LOAD_32(gba->timers[i].lastEvent, 0, &state->timers[i].lastEvent);
LOAD_32(gba->timers[i].nextEvent, 0, &state->timers[i].nextEvent);
}
LOAD_16(gba->memory.dma[i].reg, (REG_DMA0CNT_HI + i * 12), state->io);
LOAD_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource);
@ -958,10 +954,6 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) {
if (GBADMARegisterGetTiming(gba->memory.dma[i].reg) != DMA_TIMING_NOW) {
GBAMemoryScheduleDMA(gba, i, &gba->memory.dma[i]);
}
if (GBATimerFlagsIsEnable(gba->timers[i].flags)) {
gba->timersEnabled |= 1 << i;
}
}
GBAAudioWriteSOUNDCNT_X(&gba->audio, gba->memory.io[REG_SOUNDCNT_X >> 1]);
GBAMemoryUpdateDMAs(gba, 0);

137
src/gba/timer.c Normal file
View File

@ -0,0 +1,137 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "timer.h"
#include "gba/gba.h"
#include "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;
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);
}
}
static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
GBATimerUpdate(timing, context, 0, cyclesLate);
}
static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
GBATimerUpdate(timing, context, 1, cyclesLate);
}
static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
GBATimerUpdate(timing, context, 2, cyclesLate);
}
static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
GBATimerUpdate(timing, context, 3, cyclesLate);
}
void GBATimerInit(struct GBA* gba) {
memset(gba->timers, 0, sizeof(gba->timers));
gba->timers[0].event.name = "GBA Timer 0";
gba->timers[0].event.callback = GBATimerUpdate0;
gba->timers[0].event.context = gba;
gba->timers[1].event.name = "GBA Timer 1";
gba->timers[1].event.callback = GBATimerUpdate1;
gba->timers[1].event.context = gba;
gba->timers[2].event.name = "GBA Timer 2";
gba->timers[2].event.callback = GBATimerUpdate2;
gba->timers[2].event.context = gba;
gba->timers[3].event.name = "GBA Timer 3";
gba->timers[3].event.callback = GBATimerUpdate3;
gba->timers[3].event.context = gba;
}
void GBATimerUpdateRegister(struct GBA* gba, int timer) {
struct GBATimer* currentTimer = &gba->timers[timer];
if (GBATimerFlagsIsEnable(currentTimer->flags) && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
int32_t prefetchSkew = 0;
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));
}
}
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 GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) {
struct GBATimer* currentTimer = &gba->timers[timer];
GBATimerUpdateRegister(gba, timer);
unsigned oldPrescale = GBATimerFlagsGetPrescaleBits(currentTimer->flags);
switch (control & 0x0003) {
case 0x0000:
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 0);
break;
case 0x0001:
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 6);
break;
case 0x0002:
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->flags, 8);
break;
case 0x0003:
currentTimer->flags = GBATimerFlagsSetPrescaleBits(currentTimer->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, &currentTimer->event);
if (!GBATimerFlagsIsCountUp(currentTimer->flags)) {
mTimingSchedule(&gba->timing, &currentTimer->event, currentTimer->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)) {
if (!GBATimerFlagsIsCountUp(currentTimer->flags)) {
gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((gba->cpu->cycles - currentTimer->lastEvent) >> oldPrescale);
}
} else if (GBATimerFlagsIsEnable(currentTimer->flags) && GBATimerFlagsGetPrescaleBits(currentTimer->flags) != oldPrescale && !GBATimerFlagsIsCountUp(currentTimer->flags)) {
mTimingDeschedule(&gba->timing, &currentTimer->event);
mTimingSchedule(&gba->timing, &currentTimer->event, currentTimer->overflowInterval - currentTimer->lastEvent);
}
}

33
src/gba/timer.h Normal file
View File

@ -0,0 +1,33 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef GBA_TIMER_H
#define GBA_TIMER_H
#include "util/common.h"
#include "core/timing.h"
DECL_BITFIELD(GBATimerFlags, uint32_t);
DECL_BITS(GBATimerFlags, PrescaleBits, 0, 4);
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;
uint32_t lastEvent;
struct mTimingEvent event;
int32_t overflowInterval;
GBATimerFlags flags;
};
void GBATimerInit(struct GBA* gba);
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);
#endif