From b2f68edfd5bb1c7613628a66ba71a0db0266ee22 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Thu, 12 Oct 2017 12:40:10 +0200 Subject: gpio: Add driver for Maxim MAX3191x industrial serializer The driver was developed for and tested with the MAX31913 built into the Revolution Pi by KUNBUS, but should work with all members of the MAX3191x family: MAX31910: low power MAX31911: LED drivers MAX31912: LED drivers + 2nd voltage monitor + low power MAX31913: LED drivers + 2nd voltage monitor MAX31953: LED drivers + 2nd voltage monitor + isolation MAX31963: LED drivers + 2nd voltage monitor + isolation + buck regulator Cc: Mathias Duckeck Signed-off-by: Lukas Wunner Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers/gpio/Kconfig') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 3388d54ba114..d5282cac6ea6 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1255,6 +1255,16 @@ config GPIO_74X164 shift registers. This driver can be used to provide access to more gpio outputs. +config GPIO_MAX3191X + tristate "Maxim MAX3191x industrial serializer" + select CRC8 + help + GPIO driver for Maxim MAX31910, MAX31911, MAX31912, MAX31913, + MAX31953 and MAX31963 industrial serializer, a daisy-chainable + chip to make 8 digital 24V inputs available via SPI. Supports + CRC checksums to guard against electromagnetic interference, + as well as undervoltage and overtemperature detection. + config GPIO_MAX7301 tristate "Maxim MAX7301 GPIO expander" select GPIO_MAX730X -- cgit v1.2.3 From dbe776c2ca54c3070358640fdf2fb28aeaa17d31 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Thu, 12 Oct 2017 20:36:16 +0900 Subject: gpio: uniphier: add UniPhier GPIO controller driver This GPIO controller is used on UniPhier SoC family. It also serves as an interrupt controller, but interrupt signals are just delivered to the parent irqchip without any latching or OR'ing. This type of hardware can be well described with hierarchy IRQ domain. One unfortunate thing for this device is that the interrupt mapping to the interrupt parent is not contiguous. I asked how DT can describe interrupt mapping between two irqchips [1], but I could not find a good solution (at least in the framework level). In fact, irqchip drivers using hierarchy domain generally hard-code the DT binding of their parent. After tackling on several approaches such as hard-code of hwirqs, irq_domain_push_irq(), I ended up with a vendor specific property. If we come up with a good idea to support this in the framework, we can migrate over to it, but we can live with a driver-level solution for now. [1] https://lkml.org/lkml/2017/7/6/758 Signed-off-by: Masahiro Yamada Signed-off-by: Linus Walleij --- MAINTAINERS | 1 + drivers/gpio/Kconfig | 8 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-uniphier.c | 507 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 517 insertions(+) create mode 100644 drivers/gpio/gpio-uniphier.c (limited to 'drivers/gpio/Kconfig') diff --git a/MAINTAINERS b/MAINTAINERS index 193b86116cd1..213adea9d9ff 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2025,6 +2025,7 @@ F: arch/arm/mm/cache-uniphier.c F: arch/arm64/boot/dts/socionext/ F: drivers/bus/uniphier-system-bus.c F: drivers/clk/uniphier/ +F: drivers/gpio/gpio-uniphier.c F: drivers/i2c/busses/i2c-uniphier* F: drivers/irqchip/irq-uniphier-aidet.c F: drivers/pinctrl/uniphier/ diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index d5282cac6ea6..e7f3f39e69df 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -474,6 +474,14 @@ config GPIO_TZ1090_PDC help Say yes here to support Toumaz Xenif TZ1090 PDC GPIOs. +config GPIO_UNIPHIER + tristate "UniPhier GPIO support" + depends on ARCH_UNIPHIER || COMPILE_TEST + depends on OF_GPIO + select IRQ_DOMAIN_HIERARCHY + help + Say yes here to support UniPhier GPIOs. + config GPIO_VF610 def_bool y depends on ARCH_MXC && SOC_VF610 diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index cf9ecfb24ef4..ac4b7c34a668 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -132,6 +132,7 @@ obj-$(CONFIG_GPIO_TWL6040) += gpio-twl6040.o obj-$(CONFIG_GPIO_TZ1090) += gpio-tz1090.o obj-$(CONFIG_GPIO_TZ1090_PDC) += gpio-tz1090-pdc.o obj-$(CONFIG_GPIO_UCB1400) += gpio-ucb1400.o +obj-$(CONFIG_GPIO_UNIPHIER) += gpio-uniphier.o obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o obj-$(CONFIG_GPIO_VIPERBOARD) += gpio-viperboard.o obj-$(CONFIG_GPIO_VR41XX) += gpio-vr41xx.o diff --git a/drivers/gpio/gpio-uniphier.c b/drivers/gpio/gpio-uniphier.c new file mode 100644 index 000000000000..d62cea4ed6b7 --- /dev/null +++ b/drivers/gpio/gpio-uniphier.c @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2017 Socionext Inc. + * Author: Masahiro Yamada + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UNIPHIER_GPIO_BANK_MASK \ + GENMASK((UNIPHIER_GPIO_LINES_PER_BANK) - 1, 0) + +#define UNIPHIER_GPIO_IRQ_MAX_NUM 24 + +#define UNIPHIER_GPIO_PORT_DATA 0x0 /* data */ +#define UNIPHIER_GPIO_PORT_DIR 0x4 /* direction (1:in, 0:out) */ +#define UNIPHIER_GPIO_IRQ_EN 0x90 /* irq enable */ +#define UNIPHIER_GPIO_IRQ_MODE 0x94 /* irq mode (1: both edge) */ +#define UNIPHIER_GPIO_IRQ_FLT_EN 0x98 /* noise filter enable */ +#define UNIPHIER_GPIO_IRQ_FLT_CYC 0x9c /* noise filter clock cycle */ + +struct uniphier_gpio_priv { + struct gpio_chip chip; + struct irq_chip irq_chip; + struct irq_domain *domain; + void __iomem *regs; + spinlock_t lock; + u32 saved_vals[0]; +}; + +static unsigned int uniphier_gpio_bank_to_reg(unsigned int bank) +{ + unsigned int reg; + + reg = (bank + 1) * 8; + + /* + * Unfortunately, the GPIO port registers are not contiguous because + * offset 0x90-0x9f is used for IRQ. Add 0x10 when crossing the region. + */ + if (reg >= UNIPHIER_GPIO_IRQ_EN) + reg += 0x10; + + return reg; +} + +static void uniphier_gpio_get_bank_and_mask(unsigned int offset, + unsigned int *bank, u32 *mask) +{ + *bank = offset / UNIPHIER_GPIO_LINES_PER_BANK; + *mask = BIT(offset % UNIPHIER_GPIO_LINES_PER_BANK); +} + +static void uniphier_gpio_reg_update(struct uniphier_gpio_priv *priv, + unsigned int reg, u32 mask, u32 val) +{ + unsigned long flags; + u32 tmp; + + spin_lock_irqsave(&priv->lock, flags); + tmp = readl(priv->regs + reg); + tmp &= ~mask; + tmp |= mask & val; + writel(tmp, priv->regs + reg); + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void uniphier_gpio_bank_write(struct gpio_chip *chip, unsigned int bank, + unsigned int reg, u32 mask, u32 val) +{ + struct uniphier_gpio_priv *priv = gpiochip_get_data(chip); + + if (!mask) + return; + + uniphier_gpio_reg_update(priv, uniphier_gpio_bank_to_reg(bank) + reg, + mask, val); +} + +static void uniphier_gpio_offset_write(struct gpio_chip *chip, + unsigned int offset, unsigned int reg, + int val) +{ + unsigned int bank; + u32 mask; + + uniphier_gpio_get_bank_and_mask(offset, &bank, &mask); + + uniphier_gpio_bank_write(chip, bank, reg, mask, val ? mask : 0); +} + +static int uniphier_gpio_offset_read(struct gpio_chip *chip, + unsigned int offset, unsigned int reg) +{ + struct uniphier_gpio_priv *priv = gpiochip_get_data(chip); + unsigned int bank, reg_offset; + u32 mask; + + uniphier_gpio_get_bank_and_mask(offset, &bank, &mask); + reg_offset = uniphier_gpio_bank_to_reg(bank) + reg; + + return !!(readl(priv->regs + reg_offset) & mask); +} + +static int uniphier_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + return uniphier_gpio_offset_read(chip, offset, UNIPHIER_GPIO_PORT_DIR); +} + +static int uniphier_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + uniphier_gpio_offset_write(chip, offset, UNIPHIER_GPIO_PORT_DIR, 1); + + return 0; +} + +static int uniphier_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int val) +{ + uniphier_gpio_offset_write(chip, offset, UNIPHIER_GPIO_PORT_DATA, val); + uniphier_gpio_offset_write(chip, offset, UNIPHIER_GPIO_PORT_DIR, 0); + + return 0; +} + +static int uniphier_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + return uniphier_gpio_offset_read(chip, offset, UNIPHIER_GPIO_PORT_DATA); +} + +static void uniphier_gpio_set(struct gpio_chip *chip, + unsigned int offset, int val) +{ + uniphier_gpio_offset_write(chip, offset, UNIPHIER_GPIO_PORT_DATA, val); +} + +static void uniphier_gpio_set_multiple(struct gpio_chip *chip, + unsigned long *mask, unsigned long *bits) +{ + unsigned int bank, shift, bank_mask, bank_bits; + int i; + + for (i = 0; i < chip->ngpio; i += UNIPHIER_GPIO_LINES_PER_BANK) { + bank = i / UNIPHIER_GPIO_LINES_PER_BANK; + shift = i % BITS_PER_LONG; + bank_mask = (mask[BIT_WORD(i)] >> shift) & + UNIPHIER_GPIO_BANK_MASK; + bank_bits = bits[BIT_WORD(i)] >> shift; + + uniphier_gpio_bank_write(chip, bank, UNIPHIER_GPIO_PORT_DATA, + bank_mask, bank_bits); + } +} + +static int uniphier_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) +{ + struct irq_fwspec fwspec; + + if (offset < UNIPHIER_GPIO_IRQ_OFFSET) + return -ENXIO; + + fwspec.fwnode = of_node_to_fwnode(chip->parent->of_node); + fwspec.param_count = 2; + fwspec.param[0] = offset - UNIPHIER_GPIO_IRQ_OFFSET; + fwspec.param[1] = IRQ_TYPE_NONE; + + return irq_create_fwspec_mapping(&fwspec); +} + +static void uniphier_gpio_irq_mask(struct irq_data *data) +{ + struct uniphier_gpio_priv *priv = data->chip_data; + u32 mask = BIT(data->hwirq); + + uniphier_gpio_reg_update(priv, UNIPHIER_GPIO_IRQ_EN, mask, 0); + + return irq_chip_mask_parent(data); +} + +static void uniphier_gpio_irq_unmask(struct irq_data *data) +{ + struct uniphier_gpio_priv *priv = data->chip_data; + u32 mask = BIT(data->hwirq); + + uniphier_gpio_reg_update(priv, UNIPHIER_GPIO_IRQ_EN, mask, mask); + + return irq_chip_unmask_parent(data); +} + +static int uniphier_gpio_irq_set_type(struct irq_data *data, unsigned int type) +{ + struct uniphier_gpio_priv *priv = data->chip_data; + u32 mask = BIT(data->hwirq); + u32 val = 0; + + if (type == IRQ_TYPE_EDGE_BOTH) { + val = mask; + type = IRQ_TYPE_EDGE_FALLING; + } + + uniphier_gpio_reg_update(priv, UNIPHIER_GPIO_IRQ_MODE, mask, val); + /* To enable both edge detection, the noise filter must be enabled. */ + uniphier_gpio_reg_update(priv, UNIPHIER_GPIO_IRQ_FLT_EN, mask, val); + + return irq_chip_set_type_parent(data, type); +} + +static int uniphier_gpio_irq_get_parent_hwirq(struct uniphier_gpio_priv *priv, + unsigned int hwirq) +{ + struct device_node *np = priv->chip.parent->of_node; + const __be32 *range; + u32 base, parent_base, size; + int len; + + range = of_get_property(np, "socionext,interrupt-ranges", &len); + if (!range) + return -EINVAL; + + len /= sizeof(*range); + + for (; len >= 3; len -= 3) { + base = be32_to_cpu(*range++); + parent_base = be32_to_cpu(*range++); + size = be32_to_cpu(*range++); + + if (base <= hwirq && hwirq < base + size) + return hwirq - base + parent_base; + } + + return -ENOENT; +} + +static int uniphier_gpio_irq_domain_translate(struct irq_domain *domain, + struct irq_fwspec *fwspec, + unsigned long *out_hwirq, + unsigned int *out_type) +{ + if (WARN_ON(fwspec->param_count < 2)) + return -EINVAL; + + *out_hwirq = fwspec->param[0]; + *out_type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; + + return 0; +} + +static int uniphier_gpio_irq_domain_alloc(struct irq_domain *domain, + unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + struct uniphier_gpio_priv *priv = domain->host_data; + struct irq_fwspec parent_fwspec; + irq_hw_number_t hwirq; + unsigned int type; + int ret; + + if (WARN_ON(nr_irqs != 1)) + return -EINVAL; + + ret = uniphier_gpio_irq_domain_translate(domain, arg, &hwirq, &type); + if (ret) + return ret; + + ret = uniphier_gpio_irq_get_parent_hwirq(priv, hwirq); + if (ret < 0) + return ret; + + /* parent is UniPhier AIDET */ + parent_fwspec.fwnode = domain->parent->fwnode; + parent_fwspec.param_count = 2; + parent_fwspec.param[0] = ret; + parent_fwspec.param[1] = (type == IRQ_TYPE_EDGE_BOTH) ? + IRQ_TYPE_EDGE_FALLING : type; + + ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, + &priv->irq_chip, priv); + if (ret) + return ret; + + return irq_domain_alloc_irqs_parent(domain, virq, 1, &parent_fwspec); +} + +static void uniphier_gpio_irq_domain_activate(struct irq_domain *domain, + struct irq_data *data) +{ + struct uniphier_gpio_priv *priv = domain->host_data; + struct gpio_chip *chip = &priv->chip; + + gpiochip_lock_as_irq(chip, data->hwirq + UNIPHIER_GPIO_IRQ_OFFSET); +} + +static void uniphier_gpio_irq_domain_deactivate(struct irq_domain *domain, + struct irq_data *data) +{ + struct uniphier_gpio_priv *priv = domain->host_data; + struct gpio_chip *chip = &priv->chip; + + gpiochip_unlock_as_irq(chip, data->hwirq + UNIPHIER_GPIO_IRQ_OFFSET); +} + +static const struct irq_domain_ops uniphier_gpio_irq_domain_ops = { + .alloc = uniphier_gpio_irq_domain_alloc, + .free = irq_domain_free_irqs_common, + .activate = uniphier_gpio_irq_domain_activate, + .deactivate = uniphier_gpio_irq_domain_deactivate, + .translate = uniphier_gpio_irq_domain_translate, +}; + +static void uniphier_gpio_hw_init(struct uniphier_gpio_priv *priv) +{ + /* + * Due to the hardware design, the noise filter must be enabled to + * detect both edge interrupts. This filter is intended to remove the + * noise from the irq lines. It does not work for GPIO input, so GPIO + * debounce is not supported. Unfortunately, the filter period is + * shared among all irq lines. Just choose a sensible period here. + */ + writel(0xff, priv->regs + UNIPHIER_GPIO_IRQ_FLT_CYC); +} + +static unsigned int uniphier_gpio_get_nbanks(unsigned int ngpio) +{ + return DIV_ROUND_UP(ngpio, UNIPHIER_GPIO_LINES_PER_BANK); +} + +static int uniphier_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *parent_np; + struct irq_domain *parent_domain; + struct uniphier_gpio_priv *priv; + struct gpio_chip *chip; + struct irq_chip *irq_chip; + struct resource *regs; + unsigned int nregs; + u32 ngpios; + int ret; + + parent_np = of_irq_find_parent(dev->of_node); + if (!parent_np) + return -ENXIO; + + parent_domain = irq_find_host(parent_np); + of_node_put(parent_np); + if (!parent_domain) + return -EPROBE_DEFER; + + ret = of_property_read_u32(dev->of_node, "ngpios", &ngpios); + if (ret) + return ret; + + nregs = uniphier_gpio_get_nbanks(ngpios) * 2 + 3; + priv = devm_kzalloc(dev, + sizeof(*priv) + sizeof(priv->saved_vals[0]) * nregs, + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(dev, regs); + if (IS_ERR(priv->regs)) + return PTR_ERR(priv->regs); + + spin_lock_init(&priv->lock); + + chip = &priv->chip; + chip->label = dev_name(dev); + chip->parent = dev; + chip->request = gpiochip_generic_request; + chip->free = gpiochip_generic_free; + chip->get_direction = uniphier_gpio_get_direction; + chip->direction_input = uniphier_gpio_direction_input; + chip->direction_output = uniphier_gpio_direction_output; + chip->get = uniphier_gpio_get; + chip->set = uniphier_gpio_set; + chip->set_multiple = uniphier_gpio_set_multiple; + chip->to_irq = uniphier_gpio_to_irq; + chip->base = -1; + chip->ngpio = ngpios; + + irq_chip = &priv->irq_chip; + irq_chip->name = dev_name(dev); + irq_chip->irq_mask = uniphier_gpio_irq_mask; + irq_chip->irq_unmask = uniphier_gpio_irq_unmask; + irq_chip->irq_eoi = irq_chip_eoi_parent; + irq_chip->irq_set_affinity = irq_chip_set_affinity_parent; + irq_chip->irq_set_type = uniphier_gpio_irq_set_type; + + uniphier_gpio_hw_init(priv); + + ret = devm_gpiochip_add_data(dev, chip, priv); + if (ret) + return ret; + + priv->domain = irq_domain_create_hierarchy( + parent_domain, 0, + UNIPHIER_GPIO_IRQ_MAX_NUM, + of_node_to_fwnode(dev->of_node), + &uniphier_gpio_irq_domain_ops, priv); + if (!priv->domain) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + return 0; +} + +static int uniphier_gpio_remove(struct platform_device *pdev) +{ + struct uniphier_gpio_priv *priv = platform_get_drvdata(pdev); + + irq_domain_remove(priv->domain); + + return 0; +} + +static int __maybe_unused uniphier_gpio_suspend(struct device *dev) +{ + struct uniphier_gpio_priv *priv = dev_get_drvdata(dev); + unsigned int nbanks = uniphier_gpio_get_nbanks(priv->chip.ngpio); + u32 *val = priv->saved_vals; + unsigned int reg; + int i; + + for (i = 0; i < nbanks; i++) { + reg = uniphier_gpio_bank_to_reg(i); + + *val++ = readl(priv->regs + reg + UNIPHIER_GPIO_PORT_DATA); + *val++ = readl(priv->regs + reg + UNIPHIER_GPIO_PORT_DIR); + } + + *val++ = readl(priv->regs + UNIPHIER_GPIO_IRQ_EN); + *val++ = readl(priv->regs + UNIPHIER_GPIO_IRQ_MODE); + *val++ = readl(priv->regs + UNIPHIER_GPIO_IRQ_FLT_EN); + + return 0; +} + +static int __maybe_unused uniphier_gpio_resume(struct device *dev) +{ + struct uniphier_gpio_priv *priv = dev_get_drvdata(dev); + unsigned int nbanks = uniphier_gpio_get_nbanks(priv->chip.ngpio); + const u32 *val = priv->saved_vals; + unsigned int reg; + int i; + + for (i = 0; i < nbanks; i++) { + reg = uniphier_gpio_bank_to_reg(i); + + writel(*val++, priv->regs + reg + UNIPHIER_GPIO_PORT_DATA); + writel(*val++, priv->regs + reg + UNIPHIER_GPIO_PORT_DIR); + } + + writel(*val++, priv->regs + UNIPHIER_GPIO_IRQ_EN); + writel(*val++, priv->regs + UNIPHIER_GPIO_IRQ_MODE); + writel(*val++, priv->regs + UNIPHIER_GPIO_IRQ_FLT_EN); + + uniphier_gpio_hw_init(priv); + + return 0; +} + +static const struct dev_pm_ops uniphier_gpio_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(uniphier_gpio_suspend, + uniphier_gpio_resume) +}; + +static const struct of_device_id uniphier_gpio_match[] = { + { .compatible = "socionext,uniphier-gpio" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, uniphier_gpio_match); + +static struct platform_driver uniphier_gpio_driver = { + .probe = uniphier_gpio_probe, + .remove = uniphier_gpio_remove, + .driver = { + .name = "uniphier-gpio", + .of_match_table = uniphier_gpio_match, + .pm = &uniphier_gpio_pm_ops, + }, +}; +module_platform_driver(uniphier_gpio_driver); + +MODULE_AUTHOR("Masahiro Yamada "); +MODULE_DESCRIPTION("UniPhier GPIO driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 0ba31dc201deb4f47277d7ff676b45da31c530e4 Mon Sep 17 00:00:00 2001 From: Doug Berger Date: Tue, 24 Oct 2017 12:54:50 -0700 Subject: gpio: brcmstb: consolidate interrupt domains The GPIOLIB IRQ chip helpers were very appealing, but badly broke the 1:1 mapping between a GPIO controller's device_node and its interrupt domain. When another device-tree node references a GPIO device as its interrupt parent, the irq_create_of_mapping() function looks for the irq domain of the GPIO device and since all bank irq domains reference the same GPIO device node it always resolves to the irq domain of the first bank regardless of which bank the number of the GPIO should resolve. This domain can only map hwirq numbers 0-31 so interrupts on GPIO above that can't be mapped by the device-tree. This commit effectively reverts the patch from Gregory Fong [1] that was accepted upstream and replaces it with a consolidated irq domain implementation with one larger interrupt domain per GPIO controller instance spanning multiple GPIO banks based on an earlier patch [2] also submitted by Gregory Fong. [1] https://patchwork.kernel.org/patch/6921561/ [2] https://patchwork.kernel.org/patch/6347811/ Fixes: 19a7b6940b78 ("gpio: brcmstb: Add interrupt and wakeup source support") Signed-off-by: Doug Berger Reviewed-by: Gregory Fong Reviewed-by: Florian Fainelli Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 2 +- drivers/gpio/gpio-brcmstb.c | 188 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 146 insertions(+), 44 deletions(-) (limited to 'drivers/gpio/Kconfig') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index e7f3f39e69df..f002edda555b 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -139,7 +139,7 @@ config GPIO_BRCMSTB default y if (ARCH_BRCMSTB || BMIPS_GENERIC) depends on OF_GPIO && (ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST) select GPIO_GENERIC - select GPIOLIB_IRQCHIP + select IRQ_DOMAIN help Say yes here to enable GPIO support for Broadcom STB (BCM7XXX) SoCs. diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c index 183863902f7f..9a8c603625a3 100644 --- a/drivers/gpio/gpio-brcmstb.c +++ b/drivers/gpio/gpio-brcmstb.c @@ -38,20 +38,22 @@ struct brcmstb_gpio_bank { struct gpio_chip gc; struct brcmstb_gpio_priv *parent_priv; u32 width; - struct irq_chip irq_chip; }; struct brcmstb_gpio_priv { struct list_head bank_list; void __iomem *reg_base; struct platform_device *pdev; + struct irq_domain *irq_domain; + struct irq_chip irq_chip; int parent_irq; int gpio_base; + int num_gpios; int parent_wake_irq; struct notifier_block reboot_notifier; }; -#define MAX_GPIO_PER_BANK 32 +#define MAX_GPIO_PER_BANK 32 #define GPIO_BANK(gpio) ((gpio) >> 5) /* assumes MAX_GPIO_PER_BANK is a multiple of 2 */ #define GPIO_BIT(gpio) ((gpio) & (MAX_GPIO_PER_BANK - 1)) @@ -78,24 +80,42 @@ brcmstb_gpio_get_active_irqs(struct brcmstb_gpio_bank *bank) return status; } +static int brcmstb_gpio_hwirq_to_offset(irq_hw_number_t hwirq, + struct brcmstb_gpio_bank *bank) +{ + return hwirq - (bank->gc.base - bank->parent_priv->gpio_base); +} + static void brcmstb_gpio_set_imask(struct brcmstb_gpio_bank *bank, - unsigned int offset, bool enable) + unsigned int hwirq, bool enable) { struct gpio_chip *gc = &bank->gc; struct brcmstb_gpio_priv *priv = bank->parent_priv; + u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(hwirq, bank)); u32 imask; unsigned long flags; spin_lock_irqsave(&gc->bgpio_lock, flags); imask = gc->read_reg(priv->reg_base + GIO_MASK(bank->id)); if (enable) - imask |= BIT(offset); + imask |= mask; else - imask &= ~BIT(offset); + imask &= ~mask; gc->write_reg(priv->reg_base + GIO_MASK(bank->id), imask); spin_unlock_irqrestore(&gc->bgpio_lock, flags); } +static int brcmstb_gpio_to_irq(struct gpio_chip *gc, unsigned offset) +{ + struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc); + /* gc_offset is relative to this gpio_chip; want real offset */ + int hwirq = offset + (gc->base - priv->gpio_base); + + if (hwirq >= priv->num_gpios) + return -ENXIO; + return irq_create_mapping(priv->irq_domain, hwirq); +} + /* -------------------- IRQ chip functions -------------------- */ static void brcmstb_gpio_irq_mask(struct irq_data *d) @@ -119,7 +139,7 @@ static void brcmstb_gpio_irq_ack(struct irq_data *d) struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc); struct brcmstb_gpio_priv *priv = bank->parent_priv; - u32 mask = BIT(d->hwirq); + u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank)); gc->write_reg(priv->reg_base + GIO_STAT(bank->id), mask); } @@ -129,7 +149,7 @@ static int brcmstb_gpio_irq_set_type(struct irq_data *d, unsigned int type) struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct brcmstb_gpio_bank *bank = gpiochip_get_data(gc); struct brcmstb_gpio_priv *priv = bank->parent_priv; - u32 mask = BIT(d->hwirq); + u32 mask = BIT(brcmstb_gpio_hwirq_to_offset(d->hwirq, bank)); u32 edge_insensitive, iedge_insensitive; u32 edge_config, iedge_config; u32 level, ilevel; @@ -226,18 +246,20 @@ static irqreturn_t brcmstb_gpio_wake_irq_handler(int irq, void *data) static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank) { struct brcmstb_gpio_priv *priv = bank->parent_priv; - struct irq_domain *irq_domain = bank->gc.irqdomain; + struct irq_domain *domain = priv->irq_domain; + int hwbase = bank->gc.base - priv->gpio_base; unsigned long status; while ((status = brcmstb_gpio_get_active_irqs(bank))) { - int bit; + unsigned int irq, offset; - for_each_set_bit(bit, &status, 32) { - if (bit >= bank->width) + for_each_set_bit(offset, &status, 32) { + if (offset >= bank->width) dev_warn(&priv->pdev->dev, "IRQ for invalid GPIO (bank=%d, offset=%d)\n", - bank->id, bit); - generic_handle_irq(irq_find_mapping(irq_domain, bit)); + bank->id, offset); + irq = irq_linear_revmap(domain, hwbase + offset); + generic_handle_irq(irq); } } } @@ -245,8 +267,7 @@ static void brcmstb_gpio_irq_bank_handler(struct brcmstb_gpio_bank *bank) /* Each UPG GIO block has one IRQ for all banks */ static void brcmstb_gpio_irq_handler(struct irq_desc *desc) { - struct gpio_chip *gc = irq_desc_get_handler_data(desc); - struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc); + struct brcmstb_gpio_priv *priv = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); struct brcmstb_gpio_bank *bank; @@ -272,6 +293,63 @@ static int brcmstb_gpio_reboot(struct notifier_block *nb, return NOTIFY_DONE; } +static struct brcmstb_gpio_bank *brcmstb_gpio_hwirq_to_bank( + struct brcmstb_gpio_priv *priv, irq_hw_number_t hwirq) +{ + struct brcmstb_gpio_bank *bank; + int i = 0; + + /* banks are in descending order */ + list_for_each_entry_reverse(bank, &priv->bank_list, node) { + i += bank->gc.ngpio; + if (hwirq < i) + return bank; + } + return NULL; +} + +/* + * This lock class tells lockdep that GPIO irqs are in a different + * category than their parents, so it won't report false recursion. + */ +static struct lock_class_key brcmstb_gpio_irq_lock_class; + + +static int brcmstb_gpio_irq_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct brcmstb_gpio_priv *priv = d->host_data; + struct brcmstb_gpio_bank *bank = + brcmstb_gpio_hwirq_to_bank(priv, hwirq); + struct platform_device *pdev = priv->pdev; + int ret; + + if (!bank) + return -EINVAL; + + dev_dbg(&pdev->dev, "Mapping irq %d for gpio line %d (bank %d)\n", + irq, (int)hwirq, bank->id); + ret = irq_set_chip_data(irq, &bank->gc); + if (ret < 0) + return ret; + irq_set_lockdep_class(irq, &brcmstb_gpio_irq_lock_class); + irq_set_chip_and_handler(irq, &priv->irq_chip, handle_level_irq); + irq_set_noprobe(irq); + return 0; +} + +static void brcmstb_gpio_irq_unmap(struct irq_domain *d, unsigned int irq) +{ + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); +} + +static const struct irq_domain_ops brcmstb_gpio_irq_domain_ops = { + .map = brcmstb_gpio_irq_map, + .unmap = brcmstb_gpio_irq_unmap, + .xlate = irq_domain_xlate_twocell, +}; + /* Make sure that the number of banks matches up between properties */ static int brcmstb_gpio_sanity_check_banks(struct device *dev, struct device_node *np, struct resource *res) @@ -293,13 +371,25 @@ static int brcmstb_gpio_remove(struct platform_device *pdev) { struct brcmstb_gpio_priv *priv = platform_get_drvdata(pdev); struct brcmstb_gpio_bank *bank; - int ret = 0; + int offset, ret = 0, virq; if (!priv) { dev_err(&pdev->dev, "called %s without drvdata!\n", __func__); return -EFAULT; } + if (priv->parent_irq > 0) + irq_set_chained_handler_and_data(priv->parent_irq, NULL, NULL); + + /* Remove all IRQ mappings and delete the domain */ + if (priv->irq_domain) { + for (offset = 0; offset < priv->num_gpios; offset++) { + virq = irq_find_mapping(priv->irq_domain, offset); + irq_dispose_mapping(virq); + } + irq_domain_remove(priv->irq_domain); + } + /* * You can lose return values below, but we report all errors, and it's * more important to actually perform all of the steps. @@ -347,26 +437,24 @@ static int brcmstb_gpio_of_xlate(struct gpio_chip *gc, return offset; } -/* Before calling, must have bank->parent_irq set and gpiochip registered */ +/* priv->parent_irq and priv->num_gpios must be set before calling */ static int brcmstb_gpio_irq_setup(struct platform_device *pdev, - struct brcmstb_gpio_bank *bank) + struct brcmstb_gpio_priv *priv) { - struct brcmstb_gpio_priv *priv = bank->parent_priv; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; int err; - bank->irq_chip.name = dev_name(dev); - bank->irq_chip.irq_mask = brcmstb_gpio_irq_mask; - bank->irq_chip.irq_unmask = brcmstb_gpio_irq_unmask; - bank->irq_chip.irq_ack = brcmstb_gpio_irq_ack; - bank->irq_chip.irq_set_type = brcmstb_gpio_irq_set_type; - - /* Ensures that all non-wakeup IRQs are disabled at suspend */ - bank->irq_chip.flags = IRQCHIP_MASK_ON_SUSPEND; + priv->irq_domain = + irq_domain_add_linear(np, priv->num_gpios, + &brcmstb_gpio_irq_domain_ops, + priv); + if (!priv->irq_domain) { + dev_err(dev, "Couldn't allocate IRQ domain\n"); + return -ENXIO; + } - if (IS_ENABLED(CONFIG_PM_SLEEP) && !priv->parent_wake_irq && - of_property_read_bool(np, "wakeup-source")) { + if (of_property_read_bool(np, "wakeup-source")) { priv->parent_wake_irq = platform_get_irq(pdev, 1); if (priv->parent_wake_irq < 0) { priv->parent_wake_irq = 0; @@ -387,7 +475,7 @@ static int brcmstb_gpio_irq_setup(struct platform_device *pdev, if (err < 0) { dev_err(dev, "Couldn't request wake IRQ"); - return err; + goto out_free_domain; } priv->reboot_notifier.notifier_call = @@ -396,17 +484,28 @@ static int brcmstb_gpio_irq_setup(struct platform_device *pdev, } } + priv->irq_chip.name = dev_name(dev); + priv->irq_chip.irq_disable = brcmstb_gpio_irq_mask; + priv->irq_chip.irq_mask = brcmstb_gpio_irq_mask; + priv->irq_chip.irq_unmask = brcmstb_gpio_irq_unmask; + priv->irq_chip.irq_ack = brcmstb_gpio_irq_ack; + priv->irq_chip.irq_set_type = brcmstb_gpio_irq_set_type; + + /* Ensures that all non-wakeup IRQs are disabled at suspend */ + priv->irq_chip.flags = IRQCHIP_MASK_ON_SUSPEND; + if (priv->parent_wake_irq) - bank->irq_chip.irq_set_wake = brcmstb_gpio_irq_set_wake; + priv->irq_chip.irq_set_wake = brcmstb_gpio_irq_set_wake; - err = gpiochip_irqchip_add(&bank->gc, &bank->irq_chip, 0, - handle_level_irq, IRQ_TYPE_NONE); - if (err) - return err; - gpiochip_set_chained_irqchip(&bank->gc, &bank->irq_chip, - priv->parent_irq, brcmstb_gpio_irq_handler); + irq_set_chained_handler_and_data(priv->parent_irq, + brcmstb_gpio_irq_handler, priv); return 0; + +out_free_domain: + irq_domain_remove(priv->irq_domain); + + return err; } static int brcmstb_gpio_probe(struct platform_device *pdev) @@ -511,6 +610,8 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) gc->of_xlate = brcmstb_gpio_of_xlate; /* not all ngpio lines are valid, will use bank width later */ gc->ngpio = MAX_GPIO_PER_BANK; + if (priv->parent_irq > 0) + gc->to_irq = brcmstb_gpio_to_irq; /* * Mask all interrupts by default, since wakeup interrupts may @@ -526,12 +627,6 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) } gpio_base += gc->ngpio; - if (priv->parent_irq > 0) { - err = brcmstb_gpio_irq_setup(pdev, bank); - if (err) - goto fail; - } - dev_dbg(dev, "bank=%d, base=%d, ngpio=%d, width=%d\n", bank->id, gc->base, gc->ngpio, bank->width); @@ -541,6 +636,13 @@ static int brcmstb_gpio_probe(struct platform_device *pdev) num_banks++; } + priv->num_gpios = gpio_base - priv->gpio_base; + if (priv->parent_irq > 0) { + err = brcmstb_gpio_irq_setup(pdev, priv); + if (err) + goto fail; + } + dev_info(dev, "Registered %d banks (GPIO(s): %d-%d)\n", num_banks, priv->gpio_base, gpio_base - 1); -- cgit v1.2.3 From e1289dba18bf905870a42b994c003586688d9353 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Fri, 27 Oct 2017 21:21:47 +0100 Subject: gpio: mb86s7x: share with other SoCs as module In order to reuse this driver for the Socionext Synquacer SC2A11 SoC, which inherited this IP from Fujitsu, remove the ARCH_MB86S7X Kconfig dependency, and revert the changes that prevent it from being built as a module. This reverts commits d65aa4b67b4f47f303bdeaef1e4d42ef18e6b293 and d5610e514e92144d19bd5e39e5cf3804bbf85f3e. Cc: Geliang Tang Cc: Paul Gortmaker Signed-off-by: Ard Biesheuvel [Folded in module_platform_driver() fixup] Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 3 +-- drivers/gpio/gpio-mb86s7x.c | 7 ++++++- 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers/gpio/Kconfig') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index f002edda555b..9feb8e1ff2ff 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -286,8 +286,7 @@ config GPIO_LYNXPOINT Requires ACPI device enumeration code to set up a platform device. config GPIO_MB86S7X - bool "GPIO support for Fujitsu MB86S7x Platforms" - depends on ARCH_MB86S7X || COMPILE_TEST + tristate "GPIO support for Fujitsu MB86S7x Platforms" help Say yes here to support the GPIO controller in Fujitsu MB86S70 SoCs. diff --git a/drivers/gpio/gpio-mb86s7x.c b/drivers/gpio/gpio-mb86s7x.c index 94d772677ed6..b140806bded3 100644 --- a/drivers/gpio/gpio-mb86s7x.c +++ b/drivers/gpio/gpio-mb86s7x.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -209,6 +210,7 @@ static const struct of_device_id mb86s70_gpio_dt_ids[] = { { .compatible = "fujitsu,mb86s70-gpio" }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, mb86s70_gpio_dt_ids); static struct platform_driver mb86s70_gpio_driver = { .driver = { @@ -218,5 +220,8 @@ static struct platform_driver mb86s70_gpio_driver = { .probe = mb86s70_gpio_probe, .remove = mb86s70_gpio_remove, }; +module_platform_driver(mb86s70_gpio_driver); -builtin_platform_driver(mb86s70_gpio_driver); +MODULE_DESCRIPTION("MB86S7x GPIO Driver"); +MODULE_ALIAS("platform:mb86s70-gpio"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 5b2b135a87fcfb2b27c3c192fd7c3b053f0c5fa2 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 7 Nov 2017 19:15:56 +0100 Subject: gpio: Add Tegra186 support Tegra186 has two GPIO controllers that are largely register compatible between one another but are completely different from the controller found on earlier generations. Signed-off-by: Thierry Reding Acked-by: Grygorii Strashko Signed-off-by: Linus Walleij --- drivers/gpio/Kconfig | 9 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-tegra186.c | 623 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 633 insertions(+) create mode 100644 drivers/gpio/gpio-tegra186.c (limited to 'drivers/gpio/Kconfig') diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 9feb8e1ff2ff..4ed6a7967784 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -441,6 +441,15 @@ config GPIO_TEGRA help Say yes here to support GPIO pins on NVIDIA Tegra SoCs. +config GPIO_TEGRA186 + tristate "NVIDIA Tegra186 GPIO support" + default ARCH_TEGRA_186_SOC + depends on ARCH_TEGRA_186_SOC || COMPILE_TEST + depends on OF_GPIO + select GPIOLIB_IRQCHIP + help + Say yes here to support GPIO pins on NVIDIA Tegra186 SoCs. + config GPIO_TS4800 tristate "TS-4800 DIO blocks and compatibles" depends on OF_GPIO diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index ac4b7c34a668..120e79c0ebb2 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -114,6 +114,7 @@ obj-$(CONFIG_GPIO_SYSCON) += gpio-syscon.o obj-$(CONFIG_GPIO_TB10X) += gpio-tb10x.o obj-$(CONFIG_GPIO_TC3589X) += gpio-tc3589x.o obj-$(CONFIG_GPIO_TEGRA) += gpio-tegra.o +obj-$(CONFIG_GPIO_TEGRA186) += gpio-tegra186.o obj-$(CONFIG_GPIO_THUNDERX) += gpio-thunderx.o obj-$(CONFIG_GPIO_TIMBERDALE) += gpio-timberdale.o obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.o diff --git a/drivers/gpio/gpio-tegra186.c b/drivers/gpio/gpio-tegra186.c new file mode 100644 index 000000000000..b55b5ca882c7 --- /dev/null +++ b/drivers/gpio/gpio-tegra186.c @@ -0,0 +1,623 @@ +/* + * Copyright (c) 2016-2017 NVIDIA Corporation + * + * Author: Thierry Reding + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#define TEGRA186_GPIO_ENABLE_CONFIG 0x00 +#define TEGRA186_GPIO_ENABLE_CONFIG_ENABLE BIT(0) +#define TEGRA186_GPIO_ENABLE_CONFIG_OUT BIT(1) +#define TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_NONE (0x0 << 2) +#define TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_LEVEL (0x1 << 2) +#define TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_SINGLE_EDGE (0x2 << 2) +#define TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_DOUBLE_EDGE (0x3 << 2) +#define TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_MASK (0x3 << 2) +#define TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL BIT(4) +#define TEGRA186_GPIO_ENABLE_CONFIG_INTERRUPT BIT(6) + +#define TEGRA186_GPIO_DEBOUNCE_CONTROL 0x04 +#define TEGRA186_GPIO_DEBOUNCE_CONTROL_THRESHOLD(x) ((x) & 0xff) + +#define TEGRA186_GPIO_INPUT 0x08 +#define TEGRA186_GPIO_INPUT_HIGH BIT(0) + +#define TEGRA186_GPIO_OUTPUT_CONTROL 0x0c +#define TEGRA186_GPIO_OUTPUT_CONTROL_FLOATED BIT(0) + +#define TEGRA186_GPIO_OUTPUT_VALUE 0x10 +#define TEGRA186_GPIO_OUTPUT_VALUE_HIGH BIT(0) + +#define TEGRA186_GPIO_INTERRUPT_CLEAR 0x14 + +#define TEGRA186_GPIO_INTERRUPT_STATUS(x) (0x100 + (x) * 4) + +struct tegra_gpio_port { + const char *name; + unsigned int offset; + unsigned int pins; + unsigned int irq; +}; + +struct tegra_gpio_soc { + const struct tegra_gpio_port *ports; + unsigned int num_ports; + const char *name; +}; + +struct tegra_gpio { + struct gpio_chip gpio; + struct irq_chip intc; + unsigned int num_irq; + unsigned int *irq; + + const struct tegra_gpio_soc *soc; + + void __iomem *base; +}; + +static const struct tegra_gpio_port * +tegra186_gpio_get_port(struct tegra_gpio *gpio, unsigned int *pin) +{ + unsigned int start = 0, i; + + for (i = 0; i < gpio->soc->num_ports; i++) { + const struct tegra_gpio_port *port = &gpio->soc->ports[i]; + + if (*pin >= start && *pin < start + port->pins) { + *pin -= start; + return port; + } + + start += port->pins; + } + + return NULL; +} + +static void __iomem *tegra186_gpio_get_base(struct tegra_gpio *gpio, + unsigned int pin) +{ + const struct tegra_gpio_port *port; + + port = tegra186_gpio_get_port(gpio, &pin); + if (!port) + return NULL; + + return gpio->base + port->offset + pin * 0x20; +} + +static int tegra186_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + struct tegra_gpio *gpio = gpiochip_get_data(chip); + void __iomem *base; + u32 value; + + base = tegra186_gpio_get_base(gpio, offset); + if (WARN_ON(base == NULL)) + return -ENODEV; + + value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG); + if (value & TEGRA186_GPIO_ENABLE_CONFIG_OUT) + return 0; + + return 1; +} + +static int tegra186_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct tegra_gpio *gpio = gpiochip_get_data(chip); + void __iomem *base; + u32 value; + + base = tegra186_gpio_get_base(gpio, offset); + if (WARN_ON(base == NULL)) + return -ENODEV; + + value = readl(base + TEGRA186_GPIO_OUTPUT_CONTROL); + value |= TEGRA186_GPIO_OUTPUT_CONTROL_FLOATED; + writel(value, base + TEGRA186_GPIO_OUTPUT_CONTROL); + + value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG); + value |= TEGRA186_GPIO_ENABLE_CONFIG_ENABLE; + value &= ~TEGRA186_GPIO_ENABLE_CONFIG_OUT; + writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG); + + return 0; +} + +static int tegra186_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int level) +{ + struct tegra_gpio *gpio = gpiochip_get_data(chip); + void __iomem *base; + u32 value; + + /* configure output level first */ + chip->set(chip, offset, level); + + base = tegra186_gpio_get_base(gpio, offset); + if (WARN_ON(base == NULL)) + return -EINVAL; + + /* set the direction */ + value = readl(base + TEGRA186_GPIO_OUTPUT_CONTROL); + value &= ~TEGRA186_GPIO_OUTPUT_CONTROL_FLOATED; + writel(value, base + TEGRA186_GPIO_OUTPUT_CONTROL); + + value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG); + value |= TEGRA186_GPIO_ENABLE_CONFIG_ENABLE; + value |= TEGRA186_GPIO_ENABLE_CONFIG_OUT; + writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG); + + return 0; +} + +static int tegra186_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct tegra_gpio *gpio = gpiochip_get_data(chip); + void __iomem *base; + u32 value; + + base = tegra186_gpio_get_base(gpio, offset); + if (WARN_ON(base == NULL)) + return -ENODEV; + + value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG); + if (value & TEGRA186_GPIO_ENABLE_CONFIG_OUT) + value = readl(base + TEGRA186_GPIO_OUTPUT_VALUE); + else + value = readl(base + TEGRA186_GPIO_INPUT); + + return value & BIT(0); +} + +static void tegra186_gpio_set(struct gpio_chip *chip, unsigned int offset, + int level) +{ + struct tegra_gpio *gpio = gpiochip_get_data(chip); + void __iomem *base; + u32 value; + + base = tegra186_gpio_get_base(gpio, offset); + if (WARN_ON(base == NULL)) + return; + + value = readl(base + TEGRA186_GPIO_OUTPUT_VALUE); + if (level == 0) + value &= ~TEGRA186_GPIO_OUTPUT_VALUE_HIGH; + else + value |= TEGRA186_GPIO_OUTPUT_VALUE_HIGH; + + writel(value, base + TEGRA186_GPIO_OUTPUT_VALUE); +} + +static int tegra186_gpio_of_xlate(struct gpio_chip *chip, + const struct of_phandle_args *spec, + u32 *flags) +{ + struct tegra_gpio *gpio = gpiochip_get_data(chip); + unsigned int port, pin, i, offset = 0; + + if (WARN_ON(chip->of_gpio_n_cells < 2)) + return -EINVAL; + + if (WARN_ON(spec->args_count < chip->of_gpio_n_cells)) + return -EINVAL; + + port = spec->args[0] / 8; + pin = spec->args[0] % 8; + + if (port >= gpio->soc->num_ports) { + dev_err(chip->parent, "invalid port number: %u\n", port); + return -EINVAL; + } + + for (i = 0; i < port; i++) + offset += gpio->soc->ports[i].pins; + + if (flags) + *flags = spec->args[1]; + + return offset + pin; +} + +static void tegra186_irq_ack(struct irq_data *data) +{ + struct tegra_gpio *gpio = irq_data_get_irq_chip_data(data); + void __iomem *base; + + base = tegra186_gpio_get_base(gpio, data->hwirq); + if (WARN_ON(base == NULL)) + return; + + writel(1, base + TEGRA186_GPIO_INTERRUPT_CLEAR); +} + +static void tegra186_irq_mask(struct irq_data *data) +{ + struct tegra_gpio *gpio = irq_data_get_irq_chip_data(data); + void __iomem *base; + u32 value; + + base = tegra186_gpio_get_base(gpio, data->hwirq); + if (WARN_ON(base == NULL)) + return; + + value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG); + value &= ~TEGRA186_GPIO_ENABLE_CONFIG_INTERRUPT; + writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG); +} + +static void tegra186_irq_unmask(struct irq_data *data) +{ + struct tegra_gpio *gpio = irq_data_get_irq_chip_data(data); + void __iomem *base; + u32 value; + + base = tegra186_gpio_get_base(gpio, data->hwirq); + if (WARN_ON(base == NULL)) + return; + + value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG); + value |= TEGRA186_GPIO_ENABLE_CONFIG_INTERRUPT; + writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG); +} + +static int tegra186_irq_set_type(struct irq_data *data, unsigned int flow) +{ + struct tegra_gpio *gpio = irq_data_get_irq_chip_data(data); + void __iomem *base; + u32 value; + + base = tegra186_gpio_get_base(gpio, data->hwirq); + if (WARN_ON(base == NULL)) + return -ENODEV; + + value = readl(base + TEGRA186_GPIO_ENABLE_CONFIG); + value &= ~TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_MASK; + value &= ~TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL; + + switch (flow & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_NONE: + break; + + case IRQ_TYPE_EDGE_RISING: + value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_SINGLE_EDGE; + value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL; + break; + + case IRQ_TYPE_EDGE_FALLING: + value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_SINGLE_EDGE; + break; + + case IRQ_TYPE_EDGE_BOTH: + value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_DOUBLE_EDGE; + break; + + case IRQ_TYPE_LEVEL_HIGH: + value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_LEVEL; + value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_LEVEL; + break; + + case IRQ_TYPE_LEVEL_LOW: + value |= TEGRA186_GPIO_ENABLE_CONFIG_TRIGGER_TYPE_LEVEL; + break; + + default: + return -EINVAL; + } + + writel(value, base + TEGRA186_GPIO_ENABLE_CONFIG); + + if ((flow & IRQ_TYPE_EDGE_BOTH) == 0) + irq_set_handler_locked(data, handle_level_irq); + else + irq_set_handler_locked(data, handle_edge_irq); + + return 0; +} + +static void tegra186_gpio_irq(struct irq_desc *desc) +{ + struct tegra_gpio *gpio = irq_desc_get_handler_data(desc); + struct irq_domain *domain = gpio->gpio.irq.domain; + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned int parent = irq_desc_get_irq(desc); + unsigned int i, offset = 0; + + chained_irq_enter(chip, desc); + + for (i = 0; i < gpio->soc->num_ports; i++) { + const struct tegra_gpio_port *port = &gpio->soc->ports[i]; + void __iomem *base = gpio->base + port->offset; + unsigned int pin, irq; + unsigned long value; + + /* skip ports that are not associated with this controller */ + if (parent != gpio->irq[port->irq]) + goto skip; + + value = readl(base + TEGRA186_GPIO_INTERRUPT_STATUS(1)); + + for_each_set_bit(pin, &value, port->pins) { + irq = irq_find_mapping(domain, offset + pin); + if (WARN_ON(irq == 0)) + continue; + + generic_handle_irq(irq); + } + +skip: + offset += port->pins; + } + + chained_irq_exit(chip, desc); +} + +static int tegra186_gpio_irq_domain_xlate(struct irq_domain *domain, + struct device_node *np, + const u32 *spec, unsigned int size, + unsigned long *hwirq, + unsigned int *type) +{ + struct tegra_gpio *gpio = gpiochip_get_data(domain->host_data); + unsigned int port, pin, i, offset = 0; + + if (size < 2) + return -EINVAL; + + port = spec[0] / 8; + pin = spec[0] % 8; + + if (port >= gpio->soc->num_ports) { + dev_err(gpio->gpio.parent, "invalid port number: %u\n", port); + return -EINVAL; + } + + for (i = 0; i < port; i++) + offset += gpio->soc->ports[i].pins; + + *type = spec[1] & IRQ_TYPE_SENSE_MASK; + *hwirq = offset + pin; + + return 0; +} + +static const struct irq_domain_ops tegra186_gpio_irq_domain_ops = { + .map = gpiochip_irq_map, + .unmap = gpiochip_irq_unmap, + .xlate = tegra186_gpio_irq_domain_xlate, +}; + +static struct lock_class_key tegra186_gpio_lock_class; + +static int tegra186_gpio_probe(struct platform_device *pdev) +{ + unsigned int i, j, offset; + struct gpio_irq_chip *irq; + struct tegra_gpio *gpio; + struct resource *res; + char **names; + int err; + + gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); + if (!gpio) + return -ENOMEM; + + gpio->soc = of_device_get_match_data(&pdev->dev); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gpio"); + gpio->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(gpio->base)) + return PTR_ERR(gpio->base); + + err = platform_irq_count(pdev); + if (err < 0) + return err; + + gpio->num_irq = err; + + gpio->irq = devm_kcalloc(&pdev->dev, gpio->num_irq, sizeof(*gpio->irq), + GFP_KERNEL); + if (!gpio->irq) + return -ENOMEM; + + for (i = 0; i < gpio->num_irq; i++) { + err = platform_get_irq(pdev, i); + if (err < 0) + return err; + + gpio->irq[i] = err; + } + + gpio->gpio.label = gpio->soc->name; + gpio->gpio.parent = &pdev->dev; + + gpio->gpio.get_direction = tegra186_gpio_get_direction; + gpio->gpio.direction_input = tegra186_gpio_direction_input; + gpio->gpio.direction_output = tegra186_gpio_direction_output; + gpio->gpio.get = tegra186_gpio_get, + gpio->gpio.set = tegra186_gpio_set; + + gpio->gpio.base = -1; + + for (i = 0; i < gpio->soc->num_ports; i++) + gpio->gpio.ngpio += gpio->soc->ports[i].pins; + + names = devm_kcalloc(gpio->gpio.parent, gpio->gpio.ngpio, + sizeof(*names), GFP_KERNEL); + if (!names) + return -ENOMEM; + + for (i = 0, offset = 0; i < gpio->soc->num_ports; i++) { + const struct tegra_gpio_port *port = &gpio->soc->ports[i]; + char *name; + + for (j = 0; j < port->pins; j++) { + name = devm_kasprintf(gpio->gpio.parent, GFP_KERNEL, + "P%s.%02x", port->name, j); + if (!name) + return -ENOMEM; + + names[offset + j] = name; + } + + offset += port->pins; + } + + gpio->gpio.names = (const char * const *)names; + + gpio->gpio.of_node = pdev->dev.of_node; + gpio->gpio.of_gpio_n_cells = 2; + gpio->gpio.of_xlate = tegra186_gpio_of_xlate; + + gpio->intc.name = pdev->dev.of_node->name; + gpio->intc.irq_ack = tegra186_irq_ack; + gpio->intc.irq_mask = tegra186_irq_mask; + gpio->intc.irq_unmask = tegra186_irq_unmask; + gpio->intc.irq_set_type = tegra186_irq_set_type; + + irq = &gpio->gpio.irq; + irq->chip = &gpio->intc; + irq->domain_ops = &tegra186_gpio_irq_domain_ops; + irq->handler = handle_simple_irq; + irq->lock_key = &tegra186_gpio_lock_class; + irq->default_type = IRQ_TYPE_NONE; + irq->parent_handler = tegra186_gpio_irq; + irq->parent_handler_data = gpio; + irq->num_parents = gpio->num_irq; + irq->parents = gpio->irq; + + irq->map = devm_kcalloc(&pdev->dev, gpio->gpio.ngpio, + sizeof(*irq->map), GFP_KERNEL); + if (!irq->map) + return -ENOMEM; + + for (i = 0, offset = 0; i < gpio->soc->num_ports; i++) { + const struct tegra_gpio_port *port = &gpio->soc->ports[i]; + + for (j = 0; j < port->pins; j++) + irq->map[offset + j] = irq->parents[port->irq]; + + offset += port->pins; + } + + platform_set_drvdata(pdev, gpio); + + err = devm_gpiochip_add_data(&pdev->dev, &gpio->gpio, gpio); + if (err < 0) + return err; + + return 0; +} + +static int tegra186_gpio_remove(struct platform_device *pdev) +{ + return 0; +} + +#define TEGRA_MAIN_GPIO_PORT(port, base, count, controller) \ + [TEGRA_MAIN_GPIO_PORT_##port] = { \ + .name = #port, \ + .offset = base, \ + .pins = count, \ + .irq = controller, \ + } + +static const struct tegra_gpio_port tegra186_main_ports[] = { + TEGRA_MAIN_GPIO_PORT( A, 0x2000, 7, 2), + TEGRA_MAIN_GPIO_PORT( B, 0x3000, 7, 3), + TEGRA_MAIN_GPIO_PORT( C, 0x3200, 7, 3), + TEGRA_MAIN_GPIO_PORT( D, 0x3400, 6, 3), + TEGRA_MAIN_GPIO_PORT( E, 0x2200, 8, 2), + TEGRA_MAIN_GPIO_PORT( F, 0x2400, 6, 2), + TEGRA_MAIN_GPIO_PORT( G, 0x4200, 6, 4), + TEGRA_MAIN_GPIO_PORT( H, 0x1000, 7, 1), + TEGRA_MAIN_GPIO_PORT( I, 0x0800, 8, 0), + TEGRA_MAIN_GPIO_PORT( J, 0x5000, 8, 5), + TEGRA_MAIN_GPIO_PORT( K, 0x5200, 1, 5), + TEGRA_MAIN_GPIO_PORT( L, 0x1200, 8, 1), + TEGRA_MAIN_GPIO_PORT( M, 0x5600, 6, 5), + TEGRA_MAIN_GPIO_PORT( N, 0x0000, 7, 0), + TEGRA_MAIN_GPIO_PORT( O, 0x0200, 4, 0), + TEGRA_MAIN_GPIO_PORT( P, 0x4000, 7, 4), + TEGRA_MAIN_GPIO_PORT( Q, 0x0400, 6, 0), + TEGRA_MAIN_GPIO_PORT( R, 0x0a00, 6, 0), + TEGRA_MAIN_GPIO_PORT( T, 0x0600, 4, 0), + TEGRA_MAIN_GPIO_PORT( X, 0x1400, 8, 1), + TEGRA_MAIN_GPIO_PORT( Y, 0x1600, 7, 1), + TEGRA_MAIN_GPIO_PORT(BB, 0x2600, 2, 2), + TEGRA_MAIN_GPIO_PORT(CC, 0x5400, 4, 5), +}; + +static const struct tegra_gpio_soc tegra186_main_soc = { + .num_ports = ARRAY_SIZE(tegra186_main_ports), + .ports = tegra186_main_ports, + .name = "tegra186-gpio", +}; + +#define TEGRA_AON_GPIO_PORT(port, base, count, controller) \ + [TEGRA_AON_GPIO_PORT_##port] = { \ + .name = #port, \ + .offset = base, \ + .pins = count, \ + .irq = controller, \ + } + +static const struct tegra_gpio_port tegra186_aon_ports[] = { + TEGRA_AON_GPIO_PORT( S, 0x0200, 5, 0), + TEGRA_AON_GPIO_PORT( U, 0x0400, 6, 0), + TEGRA_AON_GPIO_PORT( V, 0x0800, 8, 0), + TEGRA_AON_GPIO_PORT( W, 0x0a00, 8, 0), + TEGRA_AON_GPIO_PORT( Z, 0x0e00, 4, 0), + TEGRA_AON_GPIO_PORT(AA, 0x0c00, 8, 0), + TEGRA_AON_GPIO_PORT(EE, 0x0600, 3, 0), + TEGRA_AON_GPIO_PORT(FF, 0x0000, 5, 0), +}; + +static const struct tegra_gpio_soc tegra186_aon_soc = { + .num_ports = ARRAY_SIZE(tegra186_aon_ports), + .ports = tegra186_aon_ports, + .name = "tegra186-gpio-aon", +}; + +static const struct of_device_id tegra186_gpio_of_match[] = { + { + .compatible = "nvidia,tegra186-gpio", + .data = &tegra186_main_soc + }, { + .compatible = "nvidia,tegra186-gpio-aon", + .data = &tegra186_aon_soc + }, { + /* sentinel */ + } +}; + +static struct platform_driver tegra186_gpio_driver = { + .driver = { + .name = "tegra186-gpio", + .of_match_table = tegra186_gpio_of_match, + }, + .probe = tegra186_gpio_probe, + .remove = tegra186_gpio_remove, +}; +module_platform_driver(tegra186_gpio_driver); + +MODULE_DESCRIPTION("NVIDIA Tegra186 GPIO controller driver"); +MODULE_AUTHOR("Thierry Reding "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3