mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-03 17:40:04 +00:00
irqchip updates for 4.15
- GICv4 updates (improved performance, errata workarounds) - Workaround for Socionext's pre-ITS erratum - Meson GPIO interrupt controller - BCM7271 L2 interrupt controller - GICv3 range selector support - various cleanups -----BEGIN PGP SIGNATURE----- iQJJBAABCAAzFiEEn9UcU+C1Yxj9lZw9I9DQutE9ekMFAlnofhoVHG1hcmMuenlu Z2llckBhcm0uY29tAAoJECPQ0LrRPXpDvZEP/iLDGYdmv1bskXhObJgXx91/9RyP WY2XsrbvPYOhF3Q2tG1hhtVSPunhX/vRANgIftD2H6GYPvHiRL6gwQN8eA6W7Fkp J4zFmWOM6Vu9ZKh/CZgLHVPiouHm58uDTatOPNi/kcvkoicylC3AiQjugHx0ob/Y OjMGHGPeP2hlk4vK+r1t4AdpigqdtSb2iC37ySoLvX2luemdviy8EgZ8CTdYg29J uvasyPCS/UVWH0Kjz/F7+6rixxo0+BEhHjDtuloXHZlu5I75wgseqfaE4dTjzFyp 1v3cLdkhCN//RENHdR7z/maGjIny0hb6FOS8bacN5/JKd0YY3WdC3fLG6xJwnO5z 6x+DQSHqhjT3Jgnw+Xr1i8fDSJyk5OFmEj+b38uTnPOWZKYdCp/6j07u78wz5E+M w5M6fJ4XaiVyVPRwwABJgrCfyCvSw1vq95qeSOg3CScb3gB0aziHrXYATu96YPos L0bWaVf/dJdA2QArNozx+m5gqkGIo6UhgmZbj0a8FBD7oqEzl58Yu0SWrjhW/51T GJlooy3/aKyWTJh2FqwHjSmI1ZTbanePI17idCyHj77+c2uV6IGMYd71UKvai0Fg aHymN6aRxNFG9PUse8Uu6YtApOc+MaGyLjXsre1W8CvVXokHrgBGygrCx5ApLi1L hV291v4f4wYt9k/9 =R1it -----END PGP SIGNATURE----- Merge tag 'irqchip-4.15' of git://git.kernel.org/pub/scm/linux/kernel/git/maz/arm-platforms into irq/core Pull irqchip updates for 4.15 from Marc Zyngier - GICv4 updates (improved performance, errata workarounds) - Workaround for Socionext's pre-ITS erratum - Meson GPIO interrupt controller - BCM7271 L2 interrupt controller - GICv3 range selector support - various cleanups
This commit is contained in:
commit
e4844dede5
22 changed files with 980 additions and 150 deletions
|
@ -70,6 +70,7 @@ stable kernels.
|
|||
| | | | |
|
||||
| Hisilicon | Hip0{5,6,7} | #161010101 | HISILICON_ERRATUM_161010101 |
|
||||
| Hisilicon | Hip0{6,7} | #161010701 | N/A |
|
||||
| Hisilicon | Hip07 | #161600802 | HISILICON_ERRATUM_161600802 |
|
||||
| | | | |
|
||||
| Qualcomm Tech. | Falkor v1 | E1003 | QCOM_FALKOR_ERRATUM_1003 |
|
||||
| Qualcomm Tech. | Falkor v1 | E1009 | QCOM_FALKOR_ERRATUM_1009 |
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
Amlogic meson GPIO interrupt controller
|
||||
|
||||
Meson SoCs contains an interrupt controller which is able to watch the SoC
|
||||
pads and generate an interrupt on edge or level. The controller is essentially
|
||||
a 256 pads to 8 GIC interrupt multiplexer, with a filter block to select edge
|
||||
or level and polarity. It does not expose all 256 mux inputs because the
|
||||
documentation shows that the upper part is not mapped to any pad. The actual
|
||||
number of interrupt exposed depends on the SoC.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : must have "amlogic,meson8-gpio-intc” and either
|
||||
“amlogic,meson8b-gpio-intc” for meson8b SoCs (S805) or
|
||||
“amlogic,meson-gxbb-gpio-intc” for GXBB SoCs (S905) or
|
||||
“amlogic,meson-gxl-gpio-intc” for GXL SoCs (S905X, S912)
|
||||
- interrupt-parent : a phandle to the GIC the interrupts are routed to.
|
||||
Usually this is provided at the root level of the device tree as it is
|
||||
common to most of the SoC.
|
||||
- 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
|
||||
interrupt source. The value must be 2.
|
||||
- meson,channel-interrupts: Array with the 8 upstream hwirq numbers. These
|
||||
are the hwirqs used on the parent interrupt controller.
|
||||
|
||||
Example:
|
||||
|
||||
gpio_interrupt: interrupt-controller@9880 {
|
||||
compatible = "amlogic,meson-gxbb-gpio-intc",
|
||||
"amlogic,meson-gpio-intc";
|
||||
reg = <0x0 0x9880 0x0 0x10>;
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <2>;
|
||||
meson,channel-interrupts = <64 65 66 67 68 69 70 71>;
|
||||
};
|
|
@ -75,6 +75,10 @@ These nodes must have the following properties:
|
|||
- reg: Specifies the base physical address and size of the ITS
|
||||
registers.
|
||||
|
||||
Optional:
|
||||
- socionext,synquacer-pre-its: (u32, u32) tuple describing the untranslated
|
||||
address and size of the pre-ITS window.
|
||||
|
||||
The main GIC node must contain the appropriate #address-cells,
|
||||
#size-cells and ranges properties for the reg property of all ITS
|
||||
nodes.
|
||||
|
|
|
@ -2,7 +2,8 @@ Broadcom Generic Level 2 Interrupt Controller
|
|||
|
||||
Required properties:
|
||||
|
||||
- compatible: should be "brcm,l2-intc"
|
||||
- compatible: should be "brcm,l2-intc" for latched interrupt controllers
|
||||
should be "brcm,bcm7271-l2-intc" for level interrupt controllers
|
||||
- reg: specifies the 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
|
||||
|
|
|
@ -13,6 +13,9 @@ Required properties:
|
|||
- "renesas,irqc-r8a7793" (R-Car M2-N)
|
||||
- "renesas,irqc-r8a7794" (R-Car E2)
|
||||
- "renesas,intc-ex-r8a7795" (R-Car H3)
|
||||
- "renesas,intc-ex-r8a7796" (R-Car M3-W)
|
||||
- "renesas,intc-ex-r8a77970" (R-Car V3M)
|
||||
- "renesas,intc-ex-r8a77995" (R-Car D3)
|
||||
- #interrupt-cells: has to be <2>: an interrupt index and flags, as defined in
|
||||
interrupts.txt in this directory
|
||||
- clocks: Must contain a reference to the functional clock.
|
||||
|
|
|
@ -196,6 +196,11 @@ static inline void gic_write_ctlr(u32 val)
|
|||
isb();
|
||||
}
|
||||
|
||||
static inline u32 gic_read_ctlr(void)
|
||||
{
|
||||
return read_sysreg(ICC_CTLR);
|
||||
}
|
||||
|
||||
static inline void gic_write_grpen1(u32 val)
|
||||
{
|
||||
write_sysreg(val, ICC_IGRPEN1);
|
||||
|
|
|
@ -539,6 +539,25 @@ config QCOM_QDF2400_ERRATUM_0065
|
|||
|
||||
If unsure, say Y.
|
||||
|
||||
|
||||
config SOCIONEXT_SYNQUACER_PREITS
|
||||
bool "Socionext Synquacer: Workaround for GICv3 pre-ITS"
|
||||
default y
|
||||
help
|
||||
Socionext Synquacer SoCs implement a separate h/w block to generate
|
||||
MSI doorbell writes with non-zero values for the device ID.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
config HISILICON_ERRATUM_161600802
|
||||
bool "Hip07 161600802: Erroneous redistributor VLPI base"
|
||||
default y
|
||||
help
|
||||
The HiSilicon Hip07 SoC usees the wrong redistributor base
|
||||
when issued ITS commands such as VMOVP and VMAPP, and requires
|
||||
a 128kB offset to be applied to the target address in this commands.
|
||||
|
||||
If unsure, say Y.
|
||||
endmenu
|
||||
|
||||
|
||||
|
|
|
@ -87,6 +87,11 @@ static inline void gic_write_ctlr(u32 val)
|
|||
isb();
|
||||
}
|
||||
|
||||
static inline u32 gic_read_ctlr(void)
|
||||
{
|
||||
return read_sysreg_s(SYS_ICC_CTLR_EL1);
|
||||
}
|
||||
|
||||
static inline void gic_write_grpen1(u32 val)
|
||||
{
|
||||
write_sysreg_s(val, SYS_ICC_IGRPEN1_EL1);
|
||||
|
|
|
@ -324,4 +324,12 @@ config IRQ_UNIPHIER_AIDET
|
|||
help
|
||||
Support for the UniPhier AIDET (ARM Interrupt Detector).
|
||||
|
||||
config MESON_IRQ_GPIO
|
||||
bool "Meson GPIO Interrupt Multiplexer"
|
||||
depends on ARCH_MESON || COMPILE_TEST
|
||||
select IRQ_DOMAIN
|
||||
select IRQ_DOMAIN_HIERARCHY
|
||||
help
|
||||
Support Meson SoC Family GPIO Interrupt Multiplexer
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -79,3 +79,4 @@ obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o
|
|||
obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o
|
||||
obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o
|
||||
obj-$(CONFIG_IRQ_UNIPHIER_AIDET) += irq-uniphier-aidet.o
|
||||
obj-$(CONFIG_MESON_IRQ_GPIO) += irq-meson-gpio.o
|
||||
|
|
|
@ -76,8 +76,8 @@ static int __init aspeed_i2c_ic_of_init(struct device_node *node,
|
|||
return -ENOMEM;
|
||||
|
||||
i2c_ic->base = of_iomap(node, 0);
|
||||
if (IS_ERR(i2c_ic->base)) {
|
||||
ret = PTR_ERR(i2c_ic->base);
|
||||
if (!i2c_ic->base) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free_ic;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Generic Broadcom Set Top Box Level 2 Interrupt controller driver
|
||||
*
|
||||
* Copyright (C) 2014 Broadcom Corporation
|
||||
* Copyright (C) 2014-2017 Broadcom
|
||||
*
|
||||
* 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
|
||||
|
@ -31,35 +31,82 @@
|
|||
#include <linux/irqchip.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
|
||||
/* Register offsets in the L2 interrupt controller */
|
||||
#define CPU_STATUS 0x00
|
||||
#define CPU_SET 0x04
|
||||
#define CPU_CLEAR 0x08
|
||||
#define CPU_MASK_STATUS 0x0c
|
||||
#define CPU_MASK_SET 0x10
|
||||
#define CPU_MASK_CLEAR 0x14
|
||||
struct brcmstb_intc_init_params {
|
||||
irq_flow_handler_t handler;
|
||||
int cpu_status;
|
||||
int cpu_clear;
|
||||
int cpu_mask_status;
|
||||
int cpu_mask_set;
|
||||
int cpu_mask_clear;
|
||||
};
|
||||
|
||||
/* Register offsets in the L2 latched interrupt controller */
|
||||
static const struct brcmstb_intc_init_params l2_edge_intc_init = {
|
||||
.handler = handle_edge_irq,
|
||||
.cpu_status = 0x00,
|
||||
.cpu_clear = 0x08,
|
||||
.cpu_mask_status = 0x0c,
|
||||
.cpu_mask_set = 0x10,
|
||||
.cpu_mask_clear = 0x14
|
||||
};
|
||||
|
||||
/* Register offsets in the L2 level interrupt controller */
|
||||
static const struct brcmstb_intc_init_params l2_lvl_intc_init = {
|
||||
.handler = handle_level_irq,
|
||||
.cpu_status = 0x00,
|
||||
.cpu_clear = -1, /* Register not present */
|
||||
.cpu_mask_status = 0x04,
|
||||
.cpu_mask_set = 0x08,
|
||||
.cpu_mask_clear = 0x0C
|
||||
};
|
||||
|
||||
/* L2 intc private data structure */
|
||||
struct brcmstb_l2_intc_data {
|
||||
int parent_irq;
|
||||
void __iomem *base;
|
||||
struct irq_domain *domain;
|
||||
struct irq_chip_generic *gc;
|
||||
int status_offset;
|
||||
int mask_offset;
|
||||
bool can_wake;
|
||||
u32 saved_mask; /* for suspend/resume */
|
||||
};
|
||||
|
||||
/**
|
||||
* brcmstb_l2_mask_and_ack - Mask and ack pending interrupt
|
||||
* @d: irq_data
|
||||
*
|
||||
* Chip has separate enable/disable registers instead of a single mask
|
||||
* register and pending interrupt is acknowledged by setting a bit.
|
||||
*
|
||||
* Note: This function is generic and could easily be added to the
|
||||
* generic irqchip implementation if there ever becomes a will to do so.
|
||||
* Perhaps with a name like irq_gc_mask_disable_and_ack_set().
|
||||
*
|
||||
* e.g.: https://patchwork.kernel.org/patch/9831047/
|
||||
*/
|
||||
static void brcmstb_l2_mask_and_ack(struct irq_data *d)
|
||||
{
|
||||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
||||
struct irq_chip_type *ct = irq_data_get_chip_type(d);
|
||||
u32 mask = d->mask;
|
||||
|
||||
irq_gc_lock(gc);
|
||||
irq_reg_writel(gc, mask, ct->regs.disable);
|
||||
*ct->mask_cache &= ~mask;
|
||||
irq_reg_writel(gc, mask, ct->regs.ack);
|
||||
irq_gc_unlock(gc);
|
||||
}
|
||||
|
||||
static void brcmstb_l2_intc_irq_handle(struct irq_desc *desc)
|
||||
{
|
||||
struct brcmstb_l2_intc_data *b = irq_desc_get_handler_data(desc);
|
||||
struct irq_chip_generic *gc = irq_get_domain_generic_chip(b->domain, 0);
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
unsigned int irq;
|
||||
u32 status;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
|
||||
status = irq_reg_readl(gc, CPU_STATUS) &
|
||||
~(irq_reg_readl(gc, CPU_MASK_STATUS));
|
||||
status = irq_reg_readl(b->gc, b->status_offset) &
|
||||
~(irq_reg_readl(b->gc, b->mask_offset));
|
||||
|
||||
if (status == 0) {
|
||||
raw_spin_lock(&desc->lock);
|
||||
|
@ -70,10 +117,8 @@ static void brcmstb_l2_intc_irq_handle(struct irq_desc *desc)
|
|||
|
||||
do {
|
||||
irq = ffs(status) - 1;
|
||||
/* ack at our level */
|
||||
irq_reg_writel(gc, 1 << irq, CPU_CLEAR);
|
||||
status &= ~(1 << irq);
|
||||
generic_handle_irq(irq_find_mapping(b->domain, irq));
|
||||
generic_handle_irq(irq_linear_revmap(b->domain, irq));
|
||||
} while (status);
|
||||
out:
|
||||
chained_irq_exit(chip, desc);
|
||||
|
@ -82,16 +127,17 @@ static void brcmstb_l2_intc_irq_handle(struct irq_desc *desc)
|
|||
static void brcmstb_l2_intc_suspend(struct irq_data *d)
|
||||
{
|
||||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
||||
struct irq_chip_type *ct = irq_data_get_chip_type(d);
|
||||
struct brcmstb_l2_intc_data *b = gc->private;
|
||||
|
||||
irq_gc_lock(gc);
|
||||
/* Save the current mask */
|
||||
b->saved_mask = irq_reg_readl(gc, CPU_MASK_STATUS);
|
||||
b->saved_mask = irq_reg_readl(gc, ct->regs.mask);
|
||||
|
||||
if (b->can_wake) {
|
||||
/* Program the wakeup mask */
|
||||
irq_reg_writel(gc, ~gc->wake_active, CPU_MASK_SET);
|
||||
irq_reg_writel(gc, gc->wake_active, CPU_MASK_CLEAR);
|
||||
irq_reg_writel(gc, ~gc->wake_active, ct->regs.disable);
|
||||
irq_reg_writel(gc, gc->wake_active, ct->regs.enable);
|
||||
}
|
||||
irq_gc_unlock(gc);
|
||||
}
|
||||
|
@ -99,49 +145,56 @@ static void brcmstb_l2_intc_suspend(struct irq_data *d)
|
|||
static void brcmstb_l2_intc_resume(struct irq_data *d)
|
||||
{
|
||||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
|
||||
struct irq_chip_type *ct = irq_data_get_chip_type(d);
|
||||
struct brcmstb_l2_intc_data *b = gc->private;
|
||||
|
||||
irq_gc_lock(gc);
|
||||
/* Clear unmasked non-wakeup interrupts */
|
||||
irq_reg_writel(gc, ~b->saved_mask & ~gc->wake_active, CPU_CLEAR);
|
||||
if (ct->chip.irq_ack) {
|
||||
/* Clear unmasked non-wakeup interrupts */
|
||||
irq_reg_writel(gc, ~b->saved_mask & ~gc->wake_active,
|
||||
ct->regs.ack);
|
||||
}
|
||||
|
||||
/* Restore the saved mask */
|
||||
irq_reg_writel(gc, b->saved_mask, CPU_MASK_SET);
|
||||
irq_reg_writel(gc, ~b->saved_mask, CPU_MASK_CLEAR);
|
||||
irq_reg_writel(gc, b->saved_mask, ct->regs.disable);
|
||||
irq_reg_writel(gc, ~b->saved_mask, ct->regs.enable);
|
||||
irq_gc_unlock(gc);
|
||||
}
|
||||
|
||||
static int __init brcmstb_l2_intc_of_init(struct device_node *np,
|
||||
struct device_node *parent)
|
||||
struct device_node *parent,
|
||||
const struct brcmstb_intc_init_params
|
||||
*init_params)
|
||||
{
|
||||
unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
|
||||
struct brcmstb_l2_intc_data *data;
|
||||
struct irq_chip_generic *gc;
|
||||
struct irq_chip_type *ct;
|
||||
int ret;
|
||||
unsigned int flags;
|
||||
int parent_irq;
|
||||
void __iomem *base;
|
||||
|
||||
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->base = of_iomap(np, 0);
|
||||
if (!data->base) {
|
||||
base = of_iomap(np, 0);
|
||||
if (!base) {
|
||||
pr_err("failed to remap intc L2 registers\n");
|
||||
ret = -ENOMEM;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
/* Disable all interrupts by default */
|
||||
writel(0xffffffff, data->base + CPU_MASK_SET);
|
||||
writel(0xffffffff, base + init_params->cpu_mask_set);
|
||||
|
||||
/* Wakeup interrupts may be retained from S5 (cold boot) */
|
||||
data->can_wake = of_property_read_bool(np, "brcm,irq-can-wake");
|
||||
if (!data->can_wake)
|
||||
writel(0xffffffff, data->base + CPU_CLEAR);
|
||||
if (!data->can_wake && (init_params->cpu_clear >= 0))
|
||||
writel(0xffffffff, base + init_params->cpu_clear);
|
||||
|
||||
data->parent_irq = irq_of_parse_and_map(np, 0);
|
||||
if (!data->parent_irq) {
|
||||
parent_irq = irq_of_parse_and_map(np, 0);
|
||||
if (!parent_irq) {
|
||||
pr_err("failed to find parent interrupt\n");
|
||||
ret = -EINVAL;
|
||||
goto out_unmap;
|
||||
|
@ -163,29 +216,39 @@ static int __init brcmstb_l2_intc_of_init(struct device_node *np,
|
|||
|
||||
/* Allocate a single Generic IRQ chip for this node */
|
||||
ret = irq_alloc_domain_generic_chips(data->domain, 32, 1,
|
||||
np->full_name, handle_edge_irq, clr, 0, flags);
|
||||
np->full_name, init_params->handler, clr, 0, flags);
|
||||
if (ret) {
|
||||
pr_err("failed to allocate generic irq chip\n");
|
||||
goto out_free_domain;
|
||||
}
|
||||
|
||||
/* Set the IRQ chaining logic */
|
||||
irq_set_chained_handler_and_data(data->parent_irq,
|
||||
irq_set_chained_handler_and_data(parent_irq,
|
||||
brcmstb_l2_intc_irq_handle, data);
|
||||
|
||||
gc = irq_get_domain_generic_chip(data->domain, 0);
|
||||
gc->reg_base = data->base;
|
||||
gc->private = data;
|
||||
ct = gc->chip_types;
|
||||
data->gc = irq_get_domain_generic_chip(data->domain, 0);
|
||||
data->gc->reg_base = base;
|
||||
data->gc->private = data;
|
||||
data->status_offset = init_params->cpu_status;
|
||||
data->mask_offset = init_params->cpu_mask_status;
|
||||
|
||||
ct->chip.irq_ack = irq_gc_ack_set_bit;
|
||||
ct->regs.ack = CPU_CLEAR;
|
||||
ct = data->gc->chip_types;
|
||||
|
||||
if (init_params->cpu_clear >= 0) {
|
||||
ct->regs.ack = init_params->cpu_clear;
|
||||
ct->chip.irq_ack = irq_gc_ack_set_bit;
|
||||
ct->chip.irq_mask_ack = brcmstb_l2_mask_and_ack;
|
||||
} else {
|
||||
/* No Ack - but still slightly more efficient to define this */
|
||||
ct->chip.irq_mask_ack = irq_gc_mask_disable_reg;
|
||||
}
|
||||
|
||||
ct->chip.irq_mask = irq_gc_mask_disable_reg;
|
||||
ct->regs.disable = CPU_MASK_SET;
|
||||
ct->regs.disable = init_params->cpu_mask_set;
|
||||
ct->regs.mask = init_params->cpu_mask_status;
|
||||
|
||||
ct->chip.irq_unmask = irq_gc_unmask_enable_reg;
|
||||
ct->regs.enable = CPU_MASK_CLEAR;
|
||||
ct->regs.enable = init_params->cpu_mask_clear;
|
||||
|
||||
ct->chip.irq_suspend = brcmstb_l2_intc_suspend;
|
||||
ct->chip.irq_resume = brcmstb_l2_intc_resume;
|
||||
|
@ -195,21 +258,35 @@ static int __init brcmstb_l2_intc_of_init(struct device_node *np,
|
|||
/* This IRQ chip can wake the system, set all child interrupts
|
||||
* in wake_enabled mask
|
||||
*/
|
||||
gc->wake_enabled = 0xffffffff;
|
||||
data->gc->wake_enabled = 0xffffffff;
|
||||
ct->chip.irq_set_wake = irq_gc_set_wake;
|
||||
}
|
||||
|
||||
pr_info("registered L2 intc (mem: 0x%p, parent irq: %d)\n",
|
||||
data->base, data->parent_irq);
|
||||
base, parent_irq);
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_domain:
|
||||
irq_domain_remove(data->domain);
|
||||
out_unmap:
|
||||
iounmap(data->base);
|
||||
iounmap(base);
|
||||
out_free:
|
||||
kfree(data);
|
||||
return ret;
|
||||
}
|
||||
IRQCHIP_DECLARE(brcmstb_l2_intc, "brcm,l2-intc", brcmstb_l2_intc_of_init);
|
||||
|
||||
int __init brcmstb_l2_edge_intc_of_init(struct device_node *np,
|
||||
struct device_node *parent)
|
||||
{
|
||||
return brcmstb_l2_intc_of_init(np, parent, &l2_edge_intc_init);
|
||||
}
|
||||
IRQCHIP_DECLARE(brcmstb_l2_intc, "brcm,l2-intc", brcmstb_l2_edge_intc_of_init);
|
||||
|
||||
int __init brcmstb_l2_lvl_intc_of_init(struct device_node *np,
|
||||
struct device_node *parent)
|
||||
{
|
||||
return brcmstb_l2_intc_of_init(np, parent, &l2_lvl_intc_init);
|
||||
}
|
||||
IRQCHIP_DECLARE(bcm7271_l2_intc, "brcm,bcm7271-l2-intc",
|
||||
brcmstb_l2_lvl_intc_of_init);
|
||||
|
|
|
@ -40,8 +40,9 @@ void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
|
|||
for (; quirks->desc; quirks++) {
|
||||
if (quirks->iidr != (quirks->mask & iidr))
|
||||
continue;
|
||||
quirks->init(data);
|
||||
pr_info("GIC: enabling workaround for %s\n", quirks->desc);
|
||||
if (quirks->init(data))
|
||||
pr_info("GIC: enabling workaround for %s\n",
|
||||
quirks->desc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
struct gic_quirk {
|
||||
const char *desc;
|
||||
void (*init)(void *data);
|
||||
bool (*init)(void *data);
|
||||
u32 iidr;
|
||||
u32 mask;
|
||||
};
|
||||
|
|
|
@ -83,6 +83,8 @@ struct its_baser {
|
|||
u32 psz;
|
||||
};
|
||||
|
||||
struct its_device;
|
||||
|
||||
/*
|
||||
* The ITS structure - contains most of the infrastructure, with the
|
||||
* top-level MSI domain, the command queue, the collections, and the
|
||||
|
@ -97,12 +99,18 @@ struct its_node {
|
|||
struct its_cmd_block *cmd_write;
|
||||
struct its_baser tables[GITS_BASER_NR_REGS];
|
||||
struct its_collection *collections;
|
||||
struct fwnode_handle *fwnode_handle;
|
||||
u64 (*get_msi_base)(struct its_device *its_dev);
|
||||
struct list_head its_device_list;
|
||||
u64 flags;
|
||||
unsigned long list_nr;
|
||||
u32 ite_size;
|
||||
u32 device_ids;
|
||||
int numa_node;
|
||||
unsigned int msi_domain_flags;
|
||||
u32 pre_its_base; /* for Socionext Synquacer */
|
||||
bool is_v4;
|
||||
int vlpi_redist_offset;
|
||||
};
|
||||
|
||||
#define ITS_ITT_ALIGN SZ_256
|
||||
|
@ -148,12 +156,6 @@ static DEFINE_SPINLOCK(its_lock);
|
|||
static struct rdists *gic_rdists;
|
||||
static struct irq_domain *its_parent;
|
||||
|
||||
/*
|
||||
* We have a maximum number of 16 ITSs in the whole system if we're
|
||||
* using the ITSList mechanism
|
||||
*/
|
||||
#define ITS_LIST_MAX 16
|
||||
|
||||
static unsigned long its_list_map;
|
||||
static u16 vmovp_seq_num;
|
||||
static DEFINE_RAW_SPINLOCK(vmovp_lock);
|
||||
|
@ -268,10 +270,12 @@ struct its_cmd_block {
|
|||
#define ITS_CMD_QUEUE_SZ SZ_64K
|
||||
#define ITS_CMD_QUEUE_NR_ENTRIES (ITS_CMD_QUEUE_SZ / sizeof(struct its_cmd_block))
|
||||
|
||||
typedef struct its_collection *(*its_cmd_builder_t)(struct its_cmd_block *,
|
||||
typedef struct its_collection *(*its_cmd_builder_t)(struct its_node *,
|
||||
struct its_cmd_block *,
|
||||
struct its_cmd_desc *);
|
||||
|
||||
typedef struct its_vpe *(*its_cmd_vbuilder_t)(struct its_cmd_block *,
|
||||
typedef struct its_vpe *(*its_cmd_vbuilder_t)(struct its_node *,
|
||||
struct its_cmd_block *,
|
||||
struct its_cmd_desc *);
|
||||
|
||||
static void its_mask_encode(u64 *raw_cmd, u64 val, int h, int l)
|
||||
|
@ -375,7 +379,8 @@ static inline void its_fixup_cmd(struct its_cmd_block *cmd)
|
|||
cmd->raw_cmd[3] = cpu_to_le64(cmd->raw_cmd[3]);
|
||||
}
|
||||
|
||||
static struct its_collection *its_build_mapd_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_collection *its_build_mapd_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
unsigned long itt_addr;
|
||||
|
@ -395,7 +400,8 @@ static struct its_collection *its_build_mapd_cmd(struct its_cmd_block *cmd,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static struct its_collection *its_build_mapc_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_collection *its_build_mapc_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
its_encode_cmd(cmd, GITS_CMD_MAPC);
|
||||
|
@ -408,7 +414,8 @@ static struct its_collection *its_build_mapc_cmd(struct its_cmd_block *cmd,
|
|||
return desc->its_mapc_cmd.col;
|
||||
}
|
||||
|
||||
static struct its_collection *its_build_mapti_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_collection *its_build_mapti_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
struct its_collection *col;
|
||||
|
@ -427,7 +434,8 @@ static struct its_collection *its_build_mapti_cmd(struct its_cmd_block *cmd,
|
|||
return col;
|
||||
}
|
||||
|
||||
static struct its_collection *its_build_movi_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_collection *its_build_movi_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
struct its_collection *col;
|
||||
|
@ -445,7 +453,8 @@ static struct its_collection *its_build_movi_cmd(struct its_cmd_block *cmd,
|
|||
return col;
|
||||
}
|
||||
|
||||
static struct its_collection *its_build_discard_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_collection *its_build_discard_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
struct its_collection *col;
|
||||
|
@ -462,7 +471,8 @@ static struct its_collection *its_build_discard_cmd(struct its_cmd_block *cmd,
|
|||
return col;
|
||||
}
|
||||
|
||||
static struct its_collection *its_build_inv_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_collection *its_build_inv_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
struct its_collection *col;
|
||||
|
@ -479,7 +489,8 @@ static struct its_collection *its_build_inv_cmd(struct its_cmd_block *cmd,
|
|||
return col;
|
||||
}
|
||||
|
||||
static struct its_collection *its_build_int_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_collection *its_build_int_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
struct its_collection *col;
|
||||
|
@ -496,7 +507,8 @@ static struct its_collection *its_build_int_cmd(struct its_cmd_block *cmd,
|
|||
return col;
|
||||
}
|
||||
|
||||
static struct its_collection *its_build_clear_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_collection *its_build_clear_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
struct its_collection *col;
|
||||
|
@ -513,7 +525,8 @@ static struct its_collection *its_build_clear_cmd(struct its_cmd_block *cmd,
|
|||
return col;
|
||||
}
|
||||
|
||||
static struct its_collection *its_build_invall_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_collection *its_build_invall_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
its_encode_cmd(cmd, GITS_CMD_INVALL);
|
||||
|
@ -524,7 +537,8 @@ static struct its_collection *its_build_invall_cmd(struct its_cmd_block *cmd,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static struct its_vpe *its_build_vinvall_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_vpe *its_build_vinvall_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
its_encode_cmd(cmd, GITS_CMD_VINVALL);
|
||||
|
@ -535,17 +549,20 @@ static struct its_vpe *its_build_vinvall_cmd(struct its_cmd_block *cmd,
|
|||
return desc->its_vinvall_cmd.vpe;
|
||||
}
|
||||
|
||||
static struct its_vpe *its_build_vmapp_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_vpe *its_build_vmapp_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
unsigned long vpt_addr;
|
||||
u64 target;
|
||||
|
||||
vpt_addr = virt_to_phys(page_address(desc->its_vmapp_cmd.vpe->vpt_page));
|
||||
target = desc->its_vmapp_cmd.col->target_address + its->vlpi_redist_offset;
|
||||
|
||||
its_encode_cmd(cmd, GITS_CMD_VMAPP);
|
||||
its_encode_vpeid(cmd, desc->its_vmapp_cmd.vpe->vpe_id);
|
||||
its_encode_valid(cmd, desc->its_vmapp_cmd.valid);
|
||||
its_encode_target(cmd, desc->its_vmapp_cmd.col->target_address);
|
||||
its_encode_target(cmd, target);
|
||||
its_encode_vpt_addr(cmd, vpt_addr);
|
||||
its_encode_vpt_size(cmd, LPI_NRBITS - 1);
|
||||
|
||||
|
@ -554,7 +571,8 @@ static struct its_vpe *its_build_vmapp_cmd(struct its_cmd_block *cmd,
|
|||
return desc->its_vmapp_cmd.vpe;
|
||||
}
|
||||
|
||||
static struct its_vpe *its_build_vmapti_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_vpe *its_build_vmapti_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
u32 db;
|
||||
|
@ -576,7 +594,8 @@ static struct its_vpe *its_build_vmapti_cmd(struct its_cmd_block *cmd,
|
|||
return desc->its_vmapti_cmd.vpe;
|
||||
}
|
||||
|
||||
static struct its_vpe *its_build_vmovi_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_vpe *its_build_vmovi_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
u32 db;
|
||||
|
@ -598,14 +617,18 @@ static struct its_vpe *its_build_vmovi_cmd(struct its_cmd_block *cmd,
|
|||
return desc->its_vmovi_cmd.vpe;
|
||||
}
|
||||
|
||||
static struct its_vpe *its_build_vmovp_cmd(struct its_cmd_block *cmd,
|
||||
static struct its_vpe *its_build_vmovp_cmd(struct its_node *its,
|
||||
struct its_cmd_block *cmd,
|
||||
struct its_cmd_desc *desc)
|
||||
{
|
||||
u64 target;
|
||||
|
||||
target = desc->its_vmovp_cmd.col->target_address + its->vlpi_redist_offset;
|
||||
its_encode_cmd(cmd, GITS_CMD_VMOVP);
|
||||
its_encode_seq_num(cmd, desc->its_vmovp_cmd.seq_num);
|
||||
its_encode_its_list(cmd, desc->its_vmovp_cmd.its_list);
|
||||
its_encode_vpeid(cmd, desc->its_vmovp_cmd.vpe->vpe_id);
|
||||
its_encode_target(cmd, desc->its_vmovp_cmd.col->target_address);
|
||||
its_encode_target(cmd, target);
|
||||
|
||||
its_fixup_cmd(cmd);
|
||||
|
||||
|
@ -684,9 +707,9 @@ static void its_flush_cmd(struct its_node *its, struct its_cmd_block *cmd)
|
|||
dsb(ishst);
|
||||
}
|
||||
|
||||
static void its_wait_for_range_completion(struct its_node *its,
|
||||
struct its_cmd_block *from,
|
||||
struct its_cmd_block *to)
|
||||
static int its_wait_for_range_completion(struct its_node *its,
|
||||
struct its_cmd_block *from,
|
||||
struct its_cmd_block *to)
|
||||
{
|
||||
u64 rd_idx, from_idx, to_idx;
|
||||
u32 count = 1000000; /* 1s! */
|
||||
|
@ -707,12 +730,15 @@ static void its_wait_for_range_completion(struct its_node *its,
|
|||
|
||||
count--;
|
||||
if (!count) {
|
||||
pr_err_ratelimited("ITS queue timeout\n");
|
||||
return;
|
||||
pr_err_ratelimited("ITS queue timeout (%llu %llu %llu)\n",
|
||||
from_idx, to_idx, rd_idx);
|
||||
return -1;
|
||||
}
|
||||
cpu_relax();
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Warning, macro hell follows */
|
||||
|
@ -732,7 +758,7 @@ void name(struct its_node *its, \
|
|||
raw_spin_unlock_irqrestore(&its->lock, flags); \
|
||||
return; \
|
||||
} \
|
||||
sync_obj = builder(cmd, desc); \
|
||||
sync_obj = builder(its, cmd, desc); \
|
||||
its_flush_cmd(its, cmd); \
|
||||
\
|
||||
if (sync_obj) { \
|
||||
|
@ -740,7 +766,7 @@ void name(struct its_node *its, \
|
|||
if (!sync_cmd) \
|
||||
goto post; \
|
||||
\
|
||||
buildfn(sync_cmd, sync_obj); \
|
||||
buildfn(its, sync_cmd, sync_obj); \
|
||||
its_flush_cmd(its, sync_cmd); \
|
||||
} \
|
||||
\
|
||||
|
@ -748,10 +774,12 @@ post: \
|
|||
next_cmd = its_post_commands(its); \
|
||||
raw_spin_unlock_irqrestore(&its->lock, flags); \
|
||||
\
|
||||
its_wait_for_range_completion(its, cmd, next_cmd); \
|
||||
if (its_wait_for_range_completion(its, cmd, next_cmd)) \
|
||||
pr_err_ratelimited("ITS cmd %ps failed\n", builder); \
|
||||
}
|
||||
|
||||
static void its_build_sync_cmd(struct its_cmd_block *sync_cmd,
|
||||
static void its_build_sync_cmd(struct its_node *its,
|
||||
struct its_cmd_block *sync_cmd,
|
||||
struct its_collection *sync_col)
|
||||
{
|
||||
its_encode_cmd(sync_cmd, GITS_CMD_SYNC);
|
||||
|
@ -763,7 +791,8 @@ static void its_build_sync_cmd(struct its_cmd_block *sync_cmd,
|
|||
static BUILD_SINGLE_CMD_FUNC(its_send_single_command, its_cmd_builder_t,
|
||||
struct its_collection, its_build_sync_cmd)
|
||||
|
||||
static void its_build_vsync_cmd(struct its_cmd_block *sync_cmd,
|
||||
static void its_build_vsync_cmd(struct its_node *its,
|
||||
struct its_cmd_block *sync_cmd,
|
||||
struct its_vpe *sync_vpe)
|
||||
{
|
||||
its_encode_cmd(sync_cmd, GITS_CMD_VSYNC);
|
||||
|
@ -895,21 +924,16 @@ static void its_send_vmovi(struct its_device *dev, u32 id)
|
|||
its_send_single_vcommand(dev->its, its_build_vmovi_cmd, &desc);
|
||||
}
|
||||
|
||||
static void its_send_vmapp(struct its_vpe *vpe, bool valid)
|
||||
static void its_send_vmapp(struct its_node *its,
|
||||
struct its_vpe *vpe, bool valid)
|
||||
{
|
||||
struct its_cmd_desc desc;
|
||||
struct its_node *its;
|
||||
|
||||
desc.its_vmapp_cmd.vpe = vpe;
|
||||
desc.its_vmapp_cmd.valid = valid;
|
||||
desc.its_vmapp_cmd.col = &its->collections[vpe->col_idx];
|
||||
|
||||
list_for_each_entry(its, &its_nodes, entry) {
|
||||
if (!its->is_v4)
|
||||
continue;
|
||||
|
||||
desc.its_vmapp_cmd.col = &its->collections[vpe->col_idx];
|
||||
its_send_single_vcommand(its, its_build_vmapp_cmd, &desc);
|
||||
}
|
||||
its_send_single_vcommand(its, its_build_vmapp_cmd, &desc);
|
||||
}
|
||||
|
||||
static void its_send_vmovp(struct its_vpe *vpe)
|
||||
|
@ -947,6 +971,9 @@ static void its_send_vmovp(struct its_vpe *vpe)
|
|||
if (!its->is_v4)
|
||||
continue;
|
||||
|
||||
if (!vpe->its_vm->vlpi_count[its->list_nr])
|
||||
continue;
|
||||
|
||||
desc.its_vmovp_cmd.col = &its->collections[col_id];
|
||||
its_send_single_vcommand(its, its_build_vmovp_cmd, &desc);
|
||||
}
|
||||
|
@ -954,18 +981,12 @@ static void its_send_vmovp(struct its_vpe *vpe)
|
|||
raw_spin_unlock_irqrestore(&vmovp_lock, flags);
|
||||
}
|
||||
|
||||
static void its_send_vinvall(struct its_vpe *vpe)
|
||||
static void its_send_vinvall(struct its_node *its, struct its_vpe *vpe)
|
||||
{
|
||||
struct its_cmd_desc desc;
|
||||
struct its_node *its;
|
||||
|
||||
desc.its_vinvall_cmd.vpe = vpe;
|
||||
|
||||
list_for_each_entry(its, &its_nodes, entry) {
|
||||
if (!its->is_v4)
|
||||
continue;
|
||||
its_send_single_vcommand(its, its_build_vinvall_cmd, &desc);
|
||||
}
|
||||
its_send_single_vcommand(its, its_build_vinvall_cmd, &desc);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1095,6 +1116,13 @@ static int its_set_affinity(struct irq_data *d, const struct cpumask *mask_val,
|
|||
return IRQ_SET_MASK_OK_DONE;
|
||||
}
|
||||
|
||||
static u64 its_irq_get_msi_base(struct its_device *its_dev)
|
||||
{
|
||||
struct its_node *its = its_dev->its;
|
||||
|
||||
return its->phys_base + GITS_TRANSLATER;
|
||||
}
|
||||
|
||||
static void its_irq_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
|
||||
{
|
||||
struct its_device *its_dev = irq_data_get_irq_chip_data(d);
|
||||
|
@ -1102,7 +1130,7 @@ static void its_irq_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
|
|||
u64 addr;
|
||||
|
||||
its = its_dev->its;
|
||||
addr = its->phys_base + GITS_TRANSLATER;
|
||||
addr = its->get_msi_base(its_dev);
|
||||
|
||||
msg->address_lo = lower_32_bits(addr);
|
||||
msg->address_hi = upper_32_bits(addr);
|
||||
|
@ -1129,6 +1157,60 @@ static int its_irq_set_irqchip_state(struct irq_data *d,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void its_map_vm(struct its_node *its, struct its_vm *vm)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
/* Not using the ITS list? Everything is always mapped. */
|
||||
if (!its_list_map)
|
||||
return;
|
||||
|
||||
raw_spin_lock_irqsave(&vmovp_lock, flags);
|
||||
|
||||
/*
|
||||
* If the VM wasn't mapped yet, iterate over the vpes and get
|
||||
* them mapped now.
|
||||
*/
|
||||
vm->vlpi_count[its->list_nr]++;
|
||||
|
||||
if (vm->vlpi_count[its->list_nr] == 1) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < vm->nr_vpes; i++) {
|
||||
struct its_vpe *vpe = vm->vpes[i];
|
||||
struct irq_data *d = irq_get_irq_data(vpe->irq);
|
||||
|
||||
/* Map the VPE to the first possible CPU */
|
||||
vpe->col_idx = cpumask_first(cpu_online_mask);
|
||||
its_send_vmapp(its, vpe, true);
|
||||
its_send_vinvall(its, vpe);
|
||||
irq_data_update_effective_affinity(d, cpumask_of(vpe->col_idx));
|
||||
}
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&vmovp_lock, flags);
|
||||
}
|
||||
|
||||
static void its_unmap_vm(struct its_node *its, struct its_vm *vm)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
/* Not using the ITS list? Everything is always mapped. */
|
||||
if (!its_list_map)
|
||||
return;
|
||||
|
||||
raw_spin_lock_irqsave(&vmovp_lock, flags);
|
||||
|
||||
if (!--vm->vlpi_count[its->list_nr]) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < vm->nr_vpes; i++)
|
||||
its_send_vmapp(its, vm->vpes[i], false);
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&vmovp_lock, flags);
|
||||
}
|
||||
|
||||
static int its_vlpi_map(struct irq_data *d, struct its_cmd_info *info)
|
||||
{
|
||||
struct its_device *its_dev = irq_data_get_irq_chip_data(d);
|
||||
|
@ -1164,6 +1246,9 @@ static int its_vlpi_map(struct irq_data *d, struct its_cmd_info *info)
|
|||
/* Already mapped, move it around */
|
||||
its_send_vmovi(its_dev, event);
|
||||
} else {
|
||||
/* Ensure all the VPEs are mapped on this ITS */
|
||||
its_map_vm(its_dev->its, info->map->vm);
|
||||
|
||||
/* Drop the physical mapping */
|
||||
its_send_discard(its_dev, event);
|
||||
|
||||
|
@ -1225,6 +1310,9 @@ static int its_vlpi_unmap(struct irq_data *d)
|
|||
LPI_PROP_ENABLED |
|
||||
LPI_PROP_GROUP1));
|
||||
|
||||
/* Potentially unmap the VM from this ITS */
|
||||
its_unmap_vm(its_dev->its, its_dev->event_map.vm);
|
||||
|
||||
/*
|
||||
* Drop the refcount and make the device available again if
|
||||
* this was the last VLPI.
|
||||
|
@ -1650,23 +1738,14 @@ static void its_free_tables(struct its_node *its)
|
|||
|
||||
static int its_alloc_tables(struct its_node *its)
|
||||
{
|
||||
u64 typer = gic_read_typer(its->base + GITS_TYPER);
|
||||
u32 ids = GITS_TYPER_DEVBITS(typer);
|
||||
u64 shr = GITS_BASER_InnerShareable;
|
||||
u64 cache = GITS_BASER_RaWaWb;
|
||||
u32 psz = SZ_64K;
|
||||
int err, i;
|
||||
|
||||
if (its->flags & ITS_FLAGS_WORKAROUND_CAVIUM_22375) {
|
||||
/*
|
||||
* erratum 22375: only alloc 8MB table size
|
||||
* erratum 24313: ignore memory access type
|
||||
*/
|
||||
cache = GITS_BASER_nCnB;
|
||||
ids = 0x14; /* 20 bits, 8MB */
|
||||
}
|
||||
|
||||
its->device_ids = ids;
|
||||
if (its->flags & ITS_FLAGS_WORKAROUND_CAVIUM_22375)
|
||||
/* erratum 24313: ignore memory access type */
|
||||
cache = GITS_BASER_nCnB;
|
||||
|
||||
for (i = 0; i < GITS_BASER_NR_REGS; i++) {
|
||||
struct its_baser *baser = its->tables + i;
|
||||
|
@ -2372,6 +2451,8 @@ static int its_vpe_set_affinity(struct irq_data *d,
|
|||
its_vpe_db_proxy_move(vpe, from, cpu);
|
||||
}
|
||||
|
||||
irq_data_update_effective_affinity(d, cpumask_of(cpu));
|
||||
|
||||
return IRQ_SET_MASK_OK_DONE;
|
||||
}
|
||||
|
||||
|
@ -2439,6 +2520,26 @@ static void its_vpe_deschedule(struct its_vpe *vpe)
|
|||
}
|
||||
}
|
||||
|
||||
static void its_vpe_invall(struct its_vpe *vpe)
|
||||
{
|
||||
struct its_node *its;
|
||||
|
||||
list_for_each_entry(its, &its_nodes, entry) {
|
||||
if (!its->is_v4)
|
||||
continue;
|
||||
|
||||
if (its_list_map && !vpe->its_vm->vlpi_count[its->list_nr])
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Sending a VINVALL to a single ITS is enough, as all
|
||||
* we need is to reach the redistributors.
|
||||
*/
|
||||
its_send_vinvall(its, vpe);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static int its_vpe_set_vcpu_affinity(struct irq_data *d, void *vcpu_info)
|
||||
{
|
||||
struct its_vpe *vpe = irq_data_get_irq_chip_data(d);
|
||||
|
@ -2454,7 +2555,7 @@ static int its_vpe_set_vcpu_affinity(struct irq_data *d, void *vcpu_info)
|
|||
return 0;
|
||||
|
||||
case INVALL_VPE:
|
||||
its_send_vinvall(vpe);
|
||||
its_vpe_invall(vpe);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
|
@ -2683,11 +2784,25 @@ static int its_vpe_irq_domain_activate(struct irq_domain *domain,
|
|||
struct irq_data *d, bool early)
|
||||
{
|
||||
struct its_vpe *vpe = irq_data_get_irq_chip_data(d);
|
||||
struct its_node *its;
|
||||
|
||||
/* If we use the list map, we issue VMAPP on demand... */
|
||||
if (its_list_map)
|
||||
return true;
|
||||
|
||||
/* Map the VPE to the first possible CPU */
|
||||
vpe->col_idx = cpumask_first(cpu_online_mask);
|
||||
its_send_vmapp(vpe, true);
|
||||
its_send_vinvall(vpe);
|
||||
|
||||
list_for_each_entry(its, &its_nodes, entry) {
|
||||
if (!its->is_v4)
|
||||
continue;
|
||||
|
||||
its_send_vmapp(its, vpe, true);
|
||||
its_send_vinvall(its, vpe);
|
||||
}
|
||||
|
||||
irq_data_update_effective_affinity(d, cpumask_of(vpe->col_idx));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -2695,8 +2810,21 @@ static void its_vpe_irq_domain_deactivate(struct irq_domain *domain,
|
|||
struct irq_data *d)
|
||||
{
|
||||
struct its_vpe *vpe = irq_data_get_irq_chip_data(d);
|
||||
struct its_node *its;
|
||||
|
||||
its_send_vmapp(vpe, false);
|
||||
/*
|
||||
* If we use the list map, we unmap the VPE once no VLPIs are
|
||||
* associated with the VM.
|
||||
*/
|
||||
if (its_list_map)
|
||||
return;
|
||||
|
||||
list_for_each_entry(its, &its_nodes, entry) {
|
||||
if (!its->is_v4)
|
||||
continue;
|
||||
|
||||
its_send_vmapp(its, vpe, false);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops its_vpe_domain_ops = {
|
||||
|
@ -2739,26 +2867,85 @@ static int its_force_quiescent(void __iomem *base)
|
|||
}
|
||||
}
|
||||
|
||||
static void __maybe_unused its_enable_quirk_cavium_22375(void *data)
|
||||
static bool __maybe_unused its_enable_quirk_cavium_22375(void *data)
|
||||
{
|
||||
struct its_node *its = data;
|
||||
|
||||
/* erratum 22375: only alloc 8MB table size */
|
||||
its->device_ids = 0x14; /* 20 bits, 8MB */
|
||||
its->flags |= ITS_FLAGS_WORKAROUND_CAVIUM_22375;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __maybe_unused its_enable_quirk_cavium_23144(void *data)
|
||||
static bool __maybe_unused its_enable_quirk_cavium_23144(void *data)
|
||||
{
|
||||
struct its_node *its = data;
|
||||
|
||||
its->flags |= ITS_FLAGS_WORKAROUND_CAVIUM_23144;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void __maybe_unused its_enable_quirk_qdf2400_e0065(void *data)
|
||||
static bool __maybe_unused its_enable_quirk_qdf2400_e0065(void *data)
|
||||
{
|
||||
struct its_node *its = data;
|
||||
|
||||
/* On QDF2400, the size of the ITE is 16Bytes */
|
||||
its->ite_size = 16;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static u64 its_irq_get_msi_base_pre_its(struct its_device *its_dev)
|
||||
{
|
||||
struct its_node *its = its_dev->its;
|
||||
|
||||
/*
|
||||
* The Socionext Synquacer SoC has a so-called 'pre-ITS',
|
||||
* which maps 32-bit writes targeted at a separate window of
|
||||
* size '4 << device_id_bits' onto writes to GITS_TRANSLATER
|
||||
* with device ID taken from bits [device_id_bits + 1:2] of
|
||||
* the window offset.
|
||||
*/
|
||||
return its->pre_its_base + (its_dev->device_id << 2);
|
||||
}
|
||||
|
||||
static bool __maybe_unused its_enable_quirk_socionext_synquacer(void *data)
|
||||
{
|
||||
struct its_node *its = data;
|
||||
u32 pre_its_window[2];
|
||||
u32 ids;
|
||||
|
||||
if (!fwnode_property_read_u32_array(its->fwnode_handle,
|
||||
"socionext,synquacer-pre-its",
|
||||
pre_its_window,
|
||||
ARRAY_SIZE(pre_its_window))) {
|
||||
|
||||
its->pre_its_base = pre_its_window[0];
|
||||
its->get_msi_base = its_irq_get_msi_base_pre_its;
|
||||
|
||||
ids = ilog2(pre_its_window[1]) - 2;
|
||||
if (its->device_ids > ids)
|
||||
its->device_ids = ids;
|
||||
|
||||
/* the pre-ITS breaks isolation, so disable MSI remapping */
|
||||
its->msi_domain_flags &= ~IRQ_DOMAIN_FLAG_MSI_REMAP;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool __maybe_unused its_enable_quirk_hip07_161600802(void *data)
|
||||
{
|
||||
struct its_node *its = data;
|
||||
|
||||
/*
|
||||
* Hip07 insists on using the wrong address for the VLPI
|
||||
* page. Trick it into doing the right thing...
|
||||
*/
|
||||
its->vlpi_redist_offset = SZ_128K;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const struct gic_quirk its_quirks[] = {
|
||||
|
@ -2785,6 +2972,27 @@ static const struct gic_quirk its_quirks[] = {
|
|||
.mask = 0xffffffff,
|
||||
.init = its_enable_quirk_qdf2400_e0065,
|
||||
},
|
||||
#endif
|
||||
#ifdef CONFIG_SOCIONEXT_SYNQUACER_PREITS
|
||||
{
|
||||
/*
|
||||
* The Socionext Synquacer SoC incorporates ARM's own GIC-500
|
||||
* implementation, but with a 'pre-ITS' added that requires
|
||||
* special handling in software.
|
||||
*/
|
||||
.desc = "ITS: Socionext Synquacer pre-ITS",
|
||||
.iidr = 0x0001143b,
|
||||
.mask = 0xffffffff,
|
||||
.init = its_enable_quirk_socionext_synquacer,
|
||||
},
|
||||
#endif
|
||||
#ifdef CONFIG_HISILICON_ERRATUM_161600802
|
||||
{
|
||||
.desc = "ITS: Hip07 erratum 161600802",
|
||||
.iidr = 0x00000004,
|
||||
.mask = 0xffffffff,
|
||||
.init = its_enable_quirk_hip07_161600802,
|
||||
},
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
@ -2814,7 +3022,7 @@ static int its_init_domain(struct fwnode_handle *handle, struct its_node *its)
|
|||
|
||||
inner_domain->parent = its_parent;
|
||||
irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS);
|
||||
inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_REMAP;
|
||||
inner_domain->flags |= its->msi_domain_flags;
|
||||
info->ops = &its_msi_domain_ops;
|
||||
info->data = its;
|
||||
inner_domain->host_data = info;
|
||||
|
@ -2875,8 +3083,8 @@ static int __init its_compute_its_list_map(struct resource *res,
|
|||
* locking. Should this change, we should address
|
||||
* this.
|
||||
*/
|
||||
its_number = find_first_zero_bit(&its_list_map, ITS_LIST_MAX);
|
||||
if (its_number >= ITS_LIST_MAX) {
|
||||
its_number = find_first_zero_bit(&its_list_map, GICv4_ITS_LIST_MAX);
|
||||
if (its_number >= GICv4_ITS_LIST_MAX) {
|
||||
pr_err("ITS@%pa: No ITSList entry available!\n",
|
||||
&res->start);
|
||||
return -EINVAL;
|
||||
|
@ -2944,6 +3152,7 @@ static int __init its_probe_one(struct resource *res,
|
|||
its->base = its_base;
|
||||
its->phys_base = res->start;
|
||||
its->ite_size = GITS_TYPER_ITT_ENTRY_SIZE(typer);
|
||||
its->device_ids = GITS_TYPER_DEVBITS(typer);
|
||||
its->is_v4 = !!(typer & GITS_TYPER_VLPIS);
|
||||
if (its->is_v4) {
|
||||
if (!(typer & GITS_TYPER_VMOVP)) {
|
||||
|
@ -2951,6 +3160,8 @@ static int __init its_probe_one(struct resource *res,
|
|||
if (err < 0)
|
||||
goto out_free_its;
|
||||
|
||||
its->list_nr = err;
|
||||
|
||||
pr_info("ITS@%pa: Using ITS number %d\n",
|
||||
&res->start, err);
|
||||
} else {
|
||||
|
@ -2967,6 +3178,9 @@ static int __init its_probe_one(struct resource *res,
|
|||
goto out_free_its;
|
||||
}
|
||||
its->cmd_write = its->cmd_base;
|
||||
its->fwnode_handle = handle;
|
||||
its->get_msi_base = its_irq_get_msi_base;
|
||||
its->msi_domain_flags = IRQ_DOMAIN_FLAG_MSI_REMAP;
|
||||
|
||||
its_enable_quirks(its);
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ struct gic_chip_data {
|
|||
struct irq_domain *domain;
|
||||
u64 redist_stride;
|
||||
u32 nr_redist_regions;
|
||||
bool has_rss;
|
||||
unsigned int irq_nr;
|
||||
struct partition_desc *ppi_descs[16];
|
||||
};
|
||||
|
@ -63,7 +64,9 @@ static struct gic_chip_data gic_data __read_mostly;
|
|||
static struct static_key supports_deactivate = STATIC_KEY_INIT_TRUE;
|
||||
|
||||
static struct gic_kvm_info gic_v3_kvm_info;
|
||||
static DEFINE_PER_CPU(bool, has_rss);
|
||||
|
||||
#define MPIDR_RS(mpidr) (((mpidr) & 0xF0UL) >> 4)
|
||||
#define gic_data_rdist() (this_cpu_ptr(gic_data.rdists.rdist))
|
||||
#define gic_data_rdist_rd_base() (gic_data_rdist()->rd_base)
|
||||
#define gic_data_rdist_sgi_base() (gic_data_rdist_rd_base() + SZ_64K)
|
||||
|
@ -526,6 +529,10 @@ static void gic_update_vlpi_properties(void)
|
|||
|
||||
static void gic_cpu_sys_reg_init(void)
|
||||
{
|
||||
int i, cpu = smp_processor_id();
|
||||
u64 mpidr = cpu_logical_map(cpu);
|
||||
u64 need_rss = MPIDR_RS(mpidr);
|
||||
|
||||
/*
|
||||
* Need to check that the SRE bit has actually been set. If
|
||||
* not, it means that SRE is disabled at EL2. We're going to
|
||||
|
@ -557,6 +564,30 @@ static void gic_cpu_sys_reg_init(void)
|
|||
|
||||
/* ... and let's hit the road... */
|
||||
gic_write_grpen1(1);
|
||||
|
||||
/* Keep the RSS capability status in per_cpu variable */
|
||||
per_cpu(has_rss, cpu) = !!(gic_read_ctlr() & ICC_CTLR_EL1_RSS);
|
||||
|
||||
/* Check all the CPUs have capable of sending SGIs to other CPUs */
|
||||
for_each_online_cpu(i) {
|
||||
bool have_rss = per_cpu(has_rss, i) && per_cpu(has_rss, cpu);
|
||||
|
||||
need_rss |= MPIDR_RS(cpu_logical_map(i));
|
||||
if (need_rss && (!have_rss))
|
||||
pr_crit("CPU%d (%lx) can't SGI CPU%d (%lx), no RSS\n",
|
||||
cpu, (unsigned long)mpidr,
|
||||
i, (unsigned long)cpu_logical_map(i));
|
||||
}
|
||||
|
||||
/**
|
||||
* GIC spec says, when ICC_CTLR_EL1.RSS==1 and GICD_TYPER.RSS==0,
|
||||
* writing ICC_ASGI1R_EL1 register with RS != 0 is a CONSTRAINED
|
||||
* UNPREDICTABLE choice of :
|
||||
* - The write is ignored.
|
||||
* - The RS field is treated as 0.
|
||||
*/
|
||||
if (need_rss && (!gic_data.has_rss))
|
||||
pr_crit_once("RSS is required but GICD doesn't support it\n");
|
||||
}
|
||||
|
||||
static int gic_dist_supports_lpis(void)
|
||||
|
@ -591,6 +622,9 @@ static void gic_cpu_init(void)
|
|||
|
||||
#ifdef CONFIG_SMP
|
||||
|
||||
#define MPIDR_TO_SGI_RS(mpidr) (MPIDR_RS(mpidr) << ICC_SGI1R_RS_SHIFT)
|
||||
#define MPIDR_TO_SGI_CLUSTER_ID(mpidr) ((mpidr) & ~0xFUL)
|
||||
|
||||
static int gic_starting_cpu(unsigned int cpu)
|
||||
{
|
||||
gic_cpu_init();
|
||||
|
@ -605,13 +639,6 @@ static u16 gic_compute_target_list(int *base_cpu, const struct cpumask *mask,
|
|||
u16 tlist = 0;
|
||||
|
||||
while (cpu < nr_cpu_ids) {
|
||||
/*
|
||||
* If we ever get a cluster of more than 16 CPUs, just
|
||||
* scream and skip that CPU.
|
||||
*/
|
||||
if (WARN_ON((mpidr & 0xff) >= 16))
|
||||
goto out;
|
||||
|
||||
tlist |= 1 << (mpidr & 0xf);
|
||||
|
||||
next_cpu = cpumask_next(cpu, mask);
|
||||
|
@ -621,7 +648,7 @@ static u16 gic_compute_target_list(int *base_cpu, const struct cpumask *mask,
|
|||
|
||||
mpidr = cpu_logical_map(cpu);
|
||||
|
||||
if (cluster_id != (mpidr & ~0xffUL)) {
|
||||
if (cluster_id != MPIDR_TO_SGI_CLUSTER_ID(mpidr)) {
|
||||
cpu--;
|
||||
goto out;
|
||||
}
|
||||
|
@ -643,6 +670,7 @@ static void gic_send_sgi(u64 cluster_id, u16 tlist, unsigned int irq)
|
|||
MPIDR_TO_SGI_AFFINITY(cluster_id, 2) |
|
||||
irq << ICC_SGI1R_SGI_ID_SHIFT |
|
||||
MPIDR_TO_SGI_AFFINITY(cluster_id, 1) |
|
||||
MPIDR_TO_SGI_RS(cluster_id) |
|
||||
tlist << ICC_SGI1R_TARGET_LIST_SHIFT);
|
||||
|
||||
pr_debug("CPU%d: ICC_SGI1R_EL1 %llx\n", smp_processor_id(), val);
|
||||
|
@ -663,7 +691,7 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq)
|
|||
smp_wmb();
|
||||
|
||||
for_each_cpu(cpu, mask) {
|
||||
unsigned long cluster_id = cpu_logical_map(cpu) & ~0xffUL;
|
||||
u64 cluster_id = MPIDR_TO_SGI_CLUSTER_ID(cpu_logical_map(cpu));
|
||||
u16 tlist;
|
||||
|
||||
tlist = gic_compute_target_list(&cpu, mask, cluster_id);
|
||||
|
@ -1007,6 +1035,10 @@ static int __init gic_init_bases(void __iomem *dist_base,
|
|||
goto out_free;
|
||||
}
|
||||
|
||||
gic_data.has_rss = !!(typer & GICD_TYPER_RSS);
|
||||
pr_info("Distributor has %sRange Selector support\n",
|
||||
gic_data.has_rss ? "" : "no ");
|
||||
|
||||
set_handle_irq(gic_handle_irq);
|
||||
|
||||
gic_update_vlpi_properties();
|
||||
|
|
414
drivers/irqchip/irq-meson-gpio.c
Normal file
414
drivers/irqchip/irq-meson-gpio.c
Normal file
|
@ -0,0 +1,414 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Endless Mobile, Inc.
|
||||
* Author: Carlo Caione <carlo@endlessm.com>
|
||||
* Copyright (c) 2016 BayLibre, SAS.
|
||||
* Author: Jerome Brunet <jbrunet@baylibre.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of version 2 of the GNU General Public License 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/>.
|
||||
* The full GNU General Public License is included in this distribution
|
||||
* in the file called COPYING.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
|
||||
#define NUM_CHANNEL 8
|
||||
#define MAX_INPUT_MUX 256
|
||||
|
||||
#define REG_EDGE_POL 0x00
|
||||
#define REG_PIN_03_SEL 0x04
|
||||
#define REG_PIN_47_SEL 0x08
|
||||
#define REG_FILTER_SEL 0x0c
|
||||
|
||||
#define REG_EDGE_POL_MASK(x) (BIT(x) | BIT(16 + (x)))
|
||||
#define REG_EDGE_POL_EDGE(x) BIT(x)
|
||||
#define REG_EDGE_POL_LOW(x) BIT(16 + (x))
|
||||
#define REG_PIN_SEL_SHIFT(x) (((x) % 4) * 8)
|
||||
#define REG_FILTER_SEL_SHIFT(x) ((x) * 4)
|
||||
|
||||
struct meson_gpio_irq_params {
|
||||
unsigned int nr_hwirq;
|
||||
};
|
||||
|
||||
static const struct meson_gpio_irq_params meson8b_params = {
|
||||
.nr_hwirq = 119,
|
||||
};
|
||||
|
||||
static const struct meson_gpio_irq_params gxbb_params = {
|
||||
.nr_hwirq = 133,
|
||||
};
|
||||
|
||||
static const struct meson_gpio_irq_params gxl_params = {
|
||||
.nr_hwirq = 110,
|
||||
};
|
||||
|
||||
static const struct of_device_id meson_irq_gpio_matches[] = {
|
||||
{ .compatible = "amlogic,meson8b-gpio-intc", .data = &meson8b_params },
|
||||
{ .compatible = "amlogic,meson-gxbb-gpio-intc", .data = &gxbb_params },
|
||||
{ .compatible = "amlogic,meson-gxl-gpio-intc", .data = &gxl_params },
|
||||
{ }
|
||||
};
|
||||
|
||||
struct meson_gpio_irq_controller {
|
||||
unsigned int nr_hwirq;
|
||||
void __iomem *base;
|
||||
u32 channel_irqs[NUM_CHANNEL];
|
||||
DECLARE_BITMAP(channel_map, NUM_CHANNEL);
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
static void meson_gpio_irq_update_bits(struct meson_gpio_irq_controller *ctl,
|
||||
unsigned int reg, u32 mask, u32 val)
|
||||
{
|
||||
u32 tmp;
|
||||
|
||||
tmp = readl_relaxed(ctl->base + reg);
|
||||
tmp &= ~mask;
|
||||
tmp |= val;
|
||||
writel_relaxed(tmp, ctl->base + reg);
|
||||
}
|
||||
|
||||
static unsigned int meson_gpio_irq_channel_to_reg(unsigned int channel)
|
||||
{
|
||||
return (channel < 4) ? REG_PIN_03_SEL : REG_PIN_47_SEL;
|
||||
}
|
||||
|
||||
static int
|
||||
meson_gpio_irq_request_channel(struct meson_gpio_irq_controller *ctl,
|
||||
unsigned long hwirq,
|
||||
u32 **channel_hwirq)
|
||||
{
|
||||
unsigned int reg, idx;
|
||||
|
||||
spin_lock(&ctl->lock);
|
||||
|
||||
/* Find a free channel */
|
||||
idx = find_first_zero_bit(ctl->channel_map, NUM_CHANNEL);
|
||||
if (idx >= NUM_CHANNEL) {
|
||||
spin_unlock(&ctl->lock);
|
||||
pr_err("No channel available\n");
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
/* Mark the channel as used */
|
||||
set_bit(idx, ctl->channel_map);
|
||||
|
||||
/*
|
||||
* Setup the mux of the channel to route the signal of the pad
|
||||
* to the appropriate input of the GIC
|
||||
*/
|
||||
reg = meson_gpio_irq_channel_to_reg(idx);
|
||||
meson_gpio_irq_update_bits(ctl, reg,
|
||||
0xff << REG_PIN_SEL_SHIFT(idx),
|
||||
hwirq << REG_PIN_SEL_SHIFT(idx));
|
||||
|
||||
/*
|
||||
* Get the hwirq number assigned to this channel through
|
||||
* a pointer the channel_irq table. The added benifit of this
|
||||
* method is that we can also retrieve the channel index with
|
||||
* it, using the table base.
|
||||
*/
|
||||
*channel_hwirq = &(ctl->channel_irqs[idx]);
|
||||
|
||||
spin_unlock(&ctl->lock);
|
||||
|
||||
pr_debug("hwirq %lu assigned to channel %d - irq %u\n",
|
||||
hwirq, idx, **channel_hwirq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
meson_gpio_irq_get_channel_idx(struct meson_gpio_irq_controller *ctl,
|
||||
u32 *channel_hwirq)
|
||||
{
|
||||
return channel_hwirq - ctl->channel_irqs;
|
||||
}
|
||||
|
||||
static void
|
||||
meson_gpio_irq_release_channel(struct meson_gpio_irq_controller *ctl,
|
||||
u32 *channel_hwirq)
|
||||
{
|
||||
unsigned int idx;
|
||||
|
||||
idx = meson_gpio_irq_get_channel_idx(ctl, channel_hwirq);
|
||||
clear_bit(idx, ctl->channel_map);
|
||||
}
|
||||
|
||||
static int meson_gpio_irq_type_setup(struct meson_gpio_irq_controller *ctl,
|
||||
unsigned int type,
|
||||
u32 *channel_hwirq)
|
||||
{
|
||||
u32 val = 0;
|
||||
unsigned int idx;
|
||||
|
||||
idx = meson_gpio_irq_get_channel_idx(ctl, channel_hwirq);
|
||||
|
||||
/*
|
||||
* The controller has a filter block to operate in either LEVEL or
|
||||
* EDGE mode, then signal is sent to the GIC. To enable LEVEL_LOW and
|
||||
* EDGE_FALLING support (which the GIC does not support), the filter
|
||||
* block is also able to invert the input signal it gets before
|
||||
* providing it to the GIC.
|
||||
*/
|
||||
type &= IRQ_TYPE_SENSE_MASK;
|
||||
|
||||
if (type == IRQ_TYPE_EDGE_BOTH)
|
||||
return -EINVAL;
|
||||
|
||||
if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))
|
||||
val |= REG_EDGE_POL_EDGE(idx);
|
||||
|
||||
if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING))
|
||||
val |= REG_EDGE_POL_LOW(idx);
|
||||
|
||||
spin_lock(&ctl->lock);
|
||||
|
||||
meson_gpio_irq_update_bits(ctl, REG_EDGE_POL,
|
||||
REG_EDGE_POL_MASK(idx), val);
|
||||
|
||||
spin_unlock(&ctl->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int meson_gpio_irq_type_output(unsigned int type)
|
||||
{
|
||||
unsigned int sense = type & IRQ_TYPE_SENSE_MASK;
|
||||
|
||||
type &= ~IRQ_TYPE_SENSE_MASK;
|
||||
|
||||
/*
|
||||
* The polarity of the signal provided to the GIC should always
|
||||
* be high.
|
||||
*/
|
||||
if (sense & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW))
|
||||
type |= IRQ_TYPE_LEVEL_HIGH;
|
||||
else if (sense & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))
|
||||
type |= IRQ_TYPE_EDGE_RISING;
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
static int meson_gpio_irq_set_type(struct irq_data *data, unsigned int type)
|
||||
{
|
||||
struct meson_gpio_irq_controller *ctl = data->domain->host_data;
|
||||
u32 *channel_hwirq = irq_data_get_irq_chip_data(data);
|
||||
int ret;
|
||||
|
||||
ret = meson_gpio_irq_type_setup(ctl, type, channel_hwirq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return irq_chip_set_type_parent(data,
|
||||
meson_gpio_irq_type_output(type));
|
||||
}
|
||||
|
||||
static struct irq_chip meson_gpio_irq_chip = {
|
||||
.name = "meson-gpio-irqchip",
|
||||
.irq_mask = irq_chip_mask_parent,
|
||||
.irq_unmask = irq_chip_unmask_parent,
|
||||
.irq_eoi = irq_chip_eoi_parent,
|
||||
.irq_set_type = meson_gpio_irq_set_type,
|
||||
.irq_retrigger = irq_chip_retrigger_hierarchy,
|
||||
#ifdef CONFIG_SMP
|
||||
.irq_set_affinity = irq_chip_set_affinity_parent,
|
||||
#endif
|
||||
.flags = IRQCHIP_SET_TYPE_MASKED,
|
||||
};
|
||||
|
||||
static int meson_gpio_irq_domain_translate(struct irq_domain *domain,
|
||||
struct irq_fwspec *fwspec,
|
||||
unsigned long *hwirq,
|
||||
unsigned int *type)
|
||||
{
|
||||
if (is_of_node(fwspec->fwnode) && fwspec->param_count == 2) {
|
||||
*hwirq = fwspec->param[0];
|
||||
*type = fwspec->param[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int meson_gpio_irq_allocate_gic_irq(struct irq_domain *domain,
|
||||
unsigned int virq,
|
||||
u32 hwirq,
|
||||
unsigned int type)
|
||||
{
|
||||
struct irq_fwspec fwspec;
|
||||
|
||||
fwspec.fwnode = domain->parent->fwnode;
|
||||
fwspec.param_count = 3;
|
||||
fwspec.param[0] = 0; /* SPI */
|
||||
fwspec.param[1] = hwirq;
|
||||
fwspec.param[2] = meson_gpio_irq_type_output(type);
|
||||
|
||||
return irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
|
||||
}
|
||||
|
||||
static int meson_gpio_irq_domain_alloc(struct irq_domain *domain,
|
||||
unsigned int virq,
|
||||
unsigned int nr_irqs,
|
||||
void *data)
|
||||
{
|
||||
struct irq_fwspec *fwspec = data;
|
||||
struct meson_gpio_irq_controller *ctl = domain->host_data;
|
||||
unsigned long hwirq;
|
||||
u32 *channel_hwirq;
|
||||
unsigned int type;
|
||||
int ret;
|
||||
|
||||
if (WARN_ON(nr_irqs != 1))
|
||||
return -EINVAL;
|
||||
|
||||
ret = meson_gpio_irq_domain_translate(domain, fwspec, &hwirq, &type);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = meson_gpio_irq_request_channel(ctl, hwirq, &channel_hwirq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = meson_gpio_irq_allocate_gic_irq(domain, virq,
|
||||
*channel_hwirq, type);
|
||||
if (ret < 0) {
|
||||
pr_err("failed to allocate gic irq %u\n", *channel_hwirq);
|
||||
meson_gpio_irq_release_channel(ctl, channel_hwirq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
|
||||
&meson_gpio_irq_chip, channel_hwirq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void meson_gpio_irq_domain_free(struct irq_domain *domain,
|
||||
unsigned int virq,
|
||||
unsigned int nr_irqs)
|
||||
{
|
||||
struct meson_gpio_irq_controller *ctl = domain->host_data;
|
||||
struct irq_data *irq_data;
|
||||
u32 *channel_hwirq;
|
||||
|
||||
if (WARN_ON(nr_irqs != 1))
|
||||
return;
|
||||
|
||||
irq_domain_free_irqs_parent(domain, virq, 1);
|
||||
|
||||
irq_data = irq_domain_get_irq_data(domain, virq);
|
||||
channel_hwirq = irq_data_get_irq_chip_data(irq_data);
|
||||
|
||||
meson_gpio_irq_release_channel(ctl, channel_hwirq);
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops meson_gpio_irq_domain_ops = {
|
||||
.alloc = meson_gpio_irq_domain_alloc,
|
||||
.free = meson_gpio_irq_domain_free,
|
||||
.translate = meson_gpio_irq_domain_translate,
|
||||
};
|
||||
|
||||
static int __init meson_gpio_irq_parse_dt(struct device_node *node,
|
||||
struct meson_gpio_irq_controller *ctl)
|
||||
{
|
||||
const struct of_device_id *match;
|
||||
const struct meson_gpio_irq_params *params;
|
||||
int ret;
|
||||
|
||||
match = of_match_node(meson_irq_gpio_matches, node);
|
||||
if (!match)
|
||||
return -ENODEV;
|
||||
|
||||
params = match->data;
|
||||
ctl->nr_hwirq = params->nr_hwirq;
|
||||
|
||||
ret = of_property_read_variable_u32_array(node,
|
||||
"amlogic,channel-interrupts",
|
||||
ctl->channel_irqs,
|
||||
NUM_CHANNEL,
|
||||
NUM_CHANNEL);
|
||||
if (ret < 0) {
|
||||
pr_err("can't get %d channel interrupts\n", NUM_CHANNEL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init meson_gpio_irq_of_init(struct device_node *node,
|
||||
struct device_node *parent)
|
||||
{
|
||||
struct irq_domain *domain, *parent_domain;
|
||||
struct meson_gpio_irq_controller *ctl;
|
||||
int ret;
|
||||
|
||||
if (!parent) {
|
||||
pr_err("missing parent interrupt node\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
parent_domain = irq_find_host(parent);
|
||||
if (!parent_domain) {
|
||||
pr_err("unable to obtain parent domain\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
|
||||
if (!ctl)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&ctl->lock);
|
||||
|
||||
ctl->base = of_iomap(node, 0);
|
||||
if (!ctl->base) {
|
||||
ret = -ENOMEM;
|
||||
goto free_ctl;
|
||||
}
|
||||
|
||||
ret = meson_gpio_irq_parse_dt(node, ctl);
|
||||
if (ret)
|
||||
goto free_channel_irqs;
|
||||
|
||||
domain = irq_domain_create_hierarchy(parent_domain, 0, ctl->nr_hwirq,
|
||||
of_node_to_fwnode(node),
|
||||
&meson_gpio_irq_domain_ops,
|
||||
ctl);
|
||||
if (!domain) {
|
||||
pr_err("failed to add domain\n");
|
||||
ret = -ENODEV;
|
||||
goto free_channel_irqs;
|
||||
}
|
||||
|
||||
pr_info("%d to %d gpio interrupt mux initialized\n",
|
||||
ctl->nr_hwirq, NUM_CHANNEL);
|
||||
|
||||
return 0;
|
||||
|
||||
free_channel_irqs:
|
||||
iounmap(ctl->base);
|
||||
free_ctl:
|
||||
kfree(ctl);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
IRQCHIP_DECLARE(meson_gpio_intc, "amlogic,meson-gpio-intc",
|
||||
meson_gpio_irq_of_init);
|
|
@ -389,9 +389,8 @@ MODULE_DEVICE_TABLE(of, intc_irqpin_dt_ids);
|
|||
|
||||
static int intc_irqpin_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct intc_irqpin_config *config = NULL;
|
||||
const struct intc_irqpin_config *config;
|
||||
struct device *dev = &pdev->dev;
|
||||
const struct of_device_id *of_id;
|
||||
struct intc_irqpin_priv *p;
|
||||
struct intc_irqpin_iomem *i;
|
||||
struct resource *io[INTC_IRQPIN_REG_NR];
|
||||
|
@ -422,11 +421,9 @@ static int intc_irqpin_probe(struct platform_device *pdev)
|
|||
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;
|
||||
config = of_device_get_match_data(dev);
|
||||
if (config)
|
||||
p->needs_clk = config->needs_clk;
|
||||
}
|
||||
|
||||
p->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(p->clk)) {
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
#define GICD_CTLR_ENABLE_SS_G1 (1U << 1)
|
||||
#define GICD_CTLR_ENABLE_SS_G0 (1U << 0)
|
||||
|
||||
#define GICD_TYPER_RSS (1U << 26)
|
||||
#define GICD_TYPER_LPIS (1U << 17)
|
||||
#define GICD_TYPER_MBIS (1U << 16)
|
||||
|
||||
|
@ -459,6 +460,7 @@
|
|||
#define ICC_CTLR_EL1_SEIS_MASK (0x1 << ICC_CTLR_EL1_SEIS_SHIFT)
|
||||
#define ICC_CTLR_EL1_A3V_SHIFT 15
|
||||
#define ICC_CTLR_EL1_A3V_MASK (0x1 << ICC_CTLR_EL1_A3V_SHIFT)
|
||||
#define ICC_CTLR_EL1_RSS (0x1 << 18)
|
||||
#define ICC_PMR_EL1_SHIFT 0
|
||||
#define ICC_PMR_EL1_MASK (0xff << ICC_PMR_EL1_SHIFT)
|
||||
#define ICC_BPR0_EL1_SHIFT 0
|
||||
|
@ -547,6 +549,8 @@
|
|||
#define ICC_SGI1R_AFFINITY_2_SHIFT 32
|
||||
#define ICC_SGI1R_AFFINITY_2_MASK (0xffULL << ICC_SGI1R_AFFINITY_2_SHIFT)
|
||||
#define ICC_SGI1R_IRQ_ROUTING_MODE_BIT 40
|
||||
#define ICC_SGI1R_RS_SHIFT 44
|
||||
#define ICC_SGI1R_RS_MASK (0xfULL << ICC_SGI1R_RS_SHIFT)
|
||||
#define ICC_SGI1R_AFFINITY_3_SHIFT 48
|
||||
#define ICC_SGI1R_AFFINITY_3_MASK (0xffULL << ICC_SGI1R_AFFINITY_3_SHIFT)
|
||||
|
||||
|
|
|
@ -20,6 +20,12 @@
|
|||
|
||||
struct its_vpe;
|
||||
|
||||
/*
|
||||
* Maximum number of ITTs when GITS_TYPER.VMOVP == 0, using the
|
||||
* ITSList mechanism to perform inter-ITS synchronization.
|
||||
*/
|
||||
#define GICv4_ITS_LIST_MAX 16
|
||||
|
||||
/* Embedded in kvm.arch */
|
||||
struct its_vm {
|
||||
struct fwnode_handle *fwnode;
|
||||
|
@ -30,6 +36,7 @@ struct its_vm {
|
|||
irq_hw_number_t db_lpi_base;
|
||||
unsigned long *db_bitmap;
|
||||
int nr_db_lpis;
|
||||
u32 vlpi_count[GICv4_ITS_LIST_MAX];
|
||||
};
|
||||
|
||||
/* Embedded in kvm_vcpu.arch */
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include <linux/types.h>
|
||||
#include <linux/irqhandler.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/radix-tree.h>
|
||||
|
||||
struct device_node;
|
||||
|
@ -176,6 +177,7 @@ struct irq_domain {
|
|||
unsigned int revmap_direct_max_irq;
|
||||
unsigned int revmap_size;
|
||||
struct radix_tree_root revmap_tree;
|
||||
struct mutex revmap_tree_mutex;
|
||||
unsigned int linear_revmap[];
|
||||
};
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
static LIST_HEAD(irq_domain_list);
|
||||
static DEFINE_MUTEX(irq_domain_mutex);
|
||||
|
||||
static DEFINE_MUTEX(revmap_trees_mutex);
|
||||
static struct irq_domain *irq_default_domain;
|
||||
|
||||
static void irq_domain_check_hierarchy(struct irq_domain *domain);
|
||||
|
@ -211,6 +210,7 @@ struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
|
|||
|
||||
/* Fill structure */
|
||||
INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
|
||||
mutex_init(&domain->revmap_tree_mutex);
|
||||
domain->ops = ops;
|
||||
domain->host_data = host_data;
|
||||
domain->hwirq_max = hwirq_max;
|
||||
|
@ -462,9 +462,9 @@ static void irq_domain_clear_mapping(struct irq_domain *domain,
|
|||
if (hwirq < domain->revmap_size) {
|
||||
domain->linear_revmap[hwirq] = 0;
|
||||
} else {
|
||||
mutex_lock(&revmap_trees_mutex);
|
||||
mutex_lock(&domain->revmap_tree_mutex);
|
||||
radix_tree_delete(&domain->revmap_tree, hwirq);
|
||||
mutex_unlock(&revmap_trees_mutex);
|
||||
mutex_unlock(&domain->revmap_tree_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -475,9 +475,9 @@ static void irq_domain_set_mapping(struct irq_domain *domain,
|
|||
if (hwirq < domain->revmap_size) {
|
||||
domain->linear_revmap[hwirq] = irq_data->irq;
|
||||
} else {
|
||||
mutex_lock(&revmap_trees_mutex);
|
||||
mutex_lock(&domain->revmap_tree_mutex);
|
||||
radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
|
||||
mutex_unlock(&revmap_trees_mutex);
|
||||
mutex_unlock(&domain->revmap_tree_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -945,7 +945,7 @@ static int virq_debug_show(struct seq_file *m, void *private)
|
|||
struct irq_desc *desc;
|
||||
struct irq_domain *domain;
|
||||
struct radix_tree_iter iter;
|
||||
void **slot;
|
||||
void __rcu **slot;
|
||||
int i;
|
||||
|
||||
seq_printf(m, " %-16s %-6s %-10s %-10s %s\n",
|
||||
|
@ -1453,17 +1453,17 @@ int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
|
|||
/* The irq_data was moved, fix the revmap to refer to the new location */
|
||||
static void irq_domain_fix_revmap(struct irq_data *d)
|
||||
{
|
||||
void **slot;
|
||||
void __rcu **slot;
|
||||
|
||||
if (d->hwirq < d->domain->revmap_size)
|
||||
return; /* Not using radix tree. */
|
||||
|
||||
/* Fix up the revmap. */
|
||||
mutex_lock(&revmap_trees_mutex);
|
||||
mutex_lock(&d->domain->revmap_tree_mutex);
|
||||
slot = radix_tree_lookup_slot(&d->domain->revmap_tree, d->hwirq);
|
||||
if (slot)
|
||||
radix_tree_replace_slot(&d->domain->revmap_tree, slot, d);
|
||||
mutex_unlock(&revmap_trees_mutex);
|
||||
mutex_unlock(&d->domain->revmap_tree_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue