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