hw/arm/Kconfig | 1 + hw/sensor/Kconfig | 4 + hw/sensor/max16600.c | 197 ++++++++++++++++++++++++++++ hw/sensor/meson.build | 1 + include/hw/sensor/max16600.h | 46 +++++++ tests/qtest/max16600-test.c | 241 +++++++++++++++++++++++++++++++++++ tests/qtest/meson.build | 1 + 7 files changed, 491 insertions(+) create mode 100644 hw/sensor/max16600.c create mode 100644 include/hw/sensor/max16600.h create mode 100644 tests/qtest/max16600-test.c
From: Shengtan Mao <stmao@google.com>
Signed-off-by: Shengtan Mao <stmao@google.com>
Signed-off-by: Titus Rwantare <titusr@google.com>
Signed-off-by: Nabih Estefan <nabihestefan@google.com>
---
hw/arm/Kconfig | 1 +
hw/sensor/Kconfig | 4 +
hw/sensor/max16600.c | 197 ++++++++++++++++++++++++++++
hw/sensor/meson.build | 1 +
include/hw/sensor/max16600.h | 46 +++++++
tests/qtest/max16600-test.c | 241 +++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
7 files changed, 491 insertions(+)
create mode 100644 hw/sensor/max16600.c
create mode 100644 include/hw/sensor/max16600.h
create mode 100644 tests/qtest/max16600-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 2aa4b5d778..4ab0a93ba6 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -480,6 +480,7 @@ config NPCM7XX
select AT24C # EEPROM
select MAX34451
select ISL_PMBUS_VR
+ select MAX_16600
select PL310 # cache controller
select PMBUS
select SERIAL_MM
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index bc6331b4ab..ef7b3262a8 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -43,3 +43,7 @@ config ISL_PMBUS_VR
config MAX31785
bool
depends on PMBUS
+
+config MAX_16600
+ bool
+ depends on I2C
diff --git a/hw/sensor/max16600.c b/hw/sensor/max16600.c
new file mode 100644
index 0000000000..1941391dab
--- /dev/null
+++ b/hw/sensor/max16600.c
@@ -0,0 +1,197 @@
+/*
+ * MAX16600 VR13.HC Dual-Output Voltage Regulator Chipset
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright 2025 Google LLC
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/pmbus_device.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "hw/sensor/max16600.h"
+
+static uint8_t max16600_read_byte(PMBusDevice *pmdev)
+{
+ MAX16600State *s = MAX16600(pmdev);
+
+ switch (pmdev->code) {
+ case PMBUS_IC_DEVICE_ID:
+ pmbus_send_string(pmdev, s->ic_device_id);
+ break;
+
+ case MAX16600_PHASE_ID:
+ pmbus_send8(pmdev, s->phase_id);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+ return 0xFF;
+}
+
+static int max16600_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+ uint8_t len)
+{
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to unsupported register: 0x%02x\n", __func__,
+ pmdev->code);
+ return 0xFF;
+}
+
+static void max16600_exit_reset(Object *obj, ResetType type)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ MAX16600State *s = MAX16600(obj);
+
+ pmdev->capability = MAX16600_CAPABILITY_DEFAULT;
+ pmdev->page = 0;
+
+ pmdev->pages[0].operation = MAX16600_OPERATION_DEFAULT;
+ pmdev->pages[0].on_off_config = MAX16600_ON_OFF_CONFIG_DEFAULT;
+ pmdev->pages[0].vout_mode = MAX16600_VOUT_MODE_DEFAULT;
+
+ pmdev->pages[0].read_vin =
+ pmbus_data2linear_mode(MAX16600_READ_VIN_DEFAULT, max16600_exp.vin);
+ pmdev->pages[0].read_iin =
+ pmbus_data2linear_mode(MAX16600_READ_IIN_DEFAULT, max16600_exp.iin);
+ pmdev->pages[0].read_pin =
+ pmbus_data2linear_mode(MAX16600_READ_PIN_DEFAULT, max16600_exp.pin);
+ pmdev->pages[0].read_vout = MAX16600_READ_VOUT_DEFAULT;
+ pmdev->pages[0].read_iout =
+ pmbus_data2linear_mode(MAX16600_READ_IOUT_DEFAULT, max16600_exp.iout);
+ pmdev->pages[0].read_pout =
+ pmbus_data2linear_mode(MAX16600_READ_PIN_DEFAULT, max16600_exp.pout);
+ pmdev->pages[0].read_temperature_1 =
+ pmbus_data2linear_mode(MAX16600_READ_TEMP_DEFAULT, max16600_exp.temp);
+
+ s->ic_device_id = "MAX16601";
+ s->phase_id = MAX16600_PHASE_ID_DEFAULT;
+}
+
+static void max16600_get(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ uint16_t value;
+
+ if (strcmp(name, "vin") == 0) {
+ value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.vin);
+ } else if (strcmp(name, "iin") == 0) {
+ value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.iin);
+ } else if (strcmp(name, "pin") == 0) {
+ value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.pin);
+ } else if (strcmp(name, "iout") == 0) {
+ value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.iout);
+ } else if (strcmp(name, "pout") == 0) {
+ value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.pout);
+ } else if (strcmp(name, "temperature") == 0) {
+ value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.temp);
+ } else {
+ value = *(uint16_t *)opaque;
+ }
+
+ /* scale to milli-units */
+ if (strcmp(name, "pout") != 0 && strcmp(name, "pin") != 0) {
+ value *= 1000;
+ }
+
+ visit_type_uint16(v, name, &value, errp);
+}
+
+static void max16600_set(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint16_t *internal = opaque;
+ uint16_t value;
+ if (!visit_type_uint16(v, name, &value, errp)) {
+ return;
+ }
+
+ /* inputs match kernel driver which scales to milliunits except power */
+ if (strcmp(name, "pout") != 0 && strcmp(name, "pin") != 0) {
+ value /= 1000;
+ }
+
+ if (strcmp(name, "vin") == 0) {
+ *internal = pmbus_data2linear_mode(value, max16600_exp.vin);
+ } else if (strcmp(name, "iin") == 0) {
+ *internal = pmbus_data2linear_mode(value, max16600_exp.iin);
+ } else if (strcmp(name, "pin") == 0) {
+ *internal = pmbus_data2linear_mode(value, max16600_exp.pin);
+ } else if (strcmp(name, "iout") == 0) {
+ *internal = pmbus_data2linear_mode(value, max16600_exp.iout);
+ } else if (strcmp(name, "pout") == 0) {
+ *internal = pmbus_data2linear_mode(value, max16600_exp.pout);
+ } else if (strcmp(name, "temperature") == 0) {
+ *internal = pmbus_data2linear_mode(value, max16600_exp.temp);
+ } else {
+ *internal = value;
+ }
+
+ pmbus_check_limits(pmdev);
+}
+
+static void max16600_init(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint64_t flags = PB_HAS_VOUT_MODE | PB_HAS_VIN | PB_HAS_IIN | PB_HAS_PIN |
+ PB_HAS_IOUT | PB_HAS_POUT | PB_HAS_VOUT |
+ PB_HAS_TEMPERATURE | PB_HAS_MFR_INFO;
+ pmbus_page_config(pmdev, 0, flags);
+
+ object_property_add(obj, "vin", "uint16", max16600_get, max16600_set, NULL,
+ &pmdev->pages[0].read_vin);
+
+ object_property_add(obj, "iin", "uint16", max16600_get, max16600_set, NULL,
+ &pmdev->pages[0].read_iin);
+
+ object_property_add(obj, "pin", "uint16", max16600_get, max16600_set, NULL,
+ &pmdev->pages[0].read_pin);
+
+ object_property_add(obj, "vout", "uint16", max16600_get, max16600_set,
+ NULL, &pmdev->pages[0].read_vout);
+
+ object_property_add(obj, "iout", "uint16", max16600_get, max16600_set,
+ NULL, &pmdev->pages[0].read_iout);
+
+ object_property_add(obj, "pout", "uint16", max16600_get, max16600_set,
+ NULL, &pmdev->pages[0].read_pout);
+
+ object_property_add(obj, "temperature", "uint16",
+ max16600_get, max16600_set,
+ NULL, &pmdev->pages[0].read_temperature_1);
+}
+
+static void max16600_class_init(ObjectClass *klass, const void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
+
+ dc->desc = "MAX16600 Dual-Output Voltage Regulator";
+ k->write_data = max16600_write_data;
+ k->receive_byte = max16600_read_byte;
+ k->device_num_pages = 1;
+
+ rc->phases.exit = max16600_exit_reset;
+}
+
+static const TypeInfo max16600_info = {
+ .name = TYPE_MAX16600,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(MAX16600State),
+ .instance_init = max16600_init,
+ .class_init = max16600_class_init,
+};
+
+static void max16600_register_types(void)
+{
+ type_register_static(&max16600_info);
+}
+
+type_init(max16600_register_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index 420fdc3359..85c2c73c99 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -8,3 +8,4 @@ system_ss.add(when: 'CONFIG_MAX34451', if_true: files('max34451.c'))
system_ss.add(when: 'CONFIG_LSM303DLHC_MAG', if_true: files('lsm303dlhc_mag.c'))
system_ss.add(when: 'CONFIG_ISL_PMBUS_VR', if_true: files('isl_pmbus_vr.c'))
system_ss.add(when: 'CONFIG_MAX31785', if_true: files('max31785.c'))
+system_ss.add(when: 'CONFIG_MAX_16600', if_true: files('max16600.c'))
diff --git a/include/hw/sensor/max16600.h b/include/hw/sensor/max16600.h
new file mode 100644
index 0000000000..a8cd0a5d4b
--- /dev/null
+++ b/include/hw/sensor/max16600.h
@@ -0,0 +1,46 @@
+/*
+ * MAX16600 VR13.HC Dual-Output Voltage Regulator Chipset
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright 2025 Google LLC
+ */
+
+#include "hw/i2c/pmbus_device.h"
+
+#define TYPE_MAX16600 "max16600"
+#define MAX16600(obj) OBJECT_CHECK(MAX16600State, (obj), TYPE_MAX16600)
+
+#define MAX16600_PHASE_ID 0xF3
+/*
+ * Packet error checking capability is disabled.
+ * Pending QEMU support
+ */
+#define MAX16600_CAPABILITY_DEFAULT 0x30
+#define MAX16600_OPERATION_DEFAULT 0x88
+#define MAX16600_ON_OFF_CONFIG_DEFAULT 0x17
+#define MAX16600_VOUT_MODE_DEFAULT 0x22
+#define MAX16600_PHASE_ID_DEFAULT 0x80
+
+#define MAX16600_READ_VIN_DEFAULT 5 /* Volts */
+#define MAX16600_READ_IIN_DEFAULT 3 /* Amps */
+#define MAX16600_READ_PIN_DEFAULT 100 /* Watts */
+#define MAX16600_READ_VOUT_DEFAULT 5 /* Volts */
+#define MAX16600_READ_IOUT_DEFAULT 3 /* Amps */
+#define MAX16600_READ_POUT_DEFAULT 100 /* Watts */
+#define MAX16600_READ_TEMP_DEFAULT 40 /* Celsius */
+
+typedef struct MAX16600State {
+ PMBusDevice parent;
+ const char *ic_device_id;
+ uint8_t phase_id;
+} MAX16600State;
+
+/*
+ * determines the exponents used in linear conversion for CORE
+ * (iin, pin) may be (-4, 0) or (-3, 1)
+ * iout may be -2, -1, 0, 1
+ */
+static const struct {
+ int vin, iin, pin, iout, pout, temp;
+} max16600_exp = {-6, -4, 0, -2, -1, 0};
diff --git a/tests/qtest/max16600-test.c b/tests/qtest/max16600-test.c
new file mode 100644
index 0000000000..bad5da7989
--- /dev/null
+++ b/tests/qtest/max16600-test.c
@@ -0,0 +1,241 @@
+/*
+ * QTest for the MAX16600 VR13.HC Dual-Output Voltage Regulator Chipset
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright 2025 Google LLC
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/pmbus_device.h"
+#include "hw/sensor/max16600.h"
+#include "libqtest-single.h"
+#include "libqos/qgraph.h"
+#include "libqos/i2c.h"
+#include "qobject/qdict.h"
+#include "qobject/qnum.h"
+#include "qemu/bitops.h"
+
+#define TEST_ID "max16600-test"
+#define TEST_ADDR (0x61)
+
+uint16_t pmbus_linear_mode2data(uint16_t value, int exp)
+{
+ /* D = L * 2^e */
+ if (exp < 0) {
+ return value >> (-exp);
+ }
+ return value << exp;
+}
+
+static uint16_t qmp_max16600_get(const char *id, const char *property)
+{
+ QDict *response;
+ uint64_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_max16600_set(const char *id, const char *property,
+ uint16_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"));
+ qobject_unref(response);
+}
+
+static uint16_t max16600_i2c_get16(QI2CDevice *i2cdev, uint8_t reg)
+{
+ uint8_t resp[2];
+ i2c_read_block(i2cdev, reg, resp, sizeof(resp));
+ return (resp[1] << 8) | resp[0];
+}
+
+static void max16600_i2c_set16(QI2CDevice *i2cdev, uint8_t reg, uint16_t value)
+{
+ uint8_t data[2];
+
+ data[0] = value & 255;
+ data[1] = value >> 8;
+ i2c_write_block(i2cdev, reg, data, sizeof(data));
+}
+
+/* test default values */
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value, value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
+ g_assert_cmphex(i2c_value, ==, MAX16600_CAPABILITY_DEFAULT);
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
+ g_assert_cmphex(i2c_value, ==, MAX16600_OPERATION_DEFAULT);
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_ON_OFF_CONFIG);
+ g_assert_cmphex(i2c_value, ==, MAX16600_ON_OFF_CONFIG_DEFAULT);
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
+ g_assert_cmphex(i2c_value, ==, MAX16600_VOUT_MODE_DEFAULT);
+
+ value = qmp_max16600_get(TEST_ID, "vin") / 1000;
+ g_assert_cmpuint(value, ==, MAX16600_READ_VIN_DEFAULT);
+
+ value = qmp_max16600_get(TEST_ID, "iin") / 1000;
+ g_assert_cmpuint(value, ==, MAX16600_READ_IIN_DEFAULT);
+
+ value = qmp_max16600_get(TEST_ID, "pin");
+ g_assert_cmpuint(value, ==, MAX16600_READ_PIN_DEFAULT);
+
+ value = qmp_max16600_get(TEST_ID, "vout") / 1000;
+ g_assert_cmpuint(value, ==, MAX16600_READ_VOUT_DEFAULT);
+
+ value = qmp_max16600_get(TEST_ID, "iout") / 1000;
+ g_assert_cmpuint(value, ==, MAX16600_READ_IOUT_DEFAULT);
+
+ value = qmp_max16600_get(TEST_ID, "pout");
+ g_assert_cmpuint(value, ==, MAX16600_READ_POUT_DEFAULT);
+
+ value = qmp_max16600_get(TEST_ID, "temperature") / 1000;
+ g_assert_cmpuint(value, ==, MAX16600_READ_TEMP_DEFAULT);
+}
+
+/* test qmp access */
+static void test_tx_rx(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t value, i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ qmp_max16600_set(TEST_ID, "vin", 2000);
+ value = qmp_max16600_get(TEST_ID, "vin") / 1000;
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VIN);
+ i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.vin);
+ g_assert_cmpuint(value, ==, i2c_value);
+
+ qmp_max16600_set(TEST_ID, "iin", 3000);
+ value = qmp_max16600_get(TEST_ID, "iin") / 1000;
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IIN);
+ i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.iin);
+ g_assert_cmpuint(value, ==, i2c_value);
+
+ qmp_max16600_set(TEST_ID, "pin", 4);
+ value = qmp_max16600_get(TEST_ID, "pin");
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_PIN);
+ i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.pin);
+ g_assert_cmpuint(value, ==, i2c_value);
+
+ qmp_max16600_set(TEST_ID, "vout", 5000);
+ value = qmp_max16600_get(TEST_ID, "vout") / 1000;
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ g_assert_cmpuint(value, ==, i2c_value);
+
+ qmp_max16600_set(TEST_ID, "iout", 6000);
+ value = qmp_max16600_get(TEST_ID, "iout") / 1000;
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IOUT);
+ i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.iout);
+ g_assert_cmpuint(value, ==, i2c_value);
+
+ qmp_max16600_set(TEST_ID, "pout", 7);
+ value = qmp_max16600_get(TEST_ID, "pout");
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_POUT);
+ i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.pout);
+ g_assert_cmpuint(value, ==, i2c_value);
+
+ qmp_max16600_set(TEST_ID, "temperature", 8000);
+ value = qmp_max16600_get(TEST_ID, "temperature") / 1000;
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.temp);
+ g_assert_cmpuint(value, ==, i2c_value);
+}
+
+/* test r/w registers */
+static void test_rw_regs(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ max16600_i2c_set16(i2cdev, PMBUS_OPERATION, 0xA);
+ i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
+ g_assert_cmphex(i2c_value, ==, 0xA);
+
+ max16600_i2c_set16(i2cdev, PMBUS_ON_OFF_CONFIG, 0xB);
+ i2c_value = i2c_get8(i2cdev, PMBUS_ON_OFF_CONFIG);
+ g_assert_cmphex(i2c_value, ==, 0xB);
+
+ max16600_i2c_set16(i2cdev, PMBUS_VOUT_MODE, 0xC);
+ i2c_value = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
+ g_assert_cmphex(i2c_value, ==, 0xC);
+}
+
+/* test read-only registers */
+static void test_ro_regs(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_init_value, i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_init_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
+ max16600_i2c_set16(i2cdev, PMBUS_CAPABILITY, 0xD);
+ i2c_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VIN);
+ max16600_i2c_set16(i2cdev, PMBUS_READ_VIN, 0x1234);
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VIN);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IIN);
+ max16600_i2c_set16(i2cdev, PMBUS_READ_IIN, 0x2234);
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IIN);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_PIN);
+ max16600_i2c_set16(i2cdev, PMBUS_READ_PIN, 0x3234);
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_PIN);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ max16600_i2c_set16(i2cdev, PMBUS_READ_VOUT, 0x4234);
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IOUT);
+ max16600_i2c_set16(i2cdev, PMBUS_READ_IOUT, 0x5235);
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IOUT);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_POUT);
+ max16600_i2c_set16(i2cdev, PMBUS_READ_POUT, 0x6234);
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_POUT);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ max16600_i2c_set16(i2cdev, PMBUS_READ_TEMPERATURE_1, 0x7236);
+ i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+}
+
+static void max16600_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {.extra_device_opts =
+ "id=" TEST_ID ",address=0x61"};
+ add_qi2c_address(&opts, &(QI2CAddress){TEST_ADDR});
+
+ qos_node_create_driver("max16600", i2c_device_create);
+ qos_node_consumes("max16600", "i2c-bus", &opts);
+
+ qos_add_test("test_defaults", "max16600", test_defaults, NULL);
+ qos_add_test("test_tx_rx", "max16600", test_tx_rx, NULL);
+ qos_add_test("test_rw_regs", "max16600", test_rw_regs, NULL);
+ qos_add_test("test_ro_regs", "max16600", test_ro_regs, NULL);
+}
+libqos_init(max16600_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 669d07c06b..459cf41985 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -295,6 +295,7 @@ qos_test_ss.add(
'es1370-test.c',
'lsm303dlhc-mag-test.c',
'isl_pmbus_vr-test.c',
+ 'max16600-test.c',
'max34451-test.c',
'megasas-test.c',
'ne2000-test.c',
--
2.51.0.355.g5224444f11-goog
On Wed, 3 Sept 2025 at 16:54, Nabih Estefan <nabihestefan@google.com> wrote: > > From: Shengtan Mao <stmao@google.com> > > Signed-off-by: Shengtan Mao <stmao@google.com> > Signed-off-by: Titus Rwantare <titusr@google.com> > Signed-off-by: Nabih Estefan <nabihestefan@google.com> > --- > hw/arm/Kconfig | 1 + > hw/sensor/Kconfig | 4 + > hw/sensor/max16600.c | 197 ++++++++++++++++++++++++++++ > hw/sensor/meson.build | 1 + > include/hw/sensor/max16600.h | 46 +++++++ > tests/qtest/max16600-test.c | 241 +++++++++++++++++++++++++++++++++++ > tests/qtest/meson.build | 1 + No documentation, and no board code user of it? The empty commit message gives no hint of the motivation behind implementing this device either. thanks -- PMM
Nabih Estefan <nabihestefan@google.com> writes:
> From: Shengtan Mao <stmao@google.com>
>
> Signed-off-by: Shengtan Mao <stmao@google.com>
> Signed-off-by: Titus Rwantare <titusr@google.com>
> Signed-off-by: Nabih Estefan <nabihestefan@google.com>
> ---
> hw/arm/Kconfig | 1 +
> hw/sensor/Kconfig | 4 +
> hw/sensor/max16600.c | 197 ++++++++++++++++++++++++++++
> hw/sensor/meson.build | 1 +
> include/hw/sensor/max16600.h | 46 +++++++
> tests/qtest/max16600-test.c | 241 +++++++++++++++++++++++++++++++++++
> tests/qtest/meson.build | 1 +
> 7 files changed, 491 insertions(+)
> create mode 100644 hw/sensor/max16600.c
> create mode 100644 include/hw/sensor/max16600.h
> create mode 100644 tests/qtest/max16600-test.c
>
> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index 2aa4b5d778..4ab0a93ba6 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -480,6 +480,7 @@ config NPCM7XX
> select AT24C # EEPROM
> select MAX34451
> select ISL_PMBUS_VR
> + select MAX_16600
> select PL310 # cache controller
> select PMBUS
> select SERIAL_MM
> diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
> index bc6331b4ab..ef7b3262a8 100644
> --- a/hw/sensor/Kconfig
> +++ b/hw/sensor/Kconfig
> @@ -43,3 +43,7 @@ config ISL_PMBUS_VR
> config MAX31785
> bool
> depends on PMBUS
> +
> +config MAX_16600
> + bool
> + depends on I2C
> diff --git a/hw/sensor/max16600.c b/hw/sensor/max16600.c
> new file mode 100644
> index 0000000000..1941391dab
> --- /dev/null
> +++ b/hw/sensor/max16600.c
> @@ -0,0 +1,197 @@
> +/*
> + * MAX16600 VR13.HC Dual-Output Voltage Regulator Chipset
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright 2025 Google LLC
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/i2c/pmbus_device.h"
> +#include "qapi/visitor.h"
> +#include "qemu/log.h"
> +#include "hw/sensor/max16600.h"
> +
> +static uint8_t max16600_read_byte(PMBusDevice *pmdev)
> +{
> + MAX16600State *s = MAX16600(pmdev);
> +
> + switch (pmdev->code) {
> + case PMBUS_IC_DEVICE_ID:
> + pmbus_send_string(pmdev, s->ic_device_id);
> + break;
> +
> + case MAX16600_PHASE_ID:
> + pmbus_send8(pmdev, s->phase_id);
> + break;
> +
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: reading from unsupported register: 0x%02x\n",
> + __func__, pmdev->code);
> + break;
> + }
> + return 0xFF;
> +}
> +
> +static int max16600_write_data(PMBusDevice *pmdev, const uint8_t *buf,
> + uint8_t len)
> +{
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: write to unsupported register: 0x%02x\n", __func__,
> + pmdev->code);
> + return 0xFF;
> +}
> +
> +static void max16600_exit_reset(Object *obj, ResetType type)
> +{
> + PMBusDevice *pmdev = PMBUS_DEVICE(obj);
> + MAX16600State *s = MAX16600(obj);
> +
> + pmdev->capability = MAX16600_CAPABILITY_DEFAULT;
> + pmdev->page = 0;
> +
> + pmdev->pages[0].operation = MAX16600_OPERATION_DEFAULT;
> + pmdev->pages[0].on_off_config = MAX16600_ON_OFF_CONFIG_DEFAULT;
> + pmdev->pages[0].vout_mode = MAX16600_VOUT_MODE_DEFAULT;
> +
> + pmdev->pages[0].read_vin =
> + pmbus_data2linear_mode(MAX16600_READ_VIN_DEFAULT, max16600_exp.vin);
> + pmdev->pages[0].read_iin =
> + pmbus_data2linear_mode(MAX16600_READ_IIN_DEFAULT, max16600_exp.iin);
> + pmdev->pages[0].read_pin =
> + pmbus_data2linear_mode(MAX16600_READ_PIN_DEFAULT, max16600_exp.pin);
> + pmdev->pages[0].read_vout = MAX16600_READ_VOUT_DEFAULT;
> + pmdev->pages[0].read_iout =
> + pmbus_data2linear_mode(MAX16600_READ_IOUT_DEFAULT, max16600_exp.iout);
> + pmdev->pages[0].read_pout =
> + pmbus_data2linear_mode(MAX16600_READ_PIN_DEFAULT, max16600_exp.pout);
> + pmdev->pages[0].read_temperature_1 =
> + pmbus_data2linear_mode(MAX16600_READ_TEMP_DEFAULT, max16600_exp.temp);
> +
> + s->ic_device_id = "MAX16601";
> + s->phase_id = MAX16600_PHASE_ID_DEFAULT;
> +}
> +
> +static void max16600_get(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + uint16_t value;
> +
> + if (strcmp(name, "vin") == 0) {
> + value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.vin);
> + } else if (strcmp(name, "iin") == 0) {
> + value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.iin);
> + } else if (strcmp(name, "pin") == 0) {
> + value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.pin);
> + } else if (strcmp(name, "iout") == 0) {
> + value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.iout);
> + } else if (strcmp(name, "pout") == 0) {
> + value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.pout);
> + } else if (strcmp(name, "temperature") == 0) {
> + value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.temp);
> + } else {
> + value = *(uint16_t *)opaque;
> + }
> +
> + /* scale to milli-units */
> + if (strcmp(name, "pout") != 0 && strcmp(name, "pin") != 0) {
> + value *= 1000;
> + }
> +
> + visit_type_uint16(v, name, &value, errp);
> +}
> +
> +static void max16600_set(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + PMBusDevice *pmdev = PMBUS_DEVICE(obj);
> + uint16_t *internal = opaque;
> + uint16_t value;
> + if (!visit_type_uint16(v, name, &value, errp)) {
> + return;
> + }
> +
> + /* inputs match kernel driver which scales to milliunits except power */
> + if (strcmp(name, "pout") != 0 && strcmp(name, "pin") != 0) {
> + value /= 1000;
> + }
> +
> + if (strcmp(name, "vin") == 0) {
> + *internal = pmbus_data2linear_mode(value, max16600_exp.vin);
> + } else if (strcmp(name, "iin") == 0) {
> + *internal = pmbus_data2linear_mode(value, max16600_exp.iin);
> + } else if (strcmp(name, "pin") == 0) {
> + *internal = pmbus_data2linear_mode(value, max16600_exp.pin);
> + } else if (strcmp(name, "iout") == 0) {
> + *internal = pmbus_data2linear_mode(value, max16600_exp.iout);
> + } else if (strcmp(name, "pout") == 0) {
> + *internal = pmbus_data2linear_mode(value, max16600_exp.pout);
> + } else if (strcmp(name, "temperature") == 0) {
> + *internal = pmbus_data2linear_mode(value, max16600_exp.temp);
> + } else {
> + *internal = value;
> + }
> +
> + pmbus_check_limits(pmdev);
> +}
> +
> +static void max16600_init(Object *obj)
> +{
> + PMBusDevice *pmdev = PMBUS_DEVICE(obj);
> + uint64_t flags = PB_HAS_VOUT_MODE | PB_HAS_VIN | PB_HAS_IIN | PB_HAS_PIN |
> + PB_HAS_IOUT | PB_HAS_POUT | PB_HAS_VOUT |
> + PB_HAS_TEMPERATURE | PB_HAS_MFR_INFO;
> + pmbus_page_config(pmdev, 0, flags);
> +
> + object_property_add(obj, "vin", "uint16", max16600_get, max16600_set, NULL,
> + &pmdev->pages[0].read_vin);
> +
> + object_property_add(obj, "iin", "uint16", max16600_get, max16600_set, NULL,
> + &pmdev->pages[0].read_iin);
> +
> + object_property_add(obj, "pin", "uint16", max16600_get, max16600_set, NULL,
> + &pmdev->pages[0].read_pin);
> +
> + object_property_add(obj, "vout", "uint16", max16600_get, max16600_set,
> + NULL, &pmdev->pages[0].read_vout);
> +
> + object_property_add(obj, "iout", "uint16", max16600_get, max16600_set,
> + NULL, &pmdev->pages[0].read_iout);
> +
> + object_property_add(obj, "pout", "uint16", max16600_get, max16600_set,
> + NULL, &pmdev->pages[0].read_pout);
> +
> + object_property_add(obj, "temperature", "uint16",
> + max16600_get, max16600_set,
> + NULL, &pmdev->pages[0].read_temperature_1);
> +}
> +
> +static void max16600_class_init(ObjectClass *klass, const void *data)
> +{
> + ResettableClass *rc = RESETTABLE_CLASS(klass);
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
> +
> + dc->desc = "MAX16600 Dual-Output Voltage Regulator";
> + k->write_data = max16600_write_data;
> + k->receive_byte = max16600_read_byte;
> + k->device_num_pages = 1;
> +
> + rc->phases.exit = max16600_exit_reset;
> +}
> +
> +static const TypeInfo max16600_info = {
> + .name = TYPE_MAX16600,
> + .parent = TYPE_PMBUS_DEVICE,
> + .instance_size = sizeof(MAX16600State),
> + .instance_init = max16600_init,
> + .class_init = max16600_class_init,
> +};
> +
> +static void max16600_register_types(void)
> +{
> + type_register_static(&max16600_info);
> +}
> +
> +type_init(max16600_register_types)
> diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
> index 420fdc3359..85c2c73c99 100644
> --- a/hw/sensor/meson.build
> +++ b/hw/sensor/meson.build
> @@ -8,3 +8,4 @@ system_ss.add(when: 'CONFIG_MAX34451', if_true: files('max34451.c'))
> system_ss.add(when: 'CONFIG_LSM303DLHC_MAG', if_true: files('lsm303dlhc_mag.c'))
> system_ss.add(when: 'CONFIG_ISL_PMBUS_VR', if_true: files('isl_pmbus_vr.c'))
> system_ss.add(when: 'CONFIG_MAX31785', if_true: files('max31785.c'))
> +system_ss.add(when: 'CONFIG_MAX_16600', if_true: files('max16600.c'))
> diff --git a/include/hw/sensor/max16600.h b/include/hw/sensor/max16600.h
> new file mode 100644
> index 0000000000..a8cd0a5d4b
> --- /dev/null
> +++ b/include/hw/sensor/max16600.h
> @@ -0,0 +1,46 @@
> +/*
> + * MAX16600 VR13.HC Dual-Output Voltage Regulator Chipset
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright 2025 Google LLC
> + */
> +
> +#include "hw/i2c/pmbus_device.h"
> +
> +#define TYPE_MAX16600 "max16600"
> +#define MAX16600(obj) OBJECT_CHECK(MAX16600State, (obj), TYPE_MAX16600)
> +
> +#define MAX16600_PHASE_ID 0xF3
> +/*
> + * Packet error checking capability is disabled.
> + * Pending QEMU support
> + */
> +#define MAX16600_CAPABILITY_DEFAULT 0x30
> +#define MAX16600_OPERATION_DEFAULT 0x88
> +#define MAX16600_ON_OFF_CONFIG_DEFAULT 0x17
> +#define MAX16600_VOUT_MODE_DEFAULT 0x22
> +#define MAX16600_PHASE_ID_DEFAULT 0x80
> +
> +#define MAX16600_READ_VIN_DEFAULT 5 /* Volts */
> +#define MAX16600_READ_IIN_DEFAULT 3 /* Amps */
> +#define MAX16600_READ_PIN_DEFAULT 100 /* Watts */
> +#define MAX16600_READ_VOUT_DEFAULT 5 /* Volts */
> +#define MAX16600_READ_IOUT_DEFAULT 3 /* Amps */
> +#define MAX16600_READ_POUT_DEFAULT 100 /* Watts */
> +#define MAX16600_READ_TEMP_DEFAULT 40 /* Celsius */
> +
> +typedef struct MAX16600State {
> + PMBusDevice parent;
> + const char *ic_device_id;
> + uint8_t phase_id;
> +} MAX16600State;
> +
> +/*
> + * determines the exponents used in linear conversion for CORE
> + * (iin, pin) may be (-4, 0) or (-3, 1)
> + * iout may be -2, -1, 0, 1
> + */
> +static const struct {
> + int vin, iin, pin, iout, pout, temp;
> +} max16600_exp = {-6, -4, 0, -2, -1, 0};
> diff --git a/tests/qtest/max16600-test.c b/tests/qtest/max16600-test.c
> new file mode 100644
> index 0000000000..bad5da7989
> --- /dev/null
> +++ b/tests/qtest/max16600-test.c
> @@ -0,0 +1,241 @@
> +/*
> + * QTest for the MAX16600 VR13.HC Dual-Output Voltage Regulator Chipset
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright 2025 Google LLC
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/i2c/pmbus_device.h"
> +#include "hw/sensor/max16600.h"
> +#include "libqtest-single.h"
> +#include "libqos/qgraph.h"
> +#include "libqos/i2c.h"
> +#include "qobject/qdict.h"
> +#include "qobject/qnum.h"
> +#include "qemu/bitops.h"
> +
> +#define TEST_ID "max16600-test"
> +#define TEST_ADDR (0x61)
> +
> +uint16_t pmbus_linear_mode2data(uint16_t value, int exp)
> +{
> + /* D = L * 2^e */
> + if (exp < 0) {
> + return value >> (-exp);
> + }
> + return value << exp;
> +}
> +
> +static uint16_t qmp_max16600_get(const char *id, const char *property)
> +{
> + QDict *response;
> + uint64_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_max16600_set(const char *id, const char *property,
> + uint16_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"));
> + qobject_unref(response);
> +}
> +
> +static uint16_t max16600_i2c_get16(QI2CDevice *i2cdev, uint8_t reg)
> +{
> + uint8_t resp[2];
> + i2c_read_block(i2cdev, reg, resp, sizeof(resp));
> + return (resp[1] << 8) | resp[0];
> +}
> +
> +static void max16600_i2c_set16(QI2CDevice *i2cdev, uint8_t reg, uint16_t value)
> +{
> + uint8_t data[2];
> +
> + data[0] = value & 255;
> + data[1] = value >> 8;
> + i2c_write_block(i2cdev, reg, data, sizeof(data));
> +}
> +
> +/* test default values */
> +static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + uint16_t i2c_value, value;
> + QI2CDevice *i2cdev = (QI2CDevice *)obj;
> +
> + i2c_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
> + g_assert_cmphex(i2c_value, ==, MAX16600_CAPABILITY_DEFAULT);
> +
> + i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
> + g_assert_cmphex(i2c_value, ==, MAX16600_OPERATION_DEFAULT);
> +
> + i2c_value = i2c_get8(i2cdev, PMBUS_ON_OFF_CONFIG);
> + g_assert_cmphex(i2c_value, ==, MAX16600_ON_OFF_CONFIG_DEFAULT);
> +
> + i2c_value = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
> + g_assert_cmphex(i2c_value, ==, MAX16600_VOUT_MODE_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "vin") / 1000;
> + g_assert_cmpuint(value, ==, MAX16600_READ_VIN_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "iin") / 1000;
> + g_assert_cmpuint(value, ==, MAX16600_READ_IIN_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "pin");
> + g_assert_cmpuint(value, ==, MAX16600_READ_PIN_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "vout") / 1000;
> + g_assert_cmpuint(value, ==, MAX16600_READ_VOUT_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "iout") / 1000;
> + g_assert_cmpuint(value, ==, MAX16600_READ_IOUT_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "pout");
> + g_assert_cmpuint(value, ==, MAX16600_READ_POUT_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "temperature") / 1000;
> + g_assert_cmpuint(value, ==, MAX16600_READ_TEMP_DEFAULT);
> +}
> +
> +/* test qmp access */
> +static void test_tx_rx(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + uint16_t value, i2c_value;
> + QI2CDevice *i2cdev = (QI2CDevice *)obj;
> +
> + qmp_max16600_set(TEST_ID, "vin", 2000);
> + value = qmp_max16600_get(TEST_ID, "vin") / 1000;
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VIN);
> + i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.vin);
> + g_assert_cmpuint(value, ==, i2c_value);
> +
> + qmp_max16600_set(TEST_ID, "iin", 3000);
> + value = qmp_max16600_get(TEST_ID, "iin") / 1000;
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IIN);
> + i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.iin);
> + g_assert_cmpuint(value, ==, i2c_value);
> +
> + qmp_max16600_set(TEST_ID, "pin", 4);
> + value = qmp_max16600_get(TEST_ID, "pin");
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_PIN);
> + i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.pin);
> + g_assert_cmpuint(value, ==, i2c_value);
> +
> + qmp_max16600_set(TEST_ID, "vout", 5000);
> + value = qmp_max16600_get(TEST_ID, "vout") / 1000;
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VOUT);
> + g_assert_cmpuint(value, ==, i2c_value);
> +
> + qmp_max16600_set(TEST_ID, "iout", 6000);
> + value = qmp_max16600_get(TEST_ID, "iout") / 1000;
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IOUT);
> + i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.iout);
> + g_assert_cmpuint(value, ==, i2c_value);
> +
> + qmp_max16600_set(TEST_ID, "pout", 7);
> + value = qmp_max16600_get(TEST_ID, "pout");
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_POUT);
> + i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.pout);
> + g_assert_cmpuint(value, ==, i2c_value);
> +
> + qmp_max16600_set(TEST_ID, "temperature", 8000);
> + value = qmp_max16600_get(TEST_ID, "temperature") / 1000;
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
> + i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.temp);
> + g_assert_cmpuint(value, ==, i2c_value);
> +}
> +
> +/* test r/w registers */
> +static void test_rw_regs(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + uint16_t i2c_value;
> + QI2CDevice *i2cdev = (QI2CDevice *)obj;
> +
> + max16600_i2c_set16(i2cdev, PMBUS_OPERATION, 0xA);
> + i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
> + g_assert_cmphex(i2c_value, ==, 0xA);
> +
> + max16600_i2c_set16(i2cdev, PMBUS_ON_OFF_CONFIG, 0xB);
> + i2c_value = i2c_get8(i2cdev, PMBUS_ON_OFF_CONFIG);
> + g_assert_cmphex(i2c_value, ==, 0xB);
> +
> + max16600_i2c_set16(i2cdev, PMBUS_VOUT_MODE, 0xC);
> + i2c_value = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
> + g_assert_cmphex(i2c_value, ==, 0xC);
> +}
> +
> +/* test read-only registers */
> +static void test_ro_regs(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + uint16_t i2c_init_value, i2c_value;
> + QI2CDevice *i2cdev = (QI2CDevice *)obj;
> +
> + i2c_init_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
> + max16600_i2c_set16(i2cdev, PMBUS_CAPABILITY, 0xD);
> + i2c_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VIN);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_VIN, 0x1234);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VIN);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IIN);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_IIN, 0x2234);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IIN);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_PIN);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_PIN, 0x3234);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_PIN);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VOUT);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_VOUT, 0x4234);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VOUT);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IOUT);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_IOUT, 0x5235);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IOUT);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_POUT);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_POUT, 0x6234);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_POUT);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_TEMPERATURE_1, 0x7236);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +}
> +
> +static void max16600_register_nodes(void)
> +{
> + QOSGraphEdgeOptions opts = {.extra_device_opts =
> + "id=" TEST_ID ",address=0x61"};
> + add_qi2c_address(&opts, &(QI2CAddress){TEST_ADDR});
> +
> + qos_node_create_driver("max16600", i2c_device_create);
> + qos_node_consumes("max16600", "i2c-bus", &opts);
> +
> + qos_add_test("test_defaults", "max16600", test_defaults, NULL);
> + qos_add_test("test_tx_rx", "max16600", test_tx_rx, NULL);
> + qos_add_test("test_rw_regs", "max16600", test_rw_regs, NULL);
> + qos_add_test("test_ro_regs", "max16600", test_ro_regs, NULL);
> +}
> +libqos_init(max16600_register_nodes);
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 669d07c06b..459cf41985 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -295,6 +295,7 @@ qos_test_ss.add(
> 'es1370-test.c',
> 'lsm303dlhc-mag-test.c',
> 'isl_pmbus_vr-test.c',
> + 'max16600-test.c',
> 'max34451-test.c',
> 'megasas-test.c',
> 'ne2000-test.c',
For qtest:
Acked-by: Fabiano Rosas <farosas@suse.de>
Friendly ping on the review!
Thanks,
Nabih
On Wed, Sep 3, 2025 at 8:54 AM Nabih Estefan <nabihestefan@google.com> wrote:
>
> From: Shengtan Mao <stmao@google.com>
>
> Signed-off-by: Shengtan Mao <stmao@google.com>
> Signed-off-by: Titus Rwantare <titusr@google.com>
> Signed-off-by: Nabih Estefan <nabihestefan@google.com>
> ---
> hw/arm/Kconfig | 1 +
> hw/sensor/Kconfig | 4 +
> hw/sensor/max16600.c | 197 ++++++++++++++++++++++++++++
> hw/sensor/meson.build | 1 +
> include/hw/sensor/max16600.h | 46 +++++++
> tests/qtest/max16600-test.c | 241 +++++++++++++++++++++++++++++++++++
> tests/qtest/meson.build | 1 +
> 7 files changed, 491 insertions(+)
> create mode 100644 hw/sensor/max16600.c
> create mode 100644 include/hw/sensor/max16600.h
> create mode 100644 tests/qtest/max16600-test.c
>
> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index 2aa4b5d778..4ab0a93ba6 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -480,6 +480,7 @@ config NPCM7XX
> select AT24C # EEPROM
> select MAX34451
> select ISL_PMBUS_VR
> + select MAX_16600
> select PL310 # cache controller
> select PMBUS
> select SERIAL_MM
> diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
> index bc6331b4ab..ef7b3262a8 100644
> --- a/hw/sensor/Kconfig
> +++ b/hw/sensor/Kconfig
> @@ -43,3 +43,7 @@ config ISL_PMBUS_VR
> config MAX31785
> bool
> depends on PMBUS
> +
> +config MAX_16600
> + bool
> + depends on I2C
> diff --git a/hw/sensor/max16600.c b/hw/sensor/max16600.c
> new file mode 100644
> index 0000000000..1941391dab
> --- /dev/null
> +++ b/hw/sensor/max16600.c
> @@ -0,0 +1,197 @@
> +/*
> + * MAX16600 VR13.HC Dual-Output Voltage Regulator Chipset
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright 2025 Google LLC
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/i2c/pmbus_device.h"
> +#include "qapi/visitor.h"
> +#include "qemu/log.h"
> +#include "hw/sensor/max16600.h"
> +
> +static uint8_t max16600_read_byte(PMBusDevice *pmdev)
> +{
> + MAX16600State *s = MAX16600(pmdev);
> +
> + switch (pmdev->code) {
> + case PMBUS_IC_DEVICE_ID:
> + pmbus_send_string(pmdev, s->ic_device_id);
> + break;
> +
> + case MAX16600_PHASE_ID:
> + pmbus_send8(pmdev, s->phase_id);
> + break;
> +
> + default:
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: reading from unsupported register: 0x%02x\n",
> + __func__, pmdev->code);
> + break;
> + }
> + return 0xFF;
> +}
> +
> +static int max16600_write_data(PMBusDevice *pmdev, const uint8_t *buf,
> + uint8_t len)
> +{
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: write to unsupported register: 0x%02x\n", __func__,
> + pmdev->code);
> + return 0xFF;
> +}
> +
> +static void max16600_exit_reset(Object *obj, ResetType type)
> +{
> + PMBusDevice *pmdev = PMBUS_DEVICE(obj);
> + MAX16600State *s = MAX16600(obj);
> +
> + pmdev->capability = MAX16600_CAPABILITY_DEFAULT;
> + pmdev->page = 0;
> +
> + pmdev->pages[0].operation = MAX16600_OPERATION_DEFAULT;
> + pmdev->pages[0].on_off_config = MAX16600_ON_OFF_CONFIG_DEFAULT;
> + pmdev->pages[0].vout_mode = MAX16600_VOUT_MODE_DEFAULT;
> +
> + pmdev->pages[0].read_vin =
> + pmbus_data2linear_mode(MAX16600_READ_VIN_DEFAULT, max16600_exp.vin);
> + pmdev->pages[0].read_iin =
> + pmbus_data2linear_mode(MAX16600_READ_IIN_DEFAULT, max16600_exp.iin);
> + pmdev->pages[0].read_pin =
> + pmbus_data2linear_mode(MAX16600_READ_PIN_DEFAULT, max16600_exp.pin);
> + pmdev->pages[0].read_vout = MAX16600_READ_VOUT_DEFAULT;
> + pmdev->pages[0].read_iout =
> + pmbus_data2linear_mode(MAX16600_READ_IOUT_DEFAULT, max16600_exp.iout);
> + pmdev->pages[0].read_pout =
> + pmbus_data2linear_mode(MAX16600_READ_PIN_DEFAULT, max16600_exp.pout);
> + pmdev->pages[0].read_temperature_1 =
> + pmbus_data2linear_mode(MAX16600_READ_TEMP_DEFAULT, max16600_exp.temp);
> +
> + s->ic_device_id = "MAX16601";
> + s->phase_id = MAX16600_PHASE_ID_DEFAULT;
> +}
> +
> +static void max16600_get(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + uint16_t value;
> +
> + if (strcmp(name, "vin") == 0) {
> + value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.vin);
> + } else if (strcmp(name, "iin") == 0) {
> + value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.iin);
> + } else if (strcmp(name, "pin") == 0) {
> + value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.pin);
> + } else if (strcmp(name, "iout") == 0) {
> + value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.iout);
> + } else if (strcmp(name, "pout") == 0) {
> + value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.pout);
> + } else if (strcmp(name, "temperature") == 0) {
> + value = pmbus_linear_mode2data(*(uint16_t *)opaque, max16600_exp.temp);
> + } else {
> + value = *(uint16_t *)opaque;
> + }
> +
> + /* scale to milli-units */
> + if (strcmp(name, "pout") != 0 && strcmp(name, "pin") != 0) {
> + value *= 1000;
> + }
> +
> + visit_type_uint16(v, name, &value, errp);
> +}
> +
> +static void max16600_set(Object *obj, Visitor *v, const char *name,
> + void *opaque, Error **errp)
> +{
> + PMBusDevice *pmdev = PMBUS_DEVICE(obj);
> + uint16_t *internal = opaque;
> + uint16_t value;
> + if (!visit_type_uint16(v, name, &value, errp)) {
> + return;
> + }
> +
> + /* inputs match kernel driver which scales to milliunits except power */
> + if (strcmp(name, "pout") != 0 && strcmp(name, "pin") != 0) {
> + value /= 1000;
> + }
> +
> + if (strcmp(name, "vin") == 0) {
> + *internal = pmbus_data2linear_mode(value, max16600_exp.vin);
> + } else if (strcmp(name, "iin") == 0) {
> + *internal = pmbus_data2linear_mode(value, max16600_exp.iin);
> + } else if (strcmp(name, "pin") == 0) {
> + *internal = pmbus_data2linear_mode(value, max16600_exp.pin);
> + } else if (strcmp(name, "iout") == 0) {
> + *internal = pmbus_data2linear_mode(value, max16600_exp.iout);
> + } else if (strcmp(name, "pout") == 0) {
> + *internal = pmbus_data2linear_mode(value, max16600_exp.pout);
> + } else if (strcmp(name, "temperature") == 0) {
> + *internal = pmbus_data2linear_mode(value, max16600_exp.temp);
> + } else {
> + *internal = value;
> + }
> +
> + pmbus_check_limits(pmdev);
> +}
> +
> +static void max16600_init(Object *obj)
> +{
> + PMBusDevice *pmdev = PMBUS_DEVICE(obj);
> + uint64_t flags = PB_HAS_VOUT_MODE | PB_HAS_VIN | PB_HAS_IIN | PB_HAS_PIN |
> + PB_HAS_IOUT | PB_HAS_POUT | PB_HAS_VOUT |
> + PB_HAS_TEMPERATURE | PB_HAS_MFR_INFO;
> + pmbus_page_config(pmdev, 0, flags);
> +
> + object_property_add(obj, "vin", "uint16", max16600_get, max16600_set, NULL,
> + &pmdev->pages[0].read_vin);
> +
> + object_property_add(obj, "iin", "uint16", max16600_get, max16600_set, NULL,
> + &pmdev->pages[0].read_iin);
> +
> + object_property_add(obj, "pin", "uint16", max16600_get, max16600_set, NULL,
> + &pmdev->pages[0].read_pin);
> +
> + object_property_add(obj, "vout", "uint16", max16600_get, max16600_set,
> + NULL, &pmdev->pages[0].read_vout);
> +
> + object_property_add(obj, "iout", "uint16", max16600_get, max16600_set,
> + NULL, &pmdev->pages[0].read_iout);
> +
> + object_property_add(obj, "pout", "uint16", max16600_get, max16600_set,
> + NULL, &pmdev->pages[0].read_pout);
> +
> + object_property_add(obj, "temperature", "uint16",
> + max16600_get, max16600_set,
> + NULL, &pmdev->pages[0].read_temperature_1);
> +}
> +
> +static void max16600_class_init(ObjectClass *klass, const void *data)
> +{
> + ResettableClass *rc = RESETTABLE_CLASS(klass);
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
> +
> + dc->desc = "MAX16600 Dual-Output Voltage Regulator";
> + k->write_data = max16600_write_data;
> + k->receive_byte = max16600_read_byte;
> + k->device_num_pages = 1;
> +
> + rc->phases.exit = max16600_exit_reset;
> +}
> +
> +static const TypeInfo max16600_info = {
> + .name = TYPE_MAX16600,
> + .parent = TYPE_PMBUS_DEVICE,
> + .instance_size = sizeof(MAX16600State),
> + .instance_init = max16600_init,
> + .class_init = max16600_class_init,
> +};
> +
> +static void max16600_register_types(void)
> +{
> + type_register_static(&max16600_info);
> +}
> +
> +type_init(max16600_register_types)
> diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
> index 420fdc3359..85c2c73c99 100644
> --- a/hw/sensor/meson.build
> +++ b/hw/sensor/meson.build
> @@ -8,3 +8,4 @@ system_ss.add(when: 'CONFIG_MAX34451', if_true: files('max34451.c'))
> system_ss.add(when: 'CONFIG_LSM303DLHC_MAG', if_true: files('lsm303dlhc_mag.c'))
> system_ss.add(when: 'CONFIG_ISL_PMBUS_VR', if_true: files('isl_pmbus_vr.c'))
> system_ss.add(when: 'CONFIG_MAX31785', if_true: files('max31785.c'))
> +system_ss.add(when: 'CONFIG_MAX_16600', if_true: files('max16600.c'))
> diff --git a/include/hw/sensor/max16600.h b/include/hw/sensor/max16600.h
> new file mode 100644
> index 0000000000..a8cd0a5d4b
> --- /dev/null
> +++ b/include/hw/sensor/max16600.h
> @@ -0,0 +1,46 @@
> +/*
> + * MAX16600 VR13.HC Dual-Output Voltage Regulator Chipset
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright 2025 Google LLC
> + */
> +
> +#include "hw/i2c/pmbus_device.h"
> +
> +#define TYPE_MAX16600 "max16600"
> +#define MAX16600(obj) OBJECT_CHECK(MAX16600State, (obj), TYPE_MAX16600)
> +
> +#define MAX16600_PHASE_ID 0xF3
> +/*
> + * Packet error checking capability is disabled.
> + * Pending QEMU support
> + */
> +#define MAX16600_CAPABILITY_DEFAULT 0x30
> +#define MAX16600_OPERATION_DEFAULT 0x88
> +#define MAX16600_ON_OFF_CONFIG_DEFAULT 0x17
> +#define MAX16600_VOUT_MODE_DEFAULT 0x22
> +#define MAX16600_PHASE_ID_DEFAULT 0x80
> +
> +#define MAX16600_READ_VIN_DEFAULT 5 /* Volts */
> +#define MAX16600_READ_IIN_DEFAULT 3 /* Amps */
> +#define MAX16600_READ_PIN_DEFAULT 100 /* Watts */
> +#define MAX16600_READ_VOUT_DEFAULT 5 /* Volts */
> +#define MAX16600_READ_IOUT_DEFAULT 3 /* Amps */
> +#define MAX16600_READ_POUT_DEFAULT 100 /* Watts */
> +#define MAX16600_READ_TEMP_DEFAULT 40 /* Celsius */
> +
> +typedef struct MAX16600State {
> + PMBusDevice parent;
> + const char *ic_device_id;
> + uint8_t phase_id;
> +} MAX16600State;
> +
> +/*
> + * determines the exponents used in linear conversion for CORE
> + * (iin, pin) may be (-4, 0) or (-3, 1)
> + * iout may be -2, -1, 0, 1
> + */
> +static const struct {
> + int vin, iin, pin, iout, pout, temp;
> +} max16600_exp = {-6, -4, 0, -2, -1, 0};
> diff --git a/tests/qtest/max16600-test.c b/tests/qtest/max16600-test.c
> new file mode 100644
> index 0000000000..bad5da7989
> --- /dev/null
> +++ b/tests/qtest/max16600-test.c
> @@ -0,0 +1,241 @@
> +/*
> + * QTest for the MAX16600 VR13.HC Dual-Output Voltage Regulator Chipset
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright 2025 Google LLC
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/i2c/pmbus_device.h"
> +#include "hw/sensor/max16600.h"
> +#include "libqtest-single.h"
> +#include "libqos/qgraph.h"
> +#include "libqos/i2c.h"
> +#include "qobject/qdict.h"
> +#include "qobject/qnum.h"
> +#include "qemu/bitops.h"
> +
> +#define TEST_ID "max16600-test"
> +#define TEST_ADDR (0x61)
> +
> +uint16_t pmbus_linear_mode2data(uint16_t value, int exp)
> +{
> + /* D = L * 2^e */
> + if (exp < 0) {
> + return value >> (-exp);
> + }
> + return value << exp;
> +}
> +
> +static uint16_t qmp_max16600_get(const char *id, const char *property)
> +{
> + QDict *response;
> + uint64_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_max16600_set(const char *id, const char *property,
> + uint16_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"));
> + qobject_unref(response);
> +}
> +
> +static uint16_t max16600_i2c_get16(QI2CDevice *i2cdev, uint8_t reg)
> +{
> + uint8_t resp[2];
> + i2c_read_block(i2cdev, reg, resp, sizeof(resp));
> + return (resp[1] << 8) | resp[0];
> +}
> +
> +static void max16600_i2c_set16(QI2CDevice *i2cdev, uint8_t reg, uint16_t value)
> +{
> + uint8_t data[2];
> +
> + data[0] = value & 255;
> + data[1] = value >> 8;
> + i2c_write_block(i2cdev, reg, data, sizeof(data));
> +}
> +
> +/* test default values */
> +static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + uint16_t i2c_value, value;
> + QI2CDevice *i2cdev = (QI2CDevice *)obj;
> +
> + i2c_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
> + g_assert_cmphex(i2c_value, ==, MAX16600_CAPABILITY_DEFAULT);
> +
> + i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
> + g_assert_cmphex(i2c_value, ==, MAX16600_OPERATION_DEFAULT);
> +
> + i2c_value = i2c_get8(i2cdev, PMBUS_ON_OFF_CONFIG);
> + g_assert_cmphex(i2c_value, ==, MAX16600_ON_OFF_CONFIG_DEFAULT);
> +
> + i2c_value = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
> + g_assert_cmphex(i2c_value, ==, MAX16600_VOUT_MODE_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "vin") / 1000;
> + g_assert_cmpuint(value, ==, MAX16600_READ_VIN_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "iin") / 1000;
> + g_assert_cmpuint(value, ==, MAX16600_READ_IIN_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "pin");
> + g_assert_cmpuint(value, ==, MAX16600_READ_PIN_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "vout") / 1000;
> + g_assert_cmpuint(value, ==, MAX16600_READ_VOUT_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "iout") / 1000;
> + g_assert_cmpuint(value, ==, MAX16600_READ_IOUT_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "pout");
> + g_assert_cmpuint(value, ==, MAX16600_READ_POUT_DEFAULT);
> +
> + value = qmp_max16600_get(TEST_ID, "temperature") / 1000;
> + g_assert_cmpuint(value, ==, MAX16600_READ_TEMP_DEFAULT);
> +}
> +
> +/* test qmp access */
> +static void test_tx_rx(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + uint16_t value, i2c_value;
> + QI2CDevice *i2cdev = (QI2CDevice *)obj;
> +
> + qmp_max16600_set(TEST_ID, "vin", 2000);
> + value = qmp_max16600_get(TEST_ID, "vin") / 1000;
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VIN);
> + i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.vin);
> + g_assert_cmpuint(value, ==, i2c_value);
> +
> + qmp_max16600_set(TEST_ID, "iin", 3000);
> + value = qmp_max16600_get(TEST_ID, "iin") / 1000;
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IIN);
> + i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.iin);
> + g_assert_cmpuint(value, ==, i2c_value);
> +
> + qmp_max16600_set(TEST_ID, "pin", 4);
> + value = qmp_max16600_get(TEST_ID, "pin");
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_PIN);
> + i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.pin);
> + g_assert_cmpuint(value, ==, i2c_value);
> +
> + qmp_max16600_set(TEST_ID, "vout", 5000);
> + value = qmp_max16600_get(TEST_ID, "vout") / 1000;
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VOUT);
> + g_assert_cmpuint(value, ==, i2c_value);
> +
> + qmp_max16600_set(TEST_ID, "iout", 6000);
> + value = qmp_max16600_get(TEST_ID, "iout") / 1000;
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IOUT);
> + i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.iout);
> + g_assert_cmpuint(value, ==, i2c_value);
> +
> + qmp_max16600_set(TEST_ID, "pout", 7);
> + value = qmp_max16600_get(TEST_ID, "pout");
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_POUT);
> + i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.pout);
> + g_assert_cmpuint(value, ==, i2c_value);
> +
> + qmp_max16600_set(TEST_ID, "temperature", 8000);
> + value = qmp_max16600_get(TEST_ID, "temperature") / 1000;
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
> + i2c_value = pmbus_linear_mode2data(i2c_value, max16600_exp.temp);
> + g_assert_cmpuint(value, ==, i2c_value);
> +}
> +
> +/* test r/w registers */
> +static void test_rw_regs(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + uint16_t i2c_value;
> + QI2CDevice *i2cdev = (QI2CDevice *)obj;
> +
> + max16600_i2c_set16(i2cdev, PMBUS_OPERATION, 0xA);
> + i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
> + g_assert_cmphex(i2c_value, ==, 0xA);
> +
> + max16600_i2c_set16(i2cdev, PMBUS_ON_OFF_CONFIG, 0xB);
> + i2c_value = i2c_get8(i2cdev, PMBUS_ON_OFF_CONFIG);
> + g_assert_cmphex(i2c_value, ==, 0xB);
> +
> + max16600_i2c_set16(i2cdev, PMBUS_VOUT_MODE, 0xC);
> + i2c_value = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
> + g_assert_cmphex(i2c_value, ==, 0xC);
> +}
> +
> +/* test read-only registers */
> +static void test_ro_regs(void *obj, void *data, QGuestAllocator *alloc)
> +{
> + uint16_t i2c_init_value, i2c_value;
> + QI2CDevice *i2cdev = (QI2CDevice *)obj;
> +
> + i2c_init_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
> + max16600_i2c_set16(i2cdev, PMBUS_CAPABILITY, 0xD);
> + i2c_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VIN);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_VIN, 0x1234);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VIN);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IIN);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_IIN, 0x2234);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IIN);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_PIN);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_PIN, 0x3234);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_PIN);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VOUT);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_VOUT, 0x4234);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_VOUT);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IOUT);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_IOUT, 0x5235);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_IOUT);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_POUT);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_POUT, 0x6234);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_POUT);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +
> + i2c_init_value = max16600_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
> + max16600_i2c_set16(i2cdev, PMBUS_READ_TEMPERATURE_1, 0x7236);
> + i2c_value = max16600_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
> + g_assert_cmphex(i2c_init_value, ==, i2c_value);
> +}
> +
> +static void max16600_register_nodes(void)
> +{
> + QOSGraphEdgeOptions opts = {.extra_device_opts =
> + "id=" TEST_ID ",address=0x61"};
> + add_qi2c_address(&opts, &(QI2CAddress){TEST_ADDR});
> +
> + qos_node_create_driver("max16600", i2c_device_create);
> + qos_node_consumes("max16600", "i2c-bus", &opts);
> +
> + qos_add_test("test_defaults", "max16600", test_defaults, NULL);
> + qos_add_test("test_tx_rx", "max16600", test_tx_rx, NULL);
> + qos_add_test("test_rw_regs", "max16600", test_rw_regs, NULL);
> + qos_add_test("test_ro_regs", "max16600", test_ro_regs, NULL);
> +}
> +libqos_init(max16600_register_nodes);
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 669d07c06b..459cf41985 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -295,6 +295,7 @@ qos_test_ss.add(
> 'es1370-test.c',
> 'lsm303dlhc-mag-test.c',
> 'isl_pmbus_vr-test.c',
> + 'max16600-test.c',
> 'max34451-test.c',
> 'megasas-test.c',
> 'ne2000-test.c',
> --
> 2.51.0.355.g5224444f11-goog
>
© 2016 - 2026 Red Hat, Inc.