1 //
2 // Copyright (c) 2010-2024 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using Antmicro.Renode.Peripherals.Bus;
9 using Antmicro.Renode.Logging;
10 using Antmicro.Renode.Core.Structure;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Core.Structure.Registers;
13 using Antmicro.Renode.Utilities.Collections;
14 
15 namespace Antmicro.Renode.Peripherals.SPI
16 {
17     public sealed class STM32SPI : NullRegistrationPointPeripheralContainer<ISPIPeripheral>, IWordPeripheral, IDoubleWordPeripheral, IBytePeripheral, IKnownSize
18     {
STM32SPI(IMachine machine, int bufferCapacity = DefaultBufferCapacity)19         public STM32SPI(IMachine machine, int bufferCapacity = DefaultBufferCapacity) : base(machine)
20         {
21             receiveBuffer = new CircularBuffer<byte>(bufferCapacity);
22             IRQ = new GPIO();
23             DMARecieve = new GPIO();
24             registers = new DoubleWordRegisterCollection(this);
25             SetupRegisters();
26             Reset();
27         }
28 
29         // We can't use AllowedTranslations because then WriteByte/WriteWord will trigger
30         // an additional read (see ReadWriteExtensions:WriteByteUsingDoubleWord).
31         // We can't have this happen for the data register.
ReadByte(long offset)32         public byte ReadByte(long offset)
33         {
34             // byte interface is there for DMA
35             if(offset % 4 == 0)
36             {
37                 return (byte)ReadDoubleWord(offset);
38             }
39             this.LogUnhandledRead(offset);
40             return 0;
41         }
42 
WriteByte(long offset, byte value)43         public void WriteByte(long offset, byte value)
44         {
45             if(offset % 4 == 0)
46             {
47                 WriteDoubleWord(offset, (uint)value);
48             }
49             else
50             {
51                 this.LogUnhandledWrite(offset, value);
52             }
53         }
54 
ReadWord(long offset)55         public ushort ReadWord(long offset)
56         {
57             return (ushort)ReadDoubleWord(offset);
58         }
59 
WriteWord(long offset, ushort value)60         public void WriteWord(long offset, ushort value)
61         {
62             WriteDoubleWord(offset, (uint)value);
63         }
64 
ReadDoubleWord(long offset)65         public uint ReadDoubleWord(long offset)
66         {
67             return registers.Read(offset);
68         }
69 
WriteDoubleWord(long offset, uint value)70         public void WriteDoubleWord(long offset, uint value)
71         {
72             registers.Write(offset, value);
73         }
74 
Reset()75         public override void Reset()
76         {
77             IRQ.Unset();
78             DMARecieve.Unset();
79             lock(receiveBuffer)
80             {
81                 receiveBuffer.Clear();
82             }
83             registers.Reset();
84         }
85 
86         public long Size
87         {
88             get
89             {
90                 return 0x400;
91             }
92         }
93 
94         public GPIO IRQ { get; }
95 
96         public GPIO DMARecieve { get; }
97 
HandleDataRead()98         private uint HandleDataRead()
99         {
100             IRQ.Unset();
101             lock(receiveBuffer)
102             {
103                 if(receiveBuffer.TryDequeue(out var value))
104                 {
105                     Update();
106                     return value;
107                 }
108                 // We don't warn when the data register is read while it's empty because the HAL
109                 // (for example L0, F4) does this intentionally.
110                 // See https://github.com/STMicroelectronics/STM32CubeL0/blob/bec4e499a74de98ab60784bf2ef1912bee9c1a22/Drivers/STM32L0xx_HAL_Driver/Src/stm32l0xx_hal_spi.c#L1368-L1372
111                 return 0;
112             }
113         }
114 
HandleDataWrite(uint value)115         private void HandleDataWrite(uint value)
116         {
117             IRQ.Unset();
118             lock(receiveBuffer)
119             {
120                 var peripheral = RegisteredPeripheral;
121                 if(peripheral == null)
122                 {
123                     this.Log(LogLevel.Warning, "SPI transmission while no SPI peripheral is connected.");
124                     receiveBuffer.Enqueue(0x0);
125                     return;
126                 }
127                 var response = peripheral.Transmit((byte)value); // currently byte mode is the only one we support
128                 receiveBuffer.Enqueue(response);
129                 if(rxDmaEnable.Value)
130                 {
131                     // This blink is used to signal the DMA that it should perform the peripheral -> memory transaction now
132                     // Without this signal DMA will never move data from the receive buffer to memory
133                     // See STM32DMA:OnGPIO
134                     DMARecieve.Blink();
135                 }
136                 this.NoisyLog("Transmitted 0x{0:X}, received 0x{1:X}.", value, response);
137             }
138             Update();
139         }
140 
Update()141         private void Update()
142         {
143             var rxBufferNotEmpty = receiveBuffer.Count != 0;
144             var rxBufferNotEmptyInterruptFlag = rxBufferNotEmpty && rxBufferNotEmptyInterruptEnable.Value;
145 
146             IRQ.Set(txBufferEmptyInterruptEnable.Value || rxBufferNotEmptyInterruptFlag);
147         }
148 
SetupRegisters()149         private void SetupRegisters()
150         {
151             // Some fields relate to the physical layer of the SPI protocol, these are not
152             // taken into account in Renode and are defined as flags or value fields without
153             // a corresponding RegisterField or read/write callbacks. They are marked with
154             // comments.
155             Registers.Control1.Define(registers)
156                 .WithFlag(0, name: "CPHA") // Physical
157                 .WithFlag(1, name: "CPOL") // Physical
158                 .WithFlag(2, writeCallback: (_, value) =>
159                 {
160                     if(!value)
161                     {
162                         this.Log(LogLevel.Warning, "Slave mode is not supported");
163                     }
164                 }, name: "MSTR")
165                 .WithValueField(3, 3, name: "Baud") // Physical
166                 .WithFlag(6, changeCallback: (oldValue, newValue) =>
167                 {
168                     if(!newValue)
169                     {
170                         IRQ.Unset();
171                     }
172                 }, name: "SpiEnable")
173                 .WithFlag(7, name: "LSBFIRST") // Physical
174                 // We keep these as flags to preserve written values. SSI flag is used by drivers to select/detect operation mode (Master or Slave)
175                 .WithFlag(8, name: "SSI") // Internal slave select
176                 .WithFlag(9, name: "SSM") // Software slave management
177                 .WithTaggedFlag("RXONLY", 10)
178                 .WithTaggedFlag("DFF", 11)
179                 .WithTaggedFlag("CRCNEXT", 12)
180                 .WithTaggedFlag("CRCEN", 13)
181                 .WithTaggedFlag("BIDIOE", 14)
182                 .WithTaggedFlag("BIDIMODE", 15);
183 
184             Registers.Control2.Define(registers)
185                 .WithFlag(0, out rxDmaEnable, name: "RXDMAEN")
186                 .WithTaggedFlag("TXDMAEN", 1)
187                 .WithTaggedFlag("SSOE", 2)
188                 .WithReservedBits(3, 1)
189                 .WithTaggedFlag("FRF", 4)
190                 .WithTaggedFlag("ERRIE", 5)
191                 .WithFlag(6, out rxBufferNotEmptyInterruptEnable, name: "RXNEIE")
192                 .WithFlag(7, out txBufferEmptyInterruptEnable, name: "TXEIE")
193                 .WithReservedBits(8, 24)
194                 .WithWriteCallback((_, __) => Update());
195 
196             Registers.Status.Define(registers, 2)
197                 .WithFlag(0, FieldMode.Read, valueProviderCallback: _ => receiveBuffer.Count != 0, name: "RXNE")
198                 .WithFlag(1, FieldMode.Read, valueProviderCallback: _ => true, name: "TXE") // transfers are instant
199                 .WithTaggedFlag("CHSIDE", 2) // r/o
200                 .WithTaggedFlag("UDR", 3) // r/o
201                 .WithTaggedFlag("CRCERR", 4) // rc_w0
202                 .WithTaggedFlag("MODF", 5) // r/o
203                 .WithTaggedFlag("OVR", 6) // r/o
204                 .WithTaggedFlag("BSY", 7) // r/o
205                 .WithTaggedFlag("FRE", 8) // r/o
206                 .WithReservedBits(9, 23);
207 
208             Registers.Data.Define(registers)
209                 .WithValueField(0, 16, valueProviderCallback: _ => HandleDataRead(),
210                     writeCallback: (_, value) => HandleDataWrite((uint)value), name: "DR")
211                 .WithReservedBits(16, 16);
212 
213             Registers.CRCPolynomial.Define(registers, 7)
214                 .WithTag("CRCPOLY", 0, 16)
215                 .WithReservedBits(16, 16);
216 
217             Registers.ReceivedCRC.Define(registers)
218                 .WithTag("RxCRC", 0, 16) // r/o
219                 .WithReservedBits(16, 16);
220 
221             Registers.TransmittedCRC.Define(registers)
222                 .WithTag("TxCRC", 0, 16) // r/o
223                 .WithReservedBits(16, 16);
224 
225             Registers.I2SConfiguration.Define(registers)
226                 .WithTaggedFlag("CHLEN", 0)
227                 .WithTag("DATLEN", 1, 2)
228                 .WithTaggedFlag("CKPOL", 3)
229                 .WithTag("I2SSTD", 4, 2)
230                 .WithReservedBits(6, 1)
231                 .WithTaggedFlag("PCMSYNC", 7)
232                 .WithTag("I2SCFG", 8, 2)
233                 .WithFlag(10, FieldMode.Read | FieldMode.WriteOneToClear, writeCallback: (oldValue, newValue) =>
234                 {
235                     // write one to clear to keep this bit 0
236                     if(newValue)
237                     {
238                         this.Log(LogLevel.Warning, "Trying to enable not supported I2S mode.");
239                     }
240                 }, name: "I2SE")
241                 .WithTaggedFlag("I2SMOD", 11)
242                 .WithReservedBits(12, 20);
243 
244             Registers.I2SPrescaler.Define(registers, 2)
245                 .WithTag("I2SDIV", 0, 8)
246                 .WithTaggedFlag("ODD", 8)
247                 .WithTaggedFlag("MCKOE", 9)
248                 .WithReservedBits(10, 22);
249         }
250 
251         private DoubleWordRegisterCollection registers;
252         private IFlagRegisterField txBufferEmptyInterruptEnable, rxBufferNotEmptyInterruptEnable, rxDmaEnable;
253 
254         private readonly CircularBuffer<byte> receiveBuffer;
255 
256         private const int DefaultBufferCapacity = 4;
257 
258         private enum Registers
259         {
260             Control1 = 0x0, // SPI_CR1,
261             Control2 = 0x4, // SPI_CR2
262             Status = 0x8, // SPI_SR
263             Data = 0xC, // SPI_DR
264             CRCPolynomial = 0x10, // SPI_CRCPR
265             ReceivedCRC = 0x14, // SPI_RXCRCR
266             TransmittedCRC = 0x18, // SPI_TXCRCR
267             I2SConfiguration = 0x1C, // SPI_I2SCFGR
268             I2SPrescaler = 0x20, // SPI_I2SPR
269         }
270     }
271 }
272