From: Eliav Farber <farbere@amazon.com>
To: Thomas Gleixner <tglx@kernel.org>, Talel Shenhar <talel@amazon.com>
Cc: Radu Rendec <radu@rendec.net>, Rob Herring <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
"Eliav Farber" <farbere@amazon.com>, <devicetree@vger.kernel.org>,
<linux-kernel@vger.kernel.org>
Subject: [PATCH 4/6] irqchip/al-fic: add support for FIC v2
Date: Thu, 24 Sep 2026 06:23:09 +0000 [thread overview]
Message-ID: <20260924062311.37308-5-farbere@amazon.com> (raw)
In-Reply-To: <20260924062311.37308-1-farbere@amazon.com>
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
prev parent reply other threads:[~2026-09-24 6:23 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` [PATCH 2/6] irqchip/al-fic: switch to shared parent interrupt Eliav Farber
2026-09-24 6:23 ` [PATCH 3/6] dt-bindings: interrupt-controller: amazon,al-fic: add error/fatal groups Eliav Farber
2026-09-24 17:13 ` Conor Dooley
2026-09-25 10:24 ` [PATCH 3/6] dt-bindings: interrupt-controller: amazon, al-fic: " Farber, Eliav
2026-09-25 17:13 ` Conor Dooley
2026-09-27 8:03 ` Farber, Eliav
2026-09-24 6:23 ` Eliav Farber [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260924062311.37308-5-farbere@amazon.com \
--to=farbere@amazon.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=radu@rendec.net \
--cc=robh@kernel.org \
--cc=talel@amazon.com \
--cc=tglx@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.