diff options
Diffstat (limited to 'drivers/irqchip/irq-sifive-plic.c')
-rw-r--r-- | drivers/irqchip/irq-sifive-plic.c | 287 |
1 files changed, 176 insertions, 111 deletions
diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c index bf0b40b0fa..d246b7230b 100644 --- a/drivers/irqchip/irq-sifive-plic.c +++ b/drivers/irqchip/irq-sifive-plic.c @@ -3,7 +3,6 @@ * Copyright (C) 2017 SiFive * Copyright (C) 2018 Christoph Hellwig */ -#define pr_fmt(fmt) "plic: " fmt #include <linux/cpu.h> #include <linux/interrupt.h> #include <linux/io.h> @@ -64,6 +63,7 @@ #define PLIC_QUIRK_EDGE_INTERRUPT 0 struct plic_priv { + struct device *dev; struct cpumask lmask; struct irq_domain *irqdomain; void __iomem *regs; @@ -85,7 +85,7 @@ struct plic_handler { struct plic_priv *priv; }; static int plic_parent_irq __ro_after_init; -static bool plic_cpuhp_setup_done __ro_after_init; +static bool plic_global_setup_done __ro_after_init; static DEFINE_PER_CPU(struct plic_handler, plic_handlers); static int plic_irq_set_type(struct irq_data *d, unsigned int type); @@ -103,9 +103,11 @@ static void __plic_toggle(void __iomem *enable_base, int hwirq, int enable) static void plic_toggle(struct plic_handler *handler, int hwirq, int enable) { - raw_spin_lock(&handler->enable_lock); + unsigned long flags; + + raw_spin_lock_irqsave(&handler->enable_lock, flags); __plic_toggle(handler->enable_base, hwirq, enable); - raw_spin_unlock(&handler->enable_lock); + raw_spin_unlock_irqrestore(&handler->enable_lock, flags); } static inline void plic_irq_toggle(const struct cpumask *mask, @@ -242,6 +244,7 @@ static int plic_irq_set_type(struct irq_data *d, unsigned int type) static int plic_irq_suspend(void) { unsigned int i, cpu; + unsigned long flags; u32 __iomem *reg; struct plic_priv *priv; @@ -259,12 +262,12 @@ static int plic_irq_suspend(void) if (!handler->present) continue; - raw_spin_lock(&handler->enable_lock); + raw_spin_lock_irqsave(&handler->enable_lock, flags); for (i = 0; i < DIV_ROUND_UP(priv->nr_irqs, 32); i++) { reg = handler->enable_base + i * sizeof(u32); handler->enable_save[i] = readl(reg); } - raw_spin_unlock(&handler->enable_lock); + raw_spin_unlock_irqrestore(&handler->enable_lock, flags); } return 0; @@ -273,6 +276,7 @@ static int plic_irq_suspend(void) static void plic_irq_resume(void) { unsigned int i, index, cpu; + unsigned long flags; u32 __iomem *reg; struct plic_priv *priv; @@ -290,12 +294,12 @@ static void plic_irq_resume(void) if (!handler->present) continue; - raw_spin_lock(&handler->enable_lock); + raw_spin_lock_irqsave(&handler->enable_lock, flags); for (i = 0; i < DIV_ROUND_UP(priv->nr_irqs, 32); i++) { reg = handler->enable_base + i * sizeof(u32); writel(handler->enable_save[i], reg); } - raw_spin_unlock(&handler->enable_lock); + raw_spin_unlock_irqrestore(&handler->enable_lock, flags); } } @@ -376,9 +380,10 @@ static void plic_handle_irq(struct irq_desc *desc) while ((hwirq = readl(claim))) { int err = generic_handle_domain_irq(handler->priv->irqdomain, hwirq); - if (unlikely(err)) - pr_warn_ratelimited("can't find mapping for hwirq %lu\n", - hwirq); + if (unlikely(err)) { + dev_warn_ratelimited(handler->priv->dev, + "can't find mapping for hwirq %lu\n", hwirq); + } } chained_irq_exit(chip, desc); @@ -406,63 +411,120 @@ static int plic_starting_cpu(unsigned int cpu) enable_percpu_irq(plic_parent_irq, irq_get_trigger_type(plic_parent_irq)); else - pr_warn("cpu%d: parent irq not available\n", cpu); + dev_warn(handler->priv->dev, "cpu%d: parent irq not available\n", cpu); plic_set_threshold(handler, PLIC_ENABLE_THRESHOLD); return 0; } -static int __init __plic_init(struct device_node *node, - struct device_node *parent, - unsigned long plic_quirks) +static const struct of_device_id plic_match[] = { + { .compatible = "sifive,plic-1.0.0" }, + { .compatible = "riscv,plic0" }, + { .compatible = "andestech,nceplic100", + .data = (const void *)BIT(PLIC_QUIRK_EDGE_INTERRUPT) }, + { .compatible = "thead,c900-plic", + .data = (const void *)BIT(PLIC_QUIRK_EDGE_INTERRUPT) }, + {} +}; + +static int plic_parse_nr_irqs_and_contexts(struct platform_device *pdev, + u32 *nr_irqs, u32 *nr_contexts) { - int error = 0, nr_contexts, nr_handlers = 0, i; - u32 nr_irqs; - struct plic_priv *priv; - struct plic_handler *handler; - unsigned int cpu; + struct device *dev = &pdev->dev; + int rc; - priv = kzalloc(sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; + /* + * Currently, only OF fwnode is supported so extend this + * function for ACPI support. + */ + if (!is_of_node(dev->fwnode)) + return -EINVAL; - priv->plic_quirks = plic_quirks; + rc = of_property_read_u32(to_of_node(dev->fwnode), "riscv,ndev", nr_irqs); + if (rc) { + dev_err(dev, "riscv,ndev property not available\n"); + return rc; + } - priv->regs = of_iomap(node, 0); - if (WARN_ON(!priv->regs)) { - error = -EIO; - goto out_free_priv; + *nr_contexts = of_irq_count(to_of_node(dev->fwnode)); + if (WARN_ON(!(*nr_contexts))) { + dev_err(dev, "no PLIC context available\n"); + return -EINVAL; } - error = -EINVAL; - of_property_read_u32(node, "riscv,ndev", &nr_irqs); - if (WARN_ON(!nr_irqs)) - goto out_iounmap; + return 0; +} - priv->nr_irqs = nr_irqs; +static int plic_parse_context_parent(struct platform_device *pdev, u32 context, + u32 *parent_hwirq, int *parent_cpu) +{ + struct device *dev = &pdev->dev; + struct of_phandle_args parent; + unsigned long hartid; + int rc; - priv->prio_save = bitmap_alloc(nr_irqs, GFP_KERNEL); - if (!priv->prio_save) - goto out_free_priority_reg; + /* + * Currently, only OF fwnode is supported so extend this + * function for ACPI support. + */ + if (!is_of_node(dev->fwnode)) + return -EINVAL; - nr_contexts = of_irq_count(node); - if (WARN_ON(!nr_contexts)) - goto out_free_priority_reg; + rc = of_irq_parse_one(to_of_node(dev->fwnode), context, &parent); + if (rc) + return rc; - error = -ENOMEM; - priv->irqdomain = irq_domain_add_linear(node, nr_irqs + 1, - &plic_irqdomain_ops, priv); - if (WARN_ON(!priv->irqdomain)) - goto out_free_priority_reg; + rc = riscv_of_parent_hartid(parent.np, &hartid); + if (rc) + return rc; - for (i = 0; i < nr_contexts; i++) { - struct of_phandle_args parent; - irq_hw_number_t hwirq; - int cpu; - unsigned long hartid; + *parent_hwirq = parent.args[0]; + *parent_cpu = riscv_hartid_to_cpuid(hartid); + return 0; +} + +static int plic_probe(struct platform_device *pdev) +{ + int error = 0, nr_contexts, nr_handlers = 0, cpu, i; + struct device *dev = &pdev->dev; + unsigned long plic_quirks = 0; + struct plic_handler *handler; + u32 nr_irqs, parent_hwirq; + struct plic_priv *priv; + irq_hw_number_t hwirq; + + if (is_of_node(dev->fwnode)) { + const struct of_device_id *id; + + id = of_match_node(plic_match, to_of_node(dev->fwnode)); + if (id) + plic_quirks = (unsigned long)id->data; + } + + error = plic_parse_nr_irqs_and_contexts(pdev, &nr_irqs, &nr_contexts); + if (error) + return error; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->plic_quirks = plic_quirks; + priv->nr_irqs = nr_irqs; - if (of_irq_parse_one(node, i, &parent)) { - pr_err("failed to parse parent for context %d.\n", i); + priv->regs = devm_platform_ioremap_resource(pdev, 0); + if (WARN_ON(!priv->regs)) + return -EIO; + + priv->prio_save = devm_bitmap_zalloc(dev, nr_irqs, GFP_KERNEL); + if (!priv->prio_save) + return -ENOMEM; + + for (i = 0; i < nr_contexts; i++) { + error = plic_parse_context_parent(pdev, i, &parent_hwirq, &cpu); + if (error) { + dev_warn(dev, "hwirq for context%d not found\n", i); continue; } @@ -470,7 +532,7 @@ static int __init __plic_init(struct device_node *node, * Skip contexts other than external interrupts for our * privilege level. */ - if (parent.args[0] != RV_IRQ_EXT) { + if (parent_hwirq != RV_IRQ_EXT) { /* Disable S-mode enable bits if running in M-mode. */ if (IS_ENABLED(CONFIG_RISCV_M_MODE)) { void __iomem *enable_base = priv->regs + @@ -483,26 +545,11 @@ static int __init __plic_init(struct device_node *node, continue; } - error = riscv_of_parent_hartid(parent.np, &hartid); - if (error < 0) { - pr_warn("failed to parse hart ID for context %d.\n", i); - continue; - } - - cpu = riscv_hartid_to_cpuid(hartid); if (cpu < 0) { - pr_warn("Invalid cpuid for context %d\n", i); + dev_warn(dev, "Invalid cpuid for context %d\n", i); continue; } - /* Find parent domain and register chained handler */ - if (!plic_parent_irq && irq_find_host(parent.np)) { - plic_parent_irq = irq_of_parse_and_map(node, i); - if (plic_parent_irq) - irq_set_chained_handler(plic_parent_irq, - plic_handle_irq); - } - /* * When running in M-mode we need to ignore the S-mode handler. * Here we assume it always comes later, but that might be a @@ -510,7 +557,7 @@ static int __init __plic_init(struct device_node *node, */ handler = per_cpu_ptr(&plic_handlers, cpu); if (handler->present) { - pr_warn("handler already present for context %d.\n", i); + dev_warn(dev, "handler already present for context %d.\n", i); plic_set_threshold(handler, PLIC_DISABLE_THRESHOLD); goto done; } @@ -524,10 +571,10 @@ static int __init __plic_init(struct device_node *node, i * CONTEXT_ENABLE_SIZE; handler->priv = priv; - handler->enable_save = kcalloc(DIV_ROUND_UP(nr_irqs, 32), - sizeof(*handler->enable_save), GFP_KERNEL); + handler->enable_save = devm_kcalloc(dev, DIV_ROUND_UP(nr_irqs, 32), + sizeof(*handler->enable_save), GFP_KERNEL); if (!handler->enable_save) - goto out_free_enable_reg; + goto fail_cleanup_contexts; done: for (hwirq = 1; hwirq <= nr_irqs; hwirq++) { plic_toggle(handler, hwirq, 0); @@ -537,52 +584,70 @@ done: nr_handlers++; } + priv->irqdomain = irq_domain_add_linear(to_of_node(dev->fwnode), nr_irqs + 1, + &plic_irqdomain_ops, priv); + if (WARN_ON(!priv->irqdomain)) + goto fail_cleanup_contexts; + /* - * We can have multiple PLIC instances so setup cpuhp state - * and register syscore operations only when context handler - * for current/boot CPU is present. + * We can have multiple PLIC instances so setup global state + * and register syscore operations only once after context + * handlers of all online CPUs are initialized. */ - handler = this_cpu_ptr(&plic_handlers); - if (handler->present && !plic_cpuhp_setup_done) { - cpuhp_setup_state(CPUHP_AP_IRQ_SIFIVE_PLIC_STARTING, - "irqchip/sifive/plic:starting", - plic_starting_cpu, plic_dying_cpu); - register_syscore_ops(&plic_irq_syscore_ops); - plic_cpuhp_setup_done = true; - } + if (!plic_global_setup_done) { + struct irq_domain *domain; + bool global_setup = true; + + for_each_online_cpu(cpu) { + handler = per_cpu_ptr(&plic_handlers, cpu); + if (!handler->present) { + global_setup = false; + break; + } + } - pr_info("%pOFP: mapped %d interrupts with %d handlers for" - " %d contexts.\n", node, nr_irqs, nr_handlers, nr_contexts); - return 0; + if (global_setup) { + /* Find parent domain and register chained handler */ + domain = irq_find_matching_fwnode(riscv_get_intc_hwnode(), DOMAIN_BUS_ANY); + if (domain) + plic_parent_irq = irq_create_mapping(domain, RV_IRQ_EXT); + if (plic_parent_irq) + irq_set_chained_handler(plic_parent_irq, plic_handle_irq); -out_free_enable_reg: - for_each_cpu(cpu, cpu_present_mask) { - handler = per_cpu_ptr(&plic_handlers, cpu); - kfree(handler->enable_save); + cpuhp_setup_state(CPUHP_AP_IRQ_SIFIVE_PLIC_STARTING, + "irqchip/sifive/plic:starting", + plic_starting_cpu, plic_dying_cpu); + register_syscore_ops(&plic_irq_syscore_ops); + plic_global_setup_done = true; + } } -out_free_priority_reg: - kfree(priv->prio_save); -out_iounmap: - iounmap(priv->regs); -out_free_priv: - kfree(priv); - return error; -} -static int __init plic_init(struct device_node *node, - struct device_node *parent) -{ - return __plic_init(node, parent, 0); -} + dev_info(dev, "mapped %d interrupts with %d handlers for %d contexts.\n", + nr_irqs, nr_handlers, nr_contexts); + return 0; -IRQCHIP_DECLARE(sifive_plic, "sifive,plic-1.0.0", plic_init); -IRQCHIP_DECLARE(riscv_plic0, "riscv,plic0", plic_init); /* for legacy systems */ +fail_cleanup_contexts: + for (i = 0; i < nr_contexts; i++) { + if (plic_parse_context_parent(pdev, i, &parent_hwirq, &cpu)) + continue; + if (parent_hwirq != RV_IRQ_EXT || cpu < 0) + continue; -static int __init plic_edge_init(struct device_node *node, - struct device_node *parent) -{ - return __plic_init(node, parent, BIT(PLIC_QUIRK_EDGE_INTERRUPT)); + handler = per_cpu_ptr(&plic_handlers, cpu); + handler->present = false; + handler->hart_base = NULL; + handler->enable_base = NULL; + handler->enable_save = NULL; + handler->priv = NULL; + } + return -ENOMEM; } -IRQCHIP_DECLARE(andestech_nceplic100, "andestech,nceplic100", plic_edge_init); -IRQCHIP_DECLARE(thead_c900_plic, "thead,c900-plic", plic_edge_init); +static struct platform_driver plic_driver = { + .driver = { + .name = "riscv-plic", + .of_match_table = plic_match, + }, + .probe = plic_probe, +}; +builtin_platform_driver(plic_driver); |