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