|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * ACPI quirks for GPIO ACPI helpers | 
|  | * | 
|  | * Author: Hans de Goede <hdegoede@redhat.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/dmi.h> | 
|  | #include <linux/kstrtox.h> | 
|  | #include <linux/list.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/mutex.h> | 
|  | #include <linux/printk.h> | 
|  | #include <linux/string.h> | 
|  | #include <linux/types.h> | 
|  |  | 
|  | #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", | 
|  | }, | 
|  | }, | 
|  | { | 
|  | /* | 
|  | * Same as G1619-04. New model. | 
|  | */ | 
|  | .matches = { | 
|  | DMI_MATCH(DMI_SYS_VENDOR, "GPD"), | 
|  | DMI_MATCH(DMI_PRODUCT_NAME, "G1619-05"), | 
|  | }, | 
|  | .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", | 
|  | }, | 
|  | }, | 
|  | { | 
|  | /* | 
|  | * Wakeup only works when keyboard backlight is turned off | 
|  | * https://gitlab.freedesktop.org/drm/amd/-/issues/4169 | 
|  | */ | 
|  | .matches = { | 
|  | DMI_MATCH(DMI_SYS_VENDOR, "Acer"), | 
|  | DMI_MATCH(DMI_PRODUCT_FAMILY, "Acer Nitro V 15"), | 
|  | }, | 
|  | .driver_data = &(struct acpi_gpiolib_dmi_quirk) { | 
|  | .ignore_interrupt = "AMDI0030:00@8", | 
|  | }, | 
|  | }, | 
|  | { | 
|  | /* | 
|  | * Spurious wakeups from TP_ATTN# pin | 
|  | * Found in BIOS 5.35 | 
|  | * https://gitlab.freedesktop.org/drm/amd/-/issues/4482 | 
|  | */ | 
|  | .matches = { | 
|  | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | 
|  | DMI_MATCH(DMI_PRODUCT_FAMILY, "ProArt PX13"), | 
|  | }, | 
|  | .driver_data = &(struct acpi_gpiolib_dmi_quirk) { | 
|  | .ignore_wake = "ASCP1A00:00@8", | 
|  | }, | 
|  | }, | 
|  | {} /* 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); |