xref: /openbmc/qemu/hw/intc/loongarch_pch_pic.c (revision 45b1f81d)
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 /*
3  * QEMU Loongson 7A1000 I/O interrupt controller.
4  *
5  * Copyright (C) 2021 Loongson Technology Corporation Limited
6  */
7 
8 #include "qemu/osdep.h"
9 #include "qemu/bitops.h"
10 #include "hw/sysbus.h"
11 #include "hw/loongarch/virt.h"
12 #include "hw/pci-host/ls7a.h"
13 #include "hw/irq.h"
14 #include "hw/intc/loongarch_pch_pic.h"
15 #include "hw/qdev-properties.h"
16 #include "migration/vmstate.h"
17 #include "trace.h"
18 #include "qapi/error.h"
19 
pch_pic_update_irq(LoongArchPCHPIC * s,uint64_t mask,int level)20 static void pch_pic_update_irq(LoongArchPCHPIC *s, uint64_t mask, int level)
21 {
22     uint64_t val;
23     int irq;
24 
25     if (level) {
26         val = mask & s->intirr & ~s->int_mask;
27         if (val) {
28             irq = ctz64(val);
29             s->intisr |= MAKE_64BIT_MASK(irq, 1);
30             qemu_set_irq(s->parent_irq[s->htmsi_vector[irq]], 1);
31         }
32     } else {
33         /*
34          * intirr means requested pending irq
35          * do not clear pending irq for edge-triggered on lowering edge
36          */
37         val = mask & s->intisr & ~s->intirr;
38         if (val) {
39             irq = ctz64(val);
40             s->intisr &= ~MAKE_64BIT_MASK(irq, 1);
41             qemu_set_irq(s->parent_irq[s->htmsi_vector[irq]], 0);
42         }
43     }
44 }
45 
pch_pic_irq_handler(void * opaque,int irq,int level)46 static void pch_pic_irq_handler(void *opaque, int irq, int level)
47 {
48     LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
49     uint64_t mask = 1ULL << irq;
50 
51     assert(irq < s->irq_num);
52     trace_loongarch_pch_pic_irq_handler(irq, level);
53 
54     if (s->intedge & mask) {
55         /* Edge triggered */
56         if (level) {
57             if ((s->last_intirr & mask) == 0) {
58                 /* marked pending on a rising edge */
59                 s->intirr |= mask;
60             }
61             s->last_intirr |= mask;
62         } else {
63             s->last_intirr &= ~mask;
64         }
65     } else {
66         /* Level triggered */
67         if (level) {
68             s->intirr |= mask;
69             s->last_intirr |= mask;
70         } else {
71             s->intirr &= ~mask;
72             s->last_intirr &= ~mask;
73         }
74     }
75     pch_pic_update_irq(s, mask, level);
76 }
77 
loongarch_pch_pic_low_readw(void * opaque,hwaddr addr,unsigned size)78 static uint64_t loongarch_pch_pic_low_readw(void *opaque, hwaddr addr,
79                                             unsigned size)
80 {
81     LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
82     uint64_t val = 0;
83     uint32_t offset = addr & 0xfff;
84 
85     switch (offset) {
86     case PCH_PIC_INT_ID_LO:
87         val = PCH_PIC_INT_ID_VAL;
88         break;
89     case PCH_PIC_INT_ID_HI:
90         /*
91          * With 7A1000 manual
92          *   bit  0-15 pch irqchip version
93          *   bit 16-31 irq number supported with pch irqchip
94          */
95         val = deposit32(PCH_PIC_INT_ID_VER, 16, 16, s->irq_num - 1);
96         break;
97     case PCH_PIC_INT_MASK_LO:
98         val = (uint32_t)s->int_mask;
99         break;
100     case PCH_PIC_INT_MASK_HI:
101         val = s->int_mask >> 32;
102         break;
103     case PCH_PIC_INT_EDGE_LO:
104         val = (uint32_t)s->intedge;
105         break;
106     case PCH_PIC_INT_EDGE_HI:
107         val = s->intedge >> 32;
108         break;
109     case PCH_PIC_HTMSI_EN_LO:
110         val = (uint32_t)s->htmsi_en;
111         break;
112     case PCH_PIC_HTMSI_EN_HI:
113         val = s->htmsi_en >> 32;
114         break;
115     case PCH_PIC_AUTO_CTRL0_LO:
116     case PCH_PIC_AUTO_CTRL0_HI:
117     case PCH_PIC_AUTO_CTRL1_LO:
118     case PCH_PIC_AUTO_CTRL1_HI:
119         break;
120     default:
121         break;
122     }
123 
124     trace_loongarch_pch_pic_low_readw(size, addr, val);
125     return val;
126 }
127 
get_writew_val(uint64_t value,uint32_t target,bool hi)128 static uint64_t get_writew_val(uint64_t value, uint32_t target, bool hi)
129 {
130     uint64_t mask = 0xffffffff00000000;
131     uint64_t data = target;
132 
133     return hi ? (value & ~mask) | (data << 32) : (value & mask) | data;
134 }
135 
loongarch_pch_pic_low_writew(void * opaque,hwaddr addr,uint64_t value,unsigned size)136 static void loongarch_pch_pic_low_writew(void *opaque, hwaddr addr,
137                                          uint64_t value, unsigned size)
138 {
139     LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
140     uint32_t offset, old_valid, data = (uint32_t)value;
141     uint64_t old, int_mask;
142     offset = addr & 0xfff;
143 
144     trace_loongarch_pch_pic_low_writew(size, addr, data);
145 
146     switch (offset) {
147     case PCH_PIC_INT_MASK_LO:
148         old = s->int_mask;
149         s->int_mask = get_writew_val(old, data, 0);
150         old_valid = (uint32_t)old;
151         if (old_valid & ~data) {
152             pch_pic_update_irq(s, (old_valid & ~data), 1);
153         }
154         if (~old_valid & data) {
155             pch_pic_update_irq(s, (~old_valid & data), 0);
156         }
157         break;
158     case PCH_PIC_INT_MASK_HI:
159         old = s->int_mask;
160         s->int_mask = get_writew_val(old, data, 1);
161         old_valid = (uint32_t)(old >> 32);
162         int_mask = old_valid & ~data;
163         if (int_mask) {
164             pch_pic_update_irq(s, int_mask << 32, 1);
165         }
166         int_mask = ~old_valid & data;
167         if (int_mask) {
168             pch_pic_update_irq(s, int_mask << 32, 0);
169         }
170         break;
171     case PCH_PIC_INT_EDGE_LO:
172         s->intedge = get_writew_val(s->intedge, data, 0);
173         break;
174     case PCH_PIC_INT_EDGE_HI:
175         s->intedge = get_writew_val(s->intedge, data, 1);
176         break;
177     case PCH_PIC_INT_CLEAR_LO:
178         if (s->intedge & data) {
179             s->intirr &= (~data);
180             pch_pic_update_irq(s, data, 0);
181             s->intisr &= (~data);
182         }
183         break;
184     case PCH_PIC_INT_CLEAR_HI:
185         value <<= 32;
186         if (s->intedge & value) {
187             s->intirr &= (~value);
188             pch_pic_update_irq(s, value, 0);
189             s->intisr &= (~value);
190         }
191         break;
192     case PCH_PIC_HTMSI_EN_LO:
193         s->htmsi_en = get_writew_val(s->htmsi_en, data, 0);
194         break;
195     case PCH_PIC_HTMSI_EN_HI:
196         s->htmsi_en = get_writew_val(s->htmsi_en, data, 1);
197         break;
198     case PCH_PIC_AUTO_CTRL0_LO:
199     case PCH_PIC_AUTO_CTRL0_HI:
200     case PCH_PIC_AUTO_CTRL1_LO:
201     case PCH_PIC_AUTO_CTRL1_HI:
202         break;
203     default:
204         break;
205     }
206 }
207 
loongarch_pch_pic_high_readw(void * opaque,hwaddr addr,unsigned size)208 static uint64_t loongarch_pch_pic_high_readw(void *opaque, hwaddr addr,
209                                         unsigned size)
210 {
211     LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
212     uint64_t val = 0;
213     uint32_t offset = addr & 0xfff;
214 
215     switch (offset) {
216     case STATUS_LO_START:
217         val = (uint32_t)(s->intisr & (~s->int_mask));
218         break;
219     case STATUS_HI_START:
220         val = (s->intisr & (~s->int_mask)) >> 32;
221         break;
222     case POL_LO_START:
223         val = (uint32_t)s->int_polarity;
224         break;
225     case POL_HI_START:
226         val = s->int_polarity >> 32;
227         break;
228     default:
229         break;
230     }
231 
232     trace_loongarch_pch_pic_high_readw(size, addr, val);
233     return val;
234 }
235 
loongarch_pch_pic_high_writew(void * opaque,hwaddr addr,uint64_t value,unsigned size)236 static void loongarch_pch_pic_high_writew(void *opaque, hwaddr addr,
237                                      uint64_t value, unsigned size)
238 {
239     LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
240     uint32_t offset, data = (uint32_t)value;
241     offset = addr & 0xfff;
242 
243     trace_loongarch_pch_pic_high_writew(size, addr, data);
244 
245     switch (offset) {
246     case STATUS_LO_START:
247         s->intisr = get_writew_val(s->intisr, data, 0);
248         break;
249     case STATUS_HI_START:
250         s->intisr = get_writew_val(s->intisr, data, 1);
251         break;
252     case POL_LO_START:
253         s->int_polarity = get_writew_val(s->int_polarity, data, 0);
254         break;
255     case POL_HI_START:
256         s->int_polarity = get_writew_val(s->int_polarity, data, 1);
257         break;
258     default:
259         break;
260     }
261 }
262 
loongarch_pch_pic_readb(void * opaque,hwaddr addr,unsigned size)263 static uint64_t loongarch_pch_pic_readb(void *opaque, hwaddr addr,
264                                         unsigned size)
265 {
266     LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
267     uint64_t val = 0;
268     uint32_t offset = (addr & 0xfff) + PCH_PIC_ROUTE_ENTRY_OFFSET;
269     int64_t offset_tmp;
270 
271     switch (offset) {
272     case PCH_PIC_HTMSI_VEC_OFFSET ... PCH_PIC_HTMSI_VEC_END:
273         offset_tmp = offset - PCH_PIC_HTMSI_VEC_OFFSET;
274         if (offset_tmp >= 0 && offset_tmp < 64) {
275             val = s->htmsi_vector[offset_tmp];
276         }
277         break;
278     case PCH_PIC_ROUTE_ENTRY_OFFSET ... PCH_PIC_ROUTE_ENTRY_END:
279         offset_tmp = offset - PCH_PIC_ROUTE_ENTRY_OFFSET;
280         if (offset_tmp >= 0 && offset_tmp < 64) {
281             val = s->route_entry[offset_tmp];
282         }
283         break;
284     default:
285         break;
286     }
287 
288     trace_loongarch_pch_pic_readb(size, addr, val);
289     return val;
290 }
291 
loongarch_pch_pic_writeb(void * opaque,hwaddr addr,uint64_t data,unsigned size)292 static void loongarch_pch_pic_writeb(void *opaque, hwaddr addr,
293                                      uint64_t data, unsigned size)
294 {
295     LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
296     int32_t offset_tmp;
297     uint32_t offset = (addr & 0xfff) + PCH_PIC_ROUTE_ENTRY_OFFSET;
298 
299     trace_loongarch_pch_pic_writeb(size, addr, data);
300 
301     switch (offset) {
302     case PCH_PIC_HTMSI_VEC_OFFSET ... PCH_PIC_HTMSI_VEC_END:
303         offset_tmp = offset - PCH_PIC_HTMSI_VEC_OFFSET;
304         if (offset_tmp >= 0 && offset_tmp < 64) {
305             s->htmsi_vector[offset_tmp] = (uint8_t)(data & 0xff);
306         }
307         break;
308     case PCH_PIC_ROUTE_ENTRY_OFFSET ... PCH_PIC_ROUTE_ENTRY_END:
309         offset_tmp = offset - PCH_PIC_ROUTE_ENTRY_OFFSET;
310         if (offset_tmp >= 0 && offset_tmp < 64) {
311             s->route_entry[offset_tmp] = (uint8_t)(data & 0xff);
312         }
313         break;
314     default:
315         break;
316     }
317 }
318 
319 static const MemoryRegionOps loongarch_pch_pic_reg32_low_ops = {
320     .read = loongarch_pch_pic_low_readw,
321     .write = loongarch_pch_pic_low_writew,
322     .valid = {
323         .min_access_size = 4,
324         .max_access_size = 8,
325     },
326     .impl = {
327         .min_access_size = 4,
328         .max_access_size = 4,
329     },
330     .endianness = DEVICE_LITTLE_ENDIAN,
331 };
332 
333 static const MemoryRegionOps loongarch_pch_pic_reg32_high_ops = {
334     .read = loongarch_pch_pic_high_readw,
335     .write = loongarch_pch_pic_high_writew,
336     .valid = {
337         .min_access_size = 4,
338         .max_access_size = 8,
339     },
340     .impl = {
341         .min_access_size = 4,
342         .max_access_size = 4,
343     },
344     .endianness = DEVICE_LITTLE_ENDIAN,
345 };
346 
347 static const MemoryRegionOps loongarch_pch_pic_reg8_ops = {
348     .read = loongarch_pch_pic_readb,
349     .write = loongarch_pch_pic_writeb,
350     .valid = {
351         .min_access_size = 1,
352         .max_access_size = 1,
353     },
354     .impl = {
355         .min_access_size = 1,
356         .max_access_size = 1,
357     },
358     .endianness = DEVICE_LITTLE_ENDIAN,
359 };
360 
loongarch_pch_pic_reset(DeviceState * d)361 static void loongarch_pch_pic_reset(DeviceState *d)
362 {
363     LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(d);
364     int i;
365 
366     s->int_mask = -1;
367     s->htmsi_en = 0x0;
368     s->intedge  = 0x0;
369     s->intclr   = 0x0;
370     s->auto_crtl0 = 0x0;
371     s->auto_crtl1 = 0x0;
372     for (i = 0; i < 64; i++) {
373         s->route_entry[i] = 0x1;
374         s->htmsi_vector[i] = 0x0;
375     }
376     s->intirr = 0x0;
377     s->intisr = 0x0;
378     s->last_intirr = 0x0;
379     s->int_polarity = 0x0;
380 }
381 
loongarch_pch_pic_realize(DeviceState * dev,Error ** errp)382 static void loongarch_pch_pic_realize(DeviceState *dev, Error **errp)
383 {
384     LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(dev);
385 
386     if (!s->irq_num || s->irq_num  > VIRT_PCH_PIC_IRQ_NUM) {
387         error_setg(errp, "Invalid 'pic_irq_num'");
388         return;
389     }
390 
391     qdev_init_gpio_out(dev, s->parent_irq, s->irq_num);
392     qdev_init_gpio_in(dev, pch_pic_irq_handler, s->irq_num);
393 }
394 
loongarch_pch_pic_init(Object * obj)395 static void loongarch_pch_pic_init(Object *obj)
396 {
397     LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(obj);
398     SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
399 
400     memory_region_init_io(&s->iomem32_low, obj,
401                           &loongarch_pch_pic_reg32_low_ops,
402                           s, PCH_PIC_NAME(.reg32_part1), 0x100);
403     memory_region_init_io(&s->iomem8, obj, &loongarch_pch_pic_reg8_ops,
404                           s, PCH_PIC_NAME(.reg8), 0x2a0);
405     memory_region_init_io(&s->iomem32_high, obj,
406                           &loongarch_pch_pic_reg32_high_ops,
407                           s, PCH_PIC_NAME(.reg32_part2), 0xc60);
408     sysbus_init_mmio(sbd, &s->iomem32_low);
409     sysbus_init_mmio(sbd, &s->iomem8);
410     sysbus_init_mmio(sbd, &s->iomem32_high);
411 
412 }
413 
414 static Property loongarch_pch_pic_properties[] = {
415     DEFINE_PROP_UINT32("pch_pic_irq_num",  LoongArchPCHPIC, irq_num, 0),
416     DEFINE_PROP_END_OF_LIST(),
417 };
418 
419 static const VMStateDescription vmstate_loongarch_pch_pic = {
420     .name = TYPE_LOONGARCH_PCH_PIC,
421     .version_id = 1,
422     .minimum_version_id = 1,
423     .fields = (const VMStateField[]) {
424         VMSTATE_UINT64(int_mask, LoongArchPCHPIC),
425         VMSTATE_UINT64(htmsi_en, LoongArchPCHPIC),
426         VMSTATE_UINT64(intedge, LoongArchPCHPIC),
427         VMSTATE_UINT64(intclr, LoongArchPCHPIC),
428         VMSTATE_UINT64(auto_crtl0, LoongArchPCHPIC),
429         VMSTATE_UINT64(auto_crtl1, LoongArchPCHPIC),
430         VMSTATE_UINT8_ARRAY(route_entry, LoongArchPCHPIC, 64),
431         VMSTATE_UINT8_ARRAY(htmsi_vector, LoongArchPCHPIC, 64),
432         VMSTATE_UINT64(last_intirr, LoongArchPCHPIC),
433         VMSTATE_UINT64(intirr, LoongArchPCHPIC),
434         VMSTATE_UINT64(intisr, LoongArchPCHPIC),
435         VMSTATE_UINT64(int_polarity, LoongArchPCHPIC),
436         VMSTATE_END_OF_LIST()
437     }
438 };
439 
loongarch_pch_pic_class_init(ObjectClass * klass,void * data)440 static void loongarch_pch_pic_class_init(ObjectClass *klass, void *data)
441 {
442     DeviceClass *dc = DEVICE_CLASS(klass);
443 
444     dc->realize = loongarch_pch_pic_realize;
445     dc->reset = loongarch_pch_pic_reset;
446     dc->vmsd = &vmstate_loongarch_pch_pic;
447     device_class_set_props(dc, loongarch_pch_pic_properties);
448 }
449 
450 static const TypeInfo loongarch_pch_pic_info = {
451     .name          = TYPE_LOONGARCH_PCH_PIC,
452     .parent        = TYPE_SYS_BUS_DEVICE,
453     .instance_size = sizeof(LoongArchPCHPIC),
454     .instance_init = loongarch_pch_pic_init,
455     .class_init    = loongarch_pch_pic_class_init,
456 };
457 
loongarch_pch_pic_register_types(void)458 static void loongarch_pch_pic_register_types(void)
459 {
460     type_register_static(&loongarch_pch_pic_info);
461 }
462 
463 type_init(loongarch_pch_pic_register_types)
464