Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UX8406 (Zenbook Duo 2024) support #25

Open
hacker1024 opened this issue Apr 8, 2024 · 5 comments
Open

UX8406 (Zenbook Duo 2024) support #25

hacker1024 opened this issue Apr 8, 2024 · 5 comments

Comments

@hacker1024
Copy link

hacker1024 commented Apr 8, 2024

I'm creating this umbrella issue to discuss all features UX8406-related.

To start, here's a list of known issues:

Keyboard

  • The keyboard hotkeys do not work (USB and Bluetooth)
  • The keyboard backlight does not work (USB and Bluetooth)
  • Attaching or detaching the keyboard results in an unfiltered RFKILL keypress (internal USB only)

Display

  • The secondary display remains active when the keyboard is attached (ideally, this would be integrated into i915/KMS rather than work at a desktop environment level)
  • An ASUS ScreenPad brightness control is erroneously available
  • PSR causes gamma flickering (though this happens in Windows as well)

Power

  • The power profiles driver does not work - nothing shows up in sysfs or power-profiles-daemon
  • The battery charge limit does not persist when the system is powered off. It does when set from Windows.
@hacker1024
Copy link
Author

hacker1024 commented Apr 8, 2024

Here's my keyboard work so far (from my post on a NixOS forum thread)

Alright, with a lot of experimentation I've got the keyboard hotkeys working over Bluetooth. Here's what I've discovered so far:

  1. ASUS keyboards send hotkey events over custom HID reports. The ASUS HID driver turns these custom reports into standard keypresses, and is mostly compatible with this laptop too.
  2. The relevant HID descriptor is slightly different from what the driver expects, and so it needs to be modified. Luckily, a similar thing has already been done for previous detachable ASUS keyboards. Unfortunately, the logic is not exactly the same (the byte layout is a little different) - but it's close enough, so I used it for a working prototype. The kernel can handle the slightly mangled resultant descriptor.
  3. The ASUS HID driver will prefer to use WMI for backlight control if it is available. This is not great for our usecase (I don't think it works with Bluetooth or even USB - it's probably there for legacy reasons), so this behaviour needs to be changed. There's also no reason that the backlight should be disabled if the keyboard is plugged in to another ASUS laptop, either.
  4. The keyboard actually shows up as two HID devices: One for the keyboard itself, and one for the touchpad. Oddly, the function key by itself actually does generate HID reports - but on the touchpad device, for some reason. I don't think any driver currently takes advantage of this.
  5. Over USB, the relevant hotkey HID report descriptors are missing entirely, which is why I haven't been able to get it working. There's also some sort of third HID device, but I can't seem to generate any events from it. The function key reports over the touchpad are still available, though, so that might be an easy way to get similar functionality.
  6. I have been unable to get the backlight working in any capacity. This requires more investigation.

I think my next plan of action is to look at the USB HID descriptors and run some packet capture in Windows to see how it compares to Linux.

CC @mfenniak as you were working on this too - you were right about the initialization step, but luckily hid_asus can already do it.

Here's my prototype kernel patch. Note that for development, I have been using CCache along with the technique to patch in-tree modules to keep things nice and fast (not that anything is particularly slow on this thing anyway!).

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 78cdfb8b9a7a..0450ad7424a8 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -84,6 +84,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define QUIRK_MEDION_E1239T		BIT(10)
 #define QUIRK_ROG_NKEY_KEYBOARD		BIT(11)
 #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
+#define QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD	BIT(13)
 
 #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
 						 QUIRK_NO_INIT_REPORTS | \
@@ -835,7 +836,7 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
 	drvdata->input = input;
 
 	if (drvdata->enable_backlight &&
-	    !asus_kbd_wmi_led_control_present(hdev) &&
+	    (!asus_kbd_wmi_led_control_present(hdev) || (drvdata->quirks & QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD)) &&
 	    asus_kbd_register_leds(hdev))
 		hid_warn(hdev, "Failed to initialize backlight.\n");
 
@@ -897,7 +898,9 @@ static int asus_input_mapping(struct hid_device *hdev,
 		case 0xb3: asus_map_key_clear(KEY_PROG3);	break; /* Fn+Left next aura */
 		case 0x6a: asus_map_key_clear(KEY_F13);		break; /* Screenpad toggle */
 		case 0x4b: asus_map_key_clear(KEY_F14);		break; /* Arrows/Pg-Up/Dn toggle */
-
+		case 0x9c: asus_map_key_clear(KEY_F15);     break; /* Screen swap */
+		case 0x7e: asus_map_key_clear(KEY_F16);     break; /* Emoji panel */
+		case 0x86: asus_map_key_clear(KEY_F17);     break; /* MyASUS */
 
 		default:
 			/* ASUS lazily declares 256 usages, ignore the rest,
@@ -1183,17 +1186,20 @@ static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 		hid_info(hdev, "Fixing up Asus T100 keyb report descriptor\n");
 		rdesc[74] &= ~HID_MAIN_ITEM_CONSTANT;
 	}
-	/* For the T100CHI/T90CHI keyboard dock */
-	if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
+	/* For the T100CHI/T90CHI keyboard dock and Zenbook Duo 2024+ keyboards */
+	if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI | QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD)) {
 		int rsize_orig;
 		int offs;
 
 		if (drvdata->quirks & QUIRK_T100CHI) {
 			rsize_orig = 403;
 			offs = 388;
-		} else {
+		} else if (drvdata->quirks & QUIRK_T90CHI) {
 			rsize_orig = 306;
 			offs = 291;
+		} else if (drvdata->quirks & QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD) {
+			rsize_orig = 257;
+			offs = 176;
 		}
 
 		/*
@@ -1298,6 +1304,12 @@ static const struct hid_device_id asus_devices[] = {
 	 */
 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
 		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
+	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+	    USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406_KEYBOARD),
+		QUIRK_USE_KBD_BACKLIGHT | QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD },
+	{ HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC,
+	    USB_VENDOR_ID_ASUSTEK, BT_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406_KEYBOARD),
+	  	QUIRK_USE_KBD_BACKLIGHT | QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD },
 	{ }
 };
 MODULE_DEVICE_TABLE(hid, asus_devices);
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 828a5c022c64..8d9a3e29f28f 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -208,6 +208,8 @@
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD	0x1866
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2	0x19b6
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3	0x1a30
+#define USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406_KEYBOARD	0x1b2c
+#define BT_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406_KEYBOARD    0x1b2d
 #define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD	0x196b
 #define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD	0x1869
 

@mfenniak
Copy link

mfenniak commented Apr 8, 2024

Excellent work! I applied this patch to my kernel though and didn't experience working hotkeys over bluetooth... in fact no keys were working.

I checked the logs and it seems that when connecting the bluetooth keyboard, the hid data failed to parse...

[  366.197243] usb 1-6: USB disconnect, device number 3
[  368.015051] asus 0005:0B05:1B2D.000C: Fixing up T90CHI keyb report descriptor
[  368.015194] asus 0005:0B05:1B2D.000C: item fetching failed at offset 257/258
[  368.015207] asus 0005:0B05:1B2D.000C: Asus hid parse failed: -22
[  368.015212] asus: probe of 0005:0B05:1B2D.000C failed with error -22
[  368.082864] input: ASUS Zenbook Duo Keyboard Mouse as /devices/virtual/misc/uhid/0005:0B05:1B2D.000B/input/input51
[  368.082971] input: ASUS Zenbook Duo Keyboard Touchpad as /devices/virtual/misc/uhid/0005:0B05:1B2D.000B/input/input52
[  368.083037] hid-multitouch 0005:0B05:1B2D.000B: input,hidraw0: BLUETOOTH HID v1.23 Mouse [ASUS Zenbook Duo Keyboard] on 08:8e:90:80:82:f1

This seems related to the tweak you've made to asus_report_fixup, but obviously not the same outcome... any thoughts on what could be different?

@hacker1024
Copy link
Author

Strange. I just tried applying the patch directly myself, and the keyboard with hotkeys worked fine, but the touchpad wasn't showing up...

Are you able to upload the contents of all files matching /sys/bus/hid/devices/*:0B05:1B2D.*/report_descriptor while the keyboard is connected over Bluetooth? I suspect there may be differing firmware or hardware revisions.

@hacker1024
Copy link
Author

On the topic of power profiles, here's a patch that should get them working. The firmware API seems to match the Vivobook Pro 16X 2023 (K6604).

I'm actually pretty happy with this, so I guess it's time to learn how to send a kernel patch upstream 😅 .

Click to expand
From 2f8f8e6bb2da47c677a07f76f141c527ddd5d51c Mon Sep 17 00:00:00 2001
From: hacker1024 <hacker1024@users.sourceforge.net>
Date: Fri, 12 Apr 2024 13:59:24 +1000
Subject: [PATCH] asus-wmi: Add additional DEVID_THROTTLE_THERMAL_POLICY

---
 drivers/platform/x86/asus-wmi.c            | 102 +++++++++++++++------
 include/linux/platform_data/x86/asus-wmi.h |   1 +
 2 files changed, 76 insertions(+), 27 deletions(-)

diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 18be35fdb381..c7958b31dae0 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -93,9 +93,13 @@ module_param(fnlock_default, bool, 0444);
 #define ASUS_FAN_BOOST_MODE_SILENT_MASK                0x02
 #define ASUS_FAN_BOOST_MODES_MASK              0x03
 
+#define ASUS_THROTTLE_THERMAL_POLICY_COUNT 3
 #define ASUS_THROTTLE_THERMAL_POLICY_DEFAULT   0
 #define ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST 1
 #define ASUS_THROTTLE_THERMAL_POLICY_SILENT    2
+#define ASUS_THROTTLE_THERMAL_POLICY_LITE_DEFAULT 0
+#define ASUS_THROTTLE_THERMAL_POLICY_LITE_OVERBOOST 2
+#define ASUS_THROTTLE_THERMAL_POLICY_LITE_SILENT 1
 
 #define USB_INTEL_XUSB2PR              0xD0
 #define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI  0x9c31
@@ -282,6 +286,7 @@ struct asus_wmi {
        bool kbd_rgb_state_available;
 
        bool throttle_thermal_policy_available;
+       bool throttle_thermal_policy_lite;
        u8 throttle_thermal_policy_mode;
 
        bool cpu_fan_curve_available;
@@ -3404,6 +3409,14 @@ static int throttle_thermal_policy_check_present(struct asus_wmi *asus)
        err = asus_wmi_get_devstate(asus,
                                    ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY,
                                    &result);
+       if (err == -ENODEV) {
+               err = asus_wmi_get_devstate(asus,
+                                               ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_LITE,
+                                               &result);
+               asus->throttle_thermal_policy_lite = true;
+       } else {
+               asus->throttle_thermal_policy_lite = false;
+       }
        if (err) {
                if (err == -ENODEV)
                        return 0;
@@ -3424,7 +3437,10 @@ static int throttle_thermal_policy_write(struct asus_wmi *asus)
 
        value = asus->throttle_thermal_policy_mode;
 
-       err = asus_wmi_set_devstate(ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY,
+       err = asus_wmi_set_devstate(
+                                       asus->throttle_thermal_policy_lite
+                                               ? ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_LITE
+                                               : ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY,
                                    value, &retval);
 
        sysfs_notify(&asus->platform_device->dev.kobj, NULL,
@@ -3466,7 +3482,7 @@ static int throttle_thermal_policy_switch_next(struct asus_wmi *asus)
        u8 new_mode = asus->throttle_thermal_policy_mode + 1;
        int err;
 
-       if (new_mode > ASUS_THROTTLE_THERMAL_POLICY_SILENT)
+       if (new_mode > ASUS_THROTTLE_THERMAL_POLICY_COUNT - 1)
                new_mode = ASUS_THROTTLE_THERMAL_POLICY_DEFAULT;
 
        asus->throttle_thermal_policy_mode = new_mode;
@@ -3505,7 +3521,7 @@ static ssize_t throttle_thermal_policy_store(struct device *dev,
        if (result < 0)
                return result;
 
-       if (new_mode > ASUS_THROTTLE_THERMAL_POLICY_SILENT)
+       if (new_mode > ASUS_THROTTLE_THERMAL_POLICY_COUNT - 1)
                return -EINVAL;
 
        asus->throttle_thermal_policy_mode = new_mode;
@@ -3536,18 +3552,34 @@ static int asus_wmi_platform_profile_get(struct platform_profile_handler *pprof,
 
        tp = asus->throttle_thermal_policy_mode;
 
-       switch (tp) {
-       case ASUS_THROTTLE_THERMAL_POLICY_DEFAULT:
-               *profile = PLATFORM_PROFILE_BALANCED;
-               break;
-       case ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST:
-               *profile = PLATFORM_PROFILE_PERFORMANCE;
-               break;
-       case ASUS_THROTTLE_THERMAL_POLICY_SILENT:
-               *profile = PLATFORM_PROFILE_QUIET;
-               break;
-       default:
-               return -EINVAL;
+       if (!asus->throttle_thermal_policy_lite) {
+               switch (tp) {
+               case ASUS_THROTTLE_THERMAL_POLICY_DEFAULT:
+                       *profile = PLATFORM_PROFILE_BALANCED;
+                       break;
+               case ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST:
+                       *profile = PLATFORM_PROFILE_PERFORMANCE;
+                       break;
+               case ASUS_THROTTLE_THERMAL_POLICY_SILENT:
+                       *profile = PLATFORM_PROFILE_QUIET;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+       } else {
+               switch (tp) {
+               case ASUS_THROTTLE_THERMAL_POLICY_LITE_DEFAULT:
+                       *profile = PLATFORM_PROFILE_BALANCED;
+                       break;
+               case ASUS_THROTTLE_THERMAL_POLICY_LITE_OVERBOOST:
+                       *profile = PLATFORM_PROFILE_PERFORMANCE;
+                       break;
+               case ASUS_THROTTLE_THERMAL_POLICY_LITE_SILENT:
+                       *profile = PLATFORM_PROFILE_QUIET;
+                       break;
+               default:
+                       return -EINVAL;
+               }
        }
 
        return 0;
@@ -3561,18 +3593,34 @@ static int asus_wmi_platform_profile_set(struct platform_profile_handler *pprof,
 
        asus = container_of(pprof, struct asus_wmi, platform_profile_handler);
 
-       switch (profile) {
-       case PLATFORM_PROFILE_PERFORMANCE:
-               tp = ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST;
-               break;
-       case PLATFORM_PROFILE_BALANCED:
-               tp = ASUS_THROTTLE_THERMAL_POLICY_DEFAULT;
-               break;
-       case PLATFORM_PROFILE_QUIET:
-               tp = ASUS_THROTTLE_THERMAL_POLICY_SILENT;
-               break;
-       default:
-               return -EOPNOTSUPP;
+       if (!asus->throttle_thermal_policy_lite) {
+               switch (profile) {
+               case PLATFORM_PROFILE_PERFORMANCE:
+                       tp = ASUS_THROTTLE_THERMAL_POLICY_OVERBOOST;
+                       break;
+               case PLATFORM_PROFILE_BALANCED:
+                       tp = ASUS_THROTTLE_THERMAL_POLICY_DEFAULT;
+                       break;
+               case PLATFORM_PROFILE_QUIET:
+                       tp = ASUS_THROTTLE_THERMAL_POLICY_SILENT;
+                       break;
+               default:
+                       return -EOPNOTSUPP;
+               }
+       } else {
+               switch (profile) {
+               case PLATFORM_PROFILE_PERFORMANCE:
+                       tp = ASUS_THROTTLE_THERMAL_POLICY_LITE_OVERBOOST;
+                       break;
+               case PLATFORM_PROFILE_BALANCED:
+                       tp = ASUS_THROTTLE_THERMAL_POLICY_LITE_DEFAULT;
+                       break;
+               case PLATFORM_PROFILE_QUIET:
+                       tp = ASUS_THROTTLE_THERMAL_POLICY_LITE_SILENT;
+                       break;
+               default:
+                       return -EOPNOTSUPP;
+               }
        }
 
        asus->throttle_thermal_policy_mode = tp;
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
index ab1c7deff118..ddf5b3766cd2 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -64,6 +64,7 @@
 #define ASUS_WMI_DEVID_SCREENPAD_LIGHT 0x00050032
 #define ASUS_WMI_DEVID_FAN_BOOST_MODE  0x00110018
 #define ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY 0x00120075
+#define ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_LITE 0x00110019
 
 /* Misc */
 #define ASUS_WMI_DEVID_PANEL_OD                0x00050019
-- 
2.44.0

Of course, if you don't want to bother with a kernel patch, you can do it over the CLI - 0x0 for standard, 0x1 for silent, and 0x2 for performance.

# echo 0x00110019 > /sys/kernel/debug/asus-nb-wmi/dev_id
# echo 0x0 > /sys/kernel/debug/asus-nb-wmi/ctrl_param
# cat /sys/kernel/debug/asus-nb-wmi/devs

Interestingly, there's another ACPI endpoint that relates to the fan. It seems to set the Intel Dynamic Platform and Thermal Framework OEM variable 3 and call an EC function based on the given argument (0-3) and fan setting.

Search for 0x0006006C in the DSDT to find this endpoint. Without any information on ASUS's DPTF and EC implementations, though, it's very hard to make any sense of this - I might look into MyASUS to see how and when it uses this endpoint, but that won't be very easy either.

Has anyone more experienced with ACPI, DPTF or ECs seen anything like this before?

@ashtegro
Copy link

Following this! Thanks a lot for your work so far, @hacker1024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants