Files

359 lines
8.8 KiB
C++

#include "pch.h"
#include "Shared/Utilities/S3511ARtc.h"
#include "Shared/Emulator.h"
#include "Shared/BatteryManager.h"
#include "Utilities/Serializer.h"
#include "Utilities/TimeUtilities.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::seconds>(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<Command>(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<Command>(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::seconds>(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<uint8_t> 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::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
}
void S3511ARtc::SaveBattery()
{
vector<uint8_t> 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);
}