/* Wyse-700 emulation*/
#include <stdlib.h>
#include "ibm.h"
#include "device.h"
#include "io.h"
#include "mem.h"
#include "timer.h"
#include "video.h"
#include "vid_wy700.h"

#define WY700_XSIZE 1280
#define WY700_YSIZE 800

void updatewindowsize(int x, int y);

/* The Wyse 700 is an unusual video card. Though it has an MC6845 CRTC, this
 * is not exposed directly to the host PC. Instead, the CRTC is controlled by
 * an MC68705P3 microcontroller.
 *
 * Rather than emulate the real CRTC, I'm writing this as more or less a
 * fixed-frequency card with a 1280x800 display, and scaling its selection
 * of modes to that window.
 *
 * By default, the card responds to both the CGA and MDA I/O and memory
 * ranges. Either range can be disabled by means of jumpers; this allows
 * the Wy700 to coexist with a CGA or MDA.
 *
 * wy700->wy700_mode indicates which of the supported video modes is in use:
 *
 * 0x00:   40x 25   text     (CGA compatible)        [32x32 character cell]
 * 0x02:   80x 25   text     (CGA / MDA compatible)  [16x32 character cell]
 * 0x04:  320x200x4 graphics (CGA compatible)
 * 0x06:  640x200x2 graphics (CGA compatible)
 * 0x80:  640x400x2 graphics
 * 0x90:  320x400x4 graphics
 * 0xA0: 1280x400x2 graphics
 * 0xB0:  640x400x4 graphics
 * 0xC0: 1280x800x2 graphics (interleaved)
 * 0xD0:  640x800x4 graphics (interleaved)
 * In hi-res graphics modes, bit 3 of the mode byte is the enable flag.
 *
 */

/* What works (or appears to) :
 * MDA/CGA 80x25 text mode
 * CGA 40x25 text mode
 * CGA 640x200 graphics mode
 * CGA 320x200 graphics mode
 * Hi-res graphics modes
 * Font selection
 * Display enable / disable
 *   -- via Wy700 mode register      (in hi-res modes)
 *   -- via Wy700 command register   (in text & CGA modes)
 *   -- via CGA/MDA control register (in text & CGA modes)
 *
 * What doesn't work, is untested or not well understood:
 * - Cursor detach (commands 4 and 5)
 */

/* The microcontroller sets up the real CRTC with one of five fixed mode
 * definitions. As written, this is a fairly simplistic emulation that
 * doesn't attempt to closely follow the actual working of the CRTC; but I've
 * included the definitions here for information. */

static uint8_t mode_1280x800[] = {
        0x31, /* Horizontal total */
        0x28, /* Horizontal displayed */
        0x29, /* Horizontal sync position */
        0x06, /* Horizontal sync width */
        0x1b, /* Vertical total */
        0x00, /* Vertical total adjust */
        0x19, /* Vertical displayed */
        0x1a, /* Vsync position */
        0x03, /* Interlace and skew */
        0x0f, /* Maximum raster address */
};

static uint8_t mode_1280x400[] = {
        0x31, /* Horizontal total */
        0x28, /* Horizontal displayed */
        0x29, /* Horizontal sync position */
        0x06, /* Horizontal sync width */
        0x1b, /* Vertical total */
        0x00, /* Vertical total adjust */
        0x19, /* Vertical displayed */
        0x1a, /* Vsync position */
        0x01, /* Interlace and skew */
        0x0f, /* Maximum raster address */
};

static uint8_t mode_640x400[] = {
        0x18, /* Horizontal total */
        0x14, /* Horizontal displayed */
        0x14, /* Horizontal sync position */
        0x03, /* Horizontal sync width */
        0x1b, /* Vertical total */
        0x00, /* Vertical total adjust */
        0x19, /* Vertical displayed */
        0x1a, /* Vsync position */
        0x01, /* Interlace and skew */
        0x0f, /* Maximum raster address */
};

static uint8_t mode_640x200[] = {
        0x18, /* Horizontal total */
        0x14, /* Horizontal displayed */
        0x14, /* Horizontal sync position */
        0xff, /* Horizontal sync width */
        0x37, /* Vertical total */
        0x00, /* Vertical total adjust */
        0x32, /* Vertical displayed */
        0x34, /* Vsync position */
        0x03, /* Interlace and skew */
        0x07, /* Maximum raster address */
};

static uint8_t mode_80x24[] = {
        0x31, /* Horizontal total */
        0x28, /* Horizontal displayed */
        0x2A, /* Horizontal sync position */
        0xff, /* Horizontal sync width */
        0x1b, /* Vertical total */
        0x00, /* Vertical total adjust */
        0x19, /* Vertical displayed */
        0x1a, /* Vsync position */
        0x01, /* Interlace and skew */
        0x0f, /* Maximum raster address */
};

static uint8_t mode_40x24[] = {
        0x18, /* Horizontal total */
        0x14, /* Horizontal displayed */
        0x15, /* Horizontal sync position */
        0xff, /* Horizontal sync width */
        0x1b, /* Vertical total */
        0x00, /* Vertical total adjust */
        0x19, /* Vertical displayed */
        0x1a, /* Vsync position */
        0x01, /* Interlace and skew */
        0x0f, /* Maximum raster address */
};

static uint32_t wy700_pal[4];

/* Font ROM: Two fonts, each containing 256 characters, 16x16 pixels */
extern uint8_t fontdatw[512][32];

typedef struct wy700_t {
        mem_mapping_t mapping;

        /* The microcontroller works by watching four ports:
         * 0x3D8 / 0x3B8 (mode control register)
         * 0x3DD         (top scanline address)
         * 0x3DF         (Wy700 control register)
         * CRTC reg 14   (cursor location high)
         *
         * It will do nothing until one of these registers is touched. When
         * one is, it then reconfigures the internal 6845 based on what it
         * sees.
         */
        uint8_t last_03D8; /* Copies of values written to the listed */
        uint8_t last_03DD; /* I/O ports */
        uint8_t last_03DF;
        uint8_t last_crtc_0E;

        uint8_t cga_crtc[32];  /* The 'CRTC' as the host PC sees it */
        uint8_t real_crtc[32]; /* The internal CRTC as the microcontroller */
        /* sees it */
        int cga_crtcreg;       /* Current CRTC register */
        uint16_t wy700_base;   /* Framebuffer base address (native modes) */
        uint8_t wy700_control; /* Native control / command register */
        uint8_t wy700_mode;    /* Current mode (see list at top of file) */
        uint8_t cga_ctrl;      /* Emulated MDA/CGA control register */
        uint8_t cga_colour;    /* Emulated CGA colour register (ignored) */

        uint8_t mda_stat; /* MDA status (IN 0x3BA) */
        uint8_t cga_stat; /* CGA status (IN 0x3DA) */

        int font;    /* Current font, 0 or 1 */
        int enabled; /* Display enabled, 0 or 1 */
        int detach;  /* Detach cursor, 0 or 1 */

        uint64_t dispontime, dispofftime;
        pc_timer_t timer;

        int linepos, displine;
        int vc;
        int dispon, blink;
        int vsynctime;

        uint8_t *vram;
} wy700_t;

/* Mapping of attributes to colours, in CGA emulation... */
static int cgacols[256][2][2];
/* ... and MDA emulation. */
static int mdacols[256][2][2];

void wy700_recalctimings(wy700_t *wy700);
void wy700_write(uint32_t addr, uint8_t val, void *p);
uint8_t wy700_read(uint32_t addr, void *p);
void wy700_checkchanges(wy700_t *wy700);

void wy700_out(uint16_t addr, uint8_t val, void *p) {
        wy700_t *wy700 = (wy700_t *)p;
        switch (addr) {
                /* These three registers are only mapped in the 3Dx range,
                 * not the 3Bx range. */
        case 0x3DD: /* Base address (low) */
                wy700->wy700_base &= 0xFF00;
                wy700->wy700_base |= val;
                wy700_checkchanges(wy700);
                break;

        case 0x3DE: /* Base address (high) */
                wy700->wy700_base &= 0xFF;
                wy700->wy700_base |= ((uint16_t)val) << 8;
                wy700_checkchanges(wy700);
                break;

        case 0x3DF: /* Command / control register */
                wy700->wy700_control = val;
                wy700_checkchanges(wy700);
                break;

                /* Emulated CRTC, register select */
        case 0x3b0:
        case 0x3b2:
        case 0x3b4:
        case 0x3b6:
        case 0x3d0:
        case 0x3d2:
        case 0x3d4:
        case 0x3d6:
                wy700->cga_crtcreg = val & 31;
                break;

                /* Emulated CRTC, value */
        case 0x3b1:
        case 0x3b3:
        case 0x3b5:
        case 0x3b7:
        case 0x3d1:
        case 0x3d3:
        case 0x3d5:
        case 0x3d7:
                wy700->cga_crtc[wy700->cga_crtcreg] = val;

                wy700_checkchanges(wy700);
                wy700_recalctimings(wy700);
                return;

                /* Emulated MDA / CGA control register */
        case 0x3b8:
        case 0x3D8:
                wy700->cga_ctrl = val;
                wy700_checkchanges(wy700);
                return;
                /* Emulated CGA colour register */
        case 0x3D9:
                wy700->cga_colour = val;
                return;
        }
}

uint8_t wy700_in(uint16_t addr, void *p) {
        wy700_t *wy700 = (wy700_t *)p;
        switch (addr) {
        case 0x3b0:
        case 0x3b2:
        case 0x3b4:
        case 0x3b6:
        case 0x3d0:
        case 0x3d2:
        case 0x3d4:
        case 0x3d6:
                return wy700->cga_crtcreg;
        case 0x3b1:
        case 0x3b3:
        case 0x3b5:
        case 0x3b7:
        case 0x3d1:
        case 0x3d3:
        case 0x3d5:
        case 0x3d7:
                return wy700->cga_crtc[wy700->cga_crtcreg];
        case 0x3b8:
        case 0x3d8:
                return wy700->cga_ctrl;
        case 0x3d9:
                return wy700->cga_colour;
        case 0x3ba:
                return wy700->mda_stat;
        case 0x3da:
                return wy700->cga_stat;
        }
        return 0xff;
}

/* Check if any of the four key registers has changed. If so, check for a
 * mode change or cursor size change */
void wy700_checkchanges(wy700_t *wy700) {
        uint8_t curstart, curend;

        if (wy700->last_03D8 == wy700->cga_ctrl && wy700->last_03DD == (wy700->wy700_base & 0xFF) &&
            wy700->last_03DF == wy700->wy700_control && wy700->last_crtc_0E == wy700->cga_crtc[0x0E]) {
                return; /* Nothing changed */
        }
        /* Check for control register changes */
        if (wy700->last_03DF != wy700->wy700_control) {
                wy700->last_03DF = wy700->wy700_control;

                /* Values 1-7 are commands. */
                switch (wy700->wy700_control) {
                case 1: /* Reset */
                        wy700->font = 0;
                        wy700->enabled = 1;
                        wy700->detach = 0;
                        break;

                case 2: /* Font 1 */
                        wy700->font = 0;
                        break;

                case 3: /* Font 2 */
                        wy700->font = 1;
                        break;

                        /* Even with the microprogram from an original card, I can't really work out
                         * what commands 4 and 5 (which I've called 'cursor detach' / 'cursor attach')
                         * do. Command 4 sets a flag in microcontroller RAM, and command 5 clears
                         * it. When the flag is set, the real cursor doesn't track the cursor in the
                         * emulated CRTC, and its blink rate increases. Possibly it's a self-test
                         * function of some kind.
                         *
                         * The card documentation doesn't cover these commands.
                         */

                case 4: /* Detach cursor */
                        wy700->detach = 1;
                        break;

                case 5: /* Attach cursor */
                        wy700->detach = 0;
                        break;

                case 6: /* Disable display */
                        wy700->enabled = 0;
                        break;

                case 7: /* Enable display */
                        wy700->enabled = 1;
                        break;
                }
                /* A control write with the top bit set selects graphics mode */
                if (wy700->wy700_control & 0x80) {
                        /* Select hi-res graphics mode; map framebuffer at A0000 */
                        mem_mapping_set_addr(&wy700->mapping, 0xa0000, 0x20000);
                        wy700->wy700_mode = wy700->wy700_control;

                        /* Select appropriate preset timings */
                        if (wy700->wy700_mode & 0x40) {
                                memcpy(wy700->real_crtc, mode_1280x800, sizeof(mode_1280x800));
                        } else if (wy700->wy700_mode & 0x20) {
                                memcpy(wy700->real_crtc, mode_1280x400, sizeof(mode_1280x400));
                        } else {
                                memcpy(wy700->real_crtc, mode_640x400, sizeof(mode_640x400));
                        }
                }
        }
        /* An attempt to program the CGA / MDA selects low-res mode */
        else if (wy700->last_03D8 != wy700->cga_ctrl) {
                wy700->last_03D8 = wy700->cga_ctrl;
                /* Set lo-res text or graphics mode.
                 * (Strictly speaking, when not in hi-res mode the card
                 *  should be mapped at B0000-B3FFF and B8000-BBFFF, leaving
                 * a 16k hole between the two ranges) */
                mem_mapping_set_addr(&wy700->mapping, 0xb0000, 0x0C000);
                if (wy700->cga_ctrl & 2) /* Graphics mode */
                {
                        wy700->wy700_mode = (wy700->cga_ctrl & 0x10) ? 6 : 4;
                        memcpy(wy700->real_crtc, mode_640x200, sizeof(mode_640x200));
                } else if (wy700->cga_ctrl & 1) /* Text mode 80x24 */
                {
                        wy700->wy700_mode = 2;
                        memcpy(wy700->real_crtc, mode_80x24, sizeof(mode_80x24));
                } else /* Text mode 40x24 */
                {
                        wy700->wy700_mode = 0;
                        memcpy(wy700->real_crtc, mode_40x24, sizeof(mode_40x24));
                }
        }
        /* Convert the cursor sizes from the ones used by the CGA or MDA
         * to native */

        if (wy700->cga_crtc[9] == 13) /* MDA scaling */
        {
                curstart = wy700->cga_crtc[10] & 0x1F;
                wy700->real_crtc[10] = ((curstart + 5) >> 3) + curstart;
                if (wy700->real_crtc[10] > 31)
                        wy700->real_crtc[10] = 31;
                /* And bring 'cursor disabled' flag across */
                if ((wy700->cga_crtc[10] & 0x60) == 0x20) {
                        wy700->real_crtc[10] |= 0x20;
                }
                curend = wy700->cga_crtc[11] & 0x1F;
                wy700->real_crtc[11] = ((curend + 5) >> 3) + curend;
                if (wy700->real_crtc[11] > 31)
                        wy700->real_crtc[11] = 31;
        } else /* CGA scaling */
        {
                curstart = wy700->cga_crtc[10] & 0x1F;
                wy700->real_crtc[10] = curstart << 1;
                if (wy700->real_crtc[10] > 31)
                        wy700->real_crtc[10] = 31;
                /* And bring 'cursor disabled' flag across */
                if ((wy700->cga_crtc[10] & 0x60) == 0x20) {
                        wy700->real_crtc[10] |= 0x20;
                }
                curend = wy700->cga_crtc[11] & 0x1F;
                wy700->real_crtc[11] = curend << 1;
                if (wy700->real_crtc[11] > 31)
                        wy700->real_crtc[11] = 31;
        }
}

void wy700_write(uint32_t addr, uint8_t val, void *p) {
        wy700_t *wy700 = (wy700_t *)p;
        egawrites++;

        if (wy700->wy700_mode & 0x80) /* High-res mode. */
        {
                addr &= 0xFFFF;
                /* In 800-line modes, bit 1 of the control register sets the high bit of the
                 * write address. */
                if ((wy700->wy700_mode & 0x42) == 0x42) {
                        addr |= 0x10000;
                }
                wy700->vram[addr] = val;
        } else {
                wy700->vram[addr & 0x3fff] = val;
        }
}

uint8_t wy700_read(uint32_t addr, void *p) {
        wy700_t *wy700 = (wy700_t *)p;
        egareads++;
        if (wy700->wy700_mode & 0x80) /* High-res mode. */
        {
                addr &= 0xFFFF;
                /* In 800-line modes, bit 0 of the control register sets the high bit of the
                 * read address. */
                if ((wy700->wy700_mode & 0x41) == 0x41) {
                        addr |= 0x10000;
                }
                return wy700->vram[addr];
        } else {
                return wy700->vram[addr & 0x3fff];
        }
}

void wy700_recalctimings(wy700_t *wy700) {
        double disptime;
        double _dispontime, _dispofftime;

        disptime = wy700->real_crtc[0] + 1;
        _dispontime = wy700->real_crtc[1];
        _dispofftime = disptime - _dispontime;
        _dispontime *= MDACONST;
        _dispofftime *= MDACONST;
        wy700->dispontime = (uint64_t)_dispontime;
        wy700->dispofftime = (uint64_t)_dispofftime;
}

/* Draw a single line of the screen in either text mode */
void wy700_textline(wy700_t *wy700) {
        int x;
        int w = (wy700->wy700_mode == 0) ? 40 : 80;
        int cw = (wy700->wy700_mode == 0) ? 32 : 16;
        uint8_t chr, attr;
        uint8_t bitmap[2];
        uint8_t *fontbase = &fontdatw[0][0];
        int blink, c;
        int drawcursor, cursorline;
        int mda = 0;
        uint16_t addr;
        uint8_t sc;
        uint16_t ma = (wy700->cga_crtc[13] | (wy700->cga_crtc[12] << 8)) & 0x3fff;
        uint16_t ca = (wy700->cga_crtc[15] | (wy700->cga_crtc[14] << 8)) & 0x3fff;

        /* The fake CRTC character height register selects whether MDA or CGA
         * attributes are used */
        if (wy700->cga_crtc[9] == 0 || wy700->cga_crtc[9] == 13) {
                mda = 1;
        }

        if (wy700->font) {
                fontbase += 256 * 32;
        }
        addr = ((ma & ~1) + (wy700->displine >> 5) * w) * 2;
        sc = (wy700->displine >> 1) & 15;

        ma += ((wy700->displine >> 5) * w);

        if ((wy700->real_crtc[10] & 0x60) == 0x20) {
                cursorline = 0;
        } else {
                cursorline = ((wy700->real_crtc[10] & 0x1F) <= sc) && ((wy700->real_crtc[11] & 0x1F) >= sc);
        }

        for (x = 0; x < w; x++) {
                chr = wy700->vram[(addr + 2 * x) & 0x3FFF];
                attr = wy700->vram[(addr + 2 * x + 1) & 0x3FFF];
                drawcursor = ((ma == ca) && cursorline && wy700->enabled && (wy700->cga_ctrl & 8) && (wy700->blink & 16));
                blink = ((wy700->blink & 16) && (wy700->cga_ctrl & 0x20) && (attr & 0x80) && !drawcursor);

                if (wy700->cga_ctrl & 0x20)
                        attr &= 0x7F;
                /* MDA underline */
                if (sc == 14 && mda && ((attr & 7) == 1)) {
                        for (c = 0; c < cw; c++)
                                ((uint32_t *)buffer32->line[wy700->displine])[(x * cw) + c] = mdacols[attr][blink][1];
                } else /* Draw 16 pixels of character */
                {
                        bitmap[0] = fontbase[chr * 32 + 2 * sc];
                        bitmap[1] = fontbase[chr * 32 + 2 * sc + 1];
                        for (c = 0; c < 16; c++) {
                                int col;
                                if (c < 8)
                                        col = (mda ? mdacols : cgacols)[attr][blink][(bitmap[0] & (1 << (c ^ 7))) ? 1 : 0];
                                else
                                        col = (mda ? mdacols : cgacols)[attr][blink][(bitmap[1] & (1 << ((c & 7) ^ 7))) ? 1 : 0];
                                if (!(wy700->enabled) || !(wy700->cga_ctrl & 8))
                                        col = mdacols[0][0][0];
                                if (w == 40) {
                                        ((uint32_t *)buffer32->line[wy700->displine])[(x * cw) + 2 * c] = col;
                                        ((uint32_t *)buffer32->line[wy700->displine])[(x * cw) + 2 * c + 1] = col;
                                } else
                                        ((uint32_t *)buffer32->line[wy700->displine])[(x * cw) + c] = col;
                        }

                        if (drawcursor) {
                                for (c = 0; c < cw; c++)
                                        ((uint32_t *)buffer32->line[wy700->displine])[(x * cw) + c] ^=
                                                (mda ? mdacols : cgacols)[attr][0][1];
                        }
                        ++ma;
                }
        }
}

/* Draw a line in either of the CGA graphics modes (320x200 or 640x200) */
void wy700_cgaline(wy700_t *wy700) {
        int x, c;
        uint32_t dat;
        uint32_t ink = 0;
        uint16_t addr;

        uint16_t ma = (wy700->cga_crtc[13] | (wy700->cga_crtc[12] << 8)) & 0x3fff;
        addr = ((wy700->displine >> 2) & 1) * 0x2000 + (wy700->displine >> 3) * 80 + ((ma & ~1) << 1);

        /* The fixed mode setting here programs the real CRTC with a screen
         * width to 20, so draw in 20 fixed chunks of 4 bytes each */
        for (x = 0; x < 20; x++) {
                dat = ((wy700->vram[addr & 0x3FFF] << 24) | (wy700->vram[(addr + 1) & 0x3FFF] << 16) |
                       (wy700->vram[(addr + 2) & 0x3FFF] << 8) | (wy700->vram[(addr + 3) & 0x3FFF]));
                addr += 4;

                if (wy700->wy700_mode == 6) {
                        for (c = 0; c < 32; c++) {
                                ink = (dat & 0x80000000) ? wy700_pal[3] : wy700_pal[0];
                                if (!(wy700->enabled) || !(wy700->cga_ctrl & 8))
                                        ink = wy700_pal[0];
                                ((uint32_t *)buffer32->line[wy700->displine])[x * 64 + 2 * c] =
                                        ((uint32_t *)buffer32->line[wy700->displine])[x * 64 + 2 * c + 1] = ink;
                                dat = dat << 1;
                        }
                } else {
                        for (c = 0; c < 16; c++) {
                                ink = wy700_pal[(dat >> 30) & 3];
                                if (!(wy700->enabled) || !(wy700->cga_ctrl & 8))
                                        ink = wy700_pal[0];
                                ((uint32_t *)buffer32->line[wy700->displine])[x * 64 + 4 * c] =
                                        ((uint32_t *)buffer32->line[wy700->displine])[x * 64 + 4 * c + 1] =
                                                ((uint32_t *)buffer32->line[wy700->displine])[x * 64 + 4 * c + 2] =
                                                        ((uint32_t *)buffer32->line[wy700->displine])[x * 64 + 4 * c + 3] = ink;
                                dat = dat << 2;
                        }
                }
        }
}

/* Draw a line in the medium-resolution graphics modes (640x400 or 320x400) */
void wy700_medresline(wy700_t *wy700) {
        int x, c;
        uint32_t dat;
        uint32_t ink = 0;
        uint32_t addr;

        addr = (wy700->displine >> 1) * 80 + 4 * wy700->wy700_base;

        for (x = 0; x < 20; x++) {
                dat = ((wy700->vram[addr & 0x1FFFF] << 24) | (wy700->vram[(addr + 1) & 0x1FFFF] << 16) |
                       (wy700->vram[(addr + 2) & 0x1FFFF] << 8) | (wy700->vram[(addr + 3) & 0x1FFFF]));
                addr += 4;

                if (wy700->wy700_mode & 0x10) {
                        for (c = 0; c < 16; c++) {
                                ink = wy700_pal[(dat >> 30) & 3];
                                /* Display disabled? */
                                if (!(wy700->wy700_mode & 8))
                                        ink = wy700_pal[0];
                                ((uint32_t *)buffer32->line[wy700->displine])[x * 64 + 4 * c] =
                                        ((uint32_t *)buffer32->line[wy700->displine])[x * 64 + 4 * c + 1] =
                                                ((uint32_t *)buffer32->line[wy700->displine])[x * 64 + 4 * c + 2] =
                                                        ((uint32_t *)buffer32->line[wy700->displine])[x * 64 + 4 * c + 3] = ink;
                                dat = dat << 2;
                        }
                } else {
                        for (c = 0; c < 32; c++) {
                                ink = (dat & 0x80000000) ? wy700_pal[3] : wy700_pal[0];
                                /* Display disabled? */
                                if (!(wy700->wy700_mode & 8))
                                        ink = wy700_pal[0];
                                ((uint32_t *)buffer32->line[wy700->displine])[x * 64 + 2 * c] =
                                        ((uint32_t *)buffer32->line[wy700->displine])[x * 64 + 2 * c + 1] = ink;
                                dat = dat << 1;
                        }
                }
        }
}

/* Draw a line in one of the high-resolution modes */
void wy700_hiresline(wy700_t *wy700) {
        int x, c;
        uint32_t dat;
        uint32_t ink = 0;
        uint32_t addr;

        addr = (wy700->displine >> 1) * 160 + 4 * wy700->wy700_base;

        if (wy700->wy700_mode & 0x40) /* 800-line interleaved modes */
        {
                if (wy700->displine & 1)
                        addr += 0x10000;
        }
        for (x = 0; x < 40; x++) {
                dat = ((wy700->vram[addr & 0x1FFFF] << 24) | (wy700->vram[(addr + 1) & 0x1FFFF] << 16) |
                       (wy700->vram[(addr + 2) & 0x1FFFF] << 8) | (wy700->vram[(addr + 3) & 0x1FFFF]));
                addr += 4;

                if (wy700->wy700_mode & 0x10) {
                        for (c = 0; c < 16; c++) {
                                ink = wy700_pal[(dat >> 30) & 3];
                                /* Display disabled? */
                                if (!(wy700->wy700_mode & 8))
                                        ink = wy700_pal[0];
                                ((uint32_t *)buffer32->line[wy700->displine])[x * 32 + 2 * c] =
                                        ((uint32_t *)buffer32->line[wy700->displine])[x * 32 + 2 * c + 1] = ink;
                                dat = dat << 2;
                        }
                } else {
                        for (c = 0; c < 32; c++) {
                                ink = (dat & 0x80000000) ? wy700_pal[3] : wy700_pal[0];
                                /* Display disabled? */
                                if (!(wy700->wy700_mode & 8))
                                        ink = wy700_pal[0];
                                ((uint32_t *)buffer32->line[wy700->displine])[x * 32 + c] = ink;
                                dat = dat << 1;
                        }
                }
        }
}

void wy700_poll(void *p) {
        wy700_t *wy700 = (wy700_t *)p;
        int mode;

        if (!wy700->linepos) {
                timer_advance_u64(&wy700->timer, wy700->dispofftime);
                wy700->cga_stat |= 1;
                wy700->mda_stat |= 1;
                wy700->linepos = 1;
                if (wy700->dispon) {
                        if (wy700->displine == 0) {
                                video_wait_for_buffer();
                        }

                        if (wy700->wy700_mode & 0x80)
                                mode = wy700->wy700_mode & 0xF0;
                        else
                                mode = wy700->wy700_mode & 0x0F;

                        switch (mode) {
                        default:
                        case 0x00:
                        case 0x02:
                                wy700_textline(wy700);
                                break;
                        case 0x04:
                        case 0x06:
                                wy700_cgaline(wy700);
                                break;
                        case 0x80:
                        case 0x90:
                                wy700_medresline(wy700);
                                break;
                        case 0xA0:
                        case 0xB0:
                        case 0xC0:
                        case 0xD0:
                        case 0xE0:
                        case 0xF0:
                                wy700_hiresline(wy700);
                                break;
                        }
                }
                wy700->displine++;
                /* Hardcode a fixed refresh rate and VSYNC timing */
                if (wy700->displine == 800) /* Start of VSYNC */
                {
                        wy700->cga_stat |= 8;
                        wy700->dispon = 0;
                }
                if (wy700->displine == 832) /* End of VSYNC */
                {
                        wy700->displine = 0;
                        wy700->cga_stat &= ~8;
                        wy700->dispon = 1;
                }
        } else {
                if (wy700->dispon) {
                        wy700->cga_stat &= ~1;
                        wy700->mda_stat &= ~1;
                }
                timer_advance_u64(&wy700->timer, wy700->dispontime);
                wy700->linepos = 0;

                if (wy700->displine == 800) {
                        /* Hardcode 1280x800 window size */
                        if (WY700_XSIZE != xsize || WY700_YSIZE != ysize) {
                                xsize = WY700_XSIZE;
                                ysize = WY700_YSIZE;
                                if (xsize < 64)
                                        xsize = 656;
                                if (ysize < 32)
                                        ysize = 200;
                                updatewindowsize(xsize, ysize);
                        }
                        video_blit_memtoscreen(0, 0, 0, ysize, xsize, ysize);

                        frames++;
                        /* Fixed 1280x800 resolution */
                        video_res_x = WY700_XSIZE;
                        video_res_y = WY700_YSIZE;
                        if (wy700->wy700_mode & 0x80)
                                mode = wy700->wy700_mode & 0xF0;
                        else
                                mode = wy700->wy700_mode & 0x0F;
                        switch (mode) {
                        case 0x00:
                        case 0x02:
                                video_bpp = 0;
                                break;
                        case 0x04:
                        case 0x90:
                        case 0xB0:
                        case 0xD0:
                        case 0xF0:
                                video_bpp = 2;
                                break;
                        default:
                                video_bpp = 1;
                                break;
                        }
                        wy700->blink++;
                }
        }
}

void *wy700_init() {
        int c;
        wy700_t *wy700 = malloc(sizeof(wy700_t));
        memset(wy700, 0, sizeof(wy700_t));

        /* 128k video RAM */
        wy700->vram = malloc(0x20000);

        timer_add(&wy700->timer, wy700_poll, wy700, 1);

        /* Occupy memory between 0xB0000 and 0xBFFFF (moves to 0xA0000 in
         * high-resolution modes)  */
        mem_mapping_add(&wy700->mapping, 0xb0000, 0x10000, wy700_read, NULL, NULL, wy700_write, NULL, NULL, NULL, 0, wy700);
        /* Respond to both MDA and CGA I/O ports */
        io_sethandler(0x03b0, 0x000C, wy700_in, NULL, NULL, wy700_out, NULL, NULL, wy700);
        io_sethandler(0x03d0, 0x0010, wy700_in, NULL, NULL, wy700_out, NULL, NULL, wy700);

        wy700_pal[0] = makecol(0x00, 0x00, 0x00);
        wy700_pal[1] = makecol(0x55, 0x55, 0x55);
        wy700_pal[2] = makecol(0xaa, 0xaa, 0xaa);
        wy700_pal[3] = makecol(0xff, 0xff, 0xff);

        /* Set up the emulated attributes.
         * CGA is done in four groups: 00-0F, 10-7F, 80-8F, 90-FF */
        for (c = 0; c < 0x10; c++) {
                cgacols[c][0][0] = cgacols[c][1][0] = cgacols[c][1][1] = wy700_pal[0];
                if (c & 8)
                        cgacols[c][0][1] = wy700_pal[3];
                else
                        cgacols[c][0][1] = wy700_pal[2];
        }
        for (c = 0x10; c < 0x80; c++) {
                cgacols[c][0][0] = cgacols[c][1][0] = cgacols[c][1][1] = wy700_pal[2];
                if (c & 8)
                        cgacols[c][0][1] = wy700_pal[3];
                else
                        cgacols[c][0][1] = wy700_pal[0];

                if ((c & 0x0F) == 8)
                        cgacols[c][0][1] = wy700_pal[1];
        }
        /* With special cases for 00, 11, 22, ... 77 */
        cgacols[0x00][0][1] = cgacols[0x00][1][1] = wy700_pal[0];
        for (c = 0x11; c <= 0x77; c += 0x11) {
                cgacols[c][0][1] = cgacols[c][1][1] = wy700_pal[2];
        }
        for (c = 0x80; c < 0x90; c++) {
                cgacols[c][0][0] = wy700_pal[1];
                if (c & 8)
                        cgacols[c][0][1] = wy700_pal[3];
                else
                        cgacols[c][0][1] = wy700_pal[2];
                cgacols[c][1][0] = cgacols[c][1][1] = cgacols[c - 0x80][0][0];
        }
        for (c = 0x90; c < 0x100; c++) {
                cgacols[c][0][0] = wy700_pal[3];
                if (c & 8)
                        cgacols[c][0][1] = wy700_pal[1];
                else
                        cgacols[c][0][1] = wy700_pal[2];
                if ((c & 0x0F) == 0)
                        cgacols[c][0][1] = wy700_pal[0];
                cgacols[c][1][0] = cgacols[c][1][1] = cgacols[c - 0x80][0][0];
        }
        /* Also special cases for 99, AA, ..., FF */
        for (c = 0x99; c <= 0xFF; c += 0x11) {
                cgacols[c][0][1] = wy700_pal[3];
        }
        /* Special cases for 08, 80 and 88 */
        cgacols[0x08][0][1] = wy700_pal[1];
        cgacols[0x80][0][1] = wy700_pal[0];
        cgacols[0x88][0][1] = wy700_pal[1];

        /* MDA attributes */
        for (c = 0; c < 256; c++) {
                mdacols[c][0][0] = mdacols[c][1][0] = mdacols[c][1][1] = wy700_pal[0];
                if (c & 8)
                        mdacols[c][0][1] = wy700_pal[3];
                else
                        mdacols[c][0][1] = wy700_pal[2];
        }
        mdacols[0x70][0][1] = wy700_pal[0];
        mdacols[0x70][0][0] = mdacols[0x70][1][0] = mdacols[0x70][1][1] = wy700_pal[3];
        mdacols[0xF0][0][1] = wy700_pal[0];
        mdacols[0xF0][0][0] = mdacols[0xF0][1][0] = mdacols[0xF0][1][1] = wy700_pal[3];
        mdacols[0x78][0][1] = wy700_pal[2];
        mdacols[0x78][0][0] = mdacols[0x78][1][0] = mdacols[0x78][1][1] = wy700_pal[3];
        mdacols[0xF8][0][1] = wy700_pal[2];
        mdacols[0xF8][0][0] = mdacols[0xF8][1][0] = mdacols[0xF8][1][1] = wy700_pal[3];
        mdacols[0x00][0][1] = mdacols[0x00][1][1] = wy700_pal[0];
        mdacols[0x08][0][1] = mdacols[0x08][1][1] = wy700_pal[0];
        mdacols[0x80][0][1] = mdacols[0x80][1][1] = wy700_pal[0];
        mdacols[0x88][0][1] = mdacols[0x88][1][1] = wy700_pal[0];

        /* Start off in 80x25 text mode */
        wy700->cga_stat = 0xF4;
        wy700->wy700_mode = 2;
        wy700->enabled = 1;
        memcpy(wy700->real_crtc, mode_80x24, sizeof(mode_80x24));
        return wy700;
}

void wy700_close(void *p) {
        wy700_t *wy700 = (wy700_t *)p;

        free(wy700->vram);
        free(wy700);
}

void wy700_speed_changed(void *p) {
        wy700_t *wy700 = (wy700_t *)p;

        wy700_recalctimings(wy700);
}

device_t wy700_device = {"Wyse 700", 0, wy700_init, wy700_close, NULL, wy700_speed_changed, NULL, NULL};
