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