diff options
Diffstat (limited to 'drivers/irqchip')
-rw-r--r-- | drivers/irqchip/Makefile | 1 | ||||
-rw-r--r-- | drivers/irqchip/irq-vf610-gpc.c | 138 | ||||
-rw-r--r-- | drivers/irqchip/irq-vf610-mscm-ir.c | 126 |
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: |