summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEliav Farber <farbere@amazon.com>2026-10-11 05:04:46 +0000
committerThomas Gleixner <tglx@kernel.org>2026-10-15 14:25:07 +0200
commit3d41f28682aa8375a025bc999e403852a78c092c (patch)
tree2e78272385be7771287c996fd2ac9b352f3f7d65
parent485ab2a36d36edb2bb630d2491d2ab7ed11178fc (diff)
irqchip/al-fic: Support error and fatal outputs and FIC v2
FIC v2 hardware adds two interrupt outputs on top of the info output: an error output and a fatal output, each with its own mask register (AL_FIC_ERROR_MASK, AL_FIC_FATAL_MASK). A group drives exactly one of the three outputs. Read which output a group drives from the amazon,al-fic-mask devicetree property (info, error or fatal; absent means info) and program the matching mask register. Detect the hardware revision from the CONTROL register version field (bits 28-29). The revision is not in the devicetree, so requesting the error or fatal output on a v1 device - which has neither - is rejected at probe against the register. Name the selected output in the probe log line, in place of the "Legacy mode" text it replaces. A booted system then shows which output each group drives. The "v1" and "v2" names are this driver's labels for the CONTROL version field encoding (0 and 1). The driver seeds mask_cache from the value it programs rather than having the generic chip read the mask register back, so the error and fatal mask registers are never read - which also sidesteps the v2 erratum where they always read as 0 regardless of their contents. Reading CONTROL to get the version field turns the write that follows into a read-modify-write instead of a value built from CONTROL_MASK_MSI_X alone. Every RW bit in this register resets to 0, so the two are equivalent at probe time; the read-modify-write is kept anyway as the better practice; it costs nothing and does not depend on the reset value staying 0. Signed-off-by: Eliav Farber <farbere@amazon.com> Signed-off-by: Thomas Gleixner <tglx@kernel.org> Reviewed-by: Radu Rendec <radu@rendec.net> Link: https://patch.msgid.link/20261011050449.41903-8-farbere@amazon.com
-rw-r--r--drivers/irqchip/irq-al-fic.c104
1 files changed, 94 insertions, 10 deletions
diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c
index e664c33250b3..33de265e52a9 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_INFO_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,6 +36,37 @@ enum al_fic_state {
AL_FIC_CONFIGURED_RISING_EDGE,
};
+/*
+ * FIC hardware revision, as reported by the CONTROL register version field
+ * (CONTROL_VERSION_ID, bits 29-28). These are this driver's names for that
+ * field's encoding.
+ */
+enum al_fic_version {
+ AL_FIC_VERSION_V1,
+ AL_FIC_VERSION_V2,
+};
+
+enum al_fic_id {
+ AL_FIC_ID_INFO,
+ AL_FIC_ID_ERROR,
+ AL_FIC_ID_FATAL,
+ AL_FIC_ID_MAX,
+};
+
+/* Mask register offset for each interrupt group */
+static const unsigned int al_fic_mask_offset[AL_FIC_ID_MAX] = {
+ [AL_FIC_ID_INFO] = AL_FIC_INFO_MASK,
+ [AL_FIC_ID_ERROR] = AL_FIC_ERROR_MASK,
+ [AL_FIC_ID_FATAL] = AL_FIC_FATAL_MASK,
+};
+
+/* amazon,al-fic-mask property value for each interrupt group */
+static const char * const al_fic_mask_name[AL_FIC_ID_MAX] = {
+ [AL_FIC_ID_INFO] = "info",
+ [AL_FIC_ID_ERROR] = "error",
+ [AL_FIC_ID_FATAL] = "fatal",
+};
+
struct al_fic {
void __iomem *base;
struct irq_domain *domain;
@@ -121,7 +155,7 @@ static int al_fic_irq_retrigger(struct irq_data *data)
return 1;
}
-static int al_fic_register(struct device_node *node, struct al_fic *fic)
+static int al_fic_register(struct device_node *node, struct al_fic *fic, enum al_fic_id fic_id)
{
struct irq_chip_generic *gc;
int ret;
@@ -149,7 +183,7 @@ static int al_fic_register(struct device_node *node, struct al_fic *fic)
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;
@@ -186,6 +220,8 @@ err_domain_remove:
* @node: pointer to the interrupt controller's device tree node
* @base: mmio to fic register
* @parent_irq: interrupt of parent
+ * @fic_id: which of the controller's outputs (info, error or fatal) this
+ * group drives
*
* This API will configure the fic hardware to work in wire mode.
* In wire mode, fic hardware is generating a wire ("wired") interrupt.
@@ -193,9 +229,9 @@ err_domain_remove:
* to be determined based on connected hardware to this fic.
*/
static struct al_fic *al_fic_wire_init(struct device_node *node, void __iomem *base,
- unsigned int parent_irq)
+ unsigned int parent_irq, enum al_fic_id fic_id)
{
- u32 control = CONTROL_MASK_MSI_X;
+ u32 control, version_id;
struct al_fic *fic;
int ret;
@@ -207,22 +243,37 @@ static struct al_fic *al_fic_wire_init(struct device_node *node, void __iomem *b
fic->parent_irq = parent_irq;
fic->node = of_node_get(node);
+ control = readl_relaxed(fic->base + AL_FIC_CONTROL);
+ version_id = FIELD_GET(CONTROL_VERSION_ID, control);
+ if (version_id == AL_FIC_VERSION_V1 && fic_id != AL_FIC_ID_INFO) {
+ pr_err("%pOF: amazon,al-fic-mask = \"%s\" not available on FIC v1\n",
+ node, al_fic_mask_name[fic_id]);
+ ret = -EINVAL;
+ goto err_free;
+ }
+
/* mask out all interrupts */
- writel_relaxed(0xFFFFFFFF, fic->base + AL_FIC_MASK);
+ writel_relaxed(~0U, fic->base + AL_FIC_INFO_MASK);
+ if (version_id > AL_FIC_VERSION_V1) {
+ writel_relaxed(~0U, fic->base + AL_FIC_ERROR_MASK);
+ writel_relaxed(~0U, 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("%pOF initialized successfully in Legacy mode (parent-irq=%u)\n",
- node, parent_irq);
+ pr_info("%pOF initialized successfully (mask=%s parent-irq=%u)\n",
+ node, al_fic_mask_name[fic_id], parent_irq);
return fic;
@@ -232,9 +283,36 @@ err_free:
return ERR_PTR(ret);
}
+/*
+ * Parse the amazon,al-fic-mask property into an enum al_fic_id, selecting
+ * which of the controller's outputs this group drives. The property is
+ * optional; an absent property means the info output.
+ */
+static int al_fic_parse_mask(struct device_node *node, enum al_fic_id *fic_id)
+{
+ const char *mask;
+ int ret;
+
+ ret = of_property_read_string(node, "amazon,al-fic-mask", &mask);
+ if (ret == -EINVAL) {
+ *fic_id = AL_FIC_ID_INFO;
+ return 0;
+ }
+ if (ret)
+ return ret;
+
+ ret = match_string(al_fic_mask_name, AL_FIC_ID_MAX, mask);
+ if (ret < 0)
+ return ret;
+
+ *fic_id = ret;
+ return 0;
+}
+
static int __init al_fic_init_dt(struct device_node *node, struct device_node *parent)
{
unsigned int parent_irq;
+ enum al_fic_id fic_id;
struct al_fic *fic;
void __iomem *base;
int ret;
@@ -244,6 +322,12 @@ static int __init al_fic_init_dt(struct device_node *node, struct device_node *p
return -EINVAL;
}
+ ret = al_fic_parse_mask(node, &fic_id);
+ if (ret) {
+ pr_err("%pOF: invalid amazon,al-fic-mask\n", node);
+ return ret;
+ }
+
base = of_iomap(node, 0);
if (!base) {
pr_err("%pOF: fail to map memory\n", node);
@@ -257,7 +341,7 @@ static int __init al_fic_init_dt(struct device_node *node, struct device_node *p
goto err_unmap;
}
- fic = al_fic_wire_init(node, base, parent_irq);
+ fic = al_fic_wire_init(node, base, parent_irq, fic_id);
if (IS_ERR(fic)) {
pr_err("%pOF: fail to initialize irqchip (%pe)\n", node, fic);