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;
12 using Antmicro.Renode.Core.Structure.Registers;
13 using Antmicro.Renode.Logging;
14 using Antmicro.Renode.Peripherals.Bus;
15 using Antmicro.Renode.Exceptions;
16 using Antmicro.Renode.Utilities;
17 
18 namespace Antmicro.Renode.Peripherals.SPI
19 {
20     public class MAX32650_SPI : SimpleContainer<ISPIPeripheral>, IDoubleWordPeripheral, IWordPeripheral, IBytePeripheral, IKnownSize
21     {
MAX32650_SPI(IMachine machine, int numberOfSlaves, bool hushTxFifoLevelWarnings = false)22         public MAX32650_SPI(IMachine machine, int numberOfSlaves, bool hushTxFifoLevelWarnings = false) : base(machine)
23         {
24             if(numberOfSlaves < 0 || numberOfSlaves > MaximumNumberOfSlaves)
25             {
26                 throw new ConstructionException($"numberOfSlaves should be between 0 and {MaximumNumberOfSlaves - 1}");
27             }
28 
29             IRQ = new GPIO();
30             NumberOfSlaves = numberOfSlaves;
31 
32             registers = new DoubleWordRegisterCollection(this, BuildRegisterMap());
33             rxQueue = new Queue<byte>();
34             txQueue = new Queue<byte>();
35             shouldDeassert = new bool[numberOfSlaves];
36 
37             this.hushTxFifoLevelWarnings = hushTxFifoLevelWarnings;
38         }
39 
Reset()40         public override void Reset()
41         {
42             IRQ.Unset();
43             registers.Reset();
44 
45             rxQueue.Clear();
46             txQueue.Clear();
47 
48             charactersToTransmit = 0;
49             transactionInProgress = false;
50 
51             for(var i = 0; i < NumberOfSlaves; ++i)
52             {
53                 shouldDeassert[i] = false;
54             }
55         }
56 
ReadByte(long address)57         public byte ReadByte(long address)
58         {
59             if(address >= (long)Registers.FIFOData + FIFODataWidth)
60             {
61                 this.Log(LogLevel.Warning, "Tried to perform byte read from different register than FIFO; ignoring");
62                 return 0x00;
63             }
64             return RxDequeue();
65         }
66 
WriteByte(long address, byte value)67         public void WriteByte(long address, byte value)
68         {
69             if(address >= (long)Registers.FIFOData + FIFODataWidth)
70             {
71                 this.Log(LogLevel.Warning, "Tried to perform byte write to different register than FIFO; ignoring");
72                 return;
73             }
74             TxEnqueue(value);
75         }
76 
ReadWord(long address)77         public ushort ReadWord(long address)
78         {
79             if(address >= (long)Registers.FIFOData + FIFODataWidth)
80             {
81                 this.Log(LogLevel.Warning, "Tried to perform word read from different register than FIFO; ignoring");
82                 return 0x00;
83             }
84 
85             var value1 = RxDequeue();
86             var value2 = (ushort)RxDequeue() << 8;
87             return (ushort)(value1 | value2);
88         }
89 
WriteWord(long address, ushort value)90         public void WriteWord(long address, ushort value)
91         {
92             if(address >= (long)Registers.FIFOData + FIFODataWidth)
93             {
94                 this.Log(LogLevel.Warning, "Tried to perform word write to different register than FIFO; ignoring");
95                 return;
96             }
97             TxEnqueue((byte)value);
98             TxEnqueue((byte)(value >> 8));
99         }
100 
ReadDoubleWord(long address)101         public uint ReadDoubleWord(long address)
102         {
103             return registers.Read(address);
104         }
105 
WriteDoubleWord(long address, uint value)106         public void WriteDoubleWord(long address, uint value)
107         {
108             registers.Write(address, value);
109         }
110 
111         public long Size => 0x1000;
112 
113         public GPIO IRQ { get; }
114 
115         public int NumberOfSlaves { get; }
116 
UpdateInterrupts()117         private void UpdateInterrupts()
118         {
119             interruptRxLevelPending.Value = rxQueue.Count >= (int)rxFIFOThreshold.Value;
120             interruptTxLevelPending.Value = txQueue.Count >= (int)txFIFOThreshold.Value;
121 
122             var pending = false;
123             pending |= interruptTxLevelEnabled.Value && interruptTxLevelPending.Value;
124             pending |= interruptTxEmptyEnabled.Value && interruptTxEmptyPending.Value;
125             pending |= interruptRxLevelEnabled.Value && interruptRxLevelPending.Value;
126             pending |= interruptRxFullEnabled.Value && interruptRxFullPending.Value;
127             pending |= interruptTransactionFinishedEnabled.Value && interruptTransactionFinishedPending.Value;
128             pending |= interruptRxOverrunEnabled.Value && interruptRxOverrunPending.Value;
129             pending |= interruptRxUnderrunEnabled.Value && interruptRxUnderrunPending.Value;
130             IRQ.Set(pending);
131         }
132 
DeassertCS(Func<int, bool> predicate)133         private void DeassertCS(Func<int, bool> predicate)
134         {
135             foreach(var indexPeripheral in ActivePeripherals)
136             {
137                 var index = indexPeripheral.Item1;
138                 var peripheral = indexPeripheral.Item2;
139 
140                 if(predicate(index))
141                 {
142                     peripheral.FinishTransmission();
143                     shouldDeassert[index] = false;
144                 }
145             }
146         }
147 
StartTransaction()148         private void StartTransaction()
149         {
150             // deassert CS of active peripherals that are not enabled in the slave select register anymore
151             DeassertCS(x => !BitHelper.IsBitSet((uint)slaveSelect.Value, (byte)x));
152 
153             foreach(var value in txQueue)
154             {
155                 Transmit(value);
156             }
157 
158             txQueue.Clear();
159 
160             transactionInProgress = true;
161             interruptTxEmptyPending.Value = true;
162 
163             UpdateInterrupts();
164             TryFinishTransaction();
165         }
166 
TryFinishTransaction()167         private void TryFinishTransaction()
168         {
169             if(charactersToTransmit > 0)
170             {
171                 return;
172             }
173 
174             transactionInProgress = false;
175             interruptTransactionFinishedPending.Value = true;
176 
177             // deassert CS of active peripherals marked in the should deassert array
178             DeassertCS(x => shouldDeassert[x]);
179             UpdateInterrupts();
180         }
181 
Transmit(byte value)182         private void Transmit(byte value)
183         {
184             var numberOfPeripherals = ActivePeripherals.Count();
185             foreach(var indexPeripheral in ActivePeripherals)
186             {
187                 var peripheral = indexPeripheral.Item2;
188                 var output = peripheral.Transmit(value);
189                 // In case multiple SS lines are chosen, we are deliberately
190                 // ignoring output from all of them. Therefore, this configuration
191                 // can only be used to send data to multiple receivers at once.
192                 if(numberOfPeripherals == 1)
193                 {
194                     RxEnqueue(output);
195                 }
196             }
197 
198             if(numberOfPeripherals == 0)
199             {
200                 // If there is no target device we still need to populate the RX queue
201                 // with dummy bytes
202                 RxEnqueue(DummyResponseByte);
203             }
204 
205             charactersToTransmit -= 1;
206             TryFinishTransaction();
207         }
208 
TryTransmit()209         private void TryTransmit()
210         {
211             if(!transactionInProgress || rxQueue.Count == FIFOLength || txQueue.Count == 0)
212             {
213                 return;
214             }
215 
216             var bytesToTransmit = Math.Min(FIFOLength - rxQueue.Count, txQueue.Count);
217             for(var i = 0; i < bytesToTransmit; ++i)
218             {
219                 Transmit(txQueue.Dequeue());
220             }
221         }
222 
RxEnqueue(byte value)223         private void RxEnqueue(byte value)
224         {
225             if(!rxFIFOEnabled.Value)
226             {
227                 return;
228             }
229 
230             if(rxQueue.Count == FIFOLength)
231             {
232                 interruptRxOverrunPending.Value = true;
233                 UpdateInterrupts();
234                 return;
235             }
236             rxQueue.Enqueue(value);
237             if(rxQueue.Count == FIFOLength)
238             {
239                 interruptRxFullPending.Value = true;
240                 UpdateInterrupts();
241             }
242         }
243 
RxDequeue()244         private byte RxDequeue()
245         {
246             if(!rxFIFOEnabled.Value)
247             {
248                 this.Log(LogLevel.Warning, "Tried to read from RX FIFO while it's disabled");
249                 return 0x00;
250             }
251 
252             if(!rxQueue.TryDequeue(out var result))
253             {
254                 interruptRxUnderrunPending.Value |= true;
255             }
256             else
257             {
258                 TryTransmit();
259             }
260 
261             TryFinishTransaction();
262             UpdateInterrupts();
263 
264             return result;
265         }
266 
TxEnqueue(byte value)267         private void TxEnqueue(byte value)
268         {
269             if(transactionInProgress && rxQueue.Count < FIFOLength)
270             {
271                 // If we have active transaction and we have room to receive data,
272                 // send/receive it immediately
273                 Transmit(value);
274             }
275             else
276             {
277                 // Otherwise, we either generate TX overrun interrupt if internal
278                 // TX buffer is full, or enqueue new data to it. This data will be
279                 // send either after START condition, or when there is room in RX
280                 // buffer when transaction is active
281                 if(txQueue.Count == FIFOLength)
282                 {
283                     interruptTxOverrunPending.Value = true;
284                 }
285                 else
286                 {
287                     txQueue.Enqueue(value);
288                 }
289             }
290             UpdateInterrupts();
291         }
292 
BuildRegisterMap()293         private Dictionary<long, DoubleWordRegister> BuildRegisterMap()
294         {
295             var registerMap = new Dictionary<long, DoubleWordRegister>()
296             {
297                 {(long)Registers.FIFOData, new DoubleWordRegister(this)
298                     .WithValueFields(0, 8, FIFODataWidth, name: "DATA.data",
299                         valueProviderCallback: (_, __) => RxDequeue(),
300                         writeCallback: (_, __, value) => TxEnqueue((byte)value))
301                 },
302                 {(long)Registers.MasterSignalsControl, new DoubleWordRegister(this)
303                     .WithFlag(0, out var spiEnabled, name: "CTRL0.spi_en",
304                         // deassert all CS lines when disabling the controller
305                         writeCallback: (_, value) => { if(!value) DeassertCS(x => true); })
306                     .WithFlag(1, name: "CTRL0.mm_en",
307                         changeCallback: (_, value) =>
308                         {
309                             if(!value && spiEnabled.Value)
310                             {
311                                 this.Log(LogLevel.Warning, "CTRL0.mm_en has been unset, but only Master mode is supported");
312                             }
313                         })
314                     .WithReservedBits(2, 2)
315                     .WithTaggedFlag("CTRL0.ss_io", 4)
316                     .WithFlag(5, FieldMode.WriteOneToClear, name: "CTRL0.start",
317                         writeCallback: (_, value) => { if(value) StartTransaction(); })
318                     .WithReservedBits(6, 2)
319                     .WithFlag(8, name: "CTRL0.ss_ctrl",
320                         changeCallback: (_, value) =>
321                         {
322                             foreach(var indexPeripheral in ActivePeripherals)
323                             {
324                                 shouldDeassert[indexPeripheral.Item1] |= !value;
325                             }
326                         })
327                     .WithReservedBits(9, 7)
328                     .WithValueField(16, 4, out slaveSelect, name: "CTRL0.ss_sel",
329                         changeCallback: (_, value) =>
330                         {
331                             for(var i = 0; i < NumberOfSlaves; ++i)
332                             {
333                                 if(BitHelper.IsBitSet(value, (byte)i) && !TryGetByAddress(i, out var __))
334                                 {
335                                     this.Log(LogLevel.Warning, "Tried to select SS{0}, but it's not connected to anything; ignoring", i);
336                                     BitHelper.SetBit(ref value, (byte)i, false);
337                                 }
338                             }
339                             slaveSelect.Value = value;
340                         })
341                     .WithReservedBits(20, 12)
342                 },
343                 {(long)Registers.TrasmitPacketSize, new DoubleWordRegister(this)
344                     .WithValueField(0, 16, name: "CTRL1.tx_num_char",
345                         writeCallback: (_, value) => charactersToTransmit = (uint)value)
346                     .WithTag("CTRL1.rx_num_char", 16, 16)
347                 },
348                 {(long)Registers.StaticConfiguration, new DoubleWordRegister(this)
349                     .WithTaggedFlag("CTRL2.clk_pha", 0)
350                     .WithTaggedFlag("CTRL2.clk_pol", 1)
351                     .WithReservedBits(2, 6)
352                     .WithValueField(8, 4, name: "CTRL2.num_bits",
353                         writeCallback: (_, value) =>
354                         {
355                             if(value >= 1 && value != 8)
356                             {
357                                 this.Log(LogLevel.Warning, "Only 8-bit characters are supported, but tried to change to {0}-bit characters; ignored", value);
358                             }
359                         })
360                     .WithTag("CTRL2.bus_width", 12, 2)
361                     .WithReservedBits(14, 1)
362                     .WithTaggedFlag("CTRL2.three_wire", 15)
363                     .WithTag("CTRL2.ss_pol", 16, 4)
364                     .WithReservedBits(20, 12)
365                 },
366                 {(long)Registers.InterruptStatusFlags, new DoubleWordRegister(this)
367                     .WithFlag(0, out interruptTxLevelPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INT_FL.tx_level")
368                     .WithFlag(1, out interruptTxEmptyPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INT_FL.tx_empty")
369                     .WithFlag(2, out interruptRxLevelPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INT_FL.rx_level")
370                     .WithFlag(3, out interruptRxFullPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INT_FL.rx_full")
371                     .WithTaggedFlag("INT_FL.ssa", 4)
372                     .WithTaggedFlag("INT_FL.ssd", 5)
373                     .WithReservedBits(6, 2)
374                     .WithTaggedFlag("INT_FL.fault", 8)
375                     .WithTaggedFlag("INT_FL.abort", 9)
376                     .WithReservedBits(10, 1)
377                     .WithFlag(11, out interruptTransactionFinishedPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INT_FL.m_done")
378                     .WithFlag(12, out interruptTxOverrunPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INT_FL.tx_ovr")
379                     .WithTaggedFlag("INT_FL.tx_und", 13)
380                     .WithFlag(14, out interruptRxOverrunPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INT_FL.rx_ovr")
381                     .WithFlag(15, out interruptRxUnderrunPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "INT_EN.rx_und")
382                     .WithReservedBits(16, 16)
383                     .WithChangeCallback((_, __) => UpdateInterrupts())
384                 },
385                 {(long)Registers.InterruptEnable, new DoubleWordRegister(this)
386                     .WithFlag(0, out interruptTxLevelEnabled, name: "INT_EN.tx_level")
387                     .WithFlag(1, out interruptTxEmptyEnabled, name: "INT_EN.tx_empty")
388                     .WithFlag(2, out interruptRxLevelEnabled, name: "INT_EN.rx_level")
389                     .WithFlag(3, out interruptRxFullEnabled, name: "INT_EN.rx_full")
390                     .WithTaggedFlag("INT_EN.ssa", 4)
391                     .WithTaggedFlag("INT_EN.ssd", 5)
392                     .WithReservedBits(6, 2)
393                     .WithTaggedFlag("INT_EN.fault", 8)
394                     .WithTaggedFlag("INT_EN.abort", 9)
395                     .WithReservedBits(10, 1)
396                     .WithFlag(11, out interruptTransactionFinishedEnabled, name: "INT_EN.m_done")
397                     .WithFlag(12, out interruptTxOverrunEnabled, name: "INT_EN.tx_ovr")
398                     .WithTaggedFlag("INT_EN.tx_und", 13)
399                     .WithFlag(14, out interruptRxOverrunEnabled, name: "INT_EN.rx_ovr")
400                     .WithFlag(15, out interruptRxUnderrunEnabled, name: "INT_EN.rx_und")
401                     .WithReservedBits(16, 16)
402                     .WithChangeCallback((_, __) => UpdateInterrupts())
403                 },
404                 {(long)Registers.ActiveStatus, new DoubleWordRegister(this)
405                     .WithTaggedFlag("STAT.busy", 0)
406                     .WithReservedBits(1, 31)
407                 }
408             };
409 
410             {
411                 var constructedRegister = new DoubleWordRegister(this)
412                     .WithValueField(0, 5, out txFIFOThreshold, name: "DMA.tx_fifo_level")
413                     // NOTE: 5th bit covered in if statement
414                     .WithFlag(6, out txFIFOEnabled, name: "DMA.tx_fifo_en")
415                     .WithFlag(7, FieldMode.WriteOneToClear, name: "DMA.tx_fifo_clear",
416                         writeCallback: (_, value) => { if(value) txQueue.Clear(); })
417                     .WithValueField(8, 6, FieldMode.Read, name: "DMA.tx_fifo_cnt",
418                         valueProviderCallback: _ => (uint)txQueue.Count)
419                     .WithReservedBits(14, 1)
420                     .WithTaggedFlag("DMA.tx_dma_en", 15)
421                     .WithValueField(16, 5, out rxFIFOThreshold, name: "DMA.rx_fifo_level")
422                     .WithReservedBits(21, 1)
423                     .WithFlag(22, out rxFIFOEnabled, name: "DMA.rx_fifo_en")
424                     .WithFlag(23, FieldMode.WriteOneToClear, name: "DMA.rx_fifo_clear",
425                         writeCallback: (_, value) => { if(value) rxQueue.Clear(); })
426                     .WithValueField(24, 6, FieldMode.Read, name: "DMA.rx_fifo_cnt",
427                         valueProviderCallback: _ => (uint)rxQueue.Count)
428                     .WithReservedBits(30, 1)
429                     .WithTag("DMA.rx_dma_en", 31, 1)
430                     .WithChangeCallback((_, __) => UpdateInterrupts())
431                 ;
432                 // Depending on the peripheral constructor argument, treat writes to reserved field as error or don't.
433                 if(hushTxFifoLevelWarnings)
434                 {
435                     constructedRegister.WithFlag(5, name: "RESERVED");
436                 }
437                 else
438                 {
439                     constructedRegister.WithReservedBits(5, 1);
440                 }
441                 registerMap.Add((long)Registers.DMAControl, constructedRegister);
442             }
443 
444             return registerMap;
445         }
446 
447         private IEnumerable<Tuple<int, ISPIPeripheral>> ActivePeripherals
448         {
449             get
450             {
451                 return Enumerable
452                     .Range(0, NumberOfSlaves)
453                     .Select(index =>
454                     {
455                         if(!BitHelper.IsBitSet(slaveSelect.Value, (byte)index))
456                         {
457                             return null;
458                         }
459                         if(!TryGetByAddress(index, out var peripheral))
460                         {
461                             return null;
462                         }
463                         return Tuple.Create(index, peripheral);
464                     })
465                     .Where(tuple => tuple != null);
466             }
467         }
468 
469         private bool[] shouldDeassert;
470         private bool transactionInProgress;
471         private bool hushTxFifoLevelWarnings;
472         private uint charactersToTransmit;
473 
474         private IValueRegisterField slaveSelect;
475 
476         private IFlagRegisterField rxFIFOEnabled;
477         private IFlagRegisterField txFIFOEnabled;
478 
479         private IValueRegisterField rxFIFOThreshold;
480         private IValueRegisterField txFIFOThreshold;
481 
482         private IFlagRegisterField interruptTxLevelPending;
483         private IFlagRegisterField interruptTxEmptyPending;
484         private IFlagRegisterField interruptRxLevelPending;
485         private IFlagRegisterField interruptRxFullPending;
486         private IFlagRegisterField interruptTransactionFinishedPending;
487         private IFlagRegisterField interruptTxOverrunPending;
488         private IFlagRegisterField interruptRxOverrunPending;
489         private IFlagRegisterField interruptRxUnderrunPending;
490 
491         private IFlagRegisterField interruptTxLevelEnabled;
492         private IFlagRegisterField interruptTxEmptyEnabled;
493         private IFlagRegisterField interruptRxLevelEnabled;
494         private IFlagRegisterField interruptRxFullEnabled;
495         private IFlagRegisterField interruptTransactionFinishedEnabled;
496         private IFlagRegisterField interruptTxOverrunEnabled;
497         private IFlagRegisterField interruptRxOverrunEnabled;
498         private IFlagRegisterField interruptRxUnderrunEnabled;
499 
500         private const int FIFODataWidth = 0x04;
501         private const int FIFOLength = 32;
502         private const int MaximumNumberOfSlaves = 4;
503 
504         private readonly Queue<byte> rxQueue;
505         private readonly Queue<byte> txQueue;
506         private readonly DoubleWordRegisterCollection registers;
507 
508         private const byte DummyResponseByte = 0xFF;
509 
510         private enum Registers : long
511         {
512             FIFOData = 0x00,
513             MasterSignalsControl = 0x04,
514             TrasmitPacketSize = 0x08,
515             StaticConfiguration = 0x0C,
516             SlaveSelectTiming = 0x10,
517             MasterClockConfiguration = 0x14,
518             DMAControl = 0x1C,
519             InterruptStatusFlags = 0x20,
520             InterruptEnable = 0x24,
521             WakeupStatusFlags = 0x28,
522             WakeupEnable = 0x2C,
523             ActiveStatus = 0x30,
524         }
525     }
526 }
527