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/common/i2c/i2c.cpp b/common/i2c/i2c.cpp
index 99de04b..6f8414d 100644
--- a/common/i2c/i2c.cpp
+++ b/common/i2c/i2c.cpp
@@ -37,41 +37,45 @@
     uint8_t readSize) const
 // NOLINTEND(readability-static-accessed-through-instance)
 {
+    bool result = true;
+
     if (fd <= 0)
     {
-        co_return false;
+        result = false;
     }
-
-    struct i2c_msg msg[2];
-    struct i2c_rdwr_ioctl_data readWriteData;
-    int msgIndex = 0;
-
-    if (writeSize)
+    else
     {
-        msg[msgIndex].addr = deviceNode;
-        msg[msgIndex].flags = 0;
-        msg[msgIndex].len = writeSize;
-        msg[msgIndex].buf = writeData;
-        msgIndex++;
-    }
+        struct i2c_msg msg[2];
+        struct i2c_rdwr_ioctl_data readWriteData;
+        int msgIndex = 0;
 
-    if (readSize)
-    {
-        msg[msgIndex].addr = deviceNode;
-        msg[msgIndex].flags = I2C_M_RD;
-        msg[msgIndex].len = readSize;
-        msg[msgIndex].buf = readData;
-        msgIndex++;
-    }
+        if (writeSize)
+        {
+            msg[msgIndex].addr = deviceNode;
+            msg[msgIndex].flags = 0;
+            msg[msgIndex].len = writeSize;
+            msg[msgIndex].buf = writeData;
+            msgIndex++;
+        }
 
-    readWriteData.msgs = msg;
-    readWriteData.nmsgs = msgIndex;
+        if (readSize)
+        {
+            msg[msgIndex].addr = deviceNode;
+            msg[msgIndex].flags = I2C_M_RD;
+            msg[msgIndex].len = readSize;
+            msg[msgIndex].buf = readData;
+            msgIndex++;
+        }
 
-    if (ioctl(fd, I2C_RDWR, &readWriteData) < 0)
-    {
-        co_return false;
+        readWriteData.msgs = msg;
+        readWriteData.nmsgs = msgIndex;
+
+        if (ioctl(fd, I2C_RDWR, &readWriteData) < 0)
+        {
+            result = false;
+        }
     }
-    co_return true;
+    co_return result;
 }
 
 int I2C::close() const
diff --git a/cpld/README.md b/cpld/README.md
new file mode 100644
index 0000000..db8af3d
--- /dev/null
+++ b/cpld/README.md
@@ -0,0 +1,21 @@
+# CPLD Update Daemon
+
+This daemon implements the update process for CPLD attached via I2C bus.
+
+## Configuration Example
+
+The following shows an example of entity manager configuration record for
+lattice LCMXO3LF-4300C CPLD.
+
+```json
+{
+  "Address": "0x40",
+  "Bus": 5,
+  "FirmwareInfo": {
+    "CompatibleHardware": "com.meta.Hardware.CPLD",
+    "VendorIANA": 40981
+  },
+  "Name": "LCMXO3LF_4300C",
+  "Type": "LatticeXO3Firmware"
+}
+```
diff --git a/cpld/cpld.cpp b/cpld/cpld.cpp
new file mode 100644
index 0000000..2392e48
--- /dev/null
+++ b/cpld/cpld.cpp
@@ -0,0 +1,52 @@
+#include "cpld.hpp"
+
+namespace phosphor::software::cpld
+{
+
+sdbusplus::async::task<bool> CPLDDevice::updateDevice(const uint8_t* image,
+                                                      size_t image_size)
+{
+    if (cpldInterface == nullptr)
+    {
+        lg2::error("CPLD interface is not initialized");
+        co_return false;
+    }
+    else
+    {
+        setUpdateProgress(1);
+        if (!(co_await cpldInterface->updateFirmware(
+                false, image, image_size, [this](int percent) -> bool {
+                    return this->setUpdateProgress(percent);
+                })))
+        {
+            lg2::error("Failed to update CPLD firmware");
+            co_return false;
+        }
+
+        setUpdateProgress(100);
+        lg2::info("Successfully updated CPLD");
+        co_return true;
+    }
+}
+
+sdbusplus::async::task<bool> CPLDDevice::getVersion(std::string& version)
+{
+    if (cpldInterface == nullptr)
+    {
+        lg2::error("CPLD interface is not initialized");
+        co_return false;
+    }
+    else
+    {
+        if (!(co_await cpldInterface->getVersion(version)))
+        {
+            lg2::error("Failed to get CPLD version");
+            co_return false;
+        }
+
+        lg2::info("CPLD version: {VERSION}", "VERSION", version);
+        co_return true;
+    }
+}
+
+} // namespace phosphor::software::cpld
diff --git a/cpld/cpld.hpp b/cpld/cpld.hpp
new file mode 100644
index 0000000..a6bf18b
--- /dev/null
+++ b/cpld/cpld.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "common/include/device.hpp"
+#include "common/include/software_manager.hpp"
+#include "cpld_interface.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async/context.hpp>
+
+PHOSPHOR_LOG2_USING;
+
+namespace ManagerInf = phosphor::software::manager;
+
+namespace phosphor::software::cpld
+{
+
+class CPLDDevice : public Device
+{
+  public:
+    CPLDDevice(sdbusplus::async::context& ctx, const std::string& chiptype,
+               const std::string& chipname, const uint16_t& bus,
+               const uint8_t& address, SoftwareConfig& config,
+               ManagerInf::SoftwareManager* parent) :
+        Device(ctx, config, parent,
+               {RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset}),
+        cpldInterface(CPLDFactory::instance().create(chiptype, ctx, chipname,
+                                                     bus, address))
+    {}
+
+    using Device::softwareCurrent;
+    sdbusplus::async::task<bool> updateDevice(const uint8_t* image,
+                                              size_t image_size) final;
+    sdbusplus::async::task<bool> getVersion(std::string& version);
+
+  private:
+    std::unique_ptr<CPLDInterface> cpldInterface;
+};
+
+} // namespace phosphor::software::cpld
diff --git a/cpld/cpld_interface.cpp b/cpld/cpld_interface.cpp
new file mode 100644
index 0000000..a67e03e
--- /dev/null
+++ b/cpld/cpld_interface.cpp
@@ -0,0 +1,43 @@
+#include "cpld_interface.hpp"
+
+#include "lattice/interface.hpp"
+
+namespace phosphor::software::cpld
+{
+
+CPLDFactory& CPLDFactory::instance()
+{
+    static CPLDFactory factory;
+    return factory;
+}
+
+void CPLDFactory::registerCPLD(const std::string& vendorName, Creator creator)
+{
+    creators[vendorName] = std::move(creator);
+}
+
+std::unique_ptr<CPLDInterface> CPLDFactory::create(
+    const std::string& vendorName, sdbusplus::async::context& ctx,
+    const std::string& chipName, uint16_t bus, uint8_t address) const
+{
+    auto it = creators.find(vendorName);
+    if (it != creators.end())
+    {
+        return (it->second)(ctx, chipName, bus, address);
+    }
+    return nullptr;
+}
+
+std::vector<std::string> CPLDFactory::getConfigs()
+{
+    std::vector<std::string> configs;
+    configs.reserve(creators.size());
+
+    std::transform(creators.begin(), creators.end(),
+                   std::back_inserter(configs),
+                   [](const auto& pair) { return pair.first; });
+
+    return configs;
+}
+
+} // namespace phosphor::software::cpld
diff --git a/cpld/cpld_interface.hpp b/cpld/cpld_interface.hpp
new file mode 100644
index 0000000..6dec356
--- /dev/null
+++ b/cpld/cpld_interface.hpp
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <sdbusplus/async.hpp>
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace phosphor::software::cpld
+{
+
+class CPLDInterface
+{
+  public:
+    CPLDInterface(sdbusplus::async::context& ctx, const std::string& chipname,
+                  uint16_t bus, uint8_t address) :
+        ctx(ctx), chipname(chipname), bus(bus), address(address)
+    {}
+
+    virtual ~CPLDInterface() = default;
+    CPLDInterface(const CPLDInterface&) = delete;
+    CPLDInterface& operator=(const CPLDInterface&) = delete;
+    CPLDInterface(CPLDInterface&&) = delete;
+    CPLDInterface& operator=(CPLDInterface&&) = delete;
+
+    virtual sdbusplus::async::task<bool> updateFirmware(
+        bool force, const uint8_t* image, size_t imageSize,
+        std::function<bool(int)> progress = nullptr) = 0;
+
+    virtual sdbusplus::async::task<bool> getVersion(std::string& version) = 0;
+
+  protected:
+    sdbusplus::async::context& ctx;
+    std::string chipname;
+    uint16_t bus;
+    uint8_t address;
+};
+
+class CPLDFactory
+{
+  public:
+    using Creator = std::function<std::unique_ptr<CPLDInterface>(
+        sdbusplus::async::context& ctx, const std::string& chipName,
+        uint16_t bus, uint8_t address)>;
+    using ConfigProvider = std::function<std::vector<std::string>()>;
+
+    static CPLDFactory& instance();
+
+    void registerCPLD(const std::string& vendorName, Creator creator);
+
+    std::unique_ptr<CPLDInterface> create(
+        const std::string& vendorName, sdbusplus::async::context& ctx,
+        const std::string& chipName, uint16_t bus, uint8_t address) const;
+
+    std::vector<std::string> getConfigs();
+
+  private:
+    std::unordered_map<std::string, Creator> creators;
+};
+
+} // namespace phosphor::software::cpld
diff --git a/cpld/cpld_software_manager.cpp b/cpld/cpld_software_manager.cpp
new file mode 100644
index 0000000..faf14a5
--- /dev/null
+++ b/cpld/cpld_software_manager.cpp
@@ -0,0 +1,93 @@
+#include "cpld_software_manager.hpp"
+
+#include "common/include/dbus_helper.hpp"
+#include "cpld.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+
+PHOSPHOR_LOG2_USING;
+
+using namespace phosphor::software::cpld;
+
+sdbusplus::async::task<bool> CPLDSoftwareManager::initDevice(
+    const std::string& service, const std::string& path, SoftwareConfig& config)
+{
+    std::string configIface =
+        "xyz.openbmc_project.Configuration." + config.configType;
+
+    std::optional<uint64_t> busNo = co_await dbusGetRequiredProperty<uint64_t>(
+        ctx, service, path, configIface, "Bus");
+    std::optional<uint64_t> address =
+        co_await dbusGetRequiredProperty<uint64_t>(ctx, service, path,
+                                                   configIface, "Address");
+    std::optional<std::string> chipType =
+        co_await dbusGetRequiredProperty<std::string>(ctx, service, path,
+                                                      configIface, "Type");
+    std::optional<std::string> chipName =
+        co_await dbusGetRequiredProperty<std::string>(ctx, service, path,
+                                                      configIface, "Name");
+
+    if (!busNo.has_value() || !address.has_value() || !chipType.has_value() ||
+        !chipName.has_value())
+    {
+        error("missing config property");
+        co_return false;
+    }
+
+    lg2::debug(
+        "CPLD device type: {TYPE} - {NAME} on Bus: {BUS} at Address: {ADDR}",
+        "TYPE", chipType.value(), "NAME", chipName.value(), "BUS",
+        busNo.value(), "ADDR", address.value());
+
+    auto cpld = std::make_unique<CPLDDevice>(
+        ctx, chipType.value(), chipName.value(), busNo.value(), address.value(),
+        config, this);
+
+    std::string version = "unknown";
+    if (!(co_await cpld->getVersion(version)))
+    {
+        lg2::error("Failed to get CPLD version for {NAME}", "NAME",
+                   chipName.value());
+    }
+
+    std::unique_ptr<Software> software = std::make_unique<Software>(ctx, *cpld);
+
+    software->setVersion(version);
+
+    std::set<RequestedApplyTimes> allowedApplyTimes = {
+        RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset};
+
+    software->enableUpdate(allowedApplyTimes);
+
+    cpld->softwareCurrent = std::move(software);
+
+    devices.insert({config.objectPath, std::move(cpld)});
+
+    co_return true;
+}
+
+void CPLDSoftwareManager::start()
+{
+    std::vector<std::string> configIntfs;
+    auto configs = CPLDFactory::instance().getConfigs();
+    configIntfs.reserve(configs.size());
+    for (const auto& config : configs)
+    {
+        configIntfs.push_back("xyz.openbmc_project.Configuration." + config);
+    }
+
+    ctx.spawn(initDevices(configIntfs));
+    ctx.run();
+}
+
+int main()
+{
+    sdbusplus::async::context ctx;
+
+    CPLDSoftwareManager cpldSoftwareManager(ctx);
+
+    cpldSoftwareManager.start();
+
+    return 0;
+}
diff --git a/cpld/cpld_software_manager.hpp b/cpld/cpld_software_manager.hpp
new file mode 100644
index 0000000..37530cf
--- /dev/null
+++ b/cpld/cpld_software_manager.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "common/include/software_manager.hpp"
+
+namespace phosphor::software::cpld
+{
+
+class CPLDSoftwareManager : public phosphor::software::manager::SoftwareManager
+{
+  public:
+    CPLDSoftwareManager(sdbusplus::async::context& ctx) :
+        SoftwareManager(ctx, "CPLD")
+    {}
+
+    sdbusplus::async::task<bool> initDevice(const std::string& service,
+                                            const std::string& path,
+                                            SoftwareConfig& config) final;
+
+    void start();
+};
+
+} // namespace phosphor::software::cpld
diff --git a/cpld/lattice/interface.cpp b/cpld/lattice/interface.cpp
new file mode 100644
index 0000000..21663f0
--- /dev/null
+++ b/cpld/lattice/interface.cpp
@@ -0,0 +1,63 @@
+#include "interface.hpp"
+
+#include "lattice.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace phosphor::software::cpld
+{
+
+const std::vector<std::string> supportedTypes = {
+    "LatticeXO2Firmware",
+    "LatticeXO3Firmware",
+};
+
+sdbusplus::async::task<bool> LatticeCPLD::updateFirmware(
+    bool /*force*/, const uint8_t* image, size_t imageSize,
+    std::function<bool(int)> progressCallBack)
+{
+    lg2::info("Updating Lattice CPLD firmware");
+
+    std::replace(chipname.begin(), chipname.end(), '_', '-');
+    auto cpldManager = std::make_unique<CpldLatticeManager>(
+        ctx, bus, address, image, imageSize, chipname, "CFG0", false);
+
+    co_return co_await cpldManager->updateFirmware(progressCallBack);
+}
+
+sdbusplus::async::task<bool> LatticeCPLD::getVersion(std::string& version)
+{
+    lg2::info("Getting Lattice CPLD version");
+
+    std::replace(chipname.begin(), chipname.end(), '_', '-');
+    auto cpldManager = std::make_unique<CpldLatticeManager>(
+        ctx, bus, address, nullptr, 0, chipname, "CFG0", false);
+
+    co_return co_await cpldManager->getVersion(version);
+}
+
+} // namespace phosphor::software::cpld
+
+// Factory function to create lattice CPLD device
+namespace
+{
+using namespace phosphor::software::cpld;
+
+// Register all the CPLD type with the CPLD factory
+const bool vendorRegistered = [] {
+    for (const auto& type : supportedTypes)
+    {
+        CPLDFactory::instance().registerCPLD(
+            type,
+            [](sdbusplus::async::context& ctx, const std::string& chipname,
+               uint16_t bus, uint8_t address) {
+                // Create and return a LatticeCPLD instance
+                // Pass the parameters to the constructor
+                return std::make_unique<LatticeCPLD>(ctx, chipname, bus,
+                                                     address);
+            });
+    }
+    return true;
+}();
+
+} // namespace
diff --git a/cpld/lattice/interface.hpp b/cpld/lattice/interface.hpp
new file mode 100644
index 0000000..67d0915
--- /dev/null
+++ b/cpld/lattice/interface.hpp
@@ -0,0 +1,21 @@
+#include "cpld/cpld_interface.hpp"
+
+namespace phosphor::software::cpld
+{
+
+class LatticeCPLD : public CPLDInterface
+{
+  public:
+    LatticeCPLD(sdbusplus::async::context& ctx, const std::string& chipname,
+                uint16_t bus, uint8_t address) :
+        CPLDInterface(ctx, chipname, bus, address)
+    {}
+
+    sdbusplus::async::task<bool> updateFirmware(
+        bool force, const uint8_t* image, size_t imageSize,
+        std::function<bool(int)> progress) final;
+
+    sdbusplus::async::task<bool> getVersion(std::string& version) final;
+};
+
+} // namespace phosphor::software::cpld
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;
+}
diff --git a/cpld/lattice/lattice.hpp b/cpld/lattice/lattice.hpp
new file mode 100644
index 0000000..0bdc2bb
--- /dev/null
+++ b/cpld/lattice/lattice.hpp
@@ -0,0 +1,63 @@
+#pragma once
+#include "common/include/i2c/i2c.hpp"
+
+#include <chrono>
+#include <iostream>
+#include <string_view>
+#include <utility>
+
+struct cpldI2cInfo
+{
+    unsigned long int QF; // Quantity of Fuses
+    unsigned int* UFM;    // User Flash Memory
+    unsigned int version;
+    unsigned int checksum;
+    std::vector<uint8_t> cfgData;
+    std::vector<uint8_t> ufmData;
+};
+
+class CpldLatticeManager
+{
+  public:
+    CpldLatticeManager(sdbusplus::async::context& ctx, const uint16_t bus,
+                       const uint8_t address, const uint8_t* image,
+                       size_t imageSize, const std::string& chip,
+                       const std::string& target, const bool debugMode) :
+        ctx(ctx), image(image), imageSize(imageSize), chip(chip),
+        target(target), debugMode(debugMode),
+        i2cInterface(phosphor::i2c::I2C(bus, address))
+    {}
+    sdbusplus::async::task<bool> updateFirmware(
+        std::function<bool(int)> progressCallBack);
+    sdbusplus::async::task<bool> getVersion(std::string& version);
+
+  private:
+    sdbusplus::async::context& ctx;
+    cpldI2cInfo fwInfo{};
+    const uint8_t* image;
+    size_t imageSize;
+    std::string chip;
+    std::string target;
+    bool isLCMXO3D = false;
+    bool debugMode = false;
+    phosphor::i2c::I2C i2cInterface;
+
+    sdbusplus::async::task<bool> XO2XO3FamilyUpdate(
+        std::function<bool(int)> progressCallBack);
+
+    int indexof(const char* str, const char* ptn);
+    bool jedFileParser();
+    bool verifyChecksum();
+    sdbusplus::async::task<bool> readDeviceId();
+    sdbusplus::async::task<bool> enableProgramMode();
+    sdbusplus::async::task<bool> eraseFlash();
+    sdbusplus::async::task<bool> resetConfigFlash();
+    sdbusplus::async::task<bool> writeProgramPage();
+    sdbusplus::async::task<bool> programUserCode();
+    sdbusplus::async::task<bool> programDone();
+    sdbusplus::async::task<bool> disableConfigInterface();
+    sdbusplus::async::task<bool> readBusyFlag(uint8_t& busyFlag);
+    sdbusplus::async::task<bool> readStatusReg(uint8_t& statusReg);
+    sdbusplus::async::task<bool> waitBusyAndVerify();
+    sdbusplus::async::task<bool> readUserCode(uint32_t& userCode);
+};
diff --git a/cpld/meson.build b/cpld/meson.build
new file mode 100644
index 0000000..2ab8793
--- /dev/null
+++ b/cpld/meson.build
@@ -0,0 +1,31 @@
+cpld_src = files('cpld.cpp', 'cpld_interface.cpp', 'cpld_software_manager.cpp')
+
+cpld_vendor_src = files('lattice/interface.cpp', 'lattice/lattice.cpp')
+
+executable(
+    'phosphor-cpld-software-update',
+    cpld_src,
+    cpld_vendor_src,
+    include_directories: [include_directories('.'), common_include, libi2c_inc],
+    dependencies: [
+        pdi_dep,
+        phosphor_logging_dep,
+        sdbusplus_dep,
+        libpldm_dep,
+        libi2c_dep,
+    ],
+    link_with: [libpldmutil, software_common_lib, libi2c_dev],
+    link_args: '-li2c',
+    install_dir: get_option('libexecdir') / 'phosphor-code-mgmt',
+    install: true,
+)
+
+systemd_system_unit_dir = dependency('systemd').get_variable(
+    'systemdsystemunitdir',
+    pkgconfig_define: ['prefix', get_option('prefix')],
+)
+
+install_data(
+    'xyz.openbmc_project.Software.CPLD.service',
+    install_dir: systemd_system_unit_dir,
+)
diff --git a/cpld/xyz.openbmc_project.Software.CPLD.service b/cpld/xyz.openbmc_project.Software.CPLD.service
new file mode 100644
index 0000000..4ec7273
--- /dev/null
+++ b/cpld/xyz.openbmc_project.Software.CPLD.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=CPLD Code Update Daemon
+After=xyz.openbmc_project.ObjectMapper
+After=xyz.openbmc_project.EntityManager.service
+
+[Service]
+Restart=always
+Type=dbus
+BusName=xyz.openbmc_project.Software.CPLD
+RemainAfterExit=no
+ExecStart=/usr/libexec/phosphor-code-mgmt/phosphor-cpld-software-update
+
+[Install]
+WantedBy=multi-user.target
diff --git a/meson.build b/meson.build
index 6a03c9a..f1a5428 100644
--- a/meson.build
+++ b/meson.build
@@ -72,6 +72,8 @@
     'i2cvr-software-update',
 ).allowed() or get_option(
     'eepromdevice-software-update',
+).allowed() or get_option(
+    'cpld-software-update',
 ).allowed()
 
 if common_build
@@ -90,8 +92,13 @@
     subdir('bios')
 endif
 
-if get_option('i2cvr-software-update').allowed()
+if get_option('i2cvr-software-update').allowed() or get_option(
+    'cpld-software-update',
+).allowed()
     subdir('common/i2c/')
+endif
+
+if get_option('i2cvr-software-update').allowed()
     subdir('i2c-vr')
 endif
 
@@ -99,6 +106,10 @@
     subdir('eeprom-device')
 endif
 
+if get_option('cpld-software-update').allowed()
+    subdir('cpld')
+endif
+
 if build_tests.allowed()
     subdir('test')
 endif
diff --git a/meson.options b/meson.options
index c178e69..6c09029 100644
--- a/meson.options
+++ b/meson.options
@@ -57,6 +57,13 @@
 )
 
 option(
+    'cpld-software-update',
+    type: 'feature',
+    value: 'enabled',
+    description: 'Enable update of CPLD',
+)
+
+option(
     'side-switch-on-boot',
     type: 'feature',
     value: 'enabled',