@@ -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_state(struct irq_data *data,
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_chip = {
.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(struct irq_domain *domain,
&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_r_intc_domain_ops = {
.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(struct device_node *node,
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(struct device_node *node,
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);