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 Antmicro.Renode.Peripherals.Bus;
10 using Antmicro.Renode.Logging;
11 using Antmicro.Renode.Core.Structure;
12 using Antmicro.Renode.Core;
13 using Antmicro.Renode.Core.Structure.Registers;
14 using Antmicro.Renode.Utilities;
15 using Antmicro.Renode.Utilities.Collections;
16 
17 namespace Antmicro.Renode.Peripherals.SPI
18 {
19     public class STM32H7_SPI : NullRegistrationPointPeripheralContainer<ISPIPeripheral>, IKnownSize, IDoubleWordPeripheral, IWordPeripheral, IBytePeripheral
20     {
STM32H7_SPI(IMachine machine)21         public STM32H7_SPI(IMachine machine) : base(machine)
22         {
23             registers = new DoubleWordRegisterCollection(this);
24             IRQ = new GPIO();
25             DMARecieve = new GPIO();
26 
27             transmitFifo = new Queue<uint>();
28             receiveFifo = new Queue<uint>();
29 
30             DefineRegisters();
31             Reset();
32         }
33 
Reset()34         public override void Reset()
35         {
36             IRQ.Unset();
37             DMARecieve.Unset();
38             iolockValue = false;
39             transmittedPackets = 0;
40             transmitFifo.Clear();
41             receiveFifo.Clear();
42             registers.Reset();
43         }
44 
45         // We can't use AllowedTranslations because then WriteByte/WriteWord will trigger
46         // an additional read (see ReadWriteExtensions:WriteByteUsingDoubleWord).
47         // We can't have this happenning for the data register.
ReadByte(long offset)48         public byte ReadByte(long offset)
49         {
50             return (byte)ReadDoubleWord(offset);
51         }
52 
WriteByte(long offset, byte value)53         public void WriteByte(long offset, byte value)
54         {
55             WriteDoubleWord(offset, value);
56         }
57 
ReadWord(long offset)58         public ushort ReadWord(long offset)
59         {
60             return (ushort)ReadDoubleWord(offset);
61         }
62 
WriteWord(long offset, ushort value)63         public void WriteWord(long offset, ushort value)
64         {
65             WriteDoubleWord(offset, value);
66         }
67 
ReadDoubleWord(long offset)68         public uint ReadDoubleWord(long offset)
69         {
70             return registers.Read(offset);
71         }
72 
WriteDoubleWord(long offset, uint value)73         public void WriteDoubleWord(long offset, uint value)
74         {
75             if(CanWriteToRegister((Registers)offset, value))
76             {
77                 registers.Write(offset, value);
78             }
79         }
80 
81         public long Size => 0x400;
82 
83         public GPIO IRQ { get; }
84         public GPIO DMARecieve { get; }
85 
86         protected virtual bool IsWba { get; } = false;
87 
DefineRegisters()88         private void DefineRegisters()
89         {
90             Registers.Control1.Define(registers)
91                 .WithFlag(0, out peripheralEnabled, name: "SPE", changeCallback: (_, value) =>
92                     {
93                         if(value)
94                         {
95                             TryTransmitData();
96                         }
97                         else
98                         {
99                             ResetTransmissionState();
100                             transmitFifo.Clear();
101                             receiveFifo.Clear();
102                             transmissionSize.Value = 0;
103                         }
104                     })
105                 .WithReservedBits(1, 7)
106                 .WithTaggedFlag("MASRX", 8)
107                 .WithFlag(9, out startTransmission, FieldMode.Read | FieldMode.Set, name: "CSTART", changeCallback: (_, value) =>
108                     {
109                         if(value)
110                         {
111                             endOfTransfer.Value = false;
112                             TryTransmitData();
113                         }
114                     })
115                 .WithFlag(10, FieldMode.Set, name: "CSUSP", writeCallback: (_, value) =>
116                     {
117                         if(value)
118                         {
119                             if(!endOfTransfer.Value)
120                             {
121                                 EndTransfer();
122                             }
123                             suspensionStatus.Value = true;
124                             UpdateInterrupts();
125                         }
126                     })
127                 .WithTaggedFlag("HDDIR", 11)
128                 .WithTaggedFlag("SSI", 12)
129                 .WithTaggedFlag("CRC33_17", 13)
130                 .WithTaggedFlag("RCRCINI", 14)
131                 .WithTaggedFlag("TCRCINI", 15)
132                 .WithFlag(16, name: "IOLOCK", valueProviderCallback: _ => iolockValue, changeCallback: (_, value) =>
133                     {
134                         if(value && !peripheralEnabled.Value)
135                         {
136                             this.Log(LogLevel.Warning, "Attempted to set IOLOCK while peripheral is enabled");
137                             return;
138                         }
139 
140                         iolockValue = value;
141                     })
142                 .WithReservedBits(17, 15);
143 
144             Registers.Control2.Define(registers)
145                 .WithValueField(0, 16, out transmissionSize, name: "TSIZE")
146                 .If(IsWba)
147                     .Then(r => r.WithReservedBits(16, 16))
148                     .Else(r => r.WithTag("TSER", 16, 16));
149 
150             Registers.Configuration1.Define(registers)
151                 .WithValueField(0, 5, out packetSizeBits, name: "DSIZE")
152                 .WithTag("FTHLV", 5, 4)
153                 .If(IsWba)
154                     .Then(r => r
155                         .WithTaggedFlag("UDRCFG", 9)
156                         .WithReservedBits(10, 3))
157                     .Else(r => r
158                         .WithTag("UDRCFG", 9, 2)
159                         .WithTag("UDRDET", 11, 2))
160                 .WithReservedBits(13, 1)
161                 .WithFlag(14, out receiveDMAEnabled, name: "RXDMAEN")
162                 // Software expects this value to be as it was set. Transmitting with DMA doesn't require any special logic
163                 .WithFlag(15, name: "TXDMAEN")
164                 .WithTag("CRCSIZE", 16, 5)
165                 .WithReservedBits(21, 1)
166                 .WithTaggedFlag("CRCEN", 22)
167                 .WithReservedBits(23, 5)
168                 .WithTag("MBR", 28, 3)
169                 .If(IsWba)
170                     .Then(r => r.WithTaggedFlag("BPASS", 31))
171                     .Else(r => r.WithReservedBits(31, 1));
172 
173             Registers.Configuration2.Define(registers)
174                 .WithTag("MSSI", 0, 4)
175                 .WithTag("MIDI", 4, 4)
176                 .WithReservedBits(8, 5)
177                 .If(IsWba)
178                     .Then(r => r
179                         .WithTaggedFlag("RDIOM", 13)
180                         .WithTaggedFlag("RDIOP", 14))
181                     .Else(r => r.WithReservedBits(13, 2))
182                 .WithTaggedFlag("IOSWP", 15)
183                 .WithReservedBits(16, 1)
184                 .WithTag("COMM", 17, 2)
185                 .WithTag("SP", 19, 3)
186                 // Only master mode is supported
187                 .WithFlag(22, name: "MASTER", valueProviderCallback: _ => true, writeCallback: (_, value) =>
188                     {
189                         if(!value)
190                         {
191                             this.Log(LogLevel.Error, "Attempted to set peripheral into SPI slave mode. Only master mode is supported");
192                         }
193                     })
194                 .WithFlag(23, out leastSignificantByteFirst, name: "LSBFRST")
195                 .WithTaggedFlag("CPHA", 24)
196                 .WithTaggedFlag("CPOL", 25)
197                 .WithTaggedFlag("SSM", 26)
198                 .WithReservedBits(27, 1)
199                 .WithTaggedFlag("SSIOP", 28)
200                 .WithTaggedFlag("SSOE", 29)
201                 .WithTaggedFlag("SSOM", 30)
202                 .WithTaggedFlag("AFCNTR", 31);
203 
204             Registers.InterruptEnable.Define(registers)
205                 .WithFlag(0, out receiveFifoThresholdInterruptEnable, name: "RXPIE")
206                 .WithFlag(1, out transmitFifoThresholdInterruptEnable, name: "TXPIE")
207                 .WithTaggedFlag("DXPIE", 2)
208                 .WithFlag(3, out endOfTransferInterruptEnable, name: "EOTIE")
209                 .WithTaggedFlag("TXTFIE", 4)
210                 .WithTaggedFlag("UDRIE", 5)
211                 .WithTaggedFlag("OVRIE", 6)
212                 .WithTaggedFlag("CRCEIE", 7)
213                 .WithTaggedFlag("TIFREIE", 8)
214                 .WithTaggedFlag("MODFIE", 9)
215                 .If(IsWba)
216                     .Then(r => r.WithReservedBits(10, 1))
217                     .Else(r => r.WithTaggedFlag("TSERFIE", 10))
218                 .WithReservedBits(11, 21)
219                 .WithWriteCallback((_, __) =>
220                 {
221                     UpdateInterrupts();
222                 });
223 
224             Registers.Status.Define(registers)
225                 .WithFlag(0, FieldMode.Read, name: "RXP", valueProviderCallback: _ => receiveFifo.Count > 0)
226                 // We always report that there is space for additional packets
227                 .WithFlag(1, FieldMode.Read, name: "TXP", valueProviderCallback: _ => true)
228                 // This flag is equal to RXP && TXP. Since TXP is always true this flag is equal to RXP
229                 .WithFlag(2, FieldMode.Read, name: "DXP", valueProviderCallback: _ => receiveFifo.Count > 0)
230                 .WithFlag(3, out endOfTransfer, FieldMode.Read, name: "EOT")
231                 .WithTaggedFlag("TXTF", 4)
232                 // Overrun and underrun never occur in this model
233                 .WithTaggedFlag("UDR", 5)
234                 .WithTaggedFlag("OVR", 6)
235                 .WithTaggedFlag("CRCE", 7)
236                 .WithTaggedFlag("TIFRE", 8)
237                 .WithTaggedFlag("MODF", 9)
238                 .If(IsWba)
239                     .Then(r => r.WithReservedBits(10, 1))
240                     .Else(r => r.WithTaggedFlag("TSERF", 10))
241                 .WithFlag(11, out suspensionStatus, FieldMode.Read, name: "SUSP")
242                 .WithFlag(12, FieldMode.Read, name: "TXC",
243                     valueProviderCallback: _ => transmissionSize.Value == 0 ? transmitFifo.Count == 0 : endOfTransfer.Value)
244                 .WithTag("RXPLVL", 13, 2)
245                 .WithTaggedFlag("RXWNE", 15)
246                 .WithValueField(16, 16, FieldMode.Read, name: "CTSIZE", valueProviderCallback: _ => transmissionSize.Value - transmittedPackets);
247 
248             Registers.InterruptStatusFlagsClear.Define(registers)
249                 .WithReservedBits(0, 3)
250                 .WithFlag(3, FieldMode.Write, name: "EOTC", writeCallback: (_, value) =>
251                     {
252                         if(value)
253                         {
254                             ResetTransmissionState();
255                         }
256                     })
257                 .WithTaggedFlag("TXTFC", 4)
258                 .WithTaggedFlag("UDRC", 5)
259                 .WithTaggedFlag("OVRC", 6)
260                 .WithTaggedFlag("CRCEC", 7)
261                 .WithTaggedFlag("TIFREC", 8)
262                 .WithTaggedFlag("MODFC", 9)
263                 .If(IsWba)
264                     .Then(r => r.WithReservedBits(10, 1))
265                     .Else(r => r.WithTaggedFlag("TSERFC", 10))
266                 .WithFlag(11, name: "SUSPC", writeCallback: (_, value) =>
267                     {
268                         if(value)
269                         {
270                             suspensionStatus.Value = false;
271                         }
272                     })
273                 .WithReservedBits(12, 20);
274 
275             Registers.TransmitData.Define(registers)
276                 .WithValueField(0, 32, FieldMode.Write, name: "SPI_TXDR", writeCallback: (_, value) =>
277                     {
278                         transmitFifo.Enqueue((uint)value);
279                         TryTransmitData();
280                     });
281 
282             Registers.ReceiveData.Define(registers)
283                 .WithValueField(0, 32, FieldMode.Read, name: "SPI_RXDR", valueProviderCallback: _ =>
284                     {
285                         if(!receiveFifo.TryDequeue(out var value))
286                         {
287                             this.Log(LogLevel.Error, "Receive data FIFO is empty. Returning 0");
288                             return 0;
289                         }
290                         UpdateInterrupts();
291                         return value;
292                     });
293 
294             if(!IsWba)
295             {
296                 Registers.I2SConfiguration.Define(registers)
297                     .WithFlag(0, name: "I2SMOD", valueProviderCallback: _ => false, writeCallback: (_, value) =>
298                         {
299                             if(value)
300                             {
301                                 this.Log(LogLevel.Error, "Attempted to enable I2S. This mode is not supported");
302                             }
303                         })
304                     .WithTag("I2SCFG[2:0]", 1, 3)
305                     .WithTag("I2SSTD[1:0]", 4, 2)
306                     .WithReservedBits(6, 1)
307                     .WithTaggedFlag("PCMSYNC", 7)
308                     .WithTag("DATLEN[1:0]", 8, 2)
309                     .WithTaggedFlag("CHLEN", 10)
310                     .WithTaggedFlag("CKPOL", 11)
311                     .WithTaggedFlag("FIXCH", 12)
312                     .WithTaggedFlag("WSINV", 13)
313                     .WithTaggedFlag("DATFMT", 14)
314                     .WithReservedBits(15, 1)
315                     .WithTag("I2SDIV[7:0]", 16, 8)
316                     .WithTaggedFlag("ODD", 24)
317                     .WithTaggedFlag("MCKOE", 25)
318                     .WithReservedBits(26, 6);
319             }
320         }
321 
CanWriteToRegister(Registers reg, uint value)322         private bool CanWriteToRegister(Registers reg, uint value)
323         {
324             if(peripheralEnabled.Value)
325             {
326                 switch(reg)
327                 {
328                     case Registers.Configuration1:
329                     case Registers.Configuration2:
330                     case Registers.CRCPolynomial:
331                     case Registers.UnderrunData:
332                         this.Log(LogLevel.Error, "Attempted to write 0x{0:X} to {0} register while peripheral is enabled", value, reg);
333                         return false;
334                 }
335             }
336 
337             return true;
338         }
339 
TryTransmitData()340         private void TryTransmitData()
341         {
342             if(!peripheralEnabled.Value || !startTransmission.Value || transmitFifo.Count == 0)
343             {
344                 return;
345             }
346 
347             // This many bytes are needed to hold all of the packet bits (using ceiling division)
348             // The value of the register is one less that the amount of required bits
349             var byteCount = (int)packetSizeBits.Value / 8 + 1;
350             var bytes = new byte[MaxPacketBytes];
351             var reverseBytes = BitConverter.IsLittleEndian && !leastSignificantByteFirst.Value;
352 
353             while(transmitFifo.Count != 0)
354             {
355                 var value = transmitFifo.Dequeue();
356                 BitHelper.GetBytesFromValue(bytes, 0, value, byteCount, reverseBytes);
357 
358                 for(var i = 0; i < byteCount; i++)
359                 {
360                     bytes[i] = RegisteredPeripheral?.Transmit(bytes[i]) ?? 0;
361                 }
362 
363                 receiveFifo.Enqueue(BitHelper.ToUInt32(bytes, 0, byteCount, reverseBytes));
364 
365                 if(receiveDMAEnabled.Value)
366                 {
367                     // This blink is used to signal the DMA that it should perform the peripheral -> memory transaction now
368                     // Without this signal DMA will never move data from the receive FIFO to memory
369                     // See STM32DMA:OnGPIO
370                     DMARecieve.Blink();
371                 }
372                 transmittedPackets++;
373             }
374 
375             if(transmissionSize.Value == 0)
376             {
377                 // [Transaction handling, p. 1621](https://www.st.com/resource/en/reference_manual/rm0493-multiprotocol-wireless-bluetooth-lowenergy-and-ieee802154-stm32wba5xxx-armbased-32bit-mcus-stmicroelectronics.pdf)
378                 // SPI operates in endless transaction mode, which means that the SPI peripheral is not able to detect when a transaction completes.
379                 // CSTART flag is not reset automatically and we don't call FinishTransmission().
380                 // To reset CSTART and de-assert CS pin, CSUSP (master suspend request) flag should be set.
381             }
382             else if(transmittedPackets == transmissionSize.Value)
383             {
384                 EndTransfer();
385             }
386 
387             UpdateInterrupts();
388         }
389 
EndTransfer()390         private void EndTransfer()
391         {
392             RegisteredPeripheral?.FinishTransmission();
393             endOfTransfer.Value = true;
394             startTransmission.Value = false;
395         }
396 
UpdateInterrupts()397         private void UpdateInterrupts()
398         {
399             var rxp = receiveFifo.Count > 0 && receiveFifoThresholdInterruptEnable.Value;
400             var eot = (endOfTransfer.Value || suspensionStatus.Value) && endOfTransferInterruptEnable.Value;
401 
402             var irqValue = transmitFifoThresholdInterruptEnable.Value || rxp || eot;
403             this.Log(LogLevel.Debug, "Setting IRQ to {0}", irqValue);
404             IRQ.Set(irqValue);
405         }
406 
ResetTransmissionState()407         private void ResetTransmissionState()
408         {
409             endOfTransfer.Value = false;
410             startTransmission.Value = false;
411             transmittedPackets = 0;
412             UpdateInterrupts();
413         }
414 
415         private readonly DoubleWordRegisterCollection registers;
416         private readonly Queue<uint> transmitFifo;
417         private readonly Queue<uint> receiveFifo;
418 
419         private bool iolockValue;
420         private IFlagRegisterField receiveFifoThresholdInterruptEnable;
421         private IFlagRegisterField transmitFifoThresholdInterruptEnable;
422         private IFlagRegisterField endOfTransferInterruptEnable;
423         private IFlagRegisterField endOfTransfer;
424         private IFlagRegisterField peripheralEnabled;
425         private IFlagRegisterField receiveDMAEnabled;
426         private IFlagRegisterField startTransmission;
427         private IFlagRegisterField leastSignificantByteFirst;
428 
429         private IValueRegisterField transmissionSize;
430         private IValueRegisterField packetSizeBits;
431         private IFlagRegisterField suspensionStatus;
432 
433         private ulong transmittedPackets;
434 
435         private const int MaxPacketBytes = 4;
436 
437         private enum Registers
438         {
439             Control1 = 0x00,                    // SPI_CR1
440             Control2 = 0x04,                    // SPI_CR2
441             Configuration1 = 0x08,              // SPI_CFG1
442             Configuration2 = 0x0C,              // SPI_CFG2
443             InterruptEnable = 0x10,             // SPI_IER
444             Status = 0x14,                      // SPI_SR
445             InterruptStatusFlagsClear = 0x18,   // SPI_IFCR
446             AutonomousModeControl = 0x1C,       // SPI_AUTOCR (WBA only)
447             TransmitData = 0x20,                // SPI_TXDR
448             ReceiveData = 0x30,                 // SPI_RXDR
449             CRCPolynomial = 0x40,               // SPI_CRCPOLY
450             TransmitterCRC = 0x44,              // SPI_TXCRC
451             ReceiverCRC = 0x48,                 // SPI_RXCRC
452             UnderrunData = 0x4C,                // SPI_UDRDR
453             I2SConfiguration = 0x50,            // SPI_I2SCFGR (non-WBA only)
454         }
455     }
456 }
457