/* * Linux driver for the PC110 pad * * 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. * * 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pc110pad.h" static struct pc110pad_params default_params = { PC110PAD_PS2, /* read mode */ 50 MS, /* bounce interval */ 200 MS, /* tap interval */ 10, /* IRQ */ 0x15E0, /* I/O port */ }; static struct pc110pad_params current_params; /* driver/filesystem interface management */ static struct wait_queue *queue; static struct fasync_struct *asyncptr; static int active=0; /* number of concurrent open()s */ /* * Utility to reset a timer to go off some time in the future. */ static void set_timer_callback(struct timer_list *timer, int ticks) { del_timer(timer); timer->expires = jiffies+ticks; add_timer(timer); } /* * 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. */ static void wake_readers(void) { wake_up_interruptible(&queue); if(asyncptr) kill_fasync(asyncptr, SIGIO); } /*****************************************************************************/ /* * 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=0; static int recent_transition=0; static int transition_count=0; static int synthesize_tap=0; static void tap_timeout(unsigned long data); static struct timer_list tap_timer = { NULL, NULL, 0, 0, tap_timeout }; /* * 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("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; } /* * 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; } set_timer_callback(&tap_timer, current_params.tap_interval); /* changes to transition_count can cause reported button to change */ button_pending = 1; wake_readers(); } 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=0; static int raw_x=0, raw_y=0; /* most recent absolute co-ords read */ static int raw_down=0; /* raw up/down state */ static int debounced_down=0; /* 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=0; /* 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 = { NULL, NULL, 0, 0, bounce_timeout }; static void bounce_timeout(unsigned long data) { /* * No further up/down transitions happened within the * bounce period, so treat this as a genuine transition. */ switch(bounce) { case NO_BOUNCE: { /* * Strange; the timer callback should only go off if * we were expecting to do bounce processing! */ printk("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("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; } } } /* * Callback when pad's irq goes off; copies values in to raw_* globals; * initiates debounce processing. */ 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; set_timer_callback(&bounce_timer, 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; set_timer_callback(&bounce_timer, current_params.bounce_interval); } } } wake_readers(); raw_data_count=0; } } 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=0; static void sample_raw(int d[3]) { d[0]=raw_data[0]; d[1]=raw_data[1]; d[2]=raw_data[2]; } 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; } static void sample_debug(int d[3]) { int thisd, thisdd, thisx, thisy; int b; 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); sti(); } 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; } static int fasync_pad(struct file *filp, int on) { int retval; retval = fasync_helper(filp, on, &asyncptr); if (retval < 0) return retval; return 0; } /* * close access to the pad */ static int close_pad(struct inode * inode, struct file * file) { fasync_pad(file, 0); if (--active) return 0; outb(0x30, current_params.io+2); /* switch off digitiser */ MOD_DEC_USE_COUNT; return 0; } /* * open access to the pad */ static int open_pad(struct inode * inode, struct file * file) { if (active++) return 0; MOD_INC_USE_COUNT; 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); sti(); return 0; } /* * writes are disallowed */ static ssize_t write_pad(struct file * file, const char * buffer, size_t count, loff_t *ppos) { return -EINVAL; } 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 data. Currently never blocks. */ static ssize_t read_pad(struct file * file, char * buffer, size_t count, loff_t *ppos) { int r; 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 -ENOIOCTLCMD; } static struct file_operations pad_fops = { NULL, /* pad_seek */ read_pad, write_pad, NULL, /* pad_readdir */ pad_poll, pad_ioctl, NULL, /* pad_mmap */ open_pad, close_pad, NULL, /* fsync */ fasync_pad, NULL, /* check_media_change */ NULL, /* revalidate */ NULL /* lock */ }; static struct miscdevice pc110_pad = { PC110PAD_MINOR, "pc110 pad", &pad_fops }; int pc110pad_init(void) { current_params = default_params; if(request_irq(current_params.irq, pad_irq, 0, "pc110pad", 0)) { printk("pc110pad: Unable to get IRQ.\n"); return -EBUSY; } if(check_region(current_params.io, 4)) { printk("pc110pad: I/O area in use.\n"); free_irq(current_params.irq,0); return -EBUSY; } request_region(current_params.io, 4, "pc110pad"); printk("PC110 digitizer pad at 0x%X, irq %d.\n", current_params.io,current_params.irq); misc_register(&pc110_pad); outb(0x30, current_params.io+2); /* switch off digitiser */ return 0; } #ifdef MODULE static void pc110pad_unload(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); } int init_module(void) { return pc110pad_init(); } void cleanup_module(void) { pc110pad_unload(); } #endif