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.Linq;
9 using Antmicro.Renode.Peripherals.Timers;
10 using Antmicro.Renode.Peripherals.Sensor;
11 using Antmicro.Renode.Peripherals.I2C;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Core;
14 using Antmicro.Renode.Core.Structure.Registers;
15 using Antmicro.Renode.Utilities;
16 using Antmicro.Renode.Utilities.RESD;
17 
18 namespace Antmicro.Renode.Peripherals.Sensors
19 {
20     public class AS6221 : II2CPeripheral, IProvidesRegisterCollection<WordRegisterCollection>, ITemperatureSensor
21     {
AS6221(IMachine machine)22         public AS6221(IMachine machine)
23         {
24             RegistersCollection = new WordRegisterCollection(this);
25 
26             this.machine = machine;
27 
28             DefineRegisters();
29 
30             conversionTimer = new LimitTimer(machine.ClockSource, 1, this, "conv", autoUpdate: true, eventEnabled: true, enabled: true);
31             conversionTimer.LimitReached += HandleConversion;
32 
33             Reset();
34         }
35 
FeedSamplesFromRESD(ReadFilePath filePath, uint channelId = 0, RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)36         public void FeedSamplesFromRESD(ReadFilePath filePath, uint channelId = 0,
37             RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)
38         {
39             resdStream = this.CreateRESDStream<TemperatureSample>(filePath, channelId, sampleOffsetType, sampleOffsetTime);
40         }
41 
Write(byte[] data)42         public void Write(byte[] data)
43         {
44             if(data.Length == 0)
45             {
46                 this.Log(LogLevel.Warning, "Unexpected write with no data");
47                 return;
48             }
49 
50             var offset = 0;
51             if(state == States.Read)
52             {
53                 this.Log(LogLevel.Warning, "Trying to write while in read mode; ignoring");
54                 return;
55             }
56             else if(state == States.Idle)
57             {
58                 registerAddress = (Registers)data[0];
59                 offset = 1;
60                 state = data.Length == 1 ? States.Read : States.Write;
61             }
62 
63             if(data.Length > offset)
64             {
65                 foreach(var item in data.Skip(offset).Select((value, index) => new { index, value }))
66                 {
67                     RegistersCollection.WriteWithOffset((long)registerAddress.Value, item.index, item.value, msbFirst: true);
68                 }
69             }
70         }
71 
Read(int count)72         public byte[] Read(int count)
73         {
74             if(!registerAddress.HasValue)
75             {
76                 this.Log(LogLevel.Error, "Trying to read without setting address");
77                 return new byte[] {};
78             }
79 
80             if(state != States.Read)
81             {
82                 this.Log(LogLevel.Error, "Trying to read while in write mode");
83                 return new byte[] {};
84             }
85 
86             var result = new byte[count];
87             for(var i = 0; i < count; ++i)
88             {
89                 result[i] = RegistersCollection.ReadWithOffset((long)registerAddress.Value, i, msbFirst: true);
90             }
91             return result;
92         }
93 
FinishTransmission()94         public void FinishTransmission()
95         {
96             registerAddress = null;
97             state = States.Idle;
98         }
99 
Reset()100         public void Reset()
101         {
102             RegistersCollection.Reset();
103             conversionTimer.Reset();
104 
105             registerAddress = null;
106             state = States.Idle;
107 
108             currentTemperature = 0;
109             temperatureLowThreshold = DefaultLowThreshold;
110             temperatureHighThreshold = DefaultHighThreshold;
111 
112             UpdateConversionFrequency();
113         }
114 
115         public WordRegisterCollection RegistersCollection { get; }
116 
117         public decimal Temperature
118         {
119             get => (decimal)currentTemperature / (decimal)Resolution;
120             set
121             {
122                 if(value > MaximumTemperature)
123                 {
124                     currentTemperature = short.MaxValue;
125                     this.Log(LogLevel.Warning, "{0} is higher than maximum of {1} and has been clamped", value, MaximumTemperature);
126                 }
127                 else if(value < MinimumTemperature)
128                 {
129                     currentTemperature = short.MinValue;
130                     this.Log(LogLevel.Warning, "{0} is lower than minimum of {1} and has been clamped", value, MinimumTemperature);
131                 }
132                 else
133                 {
134                     currentTemperature = (short)(value * (decimal)Resolution);
135                 }
136             }
137         }
138 
UpdateConversionFrequency()139         private void UpdateConversionFrequency()
140         {
141             var frequencyAndLimit = ConversionRateToFrequencyAndLimit(conversionRate.Value);
142             conversionTimer.Frequency = frequencyAndLimit.Item1;
143             conversionTimer.Limit = frequencyAndLimit.Item2;
144         }
145 
HandleConversion()146         private void HandleConversion()
147         {
148             if(resdStream != null)
149             {
150                 switch(resdStream.TryGetCurrentSample(this, (sample) => sample.Temperature / 1000m, out var temperature, out _))
151                 {
152                     case RESDStreamStatus.OK:
153                         Temperature = temperature;
154                         break;
155                     case RESDStreamStatus.BeforeStream:
156                         // Just ignore and return previously set value
157                         break;
158                     case RESDStreamStatus.AfterStream:
159                         // No more samples in file
160                         resdStream.Dispose();
161                         resdStream = null;
162                         break;
163                 }
164             }
165 
166             if(interruptMode.Value)
167             {
168                 alarmFlag =
169                     currentTemperature > temperatureHighThreshold || currentTemperature < temperatureLowThreshold;
170             }
171             else
172             {
173                 if(consecutiveFaults < consecutiveFaultsThreshold)
174                 {
175                     consecutiveFaults++;
176                 }
177                 alarmFlag = consecutiveFaults == consecutiveFaultsThreshold;
178             }
179         }
180 
DefineRegisters()181         private void DefineRegisters()
182         {
183             Registers.Temperature.Define(this)
184                 .WithValueField(0, 16, name: "TEMP.temp",
185                     valueProviderCallback: _ => (uint)currentTemperature,
186                     writeCallback: (_, value) => currentTemperature = (short)value)
187             ;
188 
189             Registers.Configuration.Define(this, 0xA0)
190                 .WithReservedBits(0, 5)
191                 .WithFlag(5, FieldMode.Read, name: "CONF.alarm",
192                     valueProviderCallback: _ => alarmFlag ? alarmPolarity.Value : !alarmPolarity.Value)
193                 .WithEnumField<WordRegister, ConversionRate>(6, 2, out conversionRate, name: "CONF.conv_rate",
194                     changeCallback: (_, __) => UpdateConversionFrequency())
195                 .WithTaggedFlag("CONF.sleep_mode", 8)
196                 .WithFlag(9, out interruptMode, name: "CONF.interrupt_mode",
197                     changeCallback: (_, value) => consecutiveFaults = 0)
198                 .WithFlag(10, out alarmPolarity, name: "CONF.polarity")
199                 .WithValueField(11, 2, name: "CONF.consecutive_faults",
200                     valueProviderCallback: _ => consecutiveFaultsThreshold - 1,
201                     writeCallback: (_, value) =>
202                     {
203                         consecutiveFaultsThreshold = (uint)value + 1;
204                         consecutiveFaults = 0;
205                     })
206                 .WithReservedBits(13, 2)
207                 .WithTaggedFlag("CONF.single_shot", 15)
208             ;
209 
210             Registers.TemperatureLow.Define(this)
211                 .WithValueField(0, 16, name: "TLOW.tlow",
212                     valueProviderCallback: _ => (uint)temperatureLowThreshold,
213                     writeCallback: (_, value) =>
214                     {
215                         temperatureLowThreshold = (short)value;
216                         consecutiveFaults = 0;
217                         alarmFlag = false;
218                     })
219             ;
220 
221             Registers.TemperatureHigh.Define(this)
222                 .WithValueField(0, 16, name: "THIGH.thigh",
223                     valueProviderCallback: _ => (uint)temperatureHighThreshold,
224                     writeCallback: (_, value) =>
225                     {
226                         temperatureHighThreshold = (short)value;
227                         consecutiveFaults = 0;
228                         alarmFlag = false;
229                     })
230             ;
231         }
232 
ConversionRateToFrequencyAndLimit(ConversionRate cr)233         private static Tuple<int, ulong> ConversionRateToFrequencyAndLimit(ConversionRate cr)
234         {
235             switch(cr)
236             {
237                 case ConversionRate.Quarter:
238                     return new Tuple<int, ulong>(1, 4);
239                 case ConversionRate.One:
240                     return new Tuple<int, ulong>(1, 1);
241                 case ConversionRate.Four:
242                     return new Tuple<int, ulong>(4, 1);
243                 case ConversionRate.Eight:
244                     return new Tuple<int, ulong>(8, 1);
245                 default:
246                     throw new Exception("unreachable code");
247             }
248         }
249 
250         private Registers? registerAddress;
251         private States state;
252         private RESDStream<TemperatureSample> resdStream;
253 
254         private IEnumRegisterField<ConversionRate> conversionRate;
255 
256         private IFlagRegisterField alarmPolarity;
257         private IFlagRegisterField interruptMode;
258 
259         private bool alarmFlag;
260         private short currentTemperature;
261         private short temperatureLowThreshold;
262         private short temperatureHighThreshold;
263         private uint consecutiveFaultsThreshold;
264 
265         private uint consecutiveFaults;
266 
267         private const short Resolution = 0x80;
268         private const short DefaultLowThreshold = 70 * Resolution;
269         private const short DefaultHighThreshold = 80 * Resolution;
270         private const decimal MaximumTemperature = (decimal)short.MaxValue / (decimal)Resolution;
271         private const decimal MinimumTemperature = (decimal)short.MinValue / (decimal)Resolution;
272 
273         private readonly IMachine machine;
274         private readonly LimitTimer conversionTimer;
275 
276         private enum States
277         {
278             Idle,
279             Write,
280             Read,
281         }
282 
283         private enum ConversionRate
284         {
285             Quarter,
286             One,
287             Four,
288             Eight,
289         }
290 
291         private enum Registers : byte
292         {
293             Temperature = 0x00,
294             Configuration = 0x01,
295             TemperatureLow = 0x02,
296             TemperatureHigh = 0x03,
297         }
298     }
299 }
300