2017-10-05 10:24:36 +00:00
|
|
|
#include "stdafx.h"
|
|
|
|
#include "IoRegisters.h"
|
|
|
|
#include "GameBoyBus.h"
|
|
|
|
|
|
|
|
EightBit::GameBoy::IoRegisters::IoRegisters(Bus& bus)
|
|
|
|
: Ram(0x80),
|
2017-11-11 11:12:09 +00:00
|
|
|
m_bus(bus) {
|
2017-10-05 10:24:36 +00:00
|
|
|
m_bus.ReadingByte.connect(std::bind(&IoRegisters::Bus_ReadingByte, this, std::placeholders::_1));
|
|
|
|
m_bus.WrittenByte.connect(std::bind(&IoRegisters::Bus_WrittenByte, this, std::placeholders::_1));
|
|
|
|
}
|
|
|
|
|
|
|
|
void EightBit::GameBoy::IoRegisters::reset() {
|
|
|
|
poke(NR52, 0xf1);
|
|
|
|
poke(LCDC, DisplayBackground | BackgroundCharacterDataSelection | LcdEnable);
|
2018-08-11 20:19:19 +00:00
|
|
|
m_divCounter = 0xabcc;
|
2017-10-05 10:24:36 +00:00
|
|
|
m_timerCounter = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EightBit::GameBoy::IoRegisters::transferDma() {
|
|
|
|
if (m_dmaTransferActive) {
|
2018-08-29 12:52:25 +00:00
|
|
|
m_bus.OAMRAM().poke(m_dmaAddress.low, m_bus.peek(m_dmaAddress));
|
2017-10-05 10:24:36 +00:00
|
|
|
m_dmaTransferActive = ++m_dmaAddress.low < 0xa0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-10 21:00:52 +00:00
|
|
|
void EightBit::GameBoy::IoRegisters::Bus_ReadingByte(EightBit::EventArgs) {
|
|
|
|
const auto address = m_bus.ADDRESS().word;
|
2017-10-05 10:24:36 +00:00
|
|
|
const auto io = (address >= BASE) && (address < 0xff80);
|
|
|
|
if (io) {
|
|
|
|
auto port = address - BASE;
|
|
|
|
switch (port) {
|
|
|
|
|
|
|
|
// Port/Mode Registers
|
|
|
|
case P1: {
|
|
|
|
auto p14 = m_scanP14 && !m_p14;
|
|
|
|
auto p15 = m_scanP15 && !m_p15;
|
|
|
|
auto live = p14 || p15;
|
|
|
|
auto p10 = live && !m_p10;
|
|
|
|
auto p11 = live && !m_p11;
|
|
|
|
auto p12 = live && !m_p12;
|
|
|
|
auto p13 = live && !m_p13;
|
|
|
|
poke(port,
|
|
|
|
((int)!p10)
|
|
|
|
| ((int)!p11 << 1)
|
|
|
|
| ((int)!p12 << 2)
|
|
|
|
| ((int)!p13 << 3)
|
2018-09-29 13:08:44 +00:00
|
|
|
| Chip::Bit4 | Chip::Bit5
|
|
|
|
| Chip::Bit6 | Chip::Bit7);
|
2017-10-05 10:24:36 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SB:
|
|
|
|
break;
|
|
|
|
case SC:
|
2018-09-29 13:08:44 +00:00
|
|
|
mask(port, Chip::Bit7 | Chip::Bit0);
|
2017-10-05 10:24:36 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
// Timer control
|
|
|
|
case DIV:
|
|
|
|
case TIMA:
|
|
|
|
case TMA:
|
|
|
|
break;
|
|
|
|
case TAC:
|
2018-09-29 13:08:44 +00:00
|
|
|
mask(port, Chip::Mask3);
|
2017-10-05 10:24:36 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
// Interrupt Flags
|
|
|
|
case IF:
|
2018-09-29 13:08:44 +00:00
|
|
|
mask(port, Chip::Mask5);
|
2017-10-05 10:24:36 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
// LCD Display Registers
|
|
|
|
case LCDC:
|
|
|
|
break;
|
|
|
|
case STAT:
|
2018-09-29 13:08:44 +00:00
|
|
|
mask(port, Chip::Mask7);
|
2017-10-05 10:24:36 +00:00
|
|
|
break;
|
|
|
|
case SCY:
|
|
|
|
case SCX:
|
|
|
|
case LY:
|
|
|
|
case LYC:
|
|
|
|
case DMA:
|
|
|
|
case BGP:
|
|
|
|
case OBP0:
|
|
|
|
case OBP1:
|
|
|
|
case WY:
|
|
|
|
case WX:
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
mask(port, 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-10 21:00:52 +00:00
|
|
|
void EightBit::GameBoy::IoRegisters::Bus_WrittenByte(EightBit::EventArgs) {
|
2017-10-05 10:24:36 +00:00
|
|
|
|
2018-06-10 21:00:52 +00:00
|
|
|
const auto address = m_bus.ADDRESS().word;
|
2017-10-05 10:24:36 +00:00
|
|
|
const auto value = m_bus.DATA();
|
|
|
|
const auto port = address - BASE;
|
|
|
|
|
|
|
|
switch (port) {
|
|
|
|
|
|
|
|
case P1:
|
2018-09-29 13:08:44 +00:00
|
|
|
m_scanP14 = (value & Chip::Bit4) == 0;
|
|
|
|
m_scanP15 = (value & Chip::Bit5) == 0;
|
2017-10-05 10:24:36 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SB: // R/W
|
|
|
|
case SC: // R/W
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DIV: // R/W
|
|
|
|
poke(port, 0);
|
|
|
|
m_timerCounter = m_divCounter.word = 0;
|
|
|
|
break;
|
|
|
|
case TIMA: // R/W
|
|
|
|
case TMA: // R/W
|
|
|
|
break;
|
|
|
|
case TAC: // R/W
|
|
|
|
m_timerRate = timerClockTicks();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IF: // R/W
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LCDC:
|
|
|
|
case STAT:
|
|
|
|
case SCY:
|
|
|
|
case SCX:
|
|
|
|
break;
|
|
|
|
case DMA:
|
2018-10-27 18:23:02 +00:00
|
|
|
m_dmaAddress = { 0, value };
|
2017-10-05 10:24:36 +00:00
|
|
|
m_dmaTransferActive = true;
|
|
|
|
break;
|
|
|
|
case LY: // R/O
|
|
|
|
poke(port, 0);
|
|
|
|
break;
|
|
|
|
case BGP:
|
|
|
|
case OBP0:
|
|
|
|
case OBP1:
|
|
|
|
case WY:
|
|
|
|
case WX:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case BOOT_DISABLE:
|
2018-08-17 12:59:59 +00:00
|
|
|
m_disableBootRom = !!value;
|
2017-10-05 10:24:36 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void EightBit::GameBoy::IoRegisters::checkTimers(int cycles) {
|
|
|
|
incrementDIV(cycles);
|
|
|
|
checkTimer(cycles);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EightBit::GameBoy::IoRegisters::checkTimer(int cycles) {
|
|
|
|
if (timerEnabled()) {
|
|
|
|
m_timerCounter -= cycles;
|
|
|
|
if (m_timerCounter <= 0) {
|
|
|
|
m_timerCounter += m_timerRate;
|
|
|
|
incrementTIMA();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int EightBit::GameBoy::IoRegisters::timerClockTicks() {
|
|
|
|
switch (timerClock()) {
|
|
|
|
case 0b00:
|
|
|
|
return 1024; // 4.096 Khz
|
|
|
|
case 0b01:
|
|
|
|
return 16; // 262.144 Khz
|
|
|
|
case 0b10:
|
|
|
|
return 64; // 65.536 Khz
|
|
|
|
case 0b11:
|
|
|
|
return 256; // 16.384 Khz
|
|
|
|
default:
|
|
|
|
UNREACHABLE;
|
|
|
|
}
|
|
|
|
throw std::domain_error("Invalid timer clock specification");
|
|
|
|
}
|
|
|
|
|
|
|
|
int EightBit::GameBoy::IoRegisters::timerClock() {
|
2018-09-29 13:08:44 +00:00
|
|
|
return peek(TAC) & Chip::Mask2;
|
2017-10-05 10:24:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool EightBit::GameBoy::IoRegisters::timerEnabled() {
|
|
|
|
return !timerDisabled();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EightBit::GameBoy::IoRegisters::timerDisabled() {
|
2018-09-29 13:08:44 +00:00
|
|
|
return (peek(TAC) & Chip::Bit2) == 0;
|
2017-10-05 10:24:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void EightBit::GameBoy::IoRegisters::incrementDIV(int cycles) {
|
2018-08-11 20:19:19 +00:00
|
|
|
m_divCounter += cycles;
|
2017-10-05 10:24:36 +00:00
|
|
|
poke(DIV, m_divCounter.high);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EightBit::GameBoy::IoRegisters::incrementTIMA() {
|
|
|
|
uint16_t updated = peek(TIMA) + 1;
|
2018-09-29 13:08:44 +00:00
|
|
|
if (updated & Chip::Bit8) {
|
2017-10-05 10:24:36 +00:00
|
|
|
triggerInterrupt(TimerOverflow);
|
|
|
|
updated = peek(TMA);
|
|
|
|
}
|
2018-09-29 13:08:44 +00:00
|
|
|
poke(TIMA, updated & Chip::Mask8);
|
2017-10-05 10:24:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void EightBit::GameBoy::IoRegisters::incrementLY() {
|
|
|
|
poke(LY, (peek(LY) + 1) % GameBoy::Bus::TotalLineCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EightBit::GameBoy::IoRegisters::resetLY() {
|
|
|
|
poke(LY, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void EightBit::GameBoy::IoRegisters::updateLcdStatusMode(int mode) {
|
2018-09-29 13:08:44 +00:00
|
|
|
const auto current = peek(STAT) & ~Chip::Mask2;
|
2017-10-05 10:24:36 +00:00
|
|
|
poke(STAT, current | mode);
|
|
|
|
DisplayStatusModeUpdated.fire(mode);
|
|
|
|
}
|