| /* |
| * |
| * 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. |
| */ |
| |
| #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/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 )(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; |
| } |
| |
| |
| 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 |