Patchwork cg14

login
register
mail settings
Submitter Bob Breuer
Date July 13, 2010, 4:26 p.m.
Message ID <4C3C93B7.9020804@mc.net>
Download mbox | patch
Permalink /patch/58803/
State New
Headers show

Comments

Bob Breuer - July 13, 2010, 4:26 p.m.
Another preview of the cg14 framebuffer.

Activate by selecting SS-20 machine and setting width > 1024, i.e. "-M SS-20 -g 1152x900".
Note that NetBSD assumes 1152x900, while OBP also supports 1024x768, 1280x1024, and 1600x1280.

New since last time:
- All video memory accesses implemented
    X under linux now works (uses RGB instead of XRGB space)
- Hooked into qdev

Todo:
- fix NetBSD display
- add draw_line templates to handle other than 32-bit RGB host displays
- use VGA_DIRTY tracking
    - What's the equivalent of stb_p that also sets the dirty bits?
- inform OpenBIOS of cg14 framebuffer
    - Can we pass "nvalias screen /obio/cgfourteen" to the firmware?

Bob

---
 Makefile.target |    1 +
 hw/cg14.c       |  850 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/sun4m.c      |   22 ++
 3 files changed, 873 insertions(+), 0 deletions(-)
 create mode 100644 hw/cg14.c
Artyom Tarasenko - July 13, 2010, 8:34 p.m.
2010/7/13 Bob Breuer <breuerr@mc.net>:
> Another preview of the cg14 framebuffer.
>
> Activate by selecting SS-20 machine and setting width > 1024, i.e. "-M SS-20 -g 1152x900".
> Note that NetBSD assumes 1152x900, while OBP also supports 1024x768, 1280x1024, and 1600x1280.
>
> New since last time:
> - All video memory accesses implemented
>    X under linux now works (uses RGB instead of XRGB space)
> - Hooked into qdev

Great job!


> Todo:
> - fix NetBSD display
> - add draw_line templates to handle other than 32-bit RGB host displays
> - use VGA_DIRTY tracking
>    - What's the equivalent of stb_p that also sets the dirty bits?

afaics stb_phys does set the dirty bits. The ones which don't are
st?_phys_notdirty .

> - inform OpenBIOS of cg14 framebuffer
>    - Can we pass "nvalias screen /obio/cgfourteen" to the firmware?

Do we have to? Why not just probe for the VSIMM?

>
> Bob
>
> ---
>  Makefile.target |    1 +
>  hw/cg14.c       |  850 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  hw/sun4m.c      |   22 ++
>  3 files changed, 873 insertions(+), 0 deletions(-)
>  create mode 100644 hw/cg14.c
>
> diff --git a/Makefile.target b/Makefile.target
> index 3ef4666..54a2ae4 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -255,6 +255,7 @@ else
>  obj-sparc-y = sun4m.o lance.o tcx.o sun4m_iommu.o slavio_intctl.o
>  obj-sparc-y += slavio_timer.o slavio_misc.o sparc32_dma.o
>  obj-sparc-y += cs4231.o eccmemctl.o sbi.o sun4c_intctl.o
> +obj-sparc-y += cg14.o
>  endif
>
>  obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o
> diff --git a/hw/cg14.c b/hw/cg14.c
> new file mode 100644
> index 0000000..533dc04
> --- /dev/null
> +++ b/hw/cg14.c
> @@ -0,0 +1,850 @@
> +/*
> + * QEMU CG14 Frame buffer
> + *
> + * Copyright (c) 2010 Bob Breuer  <breuerr@mc.net>
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a copy
> + * of this software and associated documentation files (the "Software"), to deal
> + * in the Software without restriction, including without limitation the rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> + * THE SOFTWARE.
> + */
> +
> +#include "console.h"
> +#include "sysbus.h"
> +#include "qdev-addr.h"
> +
> +//#define DEBUG_CG14
> +//#define DEBUG_DAC
> +//#define DEBUG_CONFIG
> +
> +/*
> + * Sun CG14 framebuffer (without SX)
> + *   CG14 = vsimm framebuffer (video ram and dac)
> + *   SX = pixel processor (acceleration) built into chipset
> + *
> + * Documentation: not publicly documented by Sun
> + *   linux driver: drivers/video/cg14.c
> + *   NetBSD/OpenBSD: src/sys/arch/sparc/dev/cgfourteen*
> + *
> + * Takes up one memory slot:
> + *   A[28:26] = slot number (4 to 7)
> + *   regs: size   0x10000 @ 0x09c000000  (0x80000000 + slot * 64M)
> + *   vmem: size upto 16MB @ 0x0fc000000  (0xE0000000 + slot * 64M)
> + *
> + * SS-20 OBP only supports slots 7 (onboard output) and 4 (AVB output)
> + *
> + * memory map:
> + * reg+0x0000 = control registers
> + * reg+0x1000 = cursor registers
> + * reg+0x2000 = dac registers (ADV7152)
> + * reg+0x3000 = xlut
> + * reg+0x4000 = clut1
> + * reg+0x5000 = clut2
> + * reg+0x6000 = clut3 (if implemented)
> + * reg+0xf000 = autoinc
> + *
> + * mem+0x0000000 = XBGR (01234567)
> + * mem+0x1000000 = BGR  (.123.567)  writes to X are blocked, reads are ok
> + * mem+0x2000000 = X16  (0246)
> + * mem+0x2800000 = C16  (1357)
> + * mem+0x3000000 = X32  (04)
> + * mem+0x3400000 = B32  (15)
> + * mem+0x3800000 = G32  (26)
> + * mem+0x3c00000 = R32  (37)
> + */
> +
> +#define CG14_REG_SIZE         0x10000
> +#define CG14_VMEM_SLOTSIZE    (64<<20)
> +
> +#define CG14_MONID_1024x768   0
> +#define CG14_MONID_1600x1280  1
> +#define CG14_MONID_1280x1024  2
> +#define CG14_MONID_1152x900   7
> +
> +#define CG14_MONID_DEFAULT    CG14_MONID_1024x768
> +
> +
> +#define CG14_MCR_INTENABLE     0x80
> +#define CG14_MCR_VIDENABLE     0x40
> +#define CG14_MCR_PIXMODE_MASK  0x30
> +#define   CG14_MCR_PIXMODE_8     0x00
> +#define   CG14_MCR_PIXMODE_16    0x20  /* 8+8 (X16,C16) */
> +#define   CG14_MCR_PIXMODE_32    0x30  /* XBGR */
> +
> +
> +#ifdef DEBUG_CG14
> +#define DPRINTF(fmt, ...)                                       \
> +    printf("CG14: " fmt , ## __VA_ARGS__)
> +#else
> +#define DPRINTF(fmt, ...)
> +#endif
> +
> +#ifdef DEBUG_DAC
> +#define DPRINTF_DAC(fmt, ...)                                   \
> +    printf("CG14 dac: " fmt , ## __VA_ARGS__)
> +#else
> +#define DPRINTF_DAC(fmt, ...)
> +#endif
> +
> +#ifdef DEBUG_CONFIG
> +#define DPRINTF_CONFIG(fmt, ...)                                \
> +    printf("CG14: " fmt , ## __VA_ARGS__)
> +#else
> +#define DPRINTF_CONFIG(fmt, ...)
> +#endif
> +
> +#define CG14_INFO(fmt, ...)                                     \
> +    do { printf("CG14: " fmt , ## __VA_ARGS__); } while (0)
> +#define CG14_ERROR(fmt, ...)                                    \
> +    do { printf("CG14: " fmt , ## __VA_ARGS__); } while (0)
> +
> +
> +struct dac_state {
> +    uint8_t mode;
> +    uint8_t address;
> +    int rgb_seq;
> +};
> +
> +typedef struct CG14State {
> +    SysBusDevice busdev;
> +    DisplayState *ds;
> +
> +    target_phys_addr_t vram_addr;
> +    uint8_t *vram;
> +    uint32_t vram_size;
> +    uint32_t vram_amask;
> +    uint16_t width, height;
> +    int dirty, size_changed;
> +    struct {
> +        uint8_t mcr;
> +        uint8_t ppr;
> +        uint8_t msr;
> +    } ctrl;
> +    struct dac_state dac;
> +    struct {
> +        uint16_t hblank_start;
> +        uint16_t hblank_clear;
> +        uint16_t vblank_start;
> +        uint16_t vblank_clear;
> +    } timing;
> +    uint8_t xlut[256];
> +    uint32_t clut1[256];
> +    uint32_t clut2[256];
> +} CG14State;
> +
> +static void cg14_screen_dump(void *opaque, const char *filename);
> +static void cg14_invalidate_display(void *opaque);
> +
> +static inline uint32_t bgr_to_rgb(uint32_t bgr)
> +{
> +    uint32_t rgb;
> +
> +    /* swap r & b */
> +    rgb = (bgr & 0x00FF00)
> +        | (bgr & 0x0000FF) << 16
> +        | (bgr & 0xFF0000) >> 16;
> +    return rgb;
> +}
> +
> +typedef void draw_line_func(const CG14State *s, void *dst, const uint8_t *src);
> +
> +// TODO: draw_line templates
> +static void cg14_draw_line32_rgb32(const CG14State *s, void *dst, const uint8_t *src)
> +{
> +    unsigned int i;
> +    unsigned int x, r, g, b;
> +    uint8_t xlut_val;
> +    uint32_t dval;
> +
> +    for (i=0; i<s->width; i++) {
> +        x = src[0];
> +        b = src[1];
> +        g = src[2];
> +        r = src[3];
> +        xlut_val = s->xlut[x];
> +        src += 4;
> +
> +        if (xlut_val == 0x40) {
> +            dval = bgr_to_rgb(s->clut1[x]);
> +        } else {
> +            /* xlut = 0x00 for true-color */
> +            /* possible blending between 2 colors */
> +            /* fallback to true-color just to display something if unimplemented */
> +            dval = r << 16 | g << 8 | b;
> +        }
> +        /* dac lookup ? */
> +
> +        /* to surface format */
> +        //dval = is_bgr ? (abgr & 0xFFFFFF) : bgr_to_rgb(abgr);
> +        ((uint32_t*)dst)[i] = dval;
> +    }
> +}
> +
> +static void cg14_draw_line16_rgb32(const CG14State *s, void *dst, const uint8_t *src)
> +{
> +    unsigned int i;
> +    unsigned int x, c;
> +    uint8_t xlut_val;
> +    uint32_t dval;
> +
> +    for (i=0; i<s->width; i++) {
> +        x = src[0];
> +        c = src[1];
> +        xlut_val = s->xlut[x];
> +        src += 2;
> +
> +        if (xlut_val == 0x40) {
> +            dval = bgr_to_rgb(s->clut1[x]);
> +        } else {
> +            /* possible blending between 2 colors */
> +            /* fallback to green/blue just to display something if unimplemented */
> +            dval = x << 8 | c;
> +        }
> +        /* dac lookup ? */
> +
> +        ((uint32_t*)dst)[i] = dval;
> +    }
> +}
> +
> +static void cg14_draw_line8_rgb32(const CG14State *s, void *dst, const uint8_t *src)
> +{
> +    int i;
> +    const uint32_t *clut;
> +    uint32_t *dst32 = dst;
> +
> +    if (s->ctrl.ppr == 0x40) {
> +        clut = s->clut1;
> +        for (i=0; i<s->width; i++) {
> +            dst32[i] = bgr_to_rgb(clut[src[i]]);
> +        }
> +    } else {
> +        /* FIXME: other xlut modes, just use grayscale ramp for now */
> +        for (i=0; i<s->width; i++) {
> +            dst32[i] = 0x010101 * src[i];
> +        }
> +    }
> +}
> +
> +/* TODO: use VGA_DIRTY_FLAG */
> +static void cg14_update_display(void *opaque)
> +{
> +    CG14State *s = opaque;
> +    int h, src_linesize;
> +    uint8_t *pix;
> +    uint8_t *data;
> +    int new_width, new_height;
> +    draw_line_func * draw_line;
> +
> +    if (s->size_changed) {
> +        new_width = 4 * (s->timing.hblank_start - s->timing.hblank_clear);
> +        new_height = s->timing.vblank_start - s->timing.vblank_clear;
> +        s->size_changed = 0;
> +        if ((new_width != s->width || new_height != s->height) && new_width > 0 && new_height > 0) {
> +            s->width = new_width;
> +            s->height = new_height;
> +            CG14_INFO("new resolution = %d x %d\n", new_width, new_height);
> +            qemu_console_resize(s->ds, s->width, s->height);
> +            s->dirty = 1;
> +        }
> +    }
> +
> +    if (!s->dirty || !s->width || !s->height) {
> +        return;
> +    }
> +
> +    if (ds_get_bits_per_pixel(s->ds) != 32) {
> +        CG14_ERROR("cg14_update: FIXME: bpp (%d) != 32, linesize %d\n",
> +            ds_get_bits_per_pixel(s->ds), ds_get_linesize(s->ds));
> +        return;
> +    }
> +    // if (is_surface_bgr(s->ds->surface))
> +
> +    draw_line = NULL;
> +    src_linesize = s->width;
> +    if (s->ctrl.mcr & CG14_MCR_VIDENABLE) {
> +        switch (s->ctrl.mcr & CG14_MCR_PIXMODE_MASK) {
> +        case CG14_MCR_PIXMODE_32:
> +            src_linesize *= 4;
> +            draw_line = cg14_draw_line32_rgb32;
> +            break;
> +        case CG14_MCR_PIXMODE_16:
> +            src_linesize *= 2;
> +            draw_line = cg14_draw_line16_rgb32;
> +            break;
> +        case CG14_MCR_PIXMODE_8:
> +            draw_line = cg14_draw_line8_rgb32;
> +            break;
> +        }
> +    }
> +    if (!draw_line) {
> +        /* blank */
> +        memset(ds_get_data(s->ds), 0, ds_get_linesize(s->ds)*ds_get_height(s->ds));
> +        s->dirty = 0;
> +        return;
> +    }
> +
> +    pix = s->vram;
> +    data = ds_get_data(s->ds);
> +
> +    for (h=0; h<s->height; h++) {
> +        draw_line(s, data, pix);
> +        pix += src_linesize;
> +        data += ds_get_linesize(s->ds);
> +    }
> +    dpy_update(s->ds, 0, 0, s->width, s->height);
> +    s->dirty = 0;
> +}
> +
> +static void cg14_invalidate_display(void *opaque)
> +{
> +    CG14State *s = opaque;
> +
> +    s->dirty = 1;
> +}
> +
> +static uint32_t dac_read(struct dac_state *s, unsigned int reg)
> +{
> +    uint32_t val = 0;
> +
> +    switch (reg) {
> +    case 0:
> +        val = s->address;
> +        break;
> +    case 1: /* lookup table */
> +        s->rgb_seq++;
> +        break;
> +    case 2: /* control registers */
> +        break;
> +    case 3:
> +        val = s->mode;
> +        break;
> +    }
> +    DPRINTF_DAC("read %02x from dac reg %d\n", val, reg);
> +    return val;
> +}
> +
> +static void dac_write(struct dac_state *s, unsigned int reg, unsigned int val)
> +{
> +    switch (reg) {
> +    case 0: /* address register */
> +        DPRINTF_DAC("write address %02x\n", val);
> +        s->address = val;
> +        s->rgb_seq = 0;
> +        break;
> +    case 1: /* lookup table */
> +        DPRINTF_DAC("write %02x to lookup table\n", val);
> +        s->rgb_seq++;
> +        break;
> +    case 2: /* control registers */
> +        DPRINTF_DAC("write %02x to control reg %d\n", val, s->address);
> +        switch (s->address) {
> +        default:
> +            break;
> +        }
> +        break;
> +    case 3: /* mode register */
> +        DPRINTF_DAC("write mode %02x (%d bit DAC, %d bit bus)\n",
> +            val, (val & 2) ? 10 : 8, (val & 4) ? 10 : 8);
> +        if (!val & 0x01) {
> +            // reset the dac
> +            s->rgb_seq = 0;
> +        }
> +        s->mode = val;
> +        break;
> +    }
> +}
> +
> +static uint32_t cg14_reg_readb(void *opaque, target_phys_addr_t addr)
> +{
> +    CG14State *s = opaque;
> +    uint32_t val;
> +    uint32_t i;
> +
> +    if ((addr & 0xfc00) == 0x2000) {
> +        i = (addr & 0x300) >> 8;
> +        return dac_read(&s->dac, i);
> +    }
> +
> +    switch (addr) {
> +    case 0x0000:
> +        val = s->ctrl.mcr;
> +        break;
> +    case 0x0001:
> +        val = s->ctrl.ppr;
> +        break;
> +    case 0x0004: /* status ? */
> +        val = s->ctrl.msr;
> +        break;
> +    case 0x0006: /* hw version */
> +        //val = 0x00; /* old version */
> +        val = 0x30;
> +        break;
> +    default:
> +        val = 0;
> +        CG14_INFO("readb from reg %x\n", (int)addr);
> +        break;
> +    }
> +
> +    return val;
> +}
> +
> +static void cg14_reg_writeb(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    CG14State *s = opaque;
> +    uint32_t i;
> +
> +    if ((addr & 0xfc00) == 0x2000) {
> +        i = (addr & 0x300) >> 8;
> +        dac_write(&s->dac, i, val);
> +        return;
> +    }
> +    if ((addr & 0xff00) == 0x3000) {
> +        /* xlut */
> +        i = addr & 0xff;
> +        if (s->xlut[i] != val) {
> +            s->dirty = 1;
> +            s->xlut[i] = val;
> +            if (val && val != 0x40)
> +                CG14_ERROR("writeb xlut[%d] = %02x\n", i, val);
> +        }
> +        return;
> +    }
> +
> +    s->dirty = 1;
> +
> +    switch (addr) {
> +    case 0x0000:
> +        s->ctrl.mcr = val;
> +        DPRINTF_CONFIG("write %02x to MCR\n", val);
> +        break;
> +    case 0x0001:
> +        s->ctrl.ppr = val & 0xF0;
> +        break;
> +    case 0x0007:
> +        /* clock control (ICS1562AM-001) */
> +        DPRINTF("write %02x to clock control\n", val);
> +        break;
> +    default:
> +        CG14_ERROR("writeb %02x to reg %x\n", val, (int)addr);
> +        break;
> +    }
> +}
> +
> +static uint32_t cg14_reg_readw(void *opaque, target_phys_addr_t addr)
> +{
> +    CG14State *s = opaque;
> +    uint32_t val;
> +
> +    switch (addr) {
> +    case 0x0018:
> +        val = s->timing.hblank_start;
> +        break;
> +    case 0x001a:
> +        val = s->timing.hblank_clear;
> +        break;
> +    case 0x0022:
> +        val = s->timing.vblank_start;
> +        break;
> +    case 0x0024:
> +        val = s->timing.vblank_clear;
> +        break;
> +    default:
> +        val = 0;
> +        CG14_INFO("readw from reg %x\n", (int)addr);
> +        break;
> +    }
> +
> +    return val;
> +}
> +
> +static void cg14_reg_writew(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    CG14State *s = opaque;
> +
> +    DPRINTF_CONFIG("writew %04x to reg %x\n", val, (int)addr);
> +
> +    /* timing registers are 16bit */
> +
> +    switch (addr) {
> +    case 0x0018:
> +        s->timing.hblank_start = val;
> +        break;
> +    case 0x001a:
> +        s->timing.hblank_clear = val;
> +       s->size_changed = 1;
> +        break;
> +    case 0x0022:
> +        s->timing.vblank_start = val;
> +        break;
> +    case 0x0024:
> +        s->timing.vblank_clear = val;
> +       s->size_changed = 1;
> +        break;
> +    case 0x001c: /* hsync_start */
> +    case 0x001e: /* hsync_clear */
> +    case 0x0020: /* csync_clear */
> +    case 0x0026: /* vsync_start */
> +    case 0x0028: /* vsync_clear */
> +    default:
> +        break;
> +    }
> +}
> +
> +static uint32_t cg14_reg_readl(void *opaque, target_phys_addr_t addr)
> +{
> +    CG14State *s = opaque;
> +    uint32_t val;
> +    uint32_t i;
> +
> +    i = (addr & 0x3ff) >> 2;
> +    switch (addr & 0xfc00) {
> +    case 0x4000:
> +        val = s->clut1[i];
> +        break;
> +    case 0x5000:
> +        val = s->clut2[i];
> +        break;
> +    default:
> +        val = 0;
> +        CG14_ERROR("readl %08x from reg %x\n", val, (int)addr);
> +        break;
> +    }
> +
> +    return val;
> +}
> +
> +static void cg14_reg_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    CG14State *s = opaque;
> +    uint32_t i;
> +
> +    s->dirty = 1;
> +
> +    i = addr & 0x3ff;
> +    switch (addr & 0xfc00) {
> +    case 0x3000:
> +        if (i < 256) {
> +            s->xlut[i+0] = (uint8_t)(val >> 24);
> +            s->xlut[i+1] = (uint8_t)(val >> 16);
> +            s->xlut[i+2] = (uint8_t)(val >> 8);
> +            s->xlut[i+3] = (uint8_t)val;
> +        }
> +        break;
> +    case 0x4000:
> +        s->clut1[i >> 2] = val;
> +        break;
> +    case 0x5000:
> +        s->clut2[i >> 2] = val;
> +        break;
> +    default:
> +        CG14_ERROR("writel %08x to reg %x\n", val, (int)addr);
> +        break;
> +    }
> +}
> +
> +static CPUReadMemoryFunc *cg14_reg_read[3] = {
> +    cg14_reg_readb,
> +    cg14_reg_readw,
> +    cg14_reg_readl,
> +};
> +
> +static CPUWriteMemoryFunc *cg14_reg_write[3] = {
> +    cg14_reg_writeb,
> +    cg14_reg_writew,
> +    cg14_reg_writel,
> +};
> +
> +static uint32_t cg14_vram_readb(void *opaque, target_phys_addr_t addr)
> +{
> +    CG14State *s = opaque;
> +    uint32_t offset;
> +    uint32_t val = 0;
> +
> +    switch (addr & 0x3000000) {
> +    case 0x0000000:
> +    case 0x1000000:
> +        offset = addr & s->vram_amask;
> +        val = ldub_p(s->vram+offset);
> +        break;
> +    case 0x2000000:
> +        offset = ((addr << 1) & s->vram_amask) + ((addr >> 23) & 1);
> +        val = ldub_p(s->vram+offset);
> +        break;
> +    case 0x3000000:
> +        offset = ((addr << 2) & s->vram_amask) + ((addr >> 22) & 3);
> +        val = ldub_p(s->vram+offset);
> +        break;
> +    }
> +
> +    return val;
> +}
> +
> +static void cg14_vram_writeb(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    CG14State *s = opaque;
> +    uint32_t offset;
> +
> +    s->dirty = 1;
> +
> +    switch (addr & 0x3000000) {
> +    case 0x0000000:
> +        offset = addr & s->vram_amask;
> +        stb_p(s->vram+offset, val);
> +        break;
> +    case 0x1000000:
> +        offset = addr & s->vram_amask;
> +        /* block writes to X */
> +        if (offset & 3) {
> +            stb_p(s->vram+offset, val);
> +        }
> +        break;
> +    case 0x2000000:
> +        offset = ((addr << 1) & s->vram_amask) + ((addr >> 23) & 1);
> +        stb_p(s->vram+offset, val);
> +        break;
> +    case 0x3000000:
> +        offset = ((addr << 2) & s->vram_amask) + ((addr >> 22) & 3);
> +        stb_p(s->vram+offset, val);
> +        break;
> +    }
> +}
> +
> +static uint32_t cg14_vram_readw(void *opaque, target_phys_addr_t addr)
> +{
> +    uint32_t val;
> +
> +    val = cg14_vram_readb(opaque, addr) << 8;
> +    val |= cg14_vram_readb(opaque, addr+1);
> +
> +    return val;
> +}
> +
> +static void cg14_vram_writew(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    cg14_vram_writeb(opaque, addr, val >> 8);
> +    cg14_vram_writeb(opaque, addr+1, val & 0xff);
> +}
> +
> +static uint32_t cg14_vram_readl(void *opaque, target_phys_addr_t addr)
> +{
> +    CG14State *s = opaque;
> +    uint32_t offset;
> +    uint32_t val = 0;
> +
> +    switch (addr & 0x3000000) {
> +    case 0x0000000:
> +    case 0x1000000:
> +        offset = addr & s->vram_amask;
> +        val = ldl_be_p(s->vram+offset);
> +        break;
> +    case 0x2000000:
> +        offset = ((addr << 1) & s->vram_amask) + ((addr >> 23) & 1);
> +        val =  ldub_p(s->vram+offset+0) << 24;
> +        val |= ldub_p(s->vram+offset+2) << 16;
> +        val |= ldub_p(s->vram+offset+4) << 8;
> +        val |= ldub_p(s->vram+offset+6);
> +        break;
> +    case 0x3000000:
> +        offset = ((addr << 2) & s->vram_amask) + ((addr >> 22) & 3);
> +        val =  ldub_p(s->vram+offset+0) << 24;
> +        val |= ldub_p(s->vram+offset+4) << 16;
> +        val |= ldub_p(s->vram+offset+8) << 8;
> +        val |= ldub_p(s->vram+offset+12);
> +        break;
> +    }
> +
> +    return val;
> +}
> +
> +static void cg14_vram_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    CG14State *s = opaque;
> +    uint32_t offset;
> +
> +    s->dirty = 1;
> +
> +    switch (addr & 0x3000000) {
> +    case 0x0000000:
> +        offset = addr & s->vram_amask;
> +        stl_be_p(s->vram+offset, val);
> +        break;
> +    case 0x1000000:
> +        offset = addr & s->vram_amask;
> +        /* block writes to X */
> +        stb_p(s->vram+offset+1, val >> 16);
> +        stb_p(s->vram+offset+2, val >> 8);
> +        stb_p(s->vram+offset+3, val);
> +        break;
> +    case 0x2000000:
> +        offset = ((addr << 1) & s->vram_amask) + ((addr >> 23) & 1);
> +        stb_p(s->vram+offset+0, val >> 24);
> +        stb_p(s->vram+offset+2, val >> 16);
> +        stb_p(s->vram+offset+4, val >> 8);
> +        stb_p(s->vram+offset+6, val);
> +        break;
> +    case 0x3000000:
> +        offset = ((addr << 2) & s->vram_amask) + ((addr >> 22) & 3);
> +        stb_p(s->vram+offset+0,  val >> 24);
> +        stb_p(s->vram+offset+4,  val >> 16);
> +        stb_p(s->vram+offset+8,  val >> 8);
> +        stb_p(s->vram+offset+12, val);
> +        break;
> +    }
> +}
> +
> +static CPUReadMemoryFunc *cg14_vram_read[3] = {
> +    cg14_vram_readb,
> +    cg14_vram_readw,
> +    cg14_vram_readl,
> +};
> +
> +static CPUWriteMemoryFunc *cg14_vram_write[3] = {
> +    cg14_vram_writeb,
> +    cg14_vram_writew,
> +    cg14_vram_writel,
> +};
> +
> +
> +static void cg14_set_monitor_id(CG14State *s)
> +{
> +    uint8_t id;
> +
> +    /* pick something close, used as a default by Sun's OBP */
> +    if (s->width >= 1600) {
> +        id = CG14_MONID_1600x1280;
> +    } else if (s->width >= 1280) {
> +        id = CG14_MONID_1280x1024;
> +    } else if (s->width >= 1152) {
> +        id = CG14_MONID_1152x900;
> +    } else if (s->width >= 1024) {
> +        id = CG14_MONID_1024x768;
> +    } else {
> +        id = CG14_MONID_DEFAULT;
> +    }
> +
> +    /* monitor code in bits 1..3 */
> +    s->ctrl.msr = id << 1;
> +}
> +
> +static int cg14_init1(SysBusDevice *dev)
> +{
> +    CG14State *s = FROM_SYSBUS(CG14State, dev);
> +    ram_addr_t vram_offset;
> +    uint8_t *vram;
> +    int ctrl_memory, vram_memory;
> +
> +    vram_offset = qemu_ram_alloc(NULL, "cg14.vram", s->vram_size);
> +    vram = qemu_get_ram_ptr(vram_offset);
> +
> +    s->vram = vram;
> +    s->vram_amask = s->vram_size - 1;
> +
> +    ctrl_memory = cpu_register_io_memory(cg14_reg_read, cg14_reg_write, s);
> +    sysbus_init_mmio(dev, CG14_REG_SIZE, ctrl_memory);
> +
> +    /* TODO: register first vram mapping as ram with dirty tracking */
> +    vram_memory = cpu_register_io_memory(cg14_vram_read, cg14_vram_write, s);
> +    sysbus_init_mmio(dev, CG14_VMEM_SLOTSIZE, vram_memory);
> +
> +    s->ds = graphic_console_init(cg14_update_display,
> +                                 cg14_invalidate_display,
> +                                 cg14_screen_dump, NULL, s);
> +
> +    cg14_set_monitor_id(s);
> +
> +    qemu_console_resize(s->ds, s->width, s->height);
> +    return 0;
> +}
> +
> +/* save to file */
> +static void cg14_screen_dump(void *opaque, const char *filename)
> +{
> +    CG14State *s = opaque;
> +    FILE *f;
> +    int y, src_linesize, dst_linesize;
> +    void *buf;
> +    uint8_t *pix;
> +    draw_line_func * draw_line = NULL;
> +
> +    switch (s->ctrl.mcr & CG14_MCR_PIXMODE_MASK) {
> +    case CG14_MCR_PIXMODE_32:
> +        src_linesize = s->width * 4;
> +        // draw_line = cg14_draw_line32_bgr24;
> +        break;
> +    case CG14_MCR_PIXMODE_16:
> +        src_linesize = s->width * 2;
> +        // draw_line = cg14_draw_line16_bgr24;
> +        break;
> +    case CG14_MCR_PIXMODE_8:
> +        src_linesize = s->width;
> +        // draw_line = cg14_draw_line8_bgr24;
> +        break;
> +    default:
> +        /* blank */
> +        return;
> +    }
> +
> +    f = fopen(filename, "wb");
> +    if (!f) {
> +        return;
> +    }
> +    fprintf(f, "P6\n%d %d\n%d\n", s->width, s->height, 255);
> +
> +    dst_linesize = s->width * 3;
> +    buf = qemu_mallocz(dst_linesize);
> +    pix = s->vram;
> +
> +    for (y=0; y<s->height; y++) {
> +        if (draw_line)
> +            draw_line(s, buf, pix);
> +        fwrite(buf, 1, dst_linesize, f);
> +        pix += src_linesize;
> +    }
> +
> +    qemu_free(buf);
> +    fclose(f);
> +}
> +
> +static void cg14_reset(DeviceState *d)
> +{
> +    CG14State *s = container_of(d, CG14State, busdev.qdev);
> +
> +    /* set to 8bpp so last prom output might be visible */
> +    s->ctrl.mcr = CG14_MCR_VIDENABLE | CG14_MCR_PIXMODE_8;
> +    s->dirty = 1;
> +}
> +
> +static SysBusDeviceInfo cg14_info = {
> +    .init = cg14_init1,
> +    .qdev.name = "cg14",
> +    .qdev.desc = "Sun CG14 Framebuffer",
> +    .qdev.size = sizeof(CG14State),
> +    .qdev.reset = cg14_reset,
> +    .qdev.props = (Property[]) {
> +        DEFINE_PROP_TADDR("vram_addr", CG14State, vram_addr, -1),
> +        DEFINE_PROP_HEX32("vram_size", CG14State, vram_size, 0x800000),
> +        DEFINE_PROP_UINT16("width",    CG14State, width,     0),
> +        DEFINE_PROP_UINT16("height",   CG14State, height,    0),
> +        DEFINE_PROP_END_OF_LIST(),
> +    }
> +};
> +
> +static void cg14_register_devices(void)
> +{
> +    sysbus_register_withprop(&cg14_info);
> +}
> +
> +device_init(cg14_register_devices)
> diff --git a/hw/sun4m.c b/hw/sun4m.c
> index e7a4cf6..0665e1f 100644
> --- a/hw/sun4m.c
> +++ b/hw/sun4m.c
> @@ -577,6 +577,23 @@ static void tcx_init(target_phys_addr_t addr, int vram_size, int width,
>     }
>  }
>
> +static void cg14_init(target_phys_addr_t ctrl_base,
> +                      target_phys_addr_t vram_base,
> +                      int width, int height)
> +{
> +    DeviceState *dev;
> +    SysBusDevice *s;
> +
> +    dev = qdev_create(NULL, "cg14");
> +    qdev_prop_set_taddr(dev, "vram_addr", vram_base);
> +    qdev_prop_set_uint16(dev, "width", width);
> +    qdev_prop_set_uint16(dev, "height", height);
> +    qdev_init_nofail(dev);
> +    s = sysbus_from_qdev(dev);
> +    sysbus_mmio_map(s, 0, ctrl_base);
> +    sysbus_mmio_map(s, 1, vram_base);
> +}
> +
>  /* NCR89C100/MACIO Internal ID register */
>  static const uint8_t idreg_data[] = { 0xfe, 0x81, 0x01, 0x03 };
>
> @@ -879,6 +896,11 @@ static void sun4m_hw_init(const struct sun4m_hwdef *hwdef, ram_addr_t RAM_size,
>         exit (1);
>     }
>     num_vsimms = 0;
> +    if (hwdef->vsimm[0].vram_base && (graphic_width > 1024 || !hwdef->tcx_base)) {
> +        cg14_init(hwdef->vsimm[0].reg_base, hwdef->vsimm[0].vram_base,
> +                  graphic_width, graphic_height);
> +        num_vsimms++;
> +    }
>     if (num_vsimms == 0) {
>         tcx_init(hwdef->tcx_base, 0x00100000, graphic_width, graphic_height,
>                  graphic_depth);
> --
> 1.6.2.2.1669.g7eaf8
>
>
>
>
Blue Swirl - July 20, 2010, 9:39 p.m.
On Tue, Jul 13, 2010 at 4:26 PM, Bob Breuer <breuerr@mc.net> wrote:
> Another preview of the cg14 framebuffer.
>
> Activate by selecting SS-20 machine and setting width > 1024, i.e. "-M SS-20 -g 1152x900".
> Note that NetBSD assumes 1152x900, while OBP also supports 1024x768, 1280x1024, and 1600x1280.
>
> New since last time:
> - All video memory accesses implemented
>    X under linux now works (uses RGB instead of XRGB space)
> - Hooked into qdev
>
> Todo:
> - fix NetBSD display
> - add draw_line templates to handle other than 32-bit RGB host displays
> - use VGA_DIRTY tracking
>    - What's the equivalent of stb_p that also sets the dirty bits?

You could also write directly and then call cpu_physical_memory_set_dirty().

> - inform OpenBIOS of cg14 framebuffer
>    - Can we pass "nvalias screen /obio/cgfourteen" to the firmware?
>
> Bob
>
> ---
>  Makefile.target |    1 +
>  hw/cg14.c       |  850 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  hw/sun4m.c      |   22 ++
>  3 files changed, 873 insertions(+), 0 deletions(-)
>  create mode 100644 hw/cg14.c
>
> diff --git a/Makefile.target b/Makefile.target
> index 3ef4666..54a2ae4 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -255,6 +255,7 @@ else
>  obj-sparc-y = sun4m.o lance.o tcx.o sun4m_iommu.o slavio_intctl.o
>  obj-sparc-y += slavio_timer.o slavio_misc.o sparc32_dma.o
>  obj-sparc-y += cs4231.o eccmemctl.o sbi.o sun4c_intctl.o
> +obj-sparc-y += cg14.o
>  endif
>
>  obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o
> diff --git a/hw/cg14.c b/hw/cg14.c
> new file mode 100644
> index 0000000..533dc04
> --- /dev/null
> +++ b/hw/cg14.c
> @@ -0,0 +1,850 @@
> +/*
> + * QEMU CG14 Frame buffer
> + *
> + * Copyright (c) 2010 Bob Breuer  <breuerr@mc.net>
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a copy
> + * of this software and associated documentation files (the "Software"), to deal
> + * in the Software without restriction, including without limitation the rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> + * THE SOFTWARE.
> + */
> +
> +#include "console.h"
> +#include "sysbus.h"
> +#include "qdev-addr.h"
> +
> +//#define DEBUG_CG14
> +//#define DEBUG_DAC
> +//#define DEBUG_CONFIG
> +
> +/*
> + * Sun CG14 framebuffer (without SX)
> + *   CG14 = vsimm framebuffer (video ram and dac)
> + *   SX = pixel processor (acceleration) built into chipset
> + *
> + * Documentation: not publicly documented by Sun
> + *   linux driver: drivers/video/cg14.c
> + *   NetBSD/OpenBSD: src/sys/arch/sparc/dev/cgfourteen*
> + *
> + * Takes up one memory slot:
> + *   A[28:26] = slot number (4 to 7)
> + *   regs: size   0x10000 @ 0x09c000000  (0x80000000 + slot * 64M)
> + *   vmem: size upto 16MB @ 0x0fc000000  (0xE0000000 + slot * 64M)
> + *
> + * SS-20 OBP only supports slots 7 (onboard output) and 4 (AVB output)
> + *
> + * memory map:
> + * reg+0x0000 = control registers
> + * reg+0x1000 = cursor registers
> + * reg+0x2000 = dac registers (ADV7152)
> + * reg+0x3000 = xlut
> + * reg+0x4000 = clut1
> + * reg+0x5000 = clut2
> + * reg+0x6000 = clut3 (if implemented)
> + * reg+0xf000 = autoinc
> + *
> + * mem+0x0000000 = XBGR (01234567)
> + * mem+0x1000000 = BGR  (.123.567)  writes to X are blocked, reads are ok
> + * mem+0x2000000 = X16  (0246)
> + * mem+0x2800000 = C16  (1357)
> + * mem+0x3000000 = X32  (04)
> + * mem+0x3400000 = B32  (15)
> + * mem+0x3800000 = G32  (26)
> + * mem+0x3c00000 = R32  (37)
> + */
> +
> +#define CG14_REG_SIZE         0x10000
> +#define CG14_VMEM_SLOTSIZE    (64<<20)
> +
> +#define CG14_MONID_1024x768   0
> +#define CG14_MONID_1600x1280  1
> +#define CG14_MONID_1280x1024  2
> +#define CG14_MONID_1152x900   7
> +
> +#define CG14_MONID_DEFAULT    CG14_MONID_1024x768
> +
> +
> +#define CG14_MCR_INTENABLE     0x80
> +#define CG14_MCR_VIDENABLE     0x40
> +#define CG14_MCR_PIXMODE_MASK  0x30
> +#define   CG14_MCR_PIXMODE_8     0x00
> +#define   CG14_MCR_PIXMODE_16    0x20  /* 8+8 (X16,C16) */
> +#define   CG14_MCR_PIXMODE_32    0x30  /* XBGR */
> +
> +
> +#ifdef DEBUG_CG14
> +#define DPRINTF(fmt, ...)                                       \
> +    printf("CG14: " fmt , ## __VA_ARGS__)
> +#else
> +#define DPRINTF(fmt, ...)
> +#endif
> +
> +#ifdef DEBUG_DAC
> +#define DPRINTF_DAC(fmt, ...)                                   \
> +    printf("CG14 dac: " fmt , ## __VA_ARGS__)
> +#else
> +#define DPRINTF_DAC(fmt, ...)
> +#endif
> +
> +#ifdef DEBUG_CONFIG
> +#define DPRINTF_CONFIG(fmt, ...)                                \
> +    printf("CG14: " fmt , ## __VA_ARGS__)
> +#else
> +#define DPRINTF_CONFIG(fmt, ...)
> +#endif
> +
> +#define CG14_INFO(fmt, ...)                                     \
> +    do { printf("CG14: " fmt , ## __VA_ARGS__); } while (0)
> +#define CG14_ERROR(fmt, ...)                                    \
> +    do { printf("CG14: " fmt , ## __VA_ARGS__); } while (0)
> +
> +
> +struct dac_state {
> +    uint8_t mode;
> +    uint8_t address;
> +    int rgb_seq;
> +};
> +
> +typedef struct CG14State {
> +    SysBusDevice busdev;
> +    DisplayState *ds;
> +
> +    target_phys_addr_t vram_addr;
> +    uint8_t *vram;
> +    uint32_t vram_size;
> +    uint32_t vram_amask;
> +    uint16_t width, height;
> +    int dirty, size_changed;
> +    struct {
> +        uint8_t mcr;
> +        uint8_t ppr;
> +        uint8_t msr;
> +    } ctrl;
> +    struct dac_state dac;
> +    struct {
> +        uint16_t hblank_start;
> +        uint16_t hblank_clear;
> +        uint16_t vblank_start;
> +        uint16_t vblank_clear;
> +    } timing;
> +    uint8_t xlut[256];
> +    uint32_t clut1[256];
> +    uint32_t clut2[256];
> +} CG14State;
> +
> +static void cg14_screen_dump(void *opaque, const char *filename);
> +static void cg14_invalidate_display(void *opaque);
> +
> +static inline uint32_t bgr_to_rgb(uint32_t bgr)
> +{
> +    uint32_t rgb;
> +
> +    /* swap r & b */
> +    rgb = (bgr & 0x00FF00)
> +        | (bgr & 0x0000FF) << 16
> +        | (bgr & 0xFF0000) >> 16;
> +    return rgb;
> +}
> +
> +typedef void draw_line_func(const CG14State *s, void *dst, const uint8_t *src);
> +
> +// TODO: draw_line templates
> +static void cg14_draw_line32_rgb32(const CG14State *s, void *dst, const uint8_t *src)
> +{
> +    unsigned int i;
> +    unsigned int x, r, g, b;
> +    uint8_t xlut_val;
> +    uint32_t dval;
> +
> +    for (i=0; i<s->width; i++) {
> +        x = src[0];
> +        b = src[1];
> +        g = src[2];
> +        r = src[3];
> +        xlut_val = s->xlut[x];
> +        src += 4;
> +
> +        if (xlut_val == 0x40) {
> +            dval = bgr_to_rgb(s->clut1[x]);

You could translate s->clut1[] already when the clut registers are
written so that this routine could use only table lookup.

> +        } else {
> +            /* xlut = 0x00 for true-color */
> +            /* possible blending between 2 colors */
> +            /* fallback to true-color just to display something if unimplemented */
> +            dval = r << 16 | g << 8 | b;
> +        }
> +        /* dac lookup ? */
> +
> +        /* to surface format */
> +        //dval = is_bgr ? (abgr & 0xFFFFFF) : bgr_to_rgb(abgr);
> +        ((uint32_t*)dst)[i] = dval;

I think it's better to add uint32_t *p variable and just do *p++ =
dval, like TCX does.

> +    }
> +}
> +
> +static void cg14_draw_line16_rgb32(const CG14State *s, void *dst, const uint8_t *src)
> +{
> +    unsigned int i;
> +    unsigned int x, c;
> +    uint8_t xlut_val;
> +    uint32_t dval;
> +
> +    for (i=0; i<s->width; i++) {
> +        x = src[0];
> +        c = src[1];
> +        xlut_val = s->xlut[x];
> +        src += 2;
> +
> +        if (xlut_val == 0x40) {
> +            dval = bgr_to_rgb(s->clut1[x]);
> +        } else {
> +            /* possible blending between 2 colors */
> +            /* fallback to green/blue just to display something if unimplemented */
> +            dval = x << 8 | c;
> +        }
> +        /* dac lookup ? */
> +
> +        ((uint32_t*)dst)[i] = dval;
> +    }
> +}
> +
> +static void cg14_draw_line8_rgb32(const CG14State *s, void *dst, const uint8_t *src)
> +{
> +    int i;
> +    const uint32_t *clut;
> +    uint32_t *dst32 = dst;
> +
> +    if (s->ctrl.ppr == 0x40) {
> +        clut = s->clut1;
> +        for (i=0; i<s->width; i++) {
> +            dst32[i] = bgr_to_rgb(clut[src[i]]);
> +        }
> +    } else {
> +        /* FIXME: other xlut modes, just use grayscale ramp for now */
> +        for (i=0; i<s->width; i++) {
> +            dst32[i] = 0x010101 * src[i];
> +        }
> +    }
> +}
> +
> +/* TODO: use VGA_DIRTY_FLAG */
> +static void cg14_update_display(void *opaque)
> +{
> +    CG14State *s = opaque;
> +    int h, src_linesize;
> +    uint8_t *pix;
> +    uint8_t *data;
> +    int new_width, new_height;
> +    draw_line_func * draw_line;
> +
> +    if (s->size_changed) {
> +        new_width = 4 * (s->timing.hblank_start - s->timing.hblank_clear);
> +        new_height = s->timing.vblank_start - s->timing.vblank_clear;
> +        s->size_changed = 0;
> +        if ((new_width != s->width || new_height != s->height) && new_width > 0 && new_height > 0) {
> +            s->width = new_width;
> +            s->height = new_height;
> +            CG14_INFO("new resolution = %d x %d\n", new_width, new_height);
> +            qemu_console_resize(s->ds, s->width, s->height);
> +            s->dirty = 1;
> +        }
> +    }
> +
> +    if (!s->dirty || !s->width || !s->height) {
> +        return;
> +    }
> +
> +    if (ds_get_bits_per_pixel(s->ds) != 32) {
> +        CG14_ERROR("cg14_update: FIXME: bpp (%d) != 32, linesize %d\n",
> +            ds_get_bits_per_pixel(s->ds), ds_get_linesize(s->ds));
> +        return;
> +    }
> +    // if (is_surface_bgr(s->ds->surface))
> +
> +    draw_line = NULL;
> +    src_linesize = s->width;
> +    if (s->ctrl.mcr & CG14_MCR_VIDENABLE) {
> +        switch (s->ctrl.mcr & CG14_MCR_PIXMODE_MASK) {
> +        case CG14_MCR_PIXMODE_32:
> +            src_linesize *= 4;
> +            draw_line = cg14_draw_line32_rgb32;
> +            break;
> +        case CG14_MCR_PIXMODE_16:
> +            src_linesize *= 2;
> +            draw_line = cg14_draw_line16_rgb32;
> +            break;
> +        case CG14_MCR_PIXMODE_8:
> +            draw_line = cg14_draw_line8_rgb32;
> +            break;
> +        }
> +    }
> +    if (!draw_line) {
> +        /* blank */
> +        memset(ds_get_data(s->ds), 0, ds_get_linesize(s->ds)*ds_get_height(s->ds));
> +        s->dirty = 0;
> +        return;
> +    }
> +
> +    pix = s->vram;
> +    data = ds_get_data(s->ds);
> +
> +    for (h=0; h<s->height; h++) {
> +        draw_line(s, data, pix);
> +        pix += src_linesize;
> +        data += ds_get_linesize(s->ds);
> +    }
> +    dpy_update(s->ds, 0, 0, s->width, s->height);
> +    s->dirty = 0;
> +}
> +
> +static void cg14_invalidate_display(void *opaque)
> +{
> +    CG14State *s = opaque;
> +
> +    s->dirty = 1;
> +}
> +
> +static uint32_t dac_read(struct dac_state *s, unsigned int reg)
> +{
> +    uint32_t val = 0;
> +
> +    switch (reg) {
> +    case 0:
> +        val = s->address;
> +        break;
> +    case 1: /* lookup table */
> +        s->rgb_seq++;
> +        break;
> +    case 2: /* control registers */
> +        break;
> +    case 3:
> +        val = s->mode;
> +        break;
> +    }
> +    DPRINTF_DAC("read %02x from dac reg %d\n", val, reg);
> +    return val;
> +}
> +
> +static void dac_write(struct dac_state *s, unsigned int reg, unsigned int val)
> +{
> +    switch (reg) {
> +    case 0: /* address register */
> +        DPRINTF_DAC("write address %02x\n", val);
> +        s->address = val;
> +        s->rgb_seq = 0;
> +        break;
> +    case 1: /* lookup table */
> +        DPRINTF_DAC("write %02x to lookup table\n", val);
> +        s->rgb_seq++;
> +        break;
> +    case 2: /* control registers */
> +        DPRINTF_DAC("write %02x to control reg %d\n", val, s->address);
> +        switch (s->address) {
> +        default:
> +            break;
> +        }
> +        break;
> +    case 3: /* mode register */
> +        DPRINTF_DAC("write mode %02x (%d bit DAC, %d bit bus)\n",
> +            val, (val & 2) ? 10 : 8, (val & 4) ? 10 : 8);
> +        if (!val & 0x01) {
> +            // reset the dac
> +            s->rgb_seq = 0;
> +        }
> +        s->mode = val;
> +        break;
> +    }
> +}
> +
> +static uint32_t cg14_reg_readb(void *opaque, target_phys_addr_t addr)
> +{
> +    CG14State *s = opaque;
> +    uint32_t val;
> +    uint32_t i;
> +
> +    if ((addr & 0xfc00) == 0x2000) {
> +        i = (addr & 0x300) >> 8;
> +        return dac_read(&s->dac, i);
> +    }
> +
> +    switch (addr) {
> +    case 0x0000:
> +        val = s->ctrl.mcr;
> +        break;
> +    case 0x0001:
> +        val = s->ctrl.ppr;
> +        break;
> +    case 0x0004: /* status ? */
> +        val = s->ctrl.msr;
> +        break;
> +    case 0x0006: /* hw version */
> +        //val = 0x00; /* old version */
> +        val = 0x30;
> +        break;
> +    default:
> +        val = 0;
> +        CG14_INFO("readb from reg %x\n", (int)addr);
> +        break;
> +    }
> +
> +    return val;
> +}
> +
> +static void cg14_reg_writeb(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    CG14State *s = opaque;
> +    uint32_t i;
> +
> +    if ((addr & 0xfc00) == 0x2000) {
> +        i = (addr & 0x300) >> 8;
> +        dac_write(&s->dac, i, val);
> +        return;
> +    }
> +    if ((addr & 0xff00) == 0x3000) {
> +        /* xlut */
> +        i = addr & 0xff;
> +        if (s->xlut[i] != val) {
> +            s->dirty = 1;
> +            s->xlut[i] = val;
> +            if (val && val != 0x40)
> +                CG14_ERROR("writeb xlut[%d] = %02x\n", i, val);
> +        }
> +        return;
> +    }
> +
> +    s->dirty = 1;
> +
> +    switch (addr) {
> +    case 0x0000:
> +        s->ctrl.mcr = val;
> +        DPRINTF_CONFIG("write %02x to MCR\n", val);
> +        break;
> +    case 0x0001:
> +        s->ctrl.ppr = val & 0xF0;
> +        break;
> +    case 0x0007:
> +        /* clock control (ICS1562AM-001) */
> +        DPRINTF("write %02x to clock control\n", val);
> +        break;
> +    default:
> +        CG14_ERROR("writeb %02x to reg %x\n", val, (int)addr);
> +        break;
> +    }
> +}
> +
> +static uint32_t cg14_reg_readw(void *opaque, target_phys_addr_t addr)
> +{
> +    CG14State *s = opaque;
> +    uint32_t val;
> +
> +    switch (addr) {
> +    case 0x0018:
> +        val = s->timing.hblank_start;
> +        break;
> +    case 0x001a:
> +        val = s->timing.hblank_clear;
> +        break;
> +    case 0x0022:
> +        val = s->timing.vblank_start;
> +        break;
> +    case 0x0024:
> +        val = s->timing.vblank_clear;
> +        break;
> +    default:
> +        val = 0;
> +        CG14_INFO("readw from reg %x\n", (int)addr);
> +        break;
> +    }
> +
> +    return val;
> +}
> +
> +static void cg14_reg_writew(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    CG14State *s = opaque;
> +
> +    DPRINTF_CONFIG("writew %04x to reg %x\n", val, (int)addr);
> +
> +    /* timing registers are 16bit */
> +
> +    switch (addr) {
> +    case 0x0018:
> +        s->timing.hblank_start = val;
> +        break;
> +    case 0x001a:
> +        s->timing.hblank_clear = val;
> +       s->size_changed = 1;

If the size changes, the entire display is invalid.

> +        break;
> +    case 0x0022:
> +        s->timing.vblank_start = val;
> +        break;
> +    case 0x0024:
> +        s->timing.vblank_clear = val;
> +       s->size_changed = 1;
> +        break;
> +    case 0x001c: /* hsync_start */
> +    case 0x001e: /* hsync_clear */
> +    case 0x0020: /* csync_clear */
> +    case 0x0026: /* vsync_start */
> +    case 0x0028: /* vsync_clear */
> +    default:
> +        break;
> +    }
> +}
> +
> +static uint32_t cg14_reg_readl(void *opaque, target_phys_addr_t addr)
> +{
> +    CG14State *s = opaque;
> +    uint32_t val;
> +    uint32_t i;
> +
> +    i = (addr & 0x3ff) >> 2;
> +    switch (addr & 0xfc00) {
> +    case 0x4000:
> +        val = s->clut1[i];
> +        break;
> +    case 0x5000:
> +        val = s->clut2[i];
> +        break;
> +    default:
> +        val = 0;
> +        CG14_ERROR("readl %08x from reg %x\n", val, (int)addr);
> +        break;
> +    }
> +
> +    return val;
> +}
> +
> +static void cg14_reg_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    CG14State *s = opaque;
> +    uint32_t i;
> +
> +    s->dirty = 1;
> +
> +    i = addr & 0x3ff;
> +    switch (addr & 0xfc00) {
> +    case 0x3000:
> +        if (i < 256) {
> +            s->xlut[i+0] = (uint8_t)(val >> 24);
> +            s->xlut[i+1] = (uint8_t)(val >> 16);
> +            s->xlut[i+2] = (uint8_t)(val >> 8);
> +            s->xlut[i+3] = (uint8_t)val;
> +        }
> +        break;
> +    case 0x4000:
> +        s->clut1[i >> 2] = val;
> +        break;
> +    case 0x5000:
> +        s->clut2[i >> 2] = val;
> +        break;
> +    default:
> +        CG14_ERROR("writel %08x to reg %x\n", val, (int)addr);
> +        break;
> +    }
> +}
> +
> +static CPUReadMemoryFunc *cg14_reg_read[3] = {

Please add 'const', also in other tables.

> +    cg14_reg_readb,
> +    cg14_reg_readw,
> +    cg14_reg_readl,
> +};
> +
> +static CPUWriteMemoryFunc *cg14_reg_write[3] = {
> +    cg14_reg_writeb,
> +    cg14_reg_writew,
> +    cg14_reg_writel,
> +};
> +
> +static uint32_t cg14_vram_readb(void *opaque, target_phys_addr_t addr)
> +{
> +    CG14State *s = opaque;
> +    uint32_t offset;
> +    uint32_t val = 0;
> +
> +    switch (addr & 0x3000000) {
> +    case 0x0000000:
> +    case 0x1000000:
> +        offset = addr & s->vram_amask;
> +        val = ldub_p(s->vram+offset);
> +        break;
> +    case 0x2000000:
> +        offset = ((addr << 1) & s->vram_amask) + ((addr >> 23) & 1);
> +        val = ldub_p(s->vram+offset);
> +        break;
> +    case 0x3000000:
> +        offset = ((addr << 2) & s->vram_amask) + ((addr >> 22) & 3);
> +        val = ldub_p(s->vram+offset);
> +        break;
> +    }
> +
> +    return val;
> +}
> +
> +static void cg14_vram_writeb(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    CG14State *s = opaque;
> +    uint32_t offset;
> +
> +    s->dirty = 1;
> +
> +    switch (addr & 0x3000000) {
> +    case 0x0000000:
> +        offset = addr & s->vram_amask;
> +        stb_p(s->vram+offset, val);
> +        break;
> +    case 0x1000000:
> +        offset = addr & s->vram_amask;
> +        /* block writes to X */
> +        if (offset & 3) {
> +            stb_p(s->vram+offset, val);
> +        }
> +        break;
> +    case 0x2000000:
> +        offset = ((addr << 1) & s->vram_amask) + ((addr >> 23) & 1);
> +        stb_p(s->vram+offset, val);
> +        break;
> +    case 0x3000000:
> +        offset = ((addr << 2) & s->vram_amask) + ((addr >> 22) & 3);
> +        stb_p(s->vram+offset, val);
> +        break;
> +    }
> +}
> +
> +static uint32_t cg14_vram_readw(void *opaque, target_phys_addr_t addr)
> +{
> +    uint32_t val;
> +
> +    val = cg14_vram_readb(opaque, addr) << 8;
> +    val |= cg14_vram_readb(opaque, addr+1);
> +
> +    return val;
> +}
> +
> +static void cg14_vram_writew(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    cg14_vram_writeb(opaque, addr, val >> 8);
> +    cg14_vram_writeb(opaque, addr+1, val & 0xff);
> +}
> +
> +static uint32_t cg14_vram_readl(void *opaque, target_phys_addr_t addr)
> +{
> +    CG14State *s = opaque;
> +    uint32_t offset;
> +    uint32_t val = 0;
> +
> +    switch (addr & 0x3000000) {
> +    case 0x0000000:
> +    case 0x1000000:
> +        offset = addr & s->vram_amask;
> +        val = ldl_be_p(s->vram+offset);
> +        break;
> +    case 0x2000000:
> +        offset = ((addr << 1) & s->vram_amask) + ((addr >> 23) & 1);
> +        val =  ldub_p(s->vram+offset+0) << 24;
> +        val |= ldub_p(s->vram+offset+2) << 16;
> +        val |= ldub_p(s->vram+offset+4) << 8;
> +        val |= ldub_p(s->vram+offset+6);
> +        break;
> +    case 0x3000000:
> +        offset = ((addr << 2) & s->vram_amask) + ((addr >> 22) & 3);
> +        val =  ldub_p(s->vram+offset+0) << 24;
> +        val |= ldub_p(s->vram+offset+4) << 16;
> +        val |= ldub_p(s->vram+offset+8) << 8;
> +        val |= ldub_p(s->vram+offset+12);
> +        break;
> +    }
> +
> +    return val;
> +}
> +
> +static void cg14_vram_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    CG14State *s = opaque;
> +    uint32_t offset;
> +
> +    s->dirty = 1;
> +
> +    switch (addr & 0x3000000) {
> +    case 0x0000000:
> +        offset = addr & s->vram_amask;
> +        stl_be_p(s->vram+offset, val);
> +        break;
> +    case 0x1000000:
> +        offset = addr & s->vram_amask;
> +        /* block writes to X */
> +        stb_p(s->vram+offset+1, val >> 16);
> +        stb_p(s->vram+offset+2, val >> 8);
> +        stb_p(s->vram+offset+3, val);
> +        break;
> +    case 0x2000000:
> +        offset = ((addr << 1) & s->vram_amask) + ((addr >> 23) & 1);
> +        stb_p(s->vram+offset+0, val >> 24);
> +        stb_p(s->vram+offset+2, val >> 16);
> +        stb_p(s->vram+offset+4, val >> 8);
> +        stb_p(s->vram+offset+6, val);
> +        break;
> +    case 0x3000000:
> +        offset = ((addr << 2) & s->vram_amask) + ((addr >> 22) & 3);
> +        stb_p(s->vram+offset+0,  val >> 24);
> +        stb_p(s->vram+offset+4,  val >> 16);
> +        stb_p(s->vram+offset+8,  val >> 8);
> +        stb_p(s->vram+offset+12, val);
> +        break;
> +    }
> +}
> +
> +static CPUReadMemoryFunc *cg14_vram_read[3] = {
> +    cg14_vram_readb,
> +    cg14_vram_readw,
> +    cg14_vram_readl,
> +};
> +
> +static CPUWriteMemoryFunc *cg14_vram_write[3] = {
> +    cg14_vram_writeb,
> +    cg14_vram_writew,
> +    cg14_vram_writel,
> +};
> +
> +
> +static void cg14_set_monitor_id(CG14State *s)
> +{
> +    uint8_t id;
> +
> +    /* pick something close, used as a default by Sun's OBP */
> +    if (s->width >= 1600) {
> +        id = CG14_MONID_1600x1280;
> +    } else if (s->width >= 1280) {
> +        id = CG14_MONID_1280x1024;
> +    } else if (s->width >= 1152) {
> +        id = CG14_MONID_1152x900;
> +    } else if (s->width >= 1024) {
> +        id = CG14_MONID_1024x768;
> +    } else {
> +        id = CG14_MONID_DEFAULT;
> +    }
> +
> +    /* monitor code in bits 1..3 */
> +    s->ctrl.msr = id << 1;
> +}
> +
> +static int cg14_init1(SysBusDevice *dev)
> +{
> +    CG14State *s = FROM_SYSBUS(CG14State, dev);
> +    ram_addr_t vram_offset;
> +    uint8_t *vram;
> +    int ctrl_memory, vram_memory;
> +
> +    vram_offset = qemu_ram_alloc(NULL, "cg14.vram", s->vram_size);
> +    vram = qemu_get_ram_ptr(vram_offset);
> +
> +    s->vram = vram;
> +    s->vram_amask = s->vram_size - 1;
> +
> +    ctrl_memory = cpu_register_io_memory(cg14_reg_read, cg14_reg_write, s);
> +    sysbus_init_mmio(dev, CG14_REG_SIZE, ctrl_memory);
> +
> +    /* TODO: register first vram mapping as ram with dirty tracking */
> +    vram_memory = cpu_register_io_memory(cg14_vram_read, cg14_vram_write, s);
> +    sysbus_init_mmio(dev, CG14_VMEM_SLOTSIZE, vram_memory);
> +
> +    s->ds = graphic_console_init(cg14_update_display,
> +                                 cg14_invalidate_display,
> +                                 cg14_screen_dump, NULL, s);
> +
> +    cg14_set_monitor_id(s);
> +
> +    qemu_console_resize(s->ds, s->width, s->height);
> +    return 0;
> +}
> +
> +/* save to file */
> +static void cg14_screen_dump(void *opaque, const char *filename)
> +{
> +    CG14State *s = opaque;
> +    FILE *f;
> +    int y, src_linesize, dst_linesize;
> +    void *buf;
> +    uint8_t *pix;
> +    draw_line_func * draw_line = NULL;
> +
> +    switch (s->ctrl.mcr & CG14_MCR_PIXMODE_MASK) {
> +    case CG14_MCR_PIXMODE_32:
> +        src_linesize = s->width * 4;
> +        // draw_line = cg14_draw_line32_bgr24;
> +        break;
> +    case CG14_MCR_PIXMODE_16:
> +        src_linesize = s->width * 2;
> +        // draw_line = cg14_draw_line16_bgr24;
> +        break;
> +    case CG14_MCR_PIXMODE_8:
> +        src_linesize = s->width;
> +        // draw_line = cg14_draw_line8_bgr24;
> +        break;
> +    default:
> +        /* blank */
> +        return;
> +    }
> +
> +    f = fopen(filename, "wb");
> +    if (!f) {
> +        return;
> +    }
> +    fprintf(f, "P6\n%d %d\n%d\n", s->width, s->height, 255);
> +
> +    dst_linesize = s->width * 3;
> +    buf = qemu_mallocz(dst_linesize);
> +    pix = s->vram;
> +
> +    for (y=0; y<s->height; y++) {
> +        if (draw_line)

Missing braces.

> +            draw_line(s, buf, pix);
> +        fwrite(buf, 1, dst_linesize, f);
> +        pix += src_linesize;
> +    }
> +
> +    qemu_free(buf);
> +    fclose(f);
> +}
> +
> +static void cg14_reset(DeviceState *d)
> +{
> +    CG14State *s = container_of(d, CG14State, busdev.qdev);
> +
> +    /* set to 8bpp so last prom output might be visible */
> +    s->ctrl.mcr = CG14_MCR_VIDENABLE | CG14_MCR_PIXMODE_8;
> +    s->dirty = 1;
> +}
> +
> +static SysBusDeviceInfo cg14_info = {
> +    .init = cg14_init1,
> +    .qdev.name = "cg14",
> +    .qdev.desc = "Sun CG14 Framebuffer",
> +    .qdev.size = sizeof(CG14State),
> +    .qdev.reset = cg14_reset,
> +    .qdev.props = (Property[]) {
> +        DEFINE_PROP_TADDR("vram_addr", CG14State, vram_addr, -1),
> +        DEFINE_PROP_HEX32("vram_size", CG14State, vram_size, 0x800000),
> +        DEFINE_PROP_UINT16("width",    CG14State, width,     0),
> +        DEFINE_PROP_UINT16("height",   CG14State, height,    0),
> +        DEFINE_PROP_END_OF_LIST(),
> +    }
> +};
> +
> +static void cg14_register_devices(void)
> +{
> +    sysbus_register_withprop(&cg14_info);
> +}
> +
> +device_init(cg14_register_devices)
> diff --git a/hw/sun4m.c b/hw/sun4m.c
> index e7a4cf6..0665e1f 100644
> --- a/hw/sun4m.c
> +++ b/hw/sun4m.c
> @@ -577,6 +577,23 @@ static void tcx_init(target_phys_addr_t addr, int vram_size, int width,
>     }
>  }
>
> +static void cg14_init(target_phys_addr_t ctrl_base,
> +                      target_phys_addr_t vram_base,
> +                      int width, int height)
> +{
> +    DeviceState *dev;
> +    SysBusDevice *s;
> +
> +    dev = qdev_create(NULL, "cg14");
> +    qdev_prop_set_taddr(dev, "vram_addr", vram_base);
> +    qdev_prop_set_uint16(dev, "width", width);
> +    qdev_prop_set_uint16(dev, "height", height);
> +    qdev_init_nofail(dev);
> +    s = sysbus_from_qdev(dev);
> +    sysbus_mmio_map(s, 0, ctrl_base);
> +    sysbus_mmio_map(s, 1, vram_base);
> +}
> +
>  /* NCR89C100/MACIO Internal ID register */
>  static const uint8_t idreg_data[] = { 0xfe, 0x81, 0x01, 0x03 };
>
> @@ -879,6 +896,11 @@ static void sun4m_hw_init(const struct sun4m_hwdef *hwdef, ram_addr_t RAM_size,
>         exit (1);
>     }
>     num_vsimms = 0;
> +    if (hwdef->vsimm[0].vram_base && (graphic_width > 1024 || !hwdef->tcx_base)) {
> +        cg14_init(hwdef->vsimm[0].reg_base, hwdef->vsimm[0].vram_base,
> +                  graphic_width, graphic_height);
> +        num_vsimms++;
> +    }
>     if (num_vsimms == 0) {
>         tcx_init(hwdef->tcx_base, 0x00100000, graphic_width, graphic_height,
>                  graphic_depth);
> --
> 1.6.2.2.1669.g7eaf8
>
>
>
>

Patch

diff --git a/Makefile.target b/Makefile.target
index 3ef4666..54a2ae4 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -255,6 +255,7 @@  else
 obj-sparc-y = sun4m.o lance.o tcx.o sun4m_iommu.o slavio_intctl.o
 obj-sparc-y += slavio_timer.o slavio_misc.o sparc32_dma.o
 obj-sparc-y += cs4231.o eccmemctl.o sbi.o sun4c_intctl.o
+obj-sparc-y += cg14.o
 endif
 
 obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o
diff --git a/hw/cg14.c b/hw/cg14.c
new file mode 100644
index 0000000..533dc04
--- /dev/null
+++ b/hw/cg14.c
@@ -0,0 +1,850 @@ 
+/*
+ * QEMU CG14 Frame buffer
+ *
+ * Copyright (c) 2010 Bob Breuer  <breuerr@mc.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "console.h"
+#include "sysbus.h"
+#include "qdev-addr.h"
+
+//#define DEBUG_CG14
+//#define DEBUG_DAC
+//#define DEBUG_CONFIG
+
+/*
+ * Sun CG14 framebuffer (without SX)
+ *   CG14 = vsimm framebuffer (video ram and dac)
+ *   SX = pixel processor (acceleration) built into chipset
+ *
+ * Documentation: not publicly documented by Sun
+ *   linux driver: drivers/video/cg14.c
+ *   NetBSD/OpenBSD: src/sys/arch/sparc/dev/cgfourteen*
+ *
+ * Takes up one memory slot:
+ *   A[28:26] = slot number (4 to 7)
+ *   regs: size   0x10000 @ 0x09c000000  (0x80000000 + slot * 64M)
+ *   vmem: size upto 16MB @ 0x0fc000000  (0xE0000000 + slot * 64M)
+ *
+ * SS-20 OBP only supports slots 7 (onboard output) and 4 (AVB output)
+ *
+ * memory map:
+ * reg+0x0000 = control registers
+ * reg+0x1000 = cursor registers
+ * reg+0x2000 = dac registers (ADV7152)
+ * reg+0x3000 = xlut
+ * reg+0x4000 = clut1
+ * reg+0x5000 = clut2
+ * reg+0x6000 = clut3 (if implemented)
+ * reg+0xf000 = autoinc
+ *
+ * mem+0x0000000 = XBGR (01234567)
+ * mem+0x1000000 = BGR  (.123.567)  writes to X are blocked, reads are ok
+ * mem+0x2000000 = X16  (0246)
+ * mem+0x2800000 = C16  (1357)
+ * mem+0x3000000 = X32  (04)
+ * mem+0x3400000 = B32  (15)
+ * mem+0x3800000 = G32  (26)
+ * mem+0x3c00000 = R32  (37)
+ */
+
+#define CG14_REG_SIZE         0x10000
+#define CG14_VMEM_SLOTSIZE    (64<<20)
+
+#define CG14_MONID_1024x768   0
+#define CG14_MONID_1600x1280  1
+#define CG14_MONID_1280x1024  2
+#define CG14_MONID_1152x900   7
+
+#define CG14_MONID_DEFAULT    CG14_MONID_1024x768
+
+
+#define CG14_MCR_INTENABLE     0x80
+#define CG14_MCR_VIDENABLE     0x40
+#define CG14_MCR_PIXMODE_MASK  0x30
+#define   CG14_MCR_PIXMODE_8     0x00
+#define   CG14_MCR_PIXMODE_16    0x20  /* 8+8 (X16,C16) */
+#define   CG14_MCR_PIXMODE_32    0x30  /* XBGR */
+
+
+#ifdef DEBUG_CG14
+#define DPRINTF(fmt, ...)                                       \
+    printf("CG14: " fmt , ## __VA_ARGS__)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+#ifdef DEBUG_DAC
+#define DPRINTF_DAC(fmt, ...)                                   \
+    printf("CG14 dac: " fmt , ## __VA_ARGS__)
+#else
+#define DPRINTF_DAC(fmt, ...)
+#endif
+
+#ifdef DEBUG_CONFIG
+#define DPRINTF_CONFIG(fmt, ...)                                \
+    printf("CG14: " fmt , ## __VA_ARGS__)
+#else
+#define DPRINTF_CONFIG(fmt, ...)
+#endif
+
+#define CG14_INFO(fmt, ...)                                     \
+    do { printf("CG14: " fmt , ## __VA_ARGS__); } while (0)
+#define CG14_ERROR(fmt, ...)                                    \
+    do { printf("CG14: " fmt , ## __VA_ARGS__); } while (0)
+
+
+struct dac_state {
+    uint8_t mode;
+    uint8_t address;
+    int rgb_seq;
+};
+
+typedef struct CG14State {
+    SysBusDevice busdev;
+    DisplayState *ds;
+
+    target_phys_addr_t vram_addr;
+    uint8_t *vram;
+    uint32_t vram_size;
+    uint32_t vram_amask;
+    uint16_t width, height;
+    int dirty, size_changed;
+    struct {
+        uint8_t mcr;
+        uint8_t ppr;
+        uint8_t msr;
+    } ctrl;
+    struct dac_state dac;
+    struct {
+        uint16_t hblank_start;
+        uint16_t hblank_clear;
+        uint16_t vblank_start;
+        uint16_t vblank_clear;
+    } timing;
+    uint8_t xlut[256];
+    uint32_t clut1[256];
+    uint32_t clut2[256];
+} CG14State;
+
+static void cg14_screen_dump(void *opaque, const char *filename);
+static void cg14_invalidate_display(void *opaque);
+
+static inline uint32_t bgr_to_rgb(uint32_t bgr)
+{
+    uint32_t rgb;
+
+    /* swap r & b */
+    rgb = (bgr & 0x00FF00)
+        | (bgr & 0x0000FF) << 16
+        | (bgr & 0xFF0000) >> 16;
+    return rgb;
+}
+
+typedef void draw_line_func(const CG14State *s, void *dst, const uint8_t *src);
+
+// TODO: draw_line templates
+static void cg14_draw_line32_rgb32(const CG14State *s, void *dst, const uint8_t *src)
+{
+    unsigned int i;
+    unsigned int x, r, g, b;
+    uint8_t xlut_val;
+    uint32_t dval;
+
+    for (i=0; i<s->width; i++) {
+        x = src[0];
+        b = src[1];
+        g = src[2];
+        r = src[3];
+        xlut_val = s->xlut[x];
+        src += 4;
+
+        if (xlut_val == 0x40) {
+            dval = bgr_to_rgb(s->clut1[x]);
+        } else {
+            /* xlut = 0x00 for true-color */
+            /* possible blending between 2 colors */
+            /* fallback to true-color just to display something if unimplemented */
+            dval = r << 16 | g << 8 | b;
+        }
+        /* dac lookup ? */
+
+        /* to surface format */
+        //dval = is_bgr ? (abgr & 0xFFFFFF) : bgr_to_rgb(abgr);
+        ((uint32_t*)dst)[i] = dval;
+    }
+}
+
+static void cg14_draw_line16_rgb32(const CG14State *s, void *dst, const uint8_t *src)
+{
+    unsigned int i;
+    unsigned int x, c;
+    uint8_t xlut_val;
+    uint32_t dval;
+
+    for (i=0; i<s->width; i++) {
+        x = src[0];
+        c = src[1];
+        xlut_val = s->xlut[x];
+        src += 2;
+
+        if (xlut_val == 0x40) {
+            dval = bgr_to_rgb(s->clut1[x]);
+        } else {
+            /* possible blending between 2 colors */
+            /* fallback to green/blue just to display something if unimplemented */
+            dval = x << 8 | c;
+        }
+        /* dac lookup ? */
+
+        ((uint32_t*)dst)[i] = dval;
+    }
+}
+
+static void cg14_draw_line8_rgb32(const CG14State *s, void *dst, const uint8_t *src)
+{
+    int i;
+    const uint32_t *clut;
+    uint32_t *dst32 = dst;
+
+    if (s->ctrl.ppr == 0x40) {
+        clut = s->clut1;
+        for (i=0; i<s->width; i++) {
+            dst32[i] = bgr_to_rgb(clut[src[i]]);
+        }
+    } else {
+        /* FIXME: other xlut modes, just use grayscale ramp for now */
+        for (i=0; i<s->width; i++) {
+            dst32[i] = 0x010101 * src[i];
+        }
+    }
+}
+
+/* TODO: use VGA_DIRTY_FLAG */
+static void cg14_update_display(void *opaque)
+{
+    CG14State *s = opaque;
+    int h, src_linesize;
+    uint8_t *pix;
+    uint8_t *data;
+    int new_width, new_height;
+    draw_line_func * draw_line;
+
+    if (s->size_changed) {
+        new_width = 4 * (s->timing.hblank_start - s->timing.hblank_clear);
+        new_height = s->timing.vblank_start - s->timing.vblank_clear;
+        s->size_changed = 0;
+        if ((new_width != s->width || new_height != s->height) && new_width > 0 && new_height > 0) {
+            s->width = new_width;
+            s->height = new_height;
+            CG14_INFO("new resolution = %d x %d\n", new_width, new_height);
+            qemu_console_resize(s->ds, s->width, s->height);
+            s->dirty = 1;
+        }
+    }
+
+    if (!s->dirty || !s->width || !s->height) {
+        return;
+    }
+
+    if (ds_get_bits_per_pixel(s->ds) != 32) {
+        CG14_ERROR("cg14_update: FIXME: bpp (%d) != 32, linesize %d\n",
+            ds_get_bits_per_pixel(s->ds), ds_get_linesize(s->ds));
+        return;
+    }
+    // if (is_surface_bgr(s->ds->surface))
+
+    draw_line = NULL;
+    src_linesize = s->width;
+    if (s->ctrl.mcr & CG14_MCR_VIDENABLE) {
+        switch (s->ctrl.mcr & CG14_MCR_PIXMODE_MASK) {
+        case CG14_MCR_PIXMODE_32:
+            src_linesize *= 4;
+            draw_line = cg14_draw_line32_rgb32;
+            break;
+        case CG14_MCR_PIXMODE_16:
+            src_linesize *= 2;
+            draw_line = cg14_draw_line16_rgb32;
+            break;
+        case CG14_MCR_PIXMODE_8:
+            draw_line = cg14_draw_line8_rgb32;
+            break;
+        }
+    }
+    if (!draw_line) {
+        /* blank */
+        memset(ds_get_data(s->ds), 0, ds_get_linesize(s->ds)*ds_get_height(s->ds));
+        s->dirty = 0;
+        return;
+    }
+
+    pix = s->vram;
+    data = ds_get_data(s->ds);
+
+    for (h=0; h<s->height; h++) {
+        draw_line(s, data, pix);
+        pix += src_linesize;
+        data += ds_get_linesize(s->ds);
+    }
+    dpy_update(s->ds, 0, 0, s->width, s->height);
+    s->dirty = 0;
+}
+
+static void cg14_invalidate_display(void *opaque)
+{
+    CG14State *s = opaque;
+
+    s->dirty = 1;
+}
+
+static uint32_t dac_read(struct dac_state *s, unsigned int reg)
+{
+    uint32_t val = 0;
+
+    switch (reg) {
+    case 0:
+        val = s->address;
+        break;
+    case 1: /* lookup table */
+        s->rgb_seq++;
+        break;
+    case 2: /* control registers */
+        break;
+    case 3:
+        val = s->mode;
+        break;
+    }
+    DPRINTF_DAC("read %02x from dac reg %d\n", val, reg);
+    return val;
+}
+
+static void dac_write(struct dac_state *s, unsigned int reg, unsigned int val)
+{
+    switch (reg) {
+    case 0: /* address register */
+        DPRINTF_DAC("write address %02x\n", val);
+        s->address = val;
+        s->rgb_seq = 0;
+        break;
+    case 1: /* lookup table */
+        DPRINTF_DAC("write %02x to lookup table\n", val);
+        s->rgb_seq++;
+        break;
+    case 2: /* control registers */
+        DPRINTF_DAC("write %02x to control reg %d\n", val, s->address);
+        switch (s->address) {
+        default:
+            break;
+        }
+        break;
+    case 3: /* mode register */
+        DPRINTF_DAC("write mode %02x (%d bit DAC, %d bit bus)\n",
+            val, (val & 2) ? 10 : 8, (val & 4) ? 10 : 8);
+        if (!val & 0x01) {
+            // reset the dac
+            s->rgb_seq = 0;
+        }
+        s->mode = val;
+        break;
+    }
+}
+
+static uint32_t cg14_reg_readb(void *opaque, target_phys_addr_t addr)
+{
+    CG14State *s = opaque;
+    uint32_t val;
+    uint32_t i;
+
+    if ((addr & 0xfc00) == 0x2000) {
+        i = (addr & 0x300) >> 8;
+        return dac_read(&s->dac, i);
+    }
+
+    switch (addr) {
+    case 0x0000:
+        val = s->ctrl.mcr;
+        break;
+    case 0x0001:
+        val = s->ctrl.ppr;
+        break;
+    case 0x0004: /* status ? */
+        val = s->ctrl.msr;
+        break;
+    case 0x0006: /* hw version */
+        //val = 0x00; /* old version */
+        val = 0x30;
+        break;
+    default:
+        val = 0;
+        CG14_INFO("readb from reg %x\n", (int)addr);
+        break;
+    }
+
+    return val;
+}
+
+static void cg14_reg_writeb(void *opaque, target_phys_addr_t addr, uint32_t val)
+{
+    CG14State *s = opaque;
+    uint32_t i;
+
+    if ((addr & 0xfc00) == 0x2000) {
+        i = (addr & 0x300) >> 8;
+        dac_write(&s->dac, i, val);
+        return;
+    }
+    if ((addr & 0xff00) == 0x3000) {
+        /* xlut */
+        i = addr & 0xff;
+        if (s->xlut[i] != val) {
+            s->dirty = 1;
+            s->xlut[i] = val;
+            if (val && val != 0x40)
+                CG14_ERROR("writeb xlut[%d] = %02x\n", i, val);
+        }
+        return;
+    }
+
+    s->dirty = 1;
+
+    switch (addr) {
+    case 0x0000:
+        s->ctrl.mcr = val;
+        DPRINTF_CONFIG("write %02x to MCR\n", val);
+        break;
+    case 0x0001:
+        s->ctrl.ppr = val & 0xF0;
+        break;
+    case 0x0007:
+        /* clock control (ICS1562AM-001) */
+        DPRINTF("write %02x to clock control\n", val);
+        break;
+    default:
+        CG14_ERROR("writeb %02x to reg %x\n", val, (int)addr);
+        break;
+    }
+}
+
+static uint32_t cg14_reg_readw(void *opaque, target_phys_addr_t addr)
+{
+    CG14State *s = opaque;
+    uint32_t val;
+
+    switch (addr) {
+    case 0x0018:
+        val = s->timing.hblank_start;
+        break;
+    case 0x001a:
+        val = s->timing.hblank_clear;
+        break;
+    case 0x0022:
+        val = s->timing.vblank_start;
+        break;
+    case 0x0024:
+        val = s->timing.vblank_clear;
+        break;
+    default:
+        val = 0;
+        CG14_INFO("readw from reg %x\n", (int)addr);
+        break;
+    }
+
+    return val;
+}
+
+static void cg14_reg_writew(void *opaque, target_phys_addr_t addr, uint32_t val)
+{
+    CG14State *s = opaque;
+
+    DPRINTF_CONFIG("writew %04x to reg %x\n", val, (int)addr);
+
+    /* timing registers are 16bit */
+
+    switch (addr) {
+    case 0x0018:
+        s->timing.hblank_start = val;
+        break;
+    case 0x001a:
+        s->timing.hblank_clear = val;
+	s->size_changed = 1;
+        break;
+    case 0x0022:
+        s->timing.vblank_start = val;
+        break;
+    case 0x0024:
+        s->timing.vblank_clear = val;
+	s->size_changed = 1;
+        break;
+    case 0x001c: /* hsync_start */
+    case 0x001e: /* hsync_clear */
+    case 0x0020: /* csync_clear */
+    case 0x0026: /* vsync_start */
+    case 0x0028: /* vsync_clear */
+    default:
+        break;
+    }
+}
+
+static uint32_t cg14_reg_readl(void *opaque, target_phys_addr_t addr)
+{
+    CG14State *s = opaque;
+    uint32_t val;
+    uint32_t i;
+
+    i = (addr & 0x3ff) >> 2;
+    switch (addr & 0xfc00) {
+    case 0x4000:
+        val = s->clut1[i];
+        break;
+    case 0x5000:
+        val = s->clut2[i];
+        break;
+    default:
+        val = 0;
+        CG14_ERROR("readl %08x from reg %x\n", val, (int)addr);
+        break;
+    }
+
+    return val;
+}
+
+static void cg14_reg_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
+{
+    CG14State *s = opaque;
+    uint32_t i;
+
+    s->dirty = 1;
+
+    i = addr & 0x3ff;
+    switch (addr & 0xfc00) {
+    case 0x3000:
+        if (i < 256) {
+            s->xlut[i+0] = (uint8_t)(val >> 24);
+            s->xlut[i+1] = (uint8_t)(val >> 16);
+            s->xlut[i+2] = (uint8_t)(val >> 8);
+            s->xlut[i+3] = (uint8_t)val;
+        }
+        break;
+    case 0x4000:
+        s->clut1[i >> 2] = val;
+        break;
+    case 0x5000:
+        s->clut2[i >> 2] = val;
+        break;
+    default:
+        CG14_ERROR("writel %08x to reg %x\n", val, (int)addr);
+        break;
+    }
+}
+
+static CPUReadMemoryFunc *cg14_reg_read[3] = {
+    cg14_reg_readb,
+    cg14_reg_readw,
+    cg14_reg_readl,
+};
+
+static CPUWriteMemoryFunc *cg14_reg_write[3] = {
+    cg14_reg_writeb,
+    cg14_reg_writew,
+    cg14_reg_writel,
+};
+
+static uint32_t cg14_vram_readb(void *opaque, target_phys_addr_t addr)
+{
+    CG14State *s = opaque;
+    uint32_t offset;
+    uint32_t val = 0;
+
+    switch (addr & 0x3000000) {
+    case 0x0000000:
+    case 0x1000000:
+        offset = addr & s->vram_amask;
+        val = ldub_p(s->vram+offset);
+        break;
+    case 0x2000000:
+        offset = ((addr << 1) & s->vram_amask) + ((addr >> 23) & 1);
+        val = ldub_p(s->vram+offset);
+        break;
+    case 0x3000000:
+        offset = ((addr << 2) & s->vram_amask) + ((addr >> 22) & 3);
+        val = ldub_p(s->vram+offset);
+        break;
+    }
+
+    return val;
+}
+
+static void cg14_vram_writeb(void *opaque, target_phys_addr_t addr, uint32_t val)
+{
+    CG14State *s = opaque;
+    uint32_t offset;
+
+    s->dirty = 1;
+
+    switch (addr & 0x3000000) {
+    case 0x0000000:
+        offset = addr & s->vram_amask;
+        stb_p(s->vram+offset, val);
+        break;
+    case 0x1000000:
+        offset = addr & s->vram_amask;
+        /* block writes to X */
+        if (offset & 3) {
+            stb_p(s->vram+offset, val);
+        }
+        break;
+    case 0x2000000:
+        offset = ((addr << 1) & s->vram_amask) + ((addr >> 23) & 1);
+        stb_p(s->vram+offset, val);
+        break;
+    case 0x3000000:
+        offset = ((addr << 2) & s->vram_amask) + ((addr >> 22) & 3);
+        stb_p(s->vram+offset, val);
+        break;
+    }
+}
+
+static uint32_t cg14_vram_readw(void *opaque, target_phys_addr_t addr)
+{
+    uint32_t val;
+
+    val = cg14_vram_readb(opaque, addr) << 8;
+    val |= cg14_vram_readb(opaque, addr+1);
+
+    return val;
+}
+
+static void cg14_vram_writew(void *opaque, target_phys_addr_t addr, uint32_t val)
+{
+    cg14_vram_writeb(opaque, addr, val >> 8);
+    cg14_vram_writeb(opaque, addr+1, val & 0xff);
+}
+
+static uint32_t cg14_vram_readl(void *opaque, target_phys_addr_t addr)
+{
+    CG14State *s = opaque;
+    uint32_t offset;
+    uint32_t val = 0;
+
+    switch (addr & 0x3000000) {
+    case 0x0000000:
+    case 0x1000000:
+        offset = addr & s->vram_amask;
+        val = ldl_be_p(s->vram+offset);
+        break;
+    case 0x2000000:
+        offset = ((addr << 1) & s->vram_amask) + ((addr >> 23) & 1);
+        val =  ldub_p(s->vram+offset+0) << 24;
+        val |= ldub_p(s->vram+offset+2) << 16;
+        val |= ldub_p(s->vram+offset+4) << 8;
+        val |= ldub_p(s->vram+offset+6);
+        break;
+    case 0x3000000:
+        offset = ((addr << 2) & s->vram_amask) + ((addr >> 22) & 3);
+        val =  ldub_p(s->vram+offset+0) << 24;
+        val |= ldub_p(s->vram+offset+4) << 16;
+        val |= ldub_p(s->vram+offset+8) << 8;
+        val |= ldub_p(s->vram+offset+12);
+        break;
+    }
+
+    return val;
+}
+
+static void cg14_vram_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
+{
+    CG14State *s = opaque;
+    uint32_t offset;
+
+    s->dirty = 1;
+
+    switch (addr & 0x3000000) {
+    case 0x0000000:
+        offset = addr & s->vram_amask;
+        stl_be_p(s->vram+offset, val);
+        break;
+    case 0x1000000:
+        offset = addr & s->vram_amask;
+        /* block writes to X */
+        stb_p(s->vram+offset+1, val >> 16);
+        stb_p(s->vram+offset+2, val >> 8);
+        stb_p(s->vram+offset+3, val);
+        break;
+    case 0x2000000:
+        offset = ((addr << 1) & s->vram_amask) + ((addr >> 23) & 1);
+        stb_p(s->vram+offset+0, val >> 24);
+        stb_p(s->vram+offset+2, val >> 16);
+        stb_p(s->vram+offset+4, val >> 8);
+        stb_p(s->vram+offset+6, val);
+        break;
+    case 0x3000000:
+        offset = ((addr << 2) & s->vram_amask) + ((addr >> 22) & 3);
+        stb_p(s->vram+offset+0,  val >> 24);
+        stb_p(s->vram+offset+4,  val >> 16);
+        stb_p(s->vram+offset+8,  val >> 8);
+        stb_p(s->vram+offset+12, val);
+        break;
+    }
+}
+
+static CPUReadMemoryFunc *cg14_vram_read[3] = {
+    cg14_vram_readb,
+    cg14_vram_readw,
+    cg14_vram_readl,
+};
+
+static CPUWriteMemoryFunc *cg14_vram_write[3] = {
+    cg14_vram_writeb,
+    cg14_vram_writew,
+    cg14_vram_writel,
+};
+
+
+static void cg14_set_monitor_id(CG14State *s)
+{
+    uint8_t id;
+
+    /* pick something close, used as a default by Sun's OBP */
+    if (s->width >= 1600) {
+        id = CG14_MONID_1600x1280;
+    } else if (s->width >= 1280) {
+        id = CG14_MONID_1280x1024;
+    } else if (s->width >= 1152) {
+        id = CG14_MONID_1152x900;
+    } else if (s->width >= 1024) {
+        id = CG14_MONID_1024x768;
+    } else {
+        id = CG14_MONID_DEFAULT;
+    }
+
+    /* monitor code in bits 1..3 */
+    s->ctrl.msr = id << 1;
+}
+
+static int cg14_init1(SysBusDevice *dev)
+{
+    CG14State *s = FROM_SYSBUS(CG14State, dev);
+    ram_addr_t vram_offset;
+    uint8_t *vram;
+    int ctrl_memory, vram_memory;
+
+    vram_offset = qemu_ram_alloc(NULL, "cg14.vram", s->vram_size);
+    vram = qemu_get_ram_ptr(vram_offset);
+
+    s->vram = vram;
+    s->vram_amask = s->vram_size - 1;
+
+    ctrl_memory = cpu_register_io_memory(cg14_reg_read, cg14_reg_write, s);
+    sysbus_init_mmio(dev, CG14_REG_SIZE, ctrl_memory);
+
+    /* TODO: register first vram mapping as ram with dirty tracking */
+    vram_memory = cpu_register_io_memory(cg14_vram_read, cg14_vram_write, s);
+    sysbus_init_mmio(dev, CG14_VMEM_SLOTSIZE, vram_memory);
+
+    s->ds = graphic_console_init(cg14_update_display,
+                                 cg14_invalidate_display,
+                                 cg14_screen_dump, NULL, s);
+
+    cg14_set_monitor_id(s);
+
+    qemu_console_resize(s->ds, s->width, s->height);
+    return 0;
+}
+
+/* save to file */
+static void cg14_screen_dump(void *opaque, const char *filename)
+{
+    CG14State *s = opaque;
+    FILE *f;
+    int y, src_linesize, dst_linesize;
+    void *buf;
+    uint8_t *pix;
+    draw_line_func * draw_line = NULL;
+
+    switch (s->ctrl.mcr & CG14_MCR_PIXMODE_MASK) {
+    case CG14_MCR_PIXMODE_32:
+        src_linesize = s->width * 4;
+        // draw_line = cg14_draw_line32_bgr24;
+        break;
+    case CG14_MCR_PIXMODE_16:
+        src_linesize = s->width * 2;
+        // draw_line = cg14_draw_line16_bgr24;
+        break;
+    case CG14_MCR_PIXMODE_8:
+        src_linesize = s->width;
+        // draw_line = cg14_draw_line8_bgr24;
+        break;
+    default:
+        /* blank */
+        return;
+    }
+
+    f = fopen(filename, "wb");
+    if (!f) {
+        return;
+    }
+    fprintf(f, "P6\n%d %d\n%d\n", s->width, s->height, 255);
+
+    dst_linesize = s->width * 3;
+    buf = qemu_mallocz(dst_linesize);
+    pix = s->vram;
+
+    for (y=0; y<s->height; y++) {
+        if (draw_line)
+            draw_line(s, buf, pix);
+        fwrite(buf, 1, dst_linesize, f);
+        pix += src_linesize;
+    }
+
+    qemu_free(buf);
+    fclose(f);
+}
+
+static void cg14_reset(DeviceState *d)
+{
+    CG14State *s = container_of(d, CG14State, busdev.qdev);
+
+    /* set to 8bpp so last prom output might be visible */
+    s->ctrl.mcr = CG14_MCR_VIDENABLE | CG14_MCR_PIXMODE_8;
+    s->dirty = 1;
+}
+
+static SysBusDeviceInfo cg14_info = {
+    .init = cg14_init1,
+    .qdev.name = "cg14",
+    .qdev.desc = "Sun CG14 Framebuffer",
+    .qdev.size = sizeof(CG14State),
+    .qdev.reset = cg14_reset,
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_TADDR("vram_addr", CG14State, vram_addr, -1),
+        DEFINE_PROP_HEX32("vram_size", CG14State, vram_size, 0x800000),
+        DEFINE_PROP_UINT16("width",    CG14State, width,     0),
+        DEFINE_PROP_UINT16("height",   CG14State, height,    0),
+        DEFINE_PROP_END_OF_LIST(),
+    }
+};
+
+static void cg14_register_devices(void)
+{
+    sysbus_register_withprop(&cg14_info);
+}
+
+device_init(cg14_register_devices)
diff --git a/hw/sun4m.c b/hw/sun4m.c
index e7a4cf6..0665e1f 100644
--- a/hw/sun4m.c
+++ b/hw/sun4m.c
@@ -577,6 +577,23 @@  static void tcx_init(target_phys_addr_t addr, int vram_size, int width,
     }
 }
 
+static void cg14_init(target_phys_addr_t ctrl_base,
+                      target_phys_addr_t vram_base,
+                      int width, int height)
+{
+    DeviceState *dev;
+    SysBusDevice *s;
+
+    dev = qdev_create(NULL, "cg14");
+    qdev_prop_set_taddr(dev, "vram_addr", vram_base);
+    qdev_prop_set_uint16(dev, "width", width);
+    qdev_prop_set_uint16(dev, "height", height);
+    qdev_init_nofail(dev);
+    s = sysbus_from_qdev(dev);
+    sysbus_mmio_map(s, 0, ctrl_base);
+    sysbus_mmio_map(s, 1, vram_base);
+}
+
 /* NCR89C100/MACIO Internal ID register */
 static const uint8_t idreg_data[] = { 0xfe, 0x81, 0x01, 0x03 };
 
@@ -879,6 +896,11 @@  static void sun4m_hw_init(const struct sun4m_hwdef *hwdef, ram_addr_t RAM_size,
         exit (1);
     }
     num_vsimms = 0;
+    if (hwdef->vsimm[0].vram_base && (graphic_width > 1024 || !hwdef->tcx_base)) {
+        cg14_init(hwdef->vsimm[0].reg_base, hwdef->vsimm[0].vram_base,
+                  graphic_width, graphic_height);
+        num_vsimms++;
+    }
     if (num_vsimms == 0) {
         tcx_init(hwdef->tcx_base, 0x00100000, graphic_width, graphic_height,
                  graphic_depth);