]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
gpio: dwapb: Defer clock gating until noirq
authorJia Wang <wangjia@ultrarisc.com>
Thu, 2 Jul 2026 09:22:12 +0000 (17:22 +0800)
committerBartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
Tue, 7 Jul 2026 12:30:50 +0000 (14:30 +0200)
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: 6437c7ba69c3 ("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>
drivers/gpio/gpio-dwapb.c

index 7b92b233fafe6d3f37a8239fa983530f68f254a3..21e39fb940fefdb39afe174f3752a78f7da388ff 100644 (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,11 +786,38 @@ 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);
+                       }
+               }
+       }
+
+       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;
 
-                               /* Mask out interrupts */
+                       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);
@@ -782,18 +825,27 @@ static int dwapb_gpio_suspend(struct device *dev)
        return 0;
 }
 
-static int dwapb_gpio_resume(struct device *dev)
+static int dwapb_gpio_resume_noirq(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;
+       int err;
+
+       if (gpio->clocks_on_for_wake)
+               return 0;
 
        err = clk_bulk_prepare_enable(DWAPB_NR_CLOCKS, gpio->clks);
-       if (err) {
+       if (err)
                dev_err(gpio->dev, "Cannot reenable APB/Debounce clocks\n");
-               return err;
-       }
+
+       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;
 
        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         = {