/*
 * 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 Chips&Technology's SCAT (82C235) chipset.
 *
 *          Re-worked version based on the 82C235 datasheet and errata.
 *
 *
 *
 * Authors: Original by GreatPsycho for PCem.
 *          Fred N. van Kempen, <decwiz@yahoo.com>
 *
 *          Copyright 2017-2019 GreatPsycho.
 *          Copyright 2017-2019 Fred N. van Kempen.
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <86box/86box.h>
#include <86box/device.h>
#include "cpu.h"
#include "x86.h"
#include <86box/io.h>
#include <86box/mem.h>
#include <86box/nmi.h>
#include <86box/port_92.h>
#include <86box/rom.h>
#include <86box/chipset.h>

#define SCAT_DMA_WAIT_STATE_CONTROL     0x01
#define SCAT_VERSION                    0x40
#define SCAT_CLOCK_CONTROL              0x41
#define SCAT_PERIPHERAL_CONTROL         0x44
#define SCAT_MISCELLANEOUS_STATUS       0x45
#define SCAT_POWER_MANAGEMENT           0x46
#define SCAT_ROM_ENABLE                 0x48
#define SCAT_RAM_WRITE_PROTECT          0x49
#define SCAT_SHADOW_RAM_ENABLE_1        0x4A
#define SCAT_SHADOW_RAM_ENABLE_2        0x4B
#define SCAT_SHADOW_RAM_ENABLE_3        0x4C
#define SCAT_DRAM_CONFIGURATION         0x4D
#define SCAT_EXTENDED_BOUNDARY          0x4E
#define SCAT_EMS_CONTROL                0x4F

#define SCATSX_LAPTOP_FEATURES          0x60
#define SCATSX_FAST_VIDEO_CONTROL       0x61
#define SCATSX_FAST_VIDEORAM_ENABLE     0x62
#define SCATSX_HIGH_PERFORMANCE_REFRESH 0x63
#define SCATSX_CAS_TIMING_FOR_DMA       0x64

typedef struct ems_page_t {
    uint8_t valid;
    uint8_t pad;

    uint8_t regs_2x8;
    uint8_t regs_2x9;

    struct scat_t *scat;
} ems_page_t;

typedef struct scat_t {
    uint8_t  max_reg;
    uint8_t  reg_2xA;

    uint8_t  regs[256];

    uint32_t xms_bound;

    int      type;
    int      indx;

    int      external_is_RAS;

    ems_page_t null_page;
    ems_page_t page[32];

    mem_mapping_t low_mapping[32];
    mem_mapping_t remap_mapping[6];
    mem_mapping_t efff_mapping[44];
    mem_mapping_t ems_mapping[32];
} scat_t;

static const uint8_t max_map[32] = {
// clang-format off
    0, 1,  1,  1,  2,  3,  4,  8,
    4, 8, 12, 16, 20, 24, 28, 32,
    0, 5,  9, 13,  6, 10,  0,  0,
    0, 0,  0,  0,  0,  0,  0,  0
// clang-format om
};
static const uint8_t max_map_sx[32] = {
// clang-format off
     0,  1,  2,  1,  3,  4,  6, 10,
     5,  9, 13,  4,  8, 12, 16, 14,
    18, 22, 26, 20, 24, 28, 32, 18,
    20, 32,  0,  0,  0,  0,  0,  0
// clang-format om
};
static const uint8_t scatsx_external_is_RAS[33] = {
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 1, 0, 0, 1, 1,
    0, 0, 0, 0, 0, 0, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1,
    0
};

static uint8_t scat_in(uint16_t port, void *priv);
static void    scat_out(uint16_t port, uint8_t val, void *priv);

static void
shadow_state_update(scat_t *dev)
{
    int val;

    uint32_t base;
    uint32_t bit;
    uint32_t romcs;
    uint32_t shflags = 0;

    shadowbios = shadowbios_write = 0;

    for (uint8_t i = 0; i < 24; i++) {
        if ((dev->regs[SCAT_DRAM_CONFIGURATION] & 0xf) < 4)
            val = 0;
        else
            val = (dev->regs[SCAT_SHADOW_RAM_ENABLE_1 + (i >> 3)] >> (i & 7)) & 1;

        base  = 0xa0000 + (i << 14);
        bit   = (base - 0xc0000) >> 15;
        romcs = 0;

        if (base >= 0xc0000)
            romcs = dev->regs[SCAT_ROM_ENABLE] & (1 << bit);

        if (base >= 0xe0000) {
            shadowbios |= val;
            shadowbios_write |= val;
        }

        shflags = val ? MEM_READ_INTERNAL : (romcs ? MEM_READ_EXTANY : MEM_READ_EXTERNAL);
        shflags |= (val ? MEM_WRITE_INTERNAL : (romcs ? MEM_WRITE_EXTANY : MEM_WRITE_EXTERNAL));

        mem_set_mem_state(base, 0x4000, shflags);
    }

    flushmmucache();
}

static void
set_xms_bound(scat_t *dev, uint8_t val)
{
    uint32_t xms_max = ((dev->regs[SCAT_VERSION] & 0xf0) != 0 && ((val & 0x10) != 0)) || (dev->regs[SCAT_VERSION] >= 4) ? 0xfe0000 : 0xfc0000;

    switch (val & 0x0f) {
        case 1:
            dev->xms_bound = 0x100000;
            break;

        case 2:
            dev->xms_bound = 0x140000;
            break;

        case 3:
            dev->xms_bound = 0x180000;
            break;

        case 4:
            dev->xms_bound = 0x200000;
            break;

        case 5:
            dev->xms_bound = 0x300000;
            break;

        case 6:
            dev->xms_bound = 0x400000;
            break;

        case 7:
            dev->xms_bound = ((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? 0x600000 : 0x500000;
            break;

        case 8:
            dev->xms_bound = ((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? 0x800000 : 0x700000;
            break;

        case 9:
            dev->xms_bound = ((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? 0xa00000 : 0x800000;
            break;

        case 10:
            dev->xms_bound = ((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? 0xc00000 : 0x900000;
            break;

        case 11:
            dev->xms_bound = ((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? 0xe00000 : 0xa00000;
            break;

        case 12:
            dev->xms_bound = ((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? xms_max : 0xb00000;
            break;

        case 13:
            dev->xms_bound = ((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? xms_max : 0xc00000;
            break;

        case 14:
            dev->xms_bound = ((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? xms_max : 0xd00000;
            break;

        case 15:
            dev->xms_bound = ((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? xms_max : 0xf00000;
            break;

        default:
            dev->xms_bound = xms_max;
            break;
    }

    if ((((dev->regs[SCAT_VERSION] & 0xf0) == 0) && (val & 0x40) == 0 && (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x0f) == 3) || (((dev->regs[SCAT_VERSION] & 0xf0) != 0) && (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) == 3)) {
        if ((val & 0x0f) == 0 || dev->xms_bound > 0x160000)
            dev->xms_bound = 0x160000;

        if (dev->xms_bound > 0x100000)
            mem_set_mem_state(0x100000, dev->xms_bound - 0x100000,
                              MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);

        if (dev->xms_bound < 0x160000)
            mem_set_mem_state(dev->xms_bound, 0x160000 - dev->xms_bound,
                              MEM_READ_EXTANY | MEM_WRITE_EXTANY);
    } else {
        if (dev->xms_bound > xms_max)
            dev->xms_bound = xms_max;

        if (dev->xms_bound > 0x100000)
            mem_set_mem_state(0x100000, dev->xms_bound - 0x100000,
                              MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);

        if (dev->xms_bound < ((uint32_t) mem_size << 10))
            mem_set_mem_state(dev->xms_bound, (mem_size << 10) - dev->xms_bound,
                              MEM_READ_EXTANY | MEM_WRITE_EXTANY);
    }

    mem_mapping_set_addr(&dev->low_mapping[31], 0xf80000,
                         ((dev->regs[SCAT_VERSION] & 0xf0) != 0 && ((val & 0x10) != 0)) || (dev->regs[SCAT_VERSION] >= 4) ? 0x60000 : 0x40000);
    if (dev->regs[SCAT_VERSION] & 0xf0) {
        for (uint8_t i = 0; i < 8; i++) {
            if (val & 0x10)
                mem_mapping_disable(&bios_high_mapping);
            else
                mem_mapping_enable(&bios_high_mapping);
        }
    }
}

static uint32_t
get_addr(scat_t *dev, uint32_t addr, ems_page_t *p)
{
#if 1
    int      nbanks_2048k;
    int      nbanks_512k;
    uint32_t addr2;
    int      nbank;
#else
    uint32_t nbanks_2048k, nbanks_512k, addr2, nbank;
#endif

    if (p && p->valid && (dev->regs[SCAT_EMS_CONTROL] & 0x80) && (p->regs_2x9 & 0x80))
        addr = (addr & 0x3fff) | (((p->regs_2x9 & 3) << 8) | p->regs_2x8) << 14;

    if ((dev->regs[SCAT_VERSION] & 0xf0) == 0) {
        switch ((dev->regs[SCAT_EXTENDED_BOUNDARY] & ((dev->regs[SCAT_VERSION] & 0x0f) > 3 ? 0x40 : 0)) | (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x0f)) {
            case 0x41:
                nbank = addr >> 19;
                if (nbank < 4)
                    nbank = 1;
                else if (nbank == 4)
                    nbank = 0;
                else
                    nbank -= 3;
                break;

            case 0x42:
                nbank = addr >> 19;
                if (nbank < 8)
                    nbank = 1 + (nbank >> 2);
                else if (nbank == 8)
                    nbank = 0;
                else
                    nbank -= 6;
                break;

            case 0x43:
                nbank = addr >> 19;
                if (nbank < 12)
                    nbank = 1 + (nbank >> 2);
                else if (nbank == 12)
                    nbank = 0;
                else
                    nbank -= 9;
                break;

            case 0x44:
                nbank = addr >> 19;
                if (nbank < 4)
                    nbank = 2;
                else if (nbank < 6)
                    nbank -= 4;
                else
                    nbank -= 3;
                break;

            case 0x45:
                nbank = addr >> 19;
                if (nbank < 8)
                    nbank = 2 + (nbank >> 2);
                else if (nbank < 10)
                    nbank -= 8;
                else
                    nbank -= 6;
                break;

            default:
                nbank = addr >> (((dev->regs[SCAT_DRAM_CONFIGURATION] & 0x0f) < 8 && (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x40) == 0) ? 19 : 21);
                break;
        }

        nbank &= (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x80) ? 7 : 3;

        if ((dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x40) == 0 && (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x0f) == 3 && nbank == 2 && (addr & 0x7ffff) < 0x60000 && mem_size > 640) {
            nbank = 1;
            addr ^= 0x70000;
        }

        if (dev->external_is_RAS && (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x80) == 0) {
            if (nbank == 3)
                nbank = 7;
            else
                return 0xffffffff;
        } else if (!dev->external_is_RAS && dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x80) {
            switch (nbank) {
                case 7:
                    nbank = 3;
                    break;

                /* Note - In the following cases, the chipset accesses multiple memory banks
                          at the same time, so it's impossible to predict which memory bank
                          is actually accessed. */
                case 5:
                case 1:
                    nbank = 1;
                    break;

                case 3:
                    nbank = 2;
                    break;

                default:
                    nbank = 0;
                    break;
            }
        }

        if ((dev->regs[SCAT_VERSION] & 0x0f) > 3 && (mem_size > 2048) && (mem_size & 1536)) {
            if ((mem_size & 1536) == 512) {
                if (nbank == 0)
                    addr &= 0x7ffff;
                else
                    addr = 0x80000 + ((addr & 0x1fffff) | ((nbank - 1) << 21));
            } else {
                if (nbank < 2)
                    addr = (addr & 0x7ffff) | (nbank << 19);
                else
                    addr = 0x100000 + ((addr & 0x1fffff) | ((nbank - 2) << 21));
            }
        } else {
            if (mem_size <= ((dev->regs[SCAT_VERSION] & 0x0f) > 3 ? 2048 : 4096) && (((dev->regs[SCAT_DRAM_CONFIGURATION] & 0x0f) < 8) || dev->external_is_RAS)) {
                nbanks_2048k = 0;
                nbanks_512k  = mem_size >> 9;
            } else {
                nbanks_2048k = mem_size >> 11;
                nbanks_512k  = (mem_size & 1536) >> 9;
            }

            if (nbank < nbanks_2048k || (nbanks_2048k > 0 && nbank >= nbanks_2048k + nbanks_512k + ((mem_size & 511) >> 7))) {
                addr &= 0x1fffff;
                addr |= (nbank << 21);
            } else if (nbank < nbanks_2048k + nbanks_512k || nbank >= nbanks_2048k + nbanks_512k + ((mem_size & 511) >> 7)) {
                addr &= 0x7ffff;
                addr |= (nbanks_2048k << 21) | ((nbank - nbanks_2048k) << 19);
            } else {
                addr &= 0x1ffff;
                addr |= (nbanks_2048k << 21) | (nbanks_512k << 19) | ((nbank - nbanks_2048k - nbanks_512k) << 17);
            }
        }
    } else {
        switch (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) {
            case 0x02:
            case 0x04:
                nbank = addr >> 19;
                if ((nbank & (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x80 ? 7 : 3)) < 2) {
                    nbank = (addr >> 10) & 1;
                    addr2 = addr >> 11;
                } else
                    addr2 = addr >> 10;
                break;

            case 0x03:
                nbank = addr >> 19;
                if ((nbank & (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x80 ? 7 : 3)) < 2) {
                    nbank = (addr >> 10) & 1;
                    addr2 = addr >> 11;
                } else if ((nbank & (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x80 ? 7 : 3)) == 2 && (addr & 0x7ffff) < 0x60000) {
                    addr ^= 0x1f0000;
                    nbank = (addr >> 10) & 1;
                    addr2 = addr >> 11;
                } else
                    addr2 = addr >> 10;
                break;

            case 0x05:
                nbank = addr >> 19;
                if ((nbank & (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x80 ? 7 : 3)) < 4) {
                    nbank = (addr >> 10) & 3;
                    addr2 = addr >> 12;
                } else
                    addr2 = addr >> 10;
                break;

            case 0x06:
                nbank = addr >> 19;
                if (nbank < 2) {
                    nbank = (addr >> 10) & 1;
                    addr2 = addr >> 11;
                } else {
                    nbank = 2 + ((addr - 0x100000) >> 21);
                    addr2 = (addr - 0x100000) >> 11;
                }
                break;

            case 0x07:
            case 0x0f:
                nbank = addr >> 19;
                if (nbank < 2) {
                    nbank = (addr >> 10) & 1;
                    addr2 = addr >> 11;
                } else if (nbank < 10) {
                    nbank = 2 + (((addr - 0x100000) >> 11) & 1);
                    addr2 = (addr - 0x100000) >> 12;
                } else {
                    nbank = 4 + ((addr - 0x500000) >> 21);
                    addr2 = (addr - 0x500000) >> 11;
                }
                break;

            case 0x08:
                nbank = addr >> 19;
                if (nbank < 4) {
                    nbank = 1;
                    addr2 = addr >> 11;
                } else if (nbank == 4) {
                    nbank = 0;
                    addr2 = addr >> 10;
                } else {
                    nbank -= 3;
                    addr2 = addr >> 10;
                }
                break;

            case 0x09:
                nbank = addr >> 19;
                if (nbank < 8) {
                    nbank = 1 + ((addr >> 11) & 1);
                    addr2 = addr >> 12;
                } else if (nbank == 8) {
                    nbank = 0;
                    addr2 = addr >> 10;
                } else {
                    nbank -= 6;
                    addr2 = addr >> 10;
                }
                break;

            case 0x0a:
                nbank = addr >> 19;
                if (nbank < 8) {
                    nbank = 1 + ((addr >> 11) & 1);
                    addr2 = addr >> 12;
                } else if (nbank < 12) {
                    nbank = 3;
                    addr2 = addr >> 11;
                } else if (nbank == 12) {
                    nbank = 0;
                    addr2 = addr >> 10;
                } else {
                    nbank -= 9;
                    addr2 = addr >> 10;
                }
                break;

            case 0x0b:
                nbank = addr >> 21;
                addr2 = addr >> 11;
                break;

            case 0x0c:
            case 0x0d:
                nbank = addr >> 21;
                if ((nbank & (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x80 ? 7 : 3)) < 2) {
                    nbank = (addr >> 11) & 1;
                    addr2 = addr >> 12;
                } else
                    addr2 = addr >> 11;
                break;

            case 0x0e:
            case 0x13:
                nbank = addr >> 21;
                if ((nbank & (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x80 ? 7 : 3)) < 4) {
                    nbank = (addr >> 11) & 3;
                    addr2 = addr >> 13;
                } else
                    addr2 = addr >> 11;
                break;

            case 0x10:
            case 0x11:
                nbank = addr >> 19;
                if (nbank < 2) {
                    nbank = (addr >> 10) & 1;
                    addr2 = addr >> 11;
                } else if (nbank < 10) {
                    nbank = 2 + (((addr - 0x100000) >> 11) & 1);
                    addr2 = (addr - 0x100000) >> 12;
                } else if (nbank < 18) {
                    nbank = 4 + (((addr - 0x500000) >> 11) & 1);
                    addr2 = (addr - 0x500000) >> 12;
                } else {
                    nbank = 6 + ((addr - 0x900000) >> 21);
                    addr2 = (addr - 0x900000) >> 11;
                }
                break;

            case 0x12:
                nbank = addr >> 19;
                if (nbank < 2) {
                    nbank = (addr >> 10) & 1;
                    addr2 = addr >> 11;
                } else if (nbank < 10) {
                    nbank = 2 + (((addr - 0x100000) >> 11) & 1);
                    addr2 = (addr - 0x100000) >> 12;
                } else {
                    nbank = 4 + (((addr - 0x500000) >> 11) & 3);
                    addr2 = (addr - 0x500000) >> 13;
                }
                break;

            case 0x14:
            case 0x15:
                nbank = addr >> 21;
                if ((nbank & 7) < 4) {
                    nbank = (addr >> 11) & 3;
                    addr2 = addr >> 13;
                } else if ((nbank & 7) < 6) {
                    nbank = 4 + (((addr - 0x800000) >> 11) & 1);
                    addr2 = (addr - 0x800000) >> 12;
                } else {
                    nbank = 6 + (((addr - 0xc00000) >> 11) & 3);
                    addr2 = (addr - 0xc00000) >> 13;
                }
                break;

            case 0x16:
                nbank = ((addr >> 21) & 4) | ((addr >> 11) & 3);
                addr2 = addr >> 13;
                break;

            case 0x17:
                if (dev->external_is_RAS && (addr & 0x800) == 0)
                    return 0xffffffff;
                nbank = addr >> 19;
                if (nbank < 2) {
                    nbank = (addr >> 10) & 1;
                    addr2 = addr >> 11;
                } else {
                    nbank = 2 + ((addr - 0x100000) >> 23);
                    addr2 = (addr - 0x100000) >> 12;
                }
                break;

            case 0x18:
                if (dev->external_is_RAS && (addr & 0x800) == 0)
                    return 0xffffffff;
                nbank = addr >> 21;
                if (nbank < 4) {
                    nbank = 1;
                    addr2 = addr >> 12;
                } else if (nbank == 4) {
                    nbank = 0;
                    addr2 = addr >> 11;
                } else {
                    nbank -= 3;
                    addr2 = addr >> 11;
                }
                break;

            case 0x19:
                if (dev->external_is_RAS && (addr & 0x800) == 0)
                    return 0xffffffff;
                nbank = addr >> 23;
                if ((nbank & 3) < 2) {
                    nbank = (addr >> 12) & 1;
                    addr2 = addr >> 13;
                } else
                    addr2 = addr >> 12;
                break;

            default:
                if ((dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) < 6) {
                    nbank = addr >> 19;
                    addr2 = (addr >> 10) & 0x1ff;
                } else if ((dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) < 0x17) {
                    nbank = addr >> 21;
                    addr2 = (addr >> 11) & 0x3ff;
                } else {
                    nbank = addr >> 23;
                    addr2 = (addr >> 12) & 0x7ff;
                }
                break;
        }

        nbank &= (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x80) ? 7 : 3;

        if ((dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) > 0x16 && nbank == 3)
            return 0xffffffff;

        if (dev->external_is_RAS && (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x80) == 0) {
            if (nbank == 3)
                nbank = 7;
            else
                return 0xffffffff;
        } else if (!dev->external_is_RAS && dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x80) {
            switch (nbank) {
                case 7:
                    nbank = 3;
                    break;

                /* Note - In the following cases, the chipset accesses multiple memory banks
                        at the same time, so it's impossible to predict which memory bank
                        is actually accessed. */
                case 5:
                case 1:
                    nbank = 1;
                    break;

                case 3:
                    nbank = 2;
                    break;

                default:
                    nbank = 0;
                    break;
            }
        }

        switch (mem_size & ~511) {
            case 1024:
            case 1536:
                addr &= 0x3ff;
                if (nbank < 2)
                    addr |= (nbank << 10) | ((addr2 & 0x1ff) << 11);
                else
                    addr |= ((addr2 & 0x1ff) << 10) | (nbank << 19);
                break;

            case 2048:
                if ((dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) == 5) {
                    addr &= 0x3ff;
                    if (nbank < 4)
                        addr |= (nbank << 10) | ((addr2 & 0x1ff) << 12);
                    else
                        addr |= ((addr2 & 0x1ff) << 10) | (nbank << 19);
                } else {
                    addr &= 0x7ff;
                    addr |= ((addr2 & 0x3ff) << 11) | (nbank << 21);
                }
                break;

            case 2560:
                if (nbank == 0)
                    addr = (addr & 0x3ff) | ((addr2 & 0x1ff) << 10);
                else {
                    addr &= 0x7ff;
                    addr2 &= 0x3ff;
                    addr = addr + 0x80000 + ((addr2 << 11) | ((nbank - 1) << 21));
                }
                break;

            case 3072:
                if (nbank < 2)
                    addr = (addr & 0x3ff) | (nbank << 10) | ((addr2 & 0x1ff) << 11);
                else
                    addr = 0x100000 + ((addr & 0x7ff) | ((addr2 & 0x3ff) << 11) | ((nbank - 2) << 21));
                break;

            case 4096:
            case 6144:
                addr &= 0x7ff;
                if (nbank < 2)
                    addr |= (nbank << 11) | ((addr2 & 0x3ff) << 12);
                else
                    addr |= ((addr2 & 0x3ff) << 11) | (nbank << 21);
                break;

            case 4608:
                if (((dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) >= 8 && (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) <= 0x0a) || ((dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) == 0x18)) {
                    if (nbank == 0)
                        addr = (addr & 0x3ff) | ((addr2 & 0x1ff) << 10);
                    else if (nbank < 3)
                        addr = 0x80000 + ((addr & 0x7ff) | ((nbank - 1) << 11) | ((addr2 & 0x3ff) << 12));
                    else
                        addr = 0x480000 + ((addr & 0x3ff) | ((addr2 & 0x1ff) << 10) | ((nbank - 3) << 19));
                } else if (nbank == 0)
                    addr = (addr & 0x3ff) | ((addr2 & 0x1ff) << 10);
                else {
                    addr &= 0x7ff;
                    addr2 &= 0x3ff;
                    addr = addr + 0x80000 + ((addr2 << 11) | ((nbank - 1) << 21));
                }
                break;

            case 5120:
            case 7168:
                if (nbank < 2)
                    addr = (addr & 0x3ff) | (nbank << 10) | ((addr2 & 0x1ff) << 11);
                else if (nbank < 4)
                    addr = 0x100000 + ((addr & 0x7ff) | ((addr2 & 0x3ff) << 12) | ((nbank & 1) << 11));
                else
                    addr = 0x100000 + ((addr & 0x7ff) | ((addr2 & 0x3ff) << 11) | ((nbank - 2) << 21));
                break;

            case 6656:
                if (((dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) >= 8 && (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) <= 0x0a) || ((dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) == 0x18)) {
                    if (nbank == 0)
                        addr = (addr & 0x3ff) | ((addr2 & 0x1ff) << 10);
                    else if (nbank < 3)
                        addr = 0x80000 + ((addr & 0x7ff) | ((nbank - 1) << 11) | ((addr2 & 0x3ff) << 12));
                    else if (nbank == 3)
                        addr = 0x480000 + ((addr & 0x7ff) | ((addr2 & 0x3ff) << 11));
                    else
                        addr = 0x680000 + ((addr & 0x3ff) | ((addr2 & 0x1ff) << 10) | ((nbank - 4) << 19));
                } else if (nbank == 0)
                    addr = (addr & 0x3ff) | ((addr2 & 0x1ff) << 10);
                else if (nbank == 1) {
                    addr &= 0x7ff;
                    addr2 &= 0x3ff;
                    addr = addr + 0x80000 + (addr2 << 11);
                } else {
                    addr &= 0x7ff;
                    addr2 &= 0x3ff;
                    addr = addr + 0x280000 + ((addr2 << 12) | ((nbank & 1) << 11) | (((nbank - 2) & 6) << 21));
                }
                break;

            case 8192:
                addr &= 0x7ff;
                if (nbank < 4)
                    addr |= (nbank << 11) | ((addr2 & 0x3ff) << 13);
                else
                    addr |= ((addr2 & 0x3ff) << 11) | (nbank << 21);
                break;

            case 9216:
                if (nbank < 2)
                    addr = (addr & 0x3ff) | (nbank << 10) | ((addr2 & 0x1ff) << 11);
                else if (dev->external_is_RAS) {
                    if (nbank < 6)
                        addr = 0x100000 + ((addr & 0x7ff) | ((addr2 & 0x3ff) << 12) | ((nbank & 1) << 11));
                    else
                        addr = 0x100000 + ((addr & 0x7ff) | ((addr2 & 0x3ff) << 11) | ((nbank - 2) << 21));
                } else
                    addr = 0x100000 + ((addr & 0xfff) | ((addr2 & 0x7ff) << 12) | ((nbank - 2) << 23));
                break;

            case 10240:
                if (dev->external_is_RAS) {
                    addr &= 0x7ff;
                    if (nbank < 4)
                        addr |= (nbank << 11) | ((addr2 & 0x3ff) << 13);
                    else
                        addr |= ((addr2 & 0x3ff) << 11) | (nbank << 21);
                } else if (nbank == 0)
                    addr = (addr & 0x7ff) | ((addr2 & 0x3ff) << 11);
                else {
                    addr &= 0xfff;
                    addr2 &= 0x7ff;
                    addr = addr + 0x200000 + ((addr2 << 12) | ((nbank - 1) << 23));
                }
                break;

            case 11264:
                if (nbank < 2)
                    addr = (addr & 0x3ff) | (nbank << 10) | ((addr2 & 0x1ff) << 11);
                else if (nbank < 6)
                    addr = 0x100000 + ((addr & 0x7ff) | ((addr2 & 0x3ff) << 12) | ((nbank & 1) << 11));
                else
                    addr = 0x100000 + ((addr & 0x7ff) | ((addr2 & 0x3ff) << 11) | ((nbank - 2) << 21));
                break;

            case 12288:
                if (dev->external_is_RAS) {
                    addr &= 0x7ff;
                    if (nbank < 4)
                        addr |= (nbank << 11) | ((addr2 & 0x3ff) << 13);
                    else if (nbank < 6)
                        addr |= ((nbank & 1) << 11) | ((addr2 & 0x3ff) << 12) | ((nbank & 4) << 21);
                    else
                        addr |= ((addr2 & 0x3ff) << 11) | (nbank << 21);
                } else {
                    if (nbank < 2)
                        addr = (addr & 0x7ff) | (nbank << 11) | ((addr2 & 0x3ff) << 12);
                    else
                        addr = 0x400000 + ((addr & 0xfff) | ((addr2 & 0x7ff) << 12) | ((nbank - 2) << 23));
                }
                break;

            case 13312:
                if (nbank < 2)
                    addr = (addr & 0x3FF) | (nbank << 10) | ((addr2 & 0x1FF) << 11);
                else if (nbank < 4)
                    addr = 0x100000 + ((addr & 0x7FF) | ((addr2 & 0x3FF) << 12) | ((nbank & 1) << 11));
                else
                    addr = 0x500000 + ((addr & 0x7FF) | ((addr2 & 0x3FF) << 13) | ((nbank & 3) << 11));
                break;

            case 14336:
                addr &= 0x7ff;
                if (nbank < 4)
                    addr |= (nbank << 11) | ((addr2 & 0x3ff) << 13);
                else if (nbank < 6)
                    addr |= ((nbank & 1) << 11) | ((addr2 & 0x3ff) << 12) | ((nbank & 4) << 21);
                else
                    addr |= ((addr2 & 0x3ff) << 11) | (nbank << 21);
                break;

            case 16384:
                if (dev->external_is_RAS) {
                    addr &= 0x7ff;
                    addr2 &= 0x3ff;
                    addr |= ((nbank & 3) << 11) | (addr2 << 13) | ((nbank & 4) << 21);
                } else {
                    addr &= 0xfff;
                    addr2 &= 0x7ff;
                    if (nbank < 2)
                        addr |= (addr2 << 13) | (nbank << 12);
                    else
                        addr |= (addr2 << 12) | (nbank << 23);
                }
                break;

            default:
                if (mem_size < 2048 || ((mem_size & 1536) == 512) || (mem_size == 2048 && (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) < 6)) {
                    addr &= 0x3ff;
                    addr2 &= 0x1ff;
                    addr |= (addr2 << 10) | (nbank << 19);
                } else if (mem_size < 8192 || (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) < 0x17) {
                    addr &= 0x7ff;
                    addr2 &= 0x3ff;
                    addr |= (addr2 << 11) | (nbank << 21);
                } else {
                    addr &= 0xfff;
                    addr2 &= 0x7ff;
                    addr |= (addr2 << 12) | (nbank << 23);
                }
                break;
        }
    }

    return addr;
}

static void
set_global_EMS_state(scat_t *dev, int state)
{
    uint32_t base_addr;
    uint32_t virt_addr;
    int      conf;

    for (uint32_t i = ((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? 0 : 24; i < 32; i++) {
        base_addr = (i + 16) << 14;

        if (i >= 24)
            base_addr += 0x30000;
        if (state && (dev->page[i].regs_2x9 & 0x80)) {
            virt_addr = get_addr(dev, base_addr, &dev->page[i]);
            if (i < 24)
                mem_mapping_disable(&dev->efff_mapping[i]);
            else
                mem_mapping_disable(&dev->efff_mapping[i + 12]);
            mem_mapping_enable(&dev->ems_mapping[i]);

            if (virt_addr < ((uint32_t) mem_size << 10))
                mem_mapping_set_exec(&dev->ems_mapping[i], ram + virt_addr);
            else
                mem_mapping_set_exec(&dev->ems_mapping[i], NULL);
        } else {
            mem_mapping_set_exec(&dev->ems_mapping[i], ram + base_addr);
            mem_mapping_disable(&dev->ems_mapping[i]);

            conf = (dev->regs[SCAT_VERSION] & 0xf0) ? (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f)
                                                    : (dev->regs[SCAT_DRAM_CONFIGURATION] & 0xf) | ((dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x40) >> 2);
            if (i < 24) {
                if (conf > 1 || (conf == 1 && i < 16))
                    mem_mapping_enable(&dev->efff_mapping[i]);
                else
                    mem_mapping_disable(&dev->efff_mapping[i]);
            } else if (conf > 3 || ((dev->regs[SCAT_VERSION] & 0xf0) != 0 && conf == 2))
                mem_mapping_enable(&dev->efff_mapping[i + 12]);
            else
                mem_mapping_disable(&dev->efff_mapping[i + 12]);
        }
    }

    flushmmucache();
}

static void
memmap_state_update(scat_t *dev)
{
    uint32_t addr;

    for (uint8_t i = (((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? 0 : 16); i < 44; i++) {
        addr = get_addr(dev, 0x40000 + (i << 14), &dev->null_page);
        mem_mapping_set_exec(&dev->efff_mapping[i],
                             addr < ((uint32_t) mem_size << 10) ? ram + addr : NULL);
    }

    addr = get_addr(dev, 0, &dev->null_page);
    mem_mapping_set_exec(&dev->low_mapping[0],
                         addr < ((uint32_t) mem_size << 10) ? ram + addr : NULL);

    addr = get_addr(dev, 0xf0000, &dev->null_page);
    mem_mapping_set_exec(&dev->low_mapping[1],
                         addr < ((uint32_t) mem_size << 10) ? ram + addr : NULL);

    for (uint8_t i = 2; i < 32; i++) {
        addr = get_addr(dev, i << 19, &dev->null_page);
        mem_mapping_set_exec(&dev->low_mapping[i],
                             addr < ((uint32_t) mem_size << 10) ? ram + addr : NULL);
    }

    if ((dev->regs[SCAT_VERSION] & 0xf0) == 0) {
        uint8_t j = 0;

        for (j = 0; j < max_map[(dev->regs[SCAT_DRAM_CONFIGURATION] & 0x0f) | ((dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x40) >> 2)]; j++)
            mem_mapping_enable(&dev->low_mapping[j]);

        for (; j < 32; j++)
            mem_mapping_disable(&dev->low_mapping[j]);

        for (j = 24; j < 36; j++) {
            if (((dev->regs[SCAT_DRAM_CONFIGURATION] & 0x0f) | (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x40)) < 4)
                mem_mapping_disable(&dev->efff_mapping[j]);
            else
                mem_mapping_enable(&dev->efff_mapping[j]);
        }
    } else {
        uint8_t j = 0;
        for (j = 0; j < max_map_sx[dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f]; j++)
            mem_mapping_enable(&dev->low_mapping[j]);

        for (; j < 32; j++)
            mem_mapping_disable(&dev->low_mapping[j]);

        for (j = 24; j < 36; j++) {
            if ((dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) < 2 || (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) == 3)
                mem_mapping_disable(&dev->efff_mapping[j]);
            else
                mem_mapping_enable(&dev->efff_mapping[j]);
        }
    }

    if ((((dev->regs[SCAT_VERSION] & 0xf0) == 0) && (dev->regs[SCAT_EXTENDED_BOUNDARY] & 0x40) == 0) || ((dev->regs[SCAT_VERSION] & 0xf0) != 0)) {
        if ((((dev->regs[SCAT_VERSION] & 0xf0) == 0) && (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x0f) == 3) || (((dev->regs[SCAT_VERSION] & 0xf0) != 0) && (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) == 3)) {
            mem_mapping_disable(&dev->low_mapping[2]);

            for (uint8_t i = 0; i < 6; i++) {
                addr = get_addr(dev, 0x100000 + (i << 16), &dev->null_page);
                mem_mapping_set_exec(&dev->remap_mapping[i],
                                     addr < ((uint32_t) mem_size << 10) ? ram + addr : NULL);
                mem_mapping_enable(&dev->remap_mapping[i]);
            }
        } else {
            for (uint8_t i = 0; i < 6; i++)
                mem_mapping_disable(&dev->remap_mapping[i]);

            if ((((dev->regs[SCAT_VERSION] & 0xf0) == 0) && (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x0f) > 4) || (((dev->regs[SCAT_VERSION] & 0xf0) != 0) && (dev->regs[SCAT_DRAM_CONFIGURATION] & 0x1f) > 3))
                mem_mapping_enable(&dev->low_mapping[2]);
        }
    } else {
        for (uint8_t i = 0; i < 6; i++)
            mem_mapping_disable(&dev->remap_mapping[i]);

        mem_mapping_enable(&dev->low_mapping[2]);
    }

    set_global_EMS_state(dev, dev->regs[SCAT_EMS_CONTROL] & 0x80);

    flushmmucache_nopc();
}

static void
scat_out(uint16_t port, uint8_t val, void *priv)
{
    scat_t  *dev           = (scat_t *) priv;
    uint8_t  reg_valid     = 0;
    uint8_t  shadow_update = 0;
    uint8_t  map_update    = 0;
    uint8_t  indx;
    uint32_t base_addr;
    uint32_t virt_addr;

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

        case 0x23:
            switch (dev->indx) {
                case SCAT_DMA_WAIT_STATE_CONTROL:
                case SCAT_CLOCK_CONTROL:
                case SCAT_PERIPHERAL_CONTROL:
                    reg_valid = 1;
                    break;

                case SCAT_EMS_CONTROL:
                    io_removehandler(0x0208, 0x0003, scat_in, NULL, NULL, scat_out, NULL, NULL, dev);
                    io_removehandler(0x0218, 0x0003, scat_in, NULL, NULL, scat_out, NULL, NULL, dev);

                    if (val & 0x40) {
                        if (val & 1)
                            io_sethandler(0x0218, 3, scat_in, NULL, NULL, scat_out, NULL, NULL, dev);
                        else
                            io_sethandler(0x0208, 3, scat_in, NULL, NULL, scat_out, NULL, NULL, dev);
                    }
                    set_global_EMS_state(dev, val & 0x80);
                    reg_valid = 1;
                    break;

                case SCAT_POWER_MANAGEMENT:
                    /* TODO - Only use AUX parity disable bit for this version.
                              Other bits should be implemented later. */
                    val &= (dev->regs[SCAT_VERSION] & 0xf0) == 0 ? 0x40 : 0x60;
                    reg_valid = 1;
                    break;

                case SCAT_DRAM_CONFIGURATION:
                    map_update = 1;

                    if ((dev->regs[SCAT_VERSION] & 0xf0) == 0) {
                        cpu_waitstates = (val & 0x70) == 0 ? 1 : 2;
                        cpu_update_waitstates();
                    }

                    reg_valid = 1;
                    break;

                case SCAT_EXTENDED_BOUNDARY:
                    if ((dev->regs[SCAT_VERSION] & 0xf0) == 0) {
                        if (dev->regs[SCAT_VERSION] < 4) {
                            val &= 0xbf;
                            set_xms_bound(dev, val & 0x0f);
                        } else {
                            val = (val & 0x7f) | 0x80;
                            set_xms_bound(dev, val & 0x4f);
                        }
                    } else
                        set_xms_bound(dev, val & 0x1f);

                    mem_set_mem_state(0x40000, 0x60000, (val & 0x20) ? MEM_READ_EXTANY | MEM_WRITE_EXTANY : MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                    if ((val ^ dev->regs[SCAT_EXTENDED_BOUNDARY]) & 0xc0)
                        map_update = 1;
                    reg_valid = 1;
                    break;

                case SCAT_ROM_ENABLE:
                case SCAT_RAM_WRITE_PROTECT:
                case SCAT_SHADOW_RAM_ENABLE_1:
                case SCAT_SHADOW_RAM_ENABLE_2:
                case SCAT_SHADOW_RAM_ENABLE_3:
                    reg_valid     = 1;
                    shadow_update = 1;
                    break;

                case SCATSX_LAPTOP_FEATURES:
                    if ((dev->regs[SCAT_VERSION] & 0xf0) != 0) {
                        val       = (val & ~8) | (dev->regs[SCATSX_LAPTOP_FEATURES] & 8);
                        reg_valid = 1;
                    }
                    break;

                case SCATSX_FAST_VIDEO_CONTROL:
                case SCATSX_FAST_VIDEORAM_ENABLE:
                case SCATSX_HIGH_PERFORMANCE_REFRESH:
                case SCATSX_CAS_TIMING_FOR_DMA:
                    if ((dev->regs[SCAT_VERSION] & 0xf0) != 0)
                        reg_valid = 1;
                    break;

                default:
                    break;
            }

            if (reg_valid)
                dev->regs[dev->indx] = val;

            if (shadow_update)
                shadow_state_update(dev);

            if (map_update)
                memmap_state_update(dev);
            break;

        case 0x208:
        case 0x218:
            if ((dev->regs[SCAT_EMS_CONTROL] & 0x41) == (0x40 | ((port & 0x10) >> 4))) {
                if ((dev->regs[SCAT_VERSION] & 0xf0) == 0)
                    indx = dev->reg_2xA & 0x1f;
                else
                    indx = ((dev->reg_2xA & 0x40) >> 4) + (dev->reg_2xA & 0x3) + 24;
                dev->page[indx].regs_2x8 = val;
                base_addr                = (indx + 16) << 14;
                if (indx >= 24)
                    base_addr += 0x30000;

                if ((dev->regs[SCAT_EMS_CONTROL] & 0x80) && (dev->page[indx].regs_2x9 & 0x80)) {
                    virt_addr = get_addr(dev, base_addr, &dev->page[indx]);
                    if (virt_addr < ((uint32_t) mem_size << 10))
                        mem_mapping_set_exec(&dev->ems_mapping[indx], ram + virt_addr);
                    else
                        mem_mapping_set_exec(&dev->ems_mapping[indx], NULL);
                    flushmmucache();
                }
            }
            break;

        case 0x209:
        case 0x219:
            if ((dev->regs[SCAT_EMS_CONTROL] & 0x41) == (0x40 | ((port & 0x10) >> 4))) {
                if ((dev->regs[SCAT_VERSION] & 0xf0) == 0)
                    indx = dev->reg_2xA & 0x1f;
                else
                    indx = ((dev->reg_2xA & 0x40) >> 4) + (dev->reg_2xA & 0x3) + 24;
                dev->page[indx].regs_2x9 = val;
                base_addr                = (indx + 16) << 14;
                if (indx >= 24)
                    base_addr += 0x30000;

                if (dev->regs[SCAT_EMS_CONTROL] & 0x80) {
                    if (val & 0x80) {
                        virt_addr = get_addr(dev, base_addr, &dev->page[indx]);
                        if (indx < 24)
                            mem_mapping_disable(&dev->efff_mapping[indx]);
                        else
                            mem_mapping_disable(&dev->efff_mapping[indx + 12]);
                        if (virt_addr < ((uint32_t) mem_size << 10))
                            mem_mapping_set_exec(&dev->ems_mapping[indx], ram + virt_addr);
                        else
                            mem_mapping_set_exec(&dev->ems_mapping[indx], NULL);
                        mem_mapping_enable(&dev->ems_mapping[indx]);
                    } else {
                        mem_mapping_set_exec(&dev->ems_mapping[indx], ram + base_addr);
                        mem_mapping_disable(&dev->ems_mapping[indx]);
                        if (indx < 24)
                            mem_mapping_enable(&dev->efff_mapping[indx]);
                        else
                            mem_mapping_enable(&dev->efff_mapping[indx + 12]);
                    }

                    flushmmucache();
                }

                if (dev->reg_2xA & 0x80)
                    dev->reg_2xA = (dev->reg_2xA & 0xe0) | ((dev->reg_2xA + 1) & (((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? 0x1f : 3));
            }
            break;

        case 0x20a:
        case 0x21a:
            if ((dev->regs[SCAT_EMS_CONTROL] & 0x41) == (0x40 | ((port & 0x10) >> 4)))
                dev->reg_2xA = ((dev->regs[SCAT_VERSION] & 0xf0) == 0) ? val : val & 0xc3;
            break;

        default:
            break;
    }
}

static uint8_t
scat_in(uint16_t port, void *priv)
{
    const scat_t *dev = (scat_t *) priv;
    uint8_t       ret = 0xff;
    uint8_t       indx;

    switch (port) {
        case 0x23:
            switch (dev->indx) {
                case SCAT_MISCELLANEOUS_STATUS:
                    ret = (dev->regs[dev->indx] & 0x3f) | (~nmi_mask & 0x80) | ((mem_a20_key & 2) << 5);
                    break;

                case SCAT_DRAM_CONFIGURATION:
                    if ((dev->regs[SCAT_VERSION] & 0xf0) == 0)
                        ret = (dev->regs[dev->indx] & 0x8f) | (cpu_waitstates == 1 ? 0 : 0x10);
                    else
                        ret = dev->regs[dev->indx];
                    break;

                case SCAT_EXTENDED_BOUNDARY:
                    ret = dev->regs[dev->indx];
                    if ((dev->regs[SCAT_VERSION] & 0xf0) == 0) {
                        if ((dev->regs[SCAT_VERSION] & 0x0f) >= 4)
                            ret |= 0x80;
                        else
                            ret &= 0xaf;
                    }
                    break;

                default:
                    if (dev->indx <= dev->max_reg)
                        ret = dev->regs[dev->indx];
                    break;
            }
            break;

        case 0x208:
        case 0x218:
            if ((dev->regs[SCAT_EMS_CONTROL] & 0x41) == (0x40 | ((port & 0x10) >> 4))) {
                if ((dev->regs[SCAT_VERSION] & 0xf0) == 0)
                    indx = dev->reg_2xA & 0x1f;
                else
                    indx = ((dev->reg_2xA & 0x40) >> 4) + (dev->reg_2xA & 0x3) + 24;
                ret = dev->page[indx].regs_2x8;
            }
            break;

        case 0x209:
        case 0x219:
            if ((dev->regs[SCAT_EMS_CONTROL] & 0x41) == (0x40 | ((port & 0x10) >> 4))) {
                if ((dev->regs[SCAT_VERSION] & 0xf0) == 0)
                    indx = dev->reg_2xA & 0x1f;
                else
                    indx = ((dev->reg_2xA & 0x40) >> 4) + (dev->reg_2xA & 0x3) + 24;
                ret = dev->page[indx].regs_2x9;
            }
            break;

        case 0x20a:
        case 0x21a:
            if ((dev->regs[SCAT_EMS_CONTROL] & 0x41) == (0x40 | ((port & 0x10) >> 4)))
                ret = dev->reg_2xA;
            break;
        default:
            break;
    }

    return ret;
}

static uint8_t
mem_read_scatb(uint32_t addr, void *priv)
{
    ems_page_t *page = (ems_page_t *) priv;
    scat_t     *dev  = (scat_t *) page->scat;
    uint8_t     val  = 0xff;

    addr = get_addr(dev, addr, page);
    if (addr < ((uint32_t) mem_size << 10))
        val = ram[addr];

    return val;
}

static uint16_t
mem_read_scatw(uint32_t addr, void *priv)
{
    ems_page_t *page = (ems_page_t *) priv;
    scat_t     *dev  = (scat_t *) page->scat;
    uint16_t    val  = 0xffff;

    addr = get_addr(dev, addr, page);
    if (addr < ((uint32_t) mem_size << 10))
        val = *(uint16_t *) &ram[addr];

    return val;
}

static uint32_t
mem_read_scatl(uint32_t addr, void *priv)
{
    ems_page_t *page = (ems_page_t *) priv;
    scat_t     *dev  = (scat_t *) page->scat;
    uint32_t    val  = 0xffffffff;

    addr = get_addr(dev, addr, page);
    if (addr < ((uint32_t) mem_size << 10))
        val = *(uint32_t *) &ram[addr];

    return val;
}

static void
mem_write_scatb(uint32_t addr, uint8_t val, void *priv)
{
    ems_page_t *page    = (ems_page_t *) priv;
    scat_t     *dev     = (scat_t *) page->scat;
    uint32_t    oldaddr = addr;
    uint32_t    chkaddr;

    addr    = get_addr(dev, addr, page);
    chkaddr = page->valid ? addr : oldaddr;
    if ((chkaddr >= 0xc0000) && (chkaddr < 0x100000)) {
        if (dev->regs[SCAT_RAM_WRITE_PROTECT] & (1 << ((chkaddr - 0xc0000) >> 15)))
            return;
    }

    if (addr < ((uint32_t) mem_size << 10))
        ram[addr] = val;
}

static void
mem_write_scatw(uint32_t addr, uint16_t val, void *priv)
{
    ems_page_t *page    = (ems_page_t *) priv;
    scat_t     *dev     = (scat_t *) page->scat;
    uint32_t    oldaddr = addr;
    uint32_t    chkaddr;

    addr    = get_addr(dev, addr, page);
    chkaddr = page->valid ? addr : oldaddr;
    if ((chkaddr >= 0xc0000) && (chkaddr < 0x100000)) {
        if (dev->regs[SCAT_RAM_WRITE_PROTECT] & (1 << ((chkaddr - 0xc0000) >> 15)))
            return;
    }

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

static void
mem_write_scatl(uint32_t addr, uint32_t val, void *priv)
{
    ems_page_t *page    = (ems_page_t *) priv;
    scat_t     *dev     = (scat_t *) page->scat;
    uint32_t    oldaddr = addr;
    uint32_t    chkaddr;

    addr    = get_addr(dev, addr, page);
    chkaddr = page->valid ? addr : oldaddr;
    if ((chkaddr >= 0xc0000) && (chkaddr < 0x100000)) {
        if (dev->regs[SCAT_RAM_WRITE_PROTECT] & (1 << ((chkaddr - 0xc0000) >> 15)))
            return;
    }

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

static void
scat_close(void *priv)
{
    scat_t *dev = (scat_t *) priv;

    free(dev);
}

static void *
scat_init(const device_t *info)
{
    scat_t  *dev;
    uint32_t j;
    uint32_t k;
    int      sx;

    dev = (scat_t *) malloc(sizeof(scat_t));
    memset(dev, 0x00, sizeof(scat_t));
    dev->type = info->local;

    sx = (dev->type == 32) ? 1 : 0;

    dev->max_reg = sx ? 0x64 : 0x4f;

    for (uint32_t i = 0; i < sizeof(dev->regs); i++)
        dev->regs[i] = 0xff;

    if (sx) {
        dev->regs[SCAT_VERSION]                    = 0x13;
        dev->regs[SCAT_CLOCK_CONTROL]              = 6;
        dev->regs[SCAT_PERIPHERAL_CONTROL]         = 0;
        dev->regs[SCAT_DRAM_CONFIGURATION]         = 1;
        dev->regs[SCATSX_LAPTOP_FEATURES]          = 0;
        dev->regs[SCATSX_FAST_VIDEO_CONTROL]       = 0;
        dev->regs[SCATSX_FAST_VIDEORAM_ENABLE]     = 0;
        dev->regs[SCATSX_HIGH_PERFORMANCE_REFRESH] = 8;
        dev->regs[SCATSX_CAS_TIMING_FOR_DMA]       = 3;
    } else {
        switch (dev->type) {
            case 4:
                dev->regs[SCAT_VERSION] = 4;
                break;

            default:
                dev->regs[SCAT_VERSION] = 1;
                break;
        }
        dev->regs[SCAT_CLOCK_CONTROL]      = 2;
        dev->regs[SCAT_PERIPHERAL_CONTROL] = 0x80;
        dev->regs[SCAT_DRAM_CONFIGURATION] = cpu_waitstates == 1 ? 2 : 0x12;
    }
    dev->regs[SCAT_DMA_WAIT_STATE_CONTROL] = 0;
    dev->regs[SCAT_MISCELLANEOUS_STATUS]   = 0x37;
    dev->regs[SCAT_ROM_ENABLE]             = 0xc0;
    dev->regs[SCAT_RAM_WRITE_PROTECT]      = 0;
    dev->regs[SCAT_POWER_MANAGEMENT]       = 0;
    dev->regs[SCAT_SHADOW_RAM_ENABLE_1]    = 0;
    dev->regs[SCAT_SHADOW_RAM_ENABLE_2]    = 0;
    dev->regs[SCAT_SHADOW_RAM_ENABLE_3]    = 0;
    dev->regs[SCAT_EXTENDED_BOUNDARY]      = 0;
    dev->regs[SCAT_EMS_CONTROL]            = 0;

    /* Disable all system mappings, we will override them. */
    mem_mapping_disable(&ram_low_mapping);
    if (!sx)
        mem_mapping_disable(&ram_mid_mapping);
    mem_mapping_disable(&ram_high_mapping);

    k = sx ? 0x80000 : 0x40000;

    dev->null_page.valid    = 0;
    dev->null_page.regs_2x8 = 0xff;
    dev->null_page.regs_2x9 = 0xff;
    dev->null_page.scat     = dev;

    mem_mapping_add(&dev->low_mapping[0], 0, k,
                    mem_read_scatb, mem_read_scatw, mem_read_scatl,
                    mem_write_scatb, mem_write_scatw, mem_write_scatl,
                    ram, MEM_MAPPING_INTERNAL, &dev->null_page);

    mem_mapping_add(&dev->low_mapping[1], 0xf0000, 0x10000,
                    mem_read_scatb, mem_read_scatw, mem_read_scatl,
                    mem_write_scatb, mem_write_scatw, mem_write_scatl,
                    ram + 0xf0000, MEM_MAPPING_INTERNAL, &dev->null_page);

    for (uint8_t i = 2; i < 32; i++) {
        mem_mapping_add(&dev->low_mapping[i], (i << 19), 0x80000,
                        mem_read_scatb, mem_read_scatw, mem_read_scatl,
                        mem_write_scatb, mem_write_scatw, mem_write_scatl,
                        ram + (i << 19), MEM_MAPPING_INTERNAL, &dev->null_page);
    }

    if (sx) {
        j = 16;
        k = 0x40000;
    } else {
        j = 0;
        k = (dev->regs[SCAT_VERSION] < 4) ? 0x40000 : 0x60000;
    }
    mem_mapping_set_addr(&dev->low_mapping[31], 0xf80000, k);

    for (; j < 44; j++) {
        mem_mapping_add(&dev->efff_mapping[j], 0x40000 + (j << 14), 0x4000,
                        mem_read_scatb, mem_read_scatw, mem_read_scatl,
                        mem_write_scatb, mem_write_scatw, mem_write_scatl,
                        mem_size > (256 + (j << 4)) ? ram + 0x40000 + (j << 14) : NULL,
                        MEM_MAPPING_INTERNAL, &dev->null_page);

        if (sx)
            mem_mapping_enable(&dev->efff_mapping[j]);
    }

    if (sx) {
        for (uint8_t i = 24; i < 32; i++) {
            dev->page[i].valid    = 1;
            dev->page[i].regs_2x8 = 0xff;
            dev->page[i].regs_2x9 = 0x03;
            dev->page[i].scat     = dev;
            mem_mapping_add(&dev->ems_mapping[i], (i + 28) << 14, 0x04000,
                            mem_read_scatb, mem_read_scatw, mem_read_scatl,
                            mem_write_scatb, mem_write_scatw, mem_write_scatl,
                            ram + ((i + 28) << 14), 0, &dev->page[i]);
            mem_mapping_disable(&dev->ems_mapping[i]);
        }
    } else {
        for (uint8_t i = 0; i < 32; i++) {
            dev->page[i].valid    = 1;
            dev->page[i].regs_2x8 = 0xff;
            dev->page[i].regs_2x9 = 0x03;
            dev->page[i].scat     = dev;
            mem_mapping_add(&dev->ems_mapping[i], (i + (i >= 24 ? 28 : 16)) << 14, 0x04000,
                            mem_read_scatb, mem_read_scatw, mem_read_scatl,
                            mem_write_scatb, mem_write_scatw, mem_write_scatl,
                            ram + ((i + (i >= 24 ? 28 : 16)) << 14),
                            0, &dev->page[i]);
        }
    }

    for (uint8_t i = 0; i < 6; i++) {
        mem_mapping_add(&dev->remap_mapping[i], 0x100000 + (i << 16), 0x10000,
                        mem_read_scatb, mem_read_scatw, mem_read_scatl,
                        mem_write_scatb, mem_write_scatw, mem_write_scatl,
                        mem_size >= 1024 ? ram + get_addr(dev, 0x100000 + (i << 16), &dev->null_page) : NULL,
                        MEM_MAPPING_INTERNAL, &dev->null_page);
    }

    if (sx) {
        dev->external_is_RAS = scatsx_external_is_RAS[mem_size >> 9];
    } else {
        dev->external_is_RAS = (dev->regs[SCAT_VERSION] > 3) || (((mem_size & ~2047) >> 11) + ((mem_size & 1536) >> 9) + ((mem_size & 511) >> 7)) > 4;
    }

    set_xms_bound(dev, 0);
    memmap_state_update(dev);
    shadow_state_update(dev);

    io_sethandler(0x0022, 2,
                  scat_in, NULL, NULL, scat_out, NULL, NULL, dev);

    device_add(&port_92_device);

    return dev;
}

const device_t scat_device = {
    .name          = "C&T SCAT (v1)",
    .internal_name = "scat",
    .flags         = 0,
    .local         = 0,
    .init          = scat_init,
    .close         = scat_close,
    .reset         = NULL,
    { .available = NULL },
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};

const device_t scat_4_device = {
    .name          = "C&T SCAT (v4)",
    .internal_name = "scat_4",
    .flags         = 0,
    .local         = 4,
    .init          = scat_init,
    .close         = scat_close,
    .reset         = NULL,
    { .available = NULL },
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};

const device_t scat_sx_device = {
    .name          = "C&T SCATsx",
    .internal_name = "scat_sx",
    .flags         = 0,
    .local         = 32,
    .init          = scat_init,
    .close         = scat_close,
    .reset         = NULL,
    { .available = NULL },
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};
