Reviewed-by: Glenn Miles <[email protected]> Thanks,
Glenn On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development wrote: > Introduce the INA238 qtest: the qgraph node, the QMP injection helpers > and the register map. Cover the reset defaults, the software reset, and > the Configuration register write mask. > > Signed-off-by: Emmanuel Blot <[email protected]> > --- > tests/qtest/ina238-test.c | 177 > ++++++++++++++++++++++++++++++++++++++++++++++ > tests/qtest/meson.build | 1 + > 2 files changed, 178 insertions(+) > > diff --git a/tests/qtest/ina238-test.c b/tests/qtest/ina238-test.c > new file mode 100644 > index 0000000000..6e59ed30b5 > --- /dev/null > +++ b/tests/qtest/ina238-test.c > @@ -0,0 +1,177 @@ > +/* > + * QTest testcase for the INA238 current/voltage/power monitor > + * > + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates. > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > + > +#include "qemu/osdep.h" > +#include "libqos/i2c.h" > +#include "libqos/qgraph.h" > +#include "libqtest-single.h" > +#include "qobject/qdict.h" > + > +#define INA238_TEST_ID "ina238-test" > +#define INA238_TEST_ADDR 0x40 > + > +/* Register pointer addresses */ > +#define REG_CONFIG 0x00 > +#define REG_ADC_CONFIG 0x01 > +#define REG_SHUNT_CAL 0x02 > +#define REG_VSHUNT 0x04 > +#define REG_VBUS 0x05 > +#define REG_DIETEMP 0x06 > +#define REG_CURRENT 0x07 > +#define REG_POWER 0x08 > +#define REG_DIAG_ALRT 0x0B > +#define REG_SOVL 0x0C > +#define REG_SUVL 0x0D > +#define REG_BOVL 0x0E > +#define REG_BUVL 0x0F > +#define REG_TEMP_LIMIT 0x10 > +#define REG_PWR_LIMIT 0x11 > +#define REG_MANUFACTURER_ID 0x3E > +#define REG_DEVICE_ID 0x3F > + > +/* Configuration register bits */ > +#define CONFIG_RST 0x8000 > +#define CONFIG_ADCRANGE 0x0010 > + > +/* DIAG_ALRT register bits */ > +#define DIAG_MEMSTAT 0x0001 > +#define DIAG_CNVRF 0x0002 > +#define DIAG_POL 0x0004 > +#define DIAG_BUSUL 0x0008 > +#define DIAG_BUSOL 0x0010 > +#define DIAG_SHNTUL 0x0020 > +#define DIAG_SHNTOL 0x0040 > +#define DIAG_TMPOL 0x0080 > +#define DIAG_MATHOF 0x0200 > +#define DIAG_APOL 0x1000 > +#define DIAG_SLOWALERT 0x2000 > +#define DIAG_CNVR 0x4000 > +#define DIAG_ALATCH 0x8000 > + > +/* Reset values */ > +#define CONFIG_POR 0x0000 > +#define ADC_CONFIG_POR 0xFB68 > +#define SHUNT_CAL_POR 0x1000 > +#define DIAG_ALRT_POR 0x0001 > +#define SOVL_POR 0x7FFF > +#define SUVL_POR 0x8000 > +#define BOVL_POR 0x7FFF > +#define BUVL_POR 0x0000 > +#define TEMP_LIMIT_POR 0x7FF0 > +#define PWR_LIMIT_POR 0xFFFF > +#define MANUFACTURER_ID_VAL 0x5449 > +#define DEVICE_ID_VAL 0x2381 > + > +/* Read the 24-bit POWER register (MSB first) */ > +static uint32_t i2c_get24(QI2CDevice *dev, uint8_t reg) > +{ > + uint8_t resp[3]; > + > + i2c_read_block(dev, reg, resp, sizeof(resp)); > + return (resp[0] << 16) | (resp[1] << 8) | resp[2]; > +} > + > +/* QMP helpers for injecting the physical inputs */ > + > +static void qmp_ina238_set(const char *property, int value) > +{ > + QDict *resp; > + > + resp = qmp("{ 'execute': 'qom-set', 'arguments':" > + " { 'path': %s, 'property': %s, 'value': %d } }", > + INA238_TEST_ID, property, value); > + g_assert(qdict_haskey(resp, "return")); > + qobject_unref(resp); > +} > + > +/* Power-on-reset default values and the ID registers */ > +static void test_defaults(void *obj, void *data, QGuestAllocator *alloc) > +{ > + QI2CDevice *dev = (QI2CDevice *)obj; > + > + g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR); > + g_assert_cmphex(i2c_get16(dev, REG_ADC_CONFIG), ==, ADC_CONFIG_POR); > + g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, SHUNT_CAL_POR); > + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0000); > + g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000); > + g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0000); > + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000); > + g_assert_cmphex(i2c_get24(dev, REG_POWER), ==, 0x000000); > + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT), ==, DIAG_ALRT_POR); > + g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, SOVL_POR); > + g_assert_cmphex(i2c_get16(dev, REG_SUVL), ==, SUVL_POR); > + g_assert_cmphex(i2c_get16(dev, REG_BOVL), ==, BOVL_POR); > + g_assert_cmphex(i2c_get16(dev, REG_BUVL), ==, BUVL_POR); > + g_assert_cmphex(i2c_get16(dev, REG_TEMP_LIMIT), ==, TEMP_LIMIT_POR); > + g_assert_cmphex(i2c_get16(dev, REG_PWR_LIMIT), ==, PWR_LIMIT_POR); > + g_assert_cmphex(i2c_get16(dev, REG_MANUFACTURER_ID), ==, > + MANUFACTURER_ID_VAL); > + g_assert_cmphex(i2c_get16(dev, REG_DEVICE_ID), ==, DEVICE_ID_VAL); > +} > + > +/* Software reset via the Configuration RST bit */ > +static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc) > +{ > + QI2CDevice *dev = (QI2CDevice *)obj; > + > + i2c_set16(dev, REG_SHUNT_CAL, 0x1234); > + i2c_set16(dev, REG_SOVL, 0x0500); > + i2c_set16(dev, REG_TEMP_LIMIT, 0x0A00); > + i2c_set16(dev, REG_DIAG_ALRT, DIAG_ALATCH); > + > + qmp_ina238_set("shunt-voltage", 97200000); > + qmp_ina238_set("bus-voltage", 48000000); > + qmp_ina238_set("die-temperature", 25000); > + > + g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, 0x1234); > + g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, 0x0500); > + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x4BF0); > + g_assert_cmpuint(i2c_get16(dev, REG_CURRENT), !=, 0); > + > + i2c_set16(dev, REG_CONFIG, CONFIG_RST); > + > + g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR); > + g_assert_cmphex(i2c_get16(dev, REG_SHUNT_CAL), ==, SHUNT_CAL_POR); > + g_assert_cmphex(i2c_get16(dev, REG_SOVL), ==, SOVL_POR); > + g_assert_cmphex(i2c_get16(dev, REG_TEMP_LIMIT), ==, TEMP_LIMIT_POR); > + g_assert_cmphex(i2c_get16(dev, REG_DIAG_ALRT), ==, DIAG_ALRT_POR); > + > + g_assert_cmphex(i2c_get16(dev, REG_VSHUNT), ==, 0x0000); > + g_assert_cmphex(i2c_get16(dev, REG_VBUS), ==, 0x0000); > + g_assert_cmphex(i2c_get16(dev, REG_DIETEMP), ==, 0x0000); > + g_assert_cmphex(i2c_get16(dev, REG_CURRENT), ==, 0x0000); > + g_assert_cmpuint(i2c_get24(dev, REG_POWER), ==, 0); > +} > + > +/* CONFIG keeps only the writable ADCRANGE/CONVDLY bits and self-clears RST > */ > +static void test_config_wmask(void *obj, void *data, QGuestAllocator *alloc) > +{ > + QI2CDevice *dev = (QI2CDevice *)obj; > + > + i2c_set16(dev, REG_CONFIG, CONFIG_ADCRANGE | 0x0040); > + g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_ADCRANGE | > 0x0040); > + > + i2c_set16(dev, REG_CONFIG, CONFIG_RST); > + g_assert_cmphex(i2c_get16(dev, REG_CONFIG), ==, CONFIG_POR); > +} > + > +static void ina238_register_nodes(void) > +{ > + QOSGraphEdgeOptions opts = { > + .extra_device_opts = "id=" INA238_TEST_ID ",address=0x40" > + }; > + add_qi2c_address(&opts, &(QI2CAddress) { INA238_TEST_ADDR }); > + > + qos_node_create_driver("ina238", i2c_device_create); > + qos_node_consumes("ina238", "i2c-bus", &opts); > + > + qos_add_test("defaults", "ina238", test_defaults, NULL); > + qos_add_test("soft-reset", "ina238", test_soft_reset, NULL); > + qos_add_test("config-wmask", "ina238", test_config_wmask, NULL); > +} > +libqos_init(ina238_register_nodes); > diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build > index e413a0cc75..7005509d4d 100644 > --- a/tests/qtest/meson.build > +++ b/tests/qtest/meson.build > @@ -331,6 +331,7 @@ qos_test_ss.add( > 'eepro100-test.c', > 'es1370-test.c', > 'ina230-test.c', > + 'ina238-test.c', > 'lsm303dlhc-mag-test.c', > 'isl_pmbus_vr-test.c', > 'max34451-test.c', >
