#ifndef lint
static  char sccsid[] = "@(#)pp.c 1.1 92/07/30 SMI";
#endif

/*
 * Copyright (c) 1987 by Sun Microsystems, Inc.
 */

/*
 * Parallel Port (printer) driver.
 */

#include "pp.h"				/* file generated by CONFIG;
					   contains the definition of NPP */

#include <sys/ioctl.h>
#include <sundev/ppreg.h>			/* register definitions */
#if NPP > 0

#include <sys/param.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sundev/mbvar.h>
#include <machine/psl.h>


/*
 * Buffers for use by physio().
 */
struct buf ppbuf[NPP];
#define	PPBUFSIZ	64	/* size of buffer written to printer */

/*
 * software state structure, one for each printer
 */
struct ppstate {
	int		pp_flags;	/* printer state: */
#define	PP_OPEN		0x01		/* 	currently open */
#define	PP_WANT		0x02		/* 	someone waiting for printer */
#define	PP_TIMER	0x08		/*	watchdog timer is running */
#define	PP_BUSY		0x10		/*	i/o in progress */
	char		pp_timer;	/* for detecting timeout situations*/
	u_char		pp_lostintr;	/* for tracking lost interrupts*/
	u_char		pp_notready;	/* printer not ready (no paper, etc.) */
	int		pp_unit;	/* unit number*/
	struct mb_device *pp_md;	/* pointer to mb info */
	struct buf 	*pp_bp;		/* pointer to current 'buf' */
	char		pp_buf[PPBUFSIZ]; /* buffer */
	char		*pp_cp;		/* current byte in current buffer */
	int		pp_count;	/* number of bytes left to print */
#if 	defined(SUN386)
	u_short		pp_regbase;	/* device register base in i/o space */
#else 	/* !SUN386 */
	struct pp_reg	*pp_regbase;	/* device register base in mb space */
#endif
} ppstate[NPP];

#if 	defined(SUN386)
#define	PPREG_DATA	(pp->pp_regbase)
#define	PPREG_CTRL	(pp->pp_regbase + 2)
#define	PPREG_STAT	(pp->pp_regbase + 1)
#else 	/* !SUN386 */
#define	PPREG_DATA	(pp->pp_regbase->pp_data)
#define	PPREG_CTRL	(pp->pp_regbase->pp_cntrl)
#define	PPREG_STAT	(pp->pp_regbase->pp_stat)
#endif


#define	PPUNIT(dev)	(minor(dev) & 0xf)
#define	PPDIAG(dev)	(minor(dev) & 0x10)
#define	PPPRI		(PZERO + 1)	/* sleeps are interruptable */

extern int hz;
#define	PPWATCHDOG	3	/* watchdog interval: see 'pptimeout()' */
#define	PPTICKS		(30/PPWATCHDOG + 1)
#define	PPMSGTICKS	(180/PPWATCHDOG)

#define DEBUG
#ifdef DEBUG
/*
 * Debugging stuff.
 */
#define DBINIT	0x0001
#define DBIO	0x0002
#define DBOPEN	0x0004
#define DBCLOSE	0x0008
#define DBSTRAT	0x0010
#define DBSTART	0x0020
#define DBTMOUT	0x0040
#define DBINTR	0x0080

/*
int ppdebug = 0xffff;
*/
int ppdebug = 0;
#define ppprint(flg,x)  if ((flg) & ppdebug) printf x

#else
#define ppprint(flg,x)
#endif DEBUG

int ppprobe(), ppattach(), ppintr(), pptimeout(), pppoll();

struct mb_driver ppdriver = {
	ppprobe, 0, ppattach, 0, 0, pppoll, 0, "pp", 0, 0, 0, 0,
};


/*ARGSUSED*/
ppprobe(reg, unit)
	caddr_t reg;
	int unit;
{
	ppprint(DBINIT, ("ppprobe\n"));

	if (unit >= NPP) {
		printf("pp: too many units");
		return(0);
	}
	if (peekc(reg) == -1) {
		return(0);
	}

#if 	defined(SUN386)
	ppstate[unit].pp_regbase = (u_short)reg;
#else	/* !SUN386 */
	ppstate[unit].pp_regbase = (struct pp_reg *)reg;
#endif
	return(1);
}

ppattach(md)
	register struct mb_device *md;
{
	register struct ppstate *pp;

	ppprint(DBINIT, ("ppattach\n"));

	pp = &ppstate[md->md_unit];
	pp->pp_md = md;

	/* Initialize printer.
	 * Holding PC_INIT low for 50 msecs does the trick.
	 */
#if 	defined(SUN386)
	outb(PPREG_CTRL, PC_RESET);
	DELAY(5000);
	outb(PPREG_CTRL, PC_OFF);
	DELAY(10);
#else 	/* !SUN386 */
	PPREG_CTRL = PC_RESET;
	DELAY(5000);
	PPREG_CTRL = PC_OFF;
	DELAY(10);
#endif
}

ppopen(dev, flags)
	dev_t dev;
	int flags;
{
	register struct ppstate *pp = &ppstate[PPUNIT(dev)];
	int status;

	ppprint(DBOPEN, ("ppopen: unit %d\n", PPUNIT(dev)));

	if (PPUNIT(dev) >= NPP  ||  pp->pp_md->md_alive == 0)
		return(ENXIO);
	if ((PPDIAG(dev) == 0) & (flags & FREAD))
		return(ENODEV);
	
	pp->pp_unit = PPUNIT(dev);
	
	while (pp->pp_flags & PP_OPEN) {	/* enforce exclusive access */
		ppprint(DBOPEN, ("ppopen: in use - waiting...\n"));
		if (flags & FNDELAY)
			return(EBUSY);
		pp->pp_flags |= PP_WANT;
		if (sleep((caddr_t)&pp->pp_flags, PPPRI|PCATCH)) {
			return(EINTR);
		}
	}

#if 	defined(SUN386)
	status = inb(PPREG_STAT);
#else	/* !SUN386 */
	status = PPREG_STAT;
#endif
	if ((PPDIAG(dev) == 0) &&
	    (PSNOPAPER(status)  ||  ! PSSELECT(status)  ||  PSERROR(status))) {
		if (PSNOPAPER(status))
			uprintf("pp%d: printer out of paper\n", pp->pp_unit);
		else
			uprintf("pp%d: printer not ready\n", pp->pp_unit);
		(void)wakeup((caddr_t)&pp->pp_flags);
		pp->pp_flags = 0;
		return(EIO);
	}

#if 	defined(SUN386)
	outb(PPREG_CTRL, PC_NORM);	/* enable interrupts */
#else	/* !SUN386 */
	PPREG_CTRL = PC_NORM;		/* enable interrupts */
#endif
	DELAY(50);

	if ((pp->pp_flags & PP_TIMER)  ==  0) {
		/*
		 * Kick off watchdog timer.
		 */
		timeout(pptimeout, (caddr_t)pp, PPWATCHDOG*hz);
		pp->pp_timer = 0;
		pp->pp_flags |= PP_TIMER;
	}

	pp->pp_flags |= PP_OPEN;
	return(0);
}

/*
 * ppclose:
 *	Close the printer device.
 *	Turn off interrupts.
 *	Wake up anyone waiting to open the printer.
 */
ppclose(dev)
	dev_t dev;
{
	register struct ppstate *pp = &ppstate[PPUNIT(dev)];

	ppprint(DBCLOSE, ("ppclose: unit %d\n", PPUNIT(dev)));

#if 	defined(SUN386)
	outb(PPREG_CTRL, PC_OFF);	/* disable interrupts */
#else	/* !SUN386 */
	PPREG_CTRL = PC_OFF;		/* disable interrupts */
#endif
	DELAY(50);

	if (pp->pp_flags & PP_WANT)
		wakeup((caddr_t)&pp->pp_flags);
	untimeout(pptimeout, (caddr_t)pp);
	pp->pp_flags = 0;
}


ppwrite(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	int ppstrategy();
	void ppminphys();

	ppprint(DBIO, ("ppwrite\n"));

	return(physio(ppstrategy, &ppbuf[PPUNIT(dev)], dev, B_WRITE,
	    ppminphys, uio));
}

/*
 * ppstrategy:
 */
ppstrategy(bp)
	register struct buf *bp;
{
	register struct ppstate *pp = &ppstate[PPUNIT(bp->b_dev)];

	ppprint(DBSTRAT|DBIO, ("ppstrategy\n"));

	pp->pp_bp = bp;
	pp->pp_count = bp->b_bcount;
	pp->pp_cp = pp->pp_buf;

	if (copyin(bp->b_un.b_addr, pp->pp_buf, (u_int) bp->b_bcount)) {
		bp->b_flags |= B_ERROR;
		bp->b_error = EFAULT;
		ppiodone(pp);
		return;
	}

	pp->pp_flags |= PP_BUSY;
	pp->pp_timer = PPTICKS;		/* set timer */
	pp->pp_lostintr = 0;		/* reset "lost interrupt" counter */
	pp->pp_notready = 0;		/* reset "notready" counter */
	(void) ppintr();
	ppiowait(pp, bp);
	pp->pp_timer = 0;		/* turn off timer */

	ppprint(DBSTRAT, ("ppstrategy: ***done\n"));
}

void
ppminphys(bp)
	register struct buf *bp;
{
	if (bp->b_bcount > PPBUFSIZ)
		bp->b_bcount = PPBUFSIZ;
}

/*
 * ppintr:
 *	Handle 'ack' interrupts from printer.
 */
int	ppdelay1 = 1;
int	ppdelay2 = 1;
ppintr()
{
	register struct ppstate *pp;
	int status;	/* printer status */
	int s;

	pp = &ppstate[0];		/* XXX - only works for unit #0 */

	s = splx(pritospl(pp->pp_md->md_intpri));

#if 	defined(SUN386)
	status = inb(PPREG_STAT);
#else	/* !SUN386 */
	status = PPREG_STAT;
#endif
	ppprint(DBINTR, ("ppintr: status = 0x%x\n", status));

	/* Were we expecting an interrupt?
	 */
	if ((status & (PS_READY | PS_NOTACK)) != (PS_READY | PS_NOTACK)) {
		(void) splx(s);
		return(0);
	}


	if (pp->pp_count > 0) {

		/* AT Tech Ref says data must be in data reg at least
		 * 0.5 usec before and after we strobe, and strobe must
		 * last at least 0.5 usec.
		 */
#if 		defined(SUN386)
		outb(PPREG_DATA, *pp->pp_cp);
		pp->pp_cp++;
		pp->pp_count--;
		DELAY(1);
		outb(PPREG_CTRL, PC_NORM|PC_STROBE);
		DELAY(1);
		outb(PPREG_CTRL, PC_NORM);
#else		/* !SUN386 */
		PPREG_DATA = *pp->pp_cp;
		pp->pp_cp++;
		pp->pp_count--;
		DELAY(ppdelay1);
		PPREG_CTRL = PC_NORM|PC_STROBE;
		DELAY(ppdelay2);
		PPREG_CTRL = PC_NORM;
#endif
	}

	else
		ppiodone(pp);

	(void) splx(s);
	return(1);
}

/*
 * pptimeout:
 *	Check occasionally for lost interrupts or
 *	printer errors (no paper, printer off line, etc.).
 */
pptimeout(arg)
	caddr_t arg;
{
	register struct ppstate *pp = (struct ppstate *)arg;
	int	status;	/* printer status */
	int	error = 0;
	int	s;

	ppprint(DBTMOUT, ("pptimeout\n"));

	s = splx(pritospl(pp->pp_md->md_intpri));


	/* If we're not currently doing anything, we can go away.
	 */
	if ((pp->pp_flags & PP_OPEN)  ==  0) {	/* not open */
		(void) splx(s);
		return;
	} else if (pp->pp_timer <= 0) {		/* not currently active */
		timeout(pptimeout, (caddr_t)pp, PPWATCHDOG*hz);
		(void) splx(s);
		return;
	}

#if 	defined(SUN386)
	status = inb(PPREG_STAT);
#else	/* !SUN386 */
	status = PPREG_STAT;
#endif

	/* Check for printer errors.
	 */
	if (PSNOPAPER(status)) {
		if ((pp->pp_notready++ % PPMSGTICKS)  ==  0)
			uprintf("pp%d: printer out of paper\n", pp->pp_unit);
	} else if ( ! PSSELECT(status)  ||  PSERROR(status)) {
		if ((pp->pp_notready++ % PPMSGTICKS)  ==  0)
			uprintf("pp%d: printer not ready\n", pp->pp_unit);
	} else if (--pp->pp_timer == 0) {
		/*
		 * Timer has expired - see what's wrong.
		 */
		ppprint(DBTMOUT, ("pptimeout: status = 0x%x\n", status));

		if (PSREADY(status)) {
			/*
			 * We must have dropped an interrupt.
			 * If this is the first one we've dropped, assume
			 * it's a fluke and carry on.  Otherwise, give up.
			 */
			if (pp->pp_lostintr++ == 0) {
				ppprint(DBTMOUT, ("pptimeout: dropped intr\n"));
				pp->pp_timer = PPTICKS;	/* reset timer */
				(void) ppintr();
			} else {
				printf("pp%d: not getting interrupts\n",
					pp->pp_unit);
				error = 1;
			}
		} else {
			/* printer is hung */
			error = 1;
		}
	}

	if ( ! error) {
		timeout(pptimeout, (caddr_t)pp, PPWATCHDOG*hz);
	} else {
		pp->pp_bp->b_flags |= B_ERROR;
		ppiodone(pp);
		pp->pp_flags &= ~PP_TIMER;
	}

	(void) splx(s);
}


/*ARGSUSED*/
ppioctl(dev, cmd, data, flag)
	dev_t dev;
	int cmd;
	caddr_t data;
	int flag;
{
	register struct ppstate *pp = &ppstate[PPUNIT(dev)];
	unsigned char *udata;

	udata = (unsigned char *) data;
	switch(cmd) {
	case PPIOCGETS:
		*udata = PPREG_STAT;
		break;
	case PPIOCGETC:
		*udata = PPREG_CTRL;
		break;
	case PPIOCSETC:
		PPREG_CTRL = *udata;
		break;
	default:
		return(ENOTTY);
	}
	return(0);
}

/*
 * ppiowait:
 *	Private version of 'biowait()'.
 */
ppiowait(pp, bp)
	struct ppstate *pp;
	register struct buf *bp;
{
	int s;

	s = splx(pritospl(pp->pp_md->md_intpri));
	while ( ! (bp->b_flags&B_DONE)) {
		if (sleep((caddr_t)bp, PPPRI|PCATCH)) {
			bp->b_flags |= (B_ERROR|B_DONE);
			bp->b_error = EINTR;
		}
	}

	(void) splx(s);
}

/*
 * ppiodone:
 *	Private version of 'biodone()'.
 */
ppiodone(pp)
	register struct ppstate *pp;
{
	register struct buf *bp = pp->pp_bp;

	bp->b_flags |= B_DONE;
	wakeup((caddr_t)bp);

	pp->pp_flags &= ~PP_BUSY;
}

#if !defined(SUN386)
/*
 * Handle a polling Parallel Printer interrupt.
 */
pppoll()
{
	int serviced = 0;

	/* Were we expecting an interrupt?
	 */
	ppprint(DBINTR, ("pppoll routine\n"));
	ppstate[0].pp_regbase->pp_intr = 0;
	ppprint(DBINTR, ("pppoll cleared interrupt\n"));
	if (ppstate[0].pp_flags & PP_BUSY) {
		serviced = ppintr();
	} 
	return(serviced);
}
#endif

#endif NPP
