GB MBC: Partial TAMA5 RTC

This commit is contained in:
Vicki Pfau 2022-08-28 03:53:51 -07:00
parent 2a9f32a840
commit bac42c9027
8 changed files with 314 additions and 13 deletions

View File

@ -78,6 +78,7 @@ Misc:
- FFmpeg: Support dynamic audio sample rate
- GB Audio: Increase sample rate
- GB MBC: Filter out MBC errors when cartridge is yanked (fixes mgba.io/i/2488)
- GB MBC: Partially implement TAMA5 RTC
- GB Video: Add default SGB border
- GBA: Automatically skip BIOS if ROM has invalid logo
- GBA: Refine multiboot detection (fixes mgba.io/i/2192)

View File

@ -65,7 +65,7 @@ The following mappers are partially supported:
- MBC6 (missing flash memory write support)
- MMM01
- Pocket Cam
- TAMA5 (missing RTC support)
- TAMA5 (incomplete RTC support)
- HuC-1 (missing IR support)
- HuC-3 (missing IR support)
- Sachen MMC2 (missing alternate wiring support)

View File

@ -50,12 +50,23 @@ struct GBMBCHuC3SaveBuffer {
uint64_t latchedUnix;
};
struct GBMBCTAMA5SaveBuffer {
uint8_t rtcTimerPage[0x8];
uint8_t rtcAlarmPage[0x8];
uint8_t rtcFreePage0[0x8];
uint8_t rtcFreePage1[0x8];
uint64_t latchedUnix;
};
void GBMBCRTCRead(struct GB* gb);
void GBMBCRTCWrite(struct GB* gb);
void GBMBCHuC3Read(struct GB* gb);
void GBMBCHuC3Write(struct GB* gb);
void GBMBCTAMA5Read(struct GB* gb);
void GBMBCTAMA5Write(struct GB* gb);
CXX_GUARD_END
#endif

View File

@ -94,7 +94,7 @@ enum GBTAMA5Register {
GBTAMA5_BANK_HI = 0x1,
GBTAMA5_WRITE_LO = 0x4,
GBTAMA5_WRITE_HI = 0x5,
GBTAMA5_CS = 0x6,
GBTAMA5_ADDR_HI = 0x6,
GBTAMA5_ADDR_LO = 0x7,
GBTAMA5_MAX = 0x8,
GBTAMA5_ACTIVE = 0xA,
@ -102,6 +102,33 @@ enum GBTAMA5Register {
GBTAMA5_READ_HI = 0xD,
};
enum GBTAMA6RTCRegister {
GBTAMA6_RTC_PA0_SECOND_1 = 0x0,
GBTAMA6_RTC_PA0_SECOND_10 = 0x1,
GBTAMA6_RTC_PA0_MINUTE_1 = 0x2,
GBTAMA6_RTC_PA0_MINUTE_10 = 0x3,
GBTAMA6_RTC_PA0_HOUR_1 = 0x4,
GBTAMA6_RTC_PA0_HOUR_10 = 0x5,
GBTAMA6_RTC_PA0_WEEK = 0x6,
GBTAMA6_RTC_PA0_DAY_1 = 0x7,
GBTAMA6_RTC_PA0_DAY_10 = 0x8,
GBTAMA6_RTC_PA0_MONTH_1 = 0x9,
GBTAMA6_RTC_PA0_MONTH_10 = 0xA,
GBTAMA6_RTC_PA0_YEAR_1 = 0xB,
GBTAMA6_RTC_PA0_YEAR_10 = 0xC,
GBTAMA6_RTC_PAGE = 0xD,
GBTAMA6_RTC_TEST = 0xE,
GBTAMA6_RTC_RESET = 0xF,
GBTAMA6_RTC_MAX
};
enum GBTAMA6Command {
GBTAMA6_MINUTE_WRITE = 0x4,
GBTAMA6_HOUR_WRITE = 0x5,
GBTAMA6_MINUTE_READ = 0x6,
GBTAMA6_HOUR_READ = 0x7,
};
enum GBHuC3Register {
GBHUC3_RTC_MINUTES_LO = 0x10,
GBHUC3_RTC_MINUTES_MI = 0x11,
@ -180,6 +207,7 @@ struct GBPocketCamState {
struct GBTAMA5State {
uint8_t reg;
uint8_t registers[GBTAMA5_MAX];
uint8_t rtcTimerPage[GBTAMA6_RTC_MAX];
};
struct GBHuC3State {

View File

@ -406,6 +406,10 @@ struct GBSerializedState {
uint8_t locked;
uint8_t bank0;
} mmm01;
struct {
uint64_t lastLatch;
uint8_t reg;
} tama5;
struct {
uint64_t lastLatch;
uint8_t index;
@ -456,7 +460,13 @@ struct GBSerializedState {
uint32_t reserved2[0xA4];
uint8_t huc3Registers[0x80];
union {
uint8_t huc3Registers[0x80];
struct {
uint8_t registers[8];
uint8_t rtcTimerPage[8];
} tama5Registers;
};
struct {
uint8_t attributes[90];

View File

@ -220,6 +220,8 @@ static void GBSramDeinit(struct GB* gb) {
GBMBCRTCWrite(gb);
} else if (gb->memory.mbcType == GB_HuC3) {
GBMBCHuC3Write(gb);
} else if (gb->memory.mbcType == GB_TAMA5) {
GBMBCTAMA5Write(gb);
}
}
gb->sramVf = NULL;
@ -244,6 +246,8 @@ bool GBLoadSave(struct GB* gb, struct VFile* vf) {
GBMBCRTCRead(gb);
} else if (gb->memory.mbcType == GB_HuC3) {
GBMBCHuC3Read(gb);
} else if (gb->memory.mbcType == GB_TAMA5) {
GBMBCTAMA5Read(gb);
}
}
return vf;
@ -329,6 +333,8 @@ void GBSramClean(struct GB* gb, uint32_t frameCount) {
GBMBCRTCWrite(gb);
} else if (gb->memory.mbcType == GB_HuC3) {
GBMBCHuC3Write(gb);
} else if (gb->memory.mbcType == GB_TAMA5) {
GBMBCTAMA5Write(gb);
}
if (gb->sramVf == gb->sramRealVf) {
if (gb->memory.sram && gb->sramVf->sync(gb->sramVf, gb->memory.sram, gb->sramSize)) {

View File

@ -453,8 +453,6 @@ void GBMBCInit(struct GB* gb) {
gb->memory.mbcRead = _GBHuC3Read;
break;
case GB_TAMA5:
mLOG(GB_MBC, WARN, "unimplemented MBC: TAMA5");
memset(gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs));
gb->memory.mbcWrite = _GBTAMA5;
gb->memory.mbcRead = _GBTAMA5Read;
gb->sramSize = 0x20;
@ -540,6 +538,8 @@ void GBMBCInit(struct GB* gb) {
GBMBCRTCRead(gb);
} else if (gb->memory.mbcType == GB_HuC3) {
GBMBCHuC3Read(gb);
} else if (gb->memory.mbcType == GB_TAMA5) {
GBMBCTAMA5Read(gb);
}
}
@ -1514,6 +1514,150 @@ void _GBPocketCamCapture(struct GBMemory* memory) {
}
}
static const int _daysToMonth[] = {
[ 1] = 0,
[ 2] = 31,
[ 3] = 31 + 28,
[ 4] = 31 + 28 + 31,
[ 5] = 31 + 28 + 31 + 30,
[ 6] = 31 + 28 + 31 + 30 + 31,
[ 7] = 31 + 28 + 31 + 30 + 31 + 30,
[ 8] = 31 + 28 + 31 + 30 + 31 + 30 + 31,
[ 9] = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
[10] = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
[11] = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
[12] = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
};
static int _tama6DMYToDayOfYear(int day, int month, int year) {
if (month < 1 || month > 12) {
return -1;
}
day += _daysToMonth[month];
if (month > 2 && (year % 4) == 0) {
++day;
}
return day;
}
static int _tama6DayOfYearToMonth(int day, int year) {
int month;
for (month = 1; month < 12; ++month) {
if (day <= _daysToMonth[month + 1]) {
return month;
}
if (month == 2 && year % 4 == 0) {
if (day == 60) {
return 2;
}
--day;
}
}
return 12;
}
static int _tama6DayOfYearToDayOfMonth(int day, int year) {
int month;
for (month = 1; month < 12; ++month) {
if (day <= _daysToMonth[month + 1]) {
return day - _daysToMonth[month];
}
if (month == 2 && year % 4 == 0) {
if (day == 60) {
return 29;
}
--day;
}
}
return day - _daysToMonth[12];
}
static void _latchTAMA6Rtc(struct mRTCSource* rtc, uint8_t* timerRegs, time_t* rtcLastLatch) {
time_t t;
if (rtc) {
if (rtc->sample) {
rtc->sample(rtc);
}
t = rtc->unixTime(rtc);
} else {
t = time(0);
}
time_t currentLatch = t;
t -= *rtcLastLatch;
*rtcLastLatch = currentLatch;
if (!t) {
return;
}
int64_t diff;
diff = timerRegs[GBTAMA6_RTC_PA0_SECOND_1] + timerRegs[GBTAMA6_RTC_PA0_SECOND_10] * 10 + t % 60;
if (diff < 0) {
diff += 60;
t -= 60;
}
timerRegs[GBTAMA6_RTC_PA0_SECOND_1] = diff % 10;
timerRegs[GBTAMA6_RTC_PA0_SECOND_10] = (diff % 60) / 10;
t /= 60;
t += diff / 60;
diff = timerRegs[GBTAMA6_RTC_PA0_MINUTE_1] + timerRegs[GBTAMA6_RTC_PA0_MINUTE_10] * 10 + t % 60;
if (diff < 0) {
diff += 60;
t -= 60;
}
timerRegs[GBTAMA6_RTC_PA0_MINUTE_1] = diff % 10;
timerRegs[GBTAMA6_RTC_PA0_MINUTE_10] = (diff % 60) / 10;
t /= 60;
t += diff / 60;
diff = timerRegs[GBTAMA6_RTC_PA0_HOUR_1] + timerRegs[GBTAMA6_RTC_PA0_HOUR_10] * 10 + t % 24;
if (diff < 0) {
diff += 24;
t -= 24;
}
timerRegs[GBTAMA6_RTC_PA0_HOUR_1] = (diff % 24) % 10;
timerRegs[GBTAMA6_RTC_PA0_HOUR_10] = (diff % 24) / 10;
t /= 24;
t += diff / 24;
int day = timerRegs[GBTAMA6_RTC_PA0_DAY_1] + timerRegs[GBTAMA6_RTC_PA0_DAY_10] * 10;
int month = timerRegs[GBTAMA6_RTC_PA0_MONTH_1] + timerRegs[GBTAMA6_RTC_PA0_MONTH_10] * 10;
int year = timerRegs[GBTAMA6_RTC_PA0_YEAR_1] + timerRegs[GBTAMA6_RTC_PA0_YEAR_10] * 10;
int dayInYear = _tama6DMYToDayOfYear(day, month, year);
diff = dayInYear + t;
while (diff <= 0) {
// Previous year
if (year % 4) {
diff += 365;
} else {
diff += 366;
}
--year;
}
while (diff > (year % 4 ? 365 : 366)) {
// Future year
if (year % 4) {
diff -= 365;
} else {
diff -= 366;
}
++year;
}
year %= 100;
day = _tama6DayOfYearToDayOfMonth(diff, year);
month = _tama6DayOfYearToMonth(diff, year);
timerRegs[GBTAMA6_RTC_PA0_DAY_1] = day % 10;
timerRegs[GBTAMA6_RTC_PA0_DAY_10] = day / 10;
timerRegs[GBTAMA6_RTC_PA0_MONTH_1] = month % 10;
timerRegs[GBTAMA6_RTC_PA0_MONTH_10] = month / 10;
timerRegs[GBTAMA6_RTC_PA0_YEAR_1] = year % 10;
timerRegs[GBTAMA6_RTC_PA0_YEAR_10] = year / 10;
}
void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) {
struct GBMemory* memory = &gb->memory;
struct GBTAMA5State* tama5 = &memory->mbcState.tama5;
@ -1524,8 +1668,9 @@ void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) {
} else {
value &= 0xF;
if (tama5->reg < GBTAMA5_MAX) {
mLOG(GB_MBC, DEBUG, "TAMA5 write: %02X:%X", tama5->reg, value);
tama5->registers[tama5->reg] = value;
uint8_t address = ((tama5->registers[GBTAMA5_CS] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO];
uint8_t address = ((tama5->registers[GBTAMA5_ADDR_HI] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO];
uint8_t out = (tama5->registers[GBTAMA5_WRITE_HI] << 4) | tama5->registers[GBTAMA5_WRITE_LO];
switch (tama5->reg) {
case GBTAMA5_BANK_LO:
@ -1534,18 +1679,36 @@ void _GBTAMA5(struct GB* gb, uint16_t address, uint8_t value) {
break;
case GBTAMA5_WRITE_LO:
case GBTAMA5_WRITE_HI:
case GBTAMA5_CS:
case GBTAMA5_ADDR_HI:
break;
case GBTAMA5_ADDR_LO:
switch (tama5->registers[GBTAMA5_CS] >> 1) {
switch (tama5->registers[GBTAMA5_ADDR_HI] >> 1) {
case 0x0: // RAM write
memory->sram[address] = out;
gb->sramDirty |= mSAVEDATA_DIRT_NEW;
break;
case 0x1: // RAM read
break;
case 0x2: // Other commands
switch (address) {
case GBTAMA6_MINUTE_WRITE:
tama5->rtcTimerPage[GBTAMA6_RTC_PA0_MINUTE_1] = out & 0xF;
tama5->rtcTimerPage[GBTAMA6_RTC_PA0_MINUTE_10] = out >> 4;
break;
case GBTAMA6_HOUR_WRITE:
tama5->rtcTimerPage[GBTAMA6_RTC_PA0_HOUR_1] = out & 0xF;
tama5->rtcTimerPage[GBTAMA6_RTC_PA0_HOUR_10] = out >> 4;
break;
}
break;
case 0x4: // RTC access
if (!(address & 1)) {
tama5->rtcTimerPage[out & 0xF] = out >> 4;
}
break;
default:
mLOG(GB_MBC, STUB, "TAMA5 unknown address: %X-%02X:%02X", tama5->registers[GBTAMA5_CS] >> 1, address, out);
mLOG(GB_MBC, STUB, "TAMA5 unknown address: %02X:%02X", address, out);
break;
}
break;
default:
@ -1571,18 +1734,41 @@ uint8_t _GBTAMA5Read(struct GBMemory* memory, uint16_t address) {
return 0xFF;
} else {
uint8_t value = 0xF0;
uint8_t address = ((tama5->registers[GBTAMA5_CS] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO];
uint8_t address = ((tama5->registers[GBTAMA5_ADDR_HI] << 4) & 0x10) | tama5->registers[GBTAMA5_ADDR_LO];
switch (tama5->reg) {
case GBTAMA5_ACTIVE:
return 0xF1;
case GBTAMA5_READ_LO:
case GBTAMA5_READ_HI:
switch (tama5->registers[GBTAMA5_CS] >> 1) {
case 1:
switch (tama5->registers[GBTAMA5_ADDR_HI] >> 1) {
case 0x1:
value = memory->sram[address];
break;
case 0x2:
mLOG(GB_MBC, STUB, "TAMA5 unknown read %s: %02X", tama5->reg == GBTAMA5_READ_HI ? "hi" : "lo", address);
_latchTAMA6Rtc(memory->rtc, tama5->rtcTimerPage, &memory->rtcLastLatch);
switch (address) {
case GBTAMA6_MINUTE_READ:
value = (tama5->rtcTimerPage[GBTAMA6_RTC_PA0_MINUTE_10] << 4) | tama5->rtcTimerPage[GBTAMA6_RTC_PA0_MINUTE_1];
break;
case GBTAMA6_HOUR_READ:
value = (tama5->rtcTimerPage[GBTAMA6_RTC_PA0_HOUR_10] << 4) | tama5->rtcTimerPage[GBTAMA6_RTC_PA0_HOUR_1];
break;
default:
value = address;
break;
}
break;
case 0x4:
if (tama5->reg == GBTAMA5_READ_HI) {
mLOG(GB_MBC, GAME_ERROR, "TAMA5 reading RTC incorrectly");
break;
}
_latchTAMA6Rtc(memory->rtc, tama5->rtcTimerPage, &memory->rtcLastLatch);
value = tama5->rtcTimerPage[tama5->registers[GBTAMA5_WRITE_LO]];
break;
default:
mLOG(GB_MBC, STUB, "TAMA5 unknown read: %02X", tama5->reg);
mLOG(GB_MBC, STUB, "TAMA5 unknown read %s: %02X", tama5->reg == GBTAMA5_READ_HI ? "hi" : "lo", address);
break;
}
if (tama5->reg == GBTAMA5_READ_HI) {
@ -2015,3 +2201,42 @@ void GBMBCHuC3Write(struct GB* gb) {
_appendSaveSuffix(gb, &buffer, sizeof(buffer));
}
void GBMBCTAMA5Read(struct GB* gb) {
struct GBMBCTAMA5SaveBuffer buffer;
struct VFile* vf = gb->sramVf;
if (!vf) {
return;
}
vf->seek(vf, gb->sramSize, SEEK_SET);
if (vf->read(vf, &buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer)) {
return;
}
size_t i;
for (i = 0; i < 0x8; ++i) {
gb->memory.mbcState.tama5.rtcTimerPage[i * 2] = buffer.rtcTimerPage[i] & 0xF;
gb->memory.mbcState.tama5.rtcTimerPage[i * 2 + 1] = buffer.rtcTimerPage[i] >> 4;
}
LOAD_64LE(gb->memory.rtcLastLatch, 0, &buffer.latchedUnix);
}
void GBMBCTAMA5Write(struct GB* gb) {
struct VFile* vf = gb->sramVf;
if (!vf) {
return;
}
struct GBMBCTAMA5SaveBuffer buffer;
size_t i;
for (i = 0; i < 8; ++i) {
buffer.rtcTimerPage[i] = gb->memory.mbcState.tama5.rtcTimerPage[i * 2] & 0xF;
buffer.rtcTimerPage[i] |= gb->memory.mbcState.tama5.rtcTimerPage[i * 2 + 1] << 4;
buffer.rtcAlarmPage[i] = 0;
buffer.rtcFreePage0[i] = 0;
buffer.rtcFreePage1[i] = 0;
}
STORE_64LE(gb->memory.rtcLastLatch, 0, &buffer.latchedUnix);
_appendSaveSuffix(gb, &buffer, sizeof(buffer));
}

View File

@ -765,6 +765,16 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) {
STORE_16LE(memory->mbcState.mbc7.sr, 0, &state->memory.mbc7.sr);
STORE_32LE(memory->mbcState.mbc7.writable, 0, &state->memory.mbc7.writable);
break;
case GB_TAMA5:
STORE_64LE(memory->rtcLastLatch, 0, &state->memory.tama5.lastLatch);
state->memory.tama5.reg = memory->mbcState.tama5.reg;
for (i = 0; i < 8; ++i) {
state->tama5Registers.registers[i] = memory->mbcState.tama5.registers[i * 2] & 0xF;
state->tama5Registers.registers[i] |= memory->mbcState.tama5.registers[i * 2 + 1] << 4;
state->tama5Registers.rtcTimerPage[i] = memory->mbcState.tama5.rtcTimerPage[i * 2] & 0xF;
state->tama5Registers.rtcTimerPage[i] |= memory->mbcState.tama5.rtcTimerPage[i * 2 + 1] << 4;
}
break;
case GB_HuC3:
STORE_64LE(memory->rtcLastLatch, 0, &state->memory.huc3.lastLatch);
state->memory.huc3.index = memory->mbcState.huc3.index;
@ -874,6 +884,16 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) {
LOAD_16LE(memory->mbcState.mbc7.sr, 0, &state->memory.mbc7.sr);
LOAD_32LE(memory->mbcState.mbc7.writable, 0, &state->memory.mbc7.writable);
break;
case GB_TAMA5:
LOAD_64LE(memory->rtcLastLatch, 0, &state->memory.tama5.lastLatch);
memory->mbcState.tama5.reg = state->memory.tama5.reg;
for (i = 0; i < 8; ++i) {
memory->mbcState.tama5.registers[i * 2] = state->tama5Registers.registers[i] & 0xF;
memory->mbcState.tama5.registers[i * 2 + 1] = state->tama5Registers.registers[i] >> 4;
memory->mbcState.tama5.rtcTimerPage[i * 2] = state->tama5Registers.rtcTimerPage[i] & 0xF;
memory->mbcState.tama5.rtcTimerPage[i * 2 + 1] = state->tama5Registers.rtcTimerPage[i] >> 4;
}
break;
case GB_HuC3:
LOAD_64LE(memory->rtcLastLatch, 0, &state->memory.huc3.lastLatch);
memory->mbcState.huc3.index = state->memory.huc3.index;