qemu
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
43static const uint8_t pl061_id[12] =44{ 0x00, 0x00, 0x00, 0x00, 0x61, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };45static 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"49OBJECT_DECLARE_SIMPLE_TYPE(PL061State, PL061)50
51#define N_GPIOS 852
53struct PL061State {54SysBusDevice parent_obj;55
56MemoryRegion iomem;57uint32_t locked;58uint32_t data;59uint32_t old_out_data;60uint32_t old_in_data;61uint32_t dir;62uint32_t isense;63uint32_t ibe;64uint32_t iev;65uint32_t im;66uint32_t istate;67uint32_t afsel;68uint32_t dr2r;69uint32_t dr4r;70uint32_t dr8r;71uint32_t odr;72uint32_t pur;73uint32_t pdr;74uint32_t slr;75uint32_t den;76uint32_t cr;77uint32_t amsel;78qemu_irq irq;79qemu_irq out[N_GPIOS];80const unsigned char *id;81/* Properties, for non-Luminary PL061 */82uint32_t pullups;83uint32_t pulldowns;84};85
86static const VMStateDescription vmstate_pl061 = {87.name = "pl061",88.version_id = 4,89.minimum_version_id = 4,90.fields = (const VMStateField[]) {91VMSTATE_UINT32(locked, PL061State),92VMSTATE_UINT32(data, PL061State),93VMSTATE_UINT32(old_out_data, PL061State),94VMSTATE_UINT32(old_in_data, PL061State),95VMSTATE_UINT32(dir, PL061State),96VMSTATE_UINT32(isense, PL061State),97VMSTATE_UINT32(ibe, PL061State),98VMSTATE_UINT32(iev, PL061State),99VMSTATE_UINT32(im, PL061State),100VMSTATE_UINT32(istate, PL061State),101VMSTATE_UINT32(afsel, PL061State),102VMSTATE_UINT32(dr2r, PL061State),103VMSTATE_UINT32(dr4r, PL061State),104VMSTATE_UINT32(dr8r, PL061State),105VMSTATE_UINT32(odr, PL061State),106VMSTATE_UINT32(pur, PL061State),107VMSTATE_UINT32(pdr, PL061State),108VMSTATE_UINT32(slr, PL061State),109VMSTATE_UINT32(den, PL061State),110VMSTATE_UINT32(cr, PL061State),111VMSTATE_UINT32_V(amsel, PL061State, 2),112VMSTATE_END_OF_LIST()113}114};115
116static 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*/
122uint8_t floating;123
124if (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*/
129floating = ~(s->pur | s->pdr);130} else {131floating = ~(s->pullups | s->pulldowns);132}133return floating & ~s->dir;134}
135
136static 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*/
142uint8_t pullups;143
144if (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*/
150pullups = s->pur;151} else {152pullups = s->pullups;153}154return pullups & ~s->dir;155}
156
157static void pl061_update(PL061State *s)158{
159uint8_t changed;160uint8_t mask;161uint8_t out;162int i;163uint8_t pullups = pl061_pullups(s);164uint8_t floating = pl061_floating(s);165
166trace_pl061_update(DEVICE(s)->canonical_path, s->dir, s->data,167pullups, 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*/
175out = (s->data & s->dir) | pullups | (s->old_out_data & floating);176changed = s->old_out_data ^ out;177if (changed) {178s->old_out_data = out;179for (i = 0; i < N_GPIOS; i++) {180mask = 1 << i;181if (changed & mask) {182int level = (out & mask) != 0;183trace_pl061_set_output(DEVICE(s)->canonical_path, i, level);184qemu_set_irq(s->out[i], level);185}186}187}188
189/* Inputs */190changed = (s->old_in_data ^ s->data) & ~s->dir;191if (changed) {192s->old_in_data = s->data;193for (i = 0; i < N_GPIOS; i++) {194mask = 1 << i;195if (changed & mask) {196trace_pl061_input_change(DEVICE(s)->canonical_path, i,197(s->data & mask) != 0);198
199if (!(s->isense & mask)) {200/* Edge interrupt */201if (s->ibe & mask) {202/* Any edge triggers the interrupt */203s->istate |= mask;204} else {205/* Edge is selected by IEV */206s->istate |= ~(s->data ^ s->iev) & mask;207}208}209}210}211}212
213/* Level interrupt */214s->istate |= ~(s->data ^ s->iev) & s->isense;215
216trace_pl061_update_istate(DEVICE(s)->canonical_path,217s->istate, s->im, (s->istate & s->im) != 0);218
219qemu_set_irq(s->irq, (s->istate & s->im) != 0);220}
221
222static uint64_t pl061_read(void *opaque, hwaddr offset,223unsigned size)224{
225PL061State *s = (PL061State *)opaque;226uint64_t r = 0;227
228switch (offset) {229case 0x0 ... 0x3ff: /* Data */230r = s->data & (offset >> 2);231break;232case 0x400: /* Direction */233r = s->dir;234break;235case 0x404: /* Interrupt sense */236r = s->isense;237break;238case 0x408: /* Interrupt both edges */239r = s->ibe;240break;241case 0x40c: /* Interrupt event */242r = s->iev;243break;244case 0x410: /* Interrupt mask */245r = s->im;246break;247case 0x414: /* Raw interrupt status */248r = s->istate;249break;250case 0x418: /* Masked interrupt status */251r = s->istate & s->im;252break;253case 0x420: /* Alternate function select */254r = s->afsel;255break;256case 0x500: /* 2mA drive */257if (s->id != pl061_id_luminary) {258goto bad_offset;259}260r = s->dr2r;261break;262case 0x504: /* 4mA drive */263if (s->id != pl061_id_luminary) {264goto bad_offset;265}266r = s->dr4r;267break;268case 0x508: /* 8mA drive */269if (s->id != pl061_id_luminary) {270goto bad_offset;271}272r = s->dr8r;273break;274case 0x50c: /* Open drain */275if (s->id != pl061_id_luminary) {276goto bad_offset;277}278r = s->odr;279break;280case 0x510: /* Pull-up */281if (s->id != pl061_id_luminary) {282goto bad_offset;283}284r = s->pur;285break;286case 0x514: /* Pull-down */287if (s->id != pl061_id_luminary) {288goto bad_offset;289}290r = s->pdr;291break;292case 0x518: /* Slew rate control */293if (s->id != pl061_id_luminary) {294goto bad_offset;295}296r = s->slr;297break;298case 0x51c: /* Digital enable */299if (s->id != pl061_id_luminary) {300goto bad_offset;301}302r = s->den;303break;304case 0x520: /* Lock */305if (s->id != pl061_id_luminary) {306goto bad_offset;307}308r = s->locked;309break;310case 0x524: /* Commit */311if (s->id != pl061_id_luminary) {312goto bad_offset;313}314r = s->cr;315break;316case 0x528: /* Analog mode select */317if (s->id != pl061_id_luminary) {318goto bad_offset;319}320r = s->amsel;321break;322case 0xfd0 ... 0xfff: /* ID registers */323r = s->id[(offset - 0xfd0) >> 2];324break;325default:326bad_offset:327qemu_log_mask(LOG_GUEST_ERROR,328"pl061_read: Bad offset %x\n", (int)offset);329break;330}331
332trace_pl061_read(DEVICE(s)->canonical_path, offset, r);333return r;334}
335
336static void pl061_write(void *opaque, hwaddr offset,337uint64_t value, unsigned size)338{
339PL061State *s = (PL061State *)opaque;340uint8_t mask;341
342trace_pl061_write(DEVICE(s)->canonical_path, offset, value);343
344switch (offset) {345case 0 ... 0x3ff:346mask = (offset >> 2) & s->dir;347s->data = (s->data & ~mask) | (value & mask);348pl061_update(s);349return;350case 0x400: /* Direction */351s->dir = value & 0xff;352break;353case 0x404: /* Interrupt sense */354s->isense = value & 0xff;355break;356case 0x408: /* Interrupt both edges */357s->ibe = value & 0xff;358break;359case 0x40c: /* Interrupt event */360s->iev = value & 0xff;361break;362case 0x410: /* Interrupt mask */363s->im = value & 0xff;364break;365case 0x41c: /* Interrupt clear */366s->istate &= ~value;367break;368case 0x420: /* Alternate function select */369mask = s->cr;370s->afsel = (s->afsel & ~mask) | (value & mask);371break;372case 0x500: /* 2mA drive */373if (s->id != pl061_id_luminary) {374goto bad_offset;375}376s->dr2r = value & 0xff;377break;378case 0x504: /* 4mA drive */379if (s->id != pl061_id_luminary) {380goto bad_offset;381}382s->dr4r = value & 0xff;383break;384case 0x508: /* 8mA drive */385if (s->id != pl061_id_luminary) {386goto bad_offset;387}388s->dr8r = value & 0xff;389break;390case 0x50c: /* Open drain */391if (s->id != pl061_id_luminary) {392goto bad_offset;393}394s->odr = value & 0xff;395break;396case 0x510: /* Pull-up */397if (s->id != pl061_id_luminary) {398goto bad_offset;399}400s->pur = value & 0xff;401break;402case 0x514: /* Pull-down */403if (s->id != pl061_id_luminary) {404goto bad_offset;405}406s->pdr = value & 0xff;407break;408case 0x518: /* Slew rate control */409if (s->id != pl061_id_luminary) {410goto bad_offset;411}412s->slr = value & 0xff;413break;414case 0x51c: /* Digital enable */415if (s->id != pl061_id_luminary) {416goto bad_offset;417}418s->den = value & 0xff;419break;420case 0x520: /* Lock */421if (s->id != pl061_id_luminary) {422goto bad_offset;423}424s->locked = (value != 0xacce551);425break;426case 0x524: /* Commit */427if (s->id != pl061_id_luminary) {428goto bad_offset;429}430if (!s->locked)431s->cr = value & 0xff;432break;433case 0x528:434if (s->id != pl061_id_luminary) {435goto bad_offset;436}437s->amsel = value & 0xff;438break;439default:440bad_offset:441qemu_log_mask(LOG_GUEST_ERROR,442"pl061_write: Bad offset %x\n", (int)offset);443return;444}445pl061_update(s);446return;447}
448
449static void pl061_enter_reset(Object *obj, ResetType type)450{
451PL061State *s = PL061(obj);452
453trace_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*/
465s->data = 0;466s->old_in_data = 0;467s->dir = 0;468s->isense = 0;469s->ibe = 0;470s->iev = 0;471s->im = 0;472s->istate = 0;473s->afsel = 0;474s->dr2r = 0xff;475s->dr4r = 0;476s->dr8r = 0;477s->odr = 0;478s->pur = 0;479s->pdr = 0;480s->slr = 0;481s->den = 0;482s->locked = 1;483s->cr = 0xff;484s->amsel = 0;485}
486
487static void pl061_hold_reset(Object *obj, ResetType type)488{
489PL061State *s = PL061(obj);490int i, level;491uint8_t floating = pl061_floating(s);492uint8_t pullups = pl061_pullups(s);493
494for (i = 0; i < N_GPIOS; i++) {495if (extract32(floating, i, 1)) {496continue;497}498level = extract32(pullups, i, 1);499trace_pl061_set_output(DEVICE(s)->canonical_path, i, level);500qemu_set_irq(s->out[i], level);501}502s->old_out_data = pullups;503}
504
505static void pl061_set_irq(void * opaque, int irq, int level)506{
507PL061State *s = (PL061State *)opaque;508uint8_t mask;509
510mask = 1 << irq;511if ((s->dir & mask) == 0) {512s->data &= ~mask;513if (level)514s->data |= mask;515pl061_update(s);516}517}
518
519static const MemoryRegionOps pl061_ops = {520.read = pl061_read,521.write = pl061_write,522.endianness = DEVICE_NATIVE_ENDIAN,523};524
525static void pl061_luminary_init(Object *obj)526{
527PL061State *s = PL061(obj);528
529s->id = pl061_id_luminary;530}
531
532static void pl061_init(Object *obj)533{
534PL061State *s = PL061(obj);535DeviceState *dev = DEVICE(obj);536SysBusDevice *sbd = SYS_BUS_DEVICE(obj);537
538s->id = pl061_id;539
540memory_region_init_io(&s->iomem, obj, &pl061_ops, s, "pl061", 0x1000);541sysbus_init_mmio(sbd, &s->iomem);542sysbus_init_irq(sbd, &s->irq);543qdev_init_gpio_in(dev, pl061_set_irq, N_GPIOS);544qdev_init_gpio_out(dev, s->out, N_GPIOS);545}
546
547static void pl061_realize(DeviceState *dev, Error **errp)548{
549PL061State *s = PL061(dev);550
551if (s->pullups > 0xff) {552error_setg(errp, "pullups property must be between 0 and 0xff");553return;554}555if (s->pulldowns > 0xff) {556error_setg(errp, "pulldowns property must be between 0 and 0xff");557return;558}559if (s->pullups & s->pulldowns) {560error_setg(errp, "no bit may be set both in pullups and pulldowns");561return;562}563}
564
565static Property pl061_props[] = {566DEFINE_PROP_UINT32("pullups", PL061State, pullups, 0xff),567DEFINE_PROP_UINT32("pulldowns", PL061State, pulldowns, 0x0),568DEFINE_PROP_END_OF_LIST()569};570
571static void pl061_class_init(ObjectClass *klass, void *data)572{
573DeviceClass *dc = DEVICE_CLASS(klass);574ResettableClass *rc = RESETTABLE_CLASS(klass);575
576dc->vmsd = &vmstate_pl061;577dc->realize = pl061_realize;578device_class_set_props(dc, pl061_props);579rc->phases.enter = pl061_enter_reset;580rc->phases.hold = pl061_hold_reset;581}
582
583static 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
591static const TypeInfo pl061_luminary_info = {592.name = "pl061_luminary",593.parent = TYPE_PL061,594.instance_init = pl061_luminary_init,595};596
597static void pl061_register_types(void)598{
599type_register_static(&pl061_info);600type_register_static(&pl061_luminary_info);601}
602
603type_init(pl061_register_types)604