xref: /openbmc/qemu/hw/display/tcx.c (revision d177892d)
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/osdep.h"
26 #include "qemu-common.h"
27 #include "qemu/datadir.h"
28 #include "qapi/error.h"
29 #include "ui/console.h"
30 #include "ui/pixel_ops.h"
31 #include "hw/loader.h"
32 #include "hw/qdev-properties.h"
33 #include "hw/sysbus.h"
34 #include "migration/vmstate.h"
35 #include "qemu/error-report.h"
36 #include "qemu/module.h"
37 #include "qom/object.h"
38 
39 #define TCX_ROM_FILE "QEMU,tcx.bin"
40 #define FCODE_MAX_ROM_SIZE 0x10000
41 
42 #define MAXX 1024
43 #define MAXY 768
44 #define TCX_DAC_NREGS    16
45 #define TCX_THC_NREGS    0x1000
46 #define TCX_DHC_NREGS    0x4000
47 #define TCX_TEC_NREGS    0x1000
48 #define TCX_ALT_NREGS    0x8000
49 #define TCX_STIP_NREGS   0x800000
50 #define TCX_BLIT_NREGS   0x800000
51 #define TCX_RSTIP_NREGS  0x800000
52 #define TCX_RBLIT_NREGS  0x800000
53 
54 #define TCX_THC_MISC     0x818
55 #define TCX_THC_CURSXY   0x8fc
56 #define TCX_THC_CURSMASK 0x900
57 #define TCX_THC_CURSBITS 0x980
58 
59 #define TYPE_TCX "sun-tcx"
60 OBJECT_DECLARE_SIMPLE_TYPE(TCXState, TCX)
61 
62 struct TCXState {
63     SysBusDevice parent_obj;
64 
65     QemuConsole *con;
66     qemu_irq irq;
67     uint8_t *vram;
68     uint32_t *vram24, *cplane;
69     hwaddr prom_addr;
70     MemoryRegion rom;
71     MemoryRegion vram_mem;
72     MemoryRegion vram_8bit;
73     MemoryRegion vram_24bit;
74     MemoryRegion stip;
75     MemoryRegion blit;
76     MemoryRegion vram_cplane;
77     MemoryRegion rstip;
78     MemoryRegion rblit;
79     MemoryRegion tec;
80     MemoryRegion dac;
81     MemoryRegion thc;
82     MemoryRegion dhc;
83     MemoryRegion alt;
84     MemoryRegion thc24;
85 
86     ram_addr_t vram24_offset, cplane_offset;
87     uint32_t tmpblit;
88     uint32_t vram_size;
89     uint32_t palette[260];
90     uint8_t r[260], g[260], b[260];
91     uint16_t width, height, depth;
92     uint8_t dac_index, dac_state;
93     uint32_t thcmisc;
94     uint32_t cursmask[32];
95     uint32_t cursbits[32];
96     uint16_t cursx;
97     uint16_t cursy;
98 };
99 
100 static void tcx_set_dirty(TCXState *s, ram_addr_t addr, int len)
101 {
102     memory_region_set_dirty(&s->vram_mem, addr, len);
103 
104     if (s->depth == 24) {
105         memory_region_set_dirty(&s->vram_mem, s->vram24_offset + addr * 4,
106                                 len * 4);
107         memory_region_set_dirty(&s->vram_mem, s->cplane_offset + addr * 4,
108                                 len * 4);
109     }
110 }
111 
112 static int tcx_check_dirty(TCXState *s, DirtyBitmapSnapshot *snap,
113                            ram_addr_t addr, int len)
114 {
115     int ret;
116 
117     ret = memory_region_snapshot_get_dirty(&s->vram_mem, snap, addr, len);
118 
119     if (s->depth == 24) {
120         ret |= memory_region_snapshot_get_dirty(&s->vram_mem, snap,
121                                        s->vram24_offset + addr * 4, len * 4);
122         ret |= memory_region_snapshot_get_dirty(&s->vram_mem, snap,
123                                        s->cplane_offset + addr * 4, len * 4);
124     }
125 
126     return ret;
127 }
128 
129 static void update_palette_entries(TCXState *s, int start, int end)
130 {
131     int i;
132 
133     for (i = start; i < end; i++) {
134         s->palette[i] = rgb_to_pixel32(s->r[i], s->g[i], s->b[i]);
135     }
136     tcx_set_dirty(s, 0, memory_region_size(&s->vram_mem));
137 }
138 
139 static void tcx_draw_line32(TCXState *s1, uint8_t *d,
140                             const uint8_t *s, int width)
141 {
142     int x;
143     uint8_t val;
144     uint32_t *p = (uint32_t *)d;
145 
146     for (x = 0; x < width; x++) {
147         val = *s++;
148         *p++ = s1->palette[val];
149     }
150 }
151 
152 static void tcx_draw_cursor32(TCXState *s1, uint8_t *d,
153                               int y, int width)
154 {
155     int x, len;
156     uint32_t mask, bits;
157     uint32_t *p = (uint32_t *)d;
158 
159     y = y - s1->cursy;
160     mask = s1->cursmask[y];
161     bits = s1->cursbits[y];
162     len = MIN(width - s1->cursx, 32);
163     p = &p[s1->cursx];
164     for (x = 0; x < len; x++) {
165         if (mask & 0x80000000) {
166             if (bits & 0x80000000) {
167                 *p = s1->palette[259];
168             } else {
169                 *p = s1->palette[258];
170             }
171         }
172         p++;
173         mask <<= 1;
174         bits <<= 1;
175     }
176 }
177 
178 /*
179  * XXX Could be much more optimal:
180  * detect if line/page/whole screen is in 24 bit mode
181  */
182 static inline void tcx24_draw_line32(TCXState *s1, uint8_t *d,
183                                      const uint8_t *s, int width,
184                                      const uint32_t *cplane,
185                                      const uint32_t *s24)
186 {
187     int x, r, g, b;
188     uint8_t val, *p8;
189     uint32_t *p = (uint32_t *)d;
190     uint32_t dval;
191     for(x = 0; x < width; x++, s++, s24++) {
192         if (be32_to_cpu(*cplane) & 0x03000000) {
193             /* 24-bit direct, BGR order */
194             p8 = (uint8_t *)s24;
195             p8++;
196             b = *p8++;
197             g = *p8++;
198             r = *p8;
199             dval = rgb_to_pixel32(r, g, b);
200         } else {
201             /* 8-bit pseudocolor */
202             val = *s;
203             dval = s1->palette[val];
204         }
205         *p++ = dval;
206         cplane++;
207     }
208 }
209 
210 /* Fixed line length 1024 allows us to do nice tricks not possible on
211    VGA... */
212 
213 static void tcx_update_display(void *opaque)
214 {
215     TCXState *ts = opaque;
216     DisplaySurface *surface = qemu_console_surface(ts->con);
217     ram_addr_t page;
218     DirtyBitmapSnapshot *snap = NULL;
219     int y, y_start, dd, ds;
220     uint8_t *d, *s;
221 
222     assert(surface_bits_per_pixel(surface) == 32);
223 
224     page = 0;
225     y_start = -1;
226     d = surface_data(surface);
227     s = ts->vram;
228     dd = surface_stride(surface);
229     ds = 1024;
230 
231     snap = memory_region_snapshot_and_clear_dirty(&ts->vram_mem, 0x0,
232                                              memory_region_size(&ts->vram_mem),
233                                              DIRTY_MEMORY_VGA);
234 
235     for (y = 0; y < ts->height; y++, page += ds) {
236         if (tcx_check_dirty(ts, snap, page, ds)) {
237             if (y_start < 0)
238                 y_start = y;
239 
240             tcx_draw_line32(ts, d, s, ts->width);
241             if (y >= ts->cursy && y < ts->cursy + 32 && ts->cursx < ts->width) {
242                 tcx_draw_cursor32(ts, d, y, ts->width);
243             }
244         } else {
245             if (y_start >= 0) {
246                 /* flush to display */
247                 dpy_gfx_update(ts->con, 0, y_start,
248                                ts->width, y - y_start);
249                 y_start = -1;
250             }
251         }
252         s += ds;
253         d += dd;
254     }
255     if (y_start >= 0) {
256         /* flush to display */
257         dpy_gfx_update(ts->con, 0, y_start,
258                        ts->width, y - y_start);
259     }
260     g_free(snap);
261 }
262 
263 static void tcx24_update_display(void *opaque)
264 {
265     TCXState *ts = opaque;
266     DisplaySurface *surface = qemu_console_surface(ts->con);
267     ram_addr_t page;
268     DirtyBitmapSnapshot *snap = NULL;
269     int y, y_start, dd, ds;
270     uint8_t *d, *s;
271     uint32_t *cptr, *s24;
272 
273     assert(surface_bits_per_pixel(surface) == 32);
274 
275     page = 0;
276     y_start = -1;
277     d = surface_data(surface);
278     s = ts->vram;
279     s24 = ts->vram24;
280     cptr = ts->cplane;
281     dd = surface_stride(surface);
282     ds = 1024;
283 
284     snap = memory_region_snapshot_and_clear_dirty(&ts->vram_mem, 0x0,
285                                              memory_region_size(&ts->vram_mem),
286                                              DIRTY_MEMORY_VGA);
287 
288     for (y = 0; y < ts->height; y++, page += ds) {
289         if (tcx_check_dirty(ts, snap, page, ds)) {
290             if (y_start < 0)
291                 y_start = y;
292 
293             tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
294             if (y >= ts->cursy && y < ts->cursy+32 && ts->cursx < ts->width) {
295                 tcx_draw_cursor32(ts, d, y, ts->width);
296             }
297         } else {
298             if (y_start >= 0) {
299                 /* flush to display */
300                 dpy_gfx_update(ts->con, 0, y_start,
301                                ts->width, y - y_start);
302                 y_start = -1;
303             }
304         }
305         d += dd;
306         s += ds;
307         cptr += ds;
308         s24 += ds;
309     }
310     if (y_start >= 0) {
311         /* flush to display */
312         dpy_gfx_update(ts->con, 0, y_start,
313                        ts->width, y - y_start);
314     }
315     g_free(snap);
316 }
317 
318 static void tcx_invalidate_display(void *opaque)
319 {
320     TCXState *s = opaque;
321 
322     tcx_set_dirty(s, 0, memory_region_size(&s->vram_mem));
323     qemu_console_resize(s->con, s->width, s->height);
324 }
325 
326 static void tcx24_invalidate_display(void *opaque)
327 {
328     TCXState *s = opaque;
329 
330     tcx_set_dirty(s, 0, memory_region_size(&s->vram_mem));
331     qemu_console_resize(s->con, s->width, s->height);
332 }
333 
334 static int vmstate_tcx_post_load(void *opaque, int version_id)
335 {
336     TCXState *s = opaque;
337 
338     update_palette_entries(s, 0, 256);
339     tcx_set_dirty(s, 0, memory_region_size(&s->vram_mem));
340     return 0;
341 }
342 
343 static const VMStateDescription vmstate_tcx = {
344     .name ="tcx",
345     .version_id = 4,
346     .minimum_version_id = 4,
347     .post_load = vmstate_tcx_post_load,
348     .fields = (VMStateField[]) {
349         VMSTATE_UINT16(height, TCXState),
350         VMSTATE_UINT16(width, TCXState),
351         VMSTATE_UINT16(depth, TCXState),
352         VMSTATE_BUFFER(r, TCXState),
353         VMSTATE_BUFFER(g, TCXState),
354         VMSTATE_BUFFER(b, TCXState),
355         VMSTATE_UINT8(dac_index, TCXState),
356         VMSTATE_UINT8(dac_state, TCXState),
357         VMSTATE_END_OF_LIST()
358     }
359 };
360 
361 static void tcx_reset(DeviceState *d)
362 {
363     TCXState *s = TCX(d);
364 
365     /* Initialize palette */
366     memset(s->r, 0, 260);
367     memset(s->g, 0, 260);
368     memset(s->b, 0, 260);
369     s->r[255] = s->g[255] = s->b[255] = 255;
370     s->r[256] = s->g[256] = s->b[256] = 255;
371     s->r[258] = s->g[258] = s->b[258] = 255;
372     update_palette_entries(s, 0, 260);
373     memset(s->vram, 0, MAXX*MAXY);
374     memory_region_reset_dirty(&s->vram_mem, 0, MAXX * MAXY * (1 + 4 + 4),
375                               DIRTY_MEMORY_VGA);
376     s->dac_index = 0;
377     s->dac_state = 0;
378     s->cursx = 0xf000; /* Put cursor off screen */
379     s->cursy = 0xf000;
380 }
381 
382 static uint64_t tcx_dac_readl(void *opaque, hwaddr addr,
383                               unsigned size)
384 {
385     TCXState *s = opaque;
386     uint32_t val = 0;
387 
388     switch (s->dac_state) {
389     case 0:
390         val = s->r[s->dac_index] << 24;
391         s->dac_state++;
392         break;
393     case 1:
394         val = s->g[s->dac_index] << 24;
395         s->dac_state++;
396         break;
397     case 2:
398         val = s->b[s->dac_index] << 24;
399         s->dac_index = (s->dac_index + 1) & 0xff; /* Index autoincrement */
400         /* fall through */
401     default:
402         s->dac_state = 0;
403         break;
404     }
405 
406     return val;
407 }
408 
409 static void tcx_dac_writel(void *opaque, hwaddr addr, uint64_t val,
410                            unsigned size)
411 {
412     TCXState *s = opaque;
413     unsigned index;
414 
415     switch (addr) {
416     case 0: /* Address */
417         s->dac_index = val >> 24;
418         s->dac_state = 0;
419         break;
420     case 4:  /* Pixel colours */
421     case 12: /* Overlay (cursor) colours */
422         if (addr & 8) {
423             index = (s->dac_index & 3) + 256;
424         } else {
425             index = s->dac_index;
426         }
427         switch (s->dac_state) {
428         case 0:
429             s->r[index] = val >> 24;
430             update_palette_entries(s, index, index + 1);
431             s->dac_state++;
432             break;
433         case 1:
434             s->g[index] = val >> 24;
435             update_palette_entries(s, index, index + 1);
436             s->dac_state++;
437             break;
438         case 2:
439             s->b[index] = val >> 24;
440             update_palette_entries(s, index, index + 1);
441             s->dac_index = (s->dac_index + 1) & 0xff; /* Index autoincrement */
442             /* fall through */
443         default:
444             s->dac_state = 0;
445             break;
446         }
447         break;
448     default: /* Control registers */
449         break;
450     }
451 }
452 
453 static const MemoryRegionOps tcx_dac_ops = {
454     .read = tcx_dac_readl,
455     .write = tcx_dac_writel,
456     .endianness = DEVICE_NATIVE_ENDIAN,
457     .valid = {
458         .min_access_size = 4,
459         .max_access_size = 4,
460     },
461 };
462 
463 static uint64_t tcx_stip_readl(void *opaque, hwaddr addr,
464                                unsigned size)
465 {
466     return 0;
467 }
468 
469 static void tcx_stip_writel(void *opaque, hwaddr addr,
470                             uint64_t val, unsigned size)
471 {
472     TCXState *s = opaque;
473     int i;
474     uint32_t col;
475 
476     if (!(addr & 4)) {
477         s->tmpblit = val;
478     } else {
479         addr = (addr >> 3) & 0xfffff;
480         col = cpu_to_be32(s->tmpblit);
481         if (s->depth == 24) {
482             for (i = 0; i < 32; i++)  {
483                 if (val & 0x80000000) {
484                     s->vram[addr + i] = s->tmpblit;
485                     s->vram24[addr + i] = col;
486                 }
487                 val <<= 1;
488             }
489         } else {
490             for (i = 0; i < 32; i++)  {
491                 if (val & 0x80000000) {
492                     s->vram[addr + i] = s->tmpblit;
493                 }
494                 val <<= 1;
495             }
496         }
497         tcx_set_dirty(s, addr, 32);
498     }
499 }
500 
501 static void tcx_rstip_writel(void *opaque, hwaddr addr,
502                              uint64_t val, unsigned size)
503 {
504     TCXState *s = opaque;
505     int i;
506     uint32_t col;
507 
508     if (!(addr & 4)) {
509         s->tmpblit = val;
510     } else {
511         addr = (addr >> 3) & 0xfffff;
512         col = cpu_to_be32(s->tmpblit);
513         if (s->depth == 24) {
514             for (i = 0; i < 32; i++) {
515                 if (val & 0x80000000) {
516                     s->vram[addr + i] = s->tmpblit;
517                     s->vram24[addr + i] = col;
518                     s->cplane[addr + i] = col;
519                 }
520                 val <<= 1;
521             }
522         } else {
523             for (i = 0; i < 32; i++)  {
524                 if (val & 0x80000000) {
525                     s->vram[addr + i] = s->tmpblit;
526                 }
527                 val <<= 1;
528             }
529         }
530         tcx_set_dirty(s, addr, 32);
531     }
532 }
533 
534 static const MemoryRegionOps tcx_stip_ops = {
535     .read = tcx_stip_readl,
536     .write = tcx_stip_writel,
537     .endianness = DEVICE_NATIVE_ENDIAN,
538     .impl = {
539         .min_access_size = 4,
540         .max_access_size = 4,
541     },
542     .valid = {
543         .min_access_size = 4,
544         .max_access_size = 8,
545     },
546 };
547 
548 static const MemoryRegionOps tcx_rstip_ops = {
549     .read = tcx_stip_readl,
550     .write = tcx_rstip_writel,
551     .endianness = DEVICE_NATIVE_ENDIAN,
552     .impl = {
553         .min_access_size = 4,
554         .max_access_size = 4,
555     },
556     .valid = {
557         .min_access_size = 4,
558         .max_access_size = 8,
559     },
560 };
561 
562 static uint64_t tcx_blit_readl(void *opaque, hwaddr addr,
563                                unsigned size)
564 {
565     return 0;
566 }
567 
568 static void tcx_blit_writel(void *opaque, hwaddr addr,
569                             uint64_t val, unsigned size)
570 {
571     TCXState *s = opaque;
572     uint32_t adsr, len;
573     int i;
574 
575     if (!(addr & 4)) {
576         s->tmpblit = val;
577     } else {
578         addr = (addr >> 3) & 0xfffff;
579         adsr = val & 0xffffff;
580         len = ((val >> 24) & 0x1f) + 1;
581         if (adsr == 0xffffff) {
582             memset(&s->vram[addr], s->tmpblit, len);
583             if (s->depth == 24) {
584                 val = s->tmpblit & 0xffffff;
585                 val = cpu_to_be32(val);
586                 for (i = 0; i < len; i++) {
587                     s->vram24[addr + i] = val;
588                 }
589             }
590         } else {
591             memcpy(&s->vram[addr], &s->vram[adsr], len);
592             if (s->depth == 24) {
593                 memcpy(&s->vram24[addr], &s->vram24[adsr], len * 4);
594             }
595         }
596         tcx_set_dirty(s, addr, len);
597     }
598 }
599 
600 static void tcx_rblit_writel(void *opaque, hwaddr addr,
601                          uint64_t val, unsigned size)
602 {
603     TCXState *s = opaque;
604     uint32_t adsr, len;
605     int i;
606 
607     if (!(addr & 4)) {
608         s->tmpblit = val;
609     } else {
610         addr = (addr >> 3) & 0xfffff;
611         adsr = val & 0xffffff;
612         len = ((val >> 24) & 0x1f) + 1;
613         if (adsr == 0xffffff) {
614             memset(&s->vram[addr], s->tmpblit, len);
615             if (s->depth == 24) {
616                 val = s->tmpblit & 0xffffff;
617                 val = cpu_to_be32(val);
618                 for (i = 0; i < len; i++) {
619                     s->vram24[addr + i] = val;
620                     s->cplane[addr + i] = val;
621                 }
622             }
623         } else {
624             memcpy(&s->vram[addr], &s->vram[adsr], len);
625             if (s->depth == 24) {
626                 memcpy(&s->vram24[addr], &s->vram24[adsr], len * 4);
627                 memcpy(&s->cplane[addr], &s->cplane[adsr], len * 4);
628             }
629         }
630         tcx_set_dirty(s, addr, len);
631     }
632 }
633 
634 static const MemoryRegionOps tcx_blit_ops = {
635     .read = tcx_blit_readl,
636     .write = tcx_blit_writel,
637     .endianness = DEVICE_NATIVE_ENDIAN,
638     .impl = {
639         .min_access_size = 4,
640         .max_access_size = 4,
641     },
642     .valid = {
643         .min_access_size = 4,
644         .max_access_size = 8,
645     },
646 };
647 
648 static const MemoryRegionOps tcx_rblit_ops = {
649     .read = tcx_blit_readl,
650     .write = tcx_rblit_writel,
651     .endianness = DEVICE_NATIVE_ENDIAN,
652     .impl = {
653         .min_access_size = 4,
654         .max_access_size = 4,
655     },
656     .valid = {
657         .min_access_size = 4,
658         .max_access_size = 8,
659     },
660 };
661 
662 static void tcx_invalidate_cursor_position(TCXState *s)
663 {
664     int ymin, ymax, start, end;
665 
666     /* invalidate only near the cursor */
667     ymin = s->cursy;
668     if (ymin >= s->height) {
669         return;
670     }
671     ymax = MIN(s->height, ymin + 32);
672     start = ymin * 1024;
673     end   = ymax * 1024;
674 
675     tcx_set_dirty(s, start, end - start);
676 }
677 
678 static uint64_t tcx_thc_readl(void *opaque, hwaddr addr,
679                             unsigned size)
680 {
681     TCXState *s = opaque;
682     uint64_t val;
683 
684     if (addr == TCX_THC_MISC) {
685         val = s->thcmisc | 0x02000000;
686     } else {
687         val = 0;
688     }
689     return val;
690 }
691 
692 static void tcx_thc_writel(void *opaque, hwaddr addr,
693                          uint64_t val, unsigned size)
694 {
695     TCXState *s = opaque;
696 
697     if (addr == TCX_THC_CURSXY) {
698         tcx_invalidate_cursor_position(s);
699         s->cursx = val >> 16;
700         s->cursy = val;
701         tcx_invalidate_cursor_position(s);
702     } else if (addr >= TCX_THC_CURSMASK && addr < TCX_THC_CURSMASK + 128) {
703         s->cursmask[(addr - TCX_THC_CURSMASK) >> 2] = val;
704         tcx_invalidate_cursor_position(s);
705     } else if (addr >= TCX_THC_CURSBITS && addr < TCX_THC_CURSBITS + 128) {
706         s->cursbits[(addr - TCX_THC_CURSBITS) >> 2] = val;
707         tcx_invalidate_cursor_position(s);
708     } else if (addr == TCX_THC_MISC) {
709         s->thcmisc = val;
710     }
711 
712 }
713 
714 static const MemoryRegionOps tcx_thc_ops = {
715     .read = tcx_thc_readl,
716     .write = tcx_thc_writel,
717     .endianness = DEVICE_NATIVE_ENDIAN,
718     .valid = {
719         .min_access_size = 4,
720         .max_access_size = 4,
721     },
722 };
723 
724 static uint64_t tcx_dummy_readl(void *opaque, hwaddr addr,
725                             unsigned size)
726 {
727     return 0;
728 }
729 
730 static void tcx_dummy_writel(void *opaque, hwaddr addr,
731                          uint64_t val, unsigned size)
732 {
733     return;
734 }
735 
736 static const MemoryRegionOps tcx_dummy_ops = {
737     .read = tcx_dummy_readl,
738     .write = tcx_dummy_writel,
739     .endianness = DEVICE_NATIVE_ENDIAN,
740     .valid = {
741         .min_access_size = 4,
742         .max_access_size = 4,
743     },
744 };
745 
746 static const GraphicHwOps tcx_ops = {
747     .invalidate = tcx_invalidate_display,
748     .gfx_update = tcx_update_display,
749 };
750 
751 static const GraphicHwOps tcx24_ops = {
752     .invalidate = tcx24_invalidate_display,
753     .gfx_update = tcx24_update_display,
754 };
755 
756 static void tcx_initfn(Object *obj)
757 {
758     SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
759     TCXState *s = TCX(obj);
760 
761     memory_region_init_rom_nomigrate(&s->rom, obj, "tcx.prom",
762                                      FCODE_MAX_ROM_SIZE, &error_fatal);
763     sysbus_init_mmio(sbd, &s->rom);
764 
765     /* 2/STIP : Stippler */
766     memory_region_init_io(&s->stip, obj, &tcx_stip_ops, s, "tcx.stip",
767                           TCX_STIP_NREGS);
768     sysbus_init_mmio(sbd, &s->stip);
769 
770     /* 3/BLIT : Blitter */
771     memory_region_init_io(&s->blit, obj, &tcx_blit_ops, s, "tcx.blit",
772                           TCX_BLIT_NREGS);
773     sysbus_init_mmio(sbd, &s->blit);
774 
775     /* 5/RSTIP : Raw Stippler */
776     memory_region_init_io(&s->rstip, obj, &tcx_rstip_ops, s, "tcx.rstip",
777                           TCX_RSTIP_NREGS);
778     sysbus_init_mmio(sbd, &s->rstip);
779 
780     /* 6/RBLIT : Raw Blitter */
781     memory_region_init_io(&s->rblit, obj, &tcx_rblit_ops, s, "tcx.rblit",
782                           TCX_RBLIT_NREGS);
783     sysbus_init_mmio(sbd, &s->rblit);
784 
785     /* 7/TEC : ??? */
786     memory_region_init_io(&s->tec, obj, &tcx_dummy_ops, s, "tcx.tec",
787                           TCX_TEC_NREGS);
788     sysbus_init_mmio(sbd, &s->tec);
789 
790     /* 8/CMAP : DAC */
791     memory_region_init_io(&s->dac, obj, &tcx_dac_ops, s, "tcx.dac",
792                           TCX_DAC_NREGS);
793     sysbus_init_mmio(sbd, &s->dac);
794 
795     /* 9/THC : Cursor */
796     memory_region_init_io(&s->thc, obj, &tcx_thc_ops, s, "tcx.thc",
797                           TCX_THC_NREGS);
798     sysbus_init_mmio(sbd, &s->thc);
799 
800     /* 11/DHC : ??? */
801     memory_region_init_io(&s->dhc, obj, &tcx_dummy_ops, s, "tcx.dhc",
802                           TCX_DHC_NREGS);
803     sysbus_init_mmio(sbd, &s->dhc);
804 
805     /* 12/ALT : ??? */
806     memory_region_init_io(&s->alt, obj, &tcx_dummy_ops, s, "tcx.alt",
807                           TCX_ALT_NREGS);
808     sysbus_init_mmio(sbd, &s->alt);
809 }
810 
811 static void tcx_realizefn(DeviceState *dev, Error **errp)
812 {
813     SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
814     TCXState *s = TCX(dev);
815     ram_addr_t vram_offset = 0;
816     int size, ret;
817     uint8_t *vram_base;
818     char *fcode_filename;
819 
820     memory_region_init_ram_nomigrate(&s->vram_mem, OBJECT(s), "tcx.vram",
821                            s->vram_size * (1 + 4 + 4), &error_fatal);
822     vmstate_register_ram_global(&s->vram_mem);
823     memory_region_set_log(&s->vram_mem, true, DIRTY_MEMORY_VGA);
824     vram_base = memory_region_get_ram_ptr(&s->vram_mem);
825 
826     /* 10/ROM : FCode ROM */
827     vmstate_register_ram_global(&s->rom);
828     fcode_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, TCX_ROM_FILE);
829     if (fcode_filename) {
830         ret = load_image_mr(fcode_filename, &s->rom);
831         g_free(fcode_filename);
832         if (ret < 0 || ret > FCODE_MAX_ROM_SIZE) {
833             warn_report("tcx: could not load prom '%s'", TCX_ROM_FILE);
834         }
835     }
836 
837     /* 0/DFB8 : 8-bit plane */
838     s->vram = vram_base;
839     size = s->vram_size;
840     memory_region_init_alias(&s->vram_8bit, OBJECT(s), "tcx.vram.8bit",
841                              &s->vram_mem, vram_offset, size);
842     sysbus_init_mmio(sbd, &s->vram_8bit);
843     vram_offset += size;
844     vram_base += size;
845 
846     /* 1/DFB24 : 24bit plane */
847     size = s->vram_size * 4;
848     s->vram24 = (uint32_t *)vram_base;
849     s->vram24_offset = vram_offset;
850     memory_region_init_alias(&s->vram_24bit, OBJECT(s), "tcx.vram.24bit",
851                              &s->vram_mem, vram_offset, size);
852     sysbus_init_mmio(sbd, &s->vram_24bit);
853     vram_offset += size;
854     vram_base += size;
855 
856     /* 4/RDFB32 : Raw Framebuffer */
857     size = s->vram_size * 4;
858     s->cplane = (uint32_t *)vram_base;
859     s->cplane_offset = vram_offset;
860     memory_region_init_alias(&s->vram_cplane, OBJECT(s), "tcx.vram.cplane",
861                              &s->vram_mem, vram_offset, size);
862     sysbus_init_mmio(sbd, &s->vram_cplane);
863 
864     /* 9/THC24bits : NetBSD writes here even with 8-bit display: dummy */
865     if (s->depth == 8) {
866         memory_region_init_io(&s->thc24, OBJECT(s), &tcx_dummy_ops, s,
867                               "tcx.thc24", TCX_THC_NREGS);
868         sysbus_init_mmio(sbd, &s->thc24);
869     }
870 
871     sysbus_init_irq(sbd, &s->irq);
872 
873     if (s->depth == 8) {
874         s->con = graphic_console_init(dev, 0, &tcx_ops, s);
875     } else {
876         s->con = graphic_console_init(dev, 0, &tcx24_ops, s);
877     }
878     s->thcmisc = 0;
879 
880     qemu_console_resize(s->con, s->width, s->height);
881 }
882 
883 static Property tcx_properties[] = {
884     DEFINE_PROP_UINT32("vram_size", TCXState, vram_size, -1),
885     DEFINE_PROP_UINT16("width",    TCXState, width,     -1),
886     DEFINE_PROP_UINT16("height",   TCXState, height,    -1),
887     DEFINE_PROP_UINT16("depth",    TCXState, depth,     -1),
888     DEFINE_PROP_END_OF_LIST(),
889 };
890 
891 static void tcx_class_init(ObjectClass *klass, void *data)
892 {
893     DeviceClass *dc = DEVICE_CLASS(klass);
894 
895     dc->realize = tcx_realizefn;
896     dc->reset = tcx_reset;
897     dc->vmsd = &vmstate_tcx;
898     device_class_set_props(dc, tcx_properties);
899 }
900 
901 static const TypeInfo tcx_info = {
902     .name          = TYPE_TCX,
903     .parent        = TYPE_SYS_BUS_DEVICE,
904     .instance_size = sizeof(TCXState),
905     .instance_init = tcx_initfn,
906     .class_init    = tcx_class_init,
907 };
908 
909 static void tcx_register_types(void)
910 {
911     type_register_static(&tcx_info);
912 }
913 
914 type_init(tcx_register_types)
915