summaryrefslogtreecommitdiff
path: root/drivers/irqchip
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/irqchip')
-rw-r--r--drivers/irqchip/Makefile1
-rw-r--r--drivers/irqchip/irq-vf610-gpc.c138
-rw-r--r--drivers/irqchip/irq-vf610-mscm-ir.c126
3 files changed, 257 insertions, 8 deletions
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 177f78f6e6d6..b0a94c3fc75e 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_TB10X_IRQC) += irq-tb10x.o
obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o
obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o
obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o
+obj-$(CONFIG_SOC_VF610) += irq-vf610-gpc.o
obj-$(CONFIG_SOC_VF610) += irq-vf610-mscm-ir.o
obj-$(CONFIG_BCM7038_L1_IRQ) += irq-bcm7038-l1.o
obj-$(CONFIG_BCM7120_L2_IRQ) += irq-bcm7120-l2.o
diff --git a/drivers/irqchip/irq-vf610-gpc.c b/drivers/irqchip/irq-vf610-gpc.c
new file mode 100644
index 000000000000..105a6606e425
--- /dev/null
+++ b/drivers/irqchip/irq-vf610-gpc.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2015 Toradex AG
+ * Author: Stefan Agner <stefan@agner.ch>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *
+ * The GPC (General Power Controller) irqchip driver takes care of the
+ * interrupt wakeup functionality.
+ *
+ * o All peripheral interrupts of the Vybrid SoC can be used as wakeup
+ * source from STOP mode. In LPSTOP mode however, the GPC is unpowered
+ * too and cannot be used to as a wakeup source. The WKPU (Wakeup Unit)
+ * is responsible for wakeups from LPSTOP modes.
+ */
+
+#include <linux/cpu_pm.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/syscon.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+
+#define IMR_NUM 4
+#define VF610_GPC_IMR1 0x044
+#define VF610_GPC_MAX_IRQS (IMR_NUM * 32)
+
+static void __iomem *gpc_base;
+
+static int vf610_gpc_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+ unsigned int idx = d->hwirq / 32;
+ void __iomem *reg_imr = gpc_base + VF610_GPC_IMR1 + (idx * 4);
+ u32 mask = 1 << d->hwirq % 32;
+
+ if (on)
+ writel_relaxed(readl_relaxed(reg_imr) & ~mask, reg_imr);
+ else
+ writel_relaxed(readl_relaxed(reg_imr) | mask, reg_imr);
+
+ /*
+ * Do *not* call into the parent, as the GIC doesn't have any
+ * wake-up facility...
+ */
+ return 0;
+}
+
+static struct irq_chip vf610_gpc_chip = {
+ .name = "vf610-gpc",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_enable = irq_chip_enable_parent,
+ .irq_disable = irq_chip_disable_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_retrigger = irq_chip_retrigger_hierarchy,
+ .irq_set_wake = vf610_gpc_irq_set_wake,
+};
+
+static int vf610_gpc_domain_alloc(struct irq_domain *domain, unsigned int virq,
+ unsigned int nr_irqs, void *arg)
+{
+ int i;
+ irq_hw_number_t hwirq;
+ struct irq_fwspec *fwspec = arg;
+ struct irq_fwspec parent_fwspec;
+
+ if (!irq_domain_get_of_node(domain->parent))
+ return -EINVAL;
+
+ if (fwspec->param_count != 2)
+ return -EINVAL;
+
+ hwirq = fwspec->param[0];
+ for (i = 0; i < nr_irqs; i++)
+ irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+ &vf610_gpc_chip, NULL);
+
+ parent_fwspec = *fwspec;
+ parent_fwspec.fwnode = domain->parent->fwnode;
+ return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
+ &parent_fwspec);
+}
+
+static int vf610_gpc_domain_translate(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ unsigned long *hwirq,
+ unsigned int *type)
+{
+ if (WARN_ON(fwspec->param_count < 2))
+ return -EINVAL;
+ *hwirq = fwspec->param[0];
+ *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
+ return 0;
+}
+
+static const struct irq_domain_ops gpc_irq_domain_ops = {
+ .translate = vf610_gpc_domain_translate,
+ .alloc = vf610_gpc_domain_alloc,
+ .free = irq_domain_free_irqs_common,
+};
+
+static int __init vf610_gpc_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct irq_domain *domain, *domain_parent;
+ int i;
+
+ domain_parent = irq_find_host(parent);
+ if (!domain_parent) {
+ pr_err("vf610_gpc: interrupt-parent not found\n");
+ return -EINVAL;
+ }
+
+ gpc_base = of_io_request_and_map(node, 0, "gpc");
+ if (WARN_ON(!gpc_base))
+ return -ENOMEM;
+
+ domain = irq_domain_add_hierarchy(domain_parent, 0, VF610_GPC_MAX_IRQS,
+ node, &gpc_irq_domain_ops, NULL);
+ if (!domain) {
+ iounmap(gpc_base);
+ return -ENOMEM;
+ }
+
+ /* Initially mask all interrupts for wakeup */
+ for (i = 0; i < IMR_NUM; i++)
+ writel_relaxed(~0, gpc_base + VF610_GPC_IMR1 + i * 4);
+
+ return 0;
+}
+IRQCHIP_DECLARE(vf610_gpc, "fsl,vf610-gpc", vf610_gpc_of_init);
diff --git a/drivers/irqchip/irq-vf610-mscm-ir.c b/drivers/irqchip/irq-vf610-mscm-ir.c
index 56b5e3cb9de2..a36f2ea74185 100644
--- a/drivers/irqchip/irq-vf610-mscm-ir.c
+++ b/drivers/irqchip/irq-vf610-mscm-ir.c
@@ -23,8 +23,10 @@
* variants of Vybrid.
*/
+#include <linux/bitops.h>
#include <linux/cpu_pm.h>
#include <linux/io.h>
+#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqdomain.h>
@@ -32,25 +34,48 @@
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <linux/of.h>
#include <linux/of_address.h>
+#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/regmap.h>
#define MSCM_CPxNUM 0x4
+#define MSCM_IRCP0IR 0x0
+#define MSCM_IRCP1IR 0x4
+#define MSCM_IRCPnIR(n) ((n) * 0x4 + MSCM_IRCP0IR)
+#define MSCM_IRCPnIR_INT(n) (0x1 << (n))
+#define MSCM_IRCPGIR 0x20
+
+#define MSCM_INTID_MASK 0x3
+#define MSCM_INTID(n) ((n) & MSCM_INTID_MASK)
+#define MSCM_CPUTL(n) (((n) == 0 ? 1 : 2) << 16)
+
#define MSCM_IRSPRC(n) (0x80 + 2 * (n))
#define MSCM_IRSPRC_CPEN_MASK 0x3
#define MSCM_IRSPRC_NUM 112
+#define MSCM_CPU2CPU_NUM 4
+
struct vf610_mscm_ir_chip_data {
void __iomem *mscm_ir_base;
- u16 cpu_mask;
+ u16 cpu_id;
u16 saved_irsprc[MSCM_IRSPRC_NUM];
bool is_nvic;
+ struct device_node *cpu2cpu_node;
+};
+
+struct mscm_cpu2cpu_irq_data {
+ int intid;
+ int irq;
+ irq_handler_t handler;
+ void *priv;
};
static struct vf610_mscm_ir_chip_data *mscm_ir_data;
+static struct mscm_cpu2cpu_irq_data cpu2cpu_irq_data[MSCM_CPU2CPU_NUM];
+
static inline void vf610_mscm_ir_save(struct vf610_mscm_ir_chip_data *data)
{
int i;
@@ -94,11 +119,8 @@ static void vf610_mscm_ir_enable(struct irq_data *data)
u16 irsprc;
irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
- irsprc &= MSCM_IRSPRC_CPEN_MASK;
- WARN_ON(irsprc & ~chip_data->cpu_mask);
-
- writew_relaxed(chip_data->cpu_mask,
+ writew_relaxed(irsprc | BIT(chip_data->cpu_id),
chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
irq_chip_enable_parent(data);
@@ -108,8 +130,11 @@ static void vf610_mscm_ir_disable(struct irq_data *data)
{
irq_hw_number_t hwirq = data->hwirq;
struct vf610_mscm_ir_chip_data *chip_data = data->chip_data;
+ u16 irsprc;
- writew_relaxed(0x0, chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
+ irsprc = readw_relaxed(chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
+ writew_relaxed(irsprc & ~BIT(chip_data->cpu_id),
+ chip_data->mscm_ir_base + MSCM_IRSPRC(hwirq));
irq_chip_disable_parent(data);
}
@@ -179,6 +204,90 @@ static const struct irq_domain_ops mscm_irq_domain_ops = {
.free = irq_domain_free_irqs_common,
};
+static irqreturn_t mscm_cpu2cpu_irq_handler(int irq, void *dev_id)
+{
+ irqreturn_t ret;
+ struct mscm_cpu2cpu_irq_data *data = dev_id;
+ void __iomem *mscm_base = mscm_ir_data->mscm_ir_base;
+ int cpu_id = mscm_ir_data->cpu_id;
+
+
+ ret = data->handler(data->intid, data->priv);
+ if (ret == IRQ_HANDLED)
+ writel(MSCM_IRCPnIR_INT(data->intid), mscm_base + MSCM_IRCPnIR(cpu_id));
+
+ return ret;
+}
+
+int mscm_request_cpu2cpu_irq(unsigned int intid, irq_handler_t handler,
+ const char *name, void *priv)
+{
+ int irq;
+ struct mscm_cpu2cpu_irq_data *data;
+
+ if (intid >= MSCM_CPU2CPU_NUM)
+ return -EINVAL;
+
+ irq = of_irq_get(mscm_ir_data->cpu2cpu_node, intid);
+ if (irq < 0)
+ return irq;
+
+ data = &cpu2cpu_irq_data[intid];
+ data->intid = intid;
+ data->irq = irq;
+ data->handler = handler;
+ data->priv = priv;
+
+ return request_irq(irq, mscm_cpu2cpu_irq_handler, 0, name, data);
+}
+EXPORT_SYMBOL(mscm_request_cpu2cpu_irq);
+
+void mscm_free_cpu2cpu_irq(unsigned int intid, void *priv)
+{
+ struct mscm_cpu2cpu_irq_data *data;
+
+ if (intid >= MSCM_CPU2CPU_NUM)
+ return;
+
+ data = &cpu2cpu_irq_data[intid];
+
+ if (data->irq < 0)
+ return;
+
+ free_irq(data->irq, data);
+}
+EXPORT_SYMBOL(mscm_free_cpu2cpu_irq);
+
+void mscm_trigger_cpu2cpu_irq(unsigned int intid, int cpuid)
+{
+ void __iomem *mscm_base = mscm_ir_data->mscm_ir_base;
+
+ writel(MSCM_INTID(intid) | MSCM_CPUTL(cpuid), mscm_base + MSCM_IRCPGIR);
+}
+EXPORT_SYMBOL(mscm_trigger_cpu2cpu_irq);
+
+void mscm_enable_cpu2cpu_irq(unsigned int intid)
+{
+ struct mscm_cpu2cpu_irq_data *data = &cpu2cpu_irq_data[intid];
+
+ if (intid >= MSCM_CPU2CPU_NUM)
+ return;
+
+ enable_irq(data->irq);
+}
+EXPORT_SYMBOL(mscm_enable_cpu2cpu_irq);
+
+void mscm_disable_cpu2cpu_irq(unsigned int intid)
+{
+ struct mscm_cpu2cpu_irq_data *data = &cpu2cpu_irq_data[intid];
+
+ if (intid >= MSCM_CPU2CPU_NUM)
+ return;
+
+ disable_irq(data->irq);
+}
+EXPORT_SYMBOL(mscm_disable_cpu2cpu_irq);
+
static int __init vf610_mscm_ir_of_init(struct device_node *node,
struct device_node *parent)
{
@@ -210,8 +319,7 @@ static int __init vf610_mscm_ir_of_init(struct device_node *node,
goto out_unmap;
}
- regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid);
- mscm_ir_data->cpu_mask = 0x1 << cpuid;
+ mscm_ir_data->cpu_id = regmap_read(mscm_cp_regmap, MSCM_CPxNUM, &cpuid);
domain = irq_domain_add_hierarchy(domain_parent, 0,
MSCM_IRSPRC_NUM, node,
@@ -227,6 +335,8 @@ static int __init vf610_mscm_ir_of_init(struct device_node *node,
cpu_pm_register_notifier(&mscm_ir_notifier_block);
+ mscm_ir_data->cpu2cpu_node = of_get_child_by_name(node, "cpu2cpu");
+
return 0;
out_unmap: