The T-HEAD PLIC ignores additional edges seen while an edge-triggered interrupt is being handled. Because of this behavior, the driver needs to complete edge-triggered interrupts in the .irq_ack callback before handling them, instead of in the .irq_eoi callback afterward. Otherwise, it could miss some interrupts. Co-developed-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx> Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@xxxxxxxxxxxxxx> Signed-off-by: Samuel Holland <samuel@xxxxxxxxxxxx> --- Changes in v1: - Use a flag for enabling the changes instead of a variant ID - Use handle_edge_irq instead of handle_fasteoi_ack_irq - Do not set the handler name, as RISC-V selects GENERIC_IRQ_SHOW_LEVEL drivers/irqchip/irq-sifive-plic.c | 76 +++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c index 90515865af08..462a93b4b088 100644 --- a/drivers/irqchip/irq-sifive-plic.c +++ b/drivers/irqchip/irq-sifive-plic.c @@ -69,6 +69,7 @@ struct plic_priv { struct cpumask lmask; struct irq_domain *irqdomain; void __iomem *regs; + bool needs_edge_handling; }; struct plic_handler { @@ -86,6 +87,9 @@ static int plic_parent_irq __ro_after_init; static bool plic_cpuhp_setup_done __ro_after_init; static DEFINE_PER_CPU(struct plic_handler, plic_handlers); +static struct irq_chip plic_edge_chip; +static struct irq_chip plic_chip; + static void __plic_toggle(void __iomem *enable_base, int hwirq, int enable) { u32 __iomem *reg = enable_base + (hwirq / 32) * sizeof(u32); @@ -181,6 +185,40 @@ static void plic_irq_eoi(struct irq_data *d) } } +static int plic_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + struct plic_priv *priv = irq_data_get_irq_chip_data(d); + + if (!priv->needs_edge_handling) + return IRQ_SET_MASK_OK_NOCOPY; + + switch (flow_type) { + case IRQ_TYPE_EDGE_RISING: + irq_set_chip_handler_name_locked(d, &plic_edge_chip, + handle_edge_irq, NULL); + break; + case IRQ_TYPE_LEVEL_HIGH: + irq_set_chip_handler_name_locked(d, &plic_chip, + handle_fasteoi_irq, NULL); + break; + default: + return -EINVAL; + } + + return IRQ_SET_MASK_OK; +} + +static struct irq_chip plic_edge_chip = { + .name = "PLIC", + .irq_ack = plic_irq_eoi, + .irq_mask = plic_irq_mask, + .irq_unmask = plic_irq_unmask, +#ifdef CONFIG_SMP + .irq_set_affinity = plic_set_affinity, +#endif + .irq_set_type = plic_irq_set_type, +}; + static struct irq_chip plic_chip = { .name = "PLIC", .irq_mask = plic_irq_mask, @@ -189,8 +227,22 @@ static struct irq_chip plic_chip = { #ifdef CONFIG_SMP .irq_set_affinity = plic_set_affinity, #endif + .irq_set_type = plic_irq_set_type, }; +static int plic_irq_domain_translate(struct irq_domain *d, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + struct plic_priv *priv = d->host_data; + + if (priv->needs_edge_handling) + return irq_domain_translate_twocell(d, fwspec, hwirq, type); + else + return irq_domain_translate_onecell(d, fwspec, hwirq, type); +} + static int plic_irqdomain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hwirq) { @@ -211,7 +263,7 @@ static int plic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, unsigned int type; struct irq_fwspec *fwspec = arg; - ret = irq_domain_translate_onecell(domain, fwspec, &hwirq, &type); + ret = plic_irq_domain_translate(domain, fwspec, &hwirq, &type); if (ret) return ret; @@ -225,7 +277,7 @@ static int plic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, } static const struct irq_domain_ops plic_irqdomain_ops = { - .translate = irq_domain_translate_onecell, + .translate = plic_irq_domain_translate, .alloc = plic_irq_domain_alloc, .free = irq_domain_free_irqs_top, }; @@ -286,8 +338,9 @@ static int plic_starting_cpu(unsigned int cpu) return 0; } -static int __init plic_init(struct device_node *node, - struct device_node *parent) +static int __init __plic_init(struct device_node *node, + struct device_node *parent, + bool needs_edge_handling) { int error = 0, nr_contexts, nr_handlers = 0, i; u32 nr_irqs; @@ -298,6 +351,8 @@ static int __init plic_init(struct device_node *node, if (!priv) return -ENOMEM; + priv->needs_edge_handling = needs_edge_handling; + priv->regs = of_iomap(node, 0); if (WARN_ON(!priv->regs)) { error = -EIO; @@ -415,6 +470,17 @@ static int __init plic_init(struct device_node *node, return error; } +static int __init plic_init(struct device_node *node, + struct device_node *parent) +{ + return __plic_init(node, parent, false); +} IRQCHIP_DECLARE(sifive_plic, "sifive,plic-1.0.0", plic_init); IRQCHIP_DECLARE(riscv_plic0, "riscv,plic0", plic_init); /* for legacy systems */ -IRQCHIP_DECLARE(thead_c900_plic, "thead,c900-plic", plic_init); /* for firmware driver */ + +static int __init plic_edge_init(struct device_node *node, + struct device_node *parent) +{ + return __plic_init(node, parent, true); +} +IRQCHIP_DECLARE(thead_c900_plic, "thead,c900-plic", plic_edge_init); -- 2.35.1