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.Logging;
9 using Antmicro.Renode.Time;
10 using System;
11 
12 namespace Antmicro.Renode.Peripherals.Timers
13 {
14     public class EFR32_RTCCCounter
15     {
EFR32_RTCCCounter(IMachine machine, long frequency, IPeripheral owner, string localName, int counterWidth = 32, int preCounterWidth = 32, int numberOfCaptureCompareChannels = 3)16         public EFR32_RTCCCounter(IMachine machine, long frequency, IPeripheral owner, string localName, int counterWidth = 32, int preCounterWidth = 32, int numberOfCaptureCompareChannels = 3)
17         {
18             var counterLimit = (1UL << counterWidth) - 1;
19             var preCounterLimit = (1UL << preCounterWidth) - 1;
20 
21             coreTimer = new LimitTimer(machine.ClockSource, frequency, owner, localName, counterLimit, Direction.Ascending, eventEnabled: true, autoUpdate: true);
22             coreTimerTick = new LimitTimer(machine.ClockSource, frequency, owner, $"{localName}-tick", 1, Direction.Ascending, eventEnabled: true, autoUpdate: true);
23             preTimer = new LimitTimer(machine.ClockSource, frequency, owner, $"{localName}-pre", preCounterLimit, Direction.Ascending, eventEnabled: true, autoUpdate: true);
24 
25             channels = new CCChannel[numberOfCaptureCompareChannels];
26             for(var i = 0; i < numberOfCaptureCompareChannels; ++i)
27             {
28                 var channelTimer = new LimitTimer(machine.ClockSource, frequency, owner, $"{localName}-cc{i}", counterLimit, Direction.Ascending, workMode: WorkMode.OneShot, eventEnabled: true, autoUpdate: true);
29                 channels[i] = new CCChannel(channelTimer, coreTimer, owner, i);
30             }
31             this.machine = machine;
32         }
33 
Reset()34         public void Reset()
35         {
36             coreTimer.Reset();
37             coreTimerTick.Reset();
38             preTimer.Reset();
39             foreach(var channel in channels)
40             {
41                 channel.Reset();
42             }
43         }
44 
45         public ulong Counter
46         {
47             get
48             {
49                 TrySyncTime();
50                 return coreTimer.Value;
51             }
52             set
53             {
54                 coreTimer.Value = value;
55                 foreach(var channel in channels)
56                 {
57                     channel.Value = value;
58                 }
59             }
60         }
61 
62         public ulong PreCounter
63         {
64             get
65             {
66                 TrySyncTime();
67                 return preTimer.Value;
68             }
69             set => preTimer.Value = value;
70         }
71 
72         public bool Enabled
73         {
74             get => coreTimer.Enabled;
75             set
76             {
77                 if(value == coreTimer.Enabled)
78                 {
79                     return;
80                 }
81                 coreTimer.Enabled = value;
82                 coreTimerTick.Enabled = value;
83                 preTimer.Enabled = value;
84                 foreach(var channel in channels)
85                 {
86                     channel.Refresh();
87                 }
88             }
89         }
90 
91         public int Prescaler
92         {
93             get => coreTimer.Divider;
94             set
95             {
96                 if(value == coreTimer.Divider)
97                 {
98                     return;
99                 }
100                 coreTimer.Divider = value;
101                 coreTimerTick.Divider = value;
102                 foreach(var channel in channels)
103                 {
104                     channel.Divider = value;
105                 }
106             }
107         }
108 
109         public ICCChannel[] Channels => channels;
110 
111         public event Action LimitReached
112         {
113             add => coreTimer.LimitReached += value;
114             remove => coreTimer.LimitReached -= value;
115         }
116 
117         public event Action CounterTicked
118         {
119             add => coreTimerTick.LimitReached += value;
120             remove => coreTimerTick.LimitReached -= value;
121         }
122 
TrySyncTime()123         private bool TrySyncTime()
124         {
125             if(machine.SystemBus.TryGetCurrentCPU(out var cpu))
126             {
127                 cpu.SyncTime();
128                 return true;
129             }
130             return false;
131         }
132 
133         private readonly CCChannel[] channels;
134         private readonly LimitTimer coreTimer;
135         private readonly LimitTimer coreTimerTick;
136         private readonly LimitTimer preTimer;
137         private readonly IMachine machine;
138 
139         public enum CCChannelMode
140         {
141             Off = 0,
142             InputCapture = 1,
143             OutputCompare = 2,
144         }
145 
146         public enum CCChannelComparisonBase
147         {
148             Counter = 0x0,
149             PreCounter = 0x1,
150         }
151 
152         public interface ICCChannel
153         {
154             CCChannelMode Mode { get; set; }
155             CCChannelComparisonBase ComparisonBase { get; set; }
156             ulong CompareValue { get; set; }
157             event Action CompareReached;
158         }
159 
160         private class CCChannel : ICCChannel
161         {
CCChannel(LimitTimer ownTimer, LimitTimer coreTimer, IPeripheral owner, int id)162             public CCChannel(LimitTimer ownTimer, LimitTimer coreTimer, IPeripheral owner, int id)
163             {
164                 this.channelTimer = ownTimer;
165                 this.coreTimer = coreTimer;
166                 this.owner = owner;
167                 this.id = id;
168             }
169 
Reset()170             public void Reset()
171             {
172                 channelTimer.Reset();
173             }
174 
Refresh()175             public void Refresh()
176             {
177                 var isEnabled = mode != CCChannelMode.Off;
178                 var isTargetInThePast = CompareValue < coreTimer.Value;
179                 var isAlreadyRunning = channelTimer.Enabled;
180                 var shouldEnableTimer = isEnabled && !isTargetInThePast;
181 
182                 if(!isAlreadyRunning && shouldEnableTimer)
183                 {
184                     // Reload the counter value, so the individual timer used by the channel
185                     // is synchronized with the core timer
186                     channelTimer.Value = coreTimer.Value;
187                 }
188                 channelTimer.Enabled = shouldEnableTimer;
189             }
190 
191             public CCChannelMode Mode
192             {
193                 get => mode;
194                 set
195                 {
196                     if(value == CCChannelMode.InputCapture)
197                     {
198                         owner.Log(LogLevel.Warning, "Attempt to set mode for Channel #{0} ignored. Input capture is not supported.", id);
199                         return;
200                     }
201                     mode = value;
202                     Refresh();
203                 }
204             }
205 
206             public CCChannelComparisonBase ComparisonBase
207             {
208                 get => comparisonBase;
209                 set
210                 {
211                     if(value == CCChannelComparisonBase.PreCounter)
212                     {
213                         owner.Log(LogLevel.Warning, "Attempt to set comparison base for Channel #{0} ignored. Pre-counter is not supported.", id);
214                         return;
215                     }
216                     comparisonBase = value;
217                     Refresh();
218                 }
219             }
220 
221             public ulong CompareValue
222             {
223                 get => channelTimer.Limit;
224                 set
225                 {
226                     // When changing the limit value, the value of the LimitTimer is reset
227                     // Save and restore it, so changing the limit while the timer is running
228                     // doesn't break the currently set alarm.
229                     var oldCounter = channelTimer.Value;
230                     channelTimer.Limit = value;
231                     channelTimer.Value = oldCounter;
232                     Refresh();
233                 }
234             }
235 
236             public int Divider
237             {
238                 set
239                 {
240                     channelTimer.Divider = value;
241                 }
242             }
243 
244             public ulong Value
245             {
246                 set
247                 {
248                     channelTimer.Value = value;
249                     Refresh();
250                 }
251             }
252 
253             public event Action CompareReached
254             {
255                 add => channelTimer.LimitReached += value;
256                 remove => channelTimer.LimitReached -= value;
257             }
258 
259             private CCChannelMode mode;
260             private CCChannelComparisonBase comparisonBase;
261             private readonly LimitTimer channelTimer;
262             private readonly LimitTimer coreTimer;
263             private readonly IPeripheral owner;
264             private readonly int id;
265         }
266     }
267 }