[PATCH 4/6] irqchip/al-fic: add support for FIC v2

From: Eliav Farber

Date: Thu Sep 24 2026 - 02:25:24 EST


FIC v2 hardware adds two additional interrupt groups on top of the
legacy group: an "error" group and a "fatal" group, each with its own
mask register (AL_FIC_ERROR_MASK, AL_FIC_FATAL_MASK). A single FIC
instance exposes one of these groups, selected by the compatible string.

Detect the hardware version from the CONTROL register version id field
(bits 28-29) and, on v2, mask out the error and fatal groups during
initialization.

The driver now distinguishes the three groups via enum al_fic_id, wired
up through three IRQCHIP_DECLARE entries:

amazon,al-fic - legacy group (existing, unchanged behaviour)
amazon,al-fic-error - error group (v2+)
amazon,al-fic-fatal - fatal group (v2+)

Requesting an error/fatal group on a v1 device is rejected.

On v2 the error and fatal mask registers always read back as 0,
regardless of their actual contents, so IRQ_GC_INIT_MASK_CACHE seeds
mask_cache with 0 and makes every source appear unmasked on the next
access. Since al_fic_wire_init() has already masked all sources, override
mask_cache to match for these two groups and let the driver maintain it.
The legacy mask register is not affected, so the legacy group keeps the
register-seeded mask_cache.

Signed-off-by: Eliav Farber <farbere@xxxxxxxxxx>
---
drivers/irqchip/irq-al-fic.c | 101 ++++++++++++++++++++++++++++++-----
1 file changed, 89 insertions(+), 12 deletions(-)

diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c
index 9cf3eedac97f..90a64350be63 100644
--- a/drivers/irqchip/irq-al-fic.c
+++ b/drivers/irqchip/irq-al-fic.c
@@ -16,11 +16,14 @@
/* FIC Registers */
#define AL_FIC_CAUSE 0x00
#define AL_FIC_SET_CAUSE 0x08
-#define AL_FIC_MASK 0x10
+#define AL_FIC_LEGACY_MASK 0x10
#define AL_FIC_CONTROL 0x28
+#define AL_FIC_ERROR_MASK 0x2c
+#define AL_FIC_FATAL_MASK 0x34

#define CONTROL_TRIGGER_RISING BIT(3)
#define CONTROL_MASK_MSI_X BIT(5)
+#define CONTROL_VERSION_ID GENMASK(29, 28)

#define NR_FIC_IRQS 32

@@ -33,12 +36,32 @@ enum al_fic_state {
AL_FIC_CONFIGURED_RISING_EDGE,
};

+enum al_fic_version {
+ AL_FIC_VERSION_V1,
+ AL_FIC_VERSION_V2,
+};
+
+enum al_fic_id {
+ AL_FIC_ID_LEGACY,
+ AL_FIC_ID_ERROR,
+ AL_FIC_ID_FATAL,
+ AL_FIC_ID_MAX, /* keep last */
+};
+
+/* Mask register offset for each interrupt group */
+static const unsigned int al_fic_mask_offset[AL_FIC_ID_MAX] = {
+ [AL_FIC_ID_LEGACY] = AL_FIC_LEGACY_MASK,
+ [AL_FIC_ID_ERROR] = AL_FIC_ERROR_MASK,
+ [AL_FIC_ID_FATAL] = AL_FIC_FATAL_MASK,
+};
+
struct al_fic {
void __iomem *base;
struct irq_domain *domain;
const char *name;
unsigned int parent_irq;
enum al_fic_state state;
+ enum al_fic_version version;
};

static void al_fic_set_trigger(struct al_fic *fic,
@@ -126,7 +149,8 @@ static int al_fic_irq_retrigger(struct irq_data *data)
}

static int al_fic_register(struct device_node *node,
- struct al_fic *fic)
+ struct al_fic *fic,
+ enum al_fic_id fic_id)
{
struct irq_chip_generic *gc;
int ret;
@@ -152,7 +176,7 @@ static int al_fic_register(struct device_node *node,

gc = irq_get_domain_generic_chip(fic->domain, 0);
gc->reg_base = fic->base;
- gc->chip_types->regs.mask = AL_FIC_MASK;
+ gc->chip_types->regs.mask = al_fic_mask_offset[fic_id];
gc->chip_types->regs.ack = AL_FIC_CAUSE;
gc->chip_types->chip.irq_mask = irq_gc_mask_set_bit;
gc->chip_types->chip.irq_unmask = irq_gc_mask_clr_bit;
@@ -162,6 +186,20 @@ static int al_fic_register(struct device_node *node,
gc->chip_types->chip.flags = IRQCHIP_SKIP_SET_WAKE;
gc->private = fic;

+ /*
+ * On FIC v2 the error and fatal mask registers always read back as 0,
+ * regardless of their actual contents, so IRQ_GC_INIT_MASK_CACHE seeds
+ * mask_cache with 0 and makes every source appear unmasked on the next
+ * access. al_fic_wire_init() has already masked all sources, so override
+ * mask_cache to match and let the driver maintain it from here.
+ *
+ * The legacy mask register is not affected, and the erratum is fixed
+ * from v3 onwards, so those paths keep the register-seeded mask_cache.
+ */
+ if (fic->version == AL_FIC_VERSION_V2 &&
+ (fic_id == AL_FIC_ID_ERROR || fic_id == AL_FIC_ID_FATAL))
+ gc->mask_cache = 0xFFFFFFFF;
+
ret = request_irq(fic->parent_irq, al_fic_irq_handler,
IRQF_NO_THREAD | IRQF_SHARED, fic->name, fic);
if (ret) {
@@ -194,11 +232,13 @@ static int al_fic_register(struct device_node *node,
static struct al_fic *al_fic_wire_init(struct device_node *node,
void __iomem *base,
const char *name,
- unsigned int parent_irq)
+ unsigned int parent_irq,
+ enum al_fic_id fic_id)
{
struct al_fic *fic;
+ u32 version_id;
+ u32 control;
int ret;
- u32 control = CONTROL_MASK_MSI_X;

fic = kzalloc_obj(*fic);
if (!fic)
@@ -208,22 +248,37 @@ static struct al_fic *al_fic_wire_init(struct device_node *node,
fic->parent_irq = parent_irq;
fic->name = name;

+ control = readl_relaxed(fic->base + AL_FIC_CONTROL);
+ version_id = FIELD_GET(CONTROL_VERSION_ID, control);
+ fic->version = version_id;
+ if (version_id == AL_FIC_VERSION_V1 && fic_id != AL_FIC_ID_LEGACY) {
+ pr_err("%s unexpected fic id (%d)\n", fic->name, fic_id);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
/* mask out all interrupts */
- writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_MASK);
+ writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_LEGACY_MASK);
+ if (version_id > AL_FIC_VERSION_V1) {
+ writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_ERROR_MASK);
+ writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_FATAL_MASK);
+ }

/* clear any pending interrupt */
writel_relaxed(0, fic->base + AL_FIC_CAUSE);

+ /* make sure the controller works in non msi_x mode */
+ control |= CONTROL_MASK_MSI_X;
writel_relaxed(control, fic->base + AL_FIC_CONTROL);

- ret = al_fic_register(node, fic);
+ ret = al_fic_register(node, fic, fic_id);
if (ret) {
pr_err("fail to register irqchip\n");
goto err_free;
}

- pr_info("%s initialized successfully in Legacy mode (parent-irq=%u)\n",
- fic->name, parent_irq);
+ pr_info("%s initialized successfully (fic_id=%d parent-irq=%u)\n",
+ fic->name, fic_id, parent_irq);

return fic;

@@ -233,7 +288,8 @@ static struct al_fic *al_fic_wire_init(struct device_node *node,
}

static int __init al_fic_init_dt(struct device_node *node,
- struct device_node *parent)
+ struct device_node *parent,
+ enum al_fic_id fic_id)
{
int ret;
void __iomem *base;
@@ -262,7 +318,8 @@ static int __init al_fic_init_dt(struct device_node *node,
fic = al_fic_wire_init(node,
base,
node->full_name,
- parent_irq);
+ parent_irq,
+ fic_id);
if (IS_ERR(fic)) {
pr_err("%s: fail to initialize irqchip (%lu)\n",
node->full_name,
@@ -281,4 +338,24 @@ static int __init al_fic_init_dt(struct device_node *node,
return ret;
}

-IRQCHIP_DECLARE(al_fic, "amazon,al-fic", al_fic_init_dt);
+static int __init al_fic_init_dt_legacy(struct device_node *node,
+ struct device_node *parent)
+{
+ return al_fic_init_dt(node, parent, AL_FIC_ID_LEGACY);
+}
+
+static int __init al_fic_init_dt_error(struct device_node *node,
+ struct device_node *parent)
+{
+ return al_fic_init_dt(node, parent, AL_FIC_ID_ERROR);
+}
+
+static int __init al_fic_init_dt_fatal(struct device_node *node,
+ struct device_node *parent)
+{
+ return al_fic_init_dt(node, parent, AL_FIC_ID_FATAL);
+}
+
+IRQCHIP_DECLARE(al_fic_legacy, "amazon,al-fic", al_fic_init_dt_legacy);
+IRQCHIP_DECLARE(al_fic_error, "amazon,al-fic-error", al_fic_init_dt_error);
+IRQCHIP_DECLARE(al_fic_fatal, "amazon,al-fic-fatal", al_fic_init_dt_fatal);
--
2.47.3