1 //
2 // Copyright (c) 2010-2025 Antmicro
3 // Copyright (c) 2022-2025 Silicon Labs
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 
9 using System;
10 using System.Diagnostics;
11 using System.IO;
12 using System.Collections.Generic;
13 using Antmicro.Renode.Core;
14 using Antmicro.Renode.Core.Structure.Registers;
15 using Antmicro.Renode.Logging;
16 using Antmicro.Renode.Exceptions;
17 using Antmicro.Renode.Time;
18 using Antmicro.Renode.Peripherals.Timers;
19 using Antmicro.Renode.Peripherals.CPU;
20 using Antmicro.Renode.Peripherals.Bus;
21 
22 namespace Antmicro.Renode.Peripherals.Miscellaneous.SiLabs
23 {
24     // Allows for the viewing of register contents when debugging
25     [AllowedTranslations(AllowedTranslation.ByteToDoubleWord)]
26     public class EFR32xG2_HFXO_2 : IHFXO_EFR32xG2, IDoubleWordPeripheral
27     {
EFR32xG2_HFXO_2(Machine machine, uint startupDelayTicks)28         public EFR32xG2_HFXO_2(Machine machine, uint startupDelayTicks)
29         {
30             this.machine = machine;
31             this.delayTicks = startupDelayTicks;
32 
33             timer = new LimitTimer(machine.ClockSource, 32768, this, "hfxodelay", 0xFFFFFFFFUL, direction: Direction.Ascending,
34                                    enabled: false, workMode: WorkMode.OneShot, eventEnabled: true, autoUpdate: true);
35             timer.LimitReached += OnStartUpTimerExpired;
36 
37             IRQ = new GPIO();
38 
39             registersCollection = BuildRegistersCollection();
40         }
41 
Reset()42         public void Reset()
43         {
44             timer.Enabled = false;
45         }
46 
ReadDoubleWord(long offset)47         public uint ReadDoubleWord(long offset)
48         {
49             return ReadRegister(offset);
50         }
51 
ReadRegister(long offset, bool internal_read = false)52         private uint ReadRegister(long offset, bool internal_read = false)
53         {
54             var result = 0U;
55             long internal_offset = offset;
56 
57             // Set, Clear, Toggle registers should only be used for write operations. But just in case we convert here as well.
58             if (offset >= SetRegisterOffset && offset < ClearRegisterOffset)
59             {
60                 // Set register
61                 internal_offset = offset - SetRegisterOffset;
62                 if(!internal_read)
63                 {
64                     this.Log(LogLevel.Noisy, "SET Operation on {0}, offset=0x{1:X}, internal_offset=0x{2:X}", (Registers)internal_offset, offset, internal_offset);
65                 }
66             } else if (offset >= ClearRegisterOffset && offset < ToggleRegisterOffset)
67             {
68                 // Clear register
69                 internal_offset = offset - ClearRegisterOffset;
70                 if(!internal_read)
71                 {
72                     this.Log(LogLevel.Noisy, "CLEAR Operation on {0}, offset=0x{1:X}, internal_offset=0x{2:X}", (Registers)internal_offset, offset, internal_offset);
73                 }
74             } else if (offset >= ToggleRegisterOffset)
75             {
76                 // Toggle register
77                 internal_offset = offset - ToggleRegisterOffset;
78                 if(!internal_read)
79                 {
80                     this.Log(LogLevel.Noisy, "TOGGLE Operation on {0}, offset=0x{1:X}, internal_offset=0x{2:X}", (Registers)internal_offset, offset, internal_offset);
81                 }
82             }
83 
84             if(!registersCollection.TryRead(internal_offset, out result))
85             {
86                 if(!internal_read)
87                 {
88                     this.Log(LogLevel.Noisy, "Unhandled read at offset 0x{0:X} ({1}).", internal_offset, (Registers)internal_offset);
89                 }
90             }
91             else
92             {
93                 if(!internal_read)
94                 {
95                     this.Log(LogLevel.Noisy, "Read at offset 0x{0:X} ({1}), returned 0x{2:X}.", internal_offset, (Registers)internal_offset, result);
96                 }
97             }
98 
99             return result;
100         }
101 
WriteDoubleWord(long offset, uint value)102         public void WriteDoubleWord(long offset, uint value)
103         {
104             WriteRegister(offset, value);
105         }
106 
WriteRegister(long offset, uint value, bool internal_write = false)107         private void WriteRegister(long offset, uint value, bool internal_write = false)
108         {
109             machine.ClockSource.ExecuteInLock(delegate {
110                 long internal_offset = offset;
111                 uint internal_value = value;
112 
113                 if (offset >= SetRegisterOffset && offset < ClearRegisterOffset)
114                 {
115                     // Set register
116                     internal_offset = offset - SetRegisterOffset;
117                     uint old_value = ReadRegister(internal_offset, true);
118                     internal_value = old_value | value;
119                     this.Log(LogLevel.Noisy, "SET Operation on {0}, offset=0x{1:X}, internal_offset=0x{2:X}, SET_value=0x{3:X}, old_value=0x{4:X}, new_value=0x{5:X}", (Registers)internal_offset, offset, internal_offset, value, old_value, internal_value);
120                 } else if (offset >= ClearRegisterOffset && offset < ToggleRegisterOffset)
121                 {
122                     // Clear register
123                     internal_offset = offset - ClearRegisterOffset;
124                     uint old_value = ReadRegister(internal_offset, true);
125                     internal_value = old_value & ~value;
126                     this.Log(LogLevel.Noisy, "CLEAR Operation on {0}, offset=0x{1:X}, internal_offset=0x{2:X}, CLEAR_value=0x{3:X}, old_value=0x{4:X}, new_value=0x{5:X}", (Registers)internal_offset, offset, internal_offset, value, old_value, internal_value);
127                 } else if (offset >= ToggleRegisterOffset)
128                 {
129                     // Toggle register
130                     internal_offset = offset - ToggleRegisterOffset;
131                     uint old_value = ReadRegister(internal_offset, true);
132                     internal_value = old_value ^ value;
133                     this.Log(LogLevel.Noisy, "TOGGLE Operation on {0}, offset=0x{1:X}, internal_offset=0x{2:X}, TOGGLE_value=0x{3:X}, old_value=0x{4:X}, new_value=0x{5:X}", (Registers)internal_offset, offset, internal_offset, value, old_value, internal_value);
134                 }
135 
136                 this.Log(LogLevel.Noisy, "Write at offset 0x{0:X} ({1}), value 0x{2:X}.", internal_offset, (Registers)internal_offset, internal_value);
137 
138                 if(!registersCollection.TryWrite(internal_offset, internal_value))
139                 {
140                     this.Log(LogLevel.Noisy, "Unhandled write at offset 0x{0:X} ({1}), value 0x{2:X}.", internal_offset, (Registers)internal_offset, internal_value);
141                     return;
142                 }
143             });
144         }
145 
BuildRegistersCollection()146         private DoubleWordRegisterCollection BuildRegistersCollection()
147         {
148             var registerDictionary = new Dictionary<long, DoubleWordRegister>
149             {
150                 {(long)Registers.Control, new DoubleWordRegister(this, 0x00000002)
151                     .WithFlag(0, out forceEnable, writeCallback: (oldValue, newValue) =>
152                     {
153                         if (!oldValue && newValue)
154                         {
155                             enabled.Value = true;
156                             wakeUpSource = WakeUpSource.Force;
157                             StartDelayTimer();
158                         }
159                         else if (!newValue && disableOnDemand.Value)
160                         {
161                             enabled.Value = false;
162                         }
163                     }, name: "FORCEEN")
164                     .WithFlag(1, out disableOnDemand, writeCallback: (_, value) =>
165                     {
166                         if (value && !forceEnable.Value)
167                         {
168                             enabled.Value = false;
169                         }
170                         if (!value)
171                         {
172                             fsmLock.Value = true;
173                         }
174                     }, name: "DISONDEMAND")
175                     .WithTaggedFlag("KEEPWARM", 2)
176                     .WithReservedBits(3, 1)
177                     .WithTaggedFlag("FORCEXI2GNDANA", 4)
178                     .WithTaggedFlag("FORCEXO2GNDANA", 5)
179                     .WithReservedBits(6, 26)
180                 },
181                 {(long)Registers.Command, new DoubleWordRegister(this)
182                     .WithFlag(0, out ready, FieldMode.Write, writeCallback: (_, value) =>
183                     {
184                         coreBiasReady.Value = true;
185                     }, name: "COREBIASOPT")
186                     .WithFlag(1, out ready, FieldMode.Write, writeCallback: (_, value) =>
187                     {
188                         if (coreBiasReady.Value)
189                         {
190                             if (forceEnable.Value && disableOnDemand.Value)
191                             {
192                                 fsmLock.Value = false;
193                             }
194                         }
195                     }, name: "MANUALOVERRIDE")
196                     .WithReservedBits(2, 30)
197                 },
198                 {(long)Registers.Status, new DoubleWordRegister(this)
199                     .WithFlag(0, out ready, FieldMode.Read, name: "RDY")
200                     .WithFlag(1, out coreBiasReady, FieldMode.Read, name: "COREBIASOPTRDY")
201                     .WithReservedBits(2, 14)
202                     .WithFlag(16, out enabled, FieldMode.Read, name: "ENS")
203                     .WithTaggedFlag("HWREQ", 17)
204                     .WithReservedBits(18, 1)
205                     .WithTaggedFlag("ISWARM", 19)
206                     .WithReservedBits(20, 10)
207                     .WithFlag(30, out fsmLock, FieldMode.Read, name: "FSMLOCK")
208                     .WithFlag(31, out locked, FieldMode.Read, name: "LOCK")
209                 },
210                 {(long)Registers.InterruptFlags, new DoubleWordRegister(this)
211                     .WithFlag(0, out readyInterrupt, name: "RDY")
212                     .WithTaggedFlag("COREBIASOPTRDY", 1)
213                     .WithReservedBits(2, 27)
214                     .WithTaggedFlag("DNSERR", 29)
215                     .WithReservedBits(30, 1)
216                     .WithTaggedFlag("COREBIASOPTERR", 31)
217                     .WithChangeCallback((_, __) => UpdateInterrupts())
218                 },
219                 {(long)Registers.InterruptEnable, new DoubleWordRegister(this)
220                     .WithFlag(0, out readyInterruptEnable, name: "RDY")
221                     .WithTaggedFlag("COREBIASOPTRDY", 1)
222                     .WithReservedBits(2, 27)
223                     .WithTaggedFlag("DNSERR", 29)
224                     .WithReservedBits(30, 1)
225                     .WithTaggedFlag("COREBIASOPTERR", 31)
226                     .WithChangeCallback((_, __) => UpdateInterrupts())
227                 },
228                 {(long)Registers.Lock, new DoubleWordRegister(this)
229                     .WithValueField(0, 16, writeCallback: (_, value) =>
230                     {
231                         locked.Value = (value != UnlockCode);
232                     }, name: "LOCKKEY")
233                 },
234             };
235             return new DoubleWordRegisterCollection(this, registerDictionary);
236         }
237 
238 #region methods
GetTime()239         private TimeInterval GetTime() => machine.LocalTimeSource.ElapsedVirtualTime;
StartDelayTimer()240         private void StartDelayTimer()
241         {
242             // Function which starts the start-up delay timer
243             timer.Enabled = false;
244             timer.Limit = delayTicks;
245             timer.Enabled = true;
246         }
247 
OnRequest(HFXO_REQUESTER req)248         public void OnRequest(HFXO_REQUESTER req)
249         {
250             this.Log(LogLevel.Error, "OnRequest not implemented");
251         }
252 
OnEm2Wakeup()253         public void OnEm2Wakeup()
254         {
255             HfxoEnabled?.Invoke();
256             this.Log(LogLevel.Error, "OnEm2Wakeup not implemented");
257         }
258 
OnClksel()259         public void OnClksel()
260         {
261             this.Log(LogLevel.Error, "OnClksel not implemented");
262         }
263 
OnStartUpTimerExpired()264         private void OnStartUpTimerExpired()
265         {
266             this.Log(LogLevel.Debug, "Start-up delay timer expired at: {0}", machine.ElapsedVirtualTime);
267             this.Log(LogLevel.Debug, "Wakeup Requester = {0}", wakeUpSource);
268 
269             if (wakeUpSource == WakeUpSource.Force)
270             {
271                 ready.Value = true;
272                 coreBiasReady.Value = true;
273                 readyInterrupt.Value = true;
274                 wakeUpSource = WakeUpSource.None;
275             }
276             else
277             {
278                 this.Log(LogLevel.Error, "Wake up source {0} not implemented", wakeUpSource);
279             }
280 
281             timer.Enabled = false;
282             UpdateInterrupts();
283         }
284 
UpdateInterrupts()285         private void UpdateInterrupts()
286         {
287             machine.ClockSource.ExecuteInLock(delegate {
288                 var irq = (readyInterruptEnable.Value && readyInterrupt.Value);
289                 IRQ.Set(irq);
290             });
291         }
292 #endregion
293         private readonly Machine machine;
294         public GPIO IRQ { get; }
295         private readonly DoubleWordRegisterCollection registersCollection;
296         public event Action HfxoEnabled;
297         private WakeUpSource wakeUpSource = WakeUpSource.None;
298         private LimitTimer timer;
299         private uint delayTicks;
300         private const uint UnlockCode = 0x580E;
301         private const uint SetRegisterOffset = 0x1000;
302         private const uint ClearRegisterOffset = 0x2000;
303         private const uint ToggleRegisterOffset = 0x3000;
304 #region register fields
305         private IFlagRegisterField ready;
306         private IFlagRegisterField coreBiasReady;
307         private IFlagRegisterField enabled;
308         private IFlagRegisterField locked;
309         private IFlagRegisterField fsmLock;
310         private IFlagRegisterField forceEnable;
311         private IFlagRegisterField disableOnDemand;
312         // Interrupts
313         private IFlagRegisterField readyInterrupt;
314         private IFlagRegisterField readyInterruptEnable;
315 #endregion
316 
317 #region enums
318         private enum Registers
319         {
320             IpVersion               = 0x0000,
321             CrystalConfig           = 0x0010,
322             CrystalControl          = 0x0018,
323             Config                  = 0x0020,
324             Control                 = 0x0028,
325             Command                 = 0x0050,
326             Status                  = 0x0058,
327             InterruptFlags          = 0x0070,
328             InterruptEnable         = 0x0074,
329             Lock                    = 0x0080,
330             // Set registers
331             IpVersion_Set           = 0x1000,
332             CrystalConfig_Set       = 0x1010,
333             CrystalControl_Set      = 0x1018,
334             Config_Set              = 0x1020,
335             Control_Set             = 0x1028,
336             Command_Set             = 0x1050,
337             Status_Set              = 0x1058,
338             InterruptFlags_Set      = 0x1070,
339             InterruptEnable_Set     = 0x1074,
340             Lock_Set                = 0x1080,
341             // Clear registers
342             IpVersion_Clr           = 0x2000,
343             CrystalConfig_Clr       = 0x2010,
344             CrystalControl_Clr      = 0x2018,
345             Config_Clr              = 0x2020,
346             Control_Clr             = 0x2028,
347             Command_Clr             = 0x2050,
348             Status_Clr              = 0x2058,
349             InterruptFlags_Clr      = 0x2070,
350             InterruptEnable_Clr     = 0x2074,
351             Lock_Clr                = 0x2080,
352             // Toggle registers
353             IpVersion_Tgl           = 0x3000,
354             CrystalConfig_Tgl       = 0x3010,
355             CrystalControl_Tgl      = 0x3018,
356             Config_Tgl              = 0x3020,
357             Control_Tgl             = 0x3028,
358             Command_Tgl             = 0x3050,
359             Status_Tgl              = 0x3058,
360             InterruptFlags_Tgl      = 0x3070,
361             InterruptEnable_Tgl     = 0x3074,
362             Lock_Tgl                = 0x3080,
363         }
364 
365         private enum WakeUpSource
366         {
367             None  = 0,
368             Prs   = 1,
369             Force = 2,
370         }
371 #endregion
372     }
373 }
374