mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-16 06:41:39 -04:00
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:
committed by
Thomas Gleixner
parent
61adc4813d
commit
7585a27644
@@ -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>");
|
||||
|
||||
Reference in New Issue
Block a user