xref: /openbmc/qemu/hw/intc/sh_intc.c (revision a9ded601)
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 }
516