i2c-vr: add support for MPS MP297X VR firmware update over I2C

This commit introduces support for programming MPS MP297X VR devices
over the I2C bus. It enables firmware updates for VR models such as
MP2971 and MP2973.
Support for the MP297XFirmware type in EM was added in [1].

[1] https://gerrit.openbmc.org/c/openbmc/entity-manager/+/82949

Tested on the Santabarbara platform:

1. Display the fw inventory
```
curl --silent $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory
```

```
{
  "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
  "@odata.type": "#SoftwareInventoryCollection.SoftwareInventoryCollection",
  "Members": [
    {...},
    {
      "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_VR_PEX0_8614"
    },
    {...}
  ],
  "Members@odata.count": 15,
  "Name": "Software Inventory Collection"
}
```

2. Query version.
```
curl $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_VR_PEX0_8614
```

```
{
  "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_VR_PEX0_8614",
  "@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
  "Description": "Unknown image",
  "Id": "Santabarbara_MB_VR_PEX0_8614",
  "Name": "Software Inventory",
  "Status": {
    "Health": "OK",
    "HealthRollup": "OK",
    "State": "Enabled"
  },
  "Updateable": true,
  "Version": "30EA058B"
}
```

3. Trigger the fw update via redfish.
```
curl -k ${creds} \
  -H "Content-Type:multipart/form-data" \
  -X POST \
  -F UpdateParameters="{\"Targets\":[\"/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_VR_PEX0_8614\"],\"@Redfish.OperationApplyTime\":\"OnReset\"};type=application/json" \
  -F "UpdateFile=@${fwpath};type=application/octet-stream" \
  https://${bmc}/redfish/v1/UpdateService/update-multipart
```

4. Task is returned
```
{
  "@odata.id": "/redfish/v1/TaskService/Tasks/1",
  "@odata.type": "#Task.v1_4_3.Task",
  "HidePayload": false,
  "Id": "1",
  "Messages": [
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '1' has started.",
      "MessageArgs": [
        "1"
      ],
      "MessageId": "TaskEvent.1.0.TaskStarted",
      "MessageSeverity": "OK",
      "Resolution": "None."
    }
  ],
  "Name": "Task 1",
  "Payload": {
    "HttpHeaders": [],
    "HttpOperation": "POST",
    "TargetUri": "/redfish/v1/UpdateService/update-multipart"
  },
  "PercentComplete": 0,
  "StartTime": "2025-08-20T01:51:20+00:00",
  "TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/1",
  "TaskState": "Running",
  "TaskStatus": "OK"
}
```

5. Query Task status
```
curl --silent $creds https://$bmc/redfish/v1/TaskService/Tasks/1
```

```
{
  "@odata.id": "/redfish/v1/TaskService/Tasks/1",
  "@odata.type": "#Task.v1_4_3.Task",
  "EndTime": "2025-08-20T01:51:24+00:00",
  "HidePayload": false,
  "Id": "1",
  "Messages": [
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '1' has started.",
      "MessageArgs": [
        "1"
      ],
      "MessageId": "TaskEvent.1.0.TaskStarted",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '1' has changed to progress 50 percent complete.",
      "MessageArgs": [
        "1",
        "50"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '1' has changed to progress 80 percent complete.",
      "MessageArgs": [
        "1",
        "80"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '1' has changed to progress 100 percent complete.",
      "MessageArgs": [
        "1",
        "100"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '1' has completed.",
      "MessageArgs": [
        "1"
      ],
      "MessageId": "TaskEvent.1.0.TaskCompletedOK",
      "MessageSeverity": "OK",
      "Resolution": "None."
    }
  ],
  "Name": "Task 1",
  "Payload": {
    "HttpHeaders": [],
    "HttpOperation": "POST",
    "JsonBody": "null",
    "TargetUri": "/redfish/v1/UpdateService/update-multipart"
  },
  "PercentComplete": 100,
  "StartTime": "2025-08-20T01:51:20+00:00",
  "TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/1",
  "TaskState": "Completed",
  "TaskStatus": "OK"
}
```

6. Display the fw inventory with newly updated fw.
```
curl --silent $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory
```

```
{
  "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
  "@odata.type": "#SoftwareInventoryCollection.SoftwareInventoryCollection",
  "Members": [
    {...},
    {
      "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_VR_PEX0_272"
    },
    {
      "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_VR_PEX0_8614"
    },
    {...}
  ],
  "Members@odata.count": 16,
  "Name": "Software Inventory Collection"
}
```

7. Query the new fw version.
```
curl $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_VR_PEX0_272
```

```
{
  "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_VR_PEX0_272",
  "@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
  "Description": "Unknown image",
  "Id": "Santabarbara_MB_VR_PEX0_272",
  "Name": "Software Inventory",
  "Status": {
    "Health": "OK",
    "HealthRollup": "OK",
    "State": "Enabled"
  },
  "Updateable": false,
  "Version": "E68829FD"
}
```

8. Do AC cycle to make sure the new fw is applied.
```
busctl set-property xyz.openbmc_project.State.Chassis0 /xyz/openbmc_project/state/chassis0 \
xyz.openbmc_project.State.Chassis RequestedPowerTransition s xyz.openbmc_project.State.Chassis.Transition.PowerCycle
```

9. Display the fw inventory after AC cycle.
```
curl --silent $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory
```

```
{
  "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory",
  "@odata.type": "#SoftwareInventoryCollection.SoftwareInventoryCollection",
  "Members": [
    {...},
    {
      "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_VR_PEX0_3790"
    },
    {...}
  ],
  "Members@odata.count": 15,
  "Name": "Software Inventory Collection"
}
```

10. Query the fw version after AC cycle.
```
curl $creds https://$bmc/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_VR_PEX0_3790
```

```
{
  "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_VR_PEX0_3790",
  "@odata.type": "#SoftwareInventory.v1_1_0.SoftwareInventory",
  "Description": "Unknown image",
  "Id": "Santabarbara_MB_VR_PEX0_3790",
  "Name": "Software Inventory",
  "Status": {
    "Health": "OK",
    "HealthRollup": "OK",
    "State": "Enabled"
  },
  "Updateable": true,
  "Version": "E68829FD"
}
```

Change-Id: I0162bf6335ec673dd8c2a1cfb358c4c2a010570e
Signed-off-by: Kevin Tung <kevin.tung.openbmc@gmail.com>
diff --git a/i2c-vr/mps/mp297x.cpp b/i2c-vr/mps/mp297x.cpp
new file mode 100644
index 0000000..e9ae9cd
--- /dev/null
+++ b/i2c-vr/mps/mp297x.cpp
@@ -0,0 +1,530 @@
+#include "mp297x.hpp"
+
+#include "common/include/utils.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <fstream>
+
+PHOSPHOR_LOG2_USING;
+
+namespace phosphor::software::VR
+{
+
+static constexpr size_t statusByteLength = 1;
+
+static constexpr std::string_view crcUserRegName = "CRC_USER";
+static constexpr std::string_view crcMultiRegName = "CRC_MULTI";
+
+enum class MP297XCmd : uint8_t
+{
+    // Page 0 commands
+    storeDataIntoMTP = 0xF1,
+
+    // Page 1 commands
+    writeProtectMode = 0x35,
+    enableMTPPageWR = 0x4F,
+
+    // Page 2 commands
+    enableMultiConfigCRC = 0xF3,
+
+    // Page 0x29 commands
+    readUserCodeCRC = 0xFF,
+
+    // Page 0x2A commands
+    readMultiConfigCRC = 0xBF,
+};
+
+sdbusplus::async::task<bool> MP297X::parseDeviceConfiguration()
+{
+    if (!configuration)
+    {
+        error("Device configuration not initialized");
+        co_return false;
+    }
+
+    configuration->vendorId = 0x0025;
+    configuration->productId = 0x0071;
+
+    for (const auto& tokens : parser->lineTokens)
+    {
+        if (!parser->isValidDataTokens(tokens))
+        {
+            continue;
+        }
+
+        auto regName = parser->getVal<std::string>(tokens, ATE::regName);
+
+        if (regName == crcUserRegName)
+        {
+            configuration->configId =
+                parser->getVal<uint32_t>(tokens, ATE::configId);
+            configuration->crcUser =
+                parser->getVal<uint32_t>(tokens, ATE::regDataHex);
+        }
+        else if (regName == crcMultiRegName)
+        {
+            configuration->crcMulti =
+                parser->getVal<uint32_t>(tokens, ATE::regDataHex);
+            break;
+        }
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> MP297X::verifyImage(const uint8_t* image,
+                                                 size_t imageSize)
+{
+    if (!co_await parseImage(image, imageSize))
+    {
+        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->configId == 0)
+    {
+        error("Image verification failed - missing config ID");
+        co_return false;
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> MP297X::checkId(PMBusCmd idCmd, uint32_t expected)
+{
+    constexpr size_t idLength = 2;
+
+    std::vector<uint8_t> tbuf;
+    std::vector<uint8_t> rbuf;
+
+    tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to set page 0 for ID check");
+        co_return false;
+    }
+
+    tbuf = buildByteVector(idCmd);
+    rbuf.resize(statusByteLength + idLength);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to read ID, cmd={CMD}", "CMD", lg2::hex,
+              static_cast<uint8_t>(idCmd));
+        co_return false;
+    }
+
+    auto idBytes = std::span(rbuf).subspan(statusByteLength);
+    auto id = bytesToInt<uint32_t>(idBytes);
+
+    if (id != expected)
+    {
+        error("ID check failed for cmd {CMD}: got {ID}, expected {EXP}", "CMD",
+              lg2::hex, static_cast<uint8_t>(idCmd), "ID", lg2::hex, id, "EXP",
+              lg2::hex, expected);
+        co_return false;
+    }
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> MP297X::isPasswordUnlock()
+{
+    constexpr uint8_t passwordMatchMask = 0x08;
+
+    std::vector<uint8_t> tbuf;
+    std::vector<uint8_t> rbuf;
+
+    tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to set page 0 for password unlock check");
+        co_return false;
+    }
+
+    tbuf = buildByteVector(PMBusCmd::statusCML);
+    rbuf.resize(statusByteLength);
+
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to check password unlock status");
+        co_return false;
+    }
+
+    auto unlocked = (rbuf[0] & passwordMatchMask) != 0;
+
+    debug("Password unlock status: {STATUS}", "STATUS",
+          unlocked ? "Unlocked" : "Locked");
+
+    co_return unlocked;
+}
+
+sdbusplus::async::task<bool> MP297X::unlockWriteProtect()
+{
+    constexpr uint8_t writeProtectModeMask = 0x04;
+    constexpr uint8_t unlockMemoryProtect = 0x00;
+    constexpr uint8_t unlockMTPProtect = 0x63;
+
+    std::vector<uint8_t> tbuf;
+    std::vector<uint8_t> rbuf(statusByteLength);
+
+    // Get write protection mode
+    tbuf = buildByteVector(PMBusCmd::page, MPSPage::page1);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to set page 1 to check write protection mode");
+        co_return false;
+    }
+
+    tbuf = buildByteVector(MP297XCmd::writeProtectMode);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to get write protect mode");
+        co_return false;
+    }
+
+    auto isMTPMode = (rbuf[0] & writeProtectModeMask) == 0;
+    auto unlockData = isMTPMode ? unlockMTPProtect : unlockMemoryProtect;
+
+    // Unlock write protection
+    tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to set page 0 to unlock write protection");
+        co_return false;
+    }
+
+    tbuf = buildByteVector(PMBusCmd::writeProtect, unlockData);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to unlock write protection");
+        co_return false;
+    }
+
+    debug("Unlocked {MODE} write protection", "MODE",
+          isMTPMode ? "MTP" : "memory");
+    co_return true;
+}
+
+sdbusplus::async::task<bool> MP297X::programPageRegisters(
+    MPSPage page, const std::map<uint8_t, std::vector<MPSData>>& groupedData)
+{
+    auto pageNum = static_cast<uint8_t>(page);
+
+    if (groupedData.find(pageNum) == groupedData.end())
+    {
+        debug("No data found for page {PAGE}", "PAGE", pageNum);
+        co_return true;
+    }
+
+    const auto& data = groupedData.at(pageNum);
+
+    // Page 2 is the multi-config page. Enter page2A to write config value
+    // to MTP space directly during multi-config page programming.
+    if (page == MPSPage::page2)
+    {
+        page = MPSPage::page2A;
+        pageNum = static_cast<uint8_t>(page);
+    }
+
+    std::vector<uint8_t> tbuf;
+    std::vector<uint8_t> rbuf;
+
+    tbuf = buildByteVector(PMBusCmd::page, page);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to set page {PAGE} to program registers", "PAGE",
+              pageNum);
+        co_return false;
+    }
+
+    auto i2cWriteWithRetry =
+        [&](const std::vector<uint8_t>& tbuf) -> sdbusplus::async::task<bool> {
+        std::vector<uint8_t> rbuf;
+        constexpr size_t maxRetries = 3;
+        constexpr auto retryDelay = std::chrono::milliseconds(10);
+
+        for (size_t i = 1; i <= maxRetries; ++i)
+        {
+            if (i2cInterface.sendReceive(tbuf, rbuf))
+            {
+                co_return true;
+            }
+            error("I2C write failed, retry {RETRY}", "RETRY", i);
+            co_await sdbusplus::async::sleep_for(ctx, retryDelay);
+        }
+        co_return false;
+    };
+
+    for (const auto& regData : data)
+    {
+        tbuf = {regData.addr};
+        tbuf.insert(tbuf.end(), regData.data.begin(),
+                    regData.data.begin() + regData.length);
+
+        if (!(co_await i2cWriteWithRetry(tbuf)))
+        {
+            error(
+                "Failed to write data {DATA} to register {REG} on page {PAGE}",
+                "DATA", lg2::hex, bytesToInt<uint32_t>(regData.data), "REG",
+                lg2::hex, regData.addr, "PAGE", pageNum);
+            co_return false;
+        }
+
+        if (page == MPSPage::page2A)
+        {
+            // Page 2A requires a delay after each register write
+            co_await sdbusplus::async::sleep_for(ctx,
+                                                 std::chrono::milliseconds(2));
+        }
+    }
+
+    debug("Programmed {N} registers on page {PAGE}", "N", data.size(), "PAGE",
+          pageNum);
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> MP297X::storeDataIntoMTP()
+{
+    std::vector<uint8_t> tbuf;
+    std::vector<uint8_t> rbuf;
+
+    tbuf = buildByteVector(PMBusCmd::page, MPSPage::page0);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to set page 0 for storing data into MTP");
+        co_return false;
+    }
+
+    tbuf = buildByteVector(MP297XCmd::storeDataIntoMTP);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to store data into MTP");
+        co_return false;
+    }
+
+    // Wait store data into MTP
+    co_await sdbusplus::async::sleep_for(ctx, std::chrono::milliseconds(500));
+
+    debug("Stored data into MTP");
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> MP297X::enableMTPPageWriteRead()
+{
+    constexpr uint8_t mtpByteRWEnable = 0x20;
+
+    std::vector<uint8_t> tbuf;
+    std::vector<uint8_t> rbuf;
+
+    tbuf = buildByteVector(PMBusCmd::page, MPSPage::page1);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to set page 1 to enable MTP page write/read");
+        co_return false;
+    }
+
+    tbuf = buildByteVector(MP297XCmd::enableMTPPageWR);
+    rbuf.resize(statusByteLength);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to read MTP page write/read status");
+        co_return false;
+    }
+
+    uint8_t enableMTPPageWRData = rbuf[0] | mtpByteRWEnable;
+    tbuf = buildByteVector(MP297XCmd::enableMTPPageWR, enableMTPPageWRData);
+    rbuf.resize(0);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to enable MTP page write/read");
+        co_return false;
+    }
+
+    debug("Enabled MTP page write/read");
+    co_return true;
+}
+
+sdbusplus::async::task<bool> MP297X::enableMultiConfigCRC()
+{
+    std::vector<uint8_t> tbuf;
+    std::vector<uint8_t> rbuf;
+
+    tbuf = buildByteVector(PMBusCmd::page, MPSPage::page2);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to set page 2 to enable multi-config CRC");
+        co_return false;
+    }
+
+    tbuf = buildByteVector(MP297XCmd::enableMultiConfigCRC);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to enable multi-config CRC");
+        co_return false;
+    }
+
+    debug("Enabled multi-config CRC");
+    co_return true;
+}
+
+sdbusplus::async::task<bool> MP297X::getCRC(uint32_t* checksum)
+{
+    if (checksum == nullptr)
+    {
+        error("getCRC() called with null checksum pointer");
+        co_return false;
+    }
+
+    constexpr size_t crcLength = 2;
+
+    std::vector<uint8_t> tbuf;
+    std::vector<uint8_t> rbuf;
+
+    uint16_t userCodeCRC = 0;
+    uint16_t multiConfigCRC = 0;
+
+    // Read User Code CRC
+    tbuf = buildByteVector(PMBusCmd::page, MPSPage::page29);
+    rbuf.resize(0);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to set page 29 for User Code CRC read");
+        co_return false;
+    }
+
+    tbuf = buildByteVector(MP297XCmd::readUserCodeCRC);
+    rbuf.resize(crcLength);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to read User Code CRC from device");
+        co_return false;
+    }
+
+    userCodeCRC = bytesToInt<uint16_t>(rbuf);
+
+    // Read Multi Config CRC
+    tbuf = buildByteVector(PMBusCmd::page, MPSPage::page2A);
+    rbuf.resize(0);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to set page 2A for Multi Config CRC read");
+        co_return false;
+    }
+
+    tbuf = buildByteVector(MP297XCmd::readMultiConfigCRC);
+    rbuf.resize(crcLength);
+    if (!i2cInterface.sendReceive(tbuf, rbuf))
+    {
+        error("Failed to read Multi Config CRC from device");
+        co_return false;
+    }
+
+    multiConfigCRC = bytesToInt<uint16_t>(rbuf);
+
+    // Combine: [byte3 byte2] = userCodeCRC, [byte1 byte0] = multiConfigCRC
+    *checksum = (static_cast<uint32_t>(userCodeCRC) << 16) |
+                static_cast<uint32_t>(multiConfigCRC);
+
+    debug("Read CRC: {CRC}", "CRC", lg2::hex, *checksum);
+
+    co_return true;
+}
+
+sdbusplus::async::task<bool> MP297X::checkMTPCRC()
+{
+    uint32_t crc = 0;
+    auto expectedCRC = (configuration->crcUser << 16) | configuration->crcMulti;
+
+    // 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, expectedCRC);
+
+    co_return crc == expectedCRC;
+}
+
+bool MP297X::forcedUpdateAllowed()
+{
+    return true;
+}
+
+sdbusplus::async::task<bool> MP297X::updateFirmware(bool force)
+{
+    (void)force;
+
+    auto groupedConfigData = getGroupedConfigData();
+
+    if (!co_await checkId(PMBusCmd::mfrId, configuration->vendorId))
+    {
+        co_return false;
+    }
+
+    if (!co_await checkId(PMBusCmd::mfrModel, configuration->productId))
+    {
+        co_return false;
+    }
+
+    if (!co_await isPasswordUnlock())
+    {
+        co_return false;
+    }
+
+    if (!co_await unlockWriteProtect())
+    {
+        co_return false;
+    }
+
+    if (!co_await programPageRegisters(MPSPage::page0, groupedConfigData))
+    {
+        co_return false;
+    }
+
+    if (!co_await programPageRegisters(MPSPage::page1, groupedConfigData))
+    {
+        co_return false;
+    }
+
+    if (!co_await storeDataIntoMTP())
+    {
+        co_return false;
+    }
+
+    if (!co_await enableMTPPageWriteRead())
+    {
+        co_return false;
+    }
+
+    if (!co_await enableMultiConfigCRC())
+    {
+        co_return false;
+    }
+
+    if (!co_await programPageRegisters(MPSPage::page2, groupedConfigData))
+    {
+        co_return false;
+    }
+
+    if (!(co_await checkMTPCRC()))
+    {
+        co_return false;
+    }
+
+    co_return true;
+}
+
+} // namespace phosphor::software::VR