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;
8 using System.Collections.Generic;
9 using System.Linq;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Core.Structure.Registers;
12 using Antmicro.Renode.Exceptions;
13 using Antmicro.Renode.Logging;
14 using Antmicro.Renode.Peripherals.Bus;
15 using Antmicro.Renode.Peripherals.Memory;
16 using Antmicro.Renode.Utilities;
17 
18 namespace Antmicro.Renode.Peripherals.MTD
19 {
20     public class EFR32xg13FlashController : IDoubleWordPeripheral, IKnownSize
21     {
EFR32xg13FlashController(IMachine machine, MappedMemory flash)22         public EFR32xg13FlashController(IMachine machine, MappedMemory flash)
23         {
24             this.flash = flash;
25             if(flash.Size < PageNumber * PageSize)
26             {
27                 throw new ConstructionException($"Provided flash size is too small, expected 0x{PageNumber * PageSize:X} bytes, got 0x{flash.Size:X} bytes");
28             }
29             interruptsManager = new InterruptManager<Interrupt>(this);
30 
31             var registersMap = new Dictionary<long, DoubleWordRegister>
32             {
33                 {(long)Registers.Control, new DoubleWordRegister(this, 0x1)
34                     .WithTaggedFlag("ADDRFAULTEN", 0)
35                     .WithTaggedFlag("CLKDISFAULTEN", 1)
36                     .WithTaggedFlag("PWRUPONDEMAND", 2)
37                     .WithTaggedFlag("IFCREADCLEAR", 3)
38                     .WithTaggedFlag("TIMEOUTFAULTEN", 4)
39                     .WithReservedBits(5, 3)
40                     .WithIgnoredBits(8, 1) // this is written by emlib as an errata
41                     .WithReservedBits(9, 23)
42                 },
43                 {(long)Registers.ReadControl, new DoubleWordRegister(this, 0x1000100)
44                     .WithReservedBits(0, 3)
45                     .WithTaggedFlag("IFCDIS", 3)
46                     .WithTaggedFlag("AIDIS", 4)
47                     .WithTaggedFlag("ICCDIS", 5)
48                     .WithReservedBits(6, 2)
49                     .WithTaggedFlag("PREFETCH", 8)
50                     .WithTaggedFlag("USEHPROT", 9)
51                     .WithReservedBits(10, 14)
52                     .WithTag("MODE", 24, 2)
53                     .WithReservedBits(26, 2)
54                     .WithTaggedFlag("SCBTP", 28)
55                     .WithReservedBits(29, 3)
56                 },
57                 {(long)Registers.WriteControl, new DoubleWordRegister(this)
58                     .WithFlag(0, out isWriteEnabled, name: "WREN")
59                     .WithTaggedFlag("IRQERASEABORT", 1)
60                     .WithReservedBits(2, 30)
61                 },
62                 {(long)Registers.WriteCommand, new DoubleWordRegister(this)
63                     .WithFlag(0, FieldMode.Toggle, changeCallback: (_, __) => UpdateWriteAddress(), name: "LADDRIM")
64                     .WithFlag(1, FieldMode.Toggle, changeCallback: (_, __) => ErasePage(), name: "ERASEPAGE")
65                     .WithTaggedFlag("WRITEEND", 2)
66                     .WithFlag(3, FieldMode.Toggle, changeCallback: (_, __) => WriteWordToFlash(), name: "WRITEONCE")
67                     .WithTaggedFlag("WRITETRIG", 4)
68                     .WithTaggedFlag("ERASEABORT", 5)
69                     .WithReservedBits(6, 2)
70                     .WithTaggedFlag("ERASEMAIN0", 8)
71                     .WithReservedBits(9, 3)
72                     .WithTaggedFlag("CLEARWDATA", 12)
73                     .WithReservedBits(13, 19)
74                 },
75                 {(long)Registers.AddressBuffer, new DoubleWordRegister(this)
76                     .WithValueField(0, 32, out writeAddress, name: "ADDRB")
77                 },
78                 {(long)Registers.WriteData, new DoubleWordRegister(this)
79                     .WithValueField(0, 32, out writeData, writeCallback: (_, __) => writeDataReady.Value = false, name: "WDATA")
80                 },
81                 {(long)Registers.Status, new DoubleWordRegister(this, 0x8)
82                     .WithFlag(0, FieldMode.Read, valueProviderCallback: _ => false, name: "BUSY")
83                     .WithTaggedFlag("LOCKED", 1)
84                     .WithFlag(2, out invalidAddress, FieldMode.Read, name: "INVADDR")
85                     // we assume a single-word buffer
86                     .WithFlag(3,  out writeDataReady, FieldMode.Read, name: "WDATAREADY")
87                     .WithTaggedFlag("WORDTIMEOUT", 4)
88                     .WithTaggedFlag("ERASEABORT", 5)
89                     .WithTaggedFlag("PCRUNNING", 6)
90                     .WithReservedBits(7, 17)
91                     .WithTag("WDATAVALID", 24, 4)
92                     .WithTag("CLEARWDATA", 28, 4)
93                 },
94                 {(long)Registers.InterruptFlag, interruptsManager.GetMaskedInterruptFlagRegister<DoubleWordRegister>()},
95                 {(long)Registers.InterruptFlagSet, interruptsManager.GetInterruptSetRegister<DoubleWordRegister>()},
96                 {(long)Registers.InterruptFlagClear, interruptsManager.GetInterruptClearRegister<DoubleWordRegister>()},
97                 {(long)Registers.InterruptEnable, interruptsManager.GetInterruptEnableRegister<DoubleWordRegister>()},
98                 {(long)Registers.ConfigurationLock, new DoubleWordRegister(this)
99                     .WithValueField(0, 16,
100                         writeCallback: (_, value) => { isLocked = value != UnlockPattern; },
101                         valueProviderCallback: _ => isLocked ? 1u : 0u,
102                         name: "LOCKKEY")
103                     .WithReservedBits(16, 16)
104                 },
105                 {(long)Registers.Command, new DoubleWordRegister(this)
106                     .WithTaggedFlag("PWRUP", 0)
107                     .WithReservedBits(1, 31)
108                 },
109             };
110 
111             registers = new DoubleWordRegisterCollection(this, registersMap);
112 
113             //calling Reset to fill the memory with 0xFFs
114             Reset();
115         }
116 
ReadDoubleWord(long offset)117         public uint ReadDoubleWord(long offset)
118         {
119             return registers.Read(offset);
120         }
121 
WriteDoubleWord(long offset, uint value)122         public void WriteDoubleWord(long offset, uint value)
123         {
124             if(isLocked && lockableRegisters.Contains((Registers)offset))
125             {
126                 this.Log(LogLevel.Warning, "Trying to write 0x{0:X} to {1} register but the configuration is locked", value, (Registers)offset);
127                 return;
128             }
129             registers.Write(offset, value);
130         }
131 
Reset()132         public void Reset()
133         {
134             registers.Reset();
135             isLocked = false;
136             internalAddressRegister = 0;
137 
138             // Clear the whole flash memory
139             for(var i = 0; i < PageNumber; ++i)
140             {
141                 flash.WriteBytes(PageSize * i, ErasePattern, 0, PageSize);
142             }
143         }
144 
145         [IrqProvider]
146         public GPIO IRQ => new GPIO();
147         public long Size => 0x800;
148 
WriteWordToFlash()149         private void WriteWordToFlash()
150         {
151             const uint highWord = 0xFFFF0000;
152             const uint lowWord = 0xFFFF;
153             if(isWriteEnabled.Value && !invalidAddress.Value)
154             {
155                 var effectiveValue = 0u;
156                 var oldValue = flash.ReadDoubleWord(internalAddressRegister);
157                 var writeValue = (uint)writeData.Value;
158                 var newValue = oldValue & writeValue;
159 
160                 // This code is here to reflect half-word (16bits) writes. It is legal to unset bits once for each half-word.
161                 // The driver can write 0xFFFF1111 followed by 0x2222FFFF, resulting in 0x22221111.
162                 // It is also ok to write 0xFFFF1111 followed by 0x22221111.
163                 // Technicaly, it should also be ok to write 0xFFFF1111 followed by 0x22223333 (the lower half-word is not zeroing any more bits),
164                 // which would result in 0x22221111, but we treat this situation as a possible bug and we issue a warning.
165                 // The same result would be achieved when writing 0xFFFF1111 followed 0x22220000 -> 0x22221111 (the lower part is not effective).
166                 var writeHigh = writeValue & highWord;
167                 var oldHigh = oldValue & highWord;
168                 var newHigh = newValue & highWord;
169                 if(writeHigh != highWord && oldHigh != highWord && oldHigh != writeHigh)
170                 {
171                     this.Log(LogLevel.Warning, "Writing upper word at dirty address 0x{0:X} - currently holding 0x{2:X}, trying to write 0x{1:X}", internalAddressRegister, writeHigh, oldHigh);
172                     effectiveValue |= oldHigh;
173                 }
174                 else
175                 {
176                     effectiveValue |= newHigh;
177                 }
178 
179                 var writeLow = writeValue & lowWord;
180                 var oldLow = oldValue & lowWord;
181                 var newLow = newValue & lowWord;
182                 if(writeLow != lowWord && oldLow != lowWord && oldLow != writeLow)
183                 {
184                     this.Log(LogLevel.Warning, "Writing lower word at dirty address 0x{0:X} - currently holding 0x{2:X}, trying to write 0x{1:X}", internalAddressRegister, writeLow, oldLow);
185                     effectiveValue |= oldLow;
186                 }
187                 else
188                 {
189                     effectiveValue |= newLow;
190                 }
191 
192                 this.Log(LogLevel.Noisy, "Writing 0x{0:X} to 0x{1:X}", effectiveValue, internalAddressRegister);
193                 flash.WriteDoubleWord(internalAddressRegister, effectiveValue);
194                 internalAddressRegister += 4;
195                 writeDataReady.Value = true;
196                 interruptsManager.SetInterrupt(Interrupt.WriteDone);
197             }
198             else
199             {
200                 this.Log(LogLevel.Warning, "Trying to write to flash, but it didn't work {0} {1}", isWriteEnabled.Value, invalidAddress.Value);
201             }
202         }
203 
ErasePage()204         private void ErasePage()
205         {
206             //while the MSC_WRITECMD.ERASEPAGE suggests using MSC_ADDRB directly, MSC_STATUS.INVADDR is described
207             //as "Invalid Write Address or Erase Page", suggesting using the internal address register
208             if(isWriteEnabled.Value && !invalidAddress.Value)
209             {
210                 flash.WriteBytes((long)(internalAddressRegister), ErasePattern, 0, PageSize);
211                 this.Log(LogLevel.Noisy, "Erasing page on address 0x{0:X}", internalAddressRegister);
212                 interruptsManager.SetInterrupt(Interrupt.EraseDone);
213             }
214             else
215             {
216                 this.Log(LogLevel.Warning, "Trying to erase page at 0x{0:X}, but writing is disabled", internalAddressRegister);
217             }
218         }
219 
UpdateWriteAddress()220         private void UpdateWriteAddress()
221         {
222             if(isWriteEnabled.Value)
223             {
224                 if((long)writeAddress.Value < flash.Size)
225                 {
226                     internalAddressRegister = (uint)writeAddress.Value;
227                     invalidAddress.Value = false;
228                 }
229                 else
230                 {
231                     this.Log(LogLevel.Error, "Trying to write outside the flash memory at 0x{0:X}", writeAddress.Value);
232                     invalidAddress.Value = true;
233                 }
234             }
235         }
236 
237         private bool isLocked;
238         private uint internalAddressRegister;
239 
240         private readonly IFlagRegisterField isWriteEnabled;
241         private readonly IValueRegisterField writeAddress;
242         private readonly IValueRegisterField writeData;
243         private readonly IFlagRegisterField invalidAddress;
244         private readonly IFlagRegisterField writeDataReady;
245         private readonly DoubleWordRegisterCollection registers;
246         private readonly MappedMemory flash;
247         private readonly byte[] ErasePattern = (byte[])Enumerable.Repeat((byte)0xFF, PageSize).ToArray();
248         private readonly Registers[] lockableRegisters = new Registers[] { Registers.Control, Registers.ReadControl,
249                                 Registers.WriteCommand, Registers.StartupControl, Registers.SoftwareUnlockAPPCommand };
250         private readonly InterruptManager<Interrupt> interruptsManager;
251 
252         private const uint UnlockPattern = 0x1B71;
253         private const int PageSize = 2048;
254         private const int PageNumber = 256;
255 
256         private enum Interrupt
257         {
258             EraseDone,
259             WriteDone,
260             CacheHitsOverflow,
261             CacheMissesOverflow,
262             FlashPowerUpSequenceComplete,
263             ICacheRAMParityError,
264             FlashControllerWriteBufferOverflow,
265             [NotSettable]
266             Reserved,
267             FlashLVEWriteError
268         }
269 
270         private enum Registers : long
271         {
272             Control = 0x00,
273             ReadControl = 0x04,
274             WriteControl = 0x08,
275             WriteCommand = 0x0C,
276             AddressBuffer = 0x10,
277             WriteData = 0x18,
278             Status = 0x1C,
279             InterruptFlag = 0x30,
280             InterruptFlagSet = 0x34,
281             InterruptFlagClear = 0x38,
282             InterruptEnable = 0x3C,
283             ConfigurationLock = 0x40,
284             FlashCacheCommand = 0x44,
285             CacheHitsCounter = 0x48,
286             CacheMissesCounter = 0x4C,
287             MassEraseLock = 0x54,
288             StartupControl = 0x5C,
289             Command = 0x74,
290             BootloaderReadAndWriteEnable = 0x90,
291             SoftwareUnlockAPPCommand = 0x94,
292             CacheConfiguration0 = 0x98,
293         }
294     }
295 }
296