From nobody Mon May 25 08:54:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=9elements.com ARC-Seal: i=1; a=rsa-sha256; t=1777995933; cv=none; d=zohomail.com; s=zohoarc; b=cb46kmEgXUN1NwV4cdYe52vi/ReHqrfp9DGEmRtC/ty2CpE07SDN8lsxPGugTwt+1NrQs42g3dYFOkJPLtuKYZDvl/keRu81vxP68TwXo/OBM8Oxe/OkiHIisPaoJfJ6SWiVegvcFJbf11Gs+9O23na4wcVwJGFsS3zUtv/oMPQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1777995933; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=w4k2eG+DhQYVJrvzCPtBPW/Oz+GhJIAbEOJ29Pi+g0Q=; b=HI6xJvvAQe2k24plqsifAyd+E0ur01+1b3a1iJvAoWxoZasZcNPEENmcbOBAnMF8ccNZLJOGWzg99dohJmDuT7qaS2TA/yN10sHushwOaDRbf5kdWZWSinQEY6vAMe/wb7CMnEZPXC8ALD9NFjZ0MVxZ67F9W9R6BpN78gIfBVs= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1777995933458300.5915488738806; Tue, 5 May 2026 08:45:33 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wKHxU-00080c-LI; Tue, 05 May 2026 11:45:18 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wKHxH-0007oi-H6 for qemu-devel@nongnu.org; Tue, 05 May 2026 11:45:03 -0400 Received: from mail-wm1-x32a.google.com ([2a00:1450:4864:20::32a]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wKHxB-0002f8-7w for qemu-devel@nongnu.org; Tue, 05 May 2026 11:45:02 -0400 Received: by mail-wm1-x32a.google.com with SMTP id 5b1f17b1804b1-4896c22fcbaso41568795e9.0 for ; Tue, 05 May 2026 08:44:56 -0700 (PDT) Received: from alexanderarchlinux ([188.111.3.154]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-45054b02abbsm5255305f8f.18.2026.05.05.08.44.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 05 May 2026 08:44:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=9elements.com; s=google; t=1777995895; x=1778600695; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=w4k2eG+DhQYVJrvzCPtBPW/Oz+GhJIAbEOJ29Pi+g0Q=; b=L8Q7MoaXaFkOvxhmRhBL4iEDBZOcA3xcVlGLBADWaGR/OAmx/xZj4CdboDwX9Qk2jV LCxgIU/Tp1ZXsrNfGQAGzO8KxhzzrfnqZ4ceTAnDfPFtiscig0PCuLdIe/3xVB4YdPO0 P5j7NKoQFj7tTLMCnbK34tAviGF1tA2gHbc8TgvFOYuwxzyLE8znGIUYZy4Xx9JzPtm/ CZ4ZrpN8msPIw6/tXS7jYUvwIfdrRjASl+6iuVw5pt90X4ef5L6JFXrxDS/zYXWPEtcg iTVPs59j0sDSNkPxffGGBYED3hMT0uFS2J+blfvYgFkou6pEBTRJ2+bv5qQ0UZZW5O9s AG+A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777995895; x=1778600695; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=w4k2eG+DhQYVJrvzCPtBPW/Oz+GhJIAbEOJ29Pi+g0Q=; b=KdEklRvOHkv7eUPzqNa8DJ2qdEKTn54vKR1DY0PFxRiRs73DzBeroDtDHjFQg68E7n JvPeibGhpXPZR4d0xLygp/FhdxUnKpeKvMOjr2dVPPp0rkT0RbriYlLrmUq5Px2ox2tH 8AQd5pO2ptI5E2uL3CLeKEpJcIKYBSUpSKUuaAfiiHyOYJJEQwws7pCll2f/hUXAzEI3 fjNeZoGFmSm+D/ZwRHw38tbTbFKj6IGGlN/ZJGVeR+MXCR0UCVqqfihx3X6SPR7M1ZpB WTO0sXbqc8jCaX/R2FSH3Nz6UsIAkgOkfbHyfV5YwuDdD6O+CZ8TNY1M5O8jl2xn9oSL jprw== X-Gm-Message-State: AOJu0Yyr6iijSTLnY7DBDaYutOVZ77/mUD+ph0c7nVwuri1bprhfnkYE mrpviinK6s1x0qZZPzTNllPz/HlTPfI+Pbza+Sn3kEVRzULqNKd/Lj/TfUCs6hHUXDfF/+bEv5a qyFVrCso/Yw== X-Gm-Gg: AeBDietg2XYTaKo+hOBhf2kgWGKpPC9p0MhskERjOWag4mmo+hboDP8xF5neTKdhtrf Yjv4zOqgc2I20X+HMuOhMqohxiSmq79qSbF5A7moLvPsw2r8uPIuLW3EgIzSsjvSqzfE+g2wCGJ X84pHo/0xft6nc4fMcc8b1Wb9daBj9WiLC0IkE/cFjNYS9KIDZXd6MqVLhPaIugwTz4jpDyqHHY gJdvugxfuHnc5gGmTy8SPB82M7TUUT+IkcS36qAd5Fu47kdwXoQFL5wdOe8lRB0of+7D62XItHS egVph8LwvE0PYaNZyh6/RFeZ2KHWQ+wFm83/8WLPvJo+4RHFNyPaJFqXjiQ6HP6ExpE5Zcy+b/x OIUAcLnUbB/VNJm9jDiZfe5Kne6n0Hw5t5G1dQ6RXX3V0KZJs/oVZgUz9mKHHUXFWi86ZwWdX13 MCoRvd+GSeKSUbDG0snKx6H7JMqLfYKJ+wWV2n/IVdciV9YFsdYCwL1Jw= X-Received: by 2002:a05:6000:24c9:b0:43d:21a:9a3e with SMTP id ffacd0b85a97d-44bb65dfc18mr23245661f8f.32.1777995893987; Tue, 05 May 2026 08:44:53 -0700 (PDT) From: Alexander Hansen To: qemu-devel@nongnu.org Cc: Alexander Hansen , Paolo Bonzini , Peter Maydell , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , qemu-arm@nongnu.org Subject: [PATCH v2 1/4] hw/sensor: MAX31790 support Date: Tue, 5 May 2026 17:42:55 +0200 Message-ID: <20260505154410.230969-2-alexander.hansen@9elements.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260505154410.230969-1-alexander.hansen@9elements.com> References: <20260505154410.230969-1-alexander.hansen@9elements.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::32a; envelope-from=alexander.hansen@9elements.com; helo=mail-wm1-x32a.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @9elements.com) X-ZM-MESSAGEID: 1777995935778158500 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 !=3D 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 pw= m5 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_enabl= e 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/MAX= 31790.pdf Cc: Paolo Bonzini Cc: Peter Maydell Cc: "Philippe Mathieu-Daud=C3=A9" Cc: qemu-arm@nongnu.org Cc: qemu-devel@nongnu.org Signed-off-by: Alexander Hansen --- hw/arm/Kconfig | 1 + 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 + 6 files changed, 520 insertions(+) create mode 100644 hw/sensor/max31790.c create mode 100644 include/hw/sensor/max31790.h diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index c31752e83a..c62db7e609 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -551,6 +551,7 @@ config ASPEED_SOC select LED select PMBUS select MAX31785 + select MAX31790 select FSI_APB2OPB_ASPEED select AT24C select PCI_EXPRESS diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig index bc6331b4ab..ece2f2b167 100644 --- a/hw/sensor/Kconfig +++ b/hw/sensor/Kconfig @@ -43,3 +43,7 @@ config ISL_PMBUS_VR config MAX31785 bool depends on PMBUS + +config MAX31790 + bool + depends on PMBUS diff --git a/hw/sensor/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/MAX= 31790.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 =3D 0 .. 5 */ +#define MAX31790_REG_FAN_CONFIG(N) (0x02 + N) + +/* 0x08 to 0x0d: N =3D 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 =3D 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 =3D 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 =3D 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 =3D 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 =3D 0; + uint8_t out0 =3D 0; + uint8_t out1 =3D 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 =3D 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 =3D 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 =3D 0x00; + out1 =3D 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 =3D (s->pointer - MAX31790_REG_TACH_COUNT_MSB(0)) / 2; + out0 =3D (s->rpm[index] >> 8) & 0xff; + out1 =3D 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 =3D (s->pointer - MAX31790_REG_TACH_COUNT_LSB(0)) / 2; + out0 =3D 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 =3D (s->pointer - MAX31790_REG_PWM_DUTY_CYCLE_MSB(0)) / 2; + out0 =3D (s->pwm[index] >> 8) & 0xff; + out1 =3D 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 =3D (s->pointer - MAX31790_REG_PWM_DUTY_CYCLE_LSB(0)) / 2; + out0 =3D 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 =3D (s->pointer - MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(0)) / 2; + out0 =3D (s->pwm[index] >> 8) & 0xff; + out1 =3D 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 =3D (s->pointer - MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(0)) / 2; + out0 =3D 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 =3D (s->pointer - MAX31790_REG_TACH_TARGET_COUNT_MSB(0)) / 2; + out0 =3D (s->tach_target[index] >> 8) & 0xff; + out1 =3D 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 =3D (s->pointer - MAX31790_REG_TACH_TARGET_COUNT_LSB(0)) / 2; + out0 =3D s->tach_target[index] & 0xff; + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: read of register %d", __func__, s->point= er); + break; + } + + s->outbuf[0] =3D out0; + s->outbuf[1] =3D out1; +} + +static void max31790_set_rpm(MAX31790State *s, size_t index, uint16_t rpm) +{ + /* datasheet: lowest 5 bits are 0 */ + s->rpm[index] =3D rpm & ~0b11111; +} + +static void max31790_pwm_write(MAX31790State *s, size_t index, uint16_t va= lue) +{ + trace_max31790_pwm_write(s->i2c.address, index, value); + + s->pwm[index] =3D value; + + /* change rpm based on pwm input */ + const uint16_t pwm_no_reserve =3D 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 =3D 0; + const uint8_t value0 =3D s->buf[0]; + const uint8_t value1 =3D 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 =3D (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 =3D (s->pointer - MAX31790_REG_TACH_TARGET_COUNT_MSB(0)) / 2; + s->tach_target[index] =3D (value0 << 8) | value1; + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: write to register %d", __func__, s->poin= ter); + break; + } +} + +static void max31790_write_1_byte(MAX31790State *s) +{ + + size_t index =3D 0; + uint16_t pwm =3D 0; + const uint8_t value =3D 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)] =3D 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)] =3D 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 =3D (s->pointer - MAX31790_REG_PWM_TARGET_DUTY_CYCLE_MSB(0)) / 2; + pwm =3D (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 =3D (s->pointer - MAX31790_REG_PWM_TARGET_DUTY_CYCLE_LSB(0)) / 2; + pwm =3D (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 =3D (s->pointer - MAX31790_REG_TACH_TARGET_COUNT_MSB(0)) / 2; + s->tach_target[index] =3D (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 =3D (s->pointer - MAX31790_REG_TACH_TARGET_COUNT_LSB(0)) / 2; + s->tach_target[index] =3D (s->tach_target[index] & 0xff00) | value; + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: write to register %d", __func__, s->poin= ter); + break; + } +} + +static int max31790_send(I2CSlave *i2c, uint8_t data) +{ + MAX31790State *s =3D MAX31790(i2c); + + trace_max31790_send(s->i2c.address, data); + + if (s->len =3D=3D 0) { + /* first byte is the register pointer for a read / write operation */ + s->pointer =3D 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] =3D data; + s->len++; + + if (s->len =3D=3D 2) { + max31790_write_1_byte(s); + } else if (s->len =3D=3D 3) { + max31790_write_2_byte(s); + } + + return 0; +} + +static uint8_t max31790_recv(I2CSlave *i2c) +{ + MAX31790State *s =3D MAX31790(i2c); + trace_max31790_recv(s->i2c.address, s->pointer); + + max31790_read(s); + s->len =3D 0; + + if (s->outlen >=3D 2) { + /* error */ + s->outlen =3D 0; + } + + const uint8_t data =3D 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 =3D MAX31790(i2c); + + trace_max31790_event(s->i2c.address, event); + + switch (event) { + case I2C_START_RECV: + s->outlen =3D 0; + break; + case I2C_START_SEND: + s->len =3D 0; + break; + default: + break; + } + + return 0; +} + +static const VMStateDescription vmstate_max31790 =3D { + .name =3D TYPE_MAX31790, + .version_id =3D 0, + .minimum_version_id =3D 0, + .fields =3D (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 =3D MAX31790(i2c); + + for (int i =3D 0; i < MAX31790_NUM_FANS; i++) { + /* POR-State 0b 0XX0 0000 */ + s->fan_config[i] =3D 0b00000000; + + /* same as POR-State */ + s->tach_target[i] =3D 0b0011110000000000; + + /* same as POR-State */ + s->fan_dynamics[i] =3D 0b01001100; + + s->pwm[i] =3D 0; + } + + for (int i =3D 0; i < MAX31790_NUM_TACHS; i++) { + max31790_set_rpm(s, i, 0x4444); + } +} + +static void max31790_realize(DeviceState *dev, Error **errp) +{ + MAX31790State *s =3D MAX31790(dev); + + trace_max31790_realize(s->i2c.address); + + max31790_reset(&s->i2c); +} + +static void max31790_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + I2CSlaveClass *k =3D I2C_SLAVE_CLASS(klass); + + dc->realize =3D max31790_realize; + dc->desc =3D "Maxim MAX31790 6-Channel Fan Controller"; + dc->vmsd =3D &vmstate_max31790; + k->event =3D max31790_event; + k->recv =3D max31790_recv; + k->send =3D max31790_send; +} + +static const TypeInfo max31790_info =3D { + .name =3D TYPE_MAX31790, + .parent =3D TYPE_I2C_SLAVE, + .instance_size =3D sizeof(MAX31790State), + .class_size =3D sizeof(MAX31790Class), + .instance_init =3D max31790_init, + .class_init =3D 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('max3= 4451.c')) system_ss.add(when: 'CONFIG_LSM303DLHC_MAG', if_true: files('lsm303dlhc_ma= g.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_a= ddr: 0x%02x" +max31790_recv_return(uint8_t i2c_addr, uint8_t data) "i2c_addr: 0x%02x, re= turns: 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_ad= dr: 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 --=20 2.54.0 From nobody Mon May 25 08:54:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=9elements.com ARC-Seal: i=1; a=rsa-sha256; t=1777995939; cv=none; d=zohomail.com; s=zohoarc; b=lT05Ld2pAFIFg8GSxP/XI3obrZY4wgrtklAv7wWI5fuED0a2boDoJZdwriTCGb3izhAZBqRRULaMiysfACXfb2z4t+Sh6Enhb+JxKaHFNUhcBuznOaLVD4WiErDyKvlR7c0Qr5aVftXdvq1FA13gUF5QIbC+NxYFi8hcZbMAodU= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1777995939; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=wl+T5/Hl4Nz9fenu524zvqWBWL4DMiLm/8b3Btxhv0k=; b=lvE8HI6RFFXZiR8C20eIEX50BWM6U3DjZxSSU+liJlEtt50qas7wyZSTlCvbhHMp5UFcAC+OQ5yNvKO0sEigBXtWQ8zQOjC7egMkdN2aSEYbsV6MojmDayM7jXv0JB9L+MSNFMUlpT3iugNB91FP0fCBHdgqQBV4EyfrHIclN8E= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1777995939647175.4287684249474; Tue, 5 May 2026 08:45:39 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wKHxa-00087T-B5; Tue, 05 May 2026 11:45:22 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wKHxJ-0007pe-9B for qemu-devel@nongnu.org; Tue, 05 May 2026 11:45:05 -0400 Received: from mail-wm1-x332.google.com ([2a00:1450:4864:20::332]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wKHxF-0002fm-3v for qemu-devel@nongnu.org; Tue, 05 May 2026 11:45:04 -0400 Received: by mail-wm1-x332.google.com with SMTP id 5b1f17b1804b1-488a14c31eeso41334745e9.0 for ; Tue, 05 May 2026 08:45:00 -0700 (PDT) Received: from alexanderarchlinux ([188.111.3.154]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-45054b02abbsm5255305f8f.18.2026.05.05.08.44.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 05 May 2026 08:44:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=9elements.com; s=google; t=1777995899; x=1778600699; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=wl+T5/Hl4Nz9fenu524zvqWBWL4DMiLm/8b3Btxhv0k=; b=V3NvrB0DysQZRUK5uq02+k4Xbx1P3RuhF+h0JZXrYfjiCrE5f2ynIt0skgggJk/P0g 7LLwkNycBj4o4Exe+XsWRscO5RBM8fyErzbzqQMJzA9X0IleLAHJ3ihNbJN4UElFxRFe vQqtIlv3d6H0LEoZ5g8CWt1hRBWCDjzZX8cefRR1JYavVA/H86cuolvNCAFhYY0Q4L/Y 3I59j8/SUeGB0wsElKLCT/uQlUO1j2FYeOVgeO5WJsASfHochzEfW3Fe5WBXrRJBz2tQ Mcg9ZzbDM8h2/3zoJkgvokBBZDUT7w8opi3K0CA1RIBjaY3jbY083gg9LfjkMpPtNidp B3dA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777995899; x=1778600699; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=wl+T5/Hl4Nz9fenu524zvqWBWL4DMiLm/8b3Btxhv0k=; b=Wl65lTJ7yATv+P7vzkudWL+F1jczXKq+7Oyu9m489rQx6uNuQgfCC6780LHc9U3lR0 oUqVJaMR5I8JniR4Gdfpwe3+ug+zJ1UGXVc9uio4x2qCISgq5RSoB1+zl6lvnAvqWWhS V/nxlbj3eG7JGgRwk9la4sO+0spDxXuDc/vaJObFmdtP4XNcyGYdA07ROxo4Wmg+pGN2 dVSBN9YAM0s0lBFbJq3oHBXg32IimlIpZjRpKFZcq/pgisq/lXc5cKTG9htB22UAw/Wi v3QDFGMKft8rEwyrI9BrosQbsIUGC3drJDr0DAaUfjIDKTwHQqahdCcCg5+1O9WWeMVD Q+OQ== X-Gm-Message-State: AOJu0Ywx3GEvMnoUoTNno1l3jpGCkFnRqwL1jS1Mv/zMbVn1XoDPdtkn mwipfHX8u48m1H+w6G7dpZgIon5C6nQUOCYc6ylyRISFN3AV8hT0HD2gubDCtaK9jSpqQ7Qd6gL MjT3ZnNs= X-Gm-Gg: AeBDiesSANNLytxRdyr+23Ue6J/ytmfTkR9zcuGRVwFgePcKEw8uQ6WLvp/pDs1Yaif t1DR+5KlZ20wQ+PVw/psO/Z95jP1TlHMEm9xf78SzbIHvx1p7YgPWzfaNq1KDohxP+DqtiCoWJR Yd6MAUeE4ZLBKPR6EUPg3SRNXq1msq68de76EVOAWUIZG7A9F1xWzwslGwpFUd0msYXphH/6B3k ZfpJy7iMxEb6xni1E2oUC6X2P70LLUHvu3mP5Io+0K4zYBVuOhNkA5F83GSXhr6KonVUcF24zF0 Rx8rpwzvzyeC968LVO1LDPmQ9xKoP24L5sZDxCR2GZ+opiMOx5SfkgTclRvWfr0eUtVoxY5swph 3DfIZq5aU8OS9qS3EJL0MYuNfYsXtu/OMrDCyUrTGXunJ4P+1NYKbNt5W7mFguRZOQ5YfXuskwn pMLFlQD+klOg49BxUZ9509LPLrwgq7noqWnwJ+IgQHLd7EhY20C467F9nYp4Vya4n5yQ== X-Received: by 2002:a05:600c:154b:b0:488:936a:6220 with SMTP id 5b1f17b1804b1-48a988a705dmr259592195e9.21.1777995898839; Tue, 05 May 2026 08:44:58 -0700 (PDT) From: Alexander Hansen To: qemu-devel@nongnu.org Cc: Alexander Hansen , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Paolo Bonzini , Peter Maydell , qemu-arm@nongnu.org Subject: [PATCH v2 2/4] hw/sensor: support MAX11615 Date: Tue, 5 May 2026 17:42:56 +0200 Message-ID: <20260505154410.230969-3-alexander.hansen@9elements.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260505154410.230969-1-alexander.hansen@9elements.com> References: <20260505154410.230969-1-alexander.hansen@9elements.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::332; envelope-from=alexander.hansen@9elements.com; helo=mail-wm1-x332.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @9elements.com) X-ZM-MESSAGEID: 1777995942243154100 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-deviced arbitrary value. ``` root@yosemite4:~# cat /sys/bus/iio/devices/iio:device2/in_voltage0_raw 2770 ``` 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/MAX= 11612-MAX11617.pdf Cc: "Philippe Mathieu-Daud=C3=A9" (odd fixer:Overall se= nsors) Cc: Paolo Bonzini (maintainer:Kconfig) Cc: Peter Maydell (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 --- hw/arm/Kconfig | 1 + 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 ++++ 6 files changed, 236 insertions(+) create mode 100644 hw/sensor/max11615.c create mode 100644 include/hw/sensor/max11615.h diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index c62db7e609..decbc8b0d8 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -552,6 +552,7 @@ config ASPEED_SOC select PMBUS select MAX31785 select MAX31790 + select MAX11615 select FSI_APB2OPB_ASPEED select AT24C select PCI_EXPRESS diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig index ece2f2b167..84eede9d84 100644 --- a/hw/sensor/Kconfig +++ b/hw/sensor/Kconfig @@ -47,3 +47,7 @@ config MAX31785 config MAX31790 bool depends on PMBUS + +config MAX11615 + bool + depends on I2C diff --git a/hw/sensor/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/MAX= 11612-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 =3D s->channels[s->pointer] >> 8; + uint8_t lsb =3D s->channels[s->pointer] & 0xff; + s->outbuf[0] =3D 0b11110000 | (msb & 0b00001111); + s->outbuf[1] =3D lsb; +} + +static void max11615_write_config_byte(MAX11615State *s, uint8_t data) +{ + trace_max11615_write_config(s->i2c.address, data); + + uint8_t channelSelect =3D (data >> 1) & 0b1111; + + /* Table 3. Channel Selection (AIN0 ... AIN11) */ + if (channelSelect > 11) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid channel select", __fun= c__); + channelSelect =3D 11; + } + s->pointer =3D 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 =3D MAX11615(i2c); + const uint8_t msb =3D (data >> 7) & 0b1; + + if (msb) { + max11615_write_setup_byte(s, data); + } else { + max11615_write_config_byte(s, data); + } + + s->outlen =3D 0; + return 0; +} + +static uint8_t max11615_recv(I2CSlave *i2c) +{ + MAX11615State *s =3D MAX11615(i2c); + trace_max11615_recv(s->i2c.address, s->pointer); + + max11615_read(s); + + if (s->outlen >=3D 2) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: too many bytes read", __func__= ); + s->outlen =3D 0; + } + + const uint8_t data =3D 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 =3D MAX11615(i2c); + + trace_max11615_event(s->i2c.address, event); + + switch (event) { + case I2C_START_RECV: + s->outlen =3D 0; + break; + default: + break; + } + + return 0; +} + +static const VMStateDescription vmstate_max11615 =3D { + .name =3D TYPE_MAX11615, + .version_id =3D 0, + .minimum_version_id =3D 0, + .fields =3D (const VMStateField[]){ + VMSTATE_UINT16_ARRAY(channels, MAX11615State, MAX11615_NUM_CHANNEL= S), + 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 =3D MAX11615(i2c_slave_new(TYPE_MAX11615, address)); + + for (int i =3D 0; i < MAX11615_NUM_CHANNELS && i < init_values_size; i= ++) { + + /* arbitrary value */ + uint16_t value =3D 0b0000101011010010; + + if (i < init_values_size) { + value =3D init_values[i]; + } + s->channels[i] =3D 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 =3D MAX11615(dev); + + trace_max11615_realize(s->i2c.address); + + s->pointer =3D 0; + s->outlen =3D 0; +} + +static void max11615_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + I2CSlaveClass *k =3D I2C_SLAVE_CLASS(klass); + + dc->realize =3D max11615_realize; + dc->desc =3D "Maxim MAX11615 12-bit ADC"; + dc->vmsd =3D &vmstate_max11615; + k->event =3D max11615_event; + k->recv =3D max11615_recv; + k->send =3D max11615_send; +} + +static const TypeInfo max31790_info =3D { + .name =3D TYPE_MAX11615, + .parent =3D TYPE_I2C_SLAVE, + .instance_size =3D sizeof(MAX11615State), + .class_size =3D sizeof(MAX11615Class), + .instance_init =3D max11615_init, + .class_init =3D 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) "i2= c_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_ad= dr: 0x%02x, index: %zu, value: 0x%04x" + +# max11615.c +max11615_write_setup(uint8_t i2c_addr, uint8_t send) "i2c_addr: 0x%02x, da= ta: 0x%02x" +max11615_write_config(uint8_t i2c_addr, uint8_t send) "i2c_addr: 0x%02x, d= ata: 0x%02x" +max11615_recv(uint8_t i2c_addr, uint8_t reg_addr) "i2c_addr: 0x%02x, reg_a= ddr: 0x%02x" +max11615_recv_return(uint8_t i2c_addr, uint8_t data) "i2c_addr: 0x%02x, re= turns: 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 +#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 --=20 2.54.0 From nobody Mon May 25 08:54:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=9elements.com ARC-Seal: i=1; a=rsa-sha256; t=1777995955; cv=none; d=zohomail.com; s=zohoarc; b=T6i57C6YECN0jY4Amb/8wmETSjYz58Z0YUk2ye7E5pP+vI/ts3lTuqMTFE30LK352ihCkBmRwgnJM22E5nU5btYFV2lfjX/6HQt7zaEB/y13ALWEVz081HohDDI5x09pBYTxIeRPQtfkNPGElJ2ZJFketMJ8aOKMNUbXc8NghOA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1777995955; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=D9TnfPSJn9nYnmaJK+c+67wzJezVL/H/Lj1NSosR12Q=; b=eE2CefUakjqfyYoobNkVQ0U3yHjvpLuT+pokdUzhGgxuMxUiKs86RxkH8V5GuziGrVxGTzA1Nvxd71C1jaPyAPySFN/340mK85Lb/2cIOnEyTJ0H3gwbj1vf91D4iax0B8/prcIqwrr4dlj8Ko/sPKPM9BalXE1gAGbEGp5Pl3E= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1777995955652154.02326828985213; Tue, 5 May 2026 08:45:55 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wKHxg-0008M1-I8; Tue, 05 May 2026 11:45:28 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wKHxN-0007yV-FK for qemu-devel@nongnu.org; Tue, 05 May 2026 11:45:09 -0400 Received: from mail-wr1-x42f.google.com ([2a00:1450:4864:20::42f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wKHxH-0002gq-AZ for qemu-devel@nongnu.org; Tue, 05 May 2026 11:45:07 -0400 Received: by mail-wr1-x42f.google.com with SMTP id ffacd0b85a97d-44e1860558fso1648615f8f.0 for ; Tue, 05 May 2026 08:45:02 -0700 (PDT) Received: from alexanderarchlinux ([188.111.3.154]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-45054b02abbsm5255305f8f.18.2026.05.05.08.45.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 05 May 2026 08:45:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=9elements.com; s=google; t=1777995901; x=1778600701; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=D9TnfPSJn9nYnmaJK+c+67wzJezVL/H/Lj1NSosR12Q=; b=MXqKb/BjZS7qb7mNg/HfzRXoSC0wta9N1vnmROJqgM09Lbjx2D6inuCKqevI5aJvOd dgCsbyYVUybO30lmcZ13nQWjTMU0FRSVDVeH5okk/guCmEiMeHm6TGL61rYDaIRLAGIk XYQApzVszigNLv/e31w19IW1Ay9rSSRdimZdavDmvyn2TPxjbg5YWQs/TxIqOOJq4vhI 7EXltppKuE2YhqSZAvdznrb82eFF9L8JjNQLVA5ilOm8ySETnUX6nKolyVCQXac/0cQD bWADaw9oL3N+/4H5ex0OHxVpUK/UquxMYvXGCWyQBQg2TYsJCkEHPLx92PASccNTMd4j YASA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777995901; x=1778600701; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=D9TnfPSJn9nYnmaJK+c+67wzJezVL/H/Lj1NSosR12Q=; b=EnO4J15QVv4n5U26w0jeON/PDXiW6Og7anJIsCnI7e5X61DU667AfckZqWee5Ca03e q7MxJD79hW9/Rn/PAwbciN5FfnHeyk0oPy6tnU2w0De/3IXpR97Dn9SZA+QMX9ReAMuu QqzP1cA2fSK9cMJ66ihMk0hB7Y/5VQdeQH5kKenZ3z0qLg+MgXK0MLZ9+dUjN14RA/NX 7cUVXGJCrWIRCQCia+VwRlVs5nuxsxjzNgU6bCUCl2vG3BoV4+70RHR1QGyYMCGhn+fe REs8XVOpYBaPz+g3OLWm0Z+pBwOq7MtaP3NBx/8yxA+lcBAauYbAShEMsFRYSSyuJ7iz twXw== X-Gm-Message-State: AOJu0YykYDasofH5W69CGWoPSd9LE+hah/qkcURCVFU7I7xMN+VHeL8G xylQ9mO/8Mx/9RFIcUoRa0Ktv6kfu+cNTOOKFBkHIDKsgTdFuhxJ+jBKvHH4uCdeKKG1mrXFtLB m/Qhfng4= X-Gm-Gg: AeBDieu87AGda0TA+mIr5wgBX54OHqMKDNfzMjs7MY48p0QDDoIhPLxU+1a47HYtj4p B/jG0pcCcVTm/q8uoUuucNfq2CgnqEA62vkVyIvODzZcLCd+uH0wYtVRubYLW8NubhVUOKGE26m Zse1b7ZoqDXPDY9vSEjla3EH6bik4g4n1tGHCszuiHHFKsau7g5BLUxl2RgS44kNckwyvfUQAc/ YVxk5YLQM+fwbKPO9Q7tEf+E/KMv2nb8AHSEUV6FV9KgA9EL0SYGAQw3VD/6vSCkvRkWhgaovd/ dblJSj989o1j6oN+CofaTgP2+vb2KID7PvsTBKFE0g28KE+AN/4zPmGdWBnrt5rlIkukx8jaTtj k9v2QYTsgxccSMqxHfliB3ROBgQV6XVjGYNeG6zGyt0oe+h7Qn7lxhFlV0cTiPF0ulGYuuCZy09 SO8aPbN0IO9h1EfdCl0O1YD0coFwIhOUHAxqSrdU5FlOAVuN5qs+rGHNw= X-Received: by 2002:a05:6000:2f87:b0:43d:1cec:4766 with SMTP id ffacd0b85a97d-44bb52abc17mr24743734f8f.23.1777995901260; Tue, 05 May 2026 08:45:01 -0700 (PDT) From: Alexander Hansen To: qemu-devel@nongnu.org Cc: Alexander Hansen , Paolo Bonzini , Peter Maydell , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , qemu-arm@nongnu.org Subject: [PATCH v2 3/4] hw/sensor: support Texas Instruments ADC128D818 Date: Tue, 5 May 2026 17:42:57 +0200 Message-ID: <20260505154410.230969-4-alexander.hansen@9elements.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260505154410.230969-1-alexander.hansen@9elements.com> References: <20260505154410.230969-1-alexander.hansen@9elements.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::42f; envelope-from=alexander.hansen@9elements.com; helo=mail-wr1-x42f.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @9elements.com) X-ZM-MESSAGEID: 1777995956521154100 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] =3D { 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 =3D 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: Paolo Bonzini (maintainer:Kconfig) Cc: Peter Maydell (supporter:ARM TCG CPUs) Cc: "Philippe Mathieu-Daud=C3=A9" (odd fixer:Overall se= nsors) 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 --- hw/arm/Kconfig | 1 + 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 ++ 6 files changed, 448 insertions(+) create mode 100644 hw/sensor/adc128d818.c create mode 100644 include/hw/sensor/adc128d818.h diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index decbc8b0d8..c1693325a1 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -553,6 +553,7 @@ config ASPEED_SOC select MAX31785 select MAX31790 select MAX11615 + select ADC128D818 select FSI_APB2OPB_ASPEED select AT24C select PCI_EXPRESS diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig index 84eede9d84..c9190ff780 100644 --- a/hw/sensor/Kconfig +++ b/hw/sensor/Kconfig @@ -51,3 +51,7 @@ config MAX31790 config MAX11615 bool depends on I2C + +config ADC128D818 + bool + depends on I2C diff --git a/hw/sensor/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 =3D 0..7, readonly */ +#define REG_CHANNEL_READING(N) (0x20 + N) + +/* 8 bit registers N =3D 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 =3D 0; + switch (s->pointer) { + case REG_CONFIG: + s->outbuf[0] =3D s->config; + break; + case REG_INTERRUPT_STATUS: + s->outbuf[0] =3D 0x0; /* POR State */ + break; + case REG_INTERRUPT_MASK: + s->outbuf[0] =3D s->interrupt_mask; + break; + case REG_CONVERSION_RATE: + s->outbuf[0] =3D s->conversion_rate; + break; + case REG_CHANNEL_DISABLE: + s->outbuf[0] =3D 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] =3D 0x0; + break; + case REG_DEEP_SHUTDOWN: + s->outbuf[0] =3D s->deep_shutdown ? 0x1 : 0x0; + break; + case REG_ADVANCED_CONFIG: + s->outbuf[0] =3D s->advanced_config & 0b111; + break; + case REG_BUSY_STATUS: + /* not implemented */ + s->outbuf[0] =3D 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 =3D s->pointer - REG_CHANNEL_READING(0); + /* high byte comes first, driver reads swapped */ + s->outbuf[0] =3D (s->channels[ch_num] >> 8) & 0xff; + s->outbuf[1] =3D 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] =3D s->limit[s->pointer - REG_LIMIT(0)]; + break; + case REG_MANUFACTURER_ID: + s->outbuf[0] =3D 0x1; /* readonly */ + break; + case REG_REVISION_ID: + s->outbuf[0] =3D 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 d= ata) +{ + /* + * 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 =3D (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 =3D 0b10001011; + const uint8_t config_ro_mask =3D (uint8_t)~config_w_mask; + + switch (s->pointer) { + case REG_CONFIG: + s->config =3D (s->config & config_ro_mask) | (data & config_w_mask= ); + break; + case REG_INTERRUPT_MASK: + s->interrupt_mask =3D data; + break; + case REG_CONVERSION_RATE: + s->conversion_rate =3D data; + break; + case REG_CHANNEL_DISABLE: + s->channel_disable =3D 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 =3D (data & 0x1) !=3D 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)] =3D 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 =3D ADC128D818(i2c); + trace_adc128d818_send(s->i2c.address, data); + + s->outlen =3D 0; + s->buf[s->len] =3D data; + + if (s->len =3D=3D 0) { + s->pointer =3D data; + } else if (s->len =3D=3D 1) { + adc128d818_write(s, data); + } + + s->len++; + return 0; +} + +static uint8_t adc128d818_recv(I2CSlave *i2c) +{ + ADC128D818State *s =3D ADC128D818(i2c); + trace_adc128d818_recv(s->i2c.address, s->pointer); + + adc128d818_read(s); + + if (s->outlen >=3D 2) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: too many bytes read\n", __func= __); + s->outlen =3D 0; + } + + const uint8_t data =3D 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 =3D ADC128D818(i2c); + + trace_adc128d818_event(s->i2c.address, event); + + switch (event) { + case I2C_START_RECV: + s->outlen =3D 0; + break; + case I2C_START_SEND: + s->len =3D 0; + break; + default: + break; + } + + return 0; +} + +static const VMStateDescription vmstate_adc128d818 =3D { + .name =3D TYPE_ADC128D818, + .version_id =3D 0, + .minimum_version_id =3D 0, + .fields =3D (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 =3D ADC128D818(i2c_slave_new(TYPE_ADC128D818, address)); + + for (int i =3D 0; i < ADC128D818_NUM_CHANNELS; i++) { + + /* arbitrary value */ + uint16_t value =3D 0b0000101011010010; + + if (i < init_values_size) { + value =3D init_values[i]; + } + s->channels[i] =3D value; + } + + i2c_slave_realize_and_unref(I2C_SLAVE(s), bus, &error_abort); + + return I2C_SLAVE(s); +} + +static void adc128d818_reset(I2CSlave *i2c) +{ + ADC128D818State *s =3D ADC128D818(i2c); + + s->pointer =3D 0; + s->outlen =3D 0; + + /* POR-State */ + s->config =3D 0b00001000; + s->interrupt_mask =3D 0; + s->conversion_rate =3D 0; + s->channel_disable =3D 0; + s->deep_shutdown =3D 0; + s->advanced_config =3D 0; + + /* No POR-State defined in datasheet */ + for (int i =3D 0; i < ADC128D818_NUM_CHANNELS * 2; i++) { + s->limit[i] =3D 0; + } +} + +static void adc128d818_realize(DeviceState *dev, Error **errp) +{ + ADC128D818State *s =3D ADC128D818(dev); + + trace_adc128d818_realize(s->i2c.address); + + adc128d818_reset(&s->i2c); +} + +static void adc128d818_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + I2CSlaveClass *k =3D I2C_SLAVE_CLASS(klass); + + dc->realize =3D adc128d818_realize; + dc->desc =3D "Texas Intstruments ADC128D818 12-bit ADC with temp senso= r"; + dc->vmsd =3D &vmstate_adc128d818; + k->event =3D adc128d818_event; + k->recv =3D adc128d818_recv; + k->send =3D adc128d818_send; +} + +static const TypeInfo adc128d818_info =3D { + .name =3D TYPE_ADC128D818, + .parent =3D TYPE_I2C_SLAVE, + .instance_size =3D sizeof(ADC128D818State), + .class_size =3D sizeof(ADC128D818Class), + .instance_init =3D adc128d818_init, + .class_init =3D 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_a= ddr: 0x%02x, reg_addr: 0 max11615_recv_return(uint8_t i2c_addr, uint8_t data) "i2c_addr: 0x%02x, re= turns: 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: 0= x%02x" +adc128d818_write(uint8_t i2c_addr, uint8_t reg, uint8_t data) "i2c_addr: 0= x%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 +#include "hw/i2c/i2c.h" + +#define TYPE_ADC128D818 "adc128d818" + +/* + * Create and realize a adc128d818 ADC with constant caller-supplied readi= ngs + * @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 --=20 2.54.0 From nobody Mon May 25 08:54:42 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=9elements.com ARC-Seal: i=1; a=rsa-sha256; t=1777995959; cv=none; d=zohomail.com; s=zohoarc; b=WuExY4q25SWzPmoKbYZ7czM4PetX5coxIzUFT7PxAL+E42xcSpD6+B3TUWtprtCNK0a9Tmf3wZZzsLVAYVBlygy0ijgIa+VP/0JOrx971LPPkJ5T2Itd/5X0ar39xUp159n4v92bbXdzltzC9WI+5qnHhvbUQjnEAys06Y0R4pg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1777995959; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=B0nwwAXPHrpOQLX3+o1oZmUh3uLp9M1ZG0F0CiBN9v8=; b=PxetFRt3MZl2QypSdSi4S/9b4JIWKcKln+qO0RW1HiatBsNndq+ziGBWpTv+kq/6SQSj4VaJHKzvC5R1zWZrsM+ncloaxHxIrhkVhmU3XH1G5wdwhzTpDXiosa7t7Ed9Ebdq5cywJh7lG7BGykNRSdkzPfiLQ5FOvX3gnK+CJOM= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 177799595906027.95307465240319; Tue, 5 May 2026 08:45:59 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wKHxe-0008HC-Cm; Tue, 05 May 2026 11:45:26 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wKHxP-00080s-Ed for qemu-devel@nongnu.org; Tue, 05 May 2026 11:45:13 -0400 Received: from mail-wr1-x42b.google.com ([2a00:1450:4864:20::42b]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wKHxK-0002rt-B4 for qemu-devel@nongnu.org; Tue, 05 May 2026 11:45:10 -0400 Received: by mail-wr1-x42b.google.com with SMTP id ffacd0b85a97d-44509921fbcso2807432f8f.3 for ; Tue, 05 May 2026 08:45:05 -0700 (PDT) Received: from alexanderarchlinux ([188.111.3.154]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-45054b02abbsm5255305f8f.18.2026.05.05.08.45.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 05 May 2026 08:45:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=9elements.com; s=google; t=1777995904; x=1778600704; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=B0nwwAXPHrpOQLX3+o1oZmUh3uLp9M1ZG0F0CiBN9v8=; b=OIwP3viAHBe9kGk4Bp6GtQt6su0McrEux0cmwqsAkcIj7ZM+5KvEk0suOqpkOzHlnd 2/obzjEIkLGDSqAu+uy2ryk2U7oLCUB7E79tYSyCCP9rGHSbNT2rrGRGiFxy6H27eabe c7L/RN0eHQFQNHWl4GUx9f+Q35n1oOQSBl/eUywh3hH3lw1JDzXlUj8puFPuOhUxj4NC 94SI/qKdONgTIWY2hQ+ju99hc6iU9RkgtyJxhXq0j0ZYVhDGszsltK9TifK1+qVYrOF0 wCI40m+933q3W43nACDlbzuJc7OXC3m3u8aKtiU5j1CXi3BXdKDI5q1uBpyJmeruRq90 mFaw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777995904; x=1778600704; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=B0nwwAXPHrpOQLX3+o1oZmUh3uLp9M1ZG0F0CiBN9v8=; b=Om9Vc1/Vl5XUFMbIumgce7V0LkP22/fgmXkeKzv6D9ekK7MKhTyTADqr+1gxgpWxVu lZx2tVHAvSLqaipP1hJVgzOnpabFhMl46dl06NMmD2IfFSyg4uFjaJwFJ1YiLhtD+gIw ZMJXnv9m9QNyJA0Cx/PlO6Tpslsb+Hco/udVC/580hMsfcldkROuHPn32zmMUcBFpfjb JbWEAJbbv3mKKRvdROmmb76UZKctWLl6HjlBPfYAqWMMHWXf1D2ovTIZgT/gfvD3Qt0k E0Y2Jmbw+5fWWyMqJ9JpSdA2a3D6P+0/pMbE2XEfZ/RZF/r/4jWRqg0IPP9Slo2aTpjn biQw== X-Gm-Message-State: AOJu0YxhtAW1YSTUZFocoNTeDb3Nuxfl76PYPirweX5gQJO6X/8O5hDL dwHV7sFjNqTcWcP+4rQM0xfwQJ1/XNENYaRl77x+u6SymE1bn1UN20mYYjYLcraeiCsCZ47lLfk Uijtuam1a5A== X-Gm-Gg: AeBDieugHj+6FbnA1WkZY9nc6FmTjyG0zzDjCmTNcvF7AspVRWmkt3GN/aE+z49slsx lHRDOOy6cS0yYVYWMmzTC+TPa/JuQKR5ZlNu68tthJyryIOx/DPAhJUxGdaqaUAdwALHUFIdid4 QaskSJ+qIUygwjgiJboiytOiur8/H7kuWfcpLvV0B3oJae9LMBpYpjKOq43ceXMGhnwf1ysaNF3 3VbmXgIZhqY6WYX1Knbob5Q5CxtsK9bXsrTkP+JpNsboSFxqys9DrC5ggGGTE903HJW5Imcd6ow CLb9lPrqEWrfcahB9UFJdjUXEI6tX+6Khrt23Sh4aTssTXYG+B2a5657DUxHsyW2MERVL/wjbs2 7CLSSKpM2tT50BgF8WaYxK2LP27OKSIn8S5r+tPRnIaMxLyXRxY2AL7unURKcjIzapzd4p82ZcU q1HO/Ar2SpQoLoRIhNRtBcDJRNVDDvJxK+W9CspkOr1kIRe5hPEHyn3rM= X-Received: by 2002:a05:6000:144f:b0:44d:67f7:e1a6 with SMTP id ffacd0b85a97d-44d67f7e2f6mr18604102f8f.20.1777995903761; Tue, 05 May 2026 08:45:03 -0700 (PDT) From: Alexander Hansen To: qemu-devel@nongnu.org Cc: Alexander Hansen , =?UTF-8?q?C=C3=A9dric=20Le=20Goater?= , Peter Maydell , Steven Lee , Troy Lee , Jamin Lin , Kane Chen , Andrew Jeffery , Joel Stanley , qemu-arm@nongnu.org Subject: [PATCH v2 4/4] ast2600: yosemite4 initial support Date: Tue, 5 May 2026 17:42:58 +0200 Message-ID: <20260505154410.230969-5-alexander.hansen@9elements.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260505154410.230969-1-alexander.hansen@9elements.com> References: <20260505154410.230969-1-alexander.hansen@9elements.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::42b; envelope-from=alexander.hansen@9elements.com; helo=mail-wr1-x42b.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @9elements.com) X-ZM-MESSAGEID: 1777995960628154100 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. Tested: functional test (see [5] for the image) passed. ``` export QEMU_TEST_QEMU_BINARY=3Dqemu-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 ``` References: [1] https://github.com/9elements/qemu/commit/32139f913c2bd0ebe4bd26c4676586= 1f6f9f2d49 [2] arch/arm/boot/dts/aspeed/aspeed-bmc-facebook-yosemite4.dts [3] https://github.com/legoater/qemu-aspeed-boot/pull/5 Cc: "C=C3=A9dric Le Goater" (maintainer:ASPEED BMCs) Cc: Peter Maydell (maintainer:ASPEED BMCs) Cc: Steven Lee (reviewer:ASPEED BMCs) Cc: Troy Lee (reviewer:ASPEED BMCs) Cc: Jamin Lin (reviewer:ASPEED BMCs) Cc: Kane Chen (reviewer:ASPEED BMCs) Cc: Andrew Jeffery (reviewer:ASPEED BMCs) Cc: Joel Stanley (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 --- hw/arm/aspeed_ast2600_fby4.c | 289 +++++++++++++++++++++++ hw/arm/meson.build | 1 + tests/functional/arm/meson.build | 2 + tests/functional/arm/test_aspeed_fby4.py | 44 ++++ 4 files changed, 336 insertions(+) create mode 100644 hw/arm/aspeed_ast2600_fby4.c create mode 100755 tests/functional/arm/test_aspeed_fby4.py diff --git a/hw/arm/aspeed_ast2600_fby4.c b/hw/arm/aspeed_ast2600_fby4.c new file mode 100644 index 0000000000..284f64e3f3 --- /dev/null +++ b/hw/arm/aspeed_ast2600_fby4.c @@ -0,0 +1,289 @@ +/* + * Yosemite V4 + * + * Copyright 2026 9elements. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.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/sensor/tmp105.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" +#include "system/reset.h" + +#define FBY4_BMC_RAM_SIZE ASPEED_RAM_SIZE(2 * GiB) + +/* START OF EEPROM CONTENTS */ + +/* + *./frugen -s board.mfg=3D"Wiwynn" \ + * --set board.pname=3D"Fan Board FSC-MAX ADC-TI LED-NXP EFUSE-MAX" \ + * --set text:board.pn=3D"BRD-PN-345" \ + * --board-date "10/1/2017 12:58:00" \ + * --set board.serial=3D"123456" \ + * --set product.pname=3D"Yosemite V4" \ + * --set product.mfg=3D"Wiwynn" \ + * --set product.ver=3D"v1.1" \ + * --set product.serial=3D"123456" \ + * --set product.atag=3D"PLACEHOLDER" \ + * --set product.custom=3D"Fanboard Custom1" \ + * --set product.custom.1=3D"Fanboard Custom2" \ + * --set text:product.pn=3D"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[] =3D { + 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 =3D sizeof(fru_yv4_fanboard_b= in); + +/* + *./frugen -s board.mfg=3D"Wiwynn" \ + * --set board.pname=3D"Sentinel Dome without Retimer" \ + * --set text:board.pn=3D"BRD-PN-345" \ + * --board-date "10/1/2017 12:58:00" \ + * --set board.serial=3D"123456" \ + * --set product.pname=3D"Yosemite V4" \ + * --set product.mfg=3D"Wiwynn" \ + * --set product.ver=3D"v1.1" \ + * --set product.serial=3D"123456" \ + * --set product.atag=3D"PLACEHOLDER" \ + * --set product.custom=3D"Yosemite V4 T1" \ + * --set product.custom.1=3D"Yosemite V4 T1" \ + * --set text:product.pn=3D"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[] =3D { + 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 =3D + sizeof(fru_yv4_sentineldome_board_bin); + +/* + *./frugen -s board.mfg=3D"Wiwynn" \ + * --set board.pname=3D"Sentinel Dome" \ + * --set text:board.pn=3D"BRD-PN-345" \ + * --board-date "10/1/2017 12:58:00" \ + * --set board.serial=3D"123456" \ + * --set product.pname=3D"Yosemite V4" \ + * --set product.mfg=3D"Wiwynn" \ + * --set product.ver=3D"v1.1" \ + * --set product.serial=3D"123456" \ + * --set product.atag=3D"PLACEHOLDER" \ + * --set text:product.pn=3D"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[] =3D { + 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 =3D + sizeof(fru_yv4_sentineldome_chassis_bin); + +/* + *./frugen -s board.mfg=3D"Wiwynn" \ + * --set board.pname=3D"Management Board wBMC" \ + * --set text:board.pn=3D"BRD-PN-345" \ + * --board-date "10/1/2017 12:58:00" \ + * --set board.serial=3D"123456" \ + * --set product.pname=3D"Yosemite V4" \ + * --set product.mfg=3D"Wiwynn" \ + * --set product.ver=3D"v1.1" \ + * --set product.serial=3D"123456" \ + * --set product.atag=3D"PLACEHOLDER" \ + * --set text:product.pn=3D"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[] =3D { + 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 =3D sizeof(fru_yv4_eclosure_b= in); + +/* END OF EEPROM CONTENTS */ + +static void fby4_i2c_init_fanboard(I2CSlave *fan_mux, size_t eepromSize) +{ + /* 2 fan boards */ + for (int i =3D 0; i <=3D 1; i++) { + /* downstream bus */ + I2CBus *bus =3D pca954x_i2c_get_bus(fan_mux, i); + + /* ti,adc128d818 @ 0x1f (adc) */ + /* the driver will throw away the last 4 bits, set them 0 */ + static const uint16_t adc_values1[8] =3D { + 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); + + /* + * 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) */ + i2c_slave_create_simple(bus, TYPE_MAX31790, 0x2f); + + /* maxim,max11615 @ 0x33 (adc) */ + static const uint16_t adc_values[8] =3D { + 0b011110000010, 0b010100011000, + 0b001000110100, 0b100000101001, + 0b011110000010, 0b010100011000, + 0b001000110100, 0b100000101001}; + max11615_init_with_values(bus, 0x33, adc_values, 8); + + 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 rea= son */ + for (int bus =3D 1; bus <=3D (8 / 4); bus++) { + + fby4_i2c_init_blade_chassis(i2c[bus], eepromSize); + } +} + +static void fby4_i2c_init(AspeedMachineState *bmc) +{ + AspeedSoCState *soc =3D bmc->soc; + I2CBus *i2c[16]; + + for (int i =3D 0; i < 16; i++) { + i2c[i] =3D aspeed_i2c_get_bus(&soc->i2c, i); + } + + const size_t eepromSize =3D 128 * KiB; + + const uint8_t enclosure_addr =3D 0x51; + + /* Enclosure (EM Config: yosemite4_chassis.json, yosemite4.json) */ + at24c_eeprom_init_rom(i2c[1], enclosure_addr, eepromSize, + fru_yv4_eclosure_bin, fru_yv4_eclosure_bin_len); + + fby4_i2c_init_multiple_blade_chassis(i2c, eepromSize); + + /* Yv4 fanboard connection */ + I2CSlave *fan_mux =3D i2c_slave_create_simple(i2c[14], TYPE_PCA9546, 0= x74); + + fby4_i2c_init_fanboard(fan_mux, eepromSize); +} + +static void aspeed_machine_fby4_class_init(ObjectClass *oc, const void *da= ta) +{ + MachineClass *mc =3D MACHINE_CLASS(oc); + AspeedMachineClass *amc =3D ASPEED_MACHINE_CLASS(oc); + + mc->desc =3D "Facebook fby4 BMC (Cortex-A7)"; + amc->fmc_model =3D "mx66l1g45g"; + amc->num_cs =3D 2; + amc->macs_mask =3D ASPEED_MAC3_ON; + amc->i2c_init =3D fby4_i2c_init; + mc->default_ram_size =3D FBY4_BMC_RAM_SIZE; + aspeed_machine_class_init_cpus_defaults(mc); +} + +static const TypeInfo aspeed_ast2600_fby4_types[] =3D { + { + .name =3D MACHINE_TYPE_NAME("fby4-bmc"), + .parent =3D MACHINE_TYPE_NAME("ast2600-evb"), + .class_init =3D aspeed_machine_fby4_class_init, + .interfaces =3D 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', 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 =3D { '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 =3D [ '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/ar= m/test_aspeed_fby4.py new file mode 100755 index 0000000000..9679e4a1c5 --- /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 =3D Asset( + 'https://github.com/pointbazaar/qemu-aspeed-boot/raw/3ef9a0d8cd290= 2c2039924ad1f16c8f2345486f3/images/yosemite4-bmc/openbmc-20260505132843/obm= c-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=3D{image},if=3Dmtd,format=3Draw', + '-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 =3D self.uncompress(self.ASSET_YOSEMITE_V4_FLASH) + + self.do_test_arm_aspeed_openbmc_no_network('fby4-bmc', image=3Dima= ge_path, + uboot=3D'2019.04', cpu_id=3D'0xf00= ', + soc=3D'AST2600 rev A3') + +if __name__ =3D=3D '__main__': + AspeedTest.main() --=20 2.54.0