nomaintainer: Fix Lesser GPL version number
[qemu/ar7.git] / hw / display / milkymist-tmu2.c
blob02a28c807b5b88fcaaf493fac4152be64c33432a
1 /*
2 * QEMU model of the Milkymist texture mapping unit.
4 * Copyright (c) 2010 Michael Walle <michael@walle.cc>
5 * Copyright (c) 2010 Sebastien Bourdeauducq
6 * <sebastien.bourdeauducq@lekernel.net>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
22 * Specification available at:
23 * http://milkymist.walle.cc/socdoc/tmu2.pdf
27 #include "qemu/osdep.h"
28 #include "hw/irq.h"
29 #include "hw/sysbus.h"
30 #include "migration/vmstate.h"
31 #include "trace.h"
32 #include "qapi/error.h"
33 #include "qemu/error-report.h"
34 #include "qemu/module.h"
35 #include "qapi/error.h"
36 #include "hw/display/milkymist_tmu2.h"
38 #include <X11/Xlib.h>
39 #include <epoxy/gl.h>
40 #include <epoxy/glx.h>
41 #include "qom/object.h"
43 enum {
44 R_CTL = 0,
45 R_HMESHLAST,
46 R_VMESHLAST,
47 R_BRIGHTNESS,
48 R_CHROMAKEY,
49 R_VERTICESADDR,
50 R_TEXFBUF,
51 R_TEXHRES,
52 R_TEXVRES,
53 R_TEXHMASK,
54 R_TEXVMASK,
55 R_DSTFBUF,
56 R_DSTHRES,
57 R_DSTVRES,
58 R_DSTHOFFSET,
59 R_DSTVOFFSET,
60 R_DSTSQUAREW,
61 R_DSTSQUAREH,
62 R_ALPHA,
63 R_MAX
66 enum {
67 CTL_START_BUSY = (1<<0),
68 CTL_CHROMAKEY = (1<<1),
71 enum {
72 MAX_BRIGHTNESS = 63,
73 MAX_ALPHA = 63,
76 enum {
77 MESH_MAXSIZE = 128,
80 struct vertex {
81 int x;
82 int y;
83 } QEMU_PACKED;
85 #define TYPE_MILKYMIST_TMU2 "milkymist-tmu2"
86 OBJECT_DECLARE_SIMPLE_TYPE(MilkymistTMU2State, MILKYMIST_TMU2)
88 struct MilkymistTMU2State {
89 SysBusDevice parent_obj;
91 MemoryRegion regs_region;
92 Chardev *chr;
93 qemu_irq irq;
95 uint32_t regs[R_MAX];
97 Display *dpy;
98 GLXFBConfig glx_fb_config;
99 GLXContext glx_context;
102 static const int glx_fbconfig_attr[] = {
103 GLX_GREEN_SIZE, 5,
104 GLX_GREEN_SIZE, 6,
105 GLX_BLUE_SIZE, 5,
106 None
109 static int tmu2_glx_init(MilkymistTMU2State *s)
111 GLXFBConfig *configs;
112 int nelements;
114 s->dpy = XOpenDisplay(NULL); /* FIXME: call XCloseDisplay() */
115 if (s->dpy == NULL) {
116 return 1;
119 configs = glXChooseFBConfig(s->dpy, 0, glx_fbconfig_attr, &nelements);
120 if (configs == NULL) {
121 return 1;
124 s->glx_fb_config = *configs;
125 XFree(configs);
127 /* FIXME: call glXDestroyContext() */
128 s->glx_context = glXCreateNewContext(s->dpy, s->glx_fb_config,
129 GLX_RGBA_TYPE, NULL, 1);
130 if (s->glx_context == NULL) {
131 return 1;
134 return 0;
137 static void tmu2_gl_map(struct vertex *mesh, int texhres, int texvres,
138 int hmeshlast, int vmeshlast, int ho, int vo, int sw, int sh)
140 int x, y;
141 int x0, y0, x1, y1;
142 int u0, v0, u1, v1, u2, v2, u3, v3;
143 double xscale = 1.0 / ((double)(64 * texhres));
144 double yscale = 1.0 / ((double)(64 * texvres));
146 glLoadIdentity();
147 glTranslatef(ho, vo, 0);
148 glEnable(GL_TEXTURE_2D);
149 glBegin(GL_QUADS);
151 for (y = 0; y < vmeshlast; y++) {
152 y0 = y * sh;
153 y1 = y0 + sh;
154 for (x = 0; x < hmeshlast; x++) {
155 x0 = x * sw;
156 x1 = x0 + sw;
158 u0 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x].x);
159 v0 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x].y);
160 u1 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x + 1].x);
161 v1 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x + 1].y);
162 u2 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x + 1].x);
163 v2 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x + 1].y);
164 u3 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x].x);
165 v3 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x].y);
167 glTexCoord2d(((double)u0) * xscale, ((double)v0) * yscale);
168 glVertex3i(x0, y0, 0);
169 glTexCoord2d(((double)u1) * xscale, ((double)v1) * yscale);
170 glVertex3i(x1, y0, 0);
171 glTexCoord2d(((double)u2) * xscale, ((double)v2) * yscale);
172 glVertex3i(x1, y1, 0);
173 glTexCoord2d(((double)u3) * xscale, ((double)v3) * yscale);
174 glVertex3i(x0, y1, 0);
178 glEnd();
181 static void tmu2_start(MilkymistTMU2State *s)
183 int pbuffer_attrib[6] = {
184 GLX_PBUFFER_WIDTH,
186 GLX_PBUFFER_HEIGHT,
188 GLX_PRESERVED_CONTENTS,
189 True
192 GLXPbuffer pbuffer;
193 GLuint texture;
194 void *fb;
195 hwaddr fb_len;
196 void *mesh;
197 hwaddr mesh_len;
198 float m;
200 trace_milkymist_tmu2_start();
202 /* Create and set up a suitable OpenGL context */
203 pbuffer_attrib[1] = s->regs[R_DSTHRES];
204 pbuffer_attrib[3] = s->regs[R_DSTVRES];
205 pbuffer = glXCreatePbuffer(s->dpy, s->glx_fb_config, pbuffer_attrib);
206 glXMakeContextCurrent(s->dpy, pbuffer, pbuffer, s->glx_context);
208 /* Fixup endianness. TODO: would it work on BE hosts? */
209 glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);
210 glPixelStorei(GL_PACK_SWAP_BYTES, 1);
212 /* Row alignment */
213 glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
214 glPixelStorei(GL_PACK_ALIGNMENT, 2);
216 /* Read the QEMU source framebuffer into an OpenGL texture */
217 glGenTextures(1, &texture);
218 glBindTexture(GL_TEXTURE_2D, texture);
219 fb_len = 2ULL * s->regs[R_TEXHRES] * s->regs[R_TEXVRES];
220 fb = cpu_physical_memory_map(s->regs[R_TEXFBUF], &fb_len, false);
221 if (fb == NULL) {
222 glDeleteTextures(1, &texture);
223 glXMakeContextCurrent(s->dpy, None, None, NULL);
224 glXDestroyPbuffer(s->dpy, pbuffer);
225 return;
227 glTexImage2D(GL_TEXTURE_2D, 0, 3, s->regs[R_TEXHRES], s->regs[R_TEXVRES],
228 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, fb);
229 cpu_physical_memory_unmap(fb, fb_len, 0, fb_len);
231 /* Set up texturing options */
232 /* WARNING:
233 * Many cases of TMU2 masking are not supported by OpenGL.
234 * We only implement the most common ones:
235 * - full bilinear filtering vs. nearest texel
236 * - texture clamping vs. texture wrapping
238 if ((s->regs[R_TEXHMASK] & 0x3f) > 0x20) {
239 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
240 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
241 } else {
242 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
243 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
245 if ((s->regs[R_TEXHMASK] >> 6) & s->regs[R_TEXHRES]) {
246 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
247 } else {
248 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
250 if ((s->regs[R_TEXVMASK] >> 6) & s->regs[R_TEXVRES]) {
251 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
252 } else {
253 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
256 /* Translucency and decay */
257 glEnable(GL_BLEND);
258 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
259 m = (float)(s->regs[R_BRIGHTNESS] + 1) / 64.0f;
260 glColor4f(m, m, m, (float)(s->regs[R_ALPHA] + 1) / 64.0f);
262 /* Read the QEMU dest. framebuffer into the OpenGL framebuffer */
263 fb_len = 2ULL * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];
264 fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, false);
265 if (fb == NULL) {
266 glDeleteTextures(1, &texture);
267 glXMakeContextCurrent(s->dpy, None, None, NULL);
268 glXDestroyPbuffer(s->dpy, pbuffer);
269 return;
272 glDrawPixels(s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB,
273 GL_UNSIGNED_SHORT_5_6_5, fb);
274 cpu_physical_memory_unmap(fb, fb_len, 0, fb_len);
275 glViewport(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES]);
276 glMatrixMode(GL_PROJECTION);
277 glLoadIdentity();
278 glOrtho(0.0, s->regs[R_DSTHRES], 0.0, s->regs[R_DSTVRES], -1.0, 1.0);
279 glMatrixMode(GL_MODELVIEW);
281 /* Map the texture */
282 mesh_len = MESH_MAXSIZE*MESH_MAXSIZE*sizeof(struct vertex);
283 mesh = cpu_physical_memory_map(s->regs[R_VERTICESADDR], &mesh_len, false);
284 if (mesh == NULL) {
285 glDeleteTextures(1, &texture);
286 glXMakeContextCurrent(s->dpy, None, None, NULL);
287 glXDestroyPbuffer(s->dpy, pbuffer);
288 return;
291 tmu2_gl_map((struct vertex *)mesh,
292 s->regs[R_TEXHRES], s->regs[R_TEXVRES],
293 s->regs[R_HMESHLAST], s->regs[R_VMESHLAST],
294 s->regs[R_DSTHOFFSET], s->regs[R_DSTVOFFSET],
295 s->regs[R_DSTSQUAREW], s->regs[R_DSTSQUAREH]);
296 cpu_physical_memory_unmap(mesh, mesh_len, 0, mesh_len);
298 /* Write back the OpenGL framebuffer to the QEMU framebuffer */
299 fb_len = 2ULL * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];
300 fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, true);
301 if (fb == NULL) {
302 glDeleteTextures(1, &texture);
303 glXMakeContextCurrent(s->dpy, None, None, NULL);
304 glXDestroyPbuffer(s->dpy, pbuffer);
305 return;
308 glReadPixels(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB,
309 GL_UNSIGNED_SHORT_5_6_5, fb);
310 cpu_physical_memory_unmap(fb, fb_len, 1, fb_len);
312 /* Free OpenGL allocs */
313 glDeleteTextures(1, &texture);
314 glXMakeContextCurrent(s->dpy, None, None, NULL);
315 glXDestroyPbuffer(s->dpy, pbuffer);
317 s->regs[R_CTL] &= ~CTL_START_BUSY;
319 trace_milkymist_tmu2_pulse_irq();
320 qemu_irq_pulse(s->irq);
323 static uint64_t tmu2_read(void *opaque, hwaddr addr,
324 unsigned size)
326 MilkymistTMU2State *s = opaque;
327 uint32_t r = 0;
329 addr >>= 2;
330 switch (addr) {
331 case R_CTL:
332 case R_HMESHLAST:
333 case R_VMESHLAST:
334 case R_BRIGHTNESS:
335 case R_CHROMAKEY:
336 case R_VERTICESADDR:
337 case R_TEXFBUF:
338 case R_TEXHRES:
339 case R_TEXVRES:
340 case R_TEXHMASK:
341 case R_TEXVMASK:
342 case R_DSTFBUF:
343 case R_DSTHRES:
344 case R_DSTVRES:
345 case R_DSTHOFFSET:
346 case R_DSTVOFFSET:
347 case R_DSTSQUAREW:
348 case R_DSTSQUAREH:
349 case R_ALPHA:
350 r = s->regs[addr];
351 break;
353 default:
354 error_report("milkymist_tmu2: read access to unknown register 0x"
355 TARGET_FMT_plx, addr << 2);
356 break;
359 trace_milkymist_tmu2_memory_read(addr << 2, r);
361 return r;
364 static void tmu2_check_registers(MilkymistTMU2State *s)
366 if (s->regs[R_BRIGHTNESS] > MAX_BRIGHTNESS) {
367 error_report("milkymist_tmu2: max brightness is %d", MAX_BRIGHTNESS);
370 if (s->regs[R_ALPHA] > MAX_ALPHA) {
371 error_report("milkymist_tmu2: max alpha is %d", MAX_ALPHA);
374 if (s->regs[R_VERTICESADDR] & 0x07) {
375 error_report("milkymist_tmu2: vertex mesh address has to be 64-bit "
376 "aligned");
379 if (s->regs[R_TEXFBUF] & 0x01) {
380 error_report("milkymist_tmu2: texture buffer address has to be "
381 "16-bit aligned");
385 static void tmu2_write(void *opaque, hwaddr addr, uint64_t value,
386 unsigned size)
388 MilkymistTMU2State *s = opaque;
390 trace_milkymist_tmu2_memory_write(addr, value);
392 addr >>= 2;
393 switch (addr) {
394 case R_CTL:
395 s->regs[addr] = value;
396 if (value & CTL_START_BUSY) {
397 tmu2_start(s);
399 break;
400 case R_BRIGHTNESS:
401 case R_HMESHLAST:
402 case R_VMESHLAST:
403 case R_CHROMAKEY:
404 case R_VERTICESADDR:
405 case R_TEXFBUF:
406 case R_TEXHRES:
407 case R_TEXVRES:
408 case R_TEXHMASK:
409 case R_TEXVMASK:
410 case R_DSTFBUF:
411 case R_DSTHRES:
412 case R_DSTVRES:
413 case R_DSTHOFFSET:
414 case R_DSTVOFFSET:
415 case R_DSTSQUAREW:
416 case R_DSTSQUAREH:
417 case R_ALPHA:
418 s->regs[addr] = value;
419 break;
421 default:
422 error_report("milkymist_tmu2: write access to unknown register 0x"
423 TARGET_FMT_plx, addr << 2);
424 break;
427 tmu2_check_registers(s);
430 static const MemoryRegionOps tmu2_mmio_ops = {
431 .read = tmu2_read,
432 .write = tmu2_write,
433 .valid = {
434 .min_access_size = 4,
435 .max_access_size = 4,
437 .endianness = DEVICE_NATIVE_ENDIAN,
440 static void milkymist_tmu2_reset(DeviceState *d)
442 MilkymistTMU2State *s = MILKYMIST_TMU2(d);
443 int i;
445 for (i = 0; i < R_MAX; i++) {
446 s->regs[i] = 0;
450 static void milkymist_tmu2_init(Object *obj)
452 MilkymistTMU2State *s = MILKYMIST_TMU2(obj);
453 SysBusDevice *dev = SYS_BUS_DEVICE(obj);
455 sysbus_init_irq(dev, &s->irq);
457 memory_region_init_io(&s->regs_region, obj, &tmu2_mmio_ops, s,
458 "milkymist-tmu2", R_MAX * 4);
459 sysbus_init_mmio(dev, &s->regs_region);
462 static void milkymist_tmu2_realize(DeviceState *dev, Error **errp)
464 MilkymistTMU2State *s = MILKYMIST_TMU2(dev);
466 if (tmu2_glx_init(s)) {
467 error_setg(errp, "tmu2_glx_init failed");
471 static const VMStateDescription vmstate_milkymist_tmu2 = {
472 .name = "milkymist-tmu2",
473 .version_id = 1,
474 .minimum_version_id = 1,
475 .fields = (VMStateField[]) {
476 VMSTATE_UINT32_ARRAY(regs, MilkymistTMU2State, R_MAX),
477 VMSTATE_END_OF_LIST()
481 static void milkymist_tmu2_class_init(ObjectClass *klass, void *data)
483 DeviceClass *dc = DEVICE_CLASS(klass);
485 dc->realize = milkymist_tmu2_realize;
486 dc->reset = milkymist_tmu2_reset;
487 dc->vmsd = &vmstate_milkymist_tmu2;
490 static const TypeInfo milkymist_tmu2_info = {
491 .name = TYPE_MILKYMIST_TMU2,
492 .parent = TYPE_SYS_BUS_DEVICE,
493 .instance_size = sizeof(MilkymistTMU2State),
494 .instance_init = milkymist_tmu2_init,
495 .class_init = milkymist_tmu2_class_init,
498 static void milkymist_tmu2_register_types(void)
500 type_register_static(&milkymist_tmu2_info);
503 type_init(milkymist_tmu2_register_types)
505 DeviceState *milkymist_tmu2_create(hwaddr base, qemu_irq irq)
507 DeviceState *dev;
508 Display *d;
509 GLXFBConfig *configs;
510 int nelements;
511 int ver_major, ver_minor;
513 /* check that GLX will work */
514 d = XOpenDisplay(NULL);
515 if (d == NULL) {
516 return NULL;
519 if (!glXQueryVersion(d, &ver_major, &ver_minor)) {
521 * Yeah, sometimes getting the GLX version can fail.
522 * Isn't X beautiful?
524 XCloseDisplay(d);
525 return NULL;
528 if ((ver_major < 1) || ((ver_major == 1) && (ver_minor < 3))) {
529 printf("Your GLX version is %d.%d,"
530 "but TMU emulation needs at least 1.3. TMU disabled.\n",
531 ver_major, ver_minor);
532 XCloseDisplay(d);
533 return NULL;
536 configs = glXChooseFBConfig(d, 0, glx_fbconfig_attr, &nelements);
537 if (configs == NULL) {
538 XCloseDisplay(d);
539 return NULL;
542 XFree(configs);
543 XCloseDisplay(d);
545 dev = qdev_new(TYPE_MILKYMIST_TMU2);
546 sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
547 sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
548 sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq);
550 return dev;