/* pdp11_dh.c: DH11, asynchronous serial line interface

   Copyright (c) 2022, Lars Brinkhoff

   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
   THE AUTHORS 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.
*/

#include "pdp11_defs.h"
#include "sim_tmxr.h"

t_stat dh_rd(int32 *data, int32 PA, int32 access);
t_stat dh_wr(int32 data, int32 PA, int32 access);
t_stat dh_input_svc(UNIT *uptr);
t_stat dh_output_svc(UNIT *uptr);
t_stat dh_reset(DEVICE *dptr);
t_stat dh_attach (UNIT *uptr, CONST char *cptr);
t_stat dh_detach (UNIT *uptr);
const char *dh_description (DEVICE *dptr);

#define DH_LINES 16

uint16 dh_scr;             /* System Control Register */
uint16 dh_nrcr;            /* Next Received Character Register */
uint16 dh_lpr[DH_LINES];   /* Line Parameter Regiser */
uint32 dh_car[DH_LINES];   /* Current Address Register */
uint16 dh_bcr[DH_LINES];   /* Byte Count Register */
uint16 dh_bar;             /* Buffer Active Register */
uint16 dh_brcr;            /* Break Control Register */
uint16 dh_ssr;             /* Silo Status Register */
uint16 dh_silo[64];

#define LN (dh_scr & 017)

/* DHSCR bits */
#define RIE     0000100
#define RI      0000200
#define CNXM    0000400
#define MAINT   0001000
#define NXM     0002000
#define MCLR    0004000
#define SIE     0010000
#define OIE     0020000
#define SI      0040000
#define TI      0100000

/* DHNRCR bits */
#define DPR     0100000

/* DHLPR bits */
#define RSPEED  0001700
#define TSPEED  0036000
#define HFD     0040000
#define ECHO    0100000

TMLN dh_ldsc[DH_LINES] = { { 0 } };
TMXR dh_desc = { DH_LINES, 0, 0, dh_ldsc };

#define IOLN_DH   020
DIB dh_dib = {
  IOBA_AUTO, IOLN_DH, &dh_rd, &dh_wr,
  2, IVCL (DHRX), 0, {NULL}, IOLN_DH
};

UNIT dh_unit[] = {
  { UDATA (&dh_input_svc, UNIT_ATTABLE | UNIT_IDLE, 0) },
  { UDATA (&dh_output_svc, UNIT_DIS | UNIT_IDLE, 0) }
};

REG dh_reg[] = {
  { ORDATAD(DHSCR,  dh_scr,  16, "System Control Register") },
  { ORDATAD(DHNRCR, dh_nrcr, 16, "Next Received Character Register") },
  { BRDATAD(DHLPR,  dh_lpr,  8, 16, DH_LINES, "Line Parameter Regiser") },
  { BRDATAD(DHCAR,  dh_car,  8, 18, DH_LINES, "Current Address Register") },
  { BRDATAD(DHBCR,  dh_bcr,  8, 16, DH_LINES, "Byte Count Register") },
  { ORDATAD(DHBAR,  dh_bar,  16, "Buffer Active Register") },
  { ORDATAD(DHBRCR, dh_brcr, 16, "Break Control Register") },
  { ORDATAD(DHSSR,  dh_ssr,  16, "Silo Status Register") },
  { BRDATAD(DHSILO, dh_silo, 8, 16, 64, "Silo") },
  { NULL }
};

MTAB dh_mod[] = {
  { MTAB_XTD|MTAB_VDV|MTAB_VALR, 020, "ADDRESS", "ADDRESS",
    &set_addr, &show_addr, NULL, "Bus address" },
  { MTAB_XTD|MTAB_VDV|MTAB_VALR,   0, "VECTOR",  "VECTOR",
    &set_vec,  &show_vec, NULL, "Interrupt vector" },
  { MTAB_XTD|MTAB_VDV, 0, NULL, "AUTOCONFIGURE",
    &set_addr_flt, NULL, NULL, "Enable autoconfiguration of address & vector" },
  { UNIT_ATT,         UNIT_ATT, "summary", NULL,
    NULL, &tmxr_show_summ, (void *) &dh_desc, "Display a summary of line states"  },
  { MTAB_XTD|MTAB_VDV|MTAB_NMO, 1, "CONNECTIONS", NULL,
    NULL, &tmxr_show_cstat, (void *) &dh_desc, "Display current connections" },
  { MTAB_XTD|MTAB_VDV|MTAB_NMO, 0, "STATISTICS", NULL,
    NULL, &tmxr_show_cstat, (void *) &dh_desc, "Display multiplexer statistics" },
  { MTAB_XTD|MTAB_VDV|MTAB_VALR, 1, NULL, "DISCONNECT",
    &tmxr_dscln, NULL, &dh_desc, "Disconnect a specific line" },
  { 0 }  };

#define DBG_IO        0001

DEBTAB dh_deb[] = {
  { "IO", DBG_IO, "trace" },
  { NULL, 0 }
};

DEVICE dh_dev = {
    "DH", dh_unit, dh_reg, dh_mod,
    2, 8, 16, 1, 8, 16,
    NULL, NULL, &dh_reset,
    NULL, &dh_attach, &dh_detach,
    &dh_dib, DEV_DIS | DEV_DISABLE | DEV_UBUS | DEV_DEBUG | DEV_MUX,
    0, dh_deb, NULL, NULL, NULL, NULL, NULL, 
    &dh_description
};

t_stat
dh_rd(int32 *data, int32 PA, int32 access)
{
  t_stat stat = SCPE_OK;
  *data = 0;
  switch (PA & 017) {
  case 000:
    *data = dh_scr;
    sim_debug (DBG_IO, &dh_dev, "READ DHSCR %06o\n", *data);
    break;
  case 002:
    *data = dh_nrcr;
    dh_nrcr = 0;
    dh_scr &= ~RI;
    CLR_INT (DHRX);
    sim_activate_abs (&dh_unit[0], 0);
    sim_debug (DBG_IO, &dh_dev, "READ DHNRCR %06o\n", *data);
    break;
  case 004:
    *data = 0;
    sim_debug (DBG_IO, &dh_dev, "READ DHLPR[%o]\n", LN);
    break;
  case 006:
    *data = dh_car[LN];
    sim_debug (DBG_IO, &dh_dev, "READ DHCAR[%o] %06o\n", LN, *data);
    break;
  case 010:
    *data = dh_bcr[LN];
    sim_debug (DBG_IO, &dh_dev, "READ DHBCR[%o] %06o\n", LN, *data);
    break;
  case 012:
    *data = dh_bar;
    sim_debug (DBG_IO, &dh_dev, "READ DHBAR %06o\n", *data);
    break;
  case 014:
    *data = dh_brcr;
    sim_debug (DBG_IO, &dh_dev, "READ DHBRCR %06o\n", *data);
    break;
  case 016:
    dh_ssr &= ~0300;
    dh_ssr |= (dh_car[LN] >> 10) & 0300;
    *data = dh_ssr;
    sim_debug (DBG_IO, &dh_dev, "READ DHSSR %06o\n", *data);
    break;
  default:
    *data = 0;
    break;
  }
  return stat;
}

t_stat
dh_wr(int32 data, int32 PA, int32 access)
{
  t_stat stat = SCPE_OK;

  switch (PA & 017) {
  case 000:
    sim_debug (DBG_IO, &dh_dev, "WRITE DHSCR %06o\n", data);
    if (access == WRITEB) {
        /* Only writing one byte of "SCR" */
        if ((PA & 1) == 0) {
            /* Even byte offset */
            data = (dh_scr & ~0377) | data;
        }
        else {
            /* Odd byte offset */
            data = (dh_scr & 0377) | (data << 8);
        }
    }
    dh_scr = data;
    if (data & MCLR)
      dh_reset (&dh_dev);
    if (data & CNXM)
      data &= ~NXM;
    if (data & TI) {
      if (dh_scr & OIE)
        SET_INT (DHTX);
    } else {
      if (dh_bar != 0)
        sim_activate_abs (&dh_unit[1], 0);
      CLR_INT (DHTX);
    }
    break;
  case 002:
    sim_debug (DBG_IO, &dh_dev, "WRITE DHNRCR %06o\n", data);
    break;
  case 004:
    sim_debug (DBG_IO, &dh_dev, "WRITE DHLPR[%o] %06o\n", LN, data);
    dh_lpr[LN] = data;
    if (data & RSPEED)
      sim_activate_abs (&dh_unit[0], 0);
    break;
  case 006:
    sim_debug (DBG_IO, &dh_dev, "WRITE DHCAR[%o] %06o\n", LN, data);
    dh_car[LN] = data;
    dh_car[LN] |= ((uint32)dh_scr & 060) << 12;
    break;
  case 010:
    sim_debug (DBG_IO, &dh_dev, "WRITE DHBCR[%o] %06o\n", LN, data);
    dh_bcr[LN] = data;
    if (data == 0)
      sim_cancel (&dh_unit[1]);
    break;
  case 012:
    sim_debug (DBG_IO, &dh_dev, "WRITE DHBAR %06o\n", data);
    dh_bar = data;
    if (dh_bar == 0)
      sim_cancel (&dh_unit[1]);
    else
      sim_activate_abs (&dh_unit[1], 0);
    break;
  case 014:
    sim_debug (DBG_IO, &dh_dev, "WRITE DHBRCR %06o\n", data);
    dh_brcr = data;
    break;
  case 016:
    sim_debug (DBG_IO, &dh_dev, "WRITE DHSSR %06o\n", data);
    dh_ssr &= 077700;
    dh_ssr |= data & 0100077;
    break;
  default:
    break;
  }
  return stat;
}

t_stat dh_attach (UNIT *uptr, CONST char *cptr)
{
  return tmxr_attach (&dh_desc, uptr, cptr);
}

t_stat dh_detach (UNIT *uptr)
{
  return tmxr_detach (&dh_desc, uptr);
}

t_stat dh_input_svc(UNIT *uptr)
{
  int32 ch;
  int i;

  sim_clock_coschedule (uptr, 100);

  i = tmxr_poll_conn (&dh_desc);
  if (i >= 0) {
    dh_ldsc[i].rcve = 1;
    dh_ldsc[i].xmte = 1;
    sim_debug(DBG_IO, &dh_dev, "Connect %d\n", i);
  }

  tmxr_poll_rx (&dh_desc);

  for (i = 0; i < DH_LINES; i++) {
    ch = tmxr_getc_ln (&dh_ldsc[i]);
    if (ch & TMXR_VALID) {
      ch &= (1 << ((dh_lpr[i] & 3) + 5)) - 1;
      dh_nrcr = ch | (i << 8) | DPR;
      dh_scr |= RI;
      if (dh_scr & RIE)
        SET_INT (DHRX);
      sim_debug(DBG_IO, &dh_dev, "Input character %03o line %d\n", ch, i);
      sim_cancel (&dh_unit[0]);
      break;
    }
  }

  return SCPE_OK;
}

t_stat dh_output_svc(UNIT *uptr)
{
  int32 ch;
  int i;

  sim_clock_coschedule (uptr, 100);

  for (i = 0; i < DH_LINES; i++) {
    if ((dh_bar & (1 << i)) == 0)
      continue;
    if (dh_bcr[i] == 0)
      continue;
    ch = RdMemB (dh_car[i]);
    ch &= (1 << ((dh_lpr[i] & 3) + 5)) - 1;
    if (tmxr_putc_ln (&dh_ldsc[i], ch) != SCPE_STALL) {
      sim_debug(DBG_IO, &dh_dev, "Output character %03o line %d\n", ch, i);
      dh_car[i]++;
      dh_car[i] &= 0777777;
      dh_bcr[i]++;
      dh_bcr[i] &= 0177777;
      if (dh_bcr[i] == 0) {
        dh_bar &= ~(1 << i);
        dh_scr |= TI;
        if (dh_bar == 0)
          sim_cancel (uptr);
        if (dh_scr & OIE)
          SET_INT (DHTX);
      }
    }
  }

  tmxr_poll_tx (&dh_desc);

  return SCPE_OK;
}

t_stat dh_reset(DEVICE *dptr)
{
  CLR_INT (DHRX);
  CLR_INT (DHTX);
  sim_cancel (&dh_unit[0]);
  sim_cancel (&dh_unit[1]);
  dh_scr = 0;
  dh_nrcr = 0;
  dh_bar = 0;
  dh_brcr = 0;
  dh_ssr = 0;
  memset (dh_lpr, 0, sizeof dh_lpr);
  memset (dh_car, 0, sizeof dh_car);
  memset (dh_bcr, 0, sizeof dh_bcr);
  return SCPE_OK;
}

const char *dh_description (DEVICE *dptr)
{
  return "DH-11, asynchronous serial line interface";
}
