]>
Commit | Line | Data |
---|---|---|
9ee6e8bb PB |
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 | * | |
8e31bf38 | 8 | * This code is licensed under the GPL. |
9ee6e8bb PB |
9 | */ |
10 | ||
8ef94f0b | 11 | #include "qemu/osdep.h" |
64552b6b | 12 | #include "hw/irq.h" |
83c9f4ca | 13 | #include "hw/sysbus.h" |
d6454270 | 14 | #include "migration/vmstate.h" |
03dd024f | 15 | #include "qemu/log.h" |
0b8fa32f | 16 | #include "qemu/module.h" |
db1015e9 | 17 | #include "qom/object.h" |
9ee6e8bb PB |
18 | |
19 | //#define DEBUG_PL061 1 | |
20 | ||
21 | #ifdef DEBUG_PL061 | |
001faf32 BS |
22 | #define DPRINTF(fmt, ...) \ |
23 | do { printf("pl061: " fmt , ## __VA_ARGS__); } while (0) | |
24 | #define BADF(fmt, ...) \ | |
25 | do { fprintf(stderr, "pl061: error: " fmt , ## __VA_ARGS__); exit(1);} while (0) | |
9ee6e8bb | 26 | #else |
001faf32 BS |
27 | #define DPRINTF(fmt, ...) do {} while(0) |
28 | #define BADF(fmt, ...) \ | |
29 | do { fprintf(stderr, "pl061: error: " fmt , ## __VA_ARGS__);} while (0) | |
9ee6e8bb PB |
30 | #endif |
31 | ||
32 | static const uint8_t pl061_id[12] = | |
7063f49f PM |
33 | { 0x00, 0x00, 0x00, 0x00, 0x61, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; |
34 | static const uint8_t pl061_id_luminary[12] = | |
9ee6e8bb PB |
35 | { 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1 }; |
36 | ||
692a76d1 | 37 | #define TYPE_PL061 "pl061" |
db1015e9 | 38 | typedef struct PL061State PL061State; |
692a76d1 AF |
39 | #define PL061(obj) OBJECT_CHECK(PL061State, (obj), TYPE_PL061) |
40 | ||
faf58e53 GU |
41 | #define N_GPIOS 8 |
42 | ||
db1015e9 | 43 | struct PL061State { |
692a76d1 AF |
44 | SysBusDevice parent_obj; |
45 | ||
3cf89f8a | 46 | MemoryRegion iomem; |
a35faa94 PM |
47 | uint32_t locked; |
48 | uint32_t data; | |
bfb27e60 CL |
49 | uint32_t old_out_data; |
50 | uint32_t old_in_data; | |
a35faa94 PM |
51 | uint32_t dir; |
52 | uint32_t isense; | |
53 | uint32_t ibe; | |
54 | uint32_t iev; | |
55 | uint32_t im; | |
56 | uint32_t istate; | |
57 | uint32_t afsel; | |
58 | uint32_t dr2r; | |
59 | uint32_t dr4r; | |
60 | uint32_t dr8r; | |
61 | uint32_t odr; | |
62 | uint32_t pur; | |
63 | uint32_t pdr; | |
64 | uint32_t slr; | |
65 | uint32_t den; | |
66 | uint32_t cr; | |
b3aaff11 | 67 | uint32_t amsel; |
9ee6e8bb | 68 | qemu_irq irq; |
faf58e53 | 69 | qemu_irq out[N_GPIOS]; |
7063f49f | 70 | const unsigned char *id; |
09aa3bf3 | 71 | uint32_t rsvd_start; /* reserved area: [rsvd_start, 0xfcc] */ |
db1015e9 | 72 | }; |
9ee6e8bb | 73 | |
a35faa94 PM |
74 | static const VMStateDescription vmstate_pl061 = { |
75 | .name = "pl061", | |
c3a86b35 WH |
76 | .version_id = 4, |
77 | .minimum_version_id = 4, | |
a35faa94 | 78 | .fields = (VMStateField[]) { |
ee663e96 AF |
79 | VMSTATE_UINT32(locked, PL061State), |
80 | VMSTATE_UINT32(data, PL061State), | |
bfb27e60 CL |
81 | VMSTATE_UINT32(old_out_data, PL061State), |
82 | VMSTATE_UINT32(old_in_data, PL061State), | |
ee663e96 AF |
83 | VMSTATE_UINT32(dir, PL061State), |
84 | VMSTATE_UINT32(isense, PL061State), | |
85 | VMSTATE_UINT32(ibe, PL061State), | |
86 | VMSTATE_UINT32(iev, PL061State), | |
87 | VMSTATE_UINT32(im, PL061State), | |
88 | VMSTATE_UINT32(istate, PL061State), | |
89 | VMSTATE_UINT32(afsel, PL061State), | |
90 | VMSTATE_UINT32(dr2r, PL061State), | |
91 | VMSTATE_UINT32(dr4r, PL061State), | |
92 | VMSTATE_UINT32(dr8r, PL061State), | |
93 | VMSTATE_UINT32(odr, PL061State), | |
94 | VMSTATE_UINT32(pur, PL061State), | |
95 | VMSTATE_UINT32(pdr, PL061State), | |
96 | VMSTATE_UINT32(slr, PL061State), | |
97 | VMSTATE_UINT32(den, PL061State), | |
98 | VMSTATE_UINT32(cr, PL061State), | |
ee663e96 | 99 | VMSTATE_UINT32_V(amsel, PL061State, 2), |
a35faa94 PM |
100 | VMSTATE_END_OF_LIST() |
101 | } | |
102 | }; | |
103 | ||
ee663e96 | 104 | static void pl061_update(PL061State *s) |
9ee6e8bb PB |
105 | { |
106 | uint8_t changed; | |
107 | uint8_t mask; | |
775616c3 | 108 | uint8_t out; |
9ee6e8bb PB |
109 | int i; |
110 | ||
bfb27e60 CL |
111 | DPRINTF("dir = %d, data = %d\n", s->dir, s->data); |
112 | ||
775616c3 PB |
113 | /* Outputs float high. */ |
114 | /* FIXME: This is board dependent. */ | |
115 | out = (s->data & s->dir) | ~s->dir; | |
bfb27e60 CL |
116 | changed = s->old_out_data ^ out; |
117 | if (changed) { | |
118 | s->old_out_data = out; | |
faf58e53 | 119 | for (i = 0; i < N_GPIOS; i++) { |
bfb27e60 CL |
120 | mask = 1 << i; |
121 | if (changed & mask) { | |
122 | DPRINTF("Set output %d = %d\n", i, (out & mask) != 0); | |
123 | qemu_set_irq(s->out[i], (out & mask) != 0); | |
124 | } | |
9ee6e8bb PB |
125 | } |
126 | } | |
127 | ||
bfb27e60 CL |
128 | /* Inputs */ |
129 | changed = (s->old_in_data ^ s->data) & ~s->dir; | |
130 | if (changed) { | |
131 | s->old_in_data = s->data; | |
faf58e53 | 132 | for (i = 0; i < N_GPIOS; i++) { |
bfb27e60 CL |
133 | mask = 1 << i; |
134 | if (changed & mask) { | |
135 | DPRINTF("Changed input %d = %d\n", i, (s->data & mask) != 0); | |
136 | ||
137 | if (!(s->isense & mask)) { | |
138 | /* Edge interrupt */ | |
139 | if (s->ibe & mask) { | |
140 | /* Any edge triggers the interrupt */ | |
141 | s->istate |= mask; | |
142 | } else { | |
143 | /* Edge is selected by IEV */ | |
144 | s->istate |= ~(s->data ^ s->iev) & mask; | |
145 | } | |
146 | } | |
147 | } | |
148 | } | |
149 | } | |
150 | ||
151 | /* Level interrupt */ | |
152 | s->istate |= ~(s->data ^ s->iev) & s->isense; | |
153 | ||
154 | DPRINTF("istate = %02X\n", s->istate); | |
155 | ||
156 | qemu_set_irq(s->irq, (s->istate & s->im) != 0); | |
9ee6e8bb PB |
157 | } |
158 | ||
a8170e5e | 159 | static uint64_t pl061_read(void *opaque, hwaddr offset, |
3cf89f8a | 160 | unsigned size) |
9ee6e8bb | 161 | { |
ee663e96 | 162 | PL061State *s = (PL061State *)opaque; |
9ee6e8bb | 163 | |
9ee6e8bb PB |
164 | if (offset < 0x400) { |
165 | return s->data & (offset >> 2); | |
166 | } | |
09aa3bf3 WH |
167 | if (offset >= s->rsvd_start && offset <= 0xfcc) { |
168 | goto err_out; | |
169 | } | |
170 | if (offset >= 0xfd0 && offset < 0x1000) { | |
171 | return s->id[(offset - 0xfd0) >> 2]; | |
172 | } | |
9ee6e8bb PB |
173 | switch (offset) { |
174 | case 0x400: /* Direction */ | |
175 | return s->dir; | |
176 | case 0x404: /* Interrupt sense */ | |
177 | return s->isense; | |
178 | case 0x408: /* Interrupt both edges */ | |
179 | return s->ibe; | |
ff2712ba | 180 | case 0x40c: /* Interrupt event */ |
9ee6e8bb PB |
181 | return s->iev; |
182 | case 0x410: /* Interrupt mask */ | |
183 | return s->im; | |
184 | case 0x414: /* Raw interrupt status */ | |
185 | return s->istate; | |
186 | case 0x418: /* Masked interrupt status */ | |
0b2ff2ce | 187 | return s->istate & s->im; |
9ee6e8bb PB |
188 | case 0x420: /* Alternate function select */ |
189 | return s->afsel; | |
190 | case 0x500: /* 2mA drive */ | |
191 | return s->dr2r; | |
192 | case 0x504: /* 4mA drive */ | |
193 | return s->dr4r; | |
194 | case 0x508: /* 8mA drive */ | |
195 | return s->dr8r; | |
196 | case 0x50c: /* Open drain */ | |
197 | return s->odr; | |
198 | case 0x510: /* Pull-up */ | |
199 | return s->pur; | |
200 | case 0x514: /* Pull-down */ | |
201 | return s->pdr; | |
202 | case 0x518: /* Slew rate control */ | |
203 | return s->slr; | |
204 | case 0x51c: /* Digital enable */ | |
205 | return s->den; | |
206 | case 0x520: /* Lock */ | |
207 | return s->locked; | |
208 | case 0x524: /* Commit */ | |
209 | return s->cr; | |
b3aaff11 PM |
210 | case 0x528: /* Analog mode select */ |
211 | return s->amsel; | |
9ee6e8bb | 212 | default: |
09aa3bf3 | 213 | break; |
9ee6e8bb | 214 | } |
09aa3bf3 WH |
215 | err_out: |
216 | qemu_log_mask(LOG_GUEST_ERROR, | |
217 | "pl061_read: Bad offset %x\n", (int)offset); | |
218 | return 0; | |
9ee6e8bb PB |
219 | } |
220 | ||
a8170e5e | 221 | static void pl061_write(void *opaque, hwaddr offset, |
3cf89f8a | 222 | uint64_t value, unsigned size) |
9ee6e8bb | 223 | { |
ee663e96 | 224 | PL061State *s = (PL061State *)opaque; |
9ee6e8bb PB |
225 | uint8_t mask; |
226 | ||
9ee6e8bb PB |
227 | if (offset < 0x400) { |
228 | mask = (offset >> 2) & s->dir; | |
229 | s->data = (s->data & ~mask) | (value & mask); | |
230 | pl061_update(s); | |
231 | return; | |
232 | } | |
09aa3bf3 WH |
233 | if (offset >= s->rsvd_start) { |
234 | goto err_out; | |
235 | } | |
9ee6e8bb PB |
236 | switch (offset) { |
237 | case 0x400: /* Direction */ | |
a35faa94 | 238 | s->dir = value & 0xff; |
9ee6e8bb PB |
239 | break; |
240 | case 0x404: /* Interrupt sense */ | |
a35faa94 | 241 | s->isense = value & 0xff; |
9ee6e8bb PB |
242 | break; |
243 | case 0x408: /* Interrupt both edges */ | |
a35faa94 | 244 | s->ibe = value & 0xff; |
9ee6e8bb | 245 | break; |
ff2712ba | 246 | case 0x40c: /* Interrupt event */ |
a35faa94 | 247 | s->iev = value & 0xff; |
9ee6e8bb PB |
248 | break; |
249 | case 0x410: /* Interrupt mask */ | |
a35faa94 | 250 | s->im = value & 0xff; |
9ee6e8bb PB |
251 | break; |
252 | case 0x41c: /* Interrupt clear */ | |
253 | s->istate &= ~value; | |
254 | break; | |
255 | case 0x420: /* Alternate function select */ | |
256 | mask = s->cr; | |
257 | s->afsel = (s->afsel & ~mask) | (value & mask); | |
258 | break; | |
259 | case 0x500: /* 2mA drive */ | |
a35faa94 | 260 | s->dr2r = value & 0xff; |
9ee6e8bb PB |
261 | break; |
262 | case 0x504: /* 4mA drive */ | |
a35faa94 | 263 | s->dr4r = value & 0xff; |
9ee6e8bb PB |
264 | break; |
265 | case 0x508: /* 8mA drive */ | |
a35faa94 | 266 | s->dr8r = value & 0xff; |
9ee6e8bb PB |
267 | break; |
268 | case 0x50c: /* Open drain */ | |
a35faa94 | 269 | s->odr = value & 0xff; |
9ee6e8bb PB |
270 | break; |
271 | case 0x510: /* Pull-up */ | |
a35faa94 | 272 | s->pur = value & 0xff; |
9ee6e8bb PB |
273 | break; |
274 | case 0x514: /* Pull-down */ | |
a35faa94 | 275 | s->pdr = value & 0xff; |
9ee6e8bb PB |
276 | break; |
277 | case 0x518: /* Slew rate control */ | |
a35faa94 | 278 | s->slr = value & 0xff; |
9ee6e8bb PB |
279 | break; |
280 | case 0x51c: /* Digital enable */ | |
a35faa94 | 281 | s->den = value & 0xff; |
9ee6e8bb PB |
282 | break; |
283 | case 0x520: /* Lock */ | |
284 | s->locked = (value != 0xacce551); | |
285 | break; | |
286 | case 0x524: /* Commit */ | |
287 | if (!s->locked) | |
a35faa94 | 288 | s->cr = value & 0xff; |
9ee6e8bb | 289 | break; |
b3aaff11 PM |
290 | case 0x528: |
291 | s->amsel = value & 0xff; | |
292 | break; | |
9ee6e8bb | 293 | default: |
09aa3bf3 | 294 | goto err_out; |
9ee6e8bb PB |
295 | } |
296 | pl061_update(s); | |
09aa3bf3 WH |
297 | return; |
298 | err_out: | |
299 | qemu_log_mask(LOG_GUEST_ERROR, | |
300 | "pl061_write: Bad offset %x\n", (int)offset); | |
9ee6e8bb PB |
301 | } |
302 | ||
b527db44 | 303 | static void pl061_reset(DeviceState *dev) |
9ee6e8bb | 304 | { |
b527db44 WH |
305 | PL061State *s = PL061(dev); |
306 | ||
307 | /* reset values from PL061 TRM, Stellaris LM3S5P31 & LM3S8962 Data Sheet */ | |
308 | s->data = 0; | |
309 | s->old_out_data = 0; | |
310 | s->old_in_data = 0; | |
311 | s->dir = 0; | |
312 | s->isense = 0; | |
313 | s->ibe = 0; | |
314 | s->iev = 0; | |
315 | s->im = 0; | |
316 | s->istate = 0; | |
317 | s->afsel = 0; | |
318 | s->dr2r = 0xff; | |
319 | s->dr4r = 0; | |
320 | s->dr8r = 0; | |
321 | s->odr = 0; | |
322 | s->pur = 0; | |
323 | s->pdr = 0; | |
324 | s->slr = 0; | |
325 | s->den = 0; | |
326 | s->locked = 1; | |
327 | s->cr = 0xff; | |
328 | s->amsel = 0; | |
9ee6e8bb PB |
329 | } |
330 | ||
9596ebb7 | 331 | static void pl061_set_irq(void * opaque, int irq, int level) |
9ee6e8bb | 332 | { |
ee663e96 | 333 | PL061State *s = (PL061State *)opaque; |
9ee6e8bb PB |
334 | uint8_t mask; |
335 | ||
336 | mask = 1 << irq; | |
337 | if ((s->dir & mask) == 0) { | |
338 | s->data &= ~mask; | |
339 | if (level) | |
340 | s->data |= mask; | |
341 | pl061_update(s); | |
342 | } | |
343 | } | |
344 | ||
3cf89f8a AK |
345 | static const MemoryRegionOps pl061_ops = { |
346 | .read = pl061_read, | |
347 | .write = pl061_write, | |
348 | .endianness = DEVICE_NATIVE_ENDIAN, | |
9ee6e8bb PB |
349 | }; |
350 | ||
692a76d1 | 351 | static void pl061_luminary_init(Object *obj) |
7063f49f | 352 | { |
692a76d1 AF |
353 | PL061State *s = PL061(obj); |
354 | ||
355 | s->id = pl061_id_luminary; | |
09aa3bf3 | 356 | s->rsvd_start = 0x52c; |
7063f49f PM |
357 | } |
358 | ||
692a76d1 | 359 | static void pl061_init(Object *obj) |
7063f49f | 360 | { |
692a76d1 | 361 | PL061State *s = PL061(obj); |
09e6fb3e XZ |
362 | DeviceState *dev = DEVICE(obj); |
363 | SysBusDevice *sbd = SYS_BUS_DEVICE(obj); | |
692a76d1 AF |
364 | |
365 | s->id = pl061_id; | |
09aa3bf3 | 366 | s->rsvd_start = 0x424; |
09e6fb3e XZ |
367 | |
368 | memory_region_init_io(&s->iomem, obj, &pl061_ops, s, "pl061", 0x1000); | |
369 | sysbus_init_mmio(sbd, &s->iomem); | |
370 | sysbus_init_irq(sbd, &s->irq); | |
faf58e53 GU |
371 | qdev_init_gpio_in(dev, pl061_set_irq, N_GPIOS); |
372 | qdev_init_gpio_out(dev, s->out, N_GPIOS); | |
7063f49f PM |
373 | } |
374 | ||
999e12bb AL |
375 | static void pl061_class_init(ObjectClass *klass, void *data) |
376 | { | |
39bffca2 | 377 | DeviceClass *dc = DEVICE_CLASS(klass); |
999e12bb | 378 | |
39bffca2 | 379 | dc->vmsd = &vmstate_pl061; |
b527db44 | 380 | dc->reset = &pl061_reset; |
999e12bb AL |
381 | } |
382 | ||
8c43a6f0 | 383 | static const TypeInfo pl061_info = { |
692a76d1 | 384 | .name = TYPE_PL061, |
39bffca2 | 385 | .parent = TYPE_SYS_BUS_DEVICE, |
ee663e96 | 386 | .instance_size = sizeof(PL061State), |
692a76d1 | 387 | .instance_init = pl061_init, |
39bffca2 | 388 | .class_init = pl061_class_init, |
a35faa94 PM |
389 | }; |
390 | ||
8c43a6f0 | 391 | static const TypeInfo pl061_luminary_info = { |
39bffca2 | 392 | .name = "pl061_luminary", |
692a76d1 AF |
393 | .parent = TYPE_PL061, |
394 | .instance_init = pl061_luminary_init, | |
a35faa94 PM |
395 | }; |
396 | ||
83f7d43a | 397 | static void pl061_register_types(void) |
40905a6a | 398 | { |
39bffca2 AL |
399 | type_register_static(&pl061_info); |
400 | type_register_static(&pl061_luminary_info); | |
40905a6a PB |
401 | } |
402 | ||
83f7d43a | 403 | type_init(pl061_register_types) |