mirror of https://github.com/mgba-emu/mgba.git
GBA: Convert timers to mTiming
This commit is contained in:
parent
e423cd45e5
commit
a506f6cd9d
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
237
src/gba/gba.c
237
src/gba/gba.c
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, ¤tTimer->event);
|
||||
if (!GBATimerFlagsIsCountUp(currentTimer->flags)) {
|
||||
mTimingSchedule(&gba->timing, ¤tTimer->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, ¤tTimer->event);
|
||||
mTimingSchedule(&gba->timing, ¤tTimer->event, currentTimer->overflowInterval - currentTimer->lastEvent);
|
||||
}
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue