350 lines
8.0 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|