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