ncr5385: initial nscsi implementation

This commit is contained in:
Patrick Mackinlay 2024-03-11 19:00:43 +07:00
parent d1fefb196a
commit 47f8f4b286
4 changed files with 850 additions and 170 deletions

View File

@ -1,119 +1,783 @@
// license:BSD-3-Clause
// copyright-holders:Ryan Holtz
/***********************************************************************
NCR 5385E SCSI Controller
TOOD:
- Everything.
***********************************************************************/
/*
* NCR 5385 SCSI Protocol Controller
*
* Sources:
* - NCR 5385 SCSI Protocol Controller, 1983, NCR Corporation, Dayton, Ohio, USA
* - NCR SCSI Engineering Notebook, 1984, NCR Microelectronics
*
* TODO:
* - single byte transfer
* - target mode send/receive
* - disconnect/reselection
*/
#include "emu.h"
#include "ncr5385.h"
DEFINE_DEVICE_TYPE(NCR5385, ncr5385_device, "ncr5385", "NCR 5385E SCSI Controller")
#define LOG_GENERAL (1U << 0)
#define LOG_REGW (1U << 1)
#define LOG_REGR (1U << 2)
#define LOG_STATE (1U << 3)
#define LOG_DMA (1U << 4)
#define LOG_COMMAND (1U << 5)
ncr5385_device::ncr5385_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, NCR5385, tag, owner, clock)
//#define VERBOSE (LOG_GENERAL|LOG_REGW|LOG_REGR|LOG_STATE|LOG_DMA|LOG_COMMAND)
#include "logmacro.h"
DEFINE_DEVICE_TYPE(NCR5385, ncr5385_device, "ncr5385", "NCR 5385 SCSI Protocol Controller")
// FIXME: would be better to reuse from nscsi_full_device
static unsigned const SCSI_ARB_DELAY = 2'400;
static unsigned const SCSI_BUS_CLEAR = 800;
static unsigned const SCSI_BUS_FREE = 800;
static unsigned const SCSI_BUS_SETTLE = 400;
static unsigned const SCSI_BUS_SKEW = 10;
static unsigned const SCSI_RST_HOLD = 25'000;
ncr5385_device::ncr5385_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock)
: nscsi_device(mconfig, NCR5385, tag, owner, clock)
, nscsi_slot_card_interface(mconfig, *this, DEVICE_SELF)
, m_int(*this)
, m_dreq(*this)
, m_int_state(false)
, m_dreq_state(false)
{
}
enum state : u32
{
IDLE,
DIAGNOSTIC,
ARB_BUS_FREE,
ARB_START,
ARB_EVALUATE,
SEL_START,
SEL_DELAY,
SEL_WAIT_BSY,
SEL_COMPLETE,
SEL_WAIT_REQ,
XFI_START,
XFI_IN_REQ,
XFI_IN_DRQ,
XFI_IN_ACK,
XFI_OUT_REQ,
XFI_OUT_DRQ,
XFI_OUT_ACK,
XFI_OUT_PAD,
};
enum mode : u8
{
DISCONNECTED,
INITIATOR,
TARGET,
};
enum diag_mask : u8
{
DIAG_SELF = 0x07, // self-diagnostic status
DIAG_CMD = 0x38, // diagnostic command status
DIAG_DONE = 0x80, // self-diagnostic complete
DIAG_CMD_GP = 0x18, // diagnostic good parity
DIAG_CMD_BP = 0x20, // diagnostic bad parity
};
enum int_mask : u8
{
INT_FUNC_COMPLETE = 0x01,
INT_BUS_SERVICE = 0x02,
INT_DISCONNECTED = 0x04,
INT_SELECTED = 0x08,
INT_RESELECTED = 0x10,
INT_INVALID_CMD = 0x40,
};
enum aux_status_mask : u8
{
AUX_STATUS_TC_ZERO = 0x02,
AUX_STATUS_PAUSED = 0x04,
AUX_STATUS_IO = 0x08,
AUX_STATUS_CD = 0x10,
AUX_STATUS_MSG = 0x20,
AUX_STATUS_PARITY_ERR = 0x40,
AUX_STATUS_DATA_FULL = 0x80,
};
enum cmd_mask : u8
{
CMD_INT = 0x08, // interrupting
CMD_SBX = 0x40, // single byte transfer
CMD_DMA = 0x80, // DMA mode
};
void ncr5385_device::device_start()
{
save_item(NAME(m_dat));
save_item(NAME(m_cmd));
save_item(NAME(m_ctl));
save_item(NAME(m_dst_id));
save_item(NAME(m_aux_status));
save_item(NAME(m_own_id));
save_item(NAME(m_int_status));
save_item(NAME(m_src_id));
save_item(NAME(m_dia_status));
save_item(NAME(m_cnt));
save_item(NAME(m_state));
save_item(NAME(m_xfi_phase));
save_item(NAME(m_mode));
save_item(NAME(m_int_state));
save_item(NAME(m_dreq_state));
m_state_timer = timer_alloc(timer_expired_delegate(FUNC(ncr5385_device::state_timer), this));
m_dia_status = DIAG_DONE;
}
void ncr5385_device::device_reset()
{
m_state = STATE_IDLE;
m_int_reg = 0;
m_ctrl_reg = 0;
m_aux_status_reg = AUX_STATUS_TC_ZERO;
m_diag_status_reg = DIAG_COMPLETE;
m_cmd = 0;
m_ctl = 0;
m_dst_id = 0;
m_aux_status = AUX_STATUS_TC_ZERO;
m_int_status = 0;
m_src_id = 0;
m_dia_status &= (DIAG_DONE | DIAG_SELF);
m_cnt = 0;
m_state = IDLE;
m_mode = DISCONNECTED;
// monitor all control lines
scsi_bus->ctrl_wait(scsi_refid, S_ALL, S_ALL);
update_int();
}
void ncr5385_device::write(offs_t offset, uint8_t data)
void ncr5385_device::scsi_ctrl_changed()
{
switch (offset)
u32 const ctrl = scsi_bus->ctrl_r();
static char const *const nscsi_phase[] = { "DATA OUT", "DATA IN", "COMMAND", "STATUS", "*", "*", "MESSAGE OUT", "MESSAGE IN" };
if (ctrl & S_RST)
LOGMASKED(LOG_STATE, "scsi_ctrl_changed 0x%03x BUS RESET\n", ctrl);
else if ((ctrl & S_BSY) && !(ctrl & S_SEL))
LOGMASKED(LOG_STATE, "scsi_ctrl_changed 0x%03x phase %s%s%s\n", ctrl, nscsi_phase[ctrl & S_PHASE_MASK],
ctrl & S_REQ ? " REQ" : "", ctrl & S_ACK ? " ACK" : "");
else if (ctrl & S_BSY)
LOGMASKED(LOG_STATE, "scsi_ctrl_changed 0x%03x arbitration/selection\n", ctrl);
else
{
case 0x0: // Data Register
switch (m_state)
{
case STATE_DIAGNOSTIC_GOOD_PARITY:
m_aux_status_reg &= ~AUX_STATUS_PARITY_ERR;
m_aux_status_reg |= AUX_STATUS_DATA_FULL;
m_int_reg = INT_FUNC_COMPLETE;
m_diag_status_reg = DIAG_COMPLETE | DIAG_TURN_GOOD_PARITY;
m_state = STATE_IDLE;
m_int(1);
logerror("%s: ncr5385_w: data=%02x (diagnostic w/ good parity)\n", machine().describe_context(), data);
break;
case STATE_DIAGNOSTIC_BAD_PARITY:
m_aux_status_reg |= AUX_STATUS_PARITY_ERR | AUX_STATUS_DATA_FULL;
m_int_reg = INT_FUNC_COMPLETE;
m_diag_status_reg = DIAG_COMPLETE | DIAG_TURN_BAD_PARITY;
m_state = STATE_IDLE;
m_int(1);
logerror("%s: ncr5385_w: data=%02x (diagnostic w/ bad parity)\n", machine().describe_context(), data);
break;
default:
logerror("%s: ncr5385_w: data=%02x\n", machine().describe_context(), data);
break;
}
break;
case 0x1: // Command Register
switch (data & 0x3f)
{
case 0x00: // Chip Reset
logerror("%s: ncr5385_w: command: reset\n", machine().describe_context());
m_state = STATE_IDLE;
m_int_reg = 0;
m_aux_status_reg = AUX_STATUS_TC_ZERO;
m_diag_status_reg = DIAG_COMPLETE;
m_int(0);
break;
case 0x0b: // Diagnostic
logerror("%s: ncr5385_w: command: diagnostic (%s parity)\n", machine().describe_context(), BIT(data, 6) ? "bad" : "good");
if (BIT(data, 6))
m_state = STATE_DIAGNOSTIC_BAD_PARITY;
else
m_state = STATE_DIAGNOSTIC_GOOD_PARITY;
break;
default:
logerror("%s: ncr5385_w: command: %02x\n", machine().describe_context(), data);
break;
}
break;
case 0x2: // Control Register
m_ctrl_reg = data & 0x07;
logerror("%s: ncr5385_w: control: parity_en=%d, reselect_en=%d, select_en=%d\n", machine().describe_context(), BIT(data, CTRL_PARITY_BIT), BIT(data, CTRL_RESELECT_BIT), BIT(data, CTRL_SELECT_BIT));
break;
default:
logerror("%s: ncr5385_w: %x=%02x\n", machine().describe_context(), offset, data);
break;
LOGMASKED(LOG_STATE, "scsi_ctrl_changed 0x%03x BUS FREE\n", ctrl);
if (m_mode == INITIATOR)
{
m_mode = DISCONNECTED;
m_int_status |= INT_DISCONNECTED;
update_int();
}
}
}
uint8_t ncr5385_device::read(offs_t offset)
void ncr5385_device::map(address_map &map)
{
switch (offset)
map(0x0, 0x0).rw(FUNC(ncr5385_device::dat_r), FUNC(ncr5385_device::dat_w));
map(0x1, 0x1).rw(FUNC(ncr5385_device::cmd_r), FUNC(ncr5385_device::cmd_w));
map(0x2, 0x2).rw(FUNC(ncr5385_device::ctl_r), FUNC(ncr5385_device::ctl_w));
map(0x3, 0x3).rw(FUNC(ncr5385_device::dst_id_r), FUNC(ncr5385_device::dst_id_w));
map(0x4, 0x4).r(FUNC(ncr5385_device::aux_status_r));
map(0x5, 0x5).r(FUNC(ncr5385_device::own_id_r));
map(0x6, 0x6).r(FUNC(ncr5385_device::int_status_r));
map(0x7, 0x7).r(FUNC(ncr5385_device::src_id_r));
map(0x9, 0x9).r(FUNC(ncr5385_device::dia_status_r));
map(0xc, 0xc).rw(FUNC(ncr5385_device::cnt_r<2>), FUNC(ncr5385_device::cnt_w<2>));
map(0xd, 0xd).rw(FUNC(ncr5385_device::cnt_r<1>), FUNC(ncr5385_device::cnt_w<1>));
map(0xe, 0xe).rw(FUNC(ncr5385_device::cnt_r<0>), FUNC(ncr5385_device::cnt_w<0>));
map(0xf, 0xf).rw(FUNC(ncr5385_device::tst_r), FUNC(ncr5385_device::tst_w));
}
u8 ncr5385_device::dat_r()
{
if (m_aux_status & AUX_STATUS_DATA_FULL)
m_aux_status &= ~AUX_STATUS_DATA_FULL;
else
logerror("data register empty (%s)\n", machine().describe_context());
return m_dat;
}
u8 ncr5385_device::cmd_r()
{
return m_cmd;
}
u8 ncr5385_device::ctl_r()
{
return m_ctl;
}
u8 ncr5385_device::dst_id_r()
{
return m_dst_id;
}
u8 ncr5385_device::aux_status_r()
{
u8 data = m_aux_status;
if (!m_int_status)
{
case 0x2:
logerror("%s: ncr5385_r: control (%02x)\n", machine().describe_context(), m_ctrl_reg);
return m_ctrl_reg;
case 0x4:
logerror("%s: ncr5385_r: aux status (%02x)\n", machine().describe_context(), m_aux_status_reg);
return m_aux_status_reg;
case 0x6:
logerror("%s: ncr5385_r: interrupt (%02x)\n", machine().describe_context(), m_int_reg);
m_int(1);
return m_int_reg;
case 0x9:
logerror("%s: ncr5385_r: diagnostic status (%02x)\n", machine().describe_context(), m_diag_status_reg);
return m_diag_status_reg;
default:
logerror("%s: ncr5385_r: %x (%02x)\n", machine().describe_context(), offset, 0);
return 0;
// return current phase
u32 const ctrl = scsi_bus->ctrl_r();
if (ctrl & S_MSG)
data |= AUX_STATUS_MSG;
if (ctrl & S_CTL)
data |= AUX_STATUS_CD;
if (ctrl & S_INP)
data |= AUX_STATUS_IO;
}
LOGMASKED(LOG_REGR, "aux_status_r 0x%02x (%s)\n", data, machine().describe_context());
return data;
}
u8 ncr5385_device::own_id_r()
{
return m_own_id;
}
u8 ncr5385_device::int_status_r()
{
u8 const data = m_int_status;
LOGMASKED(LOG_REGR, "int_status_r 0x%02x (%s)\n", data, machine().describe_context());
m_aux_status &= ~AUX_STATUS_PARITY_ERR;
m_int_status = 0;
update_int();
if (m_state != IDLE)
m_state_timer->adjust(attotime::zero);
return data;
}
u8 ncr5385_device::src_id_r()
{
return m_src_id;
}
u8 ncr5385_device::dia_status_r()
{
return m_dia_status;
}
template <unsigned N> u8 ncr5385_device::cnt_r()
{
return u8(m_cnt >> (N * 8));
}
u8 ncr5385_device::tst_r()
{
return 0;
}
void ncr5385_device::dat_w(u8 data)
{
LOGMASKED(LOG_REGW, "dat_w 0x%02x (%s)\n", data, machine().describe_context());
if (!(m_aux_status & AUX_STATUS_DATA_FULL))
{
m_dat = data;
m_aux_status |= AUX_STATUS_DATA_FULL;
if (m_state != IDLE)
m_state_timer->adjust(attotime::zero);
}
else
logerror("data register full\n");
}
void ncr5385_device::cmd_w(u8 data)
{
LOGMASKED(LOG_REGW, "cmd_w 0x%02x (%s)\n", data, machine().describe_context());
if (!(data & 0x18))
{
// immediate commands
switch (data & 0x1f)
{
case 0x00:
LOGMASKED(LOG_COMMAND, "reset\n");
reset();
break;
case 0x01: // disconnect
LOGMASKED(LOG_COMMAND, "disconnect\n");
m_mode = DISCONNECTED;
break;
case 0x02: // pause
LOGMASKED(LOG_COMMAND, "pause\n");
break;
case 0x03: // set atn
LOGMASKED(LOG_COMMAND, "set atn\n");
scsi_bus->ctrl_w(scsi_refid, S_ATN, S_ATN);
break;
case 0x04: // message accepted
LOGMASKED(LOG_COMMAND, "message accepted\n");
scsi_bus->ctrl_w(scsi_refid, 0, S_ACK);
break;
case 0x05: // chip disabled
LOGMASKED(LOG_COMMAND, "chip disabled\n");
break;
case 0x06: case 0x07:
// reserved
break;
}
}
else
{
// interrupting commands
m_aux_status &= ~AUX_STATUS_DATA_FULL;
m_cmd = data;
switch (data & 0x1f)
{
case 0x08: // select w/atn
LOGMASKED(LOG_COMMAND, "select %d w/atn (timeout %d)\n", m_dst_id, attotime::from_ticks(m_cnt * 1024, clock()).to_string());
m_state = ARB_BUS_FREE;
m_state_timer->adjust(attotime::zero);
break;
case 0x09: // select w/o atn
LOGMASKED(LOG_COMMAND, "select %d w/o atn (timeout %d)\n", m_dst_id, attotime::from_ticks(m_cnt * 1024, clock()).to_string());
m_state = ARB_BUS_FREE;
m_state_timer->adjust(attotime::zero);
break;
case 0x0a: // reselect
LOGMASKED(LOG_COMMAND, "reselect\n");
break;
case 0x0b: // diagnostic
LOGMASKED(LOG_COMMAND, "diagnostic (%s parity)\n", BIT(data, 6) ? "bad" : "good");
m_state = DIAGNOSTIC;
break;
case 0x0c: // receive command
LOGMASKED(LOG_COMMAND, "receive command\n");
break;
case 0x0d: // receive data
LOGMASKED(LOG_COMMAND, "receive data\n");
break;
case 0x0e: // receive message out
LOGMASKED(LOG_COMMAND, "receive message out\n");
break;
case 0x0f: // receive unspecified info out
LOGMASKED(LOG_COMMAND, "receive unspecified info out\n");
break;
case 0x10: // send status
LOGMASKED(LOG_COMMAND, "send status\n");
break;
case 0x11: // send data
LOGMASKED(LOG_COMMAND, "send data\n");
break;
case 0x12: // send message in
LOGMASKED(LOG_COMMAND, "send message in\n");
break;
case 0x13: // send unspecified info in
LOGMASKED(LOG_COMMAND, "send unspecified info in\n");
break;
case 0x14: // transfer info
LOGMASKED(LOG_COMMAND, "transfer info (count=%d)\n", m_cnt);
m_state = XFI_START;
m_state_timer->adjust(attotime::zero);
break;
case 0x15: // transfer pad
LOGMASKED(LOG_COMMAND, "transfer pad (count=%d)\n", m_cnt);
m_state = XFI_START;
m_state_timer->adjust(attotime::zero);
break;
case 0x16: case 0x17:
case 0x18: case 0x19: case 0x1a: case 0x1b:
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
// reserved
break;
}
}
}
void ncr5385_device::ctl_w(u8 data)
{
LOGMASKED(LOG_REGW, "ctl_w 0x%02x (%s)\n", data, machine().describe_context());
m_ctl = data & 7;
}
void ncr5385_device::dst_id_w(u8 data)
{
LOGMASKED(LOG_REGW, "dst_id_w 0x%02x (%s)\n", data, machine().describe_context());
m_dst_id = (data & 7);
}
template <unsigned N> void ncr5385_device::cnt_w(u8 data)
{
m_cnt = (m_cnt & ~(u32(0xff) << (N * 8))) | (u32(data) << (N * 8));
}
void ncr5385_device::tst_w(u8 data)
{
LOGMASKED(LOG_REGW, "tst_w 0x%02x (%s)\n", data, machine().describe_context());
}
u8 ncr5385_device::dma_r()
{
u8 const data = m_dat;
m_aux_status &= ~AUX_STATUS_DATA_FULL;
set_dreq(false);
m_state_timer->adjust(attotime::zero);
return data;
}
void ncr5385_device::dma_w(u8 data)
{
m_dat = data;
m_aux_status |= AUX_STATUS_DATA_FULL;
set_dreq(false);
m_state_timer->adjust(attotime::zero);
}
void ncr5385_device::state_timer(s32 param)
{
// step state machine
int const delay = state_step();
// check for data stall
if (delay < 0)
return;
// repeat until idle
if (m_state != IDLE)
m_state_timer->adjust(attotime::from_nsec(delay));
}
int ncr5385_device::state_step()
{
u32 const ctrl = scsi_bus->ctrl_r();
int delay = 0;
u8 const oid = 1 << m_own_id;
u8 const tid = 1 << m_dst_id;
switch (m_state)
{
case DIAGNOSTIC:
m_dia_status &= DIAG_DONE | DIAG_SELF;
if (BIT(m_cmd, 6))
{
m_aux_status |= AUX_STATUS_PARITY_ERR;
m_dia_status |= DIAG_CMD_BP;
}
else
{
m_aux_status &= ~AUX_STATUS_PARITY_ERR;
m_dia_status |= DIAG_CMD_GP;
}
m_int_status |= INT_FUNC_COMPLETE;
m_state = IDLE;
update_int();
break;
case ARB_BUS_FREE:
LOGMASKED(LOG_STATE, "arbitration: waiting for bus free\n");
if (!(ctrl & (S_SEL | S_BSY | S_RST)))
{
m_state = ARB_START;
delay = SCSI_BUS_FREE;
}
break;
case ARB_START:
LOGMASKED(LOG_STATE, "arbitration: started\n");
m_state = ARB_EVALUATE;
delay = SCSI_ARB_DELAY;
// assert own ID and BSY
scsi_bus->data_w(scsi_refid, oid);
scsi_bus->ctrl_w(scsi_refid, S_BSY, S_BSY);
break;
case ARB_EVALUATE:
// check if SEL asserted, or if there's a higher ID on the bus
if ((ctrl & S_SEL) || (scsi_bus->data_r() & ~((oid - 1) | oid)))
{
LOGMASKED(LOG_STATE, "arbitration: lost\n");
m_state = ARB_BUS_FREE;
// clear data and BSY
scsi_bus->data_w(scsi_refid, 0);
scsi_bus->ctrl_w(scsi_refid, 0, S_BSY);
}
else
{
LOGMASKED(LOG_STATE, "arbitration: won\n");
m_state = SEL_START;
delay = SCSI_BUS_CLEAR + SCSI_BUS_SETTLE;
}
break;
case SEL_START:
LOGMASKED(LOG_STATE, "selection: SEL asserted\n");
m_state = SEL_DELAY;
delay = SCSI_BUS_SKEW * 2;
// assert own and target ID and SEL
scsi_bus->data_w(scsi_refid, oid | tid);
scsi_bus->ctrl_w(scsi_refid, S_SEL, S_SEL);
break;
case SEL_DELAY:
LOGMASKED(LOG_STATE, "selection: BSY cleared\n");
m_state = SEL_WAIT_BSY;
delay = SCSI_BUS_SETTLE;
// clear BSY, optionally assert ATN
if (!BIT(m_cmd, 0))
scsi_bus->ctrl_w(scsi_refid, S_ATN, S_BSY | S_ATN);
else
scsi_bus->ctrl_w(scsi_refid, 0, S_BSY);
break;
case SEL_WAIT_BSY:
if (ctrl & S_BSY)
{
LOGMASKED(LOG_STATE, "selection: BSY asserted by target\n");
m_state = SEL_COMPLETE;
delay = SCSI_BUS_SKEW * 2;
}
else
{
LOGMASKED(LOG_STATE, "selection: timed out\n");
m_int_status |= INT_DISCONNECTED;
m_state = IDLE;
scsi_bus->ctrl_w(scsi_refid, 0, S_ATN | S_SEL);
update_int();
}
break;
case SEL_COMPLETE:
LOGMASKED(LOG_STATE, "selection: complete\n");
m_int_status |= INT_FUNC_COMPLETE;
m_mode = INITIATOR;
m_state = SEL_WAIT_REQ;
delay = -1;
update_int();
// clear data and SEL
scsi_bus->data_w(scsi_refid, 0);
scsi_bus->ctrl_w(scsi_refid, 0, S_SEL);
break;
case SEL_WAIT_REQ:
if (ctrl & S_REQ)
{
LOGMASKED(LOG_STATE, "selection: REQ asserted by target\n");
m_int_status |= INT_BUS_SERVICE;
m_state = IDLE;
update_int();
}
else
delay = -1;
break;
case XFI_START:
m_xfi_phase = ctrl & S_PHASE_MASK;
m_state = (ctrl & S_INP) ? XFI_IN_REQ : XFI_OUT_REQ;
break;
case XFI_IN_REQ:
// TODO: single byte transfer, disconnect
if (ctrl & S_REQ)
{
if (m_cnt && (ctrl & S_PHASE_MASK) == m_xfi_phase)
{
m_state = XFI_IN_DRQ;
// no data transferred and no dma used by transfer pad command
if (!BIT(m_cmd, 0))
{
m_aux_status |= AUX_STATUS_DATA_FULL;
m_dat = scsi_bus->data_r();
if (m_cmd & CMD_DMA)
set_dreq(true);
delay = -1;
}
}
else
{
LOGMASKED(LOG_STATE, "xfi_in: %s\n", m_cnt ? "phase change" : "transfer complete");
m_int_status |= INT_BUS_SERVICE;
m_state = IDLE;
update_int();
}
}
break;
case XFI_IN_DRQ:
m_state = XFI_IN_ACK;
LOGMASKED(LOG_STATE, "xfi_in: data 0x%02x\n", m_dat);
// assert ACK
scsi_bus->ctrl_w(scsi_refid, S_ACK, S_ACK);
break;
case XFI_IN_ACK:
if (!(ctrl & S_REQ))
{
m_state = XFI_IN_REQ;
if ((m_cmd & CMD_DMA) && !(m_cmd & CMD_SBX))
{
m_cnt--;
LOGMASKED(LOG_DMA, "xfi_in: %d remaining\n", m_cnt);
if (!m_cnt)
m_aux_status |= AUX_STATUS_TC_ZERO;
}
// clear ACK except after last byte of message input phase
if ((m_cnt == 0) && (ctrl & S_PHASE_MASK) == S_PHASE_MSG_IN)
{
m_int_status |= INT_FUNC_COMPLETE;
m_state = IDLE;
update_int();
}
else
scsi_bus->ctrl_w(scsi_refid, 0, S_ACK);
}
break;
case XFI_OUT_REQ:
if (ctrl & S_REQ)
{
// TODO: single byte transfer, disconnect
if (m_cnt && (ctrl & S_PHASE_MASK) == m_xfi_phase)
{
m_state = XFI_OUT_DRQ;
if (m_cmd & CMD_DMA)
set_dreq(true);
delay = -1;
}
else
{
LOGMASKED(LOG_STATE, "xfi_out: %s\n", m_cnt ? "phase change" : "transfer complete");
m_int_status |= INT_BUS_SERVICE;
m_state = IDLE;
update_int();
}
}
break;
case XFI_OUT_DRQ:
m_state = XFI_OUT_ACK;
m_aux_status &= ~AUX_STATUS_DATA_FULL;
LOGMASKED(LOG_STATE, "xfi_out: data 0x%02x\n", m_dat);
// assert data and ACK
scsi_bus->data_w(scsi_refid, m_dat);
if ((m_cnt == 1) && (ctrl & S_PHASE_MASK) == S_PHASE_MSG_OUT)
scsi_bus->ctrl_w(scsi_refid, S_ACK, S_ACK | S_ATN);
else
scsi_bus->ctrl_w(scsi_refid, S_ACK, S_ACK);
break;
case XFI_OUT_ACK:
if (!(ctrl & S_REQ))
{
m_state = BIT(m_cmd, 0) ? XFI_OUT_PAD : XFI_OUT_REQ;
if ((m_cmd & CMD_DMA) && !(m_cmd & CMD_SBX))
{
m_cnt--;
LOGMASKED(LOG_DMA, "xfi_out: %d remaining\n", m_cnt);
if (!m_cnt)
m_aux_status |= AUX_STATUS_TC_ZERO;
}
// clear data and ACK
scsi_bus->data_w(scsi_refid, 0);
scsi_bus->ctrl_w(scsi_refid, 0, S_ACK);
}
break;
case XFI_OUT_PAD:
if (ctrl & S_REQ)
{
// TODO: single byte transfer, disconnect
if (m_cnt && (ctrl & S_PHASE_MASK) == m_xfi_phase)
m_state = XFI_OUT_DRQ;
else
{
LOGMASKED(LOG_STATE, "xfi_out: %s\n", m_cnt ? "phase change" : "transfer complete");
m_int_status |= INT_BUS_SERVICE;
m_state = IDLE;
update_int();
}
}
break;
}
return delay;
}
void ncr5385_device::set_dreq(bool dreq)
{
if (m_dreq_state != dreq)
{
LOGMASKED(LOG_DMA, "set_dreq %d\n", dreq);
m_dreq_state = dreq;
m_dreq(m_dreq_state);
}
}
void ncr5385_device::update_int()
{
bool const int_state = m_int_status & 0x5f;
if (m_int_state != int_state)
{
LOG("update_int %d\n", int_state);
m_aux_status &= ~(AUX_STATUS_MSG | AUX_STATUS_CD | AUX_STATUS_IO);
if (int_state)
{
m_cmd = 0;
// latch current phase
u32 const ctrl = scsi_bus->ctrl_r();
if (ctrl & S_MSG)
m_aux_status |= AUX_STATUS_MSG;
if (ctrl & S_CTL)
m_aux_status |= AUX_STATUS_CD;
if (ctrl & S_INP)
m_aux_status |= AUX_STATUS_IO;
}
m_int_state = int_state;
m_int(m_int_state);
}
}

View File

@ -4,11 +4,6 @@
NCR 5385 SCSI Controller emulation
TODO:
- Everything. Currently, just enough is implemented to make the
Philips VP415 CPU / Datagrabber board satisfied that the
controller has passed its internal diagnostics.
************************************************************************
_____ _____
D2 1 |* \_/ | 48 VCC
@ -43,91 +38,87 @@
#pragma once
#include "machine/nscsi_bus.h"
//**************************************************************************
// TYPE DEFINITIONS
//**************************************************************************
// ======================> ncr5385_device
class ncr5385_device : public device_t
class ncr5385_device
: public nscsi_device
, public nscsi_slot_card_interface
{
public:
// construction/destruction
ncr5385_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
auto irq() { return m_int.bind(); }
auto dreq() { return m_dreq.bind(); }
void write(offs_t offset, uint8_t data);
uint8_t read(offs_t offset);
void set_own_id(unsigned id) { m_own_id = id; }
ncr5385_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock);
void map(address_map &map);
u8 dma_r();
void dma_w(u8 data);
protected:
// device-level overrides
// device_t implementation
virtual void device_start() override;
virtual void device_reset() override;
// ncsci_device implementation
virtual void scsi_ctrl_changed() override;
// read handlers
u8 dat_r();
u8 cmd_r();
u8 ctl_r();
u8 dst_id_r();
u8 aux_status_r();
u8 own_id_r();
u8 int_status_r();
u8 src_id_r();
u8 dia_status_r();
template <unsigned N> u8 cnt_r();
u8 tst_r();
// write handlers
void dat_w(u8 data);
void cmd_w(u8 data);
void ctl_w(u8 data);
void dst_id_w(u8 data);
template <unsigned N> void cnt_w(u8 data);
void tst_w(u8 data);
// state machine, interrupts and dma
void state_timer(s32 param);
int state_step();
void update_int();
void set_dreq(bool dreq);
private:
enum
{
STATE_IDLE,
STATE_DIAGNOSTIC_GOOD_PARITY,
STATE_DIAGNOSTIC_BAD_PARITY,
};
enum
{
DIAG_TURN_MISCOMPARE_INITIAL = 0x08,
DIAG_TURN_MISCOMPARE_FINAL = 0x10,
DIAG_TURN_GOOD_PARITY = 0x18,
DIAG_TURN_BAD_PARITY = 0x20,
DIAG_COMPLETE = 0x80,
DIAG_COMPLETE_BIT = 7,
};
enum
{
INT_FUNC_COMPLETE = 0x01,
INT_INVALID_CMD = 0x40,
INT_FUNC_COMPLETE_BIT = 0,
INT_INVALID_CMD_BIT = 6,
};
enum
{
AUX_STATUS_TC_ZERO = 0x02,
AUX_STATUS_PAUSED = 0x04,
AUX_STATUS_PARITY_ERR = 0x40,
AUX_STATUS_DATA_FULL = 0x80,
AUX_STATUS_TC_ZERO_BIT = 1,
AUX_STATUS_PAUSED_BIT = 2,
AUX_STATUS_PARITY_ERR_BIT = 6,
AUX_STATUS_DATA_FULL_BIT = 7,
};
enum
{
CTRL_SELECT = 0x01,
CTRL_RESELECT = 0x02,
CTRL_PARITY = 0x04,
CTRL_SELECT_BIT = 0,
CTRL_RESELECT_BIT = 1,
CTRL_PARITY_BIT = 2,
};
devcb_write_line m_int;
devcb_write_line m_dreq;
uint32_t m_state;
uint8_t m_ctrl_reg;
uint8_t m_int_reg;
uint8_t m_aux_status_reg;
uint8_t m_diag_status_reg;
emu_timer *m_state_timer;
// registers
u8 m_dat;
u8 m_cmd;
u8 m_ctl;
u8 m_dst_id;
u8 m_aux_status;
u8 m_own_id;
u8 m_int_status;
u8 m_src_id;
u8 m_dia_status;
u32 m_cnt;
// other state
u32 m_state;
u8 m_xfi_phase;
u8 m_mode;
bool m_int_state;
bool m_dreq_state;
};
// device type definition
DECLARE_DEVICE_TYPE(NCR5385, ncr5385_device)
#endif // MAME_MACHINE_NCR5385_H

View File

@ -421,7 +421,7 @@ void vp415_state::z80_program_map(address_map &map)
void vp415_state::z80_io_map(address_map &map)
{
map.global_mask(0xff);
map(0x00, 0x0f).rw(SCSI_TAG, FUNC(ncr5385_device::read), FUNC(ncr5385_device::write));
map(0x00, 0x0f).m(SCSI_TAG, FUNC(ncr5385_device::map));
// 0x20, 0x21: Connected to A0 + D0..D7 of SLAVE i8041
map(0x34, 0x34).w(FUNC(vp415_state::sel34_w));
map(0x37, 0x37).r(FUNC(vp415_state::sel37_r));

View File

@ -51,6 +51,8 @@
#include "machine/mos6551.h" // debug tty
#include "machine/mc146818.h"
#include "machine/mc68681.h"
#include "machine/nscsi_bus.h"
#include "bus/nscsi/hd.h"
#include "machine/ncr5385.h"
#include "tek410x_kbd.h"
#include "sound/sn76496.h"
@ -72,6 +74,8 @@ public:
m_duart(*this, "duart"),
m_keyboard(*this, "keyboard"),
m_snsnd(*this, "snsnd"),
m_rtc(*this, "rtc"),
m_scsi(*this, "scsi:7:ncr5385"),
m_prom(*this, "maincpu"),
m_mainram(*this, "mainram"),
m_vram(*this, "vram"),
@ -115,6 +119,8 @@ private:
required_device<mc68681_device> m_duart;
required_device<tek410x_keyboard_device> m_keyboard;
required_device<sn76496_device> m_snsnd;
required_device<mc146818_device> m_rtc;
required_device<ncr5385_device> m_scsi;
required_region_ptr<u16> m_prom;
required_shared_ptr<u16> m_mainram;
required_shared_ptr<u16> m_vram;
@ -326,8 +332,8 @@ void tek440x_state::physical_map(address_map &map)
// 7b6000-7b7fff: Mouse
map(0x7b8000, 0x7b8003).mirror(0x100).rw("timer", FUNC(am9513_device::read16), FUNC(am9513_device::write16));
// 7ba000-7bbfff: MC146818 RTC
// 7bc000-7bdfff: SCSI bus address registers
map(0x7be000, 0x7be01f).mirror(0x1fe0).rw("scsic", FUNC(ncr5385_device::read), FUNC(ncr5385_device::write)).umask16(0xff00).cswidth(16);
map(0x7bc000, 0x7bc000).lw8([this](u8 data) { m_scsi->set_own_id(data & 7); }, "scsi_addr"); // 7bc000-7bdfff: SCSI bus address registers
map(0x7be000, 0x7be01f).m(m_scsi, FUNC(ncr5385_device::map)).umask16(0xff00); //.mirror(0x1fe0) .cswidth(16);
}
void tek440x_state::fdccpu_map(address_map &map)
@ -351,6 +357,11 @@ INPUT_PORTS_END
*
*************************************/
static void scsi_devices(device_slot_interface &device)
{
device.option_add("harddisk", NSCSI_HARDDISK);
}
void tek440x_state::tek4404(machine_config &config)
{
/* basic machine hardware */
@ -391,9 +402,23 @@ void tek440x_state::tek4404(machine_config &config)
AM9513(config, "timer", 40_MHz_XTAL / 4 / 10); // from CPU E output
//MC146818(config, "calendar", 32.768_MHz_XTAL);
MC146818(config, m_rtc, 32.768_MHz_XTAL);
NCR5385(config, "scsic", 40_MHz_XTAL / 4).irq().set_inputline(m_maincpu, M68K_IRQ_3);
NSCSI_BUS(config, "scsi");
NSCSI_CONNECTOR(config, "scsi:0", scsi_devices, "harddisk");
NSCSI_CONNECTOR(config, "scsi:1", scsi_devices, nullptr);
NSCSI_CONNECTOR(config, "scsi:2", scsi_devices, nullptr);
NSCSI_CONNECTOR(config, "scsi:3", scsi_devices, nullptr);
NSCSI_CONNECTOR(config, "scsi:4", scsi_devices, nullptr);
NSCSI_CONNECTOR(config, "scsi:5", scsi_devices, nullptr);
NSCSI_CONNECTOR(config, "scsi:6", scsi_devices, nullptr);
NSCSI_CONNECTOR(config, "scsi:7").option_set("ncr5385", NCR5385).clock(40_MHz_XTAL / 4).machine_config(
[this](device_t *device)
{
ncr5385_device &adapter = downcast<ncr5385_device &>(*device);
adapter.irq().set_inputline(m_maincpu, M68K_IRQ_3);
});
rs232_port_device &rs232(RS232_PORT(config, "rs232", default_rs232_devices, nullptr));
rs232.rxd_handler().set("aica", FUNC(mos6551_device::write_rxd));