/*************************************************************************
 *                                                                       *
 * Copyright (c) 2007-2022 Howard M. Harte.                              *
 * https://github.com/hharte                                             *
 *                                                                       *
 * Permission is hereby granted, free of charge, to any person obtaining *
 * a copy of this software and associated documentation files (the       *
 * "Software"), to deal in the Software without restriction, including   *
 * without limitation the rights to use, copy, modify, merge, publish,   *
 * distribute, sublicense, and/or sell copies of the Software, and to    *
 * permit persons to whom the Software is furnished to do so, subject to *
 * the following conditions:                                             *
 *                                                                       *
 * The above copyright notice and this permission notice shall be        *
 * included in all copies or substantial portions of the Software.       *
 *                                                                       *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-            *
 * INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE   *
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN       *
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN     *
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE      *
 * SOFTWARE.                                                             *
 *                                                                       *
 * Except as contained in this notice, the names of The Authors shall    *
 * not be used in advertising or otherwise to promote the sale, use or   *
 * other dealings in this Software without prior written authorization   *
 * from the Authors.                                                     *
 *                                                                       *
 * SIMH Interface based on altairz80_hdsk.c, by Peter Schorn.            *
 *                                                                       *
 * Module Description:                                                   *
 *     CompuPro System Support 1 module for SIMH.                        *
 * Note this does not include the Boot ROM on the System Support 1 Card  *
 *                                                                       *
 *************************************************************************/

/*#define DBG_MSG */

#include "altairz80_defs.h"

#ifdef DBG_MSG
#define DBG_PRINT(args) sim_printf args
#else
#define DBG_PRINT(args)
#endif

/* Debug flags */
#define ERROR_MSG   (1 << 0)
#define RXIRQ_MSG   (1 << 1)
#define TXIRQ_MSG   (1 << 2)
#define UART_MSG    (1 << 3)
#define USER_MSG    (1 << 4)

#define IF3_MAX_BOARDS    4

#define UNIT_V_IF3_CONNECT     (UNIT_V_UF + 1) /* Connect/Disconnect IF3 unit */
#define UNIT_IF3_CONNECT       (1 << UNIT_V_IF3_CONNECT)

#define IF3_PORT_BASE   0x300

typedef struct {
    PNP_INFO    pnp;    /* Plug and Play */
} IF3_INFO;

static IF3_INFO if3_info_data = { { 0x0, 0, 0x10, 8 } };

extern t_stat set_iobase(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
extern t_stat show_iobase(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
extern uint32 sim_map_resource(uint32 baseaddr, uint32 size, uint32 resource_type,
                               int32 (*routine)(const int32, const int32, const int32), const char* name, uint8 unmap);
extern uint32 PCX;

extern int32 sio0d(const int32 port, const int32 io, const int32 data);
extern int32 sio0s(const int32 port, const int32 io, const int32 data);

static t_stat set_if3_connect(UNIT *uptr, int32 val, CONST char *cptr, void *desc);

static t_stat if3_reset(DEVICE *if3_dev);
static t_stat if3_svc (UNIT *uptr);
static uint8 IF3_Read(const uint32 Addr);
static uint8 IF3_Write(const uint32 Addr, uint8 cData);
static int32 if3dev(const int32 port, const int32 io, const int32 data);
static t_stat update_rx_tx_isr (UNIT *uptr);
static const char* if3_description(DEVICE *dptr);

static UNIT if3_unit[] = {
    { UDATA (&if3_svc, UNIT_FIX | UNIT_DISABLE | UNIT_ROABLE | UNIT_IF3_CONNECT, 0) },
    { UDATA (&if3_svc, UNIT_FIX | UNIT_DISABLE | UNIT_ROABLE, 0) },
    { UDATA (&if3_svc, UNIT_FIX | UNIT_DISABLE | UNIT_ROABLE, 0) },
    { UDATA (&if3_svc, UNIT_FIX | UNIT_DISABLE | UNIT_ROABLE, 0) }
};

static uint8 if3_user = 0;
static uint8 if3_board = 0;
static uint8 if3_rimr[IF3_MAX_BOARDS] = { 0, 0, 0, 0 };
static uint8 if3_timr[IF3_MAX_BOARDS] = { 0, 0, 0, 0 };
static uint8 if3_risr[IF3_MAX_BOARDS] = { 0, 0, 0, 0 };
static uint8 if3_tisr[IF3_MAX_BOARDS] = { 0, 0, 0, 0 };

static REG if3_reg[] = {
    { HRDATAD (USER,    if3_user,   3,                      "IF3 user register"),       },
    { HRDATAD (BOARD,   if3_board,  2,                      "IF3 board register"),      },
    { BRDATAD (RIMR,    if3_rimr,  16, 8, IF3_MAX_BOARDS,  "IF3 RIMR register array"), },
    { BRDATAD (RISR,    if3_risr,  16, 8, IF3_MAX_BOARDS,  "IF3 RISR register array"), },
    { BRDATAD (TIMR,    if3_timr,  16, 8, IF3_MAX_BOARDS,  "IF3 TIMR register array"), },
    { BRDATAD (TISR,    if3_tisr,  16, 8, IF3_MAX_BOARDS,  "IF3 TISR register array"), },
    { NULL }
};

#define IF3_NAME    "Compupro Interfacer 3"

static const char* if3_description(DEVICE *dptr) {
    return IF3_NAME;
}

static MTAB if3_mod[] = {
    { MTAB_XTD|MTAB_VDV,    0,               "IOBASE",   "IOBASE",
        &set_iobase, &show_iobase, NULL, "Sets disk controller I/O base address"    },
    { UNIT_IF3_CONNECT,     UNIT_IF3_CONNECT,"INSTALLED",  "INSTALLED",
        &set_if3_connect, NULL, NULL, "Installs board for unit " IF3_NAME "n"       },
    { UNIT_IF3_CONNECT,     0,               "UNINSTALLED","UNINSTALLED",
        &set_if3_connect, NULL, NULL, "Uninstalls board for unit " IF3_NAME "n"     },
    { 0 }
};

/* Debug Flags */
static DEBTAB if3_dt[] = {
    { "ERROR",  ERROR_MSG,      "Error messages"    },
    { "RXIRQ",  RXIRQ_MSG,      "RX IRQ messages"   },
    { "TXIRQ",  TXIRQ_MSG,      "TX IRQ messages"   },
    { "UART",   UART_MSG,       "UART messages"     },
    { "USER",   USER_MSG,       "User messages"     },
    { NULL,     0                                   }
};

DEVICE if3_dev = {
    "IF3", if3_unit, if3_reg, if3_mod,
    IF3_MAX_BOARDS, 10, 31, 1, IF3_MAX_BOARDS, IF3_MAX_BOARDS,
    NULL, NULL, &if3_reset,
    NULL, NULL, NULL,
    &if3_info_data, (DEV_DISABLE | DEV_DIS | DEV_DEBUG), 0,
    if3_dt, NULL, NULL, NULL, NULL, NULL, &if3_description
};

static t_stat set_if3_connect(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
    if(uptr->flags & UNIT_DISABLE) {
        sim_debug(ERROR_MSG, &if3_dev, "IF3[%d]: not enabled.\n", uptr->u3);
        return SCPE_OK;
    }

    if(val & UNIT_IF3_CONNECT) {
        sim_debug((RXIRQ_MSG|TXIRQ_MSG), &if3_dev, "IF3[%d]: IRQ polling started...\n", uptr->u3);
        sim_activate(uptr, 100000);
    } else {
        sim_debug((RXIRQ_MSG|TXIRQ_MSG), &if3_dev, "IF3[%d]: IRQ polling stopped.\n", uptr->u3);
        sim_cancel(uptr);
    }
    return (SCPE_OK);
}

/* Reset routine */
static t_stat if3_reset(DEVICE *dptr)
{
    uint8 i;

    PNP_INFO *pnp = (PNP_INFO *)dptr->ctxt;

    if(dptr->flags & DEV_DIS) { /* Disconnect I/O Ports */
        for(i=0;i<IF3_MAX_BOARDS;i++)
            sim_cancel(&if3_unit[i]);

        sim_map_resource(pnp->io_base, pnp->io_size, RESOURCE_TYPE_IO, &if3dev, "if3dev", TRUE);
    } else {
        /* Connect IF3 at base address */
        if(sim_map_resource(pnp->io_base, pnp->io_size, RESOURCE_TYPE_IO, &if3dev, "if3dev", FALSE) != 0) {
            sim_printf("%s: error mapping I/O resource at 0x%04x\n", __FUNCTION__, pnp->io_base);
            return SCPE_ARG;
        }

        for(i=0;i<IF3_MAX_BOARDS;i++) {
            if3_unit[i].u3 = i; /* Store unit board ID in u3. Also guarantees that u3 < IF3_MAX_BOARDS */
            if(if3_unit[i].flags & UNIT_IF3_CONNECT) {
                sim_debug((RXIRQ_MSG|TXIRQ_MSG), &if3_dev, "IF3[%d]: IRQ polling started...\n", i);
                sim_activate(&if3_unit[i], 200000); /* start Rx/Tx interrupt polling routine */

            }
        }
    }
    return SCPE_OK;
}

static int32 if3dev(const int32 port, const int32 io, const int32 data)
{
    DBG_PRINT(("IF3: IO %s, Port %02x\n", io ? "WR" : "RD", port));
    if(io) {
        IF3_Write(port, data);
        return 0;
    } else {
        return(IF3_Read(port));
    }
}

#define IF3_UART_DATA   0x00
#define IF3_UART_STAT   0x01
#define IF3_UART_MODE   0x02
#define IF3_UART_CMD    0x03
#define IF3_TISR        0x04
#define IF3_RISR        0x05
#define IF3_RESERVED    0x06
#define IF3_USER_SEL    0x07

static uint8 IF3_Read(const uint32 Addr)
{
    uint8 cData = 0xFF;

    /* Check if board is connected */
    if(!(if3_unit[if3_board].flags & UNIT_IF3_CONNECT)) {
        sim_debug(ERROR_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " RD UART[%d] Board not connected DATA=0x%02x\n", if3_board, PCX, if3_user, cData);
        return cData;
    }

    switch(Addr & 0x07) {
        case IF3_UART_DATA:
            cData = sio0d(IF3_PORT_BASE+(if3_board*0x10)+(if3_user*2), 0, 0);
            sim_debug(UART_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " RD UART[%d] DATA=0x%02x Port=0x%03x\n", if3_board, PCX, if3_user, cData, IF3_PORT_BASE+(if3_board*0x10)+(if3_user*2));
            break;
        case IF3_UART_STAT:
            cData = sio0s(IF3_PORT_BASE+(if3_board*0x10)+1+(if3_user*2), 0, 0);
            sim_debug(UART_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " RD UART[%d] STAT=0x%02x Port=0x%03x\n", if3_board, PCX, if3_user, cData, IF3_PORT_BASE+(if3_board*0x10)+1+(if3_user*2));
            break;
        case IF3_UART_MODE:
            sim_debug(ERROR_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " RD UART MODE cannot read 0x%02x\n", if3_board, PCX, Addr);
            break;
        case IF3_UART_CMD:
            sim_debug(ERROR_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " RD UART CMD cannot read 0x%02x\n", if3_board, PCX, Addr);
            break;
        case IF3_TISR:
            update_rx_tx_isr (&if3_unit[if3_board]);
            cData = if3_tisr[if3_board];
            sim_debug(TXIRQ_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " RD UART TISR=0x%02x\n", if3_board, PCX, cData);
            break;
        case IF3_RISR:
            update_rx_tx_isr (&if3_unit[if3_board]);
            cData = if3_risr[if3_board];
            sim_debug(RXIRQ_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " RD UART RISR=0x%02x\n", if3_board,  PCX, cData);
            break;
        case IF3_RESERVED:
            sim_debug(ERROR_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " RD UART RESERVED cannot read 0x%02x\n", if3_board, PCX, Addr);
            break;
        case IF3_USER_SEL:
            sim_debug(USER_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " Cannot read UART_SEL\n", if3_board, PCX);
            break;
    }

    return (cData);

}

static uint8 IF3_Write(const uint32 Addr, uint8 cData)
{
    /* Check if board is connected for all ports except "user select" */
    if((Addr & 0x7) != IF3_USER_SEL) {
        if(!(if3_unit[if3_board].flags & UNIT_IF3_CONNECT)) {
            sim_debug(ERROR_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " WR UART[%d] Board not connected DATA=0x%02x\n", if3_board, PCX, if3_user, cData);
            return cData;
        }
    }

    switch(Addr & 0x07) {
        case IF3_UART_DATA:
            sio0d(IF3_PORT_BASE+(if3_board*0x10)+(if3_user*2), 1, cData);
            sim_debug(UART_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " WR UART[%d] DATA=0x%02x Port=0x%03x\n", if3_board, PCX, if3_user, cData, IF3_PORT_BASE+(if3_board*0x10)+(if3_user*2));
            break;
        case IF3_UART_STAT:
            sim_debug(UART_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " WR UART[%d] STAT=0x%02x\n", if3_board, PCX, if3_user, cData);
            break;
        case IF3_UART_MODE:
            sim_debug(UART_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " WR UART[%d] MODE=0x%02x\n", if3_board, PCX, if3_user, cData);
            break;
        case IF3_UART_CMD:
            sim_debug(UART_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " WR UART[%d] CMD=0x%02x\n", if3_board, PCX, if3_user, cData);
            break;
        case IF3_TISR:
            sim_debug(TXIRQ_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " WR UART TIMR=0x%02x\n", if3_board, PCX, cData);
            if3_timr[if3_board] = cData;
            break;
        case IF3_RISR:
            sim_debug(RXIRQ_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " WR UART RIMR=0x%02x\n", if3_board, PCX, cData);
            if3_rimr[if3_board] = cData;
            break;
        case IF3_RESERVED:
            sim_debug(UART_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " WR UART[%d] RESERVED=0x%02x\n", if3_board, PCX, if3_user, cData);
            break;
        case IF3_USER_SEL:
            if3_board = (cData & 0x18) >> 3; /* guarantees that if3_board < IF3_MAX_BOARDS */
            if3_user = cData & 0x7;
            sim_debug(USER_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " WR UART_SEL=0x%02x (Board=%d, Rel_User=%d, User=%d)\n", if3_board, PCX, cData, if3_board, if3_user, cData);
            break;
    }

    return(0);
}

#define SS1_VI2_INT         2   /* IF3 Rx interrupts tied to VI2 */
#define SS1_VI3_INT         3   /* IF3 Tx interrupts tied to VI3 */

#define IF3_NUM_PORTS       8   /* Number of ports per IF3 board */

extern void raise_ss1_interrupt(uint8 isr_index);

/* Unit service routine */
static t_stat if3_svc (UNIT *uptr)
{
    uint8 pending_rx_irqs;
    uint8 pending_tx_irqs;
    uint8 board = uptr->u3;

    update_rx_tx_isr(uptr);

    pending_rx_irqs = if3_risr[board] & if3_rimr[board];
    if(pending_rx_irqs) {
        sim_debug(RXIRQ_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " Rx IRQ Pending: 0x%02x\n", board, PCX, pending_rx_irqs);
        raise_ss1_interrupt(SS1_VI2_INT);
    }

    pending_tx_irqs = if3_tisr[board] & if3_timr[board];
    if(pending_tx_irqs) {
        sim_debug(TXIRQ_MSG, &if3_dev, "IF3[%d]: " ADDRESS_FORMAT " Tx IRQ Pending: 0x%02x\n", board, PCX, pending_tx_irqs);
        raise_ss1_interrupt(SS1_VI3_INT);
    }

    sim_activate(&if3_unit[board], 200000);
    return SCPE_OK;
}


static t_stat update_rx_tx_isr (UNIT *uptr)
{
    uint8 i;
    uint8 cData;
    uint8 board = uptr->u3;

    if3_risr[board] = 0;
    if3_tisr[board] = 0;

    for(i=0;i<IF3_NUM_PORTS;i++) {
        cData = sio0s(IF3_PORT_BASE+1+(board*0x10)+(i*2), 0, 0);
        if(cData & 2) { /* RX char available? */
            if3_risr[board] |= (1 << i);
        }
        if(cData & 1) { /* TX Buffer empty? */
            if3_tisr[board] |= (1 << i);
        }
    }
    return (SCPE_OK);
}
