root/drivers/gpio/gpiolib-acpi-quirks.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",
                },
        },
        {
                /*
                 * 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",
                },
        },
        {
                /*
                 * Spurious wakeups, likely from touchpad controller
                 * Dell Precision 7780
                 * Found in BIOS 1.24.1
                 *
                 * Found in touchpad firmware, installed by Dell Touchpad Firmware Update Utility version 1160.4196.9, A01
                 * ( Dell-Touchpad-Firmware-Update-Utility_VYGNN_WIN64_1160.4196.9_A00.EXE ),
                 * released on 11 Jul 2024
                 *
                 * https://lore.kernel.org/linux-i2c/197ae95ffd8.dc819e60457077.7692120488609091556@zohomail.com/
                 */
                .matches = {
                        DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
                        DMI_MATCH(DMI_PRODUCT_FAMILY, "Precision"),
                        DMI_MATCH(DMI_PRODUCT_NAME, "Precision 7780"),
                        DMI_MATCH(DMI_BOARD_NAME, "0C6JVW"),
                },
                .driver_data = &(struct acpi_gpiolib_dmi_quirk) {
                        .ignore_wake = "VEN_0488:00@355",
                },
        },
        {} /* 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);