/*++
 * Copyright (c) 1990-96 Digi International Inc., All Rights Reserved
 * 
 * This software contains proprietary and confidential information of Digi
 * International Inc.  By accepting transfer of this copy, Recipient agrees
 * to retain this software in confidence, to prevent disclosure to others,
 * and to make no use of this software other than that for which it was
 * delivered.  This is an unpublished copyrighted work of Digi International
 * Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
 * prohibited.
 * 
 * Restricted Rights Legend
 *
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
 * Technical Data and Computer Software clause at DFARS 252.227-7031 or
 * subparagraphs (c)(1) and (2) of the Commercial Computer Software -
 * Restricted Rights at 48 CFR 52.227-19, as applicable.
 *
 * Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
 *
 *
 * Last Update: $Date: 10/22/97 11:56a $
 *
 * $Header: /Components/Windows/NT/Sync/Build/Mp570/hwtxsca.c 6     10/22/97 11:56a Darinh $
 *
 * Revision History:
 * $Log: /Components/Windows/NT/Sync/Build/Mp570/hwtxsca.c $
 * 
 * 6     10/22/97 11:56a Darinh
 * Changes to make multiple board installations work better.
 * 
 * 5     9/08/97 3:44p Darinh
 * A variety of changes, including Data General fixes are included.
 * 
 * 4     7/15/97 1:05p Darinh
 * Added DigiLogError statements and message file, altered code to function
 * in DStreams environment and SNA environment with a conditional
 * compilation.
 * 
 * 3     6/06/97 1:48p Darinh
 * Everything seems functional, but more testing is required.
 * 
 * 2     4/08/97 4:55p Darinh
 * Changes made to correspond to the new DigiUtil library.  It
 * successfully transmits data!  I don't know about receiving data yet.
 * 
 * 1     4/02/97 10:08a Darinh
 * Initial checkin; ISA cards initialize, but do not pass data.
 * 
--*/
#include "digilib.h"

#include <ntddk.h>
#include "def570.h"
#include "def64570.h"
#include "board.h"
#include "dsmp.h"
#include "hwsca.h"
#include "hwutils.h"
#include "ioctls.h"
#include "debug.h"


VOID
fnEnableTxMsciInterrupts (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function will enable the Tx interrupt.  The interrupt source is a
	CTS signal change.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	UCHAR   ScaHoldingRegister;


	DigiDump (DIGIFLOW, ("Entering fnEnableTxMsciInterrupts()\n"));

	EnableScaMemMap (pDeviceExt);

	// Enable CTS interrupt only
	ScaHoldingRegister = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->Ie1);
	ScaHoldingRegister |= MSCI_IE1_CCTSE;
	WRT_REG_UCHAR (&pDeviceExt->pScaMsci->Ie1, ScaHoldingRegister);

	// Enable MSCI's master TXINT interrupt
	ScaHoldingRegister = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->Ie0);
	ScaHoldingRegister |= MSCI_IE0_TXINTE;
	WRT_REG_UCHAR (&pDeviceExt->pScaMsci->Ie0, ScaHoldingRegister);

	// Enable Interrupts Controller's Master Transmit Interrupts
	ScaHoldingRegister = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pSca->ComIer0);
	ScaHoldingRegister |= pDeviceExt->ScaIntCtrlTxIntE;
	WRT_REG_UCHAR (&pDeviceExt->pSca->ComIer0, ScaHoldingRegister);

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnEnableTxMsciInterrupts()\n"));
}


VOID
fnEnableTxDmaInterrupts (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function will enable the Transmit DMA interrupt. The
	interrupt sources are :
		. End of transfer (EOT)
		. End of frame (EOM)
		. Buffer underflow (BOF)
			- CDA equal EDA
		. Frame end interrupt counter overflow
			- Too many unprocessed transmitted frames. The FCT value
				increment from 0xFFFF to 0x0000.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	UCHAR   ScaHoldingCell;


	DigiDump (DIGIFLOW, ("Entering fnEnableTxDmaInterrupts()\n"));

	EnableScaMemMap (pDeviceExt);

	// Enable EOT, EOM, BOF, and COF interrupts
	WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Dir, 0xF0);

	// The TX DMA will setup as Chain-Block transfer with single-frame
	WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Dmr,
		(DMAC_DMR_TMOD_CBT | DMAC_DMR_CNTE_CBT_FCTE));

	// Enable DMA interrupts
	ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) & pDeviceExt->pSca->ComIer1);
	ScaHoldingCell |= pDeviceExt->ScaIntCtrlTxDmaIntE;
	WRT_REG_UCHAR (&pDeviceExt->pSca->ComIer1, ScaHoldingCell);

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnEnableTxDmaInterrupts()\n"));
}


VOID
fnEnableTxInterrupts (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function enables the transmit process interrupts.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	DigiDump (DIGIFLOW, ("Entering fnEnableTxInterrupts()\n"));

	if (!(pDeviceExt->IsRxTxEnable & TXMSCI_INTE))
	{
		// Enable MSCI TX Interrupt
		fnEnableTxMsciInterrupts (pDeviceExt);
		pDeviceExt->IsRxTxEnable |= TXMSCI_INTE;
	}

	if (!(pDeviceExt->IsRxTxEnable & TXDMA_INTE))
	{
		// Enable TX DMAC interrupt
		fnEnableTxDmaInterrupts (pDeviceExt);
		pDeviceExt->IsRxTxEnable |= TXDMA_INTE;
	}

	DigiDump (DIGIFLOW, ("Exiting fnEnableTxInterrupts()\n"));
}


VOID
fnEnableTimerInterrupt (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function enables the timer on an SCA.  For port 0, it uses timer 0
	and for port 1, it uses timer 1.  The timer uses the system clock running
	at 9.83MHz.  The clock speed for the timer is divided by 16.  This
	creates a timer tick at every 1.628 usecs.  The driver uses this to
	enable the Tx idle interrupt.  The Tx idle interrupt cannot be set during
	the Tx IDLE state.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	UCHAR   ScaHoldingCell;


	DigiDump (DIGIFLOW, ("Entering fnEnableTimerInterrupt()\n"));

	EnableScaMemMap (pDeviceExt);

	// Set up the SCA Timer registers (with Base clock divided by 16)
	WRT_REG_UCHAR ((PUCHAR) & pDeviceExt->pScaTimer->Tepr, TEPR_ECKS16);

	// Enable timer interrupt
	ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaTimer->Tcsr);
	ScaHoldingCell |= TCSR_ECMIE;
	WRT_REG_UCHAR ((PUCHAR) & pDeviceExt->pScaTimer->Tcsr, ScaHoldingCell);

	// Enable Interrupts Controller's Master Timer Interrupts
	ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) & pDeviceExt->pSca->ComIer2);
	ScaHoldingCell |= pDeviceExt->ScaIntCtrlTimerE;
	WRT_REG_UCHAR ((PUCHAR) & pDeviceExt->pSca->ComIer2, ScaHoldingCell);

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnEnableTimerInterrupt()\n"));
}


VOID
fnDisableTimerInterrupt (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function disables the timer interrupt.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	UCHAR   ScaHoldingCell;


	DigiDump (DIGIFLOW, ("Entering fnDisableTimerInterrupt()\n"));

	EnableScaMemMap (pDeviceExt);

	// Disable the SCA Timer registers
	ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaTimer->Tcsr);
	ScaHoldingCell &= ~TCSR_ECMIE;
	WRT_REG_UCHAR (&pDeviceExt->pScaTimer->Tcsr, ScaHoldingCell);

	// Enable Interrupts Controller's Master Timer Interrupts
	ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pSca->ComIer2);
	ScaHoldingCell &= ~pDeviceExt->ScaIntCtrlTimerE;
	WRT_REG_UCHAR (&pDeviceExt->pSca->ComIer2, ScaHoldingCell);

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnDisableTimerInterrupt()\n"));
}


VOID
fnStopTimer (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function stops the timer.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	UCHAR   ScaHoldingCell;


	DigiDump (DIGIFLOW, ("Entering fnStopTimer()\n"));

	EnableScaMemMap (pDeviceExt);

	ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaTimer->Tcsr);
	// Disable Timer
	ScaHoldingCell &= ~TCSR_TMEE;
	WRT_REG_UCHAR (&pDeviceExt->pScaTimer->Tcsr, ScaHoldingCell);

	WRT_REG_USHORT (&pDeviceExt->pScaTimer->Tcnt, 0);

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnStopTimer()\n"));
}


VOID
fnDisableTxMsciInterrupts (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function disables the Transmit MSCI interrupt.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	UCHAR   ScaHoldingCell;


	DigiDump (DIGIFLOW, ("Entering fnDisableTxMsciInterrupts()\n"));

	EnableScaMemMap (pDeviceExt);

	// Disable CTS
	ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->Ie1);
	ScaHoldingCell &= ~MSCI_IE1_CCTSE;
	WRT_REG_UCHAR (&pDeviceExt->pScaMsci->Ie1, ScaHoldingCell);


	// Disable MSCI's master TXINT interrupt
	ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->Ie0);
	ScaHoldingCell &= ~MSCI_IE0_TXINTE;
	WRT_REG_UCHAR (&pDeviceExt->pScaMsci->Ie0, ScaHoldingCell);

	// Disable Interrupts Controller's Master Transmit Interrupts
	ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pSca->ComIer0);
	ScaHoldingCell &= ~pDeviceExt->ScaIntCtrlTxIntE;
	WRT_REG_UCHAR (&pDeviceExt->pSca->ComIer0, ScaHoldingCell);

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnDisableTxMsciInterrupts()\n"));
}


VOID
fnDisableTxDmaInterrupts (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function disables the Transmit DMAC interrupt.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	UCHAR   ScaHoldingCell;


	DigiDump (DIGIFLOW, ("Entering fnDisableTxDmaInterrupts()\n"));

	EnableScaMemMap (pDeviceExt);

	// Disable EOT, EOM, BOF, and COF interrupts
	WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Dir, 0);

	// Disable DMA interrupts
	ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) & pDeviceExt->pSca->ComIer1);
	ScaHoldingCell &= ~pDeviceExt->ScaIntCtrlTxDmaIntE;
	WRT_REG_UCHAR (&pDeviceExt->pSca->ComIer1, ScaHoldingCell);

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnDisableTxDmaInterrupts()\n"));
}


VOID
fnDisableTxInterrupts (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function disables the Transmit process interrupt.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	DigiDump (DIGIFLOW, ("Entering fnDisableTxInterrupts()\n"));

	if (pDeviceExt->IsRxTxEnable & TXMSCI_INTE)
	{
		// Disable the TX MSCI interrupt
		fnDisableTxMsciInterrupts (pDeviceExt);
		pDeviceExt->IsRxTxEnable &= ~TXMSCI_INTE;
	}

	if (pDeviceExt->IsRxTxEnable & TXDMA_INTE)
	{
		// Disable the TX DMAC interrupt
		fnDisableTxDmaInterrupts (pDeviceExt);
		pDeviceExt->IsRxTxEnable &= ~TXDMA_INTE;
	}

	DigiDump (DIGIFLOW, ("Exiting fnDisableTxInterrupts()\n"));
}


VOID
fnStopTxMsci (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function disables the Transmitter.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	DigiDump (DIGIFLOW, ("Entering fnStopTxMsci()\n"));

	EnableScaMemMap (pDeviceExt);

	WRT_REG_UCHAR (&pDeviceExt->pScaMsci->Cmd, CMD_TX_ABORT);
	WRT_REG_UCHAR (&pDeviceExt->pScaMsci->Cmd, CMD_TX_RESET);
	WRT_REG_UCHAR (&pDeviceExt->pScaMsci->Cmd, CMD_TX_DISABLE);
	pDeviceExt->IsRxTxEnable &= ~TXMSCI_ENABLE;

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnStopTxMsci()\n"));
}


VOID
fnStopTxDma (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function disables the Transmit DMAC.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	DigiDump (DIGIFLOW, ("Entering fnStopTxDma()\n"));

	EnableScaMemMap (pDeviceExt);

	WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Dsr, 0);
	WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Dcr, DCR_SOFTWARE_ABORT);

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnStopTxDma()\n"));
}


VOID
fnStopTransmit (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function stops the Transmitter.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	DigiDump (DIGIFLOW, ("Entering fnStopTransmit()\n"));

	fnStopTxDma (pDeviceExt);
	fnStopTxMsci (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnStopTransmit()\n"));
}


VOID
fnUpdateDmaDsp (
	IN PDEVICE_EXT	pDeviceExt,
	IN USHORT		BytesCopied,
	IN UCHAR		TotalBuffers,
	IN BOOLEAN		LastFrame,
	IN USHORT		TxDspIn)
/*++

Description:

	This function will update the contents of the DMA descriptor.  It will
	setup the following:
	-	Data Length
	-	Number of buffers required to hold this frame (for the first descriptor
		only)
	-	DMA status for the end of frame (EOM) flag.  This flag is set in
		the last descriptor for this frame.

Parameters:

	pDeviceExt
		Address of the Device extension.

	BytesCopied
		The data length.

	TotalBuffers
		The total number of buffer required to hold this frame.

	LastFrame
		Indicates if this is the last frame in the DMA chain.

	TxDspIn
		The current index in the transmit descriptor array.

Return Value:

	None.

--*/
{
	DMA_CHAIN	TxDmaDsp;


	DigiDump (DIGIFLOW, ("Entering fnUpdateDmaDsp()\n"));

	// Copy the contents of the current descriptor
	fnReadFromCardMemory (
		(ULONG_PTR) &pDeviceExt->pTxRealDmaChainsArray[TxDspIn],
		(PUCHAR) &TxDmaDsp,
		(USHORT) DMA_CHAIN_SIZE,
		pDeviceExt);

	// Data length in the buffer
	TxDmaDsp.DataLen = BytesCopied;

	// Number of buffers used for this frame
	TxDmaDsp.FrameStatus = TotalBuffers;

	TxDmaDsp.DmaStatus = (LastFrame) ? DMAC_STATUS_EOTEOM : 0;

	// Copy the descriptor back into shared memory
	fnCopyToCardMemory (
		(PUCHAR) &TxDmaDsp,
		(ULONG_PTR) &pDeviceExt->pTxRealDmaChainsArray[TxDspIn],
		(USHORT) DMA_CHAIN_SIZE,
		pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnUpdateDmaDsp()\n"));
}


VOID
fnSetupDma (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function sets up the descriptors in the Transmit DMAC.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	DigiDump (DIGIFLOW, ("Entering fnSetupDma()\n"));

	EnableScaMemMap (pDeviceExt);

	WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Cpb, pDeviceExt->pTxDmaDspCpb);
	WRT_REG_USHORT (&pDeviceExt->pScaTxDmac->Eda, pDeviceExt->pTxDmaDspEda);
	WRT_REG_USHORT (&pDeviceExt->pScaTxDmac->Cda, pDeviceExt->pTxDmaDspCda);

	DigiDump (DIGIVERYLOUD, (
		"  Tx Local CDA   = %x\n"
		"  Tx Local EDA   = %x\n"
		"  Tx Local CPB   = %x\n"
		"  Tx SCA   CDA   = %x\n"
		"  Tx SCA   EDA   = %x\n"
		"  Tx SCA   CPB   = %x\n",
		(USHORT) pDeviceExt->pTxDmaDspCda,
		(USHORT) pDeviceExt->pTxDmaDspEda,
		(UCHAR) pDeviceExt->pTxDmaDspCpb,
		READ_REGISTER_USHORT ((PUSHORT) &pDeviceExt->pScaTxDmac->Cda),
		READ_REGISTER_USHORT ((PUSHORT) &pDeviceExt->pScaTxDmac->Eda),
		READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaTxDmac->Cpb)));

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnSetupDma()\n"));
}


VOID
fnStartTxMsci (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function will setup the MSCI transmitter.

Parameters:

	pDeviceExt
		Address of the Device extension

Return Value:

	None.

--*/
{
	DigiDump (DIGIFLOW, ("Entering fnStartTxMsci()\n"));

	EnableScaMemMap (pDeviceExt);

	WRT_REG_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->Trc0, 0x1F);
	WRT_REG_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->Trc1, 0x1F);

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnStartTxMsci()\n"));
}


VOID
fnStartTxDma (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function will setup the Transmit DMA mode for Chained-block, single-
	frame transfer.  Then enable the Transmit DMA channel and MSCI transmitter.

Parameters:

	pDeviceExt
		Address of the Device Extension.

Return Value:

	None.

--*/
{
	DigiDump (DIGIFLOW, ("Entering fnStartTxDma()\n"));

	EnableScaMemMap (pDeviceExt);

	// Enable the TX DMA channel and MSCI Transmitter
	WRT_REG_UCHAR ((PUCHAR) &pDeviceExt->pScaTxDmac->Dsr, DMAC_DSR_DEE);
	WRT_REG_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->Cmd, CMD_TX_ENABLE);

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnStartTxDma()\n"));
}


VOID
fnStartTransmit (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	Start the transmit process.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	DigiDump (DIGIFLOW, ("Entering fnStartTransmit()\n"));

	fnStartTxMsci (pDeviceExt);			// Setup the MSCI transmitter
	fnRaiseRts (pDeviceExt);			// Raise RTS signal

	// If we are not waiting for CTS to Drop or Go High all is clear and we
	// can transmit
	if (!pDeviceExt->TxWaitCts && !pDeviceExt->TxWaitCtsDrop)
	{
		fnStartTxDma (pDeviceExt);		// Start the TX DMA and MSCI Transmitter
	}

	DigiDump (DIGIFLOW, ("Exiting fnStartTransmit()\n"));
}


VOID
fnCheckCtsStatus (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function is called when a Transmit Interrupt is generated
	by the SCA. The sources of this interrupt are:
	. CTS signal change
		- If the Device is waiting for CTS to raise, it will start the
		transmission.
	. Transmit Idle
		- If the DropRtsFlag is 2, it means the Device wants to drop
		the RTS. After the Transmitter enters the Transmit Idle
		state, it needs to wait one character (for closing flag)
		time in order to drop RTS safely.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	volatile UCHAR			St1RegVal = 0, St3RegVal = 0, RegVal = 0;
	UCHAR					NumDsp = 0, bCnt = 0, ScaHoldingCell = 0;


//	DigiDump (DIGIFLOW, ("Entering fnCheckCtsStatus()\n"));

	EnableScaMemMap (pDeviceExt);

	St1RegVal = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->St1);
	St3RegVal = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->St3);

	DisableMemory (pDeviceExt);

	// Change in CTS status
	if (St1RegVal & MSCI_ST1_CCTSM)
	{

		EnableScaMemMap (pDeviceExt);

		// Clear the CTS Change bit
		ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->St1);
		ScaHoldingCell |= MSCI_ST1_CCTSM;
		WRT_REG_UCHAR (&pDeviceExt->pScaMsci->St1, ScaHoldingCell);

		DisableMemory (pDeviceExt);

		// Is CTS Up?
		if (!(St3RegVal & MSCI_ST3_CTSM))
		{
			// Waiting for CTS to go up?  Now we can start TX
			if (pDeviceExt->TxWaitCts)
			{
				fnStartTxDma (pDeviceExt);
				pDeviceExt->TxWaitCts = FALSE;
			}
		}
		// CTS dropped
		else
		{

			EnableScaMemMap (pDeviceExt);
			RegVal = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaTxDmac->Dsr);
			DisableMemory (pDeviceExt);

			// During transmission
			if (RegVal & DMAC_DSR_DEE)
			{
				
				fnStopTransmit (pDeviceExt);

				// Restart transmit on CTS drop
				pDeviceExt->TxWaitCts = FALSE;

				NumDsp = ReadAndClearFrameStatus (
					(ULONG_PTR) &pDeviceExt->pTxRealDmaChainsArray[pDeviceExt->TxDspOut],
					pDeviceExt);

				// Update the Interface Record for transmit buffer space
				pDeviceExt->IR.TransmitBufferSpace += NumDsp * DEFAULT_FRAME_SIZE;

				// Update the index
				for (bCnt = 0; bCnt < NumDsp; bCnt++)
				{
					UpdateInOutIndex (&pDeviceExt->TxDspOut, pDeviceExt);
				}
				--pDeviceExt->NumberOfTxFrames;

				++pDeviceExt->IR.StatisticsCounters[SC_CTSDROP];
				++pDeviceExt->IR.StatusEvents;
				pDeviceExt->SignalService = TRUE;
				pDeviceExt->SignalService = TRUE;

				// Check to determine whether to drop the RTS
				if (!(pDeviceExt->LinkOptions & LO_4WIRE))
				{
					// Switch RTS
					if ((!pDeviceExt->NumberOfTxFrames) &&
						(pDeviceExt->bSdlcCtrl & POLLBIT_ON) &&
						(pDeviceExt->PortType != PT_X21))
					{

						EnableScaMemMap (pDeviceExt);
						// Drop RTS
						ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->Ctl);
						ScaHoldingCell |= MSCI_CTL_RTS_HIGH;
						WRT_REG_UCHAR (&pDeviceExt->pScaMsci->Ctl, ScaHoldingCell);
						DisableMemory (pDeviceExt);						
					}
				}

				pDeviceExt->pTxDmaDspCda =
					 &pDeviceExt->pTxRealDmaChainsArray[pDeviceExt->TxDspOut];

				if (pDeviceExt->NumberOfTxFrames)
				{
					if (pDeviceExt->pTxDmaDspCda == pDeviceExt->pTxDmaDspEda)
					{
						// Need to setup the DMA and start the DMA
						pDeviceExt->pTxDmaDspEda =
							&pDeviceExt->pTxRealDmaChainsArray[pDeviceExt->TxDspIn];
					}

					fnSetupDma (pDeviceExt);	// Setup the descriptors
					fnStartTransmit (pDeviceExt);
				}
			}
			else
			{

				// If we are waiting for CTS to drop in a non Contstant RTS
				// mode (half-duplex at the Link level) then we can transmit.
				if (pDeviceExt->TxWaitCtsDrop)
				{
					pDeviceExt->TxWaitCtsDrop = FALSE;
					fnStartTransmit (pDeviceExt);

				}

			}
		}
	}

	if ((pDeviceExt->DropRtsFlag == 2) && (St1RegVal & MSCI_ST1_IDLM))
	{
		fnRtsDropDelay(pDeviceExt,(USHORT) 1);		
	}

	DisableMemory (pDeviceExt);

//	DigiDump (DIGIFLOW, ("Exiting fnCheckCtsStatus()\n"));
}


VOID
fnRtsDropDelay (
	IN PDEVICE_EXT	pDeviceExt,
	IN USHORT		iMultiplier)
{

	UCHAR	ScaHoldingCell = 0;
	USHORT  usRtsTimerDrop = pDeviceExt->DropRtsTimer * iMultiplier;

	//
	// The transmitter has entered TX Idle state, need to delay for one
	// character time (for closing flag), then it is safe to drop RTS
	//
	EnableScaMemMap (pDeviceExt);

	ScaHoldingCell = READ_REGISTER_UCHAR (
		(PUCHAR) &pDeviceExt->pScaMsci->Ie1) & ~MSCI_IE1_IDLE;
	WRT_REG_UCHAR (&pDeviceExt->pScaMsci->Ie1, ScaHoldingCell);

	pDeviceExt->DropRtsFlag = 3;
	DisableMemory (pDeviceExt);

	// Delay one character time
	fnSetAndStartTimer (pDeviceExt, usRtsTimerDrop);

}


VOID
fnCheckTxDmaError (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function checks for Transitted DMA errors.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	volatile UCHAR	RegVal = 0;
	BOOLEAN			Reset = FALSE;


//	DigiDump (DIGIFLOW, ("Entering fnCheckTxDmaError()\n"));

	EnableScaMemMap (pDeviceExt);

	RegVal = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaTxDmac->Dsr);

	if (RegVal & DMAC_DSR_BOFM)
	{
		// CDA == EDA and DMA is enabled
//		DigiDump (DIGIERRORS, ("*** TxDmaError:BOF (Buffer Overflow/Underflow)\n"));
		WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Dsr, DMAC_DSR_CLEAR_BOF);
		++pDeviceExt->Stats.TxUnderrun;
		Reset = TRUE;
	}

	if (RegVal & DMAC_DSR_COFM)
	{
		// FCT overrun
//		DigiDump (DIGIERRORS, ("*** TxDmaError:COF (Counter Overflow)\n"));
		WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Dsr, DMAC_DSR_CLEAR_COF);
		WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Dcr, DMAC_DCR_DMA_SW_ABORT);
		WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Dcr, DMAC_DCR_CLEAR_FCT);
		Reset = TRUE;
	}

//	DigiDump (DIGIERRORS, (
//		"*** TX DMA DSR = %x\n"
//		"Tx Local CDA   = %x\n"
//		"Tx Local EDA   = %x\n"
//		"Tx SCA   CDA   = %x\n"
//		"Tx SCA   EDA   = %x\n"
//		"TxDspIn        = %x\n"
//		"TxDspOut       = %x\n"
//		"Tx Frames      = %x\n"
//		"Port           = %x\n",
//		RegVal,
//		(USHORT) pDeviceExt->pTxDmaDspCda,
//		(USHORT) pDeviceExt->pTxDmaDspEda,
//		READ_REGISTER_USHORT ((PUSHORT) &pDeviceExt->pScaTxDmac->Cda),
//		READ_REGISTER_USHORT ((PUSHORT) &pDeviceExt->pScaTxDmac->Eda),
//		(USHORT) pDeviceExt->TxDspIn,
//		(USHORT) pDeviceExt->TxDspOut,
//		pDeviceExt->NumberOfTxFrames,
//		pDeviceExt->PortNumber));

	--pDeviceExt->Stats.TxFrames;
	//  FixMe - need to figure out bytes here also

	if (Reset)
	{
		fnDisableTxInterrupts (pDeviceExt);
		fnStopTransmit (pDeviceExt);
		fnInitializeTxDmaChains (pDeviceExt);
		++pDeviceExt->IR.StatisticsCounters[SC_TXUNDERRUN];
		++pDeviceExt->Stats.TxUnderrun;
		++pDeviceExt->IR.StatusEvents;
		pDeviceExt->SignalService = TRUE;	// need to signal link service
		fnEnableTxInterrupts (pDeviceExt);
		pDeviceExt->IR.TransmitBufferSpace =
			(pDeviceExt->NumberOfDmaChains - 1) * DEFAULT_FRAME_SIZE;
	}

	DisableMemory (pDeviceExt);

//	DigiDump (DIGIFLOW, ("Exiting fnCheckTxDmaError()\n"));
}


VOID
fnCheckTxDmaGood (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function is called when one or more frames are transmitted
	successfully.  This function will process all the unprocessed
	outstanding transmitted frames. After it has processed all the
	transmitted frames, it will check whether it needs to drop the
	RTS signal.  The conditions for dropping the RTS signal are:
		. No more frames to send
		. The POLL/FINAL bit is set in the last transmitted frame
		. The user has configured switch RTS

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	UCHAR			NumDsp, bCnt;
	volatile UCHAR	RegVal, FctCount;
	UCHAR			ScaHoldingCell;


//	DigiDump (DIGIFLOW, ("Entering fnCheckTxDmaGood()\n"));

	EnableScaMemMap (pDeviceExt);

	// Number of unprocess transmitted frames
	FctCount = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaTxDmac->Fct);

	// Interrupt sources
	RegVal = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaTxDmac->Dsr);

	DisableMemory (pDeviceExt);

	// End of frame interrupt
	if (RegVal & DMAC_DSR_EOMM)
	{
		do
		{
			EnableScaMemMap (pDeviceExt);

			// Clear the EOM which will decrement the FCT
			WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Dsr, DMAC_DSR_CLEAR_EOM);

			DisableMemory (pDeviceExt);

			NumDsp = ReadAndClearFrameStatus (
				(ULONG_PTR) &pDeviceExt->pTxRealDmaChainsArray[pDeviceExt->TxDspOut],
				pDeviceExt);

			// Update the Interface Record for transmit buffer space
			pDeviceExt->IR.TransmitBufferSpace += NumDsp * DEFAULT_FRAME_SIZE;

			// Update the index
			for (bCnt = 0; bCnt < NumDsp; bCnt++)
			{
				UpdateInOutIndex (&pDeviceExt->TxDspOut, pDeviceExt);
			}
			--pDeviceExt->NumberOfTxFrames;
		} while (--FctCount);

		// Transfer done, clear EOT
		EnableScaMemMap (pDeviceExt);
		if (RegVal & DMAC_DSR_EOTM)
		{
			WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Dsr, DMAC_DSR_CLEAR_EOT);
		}
		DisableMemory (pDeviceExt);

		// Check to determine whether to drop the RTS
		if (!(pDeviceExt->LinkOptions & LO_4WIRE))
		{

			// Switch RTS
			if ((!pDeviceExt->NumberOfTxFrames) &&
				(pDeviceExt->bSdlcCtrl & POLLBIT_ON) &&
				(pDeviceExt->PortType != PT_X21))
			{
				pDeviceExt->DropRtsFlag = 1;
				pDeviceExt->TxWaitCts = TRUE;
				pDeviceExt->FifoWatchdog = 0;
				fnSetAndStartTimer (pDeviceExt, pDeviceExt->TxIdleTimer);
			}
		}

		pDeviceExt->pTxDmaDspCda =
			&pDeviceExt->pTxRealDmaChainsArray[pDeviceExt->TxDspOut];

		// There are frames pending to send
		if (pDeviceExt->pTxDmaDspCda != pDeviceExt->pTxDmaDspEda)
		{
			// Just start the DMA again. We already set it up.
			EnableScaMemMap (pDeviceExt);

			// Enable TX DMA
			WRT_REG_UCHAR (&pDeviceExt->pScaTxDmac->Dsr, DMAC_DSR_DEE);

			DisableMemory (pDeviceExt);
		}
	}

	pDeviceExt->SignalService = TRUE;	// need to signal link service

//	DigiDump (DIGIFLOW, ("Exiting fnCheckTxDmaGood()\n"));
}


VOID
fnRaiseRts (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function will raise RTS when the Device has a frame to send.  It will
	retrieve the CTS signal first to check if we can send out the frame
	immediately.

Parameters:

	pDeviceExt
		Address of the Device extension

Return Value:

	None.

--*/
{
	UCHAR   ScaHoldingCell = 0;
	UCHAR	St3 = 0, Rts = 0;


	DigiDump (DIGIFLOW, ("Entering fnRaiseRts()\n"));

	EnableScaMemMap (pDeviceExt);

	// Check CTS
	St3 = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->St3);
	if (St3 & MSCI_ST3_CTSM)
	{


#ifdef DBG_INTERRUPT
		pDeviceExt->TxWaitCts = FALSE;
#else
		if (PT_X21 == pDeviceExt->PortType)
			pDeviceExt->TxWaitCts = FALSE;
		else
			if (pDeviceExt->CtsRequired)
				pDeviceExt->TxWaitCts = TRUE;	   // Need to wait for CTS up
#endif
	}
	else
	{

		pDeviceExt->TxWaitCts = FALSE;

		// If constant RTS is not set this means we are running half duplex.
		// If CTS is up and RTS is low that means that we have CTS has not
		// dropped from our last poll.  We need to wait for the drop interrupt
		// and then send the data.
		if (!(pDeviceExt->LinkOptions & LO_4WIRE))
		{

			UCHAR Rts;

			// Get RTS
			Rts = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->Ctl);

			// If RTS is low then the other end is holding CTS high
			if (Rts & MSCI_CTL_RTS_HIGH)
			{

				pDeviceExt->TxWaitCtsDrop = TRUE; // Wait for CTS to drop 

			}
			else
			{

				// If we are in the process of dropping RTS, then we will stop
			    // that process.  This is not usually the case, but it doesn't
				// hurt to turn off the timer and the interrupt.  We might need
				// to turn off the time and interrupt if we didn't complete the
				// last RTS drop (This should not happen if our delay timers are
				// correct for dropping RTS.  If we wait moe than a character delay
				// it is possible the frame made it to the other end and it polled
				// use again.  The delay would have to be quite long.)

				// Disable the Drop RTS Process Timer
				ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaTimer->Tcsr);
				ScaHoldingCell &= ~TCSR_TMEE;
				WRT_REG_UCHAR (&pDeviceExt->pScaTimer->Tcsr, ScaHoldingCell);

				ScaHoldingCell = READ_REGISTER_UCHAR (
					(PUCHAR) &pDeviceExt->pScaMsci->Ie1) & ~MSCI_IE1_IDLE;
				WRT_REG_UCHAR (&pDeviceExt->pScaMsci->Ie1, ScaHoldingCell);

			}

		}
	
	}


	if (!pDeviceExt->TxWaitCtsDrop)
	{

		// Check RTS
		Rts = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->Ctl);
		if (Rts & MSCI_CTL_RTS_HIGH)
		{
			// Raise RTS
			ScaHoldingCell = Rts & ~MSCI_CTL_RTS_HIGH;
			WRT_REG_UCHAR (&pDeviceExt->pScaMsci->Ctl, ScaHoldingCell);
		}
				
		pDeviceExt->DropRtsFlag = 0;

	}

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnRaiseRts()\n"));
}


VOID
fnCheckTimer (
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function is called when a timer expires. There are two types
	of timer interrupts:
		1. To enable the TX Idle Interrupt when the TX Idle bit in the
			MSCI Status Register 1 is not set. The TX Idle Interrupt can
			not be set if the TX Idle bit in MSCI ST1 is set.
		2. To drop the RTS signal when the closing flag has been sent.

Parameters:

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
	volatile USHORT	TcntRegVal;
	volatile UCHAR	RegVal;
	UCHAR			ScaHoldingCell;


//	DigiDump (DIGIFLOW, ("Entering fnCheckTimer()\n"));

	EnableScaMemMap (pDeviceExt);

	// Need to clear the timer registers in order for timer
	// to generate another interrupt.
	RegVal = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaTimer->Tcsr);
	TcntRegVal = READ_REGISTER_USHORT ((PUSHORT) &pDeviceExt->pScaTimer->Tcnt);

	// Retrieve MSCI Status 1 (for TX Idle)
	RegVal = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->St1);

	DisableMemory (pDeviceExt);

	switch (pDeviceExt->DropRtsFlag)
	{
	case 1:						   // Need to set Tx Idle interrupt

		if (!(RegVal & MSCI_ST1_IDLM))	// Is TX Idle bit set?
		{
			// The bit is not set, save to enable the TX Idle interrupt
			EnableScaMemMap (pDeviceExt);

			// Enable TX IDLE interrupt
			ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->Ie1);
			ScaHoldingCell |= MSCI_IE1_IDLE;
			WRT_REG_UCHAR ((PUCHAR) & pDeviceExt->pScaMsci->Ie1, ScaHoldingCell);
			DisableMemory (pDeviceExt);

			// Set a Gross Timer Value.  There is a not so rare case were the idle 
			// bit is not set, but gets set before we enable the TX IDLE interrupt.
			// We need this delay to be long enough to transmit two bytes
			// on a 2400 baud link. The gross timeout is then handled by case 2.
			//
			// This fix was added on-site. A better fix would be to rewrite the
			// RTS/CTS logic to use the auto-mode RTS/CTS feature of the board.
			// This procedure is documented on page 35 of Appendix D in the HD64570
			// manual.  Although we can't get an interrupt to find out when we can
			// drop RTS polling the SLOOP bit is still better.  It also eliminates
			// the need to know the line speed when dropping RTS.
			fnSetAndStartTimer (pDeviceExt, (USHORT) 10000);


			pDeviceExt->DropRtsFlag =2;

		} else {

			if (pDeviceExt->FifoWatchdog++ > 10) {

				// If we get into this case, the frame has already left the box,
				// and RTS will never drop. We don't know if the frame has left
				// the box or not so to be safe we keep RTS high for the length
				// of the transmit FIFO plus the CRC and flag.
				//
				// This fix was added on-site. A better fix would be to rewrite the
				// RTS/CTS logic to use the auto-mode RTS/CTS feature of the board.
				// This procedure is documented on page 35 of Appendix D in the HD64570
				// manual.  Although we can't get an interrupt to find out when we can
				// drop RTS polling the SLOOP bit is still better.  It also eliminates
				// the need to know the line speed when dropping RTS.

				fnRtsDropDelay(pDeviceExt,(USHORT) 36);
				
			}

		}
		break;

	case 2:

		// Drop RTS
		fnRtsDropDelay(pDeviceExt,(USHORT)2);	
		break;

	case 3:						   // After closing flag

		EnableScaMemMap (pDeviceExt);

		// Drop RTS signal
		ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaMsci->Ctl);
		ScaHoldingCell |= MSCI_CTL_RTS_HIGH;
		WRT_REG_UCHAR ((PUCHAR) & pDeviceExt->pScaMsci->Ctl, ScaHoldingCell);

		// Disable Timer
		ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaTimer->Tcsr);
		ScaHoldingCell &= ~TCSR_TMEE;
		WRT_REG_UCHAR (&pDeviceExt->pScaTimer->Tcsr, ScaHoldingCell);

		WRT_REG_USHORT (&pDeviceExt->pScaTimer->Tcnt, 0);

		DisableMemory (pDeviceExt);
		pDeviceExt->DropRtsFlag = 0;
		return;
	}

	EnableScaMemMap (pDeviceExt);

	// Reset up-counter to zero
	WRT_REG_USHORT (&pDeviceExt->pScaTimer->Tcnt, 0);

	DisableMemory (pDeviceExt);

//	DigiDump (DIGIFLOW, ("Exiting fnCheckTimer()\n"));
}


VOID
fnSetAndStartTimer (
	IN PDEVICE_EXT	pDeviceExt,
	IN USHORT		TimeOutValue)
/*++

Description:

	This function will setup the timer counter and start the timer.

Parameters:

	pDeviceExt
		Address of the Device extension.

	TimeOutValue
		Timer counter time out value.

Return Value:

	None.

--*/
{
	UCHAR   ScaHoldingCell;


	DigiDump (DIGIFLOW, ("Entering fnSetAndStartTimer()\n"));

	EnableScaMemMap (pDeviceExt);

	// Initialize the Timer Up-Counter
	WRT_REG_USHORT (&pDeviceExt->pScaTimer->Tcnt, 0);

	// Counter for time out value
	WRT_REG_USHORT (&pDeviceExt->pScaTimer->Tcon, TimeOutValue);

	// Start the timer
	ScaHoldingCell = READ_REGISTER_UCHAR ((PUCHAR) &pDeviceExt->pScaTimer->Tcsr);
	ScaHoldingCell |= TCSR_TMEE;
	WRT_REG_UCHAR (&pDeviceExt->pScaTimer->Tcsr, ScaHoldingCell);

	DisableMemory (pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting fnSetAndStartTimer()\n"));
}


UCHAR
ReadAndClearFrameStatus (
	IN ULONG_PTR	RealAddress,
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This function will read the desired descriptor, read and then clear the
	FrameStatus from card memory.

Parameters:

	RealAddress
		Card offset

	pDeviceExt
		Address of the device extension

Return Value:

	NumDsp - the value from the FrameStatus variable.

--*/
{
	DMA_CHAIN	TxDmaDsp;
	UCHAR		NumDsp = 0;

	DigiDump (DIGIFLOW, ("Entering ReadAndClearFrameStatus()\n"));

	fnReadFromCardMemory (
		RealAddress,
		(PUCHAR) &TxDmaDsp,
		(USHORT) DMA_CHAIN_SIZE,
		pDeviceExt);

	NumDsp = TxDmaDsp.FrameStatus;
	TxDmaDsp.FrameStatus = 0;

	fnCopyToCardMemory (
		(PUCHAR) &TxDmaDsp,
		RealAddress,
		(USHORT) DMA_CHAIN_SIZE,
		pDeviceExt);

	DigiDump (DIGIFLOW, ("Exiting ReadAndClearFrameStatus()\n"));

	return (NumDsp);
}


//
//  Here's a helper function that did not really fit anywhere else
//
VOID
UpdateInOutIndex (
	IN PUSHORT		DspInOut,
	IN PDEVICE_EXT	pDeviceExt)
/*++

Description:

	This routine will increment the In/Out index value, and account for
	wrapping in the array.

Parameters:

	DspInOut
		Dsp In or Out Index value

	pDeviceExt
		Address of the Device extension.

Return Value:

	None.

--*/
{
//	DigiDump (DIGIFLOW, ("Entering UpdateInOutIndex()\n"));

	*DspInOut = ++(*DspInOut) % pDeviceExt->NumberOfDmaChains;

//	DigiDump (DIGIFLOW, ("Exiting UpdateInOutIndex()\n"));
}

