/*	$NetBSD: lk201.c,v 1.21 2003/07/15 02:54:39 lukem Exp $	*/

/*
 * The LK201 keycode mapping routine is here, along with initialization
 * functions for the keyboard and mouse.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: lk201.c,v 1.21 2003/07/15 02:54:39 lukem Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/syslog.h>

#include <dev/dec/lk201.h>

#include <pmax/dev/lk201var.h>

/*
 * Keyboard to ASCII, unshifted.
 */
static u_char unshiftedAscii[] = {
/*  0 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/*  4 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/*  8 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/*  c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 10 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 14 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 18 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 1c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 20 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 24 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 28 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 2c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 30 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 34 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 38 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 3c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 40 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 44 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 48 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 4c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 50 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 54 */ KBD_NOKEY,	KBD_NOKEY,	KBD_F1,		KBD_F2,
/* 58 */ KBD_F3,	KBD_F4,		KBD_F5,		KBD_NOKEY,
/* 5c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 60 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 64 */ KBD_F6,	KBD_F7,		KBD_F8,		KBD_F9,
/* 68 */ KBD_F10,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 6c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 70 */ KBD_NOKEY,	'\033',		KBD_F12,	KBD_F13,
/* 74 */ KBD_F14,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 78 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 7c */ KBD_HELP,	KBD_DO,		KBD_NOKEY,	KBD_NOKEY,
/* 80 */ KBD_F17,	KBD_F18,	KBD_F19,	KBD_F20,
/* 84 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 88 */ KBD_NOKEY,	KBD_NOKEY,	KBD_FIND,	KBD_INSERT,
/* 8c */ KBD_REMOVE,	KBD_SELECT,	KBD_PREVIOUS,	KBD_NEXT,
/* 90 */ KBD_NOKEY,	KBD_NOKEY,	'0',		KBD_NOKEY,
/* 94 */ '.',		KBD_KP_ENTER,	'1',		'2',
/* 98 */ '3',		'4',		'5',		'6',
/* 9c */ ',',		'7',		'8',		'9',
/* a0 */ '-',		KBD_KP_F1,	KBD_KP_F2,	KBD_KP_F3,
/* a4 */ KBD_KP_F4,	KBD_NOKEY,	KBD_NOKEY,	KBD_LEFT,
/* a8 */ KBD_RIGHT,	KBD_DOWN, 	KBD_UP,		KBD_NOKEY,
/* ac */ KBD_NOKEY,	KBD_NOKEY,	KBD_SHIFT,	KBD_CONTROL,
/* b0 */ KBD_CAPSLOCK,	KBD_ALTERNATE,	KBD_NOKEY,	KBD_NOKEY,
/* b4 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* b8 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* bc */ KBD_DEL,	KBD_RET,	KBD_TAB,	'`',
/* c0 */ '1',		'q',		'a',		'z',
/* c4 */ KBD_NOKEY,	'2',		'w',		's',
/* c8 */ 'x',		'<',		KBD_NOKEY,	'3',
/* cc */ 'e',		'd',		'c',		KBD_NOKEY,
/* d0 */ '4',		'r',		'f',		'v',
/* d4 */ ' ',		KBD_NOKEY,	'5',		't',
/* d8 */ 'g',		'b',		KBD_NOKEY,	'6',
/* dc */ 'y',		'h',		'n',		KBD_NOKEY,
/* e0 */ '7',		'u',		'j',		'm',
/* e4 */ KBD_NOKEY,	'8',		'i',		'k',
/* e8 */ ',',		KBD_NOKEY,	'9',		'o',
/* ec */ 'l',		'.',		KBD_NOKEY,	'0',
/* f0 */ 'p',		KBD_NOKEY,	';',		'/',
/* f4 */ KBD_NOKEY,	'=',		']',		'\\',
/* f8 */ KBD_NOKEY,	'-',		'[',		'\'',
/* fc */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
};

/*
 * Keyboard to Ascii, shifted.
 */
static u_char shiftedAscii[] = {
/*  0 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/*  4 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/*  8 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/*  c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 10 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 14 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 18 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 1c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 20 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 24 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 28 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 2c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 30 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 34 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 38 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 3c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 40 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 44 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 48 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 4c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 50 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 54 */ KBD_NOKEY,	KBD_NOKEY,	KBD_F1,		KBD_F2,
/* 58 */ KBD_F3,	KBD_F4,		KBD_F5,		KBD_NOKEY,
/* 5c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 60 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 64 */ KBD_F6,	KBD_F7,		KBD_F8,		KBD_F9,
/* 68 */ KBD_F10,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 6c */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 70 */ KBD_NOKEY,	KBD_F11,	KBD_F12,	KBD_F13,
/* 74 */ KBD_F14,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 78 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 7c */ KBD_HELP,	KBD_DO,		KBD_NOKEY,	KBD_NOKEY,
/* 80 */ KBD_F17,	KBD_F18,	KBD_F19,	KBD_F20,
/* 84 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* 88 */ KBD_NOKEY,	KBD_NOKEY,	KBD_FIND,	KBD_INSERT,
/* 8c */ KBD_REMOVE,	KBD_SELECT,	KBD_PREVIOUS,	KBD_NEXT,
/* 90 */ KBD_NOKEY,	KBD_NOKEY,	'0',		KBD_NOKEY,
/* 94 */ '.',		KBD_KP_ENTER,	'1',		'2',
/* 98 */ '3',		'4',		'5',		'6',
/* 9c */ ',',		'7',		'8',		'9',
/* a0 */ '-',		KBD_KP_F1,	KBD_KP_F2,	KBD_KP_F3,
/* a4 */ KBD_KP_F4,	KBD_NOKEY,	KBD_NOKEY,	KBD_LEFT,
/* a8 */ KBD_RIGHT,	KBD_DOWN, 	KBD_UP,		KBD_NOKEY,
/* ac */ KBD_NOKEY,	KBD_NOKEY,	KBD_SHIFT,	KBD_CONTROL,
/* b0 */ KBD_CAPSLOCK,	KBD_ALTERNATE,	KBD_NOKEY,	KBD_NOKEY,
/* b4 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* b8 */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
/* bc */ KBD_DEL,	KBD_RET,	KBD_TAB,	'~',
/* c0 */ '!',		'q',		'a',		'z',
/* c4 */ KBD_NOKEY,	'@',		'w',		's',
/* c8 */ 'x',		'>',		KBD_NOKEY,	'#',
/* cc */ 'e',		'd',		'c',		KBD_NOKEY,
/* d0 */ '$',		'r',		'f',		'v',
/* d4 */ ' ',		KBD_NOKEY,	'%',		't',
/* d8 */ 'g',		'b',		KBD_NOKEY,	'^',
/* dc */ 'y',		'h',		'n',		KBD_NOKEY,
/* e0 */ '&',		'u',		'j',		'm',
/* e4 */ KBD_NOKEY,	'*',		'i',		'k',
/* e8 */ '<',		KBD_NOKEY,	'(',		'o',
/* ec */ 'l',		'>',		KBD_NOKEY,	')',
/* f0 */ 'p',		KBD_NOKEY,	':',		'?',
/* f4 */ KBD_NOKEY,	'+',		'}',		'|',
/* f8 */ KBD_NOKEY,	'_',		'{',		'"',
/* fc */ KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,	KBD_NOKEY,
};

/*
 * Keyboard initialization string.
 */
static u_char lk_initstr[] = {
	LK_LED_ENABLE, LED_ALL,		/* show we are resetting keyboard */
	LK_DEFAULTS,
	LK_CMD_MODE(LK_AUTODOWN, 1),
	LK_CMD_MODE(LK_AUTODOWN, 2),
	LK_CMD_MODE(LK_AUTODOWN, 3),
	LK_CMD_MODE(LK_DOWN, 4),	/* could also be LK_AUTODOWN */
	LK_CMD_MODE(LK_UPDOWN, 5),
	LK_CMD_MODE(LK_UPDOWN, 6),
	LK_CMD_MODE(LK_AUTODOWN, 7),
	LK_CMD_MODE(LK_AUTODOWN, 8),
	LK_CMD_MODE(LK_AUTODOWN, 9),
	LK_CMD_MODE(LK_AUTODOWN, 10),
	LK_CMD_MODE(LK_AUTODOWN, 11),
	LK_CMD_MODE(LK_AUTODOWN, 12),
	LK_CMD_MODE(LK_DOWN, 13),
	LK_CMD_MODE(LK_AUTODOWN, 14),
	LK_AR_ENABLE,			/* we want autorepeat by default */
#ifdef LK_KEY_CLICK
	LK_CL_ENABLE, 0x83,		/* keyclick, volume */
#endif
	LK_KBD_ENABLE,			/* the keyboard itself */
	LK_BELL_ENABLE, 0x83,		/* keyboard bell, volume */
	LK_LED_DISABLE, LED_ALL,	/* clear keyboard leds */
};

/*
 * Keyboard to what the rcons termcap entry expects for long codes.
 * XXX function keys are handled specially.
 */
struct {
	int	ts_keycode;
	int	ts_len;
	char	*ts_string;
} static lk_keytostr[] = {			/* termcap name */
	{ KBD_UP,	3, "\033[A" },		/* ku */
	{ KBD_DOWN,	3, "\033[B" },		/* kd */
	{ KBD_RIGHT,	3, "\033[C" },		/* kr */
	{ KBD_LEFT,	3, "\033[D" },		/* kl */
	{ KBD_REMOVE,	1, "\177" },		/* kD */
	{ KBD_NEXT,	6, "\033[222z" },	/* kN */
	{ KBD_PREVIOUS,	6, "\033[216z" },	/* kP */
};

#define NUM_KEYTOSTR (sizeof(lk_keytostr) / sizeof(lk_keytostr[0]))

static void	(*raw_kbd_putc) __P((dev_t dev, int c)) = NULL;
static int	(*raw_kbd_getc) __P((dev_t dev)) = NULL;
static dev_t	lk_out_dev = NODEV;
static dev_t	lk_in_dev = NODEV;

/*
 * Initialize the keyboard.
 */
void
lk_reset(kbddev, putc)
	dev_t kbddev;
	void (*putc) __P((dev_t, int));
{
	static int inlk_reset;
	int i;

	if (inlk_reset)
		return;
	inlk_reset = 1;

	for (i = 0; i < sizeof(lk_initstr); i++) {
		(*putc)(kbddev, (int)lk_initstr[i]);
		DELAY(20000);
	}

	inlk_reset = 0;
	raw_kbd_putc = putc;
	lk_out_dev = kbddev;
}

/*
 * Sound the keyboard bell.
 */
void
lk_bell(ring)
	int ring;
{

	if ((!ring) || (lk_out_dev == NODEV) || (raw_kbd_putc == NULL))
		return;
	(*raw_kbd_putc)(lk_out_dev, LK_RING_BELL);
	DELAY(20000);
}

/*
 * Map characters from the keyboard to ASCII. Return NULL if there is
 * no valid mapping. Return length of mapped ASCII in *len - returned
 * string is not NUL terminated, as NUL is a valid code.
 */
char *
lk_mapchar(cc, len)
	int cc, *len;
{
	static u_char shiftDown, ctrlDown, capsLock;
	static char buf[8], *lastStr;
	static int lastLen;
	char *cp;
	int i;
	
	cp = NULL;

	switch (cc) {
	case KEY_REPEAT:
		*len = lastLen;
		return (lastStr);

	case KEY_UP:
		shiftDown = 0;
		ctrlDown = 0;
		*len = 0;
		return (NULL);

	case KEY_CAPSLOCK:
		capsLock ^= 1;
#if 0
		/* XXX causes a lockup - why? */
		if (capsLock)
			(*raw_kbd_putc)(lk_out_dev, LK_LED_ENABLE);
		else
			(*raw_kbd_putc)(lk_out_dev, LK_LED_DISABLE);

		(*raw_kbd_putc)(lk_out_dev, LED_1);
#endif
		*len = 0;
		return (NULL);

	case KEY_SHIFT:
	case KEY_R_SHIFT:
		shiftDown ^= 1;
		*len = 0;
		return (NULL);

	case KEY_CONTROL:
		ctrlDown ^= 1;
		*len = 0;
		return (NULL);

	case LK_POWER_ERROR:
	case LK_KDOWN_ERROR:
	case LK_INPUT_ERROR:
	case LK_OUTPUT_ERROR:
		log(LOG_WARNING, "lk201: keyboard error, code=%x\n", cc);
		*len = 0;
		return (NULL);
	}

	cc = (shiftDown ? shiftedAscii[cc] : unshiftedAscii[cc]);

	/* Map keypad 'Enter' key to return */
	if (cc == KBD_KP_ENTER)
		cc = KBD_RET;
	else if (cc >= KBD_NOKEY) {
		/* Check for keys that have multi-character codes */
		for (i = 0; i < NUM_KEYTOSTR; i++)
			if (lk_keytostr[i].ts_keycode == cc) {
				cp = lk_keytostr[i].ts_string;
				*len = lk_keytostr[i].ts_len;
				break;
			}

		/* Handle function keys specially */
		if (cp == NULL) {
			if (cc < KBD_F1 || cc > KBD_F20)
				return NULL;

			/*
			 * All the function keys (KBD_*) are contigious,
			 * except for the 'Help' and 'Do' keys, which we
			 * return as F15 and F16 since that's what they
			 * really are.
			 *
			 * XXX termcap can only handle F0->F9?
			 * XXX 'Do' is used for dropping into ddb on pmax.
			 */
			if (cc >= KBD_F1 && cc <= KBD_F6) {
				buf[3] = '2';
				buf[4] = '4' + (cc - KBD_F1);
			} else if (cc >= KBD_F7 && cc <= KBD_DO) {
				buf[3] = '3';
				buf[4] = '0' + (cc - KBD_F7);
			} else /* if (cc >= KBD_F17 && cc <= KBD_F20) */ {
				buf[3] = '4';
				buf[4] = '0' + (cc - KBD_F17);
			}

			buf[0] = '\033';
			buf[1] = '[';
			buf[2] = '2';
			cp = buf;
			*len = 5;
		}
	} else if (cc >= 'a' && cc <= 'z') {
		if (ctrlDown)
			cc = cc - 'a' + '\1'; /* ^A */
		else if (shiftDown ^ capsLock)
			cc = cc - 'a' + 'A';
	} else if (ctrlDown) {
		if (cc >= '[' && cc <= '_')
			cc = cc - '@';
		else if (cc == ' ' || cc == '@')
			cc = '\0';
	}

	if (cp == NULL) {
		buf[0] = cc;
		cp = buf;
		*len = 1;
	}

	lastStr = cp;
	lastLen = *len;
	return (cp);
}

/*
 * Divert input from a serial port to the lk-201 keyboard handler.
 */
void
lk_divert(getfn, in_dev)
	int (*getfn) __P((dev_t dev));
	dev_t in_dev;
{

	raw_kbd_getc = getfn;
	lk_in_dev = in_dev;
}

/*
 * Get an ASCII character off of the keyboard; simply pass the getc request 
 * onto the underlying serial driver, and map the resulting LK-201 keycode to 
 * ASCII.
 */
int
lk_getc(dev)
	dev_t dev;	/* ignored */
{
	static char *cp;
	static int len;
	int c;

#ifdef DIAGNOSTIC
	if (raw_kbd_getc == NULL) {
		panic("Reading from LK-201 before kbd driver diverted");
		return (-1);
	}
#endif
	for (;;) {
		if (len != 0) {
			c = *(u_char *)cp++;
			len--;
			break;
		}

		/* c = (*cn_tab.cn_kbdgetc)(cn_tab.cn_dev); */
		c = (*raw_kbd_getc) (lk_in_dev);

		if (c == 0)
			return (-1);

		cp = lk_mapchar(c & 0xff, &len);
	}

	return (c);
}

/*
 * Initialize the mouse.  (Doesn't really belong here.)
 */
void
lk_mouseinit(mdev, putc, getc)
	dev_t mdev;
	void (*putc) __P((dev_t, int));
	int (*getc) __P((dev_t));
{
	int id_byte1, id_byte2, id_byte3, id_byte4;

	/*
	 * Initialize the mouse.
	 */
	(*putc)(mdev, MOUSE_SELF_TEST);
	id_byte1 = (*getc)(mdev);
	if (id_byte1 == MOUSE_SELF_TEST) {
		printf("lk_mouseinit: mouse loopback connector.\n");
		return;
	}
	if (id_byte1 < 0) {
		printf("lk_mouseinit: Timeout on %s byte of self-test report\n", 
		    "1st");
		return;
	}
	id_byte2 = (*getc)(mdev);
	if (id_byte2 < 0) {
		printf("lk_mouseinit: Timeout on %s byte of self-test report\n", 
		    "2nd");
		return;
	}
	id_byte3 = (*getc)(mdev);
	if (id_byte3 < 0) {
		printf("lk_mouseinit: Timeout on %s byte of self-test report\n", 
		    "3rd");
		return;
	}
	id_byte4 = (*getc)(mdev);
	if (id_byte4 < 0) {
		printf("lk_mouseinit: Timeout on %s byte of self-test report\n", 
		    "4th");
		return;
	}
	if ((id_byte2 & 0x0f) != 0x2)
		printf("lk_mouseinit: We don't have a mouse!!!\n");
	/*
	 * For some reason, the mouse doesn't see this command if it comes
	 * too soon after a self test.
	 */
	DELAY(150);
	(*putc)(mdev, MOUSE_INCREMENTAL);
}
