* [PATCH v3 0/5] initial support for yosemite v4
@ 2026-05-12 10:20 Alexander Hansen
2026-05-12 10:20 ` [PATCH v3 1/5] ast2600: yosemite4 initial support Alexander Hansen
` (4 more replies)
0 siblings, 5 replies; 12+ messages in thread
From: Alexander Hansen @ 2026-05-12 10:20 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.
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 +
hw/arm/Kconfig | 3 +
hw/arm/aspeed_ast2600_fby4.c | 281 +++++++++++++
hw/arm/meson.build | 1 +
hw/sensor/Kconfig | 12 +
hw/sensor/adc128d818.c | 414 +++++++++++++++++++
hw/sensor/max11615.c | 202 +++++++++
hw/sensor/max31790.c | 499 +++++++++++++++++++++++
hw/sensor/meson.build | 3 +
hw/sensor/trace-events | 24 ++
include/hw/sensor/adc128d818.h | 20 +
include/hw/sensor/max11615.h | 20 +
include/hw/sensor/max31790.h | 7 +
tests/functional/arm/meson.build | 2 +
tests/functional/arm/test_aspeed_fby4.py | 80 ++++
15 files changed, 1571 insertions(+)
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 100644 include/hw/sensor/adc128d818.h
create mode 100644 include/hw/sensor/max11615.h
create mode 100644 include/hw/sensor/max31790.h
create mode 100755 tests/functional/arm/test_aspeed_fby4.py
--
2.54.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v3 1/5] ast2600: yosemite4 initial support 2026-05-12 10:20 [PATCH v3 0/5] initial support for yosemite v4 Alexander Hansen @ 2026-05-12 10:20 ` Alexander Hansen 2026-05-12 16:13 ` Cédric Le Goater 2026-05-12 10:20 ` [PATCH v3 2/5] ast2600: yosemite4 functional test Alexander Hansen ` (3 subsequent siblings) 4 siblings, 1 reply; 12+ messages in thread From: Alexander Hansen @ 2026-05-12 10:20 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..d79a88e2fe --- /dev/null +++ b/hw/arm/aspeed_ast2600_fby4.c @@ -0,0 +1,267 @@ +/* + * 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 84b8ec5fb5..ccbc23e549 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -60,6 +60,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_qcom-dc-scm-v1.c', -- 2.54.0 ^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v3 1/5] ast2600: yosemite4 initial support 2026-05-12 10:20 ` [PATCH v3 1/5] ast2600: yosemite4 initial support Alexander Hansen @ 2026-05-12 16:13 ` Cédric Le Goater 0 siblings, 0 replies; 12+ messages in thread From: Cédric Le Goater @ 2026-05-12 16:13 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/12/26 12:20, 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. Linux version 6.18.26-80ec78d Nice. this is a commit coming from Andrew. > > 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..d79a88e2fe > --- /dev/null > +++ b/hw/arm/aspeed_ast2600_fby4.c > @@ -0,0 +1,267 @@ > +/* > + * Yosemite V4 "Facebook Yosemite V4" may be ? > + * > + * 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 84b8ec5fb5..ccbc23e549 100644 > --- a/hw/arm/meson.build > +++ b/hw/arm/meson.build > @@ -60,6 +60,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_qcom-dc-scm-v1.c', Reviewed-by: Cédric Le Goater <clg@redhat.com> Thanks, C. ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 2/5] ast2600: yosemite4 functional test 2026-05-12 10:20 [PATCH v3 0/5] initial support for yosemite v4 Alexander Hansen 2026-05-12 10:20 ` [PATCH v3 1/5] ast2600: yosemite4 initial support Alexander Hansen @ 2026-05-12 10:20 ` Alexander Hansen 2026-05-12 16:34 ` Cédric Le Goater 2026-05-12 10:20 ` [PATCH v3 3/5] hw/sensor: MAX31790 support Alexander Hansen ` (2 subsequent siblings) 4 siblings, 1 reply; 12+ messages in thread From: Alexander Hansen @ 2026-05-12 10:20 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 Tested: functional test passed. export QEMU_TEST_QEMU_BINARY=qemu-system-arm ./build/run tests/functional/arm/test_aspeed_fby4.py TAP version 13 ok 1 test_aspeed_fby4.YosemiteV4Machine.test_arm_ast2600_yosemitev4_openbmc 1..1 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] 12+ messages in thread
* Re: [PATCH v3 2/5] ast2600: yosemite4 functional test 2026-05-12 10:20 ` [PATCH v3 2/5] ast2600: yosemite4 functional test Alexander Hansen @ 2026-05-12 16:34 ` Cédric Le Goater 0 siblings, 0 replies; 12+ messages in thread From: Cédric Le Goater @ 2026-05-12 16:34 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/12/26 12:20, Alexander Hansen wrote: > Tested: functional test passed. > > export QEMU_TEST_QEMU_BINARY=qemu-system-arm > ./build/run tests/functional/arm/test_aspeed_fby4.py > TAP version 13 > ok 1 test_aspeed_fby4.YosemiteV4Machine.test_arm_ast2600_yosemitev4_openbmc > 1..1 The above is not useful information for the reader, developer, maintainer. As a commit log, we generally provide some info on what is the test, what the test does, where the FW images come from, what is excluded if so, etc. Something like : 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. The rest looks good. I like adding network but this is up to you. Thanks, C. > 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() ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 3/5] hw/sensor: MAX31790 support 2026-05-12 10:20 [PATCH v3 0/5] initial support for yosemite v4 Alexander Hansen 2026-05-12 10:20 ` [PATCH v3 1/5] ast2600: yosemite4 initial support Alexander Hansen 2026-05-12 10:20 ` [PATCH v3 2/5] ast2600: yosemite4 functional test Alexander Hansen @ 2026-05-12 10:20 ` Alexander Hansen 2026-05-12 16:51 ` Cédric Le Goater 2026-05-12 10:20 ` [PATCH v3 4/5] hw/sensor: support MAX11615 Alexander Hansen 2026-05-12 10:20 ` [PATCH v3 5/5] hw/sensor: support Texas Instruments ADC128D818 Alexander Hansen 4 siblings, 1 reply; 12+ messages in thread From: Alexander Hansen @ 2026-05-12 10:20 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/Kconfig | 1 + hw/arm/aspeed_ast2600_fby4.c | 5 +- hw/sensor/Kconfig | 4 + hw/sensor/max31790.c | 499 +++++++++++++++++++++++ hw/sensor/meson.build | 1 + hw/sensor/trace-events | 8 + include/hw/sensor/max31790.h | 7 + tests/functional/arm/test_aspeed_fby4.py | 18 + 9 files changed, 542 insertions(+), 2 deletions(-) create mode 100644 hw/sensor/max31790.c create mode 100644 include/hw/sensor/max31790.h diff --git a/MAINTAINERS b/MAINTAINERS index 93a1e4e482..9c991f8e70 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3981,6 +3981,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/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/arm/aspeed_ast2600_fby4.c b/hw/arm/aspeed_ast2600_fby4.c index d79a88e2fe..a7c2dc09ea 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/max31790.h" #include "hw/i2c/i2c_mux_pca954x.h" #include "hw/gpio/pca9552.h" @@ -164,7 +165,7 @@ static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize) /* TODO */ /* maxim,max31790 @ 0x20 (pwm) */ - /* TODO */ + i2c_slave_create_simple(bus, TYPE_MAX31790, 0x20); /* * ti,tca6424 @ 0x22 (gpio) @@ -182,7 +183,7 @@ static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize) */ /* maxim,max31790 @ 0x2f (pwm) */ - /* TODO */ + i2c_slave_create_simple(bus, TYPE_MAX31790, 0x2f); /* maxim,max11615 @ 0x33 (adc) */ /* TODO */ 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/max31790.c b/hw/sensor/max31790.c new file mode 100644 index 0000000000..16525cba9b --- /dev/null +++ b/hw/sensor/max31790.c @@ -0,0 +1,499 @@ +/* + * 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 "hw/sensor/max31790.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 "qom/object.h" +#include "trace.h" + +#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_init(Object *obj) { /* Nothing to do */ } + +static void max31790_reset(I2CSlave *i2c) +{ + MAX31790State *s = MAX31790(i2c); + + 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); + + max31790_reset(&s->i2c); +} + +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; + 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), + .instance_init = max31790_init, + .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/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/include/hw/sensor/max31790.h b/include/hw/sensor/max31790.h new file mode 100644 index 0000000000..7ead420926 --- /dev/null +++ b/include/hw/sensor/max31790.h @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef QEMU_MAX31790_H +#define QEMU_MAX31790_H + +#define TYPE_MAX31790 "max31790" + +#endif 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] 12+ messages in thread
* Re: [PATCH v3 3/5] hw/sensor: MAX31790 support 2026-05-12 10:20 ` [PATCH v3 3/5] hw/sensor: MAX31790 support Alexander Hansen @ 2026-05-12 16:51 ` Cédric Le Goater 0 siblings, 0 replies; 12+ messages in thread From: Cédric Le Goater @ 2026-05-12 16:51 UTC (permalink / raw) To: Alexander Hansen, qemu-devel Cc: Titus Rwantare, Paolo Bonzini, Peter Maydell, Philippe Mathieu-Daudé, qemu-arm, Steven Lee, Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley On 5/12/26 12:20, Alexander Hansen wrote: > 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/Kconfig | 1 + > hw/arm/aspeed_ast2600_fby4.c | 5 +- > hw/sensor/Kconfig | 4 + > hw/sensor/max31790.c | 499 +++++++++++++++++++++++ > hw/sensor/meson.build | 1 + > hw/sensor/trace-events | 8 + > include/hw/sensor/max31790.h | 7 + Please add to your .gitconfig : [diff] orderFile = /path/to/qemu/scripts/git.orderfile > tests/functional/arm/test_aspeed_fby4.py | 18 + > 9 files changed, 542 insertions(+), 2 deletions(-) > create mode 100644 hw/sensor/max31790.c > create mode 100644 include/hw/sensor/max31790.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 93a1e4e482..9c991f8e70 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -3981,6 +3981,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/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/arm/aspeed_ast2600_fby4.c b/hw/arm/aspeed_ast2600_fby4.c > index d79a88e2fe..a7c2dc09ea 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/max31790.h" > #include "hw/i2c/i2c_mux_pca954x.h" > #include "hw/gpio/pca9552.h" > > @@ -164,7 +165,7 @@ static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize) > /* TODO */ > > /* maxim,max31790 @ 0x20 (pwm) */ > - /* TODO */ > + i2c_slave_create_simple(bus, TYPE_MAX31790, 0x20); > > /* > * ti,tca6424 @ 0x22 (gpio) > @@ -182,7 +183,7 @@ static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize) > */ > > /* maxim,max31790 @ 0x2f (pwm) */ > - /* TODO */ > + i2c_slave_create_simple(bus, TYPE_MAX31790, 0x2f); > > /* maxim,max11615 @ 0x33 (adc) */ > /* TODO */ > 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/max31790.c b/hw/sensor/max31790.c > new file mode 100644 > index 0000000000..16525cba9b > --- /dev/null > +++ b/hw/sensor/max31790.c > @@ -0,0 +1,499 @@ > +/* > + * 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 "hw/sensor/max31790.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 "qom/object.h" > +#include "trace.h" > + > +#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; indent is wrong : 2-space instead of 4-space Check docs/devel/style.rst. > + > + 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; ok. binary literals are not that common. Nothing wrong though. > +} > + > +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]; a newline to delimit the variables from the code is nice to have. > + 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_init(Object *obj) { /* Nothing to do */ } So don't define max31790_init then. > + > +static void max31790_reset(I2CSlave *i2c) > +{ > + MAX31790State *s = MAX31790(i2c); > + > + 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); > + > + max31790_reset(&s->i2c); calling reset from realize is incorrect. Use dc->reset instead. > +} > + > +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; > + 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), > + .instance_init = max31790_init, > + .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/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/include/hw/sensor/max31790.h b/include/hw/sensor/max31790.h > new file mode 100644 > index 0000000000..7ead420926 > --- /dev/null > +++ b/include/hw/sensor/max31790.h > @@ -0,0 +1,7 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +#ifndef QEMU_MAX31790_H > +#define QEMU_MAX31790_H the header guard definition does not match the path of the file. > + > +#define TYPE_MAX31790 "max31790" I'd use the "max31790" type directly in the machine. As you wish. Thanks, C. > + > +#endif > 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) > ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 4/5] hw/sensor: support MAX11615 2026-05-12 10:20 [PATCH v3 0/5] initial support for yosemite v4 Alexander Hansen ` (2 preceding siblings ...) 2026-05-12 10:20 ` [PATCH v3 3/5] hw/sensor: MAX31790 support Alexander Hansen @ 2026-05-12 10:20 ` Alexander Hansen 2026-05-12 16:55 ` Cédric Le Goater 2026-05-12 16:58 ` Cédric Le Goater 2026-05-12 10:20 ` [PATCH v3 5/5] hw/sensor: support Texas Instruments ADC128D818 Alexander Hansen 4 siblings, 2 replies; 12+ messages in thread From: Alexander Hansen @ 2026-05-12 10:20 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 + hw/arm/Kconfig | 1 + hw/arm/aspeed_ast2600_fby4.c | 8 +- hw/sensor/Kconfig | 4 + hw/sensor/max11615.c | 202 +++++++++++++++++++++++ hw/sensor/meson.build | 1 + hw/sensor/trace-events | 8 + include/hw/sensor/max11615.h | 20 +++ tests/functional/arm/test_aspeed_fby4.py | 8 + 9 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 hw/sensor/max11615.c create mode 100644 include/hw/sensor/max11615.h diff --git a/MAINTAINERS b/MAINTAINERS index 9c991f8e70..a9c88996a2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3981,6 +3981,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/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/arm/aspeed_ast2600_fby4.c b/hw/arm/aspeed_ast2600_fby4.c index a7c2dc09ea..036f5b9e38 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/max31790.h" +#include "hw/sensor/max11615.h" #include "hw/i2c/i2c_mux_pca954x.h" #include "hw/gpio/pca9552.h" @@ -186,7 +187,12 @@ static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize) i2c_slave_create_simple(bus, TYPE_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/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/max11615.c b/hw/sensor/max11615.c new file mode 100644 index 0000000000..7950e00e33 --- /dev/null +++ b/hw/sensor/max11615.c @@ -0,0 +1,202 @@ +/* + * 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 "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/max11615.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 channelSelect = (data >> 1) & 0b1111; + + /* Table 3. Channel Selection (AIN0 ... AIN11) */ + if (channelSelect > 11) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid channel select", __func__); + channelSelect = 11; + } + s->pointer = channelSelect; +} + +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_realize(DeviceState *dev, Error **errp) +{ + MAX11615State *s = MAX11615(dev); + + trace_max11615_realize(s->i2c.address); + + s->pointer = 0; + s->outlen = 0; +} + +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; + k->event = max11615_event; + k->recv = max11615_recv; + k->send = max11615_send; +} + +static const TypeInfo max31790_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 max31790_register_types(void) +{ + type_register_static(&max31790_info); +} + +type_init(max31790_register_types) 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/include/hw/sensor/max11615.h b/include/hw/sensor/max11615.h new file mode 100644 index 0000000000..ccc0c9bc2e --- /dev/null +++ b/include/hw/sensor/max11615.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef QEMU_MAX11615_H +#define QEMU_MAX11615_H + +#include <stdint.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/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] 12+ messages in thread
* Re: [PATCH v3 4/5] hw/sensor: support MAX11615 2026-05-12 10:20 ` [PATCH v3 4/5] hw/sensor: support MAX11615 Alexander Hansen @ 2026-05-12 16:55 ` Cédric Le Goater 2026-05-12 16:58 ` Cédric Le Goater 1 sibling, 0 replies; 12+ messages in thread From: Cédric Le Goater @ 2026-05-12 16:55 UTC (permalink / raw) To: Alexander Hansen, qemu-devel Cc: Titus Rwantare, Philippe Mathieu-Daudé, Paolo Bonzini, Peter Maydell, qemu-arm, Steven Lee, Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley On 5/12/26 12:20, Alexander Hansen wrote: > 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 + > hw/arm/Kconfig | 1 + > hw/arm/aspeed_ast2600_fby4.c | 8 +- > hw/sensor/Kconfig | 4 + > hw/sensor/max11615.c | 202 +++++++++++++++++++++++ > hw/sensor/meson.build | 1 + > hw/sensor/trace-events | 8 + > include/hw/sensor/max11615.h | 20 +++ > tests/functional/arm/test_aspeed_fby4.py | 8 + > 9 files changed, 252 insertions(+), 1 deletion(-) > create mode 100644 hw/sensor/max11615.c > create mode 100644 include/hw/sensor/max11615.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 9c991f8e70..a9c88996a2 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -3981,6 +3981,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/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/arm/aspeed_ast2600_fby4.c b/hw/arm/aspeed_ast2600_fby4.c > index a7c2dc09ea..036f5b9e38 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/max31790.h" > +#include "hw/sensor/max11615.h" > #include "hw/i2c/i2c_mux_pca954x.h" > #include "hw/gpio/pca9552.h" > > @@ -186,7 +187,12 @@ static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize) > i2c_slave_create_simple(bus, TYPE_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/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/max11615.c b/hw/sensor/max11615.c > new file mode 100644 > index 0000000000..7950e00e33 > --- /dev/null > +++ b/hw/sensor/max11615.c > @@ -0,0 +1,202 @@ > +/* > + * 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 "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/max11615.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 channelSelect = (data >> 1) & 0b1111; channel_select > + > + /* Table 3. Channel Selection (AIN0 ... AIN11) */ > + if (channelSelect > 11) { > + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid channel select", __func__); > + channelSelect = 11; > + } > + s->pointer = channelSelect; > +} > + > +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_realize(DeviceState *dev, Error **errp) > +{ > + MAX11615State *s = MAX11615(dev); > + > + trace_max11615_realize(s->i2c.address); > + > + s->pointer = 0; > + s->outlen = 0; > +} > + > +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; > + k->event = max11615_event; > + k->recv = max11615_recv; > + k->send = max11615_send; > +} > + > +static const TypeInfo max31790_info = { wrong name. Thanks, C. > + .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 max31790_register_types(void) > +{ > + type_register_static(&max31790_info); > +} > + > +type_init(max31790_register_types) > 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/include/hw/sensor/max11615.h b/include/hw/sensor/max11615.h > new file mode 100644 > index 0000000000..ccc0c9bc2e > --- /dev/null > +++ b/include/hw/sensor/max11615.h > @@ -0,0 +1,20 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +#ifndef QEMU_MAX11615_H > +#define QEMU_MAX11615_H > + > +#include <stdint.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/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) > ^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v3 4/5] hw/sensor: support MAX11615 2026-05-12 10:20 ` [PATCH v3 4/5] hw/sensor: support MAX11615 Alexander Hansen 2026-05-12 16:55 ` Cédric Le Goater @ 2026-05-12 16:58 ` Cédric Le Goater 1 sibling, 0 replies; 12+ messages in thread From: Cédric Le Goater @ 2026-05-12 16:58 UTC (permalink / raw) To: Alexander Hansen, qemu-devel Cc: Titus Rwantare, Philippe Mathieu-Daudé, Paolo Bonzini, Peter Maydell, qemu-arm, Steven Lee, Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley On 5/12/26 12:20, Alexander Hansen wrote: > 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 + > hw/arm/Kconfig | 1 + > hw/arm/aspeed_ast2600_fby4.c | 8 +- > hw/sensor/Kconfig | 4 + > hw/sensor/max11615.c | 202 +++++++++++++++++++++++ > hw/sensor/meson.build | 1 + > hw/sensor/trace-events | 8 + > include/hw/sensor/max11615.h | 20 +++ > tests/functional/arm/test_aspeed_fby4.py | 8 + > 9 files changed, 252 insertions(+), 1 deletion(-) > create mode 100644 hw/sensor/max11615.c > create mode 100644 include/hw/sensor/max11615.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 9c991f8e70..a9c88996a2 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -3981,6 +3981,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/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/arm/aspeed_ast2600_fby4.c b/hw/arm/aspeed_ast2600_fby4.c > index a7c2dc09ea..036f5b9e38 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/max31790.h" > +#include "hw/sensor/max11615.h" > #include "hw/i2c/i2c_mux_pca954x.h" > #include "hw/gpio/pca9552.h" > > @@ -186,7 +187,12 @@ static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize) > i2c_slave_create_simple(bus, TYPE_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/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/max11615.c b/hw/sensor/max11615.c > new file mode 100644 > index 0000000000..7950e00e33 > --- /dev/null > +++ b/hw/sensor/max11615.c > @@ -0,0 +1,202 @@ > +/* > + * 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 "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/max11615.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 channelSelect = (data >> 1) & 0b1111; > + > + /* Table 3. Channel Selection (AIN0 ... AIN11) */ > + if (channelSelect > 11) { > + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid channel select", __func__); > + channelSelect = 11; > + } > + s->pointer = channelSelect; > +} > + > +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_realize(DeviceState *dev, Error **errp) > +{ > + MAX11615State *s = MAX11615(dev); > + > + trace_max11615_realize(s->i2c.address); > + > + s->pointer = 0; > + s->outlen = 0; These should be in a reset handler. Thanks, C. > +} > + > +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; > + k->event = max11615_event; > + k->recv = max11615_recv; > + k->send = max11615_send; > +} > + > +static const TypeInfo max31790_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 max31790_register_types(void) > +{ > + type_register_static(&max31790_info); > +} > + > +type_init(max31790_register_types) > 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/include/hw/sensor/max11615.h b/include/hw/sensor/max11615.h > new file mode 100644 > index 0000000000..ccc0c9bc2e > --- /dev/null > +++ b/include/hw/sensor/max11615.h > @@ -0,0 +1,20 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +#ifndef QEMU_MAX11615_H > +#define QEMU_MAX11615_H > + > +#include <stdint.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/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) > ^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 5/5] hw/sensor: support Texas Instruments ADC128D818 2026-05-12 10:20 [PATCH v3 0/5] initial support for yosemite v4 Alexander Hansen ` (3 preceding siblings ...) 2026-05-12 10:20 ` [PATCH v3 4/5] hw/sensor: support MAX11615 Alexander Hansen @ 2026-05-12 10:20 ` Alexander Hansen 2026-05-12 16:57 ` Cédric Le Goater 4 siblings, 1 reply; 12+ messages in thread From: Alexander Hansen @ 2026-05-12 10:20 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 + hw/arm/Kconfig | 1 + hw/arm/aspeed_ast2600_fby4.c | 9 +- hw/sensor/Kconfig | 4 + hw/sensor/adc128d818.c | 414 +++++++++++++++++++++++ hw/sensor/meson.build | 1 + hw/sensor/trace-events | 8 + include/hw/sensor/adc128d818.h | 20 ++ tests/functional/arm/test_aspeed_fby4.py | 10 + 9 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 hw/sensor/adc128d818.c create mode 100644 include/hw/sensor/adc128d818.h diff --git a/MAINTAINERS b/MAINTAINERS index a9c88996a2..43831b67c4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3979,6 +3979,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/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/arm/aspeed_ast2600_fby4.c b/hw/arm/aspeed_ast2600_fby4.c index 036f5b9e38..4fd3a713e2 100644 --- a/hw/arm/aspeed_ast2600_fby4.c +++ b/hw/arm/aspeed_ast2600_fby4.c @@ -13,6 +13,7 @@ #include "hw/nvram/eeprom_at24c.h" #include "hw/sensor/max31790.h" #include "hw/sensor/max11615.h" +#include "hw/sensor/adc128d818.h" #include "hw/i2c/i2c_mux_pca954x.h" #include "hw/gpio/pca9552.h" @@ -163,7 +164,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, TYPE_MAX31790, 0x20); 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/adc128d818.c b/hw/sensor/adc128d818.c new file mode 100644 index 0000000000..83a4d43846 --- /dev/null +++ b/hw/sensor/adc128d818.c @@ -0,0 +1,414 @@ +/* + * 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(I2CSlave *i2c) +{ + ADC128D818State *s = ADC128D818(i2c); + + 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); + + adc128d818_reset(&s->i2c); +} + +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 Intstruments ADC128D818 12-bit ADC with temp sensor"; + dc->vmsd = &vmstate_adc128d818; + 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/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/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/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] 12+ messages in thread
* Re: [PATCH v3 5/5] hw/sensor: support Texas Instruments ADC128D818 2026-05-12 10:20 ` [PATCH v3 5/5] hw/sensor: support Texas Instruments ADC128D818 Alexander Hansen @ 2026-05-12 16:57 ` Cédric Le Goater 0 siblings, 0 replies; 12+ messages in thread From: Cédric Le Goater @ 2026-05-12 16:57 UTC (permalink / raw) To: Alexander Hansen, qemu-devel Cc: Titus Rwantare, Paolo Bonzini, Peter Maydell, Philippe Mathieu-Daudé, qemu-arm, Steven Lee, Troy Lee, Jamin Lin, Kane Chen, Andrew Jeffery, Joel Stanley On 5/12/26 12:20, Alexander Hansen wrote: > 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 + > hw/arm/Kconfig | 1 + > hw/arm/aspeed_ast2600_fby4.c | 9 +- > hw/sensor/Kconfig | 4 + > hw/sensor/adc128d818.c | 414 +++++++++++++++++++++++ > hw/sensor/meson.build | 1 + > hw/sensor/trace-events | 8 + > include/hw/sensor/adc128d818.h | 20 ++ > tests/functional/arm/test_aspeed_fby4.py | 10 + > 9 files changed, 467 insertions(+), 1 deletion(-) > create mode 100644 hw/sensor/adc128d818.c > create mode 100644 include/hw/sensor/adc128d818.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index a9c88996a2..43831b67c4 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -3979,6 +3979,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/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/arm/aspeed_ast2600_fby4.c b/hw/arm/aspeed_ast2600_fby4.c > index 036f5b9e38..4fd3a713e2 100644 > --- a/hw/arm/aspeed_ast2600_fby4.c > +++ b/hw/arm/aspeed_ast2600_fby4.c > @@ -13,6 +13,7 @@ > #include "hw/nvram/eeprom_at24c.h" > #include "hw/sensor/max31790.h" > #include "hw/sensor/max11615.h" > +#include "hw/sensor/adc128d818.h" > #include "hw/i2c/i2c_mux_pca954x.h" > #include "hw/gpio/pca9552.h" > > @@ -163,7 +164,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, TYPE_MAX31790, 0x20); > 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/adc128d818.c b/hw/sensor/adc128d818.c > new file mode 100644 > index 0000000000..83a4d43846 > --- /dev/null > +++ b/hw/sensor/adc128d818.c > @@ -0,0 +1,414 @@ > +/* > + * 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(I2CSlave *i2c) > +{ > + ADC128D818State *s = ADC128D818(i2c); > + > + 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); > + > + adc128d818_reset(&s->i2c); use dc->reset. > +} > + > +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 Intstruments ADC128D818 12-bit ADC with temp sensor"; Intstruments > + dc->vmsd = &vmstate_adc128d818; > + 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/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/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/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) > Thanks, C. ^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-05-12 16:58 UTC | newest] Thread overview: 12+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-05-12 10:20 [PATCH v3 0/5] initial support for yosemite v4 Alexander Hansen 2026-05-12 10:20 ` [PATCH v3 1/5] ast2600: yosemite4 initial support Alexander Hansen 2026-05-12 16:13 ` Cédric Le Goater 2026-05-12 10:20 ` [PATCH v3 2/5] ast2600: yosemite4 functional test Alexander Hansen 2026-05-12 16:34 ` Cédric Le Goater 2026-05-12 10:20 ` [PATCH v3 3/5] hw/sensor: MAX31790 support Alexander Hansen 2026-05-12 16:51 ` Cédric Le Goater 2026-05-12 10:20 ` [PATCH v3 4/5] hw/sensor: support MAX11615 Alexander Hansen 2026-05-12 16:55 ` Cédric Le Goater 2026-05-12 16:58 ` Cédric Le Goater 2026-05-12 10:20 ` [PATCH v3 5/5] hw/sensor: support Texas Instruments ADC128D818 Alexander Hansen 2026-05-12 16:57 ` Cédric Le Goater
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.