From patchwork Tue Dec 24 06:15:04 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Sangorrin X-Patchwork-Id: 3400811 Return-Path: X-Original-To: patchwork-ltsi-dev@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 5C0C9C0D4A for ; Tue, 24 Dec 2013 06:15:57 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 1AF6720148 for ; Tue, 24 Dec 2013 06:15:55 +0000 (UTC) Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) by mail.kernel.org (Postfix) with ESMTP id A411D200E3 for ; Tue, 24 Dec 2013 06:15:52 +0000 (UTC) Received: from mail.linux-foundation.org (localhost [IPv6:::1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 56DB39A8; Tue, 24 Dec 2013 06:15:33 +0000 (UTC) X-Original-To: ltsi-dev@lists.linuxfoundation.org Delivered-To: ltsi-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTP id 6B826942 for ; Tue, 24 Dec 2013 06:15:31 +0000 (UTC) X-Greylist: from auto-whitelisted by SQLgrey-1.7.6 Received: from imx12.toshiba.co.jp (imx12.toshiba.co.jp [61.202.160.132]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 5E6C01F88C for ; Tue, 24 Dec 2013 06:15:23 +0000 (UTC) Received: from tsbmgw-mgw01.tsbmgw-mgw01.toshiba.co.jp ([133.199.232.103]) by imx12.toshiba.co.jp with ESMTP id rBO6FJNZ016154 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO); Tue, 24 Dec 2013 15:15:19 +0900 (JST) Received: from tsbmgw-mgw01 (localhost [127.0.0.1]) by tsbmgw-mgw01.tsbmgw-mgw01.toshiba.co.jp (8.13.8/8.14.5) with ESMTP id rBO6FJFj004037; Tue, 24 Dec 2013 15:15:19 +0900 Received: from localhost ([127.0.0.1]) by tsbmgw-mgw01 (JAMES SMTP Server 2.3.1) with SMTP ID 731; Tue, 24 Dec 2013 15:15:18 +0900 (JST) Received: from arc11.toshiba.co.jp ([133.199.90.127]) by tsbmgw-mgw01.tsbmgw-mgw01.toshiba.co.jp (8.13.8/8.14.5) with ESMTP id rBO6FIh7004001; Tue, 24 Dec 2013 15:15:18 +0900 Received: (from root@localhost) by arc11.toshiba.co.jp id rBO6FIvT028798; Tue, 24 Dec 2013 15:15:18 +0900 (JST) Received: from ovp11.toshiba.co.jp [133.199.90.148] by arc11.toshiba.co.jp with ESMTP id RAA28789; Tue, 24 Dec 2013 15:15:18 +0900 Received: from mx2.toshiba.co.jp (localhost [127.0.0.1]) by ovp11.toshiba.co.jp with ESMTP id rBO6FHtJ000195; Tue, 24 Dec 2013 15:15:18 +0900 (JST) Received: from BK2211.rdc.toshiba.co.jp by toshiba.co.jp id rBO6FCaE025859; Tue, 24 Dec 2013 15:15:16 +0900 (JST) Received: from island.swc.toshiba.co.jp (localhost [127.0.0.1]) by BK2211.rdc.toshiba.co.jp (8.13.8+Sun/8.13.8) with ESMTP id rBO6FBlX019198; Tue, 24 Dec 2013 15:15:11 +0900 (JST) Received: from ubuntu.swc.toshiba.co.jp (unknown [133.196.174.184]) by island.swc.toshiba.co.jp (Postfix) with ESMTP id 6E31F40007; Tue, 24 Dec 2013 15:14:22 +0900 (JST) From: Daniel Sangorrin To: ltsi-dev@lists.linuxfoundation.org Date: Tue, 24 Dec 2013 15:15:04 +0900 Message-Id: <1387865711-23124-6-git-send-email-daniel.sangorrin@toshiba.co.jp> X-Mailer: git-send-email 1.8.5 In-Reply-To: <1387865711-23124-1-git-send-email-daniel.sangorrin@toshiba.co.jp> References: <1387865711-23124-1-git-send-email-daniel.sangorrin@toshiba.co.jp> X-Spam-Status: No, score=-2.4 required=5.0 tests=BAYES_00,RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org Cc: michal.simek@xilinx.com Subject: [LTSI-dev] [PATCH 05/12] gpio: xilinx: merge Xilinx gpio support into LTSI 3.10.y X-BeenThere: ltsi-dev@lists.linuxfoundation.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: "A list to discuss patches, development, and other things related to the LTSI project" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ltsi-dev-bounces@lists.linuxfoundation.org Errors-To: ltsi-dev-bounces@lists.linuxfoundation.org X-Virus-Scanned: ClamAV using ClamSMTP From: Soren Brinkmann This commits merges support for the GPIO bus from the Xilinx master branch (commit efc27505715e64526653f35274717c0fc56491e3 in master branch). Signed-off-by: Daniel Sangorrin Signed-off-by: Yoshitake Kobayashi --- drivers/gpio/Kconfig | 12 +- drivers/gpio/Makefile | 1 + drivers/gpio/gpio-xilinx.c | 321 +++++++++++++++++-- drivers/gpio/gpio-xilinxps.c | 722 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1026 insertions(+), 30 deletions(-) create mode 100644 drivers/gpio/gpio-xilinxps.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 573c449..97021aa 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -234,9 +234,17 @@ config GPIO_TS5500 config GPIO_XILINX bool "Xilinx GPIO support" - depends on PPC_OF || MICROBLAZE + depends on PPC_OF || MICROBLAZE || ARCH_ZYNQ + select GENERIC_IRQ_CHIP + help + Say yes here to support the Xilinx AXI/XPS GPIO device + +config GPIO_XILINX_PS + tristate "Xilinx GPIO PS" + depends on ARCH_ZYNQ + select GENERIC_IRQ_CHIP help - Say yes here to support the Xilinx FPGA GPIO device + Say yes here to support Xilinx GPIO PS controller config GPIO_VR41XX tristate "NEC VR4100 series General-purpose I/O Uint support" diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 0cb2d65..2636985 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -87,3 +87,4 @@ obj-$(CONFIG_GPIO_WM831X) += gpio-wm831x.o obj-$(CONFIG_GPIO_WM8350) += gpio-wm8350.o obj-$(CONFIG_GPIO_WM8994) += gpio-wm8994.o obj-$(CONFIG_GPIO_XILINX) += gpio-xilinx.o +obj-$(CONFIG_GPIO_XILINX_PS) += gpio-xilinxps.o diff --git a/drivers/gpio/gpio-xilinx.c b/drivers/gpio/gpio-xilinx.c index 792a05a..566a306 100644 --- a/drivers/gpio/gpio-xilinx.c +++ b/drivers/gpio/gpio-xilinx.c @@ -17,15 +17,25 @@ #include #include #include +#include #include #include +#include #include +#include +#include +#include #include #include /* Register Offset Definitions */ -#define XGPIO_DATA_OFFSET (0x0) /* Data register */ -#define XGPIO_TRI_OFFSET (0x4) /* I/O direction register */ +#define XGPIO_DATA_OFFSET 0x0 /* Data register */ +#define XGPIO_TRI_OFFSET 0x4 /* I/O direction register */ +#define XGPIO_GIER_OFFSET 0x11c /* Global Interrupt Enable */ +#define XGPIO_GIER_IE BIT(31) + +#define XGPIO_IPISR_OFFSET 0x120 /* IP Interrupt Status */ +#define XGPIO_IPIER_OFFSET 0x128 /* IP Interrupt Enable */ #define XGPIO_CHANNEL_OFFSET 0x8 @@ -40,18 +50,24 @@ /** * 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 + * @mmchip: OF GPIO chip for memory mapped banks + * @gpio_state: GPIO state shadow register + * @gpio_dir: GPIO direction shadow register + * @offset: GPIO channel offset + * @irq_base: GPIO channel irq base address + * @irq_enable: GPIO irq enable/disable bitfield + * @gpio_lock: Lock used for synchronization + * @irq_domain: irq_domain of the controller */ struct xgpio_instance { struct of_mm_gpio_chip mmchip; u32 gpio_state; u32 gpio_dir; u32 offset; + int irq_base; + u32 irq_enable; spinlock_t gpio_lock; + struct irq_domain *irq_domain; }; /** @@ -59,8 +75,11 @@ struct xgpio_instance { * @gc: Pointer to gpio_chip device structure. * @gpio: GPIO signal number. * - * This function reads the specified signal of the GPIO device. It returns 0 if - * the signal clear, 1 if signal is set or negative value on error. + * This function reads the specified signal of the GPIO device. + * + * Return: + * 0 if direction of GPIO signals is set as input otherwise it + * returns negative error value. */ static int xgpio_get(struct gpio_chip *gc, unsigned int gpio) { @@ -110,8 +129,10 @@ static void xgpio_set(struct gpio_chip *gc, unsigned int gpio, int val) * @gpio: GPIO signal number. * * This function sets the direction of specified GPIO signal as input. - * It returns 0 if direction of GPIO signals is set as input otherwise it - * returns negative error value. + * + * Return: + * 0 - if direction of GPIO signals is set as input + * otherwise it returns negative error value. */ static int xgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) { @@ -138,8 +159,10 @@ static int xgpio_dir_in(struct gpio_chip *gc, unsigned int gpio) * @gpio: GPIO signal number. * @val: Value to be written to specified signal. * - * This function sets the direction of specified GPIO signal as output. If all - * GPIO signals of GPIO chip is configured as input then it returns + * This function sets the direction of specified GPIO signal as output. + * + * Return: + * If all GPIO signals of GPIO chip is configured as input then it returns * error otherwise it returns 0. */ static int xgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) @@ -171,7 +194,7 @@ static int xgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val) /** * xgpio_save_regs - Set initial values of GPIO pins - * @mm_gc: pointer to memory mapped GPIO chip structure + * @mm_gc: Pointer to memory mapped GPIO chip structure */ static void xgpio_save_regs(struct of_mm_gpio_chip *mm_gc) { @@ -185,20 +208,245 @@ static void xgpio_save_regs(struct of_mm_gpio_chip *mm_gc) } /** + * xgpio_xlate - Set initial values of GPIO pins + * @gc: Pointer to gpio_chip device structure. + * @gpiospec: gpio specifier as found in the device tree + * @flags: A flags pointer based on binding + * + * Return: + * irq number otherwise -EINVAL + */ +static int xgpio_xlate(struct gpio_chip *gc, + const struct of_phandle_args *gpiospec, u32 *flags) +{ + 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); + + if (gpiospec->args[1] == chip->offset) + return gpiospec->args[0]; + + return -EINVAL; +} + +/** + * xgpio_irq_mask - Write the specified signal of the GPIO device. + * @irq_data: per irq and chip data passed down to chip functions + */ +static void xgpio_irq_mask(struct irq_data *irq_data) +{ + unsigned long flags; + struct xgpio_instance *chip = irq_data_get_irq_chip_data(irq_data); + struct of_mm_gpio_chip *mm_gc = &chip->mmchip; + u32 offset = irq_data->irq - chip->irq_base; + u32 temp; + + pr_debug("%s: Disable %d irq, irq_enable_mask 0x%x\n", + __func__, offset, chip->irq_enable); + + spin_lock_irqsave(&chip->gpio_lock, flags); + + chip->irq_enable &= ~BIT(offset); + + if (!chip->irq_enable) { + /* Enable per channel interrupt */ + temp = xgpio_readreg(mm_gc->regs + XGPIO_IPIER_OFFSET); + temp &= chip->offset / XGPIO_CHANNEL_OFFSET + 1; + xgpio_writereg(mm_gc->regs + XGPIO_IPIER_OFFSET, temp); + + /* Disable global interrupt if channel interrupts are unused */ + temp = xgpio_readreg(mm_gc->regs + XGPIO_IPIER_OFFSET); + if (!temp) + xgpio_writereg(mm_gc->regs + XGPIO_GIER_OFFSET, + ~XGPIO_GIER_IE); + + } + spin_unlock_irqrestore(&chip->gpio_lock, flags); +} + +/** + * xgpio_irq_unmask - Write the specified signal of the GPIO device. + * @irq_data: per irq and chip data passed down to chip functions + */ +static void xgpio_irq_unmask(struct irq_data *irq_data) +{ + unsigned long flags; + struct xgpio_instance *chip = irq_data_get_irq_chip_data(irq_data); + struct of_mm_gpio_chip *mm_gc = &chip->mmchip; + u32 offset = irq_data->irq - chip->irq_base; + u32 temp; + + pr_debug("%s: Enable %d irq, irq_enable_mask 0x%x\n", + __func__, offset, chip->irq_enable); + + /* Setup pin as input */ + xgpio_dir_in(&mm_gc->gc, offset); + + spin_lock_irqsave(&chip->gpio_lock, flags); + + chip->irq_enable |= BIT(offset); + + if (chip->irq_enable) { + + /* Enable per channel interrupt */ + temp = xgpio_readreg(mm_gc->regs + XGPIO_IPIER_OFFSET); + temp |= chip->offset / XGPIO_CHANNEL_OFFSET + 1; + xgpio_writereg(mm_gc->regs + XGPIO_IPIER_OFFSET, temp); + + /* Enable global interrupts */ + xgpio_writereg(mm_gc->regs + XGPIO_GIER_OFFSET, XGPIO_GIER_IE); + } + + spin_unlock_irqrestore(&chip->gpio_lock, flags); +} + +/** + * xgpio_set_irq_type - Write the specified signal of the GPIO device. + * @irq_data: Per irq and chip data passed down to chip functions + * @type: Interrupt type that is to be set for the gpio pin + * + * Return: + * 0 if interrupt type is supported otherwise otherwise -EINVAL + */ +static int xgpio_set_irq_type(struct irq_data *irq_data, unsigned int type) +{ + /* Only rising edge case is supported now */ + if (type == IRQ_TYPE_EDGE_RISING) + return 0; + + return -EINVAL; +} + +/* irq chip descriptor */ +static struct irq_chip xgpio_irqchip = { + .name = "xgpio", + .irq_mask = xgpio_irq_mask, + .irq_unmask = xgpio_irq_unmask, + .irq_set_type = xgpio_set_irq_type, +}; + +/** + * xgpio_to_irq - Find out gpio to Linux irq mapping + * @gc: Pointer to gpio_chip device structure. + * @offset: Gpio pin offset + * + * Return: + * irq number otherwise -EINVAL + */ +static int xgpio_to_irq(struct gpio_chip *gc, unsigned offset) +{ + 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 irq_find_mapping(chip->irq_domain, offset); +} + +/** + * xgpio_irqhandler - Gpio interrupt service routine + * @irq: gpio irq number + * @desc: Pointer to interrupt description + */ +static void xgpio_irqhandler(unsigned int irq, struct irq_desc *desc) +{ + struct xgpio_instance *chip = (struct xgpio_instance *) + irq_get_handler_data(irq); + struct of_mm_gpio_chip *mm_gc = &chip->mmchip; + struct irq_chip *irqchip = irq_desc_get_chip(desc); + int offset; + unsigned long val; + + chained_irq_enter(irqchip, desc); + + val = xgpio_readreg(mm_gc->regs + chip->offset); + /* Only rising edge is supported */ + val &= chip->irq_enable; + + for_each_set_bit(offset, &val, chip->mmchip.gc.ngpio) { + generic_handle_irq(chip->irq_base + offset); + } + + xgpio_writereg(mm_gc->regs + XGPIO_IPISR_OFFSET, + chip->offset / XGPIO_CHANNEL_OFFSET + 1); + + chained_irq_exit(irqchip, desc); +} + +static struct lock_class_key gpio_lock_class; + +/** + * xgpio_irq_setup - Allocate irq for gpio and setup appropriate functions + * @np: Device node of the GPIO chip + * @chip: Pointer to private gpio channel structure + * + * Return: + * 0 if success, otherwise -1 + */ +static int xgpio_irq_setup(struct device_node *np, struct xgpio_instance *chip) +{ + u32 pin_num; + struct resource res; + + int ret = of_irq_to_resource(np, 0, &res); + if (!ret) { + pr_info("GPIO IRQ not connected\n"); + return 0; + } + + chip->mmchip.gc.of_xlate = xgpio_xlate; + chip->mmchip.gc.of_gpio_n_cells = 2; + chip->mmchip.gc.to_irq = xgpio_to_irq; + + chip->irq_base = irq_alloc_descs(-1, 0, chip->mmchip.gc.ngpio, 0); + if (chip->irq_base < 0) { + pr_err("Couldn't allocate IRQ numbers\n"); + return -1; + } + chip->irq_domain = irq_domain_add_legacy(np, chip->mmchip.gc.ngpio, + chip->irq_base, 0, + &irq_domain_simple_ops, NULL); + + /* + * set the irq chip, handler and irq chip data for callbacks for + * each pin + */ + for (pin_num = 0; pin_num < chip->mmchip.gc.ngpio; pin_num++) { + u32 gpio_irq = irq_find_mapping(chip->irq_domain, pin_num); + irq_set_lockdep_class(gpio_irq, &gpio_lock_class); + pr_debug("IRQ Base: %d, Pin %d = IRQ %d\n", + chip->irq_base, pin_num, gpio_irq); + irq_set_chip_and_handler(gpio_irq, &xgpio_irqchip, + handle_simple_irq); + irq_set_chip_data(gpio_irq, (void *)chip); +#ifdef CONFIG_ARCH_ZYNQ + set_irq_flags(gpio_irq, IRQF_VALID); +#endif + } + irq_set_handler_data(res.start, (void *)chip); + irq_set_chained_handler(res.start, xgpio_irqhandler); + + return 0; +} + +/** * xgpio_of_probe - Probe method for the GPIO device. * @np: pointer to device tree node * * This function probes the GPIO device in the device tree. It initializes the - * driver data structure. It returns 0, if the driver is bound to the GPIO - * device, or a negative value if there is an error. + * driver data structure. + * + * Return: + * It returns 0, if the driver is bound to the GPIO device, or + * a negative value if there is an error. */ -static int xgpio_of_probe(struct device_node *np) +static int xgpio_of_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; struct xgpio_instance *chip; int status = 0; const u32 *tree_info; - chip = kzalloc(sizeof(*chip), GFP_KERNEL); + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; @@ -230,18 +478,24 @@ static int xgpio_of_probe(struct device_node *np) /* Call the OF gpio helper to setup and register the GPIO device */ 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; } + status = xgpio_irq_setup(np, chip); + if (status) { + pr_err("%s: GPIO IRQ initialization failed %d\n", + 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); + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; @@ -274,12 +528,18 @@ static int xgpio_of_probe(struct device_node *np) chip->mmchip.save_regs = xgpio_save_regs; + status = xgpio_irq_setup(np, chip); + if (status) { + pr_err("%s: GPIO IRQ initialization failed %d\n", + np->full_name, status); + return status; + } + /* 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); + np->full_name, status); return status; } pr_info("XGpio: %s: dual channel registered, base is %d\n", @@ -293,15 +553,20 @@ static struct of_device_id xgpio_of_match[] = { { .compatible = "xlnx,xps-gpio-1.00.a", }, { /* end of list */ }, }; +MODULE_DEVICE_TABLE(of, xgpio_of_match); + +static struct platform_driver xilinx_gpio_driver = { + .probe = xgpio_of_probe, + .driver = { + .owner = THIS_MODULE, + .name = "xilinx-gpio", + .of_match_table = xgpio_of_match, + }, +}; static int __init xgpio_init(void) { - struct device_node *np; - - for_each_matching_node(np, xgpio_of_match) - xgpio_of_probe(np); - - return 0; + return platform_driver_register(&xilinx_gpio_driver); } /* Make sure we get initialized before anyone else tries to use us */ diff --git a/drivers/gpio/gpio-xilinxps.c b/drivers/gpio/gpio-xilinxps.c new file mode 100644 index 0000000..d6b681b --- /dev/null +++ b/drivers/gpio/gpio-xilinxps.c @@ -0,0 +1,722 @@ +/* + * Xilinx PS GPIO device driver + * + * 2009-2011 (c) Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 675 Mass + * Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "xgpiops" +#define XGPIOPS_NR_GPIOS 118 + +static struct irq_domain *irq_domain; + +/* Register offsets for the GPIO device */ + +#define XGPIOPS_DATA_LSW_OFFSET(BANK) (0x000 + (8 * BANK)) /* LSW Mask & + Data -WO */ +#define XGPIOPS_DATA_MSW_OFFSET(BANK) (0x004 + (8 * BANK)) /* MSW Mask & + Data -WO */ +#define XGPIOPS_DATA_OFFSET(BANK) (0x040 + (4 * BANK)) /* Data Register + -RW */ +#define XGPIOPS_DIRM_OFFSET(BANK) (0x204 + (0x40 * BANK)) /* Direction + mode reg-RW */ +#define XGPIOPS_OUTEN_OFFSET(BANK) (0x208 + (0x40 * BANK)) /* Output + enable reg-RW + */ +#define XGPIOPS_INTMASK_OFFSET(BANK) (0x20C + (0x40 * BANK)) /* Interrupt + mask reg-RO */ +#define XGPIOPS_INTEN_OFFSET(BANK) (0x210 + (0x40 * BANK)) /* Interrupt + enable reg-WO + */ +#define XGPIOPS_INTDIS_OFFSET(BANK) (0x214 + (0x40 * BANK)) /* Interrupt + disable reg-WO + */ +#define XGPIOPS_INTSTS_OFFSET(BANK) (0x218 + (0x40 * BANK)) /* Interrupt + status reg-RO + */ +#define XGPIOPS_INTTYPE_OFFSET(BANK) (0x21C + (0x40 * BANK)) /* Interrupt + type reg-RW + */ +#define XGPIOPS_INTPOL_OFFSET(BANK) (0x220 + (0x40 * BANK)) /* Interrupt + polarity reg + -RW */ +#define XGPIOPS_INTANY_OFFSET(BANK) (0x224 + (0x40 * BANK)) /* Interrupt on + any, reg-RW */ + +/* Read/Write access to the GPIO PS registers */ +#define xgpiops_readreg(offset) __raw_readl(offset) +#define xgpiops_writereg(val, offset) __raw_writel(val, offset) + +static unsigned int xgpiops_pin_table[] = { + 31, /* 0 - 31 */ + 53, /* 32 - 53 */ + 85, /* 54 - 85 */ + 117 /* 86 - 117 */ +}; + +/** + * struct xgpiops - gpio device private data structure + * @chip: instance of the gpio_chip + * @base_addr: base address of the GPIO device + * @gpio_lock: lock used for synchronization + */ +struct xgpiops { + struct gpio_chip chip; + void __iomem *base_addr; + unsigned int irq; + unsigned int irq_base; + struct clk *clk; + spinlock_t gpio_lock; +}; + +/** + * xgpiops_get_bank_pin - Get the bank number and pin number within that bank + * for a given pin in the GPIO device + * @pin_num: gpio pin number within the device + * @bank_num: an output parameter used to return the bank number of the gpio + * pin + * @bank_pin_num: an output parameter used to return pin number within a bank + * for the given gpio pin + * + * Returns the bank number. + */ +static inline void xgpiops_get_bank_pin(unsigned int pin_num, + unsigned int *bank_num, + unsigned int *bank_pin_num) +{ + for (*bank_num = 0; *bank_num < 4; (*bank_num)++) + if (pin_num <= xgpiops_pin_table[*bank_num]) + break; + + if (*bank_num == 0) + *bank_pin_num = pin_num; + else + *bank_pin_num = pin_num % + (xgpiops_pin_table[*bank_num - 1] + 1); +} + +/** + * xgpiops_get_value - Get the state of the specified pin of GPIO device + * @chip: gpio_chip instance to be worked on + * @pin: gpio pin number within the device + * + * This function reads the state of the specified pin of the GPIO device. + * It returns 0 if the pin is low, 1 if pin is high. + */ +static int xgpiops_get_value(struct gpio_chip *chip, unsigned int pin) +{ + unsigned int bank_num, bank_pin_num; + struct xgpiops *gpio = container_of(chip, struct xgpiops, chip); + + xgpiops_get_bank_pin(pin, &bank_num, &bank_pin_num); + + return (xgpiops_readreg(gpio->base_addr + + XGPIOPS_DATA_OFFSET(bank_num)) >> + bank_pin_num) & 1; +} + +/** + * xgpiops_set_value - Modify the state of the pin with specified value + * @chip: gpio_chip instance to be worked on + * @pin: gpio pin number within the device + * @state: value used to modify the state of the specified pin + * + * This function calculates the register offset (i.e to lower 16 bits or + * upper 16 bits) based on the given pin number and sets the state of a + * gpio pin to the specified value. The state is either 0 or non-zero. + */ +static void xgpiops_set_value(struct gpio_chip *chip, unsigned int pin, + int state) +{ + unsigned long flags; + unsigned int reg_offset; + unsigned int bank_num, bank_pin_num; + struct xgpiops *gpio = container_of(chip, struct xgpiops, chip); + + xgpiops_get_bank_pin(pin, &bank_num, &bank_pin_num); + + if (bank_pin_num >= 16) { + bank_pin_num -= 16; /* only 16 data bits in bit maskable reg */ + reg_offset = XGPIOPS_DATA_MSW_OFFSET(bank_num); + } else { + reg_offset = XGPIOPS_DATA_LSW_OFFSET(bank_num); + } + + /* + * get the 32 bit value to be written to the mask/data register where + * the upper 16 bits is the mask and lower 16 bits is the data + */ + if (state) + state = 1; + state = ~(1 << (bank_pin_num + 16)) & ((state << bank_pin_num) | + 0xFFFF0000); + + spin_lock_irqsave(&gpio->gpio_lock, flags); + xgpiops_writereg(state, gpio->base_addr + reg_offset); + spin_unlock_irqrestore(&gpio->gpio_lock, flags); +} + +/** + * xgpiops_dir_in - Set the direction of the specified GPIO pin as input + * @chip: gpio_chip instance to be worked on + * @pin: gpio pin number within the device + * + * This function uses the read-modify-write sequence to set the direction of + * the gpio pin as input. Returns 0 always. + */ +static int xgpiops_dir_in(struct gpio_chip *chip, unsigned int pin) +{ + unsigned int reg, bank_num, bank_pin_num; + struct xgpiops *gpio = container_of(chip, struct xgpiops, chip); + + xgpiops_get_bank_pin(pin, &bank_num, &bank_pin_num); + /* clear the bit in direction mode reg to set the pin as input */ + reg = xgpiops_readreg(gpio->base_addr + XGPIOPS_DIRM_OFFSET(bank_num)); + reg &= ~(1 << bank_pin_num); + xgpiops_writereg(reg, gpio->base_addr + XGPIOPS_DIRM_OFFSET(bank_num)); + + return 0; +} + +/** + * xgpiops_dir_out - Set the direction of the specified GPIO pin as output + * @chip: gpio_chip instance to be worked on + * @pin: gpio pin number within the device + * @state: value to be written to specified pin + * + * This function sets the direction of specified GPIO pin as output, configures + * the Output Enable register for the pin and uses xgpiops_set to set the state + * of the pin to the value specified. Returns 0 always. + */ +static int xgpiops_dir_out(struct gpio_chip *chip, unsigned int pin, int state) +{ + struct xgpiops *gpio = container_of(chip, struct xgpiops, chip); + unsigned int reg, bank_num, bank_pin_num; + + xgpiops_get_bank_pin(pin, &bank_num, &bank_pin_num); + + /* set the GPIO pin as output */ + reg = xgpiops_readreg(gpio->base_addr + XGPIOPS_DIRM_OFFSET(bank_num)); + reg |= 1 << bank_pin_num; + xgpiops_writereg(reg, gpio->base_addr + XGPIOPS_DIRM_OFFSET(bank_num)); + + /* configure the output enable reg for the pin */ + reg = xgpiops_readreg(gpio->base_addr + XGPIOPS_OUTEN_OFFSET(bank_num)); + reg |= 1 << bank_pin_num; + xgpiops_writereg(reg, gpio->base_addr + XGPIOPS_OUTEN_OFFSET(bank_num)); + + /* set the state of the pin */ + xgpiops_set_value(chip, pin, state); + return 0; +} + +static int xgpiops_to_irq(struct gpio_chip *chip, unsigned offset) +{ + return irq_find_mapping(irq_domain, offset); +} + +/** + * xgpiops_irq_ack - Acknowledge the interrupt of a gpio pin + * @irq_data: irq data containing irq number of gpio pin for the irq to ack + * + * This function calculates gpio pin number from irq number and sets the bit + * in the Interrupt Status Register of the corresponding bank, to ACK the irq. + */ +static void xgpiops_irq_ack(struct irq_data *irq_data) +{ + struct xgpiops *gpio = (struct xgpiops *) + irq_data_get_irq_chip_data(irq_data); + unsigned int device_pin_num, bank_num, bank_pin_num; + + device_pin_num = irq_data->hwirq; + xgpiops_get_bank_pin(device_pin_num, &bank_num, &bank_pin_num); + xgpiops_writereg(1 << bank_pin_num, gpio->base_addr + + (XGPIOPS_INTSTS_OFFSET(bank_num))); +} + +/** + * xgpiops_irq_mask - Disable the interrupts for a gpio pin + * @irq: irq number of gpio pin for which interrupt is to be disabled + * + * This function calculates gpio pin number from irq number and sets the + * bit in the Interrupt Disable register of the corresponding bank to disable + * interrupts for that pin. + */ +static void xgpiops_irq_mask(struct irq_data *irq_data) +{ + struct xgpiops *gpio = (struct xgpiops *) + irq_data_get_irq_chip_data(irq_data); + unsigned int device_pin_num, bank_num, bank_pin_num; + + device_pin_num = irq_data->hwirq; + xgpiops_get_bank_pin(device_pin_num, &bank_num, &bank_pin_num); + xgpiops_writereg(1 << bank_pin_num, + gpio->base_addr + XGPIOPS_INTDIS_OFFSET(bank_num)); +} + +/** + * xgpiops_irq_unmask - Enable the interrupts for a gpio pin + * @irq_data: irq data containing irq number of gpio pin for the irq to enable + * + * This function calculates the gpio pin number from irq number and sets the + * bit in the Interrupt Enable register of the corresponding bank to enable + * interrupts for that pin. + */ +static void xgpiops_irq_unmask(struct irq_data *irq_data) +{ + struct xgpiops *gpio = irq_data_get_irq_chip_data(irq_data); + unsigned int device_pin_num, bank_num, bank_pin_num; + + device_pin_num = irq_data->hwirq; + xgpiops_get_bank_pin(device_pin_num, &bank_num, &bank_pin_num); + xgpiops_writereg(1 << bank_pin_num, + gpio->base_addr + XGPIOPS_INTEN_OFFSET(bank_num)); +} + +/** + * xgpiops_set_irq_type - Set the irq type for a gpio pin + * @irq_data: irq data containing irq number of gpio pin + * @type: interrupt type that is to be set for the gpio pin + * + * This function gets the gpio pin number and its bank from the gpio pin number + * and configures the INT_TYPE, INT_POLARITY and INT_ANY registers. Returns 0, + * negative error otherwise. + * TYPE-EDGE_RISING, INT_TYPE - 1, INT_POLARITY - 1, INT_ANY - 0; + * TYPE-EDGE_FALLING, INT_TYPE - 1, INT_POLARITY - 0, INT_ANY - 0; + * TYPE-EDGE_BOTH, INT_TYPE - 1, INT_POLARITY - NA, INT_ANY - 1; + * TYPE-LEVEL_HIGH, INT_TYPE - 0, INT_POLARITY - 1, INT_ANY - NA; + * TYPE-LEVEL_LOW, INT_TYPE - 0, INT_POLARITY - 0, INT_ANY - NA + */ +static int xgpiops_set_irq_type(struct irq_data *irq_data, unsigned int type) +{ + struct xgpiops *gpio = irq_data_get_irq_chip_data(irq_data); + unsigned int device_pin_num, bank_num, bank_pin_num; + unsigned int int_type, int_pol, int_any; + + device_pin_num = irq_data->hwirq; + xgpiops_get_bank_pin(device_pin_num, &bank_num, &bank_pin_num); + + int_type = xgpiops_readreg(gpio->base_addr + + XGPIOPS_INTTYPE_OFFSET(bank_num)); + int_pol = xgpiops_readreg(gpio->base_addr + + XGPIOPS_INTPOL_OFFSET(bank_num)); + int_any = xgpiops_readreg(gpio->base_addr + + XGPIOPS_INTANY_OFFSET(bank_num)); + + /* + * based on the type requested, configure the INT_TYPE, INT_POLARITY + * and INT_ANY registers + */ + switch (type) { + case IRQ_TYPE_EDGE_RISING: + int_type |= (1 << bank_pin_num); + int_pol |= (1 << bank_pin_num); + int_any &= ~(1 << bank_pin_num); + break; + case IRQ_TYPE_EDGE_FALLING: + int_type |= (1 << bank_pin_num); + int_pol &= ~(1 << bank_pin_num); + int_any &= ~(1 << bank_pin_num); + break; + case IRQ_TYPE_EDGE_BOTH: + int_type |= (1 << bank_pin_num); + int_any |= (1 << bank_pin_num); + break; + case IRQ_TYPE_LEVEL_HIGH: + int_type &= ~(1 << bank_pin_num); + int_pol |= (1 << bank_pin_num); + break; + case IRQ_TYPE_LEVEL_LOW: + int_type &= ~(1 << bank_pin_num); + int_pol &= ~(1 << bank_pin_num); + break; + default: + return -EINVAL; + } + + xgpiops_writereg(int_type, + gpio->base_addr + XGPIOPS_INTTYPE_OFFSET(bank_num)); + xgpiops_writereg(int_pol, + gpio->base_addr + XGPIOPS_INTPOL_OFFSET(bank_num)); + xgpiops_writereg(int_any, + gpio->base_addr + XGPIOPS_INTANY_OFFSET(bank_num)); + return 0; +} + +static int xgpiops_set_wake(struct irq_data *data, unsigned int on) +{ + if (on) + xgpiops_irq_unmask(data); + else + xgpiops_irq_mask(data); + + return 0; +} + +/* irq chip descriptor */ +static struct irq_chip xgpiops_irqchip = { + .name = DRIVER_NAME, + .irq_ack = xgpiops_irq_ack, + .irq_mask = xgpiops_irq_mask, + .irq_unmask = xgpiops_irq_unmask, + .irq_set_type = xgpiops_set_irq_type, + .irq_set_wake = xgpiops_set_wake, +}; + +/** + * xgpiops_irqhandler - IRQ handler for the gpio banks of a gpio device + * @irq: irq number of the gpio bank where interrupt has occurred + * @desc: irq descriptor instance of the 'irq' + * + * This function reads the Interrupt Status Register of each bank to get the + * gpio pin number which has triggered an interrupt. It then acks the triggered + * interrupt and calls the pin specific handler set by the higher layer + * application for that pin. + * Note: A bug is reported if no handler is set for the gpio pin. + */ +static void xgpiops_irqhandler(unsigned int irq, struct irq_desc *desc) +{ + struct xgpiops *gpio = (struct xgpiops *)irq_get_handler_data(irq); + int gpio_irq = gpio->irq_base; + unsigned int int_sts, int_enb, bank_num; + struct irq_desc *gpio_irq_desc; + struct irq_chip *chip = irq_desc_get_chip(desc); + + chained_irq_enter(chip, desc); + + for (bank_num = 0; bank_num < 4; bank_num++) { + int_sts = xgpiops_readreg(gpio->base_addr + + XGPIOPS_INTSTS_OFFSET(bank_num)); + int_enb = xgpiops_readreg(gpio->base_addr + + XGPIOPS_INTMASK_OFFSET(bank_num)); + int_sts &= ~int_enb; + + for (; int_sts != 0; int_sts >>= 1, gpio_irq++) { + if ((int_sts & 1) == 0) + continue; + gpio_irq_desc = irq_to_desc(gpio_irq); + BUG_ON(!gpio_irq_desc); + chip = irq_desc_get_chip(gpio_irq_desc); + BUG_ON(!chip); + chip->irq_ack(&gpio_irq_desc->irq_data); + + /* call the pin specific handler */ + generic_handle_irq(gpio_irq); + } + /* shift to first virtual irq of next bank */ + gpio_irq = gpio->irq_base + xgpiops_pin_table[bank_num] + 1; + } + + chip = irq_desc_get_chip(desc); + chained_irq_exit(chip, desc); +} + +#ifdef CONFIG_PM_SLEEP +static int xgpiops_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct xgpiops *gpio = platform_get_drvdata(pdev); + + if (!device_may_wakeup(dev)) { + if (!pm_runtime_suspended(dev)) + clk_disable(gpio->clk); + return 0; + } + + return 0; +} + +static int xgpiops_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct xgpiops *gpio = platform_get_drvdata(pdev); + + if (!device_may_wakeup(dev)) { + if (!pm_runtime_suspended(dev)) + return clk_enable(gpio->clk); + } + + return 0; +} +#endif + +#ifdef CONFIG_PM_RUNTIME +static int xgpiops_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct xgpiops *gpio = platform_get_drvdata(pdev); + + clk_disable(gpio->clk); + + return 0; +} + +static int xgpiops_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct xgpiops *gpio = platform_get_drvdata(pdev); + + return clk_enable(gpio->clk); +} + +static int xgpiops_idle(struct device *dev) +{ + return pm_schedule_suspend(dev, 1); +} + +static int xgpiops_request(struct gpio_chip *chip, unsigned offset) +{ + int ret; + + ret = pm_runtime_get_sync(chip->dev); + + /* + * If the device is already active pm_runtime_get() will return 1 on + * success, but gpio_request still needs to return 0. + */ + return ret < 0 ? ret : 0; +} + +static void xgpiops_free(struct gpio_chip *chip, unsigned offset) +{ + pm_runtime_put_sync(chip->dev); +} + +static void xgpiops_pm_runtime_init(struct platform_device *pdev) +{ + struct xgpiops *gpio = platform_get_drvdata(pdev); + + clk_disable(gpio->clk); + pm_runtime_enable(&pdev->dev); +} + +#else /* ! CONFIG_PM_RUNTIME */ +#define xgpiops_request NULL +#define xgpiops_free NULL +static void xgpiops_pm_runtime_init(struct platform_device *pdev) {} +#endif /* ! CONFIG_PM_RUNTIME */ + +#if defined(CONFIG_PM_RUNTIME) || defined(CONFIG_PM_SLEEP) +static const struct dev_pm_ops xgpiops_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(xgpiops_suspend, xgpiops_resume) + SET_RUNTIME_PM_OPS(xgpiops_runtime_suspend, xgpiops_runtime_resume, + xgpiops_idle) +}; +#define XGPIOPS_PM (&xgpiops_dev_pm_ops) + +#else /*! CONFIG_PM_RUNTIME || ! CONFIG_PM_SLEEP */ +#define XGPIOPS_PM NULL +#endif /*! CONFIG_PM_RUNTIME */ + +/** + * xgpiops_probe - Initialization method for a xgpiops device + * @pdev: platform device instance + * + * This function allocates memory resources for the gpio device and registers + * all the banks of the device. It will also set up interrupts for the gpio + * pins. + * Note: Interrupts are disabled for all the banks during initialization. + * Returns 0 on success, negative error otherwise. + */ +static int xgpiops_probe(struct platform_device *pdev) +{ + int ret; + unsigned int irq_num; + struct xgpiops *gpio; + struct gpio_chip *chip; + resource_size_t remap_size; + struct resource *mem_res = NULL; + int pin_num, bank_num, gpio_irq; + + gpio = kzalloc(sizeof(struct xgpiops), GFP_KERNEL); + if (!gpio) { + dev_err(&pdev->dev, + "couldn't allocate memory for gpio private data\n"); + return -ENOMEM; + } + + spin_lock_init(&gpio->gpio_lock); + + platform_set_drvdata(pdev, gpio); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_res) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_free_gpio; + } + + remap_size = mem_res->end - mem_res->start + 1; + if (!request_mem_region(mem_res->start, remap_size, pdev->name)) { + dev_err(&pdev->dev, "Cannot request IO\n"); + ret = -ENXIO; + goto err_free_gpio; + } + + gpio->base_addr = ioremap(mem_res->start, remap_size); + if (gpio->base_addr == NULL) { + dev_err(&pdev->dev, "Couldn't ioremap memory at 0x%08lx\n", + (unsigned long)mem_res->start); + ret = -ENOMEM; + goto err_release_region; + } + + irq_num = platform_get_irq(pdev, 0); + gpio->irq = irq_num; + + /* configure the gpio chip */ + chip = &gpio->chip; + chip->label = "xgpiops"; + chip->owner = THIS_MODULE; + chip->dev = &pdev->dev; + chip->get = xgpiops_get_value; + chip->set = xgpiops_set_value; + chip->request = xgpiops_request; + chip->free = xgpiops_free; + chip->direction_input = xgpiops_dir_in; + chip->direction_output = xgpiops_dir_out; + chip->to_irq = xgpiops_to_irq; + chip->dbg_show = NULL; + chip->base = 0; /* default pin base */ + chip->ngpio = XGPIOPS_NR_GPIOS; + chip->can_sleep = 0; + + gpio->irq_base = irq_alloc_descs(-1, 0, chip->ngpio, 0); + if (gpio->irq_base < 0) { + dev_err(&pdev->dev, "Couldn't allocate IRQ numbers\n"); + ret = -ENODEV; + goto err_iounmap; + } + + irq_domain = irq_domain_add_legacy(pdev->dev.of_node, + chip->ngpio, gpio->irq_base, 0, + &irq_domain_simple_ops, NULL); + + /* report a bug if gpio chip registration fails */ + ret = gpiochip_add(chip); + if (ret < 0) { + dev_err(&pdev->dev, "gpio chip registration failed\n"); + goto err_iounmap; + } else { + dev_info(&pdev->dev, "gpio at 0x%08lx mapped to 0x%08lx\n", + (unsigned long)mem_res->start, + (unsigned long)gpio->base_addr); + } + + /* Enable GPIO clock */ + gpio->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(gpio->clk)) { + dev_err(&pdev->dev, "input clock not found.\n"); + ret = PTR_ERR(gpio->clk); + goto err_chip_remove; + } + ret = clk_prepare_enable(gpio->clk); + if (ret) { + dev_err(&pdev->dev, "Unable to enable clock.\n"); + goto err_clk_put; + } + + /* disable interrupts for all banks */ + for (bank_num = 0; bank_num < 4; bank_num++) { + xgpiops_writereg(0xffffffff, gpio->base_addr + + XGPIOPS_INTDIS_OFFSET(bank_num)); + } + + /* + * set the irq chip, handler and irq chip data for callbacks for + * each pin + */ + for (pin_num = 0; pin_num < min_t(int, XGPIOPS_NR_GPIOS, + (int)chip->ngpio); pin_num++) { + gpio_irq = irq_find_mapping(irq_domain, pin_num); + irq_set_chip_and_handler(gpio_irq, &xgpiops_irqchip, + handle_simple_irq); + irq_set_chip_data(gpio_irq, (void *)gpio); + set_irq_flags(gpio_irq, IRQF_VALID); + } + + irq_set_handler_data(irq_num, (void *)gpio); + irq_set_chained_handler(irq_num, xgpiops_irqhandler); + + xgpiops_pm_runtime_init(pdev); + + device_set_wakeup_capable(&pdev->dev, 1); + + return 0; + +err_clk_put: + clk_put(gpio->clk); +err_chip_remove: + gpiochip_remove(chip); +err_iounmap: + iounmap(gpio->base_addr); +err_release_region: + release_mem_region(mem_res->start, remap_size); +err_free_gpio: + platform_set_drvdata(pdev, NULL); + kfree(gpio); + + return ret; +} + +static int xgpiops_remove(struct platform_device *pdev) +{ + struct xgpiops *gpio = platform_get_drvdata(pdev); + + clk_disable_unprepare(gpio->clk); + clk_put(gpio->clk); + device_set_wakeup_capable(&pdev->dev, 0); + return 0; +} + +static struct of_device_id xgpiops_of_match[] = { + { .compatible = "xlnx,ps7-gpio-1.00.a", }, + { /* end of table */} +}; +MODULE_DEVICE_TABLE(of, xgpiops_of_match); + +static struct platform_driver xgpiops_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .pm = XGPIOPS_PM, + .of_match_table = xgpiops_of_match, + }, + .probe = xgpiops_probe, + .remove = xgpiops_remove, +}; + +/** + * xgpiops_init - Initial driver registration call + */ +static int __init xgpiops_init(void) +{ + return platform_driver_register(&xgpiops_driver); +} + +postcore_initcall(xgpiops_init);