StoicGoose.Unity/Assets/Plugins/StoicGooseUnity/StoicGoose.Core/Cartridges/RTC.cs

350 lines
8.0 KiB
C#

using System;
using StoicGoose.Common.Utilities;
using StoicGoose.Core.Interfaces;
using StoicGoose.Core.Machines;
using static StoicGoose.Common.Utilities.BitHandling;
namespace StoicGoose.Core.Cartridges
{
/* Seiko S-3511A real-time clock, through Bandai 2003 mapper
* - https://forums.nesdev.org/viewtopic.php?t=21513
* - https://datasheetspdf.com/pdf-file/1087347/Seiko/S-3511A/1
*/
// TODO: interrupts, save/load current state
public sealed class RTC : IPortAccessComponent
{
const int cyclesInSecond = (int)MachineCommon.CpuClock;
readonly byte[] numPayloadBytes = new byte[] { 0, 1, 7, 3, 2, 2, 2 };
readonly int[] numDaysPerMonth = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
/* WS - Status & data registers */
byte wsData;
byte payloadIndex;
/* WS+RTC - Communication */
byte command;
bool isReadAccess;
/* RTC - Real-time data register */
byte year, month, day, dayOfWeek, hour, minute, second;
bool isPm, isTestModeActive;
/* RTC - Status register */
bool isPowered, is24HourMode;
bool intAE, intME, intFE;
/* RTC - Alarm time/frequency duty setting register */
ushort intRegister;
(bool pm, byte hour, byte minute) alarmTime => (IsBitSet((byte)(intRegister >> 0), 7), (byte)((intRegister >> 0) & 0b00111111), (byte)((intRegister >> 8) & 0b01111111));
int selectedInterruptFreq
{
get
{
var freq = 0;
for (var j = 0; j < 16; j++) if (((intRegister >> j) & 0b1) == 0b1) freq |= 32768 >> j;
return freq;
}
}
int cycleCount;
public RTC()
{
//
}
public void Reset()
{
wsData = 0;
payloadIndex = 0;
command = 0;
isReadAccess = false;
year = dayOfWeek = hour = minute = second = 0;
month = day = 1;
isPm = isTestModeActive = false;
is24HourMode = intAE = intME = false;
isPowered = intFE = true;
intRegister = 0x8000;
cycleCount = 0;
}
public void Shutdown()
{
//
}
public void Program(DateTime dateTime)
{
year = (byte)(dateTime.Year % 100);
month = (byte)(dateTime.Month % 13);
day = (byte)(dateTime.Day % 32);
dayOfWeek = (byte)((int)dateTime.DayOfWeek % 8);
hour = (byte)(dateTime.Hour % 25);
minute = (byte)(dateTime.Minute % 60);
second = (byte)(dateTime.Second % 60);
}
public bool Step(int clockCyclesInStep)
{
var interrupt = false;
for (var i = 0; i < clockCyclesInStep; i++)
{
if (intFE && !intME)
{
/* Selected frequency steady interrupt output */
// TODO probably not right
if (cycleCount >= selectedInterruptFreq)
interrupt = true;
}
else if (!intFE && intME)
{
/* Per-minute edge interrupt output */
// TODO
}
else if (intFE && intME)
{
/* Per-minute steady interrupt output */
// TODO
}
else if (!intFE && !intME && intAE)
{
/* Alarm interrupt output */
if (alarmTime.pm == isPm && Bcd.BcdToDecimal(alarmTime.hour) == hour && Bcd.BcdToDecimal(alarmTime.minute) == minute)
interrupt = true;
}
cycleCount++;
if (cycleCount >= cyclesInSecond)
{
UpdateClock();
cycleCount = 0;
}
}
return interrupt;
}
private void UpdateClock()
{
second++;
if (second < 60) return;
second = 0;
minute++;
if (minute < 60) return;
minute = 0;
hour++;
if (hour < 24) return;
hour = 0;
dayOfWeek++;
dayOfWeek %= 7;
day++;
var extraDay = (month == 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) ? 1 : 0;
if (day < numDaysPerMonth[month] + extraDay) return;
day = 0;
month++;
if (month < 12) return;
month = 0;
year++;
}
private void PerformAccess()
{
switch (command & 0b111)
{
case 0b000:
/* Reset */
wsData = 0;
year = 0;
month = 1;
day = 1;
dayOfWeek = 0;
hour = 0;
minute = 0;
second = 0;
isPowered = is24HourMode = false;
intAE = intME = intFE = false;
intRegister = 0x0000;
break;
case 0b001:
/* Status register access */
if (isReadAccess)
{
wsData = 0;
ChangeBit(ref wsData, 7, isPowered);
ChangeBit(ref wsData, 6, is24HourMode);
ChangeBit(ref wsData, 5, intAE);
ChangeBit(ref wsData, 3, intME);
ChangeBit(ref wsData, 1, intFE);
}
else
{
is24HourMode = IsBitSet(wsData, 6);
intAE = IsBitSet(wsData, 5);
intME = IsBitSet(wsData, 3);
intFE = IsBitSet(wsData, 1);
}
break;
case 0b010:
/* Real-time data access 1 */
if (isReadAccess)
{
wsData = 0;
switch (payloadIndex)
{
case 0: wsData = (byte)Bcd.DecimalToBcd(year); break;
case 1: wsData = (byte)Bcd.DecimalToBcd(month); break;
case 2: wsData = (byte)Bcd.DecimalToBcd(day); break;
case 3: wsData = (byte)Bcd.DecimalToBcd(dayOfWeek); break;
case 4: wsData = (byte)Bcd.DecimalToBcd(hour); ChangeBit(ref wsData, 7, isPm); break;
case 5: wsData = (byte)Bcd.DecimalToBcd(minute); break;
case 6: wsData = (byte)Bcd.DecimalToBcd(second); ChangeBit(ref wsData, 7, isTestModeActive); break;
}
}
else
{
switch (payloadIndex)
{
case 0: year = (byte)Bcd.BcdToDecimal(wsData); break;
case 1: month = (byte)Bcd.BcdToDecimal(wsData); break;
case 2: day = (byte)Bcd.BcdToDecimal(wsData); break;
case 3: dayOfWeek = (byte)Bcd.BcdToDecimal(wsData); break;
case 4: hour = (byte)(Bcd.BcdToDecimal(wsData) & 0b01111111); isPm = IsBitSet(wsData, 7); break;
case 5: minute = (byte)Bcd.BcdToDecimal(wsData); break;
case 6: second = (byte)(Bcd.BcdToDecimal(wsData) & 0b01111111); isTestModeActive = IsBitSet(wsData, 7); break;
}
}
break;
case 0b011:
/* Real-time data access 2 */
if (isReadAccess)
{
wsData = 0;
switch (payloadIndex)
{
case 0: wsData = (byte)Bcd.DecimalToBcd(hour); ChangeBit(ref wsData, 7, isPm); break;
case 1: wsData = (byte)Bcd.DecimalToBcd(minute); break;
case 2: wsData = (byte)Bcd.DecimalToBcd(second); ChangeBit(ref wsData, 7, isTestModeActive); break;
}
}
else
{
switch (payloadIndex)
{
case 0: hour = (byte)(Bcd.BcdToDecimal(wsData) & 0b01111111); isPm = IsBitSet(wsData, 7); break;
case 1: minute = (byte)Bcd.BcdToDecimal(wsData); break;
case 2: second = (byte)(Bcd.BcdToDecimal(wsData) & 0b01111111); isTestModeActive = IsBitSet(wsData, 7); break;
}
}
break;
case 0b100:
/* Alarm time/frequency duty setting */
if (isReadAccess)
{
wsData = 0;
switch (payloadIndex)
{
case 0: wsData = (byte)((intRegister >> 0) & 0xFF); break;
case 1: wsData = (byte)((intRegister >> 8) & 0xFF); break;
}
}
else
{
switch (payloadIndex)
{
case 0: intRegister = (ushort)((intRegister & 0xFF00) | (wsData << 0)); break;
case 1: intRegister = (ushort)((intRegister & 0x00FF) | (wsData << 8)); break;
}
}
break;
case 0b101:
/* Unknown/invalid */
if (isReadAccess)
{
wsData = 0xFF;
}
break;
case 0b110:
/* Test mode start -- ignored */
break;
case 0b111:
/* Test mode end -- ignored */
break;
default:
break;
}
}
public byte ReadPort(ushort port)
{
var retVal = (byte)0x90;
if (port == 0)
{
PerformAccess();
payloadIndex++;
ChangeBit(ref retVal, 7, true); // TODO: correct?
ChangeBit(ref retVal, 4, payloadIndex < numPayloadBytes[command & 0b111]);
ChangeBit(ref retVal, 0, true);
retVal |= (byte)((command & 0b1111) << 1);
if (payloadIndex >= numPayloadBytes[command & 0b111])
payloadIndex = 0;
}
else if (port == 1)
{
retVal = wsData;
}
return retVal;
}
public void WritePort(ushort port, byte value)
{
if (port == 0)
{
isReadAccess = IsBitSet(value, 0);
command = (byte)((value >> 1) & 0b111);
PerformAccess();
}
else if (port == 1)
{
wsData = value;
}
}
}
}