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.Linq;
8 using System.Collections.Generic;
9 using Antmicro.Renode.Peripherals.Sensor;
10 using Antmicro.Renode.Peripherals.I2C;
11 using Antmicro.Renode.Peripherals.Timers;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Core;
14 using Antmicro.Renode.Core.Structure.Registers;
15 using Antmicro.Renode.Time;
16 using Antmicro.Renode.Utilities;
17 using Antmicro.Renode.Utilities.RESD;
18 
19 namespace Antmicro.Renode.Peripherals.Sensors
20 {
21     public class MAX30208 : II2CPeripheral, IProvidesRegisterCollection<ByteRegisterCollection>, ITemperatureSensor, IGPIOReceiver
22     {
MAX30208(IMachine machine)23         public MAX30208(IMachine machine)
24         {
25             RegistersCollection = new ByteRegisterCollection(this);
26             this.machine = machine;
27 
28             samplesFifo = new Queue<TemperatureSampleWrapper>();
29 
30             GPIO0 = new GPIO();
31             GPIO1 = new GPIO();
32 
33             DefineRegisters();
34             UpdateInterrupts();
35         }
36 
FeedSamplesFromRESD(ReadFilePath filePath, uint channelId = 0, RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)37         public void FeedSamplesFromRESD(ReadFilePath filePath, uint channelId = 0,
38             RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)
39         {
40             resdStream = this.CreateRESDStream<TemperatureSample>(filePath, channelId, sampleOffsetType, sampleOffsetTime);
41         }
42 
Write(byte[] data)43         public void Write(byte[] data)
44         {
45             if(data.Length == 0)
46             {
47                 this.Log(LogLevel.Error, "Unexpected write with no data");
48                 return;
49             }
50 
51             registerAddress = (Registers)data[0];
52             if(data.Length > 1)
53             {
54                 foreach(var value in data.Skip(1))
55                 {
56                     RegistersCollection.Write((byte)registerAddress, value);
57                 }
58             }
59         }
60 
Read(int count)61         public byte[] Read(int count)
62         {
63             if(!registerAddress.HasValue)
64             {
65                 this.Log(LogLevel.Error, "Trying to read without setting address");
66                 return new byte[] {};
67             }
68 
69             var result = new byte[count];
70             for(var i = 0; i < count; ++i)
71             {
72                 result[i] = RegistersCollection.Read((byte)((int)registerAddress));
73             }
74             return result;
75         }
76 
OnGPIO(int pin, bool value)77         public void OnGPIO(int pin, bool value)
78         {
79             if(pin != StartConversionPin)
80             {
81                 this.Log(LogLevel.Warning, "Unexpected input on pin {0}; only pin 1 (GPIO1) is supported", pin);
82                 return;
83             }
84 
85             // Interrupt1 (pin=1) is used as input to start temperature conversion
86             if(gpio1Mode.Value == GPIOMode.IntConv && !value)
87             {
88                 MeasureTemperature();
89             }
90         }
91 
FinishTransmission()92         public void FinishTransmission()
93         {
94             registerAddress = null;
95         }
96 
Reset()97         public void Reset()
98         {
99             RegistersCollection.Reset();
100             registerAddress = null;
101 
102             currentSampleEnumerator = null;
103             alarmTemperatureLow = 0;
104             alarmTemperatureHigh = 0;
105             samplesFifo.Clear();
106         }
107 
108         public ByteRegisterCollection RegistersCollection { get; }
109 
110         public decimal Temperature
111         {
112             get
113             {
114                 var currentSample = CurrentSample;
115                 return currentSample != null ? currentSample.Temperature / 1000m : defaultTemperature;
116             }
117             set => defaultTemperature = value;
118         }
119 
120         public GPIO GPIO0 { get; }
121         public GPIO GPIO1 { get; }
122 
UpdateInterrupts()123         private void UpdateInterrupts()
124         {
125             if(gpio0Mode.Value != GPIOMode.IntConv)
126             {
127                 // If GPIO0 is not set as Interrupt, ignore
128                 return;
129             }
130 
131             var interrupt = false;
132             interrupt |= interruptTemperatureReady.Value && statusTemperatureReady.Value;
133             interrupt |= interruptTemperatureHigh.Value && statusTemperatureHigh.Value;
134             interrupt |= interruptTemperatureLow.Value && statusTemperatureLow.Value;
135             interrupt |= interruptFifoThreshold.Value && statusFifoThreshold.Value;
136 
137             GPIO0.Set(!interrupt);
138         }
139 
UpdateGPIOOutput()140         private void UpdateGPIOOutput()
141         {
142             var gpio0 = gpio0Mode.Value == GPIOMode.Output && gpio0Level.Value;
143             var gpio1 = gpio1Mode.Value == GPIOMode.Output && gpio1Level.Value;
144 
145             if(gpio0Mode.Value != GPIOMode.IntConv)
146             {
147                 GPIO0.Set(gpio0);
148             }
149 
150             if(gpio1Mode.Value != GPIOMode.IntConv)
151             {
152                 GPIO1.Set(gpio1);
153             }
154         }
155 
MeasureTemperature()156         private void MeasureTemperature()
157         {
158             if(samplesFifo.Count == FIFOSize)
159             {
160                 if(!fifoRollover.Value)
161                 {
162                     // If FIFO Rollover is disabled, just ignore sample
163                     return;
164                 }
165                 // Otherwise remove oldest item
166                 samplesFifo.Dequeue();
167             }
168 
169             // Temperature in RESD is in milli-Celsius, so scale defaultTemperature accordingly
170             var currentTemperature = CurrentSample?.Temperature ?? defaultTemperature / 0.001m;
171             // Convert from 0.001C to 0.005C
172             var convertedTemperature = (currentTemperature / 5).Clamp(short.MinValue, short.MaxValue);
173 
174             samplesFifo.Enqueue(new TemperatureSampleWrapper((short)convertedTemperature));
175 
176             if(alarmTemperatureHigh >= convertedTemperature)
177             {
178                 statusTemperatureHigh.Value = true;
179             }
180 
181             if(alarmTemperatureLow <= convertedTemperature)
182             {
183                 statusTemperatureLow.Value = true;
184             }
185 
186             if(fifoFullAssertOnThreshold.Value && (int)(fifoFullThreshold.Value + 1) == samplesFifo.Count)
187             {
188                 statusFifoThreshold.Value = true;
189             }
190             else
191             {
192                 statusFifoThreshold.Value = (int)fifoFullThreshold.Value >= samplesFifo.Count;
193             }
194 
195             statusTemperatureReady.Value = true;
196             UpdateInterrupts();
197         }
198 
DequeueSampleByte()199         private byte DequeueSampleByte()
200         {
201             var output = default(byte);
202             if(currentSampleEnumerator == null || !currentSampleEnumerator.TryGetNext(out output))
203             {
204                 if(!samplesFifo.TryDequeue(out var sample))
205                 {
206                     sample = new TemperatureSampleWrapper((short)(defaultTemperature / Sensitivity));
207                 }
208                 currentSampleEnumerator = sample.Enumerator;
209                 currentSampleEnumerator.TryGetNext(out output);
210             }
211 
212             statusTemperatureReady.Value = false;
213             if(clearFlagsOnRead.Value)
214             {
215                 statusFifoThreshold.Value = false;
216             }
217 
218             UpdateInterrupts();
219             return output;
220         }
221 
DefineRegisters()222         private void DefineRegisters()
223         {
224             Registers.Status.Define(this)
225                 .WithFlag(0, out statusTemperatureReady, FieldMode.ReadToClear, name: "STATUS.temp_rdy")
226                 .WithFlag(1, out statusTemperatureHigh, FieldMode.ReadToClear, name: "STATUS.temp_hi")
227                 .WithFlag(2, out statusTemperatureLow, FieldMode.ReadToClear, name: "STATUS.temp_lo")
228                 .WithReservedBits(3, 4)
229                 .WithFlag(7, out statusFifoThreshold, name: "STATUS.a_full")
230                 .WithChangeCallback((_, __) => UpdateInterrupts())
231             ;
232 
233             Registers.InterruptEnable.Define(this)
234                 .WithFlag(0, out interruptTemperatureReady, name: "INT_EN.temp_rdy")
235                 .WithFlag(1, out interruptTemperatureHigh, name: "INT_EN.temp_hi")
236                 .WithFlag(2, out interruptTemperatureLow, name: "INT_EN.temp_lo")
237                 .WithReservedBits(3, 4)
238                 .WithFlag(7, out interruptFifoThreshold, name: "INT_EN.a_full")
239                 .WithChangeCallback((_, __) => UpdateInterrupts())
240             ;
241 
242             Registers.FIFOWritePointer.Define(this)
243                 .WithTag("FIFO_WR_PTR.fifo_wr_ptr", 0, 5)
244                 .WithReservedBits(5, 3)
245             ;
246 
247             Registers.FIFOReadPointer.Define(this)
248                 .WithTag("FIFO_RD_PTR.fifo_rd_ptr", 0, 5)
249                 .WithReservedBits(5, 3)
250             ;
251 
252             Registers.FIFOOverflowCounter.Define(this)
253                 .WithTag("FIFO_OVF_COUNTER.ovf_counter", 0, 5)
254                 .WithReservedBits(5, 3)
255             ;
256 
257             Registers.FIFODataCounter.Define(this)
258                 .WithValueField(0, 6, FieldMode.Read, name: "FIFO_DATA_COUNT.fifo_data_count",
259                     valueProviderCallback: _ => (uint)samplesFifo.Count)
260                 .WithReservedBits(6, 2)
261             ;
262 
263             Registers.FIFOData.Define(this)
264                 .WithValueField(0, 8, FieldMode.Read, name: "FIFO_DATA.fifo_data",
265                     valueProviderCallback: _ => DequeueSampleByte())
266             ;
267 
268             Registers.FIFOConfiguration1.Define(this, 0x0F)
269                 .WithValueField(0, 5, out fifoFullThreshold, name: "FIFO_CONF1.fifo_a_full")
270                 .WithReservedBits(5, 3)
271                 .WithChangeCallback((_, __) => UpdateInterrupts())
272             ;
273 
274             Registers.FIFOConfiguration2.Define(this)
275                 .WithReservedBits(0, 1)
276                 .WithFlag(1, out fifoRollover, name: "FIFO_CONF2.fifo_ro")
277                 .WithFlag(2, out fifoFullAssertOnThreshold, name: "FIFO_CONF2.a_full_type",
278                     changeCallback: (_, value) =>
279                     {
280                         // If we disable 'on-threshold' trigger, update STATUS.a_full
281                         // otherwise if we enable it, don't change anything.
282                         if(!value)
283                         {
284                             statusFifoThreshold.Value |= (int)fifoFullThreshold.Value >= samplesFifo.Count;
285                         }
286                     })
287                 .WithFlag(3, out clearFlagsOnRead, name: "FIFO_CONF2.fifo_stat_clr")
288                 .WithFlag(4, FieldMode.WriteOneToClear, name: "FIFO_CONF2.flush_fifo",
289                     writeCallback: (_, value) => { if(value) samplesFifo.Clear(); })
290                 .WithReservedBits(5, 3)
291             ;
292 
293             Registers.SystemControl.Define(this)
294                 .WithFlag(0, FieldMode.Write, name: "SYSTEM_CTRL.reset",
295                     writeCallback: (_, value) => { if(value) Reset(); })
296             ;
297 
298             Registers.AlarmHighMSB.Define(this)
299                 .WithValueField(0, 8, name: "ALARM_HI_MSB.alarm_hi_msb",
300                     valueProviderCallback: _ => alarmTemperatureHigh >> 8,
301                     writeCallback: (_, value) => alarmTemperatureHigh = (alarmTemperatureHigh & 0xFF) | (uint)(value << 8))
302             ;
303 
304             Registers.AlarmHighLSB.Define(this)
305                 .WithValueField(0, 8, name: "ALARM_HI_LSB.alarm_hi_lsb",
306                     valueProviderCallback: _ => alarmTemperatureHigh,
307                     writeCallback: (_, value) => alarmTemperatureHigh = (alarmTemperatureHigh & 0xFF00) | (uint)value)
308             ;
309 
310             Registers.AlarmLowMSB.Define(this)
311                 .WithValueField(0, 8, name: "ALARM_LO_MSB.alarm_lo_msb",
312                     valueProviderCallback: _ => alarmTemperatureLow >> 8,
313                     writeCallback: (_, value) => alarmTemperatureLow = (alarmTemperatureLow & 0xFF) | (uint)(value << 8))
314             ;
315 
316             Registers.AlarmLowLSB.Define(this)
317                 .WithValueField(0, 8, name: "ALARM_LO_LSB.alarm_lo_lsb",
318                     writeCallback: (_, value) => alarmTemperatureLow = (alarmTemperatureLow & 0xFF00) | (uint)value)
319             ;
320 
321             Registers.TempSensorSetup.Define(this, 0xC0)
322                 .WithFlag(0, FieldMode.Read | FieldMode.WriteOneToClear, name: "TEMP_SENS_SETUP.convert_t",
323                     writeCallback: (_, value) =>
324                     {
325                         if(value)
326                         {
327 			    if(machine.SystemBus.TryGetCurrentCPU(out var cpu))
328                             {
329                                 cpu.SyncTime();
330                             }
331                             var timeSource = machine.LocalTimeSource;
332                             var now = timeSource.ElapsedVirtualTime;
333                             // about 15ms (typ value)
334                             var measurementFinishTime = now + TimeInterval.FromMilliseconds(15);
335                             var measurementFinishTimeStamp = new TimeStamp(measurementFinishTime, timeSource.Domain);
336                             timeSource.ExecuteInSyncedState(__ =>
337                             {
338                                 MeasureTemperature();
339                             }, measurementFinishTimeStamp);
340                         }
341                     })
342                 .WithReservedBits(1, 5)
343                 .WithTag("TEMP_SENS_SETUP.rfu", 6, 2)
344             ;
345 
346             Registers.GPIOSetup.Define(this, 0x82)
347                 .WithEnumField<ByteRegister, GPIOMode>(0, 2, out gpio0Mode, name: "GPIO_SETUP.gpio0_mode")
348                 .WithReservedBits(2, 4)
349                 .WithEnumField<ByteRegister, GPIOMode>(6, 2, out gpio1Mode, name: "GPIO_SETUP.gpio1_mode")
350                 .WithChangeCallback((_, __) =>
351                 {
352                     UpdateInterrupts();
353                     UpdateGPIOOutput();
354                 })
355             ;
356 
357             Registers.GPIOControl.Define(this)
358                 .WithFlag(0, out gpio0Level, name: "GPIO_CTRL.gpio0_ll")
359                 .WithReservedBits(1, 2)
360                 .WithFlag(3, out gpio1Level, name: "GPIO_CTRL.gpio1_ll")
361                 .WithReservedBits(4, 4)
362                 .WithChangeCallback((_, __) =>
363                 {
364                     UpdateGPIOOutput();
365                 })
366             ;
367 
368             Registers.PartIdentifier.Define(this, 0x30)
369                 .WithTag("PART_IDNET.data", 0, 8)
370             ;
371         }
372 
373         private TemperatureSample CurrentSample
374         {
375             get
376             {
377                 if(resdStream == null)
378                 {
379                     return null;
380                 }
381                 return resdStream.TryGetCurrentSample(this, out var sample, out _) == RESDStreamStatus.OK ? sample : null;
382             }
383         }
384 
385         private decimal defaultTemperature;
386 
387         private Registers? registerAddress;
388 
389         private uint alarmTemperatureLow;
390         private uint alarmTemperatureHigh;
391 
392         private IEnumerator<byte> currentSampleEnumerator;
393         private RESDStream<TemperatureSample> resdStream;
394 
395         private IFlagRegisterField statusTemperatureReady;
396         private IFlagRegisterField statusTemperatureHigh;
397         private IFlagRegisterField statusTemperatureLow;
398         private IFlagRegisterField statusFifoThreshold;
399 
400         private IFlagRegisterField interruptTemperatureReady;
401         private IFlagRegisterField interruptTemperatureHigh;
402         private IFlagRegisterField interruptTemperatureLow;
403         private IFlagRegisterField interruptFifoThreshold;
404 
405         private IFlagRegisterField fifoRollover;
406         private IValueRegisterField fifoFullThreshold;
407         private IFlagRegisterField clearFlagsOnRead;
408         private IFlagRegisterField fifoFullAssertOnThreshold;
409 
410         private IEnumRegisterField<GPIOMode> gpio0Mode;
411         private IEnumRegisterField<GPIOMode> gpio1Mode;
412 
413         private IFlagRegisterField gpio0Level;
414         private IFlagRegisterField gpio1Level;
415 
416         private readonly IMachine machine;
417         private readonly Queue<TemperatureSampleWrapper> samplesFifo;
418 
419         private const uint FIFOSize = 32;
420         private const decimal Sensitivity = 0.005m;
421         private const int StartConversionPin = 1;
422 
423         private struct TemperatureSampleWrapper
424         {
TemperatureSampleWrapperAntmicro.Renode.Peripherals.Sensors.MAX30208.TemperatureSampleWrapper425             public TemperatureSampleWrapper(short data)
426             {
427                 Value = data;
428             }
429 
430             public short Value { get; }
431             public byte Byte1 => (byte)(Value >> 8);
432             public byte Byte2 => (byte)Value;
433             public byte[] Bytes => new byte[] { Byte1, Byte2 };
434             public IEnumerator<byte> Enumerator => Bytes.OfType<byte>().GetEnumerator();
435         }
436 
437         private enum GPIOMode
438         {
439             Input,
440             Output,
441             PulldownInput,
442             IntConv,
443         }
444 
445         private enum Registers : byte
446         {
447             Status = 0x00,
448             InterruptEnable = 0x01,
449 
450             FIFOWritePointer = 0x04,
451             FIFOReadPointer = 0x05,
452             FIFOOverflowCounter = 0x06,
453             FIFODataCounter = 0x07,
454             FIFOData = 0x08,
455             FIFOConfiguration1 = 0x09,
456             FIFOConfiguration2 = 0xA,
457 
458             SystemControl = 0x0C,
459 
460             AlarmHighMSB = 0x10,
461             AlarmHighLSB = 0x11,
462             AlarmLowMSB = 0x12,
463             AlarmLowLSB = 0x13,
464             TempSensorSetup = 0x14,
465 
466             GPIOSetup = 0x20,
467             GPIOControl = 0x21,
468 
469             PartID1 = 0x31,
470             PartID2 = 0x32,
471             PartID3 = 0x33,
472             PartID4 = 0x34,
473             PartID5 = 0x35,
474             PartID6 = 0x36,
475             PartIdentifier = 0xFF,
476         }
477     }
478 }
479