diff options
Diffstat (limited to 'drivers/acpi/button.c')
| -rw-r--r-- | drivers/acpi/button.c | 413 |
1 files changed, 263 insertions, 150 deletions
diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index a19ff3977ac4..3c6dd9b4ba0a 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -1,22 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * button.c - ACPI Button Driver * * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com> * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #define pr_fmt(fmt) "ACPI: button: " fmt @@ -34,35 +21,37 @@ #include <linux/dmi.h> #include <acpi/button.h> -#define PREFIX "ACPI: " - #define ACPI_BUTTON_CLASS "button" -#define ACPI_BUTTON_FILE_INFO "info" #define ACPI_BUTTON_FILE_STATE "state" #define ACPI_BUTTON_TYPE_UNKNOWN 0x00 +#define ACPI_BUTTON_NOTIFY_WAKE 0x02 #define ACPI_BUTTON_NOTIFY_STATUS 0x80 #define ACPI_BUTTON_SUBCLASS_POWER "power" -#define ACPI_BUTTON_HID_POWER "PNP0C0C" #define ACPI_BUTTON_DEVICE_NAME_POWER "Power Button" #define ACPI_BUTTON_TYPE_POWER 0x01 #define ACPI_BUTTON_SUBCLASS_SLEEP "sleep" -#define ACPI_BUTTON_HID_SLEEP "PNP0C0E" #define ACPI_BUTTON_DEVICE_NAME_SLEEP "Sleep Button" #define ACPI_BUTTON_TYPE_SLEEP 0x03 #define ACPI_BUTTON_SUBCLASS_LID "lid" -#define ACPI_BUTTON_HID_LID "PNP0C0D" #define ACPI_BUTTON_DEVICE_NAME_LID "Lid Switch" #define ACPI_BUTTON_TYPE_LID 0x05 -#define ACPI_BUTTON_LID_INIT_IGNORE 0x00 -#define ACPI_BUTTON_LID_INIT_OPEN 0x01 -#define ACPI_BUTTON_LID_INIT_METHOD 0x02 +enum { + ACPI_BUTTON_LID_INIT_IGNORE, + ACPI_BUTTON_LID_INIT_OPEN, + ACPI_BUTTON_LID_INIT_METHOD, + ACPI_BUTTON_LID_INIT_DISABLED, +}; -#define _COMPONENT ACPI_BUTTON_COMPONENT -ACPI_MODULE_NAME("button"); +static const char * const lid_init_state_str[] = { + [ACPI_BUTTON_LID_INIT_IGNORE] = "ignore", + [ACPI_BUTTON_LID_INIT_OPEN] = "open", + [ACPI_BUTTON_LID_INIT_METHOD] = "method", + [ACPI_BUTTON_LID_INIT_DISABLED] = "disabled", +}; MODULE_AUTHOR("Paul Diefenbaugh"); MODULE_DESCRIPTION("ACPI Button Driver"); @@ -78,25 +67,86 @@ static const struct acpi_device_id button_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, button_device_ids); -/* - * Some devices which don't even have a lid in anyway have a broken _LID - * method (e.g. pointing to a floating gpio pin) causing spurious LID events. - */ -static const struct dmi_system_id lid_blacklst[] = { +/* Please keep this list sorted alphabetically by vendor and model */ +static const struct dmi_system_id dmi_lid_quirks[] = { { - /* GP-electronic T701 */ + /* GP-electronic T701, _LID method points to a floating GPIO */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), DMI_MATCH(DMI_PRODUCT_NAME, "T701"), DMI_MATCH(DMI_BIOS_VERSION, "BYT70A.YNCHENG.WIN.007"), }, + .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_DISABLED, + }, + { + /* Nextbook Ares 8A tablet, _LID device always reports lid closed */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "CherryTrail"), + DMI_MATCH(DMI_BIOS_VERSION, "M882"), + }, + .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_DISABLED, + }, + { + /* + * Lenovo Yoga 9 14ITL5, initial notification of the LID device + * never happens. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "82BG"), + }, + .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_OPEN, + }, + { + /* + * Medion Akoya E2215T, notification of the LID device only + * happens on close, not on open and _LID always returns closed. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDION"), + DMI_MATCH(DMI_PRODUCT_NAME, "E2215T"), + }, + .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_OPEN, + }, + { + /* + * Medion Akoya E2228T, notification of the LID device only + * happens on close, not on open and _LID always returns closed. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDION"), + DMI_MATCH(DMI_PRODUCT_NAME, "E2228T"), + }, + .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_OPEN, + }, + { + /* + * Razer Blade Stealth 13 late 2019, notification of the LID device + * only happens on close, not on open and _LID always returns closed. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Razer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Razer Blade Stealth 13 Late 2019"), + }, + .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_OPEN, + }, + { + /* + * Samsung galaxybook2 ,initial _LID device notification returns + * lid closed. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SAMSUNG ELECTRONICS CO., LTD."), + DMI_MATCH(DMI_PRODUCT_NAME, "750XED"), + }, + .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_OPEN, }, {} }; static int acpi_button_add(struct acpi_device *device); -static int acpi_button_remove(struct acpi_device *device); -static void acpi_button_notify(struct acpi_device *device, u32 event); +static void acpi_button_remove(struct acpi_device *device); #ifdef CONFIG_PM_SLEEP static int acpi_button_suspend(struct device *dev); @@ -114,7 +164,6 @@ static struct acpi_driver acpi_button_driver = { .ops = { .add = acpi_button_add, .remove = acpi_button_remove, - .notify = acpi_button_notify, }, .drv.pm = &acpi_button_pm, }; @@ -127,20 +176,17 @@ struct acpi_button { int last_state; ktime_t last_time; bool suspended; + bool lid_state_initialized; }; -static BLOCKING_NOTIFIER_HEAD(acpi_lid_notifier); static struct acpi_device *lid_device; -static u8 lid_init_state = ACPI_BUTTON_LID_INIT_METHOD; +static long lid_init_state = -1; static unsigned long lid_report_interval __read_mostly = 500; module_param(lid_report_interval, ulong, 0644); MODULE_PARM_DESC(lid_report_interval, "Interval (ms) between lid key events"); -/* -------------------------------------------------------------------------- - FS Interface (/proc) - -------------------------------------------------------------------------- */ - +/* FS Interface (/proc) */ static struct proc_dir_entry *acpi_button_dir; static struct proc_dir_entry *acpi_lid_dir; @@ -159,7 +205,6 @@ static int acpi_lid_evaluate_state(struct acpi_device *device) static int acpi_lid_notify_state(struct acpi_device *device, int state) { struct acpi_button *button = acpi_driver_data(device); - int ret; ktime_t next_report; bool do_update; @@ -236,18 +281,7 @@ static int acpi_lid_notify_state(struct acpi_device *device, int state) button->last_time = ktime_get(); } - ret = blocking_notifier_call_chain(&acpi_lid_notifier, state, device); - if (ret == NOTIFY_DONE) - ret = blocking_notifier_call_chain(&acpi_lid_notifier, state, - device); - if (ret == NOTIFY_DONE || ret == NOTIFY_OK) { - /* - * It is also regarded as success if the notifier_chain - * returns NOTIFY_OK or NOTIFY_DONE. - */ - ret = 0; - } - return ret; + return 0; } static int __maybe_unused acpi_button_state_seq_show(struct seq_file *seq, @@ -273,7 +307,7 @@ static int acpi_button_add_fs(struct acpi_device *device) return 0; if (acpi_button_dir || acpi_lid_dir) { - printk(KERN_ERR PREFIX "More than one Lid device found!\n"); + pr_info("More than one Lid device found!\n"); return -EEXIST; } @@ -341,21 +375,7 @@ static int acpi_button_remove_fs(struct acpi_device *device) return 0; } -/* -------------------------------------------------------------------------- - Driver Interface - -------------------------------------------------------------------------- */ -int acpi_lid_notifier_register(struct notifier_block *nb) -{ - return blocking_notifier_chain_register(&acpi_lid_notifier, nb); -} -EXPORT_SYMBOL(acpi_lid_notifier_register); - -int acpi_lid_notifier_unregister(struct notifier_block *nb) -{ - return blocking_notifier_chain_unregister(&acpi_lid_notifier, nb); -} -EXPORT_SYMBOL(acpi_lid_notifier_unregister); - +/* Driver Interface */ int acpi_lid_open(void) { if (!lid_device) @@ -382,6 +402,8 @@ static int acpi_lid_update_state(struct acpi_device *device, static void acpi_lid_initialize_state(struct acpi_device *device) { + struct acpi_button *button = acpi_driver_data(device); + switch (lid_init_state) { case ACPI_BUTTON_LID_INIT_OPEN: (void)acpi_lid_notify_state(device, 1); @@ -393,51 +415,74 @@ static void acpi_lid_initialize_state(struct acpi_device *device) default: break; } + + button->lid_state_initialized = true; } -static void acpi_button_notify(struct acpi_device *device, u32 event) +static void acpi_lid_notify(acpi_handle handle, u32 event, void *data) { - struct acpi_button *button = acpi_driver_data(device); + struct acpi_device *device = data; + struct acpi_button *button; + + if (event != ACPI_BUTTON_NOTIFY_STATUS) { + acpi_handle_debug(device->handle, "Unsupported event [0x%x]\n", + event); + return; + } + + button = acpi_driver_data(device); + if (!button->lid_state_initialized) + return; + + acpi_lid_update_state(device, true); +} + +static void acpi_button_notify(acpi_handle handle, u32 event, void *data) +{ + struct acpi_device *device = data; + struct acpi_button *button; struct input_dev *input; - int users; + int keycode; switch (event) { - case ACPI_FIXED_HARDWARE_EVENT: - event = ACPI_BUTTON_NOTIFY_STATUS; - /* fall through */ case ACPI_BUTTON_NOTIFY_STATUS: - input = button->input; - if (button->type == ACPI_BUTTON_TYPE_LID) { - mutex_lock(&button->input->mutex); - users = button->input->users; - mutex_unlock(&button->input->mutex); - if (users) - acpi_lid_update_state(device, true); - } else { - int keycode; - - acpi_pm_wakeup_event(&device->dev); - if (button->suspended) - break; - - keycode = test_bit(KEY_SLEEP, input->keybit) ? - KEY_SLEEP : KEY_POWER; - input_report_key(input, keycode, 1); - input_sync(input); - input_report_key(input, keycode, 0); - input_sync(input); - - acpi_bus_generate_netlink_event( - device->pnp.device_class, - dev_name(&device->dev), - event, ++button->pushed); - } break; - default: - ACPI_DEBUG_PRINT((ACPI_DB_INFO, - "Unsupported event [0x%x]\n", event)); + case ACPI_BUTTON_NOTIFY_WAKE: break; + default: + acpi_handle_debug(device->handle, "Unsupported event [0x%x]\n", + event); + return; } + + acpi_pm_wakeup_event(&device->dev); + + button = acpi_driver_data(device); + if (button->suspended || event == ACPI_BUTTON_NOTIFY_WAKE) + return; + + input = button->input; + keycode = test_bit(KEY_SLEEP, input->keybit) ? KEY_SLEEP : KEY_POWER; + + input_report_key(input, keycode, 1); + input_sync(input); + input_report_key(input, keycode, 0); + input_sync(input); + + acpi_bus_generate_netlink_event(device->pnp.device_class, + dev_name(&device->dev), + event, ++button->pushed); +} + +static void acpi_button_notify_run(void *data) +{ + acpi_button_notify(NULL, ACPI_BUTTON_NOTIFY_STATUS, data); +} + +static u32 acpi_button_event(void *data) +{ + acpi_os_execute(OSL_NOTIFY_HANDLER, acpi_button_notify_run, data); + return ACPI_INTERRUPT_HANDLED; } #ifdef CONFIG_PM_SLEEP @@ -452,12 +497,24 @@ static int acpi_button_suspend(struct device *dev) static int acpi_button_resume(struct device *dev) { + struct input_dev *input; struct acpi_device *device = to_acpi_device(dev); struct acpi_button *button = acpi_driver_data(device); button->suspended = false; - if (button->type == ACPI_BUTTON_TYPE_LID && button->input->users) + if (button->type == ACPI_BUTTON_TYPE_LID) { + button->last_state = !!acpi_lid_evaluate_state(device); + button->last_time = ktime_get(); acpi_lid_initialize_state(device); + } + + if (button->type == ACPI_BUTTON_TYPE_POWER) { + input = button->input; + input_report_key(input, KEY_WAKEUP, 1); + input_sync(input); + input_report_key(input, KEY_WAKEUP, 0); + input_sync(input); + } return 0; } #endif @@ -476,13 +533,16 @@ static int acpi_lid_input_open(struct input_dev *input) static int acpi_button_add(struct acpi_device *device) { + acpi_notify_handler handler; struct acpi_button *button; struct input_dev *input; const char *hid = acpi_device_hid(device); + acpi_status status; char *name, *class; - int error; + int error = 0; - if (!strcmp(hid, ACPI_BUTTON_HID_LID) && dmi_check_system(lid_blacklst)) + if (!strcmp(hid, ACPI_BUTTON_HID_LID) && + lid_init_state == ACPI_BUTTON_LID_INIT_DISABLED) return -ENODEV; button = kzalloc(sizeof(struct acpi_button), GFP_KERNEL); @@ -503,30 +563,36 @@ static int acpi_button_add(struct acpi_device *device) if (!strcmp(hid, ACPI_BUTTON_HID_POWER) || !strcmp(hid, ACPI_BUTTON_HID_POWERF)) { button->type = ACPI_BUTTON_TYPE_POWER; - strcpy(name, ACPI_BUTTON_DEVICE_NAME_POWER); + handler = acpi_button_notify; + strscpy(name, ACPI_BUTTON_DEVICE_NAME_POWER, MAX_ACPI_DEVICE_NAME_LEN); sprintf(class, "%s/%s", ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_POWER); } else if (!strcmp(hid, ACPI_BUTTON_HID_SLEEP) || !strcmp(hid, ACPI_BUTTON_HID_SLEEPF)) { button->type = ACPI_BUTTON_TYPE_SLEEP; - strcpy(name, ACPI_BUTTON_DEVICE_NAME_SLEEP); + handler = acpi_button_notify; + strscpy(name, ACPI_BUTTON_DEVICE_NAME_SLEEP, MAX_ACPI_DEVICE_NAME_LEN); sprintf(class, "%s/%s", ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_SLEEP); } else if (!strcmp(hid, ACPI_BUTTON_HID_LID)) { button->type = ACPI_BUTTON_TYPE_LID; - strcpy(name, ACPI_BUTTON_DEVICE_NAME_LID); + handler = acpi_lid_notify; + strscpy(name, ACPI_BUTTON_DEVICE_NAME_LID, MAX_ACPI_DEVICE_NAME_LEN); sprintf(class, "%s/%s", ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_LID); input->open = acpi_lid_input_open; } else { - printk(KERN_ERR PREFIX "Unsupported hid [%s]\n", hid); + pr_info("Unsupported hid [%s]\n", hid); error = -ENODEV; - goto err_free_input; } - error = acpi_button_add_fs(device); - if (error) - goto err_free_input; + if (!error) + error = acpi_button_add_fs(device); + + if (error) { + input_free_device(input); + goto err_free_button; + } snprintf(button->phys, sizeof(button->phys), "%s/button/input0", hid); @@ -539,6 +605,7 @@ static int acpi_button_add(struct acpi_device *device) switch (button->type) { case ACPI_BUTTON_TYPE_POWER: input_set_capability(input, EV_KEY, KEY_POWER); + input_set_capability(input, EV_KEY, KEY_WAKEUP); break; case ACPI_BUTTON_TYPE_SLEEP: @@ -552,8 +619,33 @@ static int acpi_button_add(struct acpi_device *device) input_set_drvdata(input, device); error = input_register_device(input); - if (error) + if (error) { + input_free_device(input); goto err_remove_fs; + } + + switch (device->device_type) { + case ACPI_BUS_TYPE_POWER_BUTTON: + status = acpi_install_fixed_event_handler(ACPI_EVENT_POWER_BUTTON, + acpi_button_event, + device); + break; + case ACPI_BUS_TYPE_SLEEP_BUTTON: + status = acpi_install_fixed_event_handler(ACPI_EVENT_SLEEP_BUTTON, + acpi_button_event, + device); + break; + default: + status = acpi_install_notify_handler(device->handle, + ACPI_ALL_NOTIFY, handler, + device); + break; + } + if (ACPI_FAILURE(status)) { + error = -ENODEV; + goto err_input_unregister; + } + if (button->type == ACPI_BUTTON_TYPE_LID) { /* * This assumes there's only one lid device, or if there are @@ -563,61 +655,72 @@ static int acpi_button_add(struct acpi_device *device) } device_init_wakeup(&device->dev, true); - printk(KERN_INFO PREFIX "%s [%s]\n", name, acpi_device_bid(device)); + pr_info("%s [%s]\n", name, acpi_device_bid(device)); return 0; - err_remove_fs: +err_input_unregister: + input_unregister_device(input); +err_remove_fs: acpi_button_remove_fs(device); - err_free_input: - input_free_device(input); - err_free_button: +err_free_button: kfree(button); return error; } -static int acpi_button_remove(struct acpi_device *device) +static void acpi_button_remove(struct acpi_device *device) { struct acpi_button *button = acpi_driver_data(device); + switch (device->device_type) { + case ACPI_BUS_TYPE_POWER_BUTTON: + acpi_remove_fixed_event_handler(ACPI_EVENT_POWER_BUTTON, + acpi_button_event); + break; + case ACPI_BUS_TYPE_SLEEP_BUTTON: + acpi_remove_fixed_event_handler(ACPI_EVENT_SLEEP_BUTTON, + acpi_button_event); + break; + default: + acpi_remove_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, + button->type == ACPI_BUTTON_TYPE_LID ? + acpi_lid_notify : + acpi_button_notify); + break; + } + acpi_os_wait_events_complete(); + acpi_button_remove_fs(device); input_unregister_device(button->input); kfree(button); - return 0; } static int param_set_lid_init_state(const char *val, const struct kernel_param *kp) { - int result = 0; - - if (!strncmp(val, "open", sizeof("open") - 1)) { - lid_init_state = ACPI_BUTTON_LID_INIT_OPEN; - pr_info("Notify initial lid state as open\n"); - } else if (!strncmp(val, "method", sizeof("method") - 1)) { - lid_init_state = ACPI_BUTTON_LID_INIT_METHOD; - pr_info("Notify initial lid state with _LID return value\n"); - } else if (!strncmp(val, "ignore", sizeof("ignore") - 1)) { - lid_init_state = ACPI_BUTTON_LID_INIT_IGNORE; - pr_info("Do not notify initial lid state\n"); - } else - result = -EINVAL; - return result; + int i; + + i = sysfs_match_string(lid_init_state_str, val); + if (i < 0) + return i; + + lid_init_state = i; + pr_info("Initial lid state set to '%s'\n", lid_init_state_str[i]); + return 0; } -static int param_get_lid_init_state(char *buffer, - const struct kernel_param *kp) +static int param_get_lid_init_state(char *buf, const struct kernel_param *kp) { - switch (lid_init_state) { - case ACPI_BUTTON_LID_INIT_OPEN: - return sprintf(buffer, "open"); - case ACPI_BUTTON_LID_INIT_METHOD: - return sprintf(buffer, "method"); - case ACPI_BUTTON_LID_INIT_IGNORE: - return sprintf(buffer, "ignore"); - default: - return sprintf(buffer, "invalid"); - } - return 0; + int i, c = 0; + + for (i = 0; i < ARRAY_SIZE(lid_init_state_str); i++) + if (i == lid_init_state) + c += sprintf(buf + c, "[%s] ", lid_init_state_str[i]); + else + c += sprintf(buf + c, "%s ", lid_init_state_str[i]); + + buf[c - 1] = '\n'; /* Replace the final space with a newline */ + + return c; } module_param_call(lid_init_state, @@ -627,6 +730,16 @@ MODULE_PARM_DESC(lid_init_state, "Behavior for reporting LID initial state"); static int acpi_button_register_driver(struct acpi_driver *driver) { + const struct dmi_system_id *dmi_id; + + if (lid_init_state == -1) { + dmi_id = dmi_first_match(dmi_lid_quirks); + if (dmi_id) + lid_init_state = (long)dmi_id->driver_data; + else + lid_init_state = ACPI_BUTTON_LID_INIT_METHOD; + } + /* * Modules such as nouveau.ko and i915.ko have a link time dependency * on acpi_lid_open(), and would therefore not be loadable on ACPI |
