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 System;
8 using System.Collections.Generic;
9 using Antmicro.Renode.Peripherals.Bus;
10 using Antmicro.Renode.Core.Structure.Registers;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Time;
14 
15 namespace Antmicro.Renode.Peripherals.Timers
16 {
17     [AllowedTranslations(AllowedTranslation.ByteToWord | AllowedTranslation.WordToByte)]
18     public class NPCX_TWD : IWordPeripheral, IProvidesRegisterCollection<WordRegisterCollection>, IKnownSize
19     {
NPCX_TWD(IMachine machine)20         public NPCX_TWD(IMachine machine)
21         {
22             this.machine = machine;
23 
24             RegistersCollection = new WordRegisterCollection(this, BuildRegisterMap());
25 
26             watchdog = new Watchdog(this, WatchdogCounterDefaultValue, WatchdogAlarmHandler);
27 
28             periodicInterruptTimer = new LimitTimer(machine.ClockSource, DefaultFrequency, this, "PeriodicInterruptTimer", 0xFFFF1, eventEnabled: true, enabled: true, workMode: WorkMode.Periodic);
29             periodicInterruptTimer.LimitReached += () =>
30             {
31                 IRQ.Blink();
32                 terminalCountReached = true;
33                 if(!isCounterClockSource.Value)
34                 {
35                     watchdog.Tick();
36                 }
37             };
38 
39             watchdogCounter = new LimitTimer(machine.ClockSource, DefaultFrequency, this, "WatchdogCounter", 0x1, eventEnabled: true);
40             watchdogCounter.LimitReached += () => watchdog.Tick();
41 
42             IRQ = new GPIO();
43             Reset();
44         }
45 
Reset()46         public void Reset()
47         {
48             RegistersCollection.Reset();
49             periodicInterruptTimer.Reset();
50 
51             watchdogCounter.Reset();
52             watchdog.Reset();
53 
54             IRQ.Unset();
55 
56             byteInSequence = StopUnlockSequence.None;
57             timerAndWatchdogPrescaler = DefaultDivider;
58             watchdogPrescaler = DefaultDivider;
59             terminalCountReached = false;
60             watchdogCounterPresetValue = WatchdogCounterDefaultValue;
61         }
62 
ReadWord(long offset)63         public ushort ReadWord(long offset)
64         {
65             return RegistersCollection.Read(offset);
66         }
67 
WriteWord(long offset, ushort value)68         public void WriteWord(long offset, ushort value)
69         {
70             RegistersCollection.Write(offset, value);
71         }
72 
73         public GPIO IRQ { get; }
74 
75         public long Size => 0x1000;
76 
77         public WordRegisterCollection RegistersCollection { get; }
78 
BuildRegisterMap()79         private Dictionary<long, WordRegister> BuildRegisterMap()
80         {
81             var registerMap = new Dictionary<long, WordRegister>
82             {
83                 {(long)Registers.TimerAndWatchdogConfiguration, new WordRegister(this)
84                     .WithReservedBits(6, 10)
85                     .WithFlag(5, out watchdogTouchSelect, name: "WDSDME (Watchdog Touch Select)")
86                     .WithFlag(4, out isCounterClockSource,
87                         writeCallback: (_, val) => watchdogCounter.Enabled = val,
88                         name: "WDCT0I (Watchdog Clock Select)")
89                     .WithFlag(3, out lockWatchdog, FieldMode.Set, name: "LWDCNT (Lock Watchdog Counter)")
90                     .WithFlag(2, out lockTimer, FieldMode.Set, name: "LTWDT0 (Lock T0 Timer)")
91                     .WithFlag(1, out lockPrescalers, FieldMode.Set, name: "LTWCP (Lock Prescalers)")
92                     .WithFlag(0, out lockWatchdogConfig, FieldMode.Set, name: "LTWCFG (Lock Watchdog Configuration)")
93                 },
94 
95                 {(long)Registers.TimerAndWatchdogClockPrescaler, new WordRegister(this)
96                     .WithReservedBits(4, 12)
97                     .WithValueField(0, 4,
98                         writeCallback: (__, val) =>
99                         {
100                             if(lockPrescalers.Value)
101                             {
102                                 this.Log(LogLevel.Warning, "Prescaler lock active: cannot reconfigure!");
103                                 return;
104                             }
105                             if(val > 10)
106                             {
107                                 this.Log(LogLevel.Warning, "Prescaler ratio should be in range <0,10>!");
108                                 return;
109                             }
110                             timerAndWatchdogPrescaler = (1 << (int)val);
111                             periodicInterruptTimer.Divider = timerAndWatchdogPrescaler;
112                         },
113                         valueProviderCallback: _ =>
114                         {
115                             if(lockPrescalers.Value)
116                             {
117                                 this.Log(LogLevel.Warning, "Prescaler lock active: returning zero!");
118                                 return 0;
119                             }
120                             return (ulong)timerAndWatchdogPrescaler;
121                         },
122                         name: "MDIV")
123                 },
124 
125                 {(long)Registers.Timer0, new WordRegister(this)
126                     .WithValueField(0, 16,
127                         writeCallback: (__, val) =>
128                         {
129                             if(lockTimer.Value)
130                             {
131                                 this.Log(LogLevel.Warning, "Timer lock active: cannot reconfigure!");
132                                 return;
133                             }
134                             periodicInterruptTimer.Limit = val + 1;
135                         },
136                         valueProviderCallback: _ =>
137                         {
138                             if(lockTimer.Value)
139                             {
140                                 this.Log(LogLevel.Warning, "Timer lock active: returning zero!");
141                                 return 0;
142                             }
143                             return periodicInterruptTimer.Limit - 1;
144                         },
145                         name: "T0_PRESET (T0 Counter Preset)")
146                 },
147 
148                 {(long)Registers.Timer0ControlAndStatus, new WordRegister(this)
149                     .WithReservedBits(8, 8)
150                     .WithTaggedFlag("TESDIS (Too Early Service Disable)", 7)
151                     .WithReservedBits(6, 1)
152                     .WithFlag(5, FieldMode.Read, valueProviderCallback: _ => watchdog.Enabled, name: "WD_RUN (Watchdog Run Status)")
153                     .WithFlag(4, out watchdogResetStatus, FieldMode.WriteOneToClear | FieldMode.Read, name: "WDRST_STS (Watchdog Reset Status)")
154                     .WithTaggedFlag("WDLTD (Watchdog Last Touch Delay)", 3)
155                     .WithReservedBits(2, 1)
156                     .WithFlag(1, FieldMode.Read,
157                         valueProviderCallback: _ =>
158                         {
159                             if(terminalCountReached)
160                             {
161                                 terminalCountReached = false;
162                                 return true;
163                             }
164                             return terminalCountReached;
165                         },
166                         name: "TC (Terminal Count)")
167                     .WithFlag(0,
168                         valueProviderCallback: _ => false,
169                         writeCallback: (_, val) =>
170                         {
171                             if(!val)
172                             {
173                                 return;
174                             }
175                             periodicInterruptTimer.ResetValue();
176                         },
177                         name: "RST (Reset)")
178                 },
179 
180                 {(long)Registers.WatchdogCount, new WordRegister(this)
181                     .WithReservedBits(8, 8)
182                     .WithValueField(0, 8,
183                         writeCallback: (__, val) =>
184                         {
185                             if(lockWatchdog.Value)
186                             {
187                                 watchdog.Value = watchdogCounterPresetValue;
188                             }
189                             else
190                             {
191                                 watchdog.Value = val;
192                             }
193 
194                             watchdogCounterPresetValue = (byte)val;
195                             watchdog.Enabled = true;
196                         },
197                         valueProviderCallback: _ =>
198                         {
199                             if(lockWatchdog.Value)
200                             {
201                                 this.Log(LogLevel.Warning, "Watchdog lock active: returning zero!");
202                                 return 0;
203                             }
204                             return watchdogCounterPresetValue;
205                         },
206                         name: "WD_PRESET (Watchdog Counter Preset)")
207                 },
208 
209                 {(long)Registers.WatchdogServiceDataMatch, new WordRegister(this)
210                     .WithReservedBits(8, 8)
211                     .WithValueField(0, 8, FieldMode.Write,
212                         writeCallback: (__, val) =>
213                         {
214                             // stop sequence: 87h, 61h, 63h
215                             // unlock sequence is the same
216                             var sequenceByte = HandleStopUnlockSequence((byte)val);
217                             if(sequenceByte == StopUnlockSequence.ThirdByte)
218                             {
219                                 if(watchdog.Enabled)
220                                 {
221                                     watchdog.Enabled = false;
222                                 }
223                                 else
224                                 {
225                                     lockWatchdog.Value = false;
226                                     lockTimer.Value = false;
227                                     lockPrescalers.Value = false;
228                                     lockWatchdogConfig.Value = false;
229                                 }
230                             }
231                             else if(watchdogTouchSelect.Value && val == TouchValue)
232                             {
233                                 this.Log(LogLevel.Noisy, "Watchdog has been touched!");
234                                 watchdog.Value = watchdogCounterPresetValue;
235                             }
236                             else if(sequenceByte == StopUnlockSequence.None)
237                             {
238                                 WatchdogAlarmHandler();
239                             }
240                         },
241                         name: "RSDATA (Watchdog Restart Data)")
242                 },
243 
244                 {(long)Registers.Timer0Counter, new WordRegister(this)
245                     .WithValueField(0, 16, FieldMode.Read, valueProviderCallback: _ => periodicInterruptTimer.Value, name: "T0_COUNT (T0 Counter Value)")
246                 },
247 
248                 {(long)Registers.WatchdogCounter, new WordRegister(this)
249                     .WithReservedBits(8, 8)
250                     .WithValueField(0, 8, FieldMode.Read, valueProviderCallback: _ => (ulong)watchdog.Value, name: "WD_COUNT (Watchdog Counter Value)")
251                 },
252 
253                 {(long)Registers.WatchdogClockPrescaler, new WordRegister(this)
254                     .WithReservedBits(4, 12)
255                     .WithValueField(0, 4,
256                         writeCallback: (__, val) =>
257                         {
258                             if(lockPrescalers.Value)
259                             {
260                                 this.Log(LogLevel.Warning, "Prescaler lock active: cannot reconfigure!");
261                                 return;
262                             }
263                             if(val > 15)
264                             {
265                                 this.Log(LogLevel.Warning, "Prescaler ratio should be in range <0,15>!");
266                                 return;
267                             }
268                             watchdogPrescaler = (1 << (int)val);
269 
270                             if(isCounterClockSource.Value)
271                             {
272                                 // Watchdog ticked by Counter (watchdogCounter):
273                                 // in Renode we can take a shortcut and implement two counters as one,
274                                 // hence the multiplication `timerAndWatchdogPrescaler * watchdogPrescaler`;
275                                 // We set the divider on the Counter instead of Watchdog to increase performance
276                                 watchdogCounter.Divider = timerAndWatchdogPrescaler * watchdogPrescaler;
277                                 watchdog.Divider = 1;
278                             }
279                             else
280                             {
281                                 // Watchdog ticked by Timer (periodicInterruptTimer):
282                                 // `watchdog.Divider` is set only to `watchdogPrescaler` because
283                                 // `timerAndWatchdogPrescaler` is taken into account as the divider
284                                 // of `periodicInterruptTimer`
285                                 watchdog.Divider = watchdogPrescaler;
286                             }
287                         },
288                         valueProviderCallback: _ =>
289                         {
290                             if(lockPrescalers.Value)
291                             {
292                                 this.Log(LogLevel.Warning, "Prescaler lock active: returning zero!");
293                                 return 0;
294                             }
295                             return (ulong)watchdogPrescaler;
296                         },
297                         name: "WDIV")
298                 },
299             };
300 
301             return registerMap;
302         }
303 
WatchdogAlarmHandler()304         private void WatchdogAlarmHandler()
305         {
306             this.Log(LogLevel.Debug, "Watchdog reset triggered!");
307             watchdogResetStatus.Value = true;
308             machine.RequestReset();
309         }
310 
HandleStopUnlockSequence(byte data)311         private StopUnlockSequence HandleStopUnlockSequence(byte data)
312         {
313             if((byteInSequence == StopUnlockSequence.None) && (data == 0x87))
314             {
315                 byteInSequence = StopUnlockSequence.FirstByte;
316             }
317             else if ((byteInSequence == StopUnlockSequence.FirstByte) && (data == 0x61))
318             {
319                 byteInSequence = StopUnlockSequence.SecondByte;
320             }
321             else if((byteInSequence == StopUnlockSequence.SecondByte) && (data == 0x63))
322             {
323                 byteInSequence = StopUnlockSequence.ThirdByte;
324             }
325             else
326             {
327                 byteInSequence = StopUnlockSequence.None;
328             }
329             return byteInSequence;
330         }
331 
332         private readonly IMachine machine;
333         private readonly LimitTimer periodicInterruptTimer;
334         private readonly LimitTimer watchdogCounter;
335         private readonly Watchdog watchdog;
336 
337         private IFlagRegisterField lockTimer;
338         private IFlagRegisterField watchdogTouchSelect;
339         private IFlagRegisterField lockWatchdog;
340         private IFlagRegisterField lockPrescalers;
341         private IFlagRegisterField lockWatchdogConfig;
342         private IFlagRegisterField watchdogResetStatus;
343         private IFlagRegisterField isCounterClockSource;
344         private StopUnlockSequence byteInSequence;
345         private int timerAndWatchdogPrescaler;
346         private int watchdogPrescaler;
347         private bool terminalCountReached;
348         private byte watchdogCounterPresetValue;
349 
350         private const int DefaultFrequency = 32768;
351         private const int DefaultDivider = 1;
352         private const int WatchdogCounterDefaultValue = 0xF;
353         private const int TouchValue = 0x5C;
354 
355         private class Watchdog
356         {
Watchdog(NPCX_TWD parent, ulong initialValue, Action alarmHandler)357             public Watchdog(NPCX_TWD parent, ulong initialValue, Action alarmHandler)
358             {
359                 this.parent = parent;
360                 this.initialValue = initialValue;
361                 this.alarmHandler = alarmHandler;
362                 Divider = 1;
363                 Reset();
364             }
365 
Reset()366             public void Reset()
367             {
368                 Enabled = false;
369                 RestoreClock();
370             }
371 
Tick()372             public void Tick()
373             {
374                 if(!Enabled)
375                 {
376                     return;
377                 }
378 
379                 if(internalDivider > 0)
380                 {
381                     --internalDivider;
382                     return;
383                 }
384                 --Value;
385                 if(Value > 0 && internalDivider == 0)
386                 {
387                     internalDivider = Divider;
388                     return;
389                 }
390 
391                 RestoreClock();
392                 alarmHandler();
393             }
394 
395             public ulong Value
396             {
397                 get; set;
398             }
399 
400             public bool Enabled
401             {
402                 get; set;
403             }
404 
405             public int Divider
406             {
407                 get; set;
408             }
409 
RestoreClock()410             private void RestoreClock()
411             {
412                 Value = initialValue;
413                 internalDivider = Divider;
414             }
415 
416             private readonly Action alarmHandler;
417             private readonly ulong initialValue;
418 
419             private NPCX_TWD parent;
420             private int internalDivider;
421         }
422 
423         private enum StopUnlockSequence
424         {
425             None,
426             FirstByte,
427             SecondByte,
428             ThirdByte
429         }
430 
431         private enum Registers : long
432         {
433             TimerAndWatchdogConfiguration = 0x00,   // TWCFG
434             TimerAndWatchdogClockPrescaler = 0x02,  // TWCP
435             Timer0 = 0x04,                          // TWDT0
436             Timer0ControlAndStatus = 0x06,          // T0CSR
437             WatchdogCount = 0x08,                   // WDCNT
438             WatchdogServiceDataMatch = 0x0A,        // WDSDM
439             Timer0Counter = 0x0C,                   // TWMT0
440             WatchdogCounter = 0x0E,                 // TWMWD
441             WatchdogClockPrescaler = 0x10,          // WDCP
442         }
443     }
444 }
445