// SPDX-License-Identifier: GPL-2.0 /* * ACPI quirks for GPIO ACPI helpers * * Author: Hans de Goede */ #include #include #include #include #include #include #include #include #include "gpiolib-acpi.h" static int run_edge_events_on_boot = -1; module_param(run_edge_events_on_boot, int, 0444); MODULE_PARM_DESC(run_edge_events_on_boot, "Run edge _AEI event-handlers at boot: 0=no, 1=yes, -1=auto"); static char *ignore_wake; module_param(ignore_wake, charp, 0444); MODULE_PARM_DESC(ignore_wake, "controller@pin combos on which to ignore the ACPI wake flag " "ignore_wake=controller@pin[,controller@pin[,...]]"); static char *ignore_interrupt; module_param(ignore_interrupt, charp, 0444); MODULE_PARM_DESC(ignore_interrupt, "controller@pin combos on which to ignore interrupt " "ignore_interrupt=controller@pin[,controller@pin[,...]]"); /* * For GPIO chips which call acpi_gpiochip_request_interrupts() before late_init * (so builtin drivers) we register the ACPI GpioInt IRQ handlers from a * late_initcall_sync() handler, so that other builtin drivers can register their * OpRegions before the event handlers can run. This list contains GPIO chips * for which the acpi_gpiochip_request_irqs() call has been deferred. */ static DEFINE_MUTEX(acpi_gpio_deferred_req_irqs_lock); static LIST_HEAD(acpi_gpio_deferred_req_irqs_list); static bool acpi_gpio_deferred_req_irqs_done; bool acpi_gpio_add_to_deferred_list(struct list_head *list) { bool defer; mutex_lock(&acpi_gpio_deferred_req_irqs_lock); defer = !acpi_gpio_deferred_req_irqs_done; if (defer) list_add(list, &acpi_gpio_deferred_req_irqs_list); mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); return defer; } void acpi_gpio_remove_from_deferred_list(struct list_head *list) { mutex_lock(&acpi_gpio_deferred_req_irqs_lock); if (!list_empty(list)) list_del_init(list); mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); } int acpi_gpio_need_run_edge_events_on_boot(void) { return run_edge_events_on_boot; } bool acpi_gpio_in_ignore_list(enum acpi_gpio_ignore_list list, const char *controller_in, unsigned int pin_in) { const char *ignore_list, *controller, *pin_str; unsigned int pin; char *endp; int len; switch (list) { case ACPI_GPIO_IGNORE_WAKE: ignore_list = ignore_wake; break; case ACPI_GPIO_IGNORE_INTERRUPT: ignore_list = ignore_interrupt; break; default: return false; } controller = ignore_list; while (controller) { pin_str = strchr(controller, '@'); if (!pin_str) goto err; len = pin_str - controller; if (len == strlen(controller_in) && strncmp(controller, controller_in, len) == 0) { pin = simple_strtoul(pin_str + 1, &endp, 10); if (*endp != 0 && *endp != ',') goto err; if (pin == pin_in) return true; } controller = strchr(controller, ','); if (controller) controller++; } return false; err: pr_err_once("Error: Invalid value for gpiolib_acpi.ignore_...: %s\n", ignore_list); return false; } /* Run deferred acpi_gpiochip_request_irqs() */ static int __init acpi_gpio_handle_deferred_request_irqs(void) { mutex_lock(&acpi_gpio_deferred_req_irqs_lock); acpi_gpio_process_deferred_list(&acpi_gpio_deferred_req_irqs_list); acpi_gpio_deferred_req_irqs_done = true; mutex_unlock(&acpi_gpio_deferred_req_irqs_lock); return 0; } /* We must use _sync so that this runs after the first deferred_probe run */ late_initcall_sync(acpi_gpio_handle_deferred_request_irqs); struct acpi_gpiolib_dmi_quirk { bool no_edge_events_on_boot; char *ignore_wake; char *ignore_interrupt; }; static const struct dmi_system_id gpiolib_acpi_quirks[] __initconst = { { /* * The Minix Neo Z83-4 has a micro-USB-B id-pin handler for * a non existing micro-USB-B connector which puts the HDMI * DDC pins in GPIO mode, breaking HDMI support. */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "MINIX"), DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .no_edge_events_on_boot = true, }, }, { /* * The Terra Pad 1061 has a micro-USB-B id-pin handler, which * instead of controlling the actual micro-USB-B turns the 5V * boost for its USB-A connector off. The actual micro-USB-B * connector is wired for charging only. */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Wortmann_AG"), DMI_MATCH(DMI_PRODUCT_NAME, "TERRA_PAD_1061"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .no_edge_events_on_boot = true, }, }, { /* * The Dell Venue 10 Pro 5055, with Bay Trail SoC + TI PMIC uses an * external embedded-controller connected via I2C + an ACPI GPIO * event handler on INT33FFC:02 pin 12, causing spurious wakeups. */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), DMI_MATCH(DMI_PRODUCT_NAME, "Venue 10 Pro 5055"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .ignore_wake = "INT33FC:02@12", }, }, { /* * HP X2 10 models with Cherry Trail SoC + TI PMIC use an * external embedded-controller connected via I2C + an ACPI GPIO * event handler on INT33FF:01 pin 0, causing spurious wakeups. * When suspending by closing the LID, the power to the USB * keyboard is turned off, causing INT0002 ACPI events to * trigger once the XHCI controller notices the keyboard is * gone. So INT0002 events cause spurious wakeups too. Ignoring * EC wakes breaks wakeup when opening the lid, the user needs * to press the power-button to wakeup the system. The * alternative is suspend simply not working, which is worse. */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "HP"), DMI_MATCH(DMI_PRODUCT_NAME, "HP x2 Detachable 10-p0XX"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .ignore_wake = "INT33FF:01@0,INT0002:00@2", }, }, { /* * HP X2 10 models with Bay Trail SoC + AXP288 PMIC use an * external embedded-controller connected via I2C + an ACPI GPIO * event handler on INT33FC:02 pin 28, causing spurious wakeups. */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"), DMI_MATCH(DMI_BOARD_NAME, "815D"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .ignore_wake = "INT33FC:02@28", }, }, { /* * HP X2 10 models with Cherry Trail SoC + AXP288 PMIC use an * external embedded-controller connected via I2C + an ACPI GPIO * event handler on INT33FF:01 pin 0, causing spurious wakeups. */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "HP"), DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion x2 Detachable"), DMI_MATCH(DMI_BOARD_NAME, "813E"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .ignore_wake = "INT33FF:01@0", }, }, { /* * Interrupt storm caused from edge triggered floating pin * Found in BIOS UX325UAZ.300 * https://bugzilla.kernel.org/show_bug.cgi?id=216208 */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX325UAZ_UM325UAZ"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .ignore_interrupt = "AMDI0030:00@18", }, }, { /* * Spurious wakeups from TP_ATTN# pin * Found in BIOS 1.7.8 * https://gitlab.freedesktop.org/drm/amd/-/issues/1722#note_1720627 */ .matches = { DMI_MATCH(DMI_BOARD_NAME, "NL5xNU"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .ignore_wake = "ELAN0415:00@9", }, }, { /* * Spurious wakeups from TP_ATTN# pin * Found in BIOS 1.7.8 * https://gitlab.freedesktop.org/drm/amd/-/issues/1722#note_1720627 */ .matches = { DMI_MATCH(DMI_BOARD_NAME, "NL5xRU"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .ignore_wake = "ELAN0415:00@9", }, }, { /* * Spurious wakeups from TP_ATTN# pin * Found in BIOS 1.7.7 */ .matches = { DMI_MATCH(DMI_BOARD_NAME, "NH5xAx"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .ignore_wake = "SYNA1202:00@16", }, }, { /* * On the Peaq C1010 2-in-1 INT33FC:00 pin 3 is connected to * a "dolby" button. At the ACPI level an _AEI event-handler * is connected which sets an ACPI variable to 1 on both * edges. This variable can be polled + cleared to 0 using * WMI. But since the variable is set on both edges the WMI * interface is pretty useless even when polling. * So instead the x86-android-tablets code instantiates * a gpio-keys platform device for it. * Ignore the _AEI handler for the pin, so that it is not busy. */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "PEAQ"), DMI_MATCH(DMI_PRODUCT_NAME, "PEAQ PMM C1010 MD99187"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .ignore_interrupt = "INT33FC:00@3", }, }, { /* * Spurious wakeups from TP_ATTN# pin * Found in BIOS 0.35 * https://gitlab.freedesktop.org/drm/amd/-/issues/3073 */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "GPD"), DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .ignore_wake = "PNP0C50:00@8", }, }, { /* * Spurious wakeups from GPIO 11 * Found in BIOS 1.04 * https://gitlab.freedesktop.org/drm/amd/-/issues/3954 */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Acer"), DMI_MATCH(DMI_PRODUCT_FAMILY, "Acer Nitro V 14"), }, .driver_data = &(struct acpi_gpiolib_dmi_quirk) { .ignore_interrupt = "AMDI0030:00@11", }, }, {} /* Terminating entry */ }; static int __init acpi_gpio_setup_params(void) { const struct acpi_gpiolib_dmi_quirk *quirk = NULL; const struct dmi_system_id *id; id = dmi_first_match(gpiolib_acpi_quirks); if (id) quirk = id->driver_data; if (run_edge_events_on_boot < 0) { if (quirk && quirk->no_edge_events_on_boot) run_edge_events_on_boot = 0; else run_edge_events_on_boot = 1; } if (ignore_wake == NULL && quirk && quirk->ignore_wake) ignore_wake = quirk->ignore_wake; if (ignore_interrupt == NULL && quirk && quirk->ignore_interrupt) ignore_interrupt = quirk->ignore_interrupt; return 0; } /* Directly after dmi_setup() which runs as core_initcall() */ postcore_initcall(acpi_gpio_setup_params);