Add ISL69269 Voltage Regulator support

The configuration of the voltage regulator relies on the EM-Schema[1]

1: https://gerrit.openbmc.org/c/openbmc/entity-manager/+/79823

Tested:
```
<7> Requesting Image update with 10
<7> started asynchronous update with fd 10
<7> starting async update with FD: 11

<7> Harme_MB_VR_CPU0_VCORE0_5813: created dbus interfaces on path /xyz/openbmc_project/software/Harme_MB_VR_CPU0_VCORE0_5813
<7> starting the async update with memfd 11
<7> open fd 11
<7> file size: 7646
<7> parsing package header
<7> parsing package, pkg header size: 152
<7> Harme_MB_VR_CPU0_VCORE0_5813: set version 99A5A351
<7> device id from configuration: 0x49d29b00
<7> device revision from config: 0x7000000
<7> Production hex file format recognized
<7> Config ID: 0
<7> Config Production CRC: 0x99a5a351
<7> Device revision read from device: 0x6000100
<7> Production mode revision checks out
<6> Successfully updated VR Harme_MB_VR_CPU0_VCORE0
<7> Default implementation for device reset
<7> Harme_MB_VR_CPU0_VCORE0_5813: setting association definitions
<7> inventory item at path /xyz/openbmc_project/inventory/system/board/BMC_Storage_Module
<7> inventory item at path /xyz/openbmc_project/inventory/system/board/CB
<7> inventory item at path /xyz/openbmc_project/inventory/system/board/Harma_Fan_Board_0
<7> inventory item at path /xyz/openbmc_project/inventory/system/board/Harma_Fan_Board_1
<7> inventory item at path /xyz/openbmc_project/inventory/system/board/Harma_MB
<7> found associated inventory item for Harme_MB_VR_CPU0_VCORE0: /xyz/openbmc_project/inventory/system/board/Harma_MB
<7> Harme_MB_VR_CPU0_VCORE0_5813: creating 'running' association to /xyz/openbmc_project/inventory/system/board/Harma_MB
<7> [Software] enabling update of /xyz/openbmc_project/software/Harme_MB_VR_CPU0_VCORE0_5813 (adding the update interface)
<7> Successfully updated to software version Harme_MB_VR_CPU0_VCORE0_5813
```

Change-Id: Ia7d7b19066cd2a55748eeffacc18e7d6c78ca797
Signed-off-by: Christopher Meis <christopher.meis@9elements.com>
diff --git a/i2c-vr/i2cvr_software_manager.cpp b/i2c-vr/i2cvr_software_manager.cpp
index 1185648..2c85da8 100644
--- a/i2c-vr/i2cvr_software_manager.cpp
+++ b/i2c-vr/i2cvr_software_manager.cpp
@@ -21,7 +21,7 @@
 
 const std::string configDBusName = "I2CVR";
 const std::vector<std::string> emConfigTypes = {"XDPE1X2XXFirmware",
-                                                "DummyDeviceFirmware"};
+                                                "ISL69269Firmware"};
 
 I2CVRSoftwareManager::I2CVRSoftwareManager(sdbusplus::async::context& ctx) :
     ManagerInf::SoftwareManager(ctx, configDBusName)
diff --git a/i2c-vr/isl69269/isl69269.cpp b/i2c-vr/isl69269/isl69269.cpp
new file mode 100644
index 0000000..dc9cd9e
--- /dev/null
+++ b/i2c-vr/isl69269/isl69269.cpp
@@ -0,0 +1,636 @@
+#include "isl69269.hpp"
+
+#include "common/include/i2c/i2c.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <string>
+
+PHOSPHOR_LOG2_USING;
+
+namespace phosphor::software::VR
+{
+
+constexpr uint8_t regProgStatus = 0x7E;
+constexpr uint8_t regHexModeCFG0 = 0x87;
+constexpr uint8_t regCRC = 0x94;
+constexpr uint8_t regHexModeCFG1 = 0xBD;
+constexpr uint8_t regDMAData = 0xC5;
+constexpr uint8_t regDMAAddr = 0xC7;
+constexpr uint8_t regRestoreCfg = 0xF2;
+
+constexpr uint8_t regRemainginWrites = 0x35;
+
+constexpr uint8_t gen3SWRevMin = 0x06;
+constexpr uint8_t deviceIdLength = 4;
+
+constexpr uint8_t gen3Legacy = 1;
+constexpr uint8_t gen3Production = 2;
+
+constexpr uint16_t cfgId = 7;
+constexpr uint16_t gen3FileHead = 5;
+constexpr uint16_t gen3LegacyCRC = 276 - gen3FileHead;
+constexpr uint16_t gen3ProductionCRC = 290 - gen3FileHead;
+constexpr uint8_t checksumLen = 4;
+constexpr uint8_t deviceRevisionLen = 4;
+
+// Common pmBus Command codes
+constexpr uint8_t pmBusDeviceId = 0xAD;
+constexpr uint8_t pmBusDeviceRev = 0xAE;
+
+// Config file constants
+constexpr char recordTypeData = 0x00;
+constexpr char recordTypeHeader = 0x49;
+
+constexpr uint8_t defaultBufferSize = 16;
+constexpr uint8_t programBufferSize = 32;
+
+constexpr uint8_t zeroByteLen = 0;
+constexpr uint8_t oneByteLen = 1;
+constexpr uint8_t threeByteLen = 3;
+constexpr uint8_t fourByteLen = 4;
+
+ISL69269::ISL69269(sdbusplus::async::context& ctx, uint16_t bus,
+                   uint16_t address) :
+    VoltageRegulator(ctx), i2cInterface(phosphor::i2c::I2C(bus, address))
+{}
+
+inline void shiftLeftFromLSB(const uint8_t* data, uint32_t* result)
+{
+    *result = (static_cast<uint32_t>(data[3]) << 24) |
+              (static_cast<uint32_t>(data[2]) << 16) |
+              (static_cast<uint32_t>(data[1]) << 8) |
+              (static_cast<uint32_t>(data[0]));
+}
+
+inline void shiftLeftFromMSB(const uint8_t* data, uint32_t* result)
+{
+    *result = (static_cast<uint32_t>(data[0]) << 24) |
+              (static_cast<uint32_t>(data[1]) << 16) |
+              (static_cast<uint32_t>(data[2]) << 8) |
+              (static_cast<uint32_t>(data[3]));
+}
+
+static uint8_t calcCRC8(const uint8_t* data, uint8_t len)
+{
+    uint8_t crc = 0x00;
+    int i = 0;
+    int b = 0;
+
+    for (i = 0; i < len; i++)
+    {
+        crc ^= data[i];
+        for (b = 0; b < 8; b++)
+        {
+            if (crc & 0x80)
+            {
+                crc = (crc << 1) ^ 0x07; // polynomial 0x07
+            }
+            else
+            {
+                crc = (crc << 1);
+            }
+        }
+    }
+
+    return crc;
+}
+
+sdbusplus::async::task<bool> ISL69269::dmaReadWrite(uint8_t* reg, uint8_t* resp)
+{
+    uint8_t tbuf[defaultBufferSize] = {0};
+    uint8_t tlen = threeByteLen;
+    uint8_t rbuf[defaultBufferSize] = {0};
+    uint8_t rlen = zeroByteLen;
+
+    tbuf[0] = regDMAAddr;
+    std::memcpy(&tbuf[1], reg, 2);
+
+    // NOLINTBEGIN(clang-analyzer-core.uninitialized.Branch)
+    if (!(co_await i2cInterface.sendReceive(tbuf, tlen, rbuf, rlen)))
+    // NOLINTEND(clang-analyzer-core.uninitialized.Branch)
+    {
+        error("dmaReadWrite failed with {CMD}", "CMD",
+              std::string("_REG_DMA_ADDR"));
+        co_return false;
+    }
+
+    tlen = oneByteLen;
+    rlen = fourByteLen;
+
+    tbuf[0] = regDMAData;
+    if (!(co_await i2cInterface.sendReceive(tbuf, tlen, resp, rlen)))
+    {
+        error("dmaReadWrite failed with {CMD}", "CMD",
+              std::string("_REG_DMA_DATA"));
+        co_return false;
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> ISL69269::getRemainingWrites(uint8_t* remain)
+{
+    uint8_t tbuf[defaultBufferSize] = {0};
+    uint8_t rbuf[defaultBufferSize] = {0};
+
+    tbuf[0] = regRemainginWrites;
+    tbuf[1] = 0x00;
+    if (!(co_await dmaReadWrite(tbuf, rbuf)))
+    {
+        error("getRemainingWrites failed");
+        co_return false;
+    }
+
+    *remain = rbuf[0];
+    co_return true;
+}
+
+sdbusplus::async::task<bool> ISL69269::getHexMode(uint8_t* mode)
+{
+    uint8_t tbuf[defaultBufferSize] = {0};
+    uint8_t rbuf[defaultBufferSize] = {0};
+
+    tbuf[0] = regHexModeCFG0;
+    tbuf[1] = regHexModeCFG1;
+    if (!(co_await dmaReadWrite(tbuf, rbuf)))
+    {
+        error("getHexMode failed");
+        co_return false;
+    }
+
+    *mode = (rbuf[0] == 0) ? gen3Legacy : gen3Production;
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> ISL69269::getDeviceId(uint32_t* deviceId)
+{
+    uint8_t tbuf[defaultBufferSize] = {0};
+    uint8_t tLen = oneByteLen;
+    uint8_t rbuf[defaultBufferSize] = {0};
+    uint8_t rLen = deviceIdLength + 1;
+
+    tbuf[0] = pmBusDeviceId;
+
+    if (!(co_await i2cInterface.sendReceive(tbuf, tLen, rbuf, rLen)))
+    {
+        error("getDeviceId failed");
+        co_return false;
+    }
+
+    std::memcpy(deviceId, &rbuf[1], deviceIdLength);
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> ISL69269::getDeviceRevision(uint32_t* revision)
+{
+    uint8_t tbuf[defaultBufferSize] = {0};
+    uint8_t tlen = oneByteLen;
+    uint8_t rbuf[defaultBufferSize] = {0};
+    uint8_t rlen = deviceRevisionLen + 1;
+
+    tbuf[0] = pmBusDeviceRev;
+    if (!(co_await i2cInterface.sendReceive(tbuf, tlen, rbuf, rlen)))
+    {
+        error("getDeviceRevision failed with sendreceive");
+        co_return false;
+    }
+
+    if (mode == gen3Legacy)
+    {
+        std::memcpy(revision, &rbuf[1], deviceRevisionLen);
+    }
+    else
+    {
+        shiftLeftFromLSB(rbuf + 1, revision);
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> ISL69269::getCRC(uint32_t* sum)
+{
+    uint8_t tbuf[defaultBufferSize] = {0};
+    uint8_t rbuf[defaultBufferSize] = {0};
+
+    tbuf[0] = regCRC;
+    if (!(co_await dmaReadWrite(tbuf, rbuf)))
+    {
+        error("getCRC failed");
+        co_return false;
+    }
+    std::memcpy(sum, rbuf, sizeof(uint32_t));
+
+    co_return true;
+}
+
+bool ISL69269::parseImage(const uint8_t* image, size_t imageSize)
+{
+    size_t nextLineStart = 0;
+    int dcnt = 0;
+
+    for (size_t i = 0; i < imageSize; i++)
+    {
+        if (image[i] == '\n') // We have a hex file, so we check new line.
+        {
+            char line[40];
+            char xdigit[8] = {0};
+            uint8_t sepLine[32] = {0};
+
+            size_t lineLen = i - nextLineStart;
+            std::memcpy(line, image + nextLineStart, lineLen);
+            int k = 0;
+            size_t j = 0;
+            for (k = 0, j = 0; j < lineLen; k++, j += 2)
+            {
+                // Convert two chars into a array of single values
+                std::memcpy(xdigit, &line[j], 2);
+                sepLine[k] = (uint8_t)std::strtol(xdigit, NULL, 16);
+            }
+
+            if (sepLine[0] == recordTypeHeader)
+            {
+                if (sepLine[3] == pmBusDeviceId)
+                {
+                    shiftLeftFromMSB(sepLine + 4, &configuration.devIdExp);
+                    debug("device id from configuration: {ID}", "ID", lg2::hex,
+                          configuration.devIdExp);
+                }
+                else if (sepLine[3] == pmBusDeviceRev)
+                {
+                    shiftLeftFromMSB(sepLine + 4, &configuration.devRevExp);
+                    debug("device revision from config: {ID}", "ID", lg2::hex,
+                          configuration.devRevExp);
+
+                    // According to programing guide:
+                    // If legacy hex file
+                    // MSB device revision == 0x00 | 0x01
+                    if (configuration.devRevExp < (gen3SWRevMin << 24))
+                    {
+                        debug("Legacy hex file format recognized");
+                        configuration.mode = gen3Legacy;
+                    }
+                    else
+                    {
+                        debug("Production hex file format recognized");
+                        configuration.mode = gen3Production;
+                    }
+                }
+            }
+            else if (sepLine[0] == recordTypeData)
+            {
+                if (((sepLine[1] + 2) >= (uint8_t)sizeof(sepLine)))
+                {
+                    dcnt = 0;
+                    break;
+                }
+                // According to documentation:
+                // 00 05 C2 E7 08 00 F6
+                //  |  |  |  |  |  |  |
+                //  |  |  |  |  |  |  - Packet Error Code (CRC8)
+                //  |  |  |  |  -  - Data
+                //  |  |  |  - Command Code
+                //  |  |  - Address
+                //  |  - Size of data (including Addr, Cmd, CRC8)
+                //  - Line type (0x00 - Data, 0x49 header information)
+                configuration.pData[dcnt].len = sepLine[1] - 2;
+                configuration.pData[dcnt].pec =
+                    sepLine[3 + configuration.pData[dcnt].len];
+                configuration.pData[dcnt].addr = sepLine[2];
+                configuration.pData[dcnt].cmd = sepLine[3];
+                std::memcpy(configuration.pData[dcnt].data, sepLine + 2,
+                            configuration.pData[dcnt].len + 1);
+                switch (dcnt)
+                {
+                    case cfgId:
+                        configuration.cfgId = sepLine[4] & 0x0F;
+                        debug("Config ID: {ID}", "ID", lg2::hex,
+                              configuration.cfgId);
+                        break;
+                    case gen3LegacyCRC:
+                        if (configuration.mode == gen3Legacy)
+                        {
+                            std::memcpy(&configuration.crcExp, &sepLine[4],
+                                        checksumLen);
+                            debug("Config Legacy CRC: {CRC}", "CRC", lg2::hex,
+                                  configuration.crcExp);
+                        }
+                        break;
+                    case gen3ProductionCRC:
+                        if (configuration.mode == gen3Production)
+                        {
+                            std::memcpy(&configuration.crcExp, &sepLine[4],
+                                        checksumLen);
+                            debug("Config Production CRC: {CRC}", "CRC",
+                                  lg2::hex, configuration.crcExp);
+                        }
+                        break;
+                }
+                dcnt++;
+            }
+            else
+            {
+                error("parseImage failed. Unknown recordType");
+                return false;
+            }
+
+            nextLineStart = i + 1;
+        }
+    }
+    configuration.wrCnt = dcnt;
+    return true;
+}
+
+bool ISL69269::checkImage()
+{
+    uint8_t crc8 = 0;
+
+    for (int i = 0; i < configuration.wrCnt; i++)
+    {
+        crc8 = calcCRC8(configuration.pData[i].data,
+                        configuration.pData[i].len + 1);
+        if (crc8 != configuration.pData[i].pec)
+        {
+            debug(
+                "Config line: {LINE}, failed to calculate CRC. Have {HAVE}, Want: {WANT}",
+                "LINE", i, "HAVE", lg2::hex, crc8, "WANT", lg2::hex,
+                configuration.pData[i].pec);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+sdbusplus::async::task<bool> ISL69269::program()
+{
+    uint8_t tbuf[programBufferSize] = {0};
+    uint8_t rbuf[programBufferSize] = {0};
+    uint8_t rlen = zeroByteLen;
+
+    for (int i = 0; i < configuration.wrCnt; i++)
+    {
+        tbuf[0] = configuration.pData[i].cmd;
+        std::memcpy(tbuf + 1, &configuration.pData[i].data,
+                    configuration.pData[i].len - 1);
+
+        if (!(co_await i2cInterface.sendReceive(
+                tbuf, configuration.pData[i].len, rbuf, rlen)))
+        {
+            error("program failed at writing data to voltage regulator");
+        }
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> ISL69269::getProgStatus()
+{
+    uint8_t tbuf[programBufferSize] = {0};
+    uint8_t rbuf[programBufferSize] = {0};
+    int retry = 3;
+
+    tbuf[0] = regProgStatus;
+    tbuf[1] = 0x00;
+
+    do
+    {
+        // NOLINTBEGIN(clang-analyzer-core.uninitialized.Branch)
+        if (!(co_await dmaReadWrite(tbuf, rbuf)))
+        // NOLINTEND(clang-analyzer-core.uninitialized.Branch)
+        {
+            error("getProgStatus failed on dmaReadWrite");
+            co_return false;
+        }
+
+        if (rbuf[0] & 0x01)
+        {
+            debug("Programming succesful");
+            break;
+        }
+        if (--retry == 0)
+        {
+            if ((!(rbuf[1] & 0x1)) || (rbuf[1] & 0x2))
+            {
+                error("programming the device failed");
+            }
+            if (!(rbuf[1] & 0x4))
+            {
+                error(
+                    "HEX file contains more configurations than are available");
+            }
+            if (!(rbuf[1] & 0x8))
+            {
+                error(
+                    "A CRC mismatch exists within the configuration data. Programming failed before  TP banks are consumed");
+            }
+            if (!(rbuf[1] & 0x10))
+            {
+                error(
+                    "CRC check fails on the OTP memory. Programming fails after OTP banks are consumed");
+            }
+            if (!(rbuf[1] & 0x20))
+            {
+                error("Programming fails after OTP banks are consumed.");
+            }
+
+            error("failed to program the device after exceeding retries");
+            co_return false;
+        }
+        co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(1));
+    } while (retry > 0);
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> ISL69269::restoreCfg()
+{
+    uint8_t tbuf[defaultBufferSize] = {0};
+    uint8_t rbuf[defaultBufferSize] = {0};
+
+    tbuf[0] = regRestoreCfg;
+    tbuf[1] = configuration.cfgId;
+
+    debug("Restore configurtion ID: {ID}", "ID", lg2::hex, configuration.cfgId);
+
+    if (!(co_await dmaReadWrite(tbuf, rbuf)))
+    {
+        error("restoreCfg failed at dmaReadWrite");
+        co_return false;
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> ISL69269::verifyImage(const uint8_t* image,
+                                                   size_t imageSize)
+{
+    uint8_t mode = 0xFF;
+    uint8_t remain = 0;
+    uint32_t devID = 0;
+    uint32_t devRev = 0;
+    uint32_t crc = 0;
+
+    if (!(co_await getHexMode(&mode)))
+    {
+        error("program failed at getHexMode");
+        co_return false;
+    }
+
+    if (!parseImage(image, imageSize))
+    {
+        error("verifyImage failed at parseImage");
+        co_return false;
+    }
+
+    if (!checkImage())
+    {
+        error("verifyImage failed at checkImage");
+        co_return false;
+    }
+
+    if (mode != configuration.mode)
+    {
+        error(
+            "program failed with mode of device and configuration are not equal");
+        co_return false;
+    }
+
+    if (!(co_await getRemainingWrites(&remain)))
+    {
+        error("program failed at getRemainingWrites");
+        co_return false;
+    }
+
+    if (!remain)
+    {
+        error("program failed with no remaining writes left on device");
+        co_return false;
+    }
+
+    if (!(co_await getDeviceId(&devID)))
+    {
+        error("program failed at getDeviceId");
+        co_return false;
+    }
+
+    if (devID != configuration.devIdExp)
+    {
+        error(
+            "program failed with not matching device id of device and config");
+        co_return false;
+    }
+
+    if (!(co_await getDeviceRevision(&devRev)))
+    {
+        error("program failed at getDeviceRevision");
+        co_return false;
+    }
+    debug("Device revision read from device: {REV}", "REV", lg2::hex, devRev);
+
+    switch (mode)
+    {
+        case gen3Legacy:
+            if (((devRev >> 24) >= gen3SWRevMin) &&
+                (configuration.devRevExp <= 0x1))
+            {
+                debug("Legacy mode revision checks out");
+            }
+            else
+            {
+                error(
+                    "revision requirements for legacy mode device not fulfilled");
+            }
+            break;
+        case gen3Production:
+            if (((devRev >> 24) >= gen3SWRevMin) &&
+                (configuration.devRevExp >= gen3SWRevMin))
+            {
+                debug("Production mode revision checks out");
+            }
+            else
+            {
+                error(
+                    "revision requirements for production mode device not fulfilled");
+            }
+            break;
+    }
+
+    if (!(co_await getCRC(&crc)))
+    {
+        error("program failed at getCRC");
+        co_return false;
+    }
+
+    debug("CRC from device: {CRC}", "CRC", lg2::hex, crc);
+    debug("CRC from config: {CRC}", "CRC", lg2::hex, configuration.crcExp);
+
+    if (crc == configuration.crcExp)
+    {
+        error("program failed with same CRC value at device and configuration");
+        co_return false;
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> ISL69269::reset()
+{
+    bool ret = true;
+    // NOLINTBEGIN(clang-analyzer-core.uninitialized.Branch)
+    ret = co_await getProgStatus();
+    // NOLINTEND(clang-analyzer-core.uninitialized.Branch)
+    if (!ret)
+    {
+        error("reset failed at getProgStatus");
+    }
+
+    ret = co_await restoreCfg();
+    if (!ret)
+    {
+        error("reset failed at restoreCfg");
+    }
+
+    // Reset configuration for next update.
+    configuration.addr = 0;
+    configuration.mode = 0;
+    configuration.cfgId = 0;
+    configuration.devIdExp = 0;
+    configuration.devRevExp = 0;
+    configuration.crcExp = 0x00;
+    for (int i = 0; i < configuration.wrCnt; i++)
+    {
+        configuration.pData[i].pec = 0x00;
+        configuration.pData[i].addr = 0x00;
+        configuration.pData[i].cmd = 0x00;
+        for (int j = 0; j < configuration.pData[i].len; j++)
+        {
+            configuration.pData[i].data[j] = 0x00;
+        }
+        configuration.pData[i].len = 0x00;
+    }
+    configuration.wrCnt = 0;
+
+    co_return ret;
+}
+
+bool ISL69269::forcedUpdateAllowed()
+{
+    return true;
+}
+
+sdbusplus::async::task<bool> ISL69269::updateFirmware(bool force)
+{
+    (void)force;
+    if (!(co_await program()))
+    {
+        error("programing ISL69269 failed");
+        co_return false;
+    }
+
+    co_return true;
+}
+
+} // namespace phosphor::software::VR
diff --git a/i2c-vr/isl69269/isl69269.hpp b/i2c-vr/isl69269/isl69269.hpp
new file mode 100644
index 0000000..986c65d
--- /dev/null
+++ b/i2c-vr/isl69269/isl69269.hpp
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "common/include/i2c/i2c.hpp"
+#include "i2c-vr/vr.hpp"
+
+#include <sdbusplus/async.hpp>
+
+#include <cstdint>
+
+namespace phosphor::software::VR
+{
+
+class ISL69269 : public VoltageRegulator
+{
+  public:
+    ISL69269(sdbusplus::async::context& ctx, uint16_t bus, uint16_t address);
+
+    sdbusplus::async::task<bool> verifyImage(const uint8_t* image,
+                                             size_t imageSize) final;
+
+    sdbusplus::async::task<bool> updateFirmware(bool force) final;
+    sdbusplus::async::task<bool> getCRC(uint32_t* checksum) final;
+    sdbusplus::async::task<bool> reset() final;
+
+    bool forcedUpdateAllowed() final;
+
+  private:
+    struct Data
+    {
+        uint8_t len;
+        uint8_t pec;
+        uint8_t addr;
+        uint8_t cmd;
+        uint8_t data[30];
+    };
+
+    struct Configuration
+    {
+        uint8_t addr;
+        uint8_t mode;
+        uint8_t cfgId;
+        uint16_t wrCnt;
+        uint32_t devIdExp;
+        uint32_t devRevExp;
+        uint32_t crcExp;
+        struct Data pData[1024];
+    };
+    sdbusplus::async::task<bool> dmaReadWrite(uint8_t* reg, uint8_t* resp);
+    sdbusplus::async::task<bool> getRemainingWrites(uint8_t* remain);
+    sdbusplus::async::task<bool> getHexMode(uint8_t* mode);
+    sdbusplus::async::task<bool> getDeviceId(uint32_t* deviceId);
+    sdbusplus::async::task<bool> getDeviceRevision(uint32_t* revision);
+    sdbusplus::async::task<bool> program();
+    sdbusplus::async::task<bool> getProgStatus();
+    sdbusplus::async::task<bool> restoreCfg();
+
+    bool parseImage(const uint8_t* image, size_t imageSize);
+    bool checkImage();
+
+    phosphor::i2c::I2C i2cInterface;
+    uint8_t mode;
+
+    struct Configuration configuration;
+};
+} // namespace phosphor::software::VR
diff --git a/i2c-vr/meson.build b/i2c-vr/meson.build
index 58ff477..2af9c56 100644
--- a/i2c-vr/meson.build
+++ b/i2c-vr/meson.build
@@ -1,6 +1,6 @@
 i2cvr_src = files('i2cvr_device.cpp', 'i2cvr_software_manager.cpp', 'vr.cpp')
 
-regulators_src = files('xdpe1x2xx/xdpe1x2xx.cpp')
+regulators_src = files('isl69269/isl69269.cpp', 'xdpe1x2xx/xdpe1x2xx.cpp')
 
 i2cvr_include = include_directories('.')
 
diff --git a/i2c-vr/vr.cpp b/i2c-vr/vr.cpp
index f7c7c0f..7280f91 100644
--- a/i2c-vr/vr.cpp
+++ b/i2c-vr/vr.cpp
@@ -1,5 +1,6 @@
 #include "vr.hpp"
 
+#include "isl69269/isl69269.hpp"
 #include "xdpe1x2xx/xdpe1x2xx.hpp"
 
 #include <map>
@@ -17,6 +18,9 @@
         case VRType::XDPE1X2XX:
             ret = std::make_unique<XDPE1X2XX>(ctx, bus, address);
             break;
+        case VRType::ISL69269:
+            ret = std::make_unique<ISL69269>(ctx, bus, address);
+            break;
         default:
             return NULL;
     }
@@ -27,6 +31,7 @@
 {
     std::map<std::string, enum VRType> VRTypeToString{
         {"XDPE1X2XXFirmware", VRType::XDPE1X2XX},
+        {"ISL69269Firmware", VRType::ISL69269},
     };
 
     if (VRTypeToString.contains(vrStr))
diff --git a/i2c-vr/vr.hpp b/i2c-vr/vr.hpp
index 670dc9f..688b37b 100644
--- a/i2c-vr/vr.hpp
+++ b/i2c-vr/vr.hpp
@@ -12,6 +12,7 @@
 enum class VRType
 {
     XDPE1X2XX,
+    ISL69269,
 };
 
 class VoltageRegulator