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