diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2021-07-11 14:32:49 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2021-07-11 14:32:49 +0100 |
commit | 9516034d05a8c71ef157a59f525e4c4f7ed79827 (patch) | |
tree | 7bffa57da71fec0e63a49691182852633e988145 /hw | |
parent | 3cfcc329afd99138e654b65f6f49156fca2e8cdd (diff) | |
parent | 7649086f455fe44bd076828749a93ab2a5bb0806 (diff) |
Merge remote-tracking branch 'remotes/cminyard/tags/for-qemu-6.1-2' into staging
Some qemu updates for IPMI and I2C
Move some ADC file to where they belong and move some sensors to a
sensor directory, since with new BMCs coming in lots of different
sensors should be coming in. Keep from cluttering things up.
Add support for I2C PMBus devices.
Replace the confusing and error-prone i2c_send_recv and i2c_transfer with
specific send and receive functions. Several errors have already been
made with these, avoid any new errors.
Fix the watchdog_expired field in the IPMI watchdog, it's not a bool,
it's a u8. After a vmstate transfer, the new value could be wrong.
# gpg: Signature made Fri 09 Jul 2021 17:25:04 BST
# gpg: using RSA key FD0D5CE67CE0F59A6688268661F38C90919BFF81
# gpg: Good signature from "Corey Minyard <cminyard@mvista.com>" [unknown]
# gpg: aka "Corey Minyard <minyard@acm.org>" [unknown]
# gpg: aka "Corey Minyard <corey@minyard.net>" [unknown]
# gpg: aka "Corey Minyard <minyard@mvista.com>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg: There is no indication that the signature belongs to the owner.
# Primary key fingerprint: FD0D 5CE6 7CE0 F59A 6688 2686 61F3 8C90 919B FF81
* remotes/cminyard/tags/for-qemu-6.1-2: (24 commits)
tests/qtest: add tests for MAX34451 device model
hw/misc: add MAX34451 device
tests/qtest: add tests for ADM1272 device model
hw/misc: add ADM1272 device
hw/i2c: add support for PMBus
ipmi/sim: fix watchdog_expired data type error in IPMIBmcSim struct
hw/i2c: Introduce i2c_start_recv() and i2c_start_send()
hw/i2c: Extract i2c_do_start_transfer() from i2c_start_transfer()
hw/i2c: Make i2c_start_transfer() direction argument a boolean
hw/i2c: Rename i2c_set_slave_address() -> i2c_slave_set_address()
hw/i2c: Remove confusing i2c_send_recv()
hw/misc/auxbus: Replace i2c_send_recv() by i2c_recv() & i2c_send()
hw/misc/auxbus: Replace 'is_write' boolean by its value
hw/misc/auxbus: Explode READ_I2C / WRITE_I2C_MOT cases
hw/misc/auxbus: Fix MOT/classic I2C mode
hw/i2c/ppc4xx_i2c: Replace i2c_send_recv() by i2c_recv() & i2c_send()
hw/i2c/ppc4xx_i2c: Add reference to datasheet
hw/display/sm501: Replace i2c_send_recv() by i2c_recv() & i2c_send()
hw/display/sm501: Simplify sm501_i2c_write() logic
hw/input/lm832x: Define TYPE_LM8323 in public header
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw')
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 |