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