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