xref: /openbmc/qemu/hw/intc/exynos4210_combiner.c (revision d4842052100a3b44167e34ebdce0e7b3bf7512cf)
1 /*
2  * Samsung exynos4210 Interrupt Combiner
3  *
4  * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
5  * All rights reserved.
6  *
7  * Evgeny Voevodin <e.voevodin@samsung.com>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17  * See the GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /*
24  * Exynos4210 Combiner represents an OR gate for SOC's IRQ lines. It combines
25  * IRQ sources into groups and provides signal output to GIC from each group. It
26  * is driven by common mask and enable/disable logic. Take a note that not all
27  * IRQs are passed to GIC through Combiner.
28  */
29 
30 #include "qemu/osdep.h"
31 #include "hw/sysbus.h"
32 #include "migration/vmstate.h"
33 #include "qemu/module.h"
34 
35 #include "hw/arm/exynos4210.h"
36 #include "hw/irq.h"
37 
38 //#define DEBUG_COMBINER
39 
40 #ifdef DEBUG_COMBINER
41 #define DPRINTF(fmt, ...) \
42         do { fprintf(stdout, "COMBINER: [%s:%d] " fmt, __func__ , __LINE__, \
43                 ## __VA_ARGS__); } while (0)
44 #else
45 #define DPRINTF(fmt, ...) do {} while (0)
46 #endif
47 
48 #define    IIC_NGRP        64            /* Internal Interrupt Combiner
49                                             Groups number */
50 #define    IIC_NIRQ        (IIC_NGRP * 8)/* Internal Interrupt Combiner
51                                             Interrupts number */
52 #define IIC_REGION_SIZE    0x108         /* Size of memory mapped region */
53 #define IIC_REGSET_SIZE    0x41
54 
55 /*
56  * State for each output signal of internal combiner
57  */
58 typedef struct CombinerGroupState {
59     uint8_t src_mask;            /* 1 - source enabled, 0 - disabled */
60     uint8_t src_pending;        /* Pending source interrupts before masking */
61 } CombinerGroupState;
62 
63 #define TYPE_EXYNOS4210_COMBINER "exynos4210.combiner"
64 #define EXYNOS4210_COMBINER(obj) \
65     OBJECT_CHECK(Exynos4210CombinerState, (obj), TYPE_EXYNOS4210_COMBINER)
66 
67 typedef struct Exynos4210CombinerState {
68     SysBusDevice parent_obj;
69 
70     MemoryRegion iomem;
71 
72     struct CombinerGroupState group[IIC_NGRP];
73     uint32_t reg_set[IIC_REGSET_SIZE];
74     uint32_t icipsr[2];
75     uint32_t external;          /* 1 means that this combiner is external */
76 
77     qemu_irq output_irq[IIC_NGRP];
78 } Exynos4210CombinerState;
79 
80 static const VMStateDescription vmstate_exynos4210_combiner_group_state = {
81     .name = "exynos4210.combiner.groupstate",
82     .version_id = 1,
83     .minimum_version_id = 1,
84     .fields = (VMStateField[]) {
85         VMSTATE_UINT8(src_mask, CombinerGroupState),
86         VMSTATE_UINT8(src_pending, CombinerGroupState),
87         VMSTATE_END_OF_LIST()
88     }
89 };
90 
91 static const VMStateDescription vmstate_exynos4210_combiner = {
92     .name = "exynos4210.combiner",
93     .version_id = 1,
94     .minimum_version_id = 1,
95     .fields = (VMStateField[]) {
96         VMSTATE_STRUCT_ARRAY(group, Exynos4210CombinerState, IIC_NGRP, 0,
97                 vmstate_exynos4210_combiner_group_state, CombinerGroupState),
98         VMSTATE_UINT32_ARRAY(reg_set, Exynos4210CombinerState,
99                 IIC_REGSET_SIZE),
100         VMSTATE_UINT32_ARRAY(icipsr, Exynos4210CombinerState, 2),
101         VMSTATE_UINT32(external, Exynos4210CombinerState),
102         VMSTATE_END_OF_LIST()
103     }
104 };
105 
106 /*
107  * Get Combiner input GPIO into irqs structure
108  */
109 void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev,
110         int ext)
111 {
112     int n;
113     int bit;
114     int max;
115     qemu_irq *irq;
116 
117     max = ext ? EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ :
118         EXYNOS4210_MAX_INT_COMBINER_IN_IRQ;
119     irq = ext ? irqs->ext_combiner_irq : irqs->int_combiner_irq;
120 
121     /*
122      * Some IRQs of Int/External Combiner are going to two Combiners groups,
123      * so let split them.
124      */
125     for (n = 0; n < max; n++) {
126 
127         bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
128 
129         switch (n) {
130         /* MDNIE_LCD1 INTG1 */
131         case EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 0) ...
132              EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 3):
133             irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
134                     irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(0, bit + 4)]);
135             continue;
136 
137         /* TMU INTG3 */
138         case EXYNOS4210_COMBINER_GET_IRQ_NUM(3, 4):
139             irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
140                     irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(2, bit)]);
141             continue;
142 
143         /* LCD1 INTG12 */
144         case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 0) ...
145              EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 3):
146             irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
147                     irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(11, bit + 4)]);
148             continue;
149 
150         /* Multi-Core Timer INTG12 */
151         case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4) ...
152              EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 8):
153                irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
154                        irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
155             continue;
156 
157         /* Multi-Core Timer INTG35 */
158         case EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 4) ...
159              EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 8):
160             irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
161                     irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
162             continue;
163 
164         /* Multi-Core Timer INTG51 */
165         case EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 4) ...
166              EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 8):
167             irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
168                     irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
169             continue;
170 
171         /* Multi-Core Timer INTG53 */
172         case EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 4) ...
173              EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 8):
174             irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
175                     irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
176             continue;
177         }
178 
179         irq[n] = qdev_get_gpio_in(dev, n);
180     }
181 }
182 
183 static uint64_t
184 exynos4210_combiner_read(void *opaque, hwaddr offset, unsigned size)
185 {
186     struct Exynos4210CombinerState *s =
187             (struct Exynos4210CombinerState *)opaque;
188     uint32_t req_quad_base_n;    /* Base of registers quad. Multiply it by 4 and
189                                    get a start of corresponding group quad */
190     uint32_t grp_quad_base_n;    /* Base of group quad */
191     uint32_t reg_n;              /* Register number inside the quad */
192     uint32_t val;
193 
194     req_quad_base_n = offset >> 4;
195     grp_quad_base_n = req_quad_base_n << 2;
196     reg_n = (offset - (req_quad_base_n << 4)) >> 2;
197 
198     if (req_quad_base_n >= IIC_NGRP) {
199         /* Read of ICIPSR register */
200         return s->icipsr[reg_n];
201     }
202 
203     val = 0;
204 
205     switch (reg_n) {
206     /* IISTR */
207     case 2:
208         val |= s->group[grp_quad_base_n].src_pending;
209         val |= s->group[grp_quad_base_n + 1].src_pending << 8;
210         val |= s->group[grp_quad_base_n + 2].src_pending << 16;
211         val |= s->group[grp_quad_base_n + 3].src_pending << 24;
212         break;
213     /* IIMSR */
214     case 3:
215         val |= s->group[grp_quad_base_n].src_mask &
216         s->group[grp_quad_base_n].src_pending;
217         val |= (s->group[grp_quad_base_n + 1].src_mask &
218                 s->group[grp_quad_base_n + 1].src_pending) << 8;
219         val |= (s->group[grp_quad_base_n + 2].src_mask &
220                 s->group[grp_quad_base_n + 2].src_pending) << 16;
221         val |= (s->group[grp_quad_base_n + 3].src_mask &
222                 s->group[grp_quad_base_n + 3].src_pending) << 24;
223         break;
224     default:
225         if (offset >> 2 >= IIC_REGSET_SIZE) {
226             hw_error("exynos4210.combiner: overflow of reg_set by 0x"
227                     TARGET_FMT_plx "offset\n", offset);
228         }
229         val = s->reg_set[offset >> 2];
230         return 0;
231     }
232     return val;
233 }
234 
235 static void exynos4210_combiner_update(void *opaque, uint8_t group_n)
236 {
237     struct Exynos4210CombinerState *s =
238             (struct Exynos4210CombinerState *)opaque;
239 
240     /* Send interrupt if needed */
241     if (s->group[group_n].src_mask & s->group[group_n].src_pending) {
242 #ifdef DEBUG_COMBINER
243         if (group_n != 26) {
244             /* skip uart */
245             DPRINTF("%s raise IRQ[%d]\n", s->external ? "EXT" : "INT", group_n);
246         }
247 #endif
248 
249         /* Set Combiner interrupt pending status after masking */
250         if (group_n >= 32) {
251             s->icipsr[1] |= 1 << (group_n - 32);
252         } else {
253             s->icipsr[0] |= 1 << group_n;
254         }
255 
256         qemu_irq_raise(s->output_irq[group_n]);
257     } else {
258 #ifdef DEBUG_COMBINER
259         if (group_n != 26) {
260             /* skip uart */
261             DPRINTF("%s lower IRQ[%d]\n", s->external ? "EXT" : "INT", group_n);
262         }
263 #endif
264 
265         /* Set Combiner interrupt pending status after masking */
266         if (group_n >= 32) {
267             s->icipsr[1] &= ~(1 << (group_n - 32));
268         } else {
269             s->icipsr[0] &= ~(1 << group_n);
270         }
271 
272         qemu_irq_lower(s->output_irq[group_n]);
273     }
274 }
275 
276 static void exynos4210_combiner_write(void *opaque, hwaddr offset,
277         uint64_t val, unsigned size)
278 {
279     struct Exynos4210CombinerState *s =
280             (struct Exynos4210CombinerState *)opaque;
281     uint32_t req_quad_base_n;    /* Base of registers quad. Multiply it by 4 and
282                                    get a start of corresponding group quad */
283     uint32_t grp_quad_base_n;    /* Base of group quad */
284     uint32_t reg_n;              /* Register number inside the quad */
285 
286     req_quad_base_n = offset >> 4;
287     grp_quad_base_n = req_quad_base_n << 2;
288     reg_n = (offset - (req_quad_base_n << 4)) >> 2;
289 
290     if (req_quad_base_n >= IIC_NGRP) {
291         hw_error("exynos4210.combiner: unallowed write access at offset 0x"
292                 TARGET_FMT_plx "\n", offset);
293         return;
294     }
295 
296     if (reg_n > 1) {
297         hw_error("exynos4210.combiner: unallowed write access at offset 0x"
298                 TARGET_FMT_plx "\n", offset);
299         return;
300     }
301 
302     if (offset >> 2 >= IIC_REGSET_SIZE) {
303         hw_error("exynos4210.combiner: overflow of reg_set by 0x"
304                 TARGET_FMT_plx "offset\n", offset);
305     }
306     s->reg_set[offset >> 2] = val;
307 
308     switch (reg_n) {
309     /* IIESR */
310     case 0:
311         /* FIXME: what if irq is pending, allowed by mask, and we allow it
312          * again. Interrupt will rise again! */
313 
314         DPRINTF("%s enable IRQ for groups %d, %d, %d, %d\n",
315                 s->external ? "EXT" : "INT",
316                 grp_quad_base_n,
317                 grp_quad_base_n + 1,
318                 grp_quad_base_n + 2,
319                 grp_quad_base_n + 3);
320 
321         /* Enable interrupt sources */
322         s->group[grp_quad_base_n].src_mask |= val & 0xFF;
323         s->group[grp_quad_base_n + 1].src_mask |= (val & 0xFF00) >> 8;
324         s->group[grp_quad_base_n + 2].src_mask |= (val & 0xFF0000) >> 16;
325         s->group[grp_quad_base_n + 3].src_mask |= (val & 0xFF000000) >> 24;
326 
327         exynos4210_combiner_update(s, grp_quad_base_n);
328         exynos4210_combiner_update(s, grp_quad_base_n + 1);
329         exynos4210_combiner_update(s, grp_quad_base_n + 2);
330         exynos4210_combiner_update(s, grp_quad_base_n + 3);
331         break;
332         /* IIECR */
333     case 1:
334         DPRINTF("%s disable IRQ for groups %d, %d, %d, %d\n",
335                 s->external ? "EXT" : "INT",
336                 grp_quad_base_n,
337                 grp_quad_base_n + 1,
338                 grp_quad_base_n + 2,
339                 grp_quad_base_n + 3);
340 
341         /* Disable interrupt sources */
342         s->group[grp_quad_base_n].src_mask &= ~(val & 0xFF);
343         s->group[grp_quad_base_n + 1].src_mask &= ~((val & 0xFF00) >> 8);
344         s->group[grp_quad_base_n + 2].src_mask &= ~((val & 0xFF0000) >> 16);
345         s->group[grp_quad_base_n + 3].src_mask &= ~((val & 0xFF000000) >> 24);
346 
347         exynos4210_combiner_update(s, grp_quad_base_n);
348         exynos4210_combiner_update(s, grp_quad_base_n + 1);
349         exynos4210_combiner_update(s, grp_quad_base_n + 2);
350         exynos4210_combiner_update(s, grp_quad_base_n + 3);
351         break;
352     default:
353         hw_error("exynos4210.combiner: unallowed write access at offset 0x"
354                 TARGET_FMT_plx "\n", offset);
355         break;
356     }
357 }
358 
359 /* Get combiner group and bit from irq number */
360 static uint8_t get_combiner_group_and_bit(int irq, uint8_t *bit)
361 {
362     *bit = irq - ((irq >> 3) << 3);
363     return irq >> 3;
364 }
365 
366 /* Process a change in an external IRQ input.  */
367 static void exynos4210_combiner_handler(void *opaque, int irq, int level)
368 {
369     struct Exynos4210CombinerState *s =
370             (struct Exynos4210CombinerState *)opaque;
371     uint8_t bit_n, group_n;
372 
373     group_n = get_combiner_group_and_bit(irq, &bit_n);
374 
375     if (s->external && group_n >= EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ) {
376         DPRINTF("%s unallowed IRQ group 0x%x\n", s->external ? "EXT" : "INT"
377                 , group_n);
378         return;
379     }
380 
381     if (level) {
382         s->group[group_n].src_pending |= 1 << bit_n;
383     } else {
384         s->group[group_n].src_pending &= ~(1 << bit_n);
385     }
386 
387     exynos4210_combiner_update(s, group_n);
388 }
389 
390 static void exynos4210_combiner_reset(DeviceState *d)
391 {
392     struct Exynos4210CombinerState *s = (struct Exynos4210CombinerState *)d;
393 
394     memset(&s->group, 0, sizeof(s->group));
395     memset(&s->reg_set, 0, sizeof(s->reg_set));
396 
397     s->reg_set[0xC0 >> 2] = 0x01010101;
398     s->reg_set[0xC4 >> 2] = 0x01010101;
399     s->reg_set[0xD0 >> 2] = 0x01010101;
400     s->reg_set[0xD4 >> 2] = 0x01010101;
401 }
402 
403 static const MemoryRegionOps exynos4210_combiner_ops = {
404     .read = exynos4210_combiner_read,
405     .write = exynos4210_combiner_write,
406     .endianness = DEVICE_NATIVE_ENDIAN,
407 };
408 
409 /*
410  * Internal Combiner initialization.
411  */
412 static void exynos4210_combiner_init(Object *obj)
413 {
414     DeviceState *dev = DEVICE(obj);
415     Exynos4210CombinerState *s = EXYNOS4210_COMBINER(obj);
416     SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
417     unsigned int i;
418 
419     /* Allocate general purpose input signals and connect a handler to each of
420      * them */
421     qdev_init_gpio_in(dev, exynos4210_combiner_handler, IIC_NIRQ);
422 
423     /* Connect SysBusDev irqs to device specific irqs */
424     for (i = 0; i < IIC_NGRP; i++) {
425         sysbus_init_irq(sbd, &s->output_irq[i]);
426     }
427 
428     memory_region_init_io(&s->iomem, obj, &exynos4210_combiner_ops, s,
429                           "exynos4210-combiner", IIC_REGION_SIZE);
430     sysbus_init_mmio(sbd, &s->iomem);
431 }
432 
433 static Property exynos4210_combiner_properties[] = {
434     DEFINE_PROP_UINT32("external", Exynos4210CombinerState, external, 0),
435     DEFINE_PROP_END_OF_LIST(),
436 };
437 
438 static void exynos4210_combiner_class_init(ObjectClass *klass, void *data)
439 {
440     DeviceClass *dc = DEVICE_CLASS(klass);
441 
442     dc->reset = exynos4210_combiner_reset;
443     dc->props = exynos4210_combiner_properties;
444     dc->vmsd = &vmstate_exynos4210_combiner;
445 }
446 
447 static const TypeInfo exynos4210_combiner_info = {
448     .name          = TYPE_EXYNOS4210_COMBINER,
449     .parent        = TYPE_SYS_BUS_DEVICE,
450     .instance_size = sizeof(Exynos4210CombinerState),
451     .instance_init = exynos4210_combiner_init,
452     .class_init    = exynos4210_combiner_class_init,
453 };
454 
455 static void exynos4210_combiner_register_types(void)
456 {
457     type_register_static(&exynos4210_combiner_info);
458 }
459 
460 type_init(exynos4210_combiner_register_types)
461