| Leo Yang | c1b3662 | 2025-10-28 10:39:55 +0800 | [diff] [blame^] | 1 | #include "tda38640a.hpp" |
| 2 | |
| 3 | #include "common/include/i2c/i2c.hpp" |
| 4 | #include "common/include/utils.hpp" |
| 5 | |
| 6 | #include <phosphor-logging/lg2.hpp> |
| 7 | |
| 8 | #include <iostream> |
| 9 | #include <sstream> |
| 10 | #include <string> |
| 11 | #include <vector> |
| 12 | |
| 13 | PHOSPHOR_LOG2_USING; |
| 14 | |
| 15 | namespace phosphor::software::VR |
| 16 | { |
| 17 | |
| 18 | static constexpr size_t progNVMDelay = 300; |
| 19 | static constexpr uint8_t NVMDoneMask = 0x80; |
| 20 | static constexpr uint8_t NVMErrorMask = 0x40; |
| 21 | static constexpr uint8_t pageZero = 0; |
| 22 | |
| 23 | enum class TDA38640ACmd : uint8_t |
| 24 | { |
| 25 | crcLowReg = 0xB0, |
| 26 | crcHighReg = 0xAE, |
| 27 | userWrRemain = 0xB8, |
| 28 | unlockRegsReg = 0xD4, |
| 29 | unlockRegsVal = 0x03, // Unlock i2c and PMBus address registers. |
| 30 | progCmdLowReg = 0xD6, |
| 31 | progCmdHighReg = 0xD7, |
| 32 | progCmdLowVal = 0x42, // 0x3f42 From datasheet, This will store the user |
| 33 | // register in the next available nvm user image. |
| 34 | progCmdHighVal = 0x3F, |
| 35 | revisionReg = 0xFD, // The silicon version value is stored in register |
| 36 | // 0x00FD [7:0] of page 0. |
| 37 | pageReg = 0xff |
| 38 | }; |
| 39 | |
| 40 | const std::unordered_set<uint16_t> user_section_otp_register{ |
| 41 | 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, |
| 42 | 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051, |
| 43 | 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, |
| 44 | 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0062, 0x0063, |
| 45 | 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, |
| 46 | 0x006D, 0x006E, 0x006F, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, |
| 47 | 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x0202, 0x0204, 0x0220, |
| 48 | 0x0240, 0x0242, 0x0243, 0x0248, 0x0249, 0x024A, 0x024B, 0x024C, 0x024D, |
| 49 | 0x024E, 0x024F, 0x0250, 0x0251, 0x0252, 0x0256, 0x0257, 0x0266, 0x0267, |
| 50 | 0x026A, 0x026C, 0x0270, 0x0272, 0x0273, 0x0280, 0x0281, 0x0282, 0x0288, |
| 51 | 0x0289, 0x028A, 0x028C, 0x028D, 0x028E, 0x029E, 0x02A0, 0x02A2, 0x02AA, |
| 52 | 0x02AB, 0x02AC, 0x02BC, 0x02BD, 0x02BE, 0x02BF, 0x02C0, 0x02C2, 0x02C8, |
| 53 | 0x02CA, 0x0384, 0x0385}; |
| 54 | |
| 55 | TDA38640A::TDA38640A(sdbusplus::async::context& ctx, uint16_t bus, |
| 56 | uint16_t address) : |
| 57 | VoltageRegulator(ctx), i2cInterface(phosphor::i2c::I2C(bus, address)) |
| 58 | {} |
| 59 | |
| 60 | sdbusplus::async::task<bool> TDA38640A::getUserRemainingWrites(uint8_t* remain) |
| 61 | { |
| 62 | std::vector<uint8_t> tbuf; |
| 63 | std::vector<uint8_t> rbuf; |
| 64 | uint16_t remainBits = 0; |
| 65 | |
| 66 | if (!(co_await setPage(pageZero))) |
| 67 | { |
| 68 | error("getUserRemainingWrites failed at setPage"); |
| 69 | co_return false; |
| 70 | } |
| 71 | |
| 72 | tbuf = buildByteVector(TDA38640ACmd::userWrRemain); |
| 73 | rbuf.resize(2); |
| 74 | if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| 75 | { |
| 76 | error("getUserRemainingWrites failed with sendreceive"); |
| 77 | co_return false; |
| 78 | } |
| 79 | remainBits = rbuf[0] | (rbuf[1] << 8); |
| 80 | |
| 81 | *remain = (16 - std::popcount(remainBits)); |
| 82 | |
| 83 | co_return true; |
| 84 | } |
| 85 | |
| 86 | sdbusplus::async::task<bool> TDA38640A::setPage(uint8_t page) |
| 87 | { |
| 88 | std::vector<uint8_t> tbuf; |
| 89 | std::vector<uint8_t> rbuf; |
| 90 | |
| 91 | tbuf = buildByteVector(TDA38640ACmd::pageReg, page); |
| 92 | if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| 93 | { |
| 94 | error("setPage failed with sendreceive"); |
| 95 | co_return false; |
| 96 | } |
| 97 | co_return true; |
| 98 | } |
| 99 | |
| 100 | sdbusplus::async::task<bool> TDA38640A::getDeviceRevision(uint8_t* revision) |
| 101 | { |
| 102 | std::vector<uint8_t> tbuf; |
| 103 | std::vector<uint8_t> rbuf; |
| 104 | |
| 105 | if (!(co_await setPage(pageZero))) |
| 106 | { |
| 107 | error("getDeviceRevision failed at setPage"); |
| 108 | co_return false; |
| 109 | } |
| 110 | |
| 111 | tbuf = buildByteVector(TDA38640ACmd::revisionReg); |
| 112 | rbuf.resize(1); |
| 113 | if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| 114 | { |
| 115 | error("getDeviceRevision failed with sendreceive"); |
| 116 | co_return false; |
| 117 | } |
| 118 | |
| 119 | *revision = rbuf[0]; |
| 120 | |
| 121 | co_return true; |
| 122 | } |
| 123 | |
| 124 | sdbusplus::async::task<bool> TDA38640A::getCRC(uint32_t* sum) |
| 125 | { |
| 126 | std::vector<uint8_t> tbuf; |
| 127 | std::vector<uint8_t> rbuf; |
| 128 | |
| 129 | uint32_t checksum = 0; |
| 130 | |
| 131 | if (!(co_await setPage(pageZero))) |
| 132 | { |
| 133 | error("getCRC failed at setPage"); |
| 134 | co_return false; |
| 135 | } |
| 136 | |
| 137 | tbuf = buildByteVector(TDA38640ACmd::crcLowReg); |
| 138 | rbuf.resize(2); |
| 139 | if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| 140 | { |
| 141 | error("getCRC failed with sendreceive"); |
| 142 | co_return false; |
| 143 | } |
| 144 | |
| 145 | checksum = rbuf[0] | (rbuf[1] << 8); |
| 146 | |
| 147 | tbuf = buildByteVector(TDA38640ACmd::crcHighReg); |
| 148 | if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| 149 | { |
| 150 | error("getCRC failed with sendreceive"); |
| 151 | co_return false; |
| 152 | } |
| 153 | |
| 154 | checksum |= (rbuf[0] << 16) | (rbuf[1] << 24); |
| 155 | |
| 156 | *sum = checksum; |
| 157 | |
| 158 | co_return true; |
| 159 | } |
| 160 | |
| 161 | bool TDA38640A::parseImage(const uint8_t* image, size_t imageSize) |
| 162 | { |
| 163 | std::string content(reinterpret_cast<const char*>(image), imageSize); |
| 164 | std::istringstream imageStream(content); |
| 165 | std::string line; |
| 166 | |
| 167 | configuration.clear(); |
| 168 | |
| 169 | bool inConfigData = false; |
| 170 | while (std::getline(imageStream, line)) |
| 171 | { |
| 172 | if (line.find("Part Number :") != std::string::npos) |
| 173 | { |
| 174 | if (line.back() == '\r') |
| 175 | { |
| 176 | line.pop_back(); |
| 177 | } |
| 178 | std::string s(1, line.back()); |
| 179 | configuration.rev = |
| 180 | static_cast<uint8_t>(std::stoul(s, nullptr, 16)); |
| 181 | } |
| 182 | |
| 183 | if (line.find("Configuration Checksum :") != std::string::npos) |
| 184 | { |
| 185 | size_t pos = line.find("0x"); |
| 186 | if (pos != std::string::npos) |
| 187 | { |
| 188 | std::string hexStr = line.substr(pos + 2); |
| 189 | configuration.checksum = std::stoul(hexStr, nullptr, 16); |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | if (line.find("[Configuration Data]") != std::string::npos) |
| 194 | { |
| 195 | inConfigData = true; |
| 196 | continue; |
| 197 | } |
| 198 | if (line.find("[End Configuration Data]") != std::string::npos) |
| 199 | { |
| 200 | break; |
| 201 | } |
| 202 | if (inConfigData && !line.empty()) |
| 203 | { |
| 204 | std::istringstream lineStream(line); |
| 205 | std::string seg; |
| 206 | std::vector<uint8_t> dataVector; |
| 207 | while (lineStream >> seg) |
| 208 | { |
| 209 | if (seg.length() == 2) |
| 210 | { |
| 211 | uint8_t data = |
| 212 | static_cast<uint8_t>(std::stoi(seg, nullptr, 16)); |
| 213 | dataVector.push_back(data); |
| 214 | } |
| 215 | else |
| 216 | { |
| 217 | uint16_t offset = |
| 218 | static_cast<uint16_t>(std::stoi(seg, nullptr, 16)); |
| 219 | configuration.offsets.push_back(offset); |
| 220 | } |
| 221 | } |
| 222 | configuration.data.push_back(dataVector); |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | if (configuration.offsets.size() != configuration.data.size()) |
| 227 | { |
| 228 | error("parseImage failed. Data line mismatch."); |
| 229 | return false; |
| 230 | } |
| 231 | |
| 232 | return true; |
| 233 | } |
| 234 | |
| 235 | sdbusplus::async::task<bool> TDA38640A::unlockDevice() |
| 236 | { |
| 237 | std::vector<uint8_t> tbuf; |
| 238 | std::vector<uint8_t> rbuf; |
| 239 | |
| 240 | tbuf = buildByteVector(TDA38640ACmd::unlockRegsReg, |
| 241 | TDA38640ACmd::unlockRegsVal); |
| 242 | if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| 243 | { |
| 244 | error("unlockDevice failed with sendreceive"); |
| 245 | co_return false; |
| 246 | } |
| 247 | co_return true; |
| 248 | } |
| 249 | |
| 250 | sdbusplus::async::task<bool> TDA38640A::programmingCmd() |
| 251 | { |
| 252 | std::vector<uint8_t> tbuf; |
| 253 | std::vector<uint8_t> rbuf; |
| 254 | |
| 255 | if (!(co_await setPage(pageZero))) |
| 256 | { |
| 257 | error("programmingCmd failed at setPage 0."); |
| 258 | co_return false; |
| 259 | } |
| 260 | |
| 261 | tbuf = buildByteVector(TDA38640ACmd::progCmdHighReg, |
| 262 | TDA38640ACmd::progCmdHighVal); |
| 263 | if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| 264 | { |
| 265 | error("programmingCmd high bit failed with sendreceive."); |
| 266 | co_return false; |
| 267 | } |
| 268 | |
| 269 | tbuf = buildByteVector(TDA38640ACmd::progCmdLowReg, |
| 270 | TDA38640ACmd::progCmdLowVal); |
| 271 | if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| 272 | { |
| 273 | error("programmingCmd low bit failed with sendreceive."); |
| 274 | co_return false; |
| 275 | } |
| 276 | co_return true; |
| 277 | } |
| 278 | |
| 279 | sdbusplus::async::task<bool> TDA38640A::getProgStatus(uint8_t* status) |
| 280 | { |
| 281 | std::vector<uint8_t> tbuf; |
| 282 | std::vector<uint8_t> rbuf; |
| 283 | |
| 284 | tbuf = buildByteVector(TDA38640ACmd::progCmdHighReg); |
| 285 | rbuf.resize(1); |
| 286 | if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| 287 | { |
| 288 | error("getProgStatus failed with sendreceive"); |
| 289 | co_return false; |
| 290 | } |
| 291 | |
| 292 | *status = rbuf[0]; |
| 293 | |
| 294 | co_return true; |
| 295 | } |
| 296 | |
| 297 | sdbusplus::async::task<bool> TDA38640A::program() |
| 298 | { |
| 299 | std::vector<uint8_t> tbuf; |
| 300 | std::vector<uint8_t> rbuf; |
| 301 | uint8_t status; |
| 302 | uint8_t retry = 3; |
| 303 | |
| 304 | if (!(co_await unlockDevice())) |
| 305 | { |
| 306 | error("program failed at unlockDevice"); |
| 307 | co_return false; |
| 308 | } |
| 309 | |
| 310 | uint8_t page = 0; |
| 311 | uint8_t address = 0; |
| 312 | for (size_t i = 0; i < configuration.offsets.size(); i++) |
| 313 | { |
| 314 | page = configuration.offsets[i] >> 8; |
| 315 | if (!(co_await setPage(page))) |
| 316 | { |
| 317 | error("program failed at setPage"); |
| 318 | co_return false; |
| 319 | } |
| 320 | |
| 321 | for (uint8_t bias = 0; bias < 16; bias++) |
| 322 | { |
| 323 | uint16_t full_addr = configuration.offsets[i] + bias; |
| 324 | |
| 325 | if (user_section_otp_register.find(full_addr) == |
| 326 | user_section_otp_register.end()) |
| 327 | { |
| 328 | debug( |
| 329 | "program at address {ADDR} not belone to user_section_otp_register.", |
| 330 | "ADDR", lg2::hex, full_addr); |
| 331 | continue; |
| 332 | } |
| 333 | |
| 334 | address = (configuration.offsets[i] & 0xFF) + bias; |
| 335 | |
| 336 | tbuf = buildByteVector(address, configuration.data[i][bias]); |
| 337 | if (!i2cInterface.sendReceive(tbuf, rbuf)) |
| 338 | { |
| 339 | error("program failed with sendreceive"); |
| 340 | co_return false; |
| 341 | } |
| 342 | debug("programming : at {PAGE} {ADDR} with {DATA}", "PAGE", |
| 343 | lg2::hex, page, "ADDR", lg2::hex, address, "DATA", lg2::hex, |
| 344 | configuration.data[i][bias]); |
| 345 | } |
| 346 | } |
| 347 | |
| 348 | if (!(co_await programmingCmd())) |
| 349 | { |
| 350 | error("program failed at programmingCmd"); |
| 351 | co_return false; |
| 352 | } |
| 353 | |
| 354 | for (uint8_t r = 0; r < retry; r++) |
| 355 | { |
| 356 | co_await sdbusplus::async::sleep_for( |
| 357 | ctx, std::chrono::milliseconds(progNVMDelay)); |
| 358 | |
| 359 | if (!(co_await getProgStatus(&status))) |
| 360 | { |
| 361 | error("program failed at getProgStatus"); |
| 362 | co_return false; |
| 363 | } |
| 364 | |
| 365 | if ((status & NVMDoneMask) == 0 || (status & NVMErrorMask) != 0) |
| 366 | { |
| 367 | if ((status & NVMDoneMask) == 0) |
| 368 | { |
| 369 | error( |
| 370 | "getProgStatus failed with 0x00D7[7] == 0, Programming command not completed. retry..."); |
| 371 | } |
| 372 | if ((status & NVMErrorMask) != 0) |
| 373 | { |
| 374 | error( |
| 375 | "getProgStatus failed with 0x00D7[6] == 1, The previous NVM operation encountered an error. retry..."); |
| 376 | } |
| 377 | } |
| 378 | else |
| 379 | { |
| 380 | debug("ProgStatus ok."); |
| 381 | co_return true; |
| 382 | } |
| 383 | } |
| 384 | co_return false; |
| 385 | } |
| 386 | |
| 387 | sdbusplus::async::task<bool> TDA38640A::verifyImage(const uint8_t* image, |
| 388 | size_t imageSize) |
| 389 | { |
| 390 | uint8_t remain = 0; |
| 391 | uint8_t devRev = 0; |
| 392 | uint32_t devCrc = 0; |
| 393 | |
| 394 | if (!parseImage(image, imageSize)) |
| 395 | { |
| 396 | error("verifyImage failed at parseImage"); |
| 397 | co_return false; |
| 398 | } |
| 399 | |
| 400 | if (!(co_await getUserRemainingWrites(&remain))) |
| 401 | { |
| 402 | error("program failed at getUserRemainingWrites"); |
| 403 | co_return false; |
| 404 | } |
| 405 | debug("User Remaining Writes from device: {REMAIN}", "REMAIN", lg2::dec, |
| 406 | remain); |
| 407 | |
| 408 | if (!remain) |
| 409 | { |
| 410 | error("program failed with no user remaining writes left on device"); |
| 411 | co_return false; |
| 412 | } |
| 413 | |
| 414 | if (!(co_await getDeviceRevision(&devRev))) |
| 415 | { |
| 416 | error("program failed at getDeviceRevision"); |
| 417 | co_return false; |
| 418 | } |
| 419 | debug("Device revision read from device: {REV}", "REV", lg2::hex, devRev); |
| 420 | |
| 421 | if (devRev != configuration.rev) |
| 422 | { |
| 423 | error( |
| 424 | "program failed with revision of device and configuration are not equal"); |
| 425 | co_return false; |
| 426 | } |
| 427 | |
| 428 | if (!(co_await getCRC(&devCrc))) |
| 429 | { |
| 430 | error("program failed at getCRC"); |
| 431 | co_return false; |
| 432 | } |
| 433 | |
| 434 | debug("CRC from device: {CRC}", "CRC", lg2::hex, devCrc); |
| 435 | debug("CRC from config: {CRC}", "CRC", lg2::hex, configuration.checksum); |
| 436 | |
| 437 | if (devCrc == configuration.checksum) |
| 438 | { |
| 439 | error("program failed with same CRC value at device and configuration"); |
| 440 | co_return false; |
| 441 | } |
| 442 | |
| 443 | co_return true; |
| 444 | } |
| 445 | |
| 446 | bool TDA38640A::forcedUpdateAllowed() |
| 447 | { |
| 448 | return true; |
| 449 | } |
| 450 | |
| 451 | sdbusplus::async::task<bool> TDA38640A::updateFirmware(bool force) |
| 452 | { |
| 453 | (void)force; |
| 454 | if (!(co_await program())) |
| 455 | { |
| 456 | error("programing TDA38640A failed"); |
| 457 | co_return false; |
| 458 | } |
| 459 | |
| 460 | co_return true; |
| 461 | } |
| 462 | |
| 463 | } // namespace phosphor::software::VR |