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 8 using System; 9 10 using Antmicro.Renode.Core; 11 using Antmicro.Renode.Core.Structure.Registers; 12 using Antmicro.Renode.Logging; 13 using Antmicro.Renode.Utilities; 14 using Antmicro.Renode.Time; 15 16 namespace Antmicro.Renode.Peripherals.Timers 17 { 18 public class MAX32650_RTC : BasicDoubleWordPeripheral, IKnownSize 19 { MAX32650_RTC(IMachine machine, bool subSecondsMSBOverwrite = false, string baseDateTime = null, bool secondsTickOnOneSubSecond = false)20 public MAX32650_RTC(IMachine machine, bool subSecondsMSBOverwrite = false, string baseDateTime = null, bool secondsTickOnOneSubSecond = false) : base(machine) 21 { 22 BaseDateTime = Misc.UnixEpoch; 23 if(baseDateTime != null) 24 { 25 if(!DateTime.TryParse(baseDateTime, out var parsedBaseDateTime)) 26 { 27 throw new Exceptions.ConstructionException($"Invalid 'baseDateTime': {baseDateTime}"); 28 } 29 BaseDateTime = parsedBaseDateTime; 30 } 31 machine.RealTimeClockModeChanged += _ => SetDateTimeFromMachine(); 32 33 DefineRegisters(); 34 35 internalTimer = new LimitTimer(machine.ClockSource, SubSecondCounterResolution, this, "timer", limit: SubSecondCounterResolution, direction: Direction.Ascending, enabled: false, eventEnabled: true); 36 internalTimer.LimitReached += SecondTick; 37 subSecondAlarmTimer = new LimitTimer(machine.ClockSource, SubSecondCounterResolution, this, "ss_alarm", limit: SubSecondAlarmMaxValue, direction: Direction.Ascending, enabled: false, eventEnabled: true); 38 subSecondAlarmTimer.LimitReached += SubSecondAlarm; 39 40 this.subSecondsMSBOverwrite = subSecondsMSBOverwrite; 41 this.secondsTickOnOneSubSecond = secondsTickOnOneSubSecond; 42 43 IRQ = new GPIO(); 44 45 Reset(); 46 } 47 Reset()48 public override void Reset() 49 { 50 base.Reset(); 51 IRQ.Unset(); 52 53 internalTimer.Reset(); 54 subSecondAlarmTimer.Reset(); 55 56 secondsCounter = 0; 57 secondsCounterCache = 0; 58 subSecondsCounterCache = 0; 59 secondsCounterReadFlag = false; 60 subSecondsCounterReadFlag = false; 61 62 63 SetDateTimeFromMachine(hushLog: true); 64 } 65 PrintPreciseCurrentDateTime()66 public string PrintPreciseCurrentDateTime() 67 { 68 return CurrentDateTime.ToString("o"); 69 } 70 SetDateTime(int? year = null, int? month = null, int? day = null, int? hour = null, int? minute = null, int? second = null, double? millisecond = null)71 public void SetDateTime(int? year = null, int? month = null, int? day = null, int? hour = null, int? minute = null, int? second = null, double? millisecond = null) 72 { 73 SetDateTime(CurrentDateTime.With(year, month, day, hour, minute, second, millisecond)); 74 } 75 76 public DateTime BaseDateTime { get; } 77 78 public DateTime CurrentDateTime => BaseDateTime + TimePassedSinceBaseDateTime; 79 80 public GPIO IRQ { get; } 81 82 public long Size => 0x400; 83 84 public byte SubSecondsSignificantBits => (byte)(subSecondsCounterCache >> 8); 85 86 public TimeSpan TimePassedSinceBaseDateTime => TimeSpan.FromSeconds(CurrentSecond + ((double)CurrentSubSecond / SubSecondCounterResolution)); 87 CalculateSubSeconds(double seconds)88 private static uint CalculateSubSeconds(double seconds) 89 { 90 var subSecondFraction = seconds % 1; 91 return (uint)(subSecondFraction * SubSecondCounterResolution); 92 } 93 UpdateCounterCacheIfInvalid()94 private void UpdateCounterCacheIfInvalid() 95 { 96 // MAX32650_RTC datasheet says that RTC_SEC.sec and RTC_SSEC.ssec registers should 97 // be stable for 120us after RTC_CTRL.ready is set. 98 // As we are always setting RTC_CTRL.ready, in order to simulate 99 // this behavior, we are caching both registers when one of them is read 100 // and returning cached value when another is read. 101 // It might provide invalid result when there is intentionally large 102 // delay between registers read. 103 if(secondsCounterReadFlag && subSecondsCounterReadFlag) 104 { 105 // return cached value 106 secondsCounterReadFlag = false; 107 subSecondsCounterReadFlag = false; 108 } 109 else 110 { 111 secondsCounterCache = (uint)CurrentSecond; 112 subSecondsCounterCache = (uint)CurrentSubSecond; 113 } 114 } 115 DefineRegisters()116 private void DefineRegisters() 117 { 118 Registers.Seconds.Define(this) 119 .WithValueField(0, 32, name: "RTC_SEC.sec", 120 valueProviderCallback: _ => 121 { 122 secondsCounterReadFlag = true; 123 UpdateCounterCacheIfInvalid(); 124 return secondsCounterCache; 125 }, 126 writeCallback: (_, value) => 127 { 128 lock(countersLock) 129 { 130 secondsCounter = (uint)value; 131 UpdateCounterCacheIfInvalid(); 132 } 133 }); 134 Registers.SubSeconds.Define(this) 135 .WithValueField(0, 8, name: "RTC_SSEC.ssec", 136 valueProviderCallback: _ => 137 { 138 subSecondsCounterReadFlag = true; 139 UpdateCounterCacheIfInvalid(); 140 return (byte)subSecondsCounterCache; 141 }, 142 writeCallback: (_, value) => 143 { 144 lock(countersLock) 145 { 146 internalTimer.Value = (ulong)((subSecondsMSBOverwrite ? 0xF00 : (CurrentSubSecond & 0xF00)) | (uint)value); 147 UpdateCounterCacheIfInvalid(); 148 } 149 }) 150 .WithReservedBits(8, 24); 151 Registers.TimeOfDayAlarm.Define(this) 152 .WithValueField(0, 20, out timeOfDayAlarm, name: "RTC_TODA.tod_alarm") 153 .WithReservedBits(20, 12); 154 Registers.SubSecondAlarm.Define(this) 155 .WithValueField(0, 32, out subSecondAlarm, name: "RTC_SSECA.ssec_alarm", 156 writeCallback: (_, value) => subSecondAlarmTimer.Value = value); 157 Registers.Control.Define(this) 158 .WithFlag(0, name: "RTC_CTRL.enable", 159 valueProviderCallback: _ => internalTimer.Enabled, 160 changeCallback: (_, value) => 161 { 162 if(!canBeToggled.Value) 163 { 164 this.Log(LogLevel.Warning, "Tried to write RTC_CTRL.enable with RTC_CTRL.write_en disabled"); 165 return; 166 } 167 internalTimer.Enabled = value; 168 }) 169 .WithFlag(1, out timeOfDayAlarmEnabled, name: "RTC_CTRL.tod_alarm_en") 170 .WithFlag(2, name: "RTC_CTRL.ssec_alarm_en", 171 valueProviderCallback: _ => subSecondAlarmTimer.Enabled, 172 writeCallback: (_, value) => subSecondAlarmTimer.Enabled = value) 173 .WithFlag(3, name: "RTC_CTRL.busy", valueProviderCallback: _ => false) 174 // It seems that on real HW, semantic of the READY bit is inverted, that is 175 // when RTC_CTRL.ready is set to false, then software is able to read 176 // correct data from RTC_SEC and RTC_SSEC registers. 177 .WithFlag(4, name: "RTC_CTRL.ready", 178 valueProviderCallback: _ => false, 179 writeCallback: (_, value) => 180 { 181 // SW sets the bit to false to force registers values synchronization 182 if(value == false) 183 { 184 if(machine.SystemBus.TryGetCurrentCPU(out var cpu)) 185 { 186 cpu.SyncTime(); 187 } 188 } 189 }) 190 .WithFlag(5, out readyInterruptEnabled, name: "RTC_CTRL.ready_int_en") 191 .WithFlag(6, out timeOfDayAlarmFlag, name: "RTC_CTRL.tod_alarm_fl") 192 .WithFlag(7, out subSecondAlarmFlag, name: "RTC_CTRL.ssec_alarm_fl") 193 .WithTaggedFlag("RTC_CTRL.sqwout_en", 8) 194 .WithTag("RTC_CTRL.freq_sel", 9, 2) 195 .WithReservedBits(11, 3) 196 .WithTaggedFlag("RTC_CTRL.acre", 14) 197 .WithFlag(15, out canBeToggled, name: "RTC_CTRL.write_en") 198 .WithReservedBits(16, 16); 199 Registers.OscillatorControl.Define(this) 200 .WithReservedBits(0, 4) 201 .WithTaggedFlag("RTC_OSCCTRL.bypass", 4) 202 .WithTaggedFlag("RTC_OSCCTRL.32kout", 5) 203 .WithReservedBits(6, 26); 204 } 205 SetDateTime(DateTime dateTime, bool hushLog = false)206 private void SetDateTime(DateTime dateTime, bool hushLog = false) 207 { 208 if(dateTime < BaseDateTime) 209 { 210 this.Log(LogLevel.Warning, "Tried to set DateTime older than the RTC's BaseDateTime ({0}): {1:o}", BaseDateTime, dateTime); 211 return; 212 } 213 var sinceBaseDateTime = dateTime - BaseDateTime; 214 215 lock(countersLock) 216 { 217 secondsCounter = (uint)sinceBaseDateTime.TotalSeconds; 218 internalTimer.Value = CalculateSubSeconds(sinceBaseDateTime.TotalSeconds); 219 220 if(!hushLog) 221 { 222 this.Log(LogLevel.Info, "New date time set: {0:o}", CurrentDateTime); 223 } 224 } 225 } 226 SetDateTimeFromMachine(bool hushLog = false)227 private void SetDateTimeFromMachine(bool hushLog = false) 228 { 229 SetDateTime(machine.RealTimeClockDateTime, hushLog); 230 } 231 SecondTick()232 private void SecondTick() 233 { 234 lock(countersLock) 235 { 236 secondsCounter += 1; 237 if(timeOfDayAlarmEnabled.Value && (secondsCounter & TimeOfDayAlarmMask) == timeOfDayAlarm.Value) 238 { 239 timeOfDayAlarmFlag.Value = true; 240 } 241 242 UpdateInterrupts(); 243 } 244 } 245 SubSecondAlarm()246 private void SubSecondAlarm() 247 { 248 subSecondAlarmTimer.Value = subSecondAlarm.Value; 249 subSecondAlarmFlag.Value = true; 250 UpdateInterrupts(); 251 } 252 UpdateInterrupts()253 private void UpdateInterrupts() 254 { 255 var interruptPending = readyInterruptEnabled.Value; 256 interruptPending |= timeOfDayAlarmEnabled.Value && timeOfDayAlarmFlag.Value; 257 interruptPending |= subSecondAlarmTimer.Enabled && subSecondAlarmFlag.Value; 258 IRQ.Set(interruptPending); 259 } 260 261 private ulong CurrentSecond 262 { 263 get 264 { 265 if(machine.SystemBus.TryGetCurrentCPU(out var cpu)) 266 { 267 cpu.SyncTime(); 268 } 269 // On some revisions of the HW the seconds counter won't tick on subseconds 270 // counter overflow, but rather after first tick on value=1. This enables 271 // this behaviour when secondsTickOnOneSubSecond is set. 272 if(secondsTickOnOneSubSecond && internalTimer.Value == 0) 273 { 274 return secondsCounter > 0 ? secondsCounter - 1 : secondsCounter; 275 } 276 return secondsCounter; 277 } 278 } 279 280 private ulong CurrentSubSecond 281 { 282 get 283 { 284 if(machine.SystemBus.TryGetCurrentCPU(out var cpu)) 285 { 286 cpu.SyncTime(); 287 } 288 return internalTimer.Value; 289 } 290 } 291 292 private uint secondsCounter; 293 private uint secondsCounterCache; 294 private uint subSecondsCounterCache; 295 private bool secondsCounterReadFlag; 296 private bool subSecondsCounterReadFlag; 297 298 private IFlagRegisterField canBeToggled; 299 private IFlagRegisterField readyInterruptEnabled; 300 private IFlagRegisterField subSecondAlarmFlag; 301 private IFlagRegisterField timeOfDayAlarmEnabled; 302 private IFlagRegisterField timeOfDayAlarmFlag; 303 304 private IValueRegisterField subSecondAlarm; 305 private IValueRegisterField timeOfDayAlarm; 306 307 private readonly LimitTimer internalTimer; 308 private readonly LimitTimer subSecondAlarmTimer; 309 private readonly object countersLock = new object(); 310 private readonly bool subSecondsMSBOverwrite; 311 312 private const long SubSecondAlarmMaxValue = 0xFFFFFFFF; 313 private const uint SubSecondCounterResolution = 4096; 314 private const ulong TimeOfDayAlarmMask = 0xFFFFF; 315 // Some revisions of this peripheral have HW bug that makes 316 // RTC to increase seconds counter when subseconds are 1 instead on 0. 317 // This flag allows to enable this behavior. 318 private bool secondsTickOnOneSubSecond; 319 320 private enum Registers 321 { 322 Seconds = 0x00, 323 SubSeconds = 0x04, 324 TimeOfDayAlarm = 0x08, 325 SubSecondAlarm = 0x0C, 326 Control = 0x10, 327 OscillatorControl = 0x18, 328 } 329 } 330 } 331