diff options
author | Colin Cross <ccross@android.com> | 2011-11-11 16:22:42 -0800 |
---|---|---|
committer | Colin Cross <ccross@android.com> | 2011-11-14 14:16:10 -0800 |
commit | d40caba356780995ae9be60a432a12cee4fe4dae (patch) | |
tree | 7ccff3d523b1a44a75b42beeaae5ab692c74ff81 | |
parent | 030f725173448cee5fc96a4a098b01ef10b075d5 (diff) | |
download | kernel_samsung_tuna-d40caba356780995ae9be60a432a12cee4fe4dae.zip kernel_samsung_tuna-d40caba356780995ae9be60a432a12cee4fe4dae.tar.gz kernel_samsung_tuna-d40caba356780995ae9be60a432a12cee4fe4dae.tar.bz2 |
ARM: omap4: gpio: abort suspend if a level wake interrupt is pending
If a level wake interrupt arrives after disable_irq has been called
but before suspend, the interrupt will be masked. The omap gpio
driver erases all triggering information when masking an interrupt.
Track the triggering state of gpio interrupts, and abort suspend
if a masked wakeup interrupt is active when suspending.
Change-Id: I117b592d21c455074796deaea8148edf0947eb35
Signed-off-by: Colin Cross <ccross@android.com>
-rw-r--r-- | arch/arm/mach-omap2/pm44xx.c | 2 | ||||
-rw-r--r-- | arch/arm/plat-omap/include/plat/gpio.h | 2 | ||||
-rw-r--r-- | drivers/gpio/gpio-omap.c | 44 |
3 files changed, 41 insertions, 7 deletions
diff --git a/arch/arm/mach-omap2/pm44xx.c b/arch/arm/mach-omap2/pm44xx.c index 4d0efab..302997e 100644 --- a/arch/arm/mach-omap2/pm44xx.c +++ b/arch/arm/mach-omap2/pm44xx.c @@ -137,7 +137,7 @@ void omap4_enter_sleep(unsigned int cpu, unsigned int power_state, bool suspend) core_next_state = pwrdm_read_next_pwrst(core_pwrdm); mpu_next_state = pwrdm_read_next_pwrst(mpu_pwrdm); - ret = omap2_gpio_prepare_for_idle(omap4_device_next_state_off()); + ret = omap2_gpio_prepare_for_idle(omap4_device_next_state_off(), suspend); if (ret) goto abort_gpio; diff --git a/arch/arm/plat-omap/include/plat/gpio.h b/arch/arm/plat-omap/include/plat/gpio.h index 0b1f7c5..90eae5c 100644 --- a/arch/arm/plat-omap/include/plat/gpio.h +++ b/arch/arm/plat-omap/include/plat/gpio.h @@ -213,7 +213,7 @@ struct omap_gpio_platform_data { struct omap_gpio_reg_offs *regs; }; -extern int omap2_gpio_prepare_for_idle(int off_mode); +extern int omap2_gpio_prepare_for_idle(int off_mode, bool suspend); extern void omap2_gpio_resume_after_idle(int off_mode); extern void omap_set_gpio_debounce(int gpio, int enable); extern void omap_set_gpio_debounce_time(int gpio, int enable); diff --git a/drivers/gpio/gpio-omap.c b/drivers/gpio/gpio-omap.c index a5ca770..159b3d9 100644 --- a/drivers/gpio/gpio-omap.c +++ b/drivers/gpio/gpio-omap.c @@ -87,6 +87,11 @@ struct gpio_bank { u32 width; u16 id; + u32 type_leveldetect0; + u32 type_leveldetect1; + u32 type_risingedge; + u32 type_fallingedge; + void (*set_dataout)(struct gpio_bank *bank, int gpio, int enable); struct omap_gpio_reg_offs *regs; @@ -378,7 +383,23 @@ static int gpio_irq_type(struct irq_data *d, unsigned type) return -EINVAL; spin_lock_irqsave(&bank->lock, flags); + retval = _set_gpio_triggering(bank, GPIO_INDEX(bank, gpio), type); + + bank->type_leveldetect0 &= ~GPIO_BIT(bank, gpio); + bank->type_leveldetect1 &= ~GPIO_BIT(bank, gpio); + bank->type_fallingedge &= ~GPIO_BIT(bank, gpio); + bank->type_risingedge &= ~GPIO_BIT(bank, gpio); + + if (type & IRQ_TYPE_LEVEL_LOW) + bank->type_leveldetect0 |= GPIO_BIT(bank, gpio); + if (type & IRQ_TYPE_LEVEL_HIGH) + bank->type_leveldetect1 |= GPIO_BIT(bank, gpio); + if (type & IRQ_TYPE_EDGE_FALLING) + bank->type_fallingedge |= GPIO_BIT(bank, gpio); + if (type & IRQ_TYPE_EDGE_RISING) + bank->type_risingedge |= GPIO_BIT(bank, gpio); + spin_unlock_irqrestore(&bank->lock, flags); if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) @@ -1432,11 +1453,13 @@ static int omap_gpio_pm_runtime_resume(struct device *dev) } #ifdef CONFIG_ARCH_OMAP2PLUS -static int omap2_gpio_set_edge_wakeup(struct gpio_bank *bank) +static int omap2_gpio_set_edge_wakeup(struct gpio_bank *bank, bool suspend) { int ret = 0; u32 wkup_status = 0; u32 datain; + u32 mask; + u32 active; if (pm_runtime_get_sync(bank->dev) < 0) { dev_err(bank->dev, "%s: GPIO bank %d pm_runtime_get_sync " @@ -1477,9 +1500,20 @@ static int omap2_gpio_set_edge_wakeup(struct gpio_bank *bank) * interrupt mask. */ datain = __raw_readl(bank->base + bank->regs->datain); - if ((datain & bank->context.ew_leveldetect1) || - (~datain & bank->context.ew_leveldetect0)) + if (suspend) + mask = bank->suspend_wakeup; + else + mask = wkup_status; + + active = (datain & bank->type_leveldetect1 & mask) | + (~datain & bank->type_leveldetect0 & mask); + + if (active) { + if (suspend) + pr_info("%s: aborted suspend due to gpio %d\n", + __func__, bank->id * bank->width + __ffs(active)); ret = -EBUSY; + } if (pm_runtime_put_sync_suspend(bank->dev) < 0) { dev_err(bank->dev, "%s: GPIO bank %d pm_runtime_put_sync " @@ -1514,7 +1548,7 @@ static void omap2_gpio_restore_edge_wakeup(struct gpio_bank *bank) } } -int omap2_gpio_prepare_for_idle(int off_mode) +int omap2_gpio_prepare_for_idle(int off_mode, bool suspend) { int ret = 0; struct gpio_bank *bank; @@ -1522,7 +1556,7 @@ int omap2_gpio_prepare_for_idle(int off_mode) list_for_each_entry(bank, &omap_gpio_list, node) { omap2_gpio_set_wakeupenables(bank); - if (omap2_gpio_set_edge_wakeup(bank)) + if (omap2_gpio_set_edge_wakeup(bank, suspend)) ret = -EBUSY; if (bank->mod_usage && bank->loses_context && off_mode) |