/* OS- and machine-dependent stuff for the 8250 asynch chip on a IBM-PC
 * Copyright 1991 Phil Karn, KA9Q
 *
 * 16550A support plus some statistics added mah@hpuviea.at 15/7/89
 *
 * CTS hardware flow control from dkstevens@ucdavis,
 * additional stats from delaroca@oac.ucla.edu added by karn 4/17/90
 * RLSD flow control reorganized by Bill_Simpson@um.cc.umich.edu; Feb 91
 */
#include <stdio.h>
#include <dos.h>
#include "global.h"
#include "mbuf.h"
#include "proc.h"
#include "iface.h"
#include "8250.h"
#include "asy.h"
#include "devparam.h"
#include "pc.h"

static void asy_rlsd __ARGS((int dev,void *p1,void *p2));

static void asy_output __ARGS((int dev,char *buf,unsigned short cnt));
static int asyrxint __ARGS((struct asy *asyp));
static void asytxint __ARGS((int dev));
static void asymsint __ARGS((int dev));

static void asy_tx __ARGS((int dev,void *p1,void *p2));

struct asy Asy[ASY_MAX];

/* ASY interrupt handlers */
static INTERRUPT (*Handle[])() = {asy0vec,asy1vec,asy2vec,asy3vec,asy4vec};


/* Initialize asynch port "dev" */
int
asy_init(dev,iface,arg1,arg2,bufsize,trigchar,cts,rlsd,speed)
int dev;
struct iface *iface;
char *arg1,*arg2;	/* Attach args for address and vector */
int16 bufsize;
int trigchar;
char cts;
char rlsd;
int16 speed;
{
	register unsigned base;
	register struct fifo *fp;
	register struct asy *ap;
	char *ifn;
	char i_state;

	ap = &Asy[dev];
	ap->iface = iface;
	ap->addr = htoi(arg1);
	ap->vec = htoi(arg2);
	ap->cts_flow_control = cts;

	/* set up to monitor rlsd (carrier detect) */
	if ( rlsd ) {
		ap->rlsd_line_control = RLSD_DOWN;
		ifn = if_name(iface," rlsd");
		newproc(ifn, 256, asy_rlsd, dev, NULL,NULL,0);
		free(ifn);
		pwait(NULL);
	} else {
		ap->rlsd_line_control = RLSD_NONE;
	}

	/* Set up receiver FIFO */
	fp = &ap->fifo;
	fp->buf = mallocw(bufsize);
	fp->bufsize = bufsize;
	fp->wp = fp->rp = fp->buf;
	fp->cnt = 0;
	fp->hiwat = 0;
	fp->overrun = 0;
	base = ap->addr;
	ap->trigchar = trigchar;

	/* Purge the receive data buffer */
	(void)inportb(base+RBR);

	i_state = dirps();

	/* Save original interrupt vector, mask state, control bits */
	ap->save.vec = getirq(ap->vec);
	ap->save.mask = getmask(ap->vec);
	ap->save.lcr = inportb(base+LCR);
	ap->save.ier = inportb(base+IER);
	ap->save.mcr = inportb(base+MCR);

	/* save speed bytes */
	setbit(base+LCR,LCR_DLAB);
	ap->save.divl = inportb(base+DLL);
	ap->save.divh = inportb(base+DLM);
	clrbit(base+LCR,LCR_DLAB);

	/* Set interrupt vector to SIO handler */
	setirq(ap->vec,Handle[dev]);

	/* Set line control register: 8 bits, no parity */
	outportb(base+LCR,(char)LCR_8BITS);

	/* determine if 16550A, turn on FIFO mode and clear RX and TX FIFOs */
	outportb(base+FCR,(char) FIFO_ENABLE);

	/* According to National ap note AN-493, the FIFO in the 16550 chip
	 * is broken and must not be used. To determine if this is a 16550A
	 * (which has a good FIFO implementation) check that both bits 7
	 * and 6 of the IIR are 1 after setting the fifo enable bit. If
	 * not, don't try to use the FIFO.
	 */
	if ((inportb(base+IIR) & IIR_FIFO_ENABLED) == IIR_FIFO_ENABLED) {
		ap->is_16550a = TRUE;
		outportb(base+FCR,(char) FIFO_SETUP);
	} else {
		/* Chip is not a 16550A. In case it's a 16550 (which has a
		 * broken FIFO), turn off the FIFO bit.
		 */
		outportb(base+FCR,(char)0);
		ap->is_16550a = FALSE;
	}
	if ( rlsd ) {
		/* Turn on modem status interrupt, leave receive
		 * and transmit interrupts off until RLSD is asserted
		 */
		outportb(base+IER, (char)IER_MS);
		/* DTR and RTS should match the RLSD signal */
		asymsint(dev);
		/* Turn on 8250 master interrupt enable (connected to OUT2) */
		outportb(base+MCR, (char)(MCR_OUT2));
	} else {
		/* Turn on receive interrupt enable in 8250, leave transmit
		 * and modem status interrupts turned off for now
		 */
		outportb(base+IER,(char)IER_DAV);

		/* Set modem control register: assert DTR, RTS, turn on 8250
		 * master interrupt enable (connected to OUT2)
		 */
		outportb(base+MCR,(char)(MCR_DTR|MCR_RTS|MCR_OUT2));
	}
	/* Enable interrupt */
	maskon(ap->vec);
	restore(i_state);

	asy_speed(dev,speed);

	ifn = if_name(iface," tx");
	iface->txproc = newproc(ifn,256,asy_tx,dev,NULL,NULL,0);
	free(ifn);

	if ( !rlsd ) {
		/* If we're not checking, must assume we're up */
		if ( iface->iostatus != NULL ) {
			(*iface->iostatus)(iface, PARAM_UP, 0L);
		}
	}
	return 0;
}


int
asy_stop(iface)
struct iface *iface;
{
	register unsigned base;
	register struct asy *ap;
	char i_state;

	ap = &Asy[iface->dev];

	if ( ap->rlsd_line_control ) {
		ap->rlsd_line_control = RLSD_NONE;
		psignal( &(ap->rlsd_line_control), 0 );
		pwait(NULL);
	}
	ap->iface = NULLIF;

	base = ap->addr;

	/* Purge the receive data buffer */
	(void)inportb(base+RBR);

	/* and hardware fifos if available */
	if (ap->is_16550a)
		outportb(base+FCR,(char) FIFO_SETUP);

	/* Restore original interrupt vector and 8259 mask state */
	i_state = dirps();
	setirq(ap->vec,ap->save.vec);
	if(ap->save.mask)
		maskon(ap->vec);
	else
		maskoff(ap->vec);

	/* Restore speed regs */
	setbit(base+LCR,LCR_DLAB);
	outportb(base+DLL,ap->save.divl);	/* Low byte */
	outportb(base+DLM,ap->save.divh);	/* Hi byte */
	clrbit(base+LCR,LCR_DLAB);

	/* Restore control regs */
	outportb(base+LCR,ap->save.lcr);
	outportb(base+IER,ap->save.ier);
	outportb(base+MCR,ap->save.mcr);
	restore(i_state);
	free(ap->fifo.buf);
	return 0;
}


/* Set asynch line speed */
int
asy_speed(dev,bps)
int dev;
int16 bps;
{
	register unsigned base;
	register long divisor;
	struct asy *asyp;
	char i_state;

	if(bps == 0 || dev >= ASY_MAX)
		return -1;
	asyp = &Asy[dev];
	if(asyp->iface == NULLIF)
		return -1;

	switch(bps) {
	case 300:
	case 1200:
	case 2400:
	case 4800:
	case 9600:
	case 19200:
		/* supported speed */
		asyp->speed = bps;
		break;
	default:
		/* unsupported speed */
		return -1;
	};

	base = asyp->addr;
	divisor = BAUDCLK / bps;

	i_state = dirps();

	/* Purge the receive data buffer */
	(void)inportb(base+RBR);
	if (asyp->is_16550a)		/* clear tx+rx fifos */
		outportb(base+FCR,(char) FIFO_SETUP);

	/* Turn on divisor latch access bit */
	setbit(base+LCR,LCR_DLAB);

	/* Load the two bytes of the register */
	outportb(base+DLL,(char)(divisor & 0xff));		/* Low byte */
	outportb(base+DLM,(char)((divisor >> 8) & 0xff));	/* Hi byte */

	/* Turn off divisor latch access bit */
	clrbit(base+LCR,LCR_DLAB);

	restore(i_state);
	return 0;
}


/* Asynchronous line I/O control */
int32
asy_ioctl(iface,cmd,set,val)
struct iface *iface;
int cmd;
int set;
int32 val;
{
	int16 base = Asy[iface->dev].addr;

	switch(cmd){
	case PARAM_SPEED:
		if(set)
			asy_speed(iface->dev,(int16)val);
		return Asy[iface->dev].speed;
	case PARAM_DTR:
		if(set)
			writebit(base+MCR,MCR_DTR,(int)val);
		return (inportb(base+MCR) & MCR_DTR) ? TRUE : FALSE;
	case PARAM_RTS:
		if(set)
			writebit(base+MCR,MCR_RTS,(int)val);
		return (inportb(base+MCR) & MCR_RTS) ? TRUE : FALSE;
		case PARAM_DOWN:
			clrbit(base+IER,(char)IER_DAV);
			clrbit(base+MCR,MCR_RTS);
			clrbit(base+MCR,MCR_DTR);
			return FALSE;
		case PARAM_UP:
			setbit(base+IER,(char)IER_DAV);
			setbit(base+MCR,MCR_RTS);
			setbit(base+MCR,MCR_DTR);
			return TRUE;
	};
	return -1;
}


/* Start transmission of a buffer on the serial transmitter */
static void
asy_output(dev,buf,cnt)
int dev;
char *buf;
unsigned short cnt;
{
	register struct dma *dp;
	unsigned base;
	char i_state;
	char ier;
	struct asy *asyp;

	if(dev < 0 || dev >= ASY_MAX)
		return;
	asyp = &Asy[dev];
	if(asyp->iface == NULLIF)
		return;
	base = asyp->addr;
	dp = &asyp->dma;
	i_state = dirps();
	if(dp->flags){
		restore(i_state);
		return;	/* Already busy */
	}
	dp->data = buf;
	dp->cnt = cnt;
	dp->flags = 1;

	if(asyp->cts_flow_control){
		/* CTS flow control is enabled; let the modem control
		 * interrupt enable transmit interrupts if CTS is off
		 */
		ier = IER_MS;
		if(inportb(base+MSR) & MSR_CTS)
			ier |= IER_TxE;
	} else {
		/* Enable transmit interrupts; this will cause an immediate
		 * interrupt that will start things going
		 */
		ier = IER_TxE;
	}
	setbit(base+IER,ier);
	/* "Kick start" the transmitter interrupt routine, in case just
	 * setting the interrupt enable bit doesn't case an interrupt
	 */
	if(ier & IER_TxE)
		asytxint(dev);
	restore(i_state);
}


/* Blocking read from asynch line
 * Returns character or -1 if aborting
 */
int
get_asy(dev)
int dev;
{
	char i_state;
	register struct fifo *fp;
	char c;

	fp = &Asy[dev].fifo;

	i_state = dirps();
	while(fp->cnt == 0){
		if(pwait(fp) != 0){
			restore(i_state);
			return -1;
		}
	}
	fp->cnt--;
	restore(i_state);

	c = *fp->rp++;
	if(fp->rp >= &fp->buf[fp->bufsize])
		fp->rp = fp->buf;

	return uchar(c);
}


/* Interrupt handler for 8250 asynch chip (called from asyvec.asm) */
void
asyint(dev)
int dev;
{
	struct asy *asyp;
	unsigned base;
	char iir;

	asyp = &Asy[dev];
	base = asyp->addr;
	while(((iir = inportb(base+IIR)) & IIR_IP) == 0){
		switch(iir & IIR_ID){
		case IIR_RDA:	/* Receiver interrupt */
			asyrxint(asyp);
			break;
		case IIR_THRE:	/* Transmit interrupt */
			asytxint(dev);
			break;
		case IIR_MSTAT:	/* Modem status change */
			asymsint(dev);
			asyp->cts_flow_ints++;
			break;
		}
		/* should happen at end of a single slip packet */
		if(iir & IIR_FIFO_TIMEOUT)
			asyp->fifotimeouts++;
	}
}


/* Process 8250 receiver interrupts */
static int
asyrxint(asyp)
struct asy *asyp;
{
	register struct fifo *fp;
	unsigned base;
	char c,lsr;
	int cnt = 0;
	int trigseen = FALSE;

	asyp->rxints++;
	base = asyp->addr;
	fp = &asyp->fifo;
	for(;;){
		lsr = inportb(base+LSR);
		if(lsr & LSR_OE)
			asyp->overrun++;

		if(lsr & LSR_DR){
			asyp->rxchar++;
			c = inportb(base+RBR);
			if(asyp->trigchar == uchar(c))
				trigseen = TRUE;
			/* If buffer is full, we have no choice but
			 * to drop the character
			 */
			if(fp->cnt != fp->bufsize){
				*fp->wp++ = c;
				if(fp->wp >= &fp->buf[fp->bufsize])
					/* Wrap around */
					fp->wp = fp->buf;
				fp->cnt++;
				if(fp->cnt > fp->hiwat)
					fp->hiwat = fp->cnt;
				cnt++;
			} else
				fp->overrun++;
		} else
			break;
	}
	if(cnt > asyp->rxhiwat)
		asyp->rxhiwat = cnt;
	if(trigseen)
		psignal(fp,1);
	return cnt;
}


/* Handle 8250 transmitter interrupts */
static void
asytxint(dev)
int dev;
{
	register struct dma *dp;
	register unsigned base;
	register int count;
	struct asy *asyp;

	asyp = &Asy[dev];
	base = asyp->addr;
	dp = &asyp->dma;
	asyp->txints++;
	if(!dp->flags){
		/* "Shouldn't happen", but disable transmit
		 * interrupts anyway
		 */
		clrbit(base+IER,IER_TxE);
		return;	/* Nothing to send */
	}
	if(!(inportb(base+LSR) & LSR_THRE))
		return;	/* Not really ready */

	/* If it's a 16550A, load up to 16 chars into the tx hw fifo
	 * at once. With an 8250, it can be one char at most.
	 */
	if(asyp->is_16550a){
		count = min(dp->cnt,OUTPUT_FIFO_SIZE);

		/* 16550A: LSR_THRE will drop after the first char loaded
		 * so we can't look at this bit to determine if the hw fifo is
		 * full. There seems to be no way to determine if the tx fifo
		 * is full (any clues?). So we should never get here while the
		 * fifo isn't empty yet.
		 */
		asyp->txchar += count;
		dp->cnt -= count;
#ifdef	notdef	/* This is apparently too fast for some chips */
		dp->data = outbuf(base+THR,dp->data,count);
#else
		while(count-- != 0)
			outportb(base+THR,*dp->data++);
#endif
		if(dp->cnt == 0){
			dp->flags = 0;
			/* Disable transmit interrupts */
			clrbit(base+IER,IER_TxE);
			psignal(asyp,1);
		}
	} else {	/* 8250 */
		do {
			asyp->txchar++;
			outportb(base+THR,*dp->data++);

			if(--dp->cnt == 0){
				dp->flags = 0;
				/* Disable transmit interrupts */
				clrbit(base+IER,IER_TxE);
				psignal(asyp,1);
				break;
			}
		} while(inportb(base+LSR) & LSR_THRE);
	}
}


/* Handle 8250 modem status change */
static void
asymsint(dev)
int dev;
{
	char msr;
	unsigned base;
	struct asy *asyp;
	struct dma *dp;

	asyp = &Asy[dev];
	base = asyp->addr;
	msr = inportb(base+MSR);
	dp = &asyp->dma;

	/* Check status of modem control signals from remote host */
	if (asyp->rlsd_line_control) {
		if (msr & MSR_RLSD) {
			/* RLSD is asserted */
			if (asyp->rlsd_line_control == RLSD_DOWN) {
				/* RLSD just went up */
				asyp->rlsd_line_control = RLSD_UP;
				psignal( &(asyp->rlsd_line_control), 1 );
			}
		} else {
			/* RLSD not asserted */
			if (asyp->rlsd_line_control == RLSD_UP) {
				/* RLSD just went down */
				asyp->rlsd_line_control = RLSD_DOWN;
				psignal( &(asyp->rlsd_line_control), 1 );
			}
		}
	}
	if (asyp->cts_flow_control) {
		if(msr & MSR_CTS){
			/* CTS now asserted, enable Transmit interrupts */
			if(dp->flags)
				setbit(base+IER,IER_TxE);
		} else {
			/* CTS now dropped, disable Transmit interrupts */
			clrbit(base+IER,IER_TxE);
		}
	}
}


/* Wait for a signal that the RLSD modem status has changed */
int
get_rlsd_asy(dev, new_rlsd)
int dev;
int new_rlsd;
{
	struct asy *ap = &Asy[dev];
	struct iface *ifp = ap->iface;
	int result;

	if (new_rlsd == RLSD_NONE)
		/* Just return the current value */
		return(ap->rlsd_line_control);

	if (ap->rlsd_line_control == new_rlsd)
		/* Already at requested value */
		return(new_rlsd);

	/* Wait for state change to requested value */
	while (ap->rlsd_line_control != new_rlsd) {
		pwait( &(ap->rlsd_line_control) );
		pause(2L);
	}

	if ( ap->rlsd_line_control == RLSD_UP )
		result = PARAM_UP;
	else
		result = PARAM_DOWN;

	if ( ifp->ioctl != NULL )
		(*ifp->ioctl)( ifp, result, TRUE, 0L );
	if ( ifp->iostatus != NULLFP )
		(*ifp->iostatus)( ifp, result, 0L );
	if ( ifp->supv != NULLPROC )
		alert( ifp->supv, 0 );
	return(ap->rlsd_line_control);
}


/* Monitor RLSD signal, and report status changes */
static void
asy_rlsd( dev, p1, p2 )
int dev;
void *p1;
void *p2;
{
	int save_rlsd = Asy[dev].rlsd_line_control;

	while ( save_rlsd != RLSD_NONE ) {
		if ( save_rlsd != RLSD_UP ) {
			save_rlsd = get_rlsd_asy( dev, RLSD_UP );
		} else {
			save_rlsd = get_rlsd_asy( dev, RLSD_DOWN );
		}
	}
}


/* Poll the asynch input queues; called on every clock tick.
 * This helps limit the interrupt ring buffer occupancy when long
 * packets are being received.
 */
void
asytimer()
{
	register struct asy *asyp;
	register struct fifo *fp;
	register int i;

	for(i=0;i<ASY_MAX;i++){
		asyp = &Asy[i];
		fp = &asyp->fifo;
		if(fp->cnt != 0)
			psignal(fp,1);
		if(asyp->dma.flags != 0 && (inportb(asyp->addr+LSR) & LSR_THRE) ) {
			asyp->txto++;
			asytxint(asyp->iface->dev);
		}
	}
}
int
doasystat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	register struct asy *asyp;

	for(asyp = Asy;asyp < &Asy[ASY_MAX];asyp++){
		if(asyp->iface == NULLIF)
			continue;

		tprintf("%s:",asyp->iface->name);
		if(asyp->is_16550a)
			tprintf(" [NS16550A]");
		if(asyp->trigchar != -1)
			tprintf(" [trigger 0x%02x]",asyp->trigchar);
		if(asyp->cts_flow_control)
			tprintf(" [cts flow control]");
		if(asyp->rlsd_line_control)
			tprintf(" [rlsd line control]");
		tprintf(" %u bps\n",asyp->speed);

		tprintf(" RX: %lu int, %lu chr, %lu hw over, %lu hw hi,",
			asyp->rxints,
			asyp->rxchar,
			asyp->overrun,
			asyp->rxhiwat);
		asyp->rxhiwat = 0;
		if(asyp->is_16550a)
			tprintf(" %lu fifo TO,",asyp->fifotimeouts);
		tprintf(" %lu sw over, %u sw hi\n",
			asyp->fifo.overrun,
			asyp->fifo.hiwat);
		asyp->fifo.hiwat = 0;

		if(tprintf(" TX: %lu int, %lu chr, %u q, %lu CTS/RLSD, %lu THRE TO\n",
			asyp->txints,
			asyp->txchar,
			len_q(asyp->sndq),
			asyp->cts_flow_ints,
			asyp->txto) == EOF)
			break;
	}
	return 0;
}
/* Send a message on the specified serial line */
int
asy_send(dev,bp)
int dev;
struct mbuf *bp;
{
	if(dev < 0 || dev >= ASY_MAX)
		return -1;
	enqueue(&Asy[dev].sndq,bp);
	return 0;
}

/* Serial transmit process, common to all protocols */
static void
asy_tx(dev,p1,p2)
int dev;
void *p1;
void *p2;
{
	register struct mbuf *bp;
	struct asy *asyp;
	struct dma *dp;
	int i_state;

	asyp = &Asy[dev];
	dp = &asyp->dma;

	for(;;){
		/* Fetch a buffer for transmission */
		while(asyp->sndq == NULLBUF)
			pwait(&asyp->sndq);

		bp = dequeue(&asyp->sndq);

		while(bp != NULLBUF){
			/* Start the transmitter */
			asy_output(dev,bp->data,bp->cnt);

			/* Wait for completion */
			i_state = dirps();
			while(dp->flags == 1)
				pwait(asyp);
			restore(i_state);

			/* Now do next buffer on chain */
			bp = free_mbuf(bp);
		}
	}
}
