diff options
Diffstat (limited to 'drivers/irqchip')
-rw-r--r-- | drivers/irqchip/Kconfig | 7 | ||||
-rw-r--r-- | drivers/irqchip/Makefile | 2 | ||||
-rw-r--r-- | drivers/irqchip/irq-gic-common.c | 15 | ||||
-rw-r--r-- | drivers/irqchip/irq-gic.c | 46 | ||||
-rw-r--r-- | drivers/irqchip/irq-hip04.c | 424 | ||||
-rw-r--r-- | drivers/irqchip/irq-keystone.c | 232 | ||||
-rw-r--r-- | drivers/irqchip/irq-renesas-intc-irqpin.c | 85 |
7 files changed, 763 insertions, 48 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index b8632bf9a7f3..c88896478e70 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -113,3 +113,10 @@ config IRQ_CROSSBAR The primary irqchip invokes the crossbar's callback which inturn allocates a free irq and configures the IP. Thus the peripheral interrupts are routed to one of the free irqchip interrupt lines. + +config KEYSTONE_IRQ + tristate "Keystone 2 IRQ controller IP" + depends on ARCH_KEYSTONE + help + Support for Texas Instruments Keystone 2 IRQ controller IP which + is part of the Keystone 2 IPC mechanism diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 73052ba9ca62..fc2e4f77d3db 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_IRQCHIP) += irqchip.o obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2835.o obj-$(CONFIG_ARCH_EXYNOS) += exynos-combiner.o +obj-$(CONFIG_ARCH_HIP04) += irq-hip04.o obj-$(CONFIG_ARCH_MMP) += irq-mmp.o obj-$(CONFIG_ARCH_MVEBU) += irq-armada-370-xp.o obj-$(CONFIG_ARCH_MXS) += irq-mxs.o @@ -34,3 +35,4 @@ obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o obj-$(CONFIG_BRCMSTB_L2_IRQ) += irq-brcmstb-l2.o +obj-$(CONFIG_KEYSTONE_IRQ) += irq-keystone.o diff --git a/drivers/irqchip/irq-gic-common.c b/drivers/irqchip/irq-gic-common.c index 60ac704d2090..61541ff24397 100644 --- a/drivers/irqchip/irq-gic-common.c +++ b/drivers/irqchip/irq-gic-common.c @@ -74,20 +74,22 @@ void __init gic_dist_config(void __iomem *base, int gic_irqs, * Set all global interrupts to be level triggered, active low. */ for (i = 32; i < gic_irqs; i += 16) - writel_relaxed(0, base + GIC_DIST_CONFIG + i / 4); + writel_relaxed(GICD_INT_ACTLOW_LVLTRIG, + base + GIC_DIST_CONFIG + i / 4); /* * Set priority on all global interrupts. */ for (i = 32; i < gic_irqs; i += 4) - writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i); + writel_relaxed(GICD_INT_DEF_PRI_X4, base + GIC_DIST_PRI + i); /* * Disable all interrupts. Leave the PPI and SGIs alone * as they are enabled by redistributor registers. */ for (i = 32; i < gic_irqs; i += 32) - writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i / 8); + writel_relaxed(GICD_INT_EN_CLR_X32, + base + GIC_DIST_ENABLE_CLEAR + i / 8); if (sync_access) sync_access(); @@ -101,14 +103,15 @@ void gic_cpu_config(void __iomem *base, void (*sync_access)(void)) * Deal with the banked PPI and SGI interrupts - disable all * PPI interrupts, ensure all SGI interrupts are enabled. */ - writel_relaxed(0xffff0000, base + GIC_DIST_ENABLE_CLEAR); - writel_relaxed(0x0000ffff, base + GIC_DIST_ENABLE_SET); + writel_relaxed(GICD_INT_EN_CLR_PPI, base + GIC_DIST_ENABLE_CLEAR); + writel_relaxed(GICD_INT_EN_SET_SGI, base + GIC_DIST_ENABLE_SET); /* * Set priority on PPI and SGI interrupts */ for (i = 0; i < 32; i += 4) - writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i * 4 / 4); + writel_relaxed(GICD_INT_DEF_PRI_X4, + base + GIC_DIST_PRI + i * 4 / 4); if (sync_access) sync_access(); diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 480bae864a87..55aa6f62c77c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -297,8 +297,8 @@ static void gic_handle_cascade_irq(unsigned int irq, struct irq_desc *desc) status = readl_relaxed(gic_data_cpu_base(chip_data) + GIC_CPU_INTACK); raw_spin_unlock(&irq_controller_lock); - gic_irq = (status & 0x3ff); - if (gic_irq == 1023) + gic_irq = (status & GICC_IAR_INT_ID_MASK); + if (gic_irq == GICC_INT_SPURIOUS) goto out; cascade_irq = irq_find_mapping(chip_data->domain, gic_irq); @@ -352,6 +352,21 @@ static u8 gic_get_cpumask(struct gic_chip_data *gic) return mask; } +static void gic_cpu_if_up(void) +{ + void __iomem *cpu_base = gic_data_cpu_base(&gic_data[0]); + u32 bypass = 0; + + /* + * Preserve bypass disable bits to be written back later + */ + bypass = readl(cpu_base + GIC_CPU_CTRL); + bypass &= GICC_DIS_BYPASS_MASK; + + writel_relaxed(bypass | GICC_ENABLE, cpu_base + GIC_CPU_CTRL); +} + + static void __init gic_dist_init(struct gic_chip_data *gic) { unsigned int i; @@ -359,7 +374,7 @@ static void __init gic_dist_init(struct gic_chip_data *gic) unsigned int gic_irqs = gic->gic_irqs; void __iomem *base = gic_data_dist_base(gic); - writel_relaxed(0, base + GIC_DIST_CTRL); + writel_relaxed(GICD_DISABLE, base + GIC_DIST_CTRL); /* * Set all global interrupts to this CPU only. @@ -372,7 +387,7 @@ static void __init gic_dist_init(struct gic_chip_data *gic) gic_dist_config(base, gic_irqs, NULL); - writel_relaxed(1, base + GIC_DIST_CTRL); + writel_relaxed(GICD_ENABLE, base + GIC_DIST_CTRL); } static void gic_cpu_init(struct gic_chip_data *gic) @@ -399,14 +414,18 @@ static void gic_cpu_init(struct gic_chip_data *gic) gic_cpu_config(dist_base, NULL); - writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + writel_relaxed(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK); + gic_cpu_if_up(); } void gic_cpu_if_down(void) { void __iomem *cpu_base = gic_data_cpu_base(&gic_data[0]); - writel_relaxed(0, cpu_base + GIC_CPU_CTRL); + u32 val = 0; + + val = readl(cpu_base + GIC_CPU_CTRL); + val &= ~GICC_ENABLE; + writel_relaxed(val, cpu_base + GIC_CPU_CTRL); } #ifdef CONFIG_CPU_PM @@ -466,14 +485,14 @@ static void gic_dist_restore(unsigned int gic_nr) if (!dist_base) return; - writel_relaxed(0, dist_base + GIC_DIST_CTRL); + writel_relaxed(GICD_DISABLE, dist_base + GIC_DIST_CTRL); for (i = 0; i < DIV_ROUND_UP(gic_irqs, 16); i++) writel_relaxed(gic_data[gic_nr].saved_spi_conf[i], dist_base + GIC_DIST_CONFIG + i * 4); for (i = 0; i < DIV_ROUND_UP(gic_irqs, 4); i++) - writel_relaxed(0xa0a0a0a0, + writel_relaxed(GICD_INT_DEF_PRI_X4, dist_base + GIC_DIST_PRI + i * 4); for (i = 0; i < DIV_ROUND_UP(gic_irqs, 4); i++) @@ -484,7 +503,7 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4); - writel_relaxed(1, dist_base + GIC_DIST_CTRL); + writel_relaxed(GICD_ENABLE, dist_base + GIC_DIST_CTRL); } static void gic_cpu_save(unsigned int gic_nr) @@ -538,10 +557,11 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(ptr[i], dist_base + GIC_DIST_CONFIG + i * 4); for (i = 0; i < DIV_ROUND_UP(32, 4); i++) - writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4); + writel_relaxed(GICD_INT_DEF_PRI_X4, + dist_base + GIC_DIST_PRI + i * 4); - writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(GICC_INT_PRI_THRESHOLD, cpu_base + GIC_CPU_PRIMASK); + gic_cpu_if_up(); } static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) diff --git a/drivers/irqchip/irq-hip04.c b/drivers/irqchip/irq-hip04.c new file mode 100644 index 000000000000..9c8f833522e6 --- /dev/null +++ b/drivers/irqchip/irq-hip04.c @@ -0,0 +1,424 @@ +/* + * Hisilicon HiP04 INTC + * + * Copyright (C) 2002-2014 ARM Limited. + * Copyright (c) 2013-2014 Hisilicon Ltd. + * Copyright (c) 2013-2014 Linaro Ltd. + * + * 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. + * + * Interrupt architecture for the HIP04 INTC: + * + * o There is one Interrupt Distributor, which receives interrupts + * from system devices and sends them to the Interrupt Controllers. + * + * o There is one CPU Interface per CPU, which sends interrupts sent + * by the Distributor, and interrupts generated locally, to the + * associated CPU. The base address of the CPU interface is usually + * aliased so that the same address points to different chips depending + * on the CPU it is accessed from. + * + * Note that IRQs 0-31 are special - they are local to each CPU. + * As such, the enable set/clear, pending set/clear and active bit + * registers are banked per-cpu for these sources. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/smp.h> +#include <linux/cpu.h> +#include <linux/cpu_pm.h> +#include <linux/cpumask.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/irqdomain.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/irqchip/arm-gic.h> + +#include <asm/irq.h> +#include <asm/exception.h> +#include <asm/smp_plat.h> + +#include "irq-gic-common.h" +#include "irqchip.h" + +#define HIP04_MAX_IRQS 510 + +struct hip04_irq_data { + void __iomem *dist_base; + void __iomem *cpu_base; + struct irq_domain *domain; + unsigned int nr_irqs; +}; + +static DEFINE_RAW_SPINLOCK(irq_controller_lock); + +/* + * The GIC mapping of CPU interfaces does not necessarily match + * the logical CPU numbering. Let's use a mapping as returned + * by the GIC itself. + */ +#define NR_HIP04_CPU_IF 16 +static u16 hip04_cpu_map[NR_HIP04_CPU_IF] __read_mostly; + +static struct hip04_irq_data hip04_data __read_mostly; + +static inline void __iomem *hip04_dist_base(struct irq_data *d) +{ + struct hip04_irq_data *hip04_data = irq_data_get_irq_chip_data(d); + return hip04_data->dist_base; +} + +static inline void __iomem *hip04_cpu_base(struct irq_data *d) +{ + struct hip04_irq_data *hip04_data = irq_data_get_irq_chip_data(d); + return hip04_data->cpu_base; +} + +static inline unsigned int hip04_irq(struct irq_data *d) +{ + return d->hwirq; +} + +/* + * Routines to acknowledge, disable and enable interrupts + */ +static void hip04_mask_irq(struct irq_data *d) +{ + u32 mask = 1 << (hip04_irq(d) % 32); + + raw_spin_lock(&irq_controller_lock); + writel_relaxed(mask, hip04_dist_base(d) + GIC_DIST_ENABLE_CLEAR + + (hip04_irq(d) / 32) * 4); + raw_spin_unlock(&irq_controller_lock); +} + +static void hip04_unmask_irq(struct irq_data *d) +{ + u32 mask = 1 << (hip04_irq(d) % 32); + + raw_spin_lock(&irq_controller_lock); + writel_relaxed(mask, hip04_dist_base(d) + GIC_DIST_ENABLE_SET + + (hip04_irq(d) / 32) * 4); + raw_spin_unlock(&irq_controller_lock); +} + +static void hip04_eoi_irq(struct irq_data *d) +{ + writel_relaxed(hip04_irq(d), hip04_cpu_base(d) + GIC_CPU_EOI); +} + +static int hip04_irq_set_type(struct irq_data *d, unsigned int type) +{ + void __iomem *base = hip04_dist_base(d); + unsigned int irq = hip04_irq(d); + + /* Interrupt configuration for SGIs can't be changed */ + if (irq < 16) + return -EINVAL; + + if (type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_EDGE_RISING) + return -EINVAL; + + raw_spin_lock(&irq_controller_lock); + + gic_configure_irq(irq, type, base, NULL); + + raw_spin_unlock(&irq_controller_lock); + + return 0; +} + +#ifdef CONFIG_SMP +static int hip04_irq_set_affinity(struct irq_data *d, + const struct cpumask *mask_val, + bool force) +{ + void __iomem *reg; + unsigned int cpu, shift = (hip04_irq(d) % 2) * 16; + u32 val, mask, bit; + + if (!force) + cpu = cpumask_any_and(mask_val, cpu_online_mask); + else + cpu = cpumask_first(mask_val); + + if (cpu >= NR_HIP04_CPU_IF || cpu >= nr_cpu_ids) + return -EINVAL; + + raw_spin_lock(&irq_controller_lock); + reg = hip04_dist_base(d) + GIC_DIST_TARGET + ((hip04_irq(d) * 2) & ~3); + mask = 0xffff << shift; + bit = hip04_cpu_map[cpu] << shift; + val = readl_relaxed(reg) & ~mask; + writel_relaxed(val | bit, reg); + raw_spin_unlock(&irq_controller_lock); + + return IRQ_SET_MASK_OK; +} +#endif + +static void __exception_irq_entry hip04_handle_irq(struct pt_regs *regs) +{ + u32 irqstat, irqnr; + void __iomem *cpu_base = hip04_data.cpu_base; + + do { + irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + + if (likely(irqnr > 15 && irqnr <= HIP04_MAX_IRQS)) { + irqnr = irq_find_mapping(hip04_data.domain, irqnr); + handle_IRQ(irqnr, regs); + continue; + } + if (irqnr < 16) { + writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); +#ifdef CONFIG_SMP + handle_IPI(irqnr, regs); +#endif + continue; + } + break; + } while (1); +} + +static struct irq_chip hip04_irq_chip = { + .name = "HIP04 INTC", + .irq_mask = hip04_mask_irq, + .irq_unmask = hip04_unmask_irq, + .irq_eoi = hip04_eoi_irq, + .irq_set_type = hip04_irq_set_type, +#ifdef CONFIG_SMP + .irq_set_affinity = hip04_irq_set_affinity, +#endif +}; + +static u16 hip04_get_cpumask(struct hip04_irq_data *intc) +{ + void __iomem *base = intc->dist_base; + u32 mask, i; + + for (i = mask = 0; i < 32; i += 2) { + mask = readl_relaxed(base + GIC_DIST_TARGET + i * 2); + mask |= mask >> 16; + if (mask) + break; + } + + if (!mask) + pr_crit("GIC CPU mask not found - kernel will fail to boot.\n"); + + return mask; +} + +static void __init hip04_irq_dist_init(struct hip04_irq_data *intc) +{ + unsigned int i; + u32 cpumask; + unsigned int nr_irqs = intc->nr_irqs; + void __iomem *base = intc->dist_base; + + writel_relaxed(0, base + GIC_DIST_CTRL); + + /* + * Set all global interrupts to this CPU only. + */ + cpumask = hip04_get_cpumask(intc); + cpumask |= cpumask << 16; + for (i = 32; i < nr_irqs; i += 2) + writel_relaxed(cpumask, base + GIC_DIST_TARGET + ((i * 2) & ~3)); + + gic_dist_config(base, nr_irqs, NULL); + + writel_relaxed(1, base + GIC_DIST_CTRL); +} + +static void hip04_irq_cpu_init(struct hip04_irq_data *intc) +{ + void __iomem *dist_base = intc->dist_base; + void __iomem *base = intc->cpu_base; + unsigned int cpu_mask, cpu = smp_processor_id(); + int i; + + /* + * Get what the GIC says our CPU mask is. + */ + BUG_ON(cpu >= NR_HIP04_CPU_IF); + cpu_mask = hip04_get_cpumask(intc); + hip04_cpu_map[cpu] = cpu_mask; + + /* + * Clear our mask from the other map entries in case they're + * still undefined. + */ + for (i = 0; i < NR_HIP04_CPU_IF; i++) + if (i != cpu) + hip04_cpu_map[i] &= ~cpu_mask; + + gic_cpu_config(dist_base, NULL); + + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); + writel_relaxed(1, base + GIC_CPU_CTRL); +} + +#ifdef CONFIG_SMP +static void hip04_raise_softirq(const struct cpumask *mask, unsigned int irq) +{ + int cpu; + unsigned long flags, map = 0; + + raw_spin_lock_irqsave(&irq_controller_lock, flags); + + /* Convert our logical CPU mask into a physical one. */ + for_each_cpu(cpu, mask) + map |= hip04_cpu_map[cpu]; + + /* + * Ensure that stores to Normal memory are visible to the + * other CPUs before they observe us issuing the IPI. + */ + dmb(ishst); + + /* this always happens on GIC0 */ + writel_relaxed(map << 8 | irq, hip04_data.dist_base + GIC_DIST_SOFTINT); + + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); +} +#endif + +static int hip04_irq_domain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hw) +{ + if (hw < 32) { + irq_set_percpu_devid(irq); + irq_set_chip_and_handler(irq, &hip04_irq_chip, + handle_percpu_devid_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN); + } else { + irq_set_chip_and_handler(irq, &hip04_irq_chip, + handle_fasteoi_irq); + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); + } + irq_set_chip_data(irq, d->host_data); + return 0; +} + +static int hip04_irq_domain_xlate(struct irq_domain *d, + struct device_node *controller, + const u32 *intspec, unsigned int intsize, + unsigned long *out_hwirq, + unsigned int *out_type) +{ + unsigned long ret = 0; + + if (d->of_node != controller) + return -EINVAL; + if (intsize < 3) + return -EINVAL; + + /* Get the interrupt number and add 16 to skip over SGIs */ + *out_hwirq = intspec[1] + 16; + + /* For SPIs, we need to add 16 more to get the irq ID number */ + if (!intspec[0]) + *out_hwirq += 16; + + *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; + + return ret; +} + +#ifdef CONFIG_SMP +static int hip04_irq_secondary_init(struct notifier_block *nfb, + unsigned long action, + void *hcpu) +{ + if (action == CPU_STARTING || action == CPU_STARTING_FROZEN) + hip04_irq_cpu_init(&hip04_data); + return NOTIFY_OK; +} + +/* + * Notifier for enabling the INTC CPU interface. Set an arbitrarily high + * priority because the GIC needs to be up before the ARM generic timers. + */ +static struct notifier_block hip04_irq_cpu_notifier = { + .notifier_call = hip04_irq_secondary_init, + .priority = 100, +}; +#endif + +static const struct irq_domain_ops hip04_irq_domain_ops = { + .map = hip04_irq_domain_map, + .xlate = hip04_irq_domain_xlate, +}; + +static int __init +hip04_of_init(struct device_node *node, struct device_node *parent) +{ + irq_hw_number_t hwirq_base = 16; + int nr_irqs, irq_base, i; + + if (WARN_ON(!node)) + return -ENODEV; + + hip04_data.dist_base = of_iomap(node, 0); + WARN(!hip04_data.dist_base, "fail to map hip04 intc dist registers\n"); + + hip04_data.cpu_base = of_iomap(node, 1); + WARN(!hip04_data.cpu_base, "unable to map hip04 intc cpu registers\n"); + + /* + * Initialize the CPU interface map to all CPUs. + * It will be refined as each CPU probes its ID. + */ + for (i = 0; i < NR_HIP04_CPU_IF; i++) + hip04_cpu_map[i] = 0xff; + + /* + * Find out how many interrupts are supported. + * The HIP04 INTC only supports up to 510 interrupt sources. + */ + nr_irqs = readl_relaxed(hip04_data.dist_base + GIC_DIST_CTR) & 0x1f; + nr_irqs = (nr_irqs + 1) * 32; + if (nr_irqs > HIP04_MAX_IRQS) + nr_irqs = HIP04_MAX_IRQS; + hip04_data.nr_irqs = nr_irqs; + + nr_irqs -= hwirq_base; /* calculate # of irqs to allocate */ + + irq_base = irq_alloc_descs(-1, hwirq_base, nr_irqs, numa_node_id()); + if (IS_ERR_VALUE(irq_base)) { + pr_err("failed to allocate IRQ numbers\n"); + return -EINVAL; + } + + hip04_data.domain = irq_domain_add_legacy(node, nr_irqs, irq_base, + hwirq_base, + &hip04_irq_domain_ops, + &hip04_data); + + if (WARN_ON(!hip04_data.domain)) + return -EINVAL; + +#ifdef CONFIG_SMP + set_smp_cross_call(hip04_raise_softirq); + register_cpu_notifier(&hip04_irq_cpu_notifier); +#endif + set_handle_irq(hip04_handle_irq); + + hip04_irq_dist_init(&hip04_data); + hip04_irq_cpu_init(&hip04_data); + + return 0; +} +IRQCHIP_DECLARE(hip04_intc, "hisilicon,hip04-intc", hip04_of_init); diff --git a/drivers/irqchip/irq-keystone.c b/drivers/irqchip/irq-keystone.c new file mode 100644 index 000000000000..608abf9c9283 --- /dev/null +++ b/drivers/irqchip/irq-keystone.c @@ -0,0 +1,232 @@ +/* + * Texas Instruments Keystone IRQ controller IP driver + * + * Copyright (C) 2014 Texas Instruments, Inc. + * Author: Sajesh Kumar Saran <sajesh@ti.com> + * Grygorii Strashko <grygorii.strashko@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/irq.h> +#include <linux/bitops.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/irqdomain.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/mfd/syscon.h> +#include <linux/regmap.h> +#include "irqchip.h" + + +/* The source ID bits start from 4 to 31 (total 28 bits)*/ +#define BIT_OFS 4 +#define KEYSTONE_N_IRQ (32 - BIT_OFS) + +struct keystone_irq_device { + struct device *dev; + struct irq_chip chip; + u32 mask; + int irq; + struct irq_domain *irqd; + struct regmap *devctrl_regs; + u32 devctrl_offset; +}; + +static inline u32 keystone_irq_readl(struct keystone_irq_device *kirq) +{ + int ret; + u32 val = 0; + + ret = regmap_read(kirq->devctrl_regs, kirq->devctrl_offset, &val); + if (ret < 0) + dev_dbg(kirq->dev, "irq read failed ret(%d)\n", ret); + return val; +} + +static inline void +keystone_irq_writel(struct keystone_irq_device *kirq, u32 value) +{ + int ret; + + ret = regmap_write(kirq->devctrl_regs, kirq->devctrl_offset, value); + if (ret < 0) + dev_dbg(kirq->dev, "irq write failed ret(%d)\n", ret); +} + +static void keystone_irq_setmask(struct irq_data *d) +{ + struct keystone_irq_device *kirq = irq_data_get_irq_chip_data(d); + + kirq->mask |= BIT(d->hwirq); + dev_dbg(kirq->dev, "mask %lu [%x]\n", d->hwirq, kirq->mask); +} + +static void keystone_irq_unmask(struct irq_data *d) +{ + struct keystone_irq_device *kirq = irq_data_get_irq_chip_data(d); + + kirq->mask &= ~BIT(d->hwirq); + dev_dbg(kirq->dev, "unmask %lu [%x]\n", d->hwirq, kirq->mask); +} + +static void keystone_irq_ack(struct irq_data *d) +{ + /* nothing to do here */ +} + +static void keystone_irq_handler(unsigned irq, struct irq_desc *desc) +{ + struct keystone_irq_device *kirq = irq_desc_get_handler_data(desc); + unsigned long pending; + int src, virq; + + dev_dbg(kirq->dev, "start irq %d\n", irq); + + chained_irq_enter(irq_desc_get_chip(desc), desc); + + pending = keystone_irq_readl(kirq); + keystone_irq_writel(kirq, pending); + + dev_dbg(kirq->dev, "pending 0x%lx, mask 0x%x\n", pending, kirq->mask); + + pending = (pending >> BIT_OFS) & ~kirq->mask; + + dev_dbg(kirq->dev, "pending after mask 0x%lx\n", pending); + + for (src = 0; src < KEYSTONE_N_IRQ; src++) { + if (BIT(src) & pending) { + virq = irq_find_mapping(kirq->irqd, src); + dev_dbg(kirq->dev, "dispatch bit %d, virq %d\n", + src, virq); + if (!virq) + dev_warn(kirq->dev, "sporious irq detected hwirq %d, virq %d\n", + src, virq); + generic_handle_irq(virq); + } + } + + chained_irq_exit(irq_desc_get_chip(desc), desc); + + dev_dbg(kirq->dev, "end irq %d\n", irq); +} + +static int keystone_irq_map(struct irq_domain *h, unsigned int virq, + irq_hw_number_t hw) +{ + struct keystone_irq_device *kirq = h->host_data; + + irq_set_chip_data(virq, kirq); + irq_set_chip_and_handler(virq, &kirq->chip, handle_level_irq); + set_irq_flags(virq, IRQF_VALID | IRQF_PROBE); + return 0; +} + +static struct irq_domain_ops keystone_irq_ops = { + .map = keystone_irq_map, + .xlate = irq_domain_xlate_onecell, +}; + +static int keystone_irq_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct keystone_irq_device *kirq; + int ret; + + if (np == NULL) + return -EINVAL; + + kirq = devm_kzalloc(dev, sizeof(*kirq), GFP_KERNEL); + if (!kirq) + return -ENOMEM; + + kirq->devctrl_regs = + syscon_regmap_lookup_by_phandle(np, "ti,syscon-dev"); + if (IS_ERR(kirq->devctrl_regs)) + return PTR_ERR(kirq->devctrl_regs); + + ret = of_property_read_u32_index(np, "ti,syscon-dev", 1, + &kirq->devctrl_offset); + if (ret) { + dev_err(dev, "couldn't read the devctrl_offset offset!\n"); + return ret; + } + + kirq->irq = platform_get_irq(pdev, 0); + if (kirq->irq < 0) { + dev_err(dev, "no irq resource %d\n", kirq->irq); + return kirq->irq; + } + + kirq->dev = dev; + kirq->mask = ~0x0; + kirq->chip.name = "keystone-irq"; + kirq->chip.irq_ack = keystone_irq_ack; + kirq->chip.irq_mask = keystone_irq_setmask; + kirq->chip.irq_unmask = keystone_irq_unmask; + + kirq->irqd = irq_domain_add_linear(np, KEYSTONE_N_IRQ, + &keystone_irq_ops, kirq); + if (!kirq->irqd) { + dev_err(dev, "IRQ domain registration failed\n"); + return -ENODEV; + } + + platform_set_drvdata(pdev, kirq); + + irq_set_chained_handler(kirq->irq, keystone_irq_handler); + irq_set_handler_data(kirq->irq, kirq); + + /* clear all source bits */ + keystone_irq_writel(kirq, ~0x0); + + dev_info(dev, "irqchip registered, nr_irqs %u\n", KEYSTONE_N_IRQ); + + return 0; +} + +static int keystone_irq_remove(struct platform_device *pdev) +{ + struct keystone_irq_device *kirq = platform_get_drvdata(pdev); + int hwirq; + + for (hwirq = 0; hwirq < KEYSTONE_N_IRQ; hwirq++) + irq_dispose_mapping(irq_find_mapping(kirq->irqd, hwirq)); + + irq_domain_remove(kirq->irqd); + return 0; +} + +static const struct of_device_id keystone_irq_dt_ids[] = { + { .compatible = "ti,keystone-irq", }, + {}, +}; +MODULE_DEVICE_TABLE(of, keystone_irq_dt_ids); + +static struct platform_driver keystone_irq_device_driver = { + .probe = keystone_irq_probe, + .remove = keystone_irq_remove, + .driver = { + .name = "keystone_irq", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(keystone_irq_dt_ids), + } +}; + +module_platform_driver(keystone_irq_device_driver); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_AUTHOR("Sajesh Kumar Saran"); +MODULE_AUTHOR("Grygorii Strashko"); +MODULE_DESCRIPTION("Keystone IRQ chip"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/irqchip/irq-renesas-intc-irqpin.c b/drivers/irqchip/irq-renesas-intc-irqpin.c index 3ee78f02e5d7..542e850f4946 100644 --- a/drivers/irqchip/irq-renesas-intc-irqpin.c +++ b/drivers/irqchip/irq-renesas-intc-irqpin.c @@ -17,6 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <linux/clk.h> #include <linux/init.h> #include <linux/of.h> #include <linux/platform_device.h> @@ -30,6 +31,7 @@ #include <linux/slab.h> #include <linux/module.h> #include <linux/platform_data/irq-renesas-intc-irqpin.h> +#include <linux/pm_runtime.h> #define INTC_IRQPIN_MAX 8 /* maximum 8 interrupts per driver instance */ @@ -75,6 +77,7 @@ struct intc_irqpin_priv { struct platform_device *pdev; struct irq_chip irq_chip; struct irq_domain *irq_domain; + struct clk *clk; bool shared_irqs; u8 shared_irq_mask; }; @@ -270,6 +273,21 @@ static int intc_irqpin_irq_set_type(struct irq_data *d, unsigned int type) value ^ INTC_IRQ_SENSE_VALID); } +static int intc_irqpin_irq_set_wake(struct irq_data *d, unsigned int on) +{ + struct intc_irqpin_priv *p = irq_data_get_irq_chip_data(d); + + if (!p->clk) + return 0; + + if (on) + clk_enable(p->clk); + else + clk_disable(p->clk); + + return 0; +} + static irqreturn_t intc_irqpin_irq_handler(int irq, void *dev_id) { struct intc_irqpin_irq *i = dev_id; @@ -329,7 +347,8 @@ static struct irq_domain_ops intc_irqpin_irq_domain_ops = { static int intc_irqpin_probe(struct platform_device *pdev) { - struct renesas_intc_irqpin_config *pdata = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct renesas_intc_irqpin_config *pdata = dev->platform_data; struct intc_irqpin_priv *p; struct intc_irqpin_iomem *i; struct resource *io[INTC_IRQPIN_REG_NR]; @@ -337,25 +356,24 @@ static int intc_irqpin_probe(struct platform_device *pdev) struct irq_chip *irq_chip; void (*enable_fn)(struct irq_data *d); void (*disable_fn)(struct irq_data *d); - const char *name = dev_name(&pdev->dev); + const char *name = dev_name(dev); int ref_irq; int ret; int k; - p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); + p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL); if (!p) { - dev_err(&pdev->dev, "failed to allocate driver data\n"); - ret = -ENOMEM; - goto err0; + dev_err(dev, "failed to allocate driver data\n"); + return -ENOMEM; } /* deal with driver instance configuration */ if (pdata) { memcpy(&p->config, pdata, sizeof(*pdata)); } else { - of_property_read_u32(pdev->dev.of_node, "sense-bitfield-width", + of_property_read_u32(dev->of_node, "sense-bitfield-width", &p->config.sense_bitfield_width); - p->config.control_parent = of_property_read_bool(pdev->dev.of_node, + p->config.control_parent = of_property_read_bool(dev->of_node, "control-parent"); } if (!p->config.sense_bitfield_width) @@ -364,11 +382,20 @@ static int intc_irqpin_probe(struct platform_device *pdev) p->pdev = pdev; platform_set_drvdata(pdev, p); + p->clk = devm_clk_get(dev, NULL); + if (IS_ERR(p->clk)) { + dev_warn(dev, "unable to get clock\n"); + p->clk = NULL; + } + + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + /* get hold of manadatory IOMEM */ for (k = 0; k < INTC_IRQPIN_REG_NR; k++) { io[k] = platform_get_resource(pdev, IORESOURCE_MEM, k); if (!io[k]) { - dev_err(&pdev->dev, "not enough IOMEM resources\n"); + dev_err(dev, "not enough IOMEM resources\n"); ret = -EINVAL; goto err0; } @@ -386,7 +413,7 @@ static int intc_irqpin_probe(struct platform_device *pdev) p->number_of_irqs = k; if (p->number_of_irqs < 1) { - dev_err(&pdev->dev, "not enough IRQ resources\n"); + dev_err(dev, "not enough IRQ resources\n"); ret = -EINVAL; goto err0; } @@ -407,15 +434,15 @@ static int intc_irqpin_probe(struct platform_device *pdev) i->write = intc_irqpin_write32; break; default: - dev_err(&pdev->dev, "IOMEM size mismatch\n"); + dev_err(dev, "IOMEM size mismatch\n"); ret = -EINVAL; goto err0; } - i->iomem = devm_ioremap_nocache(&pdev->dev, io[k]->start, + i->iomem = devm_ioremap_nocache(dev, io[k]->start, resource_size(io[k])); if (!i->iomem) { - dev_err(&pdev->dev, "failed to remap IOMEM\n"); + dev_err(dev, "failed to remap IOMEM\n"); ret = -ENXIO; goto err0; } @@ -454,39 +481,36 @@ static int intc_irqpin_probe(struct platform_device *pdev) irq_chip->name = name; irq_chip->irq_mask = disable_fn; irq_chip->irq_unmask = enable_fn; - irq_chip->irq_enable = enable_fn; - irq_chip->irq_disable = disable_fn; irq_chip->irq_set_type = intc_irqpin_irq_set_type; - irq_chip->flags = IRQCHIP_SKIP_SET_WAKE; + irq_chip->irq_set_wake = intc_irqpin_irq_set_wake; + irq_chip->flags = IRQCHIP_MASK_ON_SUSPEND; - p->irq_domain = irq_domain_add_simple(pdev->dev.of_node, + p->irq_domain = irq_domain_add_simple(dev->of_node, p->number_of_irqs, p->config.irq_base, &intc_irqpin_irq_domain_ops, p); if (!p->irq_domain) { ret = -ENXIO; - dev_err(&pdev->dev, "cannot initialize irq domain\n"); + dev_err(dev, "cannot initialize irq domain\n"); goto err0; } if (p->shared_irqs) { /* request one shared interrupt */ - if (devm_request_irq(&pdev->dev, p->irq[0].requested_irq, + if (devm_request_irq(dev, p->irq[0].requested_irq, intc_irqpin_shared_irq_handler, IRQF_SHARED, name, p)) { - dev_err(&pdev->dev, "failed to request low IRQ\n"); + dev_err(dev, "failed to request low IRQ\n"); ret = -ENOENT; goto err1; } } else { /* request interrupts one by one */ for (k = 0; k < p->number_of_irqs; k++) { - if (devm_request_irq(&pdev->dev, - p->irq[k].requested_irq, - intc_irqpin_irq_handler, - 0, name, &p->irq[k])) { - dev_err(&pdev->dev, - "failed to request low IRQ\n"); + if (devm_request_irq(dev, p->irq[k].requested_irq, + intc_irqpin_irq_handler, 0, name, + &p->irq[k])) { + dev_err(dev, "failed to request low IRQ\n"); ret = -ENOENT; goto err1; } @@ -497,12 +521,12 @@ static int intc_irqpin_probe(struct platform_device *pdev) for (k = 0; k < p->number_of_irqs; k++) intc_irqpin_mask_unmask_prio(p, k, 0); - dev_info(&pdev->dev, "driving %d irqs\n", p->number_of_irqs); + dev_info(dev, "driving %d irqs\n", p->number_of_irqs); /* warn in case of mismatch if irq base is specified */ if (p->config.irq_base) { if (p->config.irq_base != p->irq[0].domain_irq) - dev_warn(&pdev->dev, "irq base mismatch (%d/%d)\n", + dev_warn(dev, "irq base mismatch (%d/%d)\n", p->config.irq_base, p->irq[0].domain_irq); } @@ -511,6 +535,8 @@ static int intc_irqpin_probe(struct platform_device *pdev) err1: irq_domain_remove(p->irq_domain); err0: + pm_runtime_put(dev); + pm_runtime_disable(dev); return ret; } @@ -519,7 +545,8 @@ static int intc_irqpin_remove(struct platform_device *pdev) struct intc_irqpin_priv *p = platform_get_drvdata(pdev); irq_domain_remove(p->irq_domain); - + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); return 0; } |