]>
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 | * | |
8 | * This code is licenced under the GPL. | |
9 | */ | |
10 | ||
87ecb68b PB |
11 | #include "hw.h" |
12 | #include "primecell.h" | |
9ee6e8bb PB |
13 | |
14 | //#define DEBUG_PL061 1 | |
15 | ||
16 | #ifdef DEBUG_PL061 | |
17 | #define DPRINTF(fmt, args...) \ | |
18 | do { printf("pl061: " fmt , ##args); } while (0) | |
19 | #define BADF(fmt, args...) \ | |
20 | do { fprintf(stderr, "pl061: error: " fmt , ##args); exit(1);} while (0) | |
21 | #else | |
22 | #define DPRINTF(fmt, args...) do {} while(0) | |
23 | #define BADF(fmt, args...) \ | |
24 | do { fprintf(stderr, "pl061: error: " fmt , ##args);} while (0) | |
25 | #endif | |
26 | ||
27 | static const uint8_t pl061_id[12] = | |
28 | { 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1 }; | |
29 | ||
30 | typedef struct { | |
31 | uint32_t base; | |
32 | int locked; | |
33 | uint8_t data; | |
34 | uint8_t old_data; | |
35 | uint8_t dir; | |
36 | uint8_t isense; | |
37 | uint8_t ibe; | |
38 | uint8_t iev; | |
39 | uint8_t im; | |
40 | uint8_t istate; | |
41 | uint8_t afsel; | |
42 | uint8_t dr2r; | |
43 | uint8_t dr4r; | |
44 | uint8_t dr8r; | |
45 | uint8_t odr; | |
46 | uint8_t pur; | |
47 | uint8_t pdr; | |
48 | uint8_t slr; | |
49 | uint8_t den; | |
50 | uint8_t cr; | |
51 | qemu_irq irq; | |
52 | qemu_irq out[8]; | |
53 | } pl061_state; | |
54 | ||
55 | static void pl061_update(pl061_state *s) | |
56 | { | |
57 | uint8_t changed; | |
58 | uint8_t mask; | |
59 | int i; | |
60 | ||
61 | changed = s->old_data ^ s->data; | |
62 | if (!changed) | |
63 | return; | |
64 | ||
65 | s->old_data = s->data; | |
66 | for (i = 0; i < 8; i++) { | |
67 | mask = 1 << i; | |
68 | if ((changed & mask & s->dir) && s->out) { | |
69 | DPRINTF("Set output %d = %d\n", i, (s->data & mask) != 0); | |
70 | qemu_set_irq(s->out[i], (s->data & mask) != 0); | |
71 | } | |
72 | } | |
73 | ||
74 | /* FIXME: Implement input interrupts. */ | |
75 | } | |
76 | ||
77 | static uint32_t pl061_read(void *opaque, target_phys_addr_t offset) | |
78 | { | |
79 | pl061_state *s = (pl061_state *)opaque; | |
80 | ||
81 | offset -= s->base; | |
82 | if (offset >= 0xfd0 && offset < 0x1000) { | |
83 | return pl061_id[(offset - 0xfd0) >> 2]; | |
84 | } | |
85 | if (offset < 0x400) { | |
86 | return s->data & (offset >> 2); | |
87 | } | |
88 | switch (offset) { | |
89 | case 0x400: /* Direction */ | |
90 | return s->dir; | |
91 | case 0x404: /* Interrupt sense */ | |
92 | return s->isense; | |
93 | case 0x408: /* Interrupt both edges */ | |
94 | return s->ibe; | |
95 | case 0x40c: /* Interupt event */ | |
96 | return s->iev; | |
97 | case 0x410: /* Interrupt mask */ | |
98 | return s->im; | |
99 | case 0x414: /* Raw interrupt status */ | |
100 | return s->istate; | |
101 | case 0x418: /* Masked interrupt status */ | |
102 | return s->istate | s->im; | |
103 | case 0x420: /* Alternate function select */ | |
104 | return s->afsel; | |
105 | case 0x500: /* 2mA drive */ | |
106 | return s->dr2r; | |
107 | case 0x504: /* 4mA drive */ | |
108 | return s->dr4r; | |
109 | case 0x508: /* 8mA drive */ | |
110 | return s->dr8r; | |
111 | case 0x50c: /* Open drain */ | |
112 | return s->odr; | |
113 | case 0x510: /* Pull-up */ | |
114 | return s->pur; | |
115 | case 0x514: /* Pull-down */ | |
116 | return s->pdr; | |
117 | case 0x518: /* Slew rate control */ | |
118 | return s->slr; | |
119 | case 0x51c: /* Digital enable */ | |
120 | return s->den; | |
121 | case 0x520: /* Lock */ | |
122 | return s->locked; | |
123 | case 0x524: /* Commit */ | |
124 | return s->cr; | |
125 | default: | |
126 | cpu_abort (cpu_single_env, "pl061_read: Bad offset %x\n", | |
127 | (int)offset); | |
128 | return 0; | |
129 | } | |
130 | } | |
131 | ||
132 | static void pl061_write(void *opaque, target_phys_addr_t offset, | |
133 | uint32_t value) | |
134 | { | |
135 | pl061_state *s = (pl061_state *)opaque; | |
136 | uint8_t mask; | |
137 | ||
138 | offset -= s->base; | |
139 | if (offset < 0x400) { | |
140 | mask = (offset >> 2) & s->dir; | |
141 | s->data = (s->data & ~mask) | (value & mask); | |
142 | pl061_update(s); | |
143 | return; | |
144 | } | |
145 | switch (offset) { | |
146 | case 0x400: /* Direction */ | |
147 | s->dir = value; | |
148 | break; | |
149 | case 0x404: /* Interrupt sense */ | |
150 | s->isense = value; | |
151 | break; | |
152 | case 0x408: /* Interrupt both edges */ | |
153 | s->ibe = value; | |
154 | break; | |
155 | case 0x40c: /* Interupt event */ | |
156 | s->iev = value; | |
157 | break; | |
158 | case 0x410: /* Interrupt mask */ | |
159 | s->im = value; | |
160 | break; | |
161 | case 0x41c: /* Interrupt clear */ | |
162 | s->istate &= ~value; | |
163 | break; | |
164 | case 0x420: /* Alternate function select */ | |
165 | mask = s->cr; | |
166 | s->afsel = (s->afsel & ~mask) | (value & mask); | |
167 | break; | |
168 | case 0x500: /* 2mA drive */ | |
169 | s->dr2r = value; | |
170 | break; | |
171 | case 0x504: /* 4mA drive */ | |
172 | s->dr4r = value; | |
173 | break; | |
174 | case 0x508: /* 8mA drive */ | |
175 | s->dr8r = value; | |
176 | break; | |
177 | case 0x50c: /* Open drain */ | |
178 | s->odr = value; | |
179 | break; | |
180 | case 0x510: /* Pull-up */ | |
181 | s->pur = value; | |
182 | break; | |
183 | case 0x514: /* Pull-down */ | |
184 | s->pdr = value; | |
185 | break; | |
186 | case 0x518: /* Slew rate control */ | |
187 | s->slr = value; | |
188 | break; | |
189 | case 0x51c: /* Digital enable */ | |
190 | s->den = value; | |
191 | break; | |
192 | case 0x520: /* Lock */ | |
193 | s->locked = (value != 0xacce551); | |
194 | break; | |
195 | case 0x524: /* Commit */ | |
196 | if (!s->locked) | |
197 | s->cr = value; | |
198 | break; | |
199 | default: | |
200 | cpu_abort (cpu_single_env, "pl061_write: Bad offset %x\n", | |
201 | (int)offset); | |
202 | } | |
203 | pl061_update(s); | |
204 | } | |
205 | ||
206 | static void pl061_reset(pl061_state *s) | |
207 | { | |
208 | s->locked = 1; | |
209 | s->cr = 0xff; | |
210 | } | |
211 | ||
212 | void pl061_set_irq(void * opaque, int irq, int level) | |
213 | { | |
214 | pl061_state *s = (pl061_state *)opaque; | |
215 | uint8_t mask; | |
216 | ||
217 | mask = 1 << irq; | |
218 | if ((s->dir & mask) == 0) { | |
219 | s->data &= ~mask; | |
220 | if (level) | |
221 | s->data |= mask; | |
222 | pl061_update(s); | |
223 | } | |
224 | } | |
225 | ||
226 | static CPUReadMemoryFunc *pl061_readfn[] = { | |
227 | pl061_read, | |
228 | pl061_read, | |
229 | pl061_read | |
230 | }; | |
231 | ||
232 | static CPUWriteMemoryFunc *pl061_writefn[] = { | |
233 | pl061_write, | |
234 | pl061_write, | |
235 | pl061_write | |
236 | }; | |
237 | ||
238 | /* Returns an array of inputs. */ | |
239 | qemu_irq *pl061_init(uint32_t base, qemu_irq irq, qemu_irq **out) | |
240 | { | |
241 | int iomemtype; | |
242 | pl061_state *s; | |
243 | ||
244 | s = (pl061_state *)qemu_mallocz(sizeof(pl061_state)); | |
245 | iomemtype = cpu_register_io_memory(0, pl061_readfn, | |
246 | pl061_writefn, s); | |
247 | cpu_register_physical_memory(base, 0x00001000, iomemtype); | |
248 | s->base = base; | |
249 | s->irq = irq; | |
250 | pl061_reset(s); | |
251 | if (out) | |
252 | *out = s->out; | |
253 | ||
254 | /* ??? Save/restore. */ | |
255 | return qemu_allocate_irqs(pl061_set_irq, s, 8); | |
256 | } | |
257 |