cpld: Virtualize CpldLatticeManager for XO3/XO5 separation

This change makes `CpldLatticeManager` a virtual base class, allowing
other classes to inherit from it. This refactoring enables a cleaner
separation of XO3 and XO5 implementations while sharing common CPLD
management logic.

Test on harma:
```
1. Check firmware info
curl -k -u root:0penBmc -X GET
https://10.10.15.8/redfish/v1/UpdateService/FirmwareInventory/Harma_MB_CPLD_5688
{
    "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Harma_MB_CPLD_5688",
    "@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
    "Description": "Unknown image",
    "Id": "Harma_MB_CPLD_5688",
    "Name": "Software Inventory",
    "Status": {
    "Health": "OK",
    "HealthRollup": "OK",
    "State": "Enabled"
    },
    "Updateable": true,
    "Version": "00000220"
}
2. Trigger Update
curl -k -u root:0penBmc \
    -H "Content-Type:multipart/form-data" \
    -X POST \
    -F UpdateParameters="{\"Targets\":[\"${targetpath}\"], \
    \"@Redfish.OperationApplyTime\":\"Immediate\"};type=application/json" \
    -F "UpdateFile=@${fwpath};type=application/octet-stream" \
    https://${bmc}/redfish/v1/UpdateService/update-multipart
{
    "@odata.id": "/redfish/v1/TaskService/Tasks/0",
    "@odata.type": "#Task.v1_4_3.Task",
    "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."
    }
    ],
    "Name": "Task 0",
    "Payload": {
    "HttpHeaders": [],
    "HttpOperation": "POST",
    "TargetUri": "/redfish/v1/UpdateService/update-multipart"
    },
    "PercentComplete": 0,
    "StartTime": "2025-08-13T07:22:06+00:00",
    "TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
    "TaskState": "Running",
    "TaskStatus": "OK"
}
3. After AC cycle check firmware info again
curl -k -u root:0penBmc -X GET
https://10.10.15.8/redfish/v1/UpdateService/FirmwareInventory/Harma_MB_CPLD_5688
{
    "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Harma_MB_CPLD_5688",
    "@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
    "Description": "Unknown image",
    "Id": "Harma_MB_CPLD_5688",
    "Name": "Software Inventory",
    "Status": {
    "Health": "OK",
    "HealthRollup": "OK",
    "State": "Enabled"
    },
    "Updateable": true,
    "Version": "00000224"
}
```

Change-Id: Ic7265dbeeb9f93d4f466cba75ca38fc86342c689
Signed-off-by: Daniel Hsu <Daniel-Hsu@quantatw.com>
diff --git a/cpld/lattice/lattice_cpld_factory.cpp b/cpld/lattice/lattice_cpld_factory.cpp
new file mode 100644
index 0000000..ab0cb85
--- /dev/null
+++ b/cpld/lattice/lattice_cpld_factory.cpp
@@ -0,0 +1,91 @@
+#include "lattice_cpld_factory.hpp"
+
+#include "lattice_xo3_cpld.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace phosphor::software::cpld
+{
+
+std::unique_ptr<LatticeBaseCPLD> LatticeCPLDFactory::getLatticeCPLD()
+{
+    if (supportedDeviceMap.find(chipEnum) == supportedDeviceMap.end())
+    {
+        // invalid
+        lg2::error("Unsupported Lattice CPLD chip enum: {CHIPENUM}", "CHIPENUM",
+                   chipEnum);
+        return nullptr;
+    }
+
+    auto chipFamily = supportedDeviceMap.at(chipEnum).chipFamily;
+    auto chipModelStr =
+        getLatticeChipStr(chipEnum, latticeStringType::modelString);
+    switch (chipFamily)
+    {
+        case latticeChipFamily::XO2:
+        case latticeChipFamily::XO3:
+            return std::make_unique<LatticeXO3CPLD>(
+                CPLDInterface::ctx, CPLDInterface::bus, CPLDInterface::address,
+                chipModelStr, "CFG0", false);
+        default:
+            lg2::error("Unsupported Lattice CPLD chip family: {CHIPMODEL}",
+                       "CHIPMODEL", chipModelStr);
+            return nullptr;
+    }
+}
+
+sdbusplus::async::task<bool> LatticeCPLDFactory::updateFirmware(
+    bool /*force*/, const uint8_t* image, size_t imageSize,
+    std::function<bool(int)> progressCallBack)
+{
+    lg2::info("Updating Lattice CPLD firmware");
+    auto cpldManager = getLatticeCPLD();
+    if (cpldManager == nullptr)
+    {
+        lg2::error("CPLD manager is not initialized.");
+        co_return false;
+    }
+    co_return co_await cpldManager->updateFirmware(image, imageSize,
+                                                   progressCallBack);
+}
+
+sdbusplus::async::task<bool> LatticeCPLDFactory::getVersion(
+    std::string& version)
+{
+    lg2::info("Getting Lattice CPLD version");
+    auto cpldManager = getLatticeCPLD();
+    if (cpldManager == nullptr)
+    {
+        lg2::error("CPLD manager is not initialized.");
+        co_return 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& [chipEnum, info] : supportedDeviceMap)
+    {
+        auto typeStr =
+            getLatticeChipStr(chipEnum, latticeStringType::typeString);
+        CPLDFactory::instance().registerCPLD(
+            typeStr, [chipEnum](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<LatticeCPLDFactory>(
+                    ctx, chipName, chipEnum, bus, address);
+            });
+    }
+    return true;
+}();
+
+} // namespace