From 07ba4b061a79896315a7be4b123de12df6a9d2bd Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Sat, 23 Jan 2016 13:57:46 +0100 Subject: irqchip/ath79-misc: Move the MISC driver from arch/mips/ath79/ The driver stays the same but the initialization changes a bit. For OF boards we now get the memory map from the OF node and use a linear mapping instead of the legacy mapping. For legacy boards we still use a legacy mapping and just pass down all the parameters from the board init code. Signed-off-by: Alban Bedel Acked-by: Marc Zyngier Link: https://lkml.kernel.org/r/1453553867-27003-1-git-send-email-albeu@free.fr Signed-off-by: Jason Cooper --- drivers/irqchip/Makefile | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/irqchip/Makefile') diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 18caacb60d58..b873d4a081b4 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_IRQCHIP) += irqchip.o +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 -- cgit v1.2.3 From 81ffb18ce4a0c400b051c3df67e452410d6be1ec Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Sat, 23 Jan 2016 13:57:47 +0100 Subject: irqchip/ath79-cpu: Move the CPU IRQ driver from arch/mips/ath79/ Signed-off-by: Alban Bedel Acked-by: Marc Zyngier Link: https://lkml.kernel.org/r/1453553867-27003-2-git-send-email-albeu@free.fr Signed-off-by: Jason Cooper --- arch/mips/ath79/irq.c | 81 ++------------------------ arch/mips/include/asm/mach-ath79/ath79.h | 1 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-ath79-cpu.c | 97 ++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 75 deletions(-) create mode 100644 drivers/irqchip/irq-ath79-cpu.c (limited to 'drivers/irqchip/Makefile') diff --git a/arch/mips/ath79/irq.c b/arch/mips/ath79/irq.c index 05b45140bc1f..2dfff1f19004 100644 --- a/arch/mips/ath79/irq.c +++ b/arch/mips/ath79/irq.c @@ -128,79 +128,10 @@ static void qca955x_irq_init(void) irq_set_chained_handler(ATH79_CPU_IRQ(3), qca955x_ip3_irq_dispatch); } -/* - * The IP2/IP3 lines are tied to a PCI/WMAC/USB device. Drivers for - * these devices typically allocate coherent DMA memory, however the - * DMA controller may still have some unsynchronized data in the FIFO. - * Issue a flush in the handlers to ensure that the driver sees - * the update. - * - * This array map the interrupt lines to the DDR write buffer channels. - */ - -static unsigned irq_wb_chan[8] = { - -1, -1, -1, -1, -1, -1, -1, -1, -}; - -asmlinkage void plat_irq_dispatch(void) -{ - unsigned long pending; - int irq; - - pending = read_c0_status() & read_c0_cause() & ST0_IM; - - if (!pending) { - spurious_interrupt(); - return; - } - - pending >>= CAUSEB_IP; - while (pending) { - irq = fls(pending) - 1; - if (irq < ARRAY_SIZE(irq_wb_chan) && irq_wb_chan[irq] != -1) - ath79_ddr_wb_flush(irq_wb_chan[irq]); - do_IRQ(MIPS_CPU_IRQ_BASE + irq); - pending &= ~BIT(irq); - } -} - -static int __init ar79_cpu_intc_of_init( - struct device_node *node, struct device_node *parent) -{ - int err, i, count; - - /* Fill the irq_wb_chan table */ - count = of_count_phandle_with_args( - node, "qca,ddr-wb-channels", "#qca,ddr-wb-channel-cells"); - - for (i = 0; i < count; i++) { - struct of_phandle_args args; - u32 irq = i; - - of_property_read_u32_index( - node, "qca,ddr-wb-channel-interrupts", i, &irq); - if (irq >= ARRAY_SIZE(irq_wb_chan)) - continue; - - err = of_parse_phandle_with_args( - node, "qca,ddr-wb-channels", - "#qca,ddr-wb-channel-cells", - i, &args); - if (err) - return err; - - irq_wb_chan[irq] = args.args[0]; - pr_info("IRQ: Set flush channel of IRQ%d to %d\n", - irq, args.args[0]); - } - - return mips_cpu_irq_of_init(node, parent); -} -IRQCHIP_DECLARE(ar79_cpu_intc, "qca,ar7100-cpu-intc", - ar79_cpu_intc_of_init); - void __init arch_init_irq(void) { + unsigned irq_wb_chan2 = -1; + unsigned irq_wb_chan3 = -1; bool misc_is_ar71xx; if (mips_machtype == ATH79_MACH_GENERIC_OF) { @@ -210,13 +141,13 @@ void __init arch_init_irq(void) if (soc_is_ar71xx() || soc_is_ar724x() || soc_is_ar913x() || soc_is_ar933x()) { - irq_wb_chan[2] = 3; - irq_wb_chan[3] = 2; + irq_wb_chan2 = 3; + irq_wb_chan3 = 2; } else if (soc_is_ar934x()) { - irq_wb_chan[3] = 2; + irq_wb_chan3 = 2; } - mips_cpu_irq_init(); + ath79_cpu_irq_init(irq_wb_chan2, irq_wb_chan3); if (soc_is_ar71xx() || soc_is_ar913x()) misc_is_ar71xx = true; diff --git a/arch/mips/include/asm/mach-ath79/ath79.h b/arch/mips/include/asm/mach-ath79/ath79.h index 22a2f56ad5e9..441faa92c3cd 100644 --- a/arch/mips/include/asm/mach-ath79/ath79.h +++ b/arch/mips/include/asm/mach-ath79/ath79.h @@ -144,6 +144,7 @@ static inline u32 ath79_reset_rr(unsigned reg) void ath79_device_reset_set(u32 mask); void ath79_device_reset_clear(u32 mask); +void ath79_cpu_irq_init(unsigned irq_wb_chan2, unsigned irq_wb_chan3); void ath79_misc_irq_init(void __iomem *regs, int irq, int irq_base, bool is_ar71xx); diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index b873d4a081b4..aeb9200f0e16 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_IRQCHIP) += irqchip.o +obj-$(CONFIG_ATH79) += irq-ath79-cpu.o obj-$(CONFIG_ATH79) += irq-ath79-misc.o obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2835.o obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2836.o diff --git a/drivers/irqchip/irq-ath79-cpu.c b/drivers/irqchip/irq-ath79-cpu.c new file mode 100644 index 000000000000..befe93c5a51a --- /dev/null +++ b/drivers/irqchip/irq-ath79-cpu.c @@ -0,0 +1,97 @@ +/* + * Atheros AR71xx/AR724x/AR913x specific interrupt handling + * + * Copyright (C) 2015 Alban Bedel + * Copyright (C) 2010-2011 Jaiganesh Narayanan + * Copyright (C) 2008-2011 Gabor Juhos + * Copyright (C) 2008 Imre Kaloz + * + * Parts of this file are based on Atheros' 2.6.15/2.6.31 BSP + * + * 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. + */ + +#include +#include +#include + +#include +#include + +/* + * The IP2/IP3 lines are tied to a PCI/WMAC/USB device. Drivers for + * these devices typically allocate coherent DMA memory, however the + * DMA controller may still have some unsynchronized data in the FIFO. + * Issue a flush in the handlers to ensure that the driver sees + * the update. + * + * This array map the interrupt lines to the DDR write buffer channels. + */ + +static unsigned irq_wb_chan[8] = { + -1, -1, -1, -1, -1, -1, -1, -1, +}; + +asmlinkage void plat_irq_dispatch(void) +{ + unsigned long pending; + int irq; + + pending = read_c0_status() & read_c0_cause() & ST0_IM; + + if (!pending) { + spurious_interrupt(); + return; + } + + pending >>= CAUSEB_IP; + while (pending) { + irq = fls(pending) - 1; + if (irq < ARRAY_SIZE(irq_wb_chan) && irq_wb_chan[irq] != -1) + ath79_ddr_wb_flush(irq_wb_chan[irq]); + do_IRQ(MIPS_CPU_IRQ_BASE + irq); + pending &= ~BIT(irq); + } +} + +static int __init ar79_cpu_intc_of_init( + struct device_node *node, struct device_node *parent) +{ + int err, i, count; + + /* Fill the irq_wb_chan table */ + count = of_count_phandle_with_args( + node, "qca,ddr-wb-channels", "#qca,ddr-wb-channel-cells"); + + for (i = 0; i < count; i++) { + struct of_phandle_args args; + u32 irq = i; + + of_property_read_u32_index( + node, "qca,ddr-wb-channel-interrupts", i, &irq); + if (irq >= ARRAY_SIZE(irq_wb_chan)) + continue; + + err = of_parse_phandle_with_args( + node, "qca,ddr-wb-channels", + "#qca,ddr-wb-channel-cells", + i, &args); + if (err) + return err; + + irq_wb_chan[irq] = args.args[0]; + } + + return mips_cpu_irq_of_init(node, parent); +} +IRQCHIP_DECLARE(ar79_cpu_intc, "qca,ar7100-cpu-intc", + ar79_cpu_intc_of_init); + +void __init ath79_cpu_irq_init(unsigned irq_wb_chan2, unsigned irq_wb_chan3) +{ + irq_wb_chan[2] = irq_wb_chan2; + irq_wb_chan[3] = irq_wb_chan3; + mips_cpu_irq_init(); +} -- cgit v1.2.3 From 4bba66899ac654cb7c940a3af35d496a0b1952f0 Mon Sep 17 00:00:00 2001 From: Mans Rullgard Date: Wed, 20 Jan 2016 18:07:17 +0000 Subject: irqchip/tango: Add support for Sigma Designs SMP86xx/SMP87xx interrupt controller This adds support for the secondary interrupt controller used in Sigma Designs SMP86xx and SMP87xx chips. Signed-off-by: Mans Rullgard Acked-by: Marc Zyngier Link: https://lkml.kernel.org/r/1453313237-18570-2-git-send-email-mans@mansr.com Signed-off-by: Jason Cooper --- drivers/irqchip/Kconfig | 5 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-tango.c | 232 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 drivers/irqchip/irq-tango.c (limited to 'drivers/irqchip/Makefile') diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 715923d5236c..6bdfbce0388f 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -151,6 +151,11 @@ config ST_IRQCHIP help Enables SysCfg Controlled IRQs on STi based platforms. +config TANGO_IRQ + bool + select IRQ_DOMAIN + select GENERIC_IRQ_CHIP + config TB10X_IRQC bool select IRQ_DOMAIN diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 18caacb60d58..ec83deb0c719 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_VERSATILE_FPGA_IRQ) += irq-versatile-fpga.o obj-$(CONFIG_ARCH_NSPIRE) += irq-zevio.o obj-$(CONFIG_ARCH_VT8500) += irq-vt8500.o obj-$(CONFIG_ST_IRQCHIP) += irq-st.o +obj-$(CONFIG_TANGO_IRQ) += irq-tango.o obj-$(CONFIG_TB10X_IRQC) += irq-tb10x.o obj-$(CONFIG_TS4800_IRQ) += irq-ts4800.o obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o diff --git a/drivers/irqchip/irq-tango.c b/drivers/irqchip/irq-tango.c new file mode 100644 index 000000000000..bdbb5c0ff7fe --- /dev/null +++ b/drivers/irqchip/irq-tango.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2014 Mans Rullgard + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IRQ0_CTL_BASE 0x0000 +#define IRQ1_CTL_BASE 0x0100 +#define EDGE_CTL_BASE 0x0200 +#define IRQ2_CTL_BASE 0x0300 + +#define IRQ_CTL_HI 0x18 +#define EDGE_CTL_HI 0x20 + +#define IRQ_STATUS 0x00 +#define IRQ_RAWSTAT 0x04 +#define IRQ_EN_SET 0x08 +#define IRQ_EN_CLR 0x0c +#define IRQ_SOFT_SET 0x10 +#define IRQ_SOFT_CLR 0x14 + +#define EDGE_STATUS 0x00 +#define EDGE_RAWSTAT 0x04 +#define EDGE_CFG_RISE 0x08 +#define EDGE_CFG_FALL 0x0c +#define EDGE_CFG_RISE_SET 0x10 +#define EDGE_CFG_RISE_CLR 0x14 +#define EDGE_CFG_FALL_SET 0x18 +#define EDGE_CFG_FALL_CLR 0x1c + +struct tangox_irq_chip { + void __iomem *base; + unsigned long ctl; +}; + +static inline u32 intc_readl(struct tangox_irq_chip *chip, int reg) +{ + return readl_relaxed(chip->base + reg); +} + +static inline void intc_writel(struct tangox_irq_chip *chip, int reg, u32 val) +{ + writel_relaxed(val, chip->base + reg); +} + +static void tangox_dispatch_irqs(struct irq_domain *dom, unsigned int status, + int base) +{ + unsigned int hwirq; + unsigned int virq; + + while (status) { + hwirq = __ffs(status); + virq = irq_find_mapping(dom, base + hwirq); + if (virq) + generic_handle_irq(virq); + status &= ~BIT(hwirq); + } +} + +static void tangox_irq_handler(struct irq_desc *desc) +{ + struct irq_domain *dom = irq_desc_get_handler_data(desc); + struct irq_chip *host_chip = irq_desc_get_chip(desc); + struct tangox_irq_chip *chip = dom->host_data; + unsigned int status_lo, status_hi; + + chained_irq_enter(host_chip, desc); + + status_lo = intc_readl(chip, chip->ctl + IRQ_STATUS); + status_hi = intc_readl(chip, chip->ctl + IRQ_CTL_HI + IRQ_STATUS); + + tangox_dispatch_irqs(dom, status_lo, 0); + tangox_dispatch_irqs(dom, status_hi, 32); + + chained_irq_exit(host_chip, desc); +} + +static int tangox_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct tangox_irq_chip *chip = gc->domain->host_data; + struct irq_chip_regs *regs = &gc->chip_types[0].regs; + + switch (flow_type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_EDGE_RISING: + intc_writel(chip, regs->type + EDGE_CFG_RISE_SET, d->mask); + intc_writel(chip, regs->type + EDGE_CFG_FALL_CLR, d->mask); + break; + + case IRQ_TYPE_EDGE_FALLING: + intc_writel(chip, regs->type + EDGE_CFG_RISE_CLR, d->mask); + intc_writel(chip, regs->type + EDGE_CFG_FALL_SET, d->mask); + break; + + case IRQ_TYPE_LEVEL_HIGH: + intc_writel(chip, regs->type + EDGE_CFG_RISE_CLR, d->mask); + intc_writel(chip, regs->type + EDGE_CFG_FALL_CLR, d->mask); + break; + + case IRQ_TYPE_LEVEL_LOW: + intc_writel(chip, regs->type + EDGE_CFG_RISE_SET, d->mask); + intc_writel(chip, regs->type + EDGE_CFG_FALL_SET, d->mask); + break; + + default: + pr_err("Invalid trigger mode %x for IRQ %d\n", + flow_type, d->irq); + return -EINVAL; + } + + return irq_setup_alt_chip(d, flow_type); +} + +static void __init tangox_irq_init_chip(struct irq_chip_generic *gc, + unsigned long ctl_offs, + unsigned long edge_offs) +{ + struct tangox_irq_chip *chip = gc->domain->host_data; + struct irq_chip_type *ct = gc->chip_types; + unsigned long ctl_base = chip->ctl + ctl_offs; + unsigned long edge_base = EDGE_CTL_BASE + edge_offs; + int i; + + gc->reg_base = chip->base; + gc->unused = 0; + + for (i = 0; i < 2; i++) { + ct[i].chip.irq_ack = irq_gc_ack_set_bit; + ct[i].chip.irq_mask = irq_gc_mask_disable_reg; + ct[i].chip.irq_mask_ack = irq_gc_mask_disable_reg_and_ack; + ct[i].chip.irq_unmask = irq_gc_unmask_enable_reg; + ct[i].chip.irq_set_type = tangox_irq_set_type; + ct[i].chip.name = gc->domain->name; + + ct[i].regs.enable = ctl_base + IRQ_EN_SET; + ct[i].regs.disable = ctl_base + IRQ_EN_CLR; + ct[i].regs.ack = edge_base + EDGE_RAWSTAT; + ct[i].regs.type = edge_base; + } + + ct[0].type = IRQ_TYPE_LEVEL_MASK; + ct[0].handler = handle_level_irq; + + ct[1].type = IRQ_TYPE_EDGE_BOTH; + ct[1].handler = handle_edge_irq; + + intc_writel(chip, ct->regs.disable, 0xffffffff); + intc_writel(chip, ct->regs.ack, 0xffffffff); +} + +static void __init tangox_irq_domain_init(struct irq_domain *dom) +{ + struct irq_chip_generic *gc; + int i; + + for (i = 0; i < 2; i++) { + gc = irq_get_domain_generic_chip(dom, i * 32); + tangox_irq_init_chip(gc, i * IRQ_CTL_HI, i * EDGE_CTL_HI); + } +} + +static int __init tangox_irq_init(void __iomem *base, struct resource *baseres, + struct device_node *node) +{ + struct tangox_irq_chip *chip; + struct irq_domain *dom; + struct resource res; + int irq; + int err; + + irq = irq_of_parse_and_map(node, 0); + if (!irq) + panic("%s: failed to get IRQ", node->name); + + err = of_address_to_resource(node, 0, &res); + if (err) + panic("%s: failed to get address", node->name); + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + chip->ctl = res.start - baseres->start; + chip->base = base; + + dom = irq_domain_add_linear(node, 64, &irq_generic_chip_ops, chip); + if (!dom) + panic("%s: failed to create irqdomain", node->name); + + err = irq_alloc_domain_generic_chips(dom, 32, 2, node->name, + handle_level_irq, 0, 0, 0); + if (err) + panic("%s: failed to allocate irqchip", node->name); + + tangox_irq_domain_init(dom); + + irq_set_chained_handler(irq, tangox_irq_handler); + irq_set_handler_data(irq, dom); + + return 0; +} + +static int __init tangox_of_irq_init(struct device_node *node, + struct device_node *parent) +{ + struct device_node *c; + struct resource res; + void __iomem *base; + + base = of_iomap(node, 0); + if (!base) + panic("%s: of_iomap failed", node->name); + + of_address_to_resource(node, 0, &res); + + for_each_child_of_node(node, c) + tangox_irq_init(base, &res, c); + + return 0; +} +IRQCHIP_DECLARE(tangox_intc, "sigma,smp8642-intc", tangox_of_irq_init); -- cgit v1.2.3 From c27f29bbbf02168c9b1e8ba0fe7a8cb917e5a50f Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Fri, 19 Feb 2016 14:34:43 +0100 Subject: irqchip/mvebu-odmi: Add new driver for platform MSI on Marvell 7K/8K This commits adds a new irqchip driver that handles the ODMI controller found on Marvell 7K/8K processors. The ODMI controller provide MSI interrupt functionality to on-board peripherals, much like the GIC-v2m. Signed-off-by: Thomas Petazzoni Reviewed-by: Marc Zyngier Link: https://lkml.kernel.org/r/1455888883-5127-1-git-send-email-thomas.petazzoni@free-electrons.com Signed-off-by: Jason Cooper --- .../marvell,odmi-controller.txt | 41 ++++ drivers/irqchip/Kconfig | 4 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-mvebu-odmi.c | 248 +++++++++++++++++++++ 4 files changed, 294 insertions(+) create mode 100644 Documentation/devicetree/bindings/interrupt-controller/marvell,odmi-controller.txt create mode 100644 drivers/irqchip/irq-mvebu-odmi.c (limited to 'drivers/irqchip/Makefile') diff --git a/Documentation/devicetree/bindings/interrupt-controller/marvell,odmi-controller.txt b/Documentation/devicetree/bindings/interrupt-controller/marvell,odmi-controller.txt new file mode 100644 index 000000000000..252d5c9c31f9 --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/marvell,odmi-controller.txt @@ -0,0 +1,41 @@ + +* Marvell ODMI for MSI support + +Some Marvell SoCs have an On-Die Message Interrupt (ODMI) controller +which can be used by on-board peripheral for MSI interrupts. + +Required properties: + +- compatible : The value here should contain "marvell,odmi-controller". + +- interrupt,controller : Identifies the node as an interrupt controller. + +- msi-controller : Identifies the node as an MSI controller. + +- marvell,odmi-frames : Number of ODMI frames available. Each frame + provides a number of events. + +- reg : List of register definitions, one for each + ODMI frame. + +- marvell,spi-base : List of GIC base SPI interrupts, one for each + ODMI frame. Those SPI interrupts are 0-based, + i.e marvell,spi-base = <128> will use SPI #96. + See Documentation/devicetree/bindings/interrupt-controller/arm,gic.txt + for details about the GIC Device Tree binding. + +- interrupt-parent : Reference to the parent interrupt controller. + +Example: + + odmi: odmi@300000 { + compatible = "marvell,odmi-controller"; + interrupt-controller; + msi-controller; + marvell,odmi-frames = <4>; + reg = <0x300000 0x4000>, + <0x304000 0x4000>, + <0x308000 0x4000>, + <0x30C000 0x4000>; + marvell,spi-base = <128>, <136>, <144>, <152>; + }; diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 8115a32a553c..fa5e98c0f2bf 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -223,3 +223,7 @@ config IRQ_MXS def_bool y if MACH_ASM9260 || ARCH_MXS select IRQ_DOMAIN select STMP_DEVICE + +config MVEBU_ODMI + bool + select GENERIC_MSI_IRQ_DOMAIN diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 30dba044d0b8..b37f7084db04 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -59,3 +59,4 @@ obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o obj-$(CONFIG_PIC32_EVIC) += irq-pic32-evic.o +obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o diff --git a/drivers/irqchip/irq-mvebu-odmi.c b/drivers/irqchip/irq-mvebu-odmi.c new file mode 100644 index 000000000000..0ae98b24836f --- /dev/null +++ b/drivers/irqchip/irq-mvebu-odmi.c @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2016 Marvell + * + * Thomas Petazzoni + * + * 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. + */ + +#define pr_fmt(fmt) "GIC-ODMI: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#define GICP_ODMIN_SET 0x40 +#define GICP_ODMI_INT_NUM_SHIFT 12 +#define GICP_ODMIN_GM_EP_R0 0x110 +#define GICP_ODMIN_GM_EP_R1 0x114 +#define GICP_ODMIN_GM_EA_R0 0x108 +#define GICP_ODMIN_GM_EA_R1 0x118 + +/* + * We don't support the group events, so we simply have 8 interrupts + * per frame. + */ +#define NODMIS_SHIFT 3 +#define NODMIS_PER_FRAME (1 << NODMIS_SHIFT) +#define NODMIS_MASK (NODMIS_PER_FRAME - 1) + +struct odmi_data { + struct resource res; + void __iomem *base; + unsigned int spi_base; +}; + +static struct odmi_data *odmis; +static unsigned long *odmis_bm; +static unsigned int odmis_count; + +/* Protects odmis_bm */ +static DEFINE_SPINLOCK(odmis_bm_lock); + +static int odmi_set_affinity(struct irq_data *d, + const struct cpumask *mask, bool force) +{ + int ret; + + ret = irq_chip_set_affinity_parent(d, mask, force); + if (ret == IRQ_SET_MASK_OK) + ret = IRQ_SET_MASK_OK_DONE; + + return ret; +} + +static void odmi_compose_msi_msg(struct irq_data *d, struct msi_msg *msg) +{ + struct odmi_data *odmi; + phys_addr_t addr; + unsigned int odmin; + + if (WARN_ON(d->hwirq >= odmis_count * NODMIS_PER_FRAME)) + return; + + odmi = &odmis[d->hwirq >> NODMIS_SHIFT]; + odmin = d->hwirq & NODMIS_MASK; + + addr = odmi->res.start + GICP_ODMIN_SET; + + msg->address_hi = upper_32_bits(addr); + msg->address_lo = lower_32_bits(addr); + msg->data = odmin << GICP_ODMI_INT_NUM_SHIFT; +} + +static struct irq_chip odmi_irq_chip = { + .name = "ODMI", + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_affinity = odmi_set_affinity, + .irq_compose_msi_msg = odmi_compose_msi_msg, +}; + +static int odmi_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *args) +{ + struct odmi_data *odmi = NULL; + struct irq_fwspec fwspec; + struct irq_data *d; + unsigned int hwirq, odmin; + int ret; + + spin_lock(&odmis_bm_lock); + hwirq = find_first_zero_bit(odmis_bm, NODMIS_PER_FRAME * odmis_count); + if (hwirq >= NODMIS_PER_FRAME * odmis_count) { + spin_unlock(&odmis_bm_lock); + return -ENOSPC; + } + + __set_bit(hwirq, odmis_bm); + spin_unlock(&odmis_bm_lock); + + odmi = &odmis[hwirq >> NODMIS_SHIFT]; + odmin = hwirq & NODMIS_MASK; + + fwspec.fwnode = domain->parent->fwnode; + fwspec.param_count = 3; + fwspec.param[0] = GIC_SPI; + fwspec.param[1] = odmi->spi_base - 32 + odmin; + fwspec.param[2] = IRQ_TYPE_EDGE_RISING; + + ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); + if (ret) { + pr_err("Cannot allocate parent IRQ\n"); + spin_lock(&odmis_bm_lock); + __clear_bit(odmin, odmis_bm); + spin_unlock(&odmis_bm_lock); + return ret; + } + + /* Configure the interrupt line to be edge */ + d = irq_domain_get_irq_data(domain->parent, virq); + d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING); + + irq_domain_set_hwirq_and_chip(domain, virq, hwirq, + &odmi_irq_chip, NULL); + + return 0; +} + +static void odmi_irq_domain_free(struct irq_domain *domain, + unsigned int virq, unsigned int nr_irqs) +{ + struct irq_data *d = irq_domain_get_irq_data(domain, virq); + + if (d->hwirq >= odmis_count * NODMIS_PER_FRAME) { + pr_err("Failed to teardown msi. Invalid hwirq %lu\n", d->hwirq); + return; + } + + irq_domain_free_irqs_parent(domain, virq, nr_irqs); + + /* Actually free the MSI */ + spin_lock(&odmis_bm_lock); + __clear_bit(d->hwirq, odmis_bm); + spin_unlock(&odmis_bm_lock); +} + +static const struct irq_domain_ops odmi_domain_ops = { + .alloc = odmi_irq_domain_alloc, + .free = odmi_irq_domain_free, +}; + +static struct irq_chip odmi_msi_irq_chip = { + .name = "ODMI", +}; + +static struct msi_domain_ops odmi_msi_ops = { +}; + +static struct msi_domain_info odmi_msi_domain_info = { + .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS), + .ops = &odmi_msi_ops, + .chip = &odmi_msi_irq_chip, +}; + +static int __init mvebu_odmi_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *inner_domain, *plat_domain; + int ret, i; + + if (of_property_read_u32(node, "marvell,odmi-frames", &odmis_count)) + return -EINVAL; + + odmis = kcalloc(odmis_count, sizeof(struct odmi_data), GFP_KERNEL); + if (!odmis) + return -ENOMEM; + + odmis_bm = kcalloc(BITS_TO_LONGS(odmis_count * NODMIS_PER_FRAME), + sizeof(long), GFP_KERNEL); + if (!odmis_bm) { + ret = -ENOMEM; + goto err_alloc; + } + + for (i = 0; i < odmis_count; i++) { + struct odmi_data *odmi = &odmis[i]; + + ret = of_address_to_resource(node, i, &odmi->res); + if (ret) + goto err_unmap; + + odmi->base = of_io_request_and_map(node, i, "odmi"); + if (IS_ERR(odmi->base)) { + ret = PTR_ERR(odmi->base); + goto err_unmap; + } + + if (of_property_read_u32_index(node, "marvell,spi-base", + i, &odmi->spi_base)) { + ret = -EINVAL; + goto err_unmap; + } + } + + inner_domain = irq_domain_create_linear(of_node_to_fwnode(node), + odmis_count * NODMIS_PER_FRAME, + &odmi_domain_ops, NULL); + if (!inner_domain) { + ret = -ENOMEM; + goto err_unmap; + } + + inner_domain->parent = irq_find_host(parent); + + plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node), + &odmi_msi_domain_info, + inner_domain); + if (!plat_domain) { + ret = -ENOMEM; + goto err_remove_inner; + } + + return 0; + +err_remove_inner: + irq_domain_remove(inner_domain); +err_unmap: + for (i = 0; i < odmis_count; i++) { + struct odmi_data *odmi = &odmis[i]; + + if (odmi->base && !IS_ERR(odmi->base)) + iounmap(odmis[i].base); + } + kfree(odmis_bm); +err_alloc: + kfree(odmis); + return ret; +} + +IRQCHIP_DECLARE(mvebu_odmi, "marvell,odmi-controller", mvebu_odmi_init); -- cgit v1.2.3