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 System.Collections.Generic; 8 using Antmicro.Renode.Core.Structure.Registers; 9 using Antmicro.Renode.Peripherals.Bus; 10 using Antmicro.Renode.Utilities; 11 using Antmicro.Renode.Peripherals.Timers; 12 using Antmicro.Renode.Core; 13 using Antmicro.Renode.Time; 14 using Antmicro.Renode.Peripherals.Memory; 15 using System.Linq; 16 using Antmicro.Renode.Logging; 17 using System; 18 using Antmicro.Renode.Peripherals.CPU; 19 20 namespace Antmicro.Renode.Peripherals.MTD 21 { 22 [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] 23 public class MPFS_eNVM : IDoubleWordPeripheral, IKnownSize 24 { MPFS_eNVM(IMachine machine, MappedMemory memory)25 public MPFS_eNVM(IMachine machine, MappedMemory memory) 26 { 27 this.memory = memory; 28 IRQ = new GPIO(); 29 hvTimer = new LimitTimer(machine.ClockSource, Frequency, this, nameof(hvTimer), direction: Direction.Descending, 30 workMode: WorkMode.OneShot, eventEnabled: true); 31 hvTimer.LimitReached += TimerTick; 32 33 preProgramOrWriteActions = new Dictionary<bool, Action<uint>>() 34 { 35 { true, x => WriteFlashWithValue(x, 0xFF, RowLength) }, 36 { false, x => WriteFlashWithPageLatch(x) } 37 }; 38 39 var registerMap = new Dictionary<long, DoubleWordRegister> 40 { 41 {(long)Registers.PageAddress, new DoubleWordRegister(this) 42 .WithTag("Byte Select", 0, 2) 43 .WithValueField(8, 6, out pageLatchAddress, name: "PA") 44 }, 45 {(long)Registers.WriteDataAtPageAddress, new DoubleWordRegister(this) 46 .WithValueField(0, 32, FieldMode.Read, writeCallback: (_, value) => pageLatch[pageLatchAddress.Value] = (uint)value, name: "PAGE_WRITE_ADDR") 47 }, 48 {(long)Registers.WriteDataAtPageAddressThenIncrement, new DoubleWordRegister(this) 49 .WithValueField(0, 32, FieldMode.Read, writeCallback: (_, value) => 50 { 51 pageLatch[pageLatchAddress.Value] = (uint)value; 52 pageLatchAddress.Value = (pageLatchAddress.Value + 1) % PageLatchEntries; 53 }, name: "PAGE_WRITE_INC_ADDR") 54 }, 55 {(long)Registers.FlashMacroAddress, new DoubleWordRegister(this) 56 .WithTag("Bya", 0 ,2) 57 .WithFlag(8, out wordAddress, name: "Wa") 58 .WithValueField(9, 5, out columnAddress, name: "Ca") 59 .WithValueField(16, 8, out pageAddress, name: "Ra") 60 .WithEnumField(24, 2, out selectedSector, name: "Ba") 61 }, 62 {(long)Registers.ReadFlashData, new DoubleWordRegister(this) 63 .WithValueField(0, 32, FieldMode.Read, valueProviderCallback: _ => ReadFlashDoubleWord(FlashAddressToOffset()), name: "FM_READ") 64 }, 65 {(long)Registers.ReadFlashDataThenIncrement, new DoubleWordRegister(this) 66 .WithValueField(0, 32, FieldMode.Read, valueProviderCallback: _ => 67 { 68 var value = ReadFlashDoubleWord(FlashAddressToOffset()); 69 // The incrementation process treats the address elements as a number in the following scheme: 70 // Ba[1:0] | Ra[7:0] | Ca[4:0] | Wa 71 // Below we implement the manual addition process 72 if(!wordAddress.Value) 73 { 74 wordAddress.Value = true; 75 goto additionFinished; 76 } 77 //carry 78 wordAddress.Value = false; 79 columnAddress.Value = (columnAddress.Value + 1) % (1u << columnAddress.Width); 80 if(columnAddress.Value != 0) 81 { 82 goto additionFinished; 83 } 84 //carry 85 pageAddress.Value = (pageAddress.Value + 1) % (1u << pageAddress.Width); 86 if(pageAddress.Value != 0) 87 { 88 goto additionFinished; 89 } 90 selectedSector.Value = (Sector)(((uint)selectedSector.Value + 1) % (1u << selectedSector.Width)); 91 92 additionFinished: 93 return value; 94 }, name: "FM_READ_INC") 95 }, 96 {(long)Registers.FlashMacroConfiguration, new DoubleWordRegister(this) 97 .WithEnumField(0, 4, out flashMacroMode, name: "FM_Mode") 98 .WithEnumField(8, 2, out hvSequenceState, name: "FM_Seq") 99 .WithTag("DAA MUX Select", 16, 6) 100 }, 101 {(long)Registers.TimerConfiguration, new DoubleWordRegister(this) 102 .WithValueField(0, 16, writeCallback: (_, value) => hvTimer.Value = value, valueProviderCallback: _ => (uint)hvTimer.Value, name: "Period") 103 .WithFlag(16, writeCallback: (_, value) => hvTimer.Enabled = value, valueProviderCallback: _ => false 104 /*hvTimer.Enabled - this causes the demo to run extremely slow, as the timers are bumped only 105 *after quantum. This is not that important, as these timers are for delay purposes only*/, 106 name: "Timer_En") 107 .WithFlag(17, writeCallback: (_, value) => hvTimer.Divider = value ? 100 : 1, valueProviderCallback: _ => hvTimer.Divider == 100, name: "Scale") 108 .WithTag("Pe_en", 18, 1) 109 .WithFlag(19, writeCallback: (_, value) => { if(value) DoAclk(); }, name: "Aclk_en") 110 .WithTag("cfg", 24, 6) 111 }, 112 {(long)Registers.AnalogConfiguration, new DoubleWordRegister(this, 0x16140068) 113 .WithTag("BDAC", 0, 4) 114 .WithTag("itim", 4, 4) 115 .WithTag("MDAC", 8, 8) 116 .WithTag("PDAC", 16, 5) 117 .WithTag("NDAC", 24, 5) 118 }, 119 {(long)Registers.TestModeConfiguration, new DoubleWordRegister(this, 0x00804000) 120 .WithTag("tm", 0, 4) 121 .WithTag("tm_disneg", 6, 1) 122 .WithTag("tm_dispos", 7, 1) 123 .WithTag("tm_itim", 8, 1) 124 .WithTag("tm_rdhvpl", 9, 1) 125 .WithTag("tm_rdstrb", 10, 1) 126 .WithTag("tm_vdac_force", 12, 1) 127 .WithTag("tm_xydec", 13, 1) 128 .WithTag("turbo_b", 14, 1) 129 .WithFlag(15, out compareTestMode, name: "tm_cmpr") 130 .WithTag("extrm_tim", 20, 1) 131 .WithTag("pg_en", 23, 1) 132 .WithTag("pe_tm", 24, 1) 133 .WithTag("pnb", 25, 1) 134 .WithTag("tm_daa", 26, 1) 135 .WithTag("tm_disrow", 27, 1) 136 .WithTag("tm_isa", 31, 1) 137 }, 138 {(long)Registers.Status, new DoubleWordRegister(this) 139 .WithFlag(0, FieldMode.Read, valueProviderCallback: _ => !hvTimer.Enabled, name: "hv_timer") 140 .WithReadCallback((_, __) => IRQ.Unset()) //this is a guess based on a comment from the sources. The documentation 141 //does not describe the interrupt handling at all 142 }, 143 {(long)Registers.Wait, new DoubleWordRegister(this, 0x16) 144 .WithTag("wait_wr_hvpl", 0, 2) 145 .WithTag("wait_rd_fm", 2, 3) 146 .WithTag("vref_vlevel", 8, 3) 147 .WithTag("vref_ilevel_p", 16, 4) 148 .WithTag("vref_ilevel_n", 20, 4) 149 .WithTag("ctat", 24, 3) 150 .WithTag("vbg_ilevel", 27, 2) 151 .WithTag("vbg_curve", 29, 3) 152 }, 153 {(long)Registers.Monitor, new DoubleWordRegister(this) 154 .WithTag("xy_out", 0, 1) 155 .WithFlag(1, FieldMode.Read, valueProviderCallback: (_) => switchState == SwitchState.OnCode, name: "sw2fm_r") //the docs is not clear, but this behavior is probably right 156 .WithFlag(2, out compareResult, FieldMode.Read, name: "cmprx") 157 }, 158 {(long)Registers.SW2FMEnable, new DoubleWordRegister(this) 159 .WithEnumField<DoubleWordRegister, SwitchState>(0, 8, FieldMode.Write, writeCallback: (_, value) => 160 { 161 if((switchState == SwitchState.Off && value == SwitchState.S2OnCode) 162 || (switchState == SwitchState.S2OnCode && value == SwitchState.S1OnCode) 163 || (switchState == SwitchState.S1OnCode && value == SwitchState.OnCode)) 164 { 165 switchState = value; 166 } 167 else 168 { 169 // improper sequence code 170 switchState = SwitchState.Off; 171 } 172 }, name: "switch_code") 173 }, 174 {(long)Registers.ScratchPad, new DoubleWordRegister(this) 175 .WithValueField(0, 32, name: "scratch_pad") 176 }, 177 {(long)Registers.HVConfiguration, new DoubleWordRegister(this, 0x19) 178 .WithTag("FM Clock Frequency", 0, 8) //this field is ignored, as our timer has an absolute frequency, not depending on external clock 179 }, 180 {(long)Registers.InterruptMask, new DoubleWordRegister(this) 181 .WithFlag(0, out timerInterruptEnabled, name: "Mask_reg0") 182 }, 183 {(long)Registers.GenerateAClk, new DoubleWordRegister(this) 184 .WithValueField(0, 32, FieldMode.Write, writeCallback: (_, __) => DoAclk(), name: "ACLK_GEN_ADDR") //value of this field is ignored. The fact of being written to is important 185 }, 186 {(long)Registers.FlashMacroDummyRead, new DoubleWordRegister(this) 187 .WithValueField(0, 32, FieldMode.Read, readCallback: (_, __) => hvSequenceState.Value = HVSequence.Seq0, name: "Mask_regX") 188 }, 189 }; 190 191 registers = new DoubleWordRegisterCollection(this, registerMap); 192 } 193 ReadDoubleWord(long offset)194 public uint ReadDoubleWord(long offset) 195 { 196 return registers.Read(offset); 197 } 198 Reset()199 public void Reset() 200 { 201 preProgramMode = false; 202 registers.Reset(); 203 hvTimer.Reset(); 204 switchState = SwitchState.Off; 205 Array.Clear(pageLatch, 0, pageLatch.Length); 206 IRQ.Unset(); 207 } 208 WriteDoubleWord(long offset, uint value)209 public void WriteDoubleWord(long offset, uint value) 210 { 211 registers.Write(offset, value); 212 } 213 214 public long Size => 0x200; 215 216 public GPIO IRQ { get;private set; } 217 WriteSector(Sector sectorName, byte value)218 private void WriteSector(Sector sectorName, byte value) 219 { 220 var sector = sectorMappings[sectorName]; 221 WriteFlashWithValue(sector.Start, value, sector.Size); 222 } 223 WriteFlashWithValue(uint offset, byte value, int count)224 private void WriteFlashWithValue(uint offset, byte value, int count) 225 { 226 var valueToWrite = Enumerable.Repeat(value, count).ToArray(); 227 memory.WriteBytes(offset, valueToWrite); 228 } 229 WriteFlashWithPageLatch(uint offset)230 private void WriteFlashWithPageLatch(uint offset) 231 { 232 for(var i = 0; i < PageLatchEntries; ++i) 233 { 234 memory.WriteDoubleWord(offset + i * 4, pageLatch[i]); 235 } 236 } 237 ReadFlashDoubleWord(uint offset)238 private uint ReadFlashDoubleWord(uint offset) 239 { 240 if(switchState == SwitchState.OnCode) 241 { 242 // read based on Ca/Wa/Ra/Ba 243 return memory.ReadDoubleWord(offset); 244 } 245 else 246 { 247 this.Log(LogLevel.Warning, "Trying to read the flash in r_bus mode, aborting..."); 248 return 0; 249 } 250 } 251 DoAclk()252 private void DoAclk() 253 { 254 uint offset; 255 SectorDescription sector; 256 if(switchState != SwitchState.OnCode) 257 { 258 this.Log(LogLevel.Warning, "Aclk pulse activated with c-bus disconnected."); 259 return; 260 } 261 if(compareTestMode.Value) 262 { 263 //unclear from the docs, but we'll assume a row comparison 264 offset = FlashAddressToOffset(); 265 var low = ReadFlashDoubleWord(offset); 266 var high = ReadFlashDoubleWord(offset + 4); 267 268 compareResult.Value = pageLatch[columnAddress.Value * 2] == low && pageLatch[(columnAddress.Value * 2) + 1] == high; 269 270 return; 271 } 272 else if(hvSequenceState.Value == HVSequence.Seq0 && flashMacroMode.Value == Mode.PreProgram) 273 { 274 preProgramMode = true; 275 return; //to ease the handling of preProgramMode flag, we return here. 276 } 277 else if(hvSequenceState.Value != HVSequence.Seq2) 278 { 279 return; 280 } 281 switch(flashMacroMode.Value) 282 { 283 case Mode.Read: 284 //no-op 285 break; 286 case Mode.ResetAfterMargin: 287 preProgramMode = false; 288 hvSequenceState.Value = HVSequence.Seq0; 289 flashMacroMode.Value = Mode.Read; 290 break; 291 case Mode.ClearHVPL: 292 Array.Clear(pageLatch, 0, pageLatch.Length); 293 break; 294 case Mode.EraseRowOrPage: 295 offset = FlashAddressToOffset() & 0xFFFFFF00; 296 WriteFlashWithValue(offset, 0, RowLength); 297 break; 298 case Mode.EraseSubSector: 299 offset = FlashAddressToOffset() & 0xFFFFF800; 300 WriteFlashWithValue(offset, 0, 8 * RowLength); 301 break; 302 case Mode.EraseSector: 303 WriteSector(selectedSector.Value, 0); 304 break; 305 case Mode.EraseBulk: 306 if(selectedSector.Value < Sector.SMSector0) 307 { 308 WriteSector(Sector.FlashSector0, 0); 309 WriteSector(Sector.FlashSector1, 0); 310 } 311 else 312 { 313 WriteSector(Sector.SMSector0, 0); 314 WriteSector(Sector.SMSector1, 0); 315 } 316 break; 317 case Mode.ProgramRowOrPage: 318 offset = FlashAddressToOffset() & 0xFFFFFF00; 319 preProgramOrWriteActions[preProgramMode](offset); 320 break; 321 case Mode.ProgramSectorEvenOddRows: 322 sector = sectorMappings[selectedSector.Value]; 323 offset = sector.Start + (((pageAddress.Value & 1) != 0) ? RowLength : 0u); //LSB of pageAddress indicates even/odd rows 324 while(offset < sector.Start + sector.Size) 325 { 326 preProgramOrWriteActions[preProgramMode](offset); 327 offset += 2 * RowLength; //skip one row 328 } 329 break; 330 case Mode.ProgramSectorAllRows: 331 sector = sectorMappings[selectedSector.Value]; 332 for(var i = 0u; i < sector.Size; i += RowLength) 333 { 334 preProgramOrWriteActions[preProgramMode](sector.Start + i); 335 } 336 break; 337 case Mode.PreProgram: 338 //intentionally set blank, as it should happen only in seq0 339 break; 340 case Mode.PageWriteAll: 341 //not clear what it does, not implemented 342 case Mode.ProgramBulkEvenOddRows: 343 //todo : should it select normal/special sectors as well? 344 case Mode.ProgramBulkAllRows: 345 // Contrary to the name (and the documentation), this operation only programs either 346 // all the normal sectors or all the special sectors depending on the msb of the address. 347 default: 348 this.Log(LogLevel.Warning, "Aclk in an unsupported mode: {0}.", flashMacroMode.Value); 349 break; 350 } 351 preProgramMode = false; 352 } 353 TimerTick()354 private void TimerTick() 355 { 356 if(timerInterruptEnabled.Value) 357 { 358 IRQ.Set(); 359 } 360 } 361 FlashAddressToOffset()362 private uint FlashAddressToOffset() 363 { 364 var offset = (wordAddress.Value ? 1 : 0 365 + (columnAddress.Value << 1) 366 + 64 * pageAddress.Value) 367 * 4; 368 //we assume it won't fail, as the register infrastructure takes care of the possible values 369 offset += sectorMappings[selectedSector.Value].Start; 370 return (uint)offset; 371 } 372 373 private readonly IValueRegisterField pageLatchAddress; 374 private readonly IValueRegisterField columnAddress; 375 private readonly IValueRegisterField pageAddress; 376 private readonly IFlagRegisterField wordAddress; 377 private readonly IEnumRegisterField<Sector> selectedSector; 378 private readonly IEnumRegisterField<Mode> flashMacroMode; 379 private readonly IFlagRegisterField timerInterruptEnabled; 380 private readonly IEnumRegisterField<HVSequence> hvSequenceState; 381 private readonly IFlagRegisterField compareResult; 382 private readonly IFlagRegisterField compareTestMode; 383 384 private bool preProgramMode; 385 private uint[] pageLatch = new uint[PageLatchEntries]; 386 private SwitchState switchState; 387 private readonly DoubleWordRegisterCollection registers; 388 private readonly LimitTimer hvTimer; 389 private readonly MappedMemory memory; 390 391 private const uint Frequency = 0x83000000; 392 393 private readonly Dictionary<bool, Action<uint>> preProgramOrWriteActions = new Dictionary<bool, Action<uint>>(); 394 395 private static readonly Dictionary<Sector, SectorDescription> sectorMappings = new Dictionary<Sector, SectorDescription> 396 { 397 {Sector.SMSector0, new SectorDescription{Start = 0x00000, Size = 0x2000}}, 398 {Sector.FlashSector0, new SectorDescription{Start = 0x02000, Size = 0xE000}}, 399 {Sector.FlashSector1, new SectorDescription{Start = 0x10000, Size = 0xE000}}, 400 {Sector.SMSector1, new SectorDescription{Start = 0x1E000, Size = 0x2000}}, 401 }; 402 403 private const int PageLatchEntries = 64; 404 private const int RowLength = 256; 405 406 private enum HVSequence 407 { 408 Seq0, 409 Seq1, 410 Seq2, 411 Seq3 412 } 413 414 private enum SwitchState 415 { 416 Off = 0, 417 S2OnCode = 0b11000011, 418 S1OnCode = 0b10110010, 419 OnCode = 0b10011001 420 } 421 422 private enum Sector 423 { 424 FlashSector0, 425 FlashSector1, 426 SMSector0, 427 SMSector1 428 } 429 430 private enum Mode 431 { 432 Read = 0, 433 PreProgram = 1, 434 PageWriteAll = 2, 435 ResetAfterMargin = 3, 436 ClearHVPL = 4, 437 //two unused fields, 438 EraseRowOrPage = 7, 439 EraseSubSector = 8, 440 EraseSector = 9, 441 EraseBulk = 10, 442 ProgramRowOrPage = 11, 443 ProgramSectorEvenOddRows = 12, 444 ProgramSectorAllRows = 13, 445 ProgramBulkEvenOddRows = 14, 446 ProgramBulkAllRows = 15 447 } 448 private enum Registers 449 { 450 PageAddress = 0x00, 451 WriteDataAtPageAddress = 0x04, 452 WriteDataAtPageAddressThenIncrement = 0x08, 453 FlashMacroAddress = 0x0C, 454 ReadFlashData = 0x10, 455 ReadFlashDataThenIncrement = 0x14, 456 FlashMacroConfiguration = 0x18, 457 TimerConfiguration = 0x1C, 458 AnalogConfiguration = 0x20, 459 TestModeConfiguration = 0x24, 460 Status = 0x28, 461 Wait = 0x2C, 462 Monitor = 0x30, 463 SW2FMEnable = 0x34, 464 ScratchPad = 0x38, 465 HVConfiguration = 0x3C, 466 InterruptMask = 0x40, 467 GenerateAClk = 0x44, 468 FlashMacroDummyRead = 0x48, 469 } 470 471 private struct SectorDescription 472 { 473 public uint Start; 474 public int Size; 475 } 476 } 477 } 478