Statistics
| Branch: | Revision:

root / hw / sh_intc.c @ e96e2044

History | View | Annotate | Download (10.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 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
#define INTC_ARRAY(x) (sizeof(x) / sizeof(x[0]))
21

    
22
void sh_intc_toggle_source(struct intc_source *source,
23
                           int enable_adj, int assert_adj)
24
{
25
    int enable_changed = 0;
26
    int pending_changed = 0;
27
    int old_pending;
28

    
29
    if ((source->enable_count == source->enable_max) && (enable_adj == -1))
30
        enable_changed = -1;
31

    
32
    source->enable_count += enable_adj;
33

    
34
    if (source->enable_count == source->enable_max)
35
        enable_changed = 1;
36

    
37
    source->asserted += assert_adj;
38

    
39
    old_pending = source->pending;
40
    source->pending = source->asserted &&
41
      (source->enable_count == source->enable_max);
42

    
43
    if (old_pending != source->pending)
44
        pending_changed = 1;
45

    
46
    if (pending_changed) {
47
        if (source->pending) {
48
            source->parent->pending++;
49
            if (source->parent->pending == 1)
50
                cpu_interrupt(first_cpu, CPU_INTERRUPT_HARD);
51
        }
52
        else {
53
            source->parent->pending--;
54
            if (source->parent->pending == 0)
55
                cpu_reset_interrupt(first_cpu, CPU_INTERRUPT_HARD);
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
int sh_intc_get_pending_vector(struct intc_desc *desc, int imask)
77
{
78
    unsigned int i;
79

    
80
    /* slow: use a linked lists of pending sources instead */
81
    /* wrong: take interrupt priority into account (one list per priority) */
82

    
83
    if (imask == 0x0f) {
84
        return -1; /* FIXME, update code to include priority per source */
85
    }
86

    
87
    for (i = 0; i < desc->nr_sources; i++) {
88
        struct intc_source *source = desc->sources + i;
89

    
90
        if (source->pending) {
91
#ifdef DEBUG_INTC_SOURCES
92
            printf("sh_intc: (%d) returning interrupt source 0x%x\n",
93
                   desc->pending, source->vect);
94
#endif
95
            return source->vect;
96
        }
97
    }
98

    
99
    assert(0);
100
}
101

    
102
#define INTC_MODE_NONE       0
103
#define INTC_MODE_DUAL_SET   1
104
#define INTC_MODE_DUAL_CLR   2
105
#define INTC_MODE_ENABLE_REG 3
106
#define INTC_MODE_MASK_REG   4
107
#define INTC_MODE_IS_PRIO    8
108

    
109
static unsigned int sh_intc_mode(unsigned long address,
110
                                 unsigned long set_reg, unsigned long clr_reg)
111
{
112
    if ((address != INTC_A7(set_reg)) &&
113
        (address != INTC_A7(clr_reg)))
114
        return INTC_MODE_NONE;
115

    
116
    if (set_reg && clr_reg) {
117
        if (address == INTC_A7(set_reg))
118
            return INTC_MODE_DUAL_SET;
119
        else
120
            return INTC_MODE_DUAL_CLR;
121
    }
122

    
123
    if (set_reg)
124
        return INTC_MODE_ENABLE_REG;
125
    else
126
        return INTC_MODE_MASK_REG;
127
}
128

    
129
static void sh_intc_locate(struct intc_desc *desc,
130
                           unsigned long address,
131
                           unsigned long **datap,
132
                           intc_enum **enums,
133
                           unsigned int *first,
134
                           unsigned int *width,
135
                           unsigned int *modep)
136
{
137
    unsigned int i, mode;
138

    
139
    /* this is slow but works for now */
140

    
141
    if (desc->mask_regs) {
142
        for (i = 0; i < desc->nr_mask_regs; i++) {
143
            struct intc_mask_reg *mr = desc->mask_regs + i;
144

    
145
            mode = sh_intc_mode(address, mr->set_reg, mr->clr_reg);
146
            if (mode == INTC_MODE_NONE)
147
                continue;
148

    
149
            *modep = mode;
150
            *datap = &mr->value;
151
            *enums = mr->enum_ids;
152
            *first = mr->reg_width - 1;
153
            *width = 1;
154
            return;
155
        }
156
    }
157

    
158
    if (desc->prio_regs) {
159
        for (i = 0; i < desc->nr_prio_regs; i++) {
160
            struct intc_prio_reg *pr = desc->prio_regs + i;
161

    
162
            mode = sh_intc_mode(address, pr->set_reg, pr->clr_reg);
163
            if (mode == INTC_MODE_NONE)
164
                continue;
165

    
166
            *modep = mode | INTC_MODE_IS_PRIO;
167
            *datap = &pr->value;
168
            *enums = pr->enum_ids;
169
            *first = (pr->reg_width / pr->field_width) - 1;
170
            *width = pr->field_width;
171
            return;
172
        }
173
    }
174

    
175
    assert(0);
176
}
177

    
178
static void sh_intc_toggle_mask(struct intc_desc *desc, intc_enum id,
179
                                int enable, int is_group)
180
{
181
    struct intc_source *source = desc->sources + id;
182

    
183
    if (!id)
184
        return;
185

    
186
    if (!source->next_enum_id && (!source->enable_max || !source->vect)) {
187
#ifdef DEBUG_INTC_SOURCES
188
        printf("sh_intc: reserved interrupt source %d modified\n", id);
189
#endif
190
        return;
191
    }
192

    
193
    if (source->vect)
194
        sh_intc_toggle_source(source, enable ? 1 : -1, 0);
195

    
196
#ifdef DEBUG_INTC
197
    else {
198
        printf("setting interrupt group %d to %d\n", id, !!enable);
199
    }
200
#endif
201

    
202
    if ((is_group || !source->vect) && source->next_enum_id) {
203
        sh_intc_toggle_mask(desc, source->next_enum_id, enable, 1);
204
    }
205

    
206
#ifdef DEBUG_INTC
207
    if (!source->vect) {
208
        printf("setting interrupt group %d to %d - done\n", id, !!enable);
209
    }
210
#endif
211
}
212

    
213
static uint32_t sh_intc_read(void *opaque, target_phys_addr_t offset)
214
{
215
    struct intc_desc *desc = opaque;
216
    intc_enum *enum_ids = NULL;
217
    unsigned int first = 0;
218
    unsigned int width = 0;
219
    unsigned int mode = 0;
220
    unsigned long *valuep;
221

    
222
#ifdef DEBUG_INTC
223
    printf("sh_intc_read 0x%lx\n", (unsigned long) offset);
224
#endif
225

    
226
    sh_intc_locate(desc, (unsigned long)offset, &valuep, 
227
                   &enum_ids, &first, &width, &mode);
228
    return *valuep;
229
}
230

    
231
static void sh_intc_write(void *opaque, target_phys_addr_t offset,
232
                          uint32_t value)
233
{
234
    struct intc_desc *desc = opaque;
235
    intc_enum *enum_ids = NULL;
236
    unsigned int first = 0;
237
    unsigned int width = 0;
238
    unsigned int mode = 0;
239
    unsigned int k;
240
    unsigned long *valuep;
241
    unsigned long mask;
242

    
243
#ifdef DEBUG_INTC
244
    printf("sh_intc_write 0x%lx 0x%08x\n", (unsigned long) offset, value);
245
#endif
246

    
247
    sh_intc_locate(desc, (unsigned long)offset, &valuep, 
248
                   &enum_ids, &first, &width, &mode);
249

    
250
    switch (mode) {
251
    case INTC_MODE_ENABLE_REG | INTC_MODE_IS_PRIO: break;
252
    case INTC_MODE_DUAL_SET: value |= *valuep; break;
253
    case INTC_MODE_DUAL_CLR: value = *valuep & ~value; break;
254
    default: assert(0);
255
    }
256

    
257
    for (k = 0; k <= first; k++) {
258
        mask = ((1 << width) - 1) << ((first - k) * width);
259

    
260
        if ((*valuep & mask) == (value & mask))
261
            continue;
262
#if 0
263
        printf("k = %d, first = %d, enum = %d, mask = 0x%08x\n", 
264
               k, first, enum_ids[k], (unsigned int)mask);
265
#endif
266
        sh_intc_toggle_mask(desc, enum_ids[k], value & mask, 0);
267
    }
268

    
269
    *valuep = value;
270

    
271
#ifdef DEBUG_INTC
272
    printf("sh_intc_write 0x%lx -> 0x%08x\n", (unsigned long) offset, value);
273
#endif
274
}
275

    
276
static CPUReadMemoryFunc *sh_intc_readfn[] = {
277
    sh_intc_read,
278
    sh_intc_read,
279
    sh_intc_read
280
};
281

    
282
static CPUWriteMemoryFunc *sh_intc_writefn[] = {
283
    sh_intc_write,
284
    sh_intc_write,
285
    sh_intc_write
286
};
287

    
288
struct intc_source *sh_intc_source(struct intc_desc *desc, intc_enum id)
289
{
290
    if (id)
291
        return desc->sources + id;
292

    
293
    return NULL;
294
}
295

    
296
static void sh_intc_register(struct intc_desc *desc, 
297
                             unsigned long address)
298
{
299
    if (address)
300
        cpu_register_physical_memory(INTC_A7(address), 4, desc->iomemtype);
301
}
302

    
303
static void sh_intc_register_source(struct intc_desc *desc,
304
                                    intc_enum source,
305
                                    struct intc_group *groups,
306
                                    int nr_groups)
307
{
308
    unsigned int i, k;
309
    struct intc_source *s;
310

    
311
    if (desc->mask_regs) {
312
        for (i = 0; i < desc->nr_mask_regs; i++) {
313
            struct intc_mask_reg *mr = desc->mask_regs + i;
314

    
315
            for (k = 0; k < INTC_ARRAY(mr->enum_ids); k++) {
316
                if (mr->enum_ids[k] != source)
317
                    continue;
318

    
319
                s = sh_intc_source(desc, mr->enum_ids[k]);
320
                if (s)
321
                    s->enable_max++;
322
            }
323
        }
324
    }
325

    
326
    if (desc->prio_regs) {
327
        for (i = 0; i < desc->nr_prio_regs; i++) {
328
            struct intc_prio_reg *pr = desc->prio_regs + i;
329

    
330
            for (k = 0; k < INTC_ARRAY(pr->enum_ids); k++) {
331
                if (pr->enum_ids[k] != source)
332
                    continue;
333

    
334
                s = sh_intc_source(desc, pr->enum_ids[k]);
335
                if (s)
336
                    s->enable_max++;
337
            }
338
        }
339
    }
340

    
341
    if (groups) {
342
        for (i = 0; i < nr_groups; i++) {
343
            struct intc_group *gr = groups + i;
344

    
345
            for (k = 0; k < INTC_ARRAY(gr->enum_ids); k++) {
346
                if (gr->enum_ids[k] != source)
347
                    continue;
348

    
349
                s = sh_intc_source(desc, gr->enum_ids[k]);
350
                if (s)
351
                    s->enable_max++;
352
            }
353
        }
354
    }
355

    
356
}
357

    
358
void sh_intc_register_sources(struct intc_desc *desc,
359
                              struct intc_vect *vectors,
360
                              int nr_vectors,
361
                              struct intc_group *groups,
362
                              int nr_groups)
363
{
364
    unsigned int i, k;
365
    struct intc_source *s;
366

    
367
    for (i = 0; i < nr_vectors; i++) {
368
        struct intc_vect *vect = vectors + i;
369

    
370
        sh_intc_register_source(desc, vect->enum_id, groups, nr_groups);
371
        s = sh_intc_source(desc, vect->enum_id);
372
        if (s)
373
            s->vect = vect->vect;
374

    
375
#ifdef DEBUG_INTC_SOURCES
376
        printf("sh_intc: registered source %d -> 0x%04x (%d/%d)\n",
377
               vect->enum_id, s->vect, s->enable_count, s->enable_max);
378
#endif
379
    }
380

    
381
    if (groups) {
382
        for (i = 0; i < nr_groups; i++) {
383
            struct intc_group *gr = groups + i;
384

    
385
            s = sh_intc_source(desc, gr->enum_id);
386
            s->next_enum_id = gr->enum_ids[0];
387

    
388
            for (k = 1; k < INTC_ARRAY(gr->enum_ids); k++) {
389
                if (!gr->enum_ids[k])
390
                    continue;
391

    
392
                s = sh_intc_source(desc, gr->enum_ids[k - 1]);
393
                s->next_enum_id = gr->enum_ids[k];
394
            }
395

    
396
#ifdef DEBUG_INTC_SOURCES
397
            printf("sh_intc: registered group %d (%d/%d)\n",
398
                   gr->enum_id, s->enable_count, s->enable_max);
399
#endif
400
        }
401
    }
402
}
403

    
404
int sh_intc_init(struct intc_desc *desc,
405
                 int nr_sources,
406
                 struct intc_mask_reg *mask_regs,
407
                 int nr_mask_regs,
408
                 struct intc_prio_reg *prio_regs,
409
                 int nr_prio_regs)
410
{
411
    unsigned int i;
412

    
413
    desc->pending = 0;
414
    desc->nr_sources = nr_sources;
415
    desc->mask_regs = mask_regs;
416
    desc->nr_mask_regs = nr_mask_regs;
417
    desc->prio_regs = prio_regs;
418
    desc->nr_prio_regs = nr_prio_regs;
419

    
420
    i = sizeof(struct intc_source) * nr_sources;
421
    desc->sources = malloc(i);
422
    if (!desc->sources)
423
        return -1;
424

    
425
    memset(desc->sources, 0, i);
426
    for (i = 0; i < desc->nr_sources; i++) {
427
        struct intc_source *source = desc->sources + i;
428

    
429
        source->parent = desc;
430
    }
431
 
432
    desc->iomemtype = cpu_register_io_memory(0, sh_intc_readfn,
433
                                             sh_intc_writefn, desc);
434
    if (desc->mask_regs) {
435
        for (i = 0; i < desc->nr_mask_regs; i++) {
436
            struct intc_mask_reg *mr = desc->mask_regs + i;
437

    
438
            sh_intc_register(desc, mr->set_reg);
439
            sh_intc_register(desc, mr->clr_reg);
440
        }
441
    }
442

    
443
    if (desc->prio_regs) {
444
        for (i = 0; i < desc->nr_prio_regs; i++) {
445
            struct intc_prio_reg *pr = desc->prio_regs + i;
446

    
447
            sh_intc_register(desc, pr->set_reg);
448
            sh_intc_register(desc, pr->clr_reg);
449
        }
450
    }
451

    
452
    return 0;
453
}