Merge tag 'irqchip-4.21' of git://git.kernel.org/pub/scm/linux/kernel/git/maz/arm-platforms into irq/core
Pull irqchip updates from Marc Zyngier: - A bunch of new irqchip drivers (RDA8810PL, Madera, imx-irqsteer) - Updates for new (and old) platforms (i.MX8MQ, F1C100s) - A number of SPDX cleanups - A workaround for a very broken GICv3 implementation - A platform-msi fix - Various cleanups
This commit is contained in:
@@ -2,7 +2,9 @@ Allwinner Sunxi Interrupt Controller
|
|||||||
|
|
||||||
Required properties:
|
Required properties:
|
||||||
|
|
||||||
- compatible : should be "allwinner,sun4i-a10-ic"
|
- compatible : should be one of the following:
|
||||||
|
"allwinner,sun4i-a10-ic"
|
||||||
|
"allwinner,suniv-f1c100s-ic"
|
||||||
- reg : Specifies base physical address and size of the registers.
|
- reg : Specifies base physical address and size of the registers.
|
||||||
- interrupt-controller : Identifies the node as an interrupt controller
|
- interrupt-controller : Identifies the node as an interrupt controller
|
||||||
- #interrupt-cells : Specifies the number of cells needed to encode an
|
- #interrupt-cells : Specifies the number of cells needed to encode an
|
||||||
|
@@ -7,7 +7,9 @@ Interrupts (LPI).
|
|||||||
|
|
||||||
Main node required properties:
|
Main node required properties:
|
||||||
|
|
||||||
- compatible : should at least contain "arm,gic-v3".
|
- compatible : should at least contain "arm,gic-v3" or either
|
||||||
|
"qcom,msm8996-gic-v3", "arm,gic-v3" for msm8996 SoCs
|
||||||
|
to address SoC specific bugs/quirks
|
||||||
- interrupt-controller : Identifies the node as an interrupt controller
|
- interrupt-controller : Identifies the node as an interrupt controller
|
||||||
- #interrupt-cells : Specifies the number of cells needed to encode an
|
- #interrupt-cells : Specifies the number of cells needed to encode an
|
||||||
interrupt source. Must be a single cell with a value of at least 3.
|
interrupt source. Must be a single cell with a value of at least 3.
|
||||||
|
@@ -0,0 +1,34 @@
|
|||||||
|
Freescale IRQSTEER Interrupt multiplexer
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
|
||||||
|
- compatible: should be:
|
||||||
|
- "fsl,imx8m-irqsteer"
|
||||||
|
- "fsl,imx-irqsteer"
|
||||||
|
- reg: Physical base address and size of registers.
|
||||||
|
- interrupts: Should contain the parent interrupt line used to multiplex the
|
||||||
|
input interrupts.
|
||||||
|
- clocks: Should contain one clock for entry in clock-names
|
||||||
|
see Documentation/devicetree/bindings/clock/clock-bindings.txt
|
||||||
|
- clock-names:
|
||||||
|
- "ipg": main logic clock
|
||||||
|
- interrupt-controller: Identifies the node as an interrupt controller.
|
||||||
|
- #interrupt-cells: Specifies the number of cells needed to encode an
|
||||||
|
interrupt source. The value must be 1.
|
||||||
|
- fsl,channel: The output channel that all input IRQs should be steered into.
|
||||||
|
- fsl,irq-groups: Number of IRQ groups managed by this controller instance.
|
||||||
|
Each group manages 64 input interrupts.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
interrupt-controller@32e2d000 {
|
||||||
|
compatible = "fsl,imx8m-irqsteer", "fsl,imx-irqsteer";
|
||||||
|
reg = <0x32e2d000 0x1000>;
|
||||||
|
interrupts = <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>;
|
||||||
|
clocks = <&clk IMX8MQ_CLK_DISP_APB_ROOT>;
|
||||||
|
clock-names = "ipg";
|
||||||
|
fsl,channel = <0>;
|
||||||
|
fsl,irq-groups = <1>;
|
||||||
|
interrupt-controller;
|
||||||
|
#interrupt-cells = <1>;
|
||||||
|
};
|
@@ -0,0 +1,61 @@
|
|||||||
|
RDA Micro RDA8810PL Interrupt Controller
|
||||||
|
|
||||||
|
The interrupt controller in RDA8810PL SoC is a custom interrupt controller
|
||||||
|
which supports up to 32 interrupts.
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
|
||||||
|
- compatible: Should be "rda,8810pl-intc".
|
||||||
|
- reg: Specifies base physical address of the registers set.
|
||||||
|
- interrupt-controller: Identifies the node as an interrupt controller.
|
||||||
|
- #interrupt-cells: Specifies the number of cells needed to encode an
|
||||||
|
interrupt source. The value shall be 2.
|
||||||
|
|
||||||
|
The interrupt sources are as follows:
|
||||||
|
|
||||||
|
ID Name
|
||||||
|
------------
|
||||||
|
0: PULSE_DUMMY
|
||||||
|
1: I2C
|
||||||
|
2: NAND_NFSC
|
||||||
|
3: SDMMC1
|
||||||
|
4: SDMMC2
|
||||||
|
5: SDMMC3
|
||||||
|
6: SPI1
|
||||||
|
7: SPI2
|
||||||
|
8: SPI3
|
||||||
|
9: UART1
|
||||||
|
10: UART2
|
||||||
|
11: UART3
|
||||||
|
12: GPIO1
|
||||||
|
13: GPIO2
|
||||||
|
14: GPIO3
|
||||||
|
15: KEYPAD
|
||||||
|
16: TIMER
|
||||||
|
17: TIMEROS
|
||||||
|
18: COMREG0
|
||||||
|
19: COMREG1
|
||||||
|
20: USB
|
||||||
|
21: DMC
|
||||||
|
22: DMA
|
||||||
|
23: CAMERA
|
||||||
|
24: GOUDA
|
||||||
|
25: GPU
|
||||||
|
26: VPU_JPG
|
||||||
|
27: VPU_HOST
|
||||||
|
28: VOC
|
||||||
|
29: AUIFC0
|
||||||
|
30: AUIFC1
|
||||||
|
31: L2CC
|
||||||
|
|
||||||
|
Example:
|
||||||
|
apb@20800000 {
|
||||||
|
compatible = "simple-bus";
|
||||||
|
...
|
||||||
|
intc: interrupt-controller@0 {
|
||||||
|
compatible = "rda,8810pl-intc";
|
||||||
|
reg = <0x0 0x1000>;
|
||||||
|
interrupt-controller;
|
||||||
|
#interrupt-cells = <2>;
|
||||||
|
};
|
||||||
|
};
|
@@ -14,6 +14,10 @@ Required properties:
|
|||||||
(only needed for exti controller with multiple exti under
|
(only needed for exti controller with multiple exti under
|
||||||
same parent interrupt: st,stm32-exti and st,stm32h7-exti)
|
same parent interrupt: st,stm32-exti and st,stm32h7-exti)
|
||||||
|
|
||||||
|
Optional properties:
|
||||||
|
|
||||||
|
- hwlocks: reference to a phandle of a hardware spinlock provider node.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
exti: interrupt-controller@40013c00 {
|
exti: interrupt-controller@40013c00 {
|
||||||
|
@@ -3669,8 +3669,10 @@ W: https://github.com/CirrusLogic/linux-drivers/wiki
|
|||||||
S: Supported
|
S: Supported
|
||||||
F: Documentation/devicetree/bindings/mfd/madera.txt
|
F: Documentation/devicetree/bindings/mfd/madera.txt
|
||||||
F: Documentation/devicetree/bindings/pinctrl/cirrus,madera-pinctrl.txt
|
F: Documentation/devicetree/bindings/pinctrl/cirrus,madera-pinctrl.txt
|
||||||
|
F: include/linux/irqchip/irq-madera*
|
||||||
F: include/linux/mfd/madera/*
|
F: include/linux/mfd/madera/*
|
||||||
F: drivers/gpio/gpio-madera*
|
F: drivers/gpio/gpio-madera*
|
||||||
|
F: drivers/irqchip/irq-madera*
|
||||||
F: drivers/mfd/madera*
|
F: drivers/mfd/madera*
|
||||||
F: drivers/mfd/cs47l*
|
F: drivers/mfd/cs47l*
|
||||||
F: drivers/pinctrl/cirrus/*
|
F: drivers/pinctrl/cirrus/*
|
||||||
|
@@ -368,14 +368,16 @@ void platform_msi_domain_free(struct irq_domain *domain, unsigned int virq,
|
|||||||
unsigned int nvec)
|
unsigned int nvec)
|
||||||
{
|
{
|
||||||
struct platform_msi_priv_data *data = domain->host_data;
|
struct platform_msi_priv_data *data = domain->host_data;
|
||||||
struct msi_desc *desc;
|
struct msi_desc *desc, *tmp;
|
||||||
for_each_msi_entry(desc, data->dev) {
|
for_each_msi_entry_safe(desc, tmp, data->dev) {
|
||||||
if (WARN_ON(!desc->irq || desc->nvec_used != 1))
|
if (WARN_ON(!desc->irq || desc->nvec_used != 1))
|
||||||
return;
|
return;
|
||||||
if (!(desc->irq >= virq && desc->irq < (virq + nvec)))
|
if (!(desc->irq >= virq && desc->irq < (virq + nvec)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
irq_domain_free_irqs_common(domain, desc->irq, 1);
|
irq_domain_free_irqs_common(domain, desc->irq, 1);
|
||||||
|
list_del(&desc->list);
|
||||||
|
free_msi_entry(desc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -150,6 +150,9 @@ config IMGPDC_IRQ
|
|||||||
select GENERIC_IRQ_CHIP
|
select GENERIC_IRQ_CHIP
|
||||||
select IRQ_DOMAIN
|
select IRQ_DOMAIN
|
||||||
|
|
||||||
|
config MADERA_IRQ
|
||||||
|
tristate
|
||||||
|
|
||||||
config IRQ_MIPS_CPU
|
config IRQ_MIPS_CPU
|
||||||
bool
|
bool
|
||||||
select GENERIC_IRQ_CHIP
|
select GENERIC_IRQ_CHIP
|
||||||
@@ -195,6 +198,10 @@ config JCORE_AIC
|
|||||||
help
|
help
|
||||||
Support for the J-Core integrated AIC.
|
Support for the J-Core integrated AIC.
|
||||||
|
|
||||||
|
config RDA_INTC
|
||||||
|
bool
|
||||||
|
select IRQ_DOMAIN
|
||||||
|
|
||||||
config RENESAS_INTC_IRQPIN
|
config RENESAS_INTC_IRQPIN
|
||||||
bool
|
bool
|
||||||
select IRQ_DOMAIN
|
select IRQ_DOMAIN
|
||||||
@@ -391,6 +398,14 @@ config CSKY_APB_INTC
|
|||||||
by C-SKY single core SOC system. It use mmio map apb-bus to visit
|
by C-SKY single core SOC system. It use mmio map apb-bus to visit
|
||||||
the controller's register.
|
the controller's register.
|
||||||
|
|
||||||
|
config IMX_IRQSTEER
|
||||||
|
bool "i.MX IRQSTEER support"
|
||||||
|
depends on ARCH_MXC || COMPILE_TEST
|
||||||
|
default ARCH_MXC
|
||||||
|
select IRQ_DOMAIN
|
||||||
|
help
|
||||||
|
Support for the i.MX IRQSTEER interrupt multiplexer/remapper.
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
config SIFIVE_PLIC
|
config SIFIVE_PLIC
|
||||||
|
@@ -43,6 +43,7 @@ obj-$(CONFIG_IMGPDC_IRQ) += irq-imgpdc.o
|
|||||||
obj-$(CONFIG_IRQ_MIPS_CPU) += irq-mips-cpu.o
|
obj-$(CONFIG_IRQ_MIPS_CPU) += irq-mips-cpu.o
|
||||||
obj-$(CONFIG_SIRF_IRQ) += irq-sirfsoc.o
|
obj-$(CONFIG_SIRF_IRQ) += irq-sirfsoc.o
|
||||||
obj-$(CONFIG_JCORE_AIC) += irq-jcore-aic.o
|
obj-$(CONFIG_JCORE_AIC) += irq-jcore-aic.o
|
||||||
|
obj-$(CONFIG_RDA_INTC) += irq-rda-intc.o
|
||||||
obj-$(CONFIG_RENESAS_INTC_IRQPIN) += irq-renesas-intc-irqpin.o
|
obj-$(CONFIG_RENESAS_INTC_IRQPIN) += irq-renesas-intc-irqpin.o
|
||||||
obj-$(CONFIG_RENESAS_IRQC) += irq-renesas-irqc.o
|
obj-$(CONFIG_RENESAS_IRQC) += irq-renesas-irqc.o
|
||||||
obj-$(CONFIG_VERSATILE_FPGA_IRQ) += irq-versatile-fpga.o
|
obj-$(CONFIG_VERSATILE_FPGA_IRQ) += irq-versatile-fpga.o
|
||||||
@@ -91,3 +92,5 @@ obj-$(CONFIG_QCOM_PDC) += qcom-pdc.o
|
|||||||
obj-$(CONFIG_CSKY_MPINTC) += irq-csky-mpintc.o
|
obj-$(CONFIG_CSKY_MPINTC) += irq-csky-mpintc.o
|
||||||
obj-$(CONFIG_CSKY_APB_INTC) += irq-csky-apb-intc.o
|
obj-$(CONFIG_CSKY_APB_INTC) += irq-csky-apb-intc.o
|
||||||
obj-$(CONFIG_SIFIVE_PLIC) += irq-sifive-plic.o
|
obj-$(CONFIG_SIFIVE_PLIC) += irq-sifive-plic.o
|
||||||
|
obj-$(CONFIG_IMX_IRQSTEER) += irq-imx-irqsteer.o
|
||||||
|
obj-$(CONFIG_MADERA_IRQ) += irq-madera.o
|
||||||
|
@@ -1,17 +1,8 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0+
|
||||||
/*
|
/*
|
||||||
* Copyright 2010 Broadcom
|
* Copyright 2010 Broadcom
|
||||||
* Copyright 2012 Simon Arlott, Chris Boot, Stephen Warren
|
* Copyright 2012 Simon Arlott, Chris Boot, Stephen Warren
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* Quirk 1: Shortcut interrupts don't set the bank 1/2 register pending bits
|
* Quirk 1: Shortcut interrupts don't set the bank 1/2 register pending bits
|
||||||
*
|
*
|
||||||
* If an interrupt fires on bank 1 that isn't in the shortcuts list, bit 8
|
* If an interrupt fires on bank 1 that isn't in the shortcuts list, bit 8
|
||||||
|
@@ -1,17 +1,8 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0+
|
||||||
/*
|
/*
|
||||||
* Root interrupt controller for the BCM2836 (Raspberry Pi 2).
|
* Root interrupt controller for the BCM2836 (Raspberry Pi 2).
|
||||||
*
|
*
|
||||||
* Copyright 2015 Broadcom
|
* Copyright 2015 Broadcom
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/cpu.h>
|
#include <linux/cpu.h>
|
||||||
|
@@ -36,6 +36,18 @@ void gic_set_kvm_info(const struct gic_kvm_info *info)
|
|||||||
gic_kvm_info = info;
|
gic_kvm_info = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gic_enable_of_quirks(const struct device_node *np,
|
||||||
|
const struct gic_quirk *quirks, void *data)
|
||||||
|
{
|
||||||
|
for (; quirks->desc; quirks++) {
|
||||||
|
if (!of_device_is_compatible(np, quirks->compatible))
|
||||||
|
continue;
|
||||||
|
if (quirks->init(data))
|
||||||
|
pr_info("GIC: enabling workaround for %s\n",
|
||||||
|
quirks->desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
|
void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
|
||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
struct gic_quirk {
|
struct gic_quirk {
|
||||||
const char *desc;
|
const char *desc;
|
||||||
|
const char *compatible;
|
||||||
bool (*init)(void *data);
|
bool (*init)(void *data);
|
||||||
u32 iidr;
|
u32 iidr;
|
||||||
u32 mask;
|
u32 mask;
|
||||||
@@ -35,6 +36,8 @@ void gic_dist_config(void __iomem *base, int gic_irqs,
|
|||||||
void gic_cpu_config(void __iomem *base, void (*sync_access)(void));
|
void gic_cpu_config(void __iomem *base, void (*sync_access)(void));
|
||||||
void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
|
void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
|
||||||
void *data);
|
void *data);
|
||||||
|
void gic_enable_of_quirks(const struct device_node *np,
|
||||||
|
const struct gic_quirk *quirks, void *data);
|
||||||
|
|
||||||
void gic_set_kvm_info(const struct gic_kvm_info *info);
|
void gic_set_kvm_info(const struct gic_kvm_info *info);
|
||||||
|
|
||||||
|
@@ -41,6 +41,8 @@
|
|||||||
|
|
||||||
#include "irq-gic-common.h"
|
#include "irq-gic-common.h"
|
||||||
|
|
||||||
|
#define FLAGS_WORKAROUND_GICR_WAKER_MSM8996 (1ULL << 0)
|
||||||
|
|
||||||
struct redist_region {
|
struct redist_region {
|
||||||
void __iomem *redist_base;
|
void __iomem *redist_base;
|
||||||
phys_addr_t phys_base;
|
phys_addr_t phys_base;
|
||||||
@@ -55,6 +57,7 @@ struct gic_chip_data {
|
|||||||
struct irq_domain *domain;
|
struct irq_domain *domain;
|
||||||
u64 redist_stride;
|
u64 redist_stride;
|
||||||
u32 nr_redist_regions;
|
u32 nr_redist_regions;
|
||||||
|
u64 flags;
|
||||||
bool has_rss;
|
bool has_rss;
|
||||||
unsigned int irq_nr;
|
unsigned int irq_nr;
|
||||||
struct partition_desc *ppi_descs[16];
|
struct partition_desc *ppi_descs[16];
|
||||||
@@ -139,6 +142,9 @@ static void gic_enable_redist(bool enable)
|
|||||||
u32 count = 1000000; /* 1s! */
|
u32 count = 1000000; /* 1s! */
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
|
if (gic_data.flags & FLAGS_WORKAROUND_GICR_WAKER_MSM8996)
|
||||||
|
return;
|
||||||
|
|
||||||
rbase = gic_data_rdist_rd_base();
|
rbase = gic_data_rdist_rd_base();
|
||||||
|
|
||||||
val = readl_relaxed(rbase + GICR_WAKER);
|
val = readl_relaxed(rbase + GICR_WAKER);
|
||||||
@@ -1067,6 +1073,15 @@ static const struct irq_domain_ops partition_domain_ops = {
|
|||||||
.select = gic_irq_domain_select,
|
.select = gic_irq_domain_select,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool gic_enable_quirk_msm8996(void *data)
|
||||||
|
{
|
||||||
|
struct gic_chip_data *d = data;
|
||||||
|
|
||||||
|
d->flags |= FLAGS_WORKAROUND_GICR_WAKER_MSM8996;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static int __init gic_init_bases(void __iomem *dist_base,
|
static int __init gic_init_bases(void __iomem *dist_base,
|
||||||
struct redist_region *rdist_regs,
|
struct redist_region *rdist_regs,
|
||||||
u32 nr_redist_regions,
|
u32 nr_redist_regions,
|
||||||
@@ -1271,6 +1286,16 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
|
|||||||
gic_set_kvm_info(&gic_v3_kvm_info);
|
gic_set_kvm_info(&gic_v3_kvm_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const struct gic_quirk gic_quirks[] = {
|
||||||
|
{
|
||||||
|
.desc = "GICv3: Qualcomm MSM8996 broken firmware",
|
||||||
|
.compatible = "qcom,msm8996-gic-v3",
|
||||||
|
.init = gic_enable_quirk_msm8996,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static int __init gic_of_init(struct device_node *node, struct device_node *parent)
|
static int __init gic_of_init(struct device_node *node, struct device_node *parent)
|
||||||
{
|
{
|
||||||
void __iomem *dist_base;
|
void __iomem *dist_base;
|
||||||
@@ -1318,6 +1343,8 @@ static int __init gic_of_init(struct device_node *node, struct device_node *pare
|
|||||||
if (of_property_read_u64(node, "redistributor-stride", &redist_stride))
|
if (of_property_read_u64(node, "redistributor-stride", &redist_stride))
|
||||||
redist_stride = 0;
|
redist_stride = 0;
|
||||||
|
|
||||||
|
gic_enable_of_quirks(node, gic_quirks, &gic_data);
|
||||||
|
|
||||||
err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions,
|
err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions,
|
||||||
redist_stride, &node->fwnode);
|
redist_stride, &node->fwnode);
|
||||||
if (err)
|
if (err)
|
||||||
|
@@ -17,6 +17,9 @@
|
|||||||
|
|
||||||
#define GPC_IMR1_CORE0 0x30
|
#define GPC_IMR1_CORE0 0x30
|
||||||
#define GPC_IMR1_CORE1 0x40
|
#define GPC_IMR1_CORE1 0x40
|
||||||
|
#define GPC_IMR1_CORE2 0x1c0
|
||||||
|
#define GPC_IMR1_CORE3 0x1d0
|
||||||
|
|
||||||
|
|
||||||
struct gpcv2_irqchip_data {
|
struct gpcv2_irqchip_data {
|
||||||
struct raw_spinlock rlock;
|
struct raw_spinlock rlock;
|
||||||
@@ -28,6 +31,11 @@ struct gpcv2_irqchip_data {
|
|||||||
|
|
||||||
static struct gpcv2_irqchip_data *imx_gpcv2_instance;
|
static struct gpcv2_irqchip_data *imx_gpcv2_instance;
|
||||||
|
|
||||||
|
static void __iomem *gpcv2_idx_to_reg(struct gpcv2_irqchip_data *cd, int i)
|
||||||
|
{
|
||||||
|
return cd->gpc_base + cd->cpu2wakeup + i * 4;
|
||||||
|
}
|
||||||
|
|
||||||
static int gpcv2_wakeup_source_save(void)
|
static int gpcv2_wakeup_source_save(void)
|
||||||
{
|
{
|
||||||
struct gpcv2_irqchip_data *cd;
|
struct gpcv2_irqchip_data *cd;
|
||||||
@@ -39,7 +47,7 @@ static int gpcv2_wakeup_source_save(void)
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
for (i = 0; i < IMR_NUM; i++) {
|
for (i = 0; i < IMR_NUM; i++) {
|
||||||
reg = cd->gpc_base + cd->cpu2wakeup + i * 4;
|
reg = gpcv2_idx_to_reg(cd, i);
|
||||||
cd->saved_irq_mask[i] = readl_relaxed(reg);
|
cd->saved_irq_mask[i] = readl_relaxed(reg);
|
||||||
writel_relaxed(cd->wakeup_sources[i], reg);
|
writel_relaxed(cd->wakeup_sources[i], reg);
|
||||||
}
|
}
|
||||||
@@ -50,17 +58,14 @@ static int gpcv2_wakeup_source_save(void)
|
|||||||
static void gpcv2_wakeup_source_restore(void)
|
static void gpcv2_wakeup_source_restore(void)
|
||||||
{
|
{
|
||||||
struct gpcv2_irqchip_data *cd;
|
struct gpcv2_irqchip_data *cd;
|
||||||
void __iomem *reg;
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
cd = imx_gpcv2_instance;
|
cd = imx_gpcv2_instance;
|
||||||
if (!cd)
|
if (!cd)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (i = 0; i < IMR_NUM; i++) {
|
for (i = 0; i < IMR_NUM; i++)
|
||||||
reg = cd->gpc_base + cd->cpu2wakeup + i * 4;
|
writel_relaxed(cd->saved_irq_mask[i], gpcv2_idx_to_reg(cd, i));
|
||||||
writel_relaxed(cd->saved_irq_mask[i], reg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct syscore_ops imx_gpcv2_syscore_ops = {
|
static struct syscore_ops imx_gpcv2_syscore_ops = {
|
||||||
@@ -73,12 +78,10 @@ static int imx_gpcv2_irq_set_wake(struct irq_data *d, unsigned int on)
|
|||||||
struct gpcv2_irqchip_data *cd = d->chip_data;
|
struct gpcv2_irqchip_data *cd = d->chip_data;
|
||||||
unsigned int idx = d->hwirq / 32;
|
unsigned int idx = d->hwirq / 32;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
void __iomem *reg;
|
|
||||||
u32 mask, val;
|
u32 mask, val;
|
||||||
|
|
||||||
raw_spin_lock_irqsave(&cd->rlock, flags);
|
raw_spin_lock_irqsave(&cd->rlock, flags);
|
||||||
reg = cd->gpc_base + cd->cpu2wakeup + idx * 4;
|
mask = BIT(d->hwirq % 32);
|
||||||
mask = 1 << d->hwirq % 32;
|
|
||||||
val = cd->wakeup_sources[idx];
|
val = cd->wakeup_sources[idx];
|
||||||
|
|
||||||
cd->wakeup_sources[idx] = on ? (val & ~mask) : (val | mask);
|
cd->wakeup_sources[idx] = on ? (val & ~mask) : (val | mask);
|
||||||
@@ -99,9 +102,9 @@ static void imx_gpcv2_irq_unmask(struct irq_data *d)
|
|||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
raw_spin_lock(&cd->rlock);
|
raw_spin_lock(&cd->rlock);
|
||||||
reg = cd->gpc_base + cd->cpu2wakeup + d->hwirq / 32 * 4;
|
reg = gpcv2_idx_to_reg(cd, d->hwirq / 32);
|
||||||
val = readl_relaxed(reg);
|
val = readl_relaxed(reg);
|
||||||
val &= ~(1 << d->hwirq % 32);
|
val &= ~BIT(d->hwirq % 32);
|
||||||
writel_relaxed(val, reg);
|
writel_relaxed(val, reg);
|
||||||
raw_spin_unlock(&cd->rlock);
|
raw_spin_unlock(&cd->rlock);
|
||||||
|
|
||||||
@@ -115,9 +118,9 @@ static void imx_gpcv2_irq_mask(struct irq_data *d)
|
|||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
raw_spin_lock(&cd->rlock);
|
raw_spin_lock(&cd->rlock);
|
||||||
reg = cd->gpc_base + cd->cpu2wakeup + d->hwirq / 32 * 4;
|
reg = gpcv2_idx_to_reg(cd, d->hwirq / 32);
|
||||||
val = readl_relaxed(reg);
|
val = readl_relaxed(reg);
|
||||||
val |= 1 << (d->hwirq % 32);
|
val |= BIT(d->hwirq % 32);
|
||||||
writel_relaxed(val, reg);
|
writel_relaxed(val, reg);
|
||||||
raw_spin_unlock(&cd->rlock);
|
raw_spin_unlock(&cd->rlock);
|
||||||
|
|
||||||
@@ -192,11 +195,19 @@ static const struct irq_domain_ops gpcv2_irqchip_data_domain_ops = {
|
|||||||
.free = irq_domain_free_irqs_common,
|
.free = irq_domain_free_irqs_common,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const struct of_device_id gpcv2_of_match[] = {
|
||||||
|
{ .compatible = "fsl,imx7d-gpc", .data = (const void *) 2 },
|
||||||
|
{ .compatible = "fsl,imx8mq-gpc", .data = (const void *) 4 },
|
||||||
|
{ /* END */ }
|
||||||
|
};
|
||||||
|
|
||||||
static int __init imx_gpcv2_irqchip_init(struct device_node *node,
|
static int __init imx_gpcv2_irqchip_init(struct device_node *node,
|
||||||
struct device_node *parent)
|
struct device_node *parent)
|
||||||
{
|
{
|
||||||
struct irq_domain *parent_domain, *domain;
|
struct irq_domain *parent_domain, *domain;
|
||||||
struct gpcv2_irqchip_data *cd;
|
struct gpcv2_irqchip_data *cd;
|
||||||
|
const struct of_device_id *id;
|
||||||
|
unsigned long core_num;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
@@ -204,6 +215,14 @@ static int __init imx_gpcv2_irqchip_init(struct device_node *node,
|
|||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
id = of_match_node(gpcv2_of_match, node);
|
||||||
|
if (!id) {
|
||||||
|
pr_err("%pOF: unknown compatibility string\n", node);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
core_num = (unsigned long)id->data;
|
||||||
|
|
||||||
parent_domain = irq_find_host(parent);
|
parent_domain = irq_find_host(parent);
|
||||||
if (!parent_domain) {
|
if (!parent_domain) {
|
||||||
pr_err("%pOF: unable to get parent domain\n", node);
|
pr_err("%pOF: unable to get parent domain\n", node);
|
||||||
@@ -212,7 +231,7 @@ static int __init imx_gpcv2_irqchip_init(struct device_node *node,
|
|||||||
|
|
||||||
cd = kzalloc(sizeof(struct gpcv2_irqchip_data), GFP_KERNEL);
|
cd = kzalloc(sizeof(struct gpcv2_irqchip_data), GFP_KERNEL);
|
||||||
if (!cd) {
|
if (!cd) {
|
||||||
pr_err("kzalloc failed!\n");
|
pr_err("%pOF: kzalloc failed!\n", node);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +239,7 @@ static int __init imx_gpcv2_irqchip_init(struct device_node *node,
|
|||||||
|
|
||||||
cd->gpc_base = of_iomap(node, 0);
|
cd->gpc_base = of_iomap(node, 0);
|
||||||
if (!cd->gpc_base) {
|
if (!cd->gpc_base) {
|
||||||
pr_err("fsl-gpcv2: unable to map gpc registers\n");
|
pr_err("%pOF: unable to map gpc registers\n", node);
|
||||||
kfree(cd);
|
kfree(cd);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
@@ -236,8 +255,17 @@ static int __init imx_gpcv2_irqchip_init(struct device_node *node,
|
|||||||
|
|
||||||
/* Initially mask all interrupts */
|
/* Initially mask all interrupts */
|
||||||
for (i = 0; i < IMR_NUM; i++) {
|
for (i = 0; i < IMR_NUM; i++) {
|
||||||
writel_relaxed(~0, cd->gpc_base + GPC_IMR1_CORE0 + i * 4);
|
void __iomem *reg = cd->gpc_base + i * 4;
|
||||||
writel_relaxed(~0, cd->gpc_base + GPC_IMR1_CORE1 + i * 4);
|
|
||||||
|
switch (core_num) {
|
||||||
|
case 4:
|
||||||
|
writel_relaxed(~0, reg + GPC_IMR1_CORE2);
|
||||||
|
writel_relaxed(~0, reg + GPC_IMR1_CORE3);
|
||||||
|
/* fall through */
|
||||||
|
case 2:
|
||||||
|
writel_relaxed(~0, reg + GPC_IMR1_CORE0);
|
||||||
|
writel_relaxed(~0, reg + GPC_IMR1_CORE1);
|
||||||
|
}
|
||||||
cd->wakeup_sources[i] = ~0;
|
cd->wakeup_sources[i] = ~0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,4 +290,5 @@ static int __init imx_gpcv2_irqchip_init(struct device_node *node,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
IRQCHIP_DECLARE(imx_gpcv2, "fsl,imx7d-gpc", imx_gpcv2_irqchip_init);
|
IRQCHIP_DECLARE(imx_gpcv2_imx7d, "fsl,imx7d-gpc", imx_gpcv2_irqchip_init);
|
||||||
|
IRQCHIP_DECLARE(imx_gpcv2_imx8mq, "fsl,imx8mq-gpc", imx_gpcv2_irqchip_init);
|
||||||
|
261
drivers/irqchip/irq-imx-irqsteer.c
Normal file
261
drivers/irqchip/irq-imx-irqsteer.c
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0+
|
||||||
|
/*
|
||||||
|
* Copyright 2017 NXP
|
||||||
|
* Copyright (C) 2018 Pengutronix, Lucas Stach <kernel@pengutronix.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/clk.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/irq.h>
|
||||||
|
#include <linux/irqchip/chained_irq.h>
|
||||||
|
#include <linux/irqdomain.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/of_platform.h>
|
||||||
|
#include <linux/spinlock.h>
|
||||||
|
|
||||||
|
#define CTRL_STRIDE_OFF(_t, _r) (_t * 8 * _r)
|
||||||
|
#define CHANCTRL 0x0
|
||||||
|
#define CHANMASK(n, t) (CTRL_STRIDE_OFF(t, 0) + 0x4 * (n) + 0x4)
|
||||||
|
#define CHANSET(n, t) (CTRL_STRIDE_OFF(t, 1) + 0x4 * (n) + 0x4)
|
||||||
|
#define CHANSTATUS(n, t) (CTRL_STRIDE_OFF(t, 2) + 0x4 * (n) + 0x4)
|
||||||
|
#define CHAN_MINTDIS(t) (CTRL_STRIDE_OFF(t, 3) + 0x4)
|
||||||
|
#define CHAN_MASTRSTAT(t) (CTRL_STRIDE_OFF(t, 3) + 0x8)
|
||||||
|
|
||||||
|
struct irqsteer_data {
|
||||||
|
void __iomem *regs;
|
||||||
|
struct clk *ipg_clk;
|
||||||
|
int irq;
|
||||||
|
raw_spinlock_t lock;
|
||||||
|
int irq_groups;
|
||||||
|
int channel;
|
||||||
|
struct irq_domain *domain;
|
||||||
|
u32 *saved_reg;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int imx_irqsteer_get_reg_index(struct irqsteer_data *data,
|
||||||
|
unsigned long irqnum)
|
||||||
|
{
|
||||||
|
return (data->irq_groups * 2 - irqnum / 32 - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void imx_irqsteer_irq_unmask(struct irq_data *d)
|
||||||
|
{
|
||||||
|
struct irqsteer_data *data = d->chip_data;
|
||||||
|
int idx = imx_irqsteer_get_reg_index(data, d->hwirq);
|
||||||
|
unsigned long flags;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
raw_spin_lock_irqsave(&data->lock, flags);
|
||||||
|
val = readl_relaxed(data->regs + CHANMASK(idx, data->irq_groups));
|
||||||
|
val |= BIT(d->hwirq % 32);
|
||||||
|
writel_relaxed(val, data->regs + CHANMASK(idx, data->irq_groups));
|
||||||
|
raw_spin_unlock_irqrestore(&data->lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void imx_irqsteer_irq_mask(struct irq_data *d)
|
||||||
|
{
|
||||||
|
struct irqsteer_data *data = d->chip_data;
|
||||||
|
int idx = imx_irqsteer_get_reg_index(data, d->hwirq);
|
||||||
|
unsigned long flags;
|
||||||
|
u32 val;
|
||||||
|
|
||||||
|
raw_spin_lock_irqsave(&data->lock, flags);
|
||||||
|
val = readl_relaxed(data->regs + CHANMASK(idx, data->irq_groups));
|
||||||
|
val &= ~BIT(d->hwirq % 32);
|
||||||
|
writel_relaxed(val, data->regs + CHANMASK(idx, data->irq_groups));
|
||||||
|
raw_spin_unlock_irqrestore(&data->lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct irq_chip imx_irqsteer_irq_chip = {
|
||||||
|
.name = "irqsteer",
|
||||||
|
.irq_mask = imx_irqsteer_irq_mask,
|
||||||
|
.irq_unmask = imx_irqsteer_irq_unmask,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int imx_irqsteer_irq_map(struct irq_domain *h, unsigned int irq,
|
||||||
|
irq_hw_number_t hwirq)
|
||||||
|
{
|
||||||
|
irq_set_status_flags(irq, IRQ_LEVEL);
|
||||||
|
irq_set_chip_data(irq, h->host_data);
|
||||||
|
irq_set_chip_and_handler(irq, &imx_irqsteer_irq_chip, handle_level_irq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct irq_domain_ops imx_irqsteer_domain_ops = {
|
||||||
|
.map = imx_irqsteer_irq_map,
|
||||||
|
.xlate = irq_domain_xlate_onecell,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void imx_irqsteer_irq_handler(struct irq_desc *desc)
|
||||||
|
{
|
||||||
|
struct irqsteer_data *data = irq_desc_get_handler_data(desc);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
chained_irq_enter(irq_desc_get_chip(desc), desc);
|
||||||
|
|
||||||
|
for (i = 0; i < data->irq_groups * 64; i += 32) {
|
||||||
|
int idx = imx_irqsteer_get_reg_index(data, i);
|
||||||
|
unsigned long irqmap;
|
||||||
|
int pos, virq;
|
||||||
|
|
||||||
|
irqmap = readl_relaxed(data->regs +
|
||||||
|
CHANSTATUS(idx, data->irq_groups));
|
||||||
|
|
||||||
|
for_each_set_bit(pos, &irqmap, 32) {
|
||||||
|
virq = irq_find_mapping(data->domain, pos + i);
|
||||||
|
if (virq)
|
||||||
|
generic_handle_irq(virq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chained_irq_exit(irq_desc_get_chip(desc), desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int imx_irqsteer_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device_node *np = pdev->dev.of_node;
|
||||||
|
struct irqsteer_data *data;
|
||||||
|
struct resource *res;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||||
|
if (!data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
data->regs = devm_ioremap_resource(&pdev->dev, res);
|
||||||
|
if (IS_ERR(data->regs)) {
|
||||||
|
dev_err(&pdev->dev, "failed to initialize reg\n");
|
||||||
|
return PTR_ERR(data->regs);
|
||||||
|
}
|
||||||
|
|
||||||
|
data->irq = platform_get_irq(pdev, 0);
|
||||||
|
if (data->irq <= 0) {
|
||||||
|
dev_err(&pdev->dev, "failed to get irq\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->ipg_clk = devm_clk_get(&pdev->dev, "ipg");
|
||||||
|
if (IS_ERR(data->ipg_clk)) {
|
||||||
|
ret = PTR_ERR(data->ipg_clk);
|
||||||
|
if (ret != -EPROBE_DEFER)
|
||||||
|
dev_err(&pdev->dev, "failed to get ipg clk: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
raw_spin_lock_init(&data->lock);
|
||||||
|
|
||||||
|
of_property_read_u32(np, "fsl,irq-groups", &data->irq_groups);
|
||||||
|
of_property_read_u32(np, "fsl,channel", &data->channel);
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_PM_SLEEP)) {
|
||||||
|
data->saved_reg = devm_kzalloc(&pdev->dev,
|
||||||
|
sizeof(u32) * data->irq_groups * 2,
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!data->saved_reg)
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(data->ipg_clk);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "failed to enable ipg clk: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* steer all IRQs into configured channel */
|
||||||
|
writel_relaxed(BIT(data->channel), data->regs + CHANCTRL);
|
||||||
|
|
||||||
|
data->domain = irq_domain_add_linear(np, data->irq_groups * 64,
|
||||||
|
&imx_irqsteer_domain_ops, data);
|
||||||
|
if (!data->domain) {
|
||||||
|
dev_err(&pdev->dev, "failed to create IRQ domain\n");
|
||||||
|
clk_disable_unprepare(data->ipg_clk);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
irq_set_chained_handler_and_data(data->irq, imx_irqsteer_irq_handler,
|
||||||
|
data);
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int imx_irqsteer_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct irqsteer_data *irqsteer_data = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
irq_set_chained_handler_and_data(irqsteer_data->irq, NULL, NULL);
|
||||||
|
irq_domain_remove(irqsteer_data->domain);
|
||||||
|
|
||||||
|
clk_disable_unprepare(irqsteer_data->ipg_clk);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_PM_SLEEP
|
||||||
|
static void imx_irqsteer_save_regs(struct irqsteer_data *data)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < data->irq_groups * 2; i++)
|
||||||
|
data->saved_reg[i] = readl_relaxed(data->regs +
|
||||||
|
CHANMASK(i, data->irq_groups));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void imx_irqsteer_restore_regs(struct irqsteer_data *data)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
writel_relaxed(BIT(data->channel), data->regs + CHANCTRL);
|
||||||
|
for (i = 0; i < data->irq_groups * 2; i++)
|
||||||
|
writel_relaxed(data->saved_reg[i],
|
||||||
|
data->regs + CHANMASK(i, data->irq_groups));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int imx_irqsteer_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
imx_irqsteer_save_regs(irqsteer_data);
|
||||||
|
clk_disable_unprepare(irqsteer_data->ipg_clk);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int imx_irqsteer_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(irqsteer_data->ipg_clk);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev, "failed to enable ipg clk: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
imx_irqsteer_restore_regs(irqsteer_data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const struct dev_pm_ops imx_irqsteer_pm_ops = {
|
||||||
|
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_irqsteer_suspend, imx_irqsteer_resume)
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct of_device_id imx_irqsteer_dt_ids[] = {
|
||||||
|
{ .compatible = "fsl,imx-irqsteer", },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct platform_driver imx_irqsteer_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "imx-irqsteer",
|
||||||
|
.of_match_table = imx_irqsteer_dt_ids,
|
||||||
|
.pm = &imx_irqsteer_pm_ops,
|
||||||
|
},
|
||||||
|
.probe = imx_irqsteer_probe,
|
||||||
|
.remove = imx_irqsteer_remove,
|
||||||
|
};
|
||||||
|
builtin_platform_driver(imx_irqsteer_driver);
|
256
drivers/irqchip/irq-madera.c
Normal file
256
drivers/irqchip/irq-madera.c
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Interrupt support for Cirrus Logic Madera codecs
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015-2018 Cirrus Logic, Inc. and
|
||||||
|
* Cirrus Logic International Semiconductor Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/gpio.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/irq.h>
|
||||||
|
#include <linux/irqdomain.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
|
#include <linux/of_gpio.h>
|
||||||
|
#include <linux/of_irq.h>
|
||||||
|
#include <linux/irqchip/irq-madera.h>
|
||||||
|
#include <linux/mfd/madera/core.h>
|
||||||
|
#include <linux/mfd/madera/pdata.h>
|
||||||
|
#include <linux/mfd/madera/registers.h>
|
||||||
|
|
||||||
|
#define MADERA_IRQ(_irq, _reg) \
|
||||||
|
[MADERA_IRQ_ ## _irq] = { \
|
||||||
|
.reg_offset = (_reg) - MADERA_IRQ1_STATUS_2, \
|
||||||
|
.mask = MADERA_ ## _irq ## _EINT1 \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mappings are the same for all Madera codecs */
|
||||||
|
static const struct regmap_irq madera_irqs[MADERA_NUM_IRQ] = {
|
||||||
|
MADERA_IRQ(FLL1_LOCK, MADERA_IRQ1_STATUS_2),
|
||||||
|
MADERA_IRQ(FLL2_LOCK, MADERA_IRQ1_STATUS_2),
|
||||||
|
MADERA_IRQ(FLL3_LOCK, MADERA_IRQ1_STATUS_2),
|
||||||
|
MADERA_IRQ(FLLAO_LOCK, MADERA_IRQ1_STATUS_2),
|
||||||
|
|
||||||
|
MADERA_IRQ(MICDET1, MADERA_IRQ1_STATUS_6),
|
||||||
|
MADERA_IRQ(MICDET2, MADERA_IRQ1_STATUS_6),
|
||||||
|
MADERA_IRQ(HPDET, MADERA_IRQ1_STATUS_6),
|
||||||
|
|
||||||
|
MADERA_IRQ(MICD_CLAMP_RISE, MADERA_IRQ1_STATUS_7),
|
||||||
|
MADERA_IRQ(MICD_CLAMP_FALL, MADERA_IRQ1_STATUS_7),
|
||||||
|
MADERA_IRQ(JD1_RISE, MADERA_IRQ1_STATUS_7),
|
||||||
|
MADERA_IRQ(JD1_FALL, MADERA_IRQ1_STATUS_7),
|
||||||
|
|
||||||
|
MADERA_IRQ(ASRC2_IN1_LOCK, MADERA_IRQ1_STATUS_9),
|
||||||
|
MADERA_IRQ(ASRC2_IN2_LOCK, MADERA_IRQ1_STATUS_9),
|
||||||
|
MADERA_IRQ(ASRC1_IN1_LOCK, MADERA_IRQ1_STATUS_9),
|
||||||
|
MADERA_IRQ(ASRC1_IN2_LOCK, MADERA_IRQ1_STATUS_9),
|
||||||
|
MADERA_IRQ(DRC2_SIG_DET, MADERA_IRQ1_STATUS_9),
|
||||||
|
MADERA_IRQ(DRC1_SIG_DET, MADERA_IRQ1_STATUS_9),
|
||||||
|
|
||||||
|
MADERA_IRQ(DSP_IRQ1, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ2, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ3, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ4, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ5, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ6, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ7, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ8, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ9, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ10, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ11, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ12, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ13, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ14, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ15, MADERA_IRQ1_STATUS_11),
|
||||||
|
MADERA_IRQ(DSP_IRQ16, MADERA_IRQ1_STATUS_11),
|
||||||
|
|
||||||
|
MADERA_IRQ(HP3R_SC, MADERA_IRQ1_STATUS_12),
|
||||||
|
MADERA_IRQ(HP3L_SC, MADERA_IRQ1_STATUS_12),
|
||||||
|
MADERA_IRQ(HP2R_SC, MADERA_IRQ1_STATUS_12),
|
||||||
|
MADERA_IRQ(HP2L_SC, MADERA_IRQ1_STATUS_12),
|
||||||
|
MADERA_IRQ(HP1R_SC, MADERA_IRQ1_STATUS_12),
|
||||||
|
MADERA_IRQ(HP1L_SC, MADERA_IRQ1_STATUS_12),
|
||||||
|
|
||||||
|
MADERA_IRQ(SPK_OVERHEAT_WARN, MADERA_IRQ1_STATUS_15),
|
||||||
|
MADERA_IRQ(SPK_OVERHEAT, MADERA_IRQ1_STATUS_15),
|
||||||
|
|
||||||
|
MADERA_IRQ(DSP1_BUS_ERR, MADERA_IRQ1_STATUS_33),
|
||||||
|
MADERA_IRQ(DSP2_BUS_ERR, MADERA_IRQ1_STATUS_33),
|
||||||
|
MADERA_IRQ(DSP3_BUS_ERR, MADERA_IRQ1_STATUS_33),
|
||||||
|
MADERA_IRQ(DSP4_BUS_ERR, MADERA_IRQ1_STATUS_33),
|
||||||
|
MADERA_IRQ(DSP5_BUS_ERR, MADERA_IRQ1_STATUS_33),
|
||||||
|
MADERA_IRQ(DSP6_BUS_ERR, MADERA_IRQ1_STATUS_33),
|
||||||
|
MADERA_IRQ(DSP7_BUS_ERR, MADERA_IRQ1_STATUS_33),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct regmap_irq_chip madera_irq_chip = {
|
||||||
|
.name = "madera IRQ",
|
||||||
|
.status_base = MADERA_IRQ1_STATUS_2,
|
||||||
|
.mask_base = MADERA_IRQ1_MASK_2,
|
||||||
|
.ack_base = MADERA_IRQ1_STATUS_2,
|
||||||
|
.runtime_pm = true,
|
||||||
|
.num_regs = 32,
|
||||||
|
.irqs = madera_irqs,
|
||||||
|
.num_irqs = ARRAY_SIZE(madera_irqs),
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef CONFIG_PM_SLEEP
|
||||||
|
static int madera_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct madera *madera = dev_get_drvdata(dev->parent);
|
||||||
|
|
||||||
|
dev_dbg(madera->irq_dev, "Suspend, disabling IRQ\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A runtime resume would be needed to access the chip interrupt
|
||||||
|
* controller but runtime pm doesn't function during suspend.
|
||||||
|
* Temporarily disable interrupts until we reach suspend_noirq state.
|
||||||
|
*/
|
||||||
|
disable_irq(madera->irq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int madera_suspend_noirq(struct device *dev)
|
||||||
|
{
|
||||||
|
struct madera *madera = dev_get_drvdata(dev->parent);
|
||||||
|
|
||||||
|
dev_dbg(madera->irq_dev, "No IRQ suspend, reenabling IRQ\n");
|
||||||
|
|
||||||
|
/* Re-enable interrupts to service wakeup interrupts from the chip */
|
||||||
|
enable_irq(madera->irq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int madera_resume_noirq(struct device *dev)
|
||||||
|
{
|
||||||
|
struct madera *madera = dev_get_drvdata(dev->parent);
|
||||||
|
|
||||||
|
dev_dbg(madera->irq_dev, "No IRQ resume, disabling IRQ\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We can't handle interrupts until runtime pm is available again.
|
||||||
|
* Disable them temporarily.
|
||||||
|
*/
|
||||||
|
disable_irq(madera->irq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int madera_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct madera *madera = dev_get_drvdata(dev->parent);
|
||||||
|
|
||||||
|
dev_dbg(madera->irq_dev, "Resume, reenabling IRQ\n");
|
||||||
|
|
||||||
|
/* Interrupts can now be handled */
|
||||||
|
enable_irq(madera->irq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const struct dev_pm_ops madera_irq_pm_ops = {
|
||||||
|
SET_SYSTEM_SLEEP_PM_OPS(madera_suspend, madera_resume)
|
||||||
|
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(madera_suspend_noirq,
|
||||||
|
madera_resume_noirq)
|
||||||
|
};
|
||||||
|
|
||||||
|
static int madera_irq_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct madera *madera = dev_get_drvdata(pdev->dev.parent);
|
||||||
|
struct irq_data *irq_data;
|
||||||
|
unsigned int irq_flags = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dev_dbg(&pdev->dev, "probe\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the flags from the interrupt controller if not specified
|
||||||
|
* by pdata
|
||||||
|
*/
|
||||||
|
irq_flags = madera->pdata.irq_flags;
|
||||||
|
if (!irq_flags) {
|
||||||
|
irq_data = irq_get_irq_data(madera->irq);
|
||||||
|
if (!irq_data) {
|
||||||
|
dev_err(&pdev->dev, "Invalid IRQ: %d\n", madera->irq);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
irq_flags = irqd_get_trigger_type(irq_data);
|
||||||
|
|
||||||
|
/* Codec defaults to trigger low, use this if no flags given */
|
||||||
|
if (irq_flags == IRQ_TYPE_NONE)
|
||||||
|
irq_flags = IRQF_TRIGGER_LOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) {
|
||||||
|
dev_err(&pdev->dev, "Host interrupt not level-triggered\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The silicon always starts at active-low, check if we need to
|
||||||
|
* switch to active-high.
|
||||||
|
*/
|
||||||
|
if (irq_flags & IRQF_TRIGGER_HIGH) {
|
||||||
|
ret = regmap_update_bits(madera->regmap, MADERA_IRQ1_CTRL,
|
||||||
|
MADERA_IRQ_POL_MASK, 0);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev,
|
||||||
|
"Failed to set IRQ polarity: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE: regmap registers this against the OF node of the parent of
|
||||||
|
* the regmap - that is, against the mfd driver
|
||||||
|
*/
|
||||||
|
ret = regmap_add_irq_chip(madera->regmap, madera->irq, IRQF_ONESHOT, 0,
|
||||||
|
&madera_irq_chip, &madera->irq_data);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(&pdev->dev, "add_irq_chip failed: %d\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save dev in parent MFD struct so it is accessible to siblings */
|
||||||
|
madera->irq_dev = &pdev->dev;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int madera_irq_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct madera *madera = dev_get_drvdata(pdev->dev.parent);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The IRQ is disabled by the parent MFD driver before
|
||||||
|
* it starts cleaning up all child drivers
|
||||||
|
*/
|
||||||
|
madera->irq_dev = NULL;
|
||||||
|
regmap_del_irq_chip(madera->irq, madera->irq_data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct platform_driver madera_irq_driver = {
|
||||||
|
.probe = &madera_irq_probe,
|
||||||
|
.remove = &madera_irq_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = "madera-irq",
|
||||||
|
.pm = &madera_irq_pm_ops,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
module_platform_driver(madera_irq_driver);
|
||||||
|
|
||||||
|
MODULE_SOFTDEP("pre: madera");
|
||||||
|
MODULE_DESCRIPTION("Madera IRQ driver");
|
||||||
|
MODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
@@ -72,7 +72,7 @@ static int __init ocelot_irq_init(struct device_node *node,
|
|||||||
domain = irq_domain_add_linear(node, OCELOT_NR_IRQ,
|
domain = irq_domain_add_linear(node, OCELOT_NR_IRQ,
|
||||||
&irq_generic_chip_ops, NULL);
|
&irq_generic_chip_ops, NULL);
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
pr_err("%s: unable to add irq domain\n", node->name);
|
pr_err("%pOFn: unable to add irq domain\n", node);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,14 +80,14 @@ static int __init ocelot_irq_init(struct device_node *node,
|
|||||||
"icpu", handle_level_irq,
|
"icpu", handle_level_irq,
|
||||||
0, 0, 0);
|
0, 0, 0);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
pr_err("%s: unable to alloc irq domain gc\n", node->name);
|
pr_err("%pOFn: unable to alloc irq domain gc\n", node);
|
||||||
goto err_domain_remove;
|
goto err_domain_remove;
|
||||||
}
|
}
|
||||||
|
|
||||||
gc = irq_get_domain_generic_chip(domain, 0);
|
gc = irq_get_domain_generic_chip(domain, 0);
|
||||||
gc->reg_base = of_iomap(node, 0);
|
gc->reg_base = of_iomap(node, 0);
|
||||||
if (!gc->reg_base) {
|
if (!gc->reg_base) {
|
||||||
pr_err("%s: unable to map resource\n", node->name);
|
pr_err("%pOFn: unable to map resource\n", node);
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
goto err_gc_free;
|
goto err_gc_free;
|
||||||
}
|
}
|
||||||
|
107
drivers/irqchip/irq-rda-intc.c
Normal file
107
drivers/irqchip/irq-rda-intc.c
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0+
|
||||||
|
/*
|
||||||
|
* RDA8810PL SoC irqchip driver
|
||||||
|
*
|
||||||
|
* Copyright RDA Microelectronics Company Limited
|
||||||
|
* Copyright (c) 2017 Andreas Färber
|
||||||
|
* Copyright (c) 2018 Manivannan Sadhasivam
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/irq.h>
|
||||||
|
#include <linux/irqchip.h>
|
||||||
|
#include <linux/irqdomain.h>
|
||||||
|
#include <linux/of_address.h>
|
||||||
|
|
||||||
|
#include <asm/exception.h>
|
||||||
|
|
||||||
|
#define RDA_INTC_FINALSTATUS 0x00
|
||||||
|
#define RDA_INTC_MASK_SET 0x08
|
||||||
|
#define RDA_INTC_MASK_CLR 0x0c
|
||||||
|
|
||||||
|
#define RDA_IRQ_MASK_ALL 0xFFFFFFFF
|
||||||
|
|
||||||
|
#define RDA_NR_IRQS 32
|
||||||
|
|
||||||
|
static void __iomem *rda_intc_base;
|
||||||
|
static struct irq_domain *rda_irq_domain;
|
||||||
|
|
||||||
|
static void rda_intc_mask_irq(struct irq_data *d)
|
||||||
|
{
|
||||||
|
writel_relaxed(BIT(d->hwirq), rda_intc_base + RDA_INTC_MASK_CLR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rda_intc_unmask_irq(struct irq_data *d)
|
||||||
|
{
|
||||||
|
writel_relaxed(BIT(d->hwirq), rda_intc_base + RDA_INTC_MASK_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rda_intc_set_type(struct irq_data *data, unsigned int flow_type)
|
||||||
|
{
|
||||||
|
/* Hardware supports only level triggered interrupts */
|
||||||
|
if ((flow_type & (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW)) == flow_type)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exception_irq_entry rda_handle_irq(struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
u32 stat = readl_relaxed(rda_intc_base + RDA_INTC_FINALSTATUS);
|
||||||
|
u32 hwirq;
|
||||||
|
|
||||||
|
while (stat) {
|
||||||
|
hwirq = __fls(stat);
|
||||||
|
handle_domain_irq(rda_irq_domain, hwirq, regs);
|
||||||
|
stat &= ~BIT(hwirq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct irq_chip rda_irq_chip = {
|
||||||
|
.name = "rda-intc",
|
||||||
|
.irq_mask = rda_intc_mask_irq,
|
||||||
|
.irq_unmask = rda_intc_unmask_irq,
|
||||||
|
.irq_set_type = rda_intc_set_type,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int rda_irq_map(struct irq_domain *d,
|
||||||
|
unsigned int virq, irq_hw_number_t hw)
|
||||||
|
{
|
||||||
|
irq_set_status_flags(virq, IRQ_LEVEL);
|
||||||
|
irq_set_chip_and_handler(virq, &rda_irq_chip, handle_level_irq);
|
||||||
|
irq_set_chip_data(virq, d->host_data);
|
||||||
|
irq_set_probe(virq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct irq_domain_ops rda_irq_domain_ops = {
|
||||||
|
.map = rda_irq_map,
|
||||||
|
.xlate = irq_domain_xlate_onecell,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init rda8810_intc_init(struct device_node *node,
|
||||||
|
struct device_node *parent)
|
||||||
|
{
|
||||||
|
rda_intc_base = of_io_request_and_map(node, 0, "rda-intc");
|
||||||
|
if (IS_ERR(rda_intc_base))
|
||||||
|
return PTR_ERR(rda_intc_base);
|
||||||
|
|
||||||
|
/* Mask all interrupt sources */
|
||||||
|
writel_relaxed(RDA_IRQ_MASK_ALL, rda_intc_base + RDA_INTC_MASK_CLR);
|
||||||
|
|
||||||
|
rda_irq_domain = irq_domain_create_linear(&node->fwnode, RDA_NR_IRQS,
|
||||||
|
&rda_irq_domain_ops,
|
||||||
|
rda_intc_base);
|
||||||
|
if (!rda_irq_domain) {
|
||||||
|
iounmap(rda_intc_base);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_handle_irq(rda_handle_irq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
IRQCHIP_DECLARE(rda_intc, "rda,8810pl-intc", rda8810_intc_init);
|
@@ -1,20 +1,8 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
/*
|
/*
|
||||||
* Renesas INTC External IRQ Pin Driver
|
* Renesas INTC External IRQ Pin Driver
|
||||||
*
|
*
|
||||||
* Copyright (C) 2013 Magnus Damm
|
* 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
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
|
@@ -1,20 +1,8 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
/*
|
/*
|
||||||
* Renesas IRQC Driver
|
* Renesas IRQC Driver
|
||||||
*
|
*
|
||||||
* Copyright (C) 2013 Magnus Damm
|
* 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
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
|
@@ -6,6 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/bitops.h>
|
#include <linux/bitops.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/hwspinlock.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include <linux/irq.h>
|
#include <linux/irq.h>
|
||||||
@@ -20,6 +22,9 @@
|
|||||||
|
|
||||||
#define IRQS_PER_BANK 32
|
#define IRQS_PER_BANK 32
|
||||||
|
|
||||||
|
#define HWSPNLCK_TIMEOUT 1000 /* usec */
|
||||||
|
#define HWSPNLCK_RETRY_DELAY 100 /* usec */
|
||||||
|
|
||||||
struct stm32_exti_bank {
|
struct stm32_exti_bank {
|
||||||
u32 imr_ofst;
|
u32 imr_ofst;
|
||||||
u32 emr_ofst;
|
u32 emr_ofst;
|
||||||
@@ -32,6 +37,12 @@ struct stm32_exti_bank {
|
|||||||
|
|
||||||
#define UNDEF_REG ~0
|
#define UNDEF_REG ~0
|
||||||
|
|
||||||
|
enum stm32_exti_hwspinlock {
|
||||||
|
HWSPINLOCK_UNKNOWN,
|
||||||
|
HWSPINLOCK_NONE,
|
||||||
|
HWSPINLOCK_READY,
|
||||||
|
};
|
||||||
|
|
||||||
struct stm32_desc_irq {
|
struct stm32_desc_irq {
|
||||||
u32 exti;
|
u32 exti;
|
||||||
u32 irq_parent;
|
u32 irq_parent;
|
||||||
@@ -58,6 +69,9 @@ struct stm32_exti_host_data {
|
|||||||
void __iomem *base;
|
void __iomem *base;
|
||||||
struct stm32_exti_chip_data *chips_data;
|
struct stm32_exti_chip_data *chips_data;
|
||||||
const struct stm32_exti_drv_data *drv_data;
|
const struct stm32_exti_drv_data *drv_data;
|
||||||
|
struct device_node *node;
|
||||||
|
enum stm32_exti_hwspinlock hwlock_state;
|
||||||
|
struct hwspinlock *hwlock;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct stm32_exti_host_data *stm32_host_data;
|
static struct stm32_exti_host_data *stm32_host_data;
|
||||||
@@ -269,6 +283,64 @@ static int stm32_exti_set_type(struct irq_data *d,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int stm32_exti_hwspin_lock(struct stm32_exti_chip_data *chip_data)
|
||||||
|
{
|
||||||
|
struct stm32_exti_host_data *host_data = chip_data->host_data;
|
||||||
|
struct hwspinlock *hwlock;
|
||||||
|
int id, ret = 0, timeout = 0;
|
||||||
|
|
||||||
|
/* first time, check for hwspinlock availability */
|
||||||
|
if (unlikely(host_data->hwlock_state == HWSPINLOCK_UNKNOWN)) {
|
||||||
|
id = of_hwspin_lock_get_id(host_data->node, 0);
|
||||||
|
if (id >= 0) {
|
||||||
|
hwlock = hwspin_lock_request_specific(id);
|
||||||
|
if (hwlock) {
|
||||||
|
/* found valid hwspinlock */
|
||||||
|
host_data->hwlock_state = HWSPINLOCK_READY;
|
||||||
|
host_data->hwlock = hwlock;
|
||||||
|
pr_debug("%s hwspinlock = %d\n", __func__, id);
|
||||||
|
} else {
|
||||||
|
host_data->hwlock_state = HWSPINLOCK_NONE;
|
||||||
|
}
|
||||||
|
} else if (id != -EPROBE_DEFER) {
|
||||||
|
host_data->hwlock_state = HWSPINLOCK_NONE;
|
||||||
|
} else {
|
||||||
|
/* hwspinlock driver shall be ready at that stage */
|
||||||
|
ret = -EPROBE_DEFER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (likely(host_data->hwlock_state == HWSPINLOCK_READY)) {
|
||||||
|
/*
|
||||||
|
* Use the x_raw API since we are under spin_lock protection.
|
||||||
|
* Do not use the x_timeout API because we are under irq_disable
|
||||||
|
* mode (see __setup_irq())
|
||||||
|
*/
|
||||||
|
do {
|
||||||
|
ret = hwspin_trylock_raw(host_data->hwlock);
|
||||||
|
if (!ret)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
udelay(HWSPNLCK_RETRY_DELAY);
|
||||||
|
timeout += HWSPNLCK_RETRY_DELAY;
|
||||||
|
} while (timeout < HWSPNLCK_TIMEOUT);
|
||||||
|
|
||||||
|
if (ret == -EBUSY)
|
||||||
|
ret = -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
pr_err("%s can't get hwspinlock (%d)\n", __func__, ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stm32_exti_hwspin_unlock(struct stm32_exti_chip_data *chip_data)
|
||||||
|
{
|
||||||
|
if (likely(chip_data->host_data->hwlock_state == HWSPINLOCK_READY))
|
||||||
|
hwspin_unlock_raw(chip_data->host_data->hwlock);
|
||||||
|
}
|
||||||
|
|
||||||
static int stm32_irq_set_type(struct irq_data *d, unsigned int type)
|
static int stm32_irq_set_type(struct irq_data *d, unsigned int type)
|
||||||
{
|
{
|
||||||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
||||||
@@ -279,21 +351,26 @@ static int stm32_irq_set_type(struct irq_data *d, unsigned int type)
|
|||||||
|
|
||||||
irq_gc_lock(gc);
|
irq_gc_lock(gc);
|
||||||
|
|
||||||
|
err = stm32_exti_hwspin_lock(chip_data);
|
||||||
|
if (err)
|
||||||
|
goto unlock;
|
||||||
|
|
||||||
rtsr = irq_reg_readl(gc, stm32_bank->rtsr_ofst);
|
rtsr = irq_reg_readl(gc, stm32_bank->rtsr_ofst);
|
||||||
ftsr = irq_reg_readl(gc, stm32_bank->ftsr_ofst);
|
ftsr = irq_reg_readl(gc, stm32_bank->ftsr_ofst);
|
||||||
|
|
||||||
err = stm32_exti_set_type(d, type, &rtsr, &ftsr);
|
err = stm32_exti_set_type(d, type, &rtsr, &ftsr);
|
||||||
if (err) {
|
if (err)
|
||||||
irq_gc_unlock(gc);
|
goto unspinlock;
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
irq_reg_writel(gc, rtsr, stm32_bank->rtsr_ofst);
|
irq_reg_writel(gc, rtsr, stm32_bank->rtsr_ofst);
|
||||||
irq_reg_writel(gc, ftsr, stm32_bank->ftsr_ofst);
|
irq_reg_writel(gc, ftsr, stm32_bank->ftsr_ofst);
|
||||||
|
|
||||||
|
unspinlock:
|
||||||
|
stm32_exti_hwspin_unlock(chip_data);
|
||||||
|
unlock:
|
||||||
irq_gc_unlock(gc);
|
irq_gc_unlock(gc);
|
||||||
|
|
||||||
return 0;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stm32_chip_suspend(struct stm32_exti_chip_data *chip_data,
|
static void stm32_chip_suspend(struct stm32_exti_chip_data *chip_data,
|
||||||
@@ -460,20 +537,27 @@ static int stm32_exti_h_set_type(struct irq_data *d, unsigned int type)
|
|||||||
int err;
|
int err;
|
||||||
|
|
||||||
raw_spin_lock(&chip_data->rlock);
|
raw_spin_lock(&chip_data->rlock);
|
||||||
|
|
||||||
|
err = stm32_exti_hwspin_lock(chip_data);
|
||||||
|
if (err)
|
||||||
|
goto unlock;
|
||||||
|
|
||||||
rtsr = readl_relaxed(base + stm32_bank->rtsr_ofst);
|
rtsr = readl_relaxed(base + stm32_bank->rtsr_ofst);
|
||||||
ftsr = readl_relaxed(base + stm32_bank->ftsr_ofst);
|
ftsr = readl_relaxed(base + stm32_bank->ftsr_ofst);
|
||||||
|
|
||||||
err = stm32_exti_set_type(d, type, &rtsr, &ftsr);
|
err = stm32_exti_set_type(d, type, &rtsr, &ftsr);
|
||||||
if (err) {
|
if (err)
|
||||||
raw_spin_unlock(&chip_data->rlock);
|
goto unspinlock;
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
writel_relaxed(rtsr, base + stm32_bank->rtsr_ofst);
|
writel_relaxed(rtsr, base + stm32_bank->rtsr_ofst);
|
||||||
writel_relaxed(ftsr, base + stm32_bank->ftsr_ofst);
|
writel_relaxed(ftsr, base + stm32_bank->ftsr_ofst);
|
||||||
|
|
||||||
|
unspinlock:
|
||||||
|
stm32_exti_hwspin_unlock(chip_data);
|
||||||
|
unlock:
|
||||||
raw_spin_unlock(&chip_data->rlock);
|
raw_spin_unlock(&chip_data->rlock);
|
||||||
|
|
||||||
return 0;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int stm32_exti_h_set_wake(struct irq_data *d, unsigned int on)
|
static int stm32_exti_h_set_wake(struct irq_data *d, unsigned int on)
|
||||||
@@ -599,6 +683,8 @@ stm32_exti_host_data *stm32_exti_host_init(const struct stm32_exti_drv_data *dd,
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
host_data->drv_data = dd;
|
host_data->drv_data = dd;
|
||||||
|
host_data->node = node;
|
||||||
|
host_data->hwlock_state = HWSPINLOCK_UNKNOWN;
|
||||||
host_data->chips_data = kcalloc(dd->bank_nr,
|
host_data->chips_data = kcalloc(dd->bank_nr,
|
||||||
sizeof(struct stm32_exti_chip_data),
|
sizeof(struct stm32_exti_chip_data),
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
@@ -625,8 +711,7 @@ free_host_data:
|
|||||||
|
|
||||||
static struct
|
static struct
|
||||||
stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data,
|
stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data,
|
||||||
u32 bank_idx,
|
u32 bank_idx)
|
||||||
struct device_node *node)
|
|
||||||
{
|
{
|
||||||
const struct stm32_exti_bank *stm32_bank;
|
const struct stm32_exti_bank *stm32_bank;
|
||||||
struct stm32_exti_chip_data *chip_data;
|
struct stm32_exti_chip_data *chip_data;
|
||||||
@@ -656,8 +741,7 @@ stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data,
|
|||||||
if (stm32_bank->fpr_ofst != UNDEF_REG)
|
if (stm32_bank->fpr_ofst != UNDEF_REG)
|
||||||
writel_relaxed(~0UL, base + stm32_bank->fpr_ofst);
|
writel_relaxed(~0UL, base + stm32_bank->fpr_ofst);
|
||||||
|
|
||||||
pr_info("%s: bank%d, External IRQs available:%#x\n",
|
pr_info("%pOF: bank%d\n", h_data->node, bank_idx);
|
||||||
node->full_name, bank_idx, irqs_mask);
|
|
||||||
|
|
||||||
return chip_data;
|
return chip_data;
|
||||||
}
|
}
|
||||||
@@ -678,8 +762,8 @@ static int __init stm32_exti_init(const struct stm32_exti_drv_data *drv_data,
|
|||||||
domain = irq_domain_add_linear(node, drv_data->bank_nr * IRQS_PER_BANK,
|
domain = irq_domain_add_linear(node, drv_data->bank_nr * IRQS_PER_BANK,
|
||||||
&irq_exti_domain_ops, NULL);
|
&irq_exti_domain_ops, NULL);
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
pr_err("%s: Could not register interrupt domain.\n",
|
pr_err("%pOFn: Could not register interrupt domain.\n",
|
||||||
node->name);
|
node);
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
goto out_unmap;
|
goto out_unmap;
|
||||||
}
|
}
|
||||||
@@ -697,7 +781,7 @@ static int __init stm32_exti_init(const struct stm32_exti_drv_data *drv_data,
|
|||||||
struct stm32_exti_chip_data *chip_data;
|
struct stm32_exti_chip_data *chip_data;
|
||||||
|
|
||||||
stm32_bank = drv_data->exti_banks[i];
|
stm32_bank = drv_data->exti_banks[i];
|
||||||
chip_data = stm32_exti_chip_init(host_data, i, node);
|
chip_data = stm32_exti_chip_init(host_data, i);
|
||||||
|
|
||||||
gc = irq_get_domain_generic_chip(domain, i * IRQS_PER_BANK);
|
gc = irq_get_domain_generic_chip(domain, i * IRQS_PER_BANK);
|
||||||
|
|
||||||
@@ -760,7 +844,7 @@ __init stm32_exti_hierarchy_init(const struct stm32_exti_drv_data *drv_data,
|
|||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
for (i = 0; i < drv_data->bank_nr; i++)
|
for (i = 0; i < drv_data->bank_nr; i++)
|
||||||
stm32_exti_chip_init(host_data, i, node);
|
stm32_exti_chip_init(host_data, i);
|
||||||
|
|
||||||
domain = irq_domain_add_hierarchy(parent_domain, 0,
|
domain = irq_domain_add_hierarchy(parent_domain, 0,
|
||||||
drv_data->bank_nr * IRQS_PER_BANK,
|
drv_data->bank_nr * IRQS_PER_BANK,
|
||||||
@@ -768,7 +852,7 @@ __init stm32_exti_hierarchy_init(const struct stm32_exti_drv_data *drv_data,
|
|||||||
host_data);
|
host_data);
|
||||||
|
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
pr_err("%s: Could not register exti domain.\n", node->name);
|
pr_err("%pOFn: Could not register exti domain.\n", node);
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
goto out_unmap;
|
goto out_unmap;
|
||||||
}
|
}
|
||||||
|
@@ -28,11 +28,21 @@
|
|||||||
#define SUN4I_IRQ_NMI_CTRL_REG 0x0c
|
#define SUN4I_IRQ_NMI_CTRL_REG 0x0c
|
||||||
#define SUN4I_IRQ_PENDING_REG(x) (0x10 + 0x4 * x)
|
#define SUN4I_IRQ_PENDING_REG(x) (0x10 + 0x4 * x)
|
||||||
#define SUN4I_IRQ_FIQ_PENDING_REG(x) (0x20 + 0x4 * x)
|
#define SUN4I_IRQ_FIQ_PENDING_REG(x) (0x20 + 0x4 * x)
|
||||||
#define SUN4I_IRQ_ENABLE_REG(x) (0x40 + 0x4 * x)
|
#define SUN4I_IRQ_ENABLE_REG(data, x) ((data)->enable_reg_offset + 0x4 * x)
|
||||||
#define SUN4I_IRQ_MASK_REG(x) (0x50 + 0x4 * x)
|
#define SUN4I_IRQ_MASK_REG(data, x) ((data)->mask_reg_offset + 0x4 * x)
|
||||||
|
#define SUN4I_IRQ_ENABLE_REG_OFFSET 0x40
|
||||||
|
#define SUN4I_IRQ_MASK_REG_OFFSET 0x50
|
||||||
|
#define SUNIV_IRQ_ENABLE_REG_OFFSET 0x20
|
||||||
|
#define SUNIV_IRQ_MASK_REG_OFFSET 0x30
|
||||||
|
|
||||||
static void __iomem *sun4i_irq_base;
|
struct sun4i_irq_chip_data {
|
||||||
static struct irq_domain *sun4i_irq_domain;
|
void __iomem *irq_base;
|
||||||
|
struct irq_domain *irq_domain;
|
||||||
|
u32 enable_reg_offset;
|
||||||
|
u32 mask_reg_offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct sun4i_irq_chip_data *irq_ic_data;
|
||||||
|
|
||||||
static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs);
|
static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs);
|
||||||
|
|
||||||
@@ -43,7 +53,7 @@ static void sun4i_irq_ack(struct irq_data *irqd)
|
|||||||
if (irq != 0)
|
if (irq != 0)
|
||||||
return; /* Only IRQ 0 / the ENMI needs to be acked */
|
return; /* Only IRQ 0 / the ENMI needs to be acked */
|
||||||
|
|
||||||
writel(BIT(0), sun4i_irq_base + SUN4I_IRQ_PENDING_REG(0));
|
writel(BIT(0), irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sun4i_irq_mask(struct irq_data *irqd)
|
static void sun4i_irq_mask(struct irq_data *irqd)
|
||||||
@@ -53,9 +63,10 @@ static void sun4i_irq_mask(struct irq_data *irqd)
|
|||||||
int reg = irq / 32;
|
int reg = irq / 32;
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
val = readl(sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(reg));
|
val = readl(irq_ic_data->irq_base +
|
||||||
|
SUN4I_IRQ_ENABLE_REG(irq_ic_data, reg));
|
||||||
writel(val & ~(1 << irq_off),
|
writel(val & ~(1 << irq_off),
|
||||||
sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(reg));
|
irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, reg));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sun4i_irq_unmask(struct irq_data *irqd)
|
static void sun4i_irq_unmask(struct irq_data *irqd)
|
||||||
@@ -65,9 +76,10 @@ static void sun4i_irq_unmask(struct irq_data *irqd)
|
|||||||
int reg = irq / 32;
|
int reg = irq / 32;
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
val = readl(sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(reg));
|
val = readl(irq_ic_data->irq_base +
|
||||||
|
SUN4I_IRQ_ENABLE_REG(irq_ic_data, reg));
|
||||||
writel(val | (1 << irq_off),
|
writel(val | (1 << irq_off),
|
||||||
sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(reg));
|
irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, reg));
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct irq_chip sun4i_irq_chip = {
|
static struct irq_chip sun4i_irq_chip = {
|
||||||
@@ -95,42 +107,76 @@ static const struct irq_domain_ops sun4i_irq_ops = {
|
|||||||
static int __init sun4i_of_init(struct device_node *node,
|
static int __init sun4i_of_init(struct device_node *node,
|
||||||
struct device_node *parent)
|
struct device_node *parent)
|
||||||
{
|
{
|
||||||
sun4i_irq_base = of_iomap(node, 0);
|
irq_ic_data->irq_base = of_iomap(node, 0);
|
||||||
if (!sun4i_irq_base)
|
if (!irq_ic_data->irq_base)
|
||||||
panic("%pOF: unable to map IC registers\n",
|
panic("%pOF: unable to map IC registers\n",
|
||||||
node);
|
node);
|
||||||
|
|
||||||
/* Disable all interrupts */
|
/* Disable all interrupts */
|
||||||
writel(0, sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(0));
|
writel(0, irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, 0));
|
||||||
writel(0, sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(1));
|
writel(0, irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, 1));
|
||||||
writel(0, sun4i_irq_base + SUN4I_IRQ_ENABLE_REG(2));
|
writel(0, irq_ic_data->irq_base + SUN4I_IRQ_ENABLE_REG(irq_ic_data, 2));
|
||||||
|
|
||||||
/* Unmask all the interrupts, ENABLE_REG(x) is used for masking */
|
/* Unmask all the interrupts, ENABLE_REG(x) is used for masking */
|
||||||
writel(0, sun4i_irq_base + SUN4I_IRQ_MASK_REG(0));
|
writel(0, irq_ic_data->irq_base + SUN4I_IRQ_MASK_REG(irq_ic_data, 0));
|
||||||
writel(0, sun4i_irq_base + SUN4I_IRQ_MASK_REG(1));
|
writel(0, irq_ic_data->irq_base + SUN4I_IRQ_MASK_REG(irq_ic_data, 1));
|
||||||
writel(0, sun4i_irq_base + SUN4I_IRQ_MASK_REG(2));
|
writel(0, irq_ic_data->irq_base + SUN4I_IRQ_MASK_REG(irq_ic_data, 2));
|
||||||
|
|
||||||
/* Clear all the pending interrupts */
|
/* Clear all the pending interrupts */
|
||||||
writel(0xffffffff, sun4i_irq_base + SUN4I_IRQ_PENDING_REG(0));
|
writel(0xffffffff, irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(0));
|
||||||
writel(0xffffffff, sun4i_irq_base + SUN4I_IRQ_PENDING_REG(1));
|
writel(0xffffffff, irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(1));
|
||||||
writel(0xffffffff, sun4i_irq_base + SUN4I_IRQ_PENDING_REG(2));
|
writel(0xffffffff, irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(2));
|
||||||
|
|
||||||
/* Enable protection mode */
|
/* Enable protection mode */
|
||||||
writel(0x01, sun4i_irq_base + SUN4I_IRQ_PROTECTION_REG);
|
writel(0x01, irq_ic_data->irq_base + SUN4I_IRQ_PROTECTION_REG);
|
||||||
|
|
||||||
/* Configure the external interrupt source type */
|
/* Configure the external interrupt source type */
|
||||||
writel(0x00, sun4i_irq_base + SUN4I_IRQ_NMI_CTRL_REG);
|
writel(0x00, irq_ic_data->irq_base + SUN4I_IRQ_NMI_CTRL_REG);
|
||||||
|
|
||||||
sun4i_irq_domain = irq_domain_add_linear(node, 3 * 32,
|
irq_ic_data->irq_domain = irq_domain_add_linear(node, 3 * 32,
|
||||||
&sun4i_irq_ops, NULL);
|
&sun4i_irq_ops, NULL);
|
||||||
if (!sun4i_irq_domain)
|
if (!irq_ic_data->irq_domain)
|
||||||
panic("%pOF: unable to create IRQ domain\n", node);
|
panic("%pOF: unable to create IRQ domain\n", node);
|
||||||
|
|
||||||
set_handle_irq(sun4i_handle_irq);
|
set_handle_irq(sun4i_handle_irq);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
IRQCHIP_DECLARE(allwinner_sun4i_ic, "allwinner,sun4i-a10-ic", sun4i_of_init);
|
|
||||||
|
static int __init sun4i_ic_of_init(struct device_node *node,
|
||||||
|
struct device_node *parent)
|
||||||
|
{
|
||||||
|
irq_ic_data = kzalloc(sizeof(struct sun4i_irq_chip_data), GFP_KERNEL);
|
||||||
|
if (!irq_ic_data) {
|
||||||
|
pr_err("kzalloc failed!\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
irq_ic_data->enable_reg_offset = SUN4I_IRQ_ENABLE_REG_OFFSET;
|
||||||
|
irq_ic_data->mask_reg_offset = SUN4I_IRQ_MASK_REG_OFFSET;
|
||||||
|
|
||||||
|
return sun4i_of_init(node, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
IRQCHIP_DECLARE(allwinner_sun4i_ic, "allwinner,sun4i-a10-ic", sun4i_ic_of_init);
|
||||||
|
|
||||||
|
static int __init suniv_ic_of_init(struct device_node *node,
|
||||||
|
struct device_node *parent)
|
||||||
|
{
|
||||||
|
irq_ic_data = kzalloc(sizeof(struct sun4i_irq_chip_data), GFP_KERNEL);
|
||||||
|
if (!irq_ic_data) {
|
||||||
|
pr_err("kzalloc failed!\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
irq_ic_data->enable_reg_offset = SUNIV_IRQ_ENABLE_REG_OFFSET;
|
||||||
|
irq_ic_data->mask_reg_offset = SUNIV_IRQ_MASK_REG_OFFSET;
|
||||||
|
|
||||||
|
return sun4i_of_init(node, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
IRQCHIP_DECLARE(allwinner_sunvi_ic, "allwinner,suniv-f1c100s-ic",
|
||||||
|
suniv_ic_of_init);
|
||||||
|
|
||||||
static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs)
|
static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
@@ -146,13 +192,15 @@ static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs)
|
|||||||
* the extra check in the common case of 1 hapening after having
|
* the extra check in the common case of 1 hapening after having
|
||||||
* read the vector-reg once.
|
* read the vector-reg once.
|
||||||
*/
|
*/
|
||||||
hwirq = readl(sun4i_irq_base + SUN4I_IRQ_VECTOR_REG) >> 2;
|
hwirq = readl(irq_ic_data->irq_base + SUN4I_IRQ_VECTOR_REG) >> 2;
|
||||||
if (hwirq == 0 &&
|
if (hwirq == 0 &&
|
||||||
!(readl(sun4i_irq_base + SUN4I_IRQ_PENDING_REG(0)) & BIT(0)))
|
!(readl(irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(0)) &
|
||||||
|
BIT(0)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
handle_domain_irq(sun4i_irq_domain, hwirq, regs);
|
handle_domain_irq(irq_ic_data->irq_domain, hwirq, regs);
|
||||||
hwirq = readl(sun4i_irq_base + SUN4I_IRQ_VECTOR_REG) >> 2;
|
hwirq = readl(irq_ic_data->irq_base +
|
||||||
|
SUN4I_IRQ_VECTOR_REG) >> 2;
|
||||||
} while (hwirq != 0);
|
} while (hwirq != 0);
|
||||||
}
|
}
|
||||||
|
@@ -184,11 +184,11 @@ static int __init tangox_irq_init(void __iomem *base, struct resource *baseres,
|
|||||||
|
|
||||||
irq = irq_of_parse_and_map(node, 0);
|
irq = irq_of_parse_and_map(node, 0);
|
||||||
if (!irq)
|
if (!irq)
|
||||||
panic("%s: failed to get IRQ", node->name);
|
panic("%pOFn: failed to get IRQ", node);
|
||||||
|
|
||||||
err = of_address_to_resource(node, 0, &res);
|
err = of_address_to_resource(node, 0, &res);
|
||||||
if (err)
|
if (err)
|
||||||
panic("%s: failed to get address", node->name);
|
panic("%pOFn: failed to get address", node);
|
||||||
|
|
||||||
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
|
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
|
||||||
chip->ctl = res.start - baseres->start;
|
chip->ctl = res.start - baseres->start;
|
||||||
@@ -196,12 +196,12 @@ static int __init tangox_irq_init(void __iomem *base, struct resource *baseres,
|
|||||||
|
|
||||||
dom = irq_domain_add_linear(node, 64, &irq_generic_chip_ops, chip);
|
dom = irq_domain_add_linear(node, 64, &irq_generic_chip_ops, chip);
|
||||||
if (!dom)
|
if (!dom)
|
||||||
panic("%s: failed to create irqdomain", node->name);
|
panic("%pOFn: failed to create irqdomain", node);
|
||||||
|
|
||||||
err = irq_alloc_domain_generic_chips(dom, 32, 2, node->name,
|
err = irq_alloc_domain_generic_chips(dom, 32, 2, node->name,
|
||||||
handle_level_irq, 0, 0, 0);
|
handle_level_irq, 0, 0, 0);
|
||||||
if (err)
|
if (err)
|
||||||
panic("%s: failed to allocate irqchip", node->name);
|
panic("%pOFn: failed to allocate irqchip", node);
|
||||||
|
|
||||||
tangox_irq_domain_init(dom);
|
tangox_irq_domain_init(dom);
|
||||||
|
|
||||||
@@ -219,7 +219,7 @@ static int __init tangox_of_irq_init(struct device_node *node,
|
|||||||
|
|
||||||
base = of_iomap(node, 0);
|
base = of_iomap(node, 0);
|
||||||
if (!base)
|
if (!base)
|
||||||
panic("%s: of_iomap failed", node->name);
|
panic("%pOFn: of_iomap failed", node);
|
||||||
|
|
||||||
of_address_to_resource(node, 0, &res);
|
of_address_to_resource(node, 0, &res);
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
struct irq_sim_work_ctx {
|
struct irq_sim_work_ctx {
|
||||||
struct irq_work work;
|
struct irq_work work;
|
||||||
int irq;
|
unsigned long *pending;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct irq_sim_irq_ctx {
|
struct irq_sim_irq_ctx {
|
||||||
|
132
include/linux/irqchip/irq-madera.h
Normal file
132
include/linux/irqchip/irq-madera.h
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* Interrupt support for Cirrus Logic Madera codecs
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016-2018 Cirrus Logic, Inc. and
|
||||||
|
* Cirrus Logic International Semiconductor Ltd.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef IRQCHIP_MADERA_H
|
||||||
|
#define IRQCHIP_MADERA_H
|
||||||
|
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/mfd/madera/core.h>
|
||||||
|
|
||||||
|
#define MADERA_IRQ_FLL1_LOCK 0
|
||||||
|
#define MADERA_IRQ_FLL2_LOCK 1
|
||||||
|
#define MADERA_IRQ_FLL3_LOCK 2
|
||||||
|
#define MADERA_IRQ_FLLAO_LOCK 3
|
||||||
|
#define MADERA_IRQ_CLK_SYS_ERR 4
|
||||||
|
#define MADERA_IRQ_CLK_ASYNC_ERR 5
|
||||||
|
#define MADERA_IRQ_CLK_DSP_ERR 6
|
||||||
|
#define MADERA_IRQ_HPDET 7
|
||||||
|
#define MADERA_IRQ_MICDET1 8
|
||||||
|
#define MADERA_IRQ_MICDET2 9
|
||||||
|
#define MADERA_IRQ_JD1_RISE 10
|
||||||
|
#define MADERA_IRQ_JD1_FALL 11
|
||||||
|
#define MADERA_IRQ_JD2_RISE 12
|
||||||
|
#define MADERA_IRQ_JD2_FALL 13
|
||||||
|
#define MADERA_IRQ_MICD_CLAMP_RISE 14
|
||||||
|
#define MADERA_IRQ_MICD_CLAMP_FALL 15
|
||||||
|
#define MADERA_IRQ_DRC2_SIG_DET 16
|
||||||
|
#define MADERA_IRQ_DRC1_SIG_DET 17
|
||||||
|
#define MADERA_IRQ_ASRC1_IN1_LOCK 18
|
||||||
|
#define MADERA_IRQ_ASRC1_IN2_LOCK 19
|
||||||
|
#define MADERA_IRQ_ASRC2_IN1_LOCK 20
|
||||||
|
#define MADERA_IRQ_ASRC2_IN2_LOCK 21
|
||||||
|
#define MADERA_IRQ_DSP_IRQ1 22
|
||||||
|
#define MADERA_IRQ_DSP_IRQ2 23
|
||||||
|
#define MADERA_IRQ_DSP_IRQ3 24
|
||||||
|
#define MADERA_IRQ_DSP_IRQ4 25
|
||||||
|
#define MADERA_IRQ_DSP_IRQ5 26
|
||||||
|
#define MADERA_IRQ_DSP_IRQ6 27
|
||||||
|
#define MADERA_IRQ_DSP_IRQ7 28
|
||||||
|
#define MADERA_IRQ_DSP_IRQ8 29
|
||||||
|
#define MADERA_IRQ_DSP_IRQ9 30
|
||||||
|
#define MADERA_IRQ_DSP_IRQ10 31
|
||||||
|
#define MADERA_IRQ_DSP_IRQ11 32
|
||||||
|
#define MADERA_IRQ_DSP_IRQ12 33
|
||||||
|
#define MADERA_IRQ_DSP_IRQ13 34
|
||||||
|
#define MADERA_IRQ_DSP_IRQ14 35
|
||||||
|
#define MADERA_IRQ_DSP_IRQ15 36
|
||||||
|
#define MADERA_IRQ_DSP_IRQ16 37
|
||||||
|
#define MADERA_IRQ_HP1L_SC 38
|
||||||
|
#define MADERA_IRQ_HP1R_SC 39
|
||||||
|
#define MADERA_IRQ_HP2L_SC 40
|
||||||
|
#define MADERA_IRQ_HP2R_SC 41
|
||||||
|
#define MADERA_IRQ_HP3L_SC 42
|
||||||
|
#define MADERA_IRQ_HP3R_SC 43
|
||||||
|
#define MADERA_IRQ_SPKOUTL_SC 44
|
||||||
|
#define MADERA_IRQ_SPKOUTR_SC 45
|
||||||
|
#define MADERA_IRQ_HP1L_ENABLE_DONE 46
|
||||||
|
#define MADERA_IRQ_HP1R_ENABLE_DONE 47
|
||||||
|
#define MADERA_IRQ_HP2L_ENABLE_DONE 48
|
||||||
|
#define MADERA_IRQ_HP2R_ENABLE_DONE 49
|
||||||
|
#define MADERA_IRQ_HP3L_ENABLE_DONE 50
|
||||||
|
#define MADERA_IRQ_HP3R_ENABLE_DONE 51
|
||||||
|
#define MADERA_IRQ_SPKOUTL_ENABLE_DONE 52
|
||||||
|
#define MADERA_IRQ_SPKOUTR_ENABLE_DONE 53
|
||||||
|
#define MADERA_IRQ_SPK_SHUTDOWN 54
|
||||||
|
#define MADERA_IRQ_SPK_OVERHEAT 55
|
||||||
|
#define MADERA_IRQ_SPK_OVERHEAT_WARN 56
|
||||||
|
#define MADERA_IRQ_GPIO1 57
|
||||||
|
#define MADERA_IRQ_GPIO2 58
|
||||||
|
#define MADERA_IRQ_GPIO3 59
|
||||||
|
#define MADERA_IRQ_GPIO4 60
|
||||||
|
#define MADERA_IRQ_GPIO5 61
|
||||||
|
#define MADERA_IRQ_GPIO6 62
|
||||||
|
#define MADERA_IRQ_GPIO7 63
|
||||||
|
#define MADERA_IRQ_GPIO8 64
|
||||||
|
#define MADERA_IRQ_DSP1_BUS_ERR 65
|
||||||
|
#define MADERA_IRQ_DSP2_BUS_ERR 66
|
||||||
|
#define MADERA_IRQ_DSP3_BUS_ERR 67
|
||||||
|
#define MADERA_IRQ_DSP4_BUS_ERR 68
|
||||||
|
#define MADERA_IRQ_DSP5_BUS_ERR 69
|
||||||
|
#define MADERA_IRQ_DSP6_BUS_ERR 70
|
||||||
|
#define MADERA_IRQ_DSP7_BUS_ERR 71
|
||||||
|
|
||||||
|
#define MADERA_NUM_IRQ 72
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These wrapper functions are for use by other child drivers of the
|
||||||
|
* same parent MFD.
|
||||||
|
*/
|
||||||
|
static inline int madera_get_irq_mapping(struct madera *madera, int irq)
|
||||||
|
{
|
||||||
|
if (!madera->irq_dev)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
return regmap_irq_get_virq(madera->irq_data, irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int madera_request_irq(struct madera *madera, int irq,
|
||||||
|
const char *name,
|
||||||
|
irq_handler_t handler, void *data)
|
||||||
|
{
|
||||||
|
irq = madera_get_irq_mapping(madera, irq);
|
||||||
|
if (irq < 0)
|
||||||
|
return irq;
|
||||||
|
|
||||||
|
return request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT, name,
|
||||||
|
data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void madera_free_irq(struct madera *madera, int irq, void *data)
|
||||||
|
{
|
||||||
|
irq = madera_get_irq_mapping(madera, irq);
|
||||||
|
if (irq < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
free_irq(irq, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int madera_set_irq_wake(struct madera *madera, int irq, int on)
|
||||||
|
{
|
||||||
|
irq = madera_get_irq_mapping(madera, irq);
|
||||||
|
if (irq < 0)
|
||||||
|
return irq;
|
||||||
|
|
||||||
|
return irq_set_irq_wake(irq, on);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@@ -116,6 +116,8 @@ struct msi_desc {
|
|||||||
list_first_entry(dev_to_msi_list((dev)), struct msi_desc, list)
|
list_first_entry(dev_to_msi_list((dev)), struct msi_desc, list)
|
||||||
#define for_each_msi_entry(desc, dev) \
|
#define for_each_msi_entry(desc, dev) \
|
||||||
list_for_each_entry((desc), dev_to_msi_list((dev)), list)
|
list_for_each_entry((desc), dev_to_msi_list((dev)), list)
|
||||||
|
#define for_each_msi_entry_safe(desc, tmp, dev) \
|
||||||
|
list_for_each_entry_safe((desc), (tmp), dev_to_msi_list((dev)), list)
|
||||||
|
|
||||||
#ifdef CONFIG_PCI_MSI
|
#ifdef CONFIG_PCI_MSI
|
||||||
#define first_pci_msi_entry(pdev) first_msi_entry(&(pdev)->dev)
|
#define first_pci_msi_entry(pdev) first_msi_entry(&(pdev)->dev)
|
||||||
|
@@ -34,9 +34,20 @@ static struct irq_chip irq_sim_irqchip = {
|
|||||||
static void irq_sim_handle_irq(struct irq_work *work)
|
static void irq_sim_handle_irq(struct irq_work *work)
|
||||||
{
|
{
|
||||||
struct irq_sim_work_ctx *work_ctx;
|
struct irq_sim_work_ctx *work_ctx;
|
||||||
|
unsigned int offset = 0;
|
||||||
|
struct irq_sim *sim;
|
||||||
|
int irqnum;
|
||||||
|
|
||||||
work_ctx = container_of(work, struct irq_sim_work_ctx, work);
|
work_ctx = container_of(work, struct irq_sim_work_ctx, work);
|
||||||
handle_simple_irq(irq_to_desc(work_ctx->irq));
|
sim = container_of(work_ctx, struct irq_sim, work_ctx);
|
||||||
|
|
||||||
|
while (!bitmap_empty(work_ctx->pending, sim->irq_count)) {
|
||||||
|
offset = find_next_bit(work_ctx->pending,
|
||||||
|
sim->irq_count, offset);
|
||||||
|
clear_bit(offset, work_ctx->pending);
|
||||||
|
irqnum = irq_sim_irqnum(sim, offset);
|
||||||
|
handle_simple_irq(irq_to_desc(irqnum));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,6 +74,13 @@ int irq_sim_init(struct irq_sim *sim, unsigned int num_irqs)
|
|||||||
return sim->irq_base;
|
return sim->irq_base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sim->work_ctx.pending = bitmap_zalloc(num_irqs, GFP_KERNEL);
|
||||||
|
if (!sim->work_ctx.pending) {
|
||||||
|
kfree(sim->irqs);
|
||||||
|
irq_free_descs(sim->irq_base, num_irqs);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < num_irqs; i++) {
|
for (i = 0; i < num_irqs; i++) {
|
||||||
sim->irqs[i].irqnum = sim->irq_base + i;
|
sim->irqs[i].irqnum = sim->irq_base + i;
|
||||||
sim->irqs[i].enabled = false;
|
sim->irqs[i].enabled = false;
|
||||||
@@ -89,6 +107,7 @@ EXPORT_SYMBOL_GPL(irq_sim_init);
|
|||||||
void irq_sim_fini(struct irq_sim *sim)
|
void irq_sim_fini(struct irq_sim *sim)
|
||||||
{
|
{
|
||||||
irq_work_sync(&sim->work_ctx.work);
|
irq_work_sync(&sim->work_ctx.work);
|
||||||
|
bitmap_free(sim->work_ctx.pending);
|
||||||
irq_free_descs(sim->irq_base, sim->irq_count);
|
irq_free_descs(sim->irq_base, sim->irq_count);
|
||||||
kfree(sim->irqs);
|
kfree(sim->irqs);
|
||||||
}
|
}
|
||||||
@@ -143,7 +162,7 @@ EXPORT_SYMBOL_GPL(devm_irq_sim_init);
|
|||||||
void irq_sim_fire(struct irq_sim *sim, unsigned int offset)
|
void irq_sim_fire(struct irq_sim *sim, unsigned int offset)
|
||||||
{
|
{
|
||||||
if (sim->irqs[offset].enabled) {
|
if (sim->irqs[offset].enabled) {
|
||||||
sim->work_ctx.irq = irq_sim_irqnum(sim, offset);
|
set_bit(offset, sim->work_ctx.pending);
|
||||||
irq_work_queue(&sim->work_ctx.work);
|
irq_work_queue(&sim->work_ctx.work);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user