spice-chardev: listen to frontend guest open / close
[qemu/ar7.git] / hw / milkymist-tmu2.c
blob9cebe3173b82274dccddcb49e21e4693ae84ef87
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 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://www.milkymist.org/socdoc/tmu2.pdf
27 #include "hw.h"
28 #include "sysbus.h"
29 #include "trace.h"
30 #include "qemu-error.h"
32 #include <X11/Xlib.h>
33 #include <GL/gl.h>
34 #include <GL/glx.h>
36 enum {
37 R_CTL = 0,
38 R_HMESHLAST,
39 R_VMESHLAST,
40 R_BRIGHTNESS,
41 R_CHROMAKEY,
42 R_VERTICESADDR,
43 R_TEXFBUF,
44 R_TEXHRES,
45 R_TEXVRES,
46 R_TEXHMASK,
47 R_TEXVMASK,
48 R_DSTFBUF,
49 R_DSTHRES,
50 R_DSTVRES,
51 R_DSTHOFFSET,
52 R_DSTVOFFSET,
53 R_DSTSQUAREW,
54 R_DSTSQUAREH,
55 R_ALPHA,
56 R_MAX
59 enum {
60 CTL_START_BUSY = (1<<0),
61 CTL_CHROMAKEY = (1<<1),
64 enum {
65 MAX_BRIGHTNESS = 63,
66 MAX_ALPHA = 63,
69 enum {
70 MESH_MAXSIZE = 128,
73 struct vertex {
74 int x;
75 int y;
76 } __attribute__((packed));
78 struct MilkymistTMU2State {
79 SysBusDevice busdev;
80 CharDriverState *chr;
81 qemu_irq irq;
83 uint32_t regs[R_MAX];
85 Display *dpy;
86 GLXFBConfig glx_fb_config;
87 GLXContext glx_context;
89 typedef struct MilkymistTMU2State MilkymistTMU2State;
91 static const int glx_fbconfig_attr[] = {
92 GLX_GREEN_SIZE, 5,
93 GLX_GREEN_SIZE, 6,
94 GLX_BLUE_SIZE, 5,
95 None
98 static int tmu2_glx_init(MilkymistTMU2State *s)
100 GLXFBConfig *configs;
101 int nelements;
103 s->dpy = XOpenDisplay(NULL); /* FIXME: call XCloseDisplay() */
104 if (s->dpy == NULL) {
105 return 1;
108 configs = glXChooseFBConfig(s->dpy, 0, glx_fbconfig_attr, &nelements);
109 if (configs == NULL) {
110 return 1;
113 s->glx_fb_config = *configs;
114 XFree(configs);
116 /* FIXME: call glXDestroyContext() */
117 s->glx_context = glXCreateNewContext(s->dpy, s->glx_fb_config,
118 GLX_RGBA_TYPE, NULL, 1);
119 if (s->glx_context == NULL) {
120 return 1;
123 return 0;
126 static void tmu2_gl_map(struct vertex *mesh, int texhres, int texvres,
127 int hmeshlast, int vmeshlast, int ho, int vo, int sw, int sh)
129 int x, y;
130 int x0, y0, x1, y1;
131 int u0, v0, u1, v1, u2, v2, u3, v3;
132 double xscale = 1.0 / ((double)(64 * texhres));
133 double yscale = 1.0 / ((double)(64 * texvres));
135 glLoadIdentity();
136 glTranslatef(ho, vo, 0);
137 glEnable(GL_TEXTURE_2D);
138 glBegin(GL_QUADS);
140 for (y = 0; y < vmeshlast; y++) {
141 y0 = y * sh;
142 y1 = y0 + sh;
143 for (x = 0; x < hmeshlast; x++) {
144 x0 = x * sw;
145 x1 = x0 + sw;
147 u0 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x].x);
148 v0 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x].y);
149 u1 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x + 1].x);
150 v1 = be32_to_cpu(mesh[MESH_MAXSIZE * y + x + 1].y);
151 u2 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x + 1].x);
152 v2 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x + 1].y);
153 u3 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x].x);
154 v3 = be32_to_cpu(mesh[MESH_MAXSIZE * (y + 1) + x].y);
156 glTexCoord2d(((double)u0) * xscale, ((double)v0) * yscale);
157 glVertex3i(x0, y0, 0);
158 glTexCoord2d(((double)u1) * xscale, ((double)v1) * yscale);
159 glVertex3i(x1, y0, 0);
160 glTexCoord2d(((double)u2) * xscale, ((double)v2) * yscale);
161 glVertex3i(x1, y1, 0);
162 glTexCoord2d(((double)u3) * xscale, ((double)v3) * yscale);
163 glVertex3i(x0, y1, 0);
167 glEnd();
170 static void tmu2_start(MilkymistTMU2State *s)
172 int pbuffer_attrib[6] = {
173 GLX_PBUFFER_WIDTH,
175 GLX_PBUFFER_HEIGHT,
177 GLX_PRESERVED_CONTENTS,
178 True
181 GLXPbuffer pbuffer;
182 GLuint texture;
183 void *fb;
184 target_phys_addr_t fb_len;
185 void *mesh;
186 target_phys_addr_t mesh_len;
187 float m;
189 trace_milkymist_tmu2_start();
191 /* Create and set up a suitable OpenGL context */
192 pbuffer_attrib[1] = s->regs[R_DSTHRES];
193 pbuffer_attrib[3] = s->regs[R_DSTVRES];
194 pbuffer = glXCreatePbuffer(s->dpy, s->glx_fb_config, pbuffer_attrib);
195 glXMakeContextCurrent(s->dpy, pbuffer, pbuffer, s->glx_context);
197 /* Fixup endianness. TODO: would it work on BE hosts? */
198 glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);
199 glPixelStorei(GL_PACK_SWAP_BYTES, 1);
201 /* Row alignment */
202 glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
203 glPixelStorei(GL_PACK_ALIGNMENT, 2);
205 /* Read the QEMU source framebuffer into an OpenGL texture */
206 glGenTextures(1, &texture);
207 glBindTexture(GL_TEXTURE_2D, texture);
208 fb_len = 2*s->regs[R_TEXHRES]*s->regs[R_TEXVRES];
209 fb = cpu_physical_memory_map(s->regs[R_TEXFBUF], &fb_len, 0);
210 if (fb == NULL) {
211 glDeleteTextures(1, &texture);
212 glXMakeContextCurrent(s->dpy, None, None, NULL);
213 glXDestroyPbuffer(s->dpy, pbuffer);
214 return;
216 glTexImage2D(GL_TEXTURE_2D, 0, 3, s->regs[R_TEXHRES], s->regs[R_TEXVRES],
217 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, fb);
218 cpu_physical_memory_unmap(fb, fb_len, 0, fb_len);
220 /* Set up texturing options */
221 /* WARNING:
222 * Many cases of TMU2 masking are not supported by OpenGL.
223 * We only implement the most common ones:
224 * - full bilinear filtering vs. nearest texel
225 * - texture clamping vs. texture wrapping
227 if ((s->regs[R_TEXHMASK] & 0x3f) > 0x20) {
228 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
229 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
230 } else {
231 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
232 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
234 if ((s->regs[R_TEXHMASK] >> 6) & s->regs[R_TEXHRES]) {
235 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
236 } else {
237 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
239 if ((s->regs[R_TEXVMASK] >> 6) & s->regs[R_TEXVRES]) {
240 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
241 } else {
242 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
245 /* Translucency and decay */
246 glEnable(GL_BLEND);
247 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
248 m = (float)(s->regs[R_BRIGHTNESS] + 1) / 64.0f;
249 glColor4f(m, m, m, (float)(s->regs[R_ALPHA] + 1) / 64.0f);
251 /* Read the QEMU dest. framebuffer into the OpenGL framebuffer */
252 fb_len = 2 * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];
253 fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, 0);
254 if (fb == NULL) {
255 glDeleteTextures(1, &texture);
256 glXMakeContextCurrent(s->dpy, None, None, NULL);
257 glXDestroyPbuffer(s->dpy, pbuffer);
258 return;
261 glDrawPixels(s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB,
262 GL_UNSIGNED_SHORT_5_6_5, fb);
263 cpu_physical_memory_unmap(fb, fb_len, 0, fb_len);
264 glViewport(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES]);
265 glMatrixMode(GL_PROJECTION);
266 glLoadIdentity();
267 glOrtho(0.0, s->regs[R_DSTHRES], 0.0, s->regs[R_DSTVRES], -1.0, 1.0);
268 glMatrixMode(GL_MODELVIEW);
270 /* Map the texture */
271 mesh_len = MESH_MAXSIZE*MESH_MAXSIZE*sizeof(struct vertex);
272 mesh = cpu_physical_memory_map(s->regs[R_VERTICESADDR], &mesh_len, 0);
273 if (mesh == NULL) {
274 glDeleteTextures(1, &texture);
275 glXMakeContextCurrent(s->dpy, None, None, NULL);
276 glXDestroyPbuffer(s->dpy, pbuffer);
277 return;
280 tmu2_gl_map((struct vertex *)mesh,
281 s->regs[R_TEXHRES], s->regs[R_TEXVRES],
282 s->regs[R_HMESHLAST], s->regs[R_VMESHLAST],
283 s->regs[R_DSTHOFFSET], s->regs[R_DSTVOFFSET],
284 s->regs[R_DSTSQUAREW], s->regs[R_DSTSQUAREH]);
285 cpu_physical_memory_unmap(mesh, mesh_len, 0, mesh_len);
287 /* Write back the OpenGL framebuffer to the QEMU framebuffer */
288 fb_len = 2 * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];
289 fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, 1);
290 if (fb == NULL) {
291 glDeleteTextures(1, &texture);
292 glXMakeContextCurrent(s->dpy, None, None, NULL);
293 glXDestroyPbuffer(s->dpy, pbuffer);
294 return;
297 glReadPixels(0, 0, s->regs[R_DSTHRES], s->regs[R_DSTVRES], GL_RGB,
298 GL_UNSIGNED_SHORT_5_6_5, fb);
299 cpu_physical_memory_unmap(fb, fb_len, 1, fb_len);
301 /* Free OpenGL allocs */
302 glDeleteTextures(1, &texture);
303 glXMakeContextCurrent(s->dpy, None, None, NULL);
304 glXDestroyPbuffer(s->dpy, pbuffer);
306 s->regs[R_CTL] &= ~CTL_START_BUSY;
308 trace_milkymist_tmu2_pulse_irq();
309 qemu_irq_pulse(s->irq);
312 static uint32_t tmu2_read(void *opaque, target_phys_addr_t addr)
314 MilkymistTMU2State *s = opaque;
315 uint32_t r = 0;
317 addr >>= 2;
318 switch (addr) {
319 case R_CTL:
320 case R_HMESHLAST:
321 case R_VMESHLAST:
322 case R_BRIGHTNESS:
323 case R_CHROMAKEY:
324 case R_VERTICESADDR:
325 case R_TEXFBUF:
326 case R_TEXHRES:
327 case R_TEXVRES:
328 case R_TEXHMASK:
329 case R_TEXVMASK:
330 case R_DSTFBUF:
331 case R_DSTHRES:
332 case R_DSTVRES:
333 case R_DSTHOFFSET:
334 case R_DSTVOFFSET:
335 case R_DSTSQUAREW:
336 case R_DSTSQUAREH:
337 case R_ALPHA:
338 r = s->regs[addr];
339 break;
341 default:
342 error_report("milkymist_tmu2: read access to unknown register 0x"
343 TARGET_FMT_plx, addr << 2);
344 break;
347 trace_milkymist_tmu2_memory_read(addr << 2, r);
349 return r;
352 static void tmu2_check_registers(MilkymistTMU2State *s)
354 if (s->regs[R_BRIGHTNESS] > MAX_BRIGHTNESS) {
355 error_report("milkymist_tmu2: max brightness is %d\n", MAX_BRIGHTNESS);
358 if (s->regs[R_ALPHA] > MAX_ALPHA) {
359 error_report("milkymist_tmu2: max alpha is %d\n", MAX_ALPHA);
362 if (s->regs[R_VERTICESADDR] & 0x07) {
363 error_report("milkymist_tmu2: vertex mesh address has to be 64-bit "
364 "aligned\n");
367 if (s->regs[R_TEXFBUF] & 0x01) {
368 error_report("milkymist_tmu2: texture buffer address has to be "
369 "16-bit aligned\n");
373 static void tmu2_write(void *opaque, target_phys_addr_t addr, uint32_t value)
375 MilkymistTMU2State *s = opaque;
377 trace_milkymist_tmu2_memory_write(addr, value);
379 addr >>= 2;
380 switch (addr) {
381 case R_CTL:
382 s->regs[addr] = value;
383 if (value & CTL_START_BUSY) {
384 tmu2_start(s);
386 break;
387 case R_BRIGHTNESS:
388 case R_HMESHLAST:
389 case R_VMESHLAST:
390 case R_CHROMAKEY:
391 case R_VERTICESADDR:
392 case R_TEXFBUF:
393 case R_TEXHRES:
394 case R_TEXVRES:
395 case R_TEXHMASK:
396 case R_TEXVMASK:
397 case R_DSTFBUF:
398 case R_DSTHRES:
399 case R_DSTVRES:
400 case R_DSTHOFFSET:
401 case R_DSTVOFFSET:
402 case R_DSTSQUAREW:
403 case R_DSTSQUAREH:
404 case R_ALPHA:
405 s->regs[addr] = value;
406 break;
408 default:
409 error_report("milkymist_tmu2: write access to unknown register 0x"
410 TARGET_FMT_plx, addr << 2);
411 break;
414 tmu2_check_registers(s);
417 static CPUReadMemoryFunc * const tmu2_read_fn[] = {
418 NULL,
419 NULL,
420 &tmu2_read,
423 static CPUWriteMemoryFunc * const tmu2_write_fn[] = {
424 NULL,
425 NULL,
426 &tmu2_write,
429 static void milkymist_tmu2_reset(DeviceState *d)
431 MilkymistTMU2State *s = container_of(d, MilkymistTMU2State, busdev.qdev);
432 int i;
434 for (i = 0; i < R_MAX; i++) {
435 s->regs[i] = 0;
439 static int milkymist_tmu2_init(SysBusDevice *dev)
441 MilkymistTMU2State *s = FROM_SYSBUS(typeof(*s), dev);
442 int tmu2_regs;
444 if (tmu2_glx_init(s)) {
445 return 1;
448 sysbus_init_irq(dev, &s->irq);
450 tmu2_regs = cpu_register_io_memory(tmu2_read_fn, tmu2_write_fn, s,
451 DEVICE_NATIVE_ENDIAN);
452 sysbus_init_mmio(dev, R_MAX * 4, tmu2_regs);
454 return 0;
457 static const VMStateDescription vmstate_milkymist_tmu2 = {
458 .name = "milkymist-tmu2",
459 .version_id = 1,
460 .minimum_version_id = 1,
461 .minimum_version_id_old = 1,
462 .fields = (VMStateField[]) {
463 VMSTATE_UINT32_ARRAY(regs, MilkymistTMU2State, R_MAX),
464 VMSTATE_END_OF_LIST()
468 static SysBusDeviceInfo milkymist_tmu2_info = {
469 .init = milkymist_tmu2_init,
470 .qdev.name = "milkymist-tmu2",
471 .qdev.size = sizeof(MilkymistTMU2State),
472 .qdev.vmsd = &vmstate_milkymist_tmu2,
473 .qdev.reset = milkymist_tmu2_reset,
476 static void milkymist_tmu2_register(void)
478 sysbus_register_withprop(&milkymist_tmu2_info);
481 device_init(milkymist_tmu2_register)