Statistics
| Branch: | Revision:

root / hw / sh_intc.c @ 7267c094

History | View | Annotate | Download (11.9 kB)

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 "sh_intc.h"
12
#include "hw.h"
13
#include "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
  if (enable_changed || assert_adj || pending_changed) {
58
#ifdef DEBUG_INTC_SOURCES
59
            printf("sh_intc: (%d/%d/%d/%d) interrupt source 0x%x %s%s%s\n",
60
                   source->parent->pending,
61
                   source->asserted,
62
                   source->enable_count,
63
                   source->enable_max,
64
                   source->vect,
65
                   source->asserted ? "asserted " :
66
                   assert_adj ? "deasserted" : "",
67
                   enable_changed == 1 ? "enabled " :
68
                   enable_changed == -1 ? "disabled " : "",
69
                   source->pending ? "pending" : "");
70
#endif
71
  }
72
}
73

    
74
static void sh_intc_set_irq (void *opaque, int n, int level)
75
{
76
  struct intc_desc *desc = opaque;
77
  struct intc_source *source = &(desc->sources[n]);
78

    
79
  if (level && !source->asserted)
80
    sh_intc_toggle_source(source, 0, 1);
81
  else if (!level && source->asserted)
82
    sh_intc_toggle_source(source, 0, -1);
83
}
84

    
85
int sh_intc_get_pending_vector(struct intc_desc *desc, int imask)
86
{
87
    unsigned int i;
88

    
89
    /* slow: use a linked lists of pending sources instead */
90
    /* wrong: take interrupt priority into account (one list per priority) */
91

    
92
    if (imask == 0x0f) {
93
        return -1; /* FIXME, update code to include priority per source */
94
    }
95

    
96
    for (i = 0; i < desc->nr_sources; i++) {
97
        struct intc_source *source = desc->sources + i;
98

    
99
        if (source->pending) {
100
#ifdef DEBUG_INTC_SOURCES
101
            printf("sh_intc: (%d) returning interrupt source 0x%x\n",
102
                   desc->pending, source->vect);
103
#endif
104
            return source->vect;
105
        }
106
    }
107

    
108
    abort();
109
}
110

    
111
#define INTC_MODE_NONE       0
112
#define INTC_MODE_DUAL_SET   1
113
#define INTC_MODE_DUAL_CLR   2
114
#define INTC_MODE_ENABLE_REG 3
115
#define INTC_MODE_MASK_REG   4
116
#define INTC_MODE_IS_PRIO    8
117

    
118
static unsigned int sh_intc_mode(unsigned long address,
119
                                 unsigned long set_reg, unsigned long clr_reg)
120
{
121
    if ((address != INTC_A7(set_reg)) &&
122
        (address != INTC_A7(clr_reg)))
123
        return INTC_MODE_NONE;
124

    
125
    if (set_reg && clr_reg) {
126
        if (address == INTC_A7(set_reg))
127
            return INTC_MODE_DUAL_SET;
128
        else
129
            return INTC_MODE_DUAL_CLR;
130
    }
131

    
132
    if (set_reg)
133
        return INTC_MODE_ENABLE_REG;
134
    else
135
        return INTC_MODE_MASK_REG;
136
}
137

    
138
static void sh_intc_locate(struct intc_desc *desc,
139
                           unsigned long address,
140
                           unsigned long **datap,
141
                           intc_enum **enums,
142
                           unsigned int *first,
143
                           unsigned int *width,
144
                           unsigned int *modep)
145
{
146
    unsigned int i, mode;
147

    
148
    /* this is slow but works for now */
149

    
150
    if (desc->mask_regs) {
151
        for (i = 0; i < desc->nr_mask_regs; i++) {
152
            struct intc_mask_reg *mr = desc->mask_regs + i;
153

    
154
            mode = sh_intc_mode(address, mr->set_reg, mr->clr_reg);
155
            if (mode == INTC_MODE_NONE)
156
                continue;
157

    
158
            *modep = mode;
159
            *datap = &mr->value;
160
            *enums = mr->enum_ids;
161
            *first = mr->reg_width - 1;
162
            *width = 1;
163
            return;
164
        }
165
    }
166

    
167
    if (desc->prio_regs) {
168
        for (i = 0; i < desc->nr_prio_regs; i++) {
169
            struct intc_prio_reg *pr = desc->prio_regs + i;
170

    
171
            mode = sh_intc_mode(address, pr->set_reg, pr->clr_reg);
172
            if (mode == INTC_MODE_NONE)
173
                continue;
174

    
175
            *modep = mode | INTC_MODE_IS_PRIO;
176
            *datap = &pr->value;
177
            *enums = pr->enum_ids;
178
            *first = (pr->reg_width / pr->field_width) - 1;
179
            *width = pr->field_width;
180
            return;
181
        }
182
    }
183

    
184
    abort();
185
}
186

    
187
static void sh_intc_toggle_mask(struct intc_desc *desc, intc_enum id,
188
                                int enable, int is_group)
189
{
190
    struct intc_source *source = desc->sources + id;
191

    
192
    if (!id)
193
        return;
194

    
195
    if (!source->next_enum_id && (!source->enable_max || !source->vect)) {
196
#ifdef DEBUG_INTC_SOURCES
197
        printf("sh_intc: reserved interrupt source %d modified\n", id);
198
#endif
199
        return;
200
    }
201

    
202
    if (source->vect)
203
        sh_intc_toggle_source(source, enable ? 1 : -1, 0);
204

    
205
#ifdef DEBUG_INTC
206
    else {
207
        printf("setting interrupt group %d to %d\n", id, !!enable);
208
    }
209
#endif
210

    
211
    if ((is_group || !source->vect) && source->next_enum_id) {
212
        sh_intc_toggle_mask(desc, source->next_enum_id, enable, 1);
213
    }
214

    
215
#ifdef DEBUG_INTC
216
    if (!source->vect) {
217
        printf("setting interrupt group %d to %d - done\n", id, !!enable);
218
    }
219
#endif
220
}
221

    
222
static uint32_t sh_intc_read(void *opaque, target_phys_addr_t offset)
223
{
224
    struct intc_desc *desc = opaque;
225
    intc_enum *enum_ids = NULL;
226
    unsigned int first = 0;
227
    unsigned int width = 0;
228
    unsigned int mode = 0;
229
    unsigned long *valuep;
230

    
231
#ifdef DEBUG_INTC
232
    printf("sh_intc_read 0x%lx\n", (unsigned long) offset);
233
#endif
234

    
235
    sh_intc_locate(desc, (unsigned long)offset, &valuep, 
236
                   &enum_ids, &first, &width, &mode);
237
    return *valuep;
238
}
239

    
240
static void sh_intc_write(void *opaque, target_phys_addr_t offset,
241
                          uint32_t value)
242
{
243
    struct intc_desc *desc = opaque;
244
    intc_enum *enum_ids = NULL;
245
    unsigned int first = 0;
246
    unsigned int width = 0;
247
    unsigned int mode = 0;
248
    unsigned int k;
249
    unsigned long *valuep;
250
    unsigned long mask;
251

    
252
#ifdef DEBUG_INTC
253
    printf("sh_intc_write 0x%lx 0x%08x\n", (unsigned long) offset, value);
254
#endif
255

    
256
    sh_intc_locate(desc, (unsigned long)offset, &valuep, 
257
                   &enum_ids, &first, &width, &mode);
258

    
259
    switch (mode) {
260
    case INTC_MODE_ENABLE_REG | INTC_MODE_IS_PRIO: break;
261
    case INTC_MODE_DUAL_SET: value |= *valuep; break;
262
    case INTC_MODE_DUAL_CLR: value = *valuep & ~value; break;
263
    default: abort();
264
    }
265

    
266
    for (k = 0; k <= first; k++) {
267
        mask = ((1 << width) - 1) << ((first - k) * width);
268

    
269
        if ((*valuep & mask) == (value & mask))
270
            continue;
271
#if 0
272
        printf("k = %d, first = %d, enum = %d, mask = 0x%08x\n", 
273
               k, first, enum_ids[k], (unsigned int)mask);
274
#endif
275
        sh_intc_toggle_mask(desc, enum_ids[k], value & mask, 0);
276
    }
277

    
278
    *valuep = value;
279

    
280
#ifdef DEBUG_INTC
281
    printf("sh_intc_write 0x%lx -> 0x%08x\n", (unsigned long) offset, value);
282
#endif
283
}
284

    
285
static CPUReadMemoryFunc * const sh_intc_readfn[] = {
286
    sh_intc_read,
287
    sh_intc_read,
288
    sh_intc_read
289
};
290

    
291
static CPUWriteMemoryFunc * const sh_intc_writefn[] = {
292
    sh_intc_write,
293
    sh_intc_write,
294
    sh_intc_write
295
};
296

    
297
struct intc_source *sh_intc_source(struct intc_desc *desc, intc_enum id)
298
{
299
    if (id)
300
        return desc->sources + id;
301

    
302
    return NULL;
303
}
304

    
305
static void sh_intc_register(struct intc_desc *desc, 
306
                             unsigned long address)
307
{
308
    if (address) {
309
        cpu_register_physical_memory_offset(P4ADDR(address), 4,
310
                                            desc->iomemtype, INTC_A7(address));
311
        cpu_register_physical_memory_offset(A7ADDR(address), 4,
312
                                            desc->iomemtype, INTC_A7(address));
313
    }
314
}
315

    
316
static void sh_intc_register_source(struct intc_desc *desc,
317
                                    intc_enum source,
318
                                    struct intc_group *groups,
319
                                    int nr_groups)
320
{
321
    unsigned int i, k;
322
    struct intc_source *s;
323

    
324
    if (desc->mask_regs) {
325
        for (i = 0; i < desc->nr_mask_regs; i++) {
326
            struct intc_mask_reg *mr = desc->mask_regs + i;
327

    
328
            for (k = 0; k < ARRAY_SIZE(mr->enum_ids); k++) {
329
                if (mr->enum_ids[k] != source)
330
                    continue;
331

    
332
                s = sh_intc_source(desc, mr->enum_ids[k]);
333
                if (s)
334
                    s->enable_max++;
335
            }
336
        }
337
    }
338

    
339
    if (desc->prio_regs) {
340
        for (i = 0; i < desc->nr_prio_regs; i++) {
341
            struct intc_prio_reg *pr = desc->prio_regs + i;
342

    
343
            for (k = 0; k < ARRAY_SIZE(pr->enum_ids); k++) {
344
                if (pr->enum_ids[k] != source)
345
                    continue;
346

    
347
                s = sh_intc_source(desc, pr->enum_ids[k]);
348
                if (s)
349
                    s->enable_max++;
350
            }
351
        }
352
    }
353

    
354
    if (groups) {
355
        for (i = 0; i < nr_groups; i++) {
356
            struct intc_group *gr = groups + i;
357

    
358
            for (k = 0; k < ARRAY_SIZE(gr->enum_ids); k++) {
359
                if (gr->enum_ids[k] != source)
360
                    continue;
361

    
362
                s = sh_intc_source(desc, gr->enum_ids[k]);
363
                if (s)
364
                    s->enable_max++;
365
            }
366
        }
367
    }
368

    
369
}
370

    
371
void sh_intc_register_sources(struct intc_desc *desc,
372
                              struct intc_vect *vectors,
373
                              int nr_vectors,
374
                              struct intc_group *groups,
375
                              int nr_groups)
376
{
377
    unsigned int i, k;
378
    struct intc_source *s;
379

    
380
    for (i = 0; i < nr_vectors; i++) {
381
        struct intc_vect *vect = vectors + i;
382

    
383
        sh_intc_register_source(desc, vect->enum_id, groups, nr_groups);
384
        s = sh_intc_source(desc, vect->enum_id);
385
        if (s)
386
            s->vect = vect->vect;
387

    
388
#ifdef DEBUG_INTC_SOURCES
389
        printf("sh_intc: registered source %d -> 0x%04x (%d/%d)\n",
390
               vect->enum_id, s->vect, s->enable_count, s->enable_max);
391
#endif
392
    }
393

    
394
    if (groups) {
395
        for (i = 0; i < nr_groups; i++) {
396
            struct intc_group *gr = groups + i;
397

    
398
            s = sh_intc_source(desc, gr->enum_id);
399
            s->next_enum_id = gr->enum_ids[0];
400

    
401
            for (k = 1; k < ARRAY_SIZE(gr->enum_ids); k++) {
402
                if (!gr->enum_ids[k])
403
                    continue;
404

    
405
                s = sh_intc_source(desc, gr->enum_ids[k - 1]);
406
                s->next_enum_id = gr->enum_ids[k];
407
            }
408

    
409
#ifdef DEBUG_INTC_SOURCES
410
            printf("sh_intc: registered group %d (%d/%d)\n",
411
                   gr->enum_id, s->enable_count, s->enable_max);
412
#endif
413
        }
414
    }
415
}
416

    
417
int sh_intc_init(struct intc_desc *desc,
418
                 int nr_sources,
419
                 struct intc_mask_reg *mask_regs,
420
                 int nr_mask_regs,
421
                 struct intc_prio_reg *prio_regs,
422
                 int nr_prio_regs)
423
{
424
    unsigned int i;
425

    
426
    desc->pending = 0;
427
    desc->nr_sources = nr_sources;
428
    desc->mask_regs = mask_regs;
429
    desc->nr_mask_regs = nr_mask_regs;
430
    desc->prio_regs = prio_regs;
431
    desc->nr_prio_regs = nr_prio_regs;
432

    
433
    i = sizeof(struct intc_source) * nr_sources;
434
    desc->sources = g_malloc0(i);
435

    
436
    for (i = 0; i < desc->nr_sources; i++) {
437
        struct intc_source *source = desc->sources + i;
438

    
439
        source->parent = desc;
440
    }
441

    
442
    desc->irqs = qemu_allocate_irqs(sh_intc_set_irq, desc, nr_sources);
443
 
444
    desc->iomemtype = cpu_register_io_memory(sh_intc_readfn,
445
                                             sh_intc_writefn, desc,
446
                                             DEVICE_NATIVE_ENDIAN);
447
    if (desc->mask_regs) {
448
        for (i = 0; i < desc->nr_mask_regs; i++) {
449
            struct intc_mask_reg *mr = desc->mask_regs + i;
450

    
451
            sh_intc_register(desc, mr->set_reg);
452
            sh_intc_register(desc, mr->clr_reg);
453
        }
454
    }
455

    
456
    if (desc->prio_regs) {
457
        for (i = 0; i < desc->nr_prio_regs; i++) {
458
            struct intc_prio_reg *pr = desc->prio_regs + i;
459

    
460
            sh_intc_register(desc, pr->set_reg);
461
            sh_intc_register(desc, pr->clr_reg);
462
        }
463
    }
464

    
465
    return 0;
466
}
467

    
468
/* Assert level <n> IRL interrupt. 
469
   0:deassert. 1:lowest priority,... 15:highest priority. */
470
void sh_intc_set_irl(void *opaque, int n, int level)
471
{
472
    struct intc_source *s = opaque;
473
    int i, irl = level ^ 15;
474
    for (i = 0; (s = sh_intc_source(s->parent, s->next_enum_id)); i++) {
475
        if (i == irl)
476
            sh_intc_toggle_source(s, s->enable_count?0:1, s->asserted?0:1);
477
        else
478
            if (s->asserted)
479
                sh_intc_toggle_source(s, 0, -1);
480
    }
481
}