Commit 3717194f authored by Tony Lindgren's avatar Tony Lindgren Committed by Dmitry Torokhov

Input: gpio-keys - add system suspend support for dedicated wakeirqs

Some SoCs have a separate dedicated wake-up interrupt controller that can
be used to wake up the system from deeper idle states. We already support
configuring a separate interrupt for a gpio-keys button to be used with a
gpio line. However, we are lacking support system suspend for cases where
a separate interrupt needs to be used in deeper sleep modes.

Because of it's nature, gpio-keys does not know about the runtime PM state
of the button gpios, and may have several gpio buttons configured for each
gpio-keys device instance. Implementing runtime PM support for gpio-keys
does not help, and we cannot use drivers/base/power/wakeirq.c support. We
need to implement custom wakeirq support for gpio-keys.

For handling a dedicated wakeirq for system suspend, we enable and disable
it with gpio_keys_enable_wakeup() and gpio_keys_disable_wakeup() that we
already use based on device_may_wakeup().

Some systems may have a dedicated wakeirq that can also be used as the
main interrupt, this is already working for gpio-keys. Let's add some
wakeirq related comments while at it as the usage with a gpio line and
separate interrupt line may not be obvious.
Tested-by: default avatarDhruva Gole <d-gole@ti.com>
Signed-off-by: default avatarTony Lindgren <tony@atomide.com>
Link: https://lore.kernel.org/r/20231129110618.27551-2-tony@atomide.comSigned-off-by: default avatarDmitry Torokhov <dmitry.torokhov@gmail.com>
parent d2ff98b7
...@@ -45,7 +45,9 @@ struct gpio_button_data { ...@@ -45,7 +45,9 @@ 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 wakeirq;
unsigned int wakeup_trigger_type; unsigned int wakeup_trigger_type;
spinlock_t lock; spinlock_t lock;
bool disabled; bool disabled;
bool key_pressed; bool key_pressed;
...@@ -511,6 +513,7 @@ static int gpio_keys_setup_key(struct platform_device *pdev, ...@@ -511,6 +513,7 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
struct gpio_button_data *bdata = &ddata->data[idx]; struct gpio_button_data *bdata = &ddata->data[idx];
irq_handler_t isr; irq_handler_t isr;
unsigned long irqflags; unsigned long irqflags;
const char *wakedesc;
int irq; int irq;
int error; int error;
...@@ -575,6 +578,14 @@ static int gpio_keys_setup_key(struct platform_device *pdev, ...@@ -575,6 +578,14 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
!gpiod_cansleep(bdata->gpiod); !gpiod_cansleep(bdata->gpiod);
} }
/*
* If an interrupt was specified, use it instead of the gpio
* interrupt and use the gpio for reading the state. A separate
* interrupt may be used as the main button interrupt for
* runtime PM to detect events also in deeper idle states. If a
* dedicated wakeirq is used for system suspend only, see below
* for bdata->wakeirq setup.
*/
if (button->irq) { if (button->irq) {
bdata->irq = button->irq; bdata->irq = button->irq;
} else { } else {
...@@ -672,6 +683,36 @@ static int gpio_keys_setup_key(struct platform_device *pdev, ...@@ -672,6 +683,36 @@ static int gpio_keys_setup_key(struct platform_device *pdev,
return error; return error;
} }
if (!button->wakeirq)
return 0;
/* Use :wakeup suffix like drivers/base/power/wakeirq.c does */
wakedesc = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", desc);
if (!wakedesc)
return -ENOMEM;
bdata->wakeirq = button->wakeirq;
irqflags |= IRQF_NO_SUSPEND;
/*
* Wakeirq shares the handler with the main interrupt, it's only
* active during system suspend. See gpio_keys_button_enable_wakeup()
* and gpio_keys_button_disable_wakeup().
*/
error = devm_request_any_context_irq(dev, bdata->wakeirq, isr,
irqflags, wakedesc, bdata);
if (error < 0) {
dev_err(dev, "Unable to claim wakeirq %d; error %d\n",
bdata->irq, error);
return error;
}
/*
* Disable wakeirq until suspend. IRQF_NO_AUTOEN won't work if
* IRQF_SHARED was set based on !button->can_disable.
*/
disable_irq(bdata->wakeirq);
return 0; return 0;
} }
...@@ -728,7 +769,7 @@ gpio_keys_get_devtree_pdata(struct device *dev) ...@@ -728,7 +769,7 @@ gpio_keys_get_devtree_pdata(struct device *dev)
struct gpio_keys_platform_data *pdata; struct gpio_keys_platform_data *pdata;
struct gpio_keys_button *button; struct gpio_keys_button *button;
struct fwnode_handle *child; struct fwnode_handle *child;
int nbuttons; int nbuttons, irq;
nbuttons = device_get_child_node_count(dev); nbuttons = device_get_child_node_count(dev);
if (nbuttons == 0) if (nbuttons == 0)
...@@ -750,9 +791,19 @@ gpio_keys_get_devtree_pdata(struct device *dev) ...@@ -750,9 +791,19 @@ gpio_keys_get_devtree_pdata(struct device *dev)
device_property_read_string(dev, "label", &pdata->name); device_property_read_string(dev, "label", &pdata->name);
device_for_each_child_node(dev, child) { device_for_each_child_node(dev, child) {
if (is_of_node(child)) if (is_of_node(child)) {
button->irq = irq = of_irq_get_byname(to_of_node(child), "irq");
irq_of_parse_and_map(to_of_node(child), 0); if (irq > 0)
button->irq = irq;
irq = of_irq_get_byname(to_of_node(child), "wakeup");
if (irq > 0)
button->wakeirq = irq;
if (!button->irq && !button->wakeirq)
button->irq =
irq_of_parse_and_map(to_of_node(child), 0);
}
if (fwnode_property_read_u32(child, "linux,code", if (fwnode_property_read_u32(child, "linux,code",
&button->code)) { &button->code)) {
...@@ -921,6 +972,11 @@ gpio_keys_button_enable_wakeup(struct gpio_button_data *bdata) ...@@ -921,6 +972,11 @@ gpio_keys_button_enable_wakeup(struct gpio_button_data *bdata)
} }
} }
if (bdata->wakeirq) {
enable_irq(bdata->wakeirq);
disable_irq(bdata->irq);
}
return 0; return 0;
} }
...@@ -929,6 +985,11 @@ gpio_keys_button_disable_wakeup(struct gpio_button_data *bdata) ...@@ -929,6 +985,11 @@ gpio_keys_button_disable_wakeup(struct gpio_button_data *bdata)
{ {
int error; int error;
if (bdata->wakeirq) {
enable_irq(bdata->irq);
disable_irq(bdata->wakeirq);
}
/* /*
* The trigger type is always both edges for gpio-based keys and we do * The trigger type is always both edges for gpio-based keys and we do
* not support changing wakeup trigger for interrupt-based keys. * not support changing wakeup trigger for interrupt-based keys.
......
...@@ -21,6 +21,7 @@ struct device; ...@@ -21,6 +21,7 @@ struct device;
* disable button via sysfs * disable button via sysfs
* @value: axis value for %EV_ABS * @value: axis value for %EV_ABS
* @irq: Irq number in case of interrupt keys * @irq: Irq number in case of interrupt keys
* @wakeirq: Optional dedicated wake-up interrupt
*/ */
struct gpio_keys_button { struct gpio_keys_button {
unsigned int code; unsigned int code;
...@@ -34,6 +35,7 @@ struct gpio_keys_button { ...@@ -34,6 +35,7 @@ struct gpio_keys_button {
bool can_disable; bool can_disable;
int value; int value;
unsigned int irq; unsigned int irq;
unsigned int wakeirq;
}; };
/** /**
......
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