1 //
2 // Copyright (c) 2010-2024 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.Registers;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Peripherals.Bus;
14 using Antmicro.Renode.Exceptions;
15 using Antmicro.Renode.Time;
16 using Antmicro.Renode.Peripherals.Timers;
17 using Antmicro.Renode.Utilities;
18 using Antmicro.Renode.Utilities.RESD;
19 
20 namespace Antmicro.Renode.Peripherals.Analog
21 {
22     public class RenesasDA14_GPADC : BasicDoubleWordPeripheral, IKnownSize
23     {
RenesasDA14_GPADC(IMachine machine)24         public RenesasDA14_GPADC(IMachine machine) : base(machine)
25         {
26             DefineRegisters();
27             resdStream = new RESDStream<VoltageSample>[NumberOfChannels];
28             samplingTimer = new LimitTimer(
29                 machine.ClockSource, 1000000, this, "samplingClock",
30                 eventEnabled: true,
31                 direction: Direction.Ascending,
32                 enabled: false,
33                 autoUpdate: false,
34                 workMode: WorkMode.OneShot);
35             samplingTimer.LimitReached += OnConversionFinished;
36         }
37 
FeedSamplesFromRESD(ReadFilePath filePath, uint adcChannel, uint resdChannel = 0, RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)38         public void FeedSamplesFromRESD(ReadFilePath filePath, uint adcChannel, uint resdChannel = 0,
39             RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)
40         {
41             EnsureChannelIsValid(adcChannel);
42             try
43             {
44                 resdStream[adcChannel] = this.CreateRESDStream<VoltageSample>(filePath, resdChannel, sampleOffsetType, sampleOffsetTime);
45             }
46             catch (RESDException)
47             {
48                 for(var channelId = 0; channelId < NumberOfChannels; channelId++)
49                 {
50                     if(resdStream[channelId] != null)
51                     {
52                         resdStream[channelId].Dispose();
53                     }
54                 }
55                 throw new RecoverableException($"Could not load RESD channel {resdChannel} from {filePath}");
56             }
57         }
58 
Reset()59         public override void Reset()
60         {
61             base.Reset();
62             samplingTimer.Reset();
63             selectNegative = 0;
64             interruptPending.Value = false;
65             UpdateInterrupts();
66         }
67 
68         public GPIO IRQ { get; } = new GPIO();
69         public long Size => 0x24;
70 
ParseResult(uint voltage)71         private uint ParseResult(uint voltage)
72         {
73             return (voltage << (6 - Math.Min(6, (int)conversionNumber.Value))) & ((1 << 16) - 1);
74         }
75 
HandleConversion(uint channelId)76         private uint HandleConversion(uint channelId)
77         {
78             EnsureChannelIsValid(channelId);
79             if(resdStream[channelId] == null || resdStream[channelId].TryGetCurrentSample(this, (sample) => sample.Voltage / 1000, out var voltage, out _) != RESDStreamStatus.OK)
80             {
81                 return ParseResult(DefaultChannelVoltage);
82             }
83             // The attenuator value scales the allowed voltage input.
84             // If the voltage is greater than the allowed level, then maxVoltage is returned.
85             var maxVoltage = (uint) (0.9 * (attenuator.Value + 1) * 1000);
86             if(voltage > maxVoltage)
87             {
88                 this.Log(LogLevel.Warning, "The enabled attenuator allows input voltage up to {0}mV. Provided value: {1}mV", maxVoltage, voltage);
89                 return ParseResult(maxVoltage);
90             }
91             return ParseResult(voltage);
92         }
93 
EnsureChannelIsValid(uint channelIdx)94         private void EnsureChannelIsValid(uint channelIdx)
95         {
96             if(channelIdx >= NumberOfChannels)
97             {
98                 throw new RecoverableException($"Invalid argument value: {channelIdx}. This peripheral implements only channels in range 0-{NumberOfChannels - 1}");
99             }
100         }
101 
StartConversion()102         private void StartConversion()
103         {
104             if(!enabled.Value)
105             {
106                 this.Log(LogLevel.Warning, "Tried to start the conversion, but the GP_ADC_EN is not enabled");
107             }
108             this.Log(LogLevel.Debug, "Starting conversion on channel: {0}", selectPositive.Value);
109 
110             // Documentation chapter 21.2.3.1
111             // The total conversion time of an AD conversion depends on various settings.
112             // Conversion time is especially important for the continuous mode
113             samplingTimer.Limit = (ulong) Math.Pow(2, conversionNumber.Value) * (sampleTime.Value == 0 ? 2 : sampleTime.Value * 8) + (samplingTimer.Mode == WorkMode.Periodic ? converionInterval.Value : 0);
114             samplingTimer.Enabled = true;
115         }
116 
OnConversionFinished()117         private void OnConversionFinished()
118         {
119             this.Log(LogLevel.Debug, "Ending conversion on channel: {0}", selectPositive.Value);
120 
121             // Documentation chapter 21.2.4
122             // With bit GP_ADC_CTRL_REG[GP_ADC_MUTE] = 1, the input is connected to 0.5 × ADC reference.
123             // So, the ideal ADC result should be 511.5. Any deviation from this is the ADC offset
124             if(mute.Value)
125             {
126                 result.Value = 0x200 << 6;
127             }
128             else
129             {
130                 result.Value = HandleConversion((uint) selectPositive.Value) - (singleEnded.Value ? 0 : HandleConversion((uint) selectNegative));
131             }
132             interruptPending.Value = true;
133             UpdateInterrupts();
134         }
135 
UpdateInterrupts()136         private void UpdateInterrupts()
137         {
138             bool value = interruptPending.Value && unmaskedInterrupt.Value;
139             IRQ.Set(value);
140         }
141 
DefineRegisters()142         private void DefineRegisters()
143         {
144             GeneralPurposeRegisters.Control.Define(this)
145                 .WithFlag(0, out enabled, name: "GP_ADC_EN", writeCallback: (_, val) => { if(!val) Reset(); })
146                 .WithFlag(1, valueProviderCallback: _ => samplingTimer.Enabled , name: "GP_ADC_START", changeCallback: (_, val) =>
147                         {
148                             if(val)
149                             {
150                                 StartConversion();
151                             }
152                             else
153                             {
154                                 this.Log(LogLevel.Warning, "It is not allowed to write GP_ADC_START bit while it is not (yet) zero.");
155                             }
156                         })
157                 .WithFlag(2, name: "GP_ADC_CONT", writeCallback: (_, value) => { if(value) samplingTimer.Mode = WorkMode.Periodic; })
158                 .WithFlag(3, out dmaEnabled, name: "GP_ADC_DMA_EN")
159                 .WithFlag(4, out interruptPending, FieldMode.Read, name: "GP_ADC_INT")
160                 .WithFlag(5, out unmaskedInterrupt, name: "GP_ADC_MINT", writeCallback: (_, __) => UpdateInterrupts())
161                 .WithFlag(6, out singleEnded, name: "GP_ADC_SE")
162                 .WithFlag(7, out mute, name: "GP_ADC_MUTE")
163                 .WithTaggedFlag("GP_ADC_SIGN", 8)
164                 .WithTaggedFlag("GP_ADC_CHOP", 9)
165                 .WithTaggedFlag("GP_ADC_LDO_HOLD", 10)
166                 .WithReservedBits(11, 1)
167                 .WithTaggedFlag("DIE_TEMP_EN", 12)
168                 .WithReservedBits(13, 19);
169 
170             GeneralPurposeRegisters.Control2.Define(this, resetValue: 0x00000200)
171                 .WithValueField(0, 2, out attenuator, name: "GP_ADC_ATTN")
172                 .WithTaggedFlag("GP_ADC_I20U", 2)
173                 .WithReservedBits(3, 3)
174                 .WithValueField(6, 3, out conversionNumber, name: "GP_ADC_CONV_NRS")
175                 .WithValueField(9, 4, out sampleTime, name: "GP_ADC_SMPL_TIME")
176                 .WithTag("GP_ADC_STORE_DEL", 13 ,3)
177                 .WithReservedBits(16, 16);
178 
179             GeneralPurposeRegisters.Control3.Define(this, resetValue: 0x00000040)
180                 .WithTag("GP_ADC_EN_DEL", 0, 8)
181                 .WithValueField(8, 8, out converionInterval, name: "GP_ADC_INTERVAL")
182                 .WithReservedBits(16, 16);
183 
184             GeneralPurposeRegisters.InputSelection.Define(this)
185                 .WithValueField(0, 4, name: "GP_ADC_SEL_N", writeCallback: (_, val) =>
186                         {
187                             if(val <= 3 || val >= 10)
188                             {
189                                 selectNegative = val;
190                             }
191                             else
192                             {
193                                 this.Log(LogLevel.Warning, "Incorrect GP_ADC_SEL_N value {0}", val);
194                             }
195                         })
196                 .WithValueField(4, 4, out selectPositive, name: "GP_ADC_SEL_P")
197                 .WithReservedBits(8, 24);
198 
199             GeneralPurposeRegisters.PositiveOffset.Define(this, resetValue: 0x00000200)
200                 .WithTag("GP_ADC_OFFP", 0, 10)
201                 .WithReservedBits(10, 22);
202 
203             GeneralPurposeRegisters.NegativeOffset.Define(this)
204                 .WithTag("GP_ADC_OFFN", 0, 10)
205                 .WithReservedBits(10, 22);
206 
207             GeneralPurposeRegisters.ClearInterrupt.Define(this)
208                 .WithValueField(0, 16, valueProviderCallback: _ => 0, writeCallback: (_, __) =>
209                     {
210                         interruptPending.Value = false;
211                         UpdateInterrupts();
212                     }, name: "GP_ADC_CLR_INT")
213                 .WithReservedBits(16, 16);
214 
215             GeneralPurposeRegisters.ResultRegister.Define(this)
216                 .WithValueField(0, 16, out result, FieldMode.Read, name: "GP_ADC_VAL")
217                 .WithReservedBits(16, 16);
218         }
219 
220         private IFlagRegisterField enabled;
221         private IFlagRegisterField dmaEnabled;
222         private IFlagRegisterField interruptPending;
223         private IFlagRegisterField unmaskedInterrupt;
224         private IFlagRegisterField singleEnded;
225         private IFlagRegisterField mute;
226         private IValueRegisterField attenuator;
227         private IValueRegisterField conversionNumber;
228         private IValueRegisterField sampleTime;
229         private IValueRegisterField converionInterval;
230         private IValueRegisterField selectPositive;
231         private IValueRegisterField result;
232 
233         private const int NumberOfChannels = 14;
234         private const uint DefaultChannelVoltage = 100;
235 
236         private readonly LimitTimer samplingTimer;
237         private readonly RESDStream<VoltageSample>[] resdStream;
238         private ulong selectNegative;
239 
240         private enum GeneralPurposeRegisters : long
241         {
242             Control = 0x0,
243             Control2 = 0x4,
244             Control3 = 0x8,
245             InputSelection = 0xC,
246             PositiveOffset = 0x10,
247             NegativeOffset = 0x14,
248             ClearInterrupt = 0x1C,
249             ResultRegister = 0x20,
250         }
251     }
252 }
253