mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-09 14:50:19 +00:00
irqchip/sifive-plic: Probe plic driver early for Allwinner D1 platform
The latest Linux RISC-V no longer boots on the Allwinner D1 platform because the sun4i_timer driver fails to get an interrupt from PLIC due to the recent conversion of the PLIC to a platform driver. Converting the sun4i timer to a platform driver does not work either because the D1 does not have a SBI timer available so early boot hangs. See the 'Closes:' link for deeper analysis. The real fix requires enabling the SBI time extension in the platform firmware (OpenSBI) and convert sun4i_timer into platform driver. Unfortunately, the real fix involves changing multiple places and can't be achieved in a short duration and aside of that requires users to update firmware. As a work-around, retrofit PLIC probing such that the PLIC is probed early only for the Allwinner D1 platform and probed as a regular platform driver for rest of the RISC-V platforms. In the process, partially revert some of the previous changes because the PLIC device pointer is not available in all probing paths. Fixes: e306a894bd51 ("irqchip/sifive-plic: Chain to parent IRQ after handlers are ready") Fixes: 8ec99b033147 ("irqchip/sifive-plic: Convert PLIC driver into a platform driver") Suggested-by: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Anup Patel <apatel@ventanamicro.com> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Tested-by: Samuel Holland <samuel.holland@sifive.com> Tested-by: Emil Renner Berthing <emil.renner.berthing@canonical.com> Tested-by: Charlie Jenkins <charlie@rivosinc.com> Reviewed-by: Samuel Holland <samuel.holland@sifive.com> Reviewed-by: Charlie Jenkins <charlie@rivosinc.com> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/20240820034850.3189912-1-apatel@ventanamicro.com Closes: https://lore.kernel.org/lkml/20240814145642.344485-1-emil.renner.berthing@canonical.com/
This commit is contained in:
parent
47ac09b91b
commit
4d936f10ff
@ -3,6 +3,7 @@
|
|||||||
* Copyright (C) 2017 SiFive
|
* Copyright (C) 2017 SiFive
|
||||||
* Copyright (C) 2018 Christoph Hellwig
|
* Copyright (C) 2018 Christoph Hellwig
|
||||||
*/
|
*/
|
||||||
|
#define pr_fmt(fmt) "riscv-plic: " fmt
|
||||||
#include <linux/cpu.h>
|
#include <linux/cpu.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
@ -63,7 +64,7 @@
|
|||||||
#define PLIC_QUIRK_EDGE_INTERRUPT 0
|
#define PLIC_QUIRK_EDGE_INTERRUPT 0
|
||||||
|
|
||||||
struct plic_priv {
|
struct plic_priv {
|
||||||
struct device *dev;
|
struct fwnode_handle *fwnode;
|
||||||
struct cpumask lmask;
|
struct cpumask lmask;
|
||||||
struct irq_domain *irqdomain;
|
struct irq_domain *irqdomain;
|
||||||
void __iomem *regs;
|
void __iomem *regs;
|
||||||
@ -378,8 +379,8 @@ static void plic_handle_irq(struct irq_desc *desc)
|
|||||||
int err = generic_handle_domain_irq(handler->priv->irqdomain,
|
int err = generic_handle_domain_irq(handler->priv->irqdomain,
|
||||||
hwirq);
|
hwirq);
|
||||||
if (unlikely(err)) {
|
if (unlikely(err)) {
|
||||||
dev_warn_ratelimited(handler->priv->dev,
|
pr_warn_ratelimited("%pfwP: can't find mapping for hwirq %lu\n",
|
||||||
"can't find mapping for hwirq %lu\n", hwirq);
|
handler->priv->fwnode, hwirq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +409,8 @@ static int plic_starting_cpu(unsigned int cpu)
|
|||||||
enable_percpu_irq(plic_parent_irq,
|
enable_percpu_irq(plic_parent_irq,
|
||||||
irq_get_trigger_type(plic_parent_irq));
|
irq_get_trigger_type(plic_parent_irq));
|
||||||
else
|
else
|
||||||
dev_warn(handler->priv->dev, "cpu%d: parent irq not available\n", cpu);
|
pr_warn("%pfwP: cpu%d: parent irq not available\n",
|
||||||
|
handler->priv->fwnode, cpu);
|
||||||
plic_set_threshold(handler, PLIC_ENABLE_THRESHOLD);
|
plic_set_threshold(handler, PLIC_ENABLE_THRESHOLD);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -424,38 +426,36 @@ static const struct of_device_id plic_match[] = {
|
|||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
static int plic_parse_nr_irqs_and_contexts(struct platform_device *pdev,
|
static int plic_parse_nr_irqs_and_contexts(struct fwnode_handle *fwnode,
|
||||||
u32 *nr_irqs, u32 *nr_contexts)
|
u32 *nr_irqs, u32 *nr_contexts)
|
||||||
{
|
{
|
||||||
struct device *dev = &pdev->dev;
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Currently, only OF fwnode is supported so extend this
|
* Currently, only OF fwnode is supported so extend this
|
||||||
* function for ACPI support.
|
* function for ACPI support.
|
||||||
*/
|
*/
|
||||||
if (!is_of_node(dev->fwnode))
|
if (!is_of_node(fwnode))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
rc = of_property_read_u32(to_of_node(dev->fwnode), "riscv,ndev", nr_irqs);
|
rc = of_property_read_u32(to_of_node(fwnode), "riscv,ndev", nr_irqs);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
dev_err(dev, "riscv,ndev property not available\n");
|
pr_err("%pfwP: riscv,ndev property not available\n", fwnode);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
*nr_contexts = of_irq_count(to_of_node(dev->fwnode));
|
*nr_contexts = of_irq_count(to_of_node(fwnode));
|
||||||
if (WARN_ON(!(*nr_contexts))) {
|
if (WARN_ON(!(*nr_contexts))) {
|
||||||
dev_err(dev, "no PLIC context available\n");
|
pr_err("%pfwP: no PLIC context available\n", fwnode);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int plic_parse_context_parent(struct platform_device *pdev, u32 context,
|
static int plic_parse_context_parent(struct fwnode_handle *fwnode, u32 context,
|
||||||
u32 *parent_hwirq, int *parent_cpu)
|
u32 *parent_hwirq, int *parent_cpu)
|
||||||
{
|
{
|
||||||
struct device *dev = &pdev->dev;
|
|
||||||
struct of_phandle_args parent;
|
struct of_phandle_args parent;
|
||||||
unsigned long hartid;
|
unsigned long hartid;
|
||||||
int rc;
|
int rc;
|
||||||
@ -464,10 +464,10 @@ static int plic_parse_context_parent(struct platform_device *pdev, u32 context,
|
|||||||
* Currently, only OF fwnode is supported so extend this
|
* Currently, only OF fwnode is supported so extend this
|
||||||
* function for ACPI support.
|
* function for ACPI support.
|
||||||
*/
|
*/
|
||||||
if (!is_of_node(dev->fwnode))
|
if (!is_of_node(fwnode))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
rc = of_irq_parse_one(to_of_node(dev->fwnode), context, &parent);
|
rc = of_irq_parse_one(to_of_node(fwnode), context, &parent);
|
||||||
if (rc)
|
if (rc)
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
@ -480,48 +480,55 @@ static int plic_parse_context_parent(struct platform_device *pdev, u32 context,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int plic_probe(struct platform_device *pdev)
|
static int plic_probe(struct fwnode_handle *fwnode)
|
||||||
{
|
{
|
||||||
int error = 0, nr_contexts, nr_handlers = 0, cpu, i;
|
int error = 0, nr_contexts, nr_handlers = 0, cpu, i;
|
||||||
struct device *dev = &pdev->dev;
|
|
||||||
unsigned long plic_quirks = 0;
|
unsigned long plic_quirks = 0;
|
||||||
struct plic_handler *handler;
|
struct plic_handler *handler;
|
||||||
u32 nr_irqs, parent_hwirq;
|
u32 nr_irqs, parent_hwirq;
|
||||||
struct plic_priv *priv;
|
struct plic_priv *priv;
|
||||||
irq_hw_number_t hwirq;
|
irq_hw_number_t hwirq;
|
||||||
|
void __iomem *regs;
|
||||||
|
|
||||||
if (is_of_node(dev->fwnode)) {
|
if (is_of_node(fwnode)) {
|
||||||
const struct of_device_id *id;
|
const struct of_device_id *id;
|
||||||
|
|
||||||
id = of_match_node(plic_match, to_of_node(dev->fwnode));
|
id = of_match_node(plic_match, to_of_node(fwnode));
|
||||||
if (id)
|
if (id)
|
||||||
plic_quirks = (unsigned long)id->data;
|
plic_quirks = (unsigned long)id->data;
|
||||||
|
|
||||||
|
regs = of_iomap(to_of_node(fwnode), 0);
|
||||||
|
if (!regs)
|
||||||
|
return -ENOMEM;
|
||||||
|
} else {
|
||||||
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = plic_parse_nr_irqs_and_contexts(pdev, &nr_irqs, &nr_contexts);
|
error = plic_parse_nr_irqs_and_contexts(fwnode, &nr_irqs, &nr_contexts);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
goto fail_free_regs;
|
||||||
|
|
||||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||||
if (!priv)
|
if (!priv) {
|
||||||
return -ENOMEM;
|
error = -ENOMEM;
|
||||||
|
goto fail_free_regs;
|
||||||
|
}
|
||||||
|
|
||||||
priv->dev = dev;
|
priv->fwnode = fwnode;
|
||||||
priv->plic_quirks = plic_quirks;
|
priv->plic_quirks = plic_quirks;
|
||||||
priv->nr_irqs = nr_irqs;
|
priv->nr_irqs = nr_irqs;
|
||||||
|
priv->regs = regs;
|
||||||
|
|
||||||
priv->regs = devm_platform_ioremap_resource(pdev, 0);
|
priv->prio_save = bitmap_zalloc(nr_irqs, GFP_KERNEL);
|
||||||
if (WARN_ON(!priv->regs))
|
if (!priv->prio_save) {
|
||||||
return -EIO;
|
error = -ENOMEM;
|
||||||
|
goto fail_free_priv;
|
||||||
priv->prio_save = devm_bitmap_zalloc(dev, nr_irqs, GFP_KERNEL);
|
}
|
||||||
if (!priv->prio_save)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
for (i = 0; i < nr_contexts; i++) {
|
for (i = 0; i < nr_contexts; i++) {
|
||||||
error = plic_parse_context_parent(pdev, i, &parent_hwirq, &cpu);
|
error = plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu);
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_warn(dev, "hwirq for context%d not found\n", i);
|
pr_warn("%pfwP: hwirq for context%d not found\n", fwnode, i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,7 +550,7 @@ static int plic_probe(struct platform_device *pdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cpu < 0) {
|
if (cpu < 0) {
|
||||||
dev_warn(dev, "Invalid cpuid for context %d\n", i);
|
pr_warn("%pfwP: Invalid cpuid for context %d\n", fwnode, i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -554,7 +561,7 @@ static int plic_probe(struct platform_device *pdev)
|
|||||||
*/
|
*/
|
||||||
handler = per_cpu_ptr(&plic_handlers, cpu);
|
handler = per_cpu_ptr(&plic_handlers, cpu);
|
||||||
if (handler->present) {
|
if (handler->present) {
|
||||||
dev_warn(dev, "handler already present for context %d.\n", i);
|
pr_warn("%pfwP: handler already present for context %d.\n", fwnode, i);
|
||||||
plic_set_threshold(handler, PLIC_DISABLE_THRESHOLD);
|
plic_set_threshold(handler, PLIC_DISABLE_THRESHOLD);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
@ -568,7 +575,7 @@ static int plic_probe(struct platform_device *pdev)
|
|||||||
i * CONTEXT_ENABLE_SIZE;
|
i * CONTEXT_ENABLE_SIZE;
|
||||||
handler->priv = priv;
|
handler->priv = priv;
|
||||||
|
|
||||||
handler->enable_save = devm_kcalloc(dev, DIV_ROUND_UP(nr_irqs, 32),
|
handler->enable_save = kcalloc(DIV_ROUND_UP(nr_irqs, 32),
|
||||||
sizeof(*handler->enable_save), GFP_KERNEL);
|
sizeof(*handler->enable_save), GFP_KERNEL);
|
||||||
if (!handler->enable_save)
|
if (!handler->enable_save)
|
||||||
goto fail_cleanup_contexts;
|
goto fail_cleanup_contexts;
|
||||||
@ -581,7 +588,7 @@ done:
|
|||||||
nr_handlers++;
|
nr_handlers++;
|
||||||
}
|
}
|
||||||
|
|
||||||
priv->irqdomain = irq_domain_add_linear(to_of_node(dev->fwnode), nr_irqs + 1,
|
priv->irqdomain = irq_domain_add_linear(to_of_node(fwnode), nr_irqs + 1,
|
||||||
&plic_irqdomain_ops, priv);
|
&plic_irqdomain_ops, priv);
|
||||||
if (WARN_ON(!priv->irqdomain))
|
if (WARN_ON(!priv->irqdomain))
|
||||||
goto fail_cleanup_contexts;
|
goto fail_cleanup_contexts;
|
||||||
@ -619,13 +626,13 @@ done:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_info(dev, "mapped %d interrupts with %d handlers for %d contexts.\n",
|
pr_info("%pfwP: mapped %d interrupts with %d handlers for %d contexts.\n",
|
||||||
nr_irqs, nr_handlers, nr_contexts);
|
fwnode, nr_irqs, nr_handlers, nr_contexts);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
fail_cleanup_contexts:
|
fail_cleanup_contexts:
|
||||||
for (i = 0; i < nr_contexts; i++) {
|
for (i = 0; i < nr_contexts; i++) {
|
||||||
if (plic_parse_context_parent(pdev, i, &parent_hwirq, &cpu))
|
if (plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu))
|
||||||
continue;
|
continue;
|
||||||
if (parent_hwirq != RV_IRQ_EXT || cpu < 0)
|
if (parent_hwirq != RV_IRQ_EXT || cpu < 0)
|
||||||
continue;
|
continue;
|
||||||
@ -634,17 +641,37 @@ fail_cleanup_contexts:
|
|||||||
handler->present = false;
|
handler->present = false;
|
||||||
handler->hart_base = NULL;
|
handler->hart_base = NULL;
|
||||||
handler->enable_base = NULL;
|
handler->enable_base = NULL;
|
||||||
|
kfree(handler->enable_save);
|
||||||
handler->enable_save = NULL;
|
handler->enable_save = NULL;
|
||||||
handler->priv = NULL;
|
handler->priv = NULL;
|
||||||
}
|
}
|
||||||
return -ENOMEM;
|
bitmap_free(priv->prio_save);
|
||||||
|
fail_free_priv:
|
||||||
|
kfree(priv);
|
||||||
|
fail_free_regs:
|
||||||
|
iounmap(regs);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int plic_platform_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
return plic_probe(pdev->dev.fwnode);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct platform_driver plic_driver = {
|
static struct platform_driver plic_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "riscv-plic",
|
.name = "riscv-plic",
|
||||||
.of_match_table = plic_match,
|
.of_match_table = plic_match,
|
||||||
|
.suppress_bind_attrs = true,
|
||||||
},
|
},
|
||||||
.probe = plic_probe,
|
.probe = plic_platform_probe,
|
||||||
};
|
};
|
||||||
builtin_platform_driver(plic_driver);
|
builtin_platform_driver(plic_driver);
|
||||||
|
|
||||||
|
static int __init plic_early_probe(struct device_node *node,
|
||||||
|
struct device_node *parent)
|
||||||
|
{
|
||||||
|
return plic_probe(&node->fwnode);
|
||||||
|
}
|
||||||
|
|
||||||
|
IRQCHIP_DECLARE(riscv, "allwinner,sun20i-d1-plic", plic_early_probe);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user