| // 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", |
| }, |
| }, |
| { |
| /* |
| * 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); |