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

#ifndef lint
static char *rcsid = "$Header:keyboard.c 12.0$";
#endif

/*
 * keyboard input routine.
 * includes keyboard initialization and test routine written in
 * PL.8 by D. Fogg (IBM Austin) and translated into C
 * by W. Webb.
 * the mapping of scan codes into ascii characters is controlled
 * by the table in codes.h, which is generated from the file "codes".
 */

#ifdef IBMRTPC
#define FULL_TEST	1	       /* do full init test */

#include "sari.h"
#include "keyboard.h"
#include "../machine/machparam.h"
#include "../machine/cpu.h"
#include "screen_conf.h"
#ifdef SECURE
#include "../machine/nvram.h"
#endif SECURE

/* the actual code array is defined in codes.h */
#include "codes.h"

int _init_kbd = 0;

getchar()
/*
 * getchar for standalone use: maps CR into NL
 * and echos each character. use _getchar to avoid these
 * effects.
 */
{
	register int c;
#ifdef ATR
	if (cpu == CPU_ATR)
		return(getchar_atr());
#endif ATR
	c = _getchar();
	if (c == '\r')
		c = '\n';
	putchar(c);
	return (c);
}


static int next_char;		       /* next character to return */

int esc_char = ESC1_CHAR;	       /* the first escape character */

#ifdef ESC2_CHAR
int esc2_char = ESC2_CHAR;
#else
int esc2_char = 0;
#endif

_getchar()
{
	register int c, ch;
	int iid;

#ifdef ATR
	if (cpu == CPU_ATR)
		return(_getchar_atr());
#endif ATR
	if (c = next_char) {
		if (c & ESC_MARK) {
			next_char = c - ESC_MARK;
			return (esc2_char);
		}
		next_char = 0;
		return (c);
	}
	if (!_init_kbd)
		init_kbd();
	for (;;) {
		ch = get_kbd(&iid, getchar_timeout);
		if (iid == KBD_TIMEOUT && (c = getchar_char))
			break;
		if (iid != KBD_DATA)
			continue;
		if ((c = key_scan(ch)) != NONE)
			break;
	}
	getchar_timeout = 0;
	TRACEF(("got character %x from scan code %x\n", c, ch));
	if (c & ESC_MARK) {
		if (esc2_char)
			next_char = c; /* leave the mark */
		else
			next_char = c - ESC_MARK; /* remove mark */
		c = esc_char;	       /* send escape character 1 */
	}
	return (c);
}


#define FAIL(n)	TRACEF(("return %d\n",n)) /* ; return(n)	/* */

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

int nobell = NOBELL;

init_kbd()
{
	register int x, i;
	int iid;

#ifdef ATR
	if (cpu == CPU_ATR)
		return(init_kbd_atr());
#endif ATR
	if (++_init_kbd)		       /* mark as done */
		return(0);
	TRACEF(("init init_kbd\n"));

#ifdef NOTIMEOUT
	getchar_timeout = 0;
#endif

	make_break = state = 0;
	out(KYB_CNTIW, 0x09);	       /* enable interrupt request */
#ifdef FULL_TEST
	kbd_reset();		       /* reset the keyboard */

	out(KYB_CNTIW, KYB_CONFIG);    /* configure 8255 */
	delay(200);		       /* delay 200 ms */

	clear_kbd(40);		       /* clear pipeline */
	if (in(KYB_CNTIR) & KYB_IBF) {
		kbd_not_empty();
		FAIL(1);
	}
	x = kbd_cmd(CMD(EXTCMD, RD1C), 0); /* issue read shared RAM command */
	if (x != (0xAE | (KBD_REQ << 8))) {
		printf("keyboard self test failed: IID/data = %x\n", x);
		FAIL(3);
	}
	clear_kbd(10);		       /* do 10 reads to empty pipeline */
	if (in(KYB_CNTIR) & KYB_IBF) {
		kbd_not_empty();
		FAIL(3);
	}
	x = kbd_cmd(CMD(EXTCMD, ENKYBD), 0); /* issue set mode 11 command */
	if (x != 0x0000) {
		printf("keyboard failed to clear: IID/data = %x\n", x);

		FAIL(4);
	}
	clear_kbd(10);		       /* do 10 reads to empty pipeline */
	if (in(KYB_CNTIR) & KYB_IBF) {
		kbd_not_empty();
		FAIL(5);
	}
	x = kbd_cmd(CMD(KYBDCMD, KRESET), 0); /* issue reset keyboard command */
	if (x != 0x0000) {
		printf("keyboard failed to reset: IID/data = %x\n", x);

		FAIL(6);
	}
	clear_kbd(10);
	for (i = 0; i < 4; ++i) {
		x = get_kbd(&iid, 2000); /* pick up the BAT etc. */
		if (x == 0xAA)
			break;
	}
	beep();			       /* ring my chimes ! */
	clear_kbd(10);
	out(KYB_CNTIW, 0x09);	       /* enable interrupt request */
#endif				       /* FULL_TEST */
	clear_kbd(10);
#ifdef SECURE
	kbd_cmd(CMD(EXTCMD,0x32),0); 
	if (nvr_is_secure()) {
		printf("Secure bit was detected. Keyboard disabled.\n");
		kbd_cmd(CMD(KYBDCMD, NKSCAN), 0);
	} else 
#endif SECURE
	kbd_cmd(CMD(KYBDCMD, KSCAN), 0); /* enable scanning */
	TRACEF(("kbd_init normal return\n"));
	kbd_status();
	return (0);
}


/*
 * wait for something to appear at the keyboard, then return it
 * and the associated status byte.
 */
get_kbd(iidptr, timeout)
	register int *iidptr;
	register int timeout;
{
	register int c, iid, x;

	TRACEF(("waiting for keyboard\n"));
	while (((iid = in(KYB_CNTIR)) & KYB_IBF) == 0) {
		DELAY(1);
		if (timeout && --timeout == 0) {
			if (iidptr != 0)
				*iidptr = KBD_TIMEOUT;
			return (0);
		}
		last_scan = 0;	       /* reset last_scan if we have to wait */
		DELAY(2);		/* make delay more predictable */
	}
	DELAY(1);
	c = in(KYB_READ) & 0377;
	TRACEF(("got %x (IID=%x) from keyboard\n", c, iid));
	if (iidptr != 0)
		*iidptr = iid & IID_MASK; /* return it if requested */
#ifdef SECURE
#define KBD_LOCK_CHG	0x90
	if (((iid & IID_MASK)==KBD_INFO) && 
		((c & KBD_LOCK_CHG) == KBD_LOCK_CHG)) {
		nvr_release();
		x = crc_gen(_nvr,sizeof(struct nvram)-2);
		nvr_set_crc(x);
		kbd_cmd(CMD(KYBDCMD, KSCAN), 0);
	}
#endif SECURE
	/*
	 * we don't want to lose any make-break's
	 * even is someone else is getting the status
	 */
	if ((iid & IID_MASK) == KBD_DATA) {
		switch (make_break) {
		case 0:
			if (c == BREAK) {
				make_break++; /* make_break == 1 */
			}
			break;
		case 1:
			make_break++; /* make_break == 2 */
			break;
		case 2:
		default:
			make_break = 0;
			break;
		}
	}
	return (c);
}


LOCAL clear_kbd(n)
	register int n;
{
	register int c;

	while (--n >= 0) {
		c = in(KYB_READ);
		delay(2);
	}
}


/* flag bits for state variable */
#define CAPS_MODE	01	       /* must be 1. caps mode */
#define ALT_MODE	02	       /* alt mode */
#define CTL_MODE	04	       /* control mode */


#ifdef CLICK_CHAR
#define CLICK_FREQ 0x8c0	       /* click frequency */
#define CLICK_TIME 0x00		       /* minimal time */
int click_freq = CLICK_FREQ;
int click_time = CLICK_TIME;
#endif CLICK_CHAR

static	int shift_lock_down = 0;

key_scan(code)
	register int code;
{
	register int n = (state & CAPS_MODE) + (code << 1); /* CAPS_MODE == 1 */
	register int c = NONE;
	static int click = 1;
	
#ifdef DEBOUNCE
	if (last_scan == code)
		return (c);	       /* ignore 'BOUNCES' */
	last_scan = code;
#endif

	if (make_break) {
		register int old_state = state;

		/* make_break = 0; */
		switch (code) {
		case SHIFT_LOCK:       /* lock key let go */
			shift_lock_down = 0;
			break;
		case ALT1:
		case ALT2:
			state &= ~ALT_MODE; /* alt key released */
			break;
		case SHIFT1:
		case SHIFT2:
			state &= ~CAPS_MODE; /* shift key released */
			/*set_kbd_led(~CAPS_LED); /* turn off cap lock led */
			break;
		case CNTRL:
			state &= ~CTL_MODE; /* control key released */
			break;
		}
		if (state != old_state)
			kbd_status();  /* reflect state change */
	} else if (code >= MAX_CODES || (c = codes[n]) == NONE) {
		switch (code) {
		case SHIFT_LOCK:       /* caps lock key pressed */
			if (shift_lock_down == 0)
			{
				state ^= CAPS_MODE; /* invert caps state each time */
				/*set_kbd_led(state & CAPS_MODE ? CAPS_LED : ~CAPS_LED); */
				shift_lock_down = 1;
				kbd_status();
			} 
			break;
		case ALT1:
		case ALT2:
			state |= ALT_MODE; /* key held down */
			kbd_status();
			break;
		case SHIFT1:
		case SHIFT2:
			state |= CAPS_MODE; /* key held down */
			kbd_status();
			break;
		case CNTRL:
			state |= CTL_MODE; /* key held down */
			kbd_status();
			break;
#ifdef DEBUG_CHAR
		case DEBUG_CHAR:
			if (state == 0)
				debug = !debug;
			else if (state == ALT_MODE)
				cnhangup();
			kbd_status();
			break;
#endif
		/* can't get to here.... */
		case BREAK:
			make_break = 1;	/* released a make/break key */
			break;
#ifdef SWITCH_CHAR
		case SWITCH_CHAR:
			if (state & ALT_MODE)
				switch_screen(SCREEN_SWITCH ); /* to next screen */
			else if (state & CTL_MODE)
				switch_screen(SCREEN_SWITCH_RELOAD); /* to next screen */
			break;
#endif

#ifdef PRINT_CHAR
		case PRINT_CHAR:
			print_screen(state & CTL_MODE);	/* print it */
			break;
#endif

#ifdef CLICK_CHAR
		case CLICK_CHAR:
			{
				click = (click + 1) & 03;
				kbd_cmd(CMD(EXTCMD, ((click & 1) ? 0x2e : 0x3e)), 0);
			}
			break;

#endif CLICK_CHAR
		}
	} else {
		if (state == (ALT_MODE | CTL_MODE) && c == 0177)
			_reboot();     /* start execution again */
		if (state & CTL_MODE)
			c &= 037;
		if (c & ESC_FLAG)
			c ^= ESC_FLAG | ESC_MARK; /* turn on extra bit for ESC */
		if (state & ALT_MODE)
			c |= ALT_MARK; /* flag as alt hit */
	}
	TRACEF(("code = %x state=%x ==>%x\n", code, state, c));
#ifdef CLICK_CHAR
	if (c != NONE && (click & 2))
		speaker(click_freq, click_time);
#endif
	return (c);
}


LOCAL kbd_status()
{
	static int xdebug = -1;

	if (xdebug != debug) {
debug_loop:
		put_status(60, debug ? "DEBUG" : "     ");
		xdebug = debug;
#if defined(KERNEL) && defined(RDB)
		if (debug) {
			debug = 0;
			asm(" setsb r12,16-16 "); /* level 0 interrupt */
			goto debug_loop; /* redisplay it */
		}
#endif
	}
	put_status(68, (state & ALT_MODE) ? "ALT" : "   ");
	put_status(71, (state & CAPS_MODE) ? "CAPS" : "    ");
	put_status(75, (state & CTL_MODE) ? "CTRL" : "    ");
}



set_kbd_led(leds)
	char leds;
{
	register int x;
	int iid;
	if (leds <= 7)
		kbd_leds_state = kbd_leds_state | leds;	/* turn leds on */
	else
		kbd_leds_state = kbd_leds_state & leds;	/* turn leds off */
	do 
	  	 x=kbd_cmd(CMD(KYBDCMD, SETLED), 0);
	while (x !=0);

	do {				/* wait for command complete */
		x=kbd_cmd(CMD(EXTCMD,READ_STAT),0);
		while (x & IID_MASK <<8 != KBD_REQ <<8) {
			x=get_kbd(&iid,0);
			x+= iid << 8;
		}
	}
	while ( x & KYB_BUSY != 0);
	 
 	kbd_cmd(CMD(KYBDCMD, kbd_leds_state), 0);
}

get_keyboard()
{
/*
 * return either -1 if no character present
 * or the character
 */
	register int c, iid;

	if (((iid = in(KYB_CNTIR)) & KYB_IBF) == 0) {
		last_scan = 0;
		return (-1);
	}
	DELAY(2);
	c = in(KYB_READ) & 0377;
	if ((iid & IID_MASK) != KBD_DATA)
		return (-1);
	switch (make_break) {
	case 0:
		if (c == BREAK) {
			make_break++; /* make_break == 1 */
		}
		break;
	case 1:
		make_break++; /* make_break == 2 */
		break;
	case 2:
	default:
		make_break = 0;
		break;
	}
	if ((c = key_scan(c)) == NONE)
		c = -1;
	return (c);
}


#ifdef FULL_TEST
static kbd_not_empty()
{
	printf("keyboard pipeline won't empty\n");
}


kbd_reset()
{
	register int x;

	TRACEF(("kbd_reset called\n"));

	x = in(CRRB);
	x &= KBD_ADAPTOR_RESET;
	out(CRRB, x);

	delay(200);
	x = in(CRRB);
	x |= KBD_ADAPTOR_RELEASE;
	out(CRRB, x);

}


#endif	FULL_TEST

LOCAL
kbd_cmd(cmd, timeout)
	register int cmd, timeout;
{
/*
 * wait for PA (8255) to be able to accept a command and then
 * send it.
 * wait for the response.
 */
	register int i;
	int iid;

	cmd &= 0xffff;		       /* mask off any junk */
	TRACEF(("kbd_cmd(%x,%d)\n", cmd, timeout));
	for (i = 0; (in(KYB_CNTIR) & KYB_OBF) == 0; ++i);
				       /* for for output buffer to empty */
	if (i > 100)
		TRACEF(("kbd_cmd took %d iterations for busy to clear\n", i));
	outw(KYB_WRITE, cmd);
	i = get_kbd(&iid, timeout);
	return ((iid << 8) + i);       /* return the resulting character */
}


int beep_freq = (FCHI << 8) + FCLO;
int beep_time = SPTIME;

beep()
{
#ifdef ATR
	if (cpu == CPU_ATR)
		beep_atr();
	else
#endif ATR
		speaker(beep_freq, beep_time);
}


speaker(beep_freq, beep_time)
register int beep_freq, beep_time;
{
	if (!_init_kbd)
		return;		       /* oops */
	if (nobell)
		return;
	TRACEF(("speaker(%d,%d) entered\n", beep_freq, beep_time));
	kbd_cmd(CMD(SETFCHI, (beep_freq >> 8)), 100);
	kbd_cmd(CMD(SETFCLO, (beep_freq)), 100);
	/* kbd_cmd(CMD(EXTCMD,SP_MEDIUM),100);	/* set volume to medium */
	kbd_cmd(CMD(SPKRON, beep_time), 100);
	TRACEF(("speaker done\n"));
}


_reboot()
{
/*
 * generate a reboot request via the keyboard interface
 */
	TRACEF(("reboot in progress..."));
	kbd_cmd(CMD(EXTCMD, 0x3a), 10000); /* turn on diagnostic mode */
	kbd_cmd(CMD(EXTCMD, 0x61), 10000); /* request soft reset */
	kbd_cmd(CMD(EXTCMD, 0x2a), 10000); /* turn off diagnostic mode */
	delay(5000);		       /* should be long enough */
/* NOTREACHED */
	TRACEF(("reboot failed\n"));   /* can't get here ! */
}


cnhangup()
{
	beep();
}
#endif IBMRTPC
