* [PATCH 1/6] irqchip/al-fic: use full node name and raise init log level
2026-09-24 6:23 [PATCH 0/6] irqchip/al-fic: shared parent IRQ, FIC v2/v3 and affinity Eliav Farber
@ 2026-09-24 6:23 ` Eliav Farber
2026-09-24 6:23 ` [PATCH 2/6] irqchip/al-fic: switch to shared parent interrupt Eliav Farber
` (3 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Eliav Farber @ 2026-09-24 6:23 UTC (permalink / raw)
To: Thomas Gleixner, Talel Shenhar
Cc: Radu Rendec, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Eliav Farber, devicetree, linux-kernel
Use node->full_name instead of node->name in the driver's log messages
so that they identify the specific FIC instance, e.g.:
interrupt-controller@fd803000 initialized successfully in Legacy mode (parent-irq=3)
Raise the successful-initialization message from pr_debug to pr_info, as
this single boot-time printout is useful when dynamic debug is not
enabled at this stage of boot. While here, fix a small typo in the
al_fic_wire_init() comment.
Signed-off-by: Eliav Farber <farbere@amazon.com>
---
drivers/irqchip/irq-al-fic.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c
index d10ac9b63c99..efa7f2b62d00 100644
--- a/drivers/irqchip/irq-al-fic.c
+++ b/drivers/irqchip/irq-al-fic.c
@@ -180,7 +180,7 @@ static int al_fic_register(struct device_node *node,
* @name: name of the fic
* @parent_irq: interrupt of parent
*
- * This API will configure the fic hardware to to work in wire mode.
+ * This API will configure the fic hardware to work in wire mode.
* In wire mode, fic hardware is generating a wire ("wired") interrupt.
* Interrupt can be generated based on positive edge or level - configuration is
* to be determined based on connected hardware to this fic.
@@ -216,8 +216,8 @@ static struct al_fic *al_fic_wire_init(struct device_node *node,
goto err_free;
}
- pr_debug("%s initialized successfully in Legacy mode (parent-irq=%u)\n",
- fic->name, parent_irq);
+ pr_info("%s initialized successfully in Legacy mode (parent-irq=%u)\n",
+ fic->name, parent_irq);
return fic;
@@ -236,30 +236,30 @@ static int __init al_fic_init_dt(struct device_node *node,
if (!parent) {
pr_err("%s: unsupported - device require a parent\n",
- node->name);
+ node->full_name);
return -EINVAL;
}
base = of_iomap(node, 0);
if (!base) {
- pr_err("%s: fail to map memory\n", node->name);
+ pr_err("%s: fail to map memory\n", node->full_name);
return -ENOMEM;
}
parent_irq = irq_of_parse_and_map(node, 0);
if (!parent_irq) {
- pr_err("%s: fail to map irq\n", node->name);
+ pr_err("%s: fail to map irq\n", node->full_name);
ret = -EINVAL;
goto err_unmap;
}
fic = al_fic_wire_init(node,
base,
- node->name,
+ node->full_name,
parent_irq);
if (IS_ERR(fic)) {
pr_err("%s: fail to initialize irqchip (%lu)\n",
- node->name,
+ node->full_name,
PTR_ERR(fic));
ret = PTR_ERR(fic);
goto err_irq_dispose;
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 2/6] irqchip/al-fic: switch to shared parent interrupt
2026-09-24 6:23 [PATCH 0/6] irqchip/al-fic: shared parent IRQ, FIC v2/v3 and affinity Eliav Farber
2026-09-24 6:23 ` [PATCH 1/6] irqchip/al-fic: use full node name and raise init log level Eliav Farber
@ 2026-09-24 6:23 ` Eliav Farber
2026-09-24 6:23 ` [PATCH 3/6] dt-bindings: interrupt-controller: amazon,al-fic: add error/fatal groups Eliav Farber
` (2 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Eliav Farber @ 2026-09-24 6:23 UTC (permalink / raw)
To: Thomas Gleixner, Talel Shenhar
Cc: Radu Rendec, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Eliav Farber, devicetree, linux-kernel
Until now the driver requested its parent interrupt using the chained
IRQ API (irq_set_chained_handler_and_data()), which only works when each
parent interrupt is wired to a single FIC instance.
On some platforms several FIC instances share the same parent GIC SPI.
To support that, request the parent interrupt as a shared interrupt
(IRQF_SHARED) instead of installing a chained handler. The handler now
has the standard irqreturn_t prototype and reports whether it handled any
child interrupt, so the shared-IRQ core can dispatch to the correct
instance.
generic_handle_domain_irq() is retained for dispatch; its return value is
used to determine whether a pending child was actually handled so the
handler can return IRQ_HANDLED/IRQ_NONE correctly.
Signed-off-by: Eliav Farber <farbere@amazon.com>
---
drivers/irqchip/irq-al-fic.c | 30 ++++++++++++++++++------------
1 file changed, 18 insertions(+), 12 deletions(-)
diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c
index efa7f2b62d00..9cf3eedac97f 100644
--- a/drivers/irqchip/irq-al-fic.c
+++ b/drivers/irqchip/irq-al-fic.c
@@ -4,9 +4,9 @@
*/
#include <linux/bitfield.h>
+#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
-#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -95,24 +95,24 @@ static int al_fic_irq_set_type(struct irq_data *data, unsigned int flow_type)
return 0;
}
-static void al_fic_irq_handler(struct irq_desc *desc)
+static irqreturn_t al_fic_irq_handler(int irq, void *data)
{
- struct al_fic *fic = irq_desc_get_handler_data(desc);
+ struct al_fic *fic = data;
struct irq_domain *domain = fic->domain;
- struct irq_chip *irqchip = irq_desc_get_chip(desc);
struct irq_chip_generic *gc = irq_get_domain_generic_chip(domain, 0);
+ irqreturn_t ret = IRQ_NONE;
unsigned long pending;
u32 hwirq;
- chained_irq_enter(irqchip, desc);
-
pending = readl_relaxed(fic->base + AL_FIC_CAUSE);
pending &= ~gc->mask_cache;
- for_each_set_bit(hwirq, &pending, NR_FIC_IRQS)
- generic_handle_domain_irq(domain, hwirq);
+ for_each_set_bit(hwirq, &pending, NR_FIC_IRQS) {
+ if (!generic_handle_domain_irq(domain, hwirq))
+ ret = IRQ_HANDLED;
+ }
- chained_irq_exit(irqchip, desc);
+ return ret;
}
static int al_fic_irq_retrigger(struct irq_data *data)
@@ -162,11 +162,17 @@ static int al_fic_register(struct device_node *node,
gc->chip_types->chip.flags = IRQCHIP_SKIP_SET_WAKE;
gc->private = fic;
- irq_set_chained_handler_and_data(fic->parent_irq,
- al_fic_irq_handler,
- fic);
+ ret = request_irq(fic->parent_irq, al_fic_irq_handler,
+ IRQF_NO_THREAD | IRQF_SHARED, fic->name, fic);
+ if (ret) {
+ pr_err("fail to request irq (%d)\n", ret);
+ goto err_free_generic_chip;
+ }
+
return 0;
+err_free_generic_chip:
+ irq_free_generic_chip(gc);
err_domain_remove:
irq_domain_remove(fic->domain);
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 4/6] irqchip/al-fic: add support for FIC v2
2026-09-24 6:23 [PATCH 0/6] irqchip/al-fic: shared parent IRQ, FIC v2/v3 and affinity Eliav Farber
` (2 preceding siblings ...)
2026-09-24 6:23 ` [PATCH 3/6] dt-bindings: interrupt-controller: amazon,al-fic: add error/fatal groups Eliav Farber
@ 2026-09-24 6:23 ` Eliav Farber
2026-09-24 6:26 ` [PATCH 5/6] irqchip/al-fic: add support for FIC v3 Eliav Farber
4 siblings, 0 replies; 8+ messages in thread
From: Eliav Farber @ 2026-09-24 6:23 UTC (permalink / raw)
To: Thomas Gleixner, Talel Shenhar
Cc: Radu Rendec, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Eliav Farber, devicetree, linux-kernel
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@amazon.com>
---
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
^ permalink raw reply [flat|nested] 8+ messages in thread