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