/*
 *  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)
 *
 *  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 "machine/devhelpr.h"
#include "mb89352.h"

/*
 *  Device config
 */

const device_type MB89352A = &device_creator<mb89352_device>;


void mb89352_device::device_config_complete()
{
    // copy static configuration if present
	const mb89352_interface *intf = reinterpret_cast<const mb89352_interface *>(static_config());
	if (intf != NULL)
		*static_cast<mb89352_interface *>(this) = *intf;

    // otherwise, initialize it to defaults
    else
    {
		scsidevs = NULL;
		memset(&irq_callback,0,sizeof(irq_callback));
		memset(&drq_callback,0,sizeof(drq_callback));
    }
}

/*
 * Device
 */

mb89352_device::mb89352_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
    : device_t(mconfig, MB89352A, "MB89352A", tag, owner, clock)
{
}

void mb89352_device::device_start()
{
	int x;

    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;
    if(m_transfer_count == 0)
    	m_spc_status |= SSTS_TC_ZERO;
    m_ints = 0x00;

    m_irq_func.resolve(irq_callback,*this);
    m_drq_func.resolve(drq_callback,*this);

    memset(m_SCSIdevices,0,sizeof(m_SCSIdevices));

    // initialise SCSI devices, if any present
    for(x=0;x<scsidevs->devs_present;x++)
    {
    	SCSIAllocInstance(machine(), scsidevs->devices[x].scsiClass,
				&m_SCSIdevices[scsidevs->devices[x].scsiID],
				scsidevs->devices[x].diskregion );
    }

    // 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_ints = 0x00;

    mb89352_rescan();
}

void mb89352_device::device_stop()
{
	int x;

	// clean up the devices
	for (x=0;x<scsidevs->devs_present;x++)
	{
		SCSIDeleteInstance( m_SCSIdevices[scsidevs->devices[x].scsiID] );
	}
}

// get the length of a SCSI command based on it's command byte type
int mb89352_device::get_scsi_cmd_len(UINT8 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", group);

	return 6;
}

void mb89352_device::mb89352_rescan()
{
	int i;

	// try to open the devices
	for (i = 0; i < scsidevs->devs_present; i++)
	{
		// if a device wasn't already allocated
//      if (!m_SCSIdevices[scsidevs->devices[i].scsiID])
		{
			SCSIDeleteInstance( m_SCSIdevices[scsidevs->devices[i].scsiID] );
			SCSIAllocInstance( machine(),
					scsidevs->devices[i].scsiClass,
					&m_SCSIdevices[scsidevs->devices[i].scsiID],
					scsidevs->devices[i].diskregion );
		}
	}
}

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_func(1);
		}
		break;
	}
}

void mb89352_device::set_phase(int phase)
{
	m_phase = phase;
	switch(phase)
	{
	case SCSI_PHASE_BUS_FREE:
		m_line_status = 0;
		if(m_int_enable != 0)
			m_irq_func(1);
		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);
}

int mb89352_device::get_phase(void)
{
	return m_phase;
}

READ8_MEMBER( mb89352_device::mb89352_r )
{
	UINT8 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) ? 0x80 : 0x00);
	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)
				SCSIReadData(m_SCSIdevices[m_target],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;
				set_phase(SCSI_PHASE_STATUS);
			}
		}
		return m_data;
	case 0x0b:  // TEMP - Temporary
		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 )
{
	device_image_interface* image;

	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
		{
			m_ints |= INTS_RESET;
			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_resel_enable = 1;
			logerror("mb89352: SCTL: Reselection enabled.\n");
		}
		else
			m_resel_enable = 0;
		if(data & 0x02)
		{
			m_sel_enable = 1;
			logerror("mb89352: SCTL: Selection enabled.\n");
		}
		else
			m_sel_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 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;
			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;
			}
			set_phase(SCSI_PHASE_COMMAND); // straight to command phase, may need a delay betweem selection and command phases
			m_line_status |= MB89352_LINE_SEL;
			if(m_int_enable != 0)
				m_irq_func(1);
			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;
			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, but apparently it's not full either.
				SCSIReadData(m_SCSIdevices[m_target],m_buffer,512);
			}
			logerror("mb89352: SCMD: Start Transfer\n");
			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
					SCSISetCommand(m_SCSIdevices[m_target],m_command,m_command_index);
					SCSIExecCommand(m_SCSIdevices[m_target],&m_result_length);
					SCSIGetPhase(m_SCSIdevices[m_target],&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: ");
					for(x=0;x<m_command_index;x++)
						logerror(" %02x",m_command[x]);
					logerror("\n");
				}
				return;
			}
			if(m_phase == SCSI_PHASE_STATUS)
			{
				image = dynamic_cast<device_image_interface*>(machine().device(scsidevs->devices[m_target].diskregion));
				if(image->exists())
					m_temp = 0x00;
				else
					m_temp = 0x02;
				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_func(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
		m_phase = data & 0x07;
		m_line_status |= (data & 0x07);
		logerror("mb89352: PCTL write %02x\n",data);
		break;
	case 0x0a:  // DREG - Data register
		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)
				SCSIWriteData(m_SCSIdevices[m_target],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;
				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);
	}
}
