Cores/Nds/NDS.Rtc.cs
namespace Emulotl.Nds;
public sealed partial class NDS
{
private ushort RtcIO;
private byte RtcInput;
private int RtcInputBit;
private int RtcInputPos;
private readonly byte[] RtcOutput = new byte[8];
private int RtcOutputBit;
private int RtcOutputPos;
private byte RtcCurCmd;
private readonly byte[] RtcDateTime = new byte[7];
private byte RtcStatusReg1;
private byte RtcStatusReg2;
private readonly byte[] RtcAlarm1 = new byte[3];
private readonly byte[] RtcAlarm2 = new byte[3];
private byte RtcClockAdjust;
private byte RtcFreeReg;
private byte RtcIRQFlag;
private uint RtcMinuteCount;
private uint RtcClockCount;
private int RtcTimerError;
private ushort RCnt;
private void ResetRtc()
{
RtcIO = 0;
RtcInput = 0;
RtcInputBit = 0;
RtcInputPos = 0;
Array.Clear( RtcOutput );
RtcOutputBit = 0;
RtcOutputPos = 0;
RtcCurCmd = 0;
RtcStatusReg1 = 0x02;
RtcStatusReg2 = 0;
Array.Clear( RtcAlarm1 );
Array.Clear( RtcAlarm2 );
RtcClockAdjust = 0;
RtcFreeReg = 0;
RtcIRQFlag = 0;
RtcMinuteCount = 0;
RCnt = 0;
RtcSyncToHost();
RtcClockCount = 0;
ScheduleRtcTimer( true );
}
private void RtcSyncToHost()
{
DateTime now = DateTime.Now;
int year = now.Year % 100;
int month = now.Month;
int day = now.Day;
int hour = now.Hour;
int minute = now.Minute;
int second = now.Second;
int[] monthdays = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if ( (year & 3) == 0 ) monthdays[2] = 29;
int numdays = (year * 365) + ((year + 3) / 4);
for ( int m = 1; m < month; m++ )
numdays += monthdays[m];
numdays += day - 1;
int dayofweek = (6 + numdays) % 7;
int pm = hour >= 12 ? 0x40 : 0;
if ( (RtcStatusReg1 & (1 << 1)) == 0 && pm != 0 )
hour -= 12;
RtcDateTime[0] = BCD( year );
RtcDateTime[1] = BCD( month );
RtcDateTime[2] = BCD( day );
RtcDateTime[3] = (byte)dayofweek;
RtcDateTime[4] = (byte)(BCD( hour ) | pm);
RtcDateTime[5] = BCD( minute );
RtcDateTime[6] = BCD( second );
}
private static byte BCD( int val ) => (byte)((val % 10) | ((val / 10) << 4));
private static byte BCDIncrement( byte val )
{
int v = val + 1;
if ( (v & 0x0F) >= 0x0A ) v += 0x06;
if ( (v & 0xF0) >= 0xA0 ) v += 0x60;
return (byte)v;
}
private void ScheduleRtcTimer( bool first )
{
if ( first ) RtcTimerError = 0;
int sysclock = 33513982 + RtcTimerError;
int delay = sysclock >> 15;
RtcTimerError = sysclock & 0x7FFF;
ScheduleEvent( SysEvent.Rtc, !first, delay, RtcClockTimer );
}
private void RtcClockTimer( uint param )
{
RtcClockCount++;
if ( (RtcClockCount & 0x7FFF) == 0 )
CountSecond();
else if ( (RtcClockCount & 0x7FFF) == 4 )
RtcIRQFlag = (byte)(RtcIRQFlag & ~0x01);
ProcessRtcIRQ( 1 );
ScheduleRtcTimer( false );
}
private void CountSecond()
{
RtcDateTime[6] = BCDIncrement( RtcDateTime[6] );
if ( RtcDateTime[6] >= 0x60 )
{
RtcDateTime[6] = 0;
CountMinute();
}
}
private void CountMinute()
{
RtcMinuteCount++;
RtcDateTime[5] = BCDIncrement( RtcDateTime[5] );
if ( RtcDateTime[5] >= 0x60 )
{
RtcDateTime[5] = 0;
CountHour();
}
RtcIRQFlag |= 0x01;
ProcessRtcIRQ( 0 );
}
private void CountHour()
{
byte hour = BCDIncrement( (byte)(RtcDateTime[4] & 0x3F) );
byte pm = (byte)(RtcDateTime[4] & 0x40);
if ( (RtcStatusReg1 & (1 << 1)) != 0 )
{
if ( hour >= 0x24 )
{
hour = 0;
CountDay();
}
pm = (byte)(hour >= 0x12 ? 0x40 : 0);
}
else
{
if ( hour >= 0x12 )
{
hour = 0;
if ( pm != 0 ) CountDay();
pm ^= 0x40;
}
}
RtcDateTime[4] = (byte)(hour | pm);
}
private void CountDay()
{
RtcDateTime[3]++;
if ( RtcDateTime[3] >= 7 )
RtcDateTime[3] = 0;
RtcDateTime[2] = BCDIncrement( RtcDateTime[2] );
CheckEndOfMonth();
}
private void CheckEndOfMonth()
{
if ( RtcDateTime[2] > DaysInMonth() )
{
RtcDateTime[2] = 1;
CountMonth();
}
}
private void CountMonth()
{
RtcDateTime[1] = BCDIncrement( RtcDateTime[1] );
if ( RtcDateTime[1] > 0x12 )
{
RtcDateTime[1] = 1;
CountYear();
}
}
private void CountYear()
{
RtcDateTime[0] = BCDIncrement( RtcDateTime[0] );
}
private byte DaysInMonth()
{
byte numdays;
switch ( RtcDateTime[1] )
{
case 0x01:
case 0x03:
case 0x05:
case 0x07:
case 0x08:
case 0x10:
case 0x12:
numdays = 0x31;
break;
case 0x04:
case 0x06:
case 0x09:
case 0x11:
numdays = 0x30;
break;
case 0x02:
{
numdays = 0x28;
int year = RtcDateTime[0];
year = (year & 0xF) + ((year >> 4) * 10);
if ( (year & 3) == 0 )
numdays = 0x29;
}
break;
default:
return 0;
}
return numdays;
}
private void SetRtcIRQ( byte irq )
{
byte oldstat = RtcIRQFlag;
RtcIRQFlag |= irq;
RtcStatusReg1 |= irq;
if ( (oldstat & 0x30) == 0 && (RtcIRQFlag & 0x30) != 0 )
{
if ( (RCnt & 0xC100) == 0x8100 )
SetIRQ( 1, IRQ.Rtc );
}
}
private void ClearRtcIRQ( byte irq )
{
RtcIRQFlag = (byte)(RtcIRQFlag & ~irq);
}
private void ProcessRtcIRQ( int type )
{
switch ( RtcStatusReg2 & 0x0F )
{
case 0x00:
if ( type == 2 )
ClearRtcIRQ( 0x10 );
break;
case 0x01:
case 0x05:
if ( (type == 1 && (RtcClockCount & 0x3FF) == 0) || type == 2 )
{
uint mask = 0;
if ( (RtcAlarm1[2] & (1 << 0)) != 0 ) mask |= 0x4000;
if ( (RtcAlarm1[2] & (1 << 1)) != 0 ) mask |= 0x2000;
if ( (RtcAlarm1[2] & (1 << 2)) != 0 ) mask |= 0x1000;
if ( (RtcAlarm1[2] & (1 << 3)) != 0 ) mask |= 0x0800;
if ( (RtcAlarm1[2] & (1 << 4)) != 0 ) mask |= 0x0400;
if ( mask != 0 && (RtcClockCount & mask) != mask )
SetRtcIRQ( 0x10 );
else
ClearRtcIRQ( 0x10 );
}
break;
case 0x02:
case 0x06:
if ( type == 0 || (type == 2 && (RtcIRQFlag & 0x01) != 0) )
SetRtcIRQ( 0x10 );
break;
case 0x03:
if ( type == 0 || (type == 2 && (RtcIRQFlag & 0x01) != 0) )
SetRtcIRQ( 0x10 );
else if ( type == 1 && RtcDateTime[6] == 0x30 && (RtcClockCount & 0x7FFF) == 0 )
ClearRtcIRQ( 0x10 );
break;
case 0x07:
if ( type == 0 || (type == 2 && (RtcIRQFlag & 0x01) != 0) )
SetRtcIRQ( 0x10 );
else if ( type == 1 && RtcDateTime[6] == 0x00 && (RtcClockCount & 0x7FFF) == 256 )
ClearRtcIRQ( 0x10 );
break;
case 0x04:
if ( type == 0 )
{
bool cond = true;
if ( (RtcAlarm1[0] & (1 << 7)) != 0 )
cond = cond && ((RtcAlarm1[0] & 0x07) == RtcDateTime[3]);
if ( (RtcAlarm1[1] & (1 << 7)) != 0 )
cond = cond && ((RtcAlarm1[1] & 0x7F) == RtcDateTime[4]);
if ( (RtcAlarm1[2] & (1 << 7)) != 0 )
cond = cond && ((RtcAlarm1[2] & 0x7F) == RtcDateTime[5]);
if ( cond )
SetRtcIRQ( 0x10 );
else
ClearRtcIRQ( 0x10 );
}
break;
default:
if ( type == 1 )
{
SetRtcIRQ( 0x10 );
ClearRtcIRQ( 0x10 );
}
break;
}
if ( (RtcStatusReg2 & (1 << 6)) != 0 )
{
if ( type == 0 )
{
bool cond = true;
if ( (RtcAlarm2[0] & (1 << 7)) != 0 )
cond = cond && ((RtcAlarm2[0] & 0x07) == RtcDateTime[3]);
if ( (RtcAlarm2[1] & (1 << 7)) != 0 )
cond = cond && ((RtcAlarm2[1] & 0x7F) == RtcDateTime[4]);
if ( (RtcAlarm2[2] & (1 << 7)) != 0 )
cond = cond && ((RtcAlarm2[2] & 0x7F) == RtcDateTime[5]);
if ( cond )
SetRtcIRQ( 0x20 );
else
ClearRtcIRQ( 0x20 );
}
}
else
{
if ( type == 2 )
ClearRtcIRQ( 0x20 );
}
}
private static byte BCDSanitize( byte val, byte min, byte max )
{
if ( val > max ) val = min;
else if ( (val & 0x0F) >= 0x0A ) val = (byte)((val & 0xF0) + 0x10);
return val;
}
private void RtcWriteDateTime( int num, byte val )
{
switch ( num )
{
case 1: RtcDateTime[0] = BCDSanitize( val, 0x00, 0x99 ); break;
case 2: RtcDateTime[1] = BCDSanitize( (byte)(val & 0x1F), 0x01, 0x12 ); break;
case 3: RtcDateTime[2] = BCDSanitize( (byte)(val & 0x3F), 0x01, 0x31 ); break;
case 4: RtcDateTime[3] = BCDSanitize( (byte)(val & 0x07), 0x00, 0x06 ); break;
case 5:
{
byte hour = (byte)(val & 0x3F);
byte pm = (byte)(val & 0x40);
if ( (RtcStatusReg1 & (1 << 1)) != 0 )
{
hour = BCDSanitize( hour, 0x00, 0x23 );
pm = (byte)(hour >= 0x12 ? 0x40 : 0);
}
else
{
hour = BCDSanitize( hour, 0x00, 0x11 );
}
RtcDateTime[4] = (byte)(hour | pm);
}
break;
case 6: RtcDateTime[5] = BCDSanitize( (byte)(val & 0x7F), 0x00, 0x59 ); break;
case 7: RtcDateTime[6] = BCDSanitize( (byte)(val & 0x7F), 0x00, 0x59 ); break;
}
}
private void RtcCmdRead()
{
if ( (RtcCurCmd & 0x0F) == 0x06 )
{
switch ( RtcCurCmd & 0x70 )
{
case 0x00:
RtcOutput[0] = RtcStatusReg1;
RtcStatusReg1 &= 0x0F;
break;
case 0x40:
RtcOutput[0] = RtcStatusReg2;
break;
case 0x20:
Array.Copy( RtcDateTime, 0, RtcOutput, 0, 7 );
break;
case 0x60:
Array.Copy( RtcDateTime, 4, RtcOutput, 0, 3 );
break;
case 0x10:
if ( (RtcStatusReg2 & 0x04) != 0 )
Array.Copy( RtcAlarm1, 0, RtcOutput, 0, 3 );
else
RtcOutput[0] = RtcAlarm1[2];
break;
case 0x50:
Array.Copy( RtcAlarm2, 0, RtcOutput, 0, 3 );
break;
case 0x30: RtcOutput[0] = RtcClockAdjust; break;
case 0x70: RtcOutput[0] = RtcFreeReg; break;
}
}
}
private void RtcCmdWrite( byte val )
{
if ( (RtcCurCmd & 0x0F) != 0x06 )
return;
switch ( RtcCurCmd & 0x70 )
{
case 0x00:
if ( RtcInputPos == 1 )
RtcStatusReg1 = (byte)((RtcStatusReg1 & 0xF0) | (val & 0x0E));
break;
case 0x40:
if ( RtcInputPos == 1 )
{
RtcStatusReg2 = val;
ProcessRtcIRQ( 2 );
}
break;
case 0x20:
if ( RtcInputPos <= 7 )
RtcWriteDateTime( RtcInputPos, val );
break;
case 0x60:
if ( RtcInputPos <= 3 )
RtcWriteDateTime( RtcInputPos + 4, val );
break;
case 0x10:
if ( (RtcStatusReg2 & 0x04) != 0 )
{
if ( RtcInputPos <= 3 ) RtcAlarm1[RtcInputPos - 1] = val;
}
else
{
if ( RtcInputPos == 1 ) RtcAlarm1[2] = val;
}
break;
case 0x50:
if ( RtcInputPos <= 3 ) RtcAlarm2[RtcInputPos - 1] = val;
break;
case 0x30:
if ( RtcInputPos == 1 ) RtcClockAdjust = val;
break;
case 0x70:
if ( RtcInputPos == 1 ) RtcFreeReg = val;
break;
}
}
private void RtcByteIn( byte val )
{
if ( RtcInputPos == 0 )
{
if ( (val & 0xF0) == 0x60 )
{
byte[] rev = [0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6];
RtcCurCmd = rev[val & 0xF];
}
else
{
RtcCurCmd = val;
}
if ( (RtcCurCmd & 0x80) != 0 )
RtcCmdRead();
return;
}
RtcCmdWrite( val );
}
public ushort RtcRead() => RtcIO;
public void RtcWrite( ushort val, bool isByte )
{
if ( isByte ) val |= (ushort)(RtcIO & 0xFF00);
if ( (val & 0x0004) != 0 )
{
if ( (RtcIO & 0x0004) == 0 )
{
RtcInput = 0;
RtcInputBit = 0;
RtcInputPos = 0;
Array.Clear( RtcOutput );
RtcOutputBit = 0;
RtcOutputPos = 0;
}
else
{
if ( (val & 0x0002) == 0 )
{
if ( (val & 0x0010) != 0 )
{
if ( (val & 0x0001) != 0 )
RtcInput |= (byte)(1 << RtcInputBit);
RtcInputBit++;
if ( RtcInputBit >= 8 )
{
RtcInputBit = 0;
RtcByteIn( RtcInput );
RtcInput = 0;
RtcInputPos++;
}
}
else
{
if ( (RtcOutput[RtcOutputPos] & (1 << RtcOutputBit)) != 0 )
RtcIO |= 0x0001;
else
RtcIO &= 0xFFFE;
RtcOutputBit++;
if ( RtcOutputBit >= 8 )
{
RtcOutputBit = 0;
if ( RtcOutputPos < 7 )
RtcOutputPos++;
}
}
}
}
}
if ( (val & 0x0010) != 0 )
RtcIO = val;
else
RtcIO = (ushort)((RtcIO & 0x0001) | (val & 0xFFFE));
}
}