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 System; 8 using System.Collections.Generic; 9 using Antmicro.Renode.Core; 10 using Antmicro.Renode.Core.Structure.Registers; 11 using Antmicro.Renode.Peripherals.Bus; 12 using Antmicro.Renode.Time; 13 using Antmicro.Renode.Logging; 14 15 namespace Antmicro.Renode.Peripherals.Timers 16 { 17 // This class does not implement advanced-control timers interrupts 18 [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] 19 public class STM32L0_LpTimer : LimitTimer, IDoubleWordPeripheral, IKnownSize 20 { STM32L0_LpTimer(IMachine machine, long frequency)21 public STM32L0_LpTimer(IMachine machine, long frequency) : base(machine.ClockSource, frequency, limit: 0x1, direction: Direction.Ascending, enabled: false, eventEnabled: true) 22 { 23 IRQ = new GPIO(); 24 25 compareTimer = new LimitTimer(machine.ClockSource, frequency, this, nameof(compareTimer), 0x1, Direction.Ascending, workMode: WorkMode.OneShot, eventEnabled: true, autoUpdate: true); 26 27 LimitReached += () => 28 { 29 this.Log(LogLevel.Debug, "AutoReload reached"); 30 autoReloadMatchInterruptStatus.Value = true; 31 if(Mode == WorkMode.Periodic) 32 { 33 TryEnableCompareTimer(compareValue.Value); 34 } 35 UpdateInterrupts(); 36 }; 37 38 compareTimer.LimitReached += () => 39 { 40 this.Log(LogLevel.Debug, "Compare reached"); 41 compareTimer.Enabled = false; 42 compareMatchInterruptStatus.Value = true; 43 UpdateInterrupts(); 44 }; 45 46 var registersMap = new Dictionary<long, DoubleWordRegister> 47 { 48 {(long)Registers.InterruptAndStatus, new DoubleWordRegister(this) 49 .WithFlag(0, out compareMatchInterruptStatus, FieldMode.Read, name:"Compare match (CMPM)") 50 .WithFlag(1, out autoReloadMatchInterruptStatus, FieldMode.Read, name: "Autoreload match (ARRM)") 51 .WithTaggedFlag("External trigger edge event (EXTTRIG)", 2) 52 .WithFlag(3, out compareRegisterUpdateOkStatus, FieldMode.Read, name: "Compare register update OK (CMPOK/CMP1OK)") 53 .WithFlag(4, out autoReloadRegisterUpdateOkStatus, FieldMode.Read, name: "Autoreload register update OK (ARROK)") 54 .WithTaggedFlag("Counter direction change down to up (UP)", 5) 55 .WithTaggedFlag("Counter direction change up to down (DOWN)", 6) 56 .WithReservedBits(7, 1) 57 .WithFlag(8, out repetitionUpdateOkStatus, FieldMode.Read, name: "Repetition register update OK (REPOK)") 58 .WithReservedBits(9, 15) 59 .WithFlag(24, out interruptEnableRegisterUpdateOkStatus, FieldMode.Read, name: "Interrupt enable register update OK (DIEROK)") 60 .WithReservedBits(25, 7) 61 }, 62 63 {(long)Registers.InterruptClear, new DoubleWordRegister(this) 64 .WithFlag(0, FieldMode.WriteOneToClear, 65 writeCallback: (_, val) => 66 { 67 if(!val) 68 { 69 return; 70 } 71 72 compareMatchInterruptStatus.Value = false; 73 }, 74 name: "Compare match clear flag (CMPMCF)") 75 .WithFlag(1, FieldMode.WriteOneToClear, 76 writeCallback: (_, val) => 77 { 78 if(!val) 79 { 80 return; 81 } 82 83 autoReloadMatchInterruptStatus.Value = false; 84 }, 85 name: "Autoreload match clear flag (ARRMCF)") 86 .WithTaggedFlag("External trigger edge event clear flag (EXTTRIGCF)", 2) 87 .WithFlag(3, FieldMode.WriteOneToClear, 88 writeCallback: (_, val) => 89 { 90 if(!val) 91 { 92 return; 93 } 94 compareRegisterUpdateOkStatus.Value = false; 95 }, 96 name: "Compare register update OK clear flag (CMPOKCF)") 97 .WithFlag(4, FieldMode.WriteOneToClear, 98 writeCallback: (_, val) => 99 { 100 if(!val) 101 { 102 return; 103 } 104 autoReloadRegisterUpdateOkStatus.Value = false; 105 }, 106 name: "Autoreload register update OK clear flag (ARROKCF)") 107 .WithTaggedFlag("Counter direction change down to up clear flag (UPCF)", 5) 108 .WithTaggedFlag("Counter direction change up to down clear flag (DOWNCF)", 6) 109 .WithReservedBits(7, 1) 110 .WithFlag(8, FieldMode.WriteOneToClear, 111 writeCallback: (_, val) => 112 { 113 if(!val) 114 { 115 return; 116 } 117 repetitionUpdateOkStatus.Value = false; 118 }, 119 name: "Repetition register update OK clear flag (REPOKCF)") 120 .WithReservedBits(9, 15) 121 .WithFlag(24, FieldMode.WriteOneToClear, 122 writeCallback: (_, val) => 123 { 124 if(!val) 125 { 126 return; 127 } 128 interruptEnableRegisterUpdateOkStatus.Value = false; 129 }, 130 name: "Interrupt enable register update OK clear flag (DIEROKCF)") 131 .WithWriteCallback( (_, __) => UpdateInterrupts()) 132 }, 133 134 // Caution: The LPTIM_IER register must only be modified when the LPTIM is disabled (ENABLE bit reset to '0') 135 {(long)Registers.InterruptEnable, new DoubleWordRegister(this) 136 .WithFlag(0, out compareMatchInterruptEnable, name:"Compare match interrupt enable (CMPMIE)") 137 .WithFlag(1, out autoReloadMatchInterruptEnable, name: "Autoreload match interrupt enable (ARRMIE)") 138 .WithTaggedFlag("External trigger edge event interrupt enable (EXTTRIGIE)", 2) 139 .WithFlag(3, out compareRegisterUpdateOkEnable, name: "Compare register update OK interrupt enable (CMPOKIE)") 140 .WithFlag(4, out autoReloadRegisterUpdateOkEnable, name: "Autoreload register update OK interrupt enable (ARROKIE)") 141 .WithFlag(5, name: "Counter direction change down to up interrupt enable (UPIE)") 142 .WithFlag(6, name: "Counter direction change up to down interrupt enable (DOWNIE)") 143 .WithReservedBits(7, 1) 144 .WithFlag(8, out repetitionUpdateOkEnable, name: "Repetition register update OK interrupt Enable (REPOKIE)") 145 .WithReservedBits(9, 23) 146 .WithWriteCallback((_, __) => {interruptEnableRegisterUpdateOkStatus.Value = true;}) 147 }, 148 149 // Caution: The LPTIM_CFGR register must only be modified when the LPTIM is disabled (ENABLE bit reset to '0') 150 {(long)Registers.Configuration, new DoubleWordRegister(this) 151 .WithTaggedFlag("Clock selector (CKSEL)", 0) 152 .WithTag("Clock Polarity (CKPOL)", 1, 2) 153 .WithTag("Configurable digital filter for external clock (CKFLT)", 3, 2) 154 .WithReservedBits(5, 1) 155 .WithTag("Configurable digital filter for trigger (TRGFLT)", 6, 2) 156 .WithReservedBits(8, 1) 157 .WithValueField(9, 3, 158 writeCallback: (_, val) => 159 { 160 var divider = (int)Math.Pow(2, val); 161 Divider = divider; 162 compareTimer.Divider = divider; 163 }, 164 valueProviderCallback: _ => (uint)Math.Log(Divider, 2), 165 name: "Clock prescaler (PSC)") 166 .WithReservedBits(12, 1) 167 .WithTag("Trigger Selector (TRIGSEL)", 13, 3) 168 .WithReservedBits(16, 1) 169 .WithTag("Trigger Enable and Polarity (TRIGEN)", 17, 2) 170 .WithTaggedFlag("Timeout enable (TIMOUT)", 19) 171 .WithTaggedFlag("Waveform Shape (WAVE)", 20) 172 .WithTaggedFlag("Waveform shape polarity (WAVPOL)", 21) 173 .WithTaggedFlag("Registers update mode (PRELOAD)", 22) 174 .WithTaggedFlag("Counter mode enabled (COUNTMODE)", 23) 175 .WithTaggedFlag("Encoder mode enable (ENC)", 24) 176 .WithReservedBits(25, 7) 177 }, 178 179 {(long)Registers.Control, new DoubleWordRegister(this) 180 .WithFlag(0, out enabled, name: "LPTIM enable (ENABLE)") 181 .WithFlag(1, out var singleStart, name: "LPTIM start in Single mode (SNGSTRT)") 182 .WithFlag(2, out var continousStart, name: "Timer start in Continuous mode (CNTSTRT)") 183 .WithReservedBits(3, 29) 184 .WithWriteCallback((_, __) => 185 { 186 if(enabled.Value) 187 { 188 if(singleStart.Value && continousStart.Value) 189 { 190 this.Log(LogLevel.Warning, "Selected both single and contiuous modes. Ignoring operation"); 191 singleStart.Value = false; 192 continousStart.Value = false; 193 return; 194 } 195 196 if(singleStart.Value) 197 { 198 this.Log(LogLevel.Debug, "Enabling timer in the single shot mode"); 199 Mode = WorkMode.OneShot; 200 Enabled = true; 201 TryEnableCompareTimer(compareValue.Value); 202 } 203 204 if(continousStart.Value) 205 { 206 this.Log(LogLevel.Debug, "Enabling timer in the continous mode"); 207 Mode = WorkMode.Periodic; 208 Enabled = true; 209 TryEnableCompareTimer(compareValue.Value); 210 } 211 } 212 else 213 { 214 this.Log(LogLevel.Debug, "Disabling timer"); 215 this.Enabled = false; 216 compareTimer.Enabled = false; 217 } 218 }) 219 }, 220 221 // Caution: The LPTIM_CMP register must only be modified when the LPTIM is enabled (ENABLE bit set to '1'). 222 {(long)Registers.Compare, new DoubleWordRegister(this) 223 .WithValueField(0, 16, out compareValue, name: "Compare value (CMP)", 224 writeCallback: (_, val) => 225 { 226 TryEnableCompareTimer(val); 227 compareRegisterUpdateOkStatus.Value = true; 228 UpdateInterrupts(); 229 }) 230 .WithReservedBits(16, 16) 231 }, 232 233 // Caution: The LPTIM_ARR register must only be modified when the LPTIM is enabled (ENABLE bit set to '1'). 234 {(long)Registers.AutoReload, new DoubleWordRegister(this, 0x1) 235 .WithValueField(0, 16, 236 writeCallback: (_, val) => 237 { 238 Limit = val; 239 autoReloadRegisterUpdateOkStatus.Value = true; 240 UpdateInterrupts(); 241 }, 242 valueProviderCallback: _ => (uint)Limit, 243 name: "Autoreload register (ARR)") 244 .WithReservedBits(16, 16) 245 }, 246 247 {(long)Registers.Counter, new DoubleWordRegister(this) 248 .WithValueField(0, 16, FieldMode.Read, name: "Counter value (CNT)", 249 valueProviderCallback: _ => (uint)this.Value) 250 .WithReservedBits(16, 16) 251 }, 252 253 {(long)Registers.Repetition, new DoubleWordRegister(this) 254 .WithTag("Repetition register value (REP)", 0, 8) 255 .WithReservedBits(8, 24) 256 .WithWriteCallback((_, __) => 257 { 258 repetitionUpdateOkStatus.Value = true; 259 UpdateInterrupts(); 260 }) 261 }, 262 }; 263 264 registers = new DoubleWordRegisterCollection(this, registersMap); 265 Reset(); 266 } 267 ReadDoubleWord(long offset)268 public uint ReadDoubleWord(long offset) 269 { 270 return registers.Read(offset); 271 } 272 WriteDoubleWord(long offset, uint value)273 public void WriteDoubleWord(long offset, uint value) 274 { 275 registers.Write(offset, value); 276 } 277 Reset()278 public override void Reset() 279 { 280 base.Reset(); 281 registers.Reset(); 282 IRQ.Set(false); 283 } 284 285 public GPIO IRQ { get; } 286 287 public long Size => 0x400; 288 UpdateInterrupts()289 private void UpdateInterrupts() 290 { 291 var flag = false; 292 293 flag |= autoReloadMatchInterruptEnable.Value && autoReloadMatchInterruptStatus.Value; 294 flag |= autoReloadRegisterUpdateOkEnable.Value && autoReloadRegisterUpdateOkStatus.Value; 295 flag |= compareRegisterUpdateOkEnable.Value && compareRegisterUpdateOkStatus.Value; 296 flag |= compareMatchInterruptEnable.Value && compareMatchInterruptStatus.Value; 297 flag |= repetitionUpdateOkStatus.Value && repetitionUpdateOkEnable.Value; 298 299 this.Log(LogLevel.Debug, "Setting IRQ to {0}", flag); 300 IRQ.Set(flag); 301 } 302 TryEnableCompareTimer(ulong compareValue)303 private void TryEnableCompareTimer(ulong compareValue) 304 { 305 if(compareValue == 0) 306 { 307 this.Log(LogLevel.Debug, "Compare value cannot be 0. Timer will not be set"); 308 return; 309 } 310 311 var autoReloadValue = GetValueAndLimit(out var autoReloadLimit); 312 if(compareValue >= autoReloadLimit) 313 { 314 this.Log(LogLevel.Warning, "Compare value ({0}) cannot be greater than auto reload limit ({1}). Compare value will be ignored", compareValue, autoReloadLimit); 315 compareTimer.Enabled = false; 316 return; 317 } 318 319 // We only want to enable the compare timer if there is still a chance that it will trigger 320 // If the timer is periodic then compare timer will be enabled on auto reload 321 // If the timer is one shot then there is not need to enable this timer 322 if(compareValue > autoReloadValue) 323 { 324 compareTimer.Limit = compareValue - autoReloadValue; 325 compareTimer.Enabled = true; 326 } 327 } 328 329 private readonly LimitTimer compareTimer; 330 331 private readonly DoubleWordRegisterCollection registers; 332 333 private readonly IValueRegisterField compareValue; 334 335 private readonly IFlagRegisterField autoReloadMatchInterruptEnable; 336 private readonly IFlagRegisterField autoReloadMatchInterruptStatus; 337 private readonly IFlagRegisterField autoReloadRegisterUpdateOkEnable; 338 private readonly IFlagRegisterField autoReloadRegisterUpdateOkStatus; 339 private readonly IFlagRegisterField compareRegisterUpdateOkEnable; 340 private readonly IFlagRegisterField compareRegisterUpdateOkStatus; 341 private readonly IFlagRegisterField compareMatchInterruptEnable; 342 private readonly IFlagRegisterField compareMatchInterruptStatus; 343 private readonly IFlagRegisterField interruptEnableRegisterUpdateOkStatus; 344 private readonly IFlagRegisterField repetitionUpdateOkStatus; 345 private readonly IFlagRegisterField repetitionUpdateOkEnable; 346 private readonly IFlagRegisterField enabled; 347 348 private enum Registers : long 349 { 350 // LPTIM_ISR 351 InterruptAndStatus = 0x0, 352 // LPTIM_ICR 353 InterruptClear = 0x04, 354 // LPTIM_IER 355 InterruptEnable = 0x08, 356 // LPTIM_CFGR 357 Configuration = 0x0C, 358 // LPTIM_CR 359 Control = 0x10, 360 // LPTIM_CMP 361 Compare = 0x14, 362 // LPTIM_ARR 363 AutoReload = 0x18, 364 // LPTIM_CNT 365 Counter = 0x1C, 366 // LPTIM_RCR 367 Repetition = 0x28, 368 } 369 } 370 } 371 372