/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          Implementation of the AHA-154x series of SCSI Host Adapters
 *          made by Adaptec, Inc. These controllers were designed for
 *          the ISA bus.
 *
 * Authors: Fred N. van Kempen, <decwiz@yahoo.com>
 *          Original Buslogic version by SA1988 and Miran Grca.
 *
 *          Copyright 2017-2018 Fred N. van Kempen.
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/io.h>
#include <86box/timer.h>
#include <86box/mca.h>
#include <86box/mem.h>
#include <86box/mca.h>
#include <86box/rom.h>
#include <86box/device.h>
#include <86box/nvr.h>
#include <86box/dma.h>
#include <86box/pic.h>
#include <86box/plat.h>
#include <86box/fdd.h>
#include <86box/fdc.h>
#include <86box/isapnp.h>
#include <86box/scsi.h>
#include <86box/scsi_device.h>
#include <86box/scsi_aha154x.h>
#include <86box/scsi_x54x.h>

enum {
    AHA_154xA,
    AHA_154xB,
    AHA_154xC,
    AHA_154xCF,
    AHA_154xCP,
    AHA_1640
};

#define CMD_WRITE_EEPROM 0x22 /* UNDOC: Write EEPROM */
#define CMD_READ_EEPROM  0x23 /* UNDOC: Read EEPROM */
#define CMD_SHADOW_RAM   0x24 /* UNDOC: BIOS shadow ram */
#define CMD_BIOS_MBINIT  0x25 /* UNDOC: BIOS mailbox initialization */
#define CMD_MEMORY_MAP_1 0x26 /* UNDOC: Memory Mapper */
#define CMD_MEMORY_MAP_2 0x27 /* UNDOC: Memory Mapper */
#define CMD_EXTBIOS      0x28 /* UNDOC: return extended BIOS info */
#define CMD_MBENABLE     0x29 /* set mailbox interface enable */
#define CMD_BIOS_SCSI    0x82 /* start ROM BIOS SCSI command */

uint16_t aha_ports[] = {
    0x0330, 0x0334, 0x0230, 0x0234,
    0x0130, 0x0134, 0x0000, 0x0000
};

static uint8_t *aha1542cp_pnp_rom = NULL;

// static char    *aha1542cp_rev     = "F001";
static char     aha1542cp_rev[16] = { 0 };

static uint16_t fw_chksum         = 0x0000;

#pragma pack(push, 1)
typedef struct aha_setup_t {
    uint8_t  CustomerSignature[20];
    uint8_t  uAutoRetry;
    uint8_t  uBoardSwitches;
    uint8_t  uChecksum;
    uint8_t  uUnknown;
    addr24_t BIOSMailboxAddress;
} aha_setup_t;
#pragma pack(pop)

#ifdef ENABLE_AHA154X_LOG
int aha_do_log = ENABLE_AHA154X_LOG;

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

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

/*
 * Write data to the BIOS space.
 *
 * AHA-1542C's and up have a feature where they map a 128-byte
 * RAM space into the ROM BIOS' address space, and then use it
 * as working memory. This function implements the writing to
 * that memory.
 *
 * We enable/disable this memory through AHA command 0x24.
 */
static void
aha_mem_write(uint32_t addr, uint8_t val, void *priv)
{
    x54x_t *dev = (x54x_t *) priv;

    addr &= 0x3fff;

    if ((addr >= dev->rom_shram) && (dev->shram_mode & 1))
        dev->shadow_ram[addr & (dev->rom_shramsz - 1)] = val;
}

static uint8_t
aha_mem_read(uint32_t addr, void *priv)
{
    const x54x_t *dev = (x54x_t *) priv;
    const rom_t  *rom = &dev->bios;

    addr &= 0x3fff;

    if ((addr >= dev->rom_shram) && (dev->shram_mode & 2))
        return dev->shadow_ram[addr & (dev->rom_shramsz - 1)];

    return (rom->rom[addr]);
}

static uint8_t
aha154x_shram(x54x_t *dev, uint8_t cmd)
{
    /* If not supported, give up. */
    if (dev->rom_shram == 0x0000)
        return 0x04;

    /* Bit 0 = Shadow RAM write enable;
       Bit 1 = Shadow RAM read enable. */
    dev->shram_mode = cmd;

    /* Firmware expects 04 status. */
    return 0x04;
}

static void
aha_eeprom_save(x54x_t *dev)
{
    FILE *fp;

    fp = nvr_fopen(dev->nvr_path, "wb");
    if (fp) {
        fwrite(dev->nvr, 1, NVR_SIZE, fp);
        fclose(fp);
        fp = NULL;
    }
}

static uint8_t
aha154x_eeprom(x54x_t *dev, uint8_t cmd, UNUSED(uint8_t arg), uint8_t len, uint8_t off, uint8_t *bufp)
{
    uint8_t r = 0xff;
    int     c;

    aha_log("%s: EEPROM cmd=%02x, arg=%02x len=%d, off=%02x\n",
            dev->name, cmd, arg, len, off);

    /* Only if we can handle it.. */
    if (dev->nvr == NULL)
        return r;

    if (cmd == 0x22) {
        /* Write data to the EEPROM. */
        for (c = 0; c < len; c++)
            dev->nvr[(off + c) & 0xff] = bufp[c];
        r = 0;

        aha_eeprom_save(dev);

        if (dev->type == AHA_154xCF) {
            if (dev->fdc_address > 0) {
                fdc_remove(dev->fdc);
                fdc_set_base(dev->fdc, (dev->nvr[0] & EE0_ALTFLOP) ? FDC_SECONDARY_ADDR : FDC_PRIMARY_ADDR);
            }
        }
    }

    if (cmd == 0x23) {
        /* Read data from the EEPROM. */
        for (c = 0; c < len; c++)
            bufp[c] = dev->nvr[(off + c) & 0xff];
        r = len;
    }

    return r;
}

/* Map either the main or utility (Select) ROM into the memory space. */
static uint8_t
aha154x_mmap(x54x_t *dev, uint8_t cmd)
{
    aha_log("%s: MEMORY cmd=%02x\n", dev->name, cmd);

    switch (cmd) {
        case 0x26:
            /* Disable the mapper, so, set ROM1 active. */
            dev->bios.rom = dev->rom1;
            break;

        case 0x27:
            /* Enable the mapper, so, set ROM2 active. */
            dev->bios.rom = dev->rom2;
            break;

        default:
            break;
    }

    return 0;
}

static uint8_t
aha_get_host_id(void *priv)
{
    const x54x_t *dev = (x54x_t *) priv;

    return dev->nvr[0] & 0x07;
}

static uint8_t
aha_get_irq(void *priv)
{
    const x54x_t *dev = (x54x_t *) priv;

    return (dev->nvr[1] & 0x07) + 9;
}

static uint8_t
aha_get_dma(void *priv)
{
    const x54x_t *dev = (x54x_t *) priv;

    return (dev->nvr[1] >> 4) & 0x07;
}

static uint8_t
aha_cmd_is_fast(void *priv)
{
    const x54x_t *dev = (x54x_t *) priv;

    if (dev->Command == CMD_BIOS_SCSI)
        return 1;
    else
        return 0;
}

static uint8_t
aha_fast_cmds(void *priv, uint8_t cmd)
{
    x54x_t *dev = (x54x_t *) priv;

    if (cmd == CMD_BIOS_SCSI) {
        dev->BIOSMailboxReq++;
        return 1;
    }

    return 0;
}

static uint8_t
aha_param_len(void *priv)
{
    const x54x_t *dev = (x54x_t *) priv;

    switch (dev->Command) {
        case CMD_BIOS_MBINIT:
            /* Same as 0x01 for AHA. */
            return sizeof(MailboxInit_t);

        case CMD_SHADOW_RAM:
            return 1;

        case CMD_WRITE_EEPROM:
            return 35;

        case CMD_READ_EEPROM:
            return 3;

        case CMD_MBENABLE:
            return 2;

        case 0x39:
            return 3;

        case 0x40:
            return 2;

        default:
            return 0;
    }
}

static uint8_t
aha_cmds(void *priv)
{
    x54x_t              *dev = (x54x_t *) priv;
    const MailboxInit_t *mbi;

    if (!dev->CmdParamLeft) {
        aha_log("Running Operation Code 0x%02X\n", dev->Command);
        switch (dev->Command) {
            case CMD_WRITE_EEPROM: /* write EEPROM */
                /* Sent by CF BIOS. */
                dev->DataReplyLeft = aha154x_eeprom(dev,
                                                    dev->Command,
                                                    dev->CmdBuf[0],
                                                    dev->CmdBuf[1],
                                                    dev->CmdBuf[2],
                                                    &(dev->CmdBuf[3]));
                if (dev->DataReplyLeft == 0xff) {
                    dev->DataReplyLeft = 0;
                    dev->Status |= STAT_INVCMD;
                }
                break;

            case CMD_READ_EEPROM: /* read EEPROM */
                /* Sent by CF BIOS. */
                dev->DataReplyLeft = aha154x_eeprom(dev,
                                                    dev->Command,
                                                    dev->CmdBuf[0],
                                                    dev->CmdBuf[1],
                                                    dev->CmdBuf[2],
                                                    dev->DataBuf);
                if (dev->DataReplyLeft == 0xff) {
                    dev->DataReplyLeft = 0;
                    dev->Status |= STAT_INVCMD;
                }
                break;

            case CMD_SHADOW_RAM: /* Shadow RAM */
                /*
                 * For AHA1542CF, this is the command
                 * to play with the Shadow RAM.  BIOS
                 * gives us one argument (00,02,03)
                 * and expects a 0x04 back in the INTR
                 * register.  --FvK
                 */
#if 0
                dev->Interrupt = aha154x_shram(dev,val);
#endif
                dev->Interrupt = aha154x_shram(dev, dev->CmdBuf[0]);
                break;

            case CMD_BIOS_MBINIT: /* BIOS Mailbox Initialization */
                /* Sent by CF BIOS. */
                dev->flags |= X54X_MBX_24BIT;

                mbi = (MailboxInit_t *) dev->CmdBuf;

                dev->BIOSMailboxInit    = 1;
                dev->BIOSMailboxCount   = mbi->Count;
                dev->BIOSMailboxOutAddr = ADDR_TO_U32(mbi->Address);

                aha_log("Initialize BIOS Mailbox: MBO=0x%08lx, %d entries at 0x%08lx\n",
                        dev->BIOSMailboxOutAddr,
                        mbi->Count,
                        ADDR_TO_U32(mbi->Address));

                dev->Status &= ~STAT_INIT;
                dev->DataReplyLeft = 0;
                break;

            case CMD_MEMORY_MAP_1: /* AHA memory mapper */
            case CMD_MEMORY_MAP_2: /* AHA memory mapper */
                /* Sent by CF BIOS. */
                dev->DataReplyLeft = aha154x_mmap(dev, dev->Command);
                break;

            case CMD_EXTBIOS: /* Return extended BIOS information */
                dev->DataBuf[0]    = 0x08;
                dev->DataBuf[1]    = dev->Lock;
                dev->DataReplyLeft = 2;
                break;

            case CMD_MBENABLE: /* Mailbox interface enable Command */
                dev->DataReplyLeft = 0;
                if (dev->CmdBuf[1] == dev->Lock) {
                    if (dev->CmdBuf[0] & 1) {
                        dev->Lock = 1;
                    } else {
                        dev->Lock = 0;
                    }
                }
                break;

            case 0x2C: /* Detect termination status */
                /* Bits 7,6 are termination status and must be 1,0 for the BIOS to work. */
                dev->DataBuf[0]    = 0x40;
                dev->DataReplyLeft = 1;
                break;

            case 0x2D: /* ???? - Returns two bytes according to the microcode */
                dev->DataBuf[0]    = 0x00;
                dev->DataBuf[0]    = 0x00;
                dev->DataReplyLeft = 2;
                break;

            case 0x33: /* Send the SCSISelect code decompressor program */
                if (dev->cmd_33_len == 0x0000) {
                    /* If we are on a controller without this command, return invalid command. */
                    dev->DataReplyLeft = 0;
                    dev->Status |= STAT_INVCMD;
                    break;
                }

                /* We have to send (decompressor program length + 2 bytes of little endian size). */
                dev->DataReplyLeft = dev->cmd_33_len + 2;
                memset(dev->DataBuf, 0x00, dev->DataReplyLeft);
                dev->DataBuf[0] = dev->cmd_33_len & 0xff;
                dev->DataBuf[1] = (dev->cmd_33_len >> 8) & 0xff;
                memcpy(&(dev->DataBuf[2]), dev->cmd_33_buf, dev->cmd_33_len);
                break;

            case 0x39: /* Receive 3 bytes: address high, address low, byte to write to that address. */
                /* Since we are not running the actual microcode, just log the received values
                   (if logging is enabled) and break. */
                aha_log("aha_cmds(): Command 0x39: %02X -> %02X%02X\n",
                        dev->CmdBuf[2], dev->CmdBuf[0], dev->CmdBuf[1]);
                break;

            case 0x40: /* Receive 2 bytes: address high, address low, then return one byte from that
                          address. */
                aha_log("aha_cmds(): Command 0x40: %02X%02X\n",
                        dev->CmdBuf[0], dev->CmdBuf[1]);
                dev->DataReplyLeft = 1;
                dev->DataBuf[0]    = 0xff;
                break;

            default:
                dev->DataReplyLeft = 0;
                dev->Status |= STAT_INVCMD;
                break;
        }
    }

    return 0;
}

static void
aha_setup_data(void *priv)
{
    x54x_t                       *dev = (x54x_t *) priv;
    ReplyInquireSetupInformation *ReplyISI;
    aha_setup_t                  *aha_setup;

    ReplyISI  = (ReplyInquireSetupInformation *) dev->DataBuf;
    aha_setup = (aha_setup_t *) ReplyISI->VendorSpecificData;

    ReplyISI->fSynchronousInitiationEnabled = dev->sync & 1;
    ReplyISI->fParityCheckingEnabled        = dev->parity & 1;

    U32_TO_ADDR(aha_setup->BIOSMailboxAddress, dev->BIOSMailboxOutAddr);
    // aha_setup->uChecksum = 0xA3;
    // aha_setup->uUnknown  = 0xC2;
    aha_setup->uChecksum = fw_chksum >> 8;
    aha_setup->uUnknown  = fw_chksum & 0xff;
}

static void
aha_do_bios_mail(x54x_t *dev)
{
    dev->MailboxIsBIOS = 1;

    if (!dev->BIOSMailboxCount) {
        aha_log("aha_do_bios_mail(): No BIOS Mailboxes\n");
        return;
    }

    /* Search for a filled mailbox - stop if we have scanned all mailboxes. */
    for (dev->BIOSMailboxOutPosCur = 0; dev->BIOSMailboxOutPosCur < dev->BIOSMailboxCount; dev->BIOSMailboxOutPosCur++) {
        if (x54x_mbo_process(dev))
            break;
    }
}

static void
aha_callback(void *priv)
{
    x54x_t *dev = (x54x_t *) priv;

    if (dev->BIOSMailboxInit && dev->BIOSMailboxReq)
        aha_do_bios_mail(dev);
}

static uint8_t
aha_mca_read(const uint16_t port, void *priv)
{
    const x54x_t *dev = (x54x_t *) priv;

    return (dev->pos_regs[port & 7]);
}

static void
aha_mca_write(const uint16_t port, uint8_t val, void *priv)
{
    x54x_t *dev = (x54x_t *) priv;

    /* MCA does not write registers below 0x0100. */
    if (port < 0x0102)
        return;

    /* Save the MCA register value. */
    dev->pos_regs[port & 7] = val;

    /* This is always necessary so that the old handler doesn't remain. */
    x54x_io_remove(dev, dev->Base, 4);

    /* Get the new assigned I/O base address. */
    dev->Base = (dev->pos_regs[3] & 7) << 8;
    dev->Base |= ((dev->pos_regs[3] & 0x40) ? 0x34 : 0x30);

    /* Save the new IRQ and DMA channel values. */
    dev->Irq        = (dev->pos_regs[4] & 0x07) + 8;
    dev->DmaChannel = dev->pos_regs[5] & 0x0f;

    /* Extract the BIOS ROM address info. */
    if (!(dev->pos_regs[2] & 0x80))
        switch (dev->pos_regs[3] & 0x38) {
            case 0x38: /* [1]=xx11 1xxx */
                dev->rom_addr = 0xDC000;
                break;

            case 0x30: /* [1]=xx11 0xxx */
                dev->rom_addr = 0xD8000;
                break;

            case 0x28: /* [1]=xx10 1xxx */
                dev->rom_addr = 0xD4000;
                break;

            case 0x20: /* [1]=xx10 0xxx */
                dev->rom_addr = 0xD0000;
                break;

            case 0x18: /* [1]=xx01 1xxx */
                dev->rom_addr = 0xCC000;
                break;

            case 0x10: /* [1]=xx01 0xxx */
                dev->rom_addr = 0xC8000;
                break;

            default:
                break;
        }
    else {
        /* Disabled. */
        dev->rom_addr = 0x000000;
    }

    /*
     * Get misc SCSI config stuff.  For now, we are only
     * interested in the configured HA target ID:
     *
     *  pos[2]=111xxxxx = 7
     *  pos[2]=000xxxxx = 0
     */
    dev->HostID = (dev->pos_regs[4] >> 5) & 0x07;

    /*
     * SYNC mode is pos[2]=xxxx1xxx.
     *
     * SCSI Parity is pos[2]=xxx1xxxx.
     */
    dev->sync   = (dev->pos_regs[4] >> 3) & 1;
    dev->parity = (dev->pos_regs[4] >> 4) & 1;

    /*
     * The PS/2 Model 80 BIOS always enables a card if it finds one,
     * even if no resources were assigned yet (because we only added
     * the card, but have not run AutoConfig yet...)
     *
     * So, remove current address, if any.
     */
    mem_mapping_disable(&dev->bios.mapping);

    /* Initialize the device if fully configured. */
    if (dev->pos_regs[2] & 0x01) {
        /* Card enabled; register (new) I/O handler. */
        x54x_io_set(dev, dev->Base, 4);

        /* Reset the device. */
        x54x_reset_ctrl(dev, CTRL_HRST);

        /* Enable or disable the BIOS ROM. */
        if (dev->rom_addr != 0x000000) {
            mem_mapping_enable(&dev->bios.mapping);
            mem_mapping_set_addr(&dev->bios.mapping, dev->rom_addr, ROM_SIZE);
        }

        /* Say hello. */
        aha_log("AHA-1640: I/O=%04x, IRQ=%d, DMA=%d, BIOS @%05X, HOST ID %i\n",
                dev->Base, dev->Irq, dev->DmaChannel, dev->rom_addr, dev->HostID);
    }
}

static uint8_t
aha_mca_feedb(void *priv)
{
    const x54x_t *dev = (x54x_t *) priv;

    return (dev->pos_regs[2] & 0x01);
}

static void
aha_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv)
{
    x54x_t *dev = (x54x_t *) priv;
    int     i;

    switch (ld) {
        case 0:
            if (dev->Base) {
                x54x_io_remove(dev, dev->Base, 4);
                dev->Base = 0;
            }

            dev->Irq        = 0;
            dev->DmaChannel = ISAPNP_DMA_DISABLED;
            dev->rom_addr   = 0;

            mem_mapping_disable(&dev->bios.mapping);

            if (config->activate) {
                dev->Base = config->io[0].base;
                if (dev->Base != ISAPNP_IO_DISABLED)
                    x54x_io_set(dev, dev->Base, 4);

                /*
                 * Patch the ROM BIOS image for stuff Adaptec deliberately
                 * made hard to understand. Well, maybe not, maybe it was
                 * their way of handling issues like these at the time..
                 *
                 * Patch 1: emulate the I/O ADDR SW setting by patching a
                 *      byte in the BIOS that indicates the I/O ADDR
                 *      switch setting on the board.
                 */
                if (dev->rom_ioaddr != 0x0000) {
                    /* Look up the I/O address in the table. */
                    for (i = 0; i < 8; i++)
                        if (aha_ports[i] == dev->Base)
                            break;
                    if (i == 8) {
                        aha_log("%s: invalid I/O address %04x selected!\n",
                                dev->name, dev->Base);
                        return;
                    }
                    dev->bios.rom[dev->rom_ioaddr] = (uint8_t) i;
                    /* Negation of the DIP switches to satify the checksum. */
                    dev->bios.rom[dev->rom_ioaddr + 1] = (uint8_t) ((i ^ 0xff) + 1);
                }

                dev->Irq        = config->irq[0].irq;
                dev->DmaChannel = config->dma[0].dma;

                dev->nvr[1] = (dev->Irq - 9) | (dev->DmaChannel << 4);
                aha_eeprom_save(dev);

                dev->rom_addr = config->mem[0].base;
                aha_log("Base = %08X, Size = %08X\n", config->mem[0].base, config->mem[0].size);
                if (dev->rom_addr) {
                    mem_mapping_enable(&dev->bios.mapping);
                    aha_log("SCSI BIOS set to: %08X-%08X\n", dev->rom_addr, dev->rom_addr + config->mem[0].size - 1);
                    mem_mapping_set_addr(&dev->bios.mapping, dev->rom_addr, config->mem[0].size);
                }
            }

            break;

#ifdef AHA1542CP_FDC
        case 1:
            if (dev->fdc_address) {
                fdc_remove(dev->fdc);
                dev->fdc_address = 0;
            }

            fdc_set_irq(dev->fdc, 0);
            fdc_set_dma_ch(dev->fdc, ISAPNP_DMA_DISABLED);

            if (config->activate) {
                dev->fdc_address = config->io[0].base;
                if (dev->fdc_address != ISAPNP_IO_DISABLED)
                    fdc_set_base(dev->fdc, dev->fdc_address);

                fdc_set_irq(dev->fdc, config->irq[0].irq);
                fdc_set_dma_ch(dev->fdc, config->dma[0].dma);
            }

            break;
#endif
        default:
            break;
    }
}

/* Initialize the board's ROM BIOS. */
static void
aha_setbios(x54x_t *dev)
{
    uint32_t size;
    uint32_t mask;
    uint32_t temp;
    FILE    *fp;
    int      i;

    /* Only if this device has a BIOS ROM. */
    if (dev->bios_path == NULL)
        return;

    /* Open the BIOS image file and make sure it exists. */
    aha_log("%s: loading BIOS from '%s'\n", dev->name, dev->bios_path);
    if ((fp = rom_fopen(dev->bios_path, "rb")) == NULL) {
        aha_log("%s: BIOS ROM not found!\n", dev->name);
        return;
    }

    /*
     * Manually load and process the ROM image.
     *
     * We *could* use the system "rom_init" function here, but for
     * this special case, we can't: we may need WRITE access to the
     * memory later on.
     */
    (void) fseek(fp, 0L, SEEK_END);
    temp = ftell(fp);
    (void) fseek(fp, 0L, SEEK_SET);

    /* Load first chunk of BIOS (which is the main BIOS, aka ROM1.) */
    dev->rom1 = calloc(1, ROM_SIZE);
    (void) !fread(dev->rom1, ROM_SIZE, 1, fp);
    temp -= ROM_SIZE;
    if (temp > 0) {
        dev->rom2 = calloc(1, ROM_SIZE);
        (void) !fread(dev->rom2, ROM_SIZE, 1, fp);
        temp -= ROM_SIZE;
    } else {
        dev->rom2 = NULL;
    }
    if (temp != 0) {
        aha_log("%s: BIOS ROM size invalid!\n", dev->name);
        free(dev->rom1);
        if (dev->rom2 != NULL)
            free(dev->rom2);
        (void) fclose(fp);
        return;
    }
    temp = ftell(fp);
    if (temp > ROM_SIZE)
        temp = ROM_SIZE;
    (void) fclose(fp);

    /* Adjust BIOS size in chunks of 2K, as per BIOS spec. */
    size = 0x10000;
    if (temp <= 0x8000)
        size = 0x8000;
    if (temp <= 0x4000)
        size = 0x4000;
    if (temp <= 0x2000)
        size = 0x2000;
    mask = (size - 1);
    aha_log("%s: BIOS at 0x%06lX, size %lu, mask %08lx\n",
            dev->name, dev->rom_addr, size, mask);

    /* Initialize the ROM entry for this BIOS. */
    memset(&dev->bios, 0x00, sizeof(rom_t));

    /* Enable ROM1 into the memory map. */
    dev->bios.rom = dev->rom1;

    /* Set up an address mask for this memory. */
    dev->bios.mask = mask;

    /* Map this system into the memory map. */
    mem_mapping_add(&dev->bios.mapping, dev->rom_addr, size,
                    aha_mem_read, NULL, NULL, /* aha_mem_readw, aha_mem_readl, */
                    aha_mem_write, NULL, NULL,
                    dev->bios.rom, MEM_MAPPING_EXTERNAL, dev);
    mem_mapping_disable(&dev->bios.mapping);

    /*
     * Patch the ROM BIOS image for stuff Adaptec deliberately
     * made hard to understand. Well, maybe not, maybe it was
     * their way of handling issues like these at the time..
     *
     * Patch 1: emulate the I/O ADDR SW setting by patching a
     *      byte in the BIOS that indicates the I/O ADDR
     *      switch setting on the board.
     */
    if (dev->rom_ioaddr != 0x0000) {
        /* Look up the I/O address in the table. */
        for (i = 0; i < 8; i++)
            if (aha_ports[i] == dev->Base)
                break;
        if (i == 8) {
            aha_log("%s: invalid I/O address %04x selected!\n",
                    dev->name, dev->Base);
            return;
        }
        dev->bios.rom[dev->rom_ioaddr] = (uint8_t) i;
        /* Negation of the DIP switches to satify the checksum. */
        dev->bios.rom[dev->rom_ioaddr + 1] = (uint8_t) ((i ^ 0xff) + 1);
    }
}

/* Get the SCSISelect code decompressor program from the microcode rom for the
   AHA-1542CP. */
static void
aha_setmcode(x54x_t *dev)
{
    uint32_t temp;
    FILE    *fp;
    uint16_t tempb = 0x00;

    /* Only if this device has a BIOS ROM. */
    if (dev->mcode_path == NULL)
        return;

    /* Open the microcode image file and make sure it exists. */
    aha_log("%s: loading microcode from '%s'\n", dev->name, dev->bios_path);
    if ((fp = rom_fopen(dev->mcode_path, "rb")) == NULL) {
        aha_log("%s: microcode ROM not found!\n", dev->name);
        return;
    }

    /*
     * Manually load and process the ROM image.
     *
     * We *could* use the system "rom_init" function here, but for
     * this special case, we can't: we may need WRITE access to the
     * memory later on.
     */
    (void) fseek(fp, 0L, SEEK_END);
    temp = ftell(fp);
    (void) fseek(fp, 0L, SEEK_SET);

    if (temp < (dev->cmd_33_offset + dev->cmd_33_len - 1)) {
        aha_log("%s: microcode ROM size invalid!\n", dev->name);
        (void) fclose(fp);
        return;
    }

    fseek(fp, 0x3136, SEEK_SET);
    (void) !fread(dev->fw_rev, 4, 1, fp);

    /* Allocate the buffer and then read the real PnP ROM into it. */
    if (aha1542cp_pnp_rom != NULL) {
        free(aha1542cp_pnp_rom);
        aha1542cp_pnp_rom = NULL;
    }
    aha1542cp_pnp_rom = (uint8_t *) calloc(1, dev->pnp_len + 7);
    fseek(fp, dev->pnp_offset, SEEK_SET);
    (void) !fread(aha1542cp_pnp_rom, 4, 1, fp);
    memset(&(aha1542cp_pnp_rom[4]), 0x00, 5);
    fseek(fp, dev->pnp_offset + 4, SEEK_SET);
    (void) !fread(&(aha1542cp_pnp_rom[9]), dev->pnp_len - 4, 1, fp);
    /* Patch determined from Dizzy's AHA-1542CP PNP ROM dump. */
    aha1542cp_pnp_rom[0x26] = 0x03;
    /* Insert the terminator and the checksum byte that will later
       be filled in by the isapnp code. */
    aha1542cp_pnp_rom[dev->pnp_len + 5] = 0x79;
    aha1542cp_pnp_rom[dev->pnp_len + 6] = 0x00;

    /* Load the SCSISelect decompression code. */
    fseek(fp, dev->cmd_33_offset, SEEK_SET);
    (void) !fread(dev->cmd_33_buf, dev->cmd_33_len, 1, fp);

    fw_chksum = 0x0000;

    for (uint16_t i = 0; i < 32768; i++) {
        (void) fseek(fp, i, SEEK_SET);
        (void) !fread(&tempb, 1, 1, fp);
        fw_chksum += tempb;
    }

    (void) fclose(fp);
}

static void
aha_initnvr(x54x_t *dev)
{
    /* Initialize the on-board EEPROM. */
    dev->nvr[0] = dev->HostID; /* SCSI ID 7 */
    dev->nvr[0] |= (0x10 | 0x20 | 0x40);
    if (dev->fdc_address == FDC_SECONDARY_ADDR)
        dev->nvr[0] |= EE0_ALTFLOP;
    dev->nvr[1] = dev->Irq - 9;            /* IRQ15 */
    dev->nvr[1] |= (dev->DmaChannel << 4); /* DMA6 */
    dev->nvr[2] = (EE2_HABIOS |            /* BIOS enabled */
                   EE2_DYNSCAN |           /* scan bus */
                   EE2_EXT1G | EE2_RMVOK); /* Imm return on seek */
    dev->nvr[3] = SPEED_50;                /* speed 5.0 MB/s */
    dev->nvr[6] = (EE6_TERM |              /* host term enable */
                   EE6_RSTBUS);            /* reset SCSI bus on boot */
}

/* Initialize the board's EEPROM (NVR.) */
static void
aha_setnvr(x54x_t *dev)
{
    FILE *fp;

    /* Only if this device has an EEPROM. */
    if (dev->nvr_path[0] == 0x00)
        return;

    /* Allocate and initialize the EEPROM. */
    dev->nvr = (uint8_t *) calloc(1, NVR_SIZE);

    fp = nvr_fopen(dev->nvr_path, "rb");
    if (fp) {
        if (fread(dev->nvr, 1, NVR_SIZE, fp) != NVR_SIZE)
            fatal("aha_setnvr(): Error reading data\n");
        fclose(fp);
        fp = NULL;
    } else
        aha_initnvr(dev);

    if (dev->type == AHA_154xCF) {
        if (dev->fdc_address > 0) {
            fdc_remove(dev->fdc);
            fdc_set_base(dev->fdc, (dev->nvr[0] & EE0_ALTFLOP) ? FDC_SECONDARY_ADDR : FDC_PRIMARY_ADDR);
        }
    }
}

void
aha1542cp_close(void *priv)
{
    if (aha1542cp_pnp_rom != NULL) {
        free(aha1542cp_pnp_rom);
        aha1542cp_pnp_rom = NULL;
    }

    x54x_close(priv);
}

/* General initialization routine for all boards. */
static void *
aha_init(const device_t *info)
{
    x54x_t *dev;
    const char *bios_rev = NULL;

    /* Call common initializer. */
    dev      = x54x_init(info);
    dev->bus = scsi_get_bus();

    /*
     * Set up the (initial) I/O address, IRQ and DMA info.
     *
     * Note that on MCA, configuration is handled by the BIOS,
     * and so any info we get here will be overwritten by the
     * MCA-assigned values later on!
     */
    dev->Base       = device_get_config_hex16("base");
    dev->Irq        = device_get_config_int("irq");
    dev->DmaChannel = device_get_config_int("dma");
    dev->rom_addr   = device_get_config_hex20("bios_addr");
    if (!(dev->card_bus & DEVICE_MCA))
        dev->fdc_address = device_get_config_hex16("fdc_addr");
    else
        dev->fdc_address = 0;
    dev->HostID         = 7; /* default HA ID */
    dev->setup_info_len = sizeof(aha_setup_t);
    dev->max_id         = 7;
    dev->flags          = 0;

    dev->ven_callback      = aha_callback;
    dev->ven_cmd_is_fast   = aha_cmd_is_fast;
    dev->ven_fast_cmds     = aha_fast_cmds;
    dev->get_ven_param_len = aha_param_len;
    dev->ven_cmds          = aha_cmds;
    dev->get_ven_data      = aha_setup_data;

    dev->mcode_path    = NULL;
    dev->cmd_33_len    = 0x0000;
    dev->cmd_33_offset = 0x0000;
    memset(dev->cmd_33_buf, 0x00, 4096);

    strcpy(dev->vendor, "Adaptec");

    fw_chksum          = 0xa3c2;

    /* Perform per-board initialization. */
    switch (dev->type) {
        case AHA_154xA:
            strcpy(dev->name, "AHA-154xA");
            bios_rev         = (char *) device_get_config_bios("bios_rev");
            dev->bios_path   = (char *) device_get_bios_file(info, bios_rev, 0);
            dev->fw_rev    = "A006"; /*3.07 (Port 0x330) normal microcode (M_E7BC.BIN)*/

            /* This is configurable from the configuration for the 154xB, the rest of the controllers read
               it from the EEPROM. */
            dev->HostID      = device_get_config_int("hostid");
            dev->rom_shram   = 0x3F80;    /* shadow RAM address base */
            dev->rom_shramsz = 128;       /* size of shadow RAM */
            dev->ha_bps      = 5000000.0; /* normal SCSI */
            break;

        case AHA_154xB:
            strcpy(dev->name, "AHA-154xB");
            bios_rev         = (char *) device_get_config_bios("bios_rev");
            dev->bios_path   = (char *) device_get_bios_file(info, bios_rev, 0);
            if (!strcmp(bios_rev, "v3_08"))
                dev->fw_rev = "A003"; /*3.08 (Port 0x330) normal microcode (U12 27C128 Microcode v3.08.bin)*/
            else if (!strcmp(bios_rev, "v3_10") || !strcmp(bios_rev, "v3_10_p334") || !strcmp(bios_rev, "v3_1b_p334"))
                dev->fw_rev = "A005"; /*3.10 (and revisions) normal microcode (M_FC8A.BIN)*/
            else if (!strcmp(bios_rev, "v3_11"))
                dev->fw_rev = "A008"; /*3.11 (Port 0x330) normal microcode (1542BU12V311.BIN)*/
            else if (!strcmp(bios_rev, "v3_20") || !strcmp(bios_rev, "v3_20_p334"))
                dev->fw_rev = "A014"; /*3.20 normal microcode (M_3054.BIN)*/
            else
                dev->fw_rev = "A012"; /*3.20 (port 0x330) extended timeout microcode (M_5D98.BIN)*/

            /* This is configurable from the configuration for the 154xB, the rest of the controllers read
               it from the EEPROM. */
            dev->HostID      = device_get_config_int("hostid");
            dev->rom_shram   = 0x3F80;    /* shadow RAM address base */
            dev->rom_shramsz = 128;       /* size of shadow RAM */
            dev->ha_bps      = 5000000.0; /* normal SCSI */
            break;

        case AHA_154xC:
            strcpy(dev->name, "AHA-154xC");
            dev->bios_path       = "roms/scsi/adaptec/aha1542c102.bin";
            sprintf(dev->nvr_path, "aha1542c_%i.nvr", device_get_instance());
            dev->fw_rev          = "D001";
            dev->rom_shram       = 0x3F80;          /* shadow RAM address base */
            dev->rom_shramsz     = 128;             /* size of shadow RAM */
            dev->rom_ioaddr      = 0x3F7E;          /* [2:0] idx into addr table */
            dev->rom_fwhigh      = 0x0022;          /* firmware version (hi/lo) */
            dev->flags |= X54X_HAS_SIGNATURE;
            dev->ven_get_host_id = aha_get_host_id; /* function to return host ID from EEPROM */
            dev->ven_get_irq     = aha_get_irq;     /* function to return IRQ from EEPROM */
            dev->ven_get_dma     = aha_get_dma;     /* function to return DMA channel from EEPROM */
            dev->ha_bps          = 5000000.0;       /* normal SCSI */
            break;

        case AHA_154xCF:
            strcpy(dev->name, "AHA-154xCF");
            dev->bios_path   = "roms/scsi/adaptec/aha1542cf211.bin";
            sprintf(dev->nvr_path, "aha1542cf_%i.nvr", device_get_instance());
            dev->fw_rev      = "E001";
            dev->rom_shram   = 0x3F80; /* shadow RAM address base */
            dev->rom_shramsz = 128;    /* size of shadow RAM */
            dev->rom_ioaddr  = 0x3F7E; /* [2:0] idx into addr table */
            dev->rom_fwhigh  = 0x0022; /* firmware version (hi/lo) */
            dev->flags |= X54X_CDROM_BOOT;
            dev->flags |= X54X_HAS_SIGNATURE;
            dev->ven_get_host_id = aha_get_host_id; /* function to return host ID from EEPROM */
            dev->ven_get_irq     = aha_get_irq;     /* function to return IRQ from EEPROM */
            dev->ven_get_dma     = aha_get_dma;     /* function to return DMA channel from EEPROM */
            dev->ha_bps          = 10000000.0;      /* fast SCSI */
            if (dev->fdc_address > 0)
                dev->fdc = device_add(&fdc_at_device);
            break;

        case AHA_154xCP:
            strcpy(dev->name, "AHA-154xCP");
            bios_rev         = (char *) device_get_config_bios("bios_rev");
            dev->bios_path   = (char *) device_get_bios_file(info, bios_rev, 0);
            dev->mcode_path  = (char *) device_get_bios_file(info, bios_rev, 1);
            sprintf(dev->nvr_path, "aha1542cp_%i.nvr", device_get_instance());
            dev->fw_rev      = aha1542cp_rev;
            dev->rom_shram   = 0x3F80; /* shadow RAM address base */
            dev->rom_shramsz = 128;    /* size of shadow RAM */
            dev->rom_ioaddr  = 0x3F7E; /* [2:0] idx into addr table */
            dev->rom_fwhigh  = 0x0055; /* firmware version (hi/lo) */
            dev->flags |= X54X_CDROM_BOOT;
            dev->flags |= X54X_ISAPNP;
            dev->flags |= X54X_HAS_SIGNATURE;
            dev->ven_get_host_id = aha_get_host_id; /* function to return host ID from EEPROM */
            dev->ven_get_irq     = aha_get_irq;     /* function to return IRQ from EEPROM */
            dev->ven_get_dma     = aha_get_dma;     /* function to return DMA channel from EEPROM */
            dev->ha_bps          = 10000000.0;      /* fast SCSI */
            dev->pnp_len         = 0x00be;          /* length of the PnP ROM */
            if (!strcmp(bios_rev, "v1_02_en"))
                dev->pnp_offset      = 0x533d;          /* offset of the PnP ROM in the microcode ROM */
            else
                dev->pnp_offset      = 0x5345;          /* offset of the PnP ROM in the microcode ROM */
            dev->cmd_33_len      = 0x06dc;          /* length of the SCSISelect code expansion routine
                                                       returned by SCSI controller command 0x33 */
            dev->cmd_33_offset   = 0x7000;          /* offset of the SCSISelect code expansion routine
                                                       in the microcode ROM */
            aha_setmcode(dev);
            if (aha1542cp_pnp_rom)
                isapnp_add_card(aha1542cp_pnp_rom, dev->pnp_len + 7, aha_pnp_config_changed, NULL, NULL, NULL, dev);
#ifdef AHA1542CP_FDC
            dev->fdc = device_add(&fdc_at_device);
#endif
            break;

        case AHA_1640:
            strcpy(dev->name, "AHA-1640");
            dev->bios_path = "roms/scsi/adaptec/aha1640.bin";
            dev->fw_rev    = "BB01";

            dev->flags |= X54X_LBA_BIOS;
            dev->flags |= X54X_HAS_SIGNATURE; /*To be confirmed*/

            /* Enable MCA. */
            dev->pos_regs[0] = 0x1F; /* MCA board ID */
            dev->pos_regs[1] = 0x0F;
            mca_add(aha_mca_read, aha_mca_write, aha_mca_feedb, NULL, dev);
            dev->ha_bps = 5000000.0; /* normal SCSI */
            break;

        default:
            break;
    }

    scsi_bus_set_speed(dev->bus, dev->ha_bps);

    /* Initialize ROM BIOS if needed. */
    aha_setbios(dev);

    /* Initialize EEPROM (NVR) if needed. */
    aha_setnvr(dev);

    if (dev->Base != 0) {
        /* Initialize the device. */
        x54x_device_reset(dev);

        if (!(dev->card_bus & DEVICE_MCA) && !(dev->flags & X54X_ISAPNP)) {
            /* Register our address space. */
            x54x_io_set(dev, dev->Base, 4);

            /* Enable the memory. */
            if (dev->rom_addr != 0x000000) {
                mem_mapping_enable(&dev->bios.mapping);
                mem_mapping_set_addr(&dev->bios.mapping, dev->rom_addr, ROM_SIZE);
            }
        }
    }

    return dev;
}

// clang-format off
static const device_config_t aha_154xa_config[] = {
    {
        .name           = "base",
        .description    = "Address",
        .type           = CONFIG_HEX16,
        .default_string = NULL,
        .default_int    = 0x334,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "None",  .value =     0 },
            { .description = "0x330", .value = 0x330 },
            { .description = "0x334", .value = 0x334 },
            { .description = "0x230", .value = 0x230 },
            { .description = "0x234", .value = 0x234 },
            { .description = "0x130", .value = 0x130 },
            { .description = "0x134", .value = 0x134 },
            { .description = ""                      }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "irq",
        .description    = "IRQ",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 11,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "IRQ 9",  .value =  9 },
            { .description = "IRQ 10", .value = 10 },
            { .description = "IRQ 11", .value = 11 },
            { .description = "IRQ 12", .value = 12 },
            { .description = "IRQ 14", .value = 14 },
            { .description = "IRQ 15", .value = 15 },
            { .description = ""                    }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "dma",
        .description    = "DMA",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 6,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "DMA 5", .value = 5 },
            { .description = "DMA 6", .value = 6 },
            { .description = "DMA 7", .value = 7 },
            { .description = ""                  }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "hostid",
        .description    = "Host ID",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 7,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "0", .value = 0 },
            { .description = "1", .value = 1 },
            { .description = "2", .value = 2 },
            { .description = "3", .value = 3 },
            { .description = "4", .value = 4 },
            { .description = "5", .value = 5 },
            { .description = "6", .value = 6 },
            { .description = "7", .value = 7 },
            { .description = ""              }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "bios_rev",
        .description    = "BIOS Revision",
        .type           = CONFIG_BIOS,
        .default_string = "v3_07",
        .default_int    = 0,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .bios           = {
            {
                .name          = "Version 3.07 (Port 0x330)",
                .internal_name = "v3_07",
                .bios_type     = BIOS_NORMAL,
                .files_no      = 1,
                .local         = 0,
                .size          = 16384,
                .files         = { "roms/scsi/adaptec/aha1540a307.bin", "" }
            },
            { .files_no = 0 }
        },
    },
    {
        .name           = "bios_addr",
        .description    = "BIOS address",
        .type           = CONFIG_HEX20,
        .default_string = NULL,
        .default_int    = 0,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "Disabled", .value =       0 },
            { .description = "C800H",    .value = 0xc8000 },
            { .description = "D000H",    .value = 0xd0000 },
            { .description = "D800H",    .value = 0xd8000 },
            { .description = "DC00H",    .value = 0xdc000 },
            { .description = ""                           }
        },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
};

static const device_config_t aha_154xb_config[] = {
    {
        .name           = "base",
        .description    = "Address",
        .type           = CONFIG_HEX16,
        .default_string = NULL,
        .default_int    = 0x334,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "None",  .value =     0 },
            { .description = "0x330", .value = 0x330 },
            { .description = "0x334", .value = 0x334 },
            { .description = "0x230", .value = 0x230 },
            { .description = "0x234", .value = 0x234 },
            { .description = "0x130", .value = 0x130 },
            { .description = "0x134", .value = 0x134 },
            { .description = ""                      }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "irq",
        .description    = "IRQ",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 11,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "IRQ 9",  .value =  9 },
            { .description = "IRQ 10", .value = 10 },
            { .description = "IRQ 11", .value = 11 },
            { .description = "IRQ 12", .value = 12 },
            { .description = "IRQ 14", .value = 14 },
            { .description = "IRQ 15", .value = 15 },
            { .description = ""                    }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "dma",
        .description    = "DMA",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 6,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "DMA 5", .value = 5 },
            { .description = "DMA 6", .value = 6 },
            { .description = "DMA 7", .value = 7 },
            { .description = ""                  }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "hostid",
        .description    = "Host ID",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 7,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "0", .value = 0 },
            { .description = "1", .value = 1 },
            { .description = "2", .value = 2 },
            { .description = "3", .value = 3 },
            { .description = "4", .value = 4 },
            { .description = "5", .value = 5 },
            { .description = "6", .value = 6 },
            { .description = "7", .value = 7 },
            { .description = ""              }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "bios_rev",
        .description    = "BIOS Revision",
        .type           = CONFIG_BIOS,
        .default_string = "v3_20_p334",
        .default_int    = 0,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .bios           = {
            {
                .name          = "Version 3.08 (Port 0x330)",
                .internal_name = "v3_08",
                .bios_type     = BIOS_NORMAL,
                .files_no      = 1,
                .local         = 0,
                .size          = 16384,
                .files         = { "roms/scsi/adaptec/U13 27C128 BIOS v3.08.bin", "" }
            },
            {
                .name          = "Version 3.10 (Port 0x330)",
                .internal_name = "v3_10",
                .bios_type     = BIOS_NORMAL,
                .files_no      = 1,
                .local         = 0,
                .size          = 16384,
                .files         = { "roms/scsi/adaptec/aha1540b310.bin", "" }
            },
            {
                .name          = "Version 3.10 (Port 0x334)",
                .internal_name = "v3_10_p334",
                .bios_type     = BIOS_NORMAL,
                .files_no      = 1,
                .local         = 0,
                .size          = 16384,
                .files         = { "roms/scsi/adaptec/154xp334.bin", "" }
            },
            {
                .name          = "Version 3.1b (Port 0x334)",
                .internal_name = "v3_1b_p334",
                .bios_type     = BIOS_NORMAL,
                .files_no      = 1,
                .local         = 0,
                .size          = 16384,
                .files         = { "roms/scsi/adaptec/154xp334_v31b.bin", "" }
            },
            {
                .name          = "Version 3.11 (Port 0x330)",
                .internal_name = "v3_11",
                .bios_type     = BIOS_NORMAL,
                .files_no      = 1,
                .local         = 0,
                .size          = 16384,
                .files         = { "roms/scsi/adaptec/1542BU13V311.BIN", "" }
            },
            {
                .name          = "Version 3.20 (Port 0x330)",
                .internal_name = "v3_20",
                .bios_type     = BIOS_NORMAL,
                .files_no      = 1,
                .local         = 0,
                .size          = 16384,
                .files         = { "roms/scsi/adaptec/aha1540b320_330.bin", "" }
            },
            {
                .name          = "Version 3.20 (Port 0x330) (Ext. Timeout)",
                .internal_name = "v3_20_exttimeout",
                .bios_type     = BIOS_NORMAL,
                .files_no      = 1,
                .local         = 0,
                .size          = 16384,
                .files         = { "roms/scsi/adaptec/B_B300.BIN", "" }
            },
            {
                .name          = "Version 3.20 (Port 0x334)",
                .internal_name = "v3_20_p334",
                .bios_type     = BIOS_NORMAL,
                .files_no      = 1,
                .local         = 0,
                .size          = 16384,
                .files         = { "roms/scsi/adaptec/aha1540b320_334.bin", "" }
            },
            { .files_no = 0 }
        },
    },
    {
        .name           = "bios_addr",
        .description    = "BIOS address",
        .type           = CONFIG_HEX20,
        .default_string = NULL,
        .default_int    = 0,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "Disabled", .value =       0 },
            { .description = "C800H",    .value = 0xc8000 },
            { .description = "D000H",    .value = 0xd0000 },
            { .description = "D800H",    .value = 0xd8000 },
            { .description = "DC00H",    .value = 0xdc000 },
            { .description = ""                           }
        },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
};

static const device_config_t aha_154xc_config[] = {
    {
        .name           = "base",
        .description    = "Address",
        .type           = CONFIG_HEX16,
        .default_string = NULL,
        .default_int    = 0x334,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "None",  .value =     0 },
            { .description = "0x330", .value = 0x330 },
            { .description = "0x334", .value = 0x334 },
            { .description = "0x230", .value = 0x230 },
            { .description = "0x234", .value = 0x234 },
            { .description = "0x130", .value = 0x130 },
            { .description = "0x134", .value = 0x134 },
            { .description = ""                      }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "irq",
        .description    = "IRQ",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 11,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "IRQ 9",  .value =  9 },
            { .description = "IRQ 10", .value = 10 },
            { .description = "IRQ 11", .value = 11 },
            { .description = "IRQ 12", .value = 12 },
            { .description = "IRQ 14", .value = 14 },
            { .description = "IRQ 15", .value = 15 },
            { .description = ""                    }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "dma",
        .description    = "DMA",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 6,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "DMA 5", .value = 5 },
            { .description = "DMA 6", .value = 6 },
            { .description = "DMA 7", .value = 7 },
            { .description = ""                  }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "bios_addr",
        .description    = "BIOS address",
        .type           = CONFIG_HEX20,
        .default_string = NULL,
        .default_int    = 0,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "Disabled", .value =       0 },
            { .description = "C800H",    .value = 0xc8000 },
            { .description = "D000H",    .value = 0xd0000 },
            { .description = "D800H",    .value = 0xd8000 },
            { .description = "DC00H",    .value = 0xdc000 },
            { .description = ""                           }
        },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
};

static const device_config_t aha_154xcf_config[] = {
    {
        .name           = "base",
        .description    = "Address",
        .type           = CONFIG_HEX16,
        .default_string = NULL,
        .default_int    = 0x334,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "None",  .value =     0 },
            { .description = "0x330", .value = 0x330 },
            { .description = "0x334", .value = 0x334 },
            { .description = "0x230", .value = 0x230 },
            { .description = "0x234", .value = 0x234 },
            { .description = "0x130", .value = 0x130 },
            { .description = "0x134", .value = 0x134 },
            { .description = ""                      }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "irq",
        .description    = "IRQ",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 11,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "IRQ 9",  .value =  9 },
            { .description = "IRQ 10", .value = 10 },
            { .description = "IRQ 11", .value = 11 },
            { .description = "IRQ 12", .value = 12 },
            { .description = "IRQ 14", .value = 14 },
            { .description = "IRQ 15", .value = 15 },
            { .description = ""                    }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "dma",
        .description    = "DMA",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = 6,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "DMA 5", .value = 5 },
            { .description = "DMA 6", .value = 6 },
            { .description = "DMA 7", .value = 7 },
            { .description = ""                  }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "bios_addr",
        .description    = "BIOS address",
        .type           = CONFIG_HEX20,
        .default_string = NULL,
        .default_int    = 0,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "Disabled", .value =       0 },
            { .description = "C800H",    .value = 0xc8000 },
            { .description = "CC00H",    .value = 0xcc000 },
            { .description = "D000H",    .value = 0xd0000 },
            { .description = "D400H",    .value = 0xd4000 },
            { .description = "D800H",    .value = 0xd8000 },
            { .description = "DC00H",    .value = 0xdc000 },
            { .description = ""                           }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "fdc_addr",
        .description    = "FDC Address",
        .type           = CONFIG_HEX16,
        .default_string = NULL,
        .default_int    = 0,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "None",  .value = 0                  },
            { .description = "0x3f0", .value = FDC_PRIMARY_ADDR   },
            { .description = "0x370", .value = FDC_SECONDARY_ADDR },
            { .description = ""                                   }
        },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
};

static const device_config_t aha_154xcp_config[] = {
    {
        .name           = "bios_rev",
        .description    = "BIOS Revision",
        .type           = CONFIG_BIOS,
        .default_string = "v1_02_en",
        .default_int    = 0,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .bios           = {
            {
                .name          = "Version 1.02 (English)",
                .internal_name = "v1_02_en",
                .bios_type     = BIOS_NORMAL,
                .files_no      = 2,
                .local         = 0,
                .size          = 32768,
                .files         = { "roms/scsi/adaptec/aha1542cp102.bin", "roms/scsi/adaptec/908301-00_f_mcode_17c9.u12", "" }
            },
            {
                .name          = "Version 1.02 (German)",
                .internal_name = "v1_02_de",
                .bios_type     = BIOS_NORMAL,
                .files_no      = 2,
                .local         = 0,
                .size          = 32768,
                .files         = { "roms/scsi/adaptec/buff_1-0_bios.bin", "roms/scsi/adaptec/buff_1-0_mcode.bin", "" }
            },
            {
                .name          = "Version 1.03 (English)",
                .internal_name = "v1_03_en",
                .bios_type     = BIOS_NORMAL,
                .files_no      = 2,
                .local         = 0,
                .size          = 32768,
                .files         = { "roms/scsi/adaptec/aha1542cp103.bin", "roms/scsi/adaptec/908301-00_g_mcode_144c.u12.bin", "" }
            },
            { .files_no = 0 }
        },
    },
    { .name = "", .description = "", .type = CONFIG_END }
};
// clang-format on

const device_t aha154xa_device = {
    .name          = "Adaptec AHA-154xA",
    .internal_name = "aha154xa",
    .flags         = DEVICE_ISA16,
    .local         = AHA_154xA,
    .init          = aha_init,
    .close         = x54x_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = aha_154xa_config
};

const device_t aha154xb_device = {
    .name          = "Adaptec AHA-154xB",
    .internal_name = "aha154xb",
    .flags         = DEVICE_ISA16,
    .local         = AHA_154xB,
    .init          = aha_init,
    .close         = x54x_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = aha_154xb_config
};

const device_t aha154xc_device = {
    .name          = "Adaptec AHA-154xC",
    .internal_name = "aha154xc",
    .flags         = DEVICE_ISA16,
    .local         = AHA_154xC,
    .init          = aha_init,
    .close         = x54x_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = aha_154xc_config
};

const device_t aha154xcf_device = {
    .name          = "Adaptec AHA-154xCF",
    .internal_name = "aha154xcf",
    .flags         = DEVICE_ISA16,
    .local         = AHA_154xCF,
    .init          = aha_init,
    .close         = x54x_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = aha_154xcf_config
};

const device_t aha154xcp_device = {
    .name          = "Adaptec AHA-154xCP",
    .internal_name = "aha154xcp",
    .flags         = DEVICE_ISA16,
    .local         = AHA_154xCP,
    .init          = aha_init,
    .close         = aha1542cp_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = aha_154xcp_config
};

const device_t aha1640_device = {
    .name          = "Adaptec AHA-1640",
    .internal_name = "aha1640",
    .flags         = DEVICE_MCA,
    .local         = AHA_1640,
    .init          = aha_init,
    .close         = x54x_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};
