]>
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; |
9ee6e8bb PB |
33 | int locked; |
34 | uint8_t data; | |
35 | uint8_t old_data; | |
36 | uint8_t dir; | |
37 | uint8_t isense; | |
38 | uint8_t ibe; | |
39 | uint8_t iev; | |
40 | uint8_t im; | |
41 | uint8_t istate; | |
42 | uint8_t afsel; | |
43 | uint8_t dr2r; | |
44 | uint8_t dr4r; | |
45 | uint8_t dr8r; | |
46 | uint8_t odr; | |
47 | uint8_t pur; | |
48 | uint8_t pdr; | |
49 | uint8_t slr; | |
50 | uint8_t den; | |
51 | uint8_t cr; | |
775616c3 | 52 | uint8_t float_high; |
9ee6e8bb PB |
53 | qemu_irq irq; |
54 | qemu_irq out[8]; | |
7063f49f | 55 | const unsigned char *id; |
9ee6e8bb PB |
56 | } pl061_state; |
57 | ||
58 | static void pl061_update(pl061_state *s) | |
59 | { | |
60 | uint8_t changed; | |
61 | uint8_t mask; | |
775616c3 | 62 | uint8_t out; |
9ee6e8bb PB |
63 | int i; |
64 | ||
775616c3 PB |
65 | /* Outputs float high. */ |
66 | /* FIXME: This is board dependent. */ | |
67 | out = (s->data & s->dir) | ~s->dir; | |
68 | changed = s->old_data ^ out; | |
9ee6e8bb PB |
69 | if (!changed) |
70 | return; | |
71 | ||
775616c3 | 72 | s->old_data = out; |
9ee6e8bb PB |
73 | for (i = 0; i < 8; i++) { |
74 | mask = 1 << i; | |
775616c3 PB |
75 | if ((changed & mask) && s->out) { |
76 | DPRINTF("Set output %d = %d\n", i, (out & mask) != 0); | |
77 | qemu_set_irq(s->out[i], (out & mask) != 0); | |
9ee6e8bb PB |
78 | } |
79 | } | |
80 | ||
81 | /* FIXME: Implement input interrupts. */ | |
82 | } | |
83 | ||
c227f099 | 84 | static uint32_t pl061_read(void *opaque, target_phys_addr_t offset) |
9ee6e8bb PB |
85 | { |
86 | pl061_state *s = (pl061_state *)opaque; | |
87 | ||
9ee6e8bb | 88 | if (offset >= 0xfd0 && offset < 0x1000) { |
7063f49f | 89 | return s->id[(offset - 0xfd0) >> 2]; |
9ee6e8bb PB |
90 | } |
91 | if (offset < 0x400) { | |
92 | return s->data & (offset >> 2); | |
93 | } | |
94 | switch (offset) { | |
95 | case 0x400: /* Direction */ | |
96 | return s->dir; | |
97 | case 0x404: /* Interrupt sense */ | |
98 | return s->isense; | |
99 | case 0x408: /* Interrupt both edges */ | |
100 | return s->ibe; | |
ff2712ba | 101 | case 0x40c: /* Interrupt event */ |
9ee6e8bb PB |
102 | return s->iev; |
103 | case 0x410: /* Interrupt mask */ | |
104 | return s->im; | |
105 | case 0x414: /* Raw interrupt status */ | |
106 | return s->istate; | |
107 | case 0x418: /* Masked interrupt status */ | |
108 | return s->istate | s->im; | |
109 | case 0x420: /* Alternate function select */ | |
110 | return s->afsel; | |
111 | case 0x500: /* 2mA drive */ | |
112 | return s->dr2r; | |
113 | case 0x504: /* 4mA drive */ | |
114 | return s->dr4r; | |
115 | case 0x508: /* 8mA drive */ | |
116 | return s->dr8r; | |
117 | case 0x50c: /* Open drain */ | |
118 | return s->odr; | |
119 | case 0x510: /* Pull-up */ | |
120 | return s->pur; | |
121 | case 0x514: /* Pull-down */ | |
122 | return s->pdr; | |
123 | case 0x518: /* Slew rate control */ | |
124 | return s->slr; | |
125 | case 0x51c: /* Digital enable */ | |
126 | return s->den; | |
127 | case 0x520: /* Lock */ | |
128 | return s->locked; | |
129 | case 0x524: /* Commit */ | |
130 | return s->cr; | |
131 | default: | |
2ac71179 | 132 | hw_error("pl061_read: Bad offset %x\n", (int)offset); |
9ee6e8bb PB |
133 | return 0; |
134 | } | |
135 | } | |
136 | ||
c227f099 | 137 | static void pl061_write(void *opaque, target_phys_addr_t offset, |
9ee6e8bb PB |
138 | uint32_t value) |
139 | { | |
140 | pl061_state *s = (pl061_state *)opaque; | |
141 | uint8_t mask; | |
142 | ||
9ee6e8bb PB |
143 | if (offset < 0x400) { |
144 | mask = (offset >> 2) & s->dir; | |
145 | s->data = (s->data & ~mask) | (value & mask); | |
146 | pl061_update(s); | |
147 | return; | |
148 | } | |
149 | switch (offset) { | |
150 | case 0x400: /* Direction */ | |
151 | s->dir = value; | |
152 | break; | |
153 | case 0x404: /* Interrupt sense */ | |
154 | s->isense = value; | |
155 | break; | |
156 | case 0x408: /* Interrupt both edges */ | |
157 | s->ibe = value; | |
158 | break; | |
ff2712ba | 159 | case 0x40c: /* Interrupt event */ |
9ee6e8bb PB |
160 | s->iev = value; |
161 | break; | |
162 | case 0x410: /* Interrupt mask */ | |
163 | s->im = value; | |
164 | break; | |
165 | case 0x41c: /* Interrupt clear */ | |
166 | s->istate &= ~value; | |
167 | break; | |
168 | case 0x420: /* Alternate function select */ | |
169 | mask = s->cr; | |
170 | s->afsel = (s->afsel & ~mask) | (value & mask); | |
171 | break; | |
172 | case 0x500: /* 2mA drive */ | |
173 | s->dr2r = value; | |
174 | break; | |
175 | case 0x504: /* 4mA drive */ | |
176 | s->dr4r = value; | |
177 | break; | |
178 | case 0x508: /* 8mA drive */ | |
179 | s->dr8r = value; | |
180 | break; | |
181 | case 0x50c: /* Open drain */ | |
182 | s->odr = value; | |
183 | break; | |
184 | case 0x510: /* Pull-up */ | |
185 | s->pur = value; | |
186 | break; | |
187 | case 0x514: /* Pull-down */ | |
188 | s->pdr = value; | |
189 | break; | |
190 | case 0x518: /* Slew rate control */ | |
191 | s->slr = value; | |
192 | break; | |
193 | case 0x51c: /* Digital enable */ | |
194 | s->den = value; | |
195 | break; | |
196 | case 0x520: /* Lock */ | |
197 | s->locked = (value != 0xacce551); | |
198 | break; | |
199 | case 0x524: /* Commit */ | |
200 | if (!s->locked) | |
201 | s->cr = value; | |
202 | break; | |
203 | default: | |
2ac71179 | 204 | hw_error("pl061_write: Bad offset %x\n", (int)offset); |
9ee6e8bb PB |
205 | } |
206 | pl061_update(s); | |
207 | } | |
208 | ||
209 | static void pl061_reset(pl061_state *s) | |
210 | { | |
211 | s->locked = 1; | |
212 | s->cr = 0xff; | |
213 | } | |
214 | ||
9596ebb7 | 215 | static void pl061_set_irq(void * opaque, int irq, int level) |
9ee6e8bb PB |
216 | { |
217 | pl061_state *s = (pl061_state *)opaque; | |
218 | uint8_t mask; | |
219 | ||
220 | mask = 1 << irq; | |
221 | if ((s->dir & mask) == 0) { | |
222 | s->data &= ~mask; | |
223 | if (level) | |
224 | s->data |= mask; | |
225 | pl061_update(s); | |
226 | } | |
227 | } | |
228 | ||
d60efc6b | 229 | static CPUReadMemoryFunc * const pl061_readfn[] = { |
9ee6e8bb PB |
230 | pl061_read, |
231 | pl061_read, | |
232 | pl061_read | |
233 | }; | |
234 | ||
d60efc6b | 235 | static CPUWriteMemoryFunc * const pl061_writefn[] = { |
9ee6e8bb PB |
236 | pl061_write, |
237 | pl061_write, | |
238 | pl061_write | |
239 | }; | |
240 | ||
23e39294 PB |
241 | static void pl061_save(QEMUFile *f, void *opaque) |
242 | { | |
243 | pl061_state *s = (pl061_state *)opaque; | |
244 | ||
245 | qemu_put_be32(f, s->locked); | |
246 | qemu_put_be32(f, s->data); | |
247 | qemu_put_be32(f, s->old_data); | |
248 | qemu_put_be32(f, s->dir); | |
249 | qemu_put_be32(f, s->isense); | |
250 | qemu_put_be32(f, s->ibe); | |
251 | qemu_put_be32(f, s->iev); | |
252 | qemu_put_be32(f, s->im); | |
253 | qemu_put_be32(f, s->istate); | |
254 | qemu_put_be32(f, s->afsel); | |
255 | qemu_put_be32(f, s->dr2r); | |
256 | qemu_put_be32(f, s->dr4r); | |
257 | qemu_put_be32(f, s->dr8r); | |
258 | qemu_put_be32(f, s->odr); | |
259 | qemu_put_be32(f, s->pur); | |
260 | qemu_put_be32(f, s->pdr); | |
261 | qemu_put_be32(f, s->slr); | |
262 | qemu_put_be32(f, s->den); | |
263 | qemu_put_be32(f, s->cr); | |
264 | qemu_put_be32(f, s->float_high); | |
265 | } | |
266 | ||
267 | static int pl061_load(QEMUFile *f, void *opaque, int version_id) | |
268 | { | |
269 | pl061_state *s = (pl061_state *)opaque; | |
270 | if (version_id != 1) | |
271 | return -EINVAL; | |
272 | ||
273 | s->locked = qemu_get_be32(f); | |
274 | s->data = qemu_get_be32(f); | |
275 | s->old_data = qemu_get_be32(f); | |
276 | s->dir = qemu_get_be32(f); | |
277 | s->isense = qemu_get_be32(f); | |
278 | s->ibe = qemu_get_be32(f); | |
279 | s->iev = qemu_get_be32(f); | |
280 | s->im = qemu_get_be32(f); | |
281 | s->istate = qemu_get_be32(f); | |
282 | s->afsel = qemu_get_be32(f); | |
283 | s->dr2r = qemu_get_be32(f); | |
284 | s->dr4r = qemu_get_be32(f); | |
285 | s->dr8r = qemu_get_be32(f); | |
286 | s->odr = qemu_get_be32(f); | |
287 | s->pur = qemu_get_be32(f); | |
288 | s->pdr = qemu_get_be32(f); | |
289 | s->slr = qemu_get_be32(f); | |
290 | s->den = qemu_get_be32(f); | |
291 | s->cr = qemu_get_be32(f); | |
292 | s->float_high = qemu_get_be32(f); | |
293 | ||
294 | return 0; | |
295 | } | |
296 | ||
7063f49f | 297 | static int pl061_init(SysBusDevice *dev, const unsigned char *id) |
9ee6e8bb PB |
298 | { |
299 | int iomemtype; | |
40905a6a | 300 | pl061_state *s = FROM_SYSBUS(pl061_state, dev); |
7063f49f | 301 | s->id = id; |
1eed09cb | 302 | iomemtype = cpu_register_io_memory(pl061_readfn, |
2507c12a AG |
303 | pl061_writefn, s, |
304 | DEVICE_NATIVE_ENDIAN); | |
40905a6a PB |
305 | sysbus_init_mmio(dev, 0x1000, iomemtype); |
306 | sysbus_init_irq(dev, &s->irq); | |
307 | qdev_init_gpio_in(&dev->qdev, pl061_set_irq, 8); | |
308 | qdev_init_gpio_out(&dev->qdev, s->out, 8); | |
9ee6e8bb | 309 | pl061_reset(s); |
0be71e32 | 310 | register_savevm(&dev->qdev, "pl061_gpio", -1, 1, pl061_save, pl061_load, s); |
81a322d4 | 311 | return 0; |
9ee6e8bb | 312 | } |
40905a6a | 313 | |
7063f49f PM |
314 | static int pl061_init_luminary(SysBusDevice *dev) |
315 | { | |
316 | return pl061_init(dev, pl061_id_luminary); | |
317 | } | |
318 | ||
319 | static int pl061_init_arm(SysBusDevice *dev) | |
320 | { | |
321 | return pl061_init(dev, pl061_id); | |
322 | } | |
323 | ||
40905a6a PB |
324 | static void pl061_register_devices(void) |
325 | { | |
326 | sysbus_register_dev("pl061", sizeof(pl061_state), | |
7063f49f PM |
327 | pl061_init_arm); |
328 | sysbus_register_dev("pl061_luminary", sizeof(pl061_state), | |
329 | pl061_init_luminary); | |
40905a6a PB |
330 | } |
331 | ||
332 | device_init(pl061_register_devices) |