mirror of https://github.com/mgba-emu/mgba.git
DS: Add timers and start interrupts
This commit is contained in:
parent
9c91235a34
commit
e649be94f5
44
src/ds/ds.c
44
src/ds/ds.c
|
@ -80,6 +80,10 @@ 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));
|
||||
ds->keySource = NULL;
|
||||
ds->rtcSource = NULL;
|
||||
ds->rumble = NULL;
|
||||
|
@ -196,22 +200,26 @@ static void DSProcessEvents(struct ARMCore* cpu) {
|
|||
ds->springIRQ7 = 0;
|
||||
}
|
||||
|
||||
do {
|
||||
int32_t cycles = cpu->nextEvent;
|
||||
int32_t nextEvent = INT_MAX;
|
||||
int32_t testEvent;
|
||||
#ifndef NDEBUG
|
||||
if (cycles < 0) {
|
||||
mLOG(DS, FATAL, "Negative cycles passed: %i", cycles);
|
||||
}
|
||||
#endif
|
||||
|
||||
testEvent = DSTimersProcessEvents(ds, cycles);
|
||||
if (testEvent < nextEvent) {
|
||||
nextEvent = testEvent;
|
||||
}
|
||||
|
||||
cpu->cycles -= cycles;
|
||||
cpu->nextEvent = nextEvent;
|
||||
|
||||
if (cpu->halted) {
|
||||
cpu->cycles = cpu->nextEvent;
|
||||
}
|
||||
} while (cpu->cycles >= cpu->nextEvent);
|
||||
}
|
||||
|
||||
void DSAttachDebugger(struct DS* ds, struct mDebugger* debugger) {
|
||||
|
@ -454,4 +462,34 @@ void DS9WriteCP15(struct ARMCore* cpu, int crn, int crm, int opcode1, int opcode
|
|||
case 9:
|
||||
_writeTCMControl(cpu, crm, opcode2, value);
|
||||
break;
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
void DSWriteIE(struct ARMCore* cpu, uint16_t* io, uint32_t value) {
|
||||
if (io[DS7_REG_IME >> 1] && (value & io[DS7_REG_IF_LO >> 1] || (value >> 16) & io[DS7_REG_IF_HI >> 1])) {
|
||||
ARMRaiseIRQ(cpu);
|
||||
}
|
||||
}
|
||||
void DSWriteIME(struct ARMCore* cpu, uint16_t* io, uint16_t value) {
|
||||
if (value && (io[DS7_REG_IE_LO >> 1] & io[DS7_REG_IF_LO >> 1] || io[DS7_REG_IE_HI >> 1] & io[DS7_REG_IF_HI >> 1])) {
|
||||
ARMRaiseIRQ(cpu);
|
||||
}
|
||||
}
|
||||
|
||||
void DSRaiseIRQ(struct ARMCore* cpu, uint16_t* io, enum DSIRQ irq) {
|
||||
if (irq < 16) {
|
||||
io[DS7_REG_IF_LO >> 1] |= 1 << irq;
|
||||
} else {
|
||||
io[DS7_REG_IF_HI >> 1] |= 1 << (irq - 16);
|
||||
}
|
||||
cpu->halted = 0;
|
||||
|
||||
if (!io[DS7_REG_IME >> 1]) {
|
||||
return;
|
||||
}
|
||||
if (irq < 16 && (io[DS7_REG_IE_LO >> 1] & 1 << irq)) {
|
||||
ARMRaiseIRQ(cpu);
|
||||
} else if (io[DS7_REG_IE_HI >> 1] & 1 << (irq - 16)) {
|
||||
ARMRaiseIRQ(cpu);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "core/log.h"
|
||||
|
||||
#include "ds/memory.h"
|
||||
#include "ds/timer.h"
|
||||
#include "ds/video.h"
|
||||
|
||||
extern const uint32_t DS_ARM946ES_FREQUENCY;
|
||||
|
@ -57,6 +58,10 @@ struct DS {
|
|||
struct ARMCore* arm9;
|
||||
struct DSMemory memory;
|
||||
struct DSVideo video;
|
||||
int timersEnabled7;
|
||||
int timersEnabled9;
|
||||
struct DSTimer timers7[4];
|
||||
struct DSTimer timers9[4];
|
||||
|
||||
struct mCoreSync* sync;
|
||||
|
||||
|
@ -131,4 +136,8 @@ bool DSIsROM(struct VFile* vf);
|
|||
void DSGetGameCode(struct DS* ds, char* out);
|
||||
void DSGetGameTitle(struct DS* ds, char* out);
|
||||
|
||||
void DSWriteIME(struct ARMCore* cpu, uint16_t* io, uint16_t value);
|
||||
void DSWriteIE(struct ARMCore* cpu, uint16_t* io, uint32_t value);
|
||||
void DSRaiseIRQ(struct ARMCore* cpu, uint16_t* io, enum DSIRQ irq);
|
||||
|
||||
#endif
|
||||
|
|
74
src/ds/io.c
74
src/ds/io.c
|
@ -24,11 +24,53 @@ void DS7IOInit(struct DS* ds) {
|
|||
|
||||
void DS7IOWrite(struct DS* ds, uint32_t address, uint16_t value) {
|
||||
switch (address) {
|
||||
case DS9_REG_IPCSYNC:
|
||||
// Timers
|
||||
case DS7_REG_TM0CNT_LO:
|
||||
DSTimerWriteTMCNT_LO(&ds->timers7[0], value);
|
||||
return;
|
||||
case DS7_REG_TM1CNT_LO:
|
||||
DSTimerWriteTMCNT_LO(&ds->timers7[1], value);
|
||||
return;
|
||||
case DS7_REG_TM2CNT_LO:
|
||||
DSTimerWriteTMCNT_LO(&ds->timers7[2], value);
|
||||
return;
|
||||
case DS7_REG_TM3CNT_LO:
|
||||
DSTimerWriteTMCNT_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);
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
break;
|
||||
|
||||
case DS7_REG_IPCSYNC:
|
||||
value &= 0x6F00;
|
||||
value |= ds->memory.io7[address >> 1] & 0x000F;
|
||||
_writeIPCSync(ds->arm9, ds->memory.io9, value);
|
||||
break;
|
||||
case DS7_REG_IME:
|
||||
DSWriteIME(ds->arm7, ds->memory.io7, value);
|
||||
break;
|
||||
default:
|
||||
mLOG(DS_IO, STUB, "Stub DS7 I/O register write: %06X:%04X", address, value);
|
||||
if (address >= DS7_REG_MAX) {
|
||||
|
@ -50,11 +92,28 @@ void DS7IOWrite8(struct DS* ds, uint32_t address, uint8_t value) {
|
|||
}
|
||||
}
|
||||
|
||||
void DS7IOWrite32(struct DS* ds, uint32_t address, uint32_t value);
|
||||
void DS7IOWrite32(struct DS* ds, uint32_t address, uint32_t value) {
|
||||
switch (address) {
|
||||
case DS7_REG_IE_LO:
|
||||
DSWriteIE(ds->arm7, ds->memory.io7, value);
|
||||
break;
|
||||
default:
|
||||
DS7IOWrite(ds, address, value & 0xFFFF);
|
||||
DS7IOWrite(ds, address | 2, value >> 16);
|
||||
return;
|
||||
}
|
||||
ds->memory.io7[address >> 1] = value;
|
||||
ds->memory.io7[(address >> 1) + 1] = value >> 16;
|
||||
}
|
||||
|
||||
uint16_t DS7IORead(struct DS* ds, uint32_t address) {
|
||||
switch (address) {
|
||||
case DS7_REG_IPCSYNC:
|
||||
case DS7_REG_IME:
|
||||
case DS7_REG_IE_LO:
|
||||
case DS7_REG_IE_HI:
|
||||
case DS7_REG_IF_LO:
|
||||
case DS7_REG_IF_HI:
|
||||
// Handled transparently by the registers
|
||||
break;
|
||||
default:
|
||||
|
@ -98,7 +157,16 @@ void DS9IOWrite8(struct DS* ds, uint32_t address, uint8_t value) {
|
|||
}
|
||||
}
|
||||
|
||||
void DS9IOWrite32(struct DS* ds, uint32_t address, uint32_t value);
|
||||
void DS9IOWrite32(struct DS* ds, uint32_t address, uint32_t value) {
|
||||
switch (address) {
|
||||
default:
|
||||
DS9IOWrite(ds, address, value & 0xFFFF);
|
||||
DS9IOWrite(ds, address | 2, value >> 16);
|
||||
return;
|
||||
}
|
||||
ds->memory.io9[address >> 1] = value;
|
||||
ds->memory.io9[(address >> 1) + 1] = value >> 16;
|
||||
}
|
||||
|
||||
uint16_t DS9IORead(struct DS* ds, uint32_t address) {
|
||||
switch (address) {
|
||||
|
|
|
@ -248,6 +248,9 @@ uint32_t DS7Load32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
}
|
||||
mLOG(DS_MEM, STUB, "Unimplemented DS7 Load32: %08X", address);
|
||||
break;
|
||||
case DS_REGION_IO:
|
||||
value = DS7IORead(ds, address & 0x00FFFFFC) | (DS7IORead(ds, (address & 0x00FFFFFC) | 2) << 16);
|
||||
break;
|
||||
default:
|
||||
mLOG(DS_MEM, STUB, "Unimplemented DS7 Load32: %08X", address);
|
||||
break;
|
||||
|
@ -347,6 +350,9 @@ void DS7Store32(struct ARMCore* cpu, uint32_t address, int32_t value, int* cycle
|
|||
}
|
||||
mLOG(DS_MEM, STUB, "Unimplemented DS7 Store32: %08X:%08X", address, value);
|
||||
break;
|
||||
case DS_REGION_IO:
|
||||
DS7IOWrite32(ds, address & 0x00FFFFFF, value);
|
||||
break;
|
||||
default:
|
||||
mLOG(DS_MEM, STUB, "Unimplemented DS7 Store32: %08X:%08X", address, value);
|
||||
break;
|
||||
|
@ -455,6 +461,9 @@ uint32_t DS7LoadMultiple(struct ARMCore* cpu, uint32_t address, int mask, enum L
|
|||
mLOG(DS_MEM, STUB, "Unimplemented DS7 LDM: %08X", address);
|
||||
});
|
||||
break;
|
||||
case DS_REGION_IO:
|
||||
LDM_LOOP(value = DS7IORead(ds, address & 0x00FFFFFC) | (DS7IORead(ds, (address & 0x00FFFFFC) | 2) << 16));
|
||||
break;
|
||||
default:
|
||||
mLOG(DS_MEM, STUB, "Unimplemented DS7 LDM: %08X", address);
|
||||
LDM_LOOP(value = 0);
|
||||
|
@ -592,6 +601,9 @@ uint32_t DS9Load32(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
|||
}
|
||||
mLOG(DS_MEM, STUB, "Unimplemented DS9 Load32: %08X", address);
|
||||
break;
|
||||
case DS_REGION_IO:
|
||||
value = DS9IORead(ds, address & 0x00FFFFFC) | (DS9IORead(ds, (address & 0x00FFFFFC) | 2) << 16);
|
||||
break;
|
||||
default:
|
||||
mLOG(DS_MEM, STUB, "Unimplemented DS9 Load32: %08X", address);
|
||||
break;
|
||||
|
@ -682,6 +694,9 @@ void DS9Store32(struct ARMCore* cpu, uint32_t address, int32_t value, int* cycle
|
|||
}
|
||||
mLOG(DS_MEM, STUB, "Unimplemented DS9 Store32: %08X:%08X", address, value);
|
||||
break;
|
||||
case DS_REGION_IO:
|
||||
DS9IOWrite32(ds, address & 0x00FFFFFF, value);
|
||||
break;
|
||||
default:
|
||||
mLOG(DS_MEM, STUB, "Unimplemented DS9 Store32: %08X:%08X", address, value);
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/* 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 "arm/arm.h"
|
||||
#include "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));
|
||||
}
|
||||
}
|
||||
|
||||
void DSTimerWriteTMCNT_LO(struct DSTimer* timer, uint16_t reload) {
|
||||
timer->reload = reload;
|
||||
timer->overflowInterval = (0x10000 - timer->reload) << DSTimerFlagsGetPrescaleBits(timer->flags);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t DSTimersProcessEvents(struct DS* ds, int32_t cycles) {
|
||||
int32_t nextEvent = INT_MAX;
|
||||
if (!ds->timersEnabled7) {
|
||||
return nextEvent;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/* 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 DS_TIMER_H
|
||||
#define DS_TIMER_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
#endif
|
Loading…
Reference in New Issue