From b01ecceaf2c0c4b3f2d24aa0adcf096ab1648253 Mon Sep 17 00:00:00 2001 From: Yash Shah Date: Tue, 10 Dec 2019 16:41:09 +0530 Subject: genirq: Introduce irq_domain_translate_onecell Add a new function irq_domain_translate_onecell() that is to be used as the translate function in struct irq_domain_ops. Signed-off-by: Yash Shah Signed-off-by: Marc Zyngier Link: https://lore.kernel.org/r/1575976274-13487-2-git-send-email-yash.shah@sifive.com --- include/linux/irqdomain.h | 5 +++++ kernel/irq/irqdomain.c | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 3c340dbc5a1f..698749f42ced 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -427,6 +427,11 @@ int irq_domain_translate_twocell(struct irq_domain *d, unsigned long *out_hwirq, unsigned int *out_type); +int irq_domain_translate_onecell(struct irq_domain *d, + struct irq_fwspec *fwspec, + unsigned long *out_hwirq, + unsigned int *out_type); + /* IPI functions */ int irq_reserve_ipi(struct irq_domain *domain, const struct cpumask *dest); int irq_destroy_ipi(unsigned int irq, const struct cpumask *dest); diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index dd822fd8a7d5..7a8808c8dfec 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -986,6 +986,23 @@ const struct irq_domain_ops irq_domain_simple_ops = { }; EXPORT_SYMBOL_GPL(irq_domain_simple_ops); +/** + * irq_domain_translate_onecell() - Generic translate for direct one cell + * bindings + */ +int irq_domain_translate_onecell(struct irq_domain *d, + struct irq_fwspec *fwspec, + unsigned long *out_hwirq, + unsigned int *out_type) +{ + if (WARN_ON(fwspec->param_count < 1)) + return -EINVAL; + *out_hwirq = fwspec->param[0]; + *out_type = IRQ_TYPE_NONE; + return 0; +} +EXPORT_SYMBOL_GPL(irq_domain_translate_onecell); + /** * irq_domain_translate_twocell() - Generic translate for direct two cell * bindings -- cgit v1.2.3 From 459c3bc8c497d6b665c2efd15ee7df0f4e19452a Mon Sep 17 00:00:00 2001 From: Yash Shah Date: Tue, 10 Dec 2019 16:41:10 +0530 Subject: irqchip/nvic: Use irq_domain_translate_onecell instead of custom func Make use of newly introduced irq_domain_translate_onecell() instead of custom made function. Signed-off-by: Yash Shah Signed-off-by: Marc Zyngier Link: https://lore.kernel.org/r/1575976274-13487-3-git-send-email-yash.shah@sifive.com --- drivers/irqchip/irq-nvic.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/drivers/irqchip/irq-nvic.c b/drivers/irqchip/irq-nvic.c index a166d30deea2..f747e2209ea9 100644 --- a/drivers/irqchip/irq-nvic.c +++ b/drivers/irqchip/irq-nvic.c @@ -45,17 +45,6 @@ nvic_handle_irq(irq_hw_number_t hwirq, struct pt_regs *regs) handle_IRQ(irq, regs); } -static int nvic_irq_domain_translate(struct irq_domain *d, - struct irq_fwspec *fwspec, - unsigned long *hwirq, unsigned int *type) -{ - if (WARN_ON(fwspec->param_count < 1)) - return -EINVAL; - *hwirq = fwspec->param[0]; - *type = IRQ_TYPE_NONE; - return 0; -} - static int nvic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs, void *arg) { @@ -64,7 +53,7 @@ static int nvic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, unsigned int type = IRQ_TYPE_NONE; struct irq_fwspec *fwspec = arg; - ret = nvic_irq_domain_translate(domain, fwspec, &hwirq, &type); + ret = irq_domain_translate_onecell(domain, fwspec, &hwirq, &type); if (ret) return ret; @@ -75,7 +64,7 @@ static int nvic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, } static const struct irq_domain_ops nvic_irq_domain_ops = { - .translate = nvic_irq_domain_translate, + .translate = irq_domain_translate_onecell, .alloc = nvic_irq_domain_alloc, .free = irq_domain_free_irqs_top, }; -- cgit v1.2.3 From 466008f984358231f4608a0a4171b0e6e8251de8 Mon Sep 17 00:00:00 2001 From: Yash Shah Date: Tue, 10 Dec 2019 16:41:11 +0530 Subject: irqchip/sifive-plic: Support irq domain hierarchy Add support for hierarchical irq domains. This is needed as pre-requisite for gpio-sifive driver. Signed-off-by: Yash Shah Signed-off-by: Marc Zyngier Link: https://lore.kernel.org/r/1575976274-13487-4-git-send-email-yash.shah@sifive.com --- drivers/irqchip/Kconfig | 1 + drivers/irqchip/irq-sifive-plic.c | 30 ++++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 697e6a8ccaae..bb89dfc82c6e 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -490,6 +490,7 @@ config TI_SCI_INTA_IRQCHIP config SIFIVE_PLIC bool "SiFive Platform-Level Interrupt Controller" depends on RISCV + select IRQ_DOMAIN_HIERARCHY help This enables support for the PLIC chip found in SiFive (and potentially other) RISC-V systems. The PLIC controls devices diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c index 8df547d2d935..0332f6092ed8 100644 --- a/drivers/irqchip/irq-sifive-plic.c +++ b/drivers/irqchip/irq-sifive-plic.c @@ -154,15 +154,37 @@ static struct irq_chip plic_chip = { static int plic_irqdomain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hwirq) { - irq_set_chip_and_handler(irq, &plic_chip, handle_fasteoi_irq); - irq_set_chip_data(irq, NULL); + irq_domain_set_info(d, irq, hwirq, &plic_chip, d->host_data, + handle_fasteoi_irq, NULL, NULL); irq_set_noprobe(irq); return 0; } +static int plic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + int i, ret; + irq_hw_number_t hwirq; + unsigned int type; + struct irq_fwspec *fwspec = arg; + + ret = irq_domain_translate_onecell(domain, fwspec, &hwirq, &type); + if (ret) + return ret; + + for (i = 0; i < nr_irqs; i++) { + ret = plic_irqdomain_map(domain, virq + i, hwirq + i); + if (ret) + return ret; + } + + return 0; +} + static const struct irq_domain_ops plic_irqdomain_ops = { - .map = plic_irqdomain_map, - .xlate = irq_domain_xlate_onecell, + .translate = irq_domain_translate_onecell, + .alloc = plic_irq_domain_alloc, + .free = irq_domain_free_irqs_top, }; static struct irq_domain *plic_irqdomain; -- cgit v1.2.3 From 7875f8242494f8e4c8a75f2aeab4a6fb742599bd Mon Sep 17 00:00:00 2001 From: Yash Shah Date: Tue, 10 Dec 2019 16:41:12 +0530 Subject: gpio/sifive: Add DT documentation for SiFive GPIO DT json-schema for GPIO controller added. Signed-off-by: Wesley W. Terpstra [Atish: Compatible string update] Signed-off-by: Atish Patra Signed-off-by: Yash Shah Signed-off-by: Marc Zyngier Reviewed-by: Linus Walleij Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/1575976274-13487-5-git-send-email-yash.shah@sifive.com --- .../devicetree/bindings/gpio/sifive,gpio.yaml | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/sifive,gpio.yaml diff --git a/Documentation/devicetree/bindings/gpio/sifive,gpio.yaml b/Documentation/devicetree/bindings/gpio/sifive,gpio.yaml new file mode 100644 index 000000000000..418e8381e07c --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/sifive,gpio.yaml @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/sifive,gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SiFive GPIO controller + +maintainers: + - Yash Shah + - Paul Walmsley + +properties: + compatible: + items: + - const: sifive,fu540-c000-gpio + - const: sifive,gpio0 + + reg: + maxItems: 1 + + interrupts: + description: + interrupt mapping one per GPIO. Maximum 16 GPIOs. + minItems: 1 + maxItems: 16 + + interrupt-controller: true + + "#interrupt-cells": + const: 2 + + clocks: + maxItems: 1 + + "#gpio-cells": + const: 2 + + gpio-controller: true + +required: + - compatible + - reg + - interrupts + - interrupt-controller + - "#interrupt-cells" + - clocks + - "#gpio-cells" + - gpio-controller + +additionalProperties: false + +examples: + - | + #include + gpio@10060000 { + compatible = "sifive,fu540-c000-gpio", "sifive,gpio0"; + interrupt-parent = <&plic>; + interrupts = <7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22>; + reg = <0x0 0x10060000 0x0 0x1000>; + clocks = <&tlclk PRCI_CLK_TLCLK>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + }; + +... -- cgit v1.2.3 From 96868dce644d002383f6d5eb575a6ce3c8779f39 Mon Sep 17 00:00:00 2001 From: Yash Shah Date: Tue, 10 Dec 2019 16:41:13 +0530 Subject: gpio/sifive: Add GPIO driver for SiFive SoCs Adds the GPIO driver for SiFive RISC-V SoCs. Signed-off-by: Wesley W. Terpstra [Atish: Various fixes and code cleanup] Signed-off-by: Atish Patra Signed-off-by: Yash Shah Signed-off-by: Marc Zyngier Reviewed-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/1575976274-13487-6-git-send-email-yash.shah@sifive.com --- drivers/gpio/Kconfig | 9 ++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-sifive.c | 252 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 drivers/gpio/gpio-sifive.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 6ab25fe1c423..809dd54a2e82 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -479,6 +479,15 @@ config GPIO_SAMA5D2_PIOBU The difference from regular GPIOs is that they maintain their value during backup/self-refresh. +config GPIO_SIFIVE + bool "SiFive GPIO support" + depends on OF_GPIO && IRQ_DOMAIN_HIERARCHY + select GPIO_GENERIC + select GPIOLIB_IRQCHIP + select REGMAP_MMIO + help + Say yes here to support the GPIO device on SiFive SoCs. + config GPIO_SIOX tristate "SIOX GPIO support" depends on SIOX diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 34eb8b2b12dd..11eeeebbde0d 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -124,6 +124,7 @@ obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o obj-$(CONFIG_GPIO_SCH) += gpio-sch.o +obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o obj-$(CONFIG_GPIO_SIOX) += gpio-siox.o obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o obj-$(CONFIG_GPIO_SPEAR_SPICS) += gpio-spear-spics.o diff --git a/drivers/gpio/gpio-sifive.c b/drivers/gpio/gpio-sifive.c new file mode 100644 index 000000000000..147a1bd04515 --- /dev/null +++ b/drivers/gpio/gpio-sifive.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 SiFive + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIFIVE_GPIO_INPUT_VAL 0x00 +#define SIFIVE_GPIO_INPUT_EN 0x04 +#define SIFIVE_GPIO_OUTPUT_EN 0x08 +#define SIFIVE_GPIO_OUTPUT_VAL 0x0C +#define SIFIVE_GPIO_RISE_IE 0x18 +#define SIFIVE_GPIO_RISE_IP 0x1C +#define SIFIVE_GPIO_FALL_IE 0x20 +#define SIFIVE_GPIO_FALL_IP 0x24 +#define SIFIVE_GPIO_HIGH_IE 0x28 +#define SIFIVE_GPIO_HIGH_IP 0x2C +#define SIFIVE_GPIO_LOW_IE 0x30 +#define SIFIVE_GPIO_LOW_IP 0x34 +#define SIFIVE_GPIO_OUTPUT_XOR 0x40 + +#define SIFIVE_GPIO_MAX 32 +#define SIFIVE_GPIO_IRQ_OFFSET 7 + +struct sifive_gpio { + void __iomem *base; + struct gpio_chip gc; + struct regmap *regs; + u32 irq_state; + unsigned int trigger[SIFIVE_GPIO_MAX]; + unsigned int irq_parent[SIFIVE_GPIO_MAX]; +}; + +static void sifive_gpio_set_ie(struct sifive_gpio *chip, unsigned int offset) +{ + unsigned long flags; + unsigned int trigger; + + spin_lock_irqsave(&chip->gc.bgpio_lock, flags); + trigger = (chip->irq_state & BIT(offset)) ? chip->trigger[offset] : 0; + regmap_update_bits(chip->regs, SIFIVE_GPIO_RISE_IE, BIT(offset), + (trigger & IRQ_TYPE_EDGE_RISING) ? BIT(offset) : 0); + regmap_update_bits(chip->regs, SIFIVE_GPIO_FALL_IE, BIT(offset), + (trigger & IRQ_TYPE_EDGE_FALLING) ? BIT(offset) : 0); + regmap_update_bits(chip->regs, SIFIVE_GPIO_HIGH_IE, BIT(offset), + (trigger & IRQ_TYPE_LEVEL_HIGH) ? BIT(offset) : 0); + regmap_update_bits(chip->regs, SIFIVE_GPIO_LOW_IE, BIT(offset), + (trigger & IRQ_TYPE_LEVEL_LOW) ? BIT(offset) : 0); + spin_unlock_irqrestore(&chip->gc.bgpio_lock, flags); +} + +static int sifive_gpio_irq_set_type(struct irq_data *d, unsigned int trigger) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct sifive_gpio *chip = gpiochip_get_data(gc); + int offset = irqd_to_hwirq(d); + + if (offset < 0 || offset >= gc->ngpio) + return -EINVAL; + + chip->trigger[offset] = trigger; + sifive_gpio_set_ie(chip, offset); + return 0; +} + +static void sifive_gpio_irq_enable(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct sifive_gpio *chip = gpiochip_get_data(gc); + int offset = irqd_to_hwirq(d) % SIFIVE_GPIO_MAX; + u32 bit = BIT(offset); + unsigned long flags; + + irq_chip_enable_parent(d); + + /* Switch to input */ + gc->direction_input(gc, offset); + + spin_lock_irqsave(&gc->bgpio_lock, flags); + /* Clear any sticky pending interrupts */ + regmap_write(chip->regs, SIFIVE_GPIO_RISE_IP, bit); + regmap_write(chip->regs, SIFIVE_GPIO_FALL_IP, bit); + regmap_write(chip->regs, SIFIVE_GPIO_HIGH_IP, bit); + regmap_write(chip->regs, SIFIVE_GPIO_LOW_IP, bit); + spin_unlock_irqrestore(&gc->bgpio_lock, flags); + + /* Enable interrupts */ + assign_bit(offset, (unsigned long *)&chip->irq_state, 1); + sifive_gpio_set_ie(chip, offset); +} + +static void sifive_gpio_irq_disable(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct sifive_gpio *chip = gpiochip_get_data(gc); + int offset = irqd_to_hwirq(d) % SIFIVE_GPIO_MAX; + + assign_bit(offset, (unsigned long *)&chip->irq_state, 0); + sifive_gpio_set_ie(chip, offset); + irq_chip_disable_parent(d); +} + +static void sifive_gpio_irq_eoi(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + struct sifive_gpio *chip = gpiochip_get_data(gc); + int offset = irqd_to_hwirq(d) % SIFIVE_GPIO_MAX; + u32 bit = BIT(offset); + unsigned long flags; + + spin_lock_irqsave(&gc->bgpio_lock, flags); + /* Clear all pending interrupts */ + regmap_write(chip->regs, SIFIVE_GPIO_RISE_IP, bit); + regmap_write(chip->regs, SIFIVE_GPIO_FALL_IP, bit); + regmap_write(chip->regs, SIFIVE_GPIO_HIGH_IP, bit); + regmap_write(chip->regs, SIFIVE_GPIO_LOW_IP, bit); + spin_unlock_irqrestore(&gc->bgpio_lock, flags); + + irq_chip_eoi_parent(d); +} + +static struct irq_chip sifive_gpio_irqchip = { + .name = "sifive-gpio", + .irq_set_type = sifive_gpio_irq_set_type, + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_enable = sifive_gpio_irq_enable, + .irq_disable = sifive_gpio_irq_disable, + .irq_eoi = sifive_gpio_irq_eoi, +}; + +static int sifive_gpio_child_to_parent_hwirq(struct gpio_chip *gc, + unsigned int child, + unsigned int child_type, + unsigned int *parent, + unsigned int *parent_type) +{ + *parent_type = IRQ_TYPE_NONE; + *parent = child + SIFIVE_GPIO_IRQ_OFFSET; + return 0; +} + +static const struct regmap_config sifive_gpio_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .fast_io = true, + .disable_locking = true, +}; + +static int sifive_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = pdev->dev.of_node; + struct device_node *irq_parent; + struct irq_domain *parent; + struct gpio_irq_chip *girq; + struct sifive_gpio *chip; + int ret, ngpio; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(chip->base)) { + dev_err(dev, "failed to allocate device memory\n"); + return PTR_ERR(chip->base); + } + + chip->regs = devm_regmap_init_mmio(dev, chip->base, + &sifive_gpio_regmap_config); + if (IS_ERR(chip->regs)) + return PTR_ERR(chip->regs); + + ngpio = of_irq_count(node); + if (ngpio >= SIFIVE_GPIO_MAX) { + dev_err(dev, "Too many GPIO interrupts (max=%d)\n", + SIFIVE_GPIO_MAX); + return -ENXIO; + } + + irq_parent = of_irq_find_parent(node); + if (!irq_parent) { + dev_err(dev, "no IRQ parent node\n"); + return -ENODEV; + } + parent = irq_find_host(irq_parent); + if (!parent) { + dev_err(dev, "no IRQ parent domain\n"); + return -ENODEV; + } + + ret = bgpio_init(&chip->gc, dev, 4, + chip->base + SIFIVE_GPIO_INPUT_VAL, + chip->base + SIFIVE_GPIO_OUTPUT_VAL, + NULL, + chip->base + SIFIVE_GPIO_OUTPUT_EN, + chip->base + SIFIVE_GPIO_INPUT_EN, + 0); + if (ret) { + dev_err(dev, "unable to init generic GPIO\n"); + return ret; + } + + /* Disable all GPIO interrupts before enabling parent interrupts */ + regmap_write(chip->regs, SIFIVE_GPIO_RISE_IE, 0); + regmap_write(chip->regs, SIFIVE_GPIO_FALL_IE, 0); + regmap_write(chip->regs, SIFIVE_GPIO_HIGH_IE, 0); + regmap_write(chip->regs, SIFIVE_GPIO_LOW_IE, 0); + chip->irq_state = 0; + + chip->gc.base = -1; + chip->gc.ngpio = ngpio; + chip->gc.label = dev_name(dev); + chip->gc.parent = dev; + chip->gc.owner = THIS_MODULE; + girq = &chip->gc.irq; + girq->chip = &sifive_gpio_irqchip; + girq->fwnode = of_node_to_fwnode(node); + girq->parent_domain = parent; + girq->child_to_parent_hwirq = sifive_gpio_child_to_parent_hwirq; + girq->handler = handle_bad_irq; + girq->default_type = IRQ_TYPE_NONE; + + platform_set_drvdata(pdev, chip); + return gpiochip_add_data(&chip->gc, chip); +} + +static const struct of_device_id sifive_gpio_match[] = { + { .compatible = "sifive,gpio0" }, + { .compatible = "sifive,fu540-c000-gpio" }, + { }, +}; + +static struct platform_driver sifive_gpio_driver = { + .probe = sifive_gpio_probe, + .driver = { + .name = "sifive_gpio", + .of_match_table = of_match_ptr(sifive_gpio_match), + }, +}; +builtin_platform_driver(sifive_gpio_driver) -- cgit v1.2.3 From 5350a237b4525ad12170f16239c9e9c7797df02f Mon Sep 17 00:00:00 2001 From: Eddie James Date: Wed, 15 Jan 2020 15:29:39 -0600 Subject: dt-bindings: interrupt-controller: Add Aspeed SCU interrupt controller Document the Aspeed SCU interrupt controller and add an include file for the interrupts it provides. Signed-off-by: Eddie James Signed-off-by: Marc Zyngier Reviewed-by: Rob Herring Acked-by: Andrew Jeffery Link: https://lore.kernel.org/r/1579123790-6894-2-git-send-email-eajames@linux.ibm.com --- .../interrupt-controller/aspeed,ast2xxx-scu-ic.txt | 23 ++++++++++++++++++++++ MAINTAINERS | 7 +++++++ .../interrupt-controller/aspeed-scu-ic.h | 23 ++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2xxx-scu-ic.txt create mode 100644 include/dt-bindings/interrupt-controller/aspeed-scu-ic.h diff --git a/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2xxx-scu-ic.txt b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2xxx-scu-ic.txt new file mode 100644 index 000000000000..251ed44171db --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2xxx-scu-ic.txt @@ -0,0 +1,23 @@ +Aspeed AST25XX and AST26XX SCU Interrupt Controller + +Required Properties: + - #interrupt-cells : must be 1 + - compatible : must be "aspeed,ast2500-scu-ic", + "aspeed,ast2600-scu-ic0" or + "aspeed,ast2600-scu-ic1" + - interrupts : interrupt from the parent controller + - interrupt-controller : indicates that the controller receives and + fires new interrupts for child busses + +Example: + + syscon@1e6e2000 { + ranges = <0 0x1e6e2000 0x1a8>; + + scu_ic: interrupt-controller@18 { + #interrupt-cells = <1>; + compatible = "aspeed,ast2500-scu-ic"; + interrupts = <21>; + interrupt-controller; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index e09bd92a1e44..f9f6e4060a92 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2692,6 +2692,13 @@ S: Maintained F: drivers/pinctrl/aspeed/ F: Documentation/devicetree/bindings/pinctrl/aspeed,* +ASPEED SCU INTERRUPT CONTROLLER DRIVER +M: Eddie James +L: linux-aspeed@lists.ozlabs.org (moderated for non-subscribers) +S: Maintained +F: Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2xxx-scu-ic.txt +F: include/dt-bindings/interrupt-controller/aspeed-scu-ic.h + ASPEED VIDEO ENGINE DRIVER M: Eddie James L: linux-media@vger.kernel.org diff --git a/include/dt-bindings/interrupt-controller/aspeed-scu-ic.h b/include/dt-bindings/interrupt-controller/aspeed-scu-ic.h new file mode 100644 index 000000000000..f315d5a7f5ee --- /dev/null +++ b/include/dt-bindings/interrupt-controller/aspeed-scu-ic.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ + +#ifndef _DT_BINDINGS_INTERRUPT_CONTROLLER_ASPEED_SCU_IC_H_ +#define _DT_BINDINGS_INTERRUPT_CONTROLLER_ASPEED_SCU_IC_H_ + +#define ASPEED_SCU_IC_VGA_CURSOR_CHANGE 0 +#define ASPEED_SCU_IC_VGA_SCRATCH_REG_CHANGE 1 + +#define ASPEED_AST2500_SCU_IC_PCIE_RESET_LO_TO_HI 2 +#define ASPEED_AST2500_SCU_IC_PCIE_RESET_HI_TO_LO 3 +#define ASPEED_AST2500_SCU_IC_LPC_RESET_LO_TO_HI 4 +#define ASPEED_AST2500_SCU_IC_LPC_RESET_HI_TO_LO 5 +#define ASPEED_AST2500_SCU_IC_ISSUE_MSI 6 + +#define ASPEED_AST2600_SCU_IC0_PCIE_PERST_LO_TO_HI 2 +#define ASPEED_AST2600_SCU_IC0_PCIE_PERST_HI_TO_LO 3 +#define ASPEED_AST2600_SCU_IC0_PCIE_RCRST_LO_TO_HI 4 +#define ASPEED_AST2600_SCU_IC0_PCIE_RCRST_HI_TO_LO 5 + +#define ASPEED_AST2600_SCU_IC1_LPC_RESET_LO_TO_HI 0 +#define ASPEED_AST2600_SCU_IC1_LPC_RESET_HI_TO_LO 1 + +#endif /* _DT_BINDINGS_INTERRUPT_CONTROLLER_ASPEED_SCU_IC_H_ */ -- cgit v1.2.3 From 04f605906ff00c649751519ca73d3058372cdc78 Mon Sep 17 00:00:00 2001 From: Eddie James Date: Wed, 15 Jan 2020 15:29:40 -0600 Subject: irqchip: Add Aspeed SCU interrupt controller The Aspeed SOCs provide some interrupts through the System Control Unit registers. Add an interrupt controller that provides these interrupts to the system. Signed-off-by: Eddie James Signed-off-by: Marc Zyngier Reviewed-by: Andrew Jeffery Link: https://lore.kernel.org/r/1579123790-6894-3-git-send-email-eajames@linux.ibm.com --- MAINTAINERS | 1 + drivers/irqchip/Makefile | 2 +- drivers/irqchip/irq-aspeed-scu-ic.c | 239 ++++++++++++++++++++++++++++++++++++ 3 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 drivers/irqchip/irq-aspeed-scu-ic.c diff --git a/MAINTAINERS b/MAINTAINERS index f9f6e4060a92..ac21e7b779c9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2697,6 +2697,7 @@ M: Eddie James L: linux-aspeed@lists.ozlabs.org (moderated for non-subscribers) S: Maintained F: Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2xxx-scu-ic.txt +F: drivers/irqchip/irq-aspeed-scu-ic.c F: include/dt-bindings/interrupt-controller/aspeed-scu-ic.h ASPEED VIDEO ENGINE DRIVER diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index e806dda690ea..6c9262c673bf 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -87,7 +87,7 @@ obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o obj-$(CONFIG_LS_EXTIRQ) += irq-ls-extirq.o obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o -obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o +obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-aspeed-scu-ic.o obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o obj-$(CONFIG_IRQ_UNIPHIER_AIDET) += irq-uniphier-aidet.o diff --git a/drivers/irqchip/irq-aspeed-scu-ic.c b/drivers/irqchip/irq-aspeed-scu-ic.c new file mode 100644 index 000000000000..c90a3346b985 --- /dev/null +++ b/drivers/irqchip/irq-aspeed-scu-ic.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Aspeed AST24XX, AST25XX, and AST26XX SCU Interrupt Controller + * Copyright 2019 IBM Corporation + * + * Eddie James + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASPEED_SCU_IC_REG 0x018 +#define ASPEED_SCU_IC_SHIFT 0 +#define ASPEED_SCU_IC_ENABLE GENMASK(6, ASPEED_SCU_IC_SHIFT) +#define ASPEED_SCU_IC_NUM_IRQS 7 +#define ASPEED_SCU_IC_STATUS_SHIFT 16 + +#define ASPEED_AST2600_SCU_IC0_REG 0x560 +#define ASPEED_AST2600_SCU_IC0_SHIFT 0 +#define ASPEED_AST2600_SCU_IC0_ENABLE \ + GENMASK(5, ASPEED_AST2600_SCU_IC0_SHIFT) +#define ASPEED_AST2600_SCU_IC0_NUM_IRQS 6 + +#define ASPEED_AST2600_SCU_IC1_REG 0x570 +#define ASPEED_AST2600_SCU_IC1_SHIFT 4 +#define ASPEED_AST2600_SCU_IC1_ENABLE \ + GENMASK(5, ASPEED_AST2600_SCU_IC1_SHIFT) +#define ASPEED_AST2600_SCU_IC1_NUM_IRQS 2 + +struct aspeed_scu_ic { + unsigned long irq_enable; + unsigned long irq_shift; + unsigned int num_irqs; + unsigned int reg; + struct regmap *scu; + struct irq_domain *irq_domain; +}; + +static void aspeed_scu_ic_irq_handler(struct irq_desc *desc) +{ + unsigned int irq; + unsigned int sts; + unsigned long bit; + unsigned long enabled; + unsigned long max; + unsigned long status; + struct aspeed_scu_ic *scu_ic = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned int mask = scu_ic->irq_enable << ASPEED_SCU_IC_STATUS_SHIFT; + + chained_irq_enter(chip, desc); + + /* + * The SCU IC has just one register to control its operation and read + * status. The interrupt enable bits occupy the lower 16 bits of the + * register, while the interrupt status bits occupy the upper 16 bits. + * The status bit for a given interrupt is always 16 bits shifted from + * the enable bit for the same interrupt. + * Therefore, perform the IRQ operations in the enable bit space by + * shifting the status down to get the mapping and then back up to + * clear the bit. + */ + regmap_read(scu_ic->scu, scu_ic->reg, &sts); + enabled = sts & scu_ic->irq_enable; + status = (sts >> ASPEED_SCU_IC_STATUS_SHIFT) & enabled; + + bit = scu_ic->irq_shift; + max = scu_ic->num_irqs + bit; + + for_each_set_bit_from(bit, &status, max) { + irq = irq_find_mapping(scu_ic->irq_domain, + bit - scu_ic->irq_shift); + generic_handle_irq(irq); + + regmap_update_bits(scu_ic->scu, scu_ic->reg, mask, + BIT(bit + ASPEED_SCU_IC_STATUS_SHIFT)); + } + + chained_irq_exit(chip, desc); +} + +static void aspeed_scu_ic_irq_mask(struct irq_data *data) +{ + struct aspeed_scu_ic *scu_ic = irq_data_get_irq_chip_data(data); + unsigned int mask = BIT(data->hwirq + scu_ic->irq_shift) | + (scu_ic->irq_enable << ASPEED_SCU_IC_STATUS_SHIFT); + + /* + * Status bits are cleared by writing 1. In order to prevent the mask + * operation from clearing the status bits, they should be under the + * mask and written with 0. + */ + regmap_update_bits(scu_ic->scu, scu_ic->reg, mask, 0); +} + +static void aspeed_scu_ic_irq_unmask(struct irq_data *data) +{ + struct aspeed_scu_ic *scu_ic = irq_data_get_irq_chip_data(data); + unsigned int bit = BIT(data->hwirq + scu_ic->irq_shift); + unsigned int mask = bit | + (scu_ic->irq_enable << ASPEED_SCU_IC_STATUS_SHIFT); + + /* + * Status bits are cleared by writing 1. In order to prevent the unmask + * operation from clearing the status bits, they should be under the + * mask and written with 0. + */ + regmap_update_bits(scu_ic->scu, scu_ic->reg, mask, bit); +} + +static int aspeed_scu_ic_irq_set_affinity(struct irq_data *data, + const struct cpumask *dest, + bool force) +{ + return -EINVAL; +} + +static struct irq_chip aspeed_scu_ic_chip = { + .name = "aspeed-scu-ic", + .irq_mask = aspeed_scu_ic_irq_mask, + .irq_unmask = aspeed_scu_ic_irq_unmask, + .irq_set_affinity = aspeed_scu_ic_irq_set_affinity, +}; + +static int aspeed_scu_ic_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_and_handler(irq, &aspeed_scu_ic_chip, handle_level_irq); + irq_set_chip_data(irq, domain->host_data); + + return 0; +} + +static const struct irq_domain_ops aspeed_scu_ic_domain_ops = { + .map = aspeed_scu_ic_map, +}; + +static int aspeed_scu_ic_of_init_common(struct aspeed_scu_ic *scu_ic, + struct device_node *node) +{ + int irq; + int rc = 0; + + if (!node->parent) { + rc = -ENODEV; + goto err; + } + + scu_ic->scu = syscon_node_to_regmap(node->parent); + if (IS_ERR(scu_ic->scu)) { + rc = PTR_ERR(scu_ic->scu); + goto err; + } + + irq = irq_of_parse_and_map(node, 0); + if (irq < 0) { + rc = irq; + goto err; + } + + scu_ic->irq_domain = irq_domain_add_linear(node, scu_ic->num_irqs, + &aspeed_scu_ic_domain_ops, + scu_ic); + if (!scu_ic->irq_domain) { + rc = -ENOMEM; + goto err; + } + + irq_set_chained_handler_and_data(irq, aspeed_scu_ic_irq_handler, + scu_ic); + + return 0; + +err: + kfree(scu_ic); + + return rc; +} + +static int __init aspeed_scu_ic_of_init(struct device_node *node, + struct device_node *parent) +{ + struct aspeed_scu_ic *scu_ic = kzalloc(sizeof(*scu_ic), GFP_KERNEL); + + if (!scu_ic) + return -ENOMEM; + + scu_ic->irq_enable = ASPEED_SCU_IC_ENABLE; + scu_ic->irq_shift = ASPEED_SCU_IC_SHIFT; + scu_ic->num_irqs = ASPEED_SCU_IC_NUM_IRQS; + scu_ic->reg = ASPEED_SCU_IC_REG; + + return aspeed_scu_ic_of_init_common(scu_ic, node); +} + +static int __init aspeed_ast2600_scu_ic0_of_init(struct device_node *node, + struct device_node *parent) +{ + struct aspeed_scu_ic *scu_ic = kzalloc(sizeof(*scu_ic), GFP_KERNEL); + + if (!scu_ic) + return -ENOMEM; + + scu_ic->irq_enable = ASPEED_AST2600_SCU_IC0_ENABLE; + scu_ic->irq_shift = ASPEED_AST2600_SCU_IC0_SHIFT; + scu_ic->num_irqs = ASPEED_AST2600_SCU_IC0_NUM_IRQS; + scu_ic->reg = ASPEED_AST2600_SCU_IC0_REG; + + return aspeed_scu_ic_of_init_common(scu_ic, node); +} + +static int __init aspeed_ast2600_scu_ic1_of_init(struct device_node *node, + struct device_node *parent) +{ + struct aspeed_scu_ic *scu_ic = kzalloc(sizeof(*scu_ic), GFP_KERNEL); + + if (!scu_ic) + return -ENOMEM; + + scu_ic->irq_enable = ASPEED_AST2600_SCU_IC1_ENABLE; + scu_ic->irq_shift = ASPEED_AST2600_SCU_IC1_SHIFT; + scu_ic->num_irqs = ASPEED_AST2600_SCU_IC1_NUM_IRQS; + scu_ic->reg = ASPEED_AST2600_SCU_IC1_REG; + + return aspeed_scu_ic_of_init_common(scu_ic, node); +} + +IRQCHIP_DECLARE(ast2400_scu_ic, "aspeed,ast2400-scu-ic", aspeed_scu_ic_of_init); +IRQCHIP_DECLARE(ast2500_scu_ic, "aspeed,ast2500-scu-ic", aspeed_scu_ic_of_init); +IRQCHIP_DECLARE(ast2600_scu_ic0, "aspeed,ast2600-scu-ic0", + aspeed_ast2600_scu_ic0_of_init); +IRQCHIP_DECLARE(ast2600_scu_ic1, "aspeed,ast2600-scu-ic1", + aspeed_ast2600_scu_ic1_of_init); -- cgit v1.2.3 From d6152e6ec9e2171280436f7b31a571509b9287e1 Mon Sep 17 00:00:00 2001 From: John Garry Date: Fri, 17 Jan 2020 01:38:43 +0800 Subject: irqchip/mbigen: Set driver .suppress_bind_attrs to avoid remove problems The following crash can be seen for setting CONFIG_DEBUG_TEST_DRIVER_REMOVE=y for DT FW (which some people still use): Hisilicon MBIGEN-V2 60080000.interrupt-controller: Failed to create mbi-gen irqdomain Hisilicon MBIGEN-V2: probe of 60080000.interrupt-controller failed with error -12 [...] Unable to handle kernel paging request at virtual address 0000000000005008 Mem abort info: ESR = 0x96000004 EC = 0x25: DABT (current EL), IL = 32 bits SET = 0, FnV = 0 EA = 0, S1PTW = 0 Data abort info: ISV = 0, ISS = 0x00000004 CM = 0, WnR = 0 user pgtable: 4k pages, 48-bit VAs, pgdp=0000041fb9990000 [0000000000005008] pgd=0000000000000000 Internal error: Oops: 96000004 [#1] PREEMPT SMP Modules linked in: CPU: 7 PID: 1 Comm: swapper/0 Not tainted 5.5.0-rc6-00002-g3fc42638a506-dirty #1622 Hardware name: Huawei Taishan 2280 /D05, BIOS Hisilicon D05 IT21 Nemo 2.0 RC0 04/18/2018 pstate: 40000085 (nZcv daIf -PAN -UAO) pc : mbigen_set_type+0x38/0x60 lr : __irq_set_trigger+0x6c/0x188 sp : ffff800014b4b400 x29: ffff800014b4b400 x28: 0000000000000007 x27: 0000000000000000 x26: 0000000000000000 x25: ffff041fd83bd0d4 x24: ffff041fd83bd188 x23: 0000000000000000 x22: ffff80001193ce00 x21: 0000000000000004 x20: 0000000000000000 x19: ffff041fd83bd000 x18: ffffffffffffffff x17: 0000000000000000 x16: 0000000000000000 x15: ffff8000119098c8 x14: ffff041fb94ec91c x13: ffff041fb94ec1a1 x12: 0000000000000030 x11: 0101010101010101 x10: 0000000000000040 x9 : 0000000000000000 x8 : ffff041fb98c6680 x7 : ffff800014b4b380 x6 : ffff041fd81636c8 x5 : 0000000000000000 x4 : 000000000000025f x3 : 0000000000005000 x2 : 0000000000005008 x1 : 0000000000000004 x0 : 0000000080000000 Call trace: mbigen_set_type+0x38/0x60 __setup_irq+0x744/0x900 request_threaded_irq+0xe0/0x198 pcie_pme_probe+0x98/0x118 pcie_port_probe_service+0x38/0x78 really_probe+0xa0/0x3e0 driver_probe_device+0x58/0x100 __device_attach_driver+0x90/0xb0 bus_for_each_drv+0x64/0xc8 __device_attach+0xd8/0x138 device_initial_probe+0x10/0x18 bus_probe_device+0x90/0x98 device_add+0x4c4/0x770 device_register+0x1c/0x28 pcie_port_device_register+0x1e4/0x4f0 pcie_portdrv_probe+0x34/0xd8 local_pci_probe+0x3c/0xa0 pci_device_probe+0x128/0x1c0 really_probe+0xa0/0x3e0 driver_probe_device+0x58/0x100 __device_attach_driver+0x90/0xb0 bus_for_each_drv+0x64/0xc8 __device_attach+0xd8/0x138 device_attach+0x10/0x18 pci_bus_add_device+0x4c/0xb8 pci_bus_add_devices+0x38/0x88 pci_host_probe+0x3c/0xc0 pci_host_common_probe+0xf0/0x208 hisi_pcie_almost_ecam_probe+0x24/0x30 platform_drv_probe+0x50/0xa0 really_probe+0xa0/0x3e0 driver_probe_device+0x58/0x100 device_driver_attach+0x6c/0x90 __driver_attach+0x84/0xc8 bus_for_each_dev+0x74/0xc8 driver_attach+0x20/0x28 bus_add_driver+0x148/0x1f0 driver_register+0x60/0x110 __platform_driver_register+0x40/0x48 hisi_pcie_almost_ecam_driver_init+0x1c/0x24 The specific problem here is that the mbigen driver real probe has failed as the mbigen_of_create_domain()->of_platform_device_create() call fails, the reason for that being that we never destroyed the platform device created during the remove test dry run and there is some conflict. Since we generally would never want to unbind this driver, and to save adding a driver tear down path for that, just set the driver .suppress_bind_attrs member to avoid this possibility. Signed-off-by: John Garry Signed-off-by: Marc Zyngier Reviewed-by: Hanjun Guo Link: https://lore.kernel.org/r/1579196323-180137-1-git-send-email-john.garry@huawei.com --- drivers/irqchip/irq-mbigen.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c index 3f09f658e8e2..6b566bba263b 100644 --- a/drivers/irqchip/irq-mbigen.c +++ b/drivers/irqchip/irq-mbigen.c @@ -374,6 +374,7 @@ static struct platform_driver mbigen_platform_driver = { .name = "Hisilicon MBIGEN-V2", .of_match_table = mbigen_of_match, .acpi_match_table = ACPI_PTR(mbigen_acpi_match), + .suppress_bind_attrs = true, }, .probe = mbigen_device_probe, }; -- cgit v1.2.3 From fd6765b4c991bb9e4dd6b384b529166db82e5931 Mon Sep 17 00:00:00 2001 From: Qianggui Song Date: Mon, 16 Dec 2019 20:36:42 +0800 Subject: dt-bindings: interrupt-controller: New binding for Meson-A1 SoCs Update dt-binding document for GPIO interrupt controller of Meson-A1 SoCs Signed-off-by: Qianggui Song Signed-off-by: Marc Zyngier Acked-by: Rob Herring Link: https://lore.kernel.org/r/20191216123645.10099-2-qianggui.song@amlogic.com --- .../devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.txt b/Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.txt index 684bb1cd75ec..23b18b92c558 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.txt +++ b/Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.txt @@ -17,6 +17,7 @@ Required properties: "amlogic,meson-axg-gpio-intc" for AXG SoCs (A113D, A113X) "amlogic,meson-g12a-gpio-intc" for G12A SoCs (S905D2, S905X2, S905Y2) "amlogic,meson-sm1-gpio-intc" for SM1 SoCs (S905D3, S905X3, S905Y3) + "amlogic,meson-a1-gpio-intc" for A1 SoCs (A113L) - reg : Specifies base physical address and size of the registers. - interrupt-controller : Identifies the node as an interrupt controller. - #interrupt-cells : Specifies the number of cells needed to encode an -- cgit v1.2.3 From e2514165f36edac382a95474a73ba2bfa51bc2b2 Mon Sep 17 00:00:00 2001 From: Qianggui Song Date: Mon, 16 Dec 2019 20:36:43 +0800 Subject: irqchip/meson-gpio: Rework meson irqchip driver to support meson-A1 SoCs Since Meson-A1 SoCs register layout of gpio interrupt controller has difference with previous chips, registers to decide irq line and offset of trigger method are all changed, the current driver should be modified. Signed-off-by: Qianggui Song Signed-off-by: Marc Zyngier Link: https://lore.kernel.org/r/20191216123645.10099-3-qianggui.song@amlogic.com --- drivers/irqchip/irq-meson-gpio.c | 95 +++++++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c index 829084b568fa..5a1a59e50bb3 100644 --- a/drivers/irqchip/irq-meson-gpio.c +++ b/drivers/irqchip/irq-meson-gpio.c @@ -30,44 +30,75 @@ * stuck at 0. Bits 8 to 15 are responsive and have the expected * effect. */ -#define REG_EDGE_POL_EDGE(x) BIT(x) -#define REG_EDGE_POL_LOW(x) BIT(16 + (x)) -#define REG_BOTH_EDGE(x) BIT(8 + (x)) -#define REG_EDGE_POL_MASK(x) ( \ - REG_EDGE_POL_EDGE(x) | \ - REG_EDGE_POL_LOW(x) | \ - REG_BOTH_EDGE(x)) +#define REG_EDGE_POL_EDGE(params, x) BIT((params)->edge_single_offset + (x)) +#define REG_EDGE_POL_LOW(params, x) BIT((params)->pol_low_offset + (x)) +#define REG_BOTH_EDGE(params, x) BIT((params)->edge_both_offset + (x)) +#define REG_EDGE_POL_MASK(params, x) ( \ + REG_EDGE_POL_EDGE(params, x) | \ + REG_EDGE_POL_LOW(params, x) | \ + REG_BOTH_EDGE(params, x)) #define REG_PIN_SEL_SHIFT(x) (((x) % 4) * 8) #define REG_FILTER_SEL_SHIFT(x) ((x) * 4) +struct meson_gpio_irq_controller; +static void meson8_gpio_irq_sel_pin(struct meson_gpio_irq_controller *ctl, + unsigned int channel, unsigned long hwirq); +static void meson_gpio_irq_init_dummy(struct meson_gpio_irq_controller *ctl); + +struct irq_ctl_ops { + void (*gpio_irq_sel_pin)(struct meson_gpio_irq_controller *ctl, + unsigned int channel, unsigned long hwirq); + void (*gpio_irq_init)(struct meson_gpio_irq_controller *ctl); +}; + struct meson_gpio_irq_params { unsigned int nr_hwirq; bool support_edge_both; + unsigned int edge_both_offset; + unsigned int edge_single_offset; + unsigned int pol_low_offset; + unsigned int pin_sel_mask; + struct irq_ctl_ops ops; }; +#define INIT_MESON_COMMON(irqs, init, sel) \ + .nr_hwirq = irqs, \ + .ops = { \ + .gpio_irq_init = init, \ + .gpio_irq_sel_pin = sel, \ + }, + +#define INIT_MESON8_COMMON_DATA(irqs) \ + INIT_MESON_COMMON(irqs, meson_gpio_irq_init_dummy, \ + meson8_gpio_irq_sel_pin) \ + .edge_single_offset = 0, \ + .pol_low_offset = 16, \ + .pin_sel_mask = 0xff, \ + static const struct meson_gpio_irq_params meson8_params = { - .nr_hwirq = 134, + INIT_MESON8_COMMON_DATA(134) }; static const struct meson_gpio_irq_params meson8b_params = { - .nr_hwirq = 119, + INIT_MESON8_COMMON_DATA(119) }; static const struct meson_gpio_irq_params gxbb_params = { - .nr_hwirq = 133, + INIT_MESON8_COMMON_DATA(133) }; static const struct meson_gpio_irq_params gxl_params = { - .nr_hwirq = 110, + INIT_MESON8_COMMON_DATA(110) }; static const struct meson_gpio_irq_params axg_params = { - .nr_hwirq = 100, + INIT_MESON8_COMMON_DATA(100) }; static const struct meson_gpio_irq_params sm1_params = { - .nr_hwirq = 100, + INIT_MESON8_COMMON_DATA(100) .support_edge_both = true, + .edge_both_offset = 8, }; static const struct of_device_id meson_irq_gpio_matches[] = { @@ -100,9 +131,22 @@ static void meson_gpio_irq_update_bits(struct meson_gpio_irq_controller *ctl, writel_relaxed(tmp, ctl->base + reg); } -static unsigned int meson_gpio_irq_channel_to_reg(unsigned int channel) +static void meson_gpio_irq_init_dummy(struct meson_gpio_irq_controller *ctl) { - return (channel < 4) ? REG_PIN_03_SEL : REG_PIN_47_SEL; +} + +static void meson8_gpio_irq_sel_pin(struct meson_gpio_irq_controller *ctl, + unsigned int channel, unsigned long hwirq) +{ + unsigned int reg_offset; + unsigned int bit_offset; + + reg_offset = (channel < 4) ? REG_PIN_03_SEL : REG_PIN_47_SEL; + bit_offset = REG_PIN_SEL_SHIFT(channel); + + meson_gpio_irq_update_bits(ctl, reg_offset, + ctl->params->pin_sel_mask << bit_offset, + hwirq << bit_offset); } static int @@ -110,7 +154,7 @@ meson_gpio_irq_request_channel(struct meson_gpio_irq_controller *ctl, unsigned long hwirq, u32 **channel_hwirq) { - unsigned int reg, idx; + unsigned int idx; spin_lock(&ctl->lock); @@ -129,10 +173,7 @@ meson_gpio_irq_request_channel(struct meson_gpio_irq_controller *ctl, * Setup the mux of the channel to route the signal of the pad * to the appropriate input of the GIC */ - reg = meson_gpio_irq_channel_to_reg(idx); - meson_gpio_irq_update_bits(ctl, reg, - 0xff << REG_PIN_SEL_SHIFT(idx), - hwirq << REG_PIN_SEL_SHIFT(idx)); + ctl->params->ops.gpio_irq_sel_pin(ctl, idx, hwirq); /* * Get the hwirq number assigned to this channel through @@ -173,7 +214,9 @@ static int meson_gpio_irq_type_setup(struct meson_gpio_irq_controller *ctl, { u32 val = 0; unsigned int idx; + const struct meson_gpio_irq_params *params; + params = ctl->params; idx = meson_gpio_irq_get_channel_idx(ctl, channel_hwirq); /* @@ -190,22 +233,22 @@ static int meson_gpio_irq_type_setup(struct meson_gpio_irq_controller *ctl, * precedence over the other edge/polarity settings */ if (type == IRQ_TYPE_EDGE_BOTH) { - if (!ctl->params->support_edge_both) + if (!params->support_edge_both) return -EINVAL; - val |= REG_BOTH_EDGE(idx); + val |= REG_BOTH_EDGE(params, idx); } else { if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)) - val |= REG_EDGE_POL_EDGE(idx); + val |= REG_EDGE_POL_EDGE(params, idx); if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING)) - val |= REG_EDGE_POL_LOW(idx); + val |= REG_EDGE_POL_LOW(params, idx); } spin_lock(&ctl->lock); meson_gpio_irq_update_bits(ctl, REG_EDGE_POL, - REG_EDGE_POL_MASK(idx), val); + REG_EDGE_POL_MASK(params, idx), val); spin_unlock(&ctl->lock); @@ -371,6 +414,8 @@ static int __init meson_gpio_irq_parse_dt(struct device_node *node, return ret; } + ctl->params->ops.gpio_irq_init(ctl); + return 0; } -- cgit v1.2.3 From 8f78bd62bdd7a7b0a9906c4827245bf17056f781 Mon Sep 17 00:00:00 2001 From: Qianggui Song Date: Mon, 16 Dec 2019 20:36:44 +0800 Subject: irqchip/meson-gpio: Add support for meson a1 SoCs The meson a1 Socs have some changes compared with previous chips. For A113L, it contains 62 pins and can be spied on: - 62:128 undefined - 61:50 12 pins on bank A - 49:37 13 pins on bank F - 36:20 17 pins on bank X - 19:13 7 pins on bank B - 12:0 13 pins on bank P There are five relative registers for gpio interrupt controller, details are as below: - PADCTRL_GPIO_IRQ_CTRL0 bit[31]: enable/disable the whole irq lines bit[16-23]: both edge trigger bit[8-15]: single edge trigger bit[0-7]: pol trigger - PADCTRL_GPIO_IRQ_CTRL[X] bit[0-6]: 7 bits to choose gpio source for irq line 2*[X] - 2 bit[16-22]: 7 bits to choose gpio source for irq line 2*[X] - 1 where X =1,2,3,4 Signed-off-by: Qianggui Song Signed-off-by: Marc Zyngier Link: https://lore.kernel.org/r/20191216123645.10099-4-qianggui.song@amlogic.com --- drivers/irqchip/irq-meson-gpio.c | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c index 5a1a59e50bb3..ccc7f823911b 100644 --- a/drivers/irqchip/irq-meson-gpio.c +++ b/drivers/irqchip/irq-meson-gpio.c @@ -24,6 +24,9 @@ #define REG_PIN_47_SEL 0x08 #define REG_FILTER_SEL 0x0c +/* use for A1 like chips */ +#define REG_PIN_A1_SEL 0x04 + /* * Note: The S905X3 datasheet reports that BOTH_EDGE is controlled by * bits 24 to 31. Tests on the actual HW show that these bits are @@ -44,6 +47,10 @@ struct meson_gpio_irq_controller; static void meson8_gpio_irq_sel_pin(struct meson_gpio_irq_controller *ctl, unsigned int channel, unsigned long hwirq); static void meson_gpio_irq_init_dummy(struct meson_gpio_irq_controller *ctl); +static void meson_a1_gpio_irq_sel_pin(struct meson_gpio_irq_controller *ctl, + unsigned int channel, + unsigned long hwirq); +static void meson_a1_gpio_irq_init(struct meson_gpio_irq_controller *ctl); struct irq_ctl_ops { void (*gpio_irq_sel_pin)(struct meson_gpio_irq_controller *ctl, @@ -75,6 +82,15 @@ struct meson_gpio_irq_params { .pol_low_offset = 16, \ .pin_sel_mask = 0xff, \ +#define INIT_MESON_A1_COMMON_DATA(irqs) \ + INIT_MESON_COMMON(irqs, meson_a1_gpio_irq_init, \ + meson_a1_gpio_irq_sel_pin) \ + .support_edge_both = true, \ + .edge_both_offset = 16, \ + .edge_single_offset = 8, \ + .pol_low_offset = 0, \ + .pin_sel_mask = 0x7f, \ + static const struct meson_gpio_irq_params meson8_params = { INIT_MESON8_COMMON_DATA(134) }; @@ -101,6 +117,10 @@ static const struct meson_gpio_irq_params sm1_params = { .edge_both_offset = 8, }; +static const struct meson_gpio_irq_params a1_params = { + INIT_MESON_A1_COMMON_DATA(62) +}; + static const struct of_device_id meson_irq_gpio_matches[] = { { .compatible = "amlogic,meson8-gpio-intc", .data = &meson8_params }, { .compatible = "amlogic,meson8b-gpio-intc", .data = &meson8b_params }, @@ -109,6 +129,7 @@ static const struct of_device_id meson_irq_gpio_matches[] = { { .compatible = "amlogic,meson-axg-gpio-intc", .data = &axg_params }, { .compatible = "amlogic,meson-g12a-gpio-intc", .data = &axg_params }, { .compatible = "amlogic,meson-sm1-gpio-intc", .data = &sm1_params }, + { .compatible = "amlogic,meson-a1-gpio-intc", .data = &a1_params }, { } }; @@ -149,6 +170,27 @@ static void meson8_gpio_irq_sel_pin(struct meson_gpio_irq_controller *ctl, hwirq << bit_offset); } +static void meson_a1_gpio_irq_sel_pin(struct meson_gpio_irq_controller *ctl, + unsigned int channel, + unsigned long hwirq) +{ + unsigned int reg_offset; + unsigned int bit_offset; + + bit_offset = ((channel % 2) == 0) ? 0 : 16; + reg_offset = REG_PIN_A1_SEL + ((channel / 2) << 2); + + meson_gpio_irq_update_bits(ctl, reg_offset, + ctl->params->pin_sel_mask << bit_offset, + hwirq << bit_offset); +} + +/* For a1 or later chips like a1 there is a switch to enable/disable irq */ +static void meson_a1_gpio_irq_init(struct meson_gpio_irq_controller *ctl) +{ + meson_gpio_irq_update_bits(ctl, REG_EDGE_POL, BIT(31), BIT(31)); +} + static int meson_gpio_irq_request_channel(struct meson_gpio_irq_controller *ctl, unsigned long hwirq, -- cgit v1.2.3 From b74416dba33be3ed934e21df8286076cbd85b97f Mon Sep 17 00:00:00 2001 From: Hyunki Koo Date: Wed, 25 Dec 2019 06:11:07 +0900 Subject: irqchip: Define EXYNOS_IRQ_COMBINER This patch is written to clean up dependency of ARCH_EXYNOS Not all exynos device have IRQ_COMBINER, especially aarch64 EXYNOS but it is built for all exynos devices. Thus add the config for EXYNOS_IRQ_COMBINER remove direct dependency between ARCH_EXYNOS and exynos-combiner.c and only selected on the aarch32 devices Signed-off-by: Hyunki Koo Signed-off-by: Marc Zyngier Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20191224211108.7128-1-hyunki00.koo@gmail.com --- arch/arm/mach-exynos/Kconfig | 1 + drivers/irqchip/Kconfig | 7 +++++++ drivers/irqchip/Makefile | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-exynos/Kconfig b/arch/arm/mach-exynos/Kconfig index 4ef56571145b..6e7f10c8098a 100644 --- a/arch/arm/mach-exynos/Kconfig +++ b/arch/arm/mach-exynos/Kconfig @@ -12,6 +12,7 @@ menuconfig ARCH_EXYNOS select ARCH_SUPPORTS_BIG_ENDIAN select ARM_AMBA select ARM_GIC + select EXYNOS_IRQ_COMBINER select COMMON_CLK_SAMSUNG select EXYNOS_ASV select EXYNOS_CHIPID diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index bb89dfc82c6e..20c62d759aa4 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -500,4 +500,11 @@ config SIFIVE_PLIC If you don't know what to do here, say Y. +config EXYNOS_IRQ_COMBINER + bool "Samsung Exynos IRQ combiner support" if COMPILE_TEST + depends on (ARCH_EXYNOS && ARM) || COMPILE_TEST + help + Say yes here to add support for the IRQ combiner devices embedded + in Samsung Exynos chips. + endmenu diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 6c9262c673bf..4b1c5113e5e4 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -9,7 +9,7 @@ obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2835.o obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2836.o obj-$(CONFIG_DAVINCI_AINTC) += irq-davinci-aintc.o obj-$(CONFIG_DAVINCI_CP_INTC) += irq-davinci-cp-intc.o -obj-$(CONFIG_ARCH_EXYNOS) += exynos-combiner.o +obj-$(CONFIG_EXYNOS_IRQ_COMBINER) += exynos-combiner.o obj-$(CONFIG_FARADAY_FTINTC010) += irq-ftintc010.o obj-$(CONFIG_ARCH_HIP04) += irq-hip04.o obj-$(CONFIG_ARCH_LPC32XX) += irq-lpc32xx.o -- cgit v1.2.3 From 618ea6275b9806909ef384c6d57f40641d7985d2 Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Fri, 17 Jan 2020 06:10:05 +0000 Subject: dt-bindings: interrupt-controller: Add binding for NXP INTMUX interrupt multiplexer This patch adds the DT bindings for the NXP INTMUX interrupt multiplexer for i.MX8 family SoCs. Signed-off-by: Joakim Zhang Signed-off-by: Marc Zyngier Reviewed-by: Rob Herring Link: https://lore.kernel.org/r/20200117060653.27485-2-qiangqing.zhang@nxp.com --- .../bindings/interrupt-controller/fsl,intmux.yaml | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 Documentation/devicetree/bindings/interrupt-controller/fsl,intmux.yaml diff --git a/Documentation/devicetree/bindings/interrupt-controller/fsl,intmux.yaml b/Documentation/devicetree/bindings/interrupt-controller/fsl,intmux.yaml new file mode 100644 index 000000000000..43c6effbb5bd --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/fsl,intmux.yaml @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/interrupt-controller/fsl,intmux.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Freescale INTMUX interrupt multiplexer + +maintainers: + - Joakim Zhang + +properties: + compatible: + const: fsl,imx-intmux + + reg: + maxItems: 1 + + interrupts: + minItems: 1 + maxItems: 8 + description: | + Should contain the parent interrupt lines (up to 8) used to multiplex + the input interrupts. + + interrupt-controller: true + + '#interrupt-cells': + const: 2 + description: | + The 1st cell is hw interrupt number, the 2nd cell is channel index. + + clocks: + description: ipg clock. + + clock-names: + const: ipg + +required: + - compatible + - reg + - interrupts + - interrupt-controller + - '#interrupt-cells' + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + interrupt-controller@37400000 { + compatible = "fsl,imx-intmux"; + reg = <0x37400000 0x1000>; + interrupts = <0 16 4>, + <0 17 4>, + <0 18 4>, + <0 19 4>, + <0 20 4>, + <0 21 4>, + <0 22 4>, + <0 23 4>; + interrupt-controller; + interrupt-parent = <&gic>; + #interrupt-cells = <2>; + clocks = <&clk>; + clock-names = "ipg"; + }; -- cgit v1.2.3 From 2fbb13961e741494992bae7bfaf7259b65769f9f Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Fri, 17 Jan 2020 06:10:10 +0000 Subject: irqchip: Add NXP INTMUX interrupt multiplexer support The Interrupt Multiplexer (INTMUX) expands the number of peripherals that can interrupt the core: * The INTMUX has 8 channels that are assigned to 8 NVIC interrupt slots. * Each INTMUX channel can receive up to 32 interrupt sources and has 1 interrupt output. * The INTMUX routes the interrupt sources to the interrupt outputs. Signed-off-by: Shengjiu Wang Signed-off-by: Joakim Zhang Signed-off-by: Marc Zyngier Link: https://lore.kernel.org/r/20200117060653.27485-3-qiangqing.zhang@nxp.com --- drivers/irqchip/Kconfig | 6 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-imx-intmux.c | 309 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 316 insertions(+) create mode 100644 drivers/irqchip/irq-imx-intmux.c diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 20c62d759aa4..1006c694d9fb 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -457,6 +457,12 @@ config IMX_IRQSTEER help Support for the i.MX IRQSTEER interrupt multiplexer/remapper. +config IMX_INTMUX + def_bool y if ARCH_MXC + select IRQ_DOMAIN + help + Support for the i.MX INTMUX interrupt multiplexer. + config LS1X_IRQ bool "Loongson-1 Interrupt Controller" depends on MACH_LOONGSON32 diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 4b1c5113e5e4..eae0d78cbf22 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -100,6 +100,7 @@ obj-$(CONFIG_CSKY_MPINTC) += irq-csky-mpintc.o obj-$(CONFIG_CSKY_APB_INTC) += irq-csky-apb-intc.o obj-$(CONFIG_SIFIVE_PLIC) += irq-sifive-plic.o obj-$(CONFIG_IMX_IRQSTEER) += irq-imx-irqsteer.o +obj-$(CONFIG_IMX_INTMUX) += irq-imx-intmux.o obj-$(CONFIG_MADERA_IRQ) += irq-madera.o obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o diff --git a/drivers/irqchip/irq-imx-intmux.c b/drivers/irqchip/irq-imx-intmux.c new file mode 100644 index 000000000000..c27577c81126 --- /dev/null +++ b/drivers/irqchip/irq-imx-intmux.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2017 NXP + +/* INTMUX Block Diagram + * + * ________________ + * interrupt source # 0 +---->| | + * | | | + * interrupt source # 1 +++-->| | + * ... | | | channel # 0 |--------->interrupt out # 0 + * ... | | | | + * ... | | | | + * interrupt source # X-1 +++-->|________________| + * | | | + * | | | + * | | | ________________ + * +---->| | + * | | | | | + * | +-->| | + * | | | | channel # 1 |--------->interrupt out # 1 + * | | +>| | + * | | | | | + * | | | |________________| + * | | | + * | | | + * | | | ... + * | | | ... + * | | | + * | | | ________________ + * +---->| | + * | | | | + * +-->| | + * | | channel # N |--------->interrupt out # N + * +>| | + * | | + * |________________| + * + * + * N: Interrupt Channel Instance Number (N=7) + * X: Interrupt Source Number for each channel (X=32) + * + * The INTMUX interrupt multiplexer has 8 channels, each channel receives 32 + * interrupt sources and generates 1 interrupt output. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CHANIER(n) (0x10 + (0x40 * n)) +#define CHANIPR(n) (0x20 + (0x40 * n)) + +#define CHAN_MAX_NUM 0x8 + +struct intmux_irqchip_data { + int chanidx; + int irq; + struct irq_domain *domain; +}; + +struct intmux_data { + raw_spinlock_t lock; + void __iomem *regs; + struct clk *ipg_clk; + int channum; + struct intmux_irqchip_data irqchip_data[]; +}; + +static void imx_intmux_irq_mask(struct irq_data *d) +{ + struct intmux_irqchip_data *irqchip_data = d->chip_data; + int idx = irqchip_data->chanidx; + struct intmux_data *data = container_of(irqchip_data, struct intmux_data, + irqchip_data[idx]); + unsigned long flags; + void __iomem *reg; + u32 val; + + raw_spin_lock_irqsave(&data->lock, flags); + reg = data->regs + CHANIER(idx); + val = readl_relaxed(reg); + /* disable the interrupt source of this channel */ + val &= ~BIT(d->hwirq); + writel_relaxed(val, reg); + raw_spin_unlock_irqrestore(&data->lock, flags); +} + +static void imx_intmux_irq_unmask(struct irq_data *d) +{ + struct intmux_irqchip_data *irqchip_data = d->chip_data; + int idx = irqchip_data->chanidx; + struct intmux_data *data = container_of(irqchip_data, struct intmux_data, + irqchip_data[idx]); + unsigned long flags; + void __iomem *reg; + u32 val; + + raw_spin_lock_irqsave(&data->lock, flags); + reg = data->regs + CHANIER(idx); + val = readl_relaxed(reg); + /* enable the interrupt source of this channel */ + val |= BIT(d->hwirq); + writel_relaxed(val, reg); + raw_spin_unlock_irqrestore(&data->lock, flags); +} + +static struct irq_chip imx_intmux_irq_chip = { + .name = "intmux", + .irq_mask = imx_intmux_irq_mask, + .irq_unmask = imx_intmux_irq_unmask, +}; + +static int imx_intmux_irq_map(struct irq_domain *h, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_data(irq, h->host_data); + irq_set_chip_and_handler(irq, &imx_intmux_irq_chip, handle_level_irq); + + return 0; +} + +static int imx_intmux_irq_xlate(struct irq_domain *d, struct device_node *node, + const u32 *intspec, unsigned int intsize, + unsigned long *out_hwirq, unsigned int *out_type) +{ + struct intmux_irqchip_data *irqchip_data = d->host_data; + int idx = irqchip_data->chanidx; + struct intmux_data *data = container_of(irqchip_data, struct intmux_data, + irqchip_data[idx]); + + /* + * two cells needed in interrupt specifier: + * the 1st cell: hw interrupt number + * the 2nd cell: channel index + */ + if (WARN_ON(intsize != 2)) + return -EINVAL; + + if (WARN_ON(intspec[1] >= data->channum)) + return -EINVAL; + + *out_hwirq = intspec[0]; + *out_type = IRQ_TYPE_LEVEL_HIGH; + + return 0; +} + +static int imx_intmux_irq_select(struct irq_domain *d, struct irq_fwspec *fwspec, + enum irq_domain_bus_token bus_token) +{ + struct intmux_irqchip_data *irqchip_data = d->host_data; + + /* Not for us */ + if (fwspec->fwnode != d->fwnode) + return false; + + return irqchip_data->chanidx == fwspec->param[1]; +} + +static const struct irq_domain_ops imx_intmux_domain_ops = { + .map = imx_intmux_irq_map, + .xlate = imx_intmux_irq_xlate, + .select = imx_intmux_irq_select, +}; + +static void imx_intmux_irq_handler(struct irq_desc *desc) +{ + struct intmux_irqchip_data *irqchip_data = irq_desc_get_handler_data(desc); + int idx = irqchip_data->chanidx; + struct intmux_data *data = container_of(irqchip_data, struct intmux_data, + irqchip_data[idx]); + unsigned long irqstat; + int pos, virq; + + chained_irq_enter(irq_desc_get_chip(desc), desc); + + /* read the interrupt source pending status of this channel */ + irqstat = readl_relaxed(data->regs + CHANIPR(idx)); + + for_each_set_bit(pos, &irqstat, 32) { + virq = irq_find_mapping(irqchip_data->domain, pos); + if (virq) + generic_handle_irq(virq); + } + + chained_irq_exit(irq_desc_get_chip(desc), desc); +} + +static int imx_intmux_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct irq_domain *domain; + struct intmux_data *data; + int channum; + int i, ret; + + channum = platform_irq_count(pdev); + if (channum == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (channum > CHAN_MAX_NUM) { + dev_err(&pdev->dev, "supports up to %d multiplex channels\n", + CHAN_MAX_NUM); + return -EINVAL; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data) + + channum * sizeof(data->irqchip_data[0]), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->regs)) { + dev_err(&pdev->dev, "failed to initialize reg\n"); + return PTR_ERR(data->regs); + } + + data->ipg_clk = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(data->ipg_clk)) { + ret = PTR_ERR(data->ipg_clk); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "failed to get ipg clk: %d\n", ret); + return ret; + } + + data->channum = channum; + raw_spin_lock_init(&data->lock); + + ret = clk_prepare_enable(data->ipg_clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable ipg clk: %d\n", ret); + return ret; + } + + for (i = 0; i < channum; i++) { + data->irqchip_data[i].chanidx = i; + + data->irqchip_data[i].irq = irq_of_parse_and_map(np, i); + if (data->irqchip_data[i].irq <= 0) { + ret = -EINVAL; + dev_err(&pdev->dev, "failed to get irq\n"); + goto out; + } + + domain = irq_domain_add_linear(np, 32, &imx_intmux_domain_ops, + &data->irqchip_data[i]); + if (!domain) { + ret = -ENOMEM; + dev_err(&pdev->dev, "failed to create IRQ domain\n"); + goto out; + } + data->irqchip_data[i].domain = domain; + + /* disable all interrupt sources of this channel firstly */ + writel_relaxed(0, data->regs + CHANIER(i)); + + irq_set_chained_handler_and_data(data->irqchip_data[i].irq, + imx_intmux_irq_handler, + &data->irqchip_data[i]); + } + + platform_set_drvdata(pdev, data); + + return 0; +out: + clk_disable_unprepare(data->ipg_clk); + return ret; +} + +static int imx_intmux_remove(struct platform_device *pdev) +{ + struct intmux_data *data = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < data->channum; i++) { + /* disable all interrupt sources of this channel */ + writel_relaxed(0, data->regs + CHANIER(i)); + + irq_set_chained_handler_and_data(data->irqchip_data[i].irq, + NULL, NULL); + + irq_domain_remove(data->irqchip_data[i].domain); + } + + clk_disable_unprepare(data->ipg_clk); + + return 0; +} + +static const struct of_device_id imx_intmux_id[] = { + { .compatible = "fsl,imx-intmux", }, + { /* s