patch-2.4.4 linux/drivers/char/qtronix.c

Next file: linux/drivers/char/qtronixmap.map
Previous file: linux/drivers/char/qpmouse.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.3/linux/drivers/char/qtronix.c linux/drivers/char/qtronix.c
@@ -0,0 +1,597 @@
+/*
+ *
+ * BRIEF MODULE DESCRIPTION
+ *	Qtronix 990P infrared keyboard driver.
+ *
+ *
+ * Copyright 2001 MontaVista Software Inc.
+ * Author: MontaVista Software, Inc.
+ *         	ppopov@mvista.com or support@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.
+ */
+
+#include <linux/config.h>
+
+/* 
+ * 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/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/random.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/malloc.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 <asm/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_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 )(SA_INTERRUPT|SA_SHIRQ), 
+			(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;
+}
+
+
+spinlock_t kbd_controller_lock = SPIN_LOCK_UNLOCKED;
+static unsigned char handle_kbd_event(void);
+
+
+int pckbd_setkeycode(unsigned int scancode, unsigned int keycode)
+{
+	printk("pckbd_setkeycode scancode %x keycode %x\n", scancode, keycode);
+	return 0;
+}
+
+int pckbd_getkeycode(unsigned int scancode)
+{
+	return scancode;
+}
+
+
+int pckbd_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 pckbd_unexpected_up(unsigned char keycode)
+{
+	//printk("pckbd_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 pckbd_leds(unsigned char leds)
+{
+}
+
+/* dummy */
+void pckbd_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;
+	}
+
+	add_mouse_randomness(scancode);
+	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)
+{
+	lock_kernel();
+	fasync_aux(-1, file, 0);
+	aux_count--;
+	unlock_kernel();
+	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) {
+		file->f_dentry->d_inode->i_atime = CURRENT_TIME;
+		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)
+{
+	misc_register(&psaux_mouse);
+	queue = (struct aux_queue *) kmalloc(sizeof(*queue), GFP_KERNEL);
+	memset(queue, 0, sizeof(*queue));
+	queue->head = queue->tail = 0;
+	init_waitqueue_head(&queue->proc_list);
+
+	return 0;
+}
+#endif

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)