1 //
2 // Copyright (c) 2010-2025 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 
9 using Antmicro.Renode.Core;
10 using Antmicro.Renode.Core.Structure.Registers;
11 using Antmicro.Renode.Logging;
12 using Antmicro.Renode.Time;
13 using Antmicro.Renode.Utilities;
14 
15 namespace Antmicro.Renode.Peripherals.Timers
16 {
17     public class ZynqMP_RTC : BasicDoubleWordPeripheral, IKnownSize
18     {
ZynqMP_RTC(IMachine machine, long frequency = 32767)19         public ZynqMP_RTC(IMachine machine, long frequency = 32767) : base(machine)
20         {
21             DefineRegisters();
22             SecondIRQ = new GPIO();
23             AlarmIRQ = new GPIO();
24 
25             // Calibration adjusts the period of the ticker up or down. It's done in units of 1/16
26             // if calibrationFractionalTicksEnable or 1 otherwise. We always do the multiply here
27             // to simplify the implementation.
28             // We also set limit == frequency to have the event at 1 Hz by default.
29             var fractionalTicks = frequency * FractionalTicksPerTick;
30             ticker = new LimitTimer(machine.ClockSource, fractionalTicks, this, nameof(ticker), (ulong)fractionalTicks, Direction.Ascending, enabled: true, eventEnabled: true);
31             ticker.LimitReached += HandleTick;
32             machine.RealTimeClockModeChanged += _ => SetTimeFromMachine();
33             Reset();
34         }
35 
Reset()36         public override void Reset()
37         {
38             base.Reset();
39             ticker.Reset();
40             SetTimeFromMachine();
41             UpdateInterrupts();
42         }
43 
44         public GPIO SecondIRQ { get; }
45         public GPIO AlarmIRQ { get; }
46         public long Size => 0x100;
47 
DefineRegisters()48         private void DefineRegisters()
49         {
50             Registers.SetTimeWrite.Define(this)
51                 .WithValueField(0, 32, out setTime, name: nameof(setTime))
52                 .WithWriteCallback((_, __) => SetTimeFromUnixTimestamp(setTime.Value))
53             ;
54 
55             Registers.SetTimeRead.Define(this)
56                 .WithValueField(0, 32, FieldMode.Read, name: nameof(setTime), valueProviderCallback: _ => setTime.Value)
57             ;
58 
59             Registers.CalibrationWrite.Define(this)
60                 .WithValueField(0, 16, out calibrationTicks, name: nameof(calibrationTicks))
61                 .WithValueField(16, 4, out calibrationFractionalTicks, name: nameof(calibrationFractionalTicks))
62                 .WithFlag(20, out calibrationFractionalTicksEnable, name: nameof(calibrationFractionalTicksEnable))
63                 .WithReservedBits(21, 11)
64                 .WithChangeCallback((_, __) => ApplyCalibration())
65             ;
66 
67             Registers.CalibrationRead.Define(this)
68                 .WithValueField(0, 16, FieldMode.Read, name: nameof(calibrationTicks), valueProviderCallback: _ => calibrationTicks.Value)
69                 .WithValueField(16, 4, FieldMode.Read, name: nameof(calibrationFractionalTicks), valueProviderCallback: _ => calibrationFractionalTicks.Value)
70                 .WithFlag(20, FieldMode.Read, name: nameof(calibrationFractionalTicksEnable), valueProviderCallback: _ => calibrationFractionalTicksEnable.Value)
71                 .WithReservedBits(21, 11)
72             ;
73 
74             Registers.CurrentTime.Define(this)
75                 .WithValueField(0, 32, FieldMode.Read, name: nameof(currentTime), valueProviderCallback: _ => DateTimeToUnixTimestamp(currentTime))
76             ;
77 
78             Registers.Alarm.Define(this)
79                 .WithValueField(0, 32, out alarmTimestamp, name: nameof(alarmTimestamp))
80             ;
81 
82             Registers.InterruptStatus.Define(this)
83                 .WithFlag(0, out secondInterruptFlag, FieldMode.Read | FieldMode.WriteOneToClear, name: nameof(secondInterruptFlag))
84                 .WithFlag(1, out alarmInterruptFlag, FieldMode.Read | FieldMode.WriteOneToClear, name: nameof(alarmInterruptFlag))
85                 .WithReservedBits(2, 30)
86                 .WithWriteCallback((_, __) => UpdateInterrupts())
87             ;
88 
89             Registers.InterruptMask.Define(this)
90                 .WithFlag(0, out secondInterruptEnable, name: nameof(secondInterruptEnable))
91                 .WithFlag(1, out alarmInterruptEnable, name: nameof(alarmInterruptEnable))
92                 .WithReservedBits(2, 30)
93                 .WithWriteCallback((_, __) => UpdateInterrupts())
94             ;
95 
96             Registers.InterruptEnable.Define(this)
97                 .WithFlag(0, name: nameof(secondInterruptEnable), writeCallback: (_, value) => { if(value) secondInterruptEnable.Value = true; })
98                 .WithFlag(1, name: nameof(alarmInterruptEnable), writeCallback: (_, value) => { if(value) alarmInterruptEnable.Value = true; })
99                 .WithReservedBits(2, 30)
100                 .WithWriteCallback((_, __) => UpdateInterrupts())
101             ;
102 
103             Registers.InterruptDisable.Define(this)
104                 .WithFlag(0, name: nameof(secondInterruptEnable), writeCallback: (_, value) => { if(value) secondInterruptEnable.Value = false; })
105                 .WithFlag(1, name: nameof(alarmInterruptEnable), writeCallback: (_, value) => { if(value) alarmInterruptEnable.Value = false; })
106                 .WithReservedBits(2, 30)
107                 .WithWriteCallback((_, __) => UpdateInterrupts())
108             ;
109 
110             Registers.Control.Define(this)
111                 .WithReservedBits(0, 31)
112                 .WithFlag(31, name: "batteryEnable")
113             ;
114         }
115 
ApplyCalibration()116         private void ApplyCalibration()
117         {
118             var newFrequency = (long)calibrationTicks.Value * FractionalTicksPerTick;
119             if(calibrationFractionalTicksEnable.Value)
120             {
121                 newFrequency += (long)calibrationFractionalTicks.Value;
122             }
123             ticker.Frequency = newFrequency;
124         }
125 
SetTime(DateTime dt)126         private void SetTime(DateTime dt)
127         {
128             currentTime = dt;
129         }
130 
SetTimeFromMachine()131         private void SetTimeFromMachine()
132         {
133             SetTime(machine.RealTimeClockDateTime);
134         }
135 
SetTimeFromUnixTimestamp(ulong timestamp)136         private void SetTimeFromUnixTimestamp(ulong timestamp)
137         {
138             currentTime = UnixTimestampToDateTime(timestamp);
139         }
140 
HandleTick()141         private void HandleTick()
142         {
143             currentTime = currentTime.AddSeconds(1);
144             secondInterruptFlag.Value = true;
145             // There is no alarm enable bit, so treat the timestamp being 0 as "alarm disabled".
146             // The XilinxProcessorIPLib's XRtcPsu_SetAlarm function forbids setting the alarm to timestamp 0
147             // so this seems to be a reasonable guess about its behavior.
148             alarmInterruptFlag.Value |= alarmTimestamp.Value != 0 && DateTimeToUnixTimestamp(currentTime) >= alarmTimestamp.Value;
149             UpdateInterrupts();
150         }
151 
UpdateInterrupts()152         private void UpdateInterrupts()
153         {
154             var newSecond = secondInterruptFlag.Value && secondInterruptEnable.Value;
155             if(SecondIRQ.IsSet != newSecond)
156             {
157                 this.DebugLog("Setting {0} to {1}", nameof(SecondIRQ), newSecond);
158                 SecondIRQ.Set(newSecond);
159             }
160             var newAlarm = alarmInterruptFlag.Value && alarmInterruptEnable.Value;
161             if(AlarmIRQ.IsSet != newAlarm)
162             {
163                 this.DebugLog("Setting {0} to {1}", nameof(AlarmIRQ), newAlarm);
164                 AlarmIRQ.Set(newAlarm);
165             }
166         }
167 
UnixTimestampToDateTime(ulong timestamp)168         private static DateTime UnixTimestampToDateTime(ulong timestamp)
169         {
170             return Misc.UnixEpoch.AddSeconds(timestamp);
171         }
172 
DateTimeToUnixTimestamp(DateTime dateTime)173         private static ulong DateTimeToUnixTimestamp(DateTime dateTime)
174         {
175             return (ulong)(dateTime - Misc.UnixEpoch).TotalSeconds;
176         }
177 
178         private IValueRegisterField setTime;
179         private IValueRegisterField alarmTimestamp;
180         private IValueRegisterField calibrationTicks;
181         private IValueRegisterField calibrationFractionalTicks;
182         private IFlagRegisterField calibrationFractionalTicksEnable;
183         private IFlagRegisterField secondInterruptFlag;
184         private IFlagRegisterField alarmInterruptFlag;
185         private IFlagRegisterField secondInterruptEnable;
186         private IFlagRegisterField alarmInterruptEnable;
187 
188         private DateTime currentTime;
189 
190         private readonly LimitTimer ticker;
191 
192         private const int FractionalTicksPerTick = 16;
193 
194         private enum Registers : long
195         {
196             SetTimeWrite = 0x00,
197             SetTimeRead = 0x04,
198             CalibrationWrite = 0x08,
199             CalibrationRead = 0x0C,
200             CurrentTime = 0x10,
201             // CurrentTick = 0x14, // mystery register not used by the XilinxProcessorIPLib or Linux
202             Alarm = 0x18,
203             InterruptStatus = 0x20,
204             InterruptMask = 0x24,
205             InterruptEnable = 0x28,
206             InterruptDisable = 0x2C,
207             Control = 0x40,
208         }
209     }
210 }
211