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