From 2cd00b51470a30198b048a5fca48a04db77e29cc Mon Sep 17 00:00:00 2001
From: INAGAKI Hiroshi <musashino.open@gmail.com>
Date: Fri, 21 May 2021 23:16:37 +0900
Subject: [PATCH] realtek: backport irq-realtek-rtl driver from 5.12 to 5.10

This patch backports "irq-realtek-rtl" driver to Kernel 5.10 from 5.12.
"MACH_REALTEK_RTL" is used as a platform name in upstream, but "RTL838X"
is used in OpenWrt, so update the dependency by the additional patch.

Submitted-by: INAGAKI Hiroshi <musashino.open@gmail.com>
---
 drivers/irqchip/irq-realtek-rtl.c             | 38 +++++++++++------
 1 files changed, 58 insertions(+), 20 deletions(-)

--- a/drivers/irqchip/irq-realtek-rtl.c
+++ b/drivers/irqchip/irq-realtek-rtl.c
@@ -28,6 +28,7 @@ static DEFINE_RAW_SPINLOCK(irq_lock);
 
 #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;
 
@@ -41,11 +42,29 @@ struct realtek_ictl_output {
 };
 
 /*
- * 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)
 
@@ -65,19 +84,33 @@ static inline void write_irr(void __iome
 	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);
 
-	for_each_cpu(cpu, &realtek_ictl_cpu_configurable) {
-		value = readl(REG(RTL_ICTL_GIMR, cpu));
-		value |= BIT(i->hwirq);
-		writel(value, REG(RTL_ICTL_GIMR, cpu));
-	}
+	for_each_cpu(cpu, &realtek_ictl_cpu_configurable)
+		enable_gimr(i->hwirq, cpu);
 
 	raw_spin_unlock_irqrestore(&irq_lock, flags);
 }
@@ -85,16 +118,12 @@ 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);
 
-	for_each_cpu(cpu, &realtek_ictl_cpu_configurable) {
-		value = readl(REG(RTL_ICTL_GIMR, cpu));
-		value &= ~BIT(i->hwirq);
-		writel(value, REG(RTL_ICTL_GIMR, cpu));
-	}
+	for_each_cpu(cpu, &realtek_ictl_cpu_configurable)
+		disable_gimr(i->hwirq, cpu);
 
 	raw_spin_unlock_irqrestore(&irq_lock, flags);
 }
@@ -116,11 +145,17 @@ static int __maybe_unused realtek_ictl_i
 	cpumask_and(&cpu_enable, &cpu_configure, dest);
 	cpumask_andnot(&cpu_disable, &cpu_configure, dest);
 
-	for_each_cpu(cpu, &cpu_disable)
+	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)
+	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);
 
@@ -149,6 +184,7 @@ static int intc_map(struct irq_domain *d
 
 	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);
 
@@ -285,9 +321,11 @@ static int __init realtek_rtl_of_init(st
 			cpumask_set_cpu(cpu, &realtek_ictl_cpu_configurable);
 
 			/* Disable all cascaded interrupts and clear routing */
-			writel(0, REG(RTL_ICTL_GIMR, cpu));
-			for (soc_irq = 0; soc_irq < RTL_ICTL_NUM_INPUTS; soc_irq++)
+			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);
+			}
 		}
 	}
 
