Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions drivers/irqchip/irq-riscv-aplic-direct.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/interrupt.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
Expand Down Expand Up @@ -171,6 +172,15 @@ static void aplic_idc_set_delivery(struct aplic_idc *idc, bool en)
writel(de, idc->regs + APLIC_IDC_IDELIVERY);
}

void aplic_direct_restore_states(struct aplic_priv *priv)
{
struct aplic_direct *direct = container_of(priv, struct aplic_direct, priv);
int cpu;

for_each_cpu(cpu, &direct->lmask)
aplic_idc_set_delivery(per_cpu_ptr(&aplic_idcs, cpu), true);
}

static int aplic_direct_dying_cpu(unsigned int cpu)
{
if (aplic_direct_parent_irq)
Expand Down
166 changes: 165 additions & 1 deletion drivers/irqchip/irq-riscv-aplic-main.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,165 @@
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/printk.h>
#include <linux/syscore_ops.h>

#include "irq-riscv-aplic-main.h"

static LIST_HEAD(aplics);

static void aplic_restore_states(struct aplic_priv *priv)
{
struct aplic_saved_regs *saved_regs = &priv->saved_hw_regs;
struct aplic_src_ctrl *srcs;
void __iomem *regs;
u32 nr_irqs, i;

regs = priv->regs;
writel(saved_regs->domaincfg, regs + APLIC_DOMAINCFG);
#ifdef CONFIG_RISCV_M_MODE
writel(saved_regs->msiaddr, regs + APLIC_xMSICFGADDR);
writel(saved_regs->msiaddrh, regs + APLIC_xMSICFGADDRH);
#endif
/*
* The sourcecfg[i] has to be restored prior to the target[i], interrupt-pending and
* interrupt-enable bits. The AIA specification states that "Whenever interrupt source i is
* inactive in an interrupt domain, the corresponding interrupt-pending and interrupt-enable
* bits within the domain are read-only zeros, and register target[i] is also read-only
* zero."
*/
nr_irqs = priv->nr_irqs;
for (i = 0; i < nr_irqs; i++) {
srcs = &priv->saved_hw_regs.srcs[i];
writel(srcs->sourcecfg, regs + APLIC_SOURCECFG_BASE + i * sizeof(u32));
writel(srcs->target, regs + APLIC_TARGET_BASE + i * sizeof(u32));
}

for (i = 0; i <= nr_irqs; i += 32) {
srcs = &priv->saved_hw_regs.srcs[i];
writel(-1U, regs + APLIC_CLRIE_BASE + (i / 32) * sizeof(u32));
writel(srcs->ie, regs + APLIC_SETIE_BASE + (i / 32) * sizeof(u32));

/* Re-trigger the interrupts if it forwards interrupts to target harts by MSIs */
if (!priv->nr_idcs)
writel(readl(regs + APLIC_CLRIP_BASE + (i / 32) * sizeof(u32)),
regs + APLIC_SETIP_BASE + (i / 32) * sizeof(u32));
}

if (priv->nr_idcs)
aplic_direct_restore_states(priv);
}

static void aplic_save_states(struct aplic_priv *priv)
{
struct aplic_src_ctrl *srcs;
void __iomem *regs;
u32 i, nr_irqs;

regs = priv->regs;
nr_irqs = priv->nr_irqs;
/* The valid interrupt source IDs range from 1 to N, where N is priv->nr_irqs */
for (i = 0; i < nr_irqs; i++) {
srcs = &priv->saved_hw_regs.srcs[i];
srcs->target = readl(regs + APLIC_TARGET_BASE + i * sizeof(u32));

if (i % 32)
continue;

srcs->ie = readl(regs + APLIC_SETIE_BASE + (i / 32) * sizeof(u32));
}

/* Save the nr_irqs bit if needed */
if (!(nr_irqs % 32)) {
srcs = &priv->saved_hw_regs.srcs[nr_irqs];
srcs->ie = readl(regs + APLIC_SETIE_BASE + (nr_irqs / 32) * sizeof(u32));
}
}

static int aplic_syscore_suspend(void)
{
struct aplic_priv *priv;

list_for_each_entry(priv, &aplics, head)
aplic_save_states(priv);

return 0;
}

static void aplic_syscore_resume(void)
{
struct aplic_priv *priv;

list_for_each_entry(priv, &aplics, head)
aplic_restore_states(priv);
}

static struct syscore_ops aplic_syscore_ops = {
.suspend = aplic_syscore_suspend,
.resume = aplic_syscore_resume,
};

static int aplic_pm_notifier(struct notifier_block *nb, unsigned long action, void *data)
{
struct aplic_priv *priv = container_of(nb, struct aplic_priv, genpd_nb);

switch (action) {
case GENPD_NOTIFY_PRE_OFF:
aplic_save_states(priv);
break;
case GENPD_NOTIFY_ON:
aplic_restore_states(priv);
break;
default:
break;
}

return 0;
}

static void aplic_pm_remove(void *data)
{
struct aplic_priv *priv = data;
struct device *dev = priv->dev;

list_del(&priv->head);
if (dev->pm_domain)
dev_pm_genpd_remove_notifier(dev);
}

static int aplic_pm_add(struct device *dev, struct aplic_priv *priv)
{
struct aplic_src_ctrl *srcs;
int ret;

srcs = devm_kzalloc(dev, (priv->nr_irqs + 1) * sizeof(*srcs), GFP_KERNEL);
if (!srcs)
return -ENOMEM;

priv->saved_hw_regs.srcs = srcs;
list_add(&priv->head, &aplics);
if (dev->pm_domain) {
priv->genpd_nb.notifier_call = aplic_pm_notifier;
ret = dev_pm_genpd_add_notifier(dev, &priv->genpd_nb);
if (ret)
goto remove_head;

ret = devm_pm_runtime_enable(dev);
if (ret)
goto remove_notifier;
}

return devm_add_action_or_reset(dev, aplic_pm_remove, priv);

remove_notifier:
dev_pm_genpd_remove_notifier(dev);
remove_head:
list_del(&priv->head);
return ret;
}

void aplic_irq_unmask(struct irq_data *d)
{
struct aplic_priv *priv = irq_data_get_irq_chip_data(d);
Expand Down Expand Up @@ -60,6 +215,8 @@ int aplic_irq_set_type(struct irq_data *d, unsigned int type)
sourcecfg += (d->hwirq - 1) * sizeof(u32);
writel(val, sourcecfg);

priv->saved_hw_regs.srcs[d->hwirq - 1].sourcecfg = val;

return 0;
}

Expand All @@ -82,6 +239,7 @@ int aplic_irqdomain_translate(struct irq_fwspec *fwspec, u32 gsi_base,

void aplic_init_hw_global(struct aplic_priv *priv, bool msi_mode)
{
struct aplic_saved_regs *saved_regs = &priv->saved_hw_regs;
u32 val;
#ifdef CONFIG_RISCV_M_MODE
u32 valh;
Expand All @@ -95,6 +253,8 @@ void aplic_init_hw_global(struct aplic_priv *priv, bool msi_mode)
valh |= FIELD_PREP(APLIC_xMSICFGADDRH_HHXS, priv->msicfg.hhxs);
writel(val, priv->regs + APLIC_xMSICFGADDR);
writel(valh, priv->regs + APLIC_xMSICFGADDRH);
saved_regs->msiaddr = val;
saved_regs->msiaddrh = valh;
}
#endif

Expand All @@ -106,6 +266,8 @@ void aplic_init_hw_global(struct aplic_priv *priv, bool msi_mode)
writel(val, priv->regs + APLIC_DOMAINCFG);
if (readl(priv->regs + APLIC_DOMAINCFG) != val)
dev_warn(priv->dev, "unable to write 0x%x in domaincfg\n", val);

saved_regs->domaincfg = val;
}

static void aplic_init_hw_irqs(struct aplic_priv *priv)
Expand Down Expand Up @@ -176,7 +338,7 @@ int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem *
/* Setup initial state APLIC interrupts */
aplic_init_hw_irqs(priv);

return 0;
return aplic_pm_add(dev, priv);
}

static int aplic_probe(struct platform_device *pdev)
Expand Down Expand Up @@ -209,6 +371,8 @@ static int aplic_probe(struct platform_device *pdev)
if (rc)
dev_err_probe(dev, rc, "failed to setup APLIC in %s mode\n",
msi_mode ? "MSI" : "direct");
else
register_syscore_ops(&aplic_syscore_ops);

#ifdef CONFIG_ACPI
if (!acpi_disabled)
Expand Down
19 changes: 19 additions & 0 deletions drivers/irqchip/irq-riscv-aplic-main.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,25 @@ struct aplic_msicfg {
u32 lhxw;
};

struct aplic_src_ctrl {
u32 sourcecfg;
u32 target;
u32 ie;
};

struct aplic_saved_regs {
u32 domaincfg;
#ifdef CONFIG_RISCV_M_MODE
u32 msiaddr;
u32 msiaddrh;
#endif
struct aplic_src_ctrl *srcs;
};

struct aplic_priv {
struct list_head head;
struct notifier_block genpd_nb;
struct aplic_saved_regs saved_hw_regs;
struct device *dev;
u32 gsi_base;
u32 nr_irqs;
Expand All @@ -40,6 +58,7 @@ int aplic_irqdomain_translate(struct irq_fwspec *fwspec, u32 gsi_base,
unsigned long *hwirq, unsigned int *type);
void aplic_init_hw_global(struct aplic_priv *priv, bool msi_mode);
int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem *regs);
void aplic_direct_restore_states(struct aplic_priv *priv);
int aplic_direct_setup(struct device *dev, void __iomem *regs);
#ifdef CONFIG_RISCV_APLIC_MSI
int aplic_msi_setup(struct device *dev, void __iomem *regs);
Expand Down
39 changes: 31 additions & 8 deletions drivers/irqchip/irq-riscv-imsic-early.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define pr_fmt(fmt) "riscv-imsic: " fmt
#include <linux/acpi.h>
#include <linux/cpu.h>
#include <linux/cpu_pm.h>
#include <linux/export.h>
#include <linux/interrupt.h>
#include <linux/init.h>
Expand Down Expand Up @@ -128,14 +129,8 @@ static void imsic_handle_irq(struct irq_desc *desc)
chained_irq_exit(chip, desc);
}

static int imsic_starting_cpu(unsigned int cpu)
static void imsic_hw_states_init(void)
{
/* Mark per-CPU IMSIC state as online */
imsic_state_online();

/* Enable per-CPU parent interrupt */
enable_percpu_irq(imsic_parent_irq, irq_get_trigger_type(imsic_parent_irq));

/* Setup IPIs */
imsic_ipi_starting_cpu();

Expand All @@ -147,6 +142,18 @@ static int imsic_starting_cpu(unsigned int cpu)

/* Enable local interrupt delivery */
imsic_local_delivery(true);
}

static int imsic_starting_cpu(unsigned int cpu)
{
/* Mark per-CPU IMSIC state as online */
imsic_state_online();

/* Enable per-CPU parent interrupt */
enable_percpu_irq(imsic_parent_irq, irq_get_trigger_type(imsic_parent_irq));

/* Initialize the IMSIC registers to enable the interrupt delivery */
imsic_hw_states_init();

return 0;
}
Expand All @@ -162,6 +169,22 @@ static int imsic_dying_cpu(unsigned int cpu)
return 0;
}

static int imsic_pm_notifier(struct notifier_block *self, unsigned long cmd, void *v)
{
switch (cmd) {
case CPU_PM_EXIT:
/* Initialize the IMSIC registers to enable the interrupt delivery */
imsic_hw_states_init();
break;
}

return NOTIFY_OK;
}

static struct notifier_block imsic_pm_notifier_block = {
.notifier_call = imsic_pm_notifier,
};

static int __init imsic_early_probe(struct fwnode_handle *fwnode)
{
struct irq_domain *domain;
Expand Down Expand Up @@ -199,7 +222,7 @@ static int __init imsic_early_probe(struct fwnode_handle *fwnode)
cpuhp_setup_state(CPUHP_AP_IRQ_RISCV_IMSIC_STARTING, "irqchip/riscv/imsic:starting",
imsic_starting_cpu, imsic_dying_cpu);

return 0;
return cpu_pm_register_notifier(&imsic_pm_notifier_block);
}

static int __init imsic_early_dt_init(struct device_node *node, struct device_node *parent)
Expand Down