ARM: sa1111: implement a gpio_chip for SA1111 GPIOs
authorRussell King <rmk+kernel@armlinux.org.uk>
Wed, 31 Aug 2016 07:49:45 +0000 (08:49 +0100)
committerRussell King <rmk+kernel@armlinux.org.uk>
Tue, 20 Sep 2016 13:21:08 +0000 (14:21 +0100)
Add a gpio_chip instance for SA1111 GPIOs.  This allows us to use
gpiolib to lookup and manipulate SA1111 GPIOs.

Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
arch/arm/common/sa1111.c

index eaa818d921a9ebab15bb3a798454a857af49ffae..16cc3b5122ad6f06751cef464327058fe8ba4679 100644 (file)
@@ -15,6 +15,7 @@
  * from machine specific code with proper arguments when required.
  */
 #include <linux/module.h>
+#include <linux/gpio/driver.h>
 #include <linux/init.h>
 #include <linux/irq.h>
 #include <linux/kernel.h>
@@ -107,6 +108,7 @@ struct sa1111 {
        spinlock_t      lock;
        void __iomem    *base;
        struct sa1111_platform_data *pdata;
+       struct gpio_chip gc;
 #ifdef CONFIG_PM
        void            *saved_state;
 #endif
@@ -527,6 +529,163 @@ static void sa1111_remove_irq(struct sa1111 *sachip)
        }
 }
 
+enum {
+       SA1111_GPIO_PXDDR = (SA1111_GPIO_PADDR - SA1111_GPIO_PADDR),
+       SA1111_GPIO_PXDRR = (SA1111_GPIO_PADRR - SA1111_GPIO_PADDR),
+       SA1111_GPIO_PXDWR = (SA1111_GPIO_PADWR - SA1111_GPIO_PADDR),
+       SA1111_GPIO_PXSDR = (SA1111_GPIO_PASDR - SA1111_GPIO_PADDR),
+       SA1111_GPIO_PXSSR = (SA1111_GPIO_PASSR - SA1111_GPIO_PADDR),
+};
+
+static struct sa1111 *gc_to_sa1111(struct gpio_chip *gc)
+{
+       return container_of(gc, struct sa1111, gc);
+}
+
+static void __iomem *sa1111_gpio_map_reg(struct sa1111 *sachip, unsigned offset)
+{
+       void __iomem *reg = sachip->base + SA1111_GPIO;
+
+       if (offset < 4)
+               return reg + SA1111_GPIO_PADDR;
+       if (offset < 10)
+               return reg + SA1111_GPIO_PBDDR;
+       if (offset < 18)
+               return reg + SA1111_GPIO_PCDDR;
+       return NULL;
+}
+
+static u32 sa1111_gpio_map_bit(unsigned offset)
+{
+       if (offset < 4)
+               return BIT(offset);
+       if (offset < 10)
+               return BIT(offset - 4);
+       if (offset < 18)
+               return BIT(offset - 10);
+       return 0;
+}
+
+static void sa1111_gpio_modify(void __iomem *reg, u32 mask, u32 set)
+{
+       u32 val;
+
+       val = readl_relaxed(reg);
+       val &= ~mask;
+       val |= mask & set;
+       writel_relaxed(val, reg);
+}
+
+static int sa1111_gpio_get_direction(struct gpio_chip *gc, unsigned offset)
+{
+       struct sa1111 *sachip = gc_to_sa1111(gc);
+       void __iomem *reg = sa1111_gpio_map_reg(sachip, offset);
+       u32 mask = sa1111_gpio_map_bit(offset);
+
+       return !!(readl_relaxed(reg + SA1111_GPIO_PXDDR) & mask);
+}
+
+static int sa1111_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
+{
+       struct sa1111 *sachip = gc_to_sa1111(gc);
+       unsigned long flags;
+       void __iomem *reg = sa1111_gpio_map_reg(sachip, offset);
+       u32 mask = sa1111_gpio_map_bit(offset);
+
+       spin_lock_irqsave(&sachip->lock, flags);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PXDDR, mask, mask);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PXSDR, mask, mask);
+       spin_unlock_irqrestore(&sachip->lock, flags);
+
+       return 0;
+}
+
+static int sa1111_gpio_direction_output(struct gpio_chip *gc, unsigned offset,
+       int value)
+{
+       struct sa1111 *sachip = gc_to_sa1111(gc);
+       unsigned long flags;
+       void __iomem *reg = sa1111_gpio_map_reg(sachip, offset);
+       u32 mask = sa1111_gpio_map_bit(offset);
+
+       spin_lock_irqsave(&sachip->lock, flags);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PXDWR, mask, value ? mask : 0);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PXSSR, mask, value ? mask : 0);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PXDDR, mask, 0);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PXSDR, mask, 0);
+       spin_unlock_irqrestore(&sachip->lock, flags);
+
+       return 0;
+}
+
+static int sa1111_gpio_get(struct gpio_chip *gc, unsigned offset)
+{
+       struct sa1111 *sachip = gc_to_sa1111(gc);
+       void __iomem *reg = sa1111_gpio_map_reg(sachip, offset);
+       u32 mask = sa1111_gpio_map_bit(offset);
+
+       return !!(readl_relaxed(reg + SA1111_GPIO_PXDRR) & mask);
+}
+
+static void sa1111_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
+{
+       struct sa1111 *sachip = gc_to_sa1111(gc);
+       unsigned long flags;
+       void __iomem *reg = sa1111_gpio_map_reg(sachip, offset);
+       u32 mask = sa1111_gpio_map_bit(offset);
+
+       spin_lock_irqsave(&sachip->lock, flags);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PXDWR, mask, value ? mask : 0);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PXSSR, mask, value ? mask : 0);
+       spin_unlock_irqrestore(&sachip->lock, flags);
+}
+
+static void sa1111_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask,
+       unsigned long *bits)
+{
+       struct sa1111 *sachip = gc_to_sa1111(gc);
+       unsigned long flags;
+       void __iomem *reg = sachip->base + SA1111_GPIO;
+       u32 msk, val;
+
+       msk = *mask;
+       val = *bits;
+
+       spin_lock_irqsave(&sachip->lock, flags);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PADWR, msk & 15, val);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PASSR, msk & 15, val);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PBDWR, (msk >> 4) & 255, val >> 4);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PBSSR, (msk >> 4) & 255, val >> 4);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PCDWR, (msk >> 12) & 255, val >> 12);
+       sa1111_gpio_modify(reg + SA1111_GPIO_PCSSR, (msk >> 12) & 255, val >> 12);
+       spin_unlock_irqrestore(&sachip->lock, flags);
+}
+
+static int sa1111_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
+{
+       struct sa1111 *sachip = gc_to_sa1111(gc);
+
+       return sachip->irq_base + offset;
+}
+
+static int sa1111_setup_gpios(struct sa1111 *sachip)
+{
+       sachip->gc.label = "sa1111";
+       sachip->gc.parent = sachip->dev;
+       sachip->gc.owner = THIS_MODULE;
+       sachip->gc.get_direction = sa1111_gpio_get_direction;
+       sachip->gc.direction_input = sa1111_gpio_direction_input;
+       sachip->gc.direction_output = sa1111_gpio_direction_output;
+       sachip->gc.get = sa1111_gpio_get;
+       sachip->gc.set = sa1111_gpio_set;
+       sachip->gc.set_multiple = sa1111_gpio_set_multiple;
+       sachip->gc.to_irq = sa1111_gpio_to_irq;
+       sachip->gc.base = -1;
+       sachip->gc.ngpio = 18;
+
+       return devm_gpiochip_add_data(sachip->dev, &sachip->gc, sachip);
+}
+
 /*
  * Bring the SA1111 out of reset.  This requires a set procedure:
  *  1. nRESET asserted (by hardware)
@@ -773,6 +932,11 @@ static int __sa1111_probe(struct device *me, struct resource *mem, int irq)
                        goto err_clk;
        }
 
+       /* Setup the GPIOs - should really be done after the IRQ setup */
+       ret = sa1111_setup_gpios(sachip);
+       if (ret)
+               goto err_irq;
+
 #ifdef CONFIG_ARCH_SA1100
        {
        unsigned int val;
@@ -815,6 +979,8 @@ static int __sa1111_probe(struct device *me, struct resource *mem, int irq)
 
        return 0;
 
+ err_irq:
+       sa1111_remove_irq(sachip);
  err_clk:
        clk_disable(sachip->clk);
  err_unmap: