summaryrefslogtreecommitdiffstats
path: root/drivers/char/qpmouse.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/qpmouse.c')
-rw-r--r--drivers/char/qpmouse.c373
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