| /* |
| * 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/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/interrupt.h> |
| #include <linux/fcntl.h> |
| #include <linux/errno.h> |
| #include <linux/timer.h> |
| #include <linux/slab.h> |
| #include <linux/miscdevice.h> |
| #include <linux/random.h> |
| #include <linux/poll.h> |
| #include <linux/init.h> |
| #include <linux/smp_lock.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; |
| wait_queue_head_t 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; |
| static int qp_count; |
| 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; |
| kill_fasync(&queue->fasync, SIGIO, POLL_IN); |
| wake_up_interruptible(&queue->proc_list); |
| } |
| |
| static int release_qp(struct inode * inode, struct file * file) |
| { |
| unsigned char status; |
| |
| lock_kernel(); |
| fasync_qp(-1, file, 0); |
| if (!--qp_count) { |
| if (!poll_qp_status()) |
| printk(KERN_WARNING "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(KERN_WARNING "Warning: Mouse device busy in release_qp()\n"); |
| free_irq(QP_IRQ, NULL); |
| } |
| unlock_kernel(); |
| 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(KERN_ERR "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 */ |
| 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) |
| { |
| 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; |
| } |
| |
| struct file_operations qp_fops = { |
| owner: THIS_MODULE, |
| read: read_qp, |
| write: write_qp, |
| poll: poll_qp, |
| open: open_qp, |
| release: release_qp, |
| fasync: fasync_qp, |
| }; |
| |
| /* |
| * Initialize driver. |
| */ |
| static struct miscdevice qp_mouse = { |
| minor: PSMOUSE_MINOR, |
| name: "QPmouse", |
| fops: &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; |
| } |
| |
| static char msg_banner[] __initdata = KERN_INFO "82C710 type pointing device detected -- driver installed.\n"; |
| static char msg_nomem[] __initdata = KERN_ERR "qpmouse: no queue memory.\n"; |
| |
| static int __init qpmouse_init_driver(void) |
| { |
| if (!probe_qp()) |
| return -EIO; |
| |
| printk(msg_banner); |
| |
| /* printk("82C710 address = %x (should be 0x310)\n", qp_data); */ |
| queue = (struct qp_queue *) kmalloc(sizeof(*queue), GFP_KERNEL); |
| if (queue == NULL) { |
| printk(msg_nomem); |
| return -ENOMEM; |
| } |
| qp_present = 1; |
| misc_register(&qp_mouse); |
| memset(queue, 0, sizeof(*queue)); |
| queue->head = queue->tail = 0; |
| init_waitqueue_head(&queue->proc_list); |
| return 0; |
| } |
| |
| static void __exit qpmouse_exit_driver(void) |
| { |
| misc_deregister(&qp_mouse); |
| kfree(queue); |
| } |
| |
| module_init(qpmouse_init_driver); |
| module_exit(qpmouse_exit_driver); |
| |
| |
| MODULE_LICENSE("GPL"); |
| EXPORT_NO_SYMBOLS; |