xref: /qemu/hw/gpio/pl061.c (revision ad06d56fc7155c7893b18efecb9fe0f2e9124eaf) !
1 /*
2  * Arm PrimeCell PL061 General Purpose IO with additional
3  * Luminary Micro Stellaris bits.
4  *
5  * Copyright (c) 2007 CodeSourcery.
6  * Written by Paul Brook
7  *
8  * This code is licensed under the GPL.
9  *
10  * QEMU interface:
11  *  + sysbus MMIO region 0: the device registers
12  *  + sysbus IRQ: the GPIOINTR interrupt line
13  *  + unnamed GPIO inputs 0..7: inputs to connect to the emulated GPIO lines
14  *  + unnamed GPIO outputs 0..7: the emulated GPIO lines, considered as
15  *    outputs
16  */
17 
18 #include "qemu/osdep.h"
19 #include "hw/irq.h"
20 #include "hw/sysbus.h"
21 #include "migration/vmstate.h"
22 #include "qemu/log.h"
23 #include "qemu/module.h"
24 #include "qom/object.h"
25 #include "trace.h"
26 
27 static const uint8_t pl061_id[12] =
28   { 0x00, 0x00, 0x00, 0x00, 0x61, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
29 static const uint8_t pl061_id_luminary[12] =
30   { 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1 };
31 
32 #define TYPE_PL061 "pl061"
33 OBJECT_DECLARE_SIMPLE_TYPE(PL061State, PL061)
34 
35 #define N_GPIOS 8
36 
37 struct PL061State {
38     SysBusDevice parent_obj;
39 
40     MemoryRegion iomem;
41     uint32_t locked;
42     uint32_t data;
43     uint32_t old_out_data;
44     uint32_t old_in_data;
45     uint32_t dir;
46     uint32_t isense;
47     uint32_t ibe;
48     uint32_t iev;
49     uint32_t im;
50     uint32_t istate;
51     uint32_t afsel;
52     uint32_t dr2r;
53     uint32_t dr4r;
54     uint32_t dr8r;
55     uint32_t odr;
56     uint32_t pur;
57     uint32_t pdr;
58     uint32_t slr;
59     uint32_t den;
60     uint32_t cr;
61     uint32_t amsel;
62     qemu_irq irq;
63     qemu_irq out[N_GPIOS];
64     const unsigned char *id;
65 };
66 
67 static const VMStateDescription vmstate_pl061 = {
68     .name = "pl061",
69     .version_id = 4,
70     .minimum_version_id = 4,
71     .fields = (VMStateField[]) {
72         VMSTATE_UINT32(locked, PL061State),
73         VMSTATE_UINT32(data, PL061State),
74         VMSTATE_UINT32(old_out_data, PL061State),
75         VMSTATE_UINT32(old_in_data, PL061State),
76         VMSTATE_UINT32(dir, PL061State),
77         VMSTATE_UINT32(isense, PL061State),
78         VMSTATE_UINT32(ibe, PL061State),
79         VMSTATE_UINT32(iev, PL061State),
80         VMSTATE_UINT32(im, PL061State),
81         VMSTATE_UINT32(istate, PL061State),
82         VMSTATE_UINT32(afsel, PL061State),
83         VMSTATE_UINT32(dr2r, PL061State),
84         VMSTATE_UINT32(dr4r, PL061State),
85         VMSTATE_UINT32(dr8r, PL061State),
86         VMSTATE_UINT32(odr, PL061State),
87         VMSTATE_UINT32(pur, PL061State),
88         VMSTATE_UINT32(pdr, PL061State),
89         VMSTATE_UINT32(slr, PL061State),
90         VMSTATE_UINT32(den, PL061State),
91         VMSTATE_UINT32(cr, PL061State),
92         VMSTATE_UINT32_V(amsel, PL061State, 2),
93         VMSTATE_END_OF_LIST()
94     }
95 };
96 
97 static uint8_t pl061_floating(PL061State *s)
98 {
99     /*
100      * Return mask of bits which correspond to pins configured as inputs
101      * and which are floating (neither pulled up to 1 nor down to 0).
102      */
103     uint8_t floating;
104 
105     if (s->id == pl061_id_luminary) {
106         /*
107          * If both PUR and PDR bits are clear, there is neither a pullup
108          * nor a pulldown in place, and the output truly floats.
109          */
110         floating = ~(s->pur | s->pdr);
111     } else {
112         /* Assume outputs are pulled high. FIXME: this is board dependent. */
113         floating = 0;
114     }
115     return floating & ~s->dir;
116 }
117 
118 static uint8_t pl061_pullups(PL061State *s)
119 {
120     /*
121      * Return mask of bits which correspond to pins configured as inputs
122      * and which are pulled up to 1.
123      */
124     uint8_t pullups;
125 
126     if (s->id == pl061_id_luminary) {
127         /*
128          * The Luminary variant of the PL061 has an extra registers which
129          * the guest can use to configure whether lines should be pullup
130          * or pulldown.
131          */
132         pullups = s->pur;
133     } else {
134         /* Assume outputs are pulled high. FIXME: this is board dependent. */
135         pullups = 0xff;
136     }
137     return pullups & ~s->dir;
138 }
139 
140 static void pl061_update(PL061State *s)
141 {
142     uint8_t changed;
143     uint8_t mask;
144     uint8_t out;
145     int i;
146     uint8_t pullups = pl061_pullups(s);
147     uint8_t floating = pl061_floating(s);
148 
149     trace_pl061_update(DEVICE(s)->canonical_path, s->dir, s->data,
150                        pullups, floating);
151 
152     /*
153      * Pins configured as output are driven from the data register;
154      * otherwise if they're pulled up they're 1, and if they're floating
155      * then we give them the same value they had previously, so we don't
156      * report any change to the other end.
157      */
158     out = (s->data & s->dir) | pullups | (s->old_out_data & floating);
159     changed = s->old_out_data ^ out;
160     if (changed) {
161         s->old_out_data = out;
162         for (i = 0; i < N_GPIOS; i++) {
163             mask = 1 << i;
164             if (changed & mask) {
165                 int level = (out & mask) != 0;
166                 trace_pl061_set_output(DEVICE(s)->canonical_path, i, level);
167                 qemu_set_irq(s->out[i], level);
168             }
169         }
170     }
171 
172     /* Inputs */
173     changed = (s->old_in_data ^ s->data) & ~s->dir;
174     if (changed) {
175         s->old_in_data = s->data;
176         for (i = 0; i < N_GPIOS; i++) {
177             mask = 1 << i;
178             if (changed & mask) {
179                 trace_pl061_input_change(DEVICE(s)->canonical_path, i,
180                                          (s->data & mask) != 0);
181 
182                 if (!(s->isense & mask)) {
183                     /* Edge interrupt */
184                     if (s->ibe & mask) {
185                         /* Any edge triggers the interrupt */
186                         s->istate |= mask;
187                     } else {
188                         /* Edge is selected by IEV */
189                         s->istate |= ~(s->data ^ s->iev) & mask;
190                     }
191                 }
192             }
193         }
194     }
195 
196     /* Level interrupt */
197     s->istate |= ~(s->data ^ s->iev) & s->isense;
198 
199     trace_pl061_update_istate(DEVICE(s)->canonical_path,
200                               s->istate, s->im, (s->istate & s->im) != 0);
201 
202     qemu_set_irq(s->irq, (s->istate & s->im) != 0);
203 }
204 
205 static uint64_t pl061_read(void *opaque, hwaddr offset,
206                            unsigned size)
207 {
208     PL061State *s = (PL061State *)opaque;
209     uint64_t r = 0;
210 
211     switch (offset) {
212     case 0x0 ... 0x3ff: /* Data */
213         r = s->data & (offset >> 2);
214         break;
215     case 0x400: /* Direction */
216         r = s->dir;
217         break;
218     case 0x404: /* Interrupt sense */
219         r = s->isense;
220         break;
221     case 0x408: /* Interrupt both edges */
222         r = s->ibe;
223         break;
224     case 0x40c: /* Interrupt event */
225         r = s->iev;
226         break;
227     case 0x410: /* Interrupt mask */
228         r = s->im;
229         break;
230     case 0x414: /* Raw interrupt status */
231         r = s->istate;
232         break;
233     case 0x418: /* Masked interrupt status */
234         r = s->istate & s->im;
235         break;
236     case 0x420: /* Alternate function select */
237         r = s->afsel;
238         break;
239     case 0x500: /* 2mA drive */
240         if (s->id != pl061_id_luminary) {
241             goto bad_offset;
242         }
243         r = s->dr2r;
244         break;
245     case 0x504: /* 4mA drive */
246         if (s->id != pl061_id_luminary) {
247             goto bad_offset;
248         }
249         r = s->dr4r;
250         break;
251     case 0x508: /* 8mA drive */
252         if (s->id != pl061_id_luminary) {
253             goto bad_offset;
254         }
255         r = s->dr8r;
256         break;
257     case 0x50c: /* Open drain */
258         if (s->id != pl061_id_luminary) {
259             goto bad_offset;
260         }
261         r = s->odr;
262         break;
263     case 0x510: /* Pull-up */
264         if (s->id != pl061_id_luminary) {
265             goto bad_offset;
266         }
267         r = s->pur;
268         break;
269     case 0x514: /* Pull-down */
270         if (s->id != pl061_id_luminary) {
271             goto bad_offset;
272         }
273         r = s->pdr;
274         break;
275     case 0x518: /* Slew rate control */
276         if (s->id != pl061_id_luminary) {
277             goto bad_offset;
278         }
279         r = s->slr;
280         break;
281     case 0x51c: /* Digital enable */
282         if (s->id != pl061_id_luminary) {
283             goto bad_offset;
284         }
285         r = s->den;
286         break;
287     case 0x520: /* Lock */
288         if (s->id != pl061_id_luminary) {
289             goto bad_offset;
290         }
291         r = s->locked;
292         break;
293     case 0x524: /* Commit */
294         if (s->id != pl061_id_luminary) {
295             goto bad_offset;
296         }
297         r = s->cr;
298         break;
299     case 0x528: /* Analog mode select */
300         if (s->id != pl061_id_luminary) {
301             goto bad_offset;
302         }
303         r = s->amsel;
304         break;
305     case 0xfd0 ... 0xfff: /* ID registers */
306         r = s->id[(offset - 0xfd0) >> 2];
307         break;
308     default:
309     bad_offset:
310         qemu_log_mask(LOG_GUEST_ERROR,
311                       "pl061_read: Bad offset %x\n", (int)offset);
312         break;
313     }
314 
315     trace_pl061_read(DEVICE(s)->canonical_path, offset, r);
316     return r;
317 }
318 
319 static void pl061_write(void *opaque, hwaddr offset,
320                         uint64_t value, unsigned size)
321 {
322     PL061State *s = (PL061State *)opaque;
323     uint8_t mask;
324 
325     trace_pl061_write(DEVICE(s)->canonical_path, offset, value);
326 
327     switch (offset) {
328     case 0 ... 0x3ff:
329         mask = (offset >> 2) & s->dir;
330         s->data = (s->data & ~mask) | (value & mask);
331         pl061_update(s);
332         return;
333     case 0x400: /* Direction */
334         s->dir = value & 0xff;
335         break;
336     case 0x404: /* Interrupt sense */
337         s->isense = value & 0xff;
338         break;
339     case 0x408: /* Interrupt both edges */
340         s->ibe = value & 0xff;
341         break;
342     case 0x40c: /* Interrupt event */
343         s->iev = value & 0xff;
344         break;
345     case 0x410: /* Interrupt mask */
346         s->im = value & 0xff;
347         break;
348     case 0x41c: /* Interrupt clear */
349         s->istate &= ~value;
350         break;
351     case 0x420: /* Alternate function select */
352         mask = s->cr;
353         s->afsel = (s->afsel & ~mask) | (value & mask);
354         break;
355     case 0x500: /* 2mA drive */
356         if (s->id != pl061_id_luminary) {
357             goto bad_offset;
358         }
359         s->dr2r = value & 0xff;
360         break;
361     case 0x504: /* 4mA drive */
362         if (s->id != pl061_id_luminary) {
363             goto bad_offset;
364         }
365         s->dr4r = value & 0xff;
366         break;
367     case 0x508: /* 8mA drive */
368         if (s->id != pl061_id_luminary) {
369             goto bad_offset;
370         }
371         s->dr8r = value & 0xff;
372         break;
373     case 0x50c: /* Open drain */
374         if (s->id != pl061_id_luminary) {
375             goto bad_offset;
376         }
377         s->odr = value & 0xff;
378         break;
379     case 0x510: /* Pull-up */
380         if (s->id != pl061_id_luminary) {
381             goto bad_offset;
382         }
383         s->pur = value & 0xff;
384         break;
385     case 0x514: /* Pull-down */
386         if (s->id != pl061_id_luminary) {
387             goto bad_offset;
388         }
389         s->pdr = value & 0xff;
390         break;
391     case 0x518: /* Slew rate control */
392         if (s->id != pl061_id_luminary) {
393             goto bad_offset;
394         }
395         s->slr = value & 0xff;
396         break;
397     case 0x51c: /* Digital enable */
398         if (s->id != pl061_id_luminary) {
399             goto bad_offset;
400         }
401         s->den = value & 0xff;
402         break;
403     case 0x520: /* Lock */
404         if (s->id != pl061_id_luminary) {
405             goto bad_offset;
406         }
407         s->locked = (value != 0xacce551);
408         break;
409     case 0x524: /* Commit */
410         if (s->id != pl061_id_luminary) {
411             goto bad_offset;
412         }
413         if (!s->locked)
414             s->cr = value & 0xff;
415         break;
416     case 0x528:
417         if (s->id != pl061_id_luminary) {
418             goto bad_offset;
419         }
420         s->amsel = value & 0xff;
421         break;
422     default:
423     bad_offset:
424         qemu_log_mask(LOG_GUEST_ERROR,
425                       "pl061_write: Bad offset %x\n", (int)offset);
426         return;
427     }
428     pl061_update(s);
429     return;
430 }
431 
432 static void pl061_reset(DeviceState *dev)
433 {
434     PL061State *s = PL061(dev);
435 
436     /* reset values from PL061 TRM, Stellaris LM3S5P31 & LM3S8962 Data Sheet */
437     s->data = 0;
438     s->old_out_data = 0;
439     s->old_in_data = 0;
440     s->dir = 0;
441     s->isense = 0;
442     s->ibe = 0;
443     s->iev = 0;
444     s->im = 0;
445     s->istate = 0;
446     s->afsel = 0;
447     s->dr2r = 0xff;
448     s->dr4r = 0;
449     s->dr8r = 0;
450     s->odr = 0;
451     s->pur = 0;
452     s->pdr = 0;
453     s->slr = 0;
454     s->den = 0;
455     s->locked = 1;
456     s->cr = 0xff;
457     s->amsel = 0;
458 }
459 
460 static void pl061_set_irq(void * opaque, int irq, int level)
461 {
462     PL061State *s = (PL061State *)opaque;
463     uint8_t mask;
464 
465     mask = 1 << irq;
466     if ((s->dir & mask) == 0) {
467         s->data &= ~mask;
468         if (level)
469             s->data |= mask;
470         pl061_update(s);
471     }
472 }
473 
474 static const MemoryRegionOps pl061_ops = {
475     .read = pl061_read,
476     .write = pl061_write,
477     .endianness = DEVICE_NATIVE_ENDIAN,
478 };
479 
480 static void pl061_luminary_init(Object *obj)
481 {
482     PL061State *s = PL061(obj);
483 
484     s->id = pl061_id_luminary;
485 }
486 
487 static void pl061_init(Object *obj)
488 {
489     PL061State *s = PL061(obj);
490     DeviceState *dev = DEVICE(obj);
491     SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
492 
493     s->id = pl061_id;
494 
495     memory_region_init_io(&s->iomem, obj, &pl061_ops, s, "pl061", 0x1000);
496     sysbus_init_mmio(sbd, &s->iomem);
497     sysbus_init_irq(sbd, &s->irq);
498     qdev_init_gpio_in(dev, pl061_set_irq, N_GPIOS);
499     qdev_init_gpio_out(dev, s->out, N_GPIOS);
500 }
501 
502 static void pl061_class_init(ObjectClass *klass, void *data)
503 {
504     DeviceClass *dc = DEVICE_CLASS(klass);
505 
506     dc->vmsd = &vmstate_pl061;
507     dc->reset = &pl061_reset;
508 }
509 
510 static const TypeInfo pl061_info = {
511     .name          = TYPE_PL061,
512     .parent        = TYPE_SYS_BUS_DEVICE,
513     .instance_size = sizeof(PL061State),
514     .instance_init = pl061_init,
515     .class_init    = pl061_class_init,
516 };
517 
518 static const TypeInfo pl061_luminary_info = {
519     .name          = "pl061_luminary",
520     .parent        = TYPE_PL061,
521     .instance_init = pl061_luminary_init,
522 };
523 
524 static void pl061_register_types(void)
525 {
526     type_register_static(&pl061_info);
527     type_register_static(&pl061_luminary_info);
528 }
529 
530 type_init(pl061_register_types)
531