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