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