gpio: dwapb: Defer clock gating until noirq

GPIO consumers such as gpio-keys can enable IRQ wake and adjust the wake
trigger type from their suspend callbacks. If the DWAPB controller suspends
first, masking interrupts and disabling its clocks in the normal suspend
phase prevents that late wake configuration from reliably reaching the
hardware. Systems with real DWAPB bus clocks then fail to wake from s2idle
through GPIO keys.

Save the register context in the normal suspend callback, but defer IRQ
masking and clock gating until suspend_noirq. At that point all consumers
have finished configuring wake IRQs, so keep the clocks enabled when wake
lines are armed and only gate them when no wake source is active.
Resume_noirq reenables clocks, if they were gated, before the normal resume
path restores registers.

Propagate wake requests to the parent irqchip while keeping the local wake
mask in sync with failures.

Fixes: 6437c7ba69 ("gpio: dwapb: Add wakeup source support")
Signed-off-by: Jia Wang <wangjia@ultrarisc.com>
Link: https://patch.msgid.link/20260702-gpio-dwapb-wakeup-v2-1-203f2f33429f@ultrarisc.com
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
This commit is contained in:
Jia Wang 2026-07-02 17:22:12 +08:00 committed by Bartosz Golaszewski
parent b30973e8c3
commit d775b9451e

View File

@ -117,6 +117,7 @@ struct dwapb_gpio {
unsigned int flags;
struct reset_control *rst;
struct clk_bulk_data clks[DWAPB_NR_CLOCKS];
bool clocks_on_for_wake;
struct dwapb_gpio_port ports[] __counted_by(nr_ports);
};
@ -364,11 +365,24 @@ static int dwapb_irq_set_wake(struct irq_data *d, unsigned int enable)
struct dwapb_gpio *gpio = to_dwapb_gpio(gc);
struct dwapb_context *ctx = gpio->ports[0].ctx;
irq_hw_number_t bit = irqd_to_hwirq(d);
u32 wake_en = ctx->wake_en;
if (enable)
ctx->wake_en |= BIT(bit);
wake_en |= BIT(bit);
else
ctx->wake_en &= ~BIT(bit);
wake_en &= ~BIT(bit);
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
if (d->parent_data && !!ctx->wake_en != !!wake_en) {
int err;
err = irq_chip_set_wake_parent(d, enable);
if (err)
return err;
}
#endif
ctx->wake_en = wake_en;
return 0;
}
@ -749,6 +763,8 @@ static int dwapb_gpio_suspend(struct device *dev)
int i;
scoped_guard(gpio_generic_lock_irqsave, gen_gc) {
gpio->clocks_on_for_wake = false;
for (i = 0; i < gpio->nr_ports; i++) {
unsigned int offset;
unsigned int idx = gpio->ports[i].idx;
@ -770,30 +786,66 @@ static int dwapb_gpio_suspend(struct device *dev)
ctx->int_pol = dwapb_read(gpio, GPIO_INT_POLARITY);
ctx->int_type = dwapb_read(gpio, GPIO_INTTYPE_LEVEL);
ctx->int_deb = dwapb_read(gpio, GPIO_PORTA_DEBOUNCE);
/* Mask out interrupts */
dwapb_write(gpio, GPIO_INTMASK, ~ctx->wake_en);
}
}
}
return 0;
}
static int dwapb_gpio_suspend_noirq(struct device *dev)
{
struct dwapb_gpio *gpio = dev_get_drvdata(dev);
struct gpio_generic_chip *gen_gc = &gpio->ports[0].chip;
bool wake_enabled = false;
int i;
scoped_guard(gpio_generic_lock_irqsave, gen_gc) {
for (i = 0; i < gpio->nr_ports; i++) {
unsigned int idx = gpio->ports[i].idx;
struct dwapb_context *ctx = gpio->ports[i].ctx;
if (idx == 0) {
wake_enabled = ctx->wake_en;
dwapb_write(gpio, GPIO_INTMASK, ~ctx->wake_en);
break;
}
}
gpio->clocks_on_for_wake = wake_enabled;
}
if (wake_enabled) {
device_set_wakeup_path(dev);
return 0;
}
clk_bulk_disable_unprepare(DWAPB_NR_CLOCKS, gpio->clks);
return 0;
}
static int dwapb_gpio_resume_noirq(struct device *dev)
{
struct dwapb_gpio *gpio = dev_get_drvdata(dev);
int err;
if (gpio->clocks_on_for_wake)
return 0;
err = clk_bulk_prepare_enable(DWAPB_NR_CLOCKS, gpio->clks);
if (err)
dev_err(gpio->dev, "Cannot reenable APB/Debounce clocks\n");
return err;
}
static int dwapb_gpio_resume(struct device *dev)
{
struct dwapb_gpio *gpio = dev_get_drvdata(dev);
struct gpio_chip *gc = &gpio->ports[0].chip.gc;
struct gpio_generic_chip *gen_gc = to_gpio_generic_chip(gc);
int i, err;
err = clk_bulk_prepare_enable(DWAPB_NR_CLOCKS, gpio->clks);
if (err) {
dev_err(gpio->dev, "Cannot reenable APB/Debounce clocks\n");
return err;
}
int i;
guard(gpio_generic_lock_irqsave)(gen_gc);
@ -827,8 +879,11 @@ static int dwapb_gpio_resume(struct device *dev)
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(dwapb_gpio_pm_ops,
dwapb_gpio_suspend, dwapb_gpio_resume);
static const struct dev_pm_ops dwapb_gpio_pm_ops = {
SYSTEM_SLEEP_PM_OPS(dwapb_gpio_suspend, dwapb_gpio_resume)
NOIRQ_SYSTEM_SLEEP_PM_OPS(dwapb_gpio_suspend_noirq,
dwapb_gpio_resume_noirq)
};
static struct platform_driver dwapb_gpio_driver = {
.driver = {