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 Antmicro.Renode.Core;
10 using Antmicro.Renode.Core.Structure;
11 using Antmicro.Renode.Core.Structure.Registers;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Peripherals.Bus;
14 using Antmicro.Renode.Utilities;
15 
16 namespace Antmicro.Renode.Peripherals.SPI
17 {
18     [AllowedTranslations(AllowedTranslation.ByteToDoubleWord)]
19     public class OpenTitan_SpiHost: SimpleContainer<ISPIPeripheral>, IWordPeripheral, IBytePeripheral, IDoubleWordPeripheral, IKnownSize
20     {
OpenTitan_SpiHost(IMachine machine, int numberOfCSLines)21         public OpenTitan_SpiHost(IMachine machine, int numberOfCSLines) : base(machine)
22         {
23             this.numberOfCSLines = numberOfCSLines;
24 
25             DefineRegisters();
26             Error = new GPIO();
27             SpiEvent = new GPIO();
28             FatalAlert = new GPIO();
29 
30             txFifo = new Queue<byte>();
31             rxFifo = new Queue<byte>();
32             cmdFifo = new Queue<CommandDefinition>();
33 
34             Reset();
35         }
36 
Reset()37         public override void Reset()
38         {
39             txFifo.Clear();
40             rxFifo.Clear();
41             cmdFifo.Clear();
42             selectedSlave = null;
43             readyFlag.Value = true;
44         }
45 
ReadDoubleWord(long addr)46         public uint ReadDoubleWord(long addr)
47         {
48             return RegistersCollection.Read(addr);
49         }
50 
WriteDoubleWord(long addr, uint val)51         public void WriteDoubleWord(long addr, uint val)
52         {
53             if(addr == (long)Registers.Txdata)
54             {
55                 EnqueueTx(val, sizeof(uint));
56                 return;
57             }
58             RegistersCollection.Write(addr, val);
59         }
60 
61         // Below methods are meeded just to make sure we don't enqueue too much tx bytes on write.
ReadWord(long offset)62         public ushort ReadWord(long offset)
63         {
64             return (ushort)RegistersCollection.Read(offset);
65         }
66 
WriteWord(long addr, ushort val)67         public void WriteWord(long addr, ushort val)
68         {
69             if(addr == (long)Registers.Txdata)
70             {
71                 EnqueueTx(val, sizeof(ushort));
72                 return;
73             }
74             RegistersCollection.Write(addr, val);
75         }
76 
WriteByte(long addr, byte val)77         public void WriteByte(long addr, byte val)
78         {
79             if(addr == (long)Registers.Txdata)
80             {
81                 EnqueueTx(val, sizeof(byte));
82                 return;
83             }
84             RegistersCollection.Write(addr, val);
85         }
86 
ReadByte(long offset)87         public byte ReadByte(long offset)
88         {
89             return (byte)RegistersCollection.Read(offset);
90         }
91 
92         public long Size => 0x1000;
93 
94         // Common Interrupt Offsets
95         public GPIO Error { get; }
96         public GPIO SpiEvent { get; }
97 
98         // Alerts
99         public GPIO FatalAlert { get; }
100 
101         public DoubleWordRegisterCollection RegistersCollection { get; private set; }
102 
DefineRegisters()103         private void DefineRegisters()
104         {
105             var registerDictionary = new Dictionary<long, DoubleWordRegister>
106             {{(long)Registers.InterruptState, new DoubleWordRegister(this)
107                 .WithFlag(0, out errorInterruptTriggered, FieldMode.Read | FieldMode.WriteOneToClear, name: "error")
108                 .WithFlag(1, out spiEventInterruptTriggered, FieldMode.Read | FieldMode.WriteOneToClear, name: "spi_event")
109                 .WithReservedBits(2, 30)
110                 .WithWriteCallback((_, __) => UpdateInterrupts())
111             },
112             {(long)Registers.InterruptEnable, new DoubleWordRegister(this)
113                 .WithFlag(0, out errorInterruptEnabled, name: "error")
114                 .WithFlag(1, out spiEventInterruptEnabled, name: "spi_event")
115                 .WithReservedBits(2, 30)
116                 .WithWriteCallback((_, __) => UpdateInterrupts())
117             },
118             {(long)Registers.InterruptTest, new DoubleWordRegister(this)
119                 .WithFlag(0, FieldMode.Write, writeCallback: (_, val) => { if(val) errorInterruptTriggered.Value = true; }, name: "error")
120                 .WithFlag(1, FieldMode.Write, writeCallback: (_, val) => { if(val) spiEventInterruptTriggered.Value = true; }, name: "spi_event")
121                 .WithReservedBits(2, 30)
122                 .WithWriteCallback((_, __) => UpdateInterrupts())
123             },
124             {(long)Registers.AlertTest, new DoubleWordRegister(this)
125                 .WithFlag(0, FieldMode.Write, writeCallback: (_, val) => {if(val) FatalAlert.Blink();}, name: "fatal_fault")
126                 .WithReservedBits(1, 31)
127             },
128             {(long)Registers.Control, new DoubleWordRegister(this, 0x7f)
129                 .WithValueField(0, 8, out rxWatermarkInDoublewords, name: "RX_WATERMARK")
130                 .WithValueField(8, 8, out txWatermarkInDoublewords, name: "TX_WATERMARK")
131                 .WithReservedBits(16, 13)
132                 .WithFlag(29, out outputEnabled, name: "OUTPUT_EN")
133                 .WithFlag(30, writeCallback: (_, val) => { if(val) Reset(); }, name: "SW_RST")
134                 .WithFlag(31, out enabled, name: "SPIEN")
135                 .WithChangeCallback((_, __) =>
136                 {
137                     this.DebugLog("Control set to 'enabled': {0}, 'outputEnabled': {1}, rxWatermark = {2}[doublewords], txWatermark = {3}[doublewords]", enabled.Value, outputEnabled.Value, rxWatermarkInDoublewords.Value, txWatermarkInDoublewords.Value);
138                     if(enabled.Value && outputEnabled.Value)
139                     {
140                         ExecuteCommands();
141                     }
142                 })
143             },
144             {(long)Registers.Status, new DoubleWordRegister(this)
145                 .WithValueField(0, 8, FieldMode.Read, valueProviderCallback: _ => txCountInDoubleWords, name: "TXQD")
146                 .WithValueField(8, 8, FieldMode.Read, valueProviderCallback: _ => rxCountInDoubleWords, name: "RXQD")
147                 .WithValueField(16, 4, FieldMode.Read, valueProviderCallback: _ => cmdCountInDoubleWords, name: "CMDQD")
148                 .WithFlag(20, out rxWatermarkEventTriggered, FieldMode.Read, name: "RXWM")
149                 .WithReservedBits(21, 1)
150                 .WithTaggedFlag("BYTEORDER", 22)
151                 .WithTaggedFlag("RXSTALL", 23)
152                 .WithFlag(24, FieldMode.Read, valueProviderCallback: _ => rxFifo.Count == 0, name: "RXEMPTY")
153                 .WithFlag(25, out rxFullEventTriggered, FieldMode.Read, name: "RXFULL")
154                 .WithFlag(26, out txWatermarkEventTriggered, FieldMode.Read, name: "TXWM")
155                 .WithTaggedFlag("TXSTALL", 27)
156                 .WithFlag(28, out txEmptyEventTriggered, FieldMode.Read, name: "TXEMPTY")
157                 .WithFlag(29, FieldMode.Read, valueProviderCallback: _ => txFifo.Count == SpiHostTxDepth, name: "TXFULL")
158                 .WithFlag(30, out active, FieldMode.Read, name: "ACTIVE")
159                 .WithFlag(31, out readyFlag, FieldMode.Read, name: "READY")
160             },
161             {(long)Registers.Configopts, new DoubleWordRegister(this)
162                 .WithTag("CLKDIV_0", 0, 16)
163                 .WithTag("CSNIDLE_0", 16, 4)
164                 .WithTag("CSNTRAIL_0", 20, 4)
165                 .WithTag("CSNLEAD_0", 24, 4)
166                 .WithTaggedFlag("FULLCYC_0", 29)
167                 .WithTaggedFlag("CPHA_0", 30)
168                 .WithTaggedFlag("CPOL_0", 31)
169             },
170             {(long)Registers.ChipSelectID, new DoubleWordRegister(this)
171                 .WithValueField(0, 32, writeCallback: (_, val) =>
172                     {
173                         if((long)val >= numberOfCSLines)
174                         {
175                             this.Log(LogLevel.Warning, "Tried to set CS id out of range: {0}", val);
176                             csIdInvalidErrorTriggered.Value = true;
177                             UpdateEvents();
178                             return;
179                         }
180 
181                         if(!TryGetByAddress((int)val, out selectedSlave))
182                         {
183                             this.Log(LogLevel.Warning, "No device connected with the ID {0}", val);
184                             return;
185                         }
186 
187                         this.DebugLog("The device address set to {0}", val);
188                     }
189                 , name: "CSID")
190             },
191             {(long)Registers.Command, new DoubleWordRegister(this)
192                 .WithValueField(0, 9, out var commandLength, FieldMode.Write, name: "LEN")
193                 .WithFlag(9, out var keepChipSelect, name: "CSAAT")
194                 .WithEnumField<DoubleWordRegister, CommandSpeed>(10, 2, out var commandSpeed, FieldMode.Write, name: "SPEED")
195                 .WithEnumField<DoubleWordRegister, CommandDirection>(12, 2, out var commandDirection, FieldMode.Write, name: "DIRECTION")
196                 .WithReservedBits(14, 18)
197                 .WithWriteCallback((_, __) => EnqueueCommand((uint)commandLength.Value, commandDirection.Value, commandSpeed.Value, keepChipSelect.Value))
198             },
199             {(long)Registers.Rxdata, new DoubleWordRegister(this)
200                 .WithValueField(0, 32, FieldMode.Read, valueProviderCallback: _ => RxDequeueAsUInt(), name: "SPIReceiveData")
201             },
202             {(long)Registers.Txdata, new DoubleWordRegister(this)
203                 // This is already implemented in the WriteDoubleWord/WriteWord/WriteByte
204                 .WithTag("SPITransmitData", 0, 32)
205             },
206             {(long)Registers.ErrorEnable, new DoubleWordRegister(this, 0x1f)
207                 .WithTaggedFlag("CMDBUSY", 0)
208                 .WithFlag(1, out txOverflowErrorEnabled, name: "OVERFLOW")
209                 .WithFlag(2, out rxUnderflowErrorEnabled, name: "UNDERFLOW")
210                 .WithFlag(3, out commandInvalidErrorEnabled, name: "CMDINVAL")
211                 .WithFlag(4, out csIdInvalidErrorEnabled, name: "CSIDINVAL")
212                 .WithReservedBits(5, 27)
213                 .WithWriteCallback((_,__) => UpdateErrors())
214             },
215             {(long)Registers.ErrorStatus, new DoubleWordRegister(this)
216                 .WithFlag(0, FieldMode.Read | FieldMode.WriteOneToClear, name: "CMDBUSY")
217                 .WithFlag(1, out txOverflowErrorTriggered, FieldMode.Read | FieldMode.WriteOneToClear, name: "OVERFLOW")
218                 .WithFlag(2, out rxUnderflowErrorTriggred, FieldMode.Read | FieldMode.WriteOneToClear, name: "UNDERFLOW")
219                 .WithFlag(3, out commandInvalidErrorTriggered, FieldMode.Read | FieldMode.WriteOneToClear, name: "CMDINVAL")
220                 .WithFlag(4, out csIdInvalidErrorTriggered, FieldMode.Read | FieldMode.WriteOneToClear, name: "CSIDINVAL")
221                 .WithTaggedFlag("ACCESSINVAL", 5)
222                 .WithReservedBits(6, 26)
223                 .WithWriteCallback((_,__) => UpdateErrors())
224             },
225             {(long)Registers.EventEnable, new DoubleWordRegister(this)
226                 .WithFlag(0, out rxFullEventEnabled, name: "RXFULL")
227                 .WithFlag(1, out txEmptyEventEnabled, name: "TXEMPTY")
228                 .WithFlag(2, out rxWatermarkEventEnabled, name: "RXWM")
229                 .WithFlag(3, out txWatermarkEventEnabled, name: "TXWM")
230                 .WithFlag(4, out readyEventEnabled, name: "READY")
231                 .WithFlag(5, out idleEventEnabled, name: "IDLE")
232                 .WithReservedBits(6, 26)
233                 .WithWriteCallback((_,__) => UpdateEvents())
234             }};
235             RegistersCollection = new DoubleWordRegisterCollection(this, registerDictionary);
236         }
237 
UpdateInterrupts()238         private void UpdateInterrupts()
239         {
240             Error.Set(errorInterruptEnabled.Value & errorInterruptTriggered.Value);
241             SpiEvent.Set(spiEventInterruptEnabled.Value & spiEventInterruptTriggered.Value);
242         }
243 
UpdateEvents()244         private void UpdateEvents()
245         {
246             spiEventInterruptTriggered.Value =
247                 (rxFullEventTriggered.Value && rxFullEventEnabled.Value) ||
248                 (txEmptyEventTriggered.Value && txEmptyEventEnabled.Value) ||
249                 (txWatermarkEventTriggered.Value && txWatermarkEventEnabled.Value) ||
250                 (rxWatermarkEventTriggered.Value && rxWatermarkEventEnabled.Value) ||
251                 (readyFlag.Value && readyEventEnabled.Value) ||
252                 (!active.Value && idleEventEnabled.Value);
253 
254             UpdateInterrupts();
255         }
256 
UpdateErrors()257         private void UpdateErrors()
258         {
259             // There should be also an CMDBUSY error, but there is no need as our peripheral will never be busy
260             errorInterruptTriggered.Value =
261                 (txOverflowErrorTriggered.Value && txOverflowErrorEnabled.Value) ||
262                 (rxUnderflowErrorTriggred.Value && rxUnderflowErrorEnabled.Value) ||
263                 (commandInvalidErrorTriggered.Value && commandInvalidErrorEnabled.Value) ||
264                 (csIdInvalidErrorTriggered.Value && csIdInvalidErrorEnabled.Value);
265 
266             UpdateInterrupts();
267         }
268 
ExecuteCommands()269         private void ExecuteCommands()
270         {
271             this.NoisyLog("Executing queued commands");
272             while(cmdFifo.TryDequeue(out var x))
273             {
274                 HandleCommand(x);
275             }
276             readyFlag.Value = true;
277             active.Value = false;
278             UpdateEvents();
279         }
280 
EnqueueCommand(uint length, CommandDirection direction, CommandSpeed speed, bool keepChipSelect)281         private void EnqueueCommand(uint length, CommandDirection direction, CommandSpeed speed, bool keepChipSelect)
282         {
283             if((direction == CommandDirection.TxRx && speed != CommandSpeed.Standard) ||
284                 speed == CommandSpeed.Reserved)
285             {
286                 commandInvalidErrorTriggered.Value = true;
287                 UpdateErrors();
288                 return;
289             }
290 
291             cmdFifo.Enqueue(new CommandDefinition(length, direction, keepChipSelect));
292             if(outputEnabled.Value && enabled.Value)
293             {
294                 ExecuteCommands();
295             }
296         }
297 
EnqueueTx(uint val, int accessSize)298         private void EnqueueTx(uint val, int accessSize)
299         {
300             var asBytes = Misc.AsBytes(new uint[] { val });
301             for(int i = 0; i < accessSize; i++)
302             {
303                 if(txFifo.Count < SpiHostTxDepth)
304                 {
305                     txFifo.Enqueue(asBytes[i]);
306                 }
307                 else
308                 {
309                     txOverflowErrorTriggered.Value = true;
310                     break;
311                 }
312             }
313             this.Log(LogLevel.Noisy, "Enqueued {0} Tx bytes, current fifo depth in words: {1}", accessSize, txCountInWords);
314 
315             txWatermarkEventTriggered.Value = (txCountInDoubleWords < txWatermarkInDoublewords.Value);
316             UpdateEvents();
317         }
318 
EnqueueRx(byte val)319         private void EnqueueRx(byte val)
320         {
321             if(rxFifo.Count < SpiHostRxDepth)
322             {
323                 rxFifo.Enqueue(val);
324             }
325             else
326             {
327                 rxFullEventTriggered.Value = true;
328             }
329 
330             rxWatermarkEventTriggered.Value = (rxCountInDoubleWords >= rxWatermarkInDoublewords.Value);
331 
332             UpdateEvents();
333         }
334 
RxDequeueAsUInt()335         private uint RxDequeueAsUInt()
336         {
337             uint output = 0;
338             var count = Math.Min(sizeof(uint), rxFifo.Count);
339             for(int i = 0; i < count; i++)
340             {
341                 output |= (uint)(rxFifo.Dequeue() << (i * 8));
342             }
343             return output;
344         }
345 
HandleCommand(CommandDefinition command)346         private void HandleCommand(CommandDefinition command)
347         {
348             if(command.Direction == CommandDirection.Dummy)
349             {
350                 return;
351             }
352 
353             // number of bytes to transfer is equal to `COMMAND.LEN + 1`
354             for(var i = 0; i <= command.Length; i++)
355             {
356                 var byteToTransfer = (byte)0;
357                 if(command.Direction == CommandDirection.TxOnly || command.Direction == CommandDirection.TxRx)
358                 {
359                     if(!Misc.TryDequeue(txFifo, out byteToTransfer))
360                     {
361                         this.Log(LogLevel.Warning, "Tx Fifo empty, transmitting 0 instead");
362                         byteToTransfer = 0;
363                     }
364                 }
365 
366                 byte response;
367                 if(selectedSlave != null)
368                 {
369                     this.NoisyLog("Transferring byte {0:x}", byteToTransfer);
370                     response = selectedSlave.Transmit(byteToTransfer);
371                     this.NoisyLog("Received byte {0:x}", response);
372                 }
373                 else
374                 {
375                     response = 0xff;
376                     this.NoisyLog("No target device is available, returning dummy byte {0:x}", response);
377                 }
378 
379                 if(command.Direction == CommandDirection.RxOnly || command.Direction == CommandDirection.TxRx)
380                 {
381                     EnqueueRx(response);
382                 }
383             }
384 
385             if(!command.KeepChipSelect)
386             {
387                 this.Log(LogLevel.Debug, "Finished the transmission. Current fifo depth in words: {0}", rxCountInWords);
388                 selectedSlave?.FinishTransmission();
389             }
390         }
391 
392         private IFlagRegisterField errorInterruptEnabled;
393         private IFlagRegisterField spiEventInterruptEnabled;
394         private IFlagRegisterField errorInterruptTriggered;
395         private IFlagRegisterField spiEventInterruptTriggered;
396 
397         private IFlagRegisterField readyFlag;
398         private IFlagRegisterField active;
399         private IFlagRegisterField enabled;
400         private IFlagRegisterField outputEnabled;
401 
402         private IValueRegisterField txWatermarkInDoublewords;
403         private IValueRegisterField rxWatermarkInDoublewords;
404 
405         private ISPIPeripheral selectedSlave;
406 
407         private IFlagRegisterField txEmptyEventTriggered;
408         private IFlagRegisterField rxFullEventTriggered;
409         private IFlagRegisterField txWatermarkEventTriggered;
410         private IFlagRegisterField rxWatermarkEventTriggered;
411         private IFlagRegisterField txOverflowErrorTriggered;
412         private IFlagRegisterField rxUnderflowErrorTriggred;
413         private IFlagRegisterField commandInvalidErrorTriggered;
414         private IFlagRegisterField csIdInvalidErrorTriggered;
415         private IFlagRegisterField rxFullEventEnabled;
416         private IFlagRegisterField txEmptyEventEnabled;
417         private IFlagRegisterField txWatermarkEventEnabled;
418         private IFlagRegisterField rxWatermarkEventEnabled;
419         private IFlagRegisterField readyEventEnabled;
420         private IFlagRegisterField idleEventEnabled;
421         private IFlagRegisterField txOverflowErrorEnabled;
422         private IFlagRegisterField rxUnderflowErrorEnabled;
423         private IFlagRegisterField commandInvalidErrorEnabled;
424         private IFlagRegisterField csIdInvalidErrorEnabled;
425 
426         private readonly Queue<CommandDefinition> cmdFifo;
427         private readonly Queue<byte> txFifo;
428         private readonly Queue<byte> rxFifo;
429         private readonly int numberOfCSLines;
430 
431         // The size of the Tx FIFO (in bytes)
432         private const uint SpiHostTxDepth = 288;
433 
434         // The size of the Rx FIFO (in bytes)
435         private const uint SpiHostRxDepth = 256;
436 
437         // The size of the Cmd FIFO (one segment descriptor per entry)
438         private const uint SpiHostCmdDepth = 4;
439 
440         private uint txCountInWords => (uint)txFifo.Count / 2;
441         private uint rxCountInWords => (uint)rxFifo.Count / 2;
442         private uint cmdCountInWords => (uint)cmdFifo.Count / 2;
443 
444         private uint txCountInDoubleWords => (uint)txFifo.Count / 4;
445         private uint rxCountInDoubleWords => (uint)rxFifo.Count / 4;
446         private uint cmdCountInDoubleWords => (uint)cmdFifo.Count / 4;
447 
448         private enum CommandDirection
449         {
450             Dummy = 0,
451             RxOnly = 1,
452             TxOnly = 2,
453             TxRx = 3,
454         }
455 
456         private enum CommandSpeed
457         {
458             Standard = 0,
459             Dual = 1,
460             Quad = 2,
461             Reserved = 3,
462         }
463 
464         public enum Registers
465         {
466             InterruptState = 0x0,
467             InterruptEnable = 0x4,
468             InterruptTest = 0x8,
469             AlertTest = 0xc,
470             Control = 0x10,
471             Status = 0x14,
472             Configopts = 0x18,
473             ChipSelectID = 0x1c,
474             Command = 0x20,
475             Rxdata = 0x24,
476             Txdata = 0x28,
477             ErrorEnable = 0x2c,
478             ErrorStatus = 0x30,
479             EventEnable = 0x34,
480         }
481 
482         private struct CommandDefinition
483         {
CommandDefinitionAntmicro.Renode.Peripherals.SPI.OpenTitan_SpiHost.CommandDefinition484             public CommandDefinition(uint length, CommandDirection direction, bool keepChipSelect)
485             {
486                 this.Length = length;
487                 this.Direction = direction;
488                 this.KeepChipSelect = keepChipSelect;
489             }
490 
491             public uint Length { get; }
492             public CommandDirection Direction { get; }
493             public bool KeepChipSelect { get; }
494         }
495     } // End class OpenTitan_SpiHost
496 } // End of namespace
497