/*********************************************************************** 
* 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.c
 *
 * Description:
 *	This file contains code that exercises the DMA.
 *	The usual memory structures (T_FRAMEs and R_FRAMEs)
 *	are used so that this child can be configured under
 *	a standard application module (LAPD, file transfer, ...).
 *	Note that the childs used are fixed to childs 4 and 5.
 *
 * Routines:
 *	dma_init
 *	dma_itx
 *	dma_regs
 *	dma_single_tx
 *	dma_single_conf
 *	dma_single_terr
 *	dma_single_done
 *	dma_single_abort
 *	start_single_dma
 *	dma_single_transfer
 *	dma_trace
 *
 * Author:
 *	Ofrit Lesser 
 ********************************************************/


#include "gct.h"
#include "config.h"
#include "modules.h"
#include "msg.h"
#include "status.h"
#include "types.h"
#include "mtypes.h"
#include "bss.h"
#include "quicc.h"
#include "states.h"
#include "mng.h"
#include "dma.h"


extern void issue_cmd(unsigned short cmd, volatile QUICC *quicc);

/********************************************************
 * routine:	dma_init
 *
 * description:
 *	Initialize the DMA child.
 *
 * arguments:
 *	port		the number of the IDMA port (0, 1)
 *	idma		points to the driver configuration structure
 *
 * return code:
 *
 * side effects:
 *
 * Note:
 * If multi-buffer mode is used the following fields in the WORK_AREA
 * have different meaning than usual.
 *
 *	rx_q	-	pointer to the next destination frame (buffer)
 *	pool_q	-	pointer to the last destination frame (buffer)
 *	rtp_q	-	pointer to a spare rx frame. It exist in case
 * 			that 2 destination buffers were needed, but only
 *			one was available.
 *
 ********************************************************/
dma_init(port, idma)
int port;
struct idma *idma;
{
	GCT *gct;
	CHILD *child;
	WORK_AREA *wa;
	QUICC *quicc;
	register unsigned long mode;
	int (* *protocol)();
	unsigned adr, rom;
	int	child_id;
	extern int dma_single_tx(), dma_single_conf(),
			dma_single_terr(), dma_single_abort();
	extern int dma_multi_tx(), dma_multi_conf(),
			dma_multi_terr(), dma_multi_abort();

	GETGCT(gct);
	child = &gct->driver->child[idma->child_id];
	child->key = idma->child_id;
	child_id = idma->child_id;
	child->flags = 0;
	child->upper = idma->upper;
	wa = (WORK_AREA *)child->work_area;
	quicc = idma->quicc;

	/*
	 * initialize this child's work area
	 */
	wa->channel = port;
	wa->quicc = quicc;
	wa->tsize = idma->isize;
	wa->rsize = idma->isize;
	wa->txconf_event = (TX_CONF << 16) | idma->child_id;
	wa->txe_event = (TX_ERR << 16) | idma->child_id;
	wa->pram = &quicc->pram[port + 2].scc;
	wa->BDfirst = 0;
	wa->BDlast = (I_I | I_L);
	wa->conf_q = 0;
	wa->tx_q = 0;
	wa->req_q = 0;
	wa->rx_q = 0;
	wa->pool_q = 0;
	wa->rtp_q = 0;
	wa->t_error = I_DE | I_SE;
	wa->intr_txbd = DMA_DONE_BIT;
	wa->intr_terr = DMA_BES_BIT | DMA_BED_BIT;

	protocol = gct->driver->protocol;
	rom = gct->driver->rom;

	/*
	 * If destination peripheral
	 */
	mode = idma->idma_cmr;
	if( (mode) & (EXTERNAL_REQG | ECO_BIT))
		gct->driver->child[child_id].flags |= DMA_TOPERIPH;

	if (mode&RCI_BIT) {
		/* If Auto Buffer or Buffer Chaining Mode */
		adr = (unsigned)dma_multi_tx + rom;
		protocol[SUB(TX_REQ) | DMA_MULTI_STATE] =
						(int (*)())adr;
		adr = (unsigned)dma_multi_conf + rom;
		protocol[SUB(TX_CONF) | DMA_MULTI_STATE] =
						(int (*)())adr;
		adr = (unsigned)dma_multi_terr + rom;
		protocol[SUB(TX_ERR) | DMA_MULTI_STATE] =
						(int (*)())adr;
		adr = (unsigned)dma_multi_abort + rom;
		protocol[SUB(ABORT) | DMA_MULTI_STATE] =
						(int (*)())adr;

		gct->driver->child[child_id].state = DMA_MULTI_STATE;
		gct->driver->child[child_id].smac = &protocol[DMA_MULTI_STATE];
		gct->driver->child[child_id].flags |= DMA_IDLE;
	}
	else {
		/* Single Buffer Mode */
		adr = (unsigned)dma_single_tx + rom;
		protocol[SUB(TX_REQ) | DMA_SINGL_STATE] =
						(int (*)())adr;
		adr = (unsigned)dma_single_conf + rom;
		protocol[SUB(TX_CONF) | DMA_SINGL_STATE] =
						(int (*)())adr;
		adr = (unsigned)dma_single_terr + rom;
		protocol[SUB(TX_ERR) | DMA_SINGL_STATE] =
						(int (*)())adr;
		adr = (unsigned)dma_single_abort + rom;
		protocol[SUB(ABORT) | DMA_SINGL_STATE] =
						(int (*)())adr;
		gct->driver->child[child_id].state = DMA_SINGL_STATE;
		gct->driver->child[child_id].smac = &protocol[DMA_SINGL_STATE];

		return;
	}
	
	flush_queues(idma->child_id);

	/*
	 * set idma parameter ram
	 */
	wa->pram->pothers.idma_smc.idma.ibase = idma->ibase;

}

/********************************************************
 * routine:	dma_itx
 *
 * description:
 *	Initialize an IDMA transmit table.
 *
 * arguments:
 *	n		the driver's child number
 *	tsize		the size of the tx table
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
dma_itx(n, tsize)
{
	GCT *gct;
	CHILD *child;
	WORK_AREA *wa;
	int i;
	T_FRAME *t;
	unsigned char *s;

	GETGCT(gct);
	child = &gct->driver->child[n];
	wa = (struct work_area *)child->work_area;

	/*
	 * release all existing T_FRAMEs
	 */
	t = wa->conf_q;
	while( t ){
		t->hdr.status = TX_FAIL;
 		conf_frm(t, gct->driver->child[n].upper);
		t = t->layer[LAYER(DRIVER)].nextf;
	}
	wa->conf_q = 0;
	wa->tx_q = 0;
	wa->req_q = 0;

	wa->tfree = wa->tsize;
	wa->quicc_tbd = (QUICC_BD *)IBD_ADDR(wa);
	wa->quicc_cbd = (QUICC_BD *)IBD_ADDR(wa);
	wa->tcount = 0;
	wa->terr_cnt = 0;

	for(i = 0; i < tsize; i++){
		(IBD_ADDR(wa) + i)->status = 0;
	}
	(IBD_ADDR(wa) + (tsize-1))->status |= I_W;

}


/********************************************************
 * routine:	dma_regs
 *
 * description:
 *	Set up all IDMA registers from the configuration strcuture.
 *
 * arguments:
 *	n		the driver's child number
 *	idma		points to the configuration structure
 *	channel		IDMA channel number
 *
 * return code:
 *	0		always succeeds
 *
 * side effects:
 *
 ********************************************************/
dma_regs(n, idma, channel)
int n;
struct idma *idma;
int	channel;
{
	GCT *gct;
	WORK_AREA *wa;
	QUICC *quicc;

	GETGCT(gct);
	wa = (WORK_AREA *)gct->driver->child[n].work_area;
	quicc = wa->quicc;

	/*
	 * Reset the IDMA port
	 */
	IDMA_CMR_WRT(channel, RST_BIT);

	IDMA_CMR_WRT(channel, (idma->idma_cmr)& ~STR_BIT);

	issue_cmd(INIT_IDMA | (channel ? 0x90 : 0xd0), quicc);

	IDMA_SAPR_WRT(channel, idma->idma_sapr);
	IDMA_DAPR_WRT(channel, idma->idma_dapr);
	IDMA_BCR_WRT(channel, idma->idma_bcr);
	IDMA_FCR_WRT(channel, idma->idma_fcr);
	IDMA_CMAR_WRT(channel, idma->idma_cmar);
	IDMA_CSR_WRT(channel, 0xff);
}


/********************************************************
 * routine:	dma_single_tx
 *
 * description:
 *	Transmit a T_FRAME using the general purpose DMA.
 *	The destination is an R_FRAME which is allocated
 *	in this routine. It is expected that both the
 *	header and data buffer fit into one receive data buffer.
 *
 * arguments:
 *	t		the T_FRAME for transmission
 *	child		the child structure
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
dma_single_tx(t, child)
register T_FRAME *t;
CHILD *child;
{
	register WORK_AREA *wa;

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

	driver_trace(TX_REQ, t->hdr.id, t);

	/*
	 * find new end of queue
	 */
	while( t->layer[LAYER(DRIVER)].nextf )
		t = t->layer[LAYER(DRIVER)].nextf;
	wa->req_q = t;
	if( start_single_dma(child) == -1 )
		dma_single_done(child);
}


/********************************************************
 * routine:	dma_single_conf
 *
 * description:
 *	A buffer has been transferred by the DMA.
 *
 * arguments:
 *	h	dummy 32-bit message from interrupt routine
 *	child	the child structure involved
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
dma_single_conf(h, child)
HDR *h;
CHILD *child;
{
	WORK_AREA *wa;
	register T_FRAME *t;
	register R_FRAME *r;
	LAYER_INFO *l;
	int finished;
	GCT *gct;
	int 	channel;
	QUICC *quicc;

	GETGCT(gct);
	wa = (WORK_AREA *)child->work_area;
	child->flags &= ~DMA_STARTED;
	channel = wa->channel;
	quicc = wa->quicc;
	t = wa->conf_q;
	if( t )
		l = &t->layer[LAYER(DRIVER)];
	r = wa->rx_q;
	if( t ){
		/* see if parameters match */
		if( l->flags & DMA_HDONE ){
			finished = 1;
			/* set received length field */
			if( r ) r->blen += (t->bsize - l->boff);
		} else {
			l->flags |= DMA_HDONE;
			if( t->buf && (t->bsize > l->boff) )
				finished = 0;
			else
				finished = 1;
			/* set received length field */
			if( r ) r->blen += (t->hsize - l->hoff);
		}
		if( finished ){
			driver_trace(TX_CONF, t->hdr.id, t);
			dma_single_done(child);
			if( r ){
				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;
				if( (*gct->send)(child->upper, r) )
					relm((HDR *)r);
			}
		}
	}

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

	/* get next request going */
	if( start_single_dma(child) == -1 )
		dma_single_done(child);
}

/********************************************************
 * routine:	dma_single_done
 *
 * description:
 *	Transmission has been finished by the DMA
 *	single buffer.
 *	Return the indication to the upper layer by
 *	calling conf_frm.
 *
 * arguments:
 *	child		the child structure involved
 *
 * return code:
 *
 * side effects:
 *	The child's queue pointers are advanced.
 *
 ********************************************************/
dma_single_done(child)
CHILD *child;
{
	WORK_AREA *wa;
	T_FRAME *t;

	wa = (WORK_AREA *)child->work_area;
	t = wa->conf_q;
	if( !t )
		return;
	wa->conf_q = t->layer[LAYER(DRIVER)].nextf;
	if( !wa->conf_q )
		wa->req_q = 0;
	conf_frm(t, child->upper);
}


/********************************************************
 * routine:	dma_single_terr
 *
 * description:
 *	Transmit error on DMA.
 *
 * arguments:
 *	h	dummy 32-bit message sent from interrupt routine
 *	child	the child strcuture involved
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
dma_single_terr(h, child)
HDR *h;
CHILD *child;
{
	register WORK_AREA *wa;
	register T_FRAME *t;
	register R_FRAME *r;

	wa = (WORK_AREA *)child->work_area;
/* OFRIT
	quicc = wa->quicc;
	if( !(t = wa->conf_q) )
		return;
	t->hdr.status = TX_FAIL;
	wa->conf_q = t->layer[LAYER(DRIVER)].nextf;
	if( !wa->conf_q )
		wa->req_q = 0;
	conf_frm(t, child->upper);
	if( r = wa->rx_q ){
		wa->rx_q = r->nextb;
		relm((HDR *)r);
	}
*/
	child->flags &= ~DMA_STARTED;
	start_single_dma(child);
}

/********************************************************
 * routine:	dma_single_abort
 *
 * description:
 *	Abort the current DMA operation and
 *	call dma_single_terr.
 *
 * arguments:
 *	h	dummy 32-bit message sent from interrupt routine
 *	child	the child strcuture involved
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
dma_single_abort(h, child)
HDR *h;
CHILD *child;
{
	QUICC *quicc;
	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));

	dma_single_terr(h, child);
}

/********************************************************
 * routine:	start_single_dma
 *
 * description:
 *	Start transmitting a block of data using the IDMA
 *	in single buffer mode
 *
 * arguments:
 *	child		the child structure
 *
 * return code:
 *	-1		on error
 *	0		otherwise
 *
 * side effects:
 *
 ********************************************************/
start_single_dma(child)
CHILD *child;
{
	register WORK_AREA *wa;
	register T_FRAME *t;
	register R_FRAME *r;
	R_FRAME *getr();
	LAYER_INFO *l;
	unsigned char *dest;
	QUICC *quicc;
	int	channel;

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

	t = wa->conf_q;
	if( !t )
		return(0);
	if( child->flags & DMA_STARTED )
		return(0);
	l = &t->layer[LAYER(DRIVER)];
	if( child->flags & DMA_TOPERIPH )
		if( IDMA_CMR(channel) & ECO_BIT)
			l->flags |= DMA_TO_PERIPH;
		else
			l->flags |= DMA_FR_PERIPH;
	if( !(l->flags & DMA_TO_PERIPH) ){
		if( !wa->rx_q ){
			wa->rx_q = getr((unsigned short)R_BUFG,
							child->key);
			if( !wa->rx_q )
				return(-1);
			wa->rx_q->flags = 0;	/* must zero RX_POOL flag */
		}
		r = wa->rx_q;
		dest = &r->buf[r->blen];
	} else 
		/* address of peripheral */
		dest = (unsigned char *)IDMA_DAPR_ADR(channel);

	if( !t->hbuf || (t->hsize <= l->hoff) ){
		if( !t->buf || (t->bsize <= l->boff) )
			return(-1);
		l->flags |= DMA_HDONE;
	} else
		l->flags &= ~DMA_HDONE;

	/*
	 * may have to transmit a header buffer first.
	 */
	child->flags |= DMA_STARTED;
	if( l->flags & DMA_HDONE )
		dma_single_transfer(child, (unsigned long)&t->buf[l->boff],
				(unsigned long)dest, t->bsize-l->boff);
	else
		dma_single_transfer(child, (unsigned long)&t->hbuf[l->hoff],
				(unsigned long)dest, t->hsize-l->hoff);
	return(0);
}

/********************************************************
 * routine:	dma_single_transfer
 *
 * description:
 *	Set up the DMA for a transfer
 *
 * arguments:
 *	quicc		points to internal RAM
 *	source		the source address
 *	dest		the destination address
 *	count		number of bytes to transfer
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
dma_single_transfer(child, source, dest, count)
CHILD *child;
unsigned long source, dest;
unsigned short count;
{
	QUICC *quicc;
	register WORK_AREA *wa;
	unsigned short	channel;

	wa = (WORK_AREA *)child->work_area;
	quicc = wa->quicc;
	channel = wa-> channel;
	dma_trace(child, source, dest, count);
	IDMA_DAPR_WRT(channel, dest);
	IDMA_SAPR_WRT(channel, source);
	IDMA_BCR_WRT(channel, count);
	IDMA_CMR_WRT(channel, (IDMA_CMR(channel) | STR_BIT));
}


/********************************************************
 * routine:	dma_trace
 *
 * description:
 *	Trace an operation of the general purpose DMA.
 *
 * arguments:
 *	source		source address
 *	dest		destination address
 *	count		byte count
 *
 * return code:
 *
 * side effects:
 *
 ********************************************************/
dma_trace(child, source, dest, count)
CHILD *child;
{
	GCT *gct;
	GWA *gwa;
	MSG *m;
	DMA_INFO *d;
	extern MSG *getm();

	GETGCT(gct);
	gwa = (GWA *)gct->driver->work_area;
	if( !(gwa->intr_trace &
			(child->key == 4 ? INTR_DMA1 : INTR_DMA2)) )
		return;
	m = getm(DMA_TRANSFER, 0, 0, sizeof(DMA_INFO));
	if( !m )
		return;
	d = (DMA_INFO *)m->param;
	d->source = source;
	d->dest = dest;
	d->count = count;
	if ((*gct->send)(SYS_MNG, m))
		relm((HDR *)m);
}

