diff options
| author | Vladimir Tsunaev <vladimirt@motorola.com> | 2014-05-09 14:15:51 -0400 |
|---|---|---|
| committer | Vladimir Tsunaev <vladimirt@motorola.com> | 2014-05-12 19:25:57 -0400 |
| commit | 50e1560470960da69a8832ab33d468e916c24263 (patch) | |
| tree | 0f7b7725149591127bba33abcb67c11a0efd9a5e /drivers/gpio/gpio-omap.c | |
| parent | ead078965f30f53417abd061c97a1b79cf1e8c09 (diff) | |
| download | olio-linux-3.10-50e1560470960da69a8832ab33d468e916c24263.tar.xz olio-linux-3.10-50e1560470960da69a8832ab33d468e916c24263.zip | |
IKXCLOCK-1044 gpio: omap: enable OMAP offmode
Output GPIO lines do not stay in correct state after exit
from OFF mode, it is happening because GPIO modules does
not keep state of registers if module is not in wake up
domain. Work around is to save pad and gpio configuration
before entering OFF state and put pads to safe mode with
pulls enabled according with level on GPIO output
After exit OFF state all pad configurations are restored.
This work around is related to GPIOs which are configured
as output.
Change-Id: Ia0ca337de1bda28dd5f01c9d51686d822fc9ec21
Signed-off-by: Vladimir Tsunaev <vladimirt@motorola.com>
Diffstat (limited to 'drivers/gpio/gpio-omap.c')
| -rw-r--r-- | drivers/gpio/gpio-omap.c | 166 |
1 files changed, 161 insertions, 5 deletions
diff --git a/drivers/gpio/gpio-omap.c b/drivers/gpio/gpio-omap.c index 4a430360af5..378e3d8a418 100644 --- a/drivers/gpio/gpio-omap.c +++ b/drivers/gpio/gpio-omap.c @@ -27,9 +27,9 @@ #include <linux/irqdomain.h> #include <linux/irqchip/chained_irq.h> #include <linux/gpio.h> +#include <linux/of_gpio.h> #include <linux/platform_data/gpio-omap.h> -#define OFF_MODE 1 static LIST_HEAD(omap_gpio_list); @@ -47,7 +47,11 @@ struct gpio_regs { u32 debounce; u32 debounce_en; }; - +struct pad_context { + int pin_offs; + int gpio_n; + u32 cfg; +}; struct gpio_bank { struct list_head node; void __iomem *base; @@ -75,6 +79,8 @@ struct gpio_bank { int context_loss_count; int power_mode; bool workaround_enabled; + struct pad_context *pads_ctx; + void __iomem *pads_base; void (*set_dataout)(struct gpio_bank *bank, int gpio, int enable); int (*get_context_loss_count)(struct device *dev); @@ -1084,6 +1090,125 @@ static void omap_gpio_chip_init(struct gpio_bank *bank) irq_set_handler_data(bank->irq, bank); } +static void omap_gpio_pad_ctx_save(struct gpio_bank *bank) +{ + int i; + unsigned long flags; + void __iomem *p_a; + void __iomem *reg; + u32 msk, v; + struct pad_context *ctx; + + spin_lock_irqsave(&bank->lock, flags); + if (!bank->pads_ctx) + goto skip; + + ctx = bank->pads_ctx; + for (i = 0; i < bank->chip.ngpio; i++, ctx++) { + if (ctx->gpio_n < 0) + break; + msk = 1 << ctx->gpio_n; + if (!(bank->context.oe & msk)) { + p_a = bank->pads_base + ctx->pin_offs; + reg = bank->base + bank->regs->direction; + /* save curr config */ + ctx->cfg = __raw_readw(p_a); + /* enable PU or PD*/ + if (bank->context.dataout & msk) + v = 0x11c; + else + v = 0x10c; + __raw_writew(v, p_a); + /* set GPIO as input */ + __raw_writel(__raw_readl(reg) | msk, reg); + /* set to safe mode */ + __raw_writew(v | 0x7, p_a); + } + } +skip: + spin_unlock_irqrestore(&bank->lock, flags); +} + +/* should be called after GPIO conext restored in locked context*/ +static void omap_gpio_pad_ctx_restore(struct gpio_bank *bank) +{ + int i; + struct pad_context *ctx = bank->pads_ctx; + if (!ctx) + return; + for (i = 0; i < bank->chip.ngpio; i++, ctx++) { + if (ctx->gpio_n < 0) + break; + if (!(bank->context.oe & (1 << ctx->gpio_n))) + __raw_writew(ctx->cfg, bank->pads_base + ctx->pin_offs); + } +} +static int __init omap_gpio_pad_ctx_init(struct gpio_bank *bank) +{ + struct gpio_pin_range *range, *tmp; + int i, cnt = 0; + struct pad_context *ctx; + static void __iomem *base __initdata; + + if (!bank->loses_context) + return 0; + + /* + * HACK: only one pad conf module should be used for all gpio + * modules which loosing context and hard coded value is for + * this pad conf module. It is done to avoid rewriting all pin + * controls and define configuration for every pin in device + * tree to swtich from pinctrl-single to pinconf-single. + * Can not use mem mapping, this region maped by pinctrl. + * Extra protection is required to access this memory (disable + * irqs). + */ + if (!base) { + if (bank->pads_base) + return -EBUSY; + bank->pads_base = devm_ioremap(bank->dev, 0x48002030, 0x05cc); + if (!bank->pads_base) { + dev_err(bank->dev, "Could not ioremap 0x48002030\n"); + return -ENOMEM; + } + } else { + bank->pads_base = base; + } + + ctx = devm_kzalloc(bank->dev, sizeof(struct pad_context) * + bank->chip.ngpio, GFP_KERNEL); + if (!ctx) { + dev_err(bank->dev, "Memory alloc for pad context failed\n"); + return -ENOMEM; + } + + bank->pads_ctx = ctx; + for (i = 0; i < bank->chip.ngpio; i++, ctx++) + ctx->gpio_n = -1; + ctx = bank->pads_ctx; + list_for_each_entry_safe(range, tmp, &bank->chip.pin_ranges, node) { + if (range->range.base < bank->chip.base || + range->range.base + range->range.npins > + bank->chip.base + bank->chip.ngpio) { + + dev_err(bank->dev, "Range base is out of limits for" + " this gpio chip\n"); + continue; + } + cnt += range->range.npins; + if (cnt <= bank->chip.ngpio) { + for (i = 0; i < range->range.npins; i++, ctx++) { + ctx->gpio_n = range->range.base + i - + bank->chip.base; + ctx->pin_offs = (range->range.pin_base + i) * 2; + } + } else + dev_err(bank->dev, "Number of pins in range greater " + "than bank width\n"); + } + return 0; +} + static const struct of_device_id omap_gpio_match[]; static int omap_gpio_probe(struct platform_device *pdev) @@ -1204,7 +1329,6 @@ static int omap_gpio_probe(struct platform_device *pdev) omap_gpio_show_rev(bank); pm_runtime_put(bank->dev); - list_add_tail(&bank->node, &omap_gpio_list); return 0; @@ -1333,6 +1457,11 @@ static int omap_gpio_runtime_resume(struct device *dev) } } + if (bank->power_mode == OFF_MODE) { + bank->power_mode = 0; + omap_gpio_pad_ctx_restore(bank); + } + if (!bank->workaround_enabled) { spin_unlock_irqrestore(&bank->lock, flags); return 0; @@ -1405,11 +1534,14 @@ void omap2_gpio_prepare_for_idle(int pwr_mode) bank->power_mode = pwr_mode; + if (pwr_mode == OFF_MODE) + omap_gpio_pad_ctx_save(bank); + pm_runtime_put_sync_suspend(bank->dev); } } -void omap2_gpio_resume_after_idle(void) +void omap2_gpio_resume_after_idle(bool in_suspend) { struct gpio_bank *bank; @@ -1418,6 +1550,11 @@ void omap2_gpio_resume_after_idle(void) continue; pm_runtime_get_sync(bank->dev); + + if (!in_suspend && bank->power_mode == OFF_MODE) { + bank->power_mode = 0; + omap_gpio_pad_ctx_restore(bank); + } } } @@ -1591,4 +1728,23 @@ static int __init omap_gpio_drv_reg(void) { return platform_driver_register(&omap_gpio_driver); } -postcore_initcall(omap_gpio_drv_reg); +postcore_initcall_sync(omap_gpio_drv_reg); + +/* delay off mode initialization until most of things done */ +static int __init omap_gpio_pad_off_ctx_init(void) +{ + struct gpio_bank *bank; + + list_for_each_entry(bank, &omap_gpio_list, node) { + /* + * rescan ranges to protect if pinctrl drv was not ready when + * chip is added. + */ + of_node_put(bank->chip.of_node); + of_gpiochip_add(&bank->chip); + /* no error checking to finsh init and see erros in log only */ + omap_gpio_pad_ctx_init(bank); + } + return 0; +} +late_initcall(omap_gpio_pad_off_ctx_init); |