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