1 // 2 // Copyright (c) 2010-2023 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.Logging; 10 using Antmicro.Renode.Peripherals.Bus; 11 using Antmicro.Renode.Time; 12 using System; 13 14 namespace Antmicro.Renode.Peripherals.Timers 15 { 16 [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] 17 public class AmbiqApollo4_SystemTimer : BasicDoubleWordPeripheral, IKnownSize, IGPIOReceiver 18 { AmbiqApollo4_SystemTimer(IMachine machine)19 public AmbiqApollo4_SystemTimer(IMachine machine) : base(machine) 20 { 21 // Changing 'CLKSEL' (it's 'NOCLK' by default) is necessary to enable 'systemTimer'. 22 systemTimer = new LimitTimer(machine.ClockSource, InvalidFrequency, this, "System Timer", uint.MaxValue, 23 Direction.Ascending, enabled: false, workMode: WorkMode.Periodic, eventEnabled: true, autoUpdate: true, divider: 1); 24 systemTimer.LimitReached += () => HandleLimitReached(); 25 26 for(var i = 0; i < interruptOutputs.Length; i++) 27 { 28 interruptOutputs[i] = new GPIO(); 29 } 30 for(var i = 0; i < captureRegisters.Length; i++) 31 { 32 captureRegisters[i] = new CaptureRegister(this, i); 33 } 34 for(var i = 0; i < compareRegisters.Length; i++) 35 { 36 compareRegisters[i] = new CompareRegister(this, i, interruptOutputs[i], systemTimer); 37 } 38 39 DefineRegisters(); 40 Reset(); 41 } 42 OnGPIO(int number, bool value)43 public void OnGPIO(int number, bool value) 44 { 45 this.Log(LogLevel.Debug, "GPIO#{0} {1}", number, value ? "set" : "unset"); 46 foreach(var captureRegister in captureRegisters) 47 { 48 captureRegister.OnGPIO(number, value); 49 } 50 } 51 Reset()52 public override void Reset() 53 { 54 Array.ForEach(captureRegisters, register => register.Reset()); 55 Array.ForEach(compareRegisters, register => register.Reset()); 56 // All but IRQI should be reset with CompareRegister.Reset; nevertheless, let's unset all IRQs. 57 Array.ForEach(interruptOutputs, irq => irq.Unset()); 58 systemTimer.Reset(); 59 60 base.Reset(); 61 } 62 63 public long Frequency => systemTimer.Frequency; 64 65 // Comparator IRQs 66 public GPIO IRQA => interruptOutputs[0]; 67 public GPIO IRQB => interruptOutputs[1]; 68 public GPIO IRQC => interruptOutputs[2]; 69 public GPIO IRQD => interruptOutputs[3]; 70 public GPIO IRQE => interruptOutputs[4]; 71 public GPIO IRQF => interruptOutputs[5]; 72 public GPIO IRQG => interruptOutputs[6]; 73 public GPIO IRQH => interruptOutputs[7]; 74 75 // Capture + overflow event IRQ 76 public GPIO IRQI => interruptOutputs[8]; 77 78 public long Size => 0x110; 79 80 public uint Value 81 { 82 get 83 { 84 if(machine.SystemBus.TryGetCurrentCPU(out var cpu)) 85 { 86 // being here means we are on the CPU thread 87 cpu.SyncTime(); 88 } 89 return (uint)systemTimer.Value; 90 } 91 } 92 DefineRegisters()93 private void DefineRegisters() 94 { 95 Registers.Capture0.DefineMany(this, 4, 96 (register, registerIndex) => 97 { 98 register.WithValueField(0, 32, FieldMode.Read, name: $"SCAPT{registerIndex}", 99 valueProviderCallback: _ => captureRegisters[registerIndex].ValueCaptured); 100 }, stepInBytes: 4); 101 102 Registers.CaptureControl0.DefineMany(this, 4, 103 (register, registerIndex) => 104 { 105 var captureRegister = captureRegisters[registerIndex]; 106 register.WithValueField(0, 7, out captureRegister.TriggerSourceGPIOPinNumber, name: $"STSEL{registerIndex}") 107 .WithReservedBits(7, 1) 108 .WithFlag(8, out captureRegister.CaptureOnHighToLowGPIOTransition, name: $"STPOL{registerIndex}") 109 .WithFlag(9, out captureRegister.Enabled, name: $"CAPTURE{registerIndex}") 110 .WithReservedBits(10, 22) 111 ; 112 }, stepInBytes: 4, resetValue: 0x7F); 113 114 Registers.Compare0.DefineMany(this, 8, 115 (register, registerIndex) => 116 { 117 register.WithValueField(0, 32, name: $"SCMPR{registerIndex}", 118 // SCMPR value written is relative to the current COUNTER (systemTimer's Value). 119 writeCallback: (_, newValue) => 120 { 121 // Ambiq HAL does a Compare delta adjustment: 122 // on HW it takes 2 clock cycles for writes to this register to be effective 123 // and the interrupt itself is delayed by 1. 124 // Hence the timer incrementation. 125 if((compareRegisters[registerIndex].CompareValue - (uint)newValue) > 3) 126 { 127 systemTimer.Increment(3); 128 } 129 compareRegisters[registerIndex].CompareValue = Value + (uint)newValue; 130 }, 131 valueProviderCallback: _ => compareRegisters[registerIndex].CompareValue); 132 }, stepInBytes: 4); 133 134 Registers.Configuration.Define(this, 0x80000000) 135 .WithEnumField(0, 4, out clockSelect, name: "CLKSEL", changeCallback: (_, __) => UpdateFrequency()) 136 .WithReservedBits(4, 4) 137 .WithFlags(8, 8, name: "COMPARExEN", changeCallback: (registerIndex, _, newValue) => compareRegisters[registerIndex].Enabled = newValue) 138 .WithReservedBits(16, 14) 139 .WithFlag(30, out clear, name: "CLEAR", changeCallback: (_, __) => UpdateSystemTimerState()) 140 .WithFlag(31, out freeze, name: "FREEZE", changeCallback: (_, __) => UpdateSystemTimerState()) 141 ; 142 143 Registers.InterruptClear.Define(this) 144 .WithFlags(0, 8, FieldMode.Write, name: "COMPAREx", 145 writeCallback: (registerIndex, _, newValue) => { if(newValue) compareRegisters[registerIndex].InterruptStatus = false; }) 146 .WithFlag(8, out overflowInterruptStatus, FieldMode.WriteOneToClear, name: "OVERFLOW", 147 changeCallback: (_, __) => UpdateCaptureOverflowIRQ()) 148 .WithFlags(9, 4, FieldMode.Write, name: "CAPTUREx", 149 writeCallback: (registerIndex, _, newValue) => { if(newValue) captureRegisters[registerIndex].InterruptStatus = false; }) 150 .WithReservedBits(13, 19) 151 ; 152 153 Registers.InterruptEnable.Define(this) 154 .WithFlags(0, 8, name: "COMPAREx", 155 changeCallback: (registerIndex, _, newValue) => compareRegisters[registerIndex].InterruptEnable = newValue, 156 valueProviderCallback: (registerIndex, _) => compareRegisters[registerIndex].InterruptEnable) 157 .WithFlag(8, out overflowInterruptEnable, name: "OVERFLOW", 158 changeCallback: (_, __) => UpdateCaptureOverflowIRQ()) 159 .WithFlags(9, 4, name: "CAPTUREx", 160 changeCallback: (registerIdx, _, newValue) => captureRegisters[registerIdx].InterruptEnable = newValue, 161 valueProviderCallback: (registerIdx, _) => captureRegisters[registerIdx].InterruptEnable) 162 .WithReservedBits(13, 19) 163 ; 164 165 Registers.InterruptSet.Define(this) 166 .WithFlags(0, 8, FieldMode.Write, name: "COMPAREx", 167 writeCallback: (registerIndex, _, newValue) => { if(newValue) compareRegisters[registerIndex].InterruptStatus = true; }) 168 .WithFlag(8, out overflowInterruptStatus, FieldMode.Set, name: "OVERFLOW", 169 changeCallback: (_, __) => UpdateCaptureOverflowIRQ()) 170 .WithFlags(9, 4, FieldMode.Write, name: "CAPTUREx", 171 writeCallback: (registerIndex, _, newValue) => { if(newValue) captureRegisters[registerIndex].InterruptStatus = true; }) 172 .WithReservedBits(13, 19) 173 ; 174 175 Registers.InterruptStatus.Define(this) 176 .WithFlags(0, 8, FieldMode.Read, name: "COMPAREx", 177 valueProviderCallback: (registerIndex, _) => compareRegisters[registerIndex].InterruptStatus) 178 .WithFlag(8, out overflowInterruptStatus, FieldMode.Read, name: "OVERFLOW") 179 .WithFlags(9, 4, FieldMode.Read, name: "CAPTUREx", 180 valueProviderCallback: (registerIdx, _) => captureRegisters[registerIdx].InterruptStatus) 181 .WithReservedBits(13, 19) 182 ; 183 184 Registers.SystemTimerCount.Define(this) 185 .WithValueField(0, 32, FieldMode.Read, name: "STTMR", valueProviderCallback: _ => Value) 186 ; 187 } 188 HandleLimitReached()189 private void HandleLimitReached() 190 { 191 this.Log(LogLevel.Debug, "COUNTER overflow occurred"); 192 overflowInterruptStatus.Value = true; 193 UpdateCaptureOverflowIRQ(); 194 } 195 UpdateCaptureOverflowIRQ()196 private void UpdateCaptureOverflowIRQ() 197 { 198 var newIrqState = false; 199 foreach(var captureRegister in captureRegisters) 200 { 201 if(captureRegister.InterruptEnable && captureRegister.InterruptStatus) 202 { 203 newIrqState = true; 204 break; 205 } 206 } 207 if(!newIrqState && overflowInterruptStatus.Value && overflowInterruptEnable.Value) 208 { 209 newIrqState = true; 210 } 211 212 if(IRQI.IsSet != newIrqState) 213 { 214 this.Log(LogLevel.Debug, "IRQI {0}", newIrqState ? "set" : "unset"); 215 IRQI.Set(newIrqState); 216 } 217 } 218 UpdateFrequency()219 private void UpdateFrequency() 220 { 221 var frequencySet = InvalidFrequency; 222 const long KHz = 1000; 223 switch(clockSelect.Value) 224 { 225 case ClockSelectValues.NOCLK: 226 this.Log(LogLevel.Debug, "CLKSEL set to NOCLK. Timer will be disabled."); 227 break; 228 case ClockSelectValues.HFRC_6MHZ: 229 frequencySet = 6 * 1000 * KHz; 230 break; 231 case ClockSelectValues.HFRC_375KHZ: 232 frequencySet = 375 * KHz; 233 break; 234 case ClockSelectValues.XTAL_32KHZ: 235 frequencySet = 32 * KHz; 236 break; 237 case ClockSelectValues.XTAL_16KHZ: 238 frequencySet = 16 * KHz; 239 break; 240 case ClockSelectValues.XTAL_1KHZ: 241 case ClockSelectValues.LFRC_1KHZ: 242 frequencySet = 1 * KHz; 243 break; 244 case ClockSelectValues.CTIMER0: 245 case ClockSelectValues.CTIMER1: 246 this.Log(LogLevel.Warning, "Unsupported CLKSEL value: {0}", clockSelect.Value); 247 break; 248 default: 249 this.Log(LogLevel.Error, "Invalid CLKSEL value: 0x{0:X}", (uint)clockSelect.Value); 250 break; 251 } 252 253 if(frequencySet != InvalidFrequency) 254 { 255 this.Log(LogLevel.Debug, "Updating timer's frequency to {0} KHz; CLKSEL={1} ({2})", frequencySet / KHz, (uint)clockSelect.Value, clockSelect.Value); 256 systemTimer.Frequency = frequencySet; 257 Array.ForEach(compareRegisters, register => register.Frequency = frequencySet); 258 } 259 // CLKSEL influences the timer state depending on whether the frequency is valid or not. 260 UpdateSystemTimerState(); 261 } 262 UpdateSystemTimerState()263 private void UpdateSystemTimerState() 264 { 265 systemTimer.Enabled = !freeze.Value && !clear.Value && systemTimer.Frequency != InvalidFrequency; 266 if(clear.Value) 267 { 268 systemTimer.ResetValue(); 269 } 270 Array.ForEach(compareRegisters, register => register.UpdateState()); 271 } 272 273 private IFlagRegisterField clear; 274 private IEnumRegisterField<ClockSelectValues> clockSelect; 275 private IFlagRegisterField freeze; 276 private IFlagRegisterField overflowInterruptEnable; 277 private IFlagRegisterField overflowInterruptStatus; 278 279 private readonly CaptureRegister[] captureRegisters = new CaptureRegister[CaptureRegistersCount]; 280 private readonly CompareRegister[] compareRegisters = new CompareRegister[CompareRegistersCount]; 281 private readonly GPIO[] interruptOutputs = new GPIO[InterruptOutputsCount]; 282 private readonly LimitTimer systemTimer; 283 284 private const uint CompareRegistersCount = 8; 285 private const uint CaptureRegistersCount = 4; 286 private const uint InterruptOutputsCount = 9; 287 // It's used for CLKSEL options which stop the timer. 0 can't be set as the timer's frequency, hence 1. 288 private const long InvalidFrequency = 1; 289 290 private class CaptureRegister 291 { CaptureRegister(AmbiqApollo4_SystemTimer owner, int index)292 public CaptureRegister(AmbiqApollo4_SystemTimer owner, int index) 293 { 294 this.owner = owner; 295 var nameSuffix = (char)('A' + index); 296 name = $"CAPTURE_{nameSuffix}"; 297 } 298 OnGPIO(int number, bool high)299 public void OnGPIO(int number, bool high) 300 { 301 if(Enabled.Value 302 && (int)TriggerSourceGPIOPinNumber.Value == number 303 // The 'value' is 'true' here if the opposite (low to high) transition has just occurred. 304 && CaptureOnHighToLowGPIOTransition.Value != high) 305 { 306 ValueCaptured = owner.Value; 307 InterruptStatus = true; 308 owner.Log(LogLevel.Debug, "{0}: Register set with value: 0x{1:X}", name, ValueCaptured); 309 } 310 } 311 Reset()312 public void Reset() 313 { 314 interruptEnable = false; 315 interruptStatus = false; 316 ValueCaptured = 0; 317 } 318 319 public bool InterruptEnable 320 { 321 get => interruptEnable; 322 set 323 { 324 owner.Log(LogLevel.Noisy, "{0}: Setting Interrupt Enable to: {1}", name, value); 325 interruptEnable = value; 326 owner.UpdateCaptureOverflowIRQ(); 327 } 328 } 329 330 public bool InterruptStatus 331 { 332 get => interruptStatus; 333 set 334 { 335 owner.Log(LogLevel.Noisy, "{0}: Setting Interrupt Status to: {1}", name, value); 336 interruptStatus = value; 337 owner.UpdateCaptureOverflowIRQ(); 338 } 339 } 340 341 public IFlagRegisterField CaptureOnHighToLowGPIOTransition; 342 public IFlagRegisterField Enabled; 343 public IValueRegisterField TriggerSourceGPIOPinNumber; 344 public uint ValueCaptured; 345 346 private bool interruptEnable; 347 private bool interruptStatus; 348 349 private readonly string name; 350 private readonly AmbiqApollo4_SystemTimer owner; 351 } 352 353 private class CompareRegister 354 { CompareRegister(AmbiqApollo4_SystemTimer owner, int index, GPIO irq, LimitTimer systemTimer)355 public CompareRegister(AmbiqApollo4_SystemTimer owner, int index, GPIO irq, LimitTimer systemTimer) 356 { 357 this.irq = irq; 358 this.owner = owner; 359 this.systemTimer = systemTimer; 360 var nameSuffix = (char)('A' + index); 361 name = $"COMPARE_{nameSuffix}"; 362 363 innerTimer = new ComparingTimer(owner.machine.ClockSource, owner.Frequency, owner, name, direction: Direction.Ascending, limit: uint.MaxValue, 364 enabled: false, workMode: WorkMode.Periodic, eventEnabled: true, compare: 0, divider: 1); 365 innerTimer.CompareReached += () => 366 { 367 owner.Log(LogLevel.Debug, "{0}: Compare value (0x{1:X}) reached", name, innerTimer.Compare); 368 InterruptStatus = true; 369 }; 370 } 371 Reset()372 public void Reset() 373 { 374 enabled = false; 375 innerTimer.Reset(); 376 interruptEnable = false; 377 interruptStatus = false; 378 irq.Unset(); 379 } 380 UpdateState()381 public void UpdateState() 382 { 383 if(enabled && systemTimer.Enabled) 384 { 385 innerTimer.Value = owner.Value; 386 innerTimer.Enabled = true; 387 } 388 else 389 { 390 innerTimer.Enabled = false; 391 } 392 } 393 394 public uint CompareValue 395 { 396 get => (uint)innerTimer.Compare; 397 set 398 { 399 owner.Log(LogLevel.Noisy, "{0}: Setting compare value to: 0x{1:X}", name, value); 400 innerTimer.Compare = value; 401 } 402 } 403 404 public bool Enabled 405 { 406 get => enabled; 407 set 408 { 409 owner.Log(LogLevel.Noisy, "{0}: Setting Enabled to: {1}", name, value); 410 enabled = value; 411 UpdateState(); 412 } 413 } 414 415 public long Frequency 416 { 417 get => innerTimer.Frequency; 418 set => innerTimer.Frequency = value; 419 } 420 421 public bool InterruptEnable 422 { 423 get => interruptEnable; 424 set 425 { 426 owner.Log(LogLevel.Noisy, "{0}: Setting interrupt enable to: {1}", name, value); 427 interruptEnable = value; 428 UpdateIRQ(); 429 } 430 } 431 432 public bool InterruptStatus 433 { 434 get => interruptStatus; 435 set 436 { 437 owner.Log(LogLevel.Noisy, "{0}: Setting interrupt status to: {1}", name, value); 438 interruptStatus = value; 439 UpdateIRQ(); 440 } 441 } 442 UpdateIRQ()443 private void UpdateIRQ() 444 { 445 var newIrqState = interruptEnable && interruptStatus; 446 if(irq.IsSet != newIrqState) 447 { 448 owner.Log(LogLevel.Debug, "{0}: {1} IRQ", name, newIrqState ? "Setting" : "Clearing"); 449 irq.Set(newIrqState); 450 } 451 } 452 453 private bool enabled; 454 private bool interruptEnable; 455 private bool interruptStatus; 456 457 private readonly ComparingTimer innerTimer; 458 private readonly GPIO irq; 459 private readonly string name; 460 private readonly AmbiqApollo4_SystemTimer owner; 461 private readonly LimitTimer systemTimer; 462 } 463 464 private enum ClockSelectValues 465 { 466 NOCLK, 467 HFRC_6MHZ, 468 HFRC_375KHZ, 469 XTAL_32KHZ, 470 XTAL_16KHZ, 471 XTAL_1KHZ, 472 LFRC_1KHZ, 473 CTIMER0, 474 CTIMER1, 475 } 476 477 private enum Registers : long 478 { 479 Configuration = 0x0, 480 SystemTimerCount = 0x4, 481 CaptureControl0 = 0x10, 482 CaptureControl1 = 0x14, 483 CaptureControl2 = 0x18, 484 CaptureControl3 = 0x1C, 485 Compare0 = 0x20, 486 Compare1 = 0x24, 487 Compare2 = 0x28, 488 Compare3 = 0x2C, 489 Compare4 = 0x30, 490 Compare5 = 0x34, 491 Compare6 = 0x38, 492 Compare7 = 0x3C, 493 Capture0 = 0x40, 494 Capture1 = 0x44, 495 Capture2 = 0x48, 496 Capture3 = 0x4C, 497 SystemTimerNVRAM0 = 0x50, 498 SystemTimerNVRAM1 = 0x54, 499 SystemTimerNVRAM2 = 0x58, 500 InterruptEnable = 0x100, 501 InterruptStatus = 0x104, 502 InterruptClear = 0x108, 503 InterruptSet = 0x10C, 504 } 505 } 506 } 507