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/interface.cpp b/cpld/lattice/interface.cpp
deleted file mode 100644
index b2734ce..0000000
--- a/cpld/lattice/interface.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-#include "interface.hpp"
-
-#include "lattice.hpp"
-
-#include <phosphor-logging/lg2.hpp>
-
-namespace phosphor::software::cpld
-{
-
-sdbusplus::async::task<bool> LatticeCPLD::updateFirmware(
- bool /*force*/, const uint8_t* image, size_t imageSize,
- std::function<bool(int)> progressCallBack)
-{
- lg2::info("Updating Lattice CPLD firmware");
-
- std::replace(chipname.begin(), chipname.end(), '_', '-');
- auto cpldManager = std::make_unique<CpldLatticeManager>(
- ctx, bus, address, image, imageSize, chipname, "CFG0", false);
-
- co_return co_await cpldManager->updateFirmware(progressCallBack);
-}
-
-sdbusplus::async::task<bool> LatticeCPLD::getVersion(std::string& version)
-{
- lg2::info("Getting Lattice CPLD version");
-
- std::replace(chipname.begin(), chipname.end(), '_', '-');
- auto cpldManager = std::make_unique<CpldLatticeManager>(
- ctx, bus, address, nullptr, 0, chipname, "CFG0", false);
-
- co_return co_await cpldManager->getVersion(version);
-}
-
-} // namespace phosphor::software::cpld
-
-// Factory function to create lattice CPLD device
-namespace
-{
-using namespace phosphor::software::cpld;
-
-// Register all the CPLD type with the CPLD factory
-const bool vendorRegistered = [] {
- for (const auto& [type, info] : supportedDeviceMap)
- {
- auto typeStr = std::string(type);
- CPLDFactory::instance().registerCPLD(
- type, [info](sdbusplus::async::context& ctx,
- const std::string& /*chipName*/, uint16_t bus,
- uint8_t address) {
- // Create and return a LatticeCPLD instance
- // Pass the parameters to the constructor
- return std::make_unique<LatticeCPLD>(ctx, info.chipName, bus,
- address);
- });
- }
- return true;
-}();
-
-} // namespace
diff --git a/cpld/lattice/interface.hpp b/cpld/lattice/interface.hpp
deleted file mode 100644
index 67d0915..0000000
--- a/cpld/lattice/interface.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#include "cpld/cpld_interface.hpp"
-
-namespace phosphor::software::cpld
-{
-
-class LatticeCPLD : public CPLDInterface
-{
- public:
- LatticeCPLD(sdbusplus::async::context& ctx, const std::string& chipname,
- uint16_t bus, uint8_t address) :
- CPLDInterface(ctx, chipname, bus, address)
- {}
-
- sdbusplus::async::task<bool> updateFirmware(
- bool force, const uint8_t* image, size_t imageSize,
- std::function<bool(int)> progress) final;
-
- sdbusplus::async::task<bool> getVersion(std::string& version) final;
-};
-
-} // namespace phosphor::software::cpld
diff --git a/cpld/lattice/lattice.cpp b/cpld/lattice/lattice.cpp
deleted file mode 100644
index 2fb7c86..0000000
--- a/cpld/lattice/lattice.cpp
+++ /dev/null
@@ -1,916 +0,0 @@
-#include "lattice.hpp"
-
-#include <phosphor-logging/lg2.hpp>
-
-#include <algorithm>
-#include <cstddef>
-#include <fstream>
-#include <map>
-#include <numeric>
-#include <thread>
-#include <vector>
-
-constexpr uint8_t busyWaitmaxRetry = 30;
-constexpr uint8_t busyFlagBit = 0x80;
-constexpr std::chrono::milliseconds waitBusyTime(200);
-
-static constexpr std::string_view tagFuseQuantity = "QF";
-static constexpr std::string_view tagUserCodeHex = "UH";
-static constexpr std::string_view tagCFStart = "L000";
-static constexpr std::string_view tagData = "NOTE TAG DATA";
-static constexpr std::string_view tagUserFlashMemory = "NOTE USER MEMORY DATA";
-static constexpr std::string_view tagChecksum = "C";
-static constexpr std::string_view tagUserCode = "NOTE User Electronic";
-static constexpr std::string_view tagEbrInitData = "NOTE EBR_INIT DATA";
-static constexpr std::string_view tagEndConfig = "NOTE END CONFIG DATA";
-static constexpr std::string_view tagDevName = "NOTE DEVICE NAME";
-
-constexpr uint8_t isOK = 0;
-constexpr uint8_t isReady = 0;
-constexpr uint8_t busyOrReadyBit = 4;
-constexpr uint8_t failOrOKBit = 5;
-
-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,
-};
-
-static uint8_t reverse_bit(uint8_t b)
-{
- b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
- b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
- b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
- return b;
-}
-
-std::string uint32ToHexStr(uint32_t value)
-{
- std::ostringstream oss;
- oss << std::setfill('0') << std::setw(8) << std::hex << std::uppercase
- << value;
- return oss.str();
-}
-
-bool CpldLatticeManager::jedFileParser()
-{
- enum class ParseState
- {
- none,
- cfg,
- endCfg,
- ufm,
- checksum,
- userCode
- };
- ParseState state = ParseState::none;
-
- if (image == nullptr || imageSize == 0)
- {
- lg2::error(
- "Error: JED file is empty or not found. Please check the file.");
- return false;
- }
-
- std::string content(reinterpret_cast<const char*>(image), imageSize);
- std::istringstream iss(content);
- std::string line;
-
- auto pushPage = [](std::string& line, std::vector<uint8_t>& sector) {
- if (line[0] == '0' || line[0] == '1')
- {
- while (line.size() >= 8)
- {
- try
- {
- sector.push_back(static_cast<uint8_t>(
- std::stoi(line.substr(0, 8), 0, 2)));
- line.erase(0, 8);
- }
- catch (...)
- {
- break;
- }
- }
- }
- };
-
- while (getline(iss, line))
- {
- if (!line.empty() && line.back() == '\r')
- {
- line.pop_back();
- }
- if (line.empty())
- {
- continue;
- }
-
- if (line.starts_with(tagFuseQuantity))
- {
- ssize_t numberSize = static_cast<ssize_t>(line.find('*')) -
- static_cast<ssize_t>(line.find('F')) - 1;
- if (numberSize > 0)
- {
- fwInfo.fuseQuantity = std::stoul(
- line.substr(tagFuseQuantity.length(), numberSize));
- lg2::debug("fuseQuantity Size = {QFSIZE}", "QFSIZE",
- fwInfo.fuseQuantity);
- }
- }
- else if (line.starts_with(tagCFStart) ||
- line.starts_with(tagEbrInitData))
- {
- state = ParseState::cfg;
- continue;
- }
- else if (line.starts_with(tagEndConfig))
- {
- state = ParseState::endCfg;
- continue;
- }
- else if (line.starts_with(tagUserFlashMemory) ||
- line.starts_with(tagData))
- {
- state = ParseState::ufm;
- continue;
- }
- else if (line.starts_with(tagUserCode))
- {
- state = ParseState::userCode;
- continue;
- }
- else if (line.starts_with(tagChecksum))
- {
- state = ParseState::checksum;
- }
- else if (line.starts_with(tagDevName))
- {
- lg2::debug("{DEVNAME}", "DEVNAME", line);
- if (line.find(chip) == std::string::npos)
- {
- lg2::debug("STOP UPDATING: The image does not match the chip.");
- return -1;
- }
- }
-
- switch (state)
- {
- case ParseState::cfg:
- pushPage(line, fwInfo.cfgData);
- break;
- case ParseState::endCfg:
- pushPage(line, sumOnly);
- break;
- case ParseState::ufm:
- pushPage(line, fwInfo.ufmData);
- break;
- case ParseState::checksum:
- if (line.size() > 1)
- {
- state = ParseState::none;
- ssize_t numberSize =
- static_cast<ssize_t>(line.find('*')) -
- static_cast<ssize_t>(line.find('C')) - 1;
- if (numberSize <= 0)
- {
- lg2::debug("Error in parsing checksum");
- return -1;
- }
- static constexpr auto start = tagChecksum.length();
- std::istringstream iss(line.substr(start, numberSize));
- iss >> std::hex >> fwInfo.checksum;
- lg2::debug("Checksum = 0x{CHECKSUM}", "CHECKSUM",
- fwInfo.checksum);
- }
- break;
- case ParseState::userCode:
- if (line.starts_with(tagUserCodeHex))
- {
- state = ParseState::none;
- ssize_t numberSize =
- static_cast<ssize_t>(line.find('*')) -
- static_cast<ssize_t>(line.find('H')) - 1;
- if (numberSize <= 0)
- {
- lg2::debug("Error in parsing usercode");
- return -1;
- }
- std::istringstream iss(
- line.substr(tagUserCodeHex.length(), numberSize));
- iss >> std::hex >> fwInfo.version;
- lg2::debug("UserCode = 0x{USERCODE}", "USERCODE",
- fwInfo.version);
- }
- break;
- default:
- break;
- }
- }
-
- lg2::debug("CFG Size = {CFGSIZE}", "CFGSIZE", fwInfo.cfgData.size());
- if (!fwInfo.ufmData.empty())
- {
- lg2::debug("userFlashMemory size = {UFMSIZE}", "UFMSIZE",
- fwInfo.ufmData.size());
- }
-
- return true;
-}
-
-bool CpldLatticeManager::verifyChecksum()
-{
- uint32_t calculated = 0U;
- auto addByte = [](uint32_t sum, uint8_t byte) {
- return sum + reverse_bit(byte);
- };
-
- calculated = std::accumulate(fwInfo.cfgData.begin(), fwInfo.cfgData.end(),
- calculated, addByte);
- calculated =
- std::accumulate(sumOnly.begin(), sumOnly.end(), calculated, addByte);
- calculated = std::accumulate(fwInfo.ufmData.begin(), fwInfo.ufmData.end(),
- calculated, addByte);
-
- lg2::debug("Calculated checksum = {CALCULATED}", "CALCULATED", lg2::hex,
- calculated);
- lg2::debug("Checksum from JED file = {JEDFILECHECKSUM}", "JEDFILECHECKSUM",
- lg2::hex, fwInfo.checksum);
-
- if (fwInfo.checksum != (calculated & 0xFFFF))
- {
- lg2::error("JED file checksum compare fail, "
- "Calculated checksum = {CALCULATED}, "
- "Checksum from JED file = {JEDFILECHECKSUM}",
- "CALCULATED", lg2::hex, calculated, "JEDFILECHECKSUM",
- lg2::hex, fwInfo.checksum);
- return false;
- }
-
- lg2::debug("JED file checksum compare success");
- return true;
-}
-
-sdbusplus::async::task<bool> CpldLatticeManager::readDeviceId()
-{
- std::vector<uint8_t> request = {commandReadDeviceId, 0x0, 0x0, 0x0};
- constexpr size_t resSize = 4;
- std::vector<uint8_t> readData(resSize, 0);
- bool success = co_await i2cInterface.sendReceive(
- request.data(), request.size(), readData.data(), resSize);
-
- if (!success)
- {
- 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) {
- return pair.second.chipName == this->chip;
- });
-
- if (chipWantToUpdate != supportedDeviceMap.end() &&
- chipWantToUpdate->second.deviceId == readData)
- {
- 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> CpldLatticeManager::enableProgramMode()
-{
- std::vector<uint8_t> request = {commandEnableConfigMode, 0x08, 0x0, 0x0};
- std::vector<uint8_t> response;
-
- if (!i2cInterface.sendReceive(request, response))
- {
- lg2::error("Failed to send enable program mode 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> CpldLatticeManager::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> CpldLatticeManager::resetConfigFlash()
-{
- std::vector<uint8_t> request;
- std::vector<uint8_t> response;
- if (isLCMXO3D)
- {
- /*
- Set Page Address pointer to the
- beginning of the different internal
- Flash sectors. The bit in YYYY
- defines which sector is selected.
- Bit Flash sector selected
- 8 CFG0
- 9 CFG1
- 10 FEA
- 11 PUBKEY
- 12 AESKEY
- 13 CSEC
- 14 UFM0
- 15 UFM1
- 16 UFM2
- 17 UFM3
- 18 USEC
- 19 Reserved
- 20 Reserved
- 21 Reserved
- 22 Reserved
- commandResetConfigFlash = 0x46, YY YY 00
- */
- if (target.empty() || target == "CFG0")
- {
- request = {commandResetConfigFlash, 0x00, 0x01, 0x00};
- }
- else if (target == "CFG1")
- {
- request = {commandResetConfigFlash, 0x00, 0x02, 0x00};
- }
- else
- {
- lg2::error(
- "Error: unknown target. Only CFG0 and CFG1 are supported.");
- co_return false;
- }
- }
- else
- {
- request = {commandResetConfigFlash, 0x0, 0x0, 0x0};
- }
-
- co_return i2cInterface.sendReceive(request, response);
-}
-
-sdbusplus::async::task<bool> CpldLatticeManager::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> CpldLatticeManager::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;
-}
-
-sdbusplus::async::task<bool> CpldLatticeManager::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> CpldLatticeManager::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> CpldLatticeManager::programDone()
-{
- std::vector<uint8_t> request = {commandProgramDone, 0x0, 0x0, 0x0};
- std::vector<uint8_t> response;
-
- if (!i2cInterface.sendReceive(request, response))
- {
- lg2::error("Failed to send program done 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> CpldLatticeManager::disableConfigInterface()
-{
- std::vector<uint8_t> request = {commandDisableConfigInterface, 0x0, 0x0};
- std::vector<uint8_t> response;
- co_return i2cInterface.sendReceive(request, response);
-}
-
-sdbusplus::async::task<bool> CpldLatticeManager::waitBusyAndVerify()
-{
- uint8_t retry = 0;
-
- while (retry <= busyWaitmaxRetry)
- {
- uint8_t busyFlag = 0xff;
-
- auto readBusyFlagResult = co_await readBusyFlag(busyFlag);
- if (!readBusyFlagResult)
- {
- lg2::error("Fail to read busy flag.");
- co_return false;
- }
-
- if (busyFlag & busyFlagBit)
- {
- co_await sdbusplus::async::sleep_for(ctx, waitBusyTime);
- retry++;
- if (retry > busyWaitmaxRetry)
- {
- lg2::error(
- "Status Reg : Busy! Please check the I2C bus and address.");
- co_return false;
- }
- }
- else
- {
- break;
- }
- } // while loop busy check
-
- // Check out status reg
- auto statusReg = std::make_unique<uint8_t>(0xff);
-
- if (!(co_await readStatusReg(*statusReg)))
- {
- lg2::error("Fail to read status register.");
- co_return false;
- }
-
- if (((*statusReg >> busyOrReadyBit) & 1) == isReady &&
- ((*statusReg >> failOrOKBit) & 1) == isOK)
- {
- lg2::debug("Status Reg : OK");
- co_return true;
- }
-
- lg2::error("Status Reg : Fail! Please check the I2C bus and address.");
- co_return false;
-}
-
-sdbusplus::async::task<bool> CpldLatticeManager::readBusyFlag(uint8_t& busyFlag)
-{
- constexpr size_t resSize = 1;
- std::vector<uint8_t> request = {commandReadBusyFlag, 0x0, 0x0, 0x0};
- std::vector<uint8_t> response(resSize, 0);
-
- auto success = i2cInterface.sendReceive(request, response);
- if (!success && response.size() != resSize)
- {
- co_return false;
- }
- busyFlag = response.at(0);
- co_return true;
-}
-
-sdbusplus::async::task<bool> CpldLatticeManager::readStatusReg(
- uint8_t& statusReg)
-{
- std::vector<uint8_t> request = {commandReadStatusReg, 0x0, 0x0, 0x0};
- std::vector<uint8_t> response(4, 0);
-
- if (!i2cInterface.sendReceive(request, response))
- {
- lg2::error("Failed to send read status register request.");
- co_return false;
- }
- /*
- Read Status Register
- [LSC_READ_STATUS]
- 0x3C 00 00 00 N/A YY YY YY YY Bit 1 0
- 12 Busy Ready
- 13 Fail OK
- */
- statusReg = response.at(2);
- co_return true;
-}
-
-sdbusplus::async::task<bool> CpldLatticeManager::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> CpldLatticeManager::XO2XO3FamilyUpdate(
- std::function<bool(int)> progressCallBack)
-{
- if (progressCallBack == nullptr)
- {
- lg2::error("Error: progressCallBack is null.");
- co_return false;
- }
-
- if (!(co_await readDeviceId()))
- {
- co_return false;
- }
- progressCallBack(10);
-
- if (!jedFileParser())
- {
- lg2::error("JED file parsing failed");
- co_return false;
- }
- progressCallBack(15);
-
- if (!verifyChecksum())
- {
- lg2::error("Checksum verification failed");
- co_return false;
- }
- progressCallBack(20);
-
- if (!isLCMXO3D)
- {
- lg2::error("is not LCMXO3D");
- }
-
- lg2::debug("Starts to update ...");
- lg2::debug("Enable program mode.");
- progressCallBack(25);
-
- co_await waitBusyAndVerify();
-
- if (!(co_await enableProgramMode()))
- {
- lg2::error("Enable program mode failed.");
- co_return false;
- }
- progressCallBack(30);
-
- lg2::debug("Erase flash.");
- if (!(co_await eraseFlash()))
- {
- lg2::error("Erase flash failed.");
- co_return false;
- }
- progressCallBack(40);
-
- lg2::debug("Reset config flash.");
- if (!(co_await resetConfigFlash()))
- {
- lg2::error("Reset config flash failed.");
- co_return false;
- }
- progressCallBack(50);
-
- lg2::debug("Write program page ...");
- if (!(co_await writeProgramPage()))
- {
- lg2::error("Write program page failed.");
- co_return false;
- }
- lg2::debug("Write program page done.");
- progressCallBack(60);
-
- lg2::debug("Program user code.");
- if (!(co_await programUserCode()))
- {
- lg2::error("Program user code failed.");
- co_return false;
- }
- progressCallBack(70);
-
- if (!(co_await programDone()))
- {
- lg2::error("Program not done.");
- co_return false;
- }
- progressCallBack(80);
-
- lg2::debug("Disable config interface.");
- if (!(co_await disableConfigInterface()))
- {
- lg2::error("Disable Config Interface failed.");
- co_return false;
- }
- progressCallBack(90);
-
- lg2::debug("Update completed!");
-
- co_return true;
-}
-
-sdbusplus::async::task<bool> CpldLatticeManager::updateFirmware(
- std::function<bool(int)> progressCallBack)
-{
- co_return co_await XO2XO3FamilyUpdate(progressCallBack);
-}
-
-sdbusplus::async::task<bool> CpldLatticeManager::getVersion(
- std::string& version)
-{
- auto userCode = std::make_unique<uint32_t>(0);
-
- if (target.empty())
- {
- if (!(co_await readUserCode(*userCode)))
- {
- lg2::error("Read usercode failed.");
- co_return false;
- }
-
- lg2::debug("CPLD version: {VERSION}", "VERSION", *userCode);
- }
- else if (target == "CFG0" || target == "CFG1")
- {
- isLCMXO3D = true;
- co_await waitBusyAndVerify();
-
- if (!(co_await enableProgramMode()))
- {
- lg2::error("Enable program mode failed.");
- co_return false;
- }
-
- if (!(co_await resetConfigFlash()))
- {
- lg2::error("Reset config flash failed.");
- co_return false;
- }
-
- if (!(co_await readUserCode(*userCode)))
- {
- lg2::error("Read usercode failed.");
- co_return false;
- }
-
- if (!(co_await programDone()))
- {
- lg2::error("Program not done.");
- co_return false;
- }
-
- if (!(co_await disableConfigInterface()))
- {
- lg2::error("Disable Config Interface failed.");
- co_return false;
- }
-
- lg2::debug("CPLD {TARGET} version: {VERSION}", "TARGET", target,
- "VERSION", *userCode);
- }
- else
- {
- lg2::error("Error: unknown target.");
- co_return false;
- }
-
- if (*userCode == 0)
- {
- lg2::error("User code is zero, cannot get version.");
- co_return false;
- }
- version = uint32ToHexStr(*userCode);
- co_return true;
-}
diff --git a/cpld/lattice/lattice.hpp b/cpld/lattice/lattice.hpp
deleted file mode 100644
index acb13f8..0000000
--- a/cpld/lattice/lattice.hpp
+++ /dev/null
@@ -1,85 +0,0 @@
-#pragma once
-#include "common/include/i2c/i2c.hpp"
-
-#include <chrono>
-#include <iostream>
-#include <string_view>
-#include <utility>
-
-struct cpldInfo
-{
- std::string chipName;
- std::vector<uint8_t> deviceId;
-};
-
-const std::map<std::string, cpldInfo> supportedDeviceMap = {
- {"LatticeLCMXO2_4000HCFirmware",
- {"LCMXO2-4000HC", {0x01, 0x2b, 0xc0, 0x43}}},
- {"LatticeLCMXO3LF_2100CFirmware",
- {"LCMXO3LF-2100C", {0x61, 0x2b, 0xb0, 0x43}}},
- {"LatticeLCMXO3LF_4300CFirmware",
- {"LCMXO3LF-4300C", {0x61, 0x2b, 0xc0, 0x43}}},
- {"LatticeLCMXO3D_4300Firmware", {"LCMXO3D-4300", {0x01, 0x2e, 0x20, 0x43}}},
- {"LatticeLCMXO3D_9400Firmware", {"LCMXO3D-9400", {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;
-};
-
-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;
- std::vector<uint8_t> sumOnly;
- 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);
- 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);
-};
diff --git a/cpld/lattice/lattice_base_cpld.cpp b/cpld/lattice/lattice_base_cpld.cpp
new file mode 100644
index 0000000..9055b42
--- /dev/null
+++ b/cpld/lattice/lattice_base_cpld.cpp
@@ -0,0 +1,548 @@
+#include "lattice_base_cpld.hpp"
+
+#include <algorithm>
+#include <cstddef>
+#include <fstream>
+#include <map>
+#include <numeric>
+#include <vector>
+
+namespace phosphor::software::cpld
+{
+
+constexpr uint8_t busyWaitmaxRetry = 30;
+constexpr uint8_t busyFlagBit = 0x80;
+
+static constexpr std::string_view tagFuseQuantity = "QF";
+static constexpr std::string_view tagUserCodeHex = "UH";
+static constexpr std::string_view tagCFStart = "L000";
+static constexpr std::string_view tagData = "NOTE TAG DATA";
+static constexpr std::string_view tagUserFlashMemory = "NOTE USER MEMORY DATA";
+static constexpr std::string_view tagChecksum = "C";
+static constexpr std::string_view tagUserCode = "NOTE User Electronic";
+static constexpr std::string_view tagEbrInitData = "NOTE EBR_INIT DATA";
+static constexpr std::string_view tagEndConfig = "NOTE END CONFIG DATA";
+static constexpr std::string_view tagDevName = "NOTE DEVICE NAME";
+
+constexpr uint8_t isOK = 0;
+constexpr uint8_t isReady = 0;
+constexpr uint8_t busyOrReadyBit = 4;
+constexpr uint8_t failOrOKBit = 5;
+
+static uint8_t reverse_bit(uint8_t b)
+{
+ b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+ b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+ b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+ return b;
+}
+
+std::string LatticeBaseCPLD::uint32ToHexStr(uint32_t value)
+{
+ std::ostringstream oss;
+ oss << std::setfill('0') << std::setw(8) << std::hex << std::uppercase
+ << value;
+ return oss.str();
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::updateFirmware(
+ const uint8_t* image, size_t imageSize,
+ std::function<bool(int)> progressCallBack)
+{
+ if (progressCallBack == nullptr)
+ {
+ lg2::error("Error: progressCallBack is null.");
+ co_return false;
+ }
+
+ if (!image || imageSize == 0)
+ {
+ lg2::error("Error: image is null.");
+ co_return false;
+ }
+
+ lg2::debug("CPLD image size: {IMAGESIZE}", "IMAGESIZE", imageSize);
+ auto result = co_await prepareUpdate(image, imageSize);
+ if (!result)
+ {
+ lg2::error("Prepare update failed.");
+ co_return false;
+ }
+ lg2::debug("Prepare update success");
+ progressCallBack(50);
+
+ result = co_await doUpdate();
+ if (!result)
+ {
+ lg2::error("Do update failed.");
+ co_return false;
+ }
+ lg2::debug("Do update success");
+ progressCallBack(90);
+
+ result = co_await finishUpdate();
+ if (!result)
+ {
+ lg2::error("Finish update failed.");
+ co_return false;
+ }
+ lg2::debug("Finish update success");
+ progressCallBack(100);
+
+ co_return true;
+}
+
+bool LatticeBaseCPLD::jedFileParser(const uint8_t* image, size_t imageSize)
+{
+ enum class ParseState
+ {
+ none,
+ cfg,
+ endCfg,
+ ufm,
+ checksum,
+ userCode
+ };
+ ParseState state = ParseState::none;
+
+ if (image == nullptr || imageSize == 0)
+ {
+ lg2::error(
+ "Error: JED file is empty or not found. Please check the file.");
+ return false;
+ }
+
+ std::string content(reinterpret_cast<const char*>(image), imageSize);
+ std::istringstream iss(content);
+ std::string line;
+
+ auto pushPage = [](std::string& line, std::vector<uint8_t>& sector) {
+ if (line[0] == '0' || line[0] == '1')
+ {
+ while (line.size() >= 8)
+ {
+ try
+ {
+ sector.push_back(static_cast<uint8_t>(
+ std::stoi(line.substr(0, 8), 0, 2)));
+ line.erase(0, 8);
+ }
+ catch (...)
+ {
+ break;
+ }
+ }
+ }
+ };
+
+ while (getline(iss, line))
+ {
+ if (!line.empty() && line.back() == '\r')
+ {
+ line.pop_back();
+ }
+ if (line.empty())
+ {
+ continue;
+ }
+
+ if (line.starts_with(tagFuseQuantity))
+ {
+ ssize_t numberSize = static_cast<ssize_t>(line.find('*')) -
+ static_cast<ssize_t>(line.find('F')) - 1;
+ if (numberSize > 0)
+ {
+ fwInfo.fuseQuantity = std::stoul(
+ line.substr(tagFuseQuantity.length(), numberSize));
+ lg2::debug("fuseQuantity Size = {QFSIZE}", "QFSIZE",
+ fwInfo.fuseQuantity);
+ }
+ }
+ else if (line.starts_with(tagCFStart) ||
+ line.starts_with(tagEbrInitData))
+ {
+ state = ParseState::cfg;
+ continue;
+ }
+ else if (line.starts_with(tagEndConfig))
+ {
+ state = ParseState::endCfg;
+ continue;
+ }
+ else if (line.starts_with(tagUserFlashMemory) ||
+ line.starts_with(tagData))
+ {
+ state = ParseState::ufm;
+ continue;
+ }
+ else if (line.starts_with(tagUserCode))
+ {
+ state = ParseState::userCode;
+ continue;
+ }
+ else if (line.starts_with(tagChecksum))
+ {
+ state = ParseState::checksum;
+ }
+ else if (line.starts_with(tagDevName))
+ {
+ lg2::debug("{DEVNAME}", "DEVNAME", line);
+ if (line.find(chip) == std::string::npos)
+ {
+ lg2::debug("STOP UPDATING: The image does not match the chip.");
+ return -1;
+ }
+ }
+
+ switch (state)
+ {
+ case ParseState::cfg:
+ pushPage(line, fwInfo.cfgData);
+ break;
+ case ParseState::endCfg:
+ pushPage(line, sumOnly);
+ break;
+ case ParseState::ufm:
+ pushPage(line, fwInfo.ufmData);
+ break;
+ case ParseState::checksum:
+ if (line.size() > 1)
+ {
+ state = ParseState::none;
+ ssize_t numberSize =
+ static_cast<ssize_t>(line.find('*')) -
+ static_cast<ssize_t>(line.find('C')) - 1;
+ if (numberSize <= 0)
+ {
+ lg2::debug("Error in parsing checksum");
+ return -1;
+ }
+ static constexpr auto start = tagChecksum.length();
+ std::istringstream iss(line.substr(start, numberSize));
+ iss >> std::hex >> fwInfo.checksum;
+ lg2::debug("Checksum = 0x{CHECKSUM}", "CHECKSUM",
+ fwInfo.checksum);
+ }
+ break;
+ case ParseState::userCode:
+ if (line.starts_with(tagUserCodeHex))
+ {
+ state = ParseState::none;
+ ssize_t numberSize =
+ static_cast<ssize_t>(line.find('*')) -
+ static_cast<ssize_t>(line.find('H')) - 1;
+ if (numberSize <= 0)
+ {
+ lg2::debug("Error in parsing usercode");
+ return -1;
+ }
+ std::istringstream iss(
+ line.substr(tagUserCodeHex.length(), numberSize));
+ iss >> std::hex >> fwInfo.version;
+ lg2::debug("UserCode = 0x{USERCODE}", "USERCODE",
+ fwInfo.version);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ lg2::debug("CFG Size = {CFGSIZE}", "CFGSIZE", fwInfo.cfgData.size());
+ if (!fwInfo.ufmData.empty())
+ {
+ lg2::debug("userFlashMemory size = {UFMSIZE}", "UFMSIZE",
+ fwInfo.ufmData.size());
+ }
+
+ return true;
+}
+
+bool LatticeBaseCPLD::verifyChecksum()
+{
+ uint32_t calculated = 0U;
+ auto addByte = [](uint32_t sum, uint8_t byte) {
+ return sum + reverse_bit(byte);
+ };
+
+ calculated = std::accumulate(fwInfo.cfgData.begin(), fwInfo.cfgData.end(),
+ calculated, addByte);
+ calculated =
+ std::accumulate(sumOnly.begin(), sumOnly.end(), calculated, addByte);
+ calculated = std::accumulate(fwInfo.ufmData.begin(), fwInfo.ufmData.end(),
+ calculated, addByte);
+
+ lg2::debug("Calculated checksum = {CALCULATED}", "CALCULATED", lg2::hex,
+ calculated);
+ lg2::debug("Checksum from JED file = {JEDFILECHECKSUM}", "JEDFILECHECKSUM",
+ lg2::hex, fwInfo.checksum);
+
+ if (fwInfo.checksum != (calculated & 0xFFFF))
+ {
+ lg2::error("JED file checksum compare fail, "
+ "Calculated checksum = {CALCULATED}, "
+ "Checksum from JED file = {JEDFILECHECKSUM}",
+ "CALCULATED", lg2::hex, calculated, "JEDFILECHECKSUM",
+ lg2::hex, fwInfo.checksum);
+ return false;
+ }
+
+ lg2::debug("JED file checksum compare success");
+ return true;
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::enableProgramMode()
+{
+ std::vector<uint8_t> request = {commandEnableConfigMode, 0x08, 0x0, 0x0};
+ std::vector<uint8_t> response;
+
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ lg2::error("Failed to send enable program mode 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> LatticeBaseCPLD::resetConfigFlash()
+{
+ std::vector<uint8_t> request;
+ std::vector<uint8_t> response;
+ if (isLCMXO3D)
+ {
+ /*
+ Set Page Address pointer to the
+ beginning of the different internal
+ Flash sectors. The bit in YYYY
+ defines which sector is selected.
+ Bit Flash sector selected
+ 8 CFG0
+ 9 CFG1
+ 10 FEA
+ 11 PUBKEY
+ 12 AESKEY
+ 13 CSEC
+ 14 UFM0
+ 15 UFM1
+ 16 UFM2
+ 17 UFM3
+ 18 USEC
+ 19 Reserved
+ 20 Reserved
+ 21 Reserved
+ 22 Reserved
+ commandResetConfigFlash = 0x46, YY YY 00
+ */
+ if (target.empty() || target == "CFG0")
+ {
+ request = {commandResetConfigFlash, 0x00, 0x01, 0x00};
+ }
+ else if (target == "CFG1")
+ {
+ request = {commandResetConfigFlash, 0x00, 0x02, 0x00};
+ }
+ else
+ {
+ lg2::error(
+ "Error: unknown target. Only CFG0 and CFG1 are supported.");
+ co_return false;
+ }
+ }
+ else
+ {
+ request = {commandResetConfigFlash, 0x0, 0x0, 0x0};
+ }
+
+ co_return i2cInterface.sendReceive(request, response);
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::programDone()
+{
+ std::vector<uint8_t> request = {commandProgramDone, 0x0, 0x0, 0x0};
+ std::vector<uint8_t> response;
+
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ lg2::error("Failed to send program done 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> LatticeBaseCPLD::disableConfigInterface()
+{
+ std::vector<uint8_t> request = {commandDisableConfigInterface, 0x0, 0x0};
+ std::vector<uint8_t> response;
+ co_return i2cInterface.sendReceive(request, response);
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::waitBusyAndVerify()
+{
+ uint8_t retry = 0;
+
+ while (retry <= busyWaitmaxRetry)
+ {
+ uint8_t busyFlag = 0xff;
+
+ auto readBusyFlagResult = co_await readBusyFlag(busyFlag);
+ if (!readBusyFlagResult)
+ {
+ lg2::error("Fail to read busy flag.");
+ co_return false;
+ }
+
+ if (busyFlag & busyFlagBit)
+ {
+ co_await sdbusplus::async::sleep_for(ctx, waitBusyTime);
+ retry++;
+ if (retry > busyWaitmaxRetry)
+ {
+ lg2::error(
+ "Status Reg : Busy! Please check the I2C bus and address.");
+ co_return false;
+ }
+ }
+ else
+ {
+ break;
+ }
+ } // while loop busy check
+
+ // Check out status reg
+ auto statusReg = std::make_unique<uint8_t>(0xff);
+
+ if (!(co_await readStatusReg(*statusReg)))
+ {
+ lg2::error("Fail to read status register.");
+ co_return false;
+ }
+
+ if (((*statusReg >> busyOrReadyBit) & 1) == isReady &&
+ ((*statusReg >> failOrOKBit) & 1) == isOK)
+ {
+ lg2::debug("Status Reg : OK");
+ co_return true;
+ }
+
+ lg2::error("Status Reg : Fail! Please check the I2C bus and address.");
+ co_return false;
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::readBusyFlag(uint8_t& busyFlag)
+{
+ constexpr size_t resSize = 1;
+ std::vector<uint8_t> request = {commandReadBusyFlag, 0x0, 0x0, 0x0};
+ std::vector<uint8_t> response(resSize, 0);
+
+ auto success = i2cInterface.sendReceive(request, response);
+ if (!success && response.size() != resSize)
+ {
+ co_return false;
+ }
+ busyFlag = response.at(0);
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::readStatusReg(uint8_t& statusReg)
+{
+ std::vector<uint8_t> request = {commandReadStatusReg, 0x0, 0x0, 0x0};
+ std::vector<uint8_t> response(4, 0);
+
+ if (!i2cInterface.sendReceive(request, response))
+ {
+ lg2::error("Failed to send read status register request.");
+ co_return false;
+ }
+ /*
+ Read Status Register
+ [LSC_READ_STATUS]
+ 0x3C 00 00 00 N/A YY YY YY YY Bit 1 0
+ 12 Busy Ready
+ 13 Fail OK
+ */
+ statusReg = response.at(2);
+ co_return true;
+}
+
+sdbusplus::async::task<bool> LatticeBaseCPLD::getVersion(std::string& version)
+{
+ auto userCode = std::make_unique<uint32_t>(0);
+
+ if (target.empty())
+ {
+ if (!(co_await readUserCode(*userCode)))
+ {
+ lg2::error("Read usercode failed.");
+ co_return false;
+ }
+
+ lg2::debug("CPLD version: {VERSION}", "VERSION", *userCode);
+ }
+ else if (target == "CFG0" || target == "CFG1")
+ {
+ isLCMXO3D = true;
+ co_await waitBusyAndVerify();
+
+ if (!(co_await enableProgramMode()))
+ {
+ lg2::error("Enable program mode failed.");
+ co_return false;
+ }
+
+ if (!(co_await resetConfigFlash()))
+ {
+ lg2::error("Reset config flash failed.");
+ co_return false;
+ }
+
+ if (!(co_await readUserCode(*userCode)))
+ {
+ lg2::error("Read usercode failed.");
+ co_return false;
+ }
+
+ if (!(co_await programDone()))
+ {
+ lg2::error("Program not done.");
+ co_return false;
+ }
+
+ if (!(co_await disableConfigInterface()))
+ {
+ lg2::error("Disable Config Interface failed.");
+ co_return false;
+ }
+
+ lg2::debug("CPLD {TARGET} version: {VERSION}", "TARGET", target,
+ "VERSION", *userCode);
+ }
+ else
+ {
+ lg2::error("Error: unknown target.");
+ co_return false;
+ }
+
+ if (*userCode == 0)
+ {
+ lg2::error("User code is zero, cannot get version.");
+ co_return false;
+ }
+ version = uint32ToHexStr(*userCode);
+ co_return true;
+}
+
+} // namespace phosphor::software::cpld
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
diff --git a/cpld/lattice/lattice_cpld_factory.cpp b/cpld/lattice/lattice_cpld_factory.cpp
new file mode 100644
index 0000000..ab0cb85
--- /dev/null
+++ b/cpld/lattice/lattice_cpld_factory.cpp
@@ -0,0 +1,91 @@
+#include "lattice_cpld_factory.hpp"
+
+#include "lattice_xo3_cpld.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+namespace phosphor::software::cpld
+{
+
+std::unique_ptr<LatticeBaseCPLD> LatticeCPLDFactory::getLatticeCPLD()
+{
+ if (supportedDeviceMap.find(chipEnum) == supportedDeviceMap.end())
+ {
+ // invalid
+ lg2::error("Unsupported Lattice CPLD chip enum: {CHIPENUM}", "CHIPENUM",
+ chipEnum);
+ return nullptr;
+ }
+
+ auto chipFamily = supportedDeviceMap.at(chipEnum).chipFamily;
+ auto chipModelStr =
+ getLatticeChipStr(chipEnum, latticeStringType::modelString);
+ switch (chipFamily)
+ {
+ case latticeChipFamily::XO2:
+ case latticeChipFamily::XO3:
+ return std::make_unique<LatticeXO3CPLD>(
+ CPLDInterface::ctx, CPLDInterface::bus, CPLDInterface::address,
+ chipModelStr, "CFG0", false);
+ default:
+ lg2::error("Unsupported Lattice CPLD chip family: {CHIPMODEL}",
+ "CHIPMODEL", chipModelStr);
+ return nullptr;
+ }
+}
+
+sdbusplus::async::task<bool> LatticeCPLDFactory::updateFirmware(
+ bool /*force*/, const uint8_t* image, size_t imageSize,
+ std::function<bool(int)> progressCallBack)
+{
+ lg2::info("Updating Lattice CPLD firmware");
+ auto cpldManager = getLatticeCPLD();
+ if (cpldManager == nullptr)
+ {
+ lg2::error("CPLD manager is not initialized.");
+ co_return false;
+ }
+ co_return co_await cpldManager->updateFirmware(image, imageSize,
+ progressCallBack);
+}
+
+sdbusplus::async::task<bool> LatticeCPLDFactory::getVersion(
+ std::string& version)
+{
+ lg2::info("Getting Lattice CPLD version");
+ auto cpldManager = getLatticeCPLD();
+ if (cpldManager == nullptr)
+ {
+ lg2::error("CPLD manager is not initialized.");
+ co_return false;
+ }
+ co_return co_await cpldManager->getVersion(version);
+}
+
+} // namespace phosphor::software::cpld
+
+// Factory function to create lattice CPLD device
+namespace
+{
+using namespace phosphor::software::cpld;
+
+// Register all the CPLD type with the CPLD factory
+const bool vendorRegistered = [] {
+ for (const auto& [chipEnum, info] : supportedDeviceMap)
+ {
+ auto typeStr =
+ getLatticeChipStr(chipEnum, latticeStringType::typeString);
+ CPLDFactory::instance().registerCPLD(
+ typeStr, [chipEnum](sdbusplus::async::context& ctx,
+ const std::string& chipName, uint16_t bus,
+ uint8_t address) {
+ // Create and return a LatticeCPLD instance
+ // Pass the parameters to the constructor
+ return std::make_unique<LatticeCPLDFactory>(
+ ctx, chipName, chipEnum, bus, address);
+ });
+ }
+ return true;
+}();
+
+} // namespace
diff --git a/cpld/lattice/lattice_cpld_factory.hpp b/cpld/lattice/lattice_cpld_factory.hpp
new file mode 100644
index 0000000..bec61d8
--- /dev/null
+++ b/cpld/lattice/lattice_cpld_factory.hpp
@@ -0,0 +1,27 @@
+#include "cpld/cpld_interface.hpp"
+#include "cpld/lattice/lattice_base_cpld.hpp"
+
+namespace phosphor::software::cpld
+{
+
+class LatticeCPLDFactory : public CPLDInterface
+{
+ public:
+ LatticeCPLDFactory(sdbusplus::async::context& ctx,
+ const std::string& chipName, latticeChip chipEnum,
+ uint16_t bus, uint8_t address) :
+ CPLDInterface(ctx, chipName, bus, address), chipEnum(chipEnum)
+ {}
+
+ sdbusplus::async::task<bool> updateFirmware(
+ bool force, const uint8_t* image, size_t imageSize,
+ std::function<bool(int)> progress) final;
+
+ sdbusplus::async::task<bool> getVersion(std::string& version) final;
+
+ private:
+ std::unique_ptr<LatticeBaseCPLD> getLatticeCPLD();
+ latticeChip chipEnum;
+};
+
+} // namespace phosphor::software::cpld
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
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