mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3 0/1] hwmon: Add Minisforum UM780 XTX EC monitoring and fan control
@ 2026-08-25 18:41 Sebastián Peyrott
  2026-08-25 18:41 ` [PATCH v3 1/1] " Sebastián Peyrott
  2026-08-28 16:02 ` [PATCH v3 0/1] " Guenter Roeck
  0 siblings, 2 replies; 4+ messages in thread
From: Sebastián Peyrott @ 2026-08-25 18:41 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: linux-hwmon, Jonathan Corbet, Shuah Khan, linux-doc, linux-kernel

Add a DMI-gated hwmon driver for both fans on the Minisforum UM780 XTX. The
EC is an ITE IT5571E, but its register layout and OEM commands are defined
by machine-specific firmware, so the driver only binds to the exact board
revision and BIOS version on which it was tested.

The driver exposes both fan-control temperatures and tachometers, two OEM
automatic CPU fan profiles, and two writable system-fan transition
temperatures. It preserves settings selected through its interface across
s2idle.

Tested on the target system with concurrent RPM reads, OEM profile changes,
system-fan threshold changes, unload/reload stress, and s2idle resume. The
settings were correct from the first userspace sample after resume and
remained stable for 15 seconds.

OpenAI Codex assisted with reverse-engineering review, code editing, test
orchestration, and patch and email preparation across a long interactive
session. I reviewed every change, ran the live tests on the target
hardware, and take responsibility for the submission.

Link: https://lore.kernel.org/r/20260818181345.203295-1-speyrott@gmail.com

Sebastián Peyrott (1):
  hwmon: Add Minisforum UM780 XTX EC monitoring and fan control

---
Changes in v3:
- return -EIO after bounded RPM coherence retries and -ENODATA for an
  unknown CPU profile;
- seed saved settings at probe, update them only after successful hwmon
  writes, and restore them at resume without reading the EC at suspend;
- clarify saved-state naming, live threshold validation, and resume
  documentation.

 Documentation/hwmon/index.rst               |   1 +
 Documentation/hwmon/minisforum-um780xtx.rst |  68 +++
 MAINTAINERS                                 |   7 +
 drivers/hwmon/Kconfig                       |  16 +
 drivers/hwmon/Makefile                      |   1 +
 drivers/hwmon/minisforum-um780xtx.c         | 501 ++++++++++++++++++++
 6 files changed, 594 insertions(+)
 create mode 100644 Documentation/hwmon/minisforum-um780xtx.rst
 create mode 100644 drivers/hwmon/minisforum-um780xtx.c


base-commit: 75f2c0b3690702c90863c2e138cb5520670845ea
-- 
2.54.0

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v3 1/1] hwmon: Add Minisforum UM780 XTX EC monitoring and fan control
  2026-08-25 18:41 [PATCH v3 0/1] hwmon: Add Minisforum UM780 XTX EC monitoring and fan control Sebastián Peyrott
@ 2026-08-25 18:41 ` Sebastián Peyrott
  2026-08-27  4:15   ` kernel test robot
  2026-08-28 16:02 ` [PATCH v3 0/1] " Guenter Roeck
  1 sibling, 1 reply; 4+ messages in thread
From: Sebastián Peyrott @ 2026-08-25 18:41 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: linux-hwmon, Jonathan Corbet, Shuah Khan, linux-doc, linux-kernel

Add a DMI-gated hwmon driver for the embedded controller used by the
Minisforum UM780 XTX with board revision 1.1 and BIOS 1.06.

Expose the CPU and system fan control temperatures and tachometers. The
tachometer protocol returns one byte per OEM command, so serialize
transactions and use high-low-high sampling to reject torn values.

Allow selecting either complete OEM CPU fan profile through pwm1_enable and
changing the two validated system fan transition temperatures through
standard automatic-curve attributes. Cache coherent settings and restore
them after the firmware reloads defaults following resume.

Signed-off-by: Sebastián Peyrott <speyrott@gmail.com>
---
 Documentation/hwmon/index.rst               |   1 +
 Documentation/hwmon/minisforum-um780xtx.rst |  68 +++
 MAINTAINERS                                 |   7 +
 drivers/hwmon/Kconfig                       |  16 +
 drivers/hwmon/Makefile                      |   1 +
 drivers/hwmon/minisforum-um780xtx.c         | 501 ++++++++++++++++++++
 6 files changed, 594 insertions(+)
 create mode 100644 Documentation/hwmon/minisforum-um780xtx.rst
 create mode 100644 drivers/hwmon/minisforum-um780xtx.c

diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index d979e6d6e9f2..c98cad395455 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -186,6 +186,7 @@ Hardware Monitoring Kernel Drivers
    mcp3021
    mcp9982
    menf21bmc
+   minisforum-um780xtx
    mlxreg-fan
    mp2856
    mp2869
diff --git a/Documentation/hwmon/minisforum-um780xtx.rst b/Documentation/hwmon/minisforum-um780xtx.rst
new file mode 100644
index 000000000000..8237d525a9c7
--- /dev/null
+++ b/Documentation/hwmon/minisforum-um780xtx.rst
@@ -0,0 +1,68 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+====================================
+Kernel driver minisforum-um780xtx
+====================================
+
+Supported systems:
+
+  * Minisforum UM780 XTX
+
+    * DMI product name: ``Venus series``
+    * Mainboard: ``F7BSD``, revision ``1.1``
+    * BIOS version: ``1.06``
+
+Author: Sebastián Peyrott <speyrott@gmail.com>
+
+Description
+-----------
+
+This driver exposes hardware monitoring and fan-control data cached by the
+IT5571E embedded controller. It uses the ACPI EC transport and only binds to
+the exact system and firmware identity listed above.
+
+The two temperature channels are the values used by the EC's fan-control
+loops. Their physical sensor placement is not known. The CPU channel is the
+filtered AMD SB-TSI temperature, while the system channel is an external
+thermistor input.
+
+The fan tachometers are read through OEM EC commands. Since each byte is
+returned by a separate command, the driver uses a high-low-high sequence and
+retries if the high byte changes.
+
+Sysfs entries
+-------------
+
+==========================  ==============================================
+``temp1_input``             CPU-fan control temperature
+``temp2_input``             System-fan control temperature
+``fan1_input``              CPU fan speed in RPM
+``fan2_input``              System fan speed in RPM
+``pwm1_enable``             CPU profile: 2 is OEM B1, 3 is OEM B2
+``pwm2_auto_point1_temp``   Off-to-low system-fan transition temperature
+``pwm2_auto_point2_temp``   Low-to-high system-fan transition temperature
+==========================  ==============================================
+
+CPU fan profiles
+----------------
+
+Values 2 and 3 of ``pwm1_enable`` select the two complete automatic profiles
+implemented by the firmware. Writing either value reloads the whole CPU fan
+curve, including firmware state which is not visible through the ACPI EC
+window. Other values are rejected.
+
+System fan thresholds
+---------------------
+
+The two writable system-fan temperatures are rounded to whole degrees Celsius
+and clamped to preserve strict ordering between both visible thresholds and
+the firmware's internal third threshold. The third threshold does not select
+a new PWM target and is therefore not exposed as another auto point.
+
+Resume state preservation
+-------------------------
+
+Fan settings are held in EC RAM. On the supported firmware, initialization
+during s2idle resume reloads the factory system-fan curve. The driver retains
+the last coherent CPU profile and system-fan thresholds selected through its
+interface and restores them synchronously at resume.
diff --git a/MAINTAINERS b/MAINTAINERS
index 6156e48e1816..f60ea9c6cfe7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18012,6 +18012,13 @@ F:	include/linux/min_heap.h
 F:	lib/min_heap.c
 F:	lib/tests/min_heap_kunit.c
 
+MINISFORUM UM780 XTX EC HARDWARE MONITOR DRIVER
+M:	Sebastián Peyrott <speyrott@gmail.com>
+L:	linux-hwmon@vger.kernel.org
+S:	Maintained
+F:	Documentation/hwmon/minisforum-um780xtx.rst
+F:	drivers/hwmon/minisforum-um780xtx.c
+
 MIPI CCS, SMIA AND SMIA++ IMAGE SENSOR DRIVER
 M:	Sakari Ailus <sakari.ailus@linux.intel.com>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 81a9a1d40eec..45ac04c89b23 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1490,6 +1490,22 @@ config SENSORS_MENF21BMC_HWMON
 	  This driver can also be built as a module. If so the module
 	  will be called menf21bmc_hwmon.
 
+config SENSORS_MINISFORUM_UM780XTX
+	tristate "Minisforum UM780 XTX EC hardware monitoring"
+	depends on ACPI && DMI && X86
+	help
+	  If you say yes here you get support for the hardware monitoring
+	  features of the embedded controller in the Minisforum UM780 XTX.
+	  This includes CPU and system fan speeds, their control temperatures,
+	  two CPU fan profiles, system fan temperature thresholds, and
+	  preservation of the selected settings across suspend and resume.
+
+	  This driver only binds to explicitly supported board and firmware
+	  versions.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called minisforum-um780xtx.
+
 config SENSORS_MR75203
 	tristate "Moortec Semiconductor MR75203 PVT Controller"
 	select REGMAP_MMIO
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 0cad7e21634c..a690f5f97ffc 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -180,6 +180,7 @@ obj-$(CONFIG_SENSORS_TC654)	+= tc654.o
 obj-$(CONFIG_SENSORS_TPS23861)	+= tps23861.o
 obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
 obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
+obj-$(CONFIG_SENSORS_MINISFORUM_UM780XTX) += minisforum-um780xtx.o
 obj-$(CONFIG_SENSORS_MR75203)	+= mr75203.o
 obj-$(CONFIG_SENSORS_NCT6683)	+= nct6683.o
 obj-$(CONFIG_SENSORS_NCT6694)	+= nct6694-hwmon.o
diff --git a/drivers/hwmon/minisforum-um780xtx.c b/drivers/hwmon/minisforum-um780xtx.c
new file mode 100644
index 000000000000..f5d6e18cb651
--- /dev/null
+++ b/drivers/hwmon/minisforum-um780xtx.c
@@ -0,0 +1,501 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Minisforum UM780 XTX (F7BSD) embedded-controller hwmon driver.
+ *
+ * Copyright (C) 2026 Sebastián Peyrott <speyrott@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#define UM780XTX_EC_TEMP_SYS	0x05
+#define UM780XTX_EC_TEMP_CPU	0x09
+#define UM780XTX_EC_CPU_PROFILE	0x2f
+#define UM780XTX_EC_SYS_POINT1	0x31
+#define UM780XTX_EC_SYS_POINT2	0x34
+#define UM780XTX_EC_SYS_POINT3	0x37
+
+#define UM780XTX_EC_PROFILE_B1	0xb1
+#define UM780XTX_EC_PROFILE_B2	0xb2
+
+#define UM780XTX_EC_FAN1_LO	0xb6
+#define UM780XTX_EC_FAN1_HI	0xb7
+#define UM780XTX_EC_FAN2_LO	0xb9
+#define UM780XTX_EC_FAN2_HI	0xba
+
+#define UM780XTX_EC_MAX_RPM	9000
+#define UM780XTX_EC_RPM_RETRIES	3
+
+static struct platform_device *um780xtx_pdev;
+
+struct um780xtx_data {
+	struct device *hwmon_dev;
+	u8 saved_profile;
+	u8 saved_sys_point1;
+	u8 saved_sys_point2;
+	u8 sys_point3;
+};
+
+static const struct dmi_system_id um780xtx_dmi_table[] = {
+	{
+		.matches = {
+			DMI_EXACT_MATCH(DMI_SYS_VENDOR,
+					"Micro Computer (HK) Tech Limited"),
+			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Venus series"),
+			DMI_EXACT_MATCH(DMI_BOARD_VENDOR,
+					"Shenzhen Meigao Electronic Equipment Co.,Ltd"),
+			DMI_EXACT_MATCH(DMI_BOARD_NAME, "F7BSD"),
+		},
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(dmi, um780xtx_dmi_table);
+
+static bool um780xtx_firmware_match(void)
+{
+	const char *board_version = dmi_get_system_info(DMI_BOARD_VERSION);
+	const char *bios_version = dmi_get_system_info(DMI_BIOS_VERSION);
+
+	return board_version && bios_version &&
+		!strcmp(board_version, "1.1") && !strcmp(bios_version, "1.06");
+}
+
+static int um780xtx_oem_read(u8 command, u8 *value)
+{
+	return ec_transaction(command, NULL, 0, value, 1);
+}
+
+static int um780xtx_read_rpm(u8 command_hi, u8 command_lo, long *rpm)
+{
+	u8 hi_before;
+	u8 hi_after;
+	u8 lo;
+	unsigned int value;
+	int attempt;
+	int ret;
+
+	for (attempt = 0; attempt < UM780XTX_EC_RPM_RETRIES; attempt++) {
+		ret = um780xtx_oem_read(command_hi, &hi_before);
+		if (ret)
+			return ret;
+		ret = um780xtx_oem_read(command_lo, &lo);
+		if (ret)
+			return ret;
+		ret = um780xtx_oem_read(command_hi, &hi_after);
+		if (ret)
+			return ret;
+		if (hi_before != hi_after)
+			continue;
+
+		value = (hi_after << 8) | lo;
+		if (value > UM780XTX_EC_MAX_RPM)
+			continue;
+
+		*rpm = value;
+		return 0;
+	}
+
+	return -EIO;
+}
+
+static int um780xtx_read_profile(long *mode)
+{
+	u8 profile;
+	int ret;
+
+	ret = ec_read(UM780XTX_EC_CPU_PROFILE, &profile);
+	if (ret)
+		return ret;
+	if (profile == UM780XTX_EC_PROFILE_B1) {
+		*mode = 2;
+		return 0;
+	}
+	if (profile == UM780XTX_EC_PROFILE_B2) {
+		*mode = 3;
+		return 0;
+	}
+
+	return -ENODATA;
+}
+
+static int um780xtx_write_profile(struct um780xtx_data *data, long mode)
+{
+	u8 expected;
+	u8 profile;
+	int ret;
+
+	if (mode != 2 && mode != 3)
+		return -EINVAL;
+	expected = mode == 2 ? UM780XTX_EC_PROFILE_B1 : UM780XTX_EC_PROFILE_B2;
+
+	ret = ec_transaction(expected, NULL, 0, NULL, 0);
+	if (ret)
+		return ret;
+	ret = ec_read(UM780XTX_EC_CPU_PROFILE, &profile);
+	if (ret)
+		return ret;
+	if (profile != expected)
+		return -EIO;
+
+	data->saved_profile = profile;
+	return 0;
+}
+
+static const u8 um780xtx_sys_point_offsets[] = {
+	UM780XTX_EC_SYS_POINT1,
+	UM780XTX_EC_SYS_POINT2,
+};
+
+static ssize_t um780xtx_sys_point_temp_show(struct device *dev,
+					    struct device_attribute *attr,
+					    char *buf)
+{
+	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+	u8 value;
+	int ret;
+
+	guard(hwmon_lock)(dev);
+	ret = ec_read(um780xtx_sys_point_offsets[sattr->index], &value);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%u\n", value * 1000);
+}
+
+static ssize_t um780xtx_sys_point_temp_store(struct device *dev,
+					     struct device_attribute *attr,
+					     const char *buf, size_t count)
+{
+	struct um780xtx_data *data = dev_get_drvdata(dev);
+	struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
+	u8 points[2];
+	u8 readback;
+	long value;
+	int ret;
+
+	ret = kstrtol(buf, 10, &value);
+	if (ret)
+		return ret;
+
+	guard(hwmon_lock)(dev);
+	/* Validate and clamp against the live peer threshold. */
+	ret = ec_read(UM780XTX_EC_SYS_POINT1, &points[0]);
+	if (ret)
+		return ret;
+	ret = ec_read(UM780XTX_EC_SYS_POINT2, &points[1]);
+	if (ret)
+		return ret;
+	if (points[0] >= points[1] || points[1] >= data->sys_point3)
+		return -EIO;
+
+	value = DIV_ROUND_CLOSEST(value, 1000);
+	if (!sattr->index)
+		value = clamp_val(value, 0, points[1] - 1);
+	else
+		value = clamp_val(value, points[0] + 1,
+				  data->sys_point3 - 1);
+	points[sattr->index] = value;
+
+	ret = ec_write(um780xtx_sys_point_offsets[sattr->index],
+		       points[sattr->index]);
+	if (ret)
+		return ret;
+	ret = ec_read(um780xtx_sys_point_offsets[sattr->index], &readback);
+	if (ret)
+		return ret;
+	if (readback != points[sattr->index])
+		return -EIO;
+
+	data->saved_sys_point1 = points[0];
+	data->saved_sys_point2 = points[1];
+	return count;
+}
+
+static SENSOR_DEVICE_ATTR_RW(pwm2_auto_point1_temp,
+			     um780xtx_sys_point_temp, 0);
+static SENSOR_DEVICE_ATTR_RW(pwm2_auto_point2_temp,
+			     um780xtx_sys_point_temp, 1);
+
+static struct attribute *um780xtx_extra_attrs[] = {
+	&sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr,
+	NULL
+};
+
+static const struct attribute_group um780xtx_extra_group = {
+	.attrs = um780xtx_extra_attrs,
+};
+
+static const struct attribute_group *um780xtx_extra_groups[] = {
+	&um780xtx_extra_group,
+	NULL
+};
+
+static umode_t um780xtx_is_visible(const void *data,
+				   enum hwmon_sensor_types type,
+				   u32 attr, int channel)
+{
+	if (type == hwmon_temp || type == hwmon_fan)
+		return 0444;
+	if (type == hwmon_pwm && attr == hwmon_pwm_enable)
+		return 0644;
+	return 0;
+}
+
+static int um780xtx_read(struct device *dev, enum hwmon_sensor_types type,
+			 u32 attr, int channel, long *value)
+{
+	u8 raw;
+	int ret;
+
+	if (type == hwmon_temp) {
+		ret = ec_read(channel ? UM780XTX_EC_TEMP_SYS :
+			      UM780XTX_EC_TEMP_CPU, &raw);
+		if (ret)
+			return ret;
+		*value = raw * 1000L;
+		return 0;
+	}
+	if (type == hwmon_fan) {
+		if (!channel)
+			ret = um780xtx_read_rpm(UM780XTX_EC_FAN1_HI,
+						UM780XTX_EC_FAN1_LO, value);
+		else
+			ret = um780xtx_read_rpm(UM780XTX_EC_FAN2_HI,
+						UM780XTX_EC_FAN2_LO, value);
+		return ret;
+	}
+	if (type == hwmon_pwm)
+		return um780xtx_read_profile(value);
+
+	return -EOPNOTSUPP;
+}
+
+static int um780xtx_write(struct device *dev, enum hwmon_sensor_types type,
+			  u32 attr, int channel, long value)
+{
+	struct um780xtx_data *data = dev_get_drvdata(dev);
+
+	if (type == hwmon_pwm && attr == hwmon_pwm_enable)
+		return um780xtx_write_profile(data, value);
+
+	return -EOPNOTSUPP;
+}
+
+static int um780xtx_read_string(struct device *dev,
+				enum hwmon_sensor_types type,
+				u32 attr, int channel, const char **str)
+{
+	static const char * const labels[] = { "CPU", "SYS" };
+
+	if (type == hwmon_temp && attr == hwmon_temp_label)
+		*str = labels[channel];
+	else if (type == hwmon_fan && attr == hwmon_fan_label)
+		*str = labels[channel];
+	else
+		return -EOPNOTSUPP;
+	return 0;
+}
+
+static const struct hwmon_ops um780xtx_hwmon_ops = {
+	.is_visible = um780xtx_is_visible,
+	.read = um780xtx_read,
+	.write = um780xtx_write,
+	.read_string = um780xtx_read_string,
+};
+
+static const struct hwmon_channel_info * const um780xtx_info[] = {
+	HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL,
+			   HWMON_T_INPUT | HWMON_T_LABEL),
+	HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL,
+			   HWMON_F_INPUT | HWMON_F_LABEL),
+	HWMON_CHANNEL_INFO(pwm, HWMON_PWM_ENABLE),
+	NULL
+};
+
+static const struct hwmon_chip_info um780xtx_chip_info = {
+	.ops = &um780xtx_hwmon_ops,
+	.info = um780xtx_info,
+};
+
+static int um780xtx_write_sys_point(u8 offset, u8 value)
+{
+	u8 readback;
+	int ret;
+
+	ret = ec_write(offset, value);
+	if (ret)
+		return ret;
+	ret = ec_read(offset, &readback);
+	if (ret)
+		return ret;
+
+	return readback == value ? 0 : -EIO;
+}
+
+static int um780xtx_restore_sys_points(struct um780xtx_data *data,
+				       u8 current_point2)
+{
+	u8 point1 = data->saved_sys_point1;
+	u8 point2 = data->saved_sys_point2;
+	int ret;
+
+	/* Keep strict ordering valid after each individual EC write. */
+	if (point1 >= current_point2) {
+		ret = um780xtx_write_sys_point(UM780XTX_EC_SYS_POINT2, point2);
+		if (ret)
+			return ret;
+		return um780xtx_write_sys_point(UM780XTX_EC_SYS_POINT1,
+						 point1);
+	}
+
+	ret = um780xtx_write_sys_point(UM780XTX_EC_SYS_POINT1, point1);
+	if (ret)
+		return ret;
+	return um780xtx_write_sys_point(UM780XTX_EC_SYS_POINT2, point2);
+}
+
+static int um780xtx_read_initial_state(struct um780xtx_data *data)
+{
+	u8 profile;
+	u8 point1;
+	u8 point2;
+	int ret;
+
+	ret = ec_read(UM780XTX_EC_CPU_PROFILE, &profile);
+	if (ret)
+		return ret;
+	if (profile != UM780XTX_EC_PROFILE_B1 &&
+	    profile != UM780XTX_EC_PROFILE_B2)
+		return -EINVAL;
+
+	ret = ec_read(UM780XTX_EC_SYS_POINT1, &point1);
+	if (ret)
+		return ret;
+	ret = ec_read(UM780XTX_EC_SYS_POINT2, &point2);
+	if (ret)
+		return ret;
+	if (point1 >= point2 || point2 >= data->sys_point3)
+		return -EINVAL;
+
+	data->saved_profile = profile;
+	data->saved_sys_point1 = point1;
+	data->saved_sys_point2 = point2;
+	return 0;
+}
+
+static int um780xtx_restore_state(struct um780xtx_data *data)
+{
+	u8 readback;
+	u8 point2;
+	int ret;
+
+	ret = ec_transaction(data->saved_profile, NULL, 0, NULL, 0);
+	if (ret)
+		return ret;
+	ret = ec_read(UM780XTX_EC_CPU_PROFILE, &readback);
+	if (ret)
+		return ret;
+	if (readback != data->saved_profile)
+		return -EIO;
+
+	ret = ec_read(UM780XTX_EC_SYS_POINT2, &point2);
+	if (ret)
+		return ret;
+
+	return um780xtx_restore_sys_points(data, point2);
+}
+
+static int um780xtx_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct um780xtx_data *data;
+	struct device *hwmon;
+	int ret;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, data);
+
+	ret = ec_read(UM780XTX_EC_SYS_POINT3, &data->sys_point3);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to read system fan limit\n");
+	ret = um780xtx_read_initial_state(data);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to read initial fan settings\n");
+
+	hwmon = devm_hwmon_device_register_with_info(dev, "um780xtx_ec", data,
+						     &um780xtx_chip_info,
+						     um780xtx_extra_groups);
+	if (IS_ERR(hwmon))
+		return PTR_ERR(hwmon);
+	data->hwmon_dev = hwmon;
+
+	return 0;
+}
+
+static int um780xtx_resume(struct device *dev)
+{
+	struct um780xtx_data *data = dev_get_drvdata(dev);
+
+	guard(hwmon_lock)(data->hwmon_dev);
+	return um780xtx_restore_state(data);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(um780xtx_pm_ops, NULL, um780xtx_resume);
+
+static struct platform_driver um780xtx_driver = {
+	.driver = {
+		.name = "um780xtx-ec-hwmon",
+		.pm = pm_sleep_ptr(&um780xtx_pm_ops),
+	},
+	.probe = um780xtx_probe,
+};
+
+static int __init um780xtx_init(void)
+{
+	int ret;
+
+	if (!dmi_check_system(um780xtx_dmi_table) ||
+	    !um780xtx_firmware_match() || !ec_get_handle())
+		return -ENODEV;
+	ret = platform_driver_register(&um780xtx_driver);
+	if (ret)
+		return ret;
+
+	um780xtx_pdev = platform_device_register_simple("um780xtx-ec-hwmon",
+							PLATFORM_DEVID_NONE,
+						       NULL, 0);
+	if (IS_ERR(um780xtx_pdev)) {
+		ret = PTR_ERR(um780xtx_pdev);
+		platform_driver_unregister(&um780xtx_driver);
+		return ret;
+	}
+	return 0;
+}
+
+static void __exit um780xtx_exit(void)
+{
+	platform_device_unregister(um780xtx_pdev);
+	platform_driver_unregister(&um780xtx_driver);
+}
+
+module_init(um780xtx_init);
+module_exit(um780xtx_exit);
+
+MODULE_AUTHOR("Sebastián Peyrott <speyrott@gmail.com>");
+MODULE_DESCRIPTION("Minisforum UM780 XTX EC hwmon driver");
+MODULE_LICENSE("GPL");
-- 
2.54.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v3 1/1] hwmon: Add Minisforum UM780 XTX EC monitoring and fan control
  2026-08-25 18:41 ` [PATCH v3 1/1] " Sebastián Peyrott
@ 2026-08-27  4:15   ` kernel test robot
  0 siblings, 0 replies; 4+ messages in thread
From: kernel test robot @ 2026-08-27  4:15 UTC (permalink / raw)
  To: Sebastián Peyrott, Guenter Roeck
  Cc: oe-kbuild-all, linux-hwmon, Jonathan Corbet, Shuah Khan,
	linux-doc, linux-kernel

Hi Sebastián,

kernel test robot noticed the following build errors:

[auto build test ERROR on 75f2c0b3690702c90863c2e138cb5520670845ea]

url:    https://github.com/intel-lab-lkp/linux/commits/Sebasti-n-Peyrott/hwmon-Add-Minisforum-UM780-XTX-EC-monitoring-and-fan-control/20260825-154107
base:   75f2c0b3690702c90863c2e138cb5520670845ea
patch link:    https://lore.kernel.org/r/20260825184107.355980-2-speyrott%40gmail.com
patch subject: [PATCH v3 1/1] hwmon: Add Minisforum UM780 XTX EC monitoring and fan control
config: i386-randconfig-141-20260827 (https://download.01.org/0day-ci/archive/20260827/202608271258.TPpO3tyX-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
smatch: v0.5.0-9187-g5189e3fb
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260827/202608271258.TPpO3tyX-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202608271258.TPpO3tyX-lkp@intel.com/

All errors (new ones prefixed by >>):

   ld: drivers/hwmon/minisforum-um780xtx.o: in function `um780xtx_oem_read':
>> drivers/hwmon/minisforum-um780xtx.c:75:(.text+0xa3): undefined reference to `ec_transaction'
   ld: drivers/hwmon/minisforum-um780xtx.o: in function `um780xtx_write_profile':
   drivers/hwmon/minisforum-um780xtx.c:141:(.text+0x19a): undefined reference to `ec_transaction'
>> ld: drivers/hwmon/minisforum-um780xtx.c:144:(.text+0x1ad): undefined reference to `ec_read'
   ld: drivers/hwmon/minisforum-um780xtx.o: in function `um780xtx_write_sys_point':
>> drivers/hwmon/minisforum-um780xtx.c:336:(.text+0x215): undefined reference to `ec_write'
   ld: drivers/hwmon/minisforum-um780xtx.c:339:(.text+0x223): undefined reference to `ec_read'
   ld: drivers/hwmon/minisforum-um780xtx.o: in function `um780xtx_sys_point_temp_store':
>> drivers/hwmon/minisforum-um780xtx.c:192:(.text+0x2ac): undefined reference to `ec_read'
   ld: drivers/hwmon/minisforum-um780xtx.c:195:(.text+0x2c3): undefined reference to `ec_read'
>> ld: drivers/hwmon/minisforum-um780xtx.c:209:(.text+0x385): undefined reference to `ec_write'
   ld: drivers/hwmon/minisforum-um780xtx.c:213:(.text+0x3ae): undefined reference to `ec_read'
   ld: drivers/hwmon/minisforum-um780xtx.o: in function `um780xtx_sys_point_temp_show':
   drivers/hwmon/minisforum-um780xtx.c:168:(.text+0x455): undefined reference to `ec_read'
   ld: drivers/hwmon/minisforum-um780xtx.o: in function `um780xtx_probe':
   drivers/hwmon/minisforum-um780xtx.c:431:(.text+0x4e9): undefined reference to `ec_read'
   ld: drivers/hwmon/minisforum-um780xtx.o: in function `um780xtx_read_initial_state':
   drivers/hwmon/minisforum-um780xtx.c:375:(.text+0x511): undefined reference to `ec_read'
   ld: drivers/hwmon/minisforum-um780xtx.c:382:(.text+0x53c): undefined reference to `ec_read'
   ld: drivers/hwmon/minisforum-um780xtx.o:drivers/hwmon/minisforum-um780xtx.c:385: more undefined references to `ec_read' follow
   ld: drivers/hwmon/minisforum-um780xtx.o: in function `um780xtx_restore_state':
   drivers/hwmon/minisforum-um780xtx.c:403:(.text+0x6f9): undefined reference to `ec_transaction'
   ld: drivers/hwmon/minisforum-um780xtx.c:406:(.text+0x712): undefined reference to `ec_read'
   ld: drivers/hwmon/minisforum-um780xtx.c:412:(.text+0x732): undefined reference to `ec_read'
   ld: drivers/hwmon/minisforum-um780xtx.o: in function `um780xtx_init':
>> drivers/hwmon/minisforum-um780xtx.c:473:(.init.text+0x75): undefined reference to `ec_get_handle'


vim +75 drivers/hwmon/minisforum-um780xtx.c

    72	
    73	static int um780xtx_oem_read(u8 command, u8 *value)
    74	{
  > 75		return ec_transaction(command, NULL, 0, value, 1);
    76	}
    77	
    78	static int um780xtx_read_rpm(u8 command_hi, u8 command_lo, long *rpm)
    79	{
    80		u8 hi_before;
    81		u8 hi_after;
    82		u8 lo;
    83		unsigned int value;
    84		int attempt;
    85		int ret;
    86	
    87		for (attempt = 0; attempt < UM780XTX_EC_RPM_RETRIES; attempt++) {
    88			ret = um780xtx_oem_read(command_hi, &hi_before);
    89			if (ret)
    90				return ret;
    91			ret = um780xtx_oem_read(command_lo, &lo);
    92			if (ret)
    93				return ret;
    94			ret = um780xtx_oem_read(command_hi, &hi_after);
    95			if (ret)
    96				return ret;
    97			if (hi_before != hi_after)
    98				continue;
    99	
   100			value = (hi_after << 8) | lo;
   101			if (value > UM780XTX_EC_MAX_RPM)
   102				continue;
   103	
   104			*rpm = value;
   105			return 0;
   106		}
   107	
   108		return -EIO;
   109	}
   110	
   111	static int um780xtx_read_profile(long *mode)
   112	{
   113		u8 profile;
   114		int ret;
   115	
   116		ret = ec_read(UM780XTX_EC_CPU_PROFILE, &profile);
   117		if (ret)
   118			return ret;
   119		if (profile == UM780XTX_EC_PROFILE_B1) {
   120			*mode = 2;
   121			return 0;
   122		}
   123		if (profile == UM780XTX_EC_PROFILE_B2) {
   124			*mode = 3;
   125			return 0;
   126		}
   127	
   128		return -ENODATA;
   129	}
   130	
   131	static int um780xtx_write_profile(struct um780xtx_data *data, long mode)
   132	{
   133		u8 expected;
   134		u8 profile;
   135		int ret;
   136	
   137		if (mode != 2 && mode != 3)
   138			return -EINVAL;
   139		expected = mode == 2 ? UM780XTX_EC_PROFILE_B1 : UM780XTX_EC_PROFILE_B2;
   140	
   141		ret = ec_transaction(expected, NULL, 0, NULL, 0);
   142		if (ret)
   143			return ret;
 > 144		ret = ec_read(UM780XTX_EC_CPU_PROFILE, &profile);
   145		if (ret)
   146			return ret;
   147		if (profile != expected)
   148			return -EIO;
   149	
   150		data->saved_profile = profile;
   151		return 0;
   152	}
   153	
   154	static const u8 um780xtx_sys_point_offsets[] = {
   155		UM780XTX_EC_SYS_POINT1,
   156		UM780XTX_EC_SYS_POINT2,
   157	};
   158	
   159	static ssize_t um780xtx_sys_point_temp_show(struct device *dev,
   160						    struct device_attribute *attr,
   161						    char *buf)
   162	{
   163		struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
   164		u8 value;
   165		int ret;
   166	
   167		guard(hwmon_lock)(dev);
   168		ret = ec_read(um780xtx_sys_point_offsets[sattr->index], &value);
   169		if (ret)
   170			return ret;
   171	
   172		return sysfs_emit(buf, "%u\n", value * 1000);
   173	}
   174	
   175	static ssize_t um780xtx_sys_point_temp_store(struct device *dev,
   176						     struct device_attribute *attr,
   177						     const char *buf, size_t count)
   178	{
   179		struct um780xtx_data *data = dev_get_drvdata(dev);
   180		struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr);
   181		u8 points[2];
   182		u8 readback;
   183		long value;
   184		int ret;
   185	
   186		ret = kstrtol(buf, 10, &value);
   187		if (ret)
   188			return ret;
   189	
   190		guard(hwmon_lock)(dev);
   191		/* Validate and clamp against the live peer threshold. */
 > 192		ret = ec_read(UM780XTX_EC_SYS_POINT1, &points[0]);
   193		if (ret)
   194			return ret;
   195		ret = ec_read(UM780XTX_EC_SYS_POINT2, &points[1]);
   196		if (ret)
   197			return ret;
   198		if (points[0] >= points[1] || points[1] >= data->sys_point3)
   199			return -EIO;
   200	
   201		value = DIV_ROUND_CLOSEST(value, 1000);
   202		if (!sattr->index)
   203			value = clamp_val(value, 0, points[1] - 1);
   204		else
   205			value = clamp_val(value, points[0] + 1,
   206					  data->sys_point3 - 1);
   207		points[sattr->index] = value;
   208	
 > 209		ret = ec_write(um780xtx_sys_point_offsets[sattr->index],
   210			       points[sattr->index]);
   211		if (ret)
   212			return ret;
   213		ret = ec_read(um780xtx_sys_point_offsets[sattr->index], &readback);
   214		if (ret)
   215			return ret;
   216		if (readback != points[sattr->index])
   217			return -EIO;
   218	
   219		data->saved_sys_point1 = points[0];
   220		data->saved_sys_point2 = points[1];
   221		return count;
   222	}
   223	

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH v3 0/1] hwmon: Add Minisforum UM780 XTX EC monitoring and fan control
  2026-08-25 18:41 [PATCH v3 0/1] hwmon: Add Minisforum UM780 XTX EC monitoring and fan control Sebastián Peyrott
  2026-08-25 18:41 ` [PATCH v3 1/1] " Sebastián Peyrott
@ 2026-08-28 16:02 ` Guenter Roeck
  1 sibling, 0 replies; 4+ messages in thread
From: Guenter Roeck @ 2026-08-28 16:02 UTC (permalink / raw)
  To: Sebastián Peyrott
  Cc: linux-hwmon, Jonathan Corbet, Shuah Khan, linux-doc, linux-kernel

On 8/25/26 11:41, Sebastián Peyrott wrote:
> Add a DMI-gated hwmon driver for both fans on the Minisforum UM780 XTX. The
> EC is an ITE IT5571E, but its register layout and OEM commands are defined
> by machine-specific firmware, so the driver only binds to the exact board
> revision and BIOS version on which it was tested.
> 
> The driver exposes both fan-control temperatures and tachometers, two OEM
> automatic CPU fan profiles, and two writable system-fan transition
> temperatures. It preserves settings selected through its interface across
> s2idle.
> 
> Tested on the target system with concurrent RPM reads, OEM profile changes,
> system-fan threshold changes, unload/reload stress, and s2idle resume. The
> settings were correct from the first userspace sample after resume and
> remained stable for 15 seconds.
> 
> OpenAI Codex assisted with reverse-engineering review, code editing, test
> orchestration, and patch and email preparation across a long interactive
> session. I reviewed every change, ran the live tests on the target
> hardware, and take responsibility for the submission.
> 
> Link: https://lore.kernel.org/r/20260818181345.203295-1-speyrott@gmail.com
> 
> Sebastián Peyrott (1):
>    hwmon: Add Minisforum UM780 XTX EC monitoring and fan control
> 

Note: For a single patch, an introduction patch is unnecessary and just adds
work for the maintainer.

Guenter

> ---
> Changes in v3:
> - return -EIO after bounded RPM coherence retries and -ENODATA for an
>    unknown CPU profile;
> - seed saved settings at probe, update them only after successful hwmon
>    writes, and restore them at resume without reading the EC at suspend;
> - clarify saved-state naming, live threshold validation, and resume
>    documentation.
> 
>   Documentation/hwmon/index.rst               |   1 +
>   Documentation/hwmon/minisforum-um780xtx.rst |  68 +++
>   MAINTAINERS                                 |   7 +
>   drivers/hwmon/Kconfig                       |  16 +
>   drivers/hwmon/Makefile                      |   1 +
>   drivers/hwmon/minisforum-um780xtx.c         | 501 ++++++++++++++++++++
>   6 files changed, 594 insertions(+)
>   create mode 100644 Documentation/hwmon/minisforum-um780xtx.rst
>   create mode 100644 drivers/hwmon/minisforum-um780xtx.c
> 
> 
> base-commit: 75f2c0b3690702c90863c2e138cb5520670845ea


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-08-28 16:02 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-25 18:41 [PATCH v3 0/1] hwmon: Add Minisforum UM780 XTX EC monitoring and fan control Sebastián Peyrott
2026-08-25 18:41 ` [PATCH v3 1/1] " Sebastián Peyrott
2026-08-27  4:15   ` kernel test robot
2026-08-28 16:02 ` [PATCH v3 0/1] " Guenter Roeck

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®