Commit 8d9d51b6 authored by Thomas Gleixner's avatar Thomas Gleixner

Merge tag 'irqchip-4.13' of...

Merge tag 'irqchip-4.13' of git://git.kernel.org/pub/scm/linux/kernel/git/maz/arm-platforms into irq/core

Pull irqchip updates for v4.13 from Marc Zyngier

- support for the new Marvell wire-to-MSI bridge
- support for the Aspeed I2C irqchip
- Armada XP370 per-cpu interrupt fixes
- GICv3 ITS ACPI NUMA support
- sunxi-nmi cleanup and updates for new platform support
- various GICv3 ITS cleanups and fixes
- some constifying in various places
parents 6a6544e5 6c31e123
...@@ -3,8 +3,11 @@ Allwinner Sunxi NMI Controller ...@@ -3,8 +3,11 @@ Allwinner Sunxi NMI Controller
Required properties: Required properties:
- compatible : should be "allwinner,sun7i-a20-sc-nmi" or - compatible : should be one of the following:
"allwinner,sun6i-a31-sc-nmi" or "allwinner,sun9i-a80-nmi" - "allwinner,sun7i-a20-sc-nmi"
- "allwinner,sun6i-a31-sc-nmi" (deprecated)
- "allwinner,sun6i-a31-r-intc"
- "allwinner,sun9i-a80-nmi"
- reg : Specifies base physical address and size of the registers. - reg : Specifies base physical address and size of the registers.
- interrupt-controller : Identifies the node as an interrupt controller - interrupt-controller : Identifies the node as an interrupt controller
- #interrupt-cells : Specifies the number of cells needed to encode an - #interrupt-cells : Specifies the number of cells needed to encode an
......
Device tree configuration for the I2C Interrupt Controller on the AST24XX and
AST25XX SoCs.
Required Properties:
- #address-cells : should be 1
- #size-cells : should be 1
- #interrupt-cells : should be 1
- compatible : should be "aspeed,ast2400-i2c-ic"
or "aspeed,ast2500-i2c-ic"
- reg : address start and range of controller
- interrupts : interrupt number
- interrupt-controller : denotes that the controller receives and fires
new interrupts for child busses
Example:
i2c_ic: interrupt-controller@0 {
#address-cells = <1>;
#size-cells = <1>;
#interrupt-cells = <1>;
compatible = "aspeed,ast2400-i2c-ic";
reg = <0x0 0x40>;
interrupts = <12>;
interrupt-controller;
};
Aspeed Vectored Interrupt Controller Aspeed Vectored Interrupt Controller
These bindings are for the Aspeed AST2400 interrupt controller register layout. These bindings are for the Aspeed interrupt controller. The AST2400 and
The SoC has an legacy register layout, but this driver does not support that AST2500 SoC families include a legacy register layout before a re-designed
mode of operation. layout, but the bindings do not prescribe the use of one or the other.
Required properties: Required properties:
- compatible : should be "aspeed,ast2400-vic". - compatible : "aspeed,ast2400-vic"
"aspeed,ast2500-vic"
- interrupt-controller : Identifies the node as an interrupt controller - interrupt-controller : Identifies the node as an interrupt controller
- #interrupt-cells : Specifies the number of cells needed to encode an - #interrupt-cells : Specifies the number of cells needed to encode an
......
Marvell GICP Controller
-----------------------
GICP is a Marvell extension of the GIC that allows to trigger GIC SPI
interrupts by doing a memory transaction. It is used by the ICU
located in the Marvell CP110 to turn wired interrupts inside the CP
into GIC SPI interrupts.
Required properties:
- compatible: Must be "marvell,ap806-gicp"
- reg: Must be the address and size of the GICP SPI registers
- marvell,spi-ranges: tuples of GIC SPI interrupts ranges available
for this GICP
- msi-controller: indicates that this is an MSI controller
Example:
gicp_spi: gicp-spi@3f0040 {
compatible = "marvell,ap806-gicp";
reg = <0x3f0040 0x10>;
marvell,spi-ranges = <64 64>, <288 64>;
msi-controller;
};
Marvell ICU Interrupt Controller
--------------------------------
The Marvell ICU (Interrupt Consolidation Unit) controller is
responsible for collecting all wired-interrupt sources in the CP and
communicating them to the GIC in the AP, the unit translates interrupt
requests on input wires to MSG memory mapped transactions to the GIC.
Required properties:
- compatible: Should be "marvell,cp110-icu"
- reg: Should contain ICU registers location and length.
- #interrupt-cells: Specifies the number of cells needed to encode an
interrupt source. The value shall be 3.
The 1st cell is the group type of the ICU interrupt. Possible group
types are:
ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure
ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure
ICU_GRP_SEI (0x4) : System error interrupt
ICU_GRP_REI (0x5) : RAM error interrupt
The 2nd cell is the index of the interrupt in the ICU unit.
The 3rd cell is the type of the interrupt. See arm,gic.txt for
details.
- interrupt-controller: Identifies the node as an interrupt
controller.
- msi-parent: Should point to the GICP controller, the GIC extension
that allows to trigger interrupts using MSG memory mapped
transactions.
Example:
icu: interrupt-controller@1e0000 {
compatible = "marvell,cp110-icu";
reg = <0x1e0000 0x10>;
#interrupt-cells = <3>;
interrupt-controller;
msi-parent = <&gicp>;
};
usb3h0: usb3@500000 {
interrupt-parent = <&icu>;
interrupts = <ICU_GRP_NSR 106 IRQ_TYPE_LEVEL_HIGH>;
};
...@@ -268,6 +268,12 @@ config IRQ_MXS ...@@ -268,6 +268,12 @@ config IRQ_MXS
select IRQ_DOMAIN select IRQ_DOMAIN
select STMP_DEVICE select STMP_DEVICE
config MVEBU_GICP
bool
config MVEBU_ICU
bool
config MVEBU_ODMI config MVEBU_ODMI
bool bool
select GENERIC_MSI_IRQ_DOMAIN select GENERIC_MSI_IRQ_DOMAIN
......
...@@ -69,10 +69,12 @@ obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o ...@@ -69,10 +69,12 @@ obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o
obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o
obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o
obj-$(CONFIG_PIC32_EVIC) += irq-pic32-evic.o obj-$(CONFIG_PIC32_EVIC) += irq-pic32-evic.o
obj-$(CONFIG_MVEBU_GICP) += irq-mvebu-gicp.o
obj-$(CONFIG_MVEBU_ICU) += irq-mvebu-icu.o
obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o
obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o
obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o
obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o
obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o
obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o
obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o
...@@ -34,25 +34,104 @@ ...@@ -34,25 +34,104 @@
#include <asm/smp_plat.h> #include <asm/smp_plat.h>
#include <asm/mach/irq.h> #include <asm/mach/irq.h>
/* Interrupt Controller Registers Map */ /*
#define ARMADA_370_XP_INT_SET_MASK_OFFS (0x48) * Overall diagram of the Armada XP interrupt controller:
#define ARMADA_370_XP_INT_CLEAR_MASK_OFFS (0x4C) *
#define ARMADA_370_XP_INT_FABRIC_MASK_OFFS (0x54) * To CPU 0 To CPU 1
#define ARMADA_370_XP_INT_CAUSE_PERF(cpu) (1 << cpu) *
* /\ /\
* || ||
* +---------------+ +---------------+
* | | | |
* | per-CPU | | per-CPU |
* | mask/unmask | | mask/unmask |
* | CPU0 | | CPU1 |
* | | | |
* +---------------+ +---------------+
* /\ /\
* || ||
* \\_______________________//
* ||
* +-------------------+
* | |
* | Global interrupt |
* | mask/unmask |
* | |
* +-------------------+
* /\
* ||
* interrupt from
* device
*
* The "global interrupt mask/unmask" is modified using the
* ARMADA_370_XP_INT_SET_ENABLE_OFFS and
* ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS registers, which are relative
* to "main_int_base".
*
* The "per-CPU mask/unmask" is modified using the
* ARMADA_370_XP_INT_SET_MASK_OFFS and
* ARMADA_370_XP_INT_CLEAR_MASK_OFFS registers, which are relative to
* "per_cpu_int_base". This base address points to a special address,
* which automatically accesses the registers of the current CPU.
*
* The per-CPU mask/unmask can also be adjusted using the global
* per-interrupt ARMADA_370_XP_INT_SOURCE_CTL register, which we use
* to configure interrupt affinity.
*
* Due to this model, all interrupts need to be mask/unmasked at two
* different levels: at the global level and at the per-CPU level.
*
* This driver takes the following approach to deal with this:
*
* - For global interrupts:
*
* At ->map() time, a global interrupt is unmasked at the per-CPU
* mask/unmask level. It is therefore unmasked at this level for
* the current CPU, running the ->map() code. This allows to have
* the interrupt unmasked at this level in non-SMP
* configurations. In SMP configurations, the ->set_affinity()
* callback is called, which using the
* ARMADA_370_XP_INT_SOURCE_CTL() readjusts the per-CPU mask/unmask
* for the interrupt.
*
* The ->mask() and ->unmask() operations only mask/unmask the
* interrupt at the "global" level.
*
* So, a global interrupt is enabled at the per-CPU level as soon
* as it is mapped. At run time, the masking/unmasking takes place
* at the global level.
*
* - For per-CPU interrupts
*
* At ->map() time, a per-CPU interrupt is unmasked at the global
* mask/unmask level.
*
* The ->mask() and ->unmask() operations mask/unmask the interrupt
* at the per-CPU level.
*
* So, a per-CPU interrupt is enabled at the global level as soon
* as it is mapped. At run time, the masking/unmasking takes place
* at the per-CPU level.
*/
/* Registers relative to main_int_base */
#define ARMADA_370_XP_INT_CONTROL (0x00) #define ARMADA_370_XP_INT_CONTROL (0x00)
#define ARMADA_370_XP_SW_TRIG_INT_OFFS (0x04)
#define ARMADA_370_XP_INT_SET_ENABLE_OFFS (0x30) #define ARMADA_370_XP_INT_SET_ENABLE_OFFS (0x30)
#define ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS (0x34) #define ARMADA_370_XP_INT_CLEAR_ENABLE_OFFS (0x34)
#define ARMADA_370_XP_INT_SOURCE_CTL(irq) (0x100 + irq*4) #define ARMADA_370_XP_INT_SOURCE_CTL(irq) (0x100 + irq*4)
#define ARMADA_370_XP_INT_SOURCE_CPU_MASK 0xF #define ARMADA_370_XP_INT_SOURCE_CPU_MASK 0xF
#define ARMADA_370_XP_INT_IRQ_FIQ_MASK(cpuid) ((BIT(0) | BIT(8)) << cpuid) #define ARMADA_370_XP_INT_IRQ_FIQ_MASK(cpuid) ((BIT(0) | BIT(8)) << cpuid)
#define ARMADA_370_XP_CPU_INTACK_OFFS (0x44) /* Registers relative to per_cpu_int_base */
#define ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS (0x08)
#define ARMADA_370_XP_IN_DRBEL_MSK_OFFS (0x0c)
#define ARMADA_375_PPI_CAUSE (0x10) #define ARMADA_375_PPI_CAUSE (0x10)
#define ARMADA_370_XP_CPU_INTACK_OFFS (0x44)
#define ARMADA_370_XP_SW_TRIG_INT_OFFS (0x4) #define ARMADA_370_XP_INT_SET_MASK_OFFS (0x48)
#define ARMADA_370_XP_IN_DRBEL_MSK_OFFS (0xc) #define ARMADA_370_XP_INT_CLEAR_MASK_OFFS (0x4C)
#define ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS (0x8) #define ARMADA_370_XP_INT_FABRIC_MASK_OFFS (0x54)
#define ARMADA_370_XP_INT_CAUSE_PERF(cpu) (1 << cpu)
#define ARMADA_370_XP_MAX_PER_CPU_IRQS (28) #define ARMADA_370_XP_MAX_PER_CPU_IRQS (28)
...@@ -281,13 +360,11 @@ static int armada_370_xp_mpic_irq_map(struct irq_domain *h, ...@@ -281,13 +360,11 @@ static int armada_370_xp_mpic_irq_map(struct irq_domain *h,
irq_set_percpu_devid(virq); irq_set_percpu_devid(virq);
irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip, irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
handle_percpu_devid_irq); handle_percpu_devid_irq);
} else { } else {
irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip, irq_set_chip_and_handler(virq, &armada_370_xp_irq_chip,
handle_level_irq); handle_level_irq);
} }
irq_set_probe(virq); irq_set_probe(virq);
irq_clear_status_flags(virq, IRQ_NOAUTOEN);
return 0; return 0;
} }
...@@ -345,16 +422,40 @@ static void armada_mpic_send_doorbell(const struct cpumask *mask, ...@@ -345,16 +422,40 @@ static void armada_mpic_send_doorbell(const struct cpumask *mask,
ARMADA_370_XP_SW_TRIG_INT_OFFS); ARMADA_370_XP_SW_TRIG_INT_OFFS);
} }
static void armada_xp_mpic_reenable_percpu(void)
{
unsigned int irq;
/* Re-enable per-CPU interrupts that were enabled before suspend */
for (irq = 0; irq < ARMADA_370_XP_MAX_PER_CPU_IRQS; irq++) {
struct irq_data *data;
int virq;
virq = irq_linear_revmap(armada_370_xp_mpic_domain, irq);
if (virq == 0)
continue;
data = irq_get_irq_data(virq);
if (!irq_percpu_is_enabled(virq))
continue;
armada_370_xp_irq_unmask(data);
}
}
static int armada_xp_mpic_starting_cpu(unsigned int cpu) static int armada_xp_mpic_starting_cpu(unsigned int cpu)
{ {
armada_xp_mpic_perf_init(); armada_xp_mpic_perf_init();
armada_xp_mpic_smp_cpu_init(); armada_xp_mpic_smp_cpu_init();
armada_xp_mpic_reenable_percpu();
return 0; return 0;
} }
static int mpic_cascaded_starting_cpu(unsigned int cpu) static int mpic_cascaded_starting_cpu(unsigned int cpu)
{ {
armada_xp_mpic_perf_init(); armada_xp_mpic_perf_init();
armada_xp_mpic_reenable_percpu();
enable_percpu_irq(parent_irq, IRQ_TYPE_NONE); enable_percpu_irq(parent_irq, IRQ_TYPE_NONE);
return 0; return 0;
} }
...@@ -502,17 +603,28 @@ static void armada_370_xp_mpic_resume(void) ...@@ -502,17 +603,28 @@ static void armada_370_xp_mpic_resume(void)
if (virq == 0) if (virq == 0)
continue; continue;
if (!is_percpu_irq(irq)) data = irq_get_irq_data(virq);
if (!is_percpu_irq(irq)) {
/* Non per-CPU interrupts */
writel(irq, per_cpu_int_base + writel(irq, per_cpu_int_base +
ARMADA_370_XP_INT_CLEAR_MASK_OFFS); ARMADA_370_XP_INT_CLEAR_MASK_OFFS);
else if (!irqd_irq_disabled(data))
armada_370_xp_irq_unmask(data);
} else {
/* Per-CPU interrupts */
writel(irq, main_int_base + writel(irq, main_int_base +
ARMADA_370_XP_INT_SET_ENABLE_OFFS); ARMADA_370_XP_INT_SET_ENABLE_OFFS);
data = irq_get_irq_data(virq); /*
if (!irqd_irq_disabled(data)) * Re-enable on the current CPU,
* armada_xp_mpic_reenable_percpu() will take
* care of secondary CPUs when they come up.
*/
if (irq_percpu_is_enabled(virq))
armada_370_xp_irq_unmask(data); armada_370_xp_irq_unmask(data);
} }
}
/* Reconfigure doorbells for IPIs and MSIs */ /* Reconfigure doorbells for IPIs and MSIs */
writel(doorbell_mask_reg, writel(doorbell_mask_reg,
......
/*
* Aspeed 24XX/25XX I2C Interrupt Controller.
*
* Copyright (C) 2012-2017 ASPEED Technology Inc.
* Copyright 2017 IBM Corporation
* Copyright 2017 Google, Inc.
*
* 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.
*/
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/io.h>
#define ASPEED_I2C_IC_NUM_BUS 14
struct aspeed_i2c_ic {
void __iomem *base;
int parent_irq;
struct irq_domain *irq_domain;
};
/*
* The aspeed chip provides a single hardware interrupt for all of the I2C
* busses, so we use a dummy interrupt chip to translate this single interrupt
* into multiple interrupts, each associated with a single I2C bus.
*/
static void aspeed_i2c_ic_irq_handler(struct irq_desc *desc)
{
struct aspeed_i2c_ic *i2c_ic = irq_desc_get_handler_data(desc);
struct irq_chip *chip = irq_desc_get_chip(desc);
unsigned long bit, status;
unsigned int bus_irq;
chained_irq_enter(chip, desc);
status = readl(i2c_ic->base);
for_each_set_bit(bit, &status, ASPEED_I2C_IC_NUM_BUS) {
bus_irq = irq_find_mapping(i2c_ic->irq_domain, bit);
generic_handle_irq(bus_irq);
}
chained_irq_exit(chip, desc);
}
/*
* Set simple handler and mark IRQ as valid. Nothing interesting to do here
* since we are using a dummy interrupt chip.
*/
static int aspeed_i2c_ic_map_irq_domain(struct irq_domain *domain,
unsigned int irq, irq_hw_number_t hwirq)
{
irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq);
irq_set_chip_data(irq, domain->host_data);
return 0;
}
static const struct irq_domain_ops aspeed_i2c_ic_irq_domain_ops = {
.map = aspeed_i2c_ic_map_irq_domain,
};
static int __init aspeed_i2c_ic_of_init(struct device_node *node,
struct device_node *parent)
{
struct aspeed_i2c_ic *i2c_ic;
int ret = 0;
i2c_ic = kzalloc(sizeof(*i2c_ic), GFP_KERNEL);
if (!i2c_ic)
return -ENOMEM;
i2c_ic->base = of_iomap(node, 0);
if (IS_ERR(i2c_ic->base)) {
ret = PTR_ERR(i2c_ic->base);
goto err_free_ic;
}
i2c_ic->parent_irq = irq_of_parse_and_map(node, 0);
if (i2c_ic->parent_irq < 0) {
ret = i2c_ic->parent_irq;
goto err_iounmap;
}
i2c_ic->irq_domain = irq_domain_add_linear(node, ASPEED_I2C_IC_NUM_BUS,
&aspeed_i2c_ic_irq_domain_ops,
NULL);
if (!i2c_ic->irq_domain) {
ret = -ENOMEM;
goto err_iounmap;
}
i2c_ic->irq_domain->name = "aspeed-i2c-domain";
irq_set_chained_handler_and_data(i2c_ic->parent_irq,
aspeed_i2c_ic_irq_handler, i2c_ic);
pr_info("i2c controller registered, irq %d\n", i2c_ic->parent_irq);
return 0;
err_iounmap:
iounmap(i2c_ic->base);
err_free_ic:
kfree(i2c_ic);
return ret;
}
IRQCHIP_DECLARE(ast2400_i2c_ic, "aspeed,ast2400-i2c-ic", aspeed_i2c_ic_of_init);
IRQCHIP_DECLARE(ast2500_i2c_ic, "aspeed,ast2500-i2c-ic", aspeed_i2c_ic_of_init);
...@@ -186,7 +186,7 @@ static int avic_map(struct irq_domain *d, unsigned int irq, ...@@ -186,7 +186,7 @@ static int avic_map(struct irq_domain *d, unsigned int irq,
return 0; return 0;
} }
static struct irq_domain_ops avic_dom_ops = { static const struct irq_domain_ops avic_dom_ops = {
.map = avic_map, .map = avic_map,
.xlate = irq_domain_xlate_onetwocell, .xlate = irq_domain_xlate_onetwocell,
}; };
...@@ -227,4 +227,5 @@ static int __init avic_of_init(struct device_node *node, ...@@ -227,4 +227,5 @@ static int __init avic_of_init(struct device_node *node,
return 0; return 0;
} }
IRQCHIP_DECLARE(aspeed_new_vic, "aspeed,ast2400-vic", avic_of_init); IRQCHIP_DECLARE(ast2400_vic, "aspeed,ast2400-vic", avic_of_init);
IRQCHIP_DECLARE(ast2500_vic, "aspeed,ast2500-vic", avic_of_init);
...@@ -41,27 +41,22 @@ static struct irq_chip its_msi_irq_chip = { ...@@ -41,27 +41,22 @@ static struct irq_chip its_msi_irq_chip = {
.irq_write_msi_msg = pci_msi_domain_write_msg, .irq_write_msi_msg = pci_msi_domain_write_msg,
}; };
struct its_pci_alias { static int its_pci_msi_vec_count(struct pci_dev *pdev, void *data)
struct pci_dev *pdev;
u32 count;
};
static int its_pci_msi_vec_count(struct pci_dev *pdev)
{ {
int msi, msix; int msi, msix, *count = data;
msi = max(pci_msi_vec_count(pdev), 0); msi = max(pci_msi_vec_count(pdev), 0);
msix = max(pci_msix_vec_count(pdev), 0); msix = max(pci_msix_vec_count(pdev), 0);
*count += max(msi, msix);
return max(msi, msix); return 0;
} }
static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data) static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data)
{ {
struct its_pci_alias *dev_alias = data; struct pci_dev **alias_dev = data;
if (pdev != dev_alias->pdev) *alias_dev = pdev;
dev_alias->count += its_pci_msi_vec_count(pdev);
return 0; return 0;
} }
...@@ -69,9 +64,9 @@ static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data) ...@@ -69,9 +64,9 @@ static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data)
static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev, static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
int nvec, msi_alloc_info_t *info) int nvec, msi_alloc_info_t *info)
{ {
struct pci_dev *pdev; struct pci_dev *pdev, *alias_dev;
struct its_pci_alias dev_alias;
struct msi_domain_info *msi_info; struct msi_domain_info *msi_info;
int alias_count = 0;
if (!dev_is_pci(dev)) if (!dev_is_pci(dev))
return -EINVAL; return -EINVAL;
...@@ -79,16 +74,20 @@ static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev, ...@@ -79,16 +74,20 @@ static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
msi_info = msi_get_domain_info(domain->parent); msi_info = msi_get_domain_info(domain->parent);
pdev = to_pci_dev(dev); pdev = to_pci_dev(dev);
dev_alias.pdev = pdev; /*
dev_alias.count = nvec; * If pdev is downstream of any aliasing bridges, take an upper
* bound of how many other vectors could map to the same DevID.
pci_for_each_dma_alias(pdev, its_get_pci_alias, &dev_alias); */
pci_for_each_dma_alias(pdev, its_get_pci_alias, &alias_dev);
if (alias_dev != pdev && alias_dev->subordinate)
pci_walk_bus(alias_dev->subordinate, its_pci_msi_vec_count,
&alias_count);
/* ITS specific DeviceID, as the core ITS ignores dev. */ /* ITS specific DeviceID, as the core ITS ignores dev. */
info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain, pdev); info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain, pdev);
return msi_info->ops->msi_prepare(domain->parent, return msi_info->ops->msi_prepare(domain->parent,
dev, dev_alias.count, info); dev, max(nvec, alias_count), info);
} }
static struct msi_domain_ops its_pci_msi_ops = { static struct msi_domain_ops its_pci_msi_ops = {
......
...@@ -86,7 +86,7 @@ static struct msi_domain_info its_pmsi_domain_info = { ...@@ -86,7 +86,7 @@ static struct msi_domain_info its_pmsi_domain_info = {
.chip = &its_pmsi_irq_chip, .chip = &its_pmsi_irq_chip,
}; };
static struct of_device_id its_device_id[] = { static const struct of_device_id its_device_id[] = {
{ .compatible = "arm,gic-v3-its", }, { .compatible = "arm,gic-v3-its", },
{}, {},
}; };
......
...@@ -644,9 +644,12 @@ static int its_set_affinity(struct irq_data *d, const struct cpumask *mask_val, ...@@ -644,9 +644,12 @@ static int its_set_affinity(struct irq_data *d, const struct cpumask *mask_val,
if (cpu >= nr_cpu_ids) if (cpu >= nr_cpu_ids)
return -EINVAL; return -EINVAL;
/* don't set the affinity when the target cpu is same as current one */
if (cpu != its_dev->event_map.col_map[id]) {
target_col = &its_dev->its->collections[cpu]; target_col = &its_dev->its->collections[cpu];
its_send_movi(its_dev, target_col, id); its_send_movi(its_dev, target_col, id);
its_dev->event_map.col_map[id] = cpu; its_dev->event_map.col_map[id] = cpu;
}
return IRQ_SET_MASK_OK_DONE; return IRQ_SET_MASK_OK_DONE;
} }
...@@ -688,9 +691,11 @@ static struct irq_chip its_irq_chip = { ...@@ -688,9 +691,11 @@ static struct irq_chip its_irq_chip = {
*/ */
#define IRQS_PER_CHUNK_SHIFT 5 #define IRQS_PER_CHUNK_SHIFT 5
#define IRQS_PER_CHUNK (1 << IRQS_PER_CHUNK_SHIFT) #define IRQS_PER_CHUNK (1 << IRQS_PER_CHUNK_SHIFT)
#define ITS_MAX_LPI_NRBITS 16 /* 64K LPIs */
static unsigned long *lpi_bitmap; static unsigned long *lpi_bitmap;
static u32 lpi_chunks; static u32 lpi_chunks;
static u32 lpi_id_bits;
static DEFINE_SPINLOCK(lpi_lock); static DEFINE_SPINLOCK(lpi_lock);
static int its_lpi_to_chunk(int lpi) static int its_lpi_to_chunk(int lpi)
...@@ -786,17 +791,13 @@ static void its_lpi_free(struct event_lpi_map *map) ...@@ -786,17 +791,13 @@ static void its_lpi_free(struct event_lpi_map *map)
} }
/* /*
* We allocate 64kB for PROPBASE. That gives us at most 64K LPIs to * We allocate memory for PROPBASE to cover 2 ^ lpi_id_bits LPIs to
* deal with (one configuration byte per interrupt). PENDBASE has to * deal with (one configuration byte per interrupt). PENDBASE has to
* be 64kB aligned (one bit per LPI, plus 8192 bits for SPI/PPI/SGI). * be 64kB aligned (one bit per LPI, plus 8192 bits for SPI/PPI/SGI).
*/ */
#define LPI_PROPBASE_SZ SZ_64K #define LPI_NRBITS lpi_id_bits
#define LPI_PENDBASE_SZ (LPI_PROPBASE_SZ / 8 + SZ_1K) #define LPI_PROPBASE_SZ ALIGN(BIT(LPI_NRBITS), SZ_64K)
#define LPI_PENDBASE_SZ ALIGN(BIT(LPI_NRBITS) / 8, SZ_64K)
/*
* This is how many bits of ID we need, including the useless ones.
*/
#define LPI_NRBITS ilog2(LPI_PROPBASE_SZ + SZ_8K)
#define LPI_PROP_DEFAULT_PRIO 0xa0 #define LPI_PROP_DEFAULT_PRIO 0xa0
...@@ -804,6 +805,7 @@ static int __init its_alloc_lpi_tables(void) ...@@ -804,6 +805,7 @@ static int __init its_alloc_lpi_tables(void)
{ {
phys_addr_t paddr; phys_addr_t paddr;
lpi_id_bits = min_t(u32, gic_rdists->id_bits, ITS_MAX_LPI_NRBITS);
gic_rdists->prop_page = alloc_pages(GFP_NOWAIT, gic_rdists->prop_page = alloc_pages(GFP_NOWAIT,
get_order(LPI_PROPBASE_SZ)); get_order(LPI_PROPBASE_SZ));
if (!gic_rdists->prop_page) { if (!gic_rdists->prop_page) {
...@@ -822,7 +824,7 @@ static int __init its_alloc_lpi_tables(void) ...@@ -822,7 +824,7 @@ static int __init its_alloc_lpi_tables(void)
/* Make sure the GIC will observe the written configuration */ /* Make sure the GIC will observe the written configuration */
gic_flush_dcache_to_poc(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; return its_lpi_init(lpi_id_bits);
} }
static const char *its_base_type_string[] = { static const char *its_base_type_string[] = {
...@@ -1097,7 +1099,7 @@ static void its_cpu_init_lpis(void) ...@@ -1097,7 +1099,7 @@ static void its_cpu_init_lpis(void)
* hence the 'max(LPI_PENDBASE_SZ, SZ_64K)' below. * hence the 'max(LPI_PENDBASE_SZ, SZ_64K)' below.
*/ */
pend_page = alloc_pages(GFP_NOWAIT | __GFP_ZERO, pend_page = alloc_pages(GFP_NOWAIT | __GFP_ZERO,
get_order(max(LPI_PENDBASE_SZ, SZ_64K))); get_order(max_t(u32, LPI_PENDBASE_SZ, SZ_64K)));
if (!pend_page) { if (!pend_page) {
pr_err("Failed to allocate PENDBASE for CPU%d\n", pr_err("Failed to allocate PENDBASE for CPU%d\n",
smp_processor_id()); smp_processor_id());
...@@ -1801,7 +1803,7 @@ int its_cpu_init(void) ...@@ -1801,7 +1803,7 @@ int its_cpu_init(void)
return 0; return 0;
} }
static struct of_device_id its_device_id[] = { static const struct of_device_id its_device_id[] = {
{ .compatible = "arm,gic-v3-its", }, { .compatible = "arm,gic-v3-its", },
{}, {},
}; };
...@@ -1833,6 +1835,78 @@ static int __init its_of_probe(struct device_node *node) ...@@ -1833,6 +1835,78 @@ static int __init its_of_probe(struct device_node *node)
#define ACPI_GICV3_ITS_MEM_SIZE (SZ_128K) #define ACPI_GICV3_ITS_MEM_SIZE (SZ_128K)
#if defined(CONFIG_ACPI_NUMA) && (ACPI_CA_VERSION >= 0x20170531)
struct its_srat_map {
/* numa node id */
u32 numa_node;
/* GIC ITS ID */
u32 its_id;
};
static struct its_srat_map its_srat_maps[MAX_NUMNODES] __initdata;
static int its_in_srat __initdata;
static int __init acpi_get_its_numa_node(u32 its_id)
{
int i;
for (i = 0; i < its_in_srat; i++) {
if (its_id == its_srat_maps[i].its_id)
return its_srat_maps[i].numa_node;
}
return NUMA_NO_NODE;
}
static int __init gic_acpi_parse_srat_its(struct acpi_subtable_header *header,
const unsigned long end)
{
int node;
struct acpi_srat_gic_its_affinity *its_affinity;
its_affinity = (struct acpi_srat_gic_its_affinity *)header;
if (!its_affinity)
return -EINVAL;
if (its_affinity->header.length < sizeof(*its_affinity)) {
pr_err("SRAT: Invalid header length %d in ITS affinity\n",
its_affinity->header.length);
return -EINVAL;
}
if (its_in_srat >= MAX_NUMNODES) {
pr_err("SRAT: ITS affinity exceeding max count[%d]\n",
MAX_NUMNODES);
return -EINVAL;
}
node = acpi_map_pxm_to_node(its_affinity->proximity_domain);
if (node == NUMA_NO_NODE || node >= MAX_NUMNODES) {
pr_err("SRAT: Invalid NUMA node %d in ITS affinity\n", node);
return 0;
}
its_srat_maps[its_in_srat].numa_node = node;
its_srat_maps[its_in_srat].its_id = its_affinity->its_id;
its_in_srat++;
pr_info("SRAT: PXM %d -> ITS %d -> Node %d\n",
its_affinity->proximity_domain, its_affinity->its_id, node);
return 0;
}
static void __init acpi_table_parse_srat_its(void)
{
acpi_table_parse_entries(ACPI_SIG_SRAT,
sizeof(struct acpi_table_srat),
ACPI_SRAT_TYPE_GIC_ITS_AFFINITY,
gic_acpi_parse_srat_its, 0);
}
#else
static void __init acpi_table_parse_srat_its(void) { }
static int __init acpi_get_its_numa_node(u32 its_id) { return NUMA_NO_NODE; }
#endif
static int __init gic_acpi_parse_madt_its(struct acpi_subtable_header *header, static int __init gic_acpi_parse_madt_its(struct acpi_subtable_header *header,
const unsigned long end) const unsigned long end)
{ {
...@@ -1861,7 +1935,8 @@ static int __init gic_acpi_parse_madt_its(struct acpi_subtable_header *header, ...@@ -1861,7 +1935,8 @@ static int __init gic_acpi_parse_madt_its(struct acpi_subtable_header *header,
goto dom_err; goto dom_err;
} }
err = its_probe_one(&res, dom_handle, NUMA_NO_NODE); err = its_probe_one(&res, dom_handle,
acpi_get_its_numa_node(its_entry->translation_id));
if (!err) if (!err)
return 0; return 0;
...@@ -1873,6 +1948,7 @@ static int __init gic_acpi_parse_madt_its(struct acpi_subtable_header *header, ...@@ -1873,6 +1948,7 @@ static int __init gic_acpi_parse_madt_its(struct acpi_subtable_header *header,
static void __init its_acpi_probe(void) static void __init its_acpi_probe(void)
{ {
acpi_table_parse_srat_its();
acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR, acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR,
gic_acpi_parse_madt_its, 0); gic_acpi_parse_madt_its, 0);
} }
...@@ -1898,8 +1974,5 @@ int __init its_init(struct fwnode_handle *handle, struct rdists *rdists, ...@@ -1898,8 +1974,5 @@ int __init its_init(struct fwnode_handle *handle, struct rdists *rdists,
} }
gic_rdists = rdists; gic_rdists = rdists;
its_alloc_lpi_tables(); return its_alloc_lpi_tables();
its_lpi_init(rdists->id_bits);
return 0;
} }
...@@ -307,7 +307,7 @@ static int i8259A_irq_domain_map(struct irq_domain *d, unsigned int virq, ...@@ -307,7 +307,7 @@ static int i8259A_irq_domain_map(struct irq_domain *d, unsigned int virq,
return 0; return 0;
} }
static struct irq_domain_ops i8259A_ops = { static const struct irq_domain_ops i8259A_ops = {
.map = i8259A_irq_domain_map, .map = i8259A_irq_domain_map,
.xlate = irq_domain_xlate_onecell, .xlate = irq_domain_xlate_onecell,
}; };
......
...@@ -200,7 +200,7 @@ static int imx_gpcv2_domain_alloc(struct irq_domain *domain, ...@@ -200,7 +200,7 @@ static int imx_gpcv2_domain_alloc(struct irq_domain *domain,
&parent_fwspec); &parent_fwspec);
} }
static struct irq_domain_ops gpcv2_irqchip_data_domain_ops = { static const struct irq_domain_ops gpcv2_irqchip_data_domain_ops = {
.translate = imx_gpcv2_domain_translate, .translate = imx_gpcv2_domain_translate,
.alloc = imx_gpcv2_domain_alloc, .alloc = imx_gpcv2_domain_alloc,
.free = irq_domain_free_irqs_common, .free = irq_domain_free_irqs_common,
......
...@@ -228,7 +228,7 @@ static int mbigen_irq_domain_alloc(struct irq_domain *domain, ...@@ -228,7 +228,7 @@ static int mbigen_irq_domain_alloc(struct irq_domain *domain,
return 0; return 0;
} }
static struct irq_domain_ops mbigen_domain_ops = { static const struct irq_domain_ops mbigen_domain_ops = {
.translate = mbigen_domain_translate, .translate = mbigen_domain_translate,
.alloc = mbigen_irq_domain_alloc, .alloc = mbigen_irq_domain_alloc,
.free = irq_domain_free_irqs_common, .free = irq_domain_free_irqs_common,
......
...@@ -874,7 +874,7 @@ int gic_ipi_domain_match(struct irq_domain *d, struct device_node *node, ...@@ -874,7 +874,7 @@ int gic_ipi_domain_match(struct irq_domain *d, struct device_node *node,
} }
} }
static struct irq_domain_ops gic_ipi_domain_ops = { static const struct irq_domain_ops gic_ipi_domain_ops = {
.xlate = gic_ipi_domain_xlate, .xlate = gic_ipi_domain_xlate,
.alloc = gic_ipi_domain_alloc, .alloc = gic_ipi_domain_alloc,
.free = gic_ipi_domain_free, .free = gic_ipi_domain_free,
......
/*
* Copyright (C) 2017 Marvell
*
* Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/msi.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include "irq-mvebu-gicp.h"
#define GICP_SETSPI_NSR_OFFSET 0x0
#define GICP_CLRSPI_NSR_OFFSET 0x8
struct mvebu_gicp_spi_range {
unsigned int start;
unsigned int count;
};
struct mvebu_gicp {
struct mvebu_gicp_spi_range *spi_ranges;
unsigned int spi_ranges_cnt;
unsigned int spi_cnt;
unsigned long *spi_bitmap;
spinlock_t spi_lock;
struct resource *res;
struct device *dev;
};
static int gicp_idx_to_spi(struct mvebu_gicp *gicp, int idx)
{
int i;
for (i = 0; i < gicp->spi_ranges_cnt; i++) {
struct mvebu_gicp_spi_range *r = &gicp->spi_ranges[i];
if (idx < r->count)
return r->start + idx;
idx -= r->count;
}
return -EINVAL;
}
int mvebu_gicp_get_doorbells(struct device_node *dn, phys_addr_t *setspi,
phys_addr_t *clrspi)
{
struct platform_device *pdev;
struct mvebu_gicp *gicp;
pdev = of_find_device_by_node(dn);
if (!pdev)
return -ENODEV;
gicp = platform_get_drvdata(pdev);
if (!gicp)
return -ENODEV;
*setspi = gicp->res->start + GICP_SETSPI_NSR_OFFSET;
*clrspi = gicp->res->start + GICP_CLRSPI_NSR_OFFSET;
return 0;
}
static void gicp_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
{
struct mvebu_gicp *gicp = data->chip_data;
phys_addr_t setspi = gicp->res->start + GICP_SETSPI_NSR_OFFSET;
msg->data = data->hwirq;
msg->address_lo = lower_32_bits(setspi);
msg->address_hi = upper_32_bits(setspi);
}
static struct irq_chip gicp_irq_chip = {
.name = "GICP",
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_eoi = irq_chip_eoi_parent,
.irq_set_affinity = irq_chip_set_affinity_parent,
.irq_set_type = irq_chip_set_type_parent,
.irq_compose_msi_msg = gicp_compose_msi_msg,
};
static int gicp_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *args)
{
struct mvebu_gicp *gicp = domain->host_data;
struct irq_fwspec fwspec;
unsigned int hwirq;
int ret;
spin_lock(&gicp->spi_lock);
hwirq = find_first_zero_bit(gicp->spi_bitmap, gicp->spi_cnt);
if (hwirq == gicp->spi_cnt) {
spin_unlock(&gicp->spi_lock);
return -ENOSPC;
}
__set_bit(hwirq, gicp->spi_bitmap);
spin_unlock(&gicp->spi_lock);
fwspec.fwnode = domain->parent->fwnode;
fwspec.param_count = 3;
fwspec.param[0] = GIC_SPI;
fwspec.param[1] = gicp_idx_to_spi(gicp, hwirq) - 32;
/*
* Assume edge rising for now, it will be properly set when
* ->set_type() is called
*/
fwspec.param[2] = IRQ_TYPE_EDGE_RISING;
ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
if (ret) {
dev_err(gicp->dev, "Cannot allocate parent IRQ\n");
goto free_hwirq;
}
ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
&gicp_irq_chip, gicp);
if (ret)
goto free_irqs_parent;
return 0;
free_irqs_parent:
irq_domain_free_irqs_parent(domain, virq, nr_irqs);
free_hwirq:
spin_lock(&gicp->spi_lock);
__clear_bit(hwirq, gicp->spi_bitmap);
spin_unlock(&gicp->spi_lock);
return ret;
}
static void gicp_irq_domain_free(struct irq_domain *domain,
unsigned int virq, unsigned int nr_irqs)
{
struct mvebu_gicp *gicp = domain->host_data;
struct irq_data *d = irq_domain_get_irq_data(domain, virq);
if (d->hwirq >= gicp->spi_cnt) {
dev_err(gicp->dev, "Invalid hwirq %lu\n", d->hwirq);
return;
}
irq_domain_free_irqs_parent(domain, virq, nr_irqs);
spin_lock(&gicp->spi_lock);
__clear_bit(d->hwirq, gicp->spi_bitmap);
spin_unlock(&gicp->spi_lock);
}
static const struct irq_domain_ops gicp_domain_ops = {
.alloc = gicp_irq_domain_alloc,
.free = gicp_irq_domain_free,
};
static struct irq_chip gicp_msi_irq_chip = {
.name = "GICP",
.irq_set_type = irq_chip_set_type_parent,
};
static struct msi_domain_ops gicp_msi_ops = {
};
static struct msi_domain_info gicp_msi_domain_info = {
.flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS),
.ops = &gicp_msi_ops,
.chip = &gicp_msi_irq_chip,
};
static int mvebu_gicp_probe(struct platform_device *pdev)
{
struct mvebu_gicp *gicp;
struct irq_domain *inner_domain, *plat_domain, *parent_domain;
struct device_node *node = pdev->dev.of_node;
struct device_node *irq_parent_dn;
int ret, i;
gicp = devm_kzalloc(&pdev->dev, sizeof(*gicp), GFP_KERNEL);
if (!gicp)
return -ENOMEM;
gicp->dev = &pdev->dev;
gicp->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!gicp->res)
return -ENODEV;
ret = of_property_count_u32_elems(node, "marvell,spi-ranges");
if (ret < 0)
return ret;
gicp->spi_ranges_cnt = ret / 2;
gicp->spi_ranges =
devm_kzalloc(&pdev->dev,
gicp->spi_ranges_cnt *
sizeof(struct mvebu_gicp_spi_range),
GFP_KERNEL);
if (!gicp->spi_ranges)
return -ENOMEM;
for (i = 0; i < gicp->spi_ranges_cnt; i++) {
of_property_read_u32_index(node, "marvell,spi-ranges",
i * 2,
&gicp->spi_ranges[i].start);
of_property_read_u32_index(node, "marvell,spi-ranges",
i * 2 + 1,
&gicp->spi_ranges[i].count);
gicp->spi_cnt += gicp->spi_ranges[i].count;
}
gicp->spi_bitmap = devm_kzalloc(&pdev->dev,
BITS_TO_LONGS(gicp->spi_cnt),
GFP_KERNEL);
if (!gicp->spi_bitmap)
return -ENOMEM;
irq_parent_dn = of_irq_find_parent(node);
if (!irq_parent_dn) {
dev_err(&pdev->dev, "failed to find parent IRQ node\n");
return -ENODEV;
}
parent_domain = irq_find_host(irq_parent_dn);
if (!parent_domain) {
dev_err(&pdev->dev, "failed to find parent IRQ domain\n");
return -ENODEV;
}
inner_domain = irq_domain_create_hierarchy(parent_domain, 0,
gicp->spi_cnt,
of_node_to_fwnode(node),
&gicp_domain_ops, gicp);
if (!inner_domain)
return -ENOMEM;
plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node),
&gicp_msi_domain_info,
inner_domain);
if (!plat_domain) {
irq_domain_remove(inner_domain);
return -ENOMEM;
}
platform_set_drvdata(pdev, gicp);
return 0;
}
static const struct of_device_id mvebu_gicp_of_match[] = {
{ .compatible = "marvell,ap806-gicp", },
{},
};
static struct platform_driver mvebu_gicp_driver = {
.probe = mvebu_gicp_probe,
.driver = {
.name = "mvebu-gicp",
.of_match_table = mvebu_gicp_of_match,
},
};
builtin_platform_driver(mvebu_gicp_driver);
#ifndef __MVEBU_GICP_H__
#define __MVEBU_GICP_H__
#include <linux/types.h>
struct device_node;
int mvebu_gicp_get_doorbells(struct device_node *dn, phys_addr_t *setspi,
phys_addr_t *clrspi);
#endif /* __MVEBU_GICP_H__ */
/*
* Copyright (C) 2017 Marvell
*
* Hanna Hawa <hannah@marvell.com>
* Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/msi.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <dt-bindings/interrupt-controller/mvebu-icu.h>
#include "irq-mvebu-gicp.h"
/* ICU registers */
#define ICU_SETSPI_NSR_AL 0x10
#define ICU_SETSPI_NSR_AH 0x14
#define ICU_CLRSPI_NSR_AL 0x18
#define ICU_CLRSPI_NSR_AH 0x1c
#define ICU_INT_CFG(x) (0x100 + 4 * (x))
#define ICU_INT_ENABLE BIT(24)
#define ICU_IS_EDGE BIT(28)
#define ICU_GROUP_SHIFT 29
/* ICU definitions */
#define ICU_MAX_IRQS 207
#define ICU_SATA0_ICU_ID 109
#define ICU_SATA1_ICU_ID 107
struct mvebu_icu {
struct irq_chip irq_chip;
void __iomem *base;
struct irq_domain *domain;
struct device *dev;
};
struct mvebu_icu_irq_data {
struct mvebu_icu *icu;
unsigned int icu_group;
unsigned int type;
};
static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
{
struct irq_data *d = irq_get_irq_data(desc->irq);
struct mvebu_icu_irq_data *icu_irqd = d->chip_data;
struct mvebu_icu *icu = icu_irqd->icu;
unsigned int icu_int;
if (msg->address_lo || msg->address_hi) {
/* Configure the ICU with irq number & type */
icu_int = msg->data | ICU_INT_ENABLE;
if (icu_irqd->type & IRQ_TYPE_EDGE_RISING)
icu_int |= ICU_IS_EDGE;
icu_int |= icu_irqd->icu_group << ICU_GROUP_SHIFT;
} else {
/* De-configure the ICU */
icu_int = 0;
}
writel_relaxed(icu_int, icu->base + ICU_INT_CFG(d->hwirq));
/*
* The SATA unit has 2 ports, and a dedicated ICU entry per
* port. The ahci sata driver supports only one irq interrupt
* per SATA unit. To solve this conflict, we configure the 2
* SATA wired interrupts in the south bridge into 1 GIC
* interrupt in the north bridge. Even if only a single port
* is enabled, if sata node is enabled, both interrupts are
* configured (regardless of which port is actually in use).
*/
if (d->hwirq == ICU_SATA0_ICU_ID || d->hwirq == ICU_SATA1_ICU_ID) {
writel_relaxed(icu_int,
icu->base + ICU_INT_CFG(ICU_SATA0_ICU_ID));
writel_relaxed(icu_int,
icu->base + ICU_INT_CFG(ICU_SATA1_ICU_ID));
}
}
static int
mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
unsigned long *hwirq, unsigned int *type)
{
struct mvebu_icu *icu = d->host_data;
unsigned int icu_group;
/* Check the count of the parameters in dt */
if (WARN_ON(fwspec->param_count < 3)) {
dev_err(icu->dev, "wrong ICU parameter count %d\n",
fwspec->param_count);
return -EINVAL;
}
/* Only ICU group type is handled */
icu_group = fwspec->param[0];
if (icu_group != ICU_GRP_NSR && icu_group != ICU_GRP_SR &&
icu_group != ICU_GRP_SEI && icu_group != ICU_GRP_REI) {
dev_err(icu->dev, "wrong ICU group type %x\n", icu_group);
return -EINVAL;
}
*hwirq = fwspec->param[1];
if (*hwirq >= ICU_MAX_IRQS) {
dev_err(icu->dev, "invalid interrupt number %ld\n", *hwirq);
return -EINVAL;
}
/* Mask the type to prevent wrong DT configuration */
*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
return 0;
}
static int
mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *args)
{
int err;
unsigned long hwirq;
struct irq_fwspec *fwspec = args;
struct mvebu_icu *icu = platform_msi_get_host_data(domain);
struct mvebu_icu_irq_data *icu_irqd;
icu_irqd = kmalloc(sizeof(*icu_irqd), GFP_KERNEL);
if (!icu_irqd)
return -ENOMEM;
err = mvebu_icu_irq_domain_translate(domain, fwspec, &hwirq,
&icu_irqd->type);
if (err) {
dev_err(icu->dev, "failed to translate ICU parameters\n");
goto free_irqd;
}
icu_irqd->icu_group = fwspec->param[0];
icu_irqd->icu = icu;
err = platform_msi_domain_alloc(domain, virq, nr_irqs);
if (err) {
dev_err(icu->dev, "failed to allocate ICU interrupt in parent domain\n");
goto free_irqd;
}
/* Make sure there is no interrupt left pending by the firmware */
err = irq_set_irqchip_state(virq, IRQCHIP_STATE_PENDING, false);
if (err)
goto free_msi;
err = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
&icu->irq_chip, icu_irqd);
if (err) {
dev_err(icu->dev, "failed to set the data to IRQ domain\n");
goto free_msi;
}
return 0;
free_msi:
platform_msi_domain_free(domain, virq, nr_irqs);
free_irqd:
kfree(icu_irqd);
return err;
}
static void
mvebu_icu_irq_domain_free(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs)
{
struct irq_data *d = irq_get_irq_data(virq);
struct mvebu_icu_irq_data *icu_irqd = d->chip_data;
kfree(icu_irqd);
platform_msi_domain_free(domain, virq, nr_irqs);
}
static const struct irq_domain_ops mvebu_icu_domain_ops = {
.translate = mvebu_icu_irq_domain_translate,
.alloc = mvebu_icu_irq_domain_alloc,
.free = mvebu_icu_irq_domain_free,
};
static int mvebu_icu_probe(struct platform_device *pdev)
{
struct mvebu_icu *icu;
struct device_node *node = pdev->dev.of_node;
struct device_node *gicp_dn;
struct resource *res;
phys_addr_t setspi, clrspi;
u32 i, icu_int;
int ret;
icu = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_icu),
GFP_KERNEL);
if (!icu)
return -ENOMEM;
icu->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
icu->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(icu->base)) {
dev_err(&pdev->dev, "Failed to map icu base address.\n");
return PTR_ERR(icu->base);
}
icu->irq_chip.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
"ICU.%x",
(unsigned int)res->start);
if (!icu->irq_chip.name)
return -ENOMEM;
icu->irq_chip.irq_mask = irq_chip_mask_parent;
icu->irq_chip.irq_unmask = irq_chip_unmask_parent;
icu->irq_chip.irq_eoi = irq_chip_eoi_parent;
icu->irq_chip.irq_set_type = irq_chip_set_type_parent;
#ifdef CONFIG_SMP
icu->irq_chip.irq_set_affinity = irq_chip_set_affinity_parent;
#endif
/*
* We're probed after MSI domains have been resolved, so force
* resolution here.
*/
pdev->dev.msi_domain = of_msi_get_domain(&pdev->dev, node,
DOMAIN_BUS_PLATFORM_MSI);
if (!pdev->dev.msi_domain)
return -EPROBE_DEFER;
gicp_dn = irq_domain_get_of_node(pdev->dev.msi_domain);
if (!gicp_dn)
return -ENODEV;
ret = mvebu_gicp_get_doorbells(gicp_dn, &setspi, &clrspi);
if (ret)
return ret;
/* Set Clear/Set ICU SPI message address in AP */
writel_relaxed(upper_32_bits(setspi), icu->base + ICU_SETSPI_NSR_AH);
writel_relaxed(lower_32_bits(setspi), icu->base + ICU_SETSPI_NSR_AL);
writel_relaxed(upper_32_bits(clrspi), icu->base + ICU_CLRSPI_NSR_AH);
writel_relaxed(lower_32_bits(clrspi), icu->base + ICU_CLRSPI_NSR_AL);
/*
* Clean all ICU interrupts with type SPI_NSR, required to
* avoid unpredictable SPI assignments done by firmware.
*/
for (i = 0 ; i < ICU_MAX_IRQS ; i++) {
icu_int = readl(icu->base + ICU_INT_CFG(i));
if ((icu_int >> ICU_GROUP_SHIFT) == ICU_GRP_NSR)
writel_relaxed(0x0, icu->base + ICU_INT_CFG(i));
}
icu->domain =
platform_msi_create_device_domain(&pdev->dev, ICU_MAX_IRQS,
mvebu_icu_write_msg,
&mvebu_icu_domain_ops, icu);
if (!icu->domain) {
dev_err(&pdev->dev, "Failed to create ICU domain\n");
return -ENOMEM;
}
return 0;
}
static const struct of_device_id mvebu_icu_of_match[] = {
{ .compatible = "marvell,cp110-icu", },
{},
};
static struct platform_driver mvebu_icu_driver = {
.probe = mvebu_icu_probe,
.driver = {
.name = "mvebu-icu",
.of_match_table = mvebu_icu_of_match,
},
};
builtin_platform_driver(mvebu_icu_driver);
...@@ -67,7 +67,7 @@ static int irq_map(struct irq_domain *h, unsigned int virq, ...@@ -67,7 +67,7 @@ static int irq_map(struct irq_domain *h, unsigned int virq,
return 0; return 0;
} }
static struct irq_domain_ops irq_ops = { static const struct irq_domain_ops irq_ops = {
.map = irq_map, .map = irq_map,
.xlate = irq_domain_xlate_onecell, .xlate = irq_domain_xlate_onecell,
}; };
......
...@@ -73,7 +73,7 @@ static __init int irq_map(struct irq_domain *h, unsigned int virq, ...@@ -73,7 +73,7 @@ static __init int irq_map(struct irq_domain *h, unsigned int virq,
return 0; return 0;
} }
static struct irq_domain_ops irq_ops = { static const struct irq_domain_ops irq_ops = {
.map = irq_map, .map = irq_map,
.xlate = irq_domain_xlate_onecell, .xlate = irq_domain_xlate_onecell,
}; };
......
...@@ -25,6 +25,29 @@ ...@@ -25,6 +25,29 @@
#define SUNXI_NMI_SRC_TYPE_MASK 0x00000003 #define SUNXI_NMI_SRC_TYPE_MASK 0x00000003
#define SUNXI_NMI_IRQ_BIT BIT(0)
#define SUN6I_R_INTC_CTRL 0x0c
#define SUN6I_R_INTC_PENDING 0x10
#define SUN6I_R_INTC_ENABLE 0x40
/*
* For deprecated sun6i-a31-sc-nmi compatible.
* Registers are offset by 0x0c.
*/
#define SUN6I_R_INTC_NMI_OFFSET 0x0c
#define SUN6I_NMI_CTRL (SUN6I_R_INTC_CTRL - SUN6I_R_INTC_NMI_OFFSET)
#define SUN6I_NMI_PENDING (SUN6I_R_INTC_PENDING - SUN6I_R_INTC_NMI_OFFSET)
#define SUN6I_NMI_ENABLE (SUN6I_R_INTC_ENABLE - SUN6I_R_INTC_NMI_OFFSET)
#define SUN7I_NMI_CTRL 0x00
#define SUN7I_NMI_PENDING 0x04
#define SUN7I_NMI_ENABLE 0x08
#define SUN9I_NMI_CTRL 0x00
#define SUN9I_NMI_ENABLE 0x04
#define SUN9I_NMI_PENDING 0x08
enum { enum {
SUNXI_SRC_TYPE_LEVEL_LOW = 0, SUNXI_SRC_TYPE_LEVEL_LOW = 0,
SUNXI_SRC_TYPE_EDGE_FALLING, SUNXI_SRC_TYPE_EDGE_FALLING,
...@@ -38,22 +61,28 @@ struct sunxi_sc_nmi_reg_offs { ...@@ -38,22 +61,28 @@ struct sunxi_sc_nmi_reg_offs {
u32 enable; u32 enable;
}; };
static struct sunxi_sc_nmi_reg_offs sun7i_reg_offs = { static const struct sunxi_sc_nmi_reg_offs sun6i_r_intc_reg_offs __initconst = {
.ctrl = 0x00, .ctrl = SUN6I_R_INTC_CTRL,
.pend = 0x04, .pend = SUN6I_R_INTC_PENDING,
.enable = 0x08, .enable = SUN6I_R_INTC_ENABLE,
}; };
static struct sunxi_sc_nmi_reg_offs sun6i_reg_offs = { static const struct sunxi_sc_nmi_reg_offs sun6i_reg_offs __initconst = {
.ctrl = 0x00, .ctrl = SUN6I_NMI_CTRL,
.pend = 0x04, .pend = SUN6I_NMI_PENDING,
.enable = 0x34, .enable = SUN6I_NMI_ENABLE,
}; };
static struct sunxi_sc_nmi_reg_offs sun9i_reg_offs = { static const struct sunxi_sc_nmi_reg_offs sun7i_reg_offs __initconst = {
.ctrl = 0x00, .ctrl = SUN7I_NMI_CTRL,
.pend = 0x08, .pend = SUN7I_NMI_PENDING,
.enable = 0x04, .enable = SUN7I_NMI_ENABLE,
};
static const struct sunxi_sc_nmi_reg_offs sun9i_reg_offs __initconst = {
.ctrl = SUN9I_NMI_CTRL,
.pend = SUN9I_NMI_PENDING,
.enable = SUN9I_NMI_ENABLE,
}; };
static inline void sunxi_sc_nmi_write(struct irq_chip_generic *gc, u32 off, static inline void sunxi_sc_nmi_write(struct irq_chip_generic *gc, u32 off,
...@@ -128,7 +157,7 @@ static int sunxi_sc_nmi_set_type(struct irq_data *data, unsigned int flow_type) ...@@ -128,7 +157,7 @@ static int sunxi_sc_nmi_set_type(struct irq_data *data, unsigned int flow_type)
} }
static int __init sunxi_sc_nmi_irq_init(struct device_node *node, static int __init sunxi_sc_nmi_irq_init(struct device_node *node,
struct sunxi_sc_nmi_reg_offs *reg_offs) const struct sunxi_sc_nmi_reg_offs *reg_offs)
{ {
struct irq_domain *domain; struct irq_domain *domain;
struct irq_chip_generic *gc; struct irq_chip_generic *gc;
...@@ -187,8 +216,11 @@ static int __init sunxi_sc_nmi_irq_init(struct device_node *node, ...@@ -187,8 +216,11 @@ static int __init sunxi_sc_nmi_irq_init(struct device_node *node,
gc->chip_types[1].regs.type = reg_offs->ctrl; gc->chip_types[1].regs.type = reg_offs->ctrl;
gc->chip_types[1].handler = handle_edge_irq; gc->chip_types[1].handler = handle_edge_irq;
/* Disable any active interrupts */
sunxi_sc_nmi_write(gc, reg_offs->enable, 0); sunxi_sc_nmi_write(gc, reg_offs->enable, 0);
sunxi_sc_nmi_write(gc, reg_offs->pend, 0x1);
/* Clear any pending NMI interrupts */
sunxi_sc_nmi_write(gc, reg_offs->pend, SUNXI_NMI_IRQ_BIT);
irq_set_chained_handler_and_data(irq, sunxi_sc_nmi_handle_irq, domain); irq_set_chained_handler_and_data(irq, sunxi_sc_nmi_handle_irq, domain);
...@@ -200,6 +232,14 @@ static int __init sunxi_sc_nmi_irq_init(struct device_node *node, ...@@ -200,6 +232,14 @@ static int __init sunxi_sc_nmi_irq_init(struct device_node *node,
return ret; return ret;
} }
static int __init sun6i_r_intc_irq_init(struct device_node *node,
struct device_node *parent)
{
return sunxi_sc_nmi_irq_init(node, &sun6i_r_intc_reg_offs);
}
IRQCHIP_DECLARE(sun6i_r_intc, "allwinner,sun6i-a31-r-intc",
sun6i_r_intc_irq_init);
static int __init sun6i_sc_nmi_irq_init(struct device_node *node, static int __init sun6i_sc_nmi_irq_init(struct device_node *node,
struct device_node *parent) struct device_node *parent)
{ {
......
...@@ -288,9 +288,4 @@ static struct platform_driver qcom_irq_combiner_probe = { ...@@ -288,9 +288,4 @@ static struct platform_driver qcom_irq_combiner_probe = {
}, },
.probe = combiner_probe, .probe = combiner_probe,
}; };
builtin_platform_driver(qcom_irq_combiner_probe);
static int __init register_qcom_irq_combiner(void)
{
return platform_driver_register(&qcom_irq_combiner_probe);
}
device_initcall(register_qcom_irq_combiner);
/*
* This header provides constants for the MVEBU ICU driver.
*/
#ifndef _DT_BINDINGS_INTERRUPT_CONTROLLER_MVEBU_ICU_H
#define _DT_BINDINGS_INTERRUPT_CONTROLLER_MVEBU_ICU_H
/* interrupt specifier cell 0 */
#define ICU_GRP_NSR 0x0
#define ICU_GRP_SR 0x1
#define ICU_GRP_SEI 0x4
#define ICU_GRP_REI 0x5
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment