summaryrefslogtreecommitdiff
path: root/drivers/gpio/gpio-omap.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpio/gpio-omap.c')
-rw-r--r--drivers/gpio/gpio-omap.c159
1 files changed, 155 insertions, 4 deletions
diff --git a/drivers/gpio/gpio-omap.c b/drivers/gpio/gpio-omap.c
index 4a430360af5..77519458954 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;
@@ -1406,6 +1535,9 @@ void omap2_gpio_prepare_for_idle(int pwr_mode)
bank->power_mode = pwr_mode;
pm_runtime_put_sync_suspend(bank->dev);
+
+ if (pwr_mode == OFF_MODE)
+ omap_gpio_pad_ctx_save(bank);
}
}
@@ -1591,4 +1723,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);