irqchip/apple-aic: Add support for AICv2
Introduce support for the new AICv2 hardware block in t6000/t6001 SoCs. It seems these blocks are missing the information required to compute the event register offset in the capability registers, so we specify that in the DT as a second reg entry. Signed-off-by: Hector Martin <marcan@marcan.st> Signed-off-by: Marc Zyngier <maz@kernel.org> Link: https://lore.kernel.org/r/20220309192123.152028-8-marcan@marcan.st
This commit is contained in:
Родитель
a801f0ee56
Коммит
768d4435de
|
@ -103,6 +103,58 @@
|
||||||
|
|
||||||
#define AIC_MAX_IRQ 0x400
|
#define AIC_MAX_IRQ 0x400
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AIC v2 registers (MMIO)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define AIC2_VERSION 0x0000
|
||||||
|
#define AIC2_VERSION_VER GENMASK(7, 0)
|
||||||
|
|
||||||
|
#define AIC2_INFO1 0x0004
|
||||||
|
#define AIC2_INFO1_NR_IRQ GENMASK(15, 0)
|
||||||
|
#define AIC2_INFO1_LAST_DIE GENMASK(27, 24)
|
||||||
|
|
||||||
|
#define AIC2_INFO2 0x0008
|
||||||
|
|
||||||
|
#define AIC2_INFO3 0x000c
|
||||||
|
#define AIC2_INFO3_MAX_IRQ GENMASK(15, 0)
|
||||||
|
#define AIC2_INFO3_MAX_DIE GENMASK(27, 24)
|
||||||
|
|
||||||
|
#define AIC2_RESET 0x0010
|
||||||
|
#define AIC2_RESET_RESET BIT(0)
|
||||||
|
|
||||||
|
#define AIC2_CONFIG 0x0014
|
||||||
|
#define AIC2_CONFIG_ENABLE BIT(0)
|
||||||
|
#define AIC2_CONFIG_PREFER_PCPU BIT(28)
|
||||||
|
|
||||||
|
#define AIC2_TIMEOUT 0x0028
|
||||||
|
#define AIC2_CLUSTER_PRIO 0x0030
|
||||||
|
#define AIC2_DELAY_GROUPS 0x0100
|
||||||
|
|
||||||
|
#define AIC2_IRQ_CFG 0x2000
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AIC2 registers are laid out like this, starting at AIC2_IRQ_CFG:
|
||||||
|
*
|
||||||
|
* Repeat for each die:
|
||||||
|
* IRQ_CFG: u32 * MAX_IRQS
|
||||||
|
* SW_SET: u32 * (MAX_IRQS / 32)
|
||||||
|
* SW_CLR: u32 * (MAX_IRQS / 32)
|
||||||
|
* MASK_SET: u32 * (MAX_IRQS / 32)
|
||||||
|
* MASK_CLR: u32 * (MAX_IRQS / 32)
|
||||||
|
* HW_STATE: u32 * (MAX_IRQS / 32)
|
||||||
|
*
|
||||||
|
* This is followed by a set of event registers, each 16K page aligned.
|
||||||
|
* The first one is the AP event register we will use. Unfortunately,
|
||||||
|
* the actual implemented die count is not specified anywhere in the
|
||||||
|
* capability registers, so we have to explicitly specify the event
|
||||||
|
* register as a second reg entry in the device tree to remain
|
||||||
|
* forward-compatible.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define AIC2_IRQ_CFG_TARGET GENMASK(3, 0)
|
||||||
|
#define AIC2_IRQ_CFG_DELAY_IDX GENMASK(7, 5)
|
||||||
|
|
||||||
#define MASK_REG(x) (4 * ((x) >> 5))
|
#define MASK_REG(x) (4 * ((x) >> 5))
|
||||||
#define MASK_BIT(x) BIT((x) & GENMASK(4, 0))
|
#define MASK_BIT(x) BIT((x) & GENMASK(4, 0))
|
||||||
|
|
||||||
|
@ -193,6 +245,7 @@ struct aic_info {
|
||||||
/* Register offsets */
|
/* Register offsets */
|
||||||
u32 event;
|
u32 event;
|
||||||
u32 target_cpu;
|
u32 target_cpu;
|
||||||
|
u32 irq_cfg;
|
||||||
u32 sw_set;
|
u32 sw_set;
|
||||||
u32 sw_clr;
|
u32 sw_clr;
|
||||||
u32 mask_set;
|
u32 mask_set;
|
||||||
|
@ -220,6 +273,14 @@ static const struct aic_info aic1_fipi_info = {
|
||||||
.fast_ipi = true,
|
.fast_ipi = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const struct aic_info aic2_info = {
|
||||||
|
.version = 2,
|
||||||
|
|
||||||
|
.irq_cfg = AIC2_IRQ_CFG,
|
||||||
|
|
||||||
|
.fast_ipi = true,
|
||||||
|
};
|
||||||
|
|
||||||
static const struct of_device_id aic_info_match[] = {
|
static const struct of_device_id aic_info_match[] = {
|
||||||
{
|
{
|
||||||
.compatible = "apple,t8103-aic",
|
.compatible = "apple,t8103-aic",
|
||||||
|
@ -229,11 +290,16 @@ static const struct of_device_id aic_info_match[] = {
|
||||||
.compatible = "apple,aic",
|
.compatible = "apple,aic",
|
||||||
.data = &aic1_info,
|
.data = &aic1_info,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.compatible = "apple,aic2",
|
||||||
|
.data = &aic2_info,
|
||||||
|
},
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct aic_irq_chip {
|
struct aic_irq_chip {
|
||||||
void __iomem *base;
|
void __iomem *base;
|
||||||
|
void __iomem *event;
|
||||||
struct irq_domain *hw_domain;
|
struct irq_domain *hw_domain;
|
||||||
struct irq_domain *ipi_domain;
|
struct irq_domain *ipi_domain;
|
||||||
|
|
||||||
|
@ -310,7 +376,7 @@ static void __exception_irq_entry aic_handle_irq(struct pt_regs *regs)
|
||||||
* We cannot use a relaxed read here, as reads from DMA buffers
|
* We cannot use a relaxed read here, as reads from DMA buffers
|
||||||
* need to be ordered after the IRQ fires.
|
* need to be ordered after the IRQ fires.
|
||||||
*/
|
*/
|
||||||
event = readl(ic->base + ic->info.event);
|
event = readl(ic->event + ic->info.event);
|
||||||
type = FIELD_GET(AIC_EVENT_TYPE, event);
|
type = FIELD_GET(AIC_EVENT_TYPE, event);
|
||||||
irq = FIELD_GET(AIC_EVENT_NUM, event);
|
irq = FIELD_GET(AIC_EVENT_NUM, event);
|
||||||
|
|
||||||
|
@ -373,6 +439,14 @@ static struct irq_chip aic_chip = {
|
||||||
.irq_set_type = aic_irq_set_type,
|
.irq_set_type = aic_irq_set_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static struct irq_chip aic2_chip = {
|
||||||
|
.name = "AIC2",
|
||||||
|
.irq_mask = aic_irq_mask,
|
||||||
|
.irq_unmask = aic_irq_unmask,
|
||||||
|
.irq_eoi = aic_irq_eoi,
|
||||||
|
.irq_set_type = aic_irq_set_type,
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* FIQ irqchip
|
* FIQ irqchip
|
||||||
*/
|
*/
|
||||||
|
@ -529,10 +603,15 @@ static struct irq_chip fiq_chip = {
|
||||||
static int aic_irq_domain_map(struct irq_domain *id, unsigned int irq,
|
static int aic_irq_domain_map(struct irq_domain *id, unsigned int irq,
|
||||||
irq_hw_number_t hw)
|
irq_hw_number_t hw)
|
||||||
{
|
{
|
||||||
|
struct aic_irq_chip *ic = id->host_data;
|
||||||
u32 type = FIELD_GET(AIC_EVENT_TYPE, hw);
|
u32 type = FIELD_GET(AIC_EVENT_TYPE, hw);
|
||||||
|
struct irq_chip *chip = &aic_chip;
|
||||||
|
|
||||||
|
if (ic->info.version == 2)
|
||||||
|
chip = &aic2_chip;
|
||||||
|
|
||||||
if (type == AIC_EVENT_TYPE_IRQ) {
|
if (type == AIC_EVENT_TYPE_IRQ) {
|
||||||
irq_domain_set_info(id, irq, hw, &aic_chip, id->host_data,
|
irq_domain_set_info(id, irq, hw, chip, id->host_data,
|
||||||
handle_fasteoi_irq, NULL, NULL);
|
handle_fasteoi_irq, NULL, NULL);
|
||||||
irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
|
irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
|
||||||
} else {
|
} else {
|
||||||
|
@ -888,24 +967,26 @@ static int aic_init_cpu(unsigned int cpu)
|
||||||
/* Commit all of the above */
|
/* Commit all of the above */
|
||||||
isb();
|
isb();
|
||||||
|
|
||||||
/*
|
if (aic_irqc->info.version == 1) {
|
||||||
* Make sure the kernel's idea of logical CPU order is the same as AIC's
|
/*
|
||||||
* If we ever end up with a mismatch here, we will have to introduce
|
* Make sure the kernel's idea of logical CPU order is the same as AIC's
|
||||||
* a mapping table similar to what other irqchip drivers do.
|
* If we ever end up with a mismatch here, we will have to introduce
|
||||||
*/
|
* a mapping table similar to what other irqchip drivers do.
|
||||||
WARN_ON(aic_ic_read(aic_irqc, AIC_WHOAMI) != smp_processor_id());
|
*/
|
||||||
|
WARN_ON(aic_ic_read(aic_irqc, AIC_WHOAMI) != smp_processor_id());
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Always keep IPIs unmasked at the hardware level (except auto-masking
|
* Always keep IPIs unmasked at the hardware level (except auto-masking
|
||||||
* by AIC during processing). We manage masks at the vIPI level.
|
* by AIC during processing). We manage masks at the vIPI level.
|
||||||
* These registers only exist on AICv1, AICv2 always uses fast IPIs.
|
* These registers only exist on AICv1, AICv2 always uses fast IPIs.
|
||||||
*/
|
*/
|
||||||
aic_ic_write(aic_irqc, AIC_IPI_ACK, AIC_IPI_SELF | AIC_IPI_OTHER);
|
aic_ic_write(aic_irqc, AIC_IPI_ACK, AIC_IPI_SELF | AIC_IPI_OTHER);
|
||||||
if (static_branch_likely(&use_fast_ipi)) {
|
if (static_branch_likely(&use_fast_ipi)) {
|
||||||
aic_ic_write(aic_irqc, AIC_IPI_MASK_SET, AIC_IPI_SELF | AIC_IPI_OTHER);
|
aic_ic_write(aic_irqc, AIC_IPI_MASK_SET, AIC_IPI_SELF | AIC_IPI_OTHER);
|
||||||
} else {
|
} else {
|
||||||
aic_ic_write(aic_irqc, AIC_IPI_MASK_SET, AIC_IPI_SELF);
|
aic_ic_write(aic_irqc, AIC_IPI_MASK_SET, AIC_IPI_SELF);
|
||||||
aic_ic_write(aic_irqc, AIC_IPI_MASK_CLR, AIC_IPI_OTHER);
|
aic_ic_write(aic_irqc, AIC_IPI_MASK_CLR, AIC_IPI_OTHER);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize the local mask state */
|
/* Initialize the local mask state */
|
||||||
|
@ -933,14 +1014,16 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p
|
||||||
return -EIO;
|
return -EIO;
|
||||||
|
|
||||||
irqc = kzalloc(sizeof(*irqc), GFP_KERNEL);
|
irqc = kzalloc(sizeof(*irqc), GFP_KERNEL);
|
||||||
if (!irqc)
|
if (!irqc) {
|
||||||
|
iounmap(regs);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
irqc->base = regs;
|
irqc->base = regs;
|
||||||
|
|
||||||
match = of_match_node(aic_info_match, node);
|
match = of_match_node(aic_info_match, node);
|
||||||
if (!match)
|
if (!match)
|
||||||
return -ENODEV;
|
goto err_unmap;
|
||||||
|
|
||||||
irqc->info = *(struct aic_info *)match->data;
|
irqc->info = *(struct aic_info *)match->data;
|
||||||
|
|
||||||
|
@ -958,6 +1041,28 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p
|
||||||
off = start_off = irqc->info.target_cpu;
|
off = start_off = irqc->info.target_cpu;
|
||||||
off += sizeof(u32) * irqc->max_irq; /* TARGET_CPU */
|
off += sizeof(u32) * irqc->max_irq; /* TARGET_CPU */
|
||||||
|
|
||||||
|
irqc->event = irqc->base;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
u32 info1, info3;
|
||||||
|
|
||||||
|
info1 = aic_ic_read(irqc, AIC2_INFO1);
|
||||||
|
info3 = aic_ic_read(irqc, AIC2_INFO3);
|
||||||
|
|
||||||
|
irqc->nr_irq = FIELD_GET(AIC2_INFO1_NR_IRQ, info1);
|
||||||
|
irqc->max_irq = FIELD_GET(AIC2_INFO3_MAX_IRQ, info3);
|
||||||
|
irqc->nr_die = FIELD_GET(AIC2_INFO1_LAST_DIE, info1) + 1;
|
||||||
|
irqc->max_die = FIELD_GET(AIC2_INFO3_MAX_DIE, info3);
|
||||||
|
|
||||||
|
off = start_off = irqc->info.irq_cfg;
|
||||||
|
off += sizeof(u32) * irqc->max_irq; /* IRQ_CFG */
|
||||||
|
|
||||||
|
irqc->event = of_iomap(node, 1);
|
||||||
|
if (WARN_ON(!irqc->event))
|
||||||
|
goto err_unmap;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -981,20 +1086,13 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p
|
||||||
|
|
||||||
irqc->hw_domain = irq_domain_create_tree(of_node_to_fwnode(node),
|
irqc->hw_domain = irq_domain_create_tree(of_node_to_fwnode(node),
|
||||||
&aic_irq_domain_ops, irqc);
|
&aic_irq_domain_ops, irqc);
|
||||||
if (WARN_ON(!irqc->hw_domain)) {
|
if (WARN_ON(!irqc->hw_domain))
|
||||||
iounmap(irqc->base);
|
goto err_unmap;
|
||||||
kfree(irqc);
|
|
||||||
return -ENODEV;
|
|
||||||
}
|
|
||||||
|
|
||||||
irq_domain_update_bus_token(irqc->hw_domain, DOMAIN_BUS_WIRED);
|
irq_domain_update_bus_token(irqc->hw_domain, DOMAIN_BUS_WIRED);
|
||||||
|
|
||||||
if (aic_init_smp(irqc, node)) {
|
if (aic_init_smp(irqc, node))
|
||||||
irq_domain_remove(irqc->hw_domain);
|
goto err_remove_domain;
|
||||||
iounmap(irqc->base);
|
|
||||||
kfree(irqc);
|
|
||||||
return -ENODEV;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_handle_irq(aic_handle_irq);
|
set_handle_irq(aic_handle_irq);
|
||||||
set_handle_fiq(aic_handle_fiq);
|
set_handle_fiq(aic_handle_fiq);
|
||||||
|
@ -1011,6 +1109,13 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p
|
||||||
off += irqc->info.die_stride;
|
off += irqc->info.die_stride;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (irqc->info.version == 2) {
|
||||||
|
u32 config = aic_ic_read(irqc, AIC2_CONFIG);
|
||||||
|
|
||||||
|
config |= AIC2_CONFIG_ENABLE;
|
||||||
|
aic_ic_write(irqc, AIC2_CONFIG, config);
|
||||||
|
}
|
||||||
|
|
||||||
if (!is_kernel_in_hyp_mode())
|
if (!is_kernel_in_hyp_mode())
|
||||||
pr_info("Kernel running in EL1, mapping interrupts");
|
pr_info("Kernel running in EL1, mapping interrupts");
|
||||||
|
|
||||||
|
@ -1027,6 +1132,16 @@ static int __init aic_of_ic_init(struct device_node *node, struct device_node *p
|
||||||
irqc->nr_irq, irqc->max_irq, irqc->nr_die, irqc->max_die, AIC_NR_FIQ, AIC_NR_SWIPI);
|
irqc->nr_irq, irqc->max_irq, irqc->nr_die, irqc->max_die, AIC_NR_FIQ, AIC_NR_SWIPI);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
err_remove_domain:
|
||||||
|
irq_domain_remove(irqc->hw_domain);
|
||||||
|
err_unmap:
|
||||||
|
if (irqc->event && irqc->event != irqc->base)
|
||||||
|
iounmap(irqc->event);
|
||||||
|
iounmap(irqc->base);
|
||||||
|
kfree(irqc);
|
||||||
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
IRQCHIP_DECLARE(apple_m1_aic, "apple,aic", aic_of_ic_init);
|
IRQCHIP_DECLARE(apple_aic, "apple,aic", aic_of_ic_init);
|
||||||
|
IRQCHIP_DECLARE(apple_aic2, "apple,aic2", aic_of_ic_init);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче