]>
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 | ||
40905a6a | 11 | #include "sysbus.h" |
9ee6e8bb PB |
12 | |
13 | //#define DEBUG_PL061 1 | |
14 | ||
15 | #ifdef DEBUG_PL061 | |
001faf32 BS |
16 | #define DPRINTF(fmt, ...) \ |
17 | do { printf("pl061: " fmt , ## __VA_ARGS__); } while (0) | |
18 | #define BADF(fmt, ...) \ | |
19 | do { fprintf(stderr, "pl061: error: " fmt , ## __VA_ARGS__); exit(1);} while (0) | |
9ee6e8bb | 20 | #else |
001faf32 BS |
21 | #define DPRINTF(fmt, ...) do {} while(0) |
22 | #define BADF(fmt, ...) \ | |
23 | do { fprintf(stderr, "pl061: error: " fmt , ## __VA_ARGS__);} while (0) | |
9ee6e8bb PB |
24 | #endif |
25 | ||
26 | static const uint8_t pl061_id[12] = | |
7063f49f PM |
27 | { 0x00, 0x00, 0x00, 0x00, 0x61, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; |
28 | static const uint8_t pl061_id_luminary[12] = | |
9ee6e8bb PB |
29 | { 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1 }; |
30 | ||
31 | typedef struct { | |
40905a6a | 32 | SysBusDevice busdev; |
3cf89f8a | 33 | MemoryRegion iomem; |
a35faa94 PM |
34 | uint32_t locked; |
35 | uint32_t data; | |
36 | uint32_t old_data; | |
37 | uint32_t dir; | |
38 | uint32_t isense; | |
39 | uint32_t ibe; | |
40 | uint32_t iev; | |
41 | uint32_t im; | |
42 | uint32_t istate; | |
43 | uint32_t afsel; | |
44 | uint32_t dr2r; | |
45 | uint32_t dr4r; | |
46 | uint32_t dr8r; | |
47 | uint32_t odr; | |
48 | uint32_t pur; | |
49 | uint32_t pdr; | |
50 | uint32_t slr; | |
51 | uint32_t den; | |
52 | uint32_t cr; | |
53 | uint32_t float_high; | |
b3aaff11 | 54 | uint32_t amsel; |
9ee6e8bb PB |
55 | qemu_irq irq; |
56 | qemu_irq out[8]; | |
7063f49f | 57 | const unsigned char *id; |
9ee6e8bb PB |
58 | } pl061_state; |
59 | ||
a35faa94 PM |
60 | static const VMStateDescription vmstate_pl061 = { |
61 | .name = "pl061", | |
b3aaff11 | 62 | .version_id = 2, |
a35faa94 PM |
63 | .minimum_version_id = 1, |
64 | .fields = (VMStateField[]) { | |
65 | VMSTATE_UINT32(locked, pl061_state), | |
66 | VMSTATE_UINT32(data, pl061_state), | |
67 | VMSTATE_UINT32(old_data, pl061_state), | |
68 | VMSTATE_UINT32(dir, pl061_state), | |
69 | VMSTATE_UINT32(isense, pl061_state), | |
70 | VMSTATE_UINT32(ibe, pl061_state), | |
71 | VMSTATE_UINT32(iev, pl061_state), | |
72 | VMSTATE_UINT32(im, pl061_state), | |
73 | VMSTATE_UINT32(istate, pl061_state), | |
74 | VMSTATE_UINT32(afsel, pl061_state), | |
75 | VMSTATE_UINT32(dr2r, pl061_state), | |
76 | VMSTATE_UINT32(dr4r, pl061_state), | |
77 | VMSTATE_UINT32(dr8r, pl061_state), | |
78 | VMSTATE_UINT32(odr, pl061_state), | |
79 | VMSTATE_UINT32(pur, pl061_state), | |
80 | VMSTATE_UINT32(pdr, pl061_state), | |
81 | VMSTATE_UINT32(slr, pl061_state), | |
82 | VMSTATE_UINT32(den, pl061_state), | |
83 | VMSTATE_UINT32(cr, pl061_state), | |
84 | VMSTATE_UINT32(float_high, pl061_state), | |
b3aaff11 | 85 | VMSTATE_UINT32_V(amsel, pl061_state, 2), |
a35faa94 PM |
86 | VMSTATE_END_OF_LIST() |
87 | } | |
88 | }; | |
89 | ||
9ee6e8bb PB |
90 | static void pl061_update(pl061_state *s) |
91 | { | |
92 | uint8_t changed; | |
93 | uint8_t mask; | |
775616c3 | 94 | uint8_t out; |
9ee6e8bb PB |
95 | int i; |
96 | ||
775616c3 PB |
97 | /* Outputs float high. */ |
98 | /* FIXME: This is board dependent. */ | |
99 | out = (s->data & s->dir) | ~s->dir; | |
100 | changed = s->old_data ^ out; | |
9ee6e8bb PB |
101 | if (!changed) |
102 | return; | |
103 | ||
775616c3 | 104 | s->old_data = out; |
9ee6e8bb PB |
105 | for (i = 0; i < 8; i++) { |
106 | mask = 1 << i; | |
b78c2b3a | 107 | if (changed & mask) { |
775616c3 PB |
108 | DPRINTF("Set output %d = %d\n", i, (out & mask) != 0); |
109 | qemu_set_irq(s->out[i], (out & mask) != 0); | |
9ee6e8bb PB |
110 | } |
111 | } | |
112 | ||
113 | /* FIXME: Implement input interrupts. */ | |
114 | } | |
115 | ||
3cf89f8a AK |
116 | static uint64_t pl061_read(void *opaque, target_phys_addr_t offset, |
117 | unsigned size) | |
9ee6e8bb PB |
118 | { |
119 | pl061_state *s = (pl061_state *)opaque; | |
120 | ||
9ee6e8bb | 121 | if (offset >= 0xfd0 && offset < 0x1000) { |
7063f49f | 122 | return s->id[(offset - 0xfd0) >> 2]; |
9ee6e8bb PB |
123 | } |
124 | if (offset < 0x400) { | |
125 | return s->data & (offset >> 2); | |
126 | } | |
127 | switch (offset) { | |
128 | case 0x400: /* Direction */ | |
129 | return s->dir; | |
130 | case 0x404: /* Interrupt sense */ | |
131 | return s->isense; | |
132 | case 0x408: /* Interrupt both edges */ | |
133 | return s->ibe; | |
ff2712ba | 134 | case 0x40c: /* Interrupt event */ |
9ee6e8bb PB |
135 | return s->iev; |
136 | case 0x410: /* Interrupt mask */ | |
137 | return s->im; | |
138 | case 0x414: /* Raw interrupt status */ | |
139 | return s->istate; | |
140 | case 0x418: /* Masked interrupt status */ | |
141 | return s->istate | s->im; | |
142 | case 0x420: /* Alternate function select */ | |
143 | return s->afsel; | |
144 | case 0x500: /* 2mA drive */ | |
145 | return s->dr2r; | |
146 | case 0x504: /* 4mA drive */ | |
147 | return s->dr4r; | |
148 | case 0x508: /* 8mA drive */ | |
149 | return s->dr8r; | |
150 | case 0x50c: /* Open drain */ | |
151 | return s->odr; | |
152 | case 0x510: /* Pull-up */ | |
153 | return s->pur; | |
154 | case 0x514: /* Pull-down */ | |
155 | return s->pdr; | |
156 | case 0x518: /* Slew rate control */ | |
157 | return s->slr; | |
158 | case 0x51c: /* Digital enable */ | |
159 | return s->den; | |
160 | case 0x520: /* Lock */ | |
161 | return s->locked; | |
162 | case 0x524: /* Commit */ | |
163 | return s->cr; | |
b3aaff11 PM |
164 | case 0x528: /* Analog mode select */ |
165 | return s->amsel; | |
9ee6e8bb | 166 | default: |
2ac71179 | 167 | hw_error("pl061_read: Bad offset %x\n", (int)offset); |
9ee6e8bb PB |
168 | return 0; |
169 | } | |
170 | } | |
171 | ||
c227f099 | 172 | static void pl061_write(void *opaque, target_phys_addr_t offset, |
3cf89f8a | 173 | uint64_t value, unsigned size) |
9ee6e8bb PB |
174 | { |
175 | pl061_state *s = (pl061_state *)opaque; | |
176 | uint8_t mask; | |
177 | ||
9ee6e8bb PB |
178 | if (offset < 0x400) { |
179 | mask = (offset >> 2) & s->dir; | |
180 | s->data = (s->data & ~mask) | (value & mask); | |
181 | pl061_update(s); | |
182 | return; | |
183 | } | |
184 | switch (offset) { | |
185 | case 0x400: /* Direction */ | |
a35faa94 | 186 | s->dir = value & 0xff; |
9ee6e8bb PB |
187 | break; |
188 | case 0x404: /* Interrupt sense */ | |
a35faa94 | 189 | s->isense = value & 0xff; |
9ee6e8bb PB |
190 | break; |
191 | case 0x408: /* Interrupt both edges */ | |
a35faa94 | 192 | s->ibe = value & 0xff; |
9ee6e8bb | 193 | break; |
ff2712ba | 194 | case 0x40c: /* Interrupt event */ |
a35faa94 | 195 | s->iev = value & 0xff; |
9ee6e8bb PB |
196 | break; |
197 | case 0x410: /* Interrupt mask */ | |
a35faa94 | 198 | s->im = value & 0xff; |
9ee6e8bb PB |
199 | break; |
200 | case 0x41c: /* Interrupt clear */ | |
201 | s->istate &= ~value; | |
202 | break; | |
203 | case 0x420: /* Alternate function select */ | |
204 | mask = s->cr; | |
205 | s->afsel = (s->afsel & ~mask) | (value & mask); | |
206 | break; | |
207 | case 0x500: /* 2mA drive */ | |
a35faa94 | 208 | s->dr2r = value & 0xff; |
9ee6e8bb PB |
209 | break; |
210 | case 0x504: /* 4mA drive */ | |
a35faa94 | 211 | s->dr4r = value & 0xff; |
9ee6e8bb PB |
212 | break; |
213 | case 0x508: /* 8mA drive */ | |
a35faa94 | 214 | s->dr8r = value & 0xff; |
9ee6e8bb PB |
215 | break; |
216 | case 0x50c: /* Open drain */ | |
a35faa94 | 217 | s->odr = value & 0xff; |
9ee6e8bb PB |
218 | break; |
219 | case 0x510: /* Pull-up */ | |
a35faa94 | 220 | s->pur = value & 0xff; |
9ee6e8bb PB |
221 | break; |
222 | case 0x514: /* Pull-down */ | |
a35faa94 | 223 | s->pdr = value & 0xff; |
9ee6e8bb PB |
224 | break; |
225 | case 0x518: /* Slew rate control */ | |
a35faa94 | 226 | s->slr = value & 0xff; |
9ee6e8bb PB |
227 | break; |
228 | case 0x51c: /* Digital enable */ | |
a35faa94 | 229 | s->den = value & 0xff; |
9ee6e8bb PB |
230 | break; |
231 | case 0x520: /* Lock */ | |
232 | s->locked = (value != 0xacce551); | |
233 | break; | |
234 | case 0x524: /* Commit */ | |
235 | if (!s->locked) | |
a35faa94 | 236 | s->cr = value & 0xff; |
9ee6e8bb | 237 | break; |
b3aaff11 PM |
238 | case 0x528: |
239 | s->amsel = value & 0xff; | |
240 | break; | |
9ee6e8bb | 241 | default: |
2ac71179 | 242 | hw_error("pl061_write: Bad offset %x\n", (int)offset); |
9ee6e8bb PB |
243 | } |
244 | pl061_update(s); | |
245 | } | |
246 | ||
247 | static void pl061_reset(pl061_state *s) | |
248 | { | |
249 | s->locked = 1; | |
250 | s->cr = 0xff; | |
251 | } | |
252 | ||
9596ebb7 | 253 | static void pl061_set_irq(void * opaque, int irq, int level) |
9ee6e8bb PB |
254 | { |
255 | pl061_state *s = (pl061_state *)opaque; | |
256 | uint8_t mask; | |
257 | ||
258 | mask = 1 << irq; | |
259 | if ((s->dir & mask) == 0) { | |
260 | s->data &= ~mask; | |
261 | if (level) | |
262 | s->data |= mask; | |
263 | pl061_update(s); | |
264 | } | |
265 | } | |
266 | ||
3cf89f8a AK |
267 | static const MemoryRegionOps pl061_ops = { |
268 | .read = pl061_read, | |
269 | .write = pl061_write, | |
270 | .endianness = DEVICE_NATIVE_ENDIAN, | |
9ee6e8bb PB |
271 | }; |
272 | ||
7063f49f | 273 | static int pl061_init(SysBusDevice *dev, const unsigned char *id) |
9ee6e8bb | 274 | { |
40905a6a | 275 | pl061_state *s = FROM_SYSBUS(pl061_state, dev); |
7063f49f | 276 | s->id = id; |
3cf89f8a | 277 | memory_region_init_io(&s->iomem, &pl061_ops, s, "pl061", 0x1000); |
750ecd44 | 278 | sysbus_init_mmio(dev, &s->iomem); |
40905a6a PB |
279 | sysbus_init_irq(dev, &s->irq); |
280 | qdev_init_gpio_in(&dev->qdev, pl061_set_irq, 8); | |
281 | qdev_init_gpio_out(&dev->qdev, s->out, 8); | |
9ee6e8bb | 282 | pl061_reset(s); |
81a322d4 | 283 | return 0; |
9ee6e8bb | 284 | } |
40905a6a | 285 | |
7063f49f PM |
286 | static int pl061_init_luminary(SysBusDevice *dev) |
287 | { | |
288 | return pl061_init(dev, pl061_id_luminary); | |
289 | } | |
290 | ||
291 | static int pl061_init_arm(SysBusDevice *dev) | |
292 | { | |
293 | return pl061_init(dev, pl061_id); | |
294 | } | |
295 | ||
999e12bb AL |
296 | static void pl061_class_init(ObjectClass *klass, void *data) |
297 | { | |
39bffca2 | 298 | DeviceClass *dc = DEVICE_CLASS(klass); |
999e12bb AL |
299 | SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); |
300 | ||
301 | k->init = pl061_init_arm; | |
39bffca2 | 302 | dc->vmsd = &vmstate_pl061; |
999e12bb AL |
303 | } |
304 | ||
39bffca2 AL |
305 | static TypeInfo pl061_info = { |
306 | .name = "pl061", | |
307 | .parent = TYPE_SYS_BUS_DEVICE, | |
308 | .instance_size = sizeof(pl061_state), | |
309 | .class_init = pl061_class_init, | |
a35faa94 PM |
310 | }; |
311 | ||
999e12bb AL |
312 | static void pl061_luminary_class_init(ObjectClass *klass, void *data) |
313 | { | |
39bffca2 | 314 | DeviceClass *dc = DEVICE_CLASS(klass); |
999e12bb AL |
315 | SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); |
316 | ||
317 | k->init = pl061_init_luminary; | |
39bffca2 | 318 | dc->vmsd = &vmstate_pl061; |
999e12bb AL |
319 | } |
320 | ||
39bffca2 AL |
321 | static TypeInfo pl061_luminary_info = { |
322 | .name = "pl061_luminary", | |
323 | .parent = TYPE_SYS_BUS_DEVICE, | |
324 | .instance_size = sizeof(pl061_state), | |
325 | .class_init = pl061_luminary_class_init, | |
a35faa94 PM |
326 | }; |
327 | ||
83f7d43a | 328 | static void pl061_register_types(void) |
40905a6a | 329 | { |
39bffca2 AL |
330 | type_register_static(&pl061_info); |
331 | type_register_static(&pl061_luminary_info); | |
40905a6a PB |
332 | } |
333 | ||
83f7d43a | 334 | type_init(pl061_register_types) |