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_base_cpld.hpp b/cpld/lattice/lattice_base_cpld.hpp
new file mode 100644
index 0000000..cb45c39
--- /dev/null
+++ b/cpld/lattice/lattice_base_cpld.hpp
@@ -0,0 +1,167 @@
+#pragma once
+#include "common/include/i2c/i2c.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <chrono>
+#include <iostream>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+
+namespace phosphor::software::cpld
+{
+
+enum class latticeChip
+{
+    LCMXO2_4000HC,
+    LCMXO3LF_2100C,
+    LCMXO3LF_4300C,
+    LCMXO3D_4300,
+    LCMXO3D_9400,
+    UNSUPPORTED = -1,
+};
+
+enum class latticeStringType
+{
+    typeString,
+    modelString,
+};
+
+inline std::string getLatticeChipStr(latticeChip chip,
+                                     latticeStringType stringType)
+{
+    static const std::unordered_map<latticeChip, std::string> chipStringMap = {
+        {latticeChip::LCMXO2_4000HC, "LCMXO2_4000HC"},
+        {latticeChip::LCMXO3LF_2100C, "LCMXO3LF_2100C"},
+        {latticeChip::LCMXO3LF_4300C, "LCMXO3LF_4300C"},
+        {latticeChip::LCMXO3D_4300, "LCMXO3D_4300"},
+        {latticeChip::LCMXO3D_9400, "LCMXO3D_9400"},
+    };
+    auto chipString = chipStringMap.at(chip);
+    if (chipStringMap.find(chip) == chipStringMap.end())
+    {
+        lg2::error("Unsupported chip enum: {CHIPENUM}", "CHIPENUM", chip);
+        return "";
+    }
+
+    switch (stringType)
+    {
+        case latticeStringType::typeString:
+            return std::string("Lattice" + chipString + "Firmware");
+        case latticeStringType::modelString:
+            std::replace(chipString.begin(), chipString.end(), '_', '-');
+            return chipString;
+        default:
+            lg2::error("Unsupported string type: {STRINGTYPE}", "STRINGTYPE",
+                       stringType);
+            return "";
+    }
+};
+
+enum class latticeChipFamily
+{
+    XO2,
+    XO3,
+};
+
+struct cpldInfo
+{
+    latticeChipFamily chipFamily;
+    std::vector<uint8_t> deviceId;
+};
+
+const std::map<latticeChip, cpldInfo> supportedDeviceMap = {
+    {latticeChip::LCMXO2_4000HC,
+     {latticeChipFamily::XO2, {0x01, 0x2b, 0xc0, 0x43}}},
+    {latticeChip::LCMXO3LF_2100C,
+     {latticeChipFamily::XO3, {0x61, 0x2b, 0xb0, 0x43}}},
+    {latticeChip::LCMXO3LF_4300C,
+     {latticeChipFamily::XO3, {0x61, 0x2b, 0xc0, 0x43}}},
+    {latticeChip::LCMXO3D_4300,
+     {latticeChipFamily::XO3, {0x01, 0x2e, 0x20, 0x43}}},
+    {latticeChip::LCMXO3D_9400,
+     {latticeChipFamily::XO3, {0x21, 0x2e, 0x30, 0x43}}},
+};
+
+struct cpldI2cInfo
+{
+    unsigned long int fuseQuantity;
+    unsigned int* userFlashMemory;
+    unsigned int version;
+    unsigned int checksum;
+    std::vector<uint8_t> cfgData;
+    std::vector<uint8_t> ufmData;
+};
+
+enum cpldI2cCmd
+{
+    commandEraseFlash = 0x0E,
+    commandDisableConfigInterface = 0x26,
+    commandReadStatusReg = 0x3C,
+    commandResetConfigFlash = 0x46,
+    commandProgramDone = 0x5E,
+    commandProgramPage = 0x70,
+    commandReadPage = 0x73,
+    commandEnableConfigMode = 0x74,
+    commandSetPageAddress = 0xB4,
+    commandReadFwVersion = 0xC0,
+    commandProgramUserCode = 0xC2,
+    commandReadDeviceId = 0xE0,
+    commandReadBusyFlag = 0xF0,
+};
+
+constexpr std::chrono::milliseconds waitBusyTime(200);
+
+class LatticeBaseCPLD
+{
+  public:
+    LatticeBaseCPLD(sdbusplus::async::context& ctx, const uint16_t bus,
+                    const uint8_t address, const std::string& chip,
+                    const std::string& target, const bool debugMode) :
+        ctx(ctx), chip(chip), target(target), debugMode(debugMode),
+        i2cInterface(phosphor::i2c::I2C(bus, address))
+    {}
+    virtual ~LatticeBaseCPLD() = default;
+    LatticeBaseCPLD(const LatticeBaseCPLD&) = delete;
+    LatticeBaseCPLD& operator=(const LatticeBaseCPLD&) = delete;
+    LatticeBaseCPLD(LatticeBaseCPLD&&) noexcept = delete;
+    LatticeBaseCPLD& operator=(LatticeBaseCPLD&&) noexcept = delete;
+
+    sdbusplus::async::task<bool> updateFirmware(
+        const uint8_t* image, size_t imageSize,
+        std::function<bool(int)> progressCallBack);
+
+    sdbusplus::async::task<bool> getVersion(std::string& version);
+
+  protected:
+    sdbusplus::async::context& ctx;
+    cpldI2cInfo fwInfo{};
+    std::string chip;
+    std::string target;
+    std::vector<uint8_t> sumOnly;
+    bool isLCMXO3D = false;
+    bool debugMode = false;
+    phosphor::i2c::I2C i2cInterface;
+
+    virtual sdbusplus::async::task<bool> prepareUpdate(const uint8_t*,
+                                                       size_t) = 0;
+    virtual sdbusplus::async::task<bool> doUpdate() = 0;
+    virtual sdbusplus::async::task<bool> finishUpdate() = 0;
+
+    bool jedFileParser(const uint8_t* image, size_t imageSize);
+    bool verifyChecksum();
+    sdbusplus::async::task<bool> enableProgramMode();
+    sdbusplus::async::task<bool> resetConfigFlash();
+    sdbusplus::async::task<bool> programDone();
+    sdbusplus::async::task<bool> disableConfigInterface();
+    sdbusplus::async::task<bool> waitBusyAndVerify();
+
+  private:
+    virtual sdbusplus::async::task<bool> readUserCode(uint32_t&) = 0;
+    sdbusplus::async::task<bool> readBusyFlag(uint8_t& busyFlag);
+    sdbusplus::async::task<bool> readStatusReg(uint8_t& statusReg);
+    static std::string uint32ToHexStr(uint32_t value);
+};
+
+} // namespace phosphor::software::cpld