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_xo3_cpld.hpp b/cpld/lattice/lattice_xo3_cpld.hpp
new file mode 100644
index 0000000..99da64c
--- /dev/null
+++ b/cpld/lattice/lattice_xo3_cpld.hpp
@@ -0,0 +1,38 @@
+#include "lattice_base_cpld.hpp"
+
+namespace phosphor::software::cpld
+{
+
+class LatticeXO3CPLD : public LatticeBaseCPLD
+{
+  public:
+    LatticeXO3CPLD(sdbusplus::async::context& ctx, const uint16_t bus,
+                   const uint8_t address, const std::string& chip,
+                   const std::string& target, const bool debugMode) :
+        LatticeBaseCPLD(ctx, bus, address, chip, target, debugMode)
+    {}
+    ~LatticeXO3CPLD() override = default;
+    LatticeXO3CPLD(const LatticeXO3CPLD&) = delete;
+    LatticeXO3CPLD& operator=(const LatticeXO3CPLD&) = delete;
+    LatticeXO3CPLD(LatticeXO3CPLD&&) noexcept = delete;
+    LatticeXO3CPLD& operator=(LatticeXO3CPLD&&) noexcept = delete;
+
+  protected:
+    sdbusplus::async::task<bool> prepareUpdate(const uint8_t* image,
+                                               size_t imageSize) override;
+    sdbusplus::async::task<bool> doUpdate() override;
+    sdbusplus::async::task<bool> finishUpdate() override;
+
+  private:
+    sdbusplus::async::task<bool> readUserCode(uint32_t& userCode) override;
+    sdbusplus::async::task<bool> readDeviceId();
+    sdbusplus::async::task<bool> eraseFlash();
+    sdbusplus::async::task<bool> writeProgramPage();
+    sdbusplus::async::task<bool> programUserCode();
+    sdbusplus::async::task<bool> programSinglePage(
+        uint16_t pageOffset, std::span<const uint8_t> pageData);
+    sdbusplus::async::task<bool> verifySinglePage(
+        uint16_t pageOffset, std::span<const uint8_t> pageData);
+};
+
+} // namespace phosphor::software::cpld