1d831c5fdSJamin Lin /*
2d831c5fdSJamin Lin * ASPEED INTC Controller
3d831c5fdSJamin Lin *
4d831c5fdSJamin Lin * Copyright (C) 2024 ASPEED Technology Inc.
5d831c5fdSJamin Lin *
6d831c5fdSJamin Lin * SPDX-License-Identifier: GPL-2.0-or-later
7d831c5fdSJamin Lin */
8d831c5fdSJamin Lin
9d831c5fdSJamin Lin #include "qemu/osdep.h"
10d831c5fdSJamin Lin #include "hw/intc/aspeed_intc.h"
11d831c5fdSJamin Lin #include "hw/irq.h"
12d831c5fdSJamin Lin #include "qemu/log.h"
13d831c5fdSJamin Lin #include "trace.h"
14d831c5fdSJamin Lin #include "hw/registerfields.h"
15d831c5fdSJamin Lin #include "qapi/error.h"
16d831c5fdSJamin Lin
17d831c5fdSJamin Lin /* INTC Registers */
18d831c5fdSJamin Lin REG32(GICINT128_EN, 0x1000)
19d831c5fdSJamin Lin REG32(GICINT128_STATUS, 0x1004)
20d831c5fdSJamin Lin REG32(GICINT129_EN, 0x1100)
21d831c5fdSJamin Lin REG32(GICINT129_STATUS, 0x1104)
22d831c5fdSJamin Lin REG32(GICINT130_EN, 0x1200)
23d831c5fdSJamin Lin REG32(GICINT130_STATUS, 0x1204)
24d831c5fdSJamin Lin REG32(GICINT131_EN, 0x1300)
25d831c5fdSJamin Lin REG32(GICINT131_STATUS, 0x1304)
26d831c5fdSJamin Lin REG32(GICINT132_EN, 0x1400)
27d831c5fdSJamin Lin REG32(GICINT132_STATUS, 0x1404)
28d831c5fdSJamin Lin REG32(GICINT133_EN, 0x1500)
29d831c5fdSJamin Lin REG32(GICINT133_STATUS, 0x1504)
30d831c5fdSJamin Lin REG32(GICINT134_EN, 0x1600)
31d831c5fdSJamin Lin REG32(GICINT134_STATUS, 0x1604)
32d831c5fdSJamin Lin REG32(GICINT135_EN, 0x1700)
33d831c5fdSJamin Lin REG32(GICINT135_STATUS, 0x1704)
34d831c5fdSJamin Lin REG32(GICINT136_EN, 0x1800)
35d831c5fdSJamin Lin REG32(GICINT136_STATUS, 0x1804)
36d831c5fdSJamin Lin
37d831c5fdSJamin Lin #define GICINT_STATUS_BASE R_GICINT128_STATUS
38d831c5fdSJamin Lin
aspeed_intc_update(AspeedINTCState * s,int irq,int level)39d831c5fdSJamin Lin static void aspeed_intc_update(AspeedINTCState *s, int irq, int level)
40d831c5fdSJamin Lin {
41d831c5fdSJamin Lin AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s);
42d831c5fdSJamin Lin
43d831c5fdSJamin Lin if (irq >= aic->num_ints) {
44d831c5fdSJamin Lin qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n",
45d831c5fdSJamin Lin __func__, irq);
46d831c5fdSJamin Lin return;
47d831c5fdSJamin Lin }
48d831c5fdSJamin Lin
49d831c5fdSJamin Lin trace_aspeed_intc_update_irq(irq, level);
50d831c5fdSJamin Lin qemu_set_irq(s->output_pins[irq], level);
51d831c5fdSJamin Lin }
52d831c5fdSJamin Lin
53d831c5fdSJamin Lin /*
54d831c5fdSJamin Lin * The address of GICINT128 to GICINT136 are from 0x1000 to 0x1804.
55d831c5fdSJamin Lin * Utilize "address & 0x0f00" to get the irq and irq output pin index
56d831c5fdSJamin Lin * The value of irq should be 0 to num_ints.
57d831c5fdSJamin Lin * The irq 0 indicates GICINT128, irq 1 indicates GICINT129 and so on.
58d831c5fdSJamin Lin */
aspeed_intc_set_irq(void * opaque,int irq,int level)59d831c5fdSJamin Lin static void aspeed_intc_set_irq(void *opaque, int irq, int level)
60d831c5fdSJamin Lin {
61d831c5fdSJamin Lin AspeedINTCState *s = (AspeedINTCState *)opaque;
62d831c5fdSJamin Lin AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s);
63d831c5fdSJamin Lin uint32_t status_addr = GICINT_STATUS_BASE + ((0x100 * irq) >> 2);
64d831c5fdSJamin Lin uint32_t select = 0;
65d831c5fdSJamin Lin uint32_t enable;
66d831c5fdSJamin Lin int i;
67d831c5fdSJamin Lin
68d831c5fdSJamin Lin if (irq >= aic->num_ints) {
69d831c5fdSJamin Lin qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n",
70d831c5fdSJamin Lin __func__, irq);
71d831c5fdSJamin Lin return;
72d831c5fdSJamin Lin }
73d831c5fdSJamin Lin
74d831c5fdSJamin Lin trace_aspeed_intc_set_irq(irq, level);
75d831c5fdSJamin Lin enable = s->enable[irq];
76d831c5fdSJamin Lin
77d831c5fdSJamin Lin if (!level) {
78d831c5fdSJamin Lin return;
79d831c5fdSJamin Lin }
80d831c5fdSJamin Lin
81d831c5fdSJamin Lin for (i = 0; i < aic->num_lines; i++) {
82d831c5fdSJamin Lin if (s->orgates[irq].levels[i]) {
83d831c5fdSJamin Lin if (enable & BIT(i)) {
84d831c5fdSJamin Lin select |= BIT(i);
85d831c5fdSJamin Lin }
86d831c5fdSJamin Lin }
87d831c5fdSJamin Lin }
88d831c5fdSJamin Lin
89d831c5fdSJamin Lin if (!select) {
90d831c5fdSJamin Lin return;
91d831c5fdSJamin Lin }
92d831c5fdSJamin Lin
93d831c5fdSJamin Lin trace_aspeed_intc_select(select);
94d831c5fdSJamin Lin
95d831c5fdSJamin Lin if (s->mask[irq] || s->regs[status_addr]) {
96d831c5fdSJamin Lin /*
97d831c5fdSJamin Lin * a. mask is not 0 means in ISR mode
98d831c5fdSJamin Lin * sources interrupt routine are executing.
99d831c5fdSJamin Lin * b. status register value is not 0 means previous
100d831c5fdSJamin Lin * source interrupt does not be executed, yet.
101d831c5fdSJamin Lin *
102d831c5fdSJamin Lin * save source interrupt to pending variable.
103d831c5fdSJamin Lin */
104d831c5fdSJamin Lin s->pending[irq] |= select;
105d831c5fdSJamin Lin trace_aspeed_intc_pending_irq(irq, s->pending[irq]);
106d831c5fdSJamin Lin } else {
107d831c5fdSJamin Lin /*
108d831c5fdSJamin Lin * notify firmware which source interrupt are coming
109d831c5fdSJamin Lin * by setting status register
110d831c5fdSJamin Lin */
111d831c5fdSJamin Lin s->regs[status_addr] = select;
112d831c5fdSJamin Lin trace_aspeed_intc_trigger_irq(irq, s->regs[status_addr]);
113d831c5fdSJamin Lin aspeed_intc_update(s, irq, 1);
114d831c5fdSJamin Lin }
115d831c5fdSJamin Lin }
116d831c5fdSJamin Lin
aspeed_intc_read(void * opaque,hwaddr offset,unsigned int size)117d831c5fdSJamin Lin static uint64_t aspeed_intc_read(void *opaque, hwaddr offset, unsigned int size)
118d831c5fdSJamin Lin {
119d831c5fdSJamin Lin AspeedINTCState *s = ASPEED_INTC(opaque);
120d831c5fdSJamin Lin uint32_t addr = offset >> 2;
121d831c5fdSJamin Lin uint32_t value = 0;
122d831c5fdSJamin Lin
123d831c5fdSJamin Lin if (addr >= ASPEED_INTC_NR_REGS) {
124d831c5fdSJamin Lin qemu_log_mask(LOG_GUEST_ERROR,
125d831c5fdSJamin Lin "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
126d831c5fdSJamin Lin __func__, offset);
127d831c5fdSJamin Lin return 0;
128d831c5fdSJamin Lin }
129d831c5fdSJamin Lin
130d831c5fdSJamin Lin value = s->regs[addr];
131d831c5fdSJamin Lin trace_aspeed_intc_read(offset, size, value);
132d831c5fdSJamin Lin
133d831c5fdSJamin Lin return value;
134d831c5fdSJamin Lin }
135d831c5fdSJamin Lin
aspeed_intc_write(void * opaque,hwaddr offset,uint64_t data,unsigned size)136d831c5fdSJamin Lin static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data,
137d831c5fdSJamin Lin unsigned size)
138d831c5fdSJamin Lin {
139d831c5fdSJamin Lin AspeedINTCState *s = ASPEED_INTC(opaque);
140d831c5fdSJamin Lin AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s);
141d831c5fdSJamin Lin uint32_t addr = offset >> 2;
142d831c5fdSJamin Lin uint32_t old_enable;
143d831c5fdSJamin Lin uint32_t change;
144d831c5fdSJamin Lin uint32_t irq;
145d831c5fdSJamin Lin
146d831c5fdSJamin Lin if (addr >= ASPEED_INTC_NR_REGS) {
147d831c5fdSJamin Lin qemu_log_mask(LOG_GUEST_ERROR,
148d831c5fdSJamin Lin "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
149d831c5fdSJamin Lin __func__, offset);
150d831c5fdSJamin Lin return;
151d831c5fdSJamin Lin }
152d831c5fdSJamin Lin
153d831c5fdSJamin Lin trace_aspeed_intc_write(offset, size, data);
154d831c5fdSJamin Lin
155d831c5fdSJamin Lin switch (addr) {
156d831c5fdSJamin Lin case R_GICINT128_EN:
157d831c5fdSJamin Lin case R_GICINT129_EN:
158d831c5fdSJamin Lin case R_GICINT130_EN:
159d831c5fdSJamin Lin case R_GICINT131_EN:
160d831c5fdSJamin Lin case R_GICINT132_EN:
161d831c5fdSJamin Lin case R_GICINT133_EN:
162d831c5fdSJamin Lin case R_GICINT134_EN:
163d831c5fdSJamin Lin case R_GICINT135_EN:
164d831c5fdSJamin Lin case R_GICINT136_EN:
165d831c5fdSJamin Lin irq = (offset & 0x0f00) >> 8;
166d831c5fdSJamin Lin
167d831c5fdSJamin Lin if (irq >= aic->num_ints) {
168d831c5fdSJamin Lin qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n",
169d831c5fdSJamin Lin __func__, irq);
170d831c5fdSJamin Lin return;
171d831c5fdSJamin Lin }
172d831c5fdSJamin Lin
173d831c5fdSJamin Lin /*
174d831c5fdSJamin Lin * These registers are used for enable sources interrupt and
175d831c5fdSJamin Lin * mask and unmask source interrupt while executing source ISR.
176d831c5fdSJamin Lin */
177d831c5fdSJamin Lin
178d831c5fdSJamin Lin /* disable all source interrupt */
179d831c5fdSJamin Lin if (!data && !s->enable[irq]) {
180d831c5fdSJamin Lin s->regs[addr] = data;
181d831c5fdSJamin Lin return;
182d831c5fdSJamin Lin }
183d831c5fdSJamin Lin
184d831c5fdSJamin Lin old_enable = s->enable[irq];
185d831c5fdSJamin Lin s->enable[irq] |= data;
186d831c5fdSJamin Lin
187d831c5fdSJamin Lin /* enable new source interrupt */
188d831c5fdSJamin Lin if (old_enable != s->enable[irq]) {
189d831c5fdSJamin Lin trace_aspeed_intc_enable(s->enable[irq]);
190d831c5fdSJamin Lin s->regs[addr] = data;
191d831c5fdSJamin Lin return;
192d831c5fdSJamin Lin }
193d831c5fdSJamin Lin
194d831c5fdSJamin Lin /* mask and unmask source interrupt */
195d831c5fdSJamin Lin change = s->regs[addr] ^ data;
196d831c5fdSJamin Lin if (change & data) {
197d831c5fdSJamin Lin s->mask[irq] &= ~change;
198d831c5fdSJamin Lin trace_aspeed_intc_unmask(change, s->mask[irq]);
199d831c5fdSJamin Lin } else {
200d831c5fdSJamin Lin s->mask[irq] |= change;
201d831c5fdSJamin Lin trace_aspeed_intc_mask(change, s->mask[irq]);
202d831c5fdSJamin Lin }
203d831c5fdSJamin Lin s->regs[addr] = data;
204d831c5fdSJamin Lin break;
205d831c5fdSJamin Lin case R_GICINT128_STATUS:
206d831c5fdSJamin Lin case R_GICINT129_STATUS:
207d831c5fdSJamin Lin case R_GICINT130_STATUS:
208d831c5fdSJamin Lin case R_GICINT131_STATUS:
209d831c5fdSJamin Lin case R_GICINT132_STATUS:
210d831c5fdSJamin Lin case R_GICINT133_STATUS:
211d831c5fdSJamin Lin case R_GICINT134_STATUS:
212d831c5fdSJamin Lin case R_GICINT135_STATUS:
213d831c5fdSJamin Lin case R_GICINT136_STATUS:
214d831c5fdSJamin Lin irq = (offset & 0x0f00) >> 8;
215d831c5fdSJamin Lin
216d831c5fdSJamin Lin if (irq >= aic->num_ints) {
217d831c5fdSJamin Lin qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n",
218d831c5fdSJamin Lin __func__, irq);
219d831c5fdSJamin Lin return;
220d831c5fdSJamin Lin }
221d831c5fdSJamin Lin
222d831c5fdSJamin Lin /* clear status */
223d831c5fdSJamin Lin s->regs[addr] &= ~data;
224d831c5fdSJamin Lin
225d831c5fdSJamin Lin /*
226d831c5fdSJamin Lin * These status registers are used for notify sources ISR are executed.
227d831c5fdSJamin Lin * If one source ISR is executed, it will clear one bit.
228d831c5fdSJamin Lin * If it clear all bits, it means to initialize this register status
229d831c5fdSJamin Lin * rather than sources ISR are executed.
230d831c5fdSJamin Lin */
231d831c5fdSJamin Lin if (data == 0xffffffff) {
232d831c5fdSJamin Lin return;
233d831c5fdSJamin Lin }
234d831c5fdSJamin Lin
235d831c5fdSJamin Lin /* All source ISR execution are done */
236d831c5fdSJamin Lin if (!s->regs[addr]) {
237d831c5fdSJamin Lin trace_aspeed_intc_all_isr_done(irq);
238d831c5fdSJamin Lin if (s->pending[irq]) {
239d831c5fdSJamin Lin /*
240d831c5fdSJamin Lin * handle pending source interrupt
241d831c5fdSJamin Lin * notify firmware which source interrupt are pending
242d831c5fdSJamin Lin * by setting status register
243d831c5fdSJamin Lin */
244d831c5fdSJamin Lin s->regs[addr] = s->pending[irq];
245d831c5fdSJamin Lin s->pending[irq] = 0;
246d831c5fdSJamin Lin trace_aspeed_intc_trigger_irq(irq, s->regs[addr]);
247d831c5fdSJamin Lin aspeed_intc_update(s, irq, 1);
248d831c5fdSJamin Lin } else {
249d831c5fdSJamin Lin /* clear irq */
250d831c5fdSJamin Lin trace_aspeed_intc_clear_irq(irq, 0);
251d831c5fdSJamin Lin aspeed_intc_update(s, irq, 0);
252d831c5fdSJamin Lin }
253d831c5fdSJamin Lin }
254d831c5fdSJamin Lin break;
255d831c5fdSJamin Lin default:
256d831c5fdSJamin Lin s->regs[addr] = data;
257d831c5fdSJamin Lin break;
258d831c5fdSJamin Lin }
259d831c5fdSJamin Lin
260d831c5fdSJamin Lin return;
261d831c5fdSJamin Lin }
262d831c5fdSJamin Lin
263d831c5fdSJamin Lin static const MemoryRegionOps aspeed_intc_ops = {
264d831c5fdSJamin Lin .read = aspeed_intc_read,
265d831c5fdSJamin Lin .write = aspeed_intc_write,
266d831c5fdSJamin Lin .endianness = DEVICE_LITTLE_ENDIAN,
267d831c5fdSJamin Lin .valid = {
268d831c5fdSJamin Lin .min_access_size = 4,
269d831c5fdSJamin Lin .max_access_size = 4,
270d831c5fdSJamin Lin }
271d831c5fdSJamin Lin };
272d831c5fdSJamin Lin
aspeed_intc_instance_init(Object * obj)273d831c5fdSJamin Lin static void aspeed_intc_instance_init(Object *obj)
274d831c5fdSJamin Lin {
275d831c5fdSJamin Lin AspeedINTCState *s = ASPEED_INTC(obj);
276d831c5fdSJamin Lin AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s);
277d831c5fdSJamin Lin int i;
278d831c5fdSJamin Lin
279d831c5fdSJamin Lin assert(aic->num_ints <= ASPEED_INTC_NR_INTS);
280d831c5fdSJamin Lin for (i = 0; i < aic->num_ints; i++) {
281d831c5fdSJamin Lin object_initialize_child(obj, "intc-orgates[*]", &s->orgates[i],
282d831c5fdSJamin Lin TYPE_OR_IRQ);
283d831c5fdSJamin Lin object_property_set_int(OBJECT(&s->orgates[i]), "num-lines",
284d831c5fdSJamin Lin aic->num_lines, &error_abort);
285d831c5fdSJamin Lin }
286d831c5fdSJamin Lin }
287d831c5fdSJamin Lin
aspeed_intc_reset(DeviceState * dev)288d831c5fdSJamin Lin static void aspeed_intc_reset(DeviceState *dev)
289d831c5fdSJamin Lin {
290d831c5fdSJamin Lin AspeedINTCState *s = ASPEED_INTC(dev);
291d831c5fdSJamin Lin
292d831c5fdSJamin Lin memset(s->regs, 0, sizeof(s->regs));
293d831c5fdSJamin Lin memset(s->enable, 0, sizeof(s->enable));
294d831c5fdSJamin Lin memset(s->mask, 0, sizeof(s->mask));
295d831c5fdSJamin Lin memset(s->pending, 0, sizeof(s->pending));
296d831c5fdSJamin Lin }
297d831c5fdSJamin Lin
aspeed_intc_realize(DeviceState * dev,Error ** errp)298d831c5fdSJamin Lin static void aspeed_intc_realize(DeviceState *dev, Error **errp)
299d831c5fdSJamin Lin {
300d831c5fdSJamin Lin SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
301d831c5fdSJamin Lin AspeedINTCState *s = ASPEED_INTC(dev);
302d831c5fdSJamin Lin AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s);
303d831c5fdSJamin Lin int i;
304d831c5fdSJamin Lin
305d831c5fdSJamin Lin memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_intc_ops, s,
306d831c5fdSJamin Lin TYPE_ASPEED_INTC ".regs", ASPEED_INTC_NR_REGS << 2);
307d831c5fdSJamin Lin
308d831c5fdSJamin Lin sysbus_init_mmio(sbd, &s->iomem);
309d831c5fdSJamin Lin qdev_init_gpio_in(dev, aspeed_intc_set_irq, aic->num_ints);
310d831c5fdSJamin Lin
311d831c5fdSJamin Lin for (i = 0; i < aic->num_ints; i++) {
312d831c5fdSJamin Lin if (!qdev_realize(DEVICE(&s->orgates[i]), NULL, errp)) {
313d831c5fdSJamin Lin return;
314d831c5fdSJamin Lin }
315d831c5fdSJamin Lin sysbus_init_irq(sbd, &s->output_pins[i]);
316d831c5fdSJamin Lin }
317d831c5fdSJamin Lin }
318d831c5fdSJamin Lin
aspeed_intc_class_init(ObjectClass * klass,void * data)319d831c5fdSJamin Lin static void aspeed_intc_class_init(ObjectClass *klass, void *data)
320d831c5fdSJamin Lin {
321d831c5fdSJamin Lin DeviceClass *dc = DEVICE_CLASS(klass);
322d831c5fdSJamin Lin
323d831c5fdSJamin Lin dc->desc = "ASPEED INTC Controller";
324d831c5fdSJamin Lin dc->realize = aspeed_intc_realize;
325*e3d08143SPeter Maydell device_class_set_legacy_reset(dc, aspeed_intc_reset);
326d831c5fdSJamin Lin dc->vmsd = NULL;
327d831c5fdSJamin Lin }
328d831c5fdSJamin Lin
329d831c5fdSJamin Lin static const TypeInfo aspeed_intc_info = {
330d831c5fdSJamin Lin .name = TYPE_ASPEED_INTC,
331d831c5fdSJamin Lin .parent = TYPE_SYS_BUS_DEVICE,
332d831c5fdSJamin Lin .instance_init = aspeed_intc_instance_init,
333d831c5fdSJamin Lin .instance_size = sizeof(AspeedINTCState),
334d831c5fdSJamin Lin .class_init = aspeed_intc_class_init,
335d831c5fdSJamin Lin .class_size = sizeof(AspeedINTCClass),
336d831c5fdSJamin Lin .abstract = true,
337d831c5fdSJamin Lin };
338d831c5fdSJamin Lin
aspeed_2700_intc_class_init(ObjectClass * klass,void * data)339d831c5fdSJamin Lin static void aspeed_2700_intc_class_init(ObjectClass *klass, void *data)
340d831c5fdSJamin Lin {
341d831c5fdSJamin Lin DeviceClass *dc = DEVICE_CLASS(klass);
342d831c5fdSJamin Lin AspeedINTCClass *aic = ASPEED_INTC_CLASS(klass);
343d831c5fdSJamin Lin
344d831c5fdSJamin Lin dc->desc = "ASPEED 2700 INTC Controller";
345d831c5fdSJamin Lin aic->num_lines = 32;
346d831c5fdSJamin Lin aic->num_ints = 9;
347d831c5fdSJamin Lin }
348d831c5fdSJamin Lin
349d831c5fdSJamin Lin static const TypeInfo aspeed_2700_intc_info = {
350d831c5fdSJamin Lin .name = TYPE_ASPEED_2700_INTC,
351d831c5fdSJamin Lin .parent = TYPE_ASPEED_INTC,
352d831c5fdSJamin Lin .class_init = aspeed_2700_intc_class_init,
353d831c5fdSJamin Lin };
354d831c5fdSJamin Lin
aspeed_intc_register_types(void)355d831c5fdSJamin Lin static void aspeed_intc_register_types(void)
356d831c5fdSJamin Lin {
357d831c5fdSJamin Lin type_register_static(&aspeed_intc_info);
358d831c5fdSJamin Lin type_register_static(&aspeed_2700_intc_info);
359d831c5fdSJamin Lin }
360d831c5fdSJamin Lin
361d831c5fdSJamin Lin type_init(aspeed_intc_register_types);
362