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 System;
8 using System.Globalization;
9 using Antmicro.Renode.Core;
10 using Antmicro.Renode.Exceptions;
11 using Antmicro.Renode.Logging;
12 using Antmicro.Renode.Utilities;
13 using Antmicro.Renode.Peripherals.I2C;
14 using Antmicro.Renode.Core.Structure.Registers;
15 
16 namespace Antmicro.Renode.Peripherals.Timers
17 {
18     public class RV8803_RTC : II2CPeripheral, IProvidesRegisterCollection<ByteRegisterCollection>
19     {
RV8803_RTC(IMachine machine)20         public RV8803_RTC(IMachine machine)
21         {
22             IRQ = new GPIO();
23 
24             periodicTimer = new LimitTimer(machine.ClockSource, DefaultPeriodicTimerFrequency, this, "periodicTimer", limit: DefaultPeriodicTimerLimit, eventEnabled: true);
25             periodicTimer.LimitReached += () =>
26             {
27                 periodicTimerFlag.Value = true;
28                 if(periodicTimerInterruptEnable.Value)
29                 {
30                     this.Log(LogLevel.Noisy, "IRQ blink");
31                     // yes, it blinks
32                     IRQ.Blink();
33                 }
34             };
35 
36             realTimeCounter = new RTCTimer(machine, this);
37 
38             RegistersCollection = new ByteRegisterCollection(this);
39             DefineRegisters();
40         }
41 
42         public string CurrentTime
43         {
44             get => realTimeCounter.CurrentTime.ToString();
45             set
46             {
47                 const string format = "dd-MM-yyyy HH:mm:ss";
48                 if(!DateTime.TryParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt))
49                 {
50                     throw new RecoverableException($"Couldn't parse time: {value}. Provide it in the {format} format (e.g., 31-12-2022 13:22:31)");
51                 }
52                 realTimeCounter.CurrentTime = DateTimeWithCustomWeekday.FromDateTime(dt);
53                 this.Log(LogLevel.Debug, "RTC time set to {0}", dt);
54             }
55         }
56 
57         public GPIO IRQ { get; }
58 
Reset()59         public void Reset()
60         {
61             realTimeCounter.Reset();
62             periodicTimer.Reset();
63 
64             RegistersCollection.Reset();
65 
66             currentState = State.WaitingForRegister;
67             register = default(Registers);
68 
69             IRQ.Unset();
70         }
71 
Write(byte[] data)72         public void Write(byte[] data)
73         {
74             this.Log(LogLevel.Debug, "Written {0} bytes: {1}", data.Length, Misc.PrettyPrintCollectionHex(data));
75             foreach(var d in data)
76             {
77                 HandleIncomingByte(d);
78             }
79         }
80 
HandleIncomingByte(byte data)81         private void HandleIncomingByte(byte data)
82         {
83             switch(currentState)
84             {
85                 case State.WaitingForRegister:
86                     register = (Registers)data;
87                     this.Log(LogLevel.Debug, "Register set to {0}", register);
88 
89                     currentState = State.HandlingData;
90                     break;
91 
92                 case State.HandlingData:
93                     HandleWrite(data);
94                     break;
95             }
96         }
97 
HandleWrite(byte data)98         private void HandleWrite(byte data)
99         {
100             this.Log(LogLevel.Debug, "Handling write of 0x{0:X} to {1}", data, register);
101             RegistersCollection.Write((long)register, data);
102             register++;
103         }
104 
Read(int count = 1)105         public byte[] Read(int count = 1)
106         {
107             var result = new byte[count];
108             for(var i = 0; i < count; i++)
109             {
110                 result[i] = HandleRead();
111             }
112             return result;
113         }
114 
HandleRead()115         private byte HandleRead()
116         {
117             var result = RegistersCollection.Read((long)register);
118             this.Log(LogLevel.Debug, "Read value 0x{0:X} from {1}", result, register);
119             register++;
120             return result;
121         }
122 
FinishTransmission()123         public void FinishTransmission()
124         {
125             this.Log(LogLevel.Debug, "Finished the transmission");
126             currentState = State.WaitingForRegister;
127         }
128 
129         public ByteRegisterCollection RegistersCollection { get; }
130 
DefineRegisters()131         private void DefineRegisters()
132         {
133             Registers.TimerCounter0.Define(this)
134                 .WithValueField(0, 8, name: "Timer Counter 0",
135                     // When read, only the preset value is returned and not the actual value.
136                     writeCallback: (_, newValue) =>
137                     {
138                         var timerValue = periodicTimer.Value;
139                         timerValue &= 0xF00;
140                         timerValue |= newValue;
141 
142                         periodicTimer.Limit = DefaultPeriodicTimerLimit;
143                         periodicTimer.Value = timerValue;
144                         periodicTimer.Limit = timerValue;
145                     });
146 
147             Registers.TimerCounter1.Define(this)
148                 .WithValueField(0, 4, name: "Timer Counter 1",
149                     // When read, only the preset value is returned and not the actual value.
150                     writeCallback: (_, newValue) =>
151                     {
152                         var timerValue = periodicTimer.Value;
153                         timerValue &= 0x0FF;
154                         timerValue |= (newValue << 8);
155 
156                         periodicTimer.Limit = DefaultPeriodicTimerLimit;
157                         periodicTimer.Value = timerValue;
158                         periodicTimer.Limit = timerValue;
159                     })
160                 .WithFlag(4, name: "GP2 - General Purpose Bit")
161                 .WithFlag(5, name: "GP3 - General Purpose Bit")
162                 .WithFlag(6, name: "GP4 - General Purpose Bit")
163                 .WithFlag(7, name: "GP5 - General Purpose Bit");
164 
165             var extensionRegister = new ByteRegister(this)
166                 .WithTag("TD - Timer Clock Frequency", 0, 2)
167                 .WithTag("FD - CLKOUT Frequency", 2, 2)
168                 .WithFlag(4, out periodicCountdownTimerEnable, name: "TE - Periodic Countdown Timer Enable",
169                     writeCallback: (_, val) =>
170                     {
171                         UpdateTimersEnable();
172                     }
173                 )
174                 .WithTag("USEL - Update Interrupt Select", 5, 1)
175                 .WithTag("WADA - Weekday Alarm / Date Alarm Select", 6, 1)
176                 .WithTag("TEST", 7, 1);
177 
178             RegistersCollection.AddRegister((long)Registers.ExtensionRegister, extensionRegister);
179             RegistersCollection.AddRegister((long)Registers.ExtensionRegister_Extended1, extensionRegister);
180 
181             var flagRegister = new ByteRegister(this)
182                 .WithTag("V1F - Voltage Low Flag 1", 0, 1)
183                 .WithTag("V2F - Voltage Low Flag 2", 1, 1)
184                 .WithTag("EVF - External Event Flag", 2, 1)
185                 .WithTag("AF - Alarm Flag", 3, 1)
186                 .WithFlag(4, out periodicTimerFlag, name: "TF - Periodic Countdown Timer Flag")
187                 .WithTag("UF - Periodic Time Update Flag", 5, 1)
188                 .WithReservedBits(6, 2);
189 
190             RegistersCollection.AddRegister((long)Registers.FlagRegister, flagRegister);
191             RegistersCollection.AddRegister((long)Registers.FlagRegister_Extended1, flagRegister);
192 
193             var controlRegister = new ByteRegister(this)
194                 .WithFlag(0, out reset, name: "RESET",
195                     writeCallback: (_, val) =>
196                     {
197                         UpdateTimersEnable();
198                     })
199                 .WithReservedBits(1, 1)
200                 .WithTag("EIE - External Event Interrupt Enable", 2, 1)
201                 .WithTag("AIE - Alarm Interrupt Enable", 3, 1)
202                 .WithFlag(4, out periodicTimerInterruptEnable, name :"TIE - Periodic Countdown Timer Interrupt Enable")
203                 .WithTag("UIE - Periodic Time Update Interrupt Enable", 5, 1)
204                 .WithReservedBits(6, 2)
205             ;
206 
207             RegistersCollection.AddRegister((long)Registers.ControlRegister, controlRegister);
208             RegistersCollection.AddRegister((long)Registers.ControlRegister_Extended1, controlRegister);
209 
210             Registers.RAM.Define(this)
211                 .WithValueField(0, 8, name: "RAM");
212 
213             var secondsRegister = new ByteRegister(this)
214                 .WithValueField(0, 7, name: "Seconds",
215                     valueProviderCallback: _ => BCDHelper.EncodeToBCD((byte)realTimeCounter.Second),
216                     writeCallback: (_, value) => realTimeCounter.Second = BCDHelper.DecodeFromBCD((byte)value))
217                 .WithReservedBits(7, 1)
218             ;
219 
220             RegistersCollection.AddRegister((long)Registers.Seconds, secondsRegister);
221             RegistersCollection.AddRegister((long)Registers.Seconds_Extended1, secondsRegister);
222 
223             var minutesRegister = new ByteRegister(this)
224                 .WithValueField(0, 7, name: "Minutes",
225                     valueProviderCallback: _ => BCDHelper.EncodeToBCD((byte)realTimeCounter.Minute),
226                     writeCallback: (_, value) => realTimeCounter.Minute = BCDHelper.DecodeFromBCD((byte)value))
227                 .WithReservedBits(7, 1)
228             ;
229 
230             RegistersCollection.AddRegister((long)Registers.Minutes, minutesRegister);
231             RegistersCollection.AddRegister((long)Registers.Minutes_Extended1, minutesRegister);
232 
233             var hoursRegister = new ByteRegister(this)
234                 .WithValueField(0, 6, name: "Hours",
235                     valueProviderCallback: _ => BCDHelper.EncodeToBCD((byte)realTimeCounter.Hour),
236                     writeCallback: (_, value) => realTimeCounter.Hour = BCDHelper.DecodeFromBCD((byte)value))
237                 .WithReservedBits(6, 2)
238             ;
239 
240             RegistersCollection.AddRegister((long)Registers.Hours, hoursRegister);
241             RegistersCollection.AddRegister((long)Registers.Hours_Extended1, hoursRegister);
242 
243             var weekdayRegister = new ByteRegister(this)
244                 .WithValueField(0, 7, name: "Weekday",
245                     valueProviderCallback: _ => (uint)realTimeCounter.Weekday,
246                     writeCallback: (_, value) => realTimeCounter.Weekday = (int)value)
247                 .WithReservedBits(7, 1)
248             ;
249 
250             RegistersCollection.AddRegister((long)Registers.Weekday, weekdayRegister);
251             RegistersCollection.AddRegister((long)Registers.Weekday_Extended1, weekdayRegister);
252 
253             var dateRegister = new ByteRegister(this)
254                 .WithValueField(0, 6, name: "Date",
255                     valueProviderCallback: _ => BCDHelper.EncodeToBCD((byte)realTimeCounter.Day),
256                     writeCallback: (_, value) => realTimeCounter.Day = BCDHelper.DecodeFromBCD((byte)value))
257                 .WithReservedBits(6, 2)
258             ;
259 
260             RegistersCollection.AddRegister((long)Registers.Date, dateRegister);
261             RegistersCollection.AddRegister((long)Registers.Date_Extended1, dateRegister);
262 
263             var monthRegister = new ByteRegister(this)
264                 .WithValueField(0, 5, name: "Month",
265                     valueProviderCallback: _ => BCDHelper.EncodeToBCD((byte)realTimeCounter.Month),
266                     writeCallback: (_, value) => realTimeCounter.Month = BCDHelper.DecodeFromBCD((byte)value))
267                 .WithReservedBits(5, 3)
268             ;
269 
270             RegistersCollection.AddRegister((long)Registers.Month, monthRegister);
271             RegistersCollection.AddRegister((long)Registers.Month_Extended1, monthRegister);
272 
273             var yearRegister = new ByteRegister(this)
274                 .WithValueField(0, 8, name: "Year",
275                     valueProviderCallback: _ => BCDHelper.EncodeToBCD((byte)realTimeCounter.Year),
276                     writeCallback: (_, value) => realTimeCounter.Year = BCDHelper.DecodeFromBCD((byte)value))
277             ;
278 
279             RegistersCollection.AddRegister((long)Registers.Year, yearRegister);
280             RegistersCollection.AddRegister((long)Registers.Year_Extended1, yearRegister);
281         }
282 
UpdateTimersEnable()283         private void UpdateTimersEnable()
284         {
285             periodicTimer.Enabled = !reset.Value && periodicCountdownTimerEnable.Value;
286             realTimeCounter.Enabled = !reset.Value;
287         }
288 
289         private IFlagRegisterField periodicCountdownTimerEnable;
290         private IFlagRegisterField reset;
291         private IFlagRegisterField periodicTimerFlag;
292         private IFlagRegisterField periodicTimerInterruptEnable;
293 
294         private State currentState;
295         private Registers register;
296 
297         private readonly LimitTimer periodicTimer;
298         private readonly RTCTimer realTimeCounter;
299 
300         private const int DefaultPeriodicTimerFrequency = 4096;
301         private const int DefaultPeriodicTimerLimit = 4096;
302 
303         private enum Registers
304         {
305             Seconds = 0x00,
306             Minutes = 0x01,
307             Hours = 0x02,
308             Weekday = 0x03,
309             Date = 0x04,
310             Month = 0x05,
311             Year = 0x06,
312             RAM = 0x07,
313             MinutesAlarm = 0x08,
314             HoursAlarm = 0x09,
315             WeekdayAlarm_DateAlarm = 0x0A,
316             TimerCounter0 = 0x0B,
317             TimerCounter1 = 0x0C,
318             ExtensionRegister = 0x0D,
319             FlagRegister = 0x0E,
320             ControlRegister = 0x0F,
321 
322             Seconds100th_Extended1 = 0x10,
323             Seconds_Extended1 = 0x11,
324             Minutes_Extended1 = 0x12,
325             Hours_Extended1 = 0x13,
326             Weekday_Extended1 = 0x14,
327             Date_Extended1 = 0x15,
328             Month_Extended1 = 0x16,
329             Year_Extended1 = 0x17,
330             MinutesAlarm_Extended1 = 0x18,
331             HoursAlarm_Extended1 = 0x19,
332             WeekdayAlarm_DateAlarm_Extended1 = 0x1A,
333             TimerCounter0_Extended1 = 0x1B,
334             TimerCounter1_Extended1 = 0x1C,
335             ExtensionRegister_Extended1 = 0x1D,
336             FlagRegister_Extended1 = 0x1E,
337             ControlRegister_Extended1 = 0x1F,
338 
339             Seconds100thCP_Extended2 = 0x20,
340             SecondsCP_Extended2 = 0x21,
341             Offset_Extended2 = 0x2C,
342             EventControl_Extended2 = 0x2F,
343         }
344 
345         private enum State
346         {
347             WaitingForRegister,
348             HandlingData,
349         }
350 
351         private enum Operation
352         {
353             Write = 0,
354             Read = 1
355         }
356 
357         private class RTCTimer
358         {
RTCTimer(IMachine machine, IPeripheral parent)359             public RTCTimer(IMachine machine, IPeripheral parent)
360             {
361                 this.parent = parent;
362 
363                 innerTimer = new LimitTimer(machine.ClockSource, RTCFrequency, parent, "RTC timer", limit: RTCLimit);
364                 innerTimer.LimitReached += () =>
365                 {
366                     // this is called every second
367                     currentTime.AddSeconds(1);
368                 };
369 
370                 Reset();
371             }
372 
Reset()373             public void Reset()
374             {
375                 currentTime = new DateTimeWithCustomWeekday();
376                 innerTimer.Reset();
377             }
378 
379             public DateTimeWithCustomWeekday CurrentTime
380             {
381                 get => currentTime;
382                 set
383                 {
384                     currentTime = value;
385                 }
386             }
387 
388             public bool Enabled
389             {
390                 get => innerTimer.Enabled;
391                 set
392                 {
393                     innerTimer.Enabled = value;
394                 }
395             }
396 
397             public int Second
398             {
399                 get => currentTime.Second;
400                 set
401                 {
402                     try
403                     {
404                         currentTime.Second = value;
405                     }
406                     catch(ArgumentException e)
407                     {
408                         parent.Log(LogLevel.Warning, e.Message);
409                     }
410                 }
411             }
412 
413             public int Minute
414             {
415                 get => currentTime.Minute;
416                 set
417                 {
418                     try
419                     {
420                         currentTime.Minute = value;
421                     }
422                     catch(ArgumentException e)
423                     {
424                         parent.Log(LogLevel.Warning, e.Message);
425                     }
426                 }
427             }
428 
429             public int Hour
430             {
431                 get => currentTime.Hour;
432                 set
433                 {
434                     try
435                     {
436                         currentTime.Hour = value;
437                     }
438                     catch(ArgumentException e)
439                     {
440                         parent.Log(LogLevel.Warning, e.Message);
441                     }
442                 }
443             }
444 
445             public int Day
446             {
447                 get => currentTime.Day;
448                 set
449                 {
450                     try
451                     {
452                         currentTime.Day = value;
453                     }
454                     catch(ArgumentException e)
455                     {
456                         parent.Log(LogLevel.Warning, e.Message);
457                     }
458                 }
459             }
460 
461             public int Month
462             {
463                 get => currentTime.Month;
464                 set
465                 {
466                     try
467                     {
468                         currentTime.Month = value;
469                     }
470                     catch(ArgumentException e)
471                     {
472                         parent.Log(LogLevel.Warning, e.Message);
473                     }
474                 }
475             }
476 
477             public int Year
478             {
479                 get => currentTime.Year % 100;
480                 set
481                 {
482                     if(value < 0 || value > 99)
483                     {
484                         parent.Log(LogLevel.Warning, "Year value out of range: {0}", value);
485                         return;
486                     }
487 
488                     currentTime.Year = 2000 + value;
489                 }
490             }
491 
492             // Encoded as a single bit at positions 0, 1, 2, 3, 4, 5 or 6
493             public int Weekday
494             {
495                 get
496                 {
497                     var v = (int)currentTime.Weekday;
498                     if(v < 1)
499                     {
500                         v += 7;
501                     }
502                     return 1 << (v - 1);
503                 }
504                 set
505                 {
506                     if(value <= 0)
507                     {
508                         parent.Log(LogLevel.Warning, "Weekday value (0x{0:X}) out of range, expected exactly one bit to be set", value);
509                         return;
510                     }
511 
512                     var originalValue = value;
513                     var newWeekDay = 0;
514                     while(!BitHelper.IsBitSet((uint)value, 0))
515                     {
516                         newWeekDay++;
517                         value >>= 1;
518                     }
519 
520                     if(value != 0 || newWeekDay > 6)
521                     {
522                         parent.Log(LogLevel.Warning, "Weekday value (0x{0:X}) out of range, expected exactly one bit to be set", originalValue);
523                         return;
524                     }
525 
526                     currentTime.Weekday = (DayOfWeek)newWeekDay;
527                 }
528             }
529 
530             private DateTimeWithCustomWeekday currentTime;
531 
532             private readonly IPeripheral parent;
533             private readonly LimitTimer innerTimer;
534 
535             private const int RTCFrequency = 1;
536             private const int RTCLimit = 1;
537         }
538     }
539 }
540