From: Markus Stockhausen Date: Sat, 6 Jun 2026 06:45:54 +0000 (+0200) Subject: realtek: irq: backport upstream patches X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f4ddf8d7efb7bdc27ee25f9a7753fe8d73ccac85;p=thirdparty%2Fopenwrt.git realtek: irq: backport upstream patches The interrupt driver downstream patches have been sent upstream and got accepted. Backport them and make the driver free of downstream hacks. Link: https://github.com/openwrt/openwrt/pull/23679 Signed-off-by: Markus Stockhausen --- diff --git a/target/linux/realtek/patches-6.18/032-01-v7.2-irqchip-irq-realtek-rtl-Add-simplify-register-helper.patch b/target/linux/realtek/patches-6.18/032-01-v7.2-irqchip-irq-realtek-rtl-Add-simplify-register-helper.patch new file mode 100644 index 00000000000..92ac48d4fae --- /dev/null +++ b/target/linux/realtek/patches-6.18/032-01-v7.2-irqchip-irq-realtek-rtl-Add-simplify-register-helper.patch @@ -0,0 +1,135 @@ +From 167883f75f83088a2b32c85ce5e3d0cd1cef157b Mon Sep 17 00:00:00 2001 +From: Markus Stockhausen +Date: Thu, 4 Jun 2026 20:25:05 +0200 +Subject: [PATCH 1/8] irqchip/irq-realtek-rtl: Add/simplify register helpers + +The Realtek interrupt controller has two important registers that are used +by the driver in several places + + - GIMR: global interrupt mask register + - IRR: Interrupt routing registers + +The usage of these registers is very inconsistent. GIMR is addressed +directly while IRR has a helper that needs a macro as an input. Harmonize +this by providing consistent helpers that improve code readability. + +The callers of these helpers use classic lock/unlock functions and +sometimes use the wrong locking helper. E.g. irqsave variants are used in +mask/unmask although not needed. Adapt and fix the surrounding call +locations. + +Signed-off-by: Markus Stockhausen +Signed-off-by: Thomas Gleixner +Link: https://patch.msgid.link/20260604182506.1113440-2-markus.stockhausen@gmx.de +--- + drivers/irqchip/irq-realtek-rtl.c | 64 +++++++++++++++---------------- + 1 file changed, 32 insertions(+), 32 deletions(-) + +--- a/drivers/irqchip/irq-realtek-rtl.c ++++ b/drivers/irqchip/irq-realtek-rtl.c +@@ -37,10 +37,29 @@ static void __iomem *realtek_ictl_base; + #define IRR_OFFSET(idx) (4 * (3 - (idx * 4) / 32)) + #define IRR_SHIFT(idx) ((idx * 4) % 32) + +-static void write_irr(void __iomem *irr0, int idx, u32 value) ++static inline void enable_gimr(unsigned int hw_irq) + { +- unsigned int offset = IRR_OFFSET(idx); +- unsigned int shift = IRR_SHIFT(idx); ++ u32 gimr; ++ ++ gimr = readl(REG(RTL_ICTL_GIMR)); ++ gimr |= BIT(hw_irq); ++ writel(gimr, REG(RTL_ICTL_GIMR)); ++} ++ ++static inline void disable_gimr(unsigned int hw_irq) ++{ ++ u32 gimr; ++ ++ gimr = readl(REG(RTL_ICTL_GIMR)); ++ gimr &= ~BIT(hw_irq); ++ writel(gimr, REG(RTL_ICTL_GIMR)); ++} ++ ++static void write_irr(int hw_irq, u32 value) ++{ ++ void __iomem *irr0 = REG(RTL_ICTL_IRR0); ++ unsigned int offset = IRR_OFFSET(hw_irq); ++ unsigned int shift = IRR_SHIFT(hw_irq); + u32 irr; + + irr = readl(irr0 + offset) & ~(0xf << shift); +@@ -50,30 +69,14 @@ static void write_irr(void __iomem *irr0 + + static void realtek_ictl_unmask_irq(struct irq_data *i) + { +- unsigned long flags; +- u32 value; +- +- raw_spin_lock_irqsave(&irq_lock, flags); +- +- value = readl(REG(RTL_ICTL_GIMR)); +- value |= BIT(i->hwirq); +- writel(value, REG(RTL_ICTL_GIMR)); +- +- raw_spin_unlock_irqrestore(&irq_lock, flags); ++ guard(raw_spinlock)(&irq_lock); ++ enable_gimr(i->hwirq); + } + + static void realtek_ictl_mask_irq(struct irq_data *i) + { +- unsigned long flags; +- u32 value; +- +- raw_spin_lock_irqsave(&irq_lock, flags); +- +- value = readl(REG(RTL_ICTL_GIMR)); +- value &= ~BIT(i->hwirq); +- writel(value, REG(RTL_ICTL_GIMR)); +- +- raw_spin_unlock_irqrestore(&irq_lock, flags); ++ guard(raw_spinlock)(&irq_lock); ++ disable_gimr(i->hwirq); + } + + static struct irq_chip realtek_ictl_irq = { +@@ -84,13 +87,10 @@ static struct irq_chip realtek_ictl_irq + + static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) + { +- unsigned long flags; +- + irq_set_chip_and_handler(irq, &realtek_ictl_irq, handle_level_irq); + +- raw_spin_lock_irqsave(&irq_lock, flags); +- write_irr(REG(RTL_ICTL_IRR0), hw, 1); +- raw_spin_unlock_irqrestore(&irq_lock, flags); ++ guard(raw_spinlock_irqsave)(&irq_lock); ++ write_irr(hw, 1); + + return 0; + } +@@ -127,7 +127,6 @@ static int __init realtek_rtl_of_init(st + { + struct of_phandle_args oirq; + struct irq_domain *domain; +- unsigned int soc_irq; + int parent_irq; + + realtek_ictl_base = of_iomap(node, 0); +@@ -135,9 +134,10 @@ static int __init realtek_rtl_of_init(st + return -ENXIO; + + /* Disable all cascaded interrupts and clear routing */ +- writel(0, REG(RTL_ICTL_GIMR)); +- for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++) +- write_irr(REG(RTL_ICTL_IRR0), soc_irq, 0); ++ for (unsigned int hw_irq = 0; hw_irq < RTL_ICTL_NUM_INPUTS; hw_irq++) { ++ disable_gimr(hw_irq); ++ write_irr(hw_irq, 0); ++ } + + if (WARN_ON(!of_irq_count(node))) { + /* diff --git a/target/linux/realtek/patches-6.18/032-02-v7.2-irqchip-irq-realtek-rtl-Add-multicore-support.patch b/target/linux/realtek/patches-6.18/032-02-v7.2-irqchip-irq-realtek-rtl-Add-multicore-support.patch new file mode 100644 index 00000000000..598efbfc7ee --- /dev/null +++ b/target/linux/realtek/patches-6.18/032-02-v7.2-irqchip-irq-realtek-rtl-Add-multicore-support.patch @@ -0,0 +1,184 @@ +From a1a35c09241f0577cc40f65d7372fed01138619d Mon Sep 17 00:00:00 2001 +From: Markus Stockhausen +Date: Thu, 4 Jun 2026 20:25:06 +0200 +Subject: [PATCH 2/8] irqchip/irq-realtek-rtl: Add multicore support + +The Realtek interrupt driver currently supports only single core +systems. So the higher end devices like RTL839x and RTL930x with +dual VPEs must be driven with NR_CPU=1. Enhance the driver to +support multicore (dual VPE) systems. For this: + + - Extend the register map for multiple cores + - Search for multiple CPU cores in the devicetree + - Improve the register helpers to support multiple cores + - Add an affinity setter + - Enhance the IRQ handler for multiple cores + +Signed-off-by: Markus Stockhausen +Signed-off-by: Thomas Gleixner +Link: https://patch.msgid.link/20260604182506.1113440-3-markus.stockhausen@gmx.de +--- + drivers/irqchip/irq-realtek-rtl.c | 82 ++++++++++++++++++++----------- + 1 file changed, 54 insertions(+), 28 deletions(-) + +--- a/drivers/irqchip/irq-realtek-rtl.c ++++ b/drivers/irqchip/irq-realtek-rtl.c +@@ -23,10 +23,10 @@ + + #define RTL_ICTL_NUM_INPUTS 32 + +-#define REG(x) (realtek_ictl_base + x) ++#define REG(cpu, x) (realtek_ictl_base[cpu] + x) + + static DEFINE_RAW_SPINLOCK(irq_lock); +-static void __iomem *realtek_ictl_base; ++static void __iomem *realtek_ictl_base[NR_CPUS]; + + /* + * IRR0-IRR3 store 4 bits per interrupt, but Realtek uses inverted numbering, +@@ -37,27 +37,27 @@ static void __iomem *realtek_ictl_base; + #define IRR_OFFSET(idx) (4 * (3 - (idx * 4) / 32)) + #define IRR_SHIFT(idx) ((idx * 4) % 32) + +-static inline void enable_gimr(unsigned int hw_irq) ++static inline void enable_gimr(unsigned int cpu, unsigned int hw_irq) + { + u32 gimr; + +- gimr = readl(REG(RTL_ICTL_GIMR)); ++ gimr = readl(REG(cpu, RTL_ICTL_GIMR)); + gimr |= BIT(hw_irq); +- writel(gimr, REG(RTL_ICTL_GIMR)); ++ writel(gimr, REG(cpu, RTL_ICTL_GIMR)); + } + +-static inline void disable_gimr(unsigned int hw_irq) ++static inline void disable_gimr(unsigned int cpu, unsigned int hw_irq) + { + u32 gimr; + +- gimr = readl(REG(RTL_ICTL_GIMR)); ++ gimr = readl(REG(cpu, RTL_ICTL_GIMR)); + gimr &= ~BIT(hw_irq); +- writel(gimr, REG(RTL_ICTL_GIMR)); ++ writel(gimr, REG(cpu, RTL_ICTL_GIMR)); + } + +-static void write_irr(int hw_irq, u32 value) ++static void write_irr(unsigned int cpu, int hw_irq, u32 value) + { +- void __iomem *irr0 = REG(RTL_ICTL_IRR0); ++ void __iomem *irr0 = REG(cpu, RTL_ICTL_IRR0); + unsigned int offset = IRR_OFFSET(hw_irq); + unsigned int shift = IRR_SHIFT(hw_irq); + u32 irr; +@@ -69,28 +69,51 @@ static void write_irr(int hw_irq, u32 va + + static void realtek_ictl_unmask_irq(struct irq_data *i) + { ++ unsigned int cpu; ++ + guard(raw_spinlock)(&irq_lock); +- enable_gimr(i->hwirq); ++ for_each_cpu(cpu, irq_data_get_effective_affinity_mask(i)) ++ enable_gimr(cpu, i->hwirq); + } + + static void realtek_ictl_mask_irq(struct irq_data *i) + { ++ unsigned int cpu; ++ + guard(raw_spinlock)(&irq_lock); +- disable_gimr(i->hwirq); ++ for_each_cpu(cpu, irq_data_get_effective_affinity_mask(i)) ++ disable_gimr(cpu, i->hwirq); ++} ++ ++static int realtek_ictl_irq_affinity(struct irq_data *i, const struct cpumask *dest, bool force) ++{ ++ if (!irqd_irq_masked(i)) ++ realtek_ictl_mask_irq(i); ++ ++ irq_data_update_effective_affinity(i, dest); ++ ++ if (!irqd_irq_masked(i)) ++ realtek_ictl_unmask_irq(i); ++ ++ return IRQ_SET_MASK_OK; + } + + static struct irq_chip realtek_ictl_irq = { +- .name = "realtek-rtl-intc", +- .irq_mask = realtek_ictl_mask_irq, +- .irq_unmask = realtek_ictl_unmask_irq, ++ .name = "realtek-rtl-intc", ++ .irq_mask = realtek_ictl_mask_irq, ++ .irq_unmask = realtek_ictl_unmask_irq, ++ .irq_set_affinity = realtek_ictl_irq_affinity, + }; + + static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) + { ++ unsigned int cpu; ++ + irq_set_chip_and_handler(irq, &realtek_ictl_irq, handle_level_irq); + + guard(raw_spinlock_irqsave)(&irq_lock); +- write_irr(hw, 1); ++ for_each_present_cpu(cpu) ++ write_irr(cpu, hw, 1); + + return 0; + } +@@ -103,12 +126,13 @@ static const struct irq_domain_ops irq_d + static void realtek_irq_dispatch(struct irq_desc *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; + + chained_irq_enter(chip, desc); +- pending = readl(REG(RTL_ICTL_GIMR)) & readl(REG(RTL_ICTL_GISR)); ++ pending = readl(REG(cpu, RTL_ICTL_GIMR)) & readl(REG(cpu, RTL_ICTL_GISR)); + + if (unlikely(!pending)) { + spurious_interrupt(); +@@ -116,7 +140,7 @@ static void realtek_irq_dispatch(struct + } + + domain = irq_desc_get_handler_data(desc); +- for_each_set_bit(soc_int, &pending, 32) ++ for_each_set_bit(soc_int, &pending, RTL_ICTL_NUM_INPUTS) + generic_handle_domain_irq(domain, soc_int); + + out: +@@ -127,16 +151,18 @@ static int __init realtek_rtl_of_init(st + { + struct of_phandle_args oirq; + struct irq_domain *domain; +- int parent_irq; ++ int cpu, parent_irq; + +- realtek_ictl_base = of_iomap(node, 0); +- if (!realtek_ictl_base) +- 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(hw_irq); +- write_irr(hw_irq, 0); ++ 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))) { diff --git a/target/linux/realtek/patches-6.18/032-03-v7.3-irqchip-irq-realtek-rtl-Use-helper-for-parent-setup.patch b/target/linux/realtek/patches-6.18/032-03-v7.3-irqchip-irq-realtek-rtl-Use-helper-for-parent-setup.patch new file mode 100644 index 00000000000..1919008877c --- /dev/null +++ b/target/linux/realtek/patches-6.18/032-03-v7.3-irqchip-irq-realtek-rtl-Use-helper-for-parent-setup.patch @@ -0,0 +1,106 @@ +From 62e8298a43e3f0de23cbd52e4ac2399356a724cc Mon Sep 17 00:00:00 2001 +From: Markus Stockhausen +Date: Fri, 5 Jun 2026 16:05:22 +0200 +Subject: [PATCH 3/8] irqchip/irq-realtek-rtl: Use helper for parent setup + +With the upcoming commits the parent interrupt setup will be extended. +Relocate it 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 +--- + drivers/irqchip/irq-realtek-rtl.c | 60 +++++++++++++++++-------------- + 1 file changed, 33 insertions(+), 27 deletions(-) + +--- 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(st + 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); diff --git a/target/linux/realtek/patches-6.18/032-04-v7.3-irqchip-irq-realtek-rtl-Add-interrupt-data-structure.patch b/target/linux/realtek/patches-6.18/032-04-v7.3-irqchip-irq-realtek-rtl-Add-interrupt-data-structure.patch new file mode 100644 index 00000000000..778b608738f --- /dev/null +++ b/target/linux/realtek/patches-6.18/032-04-v7.3-irqchip-irq-realtek-rtl-Add-interrupt-data-structure.patch @@ -0,0 +1,117 @@ +From 1f922b06f4a1aa292af1c45d00de41f259cc7f17 Mon Sep 17 00:00:00 2001 +From: Markus Stockhausen +Date: Fri, 5 Jun 2026 18:07:24 +0200 +Subject: [PATCH 4/8] 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 +--- + drivers/irqchip/irq-realtek-rtl.c | 48 ++++++++++++++++++++++--------- + 1 file changed, 34 insertions(+), 14 deletions(-) + +--- 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_d + + 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 + 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( + */ + 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( + 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) diff --git a/target/linux/realtek/patches-6.18/032-05-v7.3-irqchip-irq-realtek-rtl-Add-mask-for-interrupt-handl.patch b/target/linux/realtek/patches-6.18/032-05-v7.3-irqchip-irq-realtek-rtl-Add-mask-for-interrupt-handl.patch new file mode 100644 index 00000000000..0b990f31f0c --- /dev/null +++ b/target/linux/realtek/patches-6.18/032-05-v7.3-irqchip-irq-realtek-rtl-Add-mask-for-interrupt-handl.patch @@ -0,0 +1,53 @@ +From 79a2befb817a8bf5930d80ea6c2d237e74d16728 Mon Sep 17 00:00:00 2001 +From: Markus Stockhausen +Date: Fri, 5 Jun 2026 18:19:12 +0200 +Subject: [PATCH 5/8] 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 +--- + drivers/irqchip/irq-realtek-rtl.c | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +--- 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 + 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(); diff --git a/target/linux/realtek/patches-6.18/032-06-v7.3-irqchip-irq-realtek-rtl-Add-a-select-function.patch b/target/linux/realtek/patches-6.18/032-06-v7.3-irqchip-irq-realtek-rtl-Add-a-select-function.patch new file mode 100644 index 00000000000..8498d251aec --- /dev/null +++ b/target/linux/realtek/patches-6.18/032-06-v7.3-irqchip-irq-realtek-rtl-Add-a-select-function.patch @@ -0,0 +1,81 @@ +From 4be0e039e8456b0e3111072ac9c66c1e62d1d8df Mon Sep 17 00:00:00 2001 +From: Markus Stockhausen +Date: Fri, 5 Jun 2026 19:39:41 +0200 +Subject: [PATCH 6/8] 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 +--- + drivers/irqchip/irq-realtek-rtl.c | 24 ++++++++++++++++++++++-- + 1 file changed, 22 insertions(+), 2 deletions(-) + +--- 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 + 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( + } + + 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; diff --git a/target/linux/realtek/patches-6.18/032-07-v7.3-irqchip-irq-realtek-rtl-Allow-shuffled-interrupt-ord.patch b/target/linux/realtek/patches-6.18/032-07-v7.3-irqchip-irq-realtek-rtl-Allow-shuffled-interrupt-ord.patch new file mode 100644 index 00000000000..8908cbc04ae --- /dev/null +++ b/target/linux/realtek/patches-6.18/032-07-v7.3-irqchip-irq-realtek-rtl-Allow-shuffled-interrupt-ord.patch @@ -0,0 +1,66 @@ +From 18652e0d0527bc17542699d7dbdc14e4054acb2f Mon Sep 17 00:00:00 2001 +From: Markus Stockhausen +Date: Fri, 5 Jun 2026 19:47:35 +0200 +Subject: [PATCH 7/8] 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 +--- + drivers/irqchip/irq-realtek-rtl.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +--- 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 + 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( + { + 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( + 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( + 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; diff --git a/target/linux/realtek/patches-6.18/032-08-v7.3-irqchip-irq-realtek-rtl-Activate-multiple-parents.patch b/target/linux/realtek/patches-6.18/032-08-v7.3-irqchip-irq-realtek-rtl-Activate-multiple-parents.patch new file mode 100644 index 00000000000..8c2f5211b18 --- /dev/null +++ b/target/linux/realtek/patches-6.18/032-08-v7.3-irqchip-irq-realtek-rtl-Activate-multiple-parents.patch @@ -0,0 +1,128 @@ +From 3031d8f878219f446330088193d799a3e67122d5 Mon Sep 17 00:00:00 2001 +From: Markus Stockhausen +Date: Fri, 5 Jun 2026 20:49:47 +0200 +Subject: [PATCH 8/8] 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 +--- + drivers/irqchip/irq-realtek-rtl.c | 88 +++++++++++++++++-------------- + 1 file changed, 48 insertions(+), 40 deletions(-) + +--- 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; diff --git a/target/linux/realtek/patches-6.18/314-irqchip-irq-realtek-rtl-add-VPE-support.patch b/target/linux/realtek/patches-6.18/314-irqchip-irq-realtek-rtl-add-VPE-support.patch deleted file mode 100644 index caa4e08ed3f..00000000000 --- a/target/linux/realtek/patches-6.18/314-irqchip-irq-realtek-rtl-add-VPE-support.patch +++ /dev/null @@ -1,428 +0,0 @@ -From 6c18e9c491959ac0674ebe36b09f9ddc3f2c9bce Mon Sep 17 00:00:00 2001 -From: Birger Koblitz -Date: Fri, 31 Dec 2021 11:56:49 +0100 -Subject: [PATCH] realtek: Add VPE support for the IRQ driver - -In order to support VSMP, enable support for both VPEs of the RTL839X -and RTL930X SoCs in the irq-realtek-rtl driver. Add support for IRQ -affinity setting. - -Up to kernel 5.15 this patch was divided into two parts - -315-irqchip-irq-realtek-rtl-add-VPE-support.patch -319-irqchip-irq-realtek-rtl-fix-VPE-affinity.patch - -As both parts will only work in combination they have been merged into -one patch. - -Submitted-by: Birger Koblitz -Submitted-by: INAGAKI Hiroshi -Signed-off-by: Markus Stockhausen ---- - drivers/irqchip/irq-realtek-rtl.c | 296 +++++++++++++++++++++++++----- - 1 file changed, 249 insertions(+), 47 deletions(-) - ---- a/drivers/irqchip/irq-realtek-rtl.c -+++ b/drivers/irqchip/irq-realtek-rtl.c -@@ -22,22 +22,58 @@ - #define RTL_ICTL_IRR3 0x14 - - #define RTL_ICTL_NUM_INPUTS 32 -- --#define REG(x) (realtek_ictl_base + x) -+#define RTL_ICTL_NUM_OUTPUTS 15 - - static DEFINE_RAW_SPINLOCK(irq_lock); --static void __iomem *realtek_ictl_base; -+ -+#define REG(offset, cpu) (realtek_ictl_base[cpu] + offset) -+ -+static u32 realtek_ictl_unmask[NR_CPUS]; -+static void __iomem *realtek_ictl_base[NR_CPUS]; -+static cpumask_t realtek_ictl_cpu_configurable; -+ -+struct realtek_ictl_output { -+ /* IRQ controller data */ -+ struct fwnode_handle *fwnode; -+ /* Output specific data */ -+ unsigned int output_index; -+ struct irq_domain *domain; -+ u32 child_mask; -+}; - - /* -- * IRR0-IRR3 store 4 bits per interrupt, but Realtek uses inverted numbering, -- * placing IRQ 31 in the first four bits. A routing value of '0' means the -- * interrupt is left disconnected. Routing values {1..15} connect to output -- * lines {0..14}. -+ * Per CPU we have a set of 5 registers that determine interrupt handling for -+ * 32 external interrupts. GIMR (enable/disable interrupt) plus IRR0-IRR3 that -+ * contain "routing" or "priority" values. GIMR uses one bit for each interrupt -+ * and IRRx store 4 bits per interrupt. Realtek uses inverted numbering, -+ * placing IRQ 31 in the first four bits. The register combinations give the -+ * following results for a single interrupt in the wild: -+ * -+ * a) GIMR = 0 / IRRx > 0 -> no interrupts -+ * b) GIMR = 0 / IRRx = 0 -> no interrupts -+ * c) GIMR = 1 / IRRx > 0 -> interrupts -+ * d) GIMR = 1 / IRRx = 0 -> rare interrupts in SMP environment -+ * -+ * Combination d) seems to trigger interrupts only on a VPE if the other VPE -+ * has GIMR = 0 and IRRx > 0. E.g. busy without interrupts allowed. To provide -+ * IRQ balancing features in SMP this driver will handle the registers as -+ * follows: -+ * -+ * 1) set IRRx > 0 for VPE where the interrupt is desired -+ * 2) set IRRx = 0 for VPE where the interrupt is not desired -+ * 3) set both GIMR = 0 to mask (disabled) interrupt -+ * 4) set GIMR = 1 to unmask (enable) interrupt but only for VPE where IRRx > 0 - */ -+ - #define IRR_OFFSET(idx) (4 * (3 - (idx * 4) / 32)) - #define IRR_SHIFT(idx) ((idx * 4) % 32) - --static void write_irr(void __iomem *irr0, int idx, u32 value) -+static inline u32 read_irr(void __iomem *irr0, int idx) -+{ -+ return (readl(irr0 + IRR_OFFSET(idx)) >> IRR_SHIFT(idx)) & 0xf; -+} -+ -+static inline void write_irr(void __iomem *irr0, int idx, u32 value) - { - unsigned int offset = IRR_OFFSET(idx); - unsigned int shift = IRR_SHIFT(idx); -@@ -48,16 +84,33 @@ static void write_irr(void __iomem *irr0 - writel(irr, irr0 + offset); - } - -+static inline void enable_gimr(int hwirq, int cpu) -+{ -+ u32 value; -+ -+ value = readl(REG(RTL_ICTL_GIMR, cpu)); -+ value |= (BIT(hwirq) & realtek_ictl_unmask[cpu]); -+ writel(value, REG(RTL_ICTL_GIMR, cpu)); -+} -+ -+static inline void disable_gimr(int hwirq, int cpu) -+{ -+ u32 value; -+ -+ value = readl(REG(RTL_ICTL_GIMR, cpu)); -+ value &= ~BIT(hwirq); -+ writel(value, REG(RTL_ICTL_GIMR, cpu)); -+} -+ - static void realtek_ictl_unmask_irq(struct irq_data *i) - { - unsigned long flags; -- u32 value; -+ int cpu; - - raw_spin_lock_irqsave(&irq_lock, flags); - -- value = readl(REG(RTL_ICTL_GIMR)); -- value |= BIT(i->hwirq); -- writel(value, REG(RTL_ICTL_GIMR)); -+ for_each_cpu(cpu, &realtek_ictl_cpu_configurable) -+ enable_gimr(i->hwirq, cpu); - - raw_spin_unlock_irqrestore(&irq_lock, flags); - } -@@ -65,110 +118,261 @@ static void realtek_ictl_unmask_irq(stru - static void realtek_ictl_mask_irq(struct irq_data *i) - { - unsigned long flags; -- u32 value; -+ int cpu; - - raw_spin_lock_irqsave(&irq_lock, flags); - -- value = readl(REG(RTL_ICTL_GIMR)); -- value &= ~BIT(i->hwirq); -- writel(value, REG(RTL_ICTL_GIMR)); -+ for_each_cpu(cpu, &realtek_ictl_cpu_configurable) -+ disable_gimr(i->hwirq, cpu); - - raw_spin_unlock_irqrestore(&irq_lock, flags); - } - -+static int __maybe_unused realtek_ictl_irq_affinity(struct irq_data *i, -+ const struct cpumask *dest, -+ bool force) -+{ -+ struct realtek_ictl_output *output = i->domain->host_data; -+ cpumask_t cpu_configure; -+ cpumask_t cpu_disable; -+ cpumask_t cpu_enable; -+ unsigned long flags; -+ int cpu; -+ -+ raw_spin_lock_irqsave(&irq_lock, flags); -+ -+ cpumask_and(&cpu_configure, cpu_present_mask, &realtek_ictl_cpu_configurable); -+ -+ cpumask_and(&cpu_enable, &cpu_configure, dest); -+ cpumask_andnot(&cpu_disable, &cpu_configure, dest); -+ -+ for_each_cpu(cpu, &cpu_disable) { -+ write_irr(REG(RTL_ICTL_IRR0, cpu), i->hwirq, 0); -+ realtek_ictl_unmask[cpu] &= ~BIT(i->hwirq); -+ disable_gimr(i->hwirq, cpu); -+ } -+ -+ for_each_cpu(cpu, &cpu_enable) { -+ write_irr(REG(RTL_ICTL_IRR0, cpu), i->hwirq, output->output_index + 1); -+ realtek_ictl_unmask[cpu] |= BIT(i->hwirq); -+ enable_gimr(i->hwirq, cpu); -+ } -+ -+ irq_data_update_effective_affinity(i, &cpu_enable); -+ -+ raw_spin_unlock_irqrestore(&irq_lock, flags); -+ -+ return IRQ_SET_MASK_OK; -+} -+ - static struct irq_chip realtek_ictl_irq = { - .name = "realtek-rtl-intc", - .irq_mask = realtek_ictl_mask_irq, - .irq_unmask = realtek_ictl_unmask_irq, -+#ifdef CONFIG_SMP -+ .irq_set_affinity = realtek_ictl_irq_affinity, -+#endif - }; - - static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) - { -+ struct realtek_ictl_output *output = d->host_data; - unsigned long flags; - - irq_set_chip_and_handler(irq, &realtek_ictl_irq, handle_level_irq); - - raw_spin_lock_irqsave(&irq_lock, flags); -- write_irr(REG(RTL_ICTL_IRR0), hw, 1); -+ -+ output->child_mask |= BIT(hw); -+ write_irr(REG(RTL_ICTL_IRR0, 0), hw, output->output_index + 1); -+ realtek_ictl_unmask[0] |= BIT(hw); -+ - raw_spin_unlock_irqrestore(&irq_lock, flags); - - 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; -+ bool routed_elsewhere; -+ unsigned long flags; -+ u32 routing_old; -+ int cpu; -+ -+ if (fwspec->fwnode != output->fwnode) -+ return false; -+ -+ /* Original specifiers had only one parameter */ -+ if (fwspec->param_count < 2) -+ return true; -+ -+ raw_spin_lock_irqsave(&irq_lock, flags); -+ -+ /* -+ * Inputs can only be routed to one output, so they shouldn't be -+ * allowed to end up in multiple domains. -+ */ -+ for_each_cpu(cpu, &realtek_ictl_cpu_configurable) { -+ routing_old = read_irr(REG(RTL_ICTL_IRR0, cpu), fwspec->param[0]); -+ routed_elsewhere = routing_old && fwspec->param[1] != routing_old - 1; -+ if (routed_elsewhere) { -+ pr_warn("soc int %d already routed to output %d\n", -+ fwspec->param[0], routing_old - 1); -+ break; -+ } -+ } -+ -+ raw_spin_unlock_irqrestore(&irq_lock, flags); -+ -+ return !routed_elsewhere && fwspec->param[1] == output->output_index; -+} -+ - static const struct irq_domain_ops irq_domain_ops = { - .map = intc_map, -+ .select = intc_select, - .xlate = irq_domain_xlate_onecell, - }; - - 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); -- struct irq_domain *domain; -+ int cpu = smp_processor_id(); - unsigned long pending; - unsigned int soc_int; - - chained_irq_enter(chip, desc); -- pending = readl(REG(RTL_ICTL_GIMR)) & readl(REG(RTL_ICTL_GISR)); -+ pending = readl(REG(RTL_ICTL_GIMR, cpu)) & readl(REG(RTL_ICTL_GISR, cpu)) -+ & output->child_mask; - - if (unlikely(!pending)) { - spurious_interrupt(); - goto out; - } - -- domain = irq_desc_get_handler_data(desc); -- for_each_set_bit(soc_int, &pending, 32) -- generic_handle_domain_irq(domain, soc_int); -+ for_each_set_bit(soc_int, &pending, RTL_ICTL_NUM_INPUTS) -+ generic_handle_domain_irq(output->domain, soc_int); - - out: - chained_irq_exit(chip, desc); - } - -+/* -+ * SoC interrupts are cascaded to MIPS CPU interrupts according to the -+ * interrupt-map in the device tree. Each SoC interrupt gets 4 bits for -+ * the CPU interrupt in an Interrupt Routing Register. Max 32 SoC interrupts -+ * thus go into 4 IRRs. A routing value of '0' means the interrupt is left -+ * disconnected. Routing values {1..15} connect to output lines {0..14}. -+ */ -+static int __init setup_parent_interrupts(struct device_node *node, int *parents, -+ unsigned int num_parents) -+{ -+ struct realtek_ictl_output *outputs; -+ struct realtek_ictl_output *output; -+ struct irq_domain *domain; -+ unsigned int p; -+ -+ outputs = kcalloc(num_parents, sizeof(*outputs), GFP_KERNEL); -+ if (!outputs) -+ return -ENOMEM; -+ -+ for (p = 0; p < num_parents; p++) { -+ output = outputs + p; -+ -+ domain = irq_domain_add_linear(node, RTL_ICTL_NUM_INPUTS, &irq_domain_ops, output); -+ if (!domain) -+ goto domain_err; -+ -+ output->fwnode = of_fwnode_handle(node); -+ output->output_index = p; -+ output->domain = domain; -+ -+ irq_set_chained_handler_and_data(parents[p], realtek_irq_dispatch, output); -+ } -+ -+ return 0; -+ -+domain_err: -+ while (p--) { -+ irq_set_chained_handler_and_data(parents[p], NULL, NULL); -+ irq_domain_remove(outputs[p].domain); -+ } -+ -+ kfree(outputs); -+ -+ return -ENOMEM; -+} -+ - static int __init realtek_rtl_of_init(struct device_node *node, struct device_node *parent) - { -+ int parent_irqs[RTL_ICTL_NUM_OUTPUTS]; - struct of_phandle_args oirq; -- struct irq_domain *domain; -+ unsigned int num_parents; - unsigned int soc_irq; -- int parent_irq; -+ unsigned int p; -+ int cpu; - -- realtek_ictl_base = of_iomap(node, 0); -- if (!realtek_ictl_base) -+ cpumask_clear(&realtek_ictl_cpu_configurable); -+ -+ for (cpu = 0; cpu < NR_CPUS; cpu++) { -+ realtek_ictl_base[cpu] = of_iomap(node, cpu); -+ if (realtek_ictl_base[cpu]) { -+ cpumask_set_cpu(cpu, &realtek_ictl_cpu_configurable); -+ -+ /* Disable all cascaded interrupts and clear routing */ -+ for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++) { -+ write_irr(REG(RTL_ICTL_IRR0, cpu), soc_irq, 0); -+ realtek_ictl_unmask[cpu] &= ~BIT(soc_irq); -+ disable_gimr(soc_irq, cpu); -+ } -+ } -+ } -+ -+ if (cpumask_empty(&realtek_ictl_cpu_configurable)) - return -ENXIO; - -- /* Disable all cascaded interrupts and clear routing */ -- writel(0, REG(RTL_ICTL_GIMR)); -- for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++) -- write_irr(REG(RTL_ICTL_IRR0), soc_irq, 0); -+ num_parents = of_irq_count(node); -+ if (num_parents > RTL_ICTL_NUM_OUTPUTS) { -+ pr_err("too many parent interrupts\n"); -+ return -EINVAL; -+ } -+ -+ for (p = 0; p < num_parents; p++) -+ parent_irqs[p] = of_irq_get(node, p); - -- 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. -+ * Since this is to account for old devicetrees with one-cell -+ * interrupt specifiers, only one output domain is needed. - */ - oirq.np = of_find_compatible_node(NULL, NULL, "mti,cpu-interrupt-controller"); -- oirq.args_count = 1; -- oirq.args[0] = 2; - -- parent_irq = irq_create_of_mapping(&oirq); -+ if (oirq.np) { -+ oirq.args_count = 1; -+ oirq.args[0] = 2; -+ -+ parent_irqs[0] = irq_create_of_mapping(&oirq); -+ num_parents = 1; -+ } - - 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; -- -- domain = irq_domain_create_linear(of_fwnode_handle(node), RTL_ICTL_NUM_INPUTS, &irq_domain_ops, NULL); -- if (!domain) -- return -ENOMEM; -- -- irq_set_chained_handler_and_data(parent_irq, realtek_irq_dispatch, domain); -+ /* Ensure we haven't collected any errors before proceeding */ -+ for (p = 0; p < num_parents; p++) { -+ if (parent_irqs[p] < 0) -+ return parent_irqs[p]; -+ if (!parent_irqs[p]) -+ return -ENODEV; -+ } - -- return 0; -+ return setup_parent_interrupts(node, &parent_irqs[0], num_parents); - } - - IRQCHIP_DECLARE(realtek_rtl_intc, "realtek,rtl-intc", realtek_rtl_of_init);