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