Commit ec0daae6 authored by Tony Lindgren's avatar Tony Lindgren Committed by Linus Walleij

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

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 functional 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 for level interrupts, 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: Aaro Koskinen <aaro.koskinen@iki.fi>
Cc: Ladislav Michl <ladis@linux-mips.org>
Cc: Tero Kristo <t-kristo@ti.com>
Signed-off-by: default avatarTony Lindgren <tony@atomide.com>
Acked-by: default avatarGrygorii Strashko <grygorii.strashko@ti.com>
Tested-by: default avatarKeerthy <j-keerthy@ti.com>
Signed-off-by: default avatarLinus Walleij <linus.walleij@linaro.org>
parent 5b394b2d
...@@ -31,6 +31,8 @@ ...@@ -31,6 +31,8 @@
#define OFF_MODE 1 #define OFF_MODE 1
#define OMAP4_GPIO_DEBOUNCINGTIME_MASK 0xFF #define OMAP4_GPIO_DEBOUNCINGTIME_MASK 0xFF
#define OMAP_GPIO_QUIRK_DEFERRED_WKUP_EN BIT(1)
static LIST_HEAD(omap_gpio_list); static LIST_HEAD(omap_gpio_list);
struct gpio_regs { struct gpio_regs {
...@@ -48,6 +50,13 @@ struct gpio_regs { ...@@ -48,6 +50,13 @@ struct gpio_regs {
u32 debounce_en; 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 gpio_bank {
struct list_head node; struct list_head node;
void __iomem *base; void __iomem *base;
...@@ -55,6 +64,7 @@ struct gpio_bank { ...@@ -55,6 +64,7 @@ struct gpio_bank {
u32 non_wakeup_gpios; u32 non_wakeup_gpios;
u32 enabled_non_wakeup_gpios; u32 enabled_non_wakeup_gpios;
struct gpio_regs context; struct gpio_regs context;
struct gpio_omap_funcs funcs;
u32 saved_datain; u32 saved_datain;
u32 level_mask; u32 level_mask;
u32 toggle_mask; u32 toggle_mask;
...@@ -75,6 +85,7 @@ struct gpio_bank { ...@@ -75,6 +85,7 @@ struct gpio_bank {
int context_loss_count; int context_loss_count;
int power_mode; int power_mode;
bool workaround_enabled; bool workaround_enabled;
u32 quirks;
void (*set_dataout)(struct gpio_bank *bank, unsigned gpio, int enable); void (*set_dataout)(struct gpio_bank *bank, unsigned gpio, int enable);
void (*set_dataout_multiple)(struct gpio_bank *bank, void (*set_dataout_multiple)(struct gpio_bank *bank,
...@@ -368,10 +379,19 @@ static inline void omap_set_gpio_trigger(struct gpio_bank *bank, int gpio, ...@@ -368,10 +379,19 @@ static inline void omap_set_gpio_trigger(struct gpio_bank *bank, int gpio,
readl_relaxed(bank->base + bank->regs->fallingdetect); readl_relaxed(bank->base + bank->regs->fallingdetect);
if (likely(!(bank->non_wakeup_gpios & gpio_bit))) { if (likely(!(bank->non_wakeup_gpios & gpio_bit))) {
omap_gpio_rmw(base, bank->regs->wkup_en, gpio_bit, trigger != 0); /* Defer wkup_en register update 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 = bank->context.wake_en =
readl_relaxed(bank->base + bank->regs->wkup_en); readl_relaxed(bank->base + bank->regs->wkup_en);
} }
}
/* This part needs to be executed always for OMAP{34xx, 44xx} */ /* This part needs to be executed always for OMAP{34xx, 44xx} */
if (!bank->regs->irqctrl) { if (!bank->regs->irqctrl) {
...@@ -899,6 +919,82 @@ static void omap_gpio_unmask_irq(struct irq_data *d) ...@@ -899,6 +919,82 @@ static void omap_gpio_unmask_irq(struct irq_data *d)
raw_spin_unlock_irqrestore(&bank->lock, flags); 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 functional clock from idling until the GPIO
* instance has been reset. To avoid that, we must set wkup_en only for
* idle for level interrupts, and clear level registers for the duration
* of idle. The level interrupts will be still there on wakeup by their
* nature.
*/
static void __maybe_unused
omap4_gpio_enable_level_quirk(struct gpio_bank *bank)
{
/* Update wake register for idle, edge bits might be already set */
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 saved wkup_en for level, it will be set for next idle again */
bank->context.wake_en &= ~(bank->context.leveldetect0 |
bank->context.leveldetect1);
/* Update wake with only edge configuration */
writel_relaxed(bank->context.wake_en,
bank->base + bank->regs->wkup_en);
}
/*---------------------------------------------------------------------*/ /*---------------------------------------------------------------------*/
static int omap_mpuio_suspend_noirq(struct device *dev) static int omap_mpuio_suspend_noirq(struct device *dev)
...@@ -1270,6 +1366,7 @@ static int omap_gpio_probe(struct platform_device *pdev) ...@@ -1270,6 +1366,7 @@ static int omap_gpio_probe(struct platform_device *pdev)
bank->chip.parent = dev; bank->chip.parent = dev;
bank->chip.owner = THIS_MODULE; bank->chip.owner = THIS_MODULE;
bank->dbck_flag = pdata->dbck_flag; bank->dbck_flag = pdata->dbck_flag;
bank->quirks = pdata->quirks;
bank->stride = pdata->bank_stride; bank->stride = pdata->bank_stride;
bank->width = pdata->bank_width; bank->width = pdata->bank_width;
bank->is_mpuio = pdata->is_mpuio; bank->is_mpuio = pdata->is_mpuio;
...@@ -1278,6 +1375,7 @@ static int omap_gpio_probe(struct platform_device *pdev) ...@@ -1278,6 +1375,7 @@ static int omap_gpio_probe(struct platform_device *pdev)
#ifdef CONFIG_OF_GPIO #ifdef CONFIG_OF_GPIO
bank->chip.of_node = of_node_get(node); bank->chip.of_node = of_node_get(node);
#endif #endif
if (node) { if (node) {
if (!of_property_read_bool(node, "ti,gpio-always-on")) if (!of_property_read_bool(node, "ti,gpio-always-on"))
bank->loses_context = true; bank->loses_context = true;
...@@ -1298,6 +1396,18 @@ static int omap_gpio_probe(struct platform_device *pdev) ...@@ -1298,6 +1396,18 @@ static int omap_gpio_probe(struct platform_device *pdev)
omap_set_gpio_dataout_mask_multiple; 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->lock);
raw_spin_lock_init(&bank->wa_lock); raw_spin_lock_init(&bank->wa_lock);
...@@ -1372,29 +1482,11 @@ static int omap_gpio_runtime_suspend(struct device *dev) ...@@ -1372,29 +1482,11 @@ static int omap_gpio_runtime_suspend(struct device *dev)
struct gpio_bank *bank = platform_get_drvdata(pdev); struct gpio_bank *bank = platform_get_drvdata(pdev);
u32 l1 = 0, l2 = 0; u32 l1 = 0, l2 = 0;
unsigned long flags; unsigned long flags;
u32 wake_low, wake_hi;
raw_spin_lock_irqsave(&bank->lock, flags); raw_spin_lock_irqsave(&bank->lock, flags);
/* if (bank->funcs.idle_enable_level_quirk)
* Only edges can generate a wakeup event to the PRCM. bank->funcs.idle_enable_level_quirk(bank);
*
* 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->enabled_non_wakeup_gpios) if (!bank->enabled_non_wakeup_gpios)
goto update_gpio_context_count; goto update_gpio_context_count;
...@@ -1459,16 +1551,8 @@ static int omap_gpio_runtime_resume(struct device *dev) ...@@ -1459,16 +1551,8 @@ static int omap_gpio_runtime_resume(struct device *dev)
omap_gpio_dbck_enable(bank); omap_gpio_dbck_enable(bank);
/* if (bank->funcs.idle_disable_level_quirk)
* In ->runtime_suspend(), level-triggered, wakeup-enabled bank->funcs.idle_disable_level_quirk(bank);
* 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->loses_context) { if (bank->loses_context) {
if (!bank->get_context_loss_count) { if (!bank->get_context_loss_count) {
...@@ -1706,6 +1790,7 @@ static const struct omap_gpio_platform_data omap4_pdata = { ...@@ -1706,6 +1790,7 @@ static const struct omap_gpio_platform_data omap4_pdata = {
.regs = &omap4_gpio_regs, .regs = &omap4_gpio_regs,
.bank_width = 32, .bank_width = 32,
.dbck_flag = true, .dbck_flag = true,
.quirks = OMAP_GPIO_QUIRK_DEFERRED_WKUP_EN,
}; };
static const struct of_device_id omap_gpio_match[] = { static const struct of_device_id omap_gpio_match[] = {
......
...@@ -197,6 +197,8 @@ struct omap_gpio_platform_data { ...@@ -197,6 +197,8 @@ struct omap_gpio_platform_data {
bool is_mpuio; /* whether the bank is of type MPUIO */ bool is_mpuio; /* whether the bank is of type MPUIO */
u32 non_wakeup_gpios; u32 non_wakeup_gpios;
u32 quirks; /* Version specific quirks mask */
struct omap_gpio_reg_offs *regs; struct omap_gpio_reg_offs *regs;
/* Return context loss count due to PM states changing */ /* Return context loss count due to PM states changing */
......
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