[PATCH] platform/x86: bitland-mifs-wmi: add TM2424 ops and hotkeys

KentoNion posted 1 patch 1 month ago
[PATCH] platform/x86: bitland-mifs-wmi: add TM2424 ops and hotkeys
Posted by KentoNion 1 month ago
Xiaomi Book Pro 14 2026 (DMI sys_vendor=XIAOMI, board_name=TM2424)
uses five cmd 0x08 bytes that do not fit the four-field mode_map:

  Quiet=0x02, Turbo=0x03, Full-speed=0x04, Auto=0x09, Eco=0x0A

Add a TM2424 entry to the per-machine ops table. SET maps the three
platform_profile choices to Eco / Auto / Full-speed; GET aliases Quiet
and Turbo. Full-speed falls back to Turbo when firmware NACKs.

The reduced firmware does not implement hwmon, keyboard LED or the
gpu_mode/kb_mode/fan_boost attributes.

Map remaining TM2424 HID_EVENT20 keys (projection value 0, AI break
0x24, Mi button 0x25/0x26). Mic-mute 0x21 and settings 0x1B are
already in the merged keymap.

This supersedes the standalone TM2424 quirk
<20260816215825.289356-1-kento@kekto.ru>.

Tested on TM2424-77080: performance on AC holds ~50W package power.
acpi_listen shows HID_EVENT20 as wmi PNP0C14:00 notify 0x20 for the
mic, AI, projection, settings and Mi keys (see cover letter).

Signed-off-by: KentoNion <kento@kekto.ru>
---

This is a follow-up to Mingyou's v5, not a competing rewrite.

v5's four-field mode_map cannot express TM2424: five firmware bytes
(Quiet/Turbo/Full-speed/Auto/Eco) collapse to three platform_profile
choices, GET has aliases, and SET of performance must try Full-speed
then Turbo when firmware NACKs. The new ops callbacks stay NULL on
the Bitland/Redmi paths.

DMI is XIAOMI + TM2424. Mingyou's Xiaomi Book match is TIMI, so this
SKU would otherwise keep the Bitland 0..3 defaults and stay broken.

Yuming's 2/5 names on this machine are inverted (0x03 is Turbo, 0x09
is Auto, 0x0A is Eco). Please do not take that SET mapping for TM2424.

I did not include lid polling or EC fan tachs. On this BIOS the ACPI
EC still starts and Lid Switch already binds; ECON is an unresolved
External used only by _STA (not defined in DSDT or any SSDT). That
is an ACPI firmware bug, not a WMI one. Decoded excerpts and dmesg
are a reply on Yuming's 0/5, In-Reply-To Armin.

I also left the v5 suspend hook alone. Ilpo already nacked the
profile_valid pattern on Yuming 1/5. With this mapping GET no longer
returns unmapped cmd 0x08 bytes on TM2424, so the existing hook
should not abort sleep here. A global never-abort plus a status-word
check still belongs in a separate patch; please take Ilpo's union +
endianness nits there. This patch only looks at output.operation ==
0x80 on TM2424 SET (the accepted byte; same field Yuming packs as
0x8000) so Full-speed can fall back to Turbo.

One regression to watch when v5 is rebased: e0d6312578e1 turned some
redmi-wmi KE_IGNORE entries into KEY_KBDILLUMTOGGLE / KEY_PERFORMANCE
/ KEY_FN_ESC. v5 2/6 still KE_IGNOREs those; please carry Musaev's
mappings into the merged keymap in v6.

The LKP const-qualifier error on the old standalone quirk is obsolete
once v5's non-const DEVICE_ATTR is the base.

Tested on Xiaomi Book Pro 14 2026, SKU TM2424-77080, BIOS XMAPT4B0P0707.

acpi_listen on that machine (HID_EVENT20 is notify 0x20 on PNP0C14:00).
Volume and panel brightness are standard ACPI and do not go through
this driver. The WMI-backed keys with this keymap:

  FN+F4 mic:
    wmi PNP0C14:00 00000020 00000000
    button/micmute MICMUTE 00000080 00000000 K

  FN+F7 AI (make KEY_PROG1; break is KE_IGNORE):
    wmi PNP0C14:00 00000020 00000000
    button/prog1 PROG1 00000080 00000000 K
    wmi PNP0C14:00 00000020 00000000

  FN+F8 projection:
    wmi PNP0C14:00 00000020 00000000
    video/switchmode VMOD 00000080 00000000 K

  FN+F9 settings (KEY_CONFIG, evdev only):
    wmi PNP0C14:00 00000020 00000000

  Mi button (make KEY_VENDOR; break is KE_IGNORE):
    wmi PNP0C14:00 00000020 00000000
    button/vendor VNDR 00000080 00000000 K
    wmi PNP0C14:00 00000020 00000000

Raw session (including power/mute/volume/brightness):

  button/power PBTN 00000080 00000000
  button/power LNXPWRBN:00 00000080 00000001
  button/mute MUTE 00000080 00000000 K
  button/volumeup VOLUP 00000080 00000000 K
  button/volumeup VOLUP 00000080 00000000 K
  button/volumedown VOLDN 00000080 00000000 K
  wmi PNP0C14:00 00000020 00000000
  button/micmute MICMUTE 00000080 00000000 K
  wmi PNP0C14:00 00000020 00000000
  button/micmute MICMUTE 00000080 00000000 K
  video/brightnessdown BRTDN 00000087 00000000
  video/brightnessup BRTUP 00000086 00000000
  wmi PNP0C14:00 00000020 00000000
  button/prog1 PROG1 00000080 00000000 K
  wmi PNP0C14:00 00000020 00000000
  wmi PNP0C14:00 00000020 00000000
  video/switchmode VMOD 00000080 00000000 K
  wmi PNP0C14:00 00000020 00000000
  wmi PNP0C14:00 00000020 00000000
  wmi PNP0C14:00 00000020 00000000
  wmi PNP0C14:00 00000020 00000000
  button/vendor VNDR 00000080 00000000 K
  wmi PNP0C14:00 00000020 00000000

diff --git a/Documentation/wmi/devices/bitland-mifs-wmi.rst b/Documentation/wmi/devices/bitland-mifs-wmi.rst
index 9e86ecc..a89f872 100644
--- a/Documentation/wmi/devices/bitland-mifs-wmi.rst
+++ b/Documentation/wmi/devices/bitland-mifs-wmi.rst
@@ -93,8 +93,13 @@ The following Command IDs are used in the third byte of the buffer:
 +----------+-----------------------+------------------------------------------+
 | ID       | Name                  | Values / Description                     |
 +==========+=======================+==========================================+
-| 8        | SystemPerMode         | 0: Balance, 1: Performance, 2: Quiet,    |
-|          |                       | 3: Full-speed                            |
+| 8        | SystemPerMode         | Bitland gaming: 0 Balance, 1             |
+|          |                       | Performance, 2 Quiet, 3 Full-speed.      |
+|          |                       | Redmi: 0 Performance, 1 Balanced, 2      |
+|          |                       | Quiet, 4 Full-speed.                     |
+|          |                       | TM2424 (Xiaomi Book Pro 14): 0x02        |
+|          |                       | Quiet, 0x03 Turbo, 0x04 Full-speed,      |
+|          |                       | 0x09 Auto, 0x0A Eco.                     |
 +----------+-----------------------+------------------------------------------+
 | 9        | GPUMode               | 0: Hybrid, 1: Discrete, 2: UMA           |
 +----------+-----------------------+------------------------------------------+
@@ -188,11 +193,8 @@ Performance Modes
 -----------------
 Changing the performance mode via Command ID 0x08 (SystemPerMode) affects the
 power limits (PL1/PL2) and fan curves managed by the Embedded Controller (EC).
-Note that the "Full-speed" and "Performance" mode (1, 3) is typically only
-available when the system is connected to a DC power source (not USB-C/PD).
-
-In the driver implementation, switch to performance/full-speed mode without
-DC power connected will throw the EOPNOTSUPP error.
+The value encoding and the AC-power requirement are machine-specific (see
+Command ID 8 and the driver ops table).
 
 Graphics Switching
 ------------------
diff --git a/drivers/platform/x86/bitland-mifs-wmi.c b/drivers/platform/x86/bitland-mifs-wmi.c
index 6031b1c..b49a6fb 100644
--- a/drivers/platform/x86/bitland-mifs-wmi.c
+++ b/drivers/platform/x86/bitland-mifs-wmi.c
@@ -44,6 +44,9 @@
 #define BITLAND_MIFS_GUID	"B60BFB48-3E5B-49E4-A0E9-8CFFE1B3434B"
 #define BITLAND_EVENT_GUID	"46C93E13-EE9B-4262-8488-563BCA757FEF"
 
+/* Firmware status in output.operation (wire OutData[1]): request accepted. */
+#define MIFS_STATUS_OK		0x80
+
 enum bitland_mifs_operation {
 	WMI_METHOD_GET	= 250,
 	WMI_METHOD_SET	= 251,
@@ -110,8 +113,21 @@ static const struct bitland_profile_mode_map redmi_mode_map = {
 	.full_speed	= 4,
 };
 
+/* TM2424 SystemPerMode bytes (Eco/Auto/Full-speed; Quiet/Turbo are GET aliases). */
+enum bitland_mifs_tm2424_perf_mode {
+	WMI_TM2424_QUIET	= 0x02,
+	WMI_TM2424_TURBO	= 0x03,
+	WMI_TM2424_FULL_SPEED	= 0x04,
+	WMI_TM2424_AUTO		= 0x09,
+	WMI_TM2424_ECO		= 0x0A,
+};
+
 static int bitland_pp_check_capability(struct bitland_mifs_wmi_data *data);
 static int redmi_pp_check_capability(struct bitland_mifs_wmi_data *data);
+static int tm2424_profile_get(struct bitland_mifs_wmi_data *data,
+			      enum platform_profile_option *profile);
+static int tm2424_profile_set(struct bitland_mifs_wmi_data *data,
+			      enum platform_profile_option profile);
 
 /*
  * Machine-specific operations. The MIFS WMI interface is shared by
@@ -122,9 +138,15 @@ struct bitland_mifs_ops {
 	const char *name;
 	const struct bitland_profile_mode_map *mode_map;
 	int (*check_performance_capability)(struct bitland_mifs_wmi_data *data);
+	int (*profile_get)(struct bitland_mifs_wmi_data *data,
+			   enum platform_profile_option *profile);
+	int (*profile_set)(struct bitland_mifs_wmi_data *data,
+			   enum platform_profile_option profile);
 	bool has_full_speed;
 	bool has_cpu_power;
 	bool quirk_refresh_rate_toggle;
+	/* hwmon, keyboard LED and gpu/kb/fan sysfs are unimplemented */
+	bool reduced_fw;
 };
 
 static const struct bitland_mifs_ops bitland_ops = {
@@ -151,7 +173,22 @@ static const struct bitland_mifs_ops redmi_g_ops = {
 	.quirk_refresh_rate_toggle = true,
 };
 
+static const struct bitland_mifs_ops tm2424_ops = {
+	.name = "Xiaomi Book Pro 14",
+	.profile_get = tm2424_profile_get,
+	.profile_set = tm2424_profile_set,
+	.reduced_fw = true,
+};
+
 static const struct dmi_system_id bitland_mifs_dmi_table[] = {
+	{
+		/* Xiaomi Book Pro 14. Vendor is XIAOMI, not TIMI. */
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "XIAOMI"),
+			DMI_MATCH(DMI_BOARD_NAME, "TM2424"),
+		},
+		.driver_data = (void *)&tm2424_ops,
+	},
 	{
 		/* Redmi G */
 		.matches = {
@@ -296,11 +333,89 @@ static int bitland_mifs_wmi_call(struct bitland_mifs_wmi_data *data,
 	return 0;
 }
 
+static int tm2424_wmi_perf_set(struct bitland_mifs_wmi_data *data, u8 val)
+{
+	struct bitland_mifs_input input = {
+		.operation = WMI_METHOD_SET,
+		.function = WMI_FN_SYSTEM_PER_MODE,
+	};
+	struct bitland_mifs_output output;
+	int ret;
+
+	input.payload[0] = val;
+
+	ret = bitland_mifs_wmi_call(data, &input, &output);
+	if (ret)
+		return ret;
+
+	if (output.operation != MIFS_STATUS_OK) {
+		dev_dbg(&data->wdev->dev,
+			"perf mode %#x rejected by firmware (status=%#x)\n",
+			val, output.operation);
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int tm2424_profile_get(struct bitland_mifs_wmi_data *data,
+			      enum platform_profile_option *profile)
+{
+	struct bitland_mifs_input input = {
+		.operation = WMI_METHOD_GET,
+		.function = WMI_FN_SYSTEM_PER_MODE,
+	};
+	struct bitland_mifs_output output;
+	int ret;
+
+	ret = bitland_mifs_wmi_call(data, &input, &output);
+	if (ret)
+		return ret;
+
+	switch (output.data[0]) {
+	case WMI_TM2424_ECO:
+	case WMI_TM2424_QUIET:
+		*profile = PLATFORM_PROFILE_LOW_POWER;
+		break;
+	case WMI_TM2424_AUTO:
+		*profile = PLATFORM_PROFILE_BALANCED;
+		break;
+	case WMI_TM2424_FULL_SPEED:
+	case WMI_TM2424_TURBO:
+		*profile = PLATFORM_PROFILE_PERFORMANCE;
+		break;
+	default:
+		dev_dbg(&data->wdev->dev,
+			"unrecognized TM2424 perf mode %#x\n", output.data[0]);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int tm2424_profile_set(struct bitland_mifs_wmi_data *data,
+			      enum platform_profile_option profile)
+{
+	switch (profile) {
+	case PLATFORM_PROFILE_LOW_POWER:
+		return tm2424_wmi_perf_set(data, WMI_TM2424_ECO);
+	case PLATFORM_PROFILE_BALANCED:
+		return tm2424_wmi_perf_set(data, WMI_TM2424_AUTO);
+	case PLATFORM_PROFILE_PERFORMANCE:
+		/* Full-speed is rejected on battery; fall back to Turbo. */
+		if (tm2424_wmi_perf_set(data, WMI_TM2424_FULL_SPEED) == 0)
+			return 0;
+		return tm2424_wmi_perf_set(data, WMI_TM2424_TURBO);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
 static int laptop_profile_get(struct device *dev,
 			      enum platform_profile_option *profile)
 {
 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
-	const struct bitland_profile_mode_map *map = data->ops->mode_map;
+	const struct bitland_profile_mode_map *map;
 	struct bitland_mifs_input input = {
 		.reserved1 = 0,
 		.operation = WMI_METHOD_GET,
@@ -311,6 +426,10 @@ static int laptop_profile_get(struct device *dev,
 	u8 mode;
 	int ret;
 
+	if (data->ops->profile_get)
+		return data->ops->profile_get(data, profile);
+
+	map = data->ops->mode_map;
 	ret = bitland_mifs_wmi_call(data, &input, &result);
 	if (ret)
 		return ret;
@@ -369,7 +488,7 @@ static int laptop_profile_set(struct device *dev,
 			      enum platform_profile_option profile)
 {
 	struct bitland_mifs_wmi_data *data = dev_get_drvdata(dev);
-	const struct bitland_profile_mode_map *map = data->ops->mode_map;
+	const struct bitland_profile_mode_map *map;
 	struct bitland_mifs_input input = {
 		.reserved1 = 0,
 		.operation = WMI_METHOD_SET,
@@ -379,6 +498,11 @@ static int laptop_profile_set(struct device *dev,
 	int ret;
 	u8 mode;
 
+	if (data->ops->profile_set)
+		return data->ops->profile_set(data, profile);
+
+	map = data->ops->mode_map;
+
 	switch (profile) {
 	case PLATFORM_PROFILE_LOW_POWER:
 		mode = map->quiet;
@@ -897,6 +1021,7 @@ static const struct key_entry bitland_mifs_wmi_keymap[] = {
 	{ KE_KEY, BI_HOTKEY_CODE(WMI_EVENT_RESERVED_3, 0, 0), { KEY_ALL_APPLICATIONS } },
 	{ KE_KEY, BI_HOTKEY_CODE(0x1b, 0, 0), { KEY_CONFIG } },
 	{ KE_KEY, BI_HOTKEY_CODE(0x1b, 1, 0), { KEY_CONFIG } },
+	{ KE_KEY, BI_HOTKEY_CODE(WMI_EVENT_RESERVED_1, 0, 0), { KEY_SWITCHVIDEOMODE } },
 	{ KE_KEY, BI_HOTKEY_CODE(WMI_EVENT_RESERVED_1, 1, 0), { KEY_SWITCHVIDEOMODE } },
 	{ KE_KEY, BI_HOTKEY_CODE(WMI_EVENT_REFRESH_RATE, 0, 0), { KEY_REFRESH_RATE_TOGGLE } },
 
@@ -908,6 +1033,12 @@ static const struct key_entry bitland_mifs_wmi_keymap[] = {
 	{ KE_KEY, BI_HOTKEY_CODE(WMI_EVENT_FN_5, 1, 0), { KEY_ASSISTANT } },
 
 	{ KE_KEY, BI_HOTKEY_CODE(0x19, 1, 0), { KEY_ASSISTANT } },
+	/* TM2424 AI break event (make is 0x23 / WMI_EVENT_OPEN_APP) */
+	{ KE_IGNORE, BI_HOTKEY_CODE(0x24, 1, 0), {} },
+
+	/* TM2424 Mi button: make/break pair */
+	{ KE_KEY, BI_HOTKEY_CODE(0x25, 1, 0), { KEY_VENDOR } },
+	{ KE_IGNORE, BI_HOTKEY_CODE(0x26, 1, 0), {} },
 
 	{ KE_IGNORE, BI_HOTKEY_CODE(WMI_EVENT_KBD_BRIGHTNESS, 0, 0), {} },
 	{ KE_IGNORE, BI_HOTKEY_CODE(WMI_EVENT_KBD_BRIGHTNESS, 0, 0x80), {} },
@@ -1085,6 +1216,9 @@ static int bitland_mifs_wmi_probe(struct wmi_device *wdev, const void *context)
 	if (IS_ERR(drv_data->pp_dev))
 		return PTR_ERR(drv_data->pp_dev);
 
+	if (drv_data->ops->reduced_fw)
+		return 0;
+
 	/* Detect the keyboard type */
 	ret = bitland_detect_kbd_type(drv_data);
 	if (ret)
@@ -1243,5 +1377,6 @@ static struct wmi_driver bitland_mifs_wmi_driver = {
 module_wmi_driver(bitland_mifs_wmi_driver);
 
 MODULE_AUTHOR("Mingyou Chen <qby140326@gmail.com>");
+MODULE_AUTHOR("KentoNion <kento@kekto.ru>");
 MODULE_DESCRIPTION("Bitland MIFS (MiInterface) WMI driver");
 MODULE_LICENSE("GPL");
-- 
2.55.0