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