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;
9 using System.Collections.Generic;
10 using System.IO;
11 using System.Linq;
12 using Antmicro.Renode.Core;
13 using Antmicro.Renode.Core.Structure.Registers;
14 using Antmicro.Renode.Exceptions;
15 using Antmicro.Renode.Logging;
16 using Antmicro.Renode.Peripherals.Sensors;
17 using Antmicro.Renode.Utilities;
18 
19 namespace Antmicro.Renode.Peripherals.Analog
20 {
21     public class IMXRT_ADC : BasicDoubleWordPeripheral, IGPIOReceiver, IKnownSize
22     {
IMXRT_ADC(IMachine machine)23         public IMXRT_ADC(IMachine machine) : base(machine)
24         {
25             samplesFifos = new SensorSamplesFifo<ScalarSample>[NumberOfChannels];
26             for(var i = 0; i < samplesFifos.Length; i++)
27             {
28                 samplesFifos[i] = new SensorSamplesFifo<ScalarSample>();
29             }
30 
31             conversionCompleteInterruptEnable = new IFlagRegisterField[NumberOfHardwareTriggers];
32             inputChannel = new IValueRegisterField[NumberOfHardwareTriggers];
33             cdata = new IValueRegisterField[NumberOfHardwareTriggers];
34 
35             IRQ = new GPIO();
36             DefineRegisters();
37         }
38 
39         public GPIO IRQ { get; }
40 
41         public long Size => 0x5C;
42 
OnGPIO(int number, bool value)43         public void OnGPIO(int number, bool value)
44         {
45             if(number < 0 || number >= NumberOfHardwareTriggers)
46             {
47                 this.Log(LogLevel.Warning, "Unsupported external hardware trigger #{0} (supported are 0-{1})", number, NumberOfHardwareTriggers - 1);
48                 return;
49             }
50 
51             if(value)
52             {
53                 HandleTrigger(number, true);
54             }
55         }
56 
Reset()57         public override void Reset()
58         {
59             base.Reset();
60             UpdateInterrupts();
61         }
62 
FeedSample(byte sample, int channel)63         public void FeedSample(byte sample, int channel)
64         {
65             if(channel < 0 || channel >= NumberOfChannels)
66             {
67                 throw new RecoverableException($"This model supports channels 0-{(NumberOfChannels- 1)} inclusive");
68             }
69 
70             samplesFifos[channel].FeedSample(new ScalarSample(sample));
71         }
72 
FeedSamplesFromFile(string path, int channel)73         public void FeedSamplesFromFile(string path, int channel)
74         {
75             if(channel < 0 || channel >= NumberOfChannels)
76             {
77                 throw new RecoverableException($"This model supports channels 0-{(NumberOfChannels- 1)} inclusive");
78             }
79 
80             samplesFifos[channel].FeedSamplesFromFile(path);
81         }
82 
TriggerConversion(uint channel)83         public void TriggerConversion(uint channel)
84         {
85             if(channel >= NumberOfChannels)
86             {
87                 throw new RecoverableException($"This model supports channels 0-{(NumberOfChannels - 1)} inclusive");
88             }
89 
90             this.Log(LogLevel.Debug, "Starting conversion on channel #{0}", channel);
91             samplesFifos[channel].TryDequeueNewSample();
92             cdata[channel].Value = (uint)samplesFifos[channel].Sample.Value;
93 
94             conversionComplete.Value = true;
95             UpdateInterrupts();
96         }
97 
HandleTrigger(int triggerId, bool isHW)98         private void HandleTrigger(int triggerId, bool isHW)
99         {
100             this.Log(LogLevel.Debug, "Trigger #{0} fired", triggerId);
101 
102             var channel = (uint)inputChannel[triggerId].Value;
103             if(channel == ConversionDisabledMask)
104             {
105                 this.Log(LogLevel.Warning, "Trigger #{0} fired, but the conversion is disabled", triggerId);
106                 return;
107             }
108             if(channel == AdcEtcMask)
109             {
110                 this.Log(LogLevel.Warning, "External channel selection is not supported for trigger #{0}", triggerId);
111                 return;
112             }
113             if(channel > 15)
114             {
115                 this.Log(LogLevel.Warning, "Unsupported channel #{0} selected for trigger #{1}", channel, triggerId);
116                 return;
117             }
118 
119             if(isHW && !hardwareTriggerSelect.Value)
120             {
121                 this.Log(LogLevel.Warning, "Trigger #{0} fired, but hardware trigger select is not set", triggerId);
122                 return;
123             }
124 
125             TriggerConversion(channel);
126         }
127 
DefineRegisters()128         private void DefineRegisters()
129         {
130             Registers.HardwareTriggersControl0.DefineMany(this, NumberOfHardwareTriggers, (register, idx) =>
131             {
132                 var j = idx;
133                 register
134                     .WithValueField(0, 5, out inputChannel[j], name: "ADCH - Input Channel Select")
135                     .WithReservedBits(5, 2)
136                     .WithFlag(7, out conversionCompleteInterruptEnable[j], name: "AIEN - Conversion Complete Interrupt Enable/Disable Control")
137                     .WithReservedBits(8, 24)
138                     .WithWriteCallback((_, __) =>
139                     {
140                         if(j == 0)
141                         {
142                            // only trigger 0 can be fired by SW
143                            HandleTrigger(0, false);
144                         }
145                     });
146             });
147 
148             // NOTE: the documentation says there is only one bit here,
149             // but shouldn't there be more?
150             Registers.HardwareTriggersStatus.Define(this)
151                 .WithFlag(0, out conversionComplete, FieldMode.Read, name: "COCO0 - Conversion Complete Flag")
152                 .WithReservedBits(1, 31);
153 
154             Registers.HardwareTriggersDataResult0.DefineMany(this, NumberOfHardwareTriggers, (register, idx) =>
155             {
156               register
157                 .WithValueField(0, 12, out cdata[idx], FieldMode.Read, name: "CDATA - Data")
158                 .WithReservedBits(12, 20)
159                 .WithReadCallback((_, __) =>
160                 {
161                     conversionComplete.Value = false; // Set this to clear COCOn and turn off IRQ after read
162                     UpdateInterrupts();
163                 });
164             });
165 
166             Registers.Configuration.Define(this)
167                 .WithTag("ADICLK - Input Clock Select", 0, 2)
168                 .WithTag("MODE - Conversion Mode Selection", 2, 2)
169                 .WithTaggedFlag("ADLSMP - Long Sample Time Configuration", 4)
170                 .WithTag("ADIV - Clock Divide Select", 5, 2)
171                 .WithTaggedFlag("ADLPC - Low-Power Configuration", 7)
172                 .WithTag("ADSTS - Total Sample Duration In Number Of Full Cycles", 8, 2)
173                 .WithTaggedFlag("ADHSC - High Speed Configuration", 10)
174                 .WithTag("REFSEL - Voltage Reference Selection", 11, 2)
175                 .WithFlag(13, out hardwareTriggerSelect, name: "ADTRG - Conversion Trigger Select")
176                 .WithTag("AVGS - Hardware Avarage select", 14, 2)
177                 .WithTaggedFlag("OVWREN - Data Overwrite Enable", 16)
178                 .WithReservedBits(17, 15);
179 
180             Registers.GeneralControl.Define(this)
181                 .WithTaggedFlag("ADACKEN - Asynchronous clock output enable", 0)
182                 .WithTaggedFlag("DMAEN - DMA Enable", 1)
183                 .WithTaggedFlag("ACREN - Compare Function Range Enable", 2)
184                 .WithTaggedFlag("ACFGT - Compare Function Enable", 3)
185                 .WithTaggedFlag("ACFE - Compare Function Enable", 4)
186                 .WithTaggedFlag("AVGE - Hardware avarage enable", 5)
187                 .WithTaggedFlag("ADCO - Continouous Conversion Enable", 6)
188                 .WithFlag(7, name: "CAL - Calibration", valueProviderCallback: _ => false) // calibration ends right away
189                 .WithReservedBits(8, 24);
190 
191             Registers.GeneralStatus.Define(this)
192                 .WithTaggedFlag("ADACT - Conversion Active", 0)
193                 .WithTaggedFlag("CALF - Calibration Failed Flag", 1)
194                 .WithTaggedFlag("AWKST - Asynchronous wakeup interrupt status", 2)
195                 .WithReservedBits(3, 29);
196 
197             Registers.CompareValue.Define(this)
198                 .WithTag("CV1 - Compare Value 1", 0, 12)
199                 .WithReservedBits(12, 4)
200                 .WithTag("CV2 - Compare Value 2", 16, 12)
201                 .WithReservedBits(28, 4);
202 
203             Registers.OffsetCorrectionValue.Define(this)
204                 .WithTag("OFS - Offset value", 0, 12)
205                 .WithTaggedFlag("SIGN - Sign bit", 12)
206                 .WithReservedBits(13, 19);
207 
208             Registers.CalibrationValue.Define(this)
209                 .WithTag("CAL_CODE - Calibration Result Value", 0, 4)
210                 .WithReservedBits(4, 28);
211         }
212 
UpdateInterrupts()213         private void UpdateInterrupts()
214         {
215             var flag = false;
216 
217             flag |= conversionComplete.Value && conversionCompleteInterruptEnable.Any(x => x.Value);
218 
219             this.Log(LogLevel.Info, "Setting IRQ to {0}", flag);
220             IRQ.Set(flag);
221         }
222 
223         private IFlagRegisterField hardwareTriggerSelect;
224         private IFlagRegisterField conversionComplete;
225 
226         private readonly IFlagRegisterField[] conversionCompleteInterruptEnable;
227         private readonly IValueRegisterField[] inputChannel;
228         private readonly IValueRegisterField[] cdata;
229         private readonly SensorSamplesFifo<ScalarSample>[] samplesFifos;
230 
231         private const int NumberOfHardwareTriggers = 8;
232         private const int NumberOfChannels = 16;
233 
234         private const int AdcEtcMask = 0b10000;
235         private const int ConversionDisabledMask = 0b11111;
236 
237         private enum Registers
238         {
239             HardwareTriggersControl0 = 0x00,
240             HardwareTriggersControl1 = 0x04,
241             HardwareTriggersControl2 = 0x08,
242             HardwareTriggersControl3 = 0x0C,
243             HardwareTriggersControl4 = 0x10,
244             HardwareTriggersControl5 = 0x14,
245             HardwareTriggersControl6 = 0x18,
246             HardwareTriggersControl7 = 0x1C,
247 
248             HardwareTriggersStatus = 0x20,
249 
250             HardwareTriggersDataResult0 = 0x24,
251             HardwareTriggersDataResult1 = 0x28,
252             HardwareTriggersDataResult2 = 0x2C,
253             HardwareTriggersDataResult3 = 0x30,
254             HardwareTriggersDataResult4 = 0x34,
255             HardwareTriggersDataResult5 = 0x38,
256             HardwareTriggersDataResult6 = 0x3C,
257             HardwareTriggersDataResult7 = 0x40,
258 
259             Configuration = 0x44,
260             GeneralControl = 0x48,
261             GeneralStatus = 0x4C,
262             CompareValue = 0x50,
263             OffsetCorrectionValue = 0x54,
264             CalibrationValue = 0x58,
265         }
266     }
267 }
268