~ubuntu-branches/ubuntu/wily/qemu-kvm-spice/wily

« back to all changes in this revision

Viewing changes to hw/sh_intc.c

  • Committer: Bazaar Package Importer
  • Author(s): Serge Hallyn
  • Date: 2011-10-19 10:44:56 UTC
  • Revision ID: james.westby@ubuntu.com-20111019104456-xgvskumk3sxi97f4
Tags: upstream-0.15.0+noroms
ImportĀ upstreamĀ versionĀ 0.15.0+noroms

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 = qemu_mallocz(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
}