[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20260203231823.208661-7-prabhakar.mahadev-lad.rj@bp.renesas.com>
Date: Tue, 3 Feb 2026 23:18:23 +0000
From: Prabhakar <prabhakar.csengg@...il.com>
To: Thomas Gleixner <tglx@...nel.org>,
Philipp Zabel <p.zabel@...gutronix.de>,
Geert Uytterhoeven <geert+renesas@...der.be>,
Magnus Damm <magnus.damm@...il.com>
Cc: linux-kernel@...r.kernel.org,
linux-renesas-soc@...r.kernel.org,
Prabhakar <prabhakar.csengg@...il.com>,
Biju Das <biju.das.jz@...renesas.com>,
Fabrizio Castro <fabrizio.castro.jz@...esas.com>,
Lad Prabhakar <prabhakar.mahadev-lad.rj@...renesas.com>
Subject: [PATCH v2 6/6] irqchip/renesas-rzv2h: Handle ICU error IRQ and add SWPE trigger
From: Lad Prabhakar <prabhakar.mahadev-lad.rj@...renesas.com>
Handle the RZ/V2H ICU error interrupt to help diagnose latched bus,
ECC RAM, and CA55/IP error conditions.
Extend the hardware IRQ numbering to include a single error interrupt
line and route IRQCHIP_STATE_PENDING requests to hardware-triggered
error injection via ICU_SWPE.
Account for SoC differences in ECC RAM error register coverage so the
handler only iterates over valid ECC status/clear banks, and route the
RZ/V2N compatible to a probe path with the correct ECC range while
keeping the existing RZ/V2H and RZ/G3E handling.
Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@...renesas.com>
---
v1->v2:
- Made Error interrupt as part of ICU IRQ domain.
- Updated rzv2h_icu_irq_set_irqchip_state() to trigger pseudo interrupt.
- Updated commit message accordingly.
---
drivers/irqchip/irq-renesas-rzv2h.c | 149 ++++++++++++++++++++++++++--
1 file changed, 143 insertions(+), 6 deletions(-)
diff --git a/drivers/irqchip/irq-renesas-rzv2h.c b/drivers/irqchip/irq-renesas-rzv2h.c
index a2ff7524889c..3937a857af8b 100644
--- a/drivers/irqchip/irq-renesas-rzv2h.c
+++ b/drivers/irqchip/irq-renesas-rzv2h.c
@@ -33,7 +33,10 @@
#define ICU_CA55_INT_START (ICU_TINT_LAST + 1)
#define ICU_CA55_INT_COUNT 4
#define ICU_CA55_INT_LAST (ICU_CA55_INT_START + ICU_CA55_INT_COUNT - 1)
-#define ICU_NUM_IRQ (ICU_CA55_INT_LAST + 1)
+#define ICU_ERR_INT_START (ICU_CA55_INT_LAST + 1)
+#define ICU_ERR_INT_COUNT 1
+#define ICU_ERR_INT_LAST (ICU_ERR_INT_START + ICU_ERR_INT_COUNT - 1)
+#define ICU_NUM_IRQ (ICU_ERR_INT_LAST + 1)
/* Registers */
#define ICU_NSCNT 0x00
@@ -46,7 +49,15 @@
#define ICU_TSCLR 0x24
#define ICU_TITSR(k) (0x28 + (k) * 4)
#define ICU_TSSR(k) (0x30 + (k) * 4)
+#define ICU_BEISR(k) (0x70 + (k) * 4)
+#define ICU_BECLR(k) (0x80 + (k) * 4)
+#define ICU_EREISR(k) (0x90 + (k) * 4)
+#define ICU_ERCLR(k) (0xE0 + (k) * 4)
#define ICU_SWINT 0x130
+#define ICU_ERINTA55CTL(k) (0x338 + (k) * 4)
+#define ICU_ERINTA55CRL(k) (0x348 + (k) * 4)
+#define ICU_ERINTA55MSK(k) (0x358 + (k) * 4)
+#define ICU_SWPE 0x370
#define ICU_DMkSELy(k, y) (0x420 + (k) * 0x20 + (y) * 4)
#define ICU_DMACKSELk(k) (0x500 + (k) * 4)
@@ -97,6 +108,10 @@
#define ICU_RZG3E_TSSEL_MAX_VAL 0x8c
#define ICU_RZV2H_TSSEL_MAX_VAL 0x55
+#define ICU_SWPE_NUM 16
+#define ICU_NUM_BE 4
+#define ICU_NUM_A55ERR 4
+
/**
* struct rzv2h_irqc_reg_cache - registers cache (necessary for suspend/resume)
* @nitsr: ICU_NITSR register
@@ -115,12 +130,16 @@ struct rzv2h_irqc_reg_cache {
* @t_offs: TINT offset
* @max_tssel: TSSEL max value
* @field_width: TSSR field width
+ * @ecc_start: Start index of ECC RAM interrupts
+ * @ecc_end: End index of ECC RAM interrupts
*/
struct rzv2h_hw_info {
const u8 *tssel_lut;
u16 t_offs;
u8 max_tssel;
u8 field_width;
+ u8 ecc_start;
+ u8 ecc_end;
};
/* DMAC */
@@ -259,10 +278,10 @@ static int rzv2h_icu_irq_set_irqchip_state(struct irq_data *d,
{
unsigned int hwirq = irqd_to_hwirq(d);
struct rzv2h_icu_priv *priv;
+ void __iomem *offset;
unsigned int bit;
- if (hwirq < ICU_CA55_INT_START || hwirq > ICU_CA55_INT_LAST ||
- which != IRQCHIP_STATE_PENDING)
+ if (which != IRQCHIP_STATE_PENDING)
return irq_chip_set_parent_state(d, which, state);
if (!state)
@@ -271,9 +290,33 @@ static int rzv2h_icu_irq_set_irqchip_state(struct irq_data *d,
priv = irq_data_to_priv(d);
bit = BIT(hwirq - ICU_CA55_INT_START);
+ switch (hwirq) {
+ case ICU_CA55_INT_START ... ICU_CA55_INT_LAST:
+ bit = BIT(hwirq - ICU_CA55_INT_START);
+ offset = priv->base + ICU_SWINT;
+ break;
+ case ICU_ERR_INT_START ... ICU_ERR_INT_LAST: {
+ static u8 swpe;
+
+ bit = BIT(swpe);
+ /*
+ * SWPE has 16 bits; the bit position is rotated on each trigger
+ * and wraps around once all bits have been used.
+ */
+ if (++swpe >= ICU_SWPE_NUM)
+ swpe = 0;
+
+ offset = priv->base + ICU_SWPE;
+ break;
+ }
+ default:
+ return irq_chip_set_parent_state(d, which, state);
+ }
+
guard(raw_spinlock)(&priv->lock);
- /* Trigger the software interrupt */
- writel_relaxed(bit, priv->base + ICU_SWINT);
+ /* Trigger the error/software interrupt */
+ writel_relaxed(bit, offset);
+
return 0;
}
@@ -480,6 +523,10 @@ static int rzv2h_icu_set_type(struct irq_data *d, unsigned int type)
gic_type = IRQ_TYPE_EDGE_RISING;
ret = 0;
break;
+ case ICU_ERR_INT_START ... ICU_ERR_INT_LAST:
+ /* Error Interrupts */
+ ret = 0;
+ break;
default:
ret = -EINVAL;
}
@@ -606,6 +653,48 @@ static int rzv2h_icu_parse_interrupts(struct rzv2h_icu_priv *priv, struct device
return 0;
}
+static irqreturn_t rzv2h_icu_error_irq(int irq, void *data)
+{
+ struct rzv2h_icu_priv *priv = data;
+ const struct rzv2h_hw_info *hw_info = priv->info;
+ void __iomem *base = priv->base;
+ unsigned int k;
+ u32 st;
+
+ /* 1) Bus errors (BEISR0..3) */
+ for (k = 0; k < ICU_NUM_BE; k++) {
+ st = readl(base + ICU_BEISR(k));
+ if (!st)
+ continue;
+
+ writel_relaxed(st, base + ICU_BECLR(k));
+ pr_debug("rzv2h-icu: BUS error k=%u status=0x%08x\n", k, st);
+ }
+
+ /* 2) ECC RAM errors (EREISR0..X) */
+ for (k = hw_info->ecc_start; k <= hw_info->ecc_end; k++) {
+ st = readl(base + ICU_EREISR(k));
+ if (!st)
+ continue;
+
+ writel_relaxed(st, base + ICU_ERCLR(k));
+ pr_debug("rzv2h-icu: ECC error k=%u status=0x%08x\n", k, st);
+ }
+
+ /* 3) IP/CA55 error interrupt status (ERINTA55CTL0..3) */
+ for (k = 0; k < ICU_NUM_A55ERR; k++) {
+ st = readl(base + ICU_ERINTA55CTL(k));
+ if (!st)
+ continue;
+
+ /* there is no relation with status bits so clear all the interrupts */
+ writel_relaxed(0xffffffff, base + ICU_ERINTA55CRL(k));
+ pr_debug("rzv2h-icu: IP/CA55 error k=%u status=0x%08x\n", k, st);
+ }
+
+ return IRQ_HANDLED;
+}
+
static irqreturn_t rzv2h_icu_swint_irq(int irq, void *data)
{
u8 cpu = *(u8 *)data;
@@ -617,12 +706,15 @@ static irqreturn_t rzv2h_icu_swint_irq(int irq, void *data)
static int rzv2h_icu_setup_irqs(struct platform_device *pdev,
struct irq_domain *irq_domain)
{
+ const struct rzv2h_hw_info *hw_info = rzv2h_icu_data->info;
bool irq_inject = IS_ENABLED(CONFIG_GENERIC_IRQ_INJECTION);
static const char * const rzv2h_swint_names[] = {
"int-ca55-0", "int-ca55-1",
"int-ca55-2", "int-ca55-3",
};
+ static const char *icu_err = "icu-error-ca55";
static const u8 swint_idx[] = { 0, 1, 2, 3 };
+ void __iomem *base = rzv2h_icu_data->base;
struct device *dev = &pdev->dev;
struct irq_fwspec fwspec;
unsigned int virq;
@@ -647,6 +739,34 @@ static int rzv2h_icu_setup_irqs(struct platform_device *pdev,
rzv2h_swint_names[i]);
}
+ /* Unmask and clear all IP/CA55 error interrupts */
+ for (i = 0; i < ICU_NUM_A55ERR; i++) {
+ writel_relaxed(0xffffff, base + ICU_ERINTA55CRL(i));
+ writel_relaxed(0x0, base + ICU_ERINTA55MSK(i));
+ }
+
+ /* Clear all Bus errors */
+ for (i = 0; i < ICU_NUM_BE; i++)
+ writel_relaxed(0xffffffff, base + ICU_BECLR(i));
+
+ /* Clear all ECCRAM errors */
+ for (i = hw_info->ecc_start; i <= hw_info->ecc_end; i++)
+ writel_relaxed(0xffffffff, base + ICU_ERCLR(i));
+
+ fwspec.fwnode = irq_domain->fwnode;
+ fwspec.param_count = 2;
+ fwspec.param[0] = ICU_ERR_INT_START;
+ fwspec.param[1] = IRQ_TYPE_LEVEL_HIGH;
+
+ virq = irq_create_fwspec_mapping(&fwspec);
+ if (!virq)
+ return dev_err_probe(dev, -EINVAL, "failed to create IRQ mapping for %s\n",
+ icu_err);
+
+ 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 %s IRQ\n", icu_err);
+
return 0;
}
@@ -752,12 +872,24 @@ static const struct rzv2h_hw_info rzg3e_hw_params = {
.t_offs = ICU_RZG3E_TINT_OFFSET,
.max_tssel = ICU_RZG3E_TSSEL_MAX_VAL,
.field_width = 16,
+ .ecc_start = 1,
+ .ecc_end = 4,
+};
+
+static const struct rzv2h_hw_info rzv2n_hw_params = {
+ .t_offs = 0,
+ .max_tssel = ICU_RZV2H_TSSEL_MAX_VAL,
+ .field_width = 8,
+ .ecc_start = 0,
+ .ecc_end = 2,
};
static const struct rzv2h_hw_info rzv2h_hw_params = {
.t_offs = 0,
.max_tssel = ICU_RZV2H_TSSEL_MAX_VAL,
.field_width = 8,
+ .ecc_start = 0,
+ .ecc_end = 11,
};
static int rzg3e_icu_probe(struct platform_device *pdev, struct device_node *parent)
@@ -765,6 +897,11 @@ static int rzg3e_icu_probe(struct platform_device *pdev, struct device_node *par
return rzv2h_icu_probe_common(pdev, parent, &rzg3e_hw_params);
}
+static int rzv2n_icu_probe(struct platform_device *pdev, struct device_node *parent)
+{
+ return rzv2h_icu_probe_common(pdev, parent, &rzv2n_hw_params);
+}
+
static int rzv2h_icu_probe(struct platform_device *pdev, struct device_node *parent)
{
return rzv2h_icu_probe_common(pdev, parent, &rzv2h_hw_params);
@@ -772,7 +909,7 @@ static int rzv2h_icu_probe(struct platform_device *pdev, struct device_node *par
IRQCHIP_PLATFORM_DRIVER_BEGIN(rzv2h_icu)
IRQCHIP_MATCH("renesas,r9a09g047-icu", rzg3e_icu_probe)
-IRQCHIP_MATCH("renesas,r9a09g056-icu", rzv2h_icu_probe)
+IRQCHIP_MATCH("renesas,r9a09g056-icu", rzv2n_icu_probe)
IRQCHIP_MATCH("renesas,r9a09g057-icu", rzv2h_icu_probe)
IRQCHIP_PLATFORM_DRIVER_END(rzv2h_icu)
MODULE_AUTHOR("Fabrizio Castro <fabrizio.castro.jz@...esas.com>");
--
2.52.0
Powered by blists - more mailing lists