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 Antmicro.Renode.Core;
9 using Antmicro.Renode.Logging;
10 using Antmicro.Renode.Core.Structure.Registers;
11 using Antmicro.Renode.Peripherals.SPI;
12 using Antmicro.Renode.Peripherals.GPIOPort;
13 using Antmicro.Renode.Peripherals.Bus;
14 
15 namespace Antmicro.Renode.Peripherals.DMA
16 {
17     public class EOSS3_SPI_DMA : BaseGPIOPort, IDoubleWordPeripheral, IProvidesRegisterCollection<DoubleWordRegisterCollection>, IKnownSize
18     {
EOSS3_SPI_DMA(IMachine machine, DesignWare_SPI spi)19         public EOSS3_SPI_DMA(IMachine machine, DesignWare_SPI spi) : base(machine, 1)
20         {
21             sysbus = machine.GetSystemBus(this);
22             this.spi = spi;
23             innerLock = new object();
24             RegistersCollection = new DoubleWordRegisterCollection(this);
25 
26             DefineRegisters();
27         }
28 
ReadDoubleWord(long offset)29         public uint ReadDoubleWord(long offset)
30         {
31             return RegistersCollection.Read(offset);
32         }
33 
WriteDoubleWord(long offset, uint value)34         public void WriteDoubleWord(long offset, uint value)
35         {
36             RegistersCollection.Write(offset, value);
37         }
38 
Reset()39         public override void Reset()
40         {
41             enabled = false;
42 
43             base.Reset();
44             RegistersCollection.Reset();
45         }
46 
OnGPIO(int number, bool value)47         public override void OnGPIO(int number, bool value)
48         {
49             if(!value)
50             {
51                 return;
52             }
53 
54             lock(innerLock)
55             {
56                 if(!Enabled)
57                 {
58                     // ignore interrupts when not enabled
59                     this.Log(LogLevel.Warning, "DMA not enabled, ignoring an interrupt from SPI Master");
60                     return;
61                 }
62 
63                 // this means that the SPIMaster has some data waiting in the buffer
64                 while(spi.TryDequeueFromReceiveBuffer(out var data))
65                 {
66                     // now we must check if there is one or two bytes
67                     var bytesToHandle = 0u;
68                     switch(spi.FrameSize)
69                     {
70                         case DesignWare_SPI.TransferSize.SingleByte:
71                         {
72                             bytesToHandle = 1;
73                             break;
74                         }
75 
76                         case DesignWare_SPI.TransferSize.DoubleByte:
77                         {
78                             bytesToHandle = 2;
79                             break;
80                         }
81 
82                         default:
83                             throw new ArgumentException($"Unexpected transfer size {spi.FrameSize}");
84                     }
85 
86                     if(bytesToHandle > transferCount.Value)
87                     {
88                         this.Log(LogLevel.Warning, "DMA configuration mismatch detected - transfer count left is {0} bytes, but there are more ({1}) bytes available in the SPI Master's buffer. Some data will be lost!", transferCount.Value, bytesToHandle);
89                         bytesToHandle = (uint)transferCount.Value;
90                     }
91 
92                     // now we must check if there is one or two bytes again
93                     switch(bytesToHandle)
94                     {
95                         case 1:
96                         {
97                             this.Log(LogLevel.Noisy, "DMA transfer: writing byte 0x{0:X} at offset 0x{1:X}", data, destinationAddress.Value);
98                             sysbus.WriteByte(destinationAddress.Value, (byte)data);
99                             break;
100                         }
101 
102                         case 2:
103                         {
104                             this.Log(LogLevel.Noisy, "DMA transfer: writing ushort 0x{0:X} at offset 0x{1:X}", data, destinationAddress.Value);
105                             sysbus.WriteWord(destinationAddress.Value, data);
106                             break;
107                         }
108                     }
109 
110                     // transferCount is in bytes
111                     transferCount.Value -= bytesToHandle;
112                     destinationAddress.Value += bytesToHandle;
113 
114                     if(transferCount.Value == 0)
115                     {
116                         this.Log(LogLevel.Noisy, "That's the end of the DMA transfer");
117 
118                         dmaDataAvailable.Value = true;
119                         UpdateInterrupts();
120                         break;
121                     }
122                 }
123             }
124         }
125 
126         public long Size => 0x100;
127         public GPIO IRQ { get; } = new GPIO();
128 
129         public DoubleWordRegisterCollection RegistersCollection { get; private set; }
130 
131         public bool Enabled
132         {
133             get => enabled;
134 
135             set
136             {
137                 lock(innerLock)
138                 {
139                     if(value)
140                     {
141                         transferCount.Value += 1;
142                     }
143 
144                     enabled = value;
145                 }
146             }
147         }
148 
UpdateInterrupts()149         private void UpdateInterrupts()
150         {
151             var state = false;
152 
153             state |= (dmaDataAvailable.Value && dmaDataAvailableEnable.Value);
154 
155             this.Log(LogLevel.Noisy, "Setting interrupt to: {0}", state);
156             IRQ.Set(state);
157         }
158 
DefineRegisters()159         private void DefineRegisters()
160         {
161             Registers.Control.Define(this, 0x40) // DMA_HREADY is set on reset
162                 .WithFlag(0, FieldMode.Write, name: "dma_start", writeCallback: (_, val) => { if(val) Enabled = true; })
163                 .WithFlag(1, FieldMode.Write, name: "dma_stop", writeCallback: (_, val) => { if(val) Enabled = false; })
164                 .WithTag("dma_ahb_sel", 2, 1)
165                 .WithTag("dma_hsel", 3, 1)
166                 .WithTag("dma_htrans_0", 4, 1)
167                 .WithTag("dma_htrans_1", 5, 1)
168                 .WithFlag(6, FieldMode.Read, name: "dma_hready", valueProviderCallback: _ => true)
169                 .WithTag("dma_xfr_pending", 7, 1)
170                 .WithTag("bridge_xfr_pending", 8, 1)
171                 .WithReservedBits(9, 23)
172             ;
173 
174             Registers.DestinationAddress.Define(this)
175                 .WithValueField(0, 32, out destinationAddress, name: "dma_dest_addr")
176             ;
177 
178             Registers.TransferCount.Define(this)
179                 .WithValueField(0, 26, out transferCount, name: "dma_xfr_cnt")
180                 .WithReservedBits(27, 5)
181             ;
182 
183             Registers.ConfigFlashHeader.Define(this)
184                 .WithTag("dma_boot_xfr_size", 0, 16)
185                 .WithTag("dma_spi_clk_divide", 16, 8)
186                 .WithTag("dma_device_id", 24, 8)
187             ;
188 
189             Registers.Interrupts.Define(this)
190                 .WithTag("dma_herror", 0, 1) // FieldMode.Read | FieldMode.WriteOneToClear
191                 .WithFlag(1, out dmaDataAvailable, FieldMode.Read, name: "dmrx_data_available")
192                 .WithTag("dmahb_bridge_fifo_overflow", 2, 1) // FieldMode.Read | FieldMode.WriteOneToClear
193                 .WithTag("dmspim_ssi_txe_intr", 3, 1)
194                 .WithTag("dmspim_ssi_txo_intr", 4, 1)
195                 .WithTag("dmspim_ssi_rxf_intr", 5, 1)
196                 .WithTag("dmspim_ssi_rxo_intr", 6, 1)
197                 .WithTag("dmspim_ssi_rxu_intr", 7, 1)
198                 .WithTag("dmspim_ssi_mst_intr", 8, 1)
199                 .WithReservedBits(9, 23)
200                 .WithWriteCallback((_, __) => UpdateInterrupts())
201             ;
202 
203             Registers.InterruptMask.Define(this, 0x7)
204                 .WithTag("dma_herror_mask", 0, 1)
205                 .WithFlag(1, out dmaDataAvailableEnable, name: "dmrx_data_available_mask")
206                 .WithTag("dmahb_bridge_fifo_overflow_mask", 2, 1)
207                 .WithReservedBits(3, 29)
208                 .WithWriteCallback((_, __) => UpdateInterrupts())
209             ;
210 
211             Registers.ConfigStateMachineDelay.Define(this)
212                 .WithTag("delay_reg", 0, 16)
213                 .WithReservedBits(16, 16)
214             ;
215         }
216 
217         private IValueRegisterField destinationAddress;
218         private IValueRegisterField transferCount;
219         private IFlagRegisterField dmaDataAvailable;
220         private IFlagRegisterField dmaDataAvailableEnable;
221         private bool enabled;
222 
223         private readonly IBusController sysbus;
224         private readonly DesignWare_SPI spi;
225         private readonly object innerLock;
226 
227         private enum Registers
228         {
229             Control = 0x00,
230             DestinationAddress = 0x04,
231             TransferCount = 0x08,
232             ConfigFlashHeader = 0x0C,
233             Interrupts = 0x10,
234             InterruptMask = 0x14,
235             ConfigStateMachineDelay = 0x18
236         }
237     }
238 }
239