diff options
Diffstat (limited to 'drivers/irqchip')
67 files changed, 2580 insertions, 741 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 5438abb1baba..80e10f4e213a 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only menu "IRQ chip support" config IRQCHIP @@ -6,7 +7,6 @@ config IRQCHIP config ARM_GIC bool - select IRQ_DOMAIN select IRQ_DOMAIN_HIERARCHY select GENERIC_IRQ_MULTI_HANDLER select GENERIC_IRQ_EFFECTIVE_AFF_MASK @@ -15,10 +15,10 @@ config ARM_GIC_PM bool depends on PM select ARM_GIC - select PM_CLK config ARM_GIC_MAX_NR int + depends on ARM_GIC default 2 if ARCH_REALVIEW default 1 @@ -33,7 +33,6 @@ config GIC_NON_BANKED config ARM_GIC_V3 bool - select IRQ_DOMAIN select GENERIC_IRQ_MULTI_HANDLER select IRQ_DOMAIN_HIERARCHY select PARTITION_PERCPU @@ -59,7 +58,6 @@ config ARM_GIC_V3_ITS_FSL_MC config ARM_NVIC bool - select IRQ_DOMAIN select IRQ_DOMAIN_HIERARCHY select GENERIC_IRQ_CHIP @@ -89,6 +87,14 @@ config ALPINE_MSI select PCI_MSI select GENERIC_IRQ_CHIP +config AL_FIC + bool "Amazon's Annapurna Labs Fabric Interrupt Controller" + depends on OF || COMPILE_TEST + select GENERIC_IRQ_CHIP + select IRQ_DOMAIN + help + Support Amazon's Annapurna Labs Fabric Interrupt Controller. + config ATMEL_AIC_IRQ bool select GENERIC_IRQ_CHIP @@ -160,6 +166,12 @@ config IMGPDC_IRQ select GENERIC_IRQ_CHIP select IRQ_DOMAIN +config IXP4XX_IRQ + bool + select IRQ_DOMAIN + select GENERIC_IRQ_MULTI_HANDLER + select SPARSE_IRQ + config MADERA_IRQ tristate @@ -213,13 +225,26 @@ config RDA_INTC select IRQ_DOMAIN config RENESAS_INTC_IRQPIN - bool + bool "Renesas INTC External IRQ Pin Support" if COMPILE_TEST select IRQ_DOMAIN + help + Enable support for the Renesas Interrupt Controller for external + interrupt pins, as found on SH/R-Mobile and R-Car Gen1 SoCs. config RENESAS_IRQC - bool + bool "Renesas R-Mobile APE6 and R-Car IRQC support" if COMPILE_TEST select GENERIC_IRQ_CHIP select IRQ_DOMAIN + help + Enable support for the Renesas Interrupt Controller for external + devices, as found on R-Mobile APE6, R-Car Gen2, and R-Car Gen3 SoCs. + +config RENESAS_RZA1_IRQC + bool "Renesas RZ/A1 IRQC support" if COMPILE_TEST + select IRQ_DOMAIN_HIERARCHY + help + Enable support for the Renesas RZ/A1 Interrupt Controller, to use up + to 8 external interrupts with configurable sense select. config ST_IRQCHIP bool @@ -295,8 +320,11 @@ config RENESAS_H8300H_INTC select IRQ_DOMAIN config RENESAS_H8S_INTC - bool + bool "Renesas H8S Interrupt Controller Support" if COMPILE_TEST select IRQ_DOMAIN + help + Enable support for the Renesas H8/300 Interrupt Controller, as found + on Renesas H8S SoCs. config IMX_GPCV2 bool @@ -352,7 +380,6 @@ config STM32_EXTI config QCOM_IRQ_COMBINER bool "QCOM IRQ combiner support" depends on ARCH_QCOM && ACPI - select IRQ_DOMAIN select IRQ_DOMAIN_HIERARCHY help Say yes here to add support for the IRQ combiner devices embedded @@ -369,7 +396,6 @@ config IRQ_UNIPHIER_AIDET config MESON_IRQ_GPIO bool "Meson GPIO Interrupt Multiplexer" depends on ARCH_MESON - select IRQ_DOMAIN select IRQ_DOMAIN_HIERARCHY help Support Meson SoC Family GPIO Interrupt Multiplexer @@ -385,7 +411,6 @@ config GOLDFISH_PIC config QCOM_PDC bool "QCOM PDC" depends on ARCH_QCOM - select IRQ_DOMAIN select IRQ_DOMAIN_HIERARCHY help Power Domain Controller driver to manage and configure wakeup @@ -425,6 +450,27 @@ config LS1X_IRQ help Support for the Loongson-1 platform Interrupt Controller. +config TI_SCI_INTR_IRQCHIP + bool + depends on TI_SCI_PROTOCOL + select IRQ_DOMAIN_HIERARCHY + help + This enables the irqchip driver support for K3 Interrupt router + over TI System Control Interface available on some new TI's SoCs. + If you wish to use interrupt router irq resources managed by the + TI System Controller, say Y here. Otherwise, say N. + +config TI_SCI_INTA_IRQCHIP + bool + depends on TI_SCI_PROTOCOL + select IRQ_DOMAIN_HIERARCHY + select TI_SCI_INTA_MSI_DOMAIN + help + This enables the irqchip driver support for K3 Interrupt aggregator + over TI System Control Interface available on some new TI's SoCs. + If you wish to use interrupt aggregator irq resources managed by the + TI System Controller, say Y here. Otherwise, say N. + endmenu config SIFIVE_PLIC diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 85972ae1bd7f..8d0fcec6ab23 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_IRQCHIP) += irqchip.o +obj-$(CONFIG_AL_FIC) += irq-al-fic.o obj-$(CONFIG_ALPINE_MSI) += irq-alpine-msi.o obj-$(CONFIG_ATH79) += irq-ath79-cpu.o obj-$(CONFIG_ATH79) += irq-ath79-misc.o @@ -43,11 +44,13 @@ obj-$(CONFIG_ATMEL_AIC5_IRQ) += irq-atmel-aic-common.o irq-atmel-aic5.o obj-$(CONFIG_I8259) += irq-i8259.o obj-$(CONFIG_IMGPDC_IRQ) += irq-imgpdc.o obj-$(CONFIG_IRQ_MIPS_CPU) += irq-mips-cpu.o +obj-$(CONFIG_IXP4XX_IRQ) += irq-ixp4xx.o obj-$(CONFIG_SIRF_IRQ) += irq-sirfsoc.o obj-$(CONFIG_JCORE_AIC) += irq-jcore-aic.o obj-$(CONFIG_RDA_INTC) += irq-rda-intc.o obj-$(CONFIG_RENESAS_INTC_IRQPIN) += irq-renesas-intc-irqpin.o obj-$(CONFIG_RENESAS_IRQC) += irq-renesas-irqc.o +obj-$(CONFIG_RENESAS_RZA1_IRQC) += irq-renesas-rza1.o obj-$(CONFIG_VERSATILE_FPGA_IRQ) += irq-versatile-fpga.o obj-$(CONFIG_ARCH_NSPIRE) += irq-zevio.o obj-$(CONFIG_ARCH_VT8500) += irq-vt8500.o @@ -97,3 +100,5 @@ obj-$(CONFIG_SIFIVE_PLIC) += irq-sifive-plic.o obj-$(CONFIG_IMX_IRQSTEER) += irq-imx-irqsteer.o obj-$(CONFIG_MADERA_IRQ) += irq-madera.o obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o +obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o +obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o diff --git a/drivers/irqchip/alphascale_asm9260-icoll.h b/drivers/irqchip/alphascale_asm9260-icoll.h index 5cec108ee204..1ec59483afa1 100644 --- a/drivers/irqchip/alphascale_asm9260-icoll.h +++ b/drivers/irqchip/alphascale_asm9260-icoll.h @@ -1,10 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2014 Oleksij Rempel <linux@rempel-privat.de> - * - * 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. */ #ifndef _ALPHASCALE_ASM9260_ICOLL_H diff --git a/drivers/irqchip/exynos-combiner.c b/drivers/irqchip/exynos-combiner.c index b78a169c9c83..0b85d9a3fbff 100644 --- a/drivers/irqchip/exynos-combiner.c +++ b/drivers/irqchip/exynos-combiner.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Combiner irqchip for EXYNOS - * - * 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 <linux/err.h> #include <linux/export.h> diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c new file mode 100644 index 000000000000..1a57cee3efab --- /dev/null +++ b/drivers/irqchip/irq-al-fic.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + */ + +#include <linux/bitfield.h> +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/irqdomain.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +/* FIC Registers */ +#define AL_FIC_CAUSE 0x00 +#define AL_FIC_MASK 0x10 +#define AL_FIC_CONTROL 0x28 + +#define CONTROL_TRIGGER_RISING BIT(3) +#define CONTROL_MASK_MSI_X BIT(5) + +#define NR_FIC_IRQS 32 + +MODULE_AUTHOR("Talel Shenhar"); +MODULE_DESCRIPTION("Amazon's Annapurna Labs Interrupt Controller Driver"); +MODULE_LICENSE("GPL v2"); + +enum al_fic_state { + AL_FIC_UNCONFIGURED = 0, + AL_FIC_CONFIGURED_LEVEL, + AL_FIC_CONFIGURED_RISING_EDGE, +}; + +struct al_fic { + void __iomem *base; + struct irq_domain *domain; + const char *name; + unsigned int parent_irq; + enum al_fic_state state; +}; + +static void al_fic_set_trigger(struct al_fic *fic, + struct irq_chip_generic *gc, + enum al_fic_state new_state) +{ + irq_flow_handler_t handler; + u32 control = readl_relaxed(fic->base + AL_FIC_CONTROL); + + if (new_state == AL_FIC_CONFIGURED_LEVEL) { + handler = handle_level_irq; + control &= ~CONTROL_TRIGGER_RISING; + } else { + handler = handle_edge_irq; + control |= CONTROL_TRIGGER_RISING; + } + gc->chip_types->handler = handler; + fic->state = new_state; + writel_relaxed(control, fic->base + AL_FIC_CONTROL); +} + +static int al_fic_irq_set_type(struct irq_data *data, unsigned int flow_type) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); + struct al_fic *fic = gc->private; + enum al_fic_state new_state; + int ret = 0; + + irq_gc_lock(gc); + + if (((flow_type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_HIGH) && + ((flow_type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_EDGE_RISING)) { + pr_debug("fic doesn't support flow type %d\n", flow_type); + ret = -EINVAL; + goto err; + } + + new_state = (flow_type & IRQ_TYPE_LEVEL_HIGH) ? + AL_FIC_CONFIGURED_LEVEL : AL_FIC_CONFIGURED_RISING_EDGE; + + /* + * A given FIC instance can be either all level or all edge triggered. + * This is generally fixed depending on what pieces of HW it's wired up + * to. + * + * We configure it based on the sensitivity of the first source + * being setup, and reject any subsequent attempt at configuring it in a + * different way. + */ + if (fic->state == AL_FIC_UNCONFIGURED) { + al_fic_set_trigger(fic, gc, new_state); + } else if (fic->state != new_state) { + pr_debug("fic %s state already configured to %d\n", + fic->name, fic->state); + ret = -EINVAL; + goto err; + } + +err: + irq_gc_unlock(gc); + + return ret; +} + +static void al_fic_irq_handler(struct irq_desc *desc) +{ + struct al_fic *fic = irq_desc_get_handler_data(desc); + struct irq_domain *domain = fic->domain; + struct irq_chip *irqchip = irq_desc_get_chip(desc); + struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0); + unsigned long pending; + unsigned int irq; + u32 hwirq; + + chained_irq_enter(irqchip, desc); + + pending = readl_relaxed(fic->base + AL_FIC_CAUSE); + pending &= ~gc->mask_cache; + + for_each_set_bit(hwirq, &pending, NR_FIC_IRQS) { + irq = irq_find_mapping(domain, hwirq); + generic_handle_irq(irq); + } + + chained_irq_exit(irqchip, desc); +} + +static int al_fic_register(struct device_node *node, + struct al_fic *fic) +{ + struct irq_chip_generic *gc; + int ret; + + fic->domain = irq_domain_add_linear(node, + NR_FIC_IRQS, + &irq_generic_chip_ops, + fic); + if (!fic->domain) { + pr_err("fail to add irq domain\n"); + return -ENOMEM; + } + + ret = irq_alloc_domain_generic_chips(fic->domain, + NR_FIC_IRQS, + 1, fic->name, + handle_level_irq, + 0, 0, IRQ_GC_INIT_MASK_CACHE); + if (ret) { + pr_err("fail to allocate generic chip (%d)\n", ret); + goto err_domain_remove; + } + + gc = irq_get_domain_generic_chip(fic->domain, 0); + gc->reg_base = fic->base; + gc->chip_types->regs.mask = AL_FIC_MASK; + gc->chip_types->regs.ack = AL_FIC_CAUSE; + gc->chip_types->chip.irq_mask = irq_gc_mask_set_bit; + gc->chip_types->chip.irq_unmask = irq_gc_mask_clr_bit; + gc->chip_types->chip.irq_ack = irq_gc_ack_clr_bit; + gc->chip_types->chip.irq_set_type = al_fic_irq_set_type; + gc->chip_types->chip.flags = IRQCHIP_SKIP_SET_WAKE; + gc->private = fic; + + irq_set_chained_handler_and_data(fic->parent_irq, + al_fic_irq_handler, + fic); + return 0; + +err_domain_remove: + irq_domain_remove(fic->domain); + + return ret; +} + +/* + * al_fic_wire_init() - initialize and configure fic in wire mode + * @of_node: optional pointer to interrupt controller's device tree node. + * @base: mmio to fic register + * @name: name of the fic + * @parent_irq: interrupt of parent + * + * This API will configure the fic hardware to to work in wire mode. + * In wire mode, fic hardware is generating a wire ("wired") interrupt. + * Interrupt can be generated based on positive edge or level - configuration is + * to be determined based on connected hardware to this fic. + */ +static struct al_fic *al_fic_wire_init(struct device_node *node, + void __iomem *base, + const char *name, + unsigned int parent_irq) +{ + struct al_fic *fic; + int ret; + u32 control = CONTROL_MASK_MSI_X; + + fic = kzalloc(sizeof(*fic), GFP_KERNEL); + if (!fic) + return ERR_PTR(-ENOMEM); + + fic->base = base; + fic->parent_irq = parent_irq; + fic->name = name; + + /* mask out all interrupts */ + writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_MASK); + + /* clear any pending interrupt */ + writel_relaxed(0, fic->base + AL_FIC_CAUSE); + + writel_relaxed(control, fic->base + AL_FIC_CONTROL); + + ret = al_fic_register(node, fic); + if (ret) { + pr_err("fail to register irqchip\n"); + goto err_free; + } + + pr_debug("%s initialized successfully in Legacy mode (parent-irq=%u)\n", + fic->name, parent_irq); + + return fic; + +err_free: + kfree(fic); + return ERR_PTR(ret); +} + +static int __init al_fic_init_dt(struct device_node *node, + struct device_node *parent) +{ + int ret; + void __iomem *base; + unsigned int parent_irq; + struct al_fic *fic; + + if (!parent) { + pr_err("%s: unsupported - device require a parent\n", + node->name); + return -EINVAL; + } + + base = of_iomap(node, 0); + if (!base) { + pr_err("%s: fail to map memory\n", node->name); + return -ENOMEM; + } + + parent_irq = irq_of_parse_and_map(node, 0); + if (!parent_irq) { + pr_err("%s: fail to map irq\n", node->name); + ret = -EINVAL; + goto err_unmap; + } + + fic = al_fic_wire_init(node, + base, + node->name, + parent_irq); + if (IS_ERR(fic)) { + pr_err("%s: fail to initialize irqchip (%lu)\n", + node->name, + PTR_ERR(fic)); + ret = PTR_ERR(fic); + goto err_irq_dispose; + } + + return 0; + +err_irq_dispose: + irq_dispose_mapping(parent_irq); +err_unmap: + iounmap(base); + + return ret; +} + +IRQCHIP_DECLARE(al_fic, "amazon,al-fic", al_fic_init_dt); diff --git a/drivers/irqchip/irq-aspeed-i2c-ic.c b/drivers/irqchip/irq-aspeed-i2c-ic.c index f20200af0992..8d591c179f81 100644 --- a/drivers/irqchip/irq-aspeed-i2c-ic.c +++ b/drivers/irqchip/irq-aspeed-i2c-ic.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Aspeed 24XX/25XX I2C Interrupt Controller. * * Copyright (C) 2012-2017 ASPEED Technology Inc. * Copyright 2017 IBM Corporation * Copyright 2017 Google, Inc. - * - * 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 <linux/irq.h> diff --git a/drivers/irqchip/irq-aspeed-vic.c b/drivers/irqchip/irq-aspeed-vic.c index 03ba477ea0d0..6567ed782f82 100644 --- a/drivers/irqchip/irq-aspeed-vic.c +++ b/drivers/irqchip/irq-aspeed-vic.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2015 - Ben Herrenschmidt, IBM Corp. * @@ -7,17 +8,6 @@ * * Copyright (C) 1999 - 2003 ARM Limited * Copyright (C) 2000 Deep Blue Solutions Ltd - * - * 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. - * - * 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 <linux/export.h> diff --git a/drivers/irqchip/irq-ath79-cpu.c b/drivers/irqchip/irq-ath79-cpu.c index befe93c5a51a..923e4bba3776 100644 --- a/drivers/irqchip/irq-ath79-cpu.c +++ b/drivers/irqchip/irq-ath79-cpu.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Atheros AR71xx/AR724x/AR913x specific interrupt handling * @@ -7,10 +8,6 @@ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> * * 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 <linux/interrupt.h> diff --git a/drivers/irqchip/irq-ath79-misc.c b/drivers/irqchip/irq-ath79-misc.c index 0390603170b4..3d641bb6f3f1 100644 --- a/drivers/irqchip/irq-ath79-misc.c +++ b/drivers/irqchip/irq-ath79-misc.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Atheros AR71xx/AR724x/AR913x MISC interrupt controller * @@ -7,10 +8,6 @@ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> * * 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 <linux/irqchip.h> diff --git a/drivers/irqchip/irq-bcm6345-l1.c b/drivers/irqchip/irq-bcm6345-l1.c index 43f8abe40878..e3483789f4df 100644 --- a/drivers/irqchip/irq-bcm6345-l1.c +++ b/drivers/irqchip/irq-bcm6345-l1.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Broadcom BCM6345 style Level 1 interrupt controller driver * * Copyright (C) 2014 Broadcom Corporation * Copyright 2015 Simon Arlott * - * 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 is based on the BCM7038 (which supports SMP) but with a single * enable register instead of separate mask/set/clear registers. * diff --git a/drivers/irqchip/irq-bcm7038-l1.c b/drivers/irqchip/irq-bcm7038-l1.c index 0f6e30e9009d..fc75c61233aa 100644 --- a/drivers/irqchip/irq-bcm7038-l1.c +++ b/drivers/irqchip/irq-bcm7038-l1.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Broadcom BCM7038 style Level 1 interrupt controller driver * * Copyright (C) 2014 Broadcom Corporation * Author: Kevin Cernekee - * - * 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. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -343,6 +340,9 @@ int __init bcm7038_l1_of_init(struct device_node *dn, goto out_unmap; } + pr_info("registered BCM7038 L1 intc (%pOF, IRQs: %d)\n", + dn, IRQS_PER_WORD * intc->n_words); + return 0; out_unmap: diff --git a/drivers/irqchip/irq-bcm7120-l2.c b/drivers/irqchip/irq-bcm7120-l2.c index 8968e5e93fcb..586df3587be0 100644 --- a/drivers/irqchip/irq-bcm7120-l2.c +++ b/drivers/irqchip/irq-bcm7120-l2.c @@ -1,11 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Broadcom BCM7120 style Level 2 interrupt controller driver * * Copyright (C) 2014 Broadcom Corporation - * - * 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. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -318,6 +315,9 @@ static int __init bcm7120_l2_intc_probe(struct device_node *dn, } } + pr_info("registered %s intc (%pOF, parent IRQ(s): %d)\n", + intc_name, dn, data->num_parent_irqs); + return 0; out_free_domain: diff --git a/drivers/irqchip/irq-brcmstb-l2.c b/drivers/irqchip/irq-brcmstb-l2.c index 5e4ca139e4ea..0298ede67e51 100644 --- a/drivers/irqchip/irq-brcmstb-l2.c +++ b/drivers/irqchip/irq-brcmstb-l2.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Generic Broadcom Set Top Box Level 2 Interrupt controller driver * * Copyright (C) 2014-2017 Broadcom - * - * 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. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -264,6 +256,8 @@ static int __init brcmstb_l2_intc_of_init(struct device_node *np, ct->chip.irq_set_wake = irq_gc_set_wake; } + pr_info("registered L2 intc (%pOF, parent irq: %d)\n", np, parent_irq); + return 0; out_free_domain: diff --git a/drivers/irqchip/irq-clps711x.c b/drivers/irqchip/irq-clps711x.c index f913f4db7ae1..d0da29aeedc8 100644 --- a/drivers/irqchip/irq-clps711x.c +++ b/drivers/irqchip/irq-clps711x.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * CLPS711X IRQ driver * * Copyright (C) 2013 Alexander Shiyan <shc_work@mail.ru> - * - * 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 <linux/io.h> diff --git a/drivers/irqchip/irq-crossbar.c b/drivers/irqchip/irq-crossbar.c index 99d97d7e3fd7..a05a7501e107 100644 --- a/drivers/irqchip/irq-crossbar.c +++ b/drivers/irqchip/irq-crossbar.c @@ -1,13 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * drivers/irqchip/irq-crossbar.c * * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com * Author: Sricharan R <r.sricharan@ti.com> - * - * 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 <linux/err.h> #include <linux/io.h> diff --git a/drivers/irqchip/irq-csky-mpintc.c b/drivers/irqchip/irq-csky-mpintc.c index c67c961ab6cc..a1534edef7fa 100644 --- a/drivers/irqchip/irq-csky-mpintc.c +++ b/drivers/irqchip/irq-csky-mpintc.c @@ -32,8 +32,8 @@ static void __iomem *INTCL_base; #define INTCG_CIDSTR 0x1000 #define INTCL_PICTLR 0x0 +#define INTCL_CFGR 0x14 #define INTCL_SIGR 0x60 -#define INTCL_HPPIR 0x68 #define INTCL_RDYIR 0x6c #define INTCL_SENR 0xa0 #define INTCL_CENR 0xa4 @@ -41,21 +41,49 @@ static void __iomem *INTCL_base; static DEFINE_PER_CPU(void __iomem *, intcl_reg); +static unsigned long *__trigger; + +#define IRQ_OFFSET(irq) ((irq < COMM_IRQ_BASE) ? irq : (irq - COMM_IRQ_BASE)) + +#define TRIG_BYTE_OFFSET(i) ((((i) * 2) / 32) * 4) +#define TRIG_BIT_OFFSET(i) (((i) * 2) % 32) + +#define TRIG_VAL(trigger, irq) (trigger << TRIG_BIT_OFFSET(IRQ_OFFSET(irq))) +#define TRIG_VAL_MSK(irq) (~(3 << TRIG_BIT_OFFSET(IRQ_OFFSET(irq)))) + +#define TRIG_BASE(irq) \ + (TRIG_BYTE_OFFSET(IRQ_OFFSET(irq)) + ((irq < COMM_IRQ_BASE) ? \ + (this_cpu_read(intcl_reg) + INTCL_CFGR) : (INTCG_base + INTCG_CICFGR))) + +static DEFINE_SPINLOCK(setup_lock); +static void setup_trigger(unsigned long irq, unsigned long trigger) +{ + unsigned int tmp; + + spin_lock(&setup_lock); + + /* setup trigger */ + tmp = readl_relaxed(TRIG_BASE(irq)) & TRIG_VAL_MSK(irq); + + writel_relaxed(tmp | TRIG_VAL(trigger, irq), TRIG_BASE(irq)); + + spin_unlock(&setup_lock); +} + static void csky_mpintc_handler(struct pt_regs *regs) { void __iomem *reg_base = this_cpu_read(intcl_reg); - do { - handle_domain_irq(root_domain, - readl_relaxed(reg_base + INTCL_RDYIR), - regs); - } while (readl_relaxed(reg_base + INTCL_HPPIR) & BIT(31)); + handle_domain_irq(root_domain, + readl_relaxed(reg_base + INTCL_RDYIR), regs); } static void csky_mpintc_enable(struct irq_data *d) { void __iomem *reg_base = this_cpu_read(intcl_reg); + setup_trigger(d->hwirq, __trigger[d->hwirq]); + writel_relaxed(d->hwirq, reg_base + INTCL_SENR); } @@ -73,6 +101,28 @@ static void csky_mpintc_eoi(struct irq_data *d) writel_relaxed(d->hwirq, reg_base + INTCL_CACR); } +static int csky_mpintc_set_type(struct irq_data *d, unsigned int type) +{ + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_LEVEL_HIGH: + __trigger[d->hwirq] = 0; + break; + case IRQ_TYPE_LEVEL_LOW: + __trigger[d->hwirq] = 1; + break; + case IRQ_TYPE_EDGE_RISING: + __trigger[d->hwirq] = 2; + break; + case IRQ_TYPE_EDGE_FALLING: + __trigger[d->hwirq] = 3; + break; + default: + return -EINVAL; + } + + return 0; +} + #ifdef CONFIG_SMP static int csky_irq_set_affinity(struct irq_data *d, const struct cpumask *mask_val, @@ -89,8 +139,19 @@ static int csky_irq_set_affinity(struct irq_data *d, if (cpu >= nr_cpu_ids) return -EINVAL; - /* Enable interrupt destination */ - cpu |= BIT(31); + /* + * The csky,mpintc could support auto irq deliver, but it only + * could deliver external irq to one cpu or all cpus. So it + * doesn't support deliver external irq to a group of cpus + * with cpu_mask. + * SO we only use auto deliver mode when affinity mask_val is + * equal to cpu_present_mask. + * + */ + if (cpumask_equal(mask_val, cpu_present_mask)) + cpu = 0; + else + cpu |= BIT(31); writel_relaxed(cpu, INTCG_base + INTCG_CIDSTR + offset); @@ -105,6 +166,7 @@ static struct irq_chip csky_irq_chip = { .irq_eoi = csky_mpintc_eoi, .irq_enable = csky_mpintc_enable, .irq_disable = csky_mpintc_disable, + .irq_set_type = csky_mpintc_set_type, #ifdef CONFIG_SMP .irq_set_affinity = csky_irq_set_affinity, #endif @@ -125,9 +187,26 @@ static int csky_irqdomain_map(struct irq_domain *d, unsigned int irq, return 0; } +static int csky_irq_domain_xlate_cells(struct irq_domain *d, + struct device_node *ctrlr, const u32 *intspec, + unsigned int intsize, unsigned long *out_hwirq, + unsigned int *out_type) +{ + if (WARN_ON(intsize < 1)) + return -EINVAL; + + *out_hwirq = intspec[0]; + if (intsize > 1) + *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK; + else + *out_type = IRQ_TYPE_LEVEL_HIGH; + + return 0; +} + static const struct irq_domain_ops csky_irqdomain_ops = { .map = csky_irqdomain_map, - .xlate = irq_domain_xlate_onecell, + .xlate = csky_irq_domain_xlate_cells, }; #ifdef CONFIG_SMP @@ -161,6 +240,10 @@ csky_mpintc_init(struct device_node *node, struct device_node *parent) if (ret < 0) nr_irq = INTC_IRQS; + __trigger = kcalloc(nr_irq, sizeof(unsigned long), GFP_KERNEL); + if (__trigger == NULL) + return -ENXIO; + if (INTCG_base == NULL) { INTCG_base = ioremap(mfcr("cr<31, 14>"), INTCL_SIZE*nr_cpu_ids + INTCG_SIZE); diff --git a/drivers/irqchip/irq-gic-common.c b/drivers/irqchip/irq-gic-common.c index 3c93c6f4d1f1..b0a8215a13fc 100644 --- a/drivers/irqchip/irq-gic-common.c +++ b/drivers/irqchip/irq-gic-common.c @@ -1,17 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2002 ARM Limited, All Rights Reserved. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/interrupt.h> diff --git a/drivers/irqchip/irq-gic-common.h b/drivers/irqchip/irq-gic-common.h index 97e58fb6c232..5a46b6b57750 100644 --- a/drivers/irqchip/irq-gic-common.h +++ b/drivers/irqchip/irq-gic-common.h @@ -1,17 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (C) 2002 ARM Limited, All Rights Reserved. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _IRQ_GIC_COMMON_H diff --git a/drivers/irqchip/irq-gic-pm.c b/drivers/irqchip/irq-gic-pm.c index ecafd295c31c..1337ceceb59b 100644 --- a/drivers/irqchip/irq-gic-pm.c +++ b/drivers/irqchip/irq-gic-pm.c @@ -1,17 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2016 NVIDIA CORPORATION, All Rights Reserved. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/module.h> #include <linux/clk.h> @@ -19,7 +8,6 @@ #include <linux/of_irq.h> #include <linux/irqchip/arm-gic.h> #include <linux/platform_device.h> -#include <linux/pm_clock.h> #include <linux/pm_runtime.h> #include <linux/slab.h> @@ -28,17 +16,27 @@ struct gic_clk_data { const char *const *clocks; }; +struct gic_chip_pm { + struct gic_chip_data *chip_data; + const struct gic_clk_data *clk_data; + struct clk_bulk_data *clks; +}; + static int gic_runtime_resume(struct device *dev) { - struct gic_chip_data *gic = dev_get_drvdata(dev); + struct gic_chip_pm *chip_pm = dev_get_drvdata(dev); + struct gic_chip_data *gic = chip_pm->chip_data; + const struct gic_clk_data *data = chip_pm->clk_data; int ret; - ret = pm_clk_resume(dev); - if (ret) + ret = clk_bulk_prepare_enable(data->num_clocks, chip_pm->clks); + if (ret) { + dev_err(dev, "clk_enable failed: %d\n", ret); return ret; + } /* - * On the very first resume, the pointer to the driver data + * On the very first resume, the pointer to chip_pm->chip_data * will be NULL and this is intentional, because we do not * want to restore the GIC on the very first resume. So if * the pointer is not valid just return. @@ -54,35 +52,14 @@ static int gic_runtime_resume(struct device *dev) static int gic_runtime_suspend(struct device *dev) { - struct gic_chip_data *gic = dev_get_drvdata(dev); + struct gic_chip_pm *chip_pm = dev_get_drvdata(dev); + struct gic_chip_data *gic = chip_pm->chip_data; + const struct gic_clk_data *data = chip_pm->clk_data; gic_dist_save(gic); gic_cpu_save(gic); - return pm_clk_suspend(dev); -} - -static int gic_get_clocks(struct device *dev, const struct gic_clk_data *data) -{ - unsigned int i; - int ret; - - if (!dev || !data) - return -EINVAL; - - ret = pm_clk_create(dev); - if (ret) - return ret; - - for (i = 0; i < data->num_clocks; i++) { - ret = of_pm_clk_add_clk(dev, data->clocks[i]); - if (ret) { - dev_err(dev, "failed to add clock %s\n", - data->clocks[i]); - pm_clk_destroy(dev); - return ret; - } - } + clk_bulk_disable_unprepare(data->num_clocks, chip_pm->clks); return 0; } @@ -91,8 +68,8 @@ static int gic_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; const struct gic_clk_data *data; - struct gic_chip_data *gic; - int ret, irq; + struct gic_chip_pm *chip_pm; + int ret, irq, i; data = of_device_get_match_data(&pdev->dev); if (!data) { @@ -100,28 +77,41 @@ static int gic_probe(struct platform_device *pdev) return -ENODEV; } + chip_pm = devm_kzalloc(dev, sizeof(*chip_pm), GFP_KERNEL); + if (!chip_pm) + return -ENOMEM; + irq = irq_of_parse_and_map(dev->of_node, 0); if (!irq) { dev_err(dev, "no parent interrupt found!\n"); return -EINVAL; } - ret = gic_get_clocks(dev, data); + chip_pm->clks = devm_kcalloc(dev, data->num_clocks, + sizeof(*chip_pm->clks), GFP_KERNEL); + if (!chip_pm->clks) + return -ENOMEM; + + for (i = 0; i < data->num_clocks; i++) + chip_pm->clks[i].id = data->clocks[i]; + + ret = devm_clk_bulk_get(dev, data->num_clocks, chip_pm->clks); if (ret) goto irq_dispose; + chip_pm->clk_data = data; + dev_set_drvdata(dev, chip_pm); + pm_runtime_enable(dev); ret = pm_runtime_get_sync(dev); if (ret < 0) goto rpm_disable; - ret = gic_of_init_child(dev, &gic, irq); + ret = gic_of_init_child(dev, &chip_pm->chip_data, irq); if (ret) goto rpm_put; - platform_set_drvdata(pdev, gic); - pm_runtime_put(dev); dev_info(dev, "GIC IRQ controller registered\n"); @@ -132,7 +122,6 @@ rpm_put: pm_runtime_put_sync(dev); rpm_disable: pm_runtime_disable(dev); - pm_clk_destroy(dev); irq_dispose: irq_dispose_mapping(irq); @@ -142,6 +131,8 @@ irq_dispose: static const struct dev_pm_ops gic_pm_ops = { SET_RUNTIME_PM_OPS(gic_runtime_suspend, gic_runtime_resume, NULL) + SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) }; static const char * const gic400_clocks[] = { diff --git a/drivers/irqchip/irq-gic-v2m.c b/drivers/irqchip/irq-gic-v2m.c index f5fe0100f9ff..7338f90b2f9e 100644 --- a/drivers/irqchip/irq-gic-v2m.c +++ b/drivers/irqchip/irq-gic-v2m.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * ARM GIC v2m MSI(-X) support * Support for Message Signaled Interrupts for systems that @@ -7,10 +8,6 @@ * Authors: Suravee Suthikulpanit <suravee.suthikulpanit@amd.com> * Harish Kasiviswanathan <harish.kasiviswanathan@amd.com> * Brandon Anderson <brandon.anderson@amd.com> - * - * 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. */ #define pr_fmt(fmt) "GICv2m: " fmt @@ -56,6 +53,7 @@ /* List of flags for specific v2m implementation */ #define GICV2M_NEEDS_SPI_OFFSET 0x00000001 +#define GICV2M_GRAVITON_ADDRESS_ONLY 0x00000002 static LIST_HEAD(v2m_nodes); static DEFINE_SPINLOCK(v2m_lock); @@ -98,19 +96,30 @@ static struct msi_domain_info gicv2m_msi_domain_info = { .chip = &gicv2m_msi_irq_chip, }; +static phys_addr_t gicv2m_get_msi_addr(struct v2m_data *v2m, int hwirq) +{ + if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) + return v2m->res.start | ((hwirq - 32) << 3); + else + return v2m->res.start + V2M_MSI_SETSPI_NS; +} + static void gicv2m_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) { struct v2m_data *v2m = irq_data_get_irq_chip_data(data); - phys_addr_t addr = v2m->res.start + V2M_MSI_SETSPI_NS; + phys_addr_t addr = gicv2m_get_msi_addr(v2m, data->hwirq); msg->address_hi = upper_32_bits(addr); msg->address_lo = lower_32_bits(addr); - msg->data = data->hwirq; + if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) + msg->data = 0; + else + msg->data = data->hwirq; if (v2m->flags & GICV2M_NEEDS_SPI_OFFSET) msg->data -= v2m->spi_offset; - iommu_dma_map_msi_msg(data->irq, msg); + iommu_dma_compose_msi_msg(irq_data_get_msi_desc(data), msg); } static struct irq_chip gicv2m_irq_chip = { @@ -167,6 +176,7 @@ static void gicv2m_unalloc_msi(struct v2m_data *v2m, unsigned int hwirq, static int gicv2m_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs, void *args) { + msi_alloc_info_t *info = args; struct v2m_data *v2m = NULL, *tmp; int hwirq, offset, i, err = 0; @@ -186,6 +196,11 @@ static int gicv2m_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, hwirq = v2m->spi_start + offset; + err = iommu_dma_prepare_msi(info->desc, + gicv2m_get_msi_addr(v2m, hwirq)); + if (err) + return err; + for (i = 0; i < nr_irqs; i++) { err = gicv2m_irq_gic_domain_alloc(domain, virq + i, hwirq + i); if (err) @@ -301,7 +316,7 @@ static int gicv2m_allocate_domains(struct irq_domain *parent) static int __init gicv2m_init_one(struct fwnode_handle *fwnode, u32 spi_start, u32 nr_spis, - struct resource *res) + struct resource *res, u32 flags) { int ret; struct v2m_data *v2m; @@ -314,6 +329,7 @@ static int __init gicv2m_init_one(struct fwnode_handle *fwnode, INIT_LIST_HEAD(&v2m->entry); v2m->fwnode = fwnode; + v2m->flags = flags; memcpy(&v2m->res, res, sizeof(struct resource)); @@ -328,7 +344,14 @@ static int __init gicv2m_init_one(struct fwnode_handle *fwnode, v2m->spi_start = spi_start; v2m->nr_spis = nr_spis; } else { - u32 typer = readl_relaxed(v2m->base + V2M_MSI_TYPER); + u32 typer; + + /* Graviton should always have explicit spi_start/nr_spis */ + if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) { + ret = -EINVAL; + goto err_iounmap; + } + typer = readl_relaxed(v2m->base + V2M_MSI_TYPER); v2m->spi_start = V2M_MSI_TYPER_BASE_SPI(typer); v2m->nr_spis = V2M_MSI_TYPER_NUM_SPI(typer); @@ -349,18 +372,21 @@ static int __init gicv2m_init_one(struct fwnode_handle *fwnode, * * Broadom NS2 GICv2m implementation has an erratum where the MSI data * is 'spi_number - 32' + * + * Reading that register fails on the Graviton implementation */ - switch (readl_relaxed(v2m->base + V2M_MSI_IIDR)) { - case XGENE_GICV2M_MSI_IIDR: - v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; - v2m->spi_offset = v2m->spi_start; - break; - case BCM_NS2_GICV2M_MSI_IIDR: - v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; - v2m->spi_offset = 32; - break; + if (!(v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY)) { + switch (readl_relaxed(v2m->base + V2M_MSI_IIDR)) { + case XGENE_GICV2M_MSI_IIDR: + v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; + v2m->spi_offset = v2m->spi_start; + break; + case BCM_NS2_GICV2M_MSI_IIDR: + v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; + v2m->spi_offset = 32; + break; + } } - v2m->bm = kcalloc(BITS_TO_LONGS(v2m->nr_spis), sizeof(long), GFP_KERNEL); if (!v2m->bm) { @@ -413,7 +439,8 @@ static int __init gicv2m_of_init(struct fwnode_handle *parent_handle, pr_info("DT overriding V2M MSI_TYPER (base:%u, num:%u)\n", spi_start, nr_spis); - ret = gicv2m_init_one(&child->fwnode, spi_start, nr_spis, &res); + ret = gicv2m_init_one(&child->fwnode, spi_start, nr_spis, + &res, 0); if (ret) { of_node_put(child); break; @@ -445,8 +472,27 @@ static struct fwnode_handle *gicv2m_get_fwnode(struct device *dev) return data->fwnode; } +static bool acpi_check_amazon_graviton_quirks(void) +{ + static struct acpi_table_madt *madt; + acpi_status status; + bool rc = false; + +#define ACPI_AMZN_OEM_ID "AMAZON" + + status = acpi_get_table(ACPI_SIG_MADT, 0, + (struct acpi_table_header **)&madt); + + if (ACPI_FAILURE(status) || !madt) + return rc; + rc = !memcmp(madt->header.oem_id, ACPI_AMZN_OEM_ID, ACPI_OEM_ID_SIZE); + acpi_put_table((struct acpi_table_header *)madt); + + return rc; +} + static int __init -acpi_parse_madt_msi(struct acpi_subtable_header *header, +acpi_parse_madt_msi(union acpi_subtable_headers *header, const unsigned long end) { int ret; @@ -454,6 +500,7 @@ acpi_parse_madt_msi(struct acpi_subtable_header *header, u32 spi_start = 0, nr_spis = 0; struct acpi_madt_generic_msi_frame *m; struct fwnode_handle *fwnode; + u32 flags = 0; m = (struct acpi_madt_generic_msi_frame *)header; if (BAD_MADT_ENTRY(m, end)) @@ -463,6 +510,13 @@ acpi_parse_madt_msi(struct acpi_subtable_header *header, res.end = m->base_address + SZ_4K - 1; res.flags = IORESOURCE_MEM; + if (acpi_check_amazon_graviton_quirks()) { + pr_info("applying Amazon Graviton quirk\n"); + res.end = res.start + SZ_8K - 1; + flags |= GICV2M_GRAVITON_ADDRESS_ONLY; + gicv2m_msi_domain_info.flags &= ~MSI_FLAG_MULTI_PCI_MSI; + } + if (m->flags & ACPI_MADT_OVERRIDE_SPI_VALUES) { spi_start = m->spi_base; nr_spis = m->spi_count; @@ -477,7 +531,7 @@ acpi_parse_madt_msi(struct acpi_subtable_header *header, return -EINVAL; } - ret = gicv2m_init_one(fwnode, spi_start, nr_spis, &res); + ret = gicv2m_init_one(fwnode, spi_start, nr_spis, &res, flags); if (ret) irq_domain_free_fwnode(fwnode); diff --git a/drivers/irqchip/irq-gic-v3-its-pci-msi.c b/drivers/irqchip/irq-gic-v3-its-pci-msi.c index 8d6d009d1d58..229d586c3d7a 100644 --- a/drivers/irqchip/irq-gic-v3-its-pci-msi.c +++ b/drivers/irqchip/irq-gic-v3-its-pci-msi.c @@ -1,18 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2013-2015 ARM Limited, All Rights Reserved. * Author: Marc Zyngier <marc.zyngier@arm.com> - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/acpi_iort.h> @@ -159,7 +148,7 @@ static int __init its_pci_of_msi_init(void) #ifdef CONFIG_ACPI static int __init -its_pci_msi_parse_madt(struct acpi_subtable_header *header, +its_pci_msi_parse_madt(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_generic_translator *its_entry; diff --git a/drivers/irqchip/irq-gic-v3-its-platform-msi.c b/drivers/irqchip/irq-gic-v3-its-platform-msi.c index 7b8e87b493fe..daa6d5053bc3 100644 --- a/drivers/irqchip/irq-gic-v3-its-platform-msi.c +++ b/drivers/irqchip/irq-gic-v3-its-platform-msi.c @@ -1,18 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2013-2015 ARM Limited, All Rights Reserved. * Author: Marc Zyngier <marc.zyngier@arm.com> - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/acpi_iort.h> @@ -117,7 +106,7 @@ static int __init its_pmsi_init_one(struct fwnode_handle *fwnode, #ifdef CONFIG_ACPI static int __init -its_pmsi_parse_madt(struct acpi_subtable_header *header, +its_pmsi_parse_madt(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_generic_translator *its_entry; diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 7577755bdcf4..730fbe0e2a9d 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -1,18 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2013-2017 ARM Limited, All Rights Reserved. * Author: Marc Zyngier <marc.zyngier@arm.com> - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/acpi.h> @@ -26,7 +15,6 @@ #include <linux/interrupt.h> #include <linux/irqdomain.h> #include <linux/list.h> -#include <linux/list_sort.h> #include <linux/log2.h> #include <linux/memblock.h> #include <linux/mm.h> @@ -197,7 +185,7 @@ static struct its_collection *dev_event_to_col(struct its_device *its_dev, static struct its_collection *valid_col(struct its_collection *col) { - if (WARN_ON_ONCE(col->target_address & GENMASK_ULL(0, 15))) + if (WARN_ON_ONCE(col->target_address & GENMASK_ULL(15, 0))) return NULL; return col; @@ -745,32 +733,43 @@ static void its_flush_cmd(struct its_node *its, struct its_cmd_block *cmd) } static int its_wait_for_range_completion(struct its_node *its, - struct its_cmd_block *from, + u64 prev_idx, struct its_cmd_block *to) { - u64 rd_idx, from_idx, to_idx; + u64 rd_idx, to_idx, linear_idx; u32 count = 1000000; /* 1s! */ - from_idx = its_cmd_ptr_to_offset(its, from); + /* Linearize to_idx if the command set has wrapped around */ to_idx = its_cmd_ptr_to_offset(its, to); + if (to_idx < prev_idx) + to_idx += ITS_CMD_QUEUE_SZ; + + linear_idx = prev_idx; while (1) { + s64 delta; + rd_idx = readl_relaxed(its->base + GITS_CREADR); - /* Direct case */ - if (from_idx < to_idx && rd_idx >= to_idx) - break; + /* + * Compute the read pointer progress, taking the + * potential wrap-around into account. + */ + delta = rd_idx - prev_idx; + if (rd_idx < prev_idx) + delta += ITS_CMD_QUEUE_SZ; - /* Wrapped case */ - if (from_idx >= to_idx && rd_idx >= to_idx && rd_idx < from_idx) + linear_idx += delta; + if (linear_idx >= to_idx) break; count--; if (!count) { - pr_err_ratelimited("ITS queue timeout (%llu %llu %llu)\n", - from_idx, to_idx, rd_idx); + pr_err_ratelimited("ITS queue timeout (%llu %llu)\n", + to_idx, linear_idx); return -1; } + prev_idx = rd_idx; cpu_relax(); udelay(1); } @@ -787,6 +786,7 @@ void name(struct its_node *its, \ struct its_cmd_block *cmd, *sync_cmd, *next_cmd; \ synctype *sync_obj; \ unsigned long flags; \ + u64 rd_idx; \ \ raw_spin_lock_irqsave(&its->lock, flags); \ \ @@ -808,10 +808,11 @@ void name(struct its_node *its, \ } \ \ post: \ + rd_idx = readl_relaxed(its->base + GITS_CREADR); \ next_cmd = its_post_commands(its); \ raw_spin_unlock_irqrestore(&its->lock, flags); \ \ - if (its_wait_for_range_completion(its, cmd, next_cmd)) \ + if (its_wait_for_range_completion(its, rd_idx, next_cmd)) \ pr_err_ratelimited("ITS cmd %ps failed\n", builder); \ } @@ -1179,7 +1180,7 @@ static void its_irq_compose_msi_msg(struct irq_data *d, struct msi_msg *msg) msg->address_hi = upper_32_bits(addr); msg->data = its_get_event_id(d); - iommu_dma_map_msi_msg(d->irq, msg); + iommu_dma_compose_msi_msg(irq_data_get_msi_desc(d), msg); } static int its_irq_set_irqchip_state(struct irq_data *d, @@ -1465,9 +1466,8 @@ static struct lpi_range *mk_lpi_range(u32 base, u32 span) { struct lpi_range *range; - range = kzalloc(sizeof(*range), GFP_KERNEL); + range = kmalloc(sizeof(*range), GFP_KERNEL); if (range) { - INIT_LIST_HEAD(&range->entry); range->base_id = base; range->span = span; } @@ -1475,31 +1475,6 @@ static struct lpi_range *mk_lpi_range(u32 base, u32 span) return range; } -static int lpi_range_cmp(void *priv, struct list_head *a, struct list_head *b) -{ - struct lpi_range *ra, *rb; - - ra = container_of(a, struct lpi_range, entry); - rb = container_of(b, struct lpi_range, entry); - - return ra->base_id - rb->base_id; -} - -static void merge_lpi_ranges(void) -{ - struct lpi_range *range, *tmp; - - list_for_each_entry_safe(range, tmp, &lpi_range_list, entry) { - if (!list_is_last(&range->entry, &lpi_range_list) && - (tmp->base_id == (range->base_id + range->span))) { - tmp->base_id = range->base_id; - tmp->span += range->span; - list_del(&range->entry); - kfree(range); - } - } -} - static int alloc_lpi_range(u32 nr_lpis, u32 *base) { struct lpi_range *range, *tmp; @@ -1529,25 +1504,49 @@ static int alloc_lpi_range(u32 nr_lpis, u32 *base) return err; } +static void merge_lpi_ranges(struct lpi_range *a, struct lpi_range *b) +{ + if (&a->entry == &lpi_range_list || &b->entry == &lpi_range_list) + return; + if (a->base_id + a->span != b->base_id) + return; + b->base_id = a->base_id; + b->span += a->span; + list_del(&a->entry); + kfree(a); +} + static int free_lpi_range(u32 base, u32 nr_lpis) { - struct lpi_range *new; - int err = 0; + struct lpi_range *new, *old; + + new = mk_lpi_range(base, nr_lpis); + if (!new) + return -ENOMEM; mutex_lock(&lpi_range_lock); - new = mk_lpi_range(base, nr_lpis); - if (!new) { - err = -ENOMEM; - goto out; + list_for_each_entry_reverse(old, &lpi_range_list, entry) { + if (old->base_id < base) + break; } + /* + * old is the last element with ->base_id smaller than base, + * so new goes right after it. If there are no elements with + * ->base_id smaller than base, &old->entry ends up pointing + * at the head of the list, and inserting new it the start of + * the list is the right thing to do in that case as well. + */ + list_add(&new->entry, &old->entry); + /* + * Now check if we can merge with the preceding and/or + * following ranges. + */ + merge_lpi_ranges(old, new); + merge_lpi_ranges(new, list_next_entry(new, entry)); - list_add(&new->entry, &lpi_range_list); - list_sort(NULL, &lpi_range_list, lpi_range_cmp); - merge_lpi_ranges(); -out: mutex_unlock(&lpi_range_lock); - return err; + return 0; } static int __init its_lpi_init(u32 id_bits) @@ -2487,7 +2486,7 @@ static int its_msi_prepare(struct irq_domain *domain, struct device *dev, int err = 0; /* - * We ignore "dev" entierely, and rely on the dev_id that has + * We ignore "dev" entirely, and rely on the dev_id that has * been passed via the scratchpad. This limits this domain's * usefulness to upper layers that definitely know that they * are built on top of the ITS. @@ -2566,6 +2565,7 @@ static int its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, { msi_alloc_info_t *info = args; struct its_device *its_dev = info->scratchpad[0].ptr; + struct its_node *its = its_dev->its; irq_hw_number_t hwirq; int err; int i; @@ -2574,6 +2574,10 @@ static int its_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, if (err) return err; + err = iommu_dma_prepare_msi(info->desc, its->get_msi_base(its_dev)); + if (err) + return err; + for (i = 0; i < nr_irqs; i++) { err = its_irq_gic_domain_alloc(domain, virq + i, hwirq + i); if (err) @@ -3830,13 +3834,13 @@ static int __init acpi_get_its_numa_node(u32 its_id) return NUMA_NO_NODE; } -static int __init gic_acpi_match_srat_its(struct acpi_subtable_header *header, +static int __init gic_acpi_match_srat_its(union acpi_subtable_headers *header, const unsigned long end) { return 0; } -static int __init gic_acpi_parse_srat_its(struct acpi_subtable_header *header, +static int __init gic_acpi_parse_srat_its(union acpi_subtable_headers *header, const unsigned long end) { int node; @@ -3903,7 +3907,7 @@ static int __init acpi_get_its_numa_node(u32 its_id) { return NUMA_NO_NODE; } static void __init acpi_its_srat_maps_free(void) { } #endif -static int __init gic_acpi_parse_madt_its(struct acpi_subtable_header *header, +static int __init gic_acpi_parse_madt_its(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_generic_translator *its_entry; diff --git a/drivers/irqchip/irq-gic-v3-mbi.c b/drivers/irqchip/irq-gic-v3-mbi.c index fbfa7ff6deb1..563a9b366294 100644 --- a/drivers/irqchip/irq-gic-v3-mbi.c +++ b/drivers/irqchip/irq-gic-v3-mbi.c @@ -84,6 +84,7 @@ static void mbi_free_msi(struct mbi_range *mbi, unsigned int hwirq, static int mbi_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs, void *args) { + msi_alloc_info_t *info = args; struct mbi_range *mbi = NULL; int hwirq, offset, i, err = 0; @@ -104,6 +105,11 @@ static int mbi_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, hwirq = mbi->spi_start + offset; + err = iommu_dma_prepare_msi(info->desc, + mbi_phys_base + GICD_SETSPI_NSR); + if (err) + return err; + for (i = 0; i < nr_irqs; i++) { err = mbi_irq_gic_domain_alloc(domain, virq + i, hwirq + i); if (err) @@ -142,7 +148,7 @@ static void mbi_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) msg[0].address_lo = lower_32_bits(mbi_phys_base + GICD_SETSPI_NSR); msg[0].data = data->parent_data->hwirq; - iommu_dma_map_msi_msg(data->irq, msg); + iommu_dma_compose_msi_msg(irq_data_get_msi_desc(data), msg); } #ifdef CONFIG_PCI_MSI @@ -202,7 +208,7 @@ static void mbi_compose_mbi_msg(struct irq_data *data, struct msi_msg *msg) msg[1].address_lo = lower_32_bits(mbi_phys_base + GICD_CLRSPI_NSR); msg[1].data = data->parent_data->hwirq; - iommu_dma_map_msi_msg(data->irq, &msg[1]); + iommu_dma_compose_msi_msg(irq_data_get_msi_desc(data), &msg[1]); } /* Platform-MSI specific irqchip */ diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 15e55d327505..9bca4896fa6f 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -1,18 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2013-2017 ARM Limited, All Rights Reserved. * Author: Marc Zyngier <marc.zyngier@arm.com> - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #define pr_fmt(fmt) "GICv3: " fmt @@ -472,8 +461,12 @@ static void gic_deactivate_unhandled(u32 irqnr) static inline void gic_handle_nmi(u32 irqnr, struct pt_regs *regs) { + bool irqs_enabled = interrupts_enabled(regs); int err; + if (irqs_enabled) + nmi_enter(); + if (static_branch_likely(&supports_deactivate_key)) gic_write_eoir(irqnr); /* @@ -485,6 +478,9 @@ static inline void gic_handle_nmi(u32 irqnr, struct pt_regs *regs) err = handle_domain_nmi(gic_data.domain, irqnr, regs); if (err) gic_deactivate_unhandled(irqnr); + + if (irqs_enabled) + nmi_exit(); } static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) @@ -1343,6 +1339,9 @@ static int __init gic_init_bases(void __iomem *dist_base, if (gic_dist_supports_lpis()) { its_init(handle, &gic_data.rdists, gic_data.domain); its_cpu_init(); + } else { + if (IS_ENABLED(CONFIG_ARM_GIC_V2M)) + gicv2m_init(handle, gic_data.domain); } if (gic_prio_masking_enabled()) { @@ -1593,7 +1592,7 @@ gic_acpi_register_redist(phys_addr_t phys_base, void __iomem *redist_base) } static int __init -gic_acpi_parse_madt_redist(struct acpi_subtable_header *header, +gic_acpi_parse_madt_redist(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_generic_redistributor *redist = @@ -1611,7 +1610,7 @@ gic_acpi_parse_madt_redist(struct acpi_subtable_header *header, } static int __init -gic_acpi_parse_madt_gicc(struct acpi_subtable_header *header, +gic_acpi_parse_madt_gicc(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_generic_interrupt *gicc = @@ -1653,14 +1652,14 @@ static int __init gic_acpi_collect_gicr_base(void) return -ENODEV; } -static int __init gic_acpi_match_gicr(struct acpi_subtable_header *header, +static int __init gic_acpi_match_gicr(union acpi_subtable_headers *header, const unsigned long end) { /* Subtable presence means that redist exists, that's it */ return 0; } -static int __init gic_acpi_match_gicc(struct acpi_subtable_header *header, +static int __init gic_acpi_match_gicc(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_generic_interrupt *gicc = @@ -1726,7 +1725,7 @@ static bool __init acpi_validate_gic_table(struct acpi_subtable_header *header, return true; } -static int __init gic_acpi_parse_virt_madt_gicc(struct acpi_subtable_header *header, +static int __init gic_acpi_parse_virt_madt_gicc(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_generic_interrupt *gicc = diff --git a/drivers/irqchip/irq-gic-v4.c b/drivers/irqchip/irq-gic-v4.c index dba9d67cb9c1..563e87ed0766 100644 --- a/drivers/irqchip/irq-gic-v4.c +++ b/drivers/irqchip/irq-gic-v4.c @@ -1,18 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2016,2017 ARM Limited, All Rights Reserved. * Author: Marc Zyngier <marc.zyngier@arm.com> - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/interrupt.h> diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index fd3110c171ba..e45f45e68720 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -1,10 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2002 ARM Limited, All Rights Reserved. * - * 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. - * * Interrupt architecture for the GIC: * * o There is one Interrupt Distributor, which receives interrupts @@ -1495,7 +1492,7 @@ static struct } acpi_data __initdata; static int __init -gic_acpi_parse_madt_cpu(struct acpi_subtable_header *header, +gic_acpi_parse_madt_cpu(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_generic_interrupt *processor; @@ -1527,7 +1524,7 @@ gic_acpi_parse_madt_cpu(struct acpi_subtable_header *header, } /* The things you have to do to just *count* something... */ -static int __init acpi_dummy_func(struct acpi_subtable_header *header, +static int __init acpi_dummy_func(union acpi_subtable_headers *header, const unsigned long end) { return 0; diff --git a/drivers/irqchip/irq-goldfish-pic.c b/drivers/irqchip/irq-goldfish-pic.c index 2a92f03c73e4..4f021530e7f3 100644 --- a/drivers/irqchip/irq-goldfish-pic.c +++ b/drivers/irqchip/irq-goldfish-pic.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Driver for MIPS Goldfish Programmable Interrupt Controller. * * Author: Miodrag Dinic <miodrag.dinic@mips.com> - * - * 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. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/irqchip/irq-hip04.c b/drivers/irqchip/irq-hip04.c index 5b4fd2f4e5f8..cf705827599c 100644 --- a/drivers/irqchip/irq-hip04.c +++ b/drivers/irqchip/irq-hip04.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Hisilicon HiP04 INTC * @@ -5,10 +6,6 @@ * Copyright (c) 2013-2014 Hisilicon Ltd. * Copyright (c) 2013-2014 Linaro Ltd. * - * 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. - * * Interrupt architecture for the HIP04 INTC: * * o There is one Interrupt Distributor, which receives interrupts diff --git a/drivers/irqchip/irq-imx-gpcv2.c b/drivers/irqchip/irq-imx-gpcv2.c index 66501ea4fd75..bf2237ac5d09 100644 --- a/drivers/irqchip/irq-imx-gpcv2.c +++ b/drivers/irqchip/irq-imx-gpcv2.c @@ -1,9 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2015 Freescale Semiconductor, Inc. - * - * 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 <linux/of_address.h> diff --git a/drivers/irqchip/irq-imx-irqsteer.c b/drivers/irqchip/irq-imx-irqsteer.c index 88df3d00052c..290531ec3d61 100644 --- a/drivers/irqchip/irq-imx-irqsteer.c +++ b/drivers/irqchip/irq-imx-irqsteer.c @@ -144,7 +144,6 @@ static int imx_irqsteer_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct irqsteer_data *data; - struct resource *res; u32 irqs_num; int i, ret; @@ -152,8 +151,7 @@ static int imx_irqsteer_probe(struct platform_device *pdev) if (!data) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - data->regs = devm_ioremap_resource(&pdev->dev, res); + data->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(data->regs)) { dev_err(&pdev->dev, "failed to initialize reg\n"); return PTR_ERR(data->regs); diff --git a/drivers/irqchip/irq-ingenic.c b/drivers/irqchip/irq-ingenic.c index 2ff08986b536..f126255b3260 100644 --- a/drivers/irqchip/irq-ingenic.c +++ b/drivers/irqchip/irq-ingenic.c @@ -1,16 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> * JZ4740 platform IRQ support - * - * 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. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 675 Mass Ave, Cambridge, MA 02139, USA. - * */ #include <linux/errno.h> diff --git a/drivers/irqchip/irq-ixp4xx.c b/drivers/irqchip/irq-ixp4xx.c new file mode 100644 index 000000000000..6751c35b7e1d --- /dev/null +++ b/drivers/irqchip/irq-ixp4xx.c @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * irqchip for the IXP4xx interrupt controller + * Copyright (C) 2019 Linus Walleij <linus.walleij@linaro.org> + * + * Based on arch/arm/mach-ixp4xx/common.c + * Copyright 2002 (C) Intel Corporation + * Copyright 2003-2004 (C) MontaVista, Software, Inc. + * Copyright (C) Deepak Saxena <dsaxena@plexity.net> + */ +#include <linux/bitops.h> +#include <linux/gpio/driver.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/irqchip.h> +#include <linux/irqchip/irq-ixp4xx.h> +#include <linux/irqdomain.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/cpu.h> + +#include <asm/exception.h> +#include <asm/mach/irq.h> + +#define IXP4XX_ICPR 0x00 /* Interrupt Status */ +#define IXP4XX_ICMR 0x04 /* Interrupt Enable */ +#define IXP4XX_ICLR 0x08 /* Interrupt IRQ/FIQ Select */ +#define IXP4XX_ICIP 0x0C /* IRQ Status */ +#define IXP4XX_ICFP 0x10 /* FIQ Status */ +#define IXP4XX_ICHR 0x14 /* Interrupt Priority */ +#define IXP4XX_ICIH 0x18 /* IRQ Highest Pri Int */ +#define IXP4XX_ICFH 0x1C /* FIQ Highest Pri Int */ + +/* IXP43x and IXP46x-only */ +#define IXP4XX_ICPR2 0x20 /* Interrupt Status 2 */ +#define IXP4XX_ICMR2 0x24 /* Interrupt Enable 2 */ +#define IXP4XX_ICLR2 0x28 /* Interrupt IRQ/FIQ Select 2 */ +#define IXP4XX_ICIP2 0x2C /* IRQ Status */ +#define IXP4XX_ICFP2 0x30 /* FIQ Status */ +#define IXP4XX_ICEEN 0x34 /* Error High Pri Enable */ + +/** + * struct ixp4xx_irq - state container for the Faraday IRQ controller + * @irqbase: IRQ controller memory base in virtual memory + * @is_356: if this is an IXP43x, IXP45x or IX46x SoC (with 64 IRQs) + * @irqchip: irqchip for this instance + * @domain: IRQ domain for this instance + */ +struct ixp4xx_irq { + void __iomem *irqbase; + bool is_356; + struct irq_chip irqchip; + struct irq_domain *domain; +}; + +/* Local static state container */ +static struct ixp4xx_irq ixirq; + +/* GPIO Clocks */ +#define IXP4XX_GPIO_CLK_0 14 +#define IXP4XX_GPIO_CLK_1 15 + +static int ixp4xx_set_irq_type(struct irq_data *d, unsigned int type) +{ + /* All are level active high (asserted) here */ + if (type != IRQ_TYPE_LEVEL_HIGH) + return -EINVAL; + return 0; +} + +static void ixp4xx_irq_mask(struct irq_data *d) +{ + struct ixp4xx_irq *ixi = irq_data_get_irq_chip_data(d); + u32 val; + + if (ixi->is_356 && d->hwirq >= 32) { + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR2); + val &= ~BIT(d->hwirq - 32); + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR2); + } else { + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR); + val &= ~BIT(d->hwirq); + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR); + } +} + +/* + * Level triggered interrupts on GPIO lines can only be cleared when the + * interrupt condition disappears. + */ +static void ixp4xx_irq_unmask(struct irq_data *d) +{ + struct ixp4xx_irq *ixi = irq_data_get_irq_chip_data(d); + u32 val; + + if (ixi->is_356 && d->hwirq >= 32) { + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR2); + val |= BIT(d->hwirq - 32); + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR2); + } else { + val = __raw_readl(ixi->irqbase + IXP4XX_ICMR); + val |= BIT(d->hwirq); + __raw_writel(val, ixi->irqbase + IXP4XX_ICMR); + } +} + +asmlinkage void __exception_irq_entry ixp4xx_handle_irq(struct pt_regs *regs) +{ + struct ixp4xx_irq *ixi = &ixirq; + unsigned long status; + int i; + + status = __raw_readl(ixi->irqbase + IXP4XX_ICIP); + for_each_set_bit(i, &status, 32) + handle_domain_irq(ixi->domain, i, regs); + + /* + * IXP465/IXP435 has an upper IRQ status register + */ + if (ixi->is_356) { + status = __raw_readl(ixi->irqbase + IXP4XX_ICIP2); + for_each_set_bit(i, &status, 32) + handle_domain_irq(ixi->domain, i + 32, regs); + } +} + +static int ixp4xx_irq_domain_translate(struct irq_domain *domain, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + /* We support standard DT translation */ + if (is_of_node(fwspec->fwnode) && fwspec->param_count == 2) { + *hwirq = fwspec->param[0]; + *type = fwspec->param[1]; + return 0; + } + + if (is_fwnode_irqchip(fwspec->fwnode)) { + if (fwspec->param_count != 2) + return -EINVAL; + *hwirq = fwspec->param[0]; + *type = fwspec->param[1]; + WARN_ON(*type == IRQ_TYPE_NONE); + return 0; + } + + return -EINVAL; +} + +static int ixp4xx_irq_domain_alloc(struct irq_domain *d, + unsigned int irq, unsigned int nr_irqs, + void *data) +{ + struct ixp4xx_irq *ixi = d->host_data; + irq_hw_number_t hwirq; + unsigned int type = IRQ_TYPE_NONE; + struct irq_fwspec *fwspec = data; + int ret; + int i; + + ret = ixp4xx_irq_domain_translate(d, fwspec, &hwirq, &type); + if (ret) + return ret; + + for (i = 0; i < nr_irqs; i++) { + /* + * TODO: after converting IXP4xx to only device tree, set + * handle_bad_irq as default handler and assume all consumers + * call .set_type() as this is provided in the second cell in + * the device tree phandle. + */ + irq_domain_set_info(d, + irq + i, + hwirq + i, + &ixi->irqchip, + ixi, + handle_level_irq, + NULL, NULL); + irq_set_probe(irq + i); + } + + return 0; +} + +/* + * This needs to be a hierarchical irqdomain to work well with the + * GPIO irqchip (which is lower in the hierarchy) + */ +static const struct irq_domain_ops ixp4xx_irqdomain_ops = { + .translate = ixp4xx_irq_domain_translate, + .alloc = ixp4xx_irq_domain_alloc, + .free = irq_domain_free_irqs_common, +}; + +/** + * ixp4xx_get_irq_domain() - retrieve the ixp4xx irq domain + * + * This function will go away when we transition to DT probing. + */ +struct irq_domain *ixp4xx_get_irq_domain(void) +{ + struct ixp4xx_irq *ixi = &ixirq; + + return ixi->domain; +} +EXPORT_SYMBOL_GPL(ixp4xx_get_irq_domain); + +/* + * This is the Linux IRQ to hwirq mapping table. This goes away when + * we have DT support as all IRQ resources are defined in the device + * tree. It will register all the IRQs that are not used by the hierarchical + * GPIO IRQ chip. The "holes" inbetween these IRQs will be requested by + * the GPIO driver using . This is a step-gap solution. + */ +struct ixp4xx_irq_chunk { + int irq; + int hwirq; + int nr_irqs; +}; + +static const struct ixp4xx_irq_chunk ixp4xx_irq_chunks[] = { + { + .irq = 16, + .hwirq = 0, + .nr_irqs = 6, + }, + { + .irq = 24, + .hwirq = 8, + .nr_irqs = 11, + }, + { + .irq = 46, + .hwirq = 30, + .nr_irqs = 2, + }, + /* Only on the 436 variants */ + { + .irq = 48, + .hwirq = 32, + .nr_irqs = 10, + }, +}; + +/** + * ixp4x_irq_setup() - Common setup code for the IXP4xx interrupt controller + * @ixi: State container + * @irqbase: Virtual memory base for the interrupt controller + * @fwnode: Corresponding fwnode abstraction for this controller + * @is_356: if this is an IXP43x, IXP45x or IXP46x SoC variant + */ +static int __init ixp4xx_irq_setup(struct ixp4xx_irq *ixi, + void __iomem *irqbase, + struct fwnode_handle *fwnode, + bool is_356) +{ + int nr_irqs; + + ixi->irqbase = irqbase; + ixi->is_356 = is_356; + + /* Route all sources to IRQ instead of FIQ */ + __raw_writel(0x0, ixi->irqbase + IXP4XX_ICLR); + + /* Disable all interrupts */ + __raw_writel(0x0, ixi->irqbase + IXP4XX_ICMR); + + if (is_356) { + /* Route upper 32 sources to IRQ instead of FIQ */ + __raw_writel(0x0, ixi->irqbase + IXP4XX_ICLR2); + + /* Disable upper 32 interrupts */ + __raw_writel(0x0, ixi->irqbase + IXP4XX_ICMR2); + + nr_irqs = 64; + } else { + nr_irqs = 32; + } + + ixi->irqchip.name = "IXP4xx"; + ixi->irqchip.irq_mask = ixp4xx_irq_mask; + ixi->irqchip.irq_unmask = ixp4xx_irq_unmask; + ixi->irqchip.irq_set_type = ixp4xx_set_irq_type; + + ixi->domain = irq_domain_create_linear(fwnode, nr_irqs, + &ixp4xx_irqdomain_ops, + ixi); + if (!ixi->domain) { + pr_crit("IXP4XX: can not add primary irqdomain\n"); + return -ENODEV; + } + + set_handle_irq(ixp4xx_handle_irq); + + return 0; +} + +/** + * ixp4xx_irq_init() - Function to initialize the irqchip from boardfiles + * @irqbase: physical base for the irq controller + * @is_356: if this is an IXP43x, IXP45x or IXP46x SoC variant + */ +void __init ixp4xx_irq_init(resource_size_t irqbase, + bool is_356) +{ + struct ixp4xx_irq *ixi = &ixirq; + void __iomem *base; + struct fwnode_handle *fwnode; + struct irq_fwspec fwspec; + int nr_chunks; + int ret; + int i; + + base = ioremap(irqbase, 0x100); + if (!base) { + pr_crit("IXP4XX: could not ioremap interrupt controller\n"); + return; + } + fwnode = irq_domain_alloc_fwnode(base); + if (!fwnode) { + pr_crit("IXP4XX: no domain handle\n"); + return; + } + ret = ixp4xx_irq_setup(ixi, base, fwnode, is_356); + if (ret) { + pr_crit("IXP4XX: failed to set up irqchip\n"); + irq_domain_free_fwnode(fwnode); + } + + nr_chunks = ARRAY_SIZE(ixp4xx_irq_chunks); + if (!is_356) + nr_chunks--; + + /* + * After adding OF support, this is no longer needed: irqs + * will be allocated for the respective fwnodes. + */ + for (i = 0; i < nr_chunks; i++) { + const struct ixp4xx_irq_chunk *chunk = &ixp4xx_irq_chunks[i]; + + pr_info("Allocate Linux IRQs %d..%d HW IRQs %d..%d\n", + chunk->irq, chunk->irq + chunk->nr_irqs - 1, + chunk->hwirq, chunk->hwirq + chunk->nr_irqs - 1); + fwspec.fwnode = fwnode; + fwspec.param[0] = chunk->hwirq; + fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH; + fwspec.param_count = 2; + ret = __irq_domain_alloc_irqs(ixi->domain, + chunk->irq, + chunk->nr_irqs, + NUMA_NO_NODE, + &fwspec, + false, + NULL); + if (ret < 0) { + pr_crit("IXP4XX: can not allocate irqs in hierarchy %d\n", + ret); + return; + } + } +} +EXPORT_SYMBOL_GPL(ixp4xx_irq_init); + +#ifdef CONFIG_OF +int __init ixp4xx_of_init_irq(struct device_node *np, + struct device_node *parent) +{ + struct ixp4xx_irq *ixi = &ixirq; + void __iomem *base; + struct fwnode_handle *fwnode; + bool is_356; + int ret; + + base = of_iomap(np, 0); + if (!base) { + pr_crit("IXP4XX: could not ioremap interrupt controller\n"); + return -ENODEV; + } + fwnode = of_node_to_fwnode(np); + + /* These chip variants have 64 interrupts */ + is_356 = of_device_is_compatible(np, "intel,ixp43x-interrupt") || + of_device_is_compatible(np, "intel,ixp45x-interrupt") || + of_device_is_compatible(np, "intel,ixp46x-interrupt"); + + ret = ixp4xx_irq_setup(ixi, base, fwnode, is_356); + if (ret) + pr_crit("IXP4XX: failed to set up irqchip\n"); + + return ret; +} +IRQCHIP_DECLARE(ixp42x, "intel,ixp42x-interrupt", + ixp4xx_of_init_irq); +IRQCHIP_DECLARE(ixp43x, "intel,ixp43x-interrupt", + ixp4xx_of_init_irq); +IRQCHIP_DECLARE(ixp45x, "intel,ixp45x-interrupt", + ixp4xx_of_init_irq); +IRQCHIP_DECLARE(ixp46x, "intel,ixp46x-interrupt", + ixp4xx_of_init_irq); +#endif diff --git a/drivers/irqchip/irq-lpc32xx.c b/drivers/irqchip/irq-lpc32xx.c index a48357d369b5..7d9b388afe64 100644 --- a/drivers/irqchip/irq-lpc32xx.c +++ b/drivers/irqchip/irq-lpc32xx.c @@ -1,12 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright 2015-2016 Vladimir Zapolskiy <vz@mleia.com> - * - * The code contained herein is licensed under the GNU General Public - * License. You may obtain a copy of the GNU General Public License - * Version 2 or later at the following locations: - * - * http://www.opensource.org/licenses/gpl-license.html - * http://www.gnu.org/copyleft/gpl.html */ #define pr_fmt(fmt) "%s: " fmt, __func__ diff --git a/drivers/irqchip/irq-ls-scfg-msi.c b/drivers/irqchip/irq-ls-scfg-msi.c index c671b3212010..61dbfda08527 100644 --- a/drivers/irqchip/irq-ls-scfg-msi.c +++ b/drivers/irqchip/irq-ls-scfg-msi.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Freescale SCFG MSI(-X) support * * Copyright (C) 2016 Freescale Semiconductor. * * Author: Minghuan Lian <Minghuan.Lian@nxp.com> - * - * 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 <linux/kernel.h> @@ -100,7 +97,7 @@ static void ls_scfg_msi_compose_msg(struct irq_data *data, struct msi_msg *msg) msg->data |= cpumask_first(mask); } - iommu_dma_map_msi_msg(data->irq, msg); + iommu_dma_compose_msi_msg(irq_data_get_msi_desc(data), msg); } static int ls_scfg_msi_set_affinity(struct irq_data *irq_data, @@ -141,6 +138,7 @@ static int ls_scfg_msi_domain_irq_alloc(struct irq_domain *domain, unsigned int nr_irqs, void *args) { + msi_alloc_info_t *info = args; struct ls_scfg_msi *msi_data = domain->host_data; int pos, err = 0; @@ -157,6 +155,10 @@ static int ls_scfg_msi_domain_irq_alloc(struct irq_domain *domain, if (err) return err; + err = iommu_dma_prepare_msi(info->desc, msi_data->msiir_addr); + if (err) + return err; + irq_domain_set_info(domain, virq, pos, &ls_scfg_msi_parent_chip, msi_data, handle_simple_irq, NULL, NULL); diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c index 98b6e1d4b1a6..3dd28382d5f5 100644 --- a/drivers/irqchip/irq-mbigen.c +++ b/drivers/irqchip/irq-mbigen.c @@ -1,19 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2015 Hisilicon Limited, All Rights Reserved. * Author: Jun Ma <majun258@huawei.com> * Author: Yun Wu <wuyun.wu@huawei.com> - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/acpi.h> @@ -355,8 +344,7 @@ static int mbigen_device_probe(struct platform_device *pdev) err = -EINVAL; if (err) { - dev_err(&pdev->dev, "Failed to create mbi-gen@%p irqdomain", - mgn_chip->base); + dev_err(&pdev->dev, "Failed to create mbi-gen irqdomain\n"); return err; } diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c index 7b531fd075b8..dcdc23b9dce6 100644 --- a/drivers/irqchip/irq-meson-gpio.c +++ b/drivers/irqchip/irq-meson-gpio.c @@ -1,22 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2015 Endless Mobile, Inc. * Author: Carlo Caione <carlo@endlessm.com> * Copyright (c) 2016 BayLibre, SAS. * Author: Jerome Brunet <jbrunet@baylibre.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see <http://www.gnu.org/licenses/>. - * The full GNU General Public License is included in this distribution - * in the file called COPYING. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -73,6 +60,7 @@ static const struct of_device_id meson_irq_gpio_matches[] = { { .compatible = "amlogic,meson-gxbb-gpio-intc", .data = &gxbb_params }, { .compatible = "amlogic,meson-gxl-gpio-intc", .data = &gxl_params }, { .compatible = "amlogic,meson-axg-gpio-intc", .data = &axg_params }, + { .compatible = "amlogic,meson-g12a-gpio-intc", .data = &axg_params }, { } }; diff --git a/drivers/irqchip/irq-mips-cpu.c b/drivers/irqchip/irq-mips-cpu.c index 66f97fde13d8..95d4fd8f7a96 100644 --- a/drivers/irqchip/irq-mips-cpu.c +++ b/drivers/irqchip/irq-mips-cpu.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright 2001 MontaVista Software Inc. * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net @@ -7,11 +8,6 @@ * Author: Maciej W. Rozycki <macro@mips.com> * * This file define the irq handler for MIPS CPU interrupts. - * - * 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. */ /* diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c index d32268cc1174..f3985469c221 100644 --- a/drivers/irqchip/irq-mips-gic.c +++ b/drivers/irqchip/irq-mips-gic.c @@ -388,7 +388,7 @@ static void gic_all_vpes_irq_cpu_online(struct irq_data *d) intr = GIC_HWIRQ_TO_LOCAL(d->hwirq); cd = irq_data_get_irq_chip_data(d); - write_gic_vl_map(intr, cd->map); + write_gic_vl_map(mips_gic_vx_map_reg(intr), cd->map); if (cd->mask) write_gic_vl_smask(BIT(intr)); } @@ -517,7 +517,7 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int virq, spin_lock_irqsave(&gic_lock, flags); for_each_online_cpu(cpu) { write_gic_vl_other(mips_cm_vp_id(cpu)); - write_gic_vo_map(intr, map); + write_gic_vo_map(mips_gic_vx_map_reg(intr), map); } spin_unlock_irqrestore(&gic_lock, flags); diff --git a/drivers/irqchip/irq-mmp.c b/drivers/irqchip/irq-mmp.c index 8eed478f3b7e..14618dc0bd39 100644 --- a/drivers/irqchip/irq-mmp.c +++ b/drivers/irqchip/irq-mmp.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * linux/arch/arm/mach-mmp/irq.c * @@ -6,10 +7,6 @@ * * Author: Bin Yang <bin.yang@marvell.com> * Haojian Zhuang <haojian.zhuang@gmail.com> - * - * 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 <linux/module.h> diff --git a/drivers/irqchip/irq-mtk-cirq.c b/drivers/irqchip/irq-mtk-cirq.c index 18c65c16de28..69ba8ce3c178 100644 --- a/drivers/irqchip/irq-mtk-cirq.c +++ b/drivers/irqchip/irq-mtk-cirq.c @@ -1,15 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2016 MediaTek Inc. * Author: Youlin.Pei <youlin.pei@mediatek.com> - * - * 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 <linux/interrupt.h> diff --git a/drivers/irqchip/irq-mtk-sysirq.c b/drivers/irqchip/irq-mtk-sysirq.c index 90aaf190157f..73eae5966a40 100644 --- a/drivers/irqchip/irq-mtk-sysirq.c +++ b/drivers/irqchip/irq-mtk-sysirq.c @@ -1,15 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2014 MediaTek Inc. * Author: Joe.C <yingjoe.chen@mediatek.com> - * - * 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 <linux/irq.h> diff --git a/drivers/irqchip/irq-mxs.c b/drivers/irqchip/irq-mxs.c index e8b31f52e071..a671938fd97f 100644 --- a/drivers/irqchip/irq-mxs.c +++ b/drivers/irqchip/irq-mxs.c @@ -1,21 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright (C) 2014 Oleksij Rempel <linux@rempel-privat.de> * Add Alphascale ASM9260 support. - * - * 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. - * - * 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. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <linux/kernel.h> diff --git a/drivers/irqchip/irq-nvic.c b/drivers/irqchip/irq-nvic.c index b1777104fd9f..a166d30deea2 100644 --- a/drivers/irqchip/irq-nvic.c +++ b/drivers/irqchip/irq-nvic.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * drivers/irq/irq-nvic.c * * Copyright (C) 2008 ARM Limited, All Rights Reserved. * Copyright (C) 2013 Pengutronix * - * 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. - * * Support for the Nested Vectored Interrupt Controller found on the * ARMv7-M CPUs (Cortex-M3/M4) */ diff --git a/drivers/irqchip/irq-or1k-pic.c b/drivers/irqchip/irq-or1k-pic.c index dd9d5d12fea2..03d2366118dd 100644 --- a/drivers/irqchip/irq-or1k-pic.c +++ b/drivers/irqchip/irq-or1k-pic.c @@ -1,11 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2010-2011 Jonas Bonn <jonas@southpole.se> * Copyright (C) 2014 Stefan Kristansson <stefan.kristiansson@saunalahti.fi> - * - * 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 <linux/irq.h> diff --git a/drivers/irqchip/irq-partition-percpu.c b/drivers/irqchip/irq-partition-percpu.c index 1f7cc5933cd5..0c4c8ed7064e 100644 --- a/drivers/irqchip/irq-partition-percpu.c +++ b/drivers/irqchip/irq-partition-percpu.c @@ -1,18 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2016 ARM Limited, All Rights Reserved. * Author: Marc Zyngier <marc.zyngier@arm.com> - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/bitops.h> diff --git a/drivers/irqchip/irq-pic32-evic.c b/drivers/irqchip/irq-pic32-evic.c index 73addb4b625b..34c4b4ffacd1 100644 --- a/drivers/irqchip/irq-pic32-evic.c +++ b/drivers/irqchip/irq-pic32-evic.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Cristian Birsan <cristian.birsan@microchip.com> * Joshua Henderson <joshua.henderson@microchip.com> * Copyright (C) 2016 Microchip Technology Inc. All rights reserved. - * - * 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 <linux/kernel.h> #include <linux/module.h> diff --git a/drivers/irqchip/irq-renesas-intc-irqpin.c b/drivers/irqchip/irq-renesas-intc-irqpin.c index 8c039525703f..f82bc60a6793 100644 --- a/drivers/irqchip/irq-renesas-intc-irqpin.c +++ b/drivers/irqchip/irq-renesas-intc-irqpin.c @@ -389,10 +389,8 @@ static int intc_irqpin_probe(struct platform_device *pdev) int k; p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL); - if (!p) { - dev_err(dev, "failed to allocate driver data\n"); + if (!p) return -ENOMEM; - } /* deal with driver instance configuration */ of_property_read_u32(dev->of_node, "sense-bitfield-width", @@ -510,7 +508,8 @@ static int intc_irqpin_probe(struct platform_device *pdev) } irq_chip = &p->irq_chip; - irq_chip->name = name; + irq_chip->name = "intc-irqpin"; + irq_chip->parent_device = dev; irq_chip->irq_mask = disable_fn; irq_chip->irq_unmask = enable_fn; irq_chip->irq_set_type = intc_irqpin_irq_set_type; diff --git a/drivers/irqchip/irq-renesas-irqc.c b/drivers/irqchip/irq-renesas-irqc.c index a449a7c839b3..11abc09ef76c 100644 --- a/drivers/irqchip/irq-renesas-irqc.c +++ b/drivers/irqchip/irq-renesas-irqc.c @@ -7,7 +7,6 @@ #include <linux/init.h> #include <linux/platform_device.h> -#include <linux/spinlock.h> #include <linux/interrupt.h> #include <linux/ioport.h> #include <linux/io.h> @@ -48,7 +47,7 @@ struct irqc_priv { void __iomem *cpu_int_base; struct irqc_irq irq[IRQC_IRQ_MAX]; unsigned int number_of_irqs; - struct platform_device *pdev; + struct device *dev; struct irq_chip_generic *gc; struct irq_domain *irq_domain; atomic_t wakeup_path; @@ -61,8 +60,7 @@ static struct irqc_priv *irq_data_to_priv(struct irq_data *data) static void irqc_dbg(struct irqc_irq *i, char *str) { - dev_dbg(&i->p->pdev->dev, "%s (%d:%d)\n", - str, i->requested_irq, i->hw_irq); + dev_dbg(i->p->dev, "%s (%d:%d)\n", str, i->requested_irq, i->hw_irq); } static unsigned char irqc_sense[IRQ_TYPE_SENSE_MASK + 1] = { @@ -125,33 +123,22 @@ static irqreturn_t irqc_irq_handler(int irq, void *dev_id) static int irqc_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; + const char *name = dev_name(dev); struct irqc_priv *p; - struct resource *io; struct resource *irq; - const char *name = dev_name(&pdev->dev); int ret; int k; - p = kzalloc(sizeof(*p), GFP_KERNEL); - if (!p) { - dev_err(&pdev->dev, "failed to allocate driver data\n"); - ret = -ENOMEM; - goto err0; - } + p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; - p->pdev = pdev; + p->dev = dev; platform_set_drvdata(pdev, p); - pm_runtime_enable(&pdev->dev); - pm_runtime_get_sync(&pdev->dev); - - /* get hold of manadatory IOMEM */ - io = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!io) { - dev_err(&pdev->dev, "not enough IOMEM resources\n"); - ret = -EINVAL; - goto err1; - } + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); /* allow any number of IRQs between 1 and IRQC_IRQ_MAX */ for (k = 0; k < IRQC_IRQ_MAX; k++) { @@ -166,42 +153,41 @@ static int irqc_probe(struct platform_device *pdev) p->number_of_irqs = k; if (p->number_of_irqs < 1) { - dev_err(&pdev->dev, "not enough IRQ resources\n"); + dev_err(dev, "not enough IRQ resources\n"); ret = -EINVAL; - goto err1; + goto err_runtime_pm_disable; } /* ioremap IOMEM and setup read/write callbacks */ - p->iomem = ioremap_nocache(io->start, resource_size(io)); - if (!p->iomem) { - dev_err(&pdev->dev, "failed to remap IOMEM\n"); - ret = -ENXIO; - goto err2; + p->iomem = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(p->iomem)) { + ret = PTR_ERR(p->iomem); + goto err_runtime_pm_disable; } p->cpu_int_base = p->iomem + IRQC_INT_CPU_BASE(0); /* SYS-SPI */ - p->irq_domain = irq_domain_add_linear(pdev->dev.of_node, - p->number_of_irqs, + p->irq_domain = irq_domain_add_linear(dev->of_node, p->number_of_irqs, &irq_generic_chip_ops, p); if (!p->irq_domain) { ret = -ENXIO; - dev_err(&pdev->dev, "cannot initialize irq domain\n"); - goto err2; + dev_err(dev, "cannot initialize irq domain\n"); + goto err_runtime_pm_disable; } ret = irq_alloc_domain_generic_chips(p->irq_domain, p->number_of_irqs, - 1, name, handle_level_irq, + 1, "irqc", handle_level_irq, 0, 0, IRQ_GC_INIT_NESTED_LOCK); if (ret) { - dev_err(&pdev->dev, "cannot allocate generic chip\n"); - goto err3; + dev_err(dev, "cannot allocate generic chip\n"); + goto err_remove_domain; } p->gc = irq_get_domain_generic_chip(p->irq_domain, 0); p->gc->reg_base = p->cpu_int_base; p->gc->chip_types[0].regs.enable = IRQC_EN_SET; p->gc->chip_types[0].regs.disable = IRQC_EN_STS; + p->gc->chip_types[0].chip.parent_device = dev; p->gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg; p->gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg; p->gc->chip_types[0].chip.irq_set_type = irqc_irq_set_type; @@ -210,46 +196,33 @@ static int irqc_probe(struct platform_device *pdev) /* request interrupts one by one */ for (k = 0; k < p->number_of_irqs; k++) { - if (request_irq(p->irq[k].requested_irq, irqc_irq_handler, - 0, name, &p->irq[k])) { - dev_err(&pdev->dev, "failed to request IRQ\n"); + if (devm_request_irq(dev, p->irq[k].requested_irq, + irqc_irq_handler, 0, name, &p->irq[k])) { + dev_err(dev, "failed to request IRQ\n"); ret = -ENOENT; - goto err4; + goto err_remove_domain; } } - dev_info(&pdev->dev, "driving %d irqs\n", p->number_of_irqs); + dev_info(dev, "driving %d irqs\n", p->number_of_irqs); return 0; -err4: - while (--k >= 0) - free_irq(p->irq[k].requested_irq, &p->irq[k]); -err3: +err_remove_domain: irq_domain_remove(p->irq_domain); -err2: - iounmap(p->iomem); -err1: - pm_runtime_put(&pdev->dev); - pm_runtime_disable(&pdev->dev); - kfree(p); -err0: +err_runtime_pm_disable: + pm_runtime_put(dev); + pm_runtime_disable(dev); return ret; } static int irqc_remove(struct platform_device *pdev) { struct irqc_priv *p = platform_get_drvdata(pdev); - int k; - - for (k = 0; k < p->number_of_irqs; k++) - free_irq(p->irq[k].requested_irq, &p->irq[k]); irq_domain_remove(p->irq_domain); - iounmap(p->iomem); pm_runtime_put(&pdev->dev); pm_runtime_disable(&pdev->dev); - kfree(p); return 0; } diff --git a/drivers/irqchip/irq-renesas-rza1.c b/drivers/irqchip/irq-renesas-rza1.c new file mode 100644 index 000000000000..b0d46ac42b89 --- /dev/null +++ b/drivers/irqchip/irq-renesas-rza1.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/A1 IRQC Driver + * + * Copyright (C) 2019 Glider bvba + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irqdomain.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <dt-bindings/interrupt-controller/arm-gic.h> + +#define IRQC_NUM_IRQ 8 + +#define ICR0 0 /* Interrupt Control Register 0 */ + +#define ICR0_NMIL BIT(15) /* NMI Input Level (0=low, 1=high) */ +#define ICR0_NMIE BIT(8) /* Edge Select (0=falling, 1=rising) */ +#define ICR0_NMIF BIT(1) /* NMI Interrupt Request */ + +#define ICR1 2 /* Interrupt Control Register 1 */ + +#define ICR1_IRQS(n, sense) ((sense) << ((n) * 2)) /* IRQ Sense Select */ +#define ICR1_IRQS_LEVEL_LOW 0 +#define ICR1_IRQS_EDGE_FALLING 1 +#define ICR1_IRQS_EDGE_RISING 2 +#define ICR1_IRQS_EDGE_BOTH 3 +#define ICR1_IRQS_MASK(n) ICR1_IRQS((n), 3) + +#define IRQRR 4 /* IRQ Interrupt Request Register */ + + +struct rza1_irqc_priv { + struct device *dev; + void __iomem *base; + struct irq_chip chip; + struct irq_domain *irq_domain; + struct of_phandle_args map[IRQC_NUM_IRQ]; +}; + +static struct rza1_irqc_priv *irq_data_to_priv(struct irq_data *data) +{ + return data->domain->host_data; +} + +static void rza1_irqc_eoi(struct irq_data *d) +{ + struct rza1_irqc_priv *priv = irq_data_to_priv(d); + u16 bit = BIT(irqd_to_hwirq(d)); + u16 tmp; + + tmp = readw_relaxed(priv->base + IRQRR); + if (tmp & bit) + writew_relaxed(GENMASK(IRQC_NUM_IRQ - 1, 0) & ~bit, + priv->base + IRQRR); + + irq_chip_eoi_parent(d); +} + +static int rza1_irqc_set_type(struct irq_data *d, unsigned int type) +{ + struct rza1_irqc_priv *priv = irq_data_to_priv(d); + unsigned int hw_irq = irqd_to_hwirq(d); + u16 sense, tmp; + + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQ_TYPE_LEVEL_LOW: + sense = ICR1_IRQS_LEVEL_LOW; + break; + + case IRQ_TYPE_EDGE_FALLING: + sense = ICR1_IRQS_EDGE_FALLING; + break; + + case IRQ_TYPE_EDGE_RISING: + sense = ICR1_IRQS_EDGE_RISING; + break; + + case IRQ_TYPE_EDGE_BOTH: + sense = ICR1_IRQS_EDGE_BOTH; + break; + + default: + return -EINVAL; + } + + tmp = readw_relaxed(priv->base + ICR1); + tmp &= ~ICR1_IRQS_MASK(hw_irq); + tmp |= ICR1_IRQS(hw_irq, sense); + writew_relaxed(tmp, priv->base + ICR1); + return 0; +} + +static int rza1_irqc_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + struct rza1_irqc_priv *priv = domain->host_data; + struct irq_fwspec *fwspec = arg; + unsigned int hwirq = fwspec->param[0]; + struct irq_fwspec spec; + unsigned int i; + int ret; + + ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &priv->chip, + priv); + if (ret) + return ret; + + spec.fwnode = &priv->dev->of_node->fwnode; + spec.param_count = priv->map[hwirq].args_count; + for (i = 0; i < spec.param_count; i++) + spec.param[i] = priv->map[hwirq].args[i]; + + return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &spec); +} + +static int rza1_irqc_translate(struct irq_domain *domain, + struct irq_fwspec *fwspec, unsigned long *hwirq, + unsigned int *type) +{ + if (fwspec->param_count != 2 || fwspec->param[0] >= IRQC_NUM_IRQ) + return -EINVAL; + + *hwirq = fwspec->param[0]; + *type = fwspec->param[1]; + return 0; +} + +static const struct irq_domain_ops rza1_irqc_domain_ops = { + .alloc = rza1_irqc_alloc, + .translate = rza1_irqc_translate, +}; + +static int rza1_irqc_parse_map(struct rza1_irqc_priv *priv, + struct device_node *gic_node) +{ + unsigned int imaplen, i, j, ret; + struct device *dev = priv->dev; + struct device_node *ipar; + const __be32 *imap; + u32 intsize; + + imap = of_get_property(dev->of_node, "interrupt-map", &imaplen); + if (!imap) + return -EINVAL; + + for (i = 0; i < IRQC_NUM_IRQ; i++) { + if (imaplen < 3) + return -EINVAL; + + /* Check interrupt number, ignore sense */ + if (be32_to_cpup(imap) != i) + return -EINVAL; + + ipar = of_find_node_by_phandle(be32_to_cpup(imap + 2)); + if (ipar != gic_node) { + of_node_put(ipar); + return -EINVAL; + } + + imap += 3; + imaplen -= 3; + + ret = of_property_read_u32(ipar, "#interrupt-cells", &intsize); + of_node_put(ipar); + if (ret) + return ret; + + if (imaplen < intsize) + return -EINVAL; + + priv->map[i].args_count = intsize; + for (j = 0; j < intsize; j++) + priv->map[i].args[j] = be32_to_cpup(imap++); + + imaplen -= intsize; + } + + return 0; +} + +static int rza1_irqc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct irq_domain *parent = NULL; + struct device_node *gic_node; + struct rza1_irqc_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + priv->dev = dev; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + gic_node = of_irq_find_parent(np); + if (gic_node) + parent = irq_find_host(gic_node); + + if (!parent) { + dev_err(dev, "cannot find parent domain\n"); + ret = -ENODEV; + goto out_put_node; + } + + ret = rza1_irqc_parse_map(priv, gic_node); + if (ret) { + dev_err(dev, "cannot parse %s: %d\n", "interrupt-map", ret); + goto out_put_node; + } + + priv->chip.name = "rza1-irqc", + priv->chip.irq_mask = irq_chip_mask_parent, + priv->chip.irq_unmask = irq_chip_unmask_parent, + priv->chip.irq_eoi = rza1_irqc_eoi, + priv->chip.irq_retrigger = irq_chip_retrigger_hierarchy, + priv->chip.irq_set_type = rza1_irqc_set_type, + priv->chip.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE; + + priv->irq_domain = irq_domain_add_hierarchy(parent, 0, IRQC_NUM_IRQ, + np, &rza1_irqc_domain_ops, + priv); + if (!priv->irq_domain) { + dev_err(dev, "cannot initialize irq domain\n"); + ret = -ENOMEM; + } + +out_put_node: + of_node_put(gic_node); + return ret; +} + +static int rza1_irqc_remove(struct platform_device *pdev) +{ + struct rza1_irqc_priv *priv = platform_get_drvdata(pdev); + + irq_domain_remove(priv->irq_domain); + return 0; +} + +static const struct of_device_id rza1_irqc_dt_ids[] = { + { .compatible = "renesas,rza1-irqc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rza1_irqc_dt_ids); + +static struct platform_driver rza1_irqc_device_driver = { + .probe = rza1_irqc_probe, + .remove = rza1_irqc_remove, + .driver = { + .name = "renesas_rza1_irqc", + .of_match_table = rza1_irqc_dt_ids, + } +}; + +static int __init rza1_irqc_init(void) +{ + return platform_driver_register(&rza1_irqc_device_driver); +} +postcore_initcall(rza1_irqc_init); + +static void __exit rza1_irqc_exit(void) +{ + platform_driver_unregister(&rza1_irqc_device_driver); +} +module_exit(rza1_irqc_exit); + +MODULE_AUTHOR("Geert Uytterhoeven <geert+renesas@glider.be>"); +MODULE_DESCRIPTION("Renesas RZ/A1 IRQC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/irqchip/irq-s3c24xx.c b/drivers/irqchip/irq-s3c24xx.c index b623f300f1b1..d2031fecc386 100644 --- a/drivers/irqchip/irq-s3c24xx.c +++ b/drivers/irqchip/irq-s3c24xx.c @@ -1,19 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * S3C24XX IRQ handling * * Copyright (c) 2003-2004 Simtec Electronics * Ben Dooks <ben@simtec.co.uk> * Copyright (c) 2012 Heiko Stuebner <heiko@sntech.de> - * - * 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. - * - * 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 <linux/init.h> diff --git a/drivers/irqchip/irq-sa11x0.c b/drivers/irqchip/irq-sa11x0.c index 61bb28d7b19b..dbccc7dafbf8 100644 --- a/drivers/irqchip/irq-sa11x0.c +++ b/drivers/irqchip/irq-sa11x0.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2015 Dmitry Eremin-Solenikov * Copyright (C) 1999-2001 Nicolas Pitre * * Generic IRQ handling for the SA11x0. - * - * 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 <linux/init.h> #include <linux/module.h> diff --git a/drivers/irqchip/irq-sirfsoc.c b/drivers/irqchip/irq-sirfsoc.c index e1336848affa..c86faaa35ca4 100644 --- a/drivers/irqchip/irq-sirfsoc.c +++ b/drivers/irqchip/irq-sirfsoc.c @@ -1,9 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * interrupt controller support for CSR SiRFprimaII * * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. - * - * Licensed under GPLv2 or later. */ #include <linux/init.h> diff --git a/drivers/irqchip/irq-sni-exiu.c b/drivers/irqchip/irq-sni-exiu.c index 1927b2f36ff6..1d027623c776 100644 --- a/drivers/irqchip/irq-sni-exiu.c +++ b/drivers/irqchip/irq-sni-exiu.c @@ -1,15 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Driver for Socionext External Interrupt Unit (EXIU) * - * Copyright (c) 2017 Linaro, Ltd. <ard.biesheuvel@linaro.org> + * Copyright (c) 2017-2019 Linaro, Ltd. <ard.biesheuvel@linaro.org> * * Based on irq-tegra.c: * Copyright (C) 2011 Google, Inc. * Copyright (C) 2010,2013, NVIDIA Corporation - * - * 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 <linux/interrupt.h> @@ -20,6 +17,7 @@ #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_irq.h> +#include <linux/platform_device.h> #include <dt-bindings/interrupt-controller/arm-gic.h> @@ -134,9 +132,13 @@ static int exiu_domain_translate(struct irq_domain *domain, *hwirq = fwspec->param[1] - info->spi_base; *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; - return 0; + } else { + if (fwspec->param_count != 2) + return -EINVAL; + *hwirq = fwspec->param[0]; + *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; } - return -EINVAL; + return 0; } static int exiu_domain_alloc(struct irq_domain *dom, unsigned int virq, @@ -147,16 +149,21 @@ static int exiu_domain_alloc(struct irq_domain *dom, unsigned int virq, struct exiu_irq_data *info = dom->host_data; irq_hw_number_t hwirq; - if (fwspec->param_count != 3) - return -EINVAL; /* Not GIC compliant */ - if (fwspec->param[0] != GIC_SPI) - return -EINVAL; /* No PPI should point to this domain */ + parent_fwspec = *fwspec; + if (is_of_node(dom->parent->fwnode)) { + if (fwspec->param_count != 3) + return -EINVAL; /* Not GIC compliant */ + if (fwspec->param[0] != GIC_SPI) + return -EINVAL; /* No PPI should point to this domain */ + hwirq = fwspec->param[1] - info->spi_base; + } else { + hwirq = fwspec->param[0]; + parent_fwspec.param[0] = hwirq + info->spi_base + 32; + } WARN_ON(nr_irqs != 1); - hwirq = fwspec->param[1] - info->spi_base; irq_domain_set_hwirq_and_chip(dom, virq, hwirq, &exiu_irq_chip, info); - parent_fwspec = *fwspec; parent_fwspec.fwnode = dom->parent->fwnode; return irq_domain_alloc_irqs_parent(dom, virq, nr_irqs, &parent_fwspec); } @@ -167,35 +174,23 @@ static const struct irq_domain_ops exiu_domain_ops = { .free = irq_domain_free_irqs_common, }; -static int __init exiu_init(struct device_node *node, - struct device_node *parent) +static struct exiu_irq_data *exiu_init(const struct fwnode_handle *fwnode, + struct resource *res) { - struct irq_domain *parent_domain, *domain; struct exiu_irq_data *data; int err; - if (!parent) { - pr_err("%pOF: no parent, giving up\n", node); - return -ENODEV; - } - - parent_domain = irq_find_host(parent); - if (!parent_domain) { - pr_err("%pOF: unable to obtain parent domain\n", node); - return -ENXIO; - } - data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) - return -ENOMEM; + return ERR_PTR(-ENOMEM); - if (of_property_read_u32(node, "socionext,spi-base", &data->spi_base)) { - pr_err("%pOF: failed to parse 'spi-base' property\n", node); + if (fwnode_property_read_u32_array(fwnode, "socionext,spi-base", + &data->spi_base, 1)) { err = -ENODEV; goto out_free; } - data->base = of_iomap(node, 0); + data->base = ioremap(res->start, resource_size(res)); if (!data->base) { err = -ENODEV; goto out_free; @@ -205,11 +200,44 @@ static int __init exiu_init(struct device_node *node, writel_relaxed(0xFFFFFFFF, data->base + EIREQCLR); writel_relaxed(0xFFFFFFFF, data->base + EIMASK); + return data; + +out_free: + kfree(data); + return ERR_PTR(err); +} + +static int __init exiu_dt_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *parent_domain, *domain; + struct exiu_irq_data *data; + struct resource res; + + if (!parent) { + pr_err("%pOF: no parent, giving up\n", node); + return -ENODEV; + } + + parent_domain = irq_find_host(parent); + if (!parent_domain) { + pr_err("%pOF: unable to obtain parent domain\n", node); + return -ENXIO; + } + + if (of_address_to_resource(node, 0, &res)) { + pr_err("%pOF: failed to parse memory resource\n", node); + return -ENXIO; + } + + data = exiu_init(of_node_to_fwnode(node), &res); + if (IS_ERR(data)) + return PTR_ERR(data); + domain = irq_domain_add_hierarchy(parent_domain, 0, NUM_IRQS, node, &exiu_domain_ops, data); if (!domain) { pr_err("%pOF: failed to allocate domain\n", node); - err = -ENOMEM; goto out_unmap; } @@ -220,8 +248,57 @@ static int __init exiu_init(struct device_node *node, out_unmap: iounmap(data->base); -out_free: kfree(data); - return err; + return -ENOMEM; +} +IRQCHIP_DECLARE(exiu, "socionext,synquacer-exiu", exiu_dt_init); + +#ifdef CONFIG_ACPI +static int exiu_acpi_probe(struct platform_device *pdev) +{ + struct irq_domain *domain; + struct exiu_irq_data *data; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to parse memory resource\n"); + return -ENXIO; + } + + data = exiu_init(dev_fwnode(&pdev->dev), res); + if (IS_ERR(data)) + return PTR_ERR(data); + + domain = acpi_irq_create_hierarchy(0, NUM_IRQS, dev_fwnode(&pdev->dev), + &exiu_domain_ops, data); + if (!domain) { + dev_err(&pdev->dev, "failed to create IRQ domain\n"); + goto out_unmap; + } + + dev_info(&pdev->dev, "%d interrupts forwarded\n", NUM_IRQS); + + return 0; + +out_unmap: + iounmap(data->base); + kfree(data); + return -ENOMEM; } -IRQCHIP_DECLARE(exiu, "socionext,synquacer-exiu", exiu_init); + +static const struct acpi_device_id exiu_acpi_ids[] = { + { "SCX0008" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(acpi, exiu_acpi_ids); + +static struct platform_driver exiu_driver = { + .driver = { + .name = "exiu", + .acpi_match_table = exiu_acpi_ids, + }, + .probe = exiu_acpi_probe, +}; +builtin_platform_driver(exiu_driver); +#endif diff --git a/drivers/irqchip/irq-st.c b/drivers/irqchip/irq-st.c index 5e0e250db0be..801551e46a7b 100644 --- a/drivers/irqchip/irq-st.c +++ b/drivers/irqchip/irq-st.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2014 STMicroelectronics – All Rights Reserved * * Author: Lee Jones <lee.jones@linaro.org> * * This is a re-write of Christophe Kerello's PMU driver. - * - * 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 <dt-bindings/interrupt-controller/irq-st.h> diff --git a/drivers/irqchip/irq-stm32-exti.c b/drivers/irqchip/irq-stm32-exti.c index 7bd1d4cb2e19..e00f2fa27f00 100644 --- a/drivers/irqchip/irq-stm32-exti.c +++ b/drivers/irqchip/irq-stm32-exti.c @@ -14,8 +14,10 @@ #include <linux/irqchip.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqdomain.h> +#include <linux/module.h> #include <linux/of_address.h> #include <linux/of_irq.h> +#include <linux/of_platform.h> #include <linux/syscore_ops.h> #include <dt-bindings/interrupt-controller/arm-gic.h> @@ -37,12 +39,6 @@ struct stm32_exti_bank { #define UNDEF_REG ~0 -enum stm32_exti_hwspinlock { - HWSPINLOCK_UNKNOWN, - HWSPINLOCK_NONE, - HWSPINLOCK_READY, -}; - struct stm32_desc_irq { u32 exti; u32 irq_parent; @@ -69,8 +65,6 @@ struct stm32_exti_host_data { void __iomem *base; struct stm32_exti_chip_data *chips_data; const struct stm32_exti_drv_data *drv_data; - struct device_node *node; - enum stm32_exti_hwspinlock hwlock_state; struct hwspinlock *hwlock; }; @@ -285,49 +279,27 @@ static int stm32_exti_set_type(struct irq_data *d, static int stm32_exti_hwspin_lock(struct stm32_exti_chip_data *chip_data) { - struct stm32_exti_host_data *host_data = chip_data->host_data; - struct hwspinlock *hwlock; - int id, ret = 0, timeout = 0; - - /* first time, check for hwspinlock availability */ - if (unlikely(host_data->hwlock_state == HWSPINLOCK_UNKNOWN)) { - id = of_hwspin_lock_get_id(host_data->node, 0); - if (id >= 0) { - hwlock = hwspin_lock_request_specific(id); - if (hwlock) { - /* found valid hwspinlock */ - host_data->hwlock_state = HWSPINLOCK_READY; - host_data->hwlock = hwlock; - pr_debug("%s hwspinlock = %d\n", __func__, id); - } else { - host_data->hwlock_state = HWSPINLOCK_NONE; - } - } else if (id != -EPROBE_DEFER) { - host_data->hwlock_state = HWSPINLOCK_NONE; - } else { - /* hwspinlock driver shall be ready at that stage */ - ret = -EPROBE_DEFER; - } - } + int ret, timeout = 0; - if (likely(host_data->hwlock_state == HWSPINLOCK_READY)) { - /* - * Use the x_raw API since we are under spin_lock protection. - * Do not use the x_timeout API because we are under irq_disable - * mode (see __setup_irq()) - */ - do { - ret = hwspin_trylock_raw(host_data->hwlock); - if (!ret) - return 0; - - udelay(HWSPNLCK_RETRY_DELAY); - timeout += HWSPNLCK_RETRY_DELAY; - } while (timeout < HWSPNLCK_TIMEOUT); - - if (ret == -EBUSY) - ret = -ETIMEDOUT; - } + if (!chip_data->host_data->hwlock) + return 0; + + /* + * Use the x_raw API since we are under spin_lock protection. + * Do not use the x_timeout API because we are under irq_disable + * mode (see __setup_irq()) + */ + do { + ret = hwspin_trylock_raw(chip_data->host_data->hwlock); + if (!ret) + return 0; + + udelay(HWSPNLCK_RETRY_DELAY); + timeout += HWSPNLCK_RETRY_DELAY; + } while (timeout < HWSPNLCK_TIMEOUT); + + if (ret == -EBUSY) + ret = -ETIMEDOUT; if (ret) pr_err("%s can't get hwspinlock (%d)\n", __func__, ret); @@ -337,7 +309,7 @@ static int stm32_exti_hwspin_lock(struct stm32_exti_chip_data *chip_data) static void stm32_exti_hwspin_unlock(struct stm32_exti_chip_data *chip_data) { - if (likely(chip_data->host_data->hwlock_state == HWSPINLOCK_READY)) + if (chip_data->host_data->hwlock) hwspin_unlock_raw(chip_data->host_data->hwlock); } @@ -586,8 +558,7 @@ static int stm32_exti_h_set_affinity(struct irq_data *d, return -EINVAL; } -#ifdef CONFIG_PM -static int stm32_exti_h_suspend(void) +static int __maybe_unused stm32_exti_h_suspend(void) { struct stm32_exti_chip_data *chip_data; int i; @@ -602,7 +573,7 @@ static int stm32_exti_h_suspend(void) return 0; } -static void stm32_exti_h_resume(void) +static void __maybe_unused stm32_exti_h_resume(void) { struct stm32_exti_chip_data *chip_data; int i; @@ -616,17 +587,22 @@ static void stm32_exti_h_resume(void) } static struct syscore_ops stm32_exti_h_syscore_ops = { +#ifdef CONFIG_PM_SLEEP .suspend = stm32_exti_h_suspend, .resume = stm32_exti_h_resume, +#endif }; -static void stm32_exti_h_syscore_init(void) +static void stm32_exti_h_syscore_init(struct stm32_exti_host_data *host_data) { + stm32_host_data = host_data; register_syscore_ops(&stm32_exti_h_syscore_ops); } -#else -static inline void stm32_exti_h_syscore_init(void) {} -#endif + +static void stm32_exti_h_syscore_deinit(void) +{ + unregister_syscore_ops(&stm32_exti_h_syscore_ops); +} static struct irq_chip stm32_exti_h_chip = { .name = "stm32-exti-h", @@ -683,8 +659,6 @@ stm32_exti_host_data *stm32_exti_host_init(const struct stm32_exti_drv_data *dd, return NULL; host_data->drv_data = dd; - host_data->node = node; - host_data->hwlock_state = HWSPINLOCK_UNKNOWN; host_data->chips_data = kcalloc(dd->bank_nr, sizeof(struct stm32_exti_chip_data), GFP_KERNEL); @@ -711,7 +685,8 @@ free_host_data: static struct stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data, - u32 bank_idx) + u32 bank_idx, + struct device_node *node) { const struct stm32_exti_bank *stm32_bank; struct stm32_exti_chip_data *chip_data; @@ -731,7 +706,7 @@ stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data, writel_relaxed(0, base + stm32_bank->imr_ofst); writel_relaxed(0, base + stm32_bank->emr_ofst); - pr_info("%pOF: bank%d\n", h_data->node, bank_idx); + pr_info("%pOF: bank%d\n", node, bank_idx); return chip_data; } @@ -771,7 +746,7 @@ static int __init stm32_exti_init(const struct stm32_exti_drv_data *drv_data, struct stm32_exti_chip_data *chip_data; stm32_bank = drv_data->exti_banks[i]; - chip_data = stm32_exti_chip_init(host_data, i); + chip_data = stm32_exti_chip_init(host_data, i, node); gc = irq_get_domain_generic_chip(domain, i * IRQS_PER_BANK); @@ -815,50 +790,130 @@ static const struct irq_domain_ops stm32_exti_h_domain_ops = { .xlate = irq_domain_xlate_twocell, }; -static int -__init stm32_exti_hierarchy_init(const struct stm32_exti_drv_data *drv_data, - struct device_node *node, - struct device_node *parent) +static void stm32_exti_remove_irq(void *data) +{ + struct irq_domain *domain = data; + + irq_domain_remove(domain); +} + +static int stm32_exti_remove(struct platform_device *pdev) +{ + stm32_exti_h_syscore_deinit(); + return 0; +} + +static int stm32_exti_probe(struct platform_device *pdev) { + int ret, i; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; struct irq_domain *parent_domain, *domain; struct stm32_exti_host_data *host_data; - int ret, i; + const struct stm32_exti_drv_data *drv_data; + struct resource *res; - parent_domain = irq_find_host(parent); - if (!parent_domain) { - pr_err("interrupt-parent not found\n"); - return -EINVAL; + host_data = devm_kzalloc(dev, sizeof(*host_data), GFP_KERNEL); + if (!host_data) + return -ENOMEM; + + /* check for optional hwspinlock which may be not available yet */ + ret = of_hwspin_lock_get_id(np, 0); + if (ret == -EPROBE_DEFER) + /* hwspinlock framework not yet ready */ + return ret; + + if (ret >= 0) { + host_data->hwlock = devm_hwspin_lock_request_specific(dev, ret); + if (!host_data->hwlock) { + dev_err(dev, "Failed to request hwspinlock\n"); + return -EINVAL; + } + } else if (ret != -ENOENT) { + /* note: ENOENT is a valid case (means 'no hwspinlock') */ + dev_err(dev, "Failed to get hwspinlock\n"); + return ret; } - host_data = stm32_exti_host_init(drv_data, node); - if (!host_data) + /* initialize host_data */ + drv_data = of_device_get_match_data(dev); + if (!drv_data) { + dev_err(dev, "no of match data\n"); + return -ENODEV; + } + host_data->drv_data = drv_data; + + host_data->chips_data = devm_kcalloc(dev, drv_data->bank_nr, + sizeof(*host_data->chips_data), + GFP_KERNEL); + if (!host_data->chips_data) return -ENOMEM; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host_data->base = devm_ioremap_resource(dev, res); + if (IS_ERR(host_data->base)) { + dev_err(dev, "Unable to map registers\n"); + return PTR_ERR(host_data->base); + } + for (i = 0; i < drv_data->bank_nr; i++) - stm32_exti_chip_init(host_data, i); + stm32_exti_chip_init(host_data, i, np); + + parent_domain = irq_find_host(of_irq_find_parent(np)); + if (!parent_domain) { + dev_err(dev, "GIC interrupt-parent not found\n"); + return -EINVAL; + } domain = irq_domain_add_hierarchy(parent_domain, 0, drv_data->bank_nr * IRQS_PER_BANK, - node, &stm32_exti_h_domain_ops, + np, &stm32_exti_h_domain_ops, host_data); if (!domain) { - pr_err("%pOFn: Could not register exti domain.\n", node); - ret = -ENOMEM; - goto out_unmap; + dev_err(dev, "Could not register exti domain\n"); + return -ENOMEM; } - stm32_exti_h_syscore_init(); + ret = devm_add_action_or_reset(dev, stm32_exti_remove_irq, domain); + if (ret) + return ret; + + stm32_exti_h_syscore_init(host_data); return 0; +} -out_unmap: - iounmap(host_data->base); - kfree(host_data->chips_data); - kfree(host_data); - return ret; +/* platform driver only for MP1 */ +static const struct of_device_id stm32_exti_ids[] = { + { .compatible = "st,stm32mp1-exti", .data = &stm32mp1_drv_data}, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_exti_ids); + +static struct platform_driver stm32_exti_driver = { + .probe = stm32_exti_probe, + .remove = stm32_exti_remove, + .driver = { + .name = "stm32_exti", + .of_match_table = stm32_exti_ids, + }, +}; + +static int __init stm32_exti_arch_init(void) +{ + return platform_driver_register(&stm32_exti_driver); } +static void __exit stm32_exti_arch_exit(void) +{ + return platform_driver_unregister(&stm32_exti_driver); +} + +arch_initcall(stm32_exti_arch_init); +module_exit(stm32_exti_arch_exit); + +/* no platform driver for F4 and H7 */ static int __init stm32f4_exti_of_init(struct device_node *np, struct device_node *parent) { @@ -874,11 +929,3 @@ static int __init stm32h7_exti_of_init(struct device_node *np, } IRQCHIP_DECLARE(stm32h7_exti, "st,stm32h7-exti", stm32h7_exti_of_init); - -static int __init stm32mp1_exti_of_init(struct device_node *np, - struct device_node *parent) -{ - return stm32_exti_hierarchy_init(&stm32mp1_drv_data, np, parent); -} - -IRQCHIP_DECLARE(stm32mp1_exti, "st,stm32mp1-exti", stm32mp1_exti_of_init); diff --git a/drivers/irqchip/irq-tango.c b/drivers/irqchip/irq-tango.c index ae28d8648679..34290f09b853 100644 --- a/drivers/irqchip/irq-tango.c +++ b/drivers/irqchip/irq-tango.c @@ -1,10 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2014 Mans Rullgard <mans@mansr.com> - * - * 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 <linux/init.h> diff --git a/drivers/irqchip/irq-tb10x.c b/drivers/irqchip/irq-tb10x.c index 7e6708099a7b..9e456497c1c4 100644 --- a/drivers/irqchip/irq-tb10x.c +++ b/drivers/irqchip/irq-tb10x.c @@ -1,22 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Abilis Systems interrupt controller driver * * Copyright (C) Abilis Systems 2012 * * Author: Christian Ruppert <christian.ruppert@abilis.com> - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/interrupt.h> diff --git a/drivers/irqchip/irq-tegra.c b/drivers/irqchip/irq-tegra.c index 0abc0cd1c32e..e1f771c72fc4 100644 --- a/drivers/irqchip/irq-tegra.c +++ b/drivers/irqchip/irq-tegra.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Driver code for Tegra's Legacy Interrupt Controller * @@ -10,16 +11,6 @@ * Colin Cross <ccross@android.com> * * Copyright (C) 2010,2013, NVIDIA Corporation - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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 <linux/io.h> diff --git a/drivers/irqchip/irq-ti-sci-inta.c b/drivers/irqchip/irq-ti-sci-inta.c new file mode 100644 index 000000000000..ef4d625d2d80 --- /dev/null +++ b/drivers/irqchip/irq-ti-sci-inta.c @@ -0,0 +1,615 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments' K3 Interrupt Aggregator irqchip driver + * + * Copyright (C) 2018-2019 Texas Instruments Incorporated - http://www.ti.com/ + * Lokesh Vutla <lokeshvutla@ti.com> + */ + +#include <linux/err.h> +#include <linux/io.h> +#include <linux/irqchip.h> +#include <linux/irqdomain.h> +#include <linux/interrupt.h> +#include <linux/msi.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/soc/ti/ti_sci_inta_msi.h> +#include <linux/soc/ti/ti_sci_protocol.h> +#include <asm-generic/msi.h> + +#define TI_SCI_DEV_ID_MASK 0xffff +#define TI_SCI_DEV_ID_SHIFT 16 +#define TI_SCI_IRQ_ID_MASK 0xffff +#define TI_SCI_IRQ_ID_SHIFT 0 +#define HWIRQ_TO_DEVID(hwirq) (((hwirq) >> (TI_SCI_DEV_ID_SHIFT)) & \ + (TI_SCI_DEV_ID_MASK)) +#define HWIRQ_TO_IRQID(hwirq) ((hwirq) & (TI_SCI_IRQ_ID_MASK)) +#define TO_HWIRQ(dev, index) ((((dev) & TI_SCI_DEV_ID_MASK) << \ + TI_SCI_DEV_ID_SHIFT) | \ + ((index) & TI_SCI_IRQ_ID_MASK)) + +#define MAX_EVENTS_PER_VINT 64 +#define VINT_ENABLE_SET_OFFSET 0x0 +#define VINT_ENABLE_CLR_OFFSET 0x8 +#define VINT_STATUS_OFFSET 0x18 + +/** + * struct ti_sci_inta_event_desc - Description of an event coming to + * Interrupt Aggregator. This serves + * as a mapping table for global event, + * hwirq and vint bit. + * @global_event: Global event number corresponding to this event + * @hwirq: Hwirq of the incoming interrupt + * @vint_bit: Corresponding vint bit to which this event is attached. + */ +struct ti_sci_inta_event_desc { + u16 global_event; + u32 hwirq; + u8 vint_bit; +}; + +/** + * struct ti_sci_inta_vint_desc - Description of a virtual interrupt coming out + * of Interrupt Aggregator. + * @domain: Pointer to IRQ domain to which this vint belongs. + * @list: List entry for the vint list + * @event_map: Bitmap to manage the allocation of events to vint. + * @events: Array of event descriptors assigned to this vint. + * @parent_virq: Linux IRQ number that gets attached to parent + * @vint_id: TISCI vint ID + */ +struct ti_sci_inta_vint_desc { + struct irq_domain *domain; + struct list_head list; + DECLARE_BITMAP(event_map, MAX_EVENTS_PER_VINT); + struct ti_sci_inta_event_desc events[MAX_EVENTS_PER_VINT]; + unsigned int parent_virq; + u16 vint_id; +}; + +/** + * struct ti_sci_inta_irq_domain - Structure representing a TISCI based + * Interrupt Aggregator IRQ domain. + * @sci: Pointer to TISCI handle + * @vint: TISCI resource pointer representing IA inerrupts. + * @global_event: TISCI resource pointer representing global events. + * @vint_list: List of the vints active in the system + * @vint_mutex: Mutex to protect vint_list + * @base: Base address of the memory mapped IO registers + * @pdev: Pointer to platform device. + */ +struct ti_sci_inta_irq_domain { + const struct ti_sci_handle *sci; + struct ti_sci_resource *vint; + struct ti_sci_resource *global_event; + struct list_head vint_list; + /* Mutex to protect vint list */ + struct mutex vint_mutex; + void __iomem *base; + struct platform_device *pdev; +}; + +#define to_vint_desc(e, i) container_of(e, struct ti_sci_inta_vint_desc, \ + events[i]) + +/** + * ti_sci_inta_irq_handler() - Chained IRQ handler for the vint irqs + * @desc: Pointer to irq_desc corresponding to the irq + */ +static void ti_sci_inta_irq_handler(struct irq_desc *desc) +{ + struct ti_sci_inta_vint_desc *vint_desc; + struct ti_sci_inta_irq_domain *inta; + struct irq_domain *domain; + unsigned int virq, bit; + unsigned long val; + + vint_desc = irq_desc_get_handler_data(desc); + domain = vint_desc->domain; + inta = domain->host_data; + + chained_irq_enter(irq_desc_get_chip(desc), desc); + + val = readq_relaxed(inta->base + vint_desc->vint_id * 0x1000 + + VINT_STATUS_OFFSET); + + for_each_set_bit(bit, &val, MAX_EVENTS_PER_VINT) { + virq = irq_find_mapping(domain, vint_desc->events[bit].hwirq); + if (virq) + generic_handle_irq(virq); + } + + chained_irq_exit(irq_desc_get_chip(desc), desc); +} + +/** + * ti_sci_inta_alloc_parent_irq() - Allocate parent irq to Interrupt aggregator + * @domain: IRQ domain corresponding to Interrupt Aggregator + * + * Return 0 if all went well else corresponding error value. + */ +static struct ti_sci_inta_vint_desc *ti_sci_inta_alloc_parent_irq(struct irq_domain *domain) +{ + struct ti_sci_inta_irq_domain *inta = domain->host_data; + struct ti_sci_inta_vint_desc *vint_desc; + struct irq_fwspec parent_fwspec; + unsigned int parent_virq; + u16 vint_id; + + vint_id = ti_sci_get_free_resource(inta->vint); + if (vint_id == TI_SCI_RESOURCE_NULL) + return ERR_PTR(-EINVAL); + + vint_desc = kzalloc(sizeof(*vint_desc), GFP_KERNEL); + if (!vint_desc) + return ERR_PTR(-ENOMEM); + + vint_desc->domain = domain; + vint_desc->vint_id = vint_id; + INIT_LIST_HEAD(&vint_desc->list); + + parent_fwspec.fwnode = of_node_to_fwnode(of_irq_find_parent(dev_of_node(&inta->pdev->dev))); + parent_fwspec.param_count = 2; + parent_fwspec.param[0] = inta->pdev->id; + parent_fwspec.param[1] = vint_desc->vint_id; + + parent_virq = irq_create_fwspec_mapping(&parent_fwspec); + if (parent_virq == 0) { + kfree(vint_desc); + return ERR_PTR(-EINVAL); + } + vint_desc->parent_virq = parent_virq; + + list_add_tail(&vint_desc->list, &inta->vint_list); + irq_set_chained_handler_and_data(vint_desc->parent_virq, + ti_sci_inta_irq_handler, vint_desc); + + return vint_desc; +} + +/** + * ti_sci_inta_alloc_event() - Attach an event to a IA vint. + * @vint_desc: Pointer to vint_desc to which the event gets attached + * @free_bit: Bit inside vint to which event gets attached + * @hwirq: hwirq of the input event + * + * Return event_desc pointer if all went ok else appropriate error value. + */ +static struct ti_sci_inta_event_desc *ti_sci_inta_alloc_event(struct ti_sci_inta_vint_desc *vint_desc, + u16 free_bit, + u32 hwirq) +{ + struct ti_sci_inta_irq_domain *inta = vint_desc->domain->host_data; + struct ti_sci_inta_event_desc *event_desc; + u16 dev_id, dev_index; + int err; + + dev_id = HWIRQ_TO_DEVID(hwirq); + dev_index = HWIRQ_TO_IRQID(hwirq); + + event_desc = &vint_desc->events[free_bit]; + event_desc->hwirq = hwirq; + event_desc->vint_bit = free_bit; + event_desc->global_event = ti_sci_get_free_resource(inta->global_event); + if (event_desc->global_event == TI_SCI_RESOURCE_NULL) + return ERR_PTR(-EINVAL); + + err = inta->sci->ops.rm_irq_ops.set_event_map(inta->sci, + dev_id, dev_index, + inta->pdev->id, + vint_desc->vint_id, + event_desc->global_event, + free_bit); + if (err) + goto free_global_event; + + return event_desc; +free_global_event: + ti_sci_release_resource(inta->global_event, event_desc->global_event); + return ERR_PTR(err); +} + +/** + * ti_sci_inta_alloc_irq() - Allocate an irq within INTA domain + * @domain: irq_domain pointer corresponding to INTA + * @hwirq: hwirq of the input event + * + * Note: Allocation happens in the following manner: + * - Find a free bit available in any of the vints available in the list. + * - If not found, allocate a vint from the vint pool + * - Attach the free bit to input hwirq. + * Return event_desc if all went ok else appropriate error value. + */ +static struct ti_sci_inta_event_desc *ti_sci_inta_alloc_irq(struct irq_domain *domain, + u32 hwirq) +{ + struct ti_sci_inta_irq_domain *inta = domain->host_data; + struct ti_sci_inta_vint_desc *vint_desc = NULL; + struct ti_sci_inta_event_desc *event_desc; + u16 free_bit; + + mutex_lock(&inta->vint_mutex); + list_for_each_entry(vint_desc, &inta->vint_list, list) { + free_bit = find_first_zero_bit(vint_desc->event_map, + MAX_EVENTS_PER_VINT); + if (free_bit != MAX_EVENTS_PER_VINT) { + set_bit(free_bit, vint_desc->event_map); + goto alloc_event; + } + } + + /* No free bits available. Allocate a new vint */ + vint_desc = ti_sci_inta_alloc_parent_irq(domain); + if (IS_ERR(vint_desc)) { + mutex_unlock(&inta->vint_mutex); + return ERR_PTR(PTR_ERR(vint_desc)); + } + + free_bit = find_first_zero_bit(vint_desc->event_map, + MAX_EVENTS_PER_VINT); + set_bit(free_bit, vint_desc->event_map); + +alloc_event: + event_desc = ti_sci_inta_alloc_event(vint_desc, free_bit, hwirq); + if (IS_ERR(event_desc)) + clear_bit(free_bit, vint_desc->event_map); + + mutex_unlock(&inta->vint_mutex); + return event_desc; +} + +/** + * ti_sci_inta_free_parent_irq() - Free a parent irq to INTA + * @inta: Pointer to inta domain. + * @vint_desc: Pointer to vint_desc that needs to be freed. + */ +static void ti_sci_inta_free_parent_irq(struct ti_sci_inta_irq_domain *inta, + struct ti_sci_inta_vint_desc *vint_desc) +{ + if (find_first_bit(vint_desc->event_map, MAX_EVENTS_PER_VINT) == MAX_EVENTS_PER_VINT) { + list_del(&vint_desc->list); + ti_sci_release_resource(inta->vint, vint_desc->vint_id); + irq_dispose_mapping(vint_desc->parent_virq); + kfree(vint_desc); + } +} + +/** + * ti_sci_inta_free_irq() - Free an IRQ within INTA domain + * @event_desc: Pointer to event_desc that needs to be freed. + * @hwirq: Hwirq number within INTA domain that needs to be freed + */ +static void ti_sci_inta_free_irq(struct ti_sci_inta_event_desc *event_desc, + u32 hwirq) +{ + struct ti_sci_inta_vint_desc *vint_desc; + struct ti_sci_inta_irq_domain *inta; + + vint_desc = to_vint_desc(event_desc, event_desc->vint_bit); + inta = vint_desc->domain->host_data; + /* free event irq */ + mutex_lock(&inta->vint_mutex); + inta->sci->ops.rm_irq_ops.free_event_map(inta->sci, + HWIRQ_TO_DEVID(hwirq), + HWIRQ_TO_IRQID(hwirq), + inta->pdev->id, + vint_desc->vint_id, + event_desc->global_event, + event_desc->vint_bit); + + clear_bit(event_desc->vint_bit, vint_desc->event_map); + ti_sci_release_resource(inta->global_event, event_desc->global_event); + event_desc->global_event = TI_SCI_RESOURCE_NULL; + event_desc->hwirq = 0; + + ti_sci_inta_free_parent_irq(inta, vint_desc); + mutex_unlock(&inta->vint_mutex); +} + +/** + * ti_sci_inta_request_resources() - Allocate resources for input irq + * @data: Pointer to corresponding irq_data + * + * Note: This is the core api where the actual allocation happens for input + * hwirq. This allocation involves creating a parent irq for vint. + * If this is done in irq_domain_ops.alloc() then a deadlock is reached + * for allocation. So this allocation is being done in request_resources() + * + * Return: 0 if all went well else corresponding error. + */ +static int ti_sci_inta_request_resources(struct irq_data *data) +{ + struct ti_sci_inta_event_desc *event_desc; + + event_desc = ti_sci_inta_alloc_irq(data->domain, data->hwirq); + if (IS_ERR(event_desc)) + return PTR_ERR(event_desc); + + data->chip_data = event_desc; + + return 0; +} + +/** + * ti_sci_inta_release_resources - Release resources for input irq + * @data: Pointer to corresponding irq_data + * + * Note: Corresponding to request_resources(), all the unmapping and deletion + * of parent vint irqs happens in this api. + */ +static void ti_sci_inta_release_resources(struct irq_data *data) +{ + struct ti_sci_inta_event_desc *event_desc; + + event_desc = irq_data_get_irq_chip_data(data); + ti_sci_inta_free_irq(event_desc, data->hwirq); +} + +/** + * ti_sci_inta_manage_event() - Control the event based on the offset + * @data: Pointer to corresponding irq_data + * @offset: register offset using which event is controlled. + */ +static void ti_sci_inta_manage_event(struct irq_data *data, u32 offset) +{ + struct ti_sci_inta_event_desc *event_desc; + struct ti_sci_inta_vint_desc *vint_desc; + struct ti_sci_inta_irq_domain *inta; + + event_desc = irq_data_get_irq_chip_data(data); + vint_desc = to_vint_desc(event_desc, event_desc->vint_bit); + inta = data->domain->host_data; + + writeq_relaxed(BIT(event_desc->vint_bit), + inta->base + vint_desc->vint_id * 0x1000 + offset); +} + +/** + * ti_sci_inta_mask_irq() - Mask an event + * @data: Pointer to corresponding irq_data + */ +static void ti_sci_inta_mask_irq(struct irq_data *data) +{ + ti_sci_inta_manage_event(data, VINT_ENABLE_CLR_OFFSET); +} + +/** + * ti_sci_inta_unmask_irq() - Unmask an event + * @data: Pointer to corresponding irq_data + */ +static void ti_sci_inta_unmask_irq(struct irq_data *data) +{ + ti_sci_inta_manage_event(data, VINT_ENABLE_SET_OFFSET); +} + +/** + * ti_sci_inta_ack_irq() - Ack an event + * @data: Pointer to corresponding irq_data + */ +static void ti_sci_inta_ack_irq(struct irq_data *data) +{ + /* + * Do not clear the event if hardware is capable of sending + * a down event. + */ + if (irqd_get_trigger_type(data) != IRQF_TRIGGER_HIGH) + ti_sci_inta_manage_event(data, VINT_STATUS_OFFSET); +} + +static int ti_sci_inta_set_affinity(struct irq_data *d, + const struct cpumask *mask_val, bool force) +{ + return -EINVAL; +} + +/** + * ti_sci_inta_set_type() - Update the trigger type of the irq. + * @data: Pointer to corresponding irq_data + * @type: Trigger type as specified by user + * + * Note: This updates the handle_irq callback for level msi. + * + * Return 0 if all went well else appropriate error. + */ +static int ti_sci_inta_set_type(struct irq_data *data, unsigned int type) +{ + /* + * .alloc default sets handle_edge_irq. But if the user specifies + * that IRQ is level MSI, then update the handle to handle_level_irq + */ + switch (type & IRQ_TYPE_SENSE_MASK) { + case IRQF_TRIGGER_HIGH: + irq_set_handler_locked(data, handle_level_irq); + return 0; + case IRQF_TRIGGER_RISING: + return 0; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static struct irq_chip ti_sci_inta_irq_chip = { + .name = "INTA", + .irq_ack = ti_sci_inta_ack_irq, + .irq_mask = ti_sci_inta_mask_irq, + .irq_set_type = ti_sci_inta_set_type, + .irq_unmask = ti_sci_inta_unmask_irq, + .irq_set_affinity = ti_sci_inta_set_affinity, + .irq_request_resources = ti_sci_inta_request_resources, + .irq_release_resources = ti_sci_inta_release_resources, +}; + +/** + * ti_sci_inta_irq_domain_free() - Free an IRQ from the IRQ domain + * @domain: Domain to which the irqs belong + * @virq: base linux virtual IRQ to be freed. + * @nr_irqs: Number of continuous irqs to be freed + */ +static void ti_sci_inta_irq_domain_free(struct irq_domain *domain, + unsigned int virq, unsigned int nr_irqs) +{ + struct irq_data *data = irq_domain_get_irq_data(domain, virq); + + irq_domain_reset_irq_data(data); +} + +/** + * ti_sci_inta_irq_domain_alloc() - Allocate Interrupt aggregator IRQs + * @domain: Point to the interrupt aggregator IRQ domain + * @virq: Corresponding Linux virtual IRQ number + * @nr_irqs: Continuous irqs to be allocated + * @data: Pointer to firmware specifier + * + * No actual allocation happens here. + * + * Return 0 if all went well else appropriate error value. + */ +static int ti_sci_inta_irq_domain_alloc(struct irq_domain *domain, + unsigned int virq, unsigned int nr_irqs, + void *data) +{ + msi_alloc_info_t *arg = data; + + irq_domain_set_info(domain, virq, arg->hwirq, &ti_sci_inta_irq_chip, + NULL, handle_edge_irq, NULL, NULL); + + return 0; +} + +static const struct irq_domain_ops ti_sci_inta_irq_domain_ops = { + .free = ti_sci_inta_irq_domain_free, + .alloc = ti_sci_inta_irq_domain_alloc, +}; + +static struct irq_chip ti_sci_inta_msi_irq_chip = { + .name = "MSI-INTA", + .flags = IRQCHIP_SUPPORTS_LEVEL_MSI, +}; + +static void ti_sci_inta_msi_set_desc(msi_alloc_info_t *arg, + struct msi_desc *desc) +{ + struct platform_device *pdev = to_platform_device(desc->dev); + + arg->desc = desc; + arg->hwirq = TO_HWIRQ(pdev->id, desc->inta.dev_index); +} + +static struct msi_domain_ops ti_sci_inta_msi_ops = { + .set_desc = ti_sci_inta_msi_set_desc, +}; + +static struct msi_domain_info ti_sci_inta_msi_domain_info = { + .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | + MSI_FLAG_LEVEL_CAPABLE), + .ops = &ti_sci_inta_msi_ops, + .chip = &ti_sci_inta_msi_irq_chip, +}; + +static int ti_sci_inta_irq_domain_probe(struct platform_device *pdev) +{ + struct irq_domain *parent_domain, *domain, *msi_domain; + struct device_node *parent_node, *node; + struct ti_sci_inta_irq_domain *inta; + struct device *dev = &pdev->dev; + struct resource *res; + int ret; + + node = dev_of_node(dev); + parent_node = of_irq_find_parent(node); + if (!parent_node) { + dev_err(dev, "Failed to get IRQ parent node\n"); + return -ENODEV; + } + + parent_domain = irq_find_host(parent_node); + if (!parent_domain) + return -EPROBE_DEFER; + + inta = devm_kzalloc(dev, sizeof(*inta), GFP_KERNEL); + if (!inta) + return -ENOMEM; + + inta->pdev = pdev; + inta->sci = devm_ti_sci_get_by_phandle(dev, "ti,sci"); + if (IS_ERR(inta->sci)) { + ret = PTR_ERR(inta->sci); + if (ret != -EPROBE_DEFER) + dev_err(dev, "ti,sci read fail %d\n", ret); + inta->sci = NULL; + return ret; + } + + ret = of_property_read_u32(dev->of_node, "ti,sci-dev-id", &pdev->id); + if (ret) { + dev_err(dev, "missing 'ti,sci-dev-id' property\n"); + return -EINVAL; + } + + inta->vint = devm_ti_sci_get_of_resource(inta->sci, dev, pdev->id, + "ti,sci-rm-range-vint"); + if (IS_ERR(inta->vint)) { + dev_err(dev, "VINT resource allocation failed\n"); + return PTR_ERR(inta->vint); + } + + inta->global_event = devm_ti_sci_get_of_resource(inta->sci, dev, pdev->id, + "ti,sci-rm-range-global-event"); + if (IS_ERR(inta->global_event)) { + dev_err(dev, "Global event resource allocation failed\n"); + return PTR_ERR(inta->global_event); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + inta->base = devm_ioremap_resource(dev, res); + if (IS_ERR(inta->base)) + return -ENODEV; + + domain = irq_domain_add_linear(dev_of_node(dev), + ti_sci_get_num_resources(inta->vint), + &ti_sci_inta_irq_domain_ops, inta); + if (!domain) { + dev_err(dev, "Failed to allocate IRQ domain\n"); + return -ENOMEM; + } + + msi_domain = ti_sci_inta_msi_create_irq_domain(of_node_to_fwnode(node), + &ti_sci_inta_msi_domain_info, + domain); + if (!msi_domain) { + irq_domain_remove(domain); + dev_err(dev, "Failed to allocate msi domain\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&inta->vint_list); + mutex_init(&inta->vint_mutex); + + return 0; +} + +static const struct of_device_id ti_sci_inta_irq_domain_of_match[] = { + { .compatible = "ti,sci-inta", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ti_sci_inta_irq_domain_of_match); + +static struct platform_driver ti_sci_inta_irq_domain_driver = { + .probe = ti_sci_inta_irq_domain_probe, + .driver = { + .name = "ti-sci-inta", + .of_match_table = ti_sci_inta_irq_domain_of_match, + }, +}; +module_platform_driver(ti_sci_inta_irq_domain_driver); + +MODULE_AUTHOR("Lokesh Vutla <lokeshvutla@ticom>"); +MODULE_DESCRIPTION("K3 Interrupt Aggregator driver over TI SCI protocol"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/irqchip/irq-ti-sci-intr.c b/drivers/irqchip/irq-ti-sci-intr.c new file mode 100644 index 000000000000..59d51a20bbd8 --- /dev/null +++ b/drivers/irqchip/irq-ti-sci-intr.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Texas Instruments' K3 Interrupt Router irqchip driver + * + * Copyright (C) 2018-2019 Texas Instruments Incorporated - http://www.ti.com/ + * Lokesh Vutla <lokeshvutla@ti.com> + */ + +#include <linux/err.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/io.h> +#include <linux/irqchip.h> +#include <linux/irqdomain.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/soc/ti/ti_sci_protocol.h> + +#define TI_SCI_DEV_ID_MASK 0xffff +#define TI_SCI_DEV_ID_SHIFT 16 +#define TI_SCI_IRQ_ID_MASK 0xffff +#define TI_SCI_IRQ_ID_SHIFT 0 +#define HWIRQ_TO_DEVID(hwirq) (((hwirq) >> (TI_SCI_DEV_ID_SHIFT)) & \ + (TI_SCI_DEV_ID_MASK)) +#define HWIRQ_TO_IRQID(hwirq) ((hwirq) & (TI_SCI_IRQ_ID_MASK)) +#define TO_HWIRQ(dev, index) ((((dev) & TI_SCI_DEV_ID_MASK) << \ + TI_SCI_DEV_ID_SHIFT) | \ + ((index) & TI_SCI_IRQ_ID_MASK)) + +/** + * struct ti_sci_intr_irq_domain - Structure representing a TISCI based + * Interrupt Router IRQ domain. + * @sci: Pointer to TISCI handle + * @dst_irq: TISCI resource pointer representing GIC irq controller. + * @dst_id: TISCI device ID of the GIC irq controller. + * @type: Specifies the trigger type supported by this Interrupt Router + */ +struct ti_sci_intr_irq_domain { + const struct ti_sci_handle *sci; + struct ti_sci_resource *dst_irq; + u32 dst_id; + u32 type; +}; + +static struct irq_chip ti_sci_intr_irq_chip = { + .name = "INTR", + .irq_eoi = irq_chip_eoi_parent, + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_set_type = irq_chip_set_type_parent, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_affinity = irq_chip_set_affinity_parent, +}; + +/** + * ti_sci_intr_irq_domain_translate() - Retrieve hwirq and type from + * IRQ firmware specific handler. + * @domain: Pointer to IRQ domain + * @fwspec: Pointer to IRQ specific firmware structure + * @hwirq: IRQ number identified by hardware + * @type: IRQ type + * + * Return 0 if all went ok else appropriate error. + */ +static int ti_sci_intr_irq_domain_translate(struct irq_domain *domain, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + struct ti_sci_intr_irq_domain *intr = domain->host_data; + + if (fwspec->param_count != 2) + return -EINVAL; + + *hwirq = TO_HWIRQ(fwspec->param[0], fwspec->param[1]); + *type = intr->type; + + return 0; +} + +/** + * ti_sci_intr_irq_domain_free() - Free the specified IRQs from the domain. + * @domain: Domain to which the irqs belong + * @virq: Linux virtual IRQ to be freed. + * @nr_irqs: Number of continuous irqs to be freed + */ +static void ti_sci_intr_irq_domain_free(struct irq_domain *domain, + unsigned int virq, unsigned int nr_irqs) +{ + struct ti_sci_intr_irq_domain *intr = domain->host_data; + struct irq_data *data, *parent_data; + u16 dev_id, irq_index; + + parent_data = irq_domain_get_irq_data(domain->parent, virq); + data = irq_domain_get_irq_data(domain, virq); + irq_index = HWIRQ_TO_IRQID(data->hwirq); + dev_id = HWIRQ_TO_DEVID(data->hwirq); + + intr->sci->ops.rm_irq_ops.free_irq(intr->sci, dev_id, irq_index, + intr->dst_id, parent_data->hwirq); + ti_sci_release_resource(intr->dst_irq, parent_data->hwirq); + irq_domain_free_irqs_parent(domain, virq, 1); + irq_domain_reset_irq_data(data); +} + +/** + * ti_sci_intr_alloc_gic_irq() - Allocate GIC specific IRQ + * @domain: Pointer to the interrupt router IRQ domain + * @virq: Corresponding Linux virtual IRQ number + * @hwirq: Corresponding hwirq for the IRQ within this IRQ domain + * + * Returns 0 if all went well else appropriate error pointer. + */ +static int ti_sci_intr_alloc_gic_irq(struct irq_domain *domain, + unsigned int virq, u32 hwirq) +{ + struct ti_sci_intr_irq_domain *intr = domain->host_data; + struct irq_fwspec fwspec; + u16 dev_id, irq_index; + u16 dst_irq; + int err; + + dev_id = HWIRQ_TO_DEVID(hwirq); + irq_index = HWIRQ_TO_IRQID(hwirq); + + dst_irq = ti_sci_get_free_resource(intr->dst_irq); + if (dst_irq == TI_SCI_RESOURCE_NULL) + return -EINVAL; + + fwspec.fwnode = domain->parent->fwnode; + fwspec.param_count = 3; + fwspec.param[0] = 0; /* SPI */ + fwspec.param[1] = dst_irq - 32; /* SPI offset */ + fwspec.param[2] = intr->type; + + err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); + if (err) + goto err_irqs; + + err = intr->sci->ops.rm_irq_ops.set_irq(intr->sci, dev_id, irq_index, + intr->dst_id, dst_irq); + if (err) + goto err_msg; + + return 0; + +err_msg: + irq_domain_free_irqs_parent(domain, virq, 1); +err_irqs: + ti_sci_release_resource(intr->dst_irq, dst_irq); + return err; +} + +/** + * ti_sci_intr_irq_domain_alloc() - Allocate Interrupt router IRQs + * @domain: Point to the interrupt router IRQ domain + * @virq: Corresponding Linux virtual IRQ number + * @nr_irqs: Continuous irqs to be allocated + * @data: Pointer to firmware specifier + * + * Return 0 if all went well else appropriate error value. + */ +static int ti_sci_intr_irq_domain_alloc(struct irq_domain *domain, + unsigned int virq, unsigned int nr_irqs, + void *data) +{ + struct irq_fwspec *fwspec = data; + unsigned long hwirq; + unsigned int flags; + int err; + + err = ti_sci_intr_irq_domain_translate(domain, fwspec, &hwirq, &flags); + if (err) + return err; + + err = ti_sci_intr_alloc_gic_irq(domain, virq, hwirq); + if (err) + return err; + + irq_domain_set_hwirq_and_chip(domain, virq, hwirq, + &ti_sci_intr_irq_chip, NULL); + + return 0; +} + +static const struct irq_domain_ops ti_sci_intr_irq_domain_ops = { + .free = ti_sci_intr_irq_domain_free, + .alloc = ti_sci_intr_irq_domain_alloc, + .translate = ti_sci_intr_irq_domain_translate, +}; + +static int ti_sci_intr_irq_domain_probe(struct platform_device *pdev) +{ + struct irq_domain *parent_domain, *domain; + struct ti_sci_intr_irq_domain *intr; + struct device_node *parent_node; + struct device *dev = &pdev->dev; + int ret; + + parent_node = of_irq_find_parent(dev_of_node(dev)); + if (!parent_node) { + dev_err(dev, "Failed to get IRQ parent node\n"); + return -ENODEV; + } + + parent_domain = irq_find_host(parent_node); + if (!parent_domain) { + dev_err(dev, "Failed to find IRQ parent domain\n"); + return -ENODEV; + } + + intr = devm_kzalloc(dev, sizeof(*intr), GFP_KERNEL); + if (!intr) + return -ENOMEM; + + ret = of_property_read_u32(dev_of_node(dev), "ti,intr-trigger-type", + &intr->type); + if (ret) { + dev_err(dev, "missing ti,intr-trigger-type property\n"); + return -EINVAL; + } + + intr->sci = devm_ti_sci_get_by_phandle(dev, "ti,sci"); + if (IS_ERR(intr->sci)) { + ret = PTR_ERR(intr->sci); + if (ret != -EPROBE_DEFER) + dev_err(dev, "ti,sci read fail %d\n", ret); + intr->sci = NULL; + return ret; + } + + ret = of_property_read_u32(dev_of_node(dev), "ti,sci-dst-id", + &intr->dst_id); + if (ret) { + dev_err(dev, "missing 'ti,sci-dst-id' property\n"); + return -EINVAL; + } + + intr->dst_irq = devm_ti_sci_get_of_resource(intr->sci, dev, + intr->dst_id, + "ti,sci-rm-range-girq"); + if (IS_ERR(intr->dst_irq)) { + dev_err(dev, "Destination irq resource allocation failed\n"); + return PTR_ERR(intr->dst_irq); + } + + domain = irq_domain_add_hierarchy(parent_domain, 0, 0, dev_of_node(dev), + &ti_sci_intr_irq_domain_ops, intr); + if (!domain) { + dev_err(dev, "Failed to allocate IRQ domain\n"); + return -ENOMEM; + } + + return 0; +} + +static const struct of_device_id ti_sci_intr_irq_domain_of_match[] = { + { .compatible = "ti,sci-intr", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ti_sci_intr_irq_domain_of_match); + +static struct platform_driver ti_sci_intr_irq_domain_driver = { + .probe = ti_sci_intr_irq_domain_probe, + .driver = { + .name = "ti-sci-intr", + .of_match_table = ti_sci_intr_irq_domain_of_match, + }, +}; +module_platform_driver(ti_sci_intr_irq_domain_driver); + +MODULE_AUTHOR("Lokesh Vutla <lokeshvutla@ticom>"); +MODULE_DESCRIPTION("K3 Interrupt Router driver over TI SCI protocol"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/irqchip/irq-uniphier-aidet.c b/drivers/irqchip/irq-uniphier-aidet.c index 7ba7f253470e..ed7b4f47ff3f 100644 --- a/drivers/irqchip/irq-uniphier-aidet.c +++ b/drivers/irqchip/irq-uniphier-aidet.c @@ -1,17 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Driver for UniPhier AIDET (ARM Interrupt Detector) * * Copyright (C) 2017 Socionext Inc. * Author: Masahiro Yamada <yamada.masahiro@socionext.com> - * - * 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 <linux/bitops.h> diff --git a/drivers/irqchip/irq-vf610-mscm-ir.c b/drivers/irqchip/irq-vf610-mscm-ir.c index 56b5e3cb9de2..2b9a8ba58e26 100644 --- a/drivers/irqchip/irq-vf610-mscm-ir.c +++ b/drivers/irqchip/irq-vf610-mscm-ir.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2014-2015 Toradex AG * Author: Stefan Agner <stefan@agner.ch> * - * 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. - * - * * IRQ chip driver for MSCM interrupt router available on Vybrid SoC's. * The interrupt router is between the CPU's interrupt controller and the * peripheral. The router allows to route the peripheral interrupts to diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c index 74192f62dd67..f3f20a3cff50 100644 --- a/drivers/irqchip/irq-vic.c +++ b/drivers/irqchip/irq-vic.c @@ -1,22 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * linux/arch/arm/common/vic.c * * Copyright (C) 1999 - 2003 ARM Limited * Copyright (C) 2000 Deep Blue Solutions Ltd - * - * 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. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/export.h> diff --git a/drivers/irqchip/irq-vt8500.c b/drivers/irqchip/irq-vt8500.c index f9af0af21751..5bce936af5d9 100644 --- a/drivers/irqchip/irq-vt8500.c +++ b/drivers/irqchip/irq-vt8500.c @@ -1,22 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * arch/arm/mach-vt8500/irq.c * * Copyright (C) 2012 Tony Prisk <linux@prisktech.co.nz> * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> - * - * 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. - * - * 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* diff --git a/drivers/irqchip/irq-zevio.c b/drivers/irqchip/irq-zevio.c index cb9d8ec37507..5a7efeb3892d 100644 --- a/drivers/irqchip/irq-zevio.c +++ b/drivers/irqchip/irq-zevio.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * linux/drivers/irqchip/irq-zevio.c * * Copyright (C) 2013 Daniel Tang <tangrs@tangrs.id.au> - * - * 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 <linux/io.h> diff --git a/drivers/irqchip/qcom-irq-combiner.c b/drivers/irqchip/qcom-irq-combiner.c index 7f0c0be322e0..d88e993aa66d 100644 --- a/drivers/irqchip/qcom-irq-combiner.c +++ b/drivers/irqchip/qcom-irq-combiner.c @@ -1,13 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only 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. */ /* @@ -237,7 +229,6 @@ static int get_registers(struct platform_device *pdev, struct combiner *comb) static int __init combiner_probe(struct platform_device *pdev) { struct combiner *combiner; - size_t alloc_sz; int nregs; int err; @@ -247,8 +238,8 @@ static int __init combiner_probe(struct platform_device *pdev) return -EINVAL; } - alloc_sz = sizeof(*combiner) + sizeof(struct combiner_reg) * nregs; - combiner = devm_kzalloc(&pdev->dev, alloc_sz, GFP_KERNEL); + combiner = devm_kzalloc(&pdev->dev, struct_size(combiner, regs, nregs), + GFP_KERNEL); if (!combiner) return -ENOMEM; |