summaryrefslogtreecommitdiffstats
path: root/hw/sensor
diff options
context:
space:
mode:
authorDaniel Baumann <mail@daniel-baumann.ch>2025-06-06 10:05:23 +0000
committerDaniel Baumann <mail@daniel-baumann.ch>2025-06-06 10:05:23 +0000
commit755cc582a2473d06f3a2131d506d0311cc70e9f9 (patch)
tree3efb1ddb8d57bbb4539ac0d229b384871c57820f /hw/sensor
parentInitial commit. (diff)
downloadqemu-upstream.tar.xz
qemu-upstream.zip
Adding upstream version 1:7.2+dfsg.upstream/1%7.2+dfsgupstream
Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
Diffstat (limited to 'hw/sensor')
-rw-r--r--hw/sensor/Kconfig40
-rw-r--r--hw/sensor/adm1272.c543
-rw-r--r--hw/sensor/dps310.c225
-rw-r--r--hw/sensor/emc141x.c326
-rw-r--r--hw/sensor/isl_pmbus_vr.c319
-rw-r--r--hw/sensor/lsm303dlhc_mag.c558
-rw-r--r--hw/sensor/max31785.c573
-rw-r--r--hw/sensor/max34451.c775
-rw-r--r--hw/sensor/meson.build9
-rw-r--r--hw/sensor/tmp105.c328
-rw-r--r--hw/sensor/tmp421.c391
11 files changed, 4087 insertions, 0 deletions
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
new file mode 100644
index 00000000..e03bd09b
--- /dev/null
+++ b/hw/sensor/Kconfig
@@ -0,0 +1,40 @@
+config TMP105
+ bool
+ depends on I2C
+ default y if I2C_DEVICES
+
+config TMP421
+ bool
+ depends on I2C
+ default y if I2C_DEVICES
+
+config DPS310
+ bool
+ depends on I2C
+ default y if I2C_DEVICES
+
+config EMC141X
+ bool
+ depends on I2C
+ default y if I2C_DEVICES
+
+config ADM1272
+ bool
+ depends on I2C
+
+config MAX34451
+ bool
+ depends on I2C
+
+config LSM303DLHC_MAG
+ bool
+ depends on I2C
+ default y if I2C_DEVICES
+
+config ISL_PMBUS_VR
+ bool
+ depends on PMBUS
+
+config MAX31785
+ bool
+ depends on PMBUS
diff --git a/hw/sensor/adm1272.c b/hw/sensor/adm1272.c
new file mode 100644
index 00000000..7310c769
--- /dev/null
+++ b/hw/sensor/adm1272.c
@@ -0,0 +1,543 @@
+/*
+ * Analog Devices ADM1272 High Voltage Positive Hot Swap Controller and Digital
+ * Power Monitor with PMBus
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include <string.h>
+#include "hw/i2c/pmbus_device.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define TYPE_ADM1272 "adm1272"
+#define ADM1272(obj) OBJECT_CHECK(ADM1272State, (obj), TYPE_ADM1272)
+
+#define ADM1272_RESTART_TIME 0xCC
+#define ADM1272_MFR_PEAK_IOUT 0xD0
+#define ADM1272_MFR_PEAK_VIN 0xD1
+#define ADM1272_MFR_PEAK_VOUT 0xD2
+#define ADM1272_MFR_PMON_CONTROL 0xD3
+#define ADM1272_MFR_PMON_CONFIG 0xD4
+#define ADM1272_MFR_ALERT1_CONFIG 0xD5
+#define ADM1272_MFR_ALERT2_CONFIG 0xD6
+#define ADM1272_MFR_PEAK_TEMPERATURE 0xD7
+#define ADM1272_MFR_DEVICE_CONFIG 0xD8
+#define ADM1272_MFR_POWER_CYCLE 0xD9
+#define ADM1272_MFR_PEAK_PIN 0xDA
+#define ADM1272_MFR_READ_PIN_EXT 0xDB
+#define ADM1272_MFR_READ_EIN_EXT 0xDC
+
+#define ADM1272_HYSTERESIS_LOW 0xF2
+#define ADM1272_HYSTERESIS_HIGH 0xF3
+#define ADM1272_STATUS_HYSTERESIS 0xF4
+#define ADM1272_STATUS_GPIO 0xF5
+#define ADM1272_STRT_UP_IOUT_LIM 0xF6
+
+/* Defaults */
+#define ADM1272_OPERATION_DEFAULT 0x80
+#define ADM1272_CAPABILITY_DEFAULT 0xB0
+#define ADM1272_CAPABILITY_NO_PEC 0x30
+#define ADM1272_DIRECT_MODE 0x40
+#define ADM1272_HIGH_LIMIT_DEFAULT 0x0FFF
+#define ADM1272_PIN_OP_DEFAULT 0x7FFF
+#define ADM1272_PMBUS_REVISION_DEFAULT 0x22
+#define ADM1272_MFR_ID_DEFAULT "ADI"
+#define ADM1272_MODEL_DEFAULT "ADM1272-A1"
+#define ADM1272_MFR_DEFAULT_REVISION "25"
+#define ADM1272_DEFAULT_DATE "160301"
+#define ADM1272_RESTART_TIME_DEFAULT 0x64
+#define ADM1272_PMON_CONTROL_DEFAULT 0x1
+#define ADM1272_PMON_CONFIG_DEFAULT 0x3F35
+#define ADM1272_DEVICE_CONFIG_DEFAULT 0x8
+#define ADM1272_HYSTERESIS_HIGH_DEFAULT 0xFFFF
+#define ADM1272_STRT_UP_IOUT_LIM_DEFAULT 0x000F
+#define ADM1272_VOLT_DEFAULT 12000
+#define ADM1272_IOUT_DEFAULT 25000
+#define ADM1272_PWR_DEFAULT 300 /* 12V 25A */
+#define ADM1272_SHUNT 300 /* micro-ohms */
+#define ADM1272_VOLTAGE_COEFF_DEFAULT 1
+#define ADM1272_CURRENT_COEFF_DEFAULT 3
+#define ADM1272_PWR_COEFF_DEFAULT 7
+#define ADM1272_IOUT_OFFSET 0x5000
+#define ADM1272_IOUT_OFFSET 0x5000
+
+
+typedef struct ADM1272State {
+ PMBusDevice parent;
+
+ uint64_t ein_ext;
+ uint32_t pin_ext;
+ uint8_t restart_time;
+
+ uint16_t peak_vin;
+ uint16_t peak_vout;
+ uint16_t peak_iout;
+ uint16_t peak_temperature;
+ uint16_t peak_pin;
+
+ uint8_t pmon_control;
+ uint16_t pmon_config;
+ uint16_t alert1_config;
+ uint16_t alert2_config;
+ uint16_t device_config;
+
+ uint16_t hysteresis_low;
+ uint16_t hysteresis_high;
+ uint8_t status_hysteresis;
+ uint8_t status_gpio;
+
+ uint16_t strt_up_iout_lim;
+
+} ADM1272State;
+
+static const PMBusCoefficients adm1272_coefficients[] = {
+ [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */
+ [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */
+ [2] = { 1326, 20480, -1 }, /* current, vsense range 15mV */
+ [3] = { 663, 20480, -1 }, /* current, vsense range 30mV */
+ [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */
+ [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */
+ [6] = { 17561, 0, -3 }, /* power, vrange 60V, irange 30mV */
+ [7] = { 10535, 0, -3 }, /* power, vrange 100V, irange 30mV */
+ [8] = { 42, 31871, -1 }, /* temperature */
+};
+
+static void adm1272_check_limits(ADM1272State *s)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(s);
+
+ pmbus_check_limits(pmdev);
+
+ if (pmdev->pages[0].read_vout > s->peak_vout) {
+ s->peak_vout = pmdev->pages[0].read_vout;
+ }
+
+ if (pmdev->pages[0].read_vin > s->peak_vin) {
+ s->peak_vin = pmdev->pages[0].read_vin;
+ }
+
+ if (pmdev->pages[0].read_iout > s->peak_iout) {
+ s->peak_iout = pmdev->pages[0].read_iout;
+ }
+
+ if (pmdev->pages[0].read_temperature_1 > s->peak_temperature) {
+ s->peak_temperature = pmdev->pages[0].read_temperature_1;
+ }
+
+ if (pmdev->pages[0].read_pin > s->peak_pin) {
+ s->peak_pin = pmdev->pages[0].read_pin;
+ }
+}
+
+static uint16_t adm1272_millivolts_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_millivolts(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_VOLTAGE_COEFF_DEFAULT];
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t adm1272_milliamps_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
+ /* Y = (m * r_sense * x - b) * 10^R */
+ c.m = c.m * ADM1272_SHUNT / 1000; /* micro-ohms */
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_milliamps(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_CURRENT_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ c.b = c.b * 1000;
+ c.R = c.R - 3;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static uint16_t adm1272_watts_to_direct(uint32_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ return pmbus_data2direct_mode(c, value);
+}
+
+static uint32_t adm1272_direct_to_watts(uint16_t value)
+{
+ PMBusCoefficients c = adm1272_coefficients[ADM1272_PWR_COEFF_DEFAULT];
+ c.m = c.m * ADM1272_SHUNT / 1000;
+ return pmbus_direct_mode2data(c, value);
+}
+
+static void adm1272_exit_reset(Object *obj)
+{
+ ADM1272State *s = ADM1272(obj);
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+
+ pmdev->page = 0;
+ pmdev->pages[0].operation = ADM1272_OPERATION_DEFAULT;
+
+
+ pmdev->capability = ADM1272_CAPABILITY_NO_PEC;
+ pmdev->pages[0].revision = ADM1272_PMBUS_REVISION_DEFAULT;
+ pmdev->pages[0].vout_mode = ADM1272_DIRECT_MODE;
+ pmdev->pages[0].vout_ov_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].vout_uv_warn_limit = 0;
+ pmdev->pages[0].iout_oc_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].ot_fault_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].ot_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].vin_ov_warn_limit = ADM1272_HIGH_LIMIT_DEFAULT;
+ pmdev->pages[0].vin_uv_warn_limit = 0;
+ pmdev->pages[0].pin_op_warn_limit = ADM1272_PIN_OP_DEFAULT;
+
+ pmdev->pages[0].status_word = 0;
+ pmdev->pages[0].status_vout = 0;
+ pmdev->pages[0].status_iout = 0;
+ pmdev->pages[0].status_input = 0;
+ pmdev->pages[0].status_temperature = 0;
+ pmdev->pages[0].status_mfr_specific = 0;
+
+ pmdev->pages[0].read_vin
+ = adm1272_millivolts_to_direct(ADM1272_VOLT_DEFAULT);
+ pmdev->pages[0].read_vout
+ = adm1272_millivolts_to_direct(ADM1272_VOLT_DEFAULT);
+ pmdev->pages[0].read_iout
+ = adm1272_milliamps_to_direct(ADM1272_IOUT_DEFAULT);
+ pmdev->pages[0].read_temperature_1 = 0;
+ pmdev->pages[0].read_pin = adm1272_watts_to_direct(ADM1272_PWR_DEFAULT);
+ pmdev->pages[0].revision = ADM1272_PMBUS_REVISION_DEFAULT;
+ pmdev->pages[0].mfr_id = ADM1272_MFR_ID_DEFAULT;
+ pmdev->pages[0].mfr_model = ADM1272_MODEL_DEFAULT;
+ pmdev->pages[0].mfr_revision = ADM1272_MFR_DEFAULT_REVISION;
+ pmdev->pages[0].mfr_date = ADM1272_DEFAULT_DATE;
+
+ s->pin_ext = 0;
+ s->ein_ext = 0;
+ s->restart_time = ADM1272_RESTART_TIME_DEFAULT;
+
+ s->peak_vin = 0;
+ s->peak_vout = 0;
+ s->peak_iout = 0;
+ s->peak_temperature = 0;
+ s->peak_pin = 0;
+
+ s->pmon_control = ADM1272_PMON_CONTROL_DEFAULT;
+ s->pmon_config = ADM1272_PMON_CONFIG_DEFAULT;
+ s->alert1_config = 0;
+ s->alert2_config = 0;
+ s->device_config = ADM1272_DEVICE_CONFIG_DEFAULT;
+
+ s->hysteresis_low = 0;
+ s->hysteresis_high = ADM1272_HYSTERESIS_HIGH_DEFAULT;
+ s->status_hysteresis = 0;
+ s->status_gpio = 0;
+
+ s->strt_up_iout_lim = ADM1272_STRT_UP_IOUT_LIM_DEFAULT;
+}
+
+static uint8_t adm1272_read_byte(PMBusDevice *pmdev)
+{
+ ADM1272State *s = ADM1272(pmdev);
+
+ switch (pmdev->code) {
+ case ADM1272_RESTART_TIME:
+ pmbus_send8(pmdev, s->restart_time);
+ break;
+
+ case ADM1272_MFR_PEAK_IOUT:
+ pmbus_send16(pmdev, s->peak_iout);
+ break;
+
+ case ADM1272_MFR_PEAK_VIN:
+ pmbus_send16(pmdev, s->peak_vin);
+ break;
+
+ case ADM1272_MFR_PEAK_VOUT:
+ pmbus_send16(pmdev, s->peak_vout);
+ break;
+
+ case ADM1272_MFR_PMON_CONTROL:
+ pmbus_send8(pmdev, s->pmon_control);
+ break;
+
+ case ADM1272_MFR_PMON_CONFIG:
+ pmbus_send16(pmdev, s->pmon_config);
+ break;
+
+ case ADM1272_MFR_ALERT1_CONFIG:
+ pmbus_send16(pmdev, s->alert1_config);
+ break;
+
+ case ADM1272_MFR_ALERT2_CONFIG:
+ pmbus_send16(pmdev, s->alert2_config);
+ break;
+
+ case ADM1272_MFR_PEAK_TEMPERATURE:
+ pmbus_send16(pmdev, s->peak_temperature);
+ break;
+
+ case ADM1272_MFR_DEVICE_CONFIG:
+ pmbus_send16(pmdev, s->device_config);
+ break;
+
+ case ADM1272_MFR_PEAK_PIN:
+ pmbus_send16(pmdev, s->peak_pin);
+ break;
+
+ case ADM1272_MFR_READ_PIN_EXT:
+ pmbus_send32(pmdev, s->pin_ext);
+ break;
+
+ case ADM1272_MFR_READ_EIN_EXT:
+ pmbus_send64(pmdev, s->ein_ext);
+ break;
+
+ case ADM1272_HYSTERESIS_LOW:
+ pmbus_send16(pmdev, s->hysteresis_low);
+ break;
+
+ case ADM1272_HYSTERESIS_HIGH:
+ pmbus_send16(pmdev, s->hysteresis_high);
+ break;
+
+ case ADM1272_STATUS_HYSTERESIS:
+ pmbus_send16(pmdev, s->status_hysteresis);
+ break;
+
+ case ADM1272_STATUS_GPIO:
+ pmbus_send16(pmdev, s->status_gpio);
+ break;
+
+ case ADM1272_STRT_UP_IOUT_LIM:
+ pmbus_send16(pmdev, s->strt_up_iout_lim);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ return 0xFF;
+ break;
+ }
+
+ return 0;
+}
+
+static int adm1272_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+ uint8_t len)
+{
+ ADM1272State *s = ADM1272(pmdev);
+
+ if (len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+ return -1;
+ }
+
+ pmdev->code = buf[0]; /* PMBus command code */
+
+ if (len == 1) {
+ return 0;
+ }
+
+ /* Exclude command code from buffer */
+ buf++;
+ len--;
+
+ switch (pmdev->code) {
+
+ case ADM1272_RESTART_TIME:
+ s->restart_time = pmbus_receive8(pmdev);
+ break;
+
+ case ADM1272_MFR_PMON_CONTROL:
+ s->pmon_control = pmbus_receive8(pmdev);
+ break;
+
+ case ADM1272_MFR_PMON_CONFIG:
+ s->pmon_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_ALERT1_CONFIG:
+ s->alert1_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_ALERT2_CONFIG:
+ s->alert2_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_DEVICE_CONFIG:
+ s->device_config = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_MFR_POWER_CYCLE:
+ adm1272_exit_reset((Object *)s);
+ break;
+
+ case ADM1272_HYSTERESIS_LOW:
+ s->hysteresis_low = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_HYSTERESIS_HIGH:
+ s->hysteresis_high = pmbus_receive16(pmdev);
+ break;
+
+ case ADM1272_STRT_UP_IOUT_LIM:
+ s->strt_up_iout_lim = pmbus_receive16(pmdev);
+ adm1272_check_limits(s);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+ return 0;
+}
+
+static void adm1272_get(Object *obj, Visitor *v, const char *name, void *opaque,
+ Error **errp)
+{
+ uint16_t value;
+
+ if (strcmp(name, "vin") == 0 || strcmp(name, "vout") == 0) {
+ value = adm1272_direct_to_millivolts(*(uint16_t *)opaque);
+ } else if (strcmp(name, "iout") == 0) {
+ value = adm1272_direct_to_milliamps(*(uint16_t *)opaque);
+ } else if (strcmp(name, "pin") == 0) {
+ value = adm1272_direct_to_watts(*(uint16_t *)opaque);
+ } else {
+ value = *(uint16_t *)opaque;
+ }
+
+ visit_type_uint16(v, name, &value, errp);
+}
+
+static void adm1272_set(Object *obj, Visitor *v, const char *name, void *opaque,
+ Error **errp)
+{
+ ADM1272State *s = ADM1272(obj);
+ uint16_t *internal = opaque;
+ uint16_t value;
+
+ if (!visit_type_uint16(v, name, &value, errp)) {
+ return;
+ }
+
+ if (strcmp(name, "vin") == 0 || strcmp(name, "vout") == 0) {
+ *internal = adm1272_millivolts_to_direct(value);
+ } else if (strcmp(name, "iout") == 0) {
+ *internal = adm1272_milliamps_to_direct(value);
+ } else if (strcmp(name, "pin") == 0) {
+ *internal = adm1272_watts_to_direct(value);
+ } else {
+ *internal = value;
+ }
+
+ adm1272_check_limits(s);
+}
+
+static const VMStateDescription vmstate_adm1272 = {
+ .name = "ADM1272",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]){
+ VMSTATE_PMBUS_DEVICE(parent, ADM1272State),
+ VMSTATE_UINT64(ein_ext, ADM1272State),
+ VMSTATE_UINT32(pin_ext, ADM1272State),
+ VMSTATE_UINT8(restart_time, ADM1272State),
+
+ VMSTATE_UINT16(peak_vin, ADM1272State),
+ VMSTATE_UINT16(peak_vout, ADM1272State),
+ VMSTATE_UINT16(peak_iout, ADM1272State),
+ VMSTATE_UINT16(peak_temperature, ADM1272State),
+ VMSTATE_UINT16(peak_pin, ADM1272State),
+
+ VMSTATE_UINT8(pmon_control, ADM1272State),
+ VMSTATE_UINT16(pmon_config, ADM1272State),
+ VMSTATE_UINT16(alert1_config, ADM1272State),
+ VMSTATE_UINT16(alert2_config, ADM1272State),
+ VMSTATE_UINT16(device_config, ADM1272State),
+
+ VMSTATE_UINT16(hysteresis_low, ADM1272State),
+ VMSTATE_UINT16(hysteresis_high, ADM1272State),
+ VMSTATE_UINT8(status_hysteresis, ADM1272State),
+ VMSTATE_UINT8(status_gpio, ADM1272State),
+
+ VMSTATE_UINT16(strt_up_iout_lim, ADM1272State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void adm1272_init(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint64_t flags = PB_HAS_VOUT_MODE | PB_HAS_VOUT | PB_HAS_VIN | PB_HAS_IOUT |
+ PB_HAS_PIN | PB_HAS_TEMPERATURE | PB_HAS_MFR_INFO;
+
+ pmbus_page_config(pmdev, 0, flags);
+
+ object_property_add(obj, "vin", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_vin);
+
+ object_property_add(obj, "vout", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_vout);
+
+ object_property_add(obj, "iout", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_iout);
+
+ object_property_add(obj, "pin", "uint16",
+ adm1272_get,
+ adm1272_set, NULL, &pmdev->pages[0].read_pin);
+
+}
+
+static void adm1272_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
+
+ dc->desc = "Analog Devices ADM1272 Hot Swap controller";
+ dc->vmsd = &vmstate_adm1272;
+ k->write_data = adm1272_write_data;
+ k->receive_byte = adm1272_read_byte;
+ k->device_num_pages = 1;
+
+ rc->phases.exit = adm1272_exit_reset;
+}
+
+static const TypeInfo adm1272_info = {
+ .name = TYPE_ADM1272,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(ADM1272State),
+ .instance_init = adm1272_init,
+ .class_init = adm1272_class_init,
+};
+
+static void adm1272_register_types(void)
+{
+ type_register_static(&adm1272_info);
+}
+
+type_init(adm1272_register_types)
diff --git a/hw/sensor/dps310.c b/hw/sensor/dps310.c
new file mode 100644
index 00000000..d60a18ac
--- /dev/null
+++ b/hw/sensor/dps310.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2017-2021 Joel Stanley <joel@jms.id.au>, IBM Corporation
+ *
+ * Infineon DPS310 temperature and humidity sensor
+ *
+ * https://www.infineon.com/cms/en/product/sensor/pressure-sensors/pressure-sensors-for-iot/dps310/
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/hw.h"
+#include "hw/i2c/i2c.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "migration/vmstate.h"
+
+#define NUM_REGISTERS 0x33
+
+typedef struct DPS310State {
+ /*< private >*/
+ I2CSlave i2c;
+
+ /*< public >*/
+ uint8_t regs[NUM_REGISTERS];
+
+ uint8_t len;
+ uint8_t pointer;
+
+} DPS310State;
+
+#define TYPE_DPS310 "dps310"
+#define DPS310(obj) OBJECT_CHECK(DPS310State, (obj), TYPE_DPS310)
+
+#define DPS310_PRS_B2 0x00
+#define DPS310_PRS_B1 0x01
+#define DPS310_PRS_B0 0x02
+#define DPS310_TMP_B2 0x03
+#define DPS310_TMP_B1 0x04
+#define DPS310_TMP_B0 0x05
+#define DPS310_PRS_CFG 0x06
+#define DPS310_TMP_CFG 0x07
+#define DPS310_TMP_RATE_BITS (0x70)
+#define DPS310_MEAS_CFG 0x08
+#define DPS310_MEAS_CTRL_BITS (0x07)
+#define DPS310_PRESSURE_EN BIT(0)
+#define DPS310_TEMP_EN BIT(1)
+#define DPS310_BACKGROUND BIT(2)
+#define DPS310_PRS_RDY BIT(4)
+#define DPS310_TMP_RDY BIT(5)
+#define DPS310_SENSOR_RDY BIT(6)
+#define DPS310_COEF_RDY BIT(7)
+#define DPS310_CFG_REG 0x09
+#define DPS310_RESET 0x0c
+#define DPS310_RESET_MAGIC (BIT(0) | BIT(3))
+#define DPS310_COEF_BASE 0x10
+#define DPS310_COEF_LAST 0x21
+#define DPS310_COEF_SRC 0x28
+
+static void dps310_reset(DeviceState *dev)
+{
+ DPS310State *s = DPS310(dev);
+
+ static const uint8_t regs_reset_state[sizeof(s->regs)] = {
+ 0xfe, 0x2f, 0xee, 0x02, 0x69, 0xa6, 0x00, 0x80, 0xc7, 0x00, 0x00, 0x00,
+ 0x00, 0x10, 0x00, 0x00, 0x0e, 0x1e, 0xdd, 0x13, 0xca, 0x5f, 0x21, 0x52,
+ 0xf9, 0xc6, 0x04, 0xd1, 0xdb, 0x47, 0x00, 0x5b, 0xfb, 0x3a, 0x00, 0x00,
+ 0x20, 0x49, 0x4e, 0xa5, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x60, 0x15, 0x02
+ };
+
+ memcpy(s->regs, regs_reset_state, sizeof(s->regs));
+ s->pointer = 0;
+
+ /* TODO: assert these after some timeout ? */
+ s->regs[DPS310_MEAS_CFG] = DPS310_COEF_RDY | DPS310_SENSOR_RDY
+ | DPS310_TMP_RDY | DPS310_PRS_RDY;
+}
+
+static uint8_t dps310_read(DPS310State *s, uint8_t reg)
+{
+ if (reg >= sizeof(s->regs)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: register 0x%02x out of bounds\n",
+ __func__, s->pointer);
+ return 0xFF;
+ }
+
+ switch (reg) {
+ case DPS310_PRS_B2:
+ case DPS310_PRS_B1:
+ case DPS310_PRS_B0:
+ case DPS310_TMP_B2:
+ case DPS310_TMP_B1:
+ case DPS310_TMP_B0:
+ case DPS310_PRS_CFG:
+ case DPS310_TMP_CFG:
+ case DPS310_MEAS_CFG:
+ case DPS310_CFG_REG:
+ case DPS310_COEF_BASE...DPS310_COEF_LAST:
+ case DPS310_COEF_SRC:
+ case 0x32: /* Undocumented register to indicate workaround not required */
+ return s->regs[reg];
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: register 0x%02x unimplemented\n",
+ __func__, reg);
+ return 0xFF;
+ }
+}
+
+static void dps310_write(DPS310State *s, uint8_t reg, uint8_t data)
+{
+ if (reg >= sizeof(s->regs)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: register %d out of bounds\n",
+ __func__, s->pointer);
+ return;
+ }
+
+ switch (reg) {
+ case DPS310_RESET:
+ if (data == DPS310_RESET_MAGIC) {
+ device_cold_reset(DEVICE(s));
+ }
+ break;
+ case DPS310_PRS_CFG:
+ case DPS310_TMP_CFG:
+ case DPS310_MEAS_CFG:
+ case DPS310_CFG_REG:
+ s->regs[reg] = data;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: register 0x%02x unimplemented\n",
+ __func__, reg);
+ return;
+ }
+}
+
+static uint8_t dps310_rx(I2CSlave *i2c)
+{
+ DPS310State *s = DPS310(i2c);
+
+ if (s->len == 1) {
+ return dps310_read(s, s->pointer++);
+ } else {
+ return 0xFF;
+ }
+}
+
+static int dps310_tx(I2CSlave *i2c, uint8_t data)
+{
+ DPS310State *s = DPS310(i2c);
+
+ if (s->len == 0) {
+ /*
+ * first byte is the register pointer for a read or write
+ * operation
+ */
+ s->pointer = data;
+ s->len++;
+ } else if (s->len == 1) {
+ dps310_write(s, s->pointer++, data);
+ }
+
+ return 0;
+}
+
+static int dps310_event(I2CSlave *i2c, enum i2c_event event)
+{
+ DPS310State *s = DPS310(i2c);
+
+ switch (event) {
+ case I2C_START_SEND:
+ s->pointer = 0xFF;
+ s->len = 0;
+ break;
+ case I2C_START_RECV:
+ if (s->len != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid recv sequence\n",
+ __func__);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_dps310 = {
+ .name = "DPS310",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(len, DPS310State),
+ VMSTATE_UINT8_ARRAY(regs, DPS310State, NUM_REGISTERS),
+ VMSTATE_UINT8(pointer, DPS310State),
+ VMSTATE_I2C_SLAVE(i2c, DPS310State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void dps310_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->event = dps310_event;
+ k->recv = dps310_rx;
+ k->send = dps310_tx;
+ dc->reset = dps310_reset;
+ dc->vmsd = &vmstate_dps310;
+}
+
+static const TypeInfo dps310_info = {
+ .name = TYPE_DPS310,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(DPS310State),
+ .class_init = dps310_class_init,
+};
+
+static void dps310_register_types(void)
+{
+ type_register_static(&dps310_info);
+}
+
+type_init(dps310_register_types)
diff --git a/hw/sensor/emc141x.c b/hw/sensor/emc141x.c
new file mode 100644
index 00000000..7ce8f4e9
--- /dev/null
+++ b/hw/sensor/emc141x.c
@@ -0,0 +1,326 @@
+/*
+ * SMSC EMC141X temperature sensor.
+ *
+ * Copyright (c) 2020 Bytedance Corporation
+ * Written by John Wang <wangzhiqiang.bj@bytedance.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/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+#include "hw/sensor/emc141x_regs.h"
+
+#define SENSORS_COUNT_MAX 4
+
+struct EMC141XState {
+ I2CSlave parent_obj;
+ struct {
+ uint8_t raw_temp_min;
+ uint8_t raw_temp_current;
+ uint8_t raw_temp_max;
+ } sensor[SENSORS_COUNT_MAX];
+ uint8_t len;
+ uint8_t data;
+ uint8_t pointer;
+};
+
+struct EMC141XClass {
+ I2CSlaveClass parent_class;
+ uint8_t model;
+ unsigned sensors_count;
+};
+
+#define TYPE_EMC141X "emc141x"
+OBJECT_DECLARE_TYPE(EMC141XState, EMC141XClass, EMC141X)
+
+static void emc141x_get_temperature(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ EMC141XState *s = EMC141X(obj);
+ EMC141XClass *sc = EMC141X_GET_CLASS(s);
+ int64_t value;
+ unsigned tempid;
+
+ if (sscanf(name, "temperature%u", &tempid) != 1) {
+ error_setg(errp, "error reading %s: %s", name, g_strerror(errno));
+ return;
+ }
+
+ if (tempid >= sc->sensors_count) {
+ error_setg(errp, "error reading %s", name);
+ return;
+ }
+
+ value = s->sensor[tempid].raw_temp_current * 1000;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+static void emc141x_set_temperature(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ EMC141XState *s = EMC141X(obj);
+ EMC141XClass *sc = EMC141X_GET_CLASS(s);
+ int64_t temp;
+ unsigned tempid;
+
+ if (!visit_type_int(v, name, &temp, errp)) {
+ return;
+ }
+
+ if (sscanf(name, "temperature%u", &tempid) != 1) {
+ error_setg(errp, "error reading %s: %s", name, g_strerror(errno));
+ return;
+ }
+
+ if (tempid >= sc->sensors_count) {
+ error_setg(errp, "error reading %s", name);
+ return;
+ }
+
+ s->sensor[tempid].raw_temp_current = temp / 1000;
+}
+
+static void emc141x_read(EMC141XState *s)
+{
+ EMC141XClass *sc = EMC141X_GET_CLASS(s);
+ switch (s->pointer) {
+ case EMC141X_DEVICE_ID:
+ s->data = sc->model;
+ break;
+ case EMC141X_MANUFACTURER_ID:
+ s->data = MANUFACTURER_ID;
+ break;
+ case EMC141X_REVISION:
+ s->data = REVISION;
+ break;
+ case EMC141X_TEMP_HIGH0:
+ s->data = s->sensor[0].raw_temp_current;
+ break;
+ case EMC141X_TEMP_HIGH1:
+ s->data = s->sensor[1].raw_temp_current;
+ break;
+ case EMC141X_TEMP_HIGH2:
+ s->data = s->sensor[2].raw_temp_current;
+ break;
+ case EMC141X_TEMP_HIGH3:
+ s->data = s->sensor[3].raw_temp_current;
+ break;
+ case EMC141X_TEMP_MAX_HIGH0:
+ s->data = s->sensor[0].raw_temp_max;
+ break;
+ case EMC141X_TEMP_MAX_HIGH1:
+ s->data = s->sensor[1].raw_temp_max;
+ break;
+ case EMC141X_TEMP_MAX_HIGH2:
+ s->data = s->sensor[2].raw_temp_max;
+ break;
+ case EMC141X_TEMP_MAX_HIGH3:
+ s->data = s->sensor[3].raw_temp_max;
+ break;
+ case EMC141X_TEMP_MIN_HIGH0:
+ s->data = s->sensor[0].raw_temp_min;
+ break;
+ case EMC141X_TEMP_MIN_HIGH1:
+ s->data = s->sensor[1].raw_temp_min;
+ break;
+ case EMC141X_TEMP_MIN_HIGH2:
+ s->data = s->sensor[2].raw_temp_min;
+ break;
+ case EMC141X_TEMP_MIN_HIGH3:
+ s->data = s->sensor[3].raw_temp_min;
+ break;
+ default:
+ s->data = 0;
+ }
+}
+
+static void emc141x_write(EMC141XState *s)
+{
+ switch (s->pointer) {
+ case EMC141X_TEMP_MAX_HIGH0:
+ s->sensor[0].raw_temp_max = s->data;
+ break;
+ case EMC141X_TEMP_MAX_HIGH1:
+ s->sensor[1].raw_temp_max = s->data;
+ break;
+ case EMC141X_TEMP_MAX_HIGH2:
+ s->sensor[2].raw_temp_max = s->data;
+ break;
+ case EMC141X_TEMP_MAX_HIGH3:
+ s->sensor[3].raw_temp_max = s->data;
+ break;
+ case EMC141X_TEMP_MIN_HIGH0:
+ s->sensor[0].raw_temp_min = s->data;
+ break;
+ case EMC141X_TEMP_MIN_HIGH1:
+ s->sensor[1].raw_temp_min = s->data;
+ break;
+ case EMC141X_TEMP_MIN_HIGH2:
+ s->sensor[2].raw_temp_min = s->data;
+ break;
+ case EMC141X_TEMP_MIN_HIGH3:
+ s->sensor[3].raw_temp_min = s->data;
+ break;
+ default:
+ s->data = 0;
+ }
+}
+
+static uint8_t emc141x_rx(I2CSlave *i2c)
+{
+ EMC141XState *s = EMC141X(i2c);
+
+ if (s->len == 0) {
+ s->len++;
+ return s->data;
+ } else {
+ return 0xff;
+ }
+}
+
+static int emc141x_tx(I2CSlave *i2c, uint8_t data)
+{
+ EMC141XState *s = EMC141X(i2c);
+
+ if (s->len == 0) {
+ /* first byte is the reg pointer */
+ s->pointer = data;
+ s->len++;
+ } else if (s->len == 1) {
+ s->data = data;
+ emc141x_write(s);
+ }
+
+ return 0;
+}
+
+static int emc141x_event(I2CSlave *i2c, enum i2c_event event)
+{
+ EMC141XState *s = EMC141X(i2c);
+
+ if (event == I2C_START_RECV) {
+ emc141x_read(s);
+ }
+
+ s->len = 0;
+ return 0;
+}
+
+static const VMStateDescription vmstate_emc141x = {
+ .name = "EMC141X",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(len, EMC141XState),
+ VMSTATE_UINT8(data, EMC141XState),
+ VMSTATE_UINT8(pointer, EMC141XState),
+ VMSTATE_I2C_SLAVE(parent_obj, EMC141XState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void emc141x_reset(DeviceState *dev)
+{
+ EMC141XState *s = EMC141X(dev);
+ int i;
+
+ for (i = 0; i < SENSORS_COUNT_MAX; i++) {
+ s->sensor[i].raw_temp_max = 0x55;
+ }
+ s->pointer = 0;
+ s->len = 0;
+}
+
+static void emc141x_initfn(Object *obj)
+{
+ object_property_add(obj, "temperature0", "int",
+ emc141x_get_temperature,
+ emc141x_set_temperature, NULL, NULL);
+ object_property_add(obj, "temperature1", "int",
+ emc141x_get_temperature,
+ emc141x_set_temperature, NULL, NULL);
+ object_property_add(obj, "temperature2", "int",
+ emc141x_get_temperature,
+ emc141x_set_temperature, NULL, NULL);
+ object_property_add(obj, "temperature3", "int",
+ emc141x_get_temperature,
+ emc141x_set_temperature, NULL, NULL);
+}
+
+static void emc141x_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ dc->reset = emc141x_reset;
+ k->event = emc141x_event;
+ k->recv = emc141x_rx;
+ k->send = emc141x_tx;
+ dc->vmsd = &vmstate_emc141x;
+}
+
+static void emc1413_class_init(ObjectClass *klass, void *data)
+{
+ EMC141XClass *ec = EMC141X_CLASS(klass);
+
+ emc141x_class_init(klass, data);
+ ec->model = EMC1413_DEVICE_ID;
+ ec->sensors_count = 3;
+}
+
+static void emc1414_class_init(ObjectClass *klass, void *data)
+{
+ EMC141XClass *ec = EMC141X_CLASS(klass);
+
+ emc141x_class_init(klass, data);
+ ec->model = EMC1414_DEVICE_ID;
+ ec->sensors_count = 4;
+}
+
+static const TypeInfo emc141x_info = {
+ .name = TYPE_EMC141X,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(EMC141XState),
+ .class_size = sizeof(EMC141XClass),
+ .instance_init = emc141x_initfn,
+ .abstract = true,
+};
+
+static const TypeInfo emc1413_info = {
+ .name = "emc1413",
+ .parent = TYPE_EMC141X,
+ .class_init = emc1413_class_init,
+};
+
+static const TypeInfo emc1414_info = {
+ .name = "emc1414",
+ .parent = TYPE_EMC141X,
+ .class_init = emc1414_class_init,
+};
+
+static void emc141x_register_types(void)
+{
+ type_register_static(&emc141x_info);
+ type_register_static(&emc1413_info);
+ type_register_static(&emc1414_info);
+}
+
+type_init(emc141x_register_types)
diff --git a/hw/sensor/isl_pmbus_vr.c b/hw/sensor/isl_pmbus_vr.c
new file mode 100644
index 00000000..eb344dd5
--- /dev/null
+++ b/hw/sensor/isl_pmbus_vr.c
@@ -0,0 +1,319 @@
+/*
+ * PMBus device for Renesas Digital Multiphase Voltage Regulators
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sensor/isl_pmbus_vr.h"
+#include "hw/qdev-properties.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+static uint8_t isl_pmbus_vr_read_byte(PMBusDevice *pmdev)
+{
+ ISLState *s = ISL69260(pmdev);
+
+ switch (pmdev->code) {
+ case PMBUS_IC_DEVICE_ID:
+ if (!s->ic_device_id_len) {
+ break;
+ }
+ pmbus_send(pmdev, s->ic_device_id, s->ic_device_id_len);
+ pmbus_idle(pmdev);
+ return 0;
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ return PMBUS_ERR_BYTE;
+}
+
+static int isl_pmbus_vr_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+ uint8_t len)
+{
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ return PMBUS_ERR_BYTE;
+}
+
+/* TODO: Implement coefficients support in pmbus_device.c for qmp */
+static void isl_pmbus_vr_get(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ visit_type_uint16(v, name, (uint16_t *)opaque, errp);
+}
+
+static void isl_pmbus_vr_set(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint16_t *internal = opaque;
+ uint16_t value;
+ if (!visit_type_uint16(v, name, &value, errp)) {
+ return;
+ }
+
+ *internal = value;
+ pmbus_check_limits(pmdev);
+}
+
+static void isl_pmbus_vr_exit_reset(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+
+ pmdev->page = 0;
+ pmdev->capability = ISL_CAPABILITY_DEFAULT;
+ for (int i = 0; i < pmdev->num_pages; i++) {
+ pmdev->pages[i].operation = ISL_OPERATION_DEFAULT;
+ pmdev->pages[i].on_off_config = ISL_ON_OFF_CONFIG_DEFAULT;
+ pmdev->pages[i].vout_mode = ISL_VOUT_MODE_DEFAULT;
+ pmdev->pages[i].vout_command = ISL_VOUT_COMMAND_DEFAULT;
+ pmdev->pages[i].vout_max = ISL_VOUT_MAX_DEFAULT;
+ pmdev->pages[i].vout_margin_high = ISL_VOUT_MARGIN_HIGH_DEFAULT;
+ pmdev->pages[i].vout_margin_low = ISL_VOUT_MARGIN_LOW_DEFAULT;
+ pmdev->pages[i].vout_transition_rate = ISL_VOUT_TRANSITION_RATE_DEFAULT;
+ pmdev->pages[i].vout_ov_fault_limit = ISL_VOUT_OV_FAULT_LIMIT_DEFAULT;
+ pmdev->pages[i].ot_fault_limit = ISL_OT_FAULT_LIMIT_DEFAULT;
+ pmdev->pages[i].ot_warn_limit = ISL_OT_WARN_LIMIT_DEFAULT;
+ pmdev->pages[i].vin_ov_warn_limit = ISL_VIN_OV_WARN_LIMIT_DEFAULT;
+ pmdev->pages[i].vin_uv_warn_limit = ISL_VIN_UV_WARN_LIMIT_DEFAULT;
+ pmdev->pages[i].iin_oc_fault_limit = ISL_IIN_OC_FAULT_LIMIT_DEFAULT;
+ pmdev->pages[i].ton_delay = ISL_TON_DELAY_DEFAULT;
+ pmdev->pages[i].ton_rise = ISL_TON_RISE_DEFAULT;
+ pmdev->pages[i].toff_fall = ISL_TOFF_FALL_DEFAULT;
+ pmdev->pages[i].revision = ISL_REVISION_DEFAULT;
+
+ pmdev->pages[i].read_vout = ISL_READ_VOUT_DEFAULT;
+ pmdev->pages[i].read_iout = ISL_READ_IOUT_DEFAULT;
+ pmdev->pages[i].read_pout = ISL_READ_POUT_DEFAULT;
+ pmdev->pages[i].read_vin = ISL_READ_VIN_DEFAULT;
+ pmdev->pages[i].read_iin = ISL_READ_IIN_DEFAULT;
+ pmdev->pages[i].read_pin = ISL_READ_PIN_DEFAULT;
+ pmdev->pages[i].read_temperature_1 = ISL_READ_TEMP_DEFAULT;
+ pmdev->pages[i].read_temperature_2 = ISL_READ_TEMP_DEFAULT;
+ pmdev->pages[i].read_temperature_3 = ISL_READ_TEMP_DEFAULT;
+ }
+}
+
+/* The raa228000 uses different direct mode coefficents from most isl devices */
+static void raa228000_exit_reset(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+
+ isl_pmbus_vr_exit_reset(obj);
+
+ pmdev->pages[0].read_iout = 0;
+ pmdev->pages[0].read_pout = 0;
+ pmdev->pages[0].read_vout = 0;
+ pmdev->pages[0].read_vin = 0;
+ pmdev->pages[0].read_iin = 0;
+ pmdev->pages[0].read_pin = 0;
+ pmdev->pages[0].read_temperature_1 = 0;
+ pmdev->pages[0].read_temperature_2 = 0;
+ pmdev->pages[0].read_temperature_3 = 0;
+}
+
+static void isl69259_exit_reset(Object *obj)
+{
+ ISLState *s = ISL69260(obj);
+ static const uint8_t ic_device_id[] = {0x04, 0x00, 0x81, 0xD2, 0x49, 0x3c};
+ g_assert(sizeof(ic_device_id) <= sizeof(s->ic_device_id));
+
+ isl_pmbus_vr_exit_reset(obj);
+
+ s->ic_device_id_len = sizeof(ic_device_id);
+ memcpy(s->ic_device_id, ic_device_id, sizeof(ic_device_id));
+}
+
+static void isl_pmbus_vr_add_props(Object *obj, uint64_t *flags, uint8_t pages)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ for (int i = 0; i < pages; i++) {
+ if (flags[i] & PB_HAS_VIN) {
+ object_property_add(obj, "vin[*]", "uint16",
+ isl_pmbus_vr_get,
+ isl_pmbus_vr_set,
+ NULL, &pmdev->pages[i].read_vin);
+ }
+
+ if (flags[i] & PB_HAS_VOUT) {
+ object_property_add(obj, "vout[*]", "uint16",
+ isl_pmbus_vr_get,
+ isl_pmbus_vr_set,
+ NULL, &pmdev->pages[i].read_vout);
+ }
+
+ if (flags[i] & PB_HAS_IIN) {
+ object_property_add(obj, "iin[*]", "uint16",
+ isl_pmbus_vr_get,
+ isl_pmbus_vr_set,
+ NULL, &pmdev->pages[i].read_iin);
+ }
+
+ if (flags[i] & PB_HAS_IOUT) {
+ object_property_add(obj, "iout[*]", "uint16",
+ isl_pmbus_vr_get,
+ isl_pmbus_vr_set,
+ NULL, &pmdev->pages[i].read_iout);
+ }
+
+ if (flags[i] & PB_HAS_PIN) {
+ object_property_add(obj, "pin[*]", "uint16",
+ isl_pmbus_vr_get,
+ isl_pmbus_vr_set,
+ NULL, &pmdev->pages[i].read_pin);
+ }
+
+ if (flags[i] & PB_HAS_POUT) {
+ object_property_add(obj, "pout[*]", "uint16",
+ isl_pmbus_vr_get,
+ isl_pmbus_vr_set,
+ NULL, &pmdev->pages[i].read_pout);
+ }
+
+ if (flags[i] & PB_HAS_TEMPERATURE) {
+ object_property_add(obj, "temp1[*]", "uint16",
+ isl_pmbus_vr_get,
+ isl_pmbus_vr_set,
+ NULL, &pmdev->pages[i].read_temperature_1);
+ }
+
+ if (flags[i] & PB_HAS_TEMP2) {
+ object_property_add(obj, "temp2[*]", "uint16",
+ isl_pmbus_vr_get,
+ isl_pmbus_vr_set,
+ NULL, &pmdev->pages[i].read_temperature_2);
+ }
+
+ if (flags[i] & PB_HAS_TEMP3) {
+ object_property_add(obj, "temp3[*]", "uint16",
+ isl_pmbus_vr_get,
+ isl_pmbus_vr_set,
+ NULL, &pmdev->pages[i].read_temperature_3);
+ }
+ }
+}
+
+static void raa22xx_init(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint64_t flags[2];
+
+ flags[0] = PB_HAS_VIN | PB_HAS_VOUT | PB_HAS_VOUT_MODE |
+ PB_HAS_VOUT_RATING | PB_HAS_VOUT_MARGIN | PB_HAS_IIN |
+ PB_HAS_IOUT | PB_HAS_PIN | PB_HAS_POUT | PB_HAS_TEMPERATURE |
+ PB_HAS_TEMP2 | PB_HAS_TEMP3 | PB_HAS_STATUS_MFR_SPECIFIC;
+ flags[1] = PB_HAS_IIN | PB_HAS_PIN | PB_HAS_TEMPERATURE | PB_HAS_TEMP3 |
+ PB_HAS_VOUT | PB_HAS_VOUT_MODE | PB_HAS_VOUT_MARGIN |
+ PB_HAS_VOUT_RATING | PB_HAS_IOUT | PB_HAS_POUT |
+ PB_HAS_STATUS_MFR_SPECIFIC;
+
+ pmbus_page_config(pmdev, 0, flags[0]);
+ pmbus_page_config(pmdev, 1, flags[1]);
+ isl_pmbus_vr_add_props(obj, flags, ARRAY_SIZE(flags));
+}
+
+static void raa228000_init(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint64_t flags[1];
+
+ flags[0] = PB_HAS_VIN | PB_HAS_VOUT | PB_HAS_VOUT_MODE |
+ PB_HAS_VOUT_RATING | PB_HAS_VOUT_MARGIN | PB_HAS_IIN |
+ PB_HAS_IOUT | PB_HAS_PIN | PB_HAS_POUT | PB_HAS_TEMPERATURE |
+ PB_HAS_TEMP2 | PB_HAS_TEMP3 | PB_HAS_STATUS_MFR_SPECIFIC;
+
+ pmbus_page_config(pmdev, 0, flags[0]);
+ isl_pmbus_vr_add_props(obj, flags, 1);
+}
+
+static void isl_pmbus_vr_class_init(ObjectClass *klass, void *data,
+ uint8_t pages)
+{
+ PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
+ k->write_data = isl_pmbus_vr_write_data;
+ k->receive_byte = isl_pmbus_vr_read_byte;
+ k->device_num_pages = pages;
+}
+
+static void isl69260_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->desc = "Renesas ISL69260 Digital Multiphase Voltage Regulator";
+ rc->phases.exit = isl_pmbus_vr_exit_reset;
+ isl_pmbus_vr_class_init(klass, data, 2);
+}
+
+static void raa228000_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->desc = "Renesas 228000 Digital Multiphase Voltage Regulator";
+ rc->phases.exit = raa228000_exit_reset;
+ isl_pmbus_vr_class_init(klass, data, 1);
+}
+
+static void raa229004_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->desc = "Renesas 229004 Digital Multiphase Voltage Regulator";
+ rc->phases.exit = isl_pmbus_vr_exit_reset;
+ isl_pmbus_vr_class_init(klass, data, 2);
+}
+
+static void isl69259_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->desc = "Renesas ISL69259 Digital Multiphase Voltage Regulator";
+ rc->phases.exit = isl69259_exit_reset;
+ isl_pmbus_vr_class_init(klass, data, 2);
+}
+
+static const TypeInfo isl69259_info = {
+ .name = TYPE_ISL69259,
+ .parent = TYPE_ISL69260,
+ .class_init = isl69259_class_init,
+};
+
+static const TypeInfo isl69260_info = {
+ .name = TYPE_ISL69260,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(ISLState),
+ .instance_init = raa22xx_init,
+ .class_init = isl69260_class_init,
+};
+
+static const TypeInfo raa229004_info = {
+ .name = TYPE_RAA229004,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(ISLState),
+ .instance_init = raa22xx_init,
+ .class_init = raa229004_class_init,
+};
+
+static const TypeInfo raa228000_info = {
+ .name = TYPE_RAA228000,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(ISLState),
+ .instance_init = raa228000_init,
+ .class_init = raa228000_class_init,
+};
+
+static void isl_pmbus_vr_register_types(void)
+{
+ type_register_static(&isl69259_info);
+ type_register_static(&isl69260_info);
+ type_register_static(&raa228000_info);
+ type_register_static(&raa229004_info);
+}
+
+type_init(isl_pmbus_vr_register_types)
diff --git a/hw/sensor/lsm303dlhc_mag.c b/hw/sensor/lsm303dlhc_mag.c
new file mode 100644
index 00000000..bb8d48b2
--- /dev/null
+++ b/hw/sensor/lsm303dlhc_mag.c
@@ -0,0 +1,558 @@
+/*
+ * LSM303DLHC I2C magnetometer.
+ *
+ * Copyright (C) 2021 Linaro Ltd.
+ * Written by Kevin Townsend <kevin.townsend@linaro.org>
+ *
+ * Based on: https://www.st.com/resource/en/datasheet/lsm303dlhc.pdf
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/*
+ * The I2C address associated with this device is set on the command-line when
+ * initialising the machine, but the following address is standard: 0x1E.
+ *
+ * Get and set functions for 'mag-x', 'mag-y' and 'mag-z' assume that
+ * 1 = 0.001 uT. (NOTE the 1 gauss = 100 uT, so setting a value of 100,000
+ * would be equal to 1 gauss or 100 uT.)
+ *
+ * Get and set functions for 'temperature' assume that 1 = 0.001 C, so 23.6 C
+ * would be equal to 23600.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "qemu/bswap.h"
+
+enum LSM303DLHCMagReg {
+ LSM303DLHC_MAG_REG_CRA = 0x00,
+ LSM303DLHC_MAG_REG_CRB = 0x01,
+ LSM303DLHC_MAG_REG_MR = 0x02,
+ LSM303DLHC_MAG_REG_OUT_X_H = 0x03,
+ LSM303DLHC_MAG_REG_OUT_X_L = 0x04,
+ LSM303DLHC_MAG_REG_OUT_Z_H = 0x05,
+ LSM303DLHC_MAG_REG_OUT_Z_L = 0x06,
+ LSM303DLHC_MAG_REG_OUT_Y_H = 0x07,
+ LSM303DLHC_MAG_REG_OUT_Y_L = 0x08,
+ LSM303DLHC_MAG_REG_SR = 0x09,
+ LSM303DLHC_MAG_REG_IRA = 0x0A,
+ LSM303DLHC_MAG_REG_IRB = 0x0B,
+ LSM303DLHC_MAG_REG_IRC = 0x0C,
+ LSM303DLHC_MAG_REG_TEMP_OUT_H = 0x31,
+ LSM303DLHC_MAG_REG_TEMP_OUT_L = 0x32
+};
+
+typedef struct LSM303DLHCMagState {
+ I2CSlave parent_obj;
+ uint8_t cra;
+ uint8_t crb;
+ uint8_t mr;
+ int16_t x;
+ int16_t z;
+ int16_t y;
+ int16_t x_lock;
+ int16_t z_lock;
+ int16_t y_lock;
+ uint8_t sr;
+ uint8_t ira;
+ uint8_t irb;
+ uint8_t irc;
+ int16_t temperature;
+ int16_t temperature_lock;
+ uint8_t len;
+ uint8_t buf;
+ uint8_t pointer;
+} LSM303DLHCMagState;
+
+#define TYPE_LSM303DLHC_MAG "lsm303dlhc_mag"
+OBJECT_DECLARE_SIMPLE_TYPE(LSM303DLHCMagState, LSM303DLHC_MAG)
+
+/*
+ * Conversion factor from Gauss to sensor values for each GN gain setting,
+ * in units "lsb per Gauss" (see data sheet table 3). There is no documented
+ * behaviour if the GN setting in CRB is incorrectly set to 0b000;
+ * we arbitrarily make it the same as 0b001.
+ */
+uint32_t xy_gain[] = { 1100, 1100, 855, 670, 450, 400, 330, 230 };
+uint32_t z_gain[] = { 980, 980, 760, 600, 400, 355, 295, 205 };
+
+static void lsm303dlhc_mag_get_x(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
+ int gm = extract32(s->crb, 5, 3);
+
+ /* Convert to uT where 1000 = 1 uT. Conversion factor depends on gain. */
+ int64_t value = muldiv64(s->x, 100000, xy_gain[gm]);
+ visit_type_int(v, name, &value, errp);
+}
+
+static void lsm303dlhc_mag_get_y(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
+ int gm = extract32(s->crb, 5, 3);
+
+ /* Convert to uT where 1000 = 1 uT. Conversion factor depends on gain. */
+ int64_t value = muldiv64(s->y, 100000, xy_gain[gm]);
+ visit_type_int(v, name, &value, errp);
+}
+
+static void lsm303dlhc_mag_get_z(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
+ int gm = extract32(s->crb, 5, 3);
+
+ /* Convert to uT where 1000 = 1 uT. Conversion factor depends on gain. */
+ int64_t value = muldiv64(s->z, 100000, z_gain[gm]);
+ visit_type_int(v, name, &value, errp);
+}
+
+static void lsm303dlhc_mag_set_x(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
+ int64_t value;
+ int64_t reg;
+ int gm = extract32(s->crb, 5, 3);
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ reg = muldiv64(value, xy_gain[gm], 100000);
+
+ /* Make sure we are within a 12-bit limit. */
+ if (reg > 2047 || reg < -2048) {
+ error_setg(errp, "value %" PRId64 " out of register's range", value);
+ return;
+ }
+
+ s->x = (int16_t)reg;
+}
+
+static void lsm303dlhc_mag_set_y(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
+ int64_t value;
+ int64_t reg;
+ int gm = extract32(s->crb, 5, 3);
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ reg = muldiv64(value, xy_gain[gm], 100000);
+
+ /* Make sure we are within a 12-bit limit. */
+ if (reg > 2047 || reg < -2048) {
+ error_setg(errp, "value %" PRId64 " out of register's range", value);
+ return;
+ }
+
+ s->y = (int16_t)reg;
+}
+
+static void lsm303dlhc_mag_set_z(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
+ int64_t value;
+ int64_t reg;
+ int gm = extract32(s->crb, 5, 3);
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ reg = muldiv64(value, z_gain[gm], 100000);
+
+ /* Make sure we are within a 12-bit limit. */
+ if (reg > 2047 || reg < -2048) {
+ error_setg(errp, "value %" PRId64 " out of register's range", value);
+ return;
+ }
+
+ s->z = (int16_t)reg;
+}
+
+/*
+ * Get handler for the temperature property.
+ */
+static void lsm303dlhc_mag_get_temperature(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
+ int64_t value;
+
+ /* Convert to 1 lsb = 0.125 C to 1 = 0.001 C for 'temperature' property. */
+ value = s->temperature * 125;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+/*
+ * Set handler for the temperature property.
+ */
+static void lsm303dlhc_mag_set_temperature(Object *obj, Visitor *v,
+ const char *name, void *opaque,
+ Error **errp)
+{
+ LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
+ int64_t value;
+
+ if (!visit_type_int(v, name, &value, errp)) {
+ return;
+ }
+
+ /* Input temperature is in 0.001 C units. Convert to 1 lsb = 0.125 C. */
+ value /= 125;
+
+ if (value > 2047 || value < -2048) {
+ error_setg(errp, "value %" PRId64 " lsb is out of range", value);
+ return;
+ }
+
+ s->temperature = (int16_t)value;
+}
+
+/*
+ * Callback handler whenever a 'I2C_START_RECV' (read) event is received.
+ */
+static void lsm303dlhc_mag_read(LSM303DLHCMagState *s)
+{
+ /*
+ * Set the LOCK bit whenever a new read attempt is made. This will be
+ * cleared in I2C_FINISH. Note that DRDY is always set to 1 in this driver.
+ */
+ s->sr = 0x3;
+
+ /*
+ * Copy the current X/Y/Z and temp. values into the locked registers so
+ * that 'mag-x', 'mag-y', 'mag-z' and 'temperature' can continue to be
+ * updated via QOM, etc., without corrupting the current read event.
+ */
+ s->x_lock = s->x;
+ s->z_lock = s->z;
+ s->y_lock = s->y;
+ s->temperature_lock = s->temperature;
+}
+
+/*
+ * Callback handler whenever a 'I2C_FINISH' event is received.
+ */
+static void lsm303dlhc_mag_finish(LSM303DLHCMagState *s)
+{
+ /*
+ * Clear the LOCK bit when the read attempt terminates.
+ * This bit is initially set in the I2C_START_RECV handler.
+ */
+ s->sr = 0x1;
+}
+
+/*
+ * Callback handler when a device attempts to write to a register.
+ */
+static void lsm303dlhc_mag_write(LSM303DLHCMagState *s)
+{
+ switch (s->pointer) {
+ case LSM303DLHC_MAG_REG_CRA:
+ s->cra = s->buf;
+ break;
+ case LSM303DLHC_MAG_REG_CRB:
+ /* Make sure gain is at least 1, falling back to 1 on an error. */
+ if (s->buf >> 5 == 0) {
+ s->buf = 1 << 5;
+ }
+ s->crb = s->buf;
+ break;
+ case LSM303DLHC_MAG_REG_MR:
+ s->mr = s->buf;
+ break;
+ case LSM303DLHC_MAG_REG_SR:
+ s->sr = s->buf;
+ break;
+ case LSM303DLHC_MAG_REG_IRA:
+ s->ira = s->buf;
+ break;
+ case LSM303DLHC_MAG_REG_IRB:
+ s->irb = s->buf;
+ break;
+ case LSM303DLHC_MAG_REG_IRC:
+ s->irc = s->buf;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "reg is read-only: 0x%02X", s->buf);
+ break;
+ }
+}
+
+/*
+ * Low-level master-to-slave transaction handler.
+ */
+static int lsm303dlhc_mag_send(I2CSlave *i2c, uint8_t data)
+{
+ LSM303DLHCMagState *s = LSM303DLHC_MAG(i2c);
+
+ if (s->len == 0) {
+ /* First byte is the reg pointer */
+ s->pointer = data;
+ s->len++;
+ } else if (s->len == 1) {
+ /* Second byte is the new register value. */
+ s->buf = data;
+ lsm303dlhc_mag_write(s);
+ } else {
+ g_assert_not_reached();
+ }
+
+ return 0;
+}
+
+/*
+ * Low-level slave-to-master transaction handler (read attempts).
+ */
+static uint8_t lsm303dlhc_mag_recv(I2CSlave *i2c)
+{
+ LSM303DLHCMagState *s = LSM303DLHC_MAG(i2c);
+ uint8_t resp;
+
+ switch (s->pointer) {
+ case LSM303DLHC_MAG_REG_CRA:
+ resp = s->cra;
+ break;
+ case LSM303DLHC_MAG_REG_CRB:
+ resp = s->crb;
+ break;
+ case LSM303DLHC_MAG_REG_MR:
+ resp = s->mr;
+ break;
+ case LSM303DLHC_MAG_REG_OUT_X_H:
+ resp = (uint8_t)(s->x_lock >> 8);
+ break;
+ case LSM303DLHC_MAG_REG_OUT_X_L:
+ resp = (uint8_t)(s->x_lock);
+ break;
+ case LSM303DLHC_MAG_REG_OUT_Z_H:
+ resp = (uint8_t)(s->z_lock >> 8);
+ break;
+ case LSM303DLHC_MAG_REG_OUT_Z_L:
+ resp = (uint8_t)(s->z_lock);
+ break;
+ case LSM303DLHC_MAG_REG_OUT_Y_H:
+ resp = (uint8_t)(s->y_lock >> 8);
+ break;
+ case LSM303DLHC_MAG_REG_OUT_Y_L:
+ resp = (uint8_t)(s->y_lock);
+ break;
+ case LSM303DLHC_MAG_REG_SR:
+ resp = s->sr;
+ break;
+ case LSM303DLHC_MAG_REG_IRA:
+ resp = s->ira;
+ break;
+ case LSM303DLHC_MAG_REG_IRB:
+ resp = s->irb;
+ break;
+ case LSM303DLHC_MAG_REG_IRC:
+ resp = s->irc;
+ break;
+ case LSM303DLHC_MAG_REG_TEMP_OUT_H:
+ /* Check if the temperature sensor is enabled or not (CRA & 0x80). */
+ if (s->cra & 0x80) {
+ resp = (uint8_t)(s->temperature_lock >> 8);
+ } else {
+ resp = 0;
+ }
+ break;
+ case LSM303DLHC_MAG_REG_TEMP_OUT_L:
+ if (s->cra & 0x80) {
+ resp = (uint8_t)(s->temperature_lock & 0xff);
+ } else {
+ resp = 0;
+ }
+ break;
+ default:
+ resp = 0;
+ break;
+ }
+
+ /*
+ * The address pointer on the LSM303DLHC auto-increments whenever a byte
+ * is read, without the master device having to request the next address.
+ *
+ * The auto-increment process has the following logic:
+ *
+ * - if (s->pointer == 8) then s->pointer = 3
+ * - else: if (s->pointer == 12) then s->pointer = 0
+ * - else: s->pointer += 1
+ *
+ * Reading an invalid address return 0.
+ */
+ if (s->pointer == LSM303DLHC_MAG_REG_OUT_Y_L) {
+ s->pointer = LSM303DLHC_MAG_REG_OUT_X_H;
+ } else if (s->pointer == LSM303DLHC_MAG_REG_IRC) {
+ s->pointer = LSM303DLHC_MAG_REG_CRA;
+ } else {
+ s->pointer++;
+ }
+
+ return resp;
+}
+
+/*
+ * Bus state change handler.
+ */
+static int lsm303dlhc_mag_event(I2CSlave *i2c, enum i2c_event event)
+{
+ LSM303DLHCMagState *s = LSM303DLHC_MAG(i2c);
+
+ switch (event) {
+ case I2C_START_SEND:
+ break;
+ case I2C_START_RECV:
+ lsm303dlhc_mag_read(s);
+ break;
+ case I2C_FINISH:
+ lsm303dlhc_mag_finish(s);
+ break;
+ case I2C_NACK:
+ break;
+ default:
+ return -1;
+ }
+
+ s->len = 0;
+ return 0;
+}
+
+/*
+ * Device data description using VMSTATE macros.
+ */
+static const VMStateDescription vmstate_lsm303dlhc_mag = {
+ .name = "LSM303DLHC_MAG",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+
+ VMSTATE_I2C_SLAVE(parent_obj, LSM303DLHCMagState),
+ VMSTATE_UINT8(len, LSM303DLHCMagState),
+ VMSTATE_UINT8(buf, LSM303DLHCMagState),
+ VMSTATE_UINT8(pointer, LSM303DLHCMagState),
+ VMSTATE_UINT8(cra, LSM303DLHCMagState),
+ VMSTATE_UINT8(crb, LSM303DLHCMagState),
+ VMSTATE_UINT8(mr, LSM303DLHCMagState),
+ VMSTATE_INT16(x, LSM303DLHCMagState),
+ VMSTATE_INT16(z, LSM303DLHCMagState),
+ VMSTATE_INT16(y, LSM303DLHCMagState),
+ VMSTATE_INT16(x_lock, LSM303DLHCMagState),
+ VMSTATE_INT16(z_lock, LSM303DLHCMagState),
+ VMSTATE_INT16(y_lock, LSM303DLHCMagState),
+ VMSTATE_UINT8(sr, LSM303DLHCMagState),
+ VMSTATE_UINT8(ira, LSM303DLHCMagState),
+ VMSTATE_UINT8(irb, LSM303DLHCMagState),
+ VMSTATE_UINT8(irc, LSM303DLHCMagState),
+ VMSTATE_INT16(temperature, LSM303DLHCMagState),
+ VMSTATE_INT16(temperature_lock, LSM303DLHCMagState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/*
+ * Put the device into post-reset default state.
+ */
+static void lsm303dlhc_mag_default_cfg(LSM303DLHCMagState *s)
+{
+ /* Set the device into is default reset state. */
+ s->len = 0;
+ s->pointer = 0; /* Current register. */
+ s->buf = 0; /* Shared buffer. */
+ s->cra = 0x10; /* Temp Enabled = 0, Data Rate = 15.0 Hz. */
+ s->crb = 0x20; /* Gain = +/- 1.3 Gauss. */
+ s->mr = 0x3; /* Operating Mode = Sleep. */
+ s->x = 0;
+ s->z = 0;
+ s->y = 0;
+ s->x_lock = 0;
+ s->z_lock = 0;
+ s->y_lock = 0;
+ s->sr = 0x1; /* DRDY = 1. */
+ s->ira = 0x48;
+ s->irb = 0x34;
+ s->irc = 0x33;
+ s->temperature = 0; /* Default to 0 degrees C (0/8 lsb = 0 C). */
+ s->temperature_lock = 0;
+}
+
+/*
+ * Callback handler when DeviceState 'reset' is set to true.
+ */
+static void lsm303dlhc_mag_reset(DeviceState *dev)
+{
+ I2CSlave *i2c = I2C_SLAVE(dev);
+ LSM303DLHCMagState *s = LSM303DLHC_MAG(i2c);
+
+ /* Set the device into its default reset state. */
+ lsm303dlhc_mag_default_cfg(s);
+}
+
+/*
+ * Initialisation of any public properties.
+ */
+static void lsm303dlhc_mag_initfn(Object *obj)
+{
+ object_property_add(obj, "mag-x", "int",
+ lsm303dlhc_mag_get_x,
+ lsm303dlhc_mag_set_x, NULL, NULL);
+
+ object_property_add(obj, "mag-y", "int",
+ lsm303dlhc_mag_get_y,
+ lsm303dlhc_mag_set_y, NULL, NULL);
+
+ object_property_add(obj, "mag-z", "int",
+ lsm303dlhc_mag_get_z,
+ lsm303dlhc_mag_set_z, NULL, NULL);
+
+ object_property_add(obj, "temperature", "int",
+ lsm303dlhc_mag_get_temperature,
+ lsm303dlhc_mag_set_temperature, NULL, NULL);
+}
+
+/*
+ * Set the virtual method pointers (bus state change, tx/rx, etc.).
+ */
+static void lsm303dlhc_mag_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ dc->reset = lsm303dlhc_mag_reset;
+ dc->vmsd = &vmstate_lsm303dlhc_mag;
+ k->event = lsm303dlhc_mag_event;
+ k->recv = lsm303dlhc_mag_recv;
+ k->send = lsm303dlhc_mag_send;
+}
+
+static const TypeInfo lsm303dlhc_mag_info = {
+ .name = TYPE_LSM303DLHC_MAG,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(LSM303DLHCMagState),
+ .instance_init = lsm303dlhc_mag_initfn,
+ .class_init = lsm303dlhc_mag_class_init,
+};
+
+static void lsm303dlhc_mag_register_types(void)
+{
+ type_register_static(&lsm303dlhc_mag_info);
+}
+
+type_init(lsm303dlhc_mag_register_types)
diff --git a/hw/sensor/max31785.c b/hw/sensor/max31785.c
new file mode 100644
index 00000000..8b95e324
--- /dev/null
+++ b/hw/sensor/max31785.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Maxim MAX31785 PMBus 6-Channel Fan Controller
+ *
+ * Datasheet:
+ * https://datasheets.maximintegrated.com/en/ds/MAX31785.pdf
+ *
+ * Copyright(c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#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_MAX31785 "max31785"
+#define MAX31785(obj) OBJECT_CHECK(MAX31785State, (obj), TYPE_MAX31785)
+
+/* MAX31785 mfr specific PMBus commands */
+#define MAX31785_MFR_MODE 0xD1
+#define MAX31785_MFR_PSEN_CONFIG 0xD2
+#define MAX31785_MFR_VOUT_PEAK 0xD4
+#define MAX31785_MFR_TEMPERATURE_PEAK 0xD6
+#define MAX31785_MFR_VOUT_MIN 0xD7
+#define MAX31785_MFR_FAULT_RESPONSE 0xD9
+#define MAX31785_MFR_NV_FAULT_LOG 0xDC
+#define MAX31785_MFR_TIME_COUNT 0xDD
+#define MAX31785_MFR_TEMP_SENSOR_CONFIG 0xF0
+#define MAX31785_MFR_FAN_CONFIG 0xF1
+#define MAX31785_MFR_FAN_LUT 0xF2
+#define MAX31785_MFR_READ_FAN_PWM 0xF3
+#define MAX31785_MFR_FAN_FAULT_LIMIT 0xF5
+#define MAX31785_MFR_FAN_WARN_LIMIT 0xF6
+#define MAX31785_MFR_FAN_RUN_TIME 0xF7
+#define MAX31785_MFR_FAN_PWM_AVG 0xF8
+#define MAX31785_MFR_FAN_PWM2RPM 0xF9
+
+/* defaults as per the data sheet */
+#define MAX31785_DEFAULT_CAPABILITY 0x10
+#define MAX31785_DEFAULT_VOUT_MODE 0x40
+#define MAX31785_DEFAULT_VOUT_SCALE_MONITOR 0x7FFF
+#define MAX31785_DEFAULT_FAN_COMMAND_1 0x7FFF
+#define MAX31785_DEFAULT_OV_FAULT_LIMIT 0x7FFF
+#define MAX31785_DEFAULT_OV_WARN_LIMIT 0x7FFF
+#define MAX31785_DEFAULT_OT_FAULT_LIMIT 0x7FFF
+#define MAX31785_DEFAULT_OT_WARN_LIMIT 0x7FFF
+#define MAX31785_DEFAULT_PMBUS_REVISION 0x11
+#define MAX31785_DEFAULT_MFR_ID 0x4D
+#define MAX31785_DEFAULT_MFR_MODEL 0x53
+#define MAX31785_DEFAULT_MFR_REVISION 0x3030
+#define MAX31785A_DEFAULT_MFR_REVISION 0x3040
+#define MAX31785B_DEFAULT_MFR_REVISION 0x3061
+#define MAX31785B_DEFAULT_MFR_TEMPERATURE_PEAK 0x8000
+#define MAX31785B_DEFAULT_MFR_VOUT_MIN 0x7FFF
+#define MAX31785_DEFAULT_TEXT 0x3130313031303130
+
+/* MAX31785 pages */
+#define MAX31785_TOTAL_NUM_PAGES 23
+#define MAX31785_FAN_PAGES 6
+#define MAX31785_MIN_FAN_PAGE 0
+#define MAX31785_MAX_FAN_PAGE 5
+#define MAX31785_MIN_TEMP_PAGE 6
+#define MAX31785_MAX_TEMP_PAGE 16
+#define MAX31785_MIN_ADC_VOLTAGE_PAGE 17
+#define MAX31785_MAX_ADC_VOLTAGE_PAGE 22
+
+/* FAN_CONFIG_1_2 */
+#define MAX31785_MFR_FAN_CONFIG 0xF1
+#define MAX31785_FAN_CONFIG_ENABLE BIT(7)
+#define MAX31785_FAN_CONFIG_RPM_PWM BIT(6)
+#define MAX31785_FAN_CONFIG_PULSE(pulse) (pulse << 4)
+#define MAX31785_DEFAULT_FAN_CONFIG_1_2(pulse) \
+ (MAX31785_FAN_CONFIG_ENABLE | MAX31785_FAN_CONFIG_PULSE(pulse))
+#define MAX31785_DEFAULT_MFR_FAN_CONFIG 0x0000
+
+/* fan speed in RPM */
+#define MAX31785_DEFAULT_FAN_SPEED 0x7fff
+#define MAX31785_DEFAULT_FAN_STATUS 0x00
+
+#define MAX31785_DEFAULT_FAN_MAX_PWM 0x2710
+
+/*
+ * MAX31785State:
+ * @code: The command code received
+ * @page: Each page corresponds to a device monitored by the Max 31785
+ * The page register determines the available commands depending on device
+ * _____________________________________________________________________________
+ * | 0 | Fan Connected to PWM0 |
+ * |_______|___________________________________________________________________|
+ * | 1 | Fan Connected to PWM1 |
+ * |_______|___________________________________________________________________|
+ * | 2 | Fan Connected to PWM2 |
+ * |_______|___________________________________________________________________|
+ * | 3 | Fan Connected to PWM3 |
+ * |_______|___________________________________________________________________|
+ * | 4 | Fan Connected to PWM4 |
+ * |_______|___________________________________________________________________|
+ * | 5 | Fan Connected to PWM5 |
+ * |_______|___________________________________________________________________|
+ * | 6 | Remote Thermal Diode Connected to ADC 0 |
+ * |_______|___________________________________________________________________|
+ * | 7 | Remote Thermal Diode Connected to ADC 1 |
+ * |_______|___________________________________________________________________|
+ * | 8 | Remote Thermal Diode Connected to ADC 2 |
+ * |_______|___________________________________________________________________|
+ * | 9 | Remote Thermal Diode Connected to ADC 3 |
+ * |_______|___________________________________________________________________|
+ * | 10 | Remote Thermal Diode Connected to ADC 4 |
+ * |_______|___________________________________________________________________|
+ * | 11 | Remote Thermal Diode Connected to ADC 5 |
+ * |_______|___________________________________________________________________|
+ * | 12 | Internal Temperature Sensor |
+ * |_______|___________________________________________________________________|
+ * | 13 | Remote I2C Temperature Sensor with Address 0 |
+ * |_______|___________________________________________________________________|
+ * | 14 | Remote I2C Temperature Sensor with Address 1 |
+ * |_______|___________________________________________________________________|
+ * | 15 | Remote I2C Temperature Sensor with Address 2 |
+ * |_______|___________________________________________________________________|
+ * | 16 | Remote I2C Temperature Sensor with Address 3 |
+ * |_______|___________________________________________________________________|
+ * | 17 | Remote I2C Temperature Sensor with Address 4 |
+ * |_______|___________________________________________________________________|
+ * | 17 | Remote Voltage Connected to ADC0 |
+ * |_______|___________________________________________________________________|
+ * | 18 | Remote Voltage Connected to ADC1 |
+ * |_______|___________________________________________________________________|
+ * | 19 | Remote Voltage Connected to ADC2 |
+ * |_______|___________________________________________________________________|
+ * | 20 | Remote Voltage Connected to ADC3 |
+ * |_______|___________________________________________________________________|
+ * | 21 | Remote Voltage Connected to ADC4 |
+ * |_______|___________________________________________________________________|
+ * | 22 | Remote Voltage Connected to ADC5 |
+ * |_______|___________________________________________________________________|
+ * |23-254 | Reserved |
+ * |_______|___________________________________________________________________|
+ * | 255 | Applies to all pages |
+ * |_______|___________________________________________________________________|
+ */
+
+/* Place holder to save the max31785 mfr specific registers */
+typedef struct MAX31785State {
+ PMBusDevice parent;
+ uint16_t mfr_mode[MAX31785_TOTAL_NUM_PAGES];
+ uint16_t vout_peak[MAX31785_TOTAL_NUM_PAGES];
+ uint16_t temperature_peak[MAX31785_TOTAL_NUM_PAGES];
+ uint16_t vout_min[MAX31785_TOTAL_NUM_PAGES];
+ uint8_t fault_response[MAX31785_TOTAL_NUM_PAGES];
+ uint32_t time_count[MAX31785_TOTAL_NUM_PAGES];
+ uint16_t temp_sensor_config[MAX31785_TOTAL_NUM_PAGES];
+ uint16_t fan_config[MAX31785_TOTAL_NUM_PAGES];
+ uint16_t read_fan_pwm[MAX31785_TOTAL_NUM_PAGES];
+ uint16_t fan_fault_limit[MAX31785_TOTAL_NUM_PAGES];
+ uint16_t fan_warn_limit[MAX31785_TOTAL_NUM_PAGES];
+ uint16_t fan_run_time[MAX31785_TOTAL_NUM_PAGES];
+ uint16_t fan_pwm_avg[MAX31785_TOTAL_NUM_PAGES];
+ uint64_t fan_pwm2rpm[MAX31785_TOTAL_NUM_PAGES];
+ uint64_t mfr_location;
+ uint64_t mfr_date;
+ uint64_t mfr_serial;
+ uint16_t mfr_revision;
+} MAX31785State;
+
+static uint8_t max31785_read_byte(PMBusDevice *pmdev)
+{
+ MAX31785State *s = MAX31785(pmdev);
+ switch (pmdev->code) {
+
+ case PMBUS_FAN_CONFIG_1_2:
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmbus_send8(pmdev, pmdev->pages[pmdev->page].fan_config_1_2);
+ }
+ break;
+
+ case PMBUS_FAN_COMMAND_1:
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmbus_send16(pmdev, pmdev->pages[pmdev->page].fan_command_1);
+ }
+ break;
+
+ case PMBUS_READ_FAN_SPEED_1:
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmbus_send16(pmdev, pmdev->pages[pmdev->page].read_fan_speed_1);
+ }
+ break;
+
+ case PMBUS_STATUS_FANS_1_2:
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmbus_send16(pmdev, pmdev->pages[pmdev->page].status_fans_1_2);
+ }
+ break;
+
+ case PMBUS_MFR_REVISION:
+ pmbus_send16(pmdev, MAX31785_DEFAULT_MFR_REVISION);
+ break;
+
+ case PMBUS_MFR_ID:
+ pmbus_send8(pmdev, 0x4d); /* Maxim */
+ break;
+
+ case PMBUS_MFR_MODEL:
+ pmbus_send8(pmdev, 0x53);
+ 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 MAX31785_MFR_MODE:
+ pmbus_send16(pmdev, s->mfr_mode[pmdev->page]);
+ break;
+
+ case MAX31785_MFR_VOUT_PEAK:
+ if ((pmdev->page >= MAX31785_MIN_ADC_VOLTAGE_PAGE) &&
+ (pmdev->page <= MAX31785_MAX_ADC_VOLTAGE_PAGE)) {
+ pmbus_send16(pmdev, s->vout_peak[pmdev->page]);
+ }
+ break;
+
+ case MAX31785_MFR_TEMPERATURE_PEAK:
+ if ((pmdev->page >= MAX31785_MIN_TEMP_PAGE) &&
+ (pmdev->page <= MAX31785_MAX_TEMP_PAGE)) {
+ pmbus_send16(pmdev, s->temperature_peak[pmdev->page]);
+ }
+ break;
+
+ case MAX31785_MFR_VOUT_MIN:
+ if ((pmdev->page >= MAX31785_MIN_ADC_VOLTAGE_PAGE) &&
+ (pmdev->page <= MAX31785_MAX_ADC_VOLTAGE_PAGE)) {
+ pmbus_send16(pmdev, s->vout_min[pmdev->page]);
+ }
+ break;
+
+ case MAX31785_MFR_FAULT_RESPONSE:
+ pmbus_send8(pmdev, s->fault_response[pmdev->page]);
+ break;
+
+ case MAX31785_MFR_TIME_COUNT: /* R/W 32 */
+ pmbus_send32(pmdev, s->time_count[pmdev->page]);
+ break;
+
+ case MAX31785_MFR_TEMP_SENSOR_CONFIG: /* R/W 16 */
+ if ((pmdev->page >= MAX31785_MIN_TEMP_PAGE) &&
+ (pmdev->page <= MAX31785_MAX_TEMP_PAGE)) {
+ pmbus_send16(pmdev, s->temp_sensor_config[pmdev->page]);
+ }
+ break;
+
+ case MAX31785_MFR_FAN_CONFIG: /* R/W 16 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmbus_send16(pmdev, s->fan_config[pmdev->page]);
+ }
+ break;
+
+ case MAX31785_MFR_READ_FAN_PWM: /* R/W 16 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmbus_send16(pmdev, s->read_fan_pwm[pmdev->page]);
+ }
+ break;
+
+ case MAX31785_MFR_FAN_FAULT_LIMIT: /* R/W 16 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmbus_send16(pmdev, s->fan_fault_limit[pmdev->page]);
+ }
+ break;
+
+ case MAX31785_MFR_FAN_WARN_LIMIT: /* R/W 16 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmbus_send16(pmdev, s->fan_warn_limit[pmdev->page]);
+ }
+ break;
+
+ case MAX31785_MFR_FAN_RUN_TIME: /* R/W 16 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmbus_send16(pmdev, s->fan_run_time[pmdev->page]);
+ }
+ break;
+
+ case MAX31785_MFR_FAN_PWM_AVG: /* R/W 16 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmbus_send16(pmdev, s->fan_pwm_avg[pmdev->page]);
+ }
+ break;
+
+ case MAX31785_MFR_FAN_PWM2RPM: /* R/W 64 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmbus_send64(pmdev, s->fan_pwm2rpm[pmdev->page]);
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+
+ return 0xFF;
+}
+
+static int max31785_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+ uint8_t len)
+{
+ MAX31785State *s = MAX31785(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 PMBUS_FAN_CONFIG_1_2:
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmdev->pages[pmdev->page].fan_config_1_2 = pmbus_receive8(pmdev);
+ }
+ break;
+
+ case PMBUS_FAN_COMMAND_1:
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ pmdev->pages[pmdev->page].fan_command_1 = pmbus_receive16(pmdev);
+ pmdev->pages[pmdev->page].read_fan_speed_1 =
+ ((MAX31785_DEFAULT_FAN_SPEED / MAX31785_DEFAULT_FAN_MAX_PWM) *
+ pmdev->pages[pmdev->page].fan_command_1);
+ }
+ 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 MAX31785_MFR_MODE: /* R/W word */
+ s->mfr_mode[pmdev->page] = pmbus_receive16(pmdev);
+ break;
+
+ case MAX31785_MFR_VOUT_PEAK: /* R/W word */
+ if ((pmdev->page >= MAX31785_MIN_ADC_VOLTAGE_PAGE) &&
+ (pmdev->page <= MAX31785_MAX_ADC_VOLTAGE_PAGE)) {
+ s->vout_peak[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX31785_MFR_TEMPERATURE_PEAK: /* R/W word */
+ if ((pmdev->page >= 6) && (pmdev->page <= 16)) {
+ s->temperature_peak[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX31785_MFR_VOUT_MIN: /* R/W word */
+ if ((pmdev->page >= MAX31785_MIN_ADC_VOLTAGE_PAGE) &&
+ (pmdev->page <= MAX31785_MAX_ADC_VOLTAGE_PAGE)) {
+ s->vout_min[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX31785_MFR_FAULT_RESPONSE: /* R/W 8 */
+ s->fault_response[pmdev->page] = pmbus_receive8(pmdev);
+ break;
+
+ case MAX31785_MFR_TIME_COUNT: /* R/W 32 */
+ s->time_count[pmdev->page] = pmbus_receive32(pmdev);
+ break;
+
+ case MAX31785_MFR_TEMP_SENSOR_CONFIG: /* R/W 16 */
+ if ((pmdev->page >= MAX31785_MIN_TEMP_PAGE) &&
+ (pmdev->page <= MAX31785_MAX_TEMP_PAGE)) {
+ s->temp_sensor_config[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX31785_MFR_FAN_CONFIG: /* R/W 16 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ s->fan_config[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX31785_MFR_FAN_FAULT_LIMIT: /* R/W 16 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ s->fan_fault_limit[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX31785_MFR_FAN_WARN_LIMIT: /* R/W 16 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ s->fan_warn_limit[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX31785_MFR_FAN_RUN_TIME: /* R/W 16 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ s->fan_run_time[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX31785_MFR_FAN_PWM_AVG: /* R/W 16 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ s->fan_pwm_avg[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX31785_MFR_FAN_PWM2RPM: /* R/W 64 */
+ if (pmdev->page <= MAX31785_MAX_FAN_PAGE) {
+ s->fan_pwm2rpm[pmdev->page] = pmbus_receive64(pmdev);
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+
+ return 0;
+}
+
+static void max31785_exit_reset(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ MAX31785State *s = MAX31785(obj);
+
+ pmdev->capability = MAX31785_DEFAULT_CAPABILITY;
+
+ for (int i = MAX31785_MIN_FAN_PAGE; i <= MAX31785_MAX_FAN_PAGE; i++) {
+ pmdev->pages[i].vout_mode = MAX31785_DEFAULT_VOUT_MODE;
+ pmdev->pages[i].fan_command_1 = MAX31785_DEFAULT_FAN_COMMAND_1;
+ pmdev->pages[i].revision = MAX31785_DEFAULT_PMBUS_REVISION;
+ pmdev->pages[i].fan_config_1_2 = MAX31785_DEFAULT_FAN_CONFIG_1_2(0);
+ pmdev->pages[i].read_fan_speed_1 = MAX31785_DEFAULT_FAN_SPEED;
+ pmdev->pages[i].status_fans_1_2 = MAX31785_DEFAULT_FAN_STATUS;
+ }
+
+ for (int i = MAX31785_MIN_TEMP_PAGE; i <= MAX31785_MAX_TEMP_PAGE; i++) {
+ pmdev->pages[i].vout_mode = MAX31785_DEFAULT_VOUT_MODE;
+ pmdev->pages[i].revision = MAX31785_DEFAULT_PMBUS_REVISION;
+ pmdev->pages[i].ot_fault_limit = MAX31785_DEFAULT_OT_FAULT_LIMIT;
+ pmdev->pages[i].ot_warn_limit = MAX31785_DEFAULT_OT_WARN_LIMIT;
+ }
+
+ for (int i = MAX31785_MIN_ADC_VOLTAGE_PAGE;
+ i <= MAX31785_MAX_ADC_VOLTAGE_PAGE;
+ i++) {
+ pmdev->pages[i].vout_mode = MAX31785_DEFAULT_VOUT_MODE;
+ pmdev->pages[i].revision = MAX31785_DEFAULT_PMBUS_REVISION;
+ pmdev->pages[i].vout_scale_monitor =
+ MAX31785_DEFAULT_VOUT_SCALE_MONITOR;
+ pmdev->pages[i].vout_ov_fault_limit = MAX31785_DEFAULT_OV_FAULT_LIMIT;
+ pmdev->pages[i].vout_ov_warn_limit = MAX31785_DEFAULT_OV_WARN_LIMIT;
+ }
+
+ s->mfr_location = MAX31785_DEFAULT_TEXT;
+ s->mfr_date = MAX31785_DEFAULT_TEXT;
+ s->mfr_serial = MAX31785_DEFAULT_TEXT;
+}
+
+static const VMStateDescription vmstate_max31785 = {
+ .name = TYPE_MAX31785,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]){
+ VMSTATE_PMBUS_DEVICE(parent, MAX31785State),
+ VMSTATE_UINT16_ARRAY(mfr_mode, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT16_ARRAY(vout_peak, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT16_ARRAY(temperature_peak, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT16_ARRAY(vout_min, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT8_ARRAY(fault_response, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT32_ARRAY(time_count, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT16_ARRAY(temp_sensor_config, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT16_ARRAY(fan_config, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT16_ARRAY(read_fan_pwm, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT16_ARRAY(fan_fault_limit, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT16_ARRAY(fan_warn_limit, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT16_ARRAY(fan_run_time, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT16_ARRAY(fan_pwm_avg, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT64_ARRAY(fan_pwm2rpm, MAX31785State,
+ MAX31785_TOTAL_NUM_PAGES),
+ VMSTATE_UINT64(mfr_location, MAX31785State),
+ VMSTATE_UINT64(mfr_date, MAX31785State),
+ VMSTATE_UINT64(mfr_serial, MAX31785State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void max31785_init(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+
+ for (int i = MAX31785_MIN_FAN_PAGE; i <= MAX31785_MAX_FAN_PAGE; i++) {
+ pmbus_page_config(pmdev, i, PB_HAS_VOUT_MODE);
+ }
+
+ for (int i = MAX31785_MIN_TEMP_PAGE; i <= MAX31785_MAX_TEMP_PAGE; i++) {
+ pmbus_page_config(pmdev, i, PB_HAS_VOUT_MODE | PB_HAS_TEMPERATURE);
+ }
+
+ for (int i = MAX31785_MIN_ADC_VOLTAGE_PAGE;
+ i <= MAX31785_MAX_ADC_VOLTAGE_PAGE;
+ i++) {
+ pmbus_page_config(pmdev, i, PB_HAS_VOUT_MODE | PB_HAS_VOUT |
+ PB_HAS_VOUT_RATING);
+ }
+}
+
+static void max31785_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 MAX31785 6-Channel Fan Controller";
+ dc->vmsd = &vmstate_max31785;
+ k->write_data = max31785_write_data;
+ k->receive_byte = max31785_read_byte;
+ k->device_num_pages = MAX31785_TOTAL_NUM_PAGES;
+ rc->phases.exit = max31785_exit_reset;
+}
+
+static const TypeInfo max31785_info = {
+ .name = TYPE_MAX31785,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(MAX31785State),
+ .instance_init = max31785_init,
+ .class_init = max31785_class_init,
+};
+
+static void max31785_register_types(void)
+{
+ type_register_static(&max31785_info);
+}
+
+type_init(max31785_register_types)
diff --git a/hw/sensor/max34451.c b/hw/sensor/max34451.c
new file mode 100644
index 00000000..a91d8bd4
--- /dev/null
+++ b/hw/sensor/max34451.c
@@ -0,0 +1,775 @@
+/*
+ * Maxim MAX34451 PMBus 16-Channel V/I monitor and 12-Channel Sequencer/Marginer
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/pmbus_device.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define TYPE_MAX34451 "max34451"
+#define MAX34451(obj) OBJECT_CHECK(MAX34451State, (obj), TYPE_MAX34451)
+
+#define MAX34451_MFR_MODE 0xD1
+#define MAX34451_MFR_PSEN_CONFIG 0xD2
+#define MAX34451_MFR_VOUT_PEAK 0xD4
+#define MAX34451_MFR_IOUT_PEAK 0xD5
+#define MAX34451_MFR_TEMPERATURE_PEAK 0xD6
+#define MAX34451_MFR_VOUT_MIN 0xD7
+#define MAX34451_MFR_NV_LOG_CONFIG 0xD8
+#define MAX34451_MFR_FAULT_RESPONSE 0xD9
+#define MAX34451_MFR_FAULT_RETRY 0xDA
+#define MAX34451_MFR_NV_FAULT_LOG 0xDC
+#define MAX34451_MFR_TIME_COUNT 0xDD
+#define MAX34451_MFR_MARGIN_CONFIG 0xDF
+#define MAX34451_MFR_FW_SERIAL 0xE0
+#define MAX34451_MFR_IOUT_AVG 0xE2
+#define MAX34451_MFR_CHANNEL_CONFIG 0xE4
+#define MAX34451_MFR_TON_SEQ_MAX 0xE6
+#define MAX34451_MFR_PWM_CONFIG 0xE7
+#define MAX34451_MFR_SEQ_CONFIG 0xE8
+#define MAX34451_MFR_STORE_ALL 0xEE
+#define MAX34451_MFR_RESTORE_ALL 0xEF
+#define MAX34451_MFR_TEMP_SENSOR_CONFIG 0xF0
+#define MAX34451_MFR_STORE_SINGLE 0xFC
+#define MAX34451_MFR_CRC 0xFE
+
+#define MAX34451_NUM_MARGINED_PSU 12
+#define MAX34451_NUM_PWR_DEVICES 16
+#define MAX34451_NUM_TEMP_DEVICES 5
+#define MAX34451_NUM_PAGES 21
+
+#define DEFAULT_OP_ON 0x80
+#define DEFAULT_CAPABILITY 0x20
+#define DEFAULT_ON_OFF_CONFIG 0x1a
+#define DEFAULT_VOUT_MODE 0x40
+#define DEFAULT_TEMPERATURE 2500
+#define DEFAULT_SCALE 0x7FFF
+#define DEFAULT_OV_LIMIT 0x7FFF
+#define DEFAULT_OC_LIMIT 0x7FFF
+#define DEFAULT_OT_LIMIT 0x7FFF
+#define DEFAULT_VMIN 0x7FFF
+#define DEFAULT_TON_FAULT_LIMIT 0xFFFF
+#define DEFAULT_CHANNEL_CONFIG 0x20
+#define DEFAULT_TEXT 0x3130313031303130
+
+/**
+ * MAX34451State:
+ * @code: The command code received
+ * @page: Each page corresponds to a device monitored by the Max 34451
+ * The page register determines the available commands depending on device
+ ___________________________________________________________________________
+ | 0 | Power supply monitored by RS0, controlled by PSEN0, and |
+ | | margined with PWM0. |
+ |_______|___________________________________________________________________|
+ | 1 | Power supply monitored by RS1, controlled by PSEN1, and |
+ | | margined with PWM1. |
+ |_______|___________________________________________________________________|
+ | 2 | Power supply monitored by RS2, controlled by PSEN2, and |
+ | | margined with PWM2. |
+ |_______|___________________________________________________________________|
+ | 3 | Power supply monitored by RS3, controlled by PSEN3, and |
+ | | margined with PWM3. |
+ |_______|___________________________________________________________________|
+ | 4 | Power supply monitored by RS4, controlled by PSEN4, and |
+ | | margined with PWM4. |
+ |_______|___________________________________________________________________|
+ | 5 | Power supply monitored by RS5, controlled by PSEN5, and |
+ | | margined with PWM5. |
+ |_______|___________________________________________________________________|
+ | 6 | Power supply monitored by RS6, controlled by PSEN6, and |
+ | | margined with PWM6. |
+ |_______|___________________________________________________________________|
+ | 7 | Power supply monitored by RS7, controlled by PSEN7, and |
+ | | margined with PWM7. |
+ |_______|___________________________________________________________________|
+ | 8 | Power supply monitored by RS8, controlled by PSEN8, and |
+ | | optionally margined by OUT0 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 9 | Power supply monitored by RS9, controlled by PSEN9, and |
+ | | optionally margined by OUT1 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 10 | Power supply monitored by RS10, controlled by PSEN10, and |
+ | | optionally margined by OUT2 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 11 | Power supply monitored by RS11, controlled by PSEN11, and |
+ | | optionally margined by OUT3 of external DS4424 at I2C address A0h.|
+ |_______|___________________________________________________________________|
+ | 12 | ADC channel 12 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 13 | ADC channel 13 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 14 | ADC channel 14 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 15 | ADC channel 15 (monitors voltage or current) or GPI. |
+ |_______|___________________________________________________________________|
+ | 16 | Internal temperature sensor. |
+ |_______|___________________________________________________________________|
+ | 17 | External DS75LV temperature sensor with I2C address 90h. |
+ |_______|___________________________________________________________________|
+ | 18 | External DS75LV temperature sensor with I2C address 92h. |
+ |_______|___________________________________________________________________|
+ | 19 | External DS75LV temperature sensor with I2C address 94h. |
+ |_______|___________________________________________________________________|
+ | 20 | External DS75LV temperature sensor with I2C address 96h. |
+ |_______|___________________________________________________________________|
+ | 21=E2=80=93254| Reserved. |
+ |_______|___________________________________________________________________|
+ | 255 | Applies to all pages. |
+ |_______|___________________________________________________________________|
+ *
+ * @operation: Turn on and off power supplies
+ * @on_off_config: Configure the power supply on and off transition behaviour
+ * @write_protect: protect against changes to the device's memory
+ * @vout_margin_high: the voltage when OPERATION is set to margin high
+ * @vout_margin_low: the voltage when OPERATION is set to margin low
+ * @vout_scale: scale ADC reading to actual device reading if different
+ * @iout_cal_gain: set ratio of the voltage at the ADC input to sensed current
+ */
+typedef struct MAX34451State {
+ PMBusDevice parent;
+
+ uint16_t power_good_on[MAX34451_NUM_PWR_DEVICES];
+ uint16_t power_good_off[MAX34451_NUM_PWR_DEVICES];
+ uint16_t ton_delay[MAX34451_NUM_MARGINED_PSU];
+ uint16_t ton_max_fault_limit[MAX34451_NUM_MARGINED_PSU];
+ uint16_t toff_delay[MAX34451_NUM_MARGINED_PSU];
+ uint8_t status_mfr_specific[MAX34451_NUM_PWR_DEVICES];
+ /* Manufacturer specific function */
+ uint64_t mfr_location;
+ uint64_t mfr_date;
+ uint64_t mfr_serial;
+ uint16_t mfr_mode;
+ uint32_t psen_config[MAX34451_NUM_MARGINED_PSU];
+ uint16_t vout_peak[MAX34451_NUM_PWR_DEVICES];
+ uint16_t iout_peak[MAX34451_NUM_PWR_DEVICES];
+ uint16_t temperature_peak[MAX34451_NUM_TEMP_DEVICES];
+ uint16_t vout_min[MAX34451_NUM_PWR_DEVICES];
+ uint16_t nv_log_config;
+ uint32_t fault_response[MAX34451_NUM_PWR_DEVICES];
+ uint16_t fault_retry;
+ uint32_t fault_log;
+ uint32_t time_count;
+ uint16_t margin_config[MAX34451_NUM_MARGINED_PSU];
+ uint16_t fw_serial;
+ uint16_t iout_avg[MAX34451_NUM_PWR_DEVICES];
+ uint16_t channel_config[MAX34451_NUM_PWR_DEVICES];
+ uint16_t ton_seq_max[MAX34451_NUM_MARGINED_PSU];
+ uint32_t pwm_config[MAX34451_NUM_MARGINED_PSU];
+ uint32_t seq_config[MAX34451_NUM_MARGINED_PSU];
+ uint16_t temp_sensor_config[MAX34451_NUM_TEMP_DEVICES];
+ uint16_t store_single;
+ uint16_t crc;
+} MAX34451State;
+
+
+static void max34451_check_limits(MAX34451State *s)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(s);
+
+ pmbus_check_limits(pmdev);
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ if (pmdev->pages[i].read_vout == 0) { /* PSU disabled */
+ continue;
+ }
+
+ if (pmdev->pages[i].read_vout > s->vout_peak[i]) {
+ s->vout_peak[i] = pmdev->pages[i].read_vout;
+ }
+
+ if (pmdev->pages[i].read_vout < s->vout_min[i]) {
+ s->vout_min[i] = pmdev->pages[i].read_vout;
+ }
+
+ if (pmdev->pages[i].read_iout > s->iout_peak[i]) {
+ s->iout_peak[i] = pmdev->pages[i].read_iout;
+ }
+ }
+
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ if (pmdev->pages[i + 16].read_temperature_1 > s->temperature_peak[i]) {
+ s->temperature_peak[i] = pmdev->pages[i + 16].read_temperature_1;
+ }
+ }
+}
+
+static uint8_t max34451_read_byte(PMBusDevice *pmdev)
+{
+ MAX34451State *s = MAX34451(pmdev);
+ switch (pmdev->code) {
+
+ case PMBUS_POWER_GOOD_ON:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->power_good_on[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_POWER_GOOD_OFF:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->power_good_off[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_TON_DELAY:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->ton_delay[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_TON_MAX_FAULT_LIMIT:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->ton_max_fault_limit[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_TOFF_DELAY:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->toff_delay[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_STATUS_MFR_SPECIFIC:
+ if (pmdev->page < 16) {
+ pmbus_send8(pmdev, s->status_mfr_specific[pmdev->page]);
+ }
+ break;
+
+ case PMBUS_MFR_ID:
+ pmbus_send8(pmdev, 0x4d); /* Maxim */
+ break;
+
+ case PMBUS_MFR_MODEL:
+ pmbus_send8(pmdev, 0x59);
+ break;
+
+ case PMBUS_MFR_LOCATION:
+ pmbus_send64(pmdev, s->mfr_location);
+ break;
+
+ case PMBUS_MFR_DATE:
+ pmbus_send64(pmdev, s->mfr_date);
+ break;
+
+ case PMBUS_MFR_SERIAL:
+ pmbus_send64(pmdev, s->mfr_serial);
+ break;
+
+ case MAX34451_MFR_MODE:
+ pmbus_send16(pmdev, s->mfr_mode);
+ break;
+
+ case MAX34451_MFR_PSEN_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send32(pmdev, s->psen_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_PEAK:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->vout_peak[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_IOUT_PEAK:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->iout_peak[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_TEMPERATURE_PEAK:
+ if (15 < pmdev->page && pmdev->page < 21) {
+ pmbus_send16(pmdev, s->temperature_peak[pmdev->page % 16]);
+ } else {
+ pmbus_send16(pmdev, s->temperature_peak[0]);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_MIN:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->vout_min[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_NV_LOG_CONFIG:
+ pmbus_send16(pmdev, s->nv_log_config);
+ break;
+
+ case MAX34451_MFR_FAULT_RESPONSE:
+ if (pmdev->page < 16) {
+ pmbus_send32(pmdev, s->fault_response[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_FAULT_RETRY:
+ pmbus_send32(pmdev, s->fault_retry);
+ break;
+
+ case MAX34451_MFR_NV_FAULT_LOG:
+ pmbus_send32(pmdev, s->fault_log);
+ break;
+
+ case MAX34451_MFR_TIME_COUNT:
+ pmbus_send32(pmdev, s->time_count);
+ break;
+
+ case MAX34451_MFR_MARGIN_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->margin_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_FW_SERIAL:
+ if (pmdev->page == 255) {
+ pmbus_send16(pmdev, 1); /* Firmware revision */
+ }
+ break;
+
+ case MAX34451_MFR_IOUT_AVG:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->iout_avg[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_CHANNEL_CONFIG:
+ if (pmdev->page < 16) {
+ pmbus_send16(pmdev, s->channel_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_TON_SEQ_MAX:
+ if (pmdev->page < 12) {
+ pmbus_send16(pmdev, s->ton_seq_max[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_PWM_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send32(pmdev, s->pwm_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_SEQ_CONFIG:
+ if (pmdev->page < 12) {
+ pmbus_send32(pmdev, s->seq_config[pmdev->page]);
+ }
+ break;
+
+ case MAX34451_MFR_TEMP_SENSOR_CONFIG:
+ if (15 < pmdev->page && pmdev->page < 21) {
+ pmbus_send32(pmdev, s->temp_sensor_config[pmdev->page % 16]);
+ }
+ break;
+
+ case MAX34451_MFR_STORE_SINGLE:
+ pmbus_send32(pmdev, s->store_single);
+ break;
+
+ case MAX34451_MFR_CRC:
+ pmbus_send32(pmdev, s->crc);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: reading from unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+ return 0xFF;
+}
+
+static int max34451_write_data(PMBusDevice *pmdev, const uint8_t *buf,
+ uint8_t len)
+{
+ MAX34451State *s = MAX34451(pmdev);
+
+ if (len == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: writing empty data\n", __func__);
+ return -1;
+ }
+
+ pmdev->code = buf[0]; /* PMBus command code */
+
+ if (len == 1) {
+ return 0;
+ }
+
+ /* Exclude command code from buffer */
+ buf++;
+ len--;
+ uint8_t index = pmdev->page;
+
+ switch (pmdev->code) {
+ case MAX34451_MFR_STORE_ALL:
+ case MAX34451_MFR_RESTORE_ALL:
+ case MAX34451_MFR_STORE_SINGLE:
+ /*
+ * TODO: hardware behaviour is to move the contents of volatile
+ * memory to non-volatile memory.
+ */
+ break;
+
+ case PMBUS_POWER_GOOD_ON: /* R/W word */
+ if (pmdev->page < MAX34451_NUM_PWR_DEVICES) {
+ s->power_good_on[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_POWER_GOOD_OFF: /* R/W word */
+ if (pmdev->page < MAX34451_NUM_PWR_DEVICES) {
+ s->power_good_off[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_TON_DELAY: /* R/W word */
+ if (pmdev->page < 12) {
+ s->ton_delay[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_TON_MAX_FAULT_LIMIT: /* R/W word */
+ if (pmdev->page < 12) {
+ s->ton_max_fault_limit[pmdev->page]
+ = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_TOFF_DELAY: /* R/W word */
+ if (pmdev->page < 12) {
+ s->toff_delay[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case PMBUS_MFR_LOCATION: /* R/W 64 */
+ s->mfr_location = pmbus_receive64(pmdev);
+ break;
+
+ case PMBUS_MFR_DATE: /* R/W 64 */
+ s->mfr_date = pmbus_receive64(pmdev);
+ break;
+
+ case PMBUS_MFR_SERIAL: /* R/W 64 */
+ s->mfr_serial = pmbus_receive64(pmdev);
+ break;
+
+ case MAX34451_MFR_MODE: /* R/W word */
+ s->mfr_mode = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_PSEN_CONFIG: /* R/W 32 */
+ if (pmdev->page < 12) {
+ s->psen_config[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_PEAK: /* R/W word */
+ if (pmdev->page < 16) {
+ s->vout_peak[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_IOUT_PEAK: /* R/W word */
+ if (pmdev->page < 16) {
+ s->iout_peak[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_TEMPERATURE_PEAK: /* R/W word */
+ if (15 < pmdev->page && pmdev->page < 21) {
+ s->temperature_peak[pmdev->page % 16]
+ = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_VOUT_MIN: /* R/W word */
+ if (pmdev->page < 16) {
+ s->vout_min[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_NV_LOG_CONFIG: /* R/W word */
+ s->nv_log_config = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_FAULT_RESPONSE: /* R/W 32 */
+ if (pmdev->page < 16) {
+ s->fault_response[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_FAULT_RETRY: /* R/W word */
+ s->fault_retry = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_TIME_COUNT: /* R/W 32 */
+ s->time_count = pmbus_receive32(pmdev);
+ break;
+
+ case MAX34451_MFR_MARGIN_CONFIG: /* R/W word */
+ if (pmdev->page < 12) {
+ s->margin_config[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_CHANNEL_CONFIG: /* R/W word */
+ if (pmdev->page < 16) {
+ s->channel_config[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_TON_SEQ_MAX: /* R/W word */
+ if (pmdev->page < 12) {
+ s->ton_seq_max[pmdev->page] = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_PWM_CONFIG: /* R/W 32 */
+ if (pmdev->page < 12) {
+ s->pwm_config[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_SEQ_CONFIG: /* R/W 32 */
+ if (pmdev->page < 12) {
+ s->seq_config[pmdev->page] = pmbus_receive32(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_TEMP_SENSOR_CONFIG: /* R/W word */
+ if (15 < pmdev->page && pmdev->page < 21) {
+ s->temp_sensor_config[pmdev->page % 16]
+ = pmbus_receive16(pmdev);
+ }
+ break;
+
+ case MAX34451_MFR_CRC: /* R/W word */
+ s->crc = pmbus_receive16(pmdev);
+ break;
+
+ case MAX34451_MFR_NV_FAULT_LOG:
+ case MAX34451_MFR_FW_SERIAL:
+ case MAX34451_MFR_IOUT_AVG:
+ /* Read only commands */
+ pmdev->pages[index].status_word |= PMBUS_STATUS_CML;
+ pmdev->pages[index].status_cml |= PB_CML_FAULT_INVALID_DATA;
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to read-only register 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: writing to unsupported register: 0x%02x\n",
+ __func__, pmdev->code);
+ break;
+ }
+
+ return 0;
+}
+
+static void max34451_get(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ visit_type_uint16(v, name, (uint16_t *)opaque, errp);
+}
+
+static void max34451_set(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ MAX34451State *s = MAX34451(obj);
+ uint16_t *internal = opaque;
+ uint16_t value;
+ if (!visit_type_uint16(v, name, &value, errp)) {
+ return;
+ }
+
+ *internal = value;
+ max34451_check_limits(s);
+}
+
+/* used to init uint16_t arrays */
+static inline void *memset_word(void *s, uint16_t c, size_t n)
+{
+ size_t i;
+ uint16_t *p = s;
+
+ for (i = 0; i < n; i++) {
+ p[i] = c;
+ }
+
+ return s;
+}
+
+static void max34451_exit_reset(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ MAX34451State *s = MAX34451(obj);
+ pmdev->capability = DEFAULT_CAPABILITY;
+
+ for (int i = 0; i < MAX34451_NUM_PAGES; i++) {
+ pmdev->pages[i].operation = DEFAULT_OP_ON;
+ pmdev->pages[i].on_off_config = DEFAULT_ON_OFF_CONFIG;
+ pmdev->pages[i].revision = 0x11;
+ pmdev->pages[i].vout_mode = DEFAULT_VOUT_MODE;
+ }
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ pmdev->pages[i].vout_scale_monitor = DEFAULT_SCALE;
+ pmdev->pages[i].vout_ov_fault_limit = DEFAULT_OV_LIMIT;
+ pmdev->pages[i].vout_ov_warn_limit = DEFAULT_OV_LIMIT;
+ pmdev->pages[i].iout_oc_warn_limit = DEFAULT_OC_LIMIT;
+ pmdev->pages[i].iout_oc_fault_limit = DEFAULT_OC_LIMIT;
+ }
+
+ for (int i = 0; i < MAX34451_NUM_MARGINED_PSU; i++) {
+ pmdev->pages[i].ton_max_fault_limit = DEFAULT_TON_FAULT_LIMIT;
+ }
+
+ for (int i = 16; i < MAX34451_NUM_TEMP_DEVICES + 16; i++) {
+ pmdev->pages[i].read_temperature_1 = DEFAULT_TEMPERATURE;
+ pmdev->pages[i].ot_warn_limit = DEFAULT_OT_LIMIT;
+ pmdev->pages[i].ot_fault_limit = DEFAULT_OT_LIMIT;
+ }
+
+ memset_word(s->ton_max_fault_limit, DEFAULT_TON_FAULT_LIMIT,
+ MAX34451_NUM_MARGINED_PSU);
+ memset_word(s->channel_config, DEFAULT_CHANNEL_CONFIG,
+ MAX34451_NUM_PWR_DEVICES);
+ memset_word(s->vout_min, DEFAULT_VMIN, MAX34451_NUM_PWR_DEVICES);
+
+ s->mfr_location = DEFAULT_TEXT;
+ s->mfr_date = DEFAULT_TEXT;
+ s->mfr_serial = DEFAULT_TEXT;
+}
+
+static const VMStateDescription vmstate_max34451 = {
+ .name = TYPE_MAX34451,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]){
+ VMSTATE_PMBUS_DEVICE(parent, MAX34451State),
+ VMSTATE_UINT16_ARRAY(power_good_on, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(power_good_off, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(ton_delay, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16_ARRAY(ton_max_fault_limit, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16_ARRAY(toff_delay, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT8_ARRAY(status_mfr_specific, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT64(mfr_location, MAX34451State),
+ VMSTATE_UINT64(mfr_date, MAX34451State),
+ VMSTATE_UINT64(mfr_serial, MAX34451State),
+ VMSTATE_UINT16(mfr_mode, MAX34451State),
+ VMSTATE_UINT32_ARRAY(psen_config, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16_ARRAY(vout_peak, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(iout_peak, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(temperature_peak, MAX34451State,
+ MAX34451_NUM_TEMP_DEVICES),
+ VMSTATE_UINT16_ARRAY(vout_min, MAX34451State, MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16(nv_log_config, MAX34451State),
+ VMSTATE_UINT32_ARRAY(fault_response, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16(fault_retry, MAX34451State),
+ VMSTATE_UINT32(fault_log, MAX34451State),
+ VMSTATE_UINT32(time_count, MAX34451State),
+ VMSTATE_UINT16_ARRAY(margin_config, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16(fw_serial, MAX34451State),
+ VMSTATE_UINT16_ARRAY(iout_avg, MAX34451State, MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(channel_config, MAX34451State,
+ MAX34451_NUM_PWR_DEVICES),
+ VMSTATE_UINT16_ARRAY(ton_seq_max, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT32_ARRAY(pwm_config, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT32_ARRAY(seq_config, MAX34451State,
+ MAX34451_NUM_MARGINED_PSU),
+ VMSTATE_UINT16_ARRAY(temp_sensor_config, MAX34451State,
+ MAX34451_NUM_TEMP_DEVICES),
+ VMSTATE_UINT16(store_single, MAX34451State),
+ VMSTATE_UINT16(crc, MAX34451State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void max34451_init(Object *obj)
+{
+ PMBusDevice *pmdev = PMBUS_DEVICE(obj);
+ uint64_t psu_flags = PB_HAS_VOUT | PB_HAS_IOUT | PB_HAS_VOUT_MODE |
+ PB_HAS_IOUT_GAIN;
+
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ pmbus_page_config(pmdev, i, psu_flags);
+ }
+
+ for (int i = 0; i < MAX34451_NUM_MARGINED_PSU; i++) {
+ pmbus_page_config(pmdev, i, psu_flags | PB_HAS_VOUT_MARGIN);
+ }
+
+ for (int i = 16; i < MAX34451_NUM_TEMP_DEVICES + 16; i++) {
+ pmbus_page_config(pmdev, i, PB_HAS_TEMPERATURE | PB_HAS_VOUT_MODE);
+ }
+
+ /* get and set the voltage in millivolts, max is 32767 mV */
+ for (int i = 0; i < MAX34451_NUM_PWR_DEVICES; i++) {
+ object_property_add(obj, "vout[*]", "uint16",
+ max34451_get,
+ max34451_set, NULL, &pmdev->pages[i].read_vout);
+ }
+
+ /*
+ * get and set the temperature of the internal temperature sensor in
+ * centidegrees Celcius i.e.: 2500 -> 25.00 C, max is 327.67 C
+ */
+ for (int i = 0; i < MAX34451_NUM_TEMP_DEVICES; i++) {
+ object_property_add(obj, "temperature[*]", "uint16",
+ max34451_get,
+ max34451_set,
+ NULL,
+ &pmdev->pages[i + 16].read_temperature_1);
+ }
+
+}
+
+static void max34451_class_init(ObjectClass *klass, void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PMBusDeviceClass *k = PMBUS_DEVICE_CLASS(klass);
+ dc->desc = "Maxim MAX34451 16-Channel V/I monitor";
+ dc->vmsd = &vmstate_max34451;
+ k->write_data = max34451_write_data;
+ k->receive_byte = max34451_read_byte;
+ k->device_num_pages = MAX34451_NUM_PAGES;
+ rc->phases.exit = max34451_exit_reset;
+}
+
+static const TypeInfo max34451_info = {
+ .name = TYPE_MAX34451,
+ .parent = TYPE_PMBUS_DEVICE,
+ .instance_size = sizeof(MAX34451State),
+ .instance_init = max34451_init,
+ .class_init = max34451_class_init,
+};
+
+static void max34451_register_types(void)
+{
+ type_register_static(&max34451_info);
+}
+
+type_init(max34451_register_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
new file mode 100644
index 00000000..9e9be602
--- /dev/null
+++ b/hw/sensor/meson.build
@@ -0,0 +1,9 @@
+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_DPS310', if_true: files('dps310.c'))
+softmmu_ss.add(when: 'CONFIG_EMC141X', if_true: files('emc141x.c'))
+softmmu_ss.add(when: 'CONFIG_ADM1272', if_true: files('adm1272.c'))
+softmmu_ss.add(when: 'CONFIG_MAX34451', if_true: files('max34451.c'))
+softmmu_ss.add(when: 'CONFIG_LSM303DLHC_MAG', if_true: files('lsm303dlhc_mag.c'))
+softmmu_ss.add(when: 'CONFIG_ISL_PMBUS_VR', if_true: files('isl_pmbus_vr.c'))
+softmmu_ss.add(when: 'CONFIG_MAX31785', if_true: files('max31785.c'))
diff --git a/hw/sensor/tmp105.c b/hw/sensor/tmp105.c
new file mode 100644
index 00000000..20564494
--- /dev/null
+++ b/hw/sensor/tmp105.c
@@ -0,0 +1,328 @@
+/*
+ * Texas Instruments TMP105 temperature sensor.
+ *
+ * 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/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "hw/sensor/tmp105.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/module.h"
+
+static void tmp105_interrupt_update(TMP105State *s)
+{
+ qemu_set_irq(s->pin, s->alarm ^ ((~s->config >> 2) & 1)); /* POL */
+}
+
+static void tmp105_alarm_update(TMP105State *s)
+{
+ if ((s->config >> 0) & 1) { /* SD */
+ if ((s->config >> 7) & 1) /* OS */
+ s->config &= ~(1 << 7); /* OS */
+ else
+ return;
+ }
+
+ if (s->config >> 1 & 1) {
+ /*
+ * TM == 1 : Interrupt mode. We signal Alert when the
+ * temperature rises above T_high, and expect the guest to clear
+ * it (eg by reading a device register).
+ */
+ if (s->detect_falling) {
+ if (s->temperature < s->limit[0]) {
+ s->alarm = 1;
+ s->detect_falling = false;
+ }
+ } else {
+ if (s->temperature >= s->limit[1]) {
+ s->alarm = 1;
+ s->detect_falling = true;
+ }
+ }
+ } else {
+ /*
+ * TM == 0 : Comparator mode. We signal Alert when the temperature
+ * rises above T_high, and stop signalling it when the temperature
+ * falls below T_low.
+ */
+ if (s->detect_falling) {
+ if (s->temperature < s->limit[0]) {
+ s->alarm = 0;
+ s->detect_falling = false;
+ }
+ } else {
+ if (s->temperature >= s->limit[1]) {
+ s->alarm = 1;
+ s->detect_falling = true;
+ }
+ }
+ }
+
+ tmp105_interrupt_update(s);
+}
+
+static void tmp105_get_temperature(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ TMP105State *s = TMP105(obj);
+ int64_t value = s->temperature * 1000 / 256;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+/* Units are 0.001 centigrades relative to 0 C. s->temperature is 8.8
+ * fixed point, so units are 1/256 centigrades. A simple ratio will do.
+ */
+static void tmp105_set_temperature(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ TMP105State *s = TMP105(obj);
+ int64_t temp;
+
+ if (!visit_type_int(v, name, &temp, errp)) {
+ return;
+ }
+ if (temp >= 128000 || temp < -128000) {
+ error_setg(errp, "value %" PRId64 ".%03" PRIu64 " C is out of range",
+ temp / 1000, temp % 1000);
+ return;
+ }
+
+ s->temperature = (int16_t) (temp * 256 / 1000);
+
+ tmp105_alarm_update(s);
+}
+
+static const int tmp105_faultq[4] = { 1, 2, 4, 6 };
+
+static void tmp105_read(TMP105State *s)
+{
+ s->len = 0;
+
+ if ((s->config >> 1) & 1) { /* TM */
+ s->alarm = 0;
+ tmp105_interrupt_update(s);
+ }
+
+ switch (s->pointer & 3) {
+ case TMP105_REG_TEMPERATURE:
+ s->buf[s->len ++] = (((uint16_t) s->temperature) >> 8);
+ s->buf[s->len ++] = (((uint16_t) s->temperature) >> 0) &
+ (0xf0 << ((~s->config >> 5) & 3)); /* R */
+ break;
+
+ case TMP105_REG_CONFIG:
+ s->buf[s->len ++] = s->config;
+ break;
+
+ case TMP105_REG_T_LOW:
+ s->buf[s->len ++] = ((uint16_t) s->limit[0]) >> 8;
+ s->buf[s->len ++] = ((uint16_t) s->limit[0]) >> 0;
+ break;
+
+ case TMP105_REG_T_HIGH:
+ s->buf[s->len ++] = ((uint16_t) s->limit[1]) >> 8;
+ s->buf[s->len ++] = ((uint16_t) s->limit[1]) >> 0;
+ break;
+ }
+}
+
+static void tmp105_write(TMP105State *s)
+{
+ switch (s->pointer & 3) {
+ case TMP105_REG_TEMPERATURE:
+ break;
+
+ case TMP105_REG_CONFIG:
+ if (s->buf[0] & ~s->config & (1 << 0)) /* SD */
+ printf("%s: TMP105 shutdown\n", __func__);
+ s->config = s->buf[0];
+ s->faults = tmp105_faultq[(s->config >> 3) & 3]; /* F */
+ tmp105_alarm_update(s);
+ break;
+
+ case TMP105_REG_T_LOW:
+ case TMP105_REG_T_HIGH:
+ if (s->len >= 3)
+ s->limit[s->pointer & 1] = (int16_t)
+ ((((uint16_t) s->buf[0]) << 8) | s->buf[1]);
+ tmp105_alarm_update(s);
+ break;
+ }
+}
+
+static uint8_t tmp105_rx(I2CSlave *i2c)
+{
+ TMP105State *s = TMP105(i2c);
+
+ if (s->len < 2) {
+ return s->buf[s->len ++];
+ } else {
+ return 0xff;
+ }
+}
+
+static int tmp105_tx(I2CSlave *i2c, uint8_t data)
+{
+ TMP105State *s = TMP105(i2c);
+
+ if (s->len == 0) {
+ s->pointer = data;
+ s->len++;
+ } else {
+ if (s->len <= 2) {
+ s->buf[s->len - 1] = data;
+ }
+ s->len++;
+ tmp105_write(s);
+ }
+
+ return 0;
+}
+
+static int tmp105_event(I2CSlave *i2c, enum i2c_event event)
+{
+ TMP105State *s = TMP105(i2c);
+
+ if (event == I2C_START_RECV) {
+ tmp105_read(s);
+ }
+
+ s->len = 0;
+ return 0;
+}
+
+static int tmp105_post_load(void *opaque, int version_id)
+{
+ TMP105State *s = opaque;
+
+ s->faults = tmp105_faultq[(s->config >> 3) & 3]; /* F */
+
+ tmp105_interrupt_update(s);
+ return 0;
+}
+
+static bool detect_falling_needed(void *opaque)
+{
+ TMP105State *s = opaque;
+
+ /*
+ * We only need to migrate the detect_falling bool if it's set;
+ * for migration from older machines we assume that it is false
+ * (ie temperature is not out of range).
+ */
+ return s->detect_falling;
+}
+
+static const VMStateDescription vmstate_tmp105_detect_falling = {
+ .name = "TMP105/detect-falling",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = detect_falling_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(detect_falling, TMP105State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_tmp105 = {
+ .name = "TMP105",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = tmp105_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(len, TMP105State),
+ VMSTATE_UINT8_ARRAY(buf, TMP105State, 2),
+ VMSTATE_UINT8(pointer, TMP105State),
+ VMSTATE_UINT8(config, TMP105State),
+ VMSTATE_INT16(temperature, TMP105State),
+ VMSTATE_INT16_ARRAY(limit, TMP105State, 2),
+ VMSTATE_UINT8(alarm, TMP105State),
+ VMSTATE_I2C_SLAVE(i2c, TMP105State),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_tmp105_detect_falling,
+ NULL
+ }
+};
+
+static void tmp105_reset(I2CSlave *i2c)
+{
+ TMP105State *s = TMP105(i2c);
+
+ s->temperature = 0;
+ s->pointer = 0;
+ s->config = 0;
+ s->faults = tmp105_faultq[(s->config >> 3) & 3];
+ s->alarm = 0;
+ s->detect_falling = false;
+
+ s->limit[0] = 0x4b00; /* T_LOW, 75 degrees C */
+ s->limit[1] = 0x5000; /* T_HIGH, 80 degrees C */
+
+ tmp105_interrupt_update(s);
+}
+
+static void tmp105_realize(DeviceState *dev, Error **errp)
+{
+ I2CSlave *i2c = I2C_SLAVE(dev);
+ TMP105State *s = TMP105(i2c);
+
+ qdev_init_gpio_out(&i2c->qdev, &s->pin, 1);
+
+ tmp105_reset(&s->i2c);
+}
+
+static void tmp105_initfn(Object *obj)
+{
+ object_property_add(obj, "temperature", "int",
+ tmp105_get_temperature,
+ tmp105_set_temperature, NULL, NULL);
+}
+
+static void tmp105_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ dc->realize = tmp105_realize;
+ k->event = tmp105_event;
+ k->recv = tmp105_rx;
+ k->send = tmp105_tx;
+ dc->vmsd = &vmstate_tmp105;
+}
+
+static const TypeInfo tmp105_info = {
+ .name = TYPE_TMP105,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(TMP105State),
+ .instance_init = tmp105_initfn,
+ .class_init = tmp105_class_init,
+};
+
+static void tmp105_register_types(void)
+{
+ type_register_static(&tmp105_info);
+}
+
+type_init(tmp105_register_types)
diff --git a/hw/sensor/tmp421.c b/hw/sensor/tmp421.c
new file mode 100644
index 00000000..a3db57dc
--- /dev/null
+++ b/hw/sensor/tmp421.c
@@ -0,0 +1,391 @@
+/*
+ * Texas Instruments TMP421 temperature sensor.
+ *
+ * Copyright (c) 2016 IBM Corporation.
+ *
+ * Largely inspired by :
+ *
+ * Texas Instruments TMP105 temperature sensor.
+ *
+ * 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/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+/* Manufacturer / Device ID's */
+#define TMP421_MANUFACTURER_ID 0x55
+#define TMP421_DEVICE_ID 0x21
+#define TMP422_DEVICE_ID 0x22
+#define TMP423_DEVICE_ID 0x23
+
+typedef struct DeviceInfo {
+ int model;
+ const char *name;
+} DeviceInfo;
+
+static const DeviceInfo devices[] = {
+ { TMP421_DEVICE_ID, "tmp421" },
+ { TMP422_DEVICE_ID, "tmp422" },
+ { TMP423_DEVICE_ID, "tmp423" },
+};
+
+struct TMP421State {
+ /*< private >*/
+ I2CSlave i2c;
+ /*< public >*/
+
+ int16_t temperature[4];
+
+ uint8_t status;
+ uint8_t config[2];
+ uint8_t rate;
+
+ uint8_t len;
+ uint8_t buf[2];
+ uint8_t pointer;
+
+};
+
+struct TMP421Class {
+ I2CSlaveClass parent_class;
+ DeviceInfo *dev;
+};
+
+#define TYPE_TMP421 "tmp421-generic"
+OBJECT_DECLARE_TYPE(TMP421State, TMP421Class, TMP421)
+
+
+/* the TMP421 registers */
+#define TMP421_STATUS_REG 0x08
+#define TMP421_STATUS_BUSY (1 << 7)
+#define TMP421_CONFIG_REG_1 0x09
+#define TMP421_CONFIG_RANGE (1 << 2)
+#define TMP421_CONFIG_SHUTDOWN (1 << 6)
+#define TMP421_CONFIG_REG_2 0x0A
+#define TMP421_CONFIG_RC (1 << 2)
+#define TMP421_CONFIG_LEN (1 << 3)
+#define TMP421_CONFIG_REN (1 << 4)
+#define TMP421_CONFIG_REN2 (1 << 5)
+#define TMP421_CONFIG_REN3 (1 << 6)
+
+#define TMP421_CONVERSION_RATE_REG 0x0B
+#define TMP421_ONE_SHOT 0x0F
+
+#define TMP421_RESET 0xFC
+#define TMP421_MANUFACTURER_ID_REG 0xFE
+#define TMP421_DEVICE_ID_REG 0xFF
+
+#define TMP421_TEMP_MSB0 0x00
+#define TMP421_TEMP_MSB1 0x01
+#define TMP421_TEMP_MSB2 0x02
+#define TMP421_TEMP_MSB3 0x03
+#define TMP421_TEMP_LSB0 0x10
+#define TMP421_TEMP_LSB1 0x11
+#define TMP421_TEMP_LSB2 0x12
+#define TMP421_TEMP_LSB3 0x13
+
+static const int32_t mins[2] = { -40000, -55000 };
+static const int32_t maxs[2] = { 127000, 150000 };
+
+static void tmp421_get_temperature(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ TMP421State *s = TMP421(obj);
+ bool ext_range = (s->config[0] & TMP421_CONFIG_RANGE);
+ int offset = ext_range * 64 * 256;
+ int64_t value;
+ int tempid;
+
+ if (sscanf(name, "temperature%d", &tempid) != 1) {
+ error_setg(errp, "error reading %s: %s", name, g_strerror(errno));
+ return;
+ }
+
+ if (tempid >= 4 || tempid < 0) {
+ error_setg(errp, "error reading %s", name);
+ return;
+ }
+
+ value = ((s->temperature[tempid] - offset) * 1000 + 128) / 256;
+
+ visit_type_int(v, name, &value, errp);
+}
+
+/* Units are 0.001 centigrades relative to 0 C. s->temperature is 8.8
+ * fixed point, so units are 1/256 centigrades. A simple ratio will do.
+ */
+static void tmp421_set_temperature(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ TMP421State *s = TMP421(obj);
+ int64_t temp;
+ bool ext_range = (s->config[0] & TMP421_CONFIG_RANGE);
+ int offset = ext_range * 64 * 256;
+ int tempid;
+
+ if (!visit_type_int(v, name, &temp, errp)) {
+ return;
+ }
+
+ if (temp >= maxs[ext_range] || temp < mins[ext_range]) {
+ error_setg(errp, "value %" PRId64 ".%03" PRIu64 " C is out of range",
+ temp / 1000, temp % 1000);
+ return;
+ }
+
+ if (sscanf(name, "temperature%d", &tempid) != 1) {
+ error_setg(errp, "error reading %s: %s", name, g_strerror(errno));
+ return;
+ }
+
+ if (tempid >= 4 || tempid < 0) {
+ error_setg(errp, "error reading %s", name);
+ return;
+ }
+
+ s->temperature[tempid] = (int16_t) ((temp * 256 - 128) / 1000) + offset;
+}
+
+static void tmp421_read(TMP421State *s)
+{
+ TMP421Class *sc = TMP421_GET_CLASS(s);
+
+ s->len = 0;
+
+ switch (s->pointer) {
+ case TMP421_MANUFACTURER_ID_REG:
+ s->buf[s->len++] = TMP421_MANUFACTURER_ID;
+ break;
+ case TMP421_DEVICE_ID_REG:
+ s->buf[s->len++] = sc->dev->model;
+ break;
+ case TMP421_CONFIG_REG_1:
+ s->buf[s->len++] = s->config[0];
+ break;
+ case TMP421_CONFIG_REG_2:
+ s->buf[s->len++] = s->config[1];
+ break;
+ case TMP421_CONVERSION_RATE_REG:
+ s->buf[s->len++] = s->rate;
+ break;
+ case TMP421_STATUS_REG:
+ s->buf[s->len++] = s->status;
+ break;
+
+ /* FIXME: check for channel enablement in config registers */
+ case TMP421_TEMP_MSB0:
+ s->buf[s->len++] = (((uint16_t) s->temperature[0]) >> 8);
+ s->buf[s->len++] = (((uint16_t) s->temperature[0]) >> 0) & 0xf0;
+ break;
+ case TMP421_TEMP_MSB1:
+ s->buf[s->len++] = (((uint16_t) s->temperature[1]) >> 8);
+ s->buf[s->len++] = (((uint16_t) s->temperature[1]) >> 0) & 0xf0;
+ break;
+ case TMP421_TEMP_MSB2:
+ s->buf[s->len++] = (((uint16_t) s->temperature[2]) >> 8);
+ s->buf[s->len++] = (((uint16_t) s->temperature[2]) >> 0) & 0xf0;
+ break;
+ case TMP421_TEMP_MSB3:
+ s->buf[s->len++] = (((uint16_t) s->temperature[3]) >> 8);
+ s->buf[s->len++] = (((uint16_t) s->temperature[3]) >> 0) & 0xf0;
+ break;
+ case TMP421_TEMP_LSB0:
+ s->buf[s->len++] = (((uint16_t) s->temperature[0]) >> 0) & 0xf0;
+ break;
+ case TMP421_TEMP_LSB1:
+ s->buf[s->len++] = (((uint16_t) s->temperature[1]) >> 0) & 0xf0;
+ break;
+ case TMP421_TEMP_LSB2:
+ s->buf[s->len++] = (((uint16_t) s->temperature[2]) >> 0) & 0xf0;
+ break;
+ case TMP421_TEMP_LSB3:
+ s->buf[s->len++] = (((uint16_t) s->temperature[3]) >> 0) & 0xf0;
+ break;
+ }
+}
+
+static void tmp421_reset(I2CSlave *i2c);
+
+static void tmp421_write(TMP421State *s)
+{
+ switch (s->pointer) {
+ case TMP421_CONVERSION_RATE_REG:
+ s->rate = s->buf[0];
+ break;
+ case TMP421_CONFIG_REG_1:
+ s->config[0] = s->buf[0];
+ break;
+ case TMP421_CONFIG_REG_2:
+ s->config[1] = s->buf[0];
+ break;
+ case TMP421_RESET:
+ tmp421_reset(I2C_SLAVE(s));
+ break;
+ }
+}
+
+static uint8_t tmp421_rx(I2CSlave *i2c)
+{
+ TMP421State *s = TMP421(i2c);
+
+ if (s->len < 2) {
+ return s->buf[s->len++];
+ } else {
+ return 0xff;
+ }
+}
+
+static int tmp421_tx(I2CSlave *i2c, uint8_t data)
+{
+ TMP421State *s = TMP421(i2c);
+
+ if (s->len == 0) {
+ /* first byte is the register pointer for a read or write
+ * operation */
+ s->pointer = data;
+ s->len++;
+ } else if (s->len == 1) {
+ /* second byte is the data to write. The device only supports
+ * one byte writes */
+ s->buf[0] = data;
+ tmp421_write(s);
+ }
+
+ return 0;
+}
+
+static int tmp421_event(I2CSlave *i2c, enum i2c_event event)
+{
+ TMP421State *s = TMP421(i2c);
+
+ if (event == I2C_START_RECV) {
+ tmp421_read(s);
+ }
+
+ s->len = 0;
+ return 0;
+}
+
+static const VMStateDescription vmstate_tmp421 = {
+ .name = "TMP421",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(len, TMP421State),
+ VMSTATE_UINT8_ARRAY(buf, TMP421State, 2),
+ VMSTATE_UINT8(pointer, TMP421State),
+ VMSTATE_UINT8_ARRAY(config, TMP421State, 2),
+ VMSTATE_UINT8(status, TMP421State),
+ VMSTATE_UINT8(rate, TMP421State),
+ VMSTATE_INT16_ARRAY(temperature, TMP421State, 4),
+ VMSTATE_I2C_SLAVE(i2c, TMP421State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void tmp421_reset(I2CSlave *i2c)
+{
+ TMP421State *s = TMP421(i2c);
+ TMP421Class *sc = TMP421_GET_CLASS(s);
+
+ memset(s->temperature, 0, sizeof(s->temperature));
+ s->pointer = 0;
+
+ s->config[0] = 0; /* TMP421_CONFIG_RANGE */
+
+ /* resistance correction and channel enablement */
+ switch (sc->dev->model) {
+ case TMP421_DEVICE_ID:
+ s->config[1] = 0x1c;
+ break;
+ case TMP422_DEVICE_ID:
+ s->config[1] = 0x3c;
+ break;
+ case TMP423_DEVICE_ID:
+ s->config[1] = 0x7c;
+ break;
+ }
+
+ s->rate = 0x7; /* 8Hz */
+ s->status = 0;
+}
+
+static void tmp421_realize(DeviceState *dev, Error **errp)
+{
+ TMP421State *s = TMP421(dev);
+
+ tmp421_reset(&s->i2c);
+}
+
+static void tmp421_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+ TMP421Class *sc = TMP421_CLASS(klass);
+
+ dc->realize = tmp421_realize;
+ k->event = tmp421_event;
+ k->recv = tmp421_rx;
+ k->send = tmp421_tx;
+ dc->vmsd = &vmstate_tmp421;
+ sc->dev = (DeviceInfo *) data;
+
+ object_class_property_add(klass, "temperature0", "int",
+ tmp421_get_temperature,
+ tmp421_set_temperature, NULL, NULL);
+ object_class_property_add(klass, "temperature1", "int",
+ tmp421_get_temperature,
+ tmp421_set_temperature, NULL, NULL);
+ object_class_property_add(klass, "temperature2", "int",
+ tmp421_get_temperature,
+ tmp421_set_temperature, NULL, NULL);
+ object_class_property_add(klass, "temperature3", "int",
+ tmp421_get_temperature,
+ tmp421_set_temperature, NULL, NULL);
+}
+
+static const TypeInfo tmp421_info = {
+ .name = TYPE_TMP421,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(TMP421State),
+ .class_size = sizeof(TMP421Class),
+ .abstract = true,
+};
+
+static void tmp421_register_types(void)
+{
+ int i;
+
+ type_register_static(&tmp421_info);
+ for (i = 0; i < ARRAY_SIZE(devices); ++i) {
+ TypeInfo ti = {
+ .name = devices[i].name,
+ .parent = TYPE_TMP421,
+ .class_init = tmp421_class_init,
+ .class_data = (void *) &devices[i],
+ };
+ type_register(&ti);
+ }
+}
+
+type_init(tmp421_register_types)