* [PATCH v4 0/5] initial support for yosemite v4
@ 2026-05-15 14:57 Alexander Hansen
2026-05-15 14:57 ` [PATCH v4 1/5] ast2600: yosemite4 initial support Alexander Hansen
` (4 more replies)
0 siblings, 5 replies; 8+ messages in thread
From: Alexander Hansen @ 2026-05-15 14:57 UTC (permalink / raw)
To: qemu-devel; +Cc: Alexander Hansen
Add initial and incomplete support for yosemite v4 BMC-side emulation.
As part of this effort 3 new sensor ICs are added.
Thanks to Cedric and Titus for review of patchset 3.
Summary of changes from patchset 3:
- changed commit message for functional test to be more clear
- amended my .gitconfig to present changes matching qemu orderfil
- max31790: fix indent to 4 spaces
- max31790: removed extra header file from patch
- max11615: rename variable to channel_select to match qemu coding style
- max11615: fix typo
- all sensors in this series: fix reset handling. Set dc->legacy_reset
instead of manually calling reset function in realize.
I was mildly confused because both tmp105 and tmp421 call a reset
function in their realize functions. Anyways, hopefully i did it right
this time :)
Alexander Hansen (5):
ast2600: yosemite4 initial support
ast2600: yosemite4 functional test
hw/sensor: MAX31790 support
hw/sensor: support MAX11615
hw/sensor: support Texas Instruments ADC128D818
MAINTAINERS | 3 +
include/hw/sensor/adc128d818.h | 20 +
include/hw/sensor/max11615.h | 21 +
hw/arm/aspeed_ast2600_fby4.c | 280 +++++++++++++
hw/sensor/adc128d818.c | 410 +++++++++++++++++++
hw/sensor/max11615.c | 206 ++++++++++
hw/sensor/max31790.c | 501 +++++++++++++++++++++++
hw/arm/Kconfig | 3 +
hw/arm/meson.build | 1 +
hw/sensor/Kconfig | 12 +
hw/sensor/meson.build | 3 +
hw/sensor/trace-events | 24 ++
tests/functional/arm/meson.build | 2 +
tests/functional/arm/test_aspeed_fby4.py | 80 ++++
14 files changed, 1566 insertions(+)
create mode 100644 include/hw/sensor/adc128d818.h
create mode 100644 include/hw/sensor/max11615.h
create mode 100644 hw/arm/aspeed_ast2600_fby4.c
create mode 100644 hw/sensor/adc128d818.c
create mode 100644 hw/sensor/max11615.c
create mode 100644 hw/sensor/max31790.c
create mode 100755 tests/functional/arm/test_aspeed_fby4.py
--
2.54.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v4 1/5] ast2600: yosemite4 initial support
2026-05-15 14:57 [PATCH v4 0/5] initial support for yosemite v4 Alexander Hansen
@ 2026-05-15 14:57 ` Alexander Hansen
2026-05-15 16:30 ` Cédric Le Goater
2026-05-15 14:57 ` [PATCH v4 2/5] ast2600: yosemite4 functional test Alexander Hansen
` (3 subsequent siblings)
4 siblings, 1 reply; 8+ messages in thread
From: Alexander Hansen @ 2026-05-15 14:57 UTC (permalink / raw)
To: qemu-devel
Cc: Alexander Hansen, Titus Rwantare, Cédric Le Goater,
Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, qemu-arm
Initial patch based on [1] to support yosemite v4 bmc emulation.
The goal of this machine support is to support OpenBMC development.
Reference linux devicetree from openbmc linux is [2].
Status:
- Enclosure FRU: showing up
- Management Board FRU: showing up
- Blade Board FRU: showing up
- Blade Board sensors: not implemented
- Blade Chassis FRU: showing up
- Fan Board FRU: both showing up
- Fan Board sensors: supported
Overall the emulation is incomplete but already helpful in development
and testing.
The focus of this initial support is on the FRU eeproms and fanboard
sensors.
Tested: booted an OpenBMC image for yosemite4 target.
References:
[1] https://github.com/9elements/qemu/commit/32139f913c2bd0ebe4bd26c46765861f6f9f2d49
[2] arch/arm/boot/dts/aspeed/aspeed-bmc-facebook-yosemite4.dts
[3] https://github.com/legoater/qemu-aspeed-boot/pull/5
Cc: Titus Rwantare <titusr@google.com>
Cc: "Cédric Le Goater" <clg@kaod.org> (maintainer:ASPEED BMCs)
Cc: Peter Maydell <peter.maydell@linaro.org> (maintainer:ASPEED BMCs)
Cc: Steven Lee <steven_lee@aspeedtech.com> (reviewer:ASPEED BMCs)
Cc: Troy Lee <leetroy@gmail.com> (reviewer:ASPEED BMCs)
Cc: Jamin Lin <jamin_lin@aspeedtech.com> (reviewer:ASPEED BMCs)
Cc: Kane Chen <kane_chen@aspeedtech.com> (reviewer:ASPEED BMCs)
Cc: Andrew Jeffery <andrew@codeconstruct.com.au> (reviewer:ASPEED BMCs)
Cc: Joel Stanley <joel@jms.id.au> (reviewer:ASPEED BMCs)
Cc: qemu-arm@nongnu.org (open list:ASPEED BMCs)
Cc: qemu-devel@nongnu.org (open list:All patches CC here)
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
---
hw/arm/aspeed_ast2600_fby4.c | 267 +++++++++++++++++++++++++++++++++++
hw/arm/meson.build | 1 +
2 files changed, 268 insertions(+)
create mode 100644 hw/arm/aspeed_ast2600_fby4.c
diff --git a/hw/arm/aspeed_ast2600_fby4.c b/hw/arm/aspeed_ast2600_fby4.c
new file mode 100644
index 0000000000..89fb0ac2fd
--- /dev/null
+++ b/hw/arm/aspeed_ast2600_fby4.c
@@ -0,0 +1,267 @@
+/*
+ * Facebook Yosemite V4
+ *
+ * Copyright 2026 9elements.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/arm/machines-qom.h"
+#include "hw/arm/aspeed.h"
+#include "hw/arm/aspeed_soc.h"
+#include "hw/nvram/eeprom_at24c.h"
+#include "hw/i2c/i2c_mux_pca954x.h"
+#include "hw/gpio/pca9552.h"
+
+#define FBY4_BMC_RAM_SIZE ASPEED_RAM_SIZE(2 * GiB)
+
+/* START OF EEPROM CONTENTS */
+
+/*
+ *./frugen -s board.mfg="Wiwynn" \
+ * --set board.pname="Fan Board FSC-MAX ADC-TI LED-NXP EFUSE-MAX" \
+ * --set text:board.pn="BRD-PN-345" \
+ * --board-date "10/1/2017 12:58:00" \
+ * --set board.serial="123456" \
+ * --set product.pname="Yosemite V4" \
+ * --set product.mfg="Wiwynn" \
+ * --set product.ver="v1.1" \
+ * --set product.serial="123456" \
+ * --set product.atag="PLACEHOLDER" \
+ * --set product.custom="Fanboard Custom1" \
+ * --set product.custom.1="Fanboard Custom2" \
+ * --set text:product.pn="PN-345" \
+ * fru-yv4-fanboard.bin
+ */
+/* EM Config: yosemite4_fanboard_fsc_max_adc_ti_led_nxp_ons_efuse_max.json */
+/* Yosemite4 fan board */
+static const uint8_t fru_yv4_fanboard_bin[] = {
+ 0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a, 0x19, 0xce,
+ 0xc2, 0xa8, 0xc6, 0x57, 0x69, 0x77, 0x79, 0x6e, 0x6e, 0xea, 0x46, 0x61,
+ 0x6e, 0x20, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x46, 0x53, 0x43, 0x2d,
+ 0x4d, 0x41, 0x58, 0x20, 0x41, 0x44, 0x43, 0x2d, 0x54, 0x49, 0x20, 0x4c,
+ 0x45, 0x44, 0x2d, 0x4e, 0x58, 0x50, 0x20, 0x45, 0x46, 0x55, 0x53, 0x45,
+ 0x2d, 0x4d, 0x41, 0x58, 0x85, 0x91, 0x34, 0x51, 0x95, 0x05, 0xca, 0x42,
+ 0x52, 0x44, 0x2d, 0x50, 0x4e, 0x2d, 0x33, 0x34, 0x35, 0xc0, 0xc1, 0x00,
+ 0x00, 0x00, 0x00, 0xdb, 0x01, 0x09, 0x19, 0xc6, 0x57, 0x69, 0x77, 0x79,
+ 0x6e, 0x6e, 0xcb, 0x59, 0x6f, 0x73, 0x65, 0x6d, 0x69, 0x74, 0x65, 0x20,
+ 0x56, 0x34, 0xc6, 0x50, 0x4e, 0x2d, 0x33, 0x34, 0x35, 0xc4, 0x76, 0x31,
+ 0x2e, 0x31, 0x85, 0x91, 0x34, 0x51, 0x95, 0x05, 0x89, 0x30, 0x1b, 0x8e,
+ 0x25, 0xfa, 0xb2, 0x64, 0x29, 0x03, 0xc0, 0xd0, 0x46, 0x61, 0x6e, 0x62,
+ 0x6f, 0x61, 0x72, 0x64, 0x20, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x32,
+ 0xc1, 0x00, 0x00, 0x9d
+};
+static const size_t fru_yv4_fanboard_bin_len = sizeof(fru_yv4_fanboard_bin);
+
+/*
+ *./frugen -s board.mfg="Wiwynn" \
+ * --set board.pname="Sentinel Dome without Retimer" \
+ * --set text:board.pn="BRD-PN-345" \
+ * --board-date "10/1/2017 12:58:00" \
+ * --set board.serial="123456" \
+ * --set product.pname="Yosemite V4" \
+ * --set product.mfg="Wiwynn" \
+ * --set product.ver="v1.1" \
+ * --set product.serial="123456" \
+ * --set product.atag="PLACEHOLDER" \
+ * --set product.custom="Yosemite V4 T1" \
+ * --set product.custom.1="Yosemite V4 T1" \
+ * --set text:product.pn="PN-345" \
+ * fru-yv4-sentineldome-board.bin
+ */
+/* product.cust maps to PRODUCT_INFO_AM2 */
+/* EM Config: yosemite4_sentineldome_t1.json */
+/* Yosemite4 Sentinel Dome without Retimer Boards */
+static const uint8_t fru_yv4_sentineldome_board_bin[] = {
+ 0x01, 0x00, 0x00, 0x01, 0x09, 0x00, 0x00, 0xf5, 0x01, 0x08, 0x19, 0xce,
+ 0xc2, 0xa8, 0xc6, 0x57, 0x69, 0x77, 0x79, 0x6e, 0x6e, 0xdd, 0x53, 0x65,
+ 0x6e, 0x74, 0x69, 0x6e, 0x65, 0x6c, 0x20, 0x44, 0x6f, 0x6d, 0x65, 0x20,
+ 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x52, 0x65, 0x74, 0x69,
+ 0x6d, 0x65, 0x72, 0x85, 0x91, 0x34, 0x51, 0x95, 0x05, 0xca, 0x42, 0x52,
+ 0x44, 0x2d, 0x50, 0x4e, 0x2d, 0x33, 0x34, 0x35, 0xc0, 0xc1, 0x00, 0x78,
+ 0x01, 0x09, 0x19, 0xc6, 0x57, 0x69, 0x77, 0x79, 0x6e, 0x6e, 0xcb, 0x59,
+ 0x6f, 0x73, 0x65, 0x6d, 0x69, 0x74, 0x65, 0x20, 0x56, 0x34, 0xc6, 0x50,
+ 0x4e, 0x2d, 0x33, 0x34, 0x35, 0xc4, 0x76, 0x31, 0x2e, 0x31, 0x85, 0x91,
+ 0x34, 0x51, 0x95, 0x05, 0x89, 0x30, 0x1b, 0x8e, 0x25, 0xfa, 0xb2, 0x64,
+ 0x29, 0x03, 0xc0, 0xce, 0x59, 0x6f, 0x73, 0x65, 0x6d, 0x69, 0x74, 0x65,
+ 0x20, 0x56, 0x34, 0x20, 0x54, 0x31, 0xc1, 0x00, 0x00, 0x00, 0x00, 0xeb
+};
+static const size_t fru_yv4_sentineldome_board_bin_len =
+ sizeof(fru_yv4_sentineldome_board_bin);
+
+/*
+ *./frugen -s board.mfg="Wiwynn" \
+ * --set board.pname="Sentinel Dome" \
+ * --set text:board.pn="BRD-PN-345" \
+ * --board-date "10/1/2017 12:58:00" \
+ * --set board.serial="123456" \
+ * --set product.pname="Yosemite V4" \
+ * --set product.mfg="Wiwynn" \
+ * --set product.ver="v1.1" \
+ * --set product.serial="123456" \
+ * --set product.atag="PLACEHOLDER" \
+ * --set text:product.pn="PN-345" \
+ * fru-yv4-sentineldome-chassis.bin
+ */
+/* EM Config: yosemite4_sentineldome_chassis.json */
+/* Yosemite 4 Sentinel Dome Chassis FRU */
+static const uint8_t fru_yv4_sentineldome_chassis_bin[] = {
+ 0x01, 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0xf7, 0x01, 0x06, 0x19, 0xce,
+ 0xc2, 0xa8, 0xc6, 0x57, 0x69, 0x77, 0x79, 0x6e, 0x6e, 0xcd, 0x53, 0x65,
+ 0x6e, 0x74, 0x69, 0x6e, 0x65, 0x6c, 0x20, 0x44, 0x6f, 0x6d, 0x65, 0x85,
+ 0x91, 0x34, 0x51, 0x95, 0x05, 0xca, 0x42, 0x52, 0x44, 0x2d, 0x50, 0x4e,
+ 0x2d, 0x33, 0x34, 0x35, 0xc0, 0xc1, 0x00, 0xb6, 0x01, 0x07, 0x19, 0xc6,
+ 0x57, 0x69, 0x77, 0x79, 0x6e, 0x6e, 0xcb, 0x59, 0x6f, 0x73, 0x65, 0x6d,
+ 0x69, 0x74, 0x65, 0x20, 0x56, 0x34, 0xc6, 0x50, 0x4e, 0x2d, 0x33, 0x34,
+ 0x35, 0xc4, 0x76, 0x31, 0x2e, 0x31, 0x85, 0x91, 0x34, 0x51, 0x95, 0x05,
+ 0x89, 0x30, 0x1b, 0x8e, 0x25, 0xfa, 0xb2, 0x64, 0x29, 0x03, 0xc0, 0xc1,
+ 0x00, 0x00, 0x00, 0x59
+};
+static const size_t fru_yv4_sentineldome_chassis_bin_len =
+ sizeof(fru_yv4_sentineldome_chassis_bin);
+
+/*
+ *./frugen -s board.mfg="Wiwynn" \
+ * --set board.pname="Management Board wBMC" \
+ * --set text:board.pn="BRD-PN-345" \
+ * --board-date "10/1/2017 12:58:00" \
+ * --set board.serial="123456" \
+ * --set product.pname="Yosemite V4" \
+ * --set product.mfg="Wiwynn" \
+ * --set product.ver="v1.1" \
+ * --set product.serial="123456" \
+ * --set product.atag="PLACEHOLDER" \
+ * --set text:product.pn="PN-345" \
+ * fru-yv4-eclosure.bin
+ */
+/* EM Config: yosemite4_chassis.json, yosemite4.json */
+/* Yosemite 4 Sentinel Dome Enclosure FRU */
+static const uint8_t fru_yv4_eclosure_bin[] = {
+ 0x01, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0xf6, 0x01, 0x07, 0x19, 0xce,
+ 0xc2, 0xa8, 0xc6, 0x57, 0x69, 0x77, 0x79, 0x6e, 0x6e, 0xd5, 0x4d, 0x61,
+ 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x42, 0x6f, 0x61,
+ 0x72, 0x64, 0x20, 0x77, 0x42, 0x4d, 0x43, 0x85, 0x91, 0x34, 0x51, 0x95,
+ 0x05, 0xca, 0x42, 0x52, 0x44, 0x2d, 0x50, 0x4e, 0x2d, 0x33, 0x34, 0x35,
+ 0xc0, 0xc1, 0x00, 0x26, 0x01, 0x07, 0x19, 0xc6, 0x57, 0x69, 0x77, 0x79,
+ 0x6e, 0x6e, 0xcb, 0x59, 0x6f, 0x73, 0x65, 0x6d, 0x69, 0x74, 0x65, 0x20,
+ 0x56, 0x34, 0xc6, 0x50, 0x4e, 0x2d, 0x33, 0x34, 0x35, 0xc4, 0x76, 0x31,
+ 0x2e, 0x31, 0x85, 0x91, 0x34, 0x51, 0x95, 0x05, 0x89, 0x30, 0x1b, 0x8e,
+ 0x25, 0xfa, 0xb2, 0x64, 0x29, 0x03, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0x59
+};
+static const size_t fru_yv4_eclosure_bin_len = sizeof(fru_yv4_eclosure_bin);
+
+/* END OF EEPROM CONTENTS */
+
+static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize)
+{
+ /* 2 fan boards */
+ for (int i = 0; i <= 1; i++) {
+ /* downstream bus */
+ I2CBus *bus = pca954x_i2c_get_bus(fan_mux, i);
+
+ /* ti,adc128d818 @ 0x1f (adc) */
+ /* TODO */
+
+ /* maxim,max31790 @ 0x20 (pwm) */
+ /* TODO */
+
+ /*
+ * ti,tca6424 @ 0x22 (gpio)
+ * linux handles tca6424 with PCA953X_TYPE, same as pca9535
+ * { "pca9535", 16 | PCA953X_TYPE | PCA_INT, },
+ * { "tca6424", 24 | PCA953X_TYPE | PCA_INT, },
+ * so we _could_ be fine here unless more than 16 gpios are used
+ */
+ i2c_slave_create_simple(bus, TYPE_PCA9535, 0x22);
+
+ /*
+ * NOTE: above works and could use for gpio presence:
+ * $ gpioget gpiochip2 2
+ * 1
+ */
+
+ /* maxim,max31790 @ 0x2f (pwm) */
+ /* TODO */
+
+ /* maxim,max11615 @ 0x33 (adc) */
+ /* TODO */
+
+ at24c_eeprom_init_rom(
+ bus, 0x52, eepromSize,
+ fru_yv4_fanboard_bin,
+ fru_yv4_fanboard_bin_len);
+
+ /* LED blink driver / gpio expander */
+ /* nxp,pca9552 @ 0x61 (gpio) */
+ i2c_slave_create_simple(bus, TYPE_PCA9552, 0x61);
+ }
+}
+
+static void fby4_i2c_init_blade_chassis(I2CBus *bus, size_t eepromSize)
+{
+ /* Sentinel Dome Blade EEPROMS */
+
+ /* Board */
+ at24c_eeprom_init_rom(bus, 0x54, eepromSize,
+ fru_yv4_sentineldome_board_bin, fru_yv4_sentineldome_board_bin_len);
+
+ /* Chassis */
+ at24c_eeprom_init_rom(bus, 0x55, eepromSize,
+ fru_yv4_sentineldome_chassis_bin, fru_yv4_sentineldome_chassis_bin_len);
+}
+
+static void fby4_i2c_init_multiple_blade_chassis(I2CBus **i2c,
+ size_t eepromSize)
+{
+ /* there is 8 blade chassis, but we only emulate 2 for performance reason */
+ for (int bus = 1; bus <= (8 / 4); bus++) {
+ fby4_i2c_init_blade_chassis(i2c[bus], eepromSize);
+ }
+}
+
+static void fby4_i2c_init(AspeedMachineState *bmc)
+{
+ AspeedSoCState *soc = bmc->soc;
+ I2CBus *i2c[16];
+
+ for (int i = 0; i < 16; i++) {
+ i2c[i] = aspeed_i2c_get_bus(&soc->i2c, i);
+ }
+
+ /* Enclosure (EM Config: yosemite4_chassis.json, yosemite4.json) */
+ at24c_eeprom_init_rom(i2c[1], 0x51, 128 * KiB,
+ fru_yv4_eclosure_bin, fru_yv4_eclosure_bin_len);
+
+ fby4_i2c_init_multiple_blade_chassis(i2c, 128 * KiB);
+
+ /* Yv4 fanboard connection */
+ I2CSlave *fan_mux = i2c_slave_create_simple(i2c[14], TYPE_PCA9546, 0x74);
+
+ fby4_i2c_init_fanboard(fan_mux, 128 * KiB);
+}
+
+static void aspeed_machine_fby4_class_init(ObjectClass *oc, const void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+ AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc);
+
+ mc->desc = "Facebook fby4 BMC (Cortex-A7)";
+ amc->fmc_model = "mx66l1g45g";
+ amc->num_cs = 2;
+ amc->macs_mask = ASPEED_MAC3_ON;
+ amc->i2c_init = fby4_i2c_init;
+ mc->default_ram_size = FBY4_BMC_RAM_SIZE;
+ aspeed_machine_class_init_cpus_defaults(mc);
+}
+
+static const TypeInfo aspeed_ast2600_fby4_types[] = {
+ {
+ .name = MACHINE_TYPE_NAME("fby4-bmc"),
+ .parent = MACHINE_TYPE_NAME("ast2600-evb"),
+ .class_init = aspeed_machine_fby4_class_init,
+ .interfaces = arm_machine_interfaces,
+ }
+};
+
+DEFINE_TYPES(aspeed_ast2600_fby4_types)
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 80068f70bb..bb354a44ba 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -58,6 +58,7 @@ arm_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files(
'aspeed_ast2600_catalina.c',
'aspeed_ast2600_evb.c',
'aspeed_ast2600_fby35.c',
+ 'aspeed_ast2600_fby4.c',
'aspeed_ast2600_fuji.c',
'aspeed_ast2600_gb200nvl.c',
'aspeed_ast2600_rainier.c',
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 2/5] ast2600: yosemite4 functional test
2026-05-15 14:57 [PATCH v4 0/5] initial support for yosemite v4 Alexander Hansen
2026-05-15 14:57 ` [PATCH v4 1/5] ast2600: yosemite4 initial support Alexander Hansen
@ 2026-05-15 14:57 ` Alexander Hansen
2026-05-15 16:31 ` Cédric Le Goater
2026-05-15 14:58 ` [PATCH v4 3/5] hw/sensor: MAX31790 support Alexander Hansen
` (2 subsequent siblings)
4 siblings, 1 reply; 8+ messages in thread
From: Alexander Hansen @ 2026-05-15 14:57 UTC (permalink / raw)
To: qemu-devel
Cc: Alexander Hansen, Titus Rwantare, Cédric Le Goater,
Peter Maydell, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley, qemu-arm
Add a functional test for the Yosemite V4 (fby4-bmc) machine
that validates:
- U-Boot initialization and kernel boot sequence
The test uses an OpenBMC image built for the yosemite4 board and
validates console output through the boot process.
Cc: Titus Rwantare <titusr@google.com>
Cc: "Cédric Le Goater" <clg@kaod.org> (maintainer:ASPEED BMCs)
Cc: Peter Maydell <peter.maydell@linaro.org> (maintainer:ASPEED BMCs)
Cc: Steven Lee <steven_lee@aspeedtech.com> (reviewer:ASPEED BMCs)
Cc: Troy Lee <leetroy@gmail.com> (reviewer:ASPEED BMCs)
Cc: Jamin Lin <jamin_lin@aspeedtech.com> (reviewer:ASPEED BMCs)
Cc: Kane Chen <kane_chen@aspeedtech.com> (reviewer:ASPEED BMCs)
Cc: Andrew Jeffery <andrew@codeconstruct.com.au> (reviewer:ASPEED BMCs)
Cc: Joel Stanley <joel@jms.id.au> (reviewer:ASPEED BMCs)
Cc: qemu-arm@nongnu.org (open list:ASPEED BMCs)
Cc: qemu-devel@nongnu.org (open list:All patches CC here)
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
---
tests/functional/arm/meson.build | 2 ++
tests/functional/arm/test_aspeed_fby4.py | 44 ++++++++++++++++++++++++
2 files changed, 46 insertions(+)
create mode 100755 tests/functional/arm/test_aspeed_fby4.py
diff --git a/tests/functional/arm/meson.build b/tests/functional/arm/meson.build
index 2f538f29a2..10c0006f22 100644
--- a/tests/functional/arm/meson.build
+++ b/tests/functional/arm/meson.build
@@ -14,6 +14,7 @@ test_arm_timeouts = {
'aspeed_ast2600_sdk_otp' : 720,
'aspeed_bletchley' : 480,
'aspeed_catalina' : 480,
+ 'aspeed_fby4': 480,
'aspeed_gb200nvl_bmc' : 480,
'aspeed_rainier' : 480,
'bpim2u' : 500,
@@ -47,6 +48,7 @@ tests_arm_system_thorough = [
'aspeed_ast2600_sdk_otp',
'aspeed_bletchley',
'aspeed_catalina',
+ 'aspeed_fby4',
'aspeed_gb200nvl_bmc',
'aspeed_rainier',
'bpim2u',
diff --git a/tests/functional/arm/test_aspeed_fby4.py b/tests/functional/arm/test_aspeed_fby4.py
new file mode 100755
index 0000000000..a3124c240f
--- /dev/null
+++ b/tests/functional/arm/test_aspeed_fby4.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+#
+# Functional test that boots the ASPEED machines
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from qemu_test import Asset
+from aspeed import AspeedTest
+
+
+class YosemiteV4Machine(AspeedTest):
+
+ ASSET_YOSEMITE_V4_FLASH = Asset(
+ 'https://github.com/legoater/qemu-aspeed-boot/raw/refs/heads/master/images/yosemite4-bmc/openbmc-20260505132843/obmc-phosphor-image-yosemite4-20260505132843.static.mtd.xz',
+ 'dff6946363b41f952b15cfc3156482b89fcfc1b0ecfc3ec8b3ed496a5f001ef9')
+
+ def do_test_arm_aspeed_openbmc_no_network(self, machine, image, uboot,
+ cpu_id, soc):
+
+ self.set_machine(machine)
+ self.vm.set_console()
+ self.vm.add_args('-drive', f'file={image},if=mtd,format=raw',
+ '-snapshot')
+ self.vm.launch()
+
+ self.wait_for_console_pattern(f'U-Boot {uboot}')
+ self.wait_for_console_pattern('## Loading kernel from FIT Image')
+ self.wait_for_console_pattern('Starting kernel ...')
+ self.wait_for_console_pattern(f'Booting Linux on physical CPU {cpu_id}')
+ self.wait_for_console_pattern(f'ASPEED {soc}')
+ self.wait_for_console_pattern('/init as init process')
+ # yosemite v4 does not emit the hostname log which is
+ # different from the other machines.
+ self.wait_for_console_pattern('yosemite4 login:')
+
+ def test_arm_ast2600_yosemitev4_openbmc(self):
+ image_path = self.uncompress(self.ASSET_YOSEMITE_V4_FLASH)
+
+ self.do_test_arm_aspeed_openbmc_no_network('fby4-bmc', image=image_path,
+ uboot='2019.04', cpu_id='0xf00',
+ soc='AST2600 rev A3')
+
+if __name__ == '__main__':
+ AspeedTest.main()
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 3/5] hw/sensor: MAX31790 support
2026-05-15 14:57 [PATCH v4 0/5] initial support for yosemite v4 Alexander Hansen
2026-05-15 14:57 ` [PATCH v4 1/5] ast2600: yosemite4 initial support Alexander Hansen
2026-05-15 14:57 ` [PATCH v4 2/5] ast2600: yosemite4 functional test Alexander Hansen
@ 2026-05-15 14:58 ` Alexander Hansen
2026-05-15 14:58 ` [PATCH v4 4/5] hw/sensor: support MAX11615 Alexander Hansen
2026-05-15 14:58 ` [PATCH v4 5/5] hw/sensor: support Texas Instruments ADC128D818 Alexander Hansen
4 siblings, 0 replies; 8+ messages in thread
From: Alexander Hansen @ 2026-05-15 14:58 UTC (permalink / raw)
To: qemu-devel
Cc: Alexander Hansen, Titus Rwantare, Cédric Le Goater,
Paolo Bonzini, Peter Maydell, Philippe Mathieu-Daudé,
qemu-arm, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley
Product: [1]
Datasheet: [2]
MAX31790 Support:
- fan inputs are reading
- tach reading propertional to pwm setting from linux driver
- fans do not show any fault
- 6 PWM registers influence 6 TACH registers
There is intentional stub behavior in some places and various functions
of the device are currently unsupported.
MAX31790 currently unsupported:
- slave address restriction
- fan dynamics
- spin-up configuration
- fault state / failure possibility
- rate-of-change control
- tach mode
- mixed layouts where number of fans != number of tachs
- see Figure 5.9 in [2] for example of mixed layout
Anyone could expand it in the future for more accurate emulation.
The reason for adding this device is to support Yosemite V4 emulation.
Tested: on yosemite 4 qemu
root@yosemite4:~# ls /sys/class/hwmon/hwmon2/
device fan2_fault fan3_target fan5_fault fan6_target pwm2 pwm5
fan1_enable fan2_input fan4_enable fan5_input name pwm2_enable pwm5_enable
fan1_fault fan2_target fan4_fault fan5_target of_node pwm3 pwm6
fan1_input fan3_enable fan4_input fan6_enable power pwm3_enable pwm6_enable
fan1_target fan3_fault fan4_target fan6_fault pwm1 pwm4 subsystem
fan2_enable fan3_input fan5_enable fan6_input pwm1_enable pwm4_enable uevent
root@yosemite4:~# cat /sys/class/hwmon/hwmon2/fan1_input
4551
root@yosemite4:~# cat /sys/class/hwmon/hwmon2/fan1_enable
1
root@yosemite4:~# cat /sys/class/hwmon/hwmon2/fan1_fault
0
root@yosemite4:~# cat /sys/class/hwmon/hwmon2/fan1_target
2048
root@yosemite4:~# cat /sys/class/hwmon/hwmon2/pwm1
178
root@yosemite4:~# cat /sys/class/hwmon/hwmon2/name
max31790
Trace output:
max31790_realize i2c_addr: 0x20
max31790_realize i2c_addr: 0x2f
max31790_realize i2c_addr: 0x20
max31790_realize i2c_addr: 0x2f
max31790_event i2c_addr: 0x20, event: 0x01
max31790_send i2c_addr: 0x20, data: 0x02
max31790_event i2c_addr: 0x20, event: 0x00
max31790_recv i2c_addr: 0x20, reg_addr: 0x02
max31790_recv_return i2c_addr: 0x20, returns: 0x08
...
References:
[1] https://www.analog.com/en/products/MAX31790.html
[2] https://www.analog.com/media/en/technical-documentation/data-sheets/MAX31790.pdf
Cc: Titus Rwantare <titusr@google.com>
Cc: "Cédric Le Goater" <clg@kaod.org> (maintainer:ASPEED BMCs)
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: "Philippe Mathieu-Daudé" <philmd@linaro.org>
Cc: qemu-arm@nongnu.org
Cc: qemu-devel@nongnu.org
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
---
MAINTAINERS | 1 +
hw/arm/aspeed_ast2600_fby4.c | 4 +-
hw/sensor/max31790.c | 501 +++++++++++++++++++++++
hw/arm/Kconfig | 1 +
hw/sensor/Kconfig | 4 +
hw/sensor/meson.build | 1 +
hw/sensor/trace-events | 8 +
tests/functional/arm/test_aspeed_fby4.py | 18 +
8 files changed, 536 insertions(+), 2 deletions(-)
create mode 100644 hw/sensor/max31790.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 80d28e618d..dc0cdb4089 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3986,6 +3986,7 @@ S: Maintained
F: hw/i2c/pmbus_device.c
F: hw/sensor/adm1272.c
F: hw/sensor/isl_pmbus_vr.c
+F: hw/sensor/max31790.c
F: hw/sensor/max34451.c
F: include/hw/i2c/pmbus_device.h
F: include/hw/sensor/isl_pmbus_vr.h
diff --git a/hw/arm/aspeed_ast2600_fby4.c b/hw/arm/aspeed_ast2600_fby4.c
index 89fb0ac2fd..1f391022a2 100644
--- a/hw/arm/aspeed_ast2600_fby4.c
+++ b/hw/arm/aspeed_ast2600_fby4.c
@@ -164,7 +164,7 @@ static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize)
/* TODO */
/* maxim,max31790 @ 0x20 (pwm) */
- /* TODO */
+ i2c_slave_create_simple(bus, "max31790", 0x20);
/*
* ti,tca6424 @ 0x22 (gpio)
@@ -182,7 +182,7 @@ static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize)
*/
/* maxim,max31790 @ 0x2f (pwm) */
- /* TODO */
+ i2c_slave_create_simple(bus, "max31790", 0x2f);
/* maxim,max11615 @ 0x33 (adc) */
/* TODO */
diff --git a/hw/sensor/max31790.c b/hw/sensor/max31790.c
new file mode 100644
index 0000000000..30bab2e74e
--- /dev/null
+++ b/hw/sensor/max31790.c
@@ -0,0 +1,501 @@
+/*
+ * Maxim MAX31790 PMBus 6-Channel Fan Controller
+ *
+ * Datasheet:
+ * https://www.analog.com/media/en/technical-documentation/data-sheets/MAX31790.pdf
+ *
+ * Copyright 2026 9elements
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/i2c/i2c.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+#include "trace.h"
+
+#define TYPE_MAX31790 "max31790"
+
+#define MAX31790_NUM_FANS 6
+#define MAX31790_NUM_TACHS 12
+
+#define MAX31790_REG_GLOBAL_CONFIG 0x00
+#define MAX31790_REG_PWM_FREQ 0x01
+
+/* 0x02 to 0x07: N = 0 .. 5 */
+#define MAX31790_REG_FAN_CONFIG(N) (0x02 + N)
+
+/* 0x08 to 0x0d: N = 0 .. 5 */
+#define MAX31790_REG_FAN_DYNAMICS(N) (0x08 + N)
+
+#define MAX31790_REG_FAN_FAULT_STATUS_2 0x10
+#define MAX31790_REG_FAN_FAULT_STATUS_1 0x11
+#define MAX31790_REG_FAN_FAULT_MASK_2 0x12
+#define MAX31790_REG_FAN_FAULT_MASK_1 0x13
+#define MAX31790_REG_FAILED_FAN_OPT 0x14
+
+/* 0x18 to 0x2f: N = 0 .. 11 */
+#define MAX31790_REG_TACH_COUNT_MSB(N) (0x18 + 2 * N)
+#define MAX31790_REG_TACH_COUNT_LSB(N) (0x19 + 2 * N)
+
+/* 0x30 to 0x3b: N = 0 .. 5 */
+#define MAX31790_REG_PWM_DUTY_CYCLE_MSB(N) (0x30 + 2 * N)
+#define MAX31790_REG_PWM_DUTY_CYCLE_LSB(N) (0x31 + 2 * N)
+
+/* .. reserved registers ... */
+
+/* 0x40 to 0x4b: N = 0 .. 5 */
+#define MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(N) (0x40 + 2 * N)
+#define MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(N) (0x41 + 2 * N)
+
+/* ... 'User Byte' registers ... */
+
+/* 0x50 to 0x5b: N = 0 .. 5 */
+#define MAX31790_REG_TACH_TARGET_COUNT_MSB(N) (0x50 + 2 * N)
+#define MAX31790_REG_TACH_TARGET_COUNT_LSB(N) (0x51 + 2 * N)
+
+struct MAX31790State {
+ I2CSlave i2c;
+
+ uint8_t fan_config[MAX31790_NUM_FANS];
+ uint8_t fan_dynamics[MAX31790_NUM_FANS];
+
+ uint16_t pwm[MAX31790_NUM_FANS];
+ uint16_t tach_target[MAX31790_NUM_FANS];
+ uint16_t rpm[MAX31790_NUM_TACHS];
+
+ /* command buffer */
+ uint8_t len;
+ uint8_t buf[2];
+
+ /* output buffer */
+ uint8_t outlen;
+ uint8_t outbuf[2];
+
+ /* selected register for read/write operation */
+ uint8_t pointer;
+};
+
+struct MAX31790Class {
+ I2CSlaveClass parent_class;
+};
+
+OBJECT_DECLARE_TYPE(MAX31790State, MAX31790Class, MAX31790)
+
+static void max31790_read(MAX31790State *s)
+{
+ size_t index = 0;
+ uint8_t out0 = 0;
+ uint8_t out1 = 0;
+
+ switch (s->pointer) {
+ case MAX31790_REG_FAN_CONFIG(0):
+ case MAX31790_REG_FAN_CONFIG(1):
+ case MAX31790_REG_FAN_CONFIG(2):
+ case MAX31790_REG_FAN_CONFIG(3):
+ case MAX31790_REG_FAN_CONFIG(4):
+ case MAX31790_REG_FAN_CONFIG(5):
+ out0 = s->fan_config[s->pointer - MAX31790_REG_FAN_CONFIG(0)];
+ break;
+ case MAX31790_REG_FAN_DYNAMICS(0):
+ case MAX31790_REG_FAN_DYNAMICS(1):
+ case MAX31790_REG_FAN_DYNAMICS(2):
+ case MAX31790_REG_FAN_DYNAMICS(3):
+ case MAX31790_REG_FAN_DYNAMICS(4):
+ case MAX31790_REG_FAN_DYNAMICS(5):
+ out0 = s->fan_dynamics[s->pointer - MAX31790_REG_FAN_DYNAMICS(0)];
+ break;
+ case MAX31790_REG_FAN_FAULT_STATUS_1:
+ case MAX31790_REG_FAN_FAULT_STATUS_2:
+ /* we do not have any fan fault */
+ out0 = 0x00;
+ out1 = 0x00;
+ break;
+ case MAX31790_REG_TACH_COUNT_MSB(0):
+ case MAX31790_REG_TACH_COUNT_MSB(1):
+ case MAX31790_REG_TACH_COUNT_MSB(2):
+ case MAX31790_REG_TACH_COUNT_MSB(3):
+ case MAX31790_REG_TACH_COUNT_MSB(4):
+ case MAX31790_REG_TACH_COUNT_MSB(5):
+ case MAX31790_REG_TACH_COUNT_MSB(6):
+ case MAX31790_REG_TACH_COUNT_MSB(7):
+ case MAX31790_REG_TACH_COUNT_MSB(8):
+ case MAX31790_REG_TACH_COUNT_MSB(9):
+ case MAX31790_REG_TACH_COUNT_MSB(10):
+ case MAX31790_REG_TACH_COUNT_MSB(11):
+ index = (s->pointer - MAX31790_REG_TACH_COUNT_MSB(0)) / 2;
+ out0 = (s->rpm[index] >> 8) & 0xff;
+ out1 = s->rpm[index] & 0xff;
+ break;
+
+ case MAX31790_REG_TACH_COUNT_LSB(0):
+ case MAX31790_REG_TACH_COUNT_LSB(1):
+ case MAX31790_REG_TACH_COUNT_LSB(2):
+ case MAX31790_REG_TACH_COUNT_LSB(3):
+ case MAX31790_REG_TACH_COUNT_LSB(4):
+ case MAX31790_REG_TACH_COUNT_LSB(5):
+ case MAX31790_REG_TACH_COUNT_LSB(6):
+ case MAX31790_REG_TACH_COUNT_LSB(7):
+ case MAX31790_REG_TACH_COUNT_LSB(8):
+ case MAX31790_REG_TACH_COUNT_LSB(9):
+ case MAX31790_REG_TACH_COUNT_LSB(10):
+ case MAX31790_REG_TACH_COUNT_LSB(11):
+ index = (s->pointer - MAX31790_REG_TACH_COUNT_LSB(0)) / 2;
+ out0 = s->rpm[index] & 0xff;
+ break;
+
+ case MAX31790_REG_PWM_DUTY_CYCLE_MSB(0):
+ case MAX31790_REG_PWM_DUTY_CYCLE_MSB(1):
+ case MAX31790_REG_PWM_DUTY_CYCLE_MSB(2):
+ case MAX31790_REG_PWM_DUTY_CYCLE_MSB(3):
+ case MAX31790_REG_PWM_DUTY_CYCLE_MSB(4):
+ case MAX31790_REG_PWM_DUTY_CYCLE_MSB(5):
+ index = (s->pointer - MAX31790_REG_PWM_DUTY_CYCLE_MSB(0)) / 2;
+ out0 = (s->pwm[index] >> 8) & 0xff;
+ out1 = s->pwm[index] & 0xff;
+ break;
+ case MAX31790_REG_PWM_DUTY_CYCLE_LSB(0):
+ case MAX31790_REG_PWM_DUTY_CYCLE_LSB(1):
+ case MAX31790_REG_PWM_DUTY_CYCLE_LSB(2):
+ case MAX31790_REG_PWM_DUTY_CYCLE_LSB(3):
+ case MAX31790_REG_PWM_DUTY_CYCLE_LSB(4):
+ case MAX31790_REG_PWM_DUTY_CYCLE_LSB(5):
+ index = (s->pointer - MAX31790_REG_PWM_DUTY_CYCLE_LSB(0)) / 2;
+ out0 = s->pwm[index] & 0xff;
+ break;
+
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(0):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(1):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(2):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(3):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(4):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(5):
+ index = (s->pointer - MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(0)) / 2;
+ out0 = (s->pwm[index] >> 8) & 0xff;
+ out1 = s->pwm[index] & 0xff;
+ break;
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(0):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(1):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(2):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(3):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(4):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(5):
+ index = (s->pointer - MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(0)) / 2;
+ out0 = s->pwm[index] & 0xff;
+ break;
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(0):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(1):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(2):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(3):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(4):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(5):
+ index = (s->pointer - MAX31790_REG_TACH_TARGET_COUNT_MSB(0)) / 2;
+ out0 = (s->tach_target[index] >> 8) & 0xff;
+ out1 = s->tach_target[index] & 0xff;
+ break;
+ case MAX31790_REG_TACH_TARGET_COUNT_LSB(0):
+ case MAX31790_REG_TACH_TARGET_COUNT_LSB(1):
+ case MAX31790_REG_TACH_TARGET_COUNT_LSB(2):
+ case MAX31790_REG_TACH_TARGET_COUNT_LSB(3):
+ case MAX31790_REG_TACH_TARGET_COUNT_LSB(4):
+ case MAX31790_REG_TACH_TARGET_COUNT_LSB(5):
+ index = (s->pointer - MAX31790_REG_TACH_TARGET_COUNT_LSB(0)) / 2;
+ out0 = s->tach_target[index] & 0xff;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: read of register %d", __func__,
+ s->pointer);
+ break;
+ }
+
+ s->outbuf[0] = out0;
+ s->outbuf[1] = out1;
+}
+
+static void max31790_set_rpm(MAX31790State *s, size_t index, uint16_t rpm)
+{
+ /* datasheet: lowest 5 bits are 0 */
+ s->rpm[index] = rpm & ~0b11111;
+}
+
+static void max31790_pwm_write(MAX31790State *s, size_t index, uint16_t value)
+{
+ trace_max31790_pwm_write(s->i2c.address, index, value);
+
+ s->pwm[index] = value;
+
+ /* change rpm based on pwm input */
+ const uint16_t pwm_no_reserve = s->pwm[index] >> 7;
+
+ /*
+ * This formula has magic values which model the relationship
+ * of PWM input to a fan. Not derived from datasheet.
+ */
+ max31790_set_rpm(s, index, 0x1000 + (pwm_no_reserve << 3));
+}
+
+static void max31790_write_2_byte(MAX31790State *s)
+{
+ size_t index = 0;
+ const uint8_t value0 = s->buf[0];
+ const uint8_t value1 = s->buf[1];
+
+ switch (s->pointer) {
+ case MAX31790_REG_FAN_CONFIG(0):
+ case MAX31790_REG_FAN_CONFIG(1):
+ case MAX31790_REG_FAN_CONFIG(2):
+ case MAX31790_REG_FAN_CONFIG(3):
+ case MAX31790_REG_FAN_CONFIG(4):
+ case MAX31790_REG_FAN_CONFIG(5):
+ break; /* handled by one byte write */
+ case MAX31790_REG_FAN_DYNAMICS(0):
+ case MAX31790_REG_FAN_DYNAMICS(1):
+ case MAX31790_REG_FAN_DYNAMICS(2):
+ case MAX31790_REG_FAN_DYNAMICS(3):
+ case MAX31790_REG_FAN_DYNAMICS(4):
+ case MAX31790_REG_FAN_DYNAMICS(5):
+ break; /* handled by one byte write */
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(0):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(1):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(2):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(3):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(4):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(5):
+ index = (s->pointer - MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(0)) / 2;
+ max31790_pwm_write(s, index, value0 << 8 | value1);
+ break;
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(0):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(1):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(2):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(3):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(4):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(5):
+ index = (s->pointer - MAX31790_REG_TACH_TARGET_COUNT_MSB(0)) / 2;
+ s->tach_target[index] = (value0 << 8) | value1;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: write to register %d", __func__,
+ s->pointer);
+ break;
+ }
+}
+
+static void max31790_write_1_byte(MAX31790State *s)
+{
+
+ size_t index = 0;
+ uint16_t pwm = 0;
+ const uint8_t value = s->buf[0];
+ switch (s->pointer) {
+ case MAX31790_REG_FAN_CONFIG(0):
+ case MAX31790_REG_FAN_CONFIG(1):
+ case MAX31790_REG_FAN_CONFIG(2):
+ case MAX31790_REG_FAN_CONFIG(3):
+ case MAX31790_REG_FAN_CONFIG(4):
+ case MAX31790_REG_FAN_CONFIG(5):
+ s->fan_config[s->pointer - MAX31790_REG_FAN_CONFIG(0)] = value;
+ break;
+ case MAX31790_REG_FAN_DYNAMICS(0):
+ case MAX31790_REG_FAN_DYNAMICS(1):
+ case MAX31790_REG_FAN_DYNAMICS(2):
+ case MAX31790_REG_FAN_DYNAMICS(3):
+ case MAX31790_REG_FAN_DYNAMICS(4):
+ case MAX31790_REG_FAN_DYNAMICS(5):
+ s->fan_dynamics[s->pointer - MAX31790_REG_FAN_DYNAMICS(0)] = value;
+ break;
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(0):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(1):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(2):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(3):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(4):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(5):
+ index = (s->pointer - MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(0)) / 2;
+ pwm = (value << 8) | (s->pwm[index] & 0x00ff);
+ max31790_pwm_write(s, index, pwm);
+ break;
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(0):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(1):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(2):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(3):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(4):
+ case MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(5):
+ index = (s->pointer - MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(0)) / 2;
+ pwm = (s->pwm[index] & 0xff00) | (value & 0x00ff);
+ max31790_pwm_write(s, index, pwm);
+ break;
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(0):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(1):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(2):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(3):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(4):
+ case MAX31790_REG_TACH_TARGET_COUNT_MSB(5):
+ index = (s->pointer - MAX31790_REG_TACH_TARGET_COUNT_MSB(0)) / 2;
+ s->tach_target[index] = (s->tach_target[index] & 0x00ff) | (value << 8);
+ break;
+ case MAX31790_REG_TACH_TARGET_COUNT_LSB(0):
+ case MAX31790_REG_TACH_TARGET_COUNT_LSB(1):
+ case MAX31790_REG_TACH_TARGET_COUNT_LSB(2):
+ case MAX31790_REG_TACH_TARGET_COUNT_LSB(3):
+ case MAX31790_REG_TACH_TARGET_COUNT_LSB(4):
+ case MAX31790_REG_TACH_TARGET_COUNT_LSB(5):
+ index = (s->pointer - MAX31790_REG_TACH_TARGET_COUNT_LSB(0)) / 2;
+ s->tach_target[index] = (s->tach_target[index] & 0xff00) | value;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: write to register %d", __func__,
+ s->pointer);
+ break;
+ }
+}
+
+static int max31790_send(I2CSlave *i2c, uint8_t data)
+{
+ MAX31790State *s = MAX31790(i2c);
+
+ trace_max31790_send(s->i2c.address, data);
+
+ if (s->len == 0) {
+ /* first byte is the register pointer for a read / write operation */
+ s->pointer = data;
+ s->len++;
+ return 0;
+ }
+
+ if (s->len > 2) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: write too many bytes", __func__);
+ return 1; /* NAK */
+ }
+
+ /* second / third byte is the data to write */
+ s->buf[s->len - 1] = data;
+ s->len++;
+
+ if (s->len == 2) {
+ max31790_write_1_byte(s);
+ } else if (s->len == 3) {
+ max31790_write_2_byte(s);
+ }
+
+ return 0;
+}
+
+static uint8_t max31790_recv(I2CSlave *i2c)
+{
+ MAX31790State *s = MAX31790(i2c);
+ trace_max31790_recv(s->i2c.address, s->pointer);
+
+ max31790_read(s);
+ s->len = 0;
+
+ if (s->outlen >= 2) {
+ /* error */
+ s->outlen = 0;
+ }
+
+ const uint8_t data = s->outbuf[s->outlen++];
+
+ trace_max31790_recv_return(s->i2c.address, data);
+ return data;
+}
+
+static int max31790_event(I2CSlave *i2c, enum i2c_event event)
+{
+ MAX31790State *s = MAX31790(i2c);
+
+ trace_max31790_event(s->i2c.address, event);
+
+ switch (event) {
+ case I2C_START_RECV:
+ s->outlen = 0;
+ break;
+ case I2C_START_SEND:
+ s->len = 0;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_max31790 = {
+ .name = TYPE_MAX31790,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields =
+ (const VMStateField[]){
+ VMSTATE_UINT8(len, MAX31790State),
+ VMSTATE_UINT8_ARRAY(fan_config, MAX31790State, MAX31790_NUM_FANS),
+ VMSTATE_UINT8_ARRAY(fan_dynamics, MAX31790State, MAX31790_NUM_FANS),
+ VMSTATE_UINT16_ARRAY(pwm, MAX31790State, MAX31790_NUM_FANS),
+ VMSTATE_UINT16_ARRAY(tach_target, MAX31790State, MAX31790_NUM_FANS),
+ VMSTATE_UINT16_ARRAY(rpm, MAX31790State, MAX31790_NUM_TACHS),
+ VMSTATE_UINT8_ARRAY(buf, MAX31790State, 2),
+ VMSTATE_UINT8(outlen, MAX31790State),
+ VMSTATE_UINT8_ARRAY(outbuf, MAX31790State, 2),
+ VMSTATE_UINT8(pointer, MAX31790State),
+ VMSTATE_I2C_SLAVE(i2c, MAX31790State), VMSTATE_END_OF_LIST() }
+};
+
+static void max31790_reset(DeviceState *dev)
+{
+ MAX31790State *s = MAX31790(dev);
+
+ for (int i = 0; i < MAX31790_NUM_FANS; i++) {
+ /* POR-State 0b 0XX0 0000 */
+ s->fan_config[i] = 0b00000000;
+
+ /* same as POR-State */
+ s->tach_target[i] = 0b0011110000000000;
+
+ /* same as POR-State */
+ s->fan_dynamics[i] = 0b01001100;
+
+ s->pwm[i] = 0;
+ }
+
+ for (int i = 0; i < MAX31790_NUM_TACHS; i++) {
+ max31790_set_rpm(s, i, 0x4444);
+ }
+}
+
+static void max31790_realize(DeviceState *dev, Error **errp)
+{
+ MAX31790State *s = MAX31790(dev);
+
+ trace_max31790_realize(s->i2c.address);
+}
+
+static void max31790_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ dc->realize = max31790_realize;
+ dc->desc = "Maxim MAX31790 6-Channel Fan Controller";
+ dc->vmsd = &vmstate_max31790;
+ dc->legacy_reset = max31790_reset;
+ k->event = max31790_event;
+ k->recv = max31790_recv;
+ k->send = max31790_send;
+}
+
+static const TypeInfo max31790_info = {
+ .name = TYPE_MAX31790,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(MAX31790State),
+ .class_size = sizeof(MAX31790Class),
+ .class_init = max31790_class_init,
+};
+
+static void max31790_register_types(void)
+{
+ type_register_static(&max31790_info);
+}
+
+type_init(max31790_register_types)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 5b198402d5..99864eb878 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -552,6 +552,7 @@ config ASPEED_SOC
select LED
select PMBUS
select MAX31785
+ select MAX31790
select FSI_APB2OPB_ASPEED
select AT24C
select PCI_EXPRESS
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index bc6331b4ab..ece2f2b167 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -43,3 +43,7 @@ config ISL_PMBUS_VR
config MAX31785
bool
depends on PMBUS
+
+config MAX31790
+ bool
+ depends on PMBUS
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index 420fdc3359..4987c3b253 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -8,3 +8,4 @@ system_ss.add(when: 'CONFIG_MAX34451', if_true: files('max34451.c'))
system_ss.add(when: 'CONFIG_LSM303DLHC_MAG', if_true: files('lsm303dlhc_mag.c'))
system_ss.add(when: 'CONFIG_ISL_PMBUS_VR', if_true: files('isl_pmbus_vr.c'))
system_ss.add(when: 'CONFIG_MAX31785', if_true: files('max31785.c'))
+system_ss.add(when: 'CONFIG_MAX31790', if_true: files('max31790.c'))
diff --git a/hw/sensor/trace-events b/hw/sensor/trace-events
index a3fe54fa6d..c15c0a7e93 100644
--- a/hw/sensor/trace-events
+++ b/hw/sensor/trace-events
@@ -4,3 +4,11 @@
tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x"
tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
tmp105_write_shutdown(uint8_t dev) "device: 0x%02x"
+
+# max31790.c
+max31790_send(uint8_t i2c_addr, uint8_t send) "i2c_addr: 0x%02x, data: 0x%02x"
+max31790_recv(uint8_t i2c_addr, uint8_t reg_addr) "i2c_addr: 0x%02x, reg_addr: 0x%02x"
+max31790_recv_return(uint8_t i2c_addr, uint8_t data) "i2c_addr: 0x%02x, returns: 0x%02x"
+max31790_event(uint8_t i2c_addr, uint8_t event) "i2c_addr: 0x%02x, event: 0x%02x"
+max31790_realize(uint8_t i2c_addr) "i2c_addr: 0x%02x"
+max31790_pwm_write(uint8_t i2c_addr, size_t index, uint16_t value) "i2c_addr: 0x%02x, index: %zu, value: 0x%04x"
diff --git a/tests/functional/arm/test_aspeed_fby4.py b/tests/functional/arm/test_aspeed_fby4.py
index a3124c240f..d29423add7 100755
--- a/tests/functional/arm/test_aspeed_fby4.py
+++ b/tests/functional/arm/test_aspeed_fby4.py
@@ -7,6 +7,8 @@
from qemu_test import Asset
from aspeed import AspeedTest
+from qemu_test import wait_for_console_pattern, exec_command
+from qemu_test import exec_command_and_wait_for_pattern
class YosemiteV4Machine(AspeedTest):
@@ -33,6 +35,22 @@ def do_test_arm_aspeed_openbmc_no_network(self, machine, image, uboot,
# different from the other machines.
self.wait_for_console_pattern('yosemite4 login:')
+ # perform login
+ exec_command_and_wait_for_pattern(self,
+ "root", "Password:");
+
+ exec_command_and_wait_for_pattern(self, "0penBmc", "#");
+
+ # MAX31790 test
+ exec_command_and_wait_for_pattern(self,
+ "cat /sys/class/hwmon/hwmon2/name", "max31790");
+ exec_command_and_wait_for_pattern(self,
+ "cat /sys/class/hwmon/hwmon2/fan1_input", "4530");
+ exec_command_and_wait_for_pattern(self,
+ "cat /sys/class/hwmon/hwmon2/fan1_enable", "1");
+ exec_command_and_wait_for_pattern(self,
+ "cat /sys/class/hwmon/hwmon2/fan1_fault", "0");
+
def test_arm_ast2600_yosemitev4_openbmc(self):
image_path = self.uncompress(self.ASSET_YOSEMITE_V4_FLASH)
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 4/5] hw/sensor: support MAX11615
2026-05-15 14:57 [PATCH v4 0/5] initial support for yosemite v4 Alexander Hansen
` (2 preceding siblings ...)
2026-05-15 14:58 ` [PATCH v4 3/5] hw/sensor: MAX31790 support Alexander Hansen
@ 2026-05-15 14:58 ` Alexander Hansen
2026-05-15 14:58 ` [PATCH v4 5/5] hw/sensor: support Texas Instruments ADC128D818 Alexander Hansen
4 siblings, 0 replies; 8+ messages in thread
From: Alexander Hansen @ 2026-05-15 14:58 UTC (permalink / raw)
To: qemu-devel
Cc: Alexander Hansen, Titus Rwantare, Cédric Le Goater,
Philippe Mathieu-Daudé, Paolo Bonzini, Peter Maydell,
qemu-arm, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley
Product: [1]
Datasheet: [2]
Sensor readings can be provided upon creation of the device. In case no
readings are provided the ADC reads a pre-defined arbitrary value.
root@yosemite4:~# cat /sys/bus/iio/devices/iio:device2/name
max11615
root@yosemite4:~# cat /sys/bus/iio/devices/iio:device2/in_voltage0_raw
1922
root@yosemite4:~# cat /sys/bus/iio/devices/iio:device2/in_voltage_scale
0.500000000
trace:
less /tmp/qemu-trace.log | grep -i max116
max11615_realize i2c_addr: 0x33
max11615_realize i2c_addr: 0x33
max11615_event i2c_addr: 0x33, event: 0x01
max11615_write_setup i2c_addr: 0x33, data: 0xd2
max11615_write_config i2c_addr: 0x33, data: 0x0f
max11615_event i2c_addr: 0x33, event: 0x03
max11615_event i2c_addr: 0x33, event: 0x01
max11615_write_setup i2c_addr: 0x33, data: 0xd2
max11615_write_config i2c_addr: 0x33, data: 0x0f
max11615_event i2c_addr: 0x33, event: 0x03
max11615_event i2c_addr: 0x33, event: 0x01
max11615_write_setup i2c_addr: 0x33, data: 0xd2
max11615_write_config i2c_addr: 0x33, data: 0x61
max11615_event i2c_addr: 0x33, event: 0x03
max11615_event i2c_addr: 0x33, event: 0x00
max11615_recv i2c_addr: 0x33, reg_addr: 0x00
max11615_recv_return i2c_addr: 0x33, returns: 0xfa
max11615_recv i2c_addr: 0x33, reg_addr: 0x00
max11615_recv_return i2c_addr: 0x33, returns: 0xd2
max11615_event i2c_addr: 0x33, event: 0x04
max11615_event i2c_addr: 0x33, event: 0x03
References:
[1] https://www.analog.com/en/products/MAX11615.html
[2] https://www.analog.com/media/en/technical-documentation/data-sheets/MAX11612-MAX11617.pdf
Cc: Titus Rwantare <titusr@google.com>
Cc: "Cédric Le Goater" <clg@kaod.org> (maintainer:ASPEED BMCs)
Cc: "Philippe Mathieu-Daudé" <philmd@linaro.org> (odd fixer:Overall sensors)
Cc: Paolo Bonzini <pbonzini@redhat.com> (maintainer:Kconfig)
Cc: Peter Maydell <peter.maydell@linaro.org> (supporter:ARM TCG CPUs)
Cc: qemu-devel@nongnu.org (open list:All patches CC here)
Cc: qemu-arm@nongnu.org (open list:ARM TCG CPUs)
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
---
MAINTAINERS | 1 +
include/hw/sensor/max11615.h | 21 +++
hw/arm/aspeed_ast2600_fby4.c | 8 +-
hw/sensor/max11615.c | 206 +++++++++++++++++++++++
hw/arm/Kconfig | 1 +
hw/sensor/Kconfig | 4 +
hw/sensor/meson.build | 1 +
hw/sensor/trace-events | 8 +
tests/functional/arm/test_aspeed_fby4.py | 8 +
9 files changed, 257 insertions(+), 1 deletion(-)
create mode 100644 include/hw/sensor/max11615.h
create mode 100644 hw/sensor/max11615.c
diff --git a/MAINTAINERS b/MAINTAINERS
index dc0cdb4089..669d6e8344 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3986,6 +3986,7 @@ S: Maintained
F: hw/i2c/pmbus_device.c
F: hw/sensor/adm1272.c
F: hw/sensor/isl_pmbus_vr.c
+F: hw/sensor/max11615.c
F: hw/sensor/max31790.c
F: hw/sensor/max34451.c
F: include/hw/i2c/pmbus_device.h
diff --git a/include/hw/sensor/max11615.h b/include/hw/sensor/max11615.h
new file mode 100644
index 0000000000..c86a971a16
--- /dev/null
+++ b/include/hw/sensor/max11615.h
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef QEMU_MAX11615_H
+#define QEMU_MAX11615_H
+
+#include <stdint.h>
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+
+#define TYPE_MAX11615 "max11615"
+
+/*
+ * Create and realize a MAX11615 ADC with constant caller-supplied readings
+ * @bus: I2C bus to put it on
+ * @address: I2C address
+ * @init_values: array of readings for each ADC channel
+ * @init_values_size: Size of @init_values, can be less than the number of channels
+ */
+I2CSlave *max11615_init_with_values(I2CBus *bus, uint8_t address,
+ const uint16_t *init_values, uint32_t init_values_size);
+
+#endif
diff --git a/hw/arm/aspeed_ast2600_fby4.c b/hw/arm/aspeed_ast2600_fby4.c
index 1f391022a2..45ed4a4c0f 100644
--- a/hw/arm/aspeed_ast2600_fby4.c
+++ b/hw/arm/aspeed_ast2600_fby4.c
@@ -11,6 +11,7 @@
#include "hw/arm/aspeed.h"
#include "hw/arm/aspeed_soc.h"
#include "hw/nvram/eeprom_at24c.h"
+#include "hw/sensor/max11615.h"
#include "hw/i2c/i2c_mux_pca954x.h"
#include "hw/gpio/pca9552.h"
@@ -185,7 +186,12 @@ static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize)
i2c_slave_create_simple(bus, "max31790", 0x2f);
/* maxim,max11615 @ 0x33 (adc) */
- /* TODO */
+ static const uint16_t adc_values[8] = {
+ 0b011110000010, 0b010100011000,
+ 0b001000110100, 0b100000101001,
+ 0b011110000010, 0b010100011000,
+ 0b001000110100, 0b100000101001};
+ max11615_init_with_values(bus, 0x33, adc_values, 8);
at24c_eeprom_init_rom(
bus, 0x52, eepromSize,
diff --git a/hw/sensor/max11615.c b/hw/sensor/max11615.c
new file mode 100644
index 0000000000..e726db932c
--- /dev/null
+++ b/hw/sensor/max11615.c
@@ -0,0 +1,206 @@
+/*
+ * Maxim MAX11615 Low-Power 12 bit ADC
+ * Models MAX11612,MAX11613,MAX11614,MAX11615,MAX11616,MAX11617
+ *
+ * Datasheet:
+ * https://www.analog.com/media/en/technical-documentation/data-sheets/MAX11612-MAX11617.pdf
+ *
+ * Copyright 2026 9elements
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "hw/sensor/max11615.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/osdep.h"
+#include "trace.h"
+
+#define MAX11615_NUM_CHANNELS 8
+
+struct MAX11615State {
+ I2CSlave i2c;
+
+ uint16_t channels[MAX11615_NUM_CHANNELS];
+
+ /* output buffer */
+ uint8_t outlen;
+ uint8_t outbuf[2];
+
+ /* selected channel for read/write operation */
+ uint8_t pointer;
+};
+
+struct MAX11615Class {
+ I2CSlaveClass parent_class;
+};
+
+OBJECT_DECLARE_TYPE(MAX11615State, MAX11615Class, MAX11615)
+
+static void max11615_read(MAX11615State *s)
+{
+ /* read an ADC channel, first 4 bits must be high */
+ uint8_t msb = s->channels[s->pointer] >> 8;
+ uint8_t lsb = s->channels[s->pointer] & 0xff;
+ s->outbuf[0] = 0b11110000 | (msb & 0b00001111);
+ s->outbuf[1] = lsb;
+}
+
+static void max11615_write_config_byte(MAX11615State *s, uint8_t data)
+{
+ trace_max11615_write_config(s->i2c.address, data);
+
+ uint8_t channel_select = (data >> 1) & 0b1111;
+
+ /* Table 3. Channel Selection (AIN0 ... AIN11) */
+ if (channel_select > 11) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid channel select", __func__);
+ channel_select = 11;
+ }
+ s->pointer = channel_select;
+}
+
+static void max11615_write_setup_byte(MAX11615State *s, uint8_t data)
+{
+ trace_max11615_write_setup(s->i2c.address, data);
+ /* we ignore the setup byte, not implemented */
+}
+
+static int max11615_send(I2CSlave *i2c, uint8_t data)
+{
+ MAX11615State *s = MAX11615(i2c);
+ const uint8_t msb = (data >> 7) & 0b1;
+
+ if (msb) {
+ max11615_write_setup_byte(s, data);
+ } else {
+ max11615_write_config_byte(s, data);
+ }
+
+ s->outlen = 0;
+ return 0;
+}
+
+static uint8_t max11615_recv(I2CSlave *i2c)
+{
+ MAX11615State *s = MAX11615(i2c);
+ trace_max11615_recv(s->i2c.address, s->pointer);
+
+ max11615_read(s);
+
+ if (s->outlen >= 2) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: too many bytes read", __func__);
+ s->outlen = 0;
+ }
+
+ const uint8_t data = s->outbuf[s->outlen++];
+
+ trace_max11615_recv_return(s->i2c.address, data);
+ return data;
+}
+
+static int max11615_event(I2CSlave *i2c, enum i2c_event event)
+{
+ MAX11615State *s = MAX11615(i2c);
+
+ trace_max11615_event(s->i2c.address, event);
+
+ switch (event) {
+ case I2C_START_RECV:
+ s->outlen = 0;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_max11615 = {
+ .name = TYPE_MAX11615,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields =
+ (const VMStateField[]){ VMSTATE_UINT16_ARRAY(channels, MAX11615State,
+ MAX11615_NUM_CHANNELS),
+ VMSTATE_UINT8(outlen, MAX11615State),
+ VMSTATE_UINT8_ARRAY(outbuf, MAX11615State, 2),
+ VMSTATE_UINT8(pointer, MAX11615State),
+ VMSTATE_I2C_SLAVE(i2c, MAX11615State),
+ VMSTATE_END_OF_LIST() }
+};
+
+static void max11615_init(Object *obj) { /* Nothing to do */ }
+
+I2CSlave *max11615_init_with_values(I2CBus *bus, uint8_t address,
+ const uint16_t *init_values,
+ uint32_t init_values_size)
+{
+ MAX11615State *s;
+
+ s = MAX11615(i2c_slave_new(TYPE_MAX11615, address));
+
+ for (int i = 0; i < MAX11615_NUM_CHANNELS && i < init_values_size; i++) {
+
+ /* arbitrary value */
+ uint16_t value = 0b0000101011010010;
+
+ if (i < init_values_size) {
+ value = init_values[i];
+ }
+ s->channels[i] = value;
+ }
+
+ i2c_slave_realize_and_unref(I2C_SLAVE(s), bus, &error_abort);
+
+ return I2C_SLAVE(s);
+}
+
+static void max11615_reset(DeviceState *dev)
+{
+ MAX11615State *s = MAX11615(dev);
+
+ s->pointer = 0;
+ s->outlen = 0;
+}
+
+static void max11615_realize(DeviceState *dev, Error **errp)
+{
+ MAX11615State *s = MAX11615(dev);
+
+ trace_max11615_realize(s->i2c.address);
+}
+
+static void max11615_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ dc->realize = max11615_realize;
+ dc->desc = "Maxim MAX11615 12-bit ADC";
+ dc->vmsd = &vmstate_max11615;
+ dc->legacy_reset = max11615_reset;
+ k->event = max11615_event;
+ k->recv = max11615_recv;
+ k->send = max11615_send;
+}
+
+static const TypeInfo max11615_info = {
+ .name = TYPE_MAX11615,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(MAX11615State),
+ .class_size = sizeof(MAX11615Class),
+ .instance_init = max11615_init,
+ .class_init = max11615_class_init,
+};
+
+static void max11615_register_types(void)
+{
+ type_register_static(&max11615_info);
+}
+
+type_init(max11615_register_types)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 99864eb878..76a7d327a9 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -553,6 +553,7 @@ config ASPEED_SOC
select PMBUS
select MAX31785
select MAX31790
+ select MAX11615
select FSI_APB2OPB_ASPEED
select AT24C
select PCI_EXPRESS
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index ece2f2b167..84eede9d84 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -47,3 +47,7 @@ config MAX31785
config MAX31790
bool
depends on PMBUS
+
+config MAX11615
+ bool
+ depends on I2C
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index 4987c3b253..a1e26604fa 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -9,3 +9,4 @@ system_ss.add(when: 'CONFIG_LSM303DLHC_MAG', if_true: files('lsm303dlhc_mag.c'))
system_ss.add(when: 'CONFIG_ISL_PMBUS_VR', if_true: files('isl_pmbus_vr.c'))
system_ss.add(when: 'CONFIG_MAX31785', if_true: files('max31785.c'))
system_ss.add(when: 'CONFIG_MAX31790', if_true: files('max31790.c'))
+system_ss.add(when: 'CONFIG_MAX11615', if_true: files('max11615.c'))
diff --git a/hw/sensor/trace-events b/hw/sensor/trace-events
index c15c0a7e93..3fed979e85 100644
--- a/hw/sensor/trace-events
+++ b/hw/sensor/trace-events
@@ -12,3 +12,11 @@ max31790_recv_return(uint8_t i2c_addr, uint8_t data) "i2c_addr: 0x%02x, returns:
max31790_event(uint8_t i2c_addr, uint8_t event) "i2c_addr: 0x%02x, event: 0x%02x"
max31790_realize(uint8_t i2c_addr) "i2c_addr: 0x%02x"
max31790_pwm_write(uint8_t i2c_addr, size_t index, uint16_t value) "i2c_addr: 0x%02x, index: %zu, value: 0x%04x"
+
+# max11615.c
+max11615_write_setup(uint8_t i2c_addr, uint8_t send) "i2c_addr: 0x%02x, data: 0x%02x"
+max11615_write_config(uint8_t i2c_addr, uint8_t send) "i2c_addr: 0x%02x, data: 0x%02x"
+max11615_recv(uint8_t i2c_addr, uint8_t reg_addr) "i2c_addr: 0x%02x, reg_addr: 0x%02x"
+max11615_recv_return(uint8_t i2c_addr, uint8_t data) "i2c_addr: 0x%02x, returns: 0x%02x"
+max11615_event(uint8_t i2c_addr, uint8_t event) "i2c_addr: 0x%02x, event: 0x%02x"
+max11615_realize(uint8_t i2c_addr) "i2c_addr: 0x%02x"
diff --git a/tests/functional/arm/test_aspeed_fby4.py b/tests/functional/arm/test_aspeed_fby4.py
index d29423add7..319f1a7672 100755
--- a/tests/functional/arm/test_aspeed_fby4.py
+++ b/tests/functional/arm/test_aspeed_fby4.py
@@ -51,6 +51,14 @@ def do_test_arm_aspeed_openbmc_no_network(self, machine, image, uboot,
exec_command_and_wait_for_pattern(self,
"cat /sys/class/hwmon/hwmon2/fan1_fault", "0");
+ # MAX11615 test
+ exec_command_and_wait_for_pattern(self,
+ "cat /sys/bus/iio/devices/iio:device2/name", "max11615");
+ exec_command_and_wait_for_pattern(self,
+ "cat /sys/bus/iio/devices/iio:device2/in_voltage0_raw", "1922");
+ exec_command_and_wait_for_pattern(self,
+ "cat /sys/bus/iio/devices/iio:device2/in_voltage_scale", "0.500000000");
+
def test_arm_ast2600_yosemitev4_openbmc(self):
image_path = self.uncompress(self.ASSET_YOSEMITE_V4_FLASH)
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 5/5] hw/sensor: support Texas Instruments ADC128D818
2026-05-15 14:57 [PATCH v4 0/5] initial support for yosemite v4 Alexander Hansen
` (3 preceding siblings ...)
2026-05-15 14:58 ` [PATCH v4 4/5] hw/sensor: support MAX11615 Alexander Hansen
@ 2026-05-15 14:58 ` Alexander Hansen
4 siblings, 0 replies; 8+ messages in thread
From: Alexander Hansen @ 2026-05-15 14:58 UTC (permalink / raw)
To: qemu-devel
Cc: Alexander Hansen, Titus Rwantare, Cédric Le Goater,
Paolo Bonzini, Peter Maydell, Philippe Mathieu-Daudé,
qemu-arm, Steven Lee, Troy Lee, Jamin Lin, Kane Chen,
Andrew Jeffery, Joel Stanley
Product: [1]
Datasheet: [2]
ADC128D818 Support:
- channel readings from pre-set values
- driver can read and write most configuration registers
ADC128D818 currently unsupported:
- slave address restriction
- startup sequence and realistic busy register emulation
- external VREF
- conversion rate
- interrupts
- deep shutdown mode
- individual channel shutdown
- selection between Mode 0,1,2,3
- pseudo-differential input
Anyone could expand it in the future for more accurate emulation.
The reason for adding this device is to support Yosemite V4 emulation.
Tested: on yosemite v4 qemu
initialize the device:
// ti,adc128d818 @ 0x1f (adc)
// the driver will throw away the last 4 bits, set them 0
uint16_t adc_values1[8] = {
0b011110000000, 0b010100010000,
0b001000110000, 0b100000100000,
0b011110000000, 0b010100010000,
0b001000110000, 0b100000100000};
adc128d818_init_with_values(bus, 0x1f, adc_values1, 8);
Trace outputs directly after initialization:
adc128d818_realize i2c_addr: 0x1f
adc128d818_realize i2c_addr: 0x1f
adc128d818_event i2c_addr: 0x1f, event: 0x01
adc128d818_send i2c_addr: 0x1f, data: 0x00
adc128d818_send i2c_addr: 0x1f, data: 0x80
adc128d818_write i2c_addr: 0x1f, reg: 0x00 data: 0x80
adc128d818_event i2c_addr: 0x1f, event: 0x03
adc128d818_event i2c_addr: 0x1f, event: 0x01
...
read the values
root@yosemite4:/sys/bus/i2c/devices/30-001f# cat hwmon/hwmon0/in0_min
0
root@yosemite4:/sys/bus/i2c/devices/30-001f# cat hwmon/hwmon0/in0_max
0
root@yosemite4:/sys/bus/i2c/devices/30-001f# cat hwmon/hwmon0/in0_input
75
root@yosemite4:/sys/bus/i2c/devices/30-001f# cat hwmon/hwmon0/name
adc128d818
We initially configured 0b011110000000 for the first channel.
The driver throws away the last 4 bits and does calculation similar to
below:
val = DIV_ROUND_CLOSEST(data->in[index][nr] * data->vref, 4095);
We can check that the calculation is as expected given our configured
value.
((0b011110000000 >> 4) * 2560) / 4095
75.01831501831502
References:
[1] https://www.ti.com/product/ADC128D818
[2] https://www.ti.com/lit/gpn/adc128d818
Cc: Titus Rwantare <titusr@google.com>
Cc: "Cédric Le Goater" <clg@kaod.org> (maintainer:ASPEED BMCs)
Cc: Paolo Bonzini <pbonzini@redhat.com> (maintainer:Kconfig)
Cc: Peter Maydell <peter.maydell@linaro.org> (supporter:ARM TCG CPUs)
Cc: "Philippe Mathieu-Daudé" <philmd@linaro.org> (odd fixer:Overall sensors)
Cc: qemu-arm@nongnu.org (open list:ARM TCG CPUs)
Cc: qemu-devel@nongnu.org (open list:All patches CC here)
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
---
MAINTAINERS | 1 +
include/hw/sensor/adc128d818.h | 20 ++
hw/arm/aspeed_ast2600_fby4.c | 9 +-
hw/sensor/adc128d818.c | 410 +++++++++++++++++++++++
hw/arm/Kconfig | 1 +
hw/sensor/Kconfig | 4 +
hw/sensor/meson.build | 1 +
hw/sensor/trace-events | 8 +
tests/functional/arm/test_aspeed_fby4.py | 10 +
9 files changed, 463 insertions(+), 1 deletion(-)
create mode 100644 include/hw/sensor/adc128d818.h
create mode 100644 hw/sensor/adc128d818.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 669d6e8344..bf4d157781 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3984,6 +3984,7 @@ PMBus
M: Titus Rwantare <titusr@google.com>
S: Maintained
F: hw/i2c/pmbus_device.c
+F: hw/sensor/adc128d818.c
F: hw/sensor/adm1272.c
F: hw/sensor/isl_pmbus_vr.c
F: hw/sensor/max11615.c
diff --git a/include/hw/sensor/adc128d818.h b/include/hw/sensor/adc128d818.h
new file mode 100644
index 0000000000..e2bdc47590
--- /dev/null
+++ b/include/hw/sensor/adc128d818.h
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef QEMU_ADC128D818_H
+#define QEMU_ADC128D818_H
+
+#include <stdint.h>
+#include "hw/i2c/i2c.h"
+
+#define TYPE_ADC128D818 "adc128d818"
+
+/*
+ * Create and realize a adc128d818 ADC with constant caller-supplied readings
+ * @bus: I2C bus to put it on
+ * @address: I2C address
+ * @init_values: array of readings for each ADC channel
+ * @init_values_size: Size of @init_values, can be less than the number of channels
+ */
+I2CSlave *adc128d818_init_with_values(I2CBus *bus, uint8_t address,
+ const uint16_t *init_values, uint32_t init_values_size);
+
+#endif
diff --git a/hw/arm/aspeed_ast2600_fby4.c b/hw/arm/aspeed_ast2600_fby4.c
index 45ed4a4c0f..c70ef43a61 100644
--- a/hw/arm/aspeed_ast2600_fby4.c
+++ b/hw/arm/aspeed_ast2600_fby4.c
@@ -12,6 +12,7 @@
#include "hw/arm/aspeed_soc.h"
#include "hw/nvram/eeprom_at24c.h"
#include "hw/sensor/max11615.h"
+#include "hw/sensor/adc128d818.h"
#include "hw/i2c/i2c_mux_pca954x.h"
#include "hw/gpio/pca9552.h"
@@ -162,7 +163,13 @@ static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize)
I2CBus *bus = pca954x_i2c_get_bus(fan_mux, i);
/* ti,adc128d818 @ 0x1f (adc) */
- /* TODO */
+ /* the driver will throw away the last 4 bits, set them 0 */
+ static const uint16_t adc_values1[8] = {
+ 0b011110000000, 0b010100010000,
+ 0b001000110000, 0b100000100000,
+ 0b011110000000, 0b010100010000,
+ 0b001000110000, 0b100000100000};
+ adc128d818_init_with_values(bus, 0x1f, adc_values1, 8);
/* maxim,max31790 @ 0x20 (pwm) */
i2c_slave_create_simple(bus, "max31790", 0x20);
diff --git a/hw/sensor/adc128d818.c b/hw/sensor/adc128d818.c
new file mode 100644
index 0000000000..e362e41788
--- /dev/null
+++ b/hw/sensor/adc128d818.c
@@ -0,0 +1,410 @@
+/*
+ * Texas Instruments ADC128D818 12 bit ADC with temperature sensor
+ * Models ADC128D818
+ *
+ * Product: https://www.ti.com/product/ADC128D818
+ * Datasheet: https://www.ti.com/lit/gpn/adc128d818
+ *
+ * Copyright 2026 9elements
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+#include "hw/sensor/adc128d818.h"
+
+/* 8 bit, r/w */
+#define REG_CONFIG 0x00
+
+/* 8 bit, readonly */
+#define REG_INTERRUPT_STATUS 0x01
+
+/* 8 bit, r/w */
+#define REG_INTERRUPT_MASK 0x03
+
+/* 8 bit, r/w */
+#define REG_CONVERSION_RATE 0x07
+
+/* 8 bit, r/w */
+#define REG_CHANNEL_DISABLE 0x08
+
+/* 8 bit, write-only */
+#define REG_ONE_SHOT 0x09
+
+/* 8 bit, r/w */
+#define REG_DEEP_SHUTDOWN 0x0a
+
+/* 8 bit, r/w */
+#define REG_ADVANCED_CONFIG 0x0b
+
+/* 8 bit, readonly */
+#define REG_BUSY_STATUS 0x0c
+
+/* 16 bit registers, N = 0..7, readonly */
+#define REG_CHANNEL_READING(N) (0x20 + N)
+
+/* 8 bit registers N = 0..15, r/w */
+#define REG_LIMIT(N) (0x2a + N)
+
+/* 8 bit register, readonly */
+#define REG_MANUFACTURER_ID 0x3e
+
+/* 8 bit register, readonly */
+#define REG_REVISION_ID 0x3f
+
+#define ADC128D818_NUM_CHANNELS 8
+
+struct ADC128D818State {
+ I2CSlave i2c;
+
+ uint8_t config;
+ uint8_t interrupt_mask;
+ uint8_t conversion_rate;
+ uint8_t channel_disable;
+ bool deep_shutdown;
+ uint8_t advanced_config;
+
+ /* channel reading registers, 2 bytes each */
+ uint16_t channels[ADC128D818_NUM_CHANNELS];
+
+ /* high and low limit registers 0x2a - 0x39, one byte each */
+ uint8_t limit[ADC128D818_NUM_CHANNELS * 2];
+
+ /* input buffer */
+ uint8_t len;
+ uint8_t buf[2];
+
+ /* output buffer */
+ uint8_t outlen;
+ uint8_t outbuf[2];
+
+ /* selected channel for read/write operation */
+ uint8_t pointer;
+};
+
+struct ADC128D818Class {
+ I2CSlaveClass parent_class;
+};
+
+OBJECT_DECLARE_TYPE(ADC128D818State, ADC128D818Class, ADC128D818)
+
+static void adc128d818_read(ADC128D818State *s)
+{
+ uint8_t ch_num = 0;
+ switch (s->pointer) {
+ case REG_CONFIG:
+ s->outbuf[0] = s->config;
+ break;
+ case REG_INTERRUPT_STATUS:
+ s->outbuf[0] = 0x0; /* POR State */
+ break;
+ case REG_INTERRUPT_MASK:
+ s->outbuf[0] = s->interrupt_mask;
+ break;
+ case REG_CONVERSION_RATE:
+ s->outbuf[0] = s->conversion_rate;
+ break;
+ case REG_CHANNEL_DISABLE:
+ s->outbuf[0] = s->channel_disable;
+ break;
+ case REG_ONE_SHOT:
+ /* not marked as readable */
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: read of register 0x%02x\n",
+ __func__, s->pointer);
+ s->outbuf[0] = 0x0;
+ break;
+ case REG_DEEP_SHUTDOWN:
+ s->outbuf[0] = s->deep_shutdown ? 0x1 : 0x0;
+ break;
+ case REG_ADVANCED_CONFIG:
+ s->outbuf[0] = s->advanced_config & 0b111;
+ break;
+ case REG_BUSY_STATUS:
+ /* not implemented */
+ s->outbuf[0] = 0b00000010; /* POR State */
+ break;
+ case REG_CHANNEL_READING(0):
+ case REG_CHANNEL_READING(1):
+ case REG_CHANNEL_READING(2):
+ case REG_CHANNEL_READING(3):
+ case REG_CHANNEL_READING(4):
+ case REG_CHANNEL_READING(5):
+ case REG_CHANNEL_READING(6):
+ case REG_CHANNEL_READING(7):
+ ch_num = s->pointer - REG_CHANNEL_READING(0);
+ /* high byte comes first, driver reads swapped */
+ s->outbuf[0] = (s->channels[ch_num] >> 8) & 0xff;
+ s->outbuf[1] = s->channels[ch_num] & 0xff;
+ break;
+ case REG_LIMIT(0):
+ case REG_LIMIT(1):
+ case REG_LIMIT(2):
+ case REG_LIMIT(3):
+ case REG_LIMIT(4):
+ case REG_LIMIT(5):
+ case REG_LIMIT(6):
+ case REG_LIMIT(7):
+ case REG_LIMIT(8):
+ case REG_LIMIT(9):
+ case REG_LIMIT(10):
+ case REG_LIMIT(11):
+ case REG_LIMIT(12):
+ case REG_LIMIT(13):
+ case REG_LIMIT(14):
+ case REG_LIMIT(15):
+ s->outbuf[0] = s->limit[s->pointer - REG_LIMIT(0)];
+ break;
+ case REG_MANUFACTURER_ID:
+ s->outbuf[0] = 0x1; /* readonly */
+ break;
+ case REG_REVISION_ID:
+ s->outbuf[0] = 0b00001001; /* readonly */
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: read of register 0x%02x\n",
+ __func__, s->pointer);
+ break;
+ }
+}
+
+static void adc128d818_write_advanced_config(ADC128D818State *s, uint8_t data)
+{
+ /*
+ * Note: Whenever the Advanced Configuration Register is programmed,
+ * all of the values in the Channel Reading Registers and
+ * Interrupt Status Registers will return to their default values.
+ */
+
+ s->advanced_config = (data & 0b111);
+}
+
+static void adc128d818_write(ADC128D818State *s, uint8_t data)
+{
+ trace_adc128d818_write(s->i2c.address, s->pointer, data);
+
+ /* which bits in config register are writable */
+ const uint8_t config_w_mask = 0b10001011;
+ const uint8_t config_ro_mask = (uint8_t)~config_w_mask;
+
+ switch (s->pointer) {
+ case REG_CONFIG:
+ s->config = (s->config & config_ro_mask) | (data & config_w_mask);
+ break;
+ case REG_INTERRUPT_MASK:
+ s->interrupt_mask = data;
+ break;
+ case REG_CONVERSION_RATE:
+ s->conversion_rate = data;
+ break;
+ case REG_CHANNEL_DISABLE:
+ s->channel_disable = data;
+ break;
+ case REG_ONE_SHOT:
+ /*
+ * Initiate a single conversion and comparison cycle when
+ * the device is in shutdown mode or deep shutdown mode, after
+ * which the device returns to the respective mode that it was in
+ *
+ */
+ break;
+ case REG_DEEP_SHUTDOWN:
+ s->deep_shutdown = (data & 0x1) != 0;
+ break;
+ case REG_ADVANCED_CONFIG:
+ adc128d818_write_advanced_config(s, data);
+ break;
+ case REG_LIMIT(0):
+ case REG_LIMIT(1):
+ case REG_LIMIT(2):
+ case REG_LIMIT(3):
+ case REG_LIMIT(4):
+ case REG_LIMIT(5):
+ case REG_LIMIT(6):
+ case REG_LIMIT(7):
+ case REG_LIMIT(8):
+ case REG_LIMIT(9):
+ case REG_LIMIT(10):
+ case REG_LIMIT(11):
+ case REG_LIMIT(12):
+ case REG_LIMIT(13):
+ case REG_LIMIT(14):
+ case REG_LIMIT(15):
+ s->limit[s->pointer - REG_LIMIT(0)] = data;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: write of register 0x%02x\n",
+ __func__, s->pointer);
+ break;
+ }
+}
+
+static int adc128d818_send(I2CSlave *i2c, uint8_t data)
+{
+ ADC128D818State *s = ADC128D818(i2c);
+ trace_adc128d818_send(s->i2c.address, data);
+
+ s->outlen = 0;
+ s->buf[s->len] = data;
+
+ if (s->len == 0) {
+ s->pointer = data;
+ } else if (s->len == 1) {
+ adc128d818_write(s, data);
+ }
+
+ s->len++;
+ return 0;
+}
+
+static uint8_t adc128d818_recv(I2CSlave *i2c)
+{
+ ADC128D818State *s = ADC128D818(i2c);
+ trace_adc128d818_recv(s->i2c.address, s->pointer);
+
+ adc128d818_read(s);
+
+ if (s->outlen >= 2) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: too many bytes read\n", __func__);
+ s->outlen = 0;
+ }
+
+ const uint8_t data = s->outbuf[s->outlen++];
+
+ trace_adc128d818_recv_return(s->i2c.address, data);
+ return data;
+}
+
+static int adc128d818_event(I2CSlave *i2c, enum i2c_event event)
+{
+ ADC128D818State *s = ADC128D818(i2c);
+
+ trace_adc128d818_event(s->i2c.address, event);
+
+ switch (event) {
+ case I2C_START_RECV:
+ s->outlen = 0;
+ break;
+ case I2C_START_SEND:
+ s->len = 0;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_adc128d818 = {
+ .name = TYPE_ADC128D818,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields =
+ (const VMStateField[]){
+ VMSTATE_UINT8(config, ADC128D818State),
+ VMSTATE_UINT8(interrupt_mask, ADC128D818State),
+ VMSTATE_UINT8(conversion_rate, ADC128D818State),
+ VMSTATE_UINT8(channel_disable, ADC128D818State),
+ VMSTATE_BOOL(deep_shutdown, ADC128D818State),
+ VMSTATE_UINT8(advanced_config, ADC128D818State),
+ VMSTATE_UINT16_ARRAY(channels, ADC128D818State,
+ ADC128D818_NUM_CHANNELS),
+ VMSTATE_UINT8_ARRAY(limit, ADC128D818State,
+ ADC128D818_NUM_CHANNELS * 2),
+ VMSTATE_UINT8(len, ADC128D818State),
+ VMSTATE_UINT8_ARRAY(buf, ADC128D818State, 2),
+ VMSTATE_UINT8(outlen, ADC128D818State),
+ VMSTATE_UINT8_ARRAY(outbuf, ADC128D818State, 2),
+ VMSTATE_UINT8(pointer, ADC128D818State),
+ VMSTATE_I2C_SLAVE(i2c, ADC128D818State), VMSTATE_END_OF_LIST() }
+};
+
+static void adc128d818_init(Object *obj) { /* Nothing to do */ }
+
+I2CSlave *adc128d818_init_with_values(I2CBus *bus, uint8_t address,
+ const uint16_t *init_values,
+ uint32_t init_values_size)
+{
+ ADC128D818State *s;
+
+ s = ADC128D818(i2c_slave_new(TYPE_ADC128D818, address));
+
+ for (int i = 0; i < ADC128D818_NUM_CHANNELS; i++) {
+
+ /* arbitrary value */
+ uint16_t value = 0b0000101011010010;
+
+ if (i < init_values_size) {
+ value = init_values[i];
+ }
+ s->channels[i] = value;
+ }
+
+ i2c_slave_realize_and_unref(I2C_SLAVE(s), bus, &error_abort);
+
+ return I2C_SLAVE(s);
+}
+
+static void adc128d818_reset(DeviceState *dev)
+{
+ ADC128D818State *s = ADC128D818(dev);
+
+ s->pointer = 0;
+ s->outlen = 0;
+
+ /* POR-State */
+ s->config = 0b00001000;
+ s->interrupt_mask = 0;
+ s->conversion_rate = 0;
+ s->channel_disable = 0;
+ s->deep_shutdown = 0;
+ s->advanced_config = 0;
+
+ /* No POR-State defined in datasheet */
+ for (int i = 0; i < ADC128D818_NUM_CHANNELS * 2; i++) {
+ s->limit[i] = 0;
+ }
+}
+
+static void adc128d818_realize(DeviceState *dev, Error **errp)
+{
+ ADC128D818State *s = ADC128D818(dev);
+
+ trace_adc128d818_realize(s->i2c.address);
+}
+
+static void adc128d818_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ dc->realize = adc128d818_realize;
+ dc->desc = "Texas Instruments ADC128D818 12-bit ADC with temp sensor";
+ dc->vmsd = &vmstate_adc128d818;
+ dc->legacy_reset = adc128d818_reset;
+ k->event = adc128d818_event;
+ k->recv = adc128d818_recv;
+ k->send = adc128d818_send;
+}
+
+static const TypeInfo adc128d818_info = {
+ .name = TYPE_ADC128D818,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(ADC128D818State),
+ .class_size = sizeof(ADC128D818Class),
+ .instance_init = adc128d818_init,
+ .class_init = adc128d818_class_init,
+};
+
+static void adc128d818_register_types(void)
+{
+ type_register_static(&adc128d818_info);
+}
+
+type_init(adc128d818_register_types)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 76a7d327a9..cc89c65e3f 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -554,6 +554,7 @@ config ASPEED_SOC
select MAX31785
select MAX31790
select MAX11615
+ select ADC128D818
select FSI_APB2OPB_ASPEED
select AT24C
select PCI_EXPRESS
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index 84eede9d84..c9190ff780 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -51,3 +51,7 @@ config MAX31790
config MAX11615
bool
depends on I2C
+
+config ADC128D818
+ bool
+ depends on I2C
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index a1e26604fa..defd7647e7 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -10,3 +10,4 @@ system_ss.add(when: 'CONFIG_ISL_PMBUS_VR', if_true: files('isl_pmbus_vr.c'))
system_ss.add(when: 'CONFIG_MAX31785', if_true: files('max31785.c'))
system_ss.add(when: 'CONFIG_MAX31790', if_true: files('max31790.c'))
system_ss.add(when: 'CONFIG_MAX11615', if_true: files('max11615.c'))
+system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
diff --git a/hw/sensor/trace-events b/hw/sensor/trace-events
index 3fed979e85..4853f1944d 100644
--- a/hw/sensor/trace-events
+++ b/hw/sensor/trace-events
@@ -20,3 +20,11 @@ max11615_recv(uint8_t i2c_addr, uint8_t reg_addr) "i2c_addr: 0x%02x, reg_addr: 0
max11615_recv_return(uint8_t i2c_addr, uint8_t data) "i2c_addr: 0x%02x, returns: 0x%02x"
max11615_event(uint8_t i2c_addr, uint8_t event) "i2c_addr: 0x%02x, event: 0x%02x"
max11615_realize(uint8_t i2c_addr) "i2c_addr: 0x%02x"
+
+# adc128d818.c
+adc128d818_send(uint8_t i2c_addr, uint8_t data) "i2c_addr: 0x%02x, data: 0x%02x"
+adc128d818_write(uint8_t i2c_addr, uint8_t reg, uint8_t data) "i2c_addr: 0x%02x, reg: 0x%02x data: 0x%02x"
+adc128d818_recv(uint8_t i2c_addr, uint8_t reg) "i2c_addr: 0x%02x, reg: 0x%02x"
+adc128d818_recv_return(uint8_t i2c_addr, uint8_t data) "i2c_addr: 0x%02x, returns: 0x%02x"
+adc128d818_event(uint8_t i2c_addr, uint8_t event) "i2c_addr: 0x%02x, event: 0x%02x"
+adc128d818_realize(uint8_t i2c_addr) "i2c_addr: 0x%02x"
diff --git a/tests/functional/arm/test_aspeed_fby4.py b/tests/functional/arm/test_aspeed_fby4.py
index 319f1a7672..883e058832 100755
--- a/tests/functional/arm/test_aspeed_fby4.py
+++ b/tests/functional/arm/test_aspeed_fby4.py
@@ -59,6 +59,16 @@ def do_test_arm_aspeed_openbmc_no_network(self, machine, image, uboot,
exec_command_and_wait_for_pattern(self,
"cat /sys/bus/iio/devices/iio:device2/in_voltage_scale", "0.500000000");
+ # ADC128D818 test
+ exec_command_and_wait_for_pattern(self,
+ "cat /sys/bus/i2c/devices/30-001f/hwmon/hwmon0/name", "adc128d818");
+ exec_command_and_wait_for_pattern(self,
+ "cat /sys/bus/i2c/devices/30-001f/hwmon/hwmon0/in0_input", "75");
+ exec_command_and_wait_for_pattern(self,
+ "cat /sys/bus/i2c/devices/30-001f/hwmon/hwmon0/in0_min", "0");
+ exec_command_and_wait_for_pattern(self,
+ "cat /sys/bus/i2c/devices/30-001f/hwmon/hwmon0/in0_max", "0");
+
def test_arm_ast2600_yosemitev4_openbmc(self):
image_path = self.uncompress(self.ASSET_YOSEMITE_V4_FLASH)
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v4 1/5] ast2600: yosemite4 initial support
2026-05-15 14:57 ` [PATCH v4 1/5] ast2600: yosemite4 initial support Alexander Hansen
@ 2026-05-15 16:30 ` Cédric Le Goater
0 siblings, 0 replies; 8+ messages in thread
From: Cédric Le Goater @ 2026-05-15 16:30 UTC (permalink / raw)
To: Alexander Hansen, qemu-devel
Cc: Titus Rwantare, Peter Maydell, Steven Lee, Troy Lee, Jamin Lin,
Kane Chen, Andrew Jeffery, Joel Stanley, qemu-arm
On 5/15/26 16:57, Alexander Hansen wrote:
> Initial patch based on [1] to support yosemite v4 bmc emulation.
>
> The goal of this machine support is to support OpenBMC development.
>
> Reference linux devicetree from openbmc linux is [2].
>
> Status:
> - Enclosure FRU: showing up
> - Management Board FRU: showing up
> - Blade Board FRU: showing up
> - Blade Board sensors: not implemented
> - Blade Chassis FRU: showing up
> - Fan Board FRU: both showing up
> - Fan Board sensors: supported
>
> Overall the emulation is incomplete but already helpful in development
> and testing.
>
> The focus of this initial support is on the FRU eeproms and fanboard
> sensors.
>
> Tested: booted an OpenBMC image for yosemite4 target.
>
> References:
> [1] https://github.com/9elements/qemu/commit/32139f913c2bd0ebe4bd26c46765861f6f9f2d49
> [2] arch/arm/boot/dts/aspeed/aspeed-bmc-facebook-yosemite4.dts
> [3] https://github.com/legoater/qemu-aspeed-boot/pull/5
>
> Cc: Titus Rwantare <titusr@google.com>
> Cc: "Cédric Le Goater" <clg@kaod.org> (maintainer:ASPEED BMCs)
> Cc: Peter Maydell <peter.maydell@linaro.org> (maintainer:ASPEED BMCs)
> Cc: Steven Lee <steven_lee@aspeedtech.com> (reviewer:ASPEED BMCs)
> Cc: Troy Lee <leetroy@gmail.com> (reviewer:ASPEED BMCs)
> Cc: Jamin Lin <jamin_lin@aspeedtech.com> (reviewer:ASPEED BMCs)
> Cc: Kane Chen <kane_chen@aspeedtech.com> (reviewer:ASPEED BMCs)
> Cc: Andrew Jeffery <andrew@codeconstruct.com.au> (reviewer:ASPEED BMCs)
> Cc: Joel Stanley <joel@jms.id.au> (reviewer:ASPEED BMCs)
> Cc: qemu-arm@nongnu.org (open list:ASPEED BMCs)
> Cc: qemu-devel@nongnu.org (open list:All patches CC here)
> Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
> ---
> hw/arm/aspeed_ast2600_fby4.c | 267 +++++++++++++++++++++++++++++++++++
> hw/arm/meson.build | 1 +
> 2 files changed, 268 insertions(+)
> create mode 100644 hw/arm/aspeed_ast2600_fby4.c
(Please add a cover letter)
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4 2/5] ast2600: yosemite4 functional test
2026-05-15 14:57 ` [PATCH v4 2/5] ast2600: yosemite4 functional test Alexander Hansen
@ 2026-05-15 16:31 ` Cédric Le Goater
0 siblings, 0 replies; 8+ messages in thread
From: Cédric Le Goater @ 2026-05-15 16:31 UTC (permalink / raw)
To: Alexander Hansen, qemu-devel
Cc: Titus Rwantare, Peter Maydell, Steven Lee, Troy Lee, Jamin Lin,
Kane Chen, Andrew Jeffery, Joel Stanley, qemu-arm
On 5/15/26 16:57, Alexander Hansen wrote:
> Add a functional test for the Yosemite V4 (fby4-bmc) machine
> that validates:
> - U-Boot initialization and kernel boot sequence
>
> The test uses an OpenBMC image built for the yosemite4 board and
> validates console output through the boot process.
>
> Cc: Titus Rwantare <titusr@google.com>
> Cc: "Cédric Le Goater" <clg@kaod.org> (maintainer:ASPEED BMCs)
> Cc: Peter Maydell <peter.maydell@linaro.org> (maintainer:ASPEED BMCs)
> Cc: Steven Lee <steven_lee@aspeedtech.com> (reviewer:ASPEED BMCs)
> Cc: Troy Lee <leetroy@gmail.com> (reviewer:ASPEED BMCs)
> Cc: Jamin Lin <jamin_lin@aspeedtech.com> (reviewer:ASPEED BMCs)
> Cc: Kane Chen <kane_chen@aspeedtech.com> (reviewer:ASPEED BMCs)
> Cc: Andrew Jeffery <andrew@codeconstruct.com.au> (reviewer:ASPEED BMCs)
> Cc: Joel Stanley <joel@jms.id.au> (reviewer:ASPEED BMCs)
> Cc: qemu-arm@nongnu.org (open list:ASPEED BMCs)
> Cc: qemu-devel@nongnu.org (open list:All patches CC here)
> Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
> ---
> tests/functional/arm/meson.build | 2 ++
> tests/functional/arm/test_aspeed_fby4.py | 44 ++++++++++++++++++++++++
> 2 files changed, 46 insertions(+)
> create mode 100755 tests/functional/arm/test_aspeed_fby4.py
>
> diff --git a/tests/functional/arm/meson.build b/tests/functional/arm/meson.build
> index 2f538f29a2..10c0006f22 100644
> --- a/tests/functional/arm/meson.build
> +++ b/tests/functional/arm/meson.build
> @@ -14,6 +14,7 @@ test_arm_timeouts = {
> 'aspeed_ast2600_sdk_otp' : 720,
> 'aspeed_bletchley' : 480,
> 'aspeed_catalina' : 480,
> + 'aspeed_fby4': 480,
> 'aspeed_gb200nvl_bmc' : 480,
> 'aspeed_rainier' : 480,
> 'bpim2u' : 500,
> @@ -47,6 +48,7 @@ tests_arm_system_thorough = [
> 'aspeed_ast2600_sdk_otp',
> 'aspeed_bletchley',
> 'aspeed_catalina',
> + 'aspeed_fby4',
> 'aspeed_gb200nvl_bmc',
> 'aspeed_rainier',
> 'bpim2u',
> diff --git a/tests/functional/arm/test_aspeed_fby4.py b/tests/functional/arm/test_aspeed_fby4.py
> new file mode 100755
> index 0000000000..a3124c240f
> --- /dev/null
> +++ b/tests/functional/arm/test_aspeed_fby4.py
> @@ -0,0 +1,44 @@
> +#!/usr/bin/env python3
> +#
> +# Functional test that boots the ASPEED machines
> +#
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +
> +from qemu_test import Asset
> +from aspeed import AspeedTest
> +
> +
> +class YosemiteV4Machine(AspeedTest):
> +
> + ASSET_YOSEMITE_V4_FLASH = Asset(
> + 'https://github.com/legoater/qemu-aspeed-boot/raw/refs/heads/master/images/yosemite4-bmc/openbmc-20260505132843/obmc-phosphor-image-yosemite4-20260505132843.static.mtd.xz',
> + 'dff6946363b41f952b15cfc3156482b89fcfc1b0ecfc3ec8b3ed496a5f001ef9')
> +
> + def do_test_arm_aspeed_openbmc_no_network(self, machine, image, uboot,
> + cpu_id, soc):
> +
> + self.set_machine(machine)
> + self.vm.set_console()
> + self.vm.add_args('-drive', f'file={image},if=mtd,format=raw',
> + '-snapshot')
> + self.vm.launch()
> +
> + self.wait_for_console_pattern(f'U-Boot {uboot}')
> + self.wait_for_console_pattern('## Loading kernel from FIT Image')
> + self.wait_for_console_pattern('Starting kernel ...')
> + self.wait_for_console_pattern(f'Booting Linux on physical CPU {cpu_id}')
> + self.wait_for_console_pattern(f'ASPEED {soc}')
> + self.wait_for_console_pattern('/init as init process')
> + # yosemite v4 does not emit the hostname log which is
> + # different from the other machines.
> + self.wait_for_console_pattern('yosemite4 login:')
> +
> + def test_arm_ast2600_yosemitev4_openbmc(self):
> + image_path = self.uncompress(self.ASSET_YOSEMITE_V4_FLASH)
> +
> + self.do_test_arm_aspeed_openbmc_no_network('fby4-bmc', image=image_path,
> + uboot='2019.04', cpu_id='0xf00',
> + soc='AST2600 rev A3')
> +
> +if __name__ == '__main__':
> + AspeedTest.main()
Reviewed-by: Cédric Le Goater <clg@redhat.com>
Thanks,
C.
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-05-15 16:32 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-15 14:57 [PATCH v4 0/5] initial support for yosemite v4 Alexander Hansen
2026-05-15 14:57 ` [PATCH v4 1/5] ast2600: yosemite4 initial support Alexander Hansen
2026-05-15 16:30 ` Cédric Le Goater
2026-05-15 14:57 ` [PATCH v4 2/5] ast2600: yosemite4 functional test Alexander Hansen
2026-05-15 16:31 ` Cédric Le Goater
2026-05-15 14:58 ` [PATCH v4 3/5] hw/sensor: MAX31790 support Alexander Hansen
2026-05-15 14:58 ` [PATCH v4 4/5] hw/sensor: support MAX11615 Alexander Hansen
2026-05-15 14:58 ` [PATCH v4 5/5] hw/sensor: support Texas Instruments ADC128D818 Alexander Hansen
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.