提交 3d116a66 编写于 作者: L Linus Torvalds

Merge branch 'irq-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull irq updates from Thomas Gleixner:
 "The irq department provides:

   - Support for MSI to wire bridges and a first user of it

   - More ACPI support for ARM/GIC

   - A new TS-4800 interrupt controller driver

   - RCU based free of interrupt descriptors to support the upcoming
     Intel VMD technology without introducing a locking nightmare

   - The usual pile of fixes and updates to drivers and core code"

* 'irq-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (41 commits)
  irqchip/omap-intc: Add support for spurious irq handling
  irqchip/zevio: Use irq_data_get_chip_type() helper
  irqchip/omap-intc: Remove duplicate setup for IRQ chip type handler
  irqchip/ts4800: Add TS-4800 interrupt controller
  irqchip/ts4800: Add documentation for TS-4800 interrupt controller
  irq/platform-MSI: Increase the maximum MSIs the MSI framework can support
  irqchip/gicv2m: Miscellaneous fixes for v2m resources and SPI ranges
  irqchip/bcm2836: Make code more readable
  irqchip/bcm2836: Tolerate IRQs while no flag is set in ISR
  irqchip/bcm2836: Add SMP support for the 2836
  irqchip/bcm2836: Fix initialization of the LOCAL_IRQ_CNT timers
  irqchip/gic-v2m: acpi: Introducing GICv2m ACPI support
  irqchip/gic-v2m: Refactor to prepare for ACPI support
  irqdomain: Introduce is_fwnode_irqchip helper
  acpi: pci: Setup MSI domain for ACPI based pci devices
  genirq/msi: Export functions to allow MSI domains in modules
  irqchip/mbigen: Implement the mbigen irq chip operation functions
  irqchip/mbigen: Create irq domain for each mbigen device
  irqchip/mgigen: Add platform device driver for mbigen device
  dt-bindings: Documents the mbigen bindings
  ...
......@@ -4,7 +4,7 @@ Allwinner Sunxi NMI Controller
Required properties:
- compatible : should be "allwinner,sun7i-a20-sc-nmi" or
"allwinner,sun6i-a31-sc-nmi"
"allwinner,sun6i-a31-sc-nmi" or "allwinner,sun9i-a80-nmi"
- reg : Specifies base physical address and size of the registers.
- interrupt-controller : Identifies the node as an interrupt controller
- #interrupt-cells : Specifies the number of cells needed to encode an
......
......@@ -18,6 +18,7 @@ Main node required properties:
"arm,cortex-a9-gic"
"arm,gic-400"
"arm,pl390"
"arm,tc11mp-gic"
"brcm,brahma-b15-gic"
"qcom,msm-8660-qgic"
"qcom,msm-qgic2"
......
Hisilicon mbigen device tree bindings.
=======================================
Mbigen means: message based interrupt generator.
MBI is kind of msi interrupt only used on Non-PCI devices.
To reduce the wired interrupt number connected to GIC,
Hisilicon designed mbigen to collect and generate interrupt.
Non-pci devices can connect to mbigen and generate the
interrupt by writing ITS register.
The mbigen chip and devices connect to mbigen have the following properties:
Mbigen main node required properties:
-------------------------------------------
- compatible: Should be "hisilicon,mbigen-v2"
- reg: Specifies the base physical address and size of the Mbigen
registers.
- interrupt controller: Identifies the node as an interrupt controller
- msi-parent: Specifies the MSI controller this mbigen use.
For more detail information,please refer to the generic msi-parent binding in
Documentation/devicetree/bindings/interrupt-controller/msi.txt.
- num-pins: the total number of pins implemented in this Mbigen
instance.
- #interrupt-cells : Specifies the number of cells needed to encode an
interrupt source. The value must be 2.
The 1st cell is hardware pin number of the interrupt.This number is local to
each mbigen chip and in the range from 0 to the maximum interrupts number
of the mbigen.
The 2nd cell is the interrupt trigger type.
The value of this cell should be:
1: rising edge triggered
or
4: high level triggered
Examples:
mbigen_device_gmac:intc {
compatible = "hisilicon,mbigen-v2";
reg = <0x0 0xc0080000 0x0 0x10000>;
interrupt-controller;
msi-parent = <&its_dsa 0x40b1c>;
num-pins = <9>;
#interrupt-cells = <2>;
};
Devices connect to mbigen required properties:
----------------------------------------------------
-interrupt-parent: Specifies the mbigen device node which device connected.
-interrupts:Specifies the interrupt source.
For the specific information of each cell in this property,please refer to
the "interrupt-cells" description mentioned above.
Examples:
gmac0: ethernet@c2080000 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0 0xc2080000 0 0x20000>,
<0 0xc0000000 0 0x1000>;
interrupt-parent = <&mbigen_device_gmac>;
interrupts = <656 1>,
<657 1>;
};
TS-4800 FPGA interrupt controller
TS-4800 FPGA has an internal interrupt controller. When one of the
interrupts is triggered, the SoC is notified, usually using a GPIO as
parent interrupt source.
Required properties:
- compatible: should be "technologic,ts4800-irqc"
- interrupt-controller: identifies the node as an interrupt controller
- reg: physical base address of the controller and length of memory mapped
region
- #interrupt-cells: specifies the number of cells needed to encode an interrupt
source, should be 1.
- interrupt-parent: phandle to the parent interrupt controller this one is
cascaded from
- interrupts: specifies the interrupt line in the interrupt-parent controller
......@@ -24,13 +24,17 @@
#include <linux/msi.h>
#include <linux/slab.h>
#define DEV_ID_SHIFT 24
#define DEV_ID_SHIFT 21
#define MAX_DEV_MSIS (1 << (32 - DEV_ID_SHIFT))
/*
* Internal data structure containing a (made up, but unique) devid
* and the callback to write the MSI message.
*/
struct platform_msi_priv_data {
struct device *dev;
void *host_data;
msi_alloc_info_t arg;
irq_write_msi_msg_t write_msg;
int devid;
};
......@@ -110,39 +114,49 @@ static void platform_msi_update_chip_ops(struct msi_domain_info *info)
chip->irq_write_msi_msg = platform_msi_write_msg;
}
static void platform_msi_free_descs(struct device *dev)
static void platform_msi_free_descs(struct device *dev, int base, int nvec)
{
struct msi_desc *desc, *tmp;
list_for_each_entry_safe(desc, tmp, dev_to_msi_list(dev), list) {
list_del(&desc->list);
free_msi_entry(desc);
if (desc->platform.msi_index >= base &&
desc->platform.msi_index < (base + nvec)) {
list_del(&desc->list);
free_msi_entry(desc);
}
}
}
static int platform_msi_alloc_descs(struct device *dev, int nvec,
struct platform_msi_priv_data *data)
static int platform_msi_alloc_descs_with_irq(struct device *dev, int virq,
int nvec,
struct platform_msi_priv_data *data)
{
int i;
struct msi_desc *desc;
int i, base = 0;
for (i = 0; i < nvec; i++) {
struct msi_desc *desc;
if (!list_empty(dev_to_msi_list(dev))) {
desc = list_last_entry(dev_to_msi_list(dev),
struct msi_desc, list);
base = desc->platform.msi_index + 1;
}
for (i = 0; i < nvec; i++) {
desc = alloc_msi_entry(dev);
if (!desc)
break;
desc->platform.msi_priv_data = data;
desc->platform.msi_index = i;
desc->platform.msi_index = base + i;
desc->nvec_used = 1;
desc->irq = virq ? virq + i : 0;
list_add_tail(&desc->list, dev_to_msi_list(dev));
}
if (i != nvec) {
/* Clean up the mess */
platform_msi_free_descs(dev);
platform_msi_free_descs(dev, base, nvec);
return -ENOMEM;
}
......@@ -150,6 +164,13 @@ static int platform_msi_alloc_descs(struct device *dev, int nvec,
return 0;
}
static int platform_msi_alloc_descs(struct device *dev, int nvec,
struct platform_msi_priv_data *data)
{
return platform_msi_alloc_descs_with_irq(dev, 0, nvec, data);
}
/**
* platform_msi_create_irq_domain - Create a platform MSI interrupt domain
* @fwnode: Optional fwnode of the interrupt controller
......@@ -180,56 +201,75 @@ struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode,
return domain;
}
/**
* platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev
* @dev: The device for which to allocate interrupts
* @nvec: The number of interrupts to allocate
* @write_msi_msg: Callback to write an interrupt message for @dev
*
* Returns:
* Zero for success, or an error code in case of failure
*/
int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
irq_write_msi_msg_t write_msi_msg)
static struct platform_msi_priv_data *
platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,
irq_write_msi_msg_t write_msi_msg)
{
struct platform_msi_priv_data *priv_data;
int err;
struct platform_msi_priv_data *datap;
/*
* Limit the number of interrupts to 256 per device. Should we
* need to bump this up, DEV_ID_SHIFT should be adjusted
* accordingly (which would impact the max number of MSI
* capable devices).
*/
if (!dev->msi_domain || !write_msi_msg || !nvec ||
nvec > (1 << (32 - DEV_ID_SHIFT)))
return -EINVAL;
if (!dev->msi_domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS)
return ERR_PTR(-EINVAL);
if (dev->msi_domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) {
dev_err(dev, "Incompatible msi_domain, giving up\n");
return -EINVAL;
return ERR_PTR(-EINVAL);
}
/* Already had a helping of MSI? Greed... */
if (!list_empty(dev_to_msi_list(dev)))
return -EBUSY;
return ERR_PTR(-EBUSY);
datap = kzalloc(sizeof(*datap), GFP_KERNEL);
if (!datap)
return ERR_PTR(-ENOMEM);
datap->devid = ida_simple_get(&platform_msi_devid_ida,
0, 1 << DEV_ID_SHIFT, GFP_KERNEL);
if (datap->devid < 0) {
int err = datap->devid;
kfree(datap);
return ERR_PTR(err);
}
priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL);
if (!priv_data)
return -ENOMEM;
datap->write_msg = write_msi_msg;
datap->dev = dev;
priv_data->devid = ida_simple_get(&platform_msi_devid_ida,
0, 1 << DEV_ID_SHIFT, GFP_KERNEL);
if (priv_data->devid < 0) {
err = priv_data->devid;
goto out_free_data;
}
return datap;
}
static void platform_msi_free_priv_data(struct platform_msi_priv_data *data)
{
ida_simple_remove(&platform_msi_devid_ida, data->devid);
kfree(data);
}
/**
* platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev
* @dev: The device for which to allocate interrupts
* @nvec: The number of interrupts to allocate
* @write_msi_msg: Callback to write an interrupt message for @dev
*
* Returns:
* Zero for success, or an error code in case of failure
*/
int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
irq_write_msi_msg_t write_msi_msg)
{
struct platform_msi_priv_data *priv_data;
int err;
priv_data->write_msg = write_msi_msg;
priv_data = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg);
if (IS_ERR(priv_data))
return PTR_ERR(priv_data);
err = platform_msi_alloc_descs(dev, nvec, priv_data);
if (err)
goto out_free_id;
goto out_free_priv_data;
err = msi_domain_alloc_irqs(dev->msi_domain, dev, nvec);
if (err)
......@@ -238,11 +278,9 @@ int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
return 0;
out_free_desc:
platform_msi_free_descs(dev);
out_free_id:
ida_simple_remove(&platform_msi_devid_ida, priv_data->devid);
out_free_data:
kfree(priv_data);
platform_msi_free_descs(dev, 0, nvec);
out_free_priv_data:
platform_msi_free_priv_data(priv_data);
return err;
}
......@@ -253,18 +291,126 @@ int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
*/
void platform_msi_domain_free_irqs(struct device *dev)
{
struct msi_desc *desc;
if (!list_empty(dev_to_msi_list(dev))) {
struct msi_desc *desc;
desc = first_msi_entry(dev);
platform_msi_free_priv_data(desc->platform.msi_priv_data);
}
msi_domain_free_irqs(dev->msi_domain, dev);
platform_msi_free_descs(dev, 0, MAX_DEV_MSIS);
}
/**
* platform_msi_get_host_data - Query the private data associated with
* a platform-msi domain
* @domain: The platform-msi domain
*
* Returns the private data provided when calling
* platform_msi_create_device_domain.
*/
void *platform_msi_get_host_data(struct irq_domain *domain)
{
struct platform_msi_priv_data *data = domain->host_data;
return data->host_data;
}
/**
* platform_msi_create_device_domain - Create a platform-msi domain
*
* @dev: The device generating the MSIs
* @nvec: The number of MSIs that need to be allocated
* @write_msi_msg: Callback to write an interrupt message for @dev
* @ops: The hierarchy domain operations to use
* @host_data: Private data associated to this domain
*
* Returns an irqdomain for @nvec interrupts
*/
struct irq_domain *
platform_msi_create_device_domain(struct device *dev,
unsigned int nvec,
irq_write_msi_msg_t write_msi_msg,
const struct irq_domain_ops *ops,
void *host_data)
{
struct platform_msi_priv_data *data;
struct irq_domain *domain;
int err;
data = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg);
if (IS_ERR(data))
return NULL;
data->host_data = host_data;
domain = irq_domain_create_hierarchy(dev->msi_domain, 0, nvec,
of_node_to_fwnode(dev->of_node),
ops, data);
if (!domain)
goto free_priv;
desc = first_msi_entry(dev);
if (desc) {
struct platform_msi_priv_data *data;
err = msi_domain_prepare_irqs(domain->parent, dev, nvec, &data->arg);
if (err)
goto free_domain;
return domain;
data = desc->platform.msi_priv_data;
free_domain:
irq_domain_remove(domain);
free_priv:
platform_msi_free_priv_data(data);
return NULL;
}
/**
* platform_msi_domain_free - Free interrupts associated with a platform-msi
* domain
*
* @domain: The platform-msi domain
* @virq: The base irq from which to perform the free operation
* @nvec: How many interrupts to free from @virq
*/
void platform_msi_domain_free(struct irq_domain *domain, unsigned int virq,
unsigned int nvec)
{
struct platform_msi_priv_data *data = domain->host_data;
struct msi_desc *desc;
for_each_msi_entry(desc, data->dev) {
if (WARN_ON(!desc->irq || desc->nvec_used != 1))
return;
if (!(desc->irq >= virq && desc->irq < (virq + nvec)))
continue;
ida_simple_remove(&platform_msi_devid_ida, data->devid);
kfree(data);
irq_domain_free_irqs_common(domain, desc->irq, 1);
}
}
msi_domain_free_irqs(dev->msi_domain, dev);
platform_msi_free_descs(dev);
/**
* platform_msi_domain_alloc - Allocate interrupts associated with
* a platform-msi domain
*
* @domain: The platform-msi domain
* @virq: The base irq from which to perform the allocate operation
* @nvec: How many interrupts to free from @virq
*
* Return 0 on success, or an error code on failure. Must be called
* with irq_domain_mutex held (which can only be done as part of a
* top-level interrupt allocation).
*/
int platform_msi_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs)
{
struct platform_msi_priv_data *data = domain->host_data;
int err;
err = platform_msi_alloc_descs_with_irq(data->dev, virq, nr_irqs, data);
if (err)
return err;
err = msi_domain_populate_irqs(domain->parent, data->dev,
virq, nr_irqs, &data->arg);
if (err)
platform_msi_domain_free(domain, virq, nr_irqs);
return err;
}
......@@ -8,6 +8,11 @@ config ARM_GIC
select IRQ_DOMAIN_HIERARCHY
select MULTI_IRQ_HANDLER
config ARM_GIC_MAX_NR
int
default 2 if ARCH_REALVIEW
default 1
config ARM_GIC_V2M
bool
depends on ARM_GIC
......@@ -27,6 +32,14 @@ config ARM_GIC_V3_ITS
bool
select PCI_MSI_IRQ_DOMAIN
config HISILICON_IRQ_MBIGEN
bool "Support mbigen interrupt controller"
default n
depends on ARM_GIC_V3 && ARM_GIC_V3_ITS && GENERIC_MSI_IRQ_DOMAIN
help
Enable the mbigen interrupt controller used on
Hisilicon platform.
config ARM_NVIC
bool
select IRQ_DOMAIN
......@@ -138,6 +151,12 @@ config TB10X_IRQC
select IRQ_DOMAIN
select GENERIC_IRQ_CHIP
config TS4800_IRQ
tristate "TS-4800 IRQ controller"
select IRQ_DOMAIN
help
Support for the TS-4800 FPGA IRQ controller
config VERSATILE_FPGA_IRQ
bool
select IRQ_DOMAIN
......
......@@ -21,9 +21,11 @@ obj-$(CONFIG_ARCH_SUNXI) += irq-sun4i.o
obj-$(CONFIG_ARCH_SUNXI) += irq-sunxi-nmi.o
obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o
obj-$(CONFIG_ARM_GIC) += irq-gic.o irq-gic-common.o
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_HISILICON_IRQ_MBIGEN) += irq-mbigen.o
obj-$(CONFIG_ARM_NVIC) += irq-nvic.o
obj-$(CONFIG_ARM_VIC) += irq-vic.o
obj-$(CONFIG_ATMEL_AIC_IRQ) += irq-atmel-aic-common.o irq-atmel-aic.o
......@@ -39,6 +41,7 @@ obj-$(CONFIG_ARCH_NSPIRE) += irq-zevio.o
obj-$(CONFIG_ARCH_VT8500) += irq-vt8500.o
obj-$(CONFIG_ST_IRQCHIP) += irq-st.o
obj-$(CONFIG_TB10X_IRQC) += irq-tb10x.o
obj-$(CONFIG_TS4800_IRQ) += irq-ts4800.o
obj-$(CONFIG_XTENSA) += irq-xtensa-pic.o
obj-$(CONFIG_XTENSA_MX) += irq-xtensa-mx.o
obj-$(CONFIG_IRQ_CROSSBAR) += irq-crossbar.o
......
......@@ -21,6 +21,9 @@
#include <linux/irqdomain.h>
#include <asm/exception.h>
#define LOCAL_CONTROL 0x000
#define LOCAL_PRESCALER 0x008
/*
* The low 2 bits identify the CPU that the GPU IRQ goes to, and the
* next 2 bits identify the CPU that the GPU FIQ goes to.
......@@ -50,14 +53,16 @@
/* Same status bits as above, but for FIQ. */
#define LOCAL_FIQ_PENDING0 0x070
/*
* Mailbox0 write-to-set bits. There are 16 mailboxes, 4 per CPU, and
* Mailbox write-to-set bits. There are 16 mailboxes, 4 per CPU, and
* these bits are organized by mailbox number and then CPU number. We
* use mailbox 0 for IPIs. The mailbox's interrupt is raised while
* any bit is set.
*/
#define LOCAL_MAILBOX0_SET0 0x080
/* Mailbox0 write-to-clear bits. */
#define LOCAL_MAILBOX3_SET0 0x08c
/* Mailbox write-to-clear bits. */
#define LOCAL_MAILBOX0_CLR0 0x0c0
#define LOCAL_MAILBOX3_CLR0 0x0cc
#define LOCAL_IRQ_CNTPSIRQ 0
#define LOCAL_IRQ_CNTPNSIRQ 1
......@@ -162,7 +167,7 @@ __exception_irq_entry bcm2836_arm_irqchip_handle_irq(struct pt_regs *regs)
u32 stat;
stat = readl_relaxed(intc.base + LOCAL_IRQ_PENDING0 + 4 * cpu);
if (stat & 0x10) {
if (stat & BIT(LOCAL_IRQ_MAILBOX0)) {
#ifdef CONFIG_SMP
void __iomem *mailbox0 = (intc.base +
LOCAL_MAILBOX0_CLR0 + 16 * cpu);
......@@ -172,7 +177,7 @@ __exception_irq_entry bcm2836_arm_irqchip_handle_irq(struct pt_regs *regs)
writel(1 << ipi, mailbox0);
handle_IPI(ipi, regs);
#endif
} else {
} else if (stat) {
u32 hwirq = ffs(stat) - 1;
handle_IRQ(irq_linear_revmap(intc.domain, hwirq), regs);
......@@ -217,6 +222,24 @@ static struct notifier_block bcm2836_arm_irqchip_cpu_notifier = {
.notifier_call = bcm2836_arm_irqchip_cpu_notify,
.priority = 100,
};
int __init bcm2836_smp_boot_secondary(unsigned int cpu,
struct task_struct *idle)
{
unsigned long secondary_startup_phys =
(unsigned long)virt_to_phys((void *)secondary_startup);
dsb();
writel(secondary_startup_phys,
intc.base + LOCAL_MAILBOX3_SET0 + 16 * cpu);
return 0;
}
static const struct smp_operations bcm2836_smp_ops __initconst = {
.smp_boot_secondary = bcm2836_smp_boot_secondary,
};
#endif
static const struct irq_domain_ops bcm2836_arm_irqchip_intc_ops = {
......@@ -234,9 +257,31 @@ bcm2836_arm_irqchip_smp_init(void)
register_cpu_notifier(&bcm2836_arm_irqchip_cpu_notifier);
set_smp_cross_call(bcm2836_arm_irqchip_send_ipi);
smp_set_ops(&bcm2836_smp_ops);
#endif
}
/*
* The LOCAL_IRQ_CNT* timer firings are based off of the external
* oscillator with some scaling. The firmware sets up CNTFRQ to
* report 19.2Mhz, but doesn't set up the scaling registers.
*/
static void bcm2835_init_local_timer_frequency(void)
{
/*
* Set the timer to source from the 19.2Mhz crystal clock (bit
* 8 unset), and only increment by 1 instead of 2 (bit 9
* unset).
*/
writel(0, intc.base + LOCAL_CONTROL);
/*
* Set the timer prescaler to 1:1 (timer freq = input freq *
* 2**31 / prescaler)
*/
writel(0x80000000, intc.base + LOCAL_PRESCALER);
}
static int __init bcm2836_arm_irqchip_l1_intc_of_init(struct device_node *node,
struct device_node *parent)
{
......@@ -246,6 +291,8 @@ static int __init bcm2836_arm_irqchip_l1_intc_of_init(struct device_node *node,
node->full_name);
}
bcm2835_init_local_timer_frequency();
intc.domain = irq_domain_add_linear(node, LAST_IRQ + 1,
&bcm2836_arm_irqchip_intc_ops,
NULL);
......
/*
* Special GIC quirks for the ARM RealView
* Copyright (C) 2015 Linus Walleij
*/
#include <linux/of.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include <linux/bitops.h>
#include <linux/irqchip.h>
#include <linux/irqchip/arm-gic.h>
#define REALVIEW_SYS_LOCK_OFFSET 0x20
#define REALVIEW_PB11MP_SYS_PLD_CTRL1 0x74
#define VERSATILE_LOCK_VAL 0xA05F
#define PLD_INTMODE_MASK BIT(22)|BIT(23)|BIT(24)
#define PLD_INTMODE_LEGACY 0x0
#define PLD_INTMODE_NEW_DCC BIT(22)
#define PLD_INTMODE_NEW_NO_DCC BIT(23)
#define PLD_INTMODE_FIQ_ENABLE BIT(24)
static int __init
realview_gic_of_init(struct device_node *node, struct device_node *parent)
{
static struct regmap *map;
/* The PB11MPCore GIC needs to be configured in the syscon */
map = syscon_regmap_lookup_by_compatible("arm,realview-pb11mp-syscon");
if (!IS_ERR(map)) {
/* new irq mode with no DCC */
regmap_write(map, REALVIEW_SYS_LOCK_OFFSET,
VERSATILE_LOCK_VAL);
regmap_update_bits(map, REALVIEW_PB11MP_SYS_PLD_CTRL1,
PLD_INTMODE_NEW_NO_DCC,
PLD_INTMODE_MASK);
regmap_write(map, REALVIEW_SYS_LOCK_OFFSET, 0x0000);
pr_info("TC11MP GIC: set up interrupt controller to NEW mode, no DCC\n");
} else {
pr_err("TC11MP GIC setup: could not find syscon\n");
return -ENXIO;
}
return gic_of_init(node, parent);
}
IRQCHIP_DECLARE(armtc11mp_gic, "arm,tc11mp-gic", realview_gic_of_init);
......@@ -15,9 +15,11 @@
#define pr_fmt(fmt) "GICv2m: " fmt
#include <linux/acpi.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/msi.h>
#include <linux/of_address.h>
#include <linux/of_pci.h>
#include <linux/slab.h>
......@@ -55,7 +57,7 @@ static DEFINE_SPINLOCK(v2m_lock);
struct v2m_data {
struct list_head entry;
struct device_node *node;
struct fwnode_handle *fwnode;
struct resource res; /* GICv2m resource */
void __iomem *base; /* GICv2m virt address */
u32 spi_start; /* The SPI number that MSIs start */
......@@ -138,6 +140,11 @@ static int gicv2m_irq_gic_domain_alloc(struct irq_domain *domain,
fwspec.param[0] = 0;
fwspec.param[1] = hwirq - 32;
fwspec.param[2] = IRQ_TYPE_EDGE_RISING;
} else if (is_fwnode_irqchip(domain->parent->fwnode)) {
fwspec.fwnode = domain->parent->fwnode;
fwspec.param_count = 2;
fwspec.param[0] = hwirq;
fwspec.param[1] = IRQ_TYPE_EDGE_RISING;
} else {
return -EINVAL;
}
......@@ -254,7 +261,9 @@ static void gicv2m_teardown(void)
list_del(&v2m->entry);
kfree(v2m->bm);
iounmap(v2m->base);
of_node_put(v2m->node);
of_node_put(to_of_node(v2m->fwnode));
if (is_fwnode_irqchip(v2m->fwnode))
irq_domain_free_fwnode(v2m->fwnode);
kfree(v2m);
}
}
......@@ -268,7 +277,7 @@ static int gicv2m_allocate_domains(struct irq_domain *parent)
if (!v2m)
return 0;
inner_domain = irq_domain_create_tree(of_node_to_fwnode(v2m->node),
inner_domain = irq_domain_create_tree(v2m->fwnode,
&gicv2m_domain_ops, v2m);
if (!inner_domain) {
pr_err("Failed to create GICv2m domain\n");
......@@ -277,10 +286,10 @@ static int gicv2m_allocate_domains(struct irq_domain *parent)
inner_domain->bus_token = DOMAIN_BUS_NEXUS;
inner_domain->parent = parent;
pci_domain = pci_msi_create_irq_domain(of_node_to_fwnode(v2m->node),
pci_domain = pci_msi_create_irq_domain(v2m->fwnode,
&gicv2m_msi_domain_info,
inner_domain);
plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(v2m->node),
plat_domain = platform_msi_create_irq_domain(v2m->fwnode,
&gicv2m_pmsi_domain_info,
inner_domain);
if (!pci_domain || !plat_domain) {
......@@ -296,8 +305,9 @@ static int gicv2m_allocate_domains(struct irq_domain *parent)
return 0;
}
static int __init gicv2m_init_one(struct device_node *node,
struct irq_domain *parent)
static int __init gicv2m_init_one(struct fwnode_handle *fwnode,
u32 spi_start, u32 nr_spis,
struct resource *res)
{
int ret;
struct v2m_data *v2m;
......@@ -309,13 +319,9 @@ static int __init gicv2m_init_one(struct device_node *node,
}
INIT_LIST_HEAD(&v2m->entry);
v2m->node = node;
v2m->fwnode = fwnode;
ret = of_address_to_resource(node, 0, &v2m->res);
if (ret) {
pr_err("Failed to allocate v2m resource.\n");
goto err_free_v2m;
}
memcpy(&v2m->res, res, sizeof(struct resource));
v2m->base = ioremap(v2m->res.start, resource_size(&v2m->res));
if (!v2m->base) {
......@@ -324,10 +330,9 @@ static int __init gicv2m_init_one(struct device_node *node,
goto err_free_v2m;
}
if (!of_property_read_u32(node, "arm,msi-base-spi", &v2m->spi_start) &&
!of_property_read_u32(node, "arm,msi-num-spis", &v2m->nr_spis)) {
pr_info("Overriding V2M MSI_TYPER (base:%u, num:%u)\n",
v2m->spi_start, v2m->nr_spis);
if (spi_start && nr_spis) {
v2m->spi_start = spi_start;
v2m->nr_spis = nr_spis;
} else {
u32 typer = readl_relaxed(v2m->base + V2M_MSI_TYPER);
......@@ -359,10 +364,9 @@ static int __init gicv2m_init_one(struct device_node *node,
}
list_add_tail(&v2m->entry, &v2m_nodes);
pr_info("Node %s: range[%#lx:%#lx], SPI[%d:%d]\n", node->name,
(unsigned long)v2m->res.start, (unsigned long)v2m->res.end,
v2m->spi_start, (v2m->spi_start + v2m->nr_spis));
pr_info("range%pR, SPI[%d:%d]\n", res,
v2m->spi_start, (v2m->spi_start + v2m->nr_spis - 1));
return 0;
err_iounmap:
......@@ -377,19 +381,36 @@ static struct of_device_id gicv2m_device_id[] = {
{},
};
int __init gicv2m_of_init(struct device_node *node, struct irq_domain *parent)
static int __init gicv2m_of_init(struct fwnode_handle *parent_handle,
struct irq_domain *parent)
{
int ret = 0;
struct device_node *node = to_of_node(parent_handle);
struct device_node *child;
for (child = of_find_matching_node(node, gicv2m_device_id); child;
child = of_find_matching_node(child, gicv2m_device_id)) {
u32 spi_start = 0, nr_spis = 0;
struct resource res;
if (!of_find_property(child, "msi-controller", NULL))
continue;
ret = gicv2m_init_one(child, parent);
ret = of_address_to_resource(child, 0, &res);
if (ret) {
pr_err("Failed to allocate v2m resource.\n");
break;
}
if (!of_property_read_u32(child, "arm,msi-base-spi",
&spi_start) &&
!of_property_read_u32(child, "arm,msi-num-spis", &nr_spis))
pr_info("DT overriding V2M MSI_TYPER (base:%u, num:%u)\n",
spi_start, nr_spis);
ret = gicv2m_init_one(&child->fwnode, spi_start, nr_spis, &res);
if (ret) {
of_node_put(node);
of_node_put(child);
break;
}
}
......@@ -400,3 +421,101 @@ int __init gicv2m_of_init(struct device_node *node, struct irq_domain *parent)
gicv2m_teardown();
return ret;
}
#ifdef CONFIG_ACPI
static int acpi_num_msi;
static struct fwnode_handle *gicv2m_get_fwnode(struct device *dev)
{
struct v2m_data *data;
if (WARN_ON(acpi_num_msi <= 0))
return NULL;
/* We only return the fwnode of the first MSI frame. */
data = list_first_entry_or_null(&v2m_nodes, struct v2m_data, entry);
if (!data)
return NULL;
return data->fwnode;
}
static int __init
acpi_parse_madt_msi(struct acpi_subtable_header *header,
const unsigned long end)
{
int ret;
struct resource res;
u32 spi_start = 0, nr_spis = 0;
struct acpi_madt_generic_msi_frame *m;
struct fwnode_handle *fwnode;
m = (struct acpi_madt_generic_msi_frame *)header;
if (BAD_MADT_ENTRY(m, end))
return -EINVAL;
res.start = m->base_address;
res.end = m->base_address + SZ_4K - 1;
res.flags = IORESOURCE_MEM;
if (m->flags & ACPI_MADT_OVERRIDE_SPI_VALUES) {
spi_start = m->spi_base;
nr_spis = m->spi_count;
pr_info("ACPI overriding V2M MSI_TYPER (base:%u, num:%u)\n",
spi_start, nr_spis);
}
fwnode = irq_domain_alloc_fwnode((void *)m->base_address);
if (!fwnode) {
pr_err("Unable to allocate GICv2m domain token\n");
return -EINVAL;
}
ret = gicv2m_init_one(fwnode, spi_start, nr_spis, &res);
if (ret)
irq_domain_free_fwnode(fwnode);
return ret;
}
static int __init gicv2m_acpi_init(struct irq_domain *parent)
{
int ret;
if (acpi_num_msi > 0)
return 0;
acpi_num_msi = acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_MSI_FRAME,
acpi_parse_madt_msi, 0);
if (acpi_num_msi <= 0)
goto err_out;
ret = gicv2m_allocate_domains(parent);
if (ret)
goto err_out;
pci_msi_register_fwnode_provider(&gicv2m_get_fwnode);
return 0;
err_out:
gicv2m_teardown();
return -EINVAL;
}
#else /* CONFIG_ACPI */
static int __init gicv2m_acpi_init(struct irq_domain *parent)
{
return -EINVAL;
}
#endif /* CONFIG_ACPI */
int __init gicv2m_init(struct fwnode_handle *parent_handle,
struct irq_domain *parent)
{
if (is_of_node(parent_handle))
return gicv2m_of_init(parent_handle, parent);
return gicv2m_acpi_init(parent);
}
......@@ -69,6 +69,7 @@ union gic_base {
};
struct gic_chip_data {
struct irq_chip chip;
union gic_base dist_base;
union gic_base cpu_base;
#ifdef CONFIG_CPU_PM
......@@ -99,11 +100,7 @@ static u8 gic_cpu_map[NR_GIC_CPU_IF] __read_mostly;
static struct static_key supports_deactivate = STATIC_KEY_INIT_TRUE;
#ifndef MAX_GIC_NR
#define MAX_GIC_NR 1
#endif
static struct gic_chip_data gic_data[MAX_GIC_NR] __read_mostly;
static struct gic_chip_data gic_data[CONFIG_ARM_GIC_MAX_NR] __read_mostly;
#ifdef CONFIG_GIC_NON_BANKED
static void __iomem *gic_get_percpu_base(union gic_base *base)
......@@ -336,7 +333,7 @@ static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (likely(irqnr > 15 && irqnr < 1021)) {
if (likely(irqnr > 15 && irqnr < 1020)) {
if (static_key_true(&supports_deactivate))
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
handle_domain_irq(gic->domain, irqnr, regs);
......@@ -383,7 +380,6 @@ static void gic_handle_cascade_irq(struct irq_desc *desc)
}
static struct irq_chip gic_chip = {
.name = "GIC",
.irq_mask = gic_mask_irq,
.irq_unmask = gic_unmask_irq,
.irq_eoi = gic_eoi_irq,
......@@ -417,8 +413,7 @@ static struct irq_chip gic_eoimode1_chip = {
void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq)
{
if (gic_nr >= MAX_GIC_NR)
BUG();
BUG_ON(gic_nr >= CONFIG_ARM_GIC_MAX_NR);
irq_set_chained_handler_and_data(irq, gic_handle_cascade_irq,
&gic_data[gic_nr]);
}
......@@ -524,7 +519,7 @@ int gic_cpu_if_down(unsigned int gic_nr)
void __iomem *cpu_base;
u32 val = 0;
if (gic_nr >= MAX_GIC_NR)
if (gic_nr >= CONFIG_ARM_GIC_MAX_NR)
return -EINVAL;
cpu_base = gic_data_cpu_base(&gic_data[gic_nr]);
......@@ -548,8 +543,7 @@ static void gic_dist_save(unsigned int gic_nr)
void __iomem *dist_base;
int i;
if (gic_nr >= MAX_GIC_NR)
BUG();
BUG_ON(gic_nr >= CONFIG_ARM_GIC_MAX_NR);
gic_irqs = gic_data[gic_nr].gic_irqs;
dist_base = gic_data_dist_base(&gic_data[gic_nr]);
......@@ -587,8 +581,7 @@ static void gic_dist_restore(unsigned int gic_nr)
unsigned int i;
void __iomem *dist_base;
if (gic_nr >= MAX_GIC_NR)
BUG();
BUG_ON(gic_nr >= CONFIG_ARM_GIC_MAX_NR);
gic_irqs = gic_data[gic_nr].gic_irqs;
dist_base = gic_data_dist_base(&gic_data[gic_nr]);
......@@ -634,8 +627,7 @@ static void gic_cpu_save(unsigned int gic_nr)
void __iomem *dist_base;
void __iomem *cpu_base;
if (gic_nr >= MAX_GIC_NR)
BUG();
BUG_ON(gic_nr >= CONFIG_ARM_GIC_MAX_NR);
dist_base = gic_data_dist_base(&gic_data[gic_nr]);
cpu_base = gic_data_cpu_base(&gic_data[gic_nr]);
......@@ -664,8 +656,7 @@ static void gic_cpu_restore(unsigned int gic_nr)
void __iomem *dist_base;
void __iomem *cpu_base;
if (gic_nr >= MAX_GIC_NR)
BUG();
BUG_ON(gic_nr >= CONFIG_ARM_GIC_MAX_NR);
dist_base = gic_data_dist_base(&gic_data[gic_nr]);
cpu_base = gic_data_cpu_base(&gic_data[gic_nr]);
......@@ -703,7 +694,7 @@ static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v)
{
int i;
for (i = 0; i < MAX_GIC_NR; i++) {
for (i = 0; i < CONFIG_ARM_GIC_MAX_NR; i++) {
#ifdef CONFIG_GIC_NON_BANKED
/* Skip over unused GICs */
if (!gic_data[i].get_base)
......@@ -835,8 +826,7 @@ void gic_migrate_target(unsigned int new_cpu_id)
int i, ror_val, cpu = smp_processor_id();
u32 val, cur_target_mask, active_mask;
if (gic_nr >= MAX_GIC_NR)
BUG();
BUG_ON(gic_nr >= CONFIG_ARM_GIC_MAX_NR);
dist_base = gic_data_dist_base(&gic_data[gic_nr]);
if (!dist_base)
......@@ -925,20 +915,15 @@ void __init gic_init_physaddr(struct device_node *node)
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw)
{
struct irq_chip *chip = &gic_chip;
if (static_key_true(&supports_deactivate)) {
if (d->host_data == (void *)&gic_data[0])
chip = &gic_eoimode1_chip;
}
struct gic_chip_data *gic = d->host_data;
if (hw < 32) {
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hw, chip, d->host_data,
irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);
irq_set_status_flags(irq, IRQ_NOAUTOEN);
} else {
irq_domain_set_info(d, irq, hw, chip, d->host_data,
irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);
irq_set_probe(irq);
}
......@@ -972,7 +957,7 @@ static int gic_irq_domain_translate(struct irq_domain *d,
return 0;
}
if (fwspec->fwnode->type == FWNODE_IRQCHIP) {
if (is_fwnode_irqchip(fwspec->fwnode)) {
if(fwspec->param_count != 2)
return -EINVAL;
......@@ -1040,11 +1025,20 @@ static void __init __gic_init_bases(unsigned int gic_nr, int irq_start,
struct gic_chip_data *gic;
int gic_irqs, irq_base, i;
BUG_ON(gic_nr >= MAX_GIC_NR);
BUG_ON(gic_nr >= CONFIG_ARM_GIC_MAX_NR);
gic_check_cpu_features();
gic = &gic_data[gic_nr];
/* Initialize irq_chip */
if (static_key_true(&supports_deactivate) && gic_nr == 0) {
gic->chip = gic_eoimode1_chip;
} else {
gic->chip = gic_chip;
gic->chip.name = kasprintf(GFP_KERNEL, "GIC-%d", gic_nr);
}
#ifdef CONFIG_GIC_NON_BANKED
if (percpu_offset) { /* Frankein-GIC without banked registers... */
unsigned int cpu;
......@@ -1196,7 +1190,7 @@ static bool gic_check_eoimode(struct device_node *node, void __iomem **base)
return true;
}
static int __init
int __init
gic_of_init(struct device_node *node, struct device_node *parent)
{
void __iomem *cpu_base;
......@@ -1234,7 +1228,7 @@ gic_of_init(struct device_node *node, struct device_node *parent)
}
if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
gicv2m_of_init(node, gic_data[gic_cnt].domain);
gicv2m_init(&node->fwnode, gic_data[gic_cnt].domain);
gic_cnt++;
return 0;
......@@ -1359,6 +1353,10 @@ static int __init gic_v2_acpi_init(struct acpi_subtable_header *header,
__gic_init_bases(0, -1, dist_base, cpu_base, 0, domain_handle);
acpi_set_irq_model(ACPI_IRQ_MODEL_GIC, domain_handle);
if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
gicv2m_init(NULL, gic_data[0].domain);
return 0;
}
IRQCHIP_ACPI_DECLARE(gic_v2, ACPI_MADT_TYPE_GENERIC_DISTRIBUTOR,
......
/*
* Copyright (C) 2015 Hisilicon Limited, All Rights Reserved.
* Author: Jun Ma <majun258@huawei.com>
* Author: Yun Wu <wuyun.wu@huawei.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/interrupt.h>
#include <linux/irqchip.h>
#include <linux/module.h>
#include <linux/msi.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
/* Interrupt numbers per mbigen node supported */
#define IRQS_PER_MBIGEN_NODE 128
/* 64 irqs (Pin0-pin63) are reserved for each mbigen chip */
#define RESERVED_IRQ_PER_MBIGEN_CHIP 64
/* The maximum IRQ pin number of mbigen chip(start from 0) */
#define MAXIMUM_IRQ_PIN_NUM 1407
/**
* In mbigen vector register
* bit[21:12]: event id value
* bit[11:0]: device id
*/
#define IRQ_EVENT_ID_SHIFT 12
#define IRQ_EVENT_ID_MASK 0x3ff
/* register range of each mbigen node */
#define MBIGEN_NODE_OFFSET 0x1000
/* offset of vector register in mbigen node */
#define REG_MBIGEN_VEC_OFFSET 0x200
/**
* offset of clear register in mbigen node
* This register is used to clear the status
* of interrupt
*/
#define REG_MBIGEN_CLEAR_OFFSET 0xa000
/**
* offset of interrupt type register
* This register is used to configure interrupt
* trigger type
*/
#define REG_MBIGEN_TYPE_OFFSET 0x0
/**
* struct mbigen_device - holds the information of mbigen device.
*
* @pdev: pointer to the platform device structure of mbigen chip.
* @base: mapped address of this mbigen chip.
*/
struct mbigen_device {
struct platform_device *pdev;
void __iomem *base;
};
static inline unsigned int get_mbigen_vec_reg(irq_hw_number_t hwirq)
{
unsigned int nid, pin;
hwirq -= RESERVED_IRQ_PER_MBIGEN_CHIP;
nid = hwirq / IRQS_PER_MBIGEN_NODE + 1;
pin = hwirq % IRQS_PER_MBIGEN_NODE;
return pin * 4 + nid * MBIGEN_NODE_OFFSET
+ REG_MBIGEN_VEC_OFFSET;
}
static inline void get_mbigen_type_reg(irq_hw_number_t hwirq,
u32 *mask, u32 *addr)
{
unsigned int nid, irq_ofst, ofst;
hwirq -= RESERVED_IRQ_PER_MBIGEN_CHIP;
nid = hwirq / IRQS_PER_MBIGEN_NODE + 1;
irq_ofst = hwirq % IRQS_PER_MBIGEN_NODE;
*mask = 1 << (irq_ofst % 32);
ofst = irq_ofst / 32 * 4;
*addr = ofst + nid * MBIGEN_NODE_OFFSET
+ REG_MBIGEN_TYPE_OFFSET;
}
static inline void get_mbigen_clear_reg(irq_hw_number_t hwirq,
u32 *mask, u32 *addr)
{
unsigned int ofst;
hwirq -= RESERVED_IRQ_PER_MBIGEN_CHIP;
ofst = hwirq / 32 * 4;
*mask = 1 << (hwirq % 32);
*addr = ofst + REG_MBIGEN_CLEAR_OFFSET;
}
static void mbigen_eoi_irq(struct irq_data *data)
{
void __iomem *base = data->chip_data;
u32 mask, addr;
get_mbigen_clear_reg(data->hwirq, &mask, &addr);
writel_relaxed(mask, base + addr);
irq_chip_eoi_parent(data);
}
static int mbigen_set_type(struct irq_data *data, unsigned int type)
{
void __iomem *base = data->chip_data;
u32 mask, addr, val;
if (type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_EDGE_RISING)
return -EINVAL;
get_mbigen_type_reg(data->hwirq, &mask, &addr);
val = readl_relaxed(base + addr);
if (type == IRQ_TYPE_LEVEL_HIGH)
val |= mask;
else
val &= ~mask;
writel_relaxed(val, base + addr);
return 0;
}
static struct irq_chip mbigen_irq_chip = {
.name = "mbigen-v2",
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_eoi = mbigen_eoi_irq,
.irq_set_type = mbigen_set_type,
.irq_set_affinity = irq_chip_set_affinity_parent,
};
static void mbigen_write_msg(struct msi_desc *desc, struct msi_msg *msg)
{
struct irq_data *d = irq_get_irq_data(desc->irq);
void __iomem *base = d->chip_data;
u32 val;
base += get_mbigen_vec_reg(d->hwirq);
val = readl_relaxed(base);
val &= ~(IRQ_EVENT_ID_MASK << IRQ_EVENT_ID_SHIFT);
val |= (msg->data << IRQ_EVENT_ID_SHIFT);
/* The address of doorbell is encoded in mbigen register by default
* So,we don't need to program the doorbell address at here
*/
writel_relaxed(val, base);
}
static int mbigen_domain_translate(struct irq_domain *d,
struct irq_fwspec *fwspec,
unsigned long *hwirq,
unsigned int *type)
{
if (is_of_node(fwspec->fwnode)) {
if (fwspec->param_count != 2)
return -EINVAL;
if ((fwspec->param[0] > MAXIMUM_IRQ_PIN_NUM) ||
(fwspec->param[0] < RESERVED_IRQ_PER_MBIGEN_CHIP))
return -EINVAL;
else
*hwirq = fwspec->param[0];
/* If there is no valid irq type, just use the default type */
if ((fwspec->param[1] == IRQ_TYPE_EDGE_RISING) ||
(fwspec->param[1] == IRQ_TYPE_LEVEL_HIGH))
*type = fwspec->param[1];
else
return -EINVAL;
return 0;
}
return -EINVAL;
}
static int mbigen_irq_domain_alloc(struct irq_domain *domain,
unsigned int virq,
unsigned int nr_irqs,
void *args)
{
struct irq_fwspec *fwspec = args;
irq_hw_number_t hwirq;
unsigned int type;
struct mbigen_device *mgn_chip;
int i, err;
err = mbigen_domain_translate(domain, fwspec, &hwirq, &type);
if (err)
return err;
err = platform_msi_domain_alloc(domain, virq, nr_irqs);
if (err)
return err;
mgn_chip = platform_msi_get_host_data(domain);
for (i = 0; i < nr_irqs; i++)
irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
&mbigen_irq_chip, mgn_chip->base);
return 0;
}
static struct irq_domain_ops mbigen_domain_ops = {
.translate = mbigen_domain_translate,
.alloc = mbigen_irq_domain_alloc,
.free = irq_domain_free_irqs_common,
};
static int mbigen_device_probe(struct platform_device *pdev)
{
struct mbigen_device *mgn_chip;
struct resource *res;
struct irq_domain *domain;
u32 num_pins;
mgn_chip = devm_kzalloc(&pdev->dev, sizeof(*mgn_chip), GFP_KERNEL);
if (!mgn_chip)
return -ENOMEM;
mgn_chip->pdev = pdev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mgn_chip->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(mgn_chip->base))
return PTR_ERR(mgn_chip->base);
if (of_property_read_u32(pdev->dev.of_node, "num-pins", &num_pins) < 0) {
dev_err(&pdev->dev, "No num-pins property\n");
return -EINVAL;
}
domain = platform_msi_create_device_domain(&pdev->dev, num_pins,
mbigen_write_msg,
&mbigen_domain_ops,
mgn_chip);
if (!domain)
return -ENOMEM;
platform_set_drvdata(pdev, mgn_chip);
dev_info(&pdev->dev, "Allocated %d MSIs\n", num_pins);
return 0;
}
static const struct of_device_id mbigen_of_match[] = {
{ .compatible = "hisilicon,mbigen-v2" },
{ /* END */ }
};
MODULE_DEVICE_TABLE(of, mbigen_of_match);
static struct platform_driver mbigen_platform_driver = {
.driver = {
.name = "Hisilicon MBIGEN-V2",
.owner = THIS_MODULE,
.of_match_table = mbigen_of_match,
},
.probe = mbigen_device_probe,
};
module_platform_driver(mbigen_platform_driver);
MODULE_AUTHOR("Jun Ma <majun258@huawei.com>");
MODULE_AUTHOR("Yun Wu <wuyun.wu@huawei.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Hisilicon MBI Generator driver");
......@@ -47,6 +47,7 @@
#define INTC_ILR0 0x0100
#define ACTIVEIRQ_MASK 0x7f /* omap2/3 active interrupt bits */
#define SPURIOUSIRQ_MASK (0x1ffffff << 7)
#define INTCPS_NR_ILR_REGS 128
#define INTCPS_NR_MIR_REGS 4
......@@ -207,7 +208,6 @@ static int __init omap_alloc_gc_of(struct irq_domain *d, void __iomem *base)
ct = gc->chip_types;
ct->type = IRQ_TYPE_LEVEL_MASK;
ct->handler = handle_level_irq;
ct->chip.irq_ack = omap_mask_ack_irq;
ct->chip.irq_mask = irq_gc_mask_disable_reg;
......@@ -330,11 +330,35 @@ static int __init omap_init_irq(u32 base, struct device_node *node)
static asmlinkage void __exception_irq_entry
omap_intc_handle_irq(struct pt_regs *regs)
{
extern unsigned long irq_err_count;
u32 irqnr;
irqnr = intc_readl(INTC_SIR);
/*
* A spurious IRQ can result if interrupt that triggered the
* sorting is no longer active during the sorting (10 INTC
* functional clock cycles after interrupt assertion). Or a
* change in interrupt mask affected the result during sorting
* time. There is no special handling required except ignoring
* the SIR register value just read and retrying.
* See section 6.2.5 of AM335x TRM Literature Number: SPRUH73K
*
* Many a times, a spurious interrupt situation has been fixed
* by adding a flush for the posted write acking the IRQ in
* the device driver. Typically, this is going be the device
* driver whose interrupt was handled just before the spurious
* IRQ occurred. Pay attention to those device drivers if you
* run into hitting the spurious IRQ condition below.
*/
if (unlikely((irqnr & SPURIOUSIRQ_MASK) == SPURIOUSIRQ_MASK)) {
pr_err_once("%s: spurious irq!\n", __func__);
irq_err_count++;
omap_ack_irq(NULL);
return;
}
irqnr &= ACTIVEIRQ_MASK;
WARN_ONCE(!irqnr, "Spurious IRQ ?\n");
handle_domain_irq(domain, irqnr, regs);
}
......
......@@ -31,7 +31,6 @@
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_data/irq-renesas-intc-irqpin.h>
#include <linux/pm_runtime.h>
#define INTC_IRQPIN_MAX 8 /* maximum 8 interrupts per driver instance */
......@@ -75,18 +74,20 @@ struct intc_irqpin_irq {
struct intc_irqpin_priv {
struct intc_irqpin_iomem iomem[INTC_IRQPIN_REG_NR];
struct intc_irqpin_irq irq[INTC_IRQPIN_MAX];
struct renesas_intc_irqpin_config config;
unsigned int number_of_irqs;
unsigned int sense_bitfield_width;
struct platform_device *pdev;
struct irq_chip irq_chip;
struct irq_domain *irq_domain;
struct clk *clk;
bool shared_irqs;
unsigned shared_irqs:1;
unsigned needs_clk:1;
u8 shared_irq_mask;
};
struct intc_irqpin_irlm_config {
struct intc_irqpin_config {
unsigned int irlm_bit;
unsigned needs_irlm:1;
unsigned needs_clk:1;
};
static unsigned long intc_irqpin_read32(void __iomem *iomem)
......@@ -171,7 +172,7 @@ static void intc_irqpin_mask_unmask_prio(struct intc_irqpin_priv *p,
static int intc_irqpin_set_sense(struct intc_irqpin_priv *p, int irq, int value)
{
/* The SENSE register is assumed to be 32-bit. */
int bitfield_width = p->config.sense_bitfield_width;
int bitfield_width = p->sense_bitfield_width;
int shift = 32 - (irq + 1) * bitfield_width;
dev_dbg(&p->pdev->dev, "sense irq = %d, mode = %d\n", irq, value);
......@@ -361,8 +362,15 @@ static const struct irq_domain_ops intc_irqpin_irq_domain_ops = {
.xlate = irq_domain_xlate_twocell,
};
static const struct intc_irqpin_irlm_config intc_irqpin_irlm_r8a777x = {
static const struct intc_irqpin_config intc_irqpin_irlm_r8a777x = {
.irlm_bit = 23, /* ICR0.IRLM0 */
.needs_irlm = 1,
.needs_clk = 0,
};
static const struct intc_irqpin_config intc_irqpin_rmobile = {
.needs_irlm = 0,
.needs_clk = 1,
};
static const struct of_device_id intc_irqpin_dt_ids[] = {
......@@ -371,14 +379,18 @@ static const struct of_device_id intc_irqpin_dt_ids[] = {
.data = &intc_irqpin_irlm_r8a777x },
{ .compatible = "renesas,intc-irqpin-r8a7779",
.data = &intc_irqpin_irlm_r8a777x },
{ .compatible = "renesas,intc-irqpin-r8a7740",
.data = &intc_irqpin_rmobile },
{ .compatible = "renesas,intc-irqpin-sh73a0",
.data = &intc_irqpin_rmobile },
{},
};
MODULE_DEVICE_TABLE(of, intc_irqpin_dt_ids);
static int intc_irqpin_probe(struct platform_device *pdev)
{
const struct intc_irqpin_config *config = NULL;
struct device *dev = &pdev->dev;
struct renesas_intc_irqpin_config *pdata = dev->platform_data;
const struct of_device_id *of_id;
struct intc_irqpin_priv *p;
struct intc_irqpin_iomem *i;
......@@ -388,6 +400,8 @@ static int intc_irqpin_probe(struct platform_device *pdev)
void (*enable_fn)(struct irq_data *d);
void (*disable_fn)(struct irq_data *d);
const char *name = dev_name(dev);
bool control_parent;
unsigned int nirqs;
int ref_irq;
int ret;
int k;
......@@ -399,23 +413,28 @@ static int intc_irqpin_probe(struct platform_device *pdev)
}
/* deal with driver instance configuration */
if (pdata) {
memcpy(&p->config, pdata, sizeof(*pdata));
} else {
of_property_read_u32(dev->of_node, "sense-bitfield-width",
&p->config.sense_bitfield_width);
p->config.control_parent = of_property_read_bool(dev->of_node,
"control-parent");
}
if (!p->config.sense_bitfield_width)
p->config.sense_bitfield_width = 4; /* default to 4 bits */
of_property_read_u32(dev->of_node, "sense-bitfield-width",
&p->sense_bitfield_width);
control_parent = of_property_read_bool(dev->of_node, "control-parent");
if (!p->sense_bitfield_width)
p->sense_bitfield_width = 4; /* default to 4 bits */
p->pdev = pdev;
platform_set_drvdata(pdev, p);
of_id = of_match_device(intc_irqpin_dt_ids, dev);
if (of_id && of_id->data) {
config = of_id->data;
p->needs_clk = config->needs_clk;
}
p->clk = devm_clk_get(dev, NULL);
if (IS_ERR(p->clk)) {
dev_warn(dev, "unable to get clock\n");
if (p->needs_clk) {
dev_err(dev, "unable to get clock\n");
ret = PTR_ERR(p->clk);
goto err0;
}
p->clk = NULL;
}
......@@ -443,8 +462,8 @@ static int intc_irqpin_probe(struct platform_device *pdev)
p->irq[k].requested_irq = irq->start;
}
p->number_of_irqs = k;
if (p->number_of_irqs < 1) {
nirqs = k;
if (nirqs < 1) {
dev_err(dev, "not enough IRQ resources\n");
ret = -EINVAL;
goto err0;
......@@ -485,20 +504,16 @@ static int intc_irqpin_probe(struct platform_device *pdev)
}
/* configure "individual IRQ mode" where needed */
of_id = of_match_device(intc_irqpin_dt_ids, dev);
if (of_id && of_id->data) {
const struct intc_irqpin_irlm_config *irlm_config = of_id->data;
if (config && config->needs_irlm) {
if (io[INTC_IRQPIN_REG_IRLM])
intc_irqpin_read_modify_write(p, INTC_IRQPIN_REG_IRLM,
irlm_config->irlm_bit,
1, 1);
config->irlm_bit, 1, 1);
else
dev_warn(dev, "unable to select IRLM mode\n");
}
/* mask all interrupts using priority */
for (k = 0; k < p->number_of_irqs; k++)
for (k = 0; k < nirqs; k++)
intc_irqpin_mask_unmask_prio(p, k, 1);
/* clear all pending interrupts */
......@@ -506,16 +521,16 @@ static int intc_irqpin_probe(struct platform_device *pdev)
/* scan for shared interrupt lines */
ref_irq = p->irq[0].requested_irq;
p->shared_irqs = true;
for (k = 1; k < p->number_of_irqs; k++) {
p->shared_irqs = 1;
for (k = 1; k < nirqs; k++) {
if (ref_irq != p->irq[k].requested_irq) {
p->shared_irqs = false;
p->shared_irqs = 0;
break;
}
}
/* use more severe masking method if requested */
if (p->config.control_parent) {
if (control_parent) {
enable_fn = intc_irqpin_irq_enable_force;
disable_fn = intc_irqpin_irq_disable_force;
} else if (!p->shared_irqs) {
......@@ -534,9 +549,7 @@ static int intc_irqpin_probe(struct platform_device *pdev)
irq_chip->irq_set_wake = intc_irqpin_irq_set_wake;
irq_chip->flags = IRQCHIP_MASK_ON_SUSPEND;
p->irq_domain = irq_domain_add_simple(dev->of_node,
p->number_of_irqs,
p->config.irq_base,
p->irq_domain = irq_domain_add_simple(dev->of_node, nirqs, 0,
&intc_irqpin_irq_domain_ops, p);
if (!p->irq_domain) {
ret = -ENXIO;
......@@ -555,7 +568,7 @@ static int intc_irqpin_probe(struct platform_device *pdev)
}
} else {
/* request interrupts one by one */
for (k = 0; k < p->number_of_irqs; k++) {
for (k = 0; k < nirqs; k++) {
if (devm_request_irq(dev, p->irq[k].requested_irq,
intc_irqpin_irq_handler, 0, name,
&p->irq[k])) {
......@@ -567,17 +580,10 @@ static int intc_irqpin_probe(struct platform_device *pdev)
}
/* unmask all interrupts on prio level */
for (k = 0; k < p->number_of_irqs; k++)
for (k = 0; k < nirqs; k++)
intc_irqpin_mask_unmask_prio(p, k, 0);
dev_info(dev, "driving %d irqs\n", p->number_of_irqs);
/* warn in case of mismatch if irq base is specified */
if (p->config.irq_base) {
if (p->config.irq_base != p->irq[0].domain_irq)
dev_warn(dev, "irq base mismatch (%d/%d)\n",
p->config.irq_base, p->irq[0].domain_irq);
}
dev_info(dev, "driving %d irqs\n", nirqs);
return 0;
......
......@@ -50,6 +50,12 @@ static struct sunxi_sc_nmi_reg_offs sun6i_reg_offs = {
.enable = 0x34,
};
static struct sunxi_sc_nmi_reg_offs sun9i_reg_offs = {
.ctrl = 0x00,
.pend = 0x08,
.enable = 0x04,
};
static inline void sunxi_sc_nmi_write(struct irq_chip_generic *gc, u32 off,
u32 val)
{
......@@ -207,3 +213,10 @@ static int __init sun7i_sc_nmi_irq_init(struct device_node *node,
return sunxi_sc_nmi_irq_init(node, &sun7i_reg_offs);
}
IRQCHIP_DECLARE(sun7i_sc_nmi, "allwinner,sun7i-a20-sc-nmi", sun7i_sc_nmi_irq_init);
static int __init sun9i_nmi_irq_init(struct device_node *node,
struct device_node *parent)
{
return sunxi_sc_nmi_irq_init(node, &sun9i_reg_offs);
}
IRQCHIP_DECLARE(sun9i_nmi, "allwinner,sun9i-a80-nmi", sun9i_nmi_irq_init);
/*
* Multiplexed-IRQs driver for TS-4800's FPGA
*
* Copyright (c) 2015 - Savoir-faire Linux
*
* 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/io.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#define IRQ_MASK 0x4
#define IRQ_STATUS 0x8
struct ts4800_irq_data {
void __iomem *base;
struct irq_domain *domain;
struct irq_chip irq_chip;
};
static void ts4800_irq_mask(struct irq_data *d)
{
struct ts4800_irq_data *data = irq_data_get_irq_chip_data(d);
u16 reg = readw(data->base + IRQ_MASK);
u16 mask = 1 << d->hwirq;
writew(reg | mask, data->base + IRQ_MASK);
}
static void ts4800_irq_unmask(struct irq_data *d)
{
struct ts4800_irq_data *data = irq_data_get_irq_chip_data(d);
u16 reg = readw(data->base + IRQ_MASK);
u16 mask = 1 << d->hwirq;
writew(reg & ~mask, data->base + IRQ_MASK);
}
static int ts4800_irqdomain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hwirq)
{
struct ts4800_irq_data *data = d->host_data;
irq_set_chip_and_handler(irq, &data->irq_chip, handle_simple_irq);
irq_set_chip_data(irq, data);
irq_set_noprobe(irq);
return 0;
}
struct irq_domain_ops ts4800_ic_ops = {
.map = ts4800_irqdomain_map,
.xlate = irq_domain_xlate_onecell,
};
static void ts4800_ic_chained_handle_irq(struct irq_desc *desc)
{
struct ts4800_irq_data *data = irq_desc_get_handler_data(desc);
struct irq_chip *chip = irq_desc_get_chip(desc);
u16 status = readw(data->base + IRQ_STATUS);
chained_irq_enter(chip, desc);
if (unlikely(status == 0)) {
handle_bad_irq(desc);
goto out;
}
do {
unsigned int bit = __ffs(status);
int irq = irq_find_mapping(data->domain, bit);
status &= ~(1 << bit);
generic_handle_irq(irq);
} while (status);
out:
chained_irq_exit(chip, desc);
}
static int ts4800_ic_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct ts4800_irq_data *data;
struct irq_chip *irq_chip;
struct resource *res;
int parent_irq;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
data->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(data->base))
return PTR_ERR(data->base);
writew(0xFFFF, data->base + IRQ_MASK);
parent_irq = irq_of_parse_and_map(node, 0);
if (!parent_irq) {
dev_err(&pdev->dev, "failed to get parent IRQ\n");
return -EINVAL;
}
irq_chip = &data->irq_chip;
irq_chip->name = dev_name(&pdev->dev);
irq_chip->irq_mask = ts4800_irq_mask;
irq_chip->irq_unmask = ts4800_irq_unmask;
data->domain = irq_domain_add_linear(node, 8, &ts4800_ic_ops, data);
if (!data->domain) {
dev_err(&pdev->dev, "cannot add IRQ domain\n");
return -ENOMEM;
}
irq_set_chained_handler_and_data(parent_irq,
ts4800_ic_chained_handle_irq, data);
platform_set_drvdata(pdev, data);
return 0;
}
static int ts4800_ic_remove(struct platform_device *pdev)
{
struct ts4800_irq_data *data = platform_get_drvdata(pdev);
irq_domain_remove(data->domain);
return 0;
}
static const struct of_device_id ts4800_ic_of_match[] = {
{ .compatible = "technologic,ts4800-irqc", },
{},
};
MODULE_DEVICE_TABLE(of, ts4800_ic_of_match);
static struct platform_driver ts4800_ic_driver = {
.probe = ts4800_ic_probe,
.remove = ts4800_ic_remove,
.driver = {
.name = "ts4800-irqc",
.of_match_table = ts4800_ic_of_match,
},
};
module_platform_driver(ts4800_ic_driver);
MODULE_AUTHOR("Damien Riegel <damien.riegel@savoirfairelinux.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:ts4800_irqc");
......@@ -43,8 +43,7 @@ static void __iomem *zevio_irq_io;
static void zevio_irq_ack(struct irq_data *irqd)
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(irqd);
struct irq_chip_regs *regs =
&container_of(irqd->chip, struct irq_chip_type, chip)->regs;
struct irq_chip_regs *regs = &irq_data_get_chip_type(irqd)->regs;
readl(gc->reg_base + regs->ack);
}
......
......@@ -257,6 +257,7 @@ void pci_msi_mask_irq(struct irq_data *data)
{
msi_set_mask_bit(data, 1);
}
EXPORT_SYMBOL_GPL(pci_msi_mask_irq);
/**
* pci_msi_unmask_irq - Generic irq chip callback to unmask PCI/MSI interrupts
......@@ -266,6 +267,7 @@ void pci_msi_unmask_irq(struct irq_data *data)
{
msi_set_mask_bit(data, 0);
}
EXPORT_SYMBOL_GPL(pci_msi_unmask_irq);
void default_restore_msi_irqs(struct pci_dev *dev)
{
......@@ -1126,6 +1128,7 @@ struct pci_dev *msi_desc_to_pci_dev(struct msi_desc *desc)
{
return to_pci_dev(desc->dev);
}
EXPORT_SYMBOL(msi_desc_to_pci_dev);
void *msi_desc_to_pci_sysdata(struct msi_desc *desc)
{
......@@ -1285,6 +1288,7 @@ struct irq_domain *pci_msi_create_irq_domain(struct fwnode_handle *fwnode,
domain->bus_token = DOMAIN_BUS_PCI_MSI;
return domain;
}
EXPORT_SYMBOL_GPL(pci_msi_create_irq_domain);
/**
* pci_msi_domain_alloc_irqs - Allocate interrupts for @dev in @domain
......
......@@ -9,7 +9,9 @@
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/irqdomain.h>
#include <linux/pci.h>
#include <linux/msi.h>
#include <linux/pci_hotplug.h>
#include <linux/module.h>
#include <linux/pci-aspm.h>
......@@ -689,6 +691,46 @@ static struct acpi_bus_type acpi_pci_bus = {
.cleanup = pci_acpi_cleanup,
};
static struct fwnode_handle *(*pci_msi_get_fwnode_cb)(struct device *dev);
/**
* pci_msi_register_fwnode_provider - Register callback to retrieve fwnode
* @fn: Callback matching a device to a fwnode that identifies a PCI
* MSI domain.
*
* This should be called by irqchip driver, which is the parent of
* the MSI domain to provide callback interface to query fwnode.
*/
void
pci_msi_register_fwnode_provider(struct fwnode_handle *(*fn)(struct device *))
{
pci_msi_get_fwnode_cb = fn;
}
/**
* pci_host_bridge_acpi_msi_domain - Retrieve MSI domain of a PCI host bridge
* @bus: The PCI host bridge bus.
*
* This function uses the callback function registered by
* pci_msi_register_fwnode_provider() to retrieve the irq_domain with
* type DOMAIN_BUS_PCI_MSI of the specified host bridge bus.
* This returns NULL on error or when the domain is not found.
*/
struct irq_domain *pci_host_bridge_acpi_msi_domain(struct pci_bus *bus)
{
struct fwnode_handle *fwnode;
if (!pci_msi_get_fwnode_cb)
return NULL;
fwnode = pci_msi_get_fwnode_cb(&bus->dev);
if (!fwnode)
return NULL;
return irq_find_matching_fwnode(fwnode, DOMAIN_BUS_PCI_MSI);
}
static int __init acpi_pci_init(void)
{
int ret;
......
......@@ -672,6 +672,8 @@ static struct irq_domain *pci_host_bridge_msi_domain(struct pci_bus *bus)
* should be called from here.
*/
d = pci_host_bridge_of_msi_domain(bus);
if (!d)
d = pci_host_bridge_acpi_msi_domain(bus);
return d;
}
......
......@@ -195,6 +195,7 @@ extern void disable_irq(unsigned int irq);
extern void disable_percpu_irq(unsigned int irq);
extern void enable_irq(unsigned int irq);
extern void enable_percpu_irq(unsigned int irq, unsigned int type);
extern bool irq_percpu_is_enabled(unsigned int irq);
extern void irq_wake_thread(unsigned int irq, void *dev_id);
/* The following three functions are for the core kernel use only. */
......
......@@ -103,10 +103,21 @@ struct device_node;
void gic_cascade_irq(unsigned int gic_nr, unsigned int irq);
int gic_cpu_if_down(unsigned int gic_nr);
/*
* Subdrivers that need some preparatory work can initialize their
* chips and call this to register their GICs.
*/
int gic_of_init(struct device_node *node, struct device_node *parent);
/*
* Legacy platforms not converted to DT yet must use this to init
* their GIC
*/
void gic_init(unsigned int nr, int start,
void __iomem *dist , void __iomem *cpu);
int gicv2m_of_init(struct device_node *node, struct irq_domain *parent);
int gicv2m_init(struct fwnode_handle *parent_handle,
struct irq_domain *parent);
void gic_send_sgi(unsigned int cpu_id, unsigned int irq);
int gic_get_cpu_id(unsigned int cpu);
......
#ifndef _LINUX_IRQDESC_H
#define _LINUX_IRQDESC_H
#include <linux/rcupdate.h>
/*
* Core internal functions to deal with irq descriptors
*/
......@@ -40,6 +42,7 @@ struct pt_regs;
* IRQF_NO_SUSPEND set
* @force_resume_depth: number of irqactions on a irq descriptor with
* IRQF_FORCE_RESUME set
* @rcu: rcu head for delayed free
* @dir: /proc/irq/ procfs entry
* @name: flow handler name for /proc/interrupts output
*/
......@@ -81,6 +84,9 @@ struct irq_desc {
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
#ifdef CONFIG_SPARSE_IRQ
struct rcu_head rcu;
#endif
int parent_irq;
struct module *owner;
......
......@@ -211,6 +211,11 @@ static inline struct fwnode_handle *of_node_to_fwnode(struct device_node *node)
return node ? &node->fwnode : NULL;
}
static inline bool is_fwnode_irqchip(struct fwnode_handle *fwnode)
{
return fwnode && fwnode->type == FWNODE_IRQCHIP;
}
static inline struct irq_domain *irq_find_matching_host(struct device_node *node,
enum irq_domain_bus_token bus_token)
{
......@@ -367,6 +372,9 @@ static inline int irq_domain_alloc_irqs(struct irq_domain *domain,
return __irq_domain_alloc_irqs(domain, -1, nr_irqs, node, arg, false);
}
extern int irq_domain_alloc_irqs_recursive(struct irq_domain *domain,
unsigned int irq_base,
unsigned int nr_irqs, void *arg);
extern int irq_domain_set_hwirq_and_chip(struct irq_domain *domain,
unsigned int virq,
irq_hw_number_t hwirq,
......@@ -410,6 +418,11 @@ static inline bool irq_domain_is_hierarchy(struct irq_domain *domain)
static inline void irq_dispose_mapping(unsigned int virq) { }
static inline void irq_domain_activate_irq(struct irq_data *data) { }
static inline void irq_domain_deactivate_irq(struct irq_data *data) { }
static inline struct irq_domain *irq_find_matching_fwnode(
struct fwnode_handle *fwnode, enum irq_domain_bus_token bus_token)
{
return NULL;
}
#endif /* !CONFIG_IRQ_DOMAIN */
#endif /* _LINUX_IRQDOMAIN_H */
......@@ -174,6 +174,7 @@ struct msi_controller {
#include <asm/msi.h>
struct irq_domain;
struct irq_domain_ops;
struct irq_chip;
struct device_node;
struct fwnode_handle;
......@@ -279,6 +280,23 @@ struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode,
int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
irq_write_msi_msg_t write_msi_msg);
void platform_msi_domain_free_irqs(struct device *dev);
/* When an MSI domain is used as an intermediate domain */
int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev,
int nvec, msi_alloc_info_t *args);
int msi_domain_populate_irqs(struct irq_domain *domain, struct device *dev,
int virq, int nvec, msi_alloc_info_t *args);
struct irq_domain *
platform_msi_create_device_domain(struct device *dev,
unsigned int nvec,
irq_write_msi_msg_t write_msi_msg,
const struct irq_domain_ops *ops,
void *host_data);
int platform_msi_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs);
void platform_msi_domain_free(struct irq_domain *domain, unsigned int virq,
unsigned int nvec);
void *platform_msi_get_host_data(struct irq_domain *domain);
#endif /* CONFIG_GENERIC_MSI_IRQ_DOMAIN */
#ifdef CONFIG_PCI_MSI_IRQ_DOMAIN
......
......@@ -1946,6 +1946,16 @@ static inline struct irq_domain *
pci_host_bridge_of_msi_domain(struct pci_bus *bus) { return NULL; }
#endif /* CONFIG_OF */
#ifdef CONFIG_ACPI
struct irq_domain *pci_host_bridge_acpi_msi_domain(struct pci_bus *bus);
void
pci_msi_register_fwnode_provider(struct fwnode_handle *(*fn)(struct device *));
#else
static inline struct irq_domain *
pci_host_bridge_acpi_msi_domain(struct pci_bus *bus) { return NULL; }
#endif
#ifdef CONFIG_EEH
static inline struct eeh_dev *pci_dev_to_eeh_dev(struct pci_dev *pdev)
{
......
/*
* Renesas INTC External IRQ Pin Driver
*
* Copyright (C) 2013 Magnus Damm
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __IRQ_RENESAS_INTC_IRQPIN_H__
#define __IRQ_RENESAS_INTC_IRQPIN_H__
struct renesas_intc_irqpin_config {
unsigned int sense_bitfield_width;
unsigned int irq_base;
bool control_parent;
};
#endif /* __IRQ_RENESAS_INTC_IRQPIN_H__ */
......@@ -338,7 +338,6 @@ void handle_nested_irq(unsigned int irq)
raw_spin_lock_irq(&desc->lock);
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
kstat_incr_irqs_this_cpu(desc);
action = desc->action;
if (unlikely(!action || irqd_irq_disabled(&desc->irq_data))) {
......@@ -346,6 +345,7 @@ void handle_nested_irq(unsigned int irq)
goto out_unlock;
}
kstat_incr_irqs_this_cpu(desc);
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock_irq(&desc->lock);
......@@ -412,13 +412,13 @@ void handle_simple_irq(struct irq_desc *desc)
goto out_unlock;
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
kstat_incr_irqs_this_cpu(desc);
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
desc->istate |= IRQS_PENDING;
goto out_unlock;
}
kstat_incr_irqs_this_cpu(desc);
handle_irq_event(desc);
out_unlock:
......@@ -462,7 +462,6 @@ void handle_level_irq(struct irq_desc *desc)
goto out_unlock;
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
kstat_incr_irqs_this_cpu(desc);
/*
* If its disabled or no action available
......@@ -473,6 +472,7 @@ void handle_level_irq(struct irq_desc *desc)
goto out_unlock;
}
kstat_incr_irqs_this_cpu(desc);
handle_irq_event(desc);
cond_unmask_irq(desc);
......@@ -532,7 +532,6 @@ void handle_fasteoi_irq(struct irq_desc *desc)
goto out;
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
kstat_incr_irqs_this_cpu(desc);
/*
* If its disabled or no action available
......@@ -544,6 +543,7 @@ void handle_fasteoi_irq(struct irq_desc *desc)
goto out;
}
kstat_incr_irqs_this_cpu(desc);
if (desc->istate & IRQS_ONESHOT)
mask_irq(desc);
......@@ -950,6 +950,7 @@ void irq_chip_ack_parent(struct irq_data *data)
data = data->parent_data;
data->chip->irq_ack(data);
}
EXPORT_SYMBOL_GPL(irq_chip_ack_parent);
/**
* irq_chip_mask_parent - Mask the parent interrupt
......
......@@ -159,6 +159,7 @@ static struct irq_desc *alloc_desc(int irq, int node, struct module *owner)
raw_spin_lock_init(&desc->lock);
lockdep_set_class(&desc->lock, &irq_desc_lock_class);
init_rcu_head(&desc->rcu);
desc_set_defaults(irq, desc, node, owner);
......@@ -171,6 +172,15 @@ static struct irq_desc *alloc_desc(int irq, int node, struct module *owner)
return NULL;
}
static void delayed_free_desc(struct rcu_head *rhp)
{
struct irq_desc *desc = container_of(rhp, struct irq_desc, rcu);
free_masks(desc);
free_percpu(desc->kstat_irqs);
kfree(desc);
}
static void free_desc(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
......@@ -187,9 +197,12 @@ static void free_desc(unsigned int irq)
delete_irq_desc(irq);
mutex_unlock(&sparse_irq_lock);
free_masks(desc);
free_percpu(desc->kstat_irqs);
kfree(desc);
/*
* We free the descriptor, masks and stat fields via RCU. That
* allows demultiplex interrupts to do rcu based management of
* the child interrupts.
*/
call_rcu(&desc->rcu, delayed_free_desc);
}
static int alloc_descs(unsigned int start, unsigned int cnt, int node,
......
......@@ -60,6 +60,7 @@ struct fwnode_handle *irq_domain_alloc_fwnode(void *data)
fwid->fwnode.type = FWNODE_IRQCHIP;
return &fwid->fwnode;
}
EXPORT_SYMBOL_GPL(irq_domain_alloc_fwnode);
/**
* irq_domain_free_fwnode - Free a non-OF-backed fwnode_handle
......@@ -70,13 +71,14 @@ void irq_domain_free_fwnode(struct fwnode_handle *fwnode)
{
struct irqchip_fwid *fwid;
if (WARN_ON(fwnode->type != FWNODE_IRQCHIP))
if (WARN_ON(!is_fwnode_irqchip(fwnode)))
return;
fwid = container_of(fwnode, struct irqchip_fwid, fwnode);
kfree(fwid->name);
kfree(fwid);
}
EXPORT_SYMBOL_GPL(irq_domain_free_fwnode);
/**
* __irq_domain_add() - Allocate a new irq_domain data structure
......@@ -1013,6 +1015,7 @@ struct irq_data *irq_domain_get_irq_data(struct irq_domain *domain,
return NULL;
}
EXPORT_SYMBOL_GPL(irq_domain_get_irq_data);
/**
* irq_domain_set_hwirq_and_chip - Set hwirq and irqchip of @virq at @domain
......@@ -1125,9 +1128,9 @@ static void irq_domain_free_irqs_recursive(struct irq_domain *domain,
}
}
static int irq_domain_alloc_irqs_recursive(struct irq_domain *domain,
unsigned int irq_base,
unsigned int nr_irqs, void *arg)
int irq_domain_alloc_irqs_recursive(struct irq_domain *domain,
unsigned int irq_base,
unsigned int nr_irqs, void *arg)
{
int ret = 0;
struct irq_domain *parent = domain->parent;
......@@ -1343,6 +1346,7 @@ struct irq_data *irq_domain_get_irq_data(struct irq_domain *domain,
return (irq_data && irq_data->domain == domain) ? irq_data : NULL;
}
EXPORT_SYMBOL_GPL(irq_domain_get_irq_data);
/**
* irq_domain_set_info - Set the complete data for a @virq in @domain
......
......@@ -1743,6 +1743,31 @@ void enable_percpu_irq(unsigned int irq, unsigned int type)
}
EXPORT_SYMBOL_GPL(enable_percpu_irq);
/**
* irq_percpu_is_enabled - Check whether the per cpu irq is enabled
* @irq: Linux irq number to check for
*
* Must be called from a non migratable context. Returns the enable
* state of a per cpu interrupt on the current cpu.
*/
bool irq_percpu_is_enabled(unsigned int irq)
{
unsigned int cpu = smp_processor_id();
struct irq_desc *desc;
unsigned long flags;
bool is_enabled;
desc = irq_get_desc_lock(irq, &flags, IRQ_GET_DESC_CHECK_PERCPU);
if (!desc)
return false;
is_enabled = cpumask_test_cpu(cpu, desc->percpu_enabled);
irq_put_desc_unlock(desc, flags);
return is_enabled;
}
EXPORT_SYMBOL_GPL(irq_percpu_is_enabled);
void disable_percpu_irq(unsigned int irq)
{
unsigned int cpu = smp_processor_id();
......
......@@ -252,6 +252,60 @@ struct irq_domain *msi_create_irq_domain(struct fwnode_handle *fwnode,
&msi_domain_ops, info);
}
int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev,
int nvec, msi_alloc_info_t *arg)
{
struct msi_domain_info *info = domain->host_data;
struct msi_domain_ops *ops = info->ops;
int ret;
ret = ops->msi_check(domain, info, dev);
if (ret == 0)
ret = ops->msi_prepare(domain, dev, nvec, arg);
return ret;
}
int msi_domain_populate_irqs(struct irq_domain *domain, struct device *dev,
int virq, int nvec, msi_alloc_info_t *arg)
{
struct msi_domain_info *info = domain->host_data;
struct msi_domain_ops *ops = info->ops;
struct msi_desc *desc;
int ret = 0;
for_each_msi_entry(desc, dev) {
/* Don't even try the multi-MSI brain damage. */
if (WARN_ON(!desc->irq || desc->nvec_used != 1)) {
ret = -EINVAL;
break;
}
if (!(desc->irq >= virq && desc->irq < (virq + nvec)))
continue;
ops->set_desc(arg, desc);
/* Assumes the domain mutex is held! */
ret = irq_domain_alloc_irqs_recursive(domain, virq, 1, arg);
if (ret)
break;
irq_set_msi_desc_off(virq, 0, desc);
}
if (ret) {
/* Mop up the damage */
for_each_msi_entry(desc, dev) {
if (!(desc->irq >= virq && desc->irq < (virq + nvec)))
continue;
irq_domain_free_irqs_common(domain, desc->irq, 1);
}
}
return ret;
}
/**
* msi_domain_alloc_irqs - Allocate interrupts from a MSI interrupt domain
* @domain: The domain to allocate from
......@@ -270,9 +324,7 @@ int msi_domain_alloc_irqs(struct irq_domain *domain, struct device *dev,
struct msi_desc *desc;
int i, ret, virq = -1;
ret = ops->msi_check(domain, info, dev);
if (ret == 0)
ret = ops->msi_prepare(domain, dev, nvec, &arg);
ret = msi_domain_prepare_irqs(domain, dev, nvec, &arg);
if (ret)
return ret;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册