Merge branch 'gpio-for-grant' of git://sources.calxeda.com/kernel/linux into gpio...
[firefly-linux-kernel-4.4.55.git] / drivers / gpio / gpio-pl061.c
index 23065f6b3de17ff3ec814e55eda2bb08ca95e03c..2f399ec7bd66705d26dcec5c5baa61bc3d312756 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/amba/bus.h>
 #include <linux/amba/pl061.h>
 #include <linux/slab.h>
+#include <linux/pm.h>
 #include <asm/mach/irq.h>
 
 #define GPIODIR 0x400
 
 #define PL061_GPIO_NR  8
 
+#ifdef CONFIG_PM
+struct pl061_context_save_regs {
+       u8 gpio_data;
+       u8 gpio_dir;
+       u8 gpio_is;
+       u8 gpio_ibe;
+       u8 gpio_iev;
+       u8 gpio_ie;
+};
+#endif
+
 struct pl061_gpio {
        /* Each of the two spinlocks protects a different set of hardware
         * regiters and data structurs. This decouples the code of the IRQ from
@@ -47,6 +59,10 @@ struct pl061_gpio {
        int                     irq_base;
        struct irq_chip_generic *irq_gc;
        struct gpio_chip        gc;
+
+#ifdef CONFIG_PM
+       struct pl061_context_save_regs csave_regs;
+#endif
 };
 
 static int pl061_direction_input(struct gpio_chip *gc, unsigned offset)
@@ -278,6 +294,8 @@ static int pl061_probe(struct amba_device *dev, const struct amba_id *id)
                }
        }
 
+       amba_set_drvdata(dev, chip);
+
        return 0;
 
 iounmap:
@@ -290,6 +308,53 @@ free_mem:
        return ret;
 }
 
+#ifdef CONFIG_PM
+static int pl061_suspend(struct device *dev)
+{
+       struct pl061_gpio *chip = dev_get_drvdata(dev);
+       int offset;
+
+       chip->csave_regs.gpio_data = 0;
+       chip->csave_regs.gpio_dir = readb(chip->base + GPIODIR);
+       chip->csave_regs.gpio_is = readb(chip->base + GPIOIS);
+       chip->csave_regs.gpio_ibe = readb(chip->base + GPIOIBE);
+       chip->csave_regs.gpio_iev = readb(chip->base + GPIOIEV);
+       chip->csave_regs.gpio_ie = readb(chip->base + GPIOIE);
+
+       for (offset = 0; offset < PL061_GPIO_NR; offset++) {
+               if (chip->csave_regs.gpio_dir & (1 << offset))
+                       chip->csave_regs.gpio_data |=
+                               pl061_get_value(&chip->gc, offset) << offset;
+       }
+
+       return 0;
+}
+
+static int pl061_resume(struct device *dev)
+{
+       struct pl061_gpio *chip = dev_get_drvdata(dev);
+       int offset;
+
+       for (offset = 0; offset < PL061_GPIO_NR; offset++) {
+               if (chip->csave_regs.gpio_dir & (1 << offset))
+                       pl061_direction_output(&chip->gc, offset,
+                                       chip->csave_regs.gpio_data &
+                                       (1 << offset));
+               else
+                       pl061_direction_input(&chip->gc, offset);
+       }
+
+       writeb(chip->csave_regs.gpio_is, chip->base + GPIOIS);
+       writeb(chip->csave_regs.gpio_ibe, chip->base + GPIOIBE);
+       writeb(chip->csave_regs.gpio_iev, chip->base + GPIOIEV);
+       writeb(chip->csave_regs.gpio_ie, chip->base + GPIOIE);
+
+       return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(pl061_dev_pm_ops, pl061_suspend, pl061_resume);
+#endif
+
 static struct amba_id pl061_ids[] = {
        {
                .id     = 0x00041061,
@@ -301,6 +366,9 @@ static struct amba_id pl061_ids[] = {
 static struct amba_driver pl061_gpio_driver = {
        .drv = {
                .name   = "pl061_gpio",
+#ifdef CONFIG_PM
+               .pm     = &pl061_dev_pm_ops,
+#endif
        },
        .id_table       = pl061_ids,
        .probe          = pl061_probe,