/* * 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 - Pad reader * 0.1 1997-05-19 Robin O'Leary - PS/2 emulation * 0.2 1997-06-03 Robin O'Leary - tap gesture * 0.3 1997-06-27 Alan Cox - 2.1 commit * 0.4 1997-11-09 Alan Cox - Single Unix VFS API changes * 0.5 2000-02-10 Alan Cox - 2.3.x cleanup, documentation */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; rPC110PAD_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;