Path: blob/a-new-beginning/Cherry/Core/include/OCMMapper.h
2 views
/*1* Gearcoleco - ColecoVision Emulator2* Copyright (C) 2021 Ignacio Sanchez34* This program is free software: you can redistribute it and/or modify5* it under the terms of the GNU General Public License as published by6* the Free Software Foundation, either version 3 of the License, or7* any later version.89* This program is distributed in the hope that it will be useful,10* but WITHOUT ANY WARRANTY; without even the implied warranty of11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12* GNU General Public License for more details.1314* You should have received a copy of the GNU General Public License15* along with this program. If not, see http://www.gnu.org/licenses/16*17*/1819#ifndef OCMMAPPER_H20#define OCMMAPPER_H2122#include "Mapper.h"23#include "Cartridge.h"2425#define OCM_CYCLES_PER_FRAME 597402627class OCMMapper : public Mapper28{29public:30OCMMapper(Cartridge* pCartridge, CVMemory* pMemory);31virtual ~OCMMapper();3233virtual void Reset();34virtual u8 Read(u16 address);35virtual void Write(u16 address, u8 value);36virtual void SaveState(std::ostream& stream);37virtual void LoadState(std::istream& stream);38virtual u8 GetBankReg(int index) { return (index >= 0 && index < 4) ? m_BankReg[index] : 0; }3940private:41// EEPROM state machine42enum43{44EEP_NONE = 0,45EEP_INIT = 1,46EEP_STATUS = 2,47EEP_WRITE = 348};4950inline bool EepromReadWindowActive() const51{52if (m_EepromReadExpireCycles == 0)53return false;54u64 now = m_pMemory->GetTotalCycles();55if (now < m_EepromReadExpireCycles)56return true;57return false;58}5960inline void ArmEepromReadWindow(u8 value)61{62if ((value & 0x0F) == 0x0F)63{64u64 now = m_pMemory->GetTotalCycles();65m_EepromReadExpireCycles = now + (OCM_CYCLES_PER_FRAME * 3ULL);66}67else68m_EepromReadExpireCycles = 0;69}7071private:72CVMemory* m_pMemory;73u8 m_BankReg[4];74u8 m_EepromCmdPos;75u8 m_EepromState;76u64 m_EepromReadExpireCycles;77};7879inline OCMMapper::OCMMapper(Cartridge* pCartridge, CVMemory* pMemory)80: Mapper(pCartridge)81{82m_pMemory = pMemory;83Reset();84}8586inline OCMMapper::~OCMMapper()87{88}8990inline void OCMMapper::Reset()91{92m_BankReg[0] = 3;93m_BankReg[1] = 2;94m_BankReg[2] = 1;95m_BankReg[3] = 0;96m_EepromCmdPos = 0;97m_EepromState = EEP_NONE;98m_EepromReadExpireCycles = 0;99}100101inline u8 OCMMapper::Read(u16 address)102{103u8* pRom = m_pCartridge->GetROM();104105if (address < 0xA000)106{107// 8000-9FFF: Bank 3108int bankOffset = (address - 0x8000) + (m_BankReg[3] * 0x2000);109return pRom[bankOffset];110}111else if (address < 0xC000)112{113// A000-BFFF: Bank 0114int bankOffset = (address - 0xA000) + (m_BankReg[0] * 0x2000);115return pRom[bankOffset];116}117else if (address < 0xE000)118{119// C000-DFFF: Bank 1120int bankOffset = (address - 0xC000) + (m_BankReg[1] * 0x2000);121return pRom[bankOffset];122}123else124{125// E000-FFFF: Bank 2 (ROM or EEPROM)126// If STATUS mode, reading E000 returns 0xFF (status OK)127if (((address & 0x0FFF) == 0x0000) && (m_EepromState == EEP_STATUS))128{129return 0xFF;130}131132// EEPROM read window: bank2==0x0F, active timer, E000-E0FF -> EEPROM133if (m_BankReg[2] == 0x0F && EepromReadWindowActive() && ((address & 0x0FFF) < 0x0100))134{135u8* pEEPROM = m_pCartridge->GetEEPROM();136if (IsValidPointer(pEEPROM))137{138return pEEPROM[address & 0x03FF];139}140return 0xFF;141}142143// Otherwise, ROM from bank 2144int bankOffset = (address - 0xE000) + (m_BankReg[2] * 0x2000);145return pRom[bankOffset];146}147}148149inline void OCMMapper::Write(u16 address, u8 value)150{151// OCM EEPROM & banking are only active on E000-FFFF range152if (address < 0xE000)153{154return;155}156157// EEPROM command/byte write on E000..FFFB158if (address <= 0xFFFB)159{160// Handshake: AA -> 55 -> CMD161if (value == 0xAA && m_EepromCmdPos == 0)162{163m_EepromCmdPos = 1;164return;165}166else if (value == 0x55 && m_EepromCmdPos == 1)167{168m_EepromCmdPos = 2;169return;170}171else if (m_EepromCmdPos == 2)172{173// Interpret command byte174if (value == 0x80)175{176m_EepromState = EEP_INIT;177}178else if (value == 0x30)179{180if (m_EepromState == EEP_INIT)181{182m_EepromState = EEP_STATUS;183}184}185else if (value == 0xA0)186{187m_EepromState = EEP_WRITE;188}189else190{191Debug("--> OCM EEP Unknown command: %02X @ %04X", value, address);192}193194m_EepromCmdPos = 0;195return;196}197else if (m_EepromState == EEP_WRITE)198{199// Next write persists a single byte to 1KB EEPROM space200u8* pEEPROM = m_pCartridge->GetEEPROM();201if (IsValidPointer(pEEPROM))202{203pEEPROM[address & 0x03FF] = value;204}205m_EepromState = EEP_NONE;206return;207}208209// Any other writes in this range that don't match the handshake are ignored210return;211}212213// Bank registers and EEPROM read-window trigger214// FFFC -> bank 0, FFFD -> bank 1, FFFE -> bank 2 (also arms read-window), FFFF -> bank 3215if (address >= 0xFFFC)216{217if (address == 0xFFFE)218{219// Arm or clear the 3-frame EEPROM read window220ArmEepromReadWindow(value);221}222223m_BankReg[address & 0x0003] = (u8)(value & 0x0F);224225// Debug("--> OCM Bank[%d] = %d (addr=%04X, val=%02X)", (address & 3), m_BankReg[address & 3], address, value);226return;227}228}229230inline void OCMMapper::SaveState(std::ostream& stream)231{232233stream.write(reinterpret_cast<const char*>(m_BankReg), sizeof(m_BankReg));234stream.write(reinterpret_cast<const char*>(&m_EepromCmdPos), sizeof(m_EepromCmdPos));235stream.write(reinterpret_cast<const char*>(&m_EepromState), sizeof(m_EepromState));236237// Persist “frames remaining” instead of raw cycle deadline238u64 now = m_pMemory->GetTotalCycles();239u8 framesLeft = 0;240if (m_EepromReadExpireCycles > now)241{242u64 delta = m_EepromReadExpireCycles - now;243// Ceil to frames so the window isn't lost by rounding244framesLeft = (u8)((delta + (OCM_CYCLES_PER_FRAME - 1)) / OCM_CYCLES_PER_FRAME);245if (framesLeft > 3)246framesLeft = 3;247}248stream.write(reinterpret_cast<const char*>(&framesLeft), sizeof(framesLeft));249250u8* pEEPROM = m_pCartridge->GetEEPROM();251stream.write(reinterpret_cast<const char*>(pEEPROM), 0x400);252}253254inline void OCMMapper::LoadState(std::istream& stream)255{256stream.read(reinterpret_cast<char*>(m_BankReg), sizeof(m_BankReg));257stream.read(reinterpret_cast<char*>(&m_EepromCmdPos), sizeof(m_EepromCmdPos));258stream.read(reinterpret_cast<char*>(&m_EepromState), sizeof(m_EepromState));259260u8 framesLeft = 0;261stream.read(reinterpret_cast<char*>(&framesLeft), sizeof(framesLeft));262if (framesLeft > 0)263{264u64 now = m_pMemory->GetTotalCycles();265m_EepromReadExpireCycles = now + (u64)framesLeft * OCM_CYCLES_PER_FRAME;266}267else268{269m_EepromReadExpireCycles = 0;270}271272u8* pEEPROM = m_pCartridge->GetEEPROM();273stream.read(reinterpret_cast<char*>(pEEPROM), 0x400);274}275276#endif /* OCMMAPPER_H */277278279