// SPDX-License-Identifier: GPL-2.0+
/*
* DMI based code to deal with broken DSDTs on X86 tablets which ship with
* Android as (part of) the factory image. The factory kernels shipped on these
* devices typically have a bunch of things hardcoded, rather than specified
* in their DSDT.
*
* Copyright (C) 2021 Hans de Goede <hdegoede@redhat.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/dmi.h>
#include <linux/gpio_keys.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/gpio/machine.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/power/bq24190_charger.h>
#include <linux/serdev.h>
#include <linux/string.h>
/* For gpio_get_desc() which is EXPORT_SYMBOL_GPL() */
#include "../../gpio/gpiolib.h"
#include "../../gpio/gpiolib-acpi.h"
/*
* Helper code to get Linux IRQ numbers given a description of the IRQ source
* (either IOAPIC index, or GPIO chip name + pin-number).
*/
enum x86_acpi_irq_type {
X86_ACPI_IRQ_TYPE_NONE,
X86_ACPI_IRQ_TYPE_APIC,
X86_ACPI_IRQ_TYPE_GPIOINT,
X86_ACPI_IRQ_TYPE_PMIC,
};
struct x86_acpi_irq_data {
char *chip; /* GPIO chip label (GPIOINT) or PMIC ACPI path (PMIC) */
enum x86_acpi_irq_type type;
enum irq_domain_bus_token domain;
int index;
int trigger; /* ACPI_EDGE_SENSITIVE / ACPI_LEVEL_SENSITIVE */
int polarity; /* ACPI_ACTIVE_HIGH / ACPI_ACTIVE_LOW / ACPI_ACTIVE_BOTH */
};
static int gpiochip_find_match_label(struct gpio_chip *gc, void *data)
{
return gc->label && !strcmp(gc->label, data);
}
static int x86_android_tablet_get_gpiod(char *label, int pin, struct gpio_desc **desc)
{
struct gpio_desc *gpiod;
struct gpio_chip *chip;
chip = gpiochip_find(label, gpiochip_find_match_label);
if (!chip) {
pr_err("error cannot find GPIO chip %s\n", label);
return -ENODEV;
}
gpiod = gpiochip_get_desc(chip, pin);
if (IS_ERR(gpiod)) {
pr_err("error %ld getting GPIO %s %d\n", PTR_ERR(gpiod), label, pin);
return PTR_ERR(gpiod);
}
*desc = gpiod;
return 0;
}
static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data)
{
struct irq_fwspec fwspec = { };
struct irq_domain *domain;
struct acpi_device *adev;
struct gpio_desc *gpiod;
unsigned int irq_type;
acpi_handle handle;
acpi_status status;
int irq, ret;
switch (data->type) {
case X86_ACPI_IRQ_TYPE_APIC:
/*
* The DSDT may already reference the GSI in a device skipped by
* acpi_quirk_skip_i2c_client_enumeration(). Unregister the GSI
* to avoid EBUSY errors in this case.
*/
acpi_unregister_gsi(data->index);
irq = acpi_register_gsi(NULL, data->index, data->trigger, data->polarity);
if (irq < 0)
pr_err("error %d getting APIC IRQ %d\n", irq, data->index);
return irq;
case X86_ACPI_IRQ_TYPE_GPIOINT:
/* Like acpi_dev_gpio_irq_get(), but without parsing ACPI resources */
ret = x86_android_tablet_get_gpiod(data->chip, data->index, &gpiod);
if (ret)
return ret;
irq = gpiod_to_irq(gpiod);
if (irq < 0) {
pr_err("error %d getting IRQ %s %d\n", irq, data->chip, data->index);
return irq;
}
irq_type = acpi_dev_get_irq_type(data->trigger, data->polarity);
if (irq_type != IRQ_TYPE_NONE && irq_type != irq_get_trigger_type(irq))
irq_set_irq_type(