How i2c reads DDR5 SPD

How i2c reads DDR5 SPD?
At present, I have achieved reading DDR3 and DDR4, but I have no clue about DDR5.

As you showed in I2c read DDR4 SPD.

I'm tempted to merge the topics but decided against it because it's now about DDR5. It would however have been useful if you would have posted the code from that topic here.

It's outside my knowledge so can't help.

This code is in the GitHub - 1a2m3/SPD-Reader-Writer: SPD Reader & Writer with Software Write Protection capabilities supporting Arduino and SMBus project, you can refer to the code to get the DDR5 SPD reading method, but my level is limited, most of the code can not be understood, do you have a friend to help you see?

/*
    Arduino based EEPROM SPD reader and writer
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   For overclockers and PC hardware enthusiasts
   Repos:   https://github.com/1a2m3/SPD-Reader-Writer
   Support: https://forums.evga.com/FindPost/3053544
   Donate:  https://paypal.me/mik4rt3m
   PS: DO NOT EDIT THIS FILE UNLESS YOU KNOW WHAT YOU ARE DOING!
   CONFIGURABLE SETTINGS ARE IN "SpdReaderWriterSettings.h" FILE
*/
#include <Wire.h>
#include <EEPROM.h>
#include "SpdReaderWriterSettings.h"  // Settings
#define FW_VER 20231207  // Firmware version number (YYYYMMDD)
#define DDR5 _BV(5)  // Offline mode
#define DDR4 _BV(4)  // VHV control
#define DDR3 _BV(3)  // VHV+SA1 controls
#define MR0  0x00  // Device Type; Most Significant Byte
#define MR1  0x01  // Device Type; Least Significant Byte
#define MR6  0x06  // Device Write Recovery Time Capability
#define MR11 0x0B  // I2C Legacy Mode Device Configuration
#define MR12 0x0C  // Write Protection For NVM Blocks [7:0]
#define MR13 0x0D  // Write Protection for NVM Blocks [15:8]
#define MR14 0x0E  // Device Configuration - Host and Local Interface IO
#define MR18 0x12  // Device Configuration
#define MR20 0x14  // Clear Register MR52 Error Status Command
#define MR48 0x30  // Device Status
#define MR52 0x34  // Hub, Thermal and NVM Error Status
#define PMIC 0b1001 << 3  // PMIC local device type ID
#define SPD5_NO 0x5108  // SPD5 Hub Device
#define SPD5_TS 0x5118  // SPD5 Hub Device w/ Temp Sensor
#define SPA0 0x6C  // Set Page Address 0
#define SPA1 0x6E  // Set Page Address 1
#define RPA  0x6D  // Read Page Address
#define RPS0 0x63  // Read SWP0 status      (offsets   0-127) (DDR4/DDR3/DDR2)
#define RPS1 0x69  // Read SWP1 status      (offsets 128-255) (DDR4)
#define RPS2 0x6B  // Read SWP2 status      (offsets 256-383) (DDR4)
#define RPS3 0x61  // Read SWP3 status      (offsets 384-511) (DDR4)
#define SWP0 0x62  // Set RSWP for block 0  (offsets   0-127) (DDR4/DDR3/DDR2) *
#define SWP1 0x68  // Set RSWP for block 1  (offsets 128-255) (DDR4)
#define SWP2 0x6A  // Set RSWP for block 2  (offsets 256-383) (DDR4)
#define SWP3 0x60  // Set RSWP for block 3  (offsets 384-511) (DDR4)           *
#define CWP  0x66  // Clear RSWP                              (DDR4/DDR3/DDR2) *
#define PWPB 0b0110  // PSWP Device Type Identifier Control Code (bits 7-4) (DDR3/DDR2)
#define DNC 0x00  // "Do not care" byte
#define RESPONSE '&'
#define ALERT    '@'
#define UNKNOWN  '?'
#define A1_MASK 0b11001100  // ScanBus() bitmask response when SA1 is high: 82-83, 86-87
#define SLAVEINC '+'
#define SLAVEDEC '-'
#define CLOCKINC '/'
#define CLOCKDEC '\\'
#define NAMELENGTH 16
char deviceName[NAMELENGTH];
#define DEVICESETTINGS 0x20  // EEPROM location to store device settings
#define CLOCKMODE      0     // Bit position for I2C clock settings
#define FASTMODE       true
#define STDMODE        false
int32_t clock[] = { 100000, 400000 };
uint32_t i2cClock = clock[0];  // Initial I2C clock
uint8_t eepromPageAddress;     // Initial EEPROM page address
uint8_t slaveCountCurrent;     // Current number of slave addresses on I2C bus
uint8_t slaveCountLast;        // Last number of slave addresses on I2C bus
bool i2cClockCurrent;          // Current I2C clock mode
bool i2cClockLast;             // Last I2C clock mode
bool cmdExecuting;             // Indicates an input command is being executed
uint8_t responseBuffer[32];    // Response body buffer
uint8_t responseLength;        // Output response body length and index
enum Command : uint8_t {
  Get     = ((uint8_t)-1), // Gets current value
  Disable = 0,             // Resets variable value to default
  Enable,                  // Modifies variable value
  ReadByte,                // Read byte
  WriteByte,               // Write byte
  WritePage,               // Write page
  WriteTest,               // Write protection test
  Ddr4Detect,              // DDR4 detection
  Ddr5Detect,              // DDR5 detection
  Spd5HubReg,              // Access SPD5 Hub register space
  Size,                    // Get EEPROM size
  ScanBus,                 // Scan I2C bus
  BusClock,                // I2C clock control
  ProbeAddress,            // Probe I2C address
  PinControl,              // Config pin control
  PinReset,                // Reset config pins state to defaults
  Rswp,                    // RSWP operation
  Pswp,                    // PSWP operation
  RswpReport,              // Report current RSWP capabilities
  Version,                 // Get Firmware version
  Test,                    // Device Communication Test
  Name,                    // Name controls
  FactoryReset,            // Restore device settings to default
};
enum pin {
  HV_FEEDBACK = -1, // VHV feedback pin
  HV_SWITCH,        // Pin to toggle VHV on SA0 pin
  SA1_SWITCH,       // Pin to toggle SA1 state
};
typedef struct {
  pin number;
  const int name;
  bool defaultState;
  uint8_t mode;
} pinData;
pinData ConfigPin[] = {
  { HV_SWITCH,   HV_EN,  false, OUTPUT }, // HV control
  { SA1_SWITCH,  SA1_EN, true,  OUTPUT }, // SA1 control
  { HV_FEEDBACK, HV_FB,  false, INPUT  }, // HV feedback
};
size_t pinCount = sizeof(ConfigPin) / sizeof(ConfigPin[0]);
void setup() {
  for (uint8_t i = 0; i < pinCount; i++) {
    pinMode(ConfigPin[i].name, ConfigPin[i].mode);
  }
  resetPins();
  Wire.begin();
  Wire.setWireTimeout(10000, true);
  Wire.setClock(clock[getI2cClockMode()]);
  i2cClockCurrent = clock[getI2cClockMode()];
  i2cClockLast = i2cClockCurrent;
  slaveCountCurrent = getQuantity();
  slaveCountLast = slaveCountCurrent;
  setDdr4PageAddress(0);
  PORT.begin(BAUD_RATE);
  PORT.setTimeout(100);  // Input timeout in ms
  while (!PORT) {}
  #ifndef __AVR__
  PORT.write(UNKNOWN);
  while (true) {}
  #endif
  Respond(true);
  OutputResponse();
}
void loop() {
  resetPinsInternal();
  if (PORT.available()) {
    parseCommand();
  }
  i2cMonitor();
}
void parseCommand() {
  if (!PORT.available()) {
    cmdExecuting = false;
    return;
  }
  cmdExecuting = true;
  switch ((uint8_t)PORT.read()) {
    case Command::ReadByte:
      cmdRead();
      break;
    case Command::WriteByte:
      cmdWriteByte();
      break;
    case Command::WritePage:
      cmdWritePage();
      break;
    case Command::ScanBus:
      cmdScanBus();
      break;
    case Command::ProbeAddress:
      cmdProbeBusAddress();
      break;
    case Command::BusClock:
      cmdBusClock();
      break;
    case Command::PinControl:
      cmdPinControl();
      break;
    case Command::PinReset:
      cmdPinReset();
      break;
    case Command::Rswp:
      cmdRSWP();
      break;
    case Command::Pswp:
      cmdPSWP();
      break;
    case Command::WriteTest:
      cmdWriteTest();
      break;
    case Command::Version:
      cmdVersion();
      break;
    case Command::Test:
      cmdTest();
      break;
    case Command::RswpReport:
      cmdRswpRespond();
      break;
    case Command::Ddr4Detect:
      cmdDdr4Detect();
      break;
    case Command::Ddr5Detect:
      cmdDdr5Detect();
      break;
    case Command::Spd5HubReg:
      cmdSpd5Hub();
      break;
    case Command::Size:
      cmdSize();
      break;
    case Command::Name:
      cmdName();
      break;
    case Command::FactoryReset:
      cmdFactoryReset();
      break;
  }
  OutputResponse();
  cmdExecuting = false;
}
/*  -=  Response handlers  =-  */
void Respond(uint8_t inputData) {
  responseBuffer[responseLength] = inputData;
  responseLength++;
}
void Respond(uint8_t* inputData, size_t length) {
  for (uint8_t i = 0; i < length; i++) {
    Respond(inputData[i]);
  }
}
void Respond(String inputData) {
  for (uint8_t i = 0; i < inputData.length(); i++) {
    Respond(inputData[i]);
  }
}
void OutputResponse() {
  if (responseLength > 0) {
    uint8_t checkSum = 0;
    for (uint8_t i = 0; i < responseLength; i++) {
      checkSum += responseBuffer[i];
    }
    PORT.write(RESPONSE);
    PORT.write(responseLength);
    PORT.write(responseBuffer, responseLength);
    PORT.write(checkSum);
    PORT.flush();
    responseLength = 0;
    memset(responseBuffer, 0x00, sizeof(responseBuffer));
  }
}
/*  -=  Command handlers  =-  */
void cmdRead() {
  uint8_t buffer[4] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  uint8_t address = buffer[0];
  uint16_t offset = buffer[1] << 8 | buffer[2];
  uint8_t length  = buffer[3];
  uint8_t data[length];
  readByte(address, offset, length, data);
  Respond(data, sizeof(data));
}
void cmdWriteByte() {
  uint8_t buffer[4] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  uint8_t address = buffer[0];
  uint16_t offset = buffer[1] << 8 | buffer[2];
  uint8_t data    = buffer[3];
  Respond(writeByte(address, offset, data));
}
void cmdWritePage() {
  uint8_t buffer[4] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  uint8_t address = buffer[0];
  uint16_t offset = buffer[1] << 8 | buffer[2];
  uint8_t length  = buffer[3];
  if (length == 0) {
    Respond(0);
    return;
  }
  uint8_t data[length];
  PORT.readBytes(data, sizeof(data));
  if (length > 16) {
    Respond(false);
    return;
  }
  Respond(writePage(address, offset, length, data));
}
void cmdScanBus() {
  Respond(scanBus());
}
void cmdTest() {
  Respond(true);
}
void cmdRswpRespond() {
  Respond(rswpSupportTest());
}
void cmdDdr4Detect() {
  uint8_t buffer[1] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  uint8_t address = buffer[0];  // I2C address
  Respond(ddr4Detect(address));
}
void cmdDdr5Detect() {
  uint8_t buffer[1] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  uint8_t address = buffer[0];  // I2C address
  Respond(ddr5Detect(address));
}
void cmdSpd5Hub() {
  uint8_t buffer[3] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  uint8_t address = buffer[0];  // I2C address
  uint8_t memReg  = buffer[1];  // register
  uint8_t command = buffer[2];  // command
  if (!ddr5Detect(address)) {
    Respond(false);
  }
  if (command == Command::Enable) {
    uint8_t data[1] = { 0 };  // Byte value
    PORT.readBytes(data, sizeof(data));
    Respond(writeReg(address, memReg, data[0]));
  }
  else if (command == Command::Get) {
    Respond(readReg(address, memReg));
  }
  else {
    Respond(false);
  }
}
void cmdSize() {
  uint8_t buffer[1] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  uint8_t address = buffer[0];  // I2C address
  uint16_t size = 0; // 0
  if (probeBusAddress(address)) {
    if (validateEepromAddress(address)) { // EEPROM
      if (ddr5Detect(address)) {
        size = 1024;  // 3
      }
      else {
        if (getQuantity() == 1) {
          if (ddr4Detect()) {
            size = 512;  // 2
          }
          else {
            size = 256;  // 1
          }
        }
        else if (getQuantity() > 1) {
          if (!ddr4Detect()) {
            size = 256;  // 1
          }
        }
      }
    }
    else if (validatePmicAddress(address)) { // PMIC
      size = 256;  // 1
    }
  }
  if (!size) {
    uint8_t keyByte[1] = { 0 };
    readByte(address, 0x02, 1, keyByte);
    if (0x0C <= keyByte[0] && keyByte[0] <= 0x11) {
      size = 512;  // 2
    }
  }
  for (uint8_t i = 0; i <= 3; i++) {
    if(bitRead(highByte(size), i)) {
      Respond(i + 1);
      return;
    }
  }
  Respond(0);
}
void cmdVersion() {
  uint8_t verLength = sizeof(FW_VER);
  uint8_t data[verLength];
  for (int8_t i = verLength; i > 0; i--) {
    data[i - 1] = FW_VER >> (8 * (i - 1));
  }
  Respond(data, verLength);
}
void cmdName() {
  uint8_t buffer[1] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  if (buffer[0] == Command::Get) {
    Respond(getName());
  }
  else if (buffer[0] > 0 && buffer[0] <= NAMELENGTH) {
    char name[buffer[0] + 1];
    PORT.readBytes(name, buffer[0]);
    name[buffer[0]] = 0;
    Respond(setName(name));
  }
  else {
    Respond(false);
  }
}
void cmdProbeBusAddress() {
  uint8_t buffer[1] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  uint8_t address = buffer[0];  // I2C address
  Respond(probeBusAddress(address));
}
void cmdBusClock() {
  uint8_t buffer[1] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  if (buffer[0] == FASTMODE || buffer[0] == STDMODE) {
    setI2cClockMode(buffer[0]);
    Respond(getI2cClockMode() == buffer[0]);
  }
  else if (buffer[0] == Command::Get) {
    Respond(getI2cClockMode());
  }
  else {
    Respond(false);
  }
}
void cmdFactoryReset() {
  Respond(factoryReset());
}
void cmdRSWP() {
  uint8_t buffer[3] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  uint8_t address = buffer[0];
  uint8_t block   = buffer[1];
  char state      = buffer[2];
  if (state == Command::Enable) {
    Respond(setRswp(address, block));
  }
  else if (state == Command::Disable) {
    Respond(clearRswp(address));
  }
  else if (state == Command::Get) {
    Respond(getRswp(address, block));
  }
  else {
    Respond(false);
  }
}
void cmdPSWP() {
  uint8_t buffer[2] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  uint8_t address = buffer[0];
  char state      = buffer[1];
  if (state == Command::Enable) {
    Respond(setPswp(address));
  }
  else if (state == Command::Get) {
    Respond(getPswp(address));
  }
  else {
    Respond(false);
  }
}
void cmdWriteTest() {
  uint8_t buffer[3] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  uint8_t address = buffer[0];
  uint16_t offset = buffer[1] << 8 | buffer[2];
  uint8_t data[1];
  Respond(readByte(address, offset, 1, data) && writeByte(address, offset, data[0]));
}
void cmdPinControl() {
  uint8_t buffer[2] = { 0 };
  PORT.readBytes(buffer, sizeof(buffer));
  uint8_t pin = buffer[0];
  uint8_t state = buffer[1];
  if (pin == SA1_SWITCH) {
    if (state == Command::Enable || state == Command::Disable) {
      Respond(setConfigPin(SA1_EN, state));
    }
    else if (state == Command::Get) {
      Respond(getConfigPin(SA1_EN));
    }
    else {
      Respond(false);
    }
  }
  else if (pin == HV_SWITCH) {
    if (state == Command::Enable || state == Command::Disable) {
      Respond(setHighVoltage(state));
    }
    else if (state == Command::Get) {
      Respond(getHighVoltage());
    }
    else {
      Respond(0);
    }
  }
  else {
    Respond(false);
  }
}
void cmdPinReset() {
  Respond(resetPins());
}
/*  -=  Read/Write functions  =-  */
bool readByte(uint8_t address, uint16_t offset, uint8_t length, uint8_t* data) {
  uint8_t _offset = lowByte(offset);
  if (ddr5Detect(address)) {
    _offset |= 0x80;
  }
  adjustPageAddress(address, offset);
  Wire.beginTransmission(address);
  Wire.write(_offset);
  if (Wire.endTransmission(false) != 0) {
    return false;
  }
  Wire.requestFrom(address, length);
  while (Wire.available() < length) {}
  for (uint8_t i = 0; i < length; i++) {
    while (!Wire.available()) {}
    data[i] = Wire.read();
  }
  return true;
}
bool writeByte(uint8_t address, uint16_t offset, uint8_t data) {
  uint8_t input[1] = { data };
  return writePage(address, offset, 1, input);
}
bool writePage(uint8_t address, uint16_t offset, uint8_t length, uint8_t* data) {
  if ((offset % 16 + length) > 16) {
    return false;
  }
  if (ddr5Detect(address) && ddr5GetOfflineMode()) {
    uint8_t block = offset / 64;
    if (getRswp(address, block)) {
      return false;
    }
  }
  uint16_t _offset = offset;
  if (ddr5Detect(address)) {
    _offset |= 0x80;
    while (bitRead(readReg(address, MR48), 3)) {}
  }
  adjustPageAddress(address, offset);
  Wire.beginTransmission(address);
  Wire.write((uint8_t)(_offset));
  Wire.write(data, length);
  uint8_t status = Wire.endTransmission();
  delay(10);
  return status == 0;
}
uint8_t readReg(uint8_t address, uint8_t memReg) {
  return readReg(address, memReg, true);
}
uint8_t readReg(uint8_t address, uint8_t memReg, bool resetPage) {
  if (memReg >= 128) {
    return false;
  }
  if (resetPage) {
    adjustPageAddress(address, 0);
  }
  Wire.beginTransmission(address);
  Wire.write(memReg & 0x7F);
  uint8_t status = Wire.endTransmission(false);
  if (status != 0) {
    return false;
  }
  Wire.requestFrom(address, (uint8_t)1);
  while (!Wire.available()) {}
  uint8_t output = Wire.read();
  return output;
}
bool writeReg(uint8_t address, uint8_t memReg, uint8_t value) {
  if ( memReg >= 128 || !(MR11 <= memReg && memReg <= MR13)) {
    return false;
  }
  Wire.beginTransmission(address);
  Wire.write(memReg);
  Wire.write(value);
  uint8_t status = Wire.endTransmission();
  delay(memReg == MR11 ? 0 : 10);
  return status == 0;
}
/*  -=  RSWP functions  =-  */
bool setRswp(uint8_t address, uint8_t block) {
  if (block > 15) {
    return false;
  }
  if (ddr5Detect(address)) {
    uint8_t memReg = MR12 + bitRead(block, 3);
    uint8_t currentValue = readReg(address, memReg);
    uint8_t updatedValue = 1 << (block & 0b111);
    return writeReg(address, memReg, currentValue | updatedValue);
  }
  uint8_t commands[] = { SWP0, SWP1, SWP2, SWP3 };
  uint8_t cmd = commands[(0 < block && block <= 3) ? block : 0];
  bool result = false;
  if (setHighVoltage(true)) {
    if (block == 0) {
      setConfigPin(SA1_EN, false);  // Required for pre-DDR4
    }
    if (block > 0 && !ddr4Detect()) {
      result = false;
    }
    else {
      result = probeDeviceTypeId(cmd);
    }
    resetPins();
  }
  return result;
}
bool getRswp(uint8_t address, uint8_t block) {
  if (ddr5Detect(address)) {
    return (block <= 15)
      ? readReg(address, MR12 + bitRead(block, 3)) & (1 << (block & 0b111))
      : false;
  }
  uint8_t commands[] = { RPS0, RPS1, RPS2, RPS3 };
  uint8_t cmd = (0 < block && block <= 3) ? commands[block] : commands[0];
  if (block == 0 && !ddr4Detect()) {
    setHighVoltage(true);
  }
  bool status = probeDeviceTypeId(cmd);  // true/ack = not protected
  resetPins();
  return !status;  // true = protected or rswp not supported; false = unprotected
}
bool clearRswp(uint8_t address) {
  if (ddr5Detect(address)) {
    return ddr5GetOfflineMode()
      ? writeReg(address, MR12, 0) && readReg(address, MR12) == 0 &&
        writeReg(address, MR13, 0) && readReg(address, MR13) == 0
      : false;
  }
  if (!ddr4Detect(address)) {
    setConfigPin(SA1_EN, true); // Required for pre-DDR4
  }
  if (setHighVoltage(true)) {
    bool result = probeDeviceTypeId(CWP);
    resetPins();
    return result;
  }
  return false;
}
uint8_t rswpSupportTest() {
  resetPins();
  if (!scanBus()) {
    return 0;
  }
  uint8_t rswpSupport = 0;
  if (ddr5GetOfflineMode()) {
    rswpSupport |= DDR5;
  }
  if (setHighVoltage(true)) {
    rswpSupport |= DDR4;
    if ((setConfigPin(SA1_EN, true) && setConfigPin(SA1_EN, false))) {
      rswpSupport |= DDR3;
    }
  }
  resetPins();
  return rswpSupport;
}
/*  -=  High Voltage (9V) functions  =-  */
bool setHighVoltage(bool state) {
  digitalWrite(HV_EN, state);
  uint64_t timeout = millis() + 25;
  while (millis() < timeout) {
    if (getHighVoltage() == state) {
      return true;
    }
  }
  return false;
}
bool getHighVoltage() {
  return getConfigPin(HV_FB);
}
/*  -=  PSWP functions  =-  */
bool setPswp(uint8_t address) {
  if (ddr4Detect(address) || ddr5Detect(address)) {
    return false;
  }
  uint8_t cmd = (address & 0b111) | (PWPB << 3);
  Wire.beginTransmission(cmd);
  Wire.write(DNC);
  Wire.write(DNC);
  int status = Wire.endTransmission();
  return status == 0;
}
bool getPswp(uint8_t address) {
  uint8_t cmd = (address & 0b111) | (PWPB << 3);
  Wire.beginTransmission(cmd);
  Wire.write(DNC);
  int status = Wire.endTransmission();
  return status == 0;  // returns true if PSWP is not set
}
/*  -=  EEPROM Page functions  =-  */
uint8_t getPageAddress(bool lowLevel = false) {
  if (!lowLevel) {
    return eepromPageAddress;
  }
  int8_t status = -1;
  TWCR = _BV(TWEN) | _BV(TWINT) | _BV(TWEA) | _BV(TWSTA);
  while (!(TWCR & (_BV(TWINT)))) {}
  while ((TWSR & 0xF8) != 0x08) {}
  TWDR = RPA;
  TWCR = _BV(TWEN) | _BV(TWEA) | _BV(TWINT);
  while (!(TWCR & (_BV(TWINT)))) {}
  status = (TWSR & 0xF8);
  if (status == 0x40) {
    for (int i = 0; i < 2; i++) {
      TWDR = DNC;
      TWCR = _BV(TWEN) | _BV(TWEA) | _BV(TWINT);
      while (!(TWCR & (_BV(TWINT)))) {}
    }
  }
  TWCR = _BV(TWEN) | _BV(TWINT) | _BV(TWEA) | _BV(TWSTO);
  switch (status) {
    case 0x40: return 0;
    case 0x48: return 1;
    default: return status;
  }
}
void setDdr4PageAddress(uint8_t pageNumber) {
  probeDeviceTypeId((pageNumber == 0) ? SPA0 : SPA1);
  eepromPageAddress = pageNumber;
}
void adjustPageAddress(uint8_t address, uint16_t offset) {
  if (!validateEepromAddress(address) || offset >= 1024) {
    return;
  }
  int8_t page;
  if (ddr5Detect(address)) {
    page = offset >> 7;  // DDR5 page
    writeReg(address, MR11, page);
    return;
  }
  if (offset < 512) {
    page = bitRead(offset, 8);  // DDR4 page
    if (getPageAddress() != page) {
      setDdr4PageAddress(page);
      eepromPageAddress = page;
    }
  }
}
/*  -=  Device settings functions =-  */
bool setName(String name) {
  for (uint8_t i = 0; i < name.length(); i++) {
    EEPROM.update(i, name[i]);
  }
  EEPROM.update(name.length(), 0);
  return name == getName();
}
String getName() {
  char deviceNameChar[NAMELENGTH + 1];
  for (uint8_t i = 0; i < NAMELENGTH; i++) {
    deviceNameChar[i] = EEPROM.read(i);
  }
  deviceNameChar[NAMELENGTH] = 0;
  return deviceNameChar;
}
bool getSettings(uint8_t name) {
  return bitRead(EEPROM.read(DEVICESETTINGS), name);
}
bool saveSettings(uint8_t name, uint8_t value) {
  uint8_t currentSettings = EEPROM.read(DEVICESETTINGS);
  EEPROM.update(DEVICESETTINGS, bitWrite(currentSettings, name, value));
  return getSettings(name) == value;
}
/*  -=  I2C bus functions  =-  */
bool setI2cClockMode(bool mode) {
  saveSettings(CLOCKMODE, mode ? FASTMODE : STDMODE);
  Wire.setClock(clock[mode]);
  return getI2cClockMode() == mode;
}
bool getI2cClockMode() {
  return getSettings(CLOCKMODE);
}
uint8_t scanBus() {
  return scanBus(80, 87);
}
uint8_t scanBus(uint8_t startAddress, uint8_t endAddress) {
  uint8_t totalAddresses = endAddress - startAddress;
  if (totalAddresses > 7) {
    return 0;
  }
  uint8_t response = 0;
  for (uint8_t i = 0; i <= totalAddresses; i++) {
    if (probeBusAddress(i + startAddress)) {
      response |= 1 << i;
    }
  }
  return response;
}
uint8_t getQuantity() {
  return bitCount(scanBus());
}
uint8_t bitCount(uint8_t bitMask) {
  if (bitMask == 0) {
    return 0;
  }
  uint8_t quantity = 0;
  for (uint8_t i = 0; i <= 7; i++) {
    if (bitRead(bitMask, i)) {
      quantity++;
    }
  }
  return quantity;
}
void i2cMonitor() {
  if (cmdExecuting) {
    return;
  }
  bool i2cPause = false;
  if (!digitalRead(HV_EN) && !digitalRead(HV_FB)) {
    slaveCountCurrent = getQuantity();
    if (slaveCountCurrent != slaveCountLast) {
      uint8_t buffer[] = { ALERT, (uint8_t)(slaveCountCurrent < slaveCountLast ? SLAVEDEC : SLAVEINC) };
      PORT.write(buffer, sizeof(buffer));
      slaveCountLast = slaveCountCurrent;
      i2cPause = true;
    }
  }
  i2cClockCurrent = getI2cClockMode();
  if (i2cClockCurrent != i2cClockLast) {
    uint8_t buffer[] = { ALERT, (uint8_t)(i2cClockCurrent < i2cClockLast ? CLOCKDEC : CLOCKINC) };
    PORT.write(buffer, sizeof(buffer));
    i2cClockLast = i2cClockCurrent;
  }
  if (i2cPause) {
    delay(10);
  }
  PORT.flush();
}
bool setConfigPin(uint8_t pin, bool state) {
  digitalWrite(pin, state);
  if (pin == SA1_EN) {
    uint64_t timeout = millis() + 10;
    while (millis() < timeout) {
      if (scanBus() & (state ? A1_MASK : ~A1_MASK)) {
        return true;
      }
    }
    return false;
  }
  return getConfigPin(pin) == state;
}
bool getConfigPin(uint8_t pin) {
  return pin == SA1_EN ? scanBus() & A1_MASK : digitalRead(pin);
}
bool resetPins() {
  return setHighVoltage(ConfigPin[HV_SWITCH].defaultState) &&
         setConfigPin(ConfigPin[SA1_SWITCH].name, ConfigPin[SA1_SWITCH].defaultState);
}
void resetPinsInternal() {
  for (uint8_t i = 0; i < pinCount; i++) {
    if (ConfigPin[i].mode == OUTPUT) {
      digitalWrite(ConfigPin[i].name, ConfigPin[i].defaultState);
    }
  }
}
bool ddr5GetOfflineMode() {
  return ddr5Detect(80) && bitRead(readReg(80, MR48), 2);
}
bool probeBusAddress(uint8_t address) {
  Wire.beginTransmission(address);
  return Wire.endTransmission(false) == 0;
}
bool probeDeviceTypeId(uint8_t deviceSelectCode) {
  uint8_t status = 0;
  bool writeBit = !bitRead(deviceSelectCode, 0);
  uint8_t cmd = deviceSelectCode >> 1;
  Wire.beginTransmission(cmd);
  if (writeBit) {
    Wire.write(DNC);
    Wire.write(DNC);
  }
  status = Wire.endTransmission();
  if (writeBit) {
    return status == 0;
  }
  return Wire.requestFrom(cmd, (uint8_t)1) > 0;  // true when ACK is received after control byte
}
bool ddr4Detect(uint8_t address) {
  if (!address) {
    return ddr4Detect();
  }
  return probeBusAddress(address) && ddr4Detect() && !ddr5Detect(address);
}
bool ddr4Detect() {
  setDdr4PageAddress(0);
  return getQuantity() > 0 && getPageAddress(true) == 0;
}
bool ddr5Detect(uint8_t address) {
  if (!validateEepromAddress(address) ||
      !probeBusAddress(address) ||
      !probeBusAddress((address & 0b111) | PMIC)) {
    return false;
  }
  writeReg(address, MR11, 0);
  if (readReg(address, MR0, false) == highByte(SPD5_TS)) {
    uint8_t mr1 = readReg(address, MR1, false);
    return mr1 == lowByte(SPD5_TS) || mr1 == lowByte(SPD5_NO);
  }
  return false;
}
bool validateEepromAddress(uint8_t address) {
  return address >> 3 == 0b1010;
}
bool validatePmicAddress(uint8_t address) {
  return address >> 3 == PMIC >> 3;
}
bool factoryReset() {
  for (uint8_t i = 0; i <= 32; i++) {
    EEPROM.update(i, 0);
  }
  for (uint8_t i = 0; i <= 32; i++) {
    if (EEPROM.read(i) != 0) {
      return false;
    }
  }
  return true;
}