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

Titus Rwantare <[email protected]>
Newsgroups gmane.comp.emulators.qemu
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.