symbian-qemu-0.9.1-12/qemu-symbian-svp/hw/sh_intc.c
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     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 licenced under the GPL.
       
     9  */
       
    10 
       
    11 #include <assert.h>
       
    12 #include "sh_intc.h"
       
    13 #include "hw.h"
       
    14 #include "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   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     assert(0);
       
   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     assert(0);
       
   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 uint32_t sh_intc_read(void *opaque, target_phys_addr_t offset)
       
   224 {
       
   225     struct intc_desc *desc = opaque;
       
   226     intc_enum *enum_ids = NULL;
       
   227     unsigned int first = 0;
       
   228     unsigned int width = 0;
       
   229     unsigned int mode = 0;
       
   230     unsigned long *valuep;
       
   231 
       
   232 #ifdef DEBUG_INTC
       
   233     printf("sh_intc_read 0x%lx\n", (unsigned long) offset);
       
   234 #endif
       
   235 
       
   236     sh_intc_locate(desc, (unsigned long)offset, &valuep, 
       
   237 		   &enum_ids, &first, &width, &mode);
       
   238     return *valuep;
       
   239 }
       
   240 
       
   241 static void sh_intc_write(void *opaque, target_phys_addr_t offset,
       
   242 			  uint32_t value)
       
   243 {
       
   244     struct intc_desc *desc = opaque;
       
   245     intc_enum *enum_ids = NULL;
       
   246     unsigned int first = 0;
       
   247     unsigned int width = 0;
       
   248     unsigned int mode = 0;
       
   249     unsigned int k;
       
   250     unsigned long *valuep;
       
   251     unsigned long mask;
       
   252 
       
   253 #ifdef DEBUG_INTC
       
   254     printf("sh_intc_write 0x%lx 0x%08x\n", (unsigned long) offset, value);
       
   255 #endif
       
   256 
       
   257     sh_intc_locate(desc, (unsigned long)offset, &valuep, 
       
   258 		   &enum_ids, &first, &width, &mode);
       
   259 
       
   260     switch (mode) {
       
   261     case INTC_MODE_ENABLE_REG | INTC_MODE_IS_PRIO: break;
       
   262     case INTC_MODE_DUAL_SET: value |= *valuep; break;
       
   263     case INTC_MODE_DUAL_CLR: value = *valuep & ~value; break;
       
   264     default: assert(0);
       
   265     }
       
   266 
       
   267     for (k = 0; k <= first; k++) {
       
   268         mask = ((1 << width) - 1) << ((first - k) * width);
       
   269 
       
   270 	if ((*valuep & mask) == (value & mask))
       
   271             continue;
       
   272 #if 0
       
   273 	printf("k = %d, first = %d, enum = %d, mask = 0x%08x\n", 
       
   274 	       k, first, enum_ids[k], (unsigned int)mask);
       
   275 #endif
       
   276         sh_intc_toggle_mask(desc, enum_ids[k], value & mask, 0);
       
   277     }
       
   278 
       
   279     *valuep = value;
       
   280 
       
   281 #ifdef DEBUG_INTC
       
   282     printf("sh_intc_write 0x%lx -> 0x%08x\n", (unsigned long) offset, value);
       
   283 #endif
       
   284 }
       
   285 
       
   286 static CPUReadMemoryFunc *sh_intc_readfn[] = {
       
   287     sh_intc_read,
       
   288     sh_intc_read,
       
   289     sh_intc_read
       
   290 };
       
   291 
       
   292 static CPUWriteMemoryFunc *sh_intc_writefn[] = {
       
   293     sh_intc_write,
       
   294     sh_intc_write,
       
   295     sh_intc_write
       
   296 };
       
   297 
       
   298 struct intc_source *sh_intc_source(struct intc_desc *desc, intc_enum id)
       
   299 {
       
   300     if (id)
       
   301         return desc->sources + id;
       
   302 
       
   303     return NULL;
       
   304 }
       
   305 
       
   306 static void sh_intc_register(struct intc_desc *desc, 
       
   307 			     unsigned long address)
       
   308 {
       
   309     if (address) {
       
   310         cpu_register_physical_memory_offset(P4ADDR(address), 4,
       
   311                                             desc->iomemtype, INTC_A7(address));
       
   312         cpu_register_physical_memory_offset(A7ADDR(address), 4,
       
   313                                             desc->iomemtype, INTC_A7(address));
       
   314     }
       
   315 }
       
   316 
       
   317 static void sh_intc_register_source(struct intc_desc *desc,
       
   318 				    intc_enum source,
       
   319 				    struct intc_group *groups,
       
   320 				    int nr_groups)
       
   321 {
       
   322     unsigned int i, k;
       
   323     struct intc_source *s;
       
   324 
       
   325     if (desc->mask_regs) {
       
   326         for (i = 0; i < desc->nr_mask_regs; i++) {
       
   327 	    struct intc_mask_reg *mr = desc->mask_regs + i;
       
   328 
       
   329 	    for (k = 0; k < ARRAY_SIZE(mr->enum_ids); k++) {
       
   330                 if (mr->enum_ids[k] != source)
       
   331                     continue;
       
   332 
       
   333 		s = sh_intc_source(desc, mr->enum_ids[k]);
       
   334 		if (s)
       
   335                     s->enable_max++;
       
   336 	    }
       
   337 	}
       
   338     }
       
   339 
       
   340     if (desc->prio_regs) {
       
   341         for (i = 0; i < desc->nr_prio_regs; i++) {
       
   342 	    struct intc_prio_reg *pr = desc->prio_regs + i;
       
   343 
       
   344 	    for (k = 0; k < ARRAY_SIZE(pr->enum_ids); k++) {
       
   345                 if (pr->enum_ids[k] != source)
       
   346                     continue;
       
   347 
       
   348 		s = sh_intc_source(desc, pr->enum_ids[k]);
       
   349 		if (s)
       
   350                     s->enable_max++;
       
   351 	    }
       
   352 	}
       
   353     }
       
   354 
       
   355     if (groups) {
       
   356         for (i = 0; i < nr_groups; i++) {
       
   357 	    struct intc_group *gr = groups + i;
       
   358 
       
   359 	    for (k = 0; k < ARRAY_SIZE(gr->enum_ids); k++) {
       
   360                 if (gr->enum_ids[k] != source)
       
   361                     continue;
       
   362 
       
   363 		s = sh_intc_source(desc, gr->enum_ids[k]);
       
   364 		if (s)
       
   365                     s->enable_max++;
       
   366 	    }
       
   367 	}
       
   368     }
       
   369 
       
   370 }
       
   371 
       
   372 void sh_intc_register_sources(struct intc_desc *desc,
       
   373 			      struct intc_vect *vectors,
       
   374 			      int nr_vectors,
       
   375 			      struct intc_group *groups,
       
   376 			      int nr_groups)
       
   377 {
       
   378     unsigned int i, k;
       
   379     struct intc_source *s;
       
   380 
       
   381     for (i = 0; i < nr_vectors; i++) {
       
   382 	struct intc_vect *vect = vectors + i;
       
   383 
       
   384 	sh_intc_register_source(desc, vect->enum_id, groups, nr_groups);
       
   385 	s = sh_intc_source(desc, vect->enum_id);
       
   386 	if (s)
       
   387 	    s->vect = vect->vect;
       
   388 
       
   389 #ifdef DEBUG_INTC_SOURCES
       
   390 	printf("sh_intc: registered source %d -> 0x%04x (%d/%d)\n",
       
   391 	       vect->enum_id, s->vect, s->enable_count, s->enable_max);
       
   392 #endif
       
   393     }
       
   394 
       
   395     if (groups) {
       
   396         for (i = 0; i < nr_groups; i++) {
       
   397 	    struct intc_group *gr = groups + i;
       
   398 
       
   399 	    s = sh_intc_source(desc, gr->enum_id);
       
   400 	    s->next_enum_id = gr->enum_ids[0];
       
   401 
       
   402 	    for (k = 1; k < ARRAY_SIZE(gr->enum_ids); k++) {
       
   403                 if (!gr->enum_ids[k])
       
   404                     continue;
       
   405 
       
   406 		s = sh_intc_source(desc, gr->enum_ids[k - 1]);
       
   407 		s->next_enum_id = gr->enum_ids[k];
       
   408 	    }
       
   409 
       
   410 #ifdef DEBUG_INTC_SOURCES
       
   411 	    printf("sh_intc: registered group %d (%d/%d)\n",
       
   412 		   gr->enum_id, s->enable_count, s->enable_max);
       
   413 #endif
       
   414 	}
       
   415     }
       
   416 }
       
   417 
       
   418 int sh_intc_init(struct intc_desc *desc,
       
   419 		 int nr_sources,
       
   420 		 struct intc_mask_reg *mask_regs,
       
   421 		 int nr_mask_regs,
       
   422 		 struct intc_prio_reg *prio_regs,
       
   423 		 int nr_prio_regs)
       
   424 {
       
   425     unsigned int i;
       
   426 
       
   427     desc->pending = 0;
       
   428     desc->nr_sources = nr_sources;
       
   429     desc->mask_regs = mask_regs;
       
   430     desc->nr_mask_regs = nr_mask_regs;
       
   431     desc->prio_regs = prio_regs;
       
   432     desc->nr_prio_regs = nr_prio_regs;
       
   433 
       
   434     i = sizeof(struct intc_source) * nr_sources;
       
   435     desc->sources = malloc(i);
       
   436     if (!desc->sources)
       
   437         return -1;
       
   438 
       
   439     memset(desc->sources, 0, i);
       
   440     for (i = 0; i < desc->nr_sources; i++) {
       
   441         struct intc_source *source = desc->sources + i;
       
   442 
       
   443         source->parent = desc;
       
   444     }
       
   445 
       
   446     desc->irqs = qemu_allocate_irqs(sh_intc_set_irq, desc, nr_sources);
       
   447  
       
   448     desc->iomemtype = cpu_register_io_memory(0, sh_intc_readfn,
       
   449 					     sh_intc_writefn, desc);
       
   450     if (desc->mask_regs) {
       
   451         for (i = 0; i < desc->nr_mask_regs; i++) {
       
   452 	    struct intc_mask_reg *mr = desc->mask_regs + i;
       
   453 
       
   454 	    sh_intc_register(desc, mr->set_reg);
       
   455 	    sh_intc_register(desc, mr->clr_reg);
       
   456 	}
       
   457     }
       
   458 
       
   459     if (desc->prio_regs) {
       
   460         for (i = 0; i < desc->nr_prio_regs; i++) {
       
   461 	    struct intc_prio_reg *pr = desc->prio_regs + i;
       
   462 
       
   463 	    sh_intc_register(desc, pr->set_reg);
       
   464 	    sh_intc_register(desc, pr->clr_reg);
       
   465 	}
       
   466     }
       
   467 
       
   468     return 0;
       
   469 }
       
   470 
       
   471 /* Assert level <n> IRL interrupt. 
       
   472    0:deassert. 1:lowest priority,... 15:highest priority. */
       
   473 void sh_intc_set_irl(void *opaque, int n, int level)
       
   474 {
       
   475     struct intc_source *s = opaque;
       
   476     int i, irl = level ^ 15;
       
   477     for (i = 0; (s = sh_intc_source(s->parent, s->next_enum_id)); i++) {
       
   478 	if (i == irl)
       
   479 	    sh_intc_toggle_source(s, s->enable_count?0:1, s->asserted?0:1);
       
   480 	else
       
   481 	    if (s->asserted)
       
   482 	        sh_intc_toggle_source(s, 0, -1);
       
   483     }
       
   484 }