irqchip/renesas-rzv2h: Handle ICU error IRQ and add SWPE trigger

Handle the RZ/V2H ICU error interrupt to help diagnose latched bus,
ECC RAM, and CA55/IP error conditions.

Support error injection via ICU_SWPE to allow testing the pseudo error
error interrupts.

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.

[ tglx: Convert to hwirq_within() and upgrade to pr_warn() for those errors ]

Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Signed-off-by: Thomas Gleixner <tglx@kernel.org>
Link: https://patch.msgid.link/20260304113317.129339-8-prabhakar.mahadev-lad.rj@bp.renesas.com
This commit is contained in:
Lad Prabhakar
2026-03-04 11:33:17 +00:00
committed by Thomas Gleixner
parent 61adc4813d
commit 7585a27644

View File

@@ -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 */
@@ -454,6 +473,36 @@ static int rzv2h_icu_swint_set_irqchip_state(struct irq_data *d, enum irqchip_ir
/* Trigger the software interrupt */
writel_relaxed(bit, priv->base + ICU_SWINT);
return 0;
}
static int rzv2h_icu_swpe_set_irqchip_state(struct irq_data *d, enum irqchip_irq_state which,
bool state)
{
struct rzv2h_icu_priv *priv;
unsigned int bit;
static u8 swpe;
if (which != IRQCHIP_STATE_PENDING)
return irq_chip_set_parent_state(d, which, state);
if (!state)
return 0;
priv = irq_data_to_priv(d);
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;
/* Trigger the pseudo error interrupt */
writel_relaxed(bit, priv->base + ICU_SWPE);
return 0;
}
@@ -563,6 +612,23 @@ static const struct irq_chip rzv2h_icu_swint_chip = {
IRQCHIP_SKIP_SET_WAKE,
};
static const struct irq_chip rzv2h_icu_swpe_err_chip = {
.name = "rzv2h-icu",
.irq_eoi = irq_chip_eoi_parent,
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_disable = irq_chip_disable_parent,
.irq_enable = irq_chip_enable_parent,
.irq_get_irqchip_state = irq_chip_get_parent_state,
.irq_set_irqchip_state = rzv2h_icu_swpe_set_irqchip_state,
.irq_retrigger = irq_chip_retrigger_hierarchy,
.irq_set_type = irq_chip_set_type_parent,
.irq_set_affinity = irq_chip_set_affinity_parent,
.flags = IRQCHIP_MASK_ON_SUSPEND |
IRQCHIP_SET_TYPE_MASKED |
IRQCHIP_SKIP_SET_WAKE,
};
#define hwirq_within(hwirq, which) ((hwirq) >= which##_START && (hwirq) <= which##_LAST)
static int rzv2h_icu_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs,
@@ -596,6 +662,8 @@ static int rzv2h_icu_alloc(struct irq_domain *domain, unsigned int virq, unsigne
chip = &rzv2h_icu_irq_chip;
} else if (hwirq_within(hwirq, ICU_CA55_INT)) {
chip = &rzv2h_icu_swint_chip;
} else if (hwirq_within(hwirq, ICU_ERR_INT)) {
chip = &rzv2h_icu_swpe_err_chip;
} else {
chip = &rzv2h_icu_nmi_chip;
}
@@ -633,6 +701,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_warn("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_warn("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_warn("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;
@@ -643,12 +753,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 i, virq;
@@ -674,6 +787,35 @@ static int rzv2h_icu_setup_irqs(struct platform_device *pdev, struct irq_domain
}
}
/* 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;
}
@@ -778,12 +920,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)
@@ -791,6 +945,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);
@@ -798,7 +957,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@renesas.com>");