gpio-button-hotplug: support interrupt properties
[openwrt/openwrt.git] / package / kernel / gpio-button-hotplug / src / gpio-button-hotplug.c
index 93fc047d08f11a7e0470584782a7e46ab95eeca1..11c914d4ef3cc81ec06f7a189756e5bd811a0ba3 100644 (file)
@@ -26,6 +26,7 @@
 #include <linux/interrupt.h>
 #include <linux/platform_device.h>
 #include <linux/of_gpio.h>
+#include <linux/of_irq.h>
 #include <linux/gpio_keys.h>
 
 #define DRV_NAME       "gpio-keys"
@@ -70,7 +71,10 @@ struct gpio_keys_button_data {
        int count;
        int threshold;
        int can_sleep;
-       struct gpio_keys_button *b;
+       int irq;
+       unsigned int software_debounce;
+       struct gpio_desc *gpiod;
+       const struct gpio_keys_button *b;
 };
 
 extern u64 uevent_next_seqnum(void);
@@ -82,21 +86,31 @@ extern u64 uevent_next_seqnum(void);
        }
 
 static struct bh_map button_map[] = {
-       BH_MAP(BTN_0,           "BTN_0"),
-       BH_MAP(BTN_1,           "BTN_1"),
-       BH_MAP(BTN_2,           "BTN_2"),
-       BH_MAP(BTN_3,           "BTN_3"),
-       BH_MAP(BTN_4,           "BTN_4"),
-       BH_MAP(BTN_5,           "BTN_5"),
-       BH_MAP(BTN_6,           "BTN_6"),
-       BH_MAP(BTN_7,           "BTN_7"),
-       BH_MAP(BTN_8,           "BTN_8"),
-       BH_MAP(BTN_9,           "BTN_9"),
-       BH_MAP(KEY_POWER,       "power"),
-       BH_MAP(KEY_RESTART,     "reset"),
-       BH_MAP(KEY_RFKILL,      "rfkill"),
-       BH_MAP(KEY_WPS_BUTTON,  "wps"),
-       BH_MAP(KEY_WIMAX,       "wwan"),
+       BH_MAP(BTN_0,                   "BTN_0"),
+       BH_MAP(BTN_1,                   "BTN_1"),
+       BH_MAP(BTN_2,                   "BTN_2"),
+       BH_MAP(BTN_3,                   "BTN_3"),
+       BH_MAP(BTN_4,                   "BTN_4"),
+       BH_MAP(BTN_5,                   "BTN_5"),
+       BH_MAP(BTN_6,                   "BTN_6"),
+       BH_MAP(BTN_7,                   "BTN_7"),
+       BH_MAP(BTN_8,                   "BTN_8"),
+       BH_MAP(BTN_9,                   "BTN_9"),
+       BH_MAP(KEY_BRIGHTNESS_ZERO,     "brightness_zero"),
+       BH_MAP(KEY_CONFIG,              "config"),
+       BH_MAP(KEY_COPY,                "copy"),
+       BH_MAP(KEY_EJECTCD,             "eject"),
+       BH_MAP(KEY_HELP,                "help"),
+       BH_MAP(KEY_LIGHTS_TOGGLE,       "lights_toggle"),
+       BH_MAP(KEY_PHONE,               "phone"),
+       BH_MAP(KEY_POWER,               "power"),
+       BH_MAP(KEY_POWER2,              "reboot"),
+       BH_MAP(KEY_RESTART,             "reset"),
+       BH_MAP(KEY_RFKILL,              "rfkill"),
+       BH_MAP(KEY_VIDEO,               "video"),
+       BH_MAP(KEY_WIMAX,               "wwan"),
+       BH_MAP(KEY_WLAN,                "wlan"),
+       BH_MAP(KEY_WPS_BUTTON,          "wps"),
 };
 
 /* -------------------------------------------------------------------------*/
@@ -288,7 +302,7 @@ static void gpio_keys_polled_check_state(struct gpio_keys_button_data *bdata)
                        return;
                }
 
-               if ((bdata->last_state != -1) || (type == EV_SW))
+               if (bdata->last_state != -1 || type == EV_SW)
                        button_hotplug_event(bdata, type, state);
 
                bdata->last_state = state;
@@ -330,11 +344,29 @@ static void gpio_keys_polled_close(struct gpio_keys_button_dev *bdev)
                pdata->disable(bdev->dev);
 }
 
+static void gpio_keys_irq_work_func(struct work_struct *work)
+{
+       struct gpio_keys_button_data *bdata = container_of(work,
+               struct gpio_keys_button_data, work.work);
+       int state = gpio_button_get_value(bdata);
+
+       if (state != bdata->last_state) {
+               unsigned int type = bdata->b->type ?: EV_KEY;
+
+               if (bdata->last_state != -1 || type == EV_SW)
+                       button_hotplug_event(bdata, type, state);
+
+               bdata->last_state = state;
+       }
+}
+
 static irqreturn_t button_handle_irq(int irq, void *_bdata)
 {
-       struct gpio_keys_button_data *bdata = (struct gpio_keys_button_data *) _bdata;
+       struct gpio_keys_button_data *bdata =
+               (struct gpio_keys_button_data *) _bdata;
 
-       button_hotplug_event(bdata, bdata->b->type ?: EV_KEY, gpio_button_get_value(bdata));
+       schedule_delayed_work(&bdata->work,
+                             msecs_to_jiffies(bdata->software_debounce));
 
        return IRQ_HANDLED;
 }
@@ -380,7 +412,9 @@ gpio_keys_get_devtree_pdata(struct device *dev)
                        continue;
                }
 
-               button = &pdata->buttons[i++];
+               button = (struct gpio_keys_button *)(&pdata->buttons[i++]);
+
+               button->irq = irq_of_parse_and_map(pp, 0);
 
                button->gpio = of_get_gpio_flags(pp, 0, &flags);
                if (button->gpio < 0) {
@@ -465,7 +499,6 @@ static int gpio_keys_button_probe(struct platform_device *pdev,
                        dev_err(dev, "missing platform data\n");
                        return -EINVAL;
                }
-               pdev->dev.platform_data = pdata;
        }
 
        if (polled && !pdata->poll_interval) {
@@ -508,6 +541,9 @@ static int gpio_keys_button_probe(struct platform_device *pdev,
                                gpio, error);
                        return error;
                }
+               bdata->gpiod = gpio_to_desc(gpio);
+               if (!bdata->gpiod)
+                       return -EINVAL;
 
                error = gpio_direction_input(gpio);
                if (error) {
@@ -520,12 +556,26 @@ static int gpio_keys_button_probe(struct platform_device *pdev,
                bdata->can_sleep = gpio_cansleep(gpio);
                bdata->last_state = -1;
 
-               if (bdev->polled)
+               if (bdev->polled) {
                        bdata->threshold = DIV_ROUND_UP(button->debounce_interval,
-                                               pdata->poll_interval);
-               else
+                                                       pdata->poll_interval);
+               } else {
                        bdata->threshold = 1;
 
+                       if (button->debounce_interval) {
+                               error = gpiod_set_debounce(bdata->gpiod,
+                                       button->debounce_interval * 1000);
+                               /*
+                                * use timer if gpiolib doesn't provide
+                                * debounce.
+                                */
+                               if (error < 0) {
+                                       bdata->software_debounce =
+                                               button->debounce_interval;
+                               }
+                       }
+               }
+
                bdata->b = &pdata->buttons[i];
        }
 
@@ -550,25 +600,41 @@ static int gpio_keys_probe(struct platform_device *pdev)
        if (ret)
                return ret;
 
-       pdata = pdev->dev.platform_data;
+       pdata = bdev->pdata;
        for (i = 0; i < pdata->nbuttons; i++) {
-               struct gpio_keys_button *button = &pdata->buttons[i];
+               const struct gpio_keys_button *button = &pdata->buttons[i];
                struct gpio_keys_button_data *bdata = &bdev->data[i];
+               unsigned long irqflags = IRQF_ONESHOT;
 
-               if (!button->irq)
-                       button->irq = gpio_to_irq(button->gpio);
-               if (button->irq < 0) {
-                       dev_err(&pdev->dev, "failed to get irq for gpio:%d\n", button->gpio);
-                       continue;
+               if (!button->irq) {
+                       bdata->irq = gpio_to_irq(button->gpio);
+
+                       if (bdata->irq < 0) {
+                               dev_err(&pdev->dev, "failed to get irq for gpio:%d\n",
+                                       button->gpio);
+                               continue;
+                       }
+
+                       irqflags |= IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
+               } else {
+                       bdata->irq = button->irq;
                }
 
-               ret = devm_request_threaded_irq(&pdev->dev, button->irq, NULL, button_handle_irq,
-                                               IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
-                                               dev_name(&pdev->dev), bdata);
-               if (ret < 0)
-                       dev_err(&pdev->dev, "failed to request irq:%d for gpio:%d\n", button->irq, button->gpio);
-               else
-                       dev_dbg(&pdev->dev, "gpio:%d has irq:%d\n", button->gpio, button->irq);
+               INIT_DELAYED_WORK(&bdata->work, gpio_keys_irq_work_func);
+
+               ret = devm_request_threaded_irq(&pdev->dev,
+                       bdata->irq, NULL, button_handle_irq,
+                       irqflags, dev_name(&pdev->dev), bdata);
+
+               if (ret < 0) {
+                       bdata->irq = 0;
+                       dev_err(&pdev->dev, "failed to request irq:%d for gpio:%d\n",
+                               bdata->irq, button->gpio);
+                       continue;
+               } else {
+                       dev_dbg(&pdev->dev, "gpio:%d has irq:%d\n",
+                               button->gpio, bdata->irq);
+               }
 
                if (bdata->b->type == EV_SW)
                        button_hotplug_event(bdata, EV_SW, gpio_button_get_value(bdata));
@@ -604,6 +670,19 @@ static int gpio_keys_polled_probe(struct platform_device *pdev)
        return ret;
 }
 
+static void gpio_keys_irq_close(struct gpio_keys_button_dev *bdev)
+{
+       struct gpio_keys_platform_data *pdata = bdev->pdata;
+       size_t i;
+
+       for (i = 0; i < pdata->nbuttons; i++) {
+               struct gpio_keys_button_data *bdata = &bdev->data[i];
+
+               disable_irq(bdata->irq);
+               cancel_delayed_work_sync(&bdata->work);
+       }
+}
+
 static int gpio_keys_remove(struct platform_device *pdev)
 {
        struct gpio_keys_button_dev *bdev = platform_get_drvdata(pdev);
@@ -612,6 +691,8 @@ static int gpio_keys_remove(struct platform_device *pdev)
 
        if (bdev->polled)
                gpio_keys_polled_close(bdev);
+       else
+               gpio_keys_irq_close(bdev);
 
        return 0;
 }