diff options
Diffstat (limited to 'drivers/char/qpmouse.c')
-rw-r--r-- | drivers/char/qpmouse.c | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/drivers/char/qpmouse.c b/drivers/char/qpmouse.c new file mode 100644 index 000000000..12b134719 --- /dev/null +++ b/drivers/char/qpmouse.c @@ -0,0 +1,373 @@ +/* + * linux/drivers/char/qpmouse.c + * + * Driver for a 82C710 C&T mouse interface chip. + * + * Based on the PS/2 driver by Johan Myreen. + * + * Corrections in device setup for some laptop mice & trackballs. + * 02Feb93 (troyer@saifr00.cfsat.Honeywell.COM,mch@wimsey.bc.ca) + * + * Modified by Johan Myreen (jem@iki.fi) 04Aug93 + * to include support for QuickPort mouse. + * + * Changed references to "QuickPort" with "82C710" since "QuickPort" + * is not what this driver is all about -- QuickPort is just a + * connector type, and this driver is for the mouse port on the Chips + * & Technologies 82C710 interface chip. 15Nov93 jem@iki.fi + * + * Added support for SIGIO. 28Jul95 jem@iki.fi + * + * Rearranged SIGIO support to use code from tty_io. 9Sept95 ctm@ardi.com + * + * Modularised 8-Sep-95 Philip Blundell <pjb27@cam.ac.uk> + */ + +#include <linux/module.h> + +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/fcntl.h> +#include <linux/errno.h> +#include <linux/timer.h> +#include <linux/malloc.h> +#include <linux/miscdevice.h> +#include <linux/random.h> +#include <linux/poll.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#include <asm/semaphore.h> + +#include <linux/pc_keyb.h> /* mouse enable command.. */ + + +/* + * We use the same minor number as the PS/2 mouse for (bad) historical + * reasons.. + */ +#define PSMOUSE_MINOR 1 /* Minor device # for this mouse */ +#define QP_BUF_SIZE 2048 + +struct qp_queue { + unsigned long head; + unsigned long tail; + struct wait_queue *proc_list; + struct fasync_struct *fasync; + unsigned char buf[QP_BUF_SIZE]; +}; + +static struct qp_queue *queue; + +static unsigned int get_from_queue(void) +{ + unsigned int result; + unsigned long flags; + + save_flags(flags); + cli(); + result = queue->buf[queue->tail]; + queue->tail = (queue->tail + 1) & (QP_BUF_SIZE-1); + restore_flags(flags); + return result; +} + + +static inline int queue_empty(void) +{ + return queue->head == queue->tail; +} + +static int fasync_qp(int fd, struct file *filp, int on) +{ + int retval; + + retval = fasync_helper(fd, filp, on, &queue->fasync); + if (retval < 0) + return retval; + return 0; +} + +/* + * 82C710 Interface + */ + +#define QP_DATA 0x310 /* Data Port I/O Address */ +#define QP_STATUS 0x311 /* Status Port I/O Address */ + +#define QP_DEV_IDLE 0x01 /* Device Idle */ +#define QP_RX_FULL 0x02 /* Device Char received */ +#define QP_TX_IDLE 0x04 /* Device XMIT Idle */ +#define QP_RESET 0x08 /* Device Reset */ +#define QP_INTS_ON 0x10 /* Device Interrupt On */ +#define QP_ERROR_FLAG 0x20 /* Device Error */ +#define QP_CLEAR 0x40 /* Device Clear */ +#define QP_ENABLE 0x80 /* Device Enable */ + +#define QP_IRQ 12 + +static int qp_present = 0; +static int qp_count = 0; +static int qp_data = QP_DATA; +static int qp_status = QP_STATUS; + +static int poll_qp_status(void); +static int probe_qp(void); + +/* + * Interrupt handler for the 82C710 mouse port. A character + * is waiting in the 82C710. + */ + +static void qp_interrupt(int cpl, void *dev_id, struct pt_regs * regs) +{ + int head = queue->head; + int maxhead = (queue->tail-1) & (QP_BUF_SIZE-1); + + add_mouse_randomness(queue->buf[head] = inb(qp_data)); + if (head != maxhead) { + head++; + head &= QP_BUF_SIZE-1; + } + queue->head = head; + if (queue->fasync) + kill_fasync(queue->fasync, SIGIO); + wake_up_interruptible(&queue->proc_list); +} + +static int release_qp(struct inode * inode, struct file * file) +{ + unsigned char status; + + fasync_qp(-1, file, 0); + if (!--qp_count) { + if (!poll_qp_status()) + printk("Warning: Mouse device busy in release_qp()\n"); + status = inb_p(qp_status); + outb_p(status & ~(QP_ENABLE|QP_INTS_ON), qp_status); + if (!poll_qp_status()) + printk("Warning: Mouse device busy in release_qp()\n"); + free_irq(QP_IRQ, NULL); + MOD_DEC_USE_COUNT; + } + return 0; +} + +/* + * Install interrupt handler. + * Enable the device, enable interrupts. + */ + +static int open_qp(struct inode * inode, struct file * file) +{ + unsigned char status; + + if (!qp_present) + return -EINVAL; + + if (qp_count++) + return 0; + + if (request_irq(QP_IRQ, qp_interrupt, 0, "PS/2 Mouse", NULL)) { + qp_count--; + return -EBUSY; + } + + status = inb_p(qp_status); + status |= (QP_ENABLE|QP_RESET); + outb_p(status, qp_status); + status &= ~(QP_RESET); + outb_p(status, qp_status); + + queue->head = queue->tail = 0; /* Flush input queue */ + status |= QP_INTS_ON; + outb_p(status, qp_status); /* Enable interrupts */ + + while (!poll_qp_status()) { + printk("Error: Mouse device busy in open_qp()\n"); + qp_count--; + status &= ~(QP_ENABLE|QP_INTS_ON); + outb_p(status, qp_status); + free_irq(QP_IRQ, NULL); + return -EBUSY; + } + + outb_p(AUX_ENABLE_DEV, qp_data); /* Wake up mouse */ + MOD_INC_USE_COUNT; + return 0; +} + +/* + * Write to the 82C710 mouse device. + */ + +static ssize_t write_qp(struct file * file, const char * buffer, + size_t count, loff_t *ppos) +{ + ssize_t i = count; + + while (i--) { + char c; + if (!poll_qp_status()) + return -EIO; + get_user(c, buffer++); + outb_p(c, qp_data); + } + file->f_dentry->d_inode->i_mtime = CURRENT_TIME; + return count; +} + +static unsigned int poll_qp(struct file *file, poll_table * wait) +{ + poll_wait(file, &queue->proc_list, wait); + if (!queue_empty()) + return POLLIN | POLLRDNORM; + return 0; +} + +/* + * Wait for device to send output char and flush any input char. + */ + +#define MAX_RETRIES (60) + +static int poll_qp_status(void) +{ + int retries=0; + + while ((inb(qp_status)&(QP_RX_FULL|QP_TX_IDLE|QP_DEV_IDLE)) + != (QP_DEV_IDLE|QP_TX_IDLE) + && retries < MAX_RETRIES) { + + if (inb_p(qp_status)&(QP_RX_FULL)) + inb_p(qp_data); + current->state = TASK_INTERRUPTIBLE; + schedule_timeout((5*HZ + 99) / 100); + retries++; + } + return !(retries==MAX_RETRIES); +} + +/* + * Put bytes from input queue to buffer. + */ + +static ssize_t read_qp(struct file * file, char * buffer, + size_t count, loff_t *ppos) +{ + struct wait_queue wait = { current, NULL }; + 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: + 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; +} + +struct file_operations qp_fops = { + NULL, /* seek */ + read_qp, + write_qp, + NULL, /* readdir */ + poll_qp, + NULL, /* ioctl */ + NULL, /* mmap */ + open_qp, + NULL, /* flush */ + release_qp, + NULL, + fasync_qp, +}; + +/* + * Initialize driver. + */ +static struct miscdevice qp_mouse = { + PSMOUSE_MINOR, "QPmouse", &qp_fops +}; + +/* + * Function to read register in 82C710. + */ + +static inline unsigned char read_710(unsigned char index) +{ + outb_p(index, 0x390); /* Write index */ + return inb_p(0x391); /* Read the data */ +} + + +/* + * See if we can find a 82C710 device. Read mouse address. + */ + +static int __init probe_qp(void) +{ + outb_p(0x55, 0x2fa); /* Any value except 9, ff or 36 */ + outb_p(0xaa, 0x3fa); /* Inverse of 55 */ + outb_p(0x36, 0x3fa); /* Address the chip */ + outb_p(0xe4, 0x3fa); /* 390/4; 390 = config address */ + outb_p(0x1b, 0x2fa); /* Inverse of e4 */ + if (read_710(0x0f) != 0xe4) /* Config address found? */ + return 0; /* No: no 82C710 here */ + qp_data = read_710(0x0d)*4; /* Get mouse I/O address */ + qp_status = qp_data+1; + outb_p(0x0f, 0x390); + outb_p(0x0f, 0x391); /* Close config mode */ + return 1; +} + +int __init qpmouse_init(void) +{ + if (!probe_qp()) + return -EIO; + + printk(KERN_INFO "82C710 type pointing device detected -- driver installed.\n"); +/* printk("82C710 address = %x (should be 0x310)\n", qp_data); */ + qp_present = 1; + misc_register(&qp_mouse); + queue = (struct qp_queue *) kmalloc(sizeof(*queue), GFP_KERNEL); + memset(queue, 0, sizeof(*queue)); + queue->head = queue->tail = 0; + queue->proc_list = NULL; + + return 0; +} + +#ifdef MODULE +int init_module(void) +{ + return qpmouse_init(); +} + +void cleanup_module(void) +{ + misc_deregister(&qp_mouse); + kfree(queue); +} +#endif |