i2c-vr: add support for Infineon TDA38640A VR firmware update over I2C
This commit introduces support for programming Infineon TDA38640A VR
devices over the I2C bus. It enables firmware updates for TDA38640A.
Related commit:
EM(Merged):
[1] https://gerrit.openbmc.org/c/openbmc/entity-manager/+/84725
Schema(Merged):
[2] https://gerrit.openbmc.org/c/openbmc/entity-manager/+/84724
PDI(Merged) :
[3] https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/84412
Tested on the Yosemite5 platform.
```
$ target=$(curl -k ${creds} --silent -X GET https://${bmc}/redfish/v1/UpdateService/FirmwareInventory/ | jq -r '.Members[] | select(.["@odata.id"] | contains("MB_VR_PVDD18")) | .["@odata.id"]')
$ curl -k ${creds} -X GET https://${bmc}${target}
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Yosemite5_MB_VR_PVDD18_S5_7939",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Other image",
"Id": "Yosemite5_MB_VR_PVDD18_S5_7939",
"Name": "Software Inventory",
"Status": {
"Health": "OK",
"HealthRollup": "OK",
"State": "Enabled"
},
"Updateable": true,
"Version": "5E281514"
$ fwpath="/media/leo/data/yv5/mb_vr_sni/yosemite5-mb-vr-pvdd18-s5-B651B567.pldm"
$ curl -k ${creds} \
-H "Content-Type:multipart/form-data" \
-X POST \
-F UpdateParameters="{\"Targets\":[\"${target}\"],\"@Redfish.OperationApplyTime\":\"OnReset\"};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-11-04T06:51:04+00:00",
"TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
"TaskState": "Running",
"TaskStatus": "OK"
}
$
========== ac cycle ==========
$ target=$(curl -k ${creds} --silent -X GET https://${bmc}/redfish/v1/UpdateService/FirmwareInventory/ | jq -r '.Members[] | select(.["@odata.id"] | contains("MB_VR_PVDD18")) | .["@odata.id"]')
$ curl -k ${creds} -X GET https://${bmc}${target}
{
"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Yosemite5_MB_VR_PVDD18_S5_5761",
"@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
"Description": "Other image",
"Id": "Yosemite5_MB_VR_PVDD18_S5_5761",
"Name": "Software Inventory",
"Status": {
"Health": "OK",
"HealthRollup": "OK",
"State": "Enabled"
},
"Updateable": true,
"Version": "B651B567"
}
```
Change-Id: I69fc2e3b83f56836aceff4e8b24dc85448f7c7ff
Signed-off-by: Leo Yang <Leo-Yang@quantatw.com>
diff --git a/i2c-vr/tda38640a/tda38640a.cpp b/i2c-vr/tda38640a/tda38640a.cpp
new file mode 100644
index 0000000..0572d00
--- /dev/null
+++ b/i2c-vr/tda38640a/tda38640a.cpp
@@ -0,0 +1,463 @@
+#include "tda38640a.hpp"
+
+#include "common/include/i2c/i2c.hpp"
+#include "common/include/utils.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+PHOSPHOR_LOG2_USING;
+
+namespace phosphor::software::VR
+{
+
+static constexpr size_t progNVMDelay = 300;
+static constexpr uint8_t NVMDoneMask = 0x80;
+static constexpr uint8_t NVMErrorMask = 0x40;
+static constexpr uint8_t pageZero = 0;
+
+enum class TDA38640ACmd : uint8_t
+{
+ crcLowReg = 0xB0,
+ crcHighReg = 0xAE,
+ userWrRemain = 0xB8,
+ unlockRegsReg = 0xD4,
+ unlockRegsVal = 0x03, // Unlock i2c and PMBus address registers.
+ progCmdLowReg = 0xD6,
+ progCmdHighReg = 0xD7,
+ progCmdLowVal = 0x42, // 0x3f42 From datasheet, This will store the user
+ // register in the next available nvm user image.
+ progCmdHighVal = 0x3F,
+ revisionReg = 0xFD, // The silicon version value is stored in register
+ // 0x00FD [7:0] of page 0.
+ pageReg = 0xff
+};
+
+const std::unordered_set<uint16_t> user_section_otp_register{
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048,
+ 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051,
+ 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A,
+ 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0062, 0x0063,
+ 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C,
+ 0x006D, 0x006E, 0x006F, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075,
+ 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x0202, 0x0204, 0x0220,
+ 0x0240, 0x0242, 0x0243, 0x0248, 0x0249, 0x024A, 0x024B, 0x024C, 0x024D,
+ 0x024E, 0x024F, 0x0250, 0x0251, 0x0252, 0x0256, 0x0257, 0x0266, 0x0267,
+ 0x026A, 0x026C, 0x0270, 0x0272, 0x0273, 0x0280, 0x0281, 0x0282, 0x0288,
+ 0x0289, 0x028A, 0x028C, 0x028D, 0x028E, 0x029E, 0x02A0, 0x02A2, 0x02AA,
+ 0x02AB, 0x02AC, 0x02BC, 0x02BD, 0x02BE, 0x02BF, 0x02C0, 0x02C2, 0x02C8,
+ 0x02CA, 0x0384, 0x0385};
+
+TDA38640A::TDA38640A(sdbusplus::async::context& ctx, uint16_t bus,
+ uint16_t address) :
+ VoltageRegulator(ctx), i2cInterface(phosphor::i2c::I2C(bus, address))
+{}
+
+sdbusplus::async::task<bool> TDA38640A::getUserRemainingWrites(uint8_t* remain)
+{
+ std::vector<uint8_t> tbuf;
+ std::vector<uint8_t> rbuf;
+ uint16_t remainBits = 0;
+
+ if (!(co_await setPage(pageZero)))
+ {
+ error("getUserRemainingWrites failed at setPage");
+ co_return false;
+ }
+
+ tbuf = buildByteVector(TDA38640ACmd::userWrRemain);
+ rbuf.resize(2);
+ if (!i2cInterface.sendReceive(tbuf, rbuf))
+ {
+ error("getUserRemainingWrites failed with sendreceive");
+ co_return false;
+ }
+ remainBits = rbuf[0] | (rbuf[1] << 8);
+
+ *remain = (16 - std::popcount(remainBits));
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> TDA38640A::setPage(uint8_t page)
+{
+ std::vector<uint8_t> tbuf;
+ std::vector<uint8_t> rbuf;
+
+ tbuf = buildByteVector(TDA38640ACmd::pageReg, page);
+ if (!i2cInterface.sendReceive(tbuf, rbuf))
+ {
+ error("setPage failed with sendreceive");
+ co_return false;
+ }
+ co_return true;
+}
+
+sdbusplus::async::task<bool> TDA38640A::getDeviceRevision(uint8_t* revision)
+{
+ std::vector<uint8_t> tbuf;
+ std::vector<uint8_t> rbuf;
+
+ if (!(co_await setPage(pageZero)))
+ {
+ error("getDeviceRevision failed at setPage");
+ co_return false;
+ }
+
+ tbuf = buildByteVector(TDA38640ACmd::revisionReg);
+ rbuf.resize(1);
+ if (!i2cInterface.sendReceive(tbuf, rbuf))
+ {
+ error("getDeviceRevision failed with sendreceive");
+ co_return false;
+ }
+
+ *revision = rbuf[0];
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> TDA38640A::getCRC(uint32_t* sum)
+{
+ std::vector<uint8_t> tbuf;
+ std::vector<uint8_t> rbuf;
+
+ uint32_t checksum = 0;
+
+ if (!(co_await setPage(pageZero)))
+ {
+ error("getCRC failed at setPage");
+ co_return false;
+ }
+
+ tbuf = buildByteVector(TDA38640ACmd::crcLowReg);
+ rbuf.resize(2);
+ if (!i2cInterface.sendReceive(tbuf, rbuf))
+ {
+ error("getCRC failed with sendreceive");
+ co_return false;
+ }
+
+ checksum = rbuf[0] | (rbuf[1] << 8);
+
+ tbuf = buildByteVector(TDA38640ACmd::crcHighReg);
+ if (!i2cInterface.sendReceive(tbuf, rbuf))
+ {
+ error("getCRC failed with sendreceive");
+ co_return false;
+ }
+
+ checksum |= (rbuf[0] << 16) | (rbuf[1] << 24);
+
+ *sum = checksum;
+
+ co_return true;
+}
+
+bool TDA38640A::parseImage(const uint8_t* image, size_t imageSize)
+{
+ std::string content(reinterpret_cast<const char*>(image), imageSize);
+ std::istringstream imageStream(content);
+ std::string line;
+
+ configuration.clear();
+
+ bool inConfigData = false;
+ while (std::getline(imageStream, line))
+ {
+ if (line.find("Part Number :") != std::string::npos)
+ {
+ if (line.back() == '\r')
+ {
+ line.pop_back();
+ }
+ std::string s(1, line.back());
+ configuration.rev =
+ static_cast<uint8_t>(std::stoul(s, nullptr, 16));
+ }
+
+ if (line.find("Configuration Checksum :") != std::string::npos)
+ {
+ size_t pos = line.find("0x");
+ if (pos != std::string::npos)
+ {
+ std::string hexStr = line.substr(pos + 2);
+ configuration.checksum = std::stoul(hexStr, nullptr, 16);
+ }
+ }
+
+ if (line.find("[Configuration Data]") != std::string::npos)
+ {
+ inConfigData = true;
+ continue;
+ }
+ if (line.find("[End Configuration Data]") != std::string::npos)
+ {
+ break;
+ }
+ if (inConfigData && !line.empty())
+ {
+ std::istringstream lineStream(line);
+ std::string seg;
+ std::vector<uint8_t> dataVector;
+ while (lineStream >> seg)
+ {
+ if (seg.length() == 2)
+ {
+ uint8_t data =
+ static_cast<uint8_t>(std::stoi(seg, nullptr, 16));
+ dataVector.push_back(data);
+ }
+ else
+ {
+ uint16_t offset =
+ static_cast<uint16_t>(std::stoi(seg, nullptr, 16));
+ configuration.offsets.push_back(offset);
+ }
+ }
+ configuration.data.push_back(dataVector);
+ }
+ }
+
+ if (configuration.offsets.size() != configuration.data.size())
+ {
+ error("parseImage failed. Data line mismatch.");
+ return false;
+ }
+
+ return true;
+}
+
+sdbusplus::async::task<bool> TDA38640A::unlockDevice()
+{
+ std::vector<uint8_t> tbuf;
+ std::vector<uint8_t> rbuf;
+
+ tbuf = buildByteVector(TDA38640ACmd::unlockRegsReg,
+ TDA38640ACmd::unlockRegsVal);
+ if (!i2cInterface.sendReceive(tbuf, rbuf))
+ {
+ error("unlockDevice failed with sendreceive");
+ co_return false;
+ }
+ co_return true;
+}
+
+sdbusplus::async::task<bool> TDA38640A::programmingCmd()
+{
+ std::vector<uint8_t> tbuf;
+ std::vector<uint8_t> rbuf;
+
+ if (!(co_await setPage(pageZero)))
+ {
+ error("programmingCmd failed at setPage 0.");
+ co_return false;
+ }
+
+ tbuf = buildByteVector(TDA38640ACmd::progCmdHighReg,
+ TDA38640ACmd::progCmdHighVal);
+ if (!i2cInterface.sendReceive(tbuf, rbuf))
+ {
+ error("programmingCmd high bit failed with sendreceive.");
+ co_return false;
+ }
+
+ tbuf = buildByteVector(TDA38640ACmd::progCmdLowReg,
+ TDA38640ACmd::progCmdLowVal);
+ if (!i2cInterface.sendReceive(tbuf, rbuf))
+ {
+ error("programmingCmd low bit failed with sendreceive.");
+ co_return false;
+ }
+ co_return true;
+}
+
+sdbusplus::async::task<bool> TDA38640A::getProgStatus(uint8_t* status)
+{
+ std::vector<uint8_t> tbuf;
+ std::vector<uint8_t> rbuf;
+
+ tbuf = buildByteVector(TDA38640ACmd::progCmdHighReg);
+ rbuf.resize(1);
+ if (!i2cInterface.sendReceive(tbuf, rbuf))
+ {
+ error("getProgStatus failed with sendreceive");
+ co_return false;
+ }
+
+ *status = rbuf[0];
+
+ co_return true;
+}
+
+sdbusplus::async::task<bool> TDA38640A::program()
+{
+ std::vector<uint8_t> tbuf;
+ std::vector<uint8_t> rbuf;
+ uint8_t status;
+ uint8_t retry = 3;
+
+ if (!(co_await unlockDevice()))
+ {
+ error("program failed at unlockDevice");
+ co_return false;
+ }
+
+ uint8_t page = 0;
+ uint8_t address = 0;
+ for (size_t i = 0; i < configuration.offsets.size(); i++)
+ {
+ page = configuration.offsets[i] >> 8;
+ if (!(co_await setPage(page)))
+ {
+ error("program failed at setPage");
+ co_return false;
+ }
+
+ for (uint8_t bias = 0; bias < 16; bias++)
+ {
+ uint16_t full_addr = configuration.offsets[i] + bias;
+
+ if (user_section_otp_register.find(full_addr) ==
+ user_section_otp_register.end())
+ {
+ debug(
+ "program at address {ADDR} not belone to user_section_otp_register.",
+ "ADDR", lg2::hex, full_addr);
+ continue;
+ }
+
+ address = (configuration.offsets[i] & 0xFF) + bias;
+
+ tbuf = buildByteVector(address, configuration.data[i][bias]);
+ if (!i2cInterface.sendReceive(tbuf, rbuf))
+ {
+ error("program failed with sendreceive");
+ co_return false;
+ }
+ debug("programming : at {PAGE} {ADDR} with {DATA}", "PAGE",
+ lg2::hex, page, "ADDR", lg2::hex, address, "DATA", lg2::hex,
+ configuration.data[i][bias]);
+ }
+ }
+
+ if (!(co_await programmingCmd()))
+ {
+ error("program failed at programmingCmd");
+ co_return false;
+ }
+
+ for (uint8_t r = 0; r < retry; r++)
+ {
+ co_await sdbusplus::async::sleep_for(
+ ctx, std::chrono::milliseconds(progNVMDelay));
+
+ if (!(co_await getProgStatus(&status)))
+ {
+ error("program failed at getProgStatus");
+ co_return false;
+ }
+
+ if ((status & NVMDoneMask) == 0 || (status & NVMErrorMask) != 0)
+ {
+ if ((status & NVMDoneMask) == 0)
+ {
+ error(
+ "getProgStatus failed with 0x00D7[7] == 0, Programming command not completed. retry...");
+ }
+ if ((status & NVMErrorMask) != 0)
+ {
+ error(
+ "getProgStatus failed with 0x00D7[6] == 1, The previous NVM operation encountered an error. retry...");
+ }
+ }
+ else
+ {
+ debug("ProgStatus ok.");
+ co_return true;
+ }
+ }
+ co_return false;
+}
+
+sdbusplus::async::task<bool> TDA38640A::verifyImage(const uint8_t* image,
+ size_t imageSize)
+{
+ uint8_t remain = 0;
+ uint8_t devRev = 0;
+ uint32_t devCrc = 0;
+
+ if (!parseImage(image, imageSize))
+ {
+ error("verifyImage failed at parseImage");
+ co_return false;
+ }
+
+ if (!(co_await getUserRemainingWrites(&remain)))
+ {
+ error("program failed at getUserRemainingWrites");
+ co_return false;
+ }
+ debug("User Remaining Writes from device: {REMAIN}", "REMAIN", lg2::dec,
+ remain);
+
+ if (!remain)
+ {
+ error("program failed with no user remaining writes left on device");
+ co_return false;
+ }
+
+ if (!(co_await getDeviceRevision(&devRev)))
+ {
+ error("program failed at getDeviceRevision");
+ co_return false;
+ }
+ debug("Device revision read from device: {REV}", "REV", lg2::hex, devRev);
+
+ if (devRev != configuration.rev)
+ {
+ error(
+ "program failed with revision of device and configuration are not equal");
+ co_return false;
+ }
+
+ if (!(co_await getCRC(&devCrc)))
+ {
+ error("program failed at getCRC");
+ co_return false;
+ }
+
+ debug("CRC from device: {CRC}", "CRC", lg2::hex, devCrc);
+ debug("CRC from config: {CRC}", "CRC", lg2::hex, configuration.checksum);
+
+ if (devCrc == configuration.checksum)
+ {
+ error("program failed with same CRC value at device and configuration");
+ co_return false;
+ }
+
+ co_return true;
+}
+
+bool TDA38640A::forcedUpdateAllowed()
+{
+ return true;
+}
+
+sdbusplus::async::task<bool> TDA38640A::updateFirmware(bool force)
+{
+ (void)force;
+ if (!(co_await program()))
+ {
+ error("programing TDA38640A failed");
+ co_return false;
+ }
+
+ co_return true;
+}
+
+} // namespace phosphor::software::VR