linux/drivers/gpio/gpiolib-acpi-quirks.c
Mario Limonciello 9ab29ed505 gpiolib: acpi: Add a quirk for Acer Nitro V15
It is reported that on Acer Nitro V15 suspend only works properly if the
keyboard backlight is turned off. In looking through the issue Acer Nitro
V15 has a GPIO (#8) specified in _AEI but it has no matching notify device
in _EVT. The values for GPIO #8 change as keyboard backlight is turned on
and off.

This makes it seem that GPIO #8 is actually supposed to be solely for
keyboard backlight.  Turning off the interrupt for this GPIO fixes the issue.
Add a quirk that does just that.

Closes: https://gitlab.freedesktop.org/drm/amd/-/issues/4169
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
Acked-by: Mika Westerberg <westeri@kernel.org>
Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
2025-06-09 23:09:24 +03:00

376 lines
10 KiB
C

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