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 
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.Peripherals.Bus;
13 using Antmicro.Renode.Logging;
14 using Antmicro.Renode.Utilities;
15 
16 namespace Antmicro.Renode.Peripherals.I2C
17 {
18     public class PULP_uDMA_I2C : SimpleContainer<II2CPeripheral>, IDoubleWordPeripheral, IProvidesRegisterCollection<DoubleWordRegisterCollection>
19     {
PULP_uDMA_I2C(IMachine machine)20         public PULP_uDMA_I2C(IMachine machine) : base(machine)
21         {
22             sysbus = machine.GetSystemBus(this);
23             outputBuffer = new Queue<byte>();
24             RegistersCollection = new DoubleWordRegisterCollection(this);
25 
26             TxEvent = new GPIO();
27             RxEvent = new GPIO();
28 
29             DefineRegisters();
30 
31             Reset();
32         }
33 
Reset()34         public override void Reset()
35         {
36             RegistersCollection.Reset();
37             outputBuffer.Clear();
38 
39             repeatCounter = 1;
40             dataBytesLeft = 0;
41             bytesToRead = 0;
42 
43             state = State.WaitForCommand;
44         }
45 
ReadDoubleWord(long offset)46         public uint ReadDoubleWord(long offset)
47         {
48             return RegistersCollection.Read(offset);
49         }
50 
WriteDoubleWord(long offset, uint value)51         public void WriteDoubleWord(long offset, uint value)
52         {
53             RegistersCollection.Write(offset, value);
54         }
55 
56         public DoubleWordRegisterCollection RegistersCollection { get; }
57 
58         public GPIO TxEvent { get; }
59         public GPIO RxEvent { get; }
60 
DefineRegisters()61         private void DefineRegisters()
62         {
63             Registers.RxBufferBaseAddress.Define(this)
64                 // this is not consistent with the documentation
65                 // that states that only 21 bits are used for the address,
66                 // but otherwise the sample fails
67                 .WithValueField(0, 32, out rxBufferAddress, name: "RX_SADDR")
68             ;
69 
70             Registers.RxBufferSize.Define(this)
71                 .WithValueField(0, 20, out rxBufferSize, name: "RX_SIZE")
72                 .WithReservedBits(20, 12)
73             ;
74 
75             Registers.RxStreamConfiguration.Define(this)
76                 .WithTag("CONTINOUS", 0, 1)
77                 .WithReservedBits(1, 3)
78                 .WithFlag(4, out rxStreamEnabled, name: "EN")
79                 .WithTag("PENDING", 5, 1)
80                 .WithTag("CLR", 6, 1)
81                 .WithReservedBits(7, 25)
82             ;
83 
84             Registers.TxBufferBaseAddress.Define(this)
85                 // this is not consistent with the documentation
86                 // that states that only 21 bits are used for the address,
87                 // but otherwise the sample fails
88                 .WithValueField(0, 32, out txBufferAddress, name: "TX_SADDR")
89             ;
90 
91             Registers.TxBufferSize.Define(this)
92                 .WithValueField(0, 20, out txBufferSize, name: "TX_SIZE")
93                 .WithReservedBits(20, 12)
94             ;
95 
96             Registers.TxStreamConfiguration.Define(this)
97                 .WithTag("CONTINOUS", 0, 1)
98                 .WithReservedBits(1, 3)
99                 .WithFlag(4, name: "EN",
100                     valueProviderCallback: _ => false, // the transfer is instant + we don't support the continous mode, so we disable right away
101                     writeCallback: (_, val) =>
102                     {
103                         if(!val)
104                         {
105                             return;
106                         }
107 
108                         this.Log(LogLevel.Debug, "Starting a DMA TX transaction");
109                         var data = sysbus.ReadBytes(txBufferAddress.Value, (int)txBufferSize.Value);
110 #if DEBUG_PACKETS
111                         this.Log(LogLevel.Noisy, "Read data from the memory @ 0x{0:X}: {1}", txBufferAddress.Value, Misc.PrettyPrintCollectionHex(data));
112 #endif
113 
114                         HandleIncomingBytes(data);
115                         TxEvent.Blink();
116                     })
117                 .WithFlag(5, FieldMode.Read, name: "PENDING", valueProviderCallback: _ => false)
118                 .WithTag("CLR", 6, 1)
119                 .WithReservedBits(7, 25)
120             ;
121 
122             Registers.CommandBufferBaseAddress.Define(this)
123                 .WithValueField(0, 21, name: "CMD_SADDR")
124                 .WithReservedBits(21, 11)
125             ;
126 
127             Registers.CommandBufferSize.Define(this)
128                 .WithValueField(0, 20, name: "CMD_SIZE")
129                 .WithReservedBits(20, 12)
130             ;
131 
132             Registers.CommandStreamConfiguration.Define(this)
133                 .WithTag("CONTINOUS", 0, 1)
134                 .WithReservedBits(1, 3)
135                 .WithTag("EN", 4, 1)
136                 .WithTag("PENDING", 5, 1)
137                 .WithTag("CLR", 6, 1)
138                 .WithReservedBits(7, 25)
139             ;
140 
141             Registers.Status.Define(this)
142                 .WithTag("BUSY", 0, 1)
143                 .WithTag("ARB_LOST", 1, 1)
144                 .WithReservedBits(2, 30)
145             ;
146 
147             Registers.Setup.Define(this)
148                 .WithTag("DO_RST", 0, 1)
149                 .WithReservedBits(1, 31)
150             ;
151         }
152 
HandleIncomingBytes(byte[] data)153         private bool HandleIncomingBytes(byte[] data)
154         {
155             var index = 0;
156             while(index < data.Length)
157             {
158                 switch(state)
159                 {
160                     case State.WaitForCommand:
161                     {
162                         var result = HandleCommand((Command)(data[index] >> 4), data, index + 1);
163                         if(result == -1)
164                         {
165                             // there was an error
166                             return false;
167                         }
168                         index += result + 1;
169                     }
170                     break;
171 
172                     case State.WaitForData:
173                     {
174                         var result = HandleData(data, index);
175                         if(result == -1)
176                         {
177                             // there was an error
178                             return false;
179                         }
180                         index += result;
181                     }
182                     break;
183 
184                     default:
185                         this.Log(LogLevel.Warning, "Received data in an unexpected state: {0}", state);
186                         return false;
187                 }
188             }
189 
190             return true;
191         }
192 
HandleCommand(Command command, byte[] data, int argumentOffset)193         private int HandleCommand(Command command, byte[] data, int argumentOffset)
194         {
195             // A command can be followed by argument bytes
196             // - their actual amount is dependent on:
197             //  (a) the command type,
198             //  (b) the preceeding RPT command.
199             //  That's why we must dynamically return the number of bytes that were consumed in this particular execution.
200 
201             this.Log(LogLevel.Debug, "Handling command {0} (0x{0:X}) at offset {1}", command, argumentOffset - 1);
202 
203             int argumentBytesConsumed;
204             switch(command)
205             {
206                 case Command.Start:
207                 case Command.Stop:
208                     argumentBytesConsumed = 0;
209                     // it might be a repeated start
210                     TrySendToDevice(finishTransmission: true, generateWarningOnEmptyBuffer: false);
211                     break;
212 
213                 case Command.ReadAck:
214                     argumentBytesConsumed = 0;
215                     bytesToRead += repeatCounter;
216                     break;
217 
218                 case Command.ReadNotAck:
219                     argumentBytesConsumed = 0;
220                     bytesToRead++;
221                     TryReadFromDevice();
222                     break;
223 
224                 case Command.EndOfTransmission:
225                     argumentBytesConsumed = 0;
226                     break;
227 
228                 case Command.Write:
229                     argumentBytesConsumed = 0;
230                     dataBytesLeft = repeatCounter;
231                     state = State.WaitForData;
232                     this.Log(LogLevel.Debug, "Write command detected with {0} bytes of data", repeatCounter);
233                     break;
234 
235                 case Command.Configure:
236                     // skip the div value, we don't simulate it anyway
237                     argumentBytesConsumed = 2;
238                     break;
239 
240                 case Command.Repeat:
241                     repeatCounter = data[argumentOffset];
242                     argumentBytesConsumed = 1;
243                     break;
244 
245                 default:
246                     this.Log(LogLevel.Warning, "Unhandled command: {0} (0x{0:X})", command);
247                     argumentBytesConsumed = -1;
248                     break;
249             }
250 
251             if(command != Command.Repeat)
252             {
253                 // reset the repeat counter
254                 repeatCounter = 1;
255             }
256 
257             return argumentBytesConsumed;
258         }
259 
HandleData(byte[] data, int offset)260         private int HandleData(byte[] data, int offset)
261         {
262             var bytesConsumed = 0;
263 
264             while(offset < data.Length && dataBytesLeft > 0)
265             {
266                 outputBuffer.Enqueue(data[offset]);
267 
268                 bytesConsumed++;
269                 offset++;
270                 dataBytesLeft--;
271             }
272 
273             if(dataBytesLeft == 0)
274             {
275                 state = State.WaitForCommand;
276             }
277 
278             this.Log(LogLevel.Debug, "Handled {0} bytes of incoming data, bytes left {1}", bytesConsumed, dataBytesLeft);
279 
280             return bytesConsumed;
281         }
282 
TrySendToDevice(bool finishTransmission, bool generateWarningOnEmptyBuffer = true)283         private bool TrySendToDevice(bool finishTransmission, bool generateWarningOnEmptyBuffer = true)
284         {
285             if(outputBuffer.Count == 0)
286             {
287                 if(generateWarningOnEmptyBuffer)
288                 {
289                     this.Log(LogLevel.Warning, "Tried to send data to the device, but there is no device address in the buffer");
290                 }
291                 return false;
292             }
293 
294             var addressAndDirection = outputBuffer.Dequeue();
295             var address = addressAndDirection >> 1;
296             var isRead = BitHelper.IsBitSet(addressAndDirection, 0);
297 
298             if(isRead)
299             {
300                 this.Log(LogLevel.Warning, "Read bit should not be set when sending data to the device, ignoring the transfer");
301                 return false;
302             }
303 
304             if(outputBuffer.Count == 0)
305             {
306                 this.Log(LogLevel.Warning, "Tried to send data to the device, but the output buffer is empty");
307                 return false;
308             }
309 
310             if(!TryGetByAddress(address, out var device))
311             {
312                 this.Log(LogLevel.Warning, "Tried to send data to the device 0x{0:X}, but it's not connected", address);
313                 return false;
314             }
315 
316             var data = outputBuffer.DequeueAll();
317 
318             this.Log(LogLevel.Debug, "Sending data of size {0} to the device 0x{1:X}", data.Length, address);
319             device.Write(data);
320 
321             if(finishTransmission)
322             {
323                 device.FinishTransmission();
324             }
325             return true;
326         }
327 
TryReadFromDevice()328         private bool TryReadFromDevice()
329         {
330             if(outputBuffer.Count == 0)
331             {
332                 this.Log(LogLevel.Warning, "Tried to read data from the device, but there is no device address in the buffer");
333                 return false;
334             }
335 
336             var addressAndDirection = outputBuffer.Dequeue();
337             var address = addressAndDirection >> 1;
338             var isRead = BitHelper.IsBitSet(addressAndDirection, 0);
339 
340             if(!isRead)
341             {
342                 this.Log(LogLevel.Warning, "Read bit should be set when reading data from the device, ignoring the transfer");
343                 return false;
344             }
345 
346             if(!TryGetByAddress(address, out var device))
347             {
348                 this.Log(LogLevel.Warning, "Tried to read data from the device 0x{0:X}, but it's not connected", address);
349                 return false;
350             }
351 
352             var data = device.Read(bytesToRead);
353             if(data.Length != bytesToRead)
354             {
355                 this.Log(LogLevel.Warning, "Tried to read {0} bytes from the device, but it returned {1} bytes", bytesToRead, data.Length);
356             }
357 
358             if(!rxStreamEnabled.Value)
359             {
360                 this.Log(LogLevel.Warning, "Received {0} bytes from the device, but RX DMA stream is not enabled. Dropping it", data.Length);
361             }
362             else
363             {
364                 if(data.Length != (int)rxBufferSize.Value)
365                 {
366                     this.Log(LogLevel.Warning, "Received {0} bytes from the device, but RX DMA stream is configured for {1} bytes. This might indicate problems in the driver", data.Length, rxBufferSize.Value);
367                 }
368 
369                 sysbus.WriteBytes(data, rxBufferAddress.Value);
370                 RxEvent.Blink();
371 
372                 rxStreamEnabled.Value = false;
373             }
374 
375             bytesToRead = 0;
376             return true;
377         }
378 
379         // holds information about
380         // how many times *the next* command
381         // should be repeated;
382         // set by the RPT command;
383         // reset to 1 after the first use
384         private int repeatCounter;
385         private int dataBytesLeft;
386         private int bytesToRead;
387 
388         private State state;
389 
390         private IValueRegisterField txBufferAddress;
391         private IValueRegisterField txBufferSize;
392         private IValueRegisterField rxBufferAddress;
393         private IValueRegisterField rxBufferSize;
394         private IFlagRegisterField rxStreamEnabled;
395 
396         private readonly IBusController sysbus;
397         private readonly Queue<byte> outputBuffer;
398 
399         private enum State
400         {
401             WaitForCommand,
402             WaitForData
403         }
404 
405         private enum Command : byte
406         {
407             Start = 0x0,
408             WaitEvent = 0x1,
409             Stop = 0x2,
410             SetupStartAddress = 0x3,
411             ReadAck = 0x4,
412             SetupTransferSize = 0x5,
413             ReadNotAck = 0x6,
414             WriteByte = 0x7,
415             Write = 0x8,
416             EndOfTransmission = 0x9,
417             Wait = 0xA,
418             Repeat = 0xC,
419             Configure = 0xE
420         }
421 
422         private enum Registers
423         {
424             RxBufferBaseAddress = 0x0,
425             RxBufferSize = 0x4,
426             RxStreamConfiguration = 0x8,
427 
428             TxBufferBaseAddress = 0x10,
429             TxBufferSize = 0x14,
430             TxStreamConfiguration = 0x18,
431 
432             CommandBufferBaseAddress = 0x20,
433             CommandBufferSize = 0x24,
434             CommandStreamConfiguration = 0x28,
435 
436             Status = 0x30,
437             Setup = 0x34
438         }
439     }
440 }
441