qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH v3 1/5] hw/i2c: add support for PMBus


From: Titus Rwantare
Subject: Re: [PATCH v3 1/5] hw/i2c: add support for PMBus
Date: Tue, 18 May 2021 15:46:48 -0400

I can add it.

On Tue, 18 May 2021 at 15:37, Corey Minyard <cminyard@mvista.com> wrote:
>
> On Tue, May 18, 2021 at 11:45:23AM -0700, Titus Rwantare wrote:
> > QEMU has support for SMBus devices, and PMBus is a more specific
> > implementation of SMBus. The additions made in this commit makes it easier 
> > to
> > add new PMBus devices to QEMU.
>
> Why doesn't the PMBusDevice have a VMStateDescription?  It looks like
> at least some of those values can be updated.
>
> -corey
>
> >
> > https://pmbus.org/specification-archives/
> >
> > Reviewed-by: Joel Stanley <joel@jms.id.au>
> > Reviewed-by: Hao Wu <wuhaotsh@google.com>
> > Signed-off-by: Titus Rwantare <titusr@google.com>
> > ---
> >  include/hw/i2c/pmbus_device.h |  506 +++++++++++
> >  hw/i2c/pmbus_device.c         | 1596 +++++++++++++++++++++++++++++++++
> >  hw/arm/Kconfig                |    1 +
> >  hw/i2c/Kconfig                |    4 +
> >  hw/i2c/meson.build            |    1 +
> >  5 files changed, 2108 insertions(+)
> >  create mode 100644 include/hw/i2c/pmbus_device.h
> >  create mode 100644 hw/i2c/pmbus_device.c
> >
> > diff --git a/include/hw/i2c/pmbus_device.h b/include/hw/i2c/pmbus_device.h
> > new file mode 100644
> > index 0000000000..3940de1f32
> > --- /dev/null
> > +++ b/include/hw/i2c/pmbus_device.h
> > @@ -0,0 +1,506 @@
> > +/*
> > + * QEMU PMBus device emulation
> > + *
> > + * Copyright 2021 Google LLC
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-or-later
> > + */
> > +
> > +#ifndef HW_PMBUS_DEVICE_H
> > +#define HW_PMBUS_DEVICE_H
> > +
> > +#include "qemu/bitops.h"
> > +#include "hw/i2c/smbus_slave.h"
> > +
> > +enum pmbus_registers {
> > +    PMBUS_PAGE                      = 0x00, /* R/W byte */
> > +    PMBUS_OPERATION                 = 0x01, /* R/W byte */
> > +    PMBUS_ON_OFF_CONFIG             = 0x02, /* R/W byte */
> > +    PMBUS_CLEAR_FAULTS              = 0x03, /* Send Byte */
> > +    PMBUS_PHASE                     = 0x04, /* R/W byte */
> > +    PMBUS_PAGE_PLUS_WRITE           = 0x05, /* Block Write-only */
> > +    PMBUS_PAGE_PLUS_READ            = 0x06, /* Block Read-only */
> > +    PMBUS_WRITE_PROTECT             = 0x10, /* R/W byte */
> > +    PMBUS_STORE_DEFAULT_ALL         = 0x11, /* Send Byte */
> > +    PMBUS_RESTORE_DEFAULT_ALL       = 0x12, /* Send Byte */
> > +    PMBUS_STORE_DEFAULT_CODE        = 0x13, /* Write-only Byte */
> > +    PMBUS_RESTORE_DEFAULT_CODE      = 0x14, /* Write-only Byte */
> > +    PMBUS_STORE_USER_ALL            = 0x15, /* Send Byte */
> > +    PMBUS_RESTORE_USER_ALL          = 0x16, /* Send Byte */
> > +    PMBUS_STORE_USER_CODE           = 0x17, /* Write-only Byte */
> > +    PMBUS_RESTORE_USER_CODE         = 0x18, /* Write-only Byte */
> > +    PMBUS_CAPABILITY                = 0x19, /* Read-Only byte */
> > +    PMBUS_QUERY                     = 0x1A, /* Write-Only */
> > +    PMBUS_SMBALERT_MASK             = 0x1B, /* Block read, Word write */
> > +    PMBUS_VOUT_MODE                 = 0x20, /* R/W byte */
> > +    PMBUS_VOUT_COMMAND              = 0x21, /* R/W word */
> > +    PMBUS_VOUT_TRIM                 = 0x22, /* R/W word */
> > +    PMBUS_VOUT_CAL_OFFSET           = 0x23, /* R/W word */
> > +    PMBUS_VOUT_MAX                  = 0x24, /* R/W word */
> > +    PMBUS_VOUT_MARGIN_HIGH          = 0x25, /* R/W word */
> > +    PMBUS_VOUT_MARGIN_LOW           = 0x26, /* R/W word */
> > +    PMBUS_VOUT_TRANSITION_RATE      = 0x27, /* R/W word */
> > +    PMBUS_VOUT_DROOP                = 0x28, /* R/W word */
> > +    PMBUS_VOUT_SCALE_LOOP           = 0x29, /* R/W word */
> > +    PMBUS_VOUT_SCALE_MONITOR        = 0x2A, /* R/W word */
> > +    PMBUS_COEFFICIENTS              = 0x30, /* Read-only block 5 bytes */
> > +    PMBUS_POUT_MAX                  = 0x31, /* R/W word */
> > +    PMBUS_MAX_DUTY                  = 0x32, /* R/W word */
> > +    PMBUS_FREQUENCY_SWITCH          = 0x33, /* R/W word */
> > +    PMBUS_VIN_ON                    = 0x35, /* R/W word */
> > +    PMBUS_VIN_OFF                   = 0x36, /* R/W word */
> > +    PMBUS_INTERLEAVE                = 0x37, /* R/W word */
> > +    PMBUS_IOUT_CAL_GAIN             = 0x38, /* R/W word */
> > +    PMBUS_IOUT_CAL_OFFSET           = 0x39, /* R/W word */
> > +    PMBUS_FAN_CONFIG_1_2            = 0x3A, /* R/W byte */
> > +    PMBUS_FAN_COMMAND_1             = 0x3B, /* R/W word */
> > +    PMBUS_FAN_COMMAND_2             = 0x3C, /* R/W word */
> > +    PMBUS_FAN_CONFIG_3_4            = 0x3D, /* R/W byte */
> > +    PMBUS_FAN_COMMAND_3             = 0x3E, /* R/W word */
> > +    PMBUS_FAN_COMMAND_4             = 0x3F, /* R/W word */
> > +    PMBUS_VOUT_OV_FAULT_LIMIT       = 0x40, /* R/W word */
> > +    PMBUS_VOUT_OV_FAULT_RESPONSE    = 0x41, /* R/W byte */
> > +    PMBUS_VOUT_OV_WARN_LIMIT        = 0x42, /* R/W word */
> > +    PMBUS_VOUT_UV_WARN_LIMIT        = 0x43, /* R/W word */
> > +    PMBUS_VOUT_UV_FAULT_LIMIT       = 0x44, /* R/W word */
> > +    PMBUS_VOUT_UV_FAULT_RESPONSE    = 0x45, /* R/W byte */
> > +    PMBUS_IOUT_OC_FAULT_LIMIT       = 0x46, /* R/W word */
> > +    PMBUS_IOUT_OC_FAULT_RESPONSE    = 0x47, /* R/W byte */
> > +    PMBUS_IOUT_OC_LV_FAULT_LIMIT    = 0x48, /* R/W word */
> > +    PMBUS_IOUT_OC_LV_FAULT_RESPONSE = 0x49, /* R/W byte */
> > +    PMBUS_IOUT_OC_WARN_LIMIT        = 0x4A, /* R/W word */
> > +    PMBUS_IOUT_UC_FAULT_LIMIT       = 0x4B, /* R/W word */
> > +    PMBUS_IOUT_UC_FAULT_RESPONSE    = 0x4C, /* R/W byte */
> > +    PMBUS_OT_FAULT_LIMIT            = 0x4F, /* R/W word */
> > +    PMBUS_OT_FAULT_RESPONSE         = 0x50, /* R/W byte */
> > +    PMBUS_OT_WARN_LIMIT             = 0x51, /* R/W word */
> > +    PMBUS_UT_WARN_LIMIT             = 0x52, /* R/W word */
> > +    PMBUS_UT_FAULT_LIMIT            = 0x53, /* R/W word */
> > +    PMBUS_UT_FAULT_RESPONSE         = 0x54, /* R/W byte */
> > +    PMBUS_VIN_OV_FAULT_LIMIT        = 0x55, /* R/W word */
> > +    PMBUS_VIN_OV_FAULT_RESPONSE     = 0x56, /* R/W byte */
> > +    PMBUS_VIN_OV_WARN_LIMIT         = 0x57, /* R/W word */
> > +    PMBUS_VIN_UV_WARN_LIMIT         = 0x58, /* R/W word */
> > +    PMBUS_VIN_UV_FAULT_LIMIT        = 0x59, /* R/W word */
> > +    PMBUS_VIN_UV_FAULT_RESPONSE     = 0x5A, /* R/W byte */
> > +    PMBUS_IIN_OC_FAULT_LIMIT        = 0x5B, /* R/W word */
> > +    PMBUS_IIN_OC_FAULT_RESPONSE     = 0x5C, /* R/W byte */
> > +    PMBUS_IIN_OC_WARN_LIMIT         = 0x5D, /* R/W word */
> > +    PMBUS_POWER_GOOD_ON             = 0x5E, /* R/W word */
> > +    PMBUS_POWER_GOOD_OFF            = 0x5F, /* R/W word */
> > +    PMBUS_TON_DELAY                 = 0x60, /* R/W word */
> > +    PMBUS_TON_RISE                  = 0x61, /* R/W word */
> > +    PMBUS_TON_MAX_FAULT_LIMIT       = 0x62, /* R/W word */
> > +    PMBUS_TON_MAX_FAULT_RESPONSE    = 0x63, /* R/W byte */
> > +    PMBUS_TOFF_DELAY                = 0x64, /* R/W word */
> > +    PMBUS_TOFF_FALL                 = 0x65, /* R/W word */
> > +    PMBUS_TOFF_MAX_WARN_LIMIT       = 0x66, /* R/W word */
> > +    PMBUS_POUT_OP_FAULT_LIMIT       = 0x68, /* R/W word */
> > +    PMBUS_POUT_OP_FAULT_RESPONSE    = 0x69, /* R/W byte */
> > +    PMBUS_POUT_OP_WARN_LIMIT        = 0x6A, /* R/W word */
> > +    PMBUS_PIN_OP_WARN_LIMIT         = 0x6B, /* R/W word */
> > +    PMBUS_STATUS_BYTE               = 0x78, /* R/W byte */
> > +    PMBUS_STATUS_WORD               = 0x79, /* R/W word */
> > +    PMBUS_STATUS_VOUT               = 0x7A, /* R/W byte */
> > +    PMBUS_STATUS_IOUT               = 0x7B, /* R/W byte */
> > +    PMBUS_STATUS_INPUT              = 0x7C, /* R/W byte */
> > +    PMBUS_STATUS_TEMPERATURE        = 0x7D, /* R/W byte */
> > +    PMBUS_STATUS_CML                = 0x7E, /* R/W byte */
> > +    PMBUS_STATUS_OTHER              = 0x7F, /* R/W byte */
> > +    PMBUS_STATUS_MFR_SPECIFIC       = 0x80, /* R/W byte */
> > +    PMBUS_STATUS_FANS_1_2           = 0x81, /* R/W byte */
> > +    PMBUS_STATUS_FANS_3_4           = 0x82, /* R/W byte */
> > +    PMBUS_READ_EIN                  = 0x86, /* Read-Only block 5 bytes */
> > +    PMBUS_READ_EOUT                 = 0x87, /* Read-Only block 5 bytes */
> > +    PMBUS_READ_VIN                  = 0x88, /* Read-Only word */
> > +    PMBUS_READ_IIN                  = 0x89, /* Read-Only word */
> > +    PMBUS_READ_VCAP                 = 0x8A, /* Read-Only word */
> > +    PMBUS_READ_VOUT                 = 0x8B, /* Read-Only word */
> > +    PMBUS_READ_IOUT                 = 0x8C, /* Read-Only word */
> > +    PMBUS_READ_TEMPERATURE_1        = 0x8D, /* Read-Only word */
> > +    PMBUS_READ_TEMPERATURE_2        = 0x8E, /* Read-Only word */
> > +    PMBUS_READ_TEMPERATURE_3        = 0x8F, /* Read-Only word */
> > +    PMBUS_READ_FAN_SPEED_1          = 0x90, /* Read-Only word */
> > +    PMBUS_READ_FAN_SPEED_2          = 0x91, /* Read-Only word */
> > +    PMBUS_READ_FAN_SPEED_3          = 0x92, /* Read-Only word */
> > +    PMBUS_READ_FAN_SPEED_4          = 0x93, /* Read-Only word */
> > +    PMBUS_READ_DUTY_CYCLE           = 0x94, /* Read-Only word */
> > +    PMBUS_READ_FREQUENCY            = 0x95, /* Read-Only word */
> > +    PMBUS_READ_POUT                 = 0x96, /* Read-Only word */
> > +    PMBUS_READ_PIN                  = 0x97, /* Read-Only word */
> > +    PMBUS_REVISION                  = 0x98, /* Read-Only byte */
> > +    PMBUS_MFR_ID                    = 0x99, /* R/W block */
> > +    PMBUS_MFR_MODEL                 = 0x9A, /* R/W block */
> > +    PMBUS_MFR_REVISION              = 0x9B, /* R/W block */
> > +    PMBUS_MFR_LOCATION              = 0x9C, /* R/W block */
> > +    PMBUS_MFR_DATE                  = 0x9D, /* R/W block */
> > +    PMBUS_MFR_SERIAL                = 0x9E, /* R/W block */
> > +    PMBUS_APP_PROFILE_SUPPORT       = 0x9F, /* Read-Only block-read */
> > +    PMBUS_MFR_VIN_MIN               = 0xA0, /* Read-Only word */
> > +    PMBUS_MFR_VIN_MAX               = 0xA1, /* Read-Only word */
> > +    PMBUS_MFR_IIN_MAX               = 0xA2, /* Read-Only word */
> > +    PMBUS_MFR_PIN_MAX               = 0xA3, /* Read-Only word */
> > +    PMBUS_MFR_VOUT_MIN              = 0xA4, /* Read-Only word */
> > +    PMBUS_MFR_VOUT_MAX              = 0xA5, /* Read-Only word */
> > +    PMBUS_MFR_IOUT_MAX              = 0xA6, /* Read-Only word */
> > +    PMBUS_MFR_POUT_MAX              = 0xA7, /* Read-Only word */
> > +    PMBUS_MFR_TAMBIENT_MAX          = 0xA8, /* Read-Only word */
> > +    PMBUS_MFR_TAMBIENT_MIN          = 0xA9, /* Read-Only word */
> > +    PMBUS_MFR_EFFICIENCY_LL         = 0xAA, /* Read-Only block 14 bytes */
> > +    PMBUS_MFR_EFFICIENCY_HL         = 0xAB, /* Read-Only block 14 bytes */
> > +    PMBUS_MFR_PIN_ACCURACY          = 0xAC, /* Read-Only byte */
> > +    PMBUS_IC_DEVICE_ID              = 0xAD, /* Read-Only block-read */
> > +    PMBUS_IC_DEVICE_REV             = 0xAE, /* Read-Only block-read */
> > +    PMBUS_MFR_MAX_TEMP_1            = 0xC0, /* R/W word */
> > +    PMBUS_MFR_MAX_TEMP_2            = 0xC1, /* R/W word */
> > +    PMBUS_MFR_MAX_TEMP_3            = 0xC2, /* R/W word */
> > +};
> > +
> > +/* STATUS_WORD */
> > +#define PB_STATUS_VOUT           BIT(15)
> > +#define PB_STATUS_IOUT_POUT      BIT(14)
> > +#define PB_STATUS_INPUT          BIT(13)
> > +#define PB_STATUS_WORD_MFR       BIT(12)
> > +#define PB_STATUS_POWER_GOOD_N   BIT(11)
> > +#define PB_STATUS_FAN            BIT(10)
> > +#define PB_STATUS_OTHER          BIT(9)
> > +#define PB_STATUS_UNKNOWN        BIT(8)
> > +/* STATUS_BYTE */
> > +#define PB_STATUS_BUSY           BIT(7)
> > +#define PB_STATUS_OFF            BIT(6)
> > +#define PB_STATUS_VOUT_OV        BIT(5)
> > +#define PB_STATUS_IOUT_OC        BIT(4)
> > +#define PB_STATUS_VIN_UV         BIT(3)
> > +#define PB_STATUS_TEMPERATURE    BIT(2)
> > +#define PB_STATUS_CML            BIT(1)
> > +#define PB_STATUS_NONE_ABOVE     BIT(0)
> > +
> > +/* STATUS_VOUT */
> > +#define PB_STATUS_VOUT_OV_FAULT         BIT(7) /* Output Overvoltage Fault 
> > */
> > +#define PB_STATUS_VOUT_OV_WARN          BIT(6) /* Output Overvoltage 
> > Warning */
> > +#define PB_STATUS_VOUT_UV_WARN          BIT(5) /* Output Undervoltage 
> > Warning */
> > +#define PB_STATUS_VOUT_UV_FAULT         BIT(4) /* Output Undervoltage 
> > Fault */
> > +#define PB_STATUS_VOUT_MAX              BIT(3)
> > +#define PB_STATUS_VOUT_TON_MAX_FAULT    BIT(2)
> > +#define PB_STATUS_VOUT_TOFF_MAX_WARN    BIT(1)
> > +
> > +/* STATUS_IOUT */
> > +#define PB_STATUS_IOUT_OC_FAULT    BIT(7) /* Output Overcurrent Fault */
> > +#define PB_STATUS_IOUT_OC_LV_FAULT BIT(6) /* Output OC And Low Voltage 
> > Fault */
> > +#define PB_STATUS_IOUT_OC_WARN     BIT(5) /* Output Overcurrent Warning */
> > +#define PB_STATUS_IOUT_UC_FAULT    BIT(4) /* Output Undercurrent Fault */
> > +#define PB_STATUS_CURR_SHARE       BIT(3) /* Current Share Fault */
> > +#define PB_STATUS_PWR_LIM_MODE     BIT(2) /* In Power Limiting Mode */
> > +#define PB_STATUS_POUT_OP_FAULT    BIT(1) /* Output Overpower Fault */
> > +#define PB_STATUS_POUT_OP_WARN     BIT(0) /* Output Overpower Warning */
> > +
> > +/* STATUS_INPUT */
> > +#define PB_STATUS_INPUT_VIN_OV_FAULT    BIT(7) /* Input Overvoltage Fault 
> > */
> > +#define PB_STATUS_INPUT_VIN_OV_WARN     BIT(6) /* Input Overvoltage 
> > Warning */
> > +#define PB_STATUS_INPUT_VIN_UV_WARN     BIT(5) /* Input Undervoltage 
> > Warning */
> > +#define PB_STATUS_INPUT_VIN_UV_FAULT    BIT(4) /* Input Undervoltage Fault 
> > */
> > +#define PB_STATUS_INPUT_IIN_OC_FAULT    BIT(2) /* Input Overcurrent Fault 
> > */
> > +#define PB_STATUS_INPUT_IIN_OC_WARN     BIT(1) /* Input Overcurrent 
> > Warning */
> > +#define PB_STATUS_INPUT_PIN_OP_WARN     BIT(0) /* Input Overpower Warning 
> > */
> > +
> > +/* STATUS_TEMPERATURE */
> > +#define PB_STATUS_OT_FAULT              BIT(7) /* Overtemperature Fault */
> > +#define PB_STATUS_OT_WARN               BIT(6) /* Overtemperature Warning 
> > */
> > +#define PB_STATUS_UT_WARN               BIT(5) /* Undertemperature Warning 
> > */
> > +#define PB_STATUS_UT_FAULT              BIT(4) /* Undertemperature Fault */
> > +
> > +/* STATUS_CML */
> > +#define PB_CML_FAULT_INVALID_CMD     BIT(7) /* Invalid/Unsupported Command 
> > */
> > +#define PB_CML_FAULT_INVALID_DATA    BIT(6) /* Invalid/Unsupported Data  */
> > +#define PB_CML_FAULT_PEC             BIT(5) /* Packet Error Check Failed */
> > +#define PB_CML_FAULT_MEMORY          BIT(4) /* Memory Fault Detected */
> > +#define PB_CML_FAULT_PROCESSOR       BIT(3) /* Processor Fault Detected */
> > +#define PB_CML_FAULT_OTHER_COMM      BIT(1) /* Other communication fault */
> > +#define PB_CML_FAULT_OTHER_MEM_LOGIC BIT(0) /* Other Memory Or Logic Fault 
> > */
> > +
> > +/* OPERATION*/
> > +#define PB_OP_ON                BIT(7) /* PSU is switched on */
> > +#define PB_OP_MARGIN_HIGH       BIT(5) /* PSU vout is set to margin high */
> > +#define PB_OP_MARGIN_LOW        BIT(4) /* PSU vout is set to margin low */
> > +
> > +/* PAGES */
> > +#define PB_MAX_PAGES            0x1F
> > +#define PB_ALL_PAGES            0xFF
> > +
> > +#define TYPE_PMBUS_DEVICE "pmbus-device"
> > +OBJECT_DECLARE_TYPE(PMBusDevice, PMBusDeviceClass,
> > +                    PMBUS_DEVICE)
> > +
> > +/* flags */
> > +#define PB_HAS_COEFFICIENTS        BIT(9)
> > +#define PB_HAS_VIN                 BIT(10)
> > +#define PB_HAS_VOUT                BIT(11)
> > +#define PB_HAS_VOUT_MARGIN         BIT(12)
> > +#define PB_HAS_VIN_RATING          BIT(13)
> > +#define PB_HAS_VOUT_RATING         BIT(14)
> > +#define PB_HAS_VOUT_MODE           BIT(15)
> > +#define PB_HAS_IOUT                BIT(21)
> > +#define PB_HAS_IIN                 BIT(22)
> > +#define PB_HAS_IOUT_RATING         BIT(23)
> > +#define PB_HAS_IIN_RATING          BIT(24)
> > +#define PB_HAS_IOUT_GAIN           BIT(25)
> > +#define PB_HAS_POUT                BIT(30)
> > +#define PB_HAS_PIN                 BIT(31)
> > +#define PB_HAS_EIN                 BIT(32)
> > +#define PB_HAS_EOUT                BIT(33)
> > +#define PB_HAS_POUT_RATING         BIT(34)
> > +#define PB_HAS_PIN_RATING          BIT(35)
> > +#define PB_HAS_TEMPERATURE         BIT(40)
> > +#define PB_HAS_TEMP2               BIT(41)
> > +#define PB_HAS_TEMP3               BIT(42)
> > +#define PB_HAS_TEMP_RATING         BIT(43)
> > +#define PB_HAS_MFR_INFO            BIT(50)
> > +
> > +struct PMBusDeviceClass {
> > +    SMBusDeviceClass parent_class;
> > +    uint8_t device_num_pages;
> > +
> > +    /**
> > +     * Implement quick_cmd, receive byte, and write_data to support 
> > non-standard
> > +     * PMBus functionality
> > +     */
> > +    void (*quick_cmd)(PMBusDevice *dev, uint8_t read);
> > +    int (*write_data)(PMBusDevice *dev, const uint8_t *buf, uint8_t len);
> > +    uint8_t (*receive_byte)(PMBusDevice *dev);
> > +};
> > +
> > +/*
> > + * According to the spec, each page may offer the full range of PMBus 
> > commands
> > + * available for each output or non-PMBus device.
> > + * Therefore, we can't assume that any registers will always be the same 
> > across
> > + * all pages.
> > + * The page 0xFF is intended for writes to all pages
> > + */
> > +typedef struct PMBusPage {
> > +    uint64_t page_flags;
> > +
> > +    uint8_t page;                      /* R/W byte */
> > +    uint8_t operation;                 /* R/W byte */
> > +    uint8_t on_off_config;             /* R/W byte */
> > +    uint8_t write_protect;             /* R/W byte */
> > +    uint8_t phase;                     /* R/W byte */
> > +    uint8_t vout_mode;                 /* R/W byte */
> > +    uint16_t vout_command;             /* R/W word */
> > +    uint16_t vout_trim;                /* R/W word */
> > +    uint16_t vout_cal_offset;          /* R/W word */
> > +    uint16_t vout_max;                 /* R/W word */
> > +    uint16_t vout_margin_high;         /* R/W word */
> > +    uint16_t vout_margin_low;          /* R/W word */
> > +    uint16_t vout_transition_rate;     /* R/W word */
> > +    uint16_t vout_droop;               /* R/W word */
> > +    uint16_t vout_scale_loop;          /* R/W word */
> > +    uint16_t vout_scale_monitor;       /* R/W word */
> > +    uint8_t coefficients[5];           /* Read-only block 5 bytes */
> > +    uint16_t pout_max;                 /* R/W word */
> > +    uint16_t max_duty;                 /* R/W word */
> > +    uint16_t frequency_switch;         /* R/W word */
> > +    uint16_t vin_on;                   /* R/W word */
> > +    uint16_t vin_off;                  /* R/W word */
> > +    uint16_t iout_cal_gain;            /* R/W word */
> > +    uint16_t iout_cal_offset;          /* R/W word */
> > +    uint8_t fan_config_1_2;            /* R/W byte */
> > +    uint16_t fan_command_1;            /* R/W word */
> > +    uint16_t fan_command_2;            /* R/W word */
> > +    uint8_t fan_config_3_4;            /* R/W byte */
> > +    uint16_t fan_command_3;            /* R/W word */
> > +    uint16_t fan_command_4;            /* R/W word */
> > +    uint16_t vout_ov_fault_limit;      /* R/W word */
> > +    uint8_t vout_ov_fault_response;    /* R/W byte */
> > +    uint16_t vout_ov_warn_limit;       /* R/W word */
> > +    uint16_t vout_uv_warn_limit;       /* R/W word */
> > +    uint16_t vout_uv_fault_limit;      /* R/W word */
> > +    uint8_t vout_uv_fault_response;    /* R/W byte */
> > +    uint16_t iout_oc_fault_limit;      /* R/W word */
> > +    uint8_t iout_oc_fault_response;    /* R/W byte */
> > +    uint16_t iout_oc_lv_fault_limit;   /* R/W word */
> > +    uint8_t iout_oc_lv_fault_response; /* R/W byte */
> > +    uint16_t iout_oc_warn_limit;       /* R/W word */
> > +    uint16_t iout_uc_fault_limit;      /* R/W word */
> > +    uint8_t iout_uc_fault_response;    /* R/W byte */
> > +    uint16_t ot_fault_limit;           /* R/W word */
> > +    uint8_t ot_fault_response;         /* R/W byte */
> > +    uint16_t ot_warn_limit;            /* R/W word */
> > +    uint16_t ut_warn_limit;            /* R/W word */
> > +    uint16_t ut_fault_limit;           /* R/W word */
> > +    uint8_t ut_fault_response;         /* R/W byte */
> > +    uint16_t vin_ov_fault_limit;       /* R/W word */
> > +    uint8_t vin_ov_fault_response;     /* R/W byte */
> > +    uint16_t vin_ov_warn_limit;        /* R/W word */
> > +    uint16_t vin_uv_warn_limit;        /* R/W word */
> > +    uint16_t vin_uv_fault_limit;       /* R/W word */
> > +    uint8_t vin_uv_fault_response;     /* R/W byte */
> > +    uint16_t iin_oc_fault_limit;       /* R/W word */
> > +    uint8_t iin_oc_fault_response;     /* R/W byte */
> > +    uint16_t iin_oc_warn_limit;        /* R/W word */
> > +    uint16_t power_good_on;            /* R/W word */
> > +    uint16_t power_good_off;           /* R/W word */
> > +    uint16_t ton_delay;                /* R/W word */
> > +    uint16_t ton_rise;                 /* R/W word */
> > +    uint16_t ton_max_fault_limit;      /* R/W word */
> > +    uint8_t ton_max_fault_response;    /* R/W byte */
> > +    uint16_t toff_delay;               /* R/W word */
> > +    uint16_t toff_fall;                /* R/W word */
> > +    uint16_t toff_max_warn_limit;      /* R/W word */
> > +    uint16_t pout_op_fault_limit;      /* R/W word */
> > +    uint8_t pout_op_fault_response;    /* R/W byte */
> > +    uint16_t pout_op_warn_limit;       /* R/W word */
> > +    uint16_t pin_op_warn_limit;        /* R/W word */
> > +    uint16_t status_word;              /* R/W word */
> > +    uint8_t status_vout;               /* R/W byte */
> > +    uint8_t status_iout;               /* R/W byte */
> > +    uint8_t status_input;              /* R/W byte */
> > +    uint8_t status_temperature;        /* R/W byte */
> > +    uint8_t status_cml;                /* R/W byte */
> > +    uint8_t status_other;              /* R/W byte */
> > +    uint8_t status_mfr_specific;       /* R/W byte */
> > +    uint8_t status_fans_1_2;           /* R/W byte */
> > +    uint8_t status_fans_3_4;           /* R/W byte */
> > +    uint8_t read_ein[5];               /* Read-Only block 5 bytes */
> > +    uint8_t read_eout[5];              /* Read-Only block 5 bytes */
> > +    uint16_t read_vin;                 /* Read-Only word */
> > +    uint16_t read_iin;                 /* Read-Only word */
> > +    uint16_t read_vcap;                /* Read-Only word */
> > +    uint16_t read_vout;                /* Read-Only word */
> > +    uint16_t read_iout;                /* Read-Only word */
> > +    uint16_t read_temperature_1;       /* Read-Only word */
> > +    uint16_t read_temperature_2;       /* Read-Only word */
> > +    uint16_t read_temperature_3;       /* Read-Only word */
> > +    uint16_t read_fan_speed_1;         /* Read-Only word */
> > +    uint16_t read_fan_speed_2;         /* Read-Only word */
> > +    uint16_t read_fan_speed_3;         /* Read-Only word */
> > +    uint16_t read_fan_speed_4;         /* Read-Only word */
> > +    uint16_t read_duty_cycle;          /* Read-Only word */
> > +    uint16_t read_frequency;           /* Read-Only word */
> > +    uint16_t read_pout;                /* Read-Only word */
> > +    uint16_t read_pin;                 /* Read-Only word */
> > +    uint8_t revision;                  /* Read-Only byte */
> > +    const char *mfr_id;                /* R/W block */
> > +    const char *mfr_model;             /* R/W block */
> > +    const char *mfr_revision;          /* R/W block */
> > +    const char *mfr_location;          /* R/W block */
> > +    const char *mfr_date;              /* R/W block */
> > +    const char *mfr_serial;            /* R/W block */
> > +    const char *app_profile_support;   /* Read-Only block-read */
> > +    uint16_t mfr_vin_min;              /* Read-Only word */
> > +    uint16_t mfr_vin_max;              /* Read-Only word */
> > +    uint16_t mfr_iin_max;              /* Read-Only word */
> > +    uint16_t mfr_pin_max;              /* Read-Only word */
> > +    uint16_t mfr_vout_min;             /* Read-Only word */
> > +    uint16_t mfr_vout_max;             /* Read-Only word */
> > +    uint16_t mfr_iout_max;             /* Read-Only word */
> > +    uint16_t mfr_pout_max;             /* Read-Only word */
> > +    uint16_t mfr_tambient_max;         /* Read-Only word */
> > +    uint16_t mfr_tambient_min;         /* Read-Only word */
> > +    uint8_t mfr_efficiency_ll[14];     /* Read-Only block 14 bytes */
> > +    uint8_t mfr_efficiency_hl[14];     /* Read-Only block 14 bytes */
> > +    uint8_t mfr_pin_accuracy;          /* Read-Only byte */
> > +    uint16_t mfr_max_temp_1;           /* R/W word */
> > +    uint16_t mfr_max_temp_2;           /* R/W word */
> > +    uint16_t mfr_max_temp_3;           /* R/W word */
> > +} PMBusPage;
> > +
> > +/* State */
> > +struct PMBusDevice {
> > +    SMBusDevice smb;
> > +
> > +    uint8_t num_pages;
> > +    uint8_t code;
> > +    uint8_t page;
> > +
> > +    /*
> > +     * PMBus registers are stored in a PMBusPage structure allocated by
> > +     * calling pmbus_pages_alloc()
> > +     */
> > +    PMBusPage *pages;
> > +    uint8_t capability;
> > +
> > +
> > +    int32_t in_buf_len;
> > +    uint8_t *in_buf;
> > +    int32_t out_buf_len;
> > +    uint8_t out_buf[SMBUS_DATA_MAX_LEN];
> > +};
> > +
> > +/**
> > + * Direct mode coefficients
> > + * @var m - mantissa
> > + * @var b - offset
> > + * @var R - exponent
> > + */
> > +typedef struct PMBusCoefficients {
> > +    int32_t m;     /* mantissa */
> > +    int64_t b;     /* offset */
> > +    int32_t R;     /* exponent */
> > +} PMBusCoefficients;
> > +
> > +/**
> > + * Convert sensor values to direct mode format
> > + *
> > + * Y = (m * x - b) * 10^R
> > + *
> > + * @return uint32_t
> > + */
> > +uint16_t pmbus_data2direct_mode(PMBusCoefficients c, uint32_t value);
> > +
> > +/**
> > + * Convert direct mode formatted data into sensor reading
> > + *
> > + * X = (Y * 10^-R - b) / m
> > + *
> > + * @return uint32_t
> > + */
> > +uint32_t pmbus_direct_mode2data(PMBusCoefficients c, uint16_t value);
> > +
> > +/**
> > + * @brief Send a block of data over PMBus
> > + * Assumes that the bytes in the block are already ordered correctly,
> > + * also assumes the length has been prepended to the block if necessary
> > + *     | low_byte | ... | high_byte |
> > + * @param state - maintains state of the PMBus device
> > + * @param data - byte array to be sent by device
> > + * @param len - number
> > + */
> > +void pmbus_send(PMBusDevice *state, const uint8_t *data, uint16_t len);
> > +void pmbus_send8(PMBusDevice *state, uint8_t data);
> > +void pmbus_send16(PMBusDevice *state, uint16_t data);
> > +void pmbus_send32(PMBusDevice *state, uint32_t data);
> > +void pmbus_send64(PMBusDevice *state, uint64_t data);
> > +
> > +/**
> > + * @brief Send a string over PMBus with length prepended.
> > + * Length is calculated using str_len()
> > + */
> > +void pmbus_send_string(PMBusDevice *state, const char *data);
> > +
> > +/**
> > + * @brief Receive data over PMBus
> > + * These methods help track how much data is being received over PMBus
> > + * Log to GUEST_ERROR if too much or too little is sent.
> > + */
> > +uint8_t pmbus_receive8(PMBusDevice *pmdev);
> > +uint16_t pmbus_receive16(PMBusDevice *pmdev);
> > +uint32_t pmbus_receive32(PMBusDevice *pmdev);
> > +uint64_t pmbus_receive64(PMBusDevice *pmdev);
> > +
> > +/**
> > + * PMBus page config must be called before any page is first used.
> > + * It will allocate memory for all the pages if needed.
> > + * Passed in flags overwrite existing flags if any.
> > + * @param page_index the page to which the flags are applied, setting 
> > page_index
> > + * to 0xFF applies the passed in flags to all pages.
> > + * @param flags
> > + */
> > +int pmbus_page_config(PMBusDevice *pmdev, uint8_t page_index, uint64_t 
> > flags);
> > +
> > +/**
> > + * Update the status registers when sensor values change.
> > + * Useful if modifying sensors through qmp, this way status registers get
> > + * updated
> > + */
> > +void pmbus_check_limits(PMBusDevice *pmdev);
> > +#endif
> > diff --git a/hw/i2c/pmbus_device.c b/hw/i2c/pmbus_device.c
> > new file mode 100644
> > index 0000000000..f1edc93346
> > --- /dev/null
> > +++ b/hw/i2c/pmbus_device.c
> > @@ -0,0 +1,1596 @@
> > +/*
> > + * PMBus wrapper over SMBus
> > + *
> > + * Copyright 2021 Google LLC
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-or-later
> > + */
> > +
> > +#include "qemu/osdep.h"
> > +#include <math.h>
> > +#include <string.h>
> > +#include "hw/i2c/pmbus_device.h"
> > +#include "qemu/module.h"
> > +#include "qemu/log.h"
> > +
> > +uint16_t pmbus_data2direct_mode(PMBusCoefficients c, uint32_t value)
> > +{
> > +    /* R is usually negative to fit large readings into 16 bits */
> > +    uint16_t y = (c.m * value + c.b) * pow(10, c.R);
> > +    return y;
> > +}
> > +
> > +uint32_t pmbus_direct_mode2data(PMBusCoefficients c, uint16_t value)
> > +{
> > +    /* X = (Y * 10^-R - b) / m */
> > +    uint32_t x = (value / pow(10, c.R) - c.b) / c.m;
> > +    return x;
> > +}
> > +
> > +void pmbus_send(PMBusDevice *pmdev, const uint8_t *data, uint16_t len)
> > +{
> > +    if (pmdev->out_buf_len + len > SMBUS_DATA_MAX_LEN) {
> > +        qemu_log_mask(LOG_GUEST_ERROR,
> > +                      "PMBus device tried to send too much data");
> > +        len = 0;
> > +    }
> > +
> > +    for (int i = len - 1; i >= 0; i--) {
> > +        pmdev->out_buf[i + pmdev->out_buf_len] = data[len - i - 1];
> > +    }
> > +    pmdev->out_buf_len += len;
> > +}
> > +
> > +/* Internal only, convert unsigned ints to the little endian bus */
> > +static void pmbus_send_uint(PMBusDevice *pmdev, uint64_t data, uint8_t 
> > size)
> > +{
> > +    uint8_t bytes[8];
> > +    g_assert(size <= 8);
> > +
> > +    for (int i = 0; i < size; i++) {
> > +        bytes[i] = data & 0xFF;
> > +        data = data >> 8;
> > +    }
> > +    pmbus_send(pmdev, bytes, size);
> > +}
> > +
> > +void pmbus_send8(PMBusDevice *pmdev, uint8_t data)
> > +{
> > +    pmbus_send_uint(pmdev, data, 1);
> > +}
> > +
> > +void pmbus_send16(PMBusDevice *pmdev, uint16_t data)
> > +{
> > +    pmbus_send_uint(pmdev, data, 2);
> > +}
> > +
> > +void pmbus_send32(PMBusDevice *pmdev, uint32_t data)
> > +{
> > +    pmbus_send_uint(pmdev, data, 4);
> > +}
> > +
> > +void pmbus_send64(PMBusDevice *pmdev, uint64_t data)
> > +{
> > +    pmbus_send_uint(pmdev, data, 8);
> > +}
> > +
> > +void pmbus_send_string(PMBusDevice *pmdev, const char *data)
> > +{
> > +    size_t len = strlen(data);
> > +    g_assert(len > 0);
> > +    g_assert(len + pmdev->out_buf_len < SMBUS_DATA_MAX_LEN);
> > +    pmdev->out_buf[len + pmdev->out_buf_len] = len;
> > +
> > +    for (int 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 uint64_t pmbus_receive_uint(const uint8_t *buf, uint8_t len)
> > +{
> > +    uint64_t ret = 0;
> > +
> > +    /* Exclude command code from return value */
> > +    buf++;
> > +    len--;
> > +
> > +    for (int i = len - 1; i >= 0; i--) {
> > +        ret = ret << 8 | buf[i];
> > +    }
> > +    return ret;
> > +}
> > +
> > +uint8_t pmbus_receive8(PMBusDevice *pmdev)
> > +{
> > +    if (pmdev->in_buf_len - 1 != 1) {
> > +        qemu_log_mask(LOG_GUEST_ERROR,
> > +                      "%s: length mismatch. Expected 1 byte, got %d 
> > bytes\n",
> > +                      __func__, pmdev->in_buf_len - 1);
> > +    }
> > +    return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
> > +}
> > +
> > +uint16_t pmbus_receive16(PMBusDevice *pmdev)
> > +{
> > +    if (pmdev->in_buf_len - 1 != 2) {
> > +        qemu_log_mask(LOG_GUEST_ERROR,
> > +                      "%s: length mismatch. Expected 2 bytes, got %d 
> > bytes\n",
> > +                      __func__, pmdev->in_buf_len - 1);
> > +    }
> > +    return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
> > +}
> > +
> > +uint32_t pmbus_receive32(PMBusDevice *pmdev)
> > +{
> > +    if (pmdev->in_buf_len - 1 != 4) {
> > +        qemu_log_mask(LOG_GUEST_ERROR,
> > +                      "%s: length mismatch. Expected 4 bytes, got %d 
> > bytes\n",
> > +                      __func__, pmdev->in_buf_len - 1);
> > +    }
> > +    return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
> > +}
> > +
> > +uint64_t pmbus_receive64(PMBusDevice *pmdev)
> > +{
> > +    if (pmdev->in_buf_len - 1 != 8) {
> > +        qemu_log_mask(LOG_GUEST_ERROR,
> > +                      "%s: length mismatch. Expected 8 bytes, got %d 
> > bytes\n",
> > +                      __func__, pmdev->in_buf_len - 1);
> > +    }
> > +    return pmbus_receive_uint(pmdev->in_buf, pmdev->in_buf_len);
> > +}
> > +
> > +static uint8_t pmbus_out_buf_pop(PMBusDevice *pmdev)
> > +{
> > +    if (pmdev->out_buf_len == 0) {
> > +        qemu_log_mask(LOG_GUEST_ERROR,
> > +                      "%s: tried to read from empty buffer",
> > +                      __func__);
> > +        return 0xFF;
> > +    }
> > +    uint8_t data = pmdev->out_buf[pmdev->out_buf_len - 1];
> > +    pmdev->out_buf_len--;
> > +    return data;
> > +}
> > +
> > +static void pmbus_quick_cmd(SMBusDevice *smd, uint8_t read)
> > +{
> > +    PMBusDevice *pmdev = PMBUS_DEVICE(smd);
> > +    PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
> > +
> > +    if (pmdc->quick_cmd) {
> > +        pmdc->quick_cmd(pmdev, read);
> > +    }
> > +}
> > +
> > +static void pmbus_pages_alloc(PMBusDevice *pmdev)
> > +{
> > +    /* some PMBus devices don't use the PAGE command, so they get 1 page */
> > +    PMBusDeviceClass *k = PMBUS_DEVICE_GET_CLASS(pmdev);
> > +    if (k->device_num_pages == 0) {
> > +        k->device_num_pages = 1;
> > +    }
> > +    pmdev->num_pages = k->device_num_pages;
> > +    pmdev->pages = g_new0(PMBusPage, k->device_num_pages);
> > +}
> > +
> > +void pmbus_check_limits(PMBusDevice *pmdev)
> > +{
> > +    for (int i = 0; i < pmdev->num_pages; i++) {
> > +        if ((pmdev->pages[i].operation & PB_OP_ON) == 0) {
> > +            continue;   /* don't check powered off devices */
> > +        }
> > +
> > +        if (pmdev->pages[i].read_vout > 
> > pmdev->pages[i].vout_ov_fault_limit) {
> > +            pmdev->pages[i].status_word |= PB_STATUS_VOUT;
> > +            pmdev->pages[i].status_vout |= PB_STATUS_VOUT_OV_FAULT;
> > +        }
> > +
> > +        if (pmdev->pages[i].read_vout > 
> > pmdev->pages[i].vout_ov_warn_limit) {
> > +            pmdev->pages[i].status_word |= PB_STATUS_VOUT;
> > +            pmdev->pages[i].status_vout |= PB_STATUS_VOUT_OV_WARN;
> > +        }
> > +
> > +        if (pmdev->pages[i].read_vout < 
> > pmdev->pages[i].vout_uv_warn_limit) {
> > +            pmdev->pages[i].status_word |= PB_STATUS_VOUT;
> > +            pmdev->pages[i].status_vout |= PB_STATUS_VOUT_UV_WARN;
> > +        }
> > +
> > +        if (pmdev->pages[i].read_vout < 
> > pmdev->pages[i].vout_uv_fault_limit) {
> > +            pmdev->pages[i].status_word |= PB_STATUS_VOUT;
> > +            pmdev->pages[i].status_vout |= PB_STATUS_VOUT_UV_FAULT;
> > +        }
> > +
> > +        if (pmdev->pages[i].read_vin > pmdev->pages[i].vin_ov_warn_limit) {
> > +            pmdev->pages[i].status_word |= PB_STATUS_INPUT;
> > +            pmdev->pages[i].status_input |= PB_STATUS_INPUT_VIN_OV_WARN;
> > +        }
> > +
> > +        if (pmdev->pages[i].read_vin < pmdev->pages[i].vin_uv_warn_limit) {
> > +            pmdev->pages[i].status_word |= PB_STATUS_INPUT;
> > +            pmdev->pages[i].status_input |= PB_STATUS_INPUT_VIN_UV_WARN;
> > +        }
> > +
> > +        if (pmdev->pages[i].read_iout > 
> > pmdev->pages[i].iout_oc_warn_limit) {
> > +            pmdev->pages[i].status_word |= PB_STATUS_IOUT_POUT;
> > +            pmdev->pages[i].status_iout |= PB_STATUS_IOUT_OC_WARN;
> > +        }
> > +
> > +        if (pmdev->pages[i].read_iout > 
> > pmdev->pages[i].iout_oc_fault_limit) {
> > +            pmdev->pages[i].status_word |= PB_STATUS_IOUT_POUT;
> > +            pmdev->pages[i].status_iout |= PB_STATUS_IOUT_OC_FAULT;
> > +        }
> > +
> > +        if (pmdev->pages[i].read_pin > pmdev->pages[i].pin_op_warn_limit) {
> > +            pmdev->pages[i].status_word |= PB_STATUS_INPUT;
> > +            pmdev->pages[i].status_input |= PB_STATUS_INPUT_PIN_OP_WARN;
> > +        }
> > +
> > +        if (pmdev->pages[i].read_temperature_1
> > +                > pmdev->pages[i].ot_fault_limit) {
> > +            pmdev->pages[i].status_word |= PB_STATUS_TEMPERATURE;
> > +            pmdev->pages[i].status_temperature |= PB_STATUS_OT_FAULT;
> > +        }
> > +
> > +        if (pmdev->pages[i].read_temperature_1
> > +                > pmdev->pages[i].ot_warn_limit) {
> > +            pmdev->pages[i].status_word |= PB_STATUS_TEMPERATURE;
> > +            pmdev->pages[i].status_temperature |= PB_STATUS_OT_WARN;
> > +        }
> > +    }
> > +}
> > +
> > +static uint8_t pmbus_receive_byte(SMBusDevice *smd)
> > +{
> > +    PMBusDevice *pmdev = PMBUS_DEVICE(smd);
> > +    PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
> > +    uint8_t ret = 0xFF;
> > +    uint8_t index = pmdev->page;
> > +
> > +    if (pmdev->out_buf_len != 0) {
> > +        ret = pmbus_out_buf_pop(pmdev);
> > +        return ret;
> > +    }
> > +
> > +    switch (pmdev->code) {
> > +    case PMBUS_PAGE:
> > +        pmbus_send8(pmdev, pmdev->page);
> > +        break;
> > +
> > +    case PMBUS_OPERATION:                 /* R/W byte */
> > +        pmbus_send8(pmdev, pmdev->pages[index].operation);
> > +        break;
> > +
> > +    case PMBUS_ON_OFF_CONFIG:             /* R/W byte */
> > +        pmbus_send8(pmdev, pmdev->pages[index].on_off_config);
> > +        break;
> > +
> > +    case PMBUS_PHASE:                     /* R/W byte */
> > +        pmbus_send8(pmdev, pmdev->pages[index].phase);
> > +        break;
> > +
> > +    case PMBUS_WRITE_PROTECT:             /* R/W byte */
> > +        pmbus_send8(pmdev, pmdev->pages[index].write_protect);
> > +        break;
> > +
> > +    case PMBUS_CAPABILITY:
> > +        pmbus_send8(pmdev, pmdev->capability);
> > +        break;
> > +
> > +    case PMBUS_VOUT_MODE:                 /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MODE) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].vout_mode);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_COMMAND:              /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_command);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_TRIM:                 /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_trim);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_CAL_OFFSET:           /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_cal_offset);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_MAX:                  /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_max);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_MARGIN_HIGH:          /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_margin_high);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_MARGIN_LOW:           /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_margin_low);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_TRANSITION_RATE:      /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_transition_rate);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_DROOP:                /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_droop);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_SCALE_LOOP:           /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_scale_loop);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_SCALE_MONITOR:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_scale_monitor);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    /* TODO: implement coefficients support */
> > +
> > +    case PMBUS_POUT_MAX:                  /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].pout_max);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_ON:                    /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vin_on);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_OFF:                   /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vin_off);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_CAL_GAIN:             /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT_GAIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].iout_cal_gain);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_OV_FAULT_LIMIT:       /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_ov_fault_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_OV_FAULT_RESPONSE:    /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].vout_ov_fault_response);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_OV_WARN_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_ov_warn_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_UV_WARN_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_uv_warn_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_UV_FAULT_LIMIT:       /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vout_uv_fault_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_UV_FAULT_RESPONSE:    /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].vout_uv_fault_response);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_OC_FAULT_LIMIT:       /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].iout_oc_fault_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_OC_FAULT_RESPONSE:    /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].iout_oc_fault_response);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_OC_LV_FAULT_LIMIT:    /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmbus_send16(pmdev, 
> > pmdev->pages[index].iout_oc_lv_fault_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_OC_LV_FAULT_RESPONSE: /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmbus_send8(pmdev, 
> > pmdev->pages[index].iout_oc_lv_fault_response);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_OC_WARN_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].iout_oc_warn_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_UC_FAULT_LIMIT:       /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].iout_uc_fault_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_UC_FAULT_RESPONSE:    /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].iout_uc_fault_response);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_OT_FAULT_LIMIT:            /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].ot_fault_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_OT_FAULT_RESPONSE:         /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].ot_fault_response);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_OT_WARN_LIMIT:             /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].ot_warn_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_UT_WARN_LIMIT:             /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].ut_warn_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_UT_FAULT_LIMIT:            /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].ut_fault_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_UT_FAULT_RESPONSE:         /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].ut_fault_response);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_OV_FAULT_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vin_ov_fault_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_OV_FAULT_RESPONSE:     /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].vin_ov_fault_response);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_OV_WARN_LIMIT:         /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vin_ov_warn_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_UV_WARN_LIMIT:         /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vin_uv_warn_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_UV_FAULT_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].vin_uv_fault_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_UV_FAULT_RESPONSE:     /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].vin_uv_fault_response);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IIN_OC_FAULT_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].iin_oc_fault_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IIN_OC_FAULT_RESPONSE:     /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].iin_oc_fault_response);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IIN_OC_WARN_LIMIT:         /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].iin_oc_warn_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_POUT_OP_FAULT_LIMIT:       /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].pout_op_fault_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_POUT_OP_FAULT_RESPONSE:    /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].pout_op_fault_response);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_POUT_OP_WARN_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].pout_op_warn_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_PIN_OP_WARN_LIMIT:         /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].pin_op_warn_limit);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_STATUS_BYTE:               /* R/W byte */
> > +        pmbus_send8(pmdev, pmdev->pages[index].status_word & 0xFF);
> > +        break;
> > +
> > +    case PMBUS_STATUS_WORD:               /* R/W word */
> > +        pmbus_send16(pmdev, pmdev->pages[index].status_word);
> > +        break;
> > +
> > +    case PMBUS_STATUS_VOUT:               /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].status_vout);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_STATUS_IOUT:               /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].status_iout);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_STATUS_INPUT:              /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN ||
> > +            pmdev->pages[index].page_flags & PB_HAS_IIN ||
> > +            pmdev->pages[index].page_flags & PB_HAS_PIN) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].status_input);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_STATUS_TEMPERATURE:        /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmbus_send8(pmdev, pmdev->pages[index].status_temperature);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_STATUS_CML:                /* R/W byte */
> > +        pmbus_send8(pmdev, pmdev->pages[index].status_cml);
> > +        break;
> > +
> > +    case PMBUS_STATUS_OTHER:              /* R/W byte */
> > +        pmbus_send8(pmdev, pmdev->pages[index].status_other);
> > +        break;
> > +
> > +    case PMBUS_READ_EIN:                  /* Read-Only block 5 bytes */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_EIN) {
> > +            pmbus_send(pmdev, pmdev->pages[index].read_ein, 5);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_READ_EOUT:                 /* Read-Only block 5 bytes */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_EOUT) {
> > +            pmbus_send(pmdev, pmdev->pages[index].read_eout, 5);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_READ_VIN:                  /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].read_vin);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_READ_IIN:                  /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].read_iin);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_READ_VOUT:                 /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].read_vout);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_READ_IOUT:                 /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].read_iout);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_READ_TEMPERATURE_1:        /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].read_temperature_1);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_READ_TEMPERATURE_2:        /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMP2) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].read_temperature_2);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_READ_TEMPERATURE_3:        /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMP3) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].read_temperature_3);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_READ_POUT:                 /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_POUT) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].read_pout);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_READ_PIN:                  /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].read_pin);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_REVISION:                  /* Read-Only byte */
> > +        pmbus_send8(pmdev, pmdev->pages[index].revision);
> > +        break;
> > +
> > +    case PMBUS_MFR_ID:                    /* R/W block */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
> > +            pmbus_send_string(pmdev, pmdev->pages[index].mfr_id);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_MODEL:                 /* R/W block */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
> > +            pmbus_send_string(pmdev, pmdev->pages[index].mfr_model);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_REVISION:              /* R/W block */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
> > +            pmbus_send_string(pmdev, pmdev->pages[index].mfr_revision);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_LOCATION:              /* R/W block */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_MFR_INFO) {
> > +            pmbus_send_string(pmdev, pmdev->pages[index].mfr_location);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_VIN_MIN:               /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN_RATING) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].mfr_vin_min);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_VIN_MAX:               /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN_RATING) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].mfr_vin_max);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_IIN_MAX:               /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IIN_RATING) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].mfr_iin_max);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_PIN_MAX:               /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_PIN_RATING) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].mfr_pin_max);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_VOUT_MIN:              /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT_RATING) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].mfr_vout_min);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_VOUT_MAX:              /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT_RATING) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].mfr_vout_max);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_IOUT_MAX:              /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT_RATING) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].mfr_iout_max);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_POUT_MAX:              /* Read-Only word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_POUT_RATING) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].mfr_pout_max);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_MAX_TEMP_1:            /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_1);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_MAX_TEMP_2:            /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_2);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_MFR_MAX_TEMP_3:            /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMP_RATING) {
> > +            pmbus_send16(pmdev, pmdev->pages[index].mfr_max_temp_3);
> > +        } else {
> > +            goto passthough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_CLEAR_FAULTS:              /* Send Byte */
> > +    case PMBUS_PAGE_PLUS_WRITE:           /* Block Write-only */
> > +    case PMBUS_STORE_DEFAULT_ALL:         /* Send Byte */
> > +    case PMBUS_RESTORE_DEFAULT_ALL:       /* Send Byte */
> > +    case PMBUS_STORE_DEFAULT_CODE:        /* Write-only Byte */
> > +    case PMBUS_RESTORE_DEFAULT_CODE:      /* Write-only Byte */
> > +    case PMBUS_STORE_USER_ALL:            /* Send Byte */
> > +    case PMBUS_RESTORE_USER_ALL:          /* Send Byte */
> > +    case PMBUS_STORE_USER_CODE:           /* Write-only Byte */
> > +    case PMBUS_RESTORE_USER_CODE:         /* Write-only Byte */
> > +    case PMBUS_QUERY:                     /* Write-Only */
> > +        qemu_log_mask(LOG_GUEST_ERROR,
> > +                      "%s: reading from write only register 0x%02x\n",
> > +                      __func__, pmdev->code);
> > +        break;
> > +
> > +passthough:
> > +    default:
> > +        /* Pass through read request if not handled */
> > +        if (pmdc->receive_byte) {
> > +            ret = pmdc->receive_byte(pmdev);
> > +        }
> > +        break;
> > +    }
> > +
> > +    if (pmdev->out_buf_len != 0) {
> > +        ret = pmbus_out_buf_pop(pmdev);
> > +        return ret;
> > +    }
> > +
> > +    return ret;
> > +}
> > +
> > +/*
> > + * PMBus clear faults command applies to all status registers, existing 
> > faults
> > + * should separately get re-asserted.
> > + */
> > +static void pmbus_clear_faults(PMBusDevice *pmdev)
> > +{
> > +    for (uint8_t i = 0; i < pmdev->num_pages; i++) {
> > +        pmdev->pages[i].status_word = 0;
> > +        pmdev->pages[i].status_vout = 0;
> > +        pmdev->pages[i].status_iout = 0;
> > +        pmdev->pages[i].status_input = 0;
> > +        pmdev->pages[i].status_temperature = 0;
> > +        pmdev->pages[i].status_cml = 0;
> > +        pmdev->pages[i].status_other = 0;
> > +        pmdev->pages[i].status_mfr_specific = 0;
> > +        pmdev->pages[i].status_fans_1_2 = 0;
> > +        pmdev->pages[i].status_fans_3_4 = 0;
> > +    }
> > +
> > +}
> > +
> > +/*
> > + * PMBus operation is used to turn On and Off PSUs
> > + * Therefore, default value for the Operation should be PB_OP_ON or 0x80
> > + */
> > +static void pmbus_operation(PMBusDevice *pmdev)
> > +{
> > +    uint8_t index = pmdev->page;
> > +    if ((pmdev->pages[index].operation & PB_OP_ON) == 0) {
> > +        pmdev->pages[index].read_vout = 0;
> > +        pmdev->pages[index].read_iout = 0;
> > +        pmdev->pages[index].read_pout = 0;
> > +        return;
> > +    }
> > +
> > +    if (pmdev->pages[index].operation & (PB_OP_ON | PB_OP_MARGIN_HIGH)) {
> > +        pmdev->pages[index].read_vout = 
> > pmdev->pages[index].vout_margin_high;
> > +    }
> > +
> > +    if (pmdev->pages[index].operation & (PB_OP_ON | PB_OP_MARGIN_LOW)) {
> > +        pmdev->pages[index].read_vout = 
> > pmdev->pages[index].vout_margin_low;
> > +    }
> > +    pmbus_check_limits(pmdev);
> > +}
> > +
> > +static int pmbus_write_data(SMBusDevice *smd, uint8_t *buf, uint8_t len)
> > +{
> > +    PMBusDevice *pmdev = PMBUS_DEVICE(smd);
> > +    PMBusDeviceClass *pmdc = PMBUS_DEVICE_GET_CLASS(pmdev);
> > +    int ret = 0;
> > +    uint8_t index;
> > +
> > +    if (len == 0) {
> > +        qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", 
> > __func__);
> > +        return -1;
> > +    }
> > +
> > +    if (!pmdev->pages) { /* allocate memory for pages on first use */
> > +        pmbus_pages_alloc(pmdev);
> > +    }
> > +
> > +    pmdev->in_buf_len = len;
> > +    pmdev->in_buf = buf;
> > +
> > +    pmdev->code = buf[0]; /* PMBus command code */
> > +    if (len == 1) { /* Single length writes are command codes only */
> > +        return 0;
> > +    }
> > +
> > +    if (pmdev->code == PMBUS_PAGE) {
> > +        pmdev->page = pmbus_receive8(pmdev);
> > +        return 0;
> > +    }
> > +    /* loop through all the pages when 0xFF is received */
> > +    if (pmdev->page == PB_ALL_PAGES) {
> > +        for (int i = 0; i < pmdev->num_pages; i++) {
> > +            pmdev->page = i;
> > +            pmbus_write_data(smd, buf, len);
> > +        }
> > +        pmdev->page = PB_ALL_PAGES;
> > +        return 0;
> > +    }
> > +
> > +    index = pmdev->page;
> > +
> > +    switch (pmdev->code) {
> > +    case PMBUS_OPERATION:                 /* R/W byte */
> > +        pmdev->pages[index].operation = pmbus_receive8(pmdev);
> > +        pmbus_operation(pmdev);
> > +        break;
> > +
> > +    case PMBUS_ON_OFF_CONFIG:             /* R/W byte */
> > +        pmdev->pages[index].on_off_config = pmbus_receive8(pmdev);
> > +        break;
> > +
> > +    case PMBUS_CLEAR_FAULTS:              /* Send Byte */
> > +        pmbus_clear_faults(pmdev);
> > +        break;
> > +
> > +    case PMBUS_PHASE:                     /* R/W byte */
> > +        pmdev->pages[index].phase = pmbus_receive8(pmdev);
> > +        break;
> > +
> > +    case PMBUS_PAGE_PLUS_WRITE:           /* Block Write-only */
> > +    case PMBUS_WRITE_PROTECT:             /* R/W byte */
> > +        pmdev->pages[index].write_protect = pmbus_receive8(pmdev);
> > +        break;
> > +
> > +    case PMBUS_VOUT_MODE:                 /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MODE) {
> > +            pmdev->pages[index].vout_mode = pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_COMMAND:              /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_command = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_TRIM:                 /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_trim = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_CAL_OFFSET:           /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_cal_offset = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_MAX:                  /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_max = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_MARGIN_HIGH:          /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
> > +            pmdev->pages[index].vout_margin_high = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_MARGIN_LOW:           /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT_MARGIN) {
> > +            pmdev->pages[index].vout_margin_low = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_TRANSITION_RATE:      /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_transition_rate = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_DROOP:                /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_droop = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_SCALE_LOOP:           /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_scale_loop = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_SCALE_MONITOR:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_scale_monitor = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_POUT_MAX:                  /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].pout_max = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_ON:                    /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmdev->pages[index].vin_on = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_OFF:                   /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmdev->pages[index].vin_off = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_CAL_GAIN:             /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT_GAIN) {
> > +            pmdev->pages[index].iout_cal_gain = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_OV_FAULT_LIMIT:       /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_ov_fault_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_OV_FAULT_RESPONSE:    /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_ov_fault_response = 
> > pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_OV_WARN_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_ov_warn_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_UV_WARN_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_uv_warn_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_UV_FAULT_LIMIT:       /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_uv_fault_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VOUT_UV_FAULT_RESPONSE:    /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].vout_uv_fault_response = 
> > pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_OC_FAULT_LIMIT:       /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmdev->pages[index].iout_oc_fault_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_OC_FAULT_RESPONSE:    /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmdev->pages[index].iout_oc_fault_response = 
> > pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_OC_LV_FAULT_LIMIT:    /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmdev->pages[index].iout_oc_lv_fault_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_OC_LV_FAULT_RESPONSE: /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmdev->pages[index].iout_oc_lv_fault_response
> > +                = pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_OC_WARN_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmdev->pages[index].iout_oc_warn_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_UC_FAULT_LIMIT:       /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmdev->pages[index].iout_uc_fault_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IOUT_UC_FAULT_RESPONSE:    /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmdev->pages[index].iout_uc_fault_response = 
> > pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_OT_FAULT_LIMIT:            /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmdev->pages[index].ot_fault_limit = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_OT_FAULT_RESPONSE:         /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmdev->pages[index].ot_fault_response = pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_OT_WARN_LIMIT:             /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmdev->pages[index].ot_warn_limit = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_UT_WARN_LIMIT:             /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmdev->pages[index].ut_warn_limit = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_UT_FAULT_LIMIT:            /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmdev->pages[index].ut_fault_limit = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_UT_FAULT_RESPONSE:         /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmdev->pages[index].ut_fault_response = pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_OV_FAULT_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmdev->pages[index].vin_ov_fault_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_OV_FAULT_RESPONSE:     /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmdev->pages[index].vin_ov_fault_response = 
> > pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_OV_WARN_LIMIT:         /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmdev->pages[index].vin_ov_warn_limit = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_UV_WARN_LIMIT:         /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmdev->pages[index].vin_uv_warn_limit = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_UV_FAULT_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmdev->pages[index].vin_uv_fault_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_VIN_UV_FAULT_RESPONSE:     /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VIN) {
> > +            pmdev->pages[index].vin_uv_fault_response = 
> > pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IIN_OC_FAULT_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
> > +            pmdev->pages[index].iin_oc_fault_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IIN_OC_FAULT_RESPONSE:     /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
> > +            pmdev->pages[index].iin_oc_fault_response = 
> > pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_IIN_OC_WARN_LIMIT:         /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IIN) {
> > +            pmdev->pages[index].iin_oc_warn_limit = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_POUT_OP_FAULT_LIMIT:       /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].pout_op_fault_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_POUT_OP_FAULT_RESPONSE:    /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].pout_op_fault_response = 
> > pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_POUT_OP_WARN_LIMIT:        /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].pout_op_warn_limit = 
> > pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_PIN_OP_WARN_LIMIT:         /* R/W word */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_PIN) {
> > +            pmdev->pages[index].pin_op_warn_limit = pmbus_receive16(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_STATUS_BYTE:               /* R/W byte */
> > +        pmdev->pages[index].status_word = pmbus_receive8(pmdev);
> > +        break;
> > +
> > +    case PMBUS_STATUS_WORD:               /* R/W word */
> > +        pmdev->pages[index].status_word = pmbus_receive16(pmdev);
> > +        break;
> > +
> > +    case PMBUS_STATUS_VOUT:               /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_VOUT) {
> > +            pmdev->pages[index].status_vout = pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_STATUS_IOUT:               /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_IOUT) {
> > +            pmdev->pages[index].status_iout = pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_STATUS_INPUT:              /* R/W byte */
> > +        pmdev->pages[index].status_input = pmbus_receive8(pmdev);
> > +        break;
> > +
> > +    case PMBUS_STATUS_TEMPERATURE:        /* R/W byte */
> > +        if (pmdev->pages[index].page_flags & PB_HAS_TEMPERATURE) {
> > +            pmdev->pages[index].status_temperature = pmbus_receive8(pmdev);
> > +        } else {
> > +            goto passthrough;
> > +        }
> > +        break;
> > +
> > +    case PMBUS_STATUS_CML:                /* R/W byte */
> > +        pmdev->pages[index].status_cml = pmbus_receive8(pmdev);
> > +        break;
> > +
> > +    case PMBUS_STATUS_OTHER:              /* R/W byte */
> > +        pmdev->pages[index].status_other = pmbus_receive8(pmdev);
> > +        break;
> > +
> > +    case PMBUS_PAGE_PLUS_READ:            /* Block Read-only */
> > +    case PMBUS_CAPABILITY:                /* Read-Only byte */
> > +    case PMBUS_COEFFICIENTS:              /* Read-only block 5 bytes */
> > +    case PMBUS_READ_EIN:                  /* Read-Only block 5 bytes */
> > +    case PMBUS_READ_EOUT:                 /* Read-Only block 5 bytes */
> > +    case PMBUS_READ_VIN:                  /* Read-Only word */
> > +    case PMBUS_READ_IIN:                  /* Read-Only word */
> > +    case PMBUS_READ_VCAP:                 /* Read-Only word */
> > +    case PMBUS_READ_VOUT:                 /* Read-Only word */
> > +    case PMBUS_READ_IOUT:                 /* Read-Only word */
> > +    case PMBUS_READ_TEMPERATURE_1:        /* Read-Only word */
> > +    case PMBUS_READ_TEMPERATURE_2:        /* Read-Only word */
> > +    case PMBUS_READ_TEMPERATURE_3:        /* Read-Only word */
> > +    case PMBUS_READ_FAN_SPEED_1:          /* Read-Only word */
> > +    case PMBUS_READ_FAN_SPEED_2:          /* Read-Only word */
> > +    case PMBUS_READ_FAN_SPEED_3:          /* Read-Only word */
> > +    case PMBUS_READ_FAN_SPEED_4:          /* Read-Only word */
> > +    case PMBUS_READ_DUTY_CYCLE:           /* Read-Only word */
> > +    case PMBUS_READ_FREQUENCY:            /* Read-Only word */
> > +    case PMBUS_READ_POUT:                 /* Read-Only word */
> > +    case PMBUS_READ_PIN:                  /* Read-Only word */
> > +    case PMBUS_REVISION:                  /* Read-Only byte */
> > +    case PMBUS_APP_PROFILE_SUPPORT:       /* Read-Only block-read */
> > +    case PMBUS_MFR_VIN_MIN:               /* Read-Only word */
> > +    case PMBUS_MFR_VIN_MAX:               /* Read-Only word */
> > +    case PMBUS_MFR_IIN_MAX:               /* Read-Only word */
> > +    case PMBUS_MFR_PIN_MAX:               /* Read-Only word */
> > +    case PMBUS_MFR_VOUT_MIN:              /* Read-Only word */
> > +    case PMBUS_MFR_VOUT_MAX:              /* Read-Only word */
> > +    case PMBUS_MFR_IOUT_MAX:              /* Read-Only word */
> > +    case PMBUS_MFR_POUT_MAX:              /* Read-Only word */
> > +    case PMBUS_MFR_TAMBIENT_MAX:          /* Read-Only word */
> > +    case PMBUS_MFR_TAMBIENT_MIN:          /* Read-Only word */
> > +    case PMBUS_MFR_EFFICIENCY_LL:         /* Read-Only block 14 bytes */
> > +    case PMBUS_MFR_EFFICIENCY_HL:         /* Read-Only block 14 bytes */
> > +    case PMBUS_MFR_PIN_ACCURACY:          /* Read-Only byte */
> > +    case PMBUS_IC_DEVICE_ID:              /* Read-Only block-read */
> > +    case PMBUS_IC_DEVICE_REV:             /* Read-Only block-read */
> > +        qemu_log_mask(LOG_GUEST_ERROR,
> > +                      "%s: writing to read-only register 0x%02x\n",
> > +                      __func__, pmdev->code);
> > +        break;
> > +
> > +passthrough:
> > +    /* Unimplimented registers get passed to the device */
> > +    default:
> > +        if (pmdc->write_data) {
> > +            ret = pmdc->write_data(pmdev, buf, len);
> > +        }
> > +        break;
> > +    }
> > +    pmbus_check_limits(pmdev);
> > +    pmdev->in_buf_len = 0;
> > +    return ret;
> > +}
> > +
> > +int pmbus_page_config(PMBusDevice *pmdev, uint8_t index, uint64_t flags)
> > +{
> > +    if (!pmdev->pages) { /* allocate memory for pages on first use */
> > +        pmbus_pages_alloc(pmdev);
> > +    }
> > +
> > +    /* The 0xFF page is special for commands applying to all pages */
> > +    if (index == PB_ALL_PAGES) {
> > +        for (int i = 0; i < pmdev->num_pages; i++) {
> > +            pmdev->pages[i].page_flags = flags;
> > +        }
> > +        return 0;
> > +    }
> > +
> > +    if (index > pmdev->num_pages - 1) {
> > +        qemu_log_mask(LOG_GUEST_ERROR,
> > +                      "%s: index %u is out of range\n",
> > +                      __func__, index);
> > +        return -1;
> > +    }
> > +
> > +    pmdev->pages[index].page_flags = flags;
> > +
> > +    return 0;
> > +}
> > +
> > +static void pmbus_device_finalize(Object *obj)
> > +{
> > +    PMBusDevice *pmdev = PMBUS_DEVICE(obj);
> > +    g_free(pmdev->pages);
> > +}
> > +
> > +static void pmbus_device_class_init(ObjectClass *klass, void *data)
> > +{
> > +    SMBusDeviceClass *k = SMBUS_DEVICE_CLASS(klass);
> > +
> > +    k->quick_cmd = pmbus_quick_cmd;
> > +    k->write_data = pmbus_write_data;
> > +    k->receive_byte = pmbus_receive_byte;
> > +}
> > +
> > +static const TypeInfo pmbus_device_type_info = {
> > +    .name = TYPE_PMBUS_DEVICE,
> > +    .parent = TYPE_SMBUS_DEVICE,
> > +    .instance_size = sizeof(PMBusDevice),
> > +    .instance_finalize = pmbus_device_finalize,
> > +    .abstract = true,
> > +    .class_size = sizeof(PMBusDeviceClass),
> > +    .class_init = pmbus_device_class_init,
> > +};
> > +
> > +static void pmbus_device_register_types(void)
> > +{
> > +    type_register_static(&pmbus_device_type_info);
> > +}
> > +
> > +type_init(pmbus_device_register_types)
> > diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> > index b887f6a5b1..20bd60f10b 100644
> > --- a/hw/arm/Kconfig
> > +++ b/hw/arm/Kconfig
> > @@ -374,6 +374,7 @@ config NPCM7XX
> >      select ARM_GIC
> >      select AT24C  # EEPROM
> >      select PL310  # cache controller
> > +    select PMBUS
> >      select SERIAL
> >      select SSI
> >      select UNIMP
> > diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig
> > index 8d120a25d5..8217cb5041 100644
> > --- a/hw/i2c/Kconfig
> > +++ b/hw/i2c/Kconfig
> > @@ -32,3 +32,7 @@ config MPC_I2C
> >  config PCA954X
> >      bool
> >      select I2C
> > +
> > +config PMBUS
> > +    bool
> > +    select SMBUS
> > diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
> > index dd3aef02b2..d3df273251 100644
> > --- a/hw/i2c/meson.build
> > +++ b/hw/i2c/meson.build
> > @@ -15,4 +15,5 @@ i2c_ss.add(when: 'CONFIG_VERSATILE_I2C', if_true: 
> > files('versatile_i2c.c'))
> >  i2c_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_i2c.c'))
> >  i2c_ss.add(when: 'CONFIG_PPC4XX', if_true: files('ppc4xx_i2c.c'))
> >  i2c_ss.add(when: 'CONFIG_PCA954X', if_true: files('i2c_mux_pca954x.c'))
> > +i2c_ss.add(when: 'CONFIG_PMBUS', if_true: files('pmbus_device.c'))
> >  softmmu_ss.add_all(when: 'CONFIG_I2C', if_true: i2c_ss)
> > --
> > 2.31.1.751.gd2f1c929bd-goog
> >



reply via email to

[Prev in Thread] Current Thread [Next in Thread]