/* vax4xx_rz94.c: NCR 53C94 SCSI controller

   Copyright (c) 2019, Matt Burke

   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 AUTHOR 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 name of the author 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 author.

   rz           SCSI controller
*/

#include "vax_defs.h"
#include "sim_scsi.h"
#include "vax_rzdev.h"

/* command groups */

#define CMD_DISC        0x40                            /* disconnected state group */
#define CMD_TARG        0x20                            /* target state group */
#define CMD_INIT        0x10                            /* initiator state group */

/* status register */

#define STS_INT         0x80                            /* interrupt */
#define STS_GE          0x40                            /* gross error */
#define STS_PE          0x20                            /* parity error */
#define STS_TC          0x10                            /* terminal count */
#define STS_VGC         0x08                            /* valid group code */
#define STS_PH          0x07                            /* SCSI phase */
#define STS_CLR         0x10

/* interrupt register */

#define INT_SCSIRST     0x80                            /* SCSI reset */
#define INT_ILLCMD      0x40                            /* illegal command */
#define INT_DIS         0x20                            /* disconnect */
#define INT_BUSSV       0x10                            /* bus service */
#define INT_FC          0x08                            /* function complete */
#define INT_RSEL        0x04                            /* reselected */
#define INT_SELA        0x02                            /* selected with ATN */
#define INT_SEL         0x01                            /* selected */

/* configuration register 1 */

#define CFG1_SLOW       0x80                            /* slow cable mode */
#define CFG1_SRD        0x40                            /* disable SCSI reset int */
#define CFG1_PTST       0x20                            /* parity test */
#define CFG1_PEN        0x10                            /* parity enable */
#define CFG1_TEST       0x08                            /* chip test */
#define CFG1_MYID       0x07                            /* my bus id */

#define UNIT_V_DTYPE    (SCSI_V_UF + 0)                 /* drive type */
#define UNIT_M_DTYPE    0x1F
#define UNIT_DTYPE      (UNIT_M_DTYPE << UNIT_V_DTYPE)
#define GET_DTYPE(x)    (((x) >> UNIT_V_DTYPE) & UNIT_M_DTYPE)

#define RZ_MAXFR        (1u << 16)                      /* max transfer */

uint32 rz_last_cmd = 0;
uint32 rz_txi = 0;                                      /* transfer count */
uint32 rz_txc = 0;                                      /* transfer counter */
uint8 rz_cfg1 = 0;                                      /* config 1 */
uint8 rz_cfg2 = 0;                                      /* config 2 */
uint8 rz_cfg3 = 0;                                      /* config 3 */
uint8 rz_int = 0;                                       /* interrupt */
uint8 rz_stat = 0;                                      /* status */
uint32 rz_seq = 0;
uint32 rz_dest = 1;
uint8 rz_fifo[16] = { 0 };
uint32 rz_fifo_t = 0;
uint32 rz_fifo_b = 0;
uint32 rz_fifo_c = 0;
uint32 rz_dma = 0;
uint32 rz_dir = 0;
uint8 *rz_buf;
SCSI_BUS rz_bus;

/* debugging bitmaps */

#define DBG_REG         0x0001                          /* trace read/write registers */
#define DBG_CMD         0x0002                          /* display commands */
#define DBG_INT         0x0004                          /* display transfer requests */

DEBTAB rz_debug[] = {
    { "REG",  DBG_REG,      "Register activity" },
    { "CMD",  DBG_CMD,      "Chip commands" },
    { "INT",  DBG_INT,      "Interrupts" },
    { "SCMD", SCSI_DBG_CMD, "SCSI commands" },
    { "SMSG", SCSI_DBG_MSG, "SCSI messages" },
    { "SBUS", SCSI_DBG_BUS, "SCSI bus activity" },
    { "SDSK", SCSI_DBG_DSK, "SCSI disk activity" },
    { "STAP", SCSI_DBG_TAP, "SCSI tape activity" },
    { 0 }
};

t_stat rz_svc (UNIT *uptr);
t_stat rz_reset (DEVICE *dptr);
t_stat rz_attach (UNIT *uptr, CONST char *cptr);
void rz_sw_reset (void);
t_stat rz_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
void rz_cmd (uint32 cmd);
t_stat rz_set_type (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat rz_show_type (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
const char *rz_description (DEVICE *dptr);


/* RZ data structures

   rz_dev       RZ device descriptor
   rz_unit      RZ unit list
   rz_reg       RZ register list
*/

UNIT rz_unit[] = {
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+UNIT_ROABLE+
            (RZ23_DTYPE << UNIT_V_DTYPE), RZ_SIZE (RZ23)) },
    { UDATA (&rz_svc, UNIT_DIS, 0) }
    };

REG rz_reg[] = {
    { FLDATAD (INT, int_req[IPL_SC], INT_V_SC, "interrupt pending flag") },
    { NULL }
    };

MTAB rz_mod[] = {
    { MTAB_XTD|MTAB_VUN, 0, "write enabled", "WRITEENABLED", 
        &scsi_set_wlk, &scsi_show_wlk,   NULL, "Write enable drive" },
    { MTAB_XTD|MTAB_VUN, 1, NULL, "LOCKED", 
        &scsi_set_wlk, NULL,   NULL, "Write lock drive" },
    { MTAB_XTD|MTAB_VUN, RZ23_DTYPE, NULL, "RZ23",
      &rz_set_type, NULL, NULL, "Set RZ23 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ23L_DTYPE, NULL, "RZ23L",
      &rz_set_type, NULL, NULL, "Set RZ23L Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ24_DTYPE, NULL, "RZ24",
      &rz_set_type, NULL, NULL, "Set RZ24 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ24L_DTYPE, NULL, "RZ24L",
      &rz_set_type, NULL, NULL, "Set RZ24L Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ25_DTYPE, NULL, "RZ25",
      &rz_set_type, NULL, NULL, "Set RZ25 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ25L_DTYPE, NULL, "RZ25L",
      &rz_set_type, NULL, NULL, "Set RZ25L Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ26_DTYPE, NULL, "RZ26",
      &rz_set_type, NULL, NULL, "Set RZ26 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ26L_DTYPE, NULL, "RZ26L",
      &rz_set_type, NULL, NULL, "Set RZ26L Disk Type" },
    { MTAB_XTD|MTAB_VUN, RZ55_DTYPE, NULL, "RZ55",
      &rz_set_type, NULL, NULL, "Set RZ55 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RRD40_DTYPE, NULL, "CDROM",
      &rz_set_type, NULL, NULL, "Set RRD40 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RRD40_DTYPE, NULL, "RRD40",
      &rz_set_type, NULL, NULL, "Set RRD40 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RRD42_DTYPE, NULL, "RRD42",
      &rz_set_type, NULL, NULL, "Set RRD42 Disk Type" },
    { MTAB_XTD|MTAB_VUN, RRW11_DTYPE, NULL, "RRW11",
      &rz_set_type, NULL, NULL, "Set RRW11 Disk Type" },
    { MTAB_XTD|MTAB_VUN, CDW900_DTYPE, NULL, "CDW900",
      &rz_set_type, NULL, NULL, "Set SONY CDW-900E Disk Type" },
    { MTAB_XTD|MTAB_VUN, XR1001_DTYPE, NULL, "XR1001",
      &rz_set_type, NULL, NULL, "Set JVC XR-W1001 Disk Type" },
    { MTAB_XTD|MTAB_VUN, TZK50_DTYPE, NULL, "TZK50",
      &rz_set_type, NULL, NULL, "Set DEC TZK50 Tape Type" },
    { MTAB_XTD|MTAB_VUN, TZ30_DTYPE, NULL, "TZ30",
      &rz_set_type, NULL, NULL, "Set DEC TZ30 Tape Type" },
    { MTAB_XTD|MTAB_VUN|MTAB_VALR, RZU_DTYPE, NULL, "RZUSER",
      &rz_set_type, NULL, NULL, "Set RZUSER=size Disk Type" },
    { MTAB_XTD|MTAB_VUN, 0, "TYPE", NULL,
      NULL, &rz_show_type, NULL, "Display device type" },
    { SCSI_NOAUTO, SCSI_NOAUTO, "noautosize", "NOAUTOSIZE", NULL, NULL, NULL, "Disables disk autosize on attach" },
    { SCSI_NOAUTO,           0, "autosize",   "AUTOSIZE",   NULL, NULL, NULL, "Enables disk autosize on attach" },
    { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT",
      &scsi_set_fmt, &scsi_show_fmt, NULL, "Set/Display unit format" },
    { 0 }
    };

static const char *drv_types[] = {
    "RZ23",
    "RZ23L",
    "RZ24",
    "RZ24L",
    "RZ25",
    "RZ25L",
    "RZ26",
    "RZ26L",
    "RZ55",
    "CDROM",
    "RRD40",
    "RRD42",
    "RRW11",
    "CDW900",
    "XR1001",
    "TZK50",
    "TZ30",
    "RZUSER"
    };

DEVICE rz_dev = {
    "RZ", rz_unit, rz_reg, rz_mod,
    9, DEV_RDX, 8, 1, DEV_RDX, 8,
    NULL, NULL, &rz_reset,
    NULL, &rz_attach, &scsi_detach,
    NULL, DEV_DISABLE | DEV_DEBUG | DEV_DISK | DEV_SECTORS,
    0, rz_debug, NULL, NULL, &rz_help, NULL, NULL,
    &rz_description
    };

/* Register names for Debug tracing */
static const char *rz_rd_regs[] =
    {"TX L", "TX H", "FIFO", "CMD ",
     "STAT", "INT ", "SEQ ", "FFLG",
     "CFG1", "RSVD", "RSVD", "CFG2",
     "CFG3", "RSVD", "RSVD", "RSVD" };
static const char *rz_wr_regs[] = 
    {"TX L", "TX H", "FIFO", "CMD ",
     "DST ", "TMO ", "SYNP", "SYNO",
     "CFG1", "CLK ", "TEST", "CFG2",
     "CFG3", "RSVD", "RSVD", "FFOB" };

uint8 rz_fifo_rd (void)
{
if (rz_fifo_c) {
    rz_fifo_t &= 0xF;
    rz_fifo_c--;
    return rz_fifo[rz_fifo_t++];
    }
else
    return rz_fifo[rz_fifo_b];
}

void rz_fifo_wr (uint8 data)
{
if (rz_fifo_c < 16) {
    rz_fifo[rz_fifo_b++] = data;
    rz_fifo_b &= 0xF;
    rz_fifo_c++;
    }
else {
    rz_fifo[rz_fifo_b] = data;
    rz_stat |= STS_GE;                                  /* gross error */
    }
}

void rz_fifo_reset (void)
{
rz_fifo_c = 0;
rz_fifo_t = rz_fifo_b = 0;
rz_fifo[rz_fifo_b] = 0;
}

/* IO dispatch routines, I/O addresses 177601x0 - 177601x7 */

int32 rz_rd (int32 pa)
{
int32 data = 0;

if (pa == 0x200C0000) {
    return rz_dma;
    }
if (pa == 0x200C000C) {
    return rz_dir;
    }

switch ((pa >> 2) & 0xF) {                              /* case on PA<2:1> */

    case 0:                                             /* transfer counter LSB */
        data = rz_txc & 0xFF;
        break;

    case 1:                                             /* transfer counter MSB */
        data = (rz_txc >> 8) & 0xFF;
        break;

    case 2:                                             /* FIFO */
        data = rz_fifo_rd ();
        break;

    case 3:                                             /* command */
        data = rz_last_cmd;
        break;

    case 4:                                             /* status */
        data = rz_stat | rz_bus.phase;
        break;

    case 5:                                             /* interrupt */
        data = rz_int;
        if (rz_stat & STS_INT) {
            rz_stat &= STS_CLR;
            rz_int = 0;
            }
        break;

    case 6:                                             /* sequence step */
        data = rz_seq;
        break;

    case 7:                                             /* FIFO flags/seq step */
        data = (rz_seq << 5) | rz_fifo_c;
        break;

    case 8:                                             /* config 1 */
        data = rz_cfg1;
        break;

    case 11:                                            /* config 2 */
        data = rz_cfg2;
        break;

    case 12:                                            /* config 3 */
        data = rz_cfg3;
        break;

    default:                                            /* NCR reserved */
        data = 0;
        break;
        }

sim_debug (DBG_REG, &rz_dev, "rz_rd(PA=0x%08X [%s], data=0x%X) at %08X\n", pa, rz_rd_regs[(pa >> 2) & 0xF], data, fault_PC);

SET_IRQL;
return data;
}

void rz_wr (int32 pa, int32 data, int32 access)
{
sim_debug (DBG_REG, &rz_dev, "rz_wr(PA=0x%08X [%s], access=%d, data=0x%X) at %08X\n", pa, rz_wr_regs[(pa >> 2) & 0xF], access, data, fault_PC);

if (pa == 0x200C0000) {
    rz_dma = data;
    return;
    }
if (pa == 0x200C000C) {
    rz_dir = data;
    return;
    }

switch ((pa >> 2) & 0xF) {                              /* case on PA<2:1> */

    case 0:                                             /* transfer count LSB */
        rz_txi = (rz_txi & ~0xFF) | (data & 0xFF);
        break;

    case 1:                                             /* transfer count MSB */
        rz_txi = (rz_txi & ~0xFF00) | ((data << 8) & 0xFF00);
        break;

    case 2:                                             /* FIFO */
        rz_fifo_wr (data);
        break;

    case 3:                                             /* command */
        rz_cmd (data);
        break;

    case 4:                                             /* destination bus ID */
        rz_dest = data & 0x7;
        break;

    case 5:                                             /* select timeout - NI */
        break;

    case 6:                                             /* sync period - NI*/
        break;

    case 7:                                             /* sync offset - NI? */
        break;

    case 8:                                             /* config 1 */
        rz_cfg1 = data;
        break;

    case 9:                                             /* clock conversion - NI */
        break;

    case 10:                                            /* test - NI? */
        break;

    case 11:                                            /* config 2 */
        rz_cfg2 = data;
        break;

    case 12:                                            /* config 3 */
        rz_cfg3 = data;
        break;

    case 15:                                            /* FIFO bottom */
        break;

    default:                                            /* NCR reserved */
        break;
        }

SET_IRQL;
}

/* Unit service routine */

t_stat rz_svc (UNIT *uptr)
{
rz_stat |= STS_INT;
SET_INT (SC);
return SCPE_OK;
}

void rz_setint (uint32 flag)
{
rz_int |= flag;
sim_activate (&rz_unit[8], 50);
}

void rz_cmd (uint32 cmd)
{
uint32 ini = (rz_cfg1 & CFG1_MYID);
uint32 tgt = rz_dest;
uint32 state = scsi_state (&rz_bus, ini);
uint32 txc, i, old_phase;

if ((cmd & CMD_DISC) && (state != SCSI_DISC)) {         /* check cmd validity */
    sim_debug (DBG_CMD, &rz_dev, "disconnected cmd when not disconnected\n");
    rz_setint (INT_ILLCMD);
    return;
    }
if ((cmd & CMD_TARG) && (state != SCSI_TARG)) {
    sim_debug (DBG_CMD, &rz_dev, "target cmd when not target\n");
    rz_setint (INT_ILLCMD);
    return;
    }
if ((cmd & CMD_INIT) && (state != SCSI_INIT)) {
    sim_debug (DBG_CMD, &rz_dev, "initiator cmd when not initiator\n");
    rz_setint (INT_ILLCMD);
    return;
    }

switch (cmd & 0x7f) {

    case 0:                                         /* NOP */
        sim_debug (DBG_CMD, &rz_dev, "NOP\n");
        if (cmd & 0x80) {                           /* DMA */
            rz_stat &= ~STS_TC;
            rz_txc = (rz_txi == 0) ? RZ_MAXFR : rz_txi;
            }
        break;

    case 1:                                         /* flush FIFO */
        sim_debug (DBG_CMD, &rz_dev, "flush fifo\n");
        rz_fifo_reset ();
        rz_int |= INT_FC;
        break;

    case 2:                                         /* reset chip */
        sim_debug (DBG_CMD, &rz_dev, "sw reset\n");
        rz_sw_reset ();
        break;

    case 3:                                         /* reset SCSI */
        sim_debug (DBG_CMD, &rz_dev, "SCSI reset\n");
        scsi_reset (&rz_bus);
        if ((rz_cfg1 & CFG1_SRD) == 0) {
            rz_setint (INT_SCSIRST);
            }
        break;

    case 0x20:                                      /* send message */
        sim_debug (DBG_CMD, &rz_dev, "send message\n");
        break;

    case 0x21:                                      /* send status */
        sim_debug (DBG_CMD, &rz_dev, "send status\n");
        break;

    case 0x22:                                      /* send data */
        sim_debug (DBG_CMD, &rz_dev, "send data\n");
        break;

    case 0x23:                                      /* disconnect sequence */
        sim_debug (DBG_CMD, &rz_dev, "disconnect sequence\n");
        break;

    case 0x24:                                      /* terminate sequence */
        sim_debug (DBG_CMD, &rz_dev, "terminate sequence\n");
        break;

    case 0x25:                                      /* target cmd complete sequence */
        sim_debug (DBG_CMD, &rz_dev, "target cmd complete sequence\n");
        break;

    case 0x27:                                      /* disconnect */
        sim_debug (DBG_CMD, &rz_dev, "disconnect\n");
        break;

    case 0x28:                                      /* rcv message seq */
        sim_debug (DBG_CMD, &rz_dev, "rcv message seq\n");
        break;

    case 0x29:                                      /* rcv cmd */
        sim_debug (DBG_CMD, &rz_dev, "rcv cmd\n");
        break;

    case 0x30:                                      /* rcv data */
        sim_debug (DBG_CMD, &rz_dev, "rcv data\n");
        break;

    case 0x31:                                      /* rcv cmd seq */
        sim_debug (DBG_CMD, &rz_dev, "rcv cmd seq\n");
        break;

    case 0x40:                                      /* reselect */
        sim_debug (DBG_CMD, &rz_dev, "reselect\n");
        break;

    case 0x41:                                          /* select without ATN */
        sim_debug (DBG_CMD, &rz_dev, "select without atn\n");
        rz_seq = 0;
        if (!scsi_arbitrate (&rz_bus, ini)) {
            rz_seq = 0;
            rz_int |= INT_DIS;                          /* disconnect */
            sim_activate (&rz_unit[8], 100);
            break;
            }
        if (!scsi_select (&rz_bus, tgt)) {
            rz_seq = 0;
            rz_int |= INT_DIS;                          /* disconnect */
            scsi_release (&rz_bus);
            sim_activate (&rz_unit[8], 100);
            break;
            }
        rz_seq = 2;
        for (i = 0; rz_fifo_c > 0; i++)
            rz_buf[i] = rz_fifo_rd ();
        scsi_write (&rz_bus, &rz_buf[0], i);
        if (scsi_state (&rz_bus, tgt) == SCSI_DISC) {
            rz_seq = 3;
            rz_int |= INT_DIS;
            }
        else {
            rz_seq = 4;
            rz_int |= (INT_BUSSV | INT_FC);
            }
        sim_activate (&rz_unit[8], 50);
        break;

    case 0x42:                                          /* select with ATN */
        sim_debug (DBG_CMD, &rz_dev, "select with atn\n");
        rz_seq = 0;
        if (!scsi_arbitrate (&rz_bus, ini)) {
            rz_int |= INT_DIS;                          /* disconnect */
            sim_activate (&rz_unit[8], 100);
            break;
            }
        scsi_set_atn (&rz_bus);
        if (!scsi_select (&rz_bus, tgt)) {
            rz_seq = 0;
            rz_int |= INT_DIS;                          /* disconnect */
            scsi_release (&rz_bus);
            sim_activate (&rz_unit[8], 100);
            break;
            }
        for (i = 0; rz_fifo_c > 0; i++)
            rz_buf[i] = rz_fifo_rd ();
        scsi_write (&rz_bus, &rz_buf[0], i);
        rz_seq = 2;
        if (scsi_state (&rz_bus, tgt) == SCSI_DISC) {
            rz_seq = 3;
            rz_int |= INT_DIS;
            }
        else {
            rz_seq = 4;
            rz_int |= (INT_BUSSV | INT_FC);
            }
        sim_activate (&rz_unit[8], 50);
        break;

    case 0x43:                                          /* select with ATN and stop */
        sim_debug (DBG_CMD, &rz_dev, "select with atn and stop\n");
        if (!scsi_arbitrate (&rz_bus, ini)) {
            rz_seq = 0;
            rz_int |= INT_DIS;                          /* disconnect */
            sim_activate (&rz_unit[8], 100);
            break;
            }
        scsi_set_atn (&rz_bus);
        if (!scsi_select (&rz_bus, tgt)) {
            rz_seq = 0;
            rz_int |= INT_DIS;                          /* disconnect */
            scsi_release (&rz_bus);
            sim_activate (&rz_unit[8], 100);
            break;
            }
        rz_buf[0] = rz_fifo_rd ();
        scsi_write (&rz_bus, &rz_buf[0], 1);            /* send one message byte */
        if (scsi_state (&rz_bus, tgt) == SCSI_DISC) {   /* disconnected? */
            rz_seq = 0;
            rz_int |= INT_DIS;
            }
        else {
            scsi_set_atn (&rz_bus);                     /* keep ATN asserted */
            rz_seq = 1;
            rz_int |= (INT_BUSSV | INT_FC);             /* continue */
            }
        sim_activate (&rz_unit[8], 50);
        break;

    case 0x44:                                          /* enable selection/reselection */
        sim_debug (DBG_CMD, &rz_dev, "enable selection/reselection\n");
        break;
    
    case 0x46:                                          /* select with ATN3 */
        sim_debug (DBG_CMD, &rz_dev, "select with atn3\n");
        scsi_set_atn (&rz_bus);
        if (!scsi_select (&rz_bus, tgt)) {
            rz_int |= INT_DIS;                          /* disconnect */
            scsi_release (&rz_bus);
            }
        sim_activate (&rz_unit[8], 50);
        break;

    case 0x1A:                                          /* set ATN */
        sim_debug (DBG_CMD, &rz_dev, "set atn\n");
        scsi_set_atn (&rz_bus);
        if (rz_bus.phase == 6) {                        /* TODO: check this */
            rz_int |= (INT_BUSSV | INT_FC);
            sim_activate (&rz_unit[8], 50);
            }
        break;
    
    case 0x1B:                                          /* reset ATN */
        sim_debug (DBG_CMD, &rz_dev, "reset atn\n");
        scsi_release_atn (&rz_bus);
        break;
    
    case 0x10:
        sim_debug (DBG_CMD, &rz_dev, "transfer information\n");
        if (cmd & 0x80) {                               /* DMA */
            rz_stat &= ~STS_TC;
            rz_txc = (rz_txi == 0) ? RZ_MAXFR : rz_txi;
            }
        old_phase = rz_bus.phase;
        switch (rz_bus.phase) {
            case SCSI_DATO:                             /* data out */
            case SCSI_CMD:                              /* command */
            case SCSI_MSGO:                             /* message out */
                if (rz_bus.phase == 6)
                    scsi_release_atn (&rz_bus);
                if (cmd & 0x80) {                       /* DMA */
                    RZ_READB (rz_dma, rz_txc, &rz_buf[0]);
                    txc = scsi_write (&rz_bus, &rz_buf[0], rz_txc);
                    rz_txc -= txc;
                    if (rz_txc == 0)
                        rz_stat |= STS_TC;
                    }
                else {
                    for (txc = 0; rz_fifo_c > 0; txc++)
                        rz_buf[txc] = rz_fifo_rd ();
                    txc = scsi_write (&rz_bus, &rz_buf[0], txc);
                    }
                break;
                
            case SCSI_DATI:                             /* data in */
            case SCSI_STS:                              /* status */
            case SCSI_MSGI:                             /* message in */
                if (cmd & 0x80) {                       /* DMA */
                    while ((rz_bus.phase == old_phase) && (rz_txc != 0)) {
                        txc = scsi_read (&rz_bus, &rz_buf[0], rz_txc);
                        RZ_WRITEB (rz_dma, txc, &rz_buf[0]);
                        rz_txc -= txc;
                        }
                    if (rz_txc == 0)
                        rz_stat |= STS_TC;
                    }
                else {
                    txc = scsi_read (&rz_bus, &rz_buf[0], 1);
                    rz_fifo_wr (rz_buf[0]);
                    }
                break;
                }
        rz_seq = 0;
        if (scsi_state (&rz_bus, tgt) == SCSI_DISC) {
            rz_int |= INT_DIS;
            }
        else {
            if (rz_bus.req)
                rz_int |= INT_BUSSV;
            if (rz_bus.phase == SCSI_MSGI)
                rz_int |= INT_FC;
            }
            sim_activate (&rz_unit[8], 50);
        break;

    case 0x11:
        sim_debug (DBG_CMD, &rz_dev, "initiator command complete\n");
        txc = 0;
        txc += scsi_read (&rz_bus, &rz_buf[0], 1);
        txc += scsi_read (&rz_bus, &rz_buf[1], 1);
        rz_fifo_wr (rz_buf[0]);
        rz_fifo_wr (rz_buf[1]);
        rz_seq = 0;
        rz_int |= INT_FC;
        sim_activate (&rz_unit[8], 50);
        break;
    
    case 0x12:
        sim_debug (DBG_CMD, &rz_dev, "message accepted\n");
        scsi_release (&rz_bus);
        rz_seq = 0;
        rz_int |= INT_DIS;
        sim_activate (&rz_unit[8], 50);
        break;

    case 0x18:
        sim_debug (DBG_CMD, &rz_dev, "transfer pad\n");
        if (cmd & 0x80) {                               /* DMA */
            rz_stat &= ~STS_TC;
            rz_txc = (rz_txi == 0) ? RZ_MAXFR : rz_txi;
            }
        old_phase = rz_bus.phase;
        switch (rz_bus.phase) {
            case SCSI_DATO:                             /* data out */
            case SCSI_CMD:                              /* command */
            case SCSI_MSGO:                             /* message out */
                if (rz_bus.phase == 6)
                    scsi_release_atn (&rz_bus);
                rz_buf[0] = 0;
                for (; ((rz_bus.phase == old_phase) && (rz_txc > 0)); rz_txc--)
                    scsi_write (&rz_bus, &rz_buf[0], 1);
                if (rz_txc == 0)
                    rz_stat |= STS_TC;
                break;
                
            case SCSI_DATI:                             /* data in */
            case SCSI_STS:                              /* status */
            case SCSI_MSGI:                             /* message in */
                for (; ((rz_bus.phase == old_phase) && (rz_txc > 0)); rz_txc--)
                    txc = scsi_read (&rz_bus, &rz_buf[0], 1);
                if (rz_txc == 0)
                    rz_stat |= STS_TC;
                break;
                }
        rz_seq = 0;
        if (scsi_state (&rz_bus, tgt) == SCSI_DISC)
            rz_int |= INT_DIS;
        else {
            if (rz_bus.req)
                rz_int |= INT_BUSSV;
            /* if (rz_bus.phase == SCSI_MSGI) */
                rz_int |= INT_FC;
            }
            sim_activate (&rz_unit[8], 50);
        break;

    default:
        sim_debug (DBG_CMD, &rz_dev, "unknown command %X\n", cmd);
        break;
        }
if (cmd > 0)
    rz_last_cmd = cmd;
}

void rz_sw_reset ()
{
uint32 i;

for (i = 0; i < 9; i++)
    sim_cancel (&rz_unit[i]);
rz_txc = 0;
rz_cfg1 = rz_cfg1 & 0x7;
rz_cfg2 = 0;
rz_cfg3 = 0;
rz_stat = 0;
rz_seq = 0;
rz_int = 0;
rz_dest = 0;
rz_fifo_reset ();
CLR_INT (SC);
scsi_reset (&rz_bus);
}

t_stat rz_reset (DEVICE *dptr)
{
uint32 i;
uint32 dtyp;
UNIT *uptr;
t_stat r;

if (rz_buf == NULL)
    rz_buf = (uint8 *)calloc (RZ_MAXFR, sizeof(uint8));
if (rz_buf == NULL)
    return SCPE_MEM;
r = scsi_init (&rz_bus, RZ_MAXFR);                      /* init SCSI bus */
if (r != SCPE_OK)
    return r;
rz_bus.dptr = dptr;                                     /* set bus device */
for (i = 0; i < 8; i++) {
    uptr = dptr->units + i;
    if (i == RZ_SCSI_ID)                                /* initiator ID? */
        uptr->flags = UNIT_DIS;                         /* disable unit */
    scsi_add_unit (&rz_bus, i, &rz_unit[i]);
    dtyp = GET_DTYPE (rz_unit[i].flags);
    scsi_set_unit (&rz_bus, &rz_unit[i], &rzdev_tab[dtyp]);
    scsi_reset_unit (&rz_unit[i]);
    }
rz_sw_reset ();
return SCPE_OK;
}

/* Set unit type (and capacity if user defined) */

t_stat rz_set_type (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
{
uint32 cap;
uint32 max = sim_toffset_64? RZU_EMAXC: RZU_MAXC;
t_stat r;

if ((val < 0) || ((val != RZU_DTYPE) && cptr))
    return SCPE_ARG;
if (uptr->flags & UNIT_ATT)
    return SCPE_ALATT;
if (cptr) {
    cap = (uint32) get_uint (cptr, 10, 0xFFFFFFFF, &r);
    if ((sim_switches & SWMASK ('L')) == 0)
        cap = cap * 1954;
    if ((r != SCPE_OK) || (cap < RZU_MINC) || (cap > max))
        return SCPE_ARG;
    rzdev_tab[val].lbn = cap;
    }
uptr->flags = (uptr->flags & ~UNIT_DTYPE) | (val << UNIT_V_DTYPE);
uptr->capac = (t_addr)rzdev_tab[val].lbn;
scsi_set_unit (&rz_bus, uptr, &rzdev_tab[val]);
scsi_reset_unit (uptr);
return SCPE_OK;
}

/* Show unit type */

t_stat rz_show_type (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
fprintf (st, "%s", rzdev_tab[GET_DTYPE (uptr->flags)].name);
return SCPE_OK;
}

t_stat rz_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "NCR 53C94 SCSI Controller (%s)\n\n", dptr->name);
fprintf (st, "The %s controller simulates the NCR 53C94 SCSI controller connected\n", dptr->name);
fprintf (st, "to a bus with up to 7 target devices.\n");
if (dptr->flags & DEV_DISABLE)
    fprintf (st, "Initially the %s controller is disabled.\n", dptr->name);
else
    fprintf (st, "The %s controller cannot be disabled.\n", dptr->name);
fprintf (st, "SCSI target device %s%d is reserved for the initiator and cannot\n", dptr->name, RZ_SCSI_ID);
fprintf (st, "be enabled\n");
fprintf (st, "Each target on the SCSI bus can be set to one of several types:\n");
fprint_set_help (st, dptr);
fprintf (st, "Configured options can be displayed with:\n\n");
fprint_show_help (st, dptr);
fprint_reg_help (st, dptr);
scsi_help (st, dptr, uptr, flag, cptr);
return SCPE_OK;
}

t_stat rz_attach (UNIT *uptr, CONST char *cptr)
{
return scsi_attach_ex (uptr, cptr, drv_types);
}

const char *rz_description (DEVICE *dptr)
{
return "NCR 53C94 SCSI controller";
}
