[PATCH v1 3/5] hw/sensor: Add UCD90320 model

Mikail Sadic posted 5 patches 1 month, 4 weeks ago
Maintainers: "Cédric Le Goater" <clg@kaod.org>, Peter Maydell <peter.maydell@linaro.org>, Steven Lee <steven_lee@aspeedtech.com>, Troy Lee <leetroy@gmail.com>, Jamin Lin <jamin_lin@aspeedtech.com>, Kane Chen <kane_chen@aspeedtech.com>, Andrew Jeffery <andrew@codeconstruct.com.au>, Joel Stanley <joel@jms.id.au>, Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>, Ninad Palsule <ninad@linux.ibm.com>, Paolo Bonzini <pbonzini@redhat.com>, "Philippe Mathieu-Daudé" <philmd@mailo.com>, Titus Rwantare <titusr@google.com>, Jeuk Kim <jeuk20.kim@samsung.com>
There is a newer version of this series
[PATCH v1 3/5] hw/sensor: Add UCD90320 model
Posted by Mikail Sadic 1 month, 4 weeks ago
Add a PMBus device model for the TI-UCD90320 24-rail power
sequencer. Configures 24 pages with linear vout mode and responds to
the vendor-specific UCD9000_DEVICE_ID, NUM_PAGES, MONITOR_CONFIG, and
MFR_STATUS commands, allowing the Linux ucd9000 driver to bind and
create hwmon sysfs entries.

Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
---
 MAINTAINERS             |   1 +
 docs/specs/index.rst    |   1 +
 docs/specs/ucd90320.rst |  36 ++++++++++
 hw/i2c/aspeed_i2c.c     |   7 ++
 hw/sensor/ucd90320.c    | 144 ++++++++++++++++++++++++++++++++++++++++
 hw/arm/Kconfig          |   1 +
 hw/sensor/Kconfig       |   4 ++
 hw/sensor/meson.build   |   1 +
 8 files changed, 195 insertions(+)
 create mode 100644 docs/specs/ucd90320.rst
 create mode 100644 hw/sensor/ucd90320.c

diff --git a/MAINTAINERS b/MAINTAINERS
index ecb8cfdc41..b2861dfc79 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4059,6 +4059,7 @@ F: hw/i2c/pmbus_device.c
 F: hw/sensor/adm1272.c
 F: hw/sensor/isl_pmbus_vr.c
 F: hw/sensor/max34451.c
+F: hw/sensor/ucd90320.c
 F: include/hw/i2c/pmbus_device.h
 F: include/hw/sensor/isl_pmbus_vr.h
 F: tests/qtest/adm1272-test.c
diff --git a/docs/specs/index.rst b/docs/specs/index.rst
index b7909a108a..4de65e2fdf 100644
--- a/docs/specs/index.rst
+++ b/docs/specs/index.rst
@@ -39,4 +39,5 @@ guest hardware that is specific to QEMU.
    riscv-iommu
    riscv-aia
    aspeed-intc
+   ucd90320
    iommu-testdev
diff --git a/docs/specs/ucd90320.rst b/docs/specs/ucd90320.rst
new file mode 100644
index 0000000000..fdc0878c75
--- /dev/null
+++ b/docs/specs/ucd90320.rst
@@ -0,0 +1,36 @@
+Texas Instruments UCD90320 Power Sequencer
+==========================================
+
+The UCD90320 is a 24-rail PMBus power sequencer. QEMU models it as a PMBus
+device (``"ucd90320"``) so the Linux ``ucd9000`` driver can bind and create
+``hwmon`` sysfs entries.
+
+The model configures 24 PMBus pages (one per rail) with ``VOUT_MODE = 0x00``
+(linear, exponent 0). ``READ_VOUT`` and ``MFR_STATUS`` return zero on all
+pages.
+
+Vendor-specific commands handled:
+
++------+---------------------------+--------------------------------------------+
+| Code | Name                      | Response                                   |
++======+===========================+============================================+
+| 0xD5 | ``UCD9000_MONITOR_CONFIG``| Block: one byte ``0x00``                   |
++------+---------------------------+--------------------------------------------+
+| 0xD6 | ``UCD9000_NUM_PAGES``     | Byte: ``24``                               |
++------+---------------------------+--------------------------------------------+
+| 0xF3 | ``UCD9000_MFR_STATUS``    | Block: four bytes ``0x00 0x00 0x00 0x00``  |
++------+---------------------------+--------------------------------------------+
+| 0xFD | ``UCD9000_DEVICE_ID``     | Block: ASCII string ``"UCD90320"``         |
++------+---------------------------+--------------------------------------------+
+
+``DEVICE_ID`` uses the vendor-specific code ``0xFD`` rather than the standard
+``PMBUS_IC_DEVICE_ID`` (``0xAD``), matching the Linux ``ucd9000`` probe
+sequence.
+
+The UCD90320 is instantiated automatically in the ``huygens-bmc`` machine on
+I2C bus 5 at address ``0x11``. To instantiate on a different Aspeed machine:
+
+.. code-block:: c
+
+  i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 5),
+                          "ucd90320", 0x11);
diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c
index 27afcaecee..facb54d27e 100644
--- a/hw/i2c/aspeed_i2c.c
+++ b/hw/i2c/aspeed_i2c.c
@@ -365,6 +365,7 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
     uint32_t reg_pool_ctrl = aspeed_i2c_bus_pool_ctrl_offset(bus);
     uint32_t reg_byte_buf = aspeed_i2c_bus_byte_buf_offset(bus);
     uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
+    bool first_dma_byte;
     int pool_rx_count = SHARED_ARRAY_FIELD_EX32(bus->regs, reg_pool_ctrl,
                                                 RX_SIZE) + 1;
 
@@ -391,6 +392,7 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
         }
 
         aspeed_i2c_set_rx_dma_dram_offset(bus);
+        first_dma_byte = true;
         while (bus->regs[reg_dma_len]) {
             MemTxResult result;
 
@@ -407,6 +409,11 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
                 return;
             }
 
+            /* Mirror first byte to reg_byte_buf for I2C_M_RECV_LEN. */
+            if (first_dma_byte) {
+                SHARED_ARRAY_FIELD_DP32(bus->regs, reg_byte_buf, RX_BUF, data);
+                first_dma_byte = false;
+            }
             bus->dma_dram_offset++;
             bus->regs[reg_dma_len]--;
             /* In new mode, keep track of how many bytes we RXed */
diff --git a/hw/sensor/ucd90320.c b/hw/sensor/ucd90320.c
new file mode 100644
index 0000000000..f3b2a5d777
--- /dev/null
+++ b/hw/sensor/ucd90320.c
@@ -0,0 +1,144 @@
+/*
+ * Texas Instruments UCD90320 24-Rail PMBus Power Sequencer
+ *
+ * Copyright 2026 IBM Corp.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/pmbus_device.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define TYPE_UCD90320 "ucd90320"
+
+/* UCD90320 has 24 sequenced power-rail pages */
+#define UCD90320_NUM_PAGES 24
+
+/* Vendor-specific command codes (not in the standard PMBus register table) */
+#define UCD9000_MONITOR_CONFIG  0xd5
+#define UCD9000_NUM_PAGES       0xd6
+#define UCD9000_MFR_STATUS      0xf3
+#define UCD9000_DEVICE_ID       0xfd
+
+typedef struct UCD90320State {
+    PMBusDevice parent;
+} UCD90320State;
+
+static void ucd90320_send_block(PMBusDevice *pmdev,
+                                const uint8_t *data, uint8_t len)
+{
+    int i;
+
+    pmdev->out_buf[len + pmdev->out_buf_len] = len;
+    for (i = len - 1; i >= 0; i--) {
+        pmdev->out_buf[i + pmdev->out_buf_len] = data[len - 1 - i];
+    }
+    pmdev->out_buf_len += len + 1;
+}
+
+static uint8_t ucd90320_read_byte(PMBusDevice *pmdev)
+{
+    switch (pmdev->code) {
+    case UCD9000_DEVICE_ID: {
+        static const uint8_t id[] = "UCD90320";
+        ucd90320_send_block(pmdev, id, sizeof(id) - 1);
+        pmbus_idle(pmdev);
+        return 0;
+    }
+    case UCD9000_NUM_PAGES:
+        pmbus_send8(pmdev, UCD90320_NUM_PAGES);
+        pmbus_idle(pmdev);
+        return 0;
+
+    case UCD9000_MONITOR_CONFIG: {
+        static const uint8_t cfg[] = { 0x00 };
+        ucd90320_send_block(pmdev, cfg, sizeof(cfg));
+        pmbus_idle(pmdev);
+        return 0;
+    }
+    case UCD9000_MFR_STATUS: {
+        static const uint8_t status[] = { 0x00, 0x00, 0x00, 0x00 };
+        ucd90320_send_block(pmdev, status, sizeof(status));
+        pmbus_idle(pmdev);
+        return 0;
+    }
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: reading from unsupported register: 0x%02x\n",
+                      __func__, pmdev->code);
+        break;
+    }
+    return 0xFF;
+}
+
+static int ucd90320_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+                               uint8_t len)
+{
+    if (len == 0) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+        return -1;
+    }
+
+    pmdev->code = buf[0];
+
+    if (len == 1) {
+        return 0;
+    }
+
+    return 0;
+}
+
+static void ucd90320_exit_reset(Object *obj, ResetType type)
+{
+    PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+
+    pmdev->capability = 0x20; /* PEC supported */
+
+    for (int i = 0; i < UCD90320_NUM_PAGES; i++) {
+        pmdev->pages[i].operation     = 0x80; /* on */
+        pmdev->pages[i].on_off_config = 0x1a;
+        pmdev->pages[i].vout_mode     = 0x00; /* linear mode, exponent=0 */
+        pmdev->pages[i].read_vout     = 0;    /* rails off, pgood=0 */
+    }
+}
+
+static void ucd90320_init(Object *obj)
+{
+    PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+    uint64_t flags = PB_HAS_VOUT | PB_HAS_VOUT_MODE |
+                     PB_HAS_STATUS_MFR_SPECIFIC;
+
+    for (int i = 0; i < UCD90320_NUM_PAGES; i++) {
+        pmbus_page_config(pmdev, i, flags);
+    }
+}
+
+static void ucd90320_class_init(ObjectClass *klass, const void *data)
+{
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
+
+    dc->desc = "Texas Instruments UCD90320 24-Rail Power Sequencer";
+    k->write_data = ucd90320_write_data;
+    k->receive_byte = ucd90320_read_byte;
+    k->device_num_pages = UCD90320_NUM_PAGES;
+    rc->phases.exit = ucd90320_exit_reset;
+}
+
+static const TypeInfo ucd90320_info = {
+    .name          = TYPE_UCD90320,
+    .parent        = TYPE_PMBUS_DEVICE,
+    .instance_size = sizeof(UCD90320State),
+    .instance_init = ucd90320_init,
+    .class_init    = ucd90320_class_init,
+};
+
+static void ucd90320_register_types(void)
+{
+    type_register_static(&ucd90320_info);
+}
+
+type_init(ucd90320_register_types)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 82e0bc2e70..ac44740547 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -555,6 +555,7 @@ config ASPEED_SOC
     select LED
     select PMBUS
     select MAX31785
+    select UCD90320
     select FSI_APB2OPB_ASPEED
     select AT24C
     select PCI_EXPRESS
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index bc6331b4ab..135ffa7d5d 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -43,3 +43,7 @@ config ISL_PMBUS_VR
 config MAX31785
     bool
     depends on PMBUS
+
+config UCD90320
+    bool
+    depends on PMBUS
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index 420fdc3359..c0583161d3 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -8,3 +8,4 @@ system_ss.add(when: 'CONFIG_MAX34451', if_true: files('max34451.c'))
 system_ss.add(when: 'CONFIG_LSM303DLHC_MAG', if_true: files('lsm303dlhc_mag.c'))
 system_ss.add(when: 'CONFIG_ISL_PMBUS_VR', if_true: files('isl_pmbus_vr.c'))
 system_ss.add(when: 'CONFIG_MAX31785', if_true: files('max31785.c'))
+system_ss.add(when: 'CONFIG_UCD90320', if_true: files('ucd90320.c'))
-- 
2.53.0
Re: [PATCH v1 3/5] hw/sensor: Add UCD90320 model
Posted by Cédric Le Goater 1 month, 4 weeks ago
On 7/15/26 17:50, Mikail Sadic wrote:
> Add a PMBus device model for the TI-UCD90320 24-rail power
> sequencer. Configures 24 pages with linear vout mode and responds to
> the vendor-specific UCD9000_DEVICE_ID, NUM_PAGES, MONITOR_CONFIG, and
> MFR_STATUS commands, allowing the Linux ucd9000 driver to bind and
> create hwmon sysfs entries.
> 
> Signed-off-by: Mikail Sadic <mikail.sadic@ibm.com>
> ---
>   MAINTAINERS             |   1 +
>   docs/specs/index.rst    |   1 +
>   docs/specs/ucd90320.rst |  36 ++++++++++
>   hw/i2c/aspeed_i2c.c     |   7 ++
>   hw/sensor/ucd90320.c    | 144 ++++++++++++++++++++++++++++++++++++++++
>   hw/arm/Kconfig          |   1 +
>   hw/sensor/Kconfig       |   4 ++
>   hw/sensor/meson.build   |   1 +
>   8 files changed, 195 insertions(+)
>   create mode 100644 docs/specs/ucd90320.rst
>   create mode 100644 hw/sensor/ucd90320.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ecb8cfdc41..b2861dfc79 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -4059,6 +4059,7 @@ F: hw/i2c/pmbus_device.c
>   F: hw/sensor/adm1272.c
>   F: hw/sensor/isl_pmbus_vr.c
>   F: hw/sensor/max34451.c
> +F: hw/sensor/ucd90320.c
>   F: include/hw/i2c/pmbus_device.h
>   F: include/hw/sensor/isl_pmbus_vr.h
>   F: tests/qtest/adm1272-test.c
> diff --git a/docs/specs/index.rst b/docs/specs/index.rst
> index b7909a108a..4de65e2fdf 100644
> --- a/docs/specs/index.rst
> +++ b/docs/specs/index.rst
> @@ -39,4 +39,5 @@ guest hardware that is specific to QEMU.
>      riscv-iommu
>      riscv-aia
>      aspeed-intc
> +   ucd90320
>      iommu-testdev
> diff --git a/docs/specs/ucd90320.rst b/docs/specs/ucd90320.rst
> new file mode 100644
> index 0000000000..fdc0878c75
> --- /dev/null
> +++ b/docs/specs/ucd90320.rst
> @@ -0,0 +1,36 @@
> +Texas Instruments UCD90320 Power Sequencer
> +==========================================
> +
> +The UCD90320 is a 24-rail PMBus power sequencer. QEMU models it as a PMBus
> +device (``"ucd90320"``) so the Linux ``ucd9000`` driver can bind and create
> +``hwmon`` sysfs entries.
> +
> +The model configures 24 PMBus pages (one per rail) with ``VOUT_MODE = 0x00``
> +(linear, exponent 0). ``READ_VOUT`` and ``MFR_STATUS`` return zero on all
> +pages.
> +
> +Vendor-specific commands handled:
> +
> ++------+---------------------------+--------------------------------------------+
> +| Code | Name                      | Response                                   |
> ++======+===========================+============================================+
> +| 0xD5 | ``UCD9000_MONITOR_CONFIG``| Block: one byte ``0x00``                   |
> ++------+---------------------------+--------------------------------------------+
> +| 0xD6 | ``UCD9000_NUM_PAGES``     | Byte: ``24``                               |
> ++------+---------------------------+--------------------------------------------+
> +| 0xF3 | ``UCD9000_MFR_STATUS``    | Block: four bytes ``0x00 0x00 0x00 0x00``  |
> ++------+---------------------------+--------------------------------------------+
> +| 0xFD | ``UCD9000_DEVICE_ID``     | Block: ASCII string ``"UCD90320"``         |
> ++------+---------------------------+--------------------------------------------+
> +
> +``DEVICE_ID`` uses the vendor-specific code ``0xFD`` rather than the standard
> +``PMBUS_IC_DEVICE_ID`` (``0xAD``), matching the Linux ``ucd9000`` probe
> +sequence.
> +
> +The UCD90320 is instantiated automatically in the ``huygens-bmc`` machine on
> +I2C bus 5 at address ``0x11``. To instantiate on a different Aspeed machine:
> +
> +.. code-block:: c
> +
> +  i2c_slave_create_simple(aspeed_i2c_get_bus(&soc->i2c, 5),
> +                          "ucd90320", 0x11);
> diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c
> index 27afcaecee..facb54d27e 100644
> --- a/hw/i2c/aspeed_i2c.c
> +++ b/hw/i2c/aspeed_i2c.c
> @@ -365,6 +365,7 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
>       uint32_t reg_pool_ctrl = aspeed_i2c_bus_pool_ctrl_offset(bus);
>       uint32_t reg_byte_buf = aspeed_i2c_bus_byte_buf_offset(bus);
>       uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
> +    bool first_dma_byte;
>       int pool_rx_count = SHARED_ARRAY_FIELD_EX32(bus->regs, reg_pool_ctrl,
>                                                   RX_SIZE) + 1;
>   
> @@ -391,6 +392,7 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
>           }
>   
>           aspeed_i2c_set_rx_dma_dram_offset(bus);
> +        first_dma_byte = true;
>           while (bus->regs[reg_dma_len]) {
>               MemTxResult result;
>   
> @@ -407,6 +409,11 @@ static void aspeed_i2c_bus_recv(AspeedI2CBus *bus)
>                   return;
>               }
>   
> +            /* Mirror first byte to reg_byte_buf for I2C_M_RECV_LEN. */
> +            if (first_dma_byte) {
> +                SHARED_ARRAY_FIELD_DP32(bus->regs, reg_byte_buf, RX_BUF, data);
> +                first_dma_byte = false;
> +            }
>               bus->dma_dram_offset++;
>               bus->regs[reg_dma_len]--;
>               /* In new mode, keep track of how many bytes we RXed */


The I2C part belongs to another patch.


> diff --git a/hw/sensor/ucd90320.c b/hw/sensor/ucd90320.c
> new file mode 100644
> index 0000000000..f3b2a5d777
> --- /dev/null
> +++ b/hw/sensor/ucd90320.c
> @@ -0,0 +1,144 @@
> +/*
> + * Texas Instruments UCD90320 24-Rail PMBus Power Sequencer
> + *
> + * Copyright 2026 IBM Corp.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/i2c/pmbus_device.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +
> +#define TYPE_UCD90320 "ucd90320"
> +
> +/* UCD90320 has 24 sequenced power-rail pages */
> +#define UCD90320_NUM_PAGES 24
> +
> +/* Vendor-specific command codes (not in the standard PMBus register table) */
> +#define UCD9000_MONITOR_CONFIG  0xd5
> +#define UCD9000_NUM_PAGES       0xd6
> +#define UCD9000_MFR_STATUS      0xf3
> +#define UCD9000_DEVICE_ID       0xfd
> +
> +typedef struct UCD90320State {
> +    PMBusDevice parent;
> +} UCD90320State;
> +
> +static void ucd90320_send_block(PMBusDevice *pmdev,
> +                                const uint8_t *data, uint8_t len)
> +{
> +    int i;
> +
> +    pmdev->out_buf[len + pmdev->out_buf_len] = len;
> +    for (i = len - 1; i >= 0; i--) {
> +        pmdev->out_buf[i + pmdev->out_buf_len] = data[len - 1 - i];
> +    }
> +    pmdev->out_buf_len += len + 1;
> +}
> +
> +static uint8_t ucd90320_read_byte(PMBusDevice *pmdev)
> +{
> +    switch (pmdev->code) {
> +    case UCD9000_DEVICE_ID: {
> +        static const uint8_t id[] = "UCD90320";

this is hacky. use device constant reset values or class fields.
Same below.

> +        ucd90320_send_block(pmdev, id, sizeof(id) - 1);
> +        pmbus_idle(pmdev);
> +        return 0;
> +    }
> +    case UCD9000_NUM_PAGES:
> +        pmbus_send8(pmdev, UCD90320_NUM_PAGES);
> +        pmbus_idle(pmdev);
> +        return 0;
> +
> +    case UCD9000_MONITOR_CONFIG: {
> +        static const uint8_t cfg[] = { 0x00 };
> +        ucd90320_send_block(pmdev, cfg, sizeof(cfg));
> +        pmbus_idle(pmdev);
> +        return 0;
> +    }
> +    case UCD9000_MFR_STATUS: {
> +        static const uint8_t status[] = { 0x00, 0x00, 0x00, 0x00 };
> +        ucd90320_send_block(pmdev, status, sizeof(status));
> +        pmbus_idle(pmdev);
> +        return 0;
> +    }
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "%s: reading from unsupported register: 0x%02x\n",
> +                      __func__, pmdev->code);
> +        break;
> +    }
> +    return 0xFF;
> +}
> +
> +static int ucd90320_write_data(PMBusDevice *pmdev, const uint8_t *buf,
> +                               uint8_t len)
> +{
> +    if (len == 0) {
> +        qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
> +        return -1;
> +    }
> +
> +    pmdev->code = buf[0];
> +
> +    if (len == 1) {
> +        return 0;
> +    }
> +
> +    return 0;
> +}
> +
> +static void ucd90320_exit_reset(Object *obj, ResetType type)
> +{
> +    PMBusDevice *pmdev = PMBUS_DEVICE(obj);
> +
> +    pmdev->capability = 0x20; /* PEC supported */
> +
> +    for (int i = 0; i < UCD90320_NUM_PAGES; i++) {
> +        pmdev->pages[i].operation     = 0x80; /* on */
> +        pmdev->pages[i].on_off_config = 0x1a;
> +        pmdev->pages[i].vout_mode     = 0x00; /* linear mode, exponent=0 */
> +        pmdev->pages[i].read_vout     = 0;    /* rails off, pgood=0 */
> +    }
> +}
> +
> +static void ucd90320_init(Object *obj)
> +{
> +    PMBusDevice *pmdev = PMBUS_DEVICE(obj);
> +    uint64_t flags = PB_HAS_VOUT | PB_HAS_VOUT_MODE |
> +                     PB_HAS_STATUS_MFR_SPECIFIC;
> +
> +    for (int i = 0; i < UCD90320_NUM_PAGES; i++) {
> +        pmbus_page_config(pmdev, i, flags);
> +    }
> +}
> +
> +static void ucd90320_class_init(ObjectClass *klass, const void *data)
> +{
> +    ResettableClass *rc = RESETTABLE_CLASS(klass);
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
> +
> +    dc->desc = "Texas Instruments UCD90320 24-Rail Power Sequencer";
> +    k->write_data = ucd90320_write_data;
> +    k->receive_byte = ucd90320_read_byte;
> +    k->device_num_pages = UCD90320_NUM_PAGES;
> +    rc->phases.exit = ucd90320_exit_reset;
> +}
> +
> +static const TypeInfo ucd90320_info = {
> +    .name          = TYPE_UCD90320,
> +    .parent        = TYPE_PMBUS_DEVICE,
> +    .instance_size = sizeof(UCD90320State),
> +    .instance_init = ucd90320_init,
> +    .class_init    = ucd90320_class_init,
> +};
> +
> +static void ucd90320_register_types(void)
> +{
> +    type_register_static(&ucd90320_info);
> +}
> +
> +type_init(ucd90320_register_types)
> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index 82e0bc2e70..ac44740547 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -555,6 +555,7 @@ config ASPEED_SOC
>       select LED
>       select PMBUS
>       select MAX31785
> +    select UCD90320
>       select FSI_APB2OPB_ASPEED
>       select AT24C
>       select PCI_EXPRESS
> diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
> index bc6331b4ab..135ffa7d5d 100644
> --- a/hw/sensor/Kconfig
> +++ b/hw/sensor/Kconfig
> @@ -43,3 +43,7 @@ config ISL_PMBUS_VR
>   config MAX31785
>       bool
>       depends on PMBUS
> +
> +config UCD90320
> +    bool
> +    depends on PMBUS
> diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
> index 420fdc3359..c0583161d3 100644
> --- a/hw/sensor/meson.build
> +++ b/hw/sensor/meson.build
> @@ -8,3 +8,4 @@ system_ss.add(when: 'CONFIG_MAX34451', if_true: files('max34451.c'))
>   system_ss.add(when: 'CONFIG_LSM303DLHC_MAG', if_true: files('lsm303dlhc_mag.c'))
>   system_ss.add(when: 'CONFIG_ISL_PMBUS_VR', if_true: files('isl_pmbus_vr.c'))
>   system_ss.add(when: 'CONFIG_MAX31785', if_true: files('max31785.c'))
> +system_ss.add(when: 'CONFIG_UCD90320', if_true: files('ucd90320.c'))