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/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);
+};