Statistics
| Branch: | Revision:

root / hw / sh_intc.c @ 80f515e6

History | View | Annotate | Download (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 "vl.h"
14

    
15
//#define DEBUG_INTC
16

    
17
#define INTC_A7(x) ((x) & 0x1fffffff)
18
#define INTC_ARRAY(x) (sizeof(x) / sizeof(x[0]))
19

    
20
#define INTC_MODE_NONE       0
21
#define INTC_MODE_DUAL_SET   1
22
#define INTC_MODE_DUAL_CLR   2
23
#define INTC_MODE_ENABLE_REG 3
24
#define INTC_MODE_MASK_REG   4
25
#define INTC_MODE_IS_PRIO    8
26

    
27
static unsigned int sh_intc_mode(unsigned long address,
28
                                 unsigned long set_reg, unsigned long clr_reg)
29
{
30
    if ((address != INTC_A7(set_reg)) &&
31
        (address != INTC_A7(clr_reg)))
32
        return INTC_MODE_NONE;
33

    
34
    if (set_reg && clr_reg) {
35
        if (address == INTC_A7(set_reg))
36
            return INTC_MODE_DUAL_SET;
37
        else
38
            return INTC_MODE_DUAL_CLR;
39
    }
40

    
41
    if (set_reg)
42
        return INTC_MODE_ENABLE_REG;
43
    else
44
        return INTC_MODE_MASK_REG;
45
}
46

    
47
static void sh_intc_locate(struct intc_desc *desc,
48
                           unsigned long address,
49
                           unsigned long **datap,
50
                           intc_enum **enums,
51
                           unsigned int *first,
52
                           unsigned int *width,
53
                           unsigned int *modep)
54
{
55
    unsigned int i, mode;
56

    
57
    /* this is slow but works for now */
58

    
59
    if (desc->mask_regs) {
60
        for (i = 0; i < desc->nr_mask_regs; i++) {
61
            struct intc_mask_reg *mr = desc->mask_regs + i;
62

    
63
            mode = sh_intc_mode(address, mr->set_reg, mr->clr_reg);
64
            if (mode == INTC_MODE_NONE)
65
                continue;
66

    
67
            *modep = mode;
68
            *datap = &mr->value;
69
            *enums = mr->enum_ids;
70
            *first = mr->reg_width - 1;
71
            *width = 1;
72
            return;
73
        }
74
    }
75

    
76
    if (desc->prio_regs) {
77
        for (i = 0; i < desc->nr_prio_regs; i++) {
78
            struct intc_prio_reg *pr = desc->prio_regs + i;
79

    
80
            mode = sh_intc_mode(address, pr->set_reg, pr->clr_reg);
81
            if (mode == INTC_MODE_NONE)
82
                continue;
83

    
84
            *modep = mode | INTC_MODE_IS_PRIO;
85
            *datap = &pr->value;
86
            *enums = pr->enum_ids;
87
            *first = (pr->reg_width / pr->field_width) - 1;
88
            *width = pr->field_width;
89
            return;
90
        }
91
    }
92

    
93
    assert(0);
94
}
95

    
96
static void sh_intc_toggle(struct intc_desc *desc, intc_enum id,
97
                           int enable, int is_group)
98
{
99
    struct intc_source *source = desc->sources + id;
100
    int old = source->enable_count;
101

    
102
    if (!id)
103
        return;
104

    
105
    if (!source->next_enum_id && (!source->enable_max || !source->vect)) {
106
#ifdef DEBUG_INTC
107
        printf("sh_intc: reserved interrupt source %d modified\n", id);
108
#endif
109
        return;
110
    }
111

    
112
    if (source->vect) {
113
        if (enable)
114
            source->enable_count++;
115
        else 
116
            source->enable_count--;
117

    
118
        if (source->enable_count == source->enable_max) {
119
#ifdef DEBUG_INTC
120
            printf("sh_intc: enabling interrupt source %d -> 0x%04x\n",
121
                   id, source->vect);
122
#endif
123
        }
124

    
125
        if (old == source->enable_max) {
126
#ifdef DEBUG_INTC
127
            printf("sh_intc: disabling interrupt source %d -> 0x%04x\n",
128
                   id, source->vect);
129
#endif
130
        }
131
    }
132
#ifdef DEBUG_INTC
133
    else {
134
        printf("setting interrupt group %d to %d\n", id, !!enable);
135
    }
136
#endif
137

    
138
    if ((is_group || !source->vect) && source->next_enum_id) {
139
        sh_intc_toggle(desc, source->next_enum_id, enable, 1);
140
    }
141

    
142
#ifdef DEBUG_INTC
143
    if (!source->vect) {
144
        printf("setting interrupt group %d to %d - done\n", id, !!enable);
145
    }
146
#endif
147
}
148

    
149
static uint32_t sh_intc_read(void *opaque, target_phys_addr_t offset)
150
{
151
    struct intc_desc *desc = opaque;
152
    intc_enum *enum_ids = NULL;
153
    unsigned int first = 0;
154
    unsigned int width = 0;
155
    unsigned int mode = 0;
156
    unsigned long *valuep;
157

    
158
#ifdef DEBUG_INTC
159
    printf("sh_intc_read 0x%lx\n", (unsigned long) offset);
160
#endif
161

    
162
    sh_intc_locate(desc, (unsigned long)offset, &valuep, 
163
                   &enum_ids, &first, &width, &mode);
164
    return *valuep;
165
}
166

    
167
static void sh_intc_write(void *opaque, target_phys_addr_t offset,
168
                          uint32_t value)
169
{
170
    struct intc_desc *desc = opaque;
171
    intc_enum *enum_ids = NULL;
172
    unsigned int first = 0;
173
    unsigned int width = 0;
174
    unsigned int mode = 0;
175
    unsigned int k;
176
    unsigned long *valuep;
177
    unsigned long mask;
178

    
179
#ifdef DEBUG_INTC
180
    printf("sh_intc_write 0x%lx 0x%08x\n", (unsigned long) offset, value);
181
#endif
182

    
183
    sh_intc_locate(desc, (unsigned long)offset, &valuep, 
184
                   &enum_ids, &first, &width, &mode);
185

    
186
    switch (mode) {
187
    case INTC_MODE_ENABLE_REG | INTC_MODE_IS_PRIO: break;
188
    case INTC_MODE_DUAL_SET: value |= *valuep; break;
189
    case INTC_MODE_DUAL_CLR: value = *valuep & ~value; break;
190
    default: assert(0);
191
    }
192

    
193
    for (k = 0; k <= first; k++) {
194
        mask = ((1 << width) - 1) << ((first - k) * width);
195

    
196
        if ((*valuep & mask) == (value & mask))
197
            continue;
198
#if 0
199
        printf("k = %d, first = %d, enum = %d, mask = 0x%08x\n", 
200
               k, first, enum_ids[k], (unsigned int)mask);
201
#endif
202
        sh_intc_toggle(desc, enum_ids[k], value & mask, 0);
203
    }
204

    
205
    *valuep = value;
206

    
207
#ifdef DEBUG_INTC
208
    printf("sh_intc_write 0x%lx -> 0x%08x\n", (unsigned long) offset, value);
209
#endif
210
}
211

    
212
static CPUReadMemoryFunc *sh_intc_readfn[] = {
213
    sh_intc_read,
214
    sh_intc_read,
215
    sh_intc_read
216
};
217

    
218
static CPUWriteMemoryFunc *sh_intc_writefn[] = {
219
    sh_intc_write,
220
    sh_intc_write,
221
    sh_intc_write
222
};
223

    
224
struct intc_source *sh_intc_source(struct intc_desc *desc, intc_enum id)
225
{
226
    if (id)
227
        return desc->sources + id;
228

    
229
    return NULL;
230
}
231

    
232
static void sh_intc_register(struct intc_desc *desc, 
233
                             unsigned long address)
234
{
235
    if (address)
236
        cpu_register_physical_memory(INTC_A7(address), 4, desc->iomemtype);
237
}
238

    
239
static void sh_intc_register_source(struct intc_desc *desc,
240
                                    intc_enum source,
241
                                    struct intc_group *groups,
242
                                    int nr_groups)
243
{
244
    unsigned int i, k;
245
    struct intc_source *s;
246

    
247
    if (desc->mask_regs) {
248
        for (i = 0; i < desc->nr_mask_regs; i++) {
249
            struct intc_mask_reg *mr = desc->mask_regs + i;
250

    
251
            for (k = 0; k < INTC_ARRAY(mr->enum_ids); k++) {
252
                if (mr->enum_ids[k] != source)
253
                    continue;
254

    
255
                s = sh_intc_source(desc, mr->enum_ids[k]);
256
                if (s)
257
                    s->enable_max++;
258
            }
259
        }
260
    }
261

    
262
    if (desc->prio_regs) {
263
        for (i = 0; i < desc->nr_prio_regs; i++) {
264
            struct intc_prio_reg *pr = desc->prio_regs + i;
265

    
266
            for (k = 0; k < INTC_ARRAY(pr->enum_ids); k++) {
267
                if (pr->enum_ids[k] != source)
268
                    continue;
269

    
270
                s = sh_intc_source(desc, pr->enum_ids[k]);
271
                if (s)
272
                    s->enable_max++;
273
            }
274
        }
275
    }
276

    
277
    if (groups) {
278
        for (i = 0; i < nr_groups; i++) {
279
            struct intc_group *gr = groups + i;
280

    
281
            for (k = 0; k < INTC_ARRAY(gr->enum_ids); k++) {
282
                if (gr->enum_ids[k] != source)
283
                    continue;
284

    
285
                s = sh_intc_source(desc, gr->enum_ids[k]);
286
                if (s)
287
                    s->enable_max++;
288
            }
289
        }
290
    }
291

    
292
}
293

    
294
void sh_intc_register_sources(struct intc_desc *desc,
295
                              struct intc_vect *vectors,
296
                              int nr_vectors,
297
                              struct intc_group *groups,
298
                              int nr_groups)
299
{
300
    unsigned int i, k;
301
    struct intc_source *s;
302

    
303
    for (i = 0; i < nr_vectors; i++) {
304
        struct intc_vect *vect = vectors + i;
305

    
306
        sh_intc_register_source(desc, vect->enum_id, groups, nr_groups);
307
        s = sh_intc_source(desc, vect->enum_id);
308
        if (s)
309
            s->vect = vect->vect;
310

    
311
#ifdef DEBUG_INTC
312
        printf("sh_intc: registered source %d -> 0x%04x (%d/%d)\n",
313
               vect->enum_id, s->vect, s->enable_count, s->enable_max);
314
#endif
315
    }
316

    
317
    if (groups) {
318
        for (i = 0; i < nr_groups; i++) {
319
            struct intc_group *gr = groups + i;
320

    
321
            s = sh_intc_source(desc, gr->enum_id);
322
            s->next_enum_id = gr->enum_ids[0];
323

    
324
            for (k = 1; k < INTC_ARRAY(gr->enum_ids); k++) {
325
                if (!gr->enum_ids[k])
326
                    continue;
327

    
328
                s = sh_intc_source(desc, gr->enum_ids[k - 1]);
329
                s->next_enum_id = gr->enum_ids[k];
330
            }
331

    
332
#ifdef DEBUG_INTC
333
            printf("sh_intc: registered group %d (%d/%d)\n",
334
                   gr->enum_id, s->enable_count, s->enable_max);
335
#endif
336
        }
337
    }
338
}
339

    
340
int sh_intc_init(struct intc_desc *desc,
341
                 int nr_sources,
342
                 struct intc_mask_reg *mask_regs,
343
                 int nr_mask_regs,
344
                 struct intc_prio_reg *prio_regs,
345
                 int nr_prio_regs)
346
{
347
    unsigned int i;
348

    
349
    desc->nr_sources = nr_sources;
350
    desc->mask_regs = mask_regs;
351
    desc->nr_mask_regs = nr_mask_regs;
352
    desc->prio_regs = prio_regs;
353
    desc->nr_prio_regs = nr_prio_regs;
354

    
355
    i = sizeof(struct intc_source) * nr_sources;
356
    desc->sources = malloc(i);
357
    if (!desc->sources)
358
        return -1;
359

    
360
    memset(desc->sources, 0, i);
361
 
362
    desc->iomemtype = cpu_register_io_memory(0, sh_intc_readfn,
363
                                             sh_intc_writefn, desc);
364
    if (desc->mask_regs) {
365
        for (i = 0; i < desc->nr_mask_regs; i++) {
366
            struct intc_mask_reg *mr = desc->mask_regs + i;
367

    
368
            sh_intc_register(desc, mr->set_reg);
369
            sh_intc_register(desc, mr->clr_reg);
370
        }
371
    }
372

    
373
    if (desc->prio_regs) {
374
        for (i = 0; i < desc->nr_prio_regs; i++) {
375
            struct intc_prio_reg *pr = desc->prio_regs + i;
376

    
377
            sh_intc_register(desc, pr->set_reg);
378
            sh_intc_register(desc, pr->clr_reg);
379
        }
380
    }
381

    
382
    return 0;
383
}