/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:lp.c 12.1$ */
/* $ACIS:lp.c 12.1$ */
/* $Source: /ibm/acis/usr/sys/caio/RCS/lp.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char *rcsid = "$Header:lp.c 12.1$";
#endif

#include "lp.h"
#if NLP > 0
 /*
  * LP Line printer driver for IBM graphics printer
  */

#include "../h/param.h"
#include "../h/errno.h"
#include "../h/uio.h"
#include "../h/ioctl.h"
#include "../h/tty.h"
#include "../h/time.h"
#include "../machine/pte.h"
#include "../machine/io.h"
#include "../h/buf.h"
#include "../machineio/ioccvar.h"
#include "../machine/debug.h"
#include "../h/kernel.h"
#ifdef ATR
#include "../ca_atr/pcif.h"
#endif ATR

#include	"ps.h"

int lpprobe();
int lpattach();
int lpintr();
int lpwatch();
#ifdef ATR
int lpfakeint();	/* routine to handle fake interrupt in ATR version */
int lpdelay = 250;	/* how long to DELAY for if not ready */
#define LPADDR(iod) ((struct lpdevice *)((iod)->iod_addr + pcif_io_b))
#else
#define LPADDR(iod) ((struct lpdevice *)(iod)->iod_addr)
#endif ATR

caddr_t lpstd[] = {			/* standard line printers */
        (caddr_t)0x3bc+IO_BASE,
        (caddr_t)0x378+IO_BASE,
        (caddr_t)0x278+IO_BASE,
        0
};

struct iocc_device *lpdinfo[NLP];

struct iocc_driver lpdriver = {
        lpprobe, 0, lpattach,
        0 /* dgo */ , lpstd, "lp", lpdinfo, 0, 0, lpintr
};

/* LP commands */
#ifdef ATR		/* leave interrupt enable bit off in ATR version */
#define LP_STROBE_HIGH	(lp_noints?0x0D:0x1D)		  /* Strobe HIGH      */
#define LP_STROBE_LOW	(lp_noints?0x0C:0x1C)		  /* Strobe LOW       */
#endif ATR
#ifdef IBMRTPC
#define LP_STROBE_HIGH	0x1D		  /* Strobe HIGH      */
#define LP_STROBE_LOW	0x1C		  /* Strobe LOW       */
#endif IBMRTPC
#define LP_RESET_LOW	0x08		  /* Reset line LOW   */
#define LP_RESET_HIGH	0x0C		  /* Reset line HIGH  */
#define LP_INTR_ENABLE	0x10		  /* Enable interrupts */

/* LP status bits */
#define LP_BUSY		0x80		  /* Printer busy */
#define LP_ACK		0x40		  /* Pinter acknowledgement */
#define LP_NOPAPER	0x20		  /* Printer out of paper */
#define LP_SELECT	0x10		  /* Printer Selected for printing */
#define LP_ERROR	0x08		  /* Printer error */
#define	LP_ERROR_BITS	LP_ERROR+LP_NOPAPER /* Bits on when printer is off */
					  /* or disconnected */

#define LP_STATUS_BITS	LP_ACK + LP_NOPAPER + LP_BUSY + LP_SELECT + LP_ERROR
#define LP_INVERT	LP_ACK + LP_ERROR + LP_BUSY
#define LPBITS		"\20\10Busy\7Acknowledge\6OutOfPaper\5Selected\4Error"
#define LPNEXT(unit)	(unit == NLP - 1 ? 0 : unit+1)

#define LPPRI		(PZERO + 5)
#define LPLOWAT		50
#define LPHIWAT		300
#ifdef ATR
#define LPMAXTIME	40	/* Set timeout to 40 seconds */
#endif ATR
#ifdef IBMRTPC
#define LPMAXTIME	20	/* Set timeout to 20 seconds */
#endif IBMRTPC
#define LPUNIT(dev)		(minor(dev) >> 3)
#define LPSTATUS(lpaddr)    ((IOIN(&lpaddr->stat) & LP_STATUS_BITS) ^ LP_INVERT)
#define LPSPL() 		_spl3()


struct lpdevice {
        u_char data;			  /* Data register    */
        u_char stat;			  /* Status  register */
        u_char cmd;			  /* Command register */
};

struct lp_softc {
        struct clist sc_outq;
        int sc_state;
        int sc_timer;
	int sc_count;			  /* counter */
} lp_softc[NLP];

/* bits for state */
#define LP_OPEN		0x01		  /* Device is open */
#define LP_ASLEEP	0x02		  /* Awaiting draining of printer */
#define LP_ACTIVE	0x04		  /* Device is active */
#define LP_DEV_ERROR	0x08		  /* Device error detected */
#define LP_TIMER_ON	0x10		  /* Lpwatch timer is on */
#define LPSTATEBITS	"\20\1Open\2Asleep\3Active\4Error\5TimeoutOn"

#ifdef DEBUG
int lpdebug = 0;
#endif DEBUG

#ifdef ATR
int lp_noints = 1;			/* if we're using ints or not */
#endif ATR

lpprobe(lpaddr)
        register struct lpdevice *lpaddr;
{
/* Don't try to generate interupt. Printer must be on to do so */
	DEBUGF(lpdebug&SHOW_INIT, printf("lpprobe: %x\n", lpaddr));
#ifdef ATR
	pcvec_map[7] = 1;	/* defensive */
#endif ATR
        return (PROBE_NOINT); 
}

lpattach(iod)
        register struct iocc_device *iod;
{
        register struct lpdevice *lpaddr;

#if NPS > 0 /* Config sometimes mistakes lp for ps, so we will call it's */
            /* attach routine here to make sure it is called             */
	attach4216(iod);
#endif NPS > 0

	DEBUGF(lpdebug&SHOW_INIT, printf("lpattach: %x\n", lpaddr));
        lpaddr = LPADDR(iod);
        lpreset(lpaddr);
}


lpopen(dev, flag)
        dev_t dev;
        int flag;
{
        register int unit = LPUNIT(dev);
        register struct lpdevice *lpaddr;
        register struct lp_softc *sc = &lp_softc[unit];
        register struct iocc_device *iod = lpdinfo[unit];

	DEBUGF(lpdebug&SHOW_OPEN, printf("lpopen: dev=%x\n",dev));
        if (unit >= NLP || iod == 0 || iod->iod_alive == 0) {
                return (ENODEV);
        }
        lpaddr = LPADDR(iod);
        if ((sc->sc_state & LP_OPEN) == 0) {
                sc->sc_state |= LP_OPEN;

#ifdef ATR	/* disable interrupts for ATR version */
		if (lp_noints)
			IOOUT(&lpaddr->cmd, (IOIN(&lpaddr->cmd) & ~LP_INTR_ENABLE));
#endif ATR
                return (0);
        }
                DEBUGF(lpdebug&SHOW_OPEN,
			printf("LP: PRINTER %d ALREADY OPENED, STATE=0x%b\n",
				unit, sc->sc_state, LPSTATEBITS));
        return (EBUSY);
}


lpclose(dev, flag)
        dev_t dev;
        int flag;
{
        register struct lpdevice *lpaddr = LPADDR(lpdinfo[LPUNIT(dev)]);
        register struct lp_softc *sc = &lp_softc[LPUNIT(dev)];
        int s;

        s = LPSPL();
        DEBUGF(lpdebug&SHOW_OPEN,printf("Close: State=0x%b\n",sc->sc_state,LPSTATEBITS););
        while ((sc->sc_state & LP_ACTIVE) && ((sc->sc_state & LP_DEV_ERROR) == 0)) {
#ifdef ATR
		/* schedule a call to lpfakeint() before going to sleep */
		if (lp_noints)
			timeout(lpfakeint,(caddr_t)LPUNIT(dev),1);
#endif ATR
                sc->sc_state |= LP_ASLEEP;
		DEBUGF(lpdebug&SHOW_OPEN,printf("LP: LPCLOSE() SLEEPING UNTIL DRAINED\n"););
                sleep((caddr_t)sc, LPPRI);
        }
        DEBUGF(lpdebug&SHOW_OPEN,printf("Close: State=0x%b \n",sc->sc_state,LPSTATEBITS););
        sc->sc_state &= ~LP_OPEN;
        if (sc->sc_state & LP_DEV_ERROR) {		/* Clean up if error */
                while ((getc(&sc->sc_outq)) >= 0);	/* flush the queue */
                sc->sc_state &= ~LP_ACTIVE & ~LP_DEV_ERROR;  /* tidy up the states */
        	IOOUT(&lpaddr->cmd, (IOIN(&lpaddr->cmd) & ~LP_INTR_ENABLE));  /* No more messages */
        }
#ifdef ATR
	/*
	 * Clear all flags when closed. Otherwise, lpstart() will return 
	 * on next open if ACTIVE flag is still on.  Since we're not interrupt
	 * driven, we would not be restarted.
	 */
	if (lp_noints)
		sc->sc_state = 0;
#endif ATR
        splx(s);
        DEBUGF(lpdebug&SHOW_OPEN,
		printf("LP: LPCLOSE() PRINTER %d NOW CLOSED, State=0x%b\n",
		LPUNIT(dev),sc->sc_state,LPSTATEBITS););
}


lpwrite(dev, uio)
        dev_t dev;
        register struct uio *uio;
{
        register int unit = LPUNIT(dev);
        register struct lp_softc *sc = &lp_softc[unit];
        register int c;
        int s;

        while (uio->uio_resid != 0) {
                if ((c = uwritec(uio)) == -1) {
                        return (EFAULT);
                }
                DEBUGF(lpdebug&SHOW_RDWR,printf("LP: RECIEVED CHARACTER FROM USER 0x%x \n", c););
                s = LPSPL();
                while (sc->sc_outq.c_cc >= LPHIWAT) {
                        DEBUGF(lpdebug&SHOW_RDWR,printf("LP: REACHED HIGH WATER MARK...\n"););
                        lpstart(unit);
#ifdef ATR
		/* schedule a call to lpfakeint() before going to sleep */
			if (lp_noints)
				timeout(lpfakeint,(caddr_t)unit,1);
#endif ATR
                        sc->sc_state |= LP_ASLEEP;
                        sleep((caddr_t)sc, LPPRI);
                }
                DEBUGF(lpdebug&SHOW_RDWR,printf("LP: PUTTING CHARACTER ON QUEUE 0x%x\n", c););
                while (putc(c, &sc->sc_outq))
                        sleep((caddr_t)&lbolt, LPPRI);
                lpstart(unit);
                splx(s);
        }
        DEBUGF(lpdebug&SHOW_RDWR,printf("LP: NO MORE CHARS FROM USER... DRAINING QUEUE\n"););
                return(0);
}


lpstart(unit)
{
        register struct lpdevice *lpaddr = LPADDR(lpdinfo[unit]);
        register struct lp_softc *sc = &lp_softc[unit];

        if (sc->sc_state & LP_ACTIVE) {
                return;
        }
        DEBUGF(lpdebug&SHOW_RDWR,printf("LP: STARTING PRINTER...\n"););
        sc->sc_state |= LP_ACTIVE;
#ifdef ATR
	if (!lp_noints)
#endif ATR
		IOOUT(&lpaddr->cmd, (IOIN(&lpaddr->cmd) | LP_INTR_ENABLE));
        lpoutput(unit);
}

/*
 * Lp interupt routine. Since two of the printers come in at the same interupt 
 * level it is necessary to check which board actually generated the interupt.
 * To maintain fairness, the board with was last serviced will be the board 
 * that is check last in each round.
 */

int lpnextu = 0;

lpintr()
{
        register int unit;
        register struct lpdevice *lpaddr;
        register int lpfirst = 0;

	DEBUGF(lpdebug&SHOW_INTR, printf("lpintr:\n"));

#ifdef ATR
	if (lp_noints)
		return(1);	/* return immediately for ATR version */
#endif ATR

        for (unit = lpnextu; ((lpfirst == 0) || (unit != lpnextu)); unit = LPNEXT(unit)) {
                lpfirst++;			/* first loop is done */
                if (lp_softc[unit].sc_state & LP_ACTIVE) {
                        lpaddr = LPADDR(lpdinfo[unit]);
                        if (( LPSTATUS(lpaddr) & LP_BUSY) == 0 ) {
#if NLP > 1
                                lpnextu = LPNEXT(unit);
#endif NLP
                                return(lpoutput(unit));
                        }
                 }
        }
        return(1);
}

#ifdef ATR
/*
 * Lp fake interrupt handler for ATR version.  We fake interrupts
 * by scheduling calls to this routine using timeout().
 */

lpfakeint(unit)
int unit;
{
        register struct lpdevice *lpaddr;

	if (lp_softc[unit].sc_state & LP_ACTIVE) {
		lpaddr = LPADDR(lpdinfo[unit]);
		if (( LPSTATUS(lpaddr) & LP_BUSY) == 0 ) {
			lpoutput(unit);
			return;
		}
		/* printer marked ACTIVE but physically BUSY */
		if ((lp_softc[unit].sc_state & LP_TIMER_ON) == 0)
			lp_softc[unit].sc_state |= LP_TIMER_ON;
		DEBUGF(lpdebug&SHOW_INTR,
			printf("\nLPINTR: PRINTER %d BUSY...CALLING LPWATCH\n", unit));
		/* ATR version uses lpwatch to schedule calls 	*/
		/* to lpfakeint until the unit is not BUSY	*/
		lpwatch(unit);
		return;
	}
	DEBUGF(lpdebug&SHOW_INTR,printf("LPINTR1: PRINTER %d NOT ACTIVE...RETURNING\n", unit););
}
#endif ATR


lpoutput(unit)
{
        register struct lp_softc *sc = &lp_softc[unit];
        register struct lpdevice *lpaddr = LPADDR(lpdinfo[unit]);
        register int lpchar;
        register int lpstat;
#ifdef notdef
        int lpunit;
#endif notdef

	if (((lpstat=LPSTATUS(lpaddr)) & (LP_BUSY|LP_ERROR_BITS)) == 0) {
loop:
                sc->sc_timer=0;			/* did not time out */
                sc->sc_state &= ~LP_DEV_ERROR;
                if ((lpchar = getc(&sc->sc_outq)) >= 0) {
                        DEBUGF(lpdebug&SHOW_IO,
				printf("LP: LPOUTPUT() GOT CHAR 0x%x OFF QUEUE\n", lpchar));
                        IOOUT(&lpaddr->data, lpchar);
                        DELAY(1);
                        IOOUT(&lpaddr->cmd, LP_STROBE_HIGH);
                        DELAY(1);
                        IOOUT(&lpaddr->cmd, LP_STROBE_LOW);
                        DELAY(1);
                } else {
                        DEBUGF((lpdebug&SHOW_IO),{
                                printf("LP: LPOUTPUT() NO MORE CHARS ON QUEUE\n");
                        });
			sc->sc_state &= ~LP_ACTIVE;
#ifdef ATR
			if (!lp_noints)
#endif ATR
				IOOUT(&lpaddr->cmd, (IOIN(&lpaddr->cmd) & ~LP_INTR_ENABLE));
                }
        }

        if (((lpstat & LP_ERROR_BITS) != 0) || (sc->sc_state & LP_DEV_ERROR)) {
		sc->sc_state |= LP_DEV_ERROR;
                lpdiagnose(unit,lpstat);   /* find error type and notify user */
                sc->sc_timer=0;		   /* Kick lpoutout again in 20 sec */
        }
        if ((sc->sc_outq.c_cc <= LPLOWAT) && (sc->sc_state & LP_ASLEEP)); {
                DEBUGF(lpdebug&SHOW_IO,
			printf("LP: LPOUTPUT() WAKEUP UPPER HALF\n"));
                sc->sc_state &= ~LP_ASLEEP;
                wakeup((caddr_t)sc);	  /* top half should go on */
        }
        if (sc->sc_outq.c_cc <= 0) {
                DEBUGF(lpdebug&SHOW_IO,
                        printf("LP: LPOUTPUT() ALL DONE... INDICATE PRINTER INACTIVE\n"));
                if (sc->sc_state & LP_ASLEEP) {
                        sc->sc_state &= ~LP_ASLEEP;
                        wakeup((caddr_t)sc);
                }
#ifdef notdef
                for (lpunit=LPNEXT(unit);lpunit != unit;lpunit=LPNEXT(lpunit))
                        if (lp_softc[lpunit].sc_state & LP_ACTIVE) {
                                lpnextu = LPNEXT(lpunit);
                                (void) lpoutput(lpunit);
                        }
#endif notdef
                return (0);
        }
#ifdef ATR		/* don't watch for real interrupts in ATR version */
	if (!lp_noints)
#endif ATR
		if ((sc->sc_state & LP_TIMER_ON) == 0) {
			sc->sc_state |= LP_TIMER_ON;
			timeout(lpwatch,(caddr_t) unit,hz);
		}
#ifdef ATR		/* attempt to start more characters in ATR version */
	if (lp_noints) {
		for (lpchar=lpdelay; lpchar > 0 && (LPSTATUS(lpaddr) & LP_BUSY); --lpchar )
			DELAY(1);
		if (((lpstat=LPSTATUS(lpaddr)) & (LP_BUSY|LP_ERROR_BITS)) == 0) {
			++sc->sc_count;
			goto loop;	/* ready for more and no errors */
		}
	}
#endif ATR
        return (0);
}

lpdiagnose (unit,error)
{
        if (error & LP_NOPAPER)
                printf ("lp%d: Out of paper.\n",unit);
        else if ((error & LP_SELECT) == 0)
                printf ("lp%d: Offline.\n",unit);
        else
                printf ("lp%d: Unknown printer error, status=0x%b.\n",unit,error,LPBITS);
}


lpwatch(unit)
{
        register struct lp_softc *sc = &lp_softc[unit];
        register int s = LPSPL();

        if (sc->sc_state & LP_ACTIVE) {
  		if (sc->sc_timer++ >= LPMAXTIME) {
			sc->sc_state |= LP_DEV_ERROR;
			sc->sc_state &= ~LP_TIMER_ON;
			lpoutput(unit);
			splx(s);
			return;
  		}
#ifdef ATR
		if (!lp_noints)
#endif ATR
			timeout(lpwatch,(caddr_t) unit,hz);

#ifdef ATR
		else
		/*
		 * ATR version implements lpwatch to schedule calls to 
		 * lpfakeint until the unit is not BUSY or we hit LPMAXTIME
		 */
			timeout(lpfakeint,(caddr_t)unit,hz/2);
#endif ATR

        } else
		sc->sc_state &= ~LP_TIMER_ON;
	  splx(s);
}

lpreset(lpaddr)
        register struct lpdevice *lpaddr;
{

        IOOUT(&lpaddr->cmd, LP_RESET_LOW);
        DELAY(100);		/* 100 uS delay */
        IOOUT(&lpaddr->cmd, LP_RESET_HIGH);
}

#endif	NLP > 0
