Add support for SW_TABLET_MODE for convertibles notebook. Exactly as intel-vbtn driver, the event code 0xcc is emitted by convertibles when entering tablet mode and 0xcd when return to laptop mode. Signed-off-by: Elia Devito <eliadevito@xxxxxxxxx> --- more info: https://bugzilla.kernel.org/show_bug.cgi?id=207433 drivers/platform/x86/intel-hid.c | 84 ++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c index 86261970bd8f..5093c57102cf 100644 --- a/drivers/platform/x86/intel-hid.c +++ b/drivers/platform/x86/intel-hid.c @@ -15,6 +15,9 @@ #include <linux/platform_device.h> #include <linux/suspend.h> +/* When NOT in tablet mode, VGBS returns with the flag 0x40 */ +#define TABLET_MODE_FLAG 0x40 + MODULE_LICENSE("GPL"); MODULE_AUTHOR("Alex Hung"); @@ -61,7 +64,11 @@ static const struct key_entry intel_array_keymap[] = { { KE_IGNORE, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* Release */ { KE_KEY, 0xCE, { KEY_POWER } }, /* Press */ { KE_IGNORE, 0xCF, { KEY_POWER } }, /* Release */ - { KE_END }, +}; + +static const struct key_entry intel_array_switches[] = { + { KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } }, /* Tablet */ + { KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } }, /* Laptop */ }; static const struct dmi_system_id button_array_table[] = { @@ -89,9 +96,23 @@ static const struct dmi_system_id button_array_table[] = { { } }; +static const struct dmi_system_id button_array_switches_table[] = { + { + .matches = { + DMI_EXACT_MATCH(DMI_CHASSIS_TYPE, "31" /* Convertible */), + }, + }, + { } +}; + +#define KEYMAP_LEN \ + (ARRAY_SIZE(intel_array_keymap) + ARRAY_SIZE(intel_array_switches) + 1) + struct intel_hid_priv { + struct key_entry keymap[KEYMAP_LEN]; struct input_dev *input_dev; struct input_dev *array; + bool has_switches; bool wakeup_mode; }; @@ -327,23 +348,54 @@ static int intel_hid_input_setup(struct platform_device *device) return input_register_device(priv->input_dev); } +static void detect_tablet_mode(struct platform_device *device) +{ + struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); + acpi_handle handle = ACPI_HANDLE(&device->dev); + unsigned long long vgbs; + int m; + + if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_VGBS_FN, &vgbs)) + return; + + m = !(vgbs & TABLET_MODE_FLAG); + input_report_switch(priv->array, SW_TABLET_MODE, m); +} + static int intel_button_array_input_setup(struct platform_device *device) { struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); - int ret; + int ret, keymap_len = 0; /* Setup input device for 5 button array */ priv->array = devm_input_allocate_device(&device->dev); if (!priv->array) return -ENOMEM; - ret = sparse_keymap_setup(priv->array, intel_array_keymap, NULL); + memcpy(&priv->keymap[keymap_len], intel_array_keymap, + ARRAY_SIZE(intel_array_keymap) * + sizeof(struct key_entry)); + keymap_len += ARRAY_SIZE(intel_array_keymap); + + if (priv->has_switches) { + memcpy(&priv->keymap[keymap_len], intel_array_switches, + ARRAY_SIZE(intel_array_switches) * + sizeof(struct key_entry)); + keymap_len += ARRAY_SIZE(intel_array_switches); + } + + priv->keymap[keymap_len].type = KE_END; + + ret = sparse_keymap_setup(priv->array, priv->keymap, NULL); if (ret) return ret; priv->array->name = "Intel HID 5 button array"; priv->array->id.bustype = BUS_HOST; + if (priv->has_switches) + detect_tablet_mode(device); + return input_register_device(priv->array); } @@ -352,7 +404,10 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) struct platform_device *device = context; struct intel_hid_priv *priv = dev_get_drvdata(&device->dev); unsigned long long ev_index; + unsigned int val = !(event & 1); /* Even=press, Odd=release */ + const struct key_entry *ke; + dev_info(&device->dev, "event 0x%x\n", event); if (priv->wakeup_mode) { /* * Needed for wakeup from suspend-to-idle to work on some @@ -367,13 +422,19 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) if (event == 0xc0 || !priv->array) return; - if (!sparse_keymap_entry_from_scancode(priv->array, event)) { + ke = sparse_keymap_entry_from_scancode(priv->array, event); + if (!ke) { dev_info(&device->dev, "unknown event 0x%x\n", event); return; } wakeup: pm_wakeup_hard_event(&device->dev); + + /* report the new switch position to the input subsystem. */ + if (ke && ke->type == KE_SW) + sparse_keymap_report_event(priv->array, event, val, 0); + return; } @@ -441,6 +502,20 @@ static bool button_array_present(struct platform_device *device) return false; } +static bool intel_button_array_has_switches(struct platform_device *device) +{ + acpi_handle handle = ACPI_HANDLE(&device->dev); + unsigned long long vgbs; + + if (!dmi_check_system(button_array_switches_table)) + return false; + + if (!intel_hid_evaluate_method(handle, INTEL_HID_DSM_VGBS_FN, &vgbs)) + return false; + + return true; +} + static int intel_hid_probe(struct platform_device *device) { acpi_handle handle = ACPI_HANDLE(&device->dev); @@ -479,6 +554,7 @@ static int intel_hid_probe(struct platform_device *device) /* Setup 5 button array */ if (button_array_present(device)) { + priv->has_switches = intel_button_array_has_switches(device); dev_info(&device->dev, "platform supports 5 button array\n"); err = intel_button_array_input_setup(device); if (err) -- 2.28.0