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.Collections.Generic;
10 using System.IO;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Core.Structure.Registers;
13 using Antmicro.Renode.Exceptions;
14 using Antmicro.Renode.Logging;
15 using Antmicro.Renode.Peripherals.Bus;
16 using Antmicro.Renode.Peripherals.Timers;
17 using Antmicro.Renode.Time;
18 using Antmicro.Renode.Peripherals.CPU;
19 
20 namespace Antmicro.Renode.Peripherals.Miscellaneous.SiLabs
21 {
22     public class EFR32xG2_MSC_3 : IDoubleWordPeripheral, IKnownSize
23     {
EFR32xG2_MSC_3(Machine machine, CPUCore cpu, uint flashSize, uint flashPageSize)24         public EFR32xG2_MSC_3(Machine machine, CPUCore cpu, uint flashSize, uint flashPageSize)
25         {
26             this.machine = machine;
27             this.cpu = cpu;
28             this.FlashSize = flashSize;
29             this.FlashPageSize = flashPageSize;
30 
31             pageEraseTimer = new LimitTimer(machine.ClockSource, 1000000, this, "page_erase_timer", PageEraseTimeUs, direction: Direction.Ascending,
32                                              enabled: false, workMode: WorkMode.OneShot, eventEnabled: true, autoUpdate: true);
33             pageEraseTimer.LimitReached += PageEraseTimerHandleLimitReached;
34 
35 
36             IRQ = new GPIO();
37             registersCollection = BuildRegistersCollection();
38         }
39 
Reset()40         public void Reset()
41         {
42         }
43 
ReadDoubleWord(long offset)44         public uint ReadDoubleWord(long offset)
45         {
46             return ReadRegister(offset);
47         }
48 
ReadRegister(long offset, bool internal_read = false)49         private uint ReadRegister(long offset, bool internal_read = false)
50         {
51             var result = 0U;
52             long internal_offset = offset;
53 
54             // Set, Clear, Toggle registers should only be used for write operations. But just in case we convert here as well.
55             if (offset >= SetRegisterOffset && offset < ClearRegisterOffset)
56             {
57                 // Set register
58                 internal_offset = offset - SetRegisterOffset;
59                 if (!internal_read)
60                 {
61                     this.Log(LogLevel.Noisy, "SET Operation on {0}, offset=0x{1:X}, internal_offset=0x{2:X}", (Registers)internal_offset, offset, internal_offset);
62                 }
63             } else if (offset >= ClearRegisterOffset && offset < ToggleRegisterOffset)
64             {
65                 // Clear register
66                 internal_offset = offset - ClearRegisterOffset;
67                 if (!internal_read)
68                 {
69                     this.Log(LogLevel.Noisy, "CLEAR Operation on {0}, offset=0x{1:X}, internal_offset=0x{2:X}", (Registers)internal_offset, offset, internal_offset);
70                 }
71             } else if (offset >= ToggleRegisterOffset)
72             {
73                 // Toggle register
74                 internal_offset = offset - ToggleRegisterOffset;
75                 if (!internal_read)
76                 {
77                     this.Log(LogLevel.Noisy, "TOGGLE Operation on {0}, offset=0x{1:X}, internal_offset=0x{2:X}", (Registers)internal_offset, offset, internal_offset);
78                 }
79             }
80 
81             if(!registersCollection.TryRead(internal_offset, out result))
82             {
83                 if (!internal_read)
84                 {
85                     this.Log(LogLevel.Noisy, "Unhandled read at offset 0x{0:X} ({1}).", internal_offset, (Registers)internal_offset);
86                 }
87             }
88             else
89             {
90                 if (!internal_read)
91                 {
92                     this.Log(LogLevel.Noisy, "Read at offset 0x{0:X} ({1}), returned 0x{2:X}.", internal_offset, (Registers)internal_offset, result);
93                 }
94             }
95 
96             return result;
97         }
98 
WriteDoubleWord(long offset, uint value)99         public void WriteDoubleWord(long offset, uint value)
100         {
101             WriteRegister(offset, value);
102         }
103 
WriteRegister(long offset, uint value, bool internal_write = false)104         private void WriteRegister(long offset, uint value, bool internal_write = false)
105         {
106             machine.ClockSource.ExecuteInLock(delegate {
107                 long internal_offset = offset;
108                 uint internal_value = value;
109 
110                 if (offset >= SetRegisterOffset && offset < ClearRegisterOffset)
111                 {
112                     // Set register
113                     internal_offset = offset - SetRegisterOffset;
114                     uint old_value = ReadRegister(internal_offset, true);
115                     internal_value = old_value | value;
116                     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);
117                 } else if (offset >= ClearRegisterOffset && offset < ToggleRegisterOffset)
118                 {
119                     // Clear register
120                     internal_offset = offset - ClearRegisterOffset;
121                     uint old_value = ReadRegister(internal_offset, true);
122                     internal_value = old_value & ~value;
123                     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);
124                 } else if (offset >= ToggleRegisterOffset)
125                 {
126                     // Toggle register
127                     internal_offset = offset - ToggleRegisterOffset;
128                     uint old_value = ReadRegister(internal_offset, true);
129                     internal_value = old_value ^ value;
130                     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);
131                 }
132 
133                 this.Log(LogLevel.Noisy, "Write at offset 0x{0:X} ({1}), value 0x{2:X}.", internal_offset, (Registers)internal_offset, internal_value);
134                 if(!registersCollection.TryWrite(internal_offset, internal_value))
135                 {
136                     this.Log(LogLevel.Noisy, "Unhandled write at offset 0x{0:X} ({1}), value 0x{2:X}.", internal_offset, (Registers)internal_offset, internal_value);
137                     return;
138                 }
139             });
140         }
141 
BuildRegistersCollection()142         private DoubleWordRegisterCollection BuildRegistersCollection()
143         {
144             var registerDictionary = new Dictionary<long, DoubleWordRegister>
145             {
146                 {(long)Registers.ReadControl, new DoubleWordRegister(this)
147                     .WithReservedBits(0, 20)
148                     .WithEnumField<DoubleWordRegister, ReadMode>(20, 2, out readMode, name: "MODE")
149                     .WithReservedBits(22, 10)
150                 },
151                 {(long)Registers.ReadDataControl, new DoubleWordRegister(this)
152                     .WithReservedBits(0, 1)
153                     .WithFlag(1, out autoFlushDisable, name: "AFDIS")
154                     .WithReservedBits(2, 10)
155                     .WithFlag(12, out flashDataOutputBufferEnable, name: "DOUTBUFEN")
156                     .WithReservedBits(13, 19)
157                 },
158                 {(long)Registers.WriteControl, new DoubleWordRegister(this)
159                     .WithFlag(0, out writeAndEraseEnable, name: "WREN")
160                     .WithFlag(1, out abortPageEraseOnInterrupt, name: "IRQERASEABORT")
161                     .WithReservedBits(2, 1)
162                     .WithFlag(3, out lowPowerErase, name: "LPWRITE")
163                     .WithReservedBits(4, 12)
164                     .WithValueField(16, 10, out eraseRangeCount, name: "RANGECOUNT")
165                     .WithReservedBits(26, 6)
166                 },
167                 {(long)Registers.WriteCommand, new DoubleWordRegister(this)
168                     .WithReservedBits(0, 1)
169                     .WithFlag(1, FieldMode.Write, writeCallback: (_, value) => { if (value) { ErasePage(); } }, name: "ERASEPAGE")
170                     .WithFlag(2, FieldMode.Write, writeCallback: (_, value) => { if (value) { EndWriteMode(); } }, name: "WRITEEND")
171                     .WithReservedBits(3, 1)
172                     .WithFlag(4, FieldMode.Write, writeCallback: (_, value) => { if (value) { EraseRange(); } }, name: "ERASERANGE")
173                     .WithFlag(5, FieldMode.Write, writeCallback: (_, value) => { if (value) { EraseAbort(); } }, name: "ERASEABORT")
174                     .WithReservedBits(6, 2)
175                     .WithFlag(8, FieldMode.Write, writeCallback: (_, value) => { if (value) { MassEraseRegion0(); } }, name: "ERASEABORT")
176                     .WithReservedBits(9, 3)
177                     .WithFlag(12, FieldMode.Write, writeCallback: (_, value) => { if (value) { ClearWriteDataState(); } }, name: "CLEARWDATA")
178                     .WithReservedBits(13, 19)
179                 },
180                 {(long)Registers.PageEraseOrWriteAddress, new DoubleWordRegister(this)
181                     .WithValueField(0, 32, writeCallback: (_, value) => { PageEraseOrWriteAddress = (uint)value; }, name: "ADDRB")
182                 },
183                 {(long)Registers.WriteData, new DoubleWordRegister(this)
184                     .WithValueField(0, 32, writeCallback: (_, value) => { WriteData((uint)value); }, name: "DATAW")
185                 },
186                 {(long)Registers.Status, new DoubleWordRegister(this, 0x08000008)
187                     .WithFlag(0, out busy, FieldMode.Read, name: "BUSY")
188                     .WithFlag(1, out locked, FieldMode.Read, name: "LOCKED")
189                     .WithFlag(2, out invalidAddress, FieldMode.Read, name: "INVADDR")
190                     .WithFlag(3, out writeDataReady, FieldMode.Read, name: "WDATAREADY")
191                     .WithTaggedFlag("The Current Flash Erase Operation was aborted by interrupt (ERASEABORTED)", 4)
192                     .WithTaggedFlag("Write command is in queue (PENDING)", 5)
193                     .WithTaggedFlag("Write command timeout flag (TIMEOUT)", 6)
194                     .WithTaggedFlag("EraseRange with skipped locked pages (RANGEPARTIAL)", 7)
195                     .WithReservedBits(8, 8)
196                     .WithTaggedFlag("Register Lock Status (REGLOCK)", 16)
197                     .WithReservedBits(17, 7)
198                     .WithFlag(24, out flashPowerOnStatus, FieldMode.Read, name: "PWRON")
199                     .WithReservedBits(25, 2)
200                     .WithFlag(27, out writeOrEraseReady, FieldMode.Read, name: "WREADY")
201                     .WithTag("Flash power up checkerboard pattern check fail count (PWRUPCKBDFAILCOUNT)", 28, 4)
202                 },
203                 {(long)Registers.InterruptFlags, new DoubleWordRegister(this)
204                     .WithFlag(0, out eraseDoneInterrupt, name: "ERASEIF")
205                     .WithFlag(1, out writeDoneInterrupt, name: "WRITEIF")
206                     .WithFlag(2, out writeDataOverflowInterrupt, name: "WDATAOVIF")
207                     .WithReservedBits(3, 5)
208                     .WithFlag(8, out powerUpFinishedInterrupt, name: "PWRUPFIF")
209                     .WithFlag(9, out powerOffFinishedInterrupt, name: "PWROFFIF")
210                     .WithReservedBits(10, 22)
211                 },
212                 {(long)Registers.InterruptEnable, new DoubleWordRegister(this)
213                     .WithFlag(0, out eraseDoneInterruptEnable, name: "ERASEIEN")
214                     .WithFlag(1, out writeDoneInterruptEnable, name: "WRITEIEN")
215                     .WithFlag(2, out writeDataOverflowInterruptEnable, name: "WDATAOVIEN")
216                     .WithReservedBits(3, 5)
217                     .WithFlag(8, out powerUpFinishedInterruptEnable, name: "PWRUPFIEN")
218                     .WithFlag(9, out powerOffFinishedInterruptEnable, name: "PWROFFIEN")
219                     .WithReservedBits(10, 22)
220                     .WithChangeCallback((_, __) => UpdateInterrupts())
221                 },
222                 {(long)Registers.Command, new DoubleWordRegister(this)
223                     .WithFlag(0, FieldMode.Write, writeCallback: (_, value) => { if (value) { PowerUp(); } }, name: "PWRUP")
224                     .WithReservedBits(1, 3)
225                     .WithFlag(4, FieldMode.Write, writeCallback: (_, value) => { if (value) { PowerOff(); } }, name: "PWROFF")
226                     .WithReservedBits(5, 27)
227                 },
228                 {(long)Registers.LockConfig, new DoubleWordRegister(this)
229                     .WithValueField(0, 16, FieldMode.Write, writeCallback: (_, value) => { locked.Value = (value != UnlockCode); }, name: "LOCKKEY")
230                     .WithReservedBits(16, 16)
231                 },
232             };
233             return new DoubleWordRegisterCollection(this, registerDictionary);
234         }
235 
236         public long Size => 0x4000;
237         public GPIO IRQ { get; }
238 
239         private readonly Machine machine;
240         private readonly CPUCore cpu;
241         private readonly DoubleWordRegisterCollection registersCollection;
242         private readonly LimitTimer pageEraseTimer;
243         private const uint SetRegisterOffset = 0x1000;
244         private const uint ClearRegisterOffset = 0x2000;
245         private const uint ToggleRegisterOffset = 0x3000;
246         private const uint UnlockCode = 7025;
247 
248         // TODO: RENODE-5: change it back to 12ms
249         //private const uint PageEraseTimeUs = 12000;
250         private const uint PageEraseTimeUs = 100;
251         private const uint WordWriteTimeUs = 3;
252         private const uint FlashBase = 0x8000000;
253         private uint FlashSize;
254         private uint FlashPageSize;
255         private uint pageEraseOrWriteAddress;
256 
257 #region register fields
258         private IEnumRegisterField<ReadMode> readMode;
259         private IFlagRegisterField autoFlushDisable;
260         private IFlagRegisterField flashDataOutputBufferEnable;
261         private IFlagRegisterField writeAndEraseEnable;
262         private IFlagRegisterField abortPageEraseOnInterrupt;
263         private IFlagRegisterField lowPowerErase;
264         private IValueRegisterField eraseRangeCount;
265         private IFlagRegisterField busy;
266         private IFlagRegisterField locked;
267         private IFlagRegisterField invalidAddress;
268         private IFlagRegisterField writeDataReady;
269         private IFlagRegisterField flashPowerOnStatus;
270         private IFlagRegisterField writeOrEraseReady;
271 
272         // Interrupt flags
273         private IFlagRegisterField eraseDoneInterrupt;
274         private IFlagRegisterField writeDoneInterrupt;
275         private IFlagRegisterField writeDataOverflowInterrupt;
276         private IFlagRegisterField powerUpFinishedInterrupt;
277         private IFlagRegisterField powerOffFinishedInterrupt;
278         private IFlagRegisterField eraseDoneInterruptEnable;
279         private IFlagRegisterField writeDoneInterruptEnable;
280         private IFlagRegisterField writeDataOverflowInterruptEnable;
281         private IFlagRegisterField powerUpFinishedInterruptEnable;
282         private IFlagRegisterField powerOffFinishedInterruptEnable;
283 #endregion
284 
285 #region methods
286         private uint PageEraseOrWriteAddress
287         {
288             get => pageEraseOrWriteAddress;
289             set
290             {
291                 pageEraseOrWriteAddress = value;
292                 invalidAddress.Value = (value < FlashBase || value >= (FlashBase + FlashSize));
293             }
294         }
295 
UpdateInterrupts()296         private void UpdateInterrupts()
297         {
298             machine.ClockSource.ExecuteInLock(delegate {
299                 var irq = ((eraseDoneInterruptEnable.Value && eraseDoneInterrupt.Value)
300                         || (writeDoneInterruptEnable.Value && writeDoneInterrupt.Value)
301                         || (writeDataOverflowInterruptEnable.Value && writeDataOverflowInterrupt.Value)
302                         || (powerUpFinishedInterruptEnable.Value && powerUpFinishedInterrupt.Value)
303                         || (powerOffFinishedInterruptEnable.Value && powerOffFinishedInterrupt.Value));
304                 IRQ.Set(irq);
305             });
306         }
307 
PageEraseTimerHandleLimitReached()308         private void PageEraseTimerHandleLimitReached()
309         {
310             pageEraseTimer.Enabled = false;
311 
312             // For erase operations, the address may be any within the page to be erased.
313             uint startAddress = pageEraseOrWriteAddress & ~(FlashPageSize - 1);
314 
315             this.Log(LogLevel.Info, "CMD ERASE_PAGE - Erasing page: address={0:X}, page address={1:X}", pageEraseOrWriteAddress, startAddress);
316 
317             machine.ClockSource.ExecuteInLock(delegate {
318                 for(var addr = startAddress; addr < startAddress+FlashPageSize; addr += 4)
319                 {
320                     machine.SystemBus.WriteDoubleWord(addr, 0xFFFFFFFF);
321                 }
322                 busy.Value = false;
323                 eraseDoneInterrupt.Value = true;
324             });
325 
326             UpdateInterrupts();
327             //cpu.IsHalted = false;
328         }
329 
330         // Commands
ErasePage()331         private void ErasePage()
332         {
333             this.Log(LogLevel.Info, "MSC_CMD: ERASE_PAGE");
334 
335             if (writeAndEraseEnable.Value && !invalidAddress.Value && !locked.Value)
336             {
337                 // TODO: for now we start a timer and do the actual page erase entirely when the timer expires.
338                 pageEraseTimer.Value = 0;
339                 pageEraseTimer.Enabled = true;
340 
341                 // Halt the CPU until the timer expires.
342                 // TODO: RENODE-5
343                 //cpu.IsHalted = true;
344                 busy.Value = true;
345             }
346         }
347 
WriteData(uint value)348         private void WriteData(uint value)
349         {
350             this.Log(LogLevel.Info, "MSC_CMD: WRITE_DATA");
351 
352             if (writeAndEraseEnable.Value && !invalidAddress.Value && !locked.Value)
353             {
354                 // TODO: for now single word writes to flash are considered "instantenous".
355                 machine.ClockSource.ExecuteInLock(delegate {
356                     writeDataReady.Value = false;
357                     busy.Value = true;
358                     uint currentValue = machine.SystemBus.ReadDoubleWord(pageEraseOrWriteAddress);
359                     // 0s in currentValue stays 0s. 1s in currentValue are flipped to 0s if the corresponding bit in value is 0.
360                     uint newValue = currentValue & (~currentValue | value);
361                     machine.SystemBus.WriteDoubleWord(pageEraseOrWriteAddress, newValue);
362                     pageEraseOrWriteAddress += 4;
363                     busy.Value = false;
364                     writeDataReady.Value = true;
365                     writeDoneInterrupt.Value = true;
366                 });
367                 UpdateInterrupts();
368             }
369         }
370 
EndWriteMode()371         private void EndWriteMode()
372         {
373             this.Log(LogLevel.Info, "MSC_CMD: END_WRITE_MODE");
374 
375             // TODO: this can be a no-op since we implement writes as instantanous operations.
376         }
377 
EraseRange()378         private void EraseRange()
379         {
380             this.Log(LogLevel.Error, "MSC_CMD: ERASE_RANGE not implemented!");
381         }
382 
EraseAbort()383         private void EraseAbort()
384         {
385             this.Log(LogLevel.Error, "MSC_CMD: ERASE_ABORT not implemented!");
386         }
387 
MassEraseRegion0()388         private void MassEraseRegion0()
389         {
390             this.Log(LogLevel.Error, "MSC_CMD: MASS_ERASE_REGION0 not implemented!");
391         }
392 
ClearWriteDataState()393         private void ClearWriteDataState()
394         {
395             this.Log(LogLevel.Error, "MSC_CMD: CLEAR_WRITE_DATA not implemented!");
396         }
397 
PowerUp()398         private void PowerUp()
399         {
400             this.Log(LogLevel.Error, "MSC_CMD: POWER_UP not implemented!");
401         }
402 
PowerOff()403         private void PowerOff()
404         {
405             this.Log(LogLevel.Error, "MSC_CMD: POWER_OFF not implemented!");
406         }
407 #endregion
408 
409 #region enums
410         private enum ReadMode
411         {
412             WS0         = 0x0,
413             WS1         = 0x1,
414             WS2         = 0x2,
415             WS3         = 0x3,
416         }
417 
418         private enum Registers
419         {
420             IpVersion                           = 0x0000,
421             ReadControl                         = 0x0004,
422             ReadDataControl                     = 0x0008,
423             WriteControl                        = 0x000C,
424             WriteCommand                        = 0x0010,
425             PageEraseOrWriteAddress             = 0x0014,
426             WriteData                           = 0x0018,
427             Status                              = 0x001C,
428             InterruptFlags                      = 0x0020,
429             InterruptEnable                     = 0x0024,
430             UserDataSize                        = 0x0034,
431             Command                             = 0x0038,
432             LockConfig                          = 0x003C,
433             LockWord                            = 0x0040,
434             PowerControl                        = 0x0050,
435             // Set registers
436             IpVersion_Set                       = 0x1000,
437             ReadControl_Set                     = 0x1004,
438             ReadDataControl_Set                 = 0x1008,
439             WriteControl_Set                    = 0x100C,
440             WriteCommand_Set                    = 0x1010,
441             PageEraseOrWriteAddress_Set         = 0x1014,
442             WriteData_Set                       = 0x1018,
443             Status_Set                          = 0x101C,
444             InterruptFlags_Set                  = 0x1020,
445             InterruptEnable_Set                 = 0x1024,
446             UserDataSize_Set                    = 0x1034,
447             Command_Set                         = 0x1038,
448             ConfigLock_Set                      = 0x103C,
449             LockWord_Set                        = 0x1040,
450             PowerControl_Set                    = 0x1050,
451             // Clear registers
452             IpVersion_Clr                       = 0x2000,
453             ReadControl_Clr                     = 0x2004,
454             ReadDataControl_Clr                 = 0x2008,
455             WriteControl_Clr                    = 0x200C,
456             WriteCommand_Clr                    = 0x2010,
457             PageEraseOrWriteAddress_Clr         = 0x2014,
458             WriteData_Clr                       = 0x2018,
459             Status_Clr                          = 0x201C,
460             InterruptFlags_Clr                  = 0x2020,
461             InterruptEnable_Clr                 = 0x2024,
462             UserDataSize_Clr                    = 0x2034,
463             Command_Clr                         = 0x2038,
464             ConfigLock_Clr                      = 0x203C,
465             LockWord_Clr                        = 0x2040,
466             PowerControl_Clr                    = 0x2050,
467             // Toggle registers
468             IpVersion_Tgl                       = 0x3000,
469             ReadControl_Tgl                     = 0x3004,
470             ReadDataControl_Tgl                 = 0x3008,
471             WriteControl_Tgl                    = 0x300C,
472             WriteCommand_Tgl                    = 0x3010,
473             PageEraseOrWriteAddress_Tgl         = 0x3014,
474             WriteData_Tgl                       = 0x3018,
475             Status_Tgl                          = 0x301C,
476             InterruptFlags_Tgl                  = 0x3020,
477             InterruptEnable_Tgl                 = 0x3024,
478             UserDataSize_Tgl                    = 0x3034,
479             Command_Tgl                         = 0x3038,
480             LockConfig_Tgl                      = 0x303C,
481             LockWord_Tgl                        = 0x3040,
482             PowerControl_Tgl                    = 0x3050,
483         }
484 #endregion
485     }
486 }