/*
 * 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 VLSI 82C311 ("SCAMP") 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: Sarah Walker, <https://pcem-emulator.co.uk/>
 *
 *          Copyright 2020 Sarah Walker.
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <86box/86box.h>
#include "cpu.h"
#include <86box/timer.h>
#include <86box/device.h>
#include <86box/io.h>
#include <86box/mem.h>
#include <86box/nmi.h>
#include <86box/plat_unused.h>
#include <86box/port_92.h>
#include <86box/chipset.h>

#define CFG_ID         0x00
#define CFG_SLTPTR     0x02
#define CFG_RAMMAP     0x03
#define CFG_EMSEN1     0x0b
#define CFG_EMSEN2     0x0c
#define CFG_ABAXS      0x0e
#define CFG_CAXS       0x0f
#define CFG_DAXS       0x10
#define CFG_FEAXS      0x11

#define ID_VL82C311    0xd6

#define RAMMAP_ROMMOV  0x60
#define RAMMAP_ROMMOV1 (1 << 6)
#define RAMMAP_ROMMOV0 (1 << 5)
#define RAMMAP_REMP384 (1 << 4)

#define EMSEN1_EMSMAP  (1 << 4)
#define EMSEN1_BFENAB  (1 << 6)
#define EMSEN1_EMSENAB (1 << 7)

#define NR_ELEMS(x)    (sizeof(x) / sizeof(x[0]))

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

#define MEM_FLAG_SLOTBUS    0x40
#define MEM_FLAG_REMAP      0x20
#define MEM_FLAG_MEMCARD    0x10
#define MEM_FLAG_EMS        0x08
#define MEM_FLAG_ROMCS      0x04
#define MEM_FLAG_READ       0x02
#define MEM_FLAG_WRITE      0x01
#define MEM_FMASK_SLOTBUS   0x40
#define MEM_FMASK_REMAP     0x20
#define MEM_FMASK_MEMCARD   0x10
#define MEM_FMASK_EMS       0x08
#define MEM_FMASK_ROMCS     0x04
#define MEM_FMASK_RW        0x03

/*Commodore SL386SX requires proper memory slot decoding to detect memory size.
  Therefore we emulate the SCAMP memory address decoding, and therefore are
  limited to the DRAM combinations supported by the actual chip*/
enum {
    BANK_NONE,
    BANK_256K,
    BANK_256K_INTERLEAVED,
    BANK_1M,
    BANK_1M_INTERLEAVED,
    BANK_4M,
    BANK_4M_INTERLEAVED
};

typedef struct ram_struct_t {
    void *parent;
    int   bank;
} ram_struct_t;

typedef struct card_mem_t {
    int       in_ram;
    uint32_t  virt_addr;
    uint32_t  phys_addr;
    uint8_t  *mem;
} mem_page_t;

typedef struct scamp_t {
    int           cfg_index;
    uint8_t       cfg_regs[256];
    int           cfg_enable;
    int           ram_config;

    int           ems_index;
    int           ems_autoinc;
    uint16_t      ems[64];

    mem_mapping_t ram_mapping[2];
    ram_struct_t  ram_struct[2];

    uint32_t      ram_virt_base[2];
    uint32_t      ram_phys_base[2];
    uint32_t      ram_mask[2];
    int           row_virt_shift[2];
    int           row_phys_shift[2];
    int           ram_interleaved[2];
    int           ibank_shift[2];

    int           mem_flags[64];
    mem_mapping_t mem_mappings[64]; /* The entire first 1 MB of memory space. */
    mem_page_t    mem_pages[64];

    uint32_t      card_mem_size;
    uint8_t      *card_mem;
    mem_page_t    card_pages[4];

    port_92_t    *port_92;
} scamp_t;

static const struct {
    int size_kb;
    int rammap;
    int bank[2];
} ram_configs[] = {
    {  512,  0x0, { BANK_256K, BANK_NONE }                        },
    { 1024,  0x1, { BANK_256K_INTERLEAVED, BANK_NONE }            },
    { 1536,  0x2, { BANK_256K_INTERLEAVED, BANK_256K }            },
    { 2048,  0x3, { BANK_256K_INTERLEAVED, BANK_256K_INTERLEAVED }},
    { 3072,  0xc, { BANK_256K_INTERLEAVED, BANK_1M }              },
    { 4096,  0x5, { BANK_1M_INTERLEAVED, BANK_NONE }              },
    { 5120,  0xd, { BANK_256K_INTERLEAVED, BANK_1M_INTERLEAVED }  },
    { 6144,  0x6, { BANK_1M_INTERLEAVED, BANK_1M }                },
    { 8192,  0x7, { BANK_1M_INTERLEAVED, BANK_1M_INTERLEAVED }    },
    { 12288, 0xe, { BANK_1M_INTERLEAVED, BANK_4M }                },
    { 16384, 0x9, { BANK_4M_INTERLEAVED, BANK_NONE }              },
};

static const struct {
    int bank[2];
    int remapped;
} rammap[16] = {
    { { BANK_256K, BANK_NONE },                         0},
    { { BANK_256K_INTERLEAVED, BANK_NONE },             0},
    { { BANK_256K_INTERLEAVED, BANK_256K },             0},
    { { BANK_256K_INTERLEAVED, BANK_256K_INTERLEAVED }, 0},

    { { BANK_1M, BANK_NONE },                           0},
    { { BANK_1M_INTERLEAVED, BANK_NONE },               0},
    { { BANK_1M_INTERLEAVED, BANK_1M },                 0},
    { { BANK_1M_INTERLEAVED, BANK_1M_INTERLEAVED },     0},

    { { BANK_4M, BANK_NONE },                           0},
    { { BANK_4M_INTERLEAVED, BANK_NONE },               0},
    { { BANK_NONE, BANK_4M },                           1}, /*Bank 2 remapped to 0*/
    { { BANK_NONE, BANK_4M_INTERLEAVED },               1}, /*Banks 2/3 remapped to 0/1*/

    { { BANK_256K_INTERLEAVED, BANK_1M },               0},
    { { BANK_256K_INTERLEAVED, BANK_1M_INTERLEAVED },   0},
    { { BANK_1M_INTERLEAVED, BANK_4M },                 0},
    { { BANK_1M_INTERLEAVED, BANK_4M_INTERLEAVED },     0}, /*Undocumented - probably wrong!*/
};

#ifdef ENABLE_SCAMP_LOG
int scamp_do_log = ENABLE_SCAMP_LOG;

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

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

/* Read one byte from paged RAM. */
static uint8_t
scamp_mem_readb(uint32_t addr, void *priv)
{
    mem_page_t *dev = (mem_page_t *) priv;
    uint8_t     ret = 0xff;

    if (dev->mem != NULL)
        ret = *(uint8_t *) &(dev->mem[addr & EMS_PGMASK]);

    return ret;
}

/* Read one word from paged RAM. */
static uint16_t
scamp_mem_readw(uint32_t addr, void *priv)
{
    mem_page_t *dev = (mem_page_t *) priv;
    uint16_t    ret = 0xffff;

    if (dev->mem != NULL)
        ret = *(uint16_t *) &(dev->mem[addr & EMS_PGMASK]);

    return ret;
}

/* Write one byte to paged RAM. */
static void
scamp_mem_writeb(uint32_t addr, uint8_t val, void *priv)
{
    mem_page_t *dev = (mem_page_t *) priv;

    if (dev->mem != NULL)
        *(uint8_t *) &(dev->mem[addr & EMS_PGMASK]) = val;
}

/* Write one word to paged RAM. */
static void
scamp_mem_writew(uint32_t addr, uint16_t val, void *priv)
{
    mem_page_t *dev = (mem_page_t *) priv;

    if (dev->mem != NULL)
        *(uint16_t *) &(dev->mem[addr & EMS_PGMASK]) = val;
}

/* The column bits masked when using 256kbit DRAMs in 4Mbit mode aren't contiguous,
   so we use separate routines for that special case */
static uint8_t
ram_mirrored_256k_in_4mi_read(uint32_t addr, void *priv)
{
    const ram_struct_t *rs   = (ram_struct_t *) priv;
    const scamp_t      *dev  = rs->parent;
    int                 bank = rs->bank;
    int                 byte;
    int                 row;
    int                 column;

    addr -= dev->ram_virt_base[bank];
    byte = addr & 1;
    if (!dev->ram_interleaved[bank]) {
        if (addr & 0x400)
            return 0xff;

        addr   = (addr & 0x3ff) | ((addr & ~0x7ff) >> 1);
        column = (addr >> 1) & dev->ram_mask[bank];
        row    = ((addr & 0xff000) >> 13) | (((addr & 0x200000) >> 22) << 9);

        addr = byte | (column << 1) | (row << dev->row_phys_shift[bank]);
    } else {
        column = (addr >> 1) & ((dev->ram_mask[bank] << 1) | 1);
        row    = ((addr & 0x1fe000) >> 13) | (((addr & 0x400000) >> 22) << 9);

        addr = byte | (column << 1) | (row << (dev->row_phys_shift[bank] + 1));
    }

    return ram[addr + dev->ram_phys_base[bank]];
}

static void
ram_mirrored_256k_in_4mi_write(uint32_t addr, uint8_t val, void *priv)
{
    const ram_struct_t *rs   = (ram_struct_t *) priv;
    const scamp_t      *dev  = rs->parent;
    int                 bank = rs->bank;
    int                 byte;
    int                 row;
    int                 column;

    addr -= dev->ram_virt_base[bank];
    byte = addr & 1;
    if (!dev->ram_interleaved[bank]) {
        if (addr & 0x400)
            return;

        addr   = (addr & 0x3ff) | ((addr & ~0x7ff) >> 1);
        column = (addr >> 1) & dev->ram_mask[bank];
        row    = ((addr & 0xff000) >> 13) | (((addr & 0x200000) >> 22) << 9);

        addr = byte | (column << 1) | (row << dev->row_phys_shift[bank]);
    } else {
        column = (addr >> 1) & ((dev->ram_mask[bank] << 1) | 1);
        row    = ((addr & 0x1fe000) >> 13) | (((addr & 0x400000) >> 22) << 9);

        addr = byte | (column << 1) | (row << (dev->row_phys_shift[bank] + 1));
    }

    ram[addr + dev->ram_phys_base[bank]] = val;
}

/*Read/write handlers for interleaved memory banks. We must keep CPU and ram array
  mapping linear, otherwise we won't be able to execute code from interleaved banks*/
static uint8_t
ram_mirrored_interleaved_read(uint32_t addr, void *priv)
{
    const ram_struct_t *rs   = (ram_struct_t *) priv;
    const scamp_t      *dev  = rs->parent;
    int                 bank = rs->bank;
    int                 byte;
    int                 row;
    int                 column;

    addr -= dev->ram_virt_base[bank];
    byte = addr & 1;
    if (!dev->ram_interleaved[bank]) {
        if (addr & 0x400)
            return 0xff;

        addr   = (addr & 0x3ff) | ((addr & ~0x7ff) >> 1);
        column = (addr >> 1) & dev->ram_mask[bank];
        row    = (addr >> dev->row_virt_shift[bank]) & dev->ram_mask[bank];

        addr = byte | (column << 1) | (row << dev->row_phys_shift[bank]);
    } else {
        column = (addr >> 1) & ((dev->ram_mask[bank] << 1) | 1);
        row    = (addr >> (dev->row_virt_shift[bank] + 1)) & dev->ram_mask[bank];

        addr = byte | (column << 1) | (row << (dev->row_phys_shift[bank] + 1));
    }

    return ram[addr + dev->ram_phys_base[bank]];
}

static void
ram_mirrored_interleaved_write(uint32_t addr, uint8_t val, void *priv)
{
    const ram_struct_t *rs   = (ram_struct_t *) priv;
    const scamp_t      *dev  = rs->parent;
    int                 bank = rs->bank;
    int                 byte;
    int                 row;
    int                 column;

    addr -= dev->ram_virt_base[bank];
    byte = addr & 1;
    if (!dev->ram_interleaved[bank]) {
        if (addr & 0x400)
            return;

        addr   = (addr & 0x3ff) | ((addr & ~0x7ff) >> 1);
        column = (addr >> 1) & dev->ram_mask[bank];
        row    = (addr >> dev->row_virt_shift[bank]) & dev->ram_mask[bank];

        addr = byte | (column << 1) | (row << dev->row_phys_shift[bank]);
    } else {
        column = (addr >> 1) & ((dev->ram_mask[bank] << 1) | 1);
        row    = (addr >> (dev->row_virt_shift[bank] + 1)) & dev->ram_mask[bank];

        addr = byte | (column << 1) | (row << (dev->row_phys_shift[bank] + 1));
    }

    ram[addr + dev->ram_phys_base[bank]] = val;
}

static uint8_t
ram_mirrored_read(uint32_t addr, void *priv)
{
    const ram_struct_t *rs   = (ram_struct_t *) priv;
    const scamp_t      *dev  = rs->parent;
    int                 bank = rs->bank;
    int                 byte;
    int                 row;
    int                 column;

    addr -= dev->ram_virt_base[bank];
    byte   = addr & 1;
    column = (addr >> 1) & dev->ram_mask[bank];
    row    = (addr >> dev->row_virt_shift[bank]) & dev->ram_mask[bank];
    addr   = byte | (column << 1) | (row << dev->row_phys_shift[bank]);

    return ram[addr + dev->ram_phys_base[bank]];
}

static void
ram_mirrored_write(uint32_t addr, uint8_t val, void *priv)
{
    const ram_struct_t *rs   = (ram_struct_t *) priv;
    const scamp_t      *dev  = rs->parent;
    int                 bank = rs->bank;
    int                 byte;
    int                 row;
    int                 column;

    addr -= dev->ram_virt_base[bank];
    byte   = addr & 1;
    column = (addr >> 1) & dev->ram_mask[bank];
    row    = (addr >> dev->row_virt_shift[bank]) & dev->ram_mask[bank];
    addr   = byte | (column << 1) | (row << dev->row_phys_shift[bank]);

    ram[addr + dev->ram_phys_base[bank]] = val;
}

static void
recalc_mappings(void *priv)
{
    scamp_t *dev = (scamp_t *) priv;
    uint32_t virt_base  = 0;
    uint32_t old_virt_base;
    uint8_t  cur_rammap = dev->cfg_regs[CFG_RAMMAP] & 0xf;
    int      bank_nr    = 0;
    int      phys_bank;

    mem_set_mem_state_both((1 << 20), (16256 - 1024) * 1024, MEM_READ_EXTERNAL | MEM_WRITE_EXTERNAL);
    mem_set_mem_state(0xfe0000, 0x20000, MEM_READ_EXTANY | MEM_WRITE_EXTANY);

    for (uint8_t c = 0; c < 2; c++)
        mem_mapping_disable(&dev->ram_mapping[c]);

    /* Once the BIOS programs the correct DRAM configuration, switch to regular
       linear memory mapping */
    if (cur_rammap == ram_configs[dev->ram_config].rammap) {
        mem_mapping_disable(&ram_low_mapping);

        for (uint8_t i = 0; i < 40; i++)
            mem_mapping_enable(&(dev->mem_mappings[i]));

        if (mem_size > 1024)
            mem_set_mem_state_both((1 << 20), (mem_size - 1024) << 10, MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
        mem_mapping_enable(&ram_high_mapping);
    } else {
        mem_mapping_set_handler(&ram_low_mapping,
                                ram_mirrored_read, NULL, NULL,
                                ram_mirrored_write, NULL, NULL);

        mem_mapping_disable(&ram_low_mapping);

        for (uint8_t i = 0; i < 40; i++)
            mem_mapping_disable(&(dev->mem_mappings[i]));

        if (rammap[cur_rammap].bank[0] == BANK_NONE)
            bank_nr = 1;

        for (; bank_nr < 2; bank_nr++) {
            old_virt_base = virt_base;
            phys_bank     = ram_configs[dev->ram_config].bank[bank_nr];

            dev->ram_virt_base[bank_nr] = virt_base;

            if (virt_base == 0) {
                switch (rammap[cur_rammap].bank[bank_nr]) {
                    case BANK_NONE:
                        fatal("        Bank %i is empty!\n    }\n}\n", bank_nr);
                        break;

                    case BANK_256K:
                        if (phys_bank != BANK_NONE) {
                            mem_mapping_set_addr(&ram_low_mapping, 0, 0x80000);
                            mem_mapping_set_p(&ram_low_mapping, (void *) &dev->ram_struct[bank_nr]);
                        }
                        virt_base += (1 << 19);
                        dev->row_virt_shift[bank_nr] = 10;
                        break;

                    case BANK_256K_INTERLEAVED:
                        if (phys_bank != BANK_NONE) {
                            mem_mapping_set_addr(&ram_low_mapping, 0, 0xa0000);
                            mem_mapping_set_p(&ram_low_mapping, (void *) &dev->ram_struct[bank_nr]);
                        }
                        virt_base += (1 << 20);
                        dev->row_virt_shift[bank_nr] = 10;
                        break;

                    case BANK_1M:
                        if (phys_bank != BANK_NONE) {
                            mem_mapping_set_addr(&ram_low_mapping, 0, 0xa0000);
                            mem_mapping_set_p(&ram_low_mapping, (void *) &dev->ram_struct[bank_nr]);
                            mem_mapping_set_addr(&dev->ram_mapping[bank_nr], 0x100000, 0x100000);
                            mem_mapping_set_exec(&dev->ram_mapping[bank_nr], &ram[dev->ram_phys_base[bank_nr] + 0x100000]);
                            mem_set_mem_state_both((1 << 20), (1 << 20), MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                        }
                        virt_base += (1 << 21);
                        dev->row_virt_shift[bank_nr] = 11;
                        break;

                    case BANK_1M_INTERLEAVED:
                        if (phys_bank != BANK_NONE) {
                            mem_mapping_set_addr(&ram_low_mapping, 0, 0xa0000);
                            mem_mapping_set_p(&ram_low_mapping, (void *) &dev->ram_struct[bank_nr]);
                            mem_mapping_set_addr(&dev->ram_mapping[bank_nr], 0x100000, 0x300000);
                            mem_mapping_set_exec(&dev->ram_mapping[bank_nr], &ram[dev->ram_phys_base[bank_nr] + 0x100000]);
                            mem_set_mem_state_both((1 << 20), (3 << 20), MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                        }
                        virt_base += (1 << 22);
                        dev->row_virt_shift[bank_nr] = 11;
                        break;

                    case BANK_4M:
                        if (phys_bank != BANK_NONE) {
                            mem_mapping_set_addr(&ram_low_mapping, 0, 0xa0000);
                            mem_mapping_set_p(&ram_low_mapping, (void *) &dev->ram_struct[bank_nr]);
                            mem_mapping_set_addr(&dev->ram_mapping[bank_nr], 0x100000, 0x700000);
                            mem_mapping_set_exec(&dev->ram_mapping[bank_nr], &ram[dev->ram_phys_base[bank_nr] + 0x100000]);
                            mem_set_mem_state_both((1 << 20), (7 << 20), MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                        }
                        virt_base += (1 << 23);
                        dev->row_virt_shift[bank_nr] = 12;
                        break;

                    case BANK_4M_INTERLEAVED:
                        if (phys_bank != BANK_NONE) {
                            mem_mapping_set_addr(&ram_low_mapping, 0, 0xa0000);
                            mem_mapping_set_p(&ram_low_mapping, (void *) &dev->ram_struct[bank_nr]);
                            mem_mapping_set_addr(&dev->ram_mapping[bank_nr], 0x100000, 0xf00000);
                            mem_mapping_set_exec(&dev->ram_mapping[bank_nr], &ram[dev->ram_phys_base[bank_nr] + 0x100000]);
                            mem_set_mem_state_both((1 << 20), (15 << 20), MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                        }
                        virt_base += (1 << 24);
                        dev->row_virt_shift[bank_nr] = 12;
                        break;

                    default:
                        break;
                }
            } else {
                switch (rammap[cur_rammap].bank[bank_nr]) {
                    case BANK_NONE:
                        break;

                    case BANK_256K:
                        if (phys_bank != BANK_NONE) {
                            mem_mapping_set_addr(&dev->ram_mapping[bank_nr], virt_base, 0x80000);
                            mem_mapping_set_exec(&dev->ram_mapping[bank_nr], &ram[dev->ram_phys_base[bank_nr]]);
                            mem_set_mem_state_both(virt_base, (1 << 19), MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                        }
                        virt_base += (1 << 19);
                        dev->row_virt_shift[bank_nr] = 10;
                        break;

                    case BANK_256K_INTERLEAVED:
                        if (phys_bank != BANK_NONE) {
                            mem_mapping_set_addr(&dev->ram_mapping[bank_nr], virt_base, 0x100000);
                            mem_mapping_set_exec(&dev->ram_mapping[bank_nr], &ram[dev->ram_phys_base[bank_nr]]);
                            mem_set_mem_state_both(virt_base, (1 << 20), MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                        }
                        virt_base += (1 << 20);
                        dev->row_virt_shift[bank_nr] = 10;
                        break;

                    case BANK_1M:
                        if (phys_bank != BANK_NONE) {
                            mem_mapping_set_addr(&dev->ram_mapping[bank_nr], virt_base, 0x200000);
                            mem_mapping_set_exec(&dev->ram_mapping[bank_nr], &ram[dev->ram_phys_base[bank_nr]]);
                            mem_set_mem_state_both(virt_base, (1 << 21), MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                        }
                        virt_base += (1 << 21);
                        dev->row_virt_shift[bank_nr] = 11;
                        break;

                    case BANK_1M_INTERLEAVED:
                        if (phys_bank != BANK_NONE) {
                            mem_mapping_set_addr(&dev->ram_mapping[bank_nr], virt_base, 0x400000);
                            mem_mapping_set_exec(&dev->ram_mapping[bank_nr], &ram[dev->ram_phys_base[bank_nr]]);
                            mem_set_mem_state_both(virt_base, (1 << 22), MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                        }
                        virt_base += (1 << 22);
                        dev->row_virt_shift[bank_nr] = 11;
                        break;

                    case BANK_4M:
                        if (phys_bank != BANK_NONE) {
                            mem_mapping_set_addr(&dev->ram_mapping[bank_nr], virt_base, 0x800000);
                            mem_mapping_set_exec(&dev->ram_mapping[bank_nr], &ram[dev->ram_phys_base[bank_nr]]);
                            mem_set_mem_state_both(virt_base, (1 << 23), MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                        }
                        virt_base += (1 << 23);
                        dev->row_virt_shift[bank_nr] = 12;
                        break;

                    case BANK_4M_INTERLEAVED:
                        if (phys_bank != BANK_NONE) {
                            mem_mapping_set_addr(&dev->ram_mapping[bank_nr], virt_base, 0x1000000);
                            mem_mapping_set_exec(&dev->ram_mapping[bank_nr], &ram[dev->ram_phys_base[bank_nr]]);
                            mem_set_mem_state_both(virt_base, (1 << 24), MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
                        }
                        virt_base += (1 << 24);
                        dev->row_virt_shift[bank_nr] = 12;
                        break;

                    default:
                        break;
                }
            }

            switch (rammap[cur_rammap].bank[bank_nr]) {
                case BANK_256K:
                case BANK_1M:
                case BANK_4M:
                    mem_mapping_set_handler(&dev->ram_mapping[bank_nr],
                                            ram_mirrored_read, NULL, NULL,
                                            ram_mirrored_write, NULL, NULL);
                    if (!old_virt_base)
                        mem_mapping_set_handler(&ram_low_mapping,
                                                ram_mirrored_read, NULL, NULL,
                                                ram_mirrored_write, NULL, NULL);
                    break;

                case BANK_256K_INTERLEAVED:
                case BANK_1M_INTERLEAVED:
                    mem_mapping_set_handler(&dev->ram_mapping[bank_nr],
                                            ram_mirrored_interleaved_read, NULL, NULL,
                                            ram_mirrored_interleaved_write, NULL, NULL);
                    if (!old_virt_base)
                        mem_mapping_set_handler(&ram_low_mapping,
                                                ram_mirrored_interleaved_read, NULL, NULL,
                                                ram_mirrored_interleaved_write, NULL, NULL);
                    break;

                case BANK_4M_INTERLEAVED:
                    if (phys_bank == BANK_256K || phys_bank == BANK_256K_INTERLEAVED) {
                        mem_mapping_set_handler(&dev->ram_mapping[bank_nr],
                                                ram_mirrored_256k_in_4mi_read, NULL, NULL,
                                                ram_mirrored_256k_in_4mi_write, NULL, NULL);
                        if (!old_virt_base)
                            mem_mapping_set_handler(&ram_low_mapping,
                                                    ram_mirrored_256k_in_4mi_read, NULL, NULL,
                                                    ram_mirrored_256k_in_4mi_write, NULL, NULL);
                    } else {
                        mem_mapping_set_handler(&dev->ram_mapping[bank_nr],
                                                ram_mirrored_interleaved_read, NULL, NULL,
                                                ram_mirrored_interleaved_write, NULL, NULL);
                        if (!old_virt_base)
                            mem_mapping_set_handler(&ram_low_mapping,
                                                    ram_mirrored_interleaved_read, NULL, NULL,
                                                    ram_mirrored_interleaved_write, NULL, NULL);
                    }
                    break;

                default:
                    break;
            }
        }
    }
}

static void
scamp_mem_update_state(scamp_t *dev, uint32_t addr, uint32_t size, uint8_t new_flags, uint8_t mask)
{
    int read_ext  = MEM_READ_EXTERNAL;
    int write_ext = MEM_WRITE_EXTERNAL;

    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) {
            read_ext  = MEM_READ_ROMCS;
            write_ext = MEM_WRITE_ROMCS;
        }

        if (new_flags & (MEM_FLAG_REMAP | MEM_FLAG_SLOTBUS)) {
            scamp_log("scamp_mem_update_state(): %08X-%08X: %02X (REMAP)\n", addr, addr + size - 1, new_flags);
            mem_set_mem_state(addr, size, read_ext | write_ext);
        } else if (new_flags & (MEM_FLAG_EMS | MEM_FLAG_MEMCARD)) {
            scamp_log("scamp_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:
                scamp_log("scamp_mem_update_state(): %08X-%08X: %02X (RE | WE)\n", addr, addr + size - 1, new_flags);
                mem_set_mem_state(addr, size, read_ext | write_ext);
                break;
            case 1:
                scamp_log("scamp_mem_update_state(): %08X-%08X: %02X (RE | WI)\n", addr, addr + size - 1, new_flags);
                mem_set_mem_state(addr, size, read_ext | MEM_WRITE_INTERNAL);
                break;
            case 2:
                scamp_log("scamp_mem_update_state(): %08X-%08X: %02X (RI | WE)\n", addr, addr + size - 1, new_flags);
                mem_set_mem_state(addr, size, MEM_READ_INTERNAL | write_ext);
                break;
            case 3:
                scamp_log("scamp_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 int
is_seg_in_ram(scamp_t *dev, uint8_t s)
{
    mem_page_t *mp  = (mem_page_t *) dev->mem_mappings[s].priv;
    const int   ret = mp->in_ram;

    return ret;
}

static void
recalc_ems(scamp_t *dev)
{
    const uint8_t  seg_xlat[12] = { 40, 41, 42, 43, 52, 53, 54, 55, 44, 45, 46, 47 };
    const uint16_t seg_enable   = dev->cfg_regs[CFG_EMSEN2] | ((dev->cfg_regs[CFG_EMSEN1] & 0xf) << 8);

    for (uint8_t s = 40; s < 60; s++) {
        dev->mem_pages[s].phys_addr = dev->mem_pages[s].virt_addr;
        dev->mem_pages[s].mem       = ram + dev->mem_pages[s].phys_addr;

       if (is_seg_in_ram(dev, s))
           mem_mapping_set_exec(&(dev->mem_mappings[s]), dev->mem_pages[s].mem);

       scamp_mem_update_state(dev, s * EMS_PGSIZE, EMS_PGSIZE, 0x00, MEM_FMASK_EMS);
    }

    for (uint8_t i = 0; i < 36; i++) {
        uint8_t  s         = (i < 12) ? (i + 48) : (i + 4);
        uint8_t  on        = (dev->cfg_regs[CFG_EMSEN1] & EMSEN1_EMSENAB);
        uint32_t phys_addr = dev->ems[i] << 14;

        if (i < 12) {
            if (dev->cfg_regs[CFG_EMSEN1] & EMSEN1_EMSMAP)
                s = seg_xlat[i];

            on = on && (seg_enable & (1 << i));
        } else
            on = on && (dev->cfg_regs[CFG_EMSEN1] & EMSEN1_BFENAB);

        if (on) {
            dev->mem_pages[s].phys_addr = phys_addr;
            dev->mem_pages[s].mem       = ram + dev->mem_pages[s].phys_addr;

            if (is_seg_in_ram(dev, s))
                mem_mapping_set_exec(&(dev->mem_mappings[s]), dev->mem_pages[s].mem);

            scamp_mem_update_state(dev, s * EMS_PGSIZE, EMS_PGSIZE, MEM_FLAG_EMS, MEM_FMASK_EMS);
        } else if (i >= 12) {
            dev->mem_pages[s].phys_addr = dev->mem_pages[s].virt_addr;
            dev->mem_pages[s].mem       = ram + dev->mem_pages[s].phys_addr;

            if (is_seg_in_ram(dev, s))
                mem_mapping_set_exec(&(dev->mem_mappings[s]), dev->mem_pages[s].mem);

            scamp_mem_update_state(dev, s * EMS_PGSIZE, EMS_PGSIZE, 0x00, MEM_FMASK_EMS);
        }
    }

    flushmmucache_nopc();
}

static void
shadow_control(scamp_t *dev, uint32_t addr, uint32_t size, int state)
{
    if (size == 0x8000) {
        scamp_mem_update_state(dev, addr, EMS_PGSIZE, state, MEM_FMASK_RW);
        scamp_mem_update_state(dev, addr + EMS_PGSIZE, EMS_PGSIZE, state, MEM_FMASK_RW);
    } else
        scamp_mem_update_state(dev, addr, size, state, MEM_FMASK_RW);

    flushmmucache_nopc();
}

static void
shadow_recalc(scamp_t *dev)
{
    uint8_t  abaxs = (dev->cfg_regs[CFG_RAMMAP] & RAMMAP_REMP384) ? 0 : dev->cfg_regs[CFG_ABAXS];
    uint8_t  caxs  = (dev->cfg_regs[CFG_RAMMAP] & RAMMAP_REMP384) ? 0 : dev->cfg_regs[CFG_CAXS];
    uint8_t  daxs  = (dev->cfg_regs[CFG_RAMMAP] & RAMMAP_REMP384) ? 0 : dev->cfg_regs[CFG_DAXS];
    uint8_t  feaxs = (dev->cfg_regs[CFG_RAMMAP] & RAMMAP_REMP384) ? 0 : dev->cfg_regs[CFG_FEAXS];

    /*Enabling remapping will disable all shadowing*/
    if (dev->cfg_regs[CFG_RAMMAP] & RAMMAP_REMP384)
        mem_remap_top_nomid(384);
    else
        mem_remap_top_nomid(0);

    for (uint8_t i = 40; i < 64; i++) {
        if (dev->cfg_regs[CFG_RAMMAP] & RAMMAP_REMP384)
            scamp_mem_update_state(dev, (i * EMS_PGSIZE), EMS_PGSIZE, MEM_FLAG_REMAP, MEM_FMASK_REMAP);
        else
            scamp_mem_update_state(dev, (i * EMS_PGSIZE), EMS_PGSIZE, 0x00, MEM_FMASK_REMAP);
    }

    shadow_control(dev, 0xa0000, 0x4000, abaxs & 3);
    shadow_control(dev, 0xa0000, 0x4000, abaxs & 3);
    shadow_control(dev, 0xa8000, 0x4000, (abaxs >> 2) & 3);
    shadow_control(dev, 0xa8000, 0x4000, (abaxs >> 2) & 3);

    shadow_control(dev, 0xb0000, 0x4000, (abaxs >> 4) & 3);
    shadow_control(dev, 0xb0000, 0x4000, (abaxs >> 4) & 3);
    shadow_control(dev, 0xb8000, 0x4000, (abaxs >> 6) & 3);
    shadow_control(dev, 0xb8000, 0x4000, (abaxs >> 6) & 3);

    shadow_control(dev, 0xc0000, 0x4000, caxs & 3);
    shadow_control(dev, 0xc4000, 0x4000, (caxs >> 2) & 3);
    shadow_control(dev, 0xc8000, 0x4000, (caxs >> 4) & 3);
    shadow_control(dev, 0xcc000, 0x4000, (caxs >> 6) & 3);

    shadow_control(dev, 0xd0000, 0x4000, daxs & 3);
    shadow_control(dev, 0xd4000, 0x4000, (daxs >> 2) & 3);
    shadow_control(dev, 0xd8000, 0x4000, (daxs >> 4) & 3);
    shadow_control(dev, 0xdc000, 0x4000, (daxs >> 6) & 3);

    shadow_control(dev, 0xe0000, 0x4000, feaxs & 3);
    shadow_control(dev, 0xe4000, 0x4000, feaxs & 3);
    shadow_control(dev, 0xe8000, 0x4000, (feaxs >> 2) & 3);
    shadow_control(dev, 0xec000, 0x4000, (feaxs >> 2) & 3);

    shadow_control(dev, 0xf0000, 0x8000, (feaxs >> 4) & 3);
    shadow_control(dev, 0xf8000, 0x8000, (feaxs >> 6) & 3);
}

static void
recalc_sltptr(scamp_t *dev)
{
    uint32_t sltptr = dev->cfg_regs[CFG_SLTPTR] << 16;

    if (sltptr >= 0xa0000 && sltptr < 0x100000)
        sltptr = 0x100000;
    if (sltptr > 0xfe0000)
        sltptr = 0xfe0000;

    if (sltptr >= 0xa0000) {
        for (uint8_t i = 0; i < 40; i++)
            scamp_mem_update_state(dev, i * EMS_PGSIZE, EMS_PGSIZE, 0x00, MEM_FMASK_SLOTBUS);

        mem_set_mem_state(0x100000, sltptr - 0x100000, MEM_READ_INTERNAL | MEM_WRITE_INTERNAL);
        mem_set_mem_state(sltptr, 0x1000000 - sltptr, MEM_READ_EXTANY | MEM_WRITE_EXTANY);

        if ((sltptr >= 0x40000) && (sltptr <= 0x90000)) {
            dev->cfg_regs[CFG_EMSEN1] &= ~EMSEN1_BFENAB;
            recalc_ems(dev);
        }
    } else {
        for (uint32_t i = 0; i < (sltptr / EMS_PGSIZE); i++)
            scamp_mem_update_state(dev, i * EMS_PGSIZE, EMS_PGSIZE, 0x00, MEM_FMASK_SLOTBUS);

        for (uint8_t i = (sltptr / EMS_PGSIZE); i < 40; i++)
            scamp_mem_update_state(dev, i * EMS_PGSIZE, EMS_PGSIZE, MEM_FLAG_SLOTBUS, MEM_FMASK_SLOTBUS);

        mem_set_mem_state(sltptr, 0xa0000 - sltptr, MEM_READ_EXTANY | MEM_WRITE_EXTANY);
        mem_set_mem_state(0x100000, 0xf00000, MEM_READ_EXTANY | MEM_WRITE_EXTANY);
    }

    flushmmucache_nopc();
}

static void
recalc_rommov(scamp_t *dev)
{
    switch ((dev->cfg_regs[CFG_RAMMAP] & RAMMAP_ROMMOV) >> 5) {
        case 0x00:
            scamp_mem_update_state(dev, 0x000c0000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000c4000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000c8000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000cc000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);

            scamp_mem_update_state(dev, 0x000e0000, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000e4000, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000e8000, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000ec000, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);
            break;

        case 0x01:
            scamp_mem_update_state(dev, 0x000c0000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000c4000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000c8000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000cc000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);

            scamp_mem_update_state(dev, 0x000e0000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000e4000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000e8000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000ec000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            break;

        case 0x02:
            scamp_mem_update_state(dev, 0x000c0000, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000c4000, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000c8000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000cc000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);

            scamp_mem_update_state(dev, 0x000e0000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000e4000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000e8000, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000ec000, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);
            break;

        case 0x03:
            scamp_mem_update_state(dev, 0x000c0000, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000c4000, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000c8000, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000cc000, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);

            scamp_mem_update_state(dev, 0x000e0000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000e4000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000e8000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            scamp_mem_update_state(dev, 0x000ec000, EMS_PGSIZE,           0x00, MEM_FMASK_ROMCS);
            break;
    }

    flushmmucache_nopc();
}

static void
scamp_write(uint16_t addr, uint8_t val, void *priv)
{
    scamp_t *dev = (scamp_t *) priv;

    switch (addr) {
        case 0xe8:
            dev->ems_index   = val & 0x3f;
            dev->ems_autoinc = val & 0x40;
            break;

        case 0xea:
            if (dev->ems_index < 0x24) {
                dev->ems[dev->ems_index] = (dev->ems[dev->ems_index] & 0x300) | val;
                recalc_ems(dev);
            }
            break;
        case 0xeb:
            if (dev->ems_index < 0x24) {
                dev->ems[dev->ems_index] = (dev->ems[dev->ems_index] & 0x0ff) | ((val & 3) << 8);
                recalc_ems(dev);
            }
            if (dev->ems_autoinc)
                dev->ems_index = (dev->ems_index + 1) & 0x3f;
            break;

        case 0xec:
            if (dev->cfg_enable)
                dev->cfg_index = val;
            break;

        case 0xed:
            if (dev->cfg_enable && (dev->cfg_index >= 0x02) && (dev->cfg_index <= 0x16)) {
                dev->cfg_regs[dev->cfg_index] = val;
                switch (dev->cfg_index) {
                    case CFG_SLTPTR:
                        recalc_sltptr(dev);
                        recalc_ems(dev);
                        break;

                    case CFG_RAMMAP:
                        recalc_mappings(dev);
                        mem_mapping_disable(&ram_remapped_mapping);
                        shadow_recalc(dev);
                        recalc_rommov(dev);
                        break;

                    case CFG_EMSEN1:
                    case CFG_EMSEN2:
                        shadow_recalc(dev);
                        recalc_ems(dev);
                        break;

                    case CFG_ABAXS:
                    case CFG_CAXS:
                    case CFG_DAXS:
                    case CFG_FEAXS:
                        shadow_recalc(dev);
                        break;
                    default:
                        break;
                }
            }
            break;

        case 0xee:
            if (dev->cfg_enable && mem_a20_alt) {
                dev->port_92->reg &= 0xfd;
                mem_a20_alt = 0;
                mem_a20_recalc();
            }
            break;

        default:
            break;
    }
}

static uint8_t
scamp_read(uint16_t addr, void *priv)
{
    scamp_t *dev = (scamp_t *) priv;
    uint8_t  ret = 0xff;

    switch (addr) {
        case 0xe8:
            ret = dev->ems_index | dev->ems_autoinc;
            break;

        case 0xea:
            if (dev->ems_index < 0x24)
                ret = dev->ems[dev->ems_index] & 0xff;
            break;
        case 0xeb:
            if (dev->ems_index < 0x24)
                ret = (dev->ems[dev->ems_index] >> 8) | 0xfc;
            if (dev->ems_autoinc)
                dev->ems_index = (dev->ems_index + 1) & 0x3f;
            break;

        case 0xed:
            if (dev->cfg_enable && (dev->cfg_index >= 0x00) && (dev->cfg_index <= 0x16))
                ret = (dev->cfg_regs[dev->cfg_index]);
            break;

        case 0xee:
            if (!mem_a20_alt) {
                dev->port_92->reg |= 0x02;
                mem_a20_alt = 1;
                mem_a20_recalc();
            }
            break;

        case 0xef:
            softresetx86();
            cpu_set_edx();
            break;

        default:
            break;
    }

    return ret;
}

static void
scamp_close(void *priv)
{
    scamp_t *dev = (scamp_t *) priv;

    free(dev);
}

static void *
scamp_init(UNUSED(const device_t *info))
{
    uint32_t addr;
    scamp_t *dev = (scamp_t *) calloc(1, sizeof(scamp_t));

    dev->cfg_regs[CFG_ID] = ID_VL82C311;
    dev->cfg_enable       = 1;

    io_sethandler(0x00e8, 0x0001,
                  scamp_read, NULL, NULL, scamp_write, NULL, NULL, dev);
    io_sethandler(0x00ea, 0x0006,
                  scamp_read, NULL, NULL, scamp_write, NULL, NULL, dev);
    io_sethandler(0x00f4, 0x0002,
                  scamp_read, NULL, NULL, scamp_write, NULL, NULL, dev);
    io_sethandler(0x00f9, 0x0001,
                  scamp_read, NULL, NULL, scamp_write, NULL, NULL, dev);
    io_sethandler(0x00fb, 0x0001,
                  scamp_read, NULL, NULL, scamp_write, NULL, NULL, dev);

    dev->ram_config = 0;

    /* Find best fit configuration for the requested memory size */
    for (uint8_t c = 0; c < NR_ELEMS(ram_configs); c++) {
        if (mem_size < ram_configs[c].size_kb)
            break;

        dev->ram_config = c;
    }

    mem_mapping_set_p(&ram_low_mapping, (void *) &dev->ram_struct[0]);
    mem_mapping_set_handler(&ram_low_mapping,
                            ram_mirrored_read, NULL, NULL,
                            ram_mirrored_write, NULL, NULL);
    mem_mapping_disable(&ram_mid_mapping);
    mem_mapping_disable(&ram_high_mapping);

    addr = 0;
    for (uint8_t c = 0; c < 2; c++) {
        dev->ram_struct[c].parent = dev;
        dev->ram_struct[c].bank   = c;
        mem_mapping_add(&dev->ram_mapping[c], 0, 0,
                        ram_mirrored_read, NULL, NULL,
                        ram_mirrored_write, NULL, NULL,
                        &ram[addr], MEM_MAPPING_INTERNAL, (void *) &dev->ram_struct[c]);
        mem_mapping_disable(&dev->ram_mapping[c]);

        dev->ram_phys_base[c] = addr;

        switch (ram_configs[dev->ram_config].bank[c]) {
            case BANK_NONE:
                dev->ram_mask[c]        = 0;
                dev->ram_interleaved[c] = 0;
                break;

            case BANK_256K:
                addr += (1 << 19);
                dev->ram_mask[c]        = 0x1ff;
                dev->row_phys_shift[c]  = 10;
                dev->ram_interleaved[c] = 0;
                break;

            case BANK_256K_INTERLEAVED:
                addr += (1 << 20);
                dev->ram_mask[c]        = 0x1ff;
                dev->row_phys_shift[c]  = 10;
                dev->ibank_shift[c]     = 19;
                dev->ram_interleaved[c] = 1;
                break;

            case BANK_1M:
                addr += (1 << 21);
                dev->ram_mask[c]        = 0x3ff;
                dev->row_phys_shift[c]  = 11;
                dev->ram_interleaved[c] = 0;
                break;

            case BANK_1M_INTERLEAVED:
                addr += (1 << 22);
                dev->ram_mask[c]        = 0x3ff;
                dev->row_phys_shift[c]  = 11;
                dev->ibank_shift[c]     = 21;
                dev->ram_interleaved[c] = 1;
                break;

            case BANK_4M:
                addr += (1 << 23);
                dev->ram_mask[c]        = 0x7ff;
                dev->row_phys_shift[c]  = 12;
                dev->ram_interleaved[c] = 0;
                break;

            case BANK_4M_INTERLEAVED:
                addr += (1 << 24);
                dev->ram_mask[c]        = 0x7ff;
                dev->row_phys_shift[c]  = 12;
                dev->ibank_shift[c]     = 23;
                dev->ram_interleaved[c] = 1;
                break;

            default:
                break;
        }
    }

    mem_set_mem_state(0xfe0000, 0x20000, MEM_READ_EXTANY | MEM_WRITE_EXTANY);

    for (uint8_t i = 0; i < 12; i++)
        dev->ems[i]      = (0x000c0000 + (i * EMS_PGSIZE)) >> 14;

    for (uint8_t i = 0; i < 24; i++)
        dev->ems[i + 12] = (0x00040000 + (i * EMS_PGSIZE)) >> 14;

    for (uint8_t i = 0; i < 64; i++) {
        dev->mem_pages[i].in_ram    = 1;
        dev->mem_pages[i].virt_addr = i * EMS_PGSIZE;
        dev->mem_pages[i].phys_addr = dev->mem_pages[i].virt_addr;
        dev->mem_pages[i].mem       = ram + dev->mem_pages[i].phys_addr;

        mem_mapping_add(&(dev->mem_mappings[i]),
                        i * EMS_PGSIZE, EMS_PGSIZE,
                        scamp_mem_readb, scamp_mem_readw, NULL,
                        scamp_mem_writeb, scamp_mem_writew, NULL,
                        dev->mem_pages[i].mem, MEM_MAPPING_INTERNAL,
                        &(dev->mem_pages[i]));

        if (i < 40) {
            mem_mapping_disable(&(dev->mem_mappings[i]));

            scamp_mem_update_state(dev, i * EMS_PGSIZE, EMS_PGSIZE, MEM_FLAG_READ | MEM_FLAG_WRITE, MEM_FMASK_RW);
        } else {
            /* This is needed to the state update actually occurs. */
            dev->mem_flags[i] = MEM_FLAG_READ | MEM_FLAG_WRITE;
            scamp_mem_update_state(dev, i * EMS_PGSIZE, EMS_PGSIZE, 0x00, MEM_FMASK_RW);

            if (i >= 56)
                scamp_mem_update_state(dev, i * EMS_PGSIZE, EMS_PGSIZE, MEM_FLAG_ROMCS, MEM_FMASK_ROMCS);
        }
    }

    dev->card_mem = NULL;

    for (uint8_t i = 0; i < 4; i++) {
        dev->card_pages[i].virt_addr = i * EMS_PGSIZE;
        dev->card_pages[i].phys_addr = dev->card_pages[i].virt_addr;
        dev->card_pages[i].mem       = dev->card_mem + dev->card_pages[i].phys_addr;
    }

    dev->port_92 = device_add(&port_92_device);

    return dev;
}

const device_t vlsi_scamp_device = {
    .name          = "VLSI SCAMP",
    .internal_name = "vlsi_scamp",
    .flags         = 0,
    .local         = 0,
    .init          = scamp_init,
    .close         = scamp_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};
