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;
}