[PATCH 5/8] hw/sensor: switch adm1266 to millivolts vout

Titus Rwantare <[email protected]> Wed, 29 Jul 2026 23:13:20 +0000
Newsgroups org.nongnu.qemu-arm,org.nongnu.qemu-devel
Message-ID <[email protected]>
Enables storing fractional voltages for the ADM1266 over QMP

Signed-off-by: Titus Rwantare <[email protected]>
---
 hw/sensor/adm1266.c        |  27 ++++---
 tests/qtest/adm1266-test.c | 150 +++++++++++++++++++++++++++++++++++++
 2 files changed, 167 insertions(+), 10 deletions(-)

diff --git a/hw/sensor/adm1266.c b/hw/sensor/adm1266.c
index 2979557309..80960dc1c4 100644
--- a/hw/sensor/adm1266.c
+++ b/hw/sensor/adm1266.c
@@ -263,32 +263,39 @@ static int adm1266_write_data(PMBusDevice *pmdev, const uint8_t *buf,
 static void adm1266_get(Object *obj, Visitor *v, const char *name, void *opaque,
                         Error **errp)
 {
-    uint16_t value;
+    uint32_t value, index;
     PMBusDevice *pmdev = PMBUS_DEVICE(obj);
     PMBusVoutMode *mode = (PMBusVoutMode *)&pmdev->pages[0].vout_mode;
 
-    if (strcmp(name, "vout") == 0) {
-        value = pmbus_linear_mode2data(*(uint16_t *)opaque, mode->exp);
+    if (strncmp(name, "vout[", 5) == 0) {
+        sscanf(name, "vout[%u]", &index);
+        mode = (PMBusVoutMode *)&pmdev->pages[index].vout_mode;
+        value = pmbus_linear_mode2milliunits(*(uint16_t *)opaque, mode->exp);
     } else {
         value = *(uint16_t *)opaque;
     }
 
-    visit_type_uint16(v, name, &value, errp);
+    visit_type_uint32(v, name, &value, errp);
 }
 
 static void adm1266_set(Object *obj, Visitor *v, const char *name, void *opaque,
                         Error **errp)
 {
     uint16_t *internal = opaque;
-    uint16_t value;
+    uint32_t value, index;
     PMBusDevice *pmdev = PMBUS_DEVICE(obj);
-    PMBusVoutMode *mode = (PMBusVoutMode *)&pmdev->pages[0].vout_mode;
+    PMBusVoutMode *mode;
 
-    if (!visit_type_uint16(v, name, &value, errp)) {
+    if (!visit_type_uint32(v, name, &value, errp)) {
         return;
     }
-
-    *internal = pmbus_data2linear_mode(value, mode->exp);
+    if (strncmp(name, "vout[", 5) == 0) {
+        sscanf(name, "vout[%u]", &index);
+        mode = (PMBusVoutMode *)&pmdev->pages[index].vout_mode;
+        *internal = pmbus_milliunits2linear_mode(value, mode->exp);
+    } else {
+        *internal = value;
+    }
     pmbus_check_limits(pmdev);
 }
 
@@ -311,7 +318,7 @@ static void adm1266_init(Object *obj)
     for (int i = 0; i < ADM1266_NUM_PAGES; i++) {
         pmbus_page_config(pmdev, i, flags);
 
-        object_property_add(obj, "vout[*]", "uint16",
+        object_property_add(obj, "vout[*]", "uint32",
                             adm1266_get,
                             adm1266_set, NULL, &pmdev->pages[i].read_vout);
     }
diff --git a/tests/qtest/adm1266-test.c b/tests/qtest/adm1266-test.c
index fa8bbc5795..fd3d8079b6 100644
--- a/tests/qtest/adm1266-test.c
+++ b/tests/qtest/adm1266-test.c
@@ -16,6 +16,7 @@
 #include "qobject/qdict.h"
 #include "qobject/qnum.h"
 #include "qemu/bitops.h"
+#include "qemu/bswap.h"
 
 #define TEST_ID "adm1266-test"
 #define TEST_ADDR (0x12)
@@ -45,6 +46,57 @@
 #define TEST_STRING_B                           "b sample"
 #define TEST_STRING_C                           "rev c"
 
+#define ADM1266_NUM_PAGES                       17
+#define ADM1266_MAX_VALUE                       65535000
+
+typedef union {
+    uint8_t raw;
+    PMBusVoutMode mode;
+} ADM1266VoutMode;
+
+static uint32_t qmp_adm1266_get(const char *id, const char *property)
+{
+    QDict *response;
+    uint32_t ret;
+    response = qmp("{ 'execute': 'qom-get', 'arguments': { 'path': %s, "
+                   "'property': %s } }", id, property);
+    g_assert(qdict_haskey(response, "return"));
+    ret = qnum_get_uint(qobject_to(QNum, qdict_get(response, "return")));
+    qobject_unref(response);
+    return ret;
+}
+
+static void qmp_adm1266_set(const char *id,
+                             const char *property,
+                             uint32_t value)
+{
+    QDict *response;
+
+    response = qmp("{ 'execute': 'qom-set', 'arguments': { 'path': %s, "
+                   "'property': %s, 'value': %u } }",
+                   id, property, value);
+    g_assert(qdict_haskey(response, "return"));
+}
+
+static uint64_t adm1266_linear_mode2milliunits(uint16_t value, int exp)
+{
+    /* D = L * 2^e */
+    uint64_t val = value;
+    uint64_t ret;
+
+    if (exp < 0) {
+        ret = DIV_ROUND_CLOSEST((val * 1000), 1ULL << (-exp));
+    } else {
+        ret = (val << exp) * 1000;
+    }
+
+    if (ret > UINT32_MAX) {
+        return UINT32_MAX;
+    }
+
+    return ret;
+}
+
 static void compare_string(QI2CDevice *i2cdev, uint8_t reg,
                            const char *test_str)
 {
@@ -67,6 +119,98 @@ static void write_and_compare_string(QI2CDevice *i2cdev, uint8_t reg,
     compare_string(i2cdev, reg, test_str);
 }
 
+static void test_vout_milliunits(void *obj, void *data, QGuestAllocator *alloc)
+{
+    uint16_t i2c_value, value;
+    uint64_t i2c_milliunits;
+    QI2CDevice *i2cdev = (QI2CDevice *)obj;
+    char *path;
+    ADM1266VoutMode m;
+
+    /* set a different value in millivolts for each page */
+    for (int i = 0; i < ADM1266_NUM_PAGES; i++) {
+        path = g_strdup_printf("vout[%d]", i);
+        qmp_adm1266_set(TEST_ID, path, (1000 * (i + 1)));
+    }
+
+    for (int i = 0; i < ADM1266_NUM_PAGES; i++) {
+        i2c_set8(i2cdev, PMBUS_PAGE, i);
+
+        m.raw = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
+        i2c_value = bswap16(i2c_get16(i2cdev, PMBUS_READ_VOUT));
+        i2c_milliunits = adm1266_linear_mode2milliunits(i2c_value, m.mode.exp);
+        g_assert_cmpuint(i2c_milliunits, ==, (1000 * (i + 1)));
+
+        path = g_strdup_printf("vout[%d]", i);
+        value = qmp_adm1266_get(TEST_ID, path);
+        g_assert_cmpuint(value, ==, (1000 * (i + 1)));
+    }
+}
+
+/*
+ * Note that the exponent determines the dynamic range, large exponents can not
+ * be used with values that need to be incremented in small steps
+ */
+static void test_vout_mode_exponent(void *obj, void *data,
+                                    QGuestAllocator *alloc)
+{
+    uint16_t i2c_value, value, expected;
+    uint64_t i2c_milliunits;
+    QI2CDevice *i2cdev = (QI2CDevice *)obj;
+    ADM1266VoutMode m;
+    char *path;
+
+    /* set a different exponent per page and a different value */
+    for (int i = 0; i < ADM1266_NUM_PAGES; i++) {
+        i2c_set8(i2cdev, PMBUS_PAGE, i);
+        expected = 1000 * (i * 2);
+        m.mode.exp = i - 14;
+        i2c_set8(i2cdev, PMBUS_VOUT_MODE, m.raw);
+        path = g_strdup_printf("vout[%d]", i);
+        qmp_adm1266_set(TEST_ID, path, expected);
+    }
+
+    for (int i = 0; i < ADM1266_NUM_PAGES; i++) {
+        i2c_set8(i2cdev, PMBUS_PAGE, i);
+        expected = 1000 * (i * 2);
+        /* check correct value from i2c*/
+        m.raw = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
+        i2c_value = bswap16(i2c_get16(i2cdev, PMBUS_READ_VOUT));
+        i2c_milliunits = adm1266_linear_mode2milliunits(i2c_value, m.mode.exp);
+        g_assert_cmpuint(i2c_milliunits, ==, expected);
+
+        /* check correct value from qmp*/
+        path = g_strdup_printf("vout[%d]", i);
+        value = qmp_adm1266_get(TEST_ID, path);
+        g_assert_cmpuint(value, ==, expected);
+    }
+}
+
+static void test_vout_clamp_to_max(void *obj, void *data,
+                                   QGuestAllocator *alloc)
+{
+    uint16_t i2c_value;
+    uint32_t value;
+    QI2CDevice *i2cdev = (QI2CDevice *)obj;
+    char *path;
+
+    for (int i = 0; i < ADM1266_NUM_PAGES; i++) {
+        path = g_strdup_printf("vout[%d]", i);
+        qmp_adm1266_set(TEST_ID, path, 90000000);
+    }
+
+    for (int i = 0; i < ADM1266_NUM_PAGES; i++) {
+        i2c_set8(i2cdev, PMBUS_PAGE, i);
+
+        i2c_value = bswap16(i2c_get16(i2cdev, PMBUS_READ_VOUT));
+        g_assert_cmpuint(i2c_value, ==, UINT16_MAX);
+
+        path = g_strdup_printf("vout[%d]", i);
+        value = qmp_adm1266_get(TEST_ID, path);
+        g_assert_cmpuint(value, ==, ADM1266_MAX_VALUE);
+    }
+}
+
 static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
 {
     uint16_t i2c_value;
@@ -128,6 +272,12 @@ static void adm1266_register_nodes(void)
     qos_add_test("test_defaults", "adm1266", test_defaults, NULL);
     qos_add_test("test_partial_reads", "adm1266", test_partial_reads, NULL);
     qos_add_test("test_rw_regs", "adm1266", test_rw_regs, NULL);
+    qos_add_test("test_vout_milliunits", "adm1266",
+                    test_vout_milliunits, NULL);
+    qos_add_test("test_vout_mode_exponent", "adm1266",
+                    test_vout_mode_exponent, NULL);
+    qos_add_test("test_vout_clamp_to_max", "adm1266",
+                    test_vout_clamp_to_max, NULL);
 }
 
 libqos_init(adm1266_register_nodes);
-- 
2.55.0.508.g3f0d502094-goog