* [PATCH v5 0/8] hw/sensor: Add new device emulation for TI ADC128D818
@ 2026-07-01 17:28 Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 1/8] hw/sensor: adc128d818: add 12-bit 8-channel ADC device Emmanuel Blot
` (7 more replies)
0 siblings, 8 replies; 13+ messages in thread
From: Emmanuel Blot @ 2026-07-01 17:28 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Philippe Mathieu-Daudé, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Cédric Le Goater, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Alexander Hansen, William de Abreu Pinho, Emmanuel Blot, qemu-arm,
Emmanuel Blot, Corey Minyard, Cédric Le Goater
This is a 12-bit, 8-channel ADC I2C device, which is used on several
OpenBMC platforms.
QOM properties can be used to change input ADC values and external VREF at
run time.
Add it to AST2600-based Anacapa machine, along with some functional tests
that use QOM properties to inject voltage values into analog inputs.
Datasheet: https://www.ti.com/lit/gpn/ADC128D818
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
Changes in v5:
- Change author/committer/signed-off-by address to circumvent SMTP issues
- Link to v4: https://lore.kernel.org/qemu-devel/20260701-i2c-adc128d818-anacapa-v4-0-89d6a2febb26@meta.com
Changes in v4:
- Better follow QEMU coding style (lowercase hex number, signed integer
constants, parent object variable name in Device)
- Split adc128d818 test large commit into smaller ones
- Link to v3: https://lore.kernel.org/qemu-devel/20260627-i2c-adc128d818-anacapa-v3-0-cfb94270a980@meta.com
Changes in v3:
- Update anacapa test image with proper DTB
- Make I2C device in i2c_slave_create_simple a child of the I2C bus
- Simplify tests/functional/arm/test_aspeed_anacapa.py
- Use g_test_message rather than fprintf
- Keep review trailers
- Link to v2: https://lore.kernel.org/qemu-devel/20260617-i2c-adc128d818-anacapa-v2-0-fd38764f0465@meta.com
Changes in v2:
- Remove clang-format useless comment sections
- Link to v1: https://lore.kernel.org/qemu-devel/20260612-i2c-adc128d818-anacapa-v1-0-9e4640cd164b@meta.com
To: qemu-devel@nongnu.org
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Philippe Mathieu-Daudé <philmd@mailo.com>
Cc: Fabiano Rosas <farosas@suse.de>
Cc: Laurent Vivier <lvivier@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Cédric Le Goater <clg@kaod.org>
Cc: Steven Lee <steven_lee@aspeedtech.com>
Cc: Troy Lee <leetroy@gmail.com>
Cc: Jamin Lin <jamin_lin@aspeedtech.com>
Cc: Kane Chen <kane_chen@aspeedtech.com>
Cc: Andrew Jeffery <andrew@codeconstruct.com.au>
Cc: Joel Stanley <joel@jms.id.au>
Cc: qemu-arm@nongnu.org
Cc: Corey Minyard <cminyard@mvista.com>
---
Emmanuel Blot (8):
hw/sensor: adc128d818: add 12-bit 8-channel ADC device
tests/qtest: adc128d818: add test harness and register access
tests/qtest: adc128d818: test voltage and temperature conversion
tests/qtest: adc128d818: test limit interrupts
tests/qtest: adc128d818: test operating modes and power control
hw/i2c: parent slaves created with i2c_slave_create_simple
hw/arm: anacapa: add ADC128D818 devices
test/functional: anacapa: test ADC128D818
hw/arm/Kconfig | 1 +
hw/arm/aspeed_ast2600_anacapa.c | 21 +-
hw/i2c/core.c | 3 +
hw/sensor/Kconfig | 4 +
hw/sensor/adc128d818.c | 696 ++++++++++++++++++++++
hw/sensor/meson.build | 1 +
hw/sensor/trace-events | 8 +
include/hw/i2c/i2c.h | 7 +-
include/hw/sensor/adc128d818.h | 14 +
tests/functional/arm/test_aspeed_anacapa.py | 69 ++-
tests/qtest/adc128d818-test.c | 856 ++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
12 files changed, 1669 insertions(+), 12 deletions(-)
---
base-commit: c682d7d73adfddbbcfb2613942d7c96fb43534af
change-id: 20260612-i2c-adc128d818-anacapa-13c63e97fbd0
Best regards,
--
Emmanuel Blot <emmanuel.blot@free.fr>
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v5 1/8] hw/sensor: adc128d818: add 12-bit 8-channel ADC device
2026-07-01 17:28 [PATCH v5 0/8] hw/sensor: Add new device emulation for TI ADC128D818 Emmanuel Blot
@ 2026-07-01 17:28 ` Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 2/8] tests/qtest: adc128d818: add test harness and register access Emmanuel Blot
` (6 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Emmanuel Blot @ 2026-07-01 17:28 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Philippe Mathieu-Daudé, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Cédric Le Goater, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Alexander Hansen, William de Abreu Pinho, Emmanuel Blot, qemu-arm,
Emmanuel Blot, Corey Minyard
The ADC128D818 is a TI 12-bit, 8-channel I2C ADC used on several
OpenBMC platforms for voltage and temperature monitoring.
Implement the device with:
- four operating modes
- 12-bit voltage conversion from QOM inputs
- 9-bit temperature conversion from milli-degree Celsius QOM inputs
- switchable internal or external voltage reference
- per-channel high/low limit registers
- interrupt support
- software reset
- one-shot conversion support in shutdown mode
Reviewed-by: Alexander Hansen <alexander.hansen@9elements.com>
Tested-by: Alexander Hansen <alexander.hansen@9elements.com>
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/sensor/Kconfig | 4 +
hw/sensor/adc128d818.c | 696 +++++++++++++++++++++++++++++++++++++++++
hw/sensor/meson.build | 1 +
hw/sensor/trace-events | 8 +
include/hw/sensor/adc128d818.h | 14 +
5 files changed, 723 insertions(+)
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index bc6331b4ab..b459ac2240 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -1,3 +1,7 @@
+config ADC128D818
+ bool
+ depends on I2C
+
config TMP105
bool
depends on I2C
diff --git a/hw/sensor/adc128d818.c b/hw/sensor/adc128d818.c
new file mode 100644
index 0000000000..c65508cba1
--- /dev/null
+++ b/hw/sensor/adc128d818.c
@@ -0,0 +1,696 @@
+/*
+ * Texas Instruments ADC128D818 12-bit 8-channel ADC with I2C interface
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qom/object.h"
+#include "hw/sensor/adc128d818.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+
+/* Register addresses */
+#define REG_CONFIG 0x00
+#define REG_INT_STATUS 0x01
+#define REG_INT_MASK 0x03
+#define REG_CONV_RATE 0x07
+#define REG_CH_DISABLE 0x08
+#define REG_ONE_SHOT 0x09
+#define REG_DEEP_SHUTDOWN 0x0a
+#define REG_ADV_CONFIG 0x0b
+#define REG_BUSY_STATUS 0x0c
+
+/* Channel Reading Registers (16-bit, read-only) */
+#define REG_CH_READING_BASE 0x20
+#define REG_CH_READING_LAST 0x27
+
+/* Limit Registers (8-bit, read/write) */
+#define REG_LIMIT_BASE 0x2a
+#define REG_LIMIT_LAST 0x39
+
+/* ID Registers (read-only) */
+#define REG_MANUFACTURER_ID 0x3e
+#define REG_REVISION_ID 0x3f
+
+/* Configuration Register (0x00) bitfields */
+#define CONFIG_START BIT(0)
+#define CONFIG_INT_ENABLE BIT(1)
+#define CONFIG_INT_CLEAR BIT(3)
+#define CONFIG_INITIALIZATION BIT(7)
+#define CONFIG_WR_MASK \
+ (CONFIG_START | CONFIG_INT_ENABLE | CONFIG_INT_CLEAR)
+
+/* Advanced Configuration Register (0x0B) bitfields */
+#define ADV_CONFIG_EXT_REF_EN BIT(0)
+#define ADV_CONFIG_MODE_SHIFT 1
+#define ADV_CONFIG_MODE_MASK (0x3 << ADV_CONFIG_MODE_SHIFT)
+#define ADV_CONFIG_WR_MASK \
+ (ADV_CONFIG_EXT_REF_EN | ADV_CONFIG_MODE_MASK)
+
+/* Busy Status Register (0x0C) bitfields */
+#define BUSY_STATUS_NOT_READY BIT(1)
+
+/* Conversion Rate Register (0x07) bitfields */
+#define CONV_RATE_MASK 0x01
+
+/* Deep Shutdown Register (0x0A) bitfields */
+#define DEEP_SHUTDOWN_EN 0x01
+
+/* Device constants */
+#define ADC128D818_NUM_CHANNELS 8
+#define ADC128D818_NUM_REGS 0x40
+
+#define ADC128D818_INTERNAL_VREF_MV 2560
+#define ADC128D818_MAX_VDD_MV 5500
+#define ADC128D818_MANUFACTURER_ID_VAL 0x01
+#define ADC128D818_REVISION_ID_VAL 0x09
+
+/* ADC resolution */
+#define ADC128D818_ADC_RESOLUTION 4096
+#define ADC128D818_ADC_MAX 4095
+
+/* Temperature: 0.5 deg C per LSb = 500 milli-degrees per LSb */
+#define ADC128D818_TEMP_LSB_MC 500
+#define ADC128D818_TEMP_RAW_MIN (-256)
+#define ADC128D818_TEMP_RAW_MAX 255
+
+
+OBJECT_DECLARE_SIMPLE_TYPE(ADC128D818State, ADC128D818)
+
+struct ADC128D818State {
+ I2CSlave parent_obj;
+
+ qemu_irq irq;
+
+ uint8_t len;
+ uint8_t pointer;
+ uint8_t rx_byte;
+
+ uint8_t regs[ADC128D818_NUM_REGS];
+ uint16_t channel[ADC128D818_NUM_CHANNELS];
+
+ int16_t ain[ADC128D818_NUM_CHANNELS]; /* mV */
+ int32_t temperature; /* milli-degrees Celsius */
+ uint16_t ext_vref; /* mV, 0 means not connected */
+ bool temp_alarm; /* temperature high-limit alarm latched */
+
+ char *description;
+};
+
+static uint16_t adc128d818_get_vref(const ADC128D818State *s)
+{
+ if (s->regs[REG_ADV_CONFIG] & ADV_CONFIG_EXT_REF_EN) {
+ if (s->ext_vref > 0u) {
+ return s->ext_vref;
+ }
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: external VREF selected but not"
+ " connected, falling back to internal\n",
+ __func__, s->description);
+ }
+
+ return ADC128D818_INTERNAL_VREF_MV;
+}
+
+static uint8_t adc128d818_get_mode(const ADC128D818State *s)
+{
+ return (s->regs[REG_ADV_CONFIG] & ADV_CONFIG_MODE_MASK) >>
+ ADV_CONFIG_MODE_SHIFT;
+}
+
+static bool adc128d818_is_temp_channel(const ADC128D818State *s, unsigned ch)
+{
+ if (ch != 7u) {
+ return false;
+ }
+
+ return adc128d818_get_mode(s) != 1u;
+}
+
+static bool adc128d818_is_reserved_channel(const ADC128D818State *s,
+ unsigned ch)
+{
+ switch (adc128d818_get_mode(s)) {
+ case 2u:
+ return ch >= 4u && ch <= 6u;
+ case 3u:
+ return ch == 6u;
+ default:
+ return false;
+ }
+}
+
+static int16_t adc128d818_channel_voltage(const ADC128D818State *s, unsigned ch)
+{
+ switch (adc128d818_get_mode(s)) {
+ case 2u:
+ switch (ch) {
+ case 0u:
+ return (int16_t)(s->ain[0] - s->ain[1]);
+ case 1u:
+ return (int16_t)(s->ain[3] - s->ain[2]);
+ case 2u:
+ return (int16_t)(s->ain[4] - s->ain[5]);
+ case 3u:
+ return (int16_t)(s->ain[7] - s->ain[6]);
+ default:
+ return 0;
+ }
+ case 3u:
+ switch (ch) {
+ case 4u:
+ return (int16_t)(s->ain[4] - s->ain[5]);
+ case 5u:
+ return (int16_t)(s->ain[7] - s->ain[6]);
+ default:
+ return s->ain[ch];
+ }
+ default:
+ return s->ain[ch];
+ }
+}
+
+static void adc128d818_update_irq(ADC128D818State *s)
+{
+ uint8_t cfg = s->regs[REG_CONFIG];
+ uint8_t active;
+ bool level;
+
+ active = s->regs[REG_INT_STATUS] & ~s->regs[REG_INT_MASK];
+
+ /* INT pin is active-low */
+ level = !((cfg & CONFIG_INT_ENABLE) && !(cfg & CONFIG_INT_CLEAR) &&
+ (active != 0u));
+
+ trace_adc128d818_irq(s->description, level);
+ qemu_set_irq(s->irq, level);
+}
+
+static bool adc128d818_monitoring_active(const ADC128D818State *s)
+{
+ if (s->regs[REG_DEEP_SHUTDOWN] & DEEP_SHUTDOWN_EN) {
+ return false;
+ }
+ if (!(s->regs[REG_CONFIG] & CONFIG_START)) {
+ return false;
+ }
+ if (s->regs[REG_CONFIG] & CONFIG_INT_CLEAR) {
+ return false;
+ }
+
+ return true;
+}
+
+static void adc128d818_check_limits(ADC128D818State *s)
+{
+ uint8_t disabled = s->regs[REG_CH_DISABLE];
+ uint8_t int_status = 0u;
+
+ for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) {
+ if ((disabled & (1u << ch)) ||
+ adc128d818_is_reserved_channel(s, ch)) {
+ continue;
+ }
+
+ if (adc128d818_is_temp_channel(s, ch)) {
+ int raw = s->temperature / ADC128D818_TEMP_LSB_MC;
+ int thot;
+ int thyst;
+
+ raw = MAX(ADC128D818_TEMP_RAW_MIN,
+ MIN(ADC128D818_TEMP_RAW_MAX, raw));
+ thot = (int)(int8_t)s->regs[REG_LIMIT_BASE + ch * 2u] * 2;
+ thyst = (int)(int8_t)s->regs[REG_LIMIT_BASE + ch * 2u + 1u] * 2;
+
+ if (raw > thot) {
+ s->temp_alarm = true;
+ } else if (raw <= thyst) {
+ s->temp_alarm = false;
+ }
+ if (s->temp_alarm) {
+ int_status |= (1u << ch);
+ }
+ } else {
+ uint8_t msb = (uint8_t)(s->channel[ch] >> 8u);
+ uint8_t high_lim = s->regs[REG_LIMIT_BASE + ch * 2u];
+ uint8_t low_lim = s->regs[REG_LIMIT_BASE + ch * 2u + 1u];
+
+ if (msb > high_lim || msb <= low_lim) {
+ int_status |= (1u << ch);
+ }
+ }
+ }
+
+ s->regs[REG_INT_STATUS] = int_status;
+ adc128d818_update_irq(s);
+}
+
+static void adc128d818_convert(ADC128D818State *s)
+{
+ uint8_t disabled;
+ uint16_t vref;
+
+ disabled = s->regs[REG_CH_DISABLE];
+ vref = adc128d818_get_vref(s);
+
+ for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) {
+ if ((disabled & (1u << ch)) ||
+ adc128d818_is_reserved_channel(s, ch)) {
+ continue;
+ }
+
+ if (adc128d818_is_temp_channel(s, ch)) {
+ int32_t raw = s->temperature / ADC128D818_TEMP_LSB_MC;
+
+ raw =
+ MAX(ADC128D818_TEMP_RAW_MIN, MIN(ADC128D818_TEMP_RAW_MAX, raw));
+ s->channel[ch] = (uint16_t)(((unsigned)raw & 0x1FFu) << 7u);
+ } else {
+ int16_t vin = adc128d818_channel_voltage(s, ch);
+ int32_t dout;
+
+ dout = vin * (int32_t)ADC128D818_ADC_RESOLUTION / vref;
+ dout = MAX(0, MIN((int32_t)ADC128D818_ADC_MAX, dout));
+ s->channel[ch] = (uint16_t)(dout << 4u);
+ }
+
+ trace_adc128d818_convert(s->description, ch, s->channel[ch]);
+ }
+
+ s->regs[REG_BUSY_STATUS] &= ~BUSY_STATUS_NOT_READY;
+
+ adc128d818_check_limits(s);
+}
+
+static uint8_t adc128d818_read_channel(ADC128D818State *s, unsigned ch)
+{
+ uint8_t val;
+
+ if (s->rx_byte == 0u) {
+ val = (uint8_t)(s->channel[ch] >> 8u);
+ trace_adc128d818_read_channel(s->description, ch, s->channel[ch]);
+ } else {
+ val = (uint8_t)(s->channel[ch] & 0xFFu);
+ }
+ s->rx_byte ^= 1u;
+
+ return val;
+}
+
+static uint8_t adc128d818_read_reg(ADC128D818State *s, uint8_t reg)
+{
+ uint8_t val;
+
+ switch (reg) {
+ case REG_INT_STATUS:
+ val = s->regs[REG_INT_STATUS];
+ s->regs[REG_INT_STATUS] = 0x00u;
+ if (adc128d818_monitoring_active(s)) {
+ adc128d818_check_limits(s);
+ } else {
+ adc128d818_update_irq(s);
+ }
+ trace_adc128d818_read(s->description, reg, val);
+ return val;
+ case REG_CONFIG:
+ case REG_INT_MASK:
+ case REG_CONV_RATE:
+ case REG_CH_DISABLE:
+ case REG_ONE_SHOT:
+ case REG_DEEP_SHUTDOWN:
+ case REG_ADV_CONFIG:
+ case REG_BUSY_STATUS:
+ case REG_LIMIT_BASE ... REG_LIMIT_LAST:
+ case REG_MANUFACTURER_ID:
+ case REG_REVISION_ID:
+ trace_adc128d818_read(s->description, reg, s->regs[reg]);
+ return s->regs[reg];
+ case REG_CH_READING_BASE ... REG_CH_READING_LAST:
+ return adc128d818_read_channel(s, reg - REG_CH_READING_BASE);
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: read from undefined register 0x%02x\n",
+ __func__, s->description, reg);
+ return 0x00u;
+ }
+}
+
+static void adc128d818_write_reg(ADC128D818State *s, uint8_t reg, uint8_t val);
+
+static void adc128d818_reset_regs(ADC128D818State *s)
+{
+ memset(s->regs, 0, sizeof(s->regs));
+ memset(s->channel, 0, sizeof(s->channel));
+ s->temp_alarm = false;
+
+ s->regs[REG_CONFIG] = 0x08u;
+ s->regs[REG_BUSY_STATUS] = 0x02u;
+ s->regs[REG_MANUFACTURER_ID] = ADC128D818_MANUFACTURER_ID_VAL;
+ s->regs[REG_REVISION_ID] = ADC128D818_REVISION_ID_VAL;
+
+ for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) {
+ s->regs[REG_LIMIT_BASE + ch * 2u] = 0xFFu;
+ }
+
+ s->pointer = 0x00u;
+ s->len = 0u;
+ s->rx_byte = 0u;
+
+ adc128d818_update_irq(s);
+}
+
+static void adc128d818_write_reg(ADC128D818State *s, uint8_t reg, uint8_t val)
+{
+ trace_adc128d818_write(s->description, reg, val);
+
+ switch (reg) {
+ case REG_CONFIG:
+ if (val & CONFIG_INITIALIZATION) {
+ trace_adc128d818_reset(s->description, "reg");
+ adc128d818_reset_regs(s);
+ break;
+ }
+ s->regs[REG_CONFIG] = val & CONFIG_WR_MASK;
+ if ((val & CONFIG_START) && !(val & CONFIG_INT_CLEAR) &&
+ !(s->regs[REG_DEEP_SHUTDOWN] & DEEP_SHUTDOWN_EN)) {
+ adc128d818_convert(s);
+ }
+ adc128d818_update_irq(s);
+ break;
+ case REG_INT_MASK:
+ s->regs[REG_INT_MASK] = val;
+ adc128d818_update_irq(s);
+ break;
+ case REG_CONV_RATE:
+ if (s->regs[REG_CONFIG] & CONFIG_START) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: CONV_RATE written while running\n",
+ __func__, s->description);
+ break;
+ }
+ s->regs[REG_CONV_RATE] = val & CONV_RATE_MASK;
+ break;
+ case REG_CH_DISABLE:
+ if (s->regs[REG_CONFIG] & CONFIG_START) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: CH_DISABLE written while running\n",
+ __func__, s->description);
+ break;
+ }
+ s->regs[REG_CH_DISABLE] = val;
+ memset(s->channel, 0, sizeof(s->channel));
+ s->regs[REG_INT_STATUS] = 0x00u;
+ s->temp_alarm = false;
+ adc128d818_update_irq(s);
+ break;
+ case REG_ONE_SHOT:
+ if (!(s->regs[REG_CONFIG] & CONFIG_START)) {
+ adc128d818_convert(s);
+ }
+ break;
+ case REG_DEEP_SHUTDOWN:
+ if ((val & DEEP_SHUTDOWN_EN) && (s->regs[REG_CONFIG] & CONFIG_START)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: DEEP_SHUTDOWN set while running\n",
+ __func__, s->description);
+ break;
+ }
+ s->regs[REG_DEEP_SHUTDOWN] = val & DEEP_SHUTDOWN_EN;
+ break;
+ case REG_ADV_CONFIG:
+ if (s->regs[REG_CONFIG] & CONFIG_START) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: ADV_CONFIG written while running\n",
+ __func__, s->description);
+ break;
+ }
+ s->regs[REG_ADV_CONFIG] = val & ADV_CONFIG_WR_MASK;
+ memset(s->channel, 0, sizeof(s->channel));
+ s->regs[REG_INT_STATUS] = 0x00u;
+ s->temp_alarm = false;
+ adc128d818_update_irq(s);
+ break;
+ case REG_LIMIT_BASE ... REG_LIMIT_LAST:
+ s->regs[reg] = val;
+ break;
+ case REG_INT_STATUS:
+ case REG_BUSY_STATUS:
+ case REG_MANUFACTURER_ID:
+ case REG_REVISION_ID:
+ case REG_CH_READING_BASE ... REG_CH_READING_LAST:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: write to read-only register 0x%02x\n",
+ __func__, s->description, reg);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: %s: write to undefined register 0x%02x\n",
+ __func__, s->description, reg);
+ break;
+ }
+}
+
+static uint8_t adc128d818_recv(I2CSlave *i2c)
+{
+ ADC128D818State *s = ADC128D818(i2c);
+
+ return adc128d818_read_reg(s, s->pointer);
+}
+
+static int adc128d818_send(I2CSlave *i2c, uint8_t data)
+{
+ ADC128D818State *s = ADC128D818(i2c);
+
+ if (s->len == 0u) {
+ s->pointer = data;
+ s->len++;
+ } else {
+ adc128d818_write_reg(s, s->pointer, data);
+ }
+
+ return 0;
+}
+
+static int adc128d818_event(I2CSlave *i2c, enum i2c_event event)
+{
+ ADC128D818State *s = ADC128D818(i2c);
+
+ s->len = 0u;
+ s->rx_byte = 0u;
+
+ return 0;
+}
+
+static void adc128d818_get_ain(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ ADC128D818State *s = ADC128D818(obj);
+ int64_t value;
+ int ch_num;
+ int rc;
+
+ rc = sscanf(name, "ain%d", &ch_num);
+ if (rc != 1 || ch_num < 0 || ch_num >= (int)ADC128D818_NUM_CHANNELS) {
+ error_setg(errp, "%s: %s: invalid channel '%s'", __func__,
+ s->description, name);
+ return;
+ }
+
+ value = s->ain[ch_num];
+ visit_type_int(v, name, &value, errp);
+}
+
+static void adc128d818_set_ain(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ ADC128D818State *s = ADC128D818(obj);
+ int64_t value;
+ int ch_num;
+ int rc;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ rc = sscanf(name, "ain%d", &ch_num);
+ if (rc != 1 || ch_num < 0 || ch_num >= (int)ADC128D818_NUM_CHANNELS) {
+ error_setg(errp, "%s: %s: invalid channel '%s'", __func__,
+ s->description, name);
+ return;
+ }
+
+ if (value < INT16_MIN || value > INT16_MAX) {
+ error_setg(errp, "%s: %s: value %" PRId64 " out of range for '%s'",
+ __func__, s->description, value, name);
+ return;
+ }
+
+ s->ain[ch_num] = (int16_t)value;
+
+ if (adc128d818_monitoring_active(s)) {
+ adc128d818_convert(s);
+ }
+}
+
+static void adc128d818_get_temperature(
+ Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
+{
+ ADC128D818State *s = ADC128D818(obj);
+ int64_t value = s->temperature;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void adc128d818_set_temperature(
+ Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
+{
+ ADC128D818State *s = ADC128D818(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < INT32_MIN || value > INT32_MAX) {
+ error_setg(errp, "%s: %s: value %" PRId64 " out of range", __func__,
+ s->description, value);
+ return;
+ }
+
+ s->temperature = (int32_t)value;
+
+ if (adc128d818_monitoring_active(s)) {
+ adc128d818_convert(s);
+ }
+}
+
+static const VMStateDescription adc128d818_vmstate = {
+ .name = "ADC128D818",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(len, ADC128D818State),
+ VMSTATE_UINT8(pointer, ADC128D818State),
+ VMSTATE_UINT8(rx_byte, ADC128D818State),
+ VMSTATE_UINT8_ARRAY(regs, ADC128D818State,
+ ADC128D818_NUM_REGS),
+ VMSTATE_UINT16_ARRAY(channel, ADC128D818State,
+ ADC128D818_NUM_CHANNELS),
+ VMSTATE_INT16_ARRAY(ain, ADC128D818State,
+ ADC128D818_NUM_CHANNELS),
+ VMSTATE_INT32(temperature, ADC128D818State),
+ VMSTATE_UINT16(ext_vref, ADC128D818State),
+ VMSTATE_BOOL(temp_alarm, ADC128D818State),
+ VMSTATE_I2C_SLAVE(parent_obj, ADC128D818State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void adc128d818_reset_hold(Object *obj, ResetType type)
+{
+ ADC128D818State *s = ADC128D818(obj);
+
+ trace_adc128d818_reset(s->description, "hw");
+ adc128d818_reset_regs(s);
+}
+
+static void adc128d818_get_ext_vref(
+ Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
+{
+ ADC128D818State *s = ADC128D818(obj);
+ int64_t value = (int64_t)s->ext_vref;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void adc128d818_set_ext_vref(
+ Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
+{
+ ADC128D818State *s = ADC128D818(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ if (value < 0 || value > ADC128D818_MAX_VDD_MV) {
+ error_setg(errp,
+ "%s: %s: ext-vref-mv %" PRId64 " out of range (0..%u mV)",
+ __func__, s->description, value, ADC128D818_MAX_VDD_MV);
+ return;
+ }
+
+ s->ext_vref = (uint16_t)value;
+
+ if (adc128d818_monitoring_active(s)) {
+ adc128d818_convert(s);
+ }
+}
+
+static void adc128d818_initfn(Object *obj)
+{
+ for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) {
+ char *name = g_strdup_printf("ain%u", ch);
+
+ object_property_add(obj, name, "int", adc128d818_get_ain,
+ adc128d818_set_ain, NULL, NULL);
+ g_free(name);
+ }
+
+ object_property_add(obj, "temperature", "int", adc128d818_get_temperature,
+ adc128d818_set_temperature, NULL, NULL);
+ object_property_add(obj, "ext-vref-mv", "int", adc128d818_get_ext_vref,
+ adc128d818_set_ext_vref, NULL, NULL);
+}
+
+static void adc128d818_realize(DeviceState *dev, Error **errp)
+{
+ ADC128D818State *s = ADC128D818(dev);
+
+ if (!s->description) {
+ s->description = g_strdup(object_get_typename(OBJECT(dev)));
+ }
+
+ qdev_init_gpio_out(dev, &s->irq, 1u);
+}
+
+static const Property adc128d818_properties[] = {
+ DEFINE_PROP_STRING("description", ADC128D818State, description),
+};
+
+static void adc128d818_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *ic = I2C_SLAVE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ ic->event = adc128d818_event;
+ ic->recv = adc128d818_recv;
+ ic->send = adc128d818_send;
+ dc->realize = adc128d818_realize;
+ rc->phases.hold = adc128d818_reset_hold;
+ dc->vmsd = &adc128d818_vmstate;
+ device_class_set_props(dc, adc128d818_properties);
+}
+
+static const TypeInfo adc128d818_types[] = {
+ {
+ .name = TYPE_ADC128D818,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_init = adc128d818_initfn,
+ .instance_size = sizeof(ADC128D818State),
+ .class_init = adc128d818_class_init,
+ },
+};
+
+DEFINE_TYPES(adc128d818_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index 420fdc3359..fe36c9ef91 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -1,3 +1,4 @@
+system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
system_ss.add(when: 'CONFIG_TMP105', if_true: files('tmp105.c'))
system_ss.add(when: 'CONFIG_TMP421', if_true: files('tmp421.c'))
system_ss.add(when: 'CONFIG_DPS310', if_true: files('dps310.c'))
diff --git a/hw/sensor/trace-events b/hw/sensor/trace-events
index a3fe54fa6d..5a3630f7bb 100644
--- a/hw/sensor/trace-events
+++ b/hw/sensor/trace-events
@@ -1,5 +1,13 @@
# See docs/devel/tracing.rst for syntax documentation.
+# adc128d818.c
+adc128d818_read(const char *id, uint8_t reg, uint8_t value) "%s reg 0x%02x val 0x%02x"
+adc128d818_read_channel(const char *id, uint8_t channel, uint16_t value) "%s ch %u val 0x%04x"
+adc128d818_write(const char *id, uint8_t reg, uint8_t value) "%s reg 0x%02x val 0x%02x"
+adc128d818_convert(const char *id, uint8_t channel, uint16_t value) "%s ch %u val 0x%04x"
+adc128d818_irq(const char *id, bool level) "%s level %u"
+adc128d818_reset(const char *id, const char *source) "%s %s"
+
# tmp105.c
tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x"
tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
diff --git a/include/hw/sensor/adc128d818.h b/include/hw/sensor/adc128d818.h
new file mode 100644
index 0000000000..10c34b9646
--- /dev/null
+++ b/include/hw/sensor/adc128d818.h
@@ -0,0 +1,14 @@
+/*
+ * Texas Instruments ADC128D818 12-bit 8-channel ADC with I2C interface
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SENSOR_ADC128D818_H
+#define HW_SENSOR_ADC128D818_H
+
+#define TYPE_ADC128D818 "adc128d818"
+
+#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v5 2/8] tests/qtest: adc128d818: add test harness and register access
2026-07-01 17:28 [PATCH v5 0/8] hw/sensor: Add new device emulation for TI ADC128D818 Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 1/8] hw/sensor: adc128d818: add 12-bit 8-channel ADC device Emmanuel Blot
@ 2026-07-01 17:28 ` Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 3/8] tests/qtest: adc128d818: test voltage and temperature conversion Emmanuel Blot
` (5 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Emmanuel Blot @ 2026-07-01 17:28 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Philippe Mathieu-Daudé, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Cédric Le Goater, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Alexander Hansen, William de Abreu Pinho, Emmanuel Blot, qemu-arm,
Emmanuel Blot, Corey Minyard
Introduce the QOS test node and QMP property helpers for the
ADC128D818, and cover basic register access: manufacturer and
revision IDs, power-on-reset defaults, software reset, and the
ain and temperature property readback.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/adc128d818-test.c | 169 ++++++++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 170 insertions(+)
diff --git a/tests/qtest/adc128d818-test.c b/tests/qtest/adc128d818-test.c
new file mode 100644
index 0000000000..9a8811256f
--- /dev/null
+++ b/tests/qtest/adc128d818-test.c
@@ -0,0 +1,169 @@
+/*
+ * QTest testcase for the ADC128D818 ADC
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+#include "libqtest-single.h"
+#include "qobject/qdict.h"
+
+#define ADC128D818_TEST_ID "adc128d818-test"
+#define ADC128D818_TEST_ADDR 0x1f
+
+/* Register addresses */
+#define REG_CONFIG 0x00
+#define REG_INT_STATUS 0x01
+#define REG_INT_MASK 0x03
+#define REG_CONV_RATE 0x07
+#define REG_CH_DISABLE 0x08
+#define REG_ONE_SHOT 0x09
+#define REG_DEEP_SHUTDOWN 0x0a
+#define REG_ADV_CONFIG 0x0b
+#define REG_BUSY_STATUS 0x0c
+
+/* Channel Reading Registers (16-bit, read-only) */
+#define REG_CH_READING_BASE 0x20
+
+/* Limit Registers (8-bit, read/write) */
+#define REG_LIMIT_BASE 0x2a
+
+/* ID Registers (read-only) */
+#define REG_MANUFACTURER_ID 0x3e
+#define REG_REVISION_ID 0x3f
+
+/* Configuration Register (0x00) bitfields */
+#define CONFIG_START BIT(0)
+#define CONFIG_INT_ENABLE BIT(1)
+#define CONFIG_INT_CLEAR BIT(3)
+#define CONFIG_INITIALIZATION BIT(7)
+
+/* Advanced Configuration Register (0x0b) bitfields */
+#define ADV_CONFIG_EXT_REF_EN BIT(0)
+#define ADV_CONFIG_MODE_1 (1 << 1)
+#define ADV_CONFIG_MODE_2 (2 << 1)
+#define ADV_CONFIG_MODE_3 (3 << 1)
+
+/* Number of channels */
+#define NUM_CHANNELS 8
+
+/* Internal VREF in mV */
+#define INTERNAL_VREF_MV 2560
+
+/* QMP helpers for setting device properties */
+
+static void qmp_adc128d818_set(const char *property, int value)
+{
+ QDict *resp;
+
+ resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+ " { 'path': %s, 'property': %s, 'value': %d } }",
+ ADC128D818_TEST_ID, property, value);
+ g_assert(qdict_haskey(resp, "return"));
+ qobject_unref(resp);
+}
+
+static int qmp_adc128d818_get(const char *property)
+{
+ QDict *resp;
+ int ret;
+
+ resp = qmp("{ 'execute': 'qom-get', 'arguments':"
+ " { 'path': %s, 'property': %s } }",
+ ADC128D818_TEST_ID, property);
+ g_assert(qdict_haskey(resp, "return"));
+ ret = qdict_get_int(resp, "return");
+ qobject_unref(resp);
+ return ret;
+}
+
+/* Manufacturer and Revision ID registers */
+static void test_id_registers(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ g_assert_cmphex(i2c_get8(dev, REG_MANUFACTURER_ID), ==, 0x01);
+ g_assert_cmphex(i2c_get8(dev, REG_REVISION_ID), ==, 0x09);
+}
+
+/* Power-on-reset default values */
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ unsigned ch;
+
+ g_assert_cmphex(i2c_get8(dev, REG_CONFIG), ==, 0x08);
+ g_assert_cmphex(i2c_get8(dev, REG_INT_STATUS), ==, 0x00);
+ g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0x00);
+ g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x00);
+ g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x00);
+ g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x00);
+ g_assert_cmphex(i2c_get8(dev, REG_ADV_CONFIG), ==, 0x00);
+ g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS), ==, 0x02);
+
+ for (ch = 0u; ch < NUM_CHANNELS; ch++) {
+ g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE + ch * 2u), ==, 0xFF);
+ g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE + ch * 2u + 1u), ==, 0x00);
+ }
+}
+
+/* Software reset via INITIALIZATION bit */
+static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set8(dev, REG_INT_MASK, 0xAA);
+ i2c_set8(dev, REG_CH_DISABLE, 0x55);
+ i2c_set8(dev, REG_LIMIT_BASE, 0x42);
+
+ g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0xAA);
+ g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x55);
+ g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0x42);
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+ g_assert_cmphex(i2c_get8(dev, REG_CONFIG), ==, 0x08);
+ g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0x00);
+ g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x00);
+ g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0xFF);
+ g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS), ==, 0x02);
+}
+
+/* Verify ain property readback via QMP */
+static void test_ain_property(void *obj, void *data, QGuestAllocator *alloc)
+{
+ int value;
+
+ qmp_adc128d818_set("ain3", 1500);
+ value = qmp_adc128d818_get("ain3");
+ g_test_message("Set ain3 = 1500 mV, readback = %d mV", value);
+ g_assert_cmpint(value, ==, 1500);
+
+ qmp_adc128d818_set("temperature", 37500);
+ value = qmp_adc128d818_get("temperature");
+ g_test_message("Set temperature = 37500 mC, readback = %d mC", value);
+ g_assert_cmpint(value, ==, 37500);
+}
+
+static void adc128d818_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" ADC128D818_TEST_ID
+ ",address=0x1f"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { ADC128D818_TEST_ADDR });
+
+ qos_node_create_driver("adc128d818", i2c_device_create);
+ qos_node_consumes("adc128d818", "i2c-bus", &opts);
+
+ qos_add_test("id-registers", "adc128d818", test_id_registers, NULL);
+ qos_add_test("defaults", "adc128d818", test_defaults, NULL);
+ qos_add_test("soft-reset", "adc128d818", test_soft_reset, NULL);
+ qos_add_test("ain-property", "adc128d818", test_ain_property, NULL);
+}
+libqos_init(adc128d818_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 45ea497fa5..defa0d9941 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -296,6 +296,7 @@ qtests_riscv64 = ['riscv-csr-test'] + \
qos_test_ss = ss.source_set()
qos_test_ss.add(
'ac97-test.c',
+ 'adc128d818-test.c',
'adm1272-test.c',
'adm1266-test.c',
'ds1338-test.c',
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v5 3/8] tests/qtest: adc128d818: test voltage and temperature conversion
2026-07-01 17:28 [PATCH v5 0/8] hw/sensor: Add new device emulation for TI ADC128D818 Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 1/8] hw/sensor: adc128d818: add 12-bit 8-channel ADC device Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 2/8] tests/qtest: adc128d818: add test harness and register access Emmanuel Blot
@ 2026-07-01 17:28 ` Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 4/8] tests/qtest: adc128d818: test limit interrupts Emmanuel Blot
` (4 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Emmanuel Blot @ 2026-07-01 17:28 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Philippe Mathieu-Daudé, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Cédric Le Goater, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Alexander Hansen, William de Abreu Pinho, Emmanuel Blot, qemu-arm,
Emmanuel Blot, Corey Minyard
Cover single-ended voltage conversion across all channels, voltage
and temperature boundary and clamping cases, and scaling against an
external voltage reference.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/adc128d818-test.c | 189 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 189 insertions(+)
diff --git a/tests/qtest/adc128d818-test.c b/tests/qtest/adc128d818-test.c
index 9a8811256f..891189f0bd 100644
--- a/tests/qtest/adc128d818-test.c
+++ b/tests/qtest/adc128d818-test.c
@@ -150,6 +150,186 @@ static void test_ain_property(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmpint(value, ==, 37500);
}
+/* Voltage conversion */
+static void test_voltage_conversion(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ qmp_adc128d818_set("ain0", 1280);
+ g_test_message("Injected ain0 = 1280 mV");
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("Read ch0: raw 0x%04x -> %u mV", reading,
+ (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+ g_assert_cmphex(reading, ==, 0x8000);
+
+ qmp_adc128d818_set("ain1", 2560);
+ g_test_message("Injected ain1 = 2560 mV");
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+ g_test_message("Read ch1: raw 0x%04x -> %u mV", reading,
+ (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+ g_assert_cmphex(reading, ==, 0xFFF0);
+
+ qmp_adc128d818_set("ain2", 0);
+ g_test_message("Injected ain2 = 0 mV");
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 2u);
+ g_test_message("Read ch2: raw 0x%04x -> %u mV", reading,
+ (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+ g_assert_cmphex(reading, ==, 0x0000);
+}
+
+/* Temperature conversion (mode 0, channel 7 = temperature) */
+static void
+test_temperature_conversion(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ qmp_adc128d818_set("temperature", 25000);
+ g_test_message("Injected temperature = 25000 mC (25.0 deg C)");
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+ g_test_message("Read ch7: raw 0x%04x -> %d mC", reading,
+ (int16_t)(reading & 0xFF80u) * 500 / 128);
+ g_assert_cmphex(reading, ==, 0x1900);
+}
+
+/* Channels with distinct voltages */
+static void test_all_channels(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ static const uint16_t ain_mv[NUM_CHANNELS] = {
+ 0, 320, 640, 960, 1280, 1920, 2240, 2560
+ };
+ static const uint16_t expect[NUM_CHANNELS] = {
+ 0x0000, 0x2000, 0x4000, 0x6000, 0x8000, 0xC000, 0xE000, 0xFFF0
+ };
+ uint16_t reading;
+ unsigned ch;
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+ i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
+
+ for (ch = 0u; ch < NUM_CHANNELS; ch++) {
+ char name[8];
+ snprintf(name, sizeof(name), "ain%u", ch);
+ qmp_adc128d818_set(name, ain_mv[ch]);
+ }
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ for (ch = 0u; ch < NUM_CHANNELS; ch++) {
+ reading = i2c_get16(dev, REG_CH_READING_BASE + ch);
+ g_test_message("ch%u: ain %u mV -> raw 0x%04x (expect 0x%04x)",
+ ch, ain_mv[ch], reading, expect[ch]);
+ g_assert_cmphex(reading, ==, expect[ch]);
+ }
+}
+
+/* Voltage conversion edge cases */
+static void test_voltage_edges(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+ i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
+
+ qmp_adc128d818_set("ain0", 3000);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("Over-range 3000 mV: raw 0x%04x (expect 0xFFF0)", reading);
+ g_assert_cmphex(reading, ==, 0xFFF0);
+
+ qmp_adc128d818_set("ain1", 1);
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+ g_test_message("1 mV: raw 0x%04x (expect 0x0010)", reading);
+ g_assert_cmphex(reading, ==, 0x0010);
+
+ qmp_adc128d818_set("ain2", 640);
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 2u);
+ g_test_message("640 mV (quarter): raw 0x%04x (expect 0x4000)", reading);
+ g_assert_cmphex(reading, ==, 0x4000);
+
+ qmp_adc128d818_set("ain3", 1920);
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 3u);
+ g_test_message("1920 mV (3/4): raw 0x%04x (expect 0xC000)", reading);
+ g_assert_cmphex(reading, ==, 0xC000);
+}
+
+/* Temperature conversion edge cases */
+static void test_temperature_edges(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+ qmp_adc128d818_set("temperature", 0);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+ g_test_message("0 C: raw 0x%04x (expect 0x0000)", reading);
+ g_assert_cmphex(reading, ==, 0x0000);
+
+ qmp_adc128d818_set("temperature", -25000);
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+ g_test_message("-25 C: raw 0x%04x (expect 0xE700)", reading);
+ g_assert_cmphex(reading, ==, 0xE700);
+
+ qmp_adc128d818_set("temperature", 127500);
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+ g_test_message("+127.5 C: raw 0x%04x (expect 0x7F80)", reading);
+ g_assert_cmphex(reading, ==, 0x7F80);
+
+ qmp_adc128d818_set("temperature", -128000);
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+ g_test_message("-128 C: raw 0x%04x (expect 0x8000)", reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+
+ qmp_adc128d818_set("temperature", 200000);
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+ g_test_message("200 C (clamped): raw 0x%04x (expect 0x7F80)", reading);
+ g_assert_cmphex(reading, ==, 0x7F80);
+
+ qmp_adc128d818_set("temperature", -200000);
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+ g_test_message("-200 C (clamped): raw 0x%04x (expect 0x8000)", reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* External voltage reference */
+static void test_ext_vref(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+ i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
+
+ qmp_adc128d818_set("ext-vref-mv", 4096);
+ i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_EXT_REF_EN | ADV_CONFIG_MODE_1);
+
+ qmp_adc128d818_set("ain0", 1000);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("1000 mV / 4096 mV VREF: raw 0x%04x (expect 0x3E80)",
+ reading);
+ g_assert_cmphex(reading, ==, 0x3E80);
+
+ qmp_adc128d818_set("ain1", 2048);
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+ g_test_message("2048 mV / 4096 mV VREF: raw 0x%04x (expect 0x8000)",
+ reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+}
+
static void adc128d818_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -165,5 +345,14 @@ static void adc128d818_register_nodes(void)
qos_add_test("defaults", "adc128d818", test_defaults, NULL);
qos_add_test("soft-reset", "adc128d818", test_soft_reset, NULL);
qos_add_test("ain-property", "adc128d818", test_ain_property, NULL);
+ qos_add_test("voltage-conversion", "adc128d818", test_voltage_conversion,
+ NULL);
+ qos_add_test("temperature-conversion", "adc128d818",
+ test_temperature_conversion, NULL);
+ qos_add_test("all-channels", "adc128d818", test_all_channels, NULL);
+ qos_add_test("voltage-edges", "adc128d818", test_voltage_edges, NULL);
+ qos_add_test("temperature-edges", "adc128d818", test_temperature_edges,
+ NULL);
+ qos_add_test("ext-vref", "adc128d818", test_ext_vref, NULL);
}
libqos_init(adc128d818_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v5 4/8] tests/qtest: adc128d818: test limit interrupts
2026-07-01 17:28 [PATCH v5 0/8] hw/sensor: Add new device emulation for TI ADC128D818 Emmanuel Blot
` (2 preceding siblings ...)
2026-07-01 17:28 ` [PATCH v5 3/8] tests/qtest: adc128d818: test voltage and temperature conversion Emmanuel Blot
@ 2026-07-01 17:28 ` Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 5/8] tests/qtest: adc128d818: test operating modes and power control Emmanuel Blot
` (3 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Emmanuel Blot @ 2026-07-01 17:28 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Philippe Mathieu-Daudé, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Cédric Le Goater, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Alexander Hansen, William de Abreu Pinho, Emmanuel Blot, qemu-arm,
Emmanuel Blot, Corey Minyard
Cover per-channel high- and low-limit interrupt status, the
INT_CLEAR bit gating the monitoring loop, and the temperature
high-limit alarm with hysteresis.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/adc128d818-test.c | 121 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 121 insertions(+)
diff --git a/tests/qtest/adc128d818-test.c b/tests/qtest/adc128d818-test.c
index 891189f0bd..3e3fabb8d9 100644
--- a/tests/qtest/adc128d818-test.c
+++ b/tests/qtest/adc128d818-test.c
@@ -330,6 +330,123 @@ static void test_ext_vref(void *obj, void *data, QGuestAllocator *alloc)
g_assert_cmphex(reading, ==, 0x8000);
}
+/* Interrupt status set on limit violation; persists while fault remains */
+static void test_interrupt_status(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint8_t status;
+
+ i2c_set8(dev, REG_LIMIT_BASE, 0x10);
+ g_test_message("Set ch0 high limit = 0x10");
+
+ qmp_adc128d818_set("ain0", 2560);
+ g_test_message("Injected ain0 = 2560 mV (exceeds limit)");
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE);
+
+ status = i2c_get8(dev, REG_INT_STATUS);
+ g_test_message("INT_STATUS = 0x%02x (expect bit 0 set)", status);
+ g_assert_cmphex(status & 0x01u, ==, 0x01);
+
+ status = i2c_get8(dev, REG_INT_STATUS);
+ g_test_message("INT_STATUS after re-read = 0x%02x (expect bit 0 still set)",
+ status);
+ g_assert_cmphex(status & 0x01u, ==, 0x01);
+
+ qmp_adc128d818_set("ain0", 80);
+ g_test_message("Injected ain0 = 80 mV (within limit)");
+ status = i2c_get8(dev, REG_INT_STATUS);
+ g_test_message("INT_STATUS after fault cleared = 0x%02x "
+ "(expect bit 0 clear)", status);
+ g_assert_cmphex(status & 0x01u, ==, 0x00);
+}
+
+/* INT_CLEAR stops the round-robin monitoring loop */
+static void test_int_clear(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint8_t status;
+
+ i2c_set8(dev, REG_LIMIT_BASE, 0x10);
+ qmp_adc128d818_set("ain0", 2560);
+
+ i2c_set8(dev, REG_CONFIG,
+ CONFIG_START | CONFIG_INT_ENABLE | CONFIG_INT_CLEAR);
+ status = i2c_get8(dev, REG_INT_STATUS);
+ g_test_message("INT_STATUS with INT_CLEAR set = 0x%02x (expect 0x00)",
+ status);
+ g_assert_cmphex(status, ==, 0x00);
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE);
+ status = i2c_get8(dev, REG_INT_STATUS);
+ g_test_message("INT_STATUS after INT_CLEAR cleared = 0x%02x (expect bit 0)",
+ status);
+ g_assert_cmphex(status & 0x01u, ==, 0x01);
+}
+
+/* Low-limit interrupt triggers correctly */
+static void test_low_limit(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint8_t status;
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+ i2c_set8(dev, REG_LIMIT_BASE + 3u, 0x80);
+ g_test_message("Set ch1 low limit = 0x80");
+
+ i2c_set8(dev, REG_LIMIT_BASE + 5u, 0x80);
+ g_test_message("Set ch2 low limit = 0x80");
+
+ qmp_adc128d818_set("ain1", 640);
+ qmp_adc128d818_set("ain2", 1280);
+ qmp_adc128d818_set("ain0", 1280);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE);
+
+ status = i2c_get8(dev, REG_INT_STATUS);
+ g_test_message("INT_STATUS = 0x%02x (expect bits 1 and 2 set)", status);
+ g_assert_cmphex(status & 0x02u, ==, 0x02);
+ g_assert_cmphex(status & 0x04u, ==, 0x04);
+
+ g_assert_cmphex(status & 0x01u, ==, 0x00);
+}
+
+/* Temperature high-limit interrupt with hysteresis */
+static void test_temp_hysteresis(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint8_t status;
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+ i2c_set8(dev, REG_LIMIT_BASE + 7u * 2u, 0x32);
+ i2c_set8(dev, REG_LIMIT_BASE + 7u * 2u + 1u, 0x28);
+
+ qmp_adc128d818_set("temperature", 25000);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ status = i2c_get8(dev, REG_INT_STATUS);
+ g_test_message("25 C: INT_STATUS = 0x%02x (temp bit expect clear)", status);
+ g_assert_cmphex(status & 0x80u, ==, 0x00);
+
+ qmp_adc128d818_set("temperature", 55000);
+ status = i2c_get8(dev, REG_INT_STATUS);
+ g_test_message("55 C: INT_STATUS = 0x%02x (temp bit expect set)", status);
+ g_assert_cmphex(status & 0x80u, ==, 0x80);
+
+ qmp_adc128d818_set("temperature", 45000);
+ status = i2c_get8(dev, REG_INT_STATUS);
+ g_test_message("45 C (hysteresis): INT_STATUS = 0x%02x "
+ "(temp bit expect set)", status);
+ g_assert_cmphex(status & 0x80u, ==, 0x80);
+
+ qmp_adc128d818_set("temperature", 35000);
+ status = i2c_get8(dev, REG_INT_STATUS);
+ g_test_message("35 C: INT_STATUS = 0x%02x (temp bit expect clear)", status);
+ g_assert_cmphex(status & 0x80u, ==, 0x00);
+}
+
static void adc128d818_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -354,5 +471,9 @@ static void adc128d818_register_nodes(void)
qos_add_test("temperature-edges", "adc128d818", test_temperature_edges,
NULL);
qos_add_test("ext-vref", "adc128d818", test_ext_vref, NULL);
+ qos_add_test("interrupt-status", "adc128d818", test_interrupt_status, NULL);
+ qos_add_test("int-clear", "adc128d818", test_int_clear, NULL);
+ qos_add_test("low-limit", "adc128d818", test_low_limit, NULL);
+ qos_add_test("temp-hysteresis", "adc128d818", test_temp_hysteresis, NULL);
}
libqos_init(adc128d818_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v5 5/8] tests/qtest: adc128d818: test operating modes and power control
2026-07-01 17:28 [PATCH v5 0/8] hw/sensor: Add new device emulation for TI ADC128D818 Emmanuel Blot
` (3 preceding siblings ...)
2026-07-01 17:28 ` [PATCH v5 4/8] tests/qtest: adc128d818: test limit interrupts Emmanuel Blot
@ 2026-07-01 17:28 ` Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 6/8] hw/i2c: parent slaves created with i2c_slave_create_simple Emmanuel Blot
` (2 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Emmanuel Blot @ 2026-07-01 17:28 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Philippe Mathieu-Daudé, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Cédric Le Goater, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Alexander Hansen, William de Abreu Pinho, Emmanuel Blot, qemu-arm,
Emmanuel Blot, Corey Minyard
Cover advanced-configuration mode selection (single-ended,
pseudo-differential pairs, and mixed) and the reset of readings on
reconfiguration, plus channel disable, one-shot conversion, deep
shutdown, BUSY_STATUS lifecycle, and conversion-rate gating.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/qtest/adc128d818-test.c | 377 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 377 insertions(+)
diff --git a/tests/qtest/adc128d818-test.c b/tests/qtest/adc128d818-test.c
index 3e3fabb8d9..c3ed2c53b6 100644
--- a/tests/qtest/adc128d818-test.c
+++ b/tests/qtest/adc128d818-test.c
@@ -447,6 +447,368 @@ static void test_temp_hysteresis(void *obj, void *data,
g_assert_cmphex(status & 0x80u, ==, 0x00);
}
+/* Channel disable prevents conversion */
+static void test_channel_disable(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ i2c_set8(dev, REG_CH_DISABLE, 0x01);
+ g_test_message("Disabled channel 0");
+
+ qmp_adc128d818_set("ain0", 1280);
+ g_test_message("Injected ain0 = 1280 mV (disabled)");
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("Read ch0 (disabled): raw 0x%04x", reading);
+ g_assert_cmphex(reading, ==, 0x0000);
+
+ qmp_adc128d818_set("ain1", 1280);
+ g_test_message("Injected ain1 = 1280 mV (enabled)");
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+ g_test_message("Read ch1 (enabled): raw 0x%04x -> %u mV", reading,
+ (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+ g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* One-shot conversion in shutdown mode */
+static void test_one_shot(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ qmp_adc128d818_set("ain0", 1280);
+ g_test_message("Injected ain0 = 1280 mV (device stopped)");
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("Read ch0 before one-shot: raw 0x%04x", reading);
+ g_assert_cmphex(reading, ==, 0x0000);
+
+ g_assert_cmphex(i2c_get8(dev, REG_ONE_SHOT), ==, 0x00);
+
+ i2c_set8(dev, REG_ONE_SHOT, 0x00);
+ g_test_message("Triggered one-shot conversion with value 0x00");
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("Read ch0 after one-shot: raw 0x%04x -> %u mV", reading,
+ (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+ g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* Mode 1 makes channel 7 a voltage input instead of temperature */
+static void test_mode_selection(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
+ g_test_message("Set mode 1 (all voltage channels)");
+
+ qmp_adc128d818_set("ain7", 1280);
+ qmp_adc128d818_set("temperature", 50000);
+ g_test_message("Injected ain7 = 1280 mV, temperature = 50000 mC");
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+ g_test_message("Read ch7 (mode 1): raw 0x%04x -> %u mV", reading,
+ (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+ g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* Mode 2 — 4 pseudo-differential pairs */
+static void test_mode2_diff(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2);
+ g_test_message("Set mode 2 (4 pseudo-differential pairs)");
+
+ qmp_adc128d818_set("ain0", 2000);
+ qmp_adc128d818_set("ain1", 720);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("Pair 0 (IN0-IN1): raw 0x%04x (expect 0x8000)", reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+
+ qmp_adc128d818_set("ain3", 1920);
+ qmp_adc128d818_set("ain2", 640);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+ g_test_message("Pair 1 (IN3-IN2): raw 0x%04x (expect 0x8000)", reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+
+ qmp_adc128d818_set("ain4", 1500);
+ qmp_adc128d818_set("ain5", 220);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 2u);
+ g_test_message("Pair 2 (IN4-IN5): raw 0x%04x (expect 0x8000)", reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+
+ qmp_adc128d818_set("ain7", 2560);
+ qmp_adc128d818_set("ain6", 1280);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 3u);
+ g_test_message("Pair 3 (IN7-IN6): raw 0x%04x (expect 0x8000)", reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 4u);
+ g_test_message("Reserved ch4: raw 0x%04x (expect 0x0000)", reading);
+ g_assert_cmphex(reading, ==, 0x0000);
+
+ qmp_adc128d818_set("ain0", 500);
+ qmp_adc128d818_set("ain1", 1000);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("Pair 0 negative ΔV: raw 0x%04x (expect 0x0000)", reading);
+ g_assert_cmphex(reading, ==, 0x0000);
+}
+
+/* Mode 3 — 4 single-ended + 2 pseudo-differential pairs */
+static void test_mode3_mixed(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_3);
+ g_test_message("Set mode 3 (4 single-ended + 2 differential)");
+
+ qmp_adc128d818_set("ain0", 1280);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("Ch0 single-ended: raw 0x%04x (expect 0x8000)", reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+
+ qmp_adc128d818_set("ain4", 1500);
+ qmp_adc128d818_set("ain5", 220);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 4u);
+ g_test_message("Ch4 diff (IN4-IN5): raw 0x%04x (expect 0x8000)", reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+
+ qmp_adc128d818_set("ain7", 2560);
+ qmp_adc128d818_set("ain6", 1280);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 5u);
+ g_test_message("Ch5 diff (IN7-IN6): raw 0x%04x (expect 0x8000)", reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 6u);
+ g_test_message("Reserved ch6: raw 0x%04x (expect 0x0000)", reading);
+ g_assert_cmphex(reading, ==, 0x0000);
+
+ qmp_adc128d818_set("temperature", 25000);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+ g_test_message("Ch7 temperature: raw 0x%04x (expect 0x1900)", reading);
+ g_assert_cmphex(reading, ==, 0x1900);
+}
+
+/* Mode change resets channel readings and interrupt status */
+static void test_mode_change_reset(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+ uint8_t status;
+
+ qmp_adc128d818_set("ain0", 1280);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("Before mode change, ch0: raw 0x%04x", reading);
+ g_assert_cmphex(reading, !=, 0x0000);
+
+ i2c_set8(dev, REG_CONFIG, 0x00);
+ i2c_set8(dev, REG_LIMIT_BASE, 0x10);
+ qmp_adc128d818_set("ain0", 2560);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ i2c_set8(dev, REG_CONFIG, 0x00);
+ i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("After mode change, ch0: raw 0x%04x (expect 0x0000)",
+ reading);
+ g_assert_cmphex(reading, ==, 0x0000);
+
+ status = i2c_get8(dev, REG_INT_STATUS);
+ g_test_message("After mode change, INT_STATUS: 0x%02x (expect 0x00)",
+ status);
+ g_assert_cmphex(status, ==, 0x00);
+
+ g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0x10);
+ g_test_message("Limit register preserved after mode change");
+}
+
+/* QOM property changes trigger correct differential conversion */
+static void test_diff_qom_trigger(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+ i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2);
+
+ qmp_adc128d818_set("ain0", 0);
+ qmp_adc128d818_set("ain1", 0);
+ qmp_adc128d818_set("ain2", 0);
+ qmp_adc128d818_set("ain3", 0);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ qmp_adc128d818_set("ain0", 2000);
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("After ain0=2000, ain1=0: pair0 = 0x%04x (expect 0xC800)",
+ reading);
+ g_assert_cmphex(reading, ==, 0xC800);
+
+ qmp_adc128d818_set("ain1", 720);
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("After ain1=720: pair0 = 0x%04x (expect 0x8000)", reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+
+ qmp_adc128d818_set("ain3", 1920);
+ qmp_adc128d818_set("ain2", 640);
+ reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+ g_test_message("Pair 1 (IN3-IN2) via QOM: 0x%04x (expect 0x8000)", reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* One-shot conversion works in deep shutdown */
+static void test_deep_shutdown(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+ qmp_adc128d818_set("ain0", 1280);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_assert_cmphex(reading, ==, 0x8000);
+
+ i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x01);
+ g_test_message("DEEP_SHUTDOWN write while running rejected");
+ g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x00);
+
+ i2c_set8(dev, REG_CONFIG, 0x00);
+ i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x01);
+ qmp_adc128d818_set("ain0", 0);
+
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("Deep shutdown, no one-shot: raw 0x%04x (expect 0x8000)",
+ reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+
+ i2c_set8(dev, REG_ONE_SHOT, 0x01);
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("Deep shutdown one-shot: raw 0x%04x (expect 0x0000)",
+ reading);
+ g_assert_cmphex(reading, ==, 0x0000);
+
+ g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x01);
+
+ i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x00);
+ qmp_adc128d818_set("ain0", 1280);
+ i2c_set8(dev, REG_ONE_SHOT, 0x01);
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("After exit shutdown: raw 0x%04x (expect 0x8000)", reading);
+ g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* BUSY_STATUS NOT_READY clears after first conversion */
+static void test_busy_status(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+ g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS) & 0x02, ==, 0x02);
+ g_test_message("After reset: BUSY_STATUS = 0x%02x (NOT_READY set)",
+ i2c_get8(dev, REG_BUSY_STATUS));
+
+ qmp_adc128d818_set("ain0", 0);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+ g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS) & 0x02, ==, 0x00);
+ g_test_message("After conversion: BUSY_STATUS = 0x%02x (NOT_READY cleared)",
+ i2c_get8(dev, REG_BUSY_STATUS));
+}
+
+/* Programming Channel Disable resets channel readings */
+static void test_chan_disable_clears(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+ qmp_adc128d818_set("ain0", 1280);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+ g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000);
+
+ i2c_set8(dev, REG_CONFIG, 0x00);
+ i2c_set8(dev, REG_CH_DISABLE, 0x02);
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("After CH_DISABLE write: ch0 raw 0x%04x (expect 0x0000)",
+ reading);
+ g_assert_cmphex(reading, ==, 0x0000);
+}
+
+/* Programming Advanced Configuration always resets channel readings */
+static void test_adv_config_clears(void *obj, void *data,
+ QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+ uint16_t reading;
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+ qmp_adc128d818_set("ain0", 1280);
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+ g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000);
+
+ i2c_set8(dev, REG_CONFIG, 0x00);
+ i2c_set8(dev, REG_ADV_CONFIG, 0x00);
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("After same-mode ADV_CONFIG: ch0 0x%04x (expect 0x0000)",
+ reading);
+ g_assert_cmphex(reading, ==, 0x0000);
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+ g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000);
+ i2c_set8(dev, REG_CONFIG, 0x00);
+ qmp_adc128d818_set("ext-vref-mv", 4096);
+ i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_EXT_REF_EN);
+ reading = i2c_get16(dev, REG_CH_READING_BASE);
+ g_test_message("After ext-vref toggle: ch0 0x%04x (expect 0x0000)",
+ reading);
+ g_assert_cmphex(reading, ==, 0x0000);
+}
+
+/* Conversion Rate register may only be programmed while in shutdown */
+static void test_conv_rate(void *obj, void *data, QGuestAllocator *alloc)
+{
+ QI2CDevice *dev = (QI2CDevice *)obj;
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+ i2c_set8(dev, REG_CONV_RATE, 0x01);
+ g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x01);
+
+ i2c_set8(dev, REG_CONFIG, CONFIG_START);
+ i2c_set8(dev, REG_CONV_RATE, 0x00);
+ g_test_message("CONV_RATE while running: 0x%02x (expect unchanged 0x01)",
+ i2c_get8(dev, REG_CONV_RATE));
+ g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x01);
+}
+
static void adc128d818_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -475,5 +837,20 @@ static void adc128d818_register_nodes(void)
qos_add_test("int-clear", "adc128d818", test_int_clear, NULL);
qos_add_test("low-limit", "adc128d818", test_low_limit, NULL);
qos_add_test("temp-hysteresis", "adc128d818", test_temp_hysteresis, NULL);
+ qos_add_test("channel-disable", "adc128d818", test_channel_disable, NULL);
+ qos_add_test("one-shot", "adc128d818", test_one_shot, NULL);
+ qos_add_test("mode-selection", "adc128d818", test_mode_selection, NULL);
+ qos_add_test("mode2-diff", "adc128d818", test_mode2_diff, NULL);
+ qos_add_test("mode3-mixed", "adc128d818", test_mode3_mixed, NULL);
+ qos_add_test("mode-change-reset", "adc128d818", test_mode_change_reset,
+ NULL);
+ qos_add_test("diff-qom-trigger", "adc128d818", test_diff_qom_trigger, NULL);
+ qos_add_test("deep-shutdown", "adc128d818", test_deep_shutdown, NULL);
+ qos_add_test("busy-status", "adc128d818", test_busy_status, NULL);
+ qos_add_test("chan-disable-clears", "adc128d818", test_chan_disable_clears,
+ NULL);
+ qos_add_test("adv-config-clears", "adc128d818", test_adv_config_clears,
+ NULL);
+ qos_add_test("conv-rate", "adc128d818", test_conv_rate, NULL);
}
libqos_init(adc128d818_register_nodes);
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v5 6/8] hw/i2c: parent slaves created with i2c_slave_create_simple
2026-07-01 17:28 [PATCH v5 0/8] hw/sensor: Add new device emulation for TI ADC128D818 Emmanuel Blot
` (4 preceding siblings ...)
2026-07-01 17:28 ` [PATCH v5 5/8] tests/qtest: adc128d818: test operating modes and power control Emmanuel Blot
@ 2026-07-01 17:28 ` Emmanuel Blot
2026-07-06 7:14 ` Cédric Le Goater
` (2 more replies)
2026-07-01 17:28 ` [PATCH v5 7/8] hw/arm: anacapa: add ADC128D818 devices Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 8/8] test/functional: anacapa: test ADC128D818 Emmanuel Blot
7 siblings, 3 replies; 13+ messages in thread
From: Emmanuel Blot @ 2026-07-01 17:28 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Philippe Mathieu-Daudé, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Cédric Le Goater, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Alexander Hansen, William de Abreu Pinho, Emmanuel Blot, qemu-arm,
Emmanuel Blot, Corey Minyard
Slaves created with i2c_slave_create_simple() were left unparented and
showed up under /machine/unattached with no stable QOM path. Add each
slave as a QOM child of its bus, named after its I2C address, so it has
a deterministic and addressable QOM path.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/i2c/core.c | 3 +++
include/hw/i2c/i2c.h | 7 +++++--
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/hw/i2c/core.c b/hw/i2c/core.c
index 54f6bdca88..0bbce45d48 100644
--- a/hw/i2c/core.c
+++ b/hw/i2c/core.c
@@ -382,6 +382,9 @@ I2CSlave *i2c_slave_create_simple(I2CBus *bus, const char *name, uint8_t addr)
{
I2CSlave *dev = i2c_slave_new(name, addr);
+ g_autofree char *childname = g_strdup_printf("0x%02x", addr);
+ object_property_add_child(OBJECT(bus), childname, OBJECT(dev));
+
i2c_slave_realize_and_unref(dev, bus, &error_abort);
return dev;
diff --git a/include/hw/i2c/i2c.h b/include/hw/i2c/i2c.h
index dd5930f4b5..dc557bbf3f 100644
--- a/include/hw/i2c/i2c.h
+++ b/include/hw/i2c/i2c.h
@@ -166,13 +166,16 @@ bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
I2CSlave *i2c_slave_new(const char *name, uint8_t addr);
/**
- * Create and realize an I2C slave device on the heap.
+ * Create and realize an I2C slave device on the heap, add the device as a
+ * child of its parent bus.
+ *
* @bus: I2C bus to put it on
* @name: I2C slave device type name
* @addr: I2C address of the slave when put on a bus
*
* Create the device state structure, initialize it, put it on the
- * specified @bus, and drop the reference to it (the device is realized).
+ * specified @bus, parent it, and drop the reference to it (the device is
+ * realized).
*/
I2CSlave *i2c_slave_create_simple(I2CBus *bus, const char *name, uint8_t addr);
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v5 7/8] hw/arm: anacapa: add ADC128D818 devices
2026-07-01 17:28 [PATCH v5 0/8] hw/sensor: Add new device emulation for TI ADC128D818 Emmanuel Blot
` (5 preceding siblings ...)
2026-07-01 17:28 ` [PATCH v5 6/8] hw/i2c: parent slaves created with i2c_slave_create_simple Emmanuel Blot
@ 2026-07-01 17:28 ` Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 8/8] test/functional: anacapa: test ADC128D818 Emmanuel Blot
7 siblings, 0 replies; 13+ messages in thread
From: Emmanuel Blot @ 2026-07-01 17:28 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Philippe Mathieu-Daudé, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Cédric Le Goater, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Alexander Hansen, William de Abreu Pinho, Emmanuel Blot, qemu-arm,
Emmanuel Blot, Corey Minyard, Cédric Le Goater
Wire up the two ADC128D818 instances that appear in the Anacapa DTS:
one on i2c8 mux channel 0 and one on i2c13 mux channel 3.
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
hw/arm/Kconfig | 1 +
hw/arm/aspeed_ast2600_anacapa.c | 21 +++++++++++++++++----
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index fb798ccbee..04ecf5d473 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -553,6 +553,7 @@ config ASPEED_SOC
select LED
select PMBUS
select MAX31785
+ select ADC128D818
select FSI_APB2OPB_ASPEED
select AT24C
select PCI_EXPRESS
diff --git a/hw/arm/aspeed_ast2600_anacapa.c b/hw/arm/aspeed_ast2600_anacapa.c
index a1c8111a93..18f759f5f8 100644
--- a/hw/arm/aspeed_ast2600_anacapa.c
+++ b/hw/arm/aspeed_ast2600_anacapa.c
@@ -1,13 +1,14 @@
/*
* Facebook Anacapa
*
- * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
+#include "hw/sensor/adc128d818.h"
#include "hw/arm/machines-qom.h"
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
@@ -15,7 +16,6 @@
#include "hw/gpio/pca9552.h"
#include "hw/nvram/eeprom_at24c.h"
-/* Anacapa hardware value */
#define ANACAPA_BMC_HW_STRAP1 0x00002002
#define ANACAPA_BMC_HW_STRAP2 0x00000000
#define ANACAPA_BMC_RAM_SIZE ASPEED_RAM_SIZE(2 * GiB)
@@ -221,6 +221,17 @@ static const uint8_t hpm_brd_id_eeprom[] = {
};
static const size_t hpm_brd_id_eeprom_len = sizeof(hpm_brd_id_eeprom);
+static void anacapa_add_adc128d818(I2CBus *bus, uint8_t addr,
+ const char *description)
+{
+ DeviceState *dev = DEVICE(i2c_slave_new(TYPE_ADC128D818, addr));
+ g_autofree char *childname = g_strdup_printf("0x%02x", addr);
+
+ qdev_prop_set_string(dev, "description", description);
+ object_property_add_child(OBJECT(bus), childname, OBJECT(dev));
+ i2c_slave_realize_and_unref(I2C_SLAVE(dev), bus, &error_fatal);
+}
+
static void anacapa_bmc_i2c_init(AspeedMachineState *bmc)
{
/* Reference: aspeed-bmc-facebook-anacapa.dts */
@@ -259,7 +270,8 @@ static void anacapa_bmc_i2c_init(AspeedMachineState *bmc)
i2c_mux = i2c_slave_create_simple(i2c[8], TYPE_PCA9546, 0x72);
/* i2c8mux ch0 */
- /* adc128d818@1f — no model */
+ /* adc128d818@1f — R-PDB ADC (mode 1: 8 voltage channels) */
+ anacapa_add_adc128d818(pca954x_i2c_get_bus(i2c_mux, 0), 0x1f, "i2c8:0:1f");
/* pca9555@22 */
i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 0),
TYPE_PCA9552, 0x22);
@@ -320,7 +332,8 @@ static void anacapa_bmc_i2c_init(AspeedMachineState *bmc)
i2c_mux = i2c_slave_create_simple(i2c[13], TYPE_PCA9548, 0x70);
/* i2c13mux ch3 */
- /* adc128d818@1f - no model */
+ /* adc128d818@1f — MB ADC (mode 1: 8 voltage channels) */
+ anacapa_add_adc128d818(pca954x_i2c_get_bus(i2c_mux, 3), 0x1f, "i2c13:3:1f");
/* i2c13mux ch4 */
/* eeprom@51 */
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v5 8/8] test/functional: anacapa: test ADC128D818
2026-07-01 17:28 [PATCH v5 0/8] hw/sensor: Add new device emulation for TI ADC128D818 Emmanuel Blot
` (6 preceding siblings ...)
2026-07-01 17:28 ` [PATCH v5 7/8] hw/arm: anacapa: add ADC128D818 devices Emmanuel Blot
@ 2026-07-01 17:28 ` Emmanuel Blot
2026-07-06 8:06 ` Cédric Le Goater
7 siblings, 1 reply; 13+ messages in thread
From: Emmanuel Blot @ 2026-07-01 17:28 UTC (permalink / raw)
To: qemu-devel
Cc: Paolo Bonzini, Philippe Mathieu-Daudé, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Cédric Le Goater, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Alexander Hansen, William de Abreu Pinho, Emmanuel Blot, qemu-arm,
Emmanuel Blot, Corey Minyard
Verify the ADC128D818 on the Anacapa R-PDB (i2c8 mux channel 0): two
channels are driven with distinct voltages and each is checked to be
reported back independently through the kernel hwmon interface, and the
channel 0 limit registers are validated.
Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
---
tests/functional/arm/test_aspeed_anacapa.py | 69 ++++++++++++++++++++++++++---
1 file changed, 63 insertions(+), 6 deletions(-)
diff --git a/tests/functional/arm/test_aspeed_anacapa.py b/tests/functional/arm/test_aspeed_anacapa.py
index 2b72e46b0e..49fab7ad09 100644
--- a/tests/functional/arm/test_aspeed_anacapa.py
+++ b/tests/functional/arm/test_aspeed_anacapa.py
@@ -2,24 +2,81 @@
#
# Functional test that boots the ASPEED machines
#
+# Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+#
# SPDX-License-Identifier: GPL-2.0-or-later
+import re
+import time
+
from qemu_test import Asset
+from qemu_test import exec_command_and_wait_for_pattern
from aspeed import FacebookAspeedTest
class AnacapaMachine(FacebookAspeedTest):
ASSET_ANACAPA_FLASH = Asset(
- 'https://github.com/legoater/qemu-aspeed-boot/raw/c5f585f9f57843c0dc95e3865a57d92ec39cbb0b/images/anacapa-bmc/openbmc-20260616025349/obmc-phosphor-image-anacapa-20260616025349.static.mtd.xz',
- 'de3841fb6ed3085aec6424358ee6efc4b8ee85688361e5aa1987fd1acb7d3fb4')
+ "https://github.com/legoater/qemu-aspeed-boot/raw/c5f585f9f57843c0dc95e3865a57d92ec39cbb0b/images/anacapa-bmc/openbmc-20260616025349/obmc-phosphor-image-anacapa-20260616025349.static.mtd.xz",
+ "de3841fb6ed3085aec6424358ee6efc4b8ee85688361e5aa1987fd1acb7d3fb4",
+ )
+
+ ADC128D818_QOM_PATH = \
+ "/machine/soc/i2c/bus[8]/aspeed.i2c.bus.8/0x72/i2c.0/0x1f"
+ ADC128D818_MUX_CHANNEL = "/sys/bus/i2c/devices/8-0072/channel-0"
+ PROMPT = "root@anacapa:~#"
def test_arm_ast2600_anacapa_openbmc(self):
image_path = self.uncompress(self.ASSET_ANACAPA_FLASH)
- self.do_test_arm_aspeed_openbmc('anacapa-bmc', image=image_path,
- uboot='2019.04', cpu_id='0xf00',
- soc='AST2600 rev A3')
+ self.do_test_arm_aspeed_openbmc(
+ "anacapa-bmc",
+ image=image_path,
+ uboot="2019.04",
+ cpu_id="0xf00",
+ soc="AST2600 rev A3",
+ )
-if __name__ == '__main__':
+ exec_command_and_wait_for_pattern(self, "root", "Password:")
+ exec_command_and_wait_for_pattern(self, "0penBmc", "#")
+
+ self.adc_hwmon = self.resolve_adc128d818_hwmon()
+ self.assertIn(b"adc128d818", self.read_adc128d818("name"))
+
+ adc = self.ADC128D818_QOM_PATH
+ for ch0_mv, ch1_mv in ((108, 2000), (1280, 500)):
+ self.vm.cmd("qom-set", path=adc, property="ain0", value=ch0_mv)
+ self.vm.cmd("qom-set", path=adc, property="ain1", value=ch1_mv)
+ self.wait_adc128d818_value("in0_input", ch0_mv)
+ self.wait_adc128d818_value("in1_input", ch1_mv)
+
+ self.assertIn(b"2551", self.read_adc128d818("in0_max"))
+ self.assertRegex(self.read_adc128d818("in0_min"), rb"(?m)^0\r*$")
+
+ def resolve_adc128d818_hwmon(self):
+ out = self.read_adc128d818_console(
+ f"basename $(readlink {self.ADC128D818_MUX_CHANNEL})"
+ )
+ match = re.search(rb"i2c-(\d+)", out)
+ self.assertIsNotNone(match, "could not resolve ADC128D818 i2c bus")
+ bus = int(match.group(1))
+ return f"/sys/bus/i2c/devices/{bus}-001f/hwmon/hwmon*"
+
+ def read_adc128d818_console(self, command):
+ return exec_command_and_wait_for_pattern(self, command, self.PROMPT)
+
+ def read_adc128d818(self, attr):
+ return self.read_adc128d818_console(f"cat {self.adc_hwmon}/{attr}")
+
+ def wait_adc128d818_value(self, attr, expected, timeout=20):
+ needle = str(expected).encode()
+ deadline = time.monotonic() + timeout
+ while time.monotonic() < deadline:
+ if needle in self.read_adc128d818(attr):
+ return
+ time.sleep(2)
+ self.fail(f"{attr} did not reach {expected}")
+
+
+if __name__ == "__main__":
FacebookAspeedTest.main()
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v5 6/8] hw/i2c: parent slaves created with i2c_slave_create_simple
2026-07-01 17:28 ` [PATCH v5 6/8] hw/i2c: parent slaves created with i2c_slave_create_simple Emmanuel Blot
@ 2026-07-06 7:14 ` Cédric Le Goater
2026-07-10 15:25 ` Markus Armbruster
2026-07-10 16:05 ` Peter Maydell
2 siblings, 0 replies; 13+ messages in thread
From: Cédric Le Goater @ 2026-07-06 7:14 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Paolo Bonzini, Philippe Mathieu-Daudé, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Steven Lee, Troy Lee, Jamin Lin,
Kane Chen, Andrew Jeffery, Joel Stanley, Alexander Hansen,
William de Abreu Pinho, Emmanuel Blot, qemu-arm, Corey Minyard,
Markus Armbruster
+ Markus,
On 7/1/26 19:28, Emmanuel Blot wrote:
> Slaves created with i2c_slave_create_simple() were left unparented and
> showed up under /machine/unattached with no stable QOM path. Add each
> slave as a QOM child of its bus, named after its I2C address, so it has
> a deterministic and addressable QOM path.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/i2c/core.c | 3 +++
> include/hw/i2c/i2c.h | 7 +++++--
> 2 files changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/hw/i2c/core.c b/hw/i2c/core.c
> index 54f6bdca88..0bbce45d48 100644
> --- a/hw/i2c/core.c
> +++ b/hw/i2c/core.c
> @@ -382,6 +382,9 @@ I2CSlave *i2c_slave_create_simple(I2CBus *bus, const char *name, uint8_t addr)
> {
> I2CSlave *dev = i2c_slave_new(name, addr);
>
> + g_autofree char *childname = g_strdup_printf("0x%02x", addr);
> + object_property_add_child(OBJECT(bus), childname, OBJECT(dev));
> +
> i2c_slave_realize_and_unref(dev, bus, &error_abort);
>
> return dev;
> diff --git a/include/hw/i2c/i2c.h b/include/hw/i2c/i2c.h
> index dd5930f4b5..dc557bbf3f 100644
> --- a/include/hw/i2c/i2c.h
> +++ b/include/hw/i2c/i2c.h
> @@ -166,13 +166,16 @@ bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
> I2CSlave *i2c_slave_new(const char *name, uint8_t addr);
>
> /**
> - * Create and realize an I2C slave device on the heap.
> + * Create and realize an I2C slave device on the heap, add the device as a
> + * child of its parent bus.
> + *
> * @bus: I2C bus to put it on
> * @name: I2C slave device type name
> * @addr: I2C address of the slave when put on a bus
> *
> * Create the device state structure, initialize it, put it on the
> - * specified @bus, and drop the reference to it (the device is realized).
> + * specified @bus, parent it, and drop the reference to it (the device is
> + * realized).
> */
> I2CSlave *i2c_slave_create_simple(I2CBus *bus, const char *name, uint8_t addr);
>
>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v5 8/8] test/functional: anacapa: test ADC128D818
2026-07-01 17:28 ` [PATCH v5 8/8] test/functional: anacapa: test ADC128D818 Emmanuel Blot
@ 2026-07-06 8:06 ` Cédric Le Goater
0 siblings, 0 replies; 13+ messages in thread
From: Cédric Le Goater @ 2026-07-06 8:06 UTC (permalink / raw)
To: Emmanuel Blot, qemu-devel
Cc: Paolo Bonzini, Philippe Mathieu-Daudé, Fabiano Rosas,
Laurent Vivier, Peter Maydell, Steven Lee, Troy Lee, Jamin Lin,
Kane Chen, Andrew Jeffery, Joel Stanley, Alexander Hansen,
William de Abreu Pinho, Emmanuel Blot, qemu-arm, Corey Minyard
On 7/1/26 19:28, Emmanuel Blot wrote:
> Verify the ADC128D818 on the Anacapa R-PDB (i2c8 mux channel 0): two
> channels are driven with distinct voltages and each is checked to be
> reported back independently through the kernel hwmon interface, and the
> channel 0 limit registers are validated.
>
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> tests/functional/arm/test_aspeed_anacapa.py | 69 ++++++++++++++++++++++++++---
> 1 file changed, 63 insertions(+), 6 deletions(-)
>
> diff --git a/tests/functional/arm/test_aspeed_anacapa.py b/tests/functional/arm/test_aspeed_anacapa.py
> index 2b72e46b0e..49fab7ad09 100644
> --- a/tests/functional/arm/test_aspeed_anacapa.py
> +++ b/tests/functional/arm/test_aspeed_anacapa.py
> @@ -2,24 +2,81 @@
> #
> # Functional test that boots the ASPEED machines
> #
> +# Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
> +#
> # SPDX-License-Identifier: GPL-2.0-or-later
>
> +import re
> +import time
> +
> from qemu_test import Asset
> +from qemu_test import exec_command_and_wait_for_pattern
> from aspeed import FacebookAspeedTest
>
>
> class AnacapaMachine(FacebookAspeedTest):
>
> ASSET_ANACAPA_FLASH = Asset(
> - 'https://github.com/legoater/qemu-aspeed-boot/raw/c5f585f9f57843c0dc95e3865a57d92ec39cbb0b/images/anacapa-bmc/openbmc-20260616025349/obmc-phosphor-image-anacapa-20260616025349.static.mtd.xz',
> - 'de3841fb6ed3085aec6424358ee6efc4b8ee85688361e5aa1987fd1acb7d3fb4')
> + "https://github.com/legoater/qemu-aspeed-boot/raw/c5f585f9f57843c0dc95e3865a57d92ec39cbb0b/images/anacapa-bmc/openbmc-20260616025349/obmc-phosphor-image-anacapa-20260616025349.static.mtd.xz",
> + "de3841fb6ed3085aec6424358ee6efc4b8ee85688361e5aa1987fd1acb7d3fb4",
> + )
> +
> + ADC128D818_QOM_PATH = \
> + "/machine/soc/i2c/bus[8]/aspeed.i2c.bus.8/0x72/i2c.0/0x1f"
> + ADC128D818_MUX_CHANNEL = "/sys/bus/i2c/devices/8-0072/channel-0"
> + PROMPT = "root@anacapa:~#"
>
> def test_arm_ast2600_anacapa_openbmc(self):
> image_path = self.uncompress(self.ASSET_ANACAPA_FLASH)
>
> - self.do_test_arm_aspeed_openbmc('anacapa-bmc', image=image_path,
> - uboot='2019.04', cpu_id='0xf00',
> - soc='AST2600 rev A3')
> + self.do_test_arm_aspeed_openbmc(
> + "anacapa-bmc",
> + image=image_path,
> + uboot="2019.04",
> + cpu_id="0xf00",
> + soc="AST2600 rev A3",
> + )
>
> -if __name__ == '__main__':
> + exec_command_and_wait_for_pattern(self, "root", "Password:")
> + exec_command_and_wait_for_pattern(self, "0penBmc", "#")
> +
> + self.adc_hwmon = self.resolve_adc128d818_hwmon()
> + self.assertIn(b"adc128d818", self.read_adc128d818("name"))
> +
> + adc = self.ADC128D818_QOM_PATH
> + for ch0_mv, ch1_mv in ((108, 2000), (1280, 500)):
> + self.vm.cmd("qom-set", path=adc, property="ain0", value=ch0_mv)
> + self.vm.cmd("qom-set", path=adc, property="ain1", value=ch1_mv)
> + self.wait_adc128d818_value("in0_input", ch0_mv)
> + self.wait_adc128d818_value("in1_input", ch1_mv)
> +
> + self.assertIn(b"2551", self.read_adc128d818("in0_max"))
> + self.assertRegex(self.read_adc128d818("in0_min"), rb"(?m)^0\r*$")
> +
> + def resolve_adc128d818_hwmon(self):
> + out = self.read_adc128d818_console(
> + f"basename $(readlink {self.ADC128D818_MUX_CHANNEL})"
> + )
> + match = re.search(rb"i2c-(\d+)", out)
> + self.assertIsNotNone(match, "could not resolve ADC128D818 i2c bus")
> + bus = int(match.group(1))
> + return f"/sys/bus/i2c/devices/{bus}-001f/hwmon/hwmon*"
> +
> + def read_adc128d818_console(self, command):
> + return exec_command_and_wait_for_pattern(self, command, self.PROMPT)
> +
> + def read_adc128d818(self, attr):
> + return self.read_adc128d818_console(f"cat {self.adc_hwmon}/{attr}")
> +
> + def wait_adc128d818_value(self, attr, expected, timeout=20):
20s ...
> + needle = str(expected).encode()
> + deadline = time.monotonic() + timeout
> + while time.monotonic() < deadline:
> + if needle in self.read_adc128d818(attr):
> + return
> + time.sleep(2)
The first read of hwmon should already trigger a fresh read. If a sleep
is required, which I doubt, please do it once and then assert.
Thanks,
C.
> + self.fail(f"{attr} did not reach {expected}")
> +
> +
> +if __name__ == "__main__":
> FacebookAspeedTest.main()
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v5 6/8] hw/i2c: parent slaves created with i2c_slave_create_simple
2026-07-01 17:28 ` [PATCH v5 6/8] hw/i2c: parent slaves created with i2c_slave_create_simple Emmanuel Blot
2026-07-06 7:14 ` Cédric Le Goater
@ 2026-07-10 15:25 ` Markus Armbruster
2026-07-10 16:05 ` Peter Maydell
2 siblings, 0 replies; 13+ messages in thread
From: Markus Armbruster @ 2026-07-10 15:25 UTC (permalink / raw)
To: Emmanuel Blot
Cc: qemu-devel, Paolo Bonzini, Philippe Mathieu-Daudé,
Fabiano Rosas, Laurent Vivier, Peter Maydell,
Cédric Le Goater, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, Alexander Hansen,
William de Abreu Pinho, Emmanuel Blot, qemu-arm, Corey Minyard
Emmanuel Blot <emmanuel.blot@free.fr> writes:
> Slaves created with i2c_slave_create_simple() were left unparented and
> showed up under /machine/unattached with no stable QOM path. Add each
> slave as a QOM child of its bus, named after its I2C address, so it has
> a deterministic and addressable QOM path.
Could use an example.
Doesn't tell which machines are affected.
To find affected machines, I captured output of "info qom-tree" by running
$ echo -e 'info qom-tree\nq' | qemu-system-TARGET -M MACHINE -S -display none -monitor stdio
for all targets and all their machine types.
This fails on my box for a number of machines: misses some image file,
needs Xen, ... These are
an5206
boston
boston-aia
canon-a1100
leon3_generic
loongson3-virt
mcf5208evb
microchip-icicle-kit
niagara
nitro
q800
sx1
sx1-v1
xenfv-4.2
xenpv
xenpvh
Testing them manually would be possible. Not by me.
Of the ones that run, output differs for
anacapa-bmc
ast1030-evb
ast1040-evb
ast1060-evb
ast2500-evb
ast2600-evb
ast2700a1-evb
ast2700a2-evb
ast2700fc
bletchley-bmc
bpim2u
catalina-bmc
cubieboard
fby35-bmc
fuji-bmc
g220a-bmc
gb200nvl-bmc
kudo-bmc
lm3s811evb
mpc8544ds
npcm750-evb
npcm845-evb
palmetto-bmc
powernv10-rainier
ppce500
quanta-gsj
quanta-q71l-bmc
rainier-bmc
realview-eb
realview-eb-mpcore
realview-pb-a8
realview-pbx-a9
romulus-bmc
sam460ex
supermicro-x11spi-bmc
supermicrox11-bmc
tiogapass-bmc
tt-atlantis
versatileab
versatilepb
vexpress-a15
vexpress-a9
witherspoon-bmc
yosemitev2-bmc
Looking for one with a short diff, I found realview-eb:
/device[26] (versatile_i2c)
/arm_sbcon_i2c[0] (memory-region)
/i2c (i2c-bus)
- /device[27] (ds1338)
+ /0x68 (ds1338)
/device[2] (realview_gic)
/gic (arm_gic)
/gic_cpu[0] (memory-region)
The QOM path of this ds1338 device changes from
/machine/unattached/device[27]
to
/machine/unattached/device[26]/0x68
The new path still isn't stable, because the parent's path isn't. This
caveat should be noted in the commit message.
An example of successful stabilization is tt-atlantis, where the QOM
paths of ds1338 and tmp105 change from
/machine/unattached/device[21]
/machine/unattached/device[22]
to
/machine/i2c[0]/i2c-bus/0x6f
/machine/i2c[4]/i2c-bus/0x48
> Signed-off-by: Emmanuel Blot <emmanuel.blot@free.fr>
> ---
> hw/i2c/core.c | 3 +++
> include/hw/i2c/i2c.h | 7 +++++--
> 2 files changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/hw/i2c/core.c b/hw/i2c/core.c
> index 54f6bdca88..0bbce45d48 100644
> --- a/hw/i2c/core.c
> +++ b/hw/i2c/core.c
> @@ -382,6 +382,9 @@ I2CSlave *i2c_slave_create_simple(I2CBus *bus, const char *name, uint8_t addr)
> {
> I2CSlave *dev = i2c_slave_new(name, addr);
>
> + g_autofree char *childname = g_strdup_printf("0x%02x", addr);
Is the hex address a satisfactory child name?
> + object_property_add_child(OBJECT(bus), childname, OBJECT(dev));
> +
> i2c_slave_realize_and_unref(dev, bus, &error_abort);
>
> return dev;
> diff --git a/include/hw/i2c/i2c.h b/include/hw/i2c/i2c.h
> index dd5930f4b5..dc557bbf3f 100644
> --- a/include/hw/i2c/i2c.h
> +++ b/include/hw/i2c/i2c.h
> @@ -166,13 +166,16 @@ bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
> I2CSlave *i2c_slave_new(const char *name, uint8_t addr);
>
> /**
> - * Create and realize an I2C slave device on the heap.
> + * Create and realize an I2C slave device on the heap, add the device as a
> + * child of its parent bus.
> + *
> * @bus: I2C bus to put it on
> * @name: I2C slave device type name
> * @addr: I2C address of the slave when put on a bus
> *
> * Create the device state structure, initialize it, put it on the
> - * specified @bus, and drop the reference to it (the device is realized).
> + * specified @bus, parent it, and drop the reference to it (the device is
> + * realized).
> */
> I2CSlave *i2c_slave_create_simple(I2CBus *bus, const char *name, uint8_t addr);
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v5 6/8] hw/i2c: parent slaves created with i2c_slave_create_simple
2026-07-01 17:28 ` [PATCH v5 6/8] hw/i2c: parent slaves created with i2c_slave_create_simple Emmanuel Blot
2026-07-06 7:14 ` Cédric Le Goater
2026-07-10 15:25 ` Markus Armbruster
@ 2026-07-10 16:05 ` Peter Maydell
2 siblings, 0 replies; 13+ messages in thread
From: Peter Maydell @ 2026-07-10 16:05 UTC (permalink / raw)
To: Emmanuel Blot
Cc: qemu-devel, Paolo Bonzini, Philippe Mathieu-Daudé,
Fabiano Rosas, Laurent Vivier, Cédric Le Goater, Steven Lee,
Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley,
Alexander Hansen, William de Abreu Pinho, Emmanuel Blot, qemu-arm,
Corey Minyard
On Wed, 1 Jul 2026 at 18:31, Emmanuel Blot <emmanuel.blot@free.fr> wrote:
>
> Slaves created with i2c_slave_create_simple() were left unparented and
> showed up under /machine/unattached with no stable QOM path. Add each
> slave as a QOM child of its bus, named after its I2C address, so it has
> a deterministic and addressable QOM path.
Should devices on a bus be QOM children of the bus, though?
I would expect that they ought to be QOM children of e.g.
the SoC or machine that created them.
Do we do this for other bus types, e.g. PCI ?
-- PMM
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-07-10 16:05 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-01 17:28 [PATCH v5 0/8] hw/sensor: Add new device emulation for TI ADC128D818 Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 1/8] hw/sensor: adc128d818: add 12-bit 8-channel ADC device Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 2/8] tests/qtest: adc128d818: add test harness and register access Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 3/8] tests/qtest: adc128d818: test voltage and temperature conversion Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 4/8] tests/qtest: adc128d818: test limit interrupts Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 5/8] tests/qtest: adc128d818: test operating modes and power control Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 6/8] hw/i2c: parent slaves created with i2c_slave_create_simple Emmanuel Blot
2026-07-06 7:14 ` Cédric Le Goater
2026-07-10 15:25 ` Markus Armbruster
2026-07-10 16:05 ` Peter Maydell
2026-07-01 17:28 ` [PATCH v5 7/8] hw/arm: anacapa: add ADC128D818 devices Emmanuel Blot
2026-07-01 17:28 ` [PATCH v5 8/8] test/functional: anacapa: test ADC128D818 Emmanuel Blot
2026-07-06 8:06 ` Cédric Le Goater
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.