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