/* scelbi_io.c: I/O for the SCELBI computer.

   Copyright (c) 2017, Hans-Ake Lund

   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 NONINFRINGEMENT.  IN NO EVENT SHALL
   ROBERT M SUPNIK 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.


    This interface simulates a "bitbanger" TTY interface as implemented
    on the SCELBI computer in the SCELBAL source code.
    Inport 2 bit 7 is used as input from the TTY and
    Outport 2 bit 0 is used as output to the TTY.
    In SCELBI documentation Inport 5 is used for input from the TTY
    and Outport 6 is used for output to the TTY.
    The I/O simulation routines are mapped to both port combinations.


    There are also functions that support simulated I/O for
    the Intel 8008 computer built for a master thesis in 1975.
    These functiona are however not mapped in the i/o configuration
    table as they conflict with the SCELBI TTY interface.
    Note that Inport 0 is read by the assembler code as INP 0
    Outport 0 is written by the AS Macro Assembler code as OUT 10 (octal)

    The following i/o ports were used in this computer:
    Outport 0: used to select device for reading from Inport 0
        and writing to Outport 3.
    Inport 0: used to read external data.
    Outport 3: used to write external data.

    Outport 1: used to save interupt state, connected to Inport 1.
    Outport 2: used to save interupt state, connected to Inport 2.

    Inport 3: used to input data from tape-reader
    Outport 4: used to output character to printer (implemented).
    Inport 5: used to input character from keyboard (implemented).

    Inport 4: used for status flags for the ports (Flagport).
      Flag 1 (bit 0): set to 1 when printer ready (implemented).
      Flag 2 (bit 1): set to 1 when input available from tape-reader.
      Flag 3 (bit 2): set to 1 when tape in tape-reader.
      Flag 5 (bit 4): set to 1 when character available from keyboard (implemented).
      Flag 7 (bit 6): set to 1 when the reset key on the computer is pressed.

    Inport 7: used to start the printer motor, just using an output pulse,
        no data is read.

    04-Sep-17    HAL     Working version of SCELBI simulator
    12-Sep-17    HAL     Modules restructured in "Intel-Systems" directory

*/

#include <stdio.h>
#include "system_defs.h"

/* This is the I/O configuration table. There are 8 possible
   input device addresses (octal 0 - 7) and 24 possible output 
   device addresses (octal 10 - 37).
   The port numbers are specified as for the 8008 AS Macro Assembler,
   in other 8008 assemblers outport 012 (octal) may be specified as 2.
   If a device is plugged to a port it's routine
   address is here, 'nulldev' means no device is available.
 */
int32 ttyout_d(int32 io, int32 data);
int32 ttyin_d(int32 io, int32 data);
int32 prt_d(int32 io, int32 data);
int32 kbd_d(int32 io, int32 data);
int32 iostat_s(int32 io, int32 data);
int32 nulldev(int32 io, int32 data);

struct idev dev_table[32] = {
{&nulldev}, {&nulldev}, {&ttyin_d}, {&nulldev},     /* 000 input 0 - 3 */
{&nulldev}, {&ttyin_d}, {&nulldev}, {&nulldev},     /* 004 input 4 - 7 */
{&nulldev}, {&nulldev}, {&ttyout_d}, {&nulldev},    /* 010 output 8 - 11 */
{&nulldev}, {&nulldev}, {&ttyout_d}, {&nulldev},    /* 014 output 12 - 15 */
{&nulldev}, {&nulldev}, {&nulldev}, {&nulldev},     /* 020 output 16 - 19 */
{&nulldev}, {&nulldev}, {&nulldev}, {&nulldev},     /* 024 output 20 - 23 */
{&nulldev}, {&nulldev}, {&nulldev}, {&nulldev},     /* 030 output 24 - 27 */
{&nulldev}, {&nulldev}, {&nulldev}, {&nulldev}      /* 034 output 28 - 31 */
};

#define UNIT_V_ANSI (UNIT_V_UF + 0)                     /* ANSI mode */
#define UNIT_ANSI   (1 << UNIT_V_ANSI)

t_stat tty_svc (UNIT *uptr);
t_stat tty_reset (DEVICE *dptr);
t_stat ptr_svc (UNIT *uptr);
t_stat ptr_reset (DEVICE *dptr);

/* I/O Data Structures */

/* TTY, TeleTYpewriter - console input/output
 */
UNIT tty_unit = { UDATA (&tty_svc, 0, 0), KBD_POLL_WAIT };

REG tty_reg[] = {
    { ORDATA (DATA, tty_unit.buf, 8) },
    { ORDATA (STAT, tty_unit.u3, 8) },
    { NULL }
};

MTAB tty_mod[] = {
    { UNIT_ANSI, 0, "TTY", "TTY", NULL },
    { UNIT_ANSI, UNIT_ANSI, "ANSI", "ANSI", NULL },
    { 0 }
};

DEVICE tty_dev = {
    "TTY", &tty_unit, tty_reg, tty_mod,
    1, 10, 31, 1, 8, 8,
    NULL, NULL, &tty_reset,
    NULL, NULL, NULL
};

/* PTR, Paper Tape Reader - not implemented yet
 */
UNIT ptr_unit = { UDATA (&ptr_svc, UNIT_SEQ + UNIT_ATTABLE, 0), KBD_POLL_WAIT };

REG ptr_reg[] = {
    { ORDATA (DATA, ptr_unit.buf, 8) },
    { ORDATA (STAT, ptr_unit.u3, 8) },
    { ORDATA (POS, ptr_unit.pos, T_ADDR_W) },
    { NULL }
};

DEVICE ptr_dev = {
    "PTR", &ptr_unit, ptr_reg, NULL,
    1, 10, 31, 1, 8, 8,
    NULL, NULL, &ptr_reset,
    NULL, NULL, NULL
};

/* Service routines to handle simulator functions */

/* Service routine for TTY - actually gets char & places in buffer
 */
t_stat tty_svc (UNIT *uptr)
{
    int32 temp;

    sim_activate (&tty_unit, tty_unit.wait);        /* continue poll */
    if ((temp = sim_poll_kbd ()) < SCPE_KFLAG)
        return (temp);                              /* no char or error? */
    tty_unit.buf = temp & 0377;                     /* Save char */
    tty_unit.u3 |= 0x10;                            /* Set status
                                                       Flag 5 (bit 3) == 1 */

    /* Do any special character handling here */

    tty_unit.pos++;
    return SCPE_OK;
}

/* Service routine for Paper Tape Reader - not implemented yet
 */
t_stat ptr_svc (UNIT *uptr)
{
    return SCPE_OK;
}

/* Reset routines */

/* Reset routine for TTY
 */
t_stat tty_reset (DEVICE *dptr)
{
    tty_unit.buf = 0;                              /* Data */
    tty_unit.u3 = 0x01;                            /* Status
                                                      Flag 1 (bit 0) == 1
                                                      printer always ready */
    sim_activate (&tty_unit, tty_unit.wait);       /* activate unit */
    return SCPE_OK;
}

/* Reset routine for Paper Tape Reader - not implemented yet
 */
t_stat ptr_reset (DEVICE *dptr)
{
    ptr_unit.buf = 0;
    ptr_unit.u3 = 0;
    sim_cancel (&ptr_unit);                        /* deactivate unit */
    return SCPE_OK;
}

/*  I/O instruction handlers for the 8008 simulator.
    Called from the CPU module when an IN or OUT instruction is issued.
    Each function is passed an 'io' flag, where 0 means a read from
    the port, and 1 means a write to the port.  On input, the actual
    input (io == 0) is passed as the return value,
    on output (io != 0), 'data' is written to the device.
 */

/*  I/O instruction handlers for the SCELBI bitbanger serial interface
 */
int32 ttyin_bitcntr = 0;
int32 ttyin_charin = 0;

/* TTY input routine, assumes 1 start bit, 8 databits and 2 stop bits.
   the assumed number of INP instructions for each character are 9
 */
int32 ttyin_d(int32 io, int32 data)
{
    int32 newbit;

    /* if (ttyin_bitcntr != 0) {
        sim_printf("io: %d, bitcntr: %d, charin: 0%o\n",
           io, ttyin_bitcntr, ttyin_charin);
    }
    */
    if (io != 0) { /* not an INP instruction */
        return 0;
    }
    if (ttyin_bitcntr == 0) {
        if (tty_unit.u3 & 0x10) {
            /* Character available if Flag 5 (bit 4) set */
            ttyin_charin = tty_unit.buf | 0x80; /* bit 7 always set in SCELBAL */
            tty_unit.u3 = tty_unit.u3 & 0xEF;  /* Reset Flag 5 (bit 4) */
            ttyin_bitcntr = 1;
            return (0); /* start bit */
        }
        else {
            return (0x80); /* no start bit */
        }
    }
    if (ttyin_bitcntr > 7) { /* last data bit */
        if (ttyin_charin & 1)
            newbit = 0x80;
        else
            newbit = 0x00;
        ttyin_bitcntr = 0;
        return (newbit);
    }
    if (ttyin_charin & 1)
        newbit = 0x80;
    else
        newbit = 0x00;
    ttyin_bitcntr++;
    ttyin_charin = ttyin_charin >> 1;
    return (newbit);
}

int32 ttyout_bitcntr = 0;
int32 ttyout_charout = 0;

/* TTY output routine, assumes 1 start bit, 8 databits and 2 stop bits.
   the assumed number of OUT instructions for each character are 10
 */
int32 ttyout_d(int32 io, int32 data)
{
    int32 newbit;

    /* sim_printf("io: %d, data: 0%o, bit0: %d, bitcntr: %d, charout: 0%o\n",
        io, data, (data & 1), ttyout_bitcntr, ttyout_charout);
    */

    if (io == 0) { /* not an OUT instruction */
        return 0;
    }
    if ((ttyout_bitcntr == 0) && ((data & 1) == 0)) { /* start bit */
        ttyout_bitcntr = 1;
        return 0;
    }
    if (ttyout_bitcntr == 8) { /* last bit in character */
        if (data & 1)
            newbit = 0x80;
        else
            newbit = 0x00;
        ttyout_charout = ttyout_charout >> 1;
        ttyout_charout = ttyout_charout | newbit;
        if (ttyout_charout != 0224) /* avoid printing CTRL-T */
            sim_putchar(ttyout_charout & 0x7f); /* bit 7 always set in SCELBAL */
        ttyout_bitcntr++;
        return 0;
    }
    if (ttyout_bitcntr > 8) { /* stop bit */
        ttyout_charout = 0;
        ttyout_bitcntr = 0;
        return 0;
    }
    if (data & 1)
        newbit = 0x80;
    else
        newbit = 0x00;
    ttyout_charout = ttyout_charout >> 1;
    ttyout_charout = ttyout_charout | newbit;
    ttyout_bitcntr++;
    return 0;
}

/*  I/O instruction handlers for the master thesis computer hardware.
 */

/* Get status byte from Flagport
 */
int32 iostat_s(int32 io, int32 data)
{
    if (io == 0)
        return (tty_unit.u3);
      else
        return (0);
}

/* Get character from keyboard
 */
int32 kbd_d(int32 io, int32 data)
{
    if (io == 0) {
        tty_unit.u3 = tty_unit.u3 & 0xEF;  /* Reset Flag 5 (bit 4) */
        return (tty_unit.buf | 0x80); /* bit 7 always set in SCELBAL */
    }
    return 0;
}

/* Put character to printer
 */
int32 prt_d(int32 io, int32 data)
{
    if (io != 0) {
        sim_putchar(data & 0x7f); /* bit 7 always set in SCELBAL */
    }
    return 0;
}

/* I/O instruction handler for unused ports
 */
int32 nulldev(int32 flag, int32 data)
{
    if (flag == 0)
        return (0377);
    return 0;
}
