/*
 * Stowaway (TM) Portable Keyboard for the Compaq iPAQ 
 * Line discipline for Linux
 * 
 * Copyright 2001 Compaq Computer Corporation.
 * Stowaway is a trademark of Think Outside, Inc. (http://www.thinkoutside.com)
 *
 * Use consistent with the GNU GPL is permitted,
 * provided that this copyright notice is
 * preserved in its entirety in all copies and derived works.
 *
 * COMPAQ COMPUTER CORPORATION MAKES NO WARRANTIES, EXPRESSED OR IMPLIED,
 * AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS
 * FITNESS FOR ANY PARTICULAR PURPOSE.
 *
 * Author: Andrew Christian 
 *         <andyc@handhelds.org>
 *         5 April 2001
 * 
 * Based in part on the Stowaway PDA keyboard driver for Linux
 * by Justin Cormack <j.cormack@doc.ic.ac.uk>
 *
 *
 *  General notes:
 *
 *  The "Fn" Function key is trapped internally and switches between keycode tables.  It acts as
 *  a virtual "shift" key for keycodes.  The skbd_keycode table shows keycode mappings for 
 *  the stowaway's keys in normal, function, and numlock mode.
 *
 *  The NUMLOCK state is based on the callback from keyboard.c: we use this internally
 *  to handle switching between the numeric keypad functions and the regular keys.
 *
 *
 *  Known issues/todos:
 *
 *   1. Holding down a key and unplugging the keyboard will cause an endless repeat until
 *      you re-insert the keyboard and press another key.  We should be able to cancel the
 *      repeat as soon as the keyboard is re-inserted.
 * 
 *   2. This code should be rolled into the drivers/input/ tree of Linux code.  It's not there now
 *      because (a) the input code assumes a PC keyboard, which we don't have (forcing us to
 *      jump through hoops to generate the right scancodes), (b) the input code does not
 *      have provisions for the handshaking logic required by the Stowaway keyboard, and (c) the
 *      input code doesn't seem well suited for future "hot docking" of serial devices.
 * 
 *   3. Allow ioctl calls to change the keycode mapping tables.
 *  
 *   4. Provide for a "probing" functionality so the user-space program could check to see
 *      if a keyboard is actually present.
 *   
 *   5. Allow the user to modify the timeout values.
 *
 *
 *  Targus Stowaway Logic
 *
 *      The keyboard receives power from the DTR line on the H3600 cable.  The DTR line is 
 *      held high as long as the EGPIO_BITSY_RS232_ON extended GPIO line has power.  If we ever
 *      lower this line (say, in a low power mode), the keyboard will be unable to wake the 
 *      unit.
 *
 *      RTS controls the state of the keyboard: low = sleeping, high = awake.  The keyboard
 *      needs a transition on RTS to change states.
 *   
 *      The keyboard sends a 100 millisecond low-high-low transition on DCD under the following
 *      conditions:  (a) it is first powered up or plugged in, (b) it is asleep and someone
 *      presses a key on the keyboard.
 *
 *      Upon receiving a low-to-high transition on RTS, the first two characters sent by the 
 *      keyboard are 0xFA, 0xFD.  Subsequently, the keyboard sends a single character for each
 *      keypress and keyrelease.  If no keys are depressed, the final keyrelease is repeated
 *      once.  Characters are sent at 9600 8N1.
 *
 */


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/malloc.h>
#include <linux/module.h>
#include <linux/tty_ldisc.h>
#include <linux/kbd_ll.h>
#include <linux/init.h>
#include <linux/kbd_kern.h>
#include <asm/bitops.h>
#include <asm/irq.h>
#include <asm/arch/hardware.h>

MODULE_AUTHOR("Andrew Christian <andyc@handhelds.org>");

/* These keycodes are the defaults used by the pc keyboard */
#define SKEY_ESC		1		
#define SKEY_1			2		
#define SKEY_2			3		
#define SKEY_3			4		
#define SKEY_4			5		
#define SKEY_5			6		
#define SKEY_6			7		
#define SKEY_7			8		
#define SKEY_8			9		
#define SKEY_9			10		
#define SKEY_0			11		
#define SKEY_MINUS		12		
#define SKEY_EQUAL		13		
#define SKEY_BACKSPACE		14		/* Technically delete */
#define SKEY_TAB		15		
#define SKEY_Q			16		
#define SKEY_W			17		
#define SKEY_E			18		
#define SKEY_R			19		
#define SKEY_T			20		
#define SKEY_Y			21		
#define SKEY_U			22		
#define SKEY_I			23		
#define SKEY_O			24		
#define SKEY_P			25		
#define SKEY_LEFTBRACE		26		/* Bracket-left */
#define SKEY_RIGHTBRACE		27		/* Bracket-right */
#define SKEY_ENTER		28		/* Return */
#define SKEY_LEFTCTRL		29		/* Control */
#define SKEY_A			30		
#define SKEY_S			31		
#define SKEY_D			32		
#define SKEY_F			33		
#define SKEY_G			34		
#define SKEY_H			35		
#define SKEY_J			36		
#define SKEY_K			37		
#define SKEY_L			38		
#define SKEY_SEMICOLON		39		
#define SKEY_APOSTROPHE		40		
#define SKEY_GRAVE		41		
#define SKEY_LEFTSHIFT		42		/* Shift */
#define SKEY_BACKSLASH		43		
#define SKEY_Z			44		
#define SKEY_X			45		
#define SKEY_C			46		
#define SKEY_V			47		
#define SKEY_B			48		
#define SKEY_N			49		
#define SKEY_M			50		
#define SKEY_COMMA		51		
#define SKEY_DOT		52		/* period */
#define SKEY_SLASH		53		
#define SKEY_RIGHTSHIFT		54		/* Shift */
#define SKEY_KPASTERISK		55		/* KP_Multiply */
#define SKEY_LEFTALT		56		/* Alt */
#define SKEY_SPACE		57		
#define SKEY_CAPSLOCK		58		
#define SKEY_F1			59		
#define SKEY_F2			60		
#define SKEY_F3			61		
#define SKEY_F4			62		
#define SKEY_F5			63		
#define SKEY_F6			64		
#define SKEY_F7			65		
#define SKEY_F8			66		
#define SKEY_F9			67		
#define SKEY_F10		68		
#define SKEY_NUMLOCK		69		
#define SKEY_SCROLLLOCK		70		
#define SKEY_KP7		71		
#define SKEY_KP8		72		
#define SKEY_KP9		73		
#define SKEY_KPMINUS		74		
#define SKEY_KP4		75		
#define SKEY_KP5		76		
#define SKEY_KP6		77		
#define SKEY_KPPLUS		78		/* KP_Add */
#define SKEY_KP1		79		
#define SKEY_KP2		80		
#define SKEY_KP3		81		
#define SKEY_KP0		82		
#define SKEY_KPDOT		83		/* KP_Period */
#define SKEY_LASTCONSOLE	84      /* Last Console -- 103rd */
#define SKEY_UNASSIGNED_1	85
#define SKEY_102ND		86	/* < > */	
#define SKEY_F11		87		
#define SKEY_F12		88		
#define SKEY_UNASSIGNED_2	89
#define SKEY_UNASSIGNED_3	90	/* -- unassigned -- */			
#define SKEY_UNASSIGNED_4	91	/* -- unassigned -- */			
#define SKEY_UNASSIGNED_5	92	/* -- unassigned -- */			
#define SKEY_UNASSIGNED_6	93	/* -- unassigned -- */			
#define SKEY_UNASSIGNED_7	94	/* -- unassigned -- */			
#define SKEY_UNASSIGNED_8	95	/* -- unassigned -- */			
#define SKEY_KPENTER		96		
#define SKEY_RIGHTCTRL		97		/* Control */
#define SKEY_KPSLASH		98		/* KP_Divide */
#define SKEY_SYSRQ		99	/* Control_backslash */
#define SKEY_RIGHTALT		100		/* AltGr */
#define SKEY_LINEFEED		101     /* Break */
#define SKEY_HOME		102	/* Find  */
#define SKEY_UP			103	
#define SKEY_PAGEUP		104	/* Prior */	
#define SKEY_LEFT		105		
#define SKEY_RIGHT		106		
#define SKEY_END		107	/* Select */
#define SKEY_DOWN		108	
#define SKEY_PAGEDOWN		109	/* Next */	
#define SKEY_INSERT		110		
#define SKEY_DELETE		111             /* Remove */
#define SKEY_MACRO		112		
#define SKEY_MUTE		113	/* F13 */
#define SKEY_VOLUMEDOWN		114	/* F14 */
#define SKEY_VOLUMEUP		115	/* Help */	
#define SKEY_POWER		116	/* Do   */	
#define SKEY_KPEQUAL		117	/* F17  */
#define SKEY_KPPLUSMINUS	118     /* KP_MinPlus */
#define SKEY_PAUSE		119		
#define SKEY_F21		120    	/* -- unassigned -- */	
#define SKEY_F22		121	/* -- unassigned -- */	
#define SKEY_F23		122	/* -- unassigned -- */			
#define SKEY_F24		123	/* -- unassigned -- */			
#define SKEY_KPCOMMA		124	/* -- unassigned -- */	
#define SKEY_LEFTMETA		125	/* -- unassigned -- */			
#define SKEY_RIGHTMETA		126	/* -- unassigned -- */			
#define SKEY_COMPOSE		127	/* -- unassigned -- */			

#define SKEY_FUNCTION           128     /* The function key does not generate a scancode */

/*      ---------------- Keycode --------------                 --- Text on keyboard --- */
/*      Normal          Function        NumLock              #  White     Blue    Yellow */
static unsigned char skbd_keycode[] = {
	SKEY_1,         SKEY_F1,        0,               /*  0  1 !                      */
	SKEY_2,         SKEY_F2,        0,	         /*  1  2 @                      */
	SKEY_3,         SKEY_F3,        0,	         /*  2  3 #                      */
	SKEY_Z,         0,              0,	         /*  3  z                        */
	SKEY_4,         SKEY_F4,        0,	         /*  4  4 $                      */
	SKEY_5,         SKEY_F5,        0,	         /*  5  5 %                      */
	SKEY_6,         SKEY_F6,        0,	         /*  6  6 ^                      */
	SKEY_7,         SKEY_F7,        0,               /*  7  7 &                7     */
	SKEY_FUNCTION,  0,              0,	         /*  8            Fn             */
	SKEY_Q,         0,              0,	         /*  9  q                        */
	SKEY_W,         0,              0,	         /* 10  w                        */
	SKEY_E,         0,              0,	         /* 11  e                        */
	SKEY_R,         0,              0,	         /* 12  r                        */
	SKEY_T,         0,              0,	         /* 13  t                        */
	SKEY_Y,         0,              0,	         /* 14  y                        */
	SKEY_GRAVE,     0,              0,               /* 15  ` ~                      */
	SKEY_X,         0,              0,	         /* 16  x                        */
	SKEY_A,         0,              0,	         /* 17  a                        */
	SKEY_S,         0,              0,	         /* 18  s                        */
	SKEY_D,         0,              0,	         /* 19  d                        */
	SKEY_F,         0,              0,	         /* 20  f                        */
	SKEY_G,         0,              0,	         /* 21  g                        */
	SKEY_H,         0,              0,	         /* 22  h                        */
	SKEY_SPACE,     0,              0,	         /* 23  spacebar                 */  
	SKEY_CAPSLOCK,  SKEY_NUMLOCK,   0,	         /* 24  CapsLock  NumLock        */
	SKEY_TAB,       0,              0,	         /* 25  Tab                      */
	SKEY_LEFTCTRL,  0,              0,               /* 26  Ctrl(L)                  */
	0, 0, 0,	/* 27 */
	0, 0, 0,	/* 28 */
	0, 0, 0,	/* 29 */
	0, 0, 0,	/* 30 */
	0, 0, 0,        /* 31 */
	0, 0, 0,	/* 32 */
	0, 0, 0,	/* 33 */
	0,               0,             0,	         /* 34  windows_icon             */
	SKEY_LEFTALT,    0,             0,	         /* 35  Alt                      */
	0, 0, 0,	/* 36 */
	0, 0, 0,	/* 37 */
	0, 0, 0,	/* 38 */
	0, 0, 0,        /* 39 */
	0, 0, 0,	/* 40 */
	0, 0, 0,	/* 41 */
	0, 0, 0,	/* 42 */
	0, 0, 0,	/* 43 */
	SKEY_C,          0,             0,	         /* 44  c                        */
	SKEY_V,          0,             0,	         /* 45  v                        */
	SKEY_B,          0,             0,	         /* 46  b                        */
	SKEY_N,          0,             0,               /* 47  n                        */
	SKEY_MINUS,      0,             0,	         /* 48  - _                      */
	SKEY_EQUAL,      0,             0,	         /* 49  = +                      */
	SKEY_BACKSPACE,  0,             0,	         /* 50 Backspace  Off            */
	SKEY_HOME,       0,             0,	         /* 51 Inbox      Notes          */
	SKEY_8,          SKEY_F8,       SKEY_KP8,	 /* 52  8 *                8     */
	SKEY_9,          SKEY_F9,       SKEY_KP9,	 /* 53  9 (                9     */
	SKEY_0,          SKEY_F10,      SKEY_KPSLASH,	 /* 54  0 )                /     */
	SKEY_ESC,        0,             0,               /* 55  Space (not spacebar!)    */
        SKEY_LEFTBRACE,  0,             0,	         /* 56  [ {                      */
	SKEY_RIGHTBRACE, 0,             0,	         /* 57  ] }                      */
	SKEY_BACKSLASH,  0,             0,	         /* 58  \ |                      */
	SKEY_END,        0,             0,	         /* 59  Contacts  Word           */
	SKEY_U,          0,             SKEY_KP4,	 /* 60  u                  4     */
	SKEY_I,          0,             SKEY_KP5,	 /* 61  i                  5     */
	SKEY_O,          0,             SKEY_KP6,	 /* 62  o                  6     */
	SKEY_P,          0,             SKEY_KPASTERISK, /* 63  p                  *     */
	SKEY_APOSTROPHE, 0,             0,	         /* 64  ' "                      */
	SKEY_ENTER,      0,             0,	         /* 65  Enter     OK             */
	SKEY_PAGEUP,     0,             0,	         /* 66  Calendar  Excel          */
	0, 0, 0,	/* 67 */
	SKEY_J,          0,             SKEY_KP1,	 /* 68  j                  1     */
	SKEY_K,          0,             SKEY_KP2,      	 /* 69  k                  2     */
	SKEY_L,          0,             SKEY_KP3,	 /* 70  l                  3     */
	SKEY_SEMICOLON,  0,             SKEY_KPMINUS,    /* 71  ; :                -     */
	SKEY_SLASH,      0,             SKEY_KPPLUS,	 /* 72  / ?                +     */
	SKEY_UP,         SKEY_PAGEUP,   0,	         /* 73  up_arrow  PgUp           */
	SKEY_PAGEDOWN,   0,             0,	         /* 74  Tasks     Money          */
	0, 0, 0,	/* 75 */
	SKEY_M,          0,             SKEY_KP0,	 /* 76  m                  0     */
	SKEY_COMMA,      0,             SKEY_KPCOMMA,	 /* 77  , <                ,     */
	SKEY_DOT,        0,             SKEY_KPDOT,	 /* 78  . >                .     */
	SKEY_INSERT,     0,             0,               /* 79  Today                    */
	SKEY_DELETE,     0,             0,	         /* 80  Del                      */
	SKEY_LEFT,       SKEY_HOME,     0,	         /* 81  lt_arrow  Home           */
	SKEY_DOWN,       SKEY_PAGEDOWN, 0,	         /* 82  dn_arrow  PgDn           */
	SKEY_RIGHT,      SKEY_END,      0,	         /* 83  rt_arrow  End            */
	0, 0, 0,	/* 84 */
	0, 0, 0,	/* 85 */
	0, 0, 0,	/* 86 */
	0, 0, 0,        /* 87 */
	SKEY_LEFTSHIFT,  0,              0,	         /* 88  Shift(L)                 */
	SKEY_RIGHTSHIFT, 0,              0,	         /* 89  Shift(R)                 */
	0, 0, 0,	/* 90 */
	0, 0, 0,	/* 91 */
	0, 0, 0,	/* 92 */
	0, 0, 0,	/* 93 */
	0, 0, 0, 	/* 94 */
	0, 0, 0         /* 95 */
};

#define SKBD_PRESS	      0x7f
#define SKBD_RELEASE	      0x80
#define SKBD_KEYMAP_SIZE        96        /* Number of rows in the keymap */
#define SKBD_MODIFIER_STATES     3        /* Number of valid states (normal, function, numlock) */
#define SKBD_FUNCTION_OFFSET     1
#define SKBD_NUMLOCK_OFFSET      2
#define SKBD_MAX_KEYCODES      128        /* 128 possible keycodes */

#define SKBD_KEYCODE_TABLE_SIZE (SKBD_KEYMAP_SIZE * SKBD_MODIFIER_STATES)
#define SKBD_BITMAP_SIZE        SKBD_MAX_KEYCODES / BITS_PER_LONG

#define SKBD_STATE_INIT          0
#define SKBD_STATE_PREWAKE       1
#define SKBD_STATE_AWAKE         2
#define SKBD_STATE_SLEEP         3

#define SKBD_DEFAULT_PREWAKE_INTERVAL   HZ / 10   /* 100 milliseconds */
#define SKBD_DEFAULT_SLEEP_DELAY        HZ * 5    /* Five seconds     */

/* #define SKBD_DEBUG */
#undef SKBD_DEBUG

static int  skbd_numlock = 0;

struct skbd_struct {
	unsigned char      keycode[SKBD_KEYCODE_TABLE_SIZE];
	unsigned long      key_down[SKBD_BITMAP_SIZE];
	int                function;   /* Flag for "function key is down" */
	int                state;      /* Current keyboard state (see SKBD_STATE_*) */
	int                dcd;
	struct timer_list  timer;
	struct tty_struct *tty;
	void              (*old_ledfunc)(unsigned int led);
};


/*
 * skbd_release_all_keys() sends appropriate events to the keyboard.c driver 
 * to ensure that all keyrelease events have been recorded. 
 */

static void skbd_release_all_keys(struct skbd_struct *skbd)
{
	int i,j;
	unsigned long *b = skbd->key_down;

#ifdef SKBD_DEBUG
	printk(KERN_ALERT __FILE__ ": Releasing all keys.\n"); 
#endif
	for ( i = 0 ; i < SKBD_MAX_KEYCODES ; i+=BITS_PER_LONG, b++ ) {
		if ( *b ) {  /* Should fix this to use the ffs() function */
			for (j=0 ; j<BITS_PER_LONG; j++)
				if (test_and_clear_bit(j,b))
					handle_scancode(i+j, 0);
		}
	}
}

/*
 * skbd_goto_state() handles state transitions.
 */

static void skbd_goto_state( struct skbd_struct *skbd, int new_state )
{
	unsigned long flags;

	if ( !skbd || skbd->state == new_state )
		return;

#ifdef SKBD_DEBUG
	printk(KERN_ALERT __FILE__ ": Switching from state %d to %d (%ld)\n", skbd->state,new_state,jiffies);
#endif

	save_flags(flags); cli();
	
	switch(new_state) {
	case SKBD_STATE_PREWAKE:
		GPSR |= GPIO_BITSY_COM_RTS; /* Clear RTS */
		mod_timer(&skbd->timer, jiffies + SKBD_DEFAULT_PREWAKE_INTERVAL );
		break;
	case SKBD_STATE_AWAKE:
		GPCR |= GPIO_BITSY_COM_RTS; /* Set RTS   */
		mod_timer(&skbd->timer, jiffies + SKBD_DEFAULT_SLEEP_DELAY);
		break;
	case SKBD_STATE_SLEEP:
		GPSR |= GPIO_BITSY_COM_RTS; /* Clear RTS */
		del_timer(&skbd->timer);
		break;
	}
	skbd->state = new_state;
	restore_flags(flags);
}

static void skbd_update_state_dcd_received( struct skbd_struct *skbd )
{
	if ( !skbd ) return;

	switch (skbd->state) {
	case SKBD_STATE_PREWAKE:
		/* Do nothing...we will wake up soon anyway */
		break;
	case SKBD_STATE_AWAKE:                                 /* Keyboard was reseated       */
		skbd_goto_state( skbd, SKBD_STATE_PREWAKE );   /* We need to flip RTS once    */
		break;
	case SKBD_STATE_SLEEP:
		skbd_goto_state( skbd, SKBD_STATE_AWAKE );     /* A keypress happened */
		break;
	}
}

static void skbd_update_state_char_received( struct skbd_struct *skbd )
{
	unsigned long flags;
	if ( !skbd ) return;
	
	switch (skbd->state) {
	case SKBD_STATE_PREWAKE:
		/* Do nothing...we will wake up soon anyways */
		break;
	case SKBD_STATE_AWAKE:
		/* Reset our timeout */
		save_flags(flags); cli();
		mod_timer(&skbd->timer, jiffies + SKBD_DEFAULT_SLEEP_DELAY);
		restore_flags(flags);
		break;
	case SKBD_STATE_SLEEP:
		/* This shouldn't have happened */
		printk(KERN_ALERT __FILE__ ": received character in sleep mode\n");
		skbd_goto_state( skbd, SKBD_STATE_AWAKE );     /* Switch to keypress state */
		break;
	}
}

static void skbd_update_state_timeout( struct skbd_struct *skbd )
{
	if ( !skbd ) return;

	switch (skbd->state) {
	case SKBD_STATE_PREWAKE:
		skbd_goto_state( skbd, SKBD_STATE_AWAKE );
		break;
	case SKBD_STATE_AWAKE:
		skbd_goto_state( skbd, SKBD_STATE_SLEEP );
		break;
	case SKBD_STATE_SLEEP:
		/* This shouldn't have happened */
		printk(KERN_ALERT __FILE__ ": received timeout in sleep mode\n");
		break;
	}
}

/*
 * Timer callback: just set up a tasklet
 */

static void skbd_timer_callback(unsigned long nr)
{
	skbd_update_state_timeout( (struct skbd_struct *)nr );
/*	tasklet_schedule(&skbd_tasklet); */
}

/*
 * Callback from keyboard.c
 * We use this update to keep track of the state of the numlock key.
 */

static void skbd_ledfunc(unsigned int led)
{
	skbd_numlock = led & VC_NUMLOCK;
#ifdef SKBD_DEBUG
	printk(KERN_ALERT __FILE__ ":numlock set to %d\n",skbd_numlock);
#endif
}

/* 
 * Interrupt when the state of DCD has changed 
 */

static void skbd_handshake_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct skbd_struct *skbd;
	
#ifdef SKBD_DEBUG
	printk(KERN_ALERT __FILE__ ": SKBD handshake interrupt\n");
#endif
	
	skbd = (struct skbd_struct *)dev_id;
	if (!skbd || !skbd->tty)
		return;

	switch (irq) {
	case IRQ_GPIO_BITSY_COM_DCD:
		skbd->dcd++;
		skbd_update_state_dcd_received( skbd );
		GEDR = GPIO_BITSY_COM_DCD; /* Clear the interrupt */
		break;
	default:
		printk(KERN_ALERT __FILE__ ": Unknown IRQ %d\n", irq);
		break;
	}
}

/*
 * Forward a single character to the normal keyboard handler.
 */

static void skbd_send_char(struct skbd_struct *skbd, unsigned int data, int down)
{
	unsigned char cooked;
	unsigned char *index;

#ifdef SKBD_DEBUG
	printk(KERN_ERR __FILE__ ": Received char %d [%s]", data, (down?"down":"up"));
#endif

	if (data >= SKBD_KEYMAP_SIZE)
		return;

	index = skbd->keycode + data * SKBD_MODIFIER_STATES;
	if ( *index == SKEY_FUNCTION ) {
		skbd->function = down;
		return;
	}

	cooked = *index;
	if (skbd_numlock && index[SKBD_NUMLOCK_OFFSET])
		cooked = index[SKBD_NUMLOCK_OFFSET];
	if (skbd->function && index[SKBD_FUNCTION_OFFSET])
		cooked = index[SKBD_FUNCTION_OFFSET];
	
	if ( !cooked ) {
#ifdef SKBD_DEBUG
		printk("  --> invalid keystroke\n");
#endif
		return;
	}

	if ( (test_bit(cooked,skbd->key_down) && down)
	     || (!test_bit(cooked,skbd->key_down) && !down)) {
#ifdef SKBD_DEBUG
		printk("  --> already set\n");
#endif
		return;
	}

	if ( down ) 
		set_bit(cooked,skbd->key_down);
	else
		clear_bit(cooked,skbd->key_down);
#ifdef SKBD_DEBUG
	printk("   --> sending %d (%d)\n", cooked, down);
#endif
	handle_scancode(cooked, down);
}

/*
 * skbd_ldisc_receive() is called by the low level tty driver when characters
 * are ready for us. Forward the characters to the handle_scancode function of keyboard.c
 */

static void skbd_ldisc_receive_buf(struct tty_struct *tty, 
				   const unsigned char *cp, char *fp, int count)
{
	struct skbd_struct *skbd = (struct skbd_struct*) tty->disc_data;
	int i;

#ifdef SKBD_DEBUG
	printk(KERN_ERR __FILE__ ": Receive buffer size %d.\n", count);
#endif
	for (i = 0; i < count; i++)
		skbd_send_char(skbd, cp[i] & 0x7f, !(cp[i] & 0x80));

	if ( count > 0 ) {
		skbd_update_state_char_received(skbd);
		tasklet_schedule(&keyboard_tasklet); /* Run keyboard bottom half */
	}
}

/*
 * skbd_ldisc_room() reports how much room we do have for receiving data.
 * Although we in fact have infinite room, we need to specify some value.
 */

static int skbd_ldisc_receive_room(struct tty_struct *tty)
{
#ifdef SKBD_DEBUG
	printk(KERN_ERR __FILE__ ": Receive room\n");
#endif
	return 255;
}

/*
 * skbd_ldisc_read() returns whatever characters are available (in our case, none)
 * Instead, we hang out and let other tasks run until a signal occurs.
 */

static ssize_t skbd_ldisc_read(struct tty_struct * tty, struct file * file, 
			       unsigned char * buf, size_t nr)
{
	struct skbd_struct *skbd = (struct skbd_struct*) tty->disc_data;

#ifdef SKBD_DEBUG
	printk(KERN_ERR __FILE__ ": Read\n");
#endif
	
	if ( !skbd ) return 0;

	skbd_goto_state(skbd, SKBD_STATE_PREWAKE);
	current->state = TASK_INTERRUPTIBLE;
	while (!signal_pending(current))
		schedule();
	current->state = TASK_RUNNING;
	return 0;
}

/*
 * skbd_ldisc_ioctl() does nothing
 */


static int skbd_ldisc_ioctl(struct tty_struct * tty, struct file * file, 
			    unsigned int cmd, unsigned long arg)
{
/* 	struct skbd_struct *skbd = (struct skbd*) tty->disc_data; */
	return -EINVAL;
}


static ssize_t skbd_ldisc_write(struct tty_struct *tty, struct file *file, 
				const unsigned char *buf, size_t count)
{
/* 	struct skbd_struct *skbd = (struct skbd*) tty->disc_data; */
	return count;
}

/*
 * skbd_startup() installs the appropriate interrupt routines to watch for
 * changes on the DCD line and for timer events.
 */

static int skbd_startup(struct skbd_struct *skbd)
{
	unsigned long flags;
	int retval = 0;

	save_flags_cli(flags);

	/* Direction registers */
	GPDR |= GPIO_BITSY_COM_RTS;
	GPDR &= ~GPIO_BITSY_COM_DCD;
	set_GPIO_IRQ_edge( GPIO_BITSY_COM_DCD, GPIO_RISING_EDGE );

	/* Timer structure */
	skbd->timer.function = skbd_timer_callback;
	skbd->timer.data = (unsigned long) skbd;

	retval = request_irq(IRQ_GPIO_BITSY_COM_DCD,
			     skbd_handshake_interrupt,
			     SA_SHIRQ | SA_INTERRUPT | SA_SAMPLE_RANDOM,
			     "Stowaway DCD", (void *)skbd);
	restore_flags(flags);
	return retval;
}

/*
 * Shutdown the interrupt
 */

static void skbd_shutdown(struct skbd_struct * skbd)
{
	unsigned long	flags;
	
	del_timer(&skbd->timer);

	save_flags_cli(flags);
	free_irq(IRQ_GPIO_BITSY_COM_DCD, (void *)skbd);
	restore_flags(flags);
}

/*
 * skbd_ldisc_open() is the routine that is called upon setting our line
 * discipline on a tty. It allocates storage space for the keyboard buffer and
 * inserts itself in the tty.
 */

static int skbd_ldisc_open(struct tty_struct *tty)
{
	struct skbd_struct *skbd;
	int retval;

#ifdef SKBD_DEBUG
	printk(KERN_ERR __FILE__ ": Opening line discipline.\n");
#endif

	if (!(skbd = kmalloc(sizeof(struct skbd_struct), GFP_KERNEL))) {
		return -ENOMEM;
	}

	memset(skbd, 0, sizeof(struct skbd_struct));

	/* Set up the interrupts */
	if ( (retval = skbd_startup(skbd)) ) {
		kfree(skbd);
		return retval;
	}

	skbd->tty = tty;
	tty->disc_data = skbd;

	/* Initialize the keycode translation table */
	memcpy(skbd->keycode, skbd_keycode, SKBD_KEYCODE_TABLE_SIZE);

	/* Install the numlock keyboard feedback */
	skbd->old_ledfunc = kbd_ledfunc;
	kbd_ledfunc = skbd_ledfunc;
	skbd_numlock = 0;  /* Actually, we should check the real keyboard */

	MOD_INC_USE_COUNT;
	return 0;
}

/*
 * skbd_ldisc_close() is the opposite of skbd_ldisc_open()
 */

static void skbd_ldisc_close(struct tty_struct *tty)
{
	struct skbd_struct *skbd = (struct skbd_struct*) tty->disc_data;
#ifdef SKBD_DEBUG
	printk(KERN_ERR __FILE__ ": Closing line discipline.\n");
#endif

	skbd_release_all_keys(skbd);
	skbd_shutdown(skbd);
	kbd_ledfunc = skbd->old_ledfunc;
	kfree(skbd);

	MOD_DEC_USE_COUNT;
}

static struct tty_ldisc skbd_ldisc = {
	magic:	        TTY_LDISC_MAGIC,
	name:		"h3600_stowaway",
	open:		skbd_ldisc_open,
	close:		skbd_ldisc_close,
	read:		skbd_ldisc_read,
	write:          skbd_ldisc_write,
	ioctl:		skbd_ldisc_ioctl,
	receive_buf:	skbd_ldisc_receive_buf,
	receive_room:	skbd_ldisc_receive_room,
};

/*
 * The functions for insering/removing us as a module.
 */

#define N_SKBD 15         /* Our magic line discipline number */

int __init skbd_init(void)
{
        if (tty_register_ldisc(N_SKBD, &skbd_ldisc)) {
#ifdef SKBD_DEBUG
                printk(KERN_ERR "h3600_stowaway.c: Error registering line discipline.\n");
#endif
		return -ENODEV;
	}

	return 0;
}

void __exit 
skbd_exit(void)
{
	tty_register_ldisc(N_SKBD, NULL);
}

module_init(skbd_init);
module_exit(skbd_exit);


