mirror of
https://github.com/ukui/kernel.git
synced 2026-03-09 10:07:04 -07:00
Merge branch 'irq-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull irq updates from Thomas Gleixner: "This update delivers: - Yet another interrupt chip diver (LPC32xx) - Core functions to handle partitioned per-cpu interrupts - Enhancements to the IPI core - Proper handling of irq type configuration - A large set of ARM GIC enhancements - The usual pile of small fixes, cleanups and enhancements" * 'irq-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (31 commits) irqchip/bcm2836: Use a more generic memory barrier call irqchip/bcm2836: Fix compiler warning on 64-bit build irqchip/bcm2836: Drop smp_set_ops on arm64 builds irqchip/gic: Add helper functions for GIC setup and teardown irqchip/gic: Store GIC configuration parameters irqchip/gic: Pass GIC pointer to save/restore functions irqchip/gic: Return an error if GIC initialisation fails irqchip/gic: Remove static irq_chip definition for eoimode1 irqchip/gic: Don't initialise chip if mapping IO space fails irqchip/gic: WARN if setting the interrupt type for a PPI fails irqchip/gic: Don't unnecessarily write the IRQ configuration irqchip: Mask the non-type/sense bits when translating an IRQ genirq: Ensure IRQ descriptor is valid when setting-up the IRQ irqchip/gic-v3: Configure all interrupts as non-secure Group-1 irqchip/gic-v2m: Add workaround for Broadcom NS2 GICv2m erratum irqchip/irq-alpine-msi: Don't use <asm-generic/msi.h> irqchip/mbigen: Checking for IS_ERR() instead of NULL irqchip/gic-v3: Remove inexistant register definition irqchip/gicv3-its: Don't allow devices whose ID is outside range irqchip: Add LPC32xx interrupt controller driver ...
This commit is contained in:
@@ -11,6 +11,8 @@ Main node required properties:
|
||||
- interrupt-controller : Identifies the node as an interrupt controller
|
||||
- #interrupt-cells : Specifies the number of cells needed to encode an
|
||||
interrupt source. Must be a single cell with a value of at least 3.
|
||||
If the system requires describing PPI affinity, then the value must
|
||||
be at least 4.
|
||||
|
||||
The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
|
||||
interrupts. Other values are reserved for future use.
|
||||
@@ -24,7 +26,14 @@ Main node required properties:
|
||||
1 = edge triggered
|
||||
4 = level triggered
|
||||
|
||||
Cells 4 and beyond are reserved for future use and must have a value
|
||||
The 4th cell is a phandle to a node describing a set of CPUs this
|
||||
interrupt is affine to. The interrupt must be a PPI, and the node
|
||||
pointed must be a subnode of the "ppi-partitions" subnode. For
|
||||
interrupt types other than PPI or PPIs that are not partitionned,
|
||||
this cell must be zero. See the "ppi-partitions" node description
|
||||
below.
|
||||
|
||||
Cells 5 and beyond are reserved for future use and must have a value
|
||||
of 0 if present.
|
||||
|
||||
- reg : Specifies base physical address(s) and size of the GIC
|
||||
@@ -50,6 +59,11 @@ Optional
|
||||
|
||||
Sub-nodes:
|
||||
|
||||
PPI affinity can be expressed as a single "ppi-partitions" node,
|
||||
containing a set of sub-nodes, each with the following property:
|
||||
- affinity: Should be a list of phandles to CPU nodes (as described in
|
||||
Documentation/devicetree/bindings/arm/cpus.txt).
|
||||
|
||||
GICv3 has one or more Interrupt Translation Services (ITS) that are
|
||||
used to route Message Signalled Interrupts (MSI) to the CPUs.
|
||||
|
||||
@@ -91,7 +105,7 @@ Examples:
|
||||
|
||||
gic: interrupt-controller@2c010000 {
|
||||
compatible = "arm,gic-v3";
|
||||
#interrupt-cells = <3>;
|
||||
#interrupt-cells = <4>;
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
ranges;
|
||||
@@ -119,4 +133,20 @@ Examples:
|
||||
#msi-cells = <1>;
|
||||
reg = <0x0 0x2c400000 0 0x200000>;
|
||||
};
|
||||
|
||||
ppi-partitions {
|
||||
part0: interrupt-partition-0 {
|
||||
affinity = <&cpu0 &cpu2>;
|
||||
};
|
||||
|
||||
part1: interrupt-partition-1 {
|
||||
affinity = <&cpu1 &cpu3>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
device@0 {
|
||||
reg = <0 0 0 4>;
|
||||
interrupts = <1 1 4 &part0>;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
* Freescale Layerscape SCFG PCIe MSI controller
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: should be "fsl,<soc-name>-msi" to identify
|
||||
Layerscape PCIe MSI controller block such as:
|
||||
"fsl,1s1021a-msi"
|
||||
"fsl,1s1043a-msi"
|
||||
- msi-controller: indicates that this is a PCIe MSI controller node
|
||||
- reg: physical base address of the controller and length of memory mapped.
|
||||
- interrupts: an interrupt to the parent interrupt controller.
|
||||
|
||||
Optional properties:
|
||||
- interrupt-parent: the phandle to the parent interrupt controller.
|
||||
|
||||
This interrupt controller hardware is a second level interrupt controller that
|
||||
is hooked to a parent interrupt controller: e.g: ARM GIC for ARM-based
|
||||
platforms. If interrupt-parent is not provided, the default parent interrupt
|
||||
controller will be used.
|
||||
Each PCIe node needs to have property msi-parent that points to
|
||||
MSI controller node
|
||||
|
||||
Examples:
|
||||
|
||||
msi1: msi-controller@1571000 {
|
||||
compatible = "fsl,1s1043a-msi";
|
||||
reg = <0x0 0x1571000 0x0 0x8>,
|
||||
msi-controller;
|
||||
interrupts = <0 116 0x4>;
|
||||
};
|
||||
@@ -531,6 +531,8 @@ config ARCH_LPC32XX
|
||||
select COMMON_CLK
|
||||
select CPU_ARM926T
|
||||
select GENERIC_CLOCKEVENTS
|
||||
select MULTI_IRQ_HANDLER
|
||||
select SPARSE_IRQ
|
||||
select USE_OF
|
||||
help
|
||||
Support for the NXP LPC32XX family of processors
|
||||
|
||||
@@ -206,7 +206,6 @@ static const char *const lpc32xx_dt_compat[] __initconst = {
|
||||
DT_MACHINE_START(LPC32XX_DT, "LPC32XX SoC (Flattened Device Tree)")
|
||||
.atag_offset = 0x100,
|
||||
.map_io = lpc32xx_map_io,
|
||||
.init_irq = lpc32xx_init_irq,
|
||||
.init_machine = lpc3250_machine_init,
|
||||
.dt_compat = lpc32xx_dt_compat,
|
||||
MACHINE_END
|
||||
|
||||
@@ -27,6 +27,7 @@ config ARM_GIC_V3
|
||||
select IRQ_DOMAIN
|
||||
select MULTI_IRQ_HANDLER
|
||||
select IRQ_DOMAIN_HIERARCHY
|
||||
select PARTITION_PERCPU
|
||||
|
||||
config ARM_GIC_V3_ITS
|
||||
bool
|
||||
@@ -244,3 +245,11 @@ config IRQ_MXS
|
||||
config MVEBU_ODMI
|
||||
bool
|
||||
select GENERIC_MSI_IRQ_DOMAIN
|
||||
|
||||
config LS_SCFG_MSI
|
||||
def_bool y if SOC_LS1021A || ARCH_LAYERSCAPE
|
||||
depends on PCI && PCI_MSI
|
||||
select PCI_MSI_IRQ_DOMAIN
|
||||
|
||||
config PARTITION_PERCPU
|
||||
bool
|
||||
|
||||
@@ -7,6 +7,7 @@ obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2835.o
|
||||
obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2836.o
|
||||
obj-$(CONFIG_ARCH_EXYNOS) += exynos-combiner.o
|
||||
obj-$(CONFIG_ARCH_HIP04) += irq-hip04.o
|
||||
obj-$(CONFIG_ARCH_LPC32XX) += irq-lpc32xx.o
|
||||
obj-$(CONFIG_ARCH_MMP) += irq-mmp.o
|
||||
obj-$(CONFIG_IRQ_MXS) += irq-mxs.o
|
||||
obj-$(CONFIG_ARCH_TEGRA) += irq-tegra.o
|
||||
@@ -27,6 +28,7 @@ obj-$(CONFIG_REALVIEW_DT) += irq-gic-realview.o
|
||||
obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o
|
||||
obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-common.o
|
||||
obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v3-its-pci-msi.o irq-gic-v3-its-platform-msi.o
|
||||
obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o
|
||||
obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
|
||||
obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
|
||||
obj-$(CONFIG_ARM_VIC) += irq-vic.o
|
||||
@@ -65,3 +67,4 @@ obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o
|
||||
obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o
|
||||
obj-$(CONFIG_PIC32_EVIC) += irq-pic32-evic.o
|
||||
obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o
|
||||
obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <asm/irq.h>
|
||||
#include <asm-generic/msi.h>
|
||||
#include <asm/msi.h>
|
||||
|
||||
/* MSIX message address format: local GIC target */
|
||||
#define ALPINE_MSIX_SPI_TARGET_CLUSTER0 BIT(16)
|
||||
|
||||
@@ -195,7 +195,7 @@ static void bcm2836_arm_irqchip_send_ipi(const struct cpumask *mask,
|
||||
* Ensure that stores to normal memory are visible to the
|
||||
* other CPUs before issuing the IPI.
|
||||
*/
|
||||
dsb();
|
||||
smp_wmb();
|
||||
|
||||
for_each_cpu(cpu, mask) {
|
||||
writel(1 << ipi, mailbox0_base + 16 * cpu);
|
||||
@@ -223,6 +223,7 @@ static struct notifier_block bcm2836_arm_irqchip_cpu_notifier = {
|
||||
.priority = 100,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_ARM
|
||||
int __init bcm2836_smp_boot_secondary(unsigned int cpu,
|
||||
struct task_struct *idle)
|
||||
{
|
||||
@@ -238,7 +239,7 @@ int __init bcm2836_smp_boot_secondary(unsigned int cpu,
|
||||
static const struct smp_operations bcm2836_smp_ops __initconst = {
|
||||
.smp_boot_secondary = bcm2836_smp_boot_secondary,
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static const struct irq_domain_ops bcm2836_arm_irqchip_intc_ops = {
|
||||
@@ -252,12 +253,15 @@ bcm2836_arm_irqchip_smp_init(void)
|
||||
/* Unmask IPIs to the boot CPU. */
|
||||
bcm2836_arm_irqchip_cpu_notify(&bcm2836_arm_irqchip_cpu_notifier,
|
||||
CPU_STARTING,
|
||||
(void *)smp_processor_id());
|
||||
(void *)(uintptr_t)smp_processor_id());
|
||||
register_cpu_notifier(&bcm2836_arm_irqchip_cpu_notifier);
|
||||
|
||||
set_smp_cross_call(bcm2836_arm_irqchip_send_ipi);
|
||||
|
||||
#ifdef CONFIG_ARM
|
||||
smp_set_ops(&bcm2836_smp_ops);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -183,7 +183,7 @@ static int crossbar_domain_translate(struct irq_domain *d,
|
||||
return -EINVAL;
|
||||
|
||||
*hwirq = fwspec->param[1];
|
||||
*type = fwspec->param[2];
|
||||
*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,14 +50,26 @@ int gic_configure_irq(unsigned int irq, unsigned int type,
|
||||
else if (type & IRQ_TYPE_EDGE_BOTH)
|
||||
val |= confmask;
|
||||
|
||||
/* If the current configuration is the same, then we are done */
|
||||
if (val == oldval)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Write back the new configuration, and possibly re-enable
|
||||
* the interrupt. If we tried to write a new configuration and failed,
|
||||
* return an error.
|
||||
* the interrupt. If we fail to write a new configuration for
|
||||
* an SPI then WARN and return an error. If we fail to write the
|
||||
* configuration for a PPI this is most likely because the GIC
|
||||
* does not allow us to set the configuration or we are in a
|
||||
* non-secure mode, and hence it may not be catastrophic.
|
||||
*/
|
||||
writel_relaxed(val, base + GIC_DIST_CONFIG + confoff);
|
||||
if (readl_relaxed(base + GIC_DIST_CONFIG + confoff) != val && val != oldval)
|
||||
ret = -EINVAL;
|
||||
if (readl_relaxed(base + GIC_DIST_CONFIG + confoff) != val) {
|
||||
if (WARN_ON(irq >= 32))
|
||||
ret = -EINVAL;
|
||||
else
|
||||
pr_warn("GIC: PPI%d is secure or misconfigured\n",
|
||||
irq - 16);
|
||||
}
|
||||
|
||||
if (sync_access)
|
||||
sync_access();
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
/* APM X-Gene with GICv2m MSI_IIDR register value */
|
||||
#define XGENE_GICV2M_MSI_IIDR 0x06000170
|
||||
|
||||
/* Broadcom NS2 GICv2m MSI_IIDR register value */
|
||||
#define BCM_NS2_GICV2M_MSI_IIDR 0x0000013f
|
||||
|
||||
/* List of flags for specific v2m implementation */
|
||||
#define GICV2M_NEEDS_SPI_OFFSET 0x00000001
|
||||
|
||||
@@ -62,6 +65,7 @@ struct v2m_data {
|
||||
void __iomem *base; /* GICv2m virt address */
|
||||
u32 spi_start; /* The SPI number that MSIs start */
|
||||
u32 nr_spis; /* The number of SPIs for MSIs */
|
||||
u32 spi_offset; /* offset to be subtracted from SPI number */
|
||||
unsigned long *bm; /* MSI vector bitmap */
|
||||
u32 flags; /* v2m flags for specific implementation */
|
||||
};
|
||||
@@ -102,7 +106,7 @@ static void gicv2m_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
|
||||
msg->data = data->hwirq;
|
||||
|
||||
if (v2m->flags & GICV2M_NEEDS_SPI_OFFSET)
|
||||
msg->data -= v2m->spi_start;
|
||||
msg->data -= v2m->spi_offset;
|
||||
}
|
||||
|
||||
static struct irq_chip gicv2m_irq_chip = {
|
||||
@@ -340,9 +344,20 @@ static int __init gicv2m_init_one(struct fwnode_handle *fwnode,
|
||||
* 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).
|
||||
*
|
||||
* Broadom NS2 GICv2m implementation has an erratum where the MSI data
|
||||
* is 'spi_number - 32'
|
||||
*/
|
||||
if (readl_relaxed(v2m->base + V2M_MSI_IIDR) == XGENE_GICV2M_MSI_IIDR)
|
||||
switch (readl_relaxed(v2m->base + V2M_MSI_IIDR)) {
|
||||
case XGENE_GICV2M_MSI_IIDR:
|
||||
v2m->flags |= GICV2M_NEEDS_SPI_OFFSET;
|
||||
v2m->spi_offset = v2m->spi_start;
|
||||
break;
|
||||
case BCM_NS2_GICV2M_MSI_IIDR:
|
||||
v2m->flags |= GICV2M_NEEDS_SPI_OFFSET;
|
||||
v2m->spi_offset = 32;
|
||||
break;
|
||||
}
|
||||
|
||||
v2m->bm = kzalloc(sizeof(long) * BITS_TO_LONGS(v2m->nr_spis),
|
||||
GFP_KERNEL);
|
||||
|
||||
@@ -54,6 +54,16 @@ struct its_collection {
|
||||
u16 col_id;
|
||||
};
|
||||
|
||||
/*
|
||||
* The ITS_BASER structure - contains memory information and cached
|
||||
* value of BASER register configuration.
|
||||
*/
|
||||
struct its_baser {
|
||||
void *base;
|
||||
u64 val;
|
||||
u32 order;
|
||||
};
|
||||
|
||||
/*
|
||||
* The ITS structure - contains most of the infrastructure, with the
|
||||
* top-level MSI domain, the command queue, the collections, and the
|
||||
@@ -66,14 +76,12 @@ struct its_node {
|
||||
unsigned long phys_base;
|
||||
struct its_cmd_block *cmd_base;
|
||||
struct its_cmd_block *cmd_write;
|
||||
struct {
|
||||
void *base;
|
||||
u32 order;
|
||||
} tables[GITS_BASER_NR_REGS];
|
||||
struct its_baser tables[GITS_BASER_NR_REGS];
|
||||
struct its_collection *collections;
|
||||
struct list_head its_device_list;
|
||||
u64 flags;
|
||||
u32 ite_size;
|
||||
u32 device_ids;
|
||||
};
|
||||
|
||||
#define ITS_ITT_ALIGN SZ_256
|
||||
@@ -838,6 +846,8 @@ static int its_alloc_tables(const char *node_name, struct its_node *its)
|
||||
ids = GITS_TYPER_DEVBITS(typer);
|
||||
}
|
||||
|
||||
its->device_ids = ids;
|
||||
|
||||
for (i = 0; i < GITS_BASER_NR_REGS; i++) {
|
||||
u64 val = readq_relaxed(its->base + GITS_BASER + i * 8);
|
||||
u64 type = GITS_BASER_TYPE(val);
|
||||
@@ -913,6 +923,7 @@ retry_baser:
|
||||
}
|
||||
|
||||
val |= alloc_pages - 1;
|
||||
its->tables[i].val = val;
|
||||
|
||||
writeq_relaxed(val, its->base + GITS_BASER + i * 8);
|
||||
tmp = readq_relaxed(its->base + GITS_BASER + i * 8);
|
||||
@@ -1138,9 +1149,22 @@ static struct its_device *its_find_device(struct its_node *its, u32 dev_id)
|
||||
return its_dev;
|
||||
}
|
||||
|
||||
static struct its_baser *its_get_baser(struct its_node *its, u32 type)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < GITS_BASER_NR_REGS; i++) {
|
||||
if (GITS_BASER_TYPE(its->tables[i].val) == type)
|
||||
return &its->tables[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct its_device *its_create_device(struct its_node *its, u32 dev_id,
|
||||
int nvecs)
|
||||
{
|
||||
struct its_baser *baser;
|
||||
struct its_device *dev;
|
||||
unsigned long *lpi_map;
|
||||
unsigned long flags;
|
||||
@@ -1151,6 +1175,16 @@ static struct its_device *its_create_device(struct its_node *its, u32 dev_id,
|
||||
int nr_ites;
|
||||
int sz;
|
||||
|
||||
baser = its_get_baser(its, GITS_BASER_TYPE_DEVICE);
|
||||
|
||||
/* Don't allow 'dev_id' that exceeds single, flat table limit */
|
||||
if (baser) {
|
||||
if (dev_id >= (PAGE_ORDER_TO_SIZE(baser->order) /
|
||||
GITS_BASER_ENTRY_SIZE(baser->val)))
|
||||
return NULL;
|
||||
} else if (ilog2(dev_id) >= its->device_ids)
|
||||
return NULL;
|
||||
|
||||
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
||||
/*
|
||||
* At least one bit of EventID is being used, hence a minimum
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irqchip/arm-gic-v3.h>
|
||||
#include <linux/irqchip/irq-partition-percpu.h>
|
||||
|
||||
#include <asm/cputype.h>
|
||||
#include <asm/exception.h>
|
||||
@@ -44,6 +45,7 @@ struct redist_region {
|
||||
};
|
||||
|
||||
struct gic_chip_data {
|
||||
struct fwnode_handle *fwnode;
|
||||
void __iomem *dist_base;
|
||||
struct redist_region *redist_regions;
|
||||
struct rdists rdists;
|
||||
@@ -51,6 +53,7 @@ struct gic_chip_data {
|
||||
u64 redist_stride;
|
||||
u32 nr_redist_regions;
|
||||
unsigned int irq_nr;
|
||||
struct partition_desc *ppi_descs[16];
|
||||
};
|
||||
|
||||
static struct gic_chip_data gic_data __read_mostly;
|
||||
@@ -364,6 +367,13 @@ static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs
|
||||
if (static_key_true(&supports_deactivate))
|
||||
gic_write_dir(irqnr);
|
||||
#ifdef CONFIG_SMP
|
||||
/*
|
||||
* Unlike GICv2, we don't need an smp_rmb() here.
|
||||
* The control dependency from gic_read_iar to
|
||||
* the ISB in gic_write_eoir is enough to ensure
|
||||
* that any shared data read by handle_IPI will
|
||||
* be read after the ACK.
|
||||
*/
|
||||
handle_IPI(irqnr, regs);
|
||||
#else
|
||||
WARN_ONCE(true, "Unexpected SGI received!\n");
|
||||
@@ -383,6 +393,15 @@ static void __init gic_dist_init(void)
|
||||
writel_relaxed(0, base + GICD_CTLR);
|
||||
gic_dist_wait_for_rwp();
|
||||
|
||||
/*
|
||||
* Configure SPIs as non-secure Group-1. This will only matter
|
||||
* if the GIC only has a single security state. This will not
|
||||
* do the right thing if the kernel is running in secure mode,
|
||||
* but that's not the intended use case anyway.
|
||||
*/
|
||||
for (i = 32; i < gic_data.irq_nr; i += 32)
|
||||
writel_relaxed(~0, base + GICD_IGROUPR + i / 8);
|
||||
|
||||
gic_dist_config(base, gic_data.irq_nr, gic_dist_wait_for_rwp);
|
||||
|
||||
/* Enable distributor with ARE, Group1 */
|
||||
@@ -500,6 +519,9 @@ static void gic_cpu_init(void)
|
||||
|
||||
rbase = gic_data_rdist_sgi_base();
|
||||
|
||||
/* Configure SGIs/PPIs as non-secure Group-1 */
|
||||
writel_relaxed(~0, rbase + GICR_IGROUPR0);
|
||||
|
||||
gic_cpu_config(rbase, gic_redist_wait_for_rwp);
|
||||
|
||||
/* Give LPIs a spin */
|
||||
@@ -812,10 +834,62 @@ static void gic_irq_domain_free(struct irq_domain *domain, unsigned int virq,
|
||||
}
|
||||
}
|
||||
|
||||
static int gic_irq_domain_select(struct irq_domain *d,
|
||||
struct irq_fwspec *fwspec,
|
||||
enum irq_domain_bus_token bus_token)
|
||||
{
|
||||
/* Not for us */
|
||||
if (fwspec->fwnode != d->fwnode)
|
||||
return 0;
|
||||
|
||||
/* If this is not DT, then we have a single domain */
|
||||
if (!is_of_node(fwspec->fwnode))
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* If this is a PPI and we have a 4th (non-null) parameter,
|
||||
* then we need to match the partition domain.
|
||||
*/
|
||||
if (fwspec->param_count >= 4 &&
|
||||
fwspec->param[0] == 1 && fwspec->param[3] != 0)
|
||||
return d == partition_get_domain(gic_data.ppi_descs[fwspec->param[1]]);
|
||||
|
||||
return d == gic_data.domain;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops gic_irq_domain_ops = {
|
||||
.translate = gic_irq_domain_translate,
|
||||
.alloc = gic_irq_domain_alloc,
|
||||
.free = gic_irq_domain_free,
|
||||
.select = gic_irq_domain_select,
|
||||
};
|
||||
|
||||
static int partition_domain_translate(struct irq_domain *d,
|
||||
struct irq_fwspec *fwspec,
|
||||
unsigned long *hwirq,
|
||||
unsigned int *type)
|
||||
{
|
||||
struct device_node *np;
|
||||
int ret;
|
||||
|
||||
np = of_find_node_by_phandle(fwspec->param[3]);
|
||||
if (WARN_ON(!np))
|
||||
return -EINVAL;
|
||||
|
||||
ret = partition_translate_id(gic_data.ppi_descs[fwspec->param[1]],
|
||||
of_node_to_fwnode(np));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*hwirq = ret;
|
||||
*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops partition_domain_ops = {
|
||||
.translate = partition_domain_translate,
|
||||
.select = gic_irq_domain_select,
|
||||
};
|
||||
|
||||
static void gicv3_enable_quirks(void)
|
||||
@@ -843,6 +917,7 @@ static int __init gic_init_bases(void __iomem *dist_base,
|
||||
if (static_key_true(&supports_deactivate))
|
||||
pr_info("GIC: Using split EOI/Deactivate mode\n");
|
||||
|
||||
gic_data.fwnode = handle;
|
||||
gic_data.dist_base = dist_base;
|
||||
gic_data.redist_regions = rdist_regs;
|
||||
gic_data.nr_redist_regions = nr_redist_regions;
|
||||
@@ -901,6 +976,119 @@ static int __init gic_validate_dist_version(void __iomem *dist_base)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_cpu_number(struct device_node *dn)
|
||||
{
|
||||
const __be32 *cell;
|
||||
u64 hwid;
|
||||
int i;
|
||||
|
||||
cell = of_get_property(dn, "reg", NULL);
|
||||
if (!cell)
|
||||
return -1;
|
||||
|
||||
hwid = of_read_number(cell, of_n_addr_cells(dn));
|
||||
|
||||
/*
|
||||
* Non affinity bits must be set to 0 in the DT
|
||||
*/
|
||||
if (hwid & ~MPIDR_HWID_BITMASK)
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < num_possible_cpus(); i++)
|
||||
if (cpu_logical_map(i) == hwid)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Create all possible partitions at boot time */
|
||||
static void gic_populate_ppi_partitions(struct device_node *gic_node)
|
||||
{
|
||||
struct device_node *parts_node, *child_part;
|
||||
int part_idx = 0, i;
|
||||
int nr_parts;
|
||||
struct partition_affinity *parts;
|
||||
|
||||
parts_node = of_find_node_by_name(gic_node, "ppi-partitions");
|
||||
if (!parts_node)
|
||||
return;
|
||||
|
||||
nr_parts = of_get_child_count(parts_node);
|
||||
|
||||
if (!nr_parts)
|
||||
return;
|
||||
|
||||
parts = kzalloc(sizeof(*parts) * nr_parts, GFP_KERNEL);
|
||||
if (WARN_ON(!parts))
|
||||
return;
|
||||
|
||||
for_each_child_of_node(parts_node, child_part) {
|
||||
struct partition_affinity *part;
|
||||
int n;
|
||||
|
||||
part = &parts[part_idx];
|
||||
|
||||
part->partition_id = of_node_to_fwnode(child_part);
|
||||
|
||||
pr_info("GIC: PPI partition %s[%d] { ",
|
||||
child_part->name, part_idx);
|
||||
|
||||
n = of_property_count_elems_of_size(child_part, "affinity",
|
||||
sizeof(u32));
|
||||
WARN_ON(n <= 0);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
int err, cpu;
|
||||
u32 cpu_phandle;
|
||||
struct device_node *cpu_node;
|
||||
|
||||
err = of_property_read_u32_index(child_part, "affinity",
|
||||
i, &cpu_phandle);
|
||||
if (WARN_ON(err))
|
||||
continue;
|
||||
|
||||
cpu_node = of_find_node_by_phandle(cpu_phandle);
|
||||
if (WARN_ON(!cpu_node))
|
||||
continue;
|
||||
|
||||
cpu = get_cpu_number(cpu_node);
|
||||
if (WARN_ON(cpu == -1))
|
||||
continue;
|
||||
|
||||
pr_cont("%s[%d] ", cpu_node->full_name, cpu);
|
||||
|
||||
cpumask_set_cpu(cpu, &part->mask);
|
||||
}
|
||||
|
||||
pr_cont("}\n");
|
||||
part_idx++;
|
||||
}
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
unsigned int irq;
|
||||
struct partition_desc *desc;
|
||||
struct irq_fwspec ppi_fwspec = {
|
||||
.fwnode = gic_data.fwnode,
|
||||
.param_count = 3,
|
||||
.param = {
|
||||
[0] = 1,
|
||||
[1] = i,
|
||||
[2] = IRQ_TYPE_NONE,
|
||||
},
|
||||
};
|
||||
|
||||
irq = irq_create_fwspec_mapping(&ppi_fwspec);
|
||||
if (WARN_ON(!irq))
|
||||
continue;
|
||||
desc = partition_create_desc(gic_data.fwnode, parts, nr_parts,
|
||||
irq, &partition_domain_ops);
|
||||
if (WARN_ON(!desc))
|
||||
continue;
|
||||
|
||||
gic_data.ppi_descs[i] = desc;
|
||||
}
|
||||
}
|
||||
|
||||
static int __init gic_of_init(struct device_node *node, struct device_node *parent)
|
||||
{
|
||||
void __iomem *dist_base;
|
||||
@@ -952,8 +1140,11 @@ static int __init gic_of_init(struct device_node *node, struct device_node *pare
|
||||
|
||||
err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions,
|
||||
redist_stride, &node->fwnode);
|
||||
if (!err)
|
||||
return 0;
|
||||
if (err)
|
||||
goto out_unmap_rdist;
|
||||
|
||||
gic_populate_ppi_partitions(node);
|
||||
return 0;
|
||||
|
||||
out_unmap_rdist:
|
||||
for (i = 0; i < nr_redist_regions; i++)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
238
drivers/irqchip/irq-lpc32xx.c
Normal file
238
drivers/irqchip/irq-lpc32xx.c
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright 2015-2016 Vladimir Zapolskiy <vz@mleia.com>
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/slab.h>
|
||||
#include <asm/exception.h>
|
||||
|
||||
#define LPC32XX_INTC_MASK 0x00
|
||||
#define LPC32XX_INTC_RAW 0x04
|
||||
#define LPC32XX_INTC_STAT 0x08
|
||||
#define LPC32XX_INTC_POL 0x0C
|
||||
#define LPC32XX_INTC_TYPE 0x10
|
||||
#define LPC32XX_INTC_FIQ 0x14
|
||||
|
||||
#define NR_LPC32XX_IC_IRQS 32
|
||||
|
||||
struct lpc32xx_irq_chip {
|
||||
void __iomem *base;
|
||||
struct irq_domain *domain;
|
||||
struct irq_chip chip;
|
||||
};
|
||||
|
||||
static struct lpc32xx_irq_chip *lpc32xx_mic_irqc;
|
||||
|
||||
static inline u32 lpc32xx_ic_read(struct lpc32xx_irq_chip *ic, u32 reg)
|
||||
{
|
||||
return readl_relaxed(ic->base + reg);
|
||||
}
|
||||
|
||||
static inline void lpc32xx_ic_write(struct lpc32xx_irq_chip *ic,
|
||||
u32 reg, u32 val)
|
||||
{
|
||||
writel_relaxed(val, ic->base + reg);
|
||||
}
|
||||
|
||||
static void lpc32xx_irq_mask(struct irq_data *d)
|
||||
{
|
||||
struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
|
||||
u32 val, mask = BIT(d->hwirq);
|
||||
|
||||
val = lpc32xx_ic_read(ic, LPC32XX_INTC_MASK) & ~mask;
|
||||
lpc32xx_ic_write(ic, LPC32XX_INTC_MASK, val);
|
||||
}
|
||||
|
||||
static void lpc32xx_irq_unmask(struct irq_data *d)
|
||||
{
|
||||
struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
|
||||
u32 val, mask = BIT(d->hwirq);
|
||||
|
||||
val = lpc32xx_ic_read(ic, LPC32XX_INTC_MASK) | mask;
|
||||
lpc32xx_ic_write(ic, LPC32XX_INTC_MASK, val);
|
||||
}
|
||||
|
||||
static void lpc32xx_irq_ack(struct irq_data *d)
|
||||
{
|
||||
struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
|
||||
u32 mask = BIT(d->hwirq);
|
||||
|
||||
lpc32xx_ic_write(ic, LPC32XX_INTC_RAW, mask);
|
||||
}
|
||||
|
||||
static int lpc32xx_irq_set_type(struct irq_data *d, unsigned int type)
|
||||
{
|
||||
struct lpc32xx_irq_chip *ic = irq_data_get_irq_chip_data(d);
|
||||
u32 val, mask = BIT(d->hwirq);
|
||||
bool high, edge;
|
||||
|
||||
switch (type) {
|
||||
case IRQ_TYPE_EDGE_RISING:
|
||||
edge = true;
|
||||
high = true;
|
||||
break;
|
||||
case IRQ_TYPE_EDGE_FALLING:
|
||||
edge = true;
|
||||
high = false;
|
||||
break;
|
||||
case IRQ_TYPE_LEVEL_HIGH:
|
||||
edge = false;
|
||||
high = true;
|
||||
break;
|
||||
case IRQ_TYPE_LEVEL_LOW:
|
||||
edge = false;
|
||||
high = false;
|
||||
break;
|
||||
default:
|
||||
pr_info("unsupported irq type %d\n", type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
irqd_set_trigger_type(d, type);
|
||||
|
||||
val = lpc32xx_ic_read(ic, LPC32XX_INTC_POL);
|
||||
if (high)
|
||||
val |= mask;
|
||||
else
|
||||
val &= ~mask;
|
||||
lpc32xx_ic_write(ic, LPC32XX_INTC_POL, val);
|
||||
|
||||
val = lpc32xx_ic_read(ic, LPC32XX_INTC_TYPE);
|
||||
if (edge) {
|
||||
val |= mask;
|
||||
irq_set_handler_locked(d, handle_edge_irq);
|
||||
} else {
|
||||
val &= ~mask;
|
||||
irq_set_handler_locked(d, handle_level_irq);
|
||||
}
|
||||
lpc32xx_ic_write(ic, LPC32XX_INTC_TYPE, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exception_irq_entry lpc32xx_handle_irq(struct pt_regs *regs)
|
||||
{
|
||||
struct lpc32xx_irq_chip *ic = lpc32xx_mic_irqc;
|
||||
u32 hwirq = lpc32xx_ic_read(ic, LPC32XX_INTC_STAT), irq;
|
||||
|
||||
while (hwirq) {
|
||||
irq = __ffs(hwirq);
|
||||
hwirq &= ~BIT(irq);
|
||||
handle_domain_irq(lpc32xx_mic_irqc->domain, irq, regs);
|
||||
}
|
||||
}
|
||||
|
||||
static void lpc32xx_sic_handler(struct irq_desc *desc)
|
||||
{
|
||||
struct lpc32xx_irq_chip *ic = irq_desc_get_handler_data(desc);
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
u32 hwirq = lpc32xx_ic_read(ic, LPC32XX_INTC_STAT), irq;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
|
||||
while (hwirq) {
|
||||
irq = __ffs(hwirq);
|
||||
hwirq &= ~BIT(irq);
|
||||
generic_handle_irq(irq_find_mapping(ic->domain, irq));
|
||||
}
|
||||
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
static int lpc32xx_irq_domain_map(struct irq_domain *id, unsigned int virq,
|
||||
irq_hw_number_t hw)
|
||||
{
|
||||
struct lpc32xx_irq_chip *ic = id->host_data;
|
||||
|
||||
irq_set_chip_data(virq, ic);
|
||||
irq_set_chip_and_handler(virq, &ic->chip, handle_level_irq);
|
||||
irq_set_status_flags(virq, IRQ_LEVEL);
|
||||
irq_set_noprobe(virq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lpc32xx_irq_domain_unmap(struct irq_domain *id, unsigned int virq)
|
||||
{
|
||||
irq_set_chip_and_handler(virq, NULL, NULL);
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops lpc32xx_irq_domain_ops = {
|
||||
.map = lpc32xx_irq_domain_map,
|
||||
.unmap = lpc32xx_irq_domain_unmap,
|
||||
.xlate = irq_domain_xlate_twocell,
|
||||
};
|
||||
|
||||
static int __init lpc32xx_of_ic_init(struct device_node *node,
|
||||
struct device_node *parent)
|
||||
{
|
||||
struct lpc32xx_irq_chip *irqc;
|
||||
bool is_mic = of_device_is_compatible(node, "nxp,lpc3220-mic");
|
||||
const __be32 *reg = of_get_property(node, "reg", NULL);
|
||||
u32 parent_irq, i, addr = reg ? be32_to_cpu(*reg) : 0;
|
||||
|
||||
irqc = kzalloc(sizeof(*irqc), GFP_KERNEL);
|
||||
if (!irqc)
|
||||
return -ENOMEM;
|
||||
|
||||
irqc->base = of_iomap(node, 0);
|
||||
if (!irqc->base) {
|
||||
pr_err("%s: unable to map registers\n", node->full_name);
|
||||
kfree(irqc);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
irqc->chip.irq_ack = lpc32xx_irq_ack;
|
||||
irqc->chip.irq_mask = lpc32xx_irq_mask;
|
||||
irqc->chip.irq_unmask = lpc32xx_irq_unmask;
|
||||
irqc->chip.irq_set_type = lpc32xx_irq_set_type;
|
||||
if (is_mic)
|
||||
irqc->chip.name = kasprintf(GFP_KERNEL, "%08x.mic", addr);
|
||||
else
|
||||
irqc->chip.name = kasprintf(GFP_KERNEL, "%08x.sic", addr);
|
||||
|
||||
irqc->domain = irq_domain_add_linear(node, NR_LPC32XX_IC_IRQS,
|
||||
&lpc32xx_irq_domain_ops, irqc);
|
||||
if (!irqc->domain) {
|
||||
pr_err("unable to add irq domain\n");
|
||||
iounmap(irqc->base);
|
||||
kfree(irqc->chip.name);
|
||||
kfree(irqc);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (is_mic) {
|
||||
lpc32xx_mic_irqc = irqc;
|
||||
set_handle_irq(lpc32xx_handle_irq);
|
||||
} else {
|
||||
for (i = 0; i < of_irq_count(node); i++) {
|
||||
parent_irq = irq_of_parse_and_map(node, i);
|
||||
if (parent_irq)
|
||||
irq_set_chained_handler_and_data(parent_irq,
|
||||
lpc32xx_sic_handler, irqc);
|
||||
}
|
||||
}
|
||||
|
||||
lpc32xx_ic_write(irqc, LPC32XX_INTC_MASK, 0x00);
|
||||
lpc32xx_ic_write(irqc, LPC32XX_INTC_POL, 0x00);
|
||||
lpc32xx_ic_write(irqc, LPC32XX_INTC_TYPE, 0x00);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
IRQCHIP_DECLARE(nxp_lpc32xx_mic, "nxp,lpc3220-mic", lpc32xx_of_ic_init);
|
||||
IRQCHIP_DECLARE(nxp_lpc32xx_sic, "nxp,lpc3220-sic", lpc32xx_of_ic_init);
|
||||
240
drivers/irqchip/irq-ls-scfg-msi.c
Normal file
240
drivers/irqchip/irq-ls-scfg-msi.c
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Freescale SCFG MSI(-X) support
|
||||
*
|
||||
* Copyright (C) 2016 Freescale Semiconductor.
|
||||
*
|
||||
* Author: Minghuan Lian <Minghuan.Lian@nxp.com>
|
||||
*
|
||||
* 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/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/of_pci.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#define MSI_MAX_IRQS 32
|
||||
#define MSI_IBS_SHIFT 3
|
||||
#define MSIR 4
|
||||
|
||||
struct ls_scfg_msi {
|
||||
spinlock_t lock;
|
||||
struct platform_device *pdev;
|
||||
struct irq_domain *parent;
|
||||
struct irq_domain *msi_domain;
|
||||
void __iomem *regs;
|
||||
phys_addr_t msiir_addr;
|
||||
int irq;
|
||||
DECLARE_BITMAP(used, MSI_MAX_IRQS);
|
||||
};
|
||||
|
||||
static struct irq_chip ls_scfg_msi_irq_chip = {
|
||||
.name = "MSI",
|
||||
.irq_mask = pci_msi_mask_irq,
|
||||
.irq_unmask = pci_msi_unmask_irq,
|
||||
};
|
||||
|
||||
static struct msi_domain_info ls_scfg_msi_domain_info = {
|
||||
.flags = (MSI_FLAG_USE_DEF_DOM_OPS |
|
||||
MSI_FLAG_USE_DEF_CHIP_OPS |
|
||||
MSI_FLAG_PCI_MSIX),
|
||||
.chip = &ls_scfg_msi_irq_chip,
|
||||
};
|
||||
|
||||
static void ls_scfg_msi_compose_msg(struct irq_data *data, struct msi_msg *msg)
|
||||
{
|
||||
struct ls_scfg_msi *msi_data = irq_data_get_irq_chip_data(data);
|
||||
|
||||
msg->address_hi = upper_32_bits(msi_data->msiir_addr);
|
||||
msg->address_lo = lower_32_bits(msi_data->msiir_addr);
|
||||
msg->data = data->hwirq << MSI_IBS_SHIFT;
|
||||
}
|
||||
|
||||
static int ls_scfg_msi_set_affinity(struct irq_data *irq_data,
|
||||
const struct cpumask *mask, bool force)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static struct irq_chip ls_scfg_msi_parent_chip = {
|
||||
.name = "SCFG",
|
||||
.irq_compose_msi_msg = ls_scfg_msi_compose_msg,
|
||||
.irq_set_affinity = ls_scfg_msi_set_affinity,
|
||||
};
|
||||
|
||||
static int ls_scfg_msi_domain_irq_alloc(struct irq_domain *domain,
|
||||
unsigned int virq,
|
||||
unsigned int nr_irqs,
|
||||
void *args)
|
||||
{
|
||||
struct ls_scfg_msi *msi_data = domain->host_data;
|
||||
int pos, err = 0;
|
||||
|
||||
WARN_ON(nr_irqs != 1);
|
||||
|
||||
spin_lock(&msi_data->lock);
|
||||
pos = find_first_zero_bit(msi_data->used, MSI_MAX_IRQS);
|
||||
if (pos < MSI_MAX_IRQS)
|
||||
__set_bit(pos, msi_data->used);
|
||||
else
|
||||
err = -ENOSPC;
|
||||
spin_unlock(&msi_data->lock);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
irq_domain_set_info(domain, virq, pos,
|
||||
&ls_scfg_msi_parent_chip, msi_data,
|
||||
handle_simple_irq, NULL, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ls_scfg_msi_domain_irq_free(struct irq_domain *domain,
|
||||
unsigned int virq, unsigned int nr_irqs)
|
||||
{
|
||||
struct irq_data *d = irq_domain_get_irq_data(domain, virq);
|
||||
struct ls_scfg_msi *msi_data = irq_data_get_irq_chip_data(d);
|
||||
int pos;
|
||||
|
||||
pos = d->hwirq;
|
||||
if (pos < 0 || pos >= MSI_MAX_IRQS) {
|
||||
pr_err("failed to teardown msi. Invalid hwirq %d\n", pos);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock(&msi_data->lock);
|
||||
__clear_bit(pos, msi_data->used);
|
||||
spin_unlock(&msi_data->lock);
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops ls_scfg_msi_domain_ops = {
|
||||
.alloc = ls_scfg_msi_domain_irq_alloc,
|
||||
.free = ls_scfg_msi_domain_irq_free,
|
||||
};
|
||||
|
||||
static void ls_scfg_msi_irq_handler(struct irq_desc *desc)
|
||||
{
|
||||
struct ls_scfg_msi *msi_data = irq_desc_get_handler_data(desc);
|
||||
unsigned long val;
|
||||
int pos, virq;
|
||||
|
||||
chained_irq_enter(irq_desc_get_chip(desc), desc);
|
||||
|
||||
val = ioread32be(msi_data->regs + MSIR);
|
||||
for_each_set_bit(pos, &val, MSI_MAX_IRQS) {
|
||||
virq = irq_find_mapping(msi_data->parent, (31 - pos));
|
||||
if (virq)
|
||||
generic_handle_irq(virq);
|
||||
}
|
||||
|
||||
chained_irq_exit(irq_desc_get_chip(desc), desc);
|
||||
}
|
||||
|
||||
static int ls_scfg_msi_domains_init(struct ls_scfg_msi *msi_data)
|
||||
{
|
||||
/* Initialize MSI domain parent */
|
||||
msi_data->parent = irq_domain_add_linear(NULL,
|
||||
MSI_MAX_IRQS,
|
||||
&ls_scfg_msi_domain_ops,
|
||||
msi_data);
|
||||
if (!msi_data->parent) {
|
||||
dev_err(&msi_data->pdev->dev, "failed to create IRQ domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
msi_data->msi_domain = pci_msi_create_irq_domain(
|
||||
of_node_to_fwnode(msi_data->pdev->dev.of_node),
|
||||
&ls_scfg_msi_domain_info,
|
||||
msi_data->parent);
|
||||
if (!msi_data->msi_domain) {
|
||||
dev_err(&msi_data->pdev->dev, "failed to create MSI domain\n");
|
||||
irq_domain_remove(msi_data->parent);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_scfg_msi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ls_scfg_msi *msi_data;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
msi_data = devm_kzalloc(&pdev->dev, sizeof(*msi_data), GFP_KERNEL);
|
||||
if (!msi_data)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
msi_data->regs = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(msi_data->regs)) {
|
||||
dev_err(&pdev->dev, "failed to initialize 'regs'\n");
|
||||
return PTR_ERR(msi_data->regs);
|
||||
}
|
||||
msi_data->msiir_addr = res->start;
|
||||
|
||||
msi_data->irq = platform_get_irq(pdev, 0);
|
||||
if (msi_data->irq <= 0) {
|
||||
dev_err(&pdev->dev, "failed to get MSI irq\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
msi_data->pdev = pdev;
|
||||
spin_lock_init(&msi_data->lock);
|
||||
|
||||
ret = ls_scfg_msi_domains_init(msi_data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
irq_set_chained_handler_and_data(msi_data->irq,
|
||||
ls_scfg_msi_irq_handler,
|
||||
msi_data);
|
||||
|
||||
platform_set_drvdata(pdev, msi_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_scfg_msi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ls_scfg_msi *msi_data = platform_get_drvdata(pdev);
|
||||
|
||||
irq_set_chained_handler_and_data(msi_data->irq, NULL, NULL);
|
||||
|
||||
irq_domain_remove(msi_data->msi_domain);
|
||||
irq_domain_remove(msi_data->parent);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ls_scfg_msi_id[] = {
|
||||
{ .compatible = "fsl,1s1021a-msi", },
|
||||
{ .compatible = "fsl,1s1043a-msi", },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver ls_scfg_msi_driver = {
|
||||
.driver = {
|
||||
.name = "ls-scfg-msi",
|
||||
.of_match_table = ls_scfg_msi_id,
|
||||
},
|
||||
.probe = ls_scfg_msi_probe,
|
||||
.remove = ls_scfg_msi_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(ls_scfg_msi_driver);
|
||||
|
||||
MODULE_AUTHOR("Minghuan Lian <Minghuan.Lian@nxp.com>");
|
||||
MODULE_DESCRIPTION("Freescale Layerscape SCFG MSI controller driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
@@ -263,8 +263,8 @@ static int mbigen_device_probe(struct platform_device *pdev)
|
||||
|
||||
parent = platform_bus_type.dev_root;
|
||||
child = of_platform_device_create(np, NULL, parent);
|
||||
if (IS_ERR(child))
|
||||
return PTR_ERR(child);
|
||||
if (!child)
|
||||
return -ENOMEM;
|
||||
|
||||
if (of_property_read_u32(child->dev.of_node, "num-pins",
|
||||
&num_pins) < 0) {
|
||||
|
||||
256
drivers/irqchip/irq-partition-percpu.c
Normal file
256
drivers/irqchip/irq-partition-percpu.c
Normal file
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright (C) 2016 ARM Limited, All Rights Reserved.
|
||||
* Author: Marc Zyngier <marc.zyngier@arm.com>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/irqchip/irq-partition-percpu.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct partition_desc {
|
||||
int nr_parts;
|
||||
struct partition_affinity *parts;
|
||||
struct irq_domain *domain;
|
||||
struct irq_desc *chained_desc;
|
||||
unsigned long *bitmap;
|
||||
struct irq_domain_ops ops;
|
||||
};
|
||||
|
||||
static bool partition_check_cpu(struct partition_desc *part,
|
||||
unsigned int cpu, unsigned int hwirq)
|
||||
{
|
||||
return cpumask_test_cpu(cpu, &part->parts[hwirq].mask);
|
||||
}
|
||||
|
||||
static void partition_irq_mask(struct irq_data *d)
|
||||
{
|
||||
struct partition_desc *part = irq_data_get_irq_chip_data(d);
|
||||
struct irq_chip *chip = irq_desc_get_chip(part->chained_desc);
|
||||
struct irq_data *data = irq_desc_get_irq_data(part->chained_desc);
|
||||
|
||||
if (partition_check_cpu(part, smp_processor_id(), d->hwirq) &&
|
||||
chip->irq_mask)
|
||||
chip->irq_mask(data);
|
||||
}
|
||||
|
||||
static void partition_irq_unmask(struct irq_data *d)
|
||||
{
|
||||
struct partition_desc *part = irq_data_get_irq_chip_data(d);
|
||||
struct irq_chip *chip = irq_desc_get_chip(part->chained_desc);
|
||||
struct irq_data *data = irq_desc_get_irq_data(part->chained_desc);
|
||||
|
||||
if (partition_check_cpu(part, smp_processor_id(), d->hwirq) &&
|
||||
chip->irq_unmask)
|
||||
chip->irq_unmask(data);
|
||||
}
|
||||
|
||||
static int partition_irq_set_irqchip_state(struct irq_data *d,
|
||||
enum irqchip_irq_state which,
|
||||
bool val)
|
||||
{
|
||||
struct partition_desc *part = irq_data_get_irq_chip_data(d);
|
||||
struct irq_chip *chip = irq_desc_get_chip(part->chained_desc);
|
||||
struct irq_data *data = irq_desc_get_irq_data(part->chained_desc);
|
||||
|
||||
if (partition_check_cpu(part, smp_processor_id(), d->hwirq) &&
|
||||
chip->irq_set_irqchip_state)
|
||||
return chip->irq_set_irqchip_state(data, which, val);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int partition_irq_get_irqchip_state(struct irq_data *d,
|
||||
enum irqchip_irq_state which,
|
||||
bool *val)
|
||||
{
|
||||
struct partition_desc *part = irq_data_get_irq_chip_data(d);
|
||||
struct irq_chip *chip = irq_desc_get_chip(part->chained_desc);
|
||||
struct irq_data *data = irq_desc_get_irq_data(part->chained_desc);
|
||||
|
||||
if (partition_check_cpu(part, smp_processor_id(), d->hwirq) &&
|
||||
chip->irq_get_irqchip_state)
|
||||
return chip->irq_get_irqchip_state(data, which, val);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int partition_irq_set_type(struct irq_data *d, unsigned int type)
|
||||
{
|
||||
struct partition_desc *part = irq_data_get_irq_chip_data(d);
|
||||
struct irq_chip *chip = irq_desc_get_chip(part->chained_desc);
|
||||
struct irq_data *data = irq_desc_get_irq_data(part->chained_desc);
|
||||
|
||||
if (chip->irq_set_type)
|
||||
return chip->irq_set_type(data, type);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void partition_irq_print_chip(struct irq_data *d, struct seq_file *p)
|
||||
{
|
||||
struct partition_desc *part = irq_data_get_irq_chip_data(d);
|
||||
struct irq_chip *chip = irq_desc_get_chip(part->chained_desc);
|
||||
struct irq_data *data = irq_desc_get_irq_data(part->chained_desc);
|
||||
|
||||
seq_printf(p, " %5s-%lu", chip->name, data->hwirq);
|
||||
}
|
||||
|
||||
static struct irq_chip partition_irq_chip = {
|
||||
.irq_mask = partition_irq_mask,
|
||||
.irq_unmask = partition_irq_unmask,
|
||||
.irq_set_type = partition_irq_set_type,
|
||||
.irq_get_irqchip_state = partition_irq_get_irqchip_state,
|
||||
.irq_set_irqchip_state = partition_irq_set_irqchip_state,
|
||||
.irq_print_chip = partition_irq_print_chip,
|
||||
};
|
||||
|
||||
static void partition_handle_irq(struct irq_desc *desc)
|
||||
{
|
||||
struct partition_desc *part = irq_desc_get_handler_data(desc);
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
int cpu = smp_processor_id();
|
||||
int hwirq;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
|
||||
for_each_set_bit(hwirq, part->bitmap, part->nr_parts) {
|
||||
if (partition_check_cpu(part, cpu, hwirq))
|
||||
break;
|
||||
}
|
||||
|
||||
if (unlikely(hwirq == part->nr_parts)) {
|
||||
handle_bad_irq(desc);
|
||||
} else {
|
||||
unsigned int irq;
|
||||
irq = irq_find_mapping(part->domain, hwirq);
|
||||
generic_handle_irq(irq);
|
||||
}
|
||||
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
static int partition_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs, void *arg)
|
||||
{
|
||||
int ret;
|
||||
irq_hw_number_t hwirq;
|
||||
unsigned int type;
|
||||
struct irq_fwspec *fwspec = arg;
|
||||
struct partition_desc *part;
|
||||
|
||||
BUG_ON(nr_irqs != 1);
|
||||
ret = domain->ops->translate(domain, fwspec, &hwirq, &type);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
part = domain->host_data;
|
||||
|
||||
set_bit(hwirq, part->bitmap);
|
||||
irq_set_chained_handler_and_data(irq_desc_get_irq(part->chained_desc),
|
||||
partition_handle_irq, part);
|
||||
irq_set_percpu_devid_partition(virq, &part->parts[hwirq].mask);
|
||||
irq_domain_set_info(domain, virq, hwirq, &partition_irq_chip, part,
|
||||
handle_percpu_devid_irq, NULL, NULL);
|
||||
irq_set_status_flags(virq, IRQ_NOAUTOEN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void partition_domain_free(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs)
|
||||
{
|
||||
struct irq_data *d;
|
||||
|
||||
BUG_ON(nr_irqs != 1);
|
||||
|
||||
d = irq_domain_get_irq_data(domain, virq);
|
||||
irq_set_handler(virq, NULL);
|
||||
irq_domain_reset_irq_data(d);
|
||||
}
|
||||
|
||||
int partition_translate_id(struct partition_desc *desc, void *partition_id)
|
||||
{
|
||||
struct partition_affinity *part = NULL;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < desc->nr_parts; i++) {
|
||||
if (desc->parts[i].partition_id == partition_id) {
|
||||
part = &desc->parts[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (WARN_ON(!part)) {
|
||||
pr_err("Failed to find partition\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
struct partition_desc *partition_create_desc(struct fwnode_handle *fwnode,
|
||||
struct partition_affinity *parts,
|
||||
int nr_parts,
|
||||
int chained_irq,
|
||||
const struct irq_domain_ops *ops)
|
||||
{
|
||||
struct partition_desc *desc;
|
||||
struct irq_domain *d;
|
||||
|
||||
BUG_ON(!ops->select || !ops->translate);
|
||||
|
||||
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
|
||||
if (!desc)
|
||||
return NULL;
|
||||
|
||||
desc->ops = *ops;
|
||||
desc->ops.free = partition_domain_free;
|
||||
desc->ops.alloc = partition_domain_alloc;
|
||||
|
||||
d = irq_domain_create_linear(fwnode, nr_parts, &desc->ops, desc);
|
||||
if (!d)
|
||||
goto out;
|
||||
desc->domain = d;
|
||||
|
||||
desc->bitmap = kzalloc(sizeof(long) * BITS_TO_LONGS(nr_parts),
|
||||
GFP_KERNEL);
|
||||
if (WARN_ON(!desc->bitmap))
|
||||
goto out;
|
||||
|
||||
desc->chained_desc = irq_to_desc(chained_irq);
|
||||
desc->nr_parts = nr_parts;
|
||||
desc->parts = parts;
|
||||
|
||||
return desc;
|
||||
out:
|
||||
if (d)
|
||||
irq_domain_remove(d);
|
||||
kfree(desc);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct irq_domain *partition_get_domain(struct partition_desc *dsc)
|
||||
{
|
||||
if (dsc)
|
||||
return dsc->domain;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -235,7 +235,7 @@ static int tegra_ictlr_domain_translate(struct irq_domain *d,
|
||||
return -EINVAL;
|
||||
|
||||
*hwirq = fwspec->param[1];
|
||||
*type = fwspec->param[2];
|
||||
*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -530,6 +530,10 @@ static inline void irq_set_chip_and_handler(unsigned int irq, struct irq_chip *c
|
||||
}
|
||||
|
||||
extern int irq_set_percpu_devid(unsigned int irq);
|
||||
extern int irq_set_percpu_devid_partition(unsigned int irq,
|
||||
const struct cpumask *affinity);
|
||||
extern int irq_get_percpu_devid_partition(unsigned int irq,
|
||||
struct cpumask *affinity);
|
||||
|
||||
extern void
|
||||
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user