]> git.proxmox.com Git - mirror_qemu.git/blame - hw/display/milkymist-tmu2.c
Merge remote-tracking branch 'remotes/bkoppelmann2/tags/pull-tricore-20210314' into...
[mirror_qemu.git] / hw / display / milkymist-tmu2.c
CommitLineData
0670dadd
MW
1/*
2 * QEMU model of the Milkymist texture mapping unit.
3 *
4 * Copyright (c) 2010 Michael Walle <michael@walle.cc>
5 * Copyright (c) 2010 Sebastien Bourdeauducq
6 * <sebastien.bourdeauducq@lekernel.net>
7 *
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
61f3c91a 11 * version 2.1 of the License, or (at your option) any later version.
0670dadd
MW
12 *
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.
17 *
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/>.
20 *
21 *
22 * Specification available at:
6dbbe243 23 * http://milkymist.walle.cc/socdoc/tmu2.pdf
0670dadd
MW
24 *
25 */
26
ea99dde1 27#include "qemu/osdep.h"
64552b6b 28#include "hw/irq.h"
83c9f4ca 29#include "hw/sysbus.h"
d6454270 30#include "migration/vmstate.h"
0670dadd 31#include "trace.h"
e688df6b 32#include "qapi/error.h"
1de7afc9 33#include "qemu/error-report.h"
0b8fa32f 34#include "qemu/module.h"
55543e76 35#include "qapi/error.h"
70cc0c1f 36#include "hw/display/milkymist_tmu2.h"
0670dadd
MW
37
38#include <X11/Xlib.h>
fb719563
OH
39#include <epoxy/gl.h>
40#include <epoxy/glx.h>
db1015e9 41#include "qom/object.h"
0670dadd
MW
42
43enum {
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
64};
65
66enum {
67 CTL_START_BUSY = (1<<0),
68 CTL_CHROMAKEY = (1<<1),
69};
70
71enum {
72 MAX_BRIGHTNESS = 63,
73 MAX_ALPHA = 63,
74};
75
76enum {
77 MESH_MAXSIZE = 128,
78};
79
80struct vertex {
81 int x;
82 int y;
541dc0d4 83} QEMU_PACKED;
0670dadd 84
56299135 85#define TYPE_MILKYMIST_TMU2 "milkymist-tmu2"
8063396b 86OBJECT_DECLARE_SIMPLE_TYPE(MilkymistTMU2State, MILKYMIST_TMU2)
56299135 87
0670dadd 88struct MilkymistTMU2State {
56299135
AF
89 SysBusDevice parent_obj;
90
7100453f 91 MemoryRegion regs_region;
0ec7b3e7 92 Chardev *chr;
0670dadd
MW
93 qemu_irq irq;
94
95 uint32_t regs[R_MAX];
96
97 Display *dpy;
98 GLXFBConfig glx_fb_config;
99 GLXContext glx_context;
100};
0670dadd
MW
101
102static const int glx_fbconfig_attr[] = {
103 GLX_GREEN_SIZE, 5,
104 GLX_GREEN_SIZE, 6,
105 GLX_BLUE_SIZE, 5,
106 None
107};
108
109static int tmu2_glx_init(MilkymistTMU2State *s)
110{
111 GLXFBConfig *configs;
112 int nelements;
113
114 s->dpy = XOpenDisplay(NULL); /* FIXME: call XCloseDisplay() */
115 if (s->dpy == NULL) {
116 return 1;
117 }
118
119 configs = glXChooseFBConfig(s->dpy, 0, glx_fbconfig_attr, &nelements);
120 if (configs == NULL) {
121 return 1;
122 }
123
124 s->glx_fb_config = *configs;
125 XFree(configs);
126
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;
132 }
133
134 return 0;
135}
136
137static void tmu2_gl_map(struct vertex *mesh, int texhres, int texvres,
138 int hmeshlast, int vmeshlast, int ho, int vo, int sw, int sh)
139{
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));
145
146 glLoadIdentity();
147 glTranslatef(ho, vo, 0);
148 glEnable(GL_TEXTURE_2D);
149 glBegin(GL_QUADS);
150
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;
157
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);
166
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);
175 }
176 }
177
178 glEnd();
179}
180
181static void tmu2_start(MilkymistTMU2State *s)
182{
183 int pbuffer_attrib[6] = {
184 GLX_PBUFFER_WIDTH,
185 0,
186 GLX_PBUFFER_HEIGHT,
187 0,
188 GLX_PRESERVED_CONTENTS,
189 True
190 };
191
192 GLXPbuffer pbuffer;
193 GLuint texture;
194 void *fb;
a8170e5e 195 hwaddr fb_len;
0670dadd 196 void *mesh;
a8170e5e 197 hwaddr mesh_len;
0670dadd
MW
198 float m;
199
200 trace_milkymist_tmu2_start();
201
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);
207
208 /* Fixup endianness. TODO: would it work on BE hosts? */
209 glPixelStorei(GL_UNPACK_SWAP_BYTES, 1);
210 glPixelStorei(GL_PACK_SWAP_BYTES, 1);
211
212 /* Row alignment */
213 glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
214 glPixelStorei(GL_PACK_ALIGNMENT, 2);
215
216 /* Read the QEMU source framebuffer into an OpenGL texture */
217 glGenTextures(1, &texture);
218 glBindTexture(GL_TEXTURE_2D, texture);
237a8650 219 fb_len = 2ULL * s->regs[R_TEXHRES] * s->regs[R_TEXVRES];
85eb7c18 220 fb = cpu_physical_memory_map(s->regs[R_TEXFBUF], &fb_len, false);
0670dadd
MW
221 if (fb == NULL) {
222 glDeleteTextures(1, &texture);
223 glXMakeContextCurrent(s->dpy, None, None, NULL);
224 glXDestroyPbuffer(s->dpy, pbuffer);
225 return;
226 }
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);
230
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
237 */
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);
244 }
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);
249 }
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);
254 }
255
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);
261
262 /* Read the QEMU dest. framebuffer into the OpenGL framebuffer */
4382fa65 263 fb_len = 2ULL * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];
85eb7c18 264 fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, false);
0670dadd
MW
265 if (fb == NULL) {
266 glDeleteTextures(1, &texture);
267 glXMakeContextCurrent(s->dpy, None, None, NULL);
268 glXDestroyPbuffer(s->dpy, pbuffer);
269 return;
270 }
271
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);
280
281 /* Map the texture */
282 mesh_len = MESH_MAXSIZE*MESH_MAXSIZE*sizeof(struct vertex);
85eb7c18 283 mesh = cpu_physical_memory_map(s->regs[R_VERTICESADDR], &mesh_len, false);
0670dadd
MW
284 if (mesh == NULL) {
285 glDeleteTextures(1, &texture);
286 glXMakeContextCurrent(s->dpy, None, None, NULL);
287 glXDestroyPbuffer(s->dpy, pbuffer);
288 return;
289 }
290
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);
297
298 /* Write back the OpenGL framebuffer to the QEMU framebuffer */
3d74ee7d 299 fb_len = 2ULL * s->regs[R_DSTHRES] * s->regs[R_DSTVRES];
85eb7c18 300 fb = cpu_physical_memory_map(s->regs[R_DSTFBUF], &fb_len, true);
0670dadd
MW
301 if (fb == NULL) {
302 glDeleteTextures(1, &texture);
303 glXMakeContextCurrent(s->dpy, None, None, NULL);
304 glXDestroyPbuffer(s->dpy, pbuffer);
305 return;
306 }
307
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);
311
312 /* Free OpenGL allocs */
313 glDeleteTextures(1, &texture);
314 glXMakeContextCurrent(s->dpy, None, None, NULL);
315 glXDestroyPbuffer(s->dpy, pbuffer);
316
317 s->regs[R_CTL] &= ~CTL_START_BUSY;
318
319 trace_milkymist_tmu2_pulse_irq();
320 qemu_irq_pulse(s->irq);
321}
322
a8170e5e 323static uint64_t tmu2_read(void *opaque, hwaddr addr,
7100453f 324 unsigned size)
0670dadd
MW
325{
326 MilkymistTMU2State *s = opaque;
327 uint32_t r = 0;
328
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;
352
353 default:
354 error_report("milkymist_tmu2: read access to unknown register 0x"
355 TARGET_FMT_plx, addr << 2);
356 break;
357 }
358
359 trace_milkymist_tmu2_memory_read(addr << 2, r);
360
361 return r;
362}
363
364static void tmu2_check_registers(MilkymistTMU2State *s)
365{
366 if (s->regs[R_BRIGHTNESS] > MAX_BRIGHTNESS) {
6daf194d 367 error_report("milkymist_tmu2: max brightness is %d", MAX_BRIGHTNESS);
0670dadd
MW
368 }
369
370 if (s->regs[R_ALPHA] > MAX_ALPHA) {
6daf194d 371 error_report("milkymist_tmu2: max alpha is %d", MAX_ALPHA);
0670dadd
MW
372 }
373
374 if (s->regs[R_VERTICESADDR] & 0x07) {
375 error_report("milkymist_tmu2: vertex mesh address has to be 64-bit "
6daf194d 376 "aligned");
0670dadd
MW
377 }
378
379 if (s->regs[R_TEXFBUF] & 0x01) {
380 error_report("milkymist_tmu2: texture buffer address has to be "
6daf194d 381 "16-bit aligned");
0670dadd
MW
382 }
383}
384
a8170e5e 385static void tmu2_write(void *opaque, hwaddr addr, uint64_t value,
7100453f 386 unsigned size)
0670dadd
MW
387{
388 MilkymistTMU2State *s = opaque;
389
390 trace_milkymist_tmu2_memory_write(addr, value);
391
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);
398 }
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;
420
421 default:
422 error_report("milkymist_tmu2: write access to unknown register 0x"
423 TARGET_FMT_plx, addr << 2);
424 break;
425 }
426
427 tmu2_check_registers(s);
428}
429
7100453f
MW
430static 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,
436 },
437 .endianness = DEVICE_NATIVE_ENDIAN,
0670dadd
MW
438};
439
440static void milkymist_tmu2_reset(DeviceState *d)
441{
56299135 442 MilkymistTMU2State *s = MILKYMIST_TMU2(d);
0670dadd
MW
443 int i;
444
445 for (i = 0; i < R_MAX; i++) {
446 s->regs[i] = 0;
447 }
448}
449
cf79c64d 450static void milkymist_tmu2_init(Object *obj)
0670dadd 451{
cf79c64d
XZ
452 MilkymistTMU2State *s = MILKYMIST_TMU2(obj);
453 SysBusDevice *dev = SYS_BUS_DEVICE(obj);
0670dadd
MW
454
455 sysbus_init_irq(dev, &s->irq);
456
cf79c64d 457 memory_region_init_io(&s->regs_region, obj, &tmu2_mmio_ops, s,
7100453f 458 "milkymist-tmu2", R_MAX * 4);
750ecd44 459 sysbus_init_mmio(dev, &s->regs_region);
cf79c64d 460}
0670dadd 461
cf79c64d
XZ
462static void milkymist_tmu2_realize(DeviceState *dev, Error **errp)
463{
464 MilkymistTMU2State *s = MILKYMIST_TMU2(dev);
465
466 if (tmu2_glx_init(s)) {
467 error_setg(errp, "tmu2_glx_init failed");
468 }
0670dadd
MW
469}
470
471static const VMStateDescription vmstate_milkymist_tmu2 = {
472 .name = "milkymist-tmu2",
473 .version_id = 1,
474 .minimum_version_id = 1,
35d08458 475 .fields = (VMStateField[]) {
0670dadd
MW
476 VMSTATE_UINT32_ARRAY(regs, MilkymistTMU2State, R_MAX),
477 VMSTATE_END_OF_LIST()
478 }
479};
480
999e12bb
AL
481static void milkymist_tmu2_class_init(ObjectClass *klass, void *data)
482{
39bffca2 483 DeviceClass *dc = DEVICE_CLASS(klass);
999e12bb 484
cf79c64d 485 dc->realize = milkymist_tmu2_realize;
39bffca2
AL
486 dc->reset = milkymist_tmu2_reset;
487 dc->vmsd = &vmstate_milkymist_tmu2;
999e12bb
AL
488}
489
8c43a6f0 490static const TypeInfo milkymist_tmu2_info = {
56299135 491 .name = TYPE_MILKYMIST_TMU2,
39bffca2
AL
492 .parent = TYPE_SYS_BUS_DEVICE,
493 .instance_size = sizeof(MilkymistTMU2State),
cf79c64d 494 .instance_init = milkymist_tmu2_init,
39bffca2 495 .class_init = milkymist_tmu2_class_init,
0670dadd
MW
496};
497
83f7d43a 498static void milkymist_tmu2_register_types(void)
0670dadd 499{
39bffca2 500 type_register_static(&milkymist_tmu2_info);
0670dadd
MW
501}
502
83f7d43a 503type_init(milkymist_tmu2_register_types)
70cc0c1f
PMD
504
505DeviceState *milkymist_tmu2_create(hwaddr base, qemu_irq irq)
506{
507 DeviceState *dev;
508 Display *d;
509 GLXFBConfig *configs;
510 int nelements;
511 int ver_major, ver_minor;
512
513 /* check that GLX will work */
514 d = XOpenDisplay(NULL);
515 if (d == NULL) {
516 return NULL;
517 }
518
519 if (!glXQueryVersion(d, &ver_major, &ver_minor)) {
520 /*
521 * Yeah, sometimes getting the GLX version can fail.
522 * Isn't X beautiful?
523 */
524 XCloseDisplay(d);
525 return NULL;
526 }
527
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;
534 }
535
536 configs = glXChooseFBConfig(d, 0, glx_fbconfig_attr, &nelements);
537 if (configs == NULL) {
538 XCloseDisplay(d);
539 return NULL;
540 }
541
542 XFree(configs);
543 XCloseDisplay(d);
544
3e80f690 545 dev = qdev_new(TYPE_MILKYMIST_TMU2);
3c6ef471 546 sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
70cc0c1f
PMD
547 sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
548 sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq);
549
550 return dev;
551}