/*********************************************************************** 
* 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:	scc_t.c
 *
 * Description:
 *	This file contains routines that handle
 *	transmission of data over SCC channels.
 *	The routines here are used for all modes of
 *	operation (HDLC, UART and BISYNC).
 *
 * Routines:
 *	transmit_frames
 *	confirm_frame
 *	transmit_error
 *	driver_conf
 *
 * Author:
 *	Jonathan Masel
 ********************************************************/


#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 "bisync.h"
#include "fatal.h"

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

/********************************************************
 * routine:	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:
 *
 ********************************************************/
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++;

		if( l->flags & BISYNC_FLAGS )
			if( l->flags & (BISYNC_SOH | BISYNC_STX) )
				i += 2;

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

		/*
		 * for bisync handlers, the SOH/STX
		 * must be in a BD by itself
		 */
		if( l->flags & BISYNC_FLAGS ){
			{ GWA *gwa; GCT *gct;

			GETGCT(gct);
			gwa = (GWA *)gct->driver->work_area;
			soh = &gwa->soh;
			stx = &gwa->stx;
			etx = &gwa->etx;
			eot = &gwa->eot;
			bd1 = wa->quicc_tbd;
			bd1->status &= T_W;
			if( l->flags & BISYNC_TRANSP )
				bd1->status |= (T_DLE | T_TR);
			if( l->flags & BISYNC_SOH )
				bd1->buf = soh;
			else if( l->flags & BISYNC_STX )
				bd1->buf = stx;
			else
				FATAL("Bisync: bad flags (%x)\n",
					l->flags);
			bd1->length = 1;
			if( !first )
				first = bd1;
			else
				bd1->status |= T_R;

			if( bd1->status & T_W )
				wa->quicc_tbd = TBD_ADDR(wa);
			else
				wa->quicc_tbd++;

			}
		}

		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;
			if( l->flags & BISYNC_TRANSP )
				bd1->status |= T_TR;
			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 = 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;
			if( l->flags & BISYNC_TRANSP )
				bd2->status |= T_TR;
			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 = TBD_ADDR(wa);
			else
				wa->quicc_tbd++;
		}

		if( l->flags & (BISYNC_STX | BISYNC_SOH) ){
			bd3 = wa->quicc_tbd;
			bd3->status &= T_W;
			bd3->status |= (T_I | T_L | T_BCS | T_B);
			if( l->flags & BISYNC_TRANSP )
				bd3->status |= T_DLE;
			if( l->flags & BISYNC_STX )
				bd3->buf = etx;
			else if( l->flags & BISYNC_SOH )
				bd3->buf = eot;
			bd3->length = 1;
			if( !first )
				first = bd3;
			else
				bd3->status |= T_R;
			if( bd3->status & T_W )
				wa->quicc_tbd = TBD_ADDR(wa);
			else
				wa->quicc_tbd++;
		} else
			bd2->status |= (T_I | T_L);

		/*
		 * 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;
		issue_cmd(RESTART_TX | (wa->channel << 6), wa->quicc);
	}

	/*
	 * If transmit on demand flags is active, write
	 * to the transmit on demand register.
	 */
	if( wa->more_flags & SCC_TOD ) {
		wa->regs->scc_todr |= TODR_TOD;	
	}
}



/********************************************************
 * routine:	confirm_frame
 *
 * description:
 *	This is the routine common to all modes of
 *	SCC operations (HDLC, UART and BISYNC).
 *	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 *
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);
				transmit_error(wa);
				goto out;
			}

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

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

		wa->tcount++;
		t1 = t;
		t = t->layer[0].nextf;
		if( (wa->regs->scc_gsmra & MODE) == ETHERNET_PORT )
			t1->hdr.status =  b->status & 0x3ff;
		else
			t1->hdr.status = 0;
	}

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

	return(t1);
}


/********************************************************
 * routine:	transmit_error
 *
 * description:
 *	Handle transmit error on an SCC channel.
 *
 * arguments:
 *	wa		points to work area
 *	id		the channel involved
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
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 ){
		issue_cmd(RESTART_TX | (wa->channel << 6), 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 = 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 = TBD_ADDR(wa);
		else
			bd++;
	}
	retx->status |= T_R;

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

	issue_cmd(RESTART_TX | (wa->channel << 6), wa->quicc);
}



/********************************************************
 * routine:	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:
 *
 ********************************************************/
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);
}
