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

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