diff mbox

[RFC,v3,23/23] 40p: Add an IBM 8514/A graphics card

Message ID 1308519800-13610-1-git-send-email-andreas.faerber@web.de
State New
Headers show

Commit Message

Andreas Färber June 19, 2011, 9:43 p.m. UTC
The IBM E15 is equivalent to an S3 Vision864.

Lacking S3 SDAC (86C716) support, the DAC indizes are translated
to greyscale colors. This works sufficiently to observe firmware
boot progress.

Cc: Hervé Poussineau <hpoussin@reactos.org>

Fixed off-by-one drawing issue.
Replaced hardcoded color for RECT.
Separate I/O debug output for readability.
Start cleaning up the naming s3 vs. ibm8514.
Add support for DAC_MASK, DAC_R_INDEX, DAC_W_INDEX, DAC_DATA regs.
Decouple from VGA. Implement dirty bit handling inspired by TCX.

Cc: Roy Tam <roytam@gmail.com>
Cc: Blue Swirl <blauwirbel@gmail.com>
Signed-off-by: Andreas Färber <andreas.faerber@web.de>
---
 Makefile.target |    1 +
 hw/ibm8514.c    |  961 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/pci_ids.h    |    3 +
 hw/ppc_prep.c   |    2 +
 4 files changed, 967 insertions(+), 0 deletions(-)
 create mode 100644 hw/ibm8514.c
diff mbox

Patch

diff --git a/Makefile.target b/Makefile.target
index b67b1f7..629ca35 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -244,6 +244,7 @@  obj-ppc-y += vga.o
 obj-ppc-y += i8259.o mc146818rtc.o
 obj-ppc-y += prep_systemio.o
 obj-ppc-y += ppc_prep.o
+obj-ppc-y += ibm8514.o
 # OldWorld PowerMac
 obj-ppc-y += ppc_oldworld.o
 # NewWorld PowerMac
diff --git a/hw/ibm8514.c b/hw/ibm8514.c
new file mode 100644
index 0000000..05bd07e
--- /dev/null
+++ b/hw/ibm8514.c
@@ -0,0 +1,961 @@ 
+/*
+ * QEMU PCI IBM 8514/A Emulator.
+ *
+ * Copyright (c) 2010 Hervé Poussineau
+ * Copyright (c) 2010-2011 Andreas Färber
+ *
+ * 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.
+ */
+
+/* Documentation available at
+ * http://www.datasheetarchive.com/Indexer/Datasheet-06/DSA0091551.html
+ */
+
+#include "console.h"
+#include "pci.h"
+#include "pixel_ops.h"
+
+//#define DEBUG_8514
+//#define DEBUG_8514_IO
+
+#ifdef DEBUG_8514
+#define DPRINTF(fmt, ...) \
+do { printf("8514: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+#ifdef DEBUG_8514_IO
+#define DPRINTF_IO(fmt, ...) \
+do { printf("8514: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF_IO(fmt, ...) do {} while (0)
+#endif
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "8514 ERROR: " fmt , ## __VA_ARGS__);} while (0)
+
+enum {
+    REG_CMD       = 0x9AE8,
+    REG_PIX_TRANS = 0xE2E8,
+};
+
+enum {
+    GP_STAT_BUSY = 0x0200,
+};
+
+enum {
+    CMD_WRTDATA  = 0x0001,
+    CMD_PLANAR   = 0x0002,
+    CMD_LASTPIX  = 0x0004,
+    CMD_LINETYPE = 0x0008,
+    CMD_DRAW     = 0x0010,
+    CMD_INC_X    = 0x0020,
+    CMD_YMAJAXIS = 0x0040,
+    CMD_INC_Y    = 0x0080,
+    CMD_PCDATA   = 0x0100,
+    CMD_16BIT    = 0x0200,
+    CMD_BYTSEQ   = 0x1000,
+};
+
+#define CMD_CMD_MASK 0xE000
+enum {
+    CMD_CMD_NOP    = 0x0000,
+    CMD_CMD_LINE   = 0x2000,
+    CMD_CMD_RECT   = 0x4000,
+    CMD_CMD_RECTV1 = 0x6000,
+    CMD_CMD_RECTV2 = 0x8000,
+    CMD_CMD_LINEAF = 0xA000,
+    CMD_CMD_BITBLT = 0xC000,
+};
+
+#define BKGD_MIX_BSS_MASK 0x0060
+enum {
+    BKGD_MIX_BSS_BKGD = 0x0000,
+    BKGD_MIX_BSS_FRGD = 0x0020,
+    BKGD_MIX_BSS_PIX  = 0x0040,
+    BKGD_MIX_BSS_BMP  = 0x0060,
+};
+
+#define FRGD_MIX_FSS_MASK 0x0060
+enum {
+    FRGD_MIX_FSS_BKGD = 0x0000,
+    FRGD_MIX_FSS_FRGD = 0x0020,
+    FRGD_MIX_FSS_PIX  = 0x0040,
+    FRGD_MIX_FSS_BMP  = 0x0060,
+};
+
+#define PIX_CNTL_MIXSEL_MASK 0x00C0
+enum {
+    PIX_CNTL_MIXSEL_FOREMIX = 0x0000,
+    PIX_CNTL_MIXSEL_PATTERN = 0x0040,
+    PIX_CNTL_MIXSEL_VAR     = 0x0080,
+    PIX_CNTL_MIXSEL_TRANS   = 0x00C0,
+};
+
+/*
+ * CMD values used by IBM firmware:
+ * 40f3 = CMD_CMD_RECT | CMD_INC_Y | CMD_YMAJAXIS | CMD_INC_X | CMD_DRAW | CMD_PLANAR | CMD_WRTDATA
+ * 4331 = CMD_CMD_RECT | CMD_16BIT | CMD_PCDATA | CMD_INC_X | CMD_DRAW | CMD_WRTDATA
+ * c0b3 = CMD_CMD_BITBLT | CMD_INC_Y | CMD_INC_X | CMD_DRAW | CMD_PLANAR | CMD_WRTDATA
+ */
+
+typedef struct IBM8514State {
+    DisplayState *ds;
+    uint8_t *vram_ptr;
+    ram_addr_t vram_offset;
+    uint32_t vram_size;
+    uint8_t r[256], g[256], b[256];
+    uint32_t palette[256];
+    uint16_t maj_axis, min_axis;
+
+    uint8_t dac_mask; /* 02ea */
+    uint8_t dac_r_index; /* 02eb */
+    uint8_t dac_w_index; /* 02ec */
+    uint8_t dac_r_temp[3];
+    uint8_t dac_r_state;
+    uint8_t dac_w_temp[3];
+    uint8_t dac_w_state;
+
+    uint16_t disp_stat; /* 02e8 */
+    uint16_t h_disp; /* 06e8 */
+    uint16_t h_sync_strt; /* 0ae8 */
+    uint16_t h_sync_wid; /* 0ee8 */
+    uint16_t v_total; /* 12e8 */
+    uint16_t v_disp; /* 16e8 */
+    uint16_t v_sync_strt; /* 1ae8 */
+    uint16_t v_sync_wid; /* 1ee8 */
+    uint16_t disp_cntl; /* 22e8 */
+    uint16_t h_total; /* 26e8 */
+    uint16_t subsys_cntl; /* 42e8 (W) */
+    uint16_t subsys_stat; /* 42e8 (R) */
+    uint16_t rom_page_sel; /* 46e8 */
+    uint16_t advfunc_cntl; /* 4ae8 */
+    uint16_t cur_y; /* 82e8 */
+    uint16_t cur_x; /* 86e8 */
+    uint16_t desty_axstep; /* 8ae8 */
+    uint16_t destx_diastp; /* 8ee8 */
+    uint16_t err_term; /* 92e8 */
+    uint16_t maj_axis_pcnt; /* 96e8 */
+    uint16_t gp_stat; /* 9ae8 (R) */
+    uint16_t cmd; /* 9ae8 (W) */
+    uint16_t short_stroke; /* 9ee8 */
+    uint16_t bkgd_color; /* a2e8 */
+    uint16_t frgd_color; /* a6e8 */
+    uint16_t wrt_mask; /* aae8 */
+    uint16_t rd_mask; /* aee8 */
+    uint16_t color_cmp; /* b2e8 */
+    uint16_t bkgd_mix; /* b6e8 */
+    uint16_t frgd_mix; /* bae8 */
+    uint16_t mfc[16]; /* bee8 */
+    uint16_t pix_trans; /* e2e8 */
+} IBM8514State;
+
+#define min_axis_pcnt mfc[0]
+#define scissors_t    mfc[1]
+#define scissors_l    mfc[2]
+#define scissors_b    mfc[3]
+#define scissors_r    mfc[4]
+#define mem_cntl      mfc[5]
+#define pattern_l     mfc[8]
+#define pattern_h     mfc[9]
+#define pix_cntl      mfc[10]
+
+static inline void do_cmd_done(IBM8514State *s)
+{
+    s->gp_stat &= ~GP_STAT_BUSY;
+}
+
+static void do_cmd_write_pixel(IBM8514State *s, uint16_t value)
+{
+    uint16_t maj_axis_pcnt;
+    uint32_t offset;
+    uint8_t* p8;
+    int dx;
+    int dy;
+
+    if (!(s->gp_stat & GP_STAT_BUSY)) {
+        return;
+    }
+
+    maj_axis_pcnt = s->maj_axis_pcnt + 1;
+    offset = s->cur_y * 640 + s->cur_x;
+    p8 = s->vram_ptr + offset;
+    dx = s->cmd & CMD_INC_X ? 1 : -1;
+    dy = s->cmd & CMD_INC_Y ? 1 : -1;
+    ++s->maj_axis;
+    if ((s->maj_axis < maj_axis_pcnt) ||
+        (s->maj_axis == maj_axis_pcnt && !(s->cmd & CMD_LASTPIX))) {
+        p8[0] = value;
+        cpu_physical_memory_set_dirty_flags((s->vram_offset + offset) &
+            TARGET_PAGE_MASK, VGA_DIRTY_FLAG);
+    }
+    if (s->maj_axis < maj_axis_pcnt) {
+        s->cur_x += dx;
+    } else if (s->maj_axis == maj_axis_pcnt) {
+        if ((maj_axis_pcnt % 2 == 0) || !(s->cmd & CMD_16BIT)) {
+            s->maj_axis = 0;
+        }
+        s->cur_x -= (s->maj_axis_pcnt) * dx;
+        s->cur_y += dy;
+        s->min_axis++;
+        if (s->min_axis == s->min_axis_pcnt + 1) {
+            do_cmd_done(s);
+        }
+    } else {
+        s->maj_axis = 0;
+    }
+}
+
+static uint16_t get_source_operand(IBM8514State *s)
+{
+    switch (s->pix_cntl & PIX_CNTL_MIXSEL_MASK) {
+        case PIX_CNTL_MIXSEL_FOREMIX:
+            switch (s->frgd_mix & FRGD_MIX_FSS_MASK) {
+                case FRGD_MIX_FSS_BKGD:
+                    return s->bkgd_color & 0xff;
+                case FRGD_MIX_FSS_FRGD:
+                    return s->frgd_color & 0xff;
+                default:
+                    BADF("%s: Unimplemented FSS %x\n",
+                         __func__, (s->frgd_mix & FRGD_MIX_FSS_MASK) >> 5);
+                    return 0;
+            }
+        default:
+            BADF("%s: Unimplemented MIXSEL %x\n",
+                 __func__, (s->pix_cntl & PIX_CNTL_MIXSEL_MASK) >> 6);
+            return 0;
+    }
+}
+
+static inline void do_cmd_init(IBM8514State *s)
+{
+    s->gp_stat |= GP_STAT_BUSY;
+    s->maj_axis = 0;
+    s->min_axis = 0;
+}
+
+static void do_cmd(IBM8514State *s)
+{
+    DPRINTF("%s: execute cmd %04x\n", __func__, s->cmd);
+
+    do_cmd_init(s);
+
+    if ((s->cmd & CMD_CMD_MASK) == CMD_CMD_RECT) {
+        DPRINTF("cmd RECT: cur_x=%d cur_y=%d inc_x=%d inc_y=%d width=%d height=%d\n",
+            s->cur_x, s->cur_y,
+            s->cmd & CMD_INC_X ? 1 : -1, s->cmd & CMD_INC_Y ? 1 : -1,
+            s->maj_axis_pcnt, s->min_axis_pcnt);
+
+        if (!(s->cmd & CMD_PCDATA)) {
+            while (s->gp_stat & GP_STAT_BUSY) {
+                do_cmd_write_pixel(s, get_source_operand(s));
+            }
+        }
+    }
+}
+
+static void ibm8514_set_dirty(IBM8514State *s)
+{
+    unsigned int i;
+
+    for (i = 0; i < 640 * 480; i += TARGET_PAGE_SIZE) {
+        cpu_physical_memory_set_dirty(s->vram_offset + i);
+    }
+}
+
+static void update_palette_entries(IBM8514State *s, int start, int end)
+{
+    int i;
+
+    for (i = start; i < end; i++) {
+        switch (ds_get_bits_per_pixel(s->ds)) {
+            default:
+            case 8:
+                s->palette[i] = rgb_to_pixel8(s->r[i], s->g[i], s->b[i]);
+                break;
+            case 15:
+                s->palette[i] = rgb_to_pixel15(s->r[i], s->g[i], s->b[i]);
+                break;
+            case 16:
+                s->palette[i] = rgb_to_pixel16(s->r[i], s->g[i], s->b[i]);
+                break;
+            case 32:
+                if (is_surface_bgr(s->ds->surface)) {
+                    s->palette[i] = rgb_to_pixel32bgr(s->r[i], s->g[i], s->b[i]);
+                } else {
+                    s->palette[i] = rgb_to_pixel32(s->r[i], s->g[i], s->b[i]);
+                }
+                break;
+        }
+    }
+    ibm8514_set_dirty(s);
+}
+
+static uint32_t ibm8514_dac_ioport_readb(void *opaque, uint32_t addr)
+{
+    IBM8514State *s = opaque;
+    uint32_t val;
+
+    switch (addr) {
+        case 0x02ea:
+            val = s->dac_mask;
+            break;
+        case 0x02eb:
+            val = s->dac_r_index;
+            break;
+        case 0x02ec:
+            val = s->dac_w_index;
+            break;
+        case 0x02ed:
+            if (s->dac_r_state == 0) {
+                s->dac_r_temp[0] = s->r[s->dac_r_index];
+                s->dac_r_temp[1] = s->g[s->dac_r_index];
+                s->dac_r_temp[2] = s->b[s->dac_r_index];
+            }
+            val = s->dac_r_temp[s->dac_r_state] & s->dac_mask;
+            s->dac_r_state = (s->dac_r_state + 1) % 3;
+            if (s->dac_r_state == 0) {
+                s->dac_r_index++;
+            }
+            break;
+        default:
+            BADF("%s: invalid register at 0x%04x\n", __func__, addr);
+            val = 0;
+            break;
+    }
+    DPRINTF_IO("%s: read %02x at %04x\n", __func__, val, addr);
+    return val;
+}
+
+static void ibm8514_dac_ioport_writeb(void *opaque, uint32_t addr, uint32_t val)
+{
+    IBM8514State *s = opaque;
+
+    DPRINTF_IO("%s: write %02x at %04x\n", __func__, val, addr);
+    switch (addr) {
+        case 0x02ea:
+            s->dac_mask = val & 0xff;
+            break;
+        case 0x02eb:
+            s->dac_r_index = val & 0xff;
+            s->dac_r_state = 0;
+            break;
+        case 0x02ec:
+            s->dac_w_index = val & 0xff;
+            s->dac_w_state = 0;
+            break;
+        case 0x02ed:
+            s->dac_w_temp[s->dac_w_state] = (val & 0xff) & s->dac_mask;
+            s->dac_w_state = (s->dac_w_state + 1) % 3;
+            if (s->dac_w_state == 0) {
+                s->r[s->dac_w_index] = s->dac_w_temp[0];
+                s->g[s->dac_w_index] = s->dac_w_temp[1];
+                s->b[s->dac_w_index] = s->dac_w_temp[2];
+                update_palette_entries(s, s->dac_w_index, s->dac_w_index + 1);
+                s->dac_w_index++;
+            }
+            break;
+        default:
+            BADF("%s: invalid register at 0x%04x\n", __func__, addr);
+            break;
+    }
+}
+
+static uint16_t* ibm8514_get_register(IBM8514State *s, uint32_t addr, int is_write, uint32_t* val_if_write)
+{
+    uint16_t *p;
+
+    switch (addr) {
+        case 0x02e8:
+            p = is_write ? &s->h_total : &s->disp_stat;
+            break;
+        case 0x06e8:
+            p = is_write ? &s->h_disp : NULL;
+            break;
+        case 0x0ae8:
+            p = is_write ? &s->h_sync_strt : NULL;
+            break;
+        case 0x0ee8:
+            p = is_write ? &s->h_sync_wid : NULL;
+            break;
+        case 0x12e8:
+            p = is_write ? &s->v_total : NULL;
+            break;
+        case 0x16e8:
+            p = is_write ? &s->v_disp : NULL;
+            break;
+        case 0x1ae8:
+            p = is_write ? &s->v_sync_strt : NULL;
+            break;
+        case 0x1ee8:
+            p = is_write ? &s->v_sync_wid : NULL;
+            break;
+        case 0x22e8:
+            p = is_write ? &s->disp_cntl : NULL;
+            break;
+        case 0x26e8:
+            p = is_write ? NULL: &s->h_total;
+            break;
+        case 0x42e8:
+            p = is_write ? &s->subsys_cntl : &s->subsys_stat;
+            break;
+        case 0x46e8:
+            p = is_write ? &s->rom_page_sel : NULL;
+            break;
+        case 0x4ae8:
+            p = is_write ? &s->advfunc_cntl : NULL;
+            break;
+        case 0x82e8:
+            p = &s->cur_y;
+            break;
+        case 0x86e8:
+            p = &s->cur_x;
+            break;
+        case 0x8ae8:
+            p = is_write ? &s->desty_axstep : NULL;
+            break;
+        case 0x8ee8:
+            p = is_write ? &s->destx_diastp : NULL;
+            break;
+        case 0x92e8:
+            p = &s->err_term;
+            break;
+        case 0x96e8:
+            p = is_write ? &s->maj_axis_pcnt : NULL;
+            break;
+        case 0x9ae8:
+            p = is_write ? &s->cmd : &s->gp_stat;
+            break;
+        case 0x9ee8:
+            p = is_write ? &s->short_stroke : NULL;
+            break;
+        case 0xa2e8:
+            p = is_write ? &s->bkgd_color : NULL;
+            break;
+        case 0xa6e8:
+            p = is_write ? &s->frgd_color : NULL;
+            break;
+        case 0xaae8:
+            p = is_write ? &s->wrt_mask : NULL;
+            break;
+        case 0xaee8:
+            p = is_write ? &s->rd_mask : NULL;
+            break;
+        case 0xb2e8:
+            p = is_write ? &s->color_cmp : NULL;
+            break;
+        case 0xb6e8:
+            p = is_write ? &s->bkgd_mix : NULL;
+            break;
+        case 0xbae8:
+            p = is_write ? &s->frgd_mix : NULL;
+            break;
+        case 0xbee8:
+            if (is_write) {
+                p = &s->mfc[(*val_if_write >> 12) & 0xf];
+                *val_if_write &= 0x0fff;
+            } else {
+                p = NULL;
+            }
+            break;
+        case 0xe2e8:
+            p = &s->pix_trans;
+            break;
+        default:
+            BADF("%s: invalid register at 0x%x\n", __func__, addr);
+            p = NULL;
+            break;
+    }
+
+    return p;
+}
+
+static uint32_t ibm8514_ioport_readb(void *opaque, uint32_t addr)
+{
+    IBM8514State *s = opaque;
+    uint32_t val;
+    uint16_t *p;
+
+    p = ibm8514_get_register(s, addr & ~0x1, 0, NULL);
+
+    if (p) {
+        val = (be16_to_cpu(*p) >> ((~addr & 1) * 8)) & 0xff;
+    } else {
+        val = 0;
+    }
+
+    DPRINTF_IO("%s: read %x at %x\n", __func__, val, addr);
+    return val;
+}
+
+static uint32_t ibm8514_ioport_readw(void *opaque, uint32_t addr)
+{
+    IBM8514State *s = opaque;
+    uint32_t val;
+    uint16_t *p;
+
+    p = ibm8514_get_register(s, addr, 0, NULL);
+
+    if (p) {
+        val = be16_to_cpu(*p);
+    } else {
+        val = 0;
+    }
+
+    DPRINTF_IO("%s: read %x at %x\n", __func__, val, addr);
+    return val;
+}
+
+static void ibm8514_ioport_writeb(void *opaque, uint32_t addr, uint32_t val)
+{
+    IBM8514State *s = opaque;
+    uint16_t *p;
+    uint8_t *c;
+
+    DPRINTF_IO("%s: write %x at %x\n", __func__, val, addr);
+    p = ibm8514_get_register(s, addr & ~0x1, 1, &val);
+
+    if (p) {
+        c = (uint8_t*)p;
+        c[~addr & 1] = val;
+    }
+    if ((addr & ~0x1) == REG_CMD) {
+        do_cmd(s);
+    } else if ((addr & ~0x1) == REG_PIX_TRANS) {
+        BADF("%s: ibm8514: 8-byte PIX_TRANS access (0x%08" PRIx32 ")\n",
+            __func__, addr);
+    }
+}
+
+static void ibm8514_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
+{
+    IBM8514State *s = opaque;
+    uint16_t *p;
+
+    val = cpu_to_be16(val);
+    DPRINTF_IO("%s: write %x at %x\n", __func__, val, addr);
+    p = ibm8514_get_register(s, addr, 1, &val);
+
+    if (p) {
+        *p = val & 0xffff;
+    }
+    if (addr == REG_CMD) {
+        do_cmd(s);
+    } else if (addr == REG_PIX_TRANS) {
+        if (!(s->cmd & CMD_16BIT)) {
+            do_cmd_write_pixel(s, val & 0xff);
+        } else if (s->cmd & CMD_BYTSEQ) {
+            do_cmd_write_pixel(s, val & 0xff);
+            do_cmd_write_pixel(s, val >> 8);
+        } else {
+            do_cmd_write_pixel(s, val >> 8);
+            do_cmd_write_pixel(s, val & 0xff);
+        }
+    }
+}
+
+static void ibm8514_register_dac_port(IBM8514State *s, uint32_t addr)
+{
+    register_ioport_read(addr, 1, 1, ibm8514_dac_ioport_readb, s);
+    register_ioport_write(addr, 1, 1, ibm8514_dac_ioport_writeb, s);
+}
+
+static void ibm8514_register_port(IBM8514State *s, uint32_t addr)
+{
+    register_ioport_read(addr, 2, 1, ibm8514_ioport_readb, s);
+    register_ioport_read(addr, 1, 2, ibm8514_ioport_readw, s);
+    register_ioport_write(addr, 2, 1, ibm8514_ioport_writeb, s);
+    register_ioport_write(addr, 1, 2, ibm8514_ioport_writew, s);
+}
+
+static void ibm8514_draw_line8(IBM8514State *s, uint8_t *dst,
+                               const uint8_t *src, int width)
+{
+    int x;
+    uint8_t val;
+
+    for (x = 0; x < width; x++) {
+        val = *src++;
+        *dst++ = s->palette[val];
+    }
+}
+
+static void ibm8514_draw_line16(IBM8514State *s, uint8_t *dst,
+                                const uint8_t *src, int width)
+{
+    int x;
+    uint8_t val;
+    uint16_t *p = (uint16_t *)dst;
+
+    for (x = 0; x < width; x++) {
+        val = *src++;
+        *p++ = s->palette[val];
+    }
+}
+
+static void ibm8514_draw_line32(IBM8514State *s, uint8_t *dst,
+                                const uint8_t *src, int width)
+{
+    int x;
+    uint8_t val;
+    uint32_t *p = (uint32_t *)dst;
+
+    for (x = 0; x < width; x++) {
+        val = *src++;
+        *p++ = s->palette[val];
+    }
+}
+
+static void ibm8514_update_display(void *opaque)
+{
+    IBM8514State *s = opaque;
+    ram_addr_t vram_offset, page0, page1, page_min = -1, page_max = 0;
+    uint8_t *vram;
+    uint8_t *data;
+    int y, y_start = -1;
+    void (*draw_line)(IBM8514State *s, uint8_t *dst, const uint8_t *src, int width);
+
+    switch (ds_get_bits_per_pixel(s->ds)) {
+        case 32:
+            draw_line = ibm8514_draw_line32;
+            break;
+        case 15:
+        case 16:
+            draw_line = ibm8514_draw_line16;
+            break;
+        case 8:
+            draw_line = ibm8514_draw_line8;
+            break;
+        default:
+            BADF("unknown host depth %d\n", ds_get_bits_per_pixel(s->ds));
+            return;
+    }
+
+    vram = s->vram_ptr;
+    vram_offset = s->vram_offset;
+    data = ds_get_data(s->ds);
+    for (y = 0; y < 480; y++) {
+        page0 = vram_offset & TARGET_PAGE_MASK;
+        page1 = (vram_offset + 640 - 1) & TARGET_PAGE_MASK;
+        if (cpu_physical_memory_get_dirty(page0, VGA_DIRTY_FLAG) ||
+            cpu_physical_memory_get_dirty(page1, VGA_DIRTY_FLAG)) {
+            if (y_start < 0) {
+                y_start = y;
+            }
+            if (page0 < page_min) {
+                page_min = page0;
+            }
+            if (page_max < page1) {
+                page_max = page1;
+            }
+            draw_line(s, data, vram, 640);
+        } else if (y_start >= 0) {
+            /* flush to display */
+            dpy_update(s->ds, 0, y_start,
+                       640, y - y_start);
+            y_start = -1;
+        }
+        data += ds_get_linesize(s->ds);
+        vram += 640;
+        vram_offset += 640;
+    }
+    if (y_start >= 0) {
+        /* flush to display */
+        dpy_update(s->ds, 0, y_start,
+                   640, y - y_start);
+    }
+    /* reset modified pages */
+    if (page_max >= page_min) {
+        cpu_physical_memory_reset_dirty(page_min,
+                                        page_max + TARGET_PAGE_SIZE,
+                                        VGA_DIRTY_FLAG);
+    }
+}
+
+static void ibm8514_screen_dump(void *opaque, const char *filename)
+{
+    IBM8514State *s = opaque;
+    FILE *f;
+    uint8_t *vram;
+    uint8_t color;
+    int x, y;
+
+    f = fopen(filename, "wb");
+    if (f == NULL) {
+        return;
+    }
+    fprintf(f, "P6\n%d %d\n%d\n", 640, 480, 255);
+    vram = s->vram_ptr;
+    for (y = 0; y < 640; y++) {
+        for (x = 0; x < 480; x++) {
+            color = *vram;
+            fputc(s->r[color], f);
+            fputc(s->g[color], f);
+            fputc(s->b[color], f);
+            vram++;
+        }
+    }
+    fclose(f);
+}
+
+static void ibm8514_invalidate_display(void *opaque)
+{
+    IBM8514State *s = opaque;
+
+    ibm8514_set_dirty(s);
+    qemu_console_resize(s->ds, 640, 480);
+}
+
+static void ibm8514_reset(IBM8514State *s)
+{
+    int i;
+
+    /* Initialize palette */
+    /* XXX initial setup unknown */
+    for (i = 0; i < 256; i++) {
+        s->r[i] = s->g[i] = s->b[i] = i;
+    }
+    update_palette_entries(s, 0, 256);
+
+    memset(s->vram_ptr, 0, s->vram_size);
+#if 0
+    cpu_physical_memory_reset_dirty(s->vram_offset,
+                                    s->vram_offset + (640 * 480),
+                                    VGA_DIRTY_FLAG);
+#endif
+
+    s->dac_r_index = 0;
+    s->dac_r_state = 0;
+    s->dac_w_index = 0;
+    s->dac_w_state = 0;
+}
+
+static void ibm8514_init(IBM8514State *s)
+{
+    s->vram_size = 0x200000;
+    s->vram_offset = qemu_ram_alloc(NULL, "ibm8514.vram", s->vram_size);
+    s->vram_ptr = qemu_get_ram_ptr(s->vram_offset);
+
+    cpu_register_physical_memory(isa_mem_base + 0x02800000, s->vram_size, s->vram_offset);
+    qemu_register_coalesced_mmio(isa_mem_base + 0x02800000, s->vram_size);
+
+    s->ds = graphic_console_init(ibm8514_update_display,
+                                 ibm8514_invalidate_display,
+                                 ibm8514_screen_dump, NULL, s);
+    qemu_console_resize(s->ds, 640, 480);
+
+    ibm8514_register_port(s, 0x02e8);
+    ibm8514_register_dac_port(s, 0x02ea);
+    ibm8514_register_dac_port(s, 0x02eb);
+    ibm8514_register_dac_port(s, 0x02ec);
+    ibm8514_register_dac_port(s, 0x02ed);
+    ibm8514_register_port(s, 0x06e8);
+    ibm8514_register_port(s, 0x0ae8);
+    ibm8514_register_port(s, 0x0ee8);
+    ibm8514_register_port(s, 0x12e8);
+    ibm8514_register_port(s, 0x16e8);
+    ibm8514_register_port(s, 0x1ae8);
+    ibm8514_register_port(s, 0x1ee8);
+    ibm8514_register_port(s, 0x22e8);
+    ibm8514_register_port(s, 0x26e8);
+    ibm8514_register_port(s, 0x42e8);
+    ibm8514_register_port(s, 0x46e8);
+    ibm8514_register_port(s, 0x4ae8);
+    ibm8514_register_port(s, 0x82e8);
+    ibm8514_register_port(s, 0x86e8);
+    ibm8514_register_port(s, 0x8ae8);
+    ibm8514_register_port(s, 0x8ee8);
+    ibm8514_register_port(s, 0x92e8);
+    ibm8514_register_port(s, 0x96e8);
+    ibm8514_register_port(s, 0x9ae8);
+    ibm8514_register_port(s, 0x9ae8);
+    ibm8514_register_port(s, 0x9ee8);
+    ibm8514_register_port(s, 0xa2e8);
+    ibm8514_register_port(s, 0xa6e8);
+    ibm8514_register_port(s, 0xaae8);
+    ibm8514_register_port(s, 0xaee8);
+    ibm8514_register_port(s, 0xb2e8);
+    ibm8514_register_port(s, 0xb6e8);
+    ibm8514_register_port(s, 0xbae8);
+    ibm8514_register_port(s, 0xbee8);
+    ibm8514_register_port(s, 0xe2e8);
+    
+    ibm8514_reset(s);
+}
+
+static int ibm8514_vmstate_post_load(void *opaque, int version_id)
+{
+    IBM8514State *s = opaque;
+
+    update_palette_entries(s, 0, 256);
+    ibm8514_set_dirty(s);
+
+    return 0;
+}
+
+static VMStateDescription vmstate_ibm8514 = {
+    .name = "ibm8514",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = ibm8514_vmstate_post_load,
+    .fields = (VMStateField []) {
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+typedef struct PCIIBM8514State {
+    PCIDevice dev;
+    IBM8514State state;
+} PCIIBM8514State;
+
+static void s3_vision864_write_config(PCIDevice *d,
+                                      uint32_t address, uint32_t val, int len)
+{
+    BADF("%s: 0x%08" PRIx32 "\n", __func__, address);
+}
+
+static int s3_vision864_init(PCIDevice *dev)
+{
+    PCIIBM8514State *pci = DO_UPCAST(PCIIBM8514State, dev, dev);
+    IBM8514State *s = &pci->state;
+    uint8_t *pci_conf = dev->config;
+
+    DPRINTF("%s\n", __func__);
+
+    pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_S3);
+    pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_S3_864);
+    pci_config_set_class(pci_conf, PCI_CLASS_DISPLAY_VGA);
+
+    ibm8514_init(s);
+
+    return 0;
+}
+
+static PCIDeviceInfo s3_vision864_info = {
+    .qdev.name    = "s3-vision864",
+    .qdev.size    = sizeof(PCIIBM8514State),
+    .qdev.vmsd    = &vmstate_ibm8514,
+    // XXX these depend on mst's PCI tree
+#if 0
+    .vendor_id    = PCI_VENDOR_ID_S3,
+    .device_id    = PCI_DEVICE_ID_S3_864,
+    .class_id     = PCI_CLASS_DISPLAY_VGA,
+#endif
+    .init         = s3_vision864_init,
+    .config_write = s3_vision864_write_config,
+    .qdev.props   = (Property[]) {
+        DEFINE_PROP_END_OF_LIST()
+    },
+};
+
+static void ibm8514_register(void)
+{
+    pci_qdev_register(&s3_vision864_info);
+}
+
+device_init(ibm8514_register)
+
+/*
+21:
+  DEVICE_ID
+    BusId = PCI
+    DevId = 0x41d00909 (PNP0909)
+    SerialNum = 0x00000000
+    Flags = 0x000061c5
+      : Output
+      : ConsoleOut
+      : PowerManaged
+      : Disableable
+      : Configurable
+      : Integrated
+      : Enabled
+    BaseType = DisplayController (3)
+    SubType = SVGAController (1)
+    Interface = GeneralSVGA (0)
+  BUS_ACCESS
+    info0 = 0
+    info1 = 112
+  AllocatedOffset  = 0x00000767
+    IRQ: 15
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x102 size 0x1 bytes
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x3b4 size 0x2 bytes
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x3b8 size 0x4 bytes
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x3bf size 0xc bytes
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x3cc size 0x1 bytes
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x3ce size 0x2 bytes
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x3d4 size 0x2 bytes
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x3d8 size 0x5 bytes
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x42e8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x46e8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x4ae8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x82e8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x86e8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x8ae8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x8ee8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x92e8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x96e8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x9ae8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0x9ee8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0xa2e8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0xa6e8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0xaae8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0xaee8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0xb2e8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0xb6e8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0xbae8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0xbee8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0xe2e8 size 0x1 bytes				ok
+    LargeVendorItem: Generic Address
+      I/O address (32 bits), at 0xe2ea size 0x1 bytes
+    LargeVendorItem: Generic Address
+      Memory address (32 bits), at 0x2800000 size 0x200000 bytes	ok
+    LargeVendorItem: Display
+      01 00 80 02 e0 01 80 02 00 00 80 c2 00 00 00 00 
+      00 00 20 00 00 00 00 00 78 
+  PossibleOffset   = 0x00000a58
+  CompatibleOffset = 0x00000a59
+
+*/
diff --git a/hw/pci_ids.h b/hw/pci_ids.h
index d3bef0e..821421c 100644
--- a/hw/pci_ids.h
+++ b/hw/pci_ids.h
@@ -97,6 +97,9 @@ 
 #define PCI_VENDOR_ID_FREESCALE          0x1957
 #define PCI_DEVICE_ID_MPC8533E           0x0030
 
+#define PCI_VENDOR_ID_S3                 0x5333
+#define PCI_DEVICE_ID_S3_864             0x88c0
+
 #define PCI_VENDOR_ID_INTEL              0x8086
 #define PCI_DEVICE_ID_INTEL_82378        0x0484
 #define PCI_DEVICE_ID_INTEL_82441        0x1237
diff --git a/hw/ppc_prep.c b/hw/ppc_prep.c
index 6ae1635..c215b0f 100644
--- a/hw/ppc_prep.c
+++ b/hw/ppc_prep.c
@@ -747,6 +747,8 @@  static void ibm_40p_init(ram_addr_t ram_size,
     qdev_prop_set_uint8(&isa->qdev, "board-identification", 0xfc);
     qdev_init_nofail(&isa->qdev);
 
+    pci_create_simple(pci_bus, PCI_DEVFN(2, 0), "s3-vision864");
+
     /* Super I/O (parallel + serial ports) */
     isa = isa_create("isa-pc87312");
     qdev_prop_set_chr(&isa->qdev, "parallel", parallel_hds[0]);