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