BizHawk/libmeteor/source/io.cpp

509 lines
12 KiB
C++

// Meteor - A Nintendo Gameboy Advance emulator
// Copyright (C) 2009-2011 Philippe Daouadi
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "ameteor/io.hpp"
#include "ameteor/dma.hpp"
#include "globals.hpp"
#include "ameteor.hpp"
#include <cstring>
#include "debug.hpp"
#define W8(add, val) \
m_iomem[(add) & 0xFFF] = (val)
#define W16(add, val) \
*(uint16_t*)(m_iomem + ((add) & 0xFFF)) = (val)
#define W32(add, val) \
*(uint32_t*)(m_iomem + ((add) & 0xFFF)) = (val)
namespace AMeteor
{
Io::Io ()
{
m_iomem = new uint8_t[IO_SIZE];
Reset ();
}
Io::~Io ()
{
delete [] m_iomem;
}
void Io::Reset ()
{
std::memset(m_iomem, 0, IO_SIZE);
// TODO use clears intead
// TODO DISPCNT should be 0x80 by default
// TODO do it also for clears
// TODO lcd should draw white lines when hblank forced
// TODO when passing disabling hblank forced, lcd should start drawing from
// first line
W16(SOUNDBIAS, 0x0200); // default value
W16(KEYINPUT, 0x03FF); // all keys released
W8(HALTCNT, 0xFF); // normal mode (internal)
W16(DISPSTAT, 0x0004); // vcount match (since default vcount is 0)
W16(BG2PA, 0x0100);
W16(BG2PD, 0x0100);
W16(BG3PA, 0x0100);
W16(BG3PD, 0x0100);
W16(RCNT, 0x8000); // we start in general purpose mode
}
void Io::ClearSio ()
{
// TODO
W16(RCNT, 0x8000);
}
void Io::ClearSound ()
{
// TODO
}
void Io::ClearOthers ()
{
// FIXME !! shouldn't we call Write*() ?
// lcd
for (uint8_t i = 0x0; i < 0x56; i += 2)
Write16(i, 0x0000);
// dma
for (uint8_t i = 0xB0; i < 0xE0; i += 4)
Write32(i, 0x0000);
// FIXME : should timers be set to 0 too ? (vba does not)
W8(HALTCNT, 0xFF); // normal mode (internal)
W16(IE, 0x0000);
W16(IF, 0x0000);
W16(IME, 0x0000);
Write16(WAITCNT, 0x0000);
W16(BG2PA, 0x0100);
W16(BG2PD, 0x0100);
W16(BG3PA, 0x0100);
W16(BG3PD, 0x0100);
}
// TODO implement unreadable or write-only io
uint8_t Io::Read8 (uint32_t add)
{
if ((add & 0xFFE) == KEYINPUT)
keyupdate_bizhawk();
//debug ("IO Read8 at " << IOS_ADD << add << " of " << IOS_ADD << (int)*(uint8_t*)(m_iomem + (add & 0xFFF)));
if ((add & 0xFF0) == 0x100)
switch (add & 0xF)
{
case 0x0:
case 0x4:
case 0x8:
case 0xC:
met_abort("Misaligned reading of timers");
}
return m_iomem[add & 0xFFF];
}
uint16_t Io::Read16 (uint32_t add)
{
if ((add & 0xFFE) == KEYINPUT)
keyupdate_bizhawk();
//debug ("IO Read16 at " << IOS_ADD << add << " of " << IOS_ADD << *(uint16_t*)(m_iomem + (add & 0xFFF)));
// special case, reading timers
if ((add & 0xFF0) == 0x100)
switch (add & 0xF)
{
case 0x0: return TIMER0.GetCount();
case 0x4: return TIMER1.GetCount();
case 0x8: return TIMER2.GetCount();
case 0xC: return TIMER3.GetCount();
}
return *(uint16_t*)(m_iomem + (add & 0xFFF));
}
uint32_t Io::Read32 (uint32_t add)
{
if ((add & 0xFFC) == KEYINPUT)
keyupdate_bizhawk();
//debug ("IO Read32 at " << IOS_ADD << add << " of " << IOS_ADD << *(uint32_t*)(m_iomem + (add & 0xFFF)));
// special case, reading timers
if ((add & 0xFF0) == 0x100)
switch (add & 0xF)
{
case 0x0: return TIMER0.GetCount();
case 0x4: return TIMER1.GetCount();
case 0x8: return TIMER2.GetCount();
case 0xC: return TIMER3.GetCount();
}
return *(uint32_t*)(m_iomem + (add & 0xFFF));
}
void Io::Write8 (uint32_t add, uint8_t val)
{
//debug ("IO Write8 at " << IOS_ADD << add << " of " << IOS_ADD << (int)val);
switch (add & 0xFFF)
{
case NR10+1:
case NR52+1:
case NR52+2:
case NR52+3:
break;
case NR10:
case NR11:
case NR13:
case NR21:
case NR23:
case NR41:
case NR43:
case NR50:
case NR51:
case SOUNDCNT_H:
W8(add, val);
break;
case NR12:
W8(add, val);
if (!(val & (0xF << 4)))
SOUND.ResetSound1Envelope();
break;
case NR14:
W8(add, val & 0xC7);
if (val & (0x1 << 7))
SOUND.ResetSound1();
break;
case NR22:
W8(add, val);
if (!(val & (0xF << 4)))
SOUND.ResetSound2Envelope();
break;
case NR24:
W8(add, val & 0xC7);
if (val & (0x1 << 7))
SOUND.ResetSound2();
break;
case NR42:
W8(add, val);
if (!(val & (0xF << 4)))
SOUND.ResetSound4Envelope();
break;
case NR44:
W8(add, val & 0xC7);
if (val & (0x1 << 7))
SOUND.ResetSound4();
break;
case SOUNDCNT_H+1:
W8(add, val);
SOUND.UpdateCntH1(val);
break;
case NR52:
// this will also reset the sound on flags
W8(add, val & 0x80);
break;
case POSTFLG:
// FIXME is this right, i have no idea about why i should do that
if (val)
val &= 0xFE;
W8(add, val);
break;
case HALTCNT:
W8(add, val);
break;
default:
//W8(add, val);
// TODO make a function which will apply masks to IO memory and trigger
// the update functions, this function will be called by write8 and
// write16
#if 1
add &= 0xFFF;
if (add % 2)
Write16(add & ~0x1, (val << 8) | m_iomem[add & ~0x1]);
else
Write16(add, (m_iomem[add | 0x1] << 8) | val);
#endif
break;
}
}
void Io::Write16 (uint32_t add, uint16_t val)
{
//debug ("IO Write16 at " << IOS_ADD << add << " of " << IOS_ADD << val);
//*(uint16_t*)(m_iomem + (add & 0xFFF)) = val;
switch (add & 0xFFF)
{
case KEYINPUT:
case VCOUNT:
break;
case DMA0CNT_L:
case DMA1CNT_L:
case DMA2CNT_L:
case DMA3CNT_L:
//W16(add, val);
DMA.SetReload(((add & 0xFFF) - DMA0CNT_L) / DMA_CHANSIZE, val);
break;
case KEYCNT:
W16(add, val & 0xC3FF);
break;
case IME:
W16(add, val & 0x0001);
CPU.CheckInterrupt();
break;
case IE:
W16(add, val & 0x3FFF);
CPU.CheckInterrupt();
break;
case IF:
*((uint16_t*)(m_iomem+IF)) ^= (val & (*((uint16_t*)(m_iomem+IF))));
CPU.CheckInterrupt();
break;
case BG0CNT:
W16(add, val & 0xFFCF);
LCD.UpdateBg0Cnt(val & 0xFFCF);
break;
case BG1CNT:
W16(add, val & 0xFFCF);
LCD.UpdateBg1Cnt(val & 0xFFCF);
break;
case BG2CNT:
W16(add, val & 0xFFCF);
LCD.UpdateBg2Cnt(val & 0xFFCF);
break;
case BG3CNT:
W16(add, val & 0xFFCF);
LCD.UpdateBg3Cnt(val & 0xFFCF);
break;
case DISPSTAT:
// the first 3 bits are read only and they are used by the lcd
// NOTE : we are in LITTLE ENDIAN
// FIXME : if vcount setting has changed to current vcount, we should
// update the vcounter flag and eventually trigger an interrupt
W16(add, (val & 0xFFF8) | (m_iomem[add & 0xFFF] & 0x07));
break;
// The BG*OFS are write-only, we don't need to W16()
// You do if you're ever going to load them from a savestate...
case BG0HOFS:
W16(add, val & 0x1FF);
LCD.UpdateBg0XOff(val & 0x1FF);
break;
case BG0VOFS:
W16(add, val & 0x1FF);
LCD.UpdateBg0YOff(val & 0x1FF);
break;
case BG1HOFS:
W16(add, val & 0x1FF);
LCD.UpdateBg1XOff(val & 0x1FF);
break;
case BG1VOFS:
W16(add, val & 0x1FF);
LCD.UpdateBg1YOff(val & 0x1FF);
break;
case BG2HOFS:
W16(add, val & 0x1FF);
LCD.UpdateBg2XOff(val & 0x1FF);
break;
case BG2VOFS:
W16(add, val & 0x1FF);
LCD.UpdateBg2YOff(val & 0x1FF);
break;
case BG3HOFS:
W16(add, val & 0x1FF);
LCD.UpdateBg3XOff(val & 0x1FF);
break;
case BG3VOFS:
W16(add, val & 0x1FF);
LCD.UpdateBg3YOff(val & 0x1FF);
break;
case BG2X_H:
val &= 0x0FFF;
case BG2X_L:
W16(add, val);
LCD.UpdateBg2RefX(IO.DRead32(Io::BG2X_L));
break;
case BG2Y_H:
val &= 0x0FFF;
case BG2Y_L:
W16(add, val);
LCD.UpdateBg2RefY(IO.DRead32(Io::BG2Y_L));
break;
case BG3X_H:
val &= 0x0FFF;
case BG3X_L:
W16(add, val);
LCD.UpdateBg3RefX(IO.DRead32(Io::BG3X_L));
break;
case BG3Y_H:
val &= 0x0FFF;
case BG3Y_L:
W16(add, val);
LCD.UpdateBg3RefY(IO.DRead32(Io::BG3Y_L));
break;
case WIN0H:
case WIN1H:
case WIN0V:
case WIN1V:
case WININ:
case WINOUT:
W16(add, val);
break;
case BLDCNT:
W16(add, val);
break;
case MOSAIC:
W16(add, val);
break;
case DISPCNT:
W16(add, val);
LCD.UpdateDispCnt(val);
break;
case DMA0CNT_H:
case DMA1CNT_H:
case DMA2CNT_H:
case DMA3CNT_H:
W16(add, val & 0xFFE0);
DMA.UpdateCnt(((add & 0xFFF) - DMA0CNT_H) / DMA_CHANSIZE);
break;
case WAITCNT:
W16(add, val & 0xDFFF);
MEM.UpdateWaitStates (val & 0xDFFF);
break;
case SOUND1CNT_L:
case SOUND1CNT_H:
case SOUND1CNT_X:
case SOUND2CNT_L:
case SOUND2CNT_H:
case SOUND4CNT_L:
case SOUND4CNT_H:
case SOUNDCNT_L:
case SOUNDCNT_H:
case SOUNDCNT_X:
case POSTFLG:
Write8(add, val & 0xFF);
Write8(add + 1, val >> 8);
break;
case TM0CNT_L:
TIMER0.SetReload(val);
break;
case TM1CNT_L:
TIMER1.SetReload(val);
break;
case TM2CNT_L:
TIMER2.SetReload(val);
break;
case TM3CNT_L:
TIMER3.SetReload(val);
break;
case TM0CNT_H:
W16(add, val & 0x00C7);
TIMER0.Reload();
break;
case TM1CNT_H:
W16(add, val & 0x00C7);
TIMER1.Reload();
break;
case TM2CNT_H:
W16(add, val & 0x00C7);
TIMER2.Reload();
break;
case TM3CNT_H:
W16(add, val & 0x00C7);
TIMER3.Reload();
break;
default:
//met_abort("Unknown IO at " << IOS_ADD << add);
W16(add, val);
break;
}
}
void Io::Write32 (uint32_t add, uint32_t val)
{
//debug ("IO Write32 at " << IOS_ADD << add << " of " << IOS_ADD << val);
switch (add & 0xFF)
{
case DMA1DAD:
case DMA0SAD:
case DMA1SAD:
case DMA2SAD:
case DMA3SAD:
case DMA0DAD:
case DMA2DAD:
case DMA3DAD:
W32(add, val);
break;
case BG0HOFS:
case BG1HOFS:
case BG2HOFS:
case BG3HOFS:
Write16(add, val & 0xFFFF);
Write16(add+2, val >> 16);
break;
case BG2X_L:
W32(add, val & 0x0FFFFFFF);
LCD.UpdateBg2RefX(IO.DRead32(Io::BG2X_L));
break;
case BG2Y_L:
W32(add, val & 0x0FFFFFFF);
LCD.UpdateBg2RefY(IO.DRead32(Io::BG2Y_L));
break;
case BG3X_L:
W32(add, val & 0x0FFFFFFF);
LCD.UpdateBg3RefX(IO.DRead32(Io::BG3X_L));
break;
case BG3Y_L:
W32(add, val & 0x0FFFFFFF);
LCD.UpdateBg3RefY(IO.DRead32(Io::BG3Y_L));
break;
case BG2PA:
case BG2PC:
case BG3PA:
case BG3PC:
case WIN0H:
case WIN0V:
case WININ:
Write16(add, val & 0xFFFF);
Write16(add+2, val >> 16);
break;
case DMA0CNT_L:
case DMA1CNT_L:
case DMA2CNT_L:
case DMA3CNT_L:
Write16(add, val & 0xFFFF);
Write16(add+2, val >> 16);
break;
case FIFO_A:
case FIFO_B:
// TODO
break;
default:
//met_abort("Unknown IO at " << IOS_ADD << add);
//*(uint32_t*)(m_iomem + (add & 0xFFF)) = val;
Write16(add, val & 0xFFFF);
Write16(add+2, val >> 16);
break;
}
}
bool Io::SaveState (std::ostream& stream)
{
SS_WRITE_DATA(m_iomem, IO_SIZE);
return true;
}
bool Io::LoadState (std::istream& stream)
{
SS_READ_DATA(m_iomem, IO_SIZE);
return true;
}
}