mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Bozhen Peng <bozhenpeng93@gmail.com>
To: "Hans de Goede" <hansg@kernel.org>,
	"Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
Cc: Armin Wolf <W_Armin@gmx.de>, Gladyshev Ilya <foxido@foxido.dev>,
	Mingyou Chen <qby140326@gmail.com>,
	Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>,
	Randy Dunlap <rdunlap@infradead.org>,
	platform-driver-x86@vger.kernel.org, linux-doc@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH] platform/x86: bitland-mifs-wmi: Support WUJIE15 PRO HPT
Date: Sun, 13 Sep 2026 18:12:08 -0700	[thread overview]
Message-ID: <0da43a8c-1755-49fe-b8e2-403489297f06@gmail.com> (raw)

I found that my WUJIE15 PRO with the HPT board uses three profiles:
performance (0), balanced (1), and quiet (2). The driver's default
mapping gets these wrong and exposes a fourth mode that the firmware
does not implement. My laptop only has USB-C power input, so the
circular-hole adapter requirement also rules out its supported external-
power configuration.

I added a check matching the vendor, product and board names to select
the correct profiles and power check. During testing, I saw
power_supply_is_system_supplied() return true even after I unplugged the
charger during sleep. I switched the check to the ACPI adapter's _PSR,
which reads the current power-present bit on this machine. I also made
resume select balanced if power was removed, rather than restore the
saved performance profile. If the power query fails, I return the error
instead of assuming the charger is connected.

I checked the firmware methods and found only two fan readings, in F2/F1
order, and no MIFS CPU temperature reading. Some unimplemented GET
commands return success with zero data, so I could not use a successful
query to establish sensor support. I register the two fans and leave out
the temperature input. I also hide the unsupported GPU, RGB mode and fan
boost controls, and check firmware replies to the remaining SET
commands. I left normal keyboard backlight control unchanged.

I found that the event GUID also matches redmi-wmi, so I made that
driver decline this machine and leave its notifications to bitland-mifs-
wmi. I kept the existing behavior for other machines and documented the
HPT differences.

Signed-off-by: Bozhen Peng <bozhenpeng93@gmail.com>
---
I tested this on my MECHREVO WUJIE15 PRO (WUJIE15 Series-HPT), comparing
the firmware methods and profile values with the Windows OEM controls.

I ran the hardware tests on Ubuntu 26.04.1, kernel 7.0.0-30-generic. I
used a small adaptation for that kernel's older WMI API in the test
modules. I have left that adaptation and the extra diagnostic logging
out of this patch.

I ran 22 automated profile, backlight, binding and sensor checks, all of
which passed, and tested the actual hotkeys. I recorded about 2280 and
2850-2970 RPM from the two fans while running, and zero when stopped. I
did not independently calibrate the RPM readings.

For the final _PSR change, I ran two s2idle tests. With the charger
connected, the driver read supplied=1 and restored performance; it
stayed in performance afterwards. With the charger unplugged during
sleep, it read supplied=0 and restored balanced; it stayed balanced
afterwards. Requesting performance on battery then returned EOPNOTSUPP.
The logs include the resume decision and firmware readbacks.

I also built a full x86_64 kernel and both modules against the for-next
base below, without the WMI API adaptation. I used x86_64_defconfig with
BITLAND_MIFS_WMI and REDMI_WMI enabled, GCC 15.2.0 and W=1. I saw no
compiler warnings from either changed driver.

 .../wmi/devices/bitland-mifs-wmi.rst          |  31 ++++
 drivers/platform/x86/bitland-mifs-wmi.c       | 170 +++++++++++++++++-
 drivers/platform/x86/redmi-wmi.c              |   7 +
 3 files changed, 201 insertions(+), 7 deletions(-)

diff --git a/Documentation/wmi/devices/bitland-mifs-wmi.rst b/Documentation/wmi/devices/bitland-mifs-wmi.rst
index 9e86ecc29..fd966cf18 100644
--- a/Documentation/wmi/devices/bitland-mifs-wmi.rst
+++ b/Documentation/wmi/devices/bitland-mifs-wmi.rst
@@ -7,6 +7,37 @@ Bitland MIFS driver (bitland-mifs-wmi)
 Introduction
 ============
 
+MECHREVO WUJIE15 PRO (HPT) firmware variant
+-----------------------------------------
+
+The MECHREVO WUJIE15 PRO with board WUJIE15 Series-HPT uses a reduced
+interface. The driver selects this variant using exact system vendor,
+product name and board name matches.
+
+The supported platform profiles are low-power (raw value 2), balanced
+(1), and performance (0). There is no separate full-speed mode.
+USB-C is the only external power input on this model. Performance is
+available on external power, but not on battery. The driver queries the
+ACPI adapter's _PSR for current power presence because USB-C connector
+ONLINE state may still be stale during resume. A failed query is not
+treated as external power being present.
+On resume, a saved performance profile becomes balanced if external
+power was removed, matching the firmware's AC-loss transition.
+
+Command 0x0d returns two little-endian tachometers, EC Fan 2 followed by
+Fan 1. No third fan or CPU-temperature input is provided. GPU switching,
+RGB modes and fan boost are not exposed. Keyboard backlight brightness
+uses the normal 0 through 3 values.
+
+Unsupported GET commands can return success (0x8000) with an empty
+payload. Successful queries alone therefore do not establish the
+presence of a sensor or control; the driver uses the known capabilities.
+Supported profile and backlight SET calls are also checked for a
+firmware acknowledgment.
+
+The event GUID is shared with redmi-wmi. On this exact DMI combination,
+redmi-wmi declines binding so bitland-mifs-wmi handles the notifications.
+
 
 EC WMI interface description
 ============================
diff --git a/drivers/platform/x86/bitland-mifs-wmi.c b/drivers/platform/x86/bitland-mifs-wmi.c
index 3a3731845..3193ae4bd 100644
--- a/drivers/platform/x86/bitland-mifs-wmi.c
+++ b/drivers/platform/x86/bitland-mifs-wmi.c
@@ -14,6 +14,7 @@
 #include <linux/dev_printk.h>
 #include <linux/device.h>
 #include <linux/device/devres.h>
+#include <linux/dmi.h>
 #include <linux/err.h>
 #include <linux/hwmon.h>
 #include <linux/init.h>
@@ -159,6 +160,19 @@ struct bitland_mifs_wmi_data {
 	struct device *hwmon_dev;
 	struct device *pp_dev;
 	enum platform_profile_option saved_profile;
+	bool is_wujie15_hpt;
+};
+
+/* Profile mapping and capabilities for the WUJIE15 PRO HPT board. */
+static const struct dmi_system_id bitland_wujie15_hpt[] = {
+	{
+		.matches = {
+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "MECHREVO"),
+			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "WUJIE15 PRO"),
+			DMI_EXACT_MATCH(DMI_BOARD_NAME, "WUJIE15 Series-HPT"),
+		},
+	},
+	{ }
 };
 
 static int bitland_mifs_wmi_call(struct bitland_mifs_wmi_data *data,
@@ -167,12 +181,15 @@ static int bitland_mifs_wmi_call(struct bitland_mifs_wmi_data *data,
 {
 	struct wmi_buffer in_buf = { .length = sizeof(*input), .data = (void *)input };
 	struct wmi_buffer out_buf = { 0 };
+	struct bitland_mifs_output reply;
 	int ret;
 
 	guard(mutex)(&data->lock);
 
-	if (!output)
+	if (!output && !data->is_wujie15_hpt)
 		return wmidev_invoke_procedure(data->wdev, 0, 1, &in_buf);
+	if (!output)
+		output = &reply;
 
 	ret = wmidev_invoke_method(data->wdev, 0, 1, &in_buf, &out_buf, sizeof(*output));
 	if (ret)
@@ -181,6 +198,20 @@ static int bitland_mifs_wmi_call(struct bitland_mifs_wmi_data *data,
 	memcpy(output, out_buf.data, sizeof(*output));
 	kfree(out_buf.data);
 
+	/* HPT's supported profile/backlight calls acknowledge with 0x8000. */
+	if (data->is_wujie15_hpt) {
+		u16 status = get_unaligned_le16(output);
+
+		if (status == 0xe000)
+			return -EOPNOTSUPP;
+		if (status != 0x8000)
+			return -EIO;
+		dev_dbg(&data->wdev->dev,
+			"HPT op=%#x fn=%#x arg=%u value=%u status=%#x\n",
+			input->operation, input->function, input->payload[0],
+			output->data[0], status);
+	}
+
 	return 0;
 }
 
@@ -201,6 +232,22 @@ static int laptop_profile_get(struct device *dev,
 	if (ret)
 		return ret;
 
+	if (data->is_wujie15_hpt) {
+		switch (result.data[0]) {
+		case 0:
+			*profile = PLATFORM_PROFILE_PERFORMANCE;
+			return 0;
+		case 1:
+			*profile = PLATFORM_PROFILE_BALANCED;
+			return 0;
+		case 2:
+			*profile = PLATFORM_PROFILE_LOW_POWER;
+			return 0;
+		default:
+			return -EINVAL;
+		}
+	}
+
 	switch (result.data[0]) {
 	case WMI_PP_BALANCED:
 		*profile = PLATFORM_PROFILE_BALANCED;
@@ -220,6 +267,26 @@ static int laptop_profile_get(struct device *dev,
 	return 0;
 }
 
+static int bitland_wujie15_is_supplied(void)
+{
+	struct acpi_device *adapter;
+	unsigned long long online;
+	acpi_status status;
+
+	/* USB-C ONLINE can still contain the pre-suspend connector state. */
+	adapter = acpi_dev_get_first_match_dev("ACPI0003", NULL, -1);
+	if (!adapter)
+		return -ENODEV;
+
+	/* This machine's adapter _PSR reads the current EC power-present bit. */
+	status = acpi_evaluate_integer(adapter->handle, "_PSR", NULL, &online);
+	acpi_dev_put(adapter);
+	if (ACPI_FAILURE(status) || online > 1)
+		return -EIO;
+
+	return online;
+}
+
 static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *data)
 {
 	struct bitland_mifs_input input = {
@@ -229,7 +296,15 @@ static int bitland_check_performance_capability(struct bitland_mifs_wmi_data *da
 	struct bitland_mifs_output output;
 	int ret;
 
-	/* Full-speed/performance mode requires DC power (not USB-C) */
+	/* WUJIE15 PRO is powered through USB-C only. */
+	if (data->is_wujie15_hpt) {
+		ret = bitland_wujie15_is_supplied();
+		if (ret < 0)
+			return ret;
+		return ret ? 0 : -EOPNOTSUPP;
+	}
+
+	/* Other models require the firmware's circular-hole adapter type. */
 	if (!power_supply_is_system_supplied())
 		return -EOPNOTSUPP;
 
@@ -256,6 +331,27 @@ static int laptop_profile_set(struct device *dev,
 	int ret;
 	u8 val;
 
+	if (data->is_wujie15_hpt) {
+		switch (profile) {
+		case PLATFORM_PROFILE_LOW_POWER:
+			val = 2;
+			break;
+		case PLATFORM_PROFILE_BALANCED:
+			val = 1;
+			break;
+		case PLATFORM_PROFILE_PERFORMANCE:
+			ret = bitland_check_performance_capability(data);
+			if (ret)
+				return ret;
+			val = 0;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+		input.payload[0] = val;
+		return bitland_mifs_wmi_call(data, &input, NULL);
+	}
+
 	switch (profile) {
 	case PLATFORM_PROFILE_LOW_POWER:
 		val = WMI_PP_QUIET;
@@ -286,9 +382,12 @@ static int laptop_profile_set(struct device *dev,
 
 static int platform_profile_probe(void *drvdata, unsigned long *choices)
 {
+	struct bitland_mifs_wmi_data *data = drvdata;
+
 	set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
 	set_bit(PLATFORM_PROFILE_BALANCED, choices);
-	set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
+	if (!data->is_wujie15_hpt)
+		set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
 	set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
 
 	return 0;
@@ -314,13 +413,25 @@ static int bitland_mifs_wmi_suspend(struct device *dev)
 static int bitland_mifs_wmi_resume(struct device *dev)
 {
 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
+	enum platform_profile_option profile = data->saved_profile;
+	int supplied;
 
 	/* Skip event device */
 	if (!data->pp_dev)
 		return 0;
 
-	dev_dbg(dev, "Resuming, restoring profile %d\n", data->saved_profile);
-	return laptop_profile_set(dev, data->saved_profile);
+	/* Match the firmware's performance-to-balanced transition on AC loss. */
+	if (data->is_wujie15_hpt &&
+	    profile == PLATFORM_PROFILE_PERFORMANCE) {
+		supplied = bitland_wujie15_is_supplied();
+		if (supplied < 0)
+			return supplied;
+		if (!supplied)
+			profile = PLATFORM_PROFILE_BALANCED;
+	}
+
+	dev_dbg(dev, "Resuming, restoring profile %d\n", profile);
+	return laptop_profile_set(dev, profile);
 }
 
 static DEFINE_SIMPLE_DEV_PM_OPS(bitland_mifs_wmi_pm_ops,
@@ -386,7 +497,16 @@ static int laptop_hwmon_read_string(struct device *dev,
 				    enum hwmon_sensor_types type, u32 attr,
 				    int channel, const char **str)
 {
+	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
+	static const char *const hpt_fan_labels[] = { "Fan 2", "Fan 1" };
+
 	if (type == hwmon_fan && attr == hwmon_fan_label) {
+		if (data->is_wujie15_hpt) {
+			if (channel < 0 || channel >= ARRAY_SIZE(hpt_fan_labels))
+				return -EINVAL;
+			*str = hpt_fan_labels[channel];
+			return 0;
+		}
 		if (channel >= 0 && channel < ARRAY_SIZE(fan_labels)) {
 			*str = fan_labels[channel];
 			return 0;
@@ -414,6 +534,18 @@ static const struct hwmon_chip_info laptop_chip_info = {
 	.info = laptop_hwmon_info,
 };
 
+/* HPT fills only the F2/F1 tachometers; the temperature GET is a no-op. */
+static const struct hwmon_channel_info *hpt_hwmon_info[] = {
+	HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL,
+			   HWMON_F_INPUT | HWMON_F_LABEL),
+	NULL
+};
+
+static const struct hwmon_chip_info hpt_chip_info = {
+	.ops = &laptop_hwmon_ops,
+	.info = hpt_hwmon_info,
+};
+
 static int laptop_kbd_led_set(struct led_classdev *led_cdev,
 			      enum led_brightness value)
 {
@@ -606,7 +738,28 @@ static const struct attribute *const laptop_attrs[] = {
 	&dev_attr_fan_boost.attr,
 	NULL,
 };
-ATTRIBUTE_GROUPS(laptop);
+
+static umode_t laptop_attr_is_visible(struct kobject *kobj,
+				      const struct attribute *attr, int index)
+{
+	struct bitland_mifs_wmi_data *data = dev_get_drvdata(kobj_to_dev(kobj));
+
+	/* All three custom attributes require methods absent on HPT. */
+	if (data && data->is_wujie15_hpt)
+		return 0;
+
+	return attr->mode;
+}
+
+static const struct attribute_group laptop_group = {
+	.attrs_const = laptop_attrs,
+	.is_visible_const = laptop_attr_is_visible,
+};
+
+static const struct attribute_group *laptop_groups[] = {
+	&laptop_group,
+	NULL
+};
 
 static const struct key_entry bitland_mifs_wmi_keymap[] = {
 	{ KE_KEY, WMI_EVENT_OPEN_APP, { KEY_PROG1 } },
@@ -661,6 +814,7 @@ static int bitland_notifier_callback(struct notifier_block *nb,
 static int bitland_mifs_wmi_probe(struct wmi_device *wdev, const void *context)
 {
 	struct bitland_mifs_wmi_data *drv_data;
+	const struct hwmon_chip_info *chip_info;
 	enum bitland_wmi_device_type dev_type =
 		(enum bitland_wmi_device_type)(unsigned long)context;
 	struct led_init_data init_data = {
@@ -675,6 +829,7 @@ static int bitland_mifs_wmi_probe(struct wmi_device *wdev, const void *context)
 		return -ENOMEM;
 
 	drv_data->wdev = wdev;
+	drv_data->is_wujie15_hpt = dmi_check_system(bitland_wujie15_hpt);
 
 	ret = devm_mutex_init(&wdev->dev, &drv_data->lock);
 	if (ret)
@@ -708,10 +863,11 @@ static int bitland_mifs_wmi_probe(struct wmi_device *wdev, const void *context)
 		return PTR_ERR(drv_data->pp_dev);
 
 	/* Register hwmon */
+	chip_info = drv_data->is_wujie15_hpt ? &hpt_chip_info : &laptop_chip_info;
 	drv_data->hwmon_dev = devm_hwmon_device_register_with_info(&wdev->dev,
 								   "bitland_mifs",
 								   drv_data,
-								   &laptop_chip_info,
+								   chip_info,
 								   NULL);
 	if (IS_ERR(drv_data->hwmon_dev))
 		return PTR_ERR(drv_data->hwmon_dev);
diff --git a/drivers/platform/x86/redmi-wmi.c b/drivers/platform/x86/redmi-wmi.c
index cc82ef502..de41642b7 100644
--- a/drivers/platform/x86/redmi-wmi.c
+++ b/drivers/platform/x86/redmi-wmi.c
@@ -4,6 +4,7 @@
 #include <linux/acpi.h>
 #include <linux/bits.h>
 #include <linux/device.h>
+#include <linux/dmi.h>
 #include <linux/input.h>
 #include <linux/input/sparse-keymap.h>
 #include <linux/module.h>
@@ -69,6 +70,12 @@ static int redmi_wmi_probe(struct wmi_device *wdev, const void *context)
 	struct redmi_wmi *data;
 	int err;
 
+	/* This shared GUID belongs to bitland-mifs-wmi on the WUJIE15 PRO HPT. */
+	if (dmi_match(DMI_SYS_VENDOR, "MECHREVO") &&
+	    dmi_match(DMI_PRODUCT_NAME, "WUJIE15 PRO") &&
+	    dmi_match(DMI_BOARD_NAME, "WUJIE15 Series-HPT"))
+		return -ENODEV;
+
 	/* Init dev */
 	data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
 	if (!data)

base-commit: cee9395acd8043be0644b25c34bfa86623f2b935

             reply	other threads:[~2026-09-14  1:12 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-14  1:12 Bozhen Peng [this message]
2026-09-14  2:01 ` Randy Dunlap
2026-09-14  2:20   ` Bozhen Peng

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=0da43a8c-1755-49fe-b8e2-403489297f06@gmail.com \
    --to=bozhenpeng93@gmail.com \
    --cc=W_Armin@gmx.de \
    --cc=corbet@lwn.net \
    --cc=foxido@foxido.dev \
    --cc=hansg@kernel.org \
    --cc=ilpo.jarvinen@linux.intel.com \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=platform-driver-x86@vger.kernel.org \
    --cc=qby140326@gmail.com \
    --cc=rdunlap@infradead.org \
    --cc=skhan@linuxfoundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®