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