]>
Commit | Line | Data |
---|---|---|
5e9c2a8d GE |
1 | /* |
2 | * Raspberry Pi emulation (c) 2012 Gregory Estrade | |
3 | * Refactoring for Pi2 Copyright (c) 2015, Microsoft. Written by Andrew Baumann. | |
4 | * This code is licensed under the GNU GPLv2 and later. | |
5 | * | |
6 | * Heavily based on milkymist-vgafb.c, copyright terms below: | |
7 | * QEMU model of the Milkymist VGA framebuffer. | |
8 | * | |
9 | * Copyright (c) 2010-2012 Michael Walle <michael@walle.cc> | |
10 | * | |
11 | * This library is free software; you can redistribute it and/or | |
12 | * modify it under the terms of the GNU Lesser General Public | |
13 | * License as published by the Free Software Foundation; either | |
14 | * version 2 of the License, or (at your option) any later version. | |
15 | * | |
16 | * This library is distributed in the hope that it will be useful, | |
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
19 | * Lesser General Public License for more details. | |
20 | * | |
21 | * You should have received a copy of the GNU Lesser General Public | |
22 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. | |
23 | * | |
24 | */ | |
25 | ||
26 | #include "qemu/osdep.h" | |
da34e65c | 27 | #include "qapi/error.h" |
5e9c2a8d GE |
28 | #include "hw/display/bcm2835_fb.h" |
29 | #include "hw/display/framebuffer.h" | |
30 | #include "ui/pixel_ops.h" | |
31 | #include "hw/misc/bcm2835_mbox_defs.h" | |
03dd024f | 32 | #include "qemu/log.h" |
5e9c2a8d GE |
33 | |
34 | #define DEFAULT_VCRAM_SIZE 0x4000000 | |
35 | #define BCM2835_FB_OFFSET 0x00100000 | |
36 | ||
37 | static void fb_invalidate_display(void *opaque) | |
38 | { | |
39 | BCM2835FBState *s = BCM2835_FB(opaque); | |
40 | ||
41 | s->invalidate = true; | |
42 | } | |
43 | ||
44 | static void draw_line_src16(void *opaque, uint8_t *dst, const uint8_t *src, | |
45 | int width, int deststep) | |
46 | { | |
47 | BCM2835FBState *s = opaque; | |
48 | uint16_t rgb565; | |
49 | uint32_t rgb888; | |
50 | uint8_t r, g, b; | |
51 | DisplaySurface *surface = qemu_console_surface(s->con); | |
52 | int bpp = surface_bits_per_pixel(surface); | |
53 | ||
54 | while (width--) { | |
55 | switch (s->bpp) { | |
56 | case 8: | |
57 | /* lookup palette starting at video ram base | |
58 | * TODO: cache translation, rather than doing this each time! | |
59 | */ | |
60 | rgb888 = ldl_le_phys(&s->dma_as, s->vcram_base + (*src << 2)); | |
61 | r = (rgb888 >> 0) & 0xff; | |
62 | g = (rgb888 >> 8) & 0xff; | |
63 | b = (rgb888 >> 16) & 0xff; | |
64 | src++; | |
65 | break; | |
66 | case 16: | |
67 | rgb565 = lduw_le_p(src); | |
68 | r = ((rgb565 >> 11) & 0x1f) << 3; | |
69 | g = ((rgb565 >> 5) & 0x3f) << 2; | |
70 | b = ((rgb565 >> 0) & 0x1f) << 3; | |
71 | src += 2; | |
72 | break; | |
73 | case 24: | |
74 | rgb888 = ldl_le_p(src); | |
75 | r = (rgb888 >> 0) & 0xff; | |
76 | g = (rgb888 >> 8) & 0xff; | |
77 | b = (rgb888 >> 16) & 0xff; | |
78 | src += 3; | |
79 | break; | |
80 | case 32: | |
81 | rgb888 = ldl_le_p(src); | |
82 | r = (rgb888 >> 0) & 0xff; | |
83 | g = (rgb888 >> 8) & 0xff; | |
84 | b = (rgb888 >> 16) & 0xff; | |
85 | src += 4; | |
86 | break; | |
87 | default: | |
88 | r = 0; | |
89 | g = 0; | |
90 | b = 0; | |
91 | break; | |
92 | } | |
93 | ||
94 | if (s->pixo == 0) { | |
95 | /* swap to BGR pixel format */ | |
96 | uint8_t tmp = r; | |
97 | r = b; | |
98 | b = tmp; | |
99 | } | |
100 | ||
101 | switch (bpp) { | |
102 | case 8: | |
103 | *dst++ = rgb_to_pixel8(r, g, b); | |
104 | break; | |
105 | case 15: | |
106 | *(uint16_t *)dst = rgb_to_pixel15(r, g, b); | |
107 | dst += 2; | |
108 | break; | |
109 | case 16: | |
110 | *(uint16_t *)dst = rgb_to_pixel16(r, g, b); | |
111 | dst += 2; | |
112 | break; | |
113 | case 24: | |
114 | rgb888 = rgb_to_pixel24(r, g, b); | |
115 | *dst++ = rgb888 & 0xff; | |
116 | *dst++ = (rgb888 >> 8) & 0xff; | |
117 | *dst++ = (rgb888 >> 16) & 0xff; | |
118 | break; | |
119 | case 32: | |
120 | *(uint32_t *)dst = rgb_to_pixel32(r, g, b); | |
121 | dst += 4; | |
122 | break; | |
123 | default: | |
124 | return; | |
125 | } | |
126 | } | |
127 | } | |
128 | ||
129 | static void fb_update_display(void *opaque) | |
130 | { | |
131 | BCM2835FBState *s = opaque; | |
132 | DisplaySurface *surface = qemu_console_surface(s->con); | |
133 | int first = 0; | |
134 | int last = 0; | |
135 | int src_width = 0; | |
136 | int dest_width = 0; | |
137 | ||
138 | if (s->lock || !s->xres) { | |
139 | return; | |
140 | } | |
141 | ||
142 | src_width = s->xres * (s->bpp >> 3); | |
143 | dest_width = s->xres; | |
144 | ||
145 | switch (surface_bits_per_pixel(surface)) { | |
146 | case 0: | |
147 | return; | |
148 | case 8: | |
149 | break; | |
150 | case 15: | |
151 | dest_width *= 2; | |
152 | break; | |
153 | case 16: | |
154 | dest_width *= 2; | |
155 | break; | |
156 | case 24: | |
157 | dest_width *= 3; | |
158 | break; | |
159 | case 32: | |
160 | dest_width *= 4; | |
161 | break; | |
162 | default: | |
163 | hw_error("bcm2835_fb: bad color depth\n"); | |
164 | break; | |
165 | } | |
166 | ||
167 | if (s->invalidate) { | |
168 | framebuffer_update_memory_section(&s->fbsection, s->dma_mr, s->base, | |
169 | s->yres, src_width); | |
170 | } | |
171 | ||
172 | framebuffer_update_display(surface, &s->fbsection, s->xres, s->yres, | |
173 | src_width, dest_width, 0, s->invalidate, | |
174 | draw_line_src16, s, &first, &last); | |
175 | ||
176 | if (first >= 0) { | |
177 | dpy_gfx_update(s->con, 0, first, s->xres, last - first + 1); | |
178 | } | |
179 | ||
180 | s->invalidate = false; | |
181 | } | |
182 | ||
183 | static void bcm2835_fb_mbox_push(BCM2835FBState *s, uint32_t value) | |
184 | { | |
185 | value &= ~0xf; | |
186 | ||
187 | s->lock = true; | |
188 | ||
189 | s->xres = ldl_le_phys(&s->dma_as, value); | |
190 | s->yres = ldl_le_phys(&s->dma_as, value + 4); | |
191 | s->xres_virtual = ldl_le_phys(&s->dma_as, value + 8); | |
192 | s->yres_virtual = ldl_le_phys(&s->dma_as, value + 12); | |
193 | s->bpp = ldl_le_phys(&s->dma_as, value + 20); | |
194 | s->xoffset = ldl_le_phys(&s->dma_as, value + 24); | |
195 | s->yoffset = ldl_le_phys(&s->dma_as, value + 28); | |
196 | ||
197 | s->base = s->vcram_base | (value & 0xc0000000); | |
198 | s->base += BCM2835_FB_OFFSET; | |
199 | ||
200 | /* TODO - Manage properly virtual resolution */ | |
201 | ||
202 | s->pitch = s->xres * (s->bpp >> 3); | |
203 | s->size = s->yres * s->pitch; | |
204 | ||
205 | stl_le_phys(&s->dma_as, value + 16, s->pitch); | |
206 | stl_le_phys(&s->dma_as, value + 32, s->base); | |
207 | stl_le_phys(&s->dma_as, value + 36, s->size); | |
208 | ||
209 | s->invalidate = true; | |
210 | qemu_console_resize(s->con, s->xres, s->yres); | |
211 | s->lock = false; | |
212 | } | |
213 | ||
214 | void bcm2835_fb_reconfigure(BCM2835FBState *s, uint32_t *xres, uint32_t *yres, | |
215 | uint32_t *xoffset, uint32_t *yoffset, uint32_t *bpp, | |
216 | uint32_t *pixo, uint32_t *alpha) | |
217 | { | |
218 | s->lock = true; | |
219 | ||
220 | /* TODO: input validation! */ | |
221 | if (xres) { | |
222 | s->xres = *xres; | |
223 | } | |
224 | if (yres) { | |
225 | s->yres = *yres; | |
226 | } | |
227 | if (xoffset) { | |
228 | s->xoffset = *xoffset; | |
229 | } | |
230 | if (yoffset) { | |
231 | s->yoffset = *yoffset; | |
232 | } | |
233 | if (bpp) { | |
234 | s->bpp = *bpp; | |
235 | } | |
236 | if (pixo) { | |
237 | s->pixo = *pixo; | |
238 | } | |
239 | if (alpha) { | |
240 | s->alpha = *alpha; | |
241 | } | |
242 | ||
243 | /* TODO - Manage properly virtual resolution */ | |
244 | ||
245 | s->pitch = s->xres * (s->bpp >> 3); | |
246 | s->size = s->yres * s->pitch; | |
247 | ||
248 | s->invalidate = true; | |
249 | qemu_console_resize(s->con, s->xres, s->yres); | |
250 | s->lock = false; | |
251 | } | |
252 | ||
253 | static uint64_t bcm2835_fb_read(void *opaque, hwaddr offset, unsigned size) | |
254 | { | |
255 | BCM2835FBState *s = opaque; | |
256 | uint32_t res = 0; | |
257 | ||
258 | switch (offset) { | |
259 | case MBOX_AS_DATA: | |
260 | res = MBOX_CHAN_FB; | |
261 | s->pending = false; | |
262 | qemu_set_irq(s->mbox_irq, 0); | |
263 | break; | |
264 | ||
265 | case MBOX_AS_PENDING: | |
266 | res = s->pending; | |
267 | break; | |
268 | ||
269 | default: | |
270 | qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n", | |
271 | __func__, offset); | |
272 | return 0; | |
273 | } | |
274 | ||
275 | return res; | |
276 | } | |
277 | ||
278 | static void bcm2835_fb_write(void *opaque, hwaddr offset, uint64_t value, | |
279 | unsigned size) | |
280 | { | |
281 | BCM2835FBState *s = opaque; | |
282 | ||
283 | switch (offset) { | |
284 | case MBOX_AS_DATA: | |
285 | /* bcm2835_mbox should check our pending status before pushing */ | |
286 | assert(!s->pending); | |
287 | s->pending = true; | |
288 | bcm2835_fb_mbox_push(s, value); | |
289 | qemu_set_irq(s->mbox_irq, 1); | |
290 | break; | |
291 | ||
292 | default: | |
293 | qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n", | |
294 | __func__, offset); | |
295 | return; | |
296 | } | |
297 | } | |
298 | ||
299 | static const MemoryRegionOps bcm2835_fb_ops = { | |
300 | .read = bcm2835_fb_read, | |
301 | .write = bcm2835_fb_write, | |
302 | .endianness = DEVICE_NATIVE_ENDIAN, | |
303 | .valid.min_access_size = 4, | |
304 | .valid.max_access_size = 4, | |
305 | }; | |
306 | ||
307 | static const VMStateDescription vmstate_bcm2835_fb = { | |
308 | .name = TYPE_BCM2835_FB, | |
309 | .version_id = 1, | |
310 | .minimum_version_id = 1, | |
311 | .fields = (VMStateField[]) { | |
312 | VMSTATE_BOOL(lock, BCM2835FBState), | |
313 | VMSTATE_BOOL(invalidate, BCM2835FBState), | |
314 | VMSTATE_BOOL(pending, BCM2835FBState), | |
315 | VMSTATE_UINT32(xres, BCM2835FBState), | |
316 | VMSTATE_UINT32(yres, BCM2835FBState), | |
317 | VMSTATE_UINT32(xres_virtual, BCM2835FBState), | |
318 | VMSTATE_UINT32(yres_virtual, BCM2835FBState), | |
319 | VMSTATE_UINT32(xoffset, BCM2835FBState), | |
320 | VMSTATE_UINT32(yoffset, BCM2835FBState), | |
321 | VMSTATE_UINT32(bpp, BCM2835FBState), | |
322 | VMSTATE_UINT32(base, BCM2835FBState), | |
323 | VMSTATE_UINT32(pitch, BCM2835FBState), | |
324 | VMSTATE_UINT32(size, BCM2835FBState), | |
325 | VMSTATE_UINT32(pixo, BCM2835FBState), | |
326 | VMSTATE_UINT32(alpha, BCM2835FBState), | |
327 | VMSTATE_END_OF_LIST() | |
328 | } | |
329 | }; | |
330 | ||
331 | static const GraphicHwOps vgafb_ops = { | |
332 | .invalidate = fb_invalidate_display, | |
333 | .gfx_update = fb_update_display, | |
334 | }; | |
335 | ||
336 | static void bcm2835_fb_init(Object *obj) | |
337 | { | |
338 | BCM2835FBState *s = BCM2835_FB(obj); | |
339 | ||
340 | memory_region_init_io(&s->iomem, obj, &bcm2835_fb_ops, s, TYPE_BCM2835_FB, | |
341 | 0x10); | |
342 | sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); | |
343 | sysbus_init_irq(SYS_BUS_DEVICE(s), &s->mbox_irq); | |
344 | } | |
345 | ||
346 | static void bcm2835_fb_reset(DeviceState *dev) | |
347 | { | |
348 | BCM2835FBState *s = BCM2835_FB(dev); | |
349 | ||
350 | s->pending = false; | |
351 | ||
352 | s->xres_virtual = s->xres; | |
353 | s->yres_virtual = s->yres; | |
354 | s->xoffset = 0; | |
355 | s->yoffset = 0; | |
356 | s->base = s->vcram_base + BCM2835_FB_OFFSET; | |
357 | s->pitch = s->xres * (s->bpp >> 3); | |
358 | s->size = s->yres * s->pitch; | |
359 | ||
360 | s->invalidate = true; | |
361 | s->lock = false; | |
362 | } | |
363 | ||
364 | static void bcm2835_fb_realize(DeviceState *dev, Error **errp) | |
365 | { | |
366 | BCM2835FBState *s = BCM2835_FB(dev); | |
367 | Error *err = NULL; | |
368 | Object *obj; | |
369 | ||
370 | if (s->vcram_base == 0) { | |
371 | error_setg(errp, "%s: required vcram-base property not set", __func__); | |
372 | return; | |
373 | } | |
374 | ||
375 | obj = object_property_get_link(OBJECT(dev), "dma-mr", &err); | |
376 | if (obj == NULL) { | |
377 | error_setg(errp, "%s: required dma-mr link not found: %s", | |
378 | __func__, error_get_pretty(err)); | |
379 | return; | |
380 | } | |
381 | ||
382 | s->dma_mr = MEMORY_REGION(obj); | |
383 | address_space_init(&s->dma_as, s->dma_mr, NULL); | |
384 | ||
385 | bcm2835_fb_reset(dev); | |
386 | ||
387 | s->con = graphic_console_init(dev, 0, &vgafb_ops, s); | |
388 | qemu_console_resize(s->con, s->xres, s->yres); | |
389 | } | |
390 | ||
391 | static Property bcm2835_fb_props[] = { | |
392 | DEFINE_PROP_UINT32("vcram-base", BCM2835FBState, vcram_base, 0),/*required*/ | |
393 | DEFINE_PROP_UINT32("vcram-size", BCM2835FBState, vcram_size, | |
394 | DEFAULT_VCRAM_SIZE), | |
395 | DEFINE_PROP_UINT32("xres", BCM2835FBState, xres, 640), | |
396 | DEFINE_PROP_UINT32("yres", BCM2835FBState, yres, 480), | |
397 | DEFINE_PROP_UINT32("bpp", BCM2835FBState, bpp, 16), | |
398 | DEFINE_PROP_UINT32("pixo", BCM2835FBState, pixo, 1), /* 1=RGB, 0=BGR */ | |
399 | DEFINE_PROP_UINT32("alpha", BCM2835FBState, alpha, 2), /* alpha ignored */ | |
400 | DEFINE_PROP_END_OF_LIST() | |
401 | }; | |
402 | ||
403 | static void bcm2835_fb_class_init(ObjectClass *klass, void *data) | |
404 | { | |
405 | DeviceClass *dc = DEVICE_CLASS(klass); | |
406 | ||
407 | dc->props = bcm2835_fb_props; | |
408 | dc->realize = bcm2835_fb_realize; | |
409 | dc->reset = bcm2835_fb_reset; | |
410 | dc->vmsd = &vmstate_bcm2835_fb; | |
411 | } | |
412 | ||
413 | static TypeInfo bcm2835_fb_info = { | |
414 | .name = TYPE_BCM2835_FB, | |
415 | .parent = TYPE_SYS_BUS_DEVICE, | |
416 | .instance_size = sizeof(BCM2835FBState), | |
417 | .class_init = bcm2835_fb_class_init, | |
418 | .instance_init = bcm2835_fb_init, | |
419 | }; | |
420 | ||
421 | static void bcm2835_fb_register_types(void) | |
422 | { | |
423 | type_register_static(&bcm2835_fb_info); | |
424 | } | |
425 | ||
426 | type_init(bcm2835_fb_register_types) |