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 <[email protected]> --- 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
