diff mbox series

gpio: omap: Add level wakeup handling for omap4 based SoCs

Message ID 20180910200624.4494-1-tony@atomide.com
State New
Headers show
Series gpio: omap: Add level wakeup handling for omap4 based SoCs | expand

Commit Message

Tony Lindgren Sept. 10, 2018, 8:06 p.m. UTC
I noticed that unlike omap2 and 3 based SoCs, omap4 based SoCs keep
the GPIO clocks enabled for GPIO level interrupts with wakeup enabled.
This blocks deeper idle states as the whole domain will stay busy.

The GPIO clock seems to stay enabled if the wakeup register is enabled
and a level interrupt is triggered. In that case the only way to have
the GPIO module idle is to reset it. It is possible this has gone
unnoticed with OSWR (Open SWitch Retention) and off mode during idle
resetting GPIO context most GPIO instances in the earlier Android trees
for example.

Looks like the way to deal with this is to have omap4 based SoCs
only set wake for the duration of idle and clear level registers for
the idle. With level interrupts we can do this as the level interrupt
from device will be still there on resume.

I've taken the long path to fixing this to avoid yet more hard to
read code. I've set up a quirks flag, and a struct for function
pointers so we can use these to clean up other quirk handling easier
in the later patches. The current level quirk handling is moved to
the new functions.

Cc: Grygorii Strashko <grygorii.strashko@ti.com>
Cc: Keerthy <j-keerthy@ti.com>
Cc: Tero Kristo <t-kristo@ti.com>
Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 drivers/gpio/gpio-omap.c                | 145 ++++++++++++++++++------
 include/linux/platform_data/gpio-omap.h |   2 +
 2 files changed, 114 insertions(+), 33 deletions(-)

Comments

Grygorii Strashko Sept. 11, 2018, 4:58 p.m. UTC | #1
Hi Tony,

On 09/10/2018 03:06 PM, Tony Lindgren wrote:
> I noticed that unlike omap2 and 3 based SoCs, omap4 based SoCs keep
> the GPIO clocks enabled for GPIO level interrupts with wakeup enabled.
> This blocks deeper idle states as the whole domain will stay busy.
> 
> The GPIO clock seems to stay enabled if the wakeup register is enabled
> and a level interrupt is triggered. In that case the only way to have
> the GPIO module idle is to reset it. It is possible this has gone
> unnoticed with OSWR (Open SWitch Retention) and off mode during idle
> resetting GPIO context most GPIO instances in the earlier Android trees
> for example.
> 
> Looks like the way to deal with this is to have omap4 based SoCs
> only set wake for the duration of idle and clear level registers for
> the idle. With level interrupts we can do this as the level interrupt
> from device will be still there on resume.
> 
> I've taken the long path to fixing this to avoid yet more hard to
> read code. I've set up a quirks flag, and a struct for function
> pointers so we can use these to clean up other quirk handling easier
> in the later patches. The current level quirk handling is moved to
> the new functions.

I agree with patch in general as it seems will not break anything and
 change localized for omap4 only.
But I'd be very appreciated if you can provide more information about issue
which helps better understand it, so could you, if possible, collect dump of:
- GPIO bank regs right before entering WFI state in Idle (or as close as possible)
  I'm interesting in sysc/s, ctrl, irq's regs (+wakupen) and GPIO_DEBOUNCEN first of all
- GPIO bank CLKCTRL registers (is it "in transition" state).
in non working case.

> 
> Cc: Grygorii Strashko <grygorii.strashko@ti.com>
> Cc: Keerthy <j-keerthy@ti.com>
> Cc: Tero Kristo <t-kristo@ti.com>
> Signed-off-by: Tony Lindgren <tony@atomide.com>
> ---
>   drivers/gpio/gpio-omap.c                | 145 ++++++++++++++++++------
>   include/linux/platform_data/gpio-omap.h |   2 +
>   2 files changed, 114 insertions(+), 33 deletions(-)
>
Tony Lindgren Sept. 11, 2018, 6:24 p.m. UTC | #2
* Grygorii Strashko <grygorii.strashko@ti.com> [180911 17:02]:
> I agree with patch in general as it seems will not break anything and
>  change localized for omap4 only.

OK cool. I'll be posting a related patch to get rid of
the custom PM functions too.

> But I'd be very appreciated if you can provide more information about issue
> which helps better understand it, so could you, if possible, collect dump of:
> - GPIO bank regs right before entering WFI state in Idle (or as close as possible)
>   I'm interesting in sysc/s, ctrl, irq's regs (+wakupen) and GPIO_DEBOUNCEN first of all
> - GPIO bank CLKCTRL registers (is it "in transition" state).
> in non working case.

The thing is there is nothing special in the GPIO registers in the
non-working vs working case. The GPIO_DEBOUNCEN was my first suspect
too, but that's not enabled.

The only difference is that the related CLKSTCTRL CLKACTIVITY bit
for the 32K_GFCLK clock stays stuck showing busy after a level interrupt
and wkup_en set. The 32K_GFCLK bit will only clear after reset of the
GPIO module in question until a new level interrupt is seen.

So with this patch we go from the following dump on omap4 gpio
bank1 running before idle:

GPIO_IRQSTATUS_RAW      0x4a310024 = 0000000000
GPIO_IRQSTATUS_RAW2     0x4a310028 = 0000000000
GPIO_IRQSTATUS_0        0x4a31002c = 0000000000
GPIO_IRQSTATUS_1        0x4a310030 = 0000000000
GPIO_IRQSTATUS_SET_0    0x4a310034 = 0x00000080
GPIO_IRQSTATUS_CLR_0    0x4a31003c = 0x00000080
GPIO_IRQWAKEN_0         0x4a310044 = 0x00000080 <- set
GPIO_LEVELDETECT0       0x4a310140 = 0000000000
GPIO_LEVELDETECT1       0x4a310144 = 0x00000080
GPIO_RISINGDETECT       0x4a310148 = 0000000000
GPIO_FALLINGDETECT      0x4a31014c = 0000000000
GPIO_DEBOUNCENABLE      0x4a310150 = 0000000000
GPIO_DEBOUNCINGTIME     0x4a310154 = 0000000000

to the following dump where GPIO_IRQWAKEN_0 is not set until
for idle:

GPIO_IRQSTATUS_RAW      0x4a310024 = 0000000000
GPIO_IRQSTATUS_RAW2     0x4a310028 = 0000000000
GPIO_IRQSTATUS_0        0x4a31002c = 0000000000
GPIO_IRQSTATUS_1        0x4a310030 = 0000000000
GPIO_IRQSTATUS_SET_0    0x4a310034 = 0x00000080
GPIO_IRQSTATUS_CLR_0    0x4a31003c = 0x00000080
GPIO_IRQWAKEN_0         0x4a310044 = 0000000000 <- cleared
GPIO_LEVELDETECT0       0x4a310140 = 0000000000
GPIO_LEVELDETECT1       0x4a310144 = 0x00000080
GPIO_RISINGDETECT       0x4a310148 = 0000000000
GPIO_FALLINGDETECT      0x4a31014c = 0000000000
GPIO_DEBOUNCENABLE      0x4a310150 = 0000000000
GPIO_DEBOUNCINGTIME     0x4a310154 = 0000000000

Regards,

Tony
Tony Lindgren Sept. 11, 2018, 6:41 p.m. UTC | #3
* Tony Lindgren <tony@atomide.com> [180910 20:10]:
> I noticed that unlike omap2 and 3 based SoCs, omap4 based SoCs keep
> the GPIO clocks enabled for GPIO level interrupts with wakeup enabled.
> This blocks deeper idle states as the whole domain will stay busy.

Linus, assuming people are OK with this one, I'd appreciate an
immutable branch against v4.19-rc1 for this and a follow-up patch
"[PATCH] gpio: omap: Remove custom PM calls and use cpu_pm instead".

That's because of the arch/arm/mach-omap2 dependency that might
conflict for v4.20 (although unlikely).

Regards,

Tony
Grygorii Strashko Sept. 12, 2018, 12:23 a.m. UTC | #4
On 09/11/2018 01:24 PM, Tony Lindgren wrote:
> * Grygorii Strashko <grygorii.strashko@ti.com> [180911 17:02]:
>> I agree with patch in general as it seems will not break anything and
>>   change localized for omap4 only.
> 
> OK cool. I'll be posting a related patch to get rid of
> the custom PM functions too.
> 
>> But I'd be very appreciated if you can provide more information about issue
>> which helps better understand it, so could you, if possible, collect dump of:
>> - GPIO bank regs right before entering WFI state in Idle (or as close as possible)
>>    I'm interesting in sysc/s, ctrl, irq's regs (+wakupen) and GPIO_DEBOUNCEN first of all
>> - GPIO bank CLKCTRL registers (is it "in transition" state).
>> in non working case.
> 
> The thing is there is nothing special in the GPIO registers in the
> non-working vs working case. The GPIO_DEBOUNCEN was my first suspect
> too, but that's not enabled.
> 
> The only difference is that the related CLKSTCTRL CLKACTIVITY bit
> for the 32K_GFCLK clock stays stuck showing busy after a level interrupt
> and wkup_en set. The 32K_GFCLK bit will only clear after reset of the
> GPIO module in question until a new level interrupt is seen.
> 
> So with this patch we go from the following dump on omap4 gpio
> bank1 running before idle:
> 
> GPIO_IRQSTATUS_RAW      0x4a310024 = 0000000000
> GPIO_IRQSTATUS_RAW2     0x4a310028 = 0000000000
> GPIO_IRQSTATUS_0        0x4a31002c = 0000000000
> GPIO_IRQSTATUS_1        0x4a310030 = 0000000000
> GPIO_IRQSTATUS_SET_0    0x4a310034 = 0x00000080
> GPIO_IRQSTATUS_CLR_0    0x4a31003c = 0x00000080
> GPIO_IRQWAKEN_0         0x4a310044 = 0x00000080 <- set
> GPIO_LEVELDETECT0       0x4a310140 = 0000000000
> GPIO_LEVELDETECT1       0x4a310144 = 0x00000080
> GPIO_RISINGDETECT       0x4a310148 = 0000000000
> GPIO_FALLINGDETECT      0x4a31014c = 0000000000
> GPIO_DEBOUNCENABLE      0x4a310150 = 0000000000
> GPIO_DEBOUNCINGTIME     0x4a310154 = 0000000000
> 
> to the following dump where GPIO_IRQWAKEN_0 is not set until
> for idle:
> 
> GPIO_IRQSTATUS_RAW      0x4a310024 = 0000000000
> GPIO_IRQSTATUS_RAW2     0x4a310028 = 0000000000
> GPIO_IRQSTATUS_0        0x4a31002c = 0000000000
> GPIO_IRQSTATUS_1        0x4a310030 = 0000000000
> GPIO_IRQSTATUS_SET_0    0x4a310034 = 0x00000080
> GPIO_IRQSTATUS_CLR_0    0x4a31003c = 0x00000080
> GPIO_IRQWAKEN_0         0x4a310044 = 0000000000 <- cleared
> GPIO_LEVELDETECT0       0x4a310140 = 0000000000
> GPIO_LEVELDETECT1       0x4a310144 = 0x00000080
> GPIO_RISINGDETECT       0x4a310148 = 0000000000
> GPIO_FALLINGDETECT      0x4a31014c = 0000000000
> GPIO_DEBOUNCENABLE      0x4a310150 = 0000000000
> GPIO_DEBOUNCINGTIME     0x4a310154 = 0000000000
> 

Thanks Tony, but it's still not completely clear -
it's important to see values of CLKSTCTRL before/after idle for not 
working case and GPIO SYSC/SYSS registers. There are some combinations 
which may affect on this. So, if you can, pls.
Tony Lindgren Sept. 12, 2018, 12:42 a.m. UTC | #5
* Grygorii Strashko <grygorii.strashko@ti.com> [180912 00:27]:
> Thanks Tony, but it's still not completely clear -
> it's important to see values of CLKSTCTRL before/after idle for not working
> case and GPIO SYSC/SYSS registers. There are some combinations which may
> affect on this. So, if you can, pls.

Sure I'll do some reg dumps for you tomorrow as I just started
an overnight test with randomly pinging the wlan interface
from outside to make sure it wakes up and keeps idling.

Regards,

Tony
Grygorii Strashko Sept. 12, 2018, 4:08 p.m. UTC | #6
On 09/11/2018 07:42 PM, Tony Lindgren wrote:
> * Grygorii Strashko <grygorii.strashko@ti.com> [180912 00:27]:
>> Thanks Tony, but it's still not completely clear -
>> it's important to see values of CLKSTCTRL before/after idle for not working
>> case and GPIO SYSC/SYSS registers. There are some combinations which may
>> affect on this. So, if you can, pls.
> 
> Sure I'll do some reg dumps for you tomorrow as I just started
> an overnight test with randomly pinging the wlan interface
> from outside to make sure it wakes up and keeps idling.

Pls, postpone this until we finish our discussion/debugging
Tony Lindgren Sept. 12, 2018, 5:09 p.m. UTC | #7
* Tony Lindgren <tony@atomide.com> [180910 20:10]:
> +static void __maybe_unused
> +omap4_gpio_disable_level_quirk(struct gpio_bank *bank)
> +{
> +	/* Restore level registers after idle */
> +	writel_relaxed(bank->context.leveldetect0,
> +		       bank->base + bank->regs->leveldetect0);
> +	writel_relaxed(bank->context.leveldetect1,
> +		       bank->base + bank->regs->leveldetect1);
> +
> +	/* Clear wake after idle */
> +	writel_relaxed(0, bank->base + bank->regs->wkup_en);

	/* Reset saved wkup_en, it will be set for next idle again */
	bank->context.wake_en = 0;
> +}

Clearing the saved wkup_en is needed above I noticed
testing again with gpio4 which has pdata->loses_context
set.

Otherwise runtime_resume will write to wkup_en enabling
it for runtime which will the next idle fail.

Regards,

Tony
Tony Lindgren Sept. 12, 2018, 5:38 p.m. UTC | #8
* Grygorii Strashko <grygorii.strashko@ti.com> [180912 16:13]:
> 
> 
> On 09/11/2018 07:42 PM, Tony Lindgren wrote:
> > * Grygorii Strashko <grygorii.strashko@ti.com> [180912 00:27]:
> > > Thanks Tony, but it's still not completely clear -
> > > it's important to see values of CLKSTCTRL before/after idle for not working
> > > case and GPIO SYSC/SYSS registers. There are some combinations which may
> > > affect on this. So, if you can, pls.
> > 
> > Sure I'll do some reg dumps for you tomorrow as I just started
> > an overnight test with randomly pinging the wlan interface
> > from outside to make sure it wakes up and keeps idling.
> 
> Pls, postpone this until we finish our discussion/debugging

Yeah I just sent you three gpio dump files privately to avoid
spamming the list with them.

Regards,

Tony
Linus Walleij Sept. 18, 2018, 11:28 p.m. UTC | #9
On Mon, Sep 10, 2018 at 1:06 PM Tony Lindgren <tony@atomide.com> wrote:

> I noticed that unlike omap2 and 3 based SoCs, omap4 based SoCs keep
> the GPIO clocks enabled for GPIO level interrupts with wakeup enabled.
> This blocks deeper idle states as the whole domain will stay busy.
>
> The GPIO clock seems to stay enabled if the wakeup register is enabled
> and a level interrupt is triggered. In that case the only way to have
> the GPIO module idle is to reset it. It is possible this has gone
> unnoticed with OSWR (Open SWitch Retention) and off mode during idle
> resetting GPIO context most GPIO instances in the earlier Android trees
> for example.
>
> Looks like the way to deal with this is to have omap4 based SoCs
> only set wake for the duration of idle and clear level registers for
> the idle. With level interrupts we can do this as the level interrupt
> from device will be still there on resume.
>
> I've taken the long path to fixing this to avoid yet more hard to
> read code. I've set up a quirks flag, and a struct for function
> pointers so we can use these to clean up other quirk handling easier
> in the later patches. The current level quirk handling is moved to
> the new functions.
>
> Cc: Grygorii Strashko <grygorii.strashko@ti.com>
> Cc: Keerthy <j-keerthy@ti.com>
> Cc: Tero Kristo <t-kristo@ti.com>
> Signed-off-by: Tony Lindgren <tony@atomide.com>

Do we have a conclusion on this? Shall I apply the patch on
an immutable branch and pull into next, or do you folks need
more time?

Yours,
Linus Walleij
Tony Lindgren Sept. 19, 2018, 12:28 a.m. UTC | #10
* Linus Walleij <linus.walleij@linaro.org> [180918 23:33]:
> On Mon, Sep 10, 2018 at 1:06 PM Tony Lindgren <tony@atomide.com> wrote:
> 
> > I noticed that unlike omap2 and 3 based SoCs, omap4 based SoCs keep
> > the GPIO clocks enabled for GPIO level interrupts with wakeup enabled.
> > This blocks deeper idle states as the whole domain will stay busy.
> >
> > The GPIO clock seems to stay enabled if the wakeup register is enabled
> > and a level interrupt is triggered. In that case the only way to have
> > the GPIO module idle is to reset it. It is possible this has gone
> > unnoticed with OSWR (Open SWitch Retention) and off mode during idle
> > resetting GPIO context most GPIO instances in the earlier Android trees
> > for example.
> >
> > Looks like the way to deal with this is to have omap4 based SoCs
> > only set wake for the duration of idle and clear level registers for
> > the idle. With level interrupts we can do this as the level interrupt
> > from device will be still there on resume.
> >
> > I've taken the long path to fixing this to avoid yet more hard to
> > read code. I've set up a quirks flag, and a struct for function
> > pointers so we can use these to clean up other quirk handling easier
> > in the later patches. The current level quirk handling is moved to
> > the new functions.
> >
> > Cc: Grygorii Strashko <grygorii.strashko@ti.com>
> > Cc: Keerthy <j-keerthy@ti.com>
> > Cc: Tero Kristo <t-kristo@ti.com>
> > Signed-off-by: Tony Lindgren <tony@atomide.com>
> 
> Do we have a conclusion on this? Shall I apply the patch on
> an immutable branch and pull into next, or do you folks need
> more time?

Thanks for checking, let's wait on this. There are few more things
I still need to check and test before reposting.

Regards,

Tony
diff mbox series

Patch

diff --git a/drivers/gpio/gpio-omap.c b/drivers/gpio/gpio-omap.c
--- a/drivers/gpio/gpio-omap.c
+++ b/drivers/gpio/gpio-omap.c
@@ -31,6 +31,8 @@ 
 #define OFF_MODE	1
 #define OMAP4_GPIO_DEBOUNCINGTIME_MASK 0xFF
 
+#define OMAP_GPIO_QUIRK_DEFERRED_WKUP_EN	BIT(1)
+
 static LIST_HEAD(omap_gpio_list);
 
 struct gpio_regs {
@@ -48,6 +50,13 @@  struct gpio_regs {
 	u32 debounce_en;
 };
 
+struct gpio_bank;
+
+struct gpio_omap_funcs {
+	void (*idle_enable_level_quirk)(struct gpio_bank *bank);
+	void (*idle_disable_level_quirk)(struct gpio_bank *bank);
+};
+
 struct gpio_bank {
 	struct list_head node;
 	void __iomem *base;
@@ -55,6 +64,7 @@  struct gpio_bank {
 	u32 non_wakeup_gpios;
 	u32 enabled_non_wakeup_gpios;
 	struct gpio_regs context;
+	struct gpio_omap_funcs funcs;
 	u32 saved_datain;
 	u32 level_mask;
 	u32 toggle_mask;
@@ -75,6 +85,7 @@  struct gpio_bank {
 	int context_loss_count;
 	int power_mode;
 	bool workaround_enabled;
+	u32 quirks;
 
 	void (*set_dataout)(struct gpio_bank *bank, unsigned gpio, int enable);
 	void (*set_dataout_multiple)(struct gpio_bank *bank,
@@ -368,9 +379,18 @@  static inline void omap_set_gpio_trigger(struct gpio_bank *bank, int gpio,
 			readl_relaxed(bank->base + bank->regs->fallingdetect);
 
 	if (likely(!(bank->non_wakeup_gpios & gpio_bit))) {
-		omap_gpio_rmw(base, bank->regs->wkup_en, gpio_bit, trigger != 0);
-		bank->context.wake_en =
-			readl_relaxed(bank->base + bank->regs->wkup_en);
+		/* Defer wkup_en register write until we idle? */
+		if (bank->quirks & OMAP_GPIO_QUIRK_DEFERRED_WKUP_EN) {
+			if (trigger)
+				bank->context.wake_en |= gpio_bit;
+			else
+				bank->context.wake_en &= ~gpio_bit;
+		} else {
+			omap_gpio_rmw(base, bank->regs->wkup_en, gpio_bit,
+				      trigger != 0);
+			bank->context.wake_en =
+				readl_relaxed(bank->base + bank->regs->wkup_en);
+		}
 	}
 
 	/* This part needs to be executed always for OMAP{34xx, 44xx} */
@@ -899,6 +919,76 @@  static void omap_gpio_unmask_irq(struct irq_data *d)
 	raw_spin_unlock_irqrestore(&bank->lock, flags);
 }
 
+/*
+ * Only edges can generate a wakeup event to the PRCM.
+ *
+ * Therefore, ensure any wake-up capable GPIOs have
+ * edge-detection enabled before going idle to ensure a wakeup
+ * to the PRCM is generated on a GPIO transition. (c.f. 34xx
+ * NDA TRM 25.5.3.1)
+ *
+ * The normal values will be restored upon ->runtime_resume()
+ * by writing back the values saved in bank->context.
+ */
+static void __maybe_unused
+omap2_gpio_enable_level_quirk(struct gpio_bank *bank)
+{
+	u32 wake_low, wake_hi;
+
+	/* Enable additional edge detection for level gpios for idle */
+	wake_low = bank->context.leveldetect0 & bank->context.wake_en;
+	if (wake_low)
+		writel_relaxed(wake_low | bank->context.fallingdetect,
+			       bank->base + bank->regs->fallingdetect);
+
+	wake_hi = bank->context.leveldetect1 & bank->context.wake_en;
+	if (wake_hi)
+		writel_relaxed(wake_hi | bank->context.risingdetect,
+			       bank->base + bank->regs->risingdetect);
+}
+
+static void __maybe_unused
+omap2_gpio_disable_level_quirk(struct gpio_bank *bank)
+{
+	/* Disable edge detection for level gpios after idle */
+	writel_relaxed(bank->context.fallingdetect,
+		       bank->base + bank->regs->fallingdetect);
+	writel_relaxed(bank->context.risingdetect,
+		       bank->base + bank->regs->risingdetect);
+}
+
+/*
+ * On omap4 and later SoC variants a level interrupt with wkup_en
+ * enabled blocks the GPIO clocks from idling until the GPIO
+ * instance been reset. To avoid that, we must set wkup_en only for
+ * idle and clear level registers for the duration of idle. The level
+ * interrupt will be still there on wakeup by it's nature.
+ */
+static void __maybe_unused
+omap4_gpio_enable_level_quirk(struct gpio_bank *bank)
+{
+	/* Set wake only for idle */
+	writel_relaxed(bank->context.wake_en,
+		       bank->base + bank->regs->wkup_en);
+
+	/* Clear level registers for idle */
+	writel_relaxed(0, bank->base + bank->regs->leveldetect0);
+	writel_relaxed(0, bank->base + bank->regs->leveldetect1);
+}
+
+static void __maybe_unused
+omap4_gpio_disable_level_quirk(struct gpio_bank *bank)
+{
+	/* Restore level registers after idle */
+	writel_relaxed(bank->context.leveldetect0,
+		       bank->base + bank->regs->leveldetect0);
+	writel_relaxed(bank->context.leveldetect1,
+		       bank->base + bank->regs->leveldetect1);
+
+	/* Clear wake after idle */
+	writel_relaxed(0, bank->base + bank->regs->wkup_en);
+}
+
 /*---------------------------------------------------------------------*/
 
 static int omap_mpuio_suspend_noirq(struct device *dev)
@@ -1270,6 +1360,7 @@  static int omap_gpio_probe(struct platform_device *pdev)
 	bank->chip.parent = dev;
 	bank->chip.owner = THIS_MODULE;
 	bank->dbck_flag = pdata->dbck_flag;
+	bank->quirks = pdata->quirks;
 	bank->stride = pdata->bank_stride;
 	bank->width = pdata->bank_width;
 	bank->is_mpuio = pdata->is_mpuio;
@@ -1278,6 +1369,7 @@  static int omap_gpio_probe(struct platform_device *pdev)
 #ifdef CONFIG_OF_GPIO
 	bank->chip.of_node = of_node_get(node);
 #endif
+
 	if (node) {
 		if (!of_property_read_bool(node, "ti,gpio-always-on"))
 			bank->loses_context = true;
@@ -1298,6 +1390,18 @@  static int omap_gpio_probe(struct platform_device *pdev)
 				omap_set_gpio_dataout_mask_multiple;
 	}
 
+	if (bank->quirks & OMAP_GPIO_QUIRK_DEFERRED_WKUP_EN) {
+		bank->funcs.idle_enable_level_quirk =
+			omap4_gpio_enable_level_quirk;
+		bank->funcs.idle_disable_level_quirk =
+			omap4_gpio_disable_level_quirk;
+	} else {
+		bank->funcs.idle_enable_level_quirk =
+			omap2_gpio_enable_level_quirk;
+		bank->funcs.idle_disable_level_quirk =
+			omap2_gpio_disable_level_quirk;
+	}
+
 	raw_spin_lock_init(&bank->lock);
 	raw_spin_lock_init(&bank->wa_lock);
 
@@ -1372,29 +1476,11 @@  static int omap_gpio_runtime_suspend(struct device *dev)
 	struct gpio_bank *bank = platform_get_drvdata(pdev);
 	u32 l1 = 0, l2 = 0;
 	unsigned long flags;
-	u32 wake_low, wake_hi;
 
 	raw_spin_lock_irqsave(&bank->lock, flags);
 
-	/*
-	 * Only edges can generate a wakeup event to the PRCM.
-	 *
-	 * Therefore, ensure any wake-up capable GPIOs have
-	 * edge-detection enabled before going idle to ensure a wakeup
-	 * to the PRCM is generated on a GPIO transition. (c.f. 34xx
-	 * NDA TRM 25.5.3.1)
-	 *
-	 * The normal values will be restored upon ->runtime_resume()
-	 * by writing back the values saved in bank->context.
-	 */
-	wake_low = bank->context.leveldetect0 & bank->context.wake_en;
-	if (wake_low)
-		writel_relaxed(wake_low | bank->context.fallingdetect,
-			     bank->base + bank->regs->fallingdetect);
-	wake_hi = bank->context.leveldetect1 & bank->context.wake_en;
-	if (wake_hi)
-		writel_relaxed(wake_hi | bank->context.risingdetect,
-			     bank->base + bank->regs->risingdetect);
+	if (bank->funcs.idle_enable_level_quirk)
+		bank->funcs.idle_enable_level_quirk(bank);
 
 	if (!bank->enabled_non_wakeup_gpios)
 		goto update_gpio_context_count;
@@ -1459,16 +1545,8 @@  static int omap_gpio_runtime_resume(struct device *dev)
 
 	omap_gpio_dbck_enable(bank);
 
-	/*
-	 * In ->runtime_suspend(), level-triggered, wakeup-enabled
-	 * GPIOs were set to edge trigger also in order to be able to
-	 * generate a PRCM wakeup.  Here we restore the
-	 * pre-runtime_suspend() values for edge triggering.
-	 */
-	writel_relaxed(bank->context.fallingdetect,
-		     bank->base + bank->regs->fallingdetect);
-	writel_relaxed(bank->context.risingdetect,
-		     bank->base + bank->regs->risingdetect);
+	if (bank->funcs.idle_disable_level_quirk)
+		bank->funcs.idle_disable_level_quirk(bank);
 
 	if (bank->loses_context) {
 		if (!bank->get_context_loss_count) {
@@ -1706,6 +1784,7 @@  static const struct omap_gpio_platform_data omap4_pdata = {
 	.regs = &omap4_gpio_regs,
 	.bank_width = 32,
 	.dbck_flag = true,
+	.quirks = OMAP_GPIO_QUIRK_DEFERRED_WKUP_EN,
 };
 
 static const struct of_device_id omap_gpio_match[] = {
diff --git a/include/linux/platform_data/gpio-omap.h b/include/linux/platform_data/gpio-omap.h
--- a/include/linux/platform_data/gpio-omap.h
+++ b/include/linux/platform_data/gpio-omap.h
@@ -197,6 +197,8 @@  struct omap_gpio_platform_data {
 	bool is_mpuio;		/* whether the bank is of type MPUIO */
 	u32 non_wakeup_gpios;
 
+	u32 quirks;		/* Version specific quirks mask */
+
 	struct omap_gpio_reg_offs *regs;
 
 	/* Return context loss count due to PM states changing */