/*********************************************************************** 
* 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:	dma_t.c
 *
 * Description:
 *	This file contains routines that handle
 *	transmission of data over IDMA channels.
 *
 * Routines:
 *	dma_multi_tx
 *	dma_frame
 *	dma_multi_conf
 *	dma_confirm_frame
 *	dma_multi_terr
 * 	dma_error
 *	dma_multi_abort
 *
 * 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 "dma.h"
#include "fatal.h"



/********************************************************
 * routine:	dma_multi_tx
 *
 * description:
 *	Request for transmission of an IDMA channel.
 *	T_FRAMEs are already linked via the driver's
 *	nextf pointer. It holds them in a linked list,
 *	linking them to the QUICC's BD table as slots
 *	become available.
 *
 * arguments:
 *	t		points to first T_FRAME for tx
 *	child           the child structure
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/

dma_multi_tx(t, child)
register T_FRAME *t;
CHILD *child;
{
	register WORK_AREA *wa;
	R_FRAME *r;
	extern R_FRAME *tx_to_rx();
	QUICC *quicc;
	int	channel;

	wa = (WORK_AREA *)child->work_area;
	quicc = wa->quicc;
	channel = wa->channel;

	if( !wa->req_q )
		wa->tx_q = wa->conf_q = t;
	else {
		wa->req_q->layer[0].nextf = t;
		if( !wa->tx_q )
			wa->tx_q = t;
	}

	/*
	 * find new end of queue
	 */
	while( t->layer[0].nextf )
		t = t->layer[0].nextf;
	wa->req_q = t;

	dma_frame(child);

	/*
	 * If IDMA is not transffering now than 
	 * start bit should be set.
	 * Otherwise start bit is set when DONE or ERROR
	 * interrupt occures.
	 */
	if (child->flags & DMA_IDLE) {
		child->flags &= ~DMA_IDLE;
		IDMA_CMR_WRT(channel, IDMA_CMR(channel) | STR_BIT);
	}
}


/********************************************************
 * routine:	dma_frame
 *
 * description:
 *	Link as many frames as possible to the corresponding
 *	QUICC idma BD table.
 *	This routine will either be called after IDMA
 *	is requested for a frame, or after the IDMA has been
 *	completed 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:
 *	child		the child structure
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
dma_frame(child)
CHILD *child;
{
	register WORK_AREA *wa;
	register T_FRAME *n;
	register R_FRAME *r, *r1, *r2;
	int	i;
	register IDMA_BD *bd1, *bd2;
	register IDMA_BD *first;
	LAYER_INFO *l;
	unsigned char *dest;
	QUICC *quicc;
	int	channel;
	R_FRAME *getr();

	wa = (WORK_AREA *)child->work_area;
	quicc = wa->quicc;
	channel = wa->channel;

	/*
	 * tx_q is the next frame for transmission
	 */
	n = wa->tx_q;
	first = 0;

	r = wa->pool_q;

	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;
		if( child->flags & DMA_TOPERIPH )
			if( IDMA_CMR(channel) & ECO_BIT)
				l->flags |= DMA_TO_PERIPH;
			else
				l->flags |= DMA_FR_PERIPH;

		r1 = r2 = 0;
		if (!(l->flags & DMA_TO_PERIPH)) {
			/*
			 * if a frame was allready allocated
			 */
			if (wa->rtp_q){
				r1 = wa->rtp_q;
				wa->rtp_q = 0;
			}
			else
				r1 = getr((unsigned short)R_BUFG,
						child->key);
			r1->flags = 0;    /* must zero RX_POOL flag */
			if (!r1)
				break;
			if (i == 2){
				r2 = getr((unsigned short)R_BUFG,
						child->key);
				r2->flags = 0;    /* must zero RX_POOL flag */
				if (!r2) {
					wa->rtp_q = r1;
					break;
				}
			}

			/*
			 * add frames to the dma destination list
			 */
			if (!wa->rx_q) 
				wa->rx_q = wa->pool_q = r1;
			else
			{
				wa->pool_q->nextb = r1;
				wa->pool_q = r1;
			}
		}
					

		wa->tfree -= i;

		bd1 = bd2 = (IDMA_BD *)wa->quicc_tbd;

		/*
		 * fill in BD entry
		 */
		if( l->hoff < n->hsize ){
			/*
			 * next rx frame for transferring
			 */
			r = wa->pool_q;
			/*
			 * clear status bit (except wrap bit)
			 * and set length and ptr fields
			 * in BD
			 */
			bd1->status &= I_W;
			bd1->sbuf = &(n->hbuf[l->hoff]);
			bd1->length = n->hsize - l->hoff;
			if( !first )
				first = bd1;
			else
				bd1->status |= I_V;

			/*
			 * If not peripheral use allocated rx buffer
			 */
			if (l->flags & DMA_TO_PERIPH)
				/* address of peripheral */
				bd1->dbuf = (unsigned char *)
					IDMA_DAPR_ADR(channel);
			else {

				bd1->dbuf = &r->buf[r->blen];
				/*
				 * Add the second rx frame to
				 * rx queue
				 */
				if (r2) {
					wa->pool_q->nextb = r2;
					wa->pool_q = r2;
				}
			}

			/*
			 * prepare index for data buffer
			 */
			if( bd1->status & T_W )
				wa->quicc_tbd =
					(QUICC_BD *)IBD_ADDR(wa);
			else
				wa->quicc_tbd = (QUICC_BD *)
					((unsigned int)wa->quicc_tbd
					+ sizeof(IDMA_BD));
		}
		if( l->boff < n->bsize ){
			/*
			 * next rx frame for transferring
			 */
			r = wa->pool_q;
			 
			bd2 = (IDMA_BD *)wa->quicc_tbd;
			/*
			 * clear status bit
			 * (except wrap and external bits)
			 */
			bd2->status &= I_W;
			bd2->sbuf = 
				&(n->buf[l->boff]);
			bd2->length = n->bsize - l->boff;
			if( !first )
				first = bd2;
			else
				bd2->status |= I_V;

			/*
			 * prepare index for next buffer
			 */
			if( bd2->status & I_W )
				wa->quicc_tbd = (QUICC_BD *)IBD_ADDR(wa);
			else
				wa->quicc_tbd = (QUICC_BD *)
					((unsigned int)wa->quicc_tbd
					+ sizeof(IDMA_BD));

			/*
			 * If not peripheral allocate rx buffer
			 */
			if (l->flags & DMA_TO_PERIPH)
				/* address of peripheral */
				bd1->dbuf = (unsigned char *)
						IDMA_DAPR(channel);
			else
				bd2->dbuf = &r->buf[r->blen];

		}

		/*
		 * 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, n);
		n = l->nextf;
	}

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


/********************************************************
 * routine:	dma_multi_conf
 *
 * description:
 *	Confirm transferred frames. This routine
 *	is reached as result of a TX_CONF (primitive)
 *	message sent to the driver module by the
 *	IDMA interrupt routine handler.
 *	It implies that transferring of an IDMA
 *	frame has been completed.
 *	Note that dma_frame is called to possibly link
 *	new frames to the BD entries that have been
 *	released.
 *	The message sent is only a 32-bit code (not an
 *	allocated MSG structure) and therefore must
 *	not be relm-ed. Its structure corresponds to
 *	the first 32-bits of a normal HDR structure.
 *
 *
 * arguments:
 *	h	dummy 32-bit message from interrupt routine
 *	child	the child structure involved
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
dma_multi_conf(h, child)
HDR *h;
CHILD *child;
{
	WORK_AREA *wa;
	register T_FRAME *t;
	register R_FRAME *r;
	LAYER_INFO *l;
	int i = 0;
	GCT *gct;
	int	channel;
	T_FRAME *dma_confirm_frame();
	QUICC *quicc;

	GETGCT(gct);
	wa = (WORK_AREA *)child->work_area;
	quicc = wa->quicc;
	channel = wa->channel;

	/*
	 * clear event bit
	 * this clears indications of old (masked) events
	 */
	IDMA_CSR_WRT(channel, DMA_DONE_BIT);

	while( t = dma_confirm_frame(child) ){
		i++;
			driver_trace(TX_CONF, t->hdr.id, t);
		conf_frm(t, child->upper);
	}

	/*
	 * re-enable interrupts before calling dma_frame!!
	 */
	IDMA_CMAR_WRT(channel, IDMA_CMAR(channel) | DMA_DONE_BIT);

	if (i)
		dma_frame(child);

	if (wa->conf_q ) {
		/*
		 * There are buffers for transfering in ring
		 */
		IDMA_CMR_WRT(channel, IDMA_CMR(channel) | STR_BIT);
	}
	else
		/*
		 * There are no buffers for trasffering by IDMA now
		 */
		child->flags |= DMA_IDLE;
}


/********************************************************
 * routine:	dma_confirm_frame
 *
 * description:
 *	This routine 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 *
dma_confirm_frame(child)
CHILD *child;
{
	register IDMA_BD *b;
	register T_FRAME *t, *t1;
	register R_FRAME *r;
	LAYER_INFO *l;
	WORK_AREA *wa;
	GCT *gct;
	int	first = 1;
	int	buf_num;

	GETGCT(gct);
	wa = (WORK_AREA *)child->work_area;

	/*
	 * confirm all transmitted BD's
	 */
	t = wa->conf_q;
	t1 = 0;
	if( t ){
		l = &t->layer[LAYER(DRIVER)];
		/*
		 * if not linked to the tx table,
		 * there's no need to proceed
		 */
		if( !(t->layer[0].flags & LINKED_TO_TABLE) )
			goto out;
		/*
		 * calculate the number of tx buffers
		 */
		buf_num = ( l->hoff < t->hsize ) ? 1 : 0;

		if( l->boff < t->bsize )
			buf_num++;

		/*
		 * for each BD of t ...
		 */
		do {
			r = wa->rx_q;
			b = (IDMA_BD *)wa->quicc_cbd;

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

			/*
			 * if first buffer in rx frame
			 */
			if (r) {
				if (l->hoff < t->hsize) {
					if (first) {
					/*
					 * header buffer dose exist
					 */
					first = 0;
					r->blen += (t->hsize - l->hoff);
				}
			}
				if (l->boff < t->bsize) 
					if ((buf_num == 1) || !first)
					r->blen += (t->bsize - l->boff);

				r->flen = r->blen;
				r->hdr.id = child->key;

				driver_trace(RX_IND, r->hdr.id, r);
				wa->rx_q = r->nextb;
				r->flags = 0;
				r->nextb = 0;	/* no more buffs in frame */
				if( (*gct->send)(child->upper, r) )
					relm((HDR *)r);
			}

			/*
			 * get next index in BD table
			 * for confirming
			 */
			if( b->status & I_W )
				wa->quicc_cbd = (QUICC_BD *)IBD_ADDR(wa);
			else
				wa->quicc_cbd = (QUICC_BD *)
					((unsigned int)wa->quicc_cbd
					+ sizeof(IDMA_BD));

			wa->tfree++;
			wa->rfree++;
		} while( !(b->status & I_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:	dma_multi_terr
 *
 * description:
 *	Transmit error on DMA (Buffer chaining mode).
 *
 * arguments:
 *	h	dummy 32-bit message sent from interrupt routine
 *	child	the child strcuture involved
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
dma_multi_terr(h, child)
HDR *h;
CHILD *child;
{
	register WORK_AREA *wa;
	register T_FRAME *t;
	register R_FRAME *r;
	int	channel;
	QUICC *quicc;

	wa = (WORK_AREA *)child->work_area;
	quicc = wa->quicc;
	channel = wa->channel;
	
	/*
	 * clean up after error
	 */
	IDMA_CSR_WRT(channel,
			(IDMA_CSR(channel) & ~(DMA_BES_BIT | DMA_BED_BIT))); 
	dma_error(wa, child);

	/*
	 * confirm all transmitted frames
	 */
	dma_multi_conf(h, child);
}

/********************************************************
 * routine:	dma_error
 *
 * description:
 *	Handle error on an IDMA channel.
 *
 * arguments:
 *	wa		points to work area
 *	child	the child strcuture involved
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
dma_error(wa, child)
register WORK_AREA *wa;
CHILD *child;
{
	register IDMA_BD *bd;
	register IDMA_BD *retx;	/* first BD for retransmission */
	register int first;
	QUICC *quicc;

	quicc = wa->quicc;
	wa->terr_cnt++;

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

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

	/* now clear Valid bits until end of frame */
	bd = retx;
	while( !(bd->status & I_V) ){
		bd->status &= ~(wa->t_error);
		if( bd != retx )
			bd->status |= I_V;
		if( bd->status & I_L )
			break;
		if( bd->status & I_W )
			bd = IBD_ADDR(wa);
		else
			bd++;
	}
	retx->status |= I_V;

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


}



/********************************************************
 * routine:	dma_multi_abort
 *
 * description:
 *	Abort the current DMA operation and
 *	call dma_terr.
 *
 * arguments:
 *	h	dummy 32-bit message sent from interrupt routine
 *	child	the child strcuture involved
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
dma_multi_abort(h, child)
HDR *h;
CHILD *child;
{
	QUICC *quicc;
	R_FRAME	*r;
	WORK_AREA *wa;
	unsigned short;
	int channel;

	wa = (WORK_AREA *)child->work_area;
	quicc = wa->quicc;
	channel = wa-> channel;
	IDMA_CMR_WRT(channel, (IDMA_CMR(channel) | RST_BIT));
	child->flags |= TX_STOPPED;

	/*
	 * release all destination buffers
	 */
	if (wa->rx_q) {
		while (wa->rx_q) {
			r = wa->rx_q;
			if (wa->rx_q == wa->pool_q) 
				wa->rx_q = wa->pool_q = 0;
			else
				wa->rx_q = r->nextb;
			relm((HDR *)r);
		}
	}
}
