mirror of https://gitee.com/openkylin/linux.git
Merge tag 'irqchip-4.20' of git://git.kernel.org/pub/scm/linux/kernel/git/maz/arm-platforms into irq/core
Pull irqchip updates from Marc Zyngier: - kexec/kdump support for EFI-based GICv3 platforms - Marvell SEI support - QC PDC fixes - GIC cleanups and optimizations - DT updates [ tglx: Dropped the madera driver as it breaks the build ]
This commit is contained in:
commit
a223464217
|
@ -5,6 +5,8 @@ The Marvell ICU (Interrupt Consolidation Unit) controller is
|
|||
responsible for collecting all wired-interrupt sources in the CP and
|
||||
communicating them to the GIC in the AP, the unit translates interrupt
|
||||
requests on input wires to MSG memory mapped transactions to the GIC.
|
||||
These messages will access a different GIC memory area depending on
|
||||
their type (NSR, SR, SEI, REI, etc).
|
||||
|
||||
Required properties:
|
||||
|
||||
|
@ -12,20 +14,23 @@ Required properties:
|
|||
|
||||
- reg: Should contain ICU registers location and length.
|
||||
|
||||
Subnodes: Each group of interrupt is declared as a subnode of the ICU,
|
||||
with their own compatible.
|
||||
|
||||
Required properties for the icu_nsr/icu_sei subnodes:
|
||||
|
||||
- compatible: Should be one of:
|
||||
* "marvell,cp110-icu-nsr"
|
||||
* "marvell,cp110-icu-sr"
|
||||
* "marvell,cp110-icu-sei"
|
||||
* "marvell,cp110-icu-rei"
|
||||
|
||||
- #interrupt-cells: Specifies the number of cells needed to encode an
|
||||
interrupt source. The value shall be 3.
|
||||
interrupt source. The value shall be 2.
|
||||
|
||||
The 1st cell is the group type of the ICU interrupt. Possible group
|
||||
types are:
|
||||
The 1st cell is the index of the interrupt in the ICU unit.
|
||||
|
||||
ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure
|
||||
ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure
|
||||
ICU_GRP_SEI (0x4) : System error interrupt
|
||||
ICU_GRP_REI (0x5) : RAM error interrupt
|
||||
|
||||
The 2nd cell is the index of the interrupt in the ICU unit.
|
||||
|
||||
The 3rd cell is the type of the interrupt. See arm,gic.txt for
|
||||
The 2nd cell is the type of the interrupt. See arm,gic.txt for
|
||||
details.
|
||||
|
||||
- interrupt-controller: Identifies the node as an interrupt
|
||||
|
@ -35,17 +40,73 @@ Required properties:
|
|||
that allows to trigger interrupts using MSG memory mapped
|
||||
transactions.
|
||||
|
||||
Note: each 'interrupts' property referring to any 'icu_xxx' node shall
|
||||
have a different number within [0:206].
|
||||
|
||||
Example:
|
||||
|
||||
icu: interrupt-controller@1e0000 {
|
||||
compatible = "marvell,cp110-icu";
|
||||
reg = <0x1e0000 0x10>;
|
||||
reg = <0x1e0000 0x440>;
|
||||
|
||||
CP110_LABEL(icu_nsr): interrupt-controller@10 {
|
||||
compatible = "marvell,cp110-icu-nsr";
|
||||
reg = <0x10 0x20>;
|
||||
#interrupt-cells = <2>;
|
||||
interrupt-controller;
|
||||
msi-parent = <&gicp>;
|
||||
};
|
||||
|
||||
CP110_LABEL(icu_sei): interrupt-controller@50 {
|
||||
compatible = "marvell,cp110-icu-sei";
|
||||
reg = <0x50 0x10>;
|
||||
#interrupt-cells = <2>;
|
||||
interrupt-controller;
|
||||
msi-parent = <&sei>;
|
||||
};
|
||||
};
|
||||
|
||||
node1 {
|
||||
interrupt-parent = <&icu_nsr>;
|
||||
interrupts = <106 IRQ_TYPE_LEVEL_HIGH>;
|
||||
};
|
||||
|
||||
node2 {
|
||||
interrupt-parent = <&icu_sei>;
|
||||
interrupts = <107 IRQ_TYPE_LEVEL_HIGH>;
|
||||
};
|
||||
|
||||
/* Would not work with the above nodes */
|
||||
node3 {
|
||||
interrupt-parent = <&icu_nsr>;
|
||||
interrupts = <107 IRQ_TYPE_LEVEL_HIGH>;
|
||||
};
|
||||
|
||||
The legacy bindings were different in this way:
|
||||
|
||||
- #interrupt-cells: The value was 3.
|
||||
The 1st cell was the group type of the ICU interrupt. Possible
|
||||
group types were:
|
||||
ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure
|
||||
ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure
|
||||
ICU_GRP_SEI (0x4) : System error interrupt
|
||||
ICU_GRP_REI (0x5) : RAM error interrupt
|
||||
The 2nd cell was the index of the interrupt in the ICU unit.
|
||||
The 3rd cell was the type of the interrupt. See arm,gic.txt for
|
||||
details.
|
||||
|
||||
Example:
|
||||
|
||||
icu: interrupt-controller@1e0000 {
|
||||
compatible = "marvell,cp110-icu";
|
||||
reg = <0x1e0000 0x440>;
|
||||
|
||||
#interrupt-cells = <3>;
|
||||
interrupt-controller;
|
||||
msi-parent = <&gicp>;
|
||||
};
|
||||
|
||||
usb3h0: usb3@500000 {
|
||||
node1 {
|
||||
interrupt-parent = <&icu>;
|
||||
interrupts = <ICU_GRP_NSR 106 IRQ_TYPE_LEVEL_HIGH>;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
Marvell SEI (System Error Interrupt) Controller
|
||||
-----------------------------------------------
|
||||
|
||||
Marvell SEI (System Error Interrupt) controller is an interrupt
|
||||
aggregator. It receives interrupts from several sources and aggregates
|
||||
them to a single interrupt line (an SPI) on the parent interrupt
|
||||
controller.
|
||||
|
||||
This interrupt controller can handle up to 64 SEIs, a set comes from the
|
||||
AP and is wired while a second set comes from the CPs by the mean of
|
||||
MSIs.
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible: should be one of:
|
||||
* "marvell,ap806-sei"
|
||||
- reg: SEI registers location and length.
|
||||
- interrupts: identifies the parent IRQ that will be triggered.
|
||||
- #interrupt-cells: number of cells to define an SEI wired interrupt
|
||||
coming from the AP, should be 1. The cell is the IRQ
|
||||
number.
|
||||
- interrupt-controller: identifies the node as an interrupt controller
|
||||
for AP interrupts.
|
||||
- msi-controller: identifies the node as an MSI controller for the CPs
|
||||
interrupts.
|
||||
|
||||
Example:
|
||||
|
||||
sei: interrupt-controller@3f0200 {
|
||||
compatible = "marvell,ap806-sei";
|
||||
reg = <0x3f0200 0x40>;
|
||||
interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>;
|
||||
#interrupt-cells = <1>;
|
||||
interrupt-controller;
|
||||
msi-controller;
|
||||
};
|
|
@ -2,10 +2,12 @@ DT bindings for the R-Mobile/R-Car/RZ/G interrupt controller
|
|||
|
||||
Required properties:
|
||||
|
||||
- compatible: has to be "renesas,irqc-<soctype>", "renesas,irqc" as fallback.
|
||||
- compatible: must be "renesas,irqc-<soctype>" or "renesas,intc-ex-<soctype>",
|
||||
and "renesas,irqc" as fallback.
|
||||
Examples with soctypes are:
|
||||
- "renesas,irqc-r8a73a4" (R-Mobile APE6)
|
||||
- "renesas,irqc-r8a7743" (RZ/G1M)
|
||||
- "renesas,irqc-r8a7744" (RZ/G1N)
|
||||
- "renesas,irqc-r8a7745" (RZ/G1E)
|
||||
- "renesas,irqc-r8a77470" (RZ/G1C)
|
||||
- "renesas,irqc-r8a7790" (R-Car H2)
|
||||
|
@ -19,6 +21,7 @@ Required properties:
|
|||
- "renesas,intc-ex-r8a77965" (R-Car M3-N)
|
||||
- "renesas,intc-ex-r8a77970" (R-Car V3M)
|
||||
- "renesas,intc-ex-r8a77980" (R-Car V3H)
|
||||
- "renesas,intc-ex-r8a77990" (R-Car E3)
|
||||
- "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
|
||||
|
|
|
@ -128,6 +128,7 @@ config ARCH_MVEBU
|
|||
select MVEBU_ICU
|
||||
select MVEBU_ODMI
|
||||
select MVEBU_PIC
|
||||
select MVEBU_SEI
|
||||
select OF_GPIO
|
||||
select PINCTRL
|
||||
select PINCTRL_ARMADA_37XX
|
||||
|
|
|
@ -321,11 +321,12 @@ void *platform_msi_get_host_data(struct irq_domain *domain)
|
|||
* Returns an irqdomain for @nvec interrupts
|
||||
*/
|
||||
struct irq_domain *
|
||||
platform_msi_create_device_domain(struct device *dev,
|
||||
unsigned int nvec,
|
||||
irq_write_msi_msg_t write_msi_msg,
|
||||
const struct irq_domain_ops *ops,
|
||||
void *host_data)
|
||||
__platform_msi_create_device_domain(struct device *dev,
|
||||
unsigned int nvec,
|
||||
bool is_tree,
|
||||
irq_write_msi_msg_t write_msi_msg,
|
||||
const struct irq_domain_ops *ops,
|
||||
void *host_data)
|
||||
{
|
||||
struct platform_msi_priv_data *data;
|
||||
struct irq_domain *domain;
|
||||
|
@ -336,7 +337,8 @@ platform_msi_create_device_domain(struct device *dev,
|
|||
return NULL;
|
||||
|
||||
data->host_data = host_data;
|
||||
domain = irq_domain_create_hierarchy(dev->msi_domain, 0, nvec,
|
||||
domain = irq_domain_create_hierarchy(dev->msi_domain, 0,
|
||||
is_tree ? 0 : nvec,
|
||||
dev->fwnode, ops, data);
|
||||
if (!domain)
|
||||
goto free_priv;
|
||||
|
|
|
@ -52,7 +52,8 @@ struct efi __read_mostly efi = {
|
|||
.properties_table = EFI_INVALID_TABLE_ADDR,
|
||||
.mem_attr_table = EFI_INVALID_TABLE_ADDR,
|
||||
.rng_seed = EFI_INVALID_TABLE_ADDR,
|
||||
.tpm_log = EFI_INVALID_TABLE_ADDR
|
||||
.tpm_log = EFI_INVALID_TABLE_ADDR,
|
||||
.mem_reserve = EFI_INVALID_TABLE_ADDR,
|
||||
};
|
||||
EXPORT_SYMBOL(efi);
|
||||
|
||||
|
@ -484,6 +485,7 @@ static __initdata efi_config_table_type_t common_tables[] = {
|
|||
{EFI_MEMORY_ATTRIBUTES_TABLE_GUID, "MEMATTR", &efi.mem_attr_table},
|
||||
{LINUX_EFI_RANDOM_SEED_TABLE_GUID, "RNG", &efi.rng_seed},
|
||||
{LINUX_EFI_TPM_EVENT_LOG_GUID, "TPMEventLog", &efi.tpm_log},
|
||||
{LINUX_EFI_MEMRESERVE_TABLE_GUID, "MEMRESERVE", &efi.mem_reserve},
|
||||
{NULL_GUID, NULL, NULL},
|
||||
};
|
||||
|
||||
|
@ -591,6 +593,29 @@ int __init efi_config_parse_tables(void *config_tables, int count, int sz,
|
|||
early_memunmap(tbl, sizeof(*tbl));
|
||||
}
|
||||
|
||||
if (efi.mem_reserve != EFI_INVALID_TABLE_ADDR) {
|
||||
unsigned long prsv = efi.mem_reserve;
|
||||
|
||||
while (prsv) {
|
||||
struct linux_efi_memreserve *rsv;
|
||||
|
||||
/* reserve the entry itself */
|
||||
memblock_reserve(prsv, sizeof(*rsv));
|
||||
|
||||
rsv = early_memremap(prsv, sizeof(*rsv));
|
||||
if (rsv == NULL) {
|
||||
pr_err("Could not map UEFI memreserve entry!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (rsv->size)
|
||||
memblock_reserve(rsv->base, rsv->size);
|
||||
|
||||
prsv = rsv->next;
|
||||
early_memunmap(rsv, sizeof(*rsv));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -937,6 +962,38 @@ bool efi_is_table_address(unsigned long phys_addr)
|
|||
return false;
|
||||
}
|
||||
|
||||
static DEFINE_SPINLOCK(efi_mem_reserve_persistent_lock);
|
||||
|
||||
int efi_mem_reserve_persistent(phys_addr_t addr, u64 size)
|
||||
{
|
||||
struct linux_efi_memreserve *rsv, *parent;
|
||||
|
||||
if (efi.mem_reserve == EFI_INVALID_TABLE_ADDR)
|
||||
return -ENODEV;
|
||||
|
||||
rsv = kmalloc(sizeof(*rsv), GFP_KERNEL);
|
||||
if (!rsv)
|
||||
return -ENOMEM;
|
||||
|
||||
parent = memremap(efi.mem_reserve, sizeof(*rsv), MEMREMAP_WB);
|
||||
if (!parent) {
|
||||
kfree(rsv);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rsv->base = addr;
|
||||
rsv->size = size;
|
||||
|
||||
spin_lock(&efi_mem_reserve_persistent_lock);
|
||||
rsv->next = parent->next;
|
||||
parent->next = __pa(rsv);
|
||||
spin_unlock(&efi_mem_reserve_persistent_lock);
|
||||
|
||||
memunmap(parent);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KEXEC
|
||||
static int update_efi_random_seed(struct notifier_block *nb,
|
||||
unsigned long code, void *unused)
|
||||
|
|
|
@ -69,6 +69,31 @@ static struct screen_info *setup_graphics(efi_system_table_t *sys_table_arg)
|
|||
return si;
|
||||
}
|
||||
|
||||
void install_memreserve_table(efi_system_table_t *sys_table_arg)
|
||||
{
|
||||
struct linux_efi_memreserve *rsv;
|
||||
efi_guid_t memreserve_table_guid = LINUX_EFI_MEMRESERVE_TABLE_GUID;
|
||||
efi_status_t status;
|
||||
|
||||
status = efi_call_early(allocate_pool, EFI_LOADER_DATA, sizeof(*rsv),
|
||||
(void **)&rsv);
|
||||
if (status != EFI_SUCCESS) {
|
||||
pr_efi_err(sys_table_arg, "Failed to allocate memreserve entry!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
rsv->next = 0;
|
||||
rsv->base = 0;
|
||||
rsv->size = 0;
|
||||
|
||||
status = efi_call_early(install_configuration_table,
|
||||
&memreserve_table_guid,
|
||||
rsv);
|
||||
if (status != EFI_SUCCESS)
|
||||
pr_efi_err(sys_table_arg, "Failed to install memreserve config table!\n");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This function handles the architcture specific differences between arm and
|
||||
* arm64 regarding where the kernel image must be loaded and any memory that
|
||||
|
@ -235,6 +260,8 @@ unsigned long efi_entry(void *handle, efi_system_table_t *sys_table,
|
|||
}
|
||||
}
|
||||
|
||||
install_memreserve_table(sys_table);
|
||||
|
||||
new_fdt_addr = fdt_addr;
|
||||
status = allocate_new_fdt_and_exit_boot(sys_table, handle,
|
||||
&new_fdt_addr, efi_get_max_fdt_addr(dram_base),
|
||||
|
|
|
@ -310,6 +310,9 @@ config MVEBU_ODMI
|
|||
config MVEBU_PIC
|
||||
bool
|
||||
|
||||
config MVEBU_SEI
|
||||
bool
|
||||
|
||||
config LS_SCFG_MSI
|
||||
def_bool y if SOC_LS1021A || ARCH_LAYERSCAPE
|
||||
depends on PCI && PCI_MSI
|
||||
|
|
|
@ -76,6 +76,7 @@ obj-$(CONFIG_MVEBU_GICP) += irq-mvebu-gicp.o
|
|||
obj-$(CONFIG_MVEBU_ICU) += irq-mvebu-icu.o
|
||||
obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o
|
||||
obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o
|
||||
obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o
|
||||
obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o
|
||||
obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o
|
||||
obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o
|
||||
|
|
|
@ -19,13 +19,16 @@
|
|||
#include <linux/acpi_iort.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/crash_dump.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dma-iommu.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/list_sort.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/memblock.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/of.h>
|
||||
|
@ -52,6 +55,7 @@
|
|||
#define ITS_FLAGS_SAVE_SUSPEND_STATE (1ULL << 3)
|
||||
|
||||
#define RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING (1 << 0)
|
||||
#define RDIST_FLAGS_RD_TABLES_PREALLOCATED (1 << 1)
|
||||
|
||||
static u32 lpi_id_bits;
|
||||
|
||||
|
@ -64,7 +68,7 @@ static u32 lpi_id_bits;
|
|||
#define LPI_PROPBASE_SZ ALIGN(BIT(LPI_NRBITS), SZ_64K)
|
||||
#define LPI_PENDBASE_SZ ALIGN(BIT(LPI_NRBITS) / 8, SZ_64K)
|
||||
|
||||
#define LPI_PROP_DEFAULT_PRIO 0xa0
|
||||
#define LPI_PROP_DEFAULT_PRIO GICD_INT_DEF_PRI
|
||||
|
||||
/*
|
||||
* Collection structure - just an ID, and a redistributor address to
|
||||
|
@ -173,6 +177,7 @@ static DEFINE_RAW_SPINLOCK(vmovp_lock);
|
|||
static DEFINE_IDA(its_vpeid_ida);
|
||||
|
||||
#define gic_data_rdist() (raw_cpu_ptr(gic_rdists->rdist))
|
||||
#define gic_data_rdist_cpu(cpu) (per_cpu_ptr(gic_rdists->rdist, cpu))
|
||||
#define gic_data_rdist_rd_base() (gic_data_rdist()->rd_base)
|
||||
#define gic_data_rdist_vlpi_base() (gic_data_rdist_rd_base() + SZ_128K)
|
||||
|
||||
|
@ -1028,7 +1033,7 @@ static inline u32 its_get_event_id(struct irq_data *d)
|
|||
static void lpi_write_config(struct irq_data *d, u8 clr, u8 set)
|
||||
{
|
||||
irq_hw_number_t hwirq;
|
||||
struct page *prop_page;
|
||||
void *va;
|
||||
u8 *cfg;
|
||||
|
||||
if (irqd_is_forwarded_to_vcpu(d)) {
|
||||
|
@ -1036,7 +1041,7 @@ static void lpi_write_config(struct irq_data *d, u8 clr, u8 set)
|
|||
u32 event = its_get_event_id(d);
|
||||
struct its_vlpi_map *map;
|
||||
|
||||
prop_page = its_dev->event_map.vm->vprop_page;
|
||||
va = page_address(its_dev->event_map.vm->vprop_page);
|
||||
map = &its_dev->event_map.vlpi_maps[event];
|
||||
hwirq = map->vintid;
|
||||
|
||||
|
@ -1044,11 +1049,11 @@ static void lpi_write_config(struct irq_data *d, u8 clr, u8 set)
|
|||
map->properties &= ~clr;
|
||||
map->properties |= set | LPI_PROP_GROUP1;
|
||||
} else {
|
||||
prop_page = gic_rdists->prop_page;
|
||||
va = gic_rdists->prop_table_va;
|
||||
hwirq = d->hwirq;
|
||||
}
|
||||
|
||||
cfg = page_address(prop_page) + hwirq - 8192;
|
||||
cfg = va + hwirq - 8192;
|
||||
*cfg &= ~clr;
|
||||
*cfg |= set | LPI_PROP_GROUP1;
|
||||
|
||||
|
@ -1597,6 +1602,15 @@ static void its_lpi_free(unsigned long *bitmap, u32 base, u32 nr_ids)
|
|||
kfree(bitmap);
|
||||
}
|
||||
|
||||
static void gic_reset_prop_table(void *va)
|
||||
{
|
||||
/* Priority 0xa0, Group-1, disabled */
|
||||
memset(va, LPI_PROP_DEFAULT_PRIO | LPI_PROP_GROUP1, LPI_PROPBASE_SZ);
|
||||
|
||||
/* Make sure the GIC will observe the written configuration */
|
||||
gic_flush_dcache_to_poc(va, LPI_PROPBASE_SZ);
|
||||
}
|
||||
|
||||
static struct page *its_allocate_prop_table(gfp_t gfp_flags)
|
||||
{
|
||||
struct page *prop_page;
|
||||
|
@ -1605,13 +1619,7 @@ static struct page *its_allocate_prop_table(gfp_t gfp_flags)
|
|||
if (!prop_page)
|
||||
return NULL;
|
||||
|
||||
/* Priority 0xa0, Group-1, disabled */
|
||||
memset(page_address(prop_page),
|
||||
LPI_PROP_DEFAULT_PRIO | LPI_PROP_GROUP1,
|
||||
LPI_PROPBASE_SZ);
|
||||
|
||||
/* Make sure the GIC will observe the written configuration */
|
||||
gic_flush_dcache_to_poc(page_address(prop_page), LPI_PROPBASE_SZ);
|
||||
gic_reset_prop_table(page_address(prop_page));
|
||||
|
||||
return prop_page;
|
||||
}
|
||||
|
@ -1622,20 +1630,74 @@ static void its_free_prop_table(struct page *prop_page)
|
|||
get_order(LPI_PROPBASE_SZ));
|
||||
}
|
||||
|
||||
static int __init its_alloc_lpi_tables(void)
|
||||
static bool gic_check_reserved_range(phys_addr_t addr, unsigned long size)
|
||||
{
|
||||
phys_addr_t paddr;
|
||||
phys_addr_t start, end, addr_end;
|
||||
u64 i;
|
||||
|
||||
lpi_id_bits = min_t(u32, GICD_TYPER_ID_BITS(gic_rdists->gicd_typer),
|
||||
ITS_MAX_LPI_NRBITS);
|
||||
gic_rdists->prop_page = its_allocate_prop_table(GFP_NOWAIT);
|
||||
if (!gic_rdists->prop_page) {
|
||||
pr_err("Failed to allocate PROPBASE\n");
|
||||
return -ENOMEM;
|
||||
/*
|
||||
* We don't bother checking for a kdump kernel as by
|
||||
* construction, the LPI tables are out of this kernel's
|
||||
* memory map.
|
||||
*/
|
||||
if (is_kdump_kernel())
|
||||
return true;
|
||||
|
||||
addr_end = addr + size - 1;
|
||||
|
||||
for_each_reserved_mem_region(i, &start, &end) {
|
||||
if (addr >= start && addr_end <= end)
|
||||
return true;
|
||||
}
|
||||
|
||||
paddr = page_to_phys(gic_rdists->prop_page);
|
||||
pr_info("GIC: using LPI property table @%pa\n", &paddr);
|
||||
/* Not found, not a good sign... */
|
||||
pr_warn("GICv3: Expected reserved range [%pa:%pa], not found\n",
|
||||
&addr, &addr_end);
|
||||
add_taint(TAINT_CRAP, LOCKDEP_STILL_OK);
|
||||
return false;
|
||||
}
|
||||
|
||||
static int gic_reserve_range(phys_addr_t addr, unsigned long size)
|
||||
{
|
||||
if (efi_enabled(EFI_CONFIG_TABLES))
|
||||
return efi_mem_reserve_persistent(addr, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init its_setup_lpi_prop_table(void)
|
||||
{
|
||||
if (gic_rdists->flags & RDIST_FLAGS_RD_TABLES_PREALLOCATED) {
|
||||
u64 val;
|
||||
|
||||
val = gicr_read_propbaser(gic_data_rdist_rd_base() + GICR_PROPBASER);
|
||||
lpi_id_bits = (val & GICR_PROPBASER_IDBITS_MASK) + 1;
|
||||
|
||||
gic_rdists->prop_table_pa = val & GENMASK_ULL(51, 12);
|
||||
gic_rdists->prop_table_va = memremap(gic_rdists->prop_table_pa,
|
||||
LPI_PROPBASE_SZ,
|
||||
MEMREMAP_WB);
|
||||
gic_reset_prop_table(gic_rdists->prop_table_va);
|
||||
} else {
|
||||
struct page *page;
|
||||
|
||||
lpi_id_bits = min_t(u32,
|
||||
GICD_TYPER_ID_BITS(gic_rdists->gicd_typer),
|
||||
ITS_MAX_LPI_NRBITS);
|
||||
page = its_allocate_prop_table(GFP_NOWAIT);
|
||||
if (!page) {
|
||||
pr_err("Failed to allocate PROPBASE\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
gic_rdists->prop_table_pa = page_to_phys(page);
|
||||
gic_rdists->prop_table_va = page_address(page);
|
||||
WARN_ON(gic_reserve_range(gic_rdists->prop_table_pa,
|
||||
LPI_PROPBASE_SZ));
|
||||
}
|
||||
|
||||
pr_info("GICv3: using LPI property table @%pa\n",
|
||||
&gic_rdists->prop_table_pa);
|
||||
|
||||
return its_lpi_init(lpi_id_bits);
|
||||
}
|
||||
|
@ -1924,12 +1986,9 @@ static int its_alloc_collections(struct its_node *its)
|
|||
static struct page *its_allocate_pending_table(gfp_t gfp_flags)
|
||||
{
|
||||
struct page *pend_page;
|
||||
/*
|
||||
* The pending pages have to be at least 64kB aligned,
|
||||
* hence the 'max(LPI_PENDBASE_SZ, SZ_64K)' below.
|
||||
*/
|
||||
|
||||
pend_page = alloc_pages(gfp_flags | __GFP_ZERO,
|
||||
get_order(max_t(u32, LPI_PENDBASE_SZ, SZ_64K)));
|
||||
get_order(LPI_PENDBASE_SZ));
|
||||
if (!pend_page)
|
||||
return NULL;
|
||||
|
||||
|
@ -1941,36 +2000,103 @@ static struct page *its_allocate_pending_table(gfp_t gfp_flags)
|
|||
|
||||
static void its_free_pending_table(struct page *pt)
|
||||
{
|
||||
free_pages((unsigned long)page_address(pt),
|
||||
get_order(max_t(u32, LPI_PENDBASE_SZ, SZ_64K)));
|
||||
free_pages((unsigned long)page_address(pt), get_order(LPI_PENDBASE_SZ));
|
||||
}
|
||||
|
||||
/*
|
||||
* Booting with kdump and LPIs enabled is generally fine. Any other
|
||||
* case is wrong in the absence of firmware/EFI support.
|
||||
*/
|
||||
static bool enabled_lpis_allowed(void)
|
||||
{
|
||||
phys_addr_t addr;
|
||||
u64 val;
|
||||
|
||||
/* Check whether the property table is in a reserved region */
|
||||
val = gicr_read_propbaser(gic_data_rdist_rd_base() + GICR_PROPBASER);
|
||||
addr = val & GENMASK_ULL(51, 12);
|
||||
|
||||
return gic_check_reserved_range(addr, LPI_PROPBASE_SZ);
|
||||
}
|
||||
|
||||
static int __init allocate_lpi_tables(void)
|
||||
{
|
||||
u64 val;
|
||||
int err, cpu;
|
||||
|
||||
/*
|
||||
* If LPIs are enabled while we run this from the boot CPU,
|
||||
* flag the RD tables as pre-allocated if the stars do align.
|
||||
*/
|
||||
val = readl_relaxed(gic_data_rdist_rd_base() + GICR_CTLR);
|
||||
if ((val & GICR_CTLR_ENABLE_LPIS) && enabled_lpis_allowed()) {
|
||||
gic_rdists->flags |= (RDIST_FLAGS_RD_TABLES_PREALLOCATED |
|
||||
RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING);
|
||||
pr_info("GICv3: Using preallocated redistributor tables\n");
|
||||
}
|
||||
|
||||
err = its_setup_lpi_prop_table();
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* We allocate all the pending tables anyway, as we may have a
|
||||
* mix of RDs that have had LPIs enabled, and some that
|
||||
* don't. We'll free the unused ones as each CPU comes online.
|
||||
*/
|
||||
for_each_possible_cpu(cpu) {
|
||||
struct page *pend_page;
|
||||
|
||||
pend_page = its_allocate_pending_table(GFP_NOWAIT);
|
||||
if (!pend_page) {
|
||||
pr_err("Failed to allocate PENDBASE for CPU%d\n", cpu);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
gic_data_rdist_cpu(cpu)->pend_page = pend_page;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void its_cpu_init_lpis(void)
|
||||
{
|
||||
void __iomem *rbase = gic_data_rdist_rd_base();
|
||||
struct page *pend_page;
|
||||
phys_addr_t paddr;
|
||||
u64 val, tmp;
|
||||
|
||||
/* If we didn't allocate the pending table yet, do it now */
|
||||
pend_page = gic_data_rdist()->pend_page;
|
||||
if (!pend_page) {
|
||||
phys_addr_t paddr;
|
||||
if (gic_data_rdist()->lpi_enabled)
|
||||
return;
|
||||
|
||||
pend_page = its_allocate_pending_table(GFP_NOWAIT);
|
||||
if (!pend_page) {
|
||||
pr_err("Failed to allocate PENDBASE for CPU%d\n",
|
||||
smp_processor_id());
|
||||
return;
|
||||
}
|
||||
val = readl_relaxed(rbase + GICR_CTLR);
|
||||
if ((gic_rdists->flags & RDIST_FLAGS_RD_TABLES_PREALLOCATED) &&
|
||||
(val & GICR_CTLR_ENABLE_LPIS)) {
|
||||
/*
|
||||
* Check that we get the same property table on all
|
||||
* RDs. If we don't, this is hopeless.
|
||||
*/
|
||||
paddr = gicr_read_propbaser(rbase + GICR_PROPBASER);
|
||||
paddr &= GENMASK_ULL(51, 12);
|
||||
if (WARN_ON(gic_rdists->prop_table_pa != paddr))
|
||||
add_taint(TAINT_CRAP, LOCKDEP_STILL_OK);
|
||||
|
||||
paddr = page_to_phys(pend_page);
|
||||
pr_info("CPU%d: using LPI pending table @%pa\n",
|
||||
smp_processor_id(), &paddr);
|
||||
gic_data_rdist()->pend_page = pend_page;
|
||||
paddr = gicr_read_pendbaser(rbase + GICR_PENDBASER);
|
||||
paddr &= GENMASK_ULL(51, 16);
|
||||
|
||||
WARN_ON(!gic_check_reserved_range(paddr, LPI_PENDBASE_SZ));
|
||||
its_free_pending_table(gic_data_rdist()->pend_page);
|
||||
gic_data_rdist()->pend_page = NULL;
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
pend_page = gic_data_rdist()->pend_page;
|
||||
paddr = page_to_phys(pend_page);
|
||||
WARN_ON(gic_reserve_range(paddr, LPI_PENDBASE_SZ));
|
||||
|
||||
/* set PROPBASE */
|
||||
val = (page_to_phys(gic_rdists->prop_page) |
|
||||
val = (gic_rdists->prop_table_pa |
|
||||
GICR_PROPBASER_InnerShareable |
|
||||
GICR_PROPBASER_RaWaWb |
|
||||
((LPI_NRBITS - 1) & GICR_PROPBASER_IDBITS_MASK));
|
||||
|
@ -2020,6 +2146,12 @@ static void its_cpu_init_lpis(void)
|
|||
|
||||
/* Make sure the GIC has seen the above */
|
||||
dsb(sy);
|
||||
out:
|
||||
gic_data_rdist()->lpi_enabled = true;
|
||||
pr_info("GICv3: CPU%d: using %s LPI pending table @%pa\n",
|
||||
smp_processor_id(),
|
||||
gic_data_rdist()->pend_page ? "allocated" : "reserved",
|
||||
&paddr);
|
||||
}
|
||||
|
||||
static void its_cpu_init_collection(struct its_node *its)
|
||||
|
@ -3498,16 +3630,6 @@ static int redist_disable_lpis(void)
|
|||
u64 timeout = USEC_PER_SEC;
|
||||
u64 val;
|
||||
|
||||
/*
|
||||
* If coming via a CPU hotplug event, we don't need to disable
|
||||
* LPIs before trying to re-enable them. They are already
|
||||
* configured and all is well in the world. Detect this case
|
||||
* by checking the allocation of the pending table for the
|
||||
* current CPU.
|
||||
*/
|
||||
if (gic_data_rdist()->pend_page)
|
||||
return 0;
|
||||
|
||||
if (!gic_rdists_supports_plpis()) {
|
||||
pr_info("CPU%d: LPIs not supported\n", smp_processor_id());
|
||||
return -ENXIO;
|
||||
|
@ -3517,7 +3639,21 @@ static int redist_disable_lpis(void)
|
|||
if (!(val & GICR_CTLR_ENABLE_LPIS))
|
||||
return 0;
|
||||
|
||||
pr_warn("CPU%d: Booted with LPIs enabled, memory probably corrupted\n",
|
||||
/*
|
||||
* If coming via a CPU hotplug event, we don't need to disable
|
||||
* LPIs before trying to re-enable them. They are already
|
||||
* configured and all is well in the world.
|
||||
*
|
||||
* If running with preallocated tables, there is nothing to do.
|
||||
*/
|
||||
if (gic_data_rdist()->lpi_enabled ||
|
||||
(gic_rdists->flags & RDIST_FLAGS_RD_TABLES_PREALLOCATED))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* From that point on, we only try to do some damage control.
|
||||
*/
|
||||
pr_warn("GICv3: CPU%d: Booted with LPIs enabled, memory probably corrupted\n",
|
||||
smp_processor_id());
|
||||
add_taint(TAINT_CRAP, LOCKDEP_STILL_OK);
|
||||
|
||||
|
@ -3773,7 +3909,8 @@ int __init its_init(struct fwnode_handle *handle, struct rdists *rdists,
|
|||
}
|
||||
|
||||
gic_rdists = rdists;
|
||||
err = its_alloc_lpi_tables();
|
||||
|
||||
err = allocate_lpi_tables();
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
|
|
|
@ -348,48 +348,45 @@ static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs
|
|||
{
|
||||
u32 irqnr;
|
||||
|
||||
do {
|
||||
irqnr = gic_read_iar();
|
||||
irqnr = gic_read_iar();
|
||||
|
||||
if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) {
|
||||
int err;
|
||||
if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) {
|
||||
int err;
|
||||
|
||||
if (static_branch_likely(&supports_deactivate_key))
|
||||
gic_write_eoir(irqnr);
|
||||
else
|
||||
isb();
|
||||
|
||||
err = handle_domain_irq(gic_data.domain, irqnr, regs);
|
||||
if (err) {
|
||||
WARN_ONCE(true, "Unexpected interrupt received!\n");
|
||||
if (static_branch_likely(&supports_deactivate_key)) {
|
||||
if (irqnr < 8192)
|
||||
gic_write_dir(irqnr);
|
||||
} else {
|
||||
gic_write_eoir(irqnr);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (irqnr < 16) {
|
||||
if (static_branch_likely(&supports_deactivate_key))
|
||||
gic_write_eoir(irqnr);
|
||||
if (static_branch_likely(&supports_deactivate_key))
|
||||
gic_write_dir(irqnr);
|
||||
#ifdef CONFIG_SMP
|
||||
/*
|
||||
* Unlike GICv2, we don't need an smp_rmb() here.
|
||||
* The control dependency from gic_read_iar to
|
||||
* the ISB in gic_write_eoir is enough to ensure
|
||||
* that any shared data read by handle_IPI will
|
||||
* be read after the ACK.
|
||||
*/
|
||||
handle_IPI(irqnr, regs);
|
||||
#else
|
||||
WARN_ONCE(true, "Unexpected SGI received!\n");
|
||||
#endif
|
||||
continue;
|
||||
else
|
||||
isb();
|
||||
|
||||
err = handle_domain_irq(gic_data.domain, irqnr, regs);
|
||||
if (err) {
|
||||
WARN_ONCE(true, "Unexpected interrupt received!\n");
|
||||
if (static_branch_likely(&supports_deactivate_key)) {
|
||||
if (irqnr < 8192)
|
||||
gic_write_dir(irqnr);
|
||||
} else {
|
||||
gic_write_eoir(irqnr);
|
||||
}
|
||||
}
|
||||
} while (irqnr != ICC_IAR1_EL1_SPURIOUS);
|
||||
return;
|
||||
}
|
||||
if (irqnr < 16) {
|
||||
gic_write_eoir(irqnr);
|
||||
if (static_branch_likely(&supports_deactivate_key))
|
||||
gic_write_dir(irqnr);
|
||||
#ifdef CONFIG_SMP
|
||||
/*
|
||||
* Unlike GICv2, we don't need an smp_rmb() here.
|
||||
* The control dependency from gic_read_iar to
|
||||
* the ISB in gic_write_eoir is enough to ensure
|
||||
* that any shared data read by handle_IPI will
|
||||
* be read after the ACK.
|
||||
*/
|
||||
handle_IPI(irqnr, regs);
|
||||
#else
|
||||
WARN_ONCE(true, "Unexpected SGI received!\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void __init gic_dist_init(void)
|
||||
|
@ -653,7 +650,9 @@ early_param("irqchip.gicv3_nolpi", gicv3_nolpi_cfg);
|
|||
|
||||
static int gic_dist_supports_lpis(void)
|
||||
{
|
||||
return !!(readl_relaxed(gic_data.dist_base + GICD_TYPER) & GICD_TYPER_LPIS) && !gicv3_nolpi;
|
||||
return (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) &&
|
||||
!!(readl_relaxed(gic_data.dist_base + GICD_TYPER) & GICD_TYPER_LPIS) &&
|
||||
!gicv3_nolpi);
|
||||
}
|
||||
|
||||
static void gic_cpu_init(void)
|
||||
|
@ -673,10 +672,6 @@ static void gic_cpu_init(void)
|
|||
|
||||
gic_cpu_config(rbase, gic_redist_wait_for_rwp);
|
||||
|
||||
/* Give LPIs a spin */
|
||||
if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis())
|
||||
its_cpu_init();
|
||||
|
||||
/* initialise system registers */
|
||||
gic_cpu_sys_reg_init();
|
||||
}
|
||||
|
@ -689,6 +684,10 @@ static void gic_cpu_init(void)
|
|||
static int gic_starting_cpu(unsigned int cpu)
|
||||
{
|
||||
gic_cpu_init();
|
||||
|
||||
if (gic_dist_supports_lpis())
|
||||
its_cpu_init();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1127,14 +1126,16 @@ static int __init gic_init_bases(void __iomem *dist_base,
|
|||
|
||||
gic_update_vlpi_properties();
|
||||
|
||||
if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis())
|
||||
its_init(handle, &gic_data.rdists, gic_data.domain);
|
||||
|
||||
gic_smp_init();
|
||||
gic_dist_init();
|
||||
gic_cpu_init();
|
||||
gic_cpu_pm_init();
|
||||
|
||||
if (gic_dist_supports_lpis()) {
|
||||
its_init(handle, &gic_data.rdists, gic_data.domain);
|
||||
its_cpu_init();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_free:
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <linux/irq.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/jump_label.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/of_irq.h>
|
||||
|
@ -26,6 +27,10 @@
|
|||
#define ICU_SETSPI_NSR_AH 0x14
|
||||
#define ICU_CLRSPI_NSR_AL 0x18
|
||||
#define ICU_CLRSPI_NSR_AH 0x1c
|
||||
#define ICU_SET_SEI_AL 0x50
|
||||
#define ICU_SET_SEI_AH 0x54
|
||||
#define ICU_CLR_SEI_AL 0x58
|
||||
#define ICU_CLR_SEI_AH 0x5C
|
||||
#define ICU_INT_CFG(x) (0x100 + 4 * (x))
|
||||
#define ICU_INT_ENABLE BIT(24)
|
||||
#define ICU_IS_EDGE BIT(28)
|
||||
|
@ -36,12 +41,23 @@
|
|||
#define ICU_SATA0_ICU_ID 109
|
||||
#define ICU_SATA1_ICU_ID 107
|
||||
|
||||
struct mvebu_icu_subset_data {
|
||||
unsigned int icu_group;
|
||||
unsigned int offset_set_ah;
|
||||
unsigned int offset_set_al;
|
||||
unsigned int offset_clr_ah;
|
||||
unsigned int offset_clr_al;
|
||||
};
|
||||
|
||||
struct mvebu_icu {
|
||||
struct irq_chip irq_chip;
|
||||
void __iomem *base;
|
||||
struct irq_domain *domain;
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
struct mvebu_icu_msi_data {
|
||||
struct mvebu_icu *icu;
|
||||
atomic_t initialized;
|
||||
const struct mvebu_icu_subset_data *subset_data;
|
||||
};
|
||||
|
||||
struct mvebu_icu_irq_data {
|
||||
|
@ -50,28 +66,40 @@ struct mvebu_icu_irq_data {
|
|||
unsigned int type;
|
||||
};
|
||||
|
||||
static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)
|
||||
DEFINE_STATIC_KEY_FALSE(legacy_bindings);
|
||||
|
||||
static void mvebu_icu_init(struct mvebu_icu *icu,
|
||||
struct mvebu_icu_msi_data *msi_data,
|
||||
struct msi_msg *msg)
|
||||
{
|
||||
if (atomic_cmpxchg(&icu->initialized, false, true))
|
||||
const struct mvebu_icu_subset_data *subset = msi_data->subset_data;
|
||||
|
||||
if (atomic_cmpxchg(&msi_data->initialized, false, true))
|
||||
return;
|
||||
|
||||
/* Set Clear/Set ICU SPI message address in AP */
|
||||
writel_relaxed(msg[0].address_hi, icu->base + ICU_SETSPI_NSR_AH);
|
||||
writel_relaxed(msg[0].address_lo, icu->base + ICU_SETSPI_NSR_AL);
|
||||
writel_relaxed(msg[1].address_hi, icu->base + ICU_CLRSPI_NSR_AH);
|
||||
writel_relaxed(msg[1].address_lo, icu->base + ICU_CLRSPI_NSR_AL);
|
||||
/* Set 'SET' ICU SPI message address in AP */
|
||||
writel_relaxed(msg[0].address_hi, icu->base + subset->offset_set_ah);
|
||||
writel_relaxed(msg[0].address_lo, icu->base + subset->offset_set_al);
|
||||
|
||||
if (subset->icu_group != ICU_GRP_NSR)
|
||||
return;
|
||||
|
||||
/* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */
|
||||
writel_relaxed(msg[1].address_hi, icu->base + subset->offset_clr_ah);
|
||||
writel_relaxed(msg[1].address_lo, icu->base + subset->offset_clr_al);
|
||||
}
|
||||
|
||||
static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
|
||||
{
|
||||
struct irq_data *d = irq_get_irq_data(desc->irq);
|
||||
struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d->domain);
|
||||
struct mvebu_icu_irq_data *icu_irqd = d->chip_data;
|
||||
struct mvebu_icu *icu = icu_irqd->icu;
|
||||
unsigned int icu_int;
|
||||
|
||||
if (msg->address_lo || msg->address_hi) {
|
||||
/* One off initialization */
|
||||
mvebu_icu_init(icu, msg);
|
||||
/* One off initialization per domain */
|
||||
mvebu_icu_init(icu, msi_data, msg);
|
||||
/* Configure the ICU with irq number & type */
|
||||
icu_int = msg->data | ICU_INT_ENABLE;
|
||||
if (icu_irqd->type & IRQ_TYPE_EDGE_RISING)
|
||||
|
@ -101,37 +129,66 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
|
|||
}
|
||||
}
|
||||
|
||||
static struct irq_chip mvebu_icu_nsr_chip = {
|
||||
.name = "ICU-NSR",
|
||||
.irq_mask = irq_chip_mask_parent,
|
||||
.irq_unmask = irq_chip_unmask_parent,
|
||||
.irq_eoi = irq_chip_eoi_parent,
|
||||
.irq_set_type = irq_chip_set_type_parent,
|
||||
.irq_set_affinity = irq_chip_set_affinity_parent,
|
||||
};
|
||||
|
||||
static struct irq_chip mvebu_icu_sei_chip = {
|
||||
.name = "ICU-SEI",
|
||||
.irq_ack = irq_chip_ack_parent,
|
||||
.irq_mask = irq_chip_mask_parent,
|
||||
.irq_unmask = irq_chip_unmask_parent,
|
||||
.irq_set_type = irq_chip_set_type_parent,
|
||||
.irq_set_affinity = irq_chip_set_affinity_parent,
|
||||
};
|
||||
|
||||
static int
|
||||
mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
|
||||
unsigned long *hwirq, unsigned int *type)
|
||||
{
|
||||
struct mvebu_icu *icu = d->host_data;
|
||||
unsigned int icu_group;
|
||||
struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d);
|
||||
struct mvebu_icu *icu = platform_msi_get_host_data(d);
|
||||
unsigned int param_count = static_branch_unlikely(&legacy_bindings) ? 3 : 2;
|
||||
|
||||
/* Check the count of the parameters in dt */
|
||||
if (WARN_ON(fwspec->param_count < 3)) {
|
||||
if (WARN_ON(fwspec->param_count != param_count)) {
|
||||
dev_err(icu->dev, "wrong ICU parameter count %d\n",
|
||||
fwspec->param_count);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Only ICU group type is handled */
|
||||
icu_group = fwspec->param[0];
|
||||
if (icu_group != ICU_GRP_NSR && icu_group != ICU_GRP_SR &&
|
||||
icu_group != ICU_GRP_SEI && icu_group != ICU_GRP_REI) {
|
||||
dev_err(icu->dev, "wrong ICU group type %x\n", icu_group);
|
||||
return -EINVAL;
|
||||
if (static_branch_unlikely(&legacy_bindings)) {
|
||||
*hwirq = fwspec->param[1];
|
||||
*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
|
||||
if (fwspec->param[0] != ICU_GRP_NSR) {
|
||||
dev_err(icu->dev, "wrong ICU group type %x\n",
|
||||
fwspec->param[0]);
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
*hwirq = fwspec->param[0];
|
||||
*type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
|
||||
|
||||
/*
|
||||
* The ICU receives level interrupts. While the NSR are also
|
||||
* level interrupts, SEI are edge interrupts. Force the type
|
||||
* here in this case. Please note that this makes the interrupt
|
||||
* handling unreliable.
|
||||
*/
|
||||
if (msi_data->subset_data->icu_group == ICU_GRP_SEI)
|
||||
*type = IRQ_TYPE_EDGE_RISING;
|
||||
}
|
||||
|
||||
*hwirq = fwspec->param[1];
|
||||
if (*hwirq >= ICU_MAX_IRQS) {
|
||||
dev_err(icu->dev, "invalid interrupt number %ld\n", *hwirq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Mask the type to prevent wrong DT configuration */
|
||||
*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -142,8 +199,10 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
|||
int err;
|
||||
unsigned long hwirq;
|
||||
struct irq_fwspec *fwspec = args;
|
||||
struct mvebu_icu *icu = platform_msi_get_host_data(domain);
|
||||
struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(domain);
|
||||
struct mvebu_icu *icu = msi_data->icu;
|
||||
struct mvebu_icu_irq_data *icu_irqd;
|
||||
struct irq_chip *chip = &mvebu_icu_nsr_chip;
|
||||
|
||||
icu_irqd = kmalloc(sizeof(*icu_irqd), GFP_KERNEL);
|
||||
if (!icu_irqd)
|
||||
|
@ -156,7 +215,10 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
|||
goto free_irqd;
|
||||
}
|
||||
|
||||
icu_irqd->icu_group = fwspec->param[0];
|
||||
if (static_branch_unlikely(&legacy_bindings))
|
||||
icu_irqd->icu_group = fwspec->param[0];
|
||||
else
|
||||
icu_irqd->icu_group = msi_data->subset_data->icu_group;
|
||||
icu_irqd->icu = icu;
|
||||
|
||||
err = platform_msi_domain_alloc(domain, virq, nr_irqs);
|
||||
|
@ -170,8 +232,11 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
|||
if (err)
|
||||
goto free_msi;
|
||||
|
||||
if (icu_irqd->icu_group == ICU_GRP_SEI)
|
||||
chip = &mvebu_icu_sei_chip;
|
||||
|
||||
err = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
|
||||
&icu->irq_chip, icu_irqd);
|
||||
chip, icu_irqd);
|
||||
if (err) {
|
||||
dev_err(icu->dev, "failed to set the data to IRQ domain\n");
|
||||
goto free_msi;
|
||||
|
@ -204,11 +269,84 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = {
|
|||
.free = mvebu_icu_irq_domain_free,
|
||||
};
|
||||
|
||||
static const struct mvebu_icu_subset_data mvebu_icu_nsr_subset_data = {
|
||||
.icu_group = ICU_GRP_NSR,
|
||||
.offset_set_ah = ICU_SETSPI_NSR_AH,
|
||||
.offset_set_al = ICU_SETSPI_NSR_AL,
|
||||
.offset_clr_ah = ICU_CLRSPI_NSR_AH,
|
||||
.offset_clr_al = ICU_CLRSPI_NSR_AL,
|
||||
};
|
||||
|
||||
static const struct mvebu_icu_subset_data mvebu_icu_sei_subset_data = {
|
||||
.icu_group = ICU_GRP_SEI,
|
||||
.offset_set_ah = ICU_SET_SEI_AH,
|
||||
.offset_set_al = ICU_SET_SEI_AL,
|
||||
};
|
||||
|
||||
static const struct of_device_id mvebu_icu_subset_of_match[] = {
|
||||
{
|
||||
.compatible = "marvell,cp110-icu-nsr",
|
||||
.data = &mvebu_icu_nsr_subset_data,
|
||||
},
|
||||
{
|
||||
.compatible = "marvell,cp110-icu-sei",
|
||||
.data = &mvebu_icu_sei_subset_data,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static int mvebu_icu_subset_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mvebu_icu_msi_data *msi_data;
|
||||
struct device_node *msi_parent_dn;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct irq_domain *irq_domain;
|
||||
|
||||
msi_data = devm_kzalloc(dev, sizeof(*msi_data), GFP_KERNEL);
|
||||
if (!msi_data)
|
||||
return -ENOMEM;
|
||||
|
||||
if (static_branch_unlikely(&legacy_bindings)) {
|
||||
msi_data->icu = dev_get_drvdata(dev);
|
||||
msi_data->subset_data = &mvebu_icu_nsr_subset_data;
|
||||
} else {
|
||||
msi_data->icu = dev_get_drvdata(dev->parent);
|
||||
msi_data->subset_data = of_device_get_match_data(dev);
|
||||
}
|
||||
|
||||
dev->msi_domain = of_msi_get_domain(dev, dev->of_node,
|
||||
DOMAIN_BUS_PLATFORM_MSI);
|
||||
if (!dev->msi_domain)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
msi_parent_dn = irq_domain_get_of_node(dev->msi_domain);
|
||||
if (!msi_parent_dn)
|
||||
return -ENODEV;
|
||||
|
||||
irq_domain = platform_msi_create_device_tree_domain(dev, ICU_MAX_IRQS,
|
||||
mvebu_icu_write_msg,
|
||||
&mvebu_icu_domain_ops,
|
||||
msi_data);
|
||||
if (!irq_domain) {
|
||||
dev_err(dev, "Failed to create ICU MSI domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver mvebu_icu_subset_driver = {
|
||||
.probe = mvebu_icu_subset_probe,
|
||||
.driver = {
|
||||
.name = "mvebu-icu-subset",
|
||||
.of_match_table = mvebu_icu_subset_of_match,
|
||||
},
|
||||
};
|
||||
builtin_platform_driver(mvebu_icu_subset_driver);
|
||||
|
||||
static int mvebu_icu_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct mvebu_icu *icu;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct device_node *gicp_dn;
|
||||
struct resource *res;
|
||||
int i;
|
||||
|
||||
|
@ -226,53 +364,38 @@ static int mvebu_icu_probe(struct platform_device *pdev)
|
|||
return PTR_ERR(icu->base);
|
||||
}
|
||||
|
||||
icu->irq_chip.name = devm_kasprintf(&pdev->dev, GFP_KERNEL,
|
||||
"ICU.%x",
|
||||
(unsigned int)res->start);
|
||||
if (!icu->irq_chip.name)
|
||||
return -ENOMEM;
|
||||
|
||||
icu->irq_chip.irq_mask = irq_chip_mask_parent;
|
||||
icu->irq_chip.irq_unmask = irq_chip_unmask_parent;
|
||||
icu->irq_chip.irq_eoi = irq_chip_eoi_parent;
|
||||
icu->irq_chip.irq_set_type = irq_chip_set_type_parent;
|
||||
#ifdef CONFIG_SMP
|
||||
icu->irq_chip.irq_set_affinity = irq_chip_set_affinity_parent;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* We're probed after MSI domains have been resolved, so force
|
||||
* resolution here.
|
||||
* Legacy bindings: ICU is one node with one MSI parent: force manually
|
||||
* the probe of the NSR interrupts side.
|
||||
* New bindings: ICU node has children, one per interrupt controller
|
||||
* having its own MSI parent: call platform_populate().
|
||||
* All ICU instances should use the same bindings.
|
||||
*/
|
||||
pdev->dev.msi_domain = of_msi_get_domain(&pdev->dev, node,
|
||||
DOMAIN_BUS_PLATFORM_MSI);
|
||||
if (!pdev->dev.msi_domain)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
gicp_dn = irq_domain_get_of_node(pdev->dev.msi_domain);
|
||||
if (!gicp_dn)
|
||||
return -ENODEV;
|
||||
if (!of_get_child_count(pdev->dev.of_node))
|
||||
static_branch_enable(&legacy_bindings);
|
||||
|
||||
/*
|
||||
* Clean all ICU interrupts with type SPI_NSR, required to
|
||||
* Clean all ICU interrupts of type NSR and SEI, required to
|
||||
* avoid unpredictable SPI assignments done by firmware.
|
||||
*/
|
||||
for (i = 0 ; i < ICU_MAX_IRQS ; i++) {
|
||||
u32 icu_int = readl_relaxed(icu->base + ICU_INT_CFG(i));
|
||||
if ((icu_int >> ICU_GROUP_SHIFT) == ICU_GRP_NSR)
|
||||
u32 icu_int, icu_grp;
|
||||
|
||||
icu_int = readl_relaxed(icu->base + ICU_INT_CFG(i));
|
||||
icu_grp = icu_int >> ICU_GROUP_SHIFT;
|
||||
|
||||
if (icu_grp == ICU_GRP_NSR ||
|
||||
(icu_grp == ICU_GRP_SEI &&
|
||||
!static_branch_unlikely(&legacy_bindings)))
|
||||
writel_relaxed(0x0, icu->base + ICU_INT_CFG(i));
|
||||
}
|
||||
|
||||
icu->domain =
|
||||
platform_msi_create_device_domain(&pdev->dev, ICU_MAX_IRQS,
|
||||
mvebu_icu_write_msg,
|
||||
&mvebu_icu_domain_ops, icu);
|
||||
if (!icu->domain) {
|
||||
dev_err(&pdev->dev, "Failed to create ICU domain\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
platform_set_drvdata(pdev, icu);
|
||||
|
||||
return 0;
|
||||
if (static_branch_unlikely(&legacy_bindings))
|
||||
return mvebu_icu_subset_probe(pdev);
|
||||
else
|
||||
return devm_of_platform_populate(&pdev->dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id mvebu_icu_of_match[] = {
|
||||
|
|
|
@ -0,0 +1,507 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#define pr_fmt(fmt) "mvebu-sei: " fmt
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/irqchip/chained_irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/msi.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
/* Cause register */
|
||||
#define GICP_SECR(idx) (0x0 + ((idx) * 0x4))
|
||||
/* Mask register */
|
||||
#define GICP_SEMR(idx) (0x20 + ((idx) * 0x4))
|
||||
#define GICP_SET_SEI_OFFSET 0x30
|
||||
|
||||
#define SEI_IRQ_COUNT_PER_REG 32
|
||||
#define SEI_IRQ_REG_COUNT 2
|
||||
#define SEI_IRQ_COUNT (SEI_IRQ_COUNT_PER_REG * SEI_IRQ_REG_COUNT)
|
||||
#define SEI_IRQ_REG_IDX(irq_id) ((irq_id) / SEI_IRQ_COUNT_PER_REG)
|
||||
#define SEI_IRQ_REG_BIT(irq_id) ((irq_id) % SEI_IRQ_COUNT_PER_REG)
|
||||
|
||||
struct mvebu_sei_interrupt_range {
|
||||
u32 first;
|
||||
u32 size;
|
||||
};
|
||||
|
||||
struct mvebu_sei_caps {
|
||||
struct mvebu_sei_interrupt_range ap_range;
|
||||
struct mvebu_sei_interrupt_range cp_range;
|
||||
};
|
||||
|
||||
struct mvebu_sei {
|
||||
struct device *dev;
|
||||
void __iomem *base;
|
||||
struct resource *res;
|
||||
struct irq_domain *sei_domain;
|
||||
struct irq_domain *ap_domain;
|
||||
struct irq_domain *cp_domain;
|
||||
const struct mvebu_sei_caps *caps;
|
||||
|
||||
/* Lock on MSI allocations/releases */
|
||||
struct mutex cp_msi_lock;
|
||||
DECLARE_BITMAP(cp_msi_bitmap, SEI_IRQ_COUNT);
|
||||
|
||||
/* Lock on IRQ masking register */
|
||||
raw_spinlock_t mask_lock;
|
||||
};
|
||||
|
||||
static void mvebu_sei_ack_irq(struct irq_data *d)
|
||||
{
|
||||
struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
|
||||
u32 reg_idx = SEI_IRQ_REG_IDX(d->hwirq);
|
||||
|
||||
writel_relaxed(BIT(SEI_IRQ_REG_BIT(d->hwirq)),
|
||||
sei->base + GICP_SECR(reg_idx));
|
||||
}
|
||||
|
||||
static void mvebu_sei_mask_irq(struct irq_data *d)
|
||||
{
|
||||
struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
|
||||
u32 reg, reg_idx = SEI_IRQ_REG_IDX(d->hwirq);
|
||||
unsigned long flags;
|
||||
|
||||
/* 1 disables the interrupt */
|
||||
raw_spin_lock_irqsave(&sei->mask_lock, flags);
|
||||
reg = readl_relaxed(sei->base + GICP_SEMR(reg_idx));
|
||||
reg |= BIT(SEI_IRQ_REG_BIT(d->hwirq));
|
||||
writel_relaxed(reg, sei->base + GICP_SEMR(reg_idx));
|
||||
raw_spin_unlock_irqrestore(&sei->mask_lock, flags);
|
||||
}
|
||||
|
||||
static void mvebu_sei_unmask_irq(struct irq_data *d)
|
||||
{
|
||||
struct mvebu_sei *sei = irq_data_get_irq_chip_data(d);
|
||||
u32 reg, reg_idx = SEI_IRQ_REG_IDX(d->hwirq);
|
||||
unsigned long flags;
|
||||
|
||||
/* 0 enables the interrupt */
|
||||
raw_spin_lock_irqsave(&sei->mask_lock, flags);
|
||||
reg = readl_relaxed(sei->base + GICP_SEMR(reg_idx));
|
||||
reg &= ~BIT(SEI_IRQ_REG_BIT(d->hwirq));
|
||||
writel_relaxed(reg, sei->base + GICP_SEMR(reg_idx));
|
||||
raw_spin_unlock_irqrestore(&sei->mask_lock, flags);
|
||||
}
|
||||
|
||||
static int mvebu_sei_set_affinity(struct irq_data *d,
|
||||
const struct cpumask *mask_val,
|
||||
bool force)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int mvebu_sei_set_irqchip_state(struct irq_data *d,
|
||||
enum irqchip_irq_state which,
|
||||
bool state)
|
||||
{
|
||||
/* We can only clear the pending state by acking the interrupt */
|
||||
if (which != IRQCHIP_STATE_PENDING || state)
|
||||
return -EINVAL;
|
||||
|
||||
mvebu_sei_ack_irq(d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_chip mvebu_sei_irq_chip = {
|
||||
.name = "SEI",
|
||||
.irq_ack = mvebu_sei_ack_irq,
|
||||
.irq_mask = mvebu_sei_mask_irq,
|
||||
.irq_unmask = mvebu_sei_unmask_irq,
|
||||
.irq_set_affinity = mvebu_sei_set_affinity,
|
||||
.irq_set_irqchip_state = mvebu_sei_set_irqchip_state,
|
||||
};
|
||||
|
||||
static int mvebu_sei_ap_set_type(struct irq_data *data, unsigned int type)
|
||||
{
|
||||
if ((type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_HIGH)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_chip mvebu_sei_ap_irq_chip = {
|
||||
.name = "AP SEI",
|
||||
.irq_ack = irq_chip_ack_parent,
|
||||
.irq_mask = irq_chip_mask_parent,
|
||||
.irq_unmask = irq_chip_unmask_parent,
|
||||
.irq_set_affinity = irq_chip_set_affinity_parent,
|
||||
.irq_set_type = mvebu_sei_ap_set_type,
|
||||
};
|
||||
|
||||
static void mvebu_sei_cp_compose_msi_msg(struct irq_data *data,
|
||||
struct msi_msg *msg)
|
||||
{
|
||||
struct mvebu_sei *sei = data->chip_data;
|
||||
phys_addr_t set = sei->res->start + GICP_SET_SEI_OFFSET;
|
||||
|
||||
msg->data = data->hwirq + sei->caps->cp_range.first;
|
||||
msg->address_lo = lower_32_bits(set);
|
||||
msg->address_hi = upper_32_bits(set);
|
||||
}
|
||||
|
||||
static int mvebu_sei_cp_set_type(struct irq_data *data, unsigned int type)
|
||||
{
|
||||
if ((type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_EDGE_RISING)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_chip mvebu_sei_cp_irq_chip = {
|
||||
.name = "CP SEI",
|
||||
.irq_ack = irq_chip_ack_parent,
|
||||
.irq_mask = irq_chip_mask_parent,
|
||||
.irq_unmask = irq_chip_unmask_parent,
|
||||
.irq_set_affinity = irq_chip_set_affinity_parent,
|
||||
.irq_set_type = mvebu_sei_cp_set_type,
|
||||
.irq_compose_msi_msg = mvebu_sei_cp_compose_msi_msg,
|
||||
};
|
||||
|
||||
static int mvebu_sei_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs, void *arg)
|
||||
{
|
||||
struct mvebu_sei *sei = domain->host_data;
|
||||
struct irq_fwspec *fwspec = arg;
|
||||
|
||||
/* Not much to do, just setup the irqdata */
|
||||
irq_domain_set_hwirq_and_chip(domain, virq, fwspec->param[0],
|
||||
&mvebu_sei_irq_chip, sei);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mvebu_sei_domain_free(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nr_irqs; i++) {
|
||||
struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
|
||||
irq_set_handler(virq + i, NULL);
|
||||
irq_domain_reset_irq_data(d);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops mvebu_sei_domain_ops = {
|
||||
.alloc = mvebu_sei_domain_alloc,
|
||||
.free = mvebu_sei_domain_free,
|
||||
};
|
||||
|
||||
static int mvebu_sei_ap_translate(struct irq_domain *domain,
|
||||
struct irq_fwspec *fwspec,
|
||||
unsigned long *hwirq,
|
||||
unsigned int *type)
|
||||
{
|
||||
*hwirq = fwspec->param[0];
|
||||
*type = IRQ_TYPE_LEVEL_HIGH;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mvebu_sei_ap_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs, void *arg)
|
||||
{
|
||||
struct mvebu_sei *sei = domain->host_data;
|
||||
struct irq_fwspec fwspec;
|
||||
unsigned long hwirq;
|
||||
unsigned int type;
|
||||
int err;
|
||||
|
||||
mvebu_sei_ap_translate(domain, arg, &hwirq, &type);
|
||||
|
||||
fwspec.fwnode = domain->parent->fwnode;
|
||||
fwspec.param_count = 1;
|
||||
fwspec.param[0] = hwirq + sei->caps->ap_range.first;
|
||||
|
||||
err = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
irq_domain_set_info(domain, virq, hwirq,
|
||||
&mvebu_sei_ap_irq_chip, sei,
|
||||
handle_level_irq, NULL, NULL);
|
||||
irq_set_probe(virq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops mvebu_sei_ap_domain_ops = {
|
||||
.translate = mvebu_sei_ap_translate,
|
||||
.alloc = mvebu_sei_ap_alloc,
|
||||
.free = irq_domain_free_irqs_parent,
|
||||
};
|
||||
|
||||
static void mvebu_sei_cp_release_irq(struct mvebu_sei *sei, unsigned long hwirq)
|
||||
{
|
||||
mutex_lock(&sei->cp_msi_lock);
|
||||
clear_bit(hwirq, sei->cp_msi_bitmap);
|
||||
mutex_unlock(&sei->cp_msi_lock);
|
||||
}
|
||||
|
||||
static int mvebu_sei_cp_domain_alloc(struct irq_domain *domain,
|
||||
unsigned int virq, unsigned int nr_irqs,
|
||||
void *args)
|
||||
{
|
||||
struct mvebu_sei *sei = domain->host_data;
|
||||
struct irq_fwspec fwspec;
|
||||
unsigned long hwirq;
|
||||
int ret;
|
||||
|
||||
/* The software only supports single allocations for now */
|
||||
if (nr_irqs != 1)
|
||||
return -ENOTSUPP;
|
||||
|
||||
mutex_lock(&sei->cp_msi_lock);
|
||||
hwirq = find_first_zero_bit(sei->cp_msi_bitmap,
|
||||
sei->caps->cp_range.size);
|
||||
if (hwirq < sei->caps->cp_range.size)
|
||||
set_bit(hwirq, sei->cp_msi_bitmap);
|
||||
mutex_unlock(&sei->cp_msi_lock);
|
||||
|
||||
if (hwirq == sei->caps->cp_range.size)
|
||||
return -ENOSPC;
|
||||
|
||||
fwspec.fwnode = domain->parent->fwnode;
|
||||
fwspec.param_count = 1;
|
||||
fwspec.param[0] = hwirq + sei->caps->cp_range.first;
|
||||
|
||||
ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
|
||||
if (ret)
|
||||
goto free_irq;
|
||||
|
||||
irq_domain_set_info(domain, virq, hwirq,
|
||||
&mvebu_sei_cp_irq_chip, sei,
|
||||
handle_edge_irq, NULL, NULL);
|
||||
|
||||
return 0;
|
||||
|
||||
free_irq:
|
||||
mvebu_sei_cp_release_irq(sei, hwirq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mvebu_sei_cp_domain_free(struct irq_domain *domain,
|
||||
unsigned int virq, unsigned int nr_irqs)
|
||||
{
|
||||
struct mvebu_sei *sei = domain->host_data;
|
||||
struct irq_data *d = irq_domain_get_irq_data(domain, virq);
|
||||
|
||||
if (nr_irqs != 1 || d->hwirq >= sei->caps->cp_range.size) {
|
||||
dev_err(sei->dev, "Invalid hwirq %lu\n", d->hwirq);
|
||||
return;
|
||||
}
|
||||
|
||||
mvebu_sei_cp_release_irq(sei, d->hwirq);
|
||||
irq_domain_free_irqs_parent(domain, virq, 1);
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops mvebu_sei_cp_domain_ops = {
|
||||
.alloc = mvebu_sei_cp_domain_alloc,
|
||||
.free = mvebu_sei_cp_domain_free,
|
||||
};
|
||||
|
||||
static struct irq_chip mvebu_sei_msi_irq_chip = {
|
||||
.name = "SEI pMSI",
|
||||
.irq_ack = irq_chip_ack_parent,
|
||||
.irq_set_type = irq_chip_set_type_parent,
|
||||
};
|
||||
|
||||
static struct msi_domain_ops mvebu_sei_msi_ops = {
|
||||
};
|
||||
|
||||
static struct msi_domain_info mvebu_sei_msi_domain_info = {
|
||||
.flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS,
|
||||
.ops = &mvebu_sei_msi_ops,
|
||||
.chip = &mvebu_sei_msi_irq_chip,
|
||||
};
|
||||
|
||||
static void mvebu_sei_handle_cascade_irq(struct irq_desc *desc)
|
||||
{
|
||||
struct mvebu_sei *sei = irq_desc_get_handler_data(desc);
|
||||
struct irq_chip *chip = irq_desc_get_chip(desc);
|
||||
u32 idx;
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
|
||||
for (idx = 0; idx < SEI_IRQ_REG_COUNT; idx++) {
|
||||
unsigned long irqmap;
|
||||
int bit;
|
||||
|
||||
irqmap = readl_relaxed(sei->base + GICP_SECR(idx));
|
||||
for_each_set_bit(bit, &irqmap, SEI_IRQ_COUNT_PER_REG) {
|
||||
unsigned long hwirq;
|
||||
unsigned int virq;
|
||||
|
||||
hwirq = idx * SEI_IRQ_COUNT_PER_REG + bit;
|
||||
virq = irq_find_mapping(sei->sei_domain, hwirq);
|
||||
if (likely(virq)) {
|
||||
generic_handle_irq(virq);
|
||||
continue;
|
||||
}
|
||||
|
||||
dev_warn(sei->dev,
|
||||
"Spurious IRQ detected (hwirq %lu)\n", hwirq);
|
||||
}
|
||||
}
|
||||
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
static void mvebu_sei_reset(struct mvebu_sei *sei)
|
||||
{
|
||||
u32 reg_idx;
|
||||
|
||||
/* Clear IRQ cause registers, mask all interrupts */
|
||||
for (reg_idx = 0; reg_idx < SEI_IRQ_REG_COUNT; reg_idx++) {
|
||||
writel_relaxed(0xFFFFFFFF, sei->base + GICP_SECR(reg_idx));
|
||||
writel_relaxed(0xFFFFFFFF, sei->base + GICP_SEMR(reg_idx));
|
||||
}
|
||||
}
|
||||
|
||||
static int mvebu_sei_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct irq_domain *plat_domain;
|
||||
struct mvebu_sei *sei;
|
||||
u32 parent_irq;
|
||||
int ret;
|
||||
|
||||
sei = devm_kzalloc(&pdev->dev, sizeof(*sei), GFP_KERNEL);
|
||||
if (!sei)
|
||||
return -ENOMEM;
|
||||
|
||||
sei->dev = &pdev->dev;
|
||||
|
||||
mutex_init(&sei->cp_msi_lock);
|
||||
raw_spin_lock_init(&sei->mask_lock);
|
||||
|
||||
sei->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
sei->base = devm_ioremap_resource(sei->dev, sei->res);
|
||||
if (!sei->base) {
|
||||
dev_err(sei->dev, "Failed to remap SEI resource\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Retrieve the SEI capabilities with the interrupt ranges */
|
||||
sei->caps = of_device_get_match_data(&pdev->dev);
|
||||
if (!sei->caps) {
|
||||
dev_err(sei->dev,
|
||||
"Could not retrieve controller capabilities\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reserve the single (top-level) parent SPI IRQ from which all the
|
||||
* interrupts handled by this driver will be signaled.
|
||||
*/
|
||||
parent_irq = irq_of_parse_and_map(node, 0);
|
||||
if (parent_irq <= 0) {
|
||||
dev_err(sei->dev, "Failed to retrieve top-level SPI IRQ\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Create the root SEI domain */
|
||||
sei->sei_domain = irq_domain_create_linear(of_node_to_fwnode(node),
|
||||
(sei->caps->ap_range.size +
|
||||
sei->caps->cp_range.size),
|
||||
&mvebu_sei_domain_ops,
|
||||
sei);
|
||||
if (!sei->sei_domain) {
|
||||
dev_err(sei->dev, "Failed to create SEI IRQ domain\n");
|
||||
ret = -ENOMEM;
|
||||
goto dispose_irq;
|
||||
}
|
||||
|
||||
irq_domain_update_bus_token(sei->sei_domain, DOMAIN_BUS_NEXUS);
|
||||
|
||||
/* Create the 'wired' domain */
|
||||
sei->ap_domain = irq_domain_create_hierarchy(sei->sei_domain, 0,
|
||||
sei->caps->ap_range.size,
|
||||
of_node_to_fwnode(node),
|
||||
&mvebu_sei_ap_domain_ops,
|
||||
sei);
|
||||
if (!sei->ap_domain) {
|
||||
dev_err(sei->dev, "Failed to create AP IRQ domain\n");
|
||||
ret = -ENOMEM;
|
||||
goto remove_sei_domain;
|
||||
}
|
||||
|
||||
irq_domain_update_bus_token(sei->ap_domain, DOMAIN_BUS_WIRED);
|
||||
|
||||
/* Create the 'MSI' domain */
|
||||
sei->cp_domain = irq_domain_create_hierarchy(sei->sei_domain, 0,
|
||||
sei->caps->cp_range.size,
|
||||
of_node_to_fwnode(node),
|
||||
&mvebu_sei_cp_domain_ops,
|
||||
sei);
|
||||
if (!sei->cp_domain) {
|
||||
pr_err("Failed to create CPs IRQ domain\n");
|
||||
ret = -ENOMEM;
|
||||
goto remove_ap_domain;
|
||||
}
|
||||
|
||||
irq_domain_update_bus_token(sei->cp_domain, DOMAIN_BUS_GENERIC_MSI);
|
||||
|
||||
plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node),
|
||||
&mvebu_sei_msi_domain_info,
|
||||
sei->cp_domain);
|
||||
if (!plat_domain) {
|
||||
pr_err("Failed to create CPs MSI domain\n");
|
||||
ret = -ENOMEM;
|
||||
goto remove_cp_domain;
|
||||
}
|
||||
|
||||
mvebu_sei_reset(sei);
|
||||
|
||||
irq_set_chained_handler_and_data(parent_irq,
|
||||
mvebu_sei_handle_cascade_irq,
|
||||
sei);
|
||||
|
||||
return 0;
|
||||
|
||||
remove_cp_domain:
|
||||
irq_domain_remove(sei->cp_domain);
|
||||
remove_ap_domain:
|
||||
irq_domain_remove(sei->ap_domain);
|
||||
remove_sei_domain:
|
||||
irq_domain_remove(sei->sei_domain);
|
||||
dispose_irq:
|
||||
irq_dispose_mapping(parent_irq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct mvebu_sei_caps mvebu_sei_ap806_caps = {
|
||||
.ap_range = {
|
||||
.first = 0,
|
||||
.size = 21,
|
||||
},
|
||||
.cp_range = {
|
||||
.first = 21,
|
||||
.size = 43,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct of_device_id mvebu_sei_of_match[] = {
|
||||
{
|
||||
.compatible = "marvell,ap806-sei",
|
||||
.data = &mvebu_sei_ap806_caps,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver mvebu_sei_driver = {
|
||||
.probe = mvebu_sei_probe,
|
||||
.driver = {
|
||||
.name = "mvebu-sei",
|
||||
.of_match_table = mvebu_sei_of_match,
|
||||
},
|
||||
};
|
||||
builtin_platform_driver(mvebu_sei_driver);
|
|
@ -124,6 +124,7 @@ static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type)
|
|||
break;
|
||||
case IRQ_TYPE_EDGE_BOTH:
|
||||
pdc_type = PDC_EDGE_DUAL;
|
||||
type = IRQ_TYPE_EDGE_RISING;
|
||||
break;
|
||||
case IRQ_TYPE_LEVEL_HIGH:
|
||||
pdc_type = PDC_LEVEL_HIGH;
|
||||
|
|
|
@ -672,6 +672,7 @@ void efi_native_runtime_setup(void);
|
|||
#define LINUX_EFI_LOADER_ENTRY_GUID EFI_GUID(0x4a67b082, 0x0a4c, 0x41cf, 0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f)
|
||||
#define LINUX_EFI_RANDOM_SEED_TABLE_GUID EFI_GUID(0x1ce1e5bc, 0x7ceb, 0x42f2, 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b)
|
||||
#define LINUX_EFI_TPM_EVENT_LOG_GUID EFI_GUID(0xb7799cb0, 0xeca2, 0x4943, 0x96, 0x67, 0x1f, 0xae, 0x07, 0xb7, 0x47, 0xfa)
|
||||
#define LINUX_EFI_MEMRESERVE_TABLE_GUID EFI_GUID(0x888eb0c6, 0x8ede, 0x4ff5, 0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2)
|
||||
|
||||
typedef struct {
|
||||
efi_guid_t guid;
|
||||
|
@ -957,6 +958,7 @@ extern struct efi {
|
|||
unsigned long mem_attr_table; /* memory attributes table */
|
||||
unsigned long rng_seed; /* UEFI firmware random seed */
|
||||
unsigned long tpm_log; /* TPM2 Event Log table */
|
||||
unsigned long mem_reserve; /* Linux EFI memreserve table */
|
||||
efi_get_time_t *get_time;
|
||||
efi_set_time_t *set_time;
|
||||
efi_get_wakeup_time_t *get_wakeup_time;
|
||||
|
@ -1041,6 +1043,7 @@ extern int __init efi_uart_console_only (void);
|
|||
extern u64 efi_mem_desc_end(efi_memory_desc_t *md);
|
||||
extern int efi_mem_desc_lookup(u64 phys_addr, efi_memory_desc_t *out_md);
|
||||
extern void efi_mem_reserve(phys_addr_t addr, u64 size);
|
||||
extern int efi_mem_reserve_persistent(phys_addr_t addr, u64 size);
|
||||
extern void efi_initialize_iomem_resources(struct resource *code_resource,
|
||||
struct resource *data_resource, struct resource *bss_resource);
|
||||
extern void efi_reserve_boot_services(void);
|
||||
|
@ -1662,4 +1665,10 @@ extern int efi_tpm_eventlog_init(void);
|
|||
/* Workqueue to queue EFI Runtime Services */
|
||||
extern struct workqueue_struct *efi_rts_wq;
|
||||
|
||||
struct linux_efi_memreserve {
|
||||
phys_addr_t next;
|
||||
phys_addr_t base;
|
||||
phys_addr_t size;
|
||||
};
|
||||
|
||||
#endif /* _LINUX_EFI_H */
|
||||
|
|
|
@ -13,6 +13,12 @@
|
|||
#include <linux/types.h>
|
||||
#include <linux/ioport.h>
|
||||
|
||||
#define GICD_INT_DEF_PRI 0xa0
|
||||
#define GICD_INT_DEF_PRI_X4 ((GICD_INT_DEF_PRI << 24) |\
|
||||
(GICD_INT_DEF_PRI << 16) |\
|
||||
(GICD_INT_DEF_PRI << 8) |\
|
||||
GICD_INT_DEF_PRI)
|
||||
|
||||
enum gic_type {
|
||||
GIC_V2,
|
||||
GIC_V3,
|
||||
|
|
|
@ -585,8 +585,10 @@ struct rdists {
|
|||
void __iomem *rd_base;
|
||||
struct page *pend_page;
|
||||
phys_addr_t phys_base;
|
||||
bool lpi_enabled;
|
||||
} __percpu *rdist;
|
||||
struct page *prop_page;
|
||||
phys_addr_t prop_table_pa;
|
||||
void *prop_table_va;
|
||||
u64 flags;
|
||||
u32 gicd_typer;
|
||||
bool has_vlpis;
|
||||
|
|
|
@ -65,11 +65,6 @@
|
|||
#define GICD_INT_EN_CLR_X32 0xffffffff
|
||||
#define GICD_INT_EN_SET_SGI 0x0000ffff
|
||||
#define GICD_INT_EN_CLR_PPI 0xffff0000
|
||||
#define GICD_INT_DEF_PRI 0xa0
|
||||
#define GICD_INT_DEF_PRI_X4 ((GICD_INT_DEF_PRI << 24) |\
|
||||
(GICD_INT_DEF_PRI << 16) |\
|
||||
(GICD_INT_DEF_PRI << 8) |\
|
||||
GICD_INT_DEF_PRI)
|
||||
|
||||
#define GICD_IIDR_IMPLEMENTER_SHIFT 0
|
||||
#define GICD_IIDR_IMPLEMENTER_MASK (0xfff << GICD_IIDR_IMPLEMENTER_SHIFT)
|
||||
|
|
|
@ -75,6 +75,7 @@ struct irq_fwspec {
|
|||
enum irq_domain_bus_token {
|
||||
DOMAIN_BUS_ANY = 0,
|
||||
DOMAIN_BUS_WIRED,
|
||||
DOMAIN_BUS_GENERIC_MSI,
|
||||
DOMAIN_BUS_PCI_MSI,
|
||||
DOMAIN_BUS_PLATFORM_MSI,
|
||||
DOMAIN_BUS_NEXUS,
|
||||
|
|
|
@ -317,11 +317,18 @@ int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev,
|
|||
int msi_domain_populate_irqs(struct irq_domain *domain, struct device *dev,
|
||||
int virq, int nvec, msi_alloc_info_t *args);
|
||||
struct irq_domain *
|
||||
platform_msi_create_device_domain(struct device *dev,
|
||||
unsigned int nvec,
|
||||
irq_write_msi_msg_t write_msi_msg,
|
||||
const struct irq_domain_ops *ops,
|
||||
void *host_data);
|
||||
__platform_msi_create_device_domain(struct device *dev,
|
||||
unsigned int nvec,
|
||||
bool is_tree,
|
||||
irq_write_msi_msg_t write_msi_msg,
|
||||
const struct irq_domain_ops *ops,
|
||||
void *host_data);
|
||||
|
||||
#define platform_msi_create_device_domain(dev, nvec, write, ops, data) \
|
||||
__platform_msi_create_device_domain(dev, nvec, false, write, ops, data)
|
||||
#define platform_msi_create_device_tree_domain(dev, nvec, write, ops, data) \
|
||||
__platform_msi_create_device_domain(dev, nvec, true, write, ops, data)
|
||||
|
||||
int platform_msi_domain_alloc(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs);
|
||||
void platform_msi_domain_free(struct irq_domain *domain, unsigned int virq,
|
||||
|
|
Loading…
Reference in New Issue