/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Implementation of the Toshiba 3100e plasma display.
 *          This display has a fixed 640x400 resolution.
 *
 *          T3100e CRTC regs (from the ROM):
 *
 *          Selecting a character height of 3 seems to be sufficient to
 *          convert the 640x200 graphics mode to 640x400 (and, by
 *          analogy, 320x200 to 320x400).
 *
 *          Horiz-----> Vert------>  I ch
 *          38 28 2D 0A 1F 06 19 1C 02 07 06 07   CO40
 *          71 50 5A 0A 1F 06 19 1C 02 07 06 07   CO80
 *          38 28 2D 0A 7F 06 64 70 02 01 06 07   Graphics
 *          61 50 52 0F 19 06 19 19 02 0D 0B 0C   MONO
 *          2D 28 22 0A 67 00 64 67 02 03 06 07   640x400
 *
 * Authors: Fred N. van Kempen, <decwiz@yahoo.com>
 *          Miran Grca, <mgrca8@gmail.com>
 *          John Elliott, <jce@seasip.info>
 *
 *          Copyright 2017-2019 Fred N. van Kempen.
 *          Copyright 2016-2019 Miran Grca.
 *          Copyright 2008-2019 John Elliott.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free  Software  Foundation; either  version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is  distributed in the hope that it will be useful, but
 * WITHOUT   ANY  WARRANTY;  without  even   the  implied  warranty  of
 * MERCHANTABILITY  or FITNESS  FOR A PARTICULAR  PURPOSE. See  the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the:
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place - Suite 330
 *   Boston, MA 02111-1307
 *   USA.
 */
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/io.h>
#include <86box/mem.h>
#include <86box/timer.h>
#include "cpu.h"
#include <86box/video.h>
#include <86box/vid_cga.h>
#include <86box/m_at_t3100e.h>
#include <86box/plat_unused.h>

#define T3100E_XSIZE 640
#define T3100E_YSIZE 400

/*Very rough estimate*/
#define VID_CLOCK (double) (651 * 416 * 60)

/* Mapping of attributes to colours */
static uint32_t amber;
static uint32_t black;
static uint8_t  boldcols[256]; /* Which attributes use the bold font */
static uint32_t blinkcols[256][2];
static uint32_t normcols[256][2];

/* Video options set by the motherboard; they will be picked up by the card
 * on the next poll.
 *
 * Bit  3:   Disable built-in video (for add-on card)
 * Bit  2:   Thin font
 * Bits 0,1: Font set (not currently implemented)
 */
static uint8_t st_video_options;
static int8_t  st_display_internal = -1;

void
t3100e_video_options_set(uint8_t options)
{
    st_video_options = options;
}

void
t3100e_display_set(uint8_t internal)
{
    st_display_internal = internal;
}

uint8_t
t3100e_display_get(void)
{
    return st_display_internal;
}

typedef struct t3100e_t {
    mem_mapping_t mapping;

    cga_t cga; /* The CGA is used for the external
                * display; most of its registers are
                * ignored by the plasma display. */

    int     font;     /* Current font, 0-3 */
    int     enabled;  /* Hardware enabled, 0 or 1 */
    int     internal; /* Using internal display? */
    uint8_t attrmap;  /* Attribute mapping register */

    uint64_t dispontime, dispofftime;

    int     linepos, displine;
    int     vc;
    int     dispon;
    int     vsynctime;
    uint8_t video_options;

    uint8_t *vram;
} t3100e_t;

static video_timings_t timing_t3100e = { VIDEO_ISA, 8, 16, 32, 8, 16, 32 };

void    t3100e_recalctimings(t3100e_t *t3100e);
void    t3100e_write(uint32_t addr, uint8_t val, void *priv);
uint8_t t3100e_read(uint32_t addr, void *priv);
void    t3100e_recalcattrs(t3100e_t *t3100e);

void
t3100e_out(uint16_t addr, uint8_t val, void *priv)
{
    t3100e_t *t3100e = (t3100e_t *) priv;
    switch (addr) {
        /* Emulated CRTC, register select */
        case 0x3d0:
        case 0x3d2:
        case 0x3d4:
        case 0x3d6:
            cga_out(addr, val, &t3100e->cga);
            break;

        /* Emulated CRTC, value */
        case 0x3d1:
        case 0x3d3:
        case 0x3d5:
        case 0x3d7:
            /* Register 0x12 controls the attribute mappings for the
             * plasma screen. */
            if (t3100e->cga.crtcreg == 0x12) {
                t3100e->attrmap = val;
                t3100e_recalcattrs(t3100e);
                return;
            }
            cga_out(addr, val, &t3100e->cga);

            t3100e_recalctimings(t3100e);
            return;

        case CGA_REGISTER_MODE_CONTROL: /* CGA control register */
        case CGA_REGISTER_COLOR_SELECT: /* CGA colour register */
            cga_out(addr, val, &t3100e->cga);
            return;

        default:
            break;
    }
}

uint8_t
t3100e_in(uint16_t addr, void *priv)
{
    t3100e_t *t3100e = (t3100e_t *) priv;
    uint8_t   val;

    switch (addr) {
        case 0x3d1:
        case 0x3d3:
        case 0x3d5:
        case 0x3d7:
            if (t3100e->cga.crtcreg == 0x12) {
                val = t3100e->attrmap & 0x0F;
                if (t3100e->internal)
                    val |= 0x30; /* Plasma / CRT */
                return val;
            }
            break;

        default:
            break;
    }

    return cga_in(addr, &t3100e->cga);
}

void
t3100e_write(uint32_t addr, uint8_t val, void *priv)
{
    t3100e_t *t3100e = (t3100e_t *) priv;

    t3100e->vram[addr & 0x7fff] = val;
    cycles -= 4;
}

uint8_t
t3100e_read(uint32_t addr, void *priv)
{
    const t3100e_t *t3100e = (t3100e_t *) priv;

    cycles -= 4;

    return t3100e->vram[addr & 0x7fff];
}

void
t3100e_recalctimings(t3100e_t *t3100e)
{
    double disptime;
    double _dispontime;
    double _dispofftime;

    if (!t3100e->internal) {
        cga_recalctimings(&t3100e->cga);
        return;
    }
    disptime            = 651;
    _dispontime         = 640;
    _dispofftime        = disptime - _dispontime;
    t3100e->dispontime  = (uint64_t) (_dispontime * (cpuclock / VID_CLOCK) * (double) (1ULL << 32));
    t3100e->dispofftime = (uint64_t) (_dispofftime * (cpuclock / VID_CLOCK) * (double) (1ULL << 32));
}

/* Draw a row of text in 80-column mode */
void
t3100e_text_row80(t3100e_t *t3100e)
{
    uint32_t cols[2];
    uint8_t  chr;
    uint8_t  attr;
    int      drawcursor;
    int      cursorline;
    int      bold;
    int      blink;
    uint16_t addr;
    uint8_t  scanline;
    uint16_t memaddr = (t3100e->cga.crtc[CGA_CRTC_START_ADDR_LOW] | (t3100e->cga.crtc[CGA_CRTC_START_ADDR_HIGH] << 8)) & 0x7fff;
    uint16_t cursoraddr = (t3100e->cga.crtc[CGA_CRTC_CURSOR_ADDR_LOW] | (t3100e->cga.crtc[CGA_CRTC_CURSOR_ADDR_HIGH] << 8)) & 0x7fff;

    scanline   = (t3100e->displine) & 15;
    addr = ((memaddr & ~1) + (t3100e->displine >> 4) * 80) * 2;
    memaddr += (t3100e->displine >> 4) * 80;

    if ((t3100e->cga.crtc[CGA_CRTC_CURSOR_START] & 0x60) == 0x20) {
        cursorline = 0;
    } else {
        cursorline = ((t3100e->cga.crtc[CGA_CRTC_CURSOR_START] & 0x0F) * 2 <= scanline) && ((t3100e->cga.crtc[CGA_CRTC_CURSOR_END] & 0x0F) * 2 >= scanline);
    }
    for (uint8_t x = 0; x < 80; x++) {
        chr        = t3100e->vram[(addr + 2 * x) & 0x7FFF];
        attr       = t3100e->vram[(addr + 2 * x + 1) & 0x7FFF];
        drawcursor = ((memaddr == cursoraddr) && cursorline && (t3100e->cga.cgamode & CGA_MODE_FLAG_VIDEO_ENABLE) && (t3100e->cga.cgablink & 16));

        blink = ((t3100e->cga.cgablink & 16) && (t3100e->cga.cgamode & CGA_MODE_FLAG_BLINK) && (attr & 0x80) && !drawcursor);

        if (t3100e->video_options & 4)
            bold = boldcols[attr] ? chr + 256 : chr;
        else
            bold = boldcols[attr] ? chr : chr + 256;
        bold += 512 * (t3100e->video_options & 3);

        if (t3100e->cga.cgamode & CGA_MODE_FLAG_BLINK) /* Blink */
        {
            cols[1] = blinkcols[attr][1];
            cols[0] = blinkcols[attr][0];
            if (blink)
                cols[1] = cols[0];
        } else {
            cols[1] = normcols[attr][1];
            cols[0] = normcols[attr][0];
        }
        if (drawcursor) {
            for (uint8_t c = 0; c < 8; c++) {
                (buffer32->line[t3100e->displine])[(x << 3) + c] = cols[(fontdatm[bold][scanline] & (1 << (c ^ 7))) ? 1 : 0] ^ (amber ^ black);
            }
        } else {
            for (uint8_t c = 0; c < 8; c++)
                (buffer32->line[t3100e->displine])[(x << 3) + c] = cols[(fontdatm[bold][scanline] & (1 << (c ^ 7))) ? 1 : 0];
        }
        ++memaddr;
    }
}

/* Draw a row of text in 40-column mode */
void
t3100e_text_row40(t3100e_t *t3100e)
{
    uint32_t cols[2];
    int      c;
    uint8_t  chr;
    uint8_t  attr;
    int      drawcursor;
    int      cursorline;
    int      bold;
    int      blink;
    uint16_t addr;
    uint8_t  scanline;
    uint16_t memaddr = (t3100e->cga.crtc[CGA_CRTC_START_ADDR_LOW] | (t3100e->cga.crtc[CGA_CRTC_START_ADDR_HIGH] << 8)) & 0x7fff;
    uint16_t cursoraddr = (t3100e->cga.crtc[CGA_CRTC_CURSOR_ADDR_LOW] | (t3100e->cga.crtc[CGA_CRTC_CURSOR_ADDR_HIGH] << 8)) & 0x7fff;

    scanline   = (t3100e->displine) & 15;
    addr = ((memaddr & ~1) + (t3100e->displine >> 4) * 40) * 2;
    memaddr += (t3100e->displine >> 4) * 40;

    if ((t3100e->cga.crtc[CGA_CRTC_CURSOR_START] & 0x60) == 0x20) {
        cursorline = 0;
    } else {
        cursorline = ((t3100e->cga.crtc[CGA_CRTC_CURSOR_START] & 0x0F) * 2 <= scanline) && ((t3100e->cga.crtc[CGA_CRTC_CURSOR_END] & 0x0F) * 2 >= scanline);
    }
    for (uint8_t x = 0; x < 40; x++) {
        chr        = t3100e->vram[(addr + 2 * x) & 0x7FFF];
        attr       = t3100e->vram[(addr + 2 * x + 1) & 0x7FFF];
        drawcursor = ((memaddr == cursoraddr) && cursorline && (t3100e->cga.cgamode & CGA_MODE_FLAG_VIDEO_ENABLE) && (t3100e->cga.cgablink & 16));

        blink = ((t3100e->cga.cgablink & 16) && (t3100e->cga.cgamode & CGA_MODE_FLAG_BLINK) && (attr & 0x80) && !drawcursor);

        if (t3100e->video_options & 4)
            bold = boldcols[attr] ? chr + 256 : chr;
        else
            bold = boldcols[attr] ? chr : chr + 256;
        bold += 512 * (t3100e->video_options & 3);

        if (t3100e->cga.cgamode & CGA_MODE_FLAG_BLINK) /* Blink */
        {
            cols[1] = blinkcols[attr][1];
            cols[0] = blinkcols[attr][0];
            if (blink)
                cols[1] = cols[0];
        } else {
            cols[1] = normcols[attr][1];
            cols[0] = normcols[attr][0];
        }
        if (drawcursor) {
            for (c = 0; c < 8; c++) {
                (buffer32->line[t3100e->displine])[(x << 4) + c * 2] = (buffer32->line[t3100e->displine])[(x << 4) + c * 2 + 1] = cols[(fontdatm[bold][scanline] & (1 << (c ^ 7))) ? 1 : 0] ^ (amber ^ black);
            }
        } else {
            for (c = 0; c < 8; c++) {
                (buffer32->line[t3100e->displine])[(x << 4) + c * 2] = (buffer32->line[t3100e->displine])[(x << 4) + c * 2 + 1] = cols[(fontdatm[bold][scanline] & (1 << (c ^ 7))) ? 1 : 0];
            }
        }
        ++memaddr;
    }
}

/* Draw a line in CGA 640x200 or T3100e 640x400 mode */
void
t3100e_cgaline6(t3100e_t *t3100e)
{
    uint8_t  dat;
    uint32_t ink = 0;
    uint16_t addr;
    uint32_t fg = (t3100e->cga.cgacol & 0x0F) ? amber : black;
    uint32_t bg = black;

    uint16_t memaddr = (t3100e->cga.crtc[CGA_CRTC_START_ADDR_LOW] | (t3100e->cga.crtc[CGA_CRTC_START_ADDR_HIGH] << 8)) & 0x7fff;

    if (t3100e->cga.crtc[CGA_CRTC_MAX_SCANLINE_ADDR] == 3) /* 640*400 */
    {
        addr = ((t3100e->displine) & 1) * 0x2000 + ((t3100e->displine >> 1) & 1) * 0x4000 + (t3100e->displine >> 2) * 80 + ((memaddr & ~1) << 1);
    } else {
        addr = ((t3100e->displine >> 1) & 1) * 0x2000 + (t3100e->displine >> 2) * 80 + ((memaddr & ~1) << 1);
    }
    for (uint8_t x = 0; x < 80; x++) {
        dat = t3100e->vram[addr & 0x7FFF];
        addr++;

        for (uint8_t c = 0; c < 8; c++) {
            ink = (dat & 0x80) ? fg : bg;
            if (!(t3100e->cga.cgamode & CGA_MODE_FLAG_VIDEO_ENABLE))
                ink = black;
            (buffer32->line[t3100e->displine])[x * 8 + c] = ink;
            dat                                           = dat << 1;
        }
    }
}

/* Draw a line in CGA 320x200 mode. Here the CGA colours are converted to
 * dither patterns: colour 1 to 25% grey, colour 2 to 50% grey */
void
t3100e_cgaline4(t3100e_t *t3100e)
{
    uint8_t  dat;
    uint8_t  pattern;
    uint32_t ink0 = 0;
    uint32_t ink1 = 0;
    uint16_t addr;

    uint16_t memaddr = (t3100e->cga.crtc[CGA_CRTC_START_ADDR_LOW] | (t3100e->cga.crtc[CGA_CRTC_START_ADDR_HIGH] << 8)) & 0x7fff;
    if (t3100e->cga.crtc[CGA_CRTC_MAX_SCANLINE_ADDR] == 3) /* 320*400 undocumented */
    {
        addr = ((t3100e->displine) & 1) * 0x2000 + ((t3100e->displine >> 1) & 1) * 0x4000 + (t3100e->displine >> 2) * 80 + ((memaddr & ~1) << 1);
    } else /* 320*200 */
    {
        addr = ((t3100e->displine >> 1) & 1) * 0x2000 + (t3100e->displine >> 2) * 80 + ((memaddr & ~1) << 1);
    }
    for (uint8_t x = 0; x < 80; x++) {
        dat = t3100e->vram[addr & 0x7FFF];
        addr++;

        for (uint8_t c = 0; c < 4; c++) {
            pattern = (dat & 0xC0) >> 6;
            if (!(t3100e->cga.cgamode & CGA_MODE_FLAG_VIDEO_ENABLE))
                pattern = 0;

            switch (pattern & 3) {
                case 0:
                    ink0 = ink1 = black;
                    break;
                case 1:
                    if (t3100e->displine & 1) {
                        ink0 = black;
                        ink1 = black;
                    } else {
                        ink0 = amber;
                        ink1 = black;
                    }
                    break;
                case 2:
                    if (t3100e->displine & 1) {
                        ink0 = black;
                        ink1 = amber;
                    } else {
                        ink0 = amber;
                        ink1 = black;
                    }
                    break;
                case 3:
                    ink0 = ink1 = amber;
                    break;

                default:
                    break;
            }
            (buffer32->line[t3100e->displine])[x * 8 + 2 * c]     = ink0;
            (buffer32->line[t3100e->displine])[x * 8 + 2 * c + 1] = ink1;
            dat                                                   = dat << 2;
        }
    }
}

void
t3100e_poll(void *priv)
{
    t3100e_t *t3100e = (t3100e_t *) priv;

    if (t3100e->video_options != st_video_options) {
        t3100e->video_options = st_video_options;

        if (t3100e->video_options & 8) /* Disable internal CGA */
            mem_mapping_disable(&t3100e->mapping);
        else
            mem_mapping_enable(&t3100e->mapping);

        /* Set the font used for the external display */
        t3100e->cga.fontbase = (512 * (t3100e->video_options & 3))
            + ((t3100e->video_options & 4) ? 256 : 0);
    }
    /* Switch between internal plasma and external CRT display. */
    if (st_display_internal != -1 && st_display_internal != t3100e->internal) {
        t3100e->internal = st_display_internal;
        t3100e_recalctimings(t3100e);
    }
    if (!t3100e->internal) {
        cga_poll(&t3100e->cga);
        return;
    }

    if (!t3100e->linepos) {
        timer_advance_u64(&t3100e->cga.timer, t3100e->dispofftime);
        t3100e->cga.cgastat |= 1;
        t3100e->linepos = 1;
        if (t3100e->dispon) {
            if (t3100e->displine == 0) {
                video_wait_for_buffer();
            }

            /* Graphics */
            if (t3100e->cga.cgamode & CGA_MODE_FLAG_GRAPHICS) {
                if (t3100e->cga.cgamode & CGA_MODE_FLAG_HIGHRES_GRAPHICS)
                    t3100e_cgaline6(t3100e);
                else
                    t3100e_cgaline4(t3100e);
            } else if (t3100e->cga.cgamode & CGA_MODE_FLAG_HIGHRES) /* High-res text */
            {
                t3100e_text_row80(t3100e);
            } else {
                t3100e_text_row40(t3100e);
            }
        }
        t3100e->displine++;
        /* Hardcode a fixed refresh rate and VSYNC timing */
        if (t3100e->displine == 400) /* Start of VSYNC */
        {
            t3100e->cga.cgastat |= 8;
            t3100e->dispon = 0;
        }
        if (t3100e->displine == 416) /* End of VSYNC */
        {
            t3100e->displine = 0;
            t3100e->cga.cgastat &= ~8;
            t3100e->dispon = 1;
        }
    } else {
        if (t3100e->dispon) {
            t3100e->cga.cgastat &= ~1;
        }
        timer_advance_u64(&t3100e->cga.timer, t3100e->dispontime);
        t3100e->linepos = 0;

        if (t3100e->displine == 400) {
            /* Hardcode 640x400 window size */
            if ((T3100E_XSIZE != xsize) || (T3100E_YSIZE != ysize) || video_force_resize_get()) {
                xsize = T3100E_XSIZE;
                ysize = T3100E_YSIZE;
                if (xsize < 64)
                    xsize = 656;
                if (ysize < 32)
                    ysize = 200;
                set_screen_size(xsize, ysize);

                if (video_force_resize_get())
                    video_force_resize_set(0);
            }
            video_blit_memtoscreen(0, 0, xsize, ysize);

            frames++;
            /* Fixed 640x400 resolution */
            video_res_x = T3100E_XSIZE;
            video_res_y = T3100E_YSIZE;

            if (t3100e->cga.cgamode & CGA_MODE_FLAG_GRAPHICS) {
                if (t3100e->cga.cgamode & CGA_MODE_FLAG_HIGHRES_GRAPHICS)
                    video_bpp = 1;
                else
                    video_bpp = 2;

            } else
                video_bpp = 0;
            t3100e->cga.cgablink++;
        }
    }
}

void
t3100e_recalcattrs(t3100e_t *t3100e)
{
    int n;

    /* val behaves as follows:
     *     Bit 0: Attributes 01-06, 08-0E are inverse video
     *     Bit 1: Attributes 01-06, 08-0E are bold
     *     Bit 2: Attributes 11-16, 18-1F, 21-26, 28-2F ... F1-F6, F8-FF
     *           are inverse video
     *     Bit 3: Attributes 11-16, 18-1F, 21-26, 28-2F ... F1-F6, F8-FF
     *           are bold */

    /* Set up colours */
    amber = makecol(0xf7, 0x7C, 0x34);
    black = makecol(0x17, 0x0C, 0x00);

    /* Initialise the attribute mapping. Start by defaulting everything
     * to black on amber, and with bold set by bit 3 */
    for (n = 0; n < 256; n++) {
        boldcols[n]     = (n & 8) != 0;
        blinkcols[n][0] = normcols[n][0] = amber;
        blinkcols[n][1] = normcols[n][1] = black;
    }

    /* Colours 0x11-0xFF are controlled by bits 2 and 3 of the
     * passed value. Exclude x0 and x8, which are always black on
     * amber. */
    for (n = 0x11; n <= 0xFF; n++) {
        if ((n & 7) == 0)
            continue;
        if (t3100e->attrmap & 4) /* Inverse */
        {
            blinkcols[n][0] = normcols[n][0] = amber;
            blinkcols[n][1] = normcols[n][1] = black;
        } else /* Normal */
        {
            blinkcols[n][0] = normcols[n][0] = black;
            blinkcols[n][1] = normcols[n][1] = amber;
        }
        if (t3100e->attrmap & 8)
            boldcols[n] = 1; /* Bold */
    }
    /* Set up the 01-0E range, controlled by bits 0 and 1 of the
     * passed value. When blinking is enabled this also affects 81-8E. */
    for (n = 0x01; n <= 0x0E; n++) {
        if (n == 7)
            continue;
        if (t3100e->attrmap & 1) {
            blinkcols[n][0] = normcols[n][0] = amber;
            blinkcols[n][1] = normcols[n][1] = black;
            blinkcols[n + 128][0]            = amber;
            blinkcols[n + 128][1]            = black;
        } else {
            blinkcols[n][0] = normcols[n][0] = black;
            blinkcols[n][1] = normcols[n][1] = amber;
            blinkcols[n + 128][0]            = black;
            blinkcols[n + 128][1]            = amber;
        }
        if (t3100e->attrmap & 2)
            boldcols[n] = 1;
    }
    /* Colours 07 and 0F are always amber on black. If blinking is
     * enabled so are 87 and 8F. */
    for (n = 0x07; n <= 0x0F; n += 8) {
        blinkcols[n][0] = normcols[n][0] = black;
        blinkcols[n][1] = normcols[n][1] = amber;
        blinkcols[n + 128][0]            = black;
        blinkcols[n + 128][1]            = amber;
    }
    /* When not blinking, colours 81-8F are always amber on black. */
    for (n = 0x81; n <= 0x8F; n++) {
        normcols[n][0] = black;
        normcols[n][1] = amber;
        boldcols[n]    = (n & 0x08) != 0;
    }

    /* Finally do the ones which are solid black. These differ between
     * the normal and blinking mappings */
    for (n = 0; n <= 0xFF; n += 0x11) {
        normcols[n][0] = normcols[n][1] = black;
    }
    /* In the blinking range, 00 11 22 .. 77 and 80 91 A2 .. F7 are black */
    for (n = 0; n <= 0x77; n += 0x11) {
        blinkcols[n][0] = blinkcols[n][1] = black;
        blinkcols[n + 128][0] = blinkcols[n + 128][1] = black;
    }
}

void *
t3100e_init(UNUSED(const device_t *info))
{
    t3100e_t *t3100e = calloc(1, sizeof(t3100e_t));
    video_load_font("roms/machines/t3100e/t3100e_font.bin", FONT_FORMAT_TOSHIBA_3100E, LOAD_FONT_NO_OFFSET);
    cga_init(&t3100e->cga);
    video_inform(VIDEO_FLAG_TYPE_CGA, &timing_t3100e);

    t3100e->internal = 1;

    /* 32k video RAM */
    t3100e->vram = calloc(1, 0x8000);

    timer_set_callback(&t3100e->cga.timer, t3100e_poll);
    timer_set_p(&t3100e->cga.timer, t3100e);

    /* Occupy memory between 0xB8000 and 0xBFFFF */
    mem_mapping_add(&t3100e->mapping, 0xb8000, 0x8000, t3100e_read, NULL, NULL, t3100e_write, NULL, NULL, NULL, 0, t3100e);
    /* Respond to CGA I/O ports */
    io_sethandler(0x03d0, 0x000c, t3100e_in, NULL, NULL, t3100e_out, NULL, NULL, t3100e);

    /* Default attribute mapping is 4 */
    t3100e->attrmap = 4;
    t3100e_recalcattrs(t3100e);

    /* Start off in 80x25 text mode */
    t3100e->cga.cgastat   = 0xF4;
    t3100e->cga.vram      = t3100e->vram;
    t3100e->enabled       = 1;
    t3100e->video_options = 0xFF;
    return t3100e;
}

void
t3100e_close(void *priv)
{
    t3100e_t *t3100e = (t3100e_t *) priv;

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

void
t3100e_speed_changed(void *priv)
{
    t3100e_t *t3100e = (t3100e_t *) priv;

    t3100e_recalctimings(t3100e);
}

const device_t t3100e_device = {
    .name          = "Toshiba T3100e",
    .internal_name = "t3100e",
    .flags         = 0,
    .local         = 0,
    .init          = t3100e_init,
    .close         = t3100e_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = t3100e_speed_changed,
    .force_redraw  = NULL,
    .config        = NULL
};
