From 45e5202213ae6541f7916bb3c64fbcd3019ec473 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 7 Mar 2017 16:28:18 +0000 Subject: [PATCH 01/28] genirq: Add support for nested shared IRQs On a specific audio system an interrupt input of an audio CODEC is used as a shared interrupt. That interrupt input is handled by a CODEC specific irq chip driver and triggers a CPU interrupt via the CODEC irq output line. The CODEC interrupt handler demultiplexes the CODEC interrupt inputs and the interrupt handlers for these demultiplexed inputs run nested in the context of the CODEC interrupt handler. The demultiplexed interrupts use handle_nested_irq() as their interrupt handler, which unfortunately has no support for shared interrupts. So the above hardware cannot be supported. Add shared interrupt support to handle_nested_irq() by iterating over the interrupt action chain. [ tglx: Massaged changelog ] Signed-off-by: Charles Keepax Cc: patches@opensource.wolfsonmicro.com Link: http://lkml.kernel.org/r/1488904098-5350-1-git-send-email-ckeepax@opensource.wolfsonmicro.com Signed-off-by: Thomas Gleixner --- kernel/irq/chip.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index be3c34e4f2ac..686be4b73018 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -348,7 +348,10 @@ void handle_nested_irq(unsigned int irq) irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); raw_spin_unlock_irq(&desc->lock); - action_ret = action->thread_fn(action->irq, action->dev_id); + action_ret = IRQ_NONE; + for_each_action_of_desc(desc, action) + action_ret |= action->thread_fn(action->irq, action->dev_id); + if (!noirqdebug) note_interrupt(desc, action_ret); From 77a452551b73aec0817d58ecf6d0dd1e74dab90c Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 18 Mar 2017 17:53:23 +0100 Subject: [PATCH 02/28] dt-bindings: gemini: augment Gemini bindings to reflect Faraday origin When we merged the Gemini interrupt controller it was not yet discovered that this IP block is actually a standard Faraday Technology interrupt controller. As the IP block will probably appear in other designs as well, let's augment the DT bindings to reflect that it is first and foremost a standard Faraday part with a function name (FTINTC010) so that people reusing the IP easily find the driver they need. Sorry for the mistakes due to lack of information. Cc: Greentime Hu Acked-by: Rob Herring Acked-by: Hans Ulli Kroll Signed-off-by: Linus Walleij Signed-off-by: Marc Zyngier --- ...interrupt-controller.txt => faraday,ftintc010.txt} | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) rename Documentation/devicetree/bindings/interrupt-controller/{cortina,gemini-interrupt-controller.txt => faraday,ftintc010.txt} (63%) diff --git a/Documentation/devicetree/bindings/interrupt-controller/cortina,gemini-interrupt-controller.txt b/Documentation/devicetree/bindings/interrupt-controller/faraday,ftintc010.txt similarity index 63% rename from Documentation/devicetree/bindings/interrupt-controller/cortina,gemini-interrupt-controller.txt rename to Documentation/devicetree/bindings/interrupt-controller/faraday,ftintc010.txt index 97c1167fa533..24428d47f487 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/cortina,gemini-interrupt-controller.txt +++ b/Documentation/devicetree/bindings/interrupt-controller/faraday,ftintc010.txt @@ -1,9 +1,12 @@ -* Cortina Systems Gemini interrupt controller +* Faraday Technologt FTINTC010 interrupt controller -This interrupt controller is found on the Gemini SoCs. +This interrupt controller is a stock IP block from Faraday Technology found +in the Gemini SoCs and other designs. Required properties: -- compatible: must be "cortina,gemini-interrupt-controller" +- compatible: must be one of + "faraday,ftintc010" + "cortina,gemini-interrupt-controller" (deprecated) - reg: The register bank for the interrupt controller. - interrupt-controller: Identifies the node as an interrupt controller - #interrupt-cells: The number of cells to define the interrupts. @@ -15,7 +18,7 @@ Required properties: Example: interrupt-controller@48000000 { - compatible = "cortina,gemini-interrupt-controller"; + compatible = "faraday,ftintc010" reg = <0x48000000 0x1000>; interrupt-controller; #interrupt-cells = <2>; From 6ee532e2faa9979f0ee00afbfd39cbc034b99153 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 18 Mar 2017 17:53:24 +0100 Subject: [PATCH 03/28] irqchip/gemini: Refactor Gemini driver to reflect Faraday origin The Gemini irqchip turns out to be a standard IP component from Faraday Technology named FTINTC010 after some research and new information. - Rename the driver and all symbols to reflect the new information. - Add the new compatible string "faraday,ftintc010" - Create a Kconfig symbol CONFIG_FARADAY_FTINTC010 so that SoCs using this interrupt controller can easily select and reuse it instead of hardwiring it to ARCH_GEMINI I have created a separate patch to select the new Kconfig symbol from the Gemini machine, which will be merged through the ARM SoC tree. Cc: Greentime Hu Cc: Paulius Zaleckas Signed-off-by: Linus Walleij Signed-off-by: Marc Zyngier --- drivers/irqchip/Kconfig | 6 + drivers/irqchip/Makefile | 2 +- drivers/irqchip/irq-ftintc010.c | 187 ++++++++++++++++++++++++++++++++ drivers/irqchip/irq-gemini.c | 185 ------------------------------- 4 files changed, 194 insertions(+), 186 deletions(-) create mode 100644 drivers/irqchip/irq-ftintc010.c delete mode 100644 drivers/irqchip/irq-gemini.c diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 125528f39e92..6afa9e01e610 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -115,6 +115,12 @@ config DW_APB_ICTL select GENERIC_IRQ_CHIP select IRQ_DOMAIN +config FARADAY_FTINTC010 + bool + select IRQ_DOMAIN + select MULTI_IRQ_HANDLER + select SPARSE_IRQ + config HISILICON_IRQ_MBIGEN bool select ARM_GIC_V3 diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 152bc40b6762..cdf3474d3851 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -6,7 +6,7 @@ obj-$(CONFIG_ATH79) += irq-ath79-misc.o obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2835.o obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2836.o obj-$(CONFIG_ARCH_EXYNOS) += exynos-combiner.o -obj-$(CONFIG_ARCH_GEMINI) += irq-gemini.o +obj-$(CONFIG_FARADAY_FTINTC010) += irq-ftintc010.o obj-$(CONFIG_ARCH_HIP04) += irq-hip04.o obj-$(CONFIG_ARCH_LPC32XX) += irq-lpc32xx.o obj-$(CONFIG_ARCH_MMP) += irq-mmp.o diff --git a/drivers/irqchip/irq-ftintc010.c b/drivers/irqchip/irq-ftintc010.c new file mode 100644 index 000000000000..81929aa33990 --- /dev/null +++ b/drivers/irqchip/irq-ftintc010.c @@ -0,0 +1,187 @@ +/* + * irqchip for the Faraday Technology FTINTC010 Copyright (C) 2017 Linus + * Walleij + * + * Based on arch/arm/mach-gemini/irq.c + * Copyright (C) 2001-2006 Storlink, Corp. + * Copyright (C) 2008-2009 Paulius Zaleckas + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define FT010_NUM_IRQS 32 + +#define FT010_IRQ_SOURCE(base_addr) (base_addr + 0x00) +#define FT010_IRQ_MASK(base_addr) (base_addr + 0x04) +#define FT010_IRQ_CLEAR(base_addr) (base_addr + 0x08) +#define FT010_IRQ_MODE(base_addr) (base_addr + 0x0C) +#define FT010_IRQ_POLARITY(base_addr) (base_addr + 0x10) +#define FT010_IRQ_STATUS(base_addr) (base_addr + 0x14) +#define FT010_FIQ_SOURCE(base_addr) (base_addr + 0x20) +#define FT010_FIQ_MASK(base_addr) (base_addr + 0x24) +#define FT010_FIQ_CLEAR(base_addr) (base_addr + 0x28) +#define FT010_FIQ_MODE(base_addr) (base_addr + 0x2C) +#define FT010_FIQ_POLARITY(base_addr) (base_addr + 0x30) +#define FT010_FIQ_STATUS(base_addr) (base_addr + 0x34) + +/** + * struct ft010_irq_data - irq data container for the Faraday IRQ controller + * @base: memory offset in virtual memory + * @chip: chip container for this instance + * @domain: IRQ domain for this instance + */ +struct ft010_irq_data { + void __iomem *base; + struct irq_chip chip; + struct irq_domain *domain; +}; + +static void ft010_irq_mask(struct irq_data *d) +{ + struct ft010_irq_data *f = irq_data_get_irq_chip_data(d); + unsigned int mask; + + mask = readl(FT010_IRQ_MASK(f->base)); + mask &= ~BIT(irqd_to_hwirq(d)); + writel(mask, FT010_IRQ_MASK(f->base)); +} + +static void ft010_irq_unmask(struct irq_data *d) +{ + struct ft010_irq_data *f = irq_data_get_irq_chip_data(d); + unsigned int mask; + + mask = readl(FT010_IRQ_MASK(f->base)); + mask |= BIT(irqd_to_hwirq(d)); + writel(mask, FT010_IRQ_MASK(f->base)); +} + +static void ft010_irq_ack(struct irq_data *d) +{ + struct ft010_irq_data *f = irq_data_get_irq_chip_data(d); + + writel(BIT(irqd_to_hwirq(d)), FT010_IRQ_CLEAR(f->base)); +} + +static int ft010_irq_set_type(struct irq_data *d, unsigned int trigger) +{ + struct ft010_irq_data *f = irq_data_get_irq_chip_data(d); + int offset = irqd_to_hwirq(d); + u32 mode, polarity; + + mode = readl(FT010_IRQ_MODE(f->base)); + polarity = readl(FT010_IRQ_POLARITY(f->base)); + + if (trigger & (IRQ_TYPE_LEVEL_HIGH)) { + irq_set_handler_locked(d, handle_level_irq); + /* Disable edge detection */ + mode &= ~BIT(offset); + polarity &= ~BIT(offset); + } else if (trigger & IRQ_TYPE_EDGE_RISING) { + irq_set_handler_locked(d, handle_edge_irq); + mode |= BIT(offset); + polarity |= BIT(offset); + } else if (trigger & IRQ_TYPE_EDGE_FALLING) { + irq_set_handler_locked(d, handle_edge_irq); + mode |= BIT(offset); + polarity &= ~BIT(offset); + } else { + irq_set_handler_locked(d, handle_bad_irq); + pr_warn("GEMINI IRQ: no supported trigger selected for line %d\n", + offset); + } + + writel(mode, FT010_IRQ_MODE(f->base)); + writel(polarity, FT010_IRQ_POLARITY(f->base)); + + return 0; +} + +static struct irq_chip ft010_irq_chip = { + .name = "FTINTC010", + .irq_ack = ft010_irq_ack, + .irq_mask = ft010_irq_mask, + .irq_unmask = ft010_irq_unmask, + .irq_set_type = ft010_irq_set_type, +}; + +/* Local static for the IRQ entry call */ +static struct ft010_irq_data firq; + +asmlinkage void __exception_irq_entry ft010_irqchip_handle_irq(struct pt_regs *regs) +{ + struct ft010_irq_data *f = &firq; + int irq; + u32 status; + + while ((status = readl(FT010_IRQ_STATUS(f->base)))) { + irq = ffs(status) - 1; + handle_domain_irq(f->domain, irq, regs); + } +} + +static int ft010_irqdomain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct ft010_irq_data *f = d->host_data; + + irq_set_chip_data(irq, f); + /* All IRQs should set up their type, flags as bad by default */ + irq_set_chip_and_handler(irq, &ft010_irq_chip, handle_bad_irq); + irq_set_probe(irq); + + return 0; +} + +static void ft010_irqdomain_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 ft010_irqdomain_ops = { + .map = ft010_irqdomain_map, + .unmap = ft010_irqdomain_unmap, + .xlate = irq_domain_xlate_onetwocell, +}; + +int __init ft010_of_init_irq(struct device_node *node, + struct device_node *parent) +{ + struct ft010_irq_data *f = &firq; + + /* + * Disable the idle handler by default since it is buggy + * For more info see arch/arm/mach-gemini/idle.c + */ + cpu_idle_poll_ctrl(true); + + f->base = of_iomap(node, 0); + WARN(!f->base, "unable to map gemini irq registers\n"); + + /* Disable all interrupts */ + writel(0, FT010_IRQ_MASK(f->base)); + writel(0, FT010_FIQ_MASK(f->base)); + + f->domain = irq_domain_add_simple(node, FT010_NUM_IRQS, 0, + &ft010_irqdomain_ops, f); + set_handle_irq(ft010_irqchip_handle_irq); + + return 0; +} +IRQCHIP_DECLARE(faraday, "faraday,ftintc010", + ft010_of_init_irq); +IRQCHIP_DECLARE(gemini, "cortina,gemini-interrupt-controller", + ft010_of_init_irq); diff --git a/drivers/irqchip/irq-gemini.c b/drivers/irqchip/irq-gemini.c deleted file mode 100644 index 495224c743ee..000000000000 --- a/drivers/irqchip/irq-gemini.c +++ /dev/null @@ -1,185 +0,0 @@ -/* - * irqchip for the Cortina Systems Gemini Copyright (C) 2017 Linus - * Walleij - * - * Based on arch/arm/mach-gemini/irq.c - * Copyright (C) 2001-2006 Storlink, Corp. - * Copyright (C) 2008-2009 Paulius Zaleckas - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define GEMINI_NUM_IRQS 32 - -#define GEMINI_IRQ_SOURCE(base_addr) (base_addr + 0x00) -#define GEMINI_IRQ_MASK(base_addr) (base_addr + 0x04) -#define GEMINI_IRQ_CLEAR(base_addr) (base_addr + 0x08) -#define GEMINI_IRQ_MODE(base_addr) (base_addr + 0x0C) -#define GEMINI_IRQ_POLARITY(base_addr) (base_addr + 0x10) -#define GEMINI_IRQ_STATUS(base_addr) (base_addr + 0x14) -#define GEMINI_FIQ_SOURCE(base_addr) (base_addr + 0x20) -#define GEMINI_FIQ_MASK(base_addr) (base_addr + 0x24) -#define GEMINI_FIQ_CLEAR(base_addr) (base_addr + 0x28) -#define GEMINI_FIQ_MODE(base_addr) (base_addr + 0x2C) -#define GEMINI_FIQ_POLARITY(base_addr) (base_addr + 0x30) -#define GEMINI_FIQ_STATUS(base_addr) (base_addr + 0x34) - -/** - * struct gemini_irq_data - irq data container for the Gemini IRQ controller - * @base: memory offset in virtual memory - * @chip: chip container for this instance - * @domain: IRQ domain for this instance - */ -struct gemini_irq_data { - void __iomem *base; - struct irq_chip chip; - struct irq_domain *domain; -}; - -static void gemini_irq_mask(struct irq_data *d) -{ - struct gemini_irq_data *g = irq_data_get_irq_chip_data(d); - unsigned int mask; - - mask = readl(GEMINI_IRQ_MASK(g->base)); - mask &= ~BIT(irqd_to_hwirq(d)); - writel(mask, GEMINI_IRQ_MASK(g->base)); -} - -static void gemini_irq_unmask(struct irq_data *d) -{ - struct gemini_irq_data *g = irq_data_get_irq_chip_data(d); - unsigned int mask; - - mask = readl(GEMINI_IRQ_MASK(g->base)); - mask |= BIT(irqd_to_hwirq(d)); - writel(mask, GEMINI_IRQ_MASK(g->base)); -} - -static void gemini_irq_ack(struct irq_data *d) -{ - struct gemini_irq_data *g = irq_data_get_irq_chip_data(d); - - writel(BIT(irqd_to_hwirq(d)), GEMINI_IRQ_CLEAR(g->base)); -} - -static int gemini_irq_set_type(struct irq_data *d, unsigned int trigger) -{ - struct gemini_irq_data *g = irq_data_get_irq_chip_data(d); - int offset = irqd_to_hwirq(d); - u32 mode, polarity; - - mode = readl(GEMINI_IRQ_MODE(g->base)); - polarity = readl(GEMINI_IRQ_POLARITY(g->base)); - - if (trigger & (IRQ_TYPE_LEVEL_HIGH)) { - irq_set_handler_locked(d, handle_level_irq); - /* Disable edge detection */ - mode &= ~BIT(offset); - polarity &= ~BIT(offset); - } else if (trigger & IRQ_TYPE_EDGE_RISING) { - irq_set_handler_locked(d, handle_edge_irq); - mode |= BIT(offset); - polarity |= BIT(offset); - } else if (trigger & IRQ_TYPE_EDGE_FALLING) { - irq_set_handler_locked(d, handle_edge_irq); - mode |= BIT(offset); - polarity &= ~BIT(offset); - } else { - irq_set_handler_locked(d, handle_bad_irq); - pr_warn("GEMINI IRQ: no supported trigger selected for line %d\n", - offset); - } - - writel(mode, GEMINI_IRQ_MODE(g->base)); - writel(polarity, GEMINI_IRQ_POLARITY(g->base)); - - return 0; -} - -static struct irq_chip gemini_irq_chip = { - .name = "GEMINI", - .irq_ack = gemini_irq_ack, - .irq_mask = gemini_irq_mask, - .irq_unmask = gemini_irq_unmask, - .irq_set_type = gemini_irq_set_type, -}; - -/* Local static for the IRQ entry call */ -static struct gemini_irq_data girq; - -asmlinkage void __exception_irq_entry gemini_irqchip_handle_irq(struct pt_regs *regs) -{ - struct gemini_irq_data *g = &girq; - int irq; - u32 status; - - while ((status = readl(GEMINI_IRQ_STATUS(g->base)))) { - irq = ffs(status) - 1; - handle_domain_irq(g->domain, irq, regs); - } -} - -static int gemini_irqdomain_map(struct irq_domain *d, unsigned int irq, - irq_hw_number_t hwirq) -{ - struct gemini_irq_data *g = d->host_data; - - irq_set_chip_data(irq, g); - /* All IRQs should set up their type, flags as bad by default */ - irq_set_chip_and_handler(irq, &gemini_irq_chip, handle_bad_irq); - irq_set_probe(irq); - - return 0; -} - -static void gemini_irqdomain_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 gemini_irqdomain_ops = { - .map = gemini_irqdomain_map, - .unmap = gemini_irqdomain_unmap, - .xlate = irq_domain_xlate_onetwocell, -}; - -int __init gemini_of_init_irq(struct device_node *node, - struct device_node *parent) -{ - struct gemini_irq_data *g = &girq; - - /* - * Disable the idle handler by default since it is buggy - * For more info see arch/arm/mach-gemini/idle.c - */ - cpu_idle_poll_ctrl(true); - - g->base = of_iomap(node, 0); - WARN(!g->base, "unable to map gemini irq registers\n"); - - /* Disable all interrupts */ - writel(0, GEMINI_IRQ_MASK(g->base)); - writel(0, GEMINI_FIQ_MASK(g->base)); - - g->domain = irq_domain_add_simple(node, GEMINI_NUM_IRQS, 0, - &gemini_irqdomain_ops, g); - set_handle_irq(gemini_irqchip_handle_irq); - - return 0; -} -IRQCHIP_DECLARE(gemini, "cortina,gemini-interrupt-controller", - gemini_of_init_irq); From d2d55ab8ad845807311130a40fc87dd5163f7133 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 18 Mar 2017 17:53:25 +0100 Subject: [PATCH 04/28] irqchip/faraday: Fix the trigger types The triggers in the driver were right for high level triggered IRQs but the edge detection on edge triggered IRQs was wrong. After studying a proper driver from Po-Yu Chuang I now know how to handle these right, and we can properly implement low level IRQs as well. The device trees for the Gemini had polarity switched around so these have been fixed to conform to the right polarity as well. Cc: Greentime Hu Signed-off-by: Linus Walleij Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-ftintc010.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/irqchip/irq-ftintc010.c b/drivers/irqchip/irq-ftintc010.c index 81929aa33990..0cc81df6f743 100644 --- a/drivers/irqchip/irq-ftintc010.c +++ b/drivers/irqchip/irq-ftintc010.c @@ -26,7 +26,9 @@ #define FT010_IRQ_SOURCE(base_addr) (base_addr + 0x00) #define FT010_IRQ_MASK(base_addr) (base_addr + 0x04) #define FT010_IRQ_CLEAR(base_addr) (base_addr + 0x08) +/* Selects level- or edge-triggered */ #define FT010_IRQ_MODE(base_addr) (base_addr + 0x0C) +/* Selects active low/high or falling/rising edge */ #define FT010_IRQ_POLARITY(base_addr) (base_addr + 0x10) #define FT010_IRQ_STATUS(base_addr) (base_addr + 0x14) #define FT010_FIQ_SOURCE(base_addr) (base_addr + 0x20) @@ -84,22 +86,25 @@ static int ft010_irq_set_type(struct irq_data *d, unsigned int trigger) mode = readl(FT010_IRQ_MODE(f->base)); polarity = readl(FT010_IRQ_POLARITY(f->base)); - if (trigger & (IRQ_TYPE_LEVEL_HIGH)) { + if (trigger & (IRQ_TYPE_LEVEL_LOW)) { + irq_set_handler_locked(d, handle_level_irq); + mode &= ~BIT(offset); + polarity |= BIT(offset); + } else if (trigger & (IRQ_TYPE_LEVEL_HIGH)) { irq_set_handler_locked(d, handle_level_irq); - /* Disable edge detection */ mode &= ~BIT(offset); polarity &= ~BIT(offset); - } else if (trigger & IRQ_TYPE_EDGE_RISING) { + } else if (trigger & IRQ_TYPE_EDGE_FALLING) { irq_set_handler_locked(d, handle_edge_irq); mode |= BIT(offset); polarity |= BIT(offset); - } else if (trigger & IRQ_TYPE_EDGE_FALLING) { + } else if (trigger & IRQ_TYPE_EDGE_RISING) { irq_set_handler_locked(d, handle_edge_irq); mode |= BIT(offset); polarity &= ~BIT(offset); } else { irq_set_handler_locked(d, handle_bad_irq); - pr_warn("GEMINI IRQ: no supported trigger selected for line %d\n", + pr_warn("Faraday IRQ: no supported trigger selected for line %d\n", offset); } From 390d2d490b4618fe147927778a8fdbc8e22f3dc3 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Sat, 18 Mar 2017 17:53:26 +0100 Subject: [PATCH 05/28] irqchip/faraday: Replace moxa with ftintc010 The Moxa Art interrupt controller is very very likely just an instance of the Faraday FTINTC010 interrupt controller from Faraday Technology. An indication would be its close association with the FA526 ARM core and the fact that the register layout is the same. The implementation in irq-moxart.c can probably be right off replaced with the irq-ftintc010.c driver by adding a compatible string, selecting this irqchip from the machine and run. As a bonus we have an irqchip driver supporting high/low and rising/falling edges for the Moxa Art, and shared code with the Gemini platform. Acked-by: Olof Johansson Tested-by: Jonas Jensen Signed-off-by: Linus Walleij Signed-off-by: Marc Zyngier --- arch/arm/mach-moxart/Kconfig | 2 +- drivers/irqchip/Makefile | 1 - drivers/irqchip/irq-ftintc010.c | 2 + drivers/irqchip/irq-moxart.c | 116 -------------------------------- 4 files changed, 3 insertions(+), 118 deletions(-) delete mode 100644 drivers/irqchip/irq-moxart.c diff --git a/arch/arm/mach-moxart/Kconfig b/arch/arm/mach-moxart/Kconfig index f69e28b85e88..70db2abf6163 100644 --- a/arch/arm/mach-moxart/Kconfig +++ b/arch/arm/mach-moxart/Kconfig @@ -3,8 +3,8 @@ menuconfig ARCH_MOXART depends on ARCH_MULTI_V4 select CPU_FA526 select ARM_DMA_MEM_BUFFERABLE + select FARADAY_FTINTC010 select MOXART_TIMER - select GENERIC_IRQ_CHIP select GPIOLIB select PHYLIB if NETDEVICES help diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index cdf3474d3851..d80bc3e9d511 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -16,7 +16,6 @@ obj-$(CONFIG_ARCH_S3C24XX) += irq-s3c24xx.o obj-$(CONFIG_DW_APB_ICTL) += irq-dw-apb-ictl.o obj-$(CONFIG_METAG) += irq-metag-ext.o obj-$(CONFIG_METAG_PERFCOUNTER_IRQS) += irq-metag.o -obj-$(CONFIG_ARCH_MOXART) += irq-moxart.o obj-$(CONFIG_CLPS711X_IRQCHIP) += irq-clps711x.o obj-$(CONFIG_OR1K_PIC) += irq-or1k-pic.o obj-$(CONFIG_ORION_IRQCHIP) += irq-orion.o diff --git a/drivers/irqchip/irq-ftintc010.c b/drivers/irqchip/irq-ftintc010.c index 0cc81df6f743..cd2dc8bbbe9c 100644 --- a/drivers/irqchip/irq-ftintc010.c +++ b/drivers/irqchip/irq-ftintc010.c @@ -190,3 +190,5 @@ IRQCHIP_DECLARE(faraday, "faraday,ftintc010", ft010_of_init_irq); IRQCHIP_DECLARE(gemini, "cortina,gemini-interrupt-controller", ft010_of_init_irq); +IRQCHIP_DECLARE(moxa, "moxa,moxart-ic", + ft010_of_init_irq); diff --git a/drivers/irqchip/irq-moxart.c b/drivers/irqchip/irq-moxart.c deleted file mode 100644 index a24b06a1718b..000000000000 --- a/drivers/irqchip/irq-moxart.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * MOXA ART SoCs IRQ chip driver. - * - * Copyright (C) 2013 Jonas Jensen - * - * Jonas Jensen - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include - -#define IRQ_SOURCE_REG 0 -#define IRQ_MASK_REG 0x04 -#define IRQ_CLEAR_REG 0x08 -#define IRQ_MODE_REG 0x0c -#define IRQ_LEVEL_REG 0x10 -#define IRQ_STATUS_REG 0x14 - -#define FIQ_SOURCE_REG 0x20 -#define FIQ_MASK_REG 0x24 -#define FIQ_CLEAR_REG 0x28 -#define FIQ_MODE_REG 0x2c -#define FIQ_LEVEL_REG 0x30 -#define FIQ_STATUS_REG 0x34 - - -struct moxart_irq_data { - void __iomem *base; - struct irq_domain *domain; - unsigned int interrupt_mask; -}; - -static struct moxart_irq_data intc; - -static void __exception_irq_entry handle_irq(struct pt_regs *regs) -{ - u32 irqstat; - int hwirq; - - irqstat = readl(intc.base + IRQ_STATUS_REG); - - while (irqstat) { - hwirq = ffs(irqstat) - 1; - handle_IRQ(irq_linear_revmap(intc.domain, hwirq), regs); - irqstat &= ~(1 << hwirq); - } -} - -static int __init moxart_of_intc_init(struct device_node *node, - struct device_node *parent) -{ - unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; - int ret; - struct irq_chip_generic *gc; - - intc.base = of_iomap(node, 0); - if (!intc.base) { - pr_err("%s: unable to map IC registers\n", - node->full_name); - return -EINVAL; - } - - intc.domain = irq_domain_add_linear(node, 32, &irq_generic_chip_ops, - intc.base); - if (!intc.domain) { - pr_err("%s: unable to create IRQ domain\n", node->full_name); - return -EINVAL; - } - - ret = irq_alloc_domain_generic_chips(intc.domain, 32, 1, - "MOXARTINTC", handle_edge_irq, - clr, 0, IRQ_GC_INIT_MASK_CACHE); - if (ret) { - pr_err("%s: could not allocate generic chip\n", - node->full_name); - irq_domain_remove(intc.domain); - return -EINVAL; - } - - ret = of_property_read_u32(node, "interrupt-mask", - &intc.interrupt_mask); - if (ret) - pr_err("%s: could not read interrupt-mask DT property\n", - node->full_name); - - gc = irq_get_domain_generic_chip(intc.domain, 0); - - gc->reg_base = intc.base; - gc->chip_types[0].regs.mask = IRQ_MASK_REG; - gc->chip_types[0].regs.ack = IRQ_CLEAR_REG; - gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit; - gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit; - gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit; - - writel(0, intc.base + IRQ_MASK_REG); - writel(0xffffffff, intc.base + IRQ_CLEAR_REG); - - writel(intc.interrupt_mask, intc.base + IRQ_MODE_REG); - writel(intc.interrupt_mask, intc.base + IRQ_LEVEL_REG); - - set_handle_irq(handle_irq); - - return 0; -} -IRQCHIP_DECLARE(moxa_moxart_ic, "moxa,moxart-ic", moxart_of_intc_init); From d240fe0a4066e592f8e3a698d8648bdd47bc51b7 Mon Sep 17 00:00:00 2001 From: Mars Cheng Date: Sun, 19 Mar 2017 23:26:22 +0800 Subject: [PATCH 06/28] dt-bindings: mtk-sysirq: Add multiple bases support for Mediatek sysirq This describes how to specify multiple base addresses for sysirq in mediatek platforms. Acked-by: Rob Herring Signed-off-by: Mars Cheng Signed-off-by: Marc Zyngier --- .../bindings/interrupt-controller/mediatek,sysirq.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/interrupt-controller/mediatek,sysirq.txt b/Documentation/devicetree/bindings/interrupt-controller/mediatek,sysirq.txt index 9d1d72c65489..a89c03bb1a81 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/mediatek,sysirq.txt +++ b/Documentation/devicetree/bindings/interrupt-controller/mediatek,sysirq.txt @@ -21,13 +21,16 @@ Required properties: - interrupt-parent: phandle of irq parent for sysirq. The parent must use the same interrupt-cells format as GIC. - reg: Physical base address of the intpol registers and length of memory - mapped region. + mapped region. Could be multiple bases here. Ex: mt6797 needs 2 reg, others + need 1. Example: - sysirq: interrupt-controller@10200100 { - compatible = "mediatek,mt6589-sysirq", "mediatek,mt6577-sysirq"; + sysirq: intpol-controller@10200620 { + compatible = "mediatek,mt6797-sysirq", + "mediatek,mt6577-sysirq"; interrupt-controller; #interrupt-cells = <3>; interrupt-parent = <&gic>; - reg = <0 0x10200100 0 0x1c>; + reg = <0 0x10220620 0 0x20>, + <0 0x10220690 0 0x10>; }; From 13683f9bb6166ba0d67e8f89cad85e064032c0f8 Mon Sep 17 00:00:00 2001 From: Mars Cheng Date: Sun, 19 Mar 2017 23:26:23 +0800 Subject: [PATCH 07/28] irqchip/mtk-sysirq: Extend intpol base to arbitrary number Originally driver only supports one base. However, MT6797 has more than one bases to configure interrupt polarity. To support possible design change, here comes a solution to use arbitrary number of bases. Signed-off-by: Mars Cheng Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-mtk-sysirq.c | 116 +++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 21 deletions(-) diff --git a/drivers/irqchip/irq-mtk-sysirq.c b/drivers/irqchip/irq-mtk-sysirq.c index 63ac73b1d9c8..9ba969cfad81 100644 --- a/drivers/irqchip/irq-mtk-sysirq.c +++ b/drivers/irqchip/irq-mtk-sysirq.c @@ -24,22 +24,29 @@ struct mtk_sysirq_chip_data { spinlock_t lock; - void __iomem *intpol_base; + u32 nr_intpol_bases; + void __iomem **intpol_bases; + u32 *intpol_words; + u8 *intpol_idx; + u16 *which_word; }; static int mtk_sysirq_set_type(struct irq_data *data, unsigned int type) { irq_hw_number_t hwirq = data->hwirq; struct mtk_sysirq_chip_data *chip_data = data->chip_data; + u8 intpol_idx = chip_data->intpol_idx[hwirq]; + void __iomem *base; u32 offset, reg_index, value; unsigned long flags; int ret; + base = chip_data->intpol_bases[intpol_idx]; + reg_index = chip_data->which_word[hwirq]; offset = hwirq & 0x1f; - reg_index = hwirq >> 5; spin_lock_irqsave(&chip_data->lock, flags); - value = readl_relaxed(chip_data->intpol_base + reg_index * 4); + value = readl_relaxed(base + reg_index * 4); if (type == IRQ_TYPE_LEVEL_LOW || type == IRQ_TYPE_EDGE_FALLING) { if (type == IRQ_TYPE_LEVEL_LOW) type = IRQ_TYPE_LEVEL_HIGH; @@ -49,7 +56,8 @@ static int mtk_sysirq_set_type(struct irq_data *data, unsigned int type) } else { value &= ~(1 << offset); } - writel(value, chip_data->intpol_base + reg_index * 4); + + writel(value, base + reg_index * 4); data = data->parent_data; ret = data->chip->irq_set_type(data, type); @@ -124,8 +132,7 @@ static int __init mtk_sysirq_of_init(struct device_node *node, { struct irq_domain *domain, *domain_parent; struct mtk_sysirq_chip_data *chip_data; - int ret, size, intpol_num; - struct resource res; + int ret, size, intpol_num = 0, nr_intpol_bases = 0, i = 0; domain_parent = irq_find_host(parent); if (!domain_parent) { @@ -133,36 +140,103 @@ static int __init mtk_sysirq_of_init(struct device_node *node, return -EINVAL; } - ret = of_address_to_resource(node, 0, &res); - if (ret) - return ret; - chip_data = kzalloc(sizeof(*chip_data), GFP_KERNEL); if (!chip_data) return -ENOMEM; - size = resource_size(&res); - intpol_num = size * 8; - chip_data->intpol_base = ioremap(res.start, size); - if (!chip_data->intpol_base) { - pr_err("mtk_sysirq: unable to map sysirq register\n"); - ret = -ENXIO; - goto out_free; + while (of_get_address(node, i++, NULL, NULL)) + nr_intpol_bases++; + + if (nr_intpol_bases == 0) { + pr_err("mtk_sysirq: base address not specified\n"); + ret = -EINVAL; + goto out_free_chip; + } + + chip_data->intpol_words = kcalloc(nr_intpol_bases, + sizeof(*chip_data->intpol_words), + GFP_KERNEL); + if (!chip_data->intpol_words) { + ret = -ENOMEM; + goto out_free_chip; + } + + chip_data->intpol_bases = kcalloc(nr_intpol_bases, + sizeof(*chip_data->intpol_bases), + GFP_KERNEL); + if (!chip_data->intpol_bases) { + ret = -ENOMEM; + goto out_free_intpol_words; + } + + for (i = 0; i < nr_intpol_bases; i++) { + struct resource res; + + ret = of_address_to_resource(node, i, &res); + size = resource_size(&res); + intpol_num += size * 8; + chip_data->intpol_words[i] = size / 4; + chip_data->intpol_bases[i] = of_iomap(node, i); + if (ret || !chip_data->intpol_bases[i]) { + pr_err("%s: couldn't map region %d\n", + node->full_name, i); + ret = -ENODEV; + goto out_free_intpol; + } + } + + chip_data->intpol_idx = kcalloc(intpol_num, + sizeof(*chip_data->intpol_idx), + GFP_KERNEL); + if (!chip_data->intpol_idx) { + ret = -ENOMEM; + goto out_free_intpol; + } + + chip_data->which_word = kcalloc(intpol_num, + sizeof(*chip_data->which_word), + GFP_KERNEL); + if (!chip_data->which_word) { + ret = -ENOMEM; + goto out_free_intpol_idx; + } + + /* + * assign an index of the intpol_bases for each irq + * to set it fast later + */ + for (i = 0; i < intpol_num ; i++) { + u32 word = i / 32, j; + + for (j = 0; word >= chip_data->intpol_words[j] ; j++) + word -= chip_data->intpol_words[j]; + + chip_data->intpol_idx[i] = j; + chip_data->which_word[i] = word; } domain = irq_domain_add_hierarchy(domain_parent, 0, intpol_num, node, &sysirq_domain_ops, chip_data); if (!domain) { ret = -ENOMEM; - goto out_unmap; + goto out_free_which_word; } spin_lock_init(&chip_data->lock); return 0; -out_unmap: - iounmap(chip_data->intpol_base); -out_free: +out_free_which_word: + kfree(chip_data->which_word); +out_free_intpol_idx: + kfree(chip_data->intpol_idx); +out_free_intpol: + for (i = 0; i < nr_intpol_bases; i++) + if (chip_data->intpol_bases[i]) + iounmap(chip_data->intpol_bases[i]); + kfree(chip_data->intpol_bases); +out_free_intpol_words: + kfree(chip_data->intpol_words); +out_free_chip: kfree(chip_data); return ret; } From 5e11d16c2e45bcc4dbaf414e95365b0f6005b342 Mon Sep 17 00:00:00 2001 From: Mars Cheng Date: Sun, 19 Mar 2017 23:26:24 +0800 Subject: [PATCH 08/28] irqchip/mtk-sysirq: Remove unnecessary barrier when configuring trigger This prevent unnecessary visibility when configuring trigger type Signed-off-by: Mars Cheng Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-mtk-sysirq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-mtk-sysirq.c b/drivers/irqchip/irq-mtk-sysirq.c index 9ba969cfad81..eeac512ec5a8 100644 --- a/drivers/irqchip/irq-mtk-sysirq.c +++ b/drivers/irqchip/irq-mtk-sysirq.c @@ -57,7 +57,7 @@ static int mtk_sysirq_set_type(struct irq_data *data, unsigned int type) value &= ~(1 << offset); } - writel(value, base + reg_index * 4); + writel_relaxed(value, base + reg_index * 4); data = data->parent_data; ret = data->chip->irq_set_type(data, type); From 8d3554b84af909549f0197ec6e68f24006a9d3f2 Mon Sep 17 00:00:00 2001 From: Hanjun Guo Date: Tue, 7 Mar 2017 20:39:59 +0800 Subject: [PATCH 09/28] irqchip/gic-v3-its: Keep the include header files in alphabetic order Rearrange header file includes in alphabetic order. Signed-off-by: Hanjun Guo [lorenzo.pieralisi@arm.com: fixed commit log] Signed-off-by: Lorenzo Pieralisi Tested-by: Ming Lei Tested-by: Wei Xu Tested-by: Sinan Kaya Cc: Tomasz Nowicki Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3-its.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index f77f840d2b5f..45ea193325d2 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -16,13 +16,13 @@ */ #include +#include #include #include #include #include #include #include -#include #include #include #include From 9ab460c2289dda9c5b37463f1509553f4b4b0215 Mon Sep 17 00:00:00 2001 From: Hanjun Guo Date: Tue, 7 Mar 2017 20:40:00 +0800 Subject: [PATCH 10/28] irqchip/gicv3-its: platform-msi: Refactor its_pmsi_prepare() By adding ACPI support for platform MSI, the gicv3 driver has to provide code to retrieve the dev id through ACPI instead of device tree bindings; given that its_pmsi_prepare() allows already to get the dev id but it is OF dependent, factor OF related code out into a single function to make its_pmsi_prepare() ready to be used with other firmware interfaces. Signed-off-by: Hanjun Guo [lorenzo.pieralisi@arm.com: rewrote commit log] Signed-off-by: Lorenzo Pieralisi Reviewed-by: Matthias Brugger Tested-by: Ming Lei Tested-by: Wei Xu Tested-by: Sinan Kaya Cc: Tomasz Nowicki Cc: Thomas Gleixner Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3-its-platform-msi.c | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3-its-platform-msi.c b/drivers/irqchip/irq-gic-v3-its-platform-msi.c index 470b4aa7d62c..3c94278fc867 100644 --- a/drivers/irqchip/irq-gic-v3-its-platform-msi.c +++ b/drivers/irqchip/irq-gic-v3-its-platform-msi.c @@ -24,15 +24,11 @@ static struct irq_chip its_pmsi_irq_chip = { .name = "ITS-pMSI", }; -static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, - int nvec, msi_alloc_info_t *info) +static int of_pmsi_get_dev_id(struct irq_domain *domain, struct device *dev, + u32 *dev_id) { - struct msi_domain_info *msi_info; - u32 dev_id; int ret, index = 0; - msi_info = msi_get_domain_info(domain->parent); - /* Suck the DeviceID out of the msi-parent property */ do { struct of_phandle_args args; @@ -43,11 +39,24 @@ static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, if (args.np == irq_domain_get_of_node(domain)) { if (WARN_ON(args.args_count != 1)) return -EINVAL; - dev_id = args.args[0]; + *dev_id = args.args[0]; break; } } while (!ret); + return ret; +} + +static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, + int nvec, msi_alloc_info_t *info) +{ + struct msi_domain_info *msi_info; + u32 dev_id; + int ret; + + msi_info = msi_get_domain_info(domain->parent); + + ret = of_pmsi_get_dev_id(domain, dev, &dev_id); if (ret) return ret; From 42677db9004f959f6f881c6c138d080c0923b5e2 Mon Sep 17 00:00:00 2001 From: Hanjun Guo Date: Tue, 7 Mar 2017 20:40:01 +0800 Subject: [PATCH 11/28] irqchip/gicv3-its: platform-msi: Refactor its_pmsi_init() to prepare for ACPI Introduce its_pmsi_init_one() to separate firmware dependent code (ie OF dependent code) and firmware agnostic code so that gic3-its code can be made to support other firmware bindings easily. Signed-off-by: Hanjun Guo [lorenzo.pieralisi@arm.com: rewrote commit log] Signed-off-by: Lorenzo Pieralisi Reviewed-by: Matthias Brugger Tested-by: Ming Lei Tested-by: Wei Xu Tested-by: Sinan Kaya Cc: Tomasz Nowicki Cc: Thomas Gleixner Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3-its-platform-msi.c | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3-its-platform-msi.c b/drivers/irqchip/irq-gic-v3-its-platform-msi.c index 3c94278fc867..3d9efd14a63f 100644 --- a/drivers/irqchip/irq-gic-v3-its-platform-msi.c +++ b/drivers/irqchip/irq-gic-v3-its-platform-msi.c @@ -82,34 +82,43 @@ static struct of_device_id its_device_id[] = { {}, }; -static int __init its_pmsi_init(void) +static int __init its_pmsi_init_one(struct fwnode_handle *fwnode, + const char *name) +{ + struct irq_domain *parent; + + parent = irq_find_matching_fwnode(fwnode, DOMAIN_BUS_NEXUS); + if (!parent || !msi_get_domain_info(parent)) { + pr_err("%s: unable to locate ITS domain\n", name); + return -ENXIO; + } + + if (!platform_msi_create_irq_domain(fwnode, &its_pmsi_domain_info, + parent)) { + pr_err("%s: unable to create platform domain\n", name); + return -ENXIO; + } + + pr_info("Platform MSI: %s domain created\n", name); + return 0; +} + +static void __init its_pmsi_of_init(void) { struct device_node *np; - struct irq_domain *parent; for (np = of_find_matching_node(NULL, its_device_id); np; np = of_find_matching_node(np, its_device_id)) { if (!of_property_read_bool(np, "msi-controller")) continue; - parent = irq_find_matching_host(np, DOMAIN_BUS_NEXUS); - if (!parent || !msi_get_domain_info(parent)) { - pr_err("%s: unable to locate ITS domain\n", - np->full_name); - continue; - } - - if (!platform_msi_create_irq_domain(of_node_to_fwnode(np), - &its_pmsi_domain_info, - parent)) { - pr_err("%s: unable to create platform domain\n", - np->full_name); - continue; - } - - pr_info("Platform MSI: %s domain created\n", np->full_name); + its_pmsi_init_one(of_node_to_fwnode(np), np->full_name); } +} +static int __init its_pmsi_init(void) +{ + its_pmsi_of_init(); return 0; } early_initcall(its_pmsi_init); From f785f7d2e37872a3aada7e4a399388bb68f43136 Mon Sep 17 00:00:00 2001 From: Hanjun Guo Date: Tue, 7 Mar 2017 20:40:02 +0800 Subject: [PATCH 12/28] irqchip/gicv3-its: platform-msi: Scan MADT to create platform msi domain With the introduction of its_pmsi_init_one(), support for ACPI firmware interface can be plugged into the gicv3 ITS driver. Add code to scan the MADT table to get the ITS entry(ies), then use the information to create the platform msi domain for devices connected to it, mirroring the ITS PCI MSI code path. Signed-off-by: Hanjun Guo [lorenzo.pieralisi@arm.com: rewrote commit log] Signed-off-by: Lorenzo Pieralisi Reviewed-by: Matthias Brugger Tested-by: Ming Lei Tested-by: Wei Xu Tested-by: Sinan Kaya Cc: Tomasz Nowicki Cc: Thomas Gleixner Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3-its-platform-msi.c | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/irqchip/irq-gic-v3-its-platform-msi.c b/drivers/irqchip/irq-gic-v3-its-platform-msi.c index 3d9efd14a63f..e4ba9f4cbd1a 100644 --- a/drivers/irqchip/irq-gic-v3-its-platform-msi.c +++ b/drivers/irqchip/irq-gic-v3-its-platform-msi.c @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include #include #include #include @@ -103,6 +104,41 @@ static int __init its_pmsi_init_one(struct fwnode_handle *fwnode, return 0; } +#ifdef CONFIG_ACPI +static int __init +its_pmsi_parse_madt(struct acpi_subtable_header *header, + const unsigned long end) +{ + struct acpi_madt_generic_translator *its_entry; + struct fwnode_handle *domain_handle; + const char *node_name; + int err = -ENXIO; + + its_entry = (struct acpi_madt_generic_translator *)header; + node_name = kasprintf(GFP_KERNEL, "ITS@0x%lx", + (long)its_entry->base_address); + domain_handle = iort_find_domain_token(its_entry->translation_id); + if (!domain_handle) { + pr_err("%s: Unable to locate ITS domain handle\n", node_name); + goto out; + } + + err = its_pmsi_init_one(domain_handle, node_name); + +out: + kfree(node_name); + return err; +} + +static void __init its_pmsi_acpi_init(void) +{ + acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR, + its_pmsi_parse_madt, 0); +} +#else +static inline void its_pmsi_acpi_init(void) { } +#endif + static void __init its_pmsi_of_init(void) { struct device_node *np; @@ -119,6 +155,7 @@ static void __init its_pmsi_of_init(void) static int __init its_pmsi_init(void) { its_pmsi_of_init(); + its_pmsi_acpi_init(); return 0; } early_initcall(its_pmsi_init); From 6943acf67311e888445998bdac7ac8f766612158 Mon Sep 17 00:00:00 2001 From: Hanjun Guo Date: Tue, 7 Mar 2017 20:40:07 +0800 Subject: [PATCH 13/28] platform-msi: Make platform_msi_create_device_domain() ACPI aware The irqdomain creation that is carried out in: platform_msi_create_device_domain() relies on the fwnode_handle interrupt controller token to associate the interrupt controller with a specific irqdomain. Current code relies on the OF layer to retrieve a fwnode_handle for the device representing the interrupt controller from its device->of_node pointer. This makes platform_msi_create_device_domain() DT specific whilst it really is not because after the merge of commit f94277af03ea ("of/platform: Initialise dev->fwnode appropriately") the fwnode_handle can easily be retrieved from the dev->fwnode pointer in a firmware agnostic way. Update platform_msi_create_device_domain() to retrieve the interrupt controller fwnode_handle from the dev->fwnode pointer so that it can be used seamlessly in ACPI and DT systems. Signed-off-by: Hanjun Guo Signed-off-by: Lorenzo Pieralisi Tested-by: Ming Lei Tested-by: Wei Xu Cc: Greg KH Cc: Thomas Gleixner Signed-off-by: Marc Zyngier --- drivers/base/platform-msi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/base/platform-msi.c b/drivers/base/platform-msi.c index 0fc7c4da7756..d35e9a20caf7 100644 --- a/drivers/base/platform-msi.c +++ b/drivers/base/platform-msi.c @@ -345,8 +345,7 @@ platform_msi_create_device_domain(struct device *dev, data->host_data = host_data; domain = irq_domain_create_hierarchy(dev->msi_domain, 0, nvec, - of_node_to_fwnode(dev->of_node), - ops, data); + dev->fwnode, ops, data); if (!domain) goto free_priv; From 2911c6d961b35fceaf1fdede3c55e11dc3423be1 Mon Sep 17 00:00:00 2001 From: Kefeng Wang Date: Tue, 7 Mar 2017 20:40:08 +0800 Subject: [PATCH 14/28] irqchip/mbigen: Drop module owner Module owner will be set by driver core, so drop it. Signed-off-by: Kefeng Wang Signed-off-by: Hanjun Guo Reviewed-by: Ma Jun Tested-by: Ming Lei Tested-by: Wei Xu Cc: Marc Zyngier Cc: Thomas Gleixner Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-mbigen.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c index 03b79b061d24..c01ab4100f9a 100644 --- a/drivers/irqchip/irq-mbigen.c +++ b/drivers/irqchip/irq-mbigen.c @@ -293,7 +293,6 @@ MODULE_DEVICE_TABLE(of, mbigen_of_match); static struct platform_driver mbigen_platform_driver = { .driver = { .name = "Hisilicon MBIGEN-V2", - .owner = THIS_MODULE, .of_match_table = mbigen_of_match, }, .probe = mbigen_device_probe, From 76e1f77f9c26ec96ce58f46cc74ad07c731bd7ba Mon Sep 17 00:00:00 2001 From: Kefeng Wang Date: Tue, 7 Mar 2017 20:40:09 +0800 Subject: [PATCH 15/28] irqchip/mbigen: Introduce mbigen_of_create_domain() Introduce mbigen_of_create_domain() to consolidate OF related code and prepare for ACPI later, no funtional change. Signed-off-by: Kefeng Wang Signed-off-by: Hanjun Guo Reviewed-by: Ma Jun Tested-by: Ming Lei Tested-by: Wei Xu Cc: Thomas Gleixner Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-mbigen.c | 43 +++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c index c01ab4100f9a..37564084ce84 100644 --- a/drivers/irqchip/irq-mbigen.c +++ b/drivers/irqchip/irq-mbigen.c @@ -236,27 +236,15 @@ static struct irq_domain_ops mbigen_domain_ops = { .free = irq_domain_free_irqs_common, }; -static int mbigen_device_probe(struct platform_device *pdev) +static int mbigen_of_create_domain(struct platform_device *pdev, + struct mbigen_device *mgn_chip) { - struct mbigen_device *mgn_chip; + struct device *parent; struct platform_device *child; struct irq_domain *domain; struct device_node *np; - struct device *parent; - struct resource *res; u32 num_pins; - mgn_chip = devm_kzalloc(&pdev->dev, sizeof(*mgn_chip), GFP_KERNEL); - if (!mgn_chip) - return -ENOMEM; - - mgn_chip->pdev = pdev; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - mgn_chip->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(mgn_chip->base)) - return PTR_ERR(mgn_chip->base); - for_each_child_of_node(pdev->dev.of_node, np) { if (!of_property_read_bool(np, "interrupt-controller")) continue; @@ -280,6 +268,31 @@ static int mbigen_device_probe(struct platform_device *pdev) return -ENOMEM; } + return 0; +} + +static int mbigen_device_probe(struct platform_device *pdev) +{ + struct mbigen_device *mgn_chip; + struct resource *res; + int err; + + mgn_chip = devm_kzalloc(&pdev->dev, sizeof(*mgn_chip), GFP_KERNEL); + if (!mgn_chip) + return -ENOMEM; + + mgn_chip->pdev = pdev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mgn_chip->base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (IS_ERR(mgn_chip->base)) + return PTR_ERR(mgn_chip->base); + + err = mbigen_of_create_domain(pdev, mgn_chip); + if (err) + return err; + platform_set_drvdata(pdev, mgn_chip); return 0; } From f907c515ffb06e6fd5e74397badd674f3c233418 Mon Sep 17 00:00:00 2001 From: Hanjun Guo Date: Tue, 28 Mar 2017 20:21:05 +0800 Subject: [PATCH 16/28] irqchip/mbigen: Add ACPI support With the preparation of platform msi support and interrupt producer in commit d44fa3d46079 ("ACPI: Add support for ResourceSource/IRQ domain mapping"), we can add mbigen ACPI support now. Now that the major framework changes are ready, we just need to add the ACPI probe code which creates the irqdomain for devices connecting to it. In order to create the irqdomain, we need to know the number of hw irqs as input which is provided by mbigen. In DT case, we are using "num-pins" property to describe it, and we will take advantage of that too using _DSD in ACPI as there is no standard way of describe it in ACPI way, also according to the _DSD rule described in Documentation/acpi/DSD-properties-rules.txt, it doesn't break the rules. The DSDT is represented as below: For mbigen, Device(MBI0) { Name(_HID, "HISI0152") Name(_UID, Zero) Name(_CRS, ResourceTemplate() { Memory32Fixed(ReadWrite, 0xa0080000, 0x10000) }) Name(_DSD, Package () { ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), Package () { Package () {"num-pins", 378} } }) } For devices, Device(SAS0) { Name(_HID, "HISIxxxx") Name(_UID, Zero) Name(_CRS, ResourceTemplate() { Memory32Fixed(ReadWrite, 0xb0030000, 0x10000) Interrupt(ResourceConsumer,..., "\_SB.MBI0") {12, ...} }) } So for the devices connected to the mbigen, as we clearly say that it refers to a specific interrupt controller (mbigen), we can get the virq from mbigen's irqdomain once it's created successfully. Signed-off-by: Hanjun Guo Signed-off-by: MaJun Cc: Al Stone Cc: Darren Hart Cc: Lorenzo Pieralisi Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-mbigen.c | 75 ++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c index 37564084ce84..061cdb8bde8b 100644 --- a/drivers/irqchip/irq-mbigen.c +++ b/drivers/irqchip/irq-mbigen.c @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +#include #include #include #include @@ -180,7 +181,7 @@ static int mbigen_domain_translate(struct irq_domain *d, unsigned long *hwirq, unsigned int *type) { - if (is_of_node(fwspec->fwnode)) { + if (is_of_node(fwspec->fwnode) || is_acpi_device_node(fwspec->fwnode)) { if (fwspec->param_count != 2) return -EINVAL; @@ -271,6 +272,58 @@ static int mbigen_of_create_domain(struct platform_device *pdev, return 0; } +#ifdef CONFIG_ACPI +static int mbigen_acpi_create_domain(struct platform_device *pdev, + struct mbigen_device *mgn_chip) +{ + struct irq_domain *domain; + u32 num_pins = 0; + int ret; + + /* + * "num-pins" is the total number of interrupt pins implemented in + * this mbigen instance, and mbigen is an interrupt controller + * connected to ITS converting wired interrupts into MSI, so we + * use "num-pins" to alloc MSI vectors which are needed by client + * devices connected to it. + * + * Here is the DSDT device node used for mbigen in firmware: + * Device(MBI0) { + * Name(_HID, "HISI0152") + * Name(_UID, Zero) + * Name(_CRS, ResourceTemplate() { + * Memory32Fixed(ReadWrite, 0xa0080000, 0x10000) + * }) + * + * Name(_DSD, Package () { + * ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), + * Package () { + * Package () {"num-pins", 378} + * } + * }) + * } + */ + ret = device_property_read_u32(&pdev->dev, "num-pins", &num_pins); + if (ret || num_pins == 0) + return -EINVAL; + + domain = platform_msi_create_device_domain(&pdev->dev, num_pins, + mbigen_write_msg, + &mbigen_domain_ops, + mgn_chip); + if (!domain) + return -ENOMEM; + + return 0; +} +#else +static inline int mbigen_acpi_create_domain(struct platform_device *pdev, + struct mbigen_device *mgn_chip) +{ + return -ENODEV; +} +#endif + static int mbigen_device_probe(struct platform_device *pdev) { struct mbigen_device *mgn_chip; @@ -289,9 +342,18 @@ static int mbigen_device_probe(struct platform_device *pdev) if (IS_ERR(mgn_chip->base)) return PTR_ERR(mgn_chip->base); - err = mbigen_of_create_domain(pdev, mgn_chip); - if (err) + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) + err = mbigen_of_create_domain(pdev, mgn_chip); + else if (ACPI_COMPANION(&pdev->dev)) + err = mbigen_acpi_create_domain(pdev, mgn_chip); + else + err = -EINVAL; + + if (err) { + dev_err(&pdev->dev, "Failed to create mbi-gen@%p irqdomain", + mgn_chip->base); return err; + } platform_set_drvdata(pdev, mgn_chip); return 0; @@ -303,10 +365,17 @@ static const struct of_device_id mbigen_of_match[] = { }; MODULE_DEVICE_TABLE(of, mbigen_of_match); +static const struct acpi_device_id mbigen_acpi_match[] = { + { "HISI0152", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, mbigen_acpi_match); + static struct platform_driver mbigen_platform_driver = { .driver = { .name = "Hisilicon MBIGEN-V2", .of_match_table = mbigen_of_match, + .acpi_match_table = ACPI_PTR(mbigen_acpi_match), }, .probe = mbigen_device_probe, }; From c2c8661fd717e2f29cd099ed213b565830b5cbcc Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Wed, 29 Mar 2017 15:13:07 +0100 Subject: [PATCH 17/28] irqchip/gic-v3-its: Add IORT hook for platform MSI support Getting hold of the DevID requires us to call iort_pmsi_get_dev_id(). Since iort_pmsi_get_dev_id() may or may not be implemented, we provide a weak function that acts as a stub. The weak function will be removed when the ACPI counterpart is merged. Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3-its-platform-msi.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v3-its-platform-msi.c b/drivers/irqchip/irq-gic-v3-its-platform-msi.c index e4ba9f4cbd1a..9e9dda33eb17 100644 --- a/drivers/irqchip/irq-gic-v3-its-platform-msi.c +++ b/drivers/irqchip/irq-gic-v3-its-platform-msi.c @@ -48,6 +48,11 @@ static int of_pmsi_get_dev_id(struct irq_domain *domain, struct device *dev, return ret; } +int __weak iort_pmsi_get_dev_id(struct device *dev, u32 *dev_id) +{ + return -1; +} + static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, int nvec, msi_alloc_info_t *info) { @@ -57,7 +62,10 @@ static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, msi_info = msi_get_domain_info(domain->parent); - ret = of_pmsi_get_dev_id(domain, dev, &dev_id); + if (dev->of_node) + ret = of_pmsi_get_dev_id(domain, dev, &dev_id); + else + ret = iort_pmsi_get_dev_id(dev, &dev_id); if (ret) return ret; From f59dd407bfc2f32ec2cf4476a2e582be03368397 Mon Sep 17 00:00:00 2001 From: Youlin Pei Date: Fri, 7 Apr 2017 16:06:35 +0800 Subject: [PATCH 18/28] dt-bindings: mtk-cirq: Add binding document This commit adds the device tree binding document for the mediatek cirq. Acked-by: Rob Herring Signed-off-by: Youlin Pei Signed-off-by: Marc Zyngier --- .../interrupt-controller/mediatek,cirq.txt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Documentation/devicetree/bindings/interrupt-controller/mediatek,cirq.txt diff --git a/Documentation/devicetree/bindings/interrupt-controller/mediatek,cirq.txt b/Documentation/devicetree/bindings/interrupt-controller/mediatek,cirq.txt new file mode 100644 index 000000000000..a7efdbc3de5b --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/mediatek,cirq.txt @@ -0,0 +1,35 @@ +* Mediatek 27xx cirq + +In Mediatek SOCs, the CIRQ is a low power interrupt controller designed to +work outside MCUSYS which comprises with Cortex-Ax cores,CCI and GIC. +The external interrupts (outside MCUSYS) will feed through CIRQ and connect +to GIC in MCUSYS. When CIRQ is enabled, it will record the edge-sensitive +interrupts and generate a pulse signal to parent interrupt controller when +flush command is executed. With CIRQ, MCUSYS can be completely turned off +to improve the system power consumption without losing interrupts. + +Required properties: +- compatible: should be one of + - "mediatek,mt2701-cirq" for mt2701 CIRQ + - "mediatek,mt8135-cirq" for mt8135 CIRQ + - "mediatek,mt8173-cirq" for mt8173 CIRQ + and "mediatek,cirq" as a fallback. +- interrupt-controller : Identifies the node as an interrupt controller. +- #interrupt-cells : Use the same format as specified by GIC in arm,gic.txt. +- interrupt-parent: phandle of irq parent for cirq. The parent must + use the same interrupt-cells format as GIC. +- reg: Physical base address of the cirq registers and length of memory + mapped region. +- mediatek,ext-irq-range: Identifies external irq number range in different + SOCs. + +Example: + cirq: interrupt-controller@10204000 { + compatible = "mediatek,mt2701-cirq", + "mediatek,mtk-cirq"; + interrupt-controller; + #interrupt-cells = <3>; + interrupt-parent = <&sysirq>; + reg = <0 0x10204000 0 0x400>; + mediatek,ext-irq-start = <32 200>; + }; From 9dbbbd33aafaf1f95b1cce940bdf4331cd8b822e Mon Sep 17 00:00:00 2001 From: Youlin Pei Date: Fri, 7 Apr 2017 16:06:36 +0800 Subject: [PATCH 19/28] irqchip: Add Mediatek mtk-cirq driver In Mediatek SOCs, the CIRQ is a low power interrupt controller designed to works outside MCUSYS which comprises with Cortex-Ax cores,CCI and GIC. The CIRQ controller is integrated in between MCUSYS( include Cortex-Ax, CCI and GIC ) and interrupt sources as the second level interrupt controller. The external interrupts which outside MCUSYS will feed through CIRQ then bypass to GIC. CIRQ can monitors all edge trigger interupts. When an edge interrupt is triggered, CIRQ can record the status and generate a pulse signal to GIC when flush command executed. When system enters sleep mode, MCUSYS will be turned off to improve power consumption, also GIC is power down. The edge trigger interrupts will be lost in this scenario without CIRQ. This commit provides the CIRQ irqchip implement. Signed-off-by: Youlin Pei Signed-off-by: Marc Zyngier --- drivers/irqchip/Makefile | 2 +- drivers/irqchip/irq-mtk-cirq.c | 306 +++++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 drivers/irqchip/irq-mtk-cirq.c diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index d80bc3e9d511..b64c59b838a0 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -61,7 +61,7 @@ obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o obj-$(CONFIG_BRCMSTB_L2_IRQ) += irq-brcmstb-l2.o obj-$(CONFIG_KEYSTONE_IRQ) += irq-keystone.o obj-$(CONFIG_MIPS_GIC) += irq-mips-gic.o -obj-$(CONFIG_ARCH_MEDIATEK) += irq-mtk-sysirq.o +obj-$(CONFIG_ARCH_MEDIATEK) += irq-mtk-sysirq.o irq-mtk-cirq.o obj-$(CONFIG_ARCH_DIGICOLOR) += irq-digicolor.o obj-$(CONFIG_RENESAS_H8300H_INTC) += irq-renesas-h8300h.o obj-$(CONFIG_RENESAS_H8S_INTC) += irq-renesas-h8s.o diff --git a/drivers/irqchip/irq-mtk-cirq.c b/drivers/irqchip/irq-mtk-cirq.c new file mode 100644 index 000000000000..18c65c16de28 --- /dev/null +++ b/drivers/irqchip/irq-mtk-cirq.c @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Youlin.Pei + * + * 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 + +#define CIRQ_ACK 0x40 +#define CIRQ_MASK_SET 0xc0 +#define CIRQ_MASK_CLR 0x100 +#define CIRQ_SENS_SET 0x180 +#define CIRQ_SENS_CLR 0x1c0 +#define CIRQ_POL_SET 0x240 +#define CIRQ_POL_CLR 0x280 +#define CIRQ_CONTROL 0x300 + +#define CIRQ_EN 0x1 +#define CIRQ_EDGE 0x2 +#define CIRQ_FLUSH 0x4 + +struct mtk_cirq_chip_data { + void __iomem *base; + unsigned int ext_irq_start; + unsigned int ext_irq_end; + struct irq_domain *domain; +}; + +static struct mtk_cirq_chip_data *cirq_data; + +static void mtk_cirq_write_mask(struct irq_data *data, unsigned int offset) +{ + struct mtk_cirq_chip_data *chip_data = data->chip_data; + unsigned int cirq_num = data->hwirq; + u32 mask = 1 << (cirq_num % 32); + + writel_relaxed(mask, chip_data->base + offset + (cirq_num / 32) * 4); +} + +static void mtk_cirq_mask(struct irq_data *data) +{ + mtk_cirq_write_mask(data, CIRQ_MASK_SET); + irq_chip_mask_parent(data); +} + +static void mtk_cirq_unmask(struct irq_data *data) +{ + mtk_cirq_write_mask(data, CIRQ_MASK_CLR); + irq_chip_unmask_parent(data); +} + +static int mtk_cirq_set_type(struct irq_data *data, unsigned int type) +{ + int ret; + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_FALLING: + mtk_cirq_write_mask(data, CIRQ_POL_CLR); + mtk_cirq_write_mask(data, CIRQ_SENS_CLR); + break; + case IRQ_TYPE_EDGE_RISING: + mtk_cirq_write_mask(data, CIRQ_POL_SET); + mtk_cirq_write_mask(data, CIRQ_SENS_CLR); + break; + case IRQ_TYPE_LEVEL_LOW: + mtk_cirq_write_mask(data, CIRQ_POL_CLR); + mtk_cirq_write_mask(data, CIRQ_SENS_SET); + break; + case IRQ_TYPE_LEVEL_HIGH: + mtk_cirq_write_mask(data, CIRQ_POL_SET); + mtk_cirq_write_mask(data, CIRQ_SENS_SET); + break; + default: + break; + } + + data = data->parent_data; + ret = data->chip->irq_set_type(data, type); + return ret; +} + +static struct irq_chip mtk_cirq_chip = { + .name = "MT_CIRQ", + .irq_mask = mtk_cirq_mask, + .irq_unmask = mtk_cirq_unmask, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_type = mtk_cirq_set_type, + .irq_retrigger = irq_chip_retrigger_hierarchy, +#ifdef CONFIG_SMP + .irq_set_affinity = irq_chip_set_affinity_parent, +#endif +}; + +static int mtk_cirq_domain_translate(struct irq_domain *d, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + if (is_of_node(fwspec->fwnode)) { + if (fwspec->param_count != 3) + return -EINVAL; + + /* No PPI should point to this domain */ + if (fwspec->param[0] != 0) + return -EINVAL; + + /* cirq support irq number check */ + if (fwspec->param[1] < cirq_data->ext_irq_start || + fwspec->param[1] > cirq_data->ext_irq_end) + return -EINVAL; + + *hwirq = fwspec->param[1] - cirq_data->ext_irq_start; + *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; + return 0; + } + + return -EINVAL; +} + +static int mtk_cirq_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + int ret; + irq_hw_number_t hwirq; + unsigned int type; + struct irq_fwspec *fwspec = arg; + struct irq_fwspec parent_fwspec = *fwspec; + + ret = mtk_cirq_domain_translate(domain, fwspec, &hwirq, &type); + if (ret) + return ret; + + if (WARN_ON(nr_irqs != 1)) + return -EINVAL; + + irq_domain_set_hwirq_and_chip(domain, virq, hwirq, + &mtk_cirq_chip, + domain->host_data); + + parent_fwspec.fwnode = domain->parent->fwnode; + return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, + &parent_fwspec); +} + +static const struct irq_domain_ops cirq_domain_ops = { + .translate = mtk_cirq_domain_translate, + .alloc = mtk_cirq_domain_alloc, + .free = irq_domain_free_irqs_common, +}; + +#ifdef CONFIG_PM_SLEEP +static int mtk_cirq_suspend(void) +{ + u32 value, mask; + unsigned int irq, hwirq_num; + bool pending, masked; + int i, pendret, maskret; + + /* + * When external interrupts happened, CIRQ will record the status + * even CIRQ is not enabled. When execute flush command, CIRQ will + * resend the signals according to the status. So if don't clear the + * status, CIRQ will resend the wrong signals. + * + * arch_suspend_disable_irqs() will be called before CIRQ suspend + * callback. If clear all the status simply, the external interrupts + * which happened between arch_suspend_disable_irqs and CIRQ suspend + * callback will be lost. Using following steps to avoid this issue; + * + * - Iterate over all the CIRQ supported interrupts; + * - For each interrupt, inspect its pending and masked status at GIC + * level; + * - If pending and unmasked, it happened between + * arch_suspend_disable_irqs and CIRQ suspend callback, don't ACK + * it. Otherwise, ACK it. + */ + hwirq_num = cirq_data->ext_irq_end - cirq_data->ext_irq_start + 1; + for (i = 0; i < hwirq_num; i++) { + irq = irq_find_mapping(cirq_data->domain, i); + if (irq) { + pendret = irq_get_irqchip_state(irq, + IRQCHIP_STATE_PENDING, + &pending); + + maskret = irq_get_irqchip_state(irq, + IRQCHIP_STATE_MASKED, + &masked); + + if (pendret == 0 && maskret == 0 && + (pending && !masked)) + continue; + } + + mask = 1 << (i % 32); + writel_relaxed(mask, cirq_data->base + CIRQ_ACK + (i / 32) * 4); + } + + /* set edge_only mode, record edge-triggerd interrupts */ + /* enable cirq */ + value = readl_relaxed(cirq_data->base + CIRQ_CONTROL); + value |= (CIRQ_EDGE | CIRQ_EN); + writel_relaxed(value, cirq_data->base + CIRQ_CONTROL); + + return 0; +} + +static void mtk_cirq_resume(void) +{ + u32 value; + + /* flush recored interrupts, will send signals to parent controller */ + value = readl_relaxed(cirq_data->base + CIRQ_CONTROL); + writel_relaxed(value | CIRQ_FLUSH, cirq_data->base + CIRQ_CONTROL); + + /* disable cirq */ + value = readl_relaxed(cirq_data->base + CIRQ_CONTROL); + value &= ~(CIRQ_EDGE | CIRQ_EN); + writel_relaxed(value, cirq_data->base + CIRQ_CONTROL); +} + +static struct syscore_ops mtk_cirq_syscore_ops = { + .suspend = mtk_cirq_suspend, + .resume = mtk_cirq_resume, +}; + +static void mtk_cirq_syscore_init(void) +{ + register_syscore_ops(&mtk_cirq_syscore_ops); +} +#else +static inline void mtk_cirq_syscore_init(void) {} +#endif + +static int __init mtk_cirq_of_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *domain, *domain_parent; + unsigned int irq_num; + int ret; + + domain_parent = irq_find_host(parent); + if (!domain_parent) { + pr_err("mtk_cirq: interrupt-parent not found\n"); + return -EINVAL; + } + + cirq_data = kzalloc(sizeof(*cirq_data), GFP_KERNEL); + if (!cirq_data) + return -ENOMEM; + + cirq_data->base = of_iomap(node, 0); + if (!cirq_data->base) { + pr_err("mtk_cirq: unable to map cirq register\n"); + ret = -ENXIO; + goto out_free; + } + + ret = of_property_read_u32_index(node, "mediatek,ext-irq-range", 0, + &cirq_data->ext_irq_start); + if (ret) + goto out_unmap; + + ret = of_property_read_u32_index(node, "mediatek,ext-irq-range", 1, + &cirq_data->ext_irq_end); + if (ret) + goto out_unmap; + + irq_num = cirq_data->ext_irq_end - cirq_data->ext_irq_start + 1; + domain = irq_domain_add_hierarchy(domain_parent, 0, + irq_num, node, + &cirq_domain_ops, cirq_data); + if (!domain) { + ret = -ENOMEM; + goto out_unmap; + } + cirq_data->domain = domain; + + mtk_cirq_syscore_init(); + + return 0; + +out_unmap: + iounmap(cirq_data->base); +out_free: + kfree(cirq_data); + return ret; +} + +IRQCHIP_DECLARE(mtk_cirq, "mediatek,mtk-cirq", mtk_cirq_of_init); From a50ac562ef48e81cd4c6805f10e14b6bacadab53 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 10 Apr 2017 16:50:11 +0200 Subject: [PATCH 20/28] irqchip/atmel-aic5: Handle suspend to RAM On sama5d2, VDD core may be cut while suspending to RAM. This means the AIC5 registers content is lost. Restore it at resume time. Signed-off-by: Alexandre Belloni Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-atmel-aic5.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-atmel-aic5.c b/drivers/irqchip/irq-atmel-aic5.c index 2a624d87a035..c04ee9a23d09 100644 --- a/drivers/irqchip/irq-atmel-aic5.c +++ b/drivers/irqchip/irq-atmel-aic5.c @@ -150,6 +150,8 @@ static int aic5_set_type(struct irq_data *d, unsigned type) } #ifdef CONFIG_PM +static u32 *smr_cache; + static void aic5_suspend(struct irq_data *d) { struct irq_domain *domain = d->domain; @@ -159,6 +161,12 @@ static void aic5_suspend(struct irq_data *d) int i; u32 mask; + if (smr_cache) + for (i = 0; i < domain->revmap_size; i++) { + irq_reg_writel(bgc, i, AT91_AIC5_SSR); + smr_cache[i] = irq_reg_readl(bgc, AT91_AIC5_SMR); + } + irq_gc_lock(bgc); for (i = 0; i < dgc->irqs_per_chip; i++) { mask = 1 << i; @@ -184,9 +192,21 @@ static void aic5_resume(struct irq_data *d) u32 mask; irq_gc_lock(bgc); + + if (smr_cache) { + irq_reg_writel(bgc, 0xffffffff, AT91_AIC5_SPU); + for (i = 0; i < domain->revmap_size; i++) { + irq_reg_writel(bgc, i, AT91_AIC5_SSR); + irq_reg_writel(bgc, i, AT91_AIC5_SVR); + irq_reg_writel(bgc, smr_cache[i], AT91_AIC5_SMR); + } + } + for (i = 0; i < dgc->irqs_per_chip; i++) { mask = 1 << i; - if ((mask & gc->mask_cache) == (mask & gc->wake_active)) + + if (!smr_cache && + ((mask & gc->mask_cache) == (mask & gc->wake_active))) continue; irq_reg_writel(bgc, i + gc->irq_base, AT91_AIC5_SSR); @@ -342,6 +362,13 @@ static int __init aic5_of_init(struct device_node *node, static int __init sama5d2_aic5_of_init(struct device_node *node, struct device_node *parent) { +#ifdef CONFIG_PM + smr_cache = kcalloc(DIV_ROUND_UP(NR_SAMA5D2_IRQS, 32) * 32, + sizeof(*smr_cache), GFP_KERNEL); + if (!smr_cache) + return -ENOMEM; +#endif + return aic5_of_init(node, parent, NR_SAMA5D2_IRQS); } IRQCHIP_DECLARE(sama5d2_aic5, "atmel,sama5d2-aic", sama5d2_aic5_of_init); From 9d4b5bdc5b34e3e89e84d7cf62a8e513b25a8905 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Tue, 11 Apr 2017 09:48:09 -0700 Subject: [PATCH 21/28] irqchip/irq-imx-gpcv2: Clear OF_POPULATED flag Clear OF_POPULATED flag, so that GPC power domain driver[1] can be bound to "gpc" node as well. [1] https://lkml.org/lkml/2017/3/28/835 Cc: yurovsky@gmail.com Cc: Thomas Gleixner Cc: Jason Cooper Signed-off-by: Andrey Smirnov Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-imx-gpcv2.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/irqchip/irq-imx-gpcv2.c b/drivers/irqchip/irq-imx-gpcv2.c index 15af9a9753e5..e13236f95236 100644 --- a/drivers/irqchip/irq-imx-gpcv2.c +++ b/drivers/irqchip/irq-imx-gpcv2.c @@ -266,6 +266,11 @@ static int __init imx_gpcv2_irqchip_init(struct device_node *node, imx_gpcv2_instance = cd; register_syscore_ops(&imx_gpcv2_syscore_ops); + /* + * Clear the OF_POPULATED flag set in of_irq_init so that + * later the GPC power domain driver will not be skipped. + */ + of_node_clear_flag(node, OF_POPULATED); return 0; } From f7e30f01a9e221067bb4b579e3cfc25cd2617467 Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Wed, 12 Apr 2017 11:20:29 -0700 Subject: [PATCH 22/28] cpumask: Add helper cpumask_available() With CONFIG_CPUMASK_OFFSTACK=y cpumask_var_t is a struct cpumask pointer, otherwise a struct cpumask array with a single element. Some code dealing with cpumasks needs to validate that a cpumask_var_t is not a NULL pointer when CONFIG_CPUMASK_OFFSTACK=y. This is typically done by performing the check always, regardless of the underlying type of cpumask_var_t. This works in both cases, however clang raises a warning like this when CONFIG_CPUMASK_OFFSTACK=n: kernel/irq/manage.c:839:28: error: address of array 'desc->irq_common_data.affinity' will always evaluate to 'true' [-Werror,-Wpointer-bool-conversion] Add the inline helper cpumask_available() which only performs the pointer check if CONFIG_CPUMASK_OFFSTACK=y. Signed-off-by: Matthias Kaehlcke Cc: Grant Grundler Cc: Rusty Russell Cc: Greg Hackmann Cc: Michael Davidson Cc: Andrew Morton Link: http://lkml.kernel.org/r/20170412182030.83657-1-mka@chromium.org Signed-off-by: Thomas Gleixner --- include/linux/cpumask.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/linux/cpumask.h b/include/linux/cpumask.h index 96f1e88b767c..1a675604b17d 100644 --- a/include/linux/cpumask.h +++ b/include/linux/cpumask.h @@ -667,6 +667,11 @@ void alloc_bootmem_cpumask_var(cpumask_var_t *mask); void free_cpumask_var(cpumask_var_t mask); void free_bootmem_cpumask_var(cpumask_var_t mask); +static inline bool cpumask_available(cpumask_var_t mask) +{ + return mask != NULL; +} + #else typedef struct cpumask cpumask_var_t[1]; @@ -708,6 +713,11 @@ static inline void free_cpumask_var(cpumask_var_t mask) static inline void free_bootmem_cpumask_var(cpumask_var_t mask) { } + +static inline bool cpumask_available(cpumask_var_t mask) +{ + return true; +} #endif /* CONFIG_CPUMASK_OFFSTACK */ /* It's common to want to use cpu_all_mask in struct member initializers, From d170fe7dd992b313d4851ae5ab77ee7a51ed8c72 Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Wed, 12 Apr 2017 11:20:30 -0700 Subject: [PATCH 23/28] genirq: Use cpumask_available() for check of cpumask variable This fixes the following clang warning when CONFIG_CPUMASK_OFFSTACK=n: kernel/irq/manage.c:839:28: error: address of array 'desc->irq_common_data.affinity' will always evaluate to 'true' [-Werror,-Wpointer-bool-conversion] Signed-off-by: Matthias Kaehlcke Cc: Grant Grundler Cc: Rusty Russell Cc: Greg Hackmann Cc: Michael Davidson Cc: Andrew Morton Link: http://lkml.kernel.org/r/20170412182030.83657-2-mka@chromium.org Signed-off-by: Thomas Gleixner --- kernel/irq/manage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index a4afe5cc5af1..155e3c3357a1 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -852,7 +852,7 @@ irq_thread_check_affinity(struct irq_desc *desc, struct irqaction *action) * This code is triggered unconditionally. Check the affinity * mask pointer. For CPU_MASK_OFFSTACK=n this is optimized out. */ - if (desc->irq_common_data.affinity) + if (cpumask_available(desc->irq_common_data.affinity)) cpumask_copy(mask, desc->irq_common_data.affinity); else valid = false; From 382bd4de61827dbaaf5fb4fb7b1f4be4a86505e7 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 15 Apr 2017 12:08:31 +0200 Subject: [PATCH 24/28] genirq: Use irqd_get_trigger_type to compare the trigger type for shared IRQs When requesting a shared irq with IRQF_TRIGGER_NONE then the irqaction flags get filled with the trigger type from the irq_data: if (!(new->flags & IRQF_TRIGGER_MASK)) new->flags |= irqd_get_trigger_type(&desc->irq_data); On the first setup_irq() the trigger type in irq_data is NONE when the above code executes, then the irq is started up for the first time and then the actual trigger type gets established, but that's too late to fix up new->flags. When then a second user of the irq requests the irq with IRQF_TRIGGER_NONE its irqaction's triggertype gets set to the actual trigger type and the following check fails: if (!((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) Resulting in the request_irq failing with -EBUSY even though both users requested the irq with IRQF_SHARED | IRQF_TRIGGER_NONE Fix this by comparing the new irqaction's trigger type to the trigger type stored in the irq_data which correctly reflects the actual trigger type being used for the irq. Suggested-by: Thomas Gleixner Signed-off-by: Hans de Goede Acked-by: Marc Zyngier Link: http://lkml.kernel.org/r/20170415100831.17073-1-hdegoede@redhat.com Signed-off-by: Thomas Gleixner --- kernel/irq/manage.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 155e3c3357a1..ae1c90f20381 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -1212,8 +1212,10 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new) * set the trigger type must match. Also all must * agree on ONESHOT. */ + unsigned int oldtype = irqd_get_trigger_type(&desc->irq_data); + if (!((old->flags & new->flags) & IRQF_SHARED) || - ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) || + (oldtype != (new->flags & IRQF_TRIGGER_MASK)) || ((old->flags ^ new->flags) & IRQF_ONESHOT)) goto mismatch; From f8dcd9e81797ae24acc44c84f0eb3b9e6cee9791 Mon Sep 17 00:00:00 2001 From: Paul Burton Date: Thu, 20 Apr 2017 10:07:34 +0100 Subject: [PATCH 25/28] irqchip/mips-gic: Separate IPI reservation & usage tracking Since commit 2af70a962070 ("irqchip/mips-gic: Add a IPI hierarchy domain") introduced the GIC IPI IRQ domain we have tracked both reservation of interrupts & their use with a single bitmap - ipi_resrv. If an interrupt is reserved for use as an IPI but not actually in use then the appropriate bit is set in ipi_resrv. If an interrupt is either not reserved for use as an IPI or has been allocated as one then the appropriate bit is clear in ipi_resrv. Unfortunately this means that checking whether a bit is set in ipi_resrv to prevent IPI interrupts being allocated for use with a device is broken, because if the interrupt has been allocated as an IPI first then its bit will be clear. Fix this by separating the tracking of IPI reservation & usage, introducing a separate ipi_available bitmap for the latter. This means that ipi_resrv will now always have bits set corresponding to all interrupts reserved for use as IPIs, whether or not they have been allocated yet, and therefore that checking it when allocating device interrupts works as expected. Fixes: 2af70a962070 ("irqchip/mips-gic: Add a IPI hierarchy domain") Signed-off-by: Paul Burton Signed-off-by: Matt Redfearn Cc: linux-mips@linux-mips.org Cc: Jason Cooper Cc: Marc Zyngier Cc: Ralf Baechle Link: http://lkml.kernel.org/r/1492679256-14513-2-git-send-email-matt.redfearn@imgtec.com Signed-off-by: Thomas Gleixner --- drivers/irqchip/irq-mips-gic.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c index cd20df12d63d..db058e10df00 100644 --- a/drivers/irqchip/irq-mips-gic.c +++ b/drivers/irqchip/irq-mips-gic.c @@ -55,6 +55,7 @@ static unsigned int gic_cpu_pin; static unsigned int timer_cpu_pin; static struct irq_chip gic_level_irq_controller, gic_edge_irq_controller; DECLARE_BITMAP(ipi_resrv, GIC_MAX_INTRS); +DECLARE_BITMAP(ipi_available, GIC_MAX_INTRS); static void __gic_irq_dispatch(void); @@ -746,17 +747,17 @@ static int gic_irq_domain_alloc(struct irq_domain *d, unsigned int virq, return gic_setup_dev_chip(d, virq, spec->hwirq); } else { - base_hwirq = find_first_bit(ipi_resrv, gic_shared_intrs); + base_hwirq = find_first_bit(ipi_available, gic_shared_intrs); if (base_hwirq == gic_shared_intrs) { return -ENOMEM; } /* check that we have enough space */ for (i = base_hwirq; i < nr_irqs; i++) { - if (!test_bit(i, ipi_resrv)) + if (!test_bit(i, ipi_available)) return -EBUSY; } - bitmap_clear(ipi_resrv, base_hwirq, nr_irqs); + bitmap_clear(ipi_available, base_hwirq, nr_irqs); /* map the hwirq for each cpu consecutively */ i = 0; @@ -787,7 +788,7 @@ static int gic_irq_domain_alloc(struct irq_domain *d, unsigned int virq, return 0; error: - bitmap_set(ipi_resrv, base_hwirq, nr_irqs); + bitmap_set(ipi_available, base_hwirq, nr_irqs); return ret; } @@ -802,7 +803,7 @@ void gic_irq_domain_free(struct irq_domain *d, unsigned int virq, return; base_hwirq = GIC_HWIRQ_TO_SHARED(irqd_to_hwirq(data)); - bitmap_set(ipi_resrv, base_hwirq, nr_irqs); + bitmap_set(ipi_available, base_hwirq, nr_irqs); } int gic_irq_domain_match(struct irq_domain *d, struct device_node *node, @@ -1098,6 +1099,7 @@ static void __init __gic_init(unsigned long gic_base_addr, 2 * gic_vpes); } + bitmap_copy(ipi_available, ipi_resrv, GIC_MAX_INTRS); gic_basic_init(); gic_map_interrupts(node); } From b87281e7f205eda08c911fdacd27d4d2f01daa09 Mon Sep 17 00:00:00 2001 From: Paul Burton Date: Thu, 20 Apr 2017 10:07:35 +0100 Subject: [PATCH 26/28] irqchip/mips-gic: Remove device IRQ domain In commit c98c1822ee13 ("irqchip/mips-gic: Add device hierarchy domain") Qais indicates that he felt having a separate device IRQ domain was cleaner, but along with everyone else I'm aware of touching this driver I disagree. Remove the separate device IRQ domain so that we simply have the main GIC IRQ domain used for devices, and an IPI IRQ domain as a child. The logic for handling the device interrupts & IPIs is cleanly separated into the appropriate domain ops, making it much easier to reason about what the driver is doing than the previous approach where the 2 child domains had to call up to their parent, which had to handle both types of interrupt & had all sorts of weird & wonderful duplication or outright clobbering of setup performed by multiple domains. Signed-off-by: Paul Burton Signed-off-by: Matt Redfearn Cc: linux-mips@linux-mips.org Cc: Jason Cooper Cc: Marc Zyngier Cc: Ralf Baechle Link: http://lkml.kernel.org/r/1492679256-14513-3-git-send-email-matt.redfearn@imgtec.com Signed-off-by: Thomas Gleixner --- drivers/irqchip/irq-mips-gic.c | 290 +++++++++++---------------------- 1 file changed, 93 insertions(+), 197 deletions(-) diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c index db058e10df00..eb448a1d57b4 100644 --- a/drivers/irqchip/irq-mips-gic.c +++ b/drivers/irqchip/irq-mips-gic.c @@ -29,25 +29,12 @@ struct gic_pcpu_mask { DECLARE_BITMAP(pcpu_mask, GIC_MAX_INTRS); }; -struct gic_irq_spec { - enum { - GIC_DEVICE, - GIC_IPI - } type; - - union { - struct cpumask *ipimask; - unsigned int hwirq; - }; -}; - static unsigned long __gic_base_addr; static void __iomem *gic_base; static struct gic_pcpu_mask pcpu_masks[NR_CPUS]; static DEFINE_SPINLOCK(gic_lock); static struct irq_domain *gic_irq_domain; -static struct irq_domain *gic_dev_domain; static struct irq_domain *gic_ipi_domain; static int gic_shared_intrs; static int gic_vpes; @@ -694,132 +681,7 @@ static int gic_shared_irq_domain_map(struct irq_domain *d, unsigned int virq, return 0; } -static int gic_setup_dev_chip(struct irq_domain *d, unsigned int virq, - unsigned int hwirq) -{ - struct irq_chip *chip; - int err; - - if (hwirq >= GIC_SHARED_HWIRQ_BASE) { - err = irq_domain_set_hwirq_and_chip(d, virq, hwirq, - &gic_level_irq_controller, - NULL); - } else { - switch (GIC_HWIRQ_TO_LOCAL(hwirq)) { - case GIC_LOCAL_INT_TIMER: - case GIC_LOCAL_INT_PERFCTR: - case GIC_LOCAL_INT_FDC: - /* - * HACK: These are all really percpu interrupts, but - * the rest of the MIPS kernel code does not use the - * percpu IRQ API for them. - */ - chip = &gic_all_vpes_local_irq_controller; - irq_set_handler(virq, handle_percpu_irq); - break; - - default: - chip = &gic_local_irq_controller; - irq_set_handler(virq, handle_percpu_devid_irq); - irq_set_percpu_devid(virq); - break; - } - - err = irq_domain_set_hwirq_and_chip(d, virq, hwirq, - chip, NULL); - } - - return err; -} - -static int gic_irq_domain_alloc(struct irq_domain *d, unsigned int virq, - unsigned int nr_irqs, void *arg) -{ - struct gic_irq_spec *spec = arg; - irq_hw_number_t hwirq, base_hwirq; - int cpu, ret, i; - - if (spec->type == GIC_DEVICE) { - /* verify that shared irqs don't conflict with an IPI irq */ - if ((spec->hwirq >= GIC_SHARED_HWIRQ_BASE) && - test_bit(GIC_HWIRQ_TO_SHARED(spec->hwirq), ipi_resrv)) - return -EBUSY; - - return gic_setup_dev_chip(d, virq, spec->hwirq); - } else { - base_hwirq = find_first_bit(ipi_available, gic_shared_intrs); - if (base_hwirq == gic_shared_intrs) { - return -ENOMEM; - } - - /* check that we have enough space */ - for (i = base_hwirq; i < nr_irqs; i++) { - if (!test_bit(i, ipi_available)) - return -EBUSY; - } - bitmap_clear(ipi_available, base_hwirq, nr_irqs); - - /* map the hwirq for each cpu consecutively */ - i = 0; - for_each_cpu(cpu, spec->ipimask) { - hwirq = GIC_SHARED_TO_HWIRQ(base_hwirq + i); - - ret = irq_domain_set_hwirq_and_chip(d, virq + i, hwirq, - &gic_level_irq_controller, - NULL); - if (ret) - goto error; - - irq_set_handler(virq + i, handle_level_irq); - - ret = gic_shared_irq_domain_map(d, virq + i, hwirq, cpu); - if (ret) - goto error; - - i++; - } - - /* - * tell the parent about the base hwirq we allocated so it can - * set its own domain data - */ - spec->hwirq = base_hwirq; - } - - return 0; -error: - bitmap_set(ipi_available, base_hwirq, nr_irqs); - return ret; -} - -void gic_irq_domain_free(struct irq_domain *d, unsigned int virq, - unsigned int nr_irqs) -{ - irq_hw_number_t base_hwirq; - struct irq_data *data; - - data = irq_get_irq_data(virq); - if (!data) - return; - - base_hwirq = GIC_HWIRQ_TO_SHARED(irqd_to_hwirq(data)); - bitmap_set(ipi_available, base_hwirq, nr_irqs); -} - -int gic_irq_domain_match(struct irq_domain *d, struct device_node *node, - enum irq_domain_bus_token bus_token) -{ - /* this domain should'nt be accessed directly */ - return 0; -} - -static const struct irq_domain_ops gic_irq_domain_ops = { - .alloc = gic_irq_domain_alloc, - .free = gic_irq_domain_free, - .match = gic_irq_domain_match, -}; - -static int gic_dev_domain_xlate(struct irq_domain *d, struct device_node *ctrlr, +static int gic_irq_domain_xlate(struct irq_domain *d, struct device_node *ctrlr, const u32 *intspec, unsigned int intsize, irq_hw_number_t *out_hwirq, unsigned int *out_type) @@ -838,58 +700,73 @@ static int gic_dev_domain_xlate(struct irq_domain *d, struct device_node *ctrlr, return 0; } -static int gic_dev_domain_alloc(struct irq_domain *d, unsigned int virq, +static int gic_irq_domain_alloc(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs, void *arg) { struct irq_fwspec *fwspec = arg; - struct gic_irq_spec spec = { - .type = GIC_DEVICE, - }; - int i, ret; + irq_hw_number_t hwirq; + int err; - if (fwspec->param[0] == GIC_SHARED) - spec.hwirq = GIC_SHARED_TO_HWIRQ(fwspec->param[1]); - else - spec.hwirq = GIC_LOCAL_TO_HWIRQ(fwspec->param[1]); + if (fwspec->param[0] == GIC_SHARED) { + hwirq = GIC_SHARED_TO_HWIRQ(fwspec->param[1]); - ret = irq_domain_alloc_irqs_parent(d, virq, nr_irqs, &spec); - if (ret) - return ret; + /* verify that shared irqs don't conflict with an IPI irq */ + if (test_bit(GIC_HWIRQ_TO_SHARED(hwirq), ipi_resrv)) + return -EBUSY; - for (i = 0; i < nr_irqs; i++) { - ret = gic_setup_dev_chip(d, virq + i, spec.hwirq + i); - if (ret) - goto error; + err = irq_domain_set_hwirq_and_chip(d, virq, hwirq, + &gic_level_irq_controller, + NULL); + if (err) + return err; + + return gic_shared_irq_domain_map(d, virq, hwirq, 0); } - return 0; + hwirq = GIC_LOCAL_TO_HWIRQ(fwspec->param[1]); -error: - irq_domain_free_irqs_parent(d, virq, nr_irqs); - return ret; + switch (GIC_HWIRQ_TO_LOCAL(hwirq)) { + case GIC_LOCAL_INT_TIMER: + case GIC_LOCAL_INT_PERFCTR: + case GIC_LOCAL_INT_FDC: + /* + * HACK: These are all really percpu interrupts, but + * the rest of the MIPS kernel code does not use the + * percpu IRQ API for them. + */ + err = irq_domain_set_hwirq_and_chip(d, virq, hwirq, + &gic_all_vpes_local_irq_controller, + NULL); + if (err) + return err; + + irq_set_handler(virq, handle_percpu_irq); + break; + + default: + err = irq_domain_set_hwirq_and_chip(d, virq, hwirq, + &gic_local_irq_controller, + NULL); + if (err) + return err; + + irq_set_handler(virq, handle_percpu_devid_irq); + irq_set_percpu_devid(virq); + break; + } + + return gic_local_irq_domain_map(d, virq, hwirq); } -void gic_dev_domain_free(struct irq_domain *d, unsigned int virq, +void gic_irq_domain_free(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs) { - /* no real allocation is done for dev irqs, so no need to free anything */ - return; } -static void gic_dev_domain_activate(struct irq_domain *domain, - struct irq_data *d) -{ - if (GIC_HWIRQ_TO_LOCAL(d->hwirq) < GIC_NUM_LOCAL_INTRS) - gic_local_irq_domain_map(domain, d->irq, d->hwirq); - else - gic_shared_irq_domain_map(domain, d->irq, d->hwirq, 0); -} - -static struct irq_domain_ops gic_dev_domain_ops = { - .xlate = gic_dev_domain_xlate, - .alloc = gic_dev_domain_alloc, - .free = gic_dev_domain_free, - .activate = gic_dev_domain_activate, +static const struct irq_domain_ops gic_irq_domain_ops = { + .xlate = gic_irq_domain_xlate, + .alloc = gic_irq_domain_alloc, + .free = gic_irq_domain_free, }; static int gic_ipi_domain_xlate(struct irq_domain *d, struct device_node *ctrlr, @@ -911,20 +788,32 @@ static int gic_ipi_domain_alloc(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs, void *arg) { struct cpumask *ipimask = arg; - struct gic_irq_spec spec = { - .type = GIC_IPI, - .ipimask = ipimask - }; - int ret, i; + irq_hw_number_t hwirq, base_hwirq; + int cpu, ret, i; - ret = irq_domain_alloc_irqs_parent(d, virq, nr_irqs, &spec); - if (ret) - return ret; + base_hwirq = find_first_bit(ipi_available, gic_shared_intrs); + if (base_hwirq == gic_shared_intrs) + return -ENOMEM; - /* the parent should have set spec.hwirq to the base_hwirq it allocated */ - for (i = 0; i < nr_irqs; i++) { - ret = irq_domain_set_hwirq_and_chip(d, virq + i, - GIC_SHARED_TO_HWIRQ(spec.hwirq + i), + /* check that we have enough space */ + for (i = base_hwirq; i < nr_irqs; i++) { + if (!test_bit(i, ipi_available)) + return -EBUSY; + } + bitmap_clear(ipi_available, base_hwirq, nr_irqs); + + /* map the hwirq for each cpu consecutively */ + i = 0; + for_each_cpu(cpu, ipimask) { + hwirq = GIC_SHARED_TO_HWIRQ(base_hwirq + i); + + ret = irq_domain_set_hwirq_and_chip(d, virq + i, hwirq, + &gic_edge_irq_controller, + NULL); + if (ret) + goto error; + + ret = irq_domain_set_hwirq_and_chip(d->parent, virq + i, hwirq, &gic_edge_irq_controller, NULL); if (ret) @@ -933,18 +822,32 @@ static int gic_ipi_domain_alloc(struct irq_domain *d, unsigned int virq, ret = irq_set_irq_type(virq + i, IRQ_TYPE_EDGE_RISING); if (ret) goto error; + + ret = gic_shared_irq_domain_map(d, virq + i, hwirq, cpu); + if (ret) + goto error; + + i++; } return 0; error: - irq_domain_free_irqs_parent(d, virq, nr_irqs); + bitmap_set(ipi_available, base_hwirq, nr_irqs); return ret; } void gic_ipi_domain_free(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs) { - irq_domain_free_irqs_parent(d, virq, nr_irqs); + irq_hw_number_t base_hwirq; + struct irq_data *data; + + data = irq_get_irq_data(virq); + if (!data) + return; + + base_hwirq = GIC_HWIRQ_TO_SHARED(irqd_to_hwirq(data)); + bitmap_set(ipi_available, base_hwirq, nr_irqs); } int gic_ipi_domain_match(struct irq_domain *d, struct device_node *node, @@ -1072,13 +975,6 @@ static void __init __gic_init(unsigned long gic_base_addr, panic("Failed to add GIC IRQ domain"); gic_irq_domain->name = "mips-gic-irq"; - gic_dev_domain = irq_domain_add_hierarchy(gic_irq_domain, 0, - GIC_NUM_LOCAL_INTRS + gic_shared_intrs, - node, &gic_dev_domain_ops, NULL); - if (!gic_dev_domain) - panic("Failed to add GIC DEV domain"); - gic_dev_domain->name = "mips-gic-dev"; - gic_ipi_domain = irq_domain_add_hierarchy(gic_irq_domain, IRQ_DOMAIN_FLAG_IPI_PER_CPU, GIC_NUM_LOCAL_INTRS + gic_shared_intrs, From 8ada00a650ec7ec639fb72964f0e4eba928786f8 Mon Sep 17 00:00:00 2001 From: Matt Redfearn Date: Thu, 20 Apr 2017 10:07:36 +0100 Subject: [PATCH 27/28] irqchip/mips-gic: Replace static map with dynamic Commit 4cfffcfa5106 ("irqchip/mips-gic: Fix local interrupts") fixed local interrupts by creating virq mappings for them all at startup. Unfortunately this change broke legacy IRQ controllers in the same system, such as the i8259 on the Malta platform, as it allocates virq numbers that were expected to be available for the legacy controller. Instead of creating the mappings statically when the GIC is probed, re-introduce the irq domain .map function, removed by commit e875bd66dfb ("irqchip/mips-gic: Fix local interrupts") and use it to set up the irq handler and chip. Since a good deal of the required functionality is already implemented by gic_irq_domain_alloc, repurpose that function for gic_irq_domain_map and add a new gic_irq_domain_alloc which wraps gic_irq_domain_map with the necessary conversion. This change fixes the legacy interrupt controller of the Malta platform without breaking the perf interrupt fixed by commit e875bd66dfb ("irqchip/mips-gic: Fix local interrupts"). Signed-off-by: Matt Redfearn Cc: linux-mips@linux-mips.org Cc: Jason Cooper Cc: Marc Zyngier Cc: Ralf Baechle Link: http://lkml.kernel.org/r/1492679256-14513-4-git-send-email-matt.redfearn@imgtec.com Signed-off-by: Thomas Gleixner --- drivers/irqchip/irq-mips-gic.c | 60 ++++++++++------------------------ 1 file changed, 18 insertions(+), 42 deletions(-) diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c index eb448a1d57b4..eb7fbe159963 100644 --- a/drivers/irqchip/irq-mips-gic.c +++ b/drivers/irqchip/irq-mips-gic.c @@ -700,16 +700,12 @@ static int gic_irq_domain_xlate(struct irq_domain *d, struct device_node *ctrlr, return 0; } -static int gic_irq_domain_alloc(struct irq_domain *d, unsigned int virq, - unsigned int nr_irqs, void *arg) +static int gic_irq_domain_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hwirq) { - struct irq_fwspec *fwspec = arg; - irq_hw_number_t hwirq; int err; - if (fwspec->param[0] == GIC_SHARED) { - hwirq = GIC_SHARED_TO_HWIRQ(fwspec->param[1]); - + if (hwirq >= GIC_SHARED_HWIRQ_BASE) { /* verify that shared irqs don't conflict with an IPI irq */ if (test_bit(GIC_HWIRQ_TO_SHARED(hwirq), ipi_resrv)) return -EBUSY; @@ -723,8 +719,6 @@ static int gic_irq_domain_alloc(struct irq_domain *d, unsigned int virq, return gic_shared_irq_domain_map(d, virq, hwirq, 0); } - hwirq = GIC_LOCAL_TO_HWIRQ(fwspec->param[1]); - switch (GIC_HWIRQ_TO_LOCAL(hwirq)) { case GIC_LOCAL_INT_TIMER: case GIC_LOCAL_INT_PERFCTR: @@ -758,6 +752,20 @@ static int gic_irq_domain_alloc(struct irq_domain *d, unsigned int virq, return gic_local_irq_domain_map(d, virq, hwirq); } +static int gic_irq_domain_alloc(struct irq_domain *d, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + struct irq_fwspec *fwspec = arg; + irq_hw_number_t hwirq; + + if (fwspec->param[0] == GIC_SHARED) + hwirq = GIC_SHARED_TO_HWIRQ(fwspec->param[1]); + else + hwirq = GIC_LOCAL_TO_HWIRQ(fwspec->param[1]); + + return gic_irq_domain_map(d, virq, hwirq); +} + void gic_irq_domain_free(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs) { @@ -767,6 +775,7 @@ static const struct irq_domain_ops gic_irq_domain_ops = { .xlate = gic_irq_domain_xlate, .alloc = gic_irq_domain_alloc, .free = gic_irq_domain_free, + .map = gic_irq_domain_map, }; static int gic_ipi_domain_xlate(struct irq_domain *d, struct device_node *ctrlr, @@ -872,38 +881,6 @@ static struct irq_domain_ops gic_ipi_domain_ops = { .match = gic_ipi_domain_match, }; -static void __init gic_map_single_int(struct device_node *node, - unsigned int irq) -{ - unsigned int linux_irq; - struct irq_fwspec local_int_fwspec = { - .fwnode = &node->fwnode, - .param_count = 3, - .param = { - [0] = GIC_LOCAL, - [1] = irq, - [2] = IRQ_TYPE_NONE, - }, - }; - - if (!gic_local_irq_is_routable(irq)) - return; - - linux_irq = irq_create_fwspec_mapping(&local_int_fwspec); - WARN_ON(!linux_irq); -} - -static void __init gic_map_interrupts(struct device_node *node) -{ - gic_map_single_int(node, GIC_LOCAL_INT_WD); - gic_map_single_int(node, GIC_LOCAL_INT_COMPARE); - gic_map_single_int(node, GIC_LOCAL_INT_TIMER); - gic_map_single_int(node, GIC_LOCAL_INT_PERFCTR); - gic_map_single_int(node, GIC_LOCAL_INT_SWINT0); - gic_map_single_int(node, GIC_LOCAL_INT_SWINT1); - gic_map_single_int(node, GIC_LOCAL_INT_FDC); -} - static void __init __gic_init(unsigned long gic_base_addr, unsigned long gic_addrspace_size, unsigned int cpu_vec, unsigned int irqbase, @@ -997,7 +974,6 @@ static void __init __gic_init(unsigned long gic_base_addr, bitmap_copy(ipi_available, ipi_resrv, GIC_MAX_INTRS); gic_basic_init(); - gic_map_interrupts(node); } void __init gic_init(unsigned long gic_base_addr, From 216646e4d82e847791f0ba66c439dedd36cb119f Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Thu, 27 Apr 2017 15:21:13 +0000 Subject: [PATCH 28/28] irqchip/mbigen: Fix return value check in mbigen_device_probe() In case of error, the function devm_ioremap() returns NULL pointer not ERR_PTR(). Use devm_ioremap_resource() instead of devm_ioremap() to fix the IS_ERR() test issue. Fixes: 76e1f77f9c26 ("irqchip/mbigen: Introduce mbigen_of_create_domain()") Signed-off-by: Wei Yongjun Cc: Marc Zyngier Cc: Kefeng Wang Cc: Jason Cooper Link: http://lkml.kernel.org/r/20170427152113.31147-1-weiyj.lk@gmail.com Signed-off-by: Thomas Gleixner --- drivers/irqchip/irq-mbigen.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c index 061cdb8bde8b..d2306c821ebb 100644 --- a/drivers/irqchip/irq-mbigen.c +++ b/drivers/irqchip/irq-mbigen.c @@ -337,8 +337,7 @@ static int mbigen_device_probe(struct platform_device *pdev) mgn_chip->pdev = pdev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - mgn_chip->base = devm_ioremap(&pdev->dev, res->start, - resource_size(res)); + mgn_chip->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(mgn_chip->base)) return PTR_ERR(mgn_chip->base);