1 // 2 // Copyright (c) 2010-2024 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.Core.Structure.Registers; 9 using Antmicro.Renode.Peripherals.Bus; 10 using Antmicro.Renode.Exceptions; 11 using Antmicro.Renode.Logging; 12 using Antmicro.Renode.Time; 13 14 namespace Antmicro.Renode.Peripherals.Timers 15 { 16 public sealed class RenesasDA_Watchdog : BasicDoubleWordPeripheral, IKnownSize 17 { RenesasDA_Watchdog(IMachine machine, long frequency, IDoubleWordPeripheral nvic)18 public RenesasDA_Watchdog(IMachine machine, long frequency, IDoubleWordPeripheral nvic) : base(machine) 19 { 20 IRQ = new GPIO(); 21 // Type comparison like this is required due to NVIC model being in another project 22 if(nvic.GetType().FullName != "Antmicro.Renode.Peripherals.IRQControllers.NVIC") 23 { 24 throw new ConstructionException($"{nvic.GetType()} is invalid type for NVIC"); 25 } 26 this.nvic = nvic; 27 28 ticker = new LimitTimer(machine.ClockSource, frequency, this, "watchdog", TickerDefaultValue, Direction.Descending, enabled: true, eventEnabled: true); 29 ticker.Divider = 320; 30 31 ticker.LimitReached += LimitReachedAction; 32 33 Registers.Value.Define(this, resetValue: 0x1FFF) 34 .WithValueField(14, 18, out writeLockFilter, name: "WDOG_WEN") 35 .WithFlag(13, FieldMode.Read, name: "WDOG_VAL_NEG", 36 valueProviderCallback: _ => 37 { 38 if(nonMaskableInterruptReset.Value) 39 { 40 return false; 41 } 42 // else was incremented by TickerShift to mimic negative values. 43 return ticker.Value < TickerShift; 44 }) 45 .WithValueField(0, 13, name: "WDOG_VAL", 46 valueProviderCallback: _ => 47 { 48 if(nonMaskableInterruptReset.Value) 49 { 50 return ticker.Value; 51 } 52 // Underflow the number. The sign is stored in another field (WDOG_VAL_NEG). 53 return unchecked(ticker.Value - TickerShift); 54 }, 55 writeCallback: (_, value) => 56 { 57 // Any value other than 0 in writeLockFilter forbids setting the value. 58 if(writeLockFilter.Value == WriteEnabled) 59 { 60 SetTickerValue(value); 61 } 62 } 63 ); 64 65 Registers.Control.Define(this, resetValue: 0x6) 66 .WithReservedBits(4, 28) 67 .WithFlag(3, FieldMode.Read, name: "WRITE_BUSY", 68 valueProviderCallback: _ => false // Not implemented. For now it's never busy. 69 ) 70 // Controls whether watchdog can be frozen by `RenesasDA14_GeneralPurposeRegisters` 71 // but it can only happen with `NMI_RST` unset; see `Frozen`. 72 .WithFlag(2, out watchdogFreezeEnabled, name: "WDOG_FREEZE_EN") 73 .WithReservedBits(1, 1) 74 .WithFlag(0, out nonMaskableInterruptReset, name: "NMI_RST") 75 .WithChangeCallback((oldValue, newValue) => 76 { 77 // Unsetting `WDOG_FREEZE_EN` or setting `NMI_RST` clears freeze. 78 UpdateEnabled(); 79 }); 80 } 81 Reset()82 public override void Reset() 83 { 84 IRQ.Unset(); 85 ticker.Reset(); // The ticker is enabled by default so it's also enabled after reset. 86 base.Reset(); 87 88 frozen = false; 89 resetRequested = false; 90 } 91 92 // Frozen keeps the value even if it effectively doesn't freeze the ticker due to `NMI_RST` or 93 // `WDOG_FREEZE_EN` so changing them might lead to immediate freeze if frozen is set. Datasheet 94 // isn't clear on what happens in such cases but this behavior seems reasonable. 95 public bool Frozen 96 { 97 get => ticker.Enabled; 98 set 99 { 100 if(value != frozen) 101 { 102 this.NoisyLog("Attempting to {0} freeze", value ? "set" : "reset"); 103 frozen = value; 104 UpdateEnabled(); 105 } 106 } 107 } 108 109 // Only this function, except for Reset, should enable and disable ticker. 110 // Use it every time conditions for ticker change. UpdateEnabled()111 private void UpdateEnabled() 112 { 113 if(resetRequested) 114 { 115 ticker.Enabled = false; 116 return; 117 } 118 119 var newEnabled = true; 120 if(frozen) 121 { 122 if(nonMaskableInterruptReset.Value) 123 { 124 this.DebugLog("Ignoring freeze because {0} is set", nameof(nonMaskableInterruptReset)); 125 } 126 else if(!watchdogFreezeEnabled.Value) 127 { 128 this.DebugLog("Ignoring freeze because {0} isn't set", nameof(watchdogFreezeEnabled)); 129 } 130 else 131 { 132 newEnabled = false; 133 } 134 } 135 136 if(ticker.Enabled != newEnabled) 137 { 138 this.NoisyLog("Freeze {0}", newEnabled ? "unset" : "set"); 139 ticker.Enabled = newEnabled; 140 } 141 } 142 143 public long Size => 0x10; 144 public GPIO IRQ { get; } 145 SetTickerValue(ulong value)146 private void SetTickerValue(ulong value) 147 { 148 IRQ.Unset(); 149 if(nonMaskableInterruptReset.Value) 150 { 151 // Only one trigger (at 0x0) is needed and it generates a reset. 152 ticker.Value = value; 153 ticker.Limit = 0x0; 154 } 155 else 156 { 157 // By default two triggers are needed, at 0x0 and negative 0x10. 158 // Shift the value by TickerShift to handle both cases as non-negative values. 159 // `TickerShift` generates a NMI and 0x0 generates a reset. 160 ticker.Value = value + TickerShift; 161 ticker.Limit = TickerShift; 162 } 163 this.Log(LogLevel.Noisy, "Ticker value set to: 0x{0:X}", value); 164 } 165 LimitReachedAction()166 private void LimitReachedAction() 167 { 168 this.Log(LogLevel.Noisy, "Limit reached"); 169 if(ticker.Limit == TickerShift && !nonMaskableInterruptReset.Value) 170 { 171 // Limit for NMI 172 this.Log(LogLevel.Noisy, "Triggering IRQ"); 173 IRQ.Set(); 174 ticker.Limit = 0x0; 175 ticker.Value = TickerShift; 176 // Send NMI to NVIC 177 ((dynamic)nvic).SetPendingIRQ(2); 178 } 179 else 180 { 181 // Limit for reset 182 this.Log(LogLevel.Warning, "Reseting machine"); 183 resetRequested = true; 184 UpdateEnabled(); 185 machine.RequestReset(); 186 } 187 } 188 189 private bool frozen; 190 private bool resetRequested; 191 192 private readonly LimitTimer ticker; 193 private readonly IDoubleWordPeripheral nvic; 194 195 private IValueRegisterField writeLockFilter; 196 private IFlagRegisterField watchdogFreezeEnabled; 197 private IFlagRegisterField nonMaskableInterruptReset; 198 199 // In hardware it counts down from 0x1fff to negative 0x10. Mock the negative range by starting from +0x10. 200 private const ulong TickerShift = 0x10; 201 private const ulong TickerDefaultValue = 0x1fff + TickerShift; 202 private const ulong WriteEnabled = 0; 203 204 private enum Registers: long 205 { 206 Value = 0x0, 207 Control = 0x4, 208 } 209 } 210 } 211