/*
 * 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.
 *
 *          Emulation of C&T CS8121 ("NEAT") 82C206/211/212/215 chipset.
 *
 * Note:    The datasheet mentions that the chipset supports up to 8MB
 *          of DRAM. This is intepreted as 'being able to refresh up to
 *          8MB of DRAM chips', because it works fine with bus-based
 *          memory expansion.
 *
 * Authors: Fred N. van Kempen, <decwiz@yahoo.com>
 *
 *          Copyright 2018 Fred N. van Kempen.
 */
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include "cpu.h"
#include <86box/device.h>
#include <86box/io.h>
#include <86box/mem.h>
#include <86box/plat_unused.h>
#include <86box/chipset.h>

#define EMS_MAXPAGE 4
#define EMS_PGSIZE  16384
#define EMS_PGMASK  16383

#define REG_MASK       0x0f

/* CS8221 82C211 controller registers. */
#define REG_RA0        0x60 /* PROCCLK selector */
#define RA0_MASK       0x34 /* RR11 X1XR */
#define RA0_READY      0x01 /*  local bus READY timeout */
#define RA0_RDYNMIEN   0x04 /*  local bus READY tmo NMI enable */
#define RA0_PROCCLK    0x10 /*  PROCCLK=BCLK (1) or CLK2IN (0) */
#define RA0_ALTRST     0x20 /*  alternate CPU reset (1) */
#define RA0_REV        0xc0 /*  chip revision ID */
#define RA0_REV_SH     6
#define RA0_REV_ID     2 /* faked revision# for 82C211 */

#define REG_RA1        0x61 /* Command Delay */
#define RA1_MASK       0xff /* 1111 1111 */
#define RA1_MASK_SX    0xbf /* 1X11 1111 */
#define RA1_BUSDLY     0x03 /*  AT BUS command delay */
#define RA1_BUSDLY_SH  0
#define RA1_BUS8DLY    0x0c /*  AT BUS 8bit command delay */
#define RA1_BUS8DLY_SH 2
#define RA1_MEMDLY     0x30 /*  AT BUS 16bit memory delay */
#define RA1_MEMDLY_SH  4
#define RA1_QUICKEN    0x40 /*  Quick Mode enable */
#define RA1_HOLDDLY    0x80 /*  Hold Time Delay */

#define REG_RA2        0x62 /* Wait State / BCLK selector */
#define RA2_MASK       0x3f /* XX11 1111 */
#define RA2_BCLK       0x03 /*  BCLK select */
#define RA2_BCLK_SH    0
#define BCLK_IN2       0    /*  BCLK = CLK2IN/2 */
#define BCLK_IN        1    /*  BCLK = CLK2IN */
#define BCLK_AT        2    /*  BCLK = ATCLK */
#define RA2_AT8WS      0x0c /*  AT 8-bit wait states */
#define RA2_AT8WS_SH   2
#define AT8WS_2        0    /*  2 wait states */
#define AT8WS_3        1    /*  3 wait states */
#define AT8WS_4        2    /*  4 wait states */
#define AT8WS_5        4    /*  5 wait states */
#define RA2_ATWS       0x30 /*  AT 16-bit wait states */
#define RA2_ATWS_SH    4
#define ATWS_2         0 /*  2 wait states */
#define ATWS_3         1 /*  3 wait states */
#define ATWS_4         2 /*  4 wait states */
#define ATWS_5         4 /*  5 wait states */
#define RA2_387SX      0x80

/* CS8221 82C212 controller registers. */
#define REG_RB0        0x64 /* Version ID */
#define RB0_MASK       0x60 /* R11X XXXX */
#define RB0_REV        0x60 /*  Chip revsion number */
#define RB0_REV_SH     5
#define RB0_REV_ID     2    /* faked revision# for 82C212 */
#define RB0_VERSION    0x80 /*  Chip version (0=82C212) */

#define REG_RB1        0x65 /* ROM configuration */
#define RB1_MASK       0xff /* 1111 1111 */
#define RB1_ROMF0      0x01 /*  ROM F0000 enabled (0) */
#define RB1_ROME0      0x02 /*  ROM E0000 disabled (1) */
#define RB1_ROMD0      0x04 /*  ROM D0000 disabled (1) */
#define RB1_ROMC0      0x08 /*  ROM C0000 disabled (1) */
#define RB1_SHADOWF0   0x10 /*  Shadow F0000 R/W (0) */
#define RB1_SHADOWE0   0x20 /*  Shadow E0000 R/W (0) */
#define RB1_SHADOWD0   0x40 /*  Shadow D0000 R/W (0) */
#define RB1_SHADOWC0   0x80 /*  Shadow C0000 R/W (0) */

#define REG_RB2        0x66 /* Memory Enable 1 */
#define RB2_MASK       0x80 /* 1XXX XXXX */
#define RB2_MASK_SX    0xe0 /* 111X XXXX */
#define RB2_BOT256     0x20 /*  bottom 256K is on sysboard (1) */
#define RB2_MID256     0x40 /*  middle 256K is on sysboard (1) */
#define RB2_TOP128     0x80 /*  top 128K is on sysboard (1) */

#define REG_RB3        0x67 /* Memory Enable 2 */
#define RB3_MASK       0xff /* 1111 1111 */
#define RB3_SHENB0     0x01 /*  enable B0000-B3FFF shadow (1) */
#define RB3_SHENB4     0x02 /*  enable B4000-B7FFF shadow (1) */
#define RB3_SHENB8     0x04 /*  enable B8000-BBFFF shadow (1) */
#define RB3_SHENBC     0x08 /*  enable BC000-BFFFF shadow (1) */
#define RB3_SHENA0     0x10 /*  enable A0000-A3FFF shadow (1) */
#define RB3_SHENA4     0x20 /*  enable A4000-A7FFF shadow (1) */
#define RB3_SHENA8     0x40 /*  enable A8000-ABFFF shadow (1) */
#define RB3_SHENAC     0x80 /*  enable AC000-AFFFF shadow (1) */

#define REG_RB4        0x68 /* Memory Enable 3 */
#define RB4_MASK       0xff /* 1111 1111 */
#define RB4_SHENC0     0x01 /*  enable C0000-C3FFF shadow (1) */
#define RB4_SHENC4     0x02 /*  enable C4000-C7FFF shadow (1) */
#define RB4_SHENC8     0x04 /*  enable C8000-CBFFF shadow (1) */
#define RB4_SHENCC     0x08 /*  enable CC000-CFFFF shadow (1) */
#define RB4_SHEND0     0x10 /*  enable D0000-D3FFF shadow (1) */
#define RB4_SHEND4     0x20 /*  enable D4000-D7FFF shadow (1) */
#define RB4_SHEND8     0x40 /*  enable D8000-DBFFF shadow (1) */
#define RB4_SHENDC     0x80 /*  enable DC000-DFFFF shadow (1) */

#define REG_RB5        0x69 /* Memory Enable 4 */
#define RB5_MASK       0xff /* 1111 1111 */
#define RB5_SHENE0     0x01 /*  enable E0000-E3FFF shadow (1) */
#define RB5_SHENE4     0x02 /*  enable E4000-E7FFF shadow (1) */
#define RB5_SHENE8     0x04 /*  enable E8000-EBFFF shadow (1) */
#define RB5_SHENEC     0x08 /*  enable EC000-EFFFF shadow (1) */
#define RB5_SHENF0     0x10 /*  enable F0000-F3FFF shadow (1) */
#define RB5_SHENF4     0x20 /*  enable F4000-F7FFF shadow (1) */
#define RB5_SHENF8     0x40 /*  enable F8000-FBFFF shadow (1) */
#define RB5_SHENFC     0x80 /*  enable FC000-FFFFF shadow (1) */

#define REG_RB6        0x6a /* Bank 0/1 Enable */
#define RB6_MASK       0xe0 /* 111R RRRR */
#define RB6_BANKS      0x20 /*  #banks used (1=two) */
#define RB6_RTYPE      0xc0 /*  DRAM chip size used */
#define RTYPE_SH       6
#define RTYPE_NONE     0 /*  Disabled */
#define RTYPE_MIXED    1 /*  64K/256K mixed (for 640K) */
#define RTYPE_256K     2 /*  256K (default) */
#define RTYPE_1M       3 /*  1M */

#define REG_RB7        0x6b /* DRAM configuration */
#define RB7_MASK       0xff /* 1111 1111 */
#define RB7_ROMWS      0x03 /*  ROM access wait states */
#define RB7_ROMWS_SH   0
#define ROMWS_0        0    /*  0 wait states */
#define ROMWS_1        1    /*  1 wait states */
#define ROMWS_2        2    /*  2 wait states */
#define ROMWS_3        3    /*  3 wait states (default) */
#define RB7_EMSWS      0x0c /*  EMS access wait states */
#define RB7_EMSWS_SH   2
#define EMSWS_0        0    /*  0 wait states */
#define EMSWS_1        1    /*  1 wait states */
#define EMSWS_2        2    /*  2 wait states */
#define EMSWS_3        3    /*  3 wait states (default) */
#define RB7_EMSEN      0x10 /* enable EMS (1=on) */
#define RB7_RAMWS      0x20 /*  RAM access wait state (1=1ws) */
#define RB7_UMAREL     0x40 /*  relocate 640-1024K to 1M */
#define RB7_PAGEEN     0x80 /*  enable Page/Interleaved mode */

#define REG_RB8        0x6c /* Bank 2/3 Enable */
#define RB8_MASK       0xf0 /* 1111 RRRR */
#define RB8_4WAY       0x10 /*  enable 4-way interleave mode */
#define RB8_BANKS      0x20 /*  enable 2 banks (1) */
#define RB8_RTYPE      0xc0 /*  DRAM chip size used */
#define RB8_RTYPE_SH   6

#define REG_RB9        0x6d /* EMS base address */
#define RB9_MASK       0xff /* 1111 1111 */
#define RB9_BASE       0x0f /*  I/O base address selection */
#define RB9_BASE_SH    0
#define RB9_FRAME      0xf0 /*  frame address selection */
#define RB9_FRAME_SH   4

#define REG_RB10       0x6e /* EMS address extension */
#define RB10_MASK      0xff /* 1111 1111 */
#define RB10_P3EXT     0x03 /*  page 3 extension */
#define RB10_P3EXT_SH  0
#define PEXT_0M        0    /*  page is at 0-2M */
#define PEXT_2M        1    /*  page is at 2-4M */
#define PEXT_4M        2    /*  page is at 4-6M */
#define PEXT_6M        3    /*  page is at 6-8M */
#define RB10_P2EXT     0x0c /*  page 2 extension */
#define RB10_P2EXT_SH  2
#define RB10_P1EXT     0x30 /*  page 1 extension */
#define RB10_P1EXT_SH  4
#define RB10_P0EXT     0xc0 /*  page 0 extension */
#define RB10_P0EXT_SH  6

#define REG_RB12       0x6f /* Miscellaneous */
#define RB12_MASK      0xe6 /* 111R R11R */
#define RB12_MASK_SX   0xf6 /* 1111 R11R */
#define RB12_GA20      0x02 /*  gate for A20 */
#define RB12_RASTMO    0x04 /*  enable RAS timeout counter */
#define RB12_EMSLEN    0xe0 /*  EMS memory chunk size */
#define RB12_EMSLEN_SH 5

#define MEM_FLAG_REMAP      0x10
#define MEM_FLAG_EMS        0x08
#define MEM_FLAG_ROMCS      0x04
#define MEM_FLAG_READ       0x02
#define MEM_FLAG_WRITE      0x01
#define MEM_FMASK_REMAP     0x10
#define MEM_FMASK_EMS       0x08
#define MEM_FMASK_SHADOW    0x07

typedef struct ram_page_t {
    int8_t        enabled;              /* 1=ENABLED */
    char          pad;
    uint32_t      phys_base;
    uint32_t      virt_base;
    mem_mapping_t mapping;             /* mapping entry for page */
} ram_page_t;

typedef struct neat_t {
    uint8_t       mem_flags[64];
    uint8_t       regs[128];           /* all the CS8221 registers */
    uint8_t       indx;                /* programmed index into registers */
    uint8_t       sx;

    uint16_t      ems_base;            /* configured base address */
    uint32_t      ems_frame;           /* configured frame address */
    uint16_t      ems_size;            /* EMS size in KB */
    uint16_t      ems_pages;           /* EMS size in pages */

    uint32_t      remap_base;

    ram_page_t    ems[EMS_MAXPAGE];    /* EMS page registers */
    ram_page_t    shadow[32];          /* Shadow RAM pages */
} neat_t;

static uint8_t defaults[2][16] = { { 0x0a, 0x45, 0xfc, 0x00, 0x00, 0x0e, 0x00, 0x00,
                                     0x00, 0x00, 0x80, 0x43, 0x10, 0x00, 0x00, 0x12 },
                                   { 0x0a, 0x45, 0x7c, 0x00, 0x00, 0x0e, 0x00, 0x00,
                                     0x00, 0x00, 0x80, 0x43, 0x00, 0x00, 0x00, 0x08 } };

static uint8_t reg_masks[2][16] = { {     RA0_MASK,     RA1_MASK,     RA2_MASK,         0x00,
                                          RB0_MASK,     RB1_MASK,     RB2_MASK,     RB3_MASK,
                                          RB4_MASK,     RB4_MASK,     RB4_MASK,     RB4_MASK,
                                          RB8_MASK,     RB9_MASK,    RB10_MASK,    RB12_MASK },
                                    {     RA0_MASK,  RA1_MASK_SX,     RA2_MASK,         0x00,
                                          RB0_MASK,     RB1_MASK,  RB2_MASK_SX,     RB3_MASK,
                                          RB4_MASK,     RB4_MASK,     RB4_MASK,     RB4_MASK,
                                          RB8_MASK,     RB9_MASK,    RB10_MASK, RB12_MASK_SX } };

static uint8_t masks[4]     = { RB10_P0EXT, RB10_P1EXT, RB10_P2EXT, RB10_P3EXT };
static uint8_t shifts[4]    = { RB10_P0EXT_SH, RB10_P1EXT_SH, RB10_P2EXT_SH, RB10_P3EXT_SH };

#ifdef ENABLE_NEAT_LOG
int neat_do_log = ENABLE_NEAT_LOG;

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

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

static uint8_t
neat_read_ram(uint32_t addr, void *priv)
{
    neat_t *dev = (neat_t *) priv;

    if (dev->regs[REG_RB7] & RB7_EMSEN)
        addr += (dev->ems_size << 10);

    if (cpu_use_exec)
        addreadlookup(mem_logical_addr, addr);

    return ram[addr];
}

static uint16_t
neat_read_ramw(uint32_t addr, void *priv)
{
    neat_t *dev = (neat_t *) priv;

    if (dev->regs[REG_RB7] & RB7_EMSEN)
        addr += (dev->ems_size << 10);

    if (cpu_use_exec)
        addreadlookup(mem_logical_addr, addr);

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

static void
neat_write_ram(uint32_t addr, uint8_t val, void *priv)
{
    neat_t *dev = (neat_t *) priv;

    if (dev->regs[REG_RB7] & RB7_EMSEN)
        addr += (dev->ems_size << 10);

    if (cpu_use_exec) {
        addwritelookup(mem_logical_addr, addr);
        mem_write_ramb_page(addr, val, &pages[addr >> 12]);
    } else
        ram[addr] = val;
}

static void
neat_write_ramw(uint32_t addr, uint16_t val, void *priv)
{
    neat_t *dev = (neat_t *) priv;

    if (dev->regs[REG_RB7] & RB7_EMSEN)
        addr += (dev->ems_size << 10);

    if (cpu_use_exec) {
        addwritelookup(mem_logical_addr, addr);
        mem_write_ramw_page(addr, val, &pages[addr >> 12]);
    } else
        *(uint16_t *) &ram[addr] = val;
}

/* Read one byte from paged RAM. */
static uint8_t
ems_readb(uint32_t addr, void *priv)
{
    ram_page_t *dev = (ram_page_t *) priv;
    uint8_t     ret = 0xff;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 3)
    uint32_t    old = addr;
#endif

    /* Grab the data. */
    addr = addr - dev->virt_base + dev->phys_base;

    if (addr < (mem_size << 10))
        ret = *(uint8_t *) &(ram[addr]);

#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 3)
    neat_log("[R08] %08X -> %08X (%08X): ret = %02X\n", old, addr, (mem_size << 10), ret);
#endif
    return ret;
}

/* Read one word from paged RAM. */
static uint16_t
ems_readw(uint32_t addr, void *priv)
{
    ram_page_t *dev = (ram_page_t *) priv;
    uint16_t    ret = 0xffff;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 3)
    uint32_t    old = addr;
#endif

    /* Grab the data. */
    addr = addr - dev->virt_base + dev->phys_base;

    if (addr < (mem_size << 10))
        ret = *(uint16_t *) &(ram[addr]);

#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 3)
    neat_log("[R16] %08X -> %08X (%08X): ret = %04X\n", old, addr, (mem_size << 10), ret);
#endif
    return ret;
}

/* Write one byte to paged RAM. */
static void
ems_writeb(uint32_t addr, uint8_t val, void *priv)
{
    ram_page_t *dev = (ram_page_t *) priv;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 3)
    uint32_t    old = addr;
#endif

    /* Write the data. */
    addr = addr - dev->virt_base + dev->phys_base;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 3)
    neat_log("[W08] %08X -> %08X (%08X): val = %02X\n", old, addr, (mem_size << 10), val);
#endif

    if (addr < (mem_size << 10))
        *(uint8_t *) &(ram[addr]) = val;
}

/* Write one word to paged RAM. */
static void
ems_writew(uint32_t addr, uint16_t val, void *priv)
{
    ram_page_t *dev = (ram_page_t *) priv;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 3)
    uint32_t    old = addr;
#endif

    /* Write the data. */
    addr = addr - dev->virt_base + dev->phys_base;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 3)
    neat_log("[W16] %08X -> %08X (%08X): val = %04X\n", old, addr, (mem_size << 10), val);
#endif

    if (addr < (mem_size << 10))
        *(uint16_t *) &(ram[addr]) = val;
}

static void
neat_mem_update_state(neat_t *dev, uint32_t addr, uint32_t size, uint8_t new_flags, uint8_t mask)
{
    if ((addr < 0x00100000) &&
        ((new_flags ^ dev->mem_flags[addr / EMS_PGSIZE]) & mask)) {
        dev->mem_flags[addr / EMS_PGSIZE] &= ~mask;
        dev->mem_flags[addr / EMS_PGSIZE] |= new_flags;

        new_flags = dev->mem_flags[addr / EMS_PGSIZE];

        if (new_flags & MEM_FLAG_ROMCS) {
            neat_log("neat_mem_update_state(): %08X-%08X: %02X (ROMCS)\n", addr, addr + size - 1, new_flags);
            mem_set_mem_state(addr, size, MEM_READ_ROMCS | MEM_WRITE_ROMCS);
        } else if (new_flags & MEM_FLAG_REMAP) {
            neat_log("neat_mem_update_state(): %08X-%08X: %02X (REMAP)\n", addr, addr + size - 1, new_flags);
            mem_set_mem_state(addr, size, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL);
        } else if (new_flags & MEM_FLAG_EMS) {
            neat_log("neat_mem_update_state(): %08X-%08X: %02X (EMS)\n", addr, addr + size - 1, new_flags);
            mem_set_mem_state(addr, size, MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
        } else  switch (new_flags & (MEM_FLAG_READ | MEM_FLAG_WRITE)) {
            case 0:
                neat_log("neat_mem_update_state(): %08X-%08X: %02X (RE | WE)\n", addr, addr + size - 1, new_flags);
                mem_set_mem_state(addr, size, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL);
                break;
            case 1:
                neat_log("neat_mem_update_state(): %08X-%08X: %02X (RE | WI)\n", addr, addr + size - 1, new_flags);
                mem_set_mem_state(addr, size, MEM_READ_EXTERNAL | MEM_WRITE_INTERNAL);
                break;
            case 2:
                neat_log("neat_mem_update_state(): %08X-%08X: %02X (RI | WE)\n", addr, addr + size - 1, new_flags);
                mem_set_mem_state(addr, size, MEM_READ_INTERNAL | MEM_WRITE_EXTERNAL);
                break;
            case 3:
                neat_log("neat_mem_update_state(): %08X-%08X: %02X (RI | WI)\n", addr, addr + size - 1, new_flags);
                mem_set_mem_state(addr, size, MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                break;
            default:
                break;
        }
    }

    flushmmucache_nopc();
}

static void
shadow_recalc(neat_t *dev)
{
    for (uint8_t i = 8; i < 32; i++) {
        int romcs      = 0;
        int write      = 1;
        int shadow_reg = REG_RB3 + ((i - 8) >> 3);
        int shadow_bit = i & 7;
        int mem_flags;
        int read;

        if (i >= 16) {
            int rb1_romcs_bit = 7 - (i >> 2);
            int rb1_write_bit = rb1_romcs_bit + 4;

            romcs = !(dev->regs[REG_RB1] & (1 << rb1_romcs_bit));
            write = !(dev->regs[REG_RB1] & (1 << rb1_write_bit));
            neat_log("Shadow %08X-%08X: [%02X, %02X] %02X:%02X, %02X, %02X\n",
                     dev->shadow[i].virt_base, dev->shadow[i].virt_base + EMS_PGSIZE - 1,
                     dev->regs[REG_RB1], dev->regs[shadow_reg],
                     shadow_reg, shadow_bit,
                     rb1_romcs_bit, rb1_write_bit);
        } else {
            shadow_bit ^= 4;
            neat_log("Shadow %08X-%08X: [--, %02X] %02X:%02X, shadow bit ^= 4\n",
                     dev->shadow[i].virt_base, dev->shadow[i].virt_base + EMS_PGSIZE - 1,
                     dev->regs[shadow_reg],
                     shadow_reg, shadow_bit);
        }

        read  = dev->regs[shadow_reg] & (1 << shadow_bit);
        write = write && read;

        mem_flags  = romcs ? MEM_FLAG_ROMCS : 0x00;
        mem_flags |= read  ? MEM_FLAG_READ  : 0x00;
        mem_flags |= write ? MEM_FLAG_WRITE : 0x00;

        if ((mem_flags > 0x00) && !(mem_flags & MEM_FLAG_ROMCS))
            mem_mapping_set_addr(&(dev->shadow[i].mapping), dev->shadow[i].virt_base, EMS_PGSIZE);
        else
            mem_mapping_disable(&(dev->shadow[i].mapping));

        neat_mem_update_state(dev, dev->shadow[i].virt_base, EMS_PGSIZE, mem_flags, MEM_FMASK_SHADOW);
    }
}

/* Re-calculate the active-page physical address. */
static void
ems_recalc(neat_t *dev, ram_page_t *ems)
{
    uint32_t page  = ems->phys_base / EMS_PGSIZE;

    neat_log("ems_recalc(): %08X, %04X, %04X\n", ems->virt_base, page, dev->ems_pages);

    if ((dev->regs[REG_RB7] & RB7_EMSEN) && ems->enabled && (page < dev->ems_pages)) {
        neat_log("ems_recalc(): %08X-%08X -> %08X-%08X\n",
                 ems->virt_base, ems->virt_base + EMS_PGSIZE - 1,
                 ems->phys_base, ems->phys_base + EMS_PGSIZE - 1);
        mem_mapping_set_addr(&ems->mapping, ems->virt_base, EMS_PGSIZE);

        /* Update the EMS RAM address for this page. */
        mem_mapping_set_exec(&ems->mapping, ram + ems->phys_base);

        neat_mem_update_state(dev, ems->virt_base, EMS_PGSIZE, MEM_FLAG_EMS, MEM_FMASK_EMS);

#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
        neat_log("NEAT EMS: page %d set to %08lx, %sabled)\n",
                 ems->page, ems->addr - ram, ems->enabled ? "en" : "dis");
#endif
    } else {
        /* Disable this page. */
        mem_mapping_disable(&ems->mapping);

        neat_mem_update_state(dev, ems->virt_base, EMS_PGSIZE, 0x00, MEM_FMASK_EMS);
    }
}

static void
ems_write(uint16_t port, uint8_t val, void *priv)
{
    neat_t     *dev           = (neat_t *) priv;
    ram_page_t *ems;
    int         vpage;
    int8_t      old_enabled;
    uint32_t    old_phys_base;
    int8_t      new_enabled;
    uint32_t    new_phys_base;

#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
    neat_log("NEAT: ems_write(%04x, %02x)\n", port, val);
#endif

    /* Get the viewport page number. */
    vpage = (port / EMS_PGSIZE);
    ems   = &dev->ems[vpage];

    neat_log("Port: %04X, val: %02X\n", port, val);

    switch (port & 0x000f) {
        case 0x0008:
        case 0x0009:
            old_enabled    = ems->enabled;
            old_phys_base  = ems->phys_base;
            new_enabled    = !!(val & 0x80);
            new_phys_base  = (ems->phys_base & 0xffe00000) | ((val & 0x7f) * EMS_PGSIZE);

            if ((old_enabled != new_enabled) || (old_phys_base != new_phys_base)) {
                if (old_enabled && (old_enabled == new_enabled)) {
                    ems->enabled   = 0;
                    ems_recalc(dev, ems);
                }

                ems->enabled   = !!(val & 0x80);

                if (old_phys_base != new_phys_base)
                    ems->phys_base = (ems->phys_base & 0xffe00000) | ((val & 0x7f) * EMS_PGSIZE);

                ems_recalc(dev, ems);
            }
            break;
        default:
            break;
    }
}

static uint8_t
ems_read(uint16_t port, void *priv)
{
    const neat_t *dev = (neat_t *) priv;
    uint8_t       ret = 0xff;
    int           vpage;

    /* Get the viewport page number. */
    vpage = (port / EMS_PGSIZE);

    switch (port & 0x000f) {
        case 0x0008: /* page number register */
        case 0x0009:
            ret = (dev->ems[vpage].phys_base / EMS_PGSIZE) & 0x7f;
            if (dev->ems[vpage].enabled)
                ret |= 0x80;
            break;
        default:
            break;
    }

    neat_log("Port: %04X, ret: %02X\n", port, ret);

#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
    neat_log("NEAT: ems_read(%04x) = %02x\n", port, ret);
#endif

    return ret;
}

static void
ems_recalc_all(neat_t *dev)
{
    for (uint8_t i = 0; i < EMS_MAXPAGE; i++)
        ems_recalc(dev, &(dev->ems[i]));
}

static void
ems_update_virt_base(neat_t *dev)
{
    for (uint8_t i = 0; i < EMS_MAXPAGE; i++)
        dev->ems[i].virt_base = dev->ems_frame + (i * EMS_PGSIZE);
}

static void
ems_remove_handlers(neat_t *dev)
{
    for (uint8_t i = 0; i < EMS_MAXPAGE; i++) {
        neat_log("Removing I/O handler at %04X-%04X\n",
                 dev->ems_base + (i * EMS_PGSIZE), dev->ems_base + (i * EMS_PGSIZE) + 1);
        /* Clean up any previous I/O port handler. */
        io_removehandler(dev->ems_base + (i * EMS_PGSIZE), 2,
                         ems_read, NULL, NULL, ems_write, NULL, NULL, dev);
    }
}

static void
ems_set_handlers(neat_t *dev)
{
    for (uint8_t i = 0; i < EMS_MAXPAGE; i++) {
        neat_log("Setting up I/O handler at %04X-%04X\n",
                 dev->ems_base + (i * EMS_PGSIZE), dev->ems_base + (i * EMS_PGSIZE) + 1);
        /* Set up an I/O port handler. */
        io_sethandler(dev->ems_base + (i * EMS_PGSIZE), 2,
                      ems_read, NULL, NULL, ems_write, NULL, NULL, dev);
    }

    ems_recalc_all(dev);
}

static void
remap_update_states(neat_t *dev, uint8_t flag)
{
    for (uint8_t i = 0; i < 24; i++)
         neat_mem_update_state(dev, 0x000a0000 + (i * EMS_PGSIZE), EMS_PGSIZE, flag, MEM_FMASK_REMAP);
}

static void
remap_update(neat_t *dev, uint8_t val)
{
    if (dev->regs[REG_RB7] & RB7_UMAREL) {
        mem_remap_top_ex_nomid(0, (dev->remap_base >= 1024) ? dev->remap_base : 1024);

        remap_update_states(dev, 0x00);
        neat_log("0 kB at %08X\n", ((dev->remap_base >= 1024) ? dev->remap_base : 1024) << 10);
    }

    if (val & RB7_EMSEN)
        dev->remap_base = mem_size - dev->ems_size;
    else
        dev->remap_base = mem_size;
    neat_log("Total contiguous memory now: %i kB\n", dev->remap_base);

    if (dev->remap_base >= 640)
        mem_mapping_set_addr(&ram_low_mapping, 0x00000000, 0x000a0000);
    else
        mem_mapping_set_addr(&ram_low_mapping, 0x00000000, dev->remap_base << 10);

    if (dev->remap_base > 1024) {
        uint32_t base = (val & RB7_EMSEN) ? (0x00100000 + (dev->ems_size << 10)) : 0x00100000;

        mem_mapping_set_addr(&ram_high_mapping, 0x00100000, (dev->remap_base << 10) - 0x00100000);
        mem_mapping_set_exec(&ram_high_mapping, &(ram[base]));
    } else
        mem_mapping_disable(&ram_high_mapping);

    if (val & RB7_UMAREL) {
        mem_remap_top_ex_nomid(384, (dev->remap_base >= 1024) ? dev->remap_base : 1024);

        remap_update_states(dev, MEM_FLAG_REMAP);
        neat_log("384 kB at %08X\n", ((dev->remap_base >= 1024) ? dev->remap_base : 1024) << 10);
    }
}

static void
neat_write(uint16_t port, uint8_t val, void *priv)
{
    neat_t  *dev = (neat_t *) priv;
    uint8_t  xval;
    uint8_t j;
    uint8_t *reg;
    uint8_t  mask;
    int      i;

#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
    neat_log("NEAT: write(%04x, %02x)\n", port, val);
#endif

    switch (port) {
        case 0x22:
            dev->indx = val;
            break;

        case 0x23:
            reg  = &dev->regs[dev->indx];
            xval = *reg ^ val;
            mask = reg_masks[dev->sx][dev->indx & REG_MASK];
            switch (dev->indx) {
                case REG_RA0:
                    val &= mask;
                    *reg = (*reg & ~mask) | val | (RA0_REV_ID << RA0_REV_SH);
                    if ((xval & 0x20) && (val & 0x20))
                        outb(0x64, 0xfe);
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RA0=%02x(%02x)\n", val, *reg);
#endif
                    break;

                case REG_RA1:
                    val &= mask;
                    *reg = (*reg & ~mask) | val;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RA1=%02x(%02x)\n", val, *reg);
#endif
                    break;

                case REG_RA2:
                    val &= mask;
                    *reg = (*reg & ~mask) | val;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RA2=%02x(%02x)\n", val, *reg);
#endif
                    break;

                case REG_RB0:
                    val &= mask;
                    *reg = (*reg & ~mask) | val | (RB0_REV_ID << RB0_REV_SH);
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RB0=%02x(%02x)\n", val, *reg);
#endif
                    break;

                case REG_RB1:
                    val &= mask;
                    *reg = (*reg & ~mask) | val;
                    shadow_recalc(dev);
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RB1=%02x(%02x)\n", val, *reg);
#endif
                    break;

                case REG_RB2:
                    val &= mask;
                    *reg = (*reg & ~mask) | val;
                    if (dev->sx) {
                        if (val & RB2_BOT256)
                            neat_mem_update_state(dev, 0x00000000, 0x00040000,
                                                  MEM_FLAG_READ | MEM_FLAG_WRITE, MEM_FMASK_SHADOW);
                        else
                            neat_mem_update_state(dev, 0x00000000, 0x00040000,
                                                  0x00, MEM_FMASK_SHADOW);

                        if (val & RB2_MID256)
                            neat_mem_update_state(dev, 0x00040000, 0x00040000,
                                                  MEM_FLAG_READ | MEM_FLAG_WRITE, MEM_FMASK_SHADOW);
                        else
                            neat_mem_update_state(dev, 0x00040000, 0x00040000,
                                                  0x00, MEM_FMASK_SHADOW);
                    }
                    if (val & RB2_TOP128)
                        neat_mem_update_state(dev, 0x00080000, 0x00020000,
                                              MEM_FLAG_READ | MEM_FLAG_WRITE, MEM_FMASK_SHADOW);
                    else
                        neat_mem_update_state(dev, 0x00080000, 0x00020000,
                                              0x00, MEM_FMASK_SHADOW);
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RB2=%02x(%02x)\n", val, *reg);
#endif
                    break;

                case REG_RB3:
                    val &= mask;
                    *reg = (*reg & ~mask) | val;
                    shadow_recalc(dev);
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RB3=%02x(%02x)\n", val, *reg);
#endif
                    break;

                case REG_RB4:
                    val &= mask;
                    *reg = (*reg & ~mask) | val;
                    shadow_recalc(dev);
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RB4=%02x(%02x)\n", val, *reg);
#endif
                    break;

                case REG_RB5:
                    val &= mask;
                    *reg = (*reg & ~mask) | val;
                    shadow_recalc(dev);
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RB5=%02x(%02x)\n", val, *reg);
#endif
                    break;

                case REG_RB6:
                    val &= mask;
                    *reg = (*reg & ~mask) | val;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RB6=%02x(%02x)\n", val, *reg);
#endif
                    break;

                case REG_RB7:
                    val &= mask;

                    if (xval & (RB7_EMSEN | RB7_UMAREL))
                        remap_update(dev, val);

                    *reg = (*reg & ~mask) | val;

                    if (xval & RB7_EMSEN)
                        ems_remove_handlers(dev);

                    if ((xval & RB7_EMSEN) && (val & RB7_EMSEN))
                        ems_set_handlers(dev);

#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RB7=%02x(%02x)\n", val, *reg);
#endif
                    break;

                case REG_RB8:
                    val &= mask;
                    *reg = (*reg & ~mask) | val;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RB8=%02x(%02x)\n", val, *reg);
#endif
                    break;

                case REG_RB9:
                    val &= mask;
                    *reg = (*reg & ~mask) | val;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RB9=%02x(%02x)\n", val, *reg);
#endif

                    ems_remove_handlers(dev);

                    /* Get configured I/O address. */
                    j              = (dev->regs[REG_RB9] & RB9_BASE) >> RB9_BASE_SH;
                    dev->ems_base  = 0x0208 + (0x10 * j);

                    /* Get configured frame address. */
                    j              = (dev->regs[REG_RB9] & RB9_FRAME) >> RB9_FRAME_SH;
                    dev->ems_frame = 0xc0000 + (EMS_PGSIZE * j);

                    ems_update_virt_base(dev);

                    if (dev->regs[REG_RB7] & RB7_EMSEN)
                        ems_set_handlers(dev);
                    break;

                case REG_RB10:
                    val &= mask;
                    *reg = (*reg & ~mask) | val;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RB10=%02x(%02x)\n", val, *reg);
#endif

                    for (uint8_t i = 0; i < EMS_MAXPAGE; i++) {
                        ram_page_t *ems = &(dev->ems[i]);
  
                        uint32_t old_phys_base = ems->phys_base & 0xffe00000;
                        uint32_t new_phys_base = (((val & masks[i]) >> shifts[i]) << 21);

                        if (new_phys_base != old_phys_base) {
                            int8_t old_enabled = ems->enabled;

                            if ((dev->regs[REG_RB7] & RB7_EMSEN) && old_enabled) {
                                ems->enabled = 0;
                                ems_recalc(dev, &(dev->ems[i]));
                            }

                            ems->phys_base = ems->phys_base - old_phys_base + new_phys_base;

                            if ((dev->regs[REG_RB7] & RB7_EMSEN) && old_enabled) {
                                ems->enabled = old_enabled;
                                ems_recalc(dev, &(dev->ems[i]));
                            }
                        }
                    }

                    neat_log("%08X, %08X, %08X, %08X\n",
                             dev->ems[0].phys_base, dev->ems[1].phys_base,
                             dev->ems[2].phys_base, dev->ems[3].phys_base);
                    break;

                case REG_RB12:
                    val &= mask;
                    *reg = (*reg & ~mask) | val;
#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
                    neat_log("NEAT: RB12=%02x(%02x)\n", val, *reg);
#endif
                    i = (val & RB12_EMSLEN) >> RB12_EMSLEN_SH;
                    switch (i) {
                        case 0: /* "less than 1MB" */
                            dev->ems_size = 512;
                            break;

                        case 1: /* 1 MB */
                        case 2: /* 2 MB */
                        case 3: /* 3 MB */
                        case 4: /* 4 MB */
                        case 5: /* 5 MB */
                        case 6: /* 6 MB */
                        case 7: /* 7 MB */
                            dev->ems_size = i << 10;
                            break;
                        default:
                            break;
                    }

                    if (mem_size < 1024)
                        /* No RAM left for EMS at all. */
                        dev->ems_size = 0;
                    else if (mem_size < (dev->ems_size + 1024))
                        /* Limit EMS size to the entirety of the remaining extended memory. */
                        dev->ems_size = mem_size - 1024;

                    if (dev->regs[REG_RB7] & RB7_EMSEN) {
                        remap_update(dev, dev->regs[REG_RB7]);

                        neat_log("NEAT: EMS %iKB\n",
                                 dev->ems_size);
                    }

                    mem_a20_alt = !(val & RB12_GA20);
                    mem_a20_recalc();
                    flushmmucache();
                    break;

                default:
                    neat_log("NEAT: inv write to reg %02x (%02x)\n",
                             dev->indx, val);
                    break;
            }
            break;

        default:
            break;
    }
}

static uint8_t
neat_read(uint16_t port, void *priv)
{
    const neat_t *dev = (neat_t *) priv;
    uint8_t       ret = 0xff;

    switch (port) {
        case 0x22:
            ret = dev->indx;
            break;

        case 0x23:
            if ((dev->indx >= 0x60) && (dev->indx <= 0x6e))
                ret = dev->regs[dev->indx];
            else if (dev->indx == 0x6f)
                ret = (dev->regs[dev->indx] & 0xfd) | ((~mem_a20_alt) & 0x02);
            break;

        default:
            break;
    }

#if defined(ENABLE_NEAT_LOG) && (ENABLE_NEAT_LOG == 2)
    neat_log("NEAT: read(%04x) = %02x\n", port, ret);
#endif

    return ret;
}

static void
neat_close(void *priv)
{
    neat_t *dev = (neat_t *) priv;

    free(dev);
}

static void *
neat_init(UNUSED(const device_t *info))
{
    neat_t *dev;
    uint8_t dram_mode = 0;
    uint8_t j;

    /* Create an instance. */
    dev = (neat_t *) calloc(1, sizeof(neat_t));

    dev->sx = info->local;

    if (mem_size > 1024) {
        mem_mapping_set_handler(&ram_high_mapping, neat_read_ram, neat_read_ramw, NULL,
                                neat_write_ram, neat_write_ramw, NULL);
        mem_mapping_set_p(&ram_high_mapping, dev);
    }

    /* Get configured I/O address. */
    j              = (dev->regs[REG_RB9] & RB9_BASE) >> RB9_BASE_SH;
    dev->ems_base  = 0x0208 + (0x10 * j);

    /* Get configured frame address. */
    j               = (dev->regs[REG_RB9] & RB9_FRAME) >> RB9_FRAME_SH;
    dev->ems_frame  = 0xc0000 + (EMS_PGSIZE * j);

    ems_update_virt_base(dev);

    dev->ems_pages  = (mem_size << 10) / EMS_PGSIZE;
    dev->remap_base = mem_size;

    mem_mapping_disable(&ram_mid_mapping);

    for (int i = 0; i < 24; i++) {
       if (i >= 20)
           neat_mem_update_state(dev, 0x000a0000 + (i * EMS_PGSIZE), EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_SHADOW);
       else {
           /* This is needed to actually trigger an update. */
           dev->mem_flags[i + 40] = MEM_FLAG_ROMCS;
           neat_mem_update_state(dev, 0x000a0000 + (i * EMS_PGSIZE), EMS_PGSIZE, 0x00, MEM_FMASK_SHADOW);
       }
    }

    /*
     * For each supported page (we can have a maximum of 4),
     * create, initialize and disable the mappings, and set
     * up the I/O control handler.
     */
    for (uint8_t i = 0; i < EMS_MAXPAGE; i++) {
        /* Create and initialize a page mapping. */
        mem_mapping_add(&dev->ems[i].mapping,
                        0x00000000, 0x00000000,
                        ems_readb, ems_readw, NULL,
                        ems_writeb, ems_writew, NULL,
                        ram + dev->ems[i].virt_base, MEM_MAPPING_INTERNAL,
                        &(dev->ems[i]));

        /* Disable for now. */
        mem_mapping_disable(&dev->ems[i].mapping);
    }

    for (uint8_t i = 0; i < 32; i++) {
        dev->shadow[i].virt_base = dev->shadow[i].phys_base =
            (i * EMS_PGSIZE) + 0x00080000;
        dev->shadow[i].enabled   = 1;

        /* Create and initialize a page mapping. */
        mem_mapping_add(&dev->shadow[i].mapping,
                        dev->shadow[i].virt_base, EMS_PGSIZE,
                        ems_readb, ems_readw, NULL,
                        ems_writeb, ems_writew, NULL,
                        ram + dev->shadow[i].virt_base, MEM_MAPPING_INTERNAL,
                        &(dev->shadow[i]));

        /* Disable for now. */
        mem_mapping_disable(&dev->shadow[i].mapping);
    }

    /* Initialize some of the registers to specific defaults. */
    for (uint8_t i = REG_RA0; i <= REG_RB12; i++) {
        dev->indx = i;
        uint8_t def = defaults[dev->sx][i & REG_MASK];
        if ((i == REG_RA2) && (fpu_type == FPU_387))
            def |= RA2_387SX;
        neat_write(0x0023, def, dev);
    }

    /*
     * Based on the value of mem_size, we have to set up
     * a proper DRAM configuration (so that EMS works.)
     *
     * TODO: We might also want to set 'valid' waitstate
     *       bits, based on our cpu speed.
     */
    switch (mem_size) {
        case 512: /* 512KB */
            /* 256K, 0, 0, 0 */
            dev->regs[REG_RB6] &= ~RB6_BANKS;               /* one bank */
            dev->regs[REG_RB6] |= (RTYPE_256K << RTYPE_SH); /* 256K */
            dev->regs[REG_RB8] &= ~RB8_BANKS;               /* one bank */
            dev->regs[REG_RB8] |= (RTYPE_NONE << RTYPE_SH); /* NONE */
            dram_mode = 2;
            break;

        case 640: /* 640KB */
            /* 256K, 64K, 0, 0 */
            dev->regs[REG_RB6] |= RB6_BANKS;                 /* two banks */
            dev->regs[REG_RB6] |= (RTYPE_MIXED << RTYPE_SH); /* mixed */
            dev->regs[REG_RB8] &= ~RB8_BANKS;                /* one bank */
            dev->regs[REG_RB8] |= (RTYPE_NONE << RTYPE_SH);  /* NONE */
            dram_mode = 4;
            break;

        case 1024: /* 1MB */
            /* 256K, 256K, 0, 0 */
            dev->regs[REG_RB6] |= RB6_BANKS;                /* two banks */
            dev->regs[REG_RB6] |= (RTYPE_256K << RTYPE_SH); /* 256K */
            dev->regs[REG_RB8] &= ~RB8_BANKS;               /* one bank */
            dev->regs[REG_RB8] |= (RTYPE_NONE << RTYPE_SH); /* NONE */
            dram_mode = 5;
            break;

        case 1536: /* 1.5MB */
            /* 256K, 256K, 256K, 0 */
            dev->regs[REG_RB6] |= RB6_BANKS;                /* two banks */
            dev->regs[REG_RB6] |= (RTYPE_256K << RTYPE_SH); /* 256K */
            dev->regs[REG_RB8] &= ~RB8_BANKS;               /* one bank */
            dev->regs[REG_RB8] |= (RTYPE_256K << RTYPE_SH); /* 256K */
            dram_mode = 7;
            break;

        case 1664: /* 1.64MB */
            /* 256K, 64K, 256K, 256K */
            dev->regs[REG_RB6] |= RB6_BANKS;                 /* two banks */
            dev->regs[REG_RB6] |= (RTYPE_MIXED << RTYPE_SH); /* mixed */
            dev->regs[REG_RB8] |= RB8_BANKS;                 /* two banks */
            dev->regs[REG_RB8] |= (RTYPE_256K << RTYPE_SH);  /* 256K */
            dram_mode = 10;
            break;

        case 2048: /* 2MB */
#if 1
            /* 256K, 256K, 256K, 256K */
            dev->regs[REG_RB6] |= RB6_BANKS;                /* two banks */
            dev->regs[REG_RB6] |= (RTYPE_256K << RTYPE_SH); /* 256K */
            dev->regs[REG_RB8] |= RB8_BANKS;                /* two banks */
            dev->regs[REG_RB8] |= (RTYPE_256K << RTYPE_SH); /* 256K */
            dev->regs[REG_RB8] |= RB8_4WAY;                 /* 4way intl */
            dram_mode = 11;
#else
            /* 1M, 0, 0, 0 */
            dev->regs[REG_RB6] &= ~RB6_BANKS;               /* one bank */
            dev->regs[REG_RB6] |= (RTYPE_1M << RTYPE_SH);   /* 1M */
            dev->regs[REG_RB8] &= ~RB8_BANKS;               /* one bank */
            dev->regs[REG_RB8] |= (RTYPE_NONE << RTYPE_SH); /* NONE */
            dram_mode = 3;
#endif
            break;

        case 3072: /* 3MB */
            /* 256K, 256K, 1M, 0 */
            dev->regs[REG_RB6] |= RB6_BANKS;                /* two banks */
            dev->regs[REG_RB6] |= (RTYPE_256K << RTYPE_SH); /* 256K */
            dev->regs[REG_RB8] &= ~RB8_BANKS;               /* one bank */
            dev->regs[REG_RB8] |= (RTYPE_1M << RTYPE_SH);   /* 1M */
            dram_mode = 8;
            break;

        case 4096: /* 4MB */
            /* 1M, 1M, 0, 0 */
            dev->regs[REG_RB6] |= RB6_BANKS;                /* two banks */
            dev->regs[REG_RB6] |= (RTYPE_1M << RTYPE_SH);   /* 1M */
            dev->regs[REG_RB8] &= ~RB8_BANKS;               /* one bank */
            dev->regs[REG_RB8] |= (RTYPE_NONE << RTYPE_SH); /* NONE */
            dram_mode = 6;
            break;

        case 4224: /* 4.64MB */
            /* 256K, 64K, 1M, 1M */
            dev->regs[REG_RB6] |= RB6_BANKS;                 /* two banks */
            dev->regs[REG_RB6] |= (RTYPE_MIXED << RTYPE_SH); /* mixed */
            dev->regs[REG_RB8] |= RB8_BANKS;                 /* two banks */
            dev->regs[REG_RB8] |= (RTYPE_1M << RTYPE_SH);    /* 1M */
            dram_mode = 12;
            break;

        case 5120: /* 5MB */
            /* 256K, 256K, 1M, 1M */
            dev->regs[REG_RB6] |= RB6_BANKS;                /* two banks */
            dev->regs[REG_RB6] |= (RTYPE_256K << RTYPE_SH); /* 256K */
            dev->regs[REG_RB8] |= RB8_BANKS;                /* two banks */
            dev->regs[REG_RB8] |= (RTYPE_1M << RTYPE_SH);   /* 1M */
            dram_mode = 13;
            break;

        case 6144: /* 6MB */
            /* 1M, 1M, 1M, 0 */
            dev->regs[REG_RB6] |= RB6_BANKS;              /* two banks */
            dev->regs[REG_RB6] |= (RTYPE_1M << RTYPE_SH); /* 1M */
            dev->regs[REG_RB8] &= ~RB8_BANKS;             /* one bank */
            dev->regs[REG_RB8] |= (RTYPE_1M << RTYPE_SH); /* 1M */
            dram_mode = 9;
            break;

        case 8192: /* 8MB */
            /* 1M, 1M, 1M, 1M */
            dev->regs[REG_RB6] |= RB6_BANKS;              /* two banks */
            dev->regs[REG_RB6] |= (RTYPE_1M << RTYPE_SH); /* 1M */
            dev->regs[REG_RB8] |= RB8_BANKS;              /* two banks */
            dev->regs[REG_RB8] |= (RTYPE_1M << RTYPE_SH); /* 1M */
            dev->regs[REG_RB8] |= RB8_4WAY;               /* 4way intl */
            dram_mode = 14;
            break;

        default:
            neat_log("NEAT: **INVALID DRAM SIZE %iKB !**\n", mem_size);
    }
    if (dram_mode > 0) {
        neat_log("NEAT: using DRAM mode #%i (mem=%iKB)\n", dram_mode, mem_size);
    }

    /* Set up an I/O handler for the chipset. */
    io_sethandler(0x0022, 2,
                  neat_read, NULL, NULL, neat_write, NULL, NULL, dev);

    return dev;
}

const device_t neat_device = {
    .name          = "C&T CS8221 (NEAT)",
    .internal_name = "neat",
    .flags         = 0,
    .local         = 0,
    .init          = neat_init,
    .close         = neat_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};

const device_t neat_sx_device = {
    .name          = "C&T CS8281 (NEATsx)",
    .internal_name = "neat_sx",
    .flags         = 0,
    .local         = 1,
    .init          = neat_init,
    .close         = neat_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};
