/*
 * 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 11.2$ */
/* $ACIS:keyboard.c 11.2$ */
/* $Source: /ibm/acis/usr/sys/standatr/RCS/keyboard.c,v $ */

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

/*
 * Keyboard input routine
 * Based on the RTPC stand-alone keyboard driver. Rewritten
 * by R. A. McKie, ACIS/Kingston.
 */

#ifdef ATR
#define ENH_AT_KEYBOARD
#define FULL_TEST


#ifdef IBMRTPC
/* if both are defined then arrange things so that we deterine the proper
 * routine to use at run time.
 * also rename common global variables to unique names
 */
#define getchar getchar_atr
#define codes codes_atr
#define _getchar _getchar_atr
#define init_kbd init_kbd_atr
#define speaker speaker_atr
#define beep beep_atr
#define get_kbd get_kbd_atr
#define key_scan key_scan_atr
#define beep_freq beep_freq_atr
#define beep_time beep_time_atr
#define click_freq click_freq_atr
#define click_time click_time_atr
#define esc_char esc_char_atr
#define esc2_char esc2_char_atr
#define nobell nobell_atr
#undef IBMRTPC
#endif IBMRTPC



#include "sa.h"
#include "keyboard.h"
#include "../h/param.h"
#include "../machine/cpu.h"
#include "screen_conf.h"
#include "../ca_atr/pcif.h"		/* need cbcb to get data from keyboard */


#include "codes.h"


struct kbdata *kbdata;			/* will point to kbd data area in PC memory  */ 
long	kb_pc_cb;			/* where keyboard structure is */

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;

	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;

	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_ASCII) {
			c = ch;		/* already in ASCII */
			break;
		}
		if (iid == KBSTAT_HOT_KEY) {
			/* 
			 * if we see a restore hotkey,
			 * reload the screen. It is
			 * always "saved".
			 */
			screen_redraw();
			continue;
		}
		if ((c = key_scan(ch)) != NONE)
			break;
	}
	getchar_timeout = 0;
	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;

/*
 *	This version of init_kbd came from ATR full-kernel version,
 *	and includes many fixes since original stand-alone version.
 */
init_kbd()
{
	register int i;
	int old_window;

	_init_kbd++;

	old_window = get_512_window();	/* save the current window */
	kb_pc_cb = get_pc_cb(KBENT);
	kbdata = (struct kbdata *)(set_512_window(kb_pc_cb) + pcif_512_fw);
	set_512_window(old_window);
	TRACEF(("init init_kbd\n"));

}


/*
 * 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;

	TRACEF(("waiting for keyboard\n"));

#ifdef PCKBD
	/*
	 *  If the PC is handling the keyboard then we must get
	 *  the PC address of the kbdata structure from the cbcb.
	 */
	register int old_window;

	old_window = get_512_window();	/* save the current window */
	kb_pc_cb = get_pc_cb(KBENT);
	kbdata = (struct kbdata *)(set_512_window(kb_pc_cb) + pcif_512_fw);

	/* check if new scan code is present */
	while ((iid = ((int)kbdata->kbstatus)) == 0) {
		if (timeout && --timeout == 0) {
			if (iidptr != 0)
				*iidptr = KBD_TIMEOUT;
			TRACEF(("Timeout: kbdata kbstatus register: %x\n", iid));
			set_512_window (old_window);	/* restore window */
			return (0);
		}
	}
	c = (int)kbdata->kbsc;		/* read scan code */
	kbdata->kbstatus = 0;		/* inform PC that scan code was read */
	set_512_window (old_window);	/* restore window */
#endif PCKBD

#ifndef PCKBD
	while (((iid = ((int)IOIN(kyb_cntir))) & KYB_OBF) == 0) {
		if (timeout && --timeout == 0) {
			if (iidptr != 0)
				*iidptr = KBD_TIMEOUT;
	TRACEF(("Timeout: keyboard status register: %x\n", IOIN(kyb_cntir)));
			return (0);
		}
		last_scan = 0;	       /* reset last_scan if we have to wait */
	}
	c = (int) IOIN(kyb_read);
#endif PCKBD

	TRACEF(("get_kbd: got %x (IID=%x) from keyboard\n", c, iid));
	if (iidptr != 0)
		*iidptr = iid & IID_MASK; /* return it if requested */
	return (c);
}


clear_kbd(n)
	register int n;
{
#ifndef PCKBD
	register int c;

	while (--n >= 0) {
		c = (int) IOIN(kyb_read);
		delay(1);
	}
#else
	register int i;
	int old_window;

	old_window = get_512_window();	/* save the current window */
	kbdata = (struct kbdata *)(set_512_window(kb_pc_cb) + pcif_512_fw);
	for(i=0;i<n;i++) {	/* clear pending scan codes */
		kbdata->kbstatus = 0;
		delay(1);
	}
	set_512_window(old_window);
#endif PCKBD
}


/* 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 */
#define NUM_MODE	010		/* numeric keypad mode */
#define SCROLL_MODE	020		/* scroll stop */

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

/*
 *	The following key_scan taken from ORIGINAL 3/31 standalone
 *	keyboard driver. ORIGINAL "codes.h" can be used for Enhanced
 *	AT keyboard, NEW codes_atr.h necessary for AT keyboard.
 */
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;
	
#ifndef PCKBD
#ifdef DEBOUNCE
	if (last_scan == code)
		return (c);	       /* ignore 'BOUNCES' */
	last_scan = code;
#endif DEBOUNCE
#endif PCKBD

	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:
#ifdef ENH_AT_KEYBOARD
		case ALT2:
#endif ENH_AT_KEYBOARD
			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:
#ifdef ENH_AT_KEYBOARD
		case ALT2:
#endif ENH_AT_KEYBOARD
			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();
			TRACEF(("DEBUG_CHAR detected!\n"));
			kbd_status();
			break;
#endif
		case BREAK:
			make_break = 1;	/* released a make/break key */
			break;
#ifdef SWITCH_CHAR
		case SWITCH_CHAR:
			switch_screen((state&CTL_MODE) ? SCREEN_SWITCH_RELOAD : SCREEN_SWITCH ); /* to next screen */
			break;
#endif SWITCH_CHAR

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

#ifdef CLICK_CHAR
		case CLICK_CHAR:
			{
				/* invert click flag */
				click = !click;
			}
			break;

#endif CLICK_CHAR
		}
	} else {
		if (state == (ALT_MODE | CTL_MODE) && c == 0177)
		TRACEF(("REBOOT not implemented!\n"));
/*			_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) )
		speaker(click_freq, click_time);
#endif CLICK_CHAR

#ifndef PCKBD
	/*
	 *  If the key detected was a 'RETURN', check to see if Keyboard Lock
	 *  is on. If so, delay until it has been turned off.
	 */
	if(c == '\r') {
		if(!(IOIN(kyb_cntir) & KYB_INHIBIT)) {
			printf("\nKeyboard locked and disabled!\n");
			while_kbd_disabled();  
		}
	}
#endif PCKBD
	return (c);
}


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

	if (xdebug != debug) {
debug_loop:
		put_status(60, debug ? "DEBUG" : "     ");
		xdebug = debug;
	}
	put_status(68, (state & ALT_MODE) ? "ALT" : "   ");
	put_status(71, (state & CAPS_MODE) ? "CAPS" : "    ");
	put_status(75, (state & CTL_MODE) ? "CTRL" : "    ");
}



static
set_kbd_led(leds)
	char leds;
{
#ifndef PCKBD
	register int x;
	if (leds <= 7)
		kbd_leds_state = kbd_leds_state | leds;	/* turn leds on */
	else
		kbd_leds_state = kbd_leds_state & leds;	/* turn leds off */
	x=kbd_cmd(CMD(KBC_LEDS,(int) kbd_leds_state));
	if(x != KBR_ACK)
		printf("set_kbd_led: unable to set leds\n");
#endif PCKBD
}


#ifdef PCKBD
/*
 *  The following get_keyboard() function apparently is only called
 *  from the module wterm.c (standalone terminal emulator).  If 
 *  wterm.c is ported, this will have to be rewritten to get scan
 *  codes from the PC.		WBW 3/17/87
 */
#endif PCKBD

static
get_keyboard()
{
#ifndef PCKBD
/*
 * return either -1 if no character present
 * or the character
 */
	register int c;

	if (((IOIN(kyb_cntir)) & KYB_OBF) == 0) {
		last_scan = 0;
		return (-1);
	}
	c = (int)(IOIN(kyb_read) & 0377);

	if ((c = key_scan(c)) == NONE)
		c = -1;
	return (c);
#endif PCKBD
}


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


/*
 *  This version of kbd_reset is from full-kernel version, and
 *  incorporates many changes.
 */
static
kbd_reset()
{
#ifndef PCKBD
	register char resp;
/*
 *  Determine what keyboard we are resetting. If we have the Enhanced
 *  keyboard, issue the command to set this into mode 3, so that normal
 *  RT/PC keyboard scan codes are produced.
 */

	/*	Controller Self-Test		*/

	if (s8042_cmd(CMD(CC_SELFTEST,0)) != 0)
		printf("kbd_reset: unable to initiate controller selftest\n");

	/*	Wait for hex 55 == "no errors"	*/
	resp = 0;
		resp = IOIN(kyb_read) & 0xff;
	for(;;) {
		resp = IOIN(kyb_read);
		if(resp == 0x55) break;
	}
TRACEF(("Controller passed Self-Test\n"));
delay(50);

	/* Interface Test                       */

	if (s8042_cmd(CMD(CC_IFTEST,0)) != 0)
		printf("kbd_reset: unable to initiate controller interface test\n");

	/*	Wait for hex 00 == "no errors"	*/
	resp = 0;
		resp = IOIN(kyb_read) & 0xff;
	for(;;) {
		resp = IOIN(kyb_read);
		if(resp == 0x00) break;
	}
TRACEF(("Interface passed Self-Test\n"));

delay(50);
	/*	Enable Keyboard Interface	*/

	if (s8042_cmd(CMD(CC_ENABLE_KYBD,0)) != 0)
		printf("kbd_reset: unable to enable keyboard interface\n");
	else
		TRACEF(("kbd_reset: enabled keyboard interface ...\n"));
delay(50);
/*	Reset the keyboard - This performs BAT	*/

	kbd_cmd(CMD(KBC_RESET,0));

	delay(50);

	/* See if BAT executed OK */
	resp = IOIN(kyb_read) & 0xff;

	if(resp == KBR_OK) {
		TRACEF(("BAT completed successfully!\n"));
		beep();
	}
	else
		TRACEF(("BAT error Returns %x\n",resp));
	
delay(50);
/*	Enable the keyboard	*/

	kbd_cmd(CMD(KBC_ENABLE,0));
	TRACEF(("Successfully enabled keyboard ...\n"));
delay(50);
#ifdef ENH_AT_KEYBOARD
	int i, iid, opt, codeset;

	/* Send a test echo command */

		/* wait for input buffer to empty */
	for(i = 0; (IOIN(kyb_cntir) & KYB_IBF) && i<200; ++i);

	if(i >= 200)
		printf("kbd_reset: input buffer won't empty\n");

	IOOUT(kyb_write, KBC_ECHO);	/* Send the controller a command */
	delay(50);
        iid = (int) IOIN(kyb_read);
	if(iid == KBC_ECHO){
		TRACEF(("Keyboard successfully passed echo test. \n"));
	}
delay(50);

	/* Set the Enhanced keyboard to send RT scan codes ... */
	opt = 3;
	kbd_cmd(CMD(KBC_SCANSELECT,opt));
	TRACEF(("Resetting Enhanced Keyboard to scan code set %x\n",opt));
	delay(50);

	/* Try reading the code set # back ...   */
	opt = 0;
	kbd_cmd(CMD(KBC_SCANSELECT,opt));
	codeset = 0;
	delay(50);
	/* Then read codeset #			*/
	for(i=0;i<100;i++) {
		codeset = (int) IOIN(kyb_read);
		if(codeset <= 3) {
			TRACEF(("Keyboard codeset # %x\n",codeset));	
			break;
		}	
		if (i >= 100) TRACEF(("Unable to read back codeset #\n"));
	}
#endif ENH_AT_KEYBOARD

/*	Turn off all the LED's		*/
	
	set_kbd_led(0x8);	
#endif PCKBD
}

#endif FULL_TEST

static
kbd_cmd(cmd)	/*	Send the keyboard a command	*/
	register int cmd;
{
#ifndef PCKBD
/*
 * Wait for the 8042 to indicate 'input buffer not full'. Then send it
 * the command 'cmd'.
 * Wait for the response.
 */
	register int i;
	int iid;
	int takes_param;
	int bcmd,bparam;

	bcmd = (int) (cmd & 0xff);
	bparam = (int) ((cmd & 0xff00) >> 8);
	takes_param = 0;
	if((bcmd == KBC_LEDS) || (bcmd == KBC_TYPEMATIC) || (bcmd == KBC_SCANSELECT)) takes_param = 1;
	TRACEF(("kbd_cmd(%x)\n",cmd));
		/* wait for input buffer to empty */
	for(i = 0; (IOIN(kyb_cntir) & KYB_IBF) && i<200; ++i);
	TRACEF(("kbd_cmd took %d iterations for busy to clear\n",i));
	if(i >= 200) {
		printf("kbd_cmd: input buffer won't empty\n");
		return(1);
	}
	TRACEF(("Sending command %x\n",bcmd));
cmd:	IOOUT(kyb_write, bcmd);		/* Send the controller a command */

	delay(1);             /* Wait for command to be accepted */ 
	for(i=0;i<1000;i++) {
		iid = (int) IOIN(kyb_read);
	        if(iid == KBR_ACK) break;
	}
	TRACEF(("kbd_cmd: resp to cmd after %d iterations = %x\n",i,iid)); 
	if(i >= 1000) {
		printf("kbd_cmd: command not acknowledged!\n");
		return(2);
	}
data:	if(takes_param) {
		TRACEF(("takes_param = %d\n",takes_param));
		/*
		 * If command takes additional
		 * param, send and wait for
		 * additional acknowledgement
		 */
		IOOUT(kyb_write, bparam);
		delay(1);
	        for(i=0;i<1000;i++) {
		       	iid = (int) IOIN(kyb_read);
			if(iid == KBR_ACK) break;
		}
		TRACEF(("kbd_cmd: resp to param after %d iterations = %x\n",i,iid));
		if(i >= 1000) {
			printf("kbd_cmd: parameter not acknowledged!\n");
			return(3);
		}
	}		
	return(iid);
#endif PCKBD
}



s8042_cmd(cmd)
register int cmd;
{
#ifndef PCKBD
/*
 * Wait for the 8042 to  be empty. Then send it the command. Then
 * wait for the input buffer to empty again, signifying receipt
 * of the command.
 *
 * If command takes a parameter (CC_WRITE), send this to 0x60 and
 * wait for input buffer to empty before returning.
 */

	register int i, ret = 0;
	int takes_param;
	int bcmd,bparam;

	bcmd = (int) (cmd & 0xff);
	bparam = (int) ((cmd & 0xff00) >> 8);
	takes_param = (bcmd == CC_WRITE)?1:0;
 	TRACEF(("s8042_cmd: cmd = %x\n", cmd));
	
		/* wait for input buffer to empty */
	for(i = 0; (IOIN(kyb_cntir) & KYB_IBF) && i<200; ++i);
	TRACEF(("s8042_cmd: %d iterations needed to clear input buffer\n"));
	if (i >= 200) {
		TRACEF(("s8042_cmd: could not clear input buffer\n"));
		ret = -1;
	}

cmd:	IOOUT(kyb_cntiw, bcmd);		/* Send the controller a command */

	TRACEF(("s8042_cmd: bcmd = %x\n",bcmd));
					/* wait for input buffer to re-empty */
	for(i = 0; (IOIN(kyb_cntir) & KYB_IBF) && i<200; ++i);
	TRACEF(("s8042_cmd: took %d iterations to accept command\n",i));
	if (i >= 200) {
		TRACEF(("s8042_cmd: command not accepted\n"));
		ret = -1;
	}

data:	if(takes_param) {
		IOOUT(kyb_write, bparam); /* If command takes additional  */
					  /* param, send and wait for     */
					  /* additional acknowledgement   */

			/* wait for input buffer to re-empty */
		for(i = 0; (IOIN(kyb_cntir) & KYB_IBF) && i<200; ++i);
		TRACEF(("8042_cmd took %d iterations to ack param receipt\n",i));
		if (i >= 200) {
			TRACEF(("s8042_cmd: parameter not accepted\n"));
			ret = -1;
		}
	}
	TRACEF(("s8042_cmd: returning ...\n")); /* */
	return(ret);
#endif PCKBD
}



int beep_freq = BEEP_FREQ;
int beep_time = BEEP_TIME;

beep()
{
	speaker(beep_freq, beep_time);
}


/*	Following had to be implemented off-keyboard on the AT	*/

speaker(beep_freq, beep_time)
register int beep_freq, beep_time;
{
#ifndef PCKBD
char cl,ch,temp;

	if (!_init_kbd)
		return;		       /* oops */
	if (nobell)
		return;
	/*
	 * Set pointers into PC I/O byte window
	 *
	 * Perhaps should be done in init_kbd() since these are
	 * checked every keystroke when clicks are on
	 */
	if (timer_3 == 0)
		timer_3 = TIMER_3 + pcif_io_b;
	if (timer_2 == 0)
		timer_2 = TIMER_2 + pcif_io_b;
	if (spkr_port == 0)
		spkr_port = SPKR_PORT + pcif_io_b;


/*	Adaptation of BIOS speaker routine. Constants	*/
/*	have been adjusted to emulate RTPC speaker	*/
/*	routine for BELL and KEY_CLICK			*/
/*	The following describes AT BIOS routine		*/
/*							*/
/*	Send 0xb6 to 0x43	                    	*/
/*	Get divisor for 896 hz tone = 0x533		*/
/*	Send low byte of above to 0x42			*/
/*	...then send high byte to 0x42			*/
/*	Get contents of 0x61: Put in 'temp'		*/
/*	Send 0x03 to 0x61				*/
/*	Delay while tone sounds				*/
/*	Send contents of 'temp' back to 0x61		*/
/*	See: BEEP - page 5-69, AT Tech Ref		*/


	ch = (char) (beep_freq >> 8);
	cl = (char) (beep_freq & 0xff);
	IOOUT(timer_3,SPKR_CONS1);	/* Write Timer Mode Register */
	IOOUT(timer_2,cl);		/* Write Timer 2 Count LSB */
	IOOUT(timer_2,ch);		/* Write Timer 2 Count MSB */
	temp = IOIN(spkr_port);		/* Save Current Setting of Port */
	IOOUT(spkr_port,SPKR_CONS2);	/* Turn Speaker On	*/
	delay(beep_time);		/* Delay a while	*/
	IOOUT(spkr_port,temp);		/* Turn off Speaker - restore port */
#endif PCKBD
}


static
while_kbd_disabled()
{
#ifndef PCKBD
/*	Continue to read keyboard controller status register		*/
/*	until the Keyboard lock switch has been turned off.		*/
/*	Keylock is 'turned off' when controller 			*/
/*	status bit 4 = 1.						*/
/*	Bit is ONLY updated when data is put in keyboard output		*/
/*	buffer. We will 'force' this by reading the keyboard 		*/
/*	controller's command byte.					*/


	do{
		/* Immediately disable keyboard */
		kbd_cmd(CMD(KBC_DEFAULT_DISABLE,0));
		delay(100);
	 	s8042_cmd(CMD(CC_READ,0)); 
		while((IOIN(kyb_cntir) & KYB_OBF) != 1); 
		IOIN(kyb_read);
	} while ((IOIN(kyb_cntir) & KYB_INHIBIT) == 0);

	kbd_cmd(CMD(KBC_ENABLE,0));
	printf("\nKeyboard unlocked and ready!\n");
#endif PCKBD
}


static cnhangup()
{
	beep();
}
#endif ATR
