1 //
2 // Copyright (c) 2010-2022 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.Core;
10 using Antmicro.Renode.Core.Structure;
11 using Antmicro.Renode.Core.Structure.Registers;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Utilities;
14 
15 namespace Antmicro.Renode.Peripherals.I2C
16 {
17     public class LC709205F : II2CPeripheral, IProvidesRegisterCollection<WordRegisterCollection>
18     {
LC709205F(IMachine machine)19         public LC709205F(IMachine machine)
20         {
21             this.machine = machine;
22             RegistersCollection = new WordRegisterCollection(this);
23             DefineRegisters();
24         }
25 
Write(byte[] data)26         public void Write(byte[] data)
27         {
28             if(data.Length == 0)
29             {
30                 this.Log(LogLevel.Warning, "Unexpected write with no data");
31                 return;
32             }
33 
34             var offset = registerAddress == null ? 1 : 0;
35             registerAddress = (Registers)data[0];
36             if(data.Length > offset)
37             {
38                 foreach(var item in data.Skip(offset).Select((value, index) => new { index, value }))
39                 {
40                     WriteWithCRC(registerAddress.Value, item.index, item.value);
41                 }
42             }
43         }
44 
Read(int count = 1)45         public byte[] Read(int count = 1)
46         {
47             if(!registerAddress.HasValue)
48             {
49                 this.Log(LogLevel.Error, "Trying to read without setting address");
50                 return new byte[] {};
51             }
52 
53             var result = new byte[count];
54             for(var i = 0; i < count; ++i)
55             {
56                 result[i] = ReadWithCRC(registerAddress.Value, i);
57             }
58             return result;
59         }
60 
FinishTransmission()61         public void FinishTransmission()
62         {
63             registerAddress = null;
64         }
65 
Reset()66         public void Reset()
67         {
68             RegistersCollection.Reset();
69             registerAddress = null;
70         }
71 
72         public uint TimeToEmpty { get; set; }
73         public uint TimeToFull { get; set; }
74         public uint CellVoltage { get; set; }
75         public uint CycleCount { get; set; }
76 
77         public decimal CellTemperature
78         {
79             get => (cellTemperature * TemperatureSensitivity) - CelciusToKelvin;
80             set => cellTemperature = (uint)((value + CelciusToKelvin) / TemperatureSensitivity).Clamp(0m, uint.MaxValue);
81         }
82 
83         public decimal AmbientTemperature
84         {
85             get => (ambientTemperature * TemperatureSensitivity) - CelciusToKelvin;
86             set => ambientTemperature = (uint)((value + CelciusToKelvin) / TemperatureSensitivity).Clamp(0m, uint.MaxValue);
87         }
88 
89         public decimal RelativeStateOfCharge { get; set; }
90 
91         public decimal FullChargeCapacity
92         {
93             get => fullChargeCapacity * CapacitySensitivity;
94             set => fullChargeCapacity = (uint)(value / CapacitySensitivity).Clamp(0, uint.MaxValue);
95         }
96 
97         public decimal DesignCapacity
98         {
99             get => designCapacity * CapacitySensitivity;
100             set => designCapacity = (uint)(value / CapacitySensitivity).Clamp(0, uint.MaxValue);
101         }
102 
103         public decimal RemainingCapacity
104         {
105             get => remainingCapacity * CapacitySensitivity;
106             set => remainingCapacity = (uint)(value / CapacitySensitivity).Clamp(0, uint.MaxValue);
107         }
108 
109         public WordRegisterCollection RegistersCollection { get; }
110 
ReadWithCRC(Registers register, int offset)111         private byte ReadWithCRC(Registers register, int offset)
112         {
113             var addressOffset = offset / 3;
114             var result = RegistersCollection.Read((byte)((int)register + addressOffset));
115             var step = (TransactionStep)(offset % 3);
116             switch(step)
117             {
118                 // First two bytes are the actual value
119                 case TransactionStep.LSB:
120                 case TransactionStep.MSB:
121                     return step == TransactionStep.MSB ? (byte)(result >> 8) : (byte)result;
122                 // Third byte is CRC of the value
123                 case TransactionStep.CRC:
124                     var crc = CalculateCrc8(new byte[] {(byte)(SlaveAddress << 1), (byte)register, (byte)((SlaveAddress << 1) + 1), (byte)result, (byte)(result >> 8)});
125                     return (byte)crc;
126                 default:
127                     throw new Exception("unreachable state");
128             }
129         }
130 
WriteWithCRC(Registers register, int offset, byte value)131         private void WriteWithCRC(Registers register, int offset, byte value)
132         {
133             var step = (TransactionStep)(offset % 3);
134             if(step == TransactionStep.CRC)
135             {
136                 // We are deliberately omitting checking CRC, as the only possibility for
137                 // it to be invalid, is when software generate wrong checksum for written data.
138                 return;
139             }
140             var addressOffset = offset / 3;
141             var realAddress = (byte)((int)register + addressOffset);
142             var underlyingValue = RegistersCollection.Read(realAddress);
143             if(step == TransactionStep.MSB)
144             {
145                 underlyingValue &= 0x00FF;
146                 underlyingValue |= (ushort)((short)value << 8);
147             }
148             else
149             {
150                 underlyingValue &= 0xFF00;
151                 underlyingValue |= (ushort)value;
152             }
153             RegistersCollection.Write(realAddress, underlyingValue);
154         }
155 
CalculateCrc8(byte[] data)156         private byte CalculateCrc8(byte[] data)
157         {
158             var result = 0x00;
159             foreach(var b in data)
160             {
161                 result = (byte)(result ^ b);
162                 for(var i = 0; i < 8; ++i)
163                 {
164                     if((result & 0x80) != 0x00)
165                     {
166                         // Polynomial x^8+x^2+x+1
167                         result = (byte)((result << 1) ^ 0x07);
168                     }
169                     else
170                     {
171                         result = (byte)(result << 1);
172                     }
173                 }
174             }
175             return (byte)result;
176         }
177 
DefineRegisters()178         private void DefineRegisters()
179         {
180             Registers.TimeToEmpty.Define(this)
181                 .WithValueField(0, 16, FieldMode.Read, name: "TimeToEmpty",
182                     valueProviderCallback: _ => TimeToEmpty)
183             ;
184 
185             Registers.TimeToFull.Define(this)
186                 .WithValueField(0, 16, FieldMode.Read, name: "TimeToFull",
187                     valueProviderCallback: _ => TimeToFull)
188             ;
189 
190             Registers.CellTemperature.Define(this)
191                 .WithValueField(0, 16, FieldMode.Read, name: "CellTemperature",
192                     valueProviderCallback: _ => cellTemperature)
193             ;
194 
195             Registers.CellVoltage.Define(this)
196                 .WithValueField(0, 16, FieldMode.Read, name: "CellVoltage",
197                     valueProviderCallback: _ => CellVoltage)
198             ;
199 
200             Registers.RelativeStateOfCharge.Define(this)
201                 .WithValueField(0, 16, FieldMode.Read, name: "RSOC",
202                     valueProviderCallback: _ => (uint)RelativeStateOfCharge.Clamp(0, 100))
203             ;
204 
205             Registers.IndicatorToEmpty.Define(this)
206                 .WithValueField(0, 16, FieldMode.Read, name: "ITE",
207                     valueProviderCallback: _ => (uint)(RelativeStateOfCharge * 10.0m).Clamp(0, 1000))
208             ;
209 
210             Registers.FullChargeCapacity.Define(this)
211                 .WithValueField(0, 16, FieldMode.Read, name: "FullChargeCapacity",
212                     valueProviderCallback: _ => fullChargeCapacity)
213             ;
214 
215             Registers.ICPowerMode.Define(this, 0x01)
216                 .WithValueField(0, 16, name: "ICPowerMode")
217             ;
218 
219             Registers.CycleCount.Define(this)
220                 .WithValueField(0, 16, FieldMode.Read, name: "CycleCount",
221                     valueProviderCallback: _ => CycleCount)
222             ;
223 
224             Registers.DesignCapacity.Define(this)
225                 .WithValueField(0, 16, FieldMode.Read, name: "DesignCapacity",
226                     valueProviderCallback: _ => designCapacity)
227             ;
228 
229             Registers.NumberOfTheParameters.Define(this, 0x1001)
230                 .WithTag("NumberOfTheParameters", 0, 16)
231             ;
232 
233             Registers.AmbientTemperature.Define(this)
234                 .WithValueField(0, 16, FieldMode.Read, name: "AmbientTemperature",
235                     valueProviderCallback: _ => ambientTemperature)
236             ;
237 
238             Registers.RemainingCapacity.Define(this)
239                 .WithValueField(0, 16, FieldMode.Read, name: "RemainingCapacity",
240                     valueProviderCallback: _ => remainingCapacity)
241             ;
242         }
243 
244         private int SlaveAddress
245         {
246             get
247             {
248                 if(!slaveAddress.HasValue)
249                 {
250                     var parentPeripheral = machine.GetParentPeripherals(this).FirstOrDefault();
251                     if(parentPeripheral == null)
252                     {
253                         this.Log(LogLevel.Warning, "Peripheral hasn't been connected to any I2C controller");
254                         return DefaultSlaveAddress;
255                     }
256                     var registrationPoint = machine.GetPeripheralRegistrationPoints(parentPeripheral, this).FirstOrDefault();
257                     if(registrationPoint == null)
258                     {
259                         throw new Exception("could not find registration point for this peripheral");
260                     }
261 
262                     if(registrationPoint is NumberRegistrationPoint<int> numberRegistrationPoint)
263                     {
264                         slaveAddress = numberRegistrationPoint.Address;
265                     }
266                     else if(registrationPoint is TypedNumberRegistrationPoint<int> typedNumberRegistrationPoint)
267                     {
268                         slaveAddress = typedNumberRegistrationPoint.Address;
269                     }
270                     else
271                     {
272                         throw new Exception($"SlaveAddress is unimplemented for {registrationPoint.GetType().ToString()}");
273                     }
274                 }
275                 return slaveAddress.Value;
276             }
277         }
278 
279         private const decimal CelciusToKelvin = 273.15m;
280         private const decimal CapacitySensitivity = 0.1m;
281         private const decimal TemperatureSensitivity = 0.1m;
282         private const int DefaultSlaveAddress = 0x0B;
283 
284         private readonly IMachine machine;
285 
286         private Registers? registerAddress;
287 
288         private int? slaveAddress;
289 
290         private uint fullChargeCapacity;
291         private uint designCapacity;
292         private uint remainingCapacity;
293         private uint cellTemperature;
294         private uint ambientTemperature;
295 
296         private enum TransactionStep
297         {
298             LSB,
299             MSB,
300             CRC
301         }
302 
303         private enum Registers
304         {
305             Prohibited1 = 0x00,
306             Prohibited2 = 0x01,
307             // Reserved (0x02)
308             TimeToEmpty = 0x03,
309             BeforeRelativeStateOfCharge = 0x04,
310             TimeToFull = 0x05,
311             TSense1TherimistorB = 0x06,
312             InitialRelativeStateOfCharge = 0x07,
313             CellTemperature = 0x08,
314             CellVoltage = 0x09,
315             CurrentDirection = 0x0A,
316             AdjustmentPackApplication = 0x0B,
317             AdjustmentPackThermistor = 0x0C,
318             RelativeStateOfCharge = 0x0D,
319             TSense2ThermistorB = 0x0E,
320             IndicatorToEmpty = 0x0F,
321             FullChargeCapacity = 0x10,
322             ICVersion = 0x11,
323             ChangeOfTheParameter = 0x12,
324             AlarmLowRelativeStateOfCharge = 0x13,
325             AlarmLowCellVoltage = 0x14,
326             ICPowerMode = 0x15,
327             StatusBit = 0x16,
328             CycleCount = 0x17,
329             DesignCapacity = 0x18,
330             BatteryStatus = 0x19,
331             NumberOfTheParameters = 0x1A,
332             // Reserved (0x1B)
333             TerminationCurrentRate = 0x1C,
334             EmptyCellVoltage = 0x1D,
335             ITEOffset = 0x1E,
336             AlarmHighCellVoltage = 0x1F,
337             AlarmLowTemperature = 0x20,
338             AlarmHighTemperature = 0x21,
339             AlarmOverChargingCurrent = 0x22,
340             AlarmOverDischargingCurrent = 0x23,
341             TotalRuntimeLow = 0x24,
342             TotalRuntimeHigh = 0x25,
343             AccumulatedTemperatureLow = 0x26,
344             AccumulatedTemperatureHigh = 0x27,
345             AccumulatedRelativeStateOfChargeLow = 0x28,
346             AccumulatedRelativeStateOfChargeHigh = 0x29,
347             MaximumCellVoltage = 0x2A,
348             MinimumCellVoltage = 0x2B,
349             MaximumCellTemperature = 0x2C,
350             MinimumCellTemperature = 0x2D,
351             MaximumCellCurrent = 0x2E,
352             MinimumCellCurrent = 0x2F,
353             AmbientTemperature = 0x30,
354             SenseResistance = 0x31,
355             StateOfHealth = 0x32,
356             DynamicCellCurrent = 0x33,
357             AverageCellCurrent = 0x34,
358             RemainingCapacity = 0x35,
359             UserIDLow = 0x36,
360             UserIDHigh = 0x37
361         }
362     }
363 }
364