/*
 * (C) 1984 UniSoft Corp. of Berkeley CA
 *
 * UniPlus Source Code. This program is proprietary
 * with Unisoft Corporation and is not to be reproduced
 * or used in any manner except as authorized in
 * writing by Unisoft.
 *
 * Driver for Centronix Citoh Dot Matrix Printer
 */

#include "sys/param.h"
#include "sys/config.h"
#include "sys/types.h"
#include "sys/sysmacros.h"
#include "sys/systm.h"
#include "sys/dir.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/file.h"
#include "sys/tty.h"
#include "sys/termio.h"
#include "sys/conf.h"
#include "sys/sysinfo.h"
#include "sys/var.h"
#include "sys/reg.h"
#include "setjmp.h"
#include "sys/mmu.h"

#include "sys/cops.h"
#include "sys/pport.h"

extern struct device_d *pro_da[];

#define	LPPRI	(PZERO+8)
#define	LPLOWAT	40
#define	LPHIWAT	100
#define	LPMAX	NPPDEVS

struct lp {
	struct	clist l_outq;
	char	flag, ind;
	int	ccc, mcc, mlc;
	int	line, col;
	int	dev;
} lp_dt[LPMAX];

/*
 *	flag values - PORT, CAP and NOCR bits from minor device
 */
#define	PORT	0x0F		/* which port - 1,2,4,5,7,8 */
#define	CAP	0x10
#define	NOCR	0x20
#define	ASLP	0x40		/* only set within driver */
#define	OPEN	0x80		/* only set within driver */

#define	physical(d)	((minor(d)) & PORT)

#define	FORM	014
#define NO_OP asm("	nop ")
char lpflg[LPMAX];		/* whether we expect another interrupt */

/* ARGSUSED */
lpopen(dev, mode)
register dev_t dev;
{
	register unit;
	register struct lp *lp;
	register unsigned char zero;
	register struct device_d *p;
	int lpintr();
	extern char slot[];

	unit = physical(dev);
	SPL5();
	/* check expansion slot number and type (must be 2-port card) */
	if (!PPOK(unit) || (slot[PPSLOT(unit)] != PR0)) {
	err:
		u.u_error = EIO;
	fini:
		SPL0();
		return;
	}
	if ((lp = &lp_dt[unit])->flag) {
		goto err;
	}
	if (setppint(pro_da[unit],lpintr)) {	/* port is already busy */
		u.u_error = ENODEV;
		goto fini;
	}
	p = pro_da[unit];
	zero = 0;
	p->d_acr = 0;	/* no output latching */
	NO_OP;
	p->d_pcr = 0x6B;	/* set controller CA2 pulse mode strobe */
	NO_OP;
	p->d_ddra = -1;	/* set port A bits to output */
	NO_OP;
/*if (p == PPADDR)	from system III
p->d_ddrb &= 0x5C;	set BSY and OCD to input
else */
	p->d_ddrb &= 0xDC;		/* two or four port cards */
	NO_OP;
	p->d_ddrb |= 0x9C;		/* set port B bits 2,3,4,7 to out */
	NO_OP;
	p->d_irb &= ~(DEN|DRW);		/* disable buffers */
	NO_OP;
	p->d_irb |= WCNT;		/* set direction to output */
	NO_OP;
	p->d_ier = FIRQ|FCA1;		/* enable interrupt on busy */
	NO_OP;
	zero = p->d_irb;

	if (zero & OCD) {		/* out of paper ?? */
		printf("lpopen: cable disconnect or out of paper\n");
	out:
		freeppin(p);
		goto err;
	}

	if ((zero & PCHK) == 0) {		/* online ?? */
		printf("lpopen: (ddrb = %x) offline\n",zero);
		goto out;
	}
	lp->flag = (dev & (PORT | CAP | NOCR)) | OPEN;
	lp->ind = 4;
	lp->col = 80;
	lp->line = 66;
	lp->dev = dev;
	lpoutput(lp, FORM);
	SPL0();
}

lpclose(dev)
register dev_t dev;
{
	register unit;
	register struct lp *lp;

	unit = physical(dev);
	lp = &lp_dt[unit];
	lpoutput(lp, FORM);
	SPL5();
	while (lpflg[unit]) {
		lp->flag |= ASLP;
		(void) sleep((caddr_t)lp, LPPRI);
	}
	freeppin(pro_da[unit]);
	lp->flag = 0;
	SPL0();
}

lpwrite(dev)
register dev_t dev;
{
	register unit;
	register c;
	register struct lp *lp;

	unit = physical(dev);
	lp = &lp_dt[unit];
	while (u.u_count) {
		SPL5();
		while(lp->l_outq.c_cc > LPHIWAT) {
			lpintr(unit);
			lp->flag |= ASLP;
			(void) sleep((caddr_t)lp, LPPRI);
		}
		SPL0();
		c = fubyte(u.u_base++);
		if (c < 0) {
			u.u_error = EFAULT;
			break;
		}
		u.u_count--;
		lpoutput(lp, c);
	}
	SPL5();
	lpintr(unit);
	SPL0();
}

lpoutput(lp, c)
register struct lp *lp;
register c;
{
	if(lp->flag&CAP) {
		if(c>='a' && c<='z')
			c += 'A'-'a'; else
		switch(c) {
		case '{':
			c = '(';
			goto esc;
		case '}':
			c = ')';
			goto esc;
		case '`':
			c = '\'';
			goto esc;
		case '|':
			c = '!';
			goto esc;
		case '~':
			c = '^';
		esc:
			lpoutput(lp, c);
			lp->ccc--;
			c = '-';
		}
	}
	switch(c) {
	case '\t':
		lp->ccc = ((lp->ccc+8-lp->ind) & ~7) + lp->ind;
		return;
	case '\n':
		lp->mlc++;
		if(lp->mlc >= lp->line )
			c = FORM;
	case FORM:
		lp->mcc = 0;
		if (lp->mlc) {
			(void) putc(c, &lp->l_outq);
			if(c == FORM)
				lp->mlc = 0;
		}
	case '\r':
		lp->ccc = lp->ind;
		SPL5();
		lpintr(lp->dev);
		SPL0();
		return;
	case 010:
		if(lp->ccc > lp->ind)
			lp->ccc--;
		return;
	case ' ':
		lp->ccc++;
		return;
	default:
		if(lp->ccc < lp->mcc) {
			if (lp->flag&NOCR) {
				lp->ccc++;
				return;
			}
			(void) putc('\r', &lp->l_outq);
			lp->mcc = 0;
		}
		if(lp->ccc < lp->col) {
			while(lp->ccc > lp->mcc) {
				(void) putc(' ', &lp->l_outq);
				lp->mcc++;
			}
			(void) putc(c, &lp->l_outq);
			lp->mcc++;
		}
		lp->ccc++;
	}
}

lpintr(dev)
register dev;
{
	register struct lp *lp;
	register struct device_d *p;
	register c;

	dev = physical(dev);
	if (lpflg[dev]) return;
	lp = &lp_dt[dev];
	p = pro_da[dev];
	while ((c = getc(&lp->l_outq)) >= 0) {
		lpflg[dev] = 1;
		p->d_ira = c;
		c = 30;
		while ((p->d_ifr & FCA1) == 0) {
			if (--c <= 0)
				return;
		}
		lpflg[dev] = 0;
	}

	if (lp->l_outq.c_cc <= LPLOWAT && lp->flag&ASLP) {
		lp->flag &= ~ASLP;
		wakeup((caddr_t)lp);
	}
}

/* ARGSUSED */
lpioctl(dev, cmd, arg, mode)
{
}
