The event data of the WMI event 0xD0, which is assumed to be the fn_lock, is used to indicate several special keys on newer Yoga 7/9 laptops. The notify_id 0xD0 is non-unique in the DSDT of the Yoga 9 14IAP7, this causes wmi_get_event_data() to report wrong values. Port the ideapad-laptop WMI code to the wmi bus infrastructure which does not suffer from the shortcomings of wmi_get_event_data(). Signed-off-by: Philipp Jungkamp <p.jungkamp@xxxxxxx> --- Hello, is this about right? It works for me. What I don't really like here is the dev_set_drvdata() which takes a non-const void * and I pass it a const pointer. I do cast the value of dev_get_drvdata() back to a const pointer, but this seems rather ugly. I preferred it over allocating a single int for the device or casting an enum to a void *. This additionally removes the need for a remove funtion. Regards, Philipp drivers/platform/x86/ideapad-laptop.c | 109 +++++++++++++++++++------- 1 file changed, 80 insertions(+), 29 deletions(-) diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 33b3dfdd1b08..6d35a9e961cf 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -30,6 +30,7 @@ #include <linux/seq_file.h> #include <linux/sysfs.h> #include <linux/types.h> +#include <linux/wmi.h> #include <acpi/video.h> @@ -38,10 +39,19 @@ #define IDEAPAD_RFKILL_DEV_NUM 3 #if IS_ENABLED(CONFIG_ACPI_WMI) -static const char *const ideapad_wmi_fnesc_events[] = { - "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", /* Yoga 3 */ - "56322276-8493-4CE8-A783-98C991274F5E", /* Yoga 700 */ - "8FC0DE0C-B4E4-43FD-B0F3-8871711C1294", /* Legion 5 */ +enum ideapad_wmi_event_type { + IDEAPAD_WMI_EVENT_ESC, + IDEAPAD_WMI_EVENT_FN_KEYS, +}; + +enum ideapad_wmi_event_type ideapad_wmi_esc = IDEAPAD_WMI_EVENT_ESC, +enum ideapad_wmi_event_type ideapad_wmi_fn_keys = IDEAPAD_WMI_EVENT_FN_KEYS; + +static const struct wmi_device_id ideapad_wmi_id_table[] = { + { "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", &ideapad_wmi_esc }, /* Yoga 3 */ + { "56322276-8493-4CE8-A783-98C991274F5E", &ideapad_wmi_esc }, /* Yoga 700 */ + { "8FC0DE0C-B4E4-43FD-B0F3-8871711C1294", &ideapad_wmi_fn_keys }, /* Legion 5 */ + {} }; #endif @@ -130,7 +140,7 @@ struct ideapad_private { struct ideapad_dytc_priv *dytc; struct dentry *debug; unsigned long cfg; - const char *fnesc_guid; + struct wmi_driver wmi_drv; struct { bool conservation_mode : 1; bool dytc : 1; @@ -1074,6 +1084,7 @@ static void ideapad_sysfs_exit(struct ideapad_private *priv) /* * input device */ +#define IDEAPAD_WMI_KEY 0x100 static const struct key_entry ideapad_keymap[] = { { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, { KE_KEY, 7, { KEY_CAMERA } }, @@ -1087,6 +1098,26 @@ static const struct key_entry ideapad_keymap[] = { { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, { KE_KEY, 128, { KEY_ESC } }, + + /* + * WMI keys + */ + + /* FnLock (handled by the firmware) */ + { KE_IGNORE, 0x02 | IDEAPAD_WMI_KEY }, + /* Customizable Lenovo Hotkey ("star" with 'S' inside) */ + { KE_KEY, 0x01 | IDEAPAD_WMI_KEY, { KEY_FAVORITES } }, + /* Dark mode toggle */ + { KE_KEY, 0x13 | IDEAPAD_WMI_KEY, { KEY_PROG1 } }, + /* Sound profile switch */ + { KE_KEY, 0x12 | IDEAPAD_WMI_KEY, { KEY_PROG2 } }, + /* Lenovo Virtual Background application */ + { KE_KEY, 0x28 | IDEAPAD_WMI_KEY, { KEY_PROG3 } }, + /* Lenovo Support */ + { KE_KEY, 0x27 | IDEAPAD_WMI_KEY, { KEY_HELP } }, + /* Refresh Rate Toggle */ + { KE_KEY, 0x0a | IDEAPAD_WMI_KEY, { KEY_DISPLAYTOGGLE } }, + { KE_END }, }; @@ -1491,25 +1522,47 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) } #if IS_ENABLED(CONFIG_ACPI_WMI) -static void ideapad_wmi_notify(u32 value, void *context) +static int ideapad_wmi_probe(struct wmi_device *wdev, const void *context) { - struct ideapad_private *priv = context; + dev_set_drvdata(&wdev->dev, (void *) context); + return 0; +} + +static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data) +{ + struct wmi_driver *wdrv = container_of(wdev->dev.driver, + struct wmi_driver, + driver); + struct ideapad_private *priv = container_of(wdrv, + struct ideapad_private, + wmi_drv); + const enum ideapad_wmi_event_type *event = dev_get_drvdata(&wdev->dev); unsigned long result; - switch (value) { - case 128: - ideapad_input_report(priv, value); + switch (*event) { + case IDEAPAD_WMI_EVENT_ESC: + ideapad_input_report(priv, 128); break; - case 208: + case IDEAPAD_WMI_EVENT_FN_KEYS: if (!eval_hals(priv->adev->handle, &result)) { bool state = test_bit(HALS_FNLOCK_STATE_BIT, &result); exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); } + + if (data->type != ACPI_TYPE_INTEGER) { + dev_warn(&wdev->dev, + "WMI event data is not an integer\n"); + break; + } + + dev_dbg(&wdev->dev, "WMI fn-key event: 0x%llx\n", + data->integer.value); + + ideapad_input_report(priv, + data->integer.value | IDEAPAD_WMI_KEY); + break; - default: - dev_info(&priv->platform_device->dev, - "Unknown WMI event: %u\n", value); } } #endif @@ -1671,25 +1724,24 @@ static int ideapad_acpi_add(struct platform_device *pdev) } #if IS_ENABLED(CONFIG_ACPI_WMI) - for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) { - status = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], - ideapad_wmi_notify, priv); - if (ACPI_SUCCESS(status)) { - priv->fnesc_guid = ideapad_wmi_fnesc_events[i]; - break; - } - } + priv->wmi_drv = (struct wmi_driver) { + .driver = { + .name = "ideapad-wmi-fn-keys", + }, + .id_table = ideapad_wmi_id_table, + .probe = ideapad_wmi_probe, + .notify = ideapad_wmi_notify, + }; - if (ACPI_FAILURE(status) && status != AE_NOT_EXIST) { - err = -EIO; - goto notification_failed_wmi; - } + err = wmi_driver_register(&priv->wmi_drv); + if (err) + goto register_failed_wmi; #endif return 0; #if IS_ENABLED(CONFIG_ACPI_WMI) -notification_failed_wmi: +register_failed_wmi: acpi_remove_notify_handler(priv->adev->handle, ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); @@ -1720,8 +1772,7 @@ static int ideapad_acpi_remove(struct platform_device *pdev) int i; #if IS_ENABLED(CONFIG_ACPI_WMI) - if (priv->fnesc_guid) - wmi_remove_notify_handler(priv->fnesc_guid); + wmi_driver_unregister(&priv->wmi_drv); #endif acpi_remove_notify_handler(priv->adev->handle, -- 2.38.1