diff options
Diffstat (limited to 'drivers/irqchip')
27 files changed, 872 insertions, 464 deletions
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 27b52c8729cd..4d7294e5d982 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -123,6 +123,7 @@ config RENESAS_INTC_IRQPIN config RENESAS_IRQC bool + select GENERIC_IRQ_CHIP select IRQ_DOMAIN config ST_IRQCHIP @@ -187,3 +188,8 @@ config IMX_GPCV2 select IRQ_DOMAIN help Enables the wakeup IRQs for IMX platforms with GPCv2 block + +config IRQ_MXS + def_bool y if MACH_ASM9260 || ARCH_MXS + select IRQ_DOMAIN + select STMP_DEVICE diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index bb3048f00e64..177f78f6e6d6 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -6,7 +6,7 @@ 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 +obj-$(CONFIG_IRQ_MXS) += irq-mxs.o obj-$(CONFIG_ARCH_TEGRA) += irq-tegra.o obj-$(CONFIG_ARCH_S3C24XX) += irq-s3c24xx.o obj-$(CONFIG_DW_APB_ICTL) += irq-dw-apb-ictl.o diff --git a/drivers/irqchip/alphascale_asm9260-icoll.h b/drivers/irqchip/alphascale_asm9260-icoll.h new file mode 100644 index 000000000000..5cec108ee204 --- /dev/null +++ b/drivers/irqchip/alphascale_asm9260-icoll.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2014 Oleksij Rempel <linux@rempel-privat.de> + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef _ALPHASCALE_ASM9260_ICOLL_H +#define _ALPHASCALE_ASM9260_ICOLL_H + +#define ASM9260_NUM_IRQS 64 +/* + * this device provide 4 offsets for each register: + * 0x0 - plain read write mode + * 0x4 - set mode, OR logic. + * 0x8 - clr mode, XOR logic. + * 0xc - togle mode. + */ + +#define ASM9260_HW_ICOLL_VECTOR 0x0000 +/* + * bits 31:2 + * This register presents the vector address for the interrupt currently + * active on the CPU IRQ input. Writing to this register notifies the + * interrupt collector that the interrupt service routine for the current + * interrupt has been entered. + * The exception trap should have a LDPC instruction from this address: + * LDPC ASM9260_HW_ICOLL_VECTOR_ADDR; IRQ exception at 0xffff0018 + */ + +/* + * The Interrupt Collector Level Acknowledge Register is used by software to + * indicate the completion of an interrupt on a specific level. + * This register is written at the very end of an interrupt service routine. If + * nesting is used then the CPU irq must be turned on before writing to this + * register to avoid a race condition in the CPU interrupt hardware. + */ +#define ASM9260_HW_ICOLL_LEVELACK 0x0010 +#define ASM9260_BM_LEVELn(nr) BIT(nr) + +#define ASM9260_HW_ICOLL_CTRL 0x0020 +/* + * ASM9260_BM_CTRL_SFTRST and ASM9260_BM_CTRL_CLKGATE are not available on + * asm9260. + */ +#define ASM9260_BM_CTRL_SFTRST BIT(31) +#define ASM9260_BM_CTRL_CLKGATE BIT(30) +/* disable interrupt level nesting */ +#define ASM9260_BM_CTRL_NO_NESTING BIT(19) +/* + * Set this bit to one enable the RISC32-style read side effect associated with + * the vector address register. In this mode, interrupt in-service is signaled + * by the read of the ASM9260_HW_ICOLL_VECTOR register to acquire the interrupt + * vector address. Set this bit to zero for normal operation, in which the ISR + * signals in-service explicitly by means of a write to the + * ASM9260_HW_ICOLL_VECTOR register. + * 0 - Must Write to Vector register to go in-service. + * 1 - Go in-service as a read side effect + */ +#define ASM9260_BM_CTRL_ARM_RSE_MODE BIT(18) +#define ASM9260_BM_CTRL_IRQ_ENABLE BIT(16) + +#define ASM9260_HW_ICOLL_STAT_OFFSET 0x0030 +/* + * bits 5:0 + * Vector number of current interrupt. Multiply by 4 and add to vector base + * address to obtain the value in ASM9260_HW_ICOLL_VECTOR. + */ + +/* + * RAW0 and RAW1 provides a read-only view of the raw interrupt request lines + * coming from various parts of the chip. Its purpose is to improve diagnostic + * observability. + */ +#define ASM9260_HW_ICOLL_RAW0 0x0040 +#define ASM9260_HW_ICOLL_RAW1 0x0050 + +#define ASM9260_HW_ICOLL_INTERRUPT0 0x0060 +#define ASM9260_HW_ICOLL_INTERRUPTn(n) (0x0060 + ((n) >> 2) * 0x10) +/* + * WARNING: Modifying the priority of an enabled interrupt may result in + * undefined behavior. + */ +#define ASM9260_BM_INT_PRIORITY_MASK 0x3 +#define ASM9260_BM_INT_ENABLE BIT(2) +#define ASM9260_BM_INT_SOFTIRQ BIT(3) + +#define ASM9260_BM_ICOLL_INTERRUPTn_SHIFT(n) (((n) & 0x3) << 3) +#define ASM9260_BM_ICOLL_INTERRUPTn_ENABLE(n) (1 << (2 + \ + ASM9260_BM_ICOLL_INTERRUPTn_SHIFT(n))) + +#define ASM9260_HW_ICOLL_VBASE 0x0160 +/* + * bits 31:2 + * This bitfield holds the upper 30 bits of the base address of the vector + * table. + */ + +#define ASM9260_HW_ICOLL_CLEAR0 0x01d0 +#define ASM9260_HW_ICOLL_CLEAR1 0x01e0 +#define ASM9260_HW_ICOLL_CLEARn(n) (((n >> 5) * 0x10) \ + + SET_REG) +#define ASM9260_BM_CLEAR_BIT(n) BIT(n & 0x1f) + +/* Scratchpad */ +#define ASM9260_HW_ICOLL_UNDEF_VECTOR 0x01f0 +#endif diff --git a/drivers/irqchip/exynos-combiner.c b/drivers/irqchip/exynos-combiner.c index cd7d3bc78e34..ead15be2d20a 100644 --- a/drivers/irqchip/exynos-combiner.c +++ b/drivers/irqchip/exynos-combiner.c @@ -144,7 +144,7 @@ static int combiner_irq_domain_xlate(struct irq_domain *d, unsigned long *out_hwirq, unsigned int *out_type) { - if (d->of_node != controller) + if (irq_domain_get_of_node(d) != controller) return -EINVAL; if (intsize < 2) diff --git a/drivers/irqchip/irq-atmel-aic-common.c b/drivers/irqchip/irq-atmel-aic-common.c index 63cd031b2c28..b12a5d58546f 100644 --- a/drivers/irqchip/irq-atmel-aic-common.c +++ b/drivers/irqchip/irq-atmel-aic-common.c @@ -114,7 +114,7 @@ int aic_common_irq_domain_xlate(struct irq_domain *d, static void __init aic_common_ext_irq_of_init(struct irq_domain *domain) { - struct device_node *node = domain->of_node; + struct device_node *node = irq_domain_get_of_node(domain); struct irq_chip_generic *gc; struct aic_chip_data *aic; struct property *prop; diff --git a/drivers/irqchip/irq-atmel-aic5.c b/drivers/irqchip/irq-atmel-aic5.c index f6d680485bee..62bb840c613f 100644 --- a/drivers/irqchip/irq-atmel-aic5.c +++ b/drivers/irqchip/irq-atmel-aic5.c @@ -70,16 +70,15 @@ static struct irq_domain *aic5_domain; static asmlinkage void __exception_irq_entry aic5_handle(struct pt_regs *regs) { - struct irq_domain_chip_generic *dgc = aic5_domain->gc; - struct irq_chip_generic *gc = dgc->gc[0]; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(aic5_domain, 0); u32 irqnr; u32 irqstat; - irqnr = irq_reg_readl(gc, AT91_AIC5_IVR); - irqstat = irq_reg_readl(gc, AT91_AIC5_ISR); + irqnr = irq_reg_readl(bgc, AT91_AIC5_IVR); + irqstat = irq_reg_readl(bgc, AT91_AIC5_ISR); if (!irqstat) - irq_reg_writel(gc, 0, AT91_AIC5_EOICR); + irq_reg_writel(bgc, 0, AT91_AIC5_EOICR); else handle_domain_irq(aic5_domain, irqnr, regs); } @@ -87,8 +86,7 @@ aic5_handle(struct pt_regs *regs) static void aic5_mask(struct irq_data *d) { struct irq_domain *domain = d->domain; - struct irq_domain_chip_generic *dgc = domain->gc; - struct irq_chip_generic *bgc = dgc->gc[0]; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); /* @@ -105,8 +103,7 @@ static void aic5_mask(struct irq_data *d) static void aic5_unmask(struct irq_data *d) { struct irq_domain *domain = d->domain; - struct irq_domain_chip_generic *dgc = domain->gc; - struct irq_chip_generic *bgc = dgc->gc[0]; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); /* @@ -123,14 +120,13 @@ static void aic5_unmask(struct irq_data *d) static int aic5_retrigger(struct irq_data *d) { struct irq_domain *domain = d->domain; - struct irq_domain_chip_generic *dgc = domain->gc; - struct irq_chip_generic *gc = dgc->gc[0]; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); /* Enable interrupt on AIC5 */ - irq_gc_lock(gc); - irq_reg_writel(gc, d->hwirq, AT91_AIC5_SSR); - irq_reg_writel(gc, 1, AT91_AIC5_ISCR); - irq_gc_unlock(gc); + irq_gc_lock(bgc); + irq_reg_writel(bgc, d->hwirq, AT91_AIC5_SSR); + irq_reg_writel(bgc, 1, AT91_AIC5_ISCR); + irq_gc_unlock(bgc); return 0; } @@ -138,18 +134,17 @@ static int aic5_retrigger(struct irq_data *d) static int aic5_set_type(struct irq_data *d, unsigned type) { struct irq_domain *domain = d->domain; - struct irq_domain_chip_generic *dgc = domain->gc; - struct irq_chip_generic *gc = dgc->gc[0]; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); unsigned int smr; int ret; - irq_gc_lock(gc); - irq_reg_writel(gc, d->hwirq, AT91_AIC5_SSR); - smr = irq_reg_readl(gc, AT91_AIC5_SMR); + irq_gc_lock(bgc); + irq_reg_writel(bgc, d->hwirq, AT91_AIC5_SSR); + smr = irq_reg_readl(bgc, AT91_AIC5_SMR); ret = aic_common_set_type(d, type, &smr); if (!ret) - irq_reg_writel(gc, smr, AT91_AIC5_SMR); - irq_gc_unlock(gc); + irq_reg_writel(bgc, smr, AT91_AIC5_SMR); + irq_gc_unlock(bgc); return ret; } @@ -159,7 +154,7 @@ static void aic5_suspend(struct irq_data *d) { struct irq_domain *domain = d->domain; struct irq_domain_chip_generic *dgc = domain->gc; - struct irq_chip_generic *bgc = dgc->gc[0]; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); int i; u32 mask; @@ -183,7 +178,7 @@ static void aic5_resume(struct irq_data *d) { struct irq_domain *domain = d->domain; struct irq_domain_chip_generic *dgc = domain->gc; - struct irq_chip_generic *bgc = dgc->gc[0]; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); int i; u32 mask; @@ -207,7 +202,7 @@ static void aic5_pm_shutdown(struct irq_data *d) { struct irq_domain *domain = d->domain; struct irq_domain_chip_generic *dgc = domain->gc; - struct irq_chip_generic *bgc = dgc->gc[0]; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(domain, 0); struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); int i; @@ -262,12 +257,11 @@ static int aic5_irq_domain_xlate(struct irq_domain *d, irq_hw_number_t *out_hwirq, unsigned int *out_type) { - struct irq_domain_chip_generic *dgc = d->gc; - struct irq_chip_generic *gc; + struct irq_chip_generic *bgc = irq_get_domain_generic_chip(d, 0); unsigned smr; int ret; - if (!dgc) + if (!bgc) return -EINVAL; ret = aic_common_irq_domain_xlate(d, ctrlr, intspec, intsize, @@ -275,15 +269,13 @@ static int aic5_irq_domain_xlate(struct irq_domain *d, if (ret) return ret; - gc = dgc->gc[0]; - - irq_gc_lock(gc); - irq_reg_writel(gc, *out_hwirq, AT91_AIC5_SSR); - smr = irq_reg_readl(gc, AT91_AIC5_SMR); + irq_gc_lock(bgc); + irq_reg_writel(bgc, *out_hwirq, AT91_AIC5_SSR); + smr = irq_reg_readl(bgc, AT91_AIC5_SMR); ret = aic_common_set_priority(intspec[2], &smr); if (!ret) - irq_reg_writel(gc, intspec[2] | smr, AT91_AIC5_SMR); - irq_gc_unlock(gc); + irq_reg_writel(bgc, intspec[2] | smr, AT91_AIC5_SMR); + irq_gc_unlock(bgc); return ret; } diff --git a/drivers/irqchip/irq-crossbar.c b/drivers/irqchip/irq-crossbar.c index a7f5626930f5..75573fa431ba 100644 --- a/drivers/irqchip/irq-crossbar.c +++ b/drivers/irqchip/irq-crossbar.c @@ -78,10 +78,13 @@ static struct irq_chip crossbar_chip = { static int allocate_gic_irq(struct irq_domain *domain, unsigned virq, irq_hw_number_t hwirq) { - struct of_phandle_args args; + struct irq_fwspec fwspec; int i; int err; + if (!irq_domain_get_of_node(domain->parent)) + return -EINVAL; + raw_spin_lock(&cb->lock); for (i = cb->int_max - 1; i >= 0; i--) { if (cb->irq_map[i] == IRQ_FREE) { @@ -94,13 +97,13 @@ static int allocate_gic_irq(struct irq_domain *domain, unsigned virq, if (i < 0) return -ENODEV; - args.np = domain->parent->of_node; - args.args_count = 3; - args.args[0] = 0; /* SPI */ - args.args[1] = i; - args.args[2] = IRQ_TYPE_LEVEL_HIGH; + fwspec.fwnode = domain->parent->fwnode; + fwspec.param_count = 3; + fwspec.param[0] = 0; /* SPI */ + fwspec.param[1] = i; + fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH; - err = irq_domain_alloc_irqs_parent(domain, virq, 1, &args); + err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); if (err) cb->irq_map[i] = IRQ_FREE; else @@ -112,16 +115,16 @@ static int allocate_gic_irq(struct irq_domain *domain, unsigned virq, static int crossbar_domain_alloc(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs, void *data) { - struct of_phandle_args *args = data; + struct irq_fwspec *fwspec = data; irq_hw_number_t hwirq; int i; - if (args->args_count != 3) + if (fwspec->param_count != 3) return -EINVAL; /* Not GIC compliant */ - if (args->args[0] != 0) + if (fwspec->param[0] != 0) return -EINVAL; /* No PPI should point to this domain */ - hwirq = args->args[1]; + hwirq = fwspec->param[1]; if ((hwirq + nr_irqs) > cb->max_crossbar_sources) return -EINVAL; /* Can't deal with this */ @@ -166,28 +169,31 @@ static void crossbar_domain_free(struct irq_domain *domain, unsigned int virq, raw_spin_unlock(&cb->lock); } -static int crossbar_domain_xlate(struct irq_domain *d, - struct device_node *controller, - const u32 *intspec, unsigned int intsize, - unsigned long *out_hwirq, - unsigned int *out_type) +static int crossbar_domain_translate(struct irq_domain *d, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) { - if (d->of_node != controller) - return -EINVAL; /* Shouldn't happen, really... */ - if (intsize != 3) - return -EINVAL; /* Not GIC compliant */ - if (intspec[0] != 0) - return -EINVAL; /* No PPI should point to this domain */ + if (is_of_node(fwspec->fwnode)) { + if (fwspec->param_count != 3) + return -EINVAL; - *out_hwirq = intspec[1]; - *out_type = intspec[2]; - return 0; + /* No PPI should point to this domain */ + if (fwspec->param[0] != 0) + return -EINVAL; + + *hwirq = fwspec->param[1]; + *type = fwspec->param[2]; + return 0; + } + + return -EINVAL; } static const struct irq_domain_ops crossbar_domain_ops = { - .alloc = crossbar_domain_alloc, - .free = crossbar_domain_free, - .xlate = crossbar_domain_xlate, + .alloc = crossbar_domain_alloc, + .free = crossbar_domain_free, + .translate = crossbar_domain_translate, }; static int __init crossbar_of_init(struct device_node *node) diff --git a/drivers/irqchip/irq-gic-common.c b/drivers/irqchip/irq-gic-common.c index 9448e391cb71..44a077f3a4a2 100644 --- a/drivers/irqchip/irq-gic-common.c +++ b/drivers/irqchip/irq-gic-common.c @@ -21,6 +21,17 @@ #include "irq-gic-common.h" +void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks, + void *data) +{ + for (; quirks->desc; quirks++) { + if (quirks->iidr != (quirks->mask & iidr)) + continue; + quirks->init(data); + pr_info("GIC: enabling workaround for %s\n", quirks->desc); + } +} + int gic_configure_irq(unsigned int irq, unsigned int type, void __iomem *base, void (*sync_access)(void)) { diff --git a/drivers/irqchip/irq-gic-common.h b/drivers/irqchip/irq-gic-common.h index 35a9884778bd..fff697db8e22 100644 --- a/drivers/irqchip/irq-gic-common.h +++ b/drivers/irqchip/irq-gic-common.h @@ -20,10 +20,19 @@ #include <linux/of.h> #include <linux/irqdomain.h> +struct gic_quirk { + const char *desc; + void (*init)(void *data); + u32 iidr; + u32 mask; +}; + int gic_configure_irq(unsigned int irq, unsigned int type, void __iomem *base, void (*sync_access)(void)); void gic_dist_config(void __iomem *base, int gic_irqs, void (*sync_access)(void)); void gic_cpu_config(void __iomem *base, void (*sync_access)(void)); +void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks, + void *data); #endif /* _IRQ_GIC_COMMON_H */ diff --git a/drivers/irqchip/irq-gic-v2m.c b/drivers/irqchip/irq-gic-v2m.c index 12985daa66ab..87f8d104acab 100644 --- a/drivers/irqchip/irq-gic-v2m.c +++ b/drivers/irqchip/irq-gic-v2m.c @@ -37,19 +37,31 @@ #define V2M_MSI_SETSPI_NS 0x040 #define V2M_MIN_SPI 32 #define V2M_MAX_SPI 1019 +#define V2M_MSI_IIDR 0xFCC #define V2M_MSI_TYPER_BASE_SPI(x) \ (((x) >> V2M_MSI_TYPER_BASE_SHIFT) & V2M_MSI_TYPER_BASE_MASK) #define V2M_MSI_TYPER_NUM_SPI(x) ((x) & V2M_MSI_TYPER_NUM_MASK) +/* APM X-Gene with GICv2m MSI_IIDR register value */ +#define XGENE_GICV2M_MSI_IIDR 0x06000170 + +/* List of flags for specific v2m implementation */ +#define GICV2M_NEEDS_SPI_OFFSET 0x00000001 + +static LIST_HEAD(v2m_nodes); +static DEFINE_SPINLOCK(v2m_lock); + struct v2m_data { - spinlock_t msi_cnt_lock; + struct list_head entry; + struct device_node *node; struct resource res; /* GICv2m resource */ void __iomem *base; /* GICv2m virt address */ u32 spi_start; /* The SPI number that MSIs start */ u32 nr_spis; /* The number of SPIs for MSIs */ unsigned long *bm; /* MSI vector bitmap */ + u32 flags; /* v2m flags for specific implementation */ }; static void gicv2m_mask_msi_irq(struct irq_data *d) @@ -98,6 +110,9 @@ static void gicv2m_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) msg->address_hi = upper_32_bits(addr); msg->address_lo = lower_32_bits(addr); msg->data = data->hwirq; + + if (v2m->flags & GICV2M_NEEDS_SPI_OFFSET) + msg->data -= v2m->spi_start; } static struct irq_chip gicv2m_irq_chip = { @@ -113,17 +128,21 @@ static int gicv2m_irq_gic_domain_alloc(struct irq_domain *domain, unsigned int virq, irq_hw_number_t hwirq) { - struct of_phandle_args args; + struct irq_fwspec fwspec; struct irq_data *d; int err; - args.np = domain->parent->of_node; - args.args_count = 3; - args.args[0] = 0; - args.args[1] = hwirq - 32; - args.args[2] = IRQ_TYPE_EDGE_RISING; + if (is_of_node(domain->parent->fwnode)) { + fwspec.fwnode = domain->parent->fwnode; + fwspec.param_count = 3; + fwspec.param[0] = 0; + fwspec.param[1] = hwirq - 32; + fwspec.param[2] = IRQ_TYPE_EDGE_RISING; + } else { + return -EINVAL; + } - err = irq_domain_alloc_irqs_parent(domain, virq, 1, &args); + err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec); if (err) return err; @@ -143,27 +162,30 @@ static void gicv2m_unalloc_msi(struct v2m_data *v2m, unsigned int hwirq) return; } - spin_lock(&v2m->msi_cnt_lock); + spin_lock(&v2m_lock); __clear_bit(pos, v2m->bm); - spin_unlock(&v2m->msi_cnt_lock); + spin_unlock(&v2m_lock); } static int gicv2m_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs, void *args) { - struct v2m_data *v2m = domain->host_data; + struct v2m_data *v2m = NULL, *tmp; int hwirq, offset, err = 0; - spin_lock(&v2m->msi_cnt_lock); - offset = find_first_zero_bit(v2m->bm, v2m->nr_spis); - if (offset < v2m->nr_spis) - __set_bit(offset, v2m->bm); - else - err = -ENOSPC; - spin_unlock(&v2m->msi_cnt_lock); + spin_lock(&v2m_lock); + list_for_each_entry(tmp, &v2m_nodes, entry) { + offset = find_first_zero_bit(tmp->bm, tmp->nr_spis); + if (offset < tmp->nr_spis) { + __set_bit(offset, tmp->bm); + v2m = tmp; + break; + } + } + spin_unlock(&v2m_lock); - if (err) - return err; + if (!v2m) + return -ENOSPC; hwirq = v2m->spi_start + offset; @@ -224,12 +246,61 @@ static struct msi_domain_info gicv2m_pmsi_domain_info = { .chip = &gicv2m_pmsi_irq_chip, }; +static void gicv2m_teardown(void) +{ + struct v2m_data *v2m, *tmp; + + list_for_each_entry_safe(v2m, tmp, &v2m_nodes, entry) { + list_del(&v2m->entry); + kfree(v2m->bm); + iounmap(v2m->base); + of_node_put(v2m->node); + kfree(v2m); + } +} + +static int gicv2m_allocate_domains(struct irq_domain *parent) +{ + struct irq_domain *inner_domain, *pci_domain, *plat_domain; + struct v2m_data *v2m; + + v2m = list_first_entry_or_null(&v2m_nodes, struct v2m_data, entry); + if (!v2m) + return 0; + + inner_domain = irq_domain_create_tree(of_node_to_fwnode(v2m->node), + &gicv2m_domain_ops, v2m); + if (!inner_domain) { + pr_err("Failed to create GICv2m domain\n"); + return -ENOMEM; + } + + inner_domain->bus_token = DOMAIN_BUS_NEXUS; + inner_domain->parent = parent; + pci_domain = pci_msi_create_irq_domain(of_node_to_fwnode(v2m->node), + &gicv2m_msi_domain_info, + inner_domain); + plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(v2m->node), + &gicv2m_pmsi_domain_info, + inner_domain); + if (!pci_domain || !plat_domain) { + pr_err("Failed to create MSI domains\n"); + if (plat_domain) + irq_domain_remove(plat_domain); + if (pci_domain) + irq_domain_remove(pci_domain); + irq_domain_remove(inner_domain); + return -ENOMEM; + } + + return 0; +} + static int __init gicv2m_init_one(struct device_node *node, struct irq_domain *parent) { int ret; struct v2m_data *v2m; - struct irq_domain *inner_domain, *pci_domain, *plat_domain; v2m = kzalloc(sizeof(struct v2m_data), GFP_KERNEL); if (!v2m) { @@ -237,6 +308,9 @@ static int __init gicv2m_init_one(struct device_node *node, return -ENOMEM; } + INIT_LIST_HEAD(&v2m->entry); + v2m->node = node; + ret = of_address_to_resource(node, 0, &v2m->res); if (ret) { pr_err("Failed to allocate v2m resource.\n"); @@ -266,6 +340,17 @@ static int __init gicv2m_init_one(struct device_node *node, goto err_iounmap; } + /* + * APM X-Gene GICv2m implementation has an erratum where + * the MSI data needs to be the offset from the spi_start + * in order to trigger the correct MSI interrupt. This is + * different from the standard GICv2m implementation where + * the MSI data is the absolute value within the range from + * spi_start to (spi_start + num_spis). + */ + if (readl_relaxed(v2m->base + V2M_MSI_IIDR) == XGENE_GICV2M_MSI_IIDR) + v2m->flags |= GICV2M_NEEDS_SPI_OFFSET; + v2m->bm = kzalloc(sizeof(long) * BITS_TO_LONGS(v2m->nr_spis), GFP_KERNEL); if (!v2m->bm) { @@ -273,43 +358,13 @@ static int __init gicv2m_init_one(struct device_node *node, goto err_iounmap; } - inner_domain = irq_domain_add_tree(node, &gicv2m_domain_ops, v2m); - if (!inner_domain) { - pr_err("Failed to create GICv2m domain\n"); - ret = -ENOMEM; - goto err_free_bm; - } - - inner_domain->bus_token = DOMAIN_BUS_NEXUS; - inner_domain->parent = parent; - pci_domain = pci_msi_create_irq_domain(node, &gicv2m_msi_domain_info, - inner_domain); - plat_domain = platform_msi_create_irq_domain(node, - &gicv2m_pmsi_domain_info, - inner_domain); - if (!pci_domain || !plat_domain) { - pr_err("Failed to create MSI domains\n"); - ret = -ENOMEM; - goto err_free_domains; - } - - spin_lock_init(&v2m->msi_cnt_lock); - + list_add_tail(&v2m->entry, &v2m_nodes); pr_info("Node %s: range[%#lx:%#lx], SPI[%d:%d]\n", node->name, (unsigned long)v2m->res.start, (unsigned long)v2m->res.end, v2m->spi_start, (v2m->spi_start + v2m->nr_spis)); return 0; -err_free_domains: - if (plat_domain) - irq_domain_remove(plat_domain); - if (pci_domain) - irq_domain_remove(pci_domain); - if (inner_domain) - irq_domain_remove(inner_domain); -err_free_bm: - kfree(v2m->bm); err_iounmap: iounmap(v2m->base); err_free_v2m: @@ -339,5 +394,9 @@ int __init gicv2m_of_init(struct device_node *node, struct irq_domain *parent) } } + if (!ret) + ret = gicv2m_allocate_domains(parent); + if (ret) + gicv2m_teardown(); return ret; } diff --git a/drivers/irqchip/irq-gic-v3-its-pci-msi.c b/drivers/irqchip/irq-gic-v3-its-pci-msi.c index a7c8c9ffbafd..aee60ed025dc 100644 --- a/drivers/irqchip/irq-gic-v3-its-pci-msi.c +++ b/drivers/irqchip/irq-gic-v3-its-pci-msi.c @@ -42,7 +42,6 @@ static struct irq_chip its_msi_irq_chip = { struct its_pci_alias { struct pci_dev *pdev; - u32 dev_id; u32 count; }; @@ -60,7 +59,6 @@ static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data) { struct its_pci_alias *dev_alias = data; - dev_alias->dev_id = alias; if (pdev != dev_alias->pdev) dev_alias->count += its_pci_msi_vec_count(pdev); @@ -86,7 +84,7 @@ static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev, pci_for_each_dma_alias(pdev, its_get_pci_alias, &dev_alias); /* ITS specific DeviceID, as the core ITS ignores dev. */ - info->scratchpad[0].ul = dev_alias.dev_id; + info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain, pdev); return msi_info->ops->msi_prepare(domain->parent, dev, dev_alias.count, info); @@ -125,7 +123,8 @@ static int __init its_pci_msi_init(void) continue; } - if (!pci_msi_create_irq_domain(np, &its_pci_msi_domain_info, + if (!pci_msi_create_irq_domain(of_node_to_fwnode(np), + &its_pci_msi_domain_info, parent)) { pr_err("%s: unable to create PCI domain\n", np->full_name); diff --git a/drivers/irqchip/irq-gic-v3-its-platform-msi.c b/drivers/irqchip/irq-gic-v3-its-platform-msi.c index a86550562779..470b4aa7d62c 100644 --- a/drivers/irqchip/irq-gic-v3-its-platform-msi.c +++ b/drivers/irqchip/irq-gic-v3-its-platform-msi.c @@ -29,13 +29,25 @@ static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, { struct msi_domain_info *msi_info; u32 dev_id; - int ret; + int ret, index = 0; msi_info = msi_get_domain_info(domain->parent); /* Suck the DeviceID out of the msi-parent property */ - ret = of_property_read_u32_index(dev->of_node, "msi-parent", - 1, &dev_id); + do { + struct of_phandle_args args; + + ret = of_parse_phandle_with_args(dev->of_node, + "msi-parent", "#msi-cells", + index, &args); + if (args.np == irq_domain_get_of_node(domain)) { + if (WARN_ON(args.args_count != 1)) + return -EINVAL; + dev_id = args.args[0]; + break; + } + } while (!ret); + if (ret) return ret; @@ -78,7 +90,8 @@ static int __init its_pmsi_init(void) continue; } - if (!platform_msi_create_irq_domain(np, &its_pmsi_domain_info, + if (!platform_msi_create_irq_domain(of_node_to_fwnode(np), + &its_pmsi_domain_info, parent)) { pr_err("%s: unable to create platform domain\n", np->full_name); diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 25ceae9f7348..e23d1d18f9d6 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/ |
