|  | /* | 
|  | * | 
|  | * BRIEF MODULE DESCRIPTION | 
|  | *	Qtronix 990P infrared keyboard driver. | 
|  | * | 
|  | * | 
|  | * Copyright 2001 MontaVista Software Inc. | 
|  | * Author: MontaVista Software, Inc. | 
|  | *         	ppopov@mvista.com or source@mvista.com | 
|  | * | 
|  | * | 
|  | *  The bottom portion of this driver was take from | 
|  | *  pc_keyb.c  Please see that file for copyrights. | 
|  | * | 
|  | *  This program is free software; you can redistribute  it and/or modify it | 
|  | *  under  the terms of  the GNU General  Public License as published by the | 
|  | *  Free Software Foundation;  either version 2 of the  License, or (at your | 
|  | *  option) any later version. | 
|  | * | 
|  | *  THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED | 
|  | *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF | 
|  | *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN | 
|  | *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT, | 
|  | *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | 
|  | *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF | 
|  | *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | 
|  | *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT | 
|  | *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 
|  | *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  | * | 
|  | *  You should have received a copy of the  GNU General Public License along | 
|  | *  with this program; if not, write  to the Free Software Foundation, Inc., | 
|  | *  675 Mass Ave, Cambridge, MA 02139, USA. | 
|  | */ | 
|  |  | 
|  |  | 
|  | /* | 
|  | * NOTE: | 
|  | * | 
|  | *	This driver has only been tested with the Consumer IR | 
|  | *	port of the ITE 8172 system controller. | 
|  | * | 
|  | *	You do not need this driver if you are using the ps/2 or | 
|  | *	USB adapter that the keyboard ships with.  You only need | 
|  | *	this driver if your board has a IR port and the keyboard | 
|  | *	data is being sent directly to the IR.  In that case, | 
|  | *	you also need some low-level IR support. See it8172_cir.c. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #ifdef CONFIG_QTRONIX_KEYBOARD | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/types.h> | 
|  | #include <linux/pci.h> | 
|  | #include <linux/kernel.h> | 
|  |  | 
|  | #include <asm/it8172/it8172.h> | 
|  | #include <asm/it8172/it8172_int.h> | 
|  | #include <asm/it8172/it8172_cir.h> | 
|  |  | 
|  | #include <linux/spinlock.h> | 
|  | #include <linux/sched.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/tty.h> | 
|  | #include <linux/mm.h> | 
|  | #include <linux/signal.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/kbd_ll.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/poll.h> | 
|  | #include <linux/miscdevice.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/kbd_kern.h> | 
|  | #include <linux/smp_lock.h> | 
|  | #include <asm/io.h> | 
|  | #include <linux/pc_keyb.h> | 
|  |  | 
|  | #include <asm/keyboard.h> | 
|  | #include <linux/bitops.h> | 
|  | #include <asm/uaccess.h> | 
|  | #include <asm/irq.h> | 
|  | #include <asm/system.h> | 
|  |  | 
|  | #define leading1 0 | 
|  | #define leading2 0xF | 
|  |  | 
|  | #define KBD_CIR_PORT 0 | 
|  | #define AUX_RECONNECT 170 /* scancode when ps2 device is plugged (back) in */ | 
|  |  | 
|  | static int data_index; | 
|  | struct cir_port *cir; | 
|  | static unsigned char kbdbytes[5]; | 
|  | static unsigned char cir_data[32]; /* we only need 16 chars */ | 
|  |  | 
|  | static void kbd_int_handler(int irq, void *dev_id, struct pt_regs *regs); | 
|  | static int handle_data(unsigned char *p_data); | 
|  | static inline void handle_mouse_event(unsigned char scancode); | 
|  | static inline void handle_keyboard_event(unsigned char scancode, int down); | 
|  | static int __init psaux_init(void); | 
|  |  | 
|  | static struct aux_queue *queue;	/* Mouse data buffer. */ | 
|  | static int aux_count = 0; | 
|  |  | 
|  | /* | 
|  | * Keys accessed through the 'Fn' key | 
|  | * The Fn key does not produce a key-up sequence. So, the first | 
|  | * time the user presses it, it will be key-down event. The key | 
|  | * stays down until the user presses it again. | 
|  | */ | 
|  | #define NUM_FN_KEYS 56 | 
|  | static unsigned char fn_keys[NUM_FN_KEYS] = { | 
|  | 0,0,0,0,0,0,0,0,        /* 0 7   */ | 
|  | 8,9,10,93,0,0,0,0,      /* 8 15  */ | 
|  | 0,0,0,0,0,0,0,5,        /* 16 23 */ | 
|  | 6,7,91,0,0,0,0,0,       /* 24 31 */ | 
|  | 0,0,0,0,0,2,3,4,        /* 32 39 */ | 
|  | 92,0,0,0,0,0,0,0,       /* 40 47 */ | 
|  | 0,0,0,0,11,0,94,95        /* 48 55 */ | 
|  |  | 
|  | }; | 
|  |  | 
|  | void __init init_qtronix_990P_kbd(void) | 
|  | { | 
|  | int retval; | 
|  |  | 
|  | cir = (struct cir_port *)kmalloc(sizeof(struct cir_port), GFP_KERNEL); | 
|  | if (!cir) { | 
|  | printk("Unable to initialize Qtronix keyboard\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * revisit | 
|  | * this should be programmable, somehow by the, by the user. | 
|  | */ | 
|  | cir->port = KBD_CIR_PORT; | 
|  | cir->baud_rate = 0x1d; | 
|  | cir->rdwos = 0; | 
|  | cir->rxdcr = 0x3; | 
|  | cir->hcfs = 0; | 
|  | cir->fifo_tl = 0; | 
|  | cir->cfq = 0x1d; | 
|  | cir_port_init(cir); | 
|  |  | 
|  | retval = request_irq(IT8172_CIR0_IRQ, kbd_int_handler, | 
|  | (unsigned long )(IRQF_DISABLED|IRQF_SHARED), | 
|  | (const char *)"Qtronix IR Keyboard", (void *)cir); | 
|  |  | 
|  | if (retval) { | 
|  | printk("unable to allocate cir %d irq %d\n", | 
|  | cir->port, IT8172_CIR0_IRQ); | 
|  | } | 
|  | #ifdef CONFIG_PSMOUSE | 
|  | psaux_init(); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | static inline unsigned char BitReverse(unsigned short key) | 
|  | { | 
|  | unsigned char rkey = 0; | 
|  | rkey |= (key & 0x1) << 7; | 
|  | rkey |= (key & 0x2) << 5; | 
|  | rkey |= (key & 0x4) << 3; | 
|  | rkey |= (key & 0x8) << 1; | 
|  | rkey |= (key & 0x10) >> 1; | 
|  | rkey |= (key & 0x20) >> 3; | 
|  | rkey |= (key & 0x40) >> 5; | 
|  | rkey |= (key & 0x80) >> 7; | 
|  | return rkey; | 
|  |  | 
|  | } | 
|  |  | 
|  |  | 
|  | static inline u_int8_t UpperByte(u_int8_t data) | 
|  | { | 
|  | return (data >> 4); | 
|  | } | 
|  |  | 
|  |  | 
|  | static inline u_int8_t LowerByte(u_int8_t data) | 
|  | { | 
|  | return (data & 0xF); | 
|  | } | 
|  |  | 
|  |  | 
|  | int CheckSumOk(u_int8_t byte1, u_int8_t byte2, | 
|  | u_int8_t byte3, u_int8_t byte4, u_int8_t byte5) | 
|  | { | 
|  | u_int8_t CheckSum; | 
|  |  | 
|  | CheckSum = (byte1 & 0x0F) + byte2 + byte3 + byte4 + byte5; | 
|  | if ( LowerByte(UpperByte(CheckSum) + LowerByte(CheckSum)) != UpperByte(byte1) ) | 
|  | return 0; | 
|  | else | 
|  | return 1; | 
|  | } | 
|  |  | 
|  |  | 
|  | static void kbd_int_handler(int irq, void *dev_id, struct pt_regs *regs) | 
|  | { | 
|  | struct cir_port *cir; | 
|  | int j; | 
|  | unsigned char int_status; | 
|  |  | 
|  | cir = (struct cir_port *)dev_id; | 
|  | int_status = get_int_status(cir); | 
|  | if (int_status & 0x4) { | 
|  | clear_fifo(cir); | 
|  | return; | 
|  | } | 
|  |  | 
|  | while (cir_get_rx_count(cir)) { | 
|  |  | 
|  | cir_data[data_index] = cir_read_data(cir); | 
|  |  | 
|  | if (data_index == 0) {/* expecting first byte */ | 
|  | if (cir_data[data_index] != leading1) { | 
|  | //printk("!leading byte %x\n", cir_data[data_index]); | 
|  | set_rx_active(cir); | 
|  | clear_fifo(cir); | 
|  | continue; | 
|  | } | 
|  | } | 
|  | if (data_index == 1) { | 
|  | if ((cir_data[data_index] & 0xf) != leading2) { | 
|  | set_rx_active(cir); | 
|  | data_index = 0; /* start over */ | 
|  | clear_fifo(cir); | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | if ( (cir_data[data_index] == 0xff)) { /* last byte */ | 
|  | //printk("data_index %d\n", data_index); | 
|  | set_rx_active(cir); | 
|  | #if 0 | 
|  | for (j=0; j<=data_index; j++) { | 
|  | printk("rx_data %d:  %x\n", j, cir_data[j]); | 
|  | } | 
|  | #endif | 
|  | data_index = 0; | 
|  | handle_data(cir_data); | 
|  | return; | 
|  | } | 
|  | else if (data_index>16) { | 
|  | set_rx_active(cir); | 
|  | #if 0 | 
|  | printk("warning: data_index %d\n", data_index); | 
|  | for (j=0; j<=data_index; j++) { | 
|  | printk("rx_data %d:  %x\n", j, cir_data[j]); | 
|  | } | 
|  | #endif | 
|  | data_index = 0; | 
|  | clear_fifo(cir); | 
|  | return; | 
|  | } | 
|  | data_index++; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | #define NUM_KBD_BYTES 5 | 
|  | static int handle_data(unsigned char *p_data) | 
|  | { | 
|  | u_int32_t bit_bucket; | 
|  | u_int32_t i, j; | 
|  | u_int32_t got_bits, next_byte; | 
|  | int down = 0; | 
|  |  | 
|  | /* Reorganize the bit stream */ | 
|  | for (i=0; i<16; i++) | 
|  | p_data[i] = BitReverse(~p_data[i]); | 
|  |  | 
|  | /* | 
|  | * We've already previously checked that p_data[0] | 
|  | * is equal to leading1 and that (p_data[1] & 0xf) | 
|  | * is equal to leading2. These twelve bits are the | 
|  | * leader code.  We can now throw them away (the 12 | 
|  | * bits) and continue parsing the stream. | 
|  | */ | 
|  | bit_bucket = p_data[1] << 12; | 
|  | got_bits = 4; | 
|  | next_byte = 2; | 
|  |  | 
|  | /* | 
|  | * Process four bits at a time | 
|  | */ | 
|  | for (i=0; i<NUM_KBD_BYTES; i++) { | 
|  |  | 
|  | kbdbytes[i]=0; | 
|  |  | 
|  | for (j=0; j<8; j++) /* 8 bits per byte */ | 
|  | { | 
|  | if (got_bits < 4) { | 
|  | bit_bucket |= (p_data[next_byte++] << (8 - got_bits)); | 
|  | got_bits += 8; | 
|  | } | 
|  |  | 
|  | if ((bit_bucket & 0xF000) == 0x8000) { | 
|  | /* Convert 1000b to 1 */ | 
|  | kbdbytes[i] = 0x80 | (kbdbytes[i] >> 1); | 
|  | got_bits -= 4; | 
|  | bit_bucket = bit_bucket << 4; | 
|  | } | 
|  | else if ((bit_bucket & 0xC000) == 0x8000) { | 
|  | /* Convert 10b to 0 */ | 
|  | kbdbytes[i] =  kbdbytes[i] >> 1; | 
|  | got_bits -= 2; | 
|  | bit_bucket = bit_bucket << 2; | 
|  | } | 
|  | else { | 
|  | /* bad serial stream */ | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | if (next_byte > 16) { | 
|  | //printk("error: too many bytes\n"); | 
|  | return 1; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | if (!CheckSumOk(kbdbytes[0], kbdbytes[1], | 
|  | kbdbytes[2], kbdbytes[3], kbdbytes[4])) { | 
|  | //printk("checksum failed\n"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | if (kbdbytes[1] & 0x08) { | 
|  | //printk("m: %x %x %x\n", kbdbytes[1], kbdbytes[2], kbdbytes[3]); | 
|  | handle_mouse_event(kbdbytes[1]); | 
|  | handle_mouse_event(kbdbytes[2]); | 
|  | handle_mouse_event(kbdbytes[3]); | 
|  | } | 
|  | else { | 
|  | if (kbdbytes[2] == 0) down = 1; | 
|  | #if 0 | 
|  | if (down) | 
|  | printk("down %d\n", kbdbytes[3]); | 
|  | else | 
|  | printk("up %d\n", kbdbytes[3]); | 
|  | #endif | 
|  | handle_keyboard_event(kbdbytes[3], down); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | DEFINE_SPINLOCK(kbd_controller_lock); | 
|  | static unsigned char handle_kbd_event(void); | 
|  |  | 
|  |  | 
|  | int kbd_setkeycode(unsigned int scancode, unsigned int keycode) | 
|  | { | 
|  | printk("kbd_setkeycode scancode %x keycode %x\n", scancode, keycode); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int kbd_getkeycode(unsigned int scancode) | 
|  | { | 
|  | return scancode; | 
|  | } | 
|  |  | 
|  |  | 
|  | int kbd_translate(unsigned char scancode, unsigned char *keycode, | 
|  | char raw_mode) | 
|  | { | 
|  | static int prev_scancode = 0; | 
|  |  | 
|  | if (scancode == 0x00 || scancode == 0xff) { | 
|  | prev_scancode = 0; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* todo */ | 
|  | if (!prev_scancode && scancode == 160) { /* Fn key down */ | 
|  | //printk("Fn key down\n"); | 
|  | prev_scancode = 160; | 
|  | return 0; | 
|  | } | 
|  | else if (prev_scancode && scancode == 160) { /* Fn key up */ | 
|  | //printk("Fn key up\n"); | 
|  | prev_scancode = 0; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* todo */ | 
|  | if (prev_scancode == 160) { | 
|  | if (scancode <= NUM_FN_KEYS) { | 
|  | *keycode = fn_keys[scancode]; | 
|  | //printk("fn keycode %d\n", *keycode); | 
|  | } | 
|  | else | 
|  | return 0; | 
|  | } | 
|  | else if (scancode <= 127) { | 
|  | *keycode = scancode; | 
|  | } | 
|  | else | 
|  | return 0; | 
|  |  | 
|  |  | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | char kbd_unexpected_up(unsigned char keycode) | 
|  | { | 
|  | //printk("kbd_unexpected_up\n"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static unsigned char kbd_exists = 1; | 
|  |  | 
|  | static inline void handle_keyboard_event(unsigned char scancode, int down) | 
|  | { | 
|  | kbd_exists = 1; | 
|  | handle_scancode(scancode, down); | 
|  | tasklet_schedule(&keyboard_tasklet); | 
|  | } | 
|  |  | 
|  |  | 
|  | void kbd_leds(unsigned char leds) | 
|  | { | 
|  | } | 
|  |  | 
|  | /* dummy */ | 
|  | void kbd_init_hw(void) | 
|  | { | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | static inline void handle_mouse_event(unsigned char scancode) | 
|  | { | 
|  | if(scancode == AUX_RECONNECT){ | 
|  | queue->head = queue->tail = 0;  /* Flush input queue */ | 
|  | //	__aux_write_ack(AUX_ENABLE_DEV);  /* ping the mouse :) */ | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (aux_count) { | 
|  | int head = queue->head; | 
|  |  | 
|  | queue->buf[head] = scancode; | 
|  | head = (head + 1) & (AUX_BUF_SIZE-1); | 
|  | if (head != queue->tail) { | 
|  | queue->head = head; | 
|  | kill_fasync(&queue->fasync, SIGIO, POLL_IN); | 
|  | wake_up_interruptible(&queue->proc_list); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static unsigned char get_from_queue(void) | 
|  | { | 
|  | unsigned char result; | 
|  | unsigned long flags; | 
|  |  | 
|  | spin_lock_irqsave(&kbd_controller_lock, flags); | 
|  | result = queue->buf[queue->tail]; | 
|  | queue->tail = (queue->tail + 1) & (AUX_BUF_SIZE-1); | 
|  | spin_unlock_irqrestore(&kbd_controller_lock, flags); | 
|  | return result; | 
|  | } | 
|  |  | 
|  |  | 
|  | static inline int queue_empty(void) | 
|  | { | 
|  | return queue->head == queue->tail; | 
|  | } | 
|  |  | 
|  | static int fasync_aux(int fd, struct file *filp, int on) | 
|  | { | 
|  | int retval; | 
|  |  | 
|  | //printk("fasync_aux\n"); | 
|  | retval = fasync_helper(fd, filp, on, &queue->fasync); | 
|  | if (retval < 0) | 
|  | return retval; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Random magic cookie for the aux device | 
|  | */ | 
|  | #define AUX_DEV ((void *)queue) | 
|  |  | 
|  | static int release_aux(struct inode * inode, struct file * file) | 
|  | { | 
|  | fasync_aux(-1, file, 0); | 
|  | aux_count--; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int open_aux(struct inode * inode, struct file * file) | 
|  | { | 
|  | if (aux_count++) { | 
|  | return 0; | 
|  | } | 
|  | queue->head = queue->tail = 0;		/* Flush input queue */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Put bytes from input queue to buffer. | 
|  | */ | 
|  |  | 
|  | static ssize_t read_aux(struct file * file, char * buffer, | 
|  | size_t count, loff_t *ppos) | 
|  | { | 
|  | DECLARE_WAITQUEUE(wait, current); | 
|  | ssize_t i = count; | 
|  | unsigned char c; | 
|  |  | 
|  | if (queue_empty()) { | 
|  | if (file->f_flags & O_NONBLOCK) | 
|  | return -EAGAIN; | 
|  | add_wait_queue(&queue->proc_list, &wait); | 
|  | repeat: | 
|  | set_current_state(TASK_INTERRUPTIBLE); | 
|  | if (queue_empty() && !signal_pending(current)) { | 
|  | schedule(); | 
|  | goto repeat; | 
|  | } | 
|  | current->state = TASK_RUNNING; | 
|  | remove_wait_queue(&queue->proc_list, &wait); | 
|  | } | 
|  | while (i > 0 && !queue_empty()) { | 
|  | c = get_from_queue(); | 
|  | put_user(c, buffer++); | 
|  | i--; | 
|  | } | 
|  | if (count-i) { | 
|  | struct inode *inode = file->f_dentry->d_inode; | 
|  | inode->i_atime = current_fs_time(inode->i_sb); | 
|  | return count-i; | 
|  | } | 
|  | if (signal_pending(current)) | 
|  | return -ERESTARTSYS; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Write to the aux device. | 
|  | */ | 
|  |  | 
|  | static ssize_t write_aux(struct file * file, const char * buffer, | 
|  | size_t count, loff_t *ppos) | 
|  | { | 
|  | /* | 
|  | * The ITE boards this was tested on did not have the | 
|  | * transmit wires connected. | 
|  | */ | 
|  | return count; | 
|  | } | 
|  |  | 
|  | static unsigned int aux_poll(struct file *file, poll_table * wait) | 
|  | { | 
|  | poll_wait(file, &queue->proc_list, wait); | 
|  | if (!queue_empty()) | 
|  | return POLLIN | POLLRDNORM; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | struct file_operations psaux_fops = { | 
|  | .read		= read_aux, | 
|  | .write		= write_aux, | 
|  | .poll		= aux_poll, | 
|  | .open		= open_aux, | 
|  | .release	= release_aux, | 
|  | .fasync		= fasync_aux, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * Initialize driver. | 
|  | */ | 
|  | static struct miscdevice psaux_mouse = { | 
|  | PSMOUSE_MINOR, "psaux", &psaux_fops | 
|  | }; | 
|  |  | 
|  | static int __init psaux_init(void) | 
|  | { | 
|  | int retval; | 
|  |  | 
|  | retval = misc_register(&psaux_mouse); | 
|  | if(retval < 0) | 
|  | return retval; | 
|  |  | 
|  | queue = (struct aux_queue *) kmalloc(sizeof(*queue), GFP_KERNEL); | 
|  | if (!queue) { | 
|  | misc_deregister(&psaux_mouse); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | memset(queue, 0, sizeof(*queue)); | 
|  | queue->head = queue->tail = 0; | 
|  | init_waitqueue_head(&queue->proc_list); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | module_init(init_qtronix_990P_kbd); | 
|  | #endif |