Skip to content

Commit

Permalink
Input: gpio-keys - add support for wakeup event action
Browse files Browse the repository at this point in the history
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: Brian Norris <[email protected]>
Signed-off-by: Jeffy Chen <[email protected]>
Reviewed-by: Rob Herring <[email protected]>
Signed-off-by: Dmitry Torokhov <[email protected]>
  • Loading branch information
JeffyCN authored and dtor committed Mar 14, 2018
1 parent 7085123 commit 83fc580
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 14 deletions.
8 changes: 8 additions & 0 deletions Documentation/devicetree/bindings/input/gpio-keys.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ Optional subnode-properties:
If not specified defaults to 5.
- wakeup-source: Boolean, button can wake-up the system.
(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
to dedicated (not shared) interrupt which can be disabled to
suppress events from the button.
Expand Down
145 changes: 131 additions & 14 deletions drivers/input/keyboard/gpio_keys.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
#include <dt-bindings/input/gpio-keys.h>

struct gpio_button_data {
const struct gpio_keys_button *button;
Expand All @@ -45,6 +46,7 @@ struct gpio_button_data {
unsigned int software_debounce; /* in msecs, for GPIO-driven buttons */

unsigned int irq;
unsigned int wakeup_trigger_type;
spinlock_t lock;
bool disabled;
bool key_pressed;
Expand Down Expand Up @@ -540,6 +542,8 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
}

if (bdata->gpiod) {
bool active_low = gpiod_is_active_low(bdata->gpiod);

if (button->debounce_interval) {
error = gpiod_set_debounce(bdata->gpiod,
button->debounce_interval * 1000);
Expand Down Expand Up @@ -568,6 +572,24 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
isr = gpio_keys_gpio_isr;
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 {
if (!button->irq) {
dev_err(dev, "Found button without gpio or irq\n");
Expand All @@ -586,6 +608,11 @@ static int gpio_keys_setup_key(struct platform_device *pdev,

isr = gpio_keys_irq_isr;
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];
Expand Down Expand Up @@ -718,6 +745,9 @@ gpio_keys_get_devtree_pdata(struct device *dev)
/* legacy name */
fwnode_property_read_bool(child, "gpio-key,wakeup");

fwnode_property_read_u32(child, "wakeup-event-action",
&button->wakeup_event_action);

button->can_disable =
fwnode_property_read_bool(child, "linux,can-disable");

Expand Down Expand Up @@ -845,19 +875,112 @@ static int gpio_keys_probe(struct platform_device *pdev)
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)
{
struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev);
struct input_dev *input = ddata->input;
int i;
int error;

if (device_may_wakeup(dev)) {
for (i = 0; i < ddata->pdata->nbuttons; i++) {
struct gpio_button_data *bdata = &ddata->data[i];
if (bdata->button->wakeup)
enable_irq_wake(bdata->irq);
bdata->suspended = true;
}
error = gpio_keys_enable_wakeup(ddata);
if (error)
return error;
} else {
mutex_lock(&input->mutex);
if (input->users)
Expand All @@ -873,15 +996,9 @@ static int __maybe_unused gpio_keys_resume(struct device *dev)
struct gpio_keys_drvdata *ddata = dev_get_drvdata(dev);
struct input_dev *input = ddata->input;
int error = 0;
int i;

if (device_may_wakeup(dev)) {
for (i = 0; i < ddata->pdata->nbuttons; i++) {
struct gpio_button_data *bdata = &ddata->data[i];
if (bdata->button->wakeup)
disable_irq_wake(bdata->irq);
bdata->suspended = false;
}
gpio_keys_disable_wakeup(ddata);
} else {
mutex_lock(&input->mutex);
if (input->users)
Expand Down
13 changes: 13 additions & 0 deletions include/dt-bindings/input/gpio-keys.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* 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 */
2 changes: 2 additions & 0 deletions include/linux/gpio_keys.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct device;
* @desc: label that will be attached to button's gpio
* @type: input event type (%EV_KEY, %EV_SW, %EV_ABS)
* @wakeup: configure the button as a wake-up source
* @wakeup_event_action: event action to trigger wakeup
* @debounce_interval: debounce ticks interval in msecs
* @can_disable: %true indicates that userspace is allowed to
* disable button via sysfs
Expand All @@ -26,6 +27,7 @@ struct gpio_keys_button {
const char *desc;
unsigned int type;
int wakeup;
int wakeup_event_action;
int debounce_interval;
bool can_disable;
int value;
Expand Down

0 comments on commit 83fc580

Please sign in to comment.