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