/*
 * 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 T1000 and T1200 portables.
 *
 *          The T1000 is the T3100e's little brother -- a real laptop
 *          with a rechargeable battery.
 *
 *          Features: 80C88 at 4.77MHz
 *          - 512k system RAM
 *          - 640x200 monochrome LCD
 *          - 82-key keyboard
 *          - Real-time clock. Not the normal 146818, but a TC8521,
 *             which is a 4-bit chip.
 *          - A ROM drive (128k, 256k or 512k) which acts as a mini
 *            hard drive and contains a copy of DOS 2.11.
 *          - 160 bytes of non-volatile RAM for the CONFIG.SYS used
 *            when booting from the ROM drive. Possibly physically
 *            located in the keyboard controller RAM.
 *
 *          An optional memory expansion board can be fitted. This adds
 *          768k of RAM, which can be used for up to three purposes:
 *          > Conventional memory -- 128k between 512k and 640k
 *          > HardRAM -- a battery-backed RAM drive.
 *          > EMS
 *
 *          This means that there are up to three different
 *          implementations of non-volatile RAM in the same computer
 *          (52 nibbles in the TC8521, 160 bytes of CONFIG.SYS, and
 *          up to 768k of HardRAM).
 *
 *          The T1200 is a slightly upgraded version with a turbo mode
 *          (double CPU clock, 9.54MHz) and an optional hard drive.
 *          The interface for this is proprietary both at the physical
 *          and programming level.
 *
 *          01F2h: If hard drive is present, low 4 bits are 0Ch [20Mb]
 *          or 0Dh [10Mb].
 *
 *          The hard drive is a 20MB (615/2/26) RLL 3.5" drive.
 *
 *          The TC8521 is a 4-bit RTC, so each memory location can only
 *          hold a single BCD digit. Hence everything has 'ones' and
 *          'tens' digits.
 *
 * NOTE:    Still need to figure out a way to load/save ConfigSys and
 *          HardRAM stuff. Needs to be linked in to the NVR code.
 *
 * Authors: Fred N. van Kempen, <decwiz@yahoo.com>
 *          Miran Grca, <mgrca8@gmail.com>
 *          John Elliott, <jce@seasip.info>
 *
 *          Copyright 2018-2019 Fred N. van Kempen.
 *          Copyright 2018-2019 Miran Grca.
 *          Copyright 2018-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>
#include <time.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include "cpu.h"
#include <86box/io.h>
#include <86box/timer.h>
#include <86box/pit.h>
#include <86box/nmi.h>
#include <86box/mem.h>
#include <86box/rom.h>
#include <86box/device.h>
#include <86box/nvr.h>
#include <86box/keyboard.h>
#include <86box/lpt.h>
#include <86box/mem.h>
#include <86box/fdd.h>
#include <86box/fdc.h>
#include <86box/gameport.h>
#include <86box/hdc.h>
#include <86box/video.h>
#include <86box/plat.h>
#include <86box/machine.h>
#include <86box/m_xt_t1000.h>

#define T1000_ROMSIZE (512 * 1024UL) /* Maximum ROM drive size is 512k */

enum TC8521_ADDR {
    /* Page 0 registers */
    TC8521_SECOND1 = 0,
    TC8521_SECOND10,
    TC8521_MINUTE1,
    TC8521_MINUTE10,
    TC8521_HOUR1,
    TC8521_HOUR10,
    TC8521_WEEKDAY,
    TC8521_DAY1,
    TC8521_DAY10,
    TC8521_MONTH1,
    TC8521_MONTH10,
    TC8521_YEAR1,
    TC8521_YEAR10,
    TC8521_PAGE,  /* PAGE register */
    TC8521_TEST,  /* TEST register */
    TC8521_RESET, /* RESET register */

    /* Page 1 registers */
    TC8521_24HR     = 0x1A,
    TC8521_LEAPYEAR = 0x1B
};

typedef struct t1000_t {
    /* ROM drive */
    uint8_t      *romdrive;
    uint8_t       rom_ctl;
    uint32_t      rom_offset;
    mem_mapping_t rom_mapping;

    /* CONFIG.SYS drive. */
    uint8_t t1000_nvram[160];
    uint8_t t1200_nvram[2048];

    /* System control registers */
    uint8_t sys_ctl[16];
    uint8_t syskeys;
    uint8_t turbo;

    /* NVRAM control */
    uint8_t       nvr_c0;
    uint8_t       nvr_tick;
    int           nvr_addr;
    uint8_t       nvr_active;
    mem_mapping_t nvr_mapping; /* T1200 NVRAM mapping */

    /* EMS data */
    uint8_t       ems_reg[4];
    mem_mapping_t mapping[4];
    uint32_t      page_exec[4];
    uint8_t       ems_port_index;
    uint16_t      ems_port;
    uint8_t       is_640k;
    uint32_t      ems_base;
    int32_t       ems_pages;

    fdc_t *fdc;

    nvr_t nvr;
    int   is_t1200;
} t1000_t;

static t1000_t t1000;

#ifdef ENABLE_T1000_LOG
int t1000_do_log = ENABLE_T1000_LOG;

static void
t1000_log(const char *fmt, ...)
{
    va_list ap;

    if (t1000_do_log) {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#else
#    define t1000_log(fmt, ...)
#endif

/* Set the chip time. */
static void
tc8521_time_set(uint8_t *regs, struct tm *tm)
{
    regs[TC8521_SECOND1]  = (tm->tm_sec % 10);
    regs[TC8521_SECOND10] = (tm->tm_sec / 10);
    regs[TC8521_MINUTE1]  = (tm->tm_min % 10);
    regs[TC8521_MINUTE10] = (tm->tm_min / 10);
    if (regs[TC8521_24HR] & 0x01) {
        regs[TC8521_HOUR1]  = (tm->tm_hour % 10);
        regs[TC8521_HOUR10] = (tm->tm_hour / 10);
    } else {
        regs[TC8521_HOUR1]  = ((tm->tm_hour % 12) % 10);
        regs[TC8521_HOUR10] = (((tm->tm_hour % 12) / 10) | ((tm->tm_hour >= 12) ? 2 : 0));
    }
    regs[TC8521_WEEKDAY] = tm->tm_wday;
    regs[TC8521_DAY1]    = (tm->tm_mday % 10);
    regs[TC8521_DAY10]   = (tm->tm_mday / 10);
    regs[TC8521_MONTH1]  = ((tm->tm_mon + 1) % 10);
    regs[TC8521_MONTH10] = ((tm->tm_mon + 1) / 10);
    regs[TC8521_YEAR1]   = ((tm->tm_year - 80) % 10);
    regs[TC8521_YEAR10]  = (((tm->tm_year - 80) % 100) / 10);
}

/* Get the chip time. */
#define nibbles(a) (regs[(a##1)] + 10 * regs[(a##10)])
static void
tc8521_time_get(uint8_t *regs, struct tm *tm)
{
    tm->tm_sec = nibbles(TC8521_SECOND);
    tm->tm_min = nibbles(TC8521_MINUTE);
    if (regs[TC8521_24HR] & 0x01)
        tm->tm_hour = nibbles(TC8521_HOUR);
    else
        tm->tm_hour = ((nibbles(TC8521_HOUR) % 12) + (regs[TC8521_HOUR10] & 0x02) ? 12 : 0);
    tm->tm_wday = regs[TC8521_WEEKDAY];
    tm->tm_mday = nibbles(TC8521_DAY);
    tm->tm_mon  = (nibbles(TC8521_MONTH) - 1);
    tm->tm_year = (nibbles(TC8521_YEAR) + 1980);
}

/* This is called every second through the NVR/RTC hook. */
static void
tc8521_tick(UNUSED(nvr_t *nvr))
{
    t1000_log("TC8521: ping\n");
}

static void
tc8521_start(nvr_t *nvr)
{
    struct tm tm;

    /* Initialize the internal and chip times. */
    if (time_sync & TIME_SYNC_ENABLED) {
        /* Use the internal clock's time. */
        nvr_time_get(&tm);
        tc8521_time_set(nvr->regs, &tm);
    } else {
        /* Set the internal clock from the chip time. */
        tc8521_time_get(nvr->regs, &tm);
        nvr_time_set(&tm);
    }

#if 0
    /* Start the RTC - BIOS will do this. */
    nvr->regs[TC8521_PAGE] |= 0x80;
#endif
}

/* Write to one of the chip registers. */
static void
tc8521_write(uint16_t addr, uint8_t val, void *priv)
{
    nvr_t  *nvr = (nvr_t *) priv;
    uint8_t page;

    /* Get to the correct register page. */
    addr &= 0x0f;
    page = nvr->regs[0x0d] & 0x03;
    if (addr < 0x0d)
        addr += (16 * page);

    if (addr >= 0x10 && nvr->regs[addr] != val)
        nvr_dosave = 1;

    /* Store the new value. */
    nvr->regs[addr] = val;
}

/* Read from one of the chip registers. */
static uint8_t
tc8521_read(uint16_t addr, void *priv)
{
    const nvr_t *nvr = (nvr_t *) priv;
    uint8_t      page;

    /* Get to the correct register page. */
    addr &= 0x0f;
    page = nvr->regs[0x0d] & 0x03;
    if (addr < 0x0d)
        addr += (16 * page);

    /* Grab and return the desired value. */
    return (nvr->regs[addr]);
}

/* Reset the 8521 to a default state. */
static void
tc8521_reset(nvr_t *nvr)
{
    /* Clear the NVRAM. */
    memset(nvr->regs, 0xff, nvr->size);

    /* Reset the RTC registers. */
    memset(nvr->regs, 0x00, 16);
    nvr->regs[TC8521_WEEKDAY] = 0x01;
    nvr->regs[TC8521_DAY1]    = 0x01;
    nvr->regs[TC8521_MONTH1]  = 0x01;
}

static void
tc8521_init(nvr_t *nvr, int size)
{
    /* This is machine specific. */
    nvr->size = size;
    nvr->irq  = -1;

    /* Set up any local handlers here. */
    nvr->reset = tc8521_reset;
    nvr->start = tc8521_start;
    nvr->tick  = tc8521_tick;

    /* Initialize the actual NVR. */
    nvr_init(nvr);

    io_sethandler(0x02c0, 16,
                  tc8521_read, NULL, NULL, tc8521_write, NULL, NULL, nvr);
}

/* Given an EMS page ID, return its physical address in RAM. */
static uint32_t
ems_execaddr(t1000_t *sys, UNUSED(int pg), uint16_t val)
{
    if (!(val & 0x80))
        return 0; /* Bit 7 reset => not mapped */
    if (!sys->ems_pages)
        return 0; /* No EMS available: all used by
                   * HardRAM or conventional RAM */
    val &= 0x7f;

#if 0
    t1000_log("Select EMS page: %d of %d\n", val, sys->ems_pages);
#endif
    if (val < sys->ems_pages) {
        /* EMS is any memory above 512k,
           with ems_base giving the start address */
        return ((512 * 1024) + (sys->ems_base * 0x10000) + (0x4000 * val));
    }

    return 0;
}

static uint8_t
ems_in(uint16_t addr, void *priv)
{
    const t1000_t *sys = (t1000_t *) priv;

#if 0
    t1000_log("ems_in(%04x)=%02x\n", addr, sys->ems_reg[(addr >> 14) & 3]);
#endif
    return (sys->ems_reg[(addr >> 14) & 3]);
}

static void
ems_out(uint16_t addr, uint8_t val, void *priv)
{
    t1000_t *sys = (t1000_t *) priv;
    int      pg  = (addr >> 14) & 3;

#if 0
    t1000_log("ems_out(%04x, %02x) pg=%d\n", addr, val, pg);
#endif
    sys->ems_reg[pg]   = val;
    sys->page_exec[pg] = ems_execaddr(sys, pg, val);
    if (sys->page_exec[pg]) {
        /* Page present */
        mem_mapping_enable(&sys->mapping[pg]);
        mem_mapping_set_exec(&sys->mapping[pg], ram + sys->page_exec[pg]);
    } else {
        mem_mapping_disable(&sys->mapping[pg]);
    }
}

/* Hardram size is in 64k units */
static void
ems_set_hardram(t1000_t *sys, uint8_t val)
{
    val &= 0x1f; /* Mask off pageframe address */
    if (val && mem_size > 512)
        sys->ems_base = val;
    else
        sys->ems_base = 0;

#if 0
    t1000_log("EMS base set to %02x\n", val);
#endif
    sys->ems_pages = ((mem_size - 512) / 16) - 4 * sys->ems_base;
    if (sys->ems_pages < 0)
        sys->ems_pages = 0;

    /* Recalculate EMS mappings */
    for (uint8_t n = 0; n < 4; n++)
        ems_out(n << 14, sys->ems_reg[n], sys);
}

static void
ems_set_640k(t1000_t *sys, uint8_t val)
{
    if (val && mem_size >= 640) {
        mem_mapping_set_addr(&ram_low_mapping, 0, 640 * 1024);
        sys->is_640k = 1;
    } else {
        mem_mapping_set_addr(&ram_low_mapping, 0, 512 * 1024);
        sys->is_640k = 0;
    }
}

static void
ems_set_port(t1000_t *sys, uint8_t val)
{
    int n;

#if 0
    t1000_log("ems_set_port(%d)", val & 0x0f);
#endif
    if (sys->ems_port) {
        for (n = 0; n <= 0xc000; n += 0x4000) {
            io_removehandler(sys->ems_port + n, 1,
                             ems_in, NULL, NULL, ems_out, NULL, NULL, sys);
        }
        sys->ems_port = 0;
    }

    val &= 0x0f;
    sys->ems_port_index = val;
    if (val == 7) {
        /* No EMS */
        sys->ems_port = 0;
    } else {
        sys->ems_port = 0x208 | (val << 4);
        for (n = 0; n <= 0xc000; n += 0x4000) {
            io_sethandler(sys->ems_port + n, 1,
                          ems_in, NULL, NULL, ems_out, NULL, NULL, sys);
        }
        sys->ems_port = 0;
    }

#if 0
    t1000_log(" -> %04x\n", sys->ems_port);
#endif
}

static int
addr_to_page(uint32_t addr)
{
    return ((addr - 0xd0000) / 0x4000);
}

/* Read RAM in the EMS page frame. */
static uint8_t
ems_read_ram(uint32_t addr, void *priv)
{
    const t1000_t *sys = (t1000_t *) priv;
    int            pg  = addr_to_page(addr);

    if (pg < 0)
        return 0xff;
    addr = sys->page_exec[pg] + (addr & 0x3fff);

    return (ram[addr]);
}

static uint16_t
ems_read_ramw(uint32_t addr, void *priv)
{
    const t1000_t *sys = (t1000_t *) priv;
    int            pg  = addr_to_page(addr);

    if (pg < 0)
        return 0xff;

#if 0
    t1000_log("ems_read_ramw addr=%05x ", addr);
#endif
    addr = sys->page_exec[pg] + (addr & 0x3FFF);

#if 0
    t1000_log("-> %06x val=%04x\n", addr, *(uint16_t *)&ram[addr]);
#endif

    return (*(uint16_t *) &ram[addr]);
}

static uint32_t
ems_read_raml(uint32_t addr, void *priv)
{
    const t1000_t *sys = (t1000_t *) priv;
    int            pg  = addr_to_page(addr);

    if (pg < 0)
        return 0xff;
    addr = sys->page_exec[pg] + (addr & 0x3fff);

    return (*(uint32_t *) &ram[addr]);
}

/* Write RAM in the EMS page frame. */
static void
ems_write_ram(uint32_t addr, uint8_t val, void *priv)
{
    const t1000_t *sys = (t1000_t *) priv;
    int            pg  = addr_to_page(addr);

    if (pg < 0)
        return;

    addr = sys->page_exec[pg] + (addr & 0x3fff);
    if (ram[addr] != val)
        nvr_dosave = 1;

    ram[addr] = val;
}

static void
ems_write_ramw(uint32_t addr, uint16_t val, void *priv)
{
    const t1000_t *sys = (t1000_t *) priv;
    int            pg  = addr_to_page(addr);

    if (pg < 0)
        return;

#if 0
    t1000_log("ems_write_ramw addr=%05x ", addr);
#endif
    addr = sys->page_exec[pg] + (addr & 0x3fff);

#if 0
    t1000_log("-> %06x val=%04x\n", addr, val);
#endif

    if (*(uint16_t *) &ram[addr] != val)
        nvr_dosave = 1;

    *(uint16_t *) &ram[addr] = val;
}

static void
ems_write_raml(uint32_t addr, uint32_t val, void *priv)
{
    const t1000_t *sys = (t1000_t *) priv;
    int            pg  = addr_to_page(addr);

    if (pg < 0)
        return;

    addr = sys->page_exec[pg] + (addr & 0x3fff);
    if (*(uint32_t *) &ram[addr] != val)
        nvr_dosave = 1;

    *(uint32_t *) &ram[addr] = val;
}

static uint8_t
read_ctl(uint16_t addr, void *priv)
{
    const t1000_t *sys = (t1000_t *) priv;
    uint8_t        ret = 0xff;

    switch (addr & 0x0f) {
        case 1:
            ret = sys->syskeys;
            break;

        case 0x0f: /* Detect EMS board */
            switch (sys->sys_ctl[0x0e]) {
                case 0x50:
                    if (mem_size > 512)
                        ret = (0x90 | sys->ems_port_index);
                    break;

                case 0x51:
                    /* 0x60 is the page frame address:
                       (0xd000 - 0xc400) / 0x20 */
                    ret = (sys->ems_base | 0x60);
                    break;

                case 0x52:
                    ret = (sys->is_640k ? 0x80 : 0);
                    break;

                default:
                    break;
            }
            break;

        default:
            ret = (sys->sys_ctl[addr & 0x0f]);
    }

    return ret;
}

static void
t1200_turbo_set(uint8_t value)
{
    if (value == t1000.turbo)
        return;

    t1000.turbo = value;
    if (!value)
        cpu_dynamic_switch(0);
    else
        cpu_dynamic_switch(cpu);
}

static void
write_ctl(uint16_t addr, uint8_t val, void *priv)
{
    t1000_t *sys = (t1000_t *) priv;

    sys->sys_ctl[addr & 0x0f] = val;
    switch (addr & 0x0f) {
        case 4: /* Video control */
            if (sys->sys_ctl[3] == 0x5A) {
                t1000_video_options_set((val & 0x20) ? 1 : 0);
                t1000_display_set((val & 0x40) ? 0 : 1);
                if (sys->is_t1200)
                    t1200_turbo_set((val & 0x80) ? 1 : 0);
            }
            break;

        /* It looks as if the T1200, like the T3100, can disable
         * its builtin video chipset if it detects the presence of
         * another video card. */
        case 6:
            if (sys->is_t1200) {
                t1000_video_enable(val & 0x01 ? 0 : 1);
            }
            break;

        case 0x0f: /* EMS control */
            switch (sys->sys_ctl[0x0e]) {
                case 0x50:
                    ems_set_port(sys, val);
                    break;

                case 0x51:
                    ems_set_hardram(sys, val);
                    break;

                case 0x52:
                    ems_set_640k(sys, val);
                    break;

                default:
                    break;
            }
            break;

        default:
            break;
    }
}

/* Ports 0xC0 to 0xC3 appear to have two purposes:
 *
 * > Access to the 160 bytes of non-volatile RAM containing CONFIG.SYS
 * > Reading the floppy changeline. I don't know why the Toshiba doesn't
 *   use the normal port 0x3F7 for this, but it doesn't.
 *
 */
static uint8_t
t1000_read_nvram(uint16_t addr, void *priv)
{
    t1000_t *sys = (t1000_t *) priv;
    uint8_t  tmp = 0xff;

    switch (addr) {
        case 0xc2: /* Read next byte from NVRAM */
            if (sys->nvr_addr >= 0 && sys->nvr_addr < 160)
                tmp = sys->t1000_nvram[sys->nvr_addr];
            sys->nvr_addr++;
            break;

        case 0xc3: /* Read floppy changeline and NVRAM ready state */
            tmp = fdc_read(0x03f7, t1000.fdc);

            tmp = (tmp & 0x80) >> 3;              /* Bit 4 is changeline */
            tmp |= (sys->nvr_active & 0xc0);      /* Bits 6, 7 are r/w mode */
            tmp |= 0x2e;                          /* Bits 5, 3, 2, 1 always 1 */
            tmp |= (sys->nvr_active & 0x40) >> 6; /* Ready state */
            break;

        default:
            break;
    }

    return tmp;
}

static void
t1000_write_nvram(uint16_t addr, uint8_t val, void *priv)
{
    t1000_t *sys = (t1000_t *) priv;

    /*
     * On the real T1000, port 0xC1 is only usable as the high byte
     * of a 16-bit write to port 0xC0, with 0x5A in the low byte.
     */
    switch (addr) {
        case 0xc0:
            sys->nvr_c0 = val;
            break;

        case 0xc1: /* Write next byte to NVRAM */
            if (sys->nvr_addr >= 0 && sys->nvr_addr < 160) {
                if (sys->t1000_nvram[sys->nvr_addr] != val)
                    nvr_dosave = 1;
                sys->t1000_nvram[sys->nvr_addr] = val;
            }
            sys->nvr_addr++;
            break;

        case 0xc2:
            break;

        case 0xc3:
            /*
             * At start of NVRAM read / write, 0x80 is written to
             * port 0xC3. This seems to reset the NVRAM address
             * counter. A single byte is then written (0xff for
             * write, 0x00 for read) which appears to be ignored.
             * Simulate that by starting the address counter off
             * at -1.
             */
            sys->nvr_active = val;
            if (val == 0x80)
                sys->nvr_addr = -1;
            break;

        default:
            break;
    }
}

static uint8_t
read_t1200_nvram(uint32_t addr, void *priv)
{
    const t1000_t *sys = (t1000_t *) priv;

    return sys->t1200_nvram[addr & 0x7FF];
}

static void
write_t1200_nvram(uint32_t addr, uint8_t value, void *priv)
{
    t1000_t *sys = (t1000_t *) priv;

    if (sys->t1200_nvram[addr & 0x7FF] != value)
        nvr_dosave = 1;

    sys->t1200_nvram[addr & 0x7FF] = value;
}

/* Port 0xC8 controls the ROM drive */
static uint8_t
t1000_read_rom_ctl(UNUSED(uint16_t addr), void *priv)
{
    const t1000_t *sys = (t1000_t *) priv;

    return (sys->rom_ctl);
}

static void
t1000_write_rom_ctl(UNUSED(uint16_t addr), uint8_t val, void *priv)
{
    t1000_t *sys = (t1000_t *) priv;

    sys->rom_ctl = val;
    if (sys->romdrive && (val & 0x80)) {
        /* Enable */
        sys->rom_offset = ((val & 0x7f) * 0x10000) % T1000_ROMSIZE;
        mem_mapping_set_addr(&sys->rom_mapping, 0xa0000, 0x10000);
        mem_mapping_set_exec(&sys->rom_mapping, sys->romdrive + sys->rom_offset);
        mem_mapping_enable(&sys->rom_mapping);
    } else {
        mem_mapping_disable(&sys->rom_mapping);
    }
}

/* Read the ROM drive */
static uint8_t
t1000_read_rom(uint32_t addr, void *priv)
{
    const t1000_t *sys = (t1000_t *) priv;

    if (!sys->romdrive)
        return 0xff;

    return (sys->romdrive[sys->rom_offset + (addr & 0xffff)]);
}

static uint16_t
t1000_read_romw(uint32_t addr, void *priv)
{
    t1000_t *sys = (t1000_t *) priv;

    if (!sys->romdrive)
        return 0xffff;

    return (*(uint16_t *) (&sys->romdrive[sys->rom_offset + (addr & 0xffff)]));
}

static uint32_t
t1000_read_roml(uint32_t addr, void *priv)
{
    t1000_t *sys = (t1000_t *) priv;

    if (!sys->romdrive)
        return 0xffffffff;

    return (*(uint32_t *) (&sys->romdrive[sys->rom_offset + (addr & 0xffff)]));
}

int
machine_xt_t1000_init(const machine_t *model)
{
    FILE *fp;
    int   ret;

    ret = bios_load_linear("roms/machines/t1000/t1000.rom",
                           0x000f8000, 32768, 0);

    if (bios_only || !ret)
        return ret;

    memset(&t1000, 0x00, sizeof(t1000));
    t1000.is_t1200       = 0;
    t1000.turbo          = 0xff;
    t1000.ems_port_index = 7; /* EMS disabled */

    /* Load the T1000 CGA Font ROM. */
    video_load_font("roms/machines/t1000/t1000font.bin", FONT_FORMAT_CGA, LOAD_FONT_NO_OFFSET);

    /*
     * The ROM drive is optional.
     *
     * If the file is missing, continue to boot; the BIOS will
     * complain 'No ROM drive' but boot normally from floppy.
     */
    fp = rom_fopen("roms/machines/t1000/t1000dos.rom", "rb");
    if (fp != NULL) {
        t1000.romdrive = calloc(1, T1000_ROMSIZE);
        if (t1000.romdrive) {
            memset(t1000.romdrive, 0xff, T1000_ROMSIZE);
            if (fread(t1000.romdrive, 1, T1000_ROMSIZE, fp) != T1000_ROMSIZE)
                fatal("machine_xt_t1000_init(): Error reading DOS ROM data\n");
        }
        fclose(fp);
    }
    mem_mapping_add(&t1000.rom_mapping, 0xa0000, 0x10000,
                    t1000_read_rom, t1000_read_romw, t1000_read_roml,
                    NULL, NULL, NULL, NULL, MEM_MAPPING_EXTERNAL, &t1000);
    mem_mapping_disable(&t1000.rom_mapping);

    /* Map the EMS page frame */
    for (uint8_t pg = 0; pg < 4; pg++) {
        mem_mapping_add(&t1000.mapping[pg], 0xd0000 + (0x4000 * pg), 16384,
                        ems_read_ram, ems_read_ramw, ems_read_raml,
                        ems_write_ram, ems_write_ramw, ems_write_raml,
                        NULL, MEM_MAPPING_EXTERNAL, &t1000);

        /* Start them all off disabled */
        mem_mapping_disable(&t1000.mapping[pg]);
    }

    /* Non-volatile RAM for CONFIG.SYS */
    io_sethandler(0xc0, 4,
                  t1000_read_nvram, NULL, NULL,
                  t1000_write_nvram, NULL, NULL, &t1000);

    /* ROM drive */
    io_sethandler(0xc8, 1,
                  t1000_read_rom_ctl, NULL, NULL,
                  t1000_write_rom_ctl, NULL, NULL, &t1000);

    /* System control functions, and add-on memory board */
    io_sethandler(0xe0, 16,
                  read_ctl, NULL, NULL, write_ctl, NULL, NULL, &t1000);

    machine_common_init(model);

    pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt);
    device_add(&kbc_xt_t1x00_device);
    t1000.fdc = device_add(&fdc_xt_device);
    nmi_init();

    tc8521_init(&t1000.nvr, model->nvrmask + 1);

    t1000_nvr_load();
    nvr_set_ven_save(t1000_nvr_save);

    if (gfxcard[0] == VID_INTERNAL)
        device_add(&t1000_video_device);

    return ret;
}

int
machine_xt_t1200_init(const machine_t *model)
{
    int ret;

    ret = bios_load_linear("roms/machines/t1200/t1200_019e.ic15.bin",
                           0x000f8000, 32768, 0);

    if (bios_only || !ret)
        return ret;

    memset(&t1000, 0x00, sizeof(t1000));
    t1000.is_t1200       = 1;
    t1000.ems_port_index = 7; /* EMS disabled */

    /* Load the T1000 CGA Font ROM. */
    video_load_font("roms/machines/t1000/t1000font.bin", FONT_FORMAT_CGA, LOAD_FONT_NO_OFFSET);

    /* Map the EMS page frame */
    for (uint8_t pg = 0; pg < 4; pg++) {
        mem_mapping_add(&t1000.mapping[pg],
                        0xd0000 + (0x4000 * pg), 16384,
                        ems_read_ram, ems_read_ramw, ems_read_raml,
                        ems_write_ram, ems_write_ramw, ems_write_raml,
                        NULL, MEM_MAPPING_EXTERNAL, &t1000);

        /* Start them all off disabled */
        mem_mapping_disable(&t1000.mapping[pg]);
    }

    /* System control functions, and add-on memory board */
    io_sethandler(0xe0, 16,
                  read_ctl, NULL, NULL, write_ctl, NULL, NULL, &t1000);

    machine_common_init(model);

    mem_mapping_add(&t1000.nvr_mapping,
                    0x000f0000, 2048,
                    read_t1200_nvram, NULL, NULL,
                    write_t1200_nvram, NULL, NULL,
                    NULL, MEM_MAPPING_EXTERNAL, &t1000);

    pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt);
    device_add(&kbc_xt_t1x00_device);
    t1000.fdc = device_add(&fdc_xt_t1x00_device);
    nmi_init();

    tc8521_init(&t1000.nvr, model->nvrmask + 1);

    t1200_nvr_load();
    nvr_set_ven_save(t1200_nvr_save);

    if (gfxcard[0] == VID_INTERNAL)
        device_add(&t1200_video_device);

    if (hdc_current[0] <= HDC_INTERNAL)
        device_add(&st506_xt_toshiba_t1200_device);

    return ret;
}

void
t1000_syskey(uint8_t andmask, uint8_t ormask, uint8_t xormask)
{
    t1000.syskeys &= ~andmask;
    t1000.syskeys |= ormask;
    t1000.syskeys ^= xormask;
}

static void
t1000_configsys_load(void)
{
    FILE *fp;
    int   size;

    memset(t1000.t1000_nvram, 0x1a, sizeof(t1000.t1000_nvram));
    fp = plat_fopen(nvr_path("t1000_config.nvr"), "rb");
    if (fp != NULL) {
        size = sizeof(t1000.t1000_nvram);
        if (fread(t1000.t1000_nvram, 1, size, fp) != size)
            fatal("t1000_configsys_load(): Error reading data\n");
        fclose(fp);
    }
}

static void
t1000_configsys_save(void)
{
    FILE *fp;
    int   size;

    fp = plat_fopen(nvr_path("t1000_config.nvr"), "wb");
    if (fp != NULL) {
        size = sizeof(t1000.t1000_nvram);
        if (fwrite(t1000.t1000_nvram, 1, size, fp) != size)
            fatal("t1000_configsys_save(): Error writing data\n");
        fclose(fp);
    }
}

static void
t1200_state_load(void)
{
    FILE *fp;
    int   size;

    memset(t1000.t1200_nvram, 0, sizeof(t1000.t1200_nvram));
    fp = plat_fopen(nvr_path("t1200_state.nvr"), "rb");
    if (fp != NULL) {
        size = sizeof(t1000.t1200_nvram);
        if (fread(t1000.t1200_nvram, 1, size, fp) != size)
            fatal("t1200_state_load(): Error reading data\n");
        fclose(fp);
    }
}

static void
t1200_state_save(void)
{
    FILE *fp;
    int   size;

    fp = plat_fopen(nvr_path("t1200_state.nvr"), "wb");
    if (fp != NULL) {
        size = sizeof(t1000.t1200_nvram);
        if (fwrite(t1000.t1200_nvram, 1, size, fp) != size)
            fatal("t1200_state_save(): Error writing data\n");
        fclose(fp);
    }
}

/* All RAM beyond 512k is non-volatile */
static void
t1000_emsboard_load(void)
{
    FILE *fp;

    if (mem_size > 512) {
        fp = plat_fopen(nvr_path("t1000_ems.nvr"), "rb");
        if (fp != NULL) {
            (void) !fread(&ram[512 * 1024], 1024, (mem_size - 512), fp);
            fclose(fp);
        }
    }
}

static void
t1000_emsboard_save(void)
{
    FILE *fp;

    if (mem_size > 512) {
        fp = plat_fopen(nvr_path("t1000_ems.nvr"), "wb");
        if (fp != NULL) {
            fwrite(&ram[512 * 1024], 1024, (mem_size - 512), fp);
            fclose(fp);
        }
    }
}

void
t1000_nvr_load(void)
{
    t1000_emsboard_load();
    t1000_configsys_load();
}

void
t1000_nvr_save(void)
{
    t1000_emsboard_save();
    t1000_configsys_save();
}

void
t1200_nvr_load(void)
{
    t1000_emsboard_load();
    t1200_state_load();
}

void
t1200_nvr_save(void)
{
    t1000_emsboard_save();
    t1200_state_save();
}
