From 03dd4cb26d967f9588437b0fc9cc0e8353322bb7 Mon Sep 17 00:00:00 2001 From: André Fabian Silva Delgado Date: Fri, 25 Mar 2016 03:53:42 -0300 Subject: Linux-libre 4.5-gnu --- drivers/pinctrl/bcm/Kconfig | 48 +- drivers/pinctrl/bcm/Makefile | 3 +- drivers/pinctrl/bcm/pinctrl-bcm2835.c | 14 +- drivers/pinctrl/bcm/pinctrl-cygnus-gpio.c | 784 ------------------------------ drivers/pinctrl/bcm/pinctrl-iproc-gpio.c | 758 +++++++++++++++++++++++++++++ drivers/pinctrl/bcm/pinctrl-nsp-gpio.c | 744 ++++++++++++++++++++++++++++ 6 files changed, 1551 insertions(+), 800 deletions(-) delete mode 100644 drivers/pinctrl/bcm/pinctrl-cygnus-gpio.c create mode 100644 drivers/pinctrl/bcm/pinctrl-iproc-gpio.c create mode 100644 drivers/pinctrl/bcm/pinctrl-nsp-gpio.c (limited to 'drivers/pinctrl/bcm') diff --git a/drivers/pinctrl/bcm/Kconfig b/drivers/pinctrl/bcm/Kconfig index cd11d4d9a..2cc74384c 100644 --- a/drivers/pinctrl/bcm/Kconfig +++ b/drivers/pinctrl/bcm/Kconfig @@ -9,6 +9,7 @@ config PINCTRL_BCM281XX select PINCONF select GENERIC_PINCONF select REGMAP_MMIO + default ARCH_BCM_MOBILE help Say Y here to support Broadcom BCM281xx pinctrl driver, which is used for the BCM281xx SoC family, including BCM11130, BCM11140, BCM11351, @@ -20,27 +21,41 @@ config PINCTRL_BCM2835 select PINMUX select PINCONF -config PINCTRL_CYGNUS_GPIO - bool "Broadcom Cygnus GPIO (with PINCONF) driver" - depends on OF_GPIO && ARCH_BCM_CYGNUS +config PINCTRL_IPROC_GPIO + bool "Broadcom iProc GPIO (with PINCONF) driver" + depends on OF_GPIO && (ARCH_BCM_IPROC || COMPILE_TEST) select GPIOLIB_IRQCHIP select PINCONF select GENERIC_PINCONF - default ARCH_BCM_CYGNUS + default ARCH_BCM_IPROC help - Say yes here to enable the Broadcom Cygnus GPIO driver. + Say yes here to enable the Broadcom iProc GPIO driver. + + The Broadcom iProc based SoCs- Cygnus, NS2, NSP and Stingray, use + same GPIO Controller IP hence this driver could be used for all. The Broadcom Cygnus SoC has 3 GPIO controllers including the ASIU GPIO controller (ASIU), the chipCommonG GPIO controller (CCM), and the always-ON GPIO controller (CRMU/AON). All 3 GPIO controllers are supported by this driver. - All 3 Cygnus GPIO controllers support basic PINCONF functions such + The Broadcom NSP has two GPIO controllers including the ChipcommonA + GPIO, the ChipcommonB GPIO. Later controller is supported by this + driver. + + The Broadcom NS2 has two GPIO controller including the CRMU GPIO, + the ChipcommonG GPIO. Both controllers are supported by this driver. + + The Broadcom Stingray GPIO controllers are supported by this driver. + + All above SoCs GPIO controllers support basic PINCONF functions such as bias pull up, pull down, and drive strength configurations, when these pins are muxed to GPIO. - Pins from the ASIU GPIO can be individually muxed to GPIO function, - through interaction with the Cygnus IOMUX controller. + It provides the framework where pins from the individual GPIO can be + individually muxed to GPIO function, through interaction with the + SoCs IOMUX controller. This features could be used only on SoCs which + support individual pin muxing. config PINCTRL_CYGNUS_MUX bool "Broadcom Cygnus IOMUX driver" @@ -54,3 +69,20 @@ config PINCTRL_CYGNUS_MUX The Broadcom Cygnus IOMUX driver supports group based IOMUX configuration, with the exception that certain individual pins can be overrided to GPIO function + +config PINCTRL_NSP_GPIO + bool "Broadcom NSP GPIO (with PINCONF) driver" + depends on OF_GPIO && (ARCH_BCM_NSP || COMPILE_TEST) + select GPIOLIB_IRQCHIP + select PINCONF + select GENERIC_PINCONF + default ARCH_BCM_NSP + help + Say yes here to enable the Broadcom NSP GPIO driver. + + The Broadcom Northstar Plus SoC ChipcommonA GPIO controller is + supported by this driver. + + The ChipcommonA GPIO controller support basic PINCONF functions such + as bias pull up, pull down, and drive strength configurations, when + these pins are muxed to GPIO. diff --git a/drivers/pinctrl/bcm/Makefile b/drivers/pinctrl/bcm/Makefile index 2b2f70ee8..6148367d5 100644 --- a/drivers/pinctrl/bcm/Makefile +++ b/drivers/pinctrl/bcm/Makefile @@ -2,5 +2,6 @@ obj-$(CONFIG_PINCTRL_BCM281XX) += pinctrl-bcm281xx.o obj-$(CONFIG_PINCTRL_BCM2835) += pinctrl-bcm2835.o -obj-$(CONFIG_PINCTRL_CYGNUS_GPIO) += pinctrl-cygnus-gpio.o +obj-$(CONFIG_PINCTRL_IPROC_GPIO) += pinctrl-iproc-gpio.o obj-$(CONFIG_PINCTRL_CYGNUS_MUX) += pinctrl-cygnus-mux.o +obj-$(CONFIG_PINCTRL_NSP_GPIO) += pinctrl-nsp-gpio.o diff --git a/drivers/pinctrl/bcm/pinctrl-bcm2835.c b/drivers/pinctrl/bcm/pinctrl-bcm2835.c index 2e6ca6963..0f5997ceb 100644 --- a/drivers/pinctrl/bcm/pinctrl-bcm2835.c +++ b/drivers/pinctrl/bcm/pinctrl-bcm2835.c @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -337,14 +337,14 @@ static int bcm2835_gpio_direction_input(struct gpio_chip *chip, unsigned offset) static int bcm2835_gpio_get(struct gpio_chip *chip, unsigned offset) { - struct bcm2835_pinctrl *pc = dev_get_drvdata(chip->dev); + struct bcm2835_pinctrl *pc = gpiochip_get_data(chip); return bcm2835_gpio_get_bit(pc, GPLEV0, offset); } static void bcm2835_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { - struct bcm2835_pinctrl *pc = dev_get_drvdata(chip->dev); + struct bcm2835_pinctrl *pc = gpiochip_get_data(chip); bcm2835_gpio_set_bit(pc, value ? GPSET0 : GPCLR0, offset); } @@ -358,7 +358,7 @@ static int bcm2835_gpio_direction_output(struct gpio_chip *chip, static int bcm2835_gpio_to_irq(struct gpio_chip *chip, unsigned offset) { - struct bcm2835_pinctrl *pc = dev_get_drvdata(chip->dev); + struct bcm2835_pinctrl *pc = gpiochip_get_data(chip); return irq_linear_revmap(pc->irq_domain, offset); } @@ -795,7 +795,7 @@ static int bcm2835_pctl_dt_node_to_map(struct pinctrl_dev *pctldev, return 0; out: - kfree(maps); + bcm2835_pctl_dt_free_map(pctldev, maps, num_pins * maps_per_pin); return err; } @@ -964,7 +964,7 @@ static int bcm2835_pinctrl_probe(struct platform_device *pdev) return PTR_ERR(pc->base); pc->gpio_chip = bcm2835_gpio_chip; - pc->gpio_chip.dev = dev; + pc->gpio_chip.parent = dev; pc->gpio_chip.of_node = np; pc->irq_domain = irq_domain_add_linear(np, BCM2835_NUM_GPIOS, @@ -1021,7 +1021,7 @@ static int bcm2835_pinctrl_probe(struct platform_device *pdev) } } - err = gpiochip_add(&pc->gpio_chip); + err = gpiochip_add_data(&pc->gpio_chip, pc); if (err) { dev_err(dev, "could not add GPIO chip\n"); return err; diff --git a/drivers/pinctrl/bcm/pinctrl-cygnus-gpio.c b/drivers/pinctrl/bcm/pinctrl-cygnus-gpio.c deleted file mode 100644 index 12a48f498..000000000 --- a/drivers/pinctrl/bcm/pinctrl-cygnus-gpio.c +++ /dev/null @@ -1,784 +0,0 @@ -/* - * Copyright (C) 2014-2015 Broadcom Corporation - * - * 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 version 2. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * This file contains the Broadcom Cygnus GPIO driver that supports 3 - * GPIO controllers on Cygnus including the ASIU GPIO controller, the - * chipCommonG GPIO controller, and the always-on GPIO controller. Basic - * PINCONF such as bias pull up/down, and drive strength are also supported - * in this driver. - * - * Pins from the ASIU GPIO can be individually muxed to GPIO function, - * through the interaction with the Cygnus IOMUX controller - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../pinctrl-utils.h" - -#define CYGNUS_GPIO_DATA_IN_OFFSET 0x00 -#define CYGNUS_GPIO_DATA_OUT_OFFSET 0x04 -#define CYGNUS_GPIO_OUT_EN_OFFSET 0x08 -#define CYGNUS_GPIO_INT_TYPE_OFFSET 0x0c -#define CYGNUS_GPIO_INT_DE_OFFSET 0x10 -#define CYGNUS_GPIO_INT_EDGE_OFFSET 0x14 -#define CYGNUS_GPIO_INT_MSK_OFFSET 0x18 -#define CYGNUS_GPIO_INT_STAT_OFFSET 0x1c -#define CYGNUS_GPIO_INT_MSTAT_OFFSET 0x20 -#define CYGNUS_GPIO_INT_CLR_OFFSET 0x24 -#define CYGNUS_GPIO_PAD_RES_OFFSET 0x34 -#define CYGNUS_GPIO_RES_EN_OFFSET 0x38 - -/* drive strength control for ASIU GPIO */ -#define CYGNUS_GPIO_ASIU_DRV0_CTRL_OFFSET 0x58 - -/* drive strength control for CCM/CRMU (AON) GPIO */ -#define CYGNUS_GPIO_DRV0_CTRL_OFFSET 0x00 - -#define GPIO_BANK_SIZE 0x200 -#define NGPIOS_PER_BANK 32 -#define GPIO_BANK(pin) ((pin) / NGPIOS_PER_BANK) - -#define CYGNUS_GPIO_REG(pin, reg) (GPIO_BANK(pin) * GPIO_BANK_SIZE + (reg)) -#define CYGNUS_GPIO_SHIFT(pin) ((pin) % NGPIOS_PER_BANK) - -#define GPIO_DRV_STRENGTH_BIT_SHIFT 20 -#define GPIO_DRV_STRENGTH_BITS 3 -#define GPIO_DRV_STRENGTH_BIT_MASK ((1 << GPIO_DRV_STRENGTH_BITS) - 1) - -/* - * Cygnus GPIO core - * - * @dev: pointer to device - * @base: I/O register base for Cygnus GPIO controller - * @io_ctrl: I/O register base for certain type of Cygnus GPIO controller that - * has the PINCONF support implemented outside of the GPIO block - * @lock: lock to protect access to I/O registers - * @gc: GPIO chip - * @num_banks: number of GPIO banks, each bank supports up to 32 GPIOs - * @pinmux_is_supported: flag to indicate this GPIO controller contains pins - * that can be individually muxed to GPIO - * @pctl: pointer to pinctrl_dev - * @pctldesc: pinctrl descriptor - */ -struct cygnus_gpio { - struct device *dev; - - void __iomem *base; - void __iomem *io_ctrl; - - spinlock_t lock; - - struct gpio_chip gc; - unsigned num_banks; - - bool pinmux_is_supported; - - struct pinctrl_dev *pctl; - struct pinctrl_desc pctldesc; -}; - -static inline struct cygnus_gpio *to_cygnus_gpio(struct gpio_chip *gc) -{ - return container_of(gc, struct cygnus_gpio, gc); -} - -/* - * Mapping from PINCONF pins to GPIO pins is 1-to-1 - */ -static inline unsigned cygnus_pin_to_gpio(unsigned pin) -{ - return pin; -} - -/** - * cygnus_set_bit - set or clear one bit (corresponding to the GPIO pin) in a - * Cygnus GPIO register - * - * @cygnus_gpio: Cygnus GPIO device - * @reg: register offset - * @gpio: GPIO pin - * @set: set or clear - */ -static inline void cygnus_set_bit(struct cygnus_gpio *chip, unsigned int reg, - unsigned gpio, bool set) -{ - unsigned int offset = CYGNUS_GPIO_REG(gpio, reg); - unsigned int shift = CYGNUS_GPIO_SHIFT(gpio); - u32 val; - - val = readl(chip->base + offset); - if (set) - val |= BIT(shift); - else - val &= ~BIT(shift); - writel(val, chip->base + offset); -} - -static inline bool cygnus_get_bit(struct cygnus_gpio *chip, unsigned int reg, - unsigned gpio) -{ - unsigned int offset = CYGNUS_GPIO_REG(gpio, reg); - unsigned int shift = CYGNUS_GPIO_SHIFT(gpio); - - return !!(readl(chip->base + offset) & BIT(shift)); -} - -static void cygnus_gpio_irq_handler(struct irq_desc *desc) -{ - struct gpio_chip *gc = irq_desc_get_handler_data(desc); - struct cygnus_gpio *chip = to_cygnus_gpio(gc); - struct irq_chip *irq_chip = irq_desc_get_chip(desc); - int i, bit; - - chained_irq_enter(irq_chip, desc); - - /* go through the entire GPIO banks and handle all interrupts */ - for (i = 0; i < chip->num_banks; i++) { - unsigned long val = readl(chip->base + (i * GPIO_BANK_SIZE) + - CYGNUS_GPIO_INT_MSTAT_OFFSET); - - for_each_set_bit(bit, &val, NGPIOS_PER_BANK) { - unsigned pin = NGPIOS_PER_BANK * i + bit; - int child_irq = irq_find_mapping(gc->irqdomain, pin); - - /* - * Clear the interrupt before invoking the - * handler, so we do not leave any window - */ - writel(BIT(bit), chip->base + (i * GPIO_BANK_SIZE) + - CYGNUS_GPIO_INT_CLR_OFFSET); - - generic_handle_irq(child_irq); - } - } - - chained_irq_exit(irq_chip, desc); -} - - -static void cygnus_gpio_irq_ack(struct irq_data *d) -{ - struct gpio_chip *gc = irq_data_get_irq_chip_data(d); - struct cygnus_gpio *chip = to_cygnus_gpio(gc); - unsigned gpio = d->hwirq; - unsigned int offset = CYGNUS_GPIO_REG(gpio, - CYGNUS_GPIO_INT_CLR_OFFSET); - unsigned int shift = CYGNUS_GPIO_SHIFT(gpio); - u32 val = BIT(shift); - - writel(val, chip->base + offset); -} - -/** - * cygnus_gpio_irq_set_mask - mask/unmask a GPIO interrupt - * - * @d: IRQ chip data - * @unmask: mask/unmask GPIO interrupt - */ -static void cygnus_gpio_irq_set_mask(struct irq_data *d, bool unmask) -{ - struct gpio_chip *gc = irq_data_get_irq_chip_data(d); - struct cygnus_gpio *chip = to_cygnus_gpio(gc); - unsigned gpio = d->hwirq; - - cygnus_set_bit(chip, CYGNUS_GPIO_INT_MSK_OFFSET, gpio, unmask); -} - -static void cygnus_gpio_irq_mask(struct irq_data *d) -{ - struct gpio_chip *gc = irq_data_get_irq_chip_data(d); - struct cygnus_gpio *chip = to_cygnus_gpio(gc); - unsigned long flags; - - spin_lock_irqsave(&chip->lock, flags); - cygnus_gpio_irq_set_mask(d, false); - spin_unlock_irqrestore(&chip->lock, flags); -} - -static void cygnus_gpio_irq_unmask(struct irq_data *d) -{ - struct gpio_chip *gc = irq_data_get_irq_chip_data(d); - struct cygnus_gpio *chip = to_cygnus_gpio(gc); - unsigned long flags; - - spin_lock_irqsave(&chip->lock, flags); - cygnus_gpio_irq_set_mask(d, true); - spin_unlock_irqrestore(&chip->lock, flags); -} - -static int cygnus_gpio_irq_set_type(struct irq_data *d, unsigned int type) -{ - struct gpio_chip *gc = irq_data_get_irq_chip_data(d); - struct cygnus_gpio *chip = to_cygnus_gpio(gc); - unsigned gpio = d->hwirq; - bool level_triggered = false; - bool dual_edge = false; - bool rising_or_high = false; - unsigned long flags; - - switch (type & IRQ_TYPE_SENSE_MASK) { - case IRQ_TYPE_EDGE_RISING: - rising_or_high = true; - break; - - case IRQ_TYPE_EDGE_FALLING: - break; - - case IRQ_TYPE_EDGE_BOTH: - dual_edge = true; - break; - - case IRQ_TYPE_LEVEL_HIGH: - level_triggered = true; - rising_or_high = true; - break; - - case IRQ_TYPE_LEVEL_LOW: - level_triggered = true; - break; - - default: - dev_err(chip->dev, "invalid GPIO IRQ type 0x%x\n", - type); - return -EINVAL; - } - - spin_lock_irqsave(&chip->lock, flags); - cygnus_set_bit(chip, CYGNUS_GPIO_INT_TYPE_OFFSET, gpio, - level_triggered); - cygnus_set_bit(chip, CYGNUS_GPIO_INT_DE_OFFSET, gpio, dual_edge); - cygnus_set_bit(chip, CYGNUS_GPIO_INT_EDGE_OFFSET, gpio, - rising_or_high); - spin_unlock_irqrestore(&chip->lock, flags); - - dev_dbg(chip->dev, - "gpio:%u level_triggered:%d dual_edge:%d rising_or_high:%d\n", - gpio, level_triggered, dual_edge, rising_or_high); - - return 0; -} - -static struct irq_chip cygnus_gpio_irq_chip = { - .name = "bcm-cygnus-gpio", - .irq_ack = cygnus_gpio_irq_ack, - .irq_mask = cygnus_gpio_irq_mask, - .irq_unmask = cygnus_gpio_irq_unmask, - .irq_set_type = cygnus_gpio_irq_set_type, -}; - -/* - * Request the Cygnus IOMUX pinmux controller to mux individual pins to GPIO - */ -static int cygnus_gpio_request(struct gpio_chip *gc, unsigned offset) -{ - struct cygnus_gpio *chip = to_cygnus_gpio(gc); - unsigned gpio = gc->base + offset; - - /* not all Cygnus GPIO pins can be muxed individually */ - if (!chip->pinmux_is_supported) - return 0; - - return pinctrl_request_gpio(gpio); -} - -static void cygnus_gpio_free(struct gpio_chip *gc, unsigned offset) -{ - struct cygnus_gpio *chip = to_cygnus_gpio(gc); - unsigned gpio = gc->base + offset; - - if (!chip->pinmux_is_supported) - return; - - pinctrl_free_gpio(gpio); -} - -static int cygnus_gpio_direction_input(struct gpio_chip *gc, unsigned gpio) -{ - struct cygnus_gpio *chip = to_cygnus_gpio(gc); - unsigned long flags; - - spin_lock_irqsave(&chip->lock, flags); - cygnus_set_bit(chip, CYGNUS_GPIO_OUT_EN_OFFSET, gpio, false); - spin_unlock_irqrestore(&chip->lock, flags); - - dev_dbg(chip->dev, "gpio:%u set input\n", gpio); - - return 0; -} - -static int cygnus_gpio_direction_output(struct gpio_chip *gc, unsigned gpio, - int val) -{ - struct cygnus_gpio *chip = to_cygnus_gpio(gc); - unsigned long flags; - - spin_lock_irqsave(&chip->lock, flags); - cygnus_set_bit(chip, CYGNUS_GPIO_OUT_EN_OFFSET, gpio, true); - cygnus_set_bit(chip, CYGNUS_GPIO_DATA_OUT_OFFSET, gpio, !!(val)); - spin_unlock_irqrestore(&chip->lock, flags); - - dev_dbg(chip->dev, "gpio:%u set output, value:%d\n", gpio, val); - - return 0; -} - -static void cygnus_gpio_set(struct gpio_chip *gc, unsigned gpio, int val) -{ - struct cygnus_gpio *chip = to_cygnus_gpio(gc); - unsigned long flags; - - spin_lock_irqsave(&chip->lock, flags); - cygnus_set_bit(chip, CYGNUS_GPIO_DATA_OUT_OFFSET, gpio, !!(val)); - spin_unlock_irqrestore(&chip->lock, flags); - - dev_dbg(chip->dev, "gpio:%u set, value:%d\n", gpio, val); -} - -static int cygnus_gpio_get(struct gpio_chip *gc, unsigned gpio) -{ - struct cygnus_gpio *chip = to_cygnus_gpio(gc); - unsigned int offset = CYGNUS_GPIO_REG(gpio, - CYGNUS_GPIO_DATA_IN_OFFSET); - unsigned int shift = CYGNUS_GPIO_SHIFT(gpio); - - return !!(readl(chip->base + offset) & BIT(shift)); -} - -static int cygnus_get_groups_count(struct pinctrl_dev *pctldev) -{ - return 1; -} - -/* - * Only one group: "gpio_grp", since this local pinctrl device only performs - * GPIO specific PINCONF configurations - */ -static const char *cygnus_get_group_name(struct pinctrl_dev *pctldev, - unsigned selector) -{ - return "gpio_grp"; -} - -static const struct pinctrl_ops cygnus_pctrl_ops = { - .get_groups_count = cygnus_get_groups_count, - .get_group_name = cygnus_get_group_name, - .dt_node_to_map = pinconf_generic_dt_node_to_map_pin, - .dt_free_map = pinctrl_utils_dt_free_map, -}; - -static int cygnus_gpio_set_pull(struct cygnus_gpio *chip, unsigned gpio, - bool disable, bool pull_up) -{ - unsigned long flags; - - spin_lock_irqsave(&chip->lock, flags); - - if (disable) { - cygnus_set_bit(chip, CYGNUS_GPIO_RES_EN_OFFSET, gpio, false); - } else { - cygnus_set_bit(chip, CYGNUS_GPIO_PAD_RES_OFFSET, gpio, - pull_up); - cygnus_set_bit(chip, CYGNUS_GPIO_RES_EN_OFFSET, gpio, true); - } - - spin_unlock_irqrestore(&chip->lock, flags); - - dev_dbg(chip->dev, "gpio:%u set pullup:%d\n", gpio, pull_up); - - return 0; -} - -static void cygnus_gpio_get_pull(struct cygnus_gpio *chip, unsigned gpio, - bool *disable, bool *pull_up) -{ - unsigned long flags; - - spin_lock_irqsave(&chip->lock, flags); - *disable = !cygnus_get_bit(chip, CYGNUS_GPIO_RES_EN_OFFSET, gpio); - *pull_up = cygnus_get_bit(chip, CYGNUS_GPIO_PAD_RES_OFFSET, gpio); - spin_unlock_irqrestore(&chip->lock, flags); -} - -static int cygnus_gpio_set_strength(struct cygnus_gpio *chip, unsigned gpio, - unsigned strength) -{ - void __iomem *base; - unsigned int i, offset, shift; - u32 val; - unsigned long flags; - - /* make sure drive strength is supported */ - if (strength < 2 || strength > 16 || (strength % 2)) - return -ENOTSUPP; - - if (chip->io_ctrl) { - base = chip->io_ctrl; - offset = CYGNUS_GPIO_DRV0_CTRL_OFFSET; - } else { - base = chip->base; - offset = CYGNUS_GPIO_REG(gpio, - CYGNUS_GPIO_ASIU_DRV0_CTRL_OFFSET); - } - - shift = CYGNUS_GPIO_SHIFT(gpio); - - dev_dbg(chip->dev, "gpio:%u set drive strength:%d mA\n", gpio, - strength); - - spin_lock_irqsave(&chip->lock, flags); - strength = (strength / 2) - 1; - for (i = 0; i < GPIO_DRV_STRENGTH_BITS; i++) { - val = readl(base + offset); - val &= ~BIT(shift); - val |= ((strength >> i) & 0x1) << shift; - writel(val, base + offset); - offset += 4; - } - spin_unlock_irqrestore(&chip->lock, flags); - - return 0; -} - -static int cygnus_gpio_get_strength(struct cygnus_gpio *chip, unsigned gpio, - u16 *strength) -{ - void __iomem *base; - unsigned int i, offset, shift; - u32 val; - unsigned long flags; - - if (chip->io_ctrl) { - base = chip->io_ctrl; - offset = CYGNUS_GPIO_DRV0_CTRL_OFFSET; - } else { - base = chip->base; - offset = CYGNUS_GPIO_REG(gpio, - CYGNUS_GPIO_ASIU_DRV0_CTRL_OFFSET); - } - - shift = CYGNUS_GPIO_SHIFT(gpio); - - spin_lock_irqsave(&chip->lock, flags); - *strength = 0; - for (i = 0; i < GPIO_DRV_STRENGTH_BITS; i++) { - val = readl(base + offset) & BIT(shift); - val >>= shift; - *strength += (val << i); - offset += 4; - } - - /* convert to mA */ - *strength = (*strength + 1) * 2; - spin_unlock_irqrestore(&chip->lock, flags); - - return 0; -} - -static int cygnus_pin_config_get(struct pinctrl_dev *pctldev, unsigned pin, - unsigned long *config) -{ - struct cygnus_gpio *chip = pinctrl_dev_get_drvdata(pctldev); - enum pin_config_param param = pinconf_to_config_param(*config); - unsigned gpio = cygnus_pin_to_gpio(pin); - u16 arg; - bool disable, pull_up; - int ret; - - switch (param) { - case PIN_CONFIG_BIAS_DISABLE: - cygnus_gpio_get_pull(chip, gpio, &disable, &pull_up); - if (disable) - return 0; - else - return -EINVAL; - - case PIN_CONFIG_BIAS_PULL_UP: - cygnus_gpio_get_pull(chip, gpio, &disable, &pull_up); - if (!disable && pull_up) - return 0; - else - return -EINVAL; - - case PIN_CONFIG_BIAS_PULL_DOWN: - cygnus_gpio_get_pull(chip, gpio, &disable, &pull_up); - if (!disable && !pull_up) - return 0; - else - return -EINVAL; - - case PIN_CONFIG_DRIVE_STRENGTH: - ret = cygnus_gpio_get_strength(chip, gpio, &arg); - if (ret) - return ret; - else - *config = pinconf_to_config_packed(param, arg); - - return 0; - - default: - return -ENOTSUPP; - } - - return -ENOTSUPP; -} - -static int cygnus_pin_config_set(struct pinctrl_dev *pctldev, unsigned pin, - unsigned long *configs, unsigned num_configs) -{ - struct cygnus_gpio *chip = pinctrl_dev_get_drvdata(pctldev); - enum pin_config_param param; - u16 arg; - unsigned i, gpio = cygnus_pin_to_gpio(pin); - int ret = -ENOTSUPP; - - for (i = 0; i < num_configs; i++) { - param = pinconf_to_config_param(configs[i]); - arg = pinconf_to_config_argument(configs[i]); - - switch (param) { - case PIN_CONFIG_BIAS_DISABLE: - ret = cygnus_gpio_set_pull(chip, gpio, true, false); - if (ret < 0) - goto out; - break; - - case PIN_CONFIG_BIAS_PULL_UP: - ret = cygnus_gpio_set_pull(chip, gpio, false, true); - if (ret < 0) - goto out; - break; - - case PIN_CONFIG_BIAS_PULL_DOWN: - ret = cygnus_gpio_set_pull(chip, gpio, false, false); - if (ret < 0) - goto out; - break; - - case PIN_CONFIG_DRIVE_STRENGTH: - ret = cygnus_gpio_set_strength(chip, gpio, arg); - if (ret < 0) - goto out; - break; - - default: - dev_err(chip->dev, "invalid configuration\n"); - return -ENOTSUPP; - } - } /* for each config */ - -out: - return ret; -} - -static const struct pinconf_ops cygnus_pconf_ops = { - .is_generic = true, - .pin_config_get = cygnus_pin_config_get, - .pin_config_set = cygnus_pin_config_set, -}; - -/* - * Cygnus GPIO controller supports some PINCONF related configurations such as - * pull up, pull down, and drive strength, when the pin is configured to GPIO - * - * Here a local pinctrl device is created with simple 1-to-1 pin mapping to the - * local GPIO pins - */ -static int cygnus_gpio_register_pinconf(struct cygnus_gpio *chip) -{ - struct pinctrl_desc *pctldesc = &chip->pctldesc; - struct pinctrl_pin_desc *pins; - struct gpio_chip *gc = &chip->gc; - int i; - - pins = devm_kcalloc(chip->dev, gc->ngpio, sizeof(*pins), GFP_KERNEL); - if (!pins) - return -ENOMEM; - - for (i = 0; i < gc->ngpio; i++) { - pins[i].number = i; - pins[i].name = devm_kasprintf(chip->dev, GFP_KERNEL, - "gpio-%d", i); - if (!pins[i].name) - return -ENOMEM; - } - - pctldesc->name = dev_name(chip->dev); - pctldesc->pctlops = &cygnus_pctrl_ops; - pctldesc->pins = pins; - pctldesc->npins = gc->ngpio; - pctldesc->confops = &cygnus_pconf_ops; - - chip->pctl = pinctrl_register(pctldesc, chip->dev, chip); - if (IS_ERR(chip->pctl)) { - dev_err(chip->dev, "unable to register pinctrl device\n"); - return PTR_ERR(chip->pctl); - } - - return 0; -} - -static void cygnus_gpio_unregister_pinconf(struct cygnus_gpio *chip) -{ - if (chip->pctl) - pinctrl_unregister(chip->pctl); -} - -struct cygnus_gpio_data { - unsigned num_gpios; -}; - -static const struct cygnus_gpio_data cygnus_cmm_gpio_data = { - .num_gpios = 24, -}; - -static const struct cygnus_gpio_data cygnus_asiu_gpio_data = { - .num_gpios = 146, -}; - -static const struct cygnus_gpio_data cygnus_crmu_gpio_data = { - .num_gpios = 6, -}; - -static const struct of_device_id cygnus_gpio_of_match[] = { - { - .compatible = "brcm,cygnus-ccm-gpio", - .data = &cygnus_cmm_gpio_data, - }, - { - .compatible = "brcm,cygnus-asiu-gpio", - .data = &cygnus_asiu_gpio_data, - }, - { - .compatible = "brcm,cygnus-crmu-gpio", - .data = &cygnus_crmu_gpio_data, - } -}; - -static int cygnus_gpio_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct resource *res; - struct cygnus_gpio *chip; - struct gpio_chip *gc; - u32 ngpios; - int irq, ret; - const struct of_device_id *match; - const struct cygnus_gpio_data *gpio_data; - - match = of_match_device(cygnus_gpio_of_match, dev); - if (!match) - return -ENODEV; - gpio_data = match->data; - ngpios = gpio_data->num_gpios; - - chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - chip->dev = dev; - platform_set_drvdata(pdev, chip); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - chip->base = devm_ioremap_resource(dev, res); - if (IS_ERR(chip->base)) { - dev_err(dev, "unable to map I/O memory\n"); - return PTR_ERR(chip->base); - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - if (res) { - chip->io_ctrl = devm_ioremap_resource(dev, res); - if (IS_ERR(chip->io_ctrl)) { - dev_err(dev, "unable to map I/O memory\n"); - return PTR_ERR(chip->io_ctrl); - } - } - - spin_lock_init(&chip->lock); - - gc = &chip->gc; - gc->base = -1; - gc->ngpio = ngpios; - chip->num_banks = (ngpios + NGPIOS_PER_BANK - 1) / NGPIOS_PER_BANK; - gc->label = dev_name(dev); - gc->dev = dev; - gc->of_node = dev->of_node; - gc->request = cygnus_gpio_request; - gc->free = cygnus_gpio_free; - gc->direction_input = cygnus_gpio_direction_input; - gc->direction_output = cygnus_gpio_direction_output; - gc->set = cygnus_gpio_set; - gc->get = cygnus_gpio_get; - - chip->pinmux_is_supported = of_property_read_bool(dev->of_node, - "gpio-ranges"); - - ret = gpiochip_add(gc); - if (ret < 0) { - dev_err(dev, "unable to add GPIO chip\n"); - return ret; - } - - ret = cygnus_gpio_register_pinconf(chip); - if (ret) { - dev_err(dev, "unable to register pinconf\n"); - goto err_rm_gpiochip; - } - - /* optional GPIO interrupt support */ - irq = platform_get_irq(pdev, 0); - if (irq) { - ret = gpiochip_irqchip_add(gc, &cygnus_gpio_irq_chip, 0, - handle_simple_irq, IRQ_TYPE_NONE); - if (ret) { - dev_err(dev, "no GPIO irqchip\n"); - goto err_unregister_pinconf; - } - - gpiochip_set_chained_irqchip(gc, &cygnus_gpio_irq_chip, irq, - cygnus_gpio_irq_handler); - } - - return 0; - -err_unregister_pinconf: - cygnus_gpio_unregister_pinconf(chip); - -err_rm_gpiochip: - gpiochip_remove(gc); - - return ret; -} - -static struct platform_driver cygnus_gpio_driver = { - .driver = { - .name = "cygnus-gpio", - .of_match_table = cygnus_gpio_of_match, - }, - .probe = cygnus_gpio_probe, -}; - -static int __init cygnus_gpio_init(void) -{ - return platform_driver_probe(&cygnus_gpio_driver, cygnus_gpio_probe); -} -arch_initcall_sync(cygnus_gpio_init); diff --git a/drivers/pinctrl/bcm/pinctrl-iproc-gpio.c b/drivers/pinctrl/bcm/pinctrl-iproc-gpio.c new file mode 100644 index 000000000..3b2ac8f77 --- /dev/null +++ b/drivers/pinctrl/bcm/pinctrl-iproc-gpio.c @@ -0,0 +1,758 @@ +/* + * Copyright (C) 2014-2015 Broadcom Corporation + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This file contains the Broadcom Iproc GPIO driver that supports 3 + * GPIO controllers on Iproc including the ASIU GPIO controller, the + * chipCommonG GPIO controller, and the always-on GPIO controller. Basic + * PINCONF such as bias pull up/down, and drive strength are also supported + * in this driver. + * + * It provides the functionality where pins from the GPIO can be + * individually muxed to GPIO function, if individual pad + * configuration is supported, through the interaction with respective + * SoCs IOMUX controller. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../pinctrl-utils.h" + +#define IPROC_GPIO_DATA_IN_OFFSET 0x00 +#define IPROC_GPIO_DATA_OUT_OFFSET 0x04 +#define IPROC_GPIO_OUT_EN_OFFSET 0x08 +#define IPROC_GPIO_INT_TYPE_OFFSET 0x0c +#define IPROC_GPIO_INT_DE_OFFSET 0x10 +#define IPROC_GPIO_INT_EDGE_OFFSET 0x14 +#define IPROC_GPIO_INT_MSK_OFFSET 0x18 +#define IPROC_GPIO_INT_STAT_OFFSET 0x1c +#define IPROC_GPIO_INT_MSTAT_OFFSET 0x20 +#define IPROC_GPIO_INT_CLR_OFFSET 0x24 +#define IPROC_GPIO_PAD_RES_OFFSET 0x34 +#define IPROC_GPIO_RES_EN_OFFSET 0x38 + +/* drive strength control for ASIU GPIO */ +#define IPROC_GPIO_ASIU_DRV0_CTRL_OFFSET 0x58 + +/* drive strength control for CCM/CRMU (AON) GPIO */ +#define IPROC_GPIO_DRV0_CTRL_OFFSET 0x00 + +#define GPIO_BANK_SIZE 0x200 +#define NGPIOS_PER_BANK 32 +#define GPIO_BANK(pin) ((pin) / NGPIOS_PER_BANK) + +#define IPROC_GPIO_REG(pin, reg) (GPIO_BANK(pin) * GPIO_BANK_SIZE + (reg)) +#define IPROC_GPIO_SHIFT(pin) ((pin) % NGPIOS_PER_BANK) + +#define GPIO_DRV_STRENGTH_BIT_SHIFT 20 +#define GPIO_DRV_STRENGTH_BITS 3 +#define GPIO_DRV_STRENGTH_BIT_MASK ((1 << GPIO_DRV_STRENGTH_BITS) - 1) + +/* + * Iproc GPIO core + * + * @dev: pointer to device + * @base: I/O register base for Iproc GPIO controller + * @io_ctrl: I/O register base for certain type of Iproc GPIO controller that + * has the PINCONF support implemented outside of the GPIO block + * @lock: lock to protect access to I/O registers + * @gc: GPIO chip + * @num_banks: number of GPIO banks, each bank supports up to 32 GPIOs + * @pinmux_is_supported: flag to indicate this GPIO controller contains pins + * that can be individually muxed to GPIO + * @pctl: pointer to pinctrl_dev + * @pctldesc: pinctrl descriptor + */ +struct iproc_gpio { + struct device *dev; + + void __iomem *base; + void __iomem *io_ctrl; + + spinlock_t lock; + + struct gpio_chip gc; + unsigned num_banks; + + bool pinmux_is_supported; + + struct pinctrl_dev *pctl; + struct pinctrl_desc pctldesc; +}; + +static inline struct iproc_gpio *to_iproc_gpio(struct gpio_chip *gc) +{ + return container_of(gc, struct iproc_gpio, gc); +} + +/* + * Mapping from PINCONF pins to GPIO pins is 1-to-1 + */ +static inline unsigned iproc_pin_to_gpio(unsigned pin) +{ + return pin; +} + +/** + * iproc_set_bit - set or clear one bit (corresponding to the GPIO pin) in a + * Iproc GPIO register + * + * @iproc_gpio: Iproc GPIO device + * @reg: register offset + * @gpio: GPIO pin + * @set: set or clear + */ +static inline void iproc_set_bit(struct iproc_gpio *chip, unsigned int reg, + unsigned gpio, bool set) +{ + unsigned int offset = IPROC_GPIO_REG(gpio, reg); + unsigned int shift = IPROC_GPIO_SHIFT(gpio); + u32 val; + + val = readl(chip->base + offset); + if (set) + val |= BIT(shift); + else + val &= ~BIT(shift); + writel(val, chip->base + offset); +} + +static inline bool iproc_get_bit(struct iproc_gpio *chip, unsigned int reg, + unsigned gpio) +{ + unsigned int offset = IPROC_GPIO_REG(gpio, reg); + unsigned int shift = IPROC_GPIO_SHIFT(gpio); + + return !!(readl(chip->base + offset) & BIT(shift)); +} + +static void iproc_gpio_irq_handler(struct irq_desc *desc) +{ + struct gpio_chip *gc = irq_desc_get_handler_data(desc); + struct iproc_gpio *chip = to_iproc_gpio(gc); + struct irq_chip *irq_chip = irq_desc_get_chip(desc); + int i, bit; + + chained_irq_enter(irq_chip, desc); + + /* go through the entire GPIO banks and handle all interrupts */ + for (i = 0; i < chip->num_banks; i++) { + unsigned long val = readl(chip->base + (i * GPIO_BANK_SIZE) + + IPROC_GPIO_INT_MSTAT_OFFSET); + + for_each_set_bit(bit, &val, NGPIOS_PER_BANK) { + unsigned pin = NGPIOS_PER_BANK * i + bit; + int child_irq = irq_find_mapping(gc->irqdomain, pin); + + /* + * Clear the interrupt before invoking the + * handler, so we do not leave any window + */ + writel(BIT(bit), chip->base + (i * GPIO_BANK_SIZE) + + IPROC_GPIO_INT_CLR_OFFSET); + + generic_handle_irq(child_irq); + } + } + + chained_irq_exit(irq_chip, desc); +} + + +static void iproc_gpio_irq_ack(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct iproc_gpio *chip = to_iproc_gpio(gc); + unsigned gpio = d->hwirq; + unsigned int offset = IPROC_GPIO_REG(gpio, + IPROC_GPIO_INT_CLR_OFFSET); + unsigned int shift = IPROC_GPIO_SHIFT(gpio); + u32 val = BIT(shift); + + writel(val, chip->base + offset); +} + +/** + * iproc_gpio_irq_set_mask - mask/unmask a GPIO interrupt + * + * @d: IRQ chip data + * @unmask: mask/unmask GPIO interrupt + */ +static void iproc_gpio_irq_set_mask(struct irq_data *d, bool unmask) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct iproc_gpio *chip = to_iproc_gpio(gc); + unsigned gpio = d->hwirq; + + iproc_set_bit(chip, IPROC_GPIO_INT_MSK_OFFSET, gpio, unmask); +} + +static void iproc_gpio_irq_mask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct iproc_gpio *chip = to_iproc_gpio(gc); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + iproc_gpio_irq_set_mask(d, false); + spin_unlock_irqrestore(&chip->lock, flags); +} + +static void iproc_gpio_irq_unmask(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct iproc_gpio *chip = to_iproc_gpio(gc); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + iproc_gpio_irq_set_mask(d, true); + spin_unlock_irqrestore(&chip->lock, flags); +} + +static int iproc_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct iproc_gpio *chip = to_iproc_gpio(gc); + unsigned gpio = d->hwirq; + bool level_triggered = false; + bool dual_edge = false; + bool rising_or_high = false; + unsigned long flags; + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_RISING: + rising_or_high = true; + break; + + case IRQ_TYPE_EDGE_FALLING: + break; + + case IRQ_TYPE_EDGE_BOTH: + dual_edge = true; + break; + + case IRQ_TYPE_LEVEL_HIGH: + level_triggered = true; + rising_or_high = true; + break; + + case IRQ_TYPE_LEVEL_LOW: + level_triggered = true; + break; + + default: + dev_err(chip->dev, "invalid GPIO IRQ type 0x%x\n", + type); + return -EINVAL; + } + + spin_lock_irqsave(&chip->lock, flags); + iproc_set_bit(chip, IPROC_GPIO_INT_TYPE_OFFSET, gpio, + level_triggered); + iproc_set_bit(chip, IPROC_GPIO_INT_DE_OFFSET, gpio, dual_edge); + iproc_set_bit(chip, IPROC_GPIO_INT_EDGE_OFFSET, gpio, + rising_or_high); + spin_unlock_irqrestore(&chip->lock, flags); + + dev_dbg(chip->dev, + "gpio:%u level_triggered:%d dual_edge:%d rising_or_high:%d\n", + gpio, level_triggered, dual_edge, rising_or_high); + + return 0; +} + +static struct irq_chip iproc_gpio_irq_chip = { + .name = "bcm-iproc-gpio", + .irq_ack = iproc_gpio_irq_ack, + .irq_mask = iproc_gpio_irq_mask, + .irq_unmask = iproc_gpio_irq_unmask, + .irq_set_type = iproc_gpio_irq_set_type, +}; + +/* + * Request the Iproc IOMUX pinmux controller to mux individual pins to GPIO + */ +static int iproc_gpio_request(struct gpio_chip *gc, unsigned offset) +{ + struct iproc_gpio *chip = to_iproc_gpio(gc); + unsigned gpio = gc->base + offset; + + /* not all Iproc GPIO pins can be muxed individually */ + if (!chip->pinmux_is_supported) + return 0; + + return pinctrl_request_gpio(gpio); +} + +static void iproc_gpio_free(struct gpio_chip *gc, unsigned offset) +{ + struct iproc_gpio *chip = to_iproc_gpio(gc); + unsigned gpio = gc->base + offset; + + if (!chip->pinmux_is_supported) + return; + + pinctrl_free_gpio(gpio); +} + +static int iproc_gpio_direction_input(struct gpio_chip *gc, unsigned gpio) +{ + struct iproc_gpio *chip = to_iproc_gpio(gc); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + iproc_set_bit(chip, IPROC_GPIO_OUT_EN_OFFSET, gpio, false); + spin_unlock_irqrestore(&chip->lock, flags); + + dev_dbg(chip->dev, "gpio:%u set input\n", gpio); + + return 0; +} + +static int iproc_gpio_direction_output(struct gpio_chip *gc, unsigned gpio, + int val) +{ + struct iproc_gpio *chip = to_iproc_gpio(gc); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + iproc_set_bit(chip, IPROC_GPIO_OUT_EN_OFFSET, gpio, true); + iproc_set_bit(chip, IPROC_GPIO_DATA_OUT_OFFSET, gpio, !!(val)); + spin_unlock_irqrestore(&chip->lock, flags); + + dev_dbg(chip->dev, "gpio:%u set output, value:%d\n", gpio, val); + + return 0; +} + +static void iproc_gpio_set(struct gpio_chip *gc, unsigned gpio, int val) +{ + struct iproc_gpio *chip = to_iproc_gpio(gc); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + iproc_set_bit(chip, IPROC_GPIO_DATA_OUT_OFFSET, gpio, !!(val)); + spin_unlock_irqrestore(&chip->lock, flags); + + dev_dbg(chip->dev, "gpio:%u set, value:%d\n", gpio, val); +} + +static int iproc_gpio_get(struct gpio_chip *gc, unsigned gpio) +{ + struct iproc_gpio *chip = to_iproc_gpio(gc); + unsigned int offset = IPROC_GPIO_REG(gpio, + IPROC_GPIO_DATA_IN_OFFSET); + unsigned int shift = IPROC_GPIO_SHIFT(gpio); + + return !!(readl(chip->base + offset) & BIT(shift)); +} + +static int iproc_get_groups_count(struct pinctrl_dev *pctldev) +{ + return 1; +} + +/* + * Only one group: "gpio_grp", since this local pinctrl device only performs + * GPIO specific PINCONF configurations + */ +static const char *iproc_get_group_name(struct pinctrl_dev *pctldev, + unsigned selector) +{ + return "gpio_grp"; +} + +static const struct pinctrl_ops iproc_pctrl_ops = { + .get_groups_count = iproc_get_groups_count, + .get_group_name = iproc_get_group_name, + .dt_node_to_map = pinconf_generic_dt_node_to_map_pin, + .dt_free_map = pinctrl_utils_dt_free_map, +}; + +static int iproc_gpio_set_pull(struct iproc_gpio *chip, unsigned gpio, + bool disable, bool pull_up) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + if (disable) { + iproc_set_bit(chip, IPROC_GPIO_RES_EN_OFFSET, gpio, false); + } else { + iproc_set_bit(chip, IPROC_GPIO_PAD_RES_OFFSET, gpio, + pull_up); + iproc_set_bit(chip, IPROC_GPIO_RES_EN_OFFSET, gpio, true); + } + + spin_unlock_irqrestore(&chip->lock, flags); + + dev_dbg(chip->dev, "gpio:%u set pullup:%d\n", gpio, pull_up); + + return 0; +} + +static void iproc_gpio_get_pull(struct iproc_gpio *chip, unsigned gpio, + bool *disable, bool *pull_up) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + *disable = !iproc_get_bit(chip, IPROC_GPIO_RES_EN_OFFSET, gpio); + *pull_up = iproc_get_bit(chip, IPROC_GPIO_PAD_RES_OFFSET, gpio); + spin_unlock_irqrestore(&chip->lock, flags); +} + +static int iproc_gpio_set_strength(struct iproc_gpio *chip, unsigned gpio, + unsigned strength) +{ + void __iomem *base; + unsigned int i, offset, shift; + u32 val; + unsigned long flags; + + /* make sure drive strength is supported */ + if (strength < 2 || strength > 16 || (strength % 2)) + return -ENOTSUPP; + + if (chip->io_ctrl) { + base = chip->io_ctrl; + offset = IPROC_GPIO_DRV0_CTRL_OFFSET; + } else { + base = chip->base; + offset = IPROC_GPIO_REG(gpio, + IPROC_GPIO_ASIU_DRV0_CTRL_OFFSET); + } + + shift = IPROC_GPIO_SHIFT(gpio); + + dev_dbg(chip->dev, "gpio:%u set drive strength:%d mA\n", gpio, + strength); + + spin_lock_irqsave(&chip->lock, flags); + strength = (strength / 2) - 1; + for (i = 0; i < GPIO_DRV_STRENGTH_BITS; i++) { + val = readl(base + offset); + val &= ~BIT(shift); + val |= ((strength >> i) & 0x1) << shift; + writel(val, base + offset); + offset += 4; + } + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int iproc_gpio_get_strength(struct iproc_gpio *chip, unsigned gpio, + u16 *strength) +{ + void __iomem *base; + unsigned int i, offset, shift; + u32 val; + unsigned long flags; + + if (chip->io_ctrl) { + base = chip->io_ctrl; + offset = IPROC_GPIO_DRV0_CTRL_OFFSET; + } else { + base = chip->base; + offset = IPROC_GPIO_REG(gpio, + IPROC_GPIO_ASIU_DRV0_CTRL_OFFSET); + } + + shift = IPROC_GPIO_SHIFT(gpio); + + spin_lock_irqsave(&chip->lock, flags); + *strength = 0; + for (i = 0; i < GPIO_DRV_STRENGTH_BITS; i++) { + val = readl(base + offset) & BIT(shift); + val >>= shift; + *strength += (val << i); + offset += 4; + } + + /* convert to mA */ + *strength = (*strength + 1) * 2; + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int iproc_pin_config_get(struct pinctrl_dev *pctldev, unsigned pin, + unsigned long *config) +{ + struct iproc_gpio *chip = pinctrl_dev_get_drvdata(pctldev); + enum pin_config_param param = pinconf_to_config_param(*config); + unsigned gpio = iproc_pin_to_gpio(pin); + u16 arg; + bool disable, pull_up; + int ret; + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + iproc_gpio_get_pull(chip, gpio, &disable, &pull_up); + if (disable) + return 0; + else + return -EINVAL; + + case PIN_CONFIG_BIAS_PULL_UP: + iproc_gpio_get_pull(chip, gpio, &disable, &pull_up); + if (!disable && pull_up) + return 0; + else + return -EINVAL; + + case PIN_CONFIG_BIAS_PULL_DOWN: + iproc_gpio_get_pull(chip, gpio, &disable, &pull_up); + if (!disable && !pull_up) + return 0; + else + return -EINVAL; + + case PIN_CONFIG_DRIVE_STRENGTH: + ret = iproc_gpio_get_strength(chip, gpio, &arg); + if (ret) + return ret; + *config = pinconf_to_config_packed(param, arg); + + return 0; + + default: + return -ENOTSUPP; + } + + return -ENOTSUPP; +} + +static int iproc_pin_config_set(struct pinctrl_dev *pctldev, unsigned pin, + unsigned long *configs, unsigned num_configs) +{ + struct iproc_gpio *chip = pinctrl_dev_get_drvdata(pctldev); + enum pin_config_param param; + u16 arg; + unsigned i, gpio = iproc_pin_to_gpio(pin); + int ret = -ENOTSUPP; + + for (i = 0; i < num_configs; i++) { + param = pinconf_to_config_param(configs[i]); + arg = pinconf_to_config_argument(configs[i]); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + ret = iproc_gpio_set_pull(chip, gpio, true, false); + if (ret < 0) + goto out; + break; + + case PIN_CONFIG_BIAS_PULL_UP: + ret = iproc_gpio_set_pull(chip, gpio, false, true); + if (ret < 0) + goto out; + break; + + case PIN_CONFIG_BIAS_PULL_DOWN: + ret = iproc_gpio_set_pull(chip, gpio, false, false); + if (ret < 0) + goto out; + break; + + case PIN_CONFIG_DRIVE_STRENGTH: + ret = iproc_gpio_set_strength(chip, gpio, arg); + if (ret < 0) + goto out; + break; + + default: + dev_err(chip->dev, "invalid configuration\n"); + return -ENOTSUPP; + } + } /* for each config */ + +out: + return ret; +} + +static const struct pinconf_ops iproc_pconf_ops = { + .is_generic = true, + .pin_config_get = iproc_pin_config_get, + .pin_config_set = iproc_pin_config_set, +}; + +/* + * Iproc GPIO controller supports some PINCONF related configurations such as + * pull up, pull down, and drive strength, when the pin is configured to GPIO + * + * Here a local pinctrl device is created with simple 1-to-1 pin mapping to the + * local GPIO pins + */ +static int iproc_gpio_register_pinconf(struct iproc_gpio *chip) +{ + struct pinctrl_desc *pctldesc = &chip->pctldesc; + struct pinctrl_pin_desc *pins; + struct gpio_chip *gc = &chip->gc; + int i; + + pins = devm_kcalloc(chip->dev, gc->ngpio, sizeof(*pins), GFP_KERNEL); + if (!pins) + return -ENOMEM; + + for (i = 0; i < gc->ngpio; i++) { + pins[i].number = i; + pins[i].name = devm_kasprintf(chip->dev, GFP_KERNEL, + "gpio-%d", i); + if (!pins[i].name) + return -ENOMEM; + } + + pctldesc->name = dev_name(chip->dev); + pctldesc->pctlops = &iproc_pctrl_ops; + pctldesc->pins = pins; + pctldesc->npins = gc->ngpio; + pctldesc->confops = &iproc_pconf_ops; + + chip->pctl = pinctrl_register(pctldesc, chip->dev, chip); + if (IS_ERR(chip->pctl)) { + dev_err(chip->dev, "unable to register pinctrl device\n"); + return PTR_ERR(chip->pctl); + } + + return 0; +} + +static void iproc_gpio_unregister_pinconf(struct iproc_gpio *chip) +{ + pinctrl_unregister(chip->pctl); +} + +static const struct of_device_id iproc_gpio_of_match[] = { + { .compatible = "brcm,cygnus-ccm-gpio" }, + { .compatible = "brcm,cygnus-asiu-gpio" }, + { .compatible = "brcm,cygnus-crmu-gpio" }, + { .compatible = "brcm,iproc-gpio" }, + { } +}; + +static int iproc_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct iproc_gpio *chip; + struct gpio_chip *gc; + u32 ngpios; + int irq, ret; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = dev; + platform_set_drvdata(pdev, chip); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + chip->base = devm_ioremap_resource(dev, res); + if (IS_ERR(chip->base)) { + dev_err(dev, "unable to map I/O memory\n"); + return PTR_ERR(chip->base); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + chip->io_ctrl = devm_ioremap_resource(dev, res); + if (IS_ERR(chip->io_ctrl)) { + dev_err(dev, "unable to map I/O memory\n"); + return PTR_ERR(chip->io_ctrl); + } + } + + if (of_property_read_u32(dev->of_node, "ngpios", &ngpios)) { + dev_err(&pdev->dev, "missing ngpios DT property\n"); + return -ENODEV; + } + + spin_lock_init(&chip->lock); + + gc = &chip->gc; + gc->base = -1; + gc->ngpio = ngpios; + chip->num_banks = (ngpios + NGPIOS_PER_BANK - 1) / NGPIOS_PER_BANK; + gc->label = dev_name(dev); + gc->parent = dev; + gc->of_node = dev->of_node; + gc->request = iproc_gpio_request; + gc->free = iproc_gpio_free; + gc->direction_input = iproc_gpio_direction_input; + gc->direction_output = iproc_gpio_direction_output; + gc->set = iproc_gpio_set; + gc->get = iproc_gpio_get; + + chip->pinmux_is_supported = of_property_read_bool(dev->of_node, + "gpio-ranges"); + + ret = gpiochip_add(gc); + if (ret < 0) { + dev_err(dev, "unable to add GPIO chip\n"); + return ret; + } + + ret = iproc_gpio_register_pinconf(chip); + if (ret) { + dev_err(dev, "unable to register pinconf\n"); + goto err_rm_gpiochip; + } + + /* optional GPIO interrupt support */ + irq = platform_get_irq(pdev, 0); + if (irq) { + ret = gpiochip_irqchip_add(gc, &iproc_gpio_irq_chip, 0, + handle_simple_irq, IRQ_TYPE_NONE); + if (ret) { + dev_err(dev, "no GPIO irqchip\n"); + goto err_unregister_pinconf; + } + + gpiochip_set_chained_irqchip(gc, &iproc_gpio_irq_chip, irq, + iproc_gpio_irq_handler); + } + + return 0; + +err_unregister_pinconf: + iproc_gpio_unregister_pinconf(chip); + +err_rm_gpiochip: + gpiochip_remove(gc); + + return ret; +} + +static struct platform_driver iproc_gpio_driver = { + .driver = { + .name = "iproc-gpio", + .of_match_table = iproc_gpio_of_match, + }, + .probe = iproc_gpio_probe, +}; + +static int __init iproc_gpio_init(void) +{ + return platform_driver_probe(&iproc_gpio_driver, iproc_gpio_probe); +} +arch_initcall_sync(iproc_gpio_init); diff --git a/drivers/pinctrl/bcm/pinctrl-nsp-gpio.c b/drivers/pinctrl/bcm/pinctrl-nsp-gpio.c new file mode 100644 index 000000000..ac900435d --- /dev/null +++ b/drivers/pinctrl/bcm/pinctrl-nsp-gpio.c @@ -0,0 +1,744 @@ +/* + * Copyright (C) 2015 Broadcom Corporation + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This file contains the Broadcom Northstar Plus (NSP) GPIO driver that + * supports the chipCommonA GPIO controller. Basic PINCONF such as bias, + * pull up/down, slew and drive strength are also supported in this driver. + * + * Pins from the chipCommonA GPIO can be individually muxed to GPIO function, + * through the interaction with the NSP IOMUX controller. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../pinctrl-utils.h" + +#define NSP_CHIP_A_INT_STATUS 0x00 +#define NSP_CHIP_A_INT_MASK 0x04 +#define NSP_GPIO_DATA_IN 0x40 +#define NSP_GPIO_DATA_OUT 0x44 +#define NSP_GPIO_OUT_EN 0x48 +#define NSP_GPIO_INT_POLARITY 0x50 +#define NSP_GPIO_INT_MASK 0x54 +#define NSP_GPIO_EVENT 0x58 +#define NSP_GPIO_EVENT_INT_MASK 0x5c +#define NSP_GPIO_EVENT_INT_POLARITY 0x64 +#define NSP_CHIP_A_GPIO_INT_BIT 0x01 + +/* I/O parameters offset for chipcommon A GPIO */ +#define NSP_GPIO_DRV_CTRL 0x00 +#define NSP_GPIO_HYSTERESIS_EN 0x10 +#define NSP_GPIO_SLEW_RATE_EN 0x14 +#define NSP_PULL_UP_EN 0x18 +#define NSP_PULL_DOWN_EN 0x1c +#define GPIO_DRV_STRENGTH_BITS 0x03 + +/* + * nsp GPIO core + * + * @dev: pointer to device + * @base: I/O register base for nsp GPIO controller + * @io_ctrl: I/O register base for PINCONF support outside the GPIO block + * @gc: GPIO chip + * @pctl: pointer to pinctrl_dev + * @pctldesc: pinctrl descriptor + * @irq_domain: pointer to irq domain + * @lock: lock to protect access to I/O registers + */ +struct nsp_gpio { + struct device *dev; + void __iomem *base; + void __iomem *io_ctrl; + struct gpio_chip gc; + struct pinctrl_dev *pctl; + struct pinctrl_desc pctldesc; + struct irq_domain *irq_domain; + spinlock_t lock; +}; + +enum base_type { + REG, + IO_CTRL +}; + +/* + * Mapping from PINCONF pins to GPIO pins is 1-to-1 + */ +static inline unsigned nsp_pin_to_gpio(unsigned pin) +{ + return pin; +} + +/* + * nsp_set_bit - set or clear one bit (corresponding to the GPIO pin) in a + * nsp GPIO register + * + * @nsp_gpio: nsp GPIO device + * @base_type: reg base to modify + * @reg: register offset + * @gpio: GPIO pin + * @set: set or clear + */ +static inline void nsp_set_bit(struct nsp_gpio *chip, enum base_type address, + unsigned int reg, unsigned gpio, bool set) +{ + u32 val; + void __iomem *base_address; + + if (address == IO_CTRL) + base_address = chip->io_ctrl; + else + base_address = chip->base; + + val = readl(base_address + reg); + if (set) + val |= BIT(gpio); + else + val &= ~BIT(gpio); + + writel(val, base_address + reg); +} + +/* + * nsp_get_bit - get one bit (corresponding to the GPIO pin) in a + * nsp GPIO register + */ +static inline bool nsp_get_bit(struct nsp_gpio *chip, enum base_type address, + unsigned int reg, unsigned gpio) +{ + if (address == IO_CTRL) + return !!(readl(chip->io_ctrl + reg) & BIT(gpio)); + else + return !!(readl(chip->base + reg) & BIT(gpio)); +} + +static irqreturn_t nsp_gpio_irq_handler(int irq, void *data) +{ + struct nsp_gpio *chip = (struct nsp_gpio *)data; + struct gpio_chip gc = chip->gc; + int bit; + unsigned long int_bits = 0; + u32 int_status; + + /* go through the entire GPIOs and handle all interrupts */ + int_status = readl(chip->base + NSP_CHIP_A_INT_STATUS); + if (int_status & NSP_CHIP_A_GPIO_INT_BIT) { + unsigned int event, level; + + /* Get level and edge interrupts */ + event = readl(chip->base + NSP_GPIO_EVENT_INT_MASK) & + readl(chip->base + NSP_GPIO_EVENT); + level = readl(chip->base + NSP_GPIO_DATA_IN) ^ + readl(chip->base + NSP_GPIO_INT_POLARITY); + level &= readl(chip->base + NSP_GPIO_INT_MASK); + int_bits = level | event; + + for_each_set_bit(bit, &int_bits, gc.ngpio) { + /* + * Clear the interrupt before invoking the + * handler, so we do not leave any window + */ + writel(BIT(bit), chip->base + NSP_GPIO_EVENT); + generic_handle_irq( + irq_linear_revmap(chip->irq_domain, bit)); + } + } + + return int_bits ? IRQ_HANDLED : IRQ_NONE; +} + +static void nsp_gpio_irq_ack(struct irq_data *d) +{ + struct nsp_gpio *chip = irq_data_get_irq_chip_data(d); + unsigned gpio = d->hwirq; + u32 val = BIT(gpio); + u32 trigger_type; + + trigger_type = irq_get_trigger_type(d->irq); + if (trigger_type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + nsp_set_bit(chip, REG, NSP_GPIO_EVENT, gpio, val); +} + +/* + * nsp_gpio_irq_set_mask - mask/unmask a GPIO interrupt + * + * @d: IRQ chip data + * @unmask: mask/unmask GPIO interrupt + */ +static void nsp_gpio_irq_set_mask(struct irq_data *d, bool unmask) +{ + struct nsp_gpio *chip = irq_data_get_irq_chip_data(d); + unsigned gpio = d->hwirq; + u32 trigger_type; + + trigger_type = irq_get_trigger_type(d->irq); + if (trigger_type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) + nsp_set_bit(chip, REG, NSP_GPIO_EVENT_INT_MASK, gpio, unmask); + else + nsp_set_bit(chip, REG, NSP_GPIO_INT_MASK, gpio, unmask); +} + +static void nsp_gpio_irq_mask(struct irq_data *d) +{ + struct nsp_gpio *chip = irq_data_get_irq_chip_data(d); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + nsp_gpio_irq_set_mask(d, false); + spin_unlock_irqrestore(&chip->lock, flags); +} + +static void nsp_gpio_irq_unmask(struct irq_data *d) +{ + struct nsp_gpio *chip = irq_data_get_irq_chip_data(d); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + nsp_gpio_irq_set_mask(d, true); + spin_unlock_irqrestore(&chip->lock, flags); +} + +static int nsp_gpio_irq_set_type(struct irq_data *d, unsigned int type) +{ + struct nsp_gpio *chip = irq_data_get_irq_chip_data(d); + unsigned gpio = d->hwirq; + bool level_low; + bool falling; + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + falling = nsp_get_bit(chip, REG, NSP_GPIO_EVENT_INT_POLARITY, gpio); + level_low = nsp_get_bit(chip, REG, NSP_GPIO_INT_POLARITY, gpio); + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_RISING: + falling = false; + break; + + case IRQ_TYPE_EDGE_FALLING: + falling = true; + break; + + case IRQ_TYPE_LEVEL_HIGH: + level_low = false; + break; + + case IRQ_TYPE_LEVEL_LOW: + level_low = true; + break; + + default: + dev_err(chip->dev, "invalid GPIO IRQ type 0x%x\n", + type); + spin_unlock_irqrestore(&chip->lock, flags); + return -EINVAL; + } + + nsp_set_bit(chip, REG, NSP_GPIO_EVENT_INT_POLARITY, gpio, falling); + nsp_set_bit(chip, REG, NSP_GPIO_INT_POLARITY, gpio, level_low); + spin_unlock_irqrestore(&chip->lock, flags); + + dev_dbg(chip->dev, "gpio:%u level_low:%s falling:%s\n", gpio, + level_low ? "true" : "false", falling ? "true" : "false"); + return 0; +} + +static struct irq_chip nsp_gpio_irq_chip = { + .name = "gpio-a", + .irq_enable = nsp_gpio_irq_unmask, + .irq_disable = nsp_gpio_irq_mask, + .irq_ack = nsp_gpio_irq_ack, + .irq_mask = nsp_gpio_irq_mask, + .irq_unmask = nsp_gpio_irq_unmask, + .irq_set_type = nsp_gpio_irq_set_type, +}; + +/* + * Request the nsp IOMUX pinmux controller to mux individual pins to GPIO + */ +static int nsp_gpio_request(struct gpio_chip *gc, unsigned offset) +{ + unsigned gpio = gc->base + offset; + + return pinctrl_request_gpio(gpio); +} + +static void nsp_gpio_free(struct gpio_chip *gc, unsigned offset) +{ + unsigned gpio = gc->base + offset; + + pinctrl_free_gpio(gpio); +} + +static int nsp_gpio_direction_input(struct gpio_chip *gc, unsigned gpio) +{ + struct nsp_gpio *chip = gpiochip_get_data(gc); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + nsp_set_bit(chip, REG, NSP_GPIO_OUT_EN, gpio, false); + spin_unlock_irqrestore(&chip->lock, flags); + + dev_dbg(chip->dev, "gpio:%u set input\n", gpio); + return 0; +} + +static int nsp_gpio_direction_output(struct gpio_chip *gc, unsigned gpio, + int val) +{ + struct nsp_gpio *chip = gpiochip_get_data(gc); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + nsp_set_bit(chip, REG, NSP_GPIO_OUT_EN, gpio, true); + nsp_set_bit(chip, REG, NSP_GPIO_DATA_OUT, gpio, !!(val)); + spin_unlock_irqrestore(&chip->lock, flags); + + dev_dbg(chip->dev, "gpio:%u set output, value:%d\n", gpio, val); + return 0; +} + +static void nsp_gpio_set(struct gpio_chip *gc, unsigned gpio, int val) +{ + struct nsp_gpio *chip = gpiochip_get_data(gc); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + nsp_set_bit(chip, REG, NSP_GPIO_DATA_OUT, gpio, !!(val)); + spin_unlock_irqrestore(&chip->lock, flags); + + dev_dbg(chip->dev, "gpio:%u set, value:%d\n", gpio, val); +} + +static int nsp_gpio_get(struct gpio_chip *gc, unsigned gpio) +{ + struct nsp_gpio *chip = gpiochip_get_data(gc); + + return !!(readl(chip->base + NSP_GPIO_DATA_IN) & BIT(gpio)); +} + +static int nsp_gpio_to_irq(struct gpio_chip *gc, unsigned offset) +{ + struct nsp_gpio *chip = gpiochip_get_data(gc); + + return irq_linear_revmap(chip->irq_domain, offset); +} + +static int nsp_get_groups_count(struct pinctrl_dev *pctldev) +{ + return 1; +} + +/* + * Only one group: "gpio_grp", since this local pinctrl device only performs + * GPIO specific PINCONF configurations + */ +static const char *nsp_get_group_name(struct pinctrl_dev *pctldev, + unsigned selector) +{ + return "gpio_grp"; +} + +static const struct pinctrl_ops nsp_pctrl_ops = { + .get_groups_count = nsp_get_groups_count, + .get_group_name = nsp_get_group_name, + .dt_node_to_map = pinconf_generic_dt_node_to_map_pin, + .dt_free_map = pinctrl_utils_dt_free_map, +}; + +static int nsp_gpio_set_slew(struct nsp_gpio *chip, unsigned gpio, u16 slew) +{ + if (slew) + nsp_set_bit(chip, IO_CTRL, NSP_GPIO_SLEW_RATE_EN, gpio, true); + else + nsp_set_bit(chip, IO_CTRL, NSP_GPIO_SLEW_RATE_EN, gpio, false); + + return 0; +} + +static int nsp_gpio_set_pull(struct nsp_gpio *chip, unsigned gpio, + bool pull_up, bool pull_down) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + nsp_set_bit(chip, IO_CTRL, NSP_PULL_DOWN_EN, gpio, pull_down); + nsp_set_bit(chip, IO_CTRL, NSP_PULL_UP_EN, gpio, pull_up); + spin_unlock_irqrestore(&chip->lock, flags); + + dev_dbg(chip->dev, "gpio:%u set pullup:%d pulldown: %d\n", + gpio, pull_up, pull_down); + return 0; +} + +static void nsp_gpio_get_pull(struct nsp_gpio *chip, unsigned gpio, + bool *pull_up, bool *pull_down) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + *pull_up = nsp_get_bit(chip, IO_CTRL, NSP_PULL_UP_EN, gpio); + *pull_down = nsp_get_bit(chip, IO_CTRL, NSP_PULL_DOWN_EN, gpio); + spin_unlock_irqrestore(&chip->lock, flags); +} + +static int nsp_gpio_set_strength(struct nsp_gpio *chip, unsigned gpio, + u16 strength) +{ + u32 offset, shift, i; + u32 val; + unsigned long flags; + + /* make sure drive strength is supported */ + if (strength < 2 || strength > 16 || (strength % 2)) + return -ENOTSUPP; + + shift = gpio; + offset = NSP_GPIO_DRV_CTRL; + dev_dbg(chip->dev, "gpio:%u set drive strength:%d mA\n", gpio, + strength); + spin_lock_irqsave(&chip->lock, flags); + strength = (strength / 2) - 1; + for (i = GPIO_DRV_STRENGTH_BITS; i > 0; i--) { + val = readl(chip->io_ctrl + offset); + val &= ~BIT(shift); + val |= ((strength >> (i-1)) & 0x1) << shift; + writel(val, chip->io_ctrl + offset); + offset += 4; + } + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +static int nsp_gpio_get_strength(struct nsp_gpio *chip, unsigned gpio, + u16 *strength) +{ + unsigned int offset, shift; + u32 val; + unsigned long flags; + int i; + + offset = NSP_GPIO_DRV_CTRL; + shift = gpio; + + spin_lock_irqsave(&chip->lock, flags); + *strength = 0; + for (i = (GPIO_DRV_STRENGTH_BITS - 1); i >= 0; i--) { + val = readl(chip->io_ctrl + offset) & BIT(shift); + val >>= shift; + *strength += (val << i); + offset += 4; + } + + /* convert to mA */ + *strength = (*strength + 1) * 2; + spin_unlock_irqrestore(&chip->lock, flags); + + return 0; +} + +int nsp_pin_config_group_get(struct pinctrl_dev *pctldev, unsigned selector, + unsigned long *config) +{ + return 0; +} + +int nsp_pin_config_group_set(struct pinctrl_dev *pctldev, unsigned selector, + unsigned long *configs, unsigned num_configs) +{ + return 0; +} + +static int nsp_pin_config_get(struct pinctrl_dev *pctldev, unsigned pin, + unsigned long *config) +{ + struct nsp_gpio *chip = pinctrl_dev_get_drvdata(pctldev); + enum pin_config_param param = pinconf_to_config_param(*config); + unsigned int gpio; + u16 arg = 0; + bool pull_up, pull_down; + int ret; + + gpio = nsp_pin_to_gpio(pin); + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + nsp_gpio_get_pull(chip, gpio, &pull_up, &pull_down); + if ((pull_up == false) && (pull_down == false)) + return 0; + else + return -EINVAL; + + case PIN_CONFIG_BIAS_PULL_UP: + nsp_gpio_get_pull(chip, gpio, &pull_up, &pull_down); + if (pull_up) + return 0; + else + return -EINVAL; + + case PIN_CONFIG_BIAS_PULL_DOWN: + nsp_gpio_get_pull(chip, gpio, &pull_up, &pull_down); + if (pull_down) + return 0; + else + return -EINVAL; + + case PIN_CONFIG_DRIVE_STRENGTH: + ret = nsp_gpio_get_strength(chip, gpio, &arg); + if (ret) + return ret; + *config = pinconf_to_config_packed(param, arg); + return 0; + + default: + return -ENOTSUPP; + } +} + +static int nsp_pin_config_set(struct pinctrl_dev *pctldev, unsigned pin, + unsigned long *configs, unsigned num_configs) +{ + struct nsp_gpio *chip = pinctrl_dev_get_drvdata(pctldev); + enum pin_config_param param; + u16 arg; + unsigned int i, gpio; + int ret = -ENOTSUPP; + + gpio = nsp_pin_to_gpio(pin); + for (i = 0; i < num_configs; i++) { + param = pinconf_to_config_param(configs[i]); + arg = pinconf_to_config_argument(configs[i]); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + ret = nsp_gpio_set_pull(chip, gpio, false, false); + if (ret < 0) + goto out; + break; + + case PIN_CONFIG_BIAS_PULL_UP: + ret = nsp_gpio_set_pull(chip, gpio, true, false); + if (ret < 0) + goto out; + break; + + case PIN_CONFIG_BIAS_PULL_DOWN: + ret = nsp_gpio_set_pull(chip, gpio, false, true); + if (ret < 0) + goto out; + break; + + case PIN_CONFIG_DRIVE_STRENGTH: + ret = nsp_gpio_set_strength(chip, gpio, arg); + if (ret < 0) + goto out; + break; + + case PIN_CONFIG_SLEW_RATE: + ret = nsp_gpio_set_slew(chip, gpio, arg); + if (ret < 0) + goto out; + break; + + default: + dev_err(chip->dev, "invalid configuration\n"); + return -ENOTSUPP; + } + } + +out: + return ret; +} + +static const struct pinconf_ops nsp_pconf_ops = { + .is_generic = true, + .pin_config_get = nsp_pin_config_get, + .pin_config_set = nsp_pin_config_set, + .pin_config_group_get = nsp_pin_config_group_get, + .pin_config_group_set = nsp_pin_config_group_set, +}; + +/* + * NSP GPIO controller supports some PINCONF related configurations such as + * pull up, pull down, slew and drive strength, when the pin is configured + * to GPIO. + * + * Here a local pinctrl device is created with simple 1-to-1 pin mapping to the + * local GPIO pins + */ +static int nsp_gpio_register_pinconf(struct nsp_gpio *chip) +{ + struct pinctrl_desc *pctldesc = &chip->pctldesc; + struct pinctrl_pin_desc *pins; + struct gpio_chip *gc = &chip->gc; + int i; + + pins = devm_kcalloc(chip->dev, gc->ngpio, sizeof(*pins), GFP_KERNEL); + if (!pins) + return -ENOMEM; + for (i = 0; i < gc->ngpio; i++) { + pins[i].number = i; + pins[i].name = devm_kasprintf(chip->dev, GFP_KERNEL, + "gpio-%d", i); + if (!pins[i].name) + return -ENOMEM; + } + pctldesc->name = dev_name(chip->dev); + pctldesc->pctlops = &nsp_pctrl_ops; + pctldesc->pins = pins; + pctldesc->npins = gc->ngpio; + pctldesc->confops = &nsp_pconf_ops; + + chip->pctl = pinctrl_register(pctldesc, chip->dev, chip); + if (IS_ERR(chip->pctl)) { + dev_err(chip->dev, "unable to register pinctrl device\n"); + return PTR_ERR(chip->pctl); + } + + return 0; +} + +static const struct of_device_id nsp_gpio_of_match[] = { + {.compatible = "brcm,nsp-gpio-a",}, + {} +}; + +static int nsp_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct nsp_gpio *chip; + struct gpio_chip *gc; + u32 val, count; + int irq, ret; + + if (of_property_read_u32(pdev->dev.of_node, "ngpios", &val)) { + dev_err(&pdev->dev, "Missing ngpios OF property\n"); + return -ENODEV; + } + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = dev; + platform_set_drvdata(pdev, chip); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + chip->base = devm_ioremap_resource(dev, res); + if (IS_ERR(chip->base)) { + dev_err(dev, "unable to map I/O memory\n"); + return PTR_ERR(chip->base); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + chip->io_ctrl = devm_ioremap_resource(dev, res); + if (IS_ERR(chip->io_ctrl)) { + dev_err(dev, "unable to map I/O memory\n"); + return PTR_ERR(chip->io_ctrl); + } + + spin_lock_init(&chip->lock); + gc = &chip->gc; + gc->base = -1; + gc->can_sleep = false; + gc->ngpio = val; + gc->label = dev_name(dev); + gc->parent = dev; + gc->of_node = dev->of_node; + gc->request = nsp_gpio_request; + gc->free = nsp_gpio_free; + gc->direction_input = nsp_gpio_direction_input; + gc->direction_output = nsp_gpio_direction_output; + gc->set = nsp_gpio_set; + gc->get = nsp_gpio_get; + gc->to_irq = nsp_gpio_to_irq; + + /* optional GPIO interrupt support */ + irq = platform_get_irq(pdev, 0); + if (irq > 0) { + /* Create irq domain so that each pin can be assigned an IRQ.*/ + chip->irq_domain = irq_domain_add_linear(gc->of_node, gc->ngpio, + &irq_domain_simple_ops, + chip); + if (!chip->irq_domain) { + dev_err(&pdev->dev, "Couldn't allocate IRQ domain\n"); + return -ENXIO; + } + + /* Map each gpio to an IRQ and set the handler for gpiolib. */ + for (count = 0; count < gc->ngpio; count++) { + int irq = irq_create_mapping(chip->irq_domain, count); + + irq_set_chip_and_handler(irq, &nsp_gpio_irq_chip, + handle_simple_irq); + irq_set_chip_data(irq, chip); + } + + /* Install ISR for this GPIO controller. */ + ret = devm_request_irq(&pdev->dev, irq, nsp_gpio_irq_handler, + IRQF_SHARED, "gpio-a", chip); + if (ret) { + dev_err(&pdev->dev, "Unable to request IRQ%d: %d\n", + irq, ret); + goto err_rm_gpiochip; + } + + val = readl(chip->base + NSP_CHIP_A_INT_MASK); + val = val | NSP_CHIP_A_GPIO_INT_BIT; + writel(val, (chip->base + NSP_CHIP_A_INT_MASK)); + } + + ret = gpiochip_add_data(gc, chip); + if (ret < 0) { + dev_err(dev, "unable to add GPIO chip\n"); + return ret; + } + + ret = nsp_gpio_register_pinconf(chip); + if (ret) { + dev_err(dev, "unable to register pinconf\n"); + goto err_rm_gpiochip; + } + + return 0; + +err_rm_gpiochip: + gpiochip_remove(gc); + + return ret; +} + +static struct platform_driver nsp_gpio_driver = { + .driver = { + .name = "nsp-gpio-a", + .of_match_table = nsp_gpio_of_match, + }, + .probe = nsp_gpio_probe, +}; + +static int __init nsp_gpio_init(void) +{ + return platform_driver_probe(&nsp_gpio_driver, nsp_gpio_probe); +} +arch_initcall_sync(nsp_gpio_init); -- cgit v1.2.3-54-g00ecf