cpld: add update support for Lattice xo5
Enable firmware update capability for Lattice xo5 devices, allowing
the update flow to recognize and handle this device type.
Test on Santabarbara:
```
1. Check firmware
curl -k -u root:0penBmc -X GET
https://10.10.15.214/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_CPLD_6213
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_CPLD_6213",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Unknown image",
"Id": "Santabarbara_MB_CPLD_6213",
"Name": "Software Inventory",
"Status": {
"Health": "OK",
"HealthRollup": "OK",
"State": "Enabled"
},
"Updateable": true,
"Version": "70000003"
}
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-14T02:26:00+00:00",
"TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
"TaskState": "Running",
"TaskStatus": "OK"
}
3. Check task
curl -u root:0penBmc -k -X GET https://${bmc}/redfish/v1/TaskService/Tasks/0
{
"@odata.id": "/redfish/v1/TaskService/Tasks/0",
"@odata.type": "#Task.v1_4_3.Task",
"EndTime": "2025-08-14T02:28:32+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": "The task with Id '0' has changed to progress 1 percent complete.",
"MessageArgs": [
"0",
"1"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 50 percent complete.",
"MessageArgs": [
"0",
"50"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to progress 90 percent complete.",
"MessageArgs": [
"0",
"90"
],
"MessageId": "TaskEvent.1.0.TaskProgressChanged",
"MessageSeverity": "OK",
"Resolution": "None."
},
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '0' has changed to 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-multipart"
},
"PercentComplete": 100,
"StartTime": "2025-08-14T02:26:00+00:00",
"TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
"TaskState": "Completed",
"TaskStatus": "OK"
}
4. Check firmware again
curl -k -u root:0penBmc -X GET
https://10.10.15.214/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_CPLD_9204
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_CPLD_9204",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Unknown image",
"Id": "Santabarbara_MB_CPLD_9204",
"Name": "Software Inventory",
"Status": {
"Health": "OK",
"HealthRollup": "OK",
"State": "Enabled"
},
"Updateable": true,
"Version": "00000004"
}
```
Change-Id: Id0aef0d105138538851f4c6d3a5496ec8b724eea
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
index cb45c39..b51770f 100644
--- a/cpld/lattice/lattice_base_cpld.hpp
+++ b/cpld/lattice/lattice_base_cpld.hpp
@@ -19,6 +19,7 @@
LCMXO3LF_4300C,
LCMXO3D_4300,
LCMXO3D_9400,
+ LFMXO5_25,
UNSUPPORTED = -1,
};
@@ -37,6 +38,7 @@
{latticeChip::LCMXO3LF_4300C, "LCMXO3LF_4300C"},
{latticeChip::LCMXO3D_4300, "LCMXO3D_4300"},
{latticeChip::LCMXO3D_9400, "LCMXO3D_9400"},
+ {latticeChip::LFMXO5_25, "LFMXO5_25"},
};
auto chipString = chipStringMap.at(chip);
if (chipStringMap.find(chip) == chipStringMap.end())
@@ -63,6 +65,7 @@
{
XO2,
XO3,
+ XO5,
};
struct cpldInfo
@@ -82,6 +85,7 @@
{latticeChipFamily::XO3, {0x01, 0x2e, 0x20, 0x43}}},
{latticeChip::LCMXO3D_9400,
{latticeChipFamily::XO3, {0x21, 0x2e, 0x30, 0x43}}},
+ {latticeChip::LFMXO5_25, {latticeChipFamily::XO5, {}}},
};
struct cpldI2cInfo
diff --git a/cpld/lattice/lattice_cpld_factory.cpp b/cpld/lattice/lattice_cpld_factory.cpp
index ab0cb85..5f5fb58 100644
--- a/cpld/lattice/lattice_cpld_factory.cpp
+++ b/cpld/lattice/lattice_cpld_factory.cpp
@@ -1,6 +1,7 @@
#include "lattice_cpld_factory.hpp"
#include "lattice_xo3_cpld.hpp"
+#include "lattice_xo5_cpld.hpp"
#include <phosphor-logging/lg2.hpp>
@@ -27,6 +28,10 @@
return std::make_unique<LatticeXO3CPLD>(
CPLDInterface::ctx, CPLDInterface::bus, CPLDInterface::address,
chipModelStr, "CFG0", false);
+ case latticeChipFamily::XO5:
+ return std::make_unique<LatticeXO5CPLD>(
+ CPLDInterface::ctx, CPLDInterface::bus, CPLDInterface::address,
+ chipModelStr, "CFG0", false);
default:
lg2::error("Unsupported Lattice CPLD chip family: {CHIPMODEL}",
"CHIPMODEL", chipModelStr);
diff --git a/cpld/lattice/lattice_xo5_cpld.cpp b/cpld/lattice/lattice_xo5_cpld.cpp
new file mode 100644
index 0000000..75e0d48
--- /dev/null
+++ b/cpld/lattice/lattice_xo5_cpld.cpp
@@ -0,0 +1,377 @@
+#include "lattice_xo5_cpld.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace phosphor::software::cpld
+{
+
+constexpr std::chrono::milliseconds ReadyPollInterval(10);
+constexpr std::chrono::milliseconds ReadyTimeout(1000);
+
+enum class xo5Cmd : uint8_t
+{
+ sectorErase = 0xd8,
+ pageProgram = 0x02,
+ pageRead = 0x0b,
+ readUsercode = 0xc0
+};
+
+enum class xo5Status : uint8_t
+{
+ ready = 0x00,
+ notReady = 0xff
+};
+
+struct xo5Cfg
+{
+ static constexpr size_t pageSize = 256;
+ static constexpr size_t pagesPerBlock = 256;
+ static constexpr size_t blocksPerCfg = 11;
+};
+
+static bool getStartBlock(uint8_t cfg, uint8_t& startBlock)
+{
+ static constexpr std::array<uint8_t, 3> cfgStartBlocks = {0x01, 0x10, 0x1F};
+
+ if (cfg >= cfgStartBlocks.size())
+ {
+ return false;
+ }
+
+ startBlock = cfgStartBlocks[cfg];
+ return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO5CPLD::waitUntilReady(
+ std::chrono::milliseconds timeout)
+{
+ const auto endTime = std::chrono::steady_clock::now() + timeout;
+
+ auto readDummy = [this]() -> sdbusplus::async::task<bool> {
+ std::vector<uint8_t> request = {};
+ std::vector<uint8_t> response = {0xff};
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ lg2::error("Failed to read.");
+ co_return false;
+ }
+ if (response.at(0) == static_cast<uint8_t>(xo5Status::ready))
+ {
+ co_return true;
+ }
+ co_return false;
+ };
+
+ while (std::chrono::steady_clock::now() < endTime)
+ {
+ if (co_await readDummy())
+ {
+ co_return true;
+ }
+ co_await sdbusplus::async::sleep_for(ctx, ReadyPollInterval);
+ }
+
+ lg2::error("Timeout waiting for device ready");
+ co_return false;
+}
+
+sdbusplus::async::task<bool> LatticeXO5CPLD::eraseCfg()
+{
+ auto cfgIndex = (target == "CFG0") ? 0 : 1;
+ uint8_t startBlock;
+ if (!getStartBlock(cfgIndex, startBlock))
+ {
+ lg2::error("Error: invalid cfg index.");
+ co_return false;
+ }
+ const auto endBlock = startBlock + xo5Cfg::blocksPerCfg;
+
+ auto eraseBlock = [this](uint8_t block) -> sdbusplus::async::task<bool> {
+ std::vector<uint8_t> request;
+ std::vector<uint8_t> response = {};
+ request.reserve(4);
+ request.push_back(static_cast<uint8_t>(xo5Cmd::sectorErase));
+ request.push_back(block);
+ request.push_back(0x0);
+ request.push_back(0x0);
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ lg2::error("Failed to erase block");
+ co_return false;
+ }
+ co_return true;
+ };
+
+ for (size_t block = startBlock; block < endBlock; ++block)
+ {
+ if (!(co_await eraseBlock(block)))
+ {
+ lg2::error("Erase failed: Block {BLOCK}", "BLOCK", block);
+ co_return false;
+ }
+ if (!(co_await waitUntilReady(ReadyTimeout)))
+ {
+ lg2::error("Failed to wait until ready");
+ co_return false;
+ }
+ }
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO5CPLD::programPage(
+ uint8_t block, uint8_t page, const std::vector<uint8_t>& data)
+{
+ std::vector<uint8_t> request;
+ std::vector<uint8_t> response = {};
+ request.reserve(4 + data.size());
+ request.push_back(static_cast<uint8_t>(xo5Cmd::pageProgram));
+ request.push_back(block);
+ request.push_back(page);
+ request.push_back(0x0);
+ request.insert(request.end(), data.begin(), data.end());
+
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ co_return false;
+ }
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO5CPLD::programCfg()
+{
+ using diff_t = std::vector<uint8_t>::difference_type;
+
+ auto cfgIndex = (target == "CFG0") ? 0 : 1;
+ uint8_t startBlock;
+ if (!getStartBlock(cfgIndex, startBlock))
+ {
+ lg2::error("Error: invalid cfg index.");
+ co_return false;
+ }
+ const auto endBlock = startBlock + xo5Cfg::blocksPerCfg;
+ const auto& cfgData = fwInfo.cfgData;
+ const auto totalBytes = cfgData.size();
+ size_t bytesWritten = 0;
+
+ for (size_t block = startBlock; block < endBlock; ++block)
+ {
+ for (size_t page = 0; page < xo5Cfg::pagesPerBlock; ++page)
+ {
+ if (bytesWritten >= totalBytes)
+ {
+ co_return true;
+ }
+
+ auto offset = static_cast<diff_t>(bytesWritten);
+ auto remaining = static_cast<diff_t>(totalBytes - bytesWritten);
+ const auto chunkSize =
+ std::min(static_cast<diff_t>(xo5Cfg::pageSize), remaining);
+ std::vector<uint8_t> chunk(
+ std::next(cfgData.begin(), offset),
+ std::next(cfgData.begin(), offset + chunkSize));
+
+ auto success = false;
+ success |= co_await programPage(block, page, chunk);
+ co_await sdbusplus::async::sleep_for(ctx, ReadyPollInterval);
+ success |= co_await waitUntilReady(ReadyTimeout);
+ if (!success)
+ {
+ lg2::error("Failed to program block {BLOCK} page {PAGE}",
+ "BLOCK", block, "PAGE", page);
+ co_return false;
+ }
+ bytesWritten += chunkSize;
+ }
+ }
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO5CPLD::readPage(
+ uint8_t block, uint8_t page, std::vector<uint8_t>& data)
+{
+ if (data.empty())
+ {
+ lg2::error("Error: data vector is empty.");
+ co_return false;
+ }
+ std::vector<uint8_t> request = {};
+ std::vector<uint8_t> response = {};
+ request.reserve(4);
+ request.push_back(static_cast<uint8_t>(xo5Cmd::pageRead));
+ request.push_back(block);
+ request.push_back(page);
+ request.push_back(0x0);
+
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ co_return false;
+ }
+ lg2::debug("Read page {BLOCK} {PAGE} succeeded", "BLOCK", block, "PAGE",
+ page);
+ request.clear();
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+
+ if (!(co_await waitUntilReady(ReadyTimeout)))
+ {
+ co_return false;
+ }
+
+ if (!i2cInterface.sendReceive(request, data))
+ {
+ co_return false;
+ }
+
+ co_return data[0] == static_cast<uint8_t>(xo5Status::ready);
+}
+
+sdbusplus::async::task<bool> LatticeXO5CPLD::verifyCfg()
+{
+ using diff_t = std::vector<uint8_t>::difference_type;
+
+ auto cfgIndex = (target == "CFG0") ? 0 : 1;
+ uint8_t startBlock;
+ if (!getStartBlock(cfgIndex, startBlock))
+ {
+ lg2::error("Error: invalid cfg index.");
+ co_return false;
+ }
+ const auto endBlock = startBlock + xo5Cfg::blocksPerCfg;
+ const auto& cfgData = fwInfo.cfgData;
+ const auto totalBytes = cfgData.size();
+ uint8_t readBuffer[1 + xo5Cfg::pageSize];
+ size_t bytesVerified = 0;
+
+ for (size_t block = startBlock; block < endBlock; ++block)
+ {
+ for (size_t page = 0; page < xo5Cfg::pagesPerBlock; ++page)
+ {
+ if (bytesVerified >= totalBytes)
+ {
+ co_return true;
+ }
+
+ auto offset = static_cast<diff_t>(bytesVerified);
+ auto remaining = static_cast<diff_t>(totalBytes - bytesVerified);
+ const auto chunkSize =
+ std::min(static_cast<diff_t>(xo5Cfg::pageSize), remaining);
+
+ std::vector<uint8_t> expected(
+ std::next(cfgData.begin(), offset),
+ std::next(cfgData.begin(), offset + chunkSize));
+
+ std::vector<uint8_t> chunk;
+ {
+ std::vector<uint8_t> readVec(readBuffer,
+ readBuffer + 1 + chunkSize);
+
+ if (co_await readPage(block, page, readVec))
+ {
+ chunk.assign(readVec.begin() + 1, readVec.end());
+ }
+ else
+ {
+ chunk.clear();
+ }
+ }
+
+ if (chunk.empty())
+ {
+ lg2::error("Failed to read Block {BLOCK} Page {PAGE}", "BLOCK",
+ block, "PAGE", page);
+ co_return false;
+ }
+ if (!std::equal(chunk.begin(), chunk.end(), expected.begin()))
+ {
+ lg2::error("VERIFY FAILED: Block {BLOCK} Page {PAGE}", "BLOCK",
+ block, "PAGE", page);
+ co_return false;
+ }
+
+ bytesVerified += chunkSize;
+ }
+ }
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO5CPLD::readUserCode(uint32_t& userCode)
+{
+ constexpr size_t resSize = 5;
+ 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;
+ }
+
+ userCode |= response[4] << 24;
+ userCode |= response[3] << 16;
+ userCode |= response[2] << 8;
+ userCode |= response[1];
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO5CPLD::prepareUpdate(const uint8_t* image,
+ size_t imageSize)
+{
+ if (target.empty())
+ {
+ target = "CFG0";
+ }
+ else if (target != "CFG0" && target != "CFG1")
+ {
+ lg2::error("Error: unknown target.");
+ co_return false;
+ }
+
+ if (!jedFileParser(image, imageSize))
+ {
+ lg2::error("JED file parsing failed");
+ co_return false;
+ }
+ lg2::debug("JED file parsing success");
+
+ if (!(co_await waitUntilReady(ReadyTimeout)))
+ {
+ lg2::error("Error: Device not ready.");
+ co_return false;
+ }
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO5CPLD::doUpdate()
+{
+ lg2::debug("Erasing {TARGET}...", "TARGET", target);
+ if (!(co_await eraseCfg()))
+ {
+ lg2::error("Erase cfg data failed.");
+ co_return false;
+ }
+
+ lg2::debug("Programming {TARGET}...", "TARGET", target);
+ if (!(co_await programCfg()))
+ {
+ lg2::error("Program cfg data failed.");
+ co_return false;
+ }
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeXO5CPLD::finishUpdate()
+{
+ lg2::debug("Verifying {TARGET}...", "TARGET", target);
+ if (!(co_await verifyCfg()))
+ {
+ lg2::error("Verify cfg data failed.");
+ co_return false;
+ }
+ co_return true;
+}
+
+} // namespace phosphor::software::cpld
diff --git a/cpld/lattice/lattice_xo5_cpld.hpp b/cpld/lattice/lattice_xo5_cpld.hpp
new file mode 100644
index 0000000..efc301f
--- /dev/null
+++ b/cpld/lattice/lattice_xo5_cpld.hpp
@@ -0,0 +1,39 @@
+#include "lattice_base_cpld.hpp"
+
+namespace phosphor::software::cpld
+{
+
+class LatticeXO5CPLD : public LatticeBaseCPLD
+{
+ public:
+ LatticeXO5CPLD(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)
+ {}
+ ~LatticeXO5CPLD() override = default;
+ LatticeXO5CPLD(const LatticeXO5CPLD&) = delete;
+ LatticeXO5CPLD& operator=(const LatticeXO5CPLD&) = delete;
+ LatticeXO5CPLD(LatticeXO5CPLD&&) noexcept = delete;
+ LatticeXO5CPLD& operator=(LatticeXO5CPLD&&) 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;
+ sdbusplus::async::task<bool> readUserCode(uint32_t& userCode) override;
+
+ private:
+ sdbusplus::async::task<bool> waitUntilReady(
+ std::chrono::milliseconds timeout);
+ sdbusplus::async::task<bool> eraseCfg();
+ sdbusplus::async::task<bool> programCfg();
+ sdbusplus::async::task<bool> programPage(uint8_t block, uint8_t page,
+ const std::vector<uint8_t>& data);
+ sdbusplus::async::task<bool> verifyCfg();
+ sdbusplus::async::task<bool> readPage(uint8_t block, uint8_t page,
+ std::vector<uint8_t>& data);
+};
+
+} // namespace phosphor::software::cpld