58217f5900
WOO HOO!
552 lines
13 KiB
C
552 lines
13 KiB
C
/*
|
|
* linux/kernel/chr_drv/psaux.c
|
|
*
|
|
* Driver for PS/2 type mouse by Johan Myreen.
|
|
*
|
|
* Supports pointing devices attached to a PS/2 type
|
|
* Keyboard and Auxiliary Device Controller.
|
|
*
|
|
* Corrections in device setup for some laptop mice & trackballs.
|
|
* 02Feb93 (troyer@saifr00.cfsat.Honeywell.COM,mch@wimsey.bc.ca)
|
|
*
|
|
* Changed to prevent keyboard lockups on AST Power Exec.
|
|
* 28Jul93 Brad Bosch - brad@lachman.com
|
|
*
|
|
* Modified by Johan Myreen (jem@cs.hut.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@cs.hut.fi
|
|
*/
|
|
|
|
/* Uncomment the following line if your mouse needs initialization. */
|
|
|
|
/* #define INITIALIZE_DEVICE */
|
|
|
|
#include <linux/timer.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/fcntl.h>
|
|
#include <linux/errno.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/segment.h>
|
|
#include <asm/system.h>
|
|
|
|
#include <linux/config.h>
|
|
|
|
/* aux controller ports */
|
|
#define AUX_INPUT_PORT 0x60 /* Aux device output buffer */
|
|
#define AUX_OUTPUT_PORT 0x60 /* Aux device input buffer */
|
|
#define AUX_COMMAND 0x64 /* Aux device command buffer */
|
|
#define AUX_STATUS 0x64 /* Aux device status reg */
|
|
|
|
/* aux controller status bits */
|
|
#define AUX_OBUF_FULL 0x21 /* output buffer (from device) full */
|
|
#define AUX_IBUF_FULL 0x02 /* input buffer (to device) full */
|
|
|
|
/* aux controller commands */
|
|
#define AUX_CMD_WRITE 0x60 /* value to write to controller */
|
|
#define AUX_MAGIC_WRITE 0xd4 /* value to send aux device data */
|
|
|
|
#define AUX_INTS_ON 0x47 /* enable controller interrupts */
|
|
#define AUX_INTS_OFF 0x65 /* disable controller interrupts */
|
|
|
|
#define AUX_DISABLE 0xa7 /* disable aux */
|
|
#define AUX_ENABLE 0xa8 /* enable aux */
|
|
|
|
/* aux device commands */
|
|
#define AUX_SET_RES 0xe8 /* set resolution */
|
|
#define AUX_SET_SCALE11 0xe6 /* set 1:1 scaling */
|
|
#define AUX_SET_SCALE21 0xe7 /* set 2:1 scaling */
|
|
#define AUX_GET_SCALE 0xe9 /* get scaling factor */
|
|
#define AUX_SET_STREAM 0xea /* set stream mode */
|
|
#define AUX_SET_SAMPLE 0xf3 /* set sample rate */
|
|
#define AUX_ENABLE_DEV 0xf4 /* enable aux device */
|
|
#define AUX_DISABLE_DEV 0xf5 /* disable aux device */
|
|
#define AUX_RESET 0xff /* reset aux device */
|
|
|
|
#define MAX_RETRIES 60 /* some aux operations take long time*/
|
|
#define AUX_IRQ 12
|
|
#define AUX_BUF_SIZE 2048
|
|
|
|
/* 82C710 definitions */
|
|
|
|
#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
|
|
|
|
extern unsigned char aux_device_present;
|
|
extern unsigned char kbd_read_mask; /* from keyboard.c */
|
|
|
|
struct aux_queue {
|
|
unsigned long head;
|
|
unsigned long tail;
|
|
struct wait_queue *proc_list;
|
|
unsigned char buf[AUX_BUF_SIZE];
|
|
};
|
|
|
|
static struct aux_queue *queue;
|
|
static int aux_ready = 0;
|
|
static int aux_busy = 0;
|
|
static int aux_present = 0;
|
|
static int poll_aux_status(void);
|
|
|
|
#ifdef CONFIG_82C710_MOUSE
|
|
static int qp_present = 0;
|
|
static int qp_busy = 0;
|
|
static int qp_data = QP_DATA;
|
|
static int qp_status = QP_STATUS;
|
|
|
|
static int poll_qp_status(void);
|
|
static int probe_qp(void);
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Write to aux device
|
|
*/
|
|
|
|
static void aux_write_dev(int val)
|
|
{
|
|
poll_aux_status();
|
|
outb_p(AUX_MAGIC_WRITE,AUX_COMMAND); /* write magic cookie */
|
|
poll_aux_status();
|
|
outb_p(val,AUX_OUTPUT_PORT); /* write data */
|
|
}
|
|
|
|
/*
|
|
* Write to device & handle returned ack
|
|
*/
|
|
|
|
#if defined INITIALIZE_DEVICE
|
|
static int aux_write_ack(int val)
|
|
{
|
|
int retries = 0;
|
|
|
|
aux_write_dev(val); /* write the value to the device */
|
|
while ((inb(AUX_STATUS) & AUX_OBUF_FULL) != AUX_OBUF_FULL
|
|
&& retries < MAX_RETRIES) { /* wait for ack */
|
|
current->state = TASK_INTERRUPTIBLE;
|
|
current->timeout = jiffies + 5;
|
|
schedule();
|
|
retries++;
|
|
}
|
|
|
|
if ((inb(AUX_STATUS) & AUX_OBUF_FULL) == AUX_OBUF_FULL)
|
|
{
|
|
return (inb(AUX_INPUT_PORT));
|
|
}
|
|
return 0;
|
|
}
|
|
#endif /* INITIALIZE_DEVICE */
|
|
|
|
/*
|
|
* Write aux device command
|
|
*/
|
|
|
|
static void aux_write_cmd(int val)
|
|
{
|
|
poll_aux_status();
|
|
outb_p(AUX_CMD_WRITE,AUX_COMMAND);
|
|
poll_aux_status();
|
|
outb_p(val,AUX_OUTPUT_PORT);
|
|
}
|
|
|
|
|
|
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) & (AUX_BUF_SIZE-1);
|
|
restore_flags(flags);
|
|
return result;
|
|
}
|
|
|
|
|
|
static inline int queue_empty(void)
|
|
{
|
|
return queue->head == queue->tail;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Interrupt from the auxiliary device: a character
|
|
* is waiting in the keyboard/aux controller.
|
|
*/
|
|
|
|
static void aux_interrupt(int cpl)
|
|
{
|
|
int head = queue->head;
|
|
int maxhead = (queue->tail-1) & (AUX_BUF_SIZE-1);
|
|
|
|
queue->buf[head] = inb(AUX_INPUT_PORT);
|
|
if (head != maxhead) {
|
|
head++;
|
|
head &= AUX_BUF_SIZE-1;
|
|
}
|
|
queue->head = head;
|
|
aux_ready = 1;
|
|
wake_up_interruptible(&queue->proc_list);
|
|
}
|
|
|
|
/*
|
|
* Interrupt handler for the 82C710 mouse port. A character
|
|
* is waiting in the 82C710.
|
|
*/
|
|
|
|
#ifdef CONFIG_82C710_MOUSE
|
|
static void qp_interrupt(int cpl)
|
|
{
|
|
int head = queue->head;
|
|
int maxhead = (queue->tail-1) & (AUX_BUF_SIZE-1);
|
|
|
|
queue->buf[head] = inb(qp_data);
|
|
if (head != maxhead) {
|
|
head++;
|
|
head &= AUX_BUF_SIZE-1;
|
|
}
|
|
queue->head = head;
|
|
aux_ready = 1;
|
|
wake_up_interruptible(&queue->proc_list);
|
|
}
|
|
#endif
|
|
|
|
|
|
static void release_aux(struct inode * inode, struct file * file)
|
|
{
|
|
aux_write_dev(AUX_DISABLE_DEV); /* disable aux device */
|
|
aux_write_cmd(AUX_INTS_OFF); /* disable controller ints */
|
|
poll_aux_status();
|
|
outb_p(AUX_DISABLE,AUX_COMMAND); /* Disable Aux device */
|
|
poll_aux_status();
|
|
free_irq(AUX_IRQ);
|
|
aux_busy = 0;
|
|
}
|
|
|
|
#ifdef CONFIG_82C710_MOUSE
|
|
static void release_qp(struct inode * inode, struct file * file)
|
|
{
|
|
unsigned char status;
|
|
|
|
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);
|
|
qp_busy = 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Install interrupt handler.
|
|
* Enable auxiliary device.
|
|
*/
|
|
|
|
static int open_aux(struct inode * inode, struct file * file)
|
|
{
|
|
if (!aux_present)
|
|
return -EINVAL;
|
|
if (aux_busy)
|
|
return -EBUSY;
|
|
if (!poll_aux_status())
|
|
return -EBUSY;
|
|
aux_busy = 1;
|
|
queue->head = queue->tail = 0; /* Flush input queue */
|
|
if (request_irq(AUX_IRQ, aux_interrupt)) {
|
|
aux_busy = 0;
|
|
return -EBUSY;
|
|
}
|
|
poll_aux_status();
|
|
outb_p(AUX_ENABLE,AUX_COMMAND); /* Enable Aux */
|
|
aux_write_dev(AUX_ENABLE_DEV); /* enable aux device */
|
|
aux_write_cmd(AUX_INTS_ON); /* enable controller ints */
|
|
poll_aux_status();
|
|
aux_ready = 0;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_82C710_MOUSE
|
|
/*
|
|
* Install interrupt handler.
|
|
* Enable the device, enable interrupts. Set qp_busy
|
|
* (allow only one opener at a time.)
|
|
*/
|
|
|
|
static int open_qp(struct inode * inode, struct file * file)
|
|
{
|
|
unsigned char status;
|
|
|
|
if (!qp_present)
|
|
return -EINVAL;
|
|
|
|
if (qp_busy)
|
|
return -EBUSY;
|
|
|
|
if (request_irq(QP_IRQ, qp_interrupt))
|
|
return -EBUSY;
|
|
|
|
qp_busy = 1;
|
|
|
|
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");
|
|
return -EBUSY;
|
|
}
|
|
|
|
outb_p(AUX_ENABLE_DEV, qp_data); /* Wake up mouse */
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Write to the aux device.
|
|
*/
|
|
|
|
static int write_aux(struct inode * inode, struct file * file, char * buffer, int count)
|
|
{
|
|
int i = count;
|
|
|
|
while (i--) {
|
|
if (!poll_aux_status())
|
|
return -EIO;
|
|
outb_p(AUX_MAGIC_WRITE,AUX_COMMAND);
|
|
if (!poll_aux_status())
|
|
return -EIO;
|
|
outb_p(get_fs_byte(buffer++),AUX_OUTPUT_PORT);
|
|
}
|
|
inode->i_mtime = CURRENT_TIME;
|
|
return count;
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_82C710_MOUSE
|
|
/*
|
|
* Write to the 82C710 mouse device.
|
|
*/
|
|
|
|
static int write_qp(struct inode * inode, struct file * file, char * buffer, int count)
|
|
{
|
|
int i = count;
|
|
|
|
while (i--) {
|
|
if (!poll_qp_status())
|
|
return -EIO;
|
|
outb_p(get_fs_byte(buffer++), qp_data);
|
|
}
|
|
inode->i_mtime = CURRENT_TIME;
|
|
return count;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Put bytes from input queue to buffer.
|
|
*/
|
|
|
|
static int read_aux(struct inode * inode, struct file * file, char * buffer, int count)
|
|
{
|
|
struct wait_queue wait = { current, NULL };
|
|
int 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() && !(current->signal & ~current->blocked)) {
|
|
schedule();
|
|
goto repeat;
|
|
}
|
|
current->state = TASK_RUNNING;
|
|
remove_wait_queue(&queue->proc_list, &wait);
|
|
}
|
|
while (i > 0 && !queue_empty()) {
|
|
c = get_from_queue();
|
|
put_fs_byte(c, buffer++);
|
|
i--;
|
|
}
|
|
aux_ready = !queue_empty();
|
|
if (count-i) {
|
|
inode->i_atime = CURRENT_TIME;
|
|
return count-i;
|
|
}
|
|
if (current->signal & ~current->blocked)
|
|
return -ERESTARTSYS;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int aux_select(struct inode *inode, struct file *file, int sel_type, select_table * wait)
|
|
{
|
|
if (sel_type != SEL_IN)
|
|
return 0;
|
|
if (aux_ready)
|
|
return 1;
|
|
select_wait(&queue->proc_list, wait);
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct file_operations psaux_fops = {
|
|
NULL, /* seek */
|
|
read_aux,
|
|
write_aux,
|
|
NULL, /* readdir */
|
|
aux_select,
|
|
NULL, /* ioctl */
|
|
NULL, /* mmap */
|
|
open_aux,
|
|
release_aux,
|
|
};
|
|
|
|
|
|
/*
|
|
* Initialize driver. First check for a 82C710 chip; if found
|
|
* forget about the Aux port and use the *_qp functions.
|
|
*/
|
|
|
|
unsigned long psaux_init(unsigned long kmem_start)
|
|
{
|
|
int qp_found = 0;
|
|
|
|
#ifdef CONFIG_82C710_MOUSE
|
|
printk("Probing 82C710 mouse port device.\n");
|
|
if ((qp_found = probe_qp())) {
|
|
printk("82C710 type pointing device detected -- driver installed.\n");
|
|
/* printk("82C710 address = %x (should be 0x310)\n", qp_data); */
|
|
qp_present = 1;
|
|
psaux_fops.write = write_qp;
|
|
psaux_fops.open = open_qp;
|
|
psaux_fops.release = release_qp;
|
|
poll_qp_status();
|
|
} else
|
|
#endif
|
|
if (aux_device_present == 0xaa) {
|
|
printk("PS/2 auxiliary pointing device detected -- driver installed.\n");
|
|
aux_present = 1;
|
|
kbd_read_mask = AUX_OBUF_FULL;
|
|
poll_aux_status();
|
|
} else {
|
|
return kmem_start; /* No mouse at all */
|
|
}
|
|
queue = (struct aux_queue *) kmem_start;
|
|
kmem_start += sizeof (struct aux_queue);
|
|
queue->head = queue->tail = 0;
|
|
queue->proc_list = NULL;
|
|
if (!qp_found) {
|
|
#if defined INITIALIZE_DEVICE
|
|
outb_p(AUX_ENABLE,AUX_COMMAND); /* Enable Aux */
|
|
aux_write_ack(AUX_SET_SAMPLE);
|
|
aux_write_ack(100); /* 100 samples/sec */
|
|
aux_write_ack(AUX_SET_RES);
|
|
aux_write_ack(3); /* 8 counts per mm */
|
|
aux_write_ack(AUX_SET_SCALE21); /* 2:1 scaling */
|
|
poll_aux_status();
|
|
#endif /* INITIALIZE_DEVICE */
|
|
outb_p(AUX_DISABLE,AUX_COMMAND); /* Disable Aux device */
|
|
aux_write_cmd(AUX_INTS_OFF); /* disable controller ints */
|
|
poll_aux_status();
|
|
}
|
|
return kmem_start;
|
|
}
|
|
|
|
static int poll_aux_status(void)
|
|
{
|
|
int retries=0;
|
|
|
|
while ((inb(AUX_STATUS)&0x03) && retries < MAX_RETRIES) {
|
|
if (inb_p(AUX_STATUS) & AUX_OBUF_FULL == AUX_OBUF_FULL)
|
|
inb_p(AUX_INPUT_PORT);
|
|
current->state = TASK_INTERRUPTIBLE;
|
|
current->timeout = jiffies + 5;
|
|
schedule();
|
|
retries++;
|
|
}
|
|
return !(retries==MAX_RETRIES);
|
|
}
|
|
|
|
#ifdef CONFIG_82C710_MOUSE
|
|
/*
|
|
* Wait for device to send output char and flush any input char.
|
|
*/
|
|
|
|
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;
|
|
current->timeout = jiffies + 5;
|
|
schedule();
|
|
retries++;
|
|
}
|
|
return !(retries==MAX_RETRIES);
|
|
}
|
|
|
|
/*
|
|
* 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 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;
|
|
}
|
|
#endif
|