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" |
27 | #include "qapi/error.h" |
28 | #include "hw/display/bcm2835_fb.h" |
29 | #include "hw/hw.h" |
30 | #include "hw/irq.h" |
31 | #include "framebuffer.h" |
32 | #include "ui/pixel_ops.h" |
33 | #include "hw/misc/bcm2835_mbox_defs.h" |
34 | #include "hw/qdev-properties.h" |
35 | #include "migration/vmstate.h" |
36 | #include "qemu/log.h" |
37 | #include "qemu/module.h" |
38 | |
39 | #define DEFAULT_VCRAM_SIZE 0x4000000 |
40 | #define BCM2835_FB_OFFSET 0x00100000 |
41 | |
42 | /* Maximum permitted framebuffer size; experimentally determined on an rpi2 */ |
43 | #define XRES_MAX 3840 |
44 | #define YRES_MAX 2560 |
45 | /* Framebuffer size used if guest requests zero size */ |
46 | #define XRES_SMALL 592 |
47 | #define YRES_SMALL 488 |
48 | |
49 | static void fb_invalidate_display(void *opaque) |
50 | { |
51 | BCM2835FBState *s = BCM2835_FB(opaque); |
52 | |
53 | s->invalidate = true; |
54 | } |
55 | |
56 | static void draw_line_src16(void *opaque, uint8_t *dst, const uint8_t *src, |
57 | int width, int deststep) |
58 | { |
59 | BCM2835FBState *s = opaque; |
60 | uint16_t rgb565; |
61 | uint32_t rgb888; |
62 | uint8_t r, g, b; |
63 | DisplaySurface *surface = qemu_console_surface(s->con); |
64 | int bpp = surface_bits_per_pixel(surface); |
65 | |
66 | while (width--) { |
67 | switch (s->config.bpp) { |
68 | case 8: |
69 | /* lookup palette starting at video ram base |
70 | * TODO: cache translation, rather than doing this each time! |
71 | */ |
72 | rgb888 = ldl_le_phys(&s->dma_as, s->vcram_base + (*src << 2)); |
73 | r = (rgb888 >> 0) & 0xff; |
74 | g = (rgb888 >> 8) & 0xff; |
75 | b = (rgb888 >> 16) & 0xff; |
76 | src++; |
77 | break; |
78 | case 16: |
79 | rgb565 = lduw_le_p(src); |
80 | r = ((rgb565 >> 11) & 0x1f) << 3; |
81 | g = ((rgb565 >> 5) & 0x3f) << 2; |
82 | b = ((rgb565 >> 0) & 0x1f) << 3; |
83 | src += 2; |
84 | break; |
85 | case 24: |
86 | rgb888 = ldl_le_p(src); |
87 | r = (rgb888 >> 0) & 0xff; |
88 | g = (rgb888 >> 8) & 0xff; |
89 | b = (rgb888 >> 16) & 0xff; |
90 | src += 3; |
91 | break; |
92 | case 32: |
93 | rgb888 = ldl_le_p(src); |
94 | r = (rgb888 >> 0) & 0xff; |
95 | g = (rgb888 >> 8) & 0xff; |
96 | b = (rgb888 >> 16) & 0xff; |
97 | src += 4; |
98 | break; |
99 | default: |
100 | r = 0; |
101 | g = 0; |
102 | b = 0; |
103 | break; |
104 | } |
105 | |
106 | if (s->config.pixo == 0) { |
107 | /* swap to BGR pixel format */ |
108 | uint8_t tmp = r; |
109 | r = b; |
110 | b = tmp; |
111 | } |
112 | |
113 | switch (bpp) { |
114 | case 8: |
115 | *dst++ = rgb_to_pixel8(r, g, b); |
116 | break; |
117 | case 15: |
118 | *(uint16_t *)dst = rgb_to_pixel15(r, g, b); |
119 | dst += 2; |
120 | break; |
121 | case 16: |
122 | *(uint16_t *)dst = rgb_to_pixel16(r, g, b); |
123 | dst += 2; |
124 | break; |
125 | case 24: |
126 | rgb888 = rgb_to_pixel24(r, g, b); |
127 | *dst++ = rgb888 & 0xff; |
128 | *dst++ = (rgb888 >> 8) & 0xff; |
129 | *dst++ = (rgb888 >> 16) & 0xff; |
130 | break; |
131 | case 32: |
132 | *(uint32_t *)dst = rgb_to_pixel32(r, g, b); |
133 | dst += 4; |
134 | break; |
135 | default: |
136 | return; |
137 | } |
138 | } |
139 | } |
140 | |
141 | static bool fb_use_offsets(BCM2835FBConfig *config) |
142 | { |
143 | /* |
144 | * Return true if we should use the viewport offsets. |
145 | * Experimentally, the hardware seems to do this only if the |
146 | * viewport size is larger than the physical screen. (It doesn't |
147 | * prevent the guest setting this silly viewport setting, though...) |
148 | */ |
149 | return config->xres_virtual > config->xres && |
150 | config->yres_virtual > config->yres; |
151 | } |
152 | |
153 | static void fb_update_display(void *opaque) |
154 | { |
155 | BCM2835FBState *s = opaque; |
156 | DisplaySurface *surface = qemu_console_surface(s->con); |
157 | int first = 0; |
158 | int last = 0; |
159 | int src_width = 0; |
160 | int dest_width = 0; |
161 | uint32_t xoff = 0, yoff = 0; |
162 | |
163 | if (s->lock || !s->config.xres) { |
164 | return; |
165 | } |
166 | |
167 | src_width = bcm2835_fb_get_pitch(&s->config); |
168 | if (fb_use_offsets(&s->config)) { |
169 | xoff = s->config.xoffset; |
170 | yoff = s->config.yoffset; |
171 | } |
172 | |
173 | dest_width = s->config.xres; |
174 | |
175 | switch (surface_bits_per_pixel(surface)) { |
176 | case 0: |
177 | return; |
178 | case 8: |
179 | break; |
180 | case 15: |
181 | dest_width *= 2; |
182 | break; |
183 | case 16: |
184 | dest_width *= 2; |
185 | break; |
186 | case 24: |
187 | dest_width *= 3; |
188 | break; |
189 | case 32: |
190 | dest_width *= 4; |
191 | break; |
192 | default: |
193 | hw_error("bcm2835_fb: bad color depth\n" ); |
194 | break; |
195 | } |
196 | |
197 | if (s->invalidate) { |
198 | hwaddr base = s->config.base + xoff + (hwaddr)yoff * src_width; |
199 | framebuffer_update_memory_section(&s->fbsection, s->dma_mr, |
200 | base, |
201 | s->config.yres, src_width); |
202 | } |
203 | |
204 | framebuffer_update_display(surface, &s->fbsection, |
205 | s->config.xres, s->config.yres, |
206 | src_width, dest_width, 0, s->invalidate, |
207 | draw_line_src16, s, &first, &last); |
208 | |
209 | if (first >= 0) { |
210 | dpy_gfx_update(s->con, 0, first, s->config.xres, |
211 | last - first + 1); |
212 | } |
213 | |
214 | s->invalidate = false; |
215 | } |
216 | |
217 | void bcm2835_fb_validate_config(BCM2835FBConfig *config) |
218 | { |
219 | /* |
220 | * Validate the config, and clip any bogus values into range, |
221 | * as the hardware does. Note that fb_update_display() relies on |
222 | * this happening to prevent it from performing out-of-range |
223 | * accesses on redraw. |
224 | */ |
225 | config->xres = MIN(config->xres, XRES_MAX); |
226 | config->xres_virtual = MIN(config->xres_virtual, XRES_MAX); |
227 | config->yres = MIN(config->yres, YRES_MAX); |
228 | config->yres_virtual = MIN(config->yres_virtual, YRES_MAX); |
229 | |
230 | /* |
231 | * These are not minima: a 40x40 framebuffer will be accepted. |
232 | * They're only used as defaults if the guest asks for zero size. |
233 | */ |
234 | if (config->xres == 0) { |
235 | config->xres = XRES_SMALL; |
236 | } |
237 | if (config->yres == 0) { |
238 | config->yres = YRES_SMALL; |
239 | } |
240 | if (config->xres_virtual == 0) { |
241 | config->xres_virtual = config->xres; |
242 | } |
243 | if (config->yres_virtual == 0) { |
244 | config->yres_virtual = config->yres; |
245 | } |
246 | |
247 | if (fb_use_offsets(config)) { |
248 | /* Clip the offsets so the viewport is within the physical screen */ |
249 | config->xoffset = MIN(config->xoffset, |
250 | config->xres_virtual - config->xres); |
251 | config->yoffset = MIN(config->yoffset, |
252 | config->yres_virtual - config->yres); |
253 | } |
254 | } |
255 | |
256 | void bcm2835_fb_reconfigure(BCM2835FBState *s, BCM2835FBConfig *newconfig) |
257 | { |
258 | s->lock = true; |
259 | |
260 | s->config = *newconfig; |
261 | |
262 | s->invalidate = true; |
263 | qemu_console_resize(s->con, s->config.xres, s->config.yres); |
264 | s->lock = false; |
265 | } |
266 | |
267 | static void bcm2835_fb_mbox_push(BCM2835FBState *s, uint32_t value) |
268 | { |
269 | uint32_t pitch; |
270 | uint32_t size; |
271 | BCM2835FBConfig newconf; |
272 | |
273 | value &= ~0xf; |
274 | |
275 | newconf.xres = ldl_le_phys(&s->dma_as, value); |
276 | newconf.yres = ldl_le_phys(&s->dma_as, value + 4); |
277 | newconf.xres_virtual = ldl_le_phys(&s->dma_as, value + 8); |
278 | newconf.yres_virtual = ldl_le_phys(&s->dma_as, value + 12); |
279 | newconf.bpp = ldl_le_phys(&s->dma_as, value + 20); |
280 | newconf.xoffset = ldl_le_phys(&s->dma_as, value + 24); |
281 | newconf.yoffset = ldl_le_phys(&s->dma_as, value + 28); |
282 | |
283 | newconf.base = s->vcram_base | (value & 0xc0000000); |
284 | newconf.base += BCM2835_FB_OFFSET; |
285 | |
286 | bcm2835_fb_validate_config(&newconf); |
287 | |
288 | pitch = bcm2835_fb_get_pitch(&newconf); |
289 | size = bcm2835_fb_get_size(&newconf); |
290 | |
291 | stl_le_phys(&s->dma_as, value + 16, pitch); |
292 | stl_le_phys(&s->dma_as, value + 32, newconf.base); |
293 | stl_le_phys(&s->dma_as, value + 36, size); |
294 | |
295 | bcm2835_fb_reconfigure(s, &newconf); |
296 | } |
297 | |
298 | static uint64_t bcm2835_fb_read(void *opaque, hwaddr offset, unsigned size) |
299 | { |
300 | BCM2835FBState *s = opaque; |
301 | uint32_t res = 0; |
302 | |
303 | switch (offset) { |
304 | case MBOX_AS_DATA: |
305 | res = MBOX_CHAN_FB; |
306 | s->pending = false; |
307 | qemu_set_irq(s->mbox_irq, 0); |
308 | break; |
309 | |
310 | case MBOX_AS_PENDING: |
311 | res = s->pending; |
312 | break; |
313 | |
314 | default: |
315 | qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx"\n" , |
316 | __func__, offset); |
317 | return 0; |
318 | } |
319 | |
320 | return res; |
321 | } |
322 | |
323 | static void bcm2835_fb_write(void *opaque, hwaddr offset, uint64_t value, |
324 | unsigned size) |
325 | { |
326 | BCM2835FBState *s = opaque; |
327 | |
328 | switch (offset) { |
329 | case MBOX_AS_DATA: |
330 | /* bcm2835_mbox should check our pending status before pushing */ |
331 | assert(!s->pending); |
332 | s->pending = true; |
333 | bcm2835_fb_mbox_push(s, value); |
334 | qemu_set_irq(s->mbox_irq, 1); |
335 | break; |
336 | |
337 | default: |
338 | qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %" HWADDR_PRIx"\n" , |
339 | __func__, offset); |
340 | return; |
341 | } |
342 | } |
343 | |
344 | static const MemoryRegionOps bcm2835_fb_ops = { |
345 | .read = bcm2835_fb_read, |
346 | .write = bcm2835_fb_write, |
347 | .endianness = DEVICE_NATIVE_ENDIAN, |
348 | .valid.min_access_size = 4, |
349 | .valid.max_access_size = 4, |
350 | }; |
351 | |
352 | static const VMStateDescription vmstate_bcm2835_fb = { |
353 | .name = TYPE_BCM2835_FB, |
354 | .version_id = 1, |
355 | .minimum_version_id = 1, |
356 | .fields = (VMStateField[]) { |
357 | VMSTATE_BOOL(lock, BCM2835FBState), |
358 | VMSTATE_BOOL(invalidate, BCM2835FBState), |
359 | VMSTATE_BOOL(pending, BCM2835FBState), |
360 | VMSTATE_UINT32(config.xres, BCM2835FBState), |
361 | VMSTATE_UINT32(config.yres, BCM2835FBState), |
362 | VMSTATE_UINT32(config.xres_virtual, BCM2835FBState), |
363 | VMSTATE_UINT32(config.yres_virtual, BCM2835FBState), |
364 | VMSTATE_UINT32(config.xoffset, BCM2835FBState), |
365 | VMSTATE_UINT32(config.yoffset, BCM2835FBState), |
366 | VMSTATE_UINT32(config.bpp, BCM2835FBState), |
367 | VMSTATE_UINT32(config.base, BCM2835FBState), |
368 | VMSTATE_UNUSED(8), /* Was pitch and size */ |
369 | VMSTATE_UINT32(config.pixo, BCM2835FBState), |
370 | VMSTATE_UINT32(config.alpha, BCM2835FBState), |
371 | VMSTATE_END_OF_LIST() |
372 | } |
373 | }; |
374 | |
375 | static const GraphicHwOps vgafb_ops = { |
376 | .invalidate = fb_invalidate_display, |
377 | .gfx_update = fb_update_display, |
378 | }; |
379 | |
380 | static void bcm2835_fb_init(Object *obj) |
381 | { |
382 | BCM2835FBState *s = BCM2835_FB(obj); |
383 | |
384 | memory_region_init_io(&s->iomem, obj, &bcm2835_fb_ops, s, TYPE_BCM2835_FB, |
385 | 0x10); |
386 | sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); |
387 | sysbus_init_irq(SYS_BUS_DEVICE(s), &s->mbox_irq); |
388 | } |
389 | |
390 | static void bcm2835_fb_reset(DeviceState *dev) |
391 | { |
392 | BCM2835FBState *s = BCM2835_FB(dev); |
393 | |
394 | s->pending = false; |
395 | |
396 | s->config = s->initial_config; |
397 | |
398 | s->invalidate = true; |
399 | s->lock = false; |
400 | } |
401 | |
402 | static void bcm2835_fb_realize(DeviceState *dev, Error **errp) |
403 | { |
404 | BCM2835FBState *s = BCM2835_FB(dev); |
405 | Error *err = NULL; |
406 | Object *obj; |
407 | |
408 | if (s->vcram_base == 0) { |
409 | error_setg(errp, "%s: required vcram-base property not set" , __func__); |
410 | return; |
411 | } |
412 | |
413 | obj = object_property_get_link(OBJECT(dev), "dma-mr" , &err); |
414 | if (obj == NULL) { |
415 | error_setg(errp, "%s: required dma-mr link not found: %s" , |
416 | __func__, error_get_pretty(err)); |
417 | return; |
418 | } |
419 | |
420 | /* Fill in the parts of initial_config that are not set by QOM properties */ |
421 | s->initial_config.xres_virtual = s->initial_config.xres; |
422 | s->initial_config.yres_virtual = s->initial_config.yres; |
423 | s->initial_config.xoffset = 0; |
424 | s->initial_config.yoffset = 0; |
425 | s->initial_config.base = s->vcram_base + BCM2835_FB_OFFSET; |
426 | |
427 | s->dma_mr = MEMORY_REGION(obj); |
428 | address_space_init(&s->dma_as, s->dma_mr, NULL); |
429 | |
430 | bcm2835_fb_reset(dev); |
431 | |
432 | s->con = graphic_console_init(dev, 0, &vgafb_ops, s); |
433 | qemu_console_resize(s->con, s->config.xres, s->config.yres); |
434 | } |
435 | |
436 | static Property bcm2835_fb_props[] = { |
437 | DEFINE_PROP_UINT32("vcram-base" , BCM2835FBState, vcram_base, 0),/*required*/ |
438 | DEFINE_PROP_UINT32("vcram-size" , BCM2835FBState, vcram_size, |
439 | DEFAULT_VCRAM_SIZE), |
440 | DEFINE_PROP_UINT32("xres" , BCM2835FBState, initial_config.xres, 640), |
441 | DEFINE_PROP_UINT32("yres" , BCM2835FBState, initial_config.yres, 480), |
442 | DEFINE_PROP_UINT32("bpp" , BCM2835FBState, initial_config.bpp, 16), |
443 | DEFINE_PROP_UINT32("pixo" , BCM2835FBState, |
444 | initial_config.pixo, 1), /* 1=RGB, 0=BGR */ |
445 | DEFINE_PROP_UINT32("alpha" , BCM2835FBState, |
446 | initial_config.alpha, 2), /* alpha ignored */ |
447 | DEFINE_PROP_END_OF_LIST() |
448 | }; |
449 | |
450 | static void bcm2835_fb_class_init(ObjectClass *klass, void *data) |
451 | { |
452 | DeviceClass *dc = DEVICE_CLASS(klass); |
453 | |
454 | dc->props = bcm2835_fb_props; |
455 | dc->realize = bcm2835_fb_realize; |
456 | dc->reset = bcm2835_fb_reset; |
457 | dc->vmsd = &vmstate_bcm2835_fb; |
458 | } |
459 | |
460 | static TypeInfo bcm2835_fb_info = { |
461 | .name = TYPE_BCM2835_FB, |
462 | .parent = TYPE_SYS_BUS_DEVICE, |
463 | .instance_size = sizeof(BCM2835FBState), |
464 | .class_init = bcm2835_fb_class_init, |
465 | .instance_init = bcm2835_fb_init, |
466 | }; |
467 | |
468 | static void bcm2835_fb_register_types(void) |
469 | { |
470 | type_register_static(&bcm2835_fb_info); |
471 | } |
472 | |
473 | type_init(bcm2835_fb_register_types) |
474 | |