gpiolib-acpi: Add ACPI5 event model support to gpio.
[firefly-linux-kernel-4.4.55.git] / drivers / gpio / gpiolib-acpi.c
index cbad6e908d30e1d093d651232d425d3976d45864..54ce2269ed25d7482d898cfa071dd809525876da 100644 (file)
@@ -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);