summary refs log tree commit diff stats
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to 'hw')
-rw-r--r--hw/Kconfig1
-rw-r--r--hw/adc/Kconfig3
-rw-r--r--hw/adc/max111x.c (renamed from hw/misc/max111x.c)2
-rw-r--r--hw/adc/meson.build2
-rw-r--r--hw/adc/zynq-xadc.c (renamed from hw/misc/zynq-xadc.c)2
-rw-r--r--hw/arm/Kconfig3
-rw-r--r--hw/arm/aspeed.c2
-rw-r--r--hw/arm/nseries.c5
-rw-r--r--hw/arm/pxa2xx.c2
-rw-r--r--hw/arm/spitz.c6
-rw-r--r--hw/arm/xilinx_zynq.c2
-rw-r--r--hw/display/ati.c2
-rw-r--r--hw/display/sm501.c16
-rw-r--r--hw/display/xlnx_dp.c2
-rw-r--r--hw/i2c/Kconfig4
-rw-r--r--hw/i2c/core.c76
-rw-r--r--hw/i2c/imx_i2c.c2
-rw-r--r--hw/i2c/meson.build1
-rw-r--r--hw/i2c/pm_smbus.c4
-rw-r--r--hw/i2c/pmbus_device.c1612
-rw-r--r--hw/i2c/ppc4xx_i2c.c15
-rw-r--r--hw/i2c/smbus_master.c22
-rw-r--r--hw/input/lm832x.c2
-rw-r--r--hw/ipmi/ipmi_bmc_sim.c4
-rw-r--r--hw/meson.build1
-rw-r--r--hw/misc/Kconfig15
-rw-r--r--hw/misc/auxbus.c68
-rw-r--r--hw/misc/meson.build6
-rw-r--r--hw/misc/tmp105.h55
-rw-r--r--hw/sensor/Kconfig19
-rw-r--r--hw/sensor/adm1272.c543
-rw-r--r--hw/sensor/emc141x.c (renamed from hw/misc/emc141x.c)2
-rw-r--r--hw/sensor/max34451.c775
-rw-r--r--hw/sensor/meson.build5
-rw-r--r--hw/sensor/tmp105.c (renamed from hw/misc/tmp105.c)2
-rw-r--r--hw/sensor/tmp421.c (renamed from hw/misc/tmp421.c)0
36 files changed, 3116 insertions, 167 deletions
diff --git a/hw/Kconfig b/hw/Kconfig
index 805860f564..8cb7664d70 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -32,6 +32,7 @@ source remote/Kconfig
 source rtc/Kconfig
 source scsi/Kconfig
 source sd/Kconfig
+source sensor/Kconfig
 source smbios/Kconfig
 source ssi/Kconfig
 source timer/Kconfig
diff --git a/hw/adc/Kconfig b/hw/adc/Kconfig
index 25d2229fb8..a825bd3d34 100644
--- a/hw/adc/Kconfig
+++ b/hw/adc/Kconfig
@@ -1,2 +1,5 @@
 config STM32F2XX_ADC
     bool
+
+config MAX111X
+    bool
diff --git a/hw/misc/max111x.c b/hw/adc/max111x.c
index 1b3234a519..e8bf4cccd4 100644
--- a/hw/misc/max111x.c
+++ b/hw/adc/max111x.c
@@ -11,7 +11,7 @@
  */
 
 #include "qemu/osdep.h"
-#include "hw/misc/max111x.h"
+#include "hw/adc/max111x.h"
 #include "hw/irq.h"
 #include "migration/vmstate.h"
 #include "qemu/module.h"
diff --git a/hw/adc/meson.build b/hw/adc/meson.build
index 6ddee23813..ac4f093fea 100644
--- a/hw/adc/meson.build
+++ b/hw/adc/meson.build
@@ -1,2 +1,4 @@
 softmmu_ss.add(when: 'CONFIG_STM32F2XX_ADC', if_true: files('stm32f2xx_adc.c'))
 softmmu_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_adc.c'))
+softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq-xadc.c'))
+softmmu_ss.add(when: 'CONFIG_MAX111X', if_true: files('max111x.c'))
diff --git a/hw/misc/zynq-xadc.c b/hw/adc/zynq-xadc.c
index 7b1972ce06..cfc7bab065 100644
--- a/hw/misc/zynq-xadc.c
+++ b/hw/adc/zynq-xadc.c
@@ -15,7 +15,7 @@
 
 #include "qemu/osdep.h"
 #include "hw/irq.h"
-#include "hw/misc/zynq-xadc.h"
+#include "hw/adc/zynq-xadc.h"
 #include "migration/vmstate.h"
 #include "qemu/timer.h"
 #include "qemu/log.h"
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index c521189628..afe13e9d67 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -382,9 +382,12 @@ config XLNX_VERSAL
 config NPCM7XX
     bool
     select A9MPCORE
+    select ADM1272
     select ARM_GIC
     select AT24C  # EEPROM
+    select MAX34451
     select PL310  # cache controller
+    select PMBUS
     select SERIAL
     select SSI
     select UNIMP
diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c
index 1301e8fdff..9d43e26c51 100644
--- a/hw/arm/aspeed.c
+++ b/hw/arm/aspeed.c
@@ -17,7 +17,7 @@
 #include "hw/i2c/i2c_mux_pca954x.h"
 #include "hw/i2c/smbus_eeprom.h"
 #include "hw/misc/pca9552.h"
-#include "hw/misc/tmp105.h"
+#include "hw/sensor/tmp105.h"
 #include "hw/misc/led.h"
 #include "hw/qdev-properties.h"
 #include "sysemu/block-backend.h"
diff --git a/hw/arm/nseries.c b/hw/arm/nseries.c
index 0aefa5d0f3..906c915df7 100644
--- a/hw/arm/nseries.c
+++ b/hw/arm/nseries.c
@@ -34,9 +34,10 @@
 #include "hw/boards.h"
 #include "hw/i2c/i2c.h"
 #include "hw/display/blizzard.h"
+#include "hw/input/lm832x.h"
 #include "hw/input/tsc2xxx.h"
 #include "hw/misc/cbus.h"
-#include "hw/misc/tmp105.h"
+#include "hw/sensor/tmp105.h"
 #include "hw/qdev-properties.h"
 #include "hw/block/flash.h"
 #include "hw/hw.h"
@@ -416,7 +417,7 @@ static void n810_kbd_setup(struct n800_s *s)
     /* Attach the LM8322 keyboard to the I2C bus,
      * should happen in n8x0_i2c_setup and s->kbd be initialised here.  */
     s->kbd = DEVICE(i2c_slave_create_simple(omap_i2c_bus(s->mpu->i2c[0]),
-                                            "lm8323", N810_LM8323_ADDR));
+                                            TYPE_LM8323, N810_LM8323_ADDR));
     qdev_connect_gpio_out(s->kbd, 0, kbd_irq);
 }
 
diff --git a/hw/arm/pxa2xx.c b/hw/arm/pxa2xx.c
index fdc4955e95..15a247efae 100644
--- a/hw/arm/pxa2xx.c
+++ b/hw/arm/pxa2xx.c
@@ -1437,7 +1437,7 @@ static void pxa2xx_i2c_write(void *opaque, hwaddr addr,
         break;
 
     case ISAR:
-        i2c_set_slave_address(I2C_SLAVE(s->slave), value & 0x7f);
+        i2c_slave_set_address(I2C_SLAVE(s->slave), value & 0x7f);
         break;
 
     case IDBR:
diff --git a/hw/arm/spitz.c b/hw/arm/spitz.c
index b45a929cbd..5aab0b8565 100644
--- a/hw/arm/spitz.c
+++ b/hw/arm/spitz.c
@@ -30,7 +30,7 @@
 #include "audio/audio.h"
 #include "hw/boards.h"
 #include "hw/sysbus.h"
-#include "hw/misc/max111x.h"
+#include "hw/adc/max111x.h"
 #include "migration/vmstate.h"
 #include "exec/address-spaces.h"
 #include "cpu.h"
@@ -769,9 +769,9 @@ static void spitz_wm8750_addr(void *opaque, int line, int level)
 {
     I2CSlave *wm = (I2CSlave *) opaque;
     if (level)
-        i2c_set_slave_address(wm, SPITZ_WM_ADDRH);
+        i2c_slave_set_address(wm, SPITZ_WM_ADDRH);
     else
-        i2c_set_slave_address(wm, SPITZ_WM_ADDRL);
+        i2c_slave_set_address(wm, SPITZ_WM_ADDRL);
 }
 
 static void spitz_i2c_setup(PXA2xxState *cpu)
diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c
index 81af32dc42..245af81bbb 100644
--- a/hw/arm/xilinx_zynq.c
+++ b/hw/arm/xilinx_zynq.c
@@ -26,7 +26,7 @@
 #include "hw/boards.h"
 #include "hw/block/flash.h"
 #include "hw/loader.h"
-#include "hw/misc/zynq-xadc.h"
+#include "hw/adc/zynq-xadc.h"
 #include "hw/ssi/ssi.h"
 #include "hw/usb/chipidea.h"
 #include "qemu/error-report.h"
diff --git a/hw/display/ati.c b/hw/display/ati.c
index 4c3ad8f47b..31f22754dc 100644
--- a/hw/display/ati.c
+++ b/hw/display/ati.c
@@ -968,7 +968,7 @@ static void ati_vga_realize(PCIDevice *dev, Error **errp)
     I2CBus *i2cbus = i2c_init_bus(DEVICE(s), "ati-vga.ddc");
     bitbang_i2c_init(&s->bbi2c, i2cbus);
     I2CSlave *i2cddc = I2C_SLAVE(qdev_new(TYPE_I2CDDC));
-    i2c_set_slave_address(i2cddc, 0x50);
+    i2c_slave_set_address(i2cddc, 0x50);
     qdev_realize_and_unref(DEVICE(i2cddc), BUS(i2cbus), &error_abort);
 
     /* mmio register space */
diff --git a/hw/display/sm501.c b/hw/display/sm501.c
index 8789722ef2..663c37e7f2 100644
--- a/hw/display/sm501.c
+++ b/hw/display/sm501.c
@@ -1033,16 +1033,18 @@ static void sm501_i2c_write(void *opaque, hwaddr addr, uint64_t value,
     case SM501_I2C_CONTROL:
         if (value & SM501_I2C_CONTROL_ENABLE) {
             if (value & SM501_I2C_CONTROL_START) {
+                bool is_recv = s->i2c_addr & 1;
                 int res = i2c_start_transfer(s->i2c_bus,
                                              s->i2c_addr >> 1,
-                                             s->i2c_addr & 1);
-                s->i2c_status |= (res ? SM501_I2C_STATUS_ERROR : 0);
-                if (!res) {
+                                             is_recv);
+                if (res) {
+                    s->i2c_status |= SM501_I2C_STATUS_ERROR;
+                } else {
                     int i;
                     for (i = 0; i <= s->i2c_byte_count; i++) {
-                        res = i2c_send_recv(s->i2c_bus, &s->i2c_data[i],
-                                            !(s->i2c_addr & 1));
-                        if (res) {
+                        if (is_recv) {
+                            s->i2c_data[i] = i2c_recv(s->i2c_bus);
+                        } else if (i2c_send(s->i2c_bus, s->i2c_data[i]) < 0) {
                             s->i2c_status |= SM501_I2C_STATUS_ERROR;
                             return;
                         }
@@ -1826,7 +1828,7 @@ static void sm501_init(SM501State *s, DeviceState *dev,
     s->i2c_bus = i2c_init_bus(dev, "sm501.i2c");
     /* ddc */
     I2CDDCState *ddc = I2CDDC(qdev_new(TYPE_I2CDDC));
-    i2c_set_slave_address(I2C_SLAVE(ddc), 0x50);
+    i2c_slave_set_address(I2C_SLAVE(ddc), 0x50);
     qdev_realize_and_unref(DEVICE(ddc), BUS(s->i2c_bus), &error_abort);
 
     /* mmio */
diff --git a/hw/display/xlnx_dp.c b/hw/display/xlnx_dp.c
index 4fd6aeb18b..2bb7a5441a 100644
--- a/hw/display/xlnx_dp.c
+++ b/hw/display/xlnx_dp.c
@@ -1253,7 +1253,7 @@ static void xlnx_dp_init(Object *obj)
     object_property_add_child(OBJECT(s), "dpcd", OBJECT(s->dpcd));
 
     s->edid = I2CDDC(qdev_new("i2c-ddc"));
-    i2c_set_slave_address(I2C_SLAVE(s->edid), 0x50);
+    i2c_slave_set_address(I2C_SLAVE(s->edid), 0x50);
     object_property_add_child(OBJECT(s), "edid", OBJECT(s->edid));
 
     fifo8_create(&s->rx_fifo, 16);
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/core.c b/hw/i2c/core.c
index 3a7bae311d..416372ad00 100644
--- a/hw/i2c/core.c
+++ b/hw/i2c/core.c
@@ -66,7 +66,7 @@ I2CBus *i2c_init_bus(DeviceState *parent, const char *name)
     return bus;
 }
 
-void i2c_set_slave_address(I2CSlave *dev, uint8_t address)
+void i2c_slave_set_address(I2CSlave *dev, uint8_t address)
 {
     dev->address = address;
 }
@@ -114,8 +114,11 @@ bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
  * protocol uses a start transfer to switch from write to read mode
  * without releasing the bus.  If that fails, the bus is still
  * in a transaction.
+ *
+ * @event must be I2C_START_RECV or I2C_START_SEND.
  */
-int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
+static int i2c_do_start_transfer(I2CBus *bus, uint8_t address,
+                                 enum i2c_event event)
 {
     I2CSlaveClass *sc;
     I2CNode *node;
@@ -157,7 +160,7 @@ int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
 
         if (sc->event) {
             trace_i2c_event("start", s->address);
-            rv = sc->event(s, recv ? I2C_START_RECV : I2C_START_SEND);
+            rv = sc->event(s, event);
             if (rv && !bus->broadcast) {
                 if (bus_scanned) {
                     /* First call, terminate the transfer. */
@@ -170,6 +173,23 @@ int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv)
     return 0;
 }
 
+int i2c_start_transfer(I2CBus *bus, uint8_t address, bool is_recv)
+{
+    return i2c_do_start_transfer(bus, address, is_recv
+                                               ? I2C_START_RECV
+                                               : I2C_START_SEND);
+}
+
+int i2c_start_recv(I2CBus *bus, uint8_t address)
+{
+    return i2c_do_start_transfer(bus, address, I2C_START_RECV);
+}
+
+int i2c_start_send(I2CBus *bus, uint8_t address)
+{
+    return i2c_do_start_transfer(bus, address, I2C_START_SEND);
+}
+
 void i2c_end_transfer(I2CBus *bus)
 {
     I2CSlaveClass *sc;
@@ -188,50 +208,42 @@ void i2c_end_transfer(I2CBus *bus)
     bus->broadcast = false;
 }
 
-int i2c_send_recv(I2CBus *bus, uint8_t *data, bool send)
+int i2c_send(I2CBus *bus, uint8_t data)
 {
     I2CSlaveClass *sc;
     I2CSlave *s;
     I2CNode *node;
     int ret = 0;
 
-    if (send) {
-        QLIST_FOREACH(node, &bus->current_devs, next) {
-            s = node->elt;
-            sc = I2C_SLAVE_GET_CLASS(s);
-            if (sc->send) {
-                trace_i2c_send(s->address, *data);
-                ret = ret || sc->send(s, *data);
-            } else {
-                ret = -1;
-            }
-        }
-        return ret ? -1 : 0;
-    } else {
-        ret = 0xff;
-        if (!QLIST_EMPTY(&bus->current_devs) && !bus->broadcast) {
-            sc = I2C_SLAVE_GET_CLASS(QLIST_FIRST(&bus->current_devs)->elt);
-            if (sc->recv) {
-                s = QLIST_FIRST(&bus->current_devs)->elt;
-                ret = sc->recv(s);
-                trace_i2c_recv(s->address, ret);
-            }
+    QLIST_FOREACH(node, &bus->current_devs, next) {
+        s = node->elt;
+        sc = I2C_SLAVE_GET_CLASS(s);
+        if (sc->send) {
+            trace_i2c_send(s->address, data);
+            ret = ret || sc->send(s, data);
+        } else {
+            ret = -1;
         }
-        *data = ret;
-        return 0;
     }
-}
 
-int i2c_send(I2CBus *bus, uint8_t data)
-{
-    return i2c_send_recv(bus, &data, true);
+    return ret ? -1 : 0;
 }
 
 uint8_t i2c_recv(I2CBus *bus)
 {
     uint8_t data = 0xff;
+    I2CSlaveClass *sc;
+    I2CSlave *s;
+
+    if (!QLIST_EMPTY(&bus->current_devs) && !bus->broadcast) {
+        sc = I2C_SLAVE_GET_CLASS(QLIST_FIRST(&bus->current_devs)->elt);
+        if (sc->recv) {
+            s = QLIST_FIRST(&bus->current_devs)->elt;
+            data = sc->recv(s);
+            trace_i2c_recv(s->address, data);
+        }
+    }
 
-    i2c_send_recv(bus, &data, false);
     return data;
 }
 
diff --git a/hw/i2c/imx_i2c.c b/hw/i2c/imx_i2c.c
index 2e02e1c4fa..9792583fea 100644
--- a/hw/i2c/imx_i2c.c
+++ b/hw/i2c/imx_i2c.c
@@ -171,7 +171,7 @@ static void imx_i2c_write(void *opaque, hwaddr offset,
     switch (offset) {
     case IADR_ADDR:
         s->iadr = value & IADR_MASK;
-        /* i2c_set_slave_address(s->bus, (uint8_t)s->iadr); */
+        /* i2c_slave_set_address(s->bus, (uint8_t)s->iadr); */
         break;
     case IFDR_ADDR:
         s->ifdr = value & IFDR_MASK;
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)
diff --git a/hw/i2c/pm_smbus.c b/hw/i2c/pm_smbus.c
index 06e1e5321b..d7eae548cb 100644
--- a/hw/i2c/pm_smbus.c
+++ b/hw/i2c/pm_smbus.c
@@ -128,14 +128,14 @@ static void smb_transaction(PMSMBus *s)
          * So at least Linux may or may not set the read bit here.
          * So just ignore the read bit for this command.
          */
-        if (i2c_start_transfer(bus, addr, 0)) {
+        if (i2c_start_send(bus, addr)) {
             goto error;
         }
         ret = i2c_send(bus, s->smb_data1);
         if (ret) {
             goto error;
         }
-        if (i2c_start_transfer(bus, addr, 1)) {
+        if (i2c_start_recv(bus, addr)) {
             goto error;
         }
         s->in_i2c_block_read = true;
diff --git a/hw/i2c/pmbus_device.c b/hw/i2c/pmbus_device.c
new file mode 100644
index 0000000000..24f8f522d9
--- /dev/null
+++ b/hw/i2c/pmbus_device.c
@@ -0,0 +1,1612 @@
+/*
+ * 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 "migration/vmstate.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;
+}
+
+/* TODO: include pmbus page info in vmstate */
+const VMStateDescription vmstate_pmbus_device = {
+    .name = TYPE_PMBUS_DEVICE,
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (VMStateField[]) {
+        VMSTATE_SMBUS_DEVICE(smb, PMBusDevice),
+        VMSTATE_UINT8(num_pages, PMBusDevice),
+        VMSTATE_UINT8(code, PMBusDevice),
+        VMSTATE_UINT8(page, PMBusDevice),
+        VMSTATE_UINT8(capability, PMBusDevice),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+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/i2c/ppc4xx_i2c.c b/hw/i2c/ppc4xx_i2c.c
index c0a8e04567..75d50f1515 100644
--- a/hw/i2c/ppc4xx_i2c.c
+++ b/hw/i2c/ppc4xx_i2c.c
@@ -1,6 +1,8 @@
 /*
  * PPC4xx I2C controller emulation
  *
+ * Documentation: PPC405GP User's Manual, Chapter 22. IIC Bus Interface
+ *
  * Copyright (c) 2007 Jocelyn Mayer
  * Copyright (c) 2012 François Revol
  * Copyright (c) 2016-2018 BALATON Zoltan
@@ -238,11 +240,14 @@ static void ppc4xx_i2c_writeb(void *opaque, hwaddr addr, uint64_t value,
                         i2c->sts &= ~IIC_STS_ERR;
                     }
                 }
-                if (!(i2c->sts & IIC_STS_ERR) &&
-                    i2c_send_recv(i2c->bus, &i2c->mdata[i], !recv)) {
-                    i2c->sts |= IIC_STS_ERR;
-                    i2c->extsts |= IIC_EXTSTS_XFRA;
-                    break;
+                if (!(i2c->sts & IIC_STS_ERR)) {
+                    if (recv) {
+                        i2c->mdata[i] = i2c_recv(i2c->bus);
+                    } else if (i2c_send(i2c->bus, i2c->mdata[i]) < 0) {
+                        i2c->sts |= IIC_STS_ERR;
+                        i2c->extsts |= IIC_EXTSTS_XFRA;
+                        break;
+                    }
                 }
                 if (value & IIC_CNTL_RPST || !(value & IIC_CNTL_CHT)) {
                     i2c_end_transfer(i2c->bus);
diff --git a/hw/i2c/smbus_master.c b/hw/i2c/smbus_master.c
index dc43b8637d..6a53c34e70 100644
--- a/hw/i2c/smbus_master.c
+++ b/hw/i2c/smbus_master.c
@@ -29,7 +29,7 @@ int smbus_receive_byte(I2CBus *bus, uint8_t addr)
 {
     uint8_t data;
 
-    if (i2c_start_transfer(bus, addr, 1)) {
+    if (i2c_start_recv(bus, addr)) {
         return -1;
     }
     data = i2c_recv(bus);
@@ -40,7 +40,7 @@ int smbus_receive_byte(I2CBus *bus, uint8_t addr)
 
 int smbus_send_byte(I2CBus *bus, uint8_t addr, uint8_t data)
 {
-    if (i2c_start_transfer(bus, addr, 0)) {
+    if (i2c_start_send(bus, addr)) {
         return -1;
     }
     i2c_send(bus, data);
@@ -51,11 +51,11 @@ int smbus_send_byte(I2CBus *bus, uint8_t addr, uint8_t data)
 int smbus_read_byte(I2CBus *bus, uint8_t addr, uint8_t command)
 {
     uint8_t data;
-    if (i2c_start_transfer(bus, addr, 0)) {
+    if (i2c_start_send(bus, addr)) {
         return -1;
     }
     i2c_send(bus, command);
-    if (i2c_start_transfer(bus, addr, 1)) {
+    if (i2c_start_recv(bus, addr)) {
         i2c_end_transfer(bus);
         return -1;
     }
@@ -67,7 +67,7 @@ int smbus_read_byte(I2CBus *bus, uint8_t addr, uint8_t command)
 
 int smbus_write_byte(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t data)
 {
-    if (i2c_start_transfer(bus, addr, 0)) {
+    if (i2c_start_send(bus, addr)) {
         return -1;
     }
     i2c_send(bus, command);
@@ -79,11 +79,11 @@ int smbus_write_byte(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t data)
 int smbus_read_word(I2CBus *bus, uint8_t addr, uint8_t command)
 {
     uint16_t data;
-    if (i2c_start_transfer(bus, addr, 0)) {
+    if (i2c_start_send(bus, addr)) {
         return -1;
     }
     i2c_send(bus, command);
-    if (i2c_start_transfer(bus, addr, 1)) {
+    if (i2c_start_recv(bus, addr)) {
         i2c_end_transfer(bus);
         return -1;
     }
@@ -96,7 +96,7 @@ int smbus_read_word(I2CBus *bus, uint8_t addr, uint8_t command)
 
 int smbus_write_word(I2CBus *bus, uint8_t addr, uint8_t command, uint16_t data)
 {
-    if (i2c_start_transfer(bus, addr, 0)) {
+    if (i2c_start_send(bus, addr)) {
         return -1;
     }
     i2c_send(bus, command);
@@ -113,12 +113,12 @@ int smbus_read_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data,
     int i;
 
     if (send_cmd) {
-        if (i2c_start_transfer(bus, addr, 0)) {
+        if (i2c_start_send(bus, addr)) {
             return -1;
         }
         i2c_send(bus, command);
     }
-    if (i2c_start_transfer(bus, addr, 1)) {
+    if (i2c_start_recv(bus, addr)) {
         if (send_cmd) {
             i2c_end_transfer(bus);
         }
@@ -149,7 +149,7 @@ int smbus_write_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data,
         len = 32;
     }
 
-    if (i2c_start_transfer(bus, addr, 0)) {
+    if (i2c_start_send(bus, addr)) {
         return -1;
     }
     i2c_send(bus, command);
diff --git a/hw/input/lm832x.c b/hw/input/lm832x.c
index 4cb1e9de01..19a646d9bb 100644
--- a/hw/input/lm832x.c
+++ b/hw/input/lm832x.c
@@ -19,6 +19,7 @@
  */
 
 #include "qemu/osdep.h"
+#include "hw/input/lm832x.h"
 #include "hw/i2c/i2c.h"
 #include "hw/irq.h"
 #include "migration/vmstate.h"
@@ -27,7 +28,6 @@
 #include "ui/console.h"
 #include "qom/object.h"
 
-#define TYPE_LM8323 "lm8323"
 OBJECT_DECLARE_SIMPLE_TYPE(LM823KbdState, LM8323)
 
 struct LM823KbdState {
diff --git a/hw/ipmi/ipmi_bmc_sim.c b/hw/ipmi/ipmi_bmc_sim.c
index 55fb81fa5a..905e091094 100644
--- a/hw/ipmi/ipmi_bmc_sim.c
+++ b/hw/ipmi/ipmi_bmc_sim.c
@@ -189,7 +189,7 @@ struct IPMIBmcSim {
     uint8_t  watchdog_use;
     uint8_t  watchdog_action;
     uint8_t  watchdog_pretimeout; /* In seconds */
-    bool     watchdog_expired;
+    uint8_t  watchdog_expired;
     uint16_t watchdog_timeout; /* in 100's of milliseconds */
 
     bool     watchdog_running;
@@ -2110,7 +2110,7 @@ static const VMStateDescription vmstate_ipmi_sim = {
         VMSTATE_UINT8(watchdog_use, IPMIBmcSim),
         VMSTATE_UINT8(watchdog_action, IPMIBmcSim),
         VMSTATE_UINT8(watchdog_pretimeout, IPMIBmcSim),
-        VMSTATE_BOOL(watchdog_expired, IPMIBmcSim),
+        VMSTATE_UINT8(watchdog_expired, IPMIBmcSim),
         VMSTATE_UINT16(watchdog_timeout, IPMIBmcSim),
         VMSTATE_BOOL(watchdog_running, IPMIBmcSim),
         VMSTATE_BOOL(watchdog_preaction_ran, IPMIBmcSim),
diff --git a/hw/meson.build b/hw/meson.build
index ba0601e36e..b3366c888e 100644
--- a/hw/meson.build
+++ b/hw/meson.build
@@ -31,6 +31,7 @@ subdir('rdma')
 subdir('rtc')
 subdir('scsi')
 subdir('sd')
+subdir('sensor')
 subdir('smbios')
 subdir('ssi')
 subdir('timer')
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index c71ed25820..507058d8bf 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -11,21 +11,6 @@ config ARMSSE_MHU
 config ARMSSE_CPU_PWRCTRL
     bool
 
-config MAX111X
-    bool
-
-config TMP105
-    bool
-    depends on I2C
-
-config TMP421
-    bool
-    depends on I2C
-
-config EMC141X
-    bool
-    depends on I2C
-
 config ISA_DEBUG
     bool
     depends on ISA_BUS
diff --git a/hw/misc/auxbus.c b/hw/misc/auxbus.c
index 6c099ae2a2..434ff8d910 100644
--- a/hw/misc/auxbus.c
+++ b/hw/misc/auxbus.c
@@ -106,7 +106,6 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
     AUXReply ret = AUX_NACK;
     I2CBus *i2c_bus = aux_get_i2c_bus(bus);
     size_t i;
-    bool is_write = false;
 
     DPRINTF("request at address 0x%" PRIX32 ", command %u, len %u\n", address,
             cmd, len);
@@ -117,11 +116,10 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
      */
     case WRITE_AUX:
     case READ_AUX:
-        is_write = cmd == READ_AUX ? false : true;
         for (i = 0; i < len; i++) {
             if (!address_space_rw(&bus->aux_addr_space, address++,
                                   MEMTXATTRS_UNSPECIFIED, data++, 1,
-                                  is_write)) {
+                                  cmd == WRITE_AUX)) {
                 ret = AUX_I2C_ACK;
             } else {
                 ret = AUX_NACK;
@@ -133,24 +131,37 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
      * Classic I2C transactions..
      */
     case READ_I2C:
+        if (i2c_bus_busy(i2c_bus)) {
+            i2c_end_transfer(i2c_bus);
+        }
+
+        if (i2c_start_recv(i2c_bus, address)) {
+            ret = AUX_I2C_NACK;
+            break;
+        }
+
+        ret = AUX_I2C_ACK;
+        for (i = 0; i < len; i++) {
+            data[i] = i2c_recv(i2c_bus);
+        }
+        i2c_end_transfer(i2c_bus);
+        break;
     case WRITE_I2C:
-        is_write = cmd == READ_I2C ? false : true;
         if (i2c_bus_busy(i2c_bus)) {
             i2c_end_transfer(i2c_bus);
         }
 
-        if (i2c_start_transfer(i2c_bus, address, is_write)) {
+        if (i2c_start_send(i2c_bus, address)) {
             ret = AUX_I2C_NACK;
             break;
         }
 
         ret = AUX_I2C_ACK;
-        while (len > 0) {
-            if (i2c_send_recv(i2c_bus, data++, is_write) < 0) {
+        for (i = 0; i < len; i++) {
+            if (i2c_send(i2c_bus, data[i]) < 0) {
                 ret = AUX_I2C_NACK;
                 break;
             }
-            len--;
         }
         i2c_end_transfer(i2c_bus);
         break;
@@ -163,14 +174,12 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
      *  - We changed the address.
      */
     case WRITE_I2C_MOT:
-    case READ_I2C_MOT:
-        is_write = cmd == READ_I2C_MOT ? false : true;
         ret = AUX_I2C_NACK;
         if (!i2c_bus_busy(i2c_bus)) {
             /*
              * No transactions started..
              */
-            if (i2c_start_transfer(i2c_bus, address, is_write)) {
+            if (i2c_start_send(i2c_bus, address)) {
                 break;
             }
         } else if ((address != bus->last_i2c_address) ||
@@ -179,23 +188,48 @@ AUXReply aux_request(AUXBus *bus, AUXCommand cmd, uint32_t address,
              * Transaction started but we need to restart..
              */
             i2c_end_transfer(i2c_bus);
-            if (i2c_start_transfer(i2c_bus, address, is_write)) {
+            if (i2c_start_send(i2c_bus, address)) {
                 break;
             }
         }
 
         bus->last_transaction = cmd;
         bus->last_i2c_address = address;
-        while (len > 0) {
-            if (i2c_send_recv(i2c_bus, data++, is_write) < 0) {
+        ret = AUX_I2C_ACK;
+        for (i = 0; i < len; i++) {
+            if (i2c_send(i2c_bus, data[i]) < 0) {
                 i2c_end_transfer(i2c_bus);
+                ret = AUX_I2C_NACK;
                 break;
             }
-            len--;
         }
-        if (len == 0) {
-            ret = AUX_I2C_ACK;
+        break;
+    case READ_I2C_MOT:
+        ret = AUX_I2C_NACK;
+        if (!i2c_bus_busy(i2c_bus)) {
+            /*
+             * No transactions started..
+             */
+            if (i2c_start_recv(i2c_bus, address)) {
+                break;
+            }
+        } else if ((address != bus->last_i2c_address) ||
+                   (bus->last_transaction != cmd)) {
+            /*
+             * Transaction started but we need to restart..
+             */
+            i2c_end_transfer(i2c_bus);
+            if (i2c_start_recv(i2c_bus, address)) {
+                break;
+            }
+        }
+
+        bus->last_transaction = cmd;
+        bus->last_i2c_address = address;
+        for (i = 0; i < len; i++) {
+            data[i] = i2c_recv(i2c_bus);
         }
+        ret = AUX_I2C_ACK;
         break;
     default:
         qemu_log_mask(LOG_UNIMP, "AUX cmd=%u not implemented\n", cmd);
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index f89b5c1643..a53b849a5a 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -3,13 +3,9 @@ softmmu_ss.add(when: 'CONFIG_EDU', if_true: files('edu.c'))
 softmmu_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
 softmmu_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
 softmmu_ss.add(when: 'CONFIG_ISA_TESTDEV', if_true: files('pc-testdev.c'))
-softmmu_ss.add(when: 'CONFIG_MAX111X', if_true: files('max111x.c'))
 softmmu_ss.add(when: 'CONFIG_PCA9552', if_true: files('pca9552.c'))
 softmmu_ss.add(when: 'CONFIG_PCI_TESTDEV', if_true: files('pci-testdev.c'))
 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_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'))
@@ -85,7 +81,7 @@ softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files(
   'bcm2835_powermgt.c',
 ))
 softmmu_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
-softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c', 'zynq-xadc.c'))
+softmmu_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
 softmmu_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-xramc.c'))
 softmmu_ss.add(when: 'CONFIG_STM32F2XX_SYSCFG', if_true: files('stm32f2xx_syscfg.c'))
 softmmu_ss.add(when: 'CONFIG_STM32F4XX_SYSCFG', if_true: files('stm32f4xx_syscfg.c'))
diff --git a/hw/misc/tmp105.h b/hw/misc/tmp105.h
deleted file mode 100644
index 7c97071ad7..0000000000
--- a/hw/misc/tmp105.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Texas Instruments TMP105 Temperature Sensor
- *
- * Browse the data sheet:
- *
- *    http://www.ti.com/lit/gpn/tmp105
- *
- * Copyright (C) 2012 Alex Horn <alex.horn@cs.ox.ac.uk>
- * Copyright (C) 2008-2012 Andrzej Zaborowski <balrogg@gmail.com>
- *
- * This work is licensed under the terms of the GNU GPL, version 2 or
- * later. See the COPYING file in the top-level directory.
- */
-#ifndef QEMU_TMP105_H
-#define QEMU_TMP105_H
-
-#include "hw/i2c/i2c.h"
-#include "hw/misc/tmp105_regs.h"
-#include "qom/object.h"
-
-#define TYPE_TMP105 "tmp105"
-OBJECT_DECLARE_SIMPLE_TYPE(TMP105State, TMP105)
-
-/**
- * TMP105State:
- * @config: Bits 5 and 6 (value 32 and 64) determine the precision of the
- * temperature. See Table 8 in the data sheet.
- *
- * @see_also: http://www.ti.com/lit/gpn/tmp105
- */
-struct TMP105State {
-    /*< private >*/
-    I2CSlave i2c;
-    /*< public >*/
-
-    uint8_t len;
-    uint8_t buf[2];
-    qemu_irq pin;
-
-    uint8_t pointer;
-    uint8_t config;
-    int16_t temperature;
-    int16_t limit[2];
-    int faults;
-    uint8_t alarm;
-    /*
-     * The TMP105 initially looks for a temperature rising above T_high;
-     * once this is detected, the condition it looks for next is the
-     * temperature falling below T_low. This flag is false when initially
-     * looking for T_high, true when looking for T_low.
-     */
-    bool detect_falling;
-};
-
-#endif
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
new file mode 100644
index 0000000000..a2b55a4fdb
--- /dev/null
+++ b/hw/sensor/Kconfig
@@ -0,0 +1,19 @@
+config TMP105
+    bool
+    depends on I2C
+
+config TMP421
+    bool
+    depends on I2C
+
+config EMC141X
+    bool
+    depends on I2C
+
+config ADM1272
+    bool
+    depends on I2C
+
+config MAX34451
+    bool
+    depends on I2C
diff --git a/hw/sensor/adm1272.c b/hw/sensor/adm1272.c
new file mode 100644
index 0000000000..7310c769be
--- /dev/null
+++ b/hw/sensor/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"
+
+#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_PMBUS_DEVICE(parent, ADM1272State),
+        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/misc/emc141x.c b/hw/sensor/emc141x.c
index f7c53d48a4..7ce8f4e979 100644
--- a/hw/misc/emc141x.c
+++ b/hw/sensor/emc141x.c
@@ -25,7 +25,7 @@
 #include "qapi/visitor.h"
 #include "qemu/module.h"
 #include "qom/object.h"
-#include "hw/misc/emc141x_regs.h"
+#include "hw/sensor/emc141x_regs.h"
 
 #define SENSORS_COUNT_MAX    4
 
diff --git a/hw/sensor/max34451.c b/hw/sensor/max34451.c
new file mode 100644
index 0000000000..a91d8bd487
--- /dev/null
+++ b/hw/sensor/max34451.c
@@ -0,0 +1,775 @@
+/*
+ * 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 "migration/vmstate.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=E2=80=93254|  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 const VMStateDescription vmstate_max34451 = {
+    .name = TYPE_MAX34451,
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (VMStateField[]){
+        VMSTATE_PMBUS_DEVICE(parent, MAX34451State),
+        VMSTATE_UINT16_ARRAY(power_good_on, MAX34451State,
+                             MAX34451_NUM_PWR_DEVICES),
+        VMSTATE_UINT16_ARRAY(power_good_off, MAX34451State,
+                             MAX34451_NUM_PWR_DEVICES),
+        VMSTATE_UINT16_ARRAY(ton_delay, MAX34451State,
+                             MAX34451_NUM_MARGINED_PSU),
+        VMSTATE_UINT16_ARRAY(ton_max_fault_limit, MAX34451State,
+                             MAX34451_NUM_MARGINED_PSU),
+        VMSTATE_UINT16_ARRAY(toff_delay, MAX34451State,
+                             MAX34451_NUM_MARGINED_PSU),
+        VMSTATE_UINT8_ARRAY(status_mfr_specific, MAX34451State,
+                             MAX34451_NUM_PWR_DEVICES),
+        VMSTATE_UINT64(mfr_location, MAX34451State),
+        VMSTATE_UINT64(mfr_date, MAX34451State),
+        VMSTATE_UINT64(mfr_serial, MAX34451State),
+        VMSTATE_UINT16(mfr_mode, MAX34451State),
+        VMSTATE_UINT32_ARRAY(psen_config, MAX34451State,
+                             MAX34451_NUM_MARGINED_PSU),
+        VMSTATE_UINT16_ARRAY(vout_peak, MAX34451State,
+                             MAX34451_NUM_PWR_DEVICES),
+        VMSTATE_UINT16_ARRAY(iout_peak, MAX34451State,
+                             MAX34451_NUM_PWR_DEVICES),
+        VMSTATE_UINT16_ARRAY(temperature_peak, MAX34451State,
+                             MAX34451_NUM_TEMP_DEVICES),
+        VMSTATE_UINT16_ARRAY(vout_min, MAX34451State, MAX34451_NUM_PWR_DEVICES),
+        VMSTATE_UINT16(nv_log_config, MAX34451State),
+        VMSTATE_UINT32_ARRAY(fault_response, MAX34451State,
+                             MAX34451_NUM_PWR_DEVICES),
+        VMSTATE_UINT16(fault_retry, MAX34451State),
+        VMSTATE_UINT32(fault_log, MAX34451State),
+        VMSTATE_UINT32(time_count, MAX34451State),
+        VMSTATE_UINT16_ARRAY(margin_config, MAX34451State,
+                             MAX34451_NUM_MARGINED_PSU),
+        VMSTATE_UINT16(fw_serial, MAX34451State),
+        VMSTATE_UINT16_ARRAY(iout_avg, MAX34451State, MAX34451_NUM_PWR_DEVICES),
+        VMSTATE_UINT16_ARRAY(channel_config, MAX34451State,
+                             MAX34451_NUM_PWR_DEVICES),
+        VMSTATE_UINT16_ARRAY(ton_seq_max, MAX34451State,
+                             MAX34451_NUM_MARGINED_PSU),
+        VMSTATE_UINT32_ARRAY(pwm_config, MAX34451State,
+                             MAX34451_NUM_MARGINED_PSU),
+        VMSTATE_UINT32_ARRAY(seq_config, MAX34451State,
+                             MAX34451_NUM_MARGINED_PSU),
+        VMSTATE_UINT16_ARRAY(temp_sensor_config, MAX34451State,
+                             MAX34451_NUM_TEMP_DEVICES),
+        VMSTATE_UINT16(store_single, MAX34451State),
+        VMSTATE_UINT16(crc, MAX34451State),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+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";
+    dc->vmsd = &vmstate_max34451;
+    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/sensor/meson.build b/hw/sensor/meson.build
new file mode 100644
index 0000000000..034e3e0207
--- /dev/null
+++ b/hw/sensor/meson.build
@@ -0,0 +1,5 @@
+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'))
diff --git a/hw/misc/tmp105.c b/hw/sensor/tmp105.c
index d299d9b21b..2056449489 100644
--- a/hw/misc/tmp105.c
+++ b/hw/sensor/tmp105.c
@@ -22,7 +22,7 @@
 #include "hw/i2c/i2c.h"
 #include "hw/irq.h"
 #include "migration/vmstate.h"
-#include "tmp105.h"
+#include "hw/sensor/tmp105.h"
 #include "qapi/error.h"
 #include "qapi/visitor.h"
 #include "qemu/module.h"
diff --git a/hw/misc/tmp421.c b/hw/sensor/tmp421.c
index a3db57dcb5..a3db57dcb5 100644
--- a/hw/misc/tmp421.c
+++ b/hw/sensor/tmp421.c