diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 29fd33122..eab83ef2e 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -931,6 +931,7 @@ + @@ -1093,6 +1094,7 @@ + @@ -1336,4 +1338,4 @@ - \ No newline at end of file + diff --git a/Core/GBA/Cart/GbaRtc.cpp b/Core/GBA/Cart/GbaRtc.cpp index 1b2d19ca0..8a87c47ea 100644 --- a/Core/GBA/Cart/GbaRtc.cpp +++ b/Core/GBA/Cart/GbaRtc.cpp @@ -1,292 +1,11 @@ +#include "Shared/Utilities/S3511ARtc.h" #include "pch.h" #include "GBA/Cart/GbaRtc.h" #include "Utilities/Serializer.h" -#include "Utilities/TimeUtilities.h" #include "Shared/Emulator.h" -#include "Shared/BatteryManager.h" -GbaRtc::GbaRtc(Emulator* emu) +GbaRtc::GbaRtc(Emulator* emu) : S3511ARtc(emu) { - _emu = emu; - - _state.Month = 1; - _state.Day = 1; - _state.Status = 0x02; - _state.IntHour = 0x80; - - _lastUpdateTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); -} - -uint8_t GbaRtc::SanitizeData(uint8_t value, uint8_t maxValue, uint8_t fixedValue) -{ - if(value > maxValue || (value & 0x0F) >= 0x0A || (value & 0xF0) >= 0xA0) { - //Invalid values - return fixedValue; - } - - return value; -} - -void GbaRtc::FromDateTime(uint64_t data, bool includeYmd) -{ - UpdateTime(); - - //TODOGBA process invalid days (e.g feb 30) - if(includeYmd) { - _state.Year = SanitizeData(data & 0xFF, 0x99, 0); - _state.Month = SanitizeData((data >> 8) & 0x1F, 0x12, 1); - _state.Day = SanitizeData((data >> 16) & 0x3F, 0x31, 1); - _state.DoW = SanitizeData((data >> 24) & 0x07, 0x06, 0); - } - _state.Minute = SanitizeData((data >> 40) & 0x7F, 0x59, 0); - _state.Second = SanitizeData((data >> 48) & 0x7F, 0x59, 0); - - bool is24hMode = _state.Status & 0x40; - uint8_t hourData = (data >> 32) & 0xBF; - if(is24hMode) { - _state.Hour = SanitizeData(hourData & 0x3F, 0x23, 0); - if(_state.Hour >= 0x12) { - _state.Hour |= 0x80; //Set PM flag - } - } else { - _state.Hour = SanitizeData(hourData & 0x3F, 0x11, 0) | (hourData & 0x80); - } -} - -uint64_t GbaRtc::ToDateTime() -{ - UpdateTime(); - return _state.Year | (_state.Month << 8) | (_state.Day << 16) | (_state.DoW << 24) | ((uint64_t)_state.Hour << 32) | ((uint64_t)_state.Minute << 40) | ((uint64_t)_state.Second << 48); -} - -uint8_t GbaRtc::GetCommandLength(Command cmd) -{ - switch(cmd) { - default: - case Command::Reset: return 0; - case Command::Status: return 8; - case Command::DateTime: return 8 * 7; - case Command::Time: return 8 * 3; - case Command::Alarm1: return 8 * 2; - case Command::Alarm2: return 8; - case Command::TestStart: return 0; - case Command::TestEnd: return 0; - } -} - -void GbaRtc::ProcessDataIn(uint8_t bit) -{ - Command cmd = (Command)((_command & 0x0F) >> 1); - - uint8_t length = GetCommandLength(cmd); - _dataIn |= ((uint64_t)bit << (length - _dataInSize)); - _dataInSize--; - - if(_dataInSize == 0) { - switch(cmd) { - case Command::Status: - _state.Status &= 0x80; - _state.Status |= (_dataIn & 0x6A); - break; - - case Command::DateTime: FromDateTime(_dataIn, true); break; - case Command::Time: FromDateTime(_dataIn << 32, false); break; - - default: - //TODOGBA not implemented - break; - } - - for(int i = 0; i < 8; i++) { - //Re-write over the last byte if written beyond the expected length - _dataInSize++; - _dataIn &= ~((uint64_t)1 << (length - _dataInSize)); - } - } -} - -void GbaRtc::ProcessDataOut() -{ - _bitOut = _dataOut & 0x01; - _dataOut >>= 1; - _dataOutSize--; - if(_dataOutSize == 0) { - //Repeat command output if read beyond expected length - ProcessCommand(); - } -} - -void GbaRtc::ProcessCommand() -{ - Command cmd = (Command)((_command & 0x0F) >> 1); - uint8_t length = GetCommandLength(cmd); - bool read = _command & 0x01; - if(read) { - _dataOutSize = length; - - if(_emu->IsDebugging()) { - _emu->DebugLog("[RTC] Read command: " + string(magic_enum::enum_name(cmd))); - } - - switch(cmd) { - case Command::Reset: Reset(); break; - case Command::Status: _dataOut = _state.Status; break; - case Command::DateTime: _dataOut = ToDateTime(); break; - case Command::Time: _dataOut = ToDateTime() >> 32; break; - default: - //TODOGBA not implemented - _dataOut = 0; - break; - } - } else { - if(_emu->IsDebugging()) { - _emu->DebugLog("[RTC] Write command: " + string(magic_enum::enum_name(cmd))); - } - - if(cmd == Command::Reset) { - Reset(); - } - - _dataInSize = length; - _dataIn = 0; - } -} - -void GbaRtc::Reset() -{ - _state = {}; - _state.Month = 1; - _state.Day = 1; -} - -void GbaRtc::UpdateTime() -{ - uint64_t currentTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - uint32_t elapsedSeconds = (uint32_t)(currentTime - _lastUpdateTime); - if(elapsedSeconds <= 0) { - return; - } - - std::tm tm = {}; - tm.tm_sec = (_state.Second & 0x0F) + ((_state.Second >> 4) * 10); - tm.tm_min = (_state.Minute & 0x0F) + ((_state.Minute >> 4) * 10); - int hour = _state.Hour & 0x3F; - tm.tm_hour = (hour & 0x0F) + ((hour >> 4) * 10); - if(tm.tm_hour < 12 && (_state.Hour & 0x80)) { - tm.tm_hour += 12; - } - tm.tm_mday = (_state.Day & 0x0F) + ((_state.Day >> 4) * 10); - - int month = _state.Month - 1; - tm.tm_mon = (month & 0x0F) + ((month >> 4) * 10); - tm.tm_year = 100 + (_state.Year & 0x0F) + ((_state.Year >> 4) * 10); - - std::time_t tt = TimeUtilities::TmToUtc(&tm); - if(tt == -1) { - //Invalid time - _lastUpdateTime = currentTime; - return; - } - - int8_t dowGap = 0; - if(tm.tm_wday != _state.DoW) { - //The DoW on the RTC can be set to any arbitrary value for a specific date - //Check the gap between the value set by the game & the real dow for that date - dowGap = (int8_t)tm.tm_wday - (int8_t)_state.DoW; - } - - std::chrono::system_clock::time_point timePoint = std::chrono::system_clock::from_time_t(tt); - timePoint += std::chrono::seconds((uint32_t)elapsedSeconds); - - std::time_t newTime = system_clock::to_time_t(timePoint); - std::tm newTm; -#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) - gmtime_s(&newTm, &newTime); -#else - gmtime_r(&newTime, &newTm); -#endif - - _state.Second = (newTm.tm_sec % 10) + ((newTm.tm_sec / 10) << 4); - _state.Minute = (newTm.tm_min % 10) + ((newTm.tm_min / 10) << 4); - - bool is24hMode = _state.Status & 0x40; - if(is24hMode) { - _state.Hour = (newTm.tm_hour % 10) + ((newTm.tm_hour / 10) << 4); - } else { - hour = newTm.tm_hour >= 12 ? newTm.tm_hour - 12 : newTm.tm_hour; - _state.Hour = (hour % 10) + ((hour / 10) << 4); - } - - if(newTm.tm_hour >= 12) { - //Set PM flag (in both 12h and 24h modes) - _state.Hour |= 0x80; - } - - _state.Day = (newTm.tm_mday % 10) + ((newTm.tm_mday / 10) << 4); - - month = newTm.tm_mon + 1; - _state.Month = (month % 10) + ((month / 10) << 4); - - int year = newTm.tm_year - 100; - _state.Year = (year % 10) + ((year / 10) << 4); - - int dow = newTm.tm_wday - dowGap; - _state.DoW = dow < 0 ? (dow + 7) : (dow % 7); - - _lastUpdateTime = currentTime; -} - -void GbaRtc::LoadBattery() -{ - vector rtcData = _emu->GetBatteryManager()->LoadBattery(".rtc"); - - if(rtcData.size() == sizeof(_state) + sizeof(uint64_t)) { - _state.Year = rtcData[0]; - _state.Month = rtcData[1]; - _state.Day = rtcData[2]; - _state.DoW = rtcData[3]; - _state.Hour = rtcData[4]; - _state.Minute = rtcData[5]; - _state.Second = rtcData[6]; - _state.Status = rtcData[7]; - _state.IntHour = rtcData[8]; - _state.IntMinute = rtcData[9]; - - uint64_t time = 0; - for(uint32_t i = 0; i < sizeof(uint64_t); i++) { - time <<= 8; - time |= rtcData[sizeof(_state) + i]; - } - _lastUpdateTime = time; - } else { - _lastUpdateTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - } -} - -void GbaRtc::SaveBattery() -{ - vector rtcData; - - rtcData.push_back(_state.Year); - rtcData.push_back(_state.Month); - rtcData.push_back(_state.Day); - rtcData.push_back(_state.DoW); - rtcData.push_back(_state.Hour); - rtcData.push_back(_state.Minute); - rtcData.push_back(_state.Second); - rtcData.push_back(_state.Status); - rtcData.push_back(_state.IntHour); - rtcData.push_back(_state.IntMinute); - - rtcData.resize(sizeof(_state) + sizeof(uint64_t), 0); - - uint64_t time = _lastUpdateTime; - for(uint32_t i = 0; i < sizeof(uint64_t); i++) { - rtcData[sizeof(_state) + i] = (time >> 56) & 0xFF; - time <<= 8; - } - - _emu->GetBatteryManager()->SaveBattery(".rtc", rtcData.data(), (uint32_t)rtcData.size()); } uint8_t GbaRtc::Read() @@ -309,72 +28,28 @@ void GbaRtc::Write(uint8_t value) bool chipSelect = value & 0x04; if(!chipSelect || !_chipSelect) { - _command = 0; - _bitCounter = 0; + ResetBus(); _clk = 0; - _dataOut = 0; - _dataOutSize = 0; - _dataIn = 0; - _dataInSize = 0; _bitOut = 1; _chipSelect = chipSelect; return; } if(clk && !_clk) { - if(_dataOutSize) { - //Data output is updated on falling edge of clock - } else if(_dataInSize) { - ProcessDataIn(data); - } else { - _command <<= 1; - _command |= data; - _bitCounter++; - - if(_bitCounter == 8) { - if((_command & 0xF0) == 0x60) { - ProcessCommand(); - } else { - //invalid command - _command = 0; - } - - _bitCounter = 0; - } - } + WriteBits(data, 1); } else if(!clk && _clk) { - if(_dataOutSize) { - ProcessDataOut(); - } else { - _bitOut = 1; - } + _bitOut = ReadBits(1); } + _clk = clk; } void GbaRtc::Serialize(Serializer& s) { - SV(_bitCounter); - SV(_command); + S3511ARtc::Serialize(s); + SV(_clk); - SV(_dataOut); - SV(_dataOutSize); - SV(_dataIn); - SV(_dataInSize); SV(_bitOut); SV(_chipSelect); - SV(_lastUpdateTime); SV(_prevValue); - - SV(_state.Year); - SV(_state.Month); - SV(_state.Day); - SV(_state.DoW); - SV(_state.Hour); - SV(_state.Minute); - SV(_state.Second); - SV(_state.Status); - SV(_state.IntHour); - SV(_state.IntMinute); - SV(_state.TestMode); } diff --git a/Core/GBA/Cart/GbaRtc.h b/Core/GBA/Cart/GbaRtc.h index 0ec736768..0cdb9be57 100644 --- a/Core/GBA/Cart/GbaRtc.h +++ b/Core/GBA/Cart/GbaRtc.h @@ -1,79 +1,21 @@ #pragma once #include "pch.h" #include "Utilities/ISerializable.h" +#include "Shared/Utilities/S3511ARtc.h" class Emulator; -struct GbaRtcState -{ - uint8_t Year; - uint8_t Month; - uint8_t Day; - uint8_t DoW; - uint8_t Hour; - uint8_t Minute; - uint8_t Second; - - uint8_t Status; - uint8_t IntHour; //unimplemented - uint8_t IntMinute; //unimplemented - - bool TestMode; //unimplemented -}; - -class GbaRtc final : public ISerializable +class GbaRtc final : public S3511ARtc { private: - enum class Command : uint8_t - { - Reset, - Status, - DateTime, - Time, - Alarm1, - Alarm2, - TestStart, - TestEnd - }; - - Emulator* _emu = nullptr; - - GbaRtcState _state = {}; - uint64_t _lastUpdateTime = 0; - - uint8_t _bitCounter = 0; - uint8_t _command = 0; uint8_t _clk = 0; uint8_t _prevValue = 0; - - uint64_t _dataOut = 0; - uint8_t _dataOutSize = 0; - - uint64_t _dataIn = 0; - uint8_t _dataInSize = 0; - uint8_t _bitOut = 0; bool _chipSelect = false; - uint8_t SanitizeData(uint8_t value, uint8_t maxValue, uint8_t fixedValue); - void FromDateTime(uint64_t data, bool includeYmd); - uint64_t ToDateTime(); - - uint8_t GetCommandLength(Command cmd); - - void ProcessDataIn(uint8_t value); - void ProcessDataOut(); - void ProcessCommand(); - - void Reset(); - void UpdateTime(); - public: GbaRtc(Emulator* emu); - void LoadBattery(); - void SaveBattery(); - uint8_t Read(); void Write(uint8_t value); diff --git a/Core/Shared/Utilities/S3511ARtc.cpp b/Core/Shared/Utilities/S3511ARtc.cpp new file mode 100644 index 000000000..50e4f81f1 --- /dev/null +++ b/Core/Shared/Utilities/S3511ARtc.cpp @@ -0,0 +1,358 @@ +#include "pch.h" +#include "S3511ARtc.h" +#include "Utilities/Serializer.h" +#include "Utilities/TimeUtilities.h" +#include "Shared/Emulator.h" +#include "Shared/BatteryManager.h" + +S3511ARtc::S3511ARtc(Emulator* emu) +{ + _emu = emu; + + _state.Month = 1; + _state.Day = 1; + _state.Status = 0x02; + _state.IntHour = 0x80; + + _lastUpdateTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +uint8_t S3511ARtc::SanitizeData(uint8_t value, uint8_t maxValue, uint8_t fixedValue) +{ + if(value > maxValue || (value & 0x0F) >= 0x0A || (value & 0xF0) >= 0xA0) { + //Invalid values + return fixedValue; + } + + return value; +} + +void S3511ARtc::FromDateTime(uint64_t data, bool includeYmd) +{ + UpdateTime(); + + //TODO process invalid days (e.g feb 30) + if(includeYmd) { + _state.Year = SanitizeData(data & 0xFF, 0x99, 0); + _state.Month = SanitizeData((data >> 8) & 0x1F, 0x12, 1); + _state.Day = SanitizeData((data >> 16) & 0x3F, 0x31, 1); + _state.DoW = SanitizeData((data >> 24) & 0x07, 0x06, 0); + } + _state.Minute = SanitizeData((data >> 40) & 0x7F, 0x59, 0); + _state.Second = SanitizeData((data >> 48) & 0x7F, 0x59, 0); + + bool is24hMode = _state.Status & 0x40; + uint8_t hourData = (data >> 32) & 0xBF; + if(is24hMode) { + _state.Hour = SanitizeData(hourData & 0x3F, 0x23, 0); + if(_state.Hour >= 0x12) { + _state.Hour |= 0x80; //Set PM flag + } + } else { + _state.Hour = SanitizeData(hourData & 0x3F, 0x11, 0) | (hourData & 0x80); + } +} + +uint64_t S3511ARtc::ToDateTime() +{ + UpdateTime(); + return _state.Year | (_state.Month << 8) | (_state.Day << 16) | (_state.DoW << 24) | ((uint64_t)_state.Hour << 32) | ((uint64_t)_state.Minute << 40) | ((uint64_t)_state.Second << 48); +} + +uint8_t S3511ARtc::GetCommandLength(Command cmd) +{ + switch(cmd) { + default: + case Command::Reset: return 0; + case Command::Status: return 1; + case Command::DateTime: return 7; + case Command::Time: return 3; + case Command::Alarm1: return 2; + case Command::Alarm2: return 1; + case Command::TestStart: return 0; + case Command::TestEnd: return 0; + } +} + +uint8_t S3511ARtc::GetCommandLength(uint8_t cmd) +{ + return GetCommandLength((Command)((cmd >> 1) & 0x07)); +} + +void S3511ARtc::ResetBus() +{ + _command = 0; + _bitCounter = 0; + _dataOut = 0; + _dataOutSize = 0; + _dataIn = 0; + _dataInSize = 0; +} + +void S3511ARtc::WriteBits(uint32_t value, int count) +{ + if(_dataOutSize) { + return; + } + if(_dataInSize == 0) { + _command <<= count; + _command |= value; + _bitCounter += count; + + assert(_bitCounter <= 8); + if(_bitCounter == 8) { + if((_command & 0xF0) == 0x60) { + ProcessCommand(); + } else { + //invalid command + _command = 0; + } + + _bitCounter = 0; + } + + return; + } + assert(_dataInSize >= count); + + Command cmd = (Command)((_command & 0x0F) >> 1); + + uint8_t length = GetCommandLength(cmd) * 8; + _dataIn |= ((uint64_t)value << (length - _dataInSize)); + _dataInSize -= count; + + if(_dataInSize == 0) { + switch(cmd) { + case Command::Status: + _state.Status &= 0x80; + _state.Status |= (_dataIn & 0x6A); + break; + + case Command::DateTime: FromDateTime(_dataIn, true); break; + case Command::Time: FromDateTime(_dataIn << 32, false); break; + + default: + //TODO not implemented + break; + } + + for(int i = 0; i < 8; i++) { + //Re-write over the last byte if written beyond the expected length + _dataInSize++; + _dataIn &= ~((uint64_t)1 << (length - _dataInSize)); + } + } +} + +uint32_t S3511ARtc::ReadBits(int count) +{ + if(_dataOutSize == 0) { + return (1 << count) - 1; + } + assert(_dataOutSize >= count); + + uint32_t output = _dataOut & ((1 << count) - 1); + _dataOut >>= count; + _dataOutSize -= count; + if(_dataOutSize == 0) { + //Repeat command output if read beyond expected length + ProcessCommand(); + } + return output; +} + +void S3511ARtc::ProcessCommand() +{ + Command cmd = (Command)((_command & 0x0F) >> 1); + uint8_t length = GetCommandLength(cmd) * 8; + bool read = _command & 0x01; + if(read) { + _dataOutSize = length; + + if(_emu->IsDebugging()) { + _emu->DebugLog("[RTC] Read command: " + string(magic_enum::enum_name(cmd))); + } + + switch(cmd) { + case Command::Reset: Reset(); break; + case Command::Status: _dataOut = _state.Status; break; + case Command::DateTime: _dataOut = ToDateTime(); break; + case Command::Time: _dataOut = ToDateTime() >> 32; break; + default: + //TODO not implemented + _dataOut = 0; + break; + } + } else { + if(_emu->IsDebugging()) { + _emu->DebugLog("[RTC] Write command: " + string(magic_enum::enum_name(cmd))); + } + + if(cmd == Command::Reset) { + Reset(); + } + + _dataInSize = length; + _dataIn = 0; + } +} + +void S3511ARtc::Reset() +{ + _state = {}; + _state.Month = 1; + _state.Day = 1; +} + +void S3511ARtc::UpdateTime() +{ + uint64_t currentTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + uint32_t elapsedSeconds = (uint32_t)(currentTime - _lastUpdateTime); + if(elapsedSeconds <= 0) { + return; + } + + std::tm tm = {}; + tm.tm_sec = (_state.Second & 0x0F) + ((_state.Second >> 4) * 10); + tm.tm_min = (_state.Minute & 0x0F) + ((_state.Minute >> 4) * 10); + int hour = _state.Hour & 0x3F; + tm.tm_hour = (hour & 0x0F) + ((hour >> 4) * 10); + if(tm.tm_hour < 12 && (_state.Hour & 0x80)) { + tm.tm_hour += 12; + } + tm.tm_mday = (_state.Day & 0x0F) + ((_state.Day >> 4) * 10); + + int month = _state.Month - 1; + tm.tm_mon = (month & 0x0F) + ((month >> 4) * 10); + tm.tm_year = 100 + (_state.Year & 0x0F) + ((_state.Year >> 4) * 10); + + std::time_t tt = TimeUtilities::TmToUtc(&tm); + if(tt == -1) { + //Invalid time + _lastUpdateTime = currentTime; + return; + } + + int8_t dowGap = 0; + if(tm.tm_wday != _state.DoW) { + //The DoW on the RTC can be set to any arbitrary value for a specific date + //Check the gap between the value set by the game & the real dow for that date + dowGap = (int8_t)tm.tm_wday - (int8_t)_state.DoW; + } + + std::chrono::system_clock::time_point timePoint = std::chrono::system_clock::from_time_t(tt); + timePoint += std::chrono::seconds((uint32_t)elapsedSeconds); + + std::time_t newTime = system_clock::to_time_t(timePoint); + std::tm newTm; +#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) + gmtime_s(&newTm, &newTime); +#else + gmtime_r(&newTime, &newTm); +#endif + + _state.Second = (newTm.tm_sec % 10) + ((newTm.tm_sec / 10) << 4); + _state.Minute = (newTm.tm_min % 10) + ((newTm.tm_min / 10) << 4); + + bool is24hMode = _state.Status & 0x40; + if(is24hMode) { + _state.Hour = (newTm.tm_hour % 10) + ((newTm.tm_hour / 10) << 4); + } else { + hour = newTm.tm_hour >= 12 ? newTm.tm_hour - 12 : newTm.tm_hour; + _state.Hour = (hour % 10) + ((hour / 10) << 4); + } + + if(newTm.tm_hour >= 12) { + //Set PM flag (in both 12h and 24h modes) + _state.Hour |= 0x80; + } + + _state.Day = (newTm.tm_mday % 10) + ((newTm.tm_mday / 10) << 4); + + month = newTm.tm_mon + 1; + _state.Month = (month % 10) + ((month / 10) << 4); + + int year = newTm.tm_year - 100; + _state.Year = (year % 10) + ((year / 10) << 4); + + int dow = newTm.tm_wday - dowGap; + _state.DoW = dow < 0 ? (dow + 7) : (dow % 7); + + _lastUpdateTime = currentTime; +} + +void S3511ARtc::LoadBattery() +{ + vector rtcData = _emu->GetBatteryManager()->LoadBattery(".rtc"); + + if(rtcData.size() == sizeof(_state) + sizeof(uint64_t)) { + _state.Year = rtcData[0]; + _state.Month = rtcData[1]; + _state.Day = rtcData[2]; + _state.DoW = rtcData[3]; + _state.Hour = rtcData[4]; + _state.Minute = rtcData[5]; + _state.Second = rtcData[6]; + _state.Status = rtcData[7]; + _state.IntHour = rtcData[8]; + _state.IntMinute = rtcData[9]; + + uint64_t time = 0; + for(uint32_t i = 0; i < sizeof(uint64_t); i++) { + time <<= 8; + time |= rtcData[sizeof(_state) + i]; + } + _lastUpdateTime = time; + } else { + _lastUpdateTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } +} + +void S3511ARtc::SaveBattery() +{ + vector rtcData; + + rtcData.push_back(_state.Year); + rtcData.push_back(_state.Month); + rtcData.push_back(_state.Day); + rtcData.push_back(_state.DoW); + rtcData.push_back(_state.Hour); + rtcData.push_back(_state.Minute); + rtcData.push_back(_state.Second); + rtcData.push_back(_state.Status); + rtcData.push_back(_state.IntHour); + rtcData.push_back(_state.IntMinute); + + rtcData.resize(sizeof(_state) + sizeof(uint64_t), 0); + + uint64_t time = _lastUpdateTime; + for(uint32_t i = 0; i < sizeof(uint64_t); i++) { + rtcData[sizeof(_state) + i] = (time >> 56) & 0xFF; + time <<= 8; + } + + _emu->GetBatteryManager()->SaveBattery(".rtc", rtcData.data(), (uint32_t)rtcData.size()); +} + +void S3511ARtc::Serialize(Serializer& s) +{ + SV(_bitCounter); + SV(_command); + SV(_dataOut); + SV(_dataOutSize); + SV(_dataIn); + SV(_dataInSize); + SV(_lastUpdateTime); + + SV(_state.Year); + SV(_state.Month); + SV(_state.Day); + SV(_state.DoW); + SV(_state.Hour); + SV(_state.Minute); + SV(_state.Second); + SV(_state.Status); + SV(_state.IntHour); + SV(_state.IntMinute); + SV(_state.TestMode); +} diff --git a/Core/Shared/Utilities/S3511ARtc.h b/Core/Shared/Utilities/S3511ARtc.h new file mode 100644 index 000000000..474d5837d --- /dev/null +++ b/Core/Shared/Utilities/S3511ARtc.h @@ -0,0 +1,78 @@ +#pragma once +#include "pch.h" +#include "Utilities/ISerializable.h" + +class Emulator; + +struct S3511ARtcState +{ + uint8_t Year; + uint8_t Month; + uint8_t Day; + uint8_t DoW; + uint8_t Hour; + uint8_t Minute; + uint8_t Second; + + uint8_t Status; + uint8_t IntHour; //unimplemented + uint8_t IntMinute; //unimplemented + + bool TestMode; //unimplemented +}; + +class S3511ARtc : public ISerializable +{ +private: + S3511ARtcState _state = {}; + uint64_t _lastUpdateTime = 0; + + uint8_t _bitCounter = 0; + uint8_t _command = 0; + + uint64_t _dataOut = 0; + uint8_t _dataOutSize = 0; + + uint64_t _dataIn = 0; + uint8_t _dataInSize = 0; + + uint8_t SanitizeData(uint8_t value, uint8_t maxValue, uint8_t fixedValue); + void FromDateTime(uint64_t data, bool includeYmd); + uint64_t ToDateTime(); + + void ProcessCommand(); + + void Reset(); + void UpdateTime(); + +protected: + enum class Command : uint8_t + { + Reset, + Status, + DateTime, + Time, + Alarm1, + Alarm2, + TestStart, + TestEnd + }; + + Emulator* _emu = nullptr; + + uint8_t GetCommandLength(Command cmd); + uint8_t GetCommandLength(uint8_t cmd); + + //GBA only uses count values of 1, WS only uses 8 + void WriteBits(uint32_t value, int count); + uint32_t ReadBits(int count); + void ResetBus(); + +public: + S3511ARtc(Emulator* emu); + + void LoadBattery(); + void SaveBattery(); + + void Serialize(Serializer& s) override; +}; diff --git a/Core/WS/Carts/WsCart.cpp b/Core/WS/Carts/WsCart.cpp index 15aef58bc..6895417a7 100644 --- a/Core/WS/Carts/WsCart.cpp +++ b/Core/WS/Carts/WsCart.cpp @@ -1,5 +1,6 @@ #include "pch.h" #include "WS/Carts/WsCart.h" +#include "WS/Carts/WsRtc.h" #include "WS/WsMemoryManager.h" #include "WS/WsEeprom.h" #include "Utilities/Serializer.h" @@ -22,10 +23,12 @@ WsCart::WsCart() _state.SelectedBanks[3] = 0xFF; } -void WsCart::Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom) +void WsCart::Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom, WsRtc* cartRtc) { _memoryManager = memoryManager; _cartEeprom = cartEeprom; + _cartRtc = cartRtc; + _state.HasRtc = cartRtc != nullptr; } void WsCart::RefreshMappings() @@ -40,8 +43,10 @@ uint8_t WsCart::ReadPort(uint16_t port) { if(port < 0xC4) { return _state.SelectedBanks[port - 0xC0]; - } else if(port < 0xC9 && _cartEeprom) { + } else if(port >= 0xC4 && port < 0xC9 && _cartEeprom) { return _cartEeprom->ReadPort(port - 0xC4); + } else if(port >= 0xCA && port < 0xCC && _cartRtc) { + return _cartRtc->ReadPort(port); } return _memoryManager->GetUnmappedPort(); @@ -52,8 +57,10 @@ void WsCart::WritePort(uint16_t port, uint8_t value) if(port < 0xC4) { _state.SelectedBanks[port - 0xC0] = value; _memoryManager->RefreshMappings(); - } else if(port < 0xC9 && _cartEeprom) { + } else if(port >= 0xC4 && port < 0xC9 && _cartEeprom) { _cartEeprom->WritePort(port - 0xC4, value); + } else if(port >= 0xCA && port < 0xCC && _cartRtc) { + _cartRtc->WritePort(port, value); } } diff --git a/Core/WS/Carts/WsCart.h b/Core/WS/Carts/WsCart.h index 87af797a9..1779c395d 100644 --- a/Core/WS/Carts/WsCart.h +++ b/Core/WS/Carts/WsCart.h @@ -4,10 +4,11 @@ #include "Utilities/ISerializable.h" #include "Shared/MemoryType.h" +class WsConsole; class WsMemoryManager; class WsEeprom; +class WsRtc; -//TODOWS RTC //TODOWS Flash class WsCart final : public ISerializable @@ -17,6 +18,7 @@ class WsCart final : public ISerializable WsMemoryManager* _memoryManager = nullptr; WsEeprom* _cartEeprom = nullptr; + WsRtc* _cartRtc = nullptr; void Map(uint32_t start, uint32_t end, MemoryType type, uint32_t offset, bool readonly); void Unmap(uint32_t start, uint32_t end); @@ -25,11 +27,12 @@ class WsCart final : public ISerializable WsCart(); virtual ~WsCart() {} - void Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom); + void Init(WsMemoryManager* memoryManager, WsEeprom* cartEeprom, WsRtc* cartRtc); void RefreshMappings(); WsCartState& GetState() { return _state; } WsEeprom* GetEeprom() { return _cartEeprom; } + WsRtc* GetRtc() { return _cartRtc; } virtual uint8_t ReadPort(uint16_t port); virtual void WritePort(uint16_t port, uint8_t value); diff --git a/Core/WS/Carts/WsRtc.cpp b/Core/WS/Carts/WsRtc.cpp new file mode 100644 index 000000000..e3e5c2b1b --- /dev/null +++ b/Core/WS/Carts/WsRtc.cpp @@ -0,0 +1,102 @@ +#include "Shared/Utilities/S3511ARtc.h" +#include "WS/WsConsole.h" +#include "pch.h" +#include "WS/Carts/WsRtc.h" +#include "Utilities/Serializer.h" +#include "Shared/Emulator.h" + +// TODOWS clock-accurate behavior, needs tests +// TODOWS edge case: changing commands mid-transfer, needs tests + +WsRtc::WsRtc(Emulator* emu, WsConsole* console) : S3511ARtc(emu), _console(console) +{ + _state.Ready = true; +} + +bool WsRtc::IsCommandRead() +{ + return (_state.Command & 0x01) != 0; +} + +void WsRtc::StartByteTransfer() +{ + if(_state.Busy) { + _state.Ready = false; + _transferClock = _console->GetCartridgeClock() + 8; + } +} + +void WsRtc::Update() +{ + if(!_state.Ready && _console->GetCartridgeClock() >= _transferClock) { + if(!_commandTransferred) { + WriteBits((_state.Command & 0x0F) | 0x60, 8); + _commandTransferred = true; + _state.Ready = true; + if(_bytesLeft) { + StartByteTransfer(); + } + } else if(_bytesLeft) { + if(IsCommandRead()) { + _state.Data = ReadBits(8); + } else { + WriteBits(_state.Data, 8); + } + _state.Ready = true; + _bytesLeft--; + } + + if(_commandTransferred && !_bytesLeft) { + ResetBus(); + _state.Busy = false; + _commandTransferred = false; + } + } +} + +uint8_t WsRtc::ReadPort(uint16_t port) +{ + Update(); + + if(port == 0xCA) { + return (_state.Command & 0x0F) | (_state.Busy ? 0x10 : 0) | (_state.Ready ? 0x80 : 0); + } else if(port == 0xCB) { + if(_bytesLeft && IsCommandRead()) { + StartByteTransfer(); + } + return _state.Data; + } + + return 0; +} + +void WsRtc::WritePort(uint16_t port, uint8_t value) +{ + Update(); + + if(port == 0xCA) { + _state.Busy = (value & 0x10) == 0x10; + _state.Command = (value & 0x0F); + _bytesLeft = GetCommandLength(_state.Command); + StartByteTransfer(); + } else if(port == 0xCB) { + _state.Data = value; + if(_bytesLeft && !IsCommandRead()) { + StartByteTransfer(); + } + } +} + +void WsRtc::Serialize(Serializer& s) +{ + S3511ARtc::Serialize(s); + + SV(_state.Command); + SV(_state.Data); + SV(_state.Ready); + SV(_state.Busy); + + SV(_bytesLeft); + SV(_commandTransferred); + SV(_transferClock); +} diff --git a/Core/WS/Carts/WsRtc.h b/Core/WS/Carts/WsRtc.h new file mode 100644 index 000000000..076a9cee6 --- /dev/null +++ b/Core/WS/Carts/WsRtc.h @@ -0,0 +1,32 @@ +#pragma once +#include "pch.h" +#include "WS/WsTypes.h" +#include "Utilities/ISerializable.h" +#include "Shared/Utilities/S3511ARtc.h" + +class Emulator; + +class WsRtc final : public S3511ARtc +{ +private: + WsConsole* _console; + WsRtcState _state = {}; + + uint8_t _bytesLeft = 0; + bool _commandTransferred = false; + uint32_t _transferClock = 0; + + bool IsCommandRead(); + void StartByteTransfer(); + void Update(); + +public: + WsRtc(Emulator* emu, WsConsole* console); + + WsRtcState& GetState() { return _state; } + + uint8_t ReadPort(uint16_t port); + void WritePort(uint16_t port, uint8_t value); + + void Serialize(Serializer& s) override; +}; diff --git a/Core/WS/WsConsole.cpp b/Core/WS/WsConsole.cpp index b6445985b..ed2f42693 100644 --- a/Core/WS/WsConsole.cpp +++ b/Core/WS/WsConsole.cpp @@ -4,6 +4,7 @@ #include "WS/WsPpu.h" #include "WS/WsTimer.h" #include "WS/Carts/WsCart.h" +#include "WS/Carts/WsRtc.h" #include "WS/WsControlManager.h" #include "WS/WsMemoryManager.h" #include "WS/WsDmaController.h" @@ -94,6 +95,10 @@ LoadRomResult WsConsole::LoadRom(VirtualFile& romFile) _cartEeprom.reset(new WsEeprom(_emu, this, (WsEepromSize)_cartEepromSize, _cartEepromData, false)); } + if(mapperType >= 0x01) { + _cartRtc.reset(new WsRtc(_emu, this)); + } + _model = _emu->GetSettings()->GetWsConfig().Model; if(_model == WsModel::Auto) { _model = hasColorSupport ? WsModel::Color : WsModel::Monochrome; @@ -129,7 +134,7 @@ LoadRomResult WsConsole::LoadRom(VirtualFile& romFile) _apu.reset(new WsApu(_emu, this, _memoryManager.get(), _dmaController.get())); _cart.reset(new WsCart()); - _cart->Init(_memoryManager.get(), _cartEeprom.get()); + _cart->Init(_memoryManager.get(), _cartEeprom.get(), _cartRtc.get()); _memoryManager->Init(_emu, this, _cpu.get(), _ppu.get(), _controlManager.get(), _cart.get(), _timer.get(), _dmaController.get(), _internalEeprom.get(), _apu.get(), _serial.get()); _timer->Init(_memoryManager.get()); _dmaController->Init(_memoryManager.get(), _apu.get()); @@ -311,6 +316,9 @@ void WsConsole::LoadBattery() if(_cartEeprom) { _cartEeprom->LoadBattery(); } + if(_cartRtc) { + _cartRtc->LoadBattery(); + } if(_saveRam) { _emu->GetBatteryManager()->LoadBattery(".sav", _saveRam, _saveRamSize); @@ -323,6 +331,9 @@ void WsConsole::SaveBattery() if(_cartEeprom) { _cartEeprom->SaveBattery(); } + if(_cartRtc) { + _cartRtc->SaveBattery(); + } if(_saveRam) { _emu->GetBatteryManager()->SaveBattery(".sav", _saveRam, _saveRamSize); @@ -349,6 +360,16 @@ vector WsConsole::GetCpuTypes() return { CpuType::Ws }; } +uint64_t WsConsole::GetCartridgeClock() +{ + return _cpu->GetCycleCount() / 8; +} + +uint32_t WsConsole::GetCartridgeClockRate() +{ + return 12288000 / 32; +} + uint64_t WsConsole::GetMasterClock() { return _cpu->GetCycleCount(); @@ -447,6 +468,9 @@ WsState WsConsole::GetState() if(_cartEeprom) { state.CartEeprom = _cartEeprom->GetState(); } + if(_cartRtc) { + state.CartRtc = _cartRtc->GetState(); + } return state; } @@ -496,4 +520,7 @@ void WsConsole::Serialize(Serializer& s) if(_cartEeprom) { SV(_cartEeprom); } + if(_cartRtc) { + SV(_cartRtc); + } } diff --git a/Core/WS/WsConsole.h b/Core/WS/WsConsole.h index 628016f73..b48814a52 100644 --- a/Core/WS/WsConsole.h +++ b/Core/WS/WsConsole.h @@ -14,6 +14,7 @@ class WsMemoryManager; class WsControlManager; class WsDmaController; class WsEeprom; +class WsRtc; enum class WsModel : uint8_t; class WsConsole final : public IConsole @@ -31,6 +32,7 @@ class WsConsole final : public IConsole unique_ptr _dmaController; unique_ptr _internalEeprom; unique_ptr _cartEeprom; + unique_ptr _cartRtc; uint8_t* _workRam = nullptr; uint32_t _workRamSize = 0; @@ -81,6 +83,8 @@ class WsConsole final : public IConsole ConsoleRegion GetRegion() override; ConsoleType GetConsoleType() override; vector GetCpuTypes() override; + uint64_t GetCartridgeClock(); + uint32_t GetCartridgeClockRate(); uint64_t GetMasterClock() override; uint32_t GetMasterClockRate() override; double GetFps() override; diff --git a/Core/WS/WsTypes.h b/Core/WS/WsTypes.h index f7c80fa90..367d22327 100644 --- a/Core/WS/WsTypes.h +++ b/Core/WS/WsTypes.h @@ -445,9 +445,18 @@ struct WsEepromState struct WsCartState { + bool HasRtc; uint8_t SelectedBanks[4]; }; +struct WsRtcState +{ + uint8_t Data; + uint8_t Command; + bool Ready; + bool Busy; +}; + struct WsState { WsCpuState Cpu; @@ -461,6 +470,7 @@ struct WsState WsEepromState InternalEeprom; WsCartState Cart; WsEepromState CartEeprom; + WsRtcState CartRtc; WsModel Model; }; diff --git a/UI/Debugger/RegisterViewer/WsRegisterViewer.cs b/UI/Debugger/RegisterViewer/WsRegisterViewer.cs index 40ac369a9..2e209c904 100644 --- a/UI/Debugger/RegisterViewer/WsRegisterViewer.cs +++ b/UI/Debugger/RegisterViewer/WsRegisterViewer.cs @@ -383,6 +383,16 @@ private static RegisterViewerTab GetCartTab(ref WsState ws) }); } + if(cart.HasRtc) { + entries.AddRange(new List() { + new RegEntry("", "Cart RTC"), + new RegEntry("$CA.0-3", "Command", ws.CartRtc.Command, Format.X8), + new RegEntry("$CA.4", "Busy", ws.CartRtc.Busy), + new RegEntry("$CA.7", "Ready", ws.CartRtc.Ready), + new RegEntry("$CB", "Data", ws.CartRtc.Data) + }); + } + return new RegisterViewerTab("Cart", entries, CpuType.Ws, MemoryType.WsPort); } diff --git a/UI/Interop/ConsoleState/WsState.cs b/UI/Interop/ConsoleState/WsState.cs index af8d317cc..7df108e42 100644 --- a/UI/Interop/ConsoleState/WsState.cs +++ b/UI/Interop/ConsoleState/WsState.cs @@ -442,10 +442,19 @@ public struct WsEepromState public struct WsCartState { + [MarshalAs(UnmanagedType.I1)] public bool HasRtc; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] SelectedBanks; } +public struct WsRtcState +{ + public byte Data; + public byte Command; + [MarshalAs(UnmanagedType.I1)] public bool Ready; + [MarshalAs(UnmanagedType.I1)] public bool Busy; +} + public struct WsState : BaseState { public WsCpuState Cpu; @@ -459,5 +468,6 @@ public struct WsState : BaseState public WsEepromState InternalEeprom; public WsCartState Cart; public WsEepromState CartEeprom; + public WsRtcState CartRtc; public WsModel Model; }