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 System.Collections.Generic;
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 
17 namespace Antmicro.Renode.Peripherals.Sensors
18 {
19     public class SI7210 : II2CPeripheral, IProvidesRegisterCollection<ByteRegisterCollection>, ITemperatureSensor
20     {
SI7210(IMachine machine, byte offset = 0, byte gain = 0)21         public SI7210(IMachine machine, byte offset = 0, byte gain = 0)
22         {
23             RegistersCollection = new ByteRegisterCollection(this);
24             DefineRegisters();
25 
26             Reset();
27 
28             temperatureOffset = offset;
29             temperatureGain = gain;
30         }
31 
Write(byte[] data)32         public void Write(byte[] data)
33         {
34             if(data.Length == 0)
35             {
36                 this.Log(LogLevel.Warning, "Unexpected write with no data");
37                 return;
38             }
39 
40             registerAddress = (Registers)data[0];
41 
42             if(data.Length > 1)
43             {
44                 foreach(var value in data.Skip(1))
45                 {
46                     RegistersCollection.Write((byte)registerAddress, value);
47                 }
48             }
49         }
50 
Read(int count)51         public byte[] Read(int count)
52         {
53             if(!registerAddress.HasValue)
54             {
55                 this.Log(LogLevel.Warning, "Trying to read without setting address");
56                 return new byte[] { 0 };
57             }
58 
59             var result = new byte[count];
60             for(var i = 0; i < count; ++i)
61             {
62                 result[i] = RegistersCollection.Read((byte)((int)registerAddress + i));
63             }
64 
65             if(autoIncrement.Value)
66             {
67                 registerAddress += 1;
68             }
69             return result;
70         }
71 
FinishTransmission()72         public void FinishTransmission()
73         {
74             registerAddress = null;
75         }
76 
Reset()77         public void Reset()
78         {
79             RegistersCollection.Reset();
80             registerAddress = null;
81             currentTemperature = ZeroCelsiusApproximation;
82         }
83 
84         public ByteRegisterCollection RegistersCollection { get; }
85 
86         public decimal Temperature
87         {
88             get => temperature;
89             set
90             {
91                 if(IsTemperatureOutOfRange(value))
92                 {
93                     return;
94                 }
95                 temperature = value;
96 
97                 var measurement = (ushort)(short)(temperature * TemperatureGainApproximation) << 3;
98                 currentTemperature = ZeroCelsiusApproximation + (ulong)measurement;
99             }
100         }
101 
DefineRegisters()102         private void DefineRegisters()
103         {
104             Registers.ChipID.Define(this)
105                 .WithValueField(0, 8, FieldMode.Read, name: "ChipID",
106                     valueProviderCallback: _ => (uint)ChipID)
107             ;
108 
109             Registers.MeasurementHigh.Define(this)
110                 .WithValueField(0, 7, name: "DSPSIGM",
111                     valueProviderCallback: _ => currentTemperature >> 8)
112                 .WithTaggedFlag("fresh", 7)
113             ;
114 
115             Registers.MeasurementLow.Define(this)
116                 .WithValueField(0, 8, name: "DSPSIGL",
117                     valueProviderCallback: _ => currentTemperature)
118             ;
119 
120             Registers.EnableTemperatureReadout.Define(this)
121                 .WithValueField(0, 3, out enableTemperatureReadout, name: "DSPSIGSEL")
122                 .WithReservedBits(3, 5)
123             ;
124 
125             Registers.PowerControl.Define(this)
126                 .WithTaggedFlag("sleep", 0)
127                 .WithTaggedFlag("stop", 1)
128                 .WithTaggedFlag("oneburst", 2)
129                 .WithTaggedFlag("usestore", 3)
130                 .WithReservedBits(4, 3)
131                 .WithTaggedFlag("meas", 7)
132             ;
133 
134             Registers.AutoIncrement.Define(this)
135                 .WithFlag(0, out autoIncrement, name: "ARAUTOINC")
136                 .WithReservedBits(1, 7)
137             ;
138 
139             Registers.OTPAddress.Define(this)
140                 .WithValueField(0, 8, name: "OTP_Addr",
141                     writeCallback: (_, value) => otpData = HandleOTP_ReadRequest((byte)value))
142             ;
143 
144             Registers.OTPData.Define(this)
145                 .WithValueField(0, 8, name: "OTP_Data",
146                     valueProviderCallback: _ =>
147                     {
148                         return (uint)otpData;
149                     },
150                     writeCallback: (_, value) => otpData = (byte)value)
151             ;
152 
153             Registers.OTPControl.Define(this)
154                 .WithTag("otp_busy", 0, 1)
155                 .WithFlag(1, out otpReadEnable, name: "OTP_Control.otp_read_en")
156                 .WithTag("reserved", 2, 6)
157             ;
158         }
159 
HandleOTP_ReadRequest(byte offset)160         private byte HandleOTP_ReadRequest(byte offset)
161         {
162             if(!otpReadEnable.Value)
163             {
164                 return 0;
165             }
166             switch((OTPRegisters)offset)
167             {
168                 case OTPRegisters.PartBase:
169                     // Base part number dropping the “Si72”, for example 01 for Si7201
170                     return PartNumber;
171                 case OTPRegisters.TempOffset:
172                     // Temp sensor offset adjustment
173                     return temperatureOffset;
174                 case OTPRegisters.TempGain:
175                     // Temp sensor gain adjustment
176                     return temperatureGain;
177                 default:
178                     this.Log(LogLevel.Noisy, "Tried to read OTP_DATA offset: 0x{0:X}, returning 0", offset);
179                     return 0;
180             }
181         }
182 
IsTemperatureOutOfRange(decimal temperature)183         private bool IsTemperatureOutOfRange(decimal temperature)
184         {
185             if (temperature < MinTemperature || temperature > MaxTemperature)
186             {
187                 this.Log(LogLevel.Warning, "Temperature {0} is out of range, use value from the range <{1:F2};{2:F2}>", temperature, MinTemperature, MaxTemperature);
188                 return true;
189             }
190             return false;
191         }
192 
193         private Registers? registerAddress;
194 
195         private decimal temperature;
196 
197         private byte otpData;
198         private IFlagRegisterField otpReadEnable;
199         private IFlagRegisterField autoIncrement;
200         private IValueRegisterField enableTemperatureReadout;
201         private byte temperatureOffset;
202         private byte temperatureGain;
203 
204         private ulong currentTemperature;
205 
206         private const decimal MinTemperature = -65.0m;
207         private const decimal MaxTemperature = 150.0m;
208 
209         private const ushort ChipID = 0x14;
210         private const byte PartNumber = 0x10;
211 
212         private const ulong ZeroCelsiusApproximation = 0x3920;
213         private const decimal TemperatureGainApproximation = 6.667m;
214 
215         private enum Registers : byte
216         {
217             ChipID = 0xC0,
218             MeasurementHigh = 0xC1,
219             MeasurementLow = 0xC2,
220             EnableTemperatureReadout = 0xC3,
221             PowerControl = 0xC4,
222             AutoIncrement = 0xC5,
223             OTPAddress = 0xE1,
224             OTPData = 0xE2,
225             OTPControl = 0xE3,
226             TestFieldGenerator = 0xE4,
227         }
228 
229         private enum OTPRegisters : byte
230         {
231             PartBase = 0x14,
232             TempOffset = 0x1D,
233             TempGain = 0x1E,
234         }
235     }
236 }
237