Commit 83fc580d authored by Jeffy Chen's avatar Jeffy Chen Committed by Dmitry Torokhov

Input: gpio-keys - add support for wakeup event action

Add support for specifying event actions to trigger wakeup when using
the gpio-keys input device as a wakeup source.

This would allow the device to configure when to wakeup the system. For
example a gpio-keys input device for pen insert, may only want to wakeup
the system when ejecting the pen.
Suggested-by: default avatarBrian Norris <briannorris@chromium.org>
Signed-off-by: default avatarJeffy Chen <jeffy.chen@rock-chips.com>
Reviewed-by: default avatarRob Herring <robh@kernel.org>
Signed-off-by: default avatarDmitry Torokhov <dmitry.torokhov@gmail.com>
parent 70851233
...@@ -26,6 +26,14 @@ Optional subnode-properties: ...@@ -26,6 +26,14 @@ Optional subnode-properties:
If not specified defaults to 5. If not specified defaults to 5.
- wakeup-source: Boolean, button can wake-up the system. - wakeup-source: Boolean, button can wake-up the system.
(Legacy property supported: "gpio-key,wakeup") (Legacy property supported: "gpio-key,wakeup")
- wakeup-event-action: Specifies whether the key should wake the
system when asserted, when deasserted, or both. This property is
only valid for keys that wake up the system (e.g., when the
"wakeup-source" property is also provided).
Supported values are defined in linux-event-codes.h:
EV_ACT_ASSERTED - asserted
EV_ACT_DEASSERTED - deasserted
EV_ACT_ANY - both asserted and deasserted
- linux,can-disable: Boolean, indicates that button is connected - linux,can-disable: Boolean, indicates that button is connected
to dedicated (not shared) interrupt which can be disabled to to dedicated (not shared) interrupt which can be disabled to
suppress events from the button. suppress events from the button.
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_irq.h> #include <linux/of_irq.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <dt-bindings/input/gpio-keys.h>
struct gpio_button_data { struct gpio_button_data {
const struct gpio_keys_button *button; const struct gpio_keys_button *button;
...@@ -45,6 +46,7 @@ struct gpio_button_data { ...@@ -45,6 +46,7 @@ struct gpio_button_data {
unsigned int software_debounce; /* in msecs, for GPIO-driven buttons */ unsigned int software_debounce; /* in msecs, for GPIO-driven buttons */
unsigned int irq; unsigned int irq;
unsigned int wakeup_trigger_type;
spinlock_t lock; spinlock_t lock;
bool disabled; bool disabled;
bool key_pressed; bool key_pressed;
...@@ -540,6 +542,8 @@ static int gpio_keys_setup_key(struct platform_device *pdev, ...@@ -540,6 +542,8 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
} }
if (bdata->gpiod) { if (bdata->gpiod) {
bool active_low = gpiod_is_active_low(bdata->gpiod);
if (button->debounce_interval) { if (button->debounce_interval) {
error = gpiod_set_debounce(bdata->gpiod, error = gpiod_set_debounce(bdata->gpiod,
button->debounce_interval * 1000); button->debounce_interval * 1000);
...@@ -568,6 +572,24 @@ static int gpio_keys_setup_key(struct platform_device *pdev, ...@@ -568,6 +572,24 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
isr = gpio_keys_gpio_isr; isr = gpio_keys_gpio_isr;
irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
switch (button->wakeup_event_action) {
case EV_ACT_ASSERTED:
bdata->wakeup_trigger_type = active_low ?
IRQ_TYPE_EDGE_FALLING : IRQ_TYPE_EDGE_RISING;
break;
case EV_ACT_DEASSERTED:
bdata->wakeup_trigger_type = active_low ?
IRQ_TYPE_EDGE_RISING : IRQ_TYPE_EDGE_FALLING;
break;
case EV_ACT_ANY:
/* fall through */
default:
/*
* For other cases, we are OK letting suspend/resume
* not reconfigure the trigger type.
*/
break;
}
} else { } else {
if (!button->irq) { if (!button->irq) {
dev_err(dev, "Found button without gpio or irq\n"); dev_err(dev, "Found button without gpio or irq\n");
...@@ -586,6 +608,11 @@ static int gpio_keys_setup_key(struct platform_device *pdev, ...@@ -586,6 +608,11 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
isr = gpio_keys_irq_isr; isr = gpio_keys_irq_isr;
irqflags = 0; irqflags = 0;
/*
* For IRQ buttons, there is no interrupt for release.
* So we don't need to reconfigure the trigger type for wakeup.
*/
} }
bdata->code = &ddata->keymap[idx]; bdata->code = &ddata->keymap[idx];
...@@ -718,6 +745,9 @@ gpio_keys_get_devtree_pdata(struct device *dev) ...@@ -718,6 +745,9 @@ gpio_keys_get_devtree_pdata(struct device *dev)
/* legacy name */ /* legacy name */
fwnode_property_read_bool(child, "gpio-key,wakeup"); fwnode_property_read_bool(child, "gpio-key,wakeup");
fwnode_property_read_u32(child, "wakeup-event-action",
&button->wakeup_event_action);
button->can_disable = button->can_disable =
fwnode_property_read_bool(child, "linux,can-disable"); fwnode_property_read_bool(child, "linux,can-disable");
...@@ -845,19 +875,112 @@ static int gpio_keys_probe(struct platform_device *pdev) ...@@ -845,19 +875,112 @@ static int gpio_keys_probe(struct platform_device *pdev)
return 0; return 0;
} }
static int __maybe_unused
gpio_keys_button_enable_wakeup(struct gpio_button_data *bdata)
{
int error;
error = enable_irq_wake(bdata->irq);
if (error) {
dev_err(bdata->input->dev.parent,
"failed to configure IRQ %d as wakeup source: %d\n",
bdata->irq, error);
return error;
}
if (bdata->wakeup_trigger_type) {
error = irq_set_irq_type(bdata->irq,
bdata->wakeup_trigger_type);
if (error) {
dev_err(bdata->input->dev.parent,
"failed to set wakeup trigger %08x for IRQ %d: %d\n",
bdata->wakeup_trigger_type, bdata->irq, error);
disable_irq_wake(bdata->irq);
return error;
}
}
return 0;
}
static void __maybe_unused
gpio_keys_button_disable_wakeup(struct gpio_button_data *bdata)
{
int error;
/*
* The trigger type is always both edges for gpio-based keys and we do
* not support changing wakeup trigger for interrupt-based keys.
*/
if (bdata->wakeup_trigger_type) {
error = irq_set_irq_type(bdata->irq, IRQ_TYPE_EDGE_BOTH);
if (error)
dev_warn(bdata->input->dev.parent,
"failed to restore interrupt trigger for IRQ %d: %d\n",
bdata->irq, error);
}
error = disable_irq_wake(bdata->irq);
if (error)
dev_warn(bdata->input->dev.parent,
"failed to disable IRQ %d as wake source: %d\n",
bdata->irq, error);
}
static int __maybe_unused
gpio_keys_enable_wakeup(struct gpio_keys_drvdata *ddata)
{
struct gpio_button_data *bdata;
int error;
int i;
for (i = 0; i < ddata->pdata->nbuttons; i++) {
bdata = &ddata->data[i];
if (bdata->button->wakeup) {
error = gpio_keys_button_enable_wakeup(bdata);
if (error)
goto err_out;
}
bdata->suspended = true;
}
return 0;
err_out:
while (i--) {
bdata = &ddata->data[i];
if (bdata->button->wakeup)
gpio_keys_button_disable_wakeup(bdata);
bdata->suspended = false;
}
return error;
}
static void __maybe_unused
gpio_keys_disable_wakeup(struct gpio_keys_drvdata *ddata)
{
struct gpio_button_data *bdata;
int i;
for (i = 0; i < ddata->pdata->nbuttons; i++) {
bdata = &ddata->data[i];
bdata->suspended = false;
if (irqd_is_wakeup_set(irq_get_irq_data(bdata->irq)))
gpio_keys_button_disable_wakeup(bdata);
}
}
static int __maybe_unused gpio_keys_suspend(struct device *dev) static int __maybe_unused gpio_keys_suspend(struct device *dev)
{ {
struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev); struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev);
struct input_dev *input = ddata->input; struct input_dev *input = ddata->input;
int i; int error;
if (device_may_wakeup(dev)) { if (device_may_wakeup(dev)) {
for (i = 0; i < ddata->pdata->nbuttons; i++) { error = gpio_keys_enable_wakeup(ddata);
struct gpio_button_data *bdata = &ddata->data[i]; if (error)
if (bdata->button->wakeup) return error;
enable_irq_wake(bdata->irq);
bdata->suspended = true;
}
} else { } else {
mutex_lock(&input->mutex); mutex_lock(&input->mutex);
if (input->users) if (input->users)
...@@ -873,15 +996,9 @@ static int __maybe_unused gpio_keys_resume(struct device *dev) ...@@ -873,15 +996,9 @@ static int __maybe_unused gpio_keys_resume(struct device *dev)
struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev); struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev);
struct input_dev *input = ddata->input; struct input_dev *input = ddata->input;
int error = 0; int error = 0;
int i;
if (device_may_wakeup(dev)) { if (device_may_wakeup(dev)) {
for (i = 0; i < ddata->pdata->nbuttons; i++) { gpio_keys_disable_wakeup(ddata);
struct gpio_button_data *bdata = &ddata->data[i];
if (bdata->button->wakeup)
disable_irq_wake(bdata->irq);
bdata->suspended = false;
}
} else { } else {
mutex_lock(&input->mutex); mutex_lock(&input->mutex);
if (input->users) if (input->users)
......
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This header provides constants for gpio keys bindings.
*/
#ifndef _DT_BINDINGS_GPIO_KEYS_H
#define _DT_BINDINGS_GPIO_KEYS_H
#define EV_ACT_ANY 0x00 /* asserted or deasserted */
#define EV_ACT_ASSERTED 0x01 /* asserted */
#define EV_ACT_DEASSERTED 0x02 /* deasserted */
#endif /* _DT_BINDINGS_GPIO_KEYS_H */
...@@ -13,6 +13,7 @@ struct device; ...@@ -13,6 +13,7 @@ struct device;
* @desc: label that will be attached to button's gpio * @desc: label that will be attached to button's gpio
* @type: input event type (%EV_KEY, %EV_SW, %EV_ABS) * @type: input event type (%EV_KEY, %EV_SW, %EV_ABS)
* @wakeup: configure the button as a wake-up source * @wakeup: configure the button as a wake-up source
* @wakeup_event_action: event action to trigger wakeup
* @debounce_interval: debounce ticks interval in msecs * @debounce_interval: debounce ticks interval in msecs
* @can_disable: %true indicates that userspace is allowed to * @can_disable: %true indicates that userspace is allowed to
* disable button via sysfs * disable button via sysfs
...@@ -26,6 +27,7 @@ struct gpio_keys_button { ...@@ -26,6 +27,7 @@ struct gpio_keys_button {
const char *desc; const char *desc;
unsigned int type; unsigned int type;
int wakeup; int wakeup;
int wakeup_event_action;
int debounce_interval; int debounce_interval;
bool can_disable; bool can_disable;
int value; int value;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment