#include <linux/keyboard.h>
#include <linux/pc_keyb.h>

unsigned char shfts, ctls, alts, caps;

extern unsigned char inb(int port);
extern void outb(int port, char val);
extern void puts(const char *);
extern void puthex(unsigned long val);
extern void udelay(long x);

extern u_short plain_map[];
extern u_short shift_map[];
extern u_short ctrl_map[];

static int kbd(int noblock)
{
	unsigned char dt, brk, val;
	unsigned code;
loop:
	if (noblock) {
 	    if ((inb(KBD_STATUS_REG) & KBD_STAT_OBF) == 0)
		return (-1);
 	} else while((inb(KBD_STATUS_REG) & KBD_STAT_OBF) == 0) ;

 	dt = inb(KBD_DATA_REG);

	brk = dt & 0x80;	/* brk == 1 on key release */
	dt = dt & 0x7f;		/* keycode */

	if (shfts)
	    code = shift_map[dt];
	else if (ctls)
	    code = ctrl_map[dt];
	else
	    code = plain_map[dt];

	val = KVAL(code);
	switch (KTYP(code) & 0x0f) {
	    case KT_LATIN:
		if (brk)
		    break;
		if (alts)
		    val |= 0x80;
		if (val == 0x7f)	/* map delete to backspace */
		    val = '\b';
		return val;

	    case KT_LETTER:
		if (brk)
		    break;
		if (caps)
		    val -= 'a'-'A';
		return val;

	    case KT_SPEC:
		if (brk)
		    break;
		if (val == KVAL(K_CAPS))
		    caps = !caps;
		else if (val == KVAL(K_ENTER)) {
enter:		    /* Wait for key up */
		    while (1) {
			while((inb(KBD_STATUS_REG) & KBD_STAT_OBF) == 0) ;
			dt = inb(KBD_DATA_REG);
			if (dt & 0x80) /* key up */ break;
		    }
		    return 10;
		}
		break;

	    case KT_PAD:
		if (brk)
		    break;
		if (val < 10)
		    return val;
		if (val == KVAL(K_PENTER))
		    goto enter;
		break;

	    case KT_SHIFT:
		switch (val) {
		    case KG_SHIFT:
		    case KG_SHIFTL:
		    case KG_SHIFTR:
			shfts = brk ? 0 : 1;
			break;
		    case KG_ALT:
		    case KG_ALTGR:
			alts = brk ? 0 : 1;
			break;
		    case KG_CTRL:
		    case KG_CTRLL:
		    case KG_CTRLR:
			ctls = brk ? 0 : 1;
			break;
		}
		break;

	    case KT_LOCK:
		switch (val) {
		    case KG_SHIFT:
		    case KG_SHIFTL:
		    case KG_SHIFTR:
			if (brk)
			    shfts = !shfts;
			break;
		    case KG_ALT:
		    case KG_ALTGR:
			if (brk)
			    alts = !alts;
			break;
		    case KG_CTRL:
		    case KG_CTRLL:
		    case KG_CTRLR:
			if (brk)
			    ctls = !ctls;
			break;
		}
		break;
	}
	if (brk) return (-1);  /* Ignore initial 'key up' codes */
	goto loop;
}

#define kbd_read_input() inb(KBD_DATA_REG)
#define kbd_read_status() inb(KBD_STATUS_REG)
#define kbd_write_output(val) outb(KBD_DATA_REG, val)
#define kbd_write_command(val) outb(KBD_CNTL_REG, val)

static unsigned char handle_kbd_event(void)
{
	unsigned char status = kbd_read_status();
	unsigned int work = 10000;

	while ((--work > 0) && (status & KBD_STAT_OBF)) {
		unsigned char scancode;

		scancode = kbd_read_input();
		if (!(status & (KBD_STAT_GTO | KBD_STAT_PERR))) {
			if (status & KBD_STAT_MOUSE_OBF)
				_printk("(mse)"); /* XXX err.. */
			else
				_printk("(kbd)"); /* XXX ditto */
		}
		
		status = kbd_read_status();
	}

	if (!work)
		_printk("keyboard controller jammed (0x%02X)\n", status);

	return status;
}

static void kbd_wait(void)
{
	unsigned long timeout = 1000;

	do {
		unsigned char status = handle_kbd_event();

		if (! (status & KBD_STAT_IBF))
			return;
		udelay(1000);
		timeout--;
	} while (timeout);

	_printk("Keyboard timed out\n");
}

#define KBD_NO_DATA	(-1)
#define KBD_BAD_DATA	(-2)

static int kbd_read_data(void)
{
	int retval = KBD_NO_DATA;
	unsigned char status;

	status = kbd_read_status();
	if (status & KBD_STAT_OBF) {
		unsigned char data = kbd_read_input();

		retval = data;
		if (status & (KBD_STAT_GTO | KBD_STAT_PERR))
			retval = KBD_BAD_DATA;
	}

	return retval;
}

static int kbd_wait_for_input(void)
{
	unsigned long timeout = 1000;

	do {
		int retval = kbd_read_data();
		if (retval >= 0)
			return retval;
		udelay(1000);
	} while (--timeout);

	return -1;
}

static void kbd_write_command_w(int data)
{
	kbd_wait();
	kbd_write_command(data);
}

static int kbd_write_command_w_and_wait(int data)
{
	kbd_write_command_w(data);
	return kbd_wait_for_input();
}

static void kbd_write_output_w(int data)
{
	kbd_wait();
	kbd_write_output(data);
}

static int kbd_write_output_w_and_wait(int data)
{
	kbd_write_output_w(data);
	return kbd_wait_for_input();
}

static void kbd_clear_input(void)
{
	int maxread = 100;	/* Random number */

	do {
		if (kbd_read_data() == KBD_NO_DATA)
			break;
	} while (--maxread);
}

static int __kbdreset(void)
{
	int status;

	/* flush input queue */
	kbd_clear_input();

	/* Send self-test */
	kbd_write_command_w(KBD_CCMD_SELF_TEST);
	if (kbd_wait_for_input() != 0x55) {
		_printk("Keyboard self test failed\n");
		return 1;
	}

	/* Perform a keyboard interface test */
	kbd_write_command_w(KBD_CCMD_KBD_TEST);
	if (kbd_wait_for_input() != 0x00) {
		_printk("Keyboard interface failed self test\n");
		return 2;
	}

	/* Enable the keyboard */
	kbd_write_command_w(KBD_CCMD_KBD_ENABLE);

	/* Reset the keyboard */
	do {
		kbd_write_output_w(KBD_CMD_RESET);
		status = kbd_wait_for_input();
		if (status == KBD_REPLY_ACK)
			break;
		if (status != KBD_REPLY_RESEND) {
			_printk("Keyboard reset failed, no ACK\n");
			return 3;
		}
	} while (1);

	if (kbd_wait_for_input() != KBD_REPLY_POR) {
		_printk("Keyboard reset failed, no POR\n");
		return 4;
	}

	/* Disable the keyboard */
	do {
		kbd_write_output_w(KBD_CMD_DISABLE);
		status = kbd_wait_for_input();
		if (status == KBD_REPLY_ACK)
			break;
		if (status != KBD_REPLY_RESEND) {
			_printk("Disable keyboard failed, no ACK\n");
			return 5;
		}
	} while (1);

	kbd_write_command_w(KBD_CCMD_WRITE_MODE);
	kbd_write_output_w(   KBD_MODE_SYS /* | KBD_MODE_KBD_INT */
			    | KBD_MODE_DISABLE_MOUSE
			    | KBD_MODE_KCC );

	if (!(kbd_write_command_w_and_wait(KBD_CCMD_READ_MODE) & KBD_MODE_KCC)) {
		kbd_write_output_w(0xF0);
		kbd_wait_for_input();
		kbd_write_output_w(0x01);
		kbd_wait_for_input();
	}

	if (kbd_write_output_w_and_wait(KBD_CMD_ENABLE) != KBD_REPLY_ACK) {
		_printk("Enable keyboard failed, no ACK\n");
		return 6;
	}

	if (kbd_write_output_w_and_wait(KBD_CMD_SET_RATE) != KBD_REPLY_ACK) {
		_printk("Set keyboard rate failed, no ACK\n");
		return 7;
	}
	if (kbd_write_output_w_and_wait(0x00) != KBD_REPLY_ACK) {
		_printk("Set keyboard rate failed, no 2nd ACK\n");
		return 8;
	}

	return 0;
}

static void kbdreset(void)
{
	int ret = __kbdreset();

	if (ret) {
		puts("__kbdreset failed: ");
		puthex(ret);
		puts("\n");
	}
}

/* We have to actually read the keyboard when CRT_tstc is called,
 * since the pending data might be a key release code, and therefore
 * not valid data.  In this case, kbd() will return -1, even though there's
 * data to be read.  Of course, we might actually read a valid key press,
 * in which case it gets queued into key_pending for use by CRT_getc.
 */

static int kbd_reset = 0;

static int key_pending = -1;

int CRT_getc(void)
{
	int c;
	if (!kbd_reset) {kbdreset(); kbd_reset++; }

        if (key_pending != -1) {
                c = key_pending;
                key_pending = -1;
                return c;
        } else {
	while ((c = kbd(0)) == 0) ;
                return c;
        }
}

int CRT_tstc(void)
{
	if (!kbd_reset) {kbdreset(); kbd_reset++; }

        while (key_pending == -1 && ((inb(KBD_STATUS_REG) & KBD_STAT_OBF) != 0)) {
                key_pending = kbd(1);
        }

        return (key_pending != -1);
}
