1 // 2 // Copyright (c) 2010-2023 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using Antmicro.Renode.Core; 8 using Antmicro.Renode.Core.Structure.Registers; 9 using Antmicro.Renode.Exceptions; 10 using Antmicro.Renode.Logging; 11 using Antmicro.Renode.Peripherals.Bus; 12 using Antmicro.Renode.Time; 13 using Antmicro.Renode.Utilities; 14 using System; 15 16 namespace Antmicro.Renode.Peripherals.Timers 17 { 18 [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] 19 public class AmbiqApollo4_RTC : BasicDoubleWordPeripheral, IKnownSize 20 { AmbiqApollo4_RTC(IMachine machine)21 public AmbiqApollo4_RTC(IMachine machine) : base(machine) 22 { 23 IRQ = new GPIO(); 24 machine.RealTimeClockModeChanged += _ => SetDateTimeFromMachine(); 25 26 var baseDateTime = Misc.UnixEpoch; 27 internalTimer = new RTCTimer(machine, this, baseDateTime, alarmAction: () => InterruptStatus = true); 28 29 DefineRegisters(); 30 Reset(); 31 } 32 ReadDoubleWord(long offset)33 public override uint ReadDoubleWord(long offset) 34 { 35 if(offset == (long)Registers.CountersLower || offset == (long)Registers.CountersUpper) 36 { 37 // Cannot be done in read callback because field values are established first. 38 UpdateCounterFields(); 39 } 40 41 return base.ReadDoubleWord(offset); 42 } 43 Reset()44 public override void Reset() 45 { 46 interruptStatus = false; 47 lastUpdateTimerValue = ulong.MaxValue; 48 writeBusy = false; 49 valueReadWithCountersLower = 0; 50 51 InitializeBCDValueFields(); 52 IRQ.Unset(); 53 internalTimer.Reset(); 54 55 base.Reset(); 56 57 SetDateTimeFromMachine(); 58 } 59 WriteDoubleWord(long offset, uint value)60 public override void WriteDoubleWord(long offset, uint value) 61 { 62 if(!counterWritesEnabled.Value && 63 (offset == (long)Registers.CountersLower || offset == (long)Registers.CountersUpper)) 64 { 65 this.Log(LogLevel.Warning, "The {0} register ({1}) cannot be written to; WRTC isn't set!", (Registers)offset, offset); 66 return; 67 } 68 69 base.WriteDoubleWord(offset, value); 70 } 71 PrintNextAlarmDateTime()72 public string PrintNextAlarmDateTime() 73 { 74 return internalTimer.IsAlarmSet() ? internalTimer.GetNextAlarmDateTime().ToString("o") : "Alarm not set."; 75 } 76 PrintPreciseCurrentDateTime()77 public string PrintPreciseCurrentDateTime() 78 { 79 return CurrentDateTime.ToString("o"); 80 } 81 SetDateTime(int? year = null, int? month = null, int? day = null, int? hours = null, int? minutes = null, int? seconds = null, int? secondHundredths = null)82 public void SetDateTime(int? year = null, int? month = null, int? day = null, int? hours = null, int? minutes = null, int? seconds = null, int? secondHundredths = null) 83 { 84 UpdateCounterFields(); 85 86 if(year == null) 87 { 88 year = CalculateYear(centuryBit.Value, yearsOfCentury); 89 } 90 else 91 { 92 // The 200 years range simply makes it possible to tell a specific year from the century bit and a two-digit year. 93 if(year < 1970 || year > 2169) 94 { 95 throw new RecoverableException("Year has to be in range: 1970 .. 2169."); 96 } 97 } 98 99 try 100 { 101 SetDateTimeInternal(new DateTime( 102 year.Value, 103 month ?? this.month, 104 day ?? this.day, 105 hours ?? this.hours, 106 minutes ?? this.minutes, 107 seconds ?? this.seconds, 108 (secondHundredths ?? this.secondHundredths) * 10)); 109 } 110 catch(ArgumentOutOfRangeException) 111 { 112 throw new RecoverableException("Provided date or time is invalid."); 113 } 114 } 115 SetDateTimeFromMachine()116 public void SetDateTimeFromMachine() 117 { 118 // Normally the warning is logged if the millisecond value isn't a multiple of 10 since that's an RTC 119 // precision. It doesn't make sense to log such a warning for the value taken from the machine. 120 SetDateTimeInternal(machine.RealTimeClockDateTime, hushPrecisionWarning: true); 121 } 122 123 public DateTime CurrentDateTime => internalTimer.GetCurrentDateTime(); 124 125 public GPIO IRQ { get; } 126 127 public long Size => 0x210; 128 CalculateYear(bool centuryBit, int yearsOfCentury)129 static private int CalculateYear(bool centuryBit, int yearsOfCentury) 130 { 131 // The century bit set indicates "1900s/2100s" according to the documentation. 132 // To make it specific, the range supported has to be 200 years. The arbitrarily chosen range is 1970-2169 133 // because of the default Machine's RTC Mode, 'Epoch', which starts in 1970. Hence the century bit set 134 // translates to 1900 only if a two-digit year number ('yearsOfCentury') is >= 70 and to 2100 otherwise. 135 if(centuryBit) 136 { 137 return (yearsOfCentury < 70 ? 2100 : 1900) + yearsOfCentury; 138 } 139 else 140 { 141 return 2000 + yearsOfCentury; 142 } 143 } 144 DefineRegisters()145 private void DefineRegisters() 146 { 147 Registers.AlarmsLower.Define(this) 148 .WithValueField(0, 8, name: "ALM100", writeCallback: (_, newValue) => alarmSecondHundredths.BCDSet((byte)newValue), valueProviderCallback: _ => alarmSecondHundredths.BCDGet()) 149 .WithValueField(8, 7, name: "ALMSEC", writeCallback: (_, newValue) => alarmSeconds.BCDSet((byte)newValue), valueProviderCallback: _ => alarmSeconds.BCDGet()) 150 .WithReservedBits(15, 1) 151 .WithValueField(16, 7, name: "ALMMIN", writeCallback: (_, newValue) => alarmMinutes.BCDSet((byte)newValue), valueProviderCallback: _ => alarmMinutes.BCDGet()) 152 .WithReservedBits(23, 1) 153 .WithValueField(24, 6, name: "ALMHR", writeCallback: (_, newValue) => alarmHours.BCDSet((byte)newValue), valueProviderCallback: _ => alarmHours.BCDGet()) 154 .WithReservedBits(30, 2) 155 .WithChangeCallback((_, __) => UpdateAlarm()) 156 ; 157 158 Registers.AlarmsUpper.Define(this) 159 .WithValueField(0, 6, name: "ALMDATE", writeCallback: (_, newValue) => alarmDay.BCDSet((byte)newValue), valueProviderCallback: _ => alarmDay.BCDGet()) 160 .WithReservedBits(6, 2) 161 .WithValueField(8, 5, name: "ALMMO", writeCallback: (_, newValue) => alarmMonth.BCDSet((byte)newValue), valueProviderCallback: _ => alarmMonth.BCDGet()) 162 .WithReservedBits(13, 3) 163 .WithValueField(16, 3, name: "ALMWKDY", writeCallback: (_, newValue) => alarmWeekday.BCDSet((byte)newValue), valueProviderCallback: _ => alarmWeekday.BCDGet()) 164 .WithReservedBits(19, 13) 165 .WithChangeCallback((_, __) => UpdateAlarm()) 166 ; 167 168 Registers.Control.Define(this) 169 .WithFlag(0, out counterWritesEnabled, name: "WRTC") 170 .WithEnumField(1, 3, out alarmRepeatInterval, name: "RPT", changeCallback: (_, __) => UpdateAlarm()) 171 .WithFlag(4, name: "RSTOP", writeCallback: (_, newValue) => { internalTimer.Enabled = !newValue; }, valueProviderCallback: _ => !internalTimer.Enabled) 172 .WithReservedBits(5, 27) 173 ; 174 175 Registers.CountersLower.Define(this) 176 .WithValueField(0, 8, name: "CTR100", writeCallback: (_, newValue) => secondHundredths.BCDSet((byte)newValue), valueProviderCallback: _ => secondHundredths.BCDGet()) 177 .WithValueField(8, 7, name: "CTRSEC", writeCallback: (_, newValue) => seconds.BCDSet((byte)newValue), valueProviderCallback: _ => seconds.BCDGet()) 178 .WithReservedBits(15, 1) 179 .WithValueField(16, 7, name: "CTRMIN", writeCallback: (_, newValue) => minutes.BCDSet((byte)newValue), valueProviderCallback: _ => minutes.BCDGet()) 180 .WithReservedBits(23, 1) 181 .WithValueField(24, 6, name: "CTRHR", writeCallback: (_, newValue) => hours.BCDSet((byte)newValue), valueProviderCallback: _ => hours.BCDGet()) 182 .WithReservedBits(30, 2) 183 .WithReadCallback((_, __) => { valueReadWithCountersLower = internalTimer.Value; readError.Value = false; }) 184 .WithWriteCallback((_, __) => writeBusy = true) 185 ; 186 187 Registers.CountersUpper.Define(this) 188 .WithValueField(0, 6, name: "CTRDATE", writeCallback: (_, newValue) => day.BCDSet((byte)newValue), valueProviderCallback: _ => day.BCDGet()) 189 .WithReservedBits(6, 2) 190 .WithValueField(8, 5, name: "CTRMO", writeCallback: (_, newValue) => month.BCDSet((byte)newValue), valueProviderCallback: _ => month.BCDGet()) 191 .WithReservedBits(13, 3) 192 .WithValueField(16, 8, name: "CTRYR", writeCallback: (_, newValue) => yearsOfCentury.BCDSet((byte)newValue), valueProviderCallback: _ => yearsOfCentury.BCDGet()) 193 .WithValueField(24, 3, name: "CTRWKDY", writeCallback: (_, newValue) => weekday.BCDSet((byte)newValue), valueProviderCallback: _ => weekday.BCDGet()) 194 .WithReservedBits(27, 1) 195 // Documentation on Century Bit set: "Century is 1900s/2100s". In this model the century bit is set for years 1970-1999 and 2100-2169. 196 .WithFlag(28, out centuryBit, name: "CB") 197 .WithFlag(29, out centuryChangeEnabled, name: "CEB") 198 .WithReservedBits(30, 1) 199 .WithFlag(31, out readError, name: "CTERR") 200 .WithWriteCallback((_, __) => 201 { 202 readError.Value = valueReadWithCountersLower == internalTimer.Value; 203 if(!writeBusy) 204 { 205 this.Log(LogLevel.Warning, "The Counters Upper register written without prior write to the Counters Lower register!", Registers.CountersLower); 206 } 207 writeBusy = false; 208 209 var year = CalculateYear(centuryBit.Value, yearsOfCentury); 210 var newDateTime = new DateTime(year, month, day, hours, minutes, seconds, secondHundredths * 10); 211 212 // Check if weekday matches (Sunday is 0 for both). 213 if(weekday != (int)newDateTime.DayOfWeek) 214 { 215 this.Log(LogLevel.Warning, "Weekday given doesn't match the given date! New date's day of week: {0} ({1})", newDateTime.DayOfWeek, (int)newDateTime.DayOfWeek); 216 } 217 218 SetDateTimeInternal(newDateTime); 219 }) 220 ; 221 222 Registers.InterruptClear.Define(this) 223 .WithFlag(0, FieldMode.Write, name: "ALM", writeCallback: (_, newValue) => { if(newValue) InterruptStatus = false; }) 224 .WithReservedBits(1, 31) 225 ; 226 227 Registers.InterruptEnable.Define(this) 228 .WithFlag(0, out interruptEnable, name: "ALM", changeCallback: (_, __) => UpdateInterrupt()) 229 .WithReservedBits(1, 31) 230 ; 231 232 Registers.InterruptSet.Define(this) 233 .WithFlag(0, FieldMode.Write, name: "ALM", writeCallback: (_, newValue) => { if(newValue) InterruptStatus = true; }) 234 .WithReservedBits(1, 31) 235 ; 236 237 Registers.InterruptStatus.Define(this) 238 .WithFlag(0, FieldMode.Read, name: "ALM", valueProviderCallback: _ => InterruptStatus) 239 .WithReservedBits(1, 31) 240 ; 241 242 Registers.Status.Define(this) 243 .WithFlag(0, FieldMode.Read, name: "WRITEBUSY", valueProviderCallback: _ => writeBusy) 244 .WithReservedBits(1, 31) 245 ; 246 } 247 InitializeBCDValueFields()248 private void InitializeBCDValueFields() 249 { 250 alarmDay = new BCDValueField(this, "days", 0x31, zeroAllowed: false); 251 alarmHours = new BCDValueField(this, "hours", 0x23); 252 alarmMinutes = new BCDValueField(this, "minutes", 0x59); 253 alarmMonth = new BCDValueField(this, "months", 0x12, zeroAllowed: false); 254 alarmSeconds = new BCDValueField(this, "seconds", 0x59); 255 alarmSecondHundredths = new BCDValueField(this, "hundredths of a second"); 256 alarmWeekday = new BCDValueField(this, "weekdays", 0x6); 257 day = new BCDValueField(this, "days", 0x31, zeroAllowed: false); 258 hours = new BCDValueField(this, "hours", 0x23); 259 minutes = new BCDValueField(this, "minutes", 0x59); 260 month = new BCDValueField(this, "months", 0x12, zeroAllowed: false); 261 secondHundredths = new BCDValueField(this, "hundredths of a second"); 262 seconds = new BCDValueField(this, "seconds", 0x59); 263 weekday = new BCDValueField(this, "weekdays", 0x6); 264 yearsOfCentury = new BCDValueField(this, "years of a century"); 265 } 266 SetDateTimeInternal(DateTime dateTime, bool hushPrecisionWarning = false)267 private void SetDateTimeInternal(DateTime dateTime, bool hushPrecisionWarning = false) 268 { 269 internalTimer.SetDateTime(dateTime, hushPrecisionWarning); 270 271 // All the other registers will be updated before reading any of the Counters registers 272 // but the century bit might not get updated if the centuryChangeEnabled is false. 273 UpdateCenturyBit(dateTime.Year); 274 275 UpdateAlarm(); 276 } 277 UpdateAlarm()278 private void UpdateAlarm() 279 { 280 internalTimer.UpdateAlarm(alarmRepeatInterval.Value, alarmMonth, alarmWeekday, alarmDay, alarmHours, alarmMinutes, alarmSeconds, alarmSecondHundredths * 10); 281 } 282 UpdateCenturyBit(int year)283 private void UpdateCenturyBit(int year) 284 { 285 centuryBit.Value = year < 2000 || year >= 2100; 286 } 287 UpdateCounterFields()288 private void UpdateCounterFields() 289 { 290 if(lastUpdateTimerValue == internalTimer.Value) 291 { 292 return; 293 } 294 295 var dateTime = internalTimer.GetCurrentDateTime(); 296 297 secondHundredths.SetFromInteger((int)Math.Round(dateTime.Millisecond / 10.0, 0)); 298 seconds.SetFromInteger(dateTime.Second); 299 minutes.SetFromInteger(dateTime.Minute); 300 hours.SetFromInteger(dateTime.Hour); 301 day.SetFromInteger(dateTime.Day); 302 month.SetFromInteger(dateTime.Month); 303 yearsOfCentury.SetFromInteger(dateTime.Year % 100); 304 weekday.SetFromInteger((int)dateTime.DayOfWeek); 305 if(centuryChangeEnabled.Value) 306 { 307 UpdateCenturyBit(dateTime.Year); 308 } 309 310 lastUpdateTimerValue = internalTimer.Value; 311 } 312 UpdateInterrupt()313 private void UpdateInterrupt() 314 { 315 var newIrqState = interruptEnable.Value && interruptStatus; 316 if(newIrqState != IRQ.IsSet) 317 { 318 this.Log(LogLevel.Debug, "IRQ {0}", newIrqState ? "set" : "reset"); 319 IRQ.Set(newIrqState); 320 } 321 } 322 323 private bool InterruptStatus 324 { 325 get => interruptStatus; 326 set 327 { 328 interruptStatus = value; 329 UpdateInterrupt(); 330 } 331 } 332 333 private readonly RTCTimer internalTimer; 334 335 private bool interruptStatus; 336 private ulong lastUpdateTimerValue; 337 private bool writeBusy; 338 private ulong valueReadWithCountersLower; 339 340 private BCDValueField alarmDay; 341 private BCDValueField alarmHours; 342 private BCDValueField alarmMinutes; 343 private BCDValueField alarmMonth; 344 private BCDValueField alarmSeconds; 345 private BCDValueField alarmSecondHundredths; 346 private BCDValueField alarmWeekday; 347 private BCDValueField day; 348 private BCDValueField hours; 349 private BCDValueField minutes; 350 private BCDValueField month; 351 private BCDValueField secondHundredths; // 0.01s 352 private BCDValueField seconds; 353 private BCDValueField weekday; 354 private BCDValueField yearsOfCentury; 355 356 private IEnumRegisterField<AlarmRepeatIntervals> alarmRepeatInterval; 357 private IFlagRegisterField centuryBit; 358 private IFlagRegisterField centuryChangeEnabled; 359 private IFlagRegisterField counterWritesEnabled; 360 private IFlagRegisterField interruptEnable; 361 private IFlagRegisterField readError; 362 363 private class BCDValueField 364 { operator int(BCDValueField field)365 static public implicit operator int(BCDValueField field) 366 { 367 return field.GetInteger(); 368 } 369 BCDValueField(IPeripheral owner, string fieldTypeName, byte maxValueBCD = 0x99, bool zeroAllowed = true)370 public BCDValueField(IPeripheral owner, string fieldTypeName, byte maxValueBCD = 0x99, bool zeroAllowed = true) 371 { 372 this.fieldTypeName = fieldTypeName; 373 this.maxValueBCD = maxValueBCD; 374 this.owner = owner; 375 this.zeroAllowed = zeroAllowed; 376 } 377 BCDGet()378 public byte BCDGet() 379 { 380 return value; 381 } 382 BCDSet(byte bcdValue)383 public void BCDSet(byte bcdValue) 384 { 385 if(bcdValue > maxValueBCD || (!zeroAllowed && bcdValue == 0x0)) 386 { 387 owner.Log(LogLevel.Warning, "Invalid value for {0}: {1:X}", fieldTypeName, bcdValue); 388 return; 389 } 390 391 value = bcdValue; 392 } 393 GetInteger()394 public int GetInteger() 395 { 396 return BCDHelper.DecodeFromBCD(value); 397 } 398 SetFromInteger(int value)399 public void SetFromInteger(int value) 400 { 401 BCDSet(BCDHelper.EncodeToBCD((byte)value)); 402 } 403 ToString()404 public override string ToString() 405 { 406 // BCD value printed in hex is always equal to its decimal value. 407 return $"{value:X}"; 408 } 409 410 private readonly string fieldTypeName; 411 private readonly byte maxValueBCD; 412 private readonly IPeripheral owner; 413 private readonly bool zeroAllowed; 414 415 private byte value; 416 } 417 418 private class RTCTimer : LimitTimer 419 { RTCTimer(IMachine machine, IBusPeripheral owner, DateTime baseDateTime, Action alarmAction)420 public RTCTimer(IMachine machine, IBusPeripheral owner, DateTime baseDateTime, Action alarmAction) : base(machine.ClockSource, Frequency, owner, "RTC", 421 limit: ulong.MaxValue, direction: Direction.Ascending, enabled: true, workMode: WorkMode.Periodic, eventEnabled: true) 422 { 423 this.alarmAction = alarmAction; 424 baseDateTimeTicks = baseDateTime.Ticks; 425 this.owner = owner; 426 systemBus = machine.GetSystemBus(owner); 427 428 // It can be reached only after setting up the alarm. 429 LimitReached += AlarmHandler; 430 } 431 GetCurrentDateTime()432 public DateTime GetCurrentDateTime() 433 { 434 return ValueToDateTime(Value); 435 } 436 GetNextAlarmDateTime()437 public DateTime GetNextAlarmDateTime() 438 { 439 return ValueToDateTime(nextAlarmValue); 440 } 441 IsAlarmSet()442 public bool IsAlarmSet() 443 { 444 return Limit != ulong.MaxValue; 445 } 446 Reset()447 public override void Reset() 448 { 449 ResetAlarm(); 450 base.Reset(); 451 } 452 SetDateTime(DateTime newDateTime, bool hushPrecisionWarning = false)453 public void SetDateTime(DateTime newDateTime, bool hushPrecisionWarning = false) 454 { 455 ResetAlarm(); 456 Value = ValueFromDateTime(newDateTime, hushPrecisionWarning); 457 458 // The format is the same as 'o' but with only first two millisecond digits. 459 // Further digits, if nonzero, were ignored setting RTC's value so let's not print them. 460 owner.Log(LogLevel.Info, "New date time set: {0:yyyy-MM-ddTHH:mm:ss.ffK}", newDateTime); 461 } 462 UpdateAlarm(AlarmRepeatIntervals interval, int month, int weekday, int day, int hour, int minute, int second, int millisecond)463 public void UpdateAlarm(AlarmRepeatIntervals interval, int month, int weekday, int day, int hour, int minute, int second, int millisecond) 464 { 465 if(interval == AlarmRepeatIntervals.Month || interval == AlarmRepeatIntervals.Year) 466 { 467 if(day == 0) 468 { 469 owner.Log(LogLevel.Warning, "Day cannot be zero for the {0} alarm repeat interval! Using 1st as an alarm day.", interval); 470 day = 1; 471 } 472 } 473 474 if(interval == AlarmRepeatIntervals.Year) 475 { 476 if(month == 0) 477 { 478 owner.Log(LogLevel.Warning, "Month cannot be zero for the {0} alarm repeat interval! Using January as an alarm month.", interval); 479 month = 1; 480 } 481 } 482 483 var currentDateTime = GetCurrentDateTime(); 484 DateTime firstAlarm, intervalDateTime; 485 switch(interval) 486 { 487 case AlarmRepeatIntervals.Second: 488 firstAlarm = new DateTime(currentDateTime.Year, currentDateTime.Month, currentDateTime.Day, 489 currentDateTime.Hour, currentDateTime.Minute, currentDateTime.Second, millisecond); 490 intervalDateTime = new DateTime().AddSeconds(1); 491 break; 492 case AlarmRepeatIntervals.Minute: 493 firstAlarm = new DateTime(currentDateTime.Year, currentDateTime.Month, currentDateTime.Day, 494 currentDateTime.Hour, currentDateTime.Minute, second, millisecond); 495 intervalDateTime = new DateTime().AddMinutes(1); 496 break; 497 case AlarmRepeatIntervals.Hour: 498 firstAlarm = new DateTime(currentDateTime.Year, currentDateTime.Month, currentDateTime.Day, 499 currentDateTime.Hour, minute, second, millisecond); 500 intervalDateTime = new DateTime().AddHours(1); 501 break; 502 case AlarmRepeatIntervals.Day: 503 firstAlarm = new DateTime(currentDateTime.Year, currentDateTime.Month, currentDateTime.Day, hour, 504 minute, second, millisecond); 505 intervalDateTime = new DateTime().AddDays(1); 506 break; 507 case AlarmRepeatIntervals.Week: 508 firstAlarm = new DateTime(currentDateTime.Year, currentDateTime.Month, currentDateTime.Day, hour, 509 minute, second, millisecond); 510 // This can take us "back in time" but we're always adjusting such a 'firstAlarm' nevertheless. 511 var daysToTheNearestAlarmWeekday = weekday - (int)firstAlarm.DayOfWeek; 512 firstAlarm = firstAlarm.AddDays(daysToTheNearestAlarmWeekday); 513 intervalDateTime = new DateTime().AddDays(7); 514 break; 515 case AlarmRepeatIntervals.Month: 516 firstAlarm = new DateTime(currentDateTime.Year, currentDateTime.Month, day, hour, minute, second, 517 millisecond); 518 intervalDateTime = new DateTime().AddMonths(1); 519 break; 520 case AlarmRepeatIntervals.Year: 521 firstAlarm = new DateTime(currentDateTime.Year, month, day, hour, minute, second, millisecond); 522 intervalDateTime = new DateTime().AddYears(1); 523 break; 524 case AlarmRepeatIntervals.Disabled: 525 ResetAlarm(); 526 return; 527 default: 528 throw new ArgumentException("Something's very wrong; this should never happen."); 529 } 530 531 // Before this adjustment 'firstAlarm' can be in the past. 532 if(firstAlarm < currentDateTime) 533 { 534 firstAlarm = firstAlarm.AddTicks(intervalDateTime.Ticks); 535 } 536 537 alarmIntervalTicks = (ulong)intervalDateTime.Ticks / TimerTickToDateTimeTicks; 538 nextAlarmValue = ValueFromDateTime(firstAlarm); 539 Limit = nextAlarmValue; 540 owner.Log(LogLevel.Debug, "First alarm set to: {0:o}, alarm repeat interval: {1}", firstAlarm, interval); 541 } 542 543 public new ulong Value 544 { 545 get 546 { 547 if(systemBus.TryGetCurrentCPU(out var cpu)) 548 { 549 // being here means we are on the CPU thread 550 cpu.SyncTime(); 551 } 552 else 553 { 554 owner.Log(LogLevel.Noisy, "Couldn't synchronize time: returned value might lack precision"); 555 } 556 return base.Value; 557 } 558 559 set => base.Value = value; 560 } 561 562 static private readonly ulong TimerTickToDateTimeTicks = (ulong)new DateTime().AddSeconds(1.0 / Frequency).Ticks; 563 AlarmHandler()564 private void AlarmHandler() 565 { 566 alarmAction(); 567 568 // Value is automatically reset when the limit is reached. 569 Value = nextAlarmValue; 570 571 nextAlarmValue += alarmIntervalTicks; 572 Limit = nextAlarmValue; 573 574 owner.Log(LogLevel.Debug, "Alarm occurred at: {0:o}; next alarm: {1:o}", GetCurrentDateTime(), GetNextAlarmDateTime()); 575 } 576 ResetAlarm()577 private void ResetAlarm() 578 { 579 Limit = ulong.MaxValue; 580 alarmIntervalTicks = 0; 581 nextAlarmValue = 0; 582 } 583 ValueFromDateTime(DateTime dateTime, bool hushPrecisionWarning = false)584 private ulong ValueFromDateTime(DateTime dateTime, bool hushPrecisionWarning = false) 585 { 586 var newDateTimeTicks = (ulong)(dateTime.Ticks - baseDateTimeTicks); 587 if(!hushPrecisionWarning && (newDateTimeTicks % TimerTickToDateTimeTicks != 0)) 588 { 589 owner.Log(LogLevel.Warning, "Requested time for RTC is more precise than it supports (0.01s): {0:o}", dateTime); 590 } 591 return newDateTimeTicks / TimerTickToDateTimeTicks; 592 } 593 ValueToDateTime(ulong value)594 private DateTime ValueToDateTime(ulong value) 595 { 596 var dateTimeTicksPassed = value * TimerTickToDateTimeTicks; 597 return new DateTime(baseDateTimeTicks + (long)dateTimeTicksPassed); 598 } 599 600 private readonly Action alarmAction; 601 private readonly long baseDateTimeTicks; 602 private readonly IPeripheral owner; 603 private readonly IBusController systemBus; 604 605 private ulong alarmIntervalTicks; 606 private ulong nextAlarmValue; 607 608 private new const long Frequency = 100; 609 } 610 611 private enum AlarmRepeatIntervals 612 { 613 Disabled, 614 Year, 615 Month, 616 Week, 617 Day, 618 Hour, 619 Minute, 620 // Docs: "Interrupt every second/10th/100th". 621 Second, 622 } 623 624 private enum Registers : long 625 { 626 Control = 0x0, 627 Status = 0x4, 628 CountersLower = 0x20, 629 CountersUpper = 0x24, 630 AlarmsLower = 0x30, 631 AlarmsUpper = 0x34, 632 InterruptEnable = 0x200, 633 InterruptStatus = 0x204, 634 InterruptClear = 0x208, 635 InterruptSet = 0x20C, 636 } 637 } 638 } 639