#include "mp994x.hpp" #include "common/include/utils.hpp" #include PHOSPHOR_LOG2_USING; namespace phosphor::software::VR { static constexpr std::string_view vendorIdRegName = "VENDOR_ID_VR"; static constexpr std::string_view mfrDeviceIDCFGRegName = "MFR_DEVICE_ID_CFG"; static constexpr std::string_view crcUserMultiRegName = "CRC_USER_MULTI"; static constexpr uint8_t pageMask = 0x0F; enum class MP994XCmd : uint8_t { // Page 0 commands storeUserAll = 0x15, userData08 = 0xB8, // Page 2 commands mfrMulconfigSel = 0xAB, configId = 0xAF, mfrNVMPmbusCtrl = 0xCA, mfrDebug = 0xD4, deviceId = 0xDB, // Page 5 commands vendorId = 0xBA, // Page 7 commands storeFaultTrigger = 0x51, }; sdbusplus::async::task MP994X::parseDeviceConfiguration() { if (!configuration) { error("Device configuration not initialized"); co_return false; } for (const auto& tokens : parser->lineTokens) { if (!parser->isValidDataTokens(tokens)) { continue; } auto regName = parser->getVal(tokens, ATE::regName); if (regName == vendorIdRegName) { configuration->vendorId = parser->getVal(tokens, ATE::regDataHex); configuration->configId = parser->getVal(tokens, ATE::configId); } else if (regName == mfrDeviceIDCFGRegName) { configuration->productId = parser->getVal(tokens, ATE::regDataHex); } else if (regName == crcUserMultiRegName) { configuration->crcMulti = parser->getVal(tokens, ATE::regDataHex); break; } } co_return true; } sdbusplus::async::task MP994X::verifyImage(const uint8_t* image, size_t imageSize) { if (!co_await parseImage(image, imageSize, MPSImageType::type1)) { error("Image verification failed: image parsing failed"); co_return false; } if (configuration->registersData.empty()) { error("Image verification failed: no data found"); co_return false; } if (configuration->vendorId == 0 || configuration->productId == 0 || configuration->configId == 0) { error("Image verification failed: missing required field " "vendor ID, product ID, or config ID"); co_return false; } co_return true; } sdbusplus::async::task MP994X::checkId(MP994XCmd idCmd, uint32_t expected) { static constexpr size_t vendorIdLength = 2; static constexpr size_t productIdLength = 1; static constexpr size_t configIdLength = 2; MPSPage page; size_t idLen = 0; const uint8_t cmd = static_cast(idCmd); switch (idCmd) { case MP994XCmd::vendorId: page = MPSPage::page5; idLen = vendorIdLength; break; case MP994XCmd::deviceId: page = MPSPage::page2; idLen = productIdLength; break; case MP994XCmd::configId: page = MPSPage::page2; idLen = configIdLength; break; default: error("Invalid command for ID check: {CMD}", "CMD", lg2::hex, cmd); co_return false; } std::vector tbuf; std::vector rbuf; tbuf = buildByteVector(PMBusCmd::page, page); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to set page for ID check"); co_return false; } tbuf = buildByteVector(idCmd); rbuf.resize(idLen); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to read ID, cmd={CMD}", "CMD", lg2::hex, cmd); co_return false; } auto id = bytesToInt(rbuf); if (id != expected) { error("ID check failed for cmd {CMD}: got {ID}, expected {EXP}", "CMD", lg2::hex, cmd, "ID", lg2::hex, id, "EXP", lg2::hex, expected); co_return false; } co_return true; } sdbusplus::async::task MP994X::unlockWriteProtect() { static constexpr uint8_t unlockWriteProtectData = 0x00; std::vector tbuf; std::vector rbuf; tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to set page 0 to unlock write protection mode"); co_return false; } tbuf = buildByteVector(PMBusCmd::writeProtect, unlockWriteProtectData); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to unlock write protect mode"); co_return false; } debug("Write protection unlocked"); co_return true; } sdbusplus::async::task MP994X::disableStoreFaultTriggering() { static constexpr size_t mfrDebugDataLength = 2; static constexpr uint16_t enableEnteringPage7Mask = 0x8000; static constexpr uint16_t disableStoreFaultTriggeringData = 0x1000; std::vector tbuf; std::vector rbuf; // enable entering page 7 tbuf = buildByteVector(PMBusCmd::page, MPSPage::page2); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to set page 2 to enable entering page 7"); co_return false; } tbuf = buildByteVector(MP994XCmd::mfrDebug); rbuf.resize(mfrDebugDataLength); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to read MFR Debug register to enable entering 7"); co_return false; } uint16_t data = (rbuf[1] << 8) | rbuf[0] | enableEnteringPage7Mask; tbuf = buildByteVector(MP994XCmd::mfrDebug, data); rbuf.clear(); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to enable entering page 7"); co_return false; } // disable store fault triggering tbuf = buildByteVector(PMBusCmd::page, MPSPage::page7); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to set page 7 to disable store fault triggering"); co_return false; } tbuf = buildByteVector(MP994XCmd::storeFaultTrigger, disableStoreFaultTriggeringData); rbuf.clear(); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to disable store fault triggering"); co_return false; } debug("Disabled store fault triggerring"); co_return true; } sdbusplus::async::task MP994X::setMultiConfigAddress(uint8_t config) { // MPS994X: Select multi-configuration address // Write to Page 2 @ 0xAB: // - Bit[3] = MFR_MULCONFIG_SEL (1 = enable) // - Bit[2:0] = MFR_MULCONFIG_ADDR (0 ~ 7 → selects one of 8 configs) // Resulting values for config set 1 ~ 8: 0x08 ~ 0x0F auto addr = config - 1; static constexpr uint8_t maxMultiConfigAddr = 7; static constexpr uint8_t enableMultiConfigAddrSel = 0x08; if (addr > maxMultiConfigAddr) { error("Invalid multi config address: {ADDR}", "ADDR", addr); } std::vector tbuf; std::vector rbuf; tbuf = buildByteVector(PMBusCmd::page, MPSPage::page2); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to set page 2 to set multi config address"); co_return false; } uint8_t selectAddrData = enableMultiConfigAddrSel + addr; tbuf = buildByteVector(MP994XCmd::mfrMulconfigSel, selectAddrData); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to write {DATA} to multi config select register {REG}", "DATA", lg2::hex, selectAddrData, "REG", lg2::hex, static_cast(MP994XCmd::mfrMulconfigSel)); co_return false; } debug("Selected multi config set address {ADDR}", "ADDR", addr); co_return true; } sdbusplus::async::task MP994X::programConfigData( const std::vector& gdata) { std::vector tbuf; std::vector rbuf; for (const auto& data : gdata) { uint8_t page = data.page & pageMask; tbuf = buildByteVector(PMBusCmd::page, page); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to set page {PAGE} for register {REG}", "PAGE", page, "REG", lg2::hex, data.addr); co_return false; } tbuf = {data.addr}; tbuf.insert(tbuf.end(), data.data.begin(), data.data.begin() + data.length); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error( "Failed to write data {DATA} to register {REG} on page {PAGE}", "DATA", lg2::hex, bytesToInt(data.data), "REG", lg2::hex, data.addr, "PAGE", page); co_return false; } } if (!co_await storeDataIntoMTP()) { error("Failed to store code into MTP after programming config data"); co_return false; } co_return true; } sdbusplus::async::task MP994X::programAllRegisters() { // config 0 = User Registers // config 1 ~ 8 = Multi-configuration Registers for (const auto& [config, gdata] : getGroupedConfigData(~pageMask, 4)) { debug("Configuring registers for config set {SET}", "SET", config); if (config > 0) { if (!co_await setMultiConfigAddress(config)) { co_return false; } } if (!co_await programConfigData(gdata)) { error("Failed to program config set {SET}", "SET", config); co_return false; } debug("Configured {SIZE} registers for config set {SET}", "SIZE", gdata.size(), "SET", config); } debug("All registers were programmed successfully"); co_return true; } sdbusplus::async::task MP994X::storeDataIntoMTP() { std::vector tbuf; std::vector rbuf; tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to set page 0 to store data into MTP"); co_return false; } tbuf = buildByteVector(MP994XCmd::storeUserAll); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to store data into MTP"); co_return false; } // Wait store data co_await sdbusplus::async::sleep_for(ctx, std::chrono::milliseconds(1000)); debug("Stored data into MTP"); co_return true; } sdbusplus::async::task MP994X::getCRC(uint32_t* checksum) { static constexpr size_t crcUserMultiDataLength = 4; static constexpr size_t statusByteLength = 1; std::vector tbuf; std::vector rbuf; tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to set page 0 for get user data"); co_return false; } tbuf = buildByteVector(MP994XCmd::userData08); rbuf.resize(crcUserMultiDataLength + statusByteLength); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to get user data on page 0"); co_return false; } auto crcBytes = std::span(rbuf).subspan(statusByteLength); *checksum = bytesToInt(crcBytes); debug("Read CRC: {CRC}", "CRC", lg2::hex, *checksum); co_return true; } sdbusplus::async::task MP994X::restoreDataFromNVM() { static constexpr size_t nvmPmbusCtrlDataLength = 2; static constexpr uint16_t enableRestoreDataFromMTPMask = 0x0008; std::vector tbuf; std::vector rbuf; // enable restore data from MTP tbuf = buildByteVector(PMBusCmd::page, MPSPage::page2); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to set page 2 to enable restore data from MTP"); co_return false; } tbuf = buildByteVector(MP994XCmd::mfrNVMPmbusCtrl); rbuf.resize(nvmPmbusCtrlDataLength); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to read NVM PMBUS Ctrl register"); co_return false; } uint16_t data = ((rbuf[1] << 8) | rbuf[0]) | enableRestoreDataFromMTPMask; tbuf = buildByteVector(MP994XCmd::mfrNVMPmbusCtrl, data); rbuf.clear(); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to enable restore data from MTP"); co_return false; } // restore data from NVM tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to set page 0 for restore MTP and verify"); } tbuf = buildByteVector(PMBusCmd::restoreUserAll); if (!i2cInterface.sendReceive(tbuf, rbuf)) { error("Failed to restore data from NVM"); co_return false; } // wait restore data co_await sdbusplus::async::sleep_for(ctx, std::chrono::milliseconds(500)); debug("Restored data from NVM success"); co_return true; } sdbusplus::async::task MP994X::checkMTPCRC() { uint32_t crc = 0; // NOLINTBEGIN(clang-analyzer-core.uninitialized.Branch) if (!co_await getCRC(&crc)) // NOLINTEND(clang-analyzer-core.uninitialized.Branch) { error("Failed to get CRC for MTP check"); co_return false; } debug("MTP CRC: {CRC}, Expected: {EXP}", "CRC", lg2::hex, crc, "EXP", lg2::hex, configuration->crcMulti); co_return configuration->crcMulti == crc; } bool MP994X::forcedUpdateAllowed() { return true; } sdbusplus::async::task MP994X::updateFirmware(bool force) { (void)force; if (!configuration) { error("Configuration not loaded"); co_return false; } if (!co_await checkId(MP994XCmd::vendorId, configuration->vendorId)) { co_return false; } if (!co_await checkId(MP994XCmd::deviceId, configuration->productId)) { co_return false; } if (!co_await checkId(MP994XCmd::configId, configuration->configId)) { co_return false; } if (!co_await unlockWriteProtect()) { co_return false; } if (!co_await disableStoreFaultTriggering()) { co_return false; } if (!co_await programAllRegisters()) { co_return false; } if (!co_await restoreDataFromNVM()) { co_return false; } if (!co_await checkMTPCRC()) { co_return false; } co_return true; } } // namespace phosphor::software::VR