|  | /* | 
|  | * 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. | 
|  | * | 
|  | * Modified by Dean Troyer (troyer@saifr00.cfsat.Honeywell.COM) 03Oct92 | 
|  | *   to perform (some of) the hardware initialization formerly done in | 
|  | *   setup.S by the BIOS | 
|  | * | 
|  | * Modified by Dean Troyer (troyer@saifr00.cfsat.Honeywell.COM) 09Oct92 | 
|  | *   to perform the hardware initialization formerly done in setup.S by | 
|  | *   the BIOS.  Mouse characteristic setup is now included. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #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> | 
|  |  | 
|  | /* 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	0x01		/* 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_SCALE	0xe9		/* set 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	3 | 
|  | #define AUX_IRQ		12 | 
|  | #define AUX_BUF_SIZE	2048 | 
|  |  | 
|  | extern unsigned char aux_device_present; | 
|  |  | 
|  | 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_status(void); | 
|  |  | 
|  | /* | 
|  | * Write to aux device | 
|  | */ | 
|  | static void aux_write_dev(int val) | 
|  | { | 
|  | poll_status(); | 
|  | outb_p(AUX_MAGIC_WRITE,AUX_COMMAND);	/* write magic cookie */ | 
|  | poll_status(); | 
|  | outb_p(val,AUX_OUTPUT_PORT);		/* write data */ | 
|  |  | 
|  | } | 
|  |  | 
|  | #if 0 | 
|  | /* | 
|  | * Write to device & handle returned ack | 
|  | */ | 
|  |  | 
|  | static int aux_write_ack(int val) | 
|  | { | 
|  | aux_write_dev(val);		/* write the value to the device */ | 
|  | while ((inb(AUX_STATUS) & AUX_OBUF_FULL) == 0);  /* wait for ack */ | 
|  | if ((inb(AUX_STATUS) & 0x20) == 0x20) | 
|  | { | 
|  | return (inb(AUX_INPUT_PORT)); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * Write aux device command | 
|  | */ | 
|  |  | 
|  | static void aux_write_cmd(int val) | 
|  | { | 
|  | poll_status(); | 
|  | outb_p(AUX_CMD_WRITE,AUX_COMMAND); | 
|  | poll_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); | 
|  | } | 
|  |  | 
|  |  | 
|  | static void release_aux(struct inode * inode, struct file * file) | 
|  | { | 
|  | poll_status(); | 
|  | aux_write_dev(AUX_DISABLE_DEV);		/* disable aux device */ | 
|  | poll_status(); | 
|  | outb_p(AUX_DISABLE,AUX_COMMAND);      	/* Disable Aux device */ | 
|  | aux_write_cmd(AUX_INTS_OFF);		/* disable controller ints */ | 
|  | free_irq(AUX_IRQ); | 
|  | aux_busy = 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * 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_status()) | 
|  | return -EBUSY; | 
|  | aux_busy = 1; | 
|  | queue->head = queue->tail = 0;  /* Flush input queue */ | 
|  | if (request_irq(AUX_IRQ, aux_interrupt)) | 
|  | return -EBUSY; | 
|  | aux_write_dev(AUX_ENABLE_DEV);		/* enable aux device */ | 
|  | aux_write_cmd(AUX_INTS_ON);		/* enable controller ints */ | 
|  | poll_status(); | 
|  | outb_p(AUX_ENABLE,AUX_COMMAND);		/* Enable Aux */ | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * 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_status()) | 
|  | return -EIO; | 
|  | outb_p(AUX_MAGIC_WRITE,AUX_COMMAND); | 
|  | if (!poll_status()) | 
|  | return -EIO; | 
|  | outb_p(get_fs_byte(buffer++),AUX_OUTPUT_PORT); | 
|  | } | 
|  | inode->i_mtime = CURRENT_TIME; | 
|  | return count; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * 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, | 
|  | }; | 
|  |  | 
|  |  | 
|  | unsigned long psaux_init(unsigned long kmem_start) | 
|  | { | 
|  | if (aux_device_present != 0xaa) { | 
|  | return kmem_start; | 
|  | } | 
|  | printk("PS/2 type pointing device detected and installed.\n"); | 
|  | queue = (struct aux_queue *) kmem_start; | 
|  | kmem_start += sizeof (struct aux_queue); | 
|  | queue->head = queue->tail = 0; | 
|  | queue->proc_list = NULL; | 
|  | aux_present = 1; | 
|  | poll_status(); | 
|  | outb_p(AUX_DISABLE,AUX_COMMAND);      	/* Disable Aux device */ | 
|  | aux_write_cmd(AUX_INTS_OFF);		/* disable controller ints */ | 
|  | return kmem_start; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int poll_status(void) | 
|  | { | 
|  | int retries=0; | 
|  |  | 
|  | while ((inb(AUX_STATUS)&0x03) && retries++ < MAX_RETRIES) { | 
|  | if (inb_p(AUX_STATUS)&0x01) | 
|  | inb_p(AUX_INPUT_PORT); | 
|  | current->state = TASK_INTERRUPTIBLE; | 
|  | current->timeout = jiffies + 5; | 
|  | schedule(); | 
|  | } | 
|  | return !(retries==MAX_RETRIES); | 
|  | } |