[gpio][interrupts] Add Interrupt support for GPIOs

Change-Id: Ib2657d24247614641f3f1d809f0371fd653d9417
diff --git a/system/dev/board/aml-s905d2/aml-gpio.c b/system/dev/board/aml-s905d2/aml-gpio.c
index 7883764..bb29c61 100644
--- a/system/dev/board/aml-s905d2/aml-gpio.c
+++ b/system/dev/board/aml-s905d2/aml-gpio.c
@@ -61,6 +61,7 @@
         .irq = S905D2_GPIO_IRQ_7,
         .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
+    /*
     {
         .irq = S905D2_A0_GPIO_IRQ_0,
         .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
@@ -69,6 +70,7 @@
         .irq = S905D2_A0_GPIO_IRQ_1,
         .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
+    */
 };
 
 static pbus_dev_t gpio_dev = {
diff --git a/system/dev/board/vim/vim-gpio.c b/system/dev/board/vim/vim-gpio.c
index f8385c1..b0764be 100644
--- a/system/dev/board/vim/vim-gpio.c
+++ b/system/dev/board/vim/vim-gpio.c
@@ -28,49 +28,43 @@
         .base = S912_GPIO_A0_BASE,
         .length = S912_GPIO_AO_LENGTH,
     },
+    {
+        .base = S912_GPIO_INTERRUPT_BASE,
+        .length = S912_GPIO_INTERRUPT_LENGTH,
+    },
 };
 
 // S905X and S912 have same GPIO IRQ numbers
 static const pbus_irq_t gpio_irqs[] = {
     {
         .irq = S912_GPIO_IRQ_0,
-        .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
     {
         .irq = S912_GPIO_IRQ_1,
-        .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
     {
         .irq = S912_GPIO_IRQ_2,
-        .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
     {
         .irq = S912_GPIO_IRQ_3,
-        .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
     {
         .irq = S912_GPIO_IRQ_4,
-        .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
     {
         .irq = S912_GPIO_IRQ_5,
-        .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
     {
         .irq = S912_GPIO_IRQ_6,
-        .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
     {
         .irq = S912_GPIO_IRQ_7,
-        .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
     {
         .irq = S912_A0_GPIO_IRQ_0,
-        .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
     {
         .irq = S912_A0_GPIO_IRQ_1,
-        .mode = ZX_INTERRUPT_MODE_EDGE_HIGH,
     },
 };
 
@@ -113,6 +107,12 @@
             // SYS_LED
             .gpio = (bus->soc_pid == PDEV_PID_AMLOGIC_S912 ? S912_GPIOAO(9) : S905X_GPIOAO(9)),
         },
+        {
+            .gpio = S912_GPIOH(7),
+        },
+        {
+            .gpio = S912_GPIOH(5),
+        }
     };
 
     const pbus_dev_t gpio_test_dev = {
diff --git a/system/dev/board/vim/vim.c b/system/dev/board/vim/vim.c
index 6708b9c..fb69015 100644
--- a/system/dev/board/vim/vim.c
+++ b/system/dev/board/vim/vim.c
@@ -63,7 +63,7 @@
 const pbus_gpio_t vim_display_gpios[] = {
     {
         // HPD
-        .gpio = S912_GPIOH(20),
+        .gpio = S912_GPIOH(0),
     },
 };
 
diff --git a/system/dev/bus/platform/platform-device.c b/system/dev/bus/platform/platform-device.c
index 0d110e1..6794265 100644
--- a/system/dev/bus/platform/platform-device.c
+++ b/system/dev/bus/platform/platform-device.c
@@ -58,15 +58,21 @@
     return status;
 }
 
-static zx_status_t platform_dev_map_interrupt(void* ctx, uint32_t index, zx_handle_t* out_handle) {
+static zx_status_t platform_dev_map_interrupt(void* ctx, uint32_t index,
+                                              uint32_t flags, zx_handle_t* out_handle) {
     platform_dev_t* dev = ctx;
-
+    uint32_t flags_;
     if (index >= dev->irq_count || !out_handle) {
         return ZX_ERR_INVALID_ARGS;
     }
     pbus_irq_t* irq = &dev->irqs[index];
+    if (flags) {
+        flags_ = flags;
+    } else {
+        flags_ = irq->mode;
+    }
 #if ENABLE_NEW_IRQ_API
-    zx_status_t status = zx_irq_create(dev->bus->resource, irq->irq, irq->mode, out_handle);
+    zx_status_t status = zx_irq_create(dev->bus->resource, irq->irq, flags_, out_handle);
     if (status != ZX_OK) {
         zxlogf(ERROR, "platform_dev_map_interrupt: zx_irq_create failed %d\n", status);
         return status;
@@ -77,7 +83,7 @@
         zxlogf(ERROR, "platform_dev_map_interrupt: zx_interrupt_create failed %d\n", status);
         return status;
     }
-    status = zx_interrupt_bind(*out_handle, 0, dev->bus->resource, irq->irq, irq->mode);
+    status = zx_interrupt_bind(*out_handle, 0, dev->bus->resource, irq->irq, flags_);
     if (status != ZX_OK) {
         zxlogf(ERROR, "platform_dev_map_interrupt: zx_interrupt_bind failed %d\n", status);
         zx_handle_close(*out_handle);
@@ -149,9 +155,10 @@
 }
 
 static zx_status_t pdev_rpc_get_interrupt(platform_dev_t* dev, uint32_t index,
+                                          uint32_t flags,
                                           zx_handle_t* out_handle, uint32_t* out_handle_count) {
 
-    zx_status_t status = platform_dev_map_interrupt(dev, index, out_handle);
+    zx_status_t status = platform_dev_map_interrupt(dev, index, flags, out_handle);
     if (status == ZX_OK) {
         *out_handle_count = 1;
     }
@@ -238,6 +245,26 @@
     return gpio_write(&bus->gpio, index, value);
 }
 
+static zx_status_t pdev_rpc_get_gpio_interrupt(platform_dev_t* dev, uint32_t index,
+                                               uint32_t flags,
+                                               zx_handle_t* out_handle,
+                                               uint32_t* out_handle_count) {
+    platform_bus_t* bus = dev->bus;
+    if (!bus->gpio.ops) {
+        return ZX_ERR_NOT_SUPPORTED;
+    }
+    if (index >= dev->gpio_count) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    index = dev->gpios[index].gpio;
+    zx_status_t status = gpio_get_interrupt(&bus->gpio, index, flags, out_handle);
+    if (status == ZX_OK) {
+        *out_handle_count = 1;
+    }
+    return status;
+}
+
 static zx_status_t pdev_rpc_i2c_transact(platform_dev_t* dev, pdev_req_t* req, uint8_t* data,
                                         zx_handle_t channel) {
     platform_bus_t* bus = dev->bus;
@@ -312,7 +339,7 @@
                                             &handle, &handle_count);
         break;
     case PDEV_GET_INTERRUPT:
-        resp.status = pdev_rpc_get_interrupt(dev, req->index, &handle, &handle_count);
+        resp.status = pdev_rpc_get_interrupt(dev, req->index, req->flags, &handle, &handle_count);
         break;
     case PDEV_GET_BTI:
         resp.status = pdev_rpc_get_bti(dev, req->index, &handle, &handle_count);
@@ -338,6 +365,9 @@
     case PDEV_GPIO_WRITE:
         resp.status = pdev_rpc_gpio_write(dev, req->index, req->gpio_value);
         break;
+    case PDEV_GPIO_GET_INTERRUPT:
+        resp.status = pdev_rpc_get_gpio_interrupt(dev, req->index, req->flags, &handle, &handle_count);
+        break;
     case PDEV_I2C_GET_MAX_TRANSFER:
         resp.status = i2c_impl_get_max_transfer_size(&dev->bus->i2c, req->index,
                                                      &resp.i2c_max_transfer);
diff --git a/system/dev/bus/platform/platform-proxy.c b/system/dev/bus/platform/platform-proxy.c
index 1a5503f..54f18cd 100644
--- a/system/dev/bus/platform/platform-proxy.c
+++ b/system/dev/bus/platform/platform-proxy.c
@@ -126,6 +126,20 @@
     return platform_dev_rpc(proxy, &req, sizeof(req), &resp, sizeof(resp), NULL, 0, NULL);
 }
 
+static zx_status_t pdev_gpio_get_interrupt(void* ctx, uint32_t index,
+                                           uint32_t flags,
+                                           zx_handle_t *out_handle) {
+    platform_proxy_t* proxy = ctx;
+    pdev_req_t req = {
+        .op = PDEV_GPIO_GET_INTERRUPT,
+        .index = index,
+        .flags = flags,
+    };
+    pdev_resp_t resp;
+
+    return platform_dev_rpc(proxy, &req, sizeof(req), &resp, sizeof(resp),
+                                          out_handle, 1, NULL);
+}
 static zx_status_t pdev_gpio_read(void* ctx, uint32_t index, uint8_t* out_value) {
     platform_proxy_t* proxy = ctx;
     pdev_req_t req = {
@@ -160,6 +174,7 @@
     .set_alt_function = pdev_gpio_set_alt_function,
     .read = pdev_gpio_read,
     .write = pdev_gpio_write,
+    .get_interrupt = pdev_gpio_get_interrupt,
 };
 
 static zx_status_t pdev_i2c_get_max_transfer_size(void* ctx, uint32_t index, size_t* out_size) {
@@ -323,11 +338,13 @@
     return status;
 }
 
-static zx_status_t platform_dev_map_interrupt(void* ctx, uint32_t index, zx_handle_t* out_handle) {
+static zx_status_t platform_dev_map_interrupt(void* ctx, uint32_t index,
+                                              uint32_t flags, zx_handle_t* out_handle) {
     platform_proxy_t* proxy = ctx;
     pdev_req_t req = {
         .op = PDEV_GET_INTERRUPT,
         .index = index,
+        .flags = flags,
     };
     pdev_resp_t resp;
 
diff --git a/system/dev/bus/platform/platform-proxy.h b/system/dev/bus/platform/platform-proxy.h
index f40018d..6061a61 100644
--- a/system/dev/bus/platform/platform-proxy.h
+++ b/system/dev/bus/platform/platform-proxy.h
@@ -30,6 +30,7 @@
     PDEV_GPIO_SET_ALT_FUNCTION,
     PDEV_GPIO_READ,
     PDEV_GPIO_WRITE,
+    PDEV_GPIO_GET_INTERRUPT,
 
     // ZX_PROTOCOL_I2C
     PDEV_I2C_GET_MAX_TRANSFER,
@@ -65,6 +66,7 @@
         uint8_t gpio_value;
         pdev_i2c_txn_ctx_t i2c_txn;
         uint32_t i2c_bitrate;
+        uint32_t flags;
     };
 } pdev_req_t;
 
diff --git a/system/dev/display/vim-display/vim-display.c b/system/dev/display/vim-display/vim-display.c
index f8e8133..c6eb070 100644
--- a/system/dev/display/vim-display/vim-display.c
+++ b/system/dev/display/vim-display/vim-display.c
@@ -265,48 +265,52 @@
     return ZX_OK;
 }
 
+static int hdmi_irq_handler(void *arg) {
+    vim2_display_t* display = arg;
+    while(1) {
+#if ENABLE_NEW_IRQ_API
+        zx_status_t status = zx_irq_wait(display->inth, NULL);
+#else
+        uint64_t slots;
+        zx_status_t status = zx_interrupt_wait(display->inth, &slots);
+#endif
+        if (status != ZX_OK) {
+            printf("hdmi_irq_handler: Waiting failed %d\n", status);
+            return -1;
+        }
+        if (display->hdmi_inited) {
+            DISP_ERROR("Display Disconnected!\n");
+            hdmi_shutdown(display);
+            io_buffer_release(&display->fbuffer);
+            device_remove(display->fbdevice);
+        }
+        if (ZX_OK == setup_hdmi(display)) {
+            display->hdmi_inited = true;
+            DISP_ERROR("Display is connected\n");
+        }
+    }
+    return 0;
+}
+
 static int main_hdmi_thread(void *arg)
 {
     vim2_display_t* display = arg;
-    static bool hdmi_inited = false;
-    static bool print_once = true;
-    uint8_t hpd_val;
+    zx_status_t status;
 
-    if (gpio_config(&display->gpio, 0, GPIO_DIR_IN) != ZX_OK) {
-        DISP_ERROR("Invalid HPD Pin!! Will try and connect to display anyways\n");
-        // try once
-        setup_hdmi(display);
-        return ZX_OK;
+    if (ZX_OK != (status = gpio_config(&display->gpio, 0, GPIO_DIR_IN))) {
+        DISP_ERROR("DISPLAY: gpio_config failed for gpio\n");
+        return status;
     }
 
-    while (1) {
-        // check HPD GPIO Pins
-        gpio_read(&display->gpio, 0, &hpd_val);
-
-        if (hpd_val == 0) {
-            if (print_once) {
-                DISP_ERROR("No Display Connected. Will try again later\n");
-                print_once = false;
-            }
-            if (hdmi_inited) {
-                // let's shutdown hdmi
-                DISP_ERROR("Display Disconnected!\n");
-                hdmi_shutdown(display);
-                io_buffer_release(&display->fbuffer);
-                device_remove(display->fbdevice);
-                hdmi_inited = false;
-            }
-        } else {
-            if (!hdmi_inited) {
-                DISP_ERROR("Display is connected\n");
-                if (setup_hdmi(display) != ZX_OK) {
-                    return ZX_ERR_UNAVAILABLE;
-                }
-                hdmi_inited = true;
-            }
-        }
-        usleep(500000); // sleep with 500ms
+    if (ZX_OK != (status = gpio_get_interrupt(&display->gpio, 0,
+                       ZX_INTERRUPT_MODE_EDGE_HIGH,
+                       &display->inth) != ZX_OK)) {
+        DISP_ERROR("DISPLAY: gpio_config failed for gpio\n");
+        return status;
     }
+
+    thrd_create_with_name(&display->main_thread, hdmi_irq_handler, display, "hdmi_irq_handler thread");
+    return ZX_OK;
 }
 
 zx_status_t vim2_display_bind(void* ctx, zx_device_t* parent) {
diff --git a/system/dev/display/vim-display/vim-display.h b/system/dev/display/vim-display/vim-display.h
index f2d1e90..575b2d2 100644
--- a/system/dev/display/vim-display/vim-display.h
+++ b/system/dev/display/vim-display/vim-display.h
@@ -31,6 +31,7 @@
     zx_device_t*                        mydevice;
     zx_device_t*                        fbdevice;
     zx_handle_t                         bti;
+    zx_handle_t                         inth;
 
     gpio_protocol_t                     gpio;
 
@@ -57,6 +58,7 @@
     disp_timing_t                       pref_disp_timing;
 
     bool console_visible;
+    bool hdmi_inited;
     zx_display_cb_t ownership_change_callback;
     void* ownership_change_cookie;
 } vim2_display_t;
diff --git a/system/dev/gpio/aml-axg-gpio/aml-axg-gpio.c b/system/dev/gpio/aml-axg-gpio/aml-axg-gpio.c
index 1156fce..c42962a 100644
--- a/system/dev/gpio/aml-axg-gpio/aml-axg-gpio.c
+++ b/system/dev/gpio/aml-axg-gpio/aml-axg-gpio.c
@@ -201,11 +201,19 @@
     return ZX_OK;
 }
 
+static zx_status_t aml_gpio_get_interrupt(void *ctx, uint32_t pin,
+                                        uint32_t flags,
+                                        zx_handle_t *out_handle) {
+    zxlogf(ERROR, "HELLO INTERRUPT AXG%u\n", pin);
+    return ZX_OK;
+}
+
 static gpio_protocol_ops_t gpio_ops = {
     .config = aml_gpio_config,
     .set_alt_function = aml_gpio_set_alt_function,
     .read = aml_gpio_read,
     .write = aml_gpio_write,
+    .get_interrupt = aml_gpio_get_interrupt,
 };
 
 static void aml_gpio_release(void* ctx) {
diff --git a/system/dev/gpio/aml-gxl-gpio/aml-gxl-gpio.c b/system/dev/gpio/aml-gxl-gpio/aml-gxl-gpio.c
index d4de42a..5915191 100644
--- a/system/dev/gpio/aml-gxl-gpio/aml-gxl-gpio.c
+++ b/system/dev/gpio/aml-gxl-gpio/aml-gxl-gpio.c
@@ -14,13 +14,10 @@
 #include <ddk/protocol/platform-defs.h>
 #include <ddk/protocol/platform-device.h>
 #include <hw/reg.h>
-
+#include "aml-gxl-gpio.h"
 #include <zircon/assert.h>
 #include <zircon/types.h>
 
-#define PINS_PER_BLOCK  32
-#define ALT_FUNCTION_MAX 6
-
 typedef struct {
     uint32_t pin_count;
     uint32_t oen_offset;
@@ -28,6 +25,9 @@
     uint32_t output_offset;
     uint32_t output_shift;  // Used for GPIOAO block
     uint32_t mmio_index;
+    uint32_t pull_offset;
+    uint32_t pull_en_offset;
+    uint32_t pin_start;
     mtx_t lock;
 } aml_gpio_block_t;
 
@@ -48,12 +48,22 @@
     gpio_protocol_t gpio;
     zx_device_t* zxdev;
     io_buffer_t mmios[2];    // separate MMIO for AO domain
+    io_buffer_t mmio_interrupt;
     aml_gpio_block_t* gpio_blocks;
     const aml_pinmux_block_t* pinmux_blocks;
     size_t block_count;
     mtx_t pinmux_lock;
+    uint32_t irq_count;
+    uint8_t irq_status;
 } aml_gpio_t;
 
+// MMIO indices (based on vim2_display_mmios)
+enum {
+    MMIO_GPIO,
+    MMIO_GPIO_A0,
+    MMIO_GPIO_INTERRUPTS,
+};
+
 #include "s912-blocks.h"
 #include "s905x-blocks.h"
 #include "s905-blocks.h"
@@ -71,7 +81,7 @@
     if (pin_index >= block->pin_count) {
         return ZX_ERR_NOT_FOUND;
     }
-
+    pin_index += block->output_shift;
     *out_block = block;
     *out_pin_index = pin_index;
     return ZX_OK;
@@ -88,21 +98,36 @@
         return status;
     }
 
+    // Set the GPIO as IN or OUT
     volatile uint32_t* reg = (volatile uint32_t *)io_buffer_virt(&gpio->mmios[block->mmio_index]);
     reg += block->oen_offset;
-
     mtx_lock(&block->lock);
 
     uint32_t regval = readl(reg);
-
-    if (flags & GPIO_DIR_OUT) {
+    uint32_t direction = flags & GPIO_DIR_MASK;
+    if (direction & GPIO_DIR_OUT) {
         regval &= ~(1 << pin_index);
     } else {
+        // Set the GPIO as pull-up or pull-down
+        uint32_t pull = flags & GPIO_PULL_MASK;
+        volatile uint32_t* pull_reg = (volatile uint32_t *)io_buffer_virt(&gpio->mmios[block->mmio_index]);
+        pull_reg += block->pull_offset;
+        volatile uint32_t* pull_en_reg = (volatile uint32_t *)io_buffer_virt(&gpio->mmios[block->mmio_index]);
+        pull_en_reg += block->pull_en_offset;
+
+        uint32_t pull_reg_val = readl(pull_reg);
+        uint32_t pull_en_reg_val = readl(pull_en_reg);
+        if (pull & GPIO_PULL_UP) {
+            pull_reg_val |= (1 << pin_index);
+        } else {
+            pull_reg_val &= ~(1 << pin_index);
+        }
+        pull_en_reg_val |= (1 << pin_index);
+        writel(pull_reg_val, pull_reg);
+        writel(pull_en_reg_val, pull_en_reg);
         regval |= (1 << pin_index);
     }
-
     writel(regval, reg);
-
     mtx_unlock(&block->lock);
 
     return ZX_OK;
@@ -132,7 +157,7 @@
 
     for (uint i = 0; i < ALT_FUNCTION_MAX; i++) {
         uint32_t reg_index = mux->regs[i];
-
+        //reg_index += block->output_shift;
         if (reg_index) {
             uint32_t mask = (1 << mux->bits[i]);
             uint32_t regval = readl(reg + reg_index);
@@ -162,7 +187,6 @@
         zxlogf(ERROR, "aml_gpio_read: pin not found %u\n", pin);
         return status;
     }
-
     const uint32_t readmask = 1 << pin_index;
 
     volatile uint32_t* reg = (volatile uint32_t *)io_buffer_virt(&gpio->mmios[block->mmio_index]);
@@ -196,7 +220,6 @@
 
     volatile uint32_t* reg = (volatile uint32_t *)io_buffer_virt(&gpio->mmios[block->mmio_index]);
     reg += block->output_offset;
-    pin_index += block->output_shift;
 
     mtx_lock(&block->lock);
 
@@ -215,18 +238,96 @@
     return ZX_OK;
 }
 
+static uint32_t aml_gpio_get_unsed_irq_index(uint8_t status) {
+    // First isolate the rightmost 0-bit
+    uint8_t zero_bit_set = ~status & (status+1);
+    // Count no. of leading zeros
+    return __builtin_ctz(zero_bit_set);
+}
+
+static zx_status_t aml_gpio_get_interrupt(void *ctx, uint32_t pin,
+                                          uint32_t flags,
+                                          zx_handle_t *out_handle) {
+    aml_gpio_t* gpio = ctx;
+    zx_status_t status;
+    mtx_lock(&gpio->pinmux_lock);
+    if (pin > MAX_GPIO_INDEX) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+    uint32_t index = aml_gpio_get_unsed_irq_index(gpio->irq_status);
+    if (index > gpio->irq_count) {
+        return ZX_ERR_NO_RESOURCES;
+    }
+
+    aml_gpio_block_t* block;
+    uint32_t pin_index;
+    if ((status = aml_pin_to_block(gpio, pin, &block, &pin_index)) != ZX_OK) {
+        zxlogf(ERROR, "aml_gpio_read: pin not found %u\n", pin);
+        return status;
+    }
+
+    // Create Interrupt Object
+    status = pdev_get_interrupt(&gpio->pdev, index, flags,
+                                    out_handle);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "aml_gpio_get_interrupt: pdev_map_interrupt failed %d\n", status);
+        return status;
+    }
+
+    // Configure GPIO interrupt
+    volatile uint32_t* reg = (volatile uint32_t *)io_buffer_virt(&gpio->mmio_interrupt);
+    reg  += (index>3)? S912_GPIO_4_7_PIN_SELECT: S912_GPIO_0_3_PIN_SELECT;
+
+    // Select GPIO IRQ(index) and program it to
+    // the requested GPIO PIN
+    uint32_t regval = readl(reg);
+    regval |= (((pin % PINS_PER_BLOCK) + block->pin_start) << (index * BITS_PER_GPIO_INTERRUPT));
+    writel(regval, reg);
+
+    // Configure GPIO Interrupt EDGE and Polarity
+    volatile uint32_t* mode_reg = (volatile uint32_t *)io_buffer_virt(&gpio->mmio_interrupt);
+    mode_reg += S912_GPIO_INT_EDGE_POLARITY;
+    uint32_t mode_reg_val = readl(mode_reg);
+
+    switch (flags & ZX_INTERRUPT_MODE_MASK) {
+    case ZX_INTERRUPT_MODE_EDGE_LOW:
+        mode_reg_val = mode_reg_val | (1 << index);
+        mode_reg_val = mode_reg_val | ((1 << index) << GPIO_INTERRUPT_POLARITY_SHIFT);
+        break;
+    case ZX_INTERRUPT_MODE_EDGE_HIGH:
+        mode_reg_val = mode_reg_val | (1 << index);
+        mode_reg_val = mode_reg_val & ~((1 << index) << GPIO_INTERRUPT_POLARITY_SHIFT);
+        break;
+    case ZX_INTERRUPT_MODE_LEVEL_LOW:
+        mode_reg_val = mode_reg_val & ~(1 << index);
+        mode_reg_val = mode_reg_val | ((1 << index) << GPIO_INTERRUPT_POLARITY_SHIFT);
+        break;
+    case ZX_INTERRUPT_MODE_LEVEL_HIGH:
+        mode_reg_val = mode_reg_val & ~(1 << index);
+        mode_reg_val = mode_reg_val & ~((1 << index) << GPIO_INTERRUPT_POLARITY_SHIFT);
+        break;
+    default:
+        return ZX_ERR_INVALID_ARGS;
+    }
+    writel(mode_reg_val, mode_reg);
+    gpio->irq_status |= 1 << index;
+    mtx_unlock(&gpio->pinmux_lock);
+    return ZX_OK;
+}
+
 static gpio_protocol_ops_t gpio_ops = {
     .config = aml_gpio_config,
     .set_alt_function = aml_gpio_set_alt_function,
     .read = aml_gpio_read,
     .write = aml_gpio_write,
+    .get_interrupt = aml_gpio_get_interrupt,
 };
 
 static void aml_gpio_release(void* ctx) {
     aml_gpio_t* gpio = ctx;
-    for (unsigned i = 0; i < countof(gpio->mmios); i++) {
-        io_buffer_release(&gpio->mmios[i]);
-    }
+    io_buffer_release(&gpio->mmios[0]);
+    io_buffer_release(&gpio->mmios[1]);
+    io_buffer_release(&gpio->mmio_interrupt);
     free(gpio);
 }
 
@@ -256,13 +357,25 @@
         goto fail;
     }
 
-    for (unsigned i = 0; i < countof(gpio->mmios); i++) {
-        status = pdev_map_mmio_buffer(&gpio->pdev, i, ZX_CACHE_POLICY_UNCACHED_DEVICE,
-                                      &gpio->mmios[i]);
-        if (status != ZX_OK) {
-            zxlogf(ERROR, "aml_gpio_bind: pdev_map_mmio_buffer failed\n");
-            goto fail;
-        }
+    status = pdev_map_mmio_buffer(&gpio->pdev, MMIO_GPIO, ZX_CACHE_POLICY_UNCACHED_DEVICE,
+                                    &gpio->mmios[0]);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "aml_gpio_bind: pdev_map_mmio_buffer failed\n");
+        goto fail;
+    }
+
+    status = pdev_map_mmio_buffer(&gpio->pdev, MMIO_GPIO_A0, ZX_CACHE_POLICY_UNCACHED_DEVICE,
+                                    &gpio->mmios[1]);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "aml_gpio_bind: pdev_map_mmio_buffer failed\n");
+        goto fail;
+    }
+
+    status = pdev_map_mmio_buffer(&gpio->pdev, MMIO_GPIO_INTERRUPTS, ZX_CACHE_POLICY_UNCACHED_DEVICE,
+                                    &gpio->mmio_interrupt);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "aml_gpio_bind: pdev_map_mmio_buffer failed\n");
+        goto fail;
     }
 
     pdev_device_info_t info;
@@ -307,6 +420,8 @@
         goto fail;
     }
 
+    gpio->irq_count = info.irq_count;
+    gpio->irq_status = 0;
     gpio->gpio.ops = &gpio_ops;
     gpio->gpio.ctx = gpio;
     pbus_set_protocol(&pbus, ZX_PROTOCOL_GPIO, &gpio->gpio);
diff --git a/system/dev/gpio/aml-gxl-gpio/aml-gxl-gpio.h b/system/dev/gpio/aml-gxl-gpio/aml-gxl-gpio.h
new file mode 100644
index 0000000..9408f0b
--- /dev/null
+++ b/system/dev/gpio/aml-gxl-gpio/aml-gxl-gpio.h
@@ -0,0 +1,17 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+// These are relative to base address 0xc1100000 and in sizeof(uint32_t)
+#define S912_GPIO_INT_EDGE_POLARITY     0x2620
+#define S912_GPIO_0_3_PIN_SELECT        0x2621
+#define S912_GPIO_4_7_PIN_SELECT        0x2622
+#define S912_GPIO_FILTER_SELECT         0x2623
+
+#define GPIO_INTERRUPT_POLARITY_SHIFT   16
+#define PINS_PER_BLOCK                  32
+#define ALT_FUNCTION_MAX                6
+#define MAX_GPIO_INDEX                  255
+#define BITS_PER_GPIO_INTERRUPT         8
diff --git a/system/dev/gpio/aml-gxl-gpio/s912-blocks.h b/system/dev/gpio/aml-gxl-gpio/s912-blocks.h
index e32166d..fb9d897 100644
--- a/system/dev/gpio/aml-gxl-gpio/s912-blocks.h
+++ b/system/dev/gpio/aml-gxl-gpio/s912-blocks.h
@@ -13,6 +13,9 @@
         .output_offset = S912_GPIOX_OUT,
         .output_shift = 0,
         .mmio_index = 0,
+        .pull_offset = S912_PULL_UP_REG4,
+        .pull_en_offset = S912_PULL_UP_EN_REG4,
+        .pin_start = S912_GPIOX_PIN_START,
         .lock = MTX_INIT,
     },
     // GPIODV Block
@@ -23,6 +26,9 @@
         .output_offset = S912_GPIODV_OUT,
         .output_shift = 0,
         .mmio_index = 0,
+        .pull_offset = S912_PULL_UP_REG0,
+        .pull_en_offset = S912_PULL_UP_EN_REG0,
+        .pin_start = S912_GPIODV_PIN_START,
         .lock = MTX_INIT,
     },
     // GPIOH Block
@@ -31,8 +37,11 @@
         .oen_offset = S912_GPIOH_0EN,
         .input_offset = S912_GPIOH_IN,
         .output_offset = S912_GPIOH_OUT,
-        .output_shift = 0,
+        .output_shift = 20,
         .mmio_index = 0,
+        .pull_offset = S912_PULL_UP_REG1,
+        .pull_en_offset = S912_PULL_UP_EN_REG1,
+        .pin_start = S912_GPIOH_PIN_START,
         .lock = MTX_INIT,
     },
     // GPIOBOOT Block
@@ -43,6 +52,9 @@
         .output_offset = S912_GPIOBOOT_OUT,
         .output_shift = 0,
         .mmio_index = 0,
+        .pull_offset = S912_PULL_UP_REG2,
+        .pull_en_offset = S912_PULL_UP_EN_REG2,
+        .pin_start = S912_GPIOBOOT_PIN_START,
         .lock = MTX_INIT,
     },
     // GPIOCARD Block
@@ -51,8 +63,11 @@
         .oen_offset = S912_GPIOCARD_0EN,
         .input_offset = S912_GPIOCARD_IN,
         .output_offset = S912_GPIOCARD_OUT,
-        .output_shift = 0,
+        .output_shift = 20,
         .mmio_index = 0,
+        .pull_offset = S912_PULL_UP_REG2,
+        .pull_en_offset = S912_PULL_UP_EN_REG2,
+        .pin_start = S912_GPIOCARD_PIN_START,
         .lock = MTX_INIT,
     },
     // GPIOCLK Block
@@ -61,8 +76,11 @@
         .oen_offset = S912_GPIOCLK_0EN,
         .input_offset = S912_GPIOCLK_IN,
         .output_offset = S912_GPIOCLK_OUT,
-        .output_shift = 0,
+        .output_shift = 28,
         .mmio_index = 0,
+        .pull_offset = S912_PULL_UP_REG3,
+        .pull_en_offset = S912_PULL_UP_EN_REG3,
+        .pin_start = S912_GPIOCLK_PIN_START,
         .lock = MTX_INIT,
     },
     // GPIOZ Block
@@ -73,6 +91,9 @@
         .output_offset = S912_GPIOZ_OUT,
         .output_shift = 0,
         .mmio_index = 0,
+        .pull_offset = S912_PULL_UP_REG3,
+        .pull_en_offset = S912_PULL_UP_EN_REG3,
+        .pin_start = S912_GPIOZ_PIN_START,
         .lock = MTX_INIT,
     },
     // GPIOAO Block
@@ -83,6 +104,9 @@
         .output_offset = S912_AO_GPIO_OEN_OUT,
         .output_shift = 16, // output is shared with OEN
         .mmio_index = 1,
+        .pull_offset = 0, // not supported
+        .pull_en_offset = 0, // not supported
+        .pin_start = S912_GPIOA0_PIN_START,
         .lock = MTX_INIT,
     },
 };
diff --git a/system/dev/gpio/gpio-test/gpio-test.c b/system/dev/gpio/gpio-test/gpio-test.c
index e8d55ef..b4331a7 100644
--- a/system/dev/gpio/gpio-test/gpio-test.c
+++ b/system/dev/gpio/gpio-test/gpio-test.c
@@ -27,7 +27,9 @@
     gpio_protocol_t gpio;
     uint32_t gpio_count;
     thrd_t thread;
+    thrd_t wait;
     bool done;
+    zx_handle_t inth;
 } gpio_test_t;
 
 static void gpio_test_release(void* ctx) {
@@ -68,6 +70,61 @@
     return 0;
 }
 
+// GPIO indices (based on gpio_test_gpios)
+enum {
+    GPIO_LED,
+    GPIO_BUTTON,
+    GPIO_OUT_LED,
+};
+
+static int gpio_waiting_thread(void *arg) {
+    gpio_test_t* gpio_test = arg;
+    gpio_protocol_t* gpio = &gpio_test->gpio;
+    while(1) {
+        printf("gpio_waiting_thread Before WAITING %x\n", gpio_test->inth);
+#if ENABLE_NEW_IRQ_API
+        zx_status_t status = zx_irq_wait(gpio_test->inth, NULL);
+#else
+        uint64_t slots;
+        zx_status_t status = zx_interrupt_wait(gpio_test->inth, &slots);
+#endif
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "gpio_waiting_thread: Waiting failed %d\n", status);
+            return -1;
+        }
+        printf("gpio_waiting_thread After WAITING %x\n", gpio_test->inth);
+        uint8_t out;
+        gpio_read(gpio, GPIO_OUT_LED, &out);
+        gpio_write(gpio, GPIO_OUT_LED, !out);
+        sleep(1);
+    }
+}
+
+// test thread that cycles all of the GPIOs provided to us
+static int gpio_interrupt_test(void *arg) {
+    gpio_test_t* gpio_test = arg;
+    gpio_protocol_t* gpio = &gpio_test->gpio;
+    zx_status_t status;
+
+    if (ZX_OK != (status = gpio_config(gpio, GPIO_OUT_LED, GPIO_DIR_OUT))) {
+        zxlogf(ERROR, "gpio_interrupt_test: gpio_config failed for gpio %u\n", GPIO_LED);
+        return -1;
+    }
+    if (gpio_get_interrupt(gpio, GPIO_BUTTON,
+                           ZX_INTERRUPT_MODE_EDGE_HIGH, &gpio_test->inth) != ZX_OK) {
+        zxlogf(ERROR, "gpio_interrupt_test: gpio_config failed for gpio %u\n", GPIO_BUTTON);
+        return -1;
+    }
+
+    if (gpio_config(gpio, GPIO_BUTTON, GPIO_DIR_IN | GPIO_PULL_DOWN) != ZX_OK) {
+        zxlogf(ERROR, "gpio_interrupt_test: gpio_config failed for gpio %u status %d\n", GPIO_BUTTON, status);
+        return -1;
+    }
+
+    thrd_create_with_name(&gpio_test->wait, gpio_waiting_thread, gpio_test, "gpio_waiting_thread");
+    return 0;
+}
+
 static zx_status_t gpio_test_bind(void* ctx, zx_device_t* parent) {
     gpio_test_t* gpio_test = calloc(1, sizeof(gpio_test_t));
     if (!gpio_test) {
@@ -107,6 +164,7 @@
     }
 
     thrd_create_with_name(&gpio_test->thread, gpio_test_thread, gpio_test, "gpio_test_thread");
+    //thrd_create_with_name(&gpio_test->thread, gpio_interrupt_test, gpio_test, "gpio_interrupt_test");
     return ZX_OK;
 }
 
diff --git a/system/dev/soc/amlogic/include/soc/aml-s912/s912-gpio.h b/system/dev/soc/amlogic/include/soc/aml-s912/s912-gpio.h
index f5b6905..78cddf9 100644
--- a/system/dev/soc/amlogic/include/soc/aml-s912/s912-gpio.h
+++ b/system/dev/soc/amlogic/include/soc/aml-s912/s912-gpio.h
@@ -6,7 +6,7 @@
 
 #define S912_GPIOX_PINS     19
 #define S912_GPIODV_PINS    30
-#define S912_GPIOH_PINS     32
+#define S912_GPIOH_PINS     10
 #define S912_GPIOBOOT_PINS  16
 #define S912_GPIOCARD_PINS  7
 #define S912_GPIOCLK_PINS   2
@@ -64,6 +64,27 @@
 #define S912_PERIPHS_PIN_MUX_8 0x34
 #define S912_PERIPHS_PIN_MUX_9 0x35
 
+#define S912_PULL_UP_REG0   0x3A
+#define S912_PULL_UP_REG1   0x3B
+#define S912_PULL_UP_REG2   0x3C
+#define S912_PULL_UP_REG3   0x3D
+#define S912_PULL_UP_REG4   0x3E
+
+#define S912_PULL_UP_EN_REG0   0x48
+#define S912_PULL_UP_EN_REG1   0x49
+#define S912_PULL_UP_EN_REG2   0x4A
+#define S912_PULL_UP_EN_REG3   0x4B
+#define S912_PULL_UP_EN_REG4   0x4C
+
+#define S912_GPIOA0_PIN_START      0
+#define S912_GPIOZ_PIN_START       10
+#define S912_GPIOH_PIN_START       26
+#define S912_GPIOBOOT_PIN_START    36
+#define S912_GPIOCARD_PIN_START    52
+#define S912_GPIODV_PIN_START      59
+#define S912_GPIOX_PIN_START       89
+#define S912_GPIOCLK_PIN_START     108
+
 // GPIO AO registers live in a seperate register bank.
 #define S912_AO_RTI_PIN_MUX_REG  0x05
 #define S912_AO_RTI_PIN_MUX_REG2 0x06
diff --git a/system/dev/soc/amlogic/include/soc/aml-s912/s912-hw.h b/system/dev/soc/amlogic/include/soc/aml-s912/s912-hw.h
index 3271903..f32d6f4 100644
--- a/system/dev/soc/amlogic/include/soc/aml-s912/s912-hw.h
+++ b/system/dev/soc/amlogic/include/soc/aml-s912/s912-hw.h
@@ -26,6 +26,8 @@
 #define S912_GPIO_LENGTH                0x1C00
 #define S912_GPIO_A0_BASE               0xc8100000
 #define S912_GPIO_AO_LENGTH             0x1000
+#define S912_GPIO_INTERRUPT_BASE        0xC1100000
+#define S912_GPIO_INTERRUPT_LENGTH      0x10000
 
 #define S912_I2C_A_BASE                 0xc1108500
 #define S912_I2C_A_LENGTH               0x20
@@ -47,7 +49,7 @@
 #define S912_UART_A_BASE                0xc11084c0
 #define S912_UART_A_LENGTH              0x18
 #define S912_UART_AO_B_BASE             0xc81004e0
-#define S912_UART_AO_B_LENGTH          0x18
+#define S912_UART_AO_B_LENGTH           0x18
 
 // IRQs
 #define S912_M_I2C_0_IRQ                53
diff --git a/system/ulib/ddk/include/ddk/protocol/gpio.h b/system/ulib/ddk/include/ddk/protocol/gpio.h
index 70e08b3..c192bdb 100644
--- a/system/ulib/ddk/include/ddk/protocol/gpio.h
+++ b/system/ulib/ddk/include/ddk/protocol/gpio.h
@@ -26,6 +26,11 @@
     // for level triggered
     GPIO_TRIGGER_HIGH       = 1 << 2,
     GPIO_TRIGGER_LOW        = 1 << 3,
+
+    // for pull-up/pull-down
+    GPIO_PULL_DOWN            = 0 << 4,
+    GPIO_PULL_UP              = 1 << 4,
+    GPIO_PULL_MASK            = 1 << 4,
 };
 
 // In the functions below, the GPIO index is relative to the list of GPIOs for the device.
@@ -38,6 +43,7 @@
     zx_status_t (*set_alt_function)(void* ctx, uint32_t index, uint64_t function);
     zx_status_t (*read)(void* ctx, uint32_t index, uint8_t* out_value);
     zx_status_t (*write)(void* ctx, uint32_t index, uint8_t value);
+    zx_status_t (*get_interrupt)(void *ctx, uint32_t pin, uint32_t flags, zx_handle_t *out_handle);
 } gpio_protocol_ops_t;
 
 typedef struct {
@@ -68,4 +74,9 @@
     return gpio->ops->write(gpio->ctx, index, value);
 }
 
+// gets an interrupt object pertaining to a particular GPIO pin
+static inline zx_status_t gpio_get_interrupt(gpio_protocol_t* gpio, uint32_t index,
+                                            uint32_t flags, zx_handle_t *out_handle) {
+    return gpio->ops->get_interrupt(gpio->ctx, index, flags, out_handle);
+}
 __END_CDECLS;
diff --git a/system/ulib/ddk/include/ddk/protocol/platform-device.h b/system/ulib/ddk/include/ddk/protocol/platform-device.h
index fd1c689..787e814 100644
--- a/system/ulib/ddk/include/ddk/protocol/platform-device.h
+++ b/system/ulib/ddk/include/ddk/protocol/platform-device.h
@@ -34,7 +34,7 @@
 typedef struct {
     zx_status_t (*map_mmio)(void* ctx, uint32_t index, uint32_t cache_policy, void** out_vaddr,
                             size_t* out_size, zx_handle_t* out_handle);
-    zx_status_t (*map_interrupt)(void* ctx, uint32_t index, zx_handle_t* out_handle);
+    zx_status_t (*map_interrupt)(void* ctx, uint32_t index, uint32_t flags, zx_handle_t* out_handle);
     zx_status_t (*get_bti)(void* ctx, uint32_t index, zx_handle_t* out_handle);
     zx_status_t (*get_device_info)(void* ctx, pdev_device_info_t* out_info);
 } platform_device_protocol_ops_t;
@@ -54,7 +54,14 @@
 // Returns an interrupt handle. "index" is relative to the list of IRQs for the device.
 static inline zx_status_t pdev_map_interrupt(platform_device_protocol_t* pdev, uint32_t index,
                                              zx_handle_t* out_handle) {
-    return pdev->ops->map_interrupt(pdev->ctx, index, out_handle);
+    return pdev->ops->map_interrupt(pdev->ctx, index, 0, out_handle);
+}
+
+// Returns an interrupt handle. "index" is relative to the list of IRQs for the device.
+// This API allows user to specify the mode
+static inline zx_status_t pdev_get_interrupt(platform_device_protocol_t* pdev, uint32_t index,
+                                             uint32_t flags, zx_handle_t* out_handle) {
+    return pdev->ops->map_interrupt(pdev->ctx, index, flags, out_handle);
 }
 
 // Returns an IOMMU bus transaction initiator handle.