211 lines
6.9 KiB
Diff
211 lines
6.9 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Samuel Holland <samuel@sholland.org>
|
|
Date: Sun, 12 Jan 2020 20:00:36 -0600
|
|
Subject: [PATCH] irqchip/sun6i-r: Add wakeup support
|
|
|
|
Maintain bitmaps of wake-enabled IRQs and mux inputs, and program them
|
|
to the hardware during the syscore phase of suspend and shutdown. Then
|
|
restore the original set of enabled IRQs (only the NMI) during resume.
|
|
|
|
This serves two purposes. First, it lets power management firmware
|
|
running on the ARISC coprocessor know which wakeup sources Linux wants
|
|
to have enabled. That way, it can avoid turning them off when it shuts
|
|
down the remainder of the clock tree. Second, it preconfigures the
|
|
coprocessor's interrupt controller, so the firmware's wakeup logic
|
|
is as simple as waiting for an interrupt to arrive.
|
|
|
|
The suspend/resume logic is not conditional on PM_SLEEP because it is
|
|
identical to the init/shutdown logic. Wake IRQs may be enabled during
|
|
shutdown to allow powering the board back on. As an example, see
|
|
commit a5c5e50cce9d ("Input: gpio-keys - add shutdown callback").
|
|
|
|
Signed-off-by: Samuel Holland <samuel@sholland.org>
|
|
---
|
|
drivers/irqchip/irq-sun6i-r.c | 107 ++++++++++++++++++++++++++++++++--
|
|
1 file changed, 101 insertions(+), 6 deletions(-)
|
|
|
|
--- a/drivers/irqchip/irq-sun6i-r.c
|
|
+++ b/drivers/irqchip/irq-sun6i-r.c
|
|
@@ -39,6 +39,7 @@
|
|
* set of 128 mux bits. This requires a second set of top-level registers.
|
|
*/
|
|
|
|
+#include <linux/bitmap.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/irqchip.h>
|
|
@@ -46,6 +47,7 @@
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
+#include <linux/syscore_ops.h>
|
|
|
|
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
|
|
|
@@ -67,8 +69,17 @@
|
|
#define SUN6I_NR_DIRECT_IRQS 16
|
|
#define SUN6I_NR_MUX_BITS 128
|
|
|
|
+struct sun6i_r_intc_variant {
|
|
+ u32 first_mux_irq;
|
|
+ u32 nr_mux_irqs;
|
|
+ u32 mux_valid[BITS_TO_U32(SUN6I_NR_MUX_BITS)];
|
|
+};
|
|
+
|
|
static void __iomem *base;
|
|
static irq_hw_number_t nmi_hwirq;
|
|
+static DECLARE_BITMAP(wake_irq_enabled, SUN6I_NR_TOP_LEVEL_IRQS);
|
|
+static DECLARE_BITMAP(wake_mux_enabled, SUN6I_NR_MUX_BITS);
|
|
+static DECLARE_BITMAP(wake_mux_valid, SUN6I_NR_MUX_BITS);
|
|
|
|
static void sun6i_r_intc_ack_nmi(void)
|
|
{
|
|
@@ -145,6 +156,21 @@ static int sun6i_r_intc_nmi_set_irqchip_
|
|
return irq_chip_set_parent_state(data, which, state);
|
|
}
|
|
|
|
+static int sun6i_r_intc_irq_set_wake(struct irq_data *data, unsigned int on)
|
|
+{
|
|
+ unsigned long offset_from_nmi = data->hwirq - nmi_hwirq;
|
|
+
|
|
+ if (offset_from_nmi < SUN6I_NR_DIRECT_IRQS)
|
|
+ assign_bit(offset_from_nmi, wake_irq_enabled, on);
|
|
+ else if (test_bit(data->hwirq, wake_mux_valid))
|
|
+ assign_bit(data->hwirq, wake_mux_enabled, on);
|
|
+ else
|
|
+ /* Not wakeup capable. */
|
|
+ return -EPERM;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static struct irq_chip sun6i_r_intc_nmi_chip = {
|
|
.name = "sun6i-r-intc",
|
|
.irq_ack = sun6i_r_intc_nmi_ack,
|
|
@@ -154,8 +180,19 @@ static struct irq_chip sun6i_r_intc_nmi_
|
|
.irq_set_affinity = irq_chip_set_affinity_parent,
|
|
.irq_set_type = sun6i_r_intc_nmi_set_type,
|
|
.irq_set_irqchip_state = sun6i_r_intc_nmi_set_irqchip_state,
|
|
- .flags = IRQCHIP_SET_TYPE_MASKED |
|
|
- IRQCHIP_SKIP_SET_WAKE,
|
|
+ .irq_set_wake = sun6i_r_intc_irq_set_wake,
|
|
+ .flags = IRQCHIP_SET_TYPE_MASKED,
|
|
+};
|
|
+
|
|
+static struct irq_chip sun6i_r_intc_wakeup_chip = {
|
|
+ .name = "sun6i-r-intc",
|
|
+ .irq_mask = irq_chip_mask_parent,
|
|
+ .irq_unmask = irq_chip_unmask_parent,
|
|
+ .irq_eoi = irq_chip_eoi_parent,
|
|
+ .irq_set_affinity = irq_chip_set_affinity_parent,
|
|
+ .irq_set_type = irq_chip_set_type_parent,
|
|
+ .irq_set_wake = sun6i_r_intc_irq_set_wake,
|
|
+ .flags = IRQCHIP_SET_TYPE_MASKED,
|
|
};
|
|
|
|
static int sun6i_r_intc_domain_translate(struct irq_domain *domain,
|
|
@@ -215,8 +252,8 @@ static int sun6i_r_intc_domain_alloc(str
|
|
&sun6i_r_intc_nmi_chip, 0);
|
|
irq_set_handler(virq, handle_fasteoi_ack_irq);
|
|
} else {
|
|
- /* Only the NMI is currently supported. */
|
|
- return -EINVAL;
|
|
+ irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
|
|
+ &sun6i_r_intc_wakeup_chip, 0);
|
|
}
|
|
}
|
|
|
|
@@ -229,6 +266,22 @@ static const struct irq_domain_ops sun6i
|
|
.free = irq_domain_free_irqs_common,
|
|
};
|
|
|
|
+static int sun6i_r_intc_suspend(void)
|
|
+{
|
|
+ u32 buf[BITS_TO_U32(max(SUN6I_NR_TOP_LEVEL_IRQS, SUN6I_NR_MUX_BITS))];
|
|
+ int i;
|
|
+
|
|
+ /* Wake IRQs are enabled during system sleep and shutdown. */
|
|
+ bitmap_to_arr32(buf, wake_irq_enabled, SUN6I_NR_TOP_LEVEL_IRQS);
|
|
+ for (i = 0; i < BITS_TO_U32(SUN6I_NR_TOP_LEVEL_IRQS); ++i)
|
|
+ writel_relaxed(buf[i], base + SUN6I_IRQ_ENABLE(i));
|
|
+ bitmap_to_arr32(buf, wake_mux_enabled, SUN6I_NR_MUX_BITS);
|
|
+ for (i = 0; i < BITS_TO_U32(SUN6I_NR_MUX_BITS); ++i)
|
|
+ writel_relaxed(buf[i], base + SUN6I_MUX_ENABLE(i));
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static void sun6i_r_intc_resume(void)
|
|
{
|
|
int i;
|
|
@@ -239,8 +292,20 @@ static void sun6i_r_intc_resume(void)
|
|
writel_relaxed(0, base + SUN6I_IRQ_ENABLE(i));
|
|
}
|
|
|
|
+static void sun6i_r_intc_shutdown(void)
|
|
+{
|
|
+ sun6i_r_intc_suspend();
|
|
+}
|
|
+
|
|
+static struct syscore_ops sun6i_r_intc_syscore_ops = {
|
|
+ .suspend = sun6i_r_intc_suspend,
|
|
+ .resume = sun6i_r_intc_resume,
|
|
+ .shutdown = sun6i_r_intc_shutdown,
|
|
+};
|
|
+
|
|
static int __init sun6i_r_intc_init(struct device_node *node,
|
|
- struct device_node *parent)
|
|
+ struct device_node *parent,
|
|
+ const struct sun6i_r_intc_variant *v)
|
|
{
|
|
struct irq_domain *domain, *parent_domain;
|
|
struct of_phandle_args nmi_parent;
|
|
@@ -256,6 +321,9 @@ static int __init sun6i_r_intc_init(stru
|
|
return -EINVAL;
|
|
nmi_hwirq = nmi_parent.args[1];
|
|
|
|
+ bitmap_set(wake_irq_enabled, v->first_mux_irq, v->nr_mux_irqs);
|
|
+ bitmap_from_arr32(wake_mux_valid, v->mux_valid, SUN6I_NR_MUX_BITS);
|
|
+
|
|
parent_domain = irq_find_host(parent);
|
|
if (!parent_domain) {
|
|
pr_err("%pOF: Failed to obtain parent domain\n", node);
|
|
@@ -276,9 +344,36 @@ static int __init sun6i_r_intc_init(stru
|
|
return -ENOMEM;
|
|
}
|
|
|
|
+ register_syscore_ops(&sun6i_r_intc_syscore_ops);
|
|
+
|
|
sun6i_r_intc_ack_nmi();
|
|
sun6i_r_intc_resume();
|
|
|
|
return 0;
|
|
}
|
|
-IRQCHIP_DECLARE(sun6i_r_intc, "allwinner,sun6i-a31-r-intc", sun6i_r_intc_init);
|
|
+
|
|
+static const struct sun6i_r_intc_variant sun6i_a31_r_intc_variant __initconst = {
|
|
+ .first_mux_irq = 19,
|
|
+ .nr_mux_irqs = 13,
|
|
+ .mux_valid = { 0xffffffff, 0xfff80000, 0xffffffff, 0x0000000f },
|
|
+};
|
|
+
|
|
+static int __init sun6i_a31_r_intc_init(struct device_node *node,
|
|
+ struct device_node *parent)
|
|
+{
|
|
+ return sun6i_r_intc_init(node, parent, &sun6i_a31_r_intc_variant);
|
|
+}
|
|
+IRQCHIP_DECLARE(sun6i_a31_r_intc, "allwinner,sun6i-a31-r-intc", sun6i_a31_r_intc_init);
|
|
+
|
|
+static const struct sun6i_r_intc_variant sun50i_h6_r_intc_variant __initconst = {
|
|
+ .first_mux_irq = 21,
|
|
+ .nr_mux_irqs = 16,
|
|
+ .mux_valid = { 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff },
|
|
+};
|
|
+
|
|
+static int __init sun50i_h6_r_intc_init(struct device_node *node,
|
|
+ struct device_node *parent)
|
|
+{
|
|
+ return sun6i_r_intc_init(node, parent, &sun50i_h6_r_intc_variant);
|
|
+}
|
|
+IRQCHIP_DECLARE(sun50i_h6_r_intc, "allwinner,sun50i-h6-r-intc", sun50i_h6_r_intc_init);
|