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

This commit introduces support for programming MPS MP2X6XX VR devices
over the I2C bus. It enables firmware updates for VR models such as
MP2869(A), MP29612(A), and MP29608(A).
Support for the MP2X6XXFirmware type in EM was added in [1].

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

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_CPU1_4228"
    },
    {...}
  ],
  "Members@odata.count": 13,
  "Name": "Software Inventory Collection"
}
```

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

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

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_CPU1_4228\"],\"@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/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-07-29T08:51:41+00:00",
  "TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
  "TaskState": "Running",
  "TaskStatus": "OK"
}
```

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

```
{
  "@odata.id": "/redfish/v1/TaskService/Tasks/0",
  "@odata.type": "#Task.v1_4_3.Task",
  "EndTime": "2025-07-29T08:51:46+00:00",
  "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."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '0' has changed to progress 20 percent complete.",
      "MessageArgs": [
        "0",
        "20"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '0' has changed to progress 50 percent complete.",
      "MessageArgs": [
        "0",
        "50"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '0' has changed to progress 80 percent complete.",
      "MessageArgs": [
        "0",
        "80"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '0' has changed to progress 100 percent complete.",
      "MessageArgs": [
        "0",
        "100"
      ],
      "MessageId": "TaskEvent.1.0.TaskProgressChanged",
      "MessageSeverity": "OK",
      "Resolution": "None."
    },
    {
      "@odata.type": "#Message.v1_1_1.Message",
      "Message": "The task with Id '0' has completed.",
      "MessageArgs": [
        "0"
      ],
      "MessageId": "TaskEvent.1.0.TaskCompletedOK",
      "MessageSeverity": "OK",
      "Resolution": "None."
    }
  ],
  "Name": "Task 0",
  "Payload": {
    "HttpHeaders": [],
    "HttpOperation": "POST",
    "JsonBody": "null",
    "TargetUri": "/redfish/v1/UpdateService/update-multipart"
  },
  "PercentComplete": 100,
  "StartTime": "2025-07-29T08:51:41+00:00",
  "TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
  "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_CPU1_4228"
    },
    {
      "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/Santabarbara_MB_VR_CPU1_5453"
    },
    {...}
  ],
  "Members@odata.count": 14,
  "Name": "Software Inventory Collection"
}
```

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

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

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_CPU1_2249"
    },
    {...}
  ],
  "Members@odata.count": 13,
  "Name": "Software Inventory Collection"
}
```

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

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

Change-Id: Ifa6fdd79f5d087adb6037e0043897fa6199ae097
Signed-off-by: Kevin Tung <Kevin.Tung@quantatw.com>
diff --git a/common/include/pmbus.hpp b/common/include/pmbus.hpp
new file mode 100644
index 0000000..d62db4d
--- /dev/null
+++ b/common/include/pmbus.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <cstdint>
+
+enum class PMBusCmd : uint8_t
+{
+    page = 0x00,
+    writeProtect = 0x10,
+    storeUserCode = 0x17,
+    mfrId = 0x99,
+    mfrSerial = 0x9E,
+    icDeviceId = 0xAD,
+};
diff --git a/common/include/utils.hpp b/common/include/utils.hpp
index 65d7022..9325678 100644
--- a/common/include/utils.hpp
+++ b/common/include/utils.hpp
@@ -14,3 +14,92 @@
 sdbusplus::async::task<bool> asyncSystem(
     sdbusplus::async::context& ctx, const std::string& cmd,
     std::optional<std::reference_wrapper<std::string>> result = std::nullopt);
+
+/**
+ * @brief Convert bytes to an integer of the given type.
+ *
+ * @tparam IntegerType Output integer type (e.g., uint16_t, uint32_t).
+ * @tparam Container A container of uint8_t bytes.
+ * @param data Byte data to convert.
+ * @param bigEndian Set true for big-endian order; false for little-endian.
+ * @return Converted integer.
+ */
+template <typename IntegerType, typename Container>
+IntegerType bytesToInt(const Container& data, bool bigEndian = false)
+{
+    static_assert(std::is_integral_v<IntegerType>,
+                  "IntegerType must be an integral type");
+    static_assert(std::is_same_v<typename Container::value_type, uint8_t>,
+                  "Container must hold uint8_t elements");
+
+    constexpr size_t maxBytes = sizeof(IntegerType);
+    size_t size = std::min(data.size(), maxBytes);
+
+    IntegerType result = 0;
+    for (size_t i = 0; i < size; ++i)
+    {
+        size_t shift = bigEndian ? (size - 1 - i) * 8 : i * 8;
+        result |= static_cast<IntegerType>(data[i]) << shift;
+    }
+
+    return result;
+}
+
+template <typename>
+inline constexpr bool always_false = false;
+
+/**
+ * @brief Constructs a vector of bytes (`std::vector<uint8_t>`) from a variable
+ *        number of arguments, which can include enums, integral values,
+ *        and initializer lists.
+ *
+ * This function is useful when building byte packets or command sequences
+ * to be sent over communication protocols (e.g., I2C, UART, SPI).
+ *
+ * @tparam Args Types of arguments to convert into bytes
+ * @param args The values to encode into the byte vector
+ * @return std::vector<uint8_t> A flattened list of bytes
+ *
+ * @note Passing unsupported types will trigger a compile-time static_assert.
+ * @note Endianness: Multi-byte integers use little-endian order.
+ *
+ * @code
+ * enum class Command : uint8_t { Start = 0x01 };
+ * auto buf = buildByteVector(Command::Start, 0x1234, {0xAA, 0xBB});
+ * // Result: { 0x01, 0x34, 0x12, 0xAA, 0xBB }
+ * @endcode
+ */
+template <typename... Args>
+std::vector<uint8_t> buildByteVector(Args&&... args)
+{
+    std::vector<uint8_t> buf;
+
+    auto append = [&](auto&& value) {
+        using T = std::decay_t<decltype(value)>;
+
+        if constexpr (std::is_enum_v<T>)
+        {
+            buf.push_back(static_cast<uint8_t>(value));
+        }
+        else if constexpr (std::is_integral_v<T>)
+        {
+            for (size_t i = 0; i < sizeof(T); ++i)
+            {
+                buf.push_back(static_cast<uint8_t>(value >> (i * 8)));
+            }
+        }
+        else if constexpr (std::is_same_v<T, std::initializer_list<uint8_t>>)
+        {
+            buf.insert(buf.end(), value.begin(), value.end());
+        }
+        else
+        {
+            static_assert(always_false<T>,
+                          "Unsupported type in buildByteVector");
+        }
+    };
+
+    (append(std::forward<Args>(args)), ...);
+
+    return buf;
+}