Skip to content

Commit

Permalink
irqchip/irq-sifive-plic: Add syscore callbacks for hibernation
Browse files Browse the repository at this point in the history
The priority and enable registers of plic will be reset
during hibernation power cycle in poweroff mode,
add the syscore callbacks to save/restore those registers.

Signed-off-by: Mason Huo <mason.huo@starfivetech.com>
Reviewed-by: Ley Foon Tan <leyfoon.tan@starfivetech.com>
Reviewed-by: Sia Jee Heng <jeeheng.sia@starfivetech.com>
Reported-by: Dan Carpenter <error27@gmail.com>
Link: https://lore.kernel.org/r/202302140709.CdkxgtPi-lkp@intel.com/
Signed-off-by: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20230404032908.89638-1-mason.huo@starfivetech.com
  • Loading branch information
masonhuo authored and Marc Zyngier committed Apr 8, 2023
1 parent 9dfc779 commit e80f0b6
Showing 1 changed file with 91 additions and 2 deletions.
93 changes: 91 additions & 2 deletions drivers/irqchip/irq-sifive-plic.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/syscore_ops.h>
#include <asm/smp.h>

/*
Expand Down Expand Up @@ -67,6 +68,8 @@ struct plic_priv {
struct irq_domain *irqdomain;
void __iomem *regs;
unsigned long plic_quirks;
unsigned int nr_irqs;
unsigned long *prio_save;
};

struct plic_handler {
Expand All @@ -78,6 +81,7 @@ struct plic_handler {
*/
raw_spinlock_t enable_lock;
void __iomem *enable_base;
u32 *enable_save;
struct plic_priv *priv;
};
static int plic_parent_irq __ro_after_init;
Expand Down Expand Up @@ -229,6 +233,71 @@ static int plic_irq_set_type(struct irq_data *d, unsigned int type)
return IRQ_SET_MASK_OK;
}

static int plic_irq_suspend(void)
{
unsigned int i, cpu;
u32 __iomem *reg;
struct plic_priv *priv;

priv = per_cpu_ptr(&plic_handlers, smp_processor_id())->priv;

for (i = 0; i < priv->nr_irqs; i++)
if (readl(priv->regs + PRIORITY_BASE + i * PRIORITY_PER_ID))
__set_bit(i, priv->prio_save);
else
__clear_bit(i, priv->prio_save);

for_each_cpu(cpu, cpu_present_mask) {
struct plic_handler *handler = per_cpu_ptr(&plic_handlers, cpu);

if (!handler->present)
continue;

raw_spin_lock(&handler->enable_lock);
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);
}

return 0;
}

static void plic_irq_resume(void)
{
unsigned int i, index, cpu;
u32 __iomem *reg;
struct plic_priv *priv;

priv = per_cpu_ptr(&plic_handlers, smp_processor_id())->priv;

for (i = 0; i < priv->nr_irqs; i++) {
index = BIT_WORD(i);
writel((priv->prio_save[index] & BIT_MASK(i)) ? 1 : 0,
priv->regs + PRIORITY_BASE + i * PRIORITY_PER_ID);
}

for_each_cpu(cpu, cpu_present_mask) {
struct plic_handler *handler = per_cpu_ptr(&plic_handlers, cpu);

if (!handler->present)
continue;

raw_spin_lock(&handler->enable_lock);
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);
}
}

static struct syscore_ops plic_irq_syscore_ops = {
.suspend = plic_irq_suspend,
.resume = plic_irq_resume,
};

static int plic_irqdomain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hwirq)
{
Expand Down Expand Up @@ -345,6 +414,7 @@ static int __init __plic_init(struct device_node *node,
u32 nr_irqs;
struct plic_priv *priv;
struct plic_handler *handler;
unsigned int cpu;

priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
Expand All @@ -363,15 +433,21 @@ static int __init __plic_init(struct device_node *node,
if (WARN_ON(!nr_irqs))
goto out_iounmap;

priv->nr_irqs = nr_irqs;

priv->prio_save = bitmap_alloc(nr_irqs, GFP_KERNEL);
if (!priv->prio_save)
goto out_free_priority_reg;

nr_contexts = of_irq_count(node);
if (WARN_ON(!nr_contexts))
goto out_iounmap;
goto out_free_priority_reg;

error = -ENOMEM;
priv->irqdomain = irq_domain_add_linear(node, nr_irqs + 1,
&plic_irqdomain_ops, priv);
if (WARN_ON(!priv->irqdomain))
goto out_iounmap;
goto out_free_priority_reg;

for (i = 0; i < nr_contexts; i++) {
struct of_phandle_args parent;
Expand Down Expand Up @@ -441,6 +517,11 @@ static int __init __plic_init(struct device_node *node,
handler->enable_base = priv->regs + CONTEXT_ENABLE_BASE +
i * CONTEXT_ENABLE_SIZE;
handler->priv = priv;

handler->enable_save = kcalloc(DIV_ROUND_UP(nr_irqs, 32),
sizeof(*handler->enable_save), GFP_KERNEL);
if (!handler->enable_save)
goto out_free_enable_reg;
done:
for (hwirq = 1; hwirq <= nr_irqs; hwirq++) {
plic_toggle(handler, hwirq, 0);
Expand All @@ -461,11 +542,19 @@ static int __init __plic_init(struct device_node *node,
plic_starting_cpu, plic_dying_cpu);
plic_cpuhp_setup_done = true;
}
register_syscore_ops(&plic_irq_syscore_ops);

pr_info("%pOFP: mapped %d interrupts with %d handlers for"
" %d contexts.\n", node, nr_irqs, nr_handlers, nr_contexts);
return 0;

out_free_enable_reg:
for_each_cpu(cpu, cpu_present_mask) {
handler = per_cpu_ptr(&plic_handlers, cpu);
kfree(handler->enable_save);
}
out_free_priority_reg:
kfree(priv->prio_save);
out_iounmap:
iounmap(priv->regs);
out_free_priv:
Expand Down

0 comments on commit e80f0b6

Please sign in to comment.