forked from Minki/linux
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 ...
This commit is contained in:
commit
3d116a66ed
@ -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"
|
||||
|
@ -0,0 +1,74 @@
|
||||
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>;
|
||||
};
|
@ -0,0 +1,16 @@
|
||||
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;
|
||||
|
||||
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++) {
|
||||
struct msi_desc *desc;
|
||||
|
||||
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,6 +201,53 @@ struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode,
|
||||
return domain;
|
||||
}
|
||||
|
||||
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 *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 > 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 ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
/* Already had a helping of MSI? Greed... */
|
||||
if (!list_empty(dev_to_msi_list(dev)))
|
||||
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);
|
||||
}
|
||||
|
||||
datap->write_msg = write_msi_msg;
|
||||
datap->dev = dev;
|
||||
|
||||
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
|
||||
@ -195,41 +263,13 @@ int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
|
||||
struct platform_msi_priv_data *priv_data;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* 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->bus_token != DOMAIN_BUS_PLATFORM_MSI) {
|
||||
dev_err(dev, "Incompatible msi_domain, giving up\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Already had a helping of MSI? Greed... */
|
||||
if (!list_empty(dev_to_msi_list(dev)))
|
||||
return -EBUSY;
|
||||
|
||||
priv_data = kzalloc(sizeof(*priv_data), GFP_KERNEL);
|
||||
if (!priv_data)
|
||||
return -ENOMEM;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 @@ out_free_data:
|
||||
*/
|
||||
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);
|
||||
if (desc) {
|
||||
struct platform_msi_priv_data *data;
|
||||
|
||||
data = desc->platform.msi_priv_data;
|
||||
|
||||
ida_simple_remove(&platform_msi_devid_ida, data->devid);
|
||||
kfree(data);
|
||||
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);
|
||||
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;
|
||||
|
||||
err = msi_domain_prepare_irqs(domain->parent, dev, nvec, &data->arg);
|
||||
if (err)
|
||||
goto free_domain;
|
||||
|
||||
return domain;
|
||||
|
||||
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;
|
||||
|
||||
irq_domain_free_irqs_common(domain, desc->irq, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
43
drivers/irqchip/irq-gic-realview.c
Normal file
43
drivers/irqchip/irq-gic-realview.c
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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) {
|
||||
of_node_put(node);
|
||||
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(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,
|
||||
|
297
drivers/irqchip/irq-mbigen.c
Normal file
297
drivers/irqchip/irq-mbigen.c
Normal file
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* 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);
|
||||
|
163
drivers/irqchip/irq-ts4800.c
Normal file
163
drivers/irqchip/irq-ts4800.c
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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);
|
||||
|
@ -1,6 +1,8 @@
|
||||
#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)
|
||||
{
|
||||
|
@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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 @@ err_desc:
|
||||
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 @@ out:
|
||||
}
|
||||
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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user