diff --git a/src/gba/gba-io.c b/src/gba/gba-io.c index 8a91ea27e..3606f3f96 100644 --- a/src/gba/gba-io.c +++ b/src/gba/gba-io.c @@ -36,6 +36,36 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) { value = GBAMemoryWriteDMACNT_HI(&gba->memory, 3, value); break; + case REG_TM0CNT_LO: + GBATimerWriteTMCNT_LO(gba, 0, value); + return; + case REG_TM1CNT_LO: + GBATimerWriteTMCNT_LO(gba, 1, value); + return; + case REG_TM2CNT_LO: + GBATimerWriteTMCNT_LO(gba, 2, value); + return; + case REG_TM3CNT_LO: + GBATimerWriteTMCNT_LO(gba, 3, value); + return; + + case REG_TM0CNT_HI: + value &= 0x00C7; + GBATimerWriteTMCNT_HI(gba, 0, value); + break; + case REG_TM1CNT_HI: + value &= 0x00C7; + GBATimerWriteTMCNT_HI(gba, 1, value); + break; + case REG_TM2CNT_HI: + value &= 0x00C7; + GBATimerWriteTMCNT_HI(gba, 2, value); + break; + case REG_TM3CNT_HI: + value &= 0x00C7; + GBATimerWriteTMCNT_HI(gba, 3, value); + break; + case REG_WAITCNT: GBAAdjustWaitstates(&gba->memory, value); break; @@ -101,6 +131,20 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { case REG_DISPSTAT: return gba->memory.io[REG_DISPSTAT >> 1] | GBAVideoReadDISPSTAT(&gba->video); break; + + case REG_TM0CNT_LO: + GBATimerUpdateRegister(gba, 0); + break; + case REG_TM1CNT_LO: + GBATimerUpdateRegister(gba, 1); + break; + case REG_TM2CNT_LO: + GBATimerUpdateRegister(gba, 2); + break; + case REG_TM3CNT_LO: + GBATimerUpdateRegister(gba, 3); + break; + case REG_DMA0CNT_LO: case REG_DMA1CNT_LO: case REG_DMA2CNT_LO: diff --git a/src/gba/gba.c b/src/gba/gba.c index 3d3fa4b8b..b98fc8d7f 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -104,7 +104,9 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) { timer = &gba->timers[0]; if (timer->enable) { timer->nextEvent -= cycles; + timer->lastEvent -= cycles; if (timer->nextEvent <= 0) { + timer->lastEvent = timer->nextEvent; timer->nextEvent += timer->overflowInterval; gba->memory.io[REG_TM0CNT_LO >> 1] = timer->reload; timer->oldReload = timer->reload; @@ -127,7 +129,9 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) { timer = &gba->timers[1]; if (timer->enable) { 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; @@ -156,8 +160,10 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) { timer = &gba->timers[2]; if (timer->enable) { timer->nextEvent -= cycles; + timer->lastEvent -= cycles; nextEvent = timer->nextEvent; 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; @@ -186,8 +192,10 @@ static int32_t GBATimersProcessEvents(struct GBA* gba, int32_t cycles) { timer = &gba->timers[3]; if (timer->enable) { timer->nextEvent -= cycles; + timer->lastEvent -= cycles; nextEvent = timer->nextEvent; 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; @@ -218,6 +226,65 @@ void GBALoadROM(struct GBA* gba, int fd) { // TODO: error check } +void GBATimerUpdateRegister(struct GBA* gba, int timer) { + struct GBATimer* currentTimer = &gba->timers[timer]; + if (currentTimer->enable && !currentTimer->countUp) { + gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((gba->cpu.cycles - currentTimer->lastEvent) >> currentTimer->prescaleBits); + } +} + +void GBATimerWriteTMCNT_LO(struct GBA* gba, int timer, uint16_t reload) { + gba->timers[timer].reload = reload; +} + +void GBATimerWriteTMCNT_HI(struct GBA* gba, int timer, uint16_t control) { + struct GBATimer* currentTimer = &gba->timers[timer]; + GBATimerUpdateRegister(gba, timer); + + int oldPrescale = currentTimer->prescaleBits; + switch (control & 0x0003) { + case 0x0000: + currentTimer->prescaleBits = 0; + break; + case 0x0001: + currentTimer->prescaleBits = 6; + break; + case 0x0002: + currentTimer->prescaleBits = 8; + break; + case 0x0003: + currentTimer->prescaleBits = 10; + break; + } + currentTimer->countUp = !!(control & 0x0004); + currentTimer->doIrq = !!(control & 0x0040); + currentTimer->overflowInterval = (0x10000 - currentTimer->reload) << currentTimer->prescaleBits; + int wasEnabled = currentTimer->enable; + currentTimer->enable = !!(control & 0x0080); + if (!wasEnabled && currentTimer->enable) { + if (!currentTimer->countUp) { + 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; + gba->timersEnabled |= 1 << timer; + } else if (wasEnabled && !currentTimer->enable) { + if (!currentTimer->countUp) { + gba->memory.io[(REG_TM0CNT_LO + (timer << 2)) >> 1] = currentTimer->oldReload + ((gba->cpu.cycles - currentTimer->lastEvent) >> oldPrescale); + } + gba->timersEnabled &= ~(1 << timer); + } else if (currentTimer->prescaleBits != oldPrescale && !currentTimer->countUp) { + // 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_SIO)) { GBALog(GBA_LOG_STUB, "SIO interrupts not implemented"); diff --git a/src/gba/gba.h b/src/gba/gba.h index 1fbcc7fa7..8160ea199 100644 --- a/src/gba/gba.h +++ b/src/gba/gba.h @@ -50,6 +50,7 @@ struct GBA { struct GBATimer { uint16_t reload; uint16_t oldReload; + int32_t lastEvent; int32_t nextEvent; int32_t overflowInterval; unsigned prescaleBits : 4; @@ -73,6 +74,10 @@ void GBAMemoryDeinit(struct GBAMemory* memory); void GBABoardInit(struct GBABoard* board); void GBABoardReset(struct ARMBoard* board); +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);