From 9d9faab6f562b8a447b4f7aae2862d071ba1ae0d Mon Sep 17 00:00:00 2001 From: Markus Stockhausen Date: Fri, 5 Jun 2026 23:16:40 +0200 Subject: dt-bindings: interrupt-controller: realtek,rtl-intc: Allow 2 interrupt cells The Realtek Otto SoC interrupt controller supports hardware interrupt routing to multiple parent CPU interrupt lines. To utilize this multi-domain support, peripheral devices need a way to explicitly specify their desired parent interrupt line in the device tree. Update the "#interrupt-cells" property to accept either 1 or 2 cells. 1 cell: Specifies the hardware interrupt index, implicitly routing it to the first available parent (maintaining backward compatibility). 2 cells: The first cell specifies the hardware interrupt index and the second cell specifies the targeted parent interrupt index. Signed-off-by: Markus Stockhausen Signed-off-by: Thomas Gleixner Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260605211646.2101652-2-markus.stockhausen@gmx.de --- .../devicetree/bindings/interrupt-controller/realtek,rtl-intc.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/interrupt-controller/realtek,rtl-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/realtek,rtl-intc.yaml index 833a01cdd1b1..5c52e57c6647 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/realtek,rtl-intc.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/realtek,rtl-intc.yaml @@ -32,8 +32,9 @@ properties: "#interrupt-cells": description: - SoC interrupt line index. - const: 1 + First cell is the SoC interrupt line index. Optional second cell + specifies the parent interrupt index to route to. + enum: [1, 2] reg: minItems: 1 -- cgit v1.2.3 From d36c1d1657113fcf442c50e074593d7b712bada1 Mon Sep 17 00:00:00 2001 From: Markus Stockhausen Date: Fri, 5 Jun 2026 23:16:41 +0200 Subject: irqchip/irq-realtek-rtl: Split out parent setup code The parent interrupt setup will be extended to support multiple parents. To prepare for that, relocate the code into a separate helper. Although it still works only for a single interrupt prepare the coding so it can be easily extended with a loop for multi parent support. For this reduce the line lengths so that the upcoming indentation still leaves the width below 100 characters. Signed-off-by: Markus Stockhausen Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260605211646.2101652-3-markus.stockhausen@gmx.de --- drivers/irqchip/irq-realtek-rtl.c | 60 +++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/drivers/irqchip/irq-realtek-rtl.c b/drivers/irqchip/irq-realtek-rtl.c index 2ae3be7fa633..3b4508ec7198 100644 --- a/drivers/irqchip/irq-realtek-rtl.c +++ b/drivers/irqchip/irq-realtek-rtl.c @@ -147,48 +147,35 @@ out: chained_irq_exit(chip, desc); } -static int __init realtek_rtl_of_init(struct device_node *node, struct device_node *parent) +static int __init realtek_setup_parents(struct device_node *node) { + int parent_irq, num_parents = of_irq_count(node); struct of_phandle_args oirq; struct irq_domain *domain; - int cpu, parent_irq; - for_each_present_cpu(cpu) { - realtek_ictl_base[cpu] = of_iomap(node, cpu); - if (!realtek_ictl_base[cpu]) - return -ENXIO; - - /* Disable all cascaded interrupts and clear routing */ - for (unsigned int hw_irq = 0; hw_irq < RTL_ICTL_NUM_INPUTS; hw_irq++) { - disable_gimr(cpu, hw_irq); - write_irr(cpu, hw_irq, 0); - } - } - - if (WARN_ON(!of_irq_count(node))) { + if (WARN_ON(!num_parents)) { /* - * If DT contains no parent interrupts, assume MIPS CPU IRQ 2 - * (HW0) is connected to the first output. This is the case for - * all known hardware anyway. "interrupt-map" is deprecated, so - * don't bother trying to parse that. + * If DT contains no parent interrupts, assume MIPS IRQ 2 (HW0) is + * connected to the first output. This is the case for all known hardware. */ - oirq.np = of_find_compatible_node(NULL, NULL, "mti,cpu-interrupt-controller"); + oirq.np = of_find_compatible_node(NULL, NULL, + "mti,cpu-interrupt-controller"); + if (!oirq.np) + return -EINVAL; + oirq.args_count = 1; oirq.args[0] = 2; - parent_irq = irq_create_of_mapping(&oirq); - of_node_put(oirq.np); } else { parent_irq = of_irq_get(node, 0); } - if (parent_irq < 0) - return parent_irq; - else if (!parent_irq) - return -ENODEV; + if (parent_irq <= 0) + return parent_irq ? parent_irq : -ENODEV; - domain = irq_domain_create_linear(of_fwnode_handle(node), RTL_ICTL_NUM_INPUTS, &irq_domain_ops, NULL); + domain = irq_domain_create_linear(of_fwnode_handle(node), RTL_ICTL_NUM_INPUTS, + &irq_domain_ops, NULL); if (!domain) return -ENOMEM; @@ -197,4 +184,23 @@ static int __init realtek_rtl_of_init(struct device_node *node, struct device_no return 0; } +static int __init realtek_rtl_of_init(struct device_node *node, struct device_node *parent) +{ + unsigned int cpu; + + for_each_present_cpu(cpu) { + realtek_ictl_base[cpu] = of_iomap(node, cpu); + if (!realtek_ictl_base[cpu]) + return -ENXIO; + + /* Disable all cascaded interrupts and clear routing */ + for (unsigned int hw_irq = 0; hw_irq < RTL_ICTL_NUM_INPUTS; hw_irq++) { + disable_gimr(cpu, hw_irq); + write_irr(cpu, hw_irq, 0); + } + } + + return realtek_setup_parents(node); +} + IRQCHIP_DECLARE(realtek_rtl_intc, "realtek,rtl-intc", realtek_rtl_of_init); -- cgit v1.2.3 From 2568f6926c667e0b4ecf523cb4015acefc40409a Mon Sep 17 00:00:00 2001 From: Markus Stockhausen Date: Fri, 5 Jun 2026 23:16:42 +0200 Subject: irqchip/irq-realtek-rtl: Add interrupt data structure To prepare for multiple parent interrupt domains add an intermediate data structure. For now this will only host the link to the domain. Additionally adapt a deviating variable name to driver standard "hw_irq". Signed-off-by: Markus Stockhausen Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260605211646.2101652-4-markus.stockhausen@gmx.de --- drivers/irqchip/irq-realtek-rtl.c | 48 +++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/drivers/irqchip/irq-realtek-rtl.c b/drivers/irqchip/irq-realtek-rtl.c index 3b4508ec7198..547f21da3edc 100644 --- a/drivers/irqchip/irq-realtek-rtl.c +++ b/drivers/irqchip/irq-realtek-rtl.c @@ -25,6 +25,10 @@ #define REG(cpu, x) (realtek_ictl_base[cpu] + x) +struct realtek_ictl_output { + struct irq_domain *domain; +}; + static DEFINE_RAW_SPINLOCK(irq_lock); static void __iomem *realtek_ictl_base[NR_CPUS]; @@ -125,11 +129,11 @@ static const struct irq_domain_ops irq_domain_ops = { static void realtek_irq_dispatch(struct irq_desc *desc) { + struct realtek_ictl_output *output = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); unsigned int cpu = smp_processor_id(); - struct irq_domain *domain; unsigned long pending; - unsigned int soc_int; + unsigned int hw_irq; chained_irq_enter(chip, desc); pending = readl(REG(cpu, RTL_ICTL_GIMR)) & readl(REG(cpu, RTL_ICTL_GISR)); @@ -139,9 +143,8 @@ static void realtek_irq_dispatch(struct irq_desc *desc) goto out; } - domain = irq_desc_get_handler_data(desc); - for_each_set_bit(soc_int, &pending, RTL_ICTL_NUM_INPUTS) - generic_handle_domain_irq(domain, soc_int); + for_each_set_bit(hw_irq, &pending, RTL_ICTL_NUM_INPUTS) + generic_handle_domain_irq(output->domain, hw_irq); out: chained_irq_exit(chip, desc); @@ -149,10 +152,15 @@ out: static int __init realtek_setup_parents(struct device_node *node) { - int parent_irq, num_parents = of_irq_count(node); + int err, parent_irq, num_parents = of_irq_count(node); + struct realtek_ictl_output *output; struct of_phandle_args oirq; struct irq_domain *domain; + output = kcalloc(1, sizeof(*output), GFP_KERNEL); + if (!output) + return -ENOMEM; + if (WARN_ON(!num_parents)) { /* * If DT contains no parent interrupts, assume MIPS IRQ 2 (HW0) is @@ -160,8 +168,10 @@ static int __init realtek_setup_parents(struct device_node *node) */ oirq.np = of_find_compatible_node(NULL, NULL, "mti,cpu-interrupt-controller"); - if (!oirq.np) - return -EINVAL; + if (!oirq.np) { + err = -EINVAL; + goto err_out; + } oirq.args_count = 1; oirq.args[0] = 2; @@ -171,17 +181,27 @@ static int __init realtek_setup_parents(struct device_node *node) parent_irq = of_irq_get(node, 0); } - if (parent_irq <= 0) - return parent_irq ? parent_irq : -ENODEV; + if (parent_irq <= 0) { + err = parent_irq ? parent_irq : -ENODEV; + goto err_out; + } domain = irq_domain_create_linear(of_fwnode_handle(node), RTL_ICTL_NUM_INPUTS, - &irq_domain_ops, NULL); - if (!domain) - return -ENOMEM; + &irq_domain_ops, output); + if (!domain) { + err = -ENOMEM; + goto err_out; + } - irq_set_chained_handler_and_data(parent_irq, realtek_irq_dispatch, domain); + output->domain = domain; + irq_set_chained_handler_and_data(parent_irq, realtek_irq_dispatch, output); return 0; + +err_out: + kfree(output); + + return err; } static int __init realtek_rtl_of_init(struct device_node *node, struct device_node *parent) -- cgit v1.2.3 From 2a3fa7f31f43580edcba5ee9f97473801aa3deec Mon Sep 17 00:00:00 2001 From: Markus Stockhausen Date: Fri, 5 Jun 2026 23:16:43 +0200 Subject: irqchip/irq-realtek-rtl: Add mask for interrupt handling When using multiple domains for the Interrupt controller, each one must know which hardware interrupts it serves. Add a mask that is filled during setup and apply it during interrupt handling. Signed-off-by: Markus Stockhausen Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260605211646.2101652-5-markus.stockhausen@gmx.de --- drivers/irqchip/irq-realtek-rtl.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/irqchip/irq-realtek-rtl.c b/drivers/irqchip/irq-realtek-rtl.c index 547f21da3edc..9f792d4cb503 100644 --- a/drivers/irqchip/irq-realtek-rtl.c +++ b/drivers/irqchip/irq-realtek-rtl.c @@ -27,6 +27,7 @@ struct realtek_ictl_output { struct irq_domain *domain; + u32 mask; }; static DEFINE_RAW_SPINLOCK(irq_lock); @@ -109,15 +110,17 @@ static struct irq_chip realtek_ictl_irq = { .irq_set_affinity = realtek_ictl_irq_affinity, }; -static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) +static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw_irq) { + struct realtek_ictl_output *output = d->host_data; unsigned int cpu; irq_set_chip_and_handler(irq, &realtek_ictl_irq, handle_level_irq); guard(raw_spinlock_irqsave)(&irq_lock); + output->mask |= BIT(hw_irq); for_each_present_cpu(cpu) - write_irr(cpu, hw, 1); + write_irr(cpu, hw_irq, 1); return 0; } @@ -136,7 +139,7 @@ static void realtek_irq_dispatch(struct irq_desc *desc) unsigned int hw_irq; chained_irq_enter(chip, desc); - pending = readl(REG(cpu, RTL_ICTL_GIMR)) & readl(REG(cpu, RTL_ICTL_GISR)); + pending = readl(REG(cpu, RTL_ICTL_GIMR)) & readl(REG(cpu, RTL_ICTL_GISR)) & output->mask; if (unlikely(!pending)) { spurious_interrupt(); -- cgit v1.2.3 From 86ddc446fd7936fdc1ad5a79a8fe1fd9f30ca3f6 Mon Sep 17 00:00:00 2001 From: Markus Stockhausen Date: Fri, 5 Jun 2026 23:16:44 +0200 Subject: irqchip/irq-realtek-rtl: Add a select function When working with multiple domains, the interrupt registration must know to which domain it attaches. Add a select function that takes care of the lookup. Logic is as follows. If a device needs explicit parent routing it can request it by giving an index as a second argument in the device tree. E.g. intc: interrupt-controller@3000 { ... interrupts = <2>, <3>, <4>, <5>, <6>, <7>; }; uart1: uart@2100 { ... interrupt-parent = <&intc>; interrupts = <31 1>; } This way the serial console with hardware interrupt 31 will be routed via SoC interrupt 3. If the second argument is not given, the first parent interrupt of the controller is selected. Signed-off-by: Markus Stockhausen Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260605211646.2101652-6-markus.stockhausen@gmx.de --- drivers/irqchip/irq-realtek-rtl.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-realtek-rtl.c b/drivers/irqchip/irq-realtek-rtl.c index 9f792d4cb503..b256f9803fd7 100644 --- a/drivers/irqchip/irq-realtek-rtl.c +++ b/drivers/irqchip/irq-realtek-rtl.c @@ -26,7 +26,9 @@ #define REG(cpu, x) (realtek_ictl_base[cpu] + x) struct realtek_ictl_output { + struct fwnode_handle *fwnode; struct irq_domain *domain; + unsigned int index; u32 mask; }; @@ -125,9 +127,25 @@ static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw_i return 0; } +static int intc_select(struct irq_domain *d, struct irq_fwspec *fwspec, + enum irq_domain_bus_token bus_token) +{ + struct realtek_ictl_output *output = d->host_data; + unsigned int index = 0; + + if (fwspec->fwnode != output->fwnode) + return false; + + if (fwspec->param_count == 2) + index = fwspec->param[1]; + + return index == output->index; +} + static const struct irq_domain_ops irq_domain_ops = { - .map = intc_map, - .xlate = irq_domain_xlate_onecell, + .map = intc_map, + .select = intc_select, + .xlate = irq_domain_xlate_onecell, }; static void realtek_irq_dispatch(struct irq_desc *desc) @@ -197,6 +215,8 @@ static int __init realtek_setup_parents(struct device_node *node) } output->domain = domain; + output->fwnode = of_fwnode_handle(node); + output->index = 0; irq_set_chained_handler_and_data(parent_irq, realtek_irq_dispatch, output); return 0; -- cgit v1.2.3 From 291e30ea93db78726c5d512afb620f9834c629d7 Mon Sep 17 00:00:00 2001 From: Markus Stockhausen Date: Fri, 5 Jun 2026 23:16:45 +0200 Subject: irqchip/irq-realtek-rtl: Allow shuffled interrupt order The driver silently assumes that the first given interrupt in the device tree is nailed to "2". Any deviation from this will break the driver. Fix this by storing the given interrupt in the domain data structure and writing the proper value to the routing register. Signed-off-by: Markus Stockhausen Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260605211646.2101652-7-markus.stockhausen@gmx.de --- drivers/irqchip/irq-realtek-rtl.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-realtek-rtl.c b/drivers/irqchip/irq-realtek-rtl.c index b256f9803fd7..96aae205b42c 100644 --- a/drivers/irqchip/irq-realtek-rtl.c +++ b/drivers/irqchip/irq-realtek-rtl.c @@ -28,6 +28,8 @@ struct realtek_ictl_output { struct fwnode_handle *fwnode; struct irq_domain *domain; + unsigned int parent_irq; + unsigned int parent_hwirq; unsigned int index; u32 mask; }; @@ -122,7 +124,7 @@ static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw_i guard(raw_spinlock_irqsave)(&irq_lock); output->mask |= BIT(hw_irq); for_each_present_cpu(cpu) - write_irr(cpu, hw_irq, 1); + write_irr(cpu, hw_irq, output->parent_hwirq - 1); return 0; } @@ -175,6 +177,7 @@ static int __init realtek_setup_parents(struct device_node *node) { int err, parent_irq, num_parents = of_irq_count(node); struct realtek_ictl_output *output; + struct irq_data *parent_data; struct of_phandle_args oirq; struct irq_domain *domain; @@ -207,6 +210,12 @@ static int __init realtek_setup_parents(struct device_node *node) goto err_out; } + parent_data = irq_get_irq_data(parent_irq); + if (!parent_data) { + err = -EINVAL; + goto err_out; + } + domain = irq_domain_create_linear(of_fwnode_handle(node), RTL_ICTL_NUM_INPUTS, &irq_domain_ops, output); if (!domain) { @@ -217,6 +226,8 @@ static int __init realtek_setup_parents(struct device_node *node) output->domain = domain; output->fwnode = of_fwnode_handle(node); output->index = 0; + output->parent_irq = parent_irq; + output->parent_hwirq = irqd_to_hwirq(parent_data); irq_set_chained_handler_and_data(parent_irq, realtek_irq_dispatch, output); return 0; -- cgit v1.2.3 From 4940e7daf4daa105c311f66b5774af1a98fc6b53 Mon Sep 17 00:00:00 2001 From: Markus Stockhausen Date: Fri, 5 Jun 2026 23:16:46 +0200 Subject: irqchip/irq-realtek-rtl: Activate multiple parents Until now the driver exactly registers a single parent interrupt. Relax this to make use of all defined interrupts in the device tree. Signed-off-by: Markus Stockhausen Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260605211646.2101652-8-markus.stockhausen@gmx.de --- drivers/irqchip/irq-realtek-rtl.c | 88 +++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/drivers/irqchip/irq-realtek-rtl.c b/drivers/irqchip/irq-realtek-rtl.c index 96aae205b42c..c8becb458da2 100644 --- a/drivers/irqchip/irq-realtek-rtl.c +++ b/drivers/irqchip/irq-realtek-rtl.c @@ -175,64 +175,72 @@ out: static int __init realtek_setup_parents(struct device_node *node) { - int err, parent_irq, num_parents = of_irq_count(node); + int p, cnt, err, parent_irq, num_parents = of_irq_count(node); struct realtek_ictl_output *output; struct irq_data *parent_data; struct of_phandle_args oirq; struct irq_domain *domain; - output = kcalloc(1, sizeof(*output), GFP_KERNEL); + cnt = max(1, num_parents); + output = kcalloc(cnt, sizeof(*output), GFP_KERNEL); if (!output) return -ENOMEM; - if (WARN_ON(!num_parents)) { - /* - * If DT contains no parent interrupts, assume MIPS IRQ 2 (HW0) is - * connected to the first output. This is the case for all known hardware. - */ - oirq.np = of_find_compatible_node(NULL, NULL, - "mti,cpu-interrupt-controller"); - if (!oirq.np) { - err = -EINVAL; - goto err_out; + for (p = 0; p < cnt; p++) { + if (WARN_ON(!num_parents)) { + /* + * If DT contains no parent interrupts, assume MIPS IRQ 2 (HW0) is + * connected to the first output. This is the case for all known hardware. + */ + oirq.np = of_find_compatible_node(NULL, NULL, + "mti,cpu-interrupt-controller"); + if (!oirq.np) { + err = -EINVAL; + goto err_out; + } + + oirq.args_count = 1; + oirq.args[0] = 2; + parent_irq = irq_create_of_mapping(&oirq); + of_node_put(oirq.np); + } else { + parent_irq = of_irq_get(node, p); } - oirq.args_count = 1; - oirq.args[0] = 2; - parent_irq = irq_create_of_mapping(&oirq); - of_node_put(oirq.np); - } else { - parent_irq = of_irq_get(node, 0); - } + if (parent_irq <= 0) { + err = parent_irq ? parent_irq : -ENODEV; + goto err_out; + } - if (parent_irq <= 0) { - err = parent_irq ? parent_irq : -ENODEV; - goto err_out; - } + parent_data = irq_get_irq_data(parent_irq); + if (!parent_data) { + err = -EINVAL; + goto err_out; + } - parent_data = irq_get_irq_data(parent_irq); - if (!parent_data) { - err = -EINVAL; - goto err_out; - } + domain = irq_domain_create_linear(of_fwnode_handle(node), RTL_ICTL_NUM_INPUTS, + &irq_domain_ops, &output[p]); + if (!domain) { + err = -ENOMEM; + goto err_out; + } - domain = irq_domain_create_linear(of_fwnode_handle(node), RTL_ICTL_NUM_INPUTS, - &irq_domain_ops, output); - if (!domain) { - err = -ENOMEM; - goto err_out; + output[p].domain = domain; + output[p].fwnode = of_fwnode_handle(node); + output[p].index = p; + output[p].parent_irq = parent_irq; + output[p].parent_hwirq = irqd_to_hwirq(parent_data); + irq_set_chained_handler_and_data(parent_irq, realtek_irq_dispatch, &output[p]); } - output->domain = domain; - output->fwnode = of_fwnode_handle(node); - output->index = 0; - output->parent_irq = parent_irq; - output->parent_hwirq = irqd_to_hwirq(parent_data); - irq_set_chained_handler_and_data(parent_irq, realtek_irq_dispatch, output); - return 0; err_out: + while (p--) { + irq_set_chained_handler_and_data(output[p].parent_irq, NULL, NULL); + irq_domain_remove(output[p].domain); + } + kfree(output); return err; -- cgit v1.2.3 From 86e9ba573c353afd1a46816682f4cc3dbabeeb23 Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Mon, 6 Jul 2026 18:53:10 +0200 Subject: irqchip/irq-imgpdc: Remove unused driver The irq-imgpdc driver is controlled by CONFIG_IMGPDC_IRQ, which cannot be enabled. It used to be selected by CONFIG_SOC_TZ1090, but that was removed along with the metag architecture in Linux 4.17. Signed-off-by: Ben Hutchings Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/akvddtMQTDKLo2PH@decadent.org.uk --- drivers/irqchip/Kconfig | 5 - drivers/irqchip/Makefile | 1 - drivers/irqchip/irq-imgpdc.c | 495 ------------------------------------------- 3 files changed, 501 deletions(-) delete mode 100644 drivers/irqchip/irq-imgpdc.c diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 42f2278a702d..20b77fbc51ee 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -212,11 +212,6 @@ config HISILICON_IRQ_MBIGEN select ARM_GIC_V3 select ARM_GIC_V3_ITS -config IMGPDC_IRQ - bool - select GENERIC_IRQ_CHIP - select IRQ_DOMAIN - config IXP4XX_IRQ bool select IRQ_DOMAIN diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 74912429c59f..ab33cccd8471 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -44,7 +44,6 @@ obj-$(CONFIG_ARMADA_370_XP_IRQ) += irq-armada-370-xp.o obj-$(CONFIG_ATMEL_AIC_IRQ) += irq-atmel-aic-common.o irq-atmel-aic.o 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_JCORE_AIC) += irq-jcore-aic.o diff --git a/drivers/irqchip/irq-imgpdc.c b/drivers/irqchip/irq-imgpdc.c deleted file mode 100644 index 4feef4ab5fec..000000000000 --- a/drivers/irqchip/irq-imgpdc.c +++ /dev/null @@ -1,495 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * IMG PowerDown Controller (PDC) - * - * Copyright 2010-2013 Imagination Technologies Ltd. - * - * Exposes the syswake and PDC peripheral wake interrupts to the system. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -/* PDC interrupt register numbers */ - -#define PDC_IRQ_STATUS 0x310 -#define PDC_IRQ_ENABLE 0x314 -#define PDC_IRQ_CLEAR 0x318 -#define PDC_IRQ_ROUTE 0x31c -#define PDC_SYS_WAKE_BASE 0x330 -#define PDC_SYS_WAKE_STRIDE 0x8 -#define PDC_SYS_WAKE_CONFIG_BASE 0x334 -#define PDC_SYS_WAKE_CONFIG_STRIDE 0x8 - -/* PDC interrupt register field masks */ - -#define PDC_IRQ_SYS3 0x08 -#define PDC_IRQ_SYS2 0x04 -#define PDC_IRQ_SYS1 0x02 -#define PDC_IRQ_SYS0 0x01 -#define PDC_IRQ_ROUTE_WU_EN_SYS3 0x08000000 -#define PDC_IRQ_ROUTE_WU_EN_SYS2 0x04000000 -#define PDC_IRQ_ROUTE_WU_EN_SYS1 0x02000000 -#define PDC_IRQ_ROUTE_WU_EN_SYS0 0x01000000 -#define PDC_IRQ_ROUTE_WU_EN_WD 0x00040000 -#define PDC_IRQ_ROUTE_WU_EN_IR 0x00020000 -#define PDC_IRQ_ROUTE_WU_EN_RTC 0x00010000 -#define PDC_IRQ_ROUTE_EXT_EN_SYS3 0x00000800 -#define PDC_IRQ_ROUTE_EXT_EN_SYS2 0x00000400 -#define PDC_IRQ_ROUTE_EXT_EN_SYS1 0x00000200 -#define PDC_IRQ_ROUTE_EXT_EN_SYS0 0x00000100 -#define PDC_IRQ_ROUTE_EXT_EN_WD 0x00000004 -#define PDC_IRQ_ROUTE_EXT_EN_IR 0x00000002 -#define PDC_IRQ_ROUTE_EXT_EN_RTC 0x00000001 -#define PDC_SYS_WAKE_RESET 0x00000010 -#define PDC_SYS_WAKE_INT_MODE 0x0000000e -#define PDC_SYS_WAKE_INT_MODE_SHIFT 1 -#define PDC_SYS_WAKE_PIN_VAL 0x00000001 - -/* PDC interrupt constants */ - -#define PDC_SYS_WAKE_INT_LOW 0x0 -#define PDC_SYS_WAKE_INT_HIGH 0x1 -#define PDC_SYS_WAKE_INT_DOWN 0x2 -#define PDC_SYS_WAKE_INT_UP 0x3 -#define PDC_SYS_WAKE_INT_CHANGE 0x6 -#define PDC_SYS_WAKE_INT_NONE 0x4 - -/** - * struct pdc_intc_priv - private pdc interrupt data. - * @nr_perips: Number of peripheral interrupt signals. - * @nr_syswakes: Number of syswake signals. - * @perip_irqs: List of peripheral IRQ numbers handled. - * @syswake_irq: Shared PDC syswake IRQ number. - * @domain: IRQ domain for PDC peripheral and syswake IRQs. - * @pdc_base: Base of PDC registers. - * @irq_route: Cached version of PDC_IRQ_ROUTE register. - * @lock: Lock to protect the PDC syswake registers and the cached - * values of those registers in this struct. - */ -struct pdc_intc_priv { - unsigned int nr_perips; - unsigned int nr_syswakes; - unsigned int *perip_irqs; - unsigned int syswake_irq; - struct irq_domain *domain; - void __iomem *pdc_base; - - u32 irq_route; - raw_spinlock_t lock; -}; - -static void pdc_write(struct pdc_intc_priv *priv, unsigned int reg_offs, - unsigned int data) -{ - iowrite32(data, priv->pdc_base + reg_offs); -} - -static unsigned int pdc_read(struct pdc_intc_priv *priv, - unsigned int reg_offs) -{ - return ioread32(priv->pdc_base + reg_offs); -} - -/* Generic IRQ callbacks */ - -#define SYS0_HWIRQ 8 - -static unsigned int hwirq_is_syswake(irq_hw_number_t hw) -{ - return hw >= SYS0_HWIRQ; -} - -static unsigned int hwirq_to_syswake(irq_hw_number_t hw) -{ - return hw - SYS0_HWIRQ; -} - -static irq_hw_number_t syswake_to_hwirq(unsigned int syswake) -{ - return SYS0_HWIRQ + syswake; -} - -static struct pdc_intc_priv *irqd_to_priv(struct irq_data *data) -{ - return (struct pdc_intc_priv *)data->domain->host_data; -} - -/* - * perip_irq_mask() and perip_irq_unmask() use IRQ_ROUTE which also contains - * wake bits, therefore we cannot use the generic irqchip mask callbacks as they - * cache the mask. - */ - -static void perip_irq_mask(struct irq_data *data) -{ - struct pdc_intc_priv *priv = irqd_to_priv(data); - - raw_spin_lock(&priv->lock); - priv->irq_route &= ~data->mask; - pdc_write(priv, PDC_IRQ_ROUTE, priv->irq_route); - raw_spin_unlock(&priv->lock); -} - -static void perip_irq_unmask(struct irq_data *data) -{ - struct pdc_intc_priv *priv = irqd_to_priv(data); - - raw_spin_lock(&priv->lock); - priv->irq_route |= data->mask; - pdc_write(priv, PDC_IRQ_ROUTE, priv->irq_route); - raw_spin_unlock(&priv->lock); -} - -static int syswake_irq_set_type(struct irq_data *data, unsigned int flow_type) -{ - struct pdc_intc_priv *priv = irqd_to_priv(data); - unsigned int syswake = hwirq_to_syswake(data->hwirq); - unsigned int irq_mode; - unsigned int soc_sys_wake_regoff, soc_sys_wake; - - /* translate to syswake IRQ mode */ - switch (flow_type) { - case IRQ_TYPE_EDGE_BOTH: - irq_mode = PDC_SYS_WAKE_INT_CHANGE; - break; - case IRQ_TYPE_EDGE_RISING: - irq_mode = PDC_SYS_WAKE_INT_UP; - break; - case IRQ_TYPE_EDGE_FALLING: - irq_mode = PDC_SYS_WAKE_INT_DOWN; - break; - case IRQ_TYPE_LEVEL_HIGH: - irq_mode = PDC_SYS_WAKE_INT_HIGH; - break; - case IRQ_TYPE_LEVEL_LOW: - irq_mode = PDC_SYS_WAKE_INT_LOW; - break; - default: - return -EINVAL; - } - - raw_spin_lock(&priv->lock); - - /* set the IRQ mode */ - soc_sys_wake_regoff = PDC_SYS_WAKE_BASE + syswake*PDC_SYS_WAKE_STRIDE; - soc_sys_wake = pdc_read(priv, soc_sys_wake_regoff); - soc_sys_wake &= ~PDC_SYS_WAKE_INT_MODE; - soc_sys_wake |= irq_mode << PDC_SYS_WAKE_INT_MODE_SHIFT; - pdc_write(priv, soc_sys_wake_regoff, soc_sys_wake); - - /* and update the handler */ - irq_setup_alt_chip(data, flow_type); - - raw_spin_unlock(&priv->lock); - - return 0; -} - -/* applies to both peripheral and syswake interrupts */ -static int pdc_irq_set_wake(struct irq_data *data, unsigned int on) -{ - struct pdc_intc_priv *priv = irqd_to_priv(data); - irq_hw_number_t hw = data->hwirq; - unsigned int mask = (1 << 16) << hw; - unsigned int dst_irq; - - raw_spin_lock(&priv->lock); - if (on) - priv->irq_route |= mask; - else - priv->irq_route &= ~mask; - pdc_write(priv, PDC_IRQ_ROUTE, priv->irq_route); - raw_spin_unlock(&priv->lock); - - /* control the destination IRQ wakeup too for standby mode */ - if (hwirq_is_syswake(hw)) - dst_irq = priv->syswake_irq; - else - dst_irq = priv->perip_irqs[hw]; - irq_set_irq_wake(dst_irq, on); - - return 0; -} - -static void pdc_intc_perip_isr(struct irq_desc *desc) -{ - unsigned int irq = irq_desc_get_irq(desc); - struct pdc_intc_priv *priv; - unsigned int i; - - priv = (struct pdc_intc_priv *)irq_desc_get_handler_data(desc); - - /* find the peripheral number */ - for (i = 0; i < priv->nr_perips; ++i) - if (irq == priv->perip_irqs[i]) - goto found; - - /* should never get here */ - return; -found: - - /* pass on the interrupt */ - generic_handle_domain_irq(priv->domain, i); -} - -static void pdc_intc_syswake_isr(struct irq_desc *desc) -{ - struct pdc_intc_priv *priv; - unsigned int syswake; - unsigned int status; - - priv = (struct pdc_intc_priv *)irq_desc_get_handler_data(desc); - - status = pdc_read(priv, PDC_IRQ_STATUS) & - pdc_read(priv, PDC_IRQ_ENABLE); - status &= (1 << priv->nr_syswakes) - 1; - - for (syswake = 0; status; status >>= 1, ++syswake) { - /* Has this sys_wake triggered? */ - if (!(status & 1)) - continue; - - generic_handle_domain_irq(priv->domain, syswake_to_hwirq(syswake)); - } -} - -static void pdc_intc_setup(struct pdc_intc_priv *priv) -{ - int i; - unsigned int soc_sys_wake_regoff; - unsigned int soc_sys_wake; - - /* - * Mask all syswake interrupts before routing, or we could receive an - * interrupt before we're ready to handle it. - */ - pdc_write(priv, PDC_IRQ_ENABLE, 0); - - /* - * Enable routing of all syswakes - * Disable all wake sources - */ - priv->irq_route = ((PDC_IRQ_ROUTE_EXT_EN_SYS0 << priv->nr_syswakes) - - PDC_IRQ_ROUTE_EXT_EN_SYS0); - pdc_write(priv, PDC_IRQ_ROUTE, priv->irq_route); - - /* Initialise syswake IRQ */ - for (i = 0; i < priv->nr_syswakes; ++i) { - /* set the IRQ mode to none */ - soc_sys_wake_regoff = PDC_SYS_WAKE_BASE + i*PDC_SYS_WAKE_STRIDE; - soc_sys_wake = PDC_SYS_WAKE_INT_NONE - << PDC_SYS_WAKE_INT_MODE_SHIFT; - pdc_write(priv, soc_sys_wake_regoff, soc_sys_wake); - } -} - -static int pdc_intc_probe(struct platform_device *pdev) -{ - struct pdc_intc_priv *priv; - struct device_node *node = pdev->dev.of_node; - struct resource *res_regs; - struct irq_chip_generic *gc; - unsigned int i; - int irq, ret; - u32 val; - - if (!node) - return -ENOENT; - - /* Get registers */ - res_regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res_regs == NULL) { - dev_err(&pdev->dev, "cannot find registers resource\n"); - return -ENOENT; - } - - /* Allocate driver data */ - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - raw_spin_lock_init(&priv->lock); - platform_set_drvdata(pdev, priv); - - /* Ioremap the registers */ - priv->pdc_base = devm_ioremap(&pdev->dev, res_regs->start, - resource_size(res_regs)); - if (!priv->pdc_base) - return -EIO; - - /* Get number of peripherals */ - ret = of_property_read_u32(node, "num-perips", &val); - if (ret) { - dev_err(&pdev->dev, "No num-perips node property found\n"); - return -EINVAL; - } - if (val > SYS0_HWIRQ) { - dev_err(&pdev->dev, "num-perips (%u) out of range\n", val); - return -EINVAL; - } - priv->nr_perips = val; - - /* Get number of syswakes */ - ret = of_property_read_u32(node, "num-syswakes", &val); - if (ret) { - dev_err(&pdev->dev, "No num-syswakes node property found\n"); - return -EINVAL; - } - if (val > SYS0_HWIRQ) { - dev_err(&pdev->dev, "num-syswakes (%u) out of range\n", val); - return -EINVAL; - } - priv->nr_syswakes = val; - - /* Get peripheral IRQ numbers */ - priv->perip_irqs = devm_kcalloc(&pdev->dev, 4, priv->nr_perips, - GFP_KERNEL); - if (!priv->perip_irqs) - return -ENOMEM; - for (i = 0; i < priv->nr_perips; ++i) { - irq = platform_get_irq(pdev, 1 + i); - if (irq < 0) - return irq; - priv->perip_irqs[i] = irq; - } - /* check if too many were provided */ - if (platform_get_irq(pdev, 1 + i) >= 0) { - dev_err(&pdev->dev, "surplus perip IRQs detected\n"); - return -EINVAL; - } - - /* Get syswake IRQ number */ - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - priv->syswake_irq = irq; - - /* Set up an IRQ domain */ - priv->domain = irq_domain_create_linear(dev_fwnode(&pdev->dev), 16, &irq_generic_chip_ops, - priv); - if (unlikely(!priv->domain)) { - dev_err(&pdev->dev, "cannot add IRQ domain\n"); - return -ENOMEM; - } - priv->domain->flags |= IRQ_DOMAIN_FLAG_DESTROY_GC; - - /* - * Set up 2 generic irq chips with 2 chip types. - * The first one for peripheral irqs (only 1 chip type used) - * The second one for syswake irqs (edge and level chip types) - */ - ret = irq_alloc_domain_generic_chips(priv->domain, 8, 2, "pdc", - handle_level_irq, 0, 0, - IRQ_GC_INIT_NESTED_LOCK); - if (ret) - goto err_generic; - - /* peripheral interrupt chip */ - - gc = irq_get_domain_generic_chip(priv->domain, 0); - gc->unused = ~(BIT(priv->nr_perips) - 1); - gc->reg_base = priv->pdc_base; - /* - * IRQ_ROUTE contains wake bits, so we can't use the generic versions as - * they cache the mask - */ - gc->chip_types[0].regs.mask = PDC_IRQ_ROUTE; - gc->chip_types[0].chip.irq_mask = perip_irq_mask; - gc->chip_types[0].chip.irq_unmask = perip_irq_unmask; - gc->chip_types[0].chip.irq_set_wake = pdc_irq_set_wake; - - /* syswake interrupt chip */ - - gc = irq_get_domain_generic_chip(priv->domain, 8); - gc->unused = ~(BIT(priv->nr_syswakes) - 1); - gc->reg_base = priv->pdc_base; - - /* edge interrupts */ - gc->chip_types[0].type = IRQ_TYPE_EDGE_BOTH; - gc->chip_types[0].handler = handle_edge_irq; - gc->chip_types[0].regs.ack = PDC_IRQ_CLEAR; - gc->chip_types[0].regs.mask = PDC_IRQ_ENABLE; - gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit; - gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit; - gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit; - gc->chip_types[0].chip.irq_set_type = syswake_irq_set_type; - gc->chip_types[0].chip.irq_set_wake = pdc_irq_set_wake; - /* for standby we pass on to the shared syswake IRQ */ - gc->chip_types[0].chip.flags = IRQCHIP_MASK_ON_SUSPEND; - - /* level interrupts */ - gc->chip_types[1].type = IRQ_TYPE_LEVEL_MASK; - gc->chip_types[1].handler = handle_level_irq; - gc->chip_types[1].regs.ack = PDC_IRQ_CLEAR; - gc->chip_types[1].regs.mask = PDC_IRQ_ENABLE; - gc->chip_types[1].chip.irq_ack = irq_gc_ack_set_bit; - gc->chip_types[1].chip.irq_mask = irq_gc_mask_clr_bit; - gc->chip_types[1].chip.irq_unmask = irq_gc_mask_set_bit; - gc->chip_types[1].chip.irq_set_type = syswake_irq_set_type; - gc->chip_types[1].chip.irq_set_wake = pdc_irq_set_wake; - /* for standby we pass on to the shared syswake IRQ */ - gc->chip_types[1].chip.flags = IRQCHIP_MASK_ON_SUSPEND; - - /* Set up the hardware to enable interrupt routing */ - pdc_intc_setup(priv); - - /* Setup chained handlers for the peripheral IRQs */ - for (i = 0; i < priv->nr_perips; ++i) { - irq = priv->perip_irqs[i]; - irq_set_chained_handler_and_data(irq, pdc_intc_perip_isr, - priv); - } - - /* Setup chained handler for the syswake IRQ */ - irq_set_chained_handler_and_data(priv->syswake_irq, - pdc_intc_syswake_isr, priv); - - dev_info(&pdev->dev, - "PDC IRQ controller initialised (%u perip IRQs, %u syswake IRQs)\n", - priv->nr_perips, - priv->nr_syswakes); - - return 0; -err_generic: - irq_domain_remove(priv->domain); - return ret; -} - -static void pdc_intc_remove(struct platform_device *pdev) -{ - struct pdc_intc_priv *priv = platform_get_drvdata(pdev); - - for (unsigned int i = 0; i < priv->nr_perips; ++i) - irq_set_chained_handler_and_data(priv->perip_irqs[i], NULL, NULL); - - irq_set_chained_handler_and_data(priv->syswake_irq, NULL, NULL); - - irq_domain_remove(priv->domain); -} - -static const struct of_device_id pdc_intc_match[] = { - { .compatible = "img,pdc-intc" }, - {} -}; - -static struct platform_driver pdc_intc_driver = { - .driver = { - .name = "pdc-intc", - .of_match_table = pdc_intc_match, - }, - .probe = pdc_intc_probe, - .remove = pdc_intc_remove, -}; - -static int __init pdc_intc_init(void) -{ - return platform_driver_register(&pdc_intc_driver); -} -core_initcall(pdc_intc_init); -- cgit v1.2.3 From 83e089ef0d4e045efa743dcbc8de5ea0a9e4195d Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Tue, 7 Jul 2026 14:51:33 +0530 Subject: irqchip/qcom-pdc: Restructure version support The PDC irqchip driver updates IRQ_ENABLE and IRQ_CFG for three different versions v2.7, v3.0 and v3.2. These registers are organized in hardware as below on various SoCs: +---------------------------------------------------------------+ | SM8350, SM8450 | SM8550, Hamoa | SM8650, SM8750 | |---------------------------------------------------------------| | v2.7 | v3.0 | v3.2 | |---------------------------------------------------------------| | IRQ_ENABLE_BANK | IRQ_ENABLE_BANK | NA | |---------------------------------------------------------------| | IRQ_CFG | IRQ_CFG | IRQ_CFG | | | | | | | | [31:6] Unused | | | [31:5] Unused | [5] GPIO_STATUS | | | [4] GPIO_STATUS| [4] GPIO_MASK | | [31:3] Unused | [3] GPIO_MASK | [3] IRQ_ENABLE | | [0:2] Type | [0:2] Type | [0:2] Type | +---------------------------------------------------------------| All SoCs PDC chips support "pass through mode" in which all interrupts are forwarded to the GIC without any latching in the PDC hardware. So far the driver did not utilize GPIO_STATUS and GPIO_MASK from IRQ_CFG register for v3.0 and v3.2 since they are only needed to be configured when PDC runs in a specific mode named "second level interrupt controller". In that mode it can latch the GPIO interrupts in GPIO_STATUS and forward GPIO interrupts to GIC as LEVEL_HIGH type SPI interrupt. All the SoCs defaulted to pass through mode with the exception of some x1e types. x1e PDC may be set to secondary controller mode for builds on CRD boards whereas it may be set to pass through mode for IoT-EVK boards. Restructure in preparation to add the second level interrupt controller mode utilizing GPIO_STATUS and GPIO_MASK bits which changed the bit positions between v3.0 and v3.2. No functional change intended. [ tglx: Massaged change log ] Signed-off-by: Maulik Shah Signed-off-by: Thomas Gleixner Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260707-hamoa_pdc_v3-v4-1-dfd1f4a3ae89@oss.qualcomm.com --- drivers/irqchip/qcom-pdc.c | 224 +++++++++++++++++++++++++++++++-------------- 1 file changed, 154 insertions(+), 70 deletions(-) diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index 2014dbb0bc43..3d272a8e506e 100644 --- a/drivers/irqchip/qcom-pdc.c +++ b/drivers/irqchip/qcom-pdc.c @@ -21,21 +21,12 @@ #include #include -#define PDC_MAX_GPIO_IRQS 256 -#define PDC_DRV_SIZE 0x10000 - -/* Valid only on HW version < 3.2 */ -#define IRQ_ENABLE_BANK 0x10 -#define IRQ_ENABLE_BANK_MAX (IRQ_ENABLE_BANK + BITS_TO_BYTES(PDC_MAX_GPIO_IRQS)) +#define PDC_MAX_IRQS 256 +#define IRQ_ENABLE_BANK_MAX BITS_TO_BYTES(PDC_MAX_IRQS) #define IRQ_ENABLE_BANK_INDEX_MASK GENMASK(31, 5) #define IRQ_ENABLE_BANK_BIT_MASK GENMASK(4, 0) -#define IRQ_i_CFG 0x110 - -/* Valid only on HW version >= 3.2 */ -#define IRQ_i_CFG_IRQ_ENABLE 3 - -#define IRQ_i_CFG_TYPE_MASK GENMASK(2, 0) +#define PDC_DRV_SIZE 0x10000 #define PDC_VERSION_REG 0x1000 #define PDC_VERSION_MAJOR GENMASK(23, 16) #define PDC_VERSION_MINOR GENMASK(15, 8) @@ -46,22 +37,113 @@ /* Notable PDC versions */ #define PDC_VERSION_3_2 PDC_VERSION(3, 2, 0) +#define PDC_VERSION_3_0 PDC_VERSION(3, 0, 0) +#define PDC_VERSION_2_7 PDC_VERSION(2, 7, 0) + +/* + * PDC Hardware registers layout per version: + * + * IRQ_ENABLE_BANK[b], b = 0....BITS_TO_BYTES(PDC_MAX_IRQS) + * IRQ_CFG[n], n = 0....PDC_MAX_IRQS + * + * +---------------------------------------------------------------+ + * | v2.7 | v3.0 | v3.2 | + * |---------------------------------------------------------------| + * | BASE | BASE | BASE | + * |---------------------------------------------------------------| + * | | + * | IRQ_ENABLE_BANK | IRQ_ENABLE_BANK | NA | + * |---------------------------------------------------------------| + * | IRQ_CFG | IRQ_CFG | IRQ_CFG | + * | | | | + * | | | [31:6] Unused | + * | | [31:5] Unused | [5] GPIO_STATUS | + * | | [4] GPIO_STATUS| [4] GPIO_MASK | + * | [31:3] Unused | [3] GPIO_MASK | [3] IRQ_ENABLE | + * | [0:2] Type | [0:2] Type | [0:2] Type | + * +---------------------------------------------------------------+ + */ + +/** + * struct pdc_regs: PDC registers location + * + * @irq_en_reg: IRQ_ENABLE_BANK register location + * @irq_cfg_reg: IRQ_CFG register location + */ +struct pdc_regs { + u32 irq_en_reg; + u32 irq_cfg_reg; +}; + +/** + * struct pdc_irq_cfg: bit fields for PDC IRQ_CFG register + * + * @irq_enable: bit number for IRQ_ENABLE field + * @irq_type: GENMASK for IRQ_TYPE field + */ +struct pdc_irq_cfg { + u32 irq_enable; + u32 irq_type; +}; + +/** + * struct pdc_desc: PDC driver state + * + * @base: PDC base register for DRV2 / HLOS + * @prev_base: PDC DRV1 base, applicable only for x1e RTL bug. + * @version: PDC version + * @regs: PDC regs (IRQ_ENABLE_BANK and IRQ_CFG) + * @cfg_fields: Fields of IRQ_CFG reg + */ +struct pdc_desc { + void __iomem *base; + void __iomem *prev_base; + u32 version; + const struct pdc_regs *regs; + const struct pdc_irq_cfg *cfg_fields; +}; + +static const struct pdc_regs pdc_v3_2 = { + .irq_cfg_reg = 0x110, +}; + +static const struct pdc_irq_cfg pdc_cfg_v3_2 = { + .irq_enable = 3, + .irq_type = GENMASK(2, 0), +}; + +static const struct pdc_regs pdc_v3_0 = { + .irq_en_reg = 0x10, + .irq_cfg_reg = 0x110, +}; + +static const struct pdc_irq_cfg pdc_cfg_v3_0 = { + .irq_type = GENMASK(2, 0), +}; + +static const struct pdc_regs pdc_v2_7 = { + .irq_en_reg = 0x10, + .irq_cfg_reg = 0x110, +}; + +static const struct pdc_irq_cfg pdc_cfg_v2_7 = { + .irq_type = GENMASK(2, 0), +}; struct pdc_pin_region { - u32 pin_base; - u32 parent_base; - u32 cnt; + u32 pin_base; + u32 parent_base; + u32 cnt; }; #define pin_to_hwirq(r, p) ((r)->parent_base + (p) - (r)->pin_base) static DEFINE_RAW_SPINLOCK(pdc_lock); -static void __iomem *pdc_base; -static void __iomem *pdc_prev_base; static struct pdc_pin_region *pdc_region; static int pdc_region_cnt; static unsigned int pdc_version; static bool pdc_x1e_quirk; +static struct pdc_desc *pdc; static void pdc_base_reg_write(void __iomem *base, int reg, u32 i, u32 val) { @@ -70,12 +152,12 @@ static void pdc_base_reg_write(void __iomem *base, int reg, u32 i, u32 val) static void pdc_reg_write(int reg, u32 i, u32 val) { - pdc_base_reg_write(pdc_base, reg, i, val); + pdc_base_reg_write(pdc->base, reg, i, val); } static u32 pdc_reg_read(int reg, u32 i) { - return readl_relaxed(pdc_base + reg + i * sizeof(u32)); + return readl_relaxed(pdc->base + reg + i * sizeof(u32)); } static void pdc_x1e_irq_enable_write(u32 bank, u32 enable) @@ -86,24 +168,24 @@ static void pdc_x1e_irq_enable_write(u32 bank, u32 enable) switch (bank) { case 0 ... 1: /* Use previous DRV (client) region and shift to bank 3-4 */ - base = pdc_prev_base; + base = pdc->prev_base; bank += 3; break; case 2 ... 4: /* Use our own region and shift to bank 0-2 */ - base = pdc_base; + base = pdc->base; bank -= 2; break; case 5: /* No fixup required for bank 5 */ - base = pdc_base; + base = pdc->base; break; default: WARN_ON(1); return; } - pdc_base_reg_write(base, IRQ_ENABLE_BANK, bank, enable); + pdc_base_reg_write(base, pdc->regs->irq_en_reg, bank, enable); } static void pdc_enable_intr_bank(int pin_out, bool on) @@ -114,21 +196,21 @@ static void pdc_enable_intr_bank(int pin_out, bool on) index = FIELD_GET(IRQ_ENABLE_BANK_INDEX_MASK, pin_out); mask = FIELD_GET(IRQ_ENABLE_BANK_BIT_MASK, pin_out); - enable = pdc_reg_read(IRQ_ENABLE_BANK, index); + enable = pdc_reg_read(pdc->regs->irq_en_reg, index); __assign_bit(mask, &enable, on); if (pdc_x1e_quirk) pdc_x1e_irq_enable_write(index, enable); else - pdc_reg_write(IRQ_ENABLE_BANK, index, enable); + pdc_reg_write(pdc->regs->irq_en_reg, index, enable); } static void pdc_enable_intr_cfg(int pin_out, bool on) { - unsigned long enable = pdc_reg_read(IRQ_i_CFG, pin_out); + unsigned long enable = pdc_reg_read(pdc->regs->irq_cfg_reg, pin_out); - __assign_bit(IRQ_i_CFG_IRQ_ENABLE, &enable, on); - pdc_reg_write(IRQ_i_CFG, pin_out, enable); + __assign_bit(pdc->cfg_fields->irq_enable, &enable, on); + pdc_reg_write(pdc->regs->irq_cfg_reg, pin_out, enable); } static void __pdc_enable_intr(int pin_out, bool on) @@ -224,9 +306,9 @@ static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type) return -EINVAL; } - old_pdc_type = pdc_reg_read(IRQ_i_CFG, d->hwirq); - pdc_type |= (old_pdc_type & ~IRQ_i_CFG_TYPE_MASK); - pdc_reg_write(IRQ_i_CFG, d->hwirq, pdc_type); + old_pdc_type = pdc_reg_read(pdc->regs->irq_cfg_reg, d->hwirq); + pdc_type |= (old_pdc_type & ~pdc->cfg_fields->irq_type); + pdc_reg_write(pdc->regs->irq_cfg_reg, d->hwirq, pdc_type); ret = irq_chip_set_type_parent(d, type); if (ret) @@ -317,8 +399,7 @@ static int qcom_pdc_alloc(struct irq_domain *domain, unsigned int virq, parent_fwspec.param[1] = pin_to_hwirq(region, hwirq); parent_fwspec.param[2] = type; - return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, - &parent_fwspec); + return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec); } static const struct irq_domain_ops qcom_pdc_ops = { @@ -327,8 +408,9 @@ static const struct irq_domain_ops qcom_pdc_ops = { .free = irq_domain_free_irqs_common, }; -static int pdc_setup_pin_mapping(struct device_node *np) +static int pdc_setup_pin_mapping(struct device *dev) { + struct device_node *np = dev->of_node; int ret, n, i; n = of_property_count_elems_of_size(np, "qcom,pdc-ranges", sizeof(u32)); @@ -336,25 +418,22 @@ static int pdc_setup_pin_mapping(struct device_node *np) return -EINVAL; pdc_region_cnt = n / 3; - pdc_region = kzalloc_objs(*pdc_region, pdc_region_cnt); + pdc_region = devm_kcalloc(dev, pdc_region_cnt, sizeof(*pdc_region), GFP_KERNEL); if (!pdc_region) { pdc_region_cnt = 0; return -ENOMEM; } for (n = 0; n < pdc_region_cnt; n++) { - ret = of_property_read_u32_index(np, "qcom,pdc-ranges", - n * 3 + 0, + ret = of_property_read_u32_index(np, "qcom,pdc-ranges", n * 3 + 0, &pdc_region[n].pin_base); if (ret) return ret; - ret = of_property_read_u32_index(np, "qcom,pdc-ranges", - n * 3 + 1, + ret = of_property_read_u32_index(np, "qcom,pdc-ranges", n * 3 + 1, &pdc_region[n].parent_base); if (ret) return ret; - ret = of_property_read_u32_index(np, "qcom,pdc-ranges", - n * 3 + 2, + ret = of_property_read_u32_index(np, "qcom,pdc-ranges", n * 3 + 2, &pdc_region[n].cnt); if (ret) return ret; @@ -366,11 +445,11 @@ static int pdc_setup_pin_mapping(struct device_node *np) return 0; } - static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *parent) { struct irq_domain *parent_domain, *pdc_domain; struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; resource_size_t res_size; struct resource res; int ret; @@ -383,6 +462,29 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare if (res_size > resource_size(&res)) pr_warn("%pOF: invalid reg size, please fix DT\n", node); + pdc = devm_kzalloc(dev, sizeof(*pdc), GFP_KERNEL); + if (!pdc) + return -ENOMEM; + + pdc->base = devm_ioremap(dev, res.start, res_size); + if (!pdc->base) { + pr_err("%pOF: unable to map PDC registers\n", node); + return -ENXIO; + } + + pdc->version = pdc_reg_read(PDC_VERSION_REG, 0); + + if (pdc->version >= PDC_VERSION_3_2) { + pdc->cfg_fields = &pdc_cfg_v3_2; + pdc->regs = &pdc_v3_2; + } else if (pdc->version >= PDC_VERSION_3_0) { + pdc->cfg_fields = &pdc_cfg_v3_0; + pdc->regs = &pdc_v3_0; + } else { + pdc->cfg_fields = &pdc_cfg_v2_7; + pdc->regs = &pdc_v2_7; + } + /* * PDC has multiple DRV regions, each one provides the same set of * registers for a particular client in the system. Due to a hardware @@ -392,8 +494,9 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare * region with the expected offset to preserve support for old DTs. */ if (of_device_is_compatible(node, "qcom,x1e80100-pdc")) { - pdc_prev_base = ioremap(res.start - PDC_DRV_SIZE, IRQ_ENABLE_BANK_MAX); - if (!pdc_prev_base) { + pdc->prev_base = devm_ioremap(dev, res.start - PDC_DRV_SIZE, + pdc->regs->irq_en_reg + IRQ_ENABLE_BANK_MAX); + if (!pdc->prev_base) { pr_err("%pOF: unable to map previous PDC DRV region\n", node); return -ENXIO; } @@ -401,48 +504,29 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare pdc_x1e_quirk = true; } - pdc_base = ioremap(res.start, res_size); - if (!pdc_base) { - pr_err("%pOF: unable to map PDC registers\n", node); - ret = -ENXIO; - goto fail; - } - - pdc_version = pdc_reg_read(PDC_VERSION_REG, 0); - parent_domain = irq_find_host(parent); if (!parent_domain) { pr_err("%pOF: unable to find PDC's parent domain\n", node); - ret = -ENXIO; - goto fail; + return -ENXIO; } - ret = pdc_setup_pin_mapping(node); + ret = pdc_setup_pin_mapping(dev); if (ret) { pr_err("%pOF: failed to init PDC pin-hwirq mapping\n", node); - goto fail; + return ret; } - pdc_domain = irq_domain_create_hierarchy(parent_domain, - IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP, - PDC_MAX_GPIO_IRQS, - of_fwnode_handle(node), - &qcom_pdc_ops, NULL); + pdc_domain = irq_domain_create_hierarchy(parent_domain, IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP, + PDC_MAX_IRQS, of_fwnode_handle(node), + &qcom_pdc_ops, NULL); if (!pdc_domain) { pr_err("%pOF: PDC domain add failed\n", node); - ret = -ENOMEM; - goto fail; + return -ENOMEM; } irq_domain_update_bus_token(pdc_domain, DOMAIN_BUS_WAKEUP); return 0; - -fail: - kfree(pdc_region); - iounmap(pdc_base); - iounmap(pdc_prev_base); - return ret; } IRQCHIP_PLATFORM_DRIVER_BEGIN(qcom_pdc) -- cgit v1.2.3 From 60caa95aa14aed8d05abc03b70e2ff14e5ab6820 Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Tue, 7 Jul 2026 14:51:34 +0530 Subject: irqchip/qcom-pdc: Move all static variables to struct pdc_desc There are multiple static variables used in the driver. Move all to struct pdc_desc to better align with versioning support. Document them. Add a new pdc->enable_intr() callback to point to the respective versions specific enable function. Remove pdc_enable_intr() and __pdc_enable_intr() and invoke pdc->enable_intr() from the call sites. Locking in pdc_enable_intr() applies lock to all version specific pdc->enable_intr() however lock is needed only for pdc_enable_intr_bank() which uses a shared bank across on PDC v2.7 and PDC v3.0. pdc_enable_intr_cfg() do not require locking as IRQ_CFG registers are one per interrupt. Move the locking to pdc_enable_intr_bank(). No functional impact intended [ tglx: Massage change log. ] Signed-off-by: Maulik Shah Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260707-hamoa_pdc_v3-v4-2-dfd1f4a3ae89@oss.qualcomm.com --- drivers/irqchip/qcom-pdc.c | 84 ++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index 3d272a8e506e..5100a10415e0 100644 --- a/drivers/irqchip/qcom-pdc.c +++ b/drivers/irqchip/qcom-pdc.c @@ -92,15 +92,30 @@ struct pdc_irq_cfg { * @base: PDC base register for DRV2 / HLOS * @prev_base: PDC DRV1 base, applicable only for x1e RTL bug. * @version: PDC version + * @region: PDC interrupt continuous range + * @region_cnt: Total PDC ranges + * @x1e_quirk: x1e H/W Bug handling + * @lock: lock for IRQ_ENABLE_BANK protection * @regs: PDC regs (IRQ_ENABLE_BANK and IRQ_CFG) * @cfg_fields: Fields of IRQ_CFG reg + * @enable_intr: pointer to enable function based on PDC version */ struct pdc_desc { void __iomem *base; void __iomem *prev_base; u32 version; + + struct pdc_pin_region *region; + int region_cnt; + + bool x1e_quirk; + + raw_spinlock_t lock; + const struct pdc_regs *regs; const struct pdc_irq_cfg *cfg_fields; + + void (*enable_intr)(int pin_out, bool on); }; static const struct pdc_regs pdc_v3_2 = { @@ -138,11 +153,6 @@ struct pdc_pin_region { #define pin_to_hwirq(r, p) ((r)->parent_base + (p) - (r)->pin_base) -static DEFINE_RAW_SPINLOCK(pdc_lock); -static struct pdc_pin_region *pdc_region; -static int pdc_region_cnt; -static unsigned int pdc_version; -static bool pdc_x1e_quirk; static struct pdc_desc *pdc; static void pdc_base_reg_write(void __iomem *base, int reg, u32 i, u32 val) @@ -196,10 +206,12 @@ static void pdc_enable_intr_bank(int pin_out, bool on) index = FIELD_GET(IRQ_ENABLE_BANK_INDEX_MASK, pin_out); mask = FIELD_GET(IRQ_ENABLE_BANK_BIT_MASK, pin_out); + guard(raw_spinlock_irqsave)(&pdc->lock); + enable = pdc_reg_read(pdc->regs->irq_en_reg, index); __assign_bit(mask, &enable, on); - if (pdc_x1e_quirk) + if (pdc->x1e_quirk) pdc_x1e_irq_enable_write(index, enable); else pdc_reg_write(pdc->regs->irq_en_reg, index, enable); @@ -213,32 +225,15 @@ static void pdc_enable_intr_cfg(int pin_out, bool on) pdc_reg_write(pdc->regs->irq_cfg_reg, pin_out, enable); } -static void __pdc_enable_intr(int pin_out, bool on) -{ - if (pdc_version < PDC_VERSION_3_2) - pdc_enable_intr_bank(pin_out, on); - else - pdc_enable_intr_cfg(pin_out, on); -} - -static void pdc_enable_intr(struct irq_data *d, bool on) -{ - unsigned long flags; - - raw_spin_lock_irqsave(&pdc_lock, flags); - __pdc_enable_intr(d->hwirq, on); - raw_spin_unlock_irqrestore(&pdc_lock, flags); -} - static void qcom_pdc_gic_disable(struct irq_data *d) { - pdc_enable_intr(d, false); + pdc->enable_intr(d->hwirq, false); irq_chip_disable_parent(d); } static void qcom_pdc_gic_enable(struct irq_data *d) { - pdc_enable_intr(d, true); + pdc->enable_intr(d->hwirq, true); irq_chip_enable_parent(d); } @@ -350,12 +345,10 @@ static struct irq_chip qcom_pdc_gic_chip = { static struct pdc_pin_region *get_pin_region(int pin) { - int i; - - for (i = 0; i < pdc_region_cnt; i++) { - if (pin >= pdc_region[i].pin_base && - pin < pdc_region[i].pin_base + pdc_region[i].cnt) - return &pdc_region[i]; + for (int i = 0; i < pdc->region_cnt; i++) { + if (pin >= pdc->region[i].pin_base && + pin < pdc->region[i].pin_base + pdc->region[i].cnt) + return &pdc->region[i]; } return NULL; @@ -411,35 +404,35 @@ static const struct irq_domain_ops qcom_pdc_ops = { static int pdc_setup_pin_mapping(struct device *dev) { struct device_node *np = dev->of_node; - int ret, n, i; + int ret, n; n = of_property_count_elems_of_size(np, "qcom,pdc-ranges", sizeof(u32)); if (n <= 0 || n % 3) return -EINVAL; - pdc_region_cnt = n / 3; - pdc_region = devm_kcalloc(dev, pdc_region_cnt, sizeof(*pdc_region), GFP_KERNEL); - if (!pdc_region) { - pdc_region_cnt = 0; + pdc->region_cnt = n / 3; + pdc->region = devm_kcalloc(dev, pdc->region_cnt, sizeof(*pdc->region), GFP_KERNEL); + if (!pdc->region) { + pdc->region_cnt = 0; return -ENOMEM; } - for (n = 0; n < pdc_region_cnt; n++) { + for (n = 0; n < pdc->region_cnt; n++) { ret = of_property_read_u32_index(np, "qcom,pdc-ranges", n * 3 + 0, - &pdc_region[n].pin_base); + &pdc->region[n].pin_base); if (ret) return ret; ret = of_property_read_u32_index(np, "qcom,pdc-ranges", n * 3 + 1, - &pdc_region[n].parent_base); + &pdc->region[n].parent_base); if (ret) return ret; ret = of_property_read_u32_index(np, "qcom,pdc-ranges", n * 3 + 2, - &pdc_region[n].cnt); + &pdc->region[n].cnt); if (ret) return ret; - for (i = 0; i < pdc_region[n].cnt; i++) - __pdc_enable_intr(i + pdc_region[n].pin_base, 0); + for (int i = 0; i < pdc->region[n].cnt; i++) + pdc->enable_intr(i + pdc->region[n].pin_base, 0); } return 0; @@ -477,12 +470,15 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare if (pdc->version >= PDC_VERSION_3_2) { pdc->cfg_fields = &pdc_cfg_v3_2; pdc->regs = &pdc_v3_2; + pdc->enable_intr = pdc_enable_intr_cfg; } else if (pdc->version >= PDC_VERSION_3_0) { pdc->cfg_fields = &pdc_cfg_v3_0; pdc->regs = &pdc_v3_0; + pdc->enable_intr = pdc_enable_intr_bank; } else { pdc->cfg_fields = &pdc_cfg_v2_7; pdc->regs = &pdc_v2_7; + pdc->enable_intr = pdc_enable_intr_bank; } /* @@ -501,7 +497,7 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare return -ENXIO; } - pdc_x1e_quirk = true; + pdc->x1e_quirk = true; } parent_domain = irq_find_host(parent); @@ -510,6 +506,8 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare return -ENXIO; } + raw_spin_lock_init(&pdc->lock); + ret = pdc_setup_pin_mapping(dev); if (ret) { pr_err("%pOF: failed to init PDC pin-hwirq mapping\n", node); -- cgit v1.2.3 From 45af2d61edf62938b021b86439a1dd797fd74a91 Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Tue, 7 Jul 2026 14:51:35 +0530 Subject: irqchip/qcom-pdc: Differentiate between direct SPI and GPIO as SPI Before commit 4dc70713dc24 ("irqchip/qcom-pdc: Kill non-wakeup irqdomain") there were separate domains for direct SPIs and GPIOs used as SPIs. Separate domains can be useful to have separate interrupt chips for different functionalities. Since the commit unified both domains there is no way to differentiate. In preparation to add the second level interrupt controller support where GPIO interrupts get latched at PDC (but not direct SPIs) there is a need to differentiate between SPIs and GPIOs as SPIs. Reverting above commit does is not a good option as it leads to waste of resources. PDC hardware provides the IRQ_PARAM register to enumerate the number of direct SPIs and the number of GPIOs as SPIs. Further PDC allocates direct SPIs at the beginning and all GPIOs as SPIs are allocated at the end. This information can be used in the driver to differentiate them. Add the support to read this register and keep this information in struct pdc_desc. Later change utilizes it. [ tglx: Massage change log ] Signed-off-by: Maulik Shah Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260707-hamoa_pdc_v3-v4-3-dfd1f4a3ae89@oss.qualcomm.com --- drivers/irqchip/qcom-pdc.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index 5100a10415e0..253fb69e7ef2 100644 --- a/drivers/irqchip/qcom-pdc.c +++ b/drivers/irqchip/qcom-pdc.c @@ -61,6 +61,11 @@ * | | [4] GPIO_STATUS| [4] GPIO_MASK | * | [31:3] Unused | [3] GPIO_MASK | [3] IRQ_ENABLE | * | [0:2] Type | [0:2] Type | [0:2] Type | + * |---------------------------------------------------------------| + * | IRQ_PARAM | IRQ_PARAM | IRQ_PARAM | + * | | | + * | [15:8] NUM_GPIO | [15:8] NUM_GPIO | [15:8] NUM_GPIO | + * | [7:0] NUM_SPI | [7:0] NUM_SPI | [7:0] NUM_SPI | * +---------------------------------------------------------------+ */ @@ -69,10 +74,12 @@ * * @irq_en_reg: IRQ_ENABLE_BANK register location * @irq_cfg_reg: IRQ_CFG register location + * @irq_param_reg: IRQ_PARAM register location */ struct pdc_regs { u32 irq_en_reg; u32 irq_cfg_reg; + u32 irq_param_reg; }; /** @@ -92,6 +99,7 @@ struct pdc_irq_cfg { * @base: PDC base register for DRV2 / HLOS * @prev_base: PDC DRV1 base, applicable only for x1e RTL bug. * @version: PDC version + * @num_spis: Total number of direct SPI interrupts * @region: PDC interrupt continuous range * @region_cnt: Total PDC ranges * @x1e_quirk: x1e H/W Bug handling @@ -104,6 +112,7 @@ struct pdc_desc { void __iomem *base; void __iomem *prev_base; u32 version; + u32 num_spis; struct pdc_pin_region *region; int region_cnt; @@ -120,6 +129,7 @@ struct pdc_desc { static const struct pdc_regs pdc_v3_2 = { .irq_cfg_reg = 0x110, + .irq_param_reg = 0x100c, }; static const struct pdc_irq_cfg pdc_cfg_v3_2 = { @@ -130,6 +140,7 @@ static const struct pdc_irq_cfg pdc_cfg_v3_2 = { static const struct pdc_regs pdc_v3_0 = { .irq_en_reg = 0x10, .irq_cfg_reg = 0x110, + .irq_param_reg = 0x100c, }; static const struct pdc_irq_cfg pdc_cfg_v3_0 = { @@ -139,6 +150,7 @@ static const struct pdc_irq_cfg pdc_cfg_v3_0 = { static const struct pdc_regs pdc_v2_7 = { .irq_en_reg = 0x10, .irq_cfg_reg = 0x110, + .irq_param_reg = 0x100c, }; static const struct pdc_irq_cfg pdc_cfg_v2_7 = { @@ -445,6 +457,7 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare struct device *dev = &pdev->dev; resource_size_t res_size; struct resource res; + u32 irq_param; int ret; /* compat with old sm8150 DT which had very small region for PDC */ @@ -500,6 +513,9 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare pdc->x1e_quirk = true; } + irq_param = pdc_reg_read(pdc->regs->irq_param_reg, 0); + pdc->num_spis = FIELD_GET(GENMASK(7, 0), irq_param); + parent_domain = irq_find_host(parent); if (!parent_domain) { pr_err("%pOF: unable to find PDC's parent domain\n", node); -- cgit v1.2.3 From ad01c2b2f29163ca0bbe1fd4f9343fbc5119c239 Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Tue, 7 Jul 2026 14:51:36 +0530 Subject: irqchip/qcom-pdc: Configure PDC to pass through mode All PDC hardware versions support pass-through mode in which both Direct SPIs and GPIO interrupts (as SPIs) are sent to the GIC without latching at the PDC level. Newer PDCs (v3.0 onwards) also support an additional secondary controller mode where PDC latches the GPIO interrupts and sends them to the GIC as level type interrupts. Direct SPIs works the same as pass-through mode without latching at PDC even in secondary controller mode. All the SoCs use pass-through mode with the exception of x1e. The x1e PDC may be set to secondary controller mode for builds on CRD boards whereas it may be set to pass through mode for IoT-EVK boards. The mode configuration is done in firmware and initially shipped windows firmware did not have a SCM interface to read or modify the PDC mode. Only later write access was opened up for the non-secure world. The availability of write access can be probed. Therefore try to modify the to modify the PDC mode to pass-through mode via SCM write. When the write fails on older firmware assume it to work in secondary mode. In secondary mode set the separate irqchip for the GPIOs to perform additional operations only for the GPIO interrupts. [ tglx: Massage change log ] Co-developed-by: Sneh Mankad Signed-off-by: Sneh Mankad Signed-off-by: Maulik Shah Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260707-hamoa_pdc_v3-v4-4-dfd1f4a3ae89@oss.qualcomm.com --- drivers/irqchip/qcom-pdc.c | 240 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 227 insertions(+), 13 deletions(-) diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index 253fb69e7ef2..f38ee74b0397 100644 --- a/drivers/irqchip/qcom-pdc.c +++ b/drivers/irqchip/qcom-pdc.c @@ -20,12 +20,18 @@ #include #include #include +#include #define PDC_MAX_IRQS 256 #define IRQ_ENABLE_BANK_MAX BITS_TO_BYTES(PDC_MAX_IRQS) #define IRQ_ENABLE_BANK_INDEX_MASK GENMASK(31, 5) #define IRQ_ENABLE_BANK_BIT_MASK GENMASK(4, 0) +/* Secure DRV register to configure the PDC mode via qcom_scm_io_writel() */ +#define PDC_GPIO_INT_CTL_ENABLE 0xb2045e8 +#define PDC_PASS_THROUGH_MODE 0x0 +#define PDC_SECONDARY_MODE 0x1 + #define PDC_DRV_SIZE 0x10000 #define PDC_VERSION_REG 0x1000 #define PDC_VERSION_MAJOR GENMASK(23, 16) @@ -85,10 +91,14 @@ struct pdc_regs { /** * struct pdc_irq_cfg: bit fields for PDC IRQ_CFG register * + * @gpio_irq_sts: bit number for GPIO_STATUS field + * @gpio_irq_mask: bit number for GPIO_MASK field * @irq_enable: bit number for IRQ_ENABLE field * @irq_type: GENMASK for IRQ_TYPE field */ struct pdc_irq_cfg { + u32 gpio_irq_sts; + u32 gpio_irq_mask; u32 irq_enable; u32 irq_type; }; @@ -102,11 +112,14 @@ struct pdc_irq_cfg { * @num_spis: Total number of direct SPI interrupts * @region: PDC interrupt continuous range * @region_cnt: Total PDC ranges + * @mode: PDC_PASS_THROUGH_MODE or PDC_SECONDARY_MODE * @x1e_quirk: x1e H/W Bug handling * @lock: lock for IRQ_ENABLE_BANK protection * @regs: PDC regs (IRQ_ENABLE_BANK and IRQ_CFG) * @cfg_fields: Fields of IRQ_CFG reg * @enable_intr: pointer to enable function based on PDC version + * @unmask_gpio: pointer to GPIO irq unmask function + * @clear_gpio: pointer to GPIO irq clear function */ struct pdc_desc { void __iomem *base; @@ -119,12 +132,15 @@ struct pdc_desc { bool x1e_quirk; + u8 mode; raw_spinlock_t lock; const struct pdc_regs *regs; const struct pdc_irq_cfg *cfg_fields; void (*enable_intr)(int pin_out, bool on); + void (*unmask_gpio)(int pin_out, bool on); + void (*clear_gpio)(int pin_out); }; static const struct pdc_regs pdc_v3_2 = { @@ -133,6 +149,8 @@ static const struct pdc_regs pdc_v3_2 = { }; static const struct pdc_irq_cfg pdc_cfg_v3_2 = { + .gpio_irq_sts = 5, + .gpio_irq_mask = 4, .irq_enable = 3, .irq_type = GENMASK(2, 0), }; @@ -144,6 +162,8 @@ static const struct pdc_regs pdc_v3_0 = { }; static const struct pdc_irq_cfg pdc_cfg_v3_0 = { + .gpio_irq_sts = 4, + .gpio_irq_mask = 3, .irq_type = GENMASK(2, 0), }; @@ -182,6 +202,15 @@ static u32 pdc_reg_read(int reg, u32 i) return readl_relaxed(pdc->base + reg + i * sizeof(u32)); } +static inline bool pdc_pin_is_gpio(int pin_out) +{ + /* + * PDC allocates direct SPIs at the beginning and + * all GPIOs as SPIs are allocated after direct SPIs. + */ + return pin_out >= pdc->num_spis; +} + static void pdc_x1e_irq_enable_write(u32 bank, u32 enable) { void __iomem *base; @@ -229,6 +258,24 @@ static void pdc_enable_intr_bank(int pin_out, bool on) pdc_reg_write(pdc->regs->irq_en_reg, index, enable); } +static void pdc_clear_gpio_cfg(int pin_out) +{ + unsigned long gpio_sts; + + gpio_sts = pdc_reg_read(pdc->regs->irq_cfg_reg, pin_out); + __clear_bit(pdc->cfg_fields->gpio_irq_sts, &gpio_sts); + pdc_reg_write(pdc->regs->irq_cfg_reg, pin_out, gpio_sts); +} + +static void pdc_unmask_gpio_cfg(int pin_out, bool unmask) +{ + unsigned long gpio_mask; + + gpio_mask = pdc_reg_read(pdc->regs->irq_cfg_reg, pin_out); + __assign_bit(pdc->cfg_fields->gpio_irq_mask, &gpio_mask, !unmask); + pdc_reg_write(pdc->regs->irq_cfg_reg, pin_out, gpio_mask); +} + static void pdc_enable_intr_cfg(int pin_out, bool on) { unsigned long enable = pdc_reg_read(pdc->regs->irq_cfg_reg, pin_out); @@ -237,6 +284,13 @@ static void pdc_enable_intr_cfg(int pin_out, bool on) pdc_reg_write(pdc->regs->irq_cfg_reg, pin_out, enable); } +static void qcom_pdc_gic_secondary_disable(struct irq_data *d) +{ + pdc->enable_intr(d->hwirq, false); + pdc->unmask_gpio(d->hwirq, false); + irq_chip_disable_parent(d); +} + static void qcom_pdc_gic_disable(struct irq_data *d) { pdc->enable_intr(d->hwirq, false); @@ -249,6 +303,41 @@ static void qcom_pdc_gic_enable(struct irq_data *d) irq_chip_enable_parent(d); } +static void qcom_pdc_gic_secondary_enable(struct irq_data *d) +{ + pdc->enable_intr(d->hwirq, true); + pdc->unmask_gpio(d->hwirq, true); + irq_chip_enable_parent(d); +} + +static void qcom_pdc_secondary_ack(struct irq_data *d) +{ + if (!irqd_is_level_type(d)) + pdc->clear_gpio(d->hwirq); +} + +static void qcom_pdc_gic_secondary_eoi(struct irq_data *d) +{ + if (irqd_is_level_type(d)) + pdc->clear_gpio(d->hwirq); + + irq_chip_eoi_parent(d); +} + +static void qcom_pdc_secondary_mask(struct irq_data *d) +{ + pdc->enable_intr(d->hwirq, false); + pdc->unmask_gpio(d->hwirq, false); + irq_chip_mask_parent(d); +} + +static void qcom_pdc_secondary_unmask(struct irq_data *d) +{ + pdc->enable_intr(d->hwirq, true); + pdc->unmask_gpio(d->hwirq, true); + irq_chip_unmask_parent(d); +} + /* * GIC does not handle falling edge or active low. To allow falling edge and * active low interrupts to be handled at GIC, PDC has an inverter that inverts @@ -275,18 +364,18 @@ enum pdc_irq_config_bits { /** * qcom_pdc_gic_set_type: Configure PDC for the interrupt * - * @d: the interrupt data + * @d: the interrupt data * @type: the interrupt type * - * If @type is edge triggered, forward that as Rising edge as PDC - * takes care of converting falling edge to rising edge signal + * If @type is edge triggered, forward that as rising edge as PDC + * takes care of converting all edge types to rising edge signal * If @type is level, then forward that as level high as PDC - * takes care of converting falling edge to rising edge signal + * takes care of converting all level types to level high signal */ static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type) { - enum pdc_irq_config_bits pdc_type; enum pdc_irq_config_bits old_pdc_type; + enum pdc_irq_config_bits pdc_type; int ret; switch (type) { @@ -336,6 +425,72 @@ static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type) return 0; } +/** + * qcom_pdc_gic_set_type: Configure PDC for the interrupt + * + * @d: the interrupt data + * @type: the interrupt type + * + * All @type are forwarded as level high type to parent GIC + */ +static int qcom_pdc_gic_secondary_set_type(struct irq_data *d, unsigned int type) +{ + enum pdc_irq_config_bits old_pdc_type; + enum pdc_irq_config_bits pdc_type; + int ret; + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + pdc_type = PDC_EDGE_RISING; + break; + case IRQ_TYPE_EDGE_FALLING: + pdc_type = PDC_EDGE_FALLING; + break; + case IRQ_TYPE_EDGE_BOTH: + pdc_type = PDC_EDGE_DUAL; + break; + case IRQ_TYPE_LEVEL_HIGH: + pdc_type = PDC_LEVEL_HIGH; + break; + case IRQ_TYPE_LEVEL_LOW: + pdc_type = PDC_LEVEL_LOW; + break; + default: + WARN_ON(1); + return -EINVAL; + } + + old_pdc_type = pdc_reg_read(pdc->regs->irq_cfg_reg, d->hwirq); + pdc_type |= (old_pdc_type & ~pdc->cfg_fields->irq_type); + pdc_reg_write(pdc->regs->irq_cfg_reg, d->hwirq, pdc_type); + + /* + * PDC forwards GPIOs as level high to GIC in secondary + * mode. Update the type and clear any previously latched + * phantom interrupt at PDC. + */ + type = IRQ_TYPE_LEVEL_HIGH; + pdc->clear_gpio(d->hwirq); + + ret = irq_chip_set_type_parent(d, type); + if (ret) + return ret; + + /* + * When we change types the PDC can give a phantom interrupt. + * Clear it. Specifically the phantom shows up when reconfiguring + * polarity of interrupt without changing the state of the signal + * but let's be consistent and clear it always. + * + * Doing this works because we have IRQCHIP_SET_TYPE_MASKED so the + * interrupt will be cleared before the rest of the system sees it. + */ + if (old_pdc_type != pdc_type) + irq_chip_set_parent_state(d, IRQCHIP_STATE_PENDING, false); + + return 0; +} + static struct irq_chip qcom_pdc_gic_chip = { .name = "PDC", .irq_eoi = irq_chip_eoi_parent, @@ -355,6 +510,26 @@ static struct irq_chip qcom_pdc_gic_chip = { .irq_set_affinity = irq_chip_set_affinity_parent, }; +static struct irq_chip qcom_pdc_gic_secondary_chip = { + .name = "PDC", + .irq_ack = qcom_pdc_secondary_ack, + .irq_eoi = qcom_pdc_gic_secondary_eoi, + .irq_mask = qcom_pdc_secondary_mask, + .irq_unmask = qcom_pdc_secondary_unmask, + .irq_disable = qcom_pdc_gic_secondary_disable, + .irq_enable = qcom_pdc_gic_secondary_enable, + .irq_get_irqchip_state = irq_chip_get_parent_state, + .irq_set_irqchip_state = irq_chip_set_parent_state, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_type = qcom_pdc_gic_secondary_set_type, + .flags = IRQCHIP_MASK_ON_SUSPEND | + IRQCHIP_SET_TYPE_MASKED | + IRQCHIP_SKIP_SET_WAKE | + IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND, + .irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, +}; + static struct pdc_pin_region *get_pin_region(int pin) { for (int i = 0; i < pdc->region_cnt; i++) { @@ -388,16 +563,38 @@ static int qcom_pdc_alloc(struct irq_domain *domain, unsigned int virq, if (ret) return ret; + /* + * PDC secondary chip is only set for the GPIO interrupts as SPIs. + * Direct SPI interrupts are still in pass through mode (no latching + * at PDC). + */ + if (pdc->mode == PDC_SECONDARY_MODE && pdc_pin_is_gpio(hwirq)) { + ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, + &qcom_pdc_gic_secondary_chip, + NULL); + if (ret) + return ret; + + /* Secondary mode converts all interrupts to LEVEL HIGH type */ + type = IRQ_TYPE_LEVEL_HIGH; + } else { + ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, + &qcom_pdc_gic_chip, + NULL); + if (ret) + return ret; + + if (type & IRQ_TYPE_EDGE_BOTH) + type = IRQ_TYPE_EDGE_RISING; + + if (type & IRQ_TYPE_LEVEL_MASK) + type = IRQ_TYPE_LEVEL_HIGH; + } + region = get_pin_region(hwirq); if (!region) return irq_domain_disconnect_hierarchy(domain->parent, virq); - if (type & IRQ_TYPE_EDGE_BOTH) - type = IRQ_TYPE_EDGE_RISING; - - if (type & IRQ_TYPE_LEVEL_MASK) - type = IRQ_TYPE_LEVEL_HIGH; - parent_fwspec.fwnode = domain->parent->fwnode; parent_fwspec.param_count = 3; parent_fwspec.param[0] = 0; @@ -443,8 +640,13 @@ static int pdc_setup_pin_mapping(struct device *dev) if (ret) return ret; - for (int i = 0; i < pdc->region[n].cnt; i++) - pdc->enable_intr(i + pdc->region[n].pin_base, 0); + for (int i = 0; i < pdc->region[n].cnt; i++) { + if (pdc_pin_is_gpio(i + pdc->region[n].pin_base) && + pdc->mode == PDC_SECONDARY_MODE) + pdc->clear_gpio(i + pdc->region[n].pin_base); + + pdc->enable_intr(i + pdc->region[n].pin_base, false); + } } return 0; @@ -494,6 +696,8 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare pdc->enable_intr = pdc_enable_intr_bank; } + pdc->mode = PDC_PASS_THROUGH_MODE; + /* * PDC has multiple DRV regions, each one provides the same set of * registers for a particular client in the system. Due to a hardware @@ -511,6 +715,16 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare } pdc->x1e_quirk = true; + + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + ret = qcom_scm_io_writel(PDC_GPIO_INT_CTL_ENABLE, PDC_PASS_THROUGH_MODE); + if (ret) { + pdc->mode = PDC_SECONDARY_MODE; + pdc->unmask_gpio = pdc_unmask_gpio_cfg; + pdc->clear_gpio = pdc_clear_gpio_cfg; + } } irq_param = pdc_reg_read(pdc->regs->irq_param_reg, 0); -- cgit v1.2.3 From 1efffab6fe336a5c4fd3c2886f255cd2f998e65f Mon Sep 17 00:00:00 2001 From: Kemeng Shi Date: Thu, 2 Jul 2026 11:30:45 +0800 Subject: irqchip/gic-v3-its: Fix memleak in its_probe_one() Fix collection leak when its_init_domain() failed in its_probe_one(). Fixes: 4c21f3c26ecc2 ("irqchip: GICv3: ITS: DT probing and initialization") Signed-off-by: Kemeng Shi Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260702033050.1583-2-shikemeng@huaweicloud.com --- drivers/irqchip/irq-gic-v3-its.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index b57d81ad33a0..1ab6fc7ec862 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -5322,7 +5322,7 @@ static int __init its_probe_one(struct its_node *its) err = its_init_domain(its); if (err) - goto out_free_tables; + goto out_free_collection; raw_spin_lock(&its_lock); list_add(&its->entry, &its_nodes); @@ -5330,6 +5330,8 @@ static int __init its_probe_one(struct its_node *its) return 0; +out_free_collection: + kfree(its->collections); out_free_tables: its_free_tables(its); out_free_cmd: -- cgit v1.2.3 From 698a8648ca8051d34722b09b8a8088c741120ac3 Mon Sep 17 00:00:00 2001 From: Kemeng Shi Date: Thu, 2 Jul 2026 11:30:46 +0800 Subject: irqchip/gic-v3-its: Fix its node leak in gic_acpi_parse_madt_its() Fix its node leak when its_probe_one() failed in gic_acpi_parse_madt_its(). Fixes: 9585a495ac936 ("irqchip/gic-v3-its: Split allocation from initialisation of its_node") Signed-off-by: Kemeng Shi Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260702033050.1583-3-shikemeng@huaweicloud.com --- drivers/irqchip/irq-gic-v3-its.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 1ab6fc7ec862..78c34e65f267 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -5743,9 +5743,13 @@ static int __init gic_acpi_parse_madt_its(union acpi_subtable_headers *header, its->flags |= ITS_FLAGS_FORCE_NON_SHAREABLE; err = its_probe_one(its); - if (!err) - return 0; + if (err) + goto probe_err; + + return 0; +probe_err: + its_node_destroy(its); node_err: iort_deregister_domain_token(its_entry->translation_id); dom_err: -- cgit v1.2.3 From d307a7e7d9395875022ef89e6179118afb66eca8 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Wed, 8 Jul 2026 19:54:14 +0200 Subject: irqchip/qcom-pdc: Fix kernel doc for qcom_pdc_gic_secondary_set_type() Copy and pasta without fixing up the comment results in kernel doc build fail. Fixes: ad01c2b2f291 ("irqchip/qcom-pdc: Configure PDC to pass through mode") Reported-by: kernel test robot Signed-off-by: Thomas Gleixner Closes: https://lore.kernel.org/oe-kbuild-all/202607081724.42MewVti-lkp@intel.com/ --- drivers/irqchip/qcom-pdc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index f38ee74b0397..ce6d80c7f17a 100644 --- a/drivers/irqchip/qcom-pdc.c +++ b/drivers/irqchip/qcom-pdc.c @@ -426,7 +426,7 @@ static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type) } /** - * qcom_pdc_gic_set_type: Configure PDC for the interrupt + * qcom_pdc_gic_secondary_set_type: Configure PDC for the interrupt in secondary mode * * @d: the interrupt data * @type: the interrupt type -- cgit v1.2.3 From 5b4fa95e425b6699c5dac47650d7a6f7b4661263 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 9 Jul 2026 17:12:08 +0200 Subject: irqchip/gic-v5/iwb: Fix stray verb in comment Remove a stray verb ('be') that is out of context in a comment in the IWB driver. Signed-off-by: Lorenzo Pieralisi Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260709151208.1168456-1-lpieralisi@kernel.org --- drivers/irqchip/irq-gic-v5-iwb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v5-iwb.c b/drivers/irqchip/irq-gic-v5-iwb.c index 9103feb70ce8..2cf0df53e9dd 100644 --- a/drivers/irqchip/irq-gic-v5-iwb.c +++ b/drivers/irqchip/irq-gic-v5-iwb.c @@ -143,7 +143,7 @@ static int gicv5_iwb_irq_domain_translate(struct irq_domain *d, struct irq_fwspe return -EINVAL; /* - * param[0] is be the wire + * param[0] is the wire interrupt number * param[1] is the interrupt type */ *hwirq = fwspec->param[0]; -- cgit v1.2.3 From 9023960b74ab32bef73273cfb5bff3b0b70d5ef8 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Tue, 14 Jul 2026 21:13:30 +0800 Subject: irqchip: Remove redundant dev_err()/dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() and dev_err_probe() calls. Signed-off-by: Pan Chuang Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260714131331.494902-1-panchuang@vivo.com --- drivers/irqchip/irq-qcom-mpm.c | 4 +--- drivers/irqchip/irq-renesas-rzt2h.c | 2 +- drivers/irqchip/irq-renesas-rzv2h.c | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/irqchip/irq-qcom-mpm.c b/drivers/irqchip/irq-qcom-mpm.c index 181320528a47..8c575d7ce8a1 100644 --- a/drivers/irqchip/irq-qcom-mpm.c +++ b/drivers/irqchip/irq-qcom-mpm.c @@ -464,10 +464,8 @@ static int qcom_mpm_probe(struct platform_device *pdev, struct device_node *pare ret = devm_request_irq(dev, irq, qcom_mpm_handler, IRQF_NO_SUSPEND, "qcom_mpm", priv); - if (ret) { - dev_err(dev, "failed to request irq: %d\n", ret); + if (ret) goto remove_domain; - } return 0; diff --git a/drivers/irqchip/irq-renesas-rzt2h.c b/drivers/irqchip/irq-renesas-rzt2h.c index e06264add3cc..9086fc3a2043 100644 --- a/drivers/irqchip/irq-renesas-rzt2h.c +++ b/drivers/irqchip/irq-renesas-rzt2h.c @@ -398,7 +398,7 @@ static int rzt2h_icu_request_irqs(struct platform_device *pdev, struct irq_domai ret = devm_request_irq(dev, virq, handler, 0, dev_name(dev), data ?: (void *)(uintptr_t)offset); if (ret) - return dev_err_probe(dev, ret, "Failed to request IRQ %u\n", offset); + return ret; } return 0; diff --git a/drivers/irqchip/irq-renesas-rzv2h.c b/drivers/irqchip/irq-renesas-rzv2h.c index 971ac83eee90..1a4703a353e9 100644 --- a/drivers/irqchip/irq-renesas-rzv2h.c +++ b/drivers/irqchip/irq-renesas-rzv2h.c @@ -816,7 +816,7 @@ static int rzv2h_icu_setup_irqs(struct platform_device *pdev, struct irq_domain ret = devm_request_irq(dev, virq, rzv2h_icu_swint_irq, 0, dev_name(dev), (void *)(uintptr_t)i); if (ret) - return dev_err_probe(dev, ret, "Failed to request int-ca55-%u IRQ\n", i); + return ret; } /* Unmask and clear all IP/CA55 error interrupts */ @@ -844,7 +844,7 @@ static int rzv2h_icu_setup_irqs(struct platform_device *pdev, struct irq_domain ret = devm_request_irq(dev, virq, rzv2h_icu_error_irq, 0, dev_name(dev), rzv2h_icu_data); if (ret) - return dev_err_probe(dev, ret, "Failed to request icu-error-ca55 IRQ\n"); + return ret; return 0; } -- cgit v1.2.3 From 0d6a7c417983b36f0aa2a61bc650e8df04dd6ba8 Mon Sep 17 00:00:00 2001 From: Jinqian Yang Date: Wed, 15 Jul 2026 11:54:37 +0800 Subject: irqchip/gic-v3-its: Enable dynamic MSI-X allocation On ARM64 platforms with GICv3 ITS, VFIO PCI passthrough currently cannot dynamically allocate MSI-X vectors after MSI-X has been enabled. When QEMU needs to extend the vector range, it must disable MSI-X, free all interrupts, then re-enable with a larger allocation. This creates an interrupt loss window for already-active vectors. Consider HNS3 with RoCE: NIC and RDMA share one PCI device and ITS DeviceID, with MSI-X vectors partitioned as NIC (lower range) then RoCE (starting at base_vector = num_nic_msi). In VFIO passthrough, loading hns_roce after hns3 forces QEMU to tear down all interrupts before re-allocating the larger range. During this process, NIC interrupts may be lost. Testing confirmed that this occasionally occurs, causing the network port reset to fail. This appears to be unavoidable, as it's a standard approach adopted by all network card vendors. On Hisilicon HIP09 (ARM64, GICv3/GICv4.1) with latest upstream kernel and QEMU 8.2. VFIO passthrough of HNS3 NIC to VM: load both hns3 and hns_roce_hw_v2 drivers and trigger FLR, this bug will occur occasionally. After enabling dynamic MSIX allocation, this bug no longer occurs. When dynamic MSI-X allocatiojn support is enabled, adding a new vector only inserts a new entry, existing entries are untouched, so already-active vectors are unaffected. The ITTs are sized to hardware size at MSI-X domain creation time. so there is always sufficient space for any vector index that may be dynamiclly allocated later. So the ITT never needs to be resized at runtime. The dynamic allocation path uses the already initialized ITT and allocates a free entry, which means no existing vector is affected. Signed-off-by: Jinqian Yang Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Link: https://patch.msgid.link/20260715035437.700601-1-yangjinqian1@huawei.com --- drivers/irqchip/irq-gic-its-msi-parent.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-its-msi-parent.c b/drivers/irqchip/irq-gic-its-msi-parent.c index b9257103a999..b2b9d2068bb1 100644 --- a/drivers/irqchip/irq-gic-its-msi-parent.c +++ b/drivers/irqchip/irq-gic-its-msi-parent.c @@ -18,7 +18,8 @@ #define ITS_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \ MSI_FLAG_PCI_MSIX | \ - MSI_FLAG_MULTI_PCI_MSI) + MSI_FLAG_MULTI_PCI_MSI | \ + MSI_FLAG_PCI_MSIX_ALLOC_DYN) static int its_translate_frame_address(struct fwnode_handle *msi_node, phys_addr_t *pa) { -- cgit v1.2.3 From 616dd89d81ad9a3cf1cfff4088a4c43e4e00d6ba Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Wed, 8 Jul 2026 18:08:46 +0800 Subject: irqchip/renesas-irqc: Fix generic interrupt chip leak on remove The driver allocates domain generic chips probe. However, on driver removal, the generic chips are not automatically freed when the interrupt domain is removed because the domain flags do not include IRQ_DOMAIN_FLAG_DESTROY_GC. This causes both the domain generic chips structure and the associated generic chips to be leaked. Additionally, the generic chips remain on the global list and may later be accessed by generic interrupt chip suspend, resume, or shutdown callbacks after the driver has been removed, potentially resulting in a use-after-free and kernel crash. Fix the resource leak by setting IRQ_DOMAIN_FLAG_DESTROY_GC on the interrupt domain; this lets the interrupt domain core automatically release all generic chips when irq_domain_remove() is invoked, removing the need for manual cleanup calls in error paths and remove callback. Fixes: 99c221df33fbfa1b ("irqchip/renesas-irqc: Move over to nested generic chip") Signed-off-by: Qingshuang Fu Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260708100846.506314-1-fffsqian@163.com --- drivers/irqchip/irq-renesas-irqc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/irqchip/irq-renesas-irqc.c b/drivers/irqchip/irq-renesas-irqc.c index a20a6471b0e4..1ff3535a4617 100644 --- a/drivers/irqchip/irq-renesas-irqc.c +++ b/drivers/irqchip/irq-renesas-irqc.c @@ -176,6 +176,7 @@ static int irqc_probe(struct platform_device *pdev) goto err_runtime_pm_disable; } + p->irq_domain->flags |= IRQ_DOMAIN_FLAG_DESTROY_GC; ret = irq_alloc_domain_generic_chips(p->irq_domain, p->number_of_irqs, 1, "irqc", handle_level_irq, 0, 0, IRQ_GC_INIT_NESTED_LOCK); -- cgit v1.2.3 From 325ff3e78c64cd619d52b99f7c8b09a3f31e1495 Mon Sep 17 00:00:00 2001 From: Kemeng Shi Date: Tue, 21 Jul 2026 14:32:40 +0800 Subject: irqchip/gic-v3-its: Prevent leak in its_vpe_irq_domain_alloc() When its_irq_gic_domain_alloc() fails, the following its_vpe_irq_domain_free() fails to invoke its_vep_teardown() for the corresponding interrupt, which leaks the resource. Invoke its_vpe_teardown() in the error handling path to avoid the leak. [ tglx: Massaged change log ] Fixes: 7d75bbb4bc1ad ("irqchip/gic-v3-its: Add VPE irq domain allocation/teardown") Signed-off-by: Kemeng Shi Signed-off-by: Thomas Gleixner Acked-by: Marc Zyngier Link: https://patch.msgid.link/20260721063241.52549-2-shikemeng@huaweicloud.com --- drivers/irqchip/irq-gic-v3-its.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 78c34e65f267..07e3a08c3732 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -4594,6 +4594,13 @@ static int its_vpe_init(struct its_vpe *vpe) static void its_vpe_teardown(struct its_vpe *vpe) { + /* + * If vpt_page is NULL, then its_vpe_init() has failed, and + * there is nothing to do as no resource has been allocated. + */ + if (vpe->vpt_page == NULL) + return; + its_vpe_db_proxy_unmap(vpe); its_vpe_id_free(vpe->vpe_id); its_free_pending_table(vpe->vpt_page); @@ -4674,8 +4681,10 @@ static int its_vpe_irq_domain_alloc(struct irq_domain *domain, unsigned int virq irqd_set_resend_when_in_progress(irq_get_irq_data(virq + i)); } - if (err) + if (err) { + its_vpe_teardown(vm->vpes[i]); its_vpe_irq_domain_free(domain, virq, i); + } return err; } -- cgit v1.2.3 From 7f328162a98ee4e10aed42b51a7f8125c6e73384 Mon Sep 17 00:00:00 2001 From: Kemeng Shi Date: Tue, 21 Jul 2026 14:32:41 +0800 Subject: irqchip/gic-v3-its: Fix grammar and replace a bit number with its symbol Fix grammatical errors in comments and simplify the comment about reading GITS_BASER_INDIRECT to check two-level support. Signed-off-by: Kemeng Shi Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Acked-by: Marc Zyngier Link: https://patch.msgid.link/20260721063241.52549-3-shikemeng@huaweicloud.com --- drivers/irqchip/irq-gic-v3-its.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 07e3a08c3732..2c43b4ab5b70 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -163,7 +163,7 @@ struct event_lpi_map { /* * The ITS view of a device - belongs to an ITS, owns an interrupt - * translation table, and a list of interrupts. If it some of its + * translation table, and a list of interrupts. If some of its * LPIs are injected into a guest (GICv4), the event_map.vm field * indicates which one. */ @@ -2501,10 +2501,7 @@ static bool its_parse_indirect_baser(struct its_node *its, /* No need to enable Indirection if memory requirement < (psz*2)bytes */ if ((esz << ids) > (psz * 2)) { - /* - * Find out whether hw supports a single or two-level table by - * table by reading bit at offset '62' after writing '1' to it. - */ + /* Find out whether the hardware supports a single or two-level table */ its_write_baser(its, baser, val | GITS_BASER_INDIRECT); indirect = !!(baser->val & GITS_BASER_INDIRECT); -- cgit v1.2.3