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.cpp b/cpld/lattice/lattice_xo3_cpld.cpp
new file mode 100644
index 0000000..ee95ba4
--- /dev/null
+++ b/cpld/lattice/lattice_xo3_cpld.cpp
@@ -0,0 +1,384 @@
+#include "lattice_xo3_cpld.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <fstream>
+#include <vector>
+
+namespace phosphor::software::cpld
+{
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::readDeviceId()
+{
+    std::vector<uint8_t> request = {commandReadDeviceId, 0x0, 0x0, 0x0};
+    std::vector<uint8_t> response = {0, 0, 0, 0};
+
+    if (!i2cInterface.sendReceive(request, response))
+    {
+        lg2::error(
+            "Fail to read device Id. Please check the I2C bus and address.");
+        co_return false;
+    }
+
+    auto chipWantToUpdate = std::find_if(
+        supportedDeviceMap.begin(), supportedDeviceMap.end(),
+        [this](const auto& pair) {
+            auto chipModel =
+                getLatticeChipStr(pair.first, latticeStringType::modelString);
+            return chipModel == this->chip;
+        });
+
+    if (chipWantToUpdate != supportedDeviceMap.end() &&
+        chipWantToUpdate->second.deviceId == response)
+    {
+        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 doesn't match with chip.");
+    co_return false;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::eraseFlash()
+{
+    std::vector<uint8_t> request;
+    std::vector<uint8_t> response;
+
+    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")
+        {
+            request = {commandEraseFlash, 0x00, 0x01, 0x00};
+        }
+        else if (target == "CFG1")
+        {
+            request = {commandEraseFlash, 0x00, 0x02, 0x00};
+        }
+        else
+        {
+            lg2::error("Error: unknown target.");
+            co_return false;
+        }
+    }
+    else
+    {
+        request = {commandEraseFlash, 0xC, 0x0, 0x0};
+    }
+
+    if (!i2cInterface.sendReceive(request, response))
+    {
+        lg2::error("Failed to send erase flash request.");
+        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> LatticeXO3CPLD::writeProgramPage()
+{
+    /*
+    Program one NVCM/Flash page. Can be
+    used to program the NVCM0/CFG or
+    NVCM1/UFM.
+    */
+    size_t iterSize = 16;
+
+    for (size_t i = 0; (i * iterSize) < fwInfo.cfgData.size(); i++)
+    {
+        size_t byteOffset = i * iterSize;
+        double progressRate =
+            ((double(byteOffset) / double(fwInfo.cfgData.size())) * 100);
+        std::cout << "Update :" << std::fixed << std::dec
+                  << std::setprecision(2) << progressRate << "% \r";
+
+        uint8_t len = ((byteOffset + iterSize) < fwInfo.cfgData.size())
+                          ? iterSize
+                          : (fwInfo.cfgData.size() - byteOffset);
+        auto pageData = std::vector<uint8_t>(
+            fwInfo.cfgData.begin() + static_cast<std::ptrdiff_t>(byteOffset),
+            fwInfo.cfgData.begin() +
+                static_cast<std::ptrdiff_t>(byteOffset + len));
+
+        size_t retry = 0;
+        const size_t maxWriteRetry = 10;
+        while (retry < maxWriteRetry)
+        {
+            if (!(co_await programSinglePage(i, pageData)))
+            {
+                retry++;
+                continue;
+            }
+
+            if (!(co_await verifySinglePage(i, pageData)))
+            {
+                retry++;
+                continue;
+            }
+
+            break;
+        }
+
+        if (retry >= maxWriteRetry)
+        {
+            lg2::error("Program and verify page failed");
+            co_return false;
+        }
+    }
+
+    if (!(co_await waitBusyAndVerify()))
+    {
+        lg2::error("Wait busy and verify fail");
+        co_return false;
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::readUserCode(uint32_t& userCode)
+{
+    constexpr size_t resSize = 4;
+    std::vector<uint8_t> request = {commandReadFwVersion, 0x0, 0x0, 0x0};
+    std::vector<uint8_t> response(resSize, 0);
+
+    if (!i2cInterface.sendReceive(request, response))
+    {
+        lg2::error("Failed to send read user code request.");
+        co_return false;
+    }
+
+    for (size_t i = 0; i < resSize; i++)
+    {
+        userCode |= response.at(i) << ((3 - i) * 8);
+    }
+    co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::programUserCode()
+{
+    std::vector<uint8_t> request = {commandProgramUserCode, 0x0, 0x0, 0x0};
+    std::vector<uint8_t> response;
+    for (int i = 3; i >= 0; i--)
+    {
+        request.push_back((fwInfo.version >> (i * 8)) & 0xFF);
+    }
+
+    if (!i2cInterface.sendReceive(request, response))
+    {
+        lg2::error("Failed to send program user code request.");
+        co_return false;
+    }
+    if (!(co_await waitBusyAndVerify()))
+    {
+        lg2::error("Wait busy and verify fail");
+        co_return false;
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::prepareUpdate(const uint8_t* image,
+                                                           size_t imageSize)
+{
+    if (!(co_await readDeviceId()))
+    {
+        co_return false;
+    }
+
+    if (!jedFileParser(image, imageSize))
+    {
+        lg2::error("JED file parsing failed");
+        co_return false;
+    }
+    lg2::debug("JED file parsing success");
+
+    if (!verifyChecksum())
+    {
+        lg2::error("Checksum verification failed");
+        co_return false;
+    }
+
+    if (!isLCMXO3D)
+    {
+        lg2::error("is not LCMXO3D");
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::doUpdate()
+{
+    co_await waitBusyAndVerify();
+
+    if (!(co_await enableProgramMode()))
+    {
+        lg2::error("Enable program mode failed.");
+        co_return false;
+    }
+
+    if (!(co_await eraseFlash()))
+    {
+        lg2::error("Erase flash failed.");
+        co_return false;
+    }
+
+    if (!(co_await resetConfigFlash()))
+    {
+        lg2::error("Reset config flash failed.");
+        co_return false;
+    }
+
+    if (!(co_await writeProgramPage()))
+    {
+        lg2::error("Write program page failed.");
+        co_return false;
+    }
+
+    if (!(co_await programUserCode()))
+    {
+        lg2::error("Program user code failed.");
+        co_return false;
+    }
+
+    if (!(co_await programDone()))
+    {
+        lg2::error("Program not done.");
+        co_return false;
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::finishUpdate()
+{
+    // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Branch)
+    if (!(co_await disableConfigInterface()))
+    {
+        lg2::error("Disable Config Interface failed.");
+        co_return false;
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::programSinglePage(
+    uint16_t pageOffset, std::span<const uint8_t> pageData)
+{
+    // Set Page Offset
+    std::vector<uint8_t> emptyResp(0);
+    std::vector<uint8_t> setPageAddrCmd = {
+        commandSetPageAddress, 0x0, 0x0, 0x0, 0x00, 0x00, 0x00, 0x00};
+    setPageAddrCmd[6] = static_cast<uint8_t>(pageOffset >> 8); // high byte
+    setPageAddrCmd[7] = static_cast<uint8_t>(pageOffset);      // low byte
+
+    // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Branch)
+    bool success = co_await i2cInterface.sendReceive(
+        setPageAddrCmd.data(), setPageAddrCmd.size(), nullptr, 0);
+    if (!success)
+    {
+        lg2::error("Write page address failed");
+        co_return false;
+    }
+
+    // Write Page Data
+    constexpr uint8_t pageCount = 1;
+    std::vector<uint8_t> writeCmd = {commandProgramPage, 0x0, 0x0, pageCount};
+    writeCmd.insert(writeCmd.end(), pageData.begin(), pageData.end());
+
+    success = co_await i2cInterface.sendReceive(writeCmd.data(),
+                                                writeCmd.size(), nullptr, 0);
+    if (!success)
+    {
+        lg2::error("Write page data failed");
+        co_return false;
+    }
+
+    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;
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO3CPLD::verifySinglePage(
+    uint16_t pageOffset, std::span<const uint8_t> pageData)
+{
+    // Set Page Offset
+    std::vector<uint8_t> emptyResp(0);
+    std::vector<uint8_t> setPageAddrCmd = {
+        commandSetPageAddress, 0x0, 0x0, 0x0, 0x00, 0x00, 0x00, 0x00};
+    setPageAddrCmd[6] = static_cast<uint8_t>(pageOffset >> 8); // high byte
+    setPageAddrCmd[7] = static_cast<uint8_t>(pageOffset);      // low byte
+
+    if (!i2cInterface.sendReceive(setPageAddrCmd, emptyResp))
+    {
+        lg2::error("Write page address failed");
+        co_return false;
+    }
+
+    // Read Page Data
+    constexpr uint8_t pageCount = 1;
+    std::vector<uint8_t> readData(pageData.size());
+    std::vector<uint8_t> readCmd = {commandReadPage, 0x0, 0x0, pageCount};
+
+    if (!i2cInterface.sendReceive(readCmd, readData))
+    {
+        lg2::error("Read page data failed");
+        co_return false;
+    }
+
+    constexpr size_t pageSize = 16;
+    auto mismatch_pair =
+        std::mismatch(pageData.begin(), pageData.end(), readData.begin());
+    if (mismatch_pair.first != pageData.end())
+    {
+        size_t idx = std::distance(pageData.begin(), mismatch_pair.first);
+        lg2::error("Verify failed at {INDEX}", "INDEX",
+                   ((static_cast<size_t>(pageSize * pageOffset)) + idx));
+        co_return false;
+    }
+
+    co_return true;
+}
+
+} // namespace phosphor::software::cpld