/*
 * VARCem   Virtual ARchaeological Computer EMulator.
 *          An emulator of (mostly) x86-based PC systems and devices,
 *          using the ISA,EISA,VLB,MCA  and PCI system buses, roughly
 *          spanning the era between 1981 and 1995.
 *
 *          Implementation of a Clock/RTC Card for the ISA PC/XT.
 *
 *          Systems starting with the PC/XT had, by default, a realtime
 *          clock and NVR chip on the mainboard. The BIOS stored config
 *          data in the NVR, and the system could maintain time and date
 *          using the RTC.
 *
 *          Originally, PC systems did not have this, and they first did
 *          show up in non-IBM clone systems. Shortly after, expansion
 *          cards with this function became available for the PC's (ISA)
 *          bus, and they came in many forms and designs.
 *
 *          This implementation offers some of those boards:
 *
 *            Everex EV-170 (using NatSemi MM58167 chip)
 *            DTK PII-147 Hexa I/O Plus (using UMC 82C8167 chip)
 *
 *          and more will follow as time permits.
 *
 * NOTE:    The IRQ functionalities have been implemented, but not yet
 *          tested, as I need to write test software for them first :)
 *
 * Authors: Fred N. van Kempen, <decwiz@yahoo.com>
 *
 *          Copyright 2018 Fred N. van Kempen.
 *
 *          Redistribution and  use  in source  and binary forms, with
 *          or  without modification, are permitted  provided that the
 *          following conditions are met:
 *
 *          1. Redistributions of  source  code must retain the entire
 *             above notice, this list of conditions and the following
 *             disclaimer.
 *
 *          2. Redistributions in binary form must reproduce the above
 *             copyright  notice,  this list  of  conditions  and  the
 *             following disclaimer in  the documentation and/or other
 *             materials provided with the distribution.
 *
 *          3. Neither the  name of the copyright holder nor the names
 *             of  its  contributors may be used to endorse or promote
 *             products  derived from  this  software without specific
 *             prior written permission.
 *
 * THIS SOFTWARE  IS  PROVIDED BY THE  COPYRIGHT  HOLDERS AND CONTRIBUTORS
 * "AS IS" AND  ANY EXPRESS  OR  IMPLIED  WARRANTIES,  INCLUDING, BUT  NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE  ARE  DISCLAIMED. IN  NO  EVENT  SHALL THE COPYRIGHT
 * HOLDER OR  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL,  EXEMPLARY,  OR  CONSEQUENTIAL  DAMAGES  (INCLUDING,  BUT  NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE  GOODS OR SERVICES;  LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  AND ON  ANY
 * THEORY OF  LIABILITY, WHETHER IN  CONTRACT, STRICT  LIABILITY, OR  TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING  IN ANY  WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include "cpu.h"
#include <86box/timer.h>
#include <86box/machine.h>
#include <86box/io.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/nvr.h>
#include <86box/rom.h>
#include <86box/ui.h>
#include <86box/plat.h>
#include <86box/pic.h>
#include <86box/isartc.h>

#define ISARTC_EV170    0
#define ISARTC_DTK      1
#define ISARTC_P5PAK    2
#define ISARTC_A6PAK    3
#define ISARTC_VENDEX   4
#define ISARTC_MPLUS2   5
#define ISARTC_RTC58167 6
#define ISARTC_MM58167  10

#define ISARTC_ROM_MM58167_1 "roms/rtc/glatick/GLaTICK_0.8.8_NS_86B.ROM"  /* Generic 58167, AST or EV-170 */
#define ISARTC_ROM_MM58167_2 "roms/rtc/glatick/GLaTICK_0.8.8_NS_86B2.ROM" /* PII-147 */

#define ISARTC_DEBUG  0

typedef struct rtcdev_t {
    const char *name;  /* board name */
    uint8_t     board; /* board type */

    uint8_t flags;        /* various flags */
#define FLAG_YEAR80  0x01 /* YEAR byte is base-80 */
#define FLAG_YEARBCD 0x02 /* YEAR byte is in BCD */

    int8_t   irq; /* configured IRQ channel */
    int8_t   base_addrsz;
    uint32_t base_addr; /* configured I/O address */
    rom_t    rom; /* BIOS ROM, If configured */

    /* Fields for the specific driver. */
    void    (*f_wr)(uint16_t, uint8_t, void *);
    uint8_t (*f_rd)(uint16_t, void *);
    int8_t    year; /* register for YEAR value */
    int8_t    century; /* register for CENTURY value */
    char      pad[2];

    nvr_t nvr; /* RTC/NVR */
} rtcdev_t;

/************************************************************************
 *                                                                      *
 *            Driver for the NatSemi MM58167 chip.                      *
 *                                                                      *
 ************************************************************************/
#define MM67_REGS 32

/* Define the RTC chip registers - see datasheet, pg4. */
#define MM67_MSEC        0    /* milliseconds */
#define MM67_HUNTEN      1    /* hundredths/tenths of seconds */
#define MM67_SEC         2    /* seconds */
#define MM67_MIN         3    /* minutes */
#define MM67_HOUR        4    /* hours */
#define MM67_DOW         5    /* day of the week */
#define MM67_DOM         6    /* day of the month */
#define MM67_MON         7    /* month */
#define MM67_AL_MSEC     8    /* milliseconds */
#define MM67_AL_HUNTEN   9    /* hundredths/tenths of seconds */
#define MM67_AL_SEC      10   /* seconds */
#define MM67_AL_MIN      11   /* minutes */
#define MM67_AL_HOUR     12   /* hours */
#define MM67_AL_DOW      13   /* day of the week */
#define MM67_AL_DOM      14   /* day of the month */
#define MM67_AL_MON      15   /* month */
#define MM67_AL_DONTCARE 0xc0 /* always match in compare */
#define MM67_ISTAT       16   /* IRQ status */
#define MM67_ICTRL       17   /* IRQ control */
#define MM67INT_COMPARE  0x01 /*  Compare */
#define MM67INT_TENTH    0x02 /*  Tenth */
#define MM67INT_SEC      0x04 /*  Second */
#define MM67INT_MIN      0x08 /*  Minute */
#define MM67INT_HOUR     0x10 /*  Hour */
#define MM67INT_DAY      0x20 /*  Day */
#define MM67INT_WEEK     0x40 /*  Week */
#define MM67INT_MON      0x80 /*  Month */
#define MM67_RSTCTR      18   /* reset counters */
#define MM67_RSTRAM      19   /* reset RAM */
#define MM67_STATUS      20   /* status bit */
#define MM67_GOCMD       21   /* GO Command */
#define MM67_STBYIRQ     22   /* standby IRQ */
#define MM67_TEST        31   /* test mode */

#ifdef ENABLE_ISARTC_LOG
int isartc_do_log = ENABLE_ISARTC_LOG;

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

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

/* Check if the current time matches a set alarm time. */
static int8_t
mm67_chkalrm(nvr_t *nvr, int8_t addr)
{
    return ((nvr->regs[addr - MM67_AL_SEC + MM67_SEC] == nvr->regs[addr]) || ((nvr->regs[addr] & MM67_AL_DONTCARE) == MM67_AL_DONTCARE));
}

/*
 * This is called every second through the NVR/RTC hook.
 *
 * We fake a 'running' RTC by updating its registers on
 * each passing second. Not exactly accurate, but good
 * enough.
 *
 * Note that this code looks nasty because of all the
 * BCD to decimal vv going on.
 */
static void
mm67_tick(nvr_t *nvr)
{
    const rtcdev_t *dev  = (rtcdev_t *) nvr->data;
    uint8_t        *regs = nvr->regs;
    int             mon;
    int             year;
    int             f = 0;

    /* Update and set interrupt if needed. */
    regs[MM67_SEC] = RTC_BCDINC(nvr->regs[MM67_SEC], 1);
    if (regs[MM67_ICTRL] & MM67INT_SEC)
        f = MM67INT_SEC;

    /* Roll over? */
    if (regs[MM67_SEC] >= RTC_BCD(60)) {
        /* Update and set interrupt if needed. */
        regs[MM67_SEC] = RTC_BCD(0);
        regs[MM67_MIN] = RTC_BCDINC(regs[MM67_MIN], 1);
        if (regs[MM67_ICTRL] & MM67INT_MIN)
            f = MM67INT_MIN;

        /* Roll over? */
        if (regs[MM67_MIN] >= RTC_BCD(60)) {
            /* Update and set interrupt if needed. */
            regs[MM67_MIN]  = RTC_BCD(0);
            regs[MM67_HOUR] = RTC_BCDINC(regs[MM67_HOUR], 1);
            if (regs[MM67_ICTRL] & MM67INT_HOUR)
                f = MM67INT_HOUR;

            /* Roll over? */
            if (regs[MM67_HOUR] >= RTC_BCD(24)) {
                /* Update and set interrupt if needed. */
                regs[MM67_HOUR] = RTC_BCD(0);
                regs[MM67_DOW]  = RTC_BCDINC(regs[MM67_DOW], 1);
                if (regs[MM67_ICTRL] & MM67INT_DAY)
                    f = MM67INT_DAY;

                /* Roll over? */
                if (regs[MM67_DOW] > RTC_BCD(7)) {
                    /* Update and set interrupt if needed. */
                    regs[MM67_DOW] = RTC_BCD(1);
                    if (regs[MM67_ICTRL] & MM67INT_WEEK)
                        f = MM67INT_WEEK;
                }

                /* Roll over? */
                regs[MM67_DOM] = RTC_BCDINC(regs[MM67_DOM], 1);
                mon            = RTC_DCB(regs[MM67_MON]);
                if (dev->year != -1) {
                    if (dev->flags & FLAG_YEARBCD)
                        year = RTC_DCB(regs[dev->year]);
                    else
                        year = regs[dev->year];
                    if (dev->flags & FLAG_YEAR80)
                        year += 80;
                } else
                    year = 80;
                year += 1900;
                if (RTC_DCB(regs[MM67_DOM]) > nvr_get_days(mon, year)) {
                    /* Update and set interrupt if needed. */
                    regs[MM67_DOM] = RTC_BCD(1);
                    regs[MM67_MON] = RTC_BCDINC(regs[MM67_MON], 1);
                    if (regs[MM67_ICTRL] & MM67INT_MON)
                        f = MM67INT_MON;

                    /* Roll over? */
                    if (regs[MM67_MON] > RTC_BCD(12)) {
                        /* Update. */
                        regs[MM67_MON] = RTC_BCD(1);
                        if (dev->year != -1) {
                            year++;
                            if (dev->flags & FLAG_YEAR80)
                                year -= 80;

                            if (dev->flags & FLAG_YEARBCD)
                                regs[dev->year] = RTC_BCD(year % 100);
                            else
                                regs[dev->year] = year % 100;
                        }
                    }
                }
            }
        }
    }

    /* Check for programmed alarm interrupt. */
    if (regs[MM67_ICTRL] & MM67INT_COMPARE) {
        year = 1;
        for (mon = MM67_AL_SEC; mon <= MM67_AL_MON; mon++)
            if (mon != dev->year)
                year &= mm67_chkalrm(nvr, mon);
        f = year ? MM67INT_COMPARE : 0x00;
    }

    /* Raise the IRQ if needed (and if we have one..) */
    if (f != 0) {
        regs[MM67_ISTAT] = f;
        if (nvr->irq != -1)
            picint(1 << nvr->irq);
    }
}

/* Get the current NVR time. */
static void
mm67_time_get(nvr_t *nvr, struct tm *tm)
{
    const rtcdev_t *dev  = (rtcdev_t *) nvr->data;
    const uint8_t  *regs = nvr->regs;

    /* NVR is in BCD data mode. */
    tm->tm_sec  = RTC_DCB(regs[MM67_SEC]);
    tm->tm_min  = RTC_DCB(regs[MM67_MIN]);
    tm->tm_hour = RTC_DCB(regs[MM67_HOUR]);
    tm->tm_wday = (RTC_DCB(regs[MM67_DOW]) - 1);
    tm->tm_mday = RTC_DCB(regs[MM67_DOM]);
    tm->tm_mon  = (RTC_DCB(regs[MM67_MON]) - 1);
    if (dev->year != -1) {
        if (dev->flags & FLAG_YEARBCD)
            tm->tm_year = RTC_DCB(regs[dev->year]);
        else
            tm->tm_year = regs[dev->year];
        if (dev->flags & FLAG_YEAR80)
            tm->tm_year += 80;

        if ((dev->century != -1) && !(dev->flags & FLAG_YEAR80)) {
            if (dev->flags & FLAG_YEARBCD)
                tm->tm_year += (RTC_DCB(regs[dev->century]) * 100) - 1900;
            else
                tm->tm_year += (regs[dev->century] * 100) - 1900;
        }

#if ISARTC_DEBUG > 1
        isartc_log("ISARTC: get_time: year=%i [%02x]\n", tm->tm_year, regs[dev->year]);
#endif
    }
}

/* Set the current NVR time. */
static void
mm67_time_set(nvr_t *nvr, struct tm *tm)
{
    const rtcdev_t *dev  = (rtcdev_t *) nvr->data;
    uint8_t        *regs = nvr->regs;
    int             year;

    /* NVR is in BCD data mode. */
    regs[MM67_SEC]  = RTC_BCD(tm->tm_sec);
    regs[MM67_MIN]  = RTC_BCD(tm->tm_min);
    regs[MM67_HOUR] = RTC_BCD(tm->tm_hour);
    regs[MM67_DOW]  = RTC_BCD(tm->tm_wday + 1);
    regs[MM67_DOM]  = RTC_BCD(tm->tm_mday);
    regs[MM67_MON]  = RTC_BCD(tm->tm_mon + 1);
    if (dev->year != -1) {
        year = tm->tm_year;
        if (dev->flags & FLAG_YEAR80)
            year -= 80;
        if (dev->flags & FLAG_YEARBCD)
            regs[dev->year] = RTC_BCD(year % 100);
        else
            regs[dev->year] = year % 100;

        if ((dev->year != -1) && !(dev->flags & FLAG_YEAR80)) {
            if (dev->flags & FLAG_YEARBCD)
                regs[dev->century] = RTC_BCD((year + 1900) / 100);
            else
                regs[dev->century] = (year + 1900) / 100;
        }

#if ISARTC_DEBUG > 1
        isartc_log("ISARTC: set_time: [%02x] year=%i (%i)\n", regs[dev->year], year, tm->tm_year);
#endif
    }
}

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

    /* Initialize the internal and chip times. */
    if (time_sync) {
        /* Use the internal clock's time. */
        nvr_time_get(&tm);
        mm67_time_set(nvr, &tm);
    } else {
        /* Set the internal clock from the chip time. */
        mm67_time_get(nvr, &tm);
        nvr_time_set(&tm);
    }
}

/* Reset the RTC counters to a sane state. */
static void
mm67_reset(nvr_t *nvr)
{
    /* Initialize the RTC to a known state. */
    for (uint8_t i = MM67_MSEC; i <= MM67_MON; i++)
        nvr->regs[i] = RTC_BCD(0);
    nvr->regs[MM67_DOW] = RTC_BCD(1);
    nvr->regs[MM67_DOM] = RTC_BCD(1);
    nvr->regs[MM67_MON] = RTC_BCD(1);
}

/* Handle a READ operation from one of our registers. */
static uint8_t
mm67_read(uint16_t port, void *priv)
{
    rtcdev_t *dev = (rtcdev_t *) priv;
    int       reg = port - dev->base_addr;
    uint8_t   ret = 0xff;

    /* This chip is directly mapped on I/O. */
    cycles -= ISA_CYCLES(4);

    switch (reg) {
        case MM67_ISTAT: /* IRQ status (RO) */
            ret                = dev->nvr.regs[reg];
            dev->nvr.regs[reg] = 0x00;
            if (dev->irq != -1)
                picintc(1 << dev->irq);
            break;

        case MM67_AL_MSEC:
        case MM67_MSEC:
            ret                = dev->nvr.regs[reg] & 0xf0;
            break;

        case MM67_AL_DOW:
            ret                = dev->nvr.regs[reg] & 0x0f;
            break;

        case MM67_DOW:
            ret                = dev->nvr.regs[reg] & 0x07;
            break;

        default:
            ret = dev->nvr.regs[reg];
            break;
    }

#if ISARTC_DEBUG
    isartc_log("ISARTC: read(%04x) = %02x\n", port - dev->base_addr, ret);
#endif

    return ret;
}

/* Handle a WRITE operation to one of our registers. */
static void
mm67_write(uint16_t port, uint8_t val, void *priv)
{
    rtcdev_t *dev = (rtcdev_t *) priv;
    int       reg = port - dev->base_addr;

#if ISARTC_DEBUG
    isartc_log("ISARTC: write(%04x, %02x)\n", port - dev->base_addr, val);
#endif

    /* This chip is directly mapped on I/O. */
    cycles -= ISA_CYCLES(4);

    switch (reg) {
        case MM67_ISTAT: /* intr status (RO) */
            break;

        case MM67_ICTRL: /* intr control */
            dev->nvr.regs[MM67_ISTAT] = 0x00;
            dev->nvr.regs[reg]        = val;
            break;

        case MM67_RSTCTR:
            if (val == 0xff)
                mm67_reset(&dev->nvr);
            break;

        case MM67_RSTRAM:
            if (val == 0xff) {
                for (uint8_t i = MM67_AL_MSEC; i <= MM67_AL_MON; i++)
                    dev->nvr.regs[i] = RTC_BCD(0);
                dev->nvr.regs[MM67_DOW] = RTC_BCD(1);
                dev->nvr.regs[MM67_DOM] = RTC_BCD(1);
                dev->nvr.regs[MM67_MON] = RTC_BCD(1);
                if (dev->year != -1) {
                    val = (dev->flags & FLAG_YEAR80) ? 0 : 80;
                    if (dev->flags & FLAG_YEARBCD)
                        dev->nvr.regs[dev->year] = RTC_BCD(val);
                    else
                        dev->nvr.regs[dev->year] = val;

                    if ((dev->century != -1) && !(dev->flags & FLAG_YEAR80)) {
                        if (dev->flags & FLAG_YEARBCD)
                            dev->nvr.regs[dev->century] = RTC_BCD(19);
                        else
                            dev->nvr.regs[dev->century] = (1900 + val) / 100;
                    }
                }
            }
            break;

        case MM67_STATUS: /* STATUS (RO) */
            break;

        case MM67_GOCMD:
            isartc_log("RTC: write gocmd=%02x\n", val);
            break;

        case MM67_STBYIRQ:
            isartc_log("RTC: write stby=%02x\n", val);
            break;

        case MM67_TEST:
            isartc_log("RTC: write test=%02x\n", val);
            break;

        case MM67_AL_MSEC:
            dev->nvr.regs[reg] = val & 0xf0;
            break;

        case MM67_AL_DOW:
            dev->nvr.regs[reg] = val & 0x0f;
            break;

        default:
            dev->nvr.regs[reg] = val;
            break;
    }
}

/* Multitech PC-500/PC-500+ onboard RTC 58167 device disigned to use I/O port
 * base+0 as register index and base+1 as register data read/write window,
 * according to the official RTC utilities SDATE.EXE, STIME.EXE, and TODAY.EXE
 *
 * the RTC utilities check the RTC millisecond counter first to deteminate the
 * presence of the RTC 58167 IC, so here implement the bogus_msec to fool them 
 */
static uint8_t rtc58167_index = 0x00;

static uint8_t
rtc58167_read(uint16_t port, void *priv)
{
    uint8_t ret = 0xff;
    uint16_t bogus_msec = (uint16_t)((tsc * 1000) / cpu_s->rspeed);

    switch (port)
    {
        case 0x2c0:
        case 0x300:
            ret = rtc58167_index;
            break;

        case 0x2c1:
        case 0x301:
            switch (rtc58167_index)
            {
                case MM67_MSEC:
                    ret = (uint8_t)(bogus_msec % 10) << 4;
                    break;

                case MM67_HUNTEN:
                    ret = RTC_BCD((uint8_t)((bogus_msec / 10) % 100));
                    break;

                default:
                    ret = mm67_read(((port - 1) + rtc58167_index), priv);
                    break;
            }
            break;

        default:
            break;
    }

    return ret;
}

static void
rtc58167_write(uint16_t port, uint8_t val, void *priv)
{
    switch (port)
    {
        case 0x2c0:
        case 0x300:
            rtc58167_index = val;
            break;

        case 0x2c1:
        case 0x301:
            mm67_write(((port - 1) + rtc58167_index), val, priv);
            break;

        default:
            break;
    }
}

/************************************************************************
 *                                                                      *
 *            Generic code for all supported chips.                     *
 *                                                                      *
 ************************************************************************/

/* Initialize the device for use. */
static void *
isartc_init(const device_t *info)
{
    rtcdev_t *dev;
    int       is_at = IS_AT(machine);
    is_at           = is_at || (machines[machine].init == machine_xt_xi8088_init);

    /* Create a device instance. */
    dev = (rtcdev_t *) calloc(1, sizeof(rtcdev_t));
    dev->name     = info->name;
    dev->board    = info->local;
    dev->irq      = -1;
    dev->year     = -1;
    dev->century  = -1;
    dev->nvr.data = dev;
    dev->nvr.size = 16;

    /* Do per-board initialization. */
    switch (dev->board) {
        case ISARTC_MM58167: /* Generic MM58167 RTC */
            {
                uint32_t rom_addr = device_get_config_hex20("bios_addr");
                if (rom_addr != 0)
                    rom_init(&dev->rom, ISARTC_ROM_MM58167_1,
                             rom_addr, 0x0800, 0x7ff, 0, MEM_MAPPING_EXTERNAL);

            }
        case ISARTC_EV170: /* Everex EV-170 Magic I/O */
            dev->flags |= FLAG_YEAR80;
            dev->base_addr   = device_get_config_hex16("base");
            dev->base_addrsz = 32;
            dev->irq         = device_get_config_int("irq");
            dev->f_rd        = mm67_read;
            dev->f_wr        = mm67_write;
            dev->nvr.reset   = mm67_reset;
            dev->nvr.start   = mm67_start;
            dev->nvr.tick    = mm67_tick;
            dev->year        = MM67_AL_DOM; /* year, NON STANDARD */
            break;

        case ISARTC_DTK: /* DTK PII-147 Hexa I/O Plus */
            dev->flags |= FLAG_YEARBCD;
            dev->base_addr   = device_get_config_hex16("base");
            dev->base_addrsz = 32;
            dev->f_rd        = mm67_read;
            dev->f_wr        = mm67_write;
            dev->nvr.reset   = mm67_reset;
            dev->nvr.start   = mm67_start;
            dev->nvr.tick    = mm67_tick;
            dev->year        = MM67_AL_HUNTEN; /* year, NON STANDARD */
            break;

        case ISARTC_P5PAK:  /* Paradise Systems 5PAK */
        case ISARTC_A6PAK:  /* AST SixPakPlus */
        case ISARTC_MPLUS2: /* AST MegaPlus II */
            dev->flags |= FLAG_YEAR80;
            dev->base_addr   = 0x02c0;
            dev->base_addrsz = 32;
            dev->irq         = device_get_config_int("irq");
            dev->f_rd        = mm67_read;
            dev->f_wr        = mm67_write;
            dev->nvr.reset   = mm67_reset;
            dev->nvr.start   = mm67_start;
            dev->nvr.tick    = mm67_tick;
            dev->year        = MM67_AL_DOM; /* year, NON STANDARD */
            break;

        case ISARTC_VENDEX: /* Vendex HeadStart Turbo 888-XT RTC */
            dev->flags |= FLAG_YEAR80 | FLAG_YEARBCD;
            dev->base_addr   = 0x0300;
            dev->base_addrsz = 32;
            dev->f_rd        = mm67_read;
            dev->f_wr        = mm67_write;
            dev->nvr.reset   = mm67_reset;
            dev->nvr.start   = mm67_start;
            dev->nvr.tick    = mm67_tick;
            dev->year        = MM67_AL_DOM; /* year, NON STANDARD */
            break;

        case ISARTC_RTC58167: /* Multitech PC-500/PC-500+ onboard RTC */
            dev->flags |= FLAG_YEARBCD;
            dev->base_addr   = machine_get_config_int("rtc_port");
            dev->base_addrsz = 8;
            dev->irq         = machine_get_config_int("rtc_irq");
            dev->f_rd        = rtc58167_read;
            dev->f_wr        = rtc58167_write;
            dev->nvr.reset   = mm67_reset;
            dev->nvr.start   = mm67_start;
            dev->nvr.tick    = mm67_tick;
            dev->year        = MM67_AL_HUNTEN;  /* year,    NON STANDARD */
            dev->century     = MM67_AL_SEC;     /* century, NON STANDARD */
            break;

        default:
            break;
    }

    /* Say hello! */
    isartc_log("ISARTC: %s (I/O=%04XH", info->name, dev->base_addr);
    if (dev->irq != -1)
        isartc_log(", IRQ%i", dev->irq);
    isartc_log(")\n");

    /* Set up an I/O port handler. */
    io_sethandler(dev->base_addr, dev->base_addrsz,
                  dev->f_rd, NULL, NULL, dev->f_wr, NULL, NULL, dev);

    /* Hook into the NVR backend. */
    dev->nvr.fn  = (char *) info->internal_name;
    dev->nvr.irq = dev->irq;
    if (!is_at)
        nvr_init(&dev->nvr);

    /* Let them know our device instance. */
    return ((void *) dev);
}

/* Remove the device from the system. */
static void
isartc_close(void *priv)
{
    rtcdev_t *dev = (rtcdev_t *) priv;

    io_removehandler(dev->base_addr, dev->base_addrsz,
                     dev->f_rd, NULL, NULL, dev->f_wr, NULL, NULL, dev);

    free(dev);
}

static const device_config_t ev170_config[] = {
  // clang-format off
    {
        .name           = "base",
		.description    = "Address",
		.type           = CONFIG_HEX16,
		.default_string = NULL,
		.default_int    = 0x02C0,
		.file_filter    = NULL,
		.spinner        = { 0 },
        .selection      = {
            { .description = "240H", .value = 0x0240 },
            { .description = "2C0H", .value = 0x02c0 },
            { .description = ""                      }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "irq",
		.description    = "IRQ",
		.type           = CONFIG_SELECTION,
		.default_string = NULL,
		.default_int    = -1,
		.file_filter    = NULL,
		.spinner        = { 0 },
        .selection      = {
            { .description = "Disabled", .value = -1 },
            { .description = "IRQ2",     .value =  2 },
            { .description = "IRQ5",     .value =  5 },
            { .description = "IRQ7",     .value =  7 },
            { .description = ""                      }
        },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
  // clang-format on
};

static const device_t ev170_device = {
    .name          = "Everex EV-170 Magic I/O",
    .internal_name = "ev170",
    .flags         = DEVICE_ISA,
    .local         = ISARTC_EV170,
    .init          = isartc_init,
    .close         = isartc_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = ev170_config
};

static const device_config_t pii147_config[] = {
  // clang-format off
    {
        .name           = "base",
		.description    = "Address",
		.type           = CONFIG_HEX16,
		.default_string = NULL,
		.default_int    = 0x0240,
		.file_filter    = NULL,
		.spinner        = { 0 },
        .selection      = {
            { .description = "Clock 1", .value = 0x0240 },
            { .description = "Clock 2", .value = 0x0340 },
            { .description = ""                         }
        },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
  // clang-format on
};

static const device_t pii147_device = {
    .name          = "DTK PII-147 Hexa I/O Plus",
    .internal_name = "pii147",
    .flags         = DEVICE_ISA,
    .local         = ISARTC_DTK,
    .init          = isartc_init,
    .close         = isartc_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = pii147_config
};

static const device_config_t p5pak_config[] = {
  // clang-format off
    {
        .name           = "irq",
		.description    = "IRQ",
		.type           = CONFIG_SELECTION,
		.default_string = NULL,
		.default_int    = -1,
		.file_filter    = NULL,
		.spinner        = { 0 },
        .selection      = {
            { .description = "Disabled", -1 },
            { .description = "IRQ2",      2 },
            { .description = "IRQ3",      3 },
            { .description = "IRQ5",      5 },
            { .description = ""             }
        },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
  // clang-format on
};

static const device_t p5pak_device = {
    .name          = "Paradise Systems 5-PAK",
    .internal_name = "p5pak",
    .flags         = DEVICE_ISA,
    .local         = ISARTC_P5PAK,
    .init          = isartc_init,
    .close         = isartc_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = p5pak_config
};

static const device_config_t a6pak_config[] = {
  // clang-format off
    {
        .name           = "irq",
        .description    = "IRQ",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = -1,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "Disabled", .value = -1 },
            { .description = "IRQ2",     .value =  2 },
            { .description = "IRQ3",     .value =  3 },
            { .description = "IRQ5",     .value =  5 },
            { .description = ""                      }
        },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
  // clang-format on
};

static const device_t a6pak_device = {
    .name          = "AST SixPakPlus",
    .internal_name = "a6pak",
    .flags         = DEVICE_ISA,
    .local         = ISARTC_A6PAK,
    .init          = isartc_init,
    .close         = isartc_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = a6pak_config
};

static const device_config_t mplus2_config[] = {
  // clang-format off
    {
        .name           = "irq",
        .description    = "IRQ",
        .type           = CONFIG_SELECTION,
        .default_string = "",
        .default_int    = -1,
        .file_filter    = "",
        .spinner        = { 0 },
        .selection      = {
            { "Disabled", -1 },
            { "IRQ2",      2 },
            { "IRQ3",      3 },
            { "IRQ5",      5 },
            { ""             }
        },
    },
    { .name = "", .description = "", .type = CONFIG_END }
  // clang-format on
};

static const device_t mplus2_device = {
    .name          = "AST MegaPlus II",
    .internal_name = "mplus2",
    .flags         = DEVICE_ISA,
    .local         = ISARTC_MPLUS2,
    .init          = isartc_init,
    .close         = isartc_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = mplus2_config
};

static const device_config_t mm58167_config[] = {
  // clang-format off
    {
        .name           = "base",
        .description    = "Address",
        .type           = CONFIG_HEX16,
        .default_string = NULL,
        .default_int    = 0x02C0,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "240H", .value = 0x0240 },
            { .description = "2C0H", .value = 0x02c0 },
            { .description = "340H", .value = 0x0340 },
            { .description = ""                      }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "irq",
        .description    = "IRQ",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = -1,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "Disabled", .value = -1 },
            { .description = "IRQ2",     .value =  2 },
            { .description = "IRQ5",     .value =  5 },
            { .description = "IRQ7",     .value =  7 },
            { .description = ""                      }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "bios_addr",
        .description    = "BIOS address",
        .type           = CONFIG_HEX20,
        .default_string = NULL,
        .default_int    = 0xcc000,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "Disabled", .value = 0x00000 },
            { .description = "C800H",    .value = 0xc8000 },
            { .description = "CA00H",    .value = 0xca000 },
            { .description = "CC00H",    .value = 0xcc000 },
            { .description = "CE00H",    .value = 0xce000 },
            { .description = "D000H",    .value = 0xd0000 },
            { .description = "D200H",    .value = 0xd2000 },
            { .description = "D400H",    .value = 0xd4000 },
            { .description = "D600H",    .value = 0xd6000 },
            { .description = "D800H",    .value = 0xd8000 },
            { .description = "DA00H",    .value = 0xda000 },
            { .description = "DC00H",    .value = 0xdc000 },
            { .description = "DE00H",    .value = 0xde000 },
            { .description = "E000H",    .value = 0xe0000 },
            { .description = "E200H",    .value = 0xe2000 },
            { .description = "E400H",    .value = 0xe4000 },
            { .description = "E600H",    .value = 0xe6000 },
            { .description = "E800H",    .value = 0xe8000 },
            { .description = "EA00H",    .value = 0xea000 },
            { .description = "EC00H",    .value = 0xec000 },
            { .description = "EE00H",    .value = 0xee000 },
            { .description = ""                           }
        },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
  // clang-format on
};

static const device_t mm58167_device = {
    .name          = "Generic MM58167 RTC",
    .internal_name = "rtc_mm58167",
    .flags         = DEVICE_ISA | DEVICE_SIDECAR,
    .local         = ISARTC_MM58167,
    .init          = isartc_init,
    .close         = isartc_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = mm58167_config
};

/* Onboard RTC devices */
const device_t vendex_xt_rtc_onboard_device = {
    .name          = "National Semiconductor MM58167 (Vendex)",
    .internal_name = "vendex_xt_rtc",
    .flags         = DEVICE_ISA,
    .local         = ISARTC_VENDEX,
    .init          = isartc_init,
    .close         = isartc_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};

const device_t rtc58167_device = {
    .name          = "RTC 58167 IC (Multitech)",
    .internal_name = "rtc58167_xt_rtc",
    .flags         = DEVICE_ISA,
    .local         = ISARTC_RTC58167,
    .init          = isartc_init,
    .close         = isartc_close,
    .reset         = NULL,
    .available     = NULL,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};

static const struct {
    const device_t *dev;
} boards[] = {
    // clang-format off
    { &device_none     },
    { &ev170_device    },
    { &pii147_device   },
    { &p5pak_device    },
    { &a6pak_device    },
    { &mplus2_device   },
    { &mm58167_device  },
    { NULL             }
    // clang-format on
};

void
isartc_reset(void)
{
    if (isartc_type == 0)
        return;

    /* Add the device to the system. */
    device_add(boards[isartc_type].dev);
}

const char *
isartc_get_internal_name(int board)
{
    return device_get_internal_name(boards[board].dev);
}

int
isartc_get_from_internal_name(const char *str)
{
    int c = 0;

    while (boards[c].dev != NULL) {
        if (!strcmp(boards[c].dev->internal_name, str))
            return c;
        c++;
    }

    /* Not found. */
    return 0;
}

const device_t *
isartc_get_device(int board)
{
    return (boards[board].dev);
}

int
isartc_has_config(int board)
{
    if (boards[board].dev == NULL)
        return 0;

    return (boards[board].dev->config ? 1 : 0);
}
