1 //
2 // Copyright (c) 2010-2024 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.Threading;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Core.Structure;
12 using Antmicro.Renode.Core.Structure.Registers;
13 using Antmicro.Renode.Logging;
14 using Antmicro.Renode.Peripherals.Bus;
15 using Antmicro.Renode.Time;
16 using Antmicro.Renode.Utilities;
17 
18 namespace Antmicro.Renode.Peripherals.SPI
19 {
20     public sealed class STM32H7_QuadSPI : NullRegistrationPointPeripheralContainer<GenericSpiFlash>, IKnownSize,
21         IDoubleWordPeripheral, IWordPeripheral, IBytePeripheral
22     {
23         // NOTE: Current implementation does not support DMA interface
STM32H7_QuadSPI(IMachine machine)24         public STM32H7_QuadSPI(IMachine machine) : base(machine)
25         {
26             registers = new DoubleWordRegisterCollection(this);
27             DefineRegisters();
28         }
29 
Reset()30         public override void Reset()
31         {
32             lock(locker)
33             {
34                 pollingTokenSource.Cancel();
35                 pollingTokenSource = new CancellationTokenSource();
36 
37                 registers.Reset();
38                 ClearTransferFifo();
39 
40                 skipInstruction = true;
41                 skipAddress = true;
42                 skipAlternateBytes = true;
43                 skipData = true;
44 
45                 IRQ.Unset();
46             }
47         }
48 
ReadDoubleWord(long offset)49         public uint ReadDoubleWord(long offset)
50         {
51             return registers.Read(offset);
52         }
53 
WriteDoubleWord(long offset, uint value)54         public void WriteDoubleWord(long offset, uint value)
55         {
56             registers.Write(offset, value);
57         }
58 
ReadWord(long offset)59         public ushort ReadWord(long offset)
60         {
61             if(!CheckDataRegisterOffset(offset))
62             {
63                 return 0;
64             }
65             return (ushort)ReadFromDataRegister(16);
66         }
67 
WriteWord(long offset, ushort value)68         public void WriteWord(long offset, ushort value)
69         {
70             if(!CheckDataRegisterOffset(offset))
71             {
72                 return;
73             }
74             WriteToDataRegister(value, 16);
75         }
76 
ReadByte(long offset)77         public byte ReadByte(long offset)
78         {
79             if(!CheckDataRegisterOffset(offset))
80             {
81                 return 0;
82             }
83             return (byte)ReadFromDataRegister(8);
84         }
85 
WriteByte(long offset, byte value)86         public void WriteByte(long offset, byte value)
87         {
88             if(!CheckDataRegisterOffset(offset))
89             {
90                 return;
91             }
92             WriteToDataRegister(value, 8);
93         }
94 
95         public long Size => 0x1000;
96         public GPIO IRQ { get; } = new GPIO();
97 
98         // This value can be used to affect the interval between next polling events
99         // since on real HW it depends on the clock speed that is configured for QSPI kernel
100         public ulong PollingMultiplier { get; set; } = 1;
101 
CheckDataRegisterOffset(long offset)102         private bool CheckDataRegisterOffset(long offset)
103         {
104             if(offset != (long)Registers.Data)
105             {
106                 this.Log(LogLevel.Error, "This peripheral can only handle non 32-bit access at {0}. This operation has no effect", Registers.Data);
107                 return false;
108             }
109             return true;
110         }
111 
DefineRegisters()112         private void DefineRegisters()
113         {
114             Registers.Control.Define(registers)
115                 .WithFlag(0, out enabled, name: "Enable")
116                 .WithFlag(1,
117                     writeCallback: (_, value) =>
118                     {
119                         if(value)
120                         {
121                             lock(locker)
122                             {
123                                 pollingTokenSource.Cancel();
124                                 pollingTokenSource = new CancellationTokenSource();
125                                 remainingBytesToTransfer = null;
126                             }
127                         }
128                     },
129                     name: "Abort")
130                 .WithReservedBits(2, 1)
131                 .WithTaggedFlag("Timeout counter enable", 3)
132                 .WithTaggedFlag("Sample shift", 4)
133                 .WithReservedBits(5, 1)
134                 .WithTaggedFlag("Dual-flash mode", 6)
135                 .WithTaggedFlag("Flash memory selection", 7)
136                 .WithValueField(8, 5, out fifoThreshold, name: "FIFO threshold level")
137                 .WithReservedBits(13, 3)
138                 .WithTaggedFlag("Transfer error interrupt enable", 16)
139                 .WithFlag(17, out transferCompleteIrqEnable, name: "Transfer complete interrupt enable")
140                 .WithFlag(18, out fifoThresholdInterruptEnable, name: "FIFO threshold interrupt enable")
141                 .WithFlag(19, out statusMatchInterruptEnable, name: "Status match interrupt enable")
142                 .WithTaggedFlag("Timeout interrupt enable", 20)
143                 .WithReservedBits(21, 1)
144                 .WithFlag(22, out pollingModeStopOnMatch, name: "Automatic status-polling mode stop")
145                 .WithEnumField(23, 1, out pollingMatchMode, name: "Polling match mode")
146                 .WithTag("Prescaler", 24, 8)
147                 .WithWriteCallback((_, __) => UpdateInterrupts());
148 
149             Registers.DeviceConfiguration.Define(registers)
150                 .WithTaggedFlag("Clock mode (mode 0/mode 3)", 0)
151                 .WithReservedBits(1, 7)
152                 .WithTag("Chip select high time", 8, 3)
153                 .WithReservedBits(11, 5)
154                 .WithValueField(16, 5, out flashSize, name: "Flash memory size")
155                 .WithReservedBits(21, 11);
156 
157             Registers.Status.Define(registers)
158                 .WithFlag(0, FieldMode.Read, name: "Transmit error flag")
159                 .WithFlag(1, out transferComplete, FieldMode.Read, name: "Transfer complete flag")
160                 .WithFlag(2, out fifoThresholdReached, FieldMode.Read, name: "FIFO threshold flag")
161                 .WithFlag(3, out statusMatch, FieldMode.Read, name: "Status match flag")
162                 .WithFlag(4, FieldMode.Read, name: "Timeout flag")
163                 .WithFlag(5, FieldMode.Read, name: "Busy")
164                 .WithReservedBits(6, 2)
165                 .WithValueField(8, 6, FieldMode.Read,
166                     valueProviderCallback: _ =>
167                     {
168                         switch(functionalMode.Value)
169                         {
170                             case ModeOfOperation.AutomaticStatusPolling:
171                             case ModeOfOperation.MemoryMapped:
172                                 return 0;
173                             case ModeOfOperation.IndirectRead:
174                             case ModeOfOperation.IndirectWrite:
175                                 var count = transferFifo.Count;
176                                 return (count > MaximumFifoDepth) ? MaximumFifoDepth : (ulong)count;
177                             default:
178                                 throw new InvalidOperationException($"Invalid mode: {functionalMode.Value}");
179                         }
180                     },
181                     name: "FIFO level")
182                 .WithReservedBits(14, 18);
183 
184             Registers.FlagClear.Define(registers)
185                 .WithFlag(0, FieldMode.WriteOneToClear, name: "Transmit error flag clear")
186                 .WithFlag(1, FieldMode.WriteOneToClear, writeCallback: (_, value) => transferComplete.Value = false, name: "Transfer complete flag clear")
187                 .WithReservedBits(2, 1)
188                 .WithFlag(3, FieldMode.WriteOneToClear, writeCallback: (_, value) => statusMatch.Value = false, name: "Status match flag clear")
189                 .WithFlag(4, FieldMode.WriteOneToClear, name: "Timeout flag clear")
190                 .WithReservedBits(5, 27)
191                 .WithWriteCallback((_, __) => UpdateInterrupts());
192 
193             Registers.DataLength.Define(registers)
194                 .WithValueField(0, 32, out dataSize, name: "Data length");
195 
196             Registers.CommunicationConfiguration.Define(registers)
197                 .WithValueField(0, 8, out instruction, name: "Instruction")
198                 .WithValueField(8, 2, writeCallback: (_, value) => skipInstruction = value == 0, name: "Instruction mode")
199                 .WithValueField(10, 2, writeCallback: (_, value) => skipAddress = value == 0, name: "Address mode")
200                 .WithValueField(12, 2, out addressSize, name: "Address size")
201                 .WithValueField(14, 2, writeCallback: (_, value) => skipAlternateBytes = value == 0, name: "Alternate byte mode")
202                 .WithValueField(16, 2, out alternateBytesSize, name: "Alternate byte size")
203                 .WithTag("Number of dummy cycles", 18, 5)
204                 .WithReservedBits(23, 1)
205                 .WithValueField(24, 2, writeCallback: (_, value) => skipData = value == 0, name: "Data mode")
206                 .WithEnumField(26, 2, out functionalMode,
207                     writeCallback: (oldValue, value) =>
208                     {
209                         // Can't use `changeCallback` here, since it will trigger after the write callback, that is hooked to triggering the transfer
210                         // this could cause the queue to loose all data immediately after the transfer happens, if there was a mode change directly before
211                         if(oldValue != value)
212                         {
213                             ClearTransferFifo();
214                             pollingTokenSource.Cancel();
215                             pollingTokenSource = new CancellationTokenSource();
216                             UpdateInterrupts();
217                         }
218                     },
219                     name: "Functional mode")
220                 .WithTaggedFlag("Send instruction only once", 28)
221                 .WithTaggedFlag("Free-running clock mode", 29)
222                 .WithTaggedFlag("DDR hold", 30)
223                 .WithTaggedFlag("DDR mode", 31)
224                 // This callback needs to be triggered last, after all other fields are populated and callbacks triggered
225                 .WithWriteCallback((_, __) => TriggerTransfer(TriggerTransferSource.Instruction));
226 
227             Registers.Address.Define(registers)
228                 .WithValueField(0, 32, out address, name: "Address")
229                 .WithWriteCallback(writeCallback: (_, __) => TriggerTransfer(TriggerTransferSource.Address));
230 
231             Registers.AlternateByte.Define(registers)
232                 .WithValueField(0, 32, out alternateBytes, name: "Alternate bytes");
233 
234             Registers.Data.Define(registers)
235                 .WithValueField(0, 32,
236                     writeCallback: (_, value) =>
237                     {
238                         WriteToDataRegister(value, 32);
239                     },
240                     valueProviderCallback: _ =>
241                     {
242                         return ReadFromDataRegister(32);
243                     },
244                     name: "Data bytes");
245 
246             Registers.PollingStatusMask.Define(registers)
247                 .WithValueField(0, 32, out pollingMask, name: "Polling status mask");
248 
249             Registers.PollingStatusMatch.Define(registers)
250                 .WithValueField(0, 32, out pollingReferenceMatch, name: "Polling status match");
251 
252             Registers.PollingInterval.Define(registers)
253                 .WithValueField(0, 16, out pollingInterval, name: "Polling interval")
254                 .WithReservedBits(16, 16);
255         }
256 
ClearTransferFifo()257         private void ClearTransferFifo()
258         {
259             transferFifo.Clear();
260             remainingBytesToTransfer = null;
261         }
262 
263         // For clarity, sizes are given in number of bits
CheckDataRegisterAccessSize(int size)264         private void CheckDataRegisterAccessSize(int size)
265         {
266             if(size % 8 != 0 || size / 8 > 4)
267             {
268                 throw new ArgumentException($"Size {size} is not properly constrained, cannot access data register");
269             }
270         }
271 
WriteToDataRegister(ulong val, int size)272         private void WriteToDataRegister(ulong val, int size)
273         {
274             CheckDataRegisterAccessSize(size);
275             if(functionalMode.Value != ModeOfOperation.IndirectWrite)
276             {
277                 this.Log(LogLevel.Error, "Data register should only be written in {0} mode. This operation has no effect", ModeOfOperation.IndirectWrite);
278                 return;
279             }
280             lock(locker)
281             {
282                 transferFifo.EnqueueRange(BitHelper.GetBytesFromValue(val, size / 8));
283                 UpdateInterrupts();
284                 TriggerTransfer(TriggerTransferSource.DataWrite);
285             }
286         }
287 
ReadFromDataRegister(int size)288         private uint ReadFromDataRegister(int size)
289         {
290             CheckDataRegisterAccessSize(size);
291             if(!(functionalMode.Value == ModeOfOperation.IndirectRead || functionalMode.Value == ModeOfOperation.AutomaticStatusPolling))
292             {
293                 this.Log(LogLevel.Error, "Data register should not be read in mode: {0}. Returning 0", functionalMode.Value);
294                 return 0;
295             }
296             byte[] response = new byte[4];
297             lock(locker)
298             {
299                 for(int i = 0; i < size / 8; ++i)
300                 {
301                     if(transferFifo.TryDequeue(out var val))
302                     {
303                         // Push the value back into the queue immediately in AutomaticStatusPolling mode, so it can be read from Data register again
304                         // It's guaranteed in `ReceiveData` that the queue won't grow beyond 4-byte limit
305                         if(functionalMode.Value == ModeOfOperation.AutomaticStatusPolling)
306                         {
307                             transferFifo.Enqueue(val);
308                         }
309                     }
310                     else
311                     {
312                         this.Log(LogLevel.Warning, "Read from empty transfer FIFO, containing less data than 32 bits. Filling with zeroes");
313                     }
314                     response[i] = val;
315                 }
316                 if(functionalMode.Value == ModeOfOperation.AutomaticStatusPolling)
317                 {
318                     // In case of polling, if less than the full size of the queue is requested, pop and push the data back, so it's not corrupted
319                     for(int i = size / 8; i < 4; ++i)
320                     {
321                         if(transferFifo.TryDequeue(out var val))
322                         {
323                             transferFifo.Enqueue(val);
324                         }
325                     }
326                 }
327                 UpdateInterrupts();
328                 TriggerTransfer(TriggerTransferSource.DataRead);
329             }
330             return BitHelper.ToUInt32(response, 0, 4, true);
331         }
332 
UpdateInterrupts()333         private void UpdateInterrupts()
334         {
335             bool thresholdReached = functionalMode.Value == ModeOfOperation.IndirectRead && (transferFifo.Count >= (int)fifoThreshold.Value);
336             thresholdReached |= functionalMode.Value == ModeOfOperation.IndirectWrite && ((MaximumFifoDepth - transferFifo.Count) >= (int)fifoThreshold.Value);
337             fifoThresholdReached.Value = thresholdReached;
338 
339             bool status = (transferComplete.Value && transferCompleteIrqEnable.Value)
340                           || (statusMatch.Value && statusMatchInterruptEnable.Value)
341                           || (fifoThresholdReached.Value && fifoThresholdInterruptEnable.Value);
342 
343             this.Log(LogLevel.Noisy, "IRQ set to: {0}", status);
344             IRQ.Set(status);
345         }
346 
SplitToBytesAndSend(IValueRegisterField value, int size, string name)347         private void SplitToBytesAndSend(IValueRegisterField value, int size, string name)
348         {
349             var bytes = BitHelper.GetBytesFromValue(value.Value, size);
350             this.Log(LogLevel.Debug, "Sending {0}: {1}", name, Misc.PrettyPrintCollectionHex(bytes));
351             foreach(var part in bytes)
352             {
353                 RegisteredPeripheral.Transmit(part);
354             }
355         }
356 
TriggerTransfer(TriggerTransferSource source)357         private void TriggerTransfer(TriggerTransferSource source)
358         {
359             if(!enabled.Value)
360             {
361                 this.Log(LogLevel.Warning, "Trying to trigger SPI transfer, but the peripheral is disabled");
362                 return;
363             }
364             if(functionalMode.Value == ModeOfOperation.MemoryMapped)
365             {
366                 this.Log(LogLevel.Debug, "The peripheral is in MemoryMapped mode, all transfers are ignored");
367                 return;
368             }
369             this.Log(LogLevel.Debug, "Trying to trigger SPI transfer, source: {0}, mode: {1}", source, functionalMode.Value);
370 
371             lock(locker)
372             {
373                 // If more data is expected in the command sequence, delay the transfer
374                 // until the data is written into the relevant register
375                 if(!VerifyIfTransferShouldTrigger(source))
376                 {
377                     return;
378                 }
379 
380                 // If a transfer requires more data to complete, then skip immediately to the data phase
381                 if(remainingBytesToTransfer == null)
382                 {
383                     if(!skipInstruction)
384                     {
385                         this.Log(LogLevel.Debug, "Sending command: 0x{0:X}", instruction.Value);
386                         RegisteredPeripheral.Transmit((byte)instruction.Value);
387                     }
388 
389                     if(!skipAddress)
390                     {
391                         SplitToBytesAndSend(address, (int)addressSize.Value + 1, "address");
392                     }
393 
394                     if(!skipAlternateBytes)
395                     {
396                         SplitToBytesAndSend(alternateBytes, (int)alternateBytesSize.Value + 1, "alternate bytes");
397                     }
398                 }
399 
400                 if(!skipData)
401                 {
402                     TransferData();
403                     HandlePollingData();
404                 }
405                 else
406                 {
407                     // Transfer is only complete here, if there was no data to send
408                     // since this function is re-entrant otherwise
409                     RegisteredPeripheral.FinishTransmission();
410                     transferComplete.Value = true;
411                 }
412                 UpdateInterrupts();
413             }
414         }
415 
VerifyIfTransferShouldTrigger(TriggerTransferSource source)416         private bool VerifyIfTransferShouldTrigger(TriggerTransferSource source)
417         {
418             // Check if a write to this register (identified by `source`), should result in data transfer
419             // or if the peripheral is still waiting for more data (e.g. it got an instruction but it's missing data)
420             switch (source)
421             {
422                 case TriggerTransferSource.Instruction:
423                     if(skipInstruction && source == TriggerTransferSource.Instruction)
424                     {
425                         // If the source is Instruction register, but instruction stage is disabled, the transfer shouldn't trigger
426                         this.Log(LogLevel.Noisy, "Not transferring, {0} is disabled", nameof(TriggerTransferSource.Instruction));
427                         return false;
428                     }
429                     if(skipAddress && !skipData)
430                     {
431                         // Data without address - let the next stage determine if it's valid
432                         goto case TriggerTransferSource.Address;
433                     }
434                     if(!skipAddress)
435                     {
436                         this.Log(LogLevel.Noisy, "Address is missing, not transferring");
437                         // Wait for address
438                         return false;
439                     }
440                     goto case TriggerTransferSource.Address;
441                 case TriggerTransferSource.Address:
442                     if(skipAddress && source == TriggerTransferSource.Address)
443                     {
444                         this.Log(LogLevel.Noisy, "Not transferring, {0} is disabled", nameof(TriggerTransferSource.Address));
445                         return false;
446                     }
447                     if(!skipData && functionalMode.Value == ModeOfOperation.IndirectWrite)
448                     {
449                         this.Log(LogLevel.Noisy, "Data is missing, not transferring");
450                         // Wait for data
451                         return false;
452                     }
453                     // It's safe to break here, instead of jumping to data, since it can be a terminal stage too
454                     break;
455                 case TriggerTransferSource.DataWrite:
456                     if(skipData && source == TriggerTransferSource.DataWrite)
457                     {
458                         this.Log(LogLevel.Noisy, "Not transferring, {0} is disabled", nameof(TriggerTransferSource.DataWrite));
459                         return false;
460                     }
461                     break;
462                 case TriggerTransferSource.DataRead:
463                     // Polling mode schedules transfers by its own
464                     if(functionalMode.Value == ModeOfOperation.AutomaticStatusPolling)
465                     {
466                         return false;
467                     }
468                     // Only allow Data read to trigger transfer, if there already is a transfer ongoing
469                     // and there was not enough space in the transfer FIFO, so the transfer was suspended
470                     if(remainingBytesToTransfer == null)
471                     {
472                         this.Log(LogLevel.Noisy, "Not transferring, {0} transfer is complete", nameof(TriggerTransferSource.DataRead));
473                         return false;
474                     }
475                     break;
476                 default:
477                     this.ErrorLog("Trigger source {0} is unrecognized, not transferring", source);
478                     return false;
479             }
480             return true;
481         }
482 
DoesPolledDataMatch(uint polledUInt)483         private bool DoesPolledDataMatch(uint polledUInt)
484         {
485             switch(pollingMatchMode.Value)
486             {
487                 case PollingMatchMode.AllBits:
488                     // Since XOR returns 1 only on differing bits, zero means no change at all
489                     // AND with the mask, and if equals zero, both values match on all bits
490                     return ((polledUInt ^ pollingReferenceMatch.Value) & pollingMask.Value) == 0;
491                 case PollingMatchMode.AnyBit:
492                     // If the diff (AND mask) is equal to the mask bytes, that means there was no single bit matched
493                     return ((polledUInt ^ pollingReferenceMatch.Value) & pollingMask.Value) != pollingMask.Value;
494                 default:
495                     throw new InvalidOperationException($"Invalid match mode: {pollingMatchMode.Value}");
496             }
497         }
498 
HandlePollingData()499         private void HandlePollingData()
500         {
501             if(functionalMode.Value != ModeOfOperation.AutomaticStatusPolling)
502             {
503                 return;
504             }
505 
506             uint responseUInt = ReadFromDataRegister((int)(dataSize.Value + 1) * 8);
507             bool matched = DoesPolledDataMatch(responseUInt);
508             if(matched)
509             {
510                 statusMatch.Value = true;
511             }
512 
513             if(!matched || !pollingModeStopOnMatch.Value)
514             {
515                 var nextEvent = pollingInterval.Value * PollingMultiplier;
516                 this.Log(LogLevel.Debug, "Polling not matched (got: 0x{0:X}, expected: 0x{1:X}, mask: 0x{2:X}), scheduling next polling in {3} microseconds",
517                     responseUInt, pollingReferenceMatch.Value, pollingMask.Value, nextEvent);
518                 var token = pollingTokenSource.Token;
519                 Machine.ScheduleAction(TimeInterval.FromMicroseconds(nextEvent), _ =>
520                 {
521                     lock(locker)
522                     {
523                         if(token.IsCancellationRequested)
524                         {
525                             return;
526                         }
527                         // Schedule a deferred polling action, if there was no match
528                         transferFifo.Clear();
529                         TransferData();
530                         HandlePollingData();
531                         UpdateInterrupts();
532                     }
533                 }, $"{nameof(STM32H7_QuadSPI)} Polling task");
534             }
535         }
536 
TransferData()537         private void TransferData()
538         {
539             if(skipData)
540             {
541                 return;
542             }
543             if(functionalMode.Value == ModeOfOperation.AutomaticStatusPolling && dataSize.Value > 3)
544             {
545                 // In Status polling only 4 bytes can be transferred at once, for the purpose of the comparison
546                 this.Log(LogLevel.Error, "DataSize cannot be more than 3 when in {0} mode. Automatically clamping the comparison length to 3",
547                     nameof(ModeOfOperation.AutomaticStatusPolling));
548                 dataSize.Value = 3;
549             }
550 
551             if(remainingBytesToTransfer == null)
552             {
553                 if(dataSize.Value == uint.MaxValue)
554                 {
555                     // Transfer all bytes, until the end of the flash memory
556                     remainingBytesToTransfer = 2 << (int)(flashSize.Value + 1);
557                 }
558                 else
559                 {
560                     remainingBytesToTransfer = (int)dataSize.Value + 1;
561                 }
562             }
563 
564             byte d = 0;
565             while((functionalMode.Value != ModeOfOperation.IndirectWrite || transferFifo.TryDequeue(out d))
566                 && remainingBytesToTransfer > 0 && transferFifo.Count <= MaximumFifoDepth)
567             {
568                 --remainingBytesToTransfer;
569                 if(functionalMode.Value == ModeOfOperation.IndirectWrite)
570                 {
571                     this.Log(LogLevel.Debug, "Sending byte: 0x{0:X}, bytes left: {1}", d, remainingBytesToTransfer);
572                 }
573                 var recv = RegisteredPeripheral.Transmit(d);
574                 if(functionalMode.Value != ModeOfOperation.IndirectWrite)
575                 {
576                     this.Log(LogLevel.Debug, "Got byte: 0x{0:X}, bytes left: {1}", recv, remainingBytesToTransfer);
577                     ReceiveData(recv);
578                 }
579             }
580             if(remainingBytesToTransfer == 0)
581             {
582                 RegisteredPeripheral.FinishTransmission();
583                 transferComplete.Value = true;
584                 remainingBytesToTransfer = null;
585             }
586         }
587 
ReceiveData(byte data)588         private void ReceiveData(byte data)
589         {
590             if(functionalMode.Value == ModeOfOperation.IndirectWrite)
591             {
592                 return;
593             }
594             transferFifo.Enqueue(data);
595             if(functionalMode.Value == ModeOfOperation.AutomaticStatusPolling)
596             {
597                 // Status polling implies that only the last 4 values obtained (uint32) make their way into the FIFO
598                 // So don't allow the queue to grow beyond this limit
599                 if(transferFifo.Count > 4)
600                 {
601                     transferFifo.Dequeue();
602                 }
603             }
604         }
605 
606         private const int MaximumFifoDepth = 32;
607 
608         private IFlagRegisterField transferCompleteIrqEnable;
609         private IFlagRegisterField statusMatchInterruptEnable;
610         private IFlagRegisterField fifoThresholdInterruptEnable;
611 
612         private IFlagRegisterField transferComplete;
613         private IFlagRegisterField statusMatch;
614         private IFlagRegisterField fifoThresholdReached;
615         private IFlagRegisterField pollingModeStopOnMatch;
616 
617         // Sizes specified here are always less by one than the real transfer size (that's how the HW handles the registers)
618         // e.g. size 0 means that 1 byte is to be transferred
619         private IFlagRegisterField enabled;
620         private IValueRegisterField fifoThreshold;
621         private IValueRegisterField instruction;
622         private IValueRegisterField dataSize;
623         private IValueRegisterField address;
624         private IValueRegisterField addressSize;
625         private IValueRegisterField alternateBytes;
626         private IValueRegisterField alternateBytesSize;
627         // Number of bytes in flash = 2 ** (flashSize + 1)
628         private IValueRegisterField flashSize;
629 
630         private IValueRegisterField pollingMask;
631         private IValueRegisterField pollingReferenceMatch;
632         private IValueRegisterField pollingInterval;
633 
634         private bool skipInstruction = true;
635         private bool skipAddress = true;
636         private bool skipAlternateBytes = true;
637         private bool skipData = true;
638         private int? remainingBytesToTransfer;
639         private readonly object locker = new object();
640 
641         private IEnumRegisterField<ModeOfOperation> functionalMode;
642         private IEnumRegisterField<PollingMatchMode> pollingMatchMode;
643 
644         private readonly DoubleWordRegisterCollection registers;
645 
646         private readonly Queue<byte> transferFifo = new Queue<byte>();
647 
648         private CancellationTokenSource pollingTokenSource = new CancellationTokenSource();
649 
650         private enum TriggerTransferSource
651         {
652             Instruction,
653             Address,
654             DataWrite,
655             DataRead
656         }
657 
658         private enum PollingMatchMode
659         {
660             AllBits = 0b0, // AND
661             AnyBit = 0b1, // OR
662         }
663 
664         private enum ModeOfOperation
665         {
666             IndirectWrite = 0b00,
667             IndirectRead = 0b01,
668             AutomaticStatusPolling = 0b10,
669             // Note, that we can't map a flash memory to address range here
670             // this has to be a property of the flash itself (e.g. GenericSpiFlash: underlyingMemory)
671             MemoryMapped = 0b11,
672         }
673 
674         private enum Registers
675         {
676             Control = 0x00,
677             DeviceConfiguration = 0x04,
678             Status = 0x08,
679             FlagClear = 0x0C,
680             DataLength = 0x10,
681             CommunicationConfiguration = 0x14,
682             Address = 0x18,
683             AlternateByte = 0x1C,
684             Data = 0x20,
685             PollingStatusMask = 0x24,
686             PollingStatusMatch = 0x28,
687             PollingInterval = 0x2C,
688             LowPowerTimeout = 0x30,
689         }
690     }
691 }
692