| From 182740a085e83bd94a291554eee572946ada657e Mon Sep 17 00:00:00 2001 |
| From: Michal Simek <michal.simek@xilinx.com> |
| Date: Mon, 3 Jun 2013 14:31:17 +0200 |
| Subject: GPIO: xilinx: Add support for dual channel |
| |
| Supporting the second channel in the driver. |
| Offset is 0x8 and both channnels share the same |
| IRQ. |
| |
| Signed-off-by: Michal Simek <michal.simek@xilinx.com> |
| Signed-off-by: Linus Walleij <linus.walleij@linaro.org> |
| (cherry picked from commit 74600ee017557b2ebb669e45237f655e9e2fbac8) |
| Signed-off-by: Daniel Sangorrin <daniel.sangorrin@toshiba.co.jp> |
| Signed-off-by: Yoshitake Kobayashi <yoshitake.kobayashi@toshiba.co.jp> |
| --- |
| drivers/gpio/gpio-xilinx.c | 103 +++++++++++++++++++++++++++++++++++++++------ |
| 1 file changed, 91 insertions(+), 12 deletions(-) |
| |
| diff --git a/drivers/gpio/gpio-xilinx.c b/drivers/gpio/gpio-xilinx.c |
| index 2aad53497a63..626eaa876f09 100644 |
| --- a/drivers/gpio/gpio-xilinx.c |
| +++ b/drivers/gpio/gpio-xilinx.c |
| @@ -1,7 +1,7 @@ |
| /* |
| - * Xilinx gpio driver |
| + * Xilinx gpio driver for xps/axi_gpio IP. |
| * |
| - * Copyright 2008 Xilinx, Inc. |
| + * Copyright 2008 - 2013 Xilinx, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 |
| @@ -12,6 +12,7 @@ |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| +#include <linux/bitops.h> |
| #include <linux/init.h> |
| #include <linux/errno.h> |
| #include <linux/module.h> |
| @@ -26,11 +27,26 @@ |
| #define XGPIO_DATA_OFFSET (0x0) /* Data register */ |
| #define XGPIO_TRI_OFFSET (0x4) /* I/O direction register */ |
| |
| +#define XGPIO_CHANNEL_OFFSET 0x8 |
| + |
| +/* Read/Write access to the GPIO registers */ |
| +#define xgpio_readreg(offset) in_be32(offset) |
| +#define xgpio_writereg(offset, val) out_be32(offset, val) |
| + |
| +/** |
| + * struct xgpio_instance - Stores information about GPIO device |
| + * struct of_mm_gpio_chip mmchip: OF GPIO chip for memory mapped banks |
| + * gpio_state: GPIO state shadow register |
| + * gpio_dir: GPIO direction shadow register |
| + * offset: GPIO channel offset |
| + * gpio_lock: Lock used for synchronization |
| + */ |
| struct xgpio_instance { |
| struct of_mm_gpio_chip mmchip; |
| - u32 gpio_state; /* GPIO state shadow register */ |
| - u32 gpio_dir; /* GPIO direction shadow register */ |
| - spinlock_t gpio_lock; /* Lock used for synchronization */ |
| + u32 gpio_state; |
| + u32 gpio_dir; |
| + u32 offset; |
| + spinlock_t gpio_lock; |
| }; |
| |
| /** |
| @@ -44,8 +60,12 @@ struct xgpio_instance { |
| static int xgpio_get(struct gpio_chip *gc, unsigned int gpio) |
| { |
| struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); |
| + struct xgpio_instance *chip = |
| + container_of(mm_gc, struct xgpio_instance, mmchip); |
| |
| - return (in_be32(mm_gc->regs + XGPIO_DATA_OFFSET) >> gpio) & 1; |
| + void __iomem *regs = mm_gc->regs + chip->offset; |
| + |
| + return !!(xgpio_readreg(regs + XGPIO_DATA_OFFSET) & BIT(gpio)); |
| } |
| |
| /** |
| @@ -63,6 +83,7 @@ static void xgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) |
| struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); |
| struct xgpio_instance *chip = |
| container_of(mm_gc, struct xgpio_instance, mmchip); |
| + void __iomem *regs = mm_gc->regs; |
| |
| spin_lock_irqsave(&chip->gpio_lock, flags); |
| |
| @@ -71,7 +92,9 @@ static void xgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) |
| chip->gpio_state |= 1 << gpio; |
| else |
| chip->gpio_state &= ~(1 << gpio); |
| - out_be32(mm_gc->regs + XGPIO_DATA_OFFSET, chip->gpio_state); |
| + |
| + xgpio_writereg(regs + chip->offset + XGPIO_DATA_OFFSET, |
| + chip->gpio_state); |
| |
| spin_unlock_irqrestore(&chip->gpio_lock, flags); |
| } |
| @@ -91,12 +114,13 @@ static int xgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) |
| struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); |
| struct xgpio_instance *chip = |
| container_of(mm_gc, struct xgpio_instance, mmchip); |
| + void __iomem *regs = mm_gc->regs; |
| |
| spin_lock_irqsave(&chip->gpio_lock, flags); |
| |
| /* Set the GPIO bit in shadow register and set direction as input */ |
| chip->gpio_dir |= (1 << gpio); |
| - out_be32(mm_gc->regs + XGPIO_TRI_OFFSET, chip->gpio_dir); |
| + xgpio_writereg(regs + chip->offset + XGPIO_TRI_OFFSET, chip->gpio_dir); |
| |
| spin_unlock_irqrestore(&chip->gpio_lock, flags); |
| |
| @@ -119,6 +143,7 @@ static int xgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) |
| struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc); |
| struct xgpio_instance *chip = |
| container_of(mm_gc, struct xgpio_instance, mmchip); |
| + void __iomem *regs = mm_gc->regs; |
| |
| spin_lock_irqsave(&chip->gpio_lock, flags); |
| |
| @@ -127,11 +152,12 @@ static int xgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) |
| chip->gpio_state |= 1 << gpio; |
| else |
| chip->gpio_state &= ~(1 << gpio); |
| - out_be32(mm_gc->regs + XGPIO_DATA_OFFSET, chip->gpio_state); |
| + xgpio_writereg(regs + chip->offset + XGPIO_DATA_OFFSET, |
| + chip->gpio_state); |
| |
| /* Clear the GPIO bit in shadow register and set direction as output */ |
| chip->gpio_dir &= (~(1 << gpio)); |
| - out_be32(mm_gc->regs + XGPIO_TRI_OFFSET, chip->gpio_dir); |
| + xgpio_writereg(regs + chip->offset + XGPIO_TRI_OFFSET, chip->gpio_dir); |
| |
| spin_unlock_irqrestore(&chip->gpio_lock, flags); |
| |
| @@ -147,8 +173,10 @@ static void xgpio_save_regs(struct of_mm_gpio_chip *mm_gc) |
| struct xgpio_instance *chip = |
| container_of(mm_gc, struct xgpio_instance, mmchip); |
| |
| - out_be32(mm_gc->regs + XGPIO_DATA_OFFSET, chip->gpio_state); |
| - out_be32(mm_gc->regs + XGPIO_TRI_OFFSET, chip->gpio_dir); |
| + xgpio_writereg(mm_gc->regs + chip->offset + XGPIO_DATA_OFFSET, |
| + chip->gpio_state); |
| + xgpio_writereg(mm_gc->regs + chip->offset + XGPIO_TRI_OFFSET, |
| + chip->gpio_dir); |
| } |
| |
| /** |
| @@ -202,6 +230,57 @@ static int xgpio_of_probe(struct device_node *np) |
| np->full_name, status); |
| return status; |
| } |
| + |
| + pr_info("XGpio: %s: registered, base is %d\n", np->full_name, |
| + chip->mmchip.gc.base); |
| + |
| + tree_info = of_get_property(np, "xlnx,is-dual", NULL); |
| + if (tree_info && be32_to_cpup(tree_info)) { |
| + chip = kzalloc(sizeof(*chip), GFP_KERNEL); |
| + if (!chip) |
| + return -ENOMEM; |
| + |
| + /* Add dual channel offset */ |
| + chip->offset = XGPIO_CHANNEL_OFFSET; |
| + |
| + /* Update GPIO state shadow register with default value */ |
| + of_property_read_u32(np, "xlnx,dout-default-2", |
| + &chip->gpio_state); |
| + |
| + /* By default, all pins are inputs */ |
| + chip->gpio_dir = 0xFFFFFFFF; |
| + |
| + /* Update GPIO direction shadow register with default value */ |
| + of_property_read_u32(np, "xlnx,tri-default-2", &chip->gpio_dir); |
| + |
| + /* By default assume full GPIO controller */ |
| + chip->mmchip.gc.ngpio = 32; |
| + |
| + /* Check device node and parent device node for device width */ |
| + of_property_read_u32(np, "xlnx,gpio2-width", |
| + (u32 *)&chip->mmchip.gc.ngpio); |
| + |
| + spin_lock_init(&chip->gpio_lock); |
| + |
| + chip->mmchip.gc.direction_input = xgpio_dir_in; |
| + chip->mmchip.gc.direction_output = xgpio_dir_out; |
| + chip->mmchip.gc.get = xgpio_get; |
| + chip->mmchip.gc.set = xgpio_set; |
| + |
| + chip->mmchip.save_regs = xgpio_save_regs; |
| + |
| + /* Call the OF gpio helper to setup and register the GPIO dev */ |
| + status = of_mm_gpiochip_add(np, &chip->mmchip); |
| + if (status) { |
| + kfree(chip); |
| + pr_err("%s: error in probe function with status %d\n", |
| + np->full_name, status); |
| + return status; |
| + } |
| + pr_info("XGpio: %s: dual channel registered, base is %d\n", |
| + np->full_name, chip->mmchip.gc.base); |
| + } |
| + |
| return 0; |
| } |
| |
| -- |
| 1.8.5.rc3 |
| |