/*****************************************************************************
* Routines to talk to the ZVG board from DOS through a 1284 compliant
* parallel port.
*
* Author:  Zonn Moore
* Created: 11/06/02
*
* History:
*
* (c) Copyright 2002, Zektor, LLC.  All Rights Reserved.
*****************************************************************************/
#include	<stdio.h>
#include	<stdlib.h>
#include	<conio.h>
#include	<pc.h>

#include	"zstddef.h"
#include	"zvgport.h"
#include	"zvgCmds.h"
#include	"timer.h"

#define	ECPF_ECP			0x01			// if set, indicates we're in ECP mode
#define	ECPF_NIBBLE		0x02			// if set, indicates we're in reverse NIBBLE mode		

static uint		EcpPort;					// Base address of port
static uint		Ecp_pdata;				// Data address of port
static uint		Ecp_pdsr;				// DSR address of port
static uint		Ecp_pdcr;				// DCR address of port
static uint		Ecp_pecr;				// ECR address of port
static uint		Ecp_pecpDFifo;			// ecpDFifo address of port

static uint		EcpDCR;					// Current state of the DCR register

static uint		EcpFlags = 0;			// Flags to keep track of ECP mode settings

static uchar	MonitorBfr[ZVG_MAX_BFRSZ];

/*****************************************************************************
* Print an error returned by the ZVG routines.
*****************************************************************************/
void zvgError( uint err)
{
	fprintf( stdout, "\nError %u: ", err);

	switch (err)
	{
	case errOk:
		fputs( "No Error", stdout);
		break;

	case errNotEcp:
		fputs( "No ECP compatible port found", stdout);
		break;

	case errDmaIrq:
		fputs( "Could not determine the DMA or IRQ values", stdout);
		break;

	case errEcpWord:
		fputs( "ECP could not be set to 8 bit mode", stdout);
		break;

	case errEcpFailed:
		fputs( "ECP negotiations failed", stdout);
		break;

	case errEcpNoData:
		fputs( "No data available from the ZVG", stdout);
		break;

	case errEcpBadData:
		fputs( "Bad information, or wrong number of bytes returned from the ZVG", stdout);
		break;

	case errEcpTimeout:
		fputs( "Timeout while waiting for ZVG", stdout);
		break;

	case errEcpBusy:
		fputs( "ZVG is busy", stdout);
		break;

	case errEcpComm:
		fputs( "Unexpected data from the ZVG", stdout);
		break;

	case errEcpBadMode:
		fputs( "An attempt to read/write to ECP port in the wrong mode", stdout);
		break;

	case errEcpToSpp:
		fputs( "ECP has dropped back into combatibility mode", stdout);
		break;

	case errZvgRomCS:
		fputs( "Bad checksum in data", stdout);
		break;

	case errZvgRomNE:
		fputs( "Data written / compared did not match FLASH memory", stdout);
		break;

	case errZvgRomTI:
		fputs( "Writing to FLASH not allowed, check SETUP jumper", stdout);
		break;
	}
	fputs( ".\n", stdout);
}

/*****************************************************************************
* Wait for the DSR to equal the given value.
*
* Called with:
*    mask   = Bitmask used to mask DSR before comparison.
*    bitVal = Bit values, that when matched, causes routine to return.
*    ms     = Timeout in milliseconds.
*
* Returns all (unmasked bits) of DSR when a match is found, or returns
* ZVG_TIMEOUT (-1) if routine timed out while waiting for match.
*****************************************************************************/
static uint waitForDsrEQ( uchar mask, uchar bitVal, ulong ms)
{
	uchar	testVal;
	uchar	readVal;
	ulong	timer;
	bool	foundF;

	testVal = (bitVal ^ DSR_InvMask) & mask;
	foundF = zFalse;
	timer = tmrRead();					// read timer value

	while (!tmrTestms( timer, ms))
		if (((readVal = inportb( Ecp_pdsr)) & mask) == testVal)
		{	foundF = zTrue;
			break;
		}

	if (!foundF)
		return (ZVG_TIMEOUT);

	return (readVal ^ DSR_InvMask);
}

/*****************************************************************************
* Wait for the DSR to NOT equal the given value.
*
* Called with:
*    mask   = Bitmask used to mask DSR before comparison.
*    bitVal = Bit values, that when not matched, causes routine to return.
*    ms     = Timeout in milliseconds.
*
* Returns all (unmasked bits) of DSR when the DSR does NOT match the given
* 'bits' value, or returns ZVG_TIMEOUT (-1) if routine timed out while waiting
* for a mismatch.
*****************************************************************************/
static uint	waitForDsrNE( uchar mask, uchar bitVal, ulong ms)
{
	uchar		testVal;
	uchar		readVal;
	ulong		timer;
	bool		foundF;

	testVal = ( bitVal ^ DSR_InvMask) & mask;
	foundF = zFalse;
	timer = tmrRead();

	while (!tmrTestms( timer, ms))
		if (((readVal = inportb( Ecp_pdsr)) & mask) != testVal)
		{	foundF = zTrue;
			break;
		}

	if (!foundF)
		return (ZVG_TIMEOUT);

	return (readVal ^ DSR_InvMask);
}

/*****************************************************************************
* Wait for the ECR to equal the given value.
*
* Called with:
*    mask   = Bitmask used to mask DSR before comparison.
*    bitVal = Bit values, that when matched, causes routine to return.
*    ms     = Timeout in milliseconds.
*
* Returns all (unmasked bits) of ECR when a match is found, or returns
* ZVG_TIMEOUT (-1) if routine timed out while waiting for match.
*****************************************************************************/
static uint waitForEcrEQ( uchar mask, uchar bitVal, ulong ms)
{
	uchar	testVal;
	uchar	readVal;
	ulong	timer;
	bool	foundF;

	testVal = bitVal & mask;
	foundF = zFalse;
	timer = tmrRead();

	while (!tmrTestms( timer, ms))
		if (((readVal = inportb( Ecp_pecr)) & mask) == testVal)
		{	foundF = zTrue;
			break;
		}

	if (!foundF)
		return (ZVG_TIMEOUT);

	return (readVal);
}

/*****************************************************************************
* Set bits in the DCR register
*****************************************************************************/
static void sdcr( uchar bits)
{
	EcpDCR |= bits;							// set control bits
	EcpDCR ^= DCR_InvMask & bits;			// invert them if needed
	outportb( Ecp_pdcr, EcpDCR);			// set new control line values
}

/*****************************************************************************
* Clear bits in the DCR register
*****************************************************************************/
static void cdcr( uchar bits)
{
	EcpDCR &= ~bits;							// clear control bits
	EcpDCR ^= DCR_InvMask & bits;			// invert them if needed
	outportb( Ecp_pdcr, EcpDCR); 			// set new control line values
}

/*****************************************************************************
* Set & Clear bits in the DCR in one update
* 
* First arg sets bits, 2nd clears
*****************************************************************************/
static void scdcr( uchar setBits, uchar clearBits)
{
	EcpDCR |= setBits;						// set control bits
	EcpDCR &= ~clearBits;					// clear control bits

	// invert them if needed

	EcpDCR ^= DCR_InvMask & (setBits | clearBits);
	outportb( Ecp_pdcr, EcpDCR);			// set new control line values
}

/*****************************************************************************
* Write to the DCR register
*****************************************************************************/
static void wdcr( uchar bits)
{
	EcpDCR = bits ^ DCR_InvMask;
	outportb( Ecp_pdcr, EcpDCR);
}

/*****************************************************************************
* Read from the DSR register
*****************************************************************************/
static uchar rdsr( void)
{
	return (inportb( Ecp_pdsr) ^ DSR_InvMask);
} 

/*****************************************************************************
* Return immediately to the compatibility mode.  Something has gone wrong,
* there has been an event out of sequence.  This routine is called to reset
* everything.
*****************************************************************************/
static uint compatibility( void)
{
	EcpFlags &= ~(ECPF_ECP | ECPF_NIBBLE);		// turn off MODE flags

	// Set the status lines to compatibility mode:
	//
	//    nSelectIn - Low
	//    nAutoFeed - High
	//    nStrobe   - High
	//    nInit     - High
	//

	wdcr( DCR_nAutoFeed | DCR_nStrobe | DCR_nInit);    
	return (errOk);
}

/*****************************************************************************
* Do negotiation sequence, does not check to see if mode given was accepted
* or not, just returns when negotiations successful, or with an error code
* if an error occured.
*****************************************************************************/
static uint negotiate_1284( uchar mode)
{
	// Setup a negotiation request, setup for at least 1us

	outportb( Ecp_pdata, mode);					// setup data lines
	outportb( Ecp_pdata, mode);
	outportb( Ecp_pdata, mode);
	outportb( Ecp_pdata, mode);

	// Set 1284_Active high and HostBusy low

	scdcr( DCR_1284_Active, DCR_HostBusy);

	// look for awhile and see if the peripheral will respond

	if (waitForDsrEQ( DSR_AckDataReq|DSR_PtrClk|DSR_nDataAvail|DSR_XFlag,
			DSR_AckDataReq|DSR_nDataAvail|DSR_XFlag, PERIPH_TIMEOUT) == ZVG_TIMEOUT)
		goto WaitTimeout;

	// set HostClk low for 1us

	cdcr( DCR_HostClk);
	outportb( Ecp_pdcr, EcpDCR);
	outportb( Ecp_pdcr, EcpDCR);
	outportb( Ecp_pdcr, EcpDCR);

	// finish pulse and set HostBusy high

	sdcr( DCR_HostClk | DCR_HostBusy);

	// wait for peripheral to respond

	if (waitForDsrNE( DSR_PtrClk, 0, PERIPH_TIMEOUT) == ZVG_TIMEOUT)
		goto WaitTimeout;

	// negotiations successful, return

	return (errOk);

WaitTimeout:
	compatibility();
	return (errEcpFailed);		// timeout is not an error, just a non-1284 device
}

/*****************************************************************************
* Do a normal negotiated terminatation to return to the Compatibility mode.
*
* Follows the standard 1284 termination sequence.
*****************************************************************************/
static uint terminate_1284( void)
{
	EcpFlags &= ~(ECPF_ECP | ECPF_NIBBLE);		// turn off MODE flags

	// set port back to SPP mode

	outportb( Ecp_pecr, ECR_SPP_mode | ECR_nErrIntrEn | ECR_serviceIntr);

	// release 1284 active lines

	scdcr( DCR_HostBusy | DCR_HostClk, DCR_1284_Active);

	// wait for P. to respond. XFlag is also inverted but we're not checking
	// for that.

	if (waitForDsrEQ( DSR_PtrClk, 0, PERIPH_TIMEOUT) == ZVG_TIMEOUT)
		goto WaitTimeout;

	// Ack P.

	cdcr( DCR_HostBusy);

	// Wait for P. to set itself back to compatibility mode

	if (waitForDsrEQ( DSR_PtrClk, DSR_PtrClk, PERIPH_TIMEOUT) == ZVG_TIMEOUT)
		goto WaitTimeout;

	// indicate we're also ready to move on

	sdcr( DCR_HostBusy | DCR_nInit);
	return (errOk);

WaitTimeout:
	compatibility();
	return (errOk);			// terminate always works, even if a timeout occured
}

/*****************************************************************************
* Get a byte of data using the NIBBLE mode.  Checks to see if data is
* available.  If not, it will return a 'errEcpNoData' error.
*
* Reverse NIBBLE mode must have already been negotiated, or an error will
* result.
*****************************************************************************/
static uint getByteNb( uchar *byte)
{
	uint	loNib, hiNib;

	// check for proper mode

	if (!(EcpFlags & ECPF_NIBBLE))
		return (errEcpBadMode);		// not in Nibble mode

	// has data just become available?

	if (rdsr() & DSR_AckDataReq)
	{	cdcr( DCR_HostBusy);			// indicate host is not busy

		// check to see if the peripheral is indicating that data is available
		// wait for awhile.  Check for a full second to see if data is available.

		if (waitForDsrNE( DSR_nDataAvail, DSR_nDataAvail, PERIPH_WAIT) == ZVG_TIMEOUT)
		{	sdcr( DCR_HostBusy);		// host is once again busy
			terminate_1284();			// if no data, leave NIBBLE mode
			return (errEcpNoData);
		}

		sdcr( DCR_HostBusy);			// indicate host has received the IRQ

		// wait for P. to ack

		if (waitForDsrNE( DSR_AckDataReq, DSR_AckDataReq, PERIPH_TIMEOUT) == ZVG_TIMEOUT)
			goto WaitTimeout;	
	}
	cdcr( DCR_HostBusy);				// indicate host is not busy

	// wait for P. response

	if (waitForDsrNE( DSR_PtrClk, DSR_PtrClk, PERIPH_TIMEOUT) == ZVG_TIMEOUT)
		goto WaitTimeout;

	// when data is ready, read it.

	loNib = rdsr();

	// indicate we've read the data

	sdcr( DCR_HostBusy);

	// shift the bits around to build a real nibble

	loNib = ((loNib >> 3) & 0x07) | ((loNib & DSR_Busy) >> 4);

	// wait for the P. to know we've read the data

	if (waitForDsrNE( DSR_PtrClk, 0, PERIPH_TIMEOUT) == ZVG_TIMEOUT)
		goto WaitTimeout;

	// indicate were ready for more data
 
	cdcr( DCR_HostBusy);

	// wait for data

	if (waitForDsrNE( DSR_PtrClk, DSR_PtrClk, PERIPH_TIMEOUT) == ZVG_TIMEOUT)
		goto WaitTimeout;

	// read next nibble

	hiNib = rdsr();

	// tell the P. we've got it.

	sdcr( DCR_HostBusy);

	// shift into real nibble

	hiNib = ((hiNib << 1) & 0x70) | (hiNib & DSR_Busy);

	// wait for P. to catch up

	if (waitForDsrNE( DSR_PtrClk, 0, PERIPH_TIMEOUT) == ZVG_TIMEOUT)
		goto WaitTimeout;

	// return the full byte

	*byte = hiNib | loNib;
	return (errOk);

WaitTimeout:
	compatibility();
	return (errEcpTimeout);
}

/*****************************************************************************
* Check if data is available, assumes we are in the reverse nibble mode.
*
* This routine should only be called in reverse NIBBLE mode.
*****************************************************************************/
static uint isDataAvail( void)
{
	// check for Nibble mode data available

	return (!(rdsr() & DSR_nDataAvail));
}

/*****************************************************************************
* Initialize the ZVG, calibrate parallel port timing.
*
* The SPP mode requires some specific timing that pulse lines for 1us.
*
* This is too fast for even the 8254 to time properly, so I do a simple
* calibration here to find out how many 'outportb()' instructions it takes to
* create a 1us pulse.
*
* This is done in a 100ms loop that is an unnoticable delay on startup.
*
* Called with:
*    NONE
*
* Returns:
*    NONE
*****************************************************************************/
void zvgInit( void)
{
	tmrInit();							// initialize timers
}

/*****************************************************************************
* Look for an ECP port at the given address.
*
* If ECP found, sets up EcpPort, EcpDMA and EcpIRQ to the values read from
* the ECP registers.  EcpDMA and EcpIRQ should be validity checked since
* most ECP ports do not support these bits and return invalid data.
*
* Port is left in the Compatibility mode.
*
* Returns:
*    errCode
*****************************************************************************/
uint zvgDetectECP( uint portAdr, uint *ecpDMA, uint *ecpIRQ)
{
	uchar		pv, cnfgA, cnfgB;

	// setup port addresses

	EcpPort = portAdr;
	Ecp_pdata = portAdr + ECP_data;
	Ecp_pdcr = portAdr + ECP_dcr;
	Ecp_pdsr = portAdr + ECP_dsr;
	Ecp_pecr = portAdr + ECP_ecr;
	Ecp_pecpDFifo = portAdr + ECP_ecpDFifo;

	// Reset all ECP flags

	EcpFlags = 0;

	// look for ECP port

	pv = inportb( Ecp_pecr);

	// Check that the full bit is off, and the empty bit is set
	
	if (!((pv & (ECR_full | ECR_empty)) == ECR_empty))
		return (errNotEcp);

	// verify that we cannot change the empty bit to a zero 

	outportb( Ecp_pecr, 0x34);				// write 0x34

	if (inportb( Ecp_pecr) != 0x35)		// verify 0x35 is read back
		return (errNotEcp);

	// gain access to the configuration registers

	outportb( Ecp_pecr, ECR_Cnfg_mode | ECR_nErrIntrEn | ECR_serviceIntr);

	// read the configuration registers

	cnfgA = inportb( portAdr + ECP_cnfgA);
	cnfgB = inportb( portAdr + ECP_cnfgB);

	// Check the size of the data word being transferred

	if ((cnfgA & 0x60) != 0x10)
	{
		// if not set to 8 bit mode, try to set it

		cnfgA &= 0x9F;			// remove word size bits
		cnfgA |= 0x10;			// set to 8 bit words

		outportb( portAdr + ECP_cnfgA, cnfgA);

		// if not writeable, error

		if (inportb( portAdr + ECP_cnfgA) != cnfgA)
			return (errEcpWord);
	}

	// Set DMA and IRQ values, they're probably not valid, most chipsets
	// do not seem to support this. It is left to the calling program to
	// make the judgement call on the validity of these values.

	*ecpDMA = cnfgB & 0x07;					// get DMA channel
	*ecpIRQ = (cnfgB >> 3) & 0x07;		// get IRQ number

	// set port to SPP mode

	outportb( Ecp_pecr, ECR_SPP_mode | ECR_nErrIntrEn | ECR_serviceIntr);

	// Set the flags to compatibility mode:
	//
	//    nSelectIn - Low
	//    nAutoFeed - High
	//    nStrobe   - High
	//    nInit     - High

	wdcr( DCR_nAutoFeed | DCR_nStrobe | DCR_nInit);

	return (errOk);
}

/*****************************************************************************
* Write a character to the SPP port.
* Toggles the lines manually, so this should work with any port.
* This routine is BUSY flag driven only and uses no IRQs.
*
* This routine must not be used to send ZVG vector data. The firmware will
* not except vector data using the SPP mode.  To send vector data call
* zvgSetEcpMode() and use zvgEcpPutc() routine.
*
* The SPP mode is used for bi-directional command and status data only.
*
* Called with:
*    cc = Character to send to the ZVG.
*
* Returns:
*    'portErrCode'
*****************************************************************************/
uint zvgPutc( uchar cc)
{
	// check for proper mode

	if (EcpFlags & (ECPF_ECP | ECPF_NIBBLE))
		return (errEcpBadMode);	// this routine only works for SPP modes

	// wait for busy to go low

	if (waitForDsrEQ( DSR_Busy, 0, PERIPH_WAIT) == ZVG_TIMEOUT)
		return (errEcpBusy);		// Peripheral is apparently too busy for us

	// place data on bus, assume at least one cycle per access, wait for at least 1us

	outportb( Ecp_pdata, cc);
	outportb( Ecp_pdata, cc);
	outportb( Ecp_pdata, cc);
	outportb( Ecp_pdata, cc);

	cdcr( DCR_nStrobe);			// set strobe low

	// this wait is outside the SPP compatibility standard.  But this assures
	// us that the ZVG (which is firmware based), has read the data.

	if (waitForDsrEQ( DSR_Busy, DSR_Busy, PERIPH_TIMEOUT) == ZVG_TIMEOUT)
	{	sdcr( DCR_nStrobe);		// set strobe back high
		return (errEcpTimeout);	// indicate error
	}
	sdcr( DCR_nStrobe);			// release strobe
	return (errOk);
}

/*****************************************************************************
* Put a block of memory to the ZVG using the SPP mode.
*
* This routine will read 'bfrLen' number of bytes, or until no more data
* is available from the ZVG.  Number of bytes actually read is returned
* in the integer pointed to by 'aReadLen'.
*
* Called with:
*    ss  = Pointer to block of memory to send to ZVG.
*    len = Number of bytes to send.
*
* Returns:
*    'portErrCode'
*****************************************************************************/
uint zvgPutMem( uchar *ss, uint len)
{
	uint	err;

	while (len-- > 0)
	{	err = zvgPutc( *ss++);

		if (err)
			break;
	}
	return (err);
}

/*****************************************************************************
* Read block of memory from ZVG.
*
* This routine will read 'bfrLen' number of bytes, or until no more data
* is available from the ZVG.  Number of bytes actually read is returned
* in the integer pointed to by 'aReadLen'.
*
* This routine will negotiate into the reverse nibble mode, if not already
* there.  This routine can be called from the ECP mode, and will
* terminate the ECP mode before entering the NIBBLE mode.
*
* Upon exit this routine will leave the port in the SPP mode, regardless
* of what the mode was when called. 
*
* Called with:
*    ss       = Pointer to buffer to accept string from ZVG.
*    bfrLen   = Length of buffer.
*    aReadLen = Pointer to 'uint' to be set to number of bytes read.
*
* Returns:
*    'portErrCode'
*****************************************************************************/
uint zvgGetMem( uchar *ss, uint bfrLen, uint *aReadLen)
{
	uint	err, readLen;

	// if in ECP mode, terminate first

	if (EcpFlags & ECPF_ECP)
		terminate_1284();

	// if not already in nibble mode, do a REQ for NIBBLE mode

	if (!(EcpFlags & ECPF_NIBBLE))
		if ((err = (negotiate_1284( EMODE_NIBBLE))) != errOk)
			return (err);

	// up to us to set the flag

	EcpFlags |= ECPF_NIBBLE;		// indicate nibble mode

	readLen = 0;

	// read first byte without checking if data is available.  This allows
	// up to PERIPH_WAIT time for data to show up.

	err = getByteNb( ss);

	// if error, return a timeout (or whatever error was).

	if (err)
		return (err);

	ss++;
	readLen++;

	// Read string of length 'len' from peripheral using the NIBBLE mode.
	// Check each time through loop to see if more data is available.
	// If no more data available, exit loop without an error.

	while (readLen < bfrLen && isDataAvail())
	{
		err = getByteNb( ss);

		if (err)
			break;

		ss++;
		readLen++;
	}
	*aReadLen = readLen;

	if (!err)
		terminate_1284();

	return (err);
}

/*****************************************************************************
* Read Device ID from ZVG.
*
* This routine will read 'bfrLen' number of bytes, or until no more data
* is available from the ZVG.  Number of bytes actually read is returned
* in the integer pointed to by 'aReadLen'.
*
* This routine will negotiate into the reverse nibble mode, if not already
* there.  This routine can be called from the ECP mode, and will
* terminate the ECP mode before entering the NIBBLE mode.
*
* Upon exit this routine will leave the port in the SPP mode, regardless
* of what the mode was when called. 
*
* Called with:
*    ss       = Pointer to buffer to accept string from ZVG.
*    idLen    = Length of buffer.
*    aReadLen = Pointer to 'uint' to be set to number of bytes read.
*
* Returns:
*    'portErrCode'
*****************************************************************************/
uint zvgGetDeviceID( uchar *ss, uint idLen, uint *aReadLen)
{
	uint	err, count, readLen;
	uchar	countMSB, countLSB;

	// if in ECP mode, terminate first

	if (EcpFlags & ECPF_ECP)
		terminate_1284();

	if (idLen < 2)
		return (errEcpNoData);				// no room in buffer

	// do a REQ for ID using NIBBLE mode

	if (!(EcpFlags & ECPF_NIBBLE))
		if ((err = (negotiate_1284( EMODE_REQID_NIBBLE))) != errOk)
			return (err);

	// up to us to set the flag

	EcpFlags |= ECPF_NIBBLE;				// indicate nibble mode

	// get count word

	err = getByteNb( &countMSB);			// get a byte using nibble mode

	if (!err)
		err = getByteNb( &countLSB);

	// check for error reading first two bytes

	if (err)
		return (err);

	count = (countMSB << 8) + countLSB;

	if (count <= 2)
	{	terminate_1284();
		*ss = '\0';
		*aReadLen = 0;
		return (errEcpNoData);				// no proper ID is given
	}

	count -= 2;									// get number of bytes to read

	// check if it will fit in buffer, leave room for trailing '\0'

	if (count > idLen - 1)
		count = idLen - 1;

	readLen = 0;

	// read string of length 'count' from peripheral using the NIBBLE mode

	while (readLen < count && isDataAvail())
	{
		err = getByteNb( ss);

		if (err)
			break;

		ss++;
		readLen++;
	}
	*ss = '\0';
	*aReadLen = readLen;

	if (!err)
		terminate_1284();

	return (err);
}

/*****************************************************************************
* Set to ECP mode from compatibility mode.
*
* If no errors occur, sets up port to ECP forward mode, no DMA, no IRQs.
*****************************************************************************/
uint zvgSetEcpMode( void)
{
	uint	err;

	if (EcpFlags & ECPF_ECP)
		return (errOk);					// already in ECP mode

	// negotiate to ECP mode

	if ((err = (negotiate_1284( EMODE_ECP))) != errOk)
		return (err);

	// is ECP mode supported?

	if (!(rdsr() & DSR_XFlag))
	{	terminate_1284();					// exit negotiations
		return (errEcpFailed);			// if ECP mode not supported, return error
	}

	// Set the ECP mode flag

	EcpFlags |= ECPF_ECP;

	// ECP setup phase

	cdcr( DCR_HostAck);					// indicate that XFlag read

	// wait for busy to go low

	if (waitForDsrEQ( DSR_nAckReverse|DSR_PeriphClk|DSR_PerphAck|DSR_XFlag,
			DSR_nAckReverse|DSR_PeriphClk|DSR_XFlag, PERIPH_WAIT) == ZVG_TIMEOUT)
	{	compatibility();					// if timeout, return to compatability mode
		return (errEcpFailed);			// could not get into ECP mode
	}

	// setup the hardware to do automatic ECP mode transfers

	outportb( Ecp_pecr, ECR_ECP_mode | ECR_nErrIntrEn | ECR_serviceIntr);
	sdcr( DCR_HostAck | DCR_HostClk);
	return (errOk);
}

/*****************************************************************************
* Return to SPP (compatibility) mode.
*
* Checks first to see if we are *not* already in SPP mode.
*****************************************************************************/
void zvgSetSppMode( void)
{
	// if in a ECP mode, terminate it

	if (EcpFlags & (ECPF_ECP | ECPF_NIBBLE))
		terminate_1284();
}

/*****************************************************************************
* Check to see if any data is available in the ECP mode.
*
* Called with:
*    time = Time to wait for data, in milliseconds.
*
* Returns:
*    errOk         - if data is available.
*    errEcpTimeout - if data was not available.
*
*    Else, returns an ECP error code.
*****************************************************************************/
uint zvgIsDataAvail( uint time)
{
	uint	dsr;

	// must be in ECP mode

	if (!(EcpFlags & ECPF_ECP))
		return (errEcpBadMode);

	// wait for nPeriphRequest to go low

	dsr = waitForDsrNE( DSR_PeriphClk|DSR_nPeriphRequest|DSR_XFlag,
			DSR_PeriphClk|DSR_nPeriphRequest|DSR_XFlag, time);

	if (dsr == ZVG_TIMEOUT)
		return (errEcpTimeout);		// nothing wrong, just no data available

	// check for a breach in the ECP protocol

	else if ((dsr & (DSR_XFlag|DSR_PeriphClk)) != (DSR_XFlag | DSR_PeriphClk))
	{	compatibility();				// if status incorrect, return to compatability mode
		return (errEcpToSpp);		// indicate no longer in ECP mode
	}

	return (errOk);
}

/*****************************************************************************
* Write a byte to the port using ECP mode with hardware assist.
*
* ECP mode must have already been negotiated.
*
* If too much time passes, this routine will timeout, but it will not
* terminate the ECP mode.  So it may be called repeatably.
*
* Returns:
*    errEcpBusy  - If no response.
*    errEcpToSpp - If DSR_XFlag line was dropped, also resets ECP to SPP mode.
*****************************************************************************/
uint zvgEcpPutc( uchar cc)
{
	uint	ii;

	// for speed, check first if room in ECP buffer

	if (!(inportb( Ecp_pecr) & ECR_full))
		outportb( Ecp_pecpDFifo, cc);		// send data, let hardware handshake

	// If not, do the longer TIMEOUT version of the code

	else
	{
		// wait for 1 second

		for (ii = 0; ii < 10; ii++)
		{
			// check every 100ms for a breach in protocol

			if (waitForEcrEQ( ECR_full, 0, 100) == ZVG_TIMEOUT)
			{
				// if no response after 100ms, do a quick check of the status lines to
				// see if XFlag or PeriphClk has dropped.

				if ((rdsr() & (DSR_XFlag | DSR_PeriphClk)) != (DSR_XFlag | DSR_PeriphClk))
				{
					// The 1284 peripheral is not allowed to drop out of ECP
					// mode without being requested to do so, and the 
					// the PeriphClk must remain high while in ECP forward transfer
					// mode, so something unusual has happened, like a cable disconnect.
				
					compatibility();				// go immediatly into SPP mode
					return (errEcpToSpp);
				}
			}
			else
				break;
		}

		if (ii == 10)
			return (errEcpBusy);					// it's taken too long, something wrong

		// if no timeout, send data, hardware takes care of handshaking

		outportb( Ecp_pecpDFifo, cc);
	}
	return (errOk);
}

/*****************************************************************************
* Write a byte to the port using ECP mode with hardware assist.
*
* ECP mode must have already been negotiated.
*
* If too much time passes, this routine will timeout, but it will not
* terminate the ECP mode.  So it may be called repeatably, to make this
* possible a pointer to a pointer is used.  When a timeout occurs, the
* pointer will be left to the byte that the timeout occured on.  This allows
* the routine to be recalled.
*
* Called with:
*    mem     = Pointer to an uchar pointer that points to memory block.
*    memSize = Size of block of data to be sent.
*****************************************************************************/
uint zvgEcpPutMem( uchar **mem, uint memSize)
{
	uint	err;

	while (memSize-- > 0)
	{
		err = zvgEcpPutc( **mem);

		if (err)
			break;

		else
			(*mem)++;
	}
	return (err);
}

/*****************************************************************************
* Read current monitor information from the ZVG.
*
* This routine must send a zcREAD_MON command to the ZVG, and then wait
* for the command to be executed, since there can be vector commands
* ahead of this command in the ZVG command buffer.
*
* When the READ_MON command is executed, the ZVG will indicates data is ready
* by setting the 'nPeriphRequest' line low, indicating data is available.
*
* Since the ZVG cannot return data in the ECP mode, the ECP mode must
* be terminated, and the reverse nibble mode used to read the data.
*
* We must just make sure that we start in the ECP mode and
* after all data is read, we return to the ECP mode.
*
* Called with:
*    mon = Pointer to a 'ZvgMon_s' structure used to hold ZVG data.
******************************************************************************/
uint zvgReadMonitorInfo( ZvgMon_s *mon)
{
	uint	readLen, err;

	// must be in ECP mode

	if (!(EcpFlags & ECPF_ECP))
		return (errEcpBadMode);

	// request monitor information
	//
	// The ZVG has a 9 byte look ahead buffer.  Until at least 9 bytes are
	// in it's command buffer, nothing will get executed.  This requires
	// the zcREAD_MON command to be followed by 8 NOPs in order to guarantee
	// execution.

	zvgEcpPutc( zcREAD_MON);				// setup to read monitor information
	zvgEcpPutc( zcNOP);						// must fill enough of buffer to allow
	zvgEcpPutc( zcNOP);						// READ_MON to execute
	zvgEcpPutc( zcNOP);
	zvgEcpPutc( zcNOP);
	zvgEcpPutc( zcNOP);
	zvgEcpPutc( zcNOP);
	zvgEcpPutc( zcNOP);
	zvgEcpPutc( zcNOP);

	// wait for command to execute, this could be behind a bunch of vector commands

	err = zvgIsDataAvail( 1000);			// allow a second for data to show up

	if (err)
		return (err);
	
	// read monitor information into a simple buffer

	err = zvgGetMem( MonitorBfr, ZVG_MAX_BFRSZ, &readLen);

	if (!err && (readLen != ZVG_MON_SIZE))
		err = errEcpBadData;
	
	if (!err)
	{
		// GCC does not pack its structure, so we need to move each value
		// one byte at a time

		mon->point_i = MonitorBfr[0];
		mon->zShift = MonitorBfr[1];
		mon->oShoot = MonitorBfr[2];
		mon->jumpFactor = MonitorBfr[3];
		mon->settle = MonitorBfr[4];
		mon->min_i = MonitorBfr[5];
		mon->max_i = MonitorBfr[6];
		mon->scale = MonitorBfr[7];
		mon->flags = MonitorBfr[8];

		// for word data, LSB byte is first.

		mon->cksum = MonitorBfr[9] + ((ushort)MonitorBfr[10] << 8);
	}
	zvgSetEcpMode();							// go back to ECP mode
	return (err);
}

/*****************************************************************************
* Read speed table information from the ZVG.
*
* This routine must send a zcREAD_SPD command to the ZVG, and then wait
* for the command to be executed, since there can be vector commands
* ahead of this command in the ZVG command buffer.
*
* When the READ_SPD command is executed, the ZVG indicates data is ready
* by setting the 'nPeriphRequest' line low, indicating data is available.
*
*
* Since the ZVG cannot return data in the ECP mode, the ECP mode must
* be terminated, and the reverse nibble mode used to read the data.
*
* We must just make sure that we start in the ECP mode and
* after all data is read, we return to the ECP mode.
*
* Called with:
*    speeds = A 4 byte buffer used to read the four different available
*             ZVG speeds.
*****************************************************************************/
uint zvgReadSpeedInfo( char *speeds)
{
	uint	readLen, err;

	// request speed table information

	// The ZVG has a 9 byte look ahead buffer.  Until at least 9 bytes are
	// in it's command buffer, nothing will get executed.  This requires
	// the zcREAD_SPD command to be followed by 8 NOPs in order to guarantee
	// execution.

	zvgEcpPutc( zcREAD_SPD);				// setup to read monitor information
	zvgEcpPutc( zcNOP);						// must fill enough of buffer to allow
	zvgEcpPutc( zcNOP);						// READ_MON to execute
	zvgEcpPutc( zcNOP);
	zvgEcpPutc( zcNOP);
	zvgEcpPutc( zcNOP);
	zvgEcpPutc( zcNOP);
	zvgEcpPutc( zcNOP);
	zvgEcpPutc( zcNOP);

	// wait for command to execute

	err = zvgIsDataAvail( 1000);			// wait for a second for data to show up

	if (err)
		return (err);
	
	// read 4 speed table bytes into buffer

	err = zvgGetMem( speeds, 4, &readLen);

	if (err)
		zvgError( err);

	if (!err && (readLen != 4))
		err = errEcpBadData;

	zvgSetEcpMode();							// go back to ECP mode
	return (err);
}
