//	Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
//
//	This file is part of SCSI2SD.
//
//	SCSI2SD is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	SCSI2SD is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with SCSI2SD.  If not, see <http://www.gnu.org/licenses/>.

#include "device.h"
#include "scsi.h"
#include "scsiPhy.h"
#include "bits.h"

#define scsiTarget_AUX_CTL (* (reg8 *) scsiTarget_datapath__DP_AUX_CTL_REG)

CY_ISR_PROTO(scsiResetISR);
CY_ISR(scsiResetISR)
{
	scsiDev.resetFlag = 1;
	SCSI_RST_ClearInterrupt();
}

uint8 scsiReadDBxPins()
{
	return
		(SCSI_ReadPin(SCSI_In_DBx_DB7) << 7) |
		(SCSI_ReadPin(SCSI_In_DBx_DB6) << 6) |
		(SCSI_ReadPin(SCSI_In_DBx_DB5) << 5) |
		(SCSI_ReadPin(SCSI_In_DBx_DB4) << 4) |
		(SCSI_ReadPin(SCSI_In_DBx_DB3) << 3) |
		(SCSI_ReadPin(SCSI_In_DBx_DB2) << 2) |
		(SCSI_ReadPin(SCSI_In_DBx_DB1) << 1) |
		SCSI_ReadPin(SCSI_In_DBx_DB0);		
}

uint8 scsiReadByte(void)
{
	while (!(CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG) & 1) &&
		!scsiDev.resetFlag) {}
	CY_SET_REG8(scsiTarget_datapath__F0_REG, 0);
	while (!(CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG) & 2) &&
		!scsiDev.resetFlag) {}
		
	while (SCSI_ReadPin(SCSI_In_ACK) && !scsiDev.resetFlag) {}
		
	return CY_GET_REG8(scsiTarget_datapath__F1_REG);
}

void scsiRead(uint8* data, uint32 count)
{
	int prep = 0;
	int i = 0;

	while (i < count && !scsiDev.resetFlag)
	{
		if (prep < count && (CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG) & 1))
		{
			CY_SET_REG8(scsiTarget_datapath__F0_REG, 0);
			++prep;
		}
		if ((CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG) & 2))
		{
			data[i] =  CY_GET_REG8(scsiTarget_datapath__F1_REG);
			++i;
		}
	}
	while (SCSI_ReadPin(SCSI_In_ACK) && !scsiDev.resetFlag) {}

}

void scsiWriteByte(uint8 value)
{
	while (!(CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG) & 1) &&
		!scsiDev.resetFlag) {}
	CY_SET_REG8(scsiTarget_datapath__F0_REG, value);

	// TODO maybe move this TX EMPTY check to scsiEnterPhase ?
	//while (!(CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG) & 4)) {}
	while (!(CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG) & 2) &&
		!scsiDev.resetFlag) {}
	value = CY_GET_REG8(scsiTarget_datapath__F1_REG);
	
	while (SCSI_ReadPin(SCSI_In_ACK) && !scsiDev.resetFlag) {}
}

void scsiWrite(uint8* data, uint32 count)
{
	int prep = 0;
	int i = 0;

	while (i < count && !scsiDev.resetFlag)
	{
		if (prep < count && (CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG) & 1))
		{
			CY_SET_REG8(scsiTarget_datapath__F0_REG, data[prep]);
			++prep;
		}
		if ((CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG) & 2))
		{
			CY_GET_REG8(scsiTarget_datapath__F1_REG);
			++i;
		}
	}
	
	while (SCSI_ReadPin(SCSI_In_ACK) && !scsiDev.resetFlag) {}
}

static void busSettleDelay(void)
{
	// Data Release time (switching IO) = 400ns
	// + Bus Settle time (switching phase) = 400ns.
	CyDelayUs(1); // Close enough.
}

void scsiEnterPhase(int phase)
{
	if (phase > 0)
	{
		if (phase & __scsiphase_msg)
		{
			SCSI_SetPin(SCSI_Out_MSG);
		}
		else
		{
			SCSI_ClearPin(SCSI_Out_MSG);
		}

		if (phase & __scsiphase_cd)
		{
			SCSI_SetPin(SCSI_Out_CD);
		}
		else
		{
			SCSI_ClearPin(SCSI_Out_CD);
		}

		SCSI_CTL_IO_Write(phase & __scsiphase_io ? 1 : 0);
	}
	else
	{
		SCSI_ClearPin(SCSI_Out_MSG);
		SCSI_ClearPin(SCSI_Out_CD);
		SCSI_CTL_IO_Write(0);
	}
	busSettleDelay();
}

void scsiPhyReset()
{
	// Set the Clear bits for both SCSI device FIFOs
	scsiTarget_AUX_CTL = scsiTarget_AUX_CTL | 0x03;

	// Trigger RST outselves.  It is connected to the datapath and will
	// ensure it returns to the idle state.  The datapath runs at the BUS clk
	// speed (ie. same as the CPU), so we can be sure it is active for a sufficient
	// duration.
	SCSI_SetPin(SCSI_Out_RST);

	SCSI_CTL_IO_Write(0);
	SCSI_ClearPin(SCSI_Out_ATN);
	SCSI_ClearPin(SCSI_Out_BSY);
	SCSI_ClearPin(SCSI_Out_ACK);
	SCSI_ClearPin(SCSI_Out_RST);
	SCSI_ClearPin(SCSI_Out_SEL);
	SCSI_ClearPin(SCSI_Out_REQ);
	SCSI_ClearPin(SCSI_Out_MSG);
	SCSI_ClearPin(SCSI_Out_CD);

	// Allow the FIFOs to fill up again.
	SCSI_ClearPin(SCSI_Out_RST);
	scsiTarget_AUX_CTL = scsiTarget_AUX_CTL & ~(0x03);
}

void scsiPhyInit()
{
	SCSI_RST_ISR_StartEx(scsiResetISR);

	// Interrupts may have already been directed to the (empty)
	// standard ISR generated by PSoC Creator.
	SCSI_RST_ClearInterrupt();
}
