Skip to content

Commit d4cb18c

Browse files
committed
irqchip/apple-aic: Add support for the Apple Interrupt Controller
This is the root interrupt controller used on Apple ARM SoCs such as the M1. Signed-off-by: Hector Martin <marcan@marcan.st>
1 parent b85355e commit d4cb18c

File tree

3 files changed

+204
-0
lines changed

3 files changed

+204
-0
lines changed

drivers/irqchip/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,4 +589,13 @@ config MST_IRQ
589589
help
590590
Support MStar Interrupt Controller.
591591

592+
config APPLE_AIC
593+
bool "Apple Interrupt Controller (AIC)"
594+
depends on ARCH_APPLE || COMPILE_TEST
595+
default ARCH_APPLE
596+
select IRQ_DOMAIN
597+
select IRQ_DOMAIN_HIERARCHY
598+
help
599+
Support for the Apple Interrupt Controller found on Apple Silicon SoCs such as the M1.
600+
592601
endmenu

drivers/irqchip/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,4 @@ obj-$(CONFIG_LOONGSON_PCH_PIC) += irq-loongson-pch-pic.o
113113
obj-$(CONFIG_LOONGSON_PCH_MSI) += irq-loongson-pch-msi.o
114114
obj-$(CONFIG_MST_IRQ) += irq-mst-intc.o
115115
obj-$(CONFIG_SL28CPLD_INTC) += irq-sl28cpld.o
116+
obj-$(CONFIG_APPLE_AIC) += irq-apple-aic.o

drivers/irqchip/irq-apple-aic.c

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Copyright 2021 Hector Martin <marcan@marcan.st>
4+
*
5+
* Based on irq-lpc32xx:
6+
* Copyright 2015-2016 Vladimir Zapolskiy <vz@mleia.com>
7+
*/
8+
9+
#define pr_fmt(fmt) "%s: " fmt, __func__
10+
11+
#include <linux/io.h>
12+
#include <linux/irqchip.h>
13+
#include <linux/irqchip/chained_irq.h>
14+
#include <linux/of_address.h>
15+
#include <linux/of_irq.h>
16+
#include <linux/of_platform.h>
17+
#include <linux/slab.h>
18+
#include <asm/exception.h>
19+
20+
#define AIC_EVENT 0x2004
21+
22+
#define AIC_EVENT_TYPE_HW 1
23+
24+
#define AIC_TARGET_CPU 0x3000
25+
#define AIC_SW_GEN_SET 0x4000
26+
#define AIC_SW_GEN_CLR 0x4080
27+
#define AIC_MASK_SET 0x4100
28+
#define AIC_MASK_CLR 0x4180
29+
30+
#define MASK_REG(x) (4 * ((x)>>5))
31+
#define MASK_BIT(x) BIT((x)&0x1f)
32+
33+
#define NR_AIC_IRQS 896
34+
35+
struct aic_irq_chip {
36+
void __iomem *base;
37+
struct irq_domain *domain;
38+
};
39+
40+
static DEFINE_SPINLOCK(aic_lock);
41+
static struct aic_irq_chip *aic_irqc;
42+
43+
static inline u32 aic_ic_read(struct aic_irq_chip *ic, u32 reg)
44+
{
45+
return readl(ic->base + reg);
46+
}
47+
48+
static inline void aic_ic_write(struct aic_irq_chip *ic,
49+
u32 reg, u32 val)
50+
{
51+
writel(val, ic->base + reg);
52+
}
53+
54+
static void aic_irq_mask(struct irq_data *d)
55+
{
56+
struct aic_irq_chip *ic = irq_data_get_irq_chip_data(d);
57+
58+
aic_ic_write(ic, AIC_MASK_SET + MASK_REG(d->hwirq), MASK_BIT(d->hwirq));
59+
}
60+
61+
static void aic_irq_unmask(struct irq_data *d)
62+
{
63+
struct aic_irq_chip *ic = irq_data_get_irq_chip_data(d);
64+
65+
aic_ic_write(ic, AIC_MASK_CLR + MASK_REG(d->hwirq), MASK_BIT(d->hwirq));
66+
}
67+
68+
static void aic_irq_eoi(struct irq_data *d)
69+
{
70+
struct aic_irq_chip *ic = irq_data_get_irq_chip_data(d);
71+
72+
/*
73+
* Reading the interrupt reason automatically acknowledges and masks the IRQ,
74+
* so we just unmask it here if needed.
75+
*/
76+
if (!irqd_irq_disabled(d) && !irqd_irq_masked(d))
77+
aic_irq_unmask(d);
78+
}
79+
80+
static int aic_irq_set_affinity(struct irq_data *d,
81+
const struct cpumask *mask_val, bool force)
82+
{
83+
irq_hw_number_t hwirq = irqd_to_hwirq(d);
84+
struct aic_irq_chip *ic = irq_data_get_irq_chip_data(d);
85+
unsigned long flags;
86+
unsigned int cpu;
87+
88+
if (force)
89+
cpu = cpumask_first(mask_val);
90+
else
91+
cpu = cpumask_any_and(mask_val, cpu_online_mask);
92+
93+
if (cpu >= nr_cpu_ids)
94+
return -EINVAL;
95+
96+
spin_lock_irqsave(&aic_lock, flags);
97+
aic_ic_write(ic, AIC_TARGET_CPU + hwirq * 4, BIT(cpu));
98+
irq_data_update_effective_affinity(d, cpumask_of(cpu));
99+
spin_unlock_irqrestore(&aic_lock, flags);
100+
101+
return IRQ_SET_MASK_OK;
102+
}
103+
104+
static void __exception_irq_entry aic_handle_irq(struct pt_regs *regs)
105+
{
106+
struct aic_irq_chip *ic = aic_irqc;
107+
u32 reason = aic_ic_read(ic, AIC_EVENT);
108+
109+
while (reason) {
110+
u32 type = reason >> 16, irq = reason & 0xffff;
111+
if (type == AIC_EVENT_TYPE_HW) {
112+
handle_domain_irq(aic_irqc->domain, irq, regs);
113+
} else {
114+
pr_err("spurious IRQ event %d, %d\n", type, reason);
115+
}
116+
reason = aic_ic_read(ic, AIC_EVENT);
117+
}
118+
}
119+
120+
static struct irq_chip aic_chip = {
121+
.name = "AIC",
122+
.irq_mask = aic_irq_mask,
123+
.irq_unmask = aic_irq_unmask,
124+
.irq_eoi = aic_irq_eoi,
125+
#ifdef CONFIG_SMP
126+
.irq_set_affinity = aic_irq_set_affinity,
127+
#endif
128+
};
129+
130+
static int aic_irq_domain_map(struct irq_domain *id, unsigned int irq,
131+
irq_hw_number_t hw)
132+
{
133+
struct aic_irq_chip *ic = id->host_data;
134+
135+
irq_set_chip_data(irq, ic);
136+
irq_set_chip_and_handler(irq, &aic_chip, handle_fasteoi_irq);
137+
irq_set_status_flags(irq, IRQ_LEVEL);
138+
irq_set_noprobe(irq);
139+
140+
return 0;
141+
}
142+
143+
static void aic_irq_domain_unmap(struct irq_domain *id, unsigned int irq)
144+
{
145+
irq_set_chip_and_handler(irq, NULL, NULL);
146+
}
147+
148+
static const struct irq_domain_ops aic_irq_domain_ops = {
149+
.map = aic_irq_domain_map,
150+
.unmap = aic_irq_domain_unmap,
151+
.xlate = irq_domain_xlate_twocell,
152+
};
153+
154+
static int __init aic_of_ic_init(struct device_node *node,
155+
struct device_node *parent)
156+
{
157+
int i;
158+
void __iomem *regs;
159+
struct aic_irq_chip *irqc;
160+
161+
regs = of_iomap(node, 0);
162+
if (WARN_ON(!regs))
163+
return -EIO;
164+
165+
irqc = kzalloc(sizeof(*irqc), GFP_KERNEL);
166+
if (!irqc)
167+
return -ENOMEM;
168+
169+
irqc->base = regs;
170+
irqc->domain = irq_domain_add_linear(node, NR_AIC_IRQS,
171+
&aic_irq_domain_ops, irqc);
172+
if (!irqc->domain) {
173+
pr_err("unable to add irq domain\n");
174+
iounmap(irqc->base);
175+
kfree(irqc);
176+
return -ENODEV;
177+
}
178+
179+
aic_irqc = irqc;
180+
set_handle_irq(aic_handle_irq);
181+
182+
for (i = 0; i < BITS_TO_LONGS(NR_AIC_IRQS); i++)
183+
aic_ic_write(irqc, AIC_MASK_SET + i * 4, ~0);
184+
for (i = 0; i < BITS_TO_LONGS(NR_AIC_IRQS); i++)
185+
aic_ic_write(irqc, AIC_SW_GEN_CLR + i * 4, ~0);
186+
for (i = 0; i < NR_AIC_IRQS; i++)
187+
aic_ic_write(irqc, AIC_TARGET_CPU + i * 4, 1);
188+
189+
pr_info("AIC: initialized\n");
190+
191+
return 0;
192+
}
193+
194+
IRQCHIP_DECLARE(apple_t8103_aic, "apple,t8103-aic", aic_of_ic_init);

0 commit comments

Comments
 (0)