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.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEE/Q1c5nzg9ZpmiCaGYfOMkJGb/4EFAmDoeGAACgkQYfOMkJGb
 /4HpkQ//YLAddy3Kqo5LbyI0BPxcx567htHH34XXp4Pz22DcAoPvZRyRlsGiAAcR
 HRmqjSXNBan+KhlA82coczHUami8C+9NQAfnABf7Mv4CQnvf9Hziy9YR0riTAgEk
 Xfc25eYYK5dzfOHVVbp2KJ2IWPo/sY3UqbIKu9MyYzriNpzIuT5NW1aGjIrhW7Js
 2feeIHxH4fEXzhkkgMKMJfXdxkYyD78mB3IfnpVCnoODq3fMZXtMskRDHWjdMEnT
 0u53zeQd5T0jElLZXlwDKAd9QpiPWVnfTJwND3fwWjSMIAVkd7kTqJbVtHpYJMWB
 amOR9qsw72r/WIOow4lLbSjS57GbyEjB344dtlrzHhhW68y9SGbtaSXWD/imkAfB
 NhLlMzNzsIb+dHZhAwdEgmg4Nz/1HZngiK4Awk2YFBVCn7G6BqAvpMwLGdYYr3Az
 P4EPs0aVdGYBbcN5XxAwkKEqESrzF+iF7iZvNTocdSJSCV+ebFNLlZWIq/aXS1TA
 IED1mBmyc/drWF+jA0D520uafT1m+zN4HhPvHpRyK65bqqiH2veDpK20kz0lwQe8
 e2ZeDVG2oVTMIQ+mmyu7kD9xgxjmapoHz64K1VkUwsnXE3jp9yqfa17B9vHOjqkq
 e/iaHiek8xeTWWyoShGk2zoAHBChkwSy/Y2JuZA6FXRnoU6kKsg=
 =8708
 -----END PGP SIGNATURE-----

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>
This commit is contained in:
Peter Maydell 2021-07-11 14:32:49 +01:00
commit 9516034d05
50 changed files with 4497 additions and 126 deletions

View file

@ -794,6 +794,7 @@ F: hw/input/tsc2005.c
F: hw/misc/cbus.c
F: hw/rtc/twl92230.c
F: include/hw/display/blizzard.h
F: include/hw/input/lm832x.h
F: include/hw/input/tsc2xxx.h
F: include/hw/misc/cbus.h
F: tests/acceptance/machine_arm_n8x0.py
@ -846,8 +847,8 @@ F: hw/display/tc6393xb.c
F: hw/gpio/max7310.c
F: hw/gpio/zaurus.c
F: hw/misc/mst_fpga.c
F: hw/misc/max111x.c
F: include/hw/misc/max111x.h
F: hw/adc/max111x.c
F: include/hw/adc/max111x.h
F: include/hw/arm/pxa.h
F: include/hw/arm/sharpsl.h
F: include/hw/display/tc6393xb.h
@ -932,8 +933,10 @@ L: qemu-arm@nongnu.org
S: Maintained
F: hw/*/xilinx_*
F: hw/*/cadence_*
F: hw/misc/zynq*
F: include/hw/misc/zynq*
F: hw/misc/zynq_slcr.c
F: hw/adc/zynq-xadc.c
F: include/hw/misc/zynq_slcr.h
F: include/hw/adc/zynq-xadc.h
X: hw/ssi/xilinx_*
Xilinx ZynqMP and Versal

View file

@ -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

View file

@ -1,2 +1,5 @@
config STM32F2XX_ADC
bool
config MAX111X
bool

View file

@ -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"

View file

@ -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'))

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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);
}

View file

@ -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:

View file

@ -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)

View file

@ -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"

View file

@ -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 */

View file

@ -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 */

View file

@ -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);

View file

@ -32,3 +32,7 @@ config MPC_I2C
config PCA954X
bool
select I2C
config PMBUS
bool
select SMBUS

View file

@ -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;
}
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);
}
}
*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;
}

View file

@ -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;

View file

@ -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)

View file

@ -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;

1612
hw/i2c/pmbus_device.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -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);

View file

@ -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);

View file

@ -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 {

View file

@ -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),

View file

@ -31,6 +31,7 @@ subdir('rdma')
subdir('rtc')
subdir('scsi')
subdir('sd')
subdir('sensor')
subdir('smbios')
subdir('ssi')
subdir('timer')

View file

@ -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

View file

@ -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:
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_recv(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++) {
data[i] = i2c_recv(i2c_bus);
}
i2c_end_transfer(i2c_bus);
break;
case WRITE_I2C:
if (i2c_bus_busy(i2c_bus)) {
i2c_end_transfer(i2c_bus);
}
if (i2c_start_send(i2c_bus, address)) {
ret = AUX_I2C_NACK;
break;
}
ret = AUX_I2C_ACK;
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);

View file

@ -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'))

19
hw/sensor/Kconfig Normal file
View file

@ -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

543
hw/sensor/adm1272.c Normal file
View file

@ -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)

View file

@ -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

775
hw/sensor/max34451.c Normal file
View file

@ -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)

5
hw/sensor/meson.build Normal file
View file

@ -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'))

View file

@ -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"

View file

@ -79,12 +79,44 @@ struct I2CBus {
};
I2CBus *i2c_init_bus(DeviceState *parent, const char *name);
void i2c_set_slave_address(I2CSlave *dev, uint8_t address);
int i2c_bus_busy(I2CBus *bus);
int i2c_start_transfer(I2CBus *bus, uint8_t address, int recv);
/**
* i2c_start_transfer: start a transfer on an I2C bus.
*
* @bus: #I2CBus to be used
* @address: address of the slave
* @is_recv: indicates the transfer direction
*
* When @is_recv is a known boolean constant, use the
* i2c_start_recv() or i2c_start_send() helper instead.
*
* Returns: 0 on success, -1 on error
*/
int i2c_start_transfer(I2CBus *bus, uint8_t address, bool is_recv);
/**
* i2c_start_recv: start a 'receive' transfer on an I2C bus.
*
* @bus: #I2CBus to be used
* @address: address of the slave
*
* Returns: 0 on success, -1 on error
*/
int i2c_start_recv(I2CBus *bus, uint8_t address);
/**
* i2c_start_send: start a 'send' transfer on an I2C bus.
*
* @bus: #I2CBus to be used
* @address: address of the slave
*
* Returns: 0 on success, -1 on error
*/
int i2c_start_send(I2CBus *bus, uint8_t address);
void i2c_end_transfer(I2CBus *bus);
void i2c_nack(I2CBus *bus);
int i2c_send_recv(I2CBus *bus, uint8_t *data, bool send);
int i2c_send(I2CBus *bus, uint8_t data);
uint8_t i2c_recv(I2CBus *bus);
bool i2c_scan_bus(I2CBus *bus, uint8_t address, bool broadcast,
@ -142,8 +174,12 @@ I2CSlave *i2c_slave_create_simple(I2CBus *bus, const char *name, uint8_t addr);
*/
bool i2c_slave_realize_and_unref(I2CSlave *dev, I2CBus *bus, Error **errp);
/* lm832x.c */
void lm832x_key_event(DeviceState *dev, int key, int state);
/**
* Set the I2C bus address of a slave device
* @dev: I2C slave device
* @address: I2C address of the slave when put on a bus
*/
void i2c_slave_set_address(I2CSlave *dev, uint8_t address);
extern const VMStateDescription vmstate_i2c_slave;

View file

@ -0,0 +1,517 @@
/*
* QEMU PMBus device emulation
*
* Copyright 2021 Google LLC
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef HW_PMBUS_DEVICE_H
#define HW_PMBUS_DEVICE_H
#include "qemu/bitops.h"
#include "hw/i2c/smbus_slave.h"
enum pmbus_registers {
PMBUS_PAGE = 0x00, /* R/W byte */
PMBUS_OPERATION = 0x01, /* R/W byte */
PMBUS_ON_OFF_CONFIG = 0x02, /* R/W byte */
PMBUS_CLEAR_FAULTS = 0x03, /* Send Byte */
PMBUS_PHASE = 0x04, /* R/W byte */
PMBUS_PAGE_PLUS_WRITE = 0x05, /* Block Write-only */
PMBUS_PAGE_PLUS_READ = 0x06, /* Block Read-only */
PMBUS_WRITE_PROTECT = 0x10, /* R/W byte */
PMBUS_STORE_DEFAULT_ALL = 0x11, /* Send Byte */
PMBUS_RESTORE_DEFAULT_ALL = 0x12, /* Send Byte */
PMBUS_STORE_DEFAULT_CODE = 0x13, /* Write-only Byte */
PMBUS_RESTORE_DEFAULT_CODE = 0x14, /* Write-only Byte */
PMBUS_STORE_USER_ALL = 0x15, /* Send Byte */
PMBUS_RESTORE_USER_ALL = 0x16, /* Send Byte */
PMBUS_STORE_USER_CODE = 0x17, /* Write-only Byte */
PMBUS_RESTORE_USER_CODE = 0x18, /* Write-only Byte */
PMBUS_CAPABILITY = 0x19, /* Read-Only byte */
PMBUS_QUERY = 0x1A, /* Write-Only */
PMBUS_SMBALERT_MASK = 0x1B, /* Block read, Word write */
PMBUS_VOUT_MODE = 0x20, /* R/W byte */
PMBUS_VOUT_COMMAND = 0x21, /* R/W word */
PMBUS_VOUT_TRIM = 0x22, /* R/W word */
PMBUS_VOUT_CAL_OFFSET = 0x23, /* R/W word */
PMBUS_VOUT_MAX = 0x24, /* R/W word */
PMBUS_VOUT_MARGIN_HIGH = 0x25, /* R/W word */
PMBUS_VOUT_MARGIN_LOW = 0x26, /* R/W word */
PMBUS_VOUT_TRANSITION_RATE = 0x27, /* R/W word */
PMBUS_VOUT_DROOP = 0x28, /* R/W word */
PMBUS_VOUT_SCALE_LOOP = 0x29, /* R/W word */
PMBUS_VOUT_SCALE_MONITOR = 0x2A, /* R/W word */
PMBUS_COEFFICIENTS = 0x30, /* Read-only block 5 bytes */
PMBUS_POUT_MAX = 0x31, /* R/W word */
PMBUS_MAX_DUTY = 0x32, /* R/W word */
PMBUS_FREQUENCY_SWITCH = 0x33, /* R/W word */
PMBUS_VIN_ON = 0x35, /* R/W word */
PMBUS_VIN_OFF = 0x36, /* R/W word */
PMBUS_INTERLEAVE = 0x37, /* R/W word */
PMBUS_IOUT_CAL_GAIN = 0x38, /* R/W word */
PMBUS_IOUT_CAL_OFFSET = 0x39, /* R/W word */
PMBUS_FAN_CONFIG_1_2 = 0x3A, /* R/W byte */
PMBUS_FAN_COMMAND_1 = 0x3B, /* R/W word */
PMBUS_FAN_COMMAND_2 = 0x3C, /* R/W word */
PMBUS_FAN_CONFIG_3_4 = 0x3D, /* R/W byte */
PMBUS_FAN_COMMAND_3 = 0x3E, /* R/W word */
PMBUS_FAN_COMMAND_4 = 0x3F, /* R/W word */
PMBUS_VOUT_OV_FAULT_LIMIT = 0x40, /* R/W word */
PMBUS_VOUT_OV_FAULT_RESPONSE = 0x41, /* R/W byte */
PMBUS_VOUT_OV_WARN_LIMIT = 0x42, /* R/W word */
PMBUS_VOUT_UV_WARN_LIMIT = 0x43, /* R/W word */
PMBUS_VOUT_UV_FAULT_LIMIT = 0x44, /* R/W word */
PMBUS_VOUT_UV_FAULT_RESPONSE = 0x45, /* R/W byte */
PMBUS_IOUT_OC_FAULT_LIMIT = 0x46, /* R/W word */
PMBUS_IOUT_OC_FAULT_RESPONSE = 0x47, /* R/W byte */
PMBUS_IOUT_OC_LV_FAULT_LIMIT = 0x48, /* R/W word */
PMBUS_IOUT_OC_LV_FAULT_RESPONSE = 0x49, /* R/W byte */
PMBUS_IOUT_OC_WARN_LIMIT = 0x4A, /* R/W word */
PMBUS_IOUT_UC_FAULT_LIMIT = 0x4B, /* R/W word */
PMBUS_IOUT_UC_FAULT_RESPONSE = 0x4C, /* R/W byte */
PMBUS_OT_FAULT_LIMIT = 0x4F, /* R/W word */
PMBUS_OT_FAULT_RESPONSE = 0x50, /* R/W byte */
PMBUS_OT_WARN_LIMIT = 0x51, /* R/W word */
PMBUS_UT_WARN_LIMIT = 0x52, /* R/W word */
PMBUS_UT_FAULT_LIMIT = 0x53, /* R/W word */
PMBUS_UT_FAULT_RESPONSE = 0x54, /* R/W byte */
PMBUS_VIN_OV_FAULT_LIMIT = 0x55, /* R/W word */
PMBUS_VIN_OV_FAULT_RESPONSE = 0x56, /* R/W byte */
PMBUS_VIN_OV_WARN_LIMIT = 0x57, /* R/W word */
PMBUS_VIN_UV_WARN_LIMIT = 0x58, /* R/W word */
PMBUS_VIN_UV_FAULT_LIMIT = 0x59, /* R/W word */
PMBUS_VIN_UV_FAULT_RESPONSE = 0x5A, /* R/W byte */
PMBUS_IIN_OC_FAULT_LIMIT = 0x5B, /* R/W word */
PMBUS_IIN_OC_FAULT_RESPONSE = 0x5C, /* R/W byte */
PMBUS_IIN_OC_WARN_LIMIT = 0x5D, /* R/W word */
PMBUS_POWER_GOOD_ON = 0x5E, /* R/W word */
PMBUS_POWER_GOOD_OFF = 0x5F, /* R/W word */
PMBUS_TON_DELAY = 0x60, /* R/W word */
PMBUS_TON_RISE = 0x61, /* R/W word */
PMBUS_TON_MAX_FAULT_LIMIT = 0x62, /* R/W word */
PMBUS_TON_MAX_FAULT_RESPONSE = 0x63, /* R/W byte */
PMBUS_TOFF_DELAY = 0x64, /* R/W word */
PMBUS_TOFF_FALL = 0x65, /* R/W word */
PMBUS_TOFF_MAX_WARN_LIMIT = 0x66, /* R/W word */
PMBUS_POUT_OP_FAULT_LIMIT = 0x68, /* R/W word */
PMBUS_POUT_OP_FAULT_RESPONSE = 0x69, /* R/W byte */
PMBUS_POUT_OP_WARN_LIMIT = 0x6A, /* R/W word */
PMBUS_PIN_OP_WARN_LIMIT = 0x6B, /* R/W word */
PMBUS_STATUS_BYTE = 0x78, /* R/W byte */
PMBUS_STATUS_WORD = 0x79, /* R/W word */
PMBUS_STATUS_VOUT = 0x7A, /* R/W byte */
PMBUS_STATUS_IOUT = 0x7B, /* R/W byte */
PMBUS_STATUS_INPUT = 0x7C, /* R/W byte */
PMBUS_STATUS_TEMPERATURE = 0x7D, /* R/W byte */
PMBUS_STATUS_CML = 0x7E, /* R/W byte */
PMBUS_STATUS_OTHER = 0x7F, /* R/W byte */
PMBUS_STATUS_MFR_SPECIFIC = 0x80, /* R/W byte */
PMBUS_STATUS_FANS_1_2 = 0x81, /* R/W byte */
PMBUS_STATUS_FANS_3_4 = 0x82, /* R/W byte */
PMBUS_READ_EIN = 0x86, /* Read-Only block 5 bytes */
PMBUS_READ_EOUT = 0x87, /* Read-Only block 5 bytes */
PMBUS_READ_VIN = 0x88, /* Read-Only word */
PMBUS_READ_IIN = 0x89, /* Read-Only word */
PMBUS_READ_VCAP = 0x8A, /* Read-Only word */
PMBUS_READ_VOUT = 0x8B, /* Read-Only word */
PMBUS_READ_IOUT = 0x8C, /* Read-Only word */
PMBUS_READ_TEMPERATURE_1 = 0x8D, /* Read-Only word */
PMBUS_READ_TEMPERATURE_2 = 0x8E, /* Read-Only word */
PMBUS_READ_TEMPERATURE_3 = 0x8F, /* Read-Only word */
PMBUS_READ_FAN_SPEED_1 = 0x90, /* Read-Only word */
PMBUS_READ_FAN_SPEED_2 = 0x91, /* Read-Only word */
PMBUS_READ_FAN_SPEED_3 = 0x92, /* Read-Only word */
PMBUS_READ_FAN_SPEED_4 = 0x93, /* Read-Only word */
PMBUS_READ_DUTY_CYCLE = 0x94, /* Read-Only word */
PMBUS_READ_FREQUENCY = 0x95, /* Read-Only word */
PMBUS_READ_POUT = 0x96, /* Read-Only word */
PMBUS_READ_PIN = 0x97, /* Read-Only word */
PMBUS_REVISION = 0x98, /* Read-Only byte */
PMBUS_MFR_ID = 0x99, /* R/W block */
PMBUS_MFR_MODEL = 0x9A, /* R/W block */
PMBUS_MFR_REVISION = 0x9B, /* R/W block */
PMBUS_MFR_LOCATION = 0x9C, /* R/W block */
PMBUS_MFR_DATE = 0x9D, /* R/W block */
PMBUS_MFR_SERIAL = 0x9E, /* R/W block */
PMBUS_APP_PROFILE_SUPPORT = 0x9F, /* Read-Only block-read */
PMBUS_MFR_VIN_MIN = 0xA0, /* Read-Only word */
PMBUS_MFR_VIN_MAX = 0xA1, /* Read-Only word */
PMBUS_MFR_IIN_MAX = 0xA2, /* Read-Only word */
PMBUS_MFR_PIN_MAX = 0xA3, /* Read-Only word */
PMBUS_MFR_VOUT_MIN = 0xA4, /* Read-Only word */
PMBUS_MFR_VOUT_MAX = 0xA5, /* Read-Only word */
PMBUS_MFR_IOUT_MAX = 0xA6, /* Read-Only word */
PMBUS_MFR_POUT_MAX = 0xA7, /* Read-Only word */
PMBUS_MFR_TAMBIENT_MAX = 0xA8, /* Read-Only word */
PMBUS_MFR_TAMBIENT_MIN = 0xA9, /* Read-Only word */
PMBUS_MFR_EFFICIENCY_LL = 0xAA, /* Read-Only block 14 bytes */
PMBUS_MFR_EFFICIENCY_HL = 0xAB, /* Read-Only block 14 bytes */
PMBUS_MFR_PIN_ACCURACY = 0xAC, /* Read-Only byte */
PMBUS_IC_DEVICE_ID = 0xAD, /* Read-Only block-read */
PMBUS_IC_DEVICE_REV = 0xAE, /* Read-Only block-read */
PMBUS_MFR_MAX_TEMP_1 = 0xC0, /* R/W word */
PMBUS_MFR_MAX_TEMP_2 = 0xC1, /* R/W word */
PMBUS_MFR_MAX_TEMP_3 = 0xC2, /* R/W word */
};
/* STATUS_WORD */
#define PB_STATUS_VOUT BIT(15)
#define PB_STATUS_IOUT_POUT BIT(14)
#define PB_STATUS_INPUT BIT(13)
#define PB_STATUS_WORD_MFR BIT(12)
#define PB_STATUS_POWER_GOOD_N BIT(11)
#define PB_STATUS_FAN BIT(10)
#define PB_STATUS_OTHER BIT(9)
#define PB_STATUS_UNKNOWN BIT(8)
/* STATUS_BYTE */
#define PB_STATUS_BUSY BIT(7)
#define PB_STATUS_OFF BIT(6)
#define PB_STATUS_VOUT_OV BIT(5)
#define PB_STATUS_IOUT_OC BIT(4)
#define PB_STATUS_VIN_UV BIT(3)
#define PB_STATUS_TEMPERATURE BIT(2)
#define PB_STATUS_CML BIT(1)
#define PB_STATUS_NONE_ABOVE BIT(0)
/* STATUS_VOUT */
#define PB_STATUS_VOUT_OV_FAULT BIT(7) /* Output Overvoltage Fault */
#define PB_STATUS_VOUT_OV_WARN BIT(6) /* Output Overvoltage Warning */
#define PB_STATUS_VOUT_UV_WARN BIT(5) /* Output Undervoltage Warning */
#define PB_STATUS_VOUT_UV_FAULT BIT(4) /* Output Undervoltage Fault */
#define PB_STATUS_VOUT_MAX BIT(3)
#define PB_STATUS_VOUT_TON_MAX_FAULT BIT(2)
#define PB_STATUS_VOUT_TOFF_MAX_WARN BIT(1)
/* STATUS_IOUT */
#define PB_STATUS_IOUT_OC_FAULT BIT(7) /* Output Overcurrent Fault */
#define PB_STATUS_IOUT_OC_LV_FAULT BIT(6) /* Output OC And Low Voltage Fault */
#define PB_STATUS_IOUT_OC_WARN BIT(5) /* Output Overcurrent Warning */
#define PB_STATUS_IOUT_UC_FAULT BIT(4) /* Output Undercurrent Fault */
#define PB_STATUS_CURR_SHARE BIT(3) /* Current Share Fault */
#define PB_STATUS_PWR_LIM_MODE BIT(2) /* In Power Limiting Mode */
#define PB_STATUS_POUT_OP_FAULT BIT(1) /* Output Overpower Fault */
#define PB_STATUS_POUT_OP_WARN BIT(0) /* Output Overpower Warning */
/* STATUS_INPUT */
#define PB_STATUS_INPUT_VIN_OV_FAULT BIT(7) /* Input Overvoltage Fault */
#define PB_STATUS_INPUT_VIN_OV_WARN BIT(6) /* Input Overvoltage Warning */
#define PB_STATUS_INPUT_VIN_UV_WARN BIT(5) /* Input Undervoltage Warning */
#define PB_STATUS_INPUT_VIN_UV_FAULT BIT(4) /* Input Undervoltage Fault */
#define PB_STATUS_INPUT_IIN_OC_FAULT BIT(2) /* Input Overcurrent Fault */
#define PB_STATUS_INPUT_IIN_OC_WARN BIT(1) /* Input Overcurrent Warning */
#define PB_STATUS_INPUT_PIN_OP_WARN BIT(0) /* Input Overpower Warning */
/* STATUS_TEMPERATURE */
#define PB_STATUS_OT_FAULT BIT(7) /* Overtemperature Fault */
#define PB_STATUS_OT_WARN BIT(6) /* Overtemperature Warning */
#define PB_STATUS_UT_WARN BIT(5) /* Undertemperature Warning */
#define PB_STATUS_UT_FAULT BIT(4) /* Undertemperature Fault */
/* STATUS_CML */
#define PB_CML_FAULT_INVALID_CMD BIT(7) /* Invalid/Unsupported Command */
#define PB_CML_FAULT_INVALID_DATA BIT(6) /* Invalid/Unsupported Data */
#define PB_CML_FAULT_PEC BIT(5) /* Packet Error Check Failed */
#define PB_CML_FAULT_MEMORY BIT(4) /* Memory Fault Detected */
#define PB_CML_FAULT_PROCESSOR BIT(3) /* Processor Fault Detected */
#define PB_CML_FAULT_OTHER_COMM BIT(1) /* Other communication fault */
#define PB_CML_FAULT_OTHER_MEM_LOGIC BIT(0) /* Other Memory Or Logic Fault */
/* OPERATION*/
#define PB_OP_ON BIT(7) /* PSU is switched on */
#define PB_OP_MARGIN_HIGH BIT(5) /* PSU vout is set to margin high */
#define PB_OP_MARGIN_LOW BIT(4) /* PSU vout is set to margin low */
/* PAGES */
#define PB_MAX_PAGES 0x1F
#define PB_ALL_PAGES 0xFF
#define TYPE_PMBUS_DEVICE "pmbus-device"
OBJECT_DECLARE_TYPE(PMBusDevice, PMBusDeviceClass,
PMBUS_DEVICE)
/* flags */
#define PB_HAS_COEFFICIENTS BIT_ULL(9)
#define PB_HAS_VIN BIT_ULL(10)
#define PB_HAS_VOUT BIT_ULL(11)
#define PB_HAS_VOUT_MARGIN BIT_ULL(12)
#define PB_HAS_VIN_RATING BIT_ULL(13)
#define PB_HAS_VOUT_RATING BIT_ULL(14)
#define PB_HAS_VOUT_MODE BIT_ULL(15)
#define PB_HAS_IOUT BIT_ULL(21)
#define PB_HAS_IIN BIT_ULL(22)
#define PB_HAS_IOUT_RATING BIT_ULL(23)
#define PB_HAS_IIN_RATING BIT_ULL(24)
#define PB_HAS_IOUT_GAIN BIT_ULL(25)
#define PB_HAS_POUT BIT_ULL(30)
#define PB_HAS_PIN BIT_ULL(31)
#define PB_HAS_EIN BIT_ULL(32)
#define PB_HAS_EOUT BIT_ULL(33)
#define PB_HAS_POUT_RATING BIT_ULL(34)
#define PB_HAS_PIN_RATING BIT_ULL(35)
#define PB_HAS_TEMPERATURE BIT_ULL(40)
#define PB_HAS_TEMP2 BIT_ULL(41)
#define PB_HAS_TEMP3 BIT_ULL(42)
#define PB_HAS_TEMP_RATING BIT_ULL(43)
#define PB_HAS_MFR_INFO BIT_ULL(50)
struct PMBusDeviceClass {
SMBusDeviceClass parent_class;
uint8_t device_num_pages;
/**
* Implement quick_cmd, receive byte, and write_data to support non-standard
* PMBus functionality
*/
void (*quick_cmd)(PMBusDevice *dev, uint8_t read);
int (*write_data)(PMBusDevice *dev, const uint8_t *buf, uint8_t len);
uint8_t (*receive_byte)(PMBusDevice *dev);
};
/*
* According to the spec, each page may offer the full range of PMBus commands
* available for each output or non-PMBus device.
* Therefore, we can't assume that any registers will always be the same across
* all pages.
* The page 0xFF is intended for writes to all pages
*/
typedef struct PMBusPage {
uint64_t page_flags;
uint8_t page; /* R/W byte */
uint8_t operation; /* R/W byte */
uint8_t on_off_config; /* R/W byte */
uint8_t write_protect; /* R/W byte */
uint8_t phase; /* R/W byte */
uint8_t vout_mode; /* R/W byte */
uint16_t vout_command; /* R/W word */
uint16_t vout_trim; /* R/W word */
uint16_t vout_cal_offset; /* R/W word */
uint16_t vout_max; /* R/W word */
uint16_t vout_margin_high; /* R/W word */
uint16_t vout_margin_low; /* R/W word */
uint16_t vout_transition_rate; /* R/W word */
uint16_t vout_droop; /* R/W word */
uint16_t vout_scale_loop; /* R/W word */
uint16_t vout_scale_monitor; /* R/W word */
uint8_t coefficients[5]; /* Read-only block 5 bytes */
uint16_t pout_max; /* R/W word */
uint16_t max_duty; /* R/W word */
uint16_t frequency_switch; /* R/W word */
uint16_t vin_on; /* R/W word */
uint16_t vin_off; /* R/W word */
uint16_t iout_cal_gain; /* R/W word */
uint16_t iout_cal_offset; /* R/W word */
uint8_t fan_config_1_2; /* R/W byte */
uint16_t fan_command_1; /* R/W word */
uint16_t fan_command_2; /* R/W word */
uint8_t fan_config_3_4; /* R/W byte */
uint16_t fan_command_3; /* R/W word */
uint16_t fan_command_4; /* R/W word */
uint16_t vout_ov_fault_limit; /* R/W word */
uint8_t vout_ov_fault_response; /* R/W byte */
uint16_t vout_ov_warn_limit; /* R/W word */
uint16_t vout_uv_warn_limit; /* R/W word */
uint16_t vout_uv_fault_limit; /* R/W word */
uint8_t vout_uv_fault_response; /* R/W byte */
uint16_t iout_oc_fault_limit; /* R/W word */
uint8_t iout_oc_fault_response; /* R/W byte */
uint16_t iout_oc_lv_fault_limit; /* R/W word */
uint8_t iout_oc_lv_fault_response; /* R/W byte */
uint16_t iout_oc_warn_limit; /* R/W word */
uint16_t iout_uc_fault_limit; /* R/W word */
uint8_t iout_uc_fault_response; /* R/W byte */
uint16_t ot_fault_limit; /* R/W word */
uint8_t ot_fault_response; /* R/W byte */
uint16_t ot_warn_limit; /* R/W word */
uint16_t ut_warn_limit; /* R/W word */
uint16_t ut_fault_limit; /* R/W word */
uint8_t ut_fault_response; /* R/W byte */
uint16_t vin_ov_fault_limit; /* R/W word */
uint8_t vin_ov_fault_response; /* R/W byte */
uint16_t vin_ov_warn_limit; /* R/W word */
uint16_t vin_uv_warn_limit; /* R/W word */
uint16_t vin_uv_fault_limit; /* R/W word */
uint8_t vin_uv_fault_response; /* R/W byte */
uint16_t iin_oc_fault_limit; /* R/W word */
uint8_t iin_oc_fault_response; /* R/W byte */
uint16_t iin_oc_warn_limit; /* R/W word */
uint16_t power_good_on; /* R/W word */
uint16_t power_good_off; /* R/W word */
uint16_t ton_delay; /* R/W word */
uint16_t ton_rise; /* R/W word */
uint16_t ton_max_fault_limit; /* R/W word */
uint8_t ton_max_fault_response; /* R/W byte */
uint16_t toff_delay; /* R/W word */
uint16_t toff_fall; /* R/W word */
uint16_t toff_max_warn_limit; /* R/W word */
uint16_t pout_op_fault_limit; /* R/W word */
uint8_t pout_op_fault_response; /* R/W byte */
uint16_t pout_op_warn_limit; /* R/W word */
uint16_t pin_op_warn_limit; /* R/W word */
uint16_t status_word; /* R/W word */
uint8_t status_vout; /* R/W byte */
uint8_t status_iout; /* R/W byte */
uint8_t status_input; /* R/W byte */
uint8_t status_temperature; /* R/W byte */
uint8_t status_cml; /* R/W byte */
uint8_t status_other; /* R/W byte */
uint8_t status_mfr_specific; /* R/W byte */
uint8_t status_fans_1_2; /* R/W byte */
uint8_t status_fans_3_4; /* R/W byte */
uint8_t read_ein[5]; /* Read-Only block 5 bytes */
uint8_t read_eout[5]; /* Read-Only block 5 bytes */
uint16_t read_vin; /* Read-Only word */
uint16_t read_iin; /* Read-Only word */
uint16_t read_vcap; /* Read-Only word */
uint16_t read_vout; /* Read-Only word */
uint16_t read_iout; /* Read-Only word */
uint16_t read_temperature_1; /* Read-Only word */
uint16_t read_temperature_2; /* Read-Only word */
uint16_t read_temperature_3; /* Read-Only word */
uint16_t read_fan_speed_1; /* Read-Only word */
uint16_t read_fan_speed_2; /* Read-Only word */
uint16_t read_fan_speed_3; /* Read-Only word */
uint16_t read_fan_speed_4; /* Read-Only word */
uint16_t read_duty_cycle; /* Read-Only word */
uint16_t read_frequency; /* Read-Only word */
uint16_t read_pout; /* Read-Only word */
uint16_t read_pin; /* Read-Only word */
uint8_t revision; /* Read-Only byte */
const char *mfr_id; /* R/W block */
const char *mfr_model; /* R/W block */
const char *mfr_revision; /* R/W block */
const char *mfr_location; /* R/W block */
const char *mfr_date; /* R/W block */
const char *mfr_serial; /* R/W block */
const char *app_profile_support; /* Read-Only block-read */
uint16_t mfr_vin_min; /* Read-Only word */
uint16_t mfr_vin_max; /* Read-Only word */
uint16_t mfr_iin_max; /* Read-Only word */
uint16_t mfr_pin_max; /* Read-Only word */
uint16_t mfr_vout_min; /* Read-Only word */
uint16_t mfr_vout_max; /* Read-Only word */
uint16_t mfr_iout_max; /* Read-Only word */
uint16_t mfr_pout_max; /* Read-Only word */
uint16_t mfr_tambient_max; /* Read-Only word */
uint16_t mfr_tambient_min; /* Read-Only word */
uint8_t mfr_efficiency_ll[14]; /* Read-Only block 14 bytes */
uint8_t mfr_efficiency_hl[14]; /* Read-Only block 14 bytes */
uint8_t mfr_pin_accuracy; /* Read-Only byte */
uint16_t mfr_max_temp_1; /* R/W word */
uint16_t mfr_max_temp_2; /* R/W word */
uint16_t mfr_max_temp_3; /* R/W word */
} PMBusPage;
/* State */
struct PMBusDevice {
SMBusDevice smb;
uint8_t num_pages;
uint8_t code;
uint8_t page;
/*
* PMBus registers are stored in a PMBusPage structure allocated by
* calling pmbus_pages_alloc()
*/
PMBusPage *pages;
uint8_t capability;
int32_t in_buf_len;
uint8_t *in_buf;
int32_t out_buf_len;
uint8_t out_buf[SMBUS_DATA_MAX_LEN];
};
/**
* Direct mode coefficients
* @var m - mantissa
* @var b - offset
* @var R - exponent
*/
typedef struct PMBusCoefficients {
int32_t m; /* mantissa */
int64_t b; /* offset */
int32_t R; /* exponent */
} PMBusCoefficients;
/**
* Convert sensor values to direct mode format
*
* Y = (m * x - b) * 10^R
*
* @return uint32_t
*/
uint16_t pmbus_data2direct_mode(PMBusCoefficients c, uint32_t value);
/**
* Convert direct mode formatted data into sensor reading
*
* X = (Y * 10^-R - b) / m
*
* @return uint32_t
*/
uint32_t pmbus_direct_mode2data(PMBusCoefficients c, uint16_t value);
/**
* @brief Send a block of data over PMBus
* Assumes that the bytes in the block are already ordered correctly,
* also assumes the length has been prepended to the block if necessary
* | low_byte | ... | high_byte |
* @param state - maintains state of the PMBus device
* @param data - byte array to be sent by device
* @param len - number
*/
void pmbus_send(PMBusDevice *state, const uint8_t *data, uint16_t len);
void pmbus_send8(PMBusDevice *state, uint8_t data);
void pmbus_send16(PMBusDevice *state, uint16_t data);
void pmbus_send32(PMBusDevice *state, uint32_t data);
void pmbus_send64(PMBusDevice *state, uint64_t data);
/**
* @brief Send a string over PMBus with length prepended.
* Length is calculated using str_len()
*/
void pmbus_send_string(PMBusDevice *state, const char *data);
/**
* @brief Receive data over PMBus
* These methods help track how much data is being received over PMBus
* Log to GUEST_ERROR if too much or too little is sent.
*/
uint8_t pmbus_receive8(PMBusDevice *pmdev);
uint16_t pmbus_receive16(PMBusDevice *pmdev);
uint32_t pmbus_receive32(PMBusDevice *pmdev);
uint64_t pmbus_receive64(PMBusDevice *pmdev);
/**
* PMBus page config must be called before any page is first used.
* It will allocate memory for all the pages if needed.
* Passed in flags overwrite existing flags if any.
* @param page_index the page to which the flags are applied, setting page_index
* to 0xFF applies the passed in flags to all pages.
* @param flags
*/
int pmbus_page_config(PMBusDevice *pmdev, uint8_t page_index, uint64_t flags);
/**
* Update the status registers when sensor values change.
* Useful if modifying sensors through qmp, this way status registers get
* updated
*/
void pmbus_check_limits(PMBusDevice *pmdev);
extern const VMStateDescription vmstate_pmbus_device;
#define VMSTATE_PMBUS_DEVICE(_field, _state) { \
.name = (stringify(_field)), \
.size = sizeof(PMBusDevice), \
.vmsd = &vmstate_pmbus_device, \
.flags = VMS_STRUCT, \
.offset = vmstate_offset_value(_state, _field, PMBusDevice), \
}
#endif

28
include/hw/input/lm832x.h Normal file
View file

@ -0,0 +1,28 @@
/*
* National Semiconductor LM8322/8323 GPIO keyboard & PWM chips.
*
* Copyright (C) 2008 Nokia Corporation
* Written by Andrzej Zaborowski <andrew@openedhand.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 or
* (at your option) version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef HW_INPUT_LM832X
#define HW_INPUT_LM832X
#define TYPE_LM8323 "lm8323"
void lm832x_key_event(DeviceState *dev, int key, int state);
#endif

View file

@ -15,7 +15,7 @@
#define QEMU_TMP105_H
#include "hw/i2c/i2c.h"
#include "hw/misc/tmp105_regs.h"
#include "hw/sensor/tmp105_regs.h"
#include "qom/object.h"
#define TYPE_TMP105 "tmp105"

445
tests/qtest/adm1272-test.c Normal file
View file

@ -0,0 +1,445 @@
/*
* QTests for the ADM1272 hotswap controller
*
* Copyright 2021 Google LLC
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include <math.h>
#include "hw/i2c/pmbus_device.h"
#include "libqtest-single.h"
#include "libqos/qgraph.h"
#include "libqos/i2c.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qnum.h"
#include "qemu/bitops.h"
#define TEST_ID "adm1272-test"
#define TEST_ADDR (0x10)
#define ADM1272_RESTART_TIME 0xCC
#define ADM1272_MFR_PEAK_IOUT 0xD0
#define ADM1272_MFR_PEAK_VIN 0xD1
#define ADM1272_MFR_PEAK_VOUT 0xD2
#define ADM1272_MFR_PMON_CONTROL 0xD3
#define ADM1272_MFR_PMON_CONFIG 0xD4
#define ADM1272_MFR_ALERT1_CONFIG 0xD5
#define ADM1272_MFR_ALERT2_CONFIG 0xD6
#define ADM1272_MFR_PEAK_TEMPERATURE 0xD7
#define ADM1272_MFR_DEVICE_CONFIG 0xD8
#define ADM1272_MFR_POWER_CYCLE 0xD9
#define ADM1272_MFR_PEAK_PIN 0xDA
#define ADM1272_MFR_READ_PIN_EXT 0xDB
#define ADM1272_MFR_READ_EIN_EXT 0xDC
#define ADM1272_HYSTERESIS_LOW 0xF2
#define ADM1272_HYSTERESIS_HIGH 0xF3
#define ADM1272_STATUS_HYSTERESIS 0xF4
#define ADM1272_STATUS_GPIO 0xF5
#define ADM1272_STRT_UP_IOUT_LIM 0xF6
/* Defaults */
#define ADM1272_OPERATION_DEFAULT 0x80
#define ADM1272_CAPABILITY_DEFAULT 0xB0
#define ADM1272_CAPABILITY_NO_PEC 0x30
#define ADM1272_DIRECT_MODE 0x40
#define ADM1272_HIGH_LIMIT_DEFAULT 0x0FFF
#define ADM1272_PIN_OP_DEFAULT 0x7FFF
#define ADM1272_PMBUS_REVISION_DEFAULT 0x22
#define ADM1272_MFR_ID_DEFAULT "ADI"
#define ADM1272_MODEL_DEFAULT "ADM1272-A1"
#define ADM1272_MFR_DEFAULT_REVISION "25"
#define ADM1272_DEFAULT_DATE "160301"
#define ADM1272_RESTART_TIME_DEFAULT 0x64
#define ADM1272_PMON_CONTROL_DEFAULT 0x1
#define ADM1272_PMON_CONFIG_DEFAULT 0x3F35
#define ADM1272_DEVICE_CONFIG_DEFAULT 0x8
#define ADM1272_HYSTERESIS_HIGH_DEFAULT 0xFFFF
#define ADM1272_STRT_UP_IOUT_LIM_DEFAULT 0x000F
#define ADM1272_VOLT_DEFAULT 12000
#define ADM1272_IOUT_DEFAULT 25000
#define ADM1272_PWR_DEFAULT 300 /* 12V 25A */
#define ADM1272_SHUNT 300 /* micro-ohms */
#define ADM1272_VOLTAGE_COEFF_DEFAULT 1
#define ADM1272_CURRENT_COEFF_DEFAULT 3
#define ADM1272_PWR_COEFF_DEFAULT 7
#define ADM1272_IOUT_OFFSET 0x5000
#define ADM1272_IOUT_OFFSET 0x5000
static const PMBusCoefficients adm1272_coefficients[] = {
[0] = { 6770, 0, -2 }, /* voltage, vrange 60V */
[1] = { 4062, 0, -2 }, /* voltage, vrange 100V */
[2] = { 1326, 20480, -1 }, /* current, vsense range 15mV */
[3] = { 663, 20480, -1 }, /* current, vsense range 30mV */
[4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */
[5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */
[6] = { 17561, 0, -3 }, /* power, vrange 60V, irange 30mV */
[7] = { 10535, 0, -3 }, /* power, vrange 100V, irange 30mV */
[8] = { 42, 31871, -1 }, /* temperature */
};
uint16_t pmbus_data2direct_mode(PMBusCoefficients c, uint32_t value)
{
/* R is usually negative to fit large readings into 16 bits */
uint16_t y = (c.m * value + c.b) * pow(10, c.R);
return y;
}
uint32_t pmbus_direct_mode2data(PMBusCoefficients c, uint16_t value)
{
/* X = (Y * 10^-R - b) / m */
uint32_t x = (value / pow(10, c.R) - c.b) / c.m;
return x;
}
static uint16_t adm1272_millivolts_to_direct(uint32_t value)
{
PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
c.b = c.b * 1000;
c.R = c.R - 3;
return pmbus_data2direct_mode(c, value);
}
static uint32_t adm1272_direct_to_millivolts(uint16_t value)
{
PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
c.b = c.b * 1000;
c.R = c.R - 3;
return pmbus_direct_mode2data(c, value);
}
static uint16_t adm1272_milliamps_to_direct(uint32_t value)
{
PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
/* Y = (m * r_sense * x - b) * 10^R */
c.m = c.m * ADM1272_SHUNT / 1000; /* micro-ohms */
c.b = c.b * 1000;
c.R = c.R - 3;
return pmbus_data2direct_mode(c, value);
}
static uint32_t adm1272_direct_to_milliamps(uint16_t value)
{
PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
c.m = c.m * ADM1272_SHUNT / 1000;
c.b = c.b * 1000;
c.R = c.R - 3;
return pmbus_direct_mode2data(c, value);
}
static uint16_t adm1272_watts_to_direct(uint32_t value)
{
PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
c.m = c.m * ADM1272_SHUNT / 1000;
return pmbus_data2direct_mode(c, value);
}
static uint32_t adm1272_direct_to_watts(uint16_t value)
{
PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
c.m = c.m * ADM1272_SHUNT / 1000;
return pmbus_direct_mode2data(c, value);
}
static uint16_t qmp_adm1272_get(const char *id, const char *property)
{
QDict *response;
uint64_t ret;
response = qmp("{ 'execute': 'qom-get', 'arguments': { 'path': %s, "
"'property': %s } }", id, property);
g_assert(qdict_haskey(response, "return"));
ret = qnum_get_uint(qobject_to(QNum, qdict_get(response, "return")));
qobject_unref(response);
return ret;
}
static void qmp_adm1272_set(const char *id,
const char *property,
uint16_t value)
{
QDict *response;
response = qmp("{ 'execute': 'qom-set', 'arguments': { 'path': %s, "
"'property': %s, 'value': %u } }", id, property, value);
g_assert(qdict_haskey(response, "return"));
qobject_unref(response);
}
/* PMBus commands are little endian vs i2c_set16 in i2c.h which is big endian */
static uint16_t adm1272_i2c_get16(QI2CDevice *i2cdev, uint8_t reg)
{
uint8_t resp[2];
i2c_read_block(i2cdev, reg, resp, sizeof(resp));
return (resp[1] << 8) | resp[0];
}
/* PMBus commands are little endian vs i2c_set16 in i2c.h which is big endian */
static void adm1272_i2c_set16(QI2CDevice *i2cdev, uint8_t reg, uint16_t value)
{
uint8_t data[2];
data[0] = value & 255;
data[1] = value >> 8;
i2c_write_block(i2cdev, reg, data, sizeof(data));
}
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
uint16_t value, i2c_value;
int16_t err;
QI2CDevice *i2cdev = (QI2CDevice *)obj;
value = qmp_adm1272_get(TEST_ID, "vout");
err = ADM1272_VOLT_DEFAULT - value;
g_assert_cmpuint(abs(err), <, ADM1272_VOLT_DEFAULT / 20);
i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
g_assert_cmphex(i2c_value, ==, ADM1272_OPERATION_DEFAULT);
i2c_value = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
g_assert_cmphex(i2c_value, ==, ADM1272_DIRECT_MODE);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, 0);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_IOUT_OC_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_OT_FAULT_LIMIT);
g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_OT_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VIN_OV_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, ADM1272_HIGH_LIMIT_DEFAULT);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VIN_UV_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, 0);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_PIN_OP_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, ADM1272_PIN_OP_DEFAULT);
i2c_value = i2c_get8(i2cdev, PMBUS_REVISION);
g_assert_cmphex(i2c_value, ==, ADM1272_PMBUS_REVISION_DEFAULT);
i2c_value = i2c_get8(i2cdev, ADM1272_MFR_PMON_CONTROL);
g_assert_cmphex(i2c_value, ==, ADM1272_PMON_CONTROL_DEFAULT);
i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_PMON_CONFIG);
g_assert_cmphex(i2c_value, ==, ADM1272_PMON_CONFIG_DEFAULT);
i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_DEVICE_CONFIG);
g_assert_cmphex(i2c_value, ==, ADM1272_DEVICE_CONFIG_DEFAULT);
i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_HYSTERESIS_HIGH);
g_assert_cmphex(i2c_value, ==, ADM1272_HYSTERESIS_HIGH_DEFAULT);
i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_STRT_UP_IOUT_LIM);
g_assert_cmphex(i2c_value, ==, ADM1272_STRT_UP_IOUT_LIM_DEFAULT);
}
/* test qmp access */
static void test_tx_rx(void *obj, void *data, QGuestAllocator *alloc)
{
uint16_t i2c_value, value, i2c_voltage, i2c_pwr, lossy_value;
QI2CDevice *i2cdev = (QI2CDevice *)obj;
/* converting to direct mode is lossy - we generate the same loss here */
lossy_value =
adm1272_direct_to_millivolts(adm1272_millivolts_to_direct(1000));
qmp_adm1272_set(TEST_ID, "vin", 1000);
value = qmp_adm1272_get(TEST_ID, "vin");
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VIN);
i2c_voltage = adm1272_direct_to_millivolts(i2c_value);
g_assert_cmpuint(value, ==, i2c_voltage);
g_assert_cmpuint(i2c_voltage, ==, lossy_value);
lossy_value =
adm1272_direct_to_millivolts(adm1272_millivolts_to_direct(1500));
qmp_adm1272_set(TEST_ID, "vout", 1500);
value = qmp_adm1272_get(TEST_ID, "vout");
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VOUT);
i2c_voltage = adm1272_direct_to_millivolts(i2c_value);
g_assert_cmpuint(value, ==, i2c_voltage);
g_assert_cmpuint(i2c_voltage, ==, lossy_value);
lossy_value =
adm1272_direct_to_milliamps(adm1272_milliamps_to_direct(1600));
qmp_adm1272_set(TEST_ID, "iout", 1600);
value = qmp_adm1272_get(TEST_ID, "iout");
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_IOUT);
i2c_value = adm1272_direct_to_milliamps(i2c_value);
g_assert_cmphex(value, ==, i2c_value);
g_assert_cmphex(i2c_value, ==, lossy_value);
lossy_value =
adm1272_direct_to_watts(adm1272_watts_to_direct(320));
qmp_adm1272_set(TEST_ID, "pin", 320);
value = qmp_adm1272_get(TEST_ID, "pin");
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_PIN);
i2c_pwr = adm1272_direct_to_watts(i2c_value);
g_assert_cmphex(value, ==, i2c_pwr);
g_assert_cmphex(i2c_pwr, ==, lossy_value);
}
/* test r/w registers */
static void test_rw_regs(void *obj, void *data, QGuestAllocator *alloc)
{
uint16_t i2c_value;
QI2CDevice *i2cdev = (QI2CDevice *)obj;
adm1272_i2c_set16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT, 0xABCD);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, 0xABCD);
adm1272_i2c_set16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT, 0xCDEF);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, 0xCDEF);
adm1272_i2c_set16(i2cdev, PMBUS_IOUT_OC_WARN_LIMIT, 0x1234);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_IOUT_OC_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, 0x1234);
adm1272_i2c_set16(i2cdev, PMBUS_OT_FAULT_LIMIT, 0x5678);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_OT_FAULT_LIMIT);
g_assert_cmphex(i2c_value, ==, 0x5678);
adm1272_i2c_set16(i2cdev, PMBUS_OT_WARN_LIMIT, 0xABDC);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_OT_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, 0xABDC);
adm1272_i2c_set16(i2cdev, PMBUS_VIN_OV_WARN_LIMIT, 0xCDEF);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VIN_OV_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, 0xCDEF);
adm1272_i2c_set16(i2cdev, PMBUS_VIN_UV_WARN_LIMIT, 0x2345);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_VIN_UV_WARN_LIMIT);
g_assert_cmphex(i2c_value, ==, 0x2345);
i2c_set8(i2cdev, ADM1272_RESTART_TIME, 0xF8);
i2c_value = i2c_get8(i2cdev, ADM1272_RESTART_TIME);
g_assert_cmphex(i2c_value, ==, 0xF8);
i2c_set8(i2cdev, ADM1272_MFR_PMON_CONTROL, 0);
i2c_value = i2c_get8(i2cdev, ADM1272_MFR_PMON_CONTROL);
g_assert_cmpuint(i2c_value, ==, 0);
adm1272_i2c_set16(i2cdev, ADM1272_MFR_PMON_CONFIG, 0xDEF0);
i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_PMON_CONFIG);
g_assert_cmphex(i2c_value, ==, 0xDEF0);
adm1272_i2c_set16(i2cdev, ADM1272_MFR_ALERT1_CONFIG, 0x0123);
i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_ALERT1_CONFIG);
g_assert_cmphex(i2c_value, ==, 0x0123);
adm1272_i2c_set16(i2cdev, ADM1272_MFR_ALERT2_CONFIG, 0x9876);
i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_ALERT2_CONFIG);
g_assert_cmphex(i2c_value, ==, 0x9876);
adm1272_i2c_set16(i2cdev, ADM1272_MFR_DEVICE_CONFIG, 0x3456);
i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_MFR_DEVICE_CONFIG);
g_assert_cmphex(i2c_value, ==, 0x3456);
adm1272_i2c_set16(i2cdev, ADM1272_HYSTERESIS_LOW, 0xCABA);
i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_HYSTERESIS_LOW);
g_assert_cmphex(i2c_value, ==, 0xCABA);
adm1272_i2c_set16(i2cdev, ADM1272_HYSTERESIS_HIGH, 0x6789);
i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_HYSTERESIS_HIGH);
g_assert_cmphex(i2c_value, ==, 0x6789);
adm1272_i2c_set16(i2cdev, ADM1272_STRT_UP_IOUT_LIM, 0x9876);
i2c_value = adm1272_i2c_get16(i2cdev, ADM1272_STRT_UP_IOUT_LIM);
g_assert_cmphex(i2c_value, ==, 0x9876);
adm1272_i2c_set16(i2cdev, PMBUS_OPERATION, 0xA);
i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
g_assert_cmphex(i2c_value, ==, 0xA);
}
/* test read-only registers */
static void test_ro_regs(void *obj, void *data, QGuestAllocator *alloc)
{
uint16_t i2c_init_value, i2c_value;
QI2CDevice *i2cdev = (QI2CDevice *)obj;
i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VIN);
adm1272_i2c_set16(i2cdev, PMBUS_READ_VIN, 0xBEEF);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VIN);
g_assert_cmphex(i2c_init_value, ==, i2c_value);
i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VOUT);
adm1272_i2c_set16(i2cdev, PMBUS_READ_VOUT, 0x1234);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_VOUT);
g_assert_cmphex(i2c_init_value, ==, i2c_value);
i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_IOUT);
adm1272_i2c_set16(i2cdev, PMBUS_READ_IOUT, 0x6547);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_IOUT);
g_assert_cmphex(i2c_init_value, ==, i2c_value);
i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
adm1272_i2c_set16(i2cdev, PMBUS_READ_TEMPERATURE_1, 0x1597);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
g_assert_cmphex(i2c_init_value, ==, i2c_value);
i2c_init_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_PIN);
adm1272_i2c_set16(i2cdev, PMBUS_READ_PIN, 0xDEAD);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_READ_PIN);
g_assert_cmphex(i2c_init_value, ==, i2c_value);
}
/* test voltage fault handling */
static void test_voltage_faults(void *obj, void *data, QGuestAllocator *alloc)
{
uint16_t i2c_value;
uint8_t i2c_byte;
QI2CDevice *i2cdev = (QI2CDevice *)obj;
adm1272_i2c_set16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT,
adm1272_millivolts_to_direct(5000));
qmp_adm1272_set(TEST_ID, "vout", 5100);
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_STATUS_WORD);
i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_VOUT);
g_assert_true((i2c_value & PB_STATUS_VOUT) != 0);
g_assert_true((i2c_byte & PB_STATUS_VOUT_OV_WARN) != 0);
qmp_adm1272_set(TEST_ID, "vout", 4500);
i2c_set8(i2cdev, PMBUS_CLEAR_FAULTS, 0);
i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_VOUT);
g_assert_true((i2c_byte & PB_STATUS_VOUT_OV_WARN) == 0);
adm1272_i2c_set16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT,
adm1272_millivolts_to_direct(4600));
i2c_value = adm1272_i2c_get16(i2cdev, PMBUS_STATUS_WORD);
i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_VOUT);
g_assert_true((i2c_value & PB_STATUS_VOUT) != 0);
g_assert_true((i2c_byte & PB_STATUS_VOUT_UV_WARN) != 0);
}
static void adm1272_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
.extra_device_opts = "id=" TEST_ID ",address=0x10"
};
add_qi2c_address(&opts, &(QI2CAddress) { TEST_ADDR });
qos_node_create_driver("adm1272", i2c_device_create);
qos_node_consumes("adm1272", "i2c-bus", &opts);
qos_add_test("test_defaults", "adm1272", test_defaults, NULL);
qos_add_test("test_tx_rx", "adm1272", test_tx_rx, NULL);
qos_add_test("test_rw_regs", "adm1272", test_rw_regs, NULL);
qos_add_test("test_ro_regs", "adm1272", test_ro_regs, NULL);
qos_add_test("test_ov_faults", "adm1272", test_voltage_faults, NULL);
}
libqos_init(adm1272_register_nodes);

View file

@ -11,7 +11,7 @@
#include "libqos/qgraph.h"
#include "libqos/i2c.h"
#include "qapi/qmp/qdict.h"
#include "hw/misc/emc141x_regs.h"
#include "hw/sensor/emc141x_regs.h"
#define EMC1414_TEST_ID "emc1414-test"

336
tests/qtest/max34451-test.c Normal file
View file

@ -0,0 +1,336 @@
/*
* QTests for the MAX34451 device
*
* Copyright 2021 Google LLC
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "hw/i2c/pmbus_device.h"
#include "libqtest-single.h"
#include "libqos/qgraph.h"
#include "libqos/i2c.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qnum.h"
#include "qemu/bitops.h"
#define TEST_ID "max34451-test"
#define TEST_ADDR (0x4e)
#define MAX34451_MFR_VOUT_PEAK 0xD4
#define MAX34451_MFR_IOUT_PEAK 0xD5
#define MAX34451_MFR_TEMPERATURE_PEAK 0xD6
#define MAX34451_MFR_VOUT_MIN 0xD7
#define DEFAULT_VOUT 0
#define DEFAULT_UV_LIMIT 0
#define DEFAULT_TEMPERATURE 2500
#define DEFAULT_SCALE 0x7FFF
#define DEFAULT_OV_LIMIT 0x7FFF
#define DEFAULT_OC_LIMIT 0x7FFF
#define DEFAULT_OT_LIMIT 0x7FFF
#define DEFAULT_VMIN 0x7FFF
#define DEFAULT_TON_FAULT_LIMIT 0xFFFF
#define DEFAULT_CHANNEL_CONFIG 0x20
#define DEFAULT_TEXT 0x20
#define MAX34451_NUM_PWR_DEVICES 16
#define MAX34451_NUM_TEMP_DEVICES 5
static uint16_t qmp_max34451_get(const char *id, const char *property)
{
QDict *response;
uint16_t ret;
response = qmp("{ 'execute': 'qom-get', 'arguments': { 'path': %s, "
"'property': %s } }", id, property);
g_assert(qdict_haskey(response, "return"));
ret = qnum_get_uint(qobject_to(QNum, qdict_get(response, "return")));
qobject_unref(response);
return ret;
}
static void qmp_max34451_set(const char *id,
const char *property,
uint16_t value)
{
QDict *response;
response = qmp("{ 'execute': 'qom-set', 'arguments': { 'path': %s, "
"'property': %s, 'value': %u } }",
id, property, value);
g_assert(qdict_haskey(response, "return"));
qobject_unref(response);
}
/* PMBus commands are little endian vs i2c_set16 in i2c.h which is big endian */
static uint16_t max34451_i2c_get16(QI2CDevice *i2cdev, uint8_t reg)
{
uint8_t resp[2];
i2c_read_block(i2cdev, reg, resp, sizeof(resp));
return (resp[1] << 8) | resp[0];
}
/* PMBus commands are little endian vs i2c_set16 in i2c.h which is big endian */
static void max34451_i2c_set16(QI2CDevice *i2cdev, uint8_t reg, uint16_t value)
{
uint8_t data[2];
data[0] = value & 255;
data[1] = value >> 8;
i2c_write_block(i2cdev, reg, data, sizeof(data));
}
/* Test default values */
static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
{
uint16_t value, i2c_value;
QI2CDevice *i2cdev = (QI2CDevice *)obj;
char *path;
/* Default temperatures and temperature fault limits */
for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
path = g_strdup_printf("temperature[%d]", i);
value = qmp_max34451_get(TEST_ID, path);
g_assert_cmpuint(value, ==, DEFAULT_TEMPERATURE);
g_free(path);
/* Temperature sensors start on page 16 */
i2c_set8(i2cdev, PMBUS_PAGE, i + 16);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
g_assert_cmpuint(i2c_value, ==, DEFAULT_TEMPERATURE);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_OT_FAULT_LIMIT);
g_assert_cmpuint(i2c_value, ==, DEFAULT_OT_LIMIT);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_OT_WARN_LIMIT);
g_assert_cmpuint(i2c_value, ==, DEFAULT_OT_LIMIT);
}
/* Default voltages and fault limits */
for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
path = g_strdup_printf("vout[%d]", i);
value = qmp_max34451_get(TEST_ID, path);
g_assert_cmpuint(value, ==, DEFAULT_VOUT);
g_free(path);
i2c_set8(i2cdev, PMBUS_PAGE, i);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_VOUT);
g_assert_cmpuint(i2c_value, ==, DEFAULT_VOUT);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_OV_FAULT_LIMIT);
g_assert_cmpuint(i2c_value, ==, DEFAULT_OV_LIMIT);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT);
g_assert_cmpuint(i2c_value, ==, DEFAULT_OV_LIMIT);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT);
g_assert_cmpuint(i2c_value, ==, DEFAULT_UV_LIMIT);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_UV_FAULT_LIMIT);
g_assert_cmpuint(i2c_value, ==, DEFAULT_UV_LIMIT);
i2c_value = max34451_i2c_get16(i2cdev, MAX34451_MFR_VOUT_MIN);
g_assert_cmpuint(i2c_value, ==, DEFAULT_VMIN);
}
i2c_value = i2c_get8(i2cdev, PMBUS_VOUT_MODE);
g_assert_cmphex(i2c_value, ==, 0x40); /* DIRECT mode */
i2c_value = i2c_get8(i2cdev, PMBUS_REVISION);
g_assert_cmphex(i2c_value, ==, 0x11); /* Rev 1.1 */
}
/* Test setting temperature */
static void test_temperature(void *obj, void *data, QGuestAllocator *alloc)
{
uint16_t value, i2c_value;
QI2CDevice *i2cdev = (QI2CDevice *)obj;
char *path;
for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
path = g_strdup_printf("temperature[%d]", i);
qmp_max34451_set(TEST_ID, path, 0xBE00 + i);
value = qmp_max34451_get(TEST_ID, path);
g_assert_cmphex(value, ==, 0xBE00 + i);
g_free(path);
}
/* compare qmp read with i2c read separately */
for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
/* temperature[0] is on page 16 */
i2c_set8(i2cdev, PMBUS_PAGE, i + 16);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
g_assert_cmphex(i2c_value, ==, 0xBE00 + i);
i2c_value = max34451_i2c_get16(i2cdev, MAX34451_MFR_TEMPERATURE_PEAK);
g_assert_cmphex(i2c_value, ==, 0xBE00 + i);
}
}
/* Test setting voltage */
static void test_voltage(void *obj, void *data, QGuestAllocator *alloc)
{
uint16_t value, i2c_value;
QI2CDevice *i2cdev = (QI2CDevice *)obj;
char *path;
for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
path = g_strdup_printf("vout[%d]", i);
qmp_max34451_set(TEST_ID, path, 3000 + i);
value = qmp_max34451_get(TEST_ID, path);
g_assert_cmpuint(value, ==, 3000 + i);
g_free(path);
}
/* compare qmp read with i2c read separately */
for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
i2c_set8(i2cdev, PMBUS_PAGE, i);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_VOUT);
g_assert_cmpuint(i2c_value, ==, 3000 + i);
i2c_value = max34451_i2c_get16(i2cdev, MAX34451_MFR_VOUT_PEAK);
g_assert_cmpuint(i2c_value, ==, 3000 + i);
i2c_value = max34451_i2c_get16(i2cdev, MAX34451_MFR_VOUT_MIN);
g_assert_cmpuint(i2c_value, ==, 3000 + i);
}
}
/* Test setting some read/write registers */
static void test_rw_regs(void *obj, void *data, QGuestAllocator *alloc)
{
uint16_t i2c_value;
QI2CDevice *i2cdev = (QI2CDevice *)obj;
i2c_set8(i2cdev, PMBUS_PAGE, 11);
i2c_value = i2c_get8(i2cdev, PMBUS_PAGE);
g_assert_cmpuint(i2c_value, ==, 11);
i2c_set8(i2cdev, PMBUS_OPERATION, 1);
i2c_value = i2c_get8(i2cdev, PMBUS_OPERATION);
g_assert_cmpuint(i2c_value, ==, 1);
max34451_i2c_set16(i2cdev, PMBUS_VOUT_MARGIN_HIGH, 5000);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_MARGIN_HIGH);
g_assert_cmpuint(i2c_value, ==, 5000);
max34451_i2c_set16(i2cdev, PMBUS_VOUT_MARGIN_LOW, 4000);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_MARGIN_LOW);
g_assert_cmpuint(i2c_value, ==, 4000);
max34451_i2c_set16(i2cdev, PMBUS_VOUT_OV_FAULT_LIMIT, 5500);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_OV_FAULT_LIMIT);
g_assert_cmpuint(i2c_value, ==, 5500);
max34451_i2c_set16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT, 5600);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_OV_WARN_LIMIT);
g_assert_cmpuint(i2c_value, ==, 5600);
max34451_i2c_set16(i2cdev, PMBUS_VOUT_UV_FAULT_LIMIT, 5700);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_UV_FAULT_LIMIT);
g_assert_cmpuint(i2c_value, ==, 5700);
max34451_i2c_set16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT, 5800);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_VOUT_UV_WARN_LIMIT);
g_assert_cmpuint(i2c_value, ==, 5800);
max34451_i2c_set16(i2cdev, PMBUS_POWER_GOOD_ON, 5900);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_POWER_GOOD_ON);
g_assert_cmpuint(i2c_value, ==, 5900);
max34451_i2c_set16(i2cdev, PMBUS_POWER_GOOD_OFF, 6100);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_POWER_GOOD_OFF);
g_assert_cmpuint(i2c_value, ==, 6100);
}
/* Test that Read only registers can't be written */
static void test_ro_regs(void *obj, void *data, QGuestAllocator *alloc)
{
uint16_t i2c_value, i2c_init_value;
QI2CDevice *i2cdev = (QI2CDevice *)obj;
i2c_set8(i2cdev, PMBUS_PAGE, 1); /* move to page 1 */
i2c_init_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
i2c_set8(i2cdev, PMBUS_CAPABILITY, 0xF9);
i2c_value = i2c_get8(i2cdev, PMBUS_CAPABILITY);
g_assert_cmpuint(i2c_init_value, ==, i2c_value);
i2c_init_value = max34451_i2c_get16(i2cdev, PMBUS_READ_VOUT);
max34451_i2c_set16(i2cdev, PMBUS_READ_VOUT, 0xDEAD);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_VOUT);
g_assert_cmpuint(i2c_init_value, ==, i2c_value);
g_assert_cmphex(i2c_value, !=, 0xDEAD);
i2c_set8(i2cdev, PMBUS_PAGE, 16); /* move to page 16 */
i2c_init_value = max34451_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
max34451_i2c_set16(i2cdev, PMBUS_READ_TEMPERATURE_1, 0xABBA);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_READ_TEMPERATURE_1);
g_assert_cmpuint(i2c_init_value, ==, i2c_value);
g_assert_cmphex(i2c_value, !=, 0xABBA);
}
/* test over voltage faults */
static void test_ov_faults(void *obj, void *data, QGuestAllocator *alloc)
{
uint16_t i2c_value;
uint8_t i2c_byte;
QI2CDevice *i2cdev = (QI2CDevice *)obj;
char *path;
/* Test ov fault reporting */
for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
path = g_strdup_printf("vout[%d]", i);
i2c_set8(i2cdev, PMBUS_PAGE, i);
max34451_i2c_set16(i2cdev, PMBUS_VOUT_OV_FAULT_LIMIT, 5000);
qmp_max34451_set(TEST_ID, path, 5100);
g_free(path);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_STATUS_WORD);
i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_VOUT);
g_assert_true((i2c_value & PB_STATUS_VOUT) != 0);
g_assert_true((i2c_byte & PB_STATUS_VOUT_OV_FAULT) != 0);
}
}
/* test over temperature faults */
static void test_ot_faults(void *obj, void *data, QGuestAllocator *alloc)
{
uint16_t i2c_value;
uint8_t i2c_byte;
QI2CDevice *i2cdev = (QI2CDevice *)obj;
char *path;
for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
path = g_strdup_printf("temperature[%d]", i);
i2c_set8(i2cdev, PMBUS_PAGE, i + 16);
max34451_i2c_set16(i2cdev, PMBUS_OT_FAULT_LIMIT, 6000);
qmp_max34451_set(TEST_ID, path, 6100);
g_free(path);
i2c_value = max34451_i2c_get16(i2cdev, PMBUS_STATUS_WORD);
i2c_byte = i2c_get8(i2cdev, PMBUS_STATUS_TEMPERATURE);
g_assert_true((i2c_value & PB_STATUS_TEMPERATURE) != 0);
g_assert_true((i2c_byte & PB_STATUS_OT_FAULT) != 0);
}
}
static void max34451_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
.extra_device_opts = "id=" TEST_ID ",address=0x4e"
};
add_qi2c_address(&opts, &(QI2CAddress) { TEST_ADDR });
qos_node_create_driver("max34451", i2c_device_create);
qos_node_consumes("max34451", "i2c-bus", &opts);
qos_add_test("test_defaults", "max34451", test_defaults, NULL);
qos_add_test("test_temperature", "max34451", test_temperature, NULL);
qos_add_test("test_voltage", "max34451", test_voltage, NULL);
qos_add_test("test_rw_regs", "max34451", test_rw_regs, NULL);
qos_add_test("test_ro_regs", "max34451", test_ro_regs, NULL);
qos_add_test("test_ov_faults", "max34451", test_ov_faults, NULL);
qos_add_test("test_ot_faults", "max34451", test_ot_faults, NULL);
}
libqos_init(max34451_register_nodes);

View file

@ -202,12 +202,14 @@ qtests_s390x = \
qos_test_ss = ss.source_set()
qos_test_ss.add(
'ac97-test.c',
'adm1272-test.c',
'ds1338-test.c',
'e1000-test.c',
'e1000e-test.c',
'eepro100-test.c',
'es1370-test.c',
'ipoctal232-test.c',
'max34451-test.c',
'megasas-test.c',
'ne2000-test.c',
'tulip-test.c',

View file

@ -18,7 +18,7 @@
#include "qemu/bitops.h"
#include "libqos/i2c.h"
#include "libqos/libqtest.h"
#include "hw/misc/tmp105_regs.h"
#include "hw/sensor/tmp105_regs.h"
#define NR_SMBUS_DEVICES 16
#define SMBUS_ADDR(x) (0xf0080000 + 0x1000 * (x))

View file

@ -13,7 +13,7 @@
#include "libqos/qgraph.h"
#include "libqos/i2c.h"
#include "qapi/qmp/qdict.h"
#include "hw/misc/tmp105_regs.h"
#include "hw/sensor/tmp105_regs.h"
#define TMP105_TEST_ID "tmp105-test"
#define TMP105_TEST_ADDR 0x49