These changes are the raw update to qemu-2.6.
[kvmfornfv.git] / qemu / hw / intc / sh_intc.c
1 /*
2  * SuperH interrupt controller module
3  *
4  * Copyright (c) 2007 Magnus Damm
5  * Based on sh_timer.c and arm_timer.c by Paul Brook
6  * Copyright (c) 2005-2006 CodeSourcery.
7  *
8  * This code is licensed under the GPL.
9  */
10
11 #include "qemu/osdep.h"
12 #include "qemu-common.h"
13 #include "cpu.h"
14 #include "hw/sh4/sh_intc.h"
15 #include "hw/hw.h"
16 #include "hw/sh4/sh.h"
17
18 //#define DEBUG_INTC
19 //#define DEBUG_INTC_SOURCES
20
21 #define INTC_A7(x) ((x) & 0x1fffffff)
22
23 void sh_intc_toggle_source(struct intc_source *source,
24                            int enable_adj, int assert_adj)
25 {
26     int enable_changed = 0;
27     int pending_changed = 0;
28     int old_pending;
29
30     if ((source->enable_count == source->enable_max) && (enable_adj == -1))
31         enable_changed = -1;
32
33     source->enable_count += enable_adj;
34
35     if (source->enable_count == source->enable_max)
36         enable_changed = 1;
37
38     source->asserted += assert_adj;
39
40     old_pending = source->pending;
41     source->pending = source->asserted &&
42       (source->enable_count == source->enable_max);
43
44     if (old_pending != source->pending)
45         pending_changed = 1;
46
47     if (pending_changed) {
48         if (source->pending) {
49             source->parent->pending++;
50             if (source->parent->pending == 1) {
51                 cpu_interrupt(first_cpu, CPU_INTERRUPT_HARD);
52             }
53         } else {
54             source->parent->pending--;
55             if (source->parent->pending == 0) {
56                 cpu_reset_interrupt(first_cpu, CPU_INTERRUPT_HARD);
57             }
58         }
59     }
60
61   if (enable_changed || assert_adj || pending_changed) {
62 #ifdef DEBUG_INTC_SOURCES
63             printf("sh_intc: (%d/%d/%d/%d) interrupt source 0x%x %s%s%s\n",
64                    source->parent->pending,
65                    source->asserted,
66                    source->enable_count,
67                    source->enable_max,
68                    source->vect,
69                    source->asserted ? "asserted " :
70                    assert_adj ? "deasserted" : "",
71                    enable_changed == 1 ? "enabled " :
72                    enable_changed == -1 ? "disabled " : "",
73                    source->pending ? "pending" : "");
74 #endif
75   }
76 }
77
78 static void sh_intc_set_irq (void *opaque, int n, int level)
79 {
80   struct intc_desc *desc = opaque;
81   struct intc_source *source = &(desc->sources[n]);
82
83   if (level && !source->asserted)
84     sh_intc_toggle_source(source, 0, 1);
85   else if (!level && source->asserted)
86     sh_intc_toggle_source(source, 0, -1);
87 }
88
89 int sh_intc_get_pending_vector(struct intc_desc *desc, int imask)
90 {
91     unsigned int i;
92
93     /* slow: use a linked lists of pending sources instead */
94     /* wrong: take interrupt priority into account (one list per priority) */
95
96     if (imask == 0x0f) {
97         return -1; /* FIXME, update code to include priority per source */
98     }
99
100     for (i = 0; i < desc->nr_sources; i++) {
101         struct intc_source *source = desc->sources + i;
102
103         if (source->pending) {
104 #ifdef DEBUG_INTC_SOURCES
105             printf("sh_intc: (%d) returning interrupt source 0x%x\n",
106                    desc->pending, source->vect);
107 #endif
108             return source->vect;
109         }
110     }
111
112     abort();
113 }
114
115 #define INTC_MODE_NONE       0
116 #define INTC_MODE_DUAL_SET   1
117 #define INTC_MODE_DUAL_CLR   2
118 #define INTC_MODE_ENABLE_REG 3
119 #define INTC_MODE_MASK_REG   4
120 #define INTC_MODE_IS_PRIO    8
121
122 static unsigned int sh_intc_mode(unsigned long address,
123                                  unsigned long set_reg, unsigned long clr_reg)
124 {
125     if ((address != INTC_A7(set_reg)) &&
126         (address != INTC_A7(clr_reg)))
127         return INTC_MODE_NONE;
128
129     if (set_reg && clr_reg) {
130         if (address == INTC_A7(set_reg))
131             return INTC_MODE_DUAL_SET;
132         else
133             return INTC_MODE_DUAL_CLR;
134     }
135
136     if (set_reg)
137         return INTC_MODE_ENABLE_REG;
138     else
139         return INTC_MODE_MASK_REG;
140 }
141
142 static void sh_intc_locate(struct intc_desc *desc,
143                            unsigned long address,
144                            unsigned long **datap,
145                            intc_enum **enums,
146                            unsigned int *first,
147                            unsigned int *width,
148                            unsigned int *modep)
149 {
150     unsigned int i, mode;
151
152     /* this is slow but works for now */
153
154     if (desc->mask_regs) {
155         for (i = 0; i < desc->nr_mask_regs; i++) {
156             struct intc_mask_reg *mr = desc->mask_regs + i;
157
158             mode = sh_intc_mode(address, mr->set_reg, mr->clr_reg);
159             if (mode == INTC_MODE_NONE)
160                 continue;
161
162             *modep = mode;
163             *datap = &mr->value;
164             *enums = mr->enum_ids;
165             *first = mr->reg_width - 1;
166             *width = 1;
167             return;
168         }
169     }
170
171     if (desc->prio_regs) {
172         for (i = 0; i < desc->nr_prio_regs; i++) {
173             struct intc_prio_reg *pr = desc->prio_regs + i;
174
175             mode = sh_intc_mode(address, pr->set_reg, pr->clr_reg);
176             if (mode == INTC_MODE_NONE)
177                 continue;
178
179             *modep = mode | INTC_MODE_IS_PRIO;
180             *datap = &pr->value;
181             *enums = pr->enum_ids;
182             *first = (pr->reg_width / pr->field_width) - 1;
183             *width = pr->field_width;
184             return;
185         }
186     }
187
188     abort();
189 }
190
191 static void sh_intc_toggle_mask(struct intc_desc *desc, intc_enum id,
192                                 int enable, int is_group)
193 {
194     struct intc_source *source = desc->sources + id;
195
196     if (!id)
197         return;
198
199     if (!source->next_enum_id && (!source->enable_max || !source->vect)) {
200 #ifdef DEBUG_INTC_SOURCES
201         printf("sh_intc: reserved interrupt source %d modified\n", id);
202 #endif
203         return;
204     }
205
206     if (source->vect)
207         sh_intc_toggle_source(source, enable ? 1 : -1, 0);
208
209 #ifdef DEBUG_INTC
210     else {
211         printf("setting interrupt group %d to %d\n", id, !!enable);
212     }
213 #endif
214
215     if ((is_group || !source->vect) && source->next_enum_id) {
216         sh_intc_toggle_mask(desc, source->next_enum_id, enable, 1);
217     }
218
219 #ifdef DEBUG_INTC
220     if (!source->vect) {
221         printf("setting interrupt group %d to %d - done\n", id, !!enable);
222     }
223 #endif
224 }
225
226 static uint64_t sh_intc_read(void *opaque, hwaddr offset,
227                              unsigned size)
228 {
229     struct intc_desc *desc = opaque;
230     intc_enum *enum_ids = NULL;
231     unsigned int first = 0;
232     unsigned int width = 0;
233     unsigned int mode = 0;
234     unsigned long *valuep;
235
236 #ifdef DEBUG_INTC
237     printf("sh_intc_read 0x%lx\n", (unsigned long) offset);
238 #endif
239
240     sh_intc_locate(desc, (unsigned long)offset, &valuep, 
241                    &enum_ids, &first, &width, &mode);
242     return *valuep;
243 }
244
245 static void sh_intc_write(void *opaque, hwaddr offset,
246                           uint64_t value, unsigned size)
247 {
248     struct intc_desc *desc = opaque;
249     intc_enum *enum_ids = NULL;
250     unsigned int first = 0;
251     unsigned int width = 0;
252     unsigned int mode = 0;
253     unsigned int k;
254     unsigned long *valuep;
255     unsigned long mask;
256
257 #ifdef DEBUG_INTC
258     printf("sh_intc_write 0x%lx 0x%08x\n", (unsigned long) offset, value);
259 #endif
260
261     sh_intc_locate(desc, (unsigned long)offset, &valuep, 
262                    &enum_ids, &first, &width, &mode);
263
264     switch (mode) {
265     case INTC_MODE_ENABLE_REG | INTC_MODE_IS_PRIO: break;
266     case INTC_MODE_DUAL_SET: value |= *valuep; break;
267     case INTC_MODE_DUAL_CLR: value = *valuep & ~value; break;
268     default: abort();
269     }
270
271     for (k = 0; k <= first; k++) {
272         mask = ((1 << width) - 1) << ((first - k) * width);
273
274         if ((*valuep & mask) == (value & mask))
275             continue;
276 #if 0
277         printf("k = %d, first = %d, enum = %d, mask = 0x%08x\n", 
278                k, first, enum_ids[k], (unsigned int)mask);
279 #endif
280         sh_intc_toggle_mask(desc, enum_ids[k], value & mask, 0);
281     }
282
283     *valuep = value;
284
285 #ifdef DEBUG_INTC
286     printf("sh_intc_write 0x%lx -> 0x%08x\n", (unsigned long) offset, value);
287 #endif
288 }
289
290 static const MemoryRegionOps sh_intc_ops = {
291     .read = sh_intc_read,
292     .write = sh_intc_write,
293     .endianness = DEVICE_NATIVE_ENDIAN,
294 };
295
296 struct intc_source *sh_intc_source(struct intc_desc *desc, intc_enum id)
297 {
298     if (id)
299         return desc->sources + id;
300
301     return NULL;
302 }
303
304 static unsigned int sh_intc_register(MemoryRegion *sysmem,
305                              struct intc_desc *desc,
306                              const unsigned long address,
307                              const char *type,
308                              const char *action,
309                              const unsigned int index)
310 {
311     char name[60];
312     MemoryRegion *iomem, *iomem_p4, *iomem_a7;
313
314     if (!address) {
315         return 0;
316     }
317
318     iomem = &desc->iomem;
319     iomem_p4 = desc->iomem_aliases + index;
320     iomem_a7 = iomem_p4 + 1;
321
322 #define SH_INTC_IOMEM_FORMAT "interrupt-controller-%s-%s-%s"
323     snprintf(name, sizeof(name), SH_INTC_IOMEM_FORMAT, type, action, "p4");
324     memory_region_init_alias(iomem_p4, NULL, name, iomem, INTC_A7(address), 4);
325     memory_region_add_subregion(sysmem, P4ADDR(address), iomem_p4);
326
327     snprintf(name, sizeof(name), SH_INTC_IOMEM_FORMAT, type, action, "a7");
328     memory_region_init_alias(iomem_a7, NULL, name, iomem, INTC_A7(address), 4);
329     memory_region_add_subregion(sysmem, A7ADDR(address), iomem_a7);
330 #undef SH_INTC_IOMEM_FORMAT
331
332     /* used to increment aliases index */
333     return 2;
334 }
335
336 static void sh_intc_register_source(struct intc_desc *desc,
337                                     intc_enum source,
338                                     struct intc_group *groups,
339                                     int nr_groups)
340 {
341     unsigned int i, k;
342     struct intc_source *s;
343
344     if (desc->mask_regs) {
345         for (i = 0; i < desc->nr_mask_regs; i++) {
346             struct intc_mask_reg *mr = desc->mask_regs + i;
347
348             for (k = 0; k < ARRAY_SIZE(mr->enum_ids); k++) {
349                 if (mr->enum_ids[k] != source)
350                     continue;
351
352                 s = sh_intc_source(desc, mr->enum_ids[k]);
353                 if (s)
354                     s->enable_max++;
355             }
356         }
357     }
358
359     if (desc->prio_regs) {
360         for (i = 0; i < desc->nr_prio_regs; i++) {
361             struct intc_prio_reg *pr = desc->prio_regs + i;
362
363             for (k = 0; k < ARRAY_SIZE(pr->enum_ids); k++) {
364                 if (pr->enum_ids[k] != source)
365                     continue;
366
367                 s = sh_intc_source(desc, pr->enum_ids[k]);
368                 if (s)
369                     s->enable_max++;
370             }
371         }
372     }
373
374     if (groups) {
375         for (i = 0; i < nr_groups; i++) {
376             struct intc_group *gr = groups + i;
377
378             for (k = 0; k < ARRAY_SIZE(gr->enum_ids); k++) {
379                 if (gr->enum_ids[k] != source)
380                     continue;
381
382                 s = sh_intc_source(desc, gr->enum_ids[k]);
383                 if (s)
384                     s->enable_max++;
385             }
386         }
387     }
388
389 }
390
391 void sh_intc_register_sources(struct intc_desc *desc,
392                               struct intc_vect *vectors,
393                               int nr_vectors,
394                               struct intc_group *groups,
395                               int nr_groups)
396 {
397     unsigned int i, k;
398     struct intc_source *s;
399
400     for (i = 0; i < nr_vectors; i++) {
401         struct intc_vect *vect = vectors + i;
402
403         sh_intc_register_source(desc, vect->enum_id, groups, nr_groups);
404         s = sh_intc_source(desc, vect->enum_id);
405         if (s) {
406             s->vect = vect->vect;
407
408 #ifdef DEBUG_INTC_SOURCES
409             printf("sh_intc: registered source %d -> 0x%04x (%d/%d)\n",
410                    vect->enum_id, s->vect, s->enable_count, s->enable_max);
411 #endif
412         }
413     }
414
415     if (groups) {
416         for (i = 0; i < nr_groups; i++) {
417             struct intc_group *gr = groups + i;
418
419             s = sh_intc_source(desc, gr->enum_id);
420             s->next_enum_id = gr->enum_ids[0];
421
422             for (k = 1; k < ARRAY_SIZE(gr->enum_ids); k++) {
423                 if (!gr->enum_ids[k])
424                     continue;
425
426                 s = sh_intc_source(desc, gr->enum_ids[k - 1]);
427                 s->next_enum_id = gr->enum_ids[k];
428             }
429
430 #ifdef DEBUG_INTC_SOURCES
431             printf("sh_intc: registered group %d (%d/%d)\n",
432                    gr->enum_id, s->enable_count, s->enable_max);
433 #endif
434         }
435     }
436 }
437
438 int sh_intc_init(MemoryRegion *sysmem,
439          struct intc_desc *desc,
440                  int nr_sources,
441                  struct intc_mask_reg *mask_regs,
442                  int nr_mask_regs,
443                  struct intc_prio_reg *prio_regs,
444                  int nr_prio_regs)
445 {
446     unsigned int i, j;
447
448     desc->pending = 0;
449     desc->nr_sources = nr_sources;
450     desc->mask_regs = mask_regs;
451     desc->nr_mask_regs = nr_mask_regs;
452     desc->prio_regs = prio_regs;
453     desc->nr_prio_regs = nr_prio_regs;
454     /* Allocate 4 MemoryRegions per register (2 actions * 2 aliases).
455      **/
456     desc->iomem_aliases = g_new0(MemoryRegion,
457                                  (nr_mask_regs + nr_prio_regs) * 4);
458
459     j = 0;
460     i = sizeof(struct intc_source) * nr_sources;
461     desc->sources = g_malloc0(i);
462
463     for (i = 0; i < desc->nr_sources; i++) {
464         struct intc_source *source = desc->sources + i;
465
466         source->parent = desc;
467     }
468
469     desc->irqs = qemu_allocate_irqs(sh_intc_set_irq, desc, nr_sources);
470  
471     memory_region_init_io(&desc->iomem, NULL, &sh_intc_ops, desc,
472                           "interrupt-controller", 0x100000000ULL);
473
474 #define INT_REG_PARAMS(reg_struct, type, action, j) \
475         reg_struct->action##_reg, #type, #action, j
476     if (desc->mask_regs) {
477         for (i = 0; i < desc->nr_mask_regs; i++) {
478             struct intc_mask_reg *mr = desc->mask_regs + i;
479
480             j += sh_intc_register(sysmem, desc,
481                                   INT_REG_PARAMS(mr, mask, set, j));
482             j += sh_intc_register(sysmem, desc,
483                                   INT_REG_PARAMS(mr, mask, clr, j));
484         }
485     }
486
487     if (desc->prio_regs) {
488         for (i = 0; i < desc->nr_prio_regs; i++) {
489             struct intc_prio_reg *pr = desc->prio_regs + i;
490
491             j += sh_intc_register(sysmem, desc,
492                                   INT_REG_PARAMS(pr, prio, set, j));
493             j += sh_intc_register(sysmem, desc,
494                                   INT_REG_PARAMS(pr, prio, clr, j));
495         }
496     }
497 #undef INT_REG_PARAMS
498
499     return 0;
500 }
501
502 /* Assert level <n> IRL interrupt. 
503    0:deassert. 1:lowest priority,... 15:highest priority. */
504 void sh_intc_set_irl(void *opaque, int n, int level)
505 {
506     struct intc_source *s = opaque;
507     int i, irl = level ^ 15;
508     for (i = 0; (s = sh_intc_source(s->parent, s->next_enum_id)); i++) {
509         if (i == irl)
510             sh_intc_toggle_source(s, s->enable_count?0:1, s->asserted?0:1);
511         else
512             if (s->asserted)
513                 sh_intc_toggle_source(s, 0, -1);
514     }
515 }