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 }