Reviewed-by: Glenn Miles <[email protected]>

Thanks,

Glenn

On Mon, 2026-10-12 at 16:32 +0200, Emmanuel Blot via qemu development
wrote:
> Add a model for the Texas Instruments INA230, a 36V, 16-bit I2C
> current-shunt and power monitor.
> 
> The device measures a differential shunt voltage and a bus voltage; the
> current and power registers are not measured directly but derived by the
> device from the shunt voltage and the guest-programmed Calibration
> register. The alert is modelled.
> 
> Shunt and bus voltages can be injected through the "shunt-voltage" and
> "bus-voltage" QOM properties (in nV and uV respectively).
> 
> Note: Conversion is instantaneous: the averaging and conversion-time
> fields are stored but have no timing effect.
> 
> Signed-off-by: Emmanuel Blot <[email protected]>
> ---
>  hw/arm/Kconfig             |   1 +
>  hw/sensor/Kconfig          |   4 +
>  hw/sensor/ina230.c         | 597 
> +++++++++++++++++++++++++++++++++++++++++++++
>  hw/sensor/meson.build      |   1 +
>  hw/sensor/trace-events     |   7 +
>  include/hw/sensor/ina230.h |  14 ++
>  6 files changed, 624 insertions(+)
> 
> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index 4445f16f45..116d1c64ac 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -539,6 +539,7 @@ config ASPEED_SOC
>      select TMP105
>      select TMP421
>      select EMC141X
> +    select INA230
>      select OR_IRQ
>      select UNIMP
>      select LED
> diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
> index b459ac2240..da519159ac 100644
> --- a/hw/sensor/Kconfig
> +++ b/hw/sensor/Kconfig
> @@ -2,6 +2,10 @@ config ADC128D818
>      bool
>      depends on I2C
>  
> +config INA230
> +    bool
> +    depends on I2C
> +
>  config TMP105
>      bool
>      depends on I2C
> diff --git a/hw/sensor/ina230.c b/hw/sensor/ina230.c
> new file mode 100644
> index 0000000000..988d12a6c5
> --- /dev/null
> +++ b/hw/sensor/ina230.c
> @@ -0,0 +1,597 @@
> +/*
> + * Texas Instruments INA230 36V, 16-bit current/voltage/power monitor
> + * with an I2C interface.
> + *
> + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * The INA230 measures a differential shunt voltage and a bus voltage.
> + * Current and power are not measured directly: they are derived by the
> + * device from the measured shunt voltage.
> + *
> + * Limitations:
> + * - Conversion timing is instantaneous. A conversion runs synchronously 
> when an
> + *   input, the calibration or the configuration changes; the per-channel
> + *   conversion times (VSH_CT/VBUS_CT), the averaging count (AVG) and 
> continuous
> + *   re-conversion on a timer are not modelled. A read always returns the 
> value
> + *   derived from the current inputs and CVRF is set once a conversion has 
> run.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/log.h"
> +#include "qapi/error.h"
> +#include "qapi/visitor.h"
> +#include "qom/object.h"
> +#include "hw/sensor/ina230.h"
> +#include "hw/core/irq.h"
> +#include "hw/core/qdev-properties.h"
> +#include "hw/core/registerfields.h"
> +#include "hw/i2c/i2c.h"
> +#include "migration/vmstate.h"
> +#include "trace.h"
> +
> +#define INA230_REG_CONFIG       0x00
> +#define INA230_REG_SHUNT        0x01
> +#define INA230_REG_BUS          0x02
> +#define INA230_REG_POWER        0x03
> +#define INA230_REG_CURRENT      0x04
> +#define INA230_REG_CALIBRATION  0x05
> +#define INA230_REG_MASK_ENABLE  0x06
> +#define INA230_REG_ALERT_LIMIT  0x07
> +#define INA230_REG_DIE_ID       0xFF
> +
> +FIELD(INA230_CONFIG, MODE,     0, 3)
> +FIELD(INA230_CONFIG, VSH_CT,   3, 3)
> +FIELD(INA230_CONFIG, VBUS_CT,  6, 3)
> +FIELD(INA230_CONFIG, AVG,      9, 3)
> +FIELD(INA230_CONFIG, RST,     15, 1)
> +
> +#define INA230_CONFIG_POR       0x4127
> +
> +FIELD(INA230_MASK_ENABLE, LEN,   0, 1)  /* alert latch enable */
> +FIELD(INA230_MASK_ENABLE, APOL,  1, 1)  /* alert polarity */
> +FIELD(INA230_MASK_ENABLE, OVF,   2, 1)  /* math overflow flag (status) */
> +FIELD(INA230_MASK_ENABLE, CVRF,  3, 1)  /* conversion ready flag (status) */
> +FIELD(INA230_MASK_ENABLE, AFF,   4, 1)  /* alert function flag (status) */
> +FIELD(INA230_MASK_ENABLE, CNVR, 10, 1)  /* conversion ready alert */
> +FIELD(INA230_MASK_ENABLE, POL,  11, 1)  /* power over-limit */
> +FIELD(INA230_MASK_ENABLE, BUL,  12, 1)  /* bus under-limit */
> +FIELD(INA230_MASK_ENABLE, BOL,  13, 1)  /* bus over-limit */
> +FIELD(INA230_MASK_ENABLE, SUL,  14, 1)  /* shunt under-limit */
> +FIELD(INA230_MASK_ENABLE, SOL,  15, 1)  /* shunt over-limit */
> +
> +/* Writable Mask/Enable bits: alert-function selects + APOL + LEN */
> +#define INA230_ME_WMASK         (R_INA230_MASK_ENABLE_SOL_MASK | \
> +                                 R_INA230_MASK_ENABLE_SUL_MASK | \
> +                                 R_INA230_MASK_ENABLE_BOL_MASK | \
> +                                 R_INA230_MASK_ENABLE_BUL_MASK | \
> +                                 R_INA230_MASK_ENABLE_POL_MASK | \
> +                                 R_INA230_MASK_ENABLE_CNVR_MASK | \
> +                                 R_INA230_MASK_ENABLE_APOL_MASK | \
> +                                 R_INA230_MASK_ENABLE_LEN_MASK)
> +/* Read-only status bits: AFF, CVRF, OVF */
> +#define INA230_ME_STATUS_MASK   (R_INA230_MASK_ENABLE_AFF_MASK | \
> +                                 R_INA230_MASK_ENABLE_CVRF_MASK | \
> +                                 R_INA230_MASK_ENABLE_OVF_MASK)
> +
> +FIELD(INA230_CALIBRATION, FS, 0, 15)
> +#define INA230_CAL_MASK         R_INA230_CALIBRATION_FS_MASK
> +
> +#define INA230_SHUNT_LSB_NV     2500     /* 2.5 uV per LSB */
> +#define INA230_BUS_LSB_UV       1250     /* 1.25 mV per LSB */
> +#define INA230_SHUNT_MIN_NV     (-81920000)  /* -32768 * 2.5 uV */
> +#define INA230_SHUNT_MAX_NV     81917500     /*  32767 * 2.5 uV */
> +/*
> + * A real rail should not drive the pin past 36 V, so bus codes above 0x7080
> + * are intentionally unreachable in the model.
> + */
> +#define INA230_BUS_MAX_UV       36000000     /* bus pin rated 0..36 V */
> +
> +#define INA230_CURRENT_DIV      2048
> +#define INA230_POWER_DIV        20000
> +
> +#define INA230_DIE_ID_VAL       0x2310
> +
> +OBJECT_DECLARE_SIMPLE_TYPE(INA230State, INA230)
> +
> +struct INA230State {
> +    I2CSlave parent_obj;
> +
> +    qemu_irq alert; /* ALERT open-drain output */
> +
> +    /* I2C transfer state */
> +    uint8_t ptr;      /* register pointer */
> +    uint8_t len;      /* bytes received in current transfer */
> +    uint8_t wr_hi;    /* pending write MSB */
> +    uint8_t rx_byte;  /* 0: return MSB next, 1: return LSB next */
> +    uint16_t rx_word; /* value latched at the start of a read */
> +
> +    /* Register file */
> +    uint16_t config;
> +    uint16_t calibration;
> +    uint16_t mask_enable;
> +    uint16_t alert_limit;
> +
> +    /* Derived measurement registers */
> +    uint16_t shunt_reg;
> +    uint16_t bus_reg;
> +    uint16_t current_reg;
> +    uint16_t power_reg;
> +
> +    /* Injected physical inputs */
> +    int32_t shunt_nv;  /* shunt voltage, nanovolts (signed: bidirectional) */
> +    uint32_t bus_uv;   /* bus voltage, microvolts (never negative) */
> +
> +    /*
> +     * In latch mode AFF and the ALERT pin stay asserted until the
> +     * Configuration register is written or the Mask/Enable register is read
> +     * (datasheet 7.3.3).
> +     */
> +    bool alert_latched;
> +
> +    char *description;
> +};
> +
> +/*
> + * Evaluate the selected alert function. @shunt_fresh and @bus_fresh tell 
> which
> + * of the measurement registers hold a fresh value; a function fed by a 
> channel
> + * that did not convert cannot assert.
> + */
> +static bool ina230_limit_alert(const INA230State *s, bool shunt_fresh,
> +                               bool bus_fresh)
> +{
> +    uint16_t me = s->mask_enable;
> +    int16_t shunt = (int16_t)s->shunt_reg;
> +    int16_t limit_s = (int16_t)s->alert_limit;
> +    uint16_t limit_u = s->alert_limit;
> +
> +    if (FIELD_EX16(me, INA230_MASK_ENABLE, SOL)) {
> +        return shunt_fresh && shunt > limit_s;
> +    }
> +    if (FIELD_EX16(me, INA230_MASK_ENABLE, SUL)) {
> +        return shunt_fresh && shunt < limit_s;
> +    }
> +    if (FIELD_EX16(me, INA230_MASK_ENABLE, BOL)) {
> +        return bus_fresh && s->bus_reg > limit_u;
> +    }
> +    if (FIELD_EX16(me, INA230_MASK_ENABLE, BUL)) {
> +        return bus_fresh && s->bus_reg < limit_u;
> +    }
> +    if (FIELD_EX16(me, INA230_MASK_ENABLE, POL)) {
> +        return bus_fresh && s->power_reg > limit_u;
> +    }
> +    return false;
> +}
> +
> +static void ina230_refresh_alert(INA230State *s)
> +{
> +    bool latch = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, LEN);
> +    bool aff = latch ? s->alert_latched : ina230_limit_alert(s, true, true);
> +    bool cnvr;
> +    bool active;
> +    int level;
> +
> +    s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, AFF, 
> aff);
> +
> +    cnvr = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, CNVR) &&
> +           FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, CVRF);
> +
> +    active = aff || cnvr;
> +
> +    level = FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, APOL) ?
> +            active : !active;
> +
> +    trace_ina230_alert(s->description, level);
> +    qemu_set_irq(s->alert, level);
> +}
> +
> +static void ina230_sample_alert(INA230State *s, bool shunt_fresh,
> +                                bool bus_fresh)
> +{
> +    if (FIELD_EX16(s->mask_enable, INA230_MASK_ENABLE, LEN) &&
> +        ina230_limit_alert(s, shunt_fresh, bus_fresh)) {
> +        s->alert_latched = true;
> +    }
> +    ina230_refresh_alert(s);
> +}
> +
> +static void ina230_convert(INA230State *s)
> +{
> +    uint8_t mode = FIELD_EX16(s->config, INA230_CONFIG, MODE);
> +    bool shunt_en = mode & 0x1;
> +    bool bus_en = mode & 0x2;
> +    int64_t sr, br, cur, pwr;
> +    bool ovf = false;
> +
> +    if (!shunt_en && !bus_en) {
> +        return;
> +    }
> +
> +    if (shunt_en) {
> +        sr = s->shunt_nv / INA230_SHUNT_LSB_NV;
> +        sr = MAX(INT16_MIN, MIN(INT16_MAX, sr));
> +        s->shunt_reg = (uint16_t)(int16_t)sr;
> +
> +        cur = (int64_t)(int16_t)s->shunt_reg * s->calibration /
> +              INA230_CURRENT_DIV;
> +        if (cur > INT16_MAX || cur < INT16_MIN) {
> +            ovf = true;
> +            cur = MAX(INT16_MIN, MIN(INT16_MAX, cur));
> +        }
> +        s->current_reg = (uint16_t)(int16_t)cur;
> +    }
> +
> +    if (bus_en) {
> +        br = s->bus_uv / INA230_BUS_LSB_UV;
> +        br = MAX(0, MIN(INT16_MAX, br));
> +        s->bus_reg = (uint16_t)br;
> +    }
> +
> +    /*
> +     * In bus-only mode the current register is retained from the last shunt
> +     * conversion rather than measured anew, but the power register is still
> +     * refreshed.
> +     */
> +    if (bus_en) {
> +        pwr = (int64_t)(int16_t)s->current_reg * s->bus_reg / 
> INA230_POWER_DIV;
> +        /* POWER is an unsigned, positive-only register (datasheet). */
> +        if (pwr < 0) {
> +            pwr = 0;
> +        } else if (pwr > UINT16_MAX) {
> +            ovf = true;
> +            pwr = UINT16_MAX;
> +        }
> +        s->power_reg = (uint16_t)pwr;
> +    }
> +
> +    /* Without a calibration the device reports neither current nor power. */
> +    if (s->calibration == 0) {
> +        s->current_reg = 0;
> +        s->power_reg = 0;
> +    }
> +
> +    s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, OVF, 
> ovf);
> +    s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE, CVRF, 1);
> +
> +    trace_ina230_convert(s->description, s->shunt_reg, s->bus_reg,
> +                         s->current_reg, s->power_reg);
> +
> +    ina230_sample_alert(s, shunt_en, bus_en);
> +}
> +
> +/*
> + * A physical input or the calibration changed.
> + * - in continuous mode the device keeps converting and immediately tracks
> + *   the new value
> + * - in a triggered mode it performs exactly one conversion per trigger
> + */
> +static void ina230_input_update(INA230State *s)
> +{
> +    if (FIELD_EX16(s->config, INA230_CONFIG, MODE) & 0x4) {
> +        ina230_convert(s);
> +    }
> +}
> +
> +static void ina230_reset_regs(INA230State *s)
> +{
> +    s->config = INA230_CONFIG_POR;
> +    s->calibration = 0;
> +    s->mask_enable = 0;
> +    s->alert_limit = 0;
> +    s->alert_latched = false;
> +
> +    s->ptr = 0;
> +    s->len = 0;
> +    s->wr_hi = 0;
> +    s->rx_byte = 0;
> +    s->rx_word = 0;
> +
> +    s->shunt_reg = 0;
> +    s->bus_reg = 0;
> +    s->current_reg = 0;
> +    s->power_reg = 0;
> +
> +    ina230_refresh_alert(s);
> +}
> +
> +static uint16_t ina230_read_reg(INA230State *s, uint8_t reg)
> +{
> +    uint16_t val;
> +
> +    switch (reg) {
> +    case INA230_REG_CONFIG:
> +        val = s->config;
> +        break;
> +    case INA230_REG_SHUNT:
> +        val = s->shunt_reg;
> +        break;
> +    case INA230_REG_BUS:
> +        val = s->bus_reg;
> +        break;
> +    case INA230_REG_POWER:
> +        val = s->power_reg;
> +        break;
> +    case INA230_REG_CURRENT:
> +        val = s->current_reg;
> +        break;
> +    case INA230_REG_CALIBRATION:
> +        val = s->calibration;
> +        break;
> +    case INA230_REG_MASK_ENABLE:
> +        val = s->mask_enable;
> +        s->mask_enable = FIELD_DP16(s->mask_enable, INA230_MASK_ENABLE,
> +                                    CVRF, 0);
> +        s->alert_latched = false;
> +        ina230_refresh_alert(s);
> +        break;
> +    case INA230_REG_ALERT_LIMIT:
> +        val = s->alert_limit;
> +        break;
> +    case INA230_REG_DIE_ID:
> +        val = INA230_DIE_ID_VAL;
> +        break;
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: %s: read from undefined register 0x%02x\n",
> +                      __func__, s->description, reg);
> +        val = 0;
> +        break;
> +    }
> +
> +    trace_ina230_read(s->description, reg, val);
> +    return val;
> +}
> +
> +static void ina230_write_reg(INA230State *s, uint8_t reg, uint16_t val)
> +{
> +    trace_ina230_write(s->description, reg, val);
> +
> +    switch (reg) {
> +    case INA230_REG_CONFIG:
> +        if (val & R_INA230_CONFIG_RST_MASK) {
> +            trace_ina230_reset(s->description, "reg");
> +            ina230_reset_regs(s);
> +            break;
> +        }
> +        s->config = val & ~R_INA230_CONFIG_RST_MASK;
> +        /*
> +         * The write itself releases a latched alert, so the release reaches
> +         * the ALERT pin before the conversion it triggers can relatch it.
> +         */
> +        s->alert_latched = false;
> +        ina230_refresh_alert(s);
> +        ina230_convert(s);
> +        break;
> +    case INA230_REG_CALIBRATION:
> +        s->calibration = val & INA230_CAL_MASK;
> +        ina230_input_update(s);
> +        break;
> +    case INA230_REG_MASK_ENABLE:
> +        s->mask_enable = (val & INA230_ME_WMASK) |
> +                         (s->mask_enable & INA230_ME_STATUS_MASK);
> +        ina230_refresh_alert(s);
> +        break;
> +    case INA230_REG_ALERT_LIMIT:
> +        s->alert_limit = val;
> +        ina230_refresh_alert(s);
> +        break;
> +    case INA230_REG_SHUNT:
> +    case INA230_REG_BUS:
> +    case INA230_REG_POWER:
> +    case INA230_REG_CURRENT:
> +    case INA230_REG_DIE_ID:
> +        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 ina230_recv(I2CSlave *i2c)
> +{
> +    INA230State *s = INA230(i2c);
> +    uint8_t byte;
> +
> +    if (s->rx_byte == 0) {
> +        s->rx_word = ina230_read_reg(s, s->ptr);
> +        byte = s->rx_word >> 8;
> +        s->rx_byte = 1;
> +    } else {
> +        byte = s->rx_word & 0xff;
> +        s->rx_byte = 0;
> +    }
> +
> +    return byte;
> +}
> +
> +static int ina230_send(I2CSlave *i2c, uint8_t data)
> +{
> +    INA230State *s = INA230(i2c);
> +
> +    if (s->len == 0) {
> +        s->ptr = data;
> +    } else if ((s->len & 1) == 1) {
> +        s->wr_hi = data;
> +    } else {
> +        ina230_write_reg(s, s->ptr, ((uint16_t)s->wr_hi << 8) | data);
> +    }
> +    s->len++;
> +
> +    return 0;
> +}
> +
> +static int ina230_event(I2CSlave *i2c, enum i2c_event event)
> +{
> +    INA230State *s = INA230(i2c);
> +
> +    s->len = 0;
> +    s->rx_byte = 0;
> +
> +    return 0;
> +}
> +
> +static void ina230_get_shunt(Object *obj, Visitor *v, const char *name,
> +                             void *opaque, Error **errp)
> +{
> +    INA230State *s = INA230(obj);
> +    int64_t value = s->shunt_nv;
> +
> +    visit_type_int(v, name, &value, errp);
> +}
> +
> +static void ina230_set_shunt(Object *obj, Visitor *v, const char *name,
> +                             void *opaque, Error **errp)
> +{
> +    INA230State *s = INA230(obj);
> +    int64_t value;
> +
> +    if (!visit_type_int(v, name, &value, errp)) {
> +        return;
> +    }
> +
> +    if (value < INA230_SHUNT_MIN_NV || value > INA230_SHUNT_MAX_NV) {
> +        error_setg(errp,
> +                   "%s: shunt-voltage %" PRId64 " out of range (%d..%d nV)",
> +                   s->description, value,
> +                   INA230_SHUNT_MIN_NV, INA230_SHUNT_MAX_NV);
> +        return;
> +    }
> +
> +    s->shunt_nv = (int32_t)value;
> +    ina230_input_update(s);
> +}
> +
> +static void ina230_get_bus(Object *obj, Visitor *v, const char *name,
> +                           void *opaque, Error **errp)
> +{
> +    INA230State *s = INA230(obj);
> +    int64_t value = s->bus_uv;
> +
> +    visit_type_int(v, name, &value, errp);
> +}
> +
> +static void ina230_set_bus(Object *obj, Visitor *v, const char *name,
> +                           void *opaque, Error **errp)
> +{
> +    INA230State *s = INA230(obj);
> +    int64_t value;
> +
> +    if (!visit_type_int(v, name, &value, errp)) {
> +        return;
> +    }
> +
> +    if (value < 0 || value > INA230_BUS_MAX_UV) {
> +        error_setg(errp,
> +                   "%s: bus-voltage %" PRId64 " out of range (0..%d uV)",
> +                   s->description, value, INA230_BUS_MAX_UV);
> +        return;
> +    }
> +
> +    s->bus_uv = (uint32_t)value;
> +    ina230_input_update(s);
> +}
> +
> +static void ina230_reset_hold(Object *obj, ResetType type)
> +{
> +    INA230State *s = INA230(obj);
> +
> +    trace_ina230_reset(s->description, "hw");
> +    ina230_reset_regs(s);
> +}
> +
> +static int ina230_post_load(void *opaque, int version_id)
> +{
> +    INA230State *s = opaque;
> +
> +    ina230_refresh_alert(s);
> +
> +    return 0;
> +}
> +
> +static const VMStateDescription ina230_vmstate = {
> +    .name = "INA230",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .post_load = ina230_post_load,
> +    .fields = (const VMStateField[]) {
> +        VMSTATE_UINT8(ptr, INA230State),
> +        VMSTATE_UINT8(len, INA230State),
> +        VMSTATE_UINT8(wr_hi, INA230State),
> +        VMSTATE_UINT8(rx_byte, INA230State),
> +        VMSTATE_UINT16(rx_word, INA230State),
> +        VMSTATE_UINT16(config, INA230State),
> +        VMSTATE_UINT16(calibration, INA230State),
> +        VMSTATE_UINT16(mask_enable, INA230State),
> +        VMSTATE_UINT16(alert_limit, INA230State),
> +        VMSTATE_UINT16(shunt_reg, INA230State),
> +        VMSTATE_UINT16(bus_reg, INA230State),
> +        VMSTATE_UINT16(current_reg, INA230State),
> +        VMSTATE_UINT16(power_reg, INA230State),
> +        VMSTATE_INT32(shunt_nv, INA230State),
> +        VMSTATE_UINT32(bus_uv, INA230State),
> +        VMSTATE_BOOL(alert_latched, INA230State),
> +        VMSTATE_I2C_SLAVE(parent_obj, INA230State),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static void ina230_initfn(Object *obj)
> +{
> +    object_property_add(obj, "shunt-voltage", "int", ina230_get_shunt,
> +                        ina230_set_shunt, NULL, NULL);
> +    object_property_set_description(obj, "shunt-voltage",
> +                                    "Injected shunt (differential) voltage, "
> +                                    "in nanovolts");
> +    object_property_add(obj, "bus-voltage", "int", ina230_get_bus,
> +                        ina230_set_bus, NULL, NULL);
> +    object_property_set_description(obj, "bus-voltage",
> +                                    "Injected bus voltage, in microvolts");
> +}
> +
> +static void ina230_realize(DeviceState *dev, Error **errp)
> +{
> +    INA230State *s = INA230(dev);
> +
> +    if (!s->description) {
> +        s->description = g_strdup(object_get_typename(OBJECT(dev)));
> +    }
> +
> +    qdev_init_gpio_out(dev, &s->alert, 1);
> +}
> +
> +static const Property ina230_properties[] = {
> +    DEFINE_PROP_STRING("description", INA230State, description),
> +};
> +
> +static void ina230_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 = ina230_event;
> +    ic->recv = ina230_recv;
> +    ic->send = ina230_send;
> +    dc->realize = ina230_realize;
> +    rc->phases.hold = ina230_reset_hold;
> +    dc->vmsd = &ina230_vmstate;
> +    device_class_set_props(dc, ina230_properties);
> +}
> +
> +static const TypeInfo ina230_types[] = {
> +    {
> +        .name          = TYPE_INA230,
> +        .parent        = TYPE_I2C_SLAVE,
> +        .instance_init = ina230_initfn,
> +        .instance_size = sizeof(INA230State),
> +        .class_init    = ina230_class_init,
> +    },
> +};
> +
> +DEFINE_TYPES(ina230_types)
> diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
> index fe36c9ef91..b81f001fdf 100644
> --- a/hw/sensor/meson.build
> +++ b/hw/sensor/meson.build
> @@ -1,4 +1,5 @@
>  system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
> +system_ss.add(when: 'CONFIG_INA230', if_true: files('ina230.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 5a3630f7bb..f809b6b506 100644
> --- a/hw/sensor/trace-events
> +++ b/hw/sensor/trace-events
> @@ -8,6 +8,13 @@ adc128d818_convert(const char *id, uint8_t channel, uint16_t 
> value) "%s ch %u va
>  adc128d818_irq(const char *id, bool level) "%s level %u"
>  adc128d818_reset(const char *id, const char *source) "%s %s"
>  
> +# ina230.c
> +ina230_read(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 
> 0x%04x"
> +ina230_write(const char *id, uint8_t reg, uint16_t value) "%s reg 0x%02x val 
> 0x%04x"
> +ina230_convert(const char *id, uint16_t shunt, uint16_t bus, uint16_t 
> current, uint16_t power) "%s shunt 0x%04x bus 0x%04x current 0x%04x power 
> 0x%04x"
> +ina230_alert(const char *id, bool level) "%s level %u"
> +ina230_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/ina230.h b/include/hw/sensor/ina230.h
> new file mode 100644
> index 0000000000..4394727d47
> --- /dev/null
> +++ b/include/hw/sensor/ina230.h
> @@ -0,0 +1,14 @@
> +/*
> + * Texas Instruments INA230 current/voltage/power monitor with I2C interface
> + *
> + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_SENSOR_INA230_H
> +#define HW_SENSOR_INA230_H
> +
> +#define TYPE_INA230 "ina230"
> +
> +#endif
> 


Reply via email to