linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 2.4] Wolfson AC97 touch screen driver - Input Event interface
@ 2003-12-17 14:44 Liam Girdwood
  2003-12-17 15:30 ` Jeff Garzik
  0 siblings, 1 reply; 3+ messages in thread
From: Liam Girdwood @ 2003-12-17 14:44 UTC (permalink / raw)
  To: Marcelo Tosatti; +Cc: Linux Kernel

[-- Attachment #1: Type: text/plain, Size: 386 bytes --]

This patch adds support for the kernel input event interface to the
Wolfson WM9705/WM9712 touchscreen driver.

Patch is against 2.4.24-pre1

Changes:-

o Added Input event interface (Stanley Cai)
o Improved driver CPU utilisation (Ian Molton)
o Removed ADC source bits from output (me)
o Added coordinate streaming mode on XScale (me) 
o Removed old h3600 TS_EVENT interface (me)

Liam

[-- Attachment #2: wm97xx-driver.diff --]
[-- Type: text/x-patch, Size: 43963 bytes --]

diff -urN a/drivers/sound/ac97_plugin_wm97xx.c b/drivers/sound/ac97_plugin_wm97xx.c
--- a/drivers/sound/ac97_plugin_wm97xx.c	2003-11-28 18:26:20.000000000 +0000
+++ b/drivers/sound/ac97_plugin_wm97xx.c	2003-12-17 14:14:36.000000000 +0000
@@ -5,6 +5,7 @@
  * Copyright 2003 Wolfson Microelectronics PLC.
  * Author: Liam Girdwood
  *         liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
+ *         parts (c) Ian Molton <spyro@f2s.com>
  *
  *  This program is free software; you can redistribute  it and/or modify it
  *  under  the terms of  the GNU General  Public License as published by the
@@ -32,56 +33,82 @@
  *       - supports WM9705, WM9712
  *       - polling mode
  *       - coordinate polling
+ *       - continuous mode
  *       - adjustable rpu/dpp settings
  *       - adjustable pressure current
  *       - adjustable sample settle delay
  *       - 4 and 5 wire touchscreens (5 wire is WM9712 only)
  *       - pen down detection
- *       - battery monitor
- *       - sample AUX adc's
  *       - power management
- *       - direct AC97 IO from userspace (#define WM97XX_TS_DEBUG)
  *
  *  TODO:
- *       - continuous mode
  *       - adjustable sample rate
  *       - AUX adc in coordinate / continous modes
- *	 - Official device identifier or misc device ?
+ *       - Codec GPIO
+ *       - battery monitor
+ *       - sample AUX adc's
  *
  *  Revision history
  *    7th May 2003   Initial version.
  *    6th June 2003  Added non module support and AC97 registration.
  *   18th June 2003  Added AUX adc sampling. 
  *   23rd June 2003  Did some minimal reformatting, fixed a couple of
- *		     locking bugs and noted a race to fix.
+ *     locking bugs and noted a race to fix.
  *   24th June 2003  Added power management and fixed race condition.
+ *   10th July 2003  Changed to a misc device.
+ *   31st July 2003  Moved TS_EVENT and TS_CAL to wm97xx.h
+ *    8th Aug  2003  Added option for read() calling wm97xx_sample_touch()
+ *                   because some ac97_read/ac_97_write call schedule()
+ *    7th Nov  2003  Added Input touch event interface, stanley.cai@intel.com
+ *   13th Nov  2003  Removed h3600 touch interface, added interrupt based
+ *                   pen down notification and implemented continous mode
+ *                   on XScale arch.
+ *    4th Dec  2003  Removed ADC src bits from sample data.
  */
 
 #include <linux/module.h>
 #include <linux/version.h>
 #include <linux/kernel.h>
 #include <linux/init.h>
-#include <linux/fs.h>
+#include <linux/input.h>
 #include <linux/delay.h>
-#include <linux/poll.h>
 #include <linux/string.h>
 #include <linux/proc_fs.h>
-#include <linux/miscdevice.h>
 #include <linux/pm.h>
-#include <linux/wm97xx.h>       /* WM97xx registers and bits */
 #include <asm/uaccess.h>        /* get_user,copy_to_user */
 #include <asm/io.h>
 
-#define TS_NAME "ac97_plugin_wm97xx"
+#include "wm97xx.h"
+
+#define TS_NAME "wm97xx"
 #define TS_MINOR 16
-#define WM_TS_VERSION "0.6"
+#define WM_TS_VERSION "0.10"
 #define AC97_NUM_REG 64
 
+/*
+ * Machine specific set up.
+ * 
+ * This is for targets that can support a PEN down interrupt and/or 
+ * streaming back touch data in an AC97 slot (not slot 1). The 
+ * streaming touch data is read back via the targets AC97 FIFO's 
+ */
+#if defined(CONFIG_ARCH_PXA)
+#include <asm/arch/pxa-regs.h>
+#include <asm/arch/irqs.h>
+#define WM97XX_IRQ 	IRQ_AC97
+#define WM97XX_FIFO_HAS_DATA MISR & (1 << 2)
+#define WM97XX_READ_FIFO MODR & (0xffff)
+#endif
+
+#ifndef WM97XX_IRQ
+#define WM97XX_IRQ 0
+#define WM97XX_FIFO_HAS_DATA 0
+#define WM97XX_READ_FIFO 0
+#endif
 
 /*
  * Debug
  */
- 
 #define PFX TS_NAME
 #define WM97XX_TS_DEBUG 0
 
@@ -97,8 +124,7 @@
 /*
  * Module parameters
  */
-	
-	
+
 /*
  * Set the codec sample mode.
  *
@@ -124,8 +150,8 @@
  */
 MODULE_PARM(mode,"i");
 MODULE_PARM_DESC(mode, "Set WM97XX operation mode");
-static int mode = 0;	
-	
+static int mode = 0;
+
 /*
  * WM9712 - Set internal pull up for pen detect. 
  * 
@@ -137,7 +163,7 @@
  */
 MODULE_PARM(rpu,"i");
 MODULE_PARM_DESC(rpu, "Set internal pull up resitor for pen detect.");
-static int rpu = 0;	
+static int rpu = 0;
 
 /*
  * WM9705 - Pen detect comparator threshold. 
@@ -151,8 +177,8 @@
  */
 MODULE_PARM(pdd,"i");
 MODULE_PARM_DESC(pdd, "Set pen detect comparator threshold");
-static int pdd = 0;	
-	
+static int pdd = 0;
+
 /*
  * Set current used for pressure measurement.
  *
@@ -174,7 +200,7 @@
  */
 MODULE_PARM(five_wire,"i");
 MODULE_PARM_DESC(five_wire, "Set 5 wire touchscreen.");
-static int five_wire = 0;	
+static int five_wire = 0;
 
 /*
  * Set adc sample delay.
@@ -190,104 +216,71 @@
  */
 MODULE_PARM(delay,"i");
 MODULE_PARM_DESC(delay, "Set adc sample delay.");
-static int delay = 4;	
-
+static int delay = 4;
 
-/* +++++++++++++ Lifted from include/linux/h3600_ts.h ++++++++++++++*/
-typedef struct {
-	unsigned short pressure;  // touch pressure
-	unsigned short x;         // calibrated X
-	unsigned short y;         // calibrated Y
-	unsigned short millisecs; // timestamp of this event
-} TS_EVENT;
-
-typedef struct {
-	int xscale;
-	int xtrans;
-	int yscale;
-	int ytrans;
-	int xyswap;
-} TS_CAL;
-
-/* Use 'f' as magic number */
-#define IOC_MAGIC  'f'
-
-#define TS_GET_RATE             _IO(IOC_MAGIC, 8)
-#define TS_SET_RATE             _IO(IOC_MAGIC, 9)
-#define TS_GET_CAL              _IOR(IOC_MAGIC, 10, TS_CAL)
-#define TS_SET_CAL              _IOW(IOC_MAGIC, 11, TS_CAL)
-
-/* +++++++++++++ Done lifted from include/linux/h3600_ts.h +++++++++*/
-
-#define TS_GET_COMP1			_IOR(IOC_MAGIC, 12, short)
-#define TS_GET_COMP2			_IOR(IOC_MAGIC, 13, short)
-#define TS_GET_BMON			_IOR(IOC_MAGIC, 14, short)
-#define TS_GET_WIPER			_IOR(IOC_MAGIC, 15, short)
-
-#ifdef WM97XX_TS_DEBUG
-/* debug get/set ac97 codec register ioctl's */
-#define TS_GET_AC97_REG			_IOR(IOC_MAGIC, 20, short)
-#define TS_SET_AC97_REG			_IOW(IOC_MAGIC, 21, short)
-#define TS_SET_AC97_INDEX		_IOW(IOC_MAGIC, 22, short)
-#endif
-
-#define EVENT_BUFSIZE 128
+/*
+ * Pen down detection
+ * 
+ * Pen down detection can either be via an interrupt (preferred) or
+ * by polling the PDEN bit. This is an option because some systems may
+ * not support the pen down interrupt.
+ *
+ * Set pen_int to 1 to enable interrupt driven pen down detection.
+ */
+MODULE_PARM(pen_int,"i");
+MODULE_PARM_DESC(pen_int, "Set pen down interrupt");
+static int pen_int = 0;	
 
 typedef struct {
-	TS_CAL cal;                       /* Calibration values */
-	TS_EVENT event_buf[EVENT_BUFSIZE];/* The event queue */
-	int nextIn, nextOut;
-	int event_count;
 	int is_wm9712:1;                  /* are we a WM912 or a WM9705 */
 	int is_registered:1;              /* Is the driver AC97 registered */
-	int line_pgal:5;
-	int line_pgar:5;
-	int phone_pga:5;
-	int mic_pgal:5;
-	int mic_pgar:5;
-	int overruns;                     /* event buffer overruns */
 	int adc_errs;                     /* sample read back errors */
-#ifdef WM97XX_TS_DEBUG
-	short ac97_index;
-#endif
-	struct fasync_struct *fasync;     /* asynch notification */
-	struct timer_list acq_timer;      /* Timer for triggering acquisitions */
 	wait_queue_head_t wait;           /* read wait queue */
 	spinlock_t lock;
 	struct ac97_codec *codec;
+#if defined(CONFIG_PROC_FS)	
 	struct proc_dir_entry *wm97xx_ts_ps;
-#ifdef WM97XX_TS_DEBUG
-	struct proc_dir_entry *wm97xx_debug_ts_ps;
 #endif
+#if defined(CONFIG_PM)
 	struct pm_dev * pm;
+	int line_pgal:5;
+	int line_pgar:5;
+	int phone_pga:5;
+	int mic_pgal:5;
+	int mic_pgar:5;
+#endif
+	struct input_dev *idev;
+	struct completion init_exit;
+	struct task_struct *rtask;
+	struct semaphore  sem;
+	int use_count;
+	int restart;
 } wm97xx_ts_t;
 
 static inline void poll_delay (void);
 static int __init wm97xx_ts_init_module(void);
-static int wm97xx_poll_read_adc (wm97xx_ts_t* ts, u16 adcsel, u16* sample);
-static int wm97xx_coord_read_adc (wm97xx_ts_t* ts, u16* x, u16* y, 
-                                  u16* pressure);
 static inline int pendown (wm97xx_ts_t *ts);
-static void wm97xx_acq_timer(unsigned long data);
-static int wm97xx_fasync(int fd, struct file *filp, int mode);
-static int wm97xx_ioctl(struct inode * inode, struct file *filp,
-	                    unsigned int cmd, unsigned long arg);
-static unsigned int wm97xx_poll(struct file * filp, poll_table * wait);
-static ssize_t wm97xx_read(struct file * filp, char * buf, size_t count, 
-	                       loff_t * l);
-static int wm97xx_open(struct inode * inode, struct file * filp);
-static int wm97xx_release(struct inode * inode, struct file * filp);
+static void wm97xx_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+static int wm97xx_poll_read_adc (wm97xx_ts_t* ts, u16 adcsel, u16* sample);
 static void init_wm97xx_phy(void);
-static int adc_get (wm97xx_ts_t *ts, unsigned short *value, int id);
 static int wm97xx_probe(struct ac97_codec *codec, struct ac97_driver *driver);
 static void wm97xx_remove(struct ac97_codec *codec,  struct ac97_driver *driver);
 static void wm97xx_ts_cleanup_module(void);
+static int wm97xx_ts_evt_add(wm97xx_ts_t* ts, u16 pressure, u16 x, u16 y);
+static int wm97xx_ts_evt_release(wm97xx_ts_t* ts);
+
+#if defined(CONFIG_PM)
 static int wm97xx_pm_event(struct pm_dev *dev, pm_request_t rqst, void *data);
 static void wm97xx_suspend(void);
 static void wm97xx_resume(void);
 static void wm9712_pga_save(wm97xx_ts_t* ts);
 static void wm9712_pga_restore(wm97xx_ts_t* ts);
+#endif
 
+/* we only support a single touchscreen */
+static wm97xx_ts_t wm97xx_ts;
+static struct input_dev wm97xx_input;
+	
 /* AC97 registration info */
 static struct ac97_driver wm9705_driver = {
 	codec_id: 0x574D4C05,
@@ -304,38 +297,40 @@
 	probe:	wm97xx_probe,
 	remove: __devexit_p(wm97xx_remove),
 };
-
-/* we only support a single touchscreen */
-static wm97xx_ts_t wm97xx_ts;
-
+	
 /*
  * ADC sample delay times in uS
  */
 static const int delay_table[16] = {
-	21,		// 1 AC97 Link frames
-	42,		// 2
-	84,		// 4
-	167,		// 8
-	333,		// 16
-	667,		// 32
-	1000,		// 48
-	1333,		// 64
-	2000,		// 96
-	2667,		// 128
-	3333,		// 160
-	4000,		// 192
-	4667,		// 224
-	5333,		// 256
-	6000,		// 288
-	0 		// No delay, switch matrix always on
+	21,// 1 AC97 Link frames
+	42,// 2
+	84,// 4
+	167,// 8
+	333,// 16
+	667,// 32
+	1000,// 48
+	1333,// 64
+	2000,// 96
+	2667,// 128
+	3333,// 160
+	4000,// 192
+	4667,// 224
+	5333,// 256
+	6000,// 288
+	0 // No delay, switch matrix always on
 };
 
+// Todo
+static void wm97xx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	info("int recv");
+}
+
 /*
  * Delay after issuing a POLL command.
  *
  * The delay is 3 AC97 link frames + the touchpanel settling delay
  */
-
 static inline void poll_delay(void)
 { 
 	int pdelay = 3 * AC97_LINK_FRAME + delay_table[delay];
@@ -344,63 +339,6 @@
 
 
 /*
- * sample the auxillary ADC's 
- */
-
-static int adc_get(wm97xx_ts_t* ts, unsigned short * value, int id)
-{
-	short adcsel = 0;
-	
-	/* first find out our adcsel flag */
-	if (ts->is_wm9712) {
-		switch (id) {
-			case TS_COMP1:
-				adcsel = WM9712_ADCSEL_COMP1;
-				break;
-			case TS_COMP2:
-				adcsel = WM9712_ADCSEL_COMP2;
-				break;
-			case TS_BMON:
-				adcsel = WM9712_ADCSEL_BMON;
-				break;
-			case TS_WIPER:
-				adcsel = WM9712_ADCSEL_WIPER;
-				break;
-		}
-	} else {
-		switch (id) {
-			case TS_COMP1:
-				adcsel = WM9705_ADCSEL_PCBEEP;
-				break;
-			case TS_COMP2:
-				adcsel = WM9705_ADCSEL_PHONE;
-				break;
-			case TS_BMON:
-				adcsel = WM9705_ADCSEL_BMON;
-				break;
-			case TS_WIPER:
-				adcsel = WM9705_ADCSEL_AUX;
-				break;
-		}
-	}
-	
-	/* now sample the adc */
-	if (mode == 1) {
-		/* coordinate mode - not currently available (TODO) */
-			return 0;
-	}
-	else
-	{
-		/* polling mode */
-		if (!wm97xx_poll_read_adc(ts, adcsel, value))
-			return 0;	
-	}
-	
-	return 1;
-}
-
-
-/*
  * Read a sample from the adc in polling mode.
  */
 static int wm97xx_poll_read_adc (wm97xx_ts_t* ts, u16 adcsel, u16* sample)
@@ -412,7 +350,7 @@
 	dig1 = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER1); 
 	dig1&=0x0fff;
 	ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER1, dig1 | adcsel |
-		WM97XX_POLL); 
+			       WM97XX_POLL); 
 
 	/* wait 3 AC97 time slots + delay for conversion */
 	poll_delay();
@@ -420,7 +358,7 @@
 	/* wait for POLL to go low */
 	while ((ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER1) & WM97XX_POLL) && timeout) { 
 		udelay(AC97_LINK_FRAME);
-		timeout--;	
+		timeout--;
 	}
 	if (timeout > 0)
 		*sample = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER_RD);
@@ -429,7 +367,7 @@
 		err ("adc sample timeout");
 		return 0;
 	}
-	
+  
 	/* check we have correct sample */
 	if ((*sample & 0x7000) != adcsel ) { 
 		err ("adc wrong sample, read %x got %x", adcsel, *sample & 0x7000);
@@ -487,334 +425,91 @@
 }
 
 /*
- * Is the pen down ?
+ * Sample the touchscreen in polling mode
  */
-static inline int pendown (wm97xx_ts_t *ts)
+int wm97xx_poll_touch(wm97xx_ts_t *ts)
 {
-	return ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER_RD) & WM97XX_PEN_DOWN;
-}
-
-/*
- * X,Y coordinates and pressure aquisition function.
- * This function is run by a kernel timer and it's frequency between
- * calls is the touchscreen polling rate;
- */
- 
-static void wm97xx_acq_timer(unsigned long data)
-{
-	wm97xx_ts_t* ts = (wm97xx_ts_t*)data;
-	unsigned long flags;
-	long x,y;
-	TS_EVENT event;
+	u16 x, y, p;
 	
-	spin_lock_irqsave(&ts->lock, flags);
-
-	/* are we still registered ? */
-	if (!ts->is_registered) {
-		spin_unlock_irqrestore(&ts->lock, flags);
-		return; /* we better stop then */
+	if (!wm97xx_poll_read_adc(ts, WM97XX_ADCSEL_X, &x)) {
+		info("x %d\n", x);
+		return -EIO;
 	}
-	
-	/* read coordinates if pen is down */
-	if (!pendown(ts))
-		goto acq_exit;
-	
-	if (mode == 1) {
-		/* coordinate mode */
-		if (!wm97xx_coord_read_adc(ts, (u16*)&x, (u16*)&y, &event.pressure))
-			goto acq_exit;
-	} else
-	{
-		/* polling mode */
-		if (!wm97xx_poll_read_adc(ts, WM97XX_ADCSEL_X, (u16*)&x))
-			goto acq_exit;
-		if (!wm97xx_poll_read_adc(ts, WM97XX_ADCSEL_Y, (u16*)&y))
-			goto acq_exit;
-		
-		/* only read pressure if we have to */
-		if (!five_wire && pil) {
-			if (!wm97xx_poll_read_adc(ts, WM97XX_ADCSEL_PRES, &event.pressure))
-				goto acq_exit;
-		}
-		else
-			event.pressure = 0;
+	if (!wm97xx_poll_read_adc(ts, WM97XX_ADCSEL_Y, &y)) {
+		info("y %d\n", y);
+		return -EIO;
 	}
-	/* timestamp this new event. */
-	event.millisecs = jiffies;
-
-	/* calibrate and remove unwanted bits from samples */
-	event.pressure &= 0x0fff;
-	
-	x &= 0x00000fff;
-	x = ((ts->cal.xscale * x) >> 8) + ts->cal.xtrans;
-	event.x = (u16)x;
-	
-	y &= 0x00000fff;
-	y = ((ts->cal.yscale * y) >> 8) + ts->cal.ytrans;
-	event.y = (u16)y;
-	
-	/* add this event to the event queue */
-	ts->event_buf[ts->nextIn++] = event;
-	if (ts->nextIn == EVENT_BUFSIZE)
-		ts->nextIn = 0;
-	if (ts->event_count < EVENT_BUFSIZE) {
-		ts->event_count++;
-	} else {
-		/* throw out the oldest event */
-		if (++ts->nextOut == EVENT_BUFSIZE) {
-			ts->nextOut = 0;
-			ts->overruns++;
-		}
-	}
-
-	/* async notify */
-	if (ts->fasync)
-		kill_fasync(&ts->fasync, SIGIO, POLL_IN);
-	/* wake up any read call */
-	if (waitqueue_active(&ts->wait))
-		wake_up_interruptible(&ts->wait);
-
-	/* schedule next acquire */
-acq_exit:
-	ts->acq_timer.expires = jiffies + HZ / 100;
-	add_timer(&ts->acq_timer);
-
-	spin_unlock_irqrestore(&ts->lock, flags);
-}
-	
-	
-/* +++++++++++++ File operations ++++++++++++++*/
-
-static int wm97xx_fasync(int fd, struct file *filp, int mode)
-{
-	wm97xx_ts_t* ts = (wm97xx_ts_t*)filp->private_data;
-	return fasync_helper(fd, filp, mode, &ts->fasync);
-}
-
-static int wm97xx_ioctl(struct inode * inode, struct file *filp,
-	     unsigned int cmd, unsigned long arg)
-{
-	unsigned short adc_value;
-#ifdef WM97XX_TS_DEBUG
-	short data;
-#endif	
-	wm97xx_ts_t* ts = (wm97xx_ts_t*)filp->private_data;
-
-	switch(cmd) {
-	case TS_GET_RATE:       /* TODO: what is this? */
-		break;
-	case TS_SET_RATE:       /* TODO: what is this? */
-		break;
-	case TS_GET_CAL:
-		if(copy_to_user((char *)arg, (char *)&ts->cal, sizeof(TS_CAL)))
-			return -EFAULT;
-		break;
-	case TS_SET_CAL:
-		if(copy_from_user((char *)&ts->cal, (char *)arg, sizeof(TS_CAL)))
-			return -EFAULT;
-		break;
-	case TS_GET_COMP1:
-		if (adc_get(ts, &adc_value, TS_COMP1)) {
-			if(copy_to_user((char *)arg, (char *)&adc_value, sizeof(adc_value)))
-				return -EFAULT;
-		}
-		else
-			return -EIO;
-		break;
-	case TS_GET_COMP2:
-		if (adc_get(ts, &adc_value, TS_COMP2)) {
-			if(copy_to_user((char *)arg, (char *)&adc_value, sizeof(adc_value)))
-				return -EFAULT;
-		}
-		else
-			return -EIO;
-		break;
-	case TS_GET_BMON:
-		if (adc_get(ts, &adc_value, TS_BMON)) {
-			if(copy_to_user((char *)arg, (char *)&adc_value, sizeof(adc_value)))
-				return -EFAULT;
-		}
-		else
+	if (pil && !five_wire) { 
+		if (!wm97xx_poll_read_adc(ts, WM97XX_ADCSEL_PRES, &p)) {
+			info("p %d\n", p);
 			return -EIO;
-		break;
-	case TS_GET_WIPER:
-		if (adc_get(ts, &adc_value, TS_WIPER)) {
-			if(copy_to_user((char *)arg, (char *)&adc_value, sizeof(adc_value)))
-				return -EFAULT;
 		}
-		else
-			return -EIO;
-		break;
-#ifdef WM97XX_TS_DEBUG
-		/* debug get/set ac97 codec register ioctl's 
-		 *
-		 * This is direct IO to the codec registers - BE CAREFULL
-		 */
-	case TS_GET_AC97_REG: /* read from ac97 reg (index) */
-		data = ts->codec->codec_read(ts->codec, ts->ac97_index);
-		if(copy_to_user((char *)arg, (char *)&data, sizeof(data)))
-			return -EFAULT;
-		break;
-	case TS_SET_AC97_REG: /* write to ac97 reg (index) */
-		if(copy_from_user((char *)&data, (char *)arg, sizeof(data)))
-			return -EFAULT;
-		ts->codec->codec_write(ts->codec, ts->ac97_index, data);
-		break;
-	case TS_SET_AC97_INDEX: /* set ac97 reg index */
-		if(copy_from_user((char *)&ts->ac97_index, (char *)arg, sizeof(ts->ac97_index)))
-			return -EFAULT;
-		break;
-#endif
-	default:
-		return -EINVAL;
+	} else {
+		p = 0xffff;
 	}
-
-	return 0;
-}
-
-static unsigned int wm97xx_poll(struct file * filp, poll_table * wait)
-{
-	wm97xx_ts_t *ts = (wm97xx_ts_t *)filp->private_data;
-	poll_wait(filp, &ts->wait, wait);
-	if (ts->event_count)
-		return POLLIN | POLLRDNORM;
-	return 0;
+	
+	wm97xx_ts_evt_add(ts, p, x, y);
+	return 1;
 }
 
-static ssize_t wm97xx_read(struct file *filp, char *buf, size_t count, loff_t *l)
+/*
+ * Sample the touchscreen in polling coordinate mode
+ */
+int wm97xx_poll_coord_touch(wm97xx_ts_t *ts)
 {
-	wm97xx_ts_t* ts = (wm97xx_ts_t*)filp->private_data;
-	unsigned long flags;
-	TS_EVENT event;
-	int i;
-
-	/* are we still registered with AC97 layer ? */
-	spin_lock_irqsave(&ts->lock, flags);
-	if (!ts->is_registered) {
-		spin_unlock_irqrestore(&ts->lock, flags);
-		return -ENXIO;
-	}
-	
-	if (ts->event_count == 0) {
-		if (filp->f_flags & O_NONBLOCK)
-			return -EAGAIN;
-		spin_unlock_irqrestore(&ts->lock, flags);
-
-		wait_event_interruptible(ts->wait, ts->event_count != 0);
-		
-		/* are we still registered after sleep ? */
-		spin_lock_irqsave(&ts->lock, flags);
-		if (!ts->is_registered) {
-			spin_unlock_irqrestore(&ts->lock, flags);
-			return -ENXIO;
-		}
-		if (signal_pending(current))
-			return -ERESTARTSYS;
-	}
+	u16 x, y, p;
 	
-	for (i = count; i >= sizeof(TS_EVENT);
-	    i -= sizeof(TS_EVENT), buf += sizeof(TS_EVENT)) {
-		if (ts->event_count == 0)
-			break;
-		spin_lock_irqsave(&ts->lock, flags);
-		event = ts->event_buf[ts->nextOut++];
-		if (ts->nextOut == EVENT_BUFSIZE)
-			ts->nextOut = 0;
-		if (ts->event_count)
-			ts->event_count--;
-		spin_unlock_irqrestore(&ts->lock, flags);
-		if(copy_to_user(buf, &event, sizeof(TS_EVENT)))
-			return i != count  ? count - i : -EFAULT;
-	}
-	return count - i;
+	if (wm97xx_coord_read_adc(ts, &x, &y, &p)) {
+		wm97xx_ts_evt_add(ts, p, x, y);
+		return 1;
+	} else
+		return -EIO;
 }
 
-
-static int wm97xx_open(struct inode * inode, struct file * filp)
+/*
+ * Sample the touchscreen in continous mode
+ */
+int wm97xx_cont_touch(wm97xx_ts_t *ts)
 {
-	wm97xx_ts_t* ts;
-	unsigned long flags;
-	u16 val;
-	int minor = MINOR(inode->i_rdev);
+	u16 x, y;
+	int count = 0;
 	
-	if (minor != TS_MINOR)
-		return -ENODEV;
-	
-	filp->private_data = ts = &wm97xx_ts;
-
-	spin_lock_irqsave(&ts->lock, flags);
-	
-	/* are we registered with AC97 layer ? */
-	if (!ts->is_registered) {
-		spin_unlock_irqrestore(&ts->lock, flags);
-		return -ENXIO;
+	/* currently assuming x is followed by y from the FIFO */
+	while (WM97XX_FIFO_HAS_DATA) {
+		x = WM97XX_READ_FIFO;
+		y = WM97XX_READ_FIFO;
+		wm97xx_ts_evt_add(ts, 0, x, y);
+		count++;
 	}
-	
-	/* start digitiser */
-	val = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER2);
-	ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER2, 
-		val | WM97XX_PRP_DET_DIG);
-	
-	/* flush event queue */
-	ts->nextIn = ts->nextOut = ts->event_count = 0;
-	
-	/* Set up timer. */
-	init_timer(&ts->acq_timer);
-	ts->acq_timer.function = wm97xx_acq_timer;
-	ts->acq_timer.data = (unsigned long)ts;
-	ts->acq_timer.expires = jiffies + HZ / 100;
-	add_timer(&ts->acq_timer);
-
-	spin_unlock_irqrestore(&ts->lock, flags);
-	return 0;
+	return count;
 }
 
-static int wm97xx_release(struct inode * inode, struct file * filp)
+/*
+ * Is the pen down ?
+ */
+static inline int pendown (wm97xx_ts_t *ts)
 {
-	wm97xx_ts_t* ts = (wm97xx_ts_t*)filp->private_data;
-	unsigned long flags;
-	u16 val;
-	
-	wm97xx_fasync(-1, filp, 0);
-	del_timer_sync(&ts->acq_timer);
-
-	spin_lock_irqsave(&ts->lock, flags);
-	
-	/* stop digitiser */
-	val = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER2);
-	ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER2, 
-		val & ~WM97XX_PRP_DET_DIG);
-	
-	spin_unlock_irqrestore(&ts->lock, flags);
-	return 0;
+	if (pen_int) {
+		
+	} else {
+		return ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER_RD) 
+		           & WM97XX_PEN_DOWN;
+	}
 }
 
-static struct file_operations ts_fops = {
-	owner:		THIS_MODULE,
-	read:           wm97xx_read,
-	poll:           wm97xx_poll,
-	ioctl:		wm97xx_ioctl,
-	fasync:         wm97xx_fasync,
-	open:		wm97xx_open,
-	release:	wm97xx_release,
-};
-
-/* +++++++++++++ End File operations ++++++++++++++*/
-
-#ifdef CONFIG_PROC_FS
+#if defined(CONFIG_PROC_FS)
 static int wm97xx_read_proc (char *page, char **start, off_t off,
-		    int count, int *eof, void *data)
+			     int count, int *eof, void *data)
 {
 	int len = 0, prpu;
 	u16 dig1, dig2, digrd, adcsel, adcsrc, slt, prp, rev;
 	unsigned long flags;
 	char srev = ' ';
-	
 	wm97xx_ts_t* ts;
 
 	if ((ts = data) == NULL)
 		return -ENODEV;
-	
+      
 	spin_lock_irqsave(&ts->lock, flags);
 	if (!ts->is_registered) {
 		spin_unlock_irqrestore(&ts->lock, flags);
@@ -828,38 +523,35 @@
 	rev = (ts->codec->codec_read(ts->codec, AC97_WM9712_REV) & 0x000c) >> 2;
 
 	spin_unlock_irqrestore(&ts->lock, flags);
-	
+      
 	adcsel = dig1 & 0x7000;
 	adcsrc = digrd & 0x7000;
 	slt = (dig1 & 0x7) + 5;
 	prp = dig2 & 0xc000;
 	prpu = dig2 & 0x003f;
 
-	/* driver version */
 	len += sprintf (page+len, "Wolfson WM97xx Version %s\n", WM_TS_VERSION);
-	
-	/* what we are using */
+      
 	len += sprintf (page+len, "Using %s", ts->is_wm9712 ? "WM9712" : "WM9705");
 	if (ts->is_wm9712) {
 		switch (rev) {
-			case 0x0:
-				srev = 'A';
+		case 0x0:
+			srev = 'A';
 			break;
-			case 0x1:
-				srev = 'B';
+		case 0x1:
+			srev = 'B';
 			break;
-			case 0x2:
-				srev = 'D';
+		case 0x2:
+			srev = 'D';
 			break;
-			case 0x3:
-				srev = 'E';
+		case 0x3:
+			srev = 'E';
 			break;
 		}
 		len += sprintf (page+len, " silicon rev %c\n",srev);
 	} else
 		len += sprintf (page+len, "\n");
-		
-	/* WM97xx settings */
+      
 	len += sprintf (page+len, "Settings     :\n%s%s%s%s",
 			dig1 & WM97XX_POLL ? " -sampling adc data(poll)\n" : "",
 			adcsel ==  WM97XX_ADCSEL_X ? " -adc set to X coordinate\n" : "",
@@ -867,18 +559,18 @@
 			adcsel ==  WM97XX_ADCSEL_PRES ? " -adc set to pressure\n" : "");
 	if (ts->is_wm9712) {
 		len += sprintf (page+len, "%s%s%s%s", 
-			adcsel ==  WM9712_ADCSEL_COMP1 ? " -adc set to COMP1/AUX1\n" : "",
-			adcsel ==  WM9712_ADCSEL_COMP2 ? " -adc set to COMP2/AUX2\n" : "",
-			adcsel ==  WM9712_ADCSEL_BMON ? " -adc set to BMON\n" : "",
-			adcsel ==  WM9712_ADCSEL_WIPER ? " -adc set to WIPER\n" : "");
-		} else {
+				adcsel ==  WM9712_ADCSEL_COMP1 ? " -adc set to COMP1/AUX1\n" : "",
+				adcsel ==  WM9712_ADCSEL_COMP2 ? " -adc set to COMP2/AUX2\n" : "",
+				adcsel ==  WM9712_ADCSEL_BMON ? " -adc set to BMON\n" : "",
+				adcsel ==  WM9712_ADCSEL_WIPER ? " -adc set to WIPER\n" : "");
+	} else {
 		len += sprintf (page+len, "%s%s%s%s",
-			adcsel ==  WM9705_ADCSEL_PCBEEP ? " -adc set to PCBEEP\n" : "",
-			adcsel ==  WM9705_ADCSEL_PHONE ? " -adc set to PHONE\n" : "",
-			adcsel ==  WM9705_ADCSEL_BMON ? " -adc set to BMON\n" : "",
-			adcsel ==  WM9705_ADCSEL_AUX ? " -adc set to AUX\n" : "");
-		}
-		
+				adcsel ==  WM9705_ADCSEL_PCBEEP ? " -adc set to PCBEEP\n" : "",
+				adcsel ==  WM9705_ADCSEL_PHONE ? " -adc set to PHONE\n" : "",
+				adcsel ==  WM9705_ADCSEL_BMON ? " -adc set to BMON\n" : "",
+				adcsel ==  WM9705_ADCSEL_AUX ? " -adc set to AUX\n" : "");
+	}
+      
 	len += sprintf (page+len, "%s%s%s%s%s%s",
 			dig1 & WM97XX_COO ? " -coordinate sampling\n" : " -individual sampling\n",
 			dig1 & WM97XX_CTC ? " -continuous mode\n" : " -polling mode\n",
@@ -886,11 +578,11 @@
 			prp == WM97XX_PRP_DETW ? " -pen detect enabled, wake up\n" : "",
 			prp == WM97XX_PRP_DET_DIG ? " -pen digitiser and pen detect enabled\n" : "",
 			dig1 & WM97XX_SLEN ? " -read back using slot " : " -read back using AC97\n");
-	
-	if ((dig1 & WM97XX_SLEN) && slt !=12)	
+      
+	if ((dig1 & WM97XX_SLEN) && slt !=12)
 		len += sprintf(page+len, "%d\n", slt);
 	len += sprintf (page+len, " -adc sample delay %d uSecs\n", delay_table[(dig1 & 0x00f0) >> 4]);
-	
+      
 	if (ts->is_wm9712) {
 		if (prpu)
 			len += sprintf (page+len, " -rpu %d Ohms\n", 64000/ prpu);
@@ -900,75 +592,38 @@
 		len += sprintf (page+len, " -pressure current %s uA\n", dig2 & WM9705_PIL ? "400" : "200");
 		len += sprintf (page+len, " -%s impedance for PHONE and PCBEEP\n", dig2 & WM9705_PHIZ ? "high" : "low");
 	}
-	
-	/* WM97xx digitiser read */
+      
 	len += sprintf(page+len, "\nADC data:\n%s%d\n%s%s\n",
-		" -adc value (decimal) : ", digrd & 0x0fff,
-		" -pen ", digrd & 0x8000 ? "Down" : "Up");
+		       " -adc value (decimal) : ", digrd & 0x0fff,
+		       " -pen ", digrd & 0x8000 ? "Down" : "Up");
 	if (ts->is_wm9712) {
 		len += sprintf (page+len, "%s%s%s%s", 
-			adcsrc ==  WM9712_ADCSEL_COMP1 ? " -adc value is COMP1/AUX1\n" : "",
-			adcsrc ==  WM9712_ADCSEL_COMP2 ? " -adc value is COMP2/AUX2\n" : "",
-			adcsrc ==  WM9712_ADCSEL_BMON ? " -adc value is BMON\n" : "",
-			adcsrc ==  WM9712_ADCSEL_WIPER ? " -adc value is WIPER\n" : "");
-		} else {
+				adcsrc ==  WM9712_ADCSEL_COMP1 ? " -adc value is COMP1/AUX1\n" : "",
+				adcsrc ==  WM9712_ADCSEL_COMP2 ? " -adc value is COMP2/AUX2\n" : "",
+				adcsrc ==  WM9712_ADCSEL_BMON ? " -adc value is BMON\n" : "",
+				adcsrc ==  WM9712_ADCSEL_WIPER ? " -adc value is WIPER\n" : "");
+	} else {
 		len += sprintf (page+len, "%s%s%s%s",
-			adcsrc ==  WM9705_ADCSEL_PCBEEP ? " -adc value is PCBEEP\n" : "",
-			adcsrc ==  WM9705_ADCSEL_PHONE ? " -adc value is PHONE\n" : "",
-			adcsrc ==  WM9705_ADCSEL_BMON ? " -adc value is BMON\n" : "",
-			adcsrc ==  WM9705_ADCSEL_AUX ? " -adc value is AUX\n" : "");
-		}
-		
-	/* register dump */
+				adcsrc ==  WM9705_ADCSEL_PCBEEP ? " -adc value is PCBEEP\n" : "",
+				adcsrc ==  WM9705_ADCSEL_PHONE ? " -adc value is PHONE\n" : "",
+				adcsrc ==  WM9705_ADCSEL_BMON ? " -adc value is BMON\n" : "",
+				adcsrc ==  WM9705_ADCSEL_AUX ? " -adc value is AUX\n" : "");
+	}
+      
 	len += sprintf(page+len, "\nRegisters:\n%s%x\n%s%x\n%s%x\n",
-		" -digitiser 1    (0x76) : 0x", dig1,
-		" -digitiser 2    (0x78) : 0x", dig2,
-		" -digitiser read (0x7a) : 0x", digrd);
-		
-	/* errors */
-	len += sprintf(page+len, "\nErrors:\n%s%d\n%s%d\n",
-		" -buffer overruns ", ts->overruns,
-		" -coordinate errors ", ts->adc_errs);
-		
-	return len;
-}
+		       " -digitiser 1    (0x76) : 0x", dig1,
+		       " -digitiser 2    (0x78) : 0x", dig2,
+		       " -digitiser read (0x7a) : 0x", digrd);
+      
+	len += sprintf(page+len, "\nErrors:\n%s%d\n",
+		       " -coordinate errors ", ts->adc_errs);
 
-#ifdef WM97XX_TS_DEBUG
-/* dump all the AC97 register space */
-static int wm_debug_read_proc (char *page, char **start, off_t off,
-		    int count, int *eof, void *data)
-{
-	int len = 0, i;
-	unsigned long flags;
-	wm97xx_ts_t* ts;
-	u16 reg[AC97_NUM_REG];
-
-	if ((ts = data) == NULL)
-		return -ENODEV;
-
-	spin_lock_irqsave(&ts->lock, flags);
-	if (!ts->is_registered) {
-		spin_unlock_irqrestore(&ts->lock, flags);
-		len += sprintf (page+len, "Not registered\n");
-		return len;
-	}
-	
-	for (i=0; i < AC97_NUM_REG; i++) {
-		reg[i] = ts->codec->codec_read(ts->codec, i * 2);
-	}
-	spin_unlock_irqrestore(&ts->lock, flags);
-	
-	for (i=0; i < AC97_NUM_REG; i++) {
-		len += sprintf (page+len, "0x%2.2x : 0x%4.4x\n",i * 2, reg[i]);
-	}
-		
 	return len;
 }
-#endif
 
 #endif
 
-#ifdef CONFIG_PM
+#if defined(CONFIG_PM)
 /* WM97xx Power Management
  * The WM9712 has extra powerdown states that are controlled in 
  * seperate registers from the AC97 power management.
@@ -996,40 +651,39 @@
 	wm97xx_ts_t* ts = &wm97xx_ts;
 	u16 reg;
 	unsigned long flags;
-	
-	/* are we registered */
+      
 	spin_lock_irqsave(&ts->lock, flags);
 	if (!ts->is_registered) {
 		spin_unlock_irqrestore(&ts->lock, flags);
 		return;
 	}
-	
+      
 	/* wm9705 does not have extra PM */
 	if (!ts->is_wm9712) {
 		spin_unlock_irqrestore(&ts->lock, flags);
 		return;
 	}
-	
+      
 	/* save and mute the PGA's */
 	wm9712_pga_save(ts);
-	
+      
 	reg = ts->codec->codec_read(ts->codec, AC97_PHONE_VOL);
 	ts->codec->codec_write(ts->codec, AC97_PHONE_VOL, reg | 0x001f);
-	
+      
 	reg = ts->codec->codec_read(ts->codec, AC97_MIC_VOL);
 	ts->codec->codec_write(ts->codec, AC97_MIC_VOL, reg | 0x1f1f);
-	
+      
 	reg = ts->codec->codec_read(ts->codec, AC97_LINEIN_VOL);
 	ts->codec->codec_write(ts->codec, AC97_LINEIN_VOL, reg | 0x1f1f);
-	
+      
 	/* power down, dont disable the AC link */
-	ts->codec->codec_write(ts->codec, AC97_WM9712_POWER, WM9712_PD(14) | WM9712_PD(13) |
-							WM9712_PD(12) | WM9712_PD(11) | WM9712_PD(10) |                    
-							WM9712_PD(9) | WM9712_PD(8) | WM9712_PD(7) |
-							WM9712_PD(6) | WM9712_PD(5) | WM9712_PD(4) |
-							WM9712_PD(3) | WM9712_PD(2) | WM9712_PD(1) |
-							WM9712_PD(0));
-	
+	ts->codec->codec_write(ts->codec, AC97_WM9712_POWER, WM9712_PD(14) | 
+			       WM9712_PD(13) | WM9712_PD(12) | WM9712_PD(11) | 
+			       WM9712_PD(10) | WM9712_PD(9) | WM9712_PD(8) | 
+			       WM9712_PD(7) | WM9712_PD(6) | WM9712_PD(5) | 
+			       WM9712_PD(4) | WM9712_PD(3) | WM9712_PD(2) | 
+			       WM9712_PD(1) | WM9712_PD(0));
+      
 	spin_unlock_irqrestore(&ts->lock, flags);
 }
 
@@ -1040,14 +694,14 @@
 {
 	wm97xx_ts_t* ts = &wm97xx_ts;
 	unsigned long flags;
-	
+      
 	/* are we registered */
 	spin_lock_irqsave(&ts->lock, flags);
 	if (!ts->is_registered) {
 		spin_unlock_irqrestore(&ts->lock, flags);
 		return;
 	}
-	
+      
 	/* wm9705 does not have extra PM */
 	if (!ts->is_wm9712) {
 		spin_unlock_irqrestore(&ts->lock, flags);
@@ -1056,14 +710,13 @@
 
 	/* power up */
 	ts->codec->codec_write(ts->codec, AC97_WM9712_POWER, 0x0);
-	
+      
 	/* restore PGA state */
 	wm9712_pga_restore(ts);
-	
+      
 	spin_unlock_irqrestore(&ts->lock, flags);
 }
 
-
 /* save state of wm9712 PGA's */
 static void wm9712_pga_save(wm97xx_ts_t* ts)
 {
@@ -1078,10 +731,10 @@
 static void wm9712_pga_restore(wm97xx_ts_t* ts)
 {
 	u16 reg;
-	
+      
 	reg = ts->codec->codec_read(ts->codec, AC97_PHONE_VOL);
 	ts->codec->codec_write(ts->codec, AC97_PHONE_VOL, reg | ts->phone_pga);
-	
+      
 	reg = ts->codec->codec_read(ts->codec, AC97_LINEIN_VOL);
 	ts->codec->codec_write(ts->codec, AC97_LINEIN_VOL, reg | ts->line_pgar | (ts->line_pgal << 8));
 
@@ -1094,19 +747,18 @@
 /*
  * set up the physical settings of the device 
  */
-
 static void init_wm97xx_phy(void)
 {
 	u16 dig1, dig2, aux, vid;
 	wm97xx_ts_t *ts = &wm97xx_ts;
 
 	/* default values */
-	dig1 = WM97XX_DELAY(4) | WM97XX_SLT(6);
+	dig1 = WM97XX_DELAY(4) | WM97XX_SLT(5);
 	if (ts->is_wm9712)
 		dig2 = WM9712_RPU(1);
 	else {
 		dig2 = 0x0;
-		
+	
 		/* 
 		 * mute VIDEO and AUX as they share X and Y touchscreen 
 		 * inputs on the WM9705 
@@ -1116,21 +768,21 @@
 			info("muting AUX mixer as it shares X touchscreen coordinate");
 			ts->codec->codec_write(ts->codec, AC97_AUX_VOL, 0x8000 | aux);
 		}
-		
+	
 		vid = ts->codec->codec_read(ts->codec, AC97_VIDEO_VOL);
 		if (!(vid & 0x8000)) {
 			info("muting VIDEO mixer as it shares Y touchscreen coordinate");
 			ts->codec->codec_write(ts->codec, AC97_VIDEO_VOL, 0x8000 | vid);
 		}
 	}
-	
+      
 	/* WM9712 rpu */
 	if (ts->is_wm9712 && rpu) {
 		dig2 &= 0xffc0;
 		dig2 |= WM9712_RPU(rpu);
 		info("setting pen detect pull-up to %d Ohms",64000 / rpu);
 	}
-	
+      
 	/* touchpanel pressure */
 	if  (pil == 2) {
 		if (ts->is_wm9712)
@@ -1140,13 +792,13 @@
 		info("setting pressure measurement current to 400uA.");
 	} else if (pil) 
 		info ("setting pressure measurement current to 200uA.");
-	
+      
 	/* WM9712 five wire */
 	if (ts->is_wm9712 && five_wire) {
 		dig2 |= WM9712_45W;
 		info("setting 5-wire touchscreen mode.");
-	}		
-	
+	}
+      
 	/* sample settling delay */
 	if (delay!=4) {
 		if (delay < 0 || delay > 15) {
@@ -1157,19 +809,29 @@
 		dig1 |= WM97XX_DELAY(delay);
 		info("setting adc sample delay to %d u Secs.", delay_table[delay]);
 	}
-	
+      
 	/* coordinate mode */
 	if (mode == 1) {
 		dig1 |= WM97XX_COO;
 		info("using coordinate mode");
-	}		
+	}
 	
+	/* continous mode */
+	if (mode == 2) {
+		dig1 |= WM97XX_CTC | WM97XX_COO | WM97XX_CM_RATE_375 | WM97XX_SLEN | WM97XX_SLT(5);
+		info("using continous mode");
+		if (ts->is_wm9712)
+			dig2 |= WM9712_PDEN;
+		else
+			dig2 |= WM9705_PDEN;
+	}
+      
 	/* WM9705 pdd */
 	if (pdd && !ts->is_wm9712) {
 		dig2 |= (pdd & 0x000f);
 		info("setting pdd to Vmid/%d", 1 - (pdd & 0x000f));
 	}
-	
+      
 	ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER1, dig1);
 	ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER2, dig2); 
 }
@@ -1182,27 +844,27 @@
 
 static int wm97xx_probe(struct ac97_codec *codec, struct ac97_driver *driver)
 {
-	 unsigned long flags;
+	unsigned long flags;
 	u16 id1, id2;
 	wm97xx_ts_t *ts = &wm97xx_ts;
-		
+  
 	spin_lock_irqsave(&ts->lock, flags);
-	
+      
 	/* we only support 1 touchscreen at the moment */
 	if (ts->is_registered) {
 		spin_unlock_irqrestore(&ts->lock, flags);
 		return -1;
 	}
-	
+      
 	/* 
 	 * We can only use a WM9705 or WM9712 that has been *first* initialised
 	 * by the AC97 audio driver. This is because we have to use the audio 
-	 * drivers codec read() and write() functions to sample the touchscreen	
-	 *	
+	 * drivers codec read() and write() functions to sample the touchscreen
+	 *
 	 * If an initialsed WM97xx is found then get the codec read and write 
-	 * functions.		 
+	 * functions. 
 	 */
-	
+      
 	/* test for a WM9712 or a WM9705 */
 	id1 = codec->codec_read(codec, AC97_VENDOR_ID1);
 	id2 = codec->codec_read(codec, AC97_VENDOR_ID2);
@@ -1210,131 +872,276 @@
 		ts->is_wm9712 = 1;
 		info("registered a WM9712");
 	} else if (id1 == WM97XX_ID1 && id2 == WM9705_ID2) {
-		    ts->is_wm9712 = 0;
-		    info("registered a WM9705");
+		ts->is_wm9712 = 0;
+		info("registered a WM9705");
 	} else {
 		err("could not find a WM97xx codec. Found a 0x%4x:0x%4x instead",
 		    id1, id2);
 		spin_unlock_irqrestore(&ts->lock, flags);
 		return -1;
 	}
-	
+      
 	/* set up AC97 codec interface */
 	ts->codec = codec;
 	codec->driver_private = (void*)&ts;
-	codec->codec_unregister = 0;
-	
+      
 	/* set up physical characteristics */
 	init_wm97xx_phy();
-		
+      
 	ts->is_registered = 1;
 	spin_unlock_irqrestore(&ts->lock, flags);
 	return 0;
 }
 
-/* this is called by the audio driver when ac97_codec is unloaded */
-
+/*
+ * Called by ac97_codec when it is unloaded. 
+ */
 static void wm97xx_remove(struct ac97_codec *codec, struct ac97_driver *driver)
 {
 	unsigned long flags;
 	u16 dig1, dig2;
 	wm97xx_ts_t *ts = codec->driver_private;
 	
+    dbg("Unloading codec\n"); 
 	spin_lock_irqsave(&ts->lock, flags);
-			
+      
 	/* check that are registered */
 	if (!ts->is_registered) {
 		err("double unregister");
 		spin_unlock_irqrestore(&ts->lock, flags);
 		return;
 	}
-	
+      
 	ts->is_registered = 0;
 	wake_up_interruptible(&ts->wait); /* So we see its gone */
-	
+      
 	/* restore default digitiser values */
 	dig1 = WM97XX_DELAY(4) | WM97XX_SLT(6);
 	if (ts->is_wm9712)
 		dig2 = WM9712_RPU(1);
 	else 
 		dig2 = 0x0;
-		
+      
 	codec->codec_write(codec, AC97_WM97XX_DIGITISER1, dig1);
 	codec->codec_write(codec, AC97_WM97XX_DIGITISER2, dig2); 
 	ts->codec = NULL;
-		
+      
 	spin_unlock_irqrestore(&ts->lock, flags);
 }
 
-static struct miscdevice wm97xx_misc = { 
-	minor:	TS_MINOR,
-	name:	"touchscreen/wm97xx",
-	fops:	&ts_fops,
-};
+/*
+ * add a touch event
+ */
+static int wm97xx_ts_evt_add(wm97xx_ts_t* ts, u16 pressure, u16 x, u16 y)
+{
+	/* add event and remove adc src bits */
+	input_report_abs(ts->idev, ABS_X, x & 0xfff);
+	input_report_abs(ts->idev, ABS_Y, y & 0xfff);
+	input_report_abs(ts->idev, ABS_PRESSURE, pressure & 0xfff);
+	return 0;
+}
+
+/*
+ * add a pen up event
+ */
+static int wm97xx_ts_evt_release(wm97xx_ts_t* ts)
+{
+//	dbg("release the stylus.\n");
+	input_report_abs(ts->idev, ABS_PRESSURE, 0);
+	return 0;
+}
+
+/*
+ * The touchscreen sample reader thread
+ */
+static int wm97xx_thread(void *_ts)
+{
+	wm97xx_ts_t *ts = (wm97xx_ts_t *)_ts;
+	struct task_struct *tsk = current;
+	int valid;
+
+	ts->rtask = tsk;
+
+	/* set up thread context */
+	daemonize();
+	reparent_to_init();
+	strcpy(tsk->comm, "ktsd");
+	tsk->tty = NULL;
+
+	/* we will die when we receive SIGKILL */
+	spin_lock_irq(&tsk->sigmask_lock);
+	siginitsetinv(&tsk->blocked, sigmask(SIGKILL));
+	recalc_sigpending(tsk);
+	spin_unlock_irq(&tsk->sigmask_lock);
+
+	/* init is complete */
+	complete(&ts->init_exit);
+	valid = 0;
+
+	/* touch reader loop */
+	for (;;) {
+		ts->restart = 0;
+		if( signal_pending(tsk))
+			break;
+
+		if(pendown(ts)) {
+			switch (mode) {
+				case 0:
+					wm97xx_poll_touch(ts);
+					valid = 1;
+					break;
+				case 1:
+					wm97xx_poll_coord_touch(ts);
+					valid = 1;
+					break;
+				case 2:
+					wm97xx_cont_touch(ts);
+					valid = 1;
+					break;
+			}
+		} else {
+			if (valid) {
+				valid = 0;
+				wm97xx_ts_evt_release(ts);
+			}
+		}
+
+		set_task_state(tsk, TASK_INTERRUPTIBLE);
+		schedule_timeout(HZ/100);
+	}
+	ts->rtask = NULL;
+	complete_and_exit(&ts->init_exit, 0);
+
+	return 0;
+}
+
+/*
+ * Start the touchscreen thread and 
+ * the touch digitiser.
+ */
+static int wm97xx_ts_input_open(struct input_dev *idev)
+{
+	wm97xx_ts_t *ts = (wm97xx_ts_t *) &wm97xx_ts;
+	u32 flags;
+	int ret, val;
+
+	if ( ts->use_count++ != 0 ) 
+		return 0;
+
+	/* start touchscreen thread */
+	ts->idev = idev;
+    init_completion(&ts->init_exit);
+    ret = kernel_thread(wm97xx_thread, ts, 0);
+    if (ret >= 0)
+		wait_for_completion(&ts->init_exit); 
+
+	spin_lock_irqsave( &ts->lock, flags );
+
+    /* start digitiser */
+    val = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER2);
+    ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER2,
+                               val | WM97XX_PRP_DET_DIG);
+
+	spin_unlock_irqrestore( &ts->lock, flags );
+
+	return 0;
+}
+
+/*
+ * Kill the touchscreen thread and stop
+ * the touch digitiser.
+ */
+static void wm97xx_ts_input_close(struct input_dev *idev)
+{
+	wm97xx_ts_t *ts = (wm97xx_ts_t *) &wm97xx_ts;
+	u32 flags;
+	int val;
+
+	if (--ts->use_count == 0) {
+		/* kill thread */
+		if (ts->rtask) {
+			send_sig(SIGKILL, ts->rtask, 1);
+			wait_for_completion(&ts->init_exit);
+		}
+
+		down(&ts->sem);
+		spin_lock_irqsave(&ts->lock, flags);
+
+		/* stop digitiser */
+		val = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER2);
+		ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER2,
+				       val & ~WM97XX_PRP_DET_DIG);
+		
+		spin_unlock_irqrestore(&ts->lock, flags);
+		up(&ts->sem);
+	}
+}
 
 static int __init wm97xx_ts_init_module(void)
 {
 	wm97xx_ts_t* ts = &wm97xx_ts;
 	int ret;
 	char proc_str[64];
-	
+      
 	info("Wolfson WM9705/WM9712 Touchscreen Controller");
 	info("Version %s  liam.girdwood@wolfsonmicro.com", WM_TS_VERSION);
-	
+      
 	memset(ts, 0, sizeof(wm97xx_ts_t));
-	
-	/* register our misc device */
-	if ((ret = misc_register(&wm97xx_misc)) < 0) {
-		err("can't register misc device");
-		return ret;
-	}
-	
+
+	/* tell input system what we events we accept and register */
+	wm97xx_input.name = "wm97xx touchscreen";
+	wm97xx_input.open = wm97xx_ts_input_open;
+	wm97xx_input.close = wm97xx_ts_input_close;
+	__set_bit(EV_ABS, wm97xx_input.evbit);
+	__set_bit(ABS_X, wm97xx_input.absbit);
+	__set_bit(ABS_Y, wm97xx_input.absbit);
+	__set_bit(ABS_PRESSURE, wm97xx_input.absbit);
+	input_register_device(&wm97xx_input);
+   
 	init_waitqueue_head(&ts->wait);
 	spin_lock_init(&ts->lock);
+	init_MUTEX(&ts->sem);
+     
 	
-	// initial calibration values
-	ts->cal.xscale = 256;
-	ts->cal.xtrans = 0;
-	ts->cal.yscale = 256;
-	ts->cal.ytrans = 0;
-	
-	/* reset error counters */
-	ts->overruns = 0;
-	ts->adc_errs = 0;
+	/* register pendown interrupt handler */
+	if (pen_int) {
+#if defined(WM97XX_IRQ)
+		if ((ret = request_irq(WM97XX_IRQ, wm97xx_interrupt, 0, "AC97-touchscreen", &ts)) != 0) {
+			err("can't get irq %d falling back to pendown polling\n", WM97XX_IRQ);
+			pen_int = 0;
+		}
+#else
+		err("touchscreen interrupt not supported/implemented on this arch\n");
+		pen_int = 0;		
+#endif
+	}
 	
 	/* register with the AC97 layer */
 	ac97_register_driver(&wm9705_driver);
 	ac97_register_driver(&wm9712_driver);
-	
-#ifdef CONFIG_PROC_FS
-	/* register proc interface */
+      
+#if defined(CONFIG_PROC_FS)
 	sprintf(proc_str, "driver/%s", TS_NAME);
 	if ((ts->wm97xx_ts_ps = create_proc_read_entry (proc_str, 0, NULL,
-					     wm97xx_read_proc, ts)) == 0)
+							wm97xx_read_proc, ts)) == 0)
 		err("could not register proc interface /proc/%s", proc_str);
-#ifdef WM97XX_TS_DEBUG
-	if ((ts->wm97xx_debug_ts_ps = create_proc_read_entry ("driver/ac97_registers",
-		0, NULL,wm_debug_read_proc, ts)) == 0)
-		err("could not register proc interface /proc/driver/ac97_registers");
-#endif
 #endif
-#ifdef CONFIG_PM
+#if defined(CONFIG_PM)
 	if ((ts->pm = pm_register(PM_UNKNOWN_DEV, PM_SYS_UNKNOWN, wm97xx_pm_event)) == 0)
 		err("could not register with power management");
 #endif
+	
 	return 0;
 }
 
 static void wm97xx_ts_cleanup_module(void)
 {
-	wm97xx_ts_t* ts = &wm97xx_ts;
-
-#ifdef CONFIG_PM
-	pm_unregister (ts->pm);
+#if defined(CONFIG_PM)
+	pm_unregister (wm97xx_ts.pm);
 #endif
-	ac97_unregister_driver(&wm9705_driver);
-	ac97_unregister_driver(&wm9712_driver);
-	misc_deregister(&wm97xx_misc);
+	if (pen_int)
+		free_irq(WM97XX_IRQ, NULL);
+	input_unregister_device(&wm97xx_input);
 }
 
 /* Module information */
@@ -1355,8 +1162,8 @@
 		return 0;
 
 	/* parse the options and check for out of range values */
-	for(this_opt=strtok(options, ",");
-	    this_opt; this_opt=strtok(NULL, ",")) {
+	for(this_opt=strsep(options, ",");
+	    this_opt; this_opt=strsep(NULL, ",")) {
 		if (!strncmp(this_opt, "pil:", 4)) {
 			this_opt+=4;
 			pil = simple_strtol(this_opt, NULL, 0);
@@ -1399,6 +1206,13 @@
 				mode = 0;
 			continue;
 		}
+		if (!strncmp(this_opt, "pen_int:", 8)) {
+			this_opt+=8;
+			pen_int = simple_strtol(this_opt, NULL, 0);
+			if (pen_int < 0 || pen_int > 1)
+				pen_int = 0;
+			continue;
+		}
 	}
 	return 1;
 }

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH 2.4] Wolfson AC97 touch screen driver - Input Event interface
  2003-12-17 14:44 [PATCH 2.4] Wolfson AC97 touch screen driver - Input Event interface Liam Girdwood
@ 2003-12-17 15:30 ` Jeff Garzik
  2003-12-17 15:56   ` Liam Girdwood
  0 siblings, 1 reply; 3+ messages in thread
From: Jeff Garzik @ 2003-12-17 15:30 UTC (permalink / raw)
  To: Liam Girdwood; +Cc: Marcelo Tosatti, Linux Kernel, Alan Cox

Liam Girdwood wrote:
> This patch adds support for the kernel input event interface to the
> Wolfson WM9705/WM9712 touchscreen driver.
> 
> Patch is against 2.4.24-pre1
> 
> Changes:-
> 
> o Added Input event interface (Stanley Cai)
> o Improved driver CPU utilisation (Ian Molton)
> o Removed ADC source bits from output (me)
> o Added coordinate streaming mode on XScale (me) 
> o Removed old h3600 TS_EVENT interface (me)

Patches look mostly OK...  a few comments though.



> +/*
> + * Pen down detection
> + * 
> + * Pen down detection can either be via an interrupt (preferred) or
> + * by polling the PDEN bit. This is an option because some systems may
> + * not support the pen down interrupt.
> + *
> + * Set pen_int to 1 to enable interrupt driven pen down detection.
> + */
> +MODULE_PARM(pen_int,"i");
> +MODULE_PARM_DESC(pen_int, "Set pen down interrupt");
> +static int pen_int = 0;	

I wouldn't call this "detection"  ;-)

This module option is awful for users.  How do users know what to do? 
(rhetorical question...)   IMO this should be selected automatically on 
a per-chipset basis.  I'm sure there is _some_ way to notice 
programmatically, for instance selected by chipset or selecting by 
sending a test interrupt.

XScale PDA users just aren't going to know this kind of information off 
the top of their heads...


>  /*
>   * ADC sample delay times in uS
>   */
>  static const int delay_table[16] = {
> -	21,		// 1 AC97 Link frames
> -	42,		// 2
> -	84,		// 4
> -	167,		// 8
> -	333,		// 16
> -	667,		// 32
> -	1000,		// 48
> -	1333,		// 64
> -	2000,		// 96
> -	2667,		// 128
> -	3333,		// 160
> -	4000,		// 192
> -	4667,		// 224
> -	5333,		// 256
> -	6000,		// 288
> -	0 		// No delay, switch matrix always on
> +	21,// 1 AC97 Link frames
> +	42,// 2
> +	84,// 4
> +	167,// 8
> +	333,// 16
> +	667,// 32
> +	1000,// 48
> +	1333,// 64
> +	2000,// 96
> +	2667,// 128
> +	3333,// 160
> +	4000,// 192
> +	4667,// 224
> +	5333,// 256
> +	6000,// 288
> +	0 // No delay, switch matrix always on
>  };

is this a mistake?  You just made the code more difficult to read.

Also in general it would be nice to include whitespace (non-content) 
changes in a separate patch, for easier reviewing of your _real_ changes.


> +// Todo
> +static void wm97xx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
> +{
> +	info("int recv");
> +}

hmmmm.

This change doesn't seem terribly appropriate for the 2.4.x stable series...



> +/*
> + * add a touch event
> + */
> +static int wm97xx_ts_evt_add(wm97xx_ts_t* ts, u16 pressure, u16 x, u16 y)
> +{
> +	/* add event and remove adc src bits */
> +	input_report_abs(ts->idev, ABS_X, x & 0xfff);
> +	input_report_abs(ts->idev, ABS_Y, y & 0xfff);
> +	input_report_abs(ts->idev, ABS_PRESSURE, pressure & 0xfff);
> +	return 0;
> +}
> +
> +/*
> + * add a pen up event
> + */
> +static int wm97xx_ts_evt_release(wm97xx_ts_t* ts)
> +{
> +//	dbg("release the stylus.\n");
> +	input_report_abs(ts->idev, ABS_PRESSURE, 0);
> +	return 0;
> +}
> +
> +/*
> + * The touchscreen sample reader thread
> + */
> +static int wm97xx_thread(void *_ts)
> +{
> +	wm97xx_ts_t *ts = (wm97xx_ts_t *)_ts;
> +	struct task_struct *tsk = current;
> +	int valid;
> +
> +	ts->rtask = tsk;
> +
> +	/* set up thread context */
> +	daemonize();
> +	reparent_to_init();
> +	strcpy(tsk->comm, "ktsd");
> +	tsk->tty = NULL;
> +
> +	/* we will die when we receive SIGKILL */
> +	spin_lock_irq(&tsk->sigmask_lock);
> +	siginitsetinv(&tsk->blocked, sigmask(SIGKILL));
> +	recalc_sigpending(tsk);
> +	spin_unlock_irq(&tsk->sigmask_lock);
> +
> +	/* init is complete */
> +	complete(&ts->init_exit);
> +	valid = 0;

(bookmark... will mention a bit lower down...)

> +	/* touch reader loop */
> +	for (;;) {
> +		ts->restart = 0;
> +		if( signal_pending(tsk))
> +			break;
> +
> +		if(pendown(ts)) {
> +			switch (mode) {
> +				case 0:
> +					wm97xx_poll_touch(ts);
> +					valid = 1;
> +					break;
> +				case 1:
> +					wm97xx_poll_coord_touch(ts);
> +					valid = 1;
> +					break;
> +				case 2:
> +					wm97xx_cont_touch(ts);
> +					valid = 1;
> +					break;
> +			}
> +		} else {
> +			if (valid) {
> +				valid = 0;
> +				wm97xx_ts_evt_release(ts);
> +			}
> +		}
> +
> +		set_task_state(tsk, TASK_INTERRUPTIBLE);
> +		schedule_timeout(HZ/100);

Hopefully this won't run on a machine with HZ < 100 ;-)


> +	}
> +	ts->rtask = NULL;
> +	complete_and_exit(&ts->init_exit, 0);

You don't want to use the _same_ completion to signal (a) thread init is 
complete and (b) thread has exited.  That's definitely an error.

For (a), just use a semaphore.  The completion should only be used once, 
at the end of the thread where you call complete_and_exit().



> +/*
> + * Start the touchscreen thread and 
> + * the touch digitiser.
> + */
> +static int wm97xx_ts_input_open(struct input_dev *idev)
> +{
> +	wm97xx_ts_t *ts = (wm97xx_ts_t *) &wm97xx_ts;
> +	u32 flags;
> +	int ret, val;
> +
> +	if ( ts->use_count++ != 0 ) 
> +		return 0;
> +
> +	/* start touchscreen thread */
> +	ts->idev = idev;
> +    init_completion(&ts->init_exit);
> +    ret = kernel_thread(wm97xx_thread, ts, 0);
> +    if (ret >= 0)
> +		wait_for_completion(&ts->init_exit); 
> +
> +	spin_lock_irqsave( &ts->lock, flags );
> +
> +    /* start digitiser */
> +    val = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER2);
> +    ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER2,
> +                               val | WM97XX_PRP_DET_DIG);
> +
> +	spin_unlock_irqrestore( &ts->lock, flags );
> +
> +	return 0;
> +}
> +
> +/*
> + * Kill the touchscreen thread and stop
> + * the touch digitiser.
> + */
> +static void wm97xx_ts_input_close(struct input_dev *idev)
> +{
> +	wm97xx_ts_t *ts = (wm97xx_ts_t *) &wm97xx_ts;
> +	u32 flags;
> +	int val;
> +
> +	if (--ts->use_count == 0) {
> +		/* kill thread */
> +		if (ts->rtask) {
> +			send_sig(SIGKILL, ts->rtask, 1);
> +			wait_for_completion(&ts->init_exit);
> +		}
> +
> +		down(&ts->sem);
> +		spin_lock_irqsave(&ts->lock, flags);
> +
> +		/* stop digitiser */
> +		val = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER2);
> +		ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER2,
> +				       val & ~WM97XX_PRP_DET_DIG);
> +		
> +		spin_unlock_irqrestore(&ts->lock, flags);
> +		up(&ts->sem);
> +	}
> +}

What lock or semaphore protects ts->use_count ?  That seems clearly 
non-atomic and potentially racy to me.


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH 2.4] Wolfson AC97 touch screen driver - Input Event interface
  2003-12-17 15:30 ` Jeff Garzik
@ 2003-12-17 15:56   ` Liam Girdwood
  0 siblings, 0 replies; 3+ messages in thread
From: Liam Girdwood @ 2003-12-17 15:56 UTC (permalink / raw)
  To: Jeff Garzik; +Cc: Marcelo Tosatti, Linux Kernel, Alan Cox

On Wed, 2003-12-17 at 15:30, Jeff Garzik wrote:

> > +/*
> > + * Pen down detection
> > + * 
> > + * Pen down detection can either be via an interrupt (preferred) or
> > + * by polling the PDEN bit. This is an option because some systems may
> > + * not support the pen down interrupt.
> > + *
> > + * Set pen_int to 1 to enable interrupt driven pen down detection.
> > + */
> > +MODULE_PARM(pen_int,"i");
> > +MODULE_PARM_DESC(pen_int, "Set pen down interrupt");
> > +static int pen_int = 0;	
> 
> I wouldn't call this "detection"  ;-)
> 
> This module option is awful for users.  How do users know what to do? 
> (rhetorical question...)   IMO this should be selected automatically on 
> a per-chipset basis.  I'm sure there is _some_ way to notice 
> programmatically, for instance selected by chipset or selecting by 
> sending a test interrupt.
> 
> XScale PDA users just aren't going to know this kind of information off 
> the top of their heads...
> 
> 

Yeah, your right. I was aiming this configuration option at developers.
Currently the driver can either poll the pen down status (expensive, but
platform agnostic) or it could be interrupted (if supported on the
platform) when the pen up / down status changed.

I'll make this automatically be determined at build time. 

> >  /*
> >   * ADC sample delay times in uS
> >   */
> >  static const int delay_table[16] = {
> > -	21,		// 1 AC97 Link frames
> > -	42,		// 2
> > -	84,		// 4
> > -	167,		// 8
> > -	333,		// 16
> > -	667,		// 32
> > -	1000,		// 48
> > -	1333,		// 64
> > -	2000,		// 96
> > -	2667,		// 128
> > -	3333,		// 160
> > -	4000,		// 192
> > -	4667,		// 224
> > -	5333,		// 256
> > -	6000,		// 288
> > -	0 		// No delay, switch matrix always on
> > +	21,// 1 AC97 Link frames
> > +	42,// 2
> > +	84,// 4
> > +	167,// 8
> > +	333,// 16
> > +	667,// 32
> > +	1000,// 48
> > +	1333,// 64
> > +	2000,// 96
> > +	2667,// 128
> > +	3333,// 160
> > +	4000,// 192
> > +	4667,// 224
> > +	5333,// 256
> > +	6000,// 288
> > +	0 // No delay, switch matrix always on
> >  };
> 
> is this a mistake?  You just made the code more difficult to read.

Yep, I seem to have some unintentional whitespace/tab changes.


> > +// Todo
> > +static void wm97xx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
> > +{
> > +	info("int recv");
> > +}
> 
> hmmmm.
> 
> This change doesn't seem terribly appropriate for the 2.4.x stable series...
> 
> 

Work in progress. Will remove for 2.4.x

I'll address the remaining issues create another patch.

Liam


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2003-12-17 15:56 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2003-12-17 14:44 [PATCH 2.4] Wolfson AC97 touch screen driver - Input Event interface Liam Girdwood
2003-12-17 15:30 ` Jeff Garzik
2003-12-17 15:56   ` Liam Girdwood

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).