* [PATCH v3 1/5] hw/i2c: add support for PMBus
2021-05-18 18:45 [PATCH v3 0/5] Add support for PMBus in QEMU Titus Rwantare
@ 2021-05-18 18:45 ` Titus Rwantare
2021-05-18 19:37 ` Corey Minyard
2021-05-18 18:45 ` [PATCH v3 2/5] hw/misc: add ADM1272 device Titus Rwantare
` (4 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Titus Rwantare @ 2021-05-18 18:45 UTC (permalink / raw)
To: Corey Minyard
Cc: qemu-arm, qemu-devel, f4bug, Titus Rwantare, Joel Stanley, Hao Wu
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.
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
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v3 1/5] hw/i2c: add support for PMBus
2021-05-18 18:45 ` [PATCH v3 1/5] hw/i2c: add support for PMBus Titus Rwantare
@ 2021-05-18 19:37 ` Corey Minyard
2021-05-18 19:46 ` Titus Rwantare
0 siblings, 1 reply; 12+ messages in thread
From: Corey Minyard @ 2021-05-18 19:37 UTC (permalink / raw)
To: Titus Rwantare; +Cc: Hao Wu, qemu-arm, Joel Stanley, qemu-devel, f4bug
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
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 1/5] hw/i2c: add support for PMBus
2021-05-18 19:37 ` Corey Minyard
@ 2021-05-18 19:46 ` Titus Rwantare
0 siblings, 0 replies; 12+ messages in thread
From: Titus Rwantare @ 2021-05-18 19:46 UTC (permalink / raw)
To: Corey Minyard
Cc: qemu-arm, qemu-devel, Philippe Mathieu-Daudé, Joel Stanley, Hao Wu
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
> >
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 2/5] hw/misc: add ADM1272 device
2021-05-18 18:45 [PATCH v3 0/5] Add support for PMBus in QEMU Titus Rwantare
2021-05-18 18:45 ` [PATCH v3 1/5] hw/i2c: add support for PMBus Titus Rwantare
@ 2021-05-18 18:45 ` Titus Rwantare
2021-05-18 18:45 ` [PATCH v3 3/5] tests/qtest: add tests for ADM1272 device model Titus Rwantare
` (3 subsequent siblings)
5 siblings, 0 replies; 12+ messages in thread
From: Titus Rwantare @ 2021-05-18 18:45 UTC (permalink / raw)
To: Corey Minyard
Cc: qemu-arm, qemu-devel, f4bug, Titus Rwantare, Joel Stanley, Hao Wu
The ADM1272 is a PMBus compliant Hot Swap Controller and Digital Power
Monitor by Analog Devices.
This commit adds support for interfacing with it, and support for
setting and monitoring sensor limits.
Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1272.pdf
Reviewed-by: Joel Stanley <joel@jms.id.au>
Reviewed-by: Hao Wu <wuhaotsh@google.com>
Signed-off-by: Titus Rwantare <titusr@google.com>
---
hw/misc/adm1272.c | 543 ++++++++++++++++++++++++++++++++++++++++++++
hw/arm/Kconfig | 1 +
hw/misc/Kconfig | 4 +
hw/misc/meson.build | 1 +
4 files changed, 549 insertions(+)
create mode 100644 hw/misc/adm1272.c
diff --git a/hw/misc/adm1272.c b/hw/misc/adm1272.c
new file mode 100644
index 0000000000..03d728d453
--- /dev/null
+++ b/hw/misc/adm1272.c
@@ -0,0 +1,543 @@
+/*
+ * Analog Devices ADM1272 High Voltage Positive Hot Swap Controller and Digital
+ * Power Monitor with PMBus
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include <string.h>
+#include "hw/i2c/pmbus_device.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+#define TYPE_ADM1272 "adm1272"
+#define ADM1272(obj) OBJECT_CHECK(ADM1272State, (obj), TYPE_ADM1272)
+
+#define ADM1272_RESTART_TIME 0xCC
+#define ADM1272_MFR_PEAK_IOUT 0xD0
+#define ADM1272_MFR_PEAK_VIN 0xD1
+#define ADM1272_MFR_PEAK_VOUT 0xD2
+#define ADM1272_MFR_PMON_CONTROL 0xD3
+#define ADM1272_MFR_PMON_CONFIG 0xD4
+#define ADM1272_MFR_ALERT1_CONFIG 0xD5
+#define ADM1272_MFR_ALERT2_CONFIG 0xD6
+#define ADM1272_MFR_PEAK_TEMPERATURE 0xD7
+#define ADM1272_MFR_DEVICE_CONFIG 0xD8
+#define ADM1272_MFR_POWER_CYCLE 0xD9
+#define ADM1272_MFR_PEAK_PIN 0xDA
+#define ADM1272_MFR_READ_PIN_EXT 0xDB
+#define ADM1272_MFR_READ_EIN_EXT 0xDC
+
+#define ADM1272_HYSTERESIS_LOW 0xF2
+#define ADM1272_HYSTERESIS_HIGH 0xF3
+#define ADM1272_STATUS_HYSTERESIS 0xF4
+#define ADM1272_STATUS_GPIO 0xF5
+#define ADM1272_STRT_UP_IOUT_LIM 0xF6
+
+/* Defaults */
+#define ADM1272_OPERATION_DEFAULT 0x80
+#define ADM1272_CAPABILITY_DEFAULT 0xB0
+#define ADM1272_CAPABILITY_NO_PEC 0x30
+#define ADM1272_DIRECT_MODE 0x40
+#define ADM1272_HIGH_LIMIT_DEFAULT 0x0FFF
+#define ADM1272_PIN_OP_DEFAULT 0x7FFF
+#define ADM1272_PMBUS_REVISION_DEFAULT 0x22
+#define ADM1272_MFR_ID_DEFAULT "ADI"
+#define ADM1272_MODEL_DEFAULT "ADM1272-A1"
+#define ADM1272_MFR_DEFAULT_REVISION "25"
+#define ADM1272_DEFAULT_DATE "160301"
+#define ADM1272_RESTART_TIME_DEFAULT 0x64
+#define ADM1272_PMON_CONTROL_DEFAULT 0x1
+#define ADM1272_PMON_CONFIG_DEFAULT 0x3F35
+#define ADM1272_DEVICE_CONFIG_DEFAULT 0x8
+#define ADM1272_HYSTERESIS_HIGH_DEFAULT 0xFFFF
+#define ADM1272_STRT_UP_IOUT_LIM_DEFAULT 0x000F
+#define ADM1272_VOLT_DEFAULT 12000
+#define ADM1272_IOUT_DEFAULT 25000
+#define ADM1272_PWR_DEFAULT 300 /* 12V 25A */
+#define ADM1272_SHUNT 300 /* micro-ohms */
+#define ADM1272_VOLTAGE_COEFF_DEFAULT 1
+#define ADM1272_CURRENT_COEFF_DEFAULT 3
+#define ADM1272_PWR_COEFF_DEFAULT 7
+#define ADM1272_IOUT_OFFSET 0x5000
+#define ADM1272_IOUT_OFFSET 0x5000
+
+
+typedef struct ADM1272State {
+ PMBusDevice parent;
+
+ uint64_t ein_ext;
+ uint32_t pin_ext;
+ uint8_t restart_time;
+
+ uint16_t peak_vin;
+ uint16_t peak_vout;
+ uint16_t peak_iout;
+ uint16_t peak_temperature;
+ uint16_t peak_pin;
+
+ uint8_t pmon_control;
+ uint16_t pmon_config;
+ uint16_t alert1_config;
+ uint16_t alert2_config;
+ uint16_t device_config;
+
+ uint16_t hysteresis_low;
+ uint16_t hysteresis_high;
+ uint8_t status_hysteresis;
+ uint8_t status_gpio;
+
+ uint16_t strt_up_iout_lim;
+
+} ADM1272State;
+
+static const PMBusCoefficients adm1272_coefficients[] = {
+ [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */
+ [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */
+ [2] = { 1326, 20480, -1 }, /* current, vsense range 15mV */
+ [3] = { 663, 20480, -1 }, /* current, vsense range 30mV */
+ [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */
+ [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */
+ [6] = { 17561, 0, -3 }, /* power, vrange 60V, irange 30mV */
+ [7] = { 10535, 0, -3 }, /* power, vrange 100V, irange 30mV */
+ [8] = { 42, 31871, -1 }, /* temperature */
+};
+
+static void adm1272_check_limits(ADM1272State *s)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(s);
+
+ pmbus_check_limits(pmdev);
+
+ if (pmdev->pages[0].read_vout > s->peak_vout) {
+ s->peak_vout = pmdev->pages[0].read_vout;
+ }
+
+ if (pmdev->pages[0].read_vin > s->peak_vin) {
+ s->peak_vin = pmdev->pages[0].read_vin;
+ }
+
+ if (pmdev->pages[0].read_iout > s->peak_iout) {
+ s->peak_iout = pmdev->pages[0].read_iout;
+ }
+
+ if (pmdev->pages[0].read_temperature_1 > s->peak_temperature) {
+ s->peak_temperature = pmdev->pages[0].read_temperature_1;
+ }
+
+ if (pmdev->pages[0].read_pin > s->peak_pin) {
+ s->peak_pin = pmdev->pages[0].read_pin;
+ }
+}
+
+static uint16_t adm1272_millivolts_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_millivolts(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t adm1272_milliamps_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
+ /* Y = (m * r_sense * x - b) * 10^R */
+ c.m = c.m * ADM1272_SHUNT / 1000; /* micro-ohms */
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_milliamps(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t adm1272_watts_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_watts(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static void adm1272_exit_reset(Object *obj)
+{
+ ADM1272State *s = ADM1272(obj);
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+
+ pmdev->page = 0;
+ pmdev->pages[0].operation = ADM1272_OPERATION_DEFAULT;
+
+
+ pmdev->capability = ADM1272_CAPABILITY_NO_PEC;
+ pmdev->pages[0].revision = ADM1272_PMBUS_REVISION_DEFAULT;
+ pmdev->pages[0].vout_mode = ADM1272_DIRECT_MODE;
+ pmdev->pages[0].vout_ov_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].vout_uv_warn_limit = 0;
+ pmdev->pages[0].iout_oc_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].ot_fault_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].ot_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].vin_ov_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].vin_uv_warn_limit = 0;
+ pmdev->pages[0].pin_op_warn_limit = ADM1272_PIN_OP_DEFAULT;
+
+ pmdev->pages[0].status_word = 0;
+ pmdev->pages[0].status_vout = 0;
+ pmdev->pages[0].status_iout = 0;
+ pmdev->pages[0].status_input = 0;
+ pmdev->pages[0].status_temperature = 0;
+ pmdev->pages[0].status_mfr_specific = 0;
+
+ pmdev->pages[0].read_vin
+ = adm1272_millivolts_to_direct(ADM1272_VOLT_DEFAULT);
+ pmdev->pages[0].read_vout
+ = adm1272_millivolts_to_direct(ADM1272_VOLT_DEFAULT);
+ pmdev->pages[0].read_iout
+ = adm1272_milliamps_to_direct(ADM1272_IOUT_DEFAULT);
+ pmdev->pages[0].read_temperature_1 = 0;
+ pmdev->pages[0].read_pin = adm1272_watts_to_direct(ADM1272_PWR_DEFAULT);
+ pmdev->pages[0].revision = ADM1272_PMBUS_REVISION_DEFAULT;
+ pmdev->pages[0].mfr_id = ADM1272_MFR_ID_DEFAULT;
+ pmdev->pages[0].mfr_model = ADM1272_MODEL_DEFAULT;
+ pmdev->pages[0].mfr_revision = ADM1272_MFR_DEFAULT_REVISION;
+ pmdev->pages[0].mfr_date = ADM1272_DEFAULT_DATE;
+
+ s->pin_ext = 0;
+ s->ein_ext = 0;
+ s->restart_time = ADM1272_RESTART_TIME_DEFAULT;
+
+ s->peak_vin = 0;
+ s->peak_vout = 0;
+ s->peak_iout = 0;
+ s->peak_temperature = 0;
+ s->peak_pin = 0;
+
+ s->pmon_control = ADM1272_PMON_CONTROL_DEFAULT;
+ s->pmon_config = ADM1272_PMON_CONFIG_DEFAULT;
+ s->alert1_config = 0;
+ s->alert2_config = 0;
+ s->device_config = ADM1272_DEVICE_CONFIG_DEFAULT;
+
+ s->hysteresis_low = 0;
+ s->hysteresis_high = ADM1272_HYSTERESIS_HIGH_DEFAULT;
+ s->status_hysteresis = 0;
+ s->status_gpio = 0;
+
+ s->strt_up_iout_lim = ADM1272_STRT_UP_IOUT_LIM_DEFAULT;
+}
+
+static uint8_t adm1272_read_byte(PMBusDevice *pmdev)
+{
+ ADM1272State *s = ADM1272(pmdev);
+
+ switch (pmdev->code) {
+ case ADM1272_RESTART_TIME:
+ pmbus_send8(pmdev, s->restart_time);
+ break;
+
+ case ADM1272_MFR_PEAK_IOUT:
+ pmbus_send16(pmdev, s->peak_iout);
+ break;
+
+ case ADM1272_MFR_PEAK_VIN:
+ pmbus_send16(pmdev, s->peak_vin);
+ break;
+
+ case ADM1272_MFR_PEAK_VOUT:
+ pmbus_send16(pmdev, s->peak_vout);
+ break;
+
+ case ADM1272_MFR_PMON_CONTROL:
+ pmbus_send8(pmdev, s->pmon_control);
+ break;
+
+ case ADM1272_MFR_PMON_CONFIG:
+ pmbus_send16(pmdev, s->pmon_config);
+ break;
+
+ case ADM1272_MFR_ALERT1_CONFIG:
+ pmbus_send16(pmdev, s->alert1_config);
+ break;
+
+ case ADM1272_MFR_ALERT2_CONFIG:
+ pmbus_send16(pmdev, s->alert2_config);
+ break;
+
+ case ADM1272_MFR_PEAK_TEMPERATURE:
+ pmbus_send16(pmdev, s->peak_temperature);
+ break;
+
+ case ADM1272_MFR_DEVICE_CONFIG:
+ pmbus_send16(pmdev, s->device_config);
+ break;
+
+ case ADM1272_MFR_PEAK_PIN:
+ pmbus_send16(pmdev, s->peak_pin);
+ break;
+
+ case ADM1272_MFR_READ_PIN_EXT:
+ pmbus_send32(pmdev, s->pin_ext);
+ break;
+
+ case ADM1272_MFR_READ_EIN_EXT:
+ pmbus_send64(pmdev, s->ein_ext);
+ break;
+
+ case ADM1272_HYSTERESIS_LOW:
+ pmbus_send16(pmdev, s->hysteresis_low);
+ break;
+
+ case ADM1272_HYSTERESIS_HIGH:
+ pmbus_send16(pmdev, s->hysteresis_high);
+ break;
+
+ case ADM1272_STATUS_HYSTERESIS:
+ pmbus_send16(pmdev, s->status_hysteresis);
+ break;
+
+ case ADM1272_STATUS_GPIO:
+ pmbus_send16(pmdev, s->status_gpio);
+ break;
+
+ case ADM1272_STRT_UP_IOUT_LIM:
+ pmbus_send16(pmdev, s->strt_up_iout_lim);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ return 0xFF;
+ break;
+ }
+
+ return 0;
+}
+
+static int adm1272_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+ uint8_t len)
+{
+ ADM1272State *s = ADM1272(pmdev);
+
+ if (len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+ return -1;
+ }
+
+ pmdev->code = buf[0]; /* PMBus command code */
+
+ if (len == 1) {
+ return 0;
+ }
+
+ /* Exclude command code from buffer */
+ buf++;
+ len--;
+
+ switch (pmdev->code) {
+
+ case ADM1272_RESTART_TIME:
+ s->restart_time = pmbus_receive8(pmdev);
+ break;
+
+ case ADM1272_MFR_PMON_CONTROL:
+ s->pmon_control = pmbus_receive8(pmdev);
+ break;
+
+ case ADM1272_MFR_PMON_CONFIG:
+ s->pmon_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_ALERT1_CONFIG:
+ s->alert1_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_ALERT2_CONFIG:
+ s->alert2_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_DEVICE_CONFIG:
+ s->device_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_POWER_CYCLE:
+ adm1272_exit_reset((Object *)s);
+ break;
+
+ case ADM1272_HYSTERESIS_LOW:
+ s->hysteresis_low = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_HYSTERESIS_HIGH:
+ s->hysteresis_high = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_STRT_UP_IOUT_LIM:
+ s->strt_up_iout_lim = pmbus_receive16(pmdev);
+ adm1272_check_limits(s);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+ return 0;
+}
+
+static void adm1272_get(Object *obj, Visitor *v, const char *name, void *opaque,
+ Error **errp)
+{
+ uint16_t value;
+
+ if (strcmp(name, "vin") == 0 || strcmp(name, "vout") == 0) {
+ value = adm1272_direct_to_millivolts(*(uint16_t *)opaque);
+ } else if (strcmp(name, "iout") == 0) {
+ value = adm1272_direct_to_milliamps(*(uint16_t *)opaque);
+ } else if (strcmp(name, "pin") == 0) {
+ value = adm1272_direct_to_watts(*(uint16_t *)opaque);
+ } else {
+ value = *(uint16_t *)opaque;
+ }
+
+ visit_type_uint16(v, name, &value, errp);
+}
+
+static void adm1272_set(Object *obj, Visitor *v, const char *name, void *opaque,
+ Error **errp)
+{
+ ADM1272State *s = ADM1272(obj);
+ uint16_t *internal = opaque;
+ uint16_t value;
+
+ if (!visit_type_uint16(v, name, &value, errp)) {
+ return;
+ }
+
+ if (strcmp(name, "vin") == 0 || strcmp(name, "vout") == 0) {
+ *internal = adm1272_millivolts_to_direct(value);
+ } else if (strcmp(name, "iout") == 0) {
+ *internal = adm1272_milliamps_to_direct(value);
+ } else if (strcmp(name, "pin") == 0) {
+ *internal = adm1272_watts_to_direct(value);
+ } else {
+ *internal = value;
+ }
+
+ adm1272_check_limits(s);
+}
+
+static const VMStateDescription vmstate_adm1272 = {
+ .name = "ADM1272",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]){
+ VMSTATE_UINT64(ein_ext, ADM1272State),
+ VMSTATE_UINT32(pin_ext, ADM1272State),
+ VMSTATE_UINT8(restart_time, ADM1272State),
+
+ VMSTATE_UINT16(peak_vin, ADM1272State),
+ VMSTATE_UINT16(peak_vout, ADM1272State),
+ VMSTATE_UINT16(peak_iout, ADM1272State),
+ VMSTATE_UINT16(peak_temperature, ADM1272State),
+ VMSTATE_UINT16(peak_pin, ADM1272State),
+
+ VMSTATE_UINT8(pmon_control, ADM1272State),
+ VMSTATE_UINT16(pmon_config, ADM1272State),
+ VMSTATE_UINT16(alert1_config, ADM1272State),
+ VMSTATE_UINT16(alert2_config, ADM1272State),
+ VMSTATE_UINT16(device_config, ADM1272State),
+
+ VMSTATE_UINT16(hysteresis_low, ADM1272State),
+ VMSTATE_UINT16(hysteresis_high, ADM1272State),
+ VMSTATE_UINT8(status_hysteresis, ADM1272State),
+ VMSTATE_UINT8(status_gpio, ADM1272State),
+
+ VMSTATE_UINT16(strt_up_iout_lim, ADM1272State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void adm1272_init(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint64_t flags = PB_HAS_VOUT_MODE | PB_HAS_VOUT | PB_HAS_VIN | PB_HAS_IOUT |
+ PB_HAS_PIN | PB_HAS_TEMPERATURE | PB_HAS_MFR_INFO;
+
+ pmbus_page_config(pmdev, 0, flags);
+
+ object_property_add(obj, "vin", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_vin);
+
+ object_property_add(obj, "vout", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_vout);
+
+ object_property_add(obj, "iout", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_iout);
+
+ object_property_add(obj, "pin", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_pin);
+
+}
+
+static void adm1272_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
+
+ dc->desc = "Analog Devices ADM1272 Hot Swap controller";
+ dc->vmsd = &vmstate_adm1272;
+ k->write_data = adm1272_write_data;
+ k->receive_byte = adm1272_read_byte;
+ k->device_num_pages = 1;
+
+ rc->phases.exit = adm1272_exit_reset;
+}
+
+static const TypeInfo adm1272_info = {
+ .name = TYPE_ADM1272,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(ADM1272State),
+ .instance_init = adm1272_init,
+ .class_init = adm1272_class_init,
+};
+
+static void adm1272_register_types(void)
+{
+ type_register_static(&adm1272_info);
+}
+
+type_init(adm1272_register_types)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 20bd60f10b..e141084a79 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -371,6 +371,7 @@ config XLNX_VERSAL
config NPCM7XX
bool
select A9MPCORE
+ select ADM1272
select ARM_GIC
select AT24C # EEPROM
select PL310 # cache controller
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index c71ed25820..0774c1f70c 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -14,6 +14,10 @@ config ARMSSE_CPU_PWRCTRL
config MAX111X
bool
+config ADM1272
+ bool
+ depends on I2C
+
config TMP105
bool
depends on I2C
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 66e1648533..8666e9225a 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -10,6 +10,7 @@ softmmu_ss.add(when: 'CONFIG_SGA', if_true: files('sga.c'))
softmmu_ss.add(when: 'CONFIG_TMP105', if_true: files('tmp105.c'))
softmmu_ss.add(when: 'CONFIG_TMP421', if_true: files('tmp421.c'))
softmmu_ss.add(when: 'CONFIG_EMC141X', if_true: files('emc141x.c'))
+softmmu_ss.add(when: 'CONFIG_ADM1272', if_true: files('adm1272.c'))
softmmu_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
softmmu_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
softmmu_ss.add(when: 'CONFIG_LED', if_true: files('led.c'))
--
2.31.1.751.gd2f1c929bd-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v3 3/5] tests/qtest: add tests for ADM1272 device model
2021-05-18 18:45 [PATCH v3 0/5] Add support for PMBus in QEMU Titus Rwantare
2021-05-18 18:45 ` [PATCH v3 1/5] hw/i2c: add support for PMBus Titus Rwantare
2021-05-18 18:45 ` [PATCH v3 2/5] hw/misc: add ADM1272 device Titus Rwantare
@ 2021-05-18 18:45 ` Titus Rwantare
2021-05-18 18:45 ` [PATCH v3 4/5] hw/misc: add MAX34451 device Titus Rwantare
` (2 subsequent siblings)
5 siblings, 0 replies; 12+ messages in thread
From: Titus Rwantare @ 2021-05-18 18:45 UTC (permalink / raw)
To: Corey Minyard; +Cc: qemu-arm, qemu-devel, f4bug, Titus Rwantare, Joel Stanley
Signed-off-by: Titus Rwantare <titusr@google.com>
Reviewed-by: Joel Stanley <joel@jms.id.au>
---
tests/qtest/adm1272-test.c | 445 +++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 446 insertions(+)
create mode 100644 tests/qtest/adm1272-test.c
diff --git a/tests/qtest/adm1272-test.c b/tests/qtest/adm1272-test.c
new file mode 100644
index 0000000000..63f8514801
--- /dev/null
+++ b/tests/qtest/adm1272-test.c
@@ -0,0 +1,445 @@
+/*
+ * QTests for the ADM1272 hotswap controller
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include <math.h>
+#include "hw/i2c/pmbus_device.h"
+#include "libqtest-single.h"
+#include "libqos/qgraph.h"
+#include "libqos/i2c.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qnum.h"
+#include "qemu/bitops.h"
+
+#define TEST_ID "adm1272-test"
+#define TEST_ADDR (0x10)
+
+#define ADM1272_RESTART_TIME 0xCC
+#define ADM1272_MFR_PEAK_IOUT 0xD0
+#define ADM1272_MFR_PEAK_VIN 0xD1
+#define ADM1272_MFR_PEAK_VOUT 0xD2
+#define ADM1272_MFR_PMON_CONTROL 0xD3
+#define ADM1272_MFR_PMON_CONFIG 0xD4
+#define ADM1272_MFR_ALERT1_CONFIG 0xD5
+#define ADM1272_MFR_ALERT2_CONFIG 0xD6
+#define ADM1272_MFR_PEAK_TEMPERATURE 0xD7
+#define ADM1272_MFR_DEVICE_CONFIG 0xD8
+#define ADM1272_MFR_POWER_CYCLE 0xD9
+#define ADM1272_MFR_PEAK_PIN 0xDA
+#define ADM1272_MFR_READ_PIN_EXT 0xDB
+#define ADM1272_MFR_READ_EIN_EXT 0xDC
+
+#define ADM1272_HYSTERESIS_LOW 0xF2
+#define ADM1272_HYSTERESIS_HIGH 0xF3
+#define ADM1272_STATUS_HYSTERESIS 0xF4
+#define ADM1272_STATUS_GPIO 0xF5
+#define ADM1272_STRT_UP_IOUT_LIM 0xF6
+
+/* Defaults */
+#define ADM1272_OPERATION_DEFAULT 0x80
+#define ADM1272_CAPABILITY_DEFAULT 0xB0
+#define ADM1272_CAPABILITY_NO_PEC 0x30
+#define ADM1272_DIRECT_MODE 0x40
+#define ADM1272_HIGH_LIMIT_DEFAULT 0x0FFF
+#define ADM1272_PIN_OP_DEFAULT 0x7FFF
+#define ADM1272_PMBUS_REVISION_DEFAULT 0x22
+#define ADM1272_MFR_ID_DEFAULT "ADI"
+#define ADM1272_MODEL_DEFAULT "ADM1272-A1"
+#define ADM1272_MFR_DEFAULT_REVISION "25"
+#define ADM1272_DEFAULT_DATE "160301"
+#define ADM1272_RESTART_TIME_DEFAULT 0x64
+#define ADM1272_PMON_CONTROL_DEFAULT 0x1
+#define ADM1272_PMON_CONFIG_DEFAULT 0x3F35
+#define ADM1272_DEVICE_CONFIG_DEFAULT 0x8
+#define ADM1272_HYSTERESIS_HIGH_DEFAULT 0xFFFF
+#define ADM1272_STRT_UP_IOUT_LIM_DEFAULT 0x000F
+#define ADM1272_VOLT_DEFAULT 12000
+#define ADM1272_IOUT_DEFAULT 25000
+#define ADM1272_PWR_DEFAULT 300 /* 12V 25A */
+#define ADM1272_SHUNT 300 /* micro-ohms */
+#define ADM1272_VOLTAGE_COEFF_DEFAULT 1
+#define ADM1272_CURRENT_COEFF_DEFAULT 3
+#define ADM1272_PWR_COEFF_DEFAULT 7
+#define ADM1272_IOUT_OFFSET 0x5000
+#define ADM1272_IOUT_OFFSET 0x5000
+
+static const PMBusCoefficients adm1272_coefficients[] = {
+ [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */
+ [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */
+ [2] = { 1326, 20480, -1 }, /* current, vsense range 15mV */
+ [3] = { 663, 20480, -1 }, /* current, vsense range 30mV */
+ [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */
+ [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */
+ [6] = { 17561, 0, -3 }, /* power, vrange 60V, irange 30mV */
+ [7] = { 10535, 0, -3 }, /* power, vrange 100V, irange 30mV */
+ [8] = { 42, 31871, -1 }, /* temperature */
+};
+
+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;
+}
+
+
+static uint16_t adm1272_millivolts_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_millivolts(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t adm1272_milliamps_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
+ /* Y = (m * r_sense * x - b) * 10^R */
+ c.m = c.m * ADM1272_SHUNT / 1000; /* micro-ohms */
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_milliamps(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t adm1272_watts_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_watts(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t qmp_adm1272_get(const char *id, const char *property)
+{
+ QDict *response;
+ uint64_t ret;
+
+ response = qmp("{ 'execute': 'qom-get', 'arguments': { 'path': %s, "
+ "'property': %s } }", id, property);
+ g_assert(qdict_haskey(response, "return"));
+ ret = qnum_get_uint(qobject_to(QNum, qdict_get(response, "return")));
+ qobject_unref(response);
+ return ret;
+}
+
+static void qmp_adm1272_set(const char *id,
+ const char *property,
+ uint16_t value)
+{
+ QDict *response;
+
+ response = qmp("{ 'execute': 'qom-set', 'arguments': { 'path': %s, "
+ "'property': %s, 'value': %u } }", id, property, value);
+ g_assert(qdict_haskey(response, "return"));
+ qobject_unref(response);
+}
+
+/* PMBus commands are little endian vs i2c_set16 in i2c.h which is big endian */
+static uint16_t adm1272_i2c_get16(QI2CDevice *i2cdev, uint8_t reg)
+{
+ uint8_t resp[2];
+ i2c_read_block(i2cdev, reg, resp, sizeof(resp));
+ return (resp[1] << 8) | resp[0];
+}
+
+/* PMBus commands are little endian vs i2c_set16 in i2c.h which is big endian */
+static void adm1272_i2c_set16(QI2CDevice *i2cdev, uint8_t reg, uint16_t value)
+{
+ uint8_t data[2];
+
+ data[0] = value & 255;
+ data[1] = value >> 8;
+ i2c_write_block(i2cdev, reg, data, sizeof(data));
+}
+
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t value, i2c_value;
+ int16_t err;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ value = qmp_adm1272_get(TEST_ID, "vout");
+ err = ADM1272_VOLT_DEFAULT - value;
+ g_assert_cmpuint(abs(err), <, ADM1272_VOLT_DEFAULT / 20);
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
+ g_assert_cmphex(i2c_value, ==, ADM1272_OPERATION_DEFAULT);
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
+ g_assert_cmphex(i2c_value, ==, ADM1272_DIRECT_MODE);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_IOUT_OC_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_OT_FAULT_LIMIT);
+ g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_OT_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VIN_OV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VIN_UV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_PIN_OP_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, ADM1272_PIN_OP_DEFAULT);
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_REVISION);
+ g_assert_cmphex(i2c_value, ==, ADM1272_PMBUS_REVISION_DEFAULT);
+
+ i2c_value = i2c_get8(i2cdev, ADM1272_MFR_PMON_CONTROL);
+ g_assert_cmphex(i2c_value, ==, ADM1272_PMON_CONTROL_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_PMON_CONFIG);
+ g_assert_cmphex(i2c_value, ==, ADM1272_PMON_CONFIG_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_DEVICE_CONFIG);
+ g_assert_cmphex(i2c_value, ==, ADM1272_DEVICE_CONFIG_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_HYSTERESIS_HIGH);
+ g_assert_cmphex(i2c_value, ==, ADM1272_HYSTERESIS_HIGH_DEFAULT);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_STRT_UP_IOUT_LIM);
+ g_assert_cmphex(i2c_value, ==, ADM1272_STRT_UP_IOUT_LIM_DEFAULT);
+}
+
+/* test qmp access */
+static void test_tx_rx(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value, value, i2c_voltage, i2c_pwr, lossy_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ /* converting to direct mode is lossy - we generate the same loss here */
+ lossy_value =
+ adm1272_direct_to_millivolts(adm1272_millivolts_to_direct(1000));
+ qmp_adm1272_set(TEST_ID, "vin", 1000);
+ value = qmp_adm1272_get(TEST_ID, "vin");
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VIN);
+ i2c_voltage = adm1272_direct_to_millivolts(i2c_value);
+ g_assert_cmpuint(value, ==, i2c_voltage);
+ g_assert_cmpuint(i2c_voltage, ==, lossy_value);
+
+ lossy_value =
+ adm1272_direct_to_millivolts(adm1272_millivolts_to_direct(1500));
+ qmp_adm1272_set(TEST_ID, "vout", 1500);
+ value = qmp_adm1272_get(TEST_ID, "vout");
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ i2c_voltage = adm1272_direct_to_millivolts(i2c_value);
+ g_assert_cmpuint(value, ==, i2c_voltage);
+ g_assert_cmpuint(i2c_voltage, ==, lossy_value);
+
+ lossy_value =
+ adm1272_direct_to_milliamps(adm1272_milliamps_to_direct(1600));
+ qmp_adm1272_set(TEST_ID, "iout", 1600);
+ value = qmp_adm1272_get(TEST_ID, "iout");
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_IOUT);
+ i2c_value = adm1272_direct_to_milliamps(i2c_value);
+ g_assert_cmphex(value, ==, i2c_value);
+ g_assert_cmphex(i2c_value, ==, lossy_value);
+
+ lossy_value =
+ adm1272_direct_to_watts(adm1272_watts_to_direct(320));
+ qmp_adm1272_set(TEST_ID, "pin", 320);
+ value = qmp_adm1272_get(TEST_ID, "pin");
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_PIN);
+ i2c_pwr = adm1272_direct_to_watts(i2c_value);
+ g_assert_cmphex(value, ==, i2c_pwr);
+ g_assert_cmphex(i2c_pwr, ==, lossy_value);
+}
+
+/* test r/w registers */
+static void test_rw_regs(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ adm1272_i2c_set16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT, 0xABCD);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0xABCD);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT, 0xCDEF);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0xCDEF);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_IOUT_OC_WARN_LIMIT, 0x1234);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_IOUT_OC_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0x1234);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_OT_FAULT_LIMIT, 0x5678);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_OT_FAULT_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0x5678);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_OT_WARN_LIMIT, 0xABDC);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_OT_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0xABDC);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_VIN_OV_WARN_LIMIT, 0xCDEF);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VIN_OV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0xCDEF);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_VIN_UV_WARN_LIMIT, 0x2345);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VIN_UV_WARN_LIMIT);
+ g_assert_cmphex(i2c_value, ==, 0x2345);
+
+ i2c_set8(i2cdev, ADM1272_RESTART_TIME, 0xF8);
+ i2c_value = i2c_get8(i2cdev, ADM1272_RESTART_TIME);
+ g_assert_cmphex(i2c_value, ==, 0xF8);
+
+ i2c_set8(i2cdev, ADM1272_MFR_PMON_CONTROL, 0);
+ i2c_value = i2c_get8(i2cdev, ADM1272_MFR_PMON_CONTROL);
+ g_assert_cmpuint(i2c_value, ==, 0);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_MFR_PMON_CONFIG, 0xDEF0);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_PMON_CONFIG);
+ g_assert_cmphex(i2c_value, ==, 0xDEF0);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_MFR_ALERT1_CONFIG, 0x0123);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_ALERT1_CONFIG);
+ g_assert_cmphex(i2c_value, ==, 0x0123);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_MFR_ALERT2_CONFIG, 0x9876);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_ALERT2_CONFIG);
+ g_assert_cmphex(i2c_value, ==, 0x9876);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_MFR_DEVICE_CONFIG, 0x3456);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_DEVICE_CONFIG);
+ g_assert_cmphex(i2c_value, ==, 0x3456);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_HYSTERESIS_LOW, 0xCABA);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_HYSTERESIS_LOW);
+ g_assert_cmphex(i2c_value, ==, 0xCABA);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_HYSTERESIS_HIGH, 0x6789);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_HYSTERESIS_HIGH);
+ g_assert_cmphex(i2c_value, ==, 0x6789);
+
+ adm1272_i2c_set16(i2cdev, ADM1272_STRT_UP_IOUT_LIM, 0x9876);
+ i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_STRT_UP_IOUT_LIM);
+ g_assert_cmphex(i2c_value, ==, 0x9876);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_OPERATION, 0xA);
+ i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
+ g_assert_cmphex(i2c_value, ==, 0xA);
+}
+
+/* test read-only registers */
+static void test_ro_regs(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_init_value, i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VIN);
+ adm1272_i2c_set16(i2cdev, PMBUS_READ_VIN, 0xBEEF);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VIN);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ adm1272_i2c_set16(i2cdev, PMBUS_READ_VOUT, 0x1234);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_IOUT);
+ adm1272_i2c_set16(i2cdev, PMBUS_READ_IOUT, 0x6547);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_IOUT);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ adm1272_i2c_set16(i2cdev, PMBUS_READ_TEMPERATURE_1, 0x1597);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_PIN);
+ adm1272_i2c_set16(i2cdev, PMBUS_READ_PIN, 0xDEAD);
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_PIN);
+ g_assert_cmphex(i2c_init_value, ==, i2c_value);
+}
+
+/* test voltage fault handling */
+static void test_voltage_faults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value;
+ uint8_t i2c_byte;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ adm1272_i2c_set16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT,
+ adm1272_millivolts_to_direct(5000));
+ qmp_adm1272_set(TEST_ID, "vout", 5100);
+
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_STATUS_WORD);
+ i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_VOUT);
+ g_assert_true((i2c_value & PB_STATUS_VOUT) != 0);
+ g_assert_true((i2c_byte & PB_STATUS_VOUT_OV_WARN) != 0);
+
+ qmp_adm1272_set(TEST_ID, "vout", 4500);
+ i2c_set8(i2cdev, PMBUS_CLEAR_FAULTS, 0);
+ i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_VOUT);
+ g_assert_true((i2c_byte & PB_STATUS_VOUT_OV_WARN) == 0);
+
+ adm1272_i2c_set16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT,
+ adm1272_millivolts_to_direct(4600));
+ i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_STATUS_WORD);
+ i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_VOUT);
+ g_assert_true((i2c_value & PB_STATUS_VOUT) != 0);
+ g_assert_true((i2c_byte & PB_STATUS_VOUT_UV_WARN) != 0);
+
+}
+
+static void adm1272_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" TEST_ID ",address=0x10"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { TEST_ADDR });
+
+ qos_node_create_driver("adm1272", i2c_device_create);
+ qos_node_consumes("adm1272", "i2c-bus", &opts);
+
+ qos_add_test("test_defaults", "adm1272", test_defaults, NULL);
+ qos_add_test("test_tx_rx", "adm1272", test_tx_rx, NULL);
+ qos_add_test("test_rw_regs", "adm1272", test_rw_regs, NULL);
+ qos_add_test("test_ro_regs", "adm1272", test_ro_regs, NULL);
+ qos_add_test("test_ov_faults", "adm1272", test_voltage_faults, NULL);
+}
+libqos_init(adm1272_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 49de74ff59..99040885fb 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -201,6 +201,7 @@ qtests_s390x = \
qos_test_ss = ss.source_set()
qos_test_ss.add(
'ac97-test.c',
+ 'adm1272-test.c',
'ds1338-test.c',
'e1000-test.c',
'e1000e-test.c',
--
2.31.1.751.gd2f1c929bd-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v3 4/5] hw/misc: add MAX34451 device
2021-05-18 18:45 [PATCH v3 0/5] Add support for PMBus in QEMU Titus Rwantare
` (2 preceding siblings ...)
2021-05-18 18:45 ` [PATCH v3 3/5] tests/qtest: add tests for ADM1272 device model Titus Rwantare
@ 2021-05-18 18:45 ` Titus Rwantare
2021-05-18 18:45 ` [PATCH v3 5/5] tests/qtest: add tests for MAX34451 device model Titus Rwantare
2021-05-18 19:45 ` [PATCH v3 0/5] Add support for PMBus in QEMU Corey Minyard
5 siblings, 0 replies; 12+ messages in thread
From: Titus Rwantare @ 2021-05-18 18:45 UTC (permalink / raw)
To: Corey Minyard
Cc: qemu-arm, qemu-devel, f4bug, Titus Rwantare, Joel Stanley, Hao Wu
The MAX34451 is a Maxim power-supply system manager that can monitor up to 16 voltage rails or currents. It also contains a temperature sensor and supports up to four external temperature sensors.
This commit adds support for interfacing with it, and setting limits on the supported sensors.
Reviewed-by: Joel Stanley <joel@jms.id.au>
Reviewed-by: Hao Wu <wuhaotsh@google.com>
Signed-off-by: Titus Rwantare <titusr@google.com>
---
hw/misc/max34451.c | 716 ++++++++++++++++++++++++++++++++++++++++++++
hw/arm/Kconfig | 1 +
hw/misc/Kconfig | 4 +
hw/misc/meson.build | 1 +
4 files changed, 722 insertions(+)
create mode 100644 hw/misc/max34451.c
diff --git a/hw/misc/max34451.c b/hw/misc/max34451.c
new file mode 100644
index 0000000000..5dd47a49f5
--- /dev/null
+++ b/hw/misc/max34451.c
@@ -0,0 +1,716 @@
+/*
+ * Maxim MAX34451 PMBus 16-Channel V/I monitor and 12-Channel Sequencer/Marginer
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/pmbus_device.h"
+#include "hw/irq.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define TYPE_MAX34451 "max34451"
+#define MAX34451(obj) OBJECT_CHECK(MAX34451State, (obj), TYPE_MAX34451)
+
+#define MAX34451_MFR_MODE 0xD1
+#define MAX34451_MFR_PSEN_CONFIG 0xD2
+#define MAX34451_MFR_VOUT_PEAK 0xD4
+#define MAX34451_MFR_IOUT_PEAK 0xD5
+#define MAX34451_MFR_TEMPERATURE_PEAK 0xD6
+#define MAX34451_MFR_VOUT_MIN 0xD7
+#define MAX34451_MFR_NV_LOG_CONFIG 0xD8
+#define MAX34451_MFR_FAULT_RESPONSE 0xD9
+#define MAX34451_MFR_FAULT_RETRY 0xDA
+#define MAX34451_MFR_NV_FAULT_LOG 0xDC
+#define MAX34451_MFR_TIME_COUNT 0xDD
+#define MAX34451_MFR_MARGIN_CONFIG 0xDF
+#define MAX34451_MFR_FW_SERIAL 0xE0
+#define MAX34451_MFR_IOUT_AVG 0xE2
+#define MAX34451_MFR_CHANNEL_CONFIG 0xE4
+#define MAX34451_MFR_TON_SEQ_MAX 0xE6
+#define MAX34451_MFR_PWM_CONFIG 0xE7
+#define MAX34451_MFR_SEQ_CONFIG 0xE8
+#define MAX34451_MFR_STORE_ALL 0xEE
+#define MAX34451_MFR_RESTORE_ALL 0xEF
+#define MAX34451_MFR_TEMP_SENSOR_CONFIG 0xF0
+#define MAX34451_MFR_STORE_SINGLE 0xFC
+#define MAX34451_MFR_CRC 0xFE
+
+#define MAX34451_NUM_MARGINED_PSU 12
+#define MAX34451_NUM_PWR_DEVICES 16
+#define MAX34451_NUM_TEMP_DEVICES 5
+#define MAX34451_NUM_PAGES 21
+
+#define DEFAULT_OP_ON 0x80
+#define DEFAULT_CAPABILITY 0x20
+#define DEFAULT_ON_OFF_CONFIG 0x1a
+#define DEFAULT_VOUT_MODE 0x40
+#define DEFAULT_TEMPERATURE 2500
+#define DEFAULT_SCALE 0x7FFF
+#define DEFAULT_OV_LIMIT 0x7FFF
+#define DEFAULT_OC_LIMIT 0x7FFF
+#define DEFAULT_OT_LIMIT 0x7FFF
+#define DEFAULT_VMIN 0x7FFF
+#define DEFAULT_TON_FAULT_LIMIT 0xFFFF
+#define DEFAULT_CHANNEL_CONFIG 0x20
+#define DEFAULT_TEXT 0x3130313031303130
+
+/**
+ * MAX34451State:
+ * @code: The command code received
+ * @page: Each page corresponds to a device monitored by the Max 34451
+ * The page register determines the available commands depending on device
+ ___________________________________________________________________________
+ | 0 | Power supply monitored by RS0, controlled by PSEN0, and |
+ | | margined with PWM0. |
+ |_______|___________________________________________________________________|
+ | 1 | Power supply monitored by RS1, controlled by PSEN1, and |
+ | | margined with PWM1. |
+ |_______|___________________________________________________________________|
+ | 2 | Power supply monitored by RS2, controlled by PSEN2, and |
+ | | margined with PWM2. |
+ |_______|___________________________________________________________________|
+ | 3 | Power supply monitored by RS3, controlled by PSEN3, and |
+ | | margined with PWM3. |
+ |_______|___________________________________________________________________|
+ | 4 | Power supply monitored by RS4, controlled by PSEN4, and |
+ | | margined with PWM4. |
+ |_______|___________________________________________________________________|
+ | 5 | Power supply monitored by RS5, controlled by PSEN5, and |
+ | | margined with PWM5. |
+ |_______|___________________________________________________________________|
+ | 6 | Power supply monitored by RS6, controlled by PSEN6, and |
+ | | margined with PWM6. |
+ |_______|___________________________________________________________________|
+ | 7 | Power supply monitored by RS7, controlled by PSEN7, and |
+ | | margined with PWM7. |
+ |_______|___________________________________________________________________|
+ | 8 | Power supply monitored by RS8, controlled by PSEN8, and |
+ | | optionally margined by OUT0 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 9 | Power supply monitored by RS9, controlled by PSEN9, and |
+ | | optionally margined by OUT1 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 10 | Power supply monitored by RS10, controlled by PSEN10, and |
+ | | optionally margined by OUT2 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 11 | Power supply monitored by RS11, controlled by PSEN11, and |
+ | | optionally margined by OUT3 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 12 | ADC channel 12 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 13 | ADC channel 13 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 14 | ADC channel 14 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 15 | ADC channel 15 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 16 | Internal temperature sensor. |
+ |_______|___________________________________________________________________|
+ | 17 | External DS75LV temperature sensor with I2C address 90h. |
+ |_______|___________________________________________________________________|
+ | 18 | External DS75LV temperature sensor with I2C address 92h. |
+ |_______|___________________________________________________________________|
+ | 19 | External DS75LV temperature sensor with I2C address 94h. |
+ |_______|___________________________________________________________________|
+ | 20 | External DS75LV temperature sensor with I2C address 96h. |
+ |_______|___________________________________________________________________|
+ | 21–254| Reserved. |
+ |_______|___________________________________________________________________|
+ | 255 | Applies to all pages. |
+ |_______|___________________________________________________________________|
+ *
+ * @operation: Turn on and off power supplies
+ * @on_off_config: Configure the power supply on and off transition behaviour
+ * @write_protect: protect against changes to the device's memory
+ * @vout_margin_high: the voltage when OPERATION is set to margin high
+ * @vout_margin_low: the voltage when OPERATION is set to margin low
+ * @vout_scale: scale ADC reading to actual device reading if different
+ * @iout_cal_gain: set ratio of the voltage at the ADC input to sensed current
+ */
+typedef struct MAX34451State {
+ PMBusDevice parent;
+
+ uint16_t power_good_on[MAX34451_NUM_PWR_DEVICES];
+ uint16_t power_good_off[MAX34451_NUM_PWR_DEVICES];
+ uint16_t ton_delay[MAX34451_NUM_MARGINED_PSU];
+ uint16_t ton_max_fault_limit[MAX34451_NUM_MARGINED_PSU];
+ uint16_t toff_delay[MAX34451_NUM_MARGINED_PSU];
+ uint8_t status_mfr_specific[MAX34451_NUM_PWR_DEVICES];
+ /* Manufacturer specific function */
+ uint64_t mfr_location;
+ uint64_t mfr_date;
+ uint64_t mfr_serial;
+ uint16_t mfr_mode;
+ uint32_t psen_config[MAX34451_NUM_MARGINED_PSU];
+ uint16_t vout_peak[MAX34451_NUM_PWR_DEVICES];
+ uint16_t iout_peak[MAX34451_NUM_PWR_DEVICES];
+ uint16_t temperature_peak[MAX34451_NUM_TEMP_DEVICES];
+ uint16_t vout_min[MAX34451_NUM_PWR_DEVICES];
+ uint16_t nv_log_config;
+ uint32_t fault_response[MAX34451_NUM_PWR_DEVICES];
+ uint16_t fault_retry;
+ uint32_t fault_log;
+ uint32_t time_count;
+ uint16_t margin_config[MAX34451_NUM_MARGINED_PSU];
+ uint16_t fw_serial;
+ uint16_t iout_avg[MAX34451_NUM_PWR_DEVICES];
+ uint16_t channel_config[MAX34451_NUM_PWR_DEVICES];
+ uint16_t ton_seq_max[MAX34451_NUM_MARGINED_PSU];
+ uint32_t pwm_config[MAX34451_NUM_MARGINED_PSU];
+ uint32_t seq_config[MAX34451_NUM_MARGINED_PSU];
+ uint16_t temp_sensor_config[MAX34451_NUM_TEMP_DEVICES];
+ uint16_t store_single;
+ uint16_t crc;
+} MAX34451State;
+
+
+static void max34451_check_limits(MAX34451State *s)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(s);
+
+ pmbus_check_limits(pmdev);
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ if (pmdev->pages[i].read_vout == 0) { /* PSU disabled */
+ continue;
+ }
+
+ if (pmdev->pages[i].read_vout > s->vout_peak[i]) {
+ s->vout_peak[i] = pmdev->pages[i].read_vout;
+ }
+
+ if (pmdev->pages[i].read_vout < s->vout_min[i]) {
+ s->vout_min[i] = pmdev->pages[i].read_vout;
+ }
+
+ if (pmdev->pages[i].read_iout > s->iout_peak[i]) {
+ s->iout_peak[i] = pmdev->pages[i].read_iout;
+ }
+ }
+
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ if (pmdev->pages[i + 16].read_temperature_1 > s->temperature_peak[i]) {
+ s->temperature_peak[i] = pmdev->pages[i + 16].read_temperature_1;
+ }
+ }
+}
+
+static uint8_t max34451_read_byte(PMBusDevice *pmdev)
+{
+ MAX34451State *s = MAX34451(pmdev);
+ switch (pmdev->code) {
+
+ case PMBUS_POWER_GOOD_ON:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->power_good_on[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_POWER_GOOD_OFF:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->power_good_off[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_TON_DELAY:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->ton_delay[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_TON_MAX_FAULT_LIMIT:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->ton_max_fault_limit[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_TOFF_DELAY:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->toff_delay[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_STATUS_MFR_SPECIFIC:
+ if (pmdev->page < 16) {
+ pmbus_send8(pmdev, s->status_mfr_specific[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_MFR_ID:
+ pmbus_send8(pmdev, 0x4d); /* Maxim */
+ break;
+
+ case PMBUS_MFR_MODEL:
+ pmbus_send8(pmdev, 0x59);
+ break;
+
+ case PMBUS_MFR_LOCATION:
+ pmbus_send64(pmdev, s->mfr_location);
+ break;
+
+ case PMBUS_MFR_DATE:
+ pmbus_send64(pmdev, s->mfr_date);
+ break;
+
+ case PMBUS_MFR_SERIAL:
+ pmbus_send64(pmdev, s->mfr_serial);
+ break;
+
+ case MAX34451_MFR_MODE:
+ pmbus_send16(pmdev, s->mfr_mode);
+ break;
+
+ case MAX34451_MFR_PSEN_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send32(pmdev, s->psen_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_PEAK:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->vout_peak[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_IOUT_PEAK:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->iout_peak[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_TEMPERATURE_PEAK:
+ if (15 < pmdev->page && pmdev->page < 21) {
+ pmbus_send16(pmdev, s->temperature_peak[pmdev->page % 16]);
+ } else {
+ pmbus_send16(pmdev, s->temperature_peak[0]);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_MIN:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->vout_min[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_NV_LOG_CONFIG:
+ pmbus_send16(pmdev, s->nv_log_config);
+ break;
+
+ case MAX34451_MFR_FAULT_RESPONSE:
+ if (pmdev->page < 16) {
+ pmbus_send32(pmdev, s->fault_response[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_FAULT_RETRY:
+ pmbus_send32(pmdev, s->fault_retry);
+ break;
+
+ case MAX34451_MFR_NV_FAULT_LOG:
+ pmbus_send32(pmdev, s->fault_log);
+ break;
+
+ case MAX34451_MFR_TIME_COUNT:
+ pmbus_send32(pmdev, s->time_count);
+ break;
+
+ case MAX34451_MFR_MARGIN_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->margin_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_FW_SERIAL:
+ if (pmdev->page == 255) {
+ pmbus_send16(pmdev, 1); /* Firmware revision */
+ }
+ break;
+
+ case MAX34451_MFR_IOUT_AVG:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->iout_avg[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_CHANNEL_CONFIG:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->channel_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_TON_SEQ_MAX:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->ton_seq_max[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_PWM_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send32(pmdev, s->pwm_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_SEQ_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send32(pmdev, s->seq_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_TEMP_SENSOR_CONFIG:
+ if (15 < pmdev->page && pmdev->page < 21) {
+ pmbus_send32(pmdev, s->temp_sensor_config[pmdev->page % 16]);
+ }
+ break;
+
+ case MAX34451_MFR_STORE_SINGLE:
+ pmbus_send32(pmdev, s->store_single);
+ break;
+
+ case MAX34451_MFR_CRC:
+ pmbus_send32(pmdev, s->crc);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+ return 0xFF;
+}
+
+static int max34451_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+ uint8_t len)
+{
+ MAX34451State *s = MAX34451(pmdev);
+
+ if (len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+ return -1;
+ }
+
+ pmdev->code = buf[0]; /* PMBus command code */
+
+ if (len == 1) {
+ return 0;
+ }
+
+ /* Exclude command code from buffer */
+ buf++;
+ len--;
+ uint8_t index = pmdev->page;
+
+ switch (pmdev->code) {
+ case MAX34451_MFR_STORE_ALL:
+ case MAX34451_MFR_RESTORE_ALL:
+ case MAX34451_MFR_STORE_SINGLE:
+ /*
+ * TODO: hardware behaviour is to move the contents of volatile
+ * memory to non-volatile memory.
+ */
+ break;
+
+ case PMBUS_POWER_GOOD_ON: /* R/W word */
+ if (pmdev->page < MAX34451_NUM_PWR_DEVICES) {
+ s->power_good_on[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_POWER_GOOD_OFF: /* R/W word */
+ if (pmdev->page < MAX34451_NUM_PWR_DEVICES) {
+ s->power_good_off[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_TON_DELAY: /* R/W word */
+ if (pmdev->page < 12) {
+ s->ton_delay[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_TON_MAX_FAULT_LIMIT: /* R/W word */
+ if (pmdev->page < 12) {
+ s->ton_max_fault_limit[pmdev->page]
+ = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_TOFF_DELAY: /* R/W word */
+ if (pmdev->page < 12) {
+ s->toff_delay[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_MFR_LOCATION: /* R/W 64 */
+ s->mfr_location = pmbus_receive64(pmdev);
+ break;
+
+ case PMBUS_MFR_DATE: /* R/W 64 */
+ s->mfr_date = pmbus_receive64(pmdev);
+ break;
+
+ case PMBUS_MFR_SERIAL: /* R/W 64 */
+ s->mfr_serial = pmbus_receive64(pmdev);
+ break;
+
+ case MAX34451_MFR_MODE: /* R/W word */
+ s->mfr_mode = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_PSEN_CONFIG: /* R/W 32 */
+ if (pmdev->page < 12) {
+ s->psen_config[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_PEAK: /* R/W word */
+ if (pmdev->page < 16) {
+ s->vout_peak[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_IOUT_PEAK: /* R/W word */
+ if (pmdev->page < 16) {
+ s->iout_peak[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_TEMPERATURE_PEAK: /* R/W word */
+ if (15 < pmdev->page && pmdev->page < 21) {
+ s->temperature_peak[pmdev->page % 16]
+ = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_MIN: /* R/W word */
+ if (pmdev->page < 16) {
+ s->vout_min[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_NV_LOG_CONFIG: /* R/W word */
+ s->nv_log_config = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_FAULT_RESPONSE: /* R/W 32 */
+ if (pmdev->page < 16) {
+ s->fault_response[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_FAULT_RETRY: /* R/W word */
+ s->fault_retry = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_TIME_COUNT: /* R/W 32 */
+ s->time_count = pmbus_receive32(pmdev);
+ break;
+
+ case MAX34451_MFR_MARGIN_CONFIG: /* R/W word */
+ if (pmdev->page < 12) {
+ s->margin_config[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_CHANNEL_CONFIG: /* R/W word */
+ if (pmdev->page < 16) {
+ s->channel_config[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_TON_SEQ_MAX: /* R/W word */
+ if (pmdev->page < 12) {
+ s->ton_seq_max[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_PWM_CONFIG: /* R/W 32 */
+ if (pmdev->page < 12) {
+ s->pwm_config[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_SEQ_CONFIG: /* R/W 32 */
+ if (pmdev->page < 12) {
+ s->seq_config[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_TEMP_SENSOR_CONFIG: /* R/W word */
+ if (15 < pmdev->page && pmdev->page < 21) {
+ s->temp_sensor_config[pmdev->page % 16]
+ = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_CRC: /* R/W word */
+ s->crc = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_NV_FAULT_LOG:
+ case MAX34451_MFR_FW_SERIAL:
+ case MAX34451_MFR_IOUT_AVG:
+ /* Read only commands */
+ pmdev->pages[index].status_word |= PMBUS_STATUS_CML;
+ pmdev->pages[index].status_cml |= PB_CML_FAULT_INVALID_DATA;
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to read-only register 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+
+ return 0;
+}
+
+static void max34451_get(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ visit_type_uint16(v, name, (uint16_t *)opaque, errp);
+}
+
+static void max34451_set(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ MAX34451State *s = MAX34451(obj);
+ uint16_t *internal = opaque;
+ uint16_t value;
+ if (!visit_type_uint16(v, name, &value, errp)) {
+ return;
+ }
+
+ *internal = value;
+ max34451_check_limits(s);
+}
+
+/* used to init uint16_t arrays */
+static inline void *memset_word(void *s, uint16_t c, size_t n)
+{
+ size_t i;
+ uint16_t *p = s;
+
+ for (i = 0; i < n; i++) {
+ p[i] = c;
+ }
+
+ return s;
+}
+
+static void max34451_exit_reset(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ MAX34451State *s = MAX34451(obj);
+ pmdev->capability = DEFAULT_CAPABILITY;
+
+ for (int i = 0; i < MAX34451_NUM_PAGES; i++) {
+ pmdev->pages[i].operation = DEFAULT_OP_ON;
+ pmdev->pages[i].on_off_config = DEFAULT_ON_OFF_CONFIG;
+ pmdev->pages[i].revision = 0x11;
+ pmdev->pages[i].vout_mode = DEFAULT_VOUT_MODE;
+ }
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ pmdev->pages[i].vout_scale_monitor = DEFAULT_SCALE;
+ pmdev->pages[i].vout_ov_fault_limit = DEFAULT_OV_LIMIT;
+ pmdev->pages[i].vout_ov_warn_limit = DEFAULT_OV_LIMIT;
+ pmdev->pages[i].iout_oc_warn_limit = DEFAULT_OC_LIMIT;
+ pmdev->pages[i].iout_oc_fault_limit = DEFAULT_OC_LIMIT;
+ }
+
+ for (int i = 0; i < MAX34451_NUM_MARGINED_PSU; i++) {
+ pmdev->pages[i].ton_max_fault_limit = DEFAULT_TON_FAULT_LIMIT;
+ }
+
+ for (int i = 16; i < MAX34451_NUM_TEMP_DEVICES + 16; i++) {
+ pmdev->pages[i].read_temperature_1 = DEFAULT_TEMPERATURE;
+ pmdev->pages[i].ot_warn_limit = DEFAULT_OT_LIMIT;
+ pmdev->pages[i].ot_fault_limit = DEFAULT_OT_LIMIT;
+ }
+
+ memset_word(s->ton_max_fault_limit, DEFAULT_TON_FAULT_LIMIT,
+ MAX34451_NUM_MARGINED_PSU);
+ memset_word(s->channel_config, DEFAULT_CHANNEL_CONFIG,
+ MAX34451_NUM_PWR_DEVICES);
+ memset_word(s->vout_min, DEFAULT_VMIN, MAX34451_NUM_PWR_DEVICES);
+
+ s->mfr_location = DEFAULT_TEXT;
+ s->mfr_date = DEFAULT_TEXT;
+ s->mfr_serial = DEFAULT_TEXT;
+}
+
+static void max34451_init(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint64_t psu_flags = PB_HAS_VOUT | PB_HAS_IOUT | PB_HAS_VOUT_MODE |
+ PB_HAS_IOUT_GAIN;
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ pmbus_page_config(pmdev, i, psu_flags);
+ }
+
+ for (int i = 0; i < MAX34451_NUM_MARGINED_PSU; i++) {
+ pmbus_page_config(pmdev, i, psu_flags | PB_HAS_VOUT_MARGIN);
+ }
+
+ for (int i = 16; i < MAX34451_NUM_TEMP_DEVICES + 16; i++) {
+ pmbus_page_config(pmdev, i, PB_HAS_TEMPERATURE | PB_HAS_VOUT_MODE);
+ }
+
+ /* get and set the voltage in millivolts, max is 32767 mV */
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ object_property_add(obj, "vout[*]", "uint16",
+ max34451_get,
+ max34451_set, NULL, &pmdev->pages[i].read_vout);
+ }
+
+ /*
+ * get and set the temperature of the internal temperature sensor in
+ * centidegrees Celcius i.e.: 2500 -> 25.00 C, max is 327.67 C
+ */
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ object_property_add(obj, "temperature[*]", "uint16",
+ max34451_get,
+ max34451_set,
+ NULL,
+ &pmdev->pages[i + 16].read_temperature_1);
+ }
+
+}
+
+static void max34451_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
+ dc->desc = "Maxim MAX34451 16-Channel V/I monitor";
+ k->write_data = max34451_write_data;
+ k->receive_byte = max34451_read_byte;
+ k->device_num_pages = MAX34451_NUM_PAGES;
+ rc->phases.exit = max34451_exit_reset;
+}
+
+static const TypeInfo max34451_info = {
+ .name = TYPE_MAX34451,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(MAX34451State),
+ .instance_init = max34451_init,
+ .class_init = max34451_class_init,
+};
+
+static void max34451_register_types(void)
+{
+ type_register_static(&max34451_info);
+}
+
+type_init(max34451_register_types)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index e141084a79..d689941f64 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -374,6 +374,7 @@ config NPCM7XX
select ADM1272
select ARM_GIC
select AT24C # EEPROM
+ select MAX34451
select PL310 # cache controller
select PMBUS
select SERIAL
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 0774c1f70c..1747667984 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -30,6 +30,10 @@ config EMC141X
bool
depends on I2C
+config MAX34451
+ bool
+ depends on I2C
+
config ISA_DEBUG
bool
depends on ISA_BUS
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 8666e9225a..b62919e1fe 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -11,6 +11,7 @@ softmmu_ss.add(when: 'CONFIG_TMP105', if_true: files('tmp105.c'))
softmmu_ss.add(when: 'CONFIG_TMP421', if_true: files('tmp421.c'))
softmmu_ss.add(when: 'CONFIG_EMC141X', if_true: files('emc141x.c'))
softmmu_ss.add(when: 'CONFIG_ADM1272', if_true: files('adm1272.c'))
+softmmu_ss.add(when: 'CONFIG_MAX34451', if_true: files('max34451.c'))
softmmu_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c'))
softmmu_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c'))
softmmu_ss.add(when: 'CONFIG_LED', if_true: files('led.c'))
--
2.31.1.751.gd2f1c929bd-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v3 5/5] tests/qtest: add tests for MAX34451 device model
2021-05-18 18:45 [PATCH v3 0/5] Add support for PMBus in QEMU Titus Rwantare
` (3 preceding siblings ...)
2021-05-18 18:45 ` [PATCH v3 4/5] hw/misc: add MAX34451 device Titus Rwantare
@ 2021-05-18 18:45 ` Titus Rwantare
2021-05-18 19:45 ` [PATCH v3 0/5] Add support for PMBus in QEMU Corey Minyard
5 siblings, 0 replies; 12+ messages in thread
From: Titus Rwantare @ 2021-05-18 18:45 UTC (permalink / raw)
To: Corey Minyard; +Cc: qemu-arm, qemu-devel, f4bug, Titus Rwantare, Joel Stanley
Signed-off-by: Titus Rwantare <titusr@google.com>
Reviewed-by: Joel Stanley <joel@jms.id.au>
---
tests/qtest/max34451-test.c | 336 ++++++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
2 files changed, 337 insertions(+)
create mode 100644 tests/qtest/max34451-test.c
diff --git a/tests/qtest/max34451-test.c b/tests/qtest/max34451-test.c
new file mode 100644
index 0000000000..0c98d0764c
--- /dev/null
+++ b/tests/qtest/max34451-test.c
@@ -0,0 +1,336 @@
+/*
+ * QTests for the MAX34451 device
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/pmbus_device.h"
+#include "libqtest-single.h"
+#include "libqos/qgraph.h"
+#include "libqos/i2c.h"
+#include "qapi/qmp/qdict.h"
+#include "qapi/qmp/qnum.h"
+#include "qemu/bitops.h"
+
+#define TEST_ID "max34451-test"
+#define TEST_ADDR (0x4e)
+
+#define MAX34451_MFR_VOUT_PEAK 0xD4
+#define MAX34451_MFR_IOUT_PEAK 0xD5
+#define MAX34451_MFR_TEMPERATURE_PEAK 0xD6
+#define MAX34451_MFR_VOUT_MIN 0xD7
+
+#define DEFAULT_VOUT 0
+#define DEFAULT_UV_LIMIT 0
+#define DEFAULT_TEMPERATURE 2500
+#define DEFAULT_SCALE 0x7FFF
+#define DEFAULT_OV_LIMIT 0x7FFF
+#define DEFAULT_OC_LIMIT 0x7FFF
+#define DEFAULT_OT_LIMIT 0x7FFF
+#define DEFAULT_VMIN 0x7FFF
+#define DEFAULT_TON_FAULT_LIMIT 0xFFFF
+#define DEFAULT_CHANNEL_CONFIG 0x20
+#define DEFAULT_TEXT 0x20
+
+#define MAX34451_NUM_PWR_DEVICES 16
+#define MAX34451_NUM_TEMP_DEVICES 5
+
+
+static uint16_t qmp_max34451_get(const char *id, const char *property)
+{
+ QDict *response;
+ uint16_t ret;
+ response = qmp("{ 'execute': 'qom-get', 'arguments': { 'path': %s, "
+ "'property': %s } }", id, property);
+ g_assert(qdict_haskey(response, "return"));
+ ret = qnum_get_uint(qobject_to(QNum, qdict_get(response, "return")));
+ qobject_unref(response);
+ return ret;
+}
+
+static void qmp_max34451_set(const char *id,
+ const char *property,
+ uint16_t value)
+{
+ QDict *response;
+
+ response = qmp("{ 'execute': 'qom-set', 'arguments': { 'path': %s, "
+ "'property': %s, 'value': %u } }",
+ id, property, value);
+ g_assert(qdict_haskey(response, "return"));
+ qobject_unref(response);
+}
+
+/* PMBus commands are little endian vs i2c_set16 in i2c.h which is big endian */
+static uint16_t max34451_i2c_get16(QI2CDevice *i2cdev, uint8_t reg)
+{
+ uint8_t resp[2];
+ i2c_read_block(i2cdev, reg, resp, sizeof(resp));
+ return (resp[1] << 8) | resp[0];
+}
+
+/* PMBus commands are little endian vs i2c_set16 in i2c.h which is big endian */
+static void max34451_i2c_set16(QI2CDevice *i2cdev, uint8_t reg, uint16_t value)
+{
+ uint8_t data[2];
+
+ data[0] = value & 255;
+ data[1] = value >> 8;
+ i2c_write_block(i2cdev, reg, data, sizeof(data));
+}
+
+/* Test default values */
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t value, i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ char *path;
+
+ /* Default temperatures and temperature fault limits */
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ path = g_strdup_printf("temperature[%d]", i);
+ value = qmp_max34451_get(TEST_ID, path);
+ g_assert_cmpuint(value, ==, DEFAULT_TEMPERATURE);
+ g_free(path);
+
+ /* Temperature sensors start on page 16 */
+ i2c_set8(i2cdev, PMBUS_PAGE, i + 16);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_TEMPERATURE);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_OT_FAULT_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_OT_LIMIT);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_OT_WARN_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_OT_LIMIT);
+ }
+
+ /* Default voltages and fault limits */
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ path = g_strdup_printf("vout[%d]", i);
+ value = qmp_max34451_get(TEST_ID, path);
+ g_assert_cmpuint(value, ==, DEFAULT_VOUT);
+ g_free(path);
+
+ i2c_set8(i2cdev, PMBUS_PAGE, i);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_VOUT);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_OV_FAULT_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_OV_LIMIT);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_OV_LIMIT);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_UV_LIMIT);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_UV_FAULT_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_UV_LIMIT);
+
+ i2c_value = max34451_i2c_get16(i2cdev, MAX34451_MFR_VOUT_MIN);
+ g_assert_cmpuint(i2c_value, ==, DEFAULT_VMIN);
+ }
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
+ g_assert_cmphex(i2c_value, ==, 0x40); /* DIRECT mode */
+
+ i2c_value = i2c_get8(i2cdev, PMBUS_REVISION);
+ g_assert_cmphex(i2c_value, ==, 0x11); /* Rev 1.1 */
+}
+
+/* Test setting temperature */
+static void test_temperature(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t value, i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ char *path;
+
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ path = g_strdup_printf("temperature[%d]", i);
+ qmp_max34451_set(TEST_ID, path, 0xBE00 + i);
+ value = qmp_max34451_get(TEST_ID, path);
+ g_assert_cmphex(value, ==, 0xBE00 + i);
+ g_free(path);
+ }
+
+ /* compare qmp read with i2c read separately */
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ /* temperature[0] is on page 16 */
+ i2c_set8(i2cdev, PMBUS_PAGE, i + 16);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ g_assert_cmphex(i2c_value, ==, 0xBE00 + i);
+
+ i2c_value = max34451_i2c_get16(i2cdev, MAX34451_MFR_TEMPERATURE_PEAK);
+ g_assert_cmphex(i2c_value, ==, 0xBE00 + i);
+ }
+}
+
+/* Test setting voltage */
+static void test_voltage(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t value, i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ char *path;
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ path = g_strdup_printf("vout[%d]", i);
+ qmp_max34451_set(TEST_ID, path, 3000 + i);
+ value = qmp_max34451_get(TEST_ID, path);
+ g_assert_cmpuint(value, ==, 3000 + i);
+ g_free(path);
+ }
+
+ /* compare qmp read with i2c read separately */
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ i2c_set8(i2cdev, PMBUS_PAGE, i);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ g_assert_cmpuint(i2c_value, ==, 3000 + i);
+
+ i2c_value = max34451_i2c_get16(i2cdev, MAX34451_MFR_VOUT_PEAK);
+ g_assert_cmpuint(i2c_value, ==, 3000 + i);
+
+ i2c_value = max34451_i2c_get16(i2cdev, MAX34451_MFR_VOUT_MIN);
+ g_assert_cmpuint(i2c_value, ==, 3000 + i);
+ }
+}
+
+/* Test setting some read/write registers */
+static void test_rw_regs(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, PMBUS_PAGE, 11);
+ i2c_value = i2c_get8(i2cdev, PMBUS_PAGE);
+ g_assert_cmpuint(i2c_value, ==, 11);
+
+ i2c_set8(i2cdev, PMBUS_OPERATION, 1);
+ i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
+ g_assert_cmpuint(i2c_value, ==, 1);
+
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_MARGIN_HIGH, 5000);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_MARGIN_HIGH);
+ g_assert_cmpuint(i2c_value, ==, 5000);
+
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_MARGIN_LOW, 4000);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_MARGIN_LOW);
+ g_assert_cmpuint(i2c_value, ==, 4000);
+
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_OV_FAULT_LIMIT, 5500);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_OV_FAULT_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, 5500);
+
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT, 5600);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, 5600);
+
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_UV_FAULT_LIMIT, 5700);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_UV_FAULT_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, 5700);
+
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT, 5800);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT);
+ g_assert_cmpuint(i2c_value, ==, 5800);
+
+ max34451_i2c_set16(i2cdev, PMBUS_POWER_GOOD_ON, 5900);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_POWER_GOOD_ON);
+ g_assert_cmpuint(i2c_value, ==, 5900);
+
+ max34451_i2c_set16(i2cdev, PMBUS_POWER_GOOD_OFF, 6100);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_POWER_GOOD_OFF);
+ g_assert_cmpuint(i2c_value, ==, 6100);
+}
+
+/* Test that Read only registers can't be written */
+static void test_ro_regs(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value, i2c_init_value;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+ i2c_set8(i2cdev, PMBUS_PAGE, 1); /* move to page 1 */
+ i2c_init_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
+ i2c_set8(i2cdev, PMBUS_CAPABILITY, 0xF9);
+ i2c_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
+ g_assert_cmpuint(i2c_init_value, ==, i2c_value);
+
+ i2c_init_value = max34451_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ max34451_i2c_set16(i2cdev, PMBUS_READ_VOUT, 0xDEAD);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_VOUT);
+ g_assert_cmpuint(i2c_init_value, ==, i2c_value);
+ g_assert_cmphex(i2c_value, !=, 0xDEAD);
+
+ i2c_set8(i2cdev, PMBUS_PAGE, 16); /* move to page 16 */
+ i2c_init_value = max34451_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ max34451_i2c_set16(i2cdev, PMBUS_READ_TEMPERATURE_1, 0xABBA);
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
+ g_assert_cmpuint(i2c_init_value, ==, i2c_value);
+ g_assert_cmphex(i2c_value, !=, 0xABBA);
+}
+
+/* test over voltage faults */
+static void test_ov_faults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value;
+ uint8_t i2c_byte;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ char *path;
+ /* Test ov fault reporting */
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ path = g_strdup_printf("vout[%d]", i);
+ i2c_set8(i2cdev, PMBUS_PAGE, i);
+ max34451_i2c_set16(i2cdev, PMBUS_VOUT_OV_FAULT_LIMIT, 5000);
+ qmp_max34451_set(TEST_ID, path, 5100);
+ g_free(path);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_STATUS_WORD);
+ i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_VOUT);
+ g_assert_true((i2c_value & PB_STATUS_VOUT) != 0);
+ g_assert_true((i2c_byte & PB_STATUS_VOUT_OV_FAULT) != 0);
+ }
+}
+
+/* test over temperature faults */
+static void test_ot_faults(void *obj, void *data, QGuestAllocator *alloc)
+{
+ uint16_t i2c_value;
+ uint8_t i2c_byte;
+ QI2CDevice *i2cdev = (QI2CDevice *)obj;
+ char *path;
+
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ path = g_strdup_printf("temperature[%d]", i);
+ i2c_set8(i2cdev, PMBUS_PAGE, i + 16);
+ max34451_i2c_set16(i2cdev, PMBUS_OT_FAULT_LIMIT, 6000);
+ qmp_max34451_set(TEST_ID, path, 6100);
+ g_free(path);
+
+ i2c_value = max34451_i2c_get16(i2cdev, PMBUS_STATUS_WORD);
+ i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_TEMPERATURE);
+ g_assert_true((i2c_value & PB_STATUS_TEMPERATURE) != 0);
+ g_assert_true((i2c_byte & PB_STATUS_OT_FAULT) != 0);
+ }
+}
+
+static void max34451_register_nodes(void)
+{
+ QOSGraphEdgeOptions opts = {
+ .extra_device_opts = "id=" TEST_ID ",address=0x4e"
+ };
+ add_qi2c_address(&opts, &(QI2CAddress) { TEST_ADDR });
+
+ qos_node_create_driver("max34451", i2c_device_create);
+ qos_node_consumes("max34451", "i2c-bus", &opts);
+
+ qos_add_test("test_defaults", "max34451", test_defaults, NULL);
+ qos_add_test("test_temperature", "max34451", test_temperature, NULL);
+ qos_add_test("test_voltage", "max34451", test_voltage, NULL);
+ qos_add_test("test_rw_regs", "max34451", test_rw_regs, NULL);
+ qos_add_test("test_ro_regs", "max34451", test_ro_regs, NULL);
+ qos_add_test("test_ov_faults", "max34451", test_ov_faults, NULL);
+ qos_add_test("test_ot_faults", "max34451", test_ot_faults, NULL);
+}
+libqos_init(max34451_register_nodes);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 99040885fb..48eb646889 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -208,6 +208,7 @@ qos_test_ss.add(
'eepro100-test.c',
'es1370-test.c',
'ipoctal232-test.c',
+ 'max34451-test.c',
'megasas-test.c',
'ne2000-test.c',
'tulip-test.c',
--
2.31.1.751.gd2f1c929bd-goog
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v3 0/5] Add support for PMBus in QEMU
2021-05-18 18:45 [PATCH v3 0/5] Add support for PMBus in QEMU Titus Rwantare
` (4 preceding siblings ...)
2021-05-18 18:45 ` [PATCH v3 5/5] tests/qtest: add tests for MAX34451 device model Titus Rwantare
@ 2021-05-18 19:45 ` Corey Minyard
2021-05-18 19:50 ` Titus Rwantare
5 siblings, 1 reply; 12+ messages in thread
From: Corey Minyard @ 2021-05-18 19:45 UTC (permalink / raw)
To: Titus Rwantare; +Cc: qemu-arm, qemu-devel, f4bug
On Tue, May 18, 2021 at 11:45:22AM -0700, Titus Rwantare wrote:
> Hello,
>
> This patch series adds an interface to start supporting PMBus devices in QEMU.
> I’ve included two PMBus devices: MAX34451 and ADM1272.
I've reviewed all these patches, and beyond my one comment, they look
good.
I'm not too excited about putting the device files in misc. I know some
SMBus sensors are in there, but they really aren't miscellaneous. They
are really sensors. But unless we want to create a sensors directory
and move things into that, misc will have to do, I guess.
-corey
>
> PMBus is a variant of SMBus meant for digital management of power supplies.
> PMBus adds to the SMBus standard by defining a number of constants and commands
> used by compliant devices. The specification for PMBus can be found at:
>
> https://pmbus.org/specification-archives/
>
> Currently, the goal for these devices is to emulate basic functionality by
> reading and writing registers. Timing, and some logical operation is not
> implemented. This implementation supports nearly all available registers for
> PMBus including:
> - Voltage inputs and outputs
> - Current inputs and outputs
> - Temperature sensors
>
> Unimplimented registers get passed through to the device model, and device
> models can opt out of using the standard registers with flags. The included
> devices make use of these fields and illustrate how to interface with the pmbus
> class.
>
> Datasheets for sensors:
>
> https://datasheets.maximintegrated.com/en/ds/MAX34451.pdf
> https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1272.pdf
>
> Since v2:
> - bump for feedback
> - removed commented out code
>
> Since v1:
> - addressed Joel's comments
> - split out tests into their own patches
>
> Thanks for reviewing,
>
> Titus Rwantare
>
> Titus Rwantare (5):
> hw/i2c: add support for PMBus
> hw/misc: add ADM1272 device
> tests/qtest: add tests for ADM1272 device model
> hw/misc: add MAX34451 device
> tests/qtest: add tests for MAX34451 device model
>
> include/hw/i2c/pmbus_device.h | 506 +++++++++++
> hw/i2c/pmbus_device.c | 1596 +++++++++++++++++++++++++++++++++
> hw/misc/adm1272.c | 543 +++++++++++
> hw/misc/max34451.c | 716 +++++++++++++++
> tests/qtest/adm1272-test.c | 445 +++++++++
> tests/qtest/max34451-test.c | 336 +++++++
> hw/arm/Kconfig | 3 +
> hw/i2c/Kconfig | 4 +
> hw/i2c/meson.build | 1 +
> hw/misc/Kconfig | 8 +
> hw/misc/meson.build | 2 +
> tests/qtest/meson.build | 2 +
> 12 files changed, 4162 insertions(+)
> create mode 100644 include/hw/i2c/pmbus_device.h
> create mode 100644 hw/i2c/pmbus_device.c
> create mode 100644 hw/misc/adm1272.c
> create mode 100644 hw/misc/max34451.c
> create mode 100644 tests/qtest/adm1272-test.c
> create mode 100644 tests/qtest/max34451-test.c
>
> --
> 2.31.1.751.gd2f1c929bd-goog
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 0/5] Add support for PMBus in QEMU
2021-05-18 19:45 ` [PATCH v3 0/5] Add support for PMBus in QEMU Corey Minyard
@ 2021-05-18 19:50 ` Titus Rwantare
2021-05-18 20:59 ` Corey Minyard
0 siblings, 1 reply; 12+ messages in thread
From: Titus Rwantare @ 2021-05-18 19:50 UTC (permalink / raw)
To: Corey Minyard
Cc: qemu-arm, qemu-devel, Philippe Mathieu-Daudé, Hao Wu, Joel Stanley
I would also like a directory for sensors. There are quite a few of
those incoming. Any objections?
-Titus
On Tue, 18 May 2021 at 15:45, Corey Minyard <cminyard@mvista.com> wrote:
>
> On Tue, May 18, 2021 at 11:45:22AM -0700, Titus Rwantare wrote:
> > Hello,
> >
> > This patch series adds an interface to start supporting PMBus devices in QEMU.
> > I’ve included two PMBus devices: MAX34451 and ADM1272.
>
> I've reviewed all these patches, and beyond my one comment, they look
> good.
>
> I'm not too excited about putting the device files in misc. I know some
> SMBus sensors are in there, but they really aren't miscellaneous. They
> are really sensors. But unless we want to create a sensors directory
> and move things into that, misc will have to do, I guess.
>
> -corey
>
> >
> > PMBus is a variant of SMBus meant for digital management of power supplies.
> > PMBus adds to the SMBus standard by defining a number of constants and commands
> > used by compliant devices. The specification for PMBus can be found at:
> >
> > https://pmbus.org/specification-archives/
> >
> > Currently, the goal for these devices is to emulate basic functionality by
> > reading and writing registers. Timing, and some logical operation is not
> > implemented. This implementation supports nearly all available registers for
> > PMBus including:
> > - Voltage inputs and outputs
> > - Current inputs and outputs
> > - Temperature sensors
> >
> > Unimplimented registers get passed through to the device model, and device
> > models can opt out of using the standard registers with flags. The included
> > devices make use of these fields and illustrate how to interface with the pmbus
> > class.
> >
> > Datasheets for sensors:
> >
> > https://datasheets.maximintegrated.com/en/ds/MAX34451.pdf
> > https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1272.pdf
> >
> > Since v2:
> > - bump for feedback
> > - removed commented out code
> >
> > Since v1:
> > - addressed Joel's comments
> > - split out tests into their own patches
> >
> > Thanks for reviewing,
> >
> > Titus Rwantare
> >
> > Titus Rwantare (5):
> > hw/i2c: add support for PMBus
> > hw/misc: add ADM1272 device
> > tests/qtest: add tests for ADM1272 device model
> > hw/misc: add MAX34451 device
> > tests/qtest: add tests for MAX34451 device model
> >
> > include/hw/i2c/pmbus_device.h | 506 +++++++++++
> > hw/i2c/pmbus_device.c | 1596 +++++++++++++++++++++++++++++++++
> > hw/misc/adm1272.c | 543 +++++++++++
> > hw/misc/max34451.c | 716 +++++++++++++++
> > tests/qtest/adm1272-test.c | 445 +++++++++
> > tests/qtest/max34451-test.c | 336 +++++++
> > hw/arm/Kconfig | 3 +
> > hw/i2c/Kconfig | 4 +
> > hw/i2c/meson.build | 1 +
> > hw/misc/Kconfig | 8 +
> > hw/misc/meson.build | 2 +
> > tests/qtest/meson.build | 2 +
> > 12 files changed, 4162 insertions(+)
> > create mode 100644 include/hw/i2c/pmbus_device.h
> > create mode 100644 hw/i2c/pmbus_device.c
> > create mode 100644 hw/misc/adm1272.c
> > create mode 100644 hw/misc/max34451.c
> > create mode 100644 tests/qtest/adm1272-test.c
> > create mode 100644 tests/qtest/max34451-test.c
> >
> > --
> > 2.31.1.751.gd2f1c929bd-goog
> >
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 0/5] Add support for PMBus in QEMU
2021-05-18 19:50 ` Titus Rwantare
@ 2021-05-18 20:59 ` Corey Minyard
2021-05-18 21:43 ` Titus Rwantare
0 siblings, 1 reply; 12+ messages in thread
From: Corey Minyard @ 2021-05-18 20:59 UTC (permalink / raw)
To: Titus Rwantare
Cc: Hao Wu, qemu-arm, Joel Stanley, qemu-devel, Philippe Mathieu-Daudé
On Tue, May 18, 2021 at 03:50:57PM -0400, Titus Rwantare wrote:
> I would also like a directory for sensors. There are quite a few of
> those incoming. Any objections?
None from me. I'll add a patch to move all the sensors from misc into
it, if you like.
-corey
>
> -Titus
>
> On Tue, 18 May 2021 at 15:45, Corey Minyard <cminyard@mvista.com> wrote:
> >
> > On Tue, May 18, 2021 at 11:45:22AM -0700, Titus Rwantare wrote:
> > > Hello,
> > >
> > > This patch series adds an interface to start supporting PMBus devices in QEMU.
> > > I’ve included two PMBus devices: MAX34451 and ADM1272.
> >
> > I've reviewed all these patches, and beyond my one comment, they look
> > good.
> >
> > I'm not too excited about putting the device files in misc. I know some
> > SMBus sensors are in there, but they really aren't miscellaneous. They
> > are really sensors. But unless we want to create a sensors directory
> > and move things into that, misc will have to do, I guess.
> >
> > -corey
> >
> > >
> > > PMBus is a variant of SMBus meant for digital management of power supplies.
> > > PMBus adds to the SMBus standard by defining a number of constants and commands
> > > used by compliant devices. The specification for PMBus can be found at:
> > >
> > > https://pmbus.org/specification-archives/
> > >
> > > Currently, the goal for these devices is to emulate basic functionality by
> > > reading and writing registers. Timing, and some logical operation is not
> > > implemented. This implementation supports nearly all available registers for
> > > PMBus including:
> > > - Voltage inputs and outputs
> > > - Current inputs and outputs
> > > - Temperature sensors
> > >
> > > Unimplimented registers get passed through to the device model, and device
> > > models can opt out of using the standard registers with flags. The included
> > > devices make use of these fields and illustrate how to interface with the pmbus
> > > class.
> > >
> > > Datasheets for sensors:
> > >
> > > https://datasheets.maximintegrated.com/en/ds/MAX34451.pdf
> > > https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1272.pdf
> > >
> > > Since v2:
> > > - bump for feedback
> > > - removed commented out code
> > >
> > > Since v1:
> > > - addressed Joel's comments
> > > - split out tests into their own patches
> > >
> > > Thanks for reviewing,
> > >
> > > Titus Rwantare
> > >
> > > Titus Rwantare (5):
> > > hw/i2c: add support for PMBus
> > > hw/misc: add ADM1272 device
> > > tests/qtest: add tests for ADM1272 device model
> > > hw/misc: add MAX34451 device
> > > tests/qtest: add tests for MAX34451 device model
> > >
> > > include/hw/i2c/pmbus_device.h | 506 +++++++++++
> > > hw/i2c/pmbus_device.c | 1596 +++++++++++++++++++++++++++++++++
> > > hw/misc/adm1272.c | 543 +++++++++++
> > > hw/misc/max34451.c | 716 +++++++++++++++
> > > tests/qtest/adm1272-test.c | 445 +++++++++
> > > tests/qtest/max34451-test.c | 336 +++++++
> > > hw/arm/Kconfig | 3 +
> > > hw/i2c/Kconfig | 4 +
> > > hw/i2c/meson.build | 1 +
> > > hw/misc/Kconfig | 8 +
> > > hw/misc/meson.build | 2 +
> > > tests/qtest/meson.build | 2 +
> > > 12 files changed, 4162 insertions(+)
> > > create mode 100644 include/hw/i2c/pmbus_device.h
> > > create mode 100644 hw/i2c/pmbus_device.c
> > > create mode 100644 hw/misc/adm1272.c
> > > create mode 100644 hw/misc/max34451.c
> > > create mode 100644 tests/qtest/adm1272-test.c
> > > create mode 100644 tests/qtest/max34451-test.c
> > >
> > > --
> > > 2.31.1.751.gd2f1c929bd-goog
> > >
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 0/5] Add support for PMBus in QEMU
2021-05-18 20:59 ` Corey Minyard
@ 2021-05-18 21:43 ` Titus Rwantare
0 siblings, 0 replies; 12+ messages in thread
From: Titus Rwantare @ 2021-05-18 21:43 UTC (permalink / raw)
To: Corey Minyard
Cc: qemu-arm, qemu-devel, Philippe Mathieu-Daudé, Hao Wu, Joel Stanley
That would be great.
On Tue, 18 May 2021, 16:59 Corey Minyard, <cminyard@mvista.com> wrote:
>
> On Tue, May 18, 2021 at 03:50:57PM -0400, Titus Rwantare wrote:
> > I would also like a directory for sensors. There are quite a few of
> > those incoming. Any objections?
>
> None from me. I'll add a patch to move all the sensors from misc into
> it, if you like.
^ permalink raw reply [flat|nested] 12+ messages in thread