diff options
Diffstat (limited to 'drivers/irqchip')
-rw-r--r-- | drivers/irqchip/Kconfig | 13 | ||||
-rw-r--r-- | drivers/irqchip/Makefile | 3 | ||||
-rw-r--r-- | drivers/irqchip/irq-armada-370-xp.c | 6 | ||||
-rw-r--r-- | drivers/irqchip/irq-bcm2836.c | 2 | ||||
-rw-r--r-- | drivers/irqchip/irq-bcm7038-l1.c | 26 | ||||
-rw-r--r-- | drivers/irqchip/irq-gemini.c | 185 | ||||
-rw-r--r-- | drivers/irqchip/irq-gic-v3-its.c | 141 | ||||
-rw-r--r-- | drivers/irqchip/irq-gic-v3.c | 19 | ||||
-rw-r--r-- | drivers/irqchip/irq-gic.c | 2 | ||||
-rw-r--r-- | drivers/irqchip/irq-hip04.c | 2 | ||||
-rw-r--r-- | drivers/irqchip/irq-keystone.c | 28 | ||||
-rw-r--r-- | drivers/irqchip/irq-mips-gic.c | 45 | ||||
-rw-r--r-- | drivers/irqchip/irq-mxs.c | 4 | ||||
-rw-r--r-- | drivers/irqchip/irq-st.c | 2 | ||||
-rw-r--r-- | drivers/irqchip/irq-xilinx-intc.c | 241 | ||||
-rw-r--r-- | drivers/irqchip/qcom-irq-combiner.c | 296 |
16 files changed, 908 insertions, 107 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index bc0af3307bbf..125528f39e92 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -211,6 +211,10 @@ config XTENSA_MX bool select IRQ_DOMAIN +config XILINX_INTC + bool + select IRQ_DOMAIN + config IRQ_CROSSBAR bool help @@ -279,3 +283,12 @@ config EZNPS_GIC config STM32_EXTI bool select IRQ_DOMAIN + +config QCOM_IRQ_COMBINER + bool "QCOM IRQ combiner support" + depends on ARCH_QCOM && ACPI + select IRQ_DOMAIN + select IRQ_DOMAIN_HIERARCHY + help + Say yes here to add support for the IRQ combiner devices embedded + in Qualcomm Technologies chips. diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index e4dbfc85abdb..152bc40b6762 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_ATH79) += irq-ath79-misc.o obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2835.o obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2836.o obj-$(CONFIG_ARCH_EXYNOS) += exynos-combiner.o +obj-$(CONFIG_ARCH_GEMINI) += irq-gemini.o obj-$(CONFIG_ARCH_HIP04) += irq-hip04.o obj-$(CONFIG_ARCH_LPC32XX) += irq-lpc32xx.o obj-$(CONFIG_ARCH_MMP) += irq-mmp.o @@ -52,6 +53,7 @@ obj-$(CONFIG_TB10X_IRQC) += irq-tb10x.o obj-$(CONFIG_TS4800_IRQ) += irq-ts4800.o obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o +obj-$(CONFIG_XILINX_INTC) += irq-xilinx-intc.o obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o obj-$(CONFIG_SOC_VF610) += irq-vf610-mscm-ir.o obj-$(CONFIG_BCM6345_L1_IRQ) += irq-bcm6345-l1.o @@ -74,3 +76,4 @@ obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o +obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o diff --git a/drivers/irqchip/irq-armada-370-xp.c b/drivers/irqchip/irq-armada-370-xp.c index 8bcee65a0b8c..eb0d4d41b156 100644 --- a/drivers/irqchip/irq-armada-370-xp.c +++ b/drivers/irqchip/irq-armada-370-xp.c @@ -578,13 +578,13 @@ static int __init armada_370_xp_mpic_of_init(struct device_node *node, #ifdef CONFIG_SMP set_smp_cross_call(armada_mpic_send_doorbell); cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_ARMADA_XP_STARTING, - "AP_IRQ_ARMADA_XP_STARTING", + "irqchip/armada/ipi:starting", armada_xp_mpic_starting_cpu, NULL); #endif } else { #ifdef CONFIG_SMP - cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_ARMADA_CASC_STARTING, - "AP_IRQ_ARMADA_CASC_STARTING", + cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_ARMADA_XP_STARTING, + "irqchip/armada/cascade:starting", mpic_cascaded_starting_cpu, NULL); #endif irq_set_chained_handler(parent_irq, diff --git a/drivers/irqchip/irq-bcm2836.c b/drivers/irqchip/irq-bcm2836.c index d96b2c947e74..e7463e3c0814 100644 --- a/drivers/irqchip/irq-bcm2836.c +++ b/drivers/irqchip/irq-bcm2836.c @@ -245,7 +245,7 @@ bcm2836_arm_irqchip_smp_init(void) #ifdef CONFIG_SMP /* Unmask IPIs to the boot CPU. */ cpuhp_setup_state(CPUHP_AP_IRQ_BCM2836_STARTING, - "AP_IRQ_BCM2836_STARTING", bcm2836_cpu_starting, + "irqchip/bcm2836:starting", bcm2836_cpu_starting, bcm2836_cpu_dying); set_smp_cross_call(bcm2836_arm_irqchip_send_ipi); diff --git a/drivers/irqchip/irq-bcm7038-l1.c b/drivers/irqchip/irq-bcm7038-l1.c index 353c54986211..c2662a1bfdd3 100644 --- a/drivers/irqchip/irq-bcm7038-l1.c +++ b/drivers/irqchip/irq-bcm7038-l1.c @@ -215,6 +215,31 @@ static int bcm7038_l1_set_affinity(struct irq_data *d, return 0; } +static void bcm7038_l1_cpu_offline(struct irq_data *d) +{ + struct cpumask *mask = irq_data_get_affinity_mask(d); + int cpu = smp_processor_id(); + cpumask_t new_affinity; + + /* This CPU was not on the affinity mask */ + if (!cpumask_test_cpu(cpu, mask)) + return; + + if (cpumask_weight(mask) > 1) { + /* + * Multiple CPU affinity, remove this CPU from the affinity + * mask + */ + cpumask_copy(&new_affinity, mask); + cpumask_clear_cpu(cpu, &new_affinity); + } else { + /* Only CPU, put on the lowest online CPU */ + cpumask_clear(&new_affinity); + cpumask_set_cpu(cpumask_first(cpu_online_mask), &new_affinity); + } + irq_set_affinity_locked(d, &new_affinity, false); +} + static int __init bcm7038_l1_init_one(struct device_node *dn, unsigned int idx, struct bcm7038_l1_chip *intc) @@ -266,6 +291,7 @@ static struct irq_chip bcm7038_l1_irq_chip = { .irq_mask = bcm7038_l1_mask, .irq_unmask = bcm7038_l1_unmask, .irq_set_affinity = bcm7038_l1_set_affinity, + .irq_cpu_offline = bcm7038_l1_cpu_offline, }; static int bcm7038_l1_map(struct irq_domain *d, unsigned int virq, diff --git a/drivers/irqchip/irq-gemini.c b/drivers/irqchip/irq-gemini.c new file mode 100644 index 000000000000..495224c743ee --- /dev/null +++ b/drivers/irqchip/irq-gemini.c @@ -0,0 +1,185 @@ +/* + * irqchip for the Cortina Systems Gemini Copyright (C) 2017 Linus + * Walleij <linus.walleij@linaro.org> + * + * Based on arch/arm/mach-gemini/irq.c + * Copyright (C) 2001-2006 Storlink, Corp. + * Copyright (C) 2008-2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt> + */ +#include <linux/bitops.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/irqchip.h> +#include <linux/irqchip/versatile-fpga.h> +#include <linux/irqdomain.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/cpu.h> + +#include <asm/exception.h> +#include <asm/mach/irq.h> + +#define GEMINI_NUM_IRQS 32 + +#define GEMINI_IRQ_SOURCE(base_addr) (base_addr + 0x00) +#define GEMINI_IRQ_MASK(base_addr) (base_addr + 0x04) +#define GEMINI_IRQ_CLEAR(base_addr) (base_addr + 0x08) +#define GEMINI_IRQ_MODE(base_addr) (base_addr + 0x0C) +#define GEMINI_IRQ_POLARITY(base_addr) (base_addr + 0x10) +#define GEMINI_IRQ_STATUS(base_addr) (base_addr + 0x14) +#define GEMINI_FIQ_SOURCE(base_addr) (base_addr + 0x20) +#define GEMINI_FIQ_MASK(base_addr) (base_addr + 0x24) +#define GEMINI_FIQ_CLEAR(base_addr) (base_addr + 0x28) +#define GEMINI_FIQ_MODE(base_addr) (base_addr + 0x2C) +#define GEMINI_FIQ_POLARITY(base_addr) (base_addr + 0x30) +#define GEMINI_FIQ_STATUS(base_addr) (base_addr + 0x34) + +/** + * struct gemini_irq_data - irq data container for the Gemini IRQ controller + * @base: memory offset in virtual memory + * @chip: chip container for this instance + * @domain: IRQ domain for this instance + */ +struct gemini_irq_data { + void __iomem *base; + struct irq_chip chip; + struct irq_domain *domain; +}; + +static void gemini_irq_mask(struct irq_data *d) +{ + struct gemini_irq_data *g = irq_data_get_irq_chip_data(d); + unsigned int mask; + + mask = readl(GEMINI_IRQ_MASK(g->base)); + mask &= ~BIT(irqd_to_hwirq(d)); + writel(mask, GEMINI_IRQ_MASK(g->base)); +} + +static void gemini_irq_unmask(struct irq_data *d) +{ + struct gemini_irq_data *g = irq_data_get_irq_chip_data(d); + unsigned int mask; + + mask = readl(GEMINI_IRQ_MASK(g->base)); + mask |= BIT(irqd_to_hwirq(d)); + writel(mask, GEMINI_IRQ_MASK(g->base)); +} + +static void gemini_irq_ack(struct irq_data *d) +{ + struct gemini_irq_data *g = irq_data_get_irq_chip_data(d); + + writel(BIT(irqd_to_hwirq(d)), GEMINI_IRQ_CLEAR(g->base)); +} + +static int gemini_irq_set_type(struct irq_data *d, unsigned int trigger) +{ + struct gemini_irq_data *g = irq_data_get_irq_chip_data(d); + int offset = irqd_to_hwirq(d); + u32 mode, polarity; + + mode = readl(GEMINI_IRQ_MODE(g->base)); + polarity = readl(GEMINI_IRQ_POLARITY(g->base)); + + if (trigger & (IRQ_TYPE_LEVEL_HIGH)) { + irq_set_handler_locked(d, handle_level_irq); + /* Disable edge detection */ + mode &= ~BIT(offset); + polarity &= ~BIT(offset); + } else if (trigger & IRQ_TYPE_EDGE_RISING) { + irq_set_handler_locked(d, handle_edge_irq); + mode |= BIT(offset); + polarity |= BIT(offset); + } else if (trigger & IRQ_TYPE_EDGE_FALLING) { + irq_set_handler_locked(d, handle_edge_irq); + mode |= BIT(offset); + polarity &= ~BIT(offset); + } else { + irq_set_handler_locked(d, handle_bad_irq); + pr_warn("GEMINI IRQ: no supported trigger selected for line %d\n", + offset); + } + + writel(mode, GEMINI_IRQ_MODE(g->base)); + writel(polarity, GEMINI_IRQ_POLARITY(g->base)); + + return 0; +} + +static struct irq_chip gemini_irq_chip = { + .name = "GEMINI", + .irq_ack = gemini_irq_ack, + .irq_mask = gemini_irq_mask, + .irq_unmask = gemini_irq_unmask, + .irq_set_type = gemini_irq_set_type, +}; + +/* Local static for the IRQ entry call */ +static struct gemini_irq_data girq; + +asmlinkage void __exception_irq_entry gemini_irqchip_handle_irq(struct pt_regs *regs) +{ + struct gemini_irq_data *g = &girq; + int irq; + u32 status; + + while ((status = readl(GEMINI_IRQ_STATUS(g->base)))) { + irq = ffs(status) - 1; + handle_domain_irq(g->domain, irq, regs); + } +} + +static int gemini_irqdomain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct gemini_irq_data *g = d->host_data; + + irq_set_chip_data(irq, g); + /* All IRQs should set up their type, flags as bad by default */ + irq_set_chip_and_handler(irq, &gemini_irq_chip, handle_bad_irq); + irq_set_probe(irq); + + return 0; +} + +static void gemini_irqdomain_unmap(struct irq_domain *d, unsigned int irq) +{ + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); +} + +static const struct irq_domain_ops gemini_irqdomain_ops = { + .map = gemini_irqdomain_map, + .unmap = gemini_irqdomain_unmap, + .xlate = irq_domain_xlate_onetwocell, +}; + +int __init gemini_of_init_irq(struct device_node *node, + struct device_node *parent) +{ + struct gemini_irq_data *g = &girq; + + /* + * Disable the idle handler by default since it is buggy + * For more info see arch/arm/mach-gemini/idle.c + */ + cpu_idle_poll_ctrl(true); + + g->base = of_iomap(node, 0); + WARN(!g->base, "unable to map gemini irq registers\n"); + + /* Disable all interrupts */ + writel(0, GEMINI_IRQ_MASK(g->base)); + writel(0, GEMINI_FIQ_MASK(g->base)); + + g->domain = irq_domain_add_simple(node, GEMINI_NUM_IRQS, 0, + &gemini_irqdomain_ops, g); + set_handle_irq(gemini_irqchip_handle_irq); + + return 0; +} +IRQCHIP_DECLARE(gemini, "cortina,gemini-interrupt-controller", + gemini_of_init_irq); diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index c5dee300e8a3..23201004fd7a 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -37,7 +37,6 @@ #include <linux/irqchip.h> #include <linux/irqchip/arm-gic-v3.h> -#include <asm/cacheflush.h> #include <asm/cputype.h> #include <asm/exception.h> @@ -162,7 +161,7 @@ struct its_cmd_desc { struct its_device *dev; u32 phys_id; u32 event_id; - } its_mapvi_cmd; + } its_mapti_cmd; struct { struct its_device *dev; @@ -194,58 +193,56 @@ struct its_cmd_block { typedef struct its_collection *(*its_cmd_builder_t)(struct its_cmd_block *, struct its_cmd_desc *); +static void its_mask_encode(u64 *raw_cmd, u64 val, int h, int l) +{ + u64 mask = GENMASK_ULL(h, l); + *raw_cmd &= ~mask; + *raw_cmd |= (val << l) & mask; +} + static void its_encode_cmd(struct its_cmd_block *cmd, u8 cmd_nr) { - cmd->raw_cmd[0] &= ~0xffUL; - cmd->raw_cmd[0] |= cmd_nr; + its_mask_encode(&cmd->raw_cmd[0], cmd_nr, 7, 0); } static void its_encode_devid(struct its_cmd_block *cmd, u32 devid) { - cmd->raw_cmd[0] &= BIT_ULL(32) - 1; - cmd->raw_cmd[0] |= ((u64)devid) << 32; + its_mask_encode(&cmd->raw_cmd[0], devid, 63, 32); } static void its_encode_event_id(struct its_cmd_block *cmd, u32 id) { - cmd->raw_cmd[1] &= ~0xffffffffUL; - cmd->raw_cmd[1] |= id; + its_mask_encode(&cmd->raw_cmd[1], id, 31, 0); } static void its_encode_phys_id(struct its_cmd_block *cmd, u32 phys_id) { - cmd->raw_cmd[1] &= 0xffffffffUL; - cmd->raw_cmd[1] |= ((u64)phys_id) << 32; + its_mask_encode(&cmd->raw_cmd[1], phys_id, 63, 32); } static void its_encode_size(struct its_cmd_block *cmd, u8 size) { - cmd->raw_cmd[1] &= ~0x1fUL; - cmd->raw_cmd[1] |= size & 0x1f; + its_mask_encode(&cmd->raw_cmd[1], size, 4, 0); } static void its_encode_itt(struct its_cmd_block *cmd, u64 itt_addr) { - cmd->raw_cmd[2] &= ~0xffffffffffffUL; - cmd->raw_cmd[2] |= itt_addr & 0xffffffffff00UL; + its_mask_encode(&cmd->raw_cmd[2], itt_addr >> 8, 50, 8); } static void its_encode_valid(struct its_cmd_block *cmd, int valid) { - cmd->raw_cmd[2] &= ~(1UL << 63); - cmd->raw_cmd[2] |= ((u64)!!valid) << 63; + its_mask_encode(&cmd->raw_cmd[2], !!valid, 63, 63); } static void its_encode_target(struct its_cmd_block *cmd, u64 target_addr) { - cmd->raw_cmd[2] &= ~(0xffffffffUL << 16); - cmd->raw_cmd[2] |= (target_addr & (0xffffffffUL << 16)); + its_mask_encode(&cmd->raw_cmd[2], target_addr >> 16, 50, 16); } static void its_encode_collection(struct its_cmd_block *cmd, u16 col) { - cmd->raw_cmd[2] &= ~0xffffUL; - cmd->raw_cmd[2] |= col; + its_mask_encode(&cmd->raw_cmd[2], col, 15, 0); } static inline void its_fixup_cmd(struct its_cmd_block *cmd) @@ -290,18 +287,18 @@ static struct its_collection *its_build_mapc_cmd(struct its_cmd_block *cmd, return desc->its_mapc_cmd.col; } -static struct its_collection *its_build_mapvi_cmd(struct its_cmd_block *cmd, +static struct its_collection *its_build_mapti_cmd(struct its_cmd_block *cmd, struct its_cmd_desc *desc) { struct its_collection *col; - col = dev_event_to_col(desc->its_mapvi_cmd.dev, - desc->its_mapvi_cmd.event_id); + col = dev_event_to_col(desc->its_mapti_cmd.dev, + desc->its_mapti_cmd.event_id); - its_encode_cmd(cmd, GITS_CMD_MAPVI); - its_encode_devid(cmd, desc->its_mapvi_cmd.dev->device_id); - its_encode_event_id(cmd, desc->its_mapvi_cmd.event_id); - its_encode_phys_id(cmd, desc->its_mapvi_cmd.phys_id); + its_encode_cmd(cmd, GITS_CMD_MAPTI); + its_encode_devid(cmd, desc->its_mapti_cmd.dev->device_id); + its_encode_event_id(cmd, desc->its_mapti_cmd.event_id); + its_encode_phys_id(cmd, desc->its_mapti_cmd.phys_id); its_encode_collection(cmd, col->col_id); its_fixup_cmd(cmd); @@ -414,6 +411,12 @@ static struct its_cmd_block *its_allocate_entry(struct its_node *its) if (its->cmd_write == (its->cmd_base + ITS_CMD_QUEUE_NR_ENTRIES)) its->cmd_write = its->cmd_base; + /* Clear command */ + cmd->raw_cmd[0] = 0; + cmd->raw_cmd[1] = 0; + cmd->raw_cmd[2] = 0; + cmd->raw_cmd[3] = 0; + return cmd; } @@ -433,7 +436,7 @@ static void its_flush_cmd(struct its_node *its, struct its_cmd_block *cmd) * the ITS. */ if (its->flags & ITS_FLAGS_CMDQ_NEEDS_FLUSHING) - __flush_dcache_area(cmd, sizeof(*cmd)); + gic_flush_dcache_to_poc(cmd, sizeof(*cmd)); else dsb(ishst); } @@ -532,15 +535,15 @@ static void its_send_mapc(struct its_node *its, struct its_collection *col, its_send_single_command(its, its_build_mapc_cmd, &desc); } -static void its_send_mapvi(struct its_device *dev, u32 irq_id, u32 id) +static void its_send_mapti(struct its_device *dev, u32 irq_id, u32 id) { struct its_cmd_desc desc; - desc.its_mapvi_cmd.dev = dev; - desc.its_mapvi_cmd.phys_id = irq_id; - desc.its_mapvi_cmd.event_id = id; + desc.its_mapti_cmd.dev = dev; + desc.its_mapti_cmd.phys_id = irq_id; + desc.its_mapti_cmd.event_id = id; - its_send_single_command(dev->its, its_build_mapvi_cmd, &desc); + its_send_single_command(dev->its, its_build_mapti_cmd, &desc); } static void its_send_movi(struct its_device *dev, @@ -602,7 +605,7 @@ static void lpi_set_config(struct irq_data *d, bool enable) * Humpf... */ if (gic_rdists->flags & RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING) - __flush_dcache_area(cfg, sizeof(*cfg)); + gic_flush_dcache_to_poc(cfg, sizeof(*cfg)); else dsb(ishst); its_send_inv(its_dev, id); @@ -657,8 +660,8 @@ static void its_irq_compose_msi_msg(struct irq_data *d, struct msi_msg *msg) its = its_dev->its; addr = its->phys_base + GITS_TRANSLATER; - msg->address_lo = addr & ((1UL << 32) - 1); - msg->address_hi = addr >> 32; + msg->address_lo = lower_32_bits(addr); + msg->address_hi = upper_32_bits(addr); msg->data = its_get_event_id(d); iommu_dma_map_msi_msg(d->irq, msg); @@ -817,7 +820,7 @@ static int __init its_alloc_lpi_tables(void) LPI_PROPBASE_SZ); /* Make sure the GIC will observe the written configuration */ - __flush_dcache_area(page_address(gic_rdists->prop_page), LPI_PROPBASE_SZ); + gic_flush_dcache_to_poc(page_address(gic_rdists->prop_page), LPI_PROPBASE_SZ); return 0; } @@ -825,7 +828,7 @@ static int __init its_alloc_lpi_tables(void) static const char *its_base_type_string[] = { [GITS_BASER_TYPE_DEVICE] = "Devices", [GITS_BASER_TYPE_VCPU] = "Virtual CPUs", - [GITS_BASER_TYPE_CPU] = "Physical CPUs", + [GITS_BASER_TYPE_RESERVED3] = "Reserved (3)", [GITS_BASER_TYPE_COLLECTION] = "Interrupt Collections", [GITS_BASER_TYPE_RESERVED5] = "Reserved (5)", [GITS_BASER_TYPE_RESERVED6] = "Reserved (6)", @@ -836,7 +839,7 @@ static u64 its_read_baser(struct its_node *its, struct its_baser *baser) { u32 idx = baser - its->tables; - return readq_relaxed(its->base + GITS_BASER + (idx << 3)); + return gits_read_baser(its->base + GITS_BASER + (idx << 3)); } static void its_write_baser(struct its_node *its, struct its_baser *baser, @@ -844,7 +847,7 @@ static void its_write_baser(struct its_node *its, struct its_baser *baser, { u32 idx = baser - its->tables; - writeq_relaxed(val, its->base + GITS_BASER + (idx << 3)); + gits_write_baser(val, its->base + GITS_BASER + (idx << 3)); baser->val = its_read_baser(its, baser); } @@ -910,7 +913,7 @@ retry_baser: shr = tmp & GITS_BASER_SHAREABILITY_MASK; if (!shr) { cache = GITS_BASER_nC; - __flush_dcache_area(base, PAGE_ORDER_TO_SIZE(order)); + gic_flush_dcache_to_poc(base, PAGE_ORDER_TO_SIZE(order)); } goto retry_baser; } @@ -935,9 +938,9 @@ retry_baser: } if (val != tmp) { - pr_err("ITS@%pa: %s doesn't stick: %lx %lx\n", + pr_err("ITS@%pa: %s doesn't stick: %llx %llx\n", &its->phys_base, its_base_type_string[type], - (unsigned long) val, (unsigned long) tmp); + val, tmp); free_pages((unsigned long)base, order); return -ENXIO; } @@ -948,7 +951,7 @@ retry_baser: tmp = indirect ? GITS_LVL1_ENTRY_SIZE : esz; pr_info("ITS@%pa: allocated %d %s @%lx (%s, esz %d, psz %dK, shr %d)\n", - &its->phys_base, (int)(PAGE_ORDER_TO_SIZE(order) / tmp), + &its->phys_base, (int)(PAGE_ORDER_TO_SIZE(order) / (int)tmp), its_base_type_string[type], (unsigned long)virt_to_phys(base), indirect ? "indirect" : "flat", (int)esz, @@ -961,7 +964,7 @@ static bool its_parse_baser_device(struct its_node *its, struct its_baser *baser u32 psz, u32 *order) { u64 esz = GITS_BASER_ENTRY_SIZE(its_read_baser(its, baser)); - u64 val = GITS_BASER_InnerShareable | GITS_BASER_WaWb; + u64 val = GITS_BASER_InnerShareable | GITS_BASER_RaWaWb; u32 ids = its->device_ids; u32 new_order = *order; bool indirect = false; @@ -983,7 +986,7 @@ static bool its_parse_baser_device(struct its_node *its, struct its_baser *baser * which is reported by ITS hardware times lvl1 table * entry size. */ - ids -= ilog2(psz / esz); + ids -= ilog2(psz / (int)esz); esz = GITS_LVL1_ENTRY_SIZE; } } @@ -998,7 +1001,7 @@ static bool its_parse_baser_device(struct its_node *its, struct its_baser *baser new_order = max_t(u32, get_order(esz << ids), new_order); if (new_order >= MAX_ORDER) { new_order = MAX_ORDER - 1; - ids = ilog2(PAGE_ORDER_TO_SIZE(new_order) / esz); + ids = ilog2(PAGE_ORDER_TO_SIZE(new_order) / (int)esz); pr_warn("ITS@%pa: Device Table too large, reduce ids %u->%u\n", &its->phys_base, its->device_ids, ids); } @@ -1026,7 +1029,7 @@ static int its_alloc_tables(struct its_node *its) u64 typer = gic_read_typer(its->base + GITS_TYPER); u32 ids = GITS_TYPER_DEVBITS(typer); u64 shr = GITS_BASER_InnerShareable; - u64 cache = GITS_BASER_WaWb; + u64 cache = GITS_BASER_RaWaWb; u32 psz = SZ_64K; int err, i; @@ -1102,7 +1105,7 @@ static void its_cpu_init_lpis(void) } /* Make sure the GIC will observe the zero-ed page */ - __flush_dcache_area(page_address(pend_page), LPI_PENDBASE_SZ); + gic_flush_dcache_to_poc(page_address(pend_page), LPI_PENDBASE_SZ); paddr = page_to_phys(pend_page); pr_info("CPU%d: using LPI pending table @%pa\n", @@ -1123,11 +1126,11 @@ static void its_cpu_init_lpis(void) /* set PROPBASE */ val = (page_to_phys(gic_rdists->prop_page) | GICR_PROPBASER_InnerShareable | - GICR_PROPBASER_WaWb | + GICR_PROPBASER_RaWaWb | ((LPI_NRBITS - 1) & GICR_PROPBASER_IDBITS_MASK)); - writeq_relaxed(val, rbase + GICR_PROPBASER); - tmp = readq_relaxed(rbase + GICR_PROPBASER); + gicr_write_propbaser(val, rbase + GICR_PROPBASER); + tmp = gicr_read_propbaser(rbase + GICR_PROPBASER); if ((tmp ^ val) & GICR_PROPBASER_SHAREABILITY_MASK) { if (!(tmp & GICR_PROPBASER_SHAREABILITY_MASK)) { @@ -1139,7 +1142,7 @@ static void its_cpu_init_lpis(void) val &= ~(GICR_PROPBASER_SHAREABILITY_MASK | GICR_PROPBASER_CACHEABILITY_MASK); val |= GICR_PROPBASER_nC; - writeq_relaxed(val, rbase + GICR_PROPBASER); + gicr_write_propbaser(val, rbase + GICR_PROPBASER); } pr_info_once("GIC: using cache flushing for LPI property table\n"); gic_rdists->flags |= RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING; @@ -1148,10 +1151,10 @@ static void its_cpu_init_lpis(void) /* set PENDBASE */ val = (page_to_phys(pend_page) | GICR_PENDBASER_InnerShareable | - GICR_PENDBASER_WaWb); + GICR_PENDBASER_RaWaWb); - writeq_relaxed(val, rbase + GICR_PENDBASER); - tmp = readq_relaxed(rbase + GICR_PENDBASER); + gicr_write_pendbaser(val, rbase + GICR_PENDBASER); + tmp = gicr_read_pendbaser(rbase + GICR_PENDBASER); if (!(tmp & GICR_PENDBASER_SHAREABILITY_MASK)) { /* @@ -1161,7 +1164,7 @@ static void its_cpu_init_lpis(void) val &= ~(GICR_PENDBASER_SHAREABILITY_MASK | GICR_PENDBASER_CACHEABILITY_MASK); val |= GICR_PENDBASER_nC; - writeq_relaxed(val, rbase + GICR_PENDBASER); + gicr_write_pendbaser(val, rbase + GICR_PENDBASER); } /* Enable LPIs */ @@ -1287,13 +1290,13 @@ static bool its_alloc_device_table(struct its_node *its, u32 dev_id) /* Flush Lvl2 table to PoC if hw doesn't support coherency */ if (!(baser->val & GITS_BASER_SHAREABILITY_MASK)) - __flush_dcache_area(page_address(page), baser->psz); + gic_flush_dcache_to_poc(page_address(page), baser->psz); table[idx] = cpu_to_le64(page_to_phys(page) | GITS_BASER_VALID); /* Flush Lvl1 entry to PoC if hw doesn't support coherency */ if (!(baser->val & GITS_BASER_SHAREABILITY_MASK)) - __flush_dcache_area(table + idx, GITS_LVL1_ENTRY_SIZE); + gic_flush_dcache_to_poc(table + idx, GITS_LVL1_ENTRY_SIZE); /* Ensure updated table contents are visible to ITS hardware */ dsb(sy); @@ -1340,7 +1343,7 @@ static struct its_device *its_create_device(struct its_node *its, u32 dev_id, return NULL; } - __flush_dcache_area(itt, sz); + gic_flush_dcache_to_poc(itt, sz); dev->its = its; dev->itt = itt; @@ -1499,7 +1502,7 @@ static void its_irq_domain_activate(struct irq_domain *domain, its_dev->event_map.col_map[event] = cpumask_first(cpu_mask); /* Map the GIC IRQ and event to the device */ - its_send_mapvi(its_dev, d->hwirq, event); + its_send_mapti(its_dev, d->hwirq, event); } static void its_irq_domain_deactivate(struct irq_domain *domain, @@ -1643,6 +1646,7 @@ static int its_init_domain(struct fwnode_handle *handle, struct its_node *its) inner_domain->parent = its_parent; inner_domain->bus_token = DOMAIN_BUS_NEXUS; + inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_REMAP; info->ops = &its_msi_domain_ops; info->data = its; inner_domain->host_data = info; @@ -1694,7 +1698,8 @@ static int __init its_probe_one(struct resource *res, its->ite_size = ((gic_read_typer(its_base + GITS_TYPER) >> 4) & 0xf) + 1; its->numa_node = numa_node; - its->cmd_base = kzalloc(ITS_CMD_QUEUE_SZ, GFP_KERNEL); + its->cmd_base = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, + get_order(ITS_CMD_QUEUE_SZ)); if (!its->cmd_base) { err = -ENOMEM; goto out_free_its; @@ -1712,13 +1717,13 @@ static int __init its_probe_one(struct resource *res, goto out_free_tables; baser = (virt_to_phys(its->cmd_base) | - GITS_CBASER_WaWb | + GITS_CBASER_RaWaWb | GITS_CBASER_InnerShareable | (ITS_CMD_QUEUE_SZ / SZ_4K - 1) | GITS_CBASER_VALID); - writeq_relaxed(baser, its->base + GITS_CBASER); - tmp = readq_relaxed(its->base + GITS_CBASER); + gits_write_cbaser(baser, its->base + GITS_CBASER); + tmp = gits_read_cbaser(its->base + GITS_CBASER); if ((tmp ^ baser) & GITS_CBASER_SHAREABILITY_MASK) { if (!(tmp & GITS_CBASER_SHAREABILITY_MASK)) { @@ -1730,13 +1735,13 @@ static int __init its_probe_one(struct resource *res, baser &= ~(GITS_CBASER_SHAREABILITY_MASK | GITS_CBASER_CACHEABILITY_MASK); baser |= GITS_CBASER_nC; - writeq_relaxed(baser, its->base + GITS_CBASER); + gits_write_cbaser(baser, its->base + GITS_CBASER); } pr_info("ITS: using cache flushing for cmd queue\n"); its->flags |= ITS_FLAGS_CMDQ_NEEDS_FLUSHING; } - writeq_relaxed(0, its->base + GITS_CWRITER); + gits_write_cwriter(0, its->base + GITS_CWRITER); writel_relaxed(GITS_CTLR_ENABLE, its->base + GITS_CTLR); err = its_init_domain(handle, its); @@ -1752,7 +1757,7 @@ static int __init its_probe_one(struct resource *res, out_free_tables: its_free_tables(its); out_free_cmd: - kfree(its->cmd_base); + free_pages((unsigned long)its->cmd_base, get_order(ITS_CMD_QUEUE_SZ)); out_free_its: kfree(its); out_unmap: diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 19d642eae096..c132f29322cc 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -120,11 +120,10 @@ static void gic_redist_wait_for_rwp(void) } #ifdef CONFIG_ARM64 -static DEFINE_STATIC_KEY_FALSE(is_cavium_thunderx); static u64 __maybe_unused gic_read_iar(void) { - if (static_branch_unlikely(&is_cavium_thunderx)) + if (cpus_have_const_cap(ARM64_WORKAROUND_CAVIUM_23154)) return gic_read_iar_cavium_thunderx(); else return gic_read_iar_common(); @@ -633,9 +632,9 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) static void gic_smp_init(void) { set_smp_cross_call(gic_raise_softirq); - cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GICV3_STARTING, - "AP_IRQ_GICV3_STARTING", gic_starting_cpu, - NULL); + cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING, + "irqchip/arm/gicv3:starting", + gic_starting_cpu, NULL); } static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val, @@ -905,14 +904,6 @@ static const struct irq_domain_ops partition_domain_ops = { .select = gic_irq_domain_select, }; -static void gicv3_enable_quirks(void) -{ -#ifdef CONFIG_ARM64 - if (cpus_have_cap(ARM64_WORKAROUND_CAVIUM_23154)) - static_branch_enable(&is_cavium_thunderx); -#endif -} - static int __init gic_init_bases(void __iomem *dist_base, struct redist_region *rdist_regs, u32 nr_redist_regions, @@ -935,8 +926,6 @@ static int __init gic_init_bases(void __iomem *dist_base, gic_data.nr_redist_regions = nr_redist_regions; gic_data.redist_stride = redist_stride; - gicv3_enable_quirks(); - /* * Find out how many interrupts are supported. * The GIC only supports up to 1020 interrupt sources (SGI+PPI+SPI) diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index d6c404b3584d..1b1df4f770bd 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -1191,7 +1191,7 @@ static int __init __gic_init_bases(struct gic_chip_data *gic, set_smp_cross_call(gic_raise_softirq); #endif cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING, - "AP_IRQ_GIC_STARTING", + "irqchip/arm/gic:starting", gic_starting_cpu, NULL); set_handle_irq(gic_handle_irq); if (static_key_true(&supports_deactivate)) diff --git a/drivers/irqchip/irq-hip04.c b/drivers/irqchip/irq-hip04.c index 021b0e0833c1..c1b4ee955dbe 100644 --- a/drivers/irqchip/irq-hip04.c +++ b/drivers/irqchip/irq-hip04.c @@ -407,7 +407,7 @@ hip04_of_init(struct device_node *node, struct device_node *parent) set_handle_irq(hip04_handle_irq); hip04_irq_dist_init(&hip04_data); - cpuhp_setup_state(CPUHP_AP_IRQ_HIP04_STARTING, "AP_IRQ_HIP04_STARTING", + cpuhp_setup_state(CPUHP_AP_IRQ_HIP04_STARTING, "irqchip/hip04:starting", hip04_irq_starting_cpu, NULL); return 0; } diff --git a/drivers/irqchip/irq-keystone.c b/drivers/irqchip/irq-keystone.c index 54a5e870a8f5..efbcf8435185 100644 --- a/drivers/irqchip/irq-keystone.c +++ b/drivers/irqchip/irq-keystone.c @@ -19,9 +19,9 @@ #include <linux/bitops.h> #include <linux/module.h> #include <linux/moduleparam.h> +#include <linux/interrupt.h> #include <linux/irqdomain.h> #include <linux/irqchip.h> -#include <linux/irqchip/chained_irq.h> #include <linux/of.h> #include <linux/of_platform.h> #include <linux/mfd/syscon.h> @@ -39,6 +39,7 @@ struct keystone_irq_device { struct irq_domain *irqd; struct regmap *devctrl_regs; u32 devctrl_offset; + raw_spinlock_t wa_lock; }; static inline u32 keystone_irq_readl(struct keystone_irq_device *kirq) @@ -83,17 +84,15 @@ static void keystone_irq_ack(struct irq_data *d) /* nothing to do here */ } -static void keystone_irq_handler(struct irq_desc *desc) +static irqreturn_t keystone_irq_handler(int irq, void *keystone_irq) { - unsigned int irq = irq_desc_get_irq(desc); - struct keystone_irq_device *kirq = irq_desc_get_handler_data(desc); + struct keystone_irq_device *kirq = keystone_irq; + unsigned long wa_lock_flags; 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); @@ -111,13 +110,15 @@ static void keystone_irq_handler(struct irq_desc *desc) if (!virq) dev_warn(kirq->dev, "spurious irq detected hwirq %d, virq %d\n", src, virq); + raw_spin_lock_irqsave(&kirq->wa_lock, wa_lock_flags); generic_handle_irq(virq); + raw_spin_unlock_irqrestore(&kirq->wa_lock, + wa_lock_flags); } } - chained_irq_exit(irq_desc_get_chip(desc), desc); - dev_dbg(kirq->dev, "end irq %d\n", irq); + return IRQ_HANDLED; } static int keystone_irq_map(struct irq_domain *h, unsigned int virq, @@ -182,9 +183,16 @@ static int keystone_irq_probe(struct platform_device *pdev) return -ENODEV; } + raw_spin_lock_init(&kirq->wa_lock); + platform_set_drvdata(pdev, kirq); - irq_set_chained_handler_and_data(kirq->irq, keystone_irq_handler, kirq); + ret = request_irq(kirq->irq, keystone_irq_handler, + 0, dev_name(dev), kirq); + if (ret) { + irq_domain_remove(kirq->irqd); + return ret; + } /* clear all source bits */ keystone_irq_writel(kirq, ~0x0); @@ -199,6 +207,8 @@ static int keystone_irq_remove(struct platform_device *pdev) struct keystone_irq_device *kirq = platform_get_drvdata(pdev); int hwirq; + free_irq(kirq->irq, kirq); + for (hwirq = 0; hwirq < KEYSTONE_N_IRQ; hwirq++) irq_dispose_mapping(irq_find_mapping(kirq->irqd, hwirq)); diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c index c0178a122940..11d12bccc4e7 100644 --- a/drivers/irqchip/irq-mips-gic.c +++ b/drivers/irqchip/irq-mips-gic.c @@ -152,12 +152,12 @@ static inline void gic_map_to_vpe(unsigned int intr, unsigned int vpe) } #ifdef CONFIG_CLKSRC_MIPS_GIC -cycle_t gic_read_count(void) +u64 gic_read_count(void) { unsigned int hi, hi2, lo; if (mips_cm_is64) - return (cycle_t)gic_read(GIC_REG(SHARED, GIC_SH_COUNTER)); + return (u64)gic_read(GIC_REG(SHARED, GIC_SH_COUNTER)); do { hi = gic_read32(GIC_REG(SHARED, GIC_SH_COUNTER_63_32)); @@ -165,7 +165,7 @@ cycle_t gic_read_count(void) hi2 = gic_read32(GIC_REG(SHARED, GIC_SH_COUNTER_63_32)); } while (hi2 != hi); - return (((cycle_t) hi) << 32) + lo; + return (((u64) hi) << 32) + lo; } unsigned int gic_get_count_width(void) @@ -179,7 +179,7 @@ unsigned int gic_get_count_width(void) return bits; } -void gic_write_compare(cycle_t cnt) +void gic_write_compare(u64 cnt) { if (mips_cm_is64) { gic_write(GIC_REG(VPE_LOCAL, GIC_VPE_COMPARE), cnt); @@ -191,7 +191,7 @@ void gic_write_compare(cycle_t cnt) } } -void gic_write_cpu_compare(cycle_t cnt, int cpu) +void gic_write_cpu_compare(u64 cnt, int cpu) { unsigned long flags; @@ -211,17 +211,17 @@ void gic_write_cpu_compare(cycle_t cnt, int cpu) local_irq_restore(flags); } -cycle_t gic_read_compare(void) +u64 gic_read_compare(void) { unsigned int hi, lo; if (mips_cm_is64) - return (cycle_t)gic_read(GIC_REG(VPE_LOCAL, GIC_VPE_COMPARE)); + return (u64)gic_read(GIC_REG(VPE_LOCAL, GIC_VPE_COMPARE)); hi = gic_read32(GIC_REG(VPE_LOCAL, GIC_VPE_COMPARE_HI)); lo = gic_read32(GIC_REG(VPE_LOCAL, GIC_VPE_COMPARE_LO)); - return (((cycle_t) hi) << 32) + lo; + return (((u64) hi) << 32) + lo; } void gic_start_count(void) @@ -968,6 +968,34 @@ static struct irq_domain_ops gic_ipi_domain_ops = { .match = gic_ipi_domain_match, }; +static void __init gic_map_single_int(struct device_node *node, + unsigned int irq) +{ + unsigned int linux_irq; + struct irq_fwspec local_int_fwspec = { + .fwnode = &node->fwnode, + .param_count = 3, + .param = { + [0] = GIC_LOCAL, + [1] = irq, + [2] = IRQ_TYPE_NONE, + }, + }; + + if (!gic_local_irq_is_routable(irq)) + return; + + linux_irq = irq_create_fwspec_mapping(&local_int_fwspec); + WARN_ON(!linux_irq); +} + +static void __init gic_map_interrupts(struct device_node *node) +{ + gic_map_single_int(node, GIC_LOCAL_INT_TIMER); + gic_map_single_int(node, GIC_LOCAL_INT_PERFCTR); + gic_map_single_int(node, GIC_LOCAL_INT_FDC); +} + static void __init __gic_init(unsigned long gic_base_addr, unsigned long gic_addrspace_size, unsigned int cpu_vec, unsigned int irqbase, @@ -1067,6 +1095,7 @@ static void __init __gic_init(unsigned long gic_base_addr, } gic_basic_init(); + gic_map_interrupts(node); } void __init gic_init(unsigned long gic_base_addr, diff --git a/drivers/irqchip/irq-mxs.c b/drivers/irqchip/irq-mxs.c index 17304705f2cf..05fa9f7af53c 100644 --- a/drivers/irqchip/irq-mxs.c +++ b/drivers/irqchip/irq-mxs.c @@ -131,12 +131,16 @@ static struct irq_chip mxs_icoll_chip = { .irq_ack = icoll_ack_irq, .irq_mask = icoll_mask_irq, .irq_unmask = icoll_unmask_irq, + .flags = IRQCHIP_MASK_ON_SUSPEND | + IRQCHIP_SKIP_SET_WAKE, }; static struct irq_chip asm9260_icoll_chip = { .irq_ack = icoll_ack_irq, .irq_mask = asm9260_mask_irq, .irq_unmask = asm9260_unmask_irq, + .flags = IRQCHIP_MASK_ON_SUSPEND | + IRQCHIP_SKIP_SET_WAKE, }; asmlinkage void __exception_irq_entry icoll_handle_irq(struct pt_regs *regs) diff --git a/drivers/irqchip/irq-st.c b/drivers/irqchip/irq-st.c index 9af48a85c16f..5e0e250db0be 100644 --- a/drivers/irqchip/irq-st.c +++ b/drivers/irqchip/irq-st.c @@ -180,7 +180,7 @@ static int st_irq_syscfg_probe(struct platform_device *pdev) return st_irq_syscfg_enable(pdev); } -static int st_irq_syscfg_resume(struct device *dev) +static int __maybe_unused st_irq_syscfg_resume(struct device *dev) { struct st_irq_syscfg *ddata = dev_get_drvdata(dev); diff --git a/drivers/irqchip/irq-xilinx-intc.c b/drivers/irqchip/irq-xilinx-intc.c new file mode 100644 index 000000000000..3db7ab1c9741 --- /dev/null +++ b/drivers/irqchip/irq-xilinx-intc.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2007-2013 Michal Simek <monstr@monstr.eu> + * Copyright (C) 2012-2013 Xilinx, Inc. + * Copyright (C) 2007-2009 PetaLogix + * Copyright (C) 2006 Atmark Techno, Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include <linux/irqdomain.h> +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/of_address.h> +#include <linux/io.h> +#include <linux/jump_label.h> +#include <linux/bug.h> +#include <linux/of_irq.h> + +/* No one else should require these constants, so define them locally here. */ +#define ISR 0x00 /* Interrupt Status Register */ +#define IPR 0x04 /* Interrupt Pending Register */ +#define IER 0x08 /* Interrupt Enable Register */ +#define IAR 0x0c /* Interrupt Acknowledge Register */ +#define SIE 0x10 /* Set Interrupt Enable bits */ +#define CIE 0x14 /* Clear Interrupt Enable bits */ +#define IVR 0x18 /* Interrupt Vector Register */ +#define MER 0x1c /* Master Enable Register */ + +#define MER_ME (1<<0) +#define MER_HIE (1<<1) + +static DEFINE_STATIC_KEY_FALSE(xintc_is_be); + +struct xintc_irq_chip { + void __iomem *base; + struct irq_domain *root_domain; + u32 intr_mask; +}; + +static struct xintc_irq_chip *xintc_irqc; + +static void xintc_write(int reg, u32 data) +{ + if (static_branch_unlikely(&xintc_is_be)) + iowrite32be(data, xintc_irqc->base + reg); + else + iowrite32(data, xintc_irqc->base + reg); +} + +static unsigned int xintc_read(int reg) +{ + if (static_branch_unlikely(&xintc_is_be)) + return ioread32be(xintc_irqc->base + reg); + else + return ioread32(xintc_irqc->base + reg); +} + +static void intc_enable_or_unmask(struct irq_data *d) +{ + unsigned long mask = 1 << d->hwirq; + + pr_debug("irq-xilinx: enable_or_unmask: %ld\n", d->hwirq); + + /* ack level irqs because they can't be acked during + * ack function since the handle_level_irq function + * acks the irq before calling the interrupt handler + */ + if (irqd_is_level_type(d)) + xintc_write(IAR, mask); + + xintc_write(SIE, mask); +} + +static void intc_disable_or_mask(struct irq_data *d) +{ + pr_debug("irq-xilinx: disable: %ld\n", d->hwirq); + xintc_write(CIE, 1 << d->hwirq); +} + +static void intc_ack(struct irq_data *d) +{ + pr_debug("irq-xilinx: ack: %ld\n", d->hwirq); + xintc_write(IAR, 1 << d->hwirq); +} + +static void intc_mask_ack(struct irq_data *d) +{ + unsigned long mask = 1 << d->hwirq; + + pr_debug("irq-xilinx: disable_and_ack: %ld\n", d->hwirq); + xintc_write(CIE, mask); + xintc_write(IAR, mask); +} + +static struct irq_chip intc_dev = { + .name = "Xilinx INTC", + .irq_unmask = intc_enable_or_unmask, + .irq_mask = intc_disable_or_mask, + .irq_ack = intc_ack, + .irq_mask_ack = intc_mask_ack, +}; + +unsigned int xintc_get_irq(void) +{ + unsigned int hwirq, irq = -1; + + hwirq = xintc_read(IVR); + if (hwirq != -1U) + irq = irq_find_mapping(xintc_irqc->root_domain, hwirq); + + pr_debug("irq-xilinx: hwirq=%d, irq=%d\n", hwirq, irq); + + return irq; +} + +static int xintc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) +{ + if (xintc_irqc->intr_mask & (1 << hw)) { + irq_set_chip_and_handler_name(irq, &intc_dev, + handle_edge_irq, "edge"); + irq_clear_status_flags(irq, IRQ_LEVEL); + } else { + irq_set_chip_and_handler_name(irq, &intc_dev, + handle_level_irq, "level"); + irq_set_status_flags(irq, IRQ_LEVEL); + } + return 0; +} + +static const struct irq_domain_ops xintc_irq_domain_ops = { + .xlate = irq_domain_xlate_onetwocell, + .map = xintc_map, +}; + +static void xil_intc_irq_handler(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + u32 pending; + + chained_irq_enter(chip, desc); + do { + pending = xintc_get_irq(); + if (pending == -1U) + break; + generic_handle_irq(pending); + } while (true); + chained_irq_exit(chip, desc); +} + +static int __init xilinx_intc_of_init(struct device_node *intc, + struct device_node *parent) +{ + u32 nr_irq; + int ret, irq; + struct xintc_irq_chip *irqc; + + if (xintc_irqc) { + pr_err("irq-xilinx: Multiple instances aren't supported\n"); + return -EINVAL; + } + + irqc = kzalloc(sizeof(*irqc), GFP_KERNEL); + if (!irqc) + return -ENOMEM; + + xintc_irqc = irqc; + + irqc->base = of_iomap(intc, 0); + BUG_ON(!irqc->base); + + ret = of_property_read_u32(intc, "xlnx,num-intr-inputs", &nr_irq); + if (ret < 0) { + pr_err("irq-xilinx: unable to read xlnx,num-intr-inputs\n"); + goto err_alloc; + } + + ret = of_property_read_u32(intc, "xlnx,kind-of-intr", &irqc->intr_mask); + if (ret < 0) { + pr_warn("irq-xilinx: unable to read xlnx,kind-of-intr\n"); + irqc->intr_mask = 0; + } + + if (irqc->intr_mask >> nr_irq) + pr_warn("irq-xilinx: mismatch in kind-of-intr param\n"); + + pr_info("irq-xilinx: %s: num_irq=%d, edge=0x%x\n", + intc->full_name, nr_irq, irqc->intr_mask); + + + /* + * Disable all external interrupts until they are + * explicity requested. + */ + xintc_write(IER, 0); + + /* Acknowledge any pending interrupts just in case. */ + xintc_write(IAR, 0xffffffff); + + /* Turn on the Master Enable. */ + xintc_write(MER, MER_HIE | MER_ME); + if (!(xintc_read(MER) & (MER_HIE | MER_ME))) { + static_branch_enable(&xintc_is_be); + xintc_write(MER, MER_HIE | MER_ME); + } + + irqc->root_domain = irq_domain_add_linear(intc, nr_irq, + &xintc_irq_domain_ops, irqc); + if (!irqc->root_domain) { + pr_err("irq-xilinx: Unable to create IRQ domain\n"); + goto err_alloc; + } + + if (parent) { + irq = irq_of_parse_and_map(intc, 0); + if (irq) { + irq_set_chained_handler_and_data(irq, + xil_intc_irq_handler, + irqc); + } else { + pr_err("irq-xilinx: interrupts property not in DT\n"); + ret = -EINVAL; + goto err_alloc; + } + } else { + irq_set_default_host(irqc->root_domain); + } + + return 0; + +err_alloc: + xintc_irqc = NULL; + kfree(irqc); + return ret; + +} + +IRQCHIP_DECLARE(xilinx_intc_xps, "xlnx,xps-intc-1.00.a", xilinx_intc_of_init); +IRQCHIP_DECLARE(xilinx_intc_opb, "xlnx,opb-intc-1.00.c", xilinx_intc_of_init); diff --git a/drivers/irqchip/qcom-irq-combiner.c b/drivers/irqchip/qcom-irq-combiner.c new file mode 100644 index 000000000000..226558698344 --- /dev/null +++ b/drivers/irqchip/qcom-irq-combiner.c @@ -0,0 +1,296 @@ +/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Driver for interrupt combiners in the Top-level Control and Status + * Registers (TCSR) hardware block in Qualcomm Technologies chips. + * An interrupt combiner in this block combines a set of interrupts by + * OR'ing the individual interrupt signals into a summary interrupt + * signal routed to a parent interrupt controller, and provides read- + * only, 32-bit registers to query the status of individual interrupts. + * The status bit for IRQ n is bit (n % 32) within register (n / 32) + * of the given combiner. Thus, each combiner can be described as a set + * of register offsets and the number of IRQs managed. + */ + +#define pr_fmt(fmt) "QCOM80B1:" fmt + +#include <linux/acpi.h> +#include <linux/irqchip/chained_irq.h> +#include <linux/irqdomain.h> +#include <linux/platform_device.h> + +#define REG_SIZE 32 + +struct combiner_reg { + void __iomem *addr; + unsigned long enabled; +}; + +struct combiner { + struct irq_domain *domain; + int parent_irq; + u32 nirqs; + u32 nregs; + struct combiner_reg regs[0]; +}; + +static inline int irq_nr(u32 reg, u32 bit) +{ + return reg * REG_SIZE + bit; +} + +/* + * Handler for the cascaded IRQ. + */ +static void combiner_handle_irq(struct irq_desc *desc) +{ + struct combiner *combiner = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + u32 reg; + + chained_irq_enter(chip, desc); + + for (reg = 0; reg < combiner->nregs; reg++) { + int virq; + int hwirq; + u32 bit; + u32 status; + + bit = readl_relaxed(combiner->regs[reg].addr); + status = bit & combiner->regs[reg].enabled; + if (!status) + pr_warn_ratelimited("Unexpected IRQ on CPU%d: (%08x %08lx %p)\n", + smp_processor_id(), bit, + combiner->regs[reg].enabled, + combiner->regs[reg].addr); + + while (status) { + bit = __ffs(status); + status &= ~(1 << bit); + hwirq = irq_nr(reg, bit); + virq = irq_find_mapping(combiner->domain, hwirq); + if (virq > 0) + generic_handle_irq(virq); + + } + } + + chained_irq_exit(chip, desc); +} + +static void combiner_irq_chip_mask_irq(struct irq_data *data) +{ + struct combiner *combiner = irq_data_get_irq_chip_data(data); + struct combiner_reg *reg = combiner->regs + data->hwirq / REG_SIZE; + + clear_bit(data->hwirq % REG_SIZE, ®->enabled); +} + +static void combiner_irq_chip_unmask_irq(struct irq_data *data) +{ + struct combiner *combiner = irq_data_get_irq_chip_data(data); + struct combiner_reg *reg = combiner->regs + data->hwirq / REG_SIZE; + + set_bit(data->hwirq % REG_SIZE, ®->enabled); +} + +static struct irq_chip irq_chip = { + .irq_mask = combiner_irq_chip_mask_irq, + .irq_unmask = combiner_irq_chip_unmask_irq, + .name = "qcom-irq-combiner" +}; + +static int combiner_irq_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_and_handler(irq, &irq_chip, handle_level_irq); + irq_set_chip_data(irq, domain->host_data); + irq_set_noprobe(irq); + return 0; +} + +static void combiner_irq_unmap(struct irq_domain *domain, unsigned int irq) +{ + irq_domain_reset_irq_data(irq_get_irq_data(irq)); +} + +static int combiner_irq_translate(struct irq_domain *d, struct irq_fwspec *fws, + unsigned long *hwirq, unsigned int *type) +{ + struct combiner *combiner = d->host_data; + + if (is_acpi_node(fws->fwnode)) { + if (WARN_ON((fws->param_count != 2) || + (fws->param[0] >= combiner->nirqs) || + (fws->param[1] & IORESOURCE_IRQ_LOWEDGE) || + (fws->param[1] & IORESOURCE_IRQ_HIGHEDGE))) + return -EINVAL; + + *hwirq = fws->param[0]; + *type = fws->param[1]; + return 0; + } + + return -EINVAL; +} + +static const struct irq_domain_ops domain_ops = { + .map = combiner_irq_map, + .unmap = combiner_irq_unmap, + .translate = combiner_irq_translate +}; + +static acpi_status count_registers_cb(struct acpi_resource *ares, void *context) +{ + int *count = context; + + if (ares->type == ACPI_RESOURCE_TYPE_GENERIC_REGISTER) + ++(*count); + return AE_OK; +} + +static int count_registers(struct platform_device *pdev) +{ + acpi_handle ahandle = ACPI_HANDLE(&pdev->dev); + acpi_status status; + int count = 0; + + if (!acpi_has_method(ahandle, METHOD_NAME__CRS)) + return -EINVAL; + + status = acpi_walk_resources(ahandle, METHOD_NAME__CRS, + count_registers_cb, &count); + if (ACPI_FAILURE(status)) + return -EINVAL; + return count; +} + +struct get_registers_context { + struct device *dev; + struct combiner *combiner; + int err; +}; + +static acpi_status get_registers_cb(struct acpi_resource *ares, void *context) +{ + struct get_registers_context *ctx = context; + struct acpi_resource_generic_register *reg; + phys_addr_t paddr; + void __iomem *vaddr; + + if (ares->type != ACPI_RESOURCE_TYPE_GENERIC_REGISTER) + return AE_OK; + + reg = &ares->data.generic_reg; + paddr = reg->address; + if ((reg->space_id != ACPI_SPACE_MEM) || + (reg->bit_offset != 0) || + (reg->bit_width > REG_SIZE)) { + dev_err(ctx->dev, "Bad register resource @%pa\n", &paddr); + ctx->err = -EINVAL; + return AE_ERROR; + } + + vaddr = devm_ioremap(ctx->dev, reg->address, REG_SIZE); + if (!vaddr) { + dev_err(ctx->dev, "Can't map register @%pa\n", &paddr); + ctx->err = -ENOMEM; + return AE_ERROR; + } + + ctx->combiner->regs[ctx->combiner->nregs].addr = vaddr; + ctx->combiner->nirqs += reg->bit_width; + ctx->combiner->nregs++; + return AE_OK; +} + +static int get_registers(struct platform_device *pdev, struct combiner *comb) +{ + acpi_handle ahandle = ACPI_HANDLE(&pdev->dev); + acpi_status status; + struct get_registers_context ctx; + + if (!acpi_has_method(ahandle, METHOD_NAME__CRS)) + return -EINVAL; + + ctx.dev = &pdev->dev; + ctx.combiner = comb; + ctx.err = 0; + + status = acpi_walk_resources(ahandle, METHOD_NAME__CRS, + get_registers_cb, &ctx); + if (ACPI_FAILURE(status)) + return ctx.err; + return 0; +} + +static int __init combiner_probe(struct platform_device *pdev) +{ + struct combiner *combiner; + size_t alloc_sz; + u32 nregs; + int err; + + nregs = count_registers(pdev); + if (nregs <= 0) { + dev_err(&pdev->dev, "Error reading register resources\n"); + return -EINVAL; + } + + alloc_sz = sizeof(*combiner) + sizeof(struct combiner_reg) * nregs; + combiner = devm_kzalloc(&pdev->dev, alloc_sz, GFP_KERNEL); + if (!combiner) + return -ENOMEM; + + err = get_registers(pdev, combiner); + if (err < 0) + return err; + + combiner->parent_irq = platform_get_irq(pdev, 0); + if (combiner->parent_irq <= 0) { + dev_err(&pdev->dev, "Error getting IRQ resource\n"); + return -EPROBE_DEFER; + } + + combiner->domain = irq_domain_create_linear(pdev->dev.fwnode, combiner->nirqs, + &domain_ops, combiner); + if (!combiner->domain) + /* Errors printed by irq_domain_create_linear */ + return -ENODEV; + + irq_set_chained_handler_and_data(combiner->parent_irq, + combiner_handle_irq, combiner); + + dev_info(&pdev->dev, "Initialized with [p=%d,n=%d,r=%p]\n", + combiner->parent_irq, combiner->nirqs, combiner->regs[0].addr); + return 0; +} + +static const struct acpi_device_id qcom_irq_combiner_ids[] = { + { "QCOM80B1", }, + { } +}; + +static struct platform_driver qcom_irq_combiner_probe = { + .driver = { + .name = "qcom-irq-combiner", + .acpi_match_table = ACPI_PTR(qcom_irq_combiner_ids), + }, + .probe = combiner_probe, +}; + +static int __init register_qcom_irq_combiner(void) +{ + return platform_driver_register(&qcom_irq_combiner_probe); +} +device_initcall(register_qcom_irq_combiner); |