fw-update: cpld code updater

This commit introduces a code updater for CPLD.
The Key features of this updater include:
- configuring chip vendor and name
- update CPLD through I2C

1. Display the fw inventory
curl ... -X GET
https://localhost/redfish/v1/UpdateService/FirmwareInventory
{
  "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
  "@odata.type":
    "#SoftwareInventoryCollection.SoftwareInventoryCollection",
  "Members": [
    ...
    {
      "@odata.id": ".../LCMXO3LF_4300C_6194"
    },
    ...
  ],
  "Members@odata.count": 26,
  "Name": "Software Inventory Collection"
}

2. Query CPLD version
curl ... -X GET
https://localhost/redfish/v1
  /UpdateService/FirmwareInventory/LCMXO3LF_4300C_6194
{
  "@odata.id": "/.../LCMXO3LF_4300C_6194",
  "@odata.type":
    "#SoftwareInventory.v1_1_0.SoftwareInventory",
  "Description": "Unknown image",
  "Id": "LCMXO3LF_4300C_6194",
  "Name": "Software Inventory",
  "Status": {
    "Health": "Warning",
    "HealthRollup": "OK",
    "State": "Disabled"
  },
  "Updateable": false,
  "Version": "00000220"
}

3. Trigger the fw update via redfish
curl -k
-H "X-Auth-Token: $token"
-H "Content-Type:multipart/form-data"
-X POST
-F UpdateParameters="{\"Targets\":
  [\"/redfish/v1/UpdateService/FirmwareInventory/LCMXO3LF_4300C_6194\"],
  \"@Redfish.OperationApplyTime\":\"Immediate\"};type=application/json"
-F "UpdateFile=@testcpld.pldm;type=application/octet-stream"
https://${bmc}/redfish/v1/UpdateService/update
{
  "@odata.id": "/redfish/v1/TaskService/Tasks/0",
  "@odata.type": "#Task.v1_4_3.Task",
  "Id": "0",
  "TaskState": "Running",
  "TaskStatus": "OK"
}

4. Query Task status
curl ... -X GET
https://localhost/redfish/v1/TaskService/Tasks/0
{
  "@odata.id": "/redfish/v1/TaskService/Tasks/0",
  "@odata.type": "#Task.v1_4_3.Task",
  "EndTime": "2025-05-28T08:10:22+00:00",
  "HidePayload": false,
  "Id": "0",
  "Messages": [
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '0' has started.",
      "MessageArgs": [
        "0"
      ],
      "MessageId": "TaskEvent.1.0.TaskStarted",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "... progress 15 percent complete.",
      "MessageArgs": [
        "0",
        "15"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "... progress 20 percent complete.",
      "MessageArgs": [
        "0",
        "20"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "... progress 25 percent complete.",
      "MessageArgs": [
        "0",
        "25"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "... progress 30 percent complete.",
      "MessageArgs": [
        "0",
        "30"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "... progress 40 percent complete.",
      "MessageArgs": [
        "0",
        "40"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "... progress 50 percent complete.",
      "MessageArgs": [
        "0",
        "50"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "... progress 60 percent complete.",
      "MessageArgs": [
        "0",
        "60"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "... progress 70 percent complete.",
      "MessageArgs": [
        "0",
        "70"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "... progress 80 percent complete.",
      "MessageArgs": [
        "0",
        "80"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "... progress 90 percent complete.",
      "MessageArgs": [
        "0",
        "90"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "... progress 100 percent complete.",
      "MessageArgs": [
        "0",
        "100"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '0' has completed.",
      "MessageArgs": [
        "0"
      ],
      "MessageId": "TaskEvent.1.0.TaskCompletedOK",
      "MessageSeverity": "OK",
      "Resolution": "None."
    }
  ],
  "Name": "Task 0",
  "Payload": {
    "HttpHeaders": [],
    "HttpOperation": "POST",
    "JsonBody": "null",
    "TargetUri": "/redfish/v1/UpdateService/update"
  },
  "PercentComplete": 100,
  "StartTime": "2025-05-28T08:10:06+00:00",
  "TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
  "TaskState": "Completed",
  "TaskStatus": "OK"
}

5. Display the fw inventory with newly updated fw.
curl ... -X GET
https://localhost/redfish/v1/UpdateService/FirmwareInventory
{
  "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
  "@odata.type":
    "#SoftwareInventoryCollection.SoftwareInventoryCollection",
  "Members": [
    ...
    {
      "@odata.id": ".../LCMXO3LF_4300C_1571"
    },
    ...
  ],
  "Members@odata.count": 26,
  "Name": "Software Inventory Collection"
}

6. Query CPLD version again
After AC cycle ..., so the number would be different.
curl ... -X GET
https://localhost/redfish/v1
  /UpdateService/FirmwareInventory/LCMXO3LF_4300C_4643
{
  "@odata.id": "/.../LCMXO3LF_4300C_4643",
  "@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
  "Description": "Unknown image",
  "Id": "LCMXO3LF_4300C_4643",
  "Name": "Software Inventory",
  "Status": {
    "Health": "Warning",
    "HealthRollup": "OK",
    "State": "Disabled"
  },
  "Updateable": false,
  "Version": "00000224"
}

Change-Id: Ife8e30a00bfbb307e6e4eb55838a397c8a8162bd
Signed-off-by: Daniel Hsu <Daniel-Hsu@quantatw.com>
diff --git a/cpld/lattice/lattice.cpp b/cpld/lattice/lattice.cpp
new file mode 100644
index 0000000..ce70fa3
--- /dev/null
+++ b/cpld/lattice/lattice.cpp
@@ -0,0 +1,933 @@
+#include "lattice.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <algorithm>
+#include <fstream>
+#include <map>
+#include <thread>
+#include <vector>
+
+using sdbusplus::async::details::context_friend;
+
+constexpr uint8_t busyWaitmaxRetry = 30;
+constexpr uint8_t busyFlagBit = 0x80;
+constexpr std::chrono::milliseconds waitBusyTime(200);
+
+static constexpr std::string_view tagQF = "QF";
+static constexpr std::string_view tagUH = "UH";
+static constexpr std::string_view tagCFStart = "L000";
+static constexpr std::string_view tagChecksum = "C";
+static constexpr std::string_view tagUserCode = "NOTE User Electronic";
+static constexpr std::string_view tagEbrInitData = "NOTE EBR_INIT DATA";
+
+constexpr uint8_t isOK = 0;
+constexpr uint8_t isReady = 0;
+constexpr uint8_t busyOrReadyBit = 4;
+constexpr uint8_t failOrOKBit = 5;
+
+constexpr bool enableUpdateEbrInit = false;
+
+enum cpldI2cCmd
+{
+    commandEraseFlash = 0x0E,
+    commandDisableConfigInterface = 0x26,
+    commandReadStatusReg = 0x3C,
+    commandResetConfigFlash = 0x46,
+    commandProgramDone = 0x5E,
+    commandProgramPage = 0x70,
+    commandEnableConfigMode = 0x74,
+    commandReadFwVersion = 0xC0,
+    commandProgramUserCode = 0xC2,
+    commandReadDeviceId = 0xE0,
+    commandReadBusyFlag = 0xF0,
+};
+
+static uint8_t reverse_bit(uint8_t b)
+{
+    b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+    b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+    b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+    return b;
+}
+
+const std::map<std::string, std::vector<uint8_t>> xo2xo3DeviceIdMap = {
+    {"LCMXO3LF-4300C", {0x61, 0x2b, 0xc0, 0x43}},
+    {"LCMXO3LF-4300", {0x61, 0x2b, 0xc0, 0x43}},
+    {"LCMXO3LF-6900", {0x61, 0x2b, 0xd0, 0x43}},
+    {"LCMXO3D-4300", {0x01, 0x2e, 0x20, 0x43}},
+    {"LCMXO3D-9400", {0x21, 0x2e, 0x30, 0x43}},
+};
+
+static int findNumberSize(const std::string& end, const std::string& start,
+                          const std::string& line)
+{
+    auto pos1 = line.find(start);
+    auto pos2 = line.find(end);
+
+    if (pos1 == std::string::npos || pos2 == std::string::npos || pos1 >= pos2)
+    {
+        return false;
+    }
+
+    return static_cast<int>(pos2 - pos1 - 1);
+}
+
+bool CpldLatticeManager::jedFileParser()
+{
+    bool cfStart = false;
+    bool ufmStart = false; // for isLCMXO3D
+    bool ufmPrepare = false;
+    bool versionStart = false;
+    bool checksumStart = false;
+    bool ebrInitDataStart = false;
+    int numberSize = 0;
+
+    if (image == nullptr || imageSize == 0)
+    {
+        lg2::error(
+            "Error: JED file is empty or not found. Please check the file.");
+        return false;
+    }
+
+    // Convert binary data to a string
+    std::string content(reinterpret_cast<const char*>(image), imageSize);
+    // Use stringstream to simulate file reading
+    std::istringstream iss(content);
+    std::string line;
+
+    // Parsing JED file
+    while (getline(iss, line))
+    {
+        if (line.rfind(tagQF, 0) == 0)
+        {
+            numberSize = findNumberSize("*", "F", line);
+            if (numberSize <= 0)
+            {
+                lg2::error("Error in parsing QF tag");
+                return false;
+            }
+            static constexpr auto start = tagQF.length();
+            fwInfo.QF = std::stoul(line.substr(start, numberSize));
+
+            lg2::debug("QF Size = {QF}", "QF", fwInfo.QF);
+        }
+        else if (line.rfind(tagCFStart, 0) == 0)
+        {
+            cfStart = true;
+        }
+        else if (enableUpdateEbrInit && line.rfind(tagEbrInitData, 0) == 0)
+        {
+            ebrInitDataStart = true;
+        }
+        else if (ufmPrepare)
+        {
+            ufmPrepare = false;
+            ufmStart = true;
+            continue;
+        }
+        else if (line.rfind(tagUserCode, 0) == 0)
+        {
+            versionStart = true;
+        }
+        else if (line.rfind(tagChecksum, 0) == 0)
+        {
+            checksumStart = true;
+        }
+
+        if (line.rfind("NOTE DEVICE NAME:", 0) == 0)
+        {
+            lg2::error(line.c_str());
+            if (line.find(chip) != std::string::npos)
+            {
+                lg2::debug("[OK] The image device name match with chip name");
+            }
+            else
+            {
+                lg2::debug("STOP UPDATEING: The image not match with chip.");
+                return false;
+            }
+        }
+
+        if (cfStart)
+        {
+            // L000
+            if ((line.rfind(tagCFStart, 0)) && (line.size() != 1))
+            {
+                if ((line.rfind('0', 0) == 0) || (line.rfind('1', 0) == 0))
+                {
+                    while (!line.empty())
+                    {
+                        auto binaryStr = line.substr(0, 8);
+                        try
+                        {
+                            fwInfo.cfgData.push_back(
+                                std::stoi(binaryStr, 0, 2));
+                            line.erase(0, 8);
+                        }
+                        catch (const std::invalid_argument& error)
+                        {
+                            break;
+                        }
+                        catch (...)
+                        {
+                            lg2::error("Error while parsing CF section");
+                            return false;
+                        }
+                    }
+                }
+                else
+                {
+                    lg2::debug("CF size = {CF}", "CF", fwInfo.cfgData.size());
+                    cfStart = false;
+                    if (!ebrInitDataStart)
+                    {
+                        ufmPrepare = true;
+                    }
+                }
+            }
+        }
+        else if (enableUpdateEbrInit && ebrInitDataStart)
+        {
+            // NOTE EBR_INIT DATA
+            if ((line.rfind(tagEbrInitData, 0)) && (line.size() != 1))
+            {
+                if ((line.rfind('L', 0)) && (line.size() != 1))
+                {
+                    if ((line.rfind('0', 0) == 0) || (line.rfind('1', 0) == 0))
+                    {
+                        while (!line.empty())
+                        {
+                            auto binaryStr = line.substr(0, 8);
+                            try
+                            {
+                                fwInfo.cfgData.push_back(
+                                    std::stoi(binaryStr, 0, 2));
+                                line.erase(0, 8);
+                            }
+                            catch (const std::invalid_argument& error)
+                            {
+                                break;
+                            }
+                            catch (...)
+                            {
+                                lg2::error("Error while parsing CF section");
+                                return false;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        lg2::debug("CF size with EBR_INIT Data = {CF}", "CF",
+                                   fwInfo.cfgData.size());
+                        ebrInitDataStart = false;
+                        ufmPrepare = true;
+                    }
+                }
+            }
+        }
+        else if ((checksumStart) && (line.size() != 1))
+        {
+            checksumStart = false;
+            numberSize = findNumberSize("*", "C", line);
+            if (numberSize <= 0)
+            {
+                lg2::error("Error in parsing checksum");
+                return false;
+            }
+            static constexpr auto start = tagChecksum.length();
+            std::istringstream iss(line.substr(start, numberSize));
+            iss >> std::hex >> fwInfo.checksum;
+
+            lg2::debug("Checksum = {CHECKSUM}", "CHECKSUM", fwInfo.checksum);
+        }
+        else if (versionStart)
+        {
+            if ((line.rfind(tagUserCode, 0)) && (line.size() != 1))
+            {
+                versionStart = false;
+
+                if (line.rfind(tagUH, 0) == 0)
+                {
+                    numberSize = findNumberSize("*", "H", line);
+                    if (numberSize <= 0)
+                    {
+                        lg2::error("Error in parsing version");
+                        return false;
+                    }
+                    static constexpr auto start = tagUH.length();
+                    std::istringstream iss(line.substr(start, numberSize));
+                    iss >> std::hex >> fwInfo.version;
+
+                    lg2::debug("UserCode = {USERCODE}", "USERCODE",
+                               fwInfo.version);
+                }
+            }
+        }
+        else if (ufmStart)
+        {
+            if ((line.rfind('L', 0)) && (line.size() != 1))
+            {
+                if ((line.rfind('0', 0) == 0) || (line.rfind('1', 0) == 0))
+                {
+                    while (!line.empty())
+                    {
+                        auto binaryStr = line.substr(0, 8);
+                        try
+                        {
+                            fwInfo.ufmData.push_back(
+                                std::stoi(binaryStr, 0, 2));
+                            line.erase(0, 8);
+                        }
+                        catch (const std::invalid_argument& error)
+                        {
+                            break;
+                        }
+                        catch (...)
+                        {
+                            lg2::error("Error while parsing UFM section");
+                            return false;
+                        }
+                    }
+                }
+                else
+                {
+                    lg2::debug("UFM size = {UFM}", "UFM",
+                               fwInfo.ufmData.size());
+                    ufmStart = false;
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+bool CpldLatticeManager::verifyChecksum()
+{
+    // Compute check sum
+    unsigned int jedFileCheckSum = 0;
+    for (unsigned i = 0; i < fwInfo.cfgData.size(); i++)
+    {
+        jedFileCheckSum += reverse_bit(fwInfo.cfgData.at(i));
+    }
+    for (unsigned i = 0; i < fwInfo.ufmData.size(); i++)
+    {
+        jedFileCheckSum += reverse_bit(fwInfo.ufmData.at(i));
+    }
+    lg2::debug("jedFileCheckSum = {JEDFILECHECKSUM}", "JEDFILECHECKSUM",
+               jedFileCheckSum);
+    jedFileCheckSum = jedFileCheckSum & 0xffff;
+
+    if ((fwInfo.checksum != jedFileCheckSum) || (fwInfo.checksum == 0))
+    {
+        lg2::error("CPLD JED File CheckSum Error = {JEDFILECHECKSUM}",
+                   "JEDFILECHECKSUM", jedFileCheckSum);
+        return false;
+    }
+
+    lg2::debug("JED File Checksum compare success");
+    return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::readDeviceId()
+{
+    auto sched = context_friend::get_scheduler(ctx);
+    std::vector<uint8_t> command = {commandReadDeviceId, 0x0, 0x0, 0x0};
+    constexpr size_t resSize = 4;
+    std::vector<uint8_t> readData(resSize, 0);
+    bool success = co_await stdexec::starts_on(
+        sched, i2cInterface.sendReceive(command.data(), command.size(),
+                                        readData.data(), resSize));
+    if (!success)
+    {
+        lg2::error(
+            "Fail to read device Id. Please check the I2C bus and address.");
+        co_return false;
+    }
+
+    auto chipWantToUpdate = xo2xo3DeviceIdMap.find(chip);
+
+    if (chipWantToUpdate != xo2xo3DeviceIdMap.end() &&
+        chipWantToUpdate->second == readData)
+    {
+        if (chip.rfind("LCMXO3D", 0) == 0)
+        {
+            isLCMXO3D = true;
+            if (!target.empty() && target != "CFG0" && target != "CFG1")
+            {
+                lg2::error("Unknown target. Only CFG0 and CFG1 are supported.");
+                co_return false;
+            }
+        }
+
+        lg2::debug("Device ID match with chip. Chip name: {CHIPNAME}",
+                   "CHIPNAME", chip);
+        co_return true;
+    }
+
+    lg2::error(
+        "The device id not match with chip. Only the following chip names are supported: ");
+    for (const auto& chip : xo2xo3DeviceIdMap)
+    {
+        lg2::error(chip.first.c_str());
+    }
+    co_return false;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::enableProgramMode()
+{
+    auto sched = context_friend::get_scheduler(ctx);
+    std::vector<uint8_t> command = {commandEnableConfigMode, 0x08, 0x0, 0x0};
+    bool success = co_await stdexec::starts_on(
+        sched,
+        i2cInterface.sendReceive(command.data(), command.size(), nullptr, 0));
+
+    if (!success)
+    {
+        co_return false;
+    }
+
+    if (!(co_await waitBusyAndVerify()))
+    {
+        lg2::error("Wait busy and verify fail");
+        co_return false;
+    }
+    co_await sdbusplus::async::sleep_for(ctx, waitBusyTime);
+    co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::eraseFlash()
+{
+    auto sched = context_friend::get_scheduler(ctx);
+    std::vector<uint8_t> command;
+
+    if (isLCMXO3D)
+    {
+        /*
+        Erase the different internal
+        memories. The bit in YYY defines
+        which memory is erased in Flash
+        access mode.
+        Bit 1=Enable
+        8 Erase CFG0
+        9 Erase CFG1
+        10 Erase UFM0
+        11 Erase UFM1
+        12 Erase UFM2
+        13 Erase UFM3
+        14 Erase CSEC
+        15 Erase USEC
+        16 Erase PUBKEY
+        17 Erase AESKEY
+        18 Erase FEA
+        19 Reserved
+        commandEraseFlash = 0x0E, 0Y YY 00
+        */
+        if (target.empty() || target == "CFG0")
+        {
+            command = {commandEraseFlash, 0x00, 0x01, 0x00};
+        }
+        else if (target == "CFG1")
+        {
+            command = {commandEraseFlash, 0x00, 0x02, 0x00};
+        }
+        else
+        {
+            lg2::error("Error: unknown target.");
+            co_return false;
+        }
+    }
+    else
+    {
+        command = {commandEraseFlash, 0xC, 0x0, 0x0};
+    }
+
+    bool success = co_await stdexec::starts_on(
+        sched,
+        i2cInterface.sendReceive(command.data(), command.size(), nullptr, 0));
+    if (!success)
+    {
+        co_return false;
+    }
+
+    if (!(co_await waitBusyAndVerify()))
+    {
+        lg2::error("Wait busy and verify fail");
+        co_return false;
+    }
+    co_await sdbusplus::async::sleep_for(ctx, waitBusyTime);
+    co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::resetConfigFlash()
+{
+    auto sched = context_friend::get_scheduler(ctx);
+    std::vector<uint8_t> command;
+    if (isLCMXO3D)
+    {
+        /*
+        Set Page Address pointer to the
+        beginning of the different internal
+        Flash sectors. The bit in YYYY
+        defines which sector is selected.
+        Bit Flash sector selected
+        8 CFG0
+        9 CFG1
+        10 FEA
+        11 PUBKEY
+        12 AESKEY
+        13 CSEC
+        14 UFM0
+        15 UFM1
+        16 UFM2
+        17 UFM3
+        18 USEC
+        19 Reserved
+        20 Reserved
+        21 Reserved
+        22 Reserved
+        commandResetConfigFlash = 0x46, YY YY 00
+        */
+        if (target.empty() || target == "CFG0")
+        {
+            command = {commandResetConfigFlash, 0x00, 0x01, 0x00};
+        }
+        else if (target == "CFG1")
+        {
+            command = {commandResetConfigFlash, 0x00, 0x02, 0x00};
+        }
+        else
+        {
+            lg2::error(
+                "Error: unknown target. Only CFG0 and CFG1 are supported.");
+            co_return false;
+        }
+    }
+    else
+    {
+        command = {commandResetConfigFlash, 0x0, 0x0, 0x0};
+    }
+
+    co_return co_await stdexec::starts_on(
+        sched,
+        i2cInterface.sendReceive(command.data(), command.size(), nullptr, 0));
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::writeProgramPage()
+{
+    /*
+    Program one NVCM/Flash page. Can be
+    used to program the NVCM0/CFG or
+    NVCM1/UFM.
+    */
+    auto sched = context_friend::get_scheduler(ctx);
+    std::vector<uint8_t> command = {commandProgramPage, 0x0, 0x0, 0x01};
+    size_t iterSize = 16;
+
+    for (size_t i = 0; i < fwInfo.cfgData.size(); i += iterSize)
+    {
+        double progressRate =
+            ((double(i) / double(fwInfo.cfgData.size())) * 100);
+        std::cout << "Update :" << std::fixed << std::dec
+                  << std::setprecision(2) << progressRate << "% \r";
+
+        uint8_t len = ((i + iterSize) < fwInfo.cfgData.size())
+                          ? iterSize
+                          : (fwInfo.cfgData.size() - i);
+        std::vector<uint8_t> data = command;
+
+        data.insert(
+            data.end(), fwInfo.cfgData.begin() + static_cast<std::ptrdiff_t>(i),
+            fwInfo.cfgData.begin() + static_cast<std::ptrdiff_t>(i + len));
+
+        bool success = co_await stdexec::starts_on(
+            sched,
+            i2cInterface.sendReceive(data.data(), data.size(), nullptr, 0));
+        if (!success)
+        {
+            co_return false;
+        }
+
+        /*
+         Reference spec
+         Important! If don't sleep, it will take a long time to update.
+        */
+        co_await sdbusplus::async::sleep_for(ctx,
+                                             std::chrono::microseconds(200));
+
+        if (!(co_await waitBusyAndVerify()))
+        {
+            lg2::error("Wait busy and verify fail");
+            co_return false;
+        }
+
+        data.clear();
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::programUserCode()
+{
+    auto sched = context_friend::get_scheduler(ctx);
+    std::vector<uint8_t> command = {commandProgramUserCode, 0x0, 0x0, 0x0};
+    for (int i = 3; i >= 0; i--)
+    {
+        command.push_back((fwInfo.version >> (i * 8)) & 0xFF);
+    }
+    bool success = co_await stdexec::starts_on(
+        sched,
+        i2cInterface.sendReceive(command.data(), command.size(), nullptr, 0));
+
+    if (!success)
+    {
+        co_return false;
+    }
+
+    if (!(co_await waitBusyAndVerify()))
+    {
+        lg2::error("Wait busy and verify fail");
+        co_return false;
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::programDone()
+{
+    auto sched = context_friend::get_scheduler(ctx);
+    std::vector<uint8_t> command = {commandProgramDone, 0x0, 0x0, 0x0};
+    bool success = co_await stdexec::starts_on(
+        sched,
+        i2cInterface.sendReceive(command.data(), command.size(), nullptr, 0));
+
+    if (!success)
+    {
+        co_return false;
+    }
+    if (!(co_await waitBusyAndVerify()))
+    {
+        lg2::error("Wait busy and verify fail");
+        co_return false;
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::disableConfigInterface()
+{
+    auto sched = context_friend::get_scheduler(ctx);
+    std::vector<uint8_t> command = {commandDisableConfigInterface, 0x0, 0x0};
+
+    bool success = co_await stdexec::starts_on(
+        sched,
+        i2cInterface.sendReceive(command.data(), command.size(), nullptr, 0));
+
+    co_return success;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::waitBusyAndVerify()
+{
+    uint8_t retry = 0;
+
+    while (retry <= busyWaitmaxRetry)
+    {
+        uint8_t busyFlag = 0xff;
+
+        if (!(co_await readBusyFlag(busyFlag)))
+        {
+            lg2::error("Fail to read busy flag.");
+            co_return false;
+        }
+
+        if (busyFlag & busyFlagBit)
+        {
+            co_await sdbusplus::async::sleep_for(ctx, waitBusyTime);
+            retry++;
+            if (retry > busyWaitmaxRetry)
+            {
+                lg2::error(
+                    "Status Reg : Busy! Please check the I2C bus and address.");
+                co_return false;
+            }
+        }
+        else
+        {
+            break;
+        }
+    } // while loop busy check
+
+    // Check out status reg
+    uint8_t statusReg = 0xff;
+
+    if (!(co_await readStatusReg(statusReg)))
+    {
+        lg2::error("Fail to read status register.");
+        co_return false;
+    }
+
+    if (((statusReg >> busyOrReadyBit) & 1) == isReady &&
+        ((statusReg >> failOrOKBit) & 1) == isOK)
+    {
+        lg2::debug("Status Reg : OK");
+        co_return true;
+    }
+
+    lg2::error("Status Reg : Fail! Please check the I2C bus and address.");
+    co_return false;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::readBusyFlag(uint8_t& busyFlag)
+{
+    auto sched = context_friend::get_scheduler(ctx);
+    std::vector<uint8_t> command = {commandReadBusyFlag, 0x0, 0x0, 0x0};
+    constexpr size_t resSize = 1;
+    std::vector<uint8_t> readData(resSize, 0);
+    bool success = co_await stdexec::starts_on(
+        sched, i2cInterface.sendReceive(command.data(), command.size(),
+                                        readData.data(), resSize));
+
+    if (!success || (readData.size() != resSize))
+    {
+        co_return false;
+    }
+    busyFlag = readData.at(0);
+    co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::readStatusReg(
+    uint8_t& statusReg)
+{
+    auto sched = context_friend::get_scheduler(ctx);
+    std::vector<uint8_t> command = {commandReadStatusReg, 0x0, 0x0, 0x0};
+    constexpr size_t resSize = 4;
+    std::vector<uint8_t> readData(resSize, 0);
+    bool success = co_await stdexec::starts_on(
+        sched, i2cInterface.sendReceive(command.data(), command.size(),
+                                        readData.data(), resSize));
+
+    if (!success || (readData.size() != resSize))
+    {
+        co_return false;
+    }
+    /*
+    Read Status Register
+    [LSC_READ_STATUS]
+    0x3C 00 00 00 N/A YY YY YY YY Bit 1 0
+    12 Busy Ready
+    13 Fail OK
+        */
+    statusReg = readData.at(2);
+    co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::readUserCode(
+    uint32_t& userCode)
+{
+    auto sched = context_friend::get_scheduler(ctx);
+    std::vector<uint8_t> command = {commandReadFwVersion, 0x0, 0x0, 0x0};
+    constexpr size_t resSize = 4;
+    std::vector<uint8_t> readData(resSize, 0);
+    bool success = co_await stdexec::starts_on(
+        sched, i2cInterface.sendReceive(command.data(), command.size(),
+                                        readData.data(), resSize));
+
+    if (!success)
+    {
+        co_return false;
+    }
+
+    for (size_t i = 0; i < resSize; i++)
+    {
+        userCode |= readData.at(i) << ((3 - i) * 8);
+    }
+    co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::XO2XO3FamilyUpdate(
+    std::function<bool(int)> progressCallBack)
+{
+    if (progressCallBack == nullptr)
+    {
+        lg2::error("Error: progressCallBack is null.");
+        co_return false;
+    }
+
+    if (!(co_await readDeviceId()))
+    {
+        co_return false;
+    }
+    progressCallBack(10);
+
+    if (!jedFileParser())
+    {
+        lg2::error("JED file parsing failed");
+        co_return false;
+    }
+    progressCallBack(15);
+
+    if (!verifyChecksum())
+    {
+        lg2::error("Checksum verification failed");
+        co_return false;
+    }
+    progressCallBack(20);
+
+    if (!isLCMXO3D)
+    {
+        lg2::error("is not LCMXO3D");
+    }
+
+    lg2::debug("Starts to update ...");
+    lg2::debug("Enable program mode.");
+    progressCallBack(25);
+
+    co_await waitBusyAndVerify();
+
+    if (!(co_await enableProgramMode()))
+    {
+        lg2::error("Enable program mode failed.");
+        co_return false;
+    }
+    progressCallBack(30);
+
+    lg2::debug("Erase flash.");
+    if (!(co_await eraseFlash()))
+    {
+        lg2::error("Erase flash failed.");
+        co_return false;
+    }
+    progressCallBack(40);
+
+    lg2::debug("Reset config flash.");
+    if (!(co_await resetConfigFlash()))
+    {
+        lg2::error("Reset config flash failed.");
+        co_return false;
+    }
+    progressCallBack(50);
+
+    lg2::debug("Write program page ...");
+    if (!(co_await writeProgramPage()))
+    {
+        lg2::error("Write program page failed.");
+        co_return false;
+    }
+    lg2::debug("Write program page done.");
+    progressCallBack(60);
+
+    lg2::debug("Program user code.");
+    if (!(co_await programUserCode()))
+    {
+        lg2::error("Program user code failed.");
+        co_return false;
+    }
+    progressCallBack(70);
+
+    if (!(co_await programDone()))
+    {
+        lg2::error("Program not done.");
+        co_return false;
+    }
+    progressCallBack(80);
+
+    lg2::debug("Disable config interface.");
+    if (!(co_await disableConfigInterface()))
+    {
+        lg2::error("Disable Config Interface failed.");
+        co_return false;
+    }
+    progressCallBack(90);
+
+    lg2::debug("Update completed!");
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::updateFirmware(
+    std::function<bool(int)> progressCallBack)
+{
+    if (xo2xo3DeviceIdMap.find(chip) != xo2xo3DeviceIdMap.end())
+    {
+        co_return co_await XO2XO3FamilyUpdate(progressCallBack);
+    }
+    lg2::error("Unsupported chip type: {CHIP}", "CHIP", chip);
+    co_return false;
+}
+
+std::string uint32ToHexStr(uint32_t value)
+{
+    std::ostringstream oss;
+    oss << std::setfill('0') << std::setw(8) << std::hex << std::uppercase
+        << value;
+    return oss.str();
+}
+
+sdbusplus::async::task<bool> CpldLatticeManager::getVersion(
+    std::string& version)
+{
+    uint32_t userCode = 0;
+
+    if (target.empty())
+    {
+        if (!(co_await readUserCode(userCode)))
+        {
+            lg2::error("Read usercode failed.");
+            co_return false;
+        }
+
+        lg2::debug("CPLD version: {VERSION}", "VERSION", userCode);
+    }
+    else if (target == "CFG0" || target == "CFG1")
+    {
+        isLCMXO3D = true;
+        co_await waitBusyAndVerify();
+
+        if (!(co_await enableProgramMode()))
+        {
+            lg2::error("Enable program mode failed.");
+            co_return false;
+        }
+
+        if (!(co_await resetConfigFlash()))
+        {
+            lg2::error("Reset config flash failed.");
+            co_return false;
+        }
+
+        if (!(co_await readUserCode(userCode)))
+        {
+            lg2::error("Read usercode failed.");
+            co_return false;
+        }
+
+        if (!(co_await programDone()))
+        {
+            lg2::error("Program not done.");
+            co_return false;
+        }
+
+        if (!(co_await disableConfigInterface()))
+        {
+            lg2::error("Disable Config Interface failed.");
+            co_return false;
+        }
+
+        lg2::debug("CPLD {TARGET} version: {VERSION}", "TARGET", target,
+                   "VERSION", userCode);
+    }
+    else
+    {
+        lg2::error("Error: unknown target.");
+        co_return false;
+    }
+
+    if (userCode == 0)
+    {
+        lg2::error("User code is zero, cannot get version.");
+        co_return false;
+    }
+    version = uint32ToHexStr(userCode);
+    co_return true;
+}