All of lore.kernel.org
 help / color / mirror / Atom feed
* [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.