/*
 * Device driver for the Ungermann-Bass PC-NIC (Ethernet) Adapter
 * This code is written for the IBM PC-RT.
 *
 * Copyright 1989 by the Massachusetts Institute of Technology
 */

#include	<types.h>
#include	<sys.h>
#include	"unreg.h"

#define	ETHMINSIZ	64		/* min. packet size   */
#define UN_RDELAY	280		/*  1 ms delay (recv) */
#define UN_XDELAY	2800		/* 10 ms delay (xmit) */

#define MM_IN(p)	(*((char volatile *) (p)))
#define MM_OUT(p, d)	(*((char volatile *) (p)) = ((char) (d)))
#define BUMP(page)	if (++(page) == UN_NUMRBUFS) page = 0

struct undevice *un_addr=
    (struct undevice *)0xf4080000;	/* Base address of "un" adapter  */
static int un_fpp;			/* Full-page pointer		 */
extern unsigned char etmyaddr[6];	/* Ethernet address of adapter	 */

/*
 * Device input initialization routine.
 */
un_in_up(devp)
reg	dct	*devp;
{
	reg	struct undevice	*addr = un_addr;
	reg	int i;

	devp->d_flg |= D_INI;
	un_reset(addr);

	/* Read the ethernet address off the board and write it to the EDLC */
	for (i = 0; i < 6; i++)
		addr->un_edlc.nodeID[i] =
		    etmyaddr[i] =
			addr->un_eprom[UN_EADDROFF + i];

	return(1);
}

un_reset(addr)
reg	struct undevice *addr;
{
	MM_OUT(&addr->un_csr, 0);		/* disable interrupts */
	MM_OUT(&addr->un_edlc.reset, RESET_ON);	/* set reset bit during init */
	MM_OUT(&addr->un_edlc.rmode, RM_NONE);
	MM_OUT(&addr->un_edlc.rstat, RS_CLEAR);
	MM_OUT(&addr->un_edlc.xstat, XS_CLEAR);
	MM_OUT(&addr->un_edlc.rmask, 0);
	MM_OUT(&addr->un_edlc.xmask, 0);

	/*
	 * Put the transmitter in loopback mode so that a spurious packet
	 * is not sent when the reset bit is cleared.  Set up a packet of
	 * one byte so that the transmission completes before turning off
	 * loopback mode.
	 */
	MM_OUT(&addr->un_xsar[0], UN_XBSIZE - 1 >> 8 & 0xf);
	MM_OUT(&addr->un_xsar[1], UN_XBSIZE - 1 & 0xff);
	MM_OUT(&addr->un_edlc.tmode, TM_NORMAL - TM_LBC);
	MM_OUT(&addr->un_edlc.reset, RESET_OFF);

	/* Clear the receive ring */
	un_fpp = MM_IN(&addr->un_fppepp) & UN_PAGE_MASK;
	MM_OUT(&addr->un_fppepp, un_fpp);
	UN_CLRPAV(addr);

	/* Set up the receiver and transmitter */
	MM_OUT(&addr->un_edlc.rmode, RM_NORMAL);
	MM_OUT(&addr->un_edlc.tmode, TM_NORMAL);
	MM_OUT(&addr->un_edlc.rstat, RS_CLEAR);
	MM_OUT(&addr->un_edlc.xstat, XS_CLEAR);
}

/*
 * Device output routine.
 */
un_out(devp)
reg dct *devp;
{
	register struct undevice *addr = un_addr;
	register int off;
	register int dlen = UN_XDELAY;
	static int next_buf=0;

	/* Copy the data into the free transmit buffer (end-aligned) */
	if (devp->d_breq < ETHMINSIZ) devp->d_breq = ETHMINSIZ;
	off = (int)addr->un_xmtbuf[next_buf] + UN_XBSIZE - devp->d_breq;
	bcopy((char *)devp->d_addr, (char *)off, devp->d_breq);

	/* Start transmission when previous transmission is done */
	while ((MM_IN(&addr->un_csr) & UN_TXDONE) == 0)
		if (dlen-- == 0) {
			un_reset(addr);
			break;
		}
	
	MM_OUT(&addr->un_edlc.xstat, XS_CLEAR);	/* Clear errors */
	UN_XMIT(addr, off);		/* Start transmission */
	next_buf = 1 - next_buf;	/* Change next_buf pointer */
}

/*
 * Device input routine.
 * Returns received packet length.
 */
un_dev_read(devp, data, len)
reg dct *devp;
byte *data;
int len;
{
	register struct undevice *addr = un_addr;
	register int rlen = 0;		/* Number of bytes received	 */
	register int dlen = UN_RDELAY;	/* Delay / (later: bytes copied) */
	register int off = un_fpp;	/* Receive buffer page offset	 */

	/* Wait until we have some data */
	while (! UN_PAV(addr))
	    if (dlen-- == 0) return(0); 		/* Timeout */
	MM_OUT(&addr->un_edlc.rstat, RS_CLEAR);		/* Clear status */
	
	/*
	 * Calculate the length of the received packet.
	 * When done:
	 *    un_fpp = last page of received packet
	 *    rlen   = number of bytes in received packet
	 */
	while ((MM_IN(&addr->un_pram[un_fpp]) & UN_LAST_PAGE) == 0) {
		rlen += UN_RBUFSIZE;
		BUMP(un_fpp);
	}
	rlen += (MM_IN(&addr->un_pram[un_fpp]) & UN_PAGE_LENGTH_MASK) + 1;
	if ((rlen > UN_XBSIZE) || (rlen > len)) {
		if (rlen > UN_XBSIZE) printf("un: huge packet\n");
		rlen = 0;
		goto read_done;
	}

	/*
	 * Get the packet from the receive ring.
	 */
	if (un_fpp < off) {		/* Ring wraps... */
	    dlen = (UN_NUMRBUFS - off) * UN_RBUFSIZE;
	    bcopy (&addr->un_rcvbuf[off][0], &data[0], dlen);
	    off = 0;
	} else
	    dlen = 0;		/* No data copied yet */
	bcopy (&addr->un_rcvbuf[off][0], &data[dlen], rlen-dlen);

	/*
	 * Clear the packet from the receive ring.
	 * 
	 * Set fpp on the controller to point to the page after
	 * the last page of the received packet.
	 * Also, clear PAV if there are no more packets.
	 */
read_done:
	BUMP(un_fpp);
	MM_OUT(&addr->un_fppepp, un_fpp);
	if (un_fpp == (MM_IN(&addr->un_fppepp) & UN_PAGE_MASK)) {
		UN_CLRPAV(addr);
	}
	return(rlen);
}
