blob: 36534874246227271e719a85df4fa5713240c349 [file] [log] [blame]
/*
* Linux driver for the PC110 pad
*/
/**
* DOC: PC110 Digitizer Hardware
*
* The pad provides triples of data. The first byte has
* 0x80=bit 8 X, 0x01=bit 7 X, 0x08=bit 8 Y, 0x01=still down
* The second byte is bits 0-6 X
* The third is bits 0-6 Y
*
* This is read internally and used to synthesize a stream of
* triples in the form expected from a PS/2 device. Specialist
* applications can choose to obtain the pad data in other formats
* including a debugging mode.
*
* It would be good to add a joystick driver mode to this pad so
* that doom and other game playing are better. One possible approach
* would be to deactive the mouse mode while the joystick port is opened.
*/
/*
* History
*
* 0.0 1997-05-16 Alan Cox <alan@redhat.com> - Pad reader
* 0.1 1997-05-19 Robin O'Leary <robin@acm.org> - PS/2 emulation
* 0.2 1997-06-03 Robin O'Leary <robin@acm.org> - tap gesture
* 0.3 1997-06-27 Alan Cox <alan@redhat.com> - 2.1 commit
* 0.4 1997-11-09 Alan Cox <alan@redhat.com> - Single Unix VFS API changes
* 0.5 2000-02-10 Alan Cox <alan@redhat.com> - 2.3.x cleanup, documentation
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/ptrace.h>
#include <linux/poll.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/smp_lock.h>
#include <linux/init.h>
#include <asm/signal.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/semaphore.h>
#include <linux/spinlock.h>
#include <asm/uaccess.h>
#include "pc110pad.h"
static struct pc110pad_params default_params = {
mode: PC110PAD_PS2,
bounce_interval: 50 MS,
tap_interval: 200 MS,
irq: 10,
io: 0x15E0,
};
static struct pc110pad_params current_params;
/* driver/filesystem interface management */
static wait_queue_head_t queue;
static struct fasync_struct *asyncptr;
static int active; /* number of concurrent open()s */
static struct semaphore reader_lock;
/**
* wake_readers:
*
* Take care of letting any waiting processes know that
* now would be a good time to do a read(). Called
* whenever a state transition occurs, real or synthetic. Also
* issue any SIGIO's to programs that use SIGIO on mice (eg
* Executor)
*/
static void wake_readers(void)
{
wake_up_interruptible(&queue);
kill_fasync(&asyncptr, SIGIO, POLL_IN);
}
/*****************************************************************************/
/*
* Deal with the messy business of synthesizing button tap and drag
* events.
*
* Exports:
* notify_pad_up_down()
* Must be called whenever debounced pad up/down state changes.
* button_pending
* Flag is set whenever read_button() has new values
* to return.
* read_button()
* Obtains the current synthetic mouse button state.
*/
/*
* These keep track of up/down transitions needed to generate the
* synthetic mouse button events. While recent_transition is set,
* up/down events cause transition_count to increment. tap_timer
* turns off the recent_transition flag and may cause some synthetic
* up/down mouse events to be created by incrementing synthesize_tap.
*/
static int button_pending;
static int recent_transition;
static int transition_count;
static int synthesize_tap;
static void tap_timeout(unsigned long data);
static struct timer_list tap_timer = { function: tap_timeout };
/**
* tap_timeout:
* @data: Unused
*
* This callback goes off a short time after an up/down transition;
* before it goes off, transitions will be considered part of a
* single PS/2 event and counted in transition_count. Once the
* timeout occurs the recent_transition flag is cleared and
* any synthetic mouse up/down events are generated.
*/
static void tap_timeout(unsigned long data)
{
if(!recent_transition)
{
printk(KERN_ERR "pc110pad: tap_timeout but no recent transition!\n");
}
if( transition_count==2 || transition_count==4 || transition_count==6 )
{
synthesize_tap+=transition_count;
button_pending = 1;
wake_readers();
}
recent_transition=0;
}
/**
* notify_pad_up_down:
*
* Called by the raw pad read routines when a (debounced) up/down
* transition is detected.
*/
void notify_pad_up_down(void)
{
if(recent_transition)
{
transition_count++;
}
else
{
transition_count=1;
recent_transition=1;
}
mod_timer(&tap_timer, jiffies + current_params.tap_interval);
/* changes to transition_count can cause reported button to change */
button_pending = 1;
wake_readers();
}
/**
* read_button:
* @b: pointer to the button status.
*
* The actual button state depends on what we are seeing. We have to check
* for the tap gesture and also for dragging.
*/
static void read_button(int *b)
{
if(synthesize_tap)
{
*b=--synthesize_tap & 1;
}
else
{
*b=(!recent_transition && transition_count==3); /* drag */
}
button_pending=(synthesize_tap>0);
}
/*****************************************************************************/
/*
* Read pad absolute co-ordinates and debounced up/down state.
*
* Exports:
* pad_irq()
* Function to be called whenever the pad signals
* that it has new data available.
* read_raw_pad()
* Returns the most current pad state.
* xy_pending
* Flag is set whenever read_raw_pad() has new values
* to return.
* Imports:
* wake_readers()
* Called when movement occurs.
* notify_pad_up_down()
* Called when debounced up/down status changes.
*/
/*
* These are up/down state and absolute co-ords read directly from pad
*/
static int raw_data[3];
static int raw_data_count;
static int raw_x, raw_y; /* most recent absolute co-ords read */
static int raw_down; /* raw up/down state */
static int debounced_down; /* up/down state after debounce processing */
static enum { NO_BOUNCE, JUST_GONE_UP, JUST_GONE_DOWN } bounce=NO_BOUNCE;
/* set just after an up/down transition */
static int xy_pending; /* set if new data have not yet been read */
/*
* Timer goes off a short while after an up/down transition and copies
* the value of raw_down to debounced_down.
*/
static void bounce_timeout(unsigned long data);
static struct timer_list bounce_timer = { function: bounce_timeout };
/**
* bounce_timeout:
* @data: Unused
*
* No further up/down transitions happened within the
* bounce period, so treat this as a genuine transition.
*/
static void bounce_timeout(unsigned long data)
{
switch(bounce)
{
case NO_BOUNCE:
{
/*
* Strange; the timer callback should only go off if
* we were expecting to do bounce processing!
*/
printk(KERN_WARNING "pc110pad, bounce_timeout: bounce flag not set!\n");
break;
}
case JUST_GONE_UP:
{
/*
* The last up we spotted really was an up, so set
* debounced state the same as raw state.
*/
bounce=NO_BOUNCE;
if(debounced_down==raw_down)
{
printk(KERN_WARNING "pc110pad, bounce_timeout: raw already debounced!\n");
}
debounced_down=raw_down;
notify_pad_up_down();
break;
}
case JUST_GONE_DOWN:
{
/*
* We don't debounce down events, but we still time
* out soon after one occurs so we can avoid the (x,y)
* skittering that sometimes happens.
*/
bounce=NO_BOUNCE;
break;
}
}
}
/**
* pad_irq:
* @irq: Interrupt number
* @ptr: Unused
* @regs: Unused
*
* Callback when pad's irq goes off; copies values in to raw_* globals;
* initiates debounce processing. This isn't SMP safe however there are
* no SMP machines with a PC110 touchpad on them.
*/
static void pad_irq(int irq, void *ptr, struct pt_regs *regs)
{
/* Obtain byte from pad and prime for next byte */
{
int value=inb_p(current_params.io);
int handshake=inb_p(current_params.io+2);
outb_p(handshake | 1, current_params.io+2);
outb_p(handshake &~1, current_params.io+2);
inb_p(0x64);
raw_data[raw_data_count++]=value;
}
if(raw_data_count==3)
{
int new_down=raw_data[0]&0x01;
int new_x=raw_data[1];
int new_y=raw_data[2];
if(raw_data[0]&0x10) new_x+=128;
if(raw_data[0]&0x80) new_x+=256;
if(raw_data[0]&0x08) new_y+=128;
if( (raw_x!=new_x) || (raw_y!=new_y) )
{
raw_x=new_x;
raw_y=new_y;
xy_pending=1;
}
if(new_down != raw_down)
{
/* Down state has changed. raw_down always holds
* the most recently observed state.
*/
raw_down=new_down;
/* Forget any earlier bounce processing */
if(bounce)
{
del_timer(&bounce_timer);
bounce=NO_BOUNCE;
}
if(new_down)
{
if(debounced_down)
{
/* pad gone down, but we were reporting
* it down anyway because we suspected
* (correctly) that the last up was just
* a bounce
*/
}
else
{
bounce=JUST_GONE_DOWN;
mod_timer(&bounce_timer,
jiffies+current_params.bounce_interval);
/* start new stroke/tap */
debounced_down=new_down;
notify_pad_up_down();
}
}
else /* just gone up */
{
if(recent_transition)
{
/* early bounces are probably part of
* a multi-tap gesture, so process
* immediately
*/
debounced_down=new_down;
notify_pad_up_down();
}
else
{
/* don't trust it yet */
bounce=JUST_GONE_UP;
mod_timer(&bounce_timer,
jiffies+current_params.bounce_interval);
}
}
}
wake_readers();
raw_data_count=0;
}
}
/**
* read_raw_pad:
* @down: set if the pen is down
* @debounced: set if the debounced pen position is down
* @x: X position
* @y: Y position
*
* Retrieve the data saved by the interrupt handler and indicate we
* have no more pending XY to do.
*
* FIXME: We should switch to a spinlock for this.
*/
static void read_raw_pad(int *down, int *debounced, int *x, int *y)
{
disable_irq(current_params.irq);
{
*down=raw_down;
*debounced=debounced_down;
*x=raw_x;
*y=raw_y;
xy_pending = 0;
}
enable_irq(current_params.irq);
}
/*****************************************************************************/
/*
* Filesystem interface
*/
/*
* Read returns byte triples, so we need to keep track of
* how much of a triple has been read. This is shared across
* all processes which have this device open---not that anything
* will make much sense in that case.
*/
static int read_bytes[3];
static int read_byte_count;
/**
* sample_raw:
* @d: sample buffer
*
* Retrieve a triple of sample data.
*/
static void sample_raw(int d[3])
{
d[0]=raw_data[0];
d[1]=raw_data[1];
d[2]=raw_data[2];
}
/**
* sample_rare:
* @d: sample buffer
*
* Retrieve a triple of sample data and sanitize it. We do the needed
* scaling and masking to get the current status.
*/
static void sample_rare(int d[3])
{
int thisd, thisdd, thisx, thisy;
read_raw_pad(&thisd, &thisdd, &thisx, &thisy);
d[0]=(thisd?0x80:0)
| (thisx/256)<<4
| (thisdd?0x08:0)
| (thisy/256)
;
d[1]=thisx%256;
d[2]=thisy%256;
}
/**
* sample_debug:
* @d: sample buffer
*
* Retrieve a triple of sample data and mix it up with the state
* information in the gesture parser. Not useful for normal users but
* handy when debugging
*/
static void sample_debug(int d[3])
{
int thisd, thisdd, thisx, thisy;
int b;
unsigned long flags;
save_flags(flags);
cli();
read_raw_pad(&thisd, &thisdd, &thisx, &thisy);
d[0]=(thisd?0x80:0) | (thisdd?0x40:0) | bounce;
d[1]=(recent_transition?0x80:0)+transition_count;
read_button(&b);
d[2]=(synthesize_tap<<4) | (b?0x01:0);
restore_flags(flags);
}
/**
* sample_ps2:
* @d: sample buffer
*
* Retrieve a triple of sample data and turn the debounced tap and
* stroke information into what appears to be a PS/2 mouse. This means
* the PC110 pad needs no funny application side support.
*/
static void sample_ps2(int d[3])
{
static int lastx, lasty, lastd;
int thisd, thisdd, thisx, thisy;
int dx, dy, b;
/*
* Obtain the current mouse parameters and limit as appropriate for
* the return data format. Interrupts are only disabled while
* obtaining the parameters, NOT during the puts_fs_byte() calls,
* so paging in put_user() does not affect mouse tracking.
*/
read_raw_pad(&thisd, &thisdd, &thisx, &thisy);
read_button(&b);
/* Now compare with previous readings. Note that we use the
* raw down flag rather than the debounced one.
*/
if( (thisd && !lastd) /* new stroke */
|| (bounce!=NO_BOUNCE) )
{
dx=0;
dy=0;
}
else
{
dx = (thisx-lastx);
dy = -(thisy-lasty);
}
lastx=thisx;
lasty=thisy;
lastd=thisd;
/*
d[0]= ((dy<0)?0x20:0)
| ((dx<0)?0x10:0)
| 0x08
| (b? 0x01:0x00)
;
*/
d[0]= ((dy<0)?0x20:0)
| ((dx<0)?0x10:0)
| (b? 0x00:0x08)
;
d[1]=dx;
d[2]=dy;
}
/**
* fasync_pad:
* @fd: file number for the file
* @filp: file handle
* @on: 1 to add, 0 to remove a notifier
*
* Update the queue of asynchronous event notifiers. We can use the
* same helper the mice do and that does almost everything we need.
*/
static int fasync_pad(int fd, struct file *filp, int on)
{
int retval;
retval = fasync_helper(fd, filp, on, &asyncptr);
if (retval < 0)
return retval;
return 0;
}
/**
* close_pad:
* @inode: inode of pad
* @file: file handle to pad
*
* Close access to the pad. We turn the pad power off if this is the
* last user of the pad. I've not actually measured the power draw but
* the DOS driver is careful to do this so we follow suit.
*/
static int close_pad(struct inode * inode, struct file * file)
{
lock_kernel();
fasync_pad(-1, file, 0);
if (!--active)
outb(0x30, current_params.io+2); /* switch off digitiser */
unlock_kernel();
return 0;
}
/**
* open_pad:
* @inode: inode of pad
* @file: file handle to pad
*
* Open access to the pad. We turn the pad off first (we turned it off
* on close but if this is the first open after a crash the state is
* indeterminate). The device has a small fifo so we empty that before
* we kick it back into action.
*/
static int open_pad(struct inode * inode, struct file * file)
{
unsigned long flags;
if (active++)
return 0;
save_flags(flags);
cli();
outb(0x30, current_params.io+2); /* switch off digitiser */
pad_irq(0,0,0); /* read to flush any pending bytes */
pad_irq(0,0,0); /* read to flush any pending bytes */
pad_irq(0,0,0); /* read to flush any pending bytes */
outb(0x38, current_params.io+2); /* switch on digitiser */
current_params = default_params;
raw_data_count=0; /* re-sync input byte counter */
read_byte_count=0; /* re-sync output byte counter */
button_pending=0;
recent_transition=0;
transition_count=0;
synthesize_tap=0;
del_timer(&bounce_timer);
del_timer(&tap_timer);
restore_flags(flags);
return 0;
}
/**
* write_pad:
* @file: File handle to the pad
* @buffer: Unused
* @count: Unused
* @ppos: Unused
*
* Writes are disallowed. A true PS/2 mouse lets you write stuff. Everyone
* seems happy with this and not faking the write modes.
*/
static ssize_t write_pad(struct file * file, const char * buffer, size_t count, loff_t *ppos)
{
return -EINVAL;
}
/*
* new_sample:
* @d: sample buffer
*
* Fetch a new sample according the current mouse mode the pad is
* using.
*/
void new_sample(int d[3])
{
switch(current_params.mode)
{
case PC110PAD_RAW: sample_raw(d); break;
case PC110PAD_RARE: sample_rare(d); break;
case PC110PAD_DEBUG: sample_debug(d); break;
case PC110PAD_PS2: sample_ps2(d); break;
}
}
/**
* read_pad:
* @file: File handle to pad
* @buffer: Target for the mouse data
* @count: Buffer length
* @ppos: Offset (unused)
*
* Read data from the pad. We use the reader_lock to avoid mess when there are
* two readers. This shouldnt be happening anyway but we play safe.
*/
static ssize_t read_pad(struct file * file, char * buffer, size_t count, loff_t *ppos)
{
int r;
down(&reader_lock);
for(r=0; r<count; r++)
{
if(!read_byte_count)
new_sample(read_bytes);
if(put_user(read_bytes[read_byte_count], buffer+r))
{
r = -EFAULT;
break;
}
read_byte_count = (read_byte_count+1)%3;
}
up(&reader_lock);
return r;
}
/**
* pad_poll:
* @file: File of the pad device
* @wait: Poll table
*
* The pad is ready to read if there is a button or any position change
* pending in the queue. The reading and interrupt routines maintain the
* required state for us and do needed wakeups.
*/
static unsigned int pad_poll(struct file *file, poll_table * wait)
{
poll_wait(file, &queue, wait);
if(button_pending || xy_pending)
return POLLIN | POLLRDNORM;
return 0;
}
/**
* pad_ioctl;
* @inode: Inode of the pad
* @file: File handle to the pad
* @cmd: Ioctl command
* @arg: Argument pointer
*
* The PC110 pad supports two ioctls both of which use the pc110pad_params
* structure. GETP queries the current pad status. SETP changes the pad
* configuration. Changing configuration during normal mouse operations
* may give momentarily odd results as things like tap gesture state
* may be lost.
*/
static int pad_ioctl(struct inode *inode, struct file * file,
unsigned int cmd, unsigned long arg)
{
struct pc110pad_params new;
if (!inode)
return -EINVAL;
switch (cmd) {
case PC110PADIOCGETP:
new = current_params;
if(copy_to_user((void *)arg, &new, sizeof(new)))
return -EFAULT;
return 0;
case PC110PADIOCSETP:
if(copy_from_user(&new, (void *)arg, sizeof(new)))
return -EFAULT;
if( (new.mode<PC110PAD_RAW)
|| (new.mode>PC110PAD_PS2)
|| (new.bounce_interval<0)
|| (new.tap_interval<0)
)
return -EINVAL;
current_params.mode = new.mode;
current_params.bounce_interval = new.bounce_interval;
current_params.tap_interval = new.tap_interval;
return 0;
}
return -ENOTTY;
}
static struct file_operations pad_fops = {
owner: THIS_MODULE,
read: read_pad,
write: write_pad,
poll: pad_poll,
ioctl: pad_ioctl,
open: open_pad,
release: close_pad,
fasync: fasync_pad,
};
static struct miscdevice pc110_pad = {
minor: PC110PAD_MINOR,
name: "pc110 pad",
fops: &pad_fops,
};
/**
* pc110pad_init_driver:
*
* We configure the pad with the default parameters (that is PS/2
* emulation mode. We then claim the needed I/O and interrupt resources.
* Finally as a matter of paranoia we turn the pad off until we are
* asked to open it by an application.
*/
static char banner[] __initdata = KERN_INFO "PC110 digitizer pad at 0x%X, irq %d.\n";
static int __init pc110pad_init_driver(void)
{
init_MUTEX(&reader_lock);
current_params = default_params;
if (request_irq(current_params.irq, pad_irq, 0, "pc110pad", 0)) {
printk(KERN_ERR "pc110pad: Unable to get IRQ.\n");
return -EBUSY;
}
if (!request_region(current_params.io, 4, "pc110pad")) {
printk(KERN_ERR "pc110pad: I/O area in use.\n");
free_irq(current_params.irq,0);
return -EBUSY;
}
init_waitqueue_head(&queue);
printk(banner, current_params.io, current_params.irq);
misc_register(&pc110_pad);
outb(0x30, current_params.io+2); /* switch off digitiser */
return 0;
}
/*
* pc110pad_exit_driver:
*
* Free the resources we acquired when the module was loaded. We also
* turn the pad off to be sure we don't leave it using power.
*/
static void __exit pc110pad_exit_driver(void)
{
outb(0x30, current_params.io+2); /* switch off digitiser */
if (current_params.irq)
free_irq(current_params.irq, 0);
current_params.irq = 0;
release_region(current_params.io, 4);
misc_deregister(&pc110_pad);
}
module_init(pc110pad_init_driver);
module_exit(pc110pad_exit_driver);
MODULE_AUTHOR("Alan Cox, Robin O'Leary");
MODULE_DESCRIPTION("Driver for the touchpad on the IBM PC110 palmtop");
MODULE_LICENSE("GPL");
EXPORT_NO_SYMBOLS;