Add PowerPC power-management state check callback.
[qemu] / hw / sh_intc.c
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 }