// license:BSD-3-Clause
// copyright-holders:Barry Rodewald
/*
* Fujitsu MB89352A SCSI Protocol Controller
*
* Should be compatible with the MB87030/31, and MB89351
*
* Used on the Sharp X68000 Super, X68000 XVI and X68030 (internal), and on SCSI expansion cards for any X680x0 (external)
* Also used in Sega's Soreike Kokology 1/2 (drives CD-ROM)
*
* Registers (based on datasheet):
*
* 0: BDID (Bus Device ID)
* bit = device ID (ie: bit 2 = device 2) (read)
* bit 0 = ID1, bit 1 = ID2, bit 2 = ID4 (write)
*
* 1: SCTL (SPC Control)
* (all read/write)
* bit 0 = INT Enable
* bit 1 = Reselect Enable
* bit 2 = Select Enable
* bit 3 = Parity Enable
* bit 4 = Arbitration Phase Enable
* bit 5 = Diag Mode
* bit 6 = Control Reset
* bit 7 = Reset and Disable
*
* 2: SCMD (Command)
* bits 0-2 = Transfer Modifier (read)
* bit 0 = Term Mode (write)
* bit 2 = PRG Xfer (write)
* bit 3 = Intercept Xfer (read/write)
* bit 4 = RST Out (read/write)
* bits 5-7 = Command code (read/write) (would have been nice if these codes were mentioned in the datasheet...)
* 000 = Bus release
* 001 = Selection
* 010 = Reset ATN
* 011 = Set ATN
* 100 = Transfer
* 101 = Transfer Pause
* 110 = Reset ACK/REQ
* 111 = Set ACK/REQ
*
* 3: Unused
*
* 4: INTS (Interrupt Sense)
* on write, clear interrupt
* on read:
* bit 0 = Reset Condition
* bit 1 = SPC Hard Error
* bit 2 = Timeout
* bit 3 = Service Required
* bit 4 = Command Complete
* bit 5 = Disconnect
* bit 6 = Reselected
* bit 7 = Selected
*
* 5: PSNS (Phase Sense) (read-only)
* b7 b0
* | REQ | ACK | ATN | SEL | BSY | MSG | C/D | I/O |
* SDGC (SPC Diag. Control) (write-only)
* bit 5 = Xfer Enable
* bit 4 = Unused
* all other bits are Diag. of the matching inputs above.
*
* 6: SSTS (SPC Status) (read-only)
* bit 0 = DREG Empty
* bit 1 = DREG Full
* bit 2 = TC=0
* bit 3 = SCSI RST
* bit 4 = Xfer in progress
* bit 5 = SPC BSY
* bit 6 = Connected to Target
* bit 7 = Connected to Initiator
*
* 7: SERR (SCSI Error Status) (read-only)
* bit 1 = Short Transfer Period
* bit 3 = TC Parity Error
* bit 5 = Xfer Out (related to SDGC bit 5)
* bits 6,7 = Data error (10 = undefined, 00 = no error, 01 and 11 = parity error)
*
* 8: PCTL (Phase Control)
* (read/write)
* bits 0-2 = transfer phase ( | MSG | C/D | I/O | )
* bits 7 = Bus Free interrupt enable
* other bits read 0
*
* 9: MBC (Modified Byte Counter)
* (read-only)
* bits 0-3 = MBC bits 0-3
* other bits read 0
*
* A: DREG (Data Register)
* 8-bit FIFO
*
* B: TEMP (Temporary Register)
* on read, from SCSI
* on write, to SCSI
*
* C, D, E: TCH, TCM, TCL (Transfer Counter High, Mid, Low)
* 24-bit transfer counter
*
*/
#include "emu.h"
#include "mb89352.h"
// SCSI lines readable via PSNS register (reg 5)
#define MB89352_LINE_REQ 0x80
#define MB89352_LINE_ACK 0x40
#define MB89352_LINE_ATN 0x20
#define MB89352_LINE_SEL 0x10
#define MB89352_LINE_BSY 0x08
#define MB89352_LINE_MSG 0x04
#define MB89352_LINE_CD 0x02
#define MB89352_LINE_IO 0x01
// INTS bits
#define INTS_RESET 0x01
#define INTS_HARD_ERROR 0x02
#define INTS_TIMEOUT 0x04
#define INTS_SERVICE_REQUIRED 0x08
#define INTS_COMMAND_COMPLETE 0x10
#define INTS_DISCONNECTED 0x20
#define INTS_RESELECTION 0x40
#define INTS_SELECTION 0x80
// SSTS status bits
#define SSTS_DREG_EMPTY 0x01
#define SSTS_DREG_FULL 0x02
#define SSTS_TC_ZERO 0x04
#define SSTS_SCSI_RST 0x08
#define SSTS_XFER_IN_PROGRESS 0x10
#define SSTS_SPC_BSY 0x20
#define SSTS_TARG_CONNECTED 0x40
#define SSTS_INIT_CONNECTED 0x80
// SERR error status bits
#define SERR_OFFSET 0x01
#define SERR_SHORT_XFR 0x02
#define SERR_PHASE_ERR 0x04
#define SERR_TC_PAR 0x08
#define SERR_SPC_PAR 0x40
#define SERR_SCSI_PAR 0x80
/*
* Device config
*/
DEFINE_DEVICE_TYPE(MB89352A, mb89352_device, "mb89352", "Fujitsu MB89352A")
/*
* Device
*/
mb89352_device::mb89352_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
legacy_scsi_host_adapter(mconfig, MB89352A, tag, owner, clock),
m_irq_cb(*this),
m_drq_cb(*this)
{
}
void mb89352_device::device_start()
{
legacy_scsi_host_adapter::device_start();
m_phase = SCSI_PHASE_BUS_FREE;
m_target = 0;
m_command_index = 0;
m_line_status = 0x00;
m_spc_status = 0x01; // presumably the data reg is empty to start with
m_error_status = 0x00;
m_transfer_count = 0;
if(m_transfer_count == 0)
m_spc_status |= SSTS_TC_ZERO;
m_ints = 0x00;
m_irq_cb.resolve_safe();
m_drq_cb.resolve_safe();
// allocate read timer
m_transfer_timer = timer_alloc(TIMER_TRANSFER);
}
void mb89352_device::device_reset()
{
m_phase = SCSI_PHASE_BUS_FREE;
m_target = 0;
m_command_index = 0;
m_line_status = 0x00;
m_error_status = 0x00;
m_transfer_count = 0;
m_spc_status = 0x05; // presumably the data reg is empty to start with
m_busfree_int_enable = 0;
}
void mb89352_device::device_stop()
{
}
// get the length of a SCSI command based on it's command byte type
int mb89352_device::get_scsi_cmd_len(uint8_t cbyte)
{
int group;
group = (cbyte>>5) & 7;
if (group == 0) return 6;
if (group == 1 || group == 2) return 10;
if (group == 5) return 12;
fatalerror("MB89352: Unknown SCSI command group %d\n", group);
// never executed
//return 6;
}
void mb89352_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch(id)
{
case TIMER_TRANSFER:
// TODO: check interrupts are actually enabled
{
m_drq_cb(1);
}
break;
}
}
void mb89352_device::set_phase(int phase)
{
m_phase = phase;
switch(phase)
{
case SCSI_PHASE_BUS_FREE:
m_line_status = 0;
m_spc_status &= ~SSTS_XFER_IN_PROGRESS;
break;
case SCSI_PHASE_COMMAND:
m_line_status |= MB89352_LINE_REQ;
m_line_status &= ~MB89352_LINE_ACK;
m_line_status &= ~MB89352_LINE_MSG;
m_line_status |= MB89352_LINE_CD;
m_line_status &= ~MB89352_LINE_IO;
break;
case SCSI_PHASE_STATUS:
// m_line_status |= MB89352_LINE_REQ;
// m_line_status &= ~MB89352_LINE_ACK;
m_line_status &= ~MB89352_LINE_MSG;
m_line_status |= MB89352_LINE_CD;
m_line_status |= MB89352_LINE_IO;
break;
case SCSI_PHASE_DATAIN:
// m_line_status |= MB89352_LINE_REQ;
// m_line_status &= ~MB89352_LINE_ACK;
m_line_status &= ~MB89352_LINE_MSG;
m_line_status &= ~MB89352_LINE_CD;
m_line_status |= MB89352_LINE_IO;
break;
case SCSI_PHASE_DATAOUT:
// m_line_status |= MB89352_LINE_REQ;
// m_line_status &= ~MB89352_LINE_ACK;
m_line_status &= ~MB89352_LINE_MSG;
m_line_status &= ~MB89352_LINE_CD;
m_line_status &= ~MB89352_LINE_IO;
break;
case SCSI_PHASE_MESSAGE_IN:
// m_line_status |= MB89352_LINE_REQ;
// m_line_status &= ~MB89352_LINE_ACK;
m_line_status |= MB89352_LINE_MSG;
m_line_status |= MB89352_LINE_CD;
m_line_status |= MB89352_LINE_IO;
break;
case SCSI_PHASE_MESSAGE_OUT:
// m_line_status |= MB89352_LINE_REQ;
// m_line_status &= ~MB89352_LINE_ACK;
m_line_status |= MB89352_LINE_MSG;
m_line_status |= MB89352_LINE_CD;
m_line_status &= ~MB89352_LINE_IO;
break;
}
logerror("MB89352: phase set to %i\n",m_phase);
}
READ8_MEMBER( mb89352_device::mb89352_r )
{
uint8_t ret;
switch(offset & 0x0f)
{
case 0x00: // BDID - Bus Device ID
return (1 << m_bdid);
case 0x01: // SCTL - SPC Control
ret = 0x00;
if(m_arbit_enable)
ret |= 0x10;
if(m_parity_enable)
ret |= 0x08;
if(m_sel_enable)
ret |= 0x04;
if(m_resel_enable)
ret |= 0x02;
if(m_int_enable)
ret |= 0x01;
return ret;
case 0x02: // SCMD - Command
return m_scmd;
case 0x03: // Unused
return 0xff;
case 0x04: // INTS - Interrupt Sense
return m_ints;
case 0x05: // PSNS - Phase Sense
return m_line_status; // active low -- but Human68k expects it to be zero?
case 0x06: // SSTS - SPC Status
return m_spc_status;
case 0x07: // SERR - SPC Error Status
/* #define SERR_SCSI_PAR 0x80
#define SERR_SPC_PAR 0x40
#define SERR_TC_PAR 0x08
#define SERR_PHASE_ERR 0x04
#define SERR_SHORT_XFR 0x02
#define SERR_OFFSET 0x01*/
return 0;
case 0x08: // PCTL - Phase Control
return ((m_busfree_int_enable) ? (m_line_status & 0x07) | 0x80 : (m_line_status & 0x07));
case 0x0a: // DREG - Data register (for data transfers)
if(m_spc_status & SSTS_XFER_IN_PROGRESS)
{
m_data = m_buffer[m_transfer_index % 512];
m_transfer_index++;
m_transfer_count--;
if(m_transfer_index % 512 == 0)
read_data(m_buffer, 512);
if(m_transfer_count == 0)
{
// End of transfer
m_spc_status &= ~SSTS_XFER_IN_PROGRESS;
m_spc_status |= SSTS_DREG_EMPTY;
m_ints |= INTS_COMMAND_COMPLETE;
if(m_int_enable != 0)
m_irq_cb(1);
if(m_phase == SCSI_PHASE_MESSAGE_IN)
set_phase(SCSI_PHASE_BUS_FREE);
else if(m_phase == SCSI_PHASE_DATAIN)
set_phase(SCSI_PHASE_STATUS);
}
}
return m_data;
case 0x0b: // TEMP - Temporary
logerror("mb89352: read temporary register.\n");
return m_temp;
case 0x0c: // TCH - Transfer Counter High
return (m_transfer_count & 0x00ff0000) >> 16;
case 0x0d: // TCM - Transfer Counter Mid
return (m_transfer_count & 0x0000ff00) >> 8;
case 0x0e: // TCL - Transfer Counter Low
return (m_transfer_count & 0x000000ff);
default:
logerror("mb89352: read from register %02x\n",offset & 0x0f);
}
return 0xff;
}
WRITE8_MEMBER( mb89352_device::mb89352_w )
{
switch(offset & 0x0f)
{
case 0x00: // BDID - Bus Device ID
m_bdid = data;
m_spc_status &= ~SSTS_TARG_CONNECTED;
m_spc_status |= SSTS_INIT_CONNECTED;
logerror("mb89352: BDID set to %i\n",data);
break;
case 0x01: // SCTL - SPC Control
if(data & 0x80) // reset and disable
{
device_reset();
logerror("mb89352: SCTL: Reset and disable.\n");
}
if(data & 0x10)
{
m_arbit_enable = 1;
logerror("mb89352: SCTL: Arbitration enabled.\n");
}
else
m_arbit_enable = 0;
if(data & 0x08)
{
m_parity_enable = 1;
logerror("mb89352: SCTL: Parity enabled.\n");
}
else
m_parity_enable = 0;
if(data & 0x04)
{
m_sel_enable = 1;
logerror("mb89352: SCTL: Selection enabled.\n");
}
else
m_sel_enable = 0;
if(data & 0x02)
{
m_resel_enable = 1;
logerror("mb89352: SCTL: Reselection enabled.\n");
}
else
m_resel_enable = 0;
if(data & 0x01)
{
m_int_enable = 1;
logerror("mb89352: SCTL: Interrupts enabled.\n");
}
else
{
m_int_enable = 0;
logerror("mb89352: SCTL: Interrupts disabled.\n");
}
break;
case 0x02: // SCMD - Command
/* From NetBSD/x68k source
#define SCMD_BUS_REL 0x00
#define SCMD_SELECT 0x20
#define SCMD_RST_ATN 0x40
#define SCMD_SET_ATN 0x60
#define SCMD_XFR 0x80
#define SCMD_XFR_PAUSE 0xa0
#define SCMD_RST_ACK 0xc0
#define SCMD_SET_ACK 0xe0
*/
m_scmd = data;
switch((data & 0xe0) >> 5)
{
case 0x00:
// Bus Free
m_line_status = 0;
m_spc_status &= ~SSTS_TARG_CONNECTED;
m_spc_status &= ~SSTS_INIT_CONNECTED;
m_spc_status &= ~SSTS_XFER_IN_PROGRESS;
set_phase(SCSI_PHASE_BUS_FREE);
if(m_busfree_int_enable)
{
if(m_int_enable != 0)
m_irq_cb(1);
}
logerror("mb89352: SCMD: Bus free\n");
break;
case 0x01:
// Selection
m_target = m_temp;
m_target &= ~(1 << m_bdid); // mask off the bit relating to initiator
switch(m_target)
{
case 0x01: m_target = 0; break;
case 0x02: m_target = 1; break;
case 0x04: m_target = 2; break;
case 0x08: m_target = 3; break;
case 0x10: m_target = 4; break;
case 0x20: m_target = 5; break;
case 0x40: m_target = 6; break;
case 0x80: m_target = 7; break;
}
if(m_sel_enable != 0)
{
//m_ints |= INTS_SELECTION;
}
select(m_target);
set_phase(SCSI_PHASE_COMMAND); // straight to command phase, may need a delay between selection and command phases
m_line_status |= MB89352_LINE_SEL;
m_line_status |= MB89352_LINE_BSY;
m_spc_status &= ~SSTS_TARG_CONNECTED;
m_spc_status |= SSTS_INIT_CONNECTED;
m_spc_status |= SSTS_SPC_BSY;
m_ints |= INTS_COMMAND_COMPLETE;
if(m_int_enable != 0)
m_irq_cb(1);
logerror("mb89352: SCMD: Selection (SCSI ID%i)\n",m_target);
break;
case 0x02: // Reset ATN
m_line_status &= ~MB89352_LINE_ATN;
logerror("mb89352: SCMD: Reset ATN\n");
break;
case 0x03: // Set ATN
m_line_status |= MB89352_LINE_ATN;
logerror("mb89352: SCMD: Set ATN\n");
break;
case 0x04: // Transfer
m_transfer_index = 0;
m_spc_status |= SSTS_XFER_IN_PROGRESS;
if(m_phase == SCSI_PHASE_DATAIN) // if we are reading data...
{
m_spc_status &= ~SSTS_DREG_EMPTY; // DREG is no longer empty
logerror("data-in\n");
read_data(m_buffer, 512);
}
if(m_phase == SCSI_PHASE_MESSAGE_IN)
{
m_spc_status &= ~SSTS_DREG_EMPTY; // DREG is no longer empty
m_data = 0;
m_temp = 0x00;
set_phase(SCSI_PHASE_BUS_FREE);
logerror("message-in\n");
m_spc_status &= ~SSTS_XFER_IN_PROGRESS;
m_command_index = 0;
}
if(m_phase == SCSI_PHASE_COMMAND)
{
logerror("command-in\n");
m_spc_status |= SSTS_SPC_BSY;
}
logerror("mb89352: SCMD: Start Transfer %02x\n",m_phase);
break;
case 0x05: // Transfer pause
logerror("mb89352: SCMD: Pause Transfer\n");
break;
case 0x06: // reset REQ/ACK
m_line_status &= ~MB89352_LINE_ACK;
if(m_phase != SCSI_PHASE_BUS_FREE)
{
m_line_status |= MB89352_LINE_REQ;
}
else
{
m_spc_status &= ~SSTS_INIT_CONNECTED;
m_spc_status &= ~SSTS_TARG_CONNECTED;
m_spc_status &= ~SSTS_SPC_BSY;
}
logerror("mb89352: SCMD: Reset REQ/ACK\n");
break;
case 0x07: // set REQ/ACK
m_line_status &= ~MB89352_LINE_REQ;
m_line_status |= MB89352_LINE_ACK;
logerror("mb89352: SCMD: Set REQ/ACK\n");
if(m_phase == SCSI_PHASE_COMMAND)
{
m_command[m_command_index++] = m_temp; // temp register puts data onto the SCSI bus
if(m_command_index >= get_scsi_cmd_len(m_command[0]))
{
int x;
int phase;
// execute SCSI command
send_command(m_command, m_command_index);
phase = get_phase();
if(m_command[0] == 1) // Rezero Unit - not implemented in SCSI code
set_phase(SCSI_PHASE_STATUS);
else
set_phase(phase);
logerror("Command executed: ");
//m_spc_status &= ~SSTS_SPC_BSY;
//m_ints |= INTS_COMMAND_COMPLETE;
for(x=0;x<m_command_index;x++)
logerror(" %02x",m_command[x]);
logerror("\n");
}
return;
}
if(m_phase == SCSI_PHASE_STATUS)
{
m_temp = get_status();
set_phase(SCSI_PHASE_MESSAGE_IN);
return;
}
if(m_phase == SCSI_PHASE_MESSAGE_IN)
{
m_temp = 0x00;
set_phase(SCSI_PHASE_BUS_FREE);
m_command_index = 0;
return;
}
break;
default:
logerror("mb89352: SCMD: Unimplemented command %02x\n",(data & 0xe0) >> 5);
break;
}
break;
case 0x04: // INTS - Interrupt Sense
m_ints &= ~data; // resets relevant status bits to zero
m_irq_cb(0); // clear IRQ
logerror("mb89352: Reset INTS status bits %02x\n",data);
break;
case 0x08: // PCTL - Phase control
if((data & 0x80) == 0 && (m_phase == SCSI_PHASE_SELECT)) // if writing 0 to bit 7, selection phase is reset
{
m_ints &= ~INTS_SELECTION;
m_target = 0;
logerror("mb89352: PCTL selection cancelled\n");
}
// writing to the low 3 bits sets the phase
if((m_phase & 0x07) != (data & 0x07))
set_phase(data & 0x07);
m_busfree_int_enable = data & 0x80;
logerror("mb89352: PCTL write %02x\n",data);
break;
case 0x0a: // DREG - Data register
if(m_phase == SCSI_PHASE_COMMAND)
{
m_command[m_command_index++] = data;
if(m_command_index >= get_scsi_cmd_len(m_command[0]))
{
int x;
int phase;
// execute SCSI command
send_command(m_command, m_command_index);
phase = get_phase();
if(m_command[0] == 1) // Rezero Unit - not implemented in SCSI code
set_phase(SCSI_PHASE_STATUS);
else
set_phase(phase);
logerror("Command executed: ");
//m_ints |= INTS_COMMAND_COMPLETE;
for(x=0;x<m_command_index;x++)
logerror(" %02x",m_command[x]);
logerror("\n");
}
return;
}
if(m_spc_status & SSTS_XFER_IN_PROGRESS)
{
m_buffer[m_transfer_index % 512] = data;
m_spc_status |= SSTS_DREG_EMPTY; // DREG is empty once sent
m_transfer_index++;
m_transfer_count--;
if(m_transfer_index % 512 == 0)
write_data(m_buffer, 512);
if(m_transfer_count == 0)
{
// End of transfer
m_spc_status &= ~SSTS_XFER_IN_PROGRESS;
m_spc_status |= SSTS_DREG_EMPTY;
m_ints |= INTS_COMMAND_COMPLETE;
if(m_int_enable != 0)
m_irq_cb(1);
set_phase(SCSI_PHASE_STATUS);
}
}
break;
case 0x0b: // TEMP - Temporary
m_temp = data;
logerror("mb89352: Write %02x to temporary register\n",data);
break;
case 0x0c: // TCH - Transfer Counter High
m_transfer_count = (m_transfer_count & 0x0000ffff) | (data << 16);
if(m_transfer_count == 0)
m_spc_status |= SSTS_TC_ZERO;
else
m_spc_status &= ~SSTS_TC_ZERO;
logerror("mb89352: TCH: Write %02x [%06x]\n",data,m_transfer_count);
break;
case 0x0d: // TCM - Transfer Counter Mid
m_transfer_count = (m_transfer_count & 0x00ff00ff) | (data << 8);
if(m_transfer_count == 0)
m_spc_status |= SSTS_TC_ZERO;
else
m_spc_status &= ~SSTS_TC_ZERO;
logerror("mb89352: TCM: Write %02x [%06x]\n",data,m_transfer_count);
break;
case 0x0e: // TCL - Transfer Counter Low
m_transfer_count = (m_transfer_count & 0x00ffff00) | data;
if(m_transfer_count == 0)
m_spc_status |= SSTS_TC_ZERO;
else
m_spc_status &= ~SSTS_TC_ZERO;
logerror("mb89352: TCL: Write %02x [%06x]\n",data,m_transfer_count);
break;
default:
logerror("mb89352: write %02x to register %02x\n",data,offset & 0x0f);
}
}