summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/gpio/gpiolib-acpi.c82
-rw-r--r--include/linux/acpi_gpio.h4
2 files changed, 86 insertions, 0 deletions
diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c
index cbad6e908d3..54ce2269ed2 100644
--- a/drivers/gpio/gpiolib-acpi.c
+++ b/drivers/gpio/gpiolib-acpi.c
@@ -15,6 +15,7 @@
#include <linux/export.h>
#include <linux/acpi_gpio.h>
#include <linux/acpi.h>
+#include <linux/interrupt.h>
static int acpi_gpiochip_find(struct gpio_chip *gc, void *data)
{
@@ -52,3 +53,84 @@ int acpi_get_gpio(char *path, int pin)
return chip->base + pin;
}
EXPORT_SYMBOL_GPL(acpi_get_gpio);
+
+
+static irqreturn_t acpi_gpio_irq_handler(int irq, void *data)
+{
+ acpi_handle handle = data;
+
+ acpi_evaluate_object(handle, NULL, NULL, NULL);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * acpi_gpiochip_request_interrupts() - Register isr for gpio chip ACPI events
+ * @chip: gpio chip
+ *
+ * ACPI5 platforms can use GPIO signaled ACPI events. These GPIO interrupts are
+ * handled by ACPI event methods which need to be called from the GPIO
+ * chip's interrupt handler. acpi_gpiochip_request_interrupts finds out which
+ * gpio pins have acpi event methods and assigns interrupt handlers that calls
+ * the acpi event methods for those pins.
+ *
+ * Interrupts are automatically freed on driver detach
+ */
+
+void acpi_gpiochip_request_interrupts(struct gpio_chip *chip)
+{
+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL};
+ struct acpi_resource *res;
+ acpi_handle handle, ev_handle;
+ acpi_status status;
+ unsigned int pin, irq;
+ char ev_name[5];
+
+ if (!chip->dev || !chip->to_irq)
+ return;
+
+ handle = ACPI_HANDLE(chip->dev);
+ if (!handle)
+ return;
+
+ status = acpi_get_event_resources(handle, &buf);
+ if (ACPI_FAILURE(status))
+ return;
+
+ /* If a gpio interrupt has an acpi event handler method, then
+ * set up an interrupt handler that calls the acpi event handler
+ */
+
+ for (res = buf.pointer;
+ res && (res->type != ACPI_RESOURCE_TYPE_END_TAG);
+ res = ACPI_NEXT_RESOURCE(res)) {
+
+ if (res->type != ACPI_RESOURCE_TYPE_GPIO ||
+ res->data.gpio.connection_type !=
+ ACPI_RESOURCE_GPIO_TYPE_INT)
+ continue;
+
+ pin = res->data.gpio.pin_table[0];
+ if (pin > chip->ngpio)
+ continue;
+
+ sprintf(ev_name, "_%c%02X",
+ res->data.gpio.triggering ? 'E' : 'L', pin);
+
+ status = acpi_get_handle(handle, ev_name, &ev_handle);
+ if (ACPI_FAILURE(status))
+ continue;
+
+ irq = chip->to_irq(chip, pin);
+ if (irq < 0)
+ continue;
+
+ /* Assume BIOS sets the triggering, so no flags */
+ devm_request_threaded_irq(chip->dev, irq, NULL,
+ acpi_gpio_irq_handler,
+ 0,
+ "GPIO-signaled-ACPI-event",
+ ev_handle);
+ }
+}
+EXPORT_SYMBOL(acpi_gpiochip_request_interrupts);
diff --git a/include/linux/acpi_gpio.h b/include/linux/acpi_gpio.h
index 91615a389b6..b76ebd08ff8 100644
--- a/include/linux/acpi_gpio.h
+++ b/include/linux/acpi_gpio.h
@@ -2,10 +2,12 @@
#define _LINUX_ACPI_GPIO_H_
#include <linux/errno.h>
+#include <linux/gpio.h>
#ifdef CONFIG_GPIO_ACPI
int acpi_get_gpio(char *path, int pin);
+void acpi_gpiochip_request_interrupts(struct gpio_chip *chip);
#else /* CONFIG_GPIO_ACPI */
@@ -14,6 +16,8 @@ static inline int acpi_get_gpio(char *path, int pin)
return -ENODEV;
}
+static inline void acpi_gpiochip_request_interrupts(struct gpio_chip *chip) { }
+
#endif /* CONFIG_GPIO_ACPI */
#endif /* _LINUX_ACPI_GPIO_H_ */