/*********************************************************************** 
* NOTICE
* All files contained on this disk are subject to the licensing conditions
* issued by MOTOROLA Inc.
*
* All files are copyright 1993 by MOTOROLA Inc. 
************************************************************************/

/********************************************************
 * File:	smc_t.c
 *
 * Description:
 *	This file contains routines that handle
 *	transmission of data over SMC channels.
 *	The routines here are used for all modes of
 *	operation (UART, transparent and GCI).
 *
 * Routines:
 *	smc_transmit_frames
 *	smc_confirm_frame
 *	smc_transmit_error
 *	smc_driver_conf
 *
 * Author:
 *	Ofrit Lesser
 ********************************************************/


#include "gct.h"
#include "msg.h"
#include "modules.h"
#include "status.h"
#include "types.h"
#include "mtypes.h"
#include "bss.h"
#include "quicc.h"
#include "fatal.h"


extern driver_trace(int type, int id, volatile int par);


/********************************************************
 * routine:	smc_transmit_frames
 *
 * description:
 *	Link as many frames as possible to the corresponding
 *	QUICC transmit BD table.
 *	This routine will either be called after transmission
 *	is requested for a frame, or after frames have been
 *	transmitted by the QUICC.
 *	An entire frame must be linked to the BD table each
 *	time (this is either a header buffer only, or
 *	header buffer + data buffer).
 *	On entry, it is assumed that only the WRAP bit may be
 *	set in any empty BD.
 *
 * arguments:
 *	wa		points to the work area
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
smc_transmit_frames(child)
register CHILD *child;
{
	register WORK_AREA *wa;
	register T_FRAME *n;
	register int i;
	register QUICC_BD *bd1, *bd2;
	register LAYER_INFO *l;
	register QUICC_BD *first;
	unsigned char *soh, *stx, *etx, *eot;
	QUICC_BD *bd3;

	wa = (WORK_AREA *)child->work_area;
	/*
	 * tx_q is the next frame for transmission
	 */
	n = wa->tx_q;
	first = 0;

	while( n ){

		l = &n->layer[LAYER(DRIVER)];

		/*
		 * see if there is room for next t_frame
		 */
		i = ( l->hoff < n->hsize ) ? 1 : 0;

		if( l->boff < n->bsize )
			i++;

		/*
		 * see if enough buffers in table
		 */
		if( wa->tfree < i )
			break;
		wa->tfree -= i;

		bd1 = bd2 = wa->quicc_tbd;

		/*
		 * fill in BD entry
		 */
		if( l->hoff < n->hsize ){
			/*
			 * clear status bit (except wrap bit)
			 * and set length and ptr fields in BD
			 */
			bd1->status &= T_W;
			bd1->buf = &(n->hbuf[l->hoff]);
			bd1->length = n->hsize - l->hoff;
			if( !first )
				first = bd1;
			else
				bd1->status |= T_R;


			/*
			 * prepare index for data buffer
			 */
			if( bd1->status & T_W )
				wa->quicc_tbd = SMC_TBD_ADDR(wa);
			else
				wa->quicc_tbd++;
		}
		if( l->boff < n->bsize ){
			bd2 = wa->quicc_tbd;
			/*
			 * clear status bit
			 * (except wrap and external bits)
			 */
			bd2->status &= T_W;
			bd2->buf = 
				&(n->buf[l->boff]);
			bd2->length = n->bsize - l->boff;
			if( !first )
				first = bd2;
			else
				bd2->status |= T_R;


			/*
			 * prepare index for data buffer
			 */
			if( bd2->status & T_W )
				wa->quicc_tbd = SMC_TBD_ADDR(wa);
			else
				wa->quicc_tbd++;
		}


		/*
		 * set optional bits for
		 * first buffer in frame
		 */
		bd1->status |= wa->BDfirst;

		/*
		 * set per-protocol
		 * status bits in last BD
		 */
		bd2->status |= wa->BDlast;

		l->flags |= LINKED_TO_TABLE;
		if( wa->conf_q )
			driver_trace(TX_REQ, wa->conf_q->hdr.id, (int)n);
		n = l->nextf;
	}

	/*
	 * update pointers and
	 * set Ready bit in first BD
	 */
	if( first ){
		wa->tx_q = n;
		first->status |= T_R;
	}

	/*
	 * issue a restart tx command if the channel was stopped
	 */
	if( child->flags & TX_STOPPED ){
		child->flags &= ~TX_STOPPED;
		if( !(wa->more_flags & SMC_GCI_MODE))
			issue_cmd(RESTART_TX |
				(wa->channel ? 0x90 : 0xd0), wa->quicc);
	}
}



/********************************************************
 * routine:	smc_confirm_frame
 *
 * description:
 *	This is the routine common to all modes of
 *	SMC operation.
 *	It is called after receiving a TX_CONF message
 *	(typically, from an interrupt routine).
 *
 * arguments:
 *	wa		points to the channel's work area
 *
 * return code:
 *	0		no more frames to confirm
 *	otherwise	pointer to next frame for confirming
 *
 * side effects:
 *
 ********************************************************/
T_FRAME *
smc_confirm_frame(wa)
register WORK_AREA *wa;
{
	register QUICC_BD *b;
	register T_FRAME *t, *t1;

	/*
	 * confirm all transmitted BD's
	 */
	t = wa->conf_q;
	t1 = 0;
	if( t ){
		/*
		 * if not linked to the tx table,
		 * there's no need to proceed
		 */
		if( !(t->layer[0].flags & LINKED_TO_TABLE) )
			goto out;

		/*
		 * for each BD of t ...
		 */
		/*
		do {
		*/
			b = wa->quicc_cbd;

			/*
			 * confirm only if transmitted:
			 * if an error was encountered,
			 * the remaining buffers will never be
			 * transmitted.
			 */
			if( b->status & T_R )
				goto out;
			if( b->status & wa->t_error ){
				if( wa->conf_q )
					driver_trace(TX_ERR,
						wa->conf_q->hdr.id, (int)b);
				smc_transmit_error(wa);
				goto out;
			}

			/*
			 * get next index in BD table
			 * for confirming
			 */
			if( b->status & T_W )
				wa->quicc_cbd = SMC_TBD_ADDR(wa);
			else
				wa->quicc_cbd++;

			wa->tfree++;
		/*
		} while( !(b->status & T_L) );
		*/

		wa->tcount++;
		t1 = t;
		t = t->layer[0].nextf;
		t1->hdr.status = 0;
	}

out:
	wa->conf_q = t;
	if( !wa->conf_q )
		wa->req_q = 0;

	return(t1);
}


/********************************************************
 * routine:	smc_transmit_error
 *
 * description:
 *	Handle transmit error on an SMC channel.
 *
 * arguments:
 *	wa		points to work area
 *	id		the channel involved
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
smc_transmit_error(wa)
register WORK_AREA *wa;
{
	register QUICC_BD *bd;
	register QUICC_BD *retx;	/* first BD for retransmission */
	register int first;

	wa->terr_cnt++;

	/*
	 * the QUICC may have set the error bits in the
	 * middle of a frame.
	 * if so, clear all Ready bits until the end
	 * of the frame and re-write the QUICC's tx pointer
	 */
	retx = bd = wa->quicc_cbd;
	first = 0;

	if( (bd->status & T_R) && !(wa->more_flags & SMC_GCI_MODE)){
		issue_cmd(RESTART_TX |
				(wa->channel ? 0x90 : 0xd0), wa->quicc);
		return;
	}

	/* find bd for which error was reported */
	while( !(bd->status & wa->t_error) ){
		first = bd->status & T_L;
		if( bd->status & T_W )
			bd = SMC_TBD_ADDR(wa);
		else
			bd++;
		if( first ) retx = bd;
		/*
		 * no error bits are set:
		 * we must have already taken the faulty buffer
		 */
		if( bd == wa->quicc_cbd )
			return;
	}

	/* now clear Ready bits until end of frame */
	bd = retx;
	while( !(bd->status & T_R) ){
		bd->status &= ~(wa->t_error);
		if( bd != retx )
			bd->status |= T_R;
		if( bd->status & T_L )
			break;
		if( bd->status & T_W )
			bd = SMC_TBD_ADDR(wa);
		else
			bd++;
	}
	retx->status |= T_R;

	/* now write QUICC's internal tx pointer */
	wa->pram->pothers.idma_smc.psmc.u.tbptr =
			(unsigned long)retx -
			(unsigned long)(wa->quicc->udata_bd_ucode);

	if( !(wa->more_flags & SMC_GCI_MODE))
		issue_cmd(RESTART_TX |
				(wa->channel ? 0x90 : 0xd0), wa->quicc);
}



/********************************************************
 * routine:	smc_driver_conf
 *
 * description:
 *	This routine either enables, activates or
 *	disables the functioning of the confirmer.
 *	Since the message is the address of a local
 *	variable, it cannot be sent.
 *
 * arguments:
 *	m		points to message received
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
smc_driver_conf(m)
MSG *m;
{
	GCT *gct;
	CHILD *child;
	long ctype;

	GETGCT(gct);
	child = gct->driver->child + m->hdr.id;
	ctype = (TX_CONF << 16) | m->hdr.id;
	switch( (int)m->param[0] ){
	case ENABLE:
		child->flags &= ~CONF_DISABLE;
		(*child->smac[SUB(TX_CONF)])(&ctype, child);
		break;
	case ACTIVATE:
		child->flags |= CONF_ACTIVATE;
		(*child->smac[SUB(TX_CONF)])(&ctype, child);
		break;
	case DISABLE:
		child->flags |= CONF_DISABLE;
		break;
	default:
		return(BAD_TYPE);
	}
	return(0);
}
