/*
 * par.c : Amiga parallel port driver for Amix.
 *
 * This is a generic driver for the Amiga parallel port, appropriate
 * for use with a Centronics printer interface.  Only output is supported.
 *
 */


#include "sys/types.h"
#include "sys/conf.h"
#include "sys/param.h"
#include "sys/signal.h"
#include "sys/dir.h"
#include "sys/file.h"
#include "sys/errno.h"
#include "sys/termio.h"
#include "sys/tty.h"
#include "sys/sysmacros.h"
#include "sys/immu.h"
#include "sys/region.h"
#include "sys/proc.h"
#include "sys/user.h"
#include "sys/inline.h"
#include "amigahr.h"


/* Ioctl commands */
#define PIOC		('P'<<8)
#define PIOCGETCTL	(PIOC|1)
#define PIOCSETCTL	(PIOC|2)


/* Bits for the "icr" of the 8520 chip */
#define	ICR_SET		0x80		/* Set these bits */
#define ICR_CLR		0x00		/* Clear these bits */
#define ICR_FLG		0x10		/* The "Flag" interrupt bit */


/* Macros for the Amiga intena,intenar,intreq registers */
#define ienable(bits) (AMIGA->intena = ASET | (bits))
#define idisable(bits) (AMIGA->intena = ACLR | (bits))
#define	I_PORTS		0x0008


/* This is the cpu priority level for all parallel-related processing */
#define splpar()	spl2()


static int parflags;			/* Various flags */
#define	P_INPUT		0x01		/* Open for input mode (unsupported) */
#define	P_OUTPUT	0x02		/* Open for output mode */
#define	P_OPEN		0x03		/* NZ if the device is open */
#define P_OUTSLP	0x08		/* Sleeping because outq is full */
#define P_FLUSHING	0x10		/* Sleeping until queue is empty */
#define P_OUTPEND	0x20		/* Output character is pending */
#define P_OUTERR	0x80		/* An output character timed out */


/* The status lines from the parallel port */
#define C_SEL		0x04		/* Select */
#define C_POUT		0x02		/* Paper Out */
#define C_BUSY		0x01		/* Busy */
static short control_bits = C_SEL|C_POUT|C_BUSY;


static struct clist par_outq;			/* Output queue */
#define PAR_LOWAT	100
#define PAR_HIWAT	400


/* sleep() priorities */
#define PAROPRI		TTOPRI


/* If Output is not acked within PARTIMEOUT ticks, an I/O Error is flagged */
#define PARTIMEOUT	(HZ*60)		/* Sixty seconds */
/* If control lines are deasserted, try again in TRYAGAIN ticks */
#define TRYAGAIN	(HZ*1)		/* One second */
static int timeout_id;

static unsigned char currentbyte;
static void parstart(), partimeout();


/* This init function just makes everything */
/* be quiet until the device is opened. */
void parinit()
{
	ACIAB->ddra &= 3;		/* Set PSEL, PPOUT & PBUSY to input */
	ACIAA->ddrb = 0;		/* Set all 8 data bits to input */
	ACIAA->icr = ICR_CLR|ICR_FLG;	/* Disable "Flag" Interrupt */
}


/* Open the device */
void paropen(dev, mode)
dev_t dev;
int mode;
{
	int s;

	if (minor(dev)>0) {
		u.u_error = ENODEV;
		return;
	}

	/* Only output is supported for now */
	if (mode&FREAD) {
		u.u_error = EIO;
		return;
	}

	if (!(parflags & P_OPEN)) {
		s = splpar();

		ACIAB->ddra &= 3;	/* Set PSEL, PPOUT & PBUSY to input */
		ACIAA->ddrb = 0xff;	/* Set all 8 data bits to output */
		ACIAA->icr = ICR_SET|ICR_FLG; /* Enable "Flag" Interrupt */
		ienable(I_PORTS);	/* Just in case it wasn't already */
		parflags = P_OUTPUT;	/* Indicate that device is open */

		splx(s);
	}
}


/* Close the device */
void parclose(dev)
dev_t dev;
{
	int s = splpar();

	/* wait for output to drain */
	while (parflags & P_OUTPEND) {
		parflags |= P_FLUSHING;
		if (sleep(&par_outq, PAROPRI|PCATCH)) {
			/* Interrupted: abort and flush all output */
			untimeout(timeout_id);
			parflags &= ~P_OUTPEND;
			while (getc(&par_outq) != -1)
				;
		}
	}
	parflags = 0;
	splx(s);

	ACIAB->ddra &= 3;		/* Set PSEL, PPOUT & PBUSY to input */
	ACIAA->ddrb = 0;		/* Set all 8 data bits to input */
	ACIAA->icr = ICR_CLR|ICR_FLG;	/* Disable "Flag" Interrupt */
}


/* Write system call */
void parwrite(dev)
dev_t dev;
{
	int s;
	register int c;

	s = splpar();

	while ( (c=cpass()) != -1 ) {
		while (!(parflags & P_OUTERR) &&
		       par_outq.c_cc > PAR_HIWAT) {
			parflags |= P_OUTSLP;
			sleep(&par_outq, PAROPRI);
		}
		if (parflags & P_OUTERR) {
			parflags &= ~P_OUTERR;
			u.u_error = EIO;
			break;
		}
		if (!(parflags & P_OUTPEND))
			parstart(c);
		else
			putc(c, &par_outq);
	}

	splx(s);
}


/* Read system call */
void parread(dev)
dev_t dev;
{
	/* Not implemented */
	u.u_error = EINVAL;
}


/* Ioctl system call */
void parioctl(dev, cmd, arg)
dev_t dev;
int cmd, arg;
{
	switch (cmd) {
	case PIOCGETCTL:
		u.u_rval1 = control_bits;
		break;
	case PIOCSETCTL:
		control_bits = arg & (C_SEL|C_POUT|C_BUSY);
		break;
	default:
		/* Unknown ioctl */
		u.u_error = EINVAL;
		break;
	}
}


/*
 * Parallel port interrupt routine, called from trap.s (via aciaaintr)
 * during level 2 interrupt when ACIAA->icr says that the parallel port
 * (aciaa "F" input) actually is interrupting.
 */
void parintr()
{
	if (parflags & P_OUTPEND) {
		untimeout(timeout_id);
		if (par_outq.c_cc) {
			parstart(getc(&par_outq));
			if ((parflags & P_OUTSLP) &&
			    par_outq.c_cc <= PAR_LOWAT) {
				parflags &= ~P_OUTSLP;
				wakeup(&par_outq);
			}
		} else {
			parflags &= ~P_OUTPEND;
			if (parflags & (P_FLUSHING|P_OUTSLP)) {
				parflags &= ~(P_FLUSHING|P_OUTSLP);
				wakeup(&par_outq);
			}
		}
	}
}


/* Handle a timeout condition */
static void partimeout()
{
	if (!((ACIAB->pra ^ C_SEL) & control_bits)) {
		parflags |= P_OUTERR;
		/* Wake up anyone who's sleeping so they notice the error */
		if (parflags & (P_FLUSHING|P_OUTSLP)) {
			parflags &= ~(P_FLUSHING|P_OUTSLP);
			wakeup(&par_outq);
		}
	}
	parstart(currentbyte);
}


/* Try to output a byte */
static void parstart(ch)
unsigned char ch;
{
	currentbyte = ch;
	parflags |= P_OUTPEND;

	if ((ACIAB->pra ^ C_SEL) & control_bits)
		timeout_id = timeout(parstart, currentbyte, TRYAGAIN);
	else {
		ACIAA->prb = ch;
		timeout_id = timeout(partimeout, 0, PARTIMEOUT);
	}
}
