xref: /openbmc/qemu/hw/display/tcx.c (revision 534f6ff9)
1 /*
2  * QEMU TCX Frame buffer
3  *
4  * Copyright (c) 2003-2005 Fabrice Bellard
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 
25 #include "qemu-common.h"
26 #include "ui/console.h"
27 #include "ui/pixel_ops.h"
28 #include "hw/sysbus.h"
29 
30 #define MAXX 1024
31 #define MAXY 768
32 #define TCX_DAC_NREGS 16
33 #define TCX_THC_NREGS_8  0x081c
34 #define TCX_THC_NREGS_24 0x1000
35 #define TCX_TEC_NREGS    0x1000
36 
37 #define TYPE_TCX "SUNW,tcx"
38 #define TCX(obj) OBJECT_CHECK(TCXState, (obj), TYPE_TCX)
39 
40 typedef struct TCXState {
41     SysBusDevice parent_obj;
42 
43     QemuConsole *con;
44     uint8_t *vram;
45     uint32_t *vram24, *cplane;
46     MemoryRegion vram_mem;
47     MemoryRegion vram_8bit;
48     MemoryRegion vram_24bit;
49     MemoryRegion vram_cplane;
50     MemoryRegion dac;
51     MemoryRegion tec;
52     MemoryRegion thc24;
53     MemoryRegion thc8;
54     ram_addr_t vram24_offset, cplane_offset;
55     uint32_t vram_size;
56     uint32_t palette[256];
57     uint8_t r[256], g[256], b[256];
58     uint16_t width, height, depth;
59     uint8_t dac_index, dac_state;
60 } TCXState;
61 
62 static void tcx_set_dirty(TCXState *s)
63 {
64     memory_region_set_dirty(&s->vram_mem, 0, MAXX * MAXY);
65 }
66 
67 static void tcx24_set_dirty(TCXState *s)
68 {
69     memory_region_set_dirty(&s->vram_mem, s->vram24_offset, MAXX * MAXY * 4);
70     memory_region_set_dirty(&s->vram_mem, s->cplane_offset, MAXX * MAXY * 4);
71 }
72 
73 static void update_palette_entries(TCXState *s, int start, int end)
74 {
75     DisplaySurface *surface = qemu_console_surface(s->con);
76     int i;
77 
78     for (i = start; i < end; i++) {
79         switch (surface_bits_per_pixel(surface)) {
80         default:
81         case 8:
82             s->palette[i] = rgb_to_pixel8(s->r[i], s->g[i], s->b[i]);
83             break;
84         case 15:
85             s->palette[i] = rgb_to_pixel15(s->r[i], s->g[i], s->b[i]);
86             break;
87         case 16:
88             s->palette[i] = rgb_to_pixel16(s->r[i], s->g[i], s->b[i]);
89             break;
90         case 32:
91             if (is_surface_bgr(surface)) {
92                 s->palette[i] = rgb_to_pixel32bgr(s->r[i], s->g[i], s->b[i]);
93             } else {
94                 s->palette[i] = rgb_to_pixel32(s->r[i], s->g[i], s->b[i]);
95             }
96             break;
97         }
98     }
99     if (s->depth == 24) {
100         tcx24_set_dirty(s);
101     } else {
102         tcx_set_dirty(s);
103     }
104 }
105 
106 static void tcx_draw_line32(TCXState *s1, uint8_t *d,
107                             const uint8_t *s, int width)
108 {
109     int x;
110     uint8_t val;
111     uint32_t *p = (uint32_t *)d;
112 
113     for(x = 0; x < width; x++) {
114         val = *s++;
115         *p++ = s1->palette[val];
116     }
117 }
118 
119 static void tcx_draw_line16(TCXState *s1, uint8_t *d,
120                             const uint8_t *s, int width)
121 {
122     int x;
123     uint8_t val;
124     uint16_t *p = (uint16_t *)d;
125 
126     for(x = 0; x < width; x++) {
127         val = *s++;
128         *p++ = s1->palette[val];
129     }
130 }
131 
132 static void tcx_draw_line8(TCXState *s1, uint8_t *d,
133                            const uint8_t *s, int width)
134 {
135     int x;
136     uint8_t val;
137 
138     for(x = 0; x < width; x++) {
139         val = *s++;
140         *d++ = s1->palette[val];
141     }
142 }
143 
144 /*
145   XXX Could be much more optimal:
146   * detect if line/page/whole screen is in 24 bit mode
147   * if destination is also BGR, use memcpy
148   */
149 static inline void tcx24_draw_line32(TCXState *s1, uint8_t *d,
150                                      const uint8_t *s, int width,
151                                      const uint32_t *cplane,
152                                      const uint32_t *s24)
153 {
154     DisplaySurface *surface = qemu_console_surface(s1->con);
155     int x, bgr, r, g, b;
156     uint8_t val, *p8;
157     uint32_t *p = (uint32_t *)d;
158     uint32_t dval;
159 
160     bgr = is_surface_bgr(surface);
161     for(x = 0; x < width; x++, s++, s24++) {
162         if ((be32_to_cpu(*cplane++) & 0xff000000) == 0x03000000) {
163             // 24-bit direct, BGR order
164             p8 = (uint8_t *)s24;
165             p8++;
166             b = *p8++;
167             g = *p8++;
168             r = *p8;
169             if (bgr)
170                 dval = rgb_to_pixel32bgr(r, g, b);
171             else
172                 dval = rgb_to_pixel32(r, g, b);
173         } else {
174             val = *s;
175             dval = s1->palette[val];
176         }
177         *p++ = dval;
178     }
179 }
180 
181 static inline int check_dirty(TCXState *s, ram_addr_t page, ram_addr_t page24,
182                               ram_addr_t cpage)
183 {
184     int ret;
185 
186     ret = memory_region_get_dirty(&s->vram_mem, page, TARGET_PAGE_SIZE,
187                                   DIRTY_MEMORY_VGA);
188     ret |= memory_region_get_dirty(&s->vram_mem, page24, TARGET_PAGE_SIZE * 4,
189                                    DIRTY_MEMORY_VGA);
190     ret |= memory_region_get_dirty(&s->vram_mem, cpage, TARGET_PAGE_SIZE * 4,
191                                    DIRTY_MEMORY_VGA);
192     return ret;
193 }
194 
195 static inline void reset_dirty(TCXState *ts, ram_addr_t page_min,
196                                ram_addr_t page_max, ram_addr_t page24,
197                               ram_addr_t cpage)
198 {
199     memory_region_reset_dirty(&ts->vram_mem,
200                               page_min,
201                               (page_max - page_min) + TARGET_PAGE_SIZE,
202                               DIRTY_MEMORY_VGA);
203     memory_region_reset_dirty(&ts->vram_mem,
204                               page24 + page_min * 4,
205                               (page_max - page_min) * 4 + TARGET_PAGE_SIZE,
206                               DIRTY_MEMORY_VGA);
207     memory_region_reset_dirty(&ts->vram_mem,
208                               cpage + page_min * 4,
209                               (page_max - page_min) * 4 + TARGET_PAGE_SIZE,
210                               DIRTY_MEMORY_VGA);
211 }
212 
213 /* Fixed line length 1024 allows us to do nice tricks not possible on
214    VGA... */
215 static void tcx_update_display(void *opaque)
216 {
217     TCXState *ts = opaque;
218     DisplaySurface *surface = qemu_console_surface(ts->con);
219     ram_addr_t page, page_min, page_max;
220     int y, y_start, dd, ds;
221     uint8_t *d, *s;
222     void (*f)(TCXState *s1, uint8_t *dst, const uint8_t *src, int width);
223 
224     if (surface_bits_per_pixel(surface) == 0) {
225         return;
226     }
227 
228     page = 0;
229     y_start = -1;
230     page_min = -1;
231     page_max = 0;
232     d = surface_data(surface);
233     s = ts->vram;
234     dd = surface_stride(surface);
235     ds = 1024;
236 
237     switch (surface_bits_per_pixel(surface)) {
238     case 32:
239         f = tcx_draw_line32;
240         break;
241     case 15:
242     case 16:
243         f = tcx_draw_line16;
244         break;
245     default:
246     case 8:
247         f = tcx_draw_line8;
248         break;
249     case 0:
250         return;
251     }
252 
253     for(y = 0; y < ts->height; y += 4, page += TARGET_PAGE_SIZE) {
254         if (memory_region_get_dirty(&ts->vram_mem, page, TARGET_PAGE_SIZE,
255                                     DIRTY_MEMORY_VGA)) {
256             if (y_start < 0)
257                 y_start = y;
258             if (page < page_min)
259                 page_min = page;
260             if (page > page_max)
261                 page_max = page;
262             f(ts, d, s, ts->width);
263             d += dd;
264             s += ds;
265             f(ts, d, s, ts->width);
266             d += dd;
267             s += ds;
268             f(ts, d, s, ts->width);
269             d += dd;
270             s += ds;
271             f(ts, d, s, ts->width);
272             d += dd;
273             s += ds;
274         } else {
275             if (y_start >= 0) {
276                 /* flush to display */
277                 dpy_gfx_update(ts->con, 0, y_start,
278                                ts->width, y - y_start);
279                 y_start = -1;
280             }
281             d += dd * 4;
282             s += ds * 4;
283         }
284     }
285     if (y_start >= 0) {
286         /* flush to display */
287         dpy_gfx_update(ts->con, 0, y_start,
288                        ts->width, y - y_start);
289     }
290     /* reset modified pages */
291     if (page_max >= page_min) {
292         memory_region_reset_dirty(&ts->vram_mem,
293                                   page_min,
294                                   (page_max - page_min) + TARGET_PAGE_SIZE,
295                                   DIRTY_MEMORY_VGA);
296     }
297 }
298 
299 static void tcx24_update_display(void *opaque)
300 {
301     TCXState *ts = opaque;
302     DisplaySurface *surface = qemu_console_surface(ts->con);
303     ram_addr_t page, page_min, page_max, cpage, page24;
304     int y, y_start, dd, ds;
305     uint8_t *d, *s;
306     uint32_t *cptr, *s24;
307 
308     if (surface_bits_per_pixel(surface) != 32) {
309             return;
310     }
311 
312     page = 0;
313     page24 = ts->vram24_offset;
314     cpage = ts->cplane_offset;
315     y_start = -1;
316     page_min = -1;
317     page_max = 0;
318     d = surface_data(surface);
319     s = ts->vram;
320     s24 = ts->vram24;
321     cptr = ts->cplane;
322     dd = surface_stride(surface);
323     ds = 1024;
324 
325     for(y = 0; y < ts->height; y += 4, page += TARGET_PAGE_SIZE,
326             page24 += TARGET_PAGE_SIZE, cpage += TARGET_PAGE_SIZE) {
327         if (check_dirty(ts, page, page24, cpage)) {
328             if (y_start < 0)
329                 y_start = y;
330             if (page < page_min)
331                 page_min = page;
332             if (page > page_max)
333                 page_max = page;
334             tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
335             d += dd;
336             s += ds;
337             cptr += ds;
338             s24 += ds;
339             tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
340             d += dd;
341             s += ds;
342             cptr += ds;
343             s24 += ds;
344             tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
345             d += dd;
346             s += ds;
347             cptr += ds;
348             s24 += ds;
349             tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
350             d += dd;
351             s += ds;
352             cptr += ds;
353             s24 += ds;
354         } else {
355             if (y_start >= 0) {
356                 /* flush to display */
357                 dpy_gfx_update(ts->con, 0, y_start,
358                                ts->width, y - y_start);
359                 y_start = -1;
360             }
361             d += dd * 4;
362             s += ds * 4;
363             cptr += ds * 4;
364             s24 += ds * 4;
365         }
366     }
367     if (y_start >= 0) {
368         /* flush to display */
369         dpy_gfx_update(ts->con, 0, y_start,
370                        ts->width, y - y_start);
371     }
372     /* reset modified pages */
373     if (page_max >= page_min) {
374         reset_dirty(ts, page_min, page_max, page24, cpage);
375     }
376 }
377 
378 static void tcx_invalidate_display(void *opaque)
379 {
380     TCXState *s = opaque;
381 
382     tcx_set_dirty(s);
383     qemu_console_resize(s->con, s->width, s->height);
384 }
385 
386 static void tcx24_invalidate_display(void *opaque)
387 {
388     TCXState *s = opaque;
389 
390     tcx_set_dirty(s);
391     tcx24_set_dirty(s);
392     qemu_console_resize(s->con, s->width, s->height);
393 }
394 
395 static int vmstate_tcx_post_load(void *opaque, int version_id)
396 {
397     TCXState *s = opaque;
398 
399     update_palette_entries(s, 0, 256);
400     if (s->depth == 24) {
401         tcx24_set_dirty(s);
402     } else {
403         tcx_set_dirty(s);
404     }
405 
406     return 0;
407 }
408 
409 static const VMStateDescription vmstate_tcx = {
410     .name ="tcx",
411     .version_id = 4,
412     .minimum_version_id = 4,
413     .minimum_version_id_old = 4,
414     .post_load = vmstate_tcx_post_load,
415     .fields      = (VMStateField []) {
416         VMSTATE_UINT16(height, TCXState),
417         VMSTATE_UINT16(width, TCXState),
418         VMSTATE_UINT16(depth, TCXState),
419         VMSTATE_BUFFER(r, TCXState),
420         VMSTATE_BUFFER(g, TCXState),
421         VMSTATE_BUFFER(b, TCXState),
422         VMSTATE_UINT8(dac_index, TCXState),
423         VMSTATE_UINT8(dac_state, TCXState),
424         VMSTATE_END_OF_LIST()
425     }
426 };
427 
428 static void tcx_reset(DeviceState *d)
429 {
430     TCXState *s = TCX(d);
431 
432     /* Initialize palette */
433     memset(s->r, 0, 256);
434     memset(s->g, 0, 256);
435     memset(s->b, 0, 256);
436     s->r[255] = s->g[255] = s->b[255] = 255;
437     update_palette_entries(s, 0, 256);
438     memset(s->vram, 0, MAXX*MAXY);
439     memory_region_reset_dirty(&s->vram_mem, 0, MAXX * MAXY * (1 + 4 + 4),
440                               DIRTY_MEMORY_VGA);
441     s->dac_index = 0;
442     s->dac_state = 0;
443 }
444 
445 static uint64_t tcx_dac_readl(void *opaque, hwaddr addr,
446                               unsigned size)
447 {
448     return 0;
449 }
450 
451 static void tcx_dac_writel(void *opaque, hwaddr addr, uint64_t val,
452                            unsigned size)
453 {
454     TCXState *s = opaque;
455 
456     switch (addr) {
457     case 0:
458         s->dac_index = val >> 24;
459         s->dac_state = 0;
460         break;
461     case 4:
462         switch (s->dac_state) {
463         case 0:
464             s->r[s->dac_index] = val >> 24;
465             update_palette_entries(s, s->dac_index, s->dac_index + 1);
466             s->dac_state++;
467             break;
468         case 1:
469             s->g[s->dac_index] = val >> 24;
470             update_palette_entries(s, s->dac_index, s->dac_index + 1);
471             s->dac_state++;
472             break;
473         case 2:
474             s->b[s->dac_index] = val >> 24;
475             update_palette_entries(s, s->dac_index, s->dac_index + 1);
476             s->dac_index = (s->dac_index + 1) & 255; // Index autoincrement
477         default:
478             s->dac_state = 0;
479             break;
480         }
481         break;
482     default:
483         break;
484     }
485 }
486 
487 static const MemoryRegionOps tcx_dac_ops = {
488     .read = tcx_dac_readl,
489     .write = tcx_dac_writel,
490     .endianness = DEVICE_NATIVE_ENDIAN,
491     .valid = {
492         .min_access_size = 4,
493         .max_access_size = 4,
494     },
495 };
496 
497 static uint64_t dummy_readl(void *opaque, hwaddr addr,
498                             unsigned size)
499 {
500     return 0;
501 }
502 
503 static void dummy_writel(void *opaque, hwaddr addr,
504                          uint64_t val, unsigned size)
505 {
506 }
507 
508 static const MemoryRegionOps dummy_ops = {
509     .read = dummy_readl,
510     .write = dummy_writel,
511     .endianness = DEVICE_NATIVE_ENDIAN,
512     .valid = {
513         .min_access_size = 4,
514         .max_access_size = 4,
515     },
516 };
517 
518 static const GraphicHwOps tcx_ops = {
519     .invalidate = tcx_invalidate_display,
520     .gfx_update = tcx_update_display,
521 };
522 
523 static const GraphicHwOps tcx24_ops = {
524     .invalidate = tcx24_invalidate_display,
525     .gfx_update = tcx24_update_display,
526 };
527 
528 static int tcx_init1(SysBusDevice *dev)
529 {
530     TCXState *s = TCX(dev);
531     ram_addr_t vram_offset = 0;
532     int size;
533     uint8_t *vram_base;
534 
535     memory_region_init_ram(&s->vram_mem, OBJECT(s), "tcx.vram",
536                            s->vram_size * (1 + 4 + 4));
537     vmstate_register_ram_global(&s->vram_mem);
538     vram_base = memory_region_get_ram_ptr(&s->vram_mem);
539 
540     /* 8-bit plane */
541     s->vram = vram_base;
542     size = s->vram_size;
543     memory_region_init_alias(&s->vram_8bit, OBJECT(s), "tcx.vram.8bit",
544                              &s->vram_mem, vram_offset, size);
545     sysbus_init_mmio(dev, &s->vram_8bit);
546     vram_offset += size;
547     vram_base += size;
548 
549     /* DAC */
550     memory_region_init_io(&s->dac, OBJECT(s), &tcx_dac_ops, s,
551                           "tcx.dac", TCX_DAC_NREGS);
552     sysbus_init_mmio(dev, &s->dac);
553 
554     /* TEC (dummy) */
555     memory_region_init_io(&s->tec, OBJECT(s), &dummy_ops, s,
556                           "tcx.tec", TCX_TEC_NREGS);
557     sysbus_init_mmio(dev, &s->tec);
558     /* THC: NetBSD writes here even with 8-bit display: dummy */
559     memory_region_init_io(&s->thc24, OBJECT(s), &dummy_ops, s, "tcx.thc24",
560                           TCX_THC_NREGS_24);
561     sysbus_init_mmio(dev, &s->thc24);
562 
563     if (s->depth == 24) {
564         /* 24-bit plane */
565         size = s->vram_size * 4;
566         s->vram24 = (uint32_t *)vram_base;
567         s->vram24_offset = vram_offset;
568         memory_region_init_alias(&s->vram_24bit, OBJECT(s), "tcx.vram.24bit",
569                                  &s->vram_mem, vram_offset, size);
570         sysbus_init_mmio(dev, &s->vram_24bit);
571         vram_offset += size;
572         vram_base += size;
573 
574         /* Control plane */
575         size = s->vram_size * 4;
576         s->cplane = (uint32_t *)vram_base;
577         s->cplane_offset = vram_offset;
578         memory_region_init_alias(&s->vram_cplane, OBJECT(s), "tcx.vram.cplane",
579                                  &s->vram_mem, vram_offset, size);
580         sysbus_init_mmio(dev, &s->vram_cplane);
581 
582         s->con = graphic_console_init(DEVICE(dev), &tcx24_ops, s);
583     } else {
584         /* THC 8 bit (dummy) */
585         memory_region_init_io(&s->thc8, OBJECT(s), &dummy_ops, s, "tcx.thc8",
586                               TCX_THC_NREGS_8);
587         sysbus_init_mmio(dev, &s->thc8);
588 
589         s->con = graphic_console_init(DEVICE(dev), &tcx_ops, s);
590     }
591 
592     qemu_console_resize(s->con, s->width, s->height);
593     return 0;
594 }
595 
596 static Property tcx_properties[] = {
597     DEFINE_PROP_HEX32("vram_size", TCXState, vram_size, -1),
598     DEFINE_PROP_UINT16("width",    TCXState, width,     -1),
599     DEFINE_PROP_UINT16("height",   TCXState, height,    -1),
600     DEFINE_PROP_UINT16("depth",    TCXState, depth,     -1),
601     DEFINE_PROP_END_OF_LIST(),
602 };
603 
604 static void tcx_class_init(ObjectClass *klass, void *data)
605 {
606     DeviceClass *dc = DEVICE_CLASS(klass);
607     SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
608 
609     k->init = tcx_init1;
610     dc->reset = tcx_reset;
611     dc->vmsd = &vmstate_tcx;
612     dc->props = tcx_properties;
613 }
614 
615 static const TypeInfo tcx_info = {
616     .name          = TYPE_TCX,
617     .parent        = TYPE_SYS_BUS_DEVICE,
618     .instance_size = sizeof(TCXState),
619     .class_init    = tcx_class_init,
620 };
621 
622 static void tcx_register_types(void)
623 {
624     type_register_static(&tcx_info);
625 }
626 
627 type_init(tcx_register_types)
628