diff options
Diffstat (limited to 'drivers/irqchip/irq-riscv-aplic-main.c')
| -rw-r--r-- | drivers/irqchip/irq-riscv-aplic-main.c | 187 |
1 files changed, 184 insertions, 3 deletions
diff --git a/drivers/irqchip/irq-riscv-aplic-main.c b/drivers/irqchip/irq-riscv-aplic-main.c index 93e7c51f944a..9f53979b6962 100644 --- a/drivers/irqchip/irq-riscv-aplic-main.c +++ b/drivers/irqchip/irq-riscv-aplic-main.c @@ -12,10 +12,179 @@ #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 *data) +{ + struct aplic_priv *priv; + + list_for_each_entry(priv, &aplics, head) + aplic_save_states(priv); + + return 0; +} + +static void aplic_syscore_resume(void *data) +{ + 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 struct syscore aplic_syscore = { + .ops = &aplic_syscore_ops, +}; + +static bool aplic_syscore_registered __ro_after_init; + +static void aplic_syscore_init(void) +{ + if (!aplic_syscore_registered) { + register_syscore(&aplic_syscore); + aplic_syscore_registered = true; + } +} + +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); @@ -60,6 +229,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; } @@ -82,6 +253,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; @@ -95,6 +267,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 @@ -106,6 +280,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) @@ -176,7 +352,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) @@ -206,16 +382,21 @@ static int aplic_probe(struct platform_device *pdev) rc = aplic_msi_setup(dev, regs); else rc = aplic_direct_setup(dev, regs); - if (rc) + + if (rc) { dev_err_probe(dev, rc, "failed to setup APLIC in %s mode\n", msi_mode ? "MSI" : "direct"); + return rc; + } + + aplic_syscore_init(); #ifdef CONFIG_ACPI if (!acpi_disabled) acpi_dev_clear_dependencies(ACPI_COMPANION(dev)); #endif - return rc; + return 0; } static const struct of_device_id aplic_match[] = { |
