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.Peripherals.Bus; 9 using Antmicro.Renode.Core.Structure.Registers; 10 using Antmicro.Renode.Time; 11 using System; 12 using static Antmicro.Renode.Utilities.BitHelper; 13 using Antmicro.Renode.Utilities; 14 using System.Globalization; 15 16 namespace Antmicro.Renode.Peripherals.Timers 17 { 18 public class MPFS_RTC : BasicDoubleWordPeripheral, IKnownSize 19 { MPFS_RTC(IMachine machine)20 public MPFS_RTC(IMachine machine) : base(machine) 21 { 22 DefineRegisters(); 23 WakeupIRQ = new GPIO(); 24 MatchIRQ = new GPIO(); 25 26 ticker = new LimitTimer(machine.ClockSource, 1, this, nameof(ticker), 1, Direction.Ascending, eventEnabled: true); 27 ticker.LimitReached += HandleTick; 28 ResetInnerTimer(); 29 } 30 Reset()31 public override void Reset() 32 { 33 base.Reset(); 34 WakeupIRQ.Set(false); 35 MatchIRQ.Set(false); 36 ResetInnerTimer(); 37 ticker.Reset(); 38 } 39 40 public GPIO MatchIRQ { get; private set; } 41 public GPIO WakeupIRQ { get; private set; } 42 public long Size => 0x1000; 43 DefineRegisters()44 private void DefineRegisters() 45 { 46 Registers.Control.Define(this) 47 .WithFlag(0, writeCallback: (_, value) => { if(value) ticker.Enabled = true; }, valueProviderCallback: _ => ticker.Enabled, name: "Start/Running") 48 .WithFlag(1, writeCallback: (_, value) => { if(value) ticker.Enabled = false; }, valueProviderCallback: _ => ticker.Enabled, name: "Stop/Running") 49 .WithFlag(2, writeCallback: (_, value) => { if(value) alarmEnabled = true; }, valueProviderCallback: _ => alarmEnabled, name: "Alarm_on/Alarm_enabled") 50 .WithFlag(3, writeCallback: (_, value) => { if(value) alarmEnabled = false; }, valueProviderCallback: _ => alarmEnabled, name: "Alarm_off/Alarm_enabled") 51 .WithFlag(4, FieldMode.Set, writeCallback: (_, value) => { if(value) ResetInnerTimer(); }, name: "Reset") 52 .WithFlag(5, writeCallback: (_, value) => { if(value) currentTime = timeToUpload; }, valueProviderCallback: _ => false, name: "Upload") 53 .WithFlag(6, writeCallback: (_, value) => { if(value) { timeToUpload = currentTime; }; }, name: "Download") 54 .WithFlag(7, out match, FieldMode.Read, name: "Match") 55 .WithFlag(8, out wakeup, FieldMode.Read | FieldMode.WriteOneToClear, writeCallback: (_, value) => { if(value) WakeupIRQ.Set(false) ; }, name: "Wakeup_clear/Wakeup") 56 .WithFlag(9, FieldMode.Write, writeCallback: (_, value) => { if(value) { wakeup.Value = true; WakeupIRQ.Set(true); } }, name: "Wakeup_set") 57 .WithFlag(10, out updated, FieldMode.Read | FieldMode.WriteOneToClear, name: "Updated") 58 ; 59 60 Registers.Mode.Define(this) 61 .WithEnumField<DoubleWordRegister, ClockMode>(0, 1, out clockMode, changeCallback: (_, __) => UpdateState(), name: "clock_mode") 62 .WithFlag(1, out wakeEnable, name: "wake_enable") 63 .WithFlag(2, out wakeReset, name: "wake_reset") 64 .WithFlag(3, out wakeContinue, name: "wake_continue") 65 .WithTag("wake_reset_ps", 4, 1) // we don't support prescaler at all 66 ; 67 68 Registers.AlarmLow.Define(this) 69 .WithValueField(0, 32, out alarmLow, changeCallback: (_, __) => UpdateState(), name: "alarm Lower") 70 ; 71 72 Registers.AlarmHigh.Define(this) 73 .WithValueField(0, 32, out alarmHigh, changeCallback: (_, __) => UpdateState(), name: "alarm Upper") 74 ; 75 76 Registers.CompareLow.Define(this) 77 .WithValueField(0, 32, out compareLow, changeCallback: (_, __) => UpdateState(), name: "compare Lower") 78 ; 79 80 Registers.CompareHigh.Define(this) 81 .WithValueField(0, 32, out compareHigh, changeCallback: (_, __) => UpdateState(), name: "compare Higher") 82 ; 83 84 Registers.DateTimeLow.Define(this) 85 .WithValueField(0, 32, name: "datetime Lower", 86 valueProviderCallback: _ => 87 { 88 switch(clockMode.Value) 89 { 90 case ClockMode.BinaryCounter: 91 return (uint)CalculateElapsedSeconds(currentTime); 92 case ClockMode.DateTimeCounter: 93 return GetDateTimeAlarmCompareLower().Bits.AsUInt32(); 94 default: 95 throw new ArgumentException("Unexpected clock mode"); 96 } 97 }, 98 writeCallback: (_, value) => 99 { 100 switch(clockMode.Value) 101 { 102 case ClockMode.BinaryCounter: 103 var currentValue = CalculateElapsedSeconds(timeToUpload); 104 var newValue = BitHelper.ReplaceBits(currentValue, source: (ulong)value, width: 32); 105 timeToUpload = ResetTimeValue.AddSeconds(newValue); 106 break; 107 case ClockMode.DateTimeCounter: 108 timeToUpload = timeToUpload.With( 109 second: (int)BitHelper.GetMaskedValue(value, 0, 8), 110 minute: (int)BitHelper.GetMaskedValue(value, 8, 8), 111 hour: (int)BitHelper.GetMaskedValue(value, 16, 8), 112 day: (int)BitHelper.GetMaskedValue(value, 24, 8) 113 ); 114 break; 115 default: 116 throw new ArgumentException("Unexpected clock mode"); 117 } 118 }) 119 ; 120 121 Registers.DateTimeHigh.Define(this) 122 .WithValueField(0, 32, name: "datetime Higher", 123 valueProviderCallback: _ => 124 { 125 switch(clockMode.Value) 126 { 127 case ClockMode.BinaryCounter: 128 return (uint)(CalculateElapsedSeconds(currentTime) >> 32); 129 case ClockMode.DateTimeCounter: 130 return GetDateTimeAlarmCompareUpper().Bits.AsUInt32(); 131 default: 132 throw new ArgumentException("Unexpected clock mode"); 133 } 134 }, 135 writeCallback: (_, value) => 136 { 137 switch(clockMode.Value) 138 { 139 case ClockMode.BinaryCounter: 140 var currentValue = CalculateElapsedSeconds(timeToUpload); 141 var newValue = BitHelper.ReplaceBits(currentValue, source: value, width: 11, destinationPosition: 32); 142 timeToUpload = ResetTimeValue.AddSeconds(newValue); 143 break; 144 case ClockMode.DateTimeCounter: 145 timeToUpload = timeToUpload.With( 146 month: (int)BitHelper.GetMaskedValue(value, 0, 8), 147 year: (int)BitHelper.GetMaskedValue(value, 8, 8) 148 // WARNING 149 // ------- 150 // The rest of bits: 151 // bits 16-23: weekday, 152 // bits 24-29: week 153 // are intentionally *ignored* as those values can be inferred from day+month+year. 154 // This might lead to an inconsistency between Renode and actual hardware. 155 ); 156 break; 157 default: 158 throw new ArgumentException("Unexpected clock mode"); 159 } 160 }) 161 ; 162 163 Registers.DateTimeSynchronizedSeconds.Define(this) 164 .WithValueField(0, 6, name: "Second", 165 valueProviderCallback: _ => 166 { 167 // an excerpt from the documentation: 168 // "The complete RTC data is read and stored internally when the second value is read, 169 // reads of minutes etc returns the value when seconds was read." 170 bufferedCurrentTime = currentTime; 171 return (uint)bufferedCurrentTime.Second; 172 }, 173 writeCallback: (_, value) => timeToUpload = timeToUpload.With(second: (int)value)) 174 ; 175 176 Registers.DateTimeSynchronizedMinutes.Define(this) 177 .WithValueField(0, 6, name: "Minute", 178 valueProviderCallback: _ => (uint)bufferedCurrentTime.Minute, 179 writeCallback: (_, value) => timeToUpload = timeToUpload.With(minute: (int)value)) 180 ; 181 182 Registers.DateTimeSynchronizedHours.Define(this) 183 .WithValueField(0, 5, name: "Hour", 184 valueProviderCallback: _ => (uint)bufferedCurrentTime.Hour, 185 writeCallback: (_, value) => timeToUpload = timeToUpload.With(hour: (int)value)) 186 ; 187 188 Registers.DateTimeSynchronizedDay.Define(this) 189 .WithValueField(0, 5, name: "Day", 190 valueProviderCallback: _ => (uint)bufferedCurrentTime.Day, 191 writeCallback: (_, value) => timeToUpload = timeToUpload.With(day: (int)value)) 192 ; 193 194 Registers.DateTimeSynchronizedMonth.Define(this) 195 .WithValueField(0, 4, name: "Month", 196 valueProviderCallback: _ => (uint)bufferedCurrentTime.Month, 197 writeCallback: (_, value) => timeToUpload = timeToUpload.With(month: (int)value)) 198 ; 199 200 Registers.DateTimeSynchronizedYear.Define(this) 201 .WithValueField(0, 8, name: "Year", 202 valueProviderCallback: _ => CalculateYear(bufferedCurrentTime), 203 writeCallback: (_, value) => timeToUpload = timeToUpload.With(year: CalculateYear((uint)value))) 204 ; 205 206 Registers.DateTimeSynchronizedWeekday.Define(this) 207 .WithValueField(0, 3, valueProviderCallback: _ => CalculateWeekday(bufferedCurrentTime), name: "Weekday") 208 // WARNING 209 // ------- 210 // The write to this register intentionally *ignored* as the weekday can be inferred from day+month+year. 211 // This might lead to an inconsistency between Renode and actual hardware. 212 ; 213 214 Registers.DateTimeSynchronizedWeek.Define(this) 215 .WithValueField(0, 6, valueProviderCallback: _ => CalculateWeek(bufferedCurrentTime), name: "Week") 216 // WARNING 217 // ------- 218 // The write to this register intentionally *ignored* as the week number can be inferred from day+month+year. 219 // This might lead to an inconsistency between Renode and actual hardware. 220 ; 221 222 Registers.DateTimeSeconds.Define(this) 223 .WithValueField(0, 6, name: "Second", 224 valueProviderCallback: _ => (uint)currentTime.Second, 225 writeCallback: (_, value) => timeToUpload = timeToUpload.With(second: (int)value)) 226 ; 227 228 Registers.DateTimeMinutes.Define(this) 229 .WithValueField(0, 6, name: "Minute", 230 valueProviderCallback: _ => (uint)currentTime.Minute, 231 writeCallback: (_, value) => timeToUpload = timeToUpload.With(minute: (int)value)) 232 ; 233 234 Registers.DateTimeHours.Define(this) 235 .WithValueField(0, 5, name: "Hour", 236 valueProviderCallback: _ => (uint)currentTime.Hour, 237 writeCallback: (_, value) => timeToUpload = timeToUpload.With(hour: (int)value)) 238 ; 239 240 Registers.DateTimeDay.Define(this) 241 .WithValueField(0, 5, name: "Day", 242 valueProviderCallback: _ => (uint)currentTime.Day, 243 writeCallback: (_, value) => timeToUpload = timeToUpload.With(day: (int)value)) 244 ; 245 246 Registers.DateTimeMonth.Define(this) 247 .WithValueField(0, 4, name: "Month", 248 valueProviderCallback: _ => (uint)currentTime.Month, 249 writeCallback: (_, value) => timeToUpload = timeToUpload.With(month: (int)value)) 250 ; 251 252 Registers.DateTimeYear.Define(this) 253 // year 0 means 2000 254 .WithValueField(0, 8, name: "Year", 255 valueProviderCallback: _ => CalculateYear(currentTime), 256 writeCallback: (_, value) => timeToUpload = timeToUpload.With(year: CalculateYear((uint)value))) 257 ; 258 259 Registers.DateTimeWeekday.Define(this) 260 .WithValueField(0, 3, valueProviderCallback: _ => CalculateWeekday(currentTime), name: "Weekday") 261 // WARNING 262 // ------- 263 // The write to this register intentionally *ignored* as the weekday can be inferred from day+month+year. 264 // This might lead to an inconsistency between Renode and actual hardware. 265 ; 266 267 Registers.DateTimeWeek.Define(this) 268 .WithValueField(0, 6, valueProviderCallback: _ => CalculateWeek(currentTime), name: "Week") 269 // WARNING 270 // ------- 271 // The write to this register intentionally *ignored* as the week number can be inferred from day+month+year. 272 // This might lead to an inconsistency between Renode and actual hardware. 273 ; 274 } 275 CalculateWeek(DateTime dt)276 private uint CalculateWeek(DateTime dt) 277 { 278 // documentation says: 279 // "The day of the week counter increments from 1 to 7 and the week counter is incremented as the day of week goes from 7 to 1." 280 // "Weekday, 1: Sunday, 2: Monday …. 7:Saturday" 281 return (uint)CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(dt, CalendarWeekRule.FirstDay, DayOfWeek.Sunday); 282 } 283 CalculateYear(DateTime dt)284 private uint CalculateYear(DateTime dt) 285 { 286 // documentation says: 287 // "Reset date is Saturday 1 January 2000." 288 return (uint)(dt.Year - 2000); 289 } 290 CalculateYear(uint year)291 private int CalculateYear(uint year) 292 { 293 // documentation says: 294 // "Reset date is Saturday 1 January 2000." 295 return (int)year + 2000; 296 } 297 CalculateWeekday(DateTime dt)298 private uint CalculateWeekday(DateTime dt) 299 { 300 // documentation says: 301 // "Weekday, 1: Sunday, 2: Monday …. 7:Saturday" 302 return (uint)currentTime.DayOfWeek + 1; 303 } 304 ResetInnerTimer()305 private void ResetInnerTimer() 306 { 307 currentTime = ResetTimeValue; 308 timeToUpload = ResetTimeValue; 309 } 310 CalculateElapsedSeconds(DateTime dt)311 private ulong CalculateElapsedSeconds(DateTime dt) 312 { 313 return (ulong)(dt - ResetTimeValue).TotalSeconds; 314 } 315 GetDateTimeAlarmCompareLower(BitConcatenator source = null)316 private BitConcatenator GetDateTimeAlarmCompareLower(BitConcatenator source = null) 317 { 318 return (source ?? BitConcatenator.New()) 319 .StackAbove((uint)currentTime.Second, 8) 320 .StackAbove((uint)currentTime.Minute, 8) 321 .StackAbove((uint)currentTime.Hour, 8) 322 .StackAbove((uint)currentTime.Day, 8); 323 } 324 GetDateTimeAlarmCompareUpper(BitConcatenator source = null)325 private BitConcatenator GetDateTimeAlarmCompareUpper(BitConcatenator source = null) 326 { 327 return (source ?? BitConcatenator.New()) 328 .StackAbove((uint)currentTime.Month, 8) 329 .StackAbove(CalculateYear(currentTime), 8) 330 .StackAbove(CalculateWeekday(currentTime), 8) 331 .StackAbove(CalculateWeek(currentTime), 6); 332 } 333 HandleTick()334 private void HandleTick() 335 { 336 currentTime = currentTime.AddSeconds(1); 337 updated.Value = true; 338 UpdateState(); 339 } 340 UpdateState()341 private void UpdateState() 342 { 343 if(!alarmEnabled) 344 { 345 return; 346 } 347 348 var currentValue = (clockMode.Value == ClockMode.BinaryCounter) 349 ? CalculateElapsedSeconds(currentTime) 350 : GetDateTimeAlarmCompareUpper(GetDateTimeAlarmCompareLower()).Bits.AsUInt64(); 351 var compareMask = (compareHigh.Value << 32) | compareLow.Value; 352 var alarmValue = (alarmHigh.Value << 32) | alarmLow.Value; 353 var isAlarmMatched = (currentValue & compareMask) == (alarmValue & compareMask); 354 355 match.Value = isAlarmMatched; 356 MatchIRQ.Set(match.Value); 357 358 wakeup.Value |= isAlarmMatched; 359 if(wakeup.Value) 360 { 361 if(wakeReset.Value) 362 { 363 ResetInnerTimer(); 364 } 365 if(!wakeContinue.Value) 366 { 367 ticker.Enabled = false; 368 } 369 if(wakeEnable.Value) 370 { 371 WakeupIRQ.Set(true); 372 } 373 } 374 } 375 376 private IValueRegisterField compareLow; 377 private IValueRegisterField compareHigh; 378 private IValueRegisterField alarmLow; 379 private IValueRegisterField alarmHigh; 380 private IFlagRegisterField match; 381 private IFlagRegisterField wakeup; 382 private IFlagRegisterField wakeEnable; 383 private IFlagRegisterField wakeReset; 384 private IFlagRegisterField wakeContinue; 385 private IFlagRegisterField updated; 386 private IEnumRegisterField<ClockMode> clockMode; 387 388 private bool alarmEnabled; 389 private DateTime timeToUpload; 390 private DateTime currentTime; 391 private DateTime bufferedCurrentTime; 392 393 private readonly LimitTimer ticker; 394 395 private static readonly DateTime ResetTimeValue = new DateTime(2000, 1, 1); 396 397 private enum ClockMode 398 { 399 BinaryCounter = 0, 400 DateTimeCounter = 1 401 } 402 403 private enum Registers 404 { 405 Control = 0x0, 406 Mode = 0x4, 407 Prescaler = 0x8, 408 AlarmLow = 0xC, 409 AlarmHigh = 0x10, 410 CompareLow = 0x14, 411 CompareHigh = 0x18, 412 DateTimeLow = 0x20, 413 DateTimeHigh = 0x24, 414 DateTimeSynchronizedSeconds = 0x30, 415 DateTimeSynchronizedMinutes= 0x34, 416 DateTimeSynchronizedHours = 0x38, 417 DateTimeSynchronizedDay = 0x3C, 418 DateTimeSynchronizedMonth = 0x40, 419 DateTimeSynchronizedYear = 0x44, 420 DateTimeSynchronizedWeekday = 0x48, 421 DateTimeSynchronizedWeek = 0x4C, 422 DateTimeSeconds = 0x50, 423 DateTimeMinutes= 0x54, 424 DateTimeHours = 0x58, 425 DateTimeDay = 0x5C, 426 DateTimeMonth = 0x60, 427 DateTimeYear = 0x64, 428 DateTimeWeekday = 0x68, 429 DateTimeWeek = 0x6C, 430 } 431 } 432 } 433