/*
 * Copyright (c) 2023 Anders Magnusson.
 * All rights reserved.
 *
 * 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 above copyright
 *    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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 "sim_defs.h"
#include "sim_tmxr.h"

#include "nd100_defs.h"

struct intr tti_int = { 0, 1 };
struct intr tto_int = { 0, 1 };

MTAB ttx_mod[];

int tti_status, tti_ctrl;
int tto_status, tto_ctrl;

t_stat tti_svc(UNIT *uptr);
t_stat tto_svc(UNIT *uptr);
t_stat tti_reset(DEVICE *dptr);
t_stat tto_reset(DEVICE *dptr);
t_stat tty_setpar(UNIT *uptr);

#define TT_ICTRL_EIRDY  0000001 /* enable int on dev ready */
#define TT_ICTRL_EIERR  0000002 /* enable int on error */
#define TT_ICTRL_ACT    0000004 /* set device active */

#define TT_ISTAT_IRDY   0000001 /* device ready gives interrupt */
#define TT_ISTAT_RDY    0000010 /* device ready for transfer */

#define TT_OCTRL_EIRDY  0000001 /* enable int on dev ready */
#define TT_OCTRL_EIERR  0000002 /* enable int on error */
#define TT_OCTRL_ACT    0000004 /* set device active */

#define TT_OSTAT_IRDY   0000001 /* device ready gives interrupt */
#define TT_OSTAT_EINT   0000002 /* error interrupt enabled */
#define TT_OSTAT_ACT    0000004 /* device active */
#define TT_OSTAT_RDY    0000010 /* device ready for transfer */

UNIT tti_unit = { UDATA (&tti_svc, 0, 0), KBD_POLL_WAIT };

REG tti_reg[] = {
        { ORDATA(BUF, tti_unit.buf, 8) },
        { ORDATA(ISTATUS, tti_status, 16) },
        { ORDATA(ICTRL, tti_ctrl, 16) },
        { DRDATA (TIME, tti_unit.wait, 24), },
        { NULL }
};

DEVICE tti_dev = {
        "TTI", &tti_unit, tti_reg, ttx_mod,
        1, 10, 31, 1, 8, 8,
        NULL, NULL, &tti_reset,
        NULL, NULL, NULL,
        NULL, 0
};

UNIT tto_unit = { UDATA (&tto_svc, 0, 0), SERIAL_OUT_WAIT };

REG tto_reg[] = {
        { ORDATA(OSTATUS, tto_status, 16) },
        { ORDATA(OCTRL, tto_ctrl, 16) },
        { DRDATA(POS, tto_unit.pos, T_ADDR_W), PV_LEFT },
        { DRDATA(TIME, tto_unit.wait, 24), REG_NZ + PV_LEFT },
        { NULL }
};

MTAB ttx_mod[] = {
#if 0
        { TT_PAR, TT_PAR_EVEN,  "even parity", "EVEN",  &tty_setpar },
        { TT_PAR, TT_PAR_ODD,   "odd parity",  "ODD",   &tty_setpar },
        { TT_PAR, TT_PAR_MARK,  "mark parity", "MARK",  &tty_setpar },
        { TT_PAR, TT_PAR_SPACE, "no parity",   "NONE",  &tty_setpar },
#endif
        { 0 }
};

DEVICE tto_dev = {
        "TTO", &tto_unit, tto_reg, ttx_mod,
        1, 10, 31, 1, 8, 8,
        NULL, NULL, &tto_reset,
        NULL, NULL, NULL,
        NULL, 0
};

t_stat
tti_reset(DEVICE *dptr)
{
        sim_cancel(&tti_unit);
        return SCPE_OK;
}

t_stat
tto_reset(DEVICE *dptr)
{
        sim_cancel(&tto_unit);
        tto_status |= TT_OSTAT_RDY;
        return SCPE_OK;
}

t_stat
tti_svc(UNIT *uptr)
{
        int temp;

        sim_activate (&tti_unit, tti_unit.wait);
        if ((temp = sim_poll_kbd()) < SCPE_KFLAG)
                return temp;

        tti_unit.buf = temp & 0177;
        if (tti_ctrl & TT_ICTRL_ACT) {
                tti_status |= TT_ISTAT_RDY;
                if (tti_ctrl & TT_ICTRL_EIRDY)
                        extint(12, &tti_int);
        }
        
        return SCPE_OK;
}

t_stat
tto_svc(UNIT *uptr)
{
        int32   c;
        t_stat  r;

        c = tto_unit.buf & 0177;
        if ((r = sim_putchar_s (c)) != SCPE_OK) {       /* output; error? */
                sim_activate (uptr, uptr->wait);        /* try again */
                return ((r == SCPE_STALL)? SCPE_OK : r);/* !stall? report */
        }
        tto_status &= ~TT_OSTAT_ACT;
        tto_status |= TT_OSTAT_RDY;
        if (tto_ctrl & TT_OCTRL_EIRDY)
                extint(10, &tto_int);
        return SCPE_OK;
}


int
iox_tty(int addr)
{
        int rv = 0;

        /*
         * First four addresses are input, following four are output.
         */
        switch (addr & 07) {
        case 0: /* read data */
                regA = tti_unit.buf;
                tti_status &= ~TT_ISTAT_RDY;
                break;

        case 1: /* Ignored */
                break;

        case 2: /* read input status reg */
                regA = tti_status;
                break;

        case 3: /* Write control reg */
                if ((tti_ctrl & TT_ICTRL_ACT) == 0 && (regA & TT_ICTRL_ACT))
                        sim_activate (&tti_unit, tti_unit.wait);
                if ((regA & TT_ICTRL_ACT) == 0)
                        sim_cancel(&tti_unit);
                tti_ctrl = regA;
                if (tti_ctrl & TT_ICTRL_EIRDY)
                        tti_status |= TT_ISTAT_IRDY;
                else
                        tti_status &= ~TT_ISTAT_IRDY;
                break;

        case 4: /* Ignored */
                break;

        case 5: /* Write data */
                tto_unit.buf = regA & 0177;
                tto_status &= ~TT_OSTAT_RDY;
                tto_status |= TT_OSTAT_ACT;
                sim_activate (&tto_unit, tto_unit.wait);
                break;

        case 6: /* Read output status */
                regA = tto_status;
                break;

        case 7: /* Write output control reg */
                /* Only interrupts are controlled */
                tto_status = (tto_status & ~03) | (regA & 03);
                break;
        }

        return rv;
}

/*
 * Real-time clock.
 */
#define CLK_PER_SEC     50

int int_enabled, dev_ready;

struct intr rtc_int = { 0, 1 };

t_stat clk_reset(DEVICE *dptr);

t_stat clk_svc(UNIT *uptr);

UNIT clk_unit = { UDATA (&clk_svc, 0, 0) };

REG clk_reg[] = {
        { FLDATA (INTENB, int_enabled, 0) },
        { FLDATA (DEVRDY, dev_ready, 0) },
        { NULL }
};

MTAB clk_mod[] = {
        { 0 }
};

DEVICE clk_dev = {
        "RTC", &clk_unit, clk_reg, clk_mod,
        1, 0, 0, 0, 0, 0,
        NULL, NULL, &clk_reset,
        NULL, NULL, NULL,
        0, 0
};

int
iox_clk(int addr)
{
        int rv = 0;

        switch (addr & 3) {
        case 0: /* return 0 in A */
                regA = 0;
                break;
        case 1: /* Reset counter */
                sim_activate_after_abs(&clk_unit, 1000000/CLK_PER_SEC);
                break;
        case 2: /* read status */
                regA = (dev_ready << 3) | int_enabled;
                break;

        case 3: /* set status */
                sim_activate_after_abs(&clk_unit, 1000000/CLK_PER_SEC);
                int_enabled = regA & 1;
                if (BIT13(regA))
                        dev_ready = 0;
                break;

        default:
                rv = STOP_UNHIOX;
        }
        return rv;
}

t_stat
clk_reset (DEVICE *dptr)
{
        sim_rtc_init(1000000/CLK_PER_SEC);
        return SCPE_OK;
}

t_stat
clk_svc(UNIT *uptr)
{
        sim_rtc_calb(CLK_PER_SEC);
        sim_activate_after(&clk_unit, 1000000/CLK_PER_SEC);
        dev_ready = 1;
        if (int_enabled)
                extint(13, &rtc_int);
        return SCPE_OK;
}
