|
1 /* |
|
2 * Arm PrimeCell PL110 Color LCD Controller |
|
3 * |
|
4 * Copyright (c) 2005-2006 CodeSourcery. |
|
5 * Written by Paul Brook |
|
6 * |
|
7 * This code is licenced under the GNU LGPL |
|
8 */ |
|
9 |
|
10 #include "hw.h" |
|
11 #include "primecell.h" |
|
12 #include "gui.h" |
|
13 |
|
14 #define PL110_CR_EN 0x001 |
|
15 #define PL110_CR_BGR 0x100 |
|
16 #define PL110_CR_BEBO 0x200 |
|
17 #define PL110_CR_BEPO 0x400 |
|
18 #define PL110_CR_PWR 0x800 |
|
19 |
|
20 enum pl110_bppmode |
|
21 { |
|
22 BPP_1, |
|
23 BPP_2, |
|
24 BPP_4, |
|
25 BPP_8, |
|
26 BPP_16, |
|
27 BPP_32 |
|
28 }; |
|
29 |
|
30 typedef struct { |
|
31 DisplayState *ds; |
|
32 |
|
33 /* The Versatile/PB uses a slightly modified PL110 controller. */ |
|
34 int versatile; |
|
35 uint32_t timing[4]; |
|
36 uint32_t cr; |
|
37 uint32_t upbase; |
|
38 uint32_t lpbase; |
|
39 uint32_t int_status; |
|
40 uint32_t int_mask; |
|
41 int cols; |
|
42 int rows; |
|
43 enum pl110_bppmode bpp; |
|
44 int invalidate; |
|
45 uint32_t pallette[256]; |
|
46 uint32_t raw_pallette[128]; |
|
47 qemu_irq irq; |
|
48 } pl110_state; |
|
49 |
|
50 static const unsigned char pl110_id[] = |
|
51 { 0x10, 0x11, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; |
|
52 |
|
53 /* The Arm documentation (DDI0224C) says the CLDC on the Versatile board |
|
54 has a different ID. However Linux only looks for the normal ID. */ |
|
55 #if 0 |
|
56 static const unsigned char pl110_versatile_id[] = |
|
57 { 0x93, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; |
|
58 #else |
|
59 #define pl110_versatile_id pl110_id |
|
60 #endif |
|
61 |
|
62 static inline uint32_t rgb_to_pixel8(unsigned int r, unsigned int g, unsigned b) |
|
63 { |
|
64 return ((r >> 5) << 5) | ((g >> 5) << 2) | (b >> 6); |
|
65 } |
|
66 |
|
67 static inline uint32_t rgb_to_pixel15(unsigned int r, unsigned int g, unsigned b) |
|
68 { |
|
69 return ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3); |
|
70 } |
|
71 |
|
72 static inline uint32_t rgb_to_pixel16(unsigned int r, unsigned int g, unsigned b) |
|
73 { |
|
74 return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); |
|
75 } |
|
76 |
|
77 static inline uint32_t rgb_to_pixel24(unsigned int r, unsigned int g, unsigned b) |
|
78 { |
|
79 return (r << 16) | (g << 8) | b; |
|
80 } |
|
81 |
|
82 static inline uint32_t rgb_to_pixel32(unsigned int r, unsigned int g, unsigned b) |
|
83 { |
|
84 return (r << 16) | (g << 8) | b; |
|
85 } |
|
86 |
|
87 typedef void (*drawfn)(uint32_t *, uint8_t *, const uint8_t *, int); |
|
88 |
|
89 #define BITS 8 |
|
90 #include "pl110_template.h" |
|
91 #define BITS 15 |
|
92 #include "pl110_template.h" |
|
93 #define BITS 16 |
|
94 #include "pl110_template.h" |
|
95 #define BITS 24 |
|
96 #include "pl110_template.h" |
|
97 #define BITS 32 |
|
98 #include "pl110_template.h" |
|
99 |
|
100 static int pl110_enabled(pl110_state *s) |
|
101 { |
|
102 return (s->cr & PL110_CR_EN) && (s->cr & PL110_CR_PWR); |
|
103 } |
|
104 |
|
105 static void pl110_update_display(void *opaque) |
|
106 { |
|
107 pl110_state *s = (pl110_state *)opaque; |
|
108 drawfn* fntable; |
|
109 drawfn fn; |
|
110 uint32_t *pallette; |
|
111 uint32_t addr; |
|
112 uint32_t base; |
|
113 int dest_width; |
|
114 int src_width; |
|
115 uint8_t *dest; |
|
116 uint8_t *src; |
|
117 int first, last = 0; |
|
118 int dirty, new_dirty; |
|
119 int i; |
|
120 int bpp_offset; |
|
121 |
|
122 if (!pl110_enabled(s)) |
|
123 return; |
|
124 |
|
125 switch (ds_get_bits_per_pixel(s->ds)) { |
|
126 case 0: |
|
127 return; |
|
128 case 8: |
|
129 fntable = pl110_draw_fn_8; |
|
130 dest_width = 1; |
|
131 break; |
|
132 case 15: |
|
133 fntable = pl110_draw_fn_15; |
|
134 dest_width = 2; |
|
135 break; |
|
136 case 16: |
|
137 fntable = pl110_draw_fn_16; |
|
138 dest_width = 2; |
|
139 break; |
|
140 case 24: |
|
141 fntable = pl110_draw_fn_24; |
|
142 dest_width = 3; |
|
143 break; |
|
144 case 32: |
|
145 fntable = pl110_draw_fn_32; |
|
146 dest_width = 4; |
|
147 break; |
|
148 default: |
|
149 fprintf(stderr, "pl110: Bad color depth\n"); |
|
150 exit(1); |
|
151 } |
|
152 if (s->cr & PL110_CR_BGR) |
|
153 bpp_offset = 0; |
|
154 else |
|
155 bpp_offset = 18; |
|
156 |
|
157 if (s->cr & PL110_CR_BEBO) |
|
158 fn = fntable[s->bpp + 6 + bpp_offset]; |
|
159 else if (s->cr & PL110_CR_BEPO) |
|
160 fn = fntable[s->bpp + 12 + bpp_offset]; |
|
161 else |
|
162 fn = fntable[s->bpp + bpp_offset]; |
|
163 |
|
164 src_width = s->cols; |
|
165 switch (s->bpp) { |
|
166 case BPP_1: |
|
167 src_width >>= 3; |
|
168 break; |
|
169 case BPP_2: |
|
170 src_width >>= 2; |
|
171 break; |
|
172 case BPP_4: |
|
173 src_width >>= 1; |
|
174 break; |
|
175 case BPP_8: |
|
176 break; |
|
177 case BPP_16: |
|
178 src_width <<= 1; |
|
179 break; |
|
180 case BPP_32: |
|
181 src_width <<= 2; |
|
182 break; |
|
183 } |
|
184 dest_width *= s->cols; |
|
185 pallette = s->pallette; |
|
186 base = s->upbase; |
|
187 /* HACK: Arm aliases physical memory at 0x80000000. */ |
|
188 if (base > 0x80000000) |
|
189 base -= 0x80000000; |
|
190 /* FIXME: This is broken if it spans multiple regions. */ |
|
191 src = host_ram_addr(base); |
|
192 dest = ds_get_data(s->ds); |
|
193 first = -1; |
|
194 addr = base; |
|
195 |
|
196 dirty = cpu_physical_memory_get_dirty(addr, VGA_DIRTY_FLAG); |
|
197 new_dirty = dirty; |
|
198 for (i = 0; i < s->rows; i++) { |
|
199 if ((addr & ~TARGET_PAGE_MASK) + src_width >= TARGET_PAGE_SIZE) { |
|
200 uint32_t tmp; |
|
201 new_dirty = 0; |
|
202 for (tmp = 0; tmp < src_width; tmp += TARGET_PAGE_SIZE) { |
|
203 new_dirty |= cpu_physical_memory_get_dirty(addr + tmp, |
|
204 VGA_DIRTY_FLAG); |
|
205 } |
|
206 } |
|
207 |
|
208 if (dirty || new_dirty || s->invalidate) { |
|
209 fn(pallette, dest, src, s->cols); |
|
210 if (first == -1) |
|
211 first = i; |
|
212 last = i; |
|
213 } |
|
214 dirty = new_dirty; |
|
215 addr += src_width; |
|
216 dest += dest_width; |
|
217 src += src_width; |
|
218 } |
|
219 if (first < 0) |
|
220 return; |
|
221 |
|
222 s->invalidate = 0; |
|
223 cpu_physical_memory_reset_dirty(base + first * src_width, |
|
224 base + (last + 1) * src_width, |
|
225 VGA_DIRTY_FLAG); |
|
226 dpy_update(s->ds, 0, first, s->cols, last - first + 1); |
|
227 } |
|
228 |
|
229 static void pl110_invalidate_display(void * opaque) |
|
230 { |
|
231 pl110_state *s = (pl110_state *)opaque; |
|
232 s->invalidate = 1; |
|
233 } |
|
234 |
|
235 static void pl110_update_pallette(pl110_state *s, int n) |
|
236 { |
|
237 int i; |
|
238 uint32_t raw; |
|
239 unsigned int r, g, b; |
|
240 |
|
241 raw = s->raw_pallette[n]; |
|
242 n <<= 1; |
|
243 for (i = 0; i < 2; i++) { |
|
244 r = (raw & 0x1f) << 3; |
|
245 raw >>= 5; |
|
246 g = (raw & 0x1f) << 3; |
|
247 raw >>= 5; |
|
248 b = (raw & 0x1f) << 3; |
|
249 /* The I bit is ignored. */ |
|
250 raw >>= 6; |
|
251 switch (ds_get_bits_per_pixel(s->ds)) { |
|
252 case 8: |
|
253 s->pallette[n] = rgb_to_pixel8(r, g, b); |
|
254 break; |
|
255 case 15: |
|
256 s->pallette[n] = rgb_to_pixel15(r, g, b); |
|
257 break; |
|
258 case 16: |
|
259 s->pallette[n] = rgb_to_pixel16(r, g, b); |
|
260 break; |
|
261 case 24: |
|
262 case 32: |
|
263 s->pallette[n] = rgb_to_pixel32(r, g, b); |
|
264 break; |
|
265 } |
|
266 n++; |
|
267 } |
|
268 } |
|
269 |
|
270 static void pl110_resize(pl110_state *s, int width, int height) |
|
271 { |
|
272 if (width != s->cols || height != s->rows) { |
|
273 if (pl110_enabled(s)) { |
|
274 gui_resize_vt(s->ds, width, height); |
|
275 } |
|
276 } |
|
277 s->cols = width; |
|
278 s->rows = height; |
|
279 } |
|
280 |
|
281 /* Update interrupts. */ |
|
282 static void pl110_update(pl110_state *s) |
|
283 { |
|
284 /* TODO: Implement interrupts. */ |
|
285 } |
|
286 |
|
287 static uint32_t pl110_read(void *opaque, target_phys_addr_t offset) |
|
288 { |
|
289 pl110_state *s = (pl110_state *)opaque; |
|
290 |
|
291 if (offset >= 0xfe0 && offset < 0x1000) { |
|
292 if (s->versatile) |
|
293 return pl110_versatile_id[(offset - 0xfe0) >> 2]; |
|
294 else |
|
295 return pl110_id[(offset - 0xfe0) >> 2]; |
|
296 } |
|
297 if (offset >= 0x200 && offset < 0x400) { |
|
298 return s->raw_pallette[(offset - 0x200) >> 2]; |
|
299 } |
|
300 switch (offset >> 2) { |
|
301 case 0: /* LCDTiming0 */ |
|
302 return s->timing[0]; |
|
303 case 1: /* LCDTiming1 */ |
|
304 return s->timing[1]; |
|
305 case 2: /* LCDTiming2 */ |
|
306 return s->timing[2]; |
|
307 case 3: /* LCDTiming3 */ |
|
308 return s->timing[3]; |
|
309 case 4: /* LCDUPBASE */ |
|
310 return s->upbase; |
|
311 case 5: /* LCDLPBASE */ |
|
312 return s->lpbase; |
|
313 case 6: /* LCDIMSC */ |
|
314 if (s->versatile) |
|
315 return s->cr; |
|
316 return s->int_mask; |
|
317 case 7: /* LCDControl */ |
|
318 if (s->versatile) |
|
319 return s->int_mask; |
|
320 return s->cr; |
|
321 case 8: /* LCDRIS */ |
|
322 return s->int_status; |
|
323 case 9: /* LCDMIS */ |
|
324 return s->int_status & s->int_mask; |
|
325 case 11: /* LCDUPCURR */ |
|
326 /* TODO: Implement vertical refresh. */ |
|
327 return s->upbase; |
|
328 case 12: /* LCDLPCURR */ |
|
329 return s->lpbase; |
|
330 default: |
|
331 cpu_abort (cpu_single_env, "pl110_read: Bad offset %x\n", (int)offset); |
|
332 return 0; |
|
333 } |
|
334 } |
|
335 |
|
336 static void pl110_write(void *opaque, target_phys_addr_t offset, |
|
337 uint32_t val) |
|
338 { |
|
339 pl110_state *s = (pl110_state *)opaque; |
|
340 int n; |
|
341 |
|
342 /* For simplicity invalidate the display whenever a control register |
|
343 is writen to. */ |
|
344 s->invalidate = 1; |
|
345 if (offset >= 0x200 && offset < 0x400) { |
|
346 /* Pallette. */ |
|
347 n = (offset - 0x200) >> 2; |
|
348 s->raw_pallette[(offset - 0x200) >> 2] = val; |
|
349 pl110_update_pallette(s, n); |
|
350 return; |
|
351 } |
|
352 switch (offset >> 2) { |
|
353 case 0: /* LCDTiming0 */ |
|
354 s->timing[0] = val; |
|
355 n = ((val & 0xfc) + 4) * 4; |
|
356 pl110_resize(s, n, s->rows); |
|
357 break; |
|
358 case 1: /* LCDTiming1 */ |
|
359 s->timing[1] = val; |
|
360 n = (val & 0x3ff) + 1; |
|
361 pl110_resize(s, s->cols, n); |
|
362 break; |
|
363 case 2: /* LCDTiming2 */ |
|
364 s->timing[2] = val; |
|
365 break; |
|
366 case 3: /* LCDTiming3 */ |
|
367 s->timing[3] = val; |
|
368 break; |
|
369 case 4: /* LCDUPBASE */ |
|
370 s->upbase = val; |
|
371 break; |
|
372 case 5: /* LCDLPBASE */ |
|
373 s->lpbase = val; |
|
374 break; |
|
375 case 6: /* LCDIMSC */ |
|
376 if (s->versatile) |
|
377 goto control; |
|
378 imsc: |
|
379 s->int_mask = val; |
|
380 pl110_update(s); |
|
381 break; |
|
382 case 7: /* LCDControl */ |
|
383 if (s->versatile) |
|
384 goto imsc; |
|
385 control: |
|
386 s->cr = val; |
|
387 s->bpp = (val >> 1) & 7; |
|
388 if (pl110_enabled(s)) { |
|
389 gui_resize_vt(s->ds, s->cols, s->rows); |
|
390 } |
|
391 break; |
|
392 case 10: /* LCDICR */ |
|
393 s->int_status &= ~val; |
|
394 pl110_update(s); |
|
395 break; |
|
396 default: |
|
397 cpu_abort (cpu_single_env, "pl110_write: Bad offset %x\n", (int)offset); |
|
398 } |
|
399 } |
|
400 |
|
401 static CPUReadMemoryFunc *pl110_readfn[] = { |
|
402 pl110_read, |
|
403 pl110_read, |
|
404 pl110_read |
|
405 }; |
|
406 |
|
407 static CPUWriteMemoryFunc *pl110_writefn[] = { |
|
408 pl110_write, |
|
409 pl110_write, |
|
410 pl110_write |
|
411 }; |
|
412 |
|
413 void *pl110_init(DisplayState *ds, uint32_t base, qemu_irq irq, |
|
414 int versatile) |
|
415 { |
|
416 pl110_state *s; |
|
417 int iomemtype; |
|
418 |
|
419 s = (pl110_state *)qemu_mallocz(sizeof(pl110_state)); |
|
420 iomemtype = cpu_register_io_memory(0, pl110_readfn, |
|
421 pl110_writefn, s); |
|
422 cpu_register_physical_memory(base, 0x00001000, iomemtype); |
|
423 s->versatile = versatile; |
|
424 s->irq = irq; |
|
425 s->ds = gui_get_graphic_console(NULL, |
|
426 pl110_update_display, |
|
427 pl110_invalidate_display, |
|
428 NULL, s); |
|
429 |
|
430 /* ??? Save/restore. */ |
|
431 return s; |
|
432 } |