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

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

/* 
 * 8051 scheduler. Controls all the access to the planar 8051 to ensure the
 * keyboard, mouse, and speaker drivers do not step on each other's toes.
 *
 */
#include "../h/param.h"
#include "../h/ioctl.h"
#include "../h/tty.h"
#include "../machine/io.h"
#include "../h/time.h"
#include "../h/kernel.h"
#include "../machinecons/consdefs.h"
#include "../machinecons/kls.h"
#include "../machine/softint.h"
#include "ms.h"

int klsdone();
caddr_t		klsminit();
struct klsq 	*klsalloc();
struct clist	kls_ret, kls_info, kbd_data,ms_block,ms_uart; /* character "queues" */
struct klsq	*klshead,*klstail;	    /* pointers to the xmit queues */
struct klsq	*klsfreel;		    /* free list */
struct klsq	klspool[KLSPOOLSZ];
int		klsdebug = 0;
int		klsinreset = 0;
extern int cold; 

static char *klserrormsgs[] = {
	"Keyboard transmit timed out",				/* E0 */
	"Keyboard receive timed out",				/* E1 */
	"Keyboard acknowledge was not received",		/* E2 */
	"Unexpected keyboard acknowledge was received",		/* E3 */
	"Hard error was detected on keyboard frame receive",	/* E4 */
	"Hard error was detected on keyboard frame transmit",	/* E5 */
	"Keyboard clock pin not plus",				/* E6 */
	"Keyboard clock pin not minus",				/* E7 */
	"Uart interrupt received without tran/rec identifier",	/* E8 */
	"Uart transmit timed out",				/* E9 */
	"Uart acknowledge timed out",				/* EA */
};

#ifdef DEBOUNCE
int		klslastscan = 0x100;
struct		timeval	klslasttime = { 0, 0};
#endif DEBOUNCE

/* These are things that happen only once during boot time */
klsinit()
{
	klshead=NULL;
	klsfreel = (struct klsq *) klsminit(klspool,sizeof(struct klsq),KLSPOOLSZ);
	kbdinit();
#if NMS > 0
	msinit();
#endif NMS
	spkinit();

	/* put a dummy pointer on the queue to prevent kls from starting */
	/* if individual reset routines queue a command */
	klsinreset = 1;

	/* turn off other devices */
	(void) kls_raw_cmd(EXTCMD,CLRSPKMOD);
	(void) kls_raw_cmd(UARTCNT,MSDIS);
	(void) kls_raw_cmd(KYBDCMD,NKSCAN);

	/* The following routines have full control over the adapter 
	 * as long as they don't enable unsolicited input. They may
	 * place a command on the queue to do so. These routines are
	 * also responsible for any special cleanup caused by the
	 * rejection of the commands commands before the reset.
	 */

	kbdreset();	/* do keyboard first */
	kls_pflush(4);
	spkreset();
	kls_pflush(4);	/* clean up any strays */
#if NMS > 0
	msreset();
	kls_pflush(4);	/* clean up any strays */
#endif NMS
	
	beep();			       /* beep AFTER speaker has been inited! */

	/* start commands that have been queued */
	klsinreset = 0;
	if (klshead != NULL)
		klsstart();
#ifndef NOCONSOLEKEY
	out(KLS_CNTIW, 0x09);	       
#endif /* NOCONSOLEKEY */
	
}

/* kls_status, kls_pflush, and kls_raw_cmd may only be called by xxxreset routines! */

/*
 *	This routine waits for the status of a klscmd to be returned before continuing
 *	Legal statuses are 1) an info byte with is not an informational interupt (iid =
 *	KLS_INFO, c & KLSSRI != 0). 2) a request return bye (iid = KLS_REQ). All
 *	other statuses are ignored. If a timeout occurs, kls_status will return a KLS_SOFT_ERROR
 */
kls_status()
{
#ifndef NOCONSOLEKEY
	register int iid,c;
	register timeout = KLSMAXTIME;

	do {
		/* wait for something on in the input buffer */
		while ( (((iid=in(KLS_CNTIR)) & KLS_IBF) == 0) && (timeout-- > 0) )
			/* void */ DELAY(1);
		if (timeout <= 0)
			return(KLS_SOFT_ERROR);
		/* read it in */
		c=in(KLS_READ);
		iid &= IID_MASK;
	/* stop reading if it matches the conditions specified above */
	} while(!( ((iid == KLS_INFO)&&((c & KLSSRI) == 0)) 
			|| (iid == KLS_REQ) ));
	return ((iid << 8) + c);
#else /* NOCONSOLEKEY */
	return(0);
#endif /* NOCONSOLEKEY */
}


kls_raw_read(iid)
	register int *iid;
{
#ifndef NOCONSOLEKEY
	register int c;
	register timeout = KLSMAXTIME;


	/* wait for something on in the input buffer */
	while ( (((*iid=in(KLS_CNTIR)) & KLS_IBF) == 0) && (timeout-- > 0) )
		/* void */ DELAY(2);
	if (timeout <= 0) {
		*iid = KLS_SOFT_ERROR >> 8;
		return(KLS_SOFT_ERROR & 0xff);
	}
	DELAY(1);
	/* read it in */
	c=in(KLS_READ);
	*iid &= IID_MASK;
	return(c);
#else /* NOCONSOLEKEY */
	return(0);
#endif /* NOCONSOLEKEY */
}

/*	Kls_pflush trys to clear the input buffer. by doing successive reads. */
kls_pflush(count) 
	register int count;
{
#ifndef NOCONSOLEKEY
	register int tmp;
	for (; count >= 0 ; count--) {
		tmp = in(KLS_READ);
		delay(1);
	}
#endif /* NOCONSOLEKEY */
}

/*	Kls_raw_cmd sends a command to the adapter and waits for a return status */
kls_raw_cmd(dest,cmd)
	register int dest,cmd;
{
#ifndef NOCONSOLEKEY
	register int x;
	while((in(KLS_CNTIR) & KLS_OBF) == 0)
		/* void */ DELAY(1);
	outw(KLS_WRITE,CMD(dest, cmd));
	x = kls_status();
	return(x);
#else /* NOCONSOLEKEY */
	return(0);
#endif /* NOCONSOLEKEY */
}

#ifdef NOCONSOLEKEY
kls_must_cmd(dest,cmd)
	register int dest,cmd;
{
	register int iid,c;
	register timeout = KLSMAXTIME;

	while((in(KLS_CNTIR) & KLS_OBF) == 0)
		/* void */ DELAY(1);
	outw(KLS_WRITE,CMD(dest, cmd));

	do {
		/* wait for something on in the input buffer */
		while ( (((iid=in(KLS_CNTIR)) & KLS_IBF) == 0) && (timeout-- > 0) )
			/* void */ DELAY(1);
		if (timeout <= 0)
			return;
		/* read it in */
		c=in(KLS_READ);
		iid &= IID_MASK;
	/* stop reading if it matches the conditions specified above */
	} while(!( ((iid == KLS_INFO)&&((c & KLSSRI) == 0)) 
			|| (iid == KLS_REQ) ));
}
#endif /* NOCONSOLEKEY */
	


/* following allows us to get rid of bell and keyboard clicks easily */
#ifndef NOBELL
#define NOBELL 0
#endif

int nobell = NOBELL;

/* WARNING: THIS MAY BE CALLED AFTER A USER MODE MACHINE CHECK! NO SLEEPING */
klsreset()
{
	register int s=spl5();	/* don't let anyone interrupt us! */
	register int x;
	register int i;
	int	tmp;
 
	if (klshead != NULL) { /* commands have been queued */
		/* clear queue & wake everyone up */
		while (klshead != NULL) {
			klshead->qp_ret=KLS_SOFT_ERROR;
			if (klshead->qp_callback)
				(* klshead->qp_callback) (klshead);
			klshead = klshead->qp_next;
		}
		/* flush out pipeline */
		kls_pflush(10);
	}

#ifndef NOCONSOLEKEY
	x = in(CRRB);
	x &= KLS_ADAPTOR_RESET;
	DELAY(1);
	out(CRRB, x);

	delay(200);
	out(KLS_CNTIW, KLS_CONFIG);    /* configure 8255 */
	delay(200);		       /* delay 200 ms */

	out(KLS_CNTIW, 0x09);	       /* enable interrupt request */
	delay(200);

	x = in(CRRB);
	x |= KLS_ADAPTOR_RELEASE;
	DELAY(1);
	out(CRRB, x);

	delay(200);

	kls_pflush(40);	       /* clear pipeline */
	if (in(KLS_CNTIR) & KLS_IBF) {
		printf("adapter pipeline won't empty\n");
		printf("probable backlevel planar, trying old init\n");
		out(KLS_CNTIW, KLS_CONFIG);    /* configure 8255 */
		delay(200);		       /* delay 200 ms */

		out(KLS_CNTIW, 0x09);	       /* enable interrupt request */
		delay(200);
		kls_pflush(40);
		if (in(KLS_CNTIR) & KLS_IBF) {
			printf("adapter still won't empty!!\n");
			FAIL(1);
		}
	}
	x = kls_raw_cmd(EXTCMD,RD1C);
	if (x != ((KLS_REQ << 8) + 0xAE)) {
		printf("adapter self test failed: IID/data = %x\n", x);
		FAIL(3);
	}
#ifdef notdef
	kls_pflush(10);		  /* do 10 reads to empty pipeline */
	if (in(KLS_CNTIR) & KLS_IBF) {
		printf("adapter pipeline won't empty\n");
		FAIL(3);
	}
#endif
	x = kls_raw_cmd(EXTCMD,ENKYBD);
	if (x != ((KLS_INFO << 8) + 0x00)) {
		printf("keyboard failed to clear: IID/data = %x\n", x);
		FAIL(4);
	}
#ifdef notdef
	kls_pflush(10);		    /* do 10 reads to empty pipeline */
	if (in(KLS_CNTIR) & KLS_IBF) {
		printf("adapter pipeline won't empty\n");
		FAIL(5);
	}
#endif
	x = kls_raw_cmd(KYBDCMD,KRESET);
	if (x != ((KLS_INFO << 8) + 0x00)) {
		printf("keyboard failed to reset: IID/data = %x\n", x);
		FAIL(6);
	}

	delay(500);

	for (i = 0; i < 4; ++i) {
		x = kls_raw_read(&tmp); /* pick up the BAT etc. */
		if (x == 0xAA)
			break;
	}

	if (x != 0xAA)
		printf("BAT was not found\n");
#ifdef notdef
	kls_pflush(10);
	x = kls_raw_cmd(EXTCMD,KEYCLICK);
	kls_pflush(10);
#endif notdef
#endif /* NOCONSOLEKEY */
	/* put a dummy pointer on the queue to prevent kls from starting */
	/* if individual reset routines queue a command */
	klsinreset = 1;

	/* turn off other devices */
	(void) kls_raw_cmd(EXTCMD,CLRSPKMOD);
	(void) kls_raw_cmd(UARTCNT,MSDIS);
	(void) kls_raw_cmd(KYBDCMD,NKSCAN);

	/* The following routines have full control over the adapter 
	 * as long as they don't enable unsolicited input. They may
	 * place a command on the queue to do so. These routines are
	 * also responsible for any special cleanup caused by the
	 * rejection of the commands commands before the reset.
	 */

	spkreset();
	kls_pflush(4);	/* clean up any strays */
#if NMS > 0
	msreset();
	kls_pflush(4);	/* clean up any strays */
#endif NMS
	kbdreset();
	kls_pflush(4);
	
	beep();			       /* beep AFTER speaker has been inited! */

	/* start commands that have been queued */
	klsinreset = 0;
	if (klshead != NULL)
		klsstart();
	splx(s);
}
	
/* ARGSUSED */
klsint(unit, icscs, iar)
	int unit;
	int icscs;
	int iar;
{
	register int c;
	register int iid;
	register int klsret=KLSINVRET;
	register int not_our_int = 1;
	register int spkr,kbdsend;
	int lastscan = 0;
#ifdef SECURE
	int kbdchgsend = 0;
#endif SECURE

	spkr = kbdsend = 0;

/* empty the 8051 input buffer into clists */
	while (((iid=in(KLS_CNTIR)) & KLS_IBF) != 0) {
		if (!(iid & KLS_INT))
			break;
		not_our_int = 0;
		c = in(KLS_READ) & 0xff;
		switch (iid & IID_MASK) {
		case KLS_KBD:
			/* XXX need to check if we run out of clists */
			if (c != lastscan)
				putc(c,&kbd_data);
			lastscan = c;
			break;
#if NMS > 0
		case KLS_UART:
			putc(c,&ms_uart);
			break;
		case KLS_BLOCK:
			putc(c,&ms_block);
			break;
#endif NMS
		case KLS_INFO:
			if (c & KLSSRI)  { /* unsolicited info report */
			    if (!cold)  {
				putc(c, &kls_info); 
			    } else {
			    if (c & SPKRDONE)
				spkr++;
			    if (c & KEYBDDONE)
				kbdsend++;
#ifdef SECURE
			    if (c & KEYBDLOCK)
				kbdchgsend++;
#endif SECURE
			    }
			    break;
			}		  /* fall through to KLS_REQ */
		case KLS_REQ:
			if (klsret != KLSINVRET) {
			    printf ("klsint: extra return status found \n");
#ifdef DEBUG
			    printf (" old=0x%x new=0x%x dest=0x%x cmd=0x%x call=0x%x\n"
				,klsret,(((iid & IID_MASK) << 8) + c)
				,klshead->qp_dest,klshead->qp_cmd
				,klshead->qp_callback);
#endif DEBUG
				if (!cold)  {
 					/* dequeue previous klsret */ 
					getc(&kls_ret);
				}
			}
			klsret = ((iid & IID_MASK) << 8) + c;
			if (!cold)  {
				putc(klsret, &kls_ret); 
			}
			break;
		case KLS_ERROR:
			klserror(c);
			break;
		case KLS_RESERVED:
		case KLS_SELF_TEST:
		default:
			printf("klsint: received unexpected iid = %x\n",iid & IID_MASK);
		}
	}
	if (!cold)  {
		setsoftKLS();
	} else {

	/* call each of the interrupt routines if data is for them */
	if (klsret != KLSINVRET)
		klssendint(klsret);
	if (kbd_data.c_cc > 0) 
		kbdint();
#if NMS > 0
	if ((ms_block.c_cc > 0) || (ms_uart.c_cc > 0))
		msrint();
#endif NMS
	if (spkr)
		spkrint();
	if (kbdsend)
		kbdsint();
#ifdef SECURE
	if (kbdchgsend)
		kbd_lock_chg();
#endif SECURE
	}
	return(not_our_int);
}

/* get queue command up from earlier level */
klsstrategy(qp)
	register struct klsq *qp;
{
	register int	s=KLSSPL();

	qp->qp_next = NULL;
	qp->qp_ret = KLSINVRET;
	if (klshead == NULL) {
		klshead =qp;
		klstail =qp;
		if (!klsinreset)
			klsstart();
	} else {
		klstail->qp_next=qp;
		klstail = qp;
	}
	splx(s);
}

/* send command to 8051 */
klsstart()
{
	if (klshead == NULL)
		return;
#ifndef NOCONSOLEKEY
	while ((in(KLS_CNTIR) & KLS_OBF) == 0)
		/* void */ DELAY(1);
	outw(KLS_WRITE, CMD(klshead->qp_dest,klshead->qp_cmd));
#else /* NOCONSOLEKEY */
	{
	register int	s = KLSSPL();
	register int	keyboard = (klshead->qp_dest == KYBDCMD);
	register int	speaker = (klshead->qp_dest == SPKDURLO);
	
	klssendint(0);
	if (keyboard)
		kbdsint();
	if (speaker)
		spkrint();
	splx(s);
	}
#endif /* NOCONSOLEKEY */
}

/* handle return status & start next command on queue */
klssendint(retstat)
	register int retstat;
{
	register struct klsq *current=klshead;

	if (current == NULL)
		panic("klssendint: no commands");
	current->qp_ret=retstat;
	if ((klshead = current->qp_next) != NULL)
		klsstart();
	if (current->qp_callback)
		(* current->qp_callback) (current);
	else
		panic("klssendint: no callback ");
}

/* General command routine, used for queueing commands not requiring
 * special attention from the drivers 
 */
klscmd(dest,cmd,callback)
	register	char dest,cmd;
	register	int (*callback)();
{
	register struct klsq *qp;

	qp=(struct klsq *) klsalloc(&klsfreel);
	if (qp == NULL)
		return(-1);
	qp->qp_dest=dest;
	qp->qp_cmd=cmd;
	qp->qp_callback = (callback ? callback : klsdone);
	klsstrategy(qp);
	return(0);
}

klsdone(qp)
	register struct klsq *qp;
{
	klsfree(klsfreel,qp);
}

caddr_t
klsminit(pool,struct_size,pool_size)
	caddr_t pool;
	int	 struct_size,pool_size;
{
	int	count;
	caddr_t	current=pool;

	/*
	 * the following cryptic loop links the elements of the pool together. This
	 * method is used to allow linking pools of different sizes
	 */
	for (count = 0 ; count < pool_size-1;  count++) {
		*(caddr_t *) current = (current + struct_size);
		current += struct_size;
	}
	*(caddr_t *) current = NULL;
	return(pool);
}
	
struct klsq *
klsalloc(free)
	register struct klsq **free;
{
	register int s = KLSSPL();
	register struct klsq *tmp = *free;

	*free = (tmp ? tmp->qp_next : NULL);
	splx(s);
	return (tmp);
}

klserror(c)
	int c;
{
#ifndef NOCONSOLEKEY
	printf("klsint: Error code detected 0x%x.\n",c);
	if ( (c >= KLS_ERROR_LOW) && (c <= KLS_ERROR_HIGH) )
		printf("%s.\n",klserrormsgs[c-KLS_ERROR_LOW]);
	switch (c) {
		case KBD_XMIT_TIMEOUT:
		case KBD_ACK_TIMEOUT:
		case KBD_XMIT_ERROR:
			kbdsint();
			break;
#if NMS > 0
		case MS_XMIT_TIMEOUT:
		case MS_ACK_TIMEOUT:
			msrint();
			break;
#endif NMS
		default:
			break;
	};
#endif /*NOCONSOLEKEY*/
}

/*
 * Used by autoconf to wait for kls initialization to complete before
 * starting to probe controllers and devices.
 */
klswait()
{
	spkwait();
#if NMS > 0
	mswait();
#endif NMS
	kbdwait();
	while (klshead != NULL)
		delay(1);		/* for hc2 */
}

 
softkls() 
{
   register int c; 
   register int s=KLSSPL();	/* don't let anyone interupt us! */
	/* call each of the interrupt routines if data is for them */
	if (kls_ret.c_cc > 0) {
		c = getc(&kls_ret); 
		if (klshead != NULL) {
			klssendint(c);
		}
	}
	if (kbd_data.c_cc > 0) 
		kbdint();
#if NMS > 0
	if ((ms_block.c_cc > 0) || (ms_uart.c_cc > 0))
		msrint();
#endif NMS
	if (kls_info.c_cc > 0) {
	    c = getc(&kls_info); 
	    if (c & SPKRDONE)
		spkrint();
	    if (c & KEYBDDONE)
		kbdsint();
#ifdef SECURE
	    if (c & KEYBDLOCK)
		kbd_lock_chg();
#endif SECURE
	}
	splx(s); 
}

