patch-2.1.45 linux/drivers/char/pc110pad.c
Next file: linux/drivers/char/pc110pad.h
Previous file: linux/drivers/char/n_tty.c
Back to the patch index
Back to the overall index
- Lines: 691
- Date:
Wed Jul 16 19:22:50 1997
- Orig file:
v2.1.44/linux/drivers/char/pc110pad.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.1.44/linux/drivers/char/pc110pad.c linux/drivers/char/pc110pad.c
@@ -0,0 +1,690 @@
+/*
+ * 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 <alan@cymru.net> - 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@cymru.net> - 2.1 commit
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/busmouse.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 <asm/signal.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+#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 inode *inode, struct file *filp, int on)
+{
+ int retval;
+
+ retval = fasync_helper(inode, 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(inode, file, 0);
+ if (--active)
+ return;
+ 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 long write_pad(struct inode * inode, struct file * file, const char * buffer, unsigned long count)
+{
+ 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 long read_pad(struct inode * inode, struct file * file, char * buffer, unsigned long count)
+{
+ int r;
+
+ for(r=0; r<count; r++)
+ {
+ if(!read_byte_count)
+ new_sample(read_bytes);
+ if(put_user(read_bytes[read_byte_count], buffer+r))
+ return -EFAULT;
+ read_byte_count = (read_byte_count+1)%3;
+ }
+ return r;
+}
+
+
+/*
+ * select for pad input
+ */
+
+static unsigned int pad_poll(struct file *file, poll_table * wait)
+{
+ poll_wait(&queue, wait);
+ if(button_pending || xy_pending)
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+
+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 -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
+};
+
+
+static 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
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov