patch-2.4.23 linux-2.4.23/drivers/sound/ac97_plugin_wm97xx.c

Next file: linux-2.4.23/drivers/sound/ad1889.c
Previous file: linux-2.4.23/drivers/sound/ac97_codec.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.22/drivers/sound/ac97_plugin_wm97xx.c linux-2.4.23/drivers/sound/ac97_plugin_wm97xx.c
@@ -0,0 +1,1408 @@
+/*
+ * ac97_plugin_wm97xx.c  --  Touch screen driver for Wolfson WM9705 and WM9712
+ *                           AC97 Codecs.
+ *
+ * Copyright 2003 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ *         liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.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
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ *  THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED
+ *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+ *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
+ *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Notes:
+ *
+ *  Features:
+ *       - supports WM9705, WM9712
+ *       - polling mode
+ *       - coordinate polling
+ *       - 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 ?
+ *
+ *  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.
+ *   24th June 2003  Added power management and fixed race condition.
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/fs.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"
+#define TS_MINOR 16
+#define WM_TS_VERSION "0.6"
+#define AC97_NUM_REG 64
+
+
+/*
+ * Debug
+ */
+ 
+#define PFX TS_NAME
+#define WM97XX_TS_DEBUG 0
+
+#ifdef WM97XX_TS_DEBUG
+#define dbg(format, arg...) printk(KERN_DEBUG PFX ": " format "\n" , ## arg)
+#else
+#define dbg(format, arg...) do {} while (0)
+#endif
+#define err(format, arg...) printk(KERN_ERR PFX ": " format "\n" , ## arg)
+#define info(format, arg...) printk(KERN_INFO PFX ": " format "\n" , ## arg)
+#define warn(format, arg...) printk(KERN_WARNING PFX ": " format "\n" , ## arg)
+
+/*
+ * Module parameters
+ */
+	
+	
+/*
+ * Set the codec sample mode.
+ *
+ * The WM9712 can sample touchscreen data in 3 different operating
+ * modes. i.e. polling, coordinate and continous.
+ *
+ * Polling:-     The driver polls the codec and issues 3 seperate commands
+ *               over the AC97 link to read X,Y and pressure.
+ * 
+ * Coordinate: - The driver polls the codec and only issues 1 command over
+ *               the AC97 link to read X,Y and pressure. This mode has
+ *               strict timing requirements and may drop samples if 
+ *               interrupted. However, it is less demanding on the AC97
+ *               link. Note: this mode requires a larger delay than polling
+ *               mode.
+ *
+ * Continuous:-  The codec automatically samples X,Y and pressure and then
+ *               sends the data over the AC97 link in slots. This is the
+ *               same method used by the codec when recording audio.
+ *
+ * Set mode = 0 for polling, 1 for coordinate and 2 for continuous.
+ *            
+ */
+MODULE_PARM(mode,"i");
+MODULE_PARM_DESC(mode, "Set WM97XX operation mode");
+static int mode = 0;	
+	
+/*
+ * WM9712 - Set internal pull up for pen detect. 
+ * 
+ * Pull up is in the range 1.02k (least sensitive) to 64k (most sensitive)
+ * i.e. pull up resistance = 64k Ohms / rpu.
+ * 
+ * Adjust this value if you are having problems with pen detect not 
+ * detecting any down events.
+ */
+MODULE_PARM(rpu,"i");
+MODULE_PARM_DESC(rpu, "Set internal pull up resitor for pen detect.");
+static int rpu = 0;	
+
+/*
+ * WM9705 - Pen detect comparator threshold. 
+ * 
+ * 0 to Vmid in 15 steps, 0 = use zero power comparator with Vmid threshold
+ * i.e. 1 =  Vmid/15 threshold
+ *      15 =  Vmid/1 threshold
+ * 
+ * Adjust this value if you are having problems with pen detect not 
+ * detecting any down events.
+ */
+MODULE_PARM(pdd,"i");
+MODULE_PARM_DESC(pdd, "Set pen detect comparator threshold");
+static int pdd = 0;	
+	
+/*
+ * Set current used for pressure measurement.
+ *
+ * Set pil = 2 to use 400uA 
+ *     pil = 1 to use 200uA and
+ *     pil = 0 to disable pressure measurement.
+ *
+ * This is used to increase the range of values returned by the adc
+ * when measureing touchpanel pressure. 
+ */
+MODULE_PARM(pil,"i");
+MODULE_PARM_DESC(pil, "Set current used for pressure measurement.");
+static int pil = 0;
+
+/*
+ * WM9712 - Set five_wire = 1 to use a 5 wire touchscreen.
+ * 
+ * NOTE: Five wire mode does not allow for readback of pressure.
+ */
+MODULE_PARM(five_wire,"i");
+MODULE_PARM_DESC(five_wire, "Set 5 wire touchscreen.");
+static int five_wire = 0;	
+
+/*
+ * Set adc sample delay.
+ * 
+ * For accurate touchpanel measurements, some settling time may be
+ * required between the switch matrix applying a voltage across the
+ * touchpanel plate and the ADC sampling the signal.
+ *
+ * This delay can be set by setting delay = n, where n is the array
+ * position of the delay in the array delay_table below.
+ * Long delays > 1ms are supported for completeness, but are not
+ * recommended.
+ */
+MODULE_PARM(delay,"i");
+MODULE_PARM_DESC(delay, "Set adc sample delay.");
+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
+
+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;
+	struct proc_dir_entry *wm97xx_ts_ps;
+#ifdef WM97XX_TS_DEBUG
+	struct proc_dir_entry *wm97xx_debug_ts_ps;
+#endif
+	struct pm_dev * pm;
+} 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 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_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);
+
+/* AC97 registration info */
+static struct ac97_driver wm9705_driver = {
+	codec_id: 0x574D4C05,
+	codec_mask: 0xFFFFFFFF,
+	name: "Wolfson WM9705 Touchscreen/BMON",
+	probe:	wm97xx_probe,
+	remove: __devexit_p(wm97xx_remove),
+};
+
+static struct ac97_driver wm9712_driver = {
+	codec_id: 0x574D4C12,
+	codec_mask: 0xFFFFFFFF,
+	name: "Wolfson WM9712 Touchscreen/BMON",
+	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
+};
+
+/*
+ * 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];
+	udelay (pdelay);
+}
+
+
+/*
+ * 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)
+{
+	u16 dig1;
+	int timeout = 5 * delay;
+
+	/* set up digitiser */
+	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); 
+
+	/* wait 3 AC97 time slots + delay for conversion */
+	poll_delay();
+
+	/* wait for POLL to go low */
+	while ((ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER1) & WM97XX_POLL) && timeout) { 
+		udelay(AC97_LINK_FRAME);
+		timeout--;	
+	}
+	if (timeout > 0)
+		*sample = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER_RD);
+	else {
+		ts->adc_errs++;
+		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);
+		return 0;
+	}
+	return 1;
+}
+
+/*
+ * Read a sample from the adc in coordinate mode.
+ */
+static int wm97xx_coord_read_adc(wm97xx_ts_t* ts, u16* x, u16* y, u16* pressure)
+{
+	u16 dig1;
+	int timeout = 5 * delay;
+
+	/* set up digitiser */
+	dig1 = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER1); 
+	dig1&=0x0fff;
+	ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER1, dig1 | WM97XX_ADCSEL_PRES |
+		WM97XX_POLL); 
+
+	/* wait 3 AC97 time slots + delay for conversion */
+	poll_delay();
+	
+	/* read X then wait for 1 AC97 link frame + settling delay */
+	*x = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER_RD);
+	udelay (AC97_LINK_FRAME + delay_table[delay]);
+
+	/* read Y */
+	*y = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER_RD);
+	
+	/* wait for POLL to go low and then read pressure */
+	while ((ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER1) & WM97XX_POLL)&& timeout) {
+			udelay(AC97_LINK_FRAME);
+			timeout--;
+	}
+	if (timeout > 0)		
+		*pressure = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER_RD);
+	else {
+		ts->adc_errs++;
+		err ("adc sample timeout");
+		return 0;
+	}
+	
+	/* check we have correct samples */
+	if (((*x & 0x7000) == 0x1000) && ((*y & 0x7000) == 0x2000) && 
+		((*pressure & 0x7000) == 0x3000)) { 
+		return 1;
+	} else {
+		ts->adc_errs++;
+		err ("adc got wrong samples, got x 0x%x y 0x%x pressure 0x%x", *x, *y, *pressure);
+		return 0;
+	}
+}
+
+/*
+ * Is the pen down ?
+ */
+static inline int pendown (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;
+	
+	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 */
+	}
+	
+	/* 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;
+	}
+	/* 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
+			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;
+	}
+
+	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;
+}
+
+static ssize_t wm97xx_read(struct file *filp, char *buf, size_t count, loff_t *l)
+{
+	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;
+	}
+	
+	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;
+}
+
+
+static int wm97xx_open(struct inode * inode, struct file * filp)
+{
+	wm97xx_ts_t* ts;
+	unsigned long flags;
+	u16 val;
+	int minor = MINOR(inode->i_rdev);
+	
+	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;
+	}
+	
+	/* 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;
+}
+
+static int wm97xx_release(struct inode * inode, struct file * filp)
+{
+	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;
+}
+
+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
+static int wm97xx_read_proc (char *page, char **start, off_t off,
+		    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);
+		len += sprintf (page+len, "No device registered\n");
+		return len;
+	}
+
+	dig1 = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER1);
+	dig2 = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER2);
+	digrd = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER_RD);
+	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';
+			break;
+			case 0x1:
+				srev = 'B';
+			break;
+			case 0x2:
+				srev = 'D';
+			break;
+			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" : "",
+			adcsel ==  WM97XX_ADCSEL_Y ? " -adc set to Y coordinate\n" : "",
+			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 {
+		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" : "");
+		}
+		
+	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",
+			prp == WM97XX_PRP_DET ? " -pen detect enabled, no wake up\n" : "",
+			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)	
+		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);
+		len += sprintf (page+len, " -pressure current %s uA\n", dig2 & WM9712_PIL ? "400" : "200");
+		len += sprintf (page+len, " -using %s wire touchscreen mode", dig2 & WM9712_45W ? "5" : "4");
+	} else {
+		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");
+	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 {
+		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 */
+	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;
+}
+
+#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
+/* WM97xx Power Management
+ * The WM9712 has extra powerdown states that are controlled in 
+ * seperate registers from the AC97 power management.
+ * We will only power down into the extra WM9712 states and leave 
+ * the AC97 power management to the sound driver.
+ */
+static int wm97xx_pm_event(struct pm_dev *dev, pm_request_t rqst, void *data)
+{
+	switch(rqst) {
+		case PM_SUSPEND:
+			wm97xx_suspend();
+			break;
+		case PM_RESUME:
+			wm97xx_resume();
+			break;
+	}
+	return 0;
+}
+
+/*
+ * Power down the codec
+ */
+static void wm97xx_suspend(void)
+{
+	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));
+	
+	spin_unlock_irqrestore(&ts->lock, flags);
+}
+
+/*
+ * Power up the Codec
+ */
+static void wm97xx_resume(void)
+{
+	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);
+		return;
+	}
+
+	/* 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)
+{
+	ts->phone_pga = ts->codec->codec_read(ts->codec, AC97_PHONE_VOL) & 0x001f;
+	ts->line_pgal = ts->codec->codec_read(ts->codec, AC97_LINEIN_VOL) & 0x1f00;
+	ts->line_pgar = ts->codec->codec_read(ts->codec, AC97_LINEIN_VOL) & 0x001f;
+	ts->mic_pgal = ts->codec->codec_read(ts->codec, AC97_MIC_VOL) & 0x1f00;
+	ts->mic_pgar = ts->codec->codec_read(ts->codec, AC97_MIC_VOL) & 0x001f;
+}
+
+/* restore state of wm9712 PGA's */
+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));
+
+	reg = ts->codec->codec_read(ts->codec, AC97_MIC_VOL);
+	ts->codec->codec_write(ts->codec, AC97_MIC_VOL, reg | ts->mic_pgar | (ts->mic_pgal << 8));
+}
+
+#endif
+
+/*
+ * 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);
+	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 
+		 */
+		aux = ts->codec->codec_read(ts->codec, AC97_AUX_VOL);
+		if (!(aux & 0x8000)) {
+			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)
+			dig2 |= WM9712_PIL;
+		else
+			dig2 |= WM9705_PIL;
+		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) {
+			info ("supplied delay out of range.");
+			delay = 4;
+		}
+		dig1 &= 0xff0f;
+		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");
+	}		
+	
+	/* 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); 
+}
+
+
+/*
+ * Called by the audio codec initialisation to register
+ * the touchscreen driver.
+ */
+
+static int wm97xx_probe(struct ac97_codec *codec, struct ac97_driver *driver)
+{
+	 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	
+	 *	
+	 * If an initialsed WM97xx is found then get the codec read and write 
+	 * functions.		 
+	 */
+	
+	/* test for a WM9712 or a WM9705 */
+	id1 = codec->codec_read(codec, AC97_VENDOR_ID1);
+	id2 = codec->codec_read(codec, AC97_VENDOR_ID2);
+	if (id1 == WM97XX_ID1 && id2 == WM9712_ID2) {
+		ts->is_wm9712 = 1;
+		info("registered a WM9712");
+	} else if (id1 == WM97XX_ID1 && id2 == WM9705_ID2) {
+		    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 */
+
+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;
+	
+	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,
+};
+
+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;
+	}
+	
+	init_waitqueue_head(&ts->wait);
+	spin_lock_init(&ts->lock);
+	
+	// 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 with the AC97 layer */
+	ac97_register_driver(&wm9705_driver);
+	ac97_register_driver(&wm9712_driver);
+	
+#ifdef CONFIG_PROC_FS
+	/* register proc interface */
+	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)
+		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 ((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);
+#endif
+	ac97_unregister_driver(&wm9705_driver);
+	ac97_unregister_driver(&wm9712_driver);
+	misc_deregister(&wm97xx_misc);
+}
+
+/* Module information */
+MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com");
+MODULE_DESCRIPTION("WM9705/WM9712 Touch Screen / BMON Driver");
+MODULE_LICENSE("GPL");
+
+module_init(wm97xx_ts_init_module);
+module_exit(wm97xx_ts_cleanup_module);
+
+#ifndef MODULE
+
+static int __init wm97xx_ts_setup(char *options)
+{
+	char *this_opt = options;
+
+	if (!options || !*options)
+		return 0;
+
+	/* parse the options and check for out of range values */
+	for(this_opt=strtok(options, ",");
+	    this_opt; this_opt=strtok(NULL, ",")) {
+		if (!strncmp(this_opt, "pil:", 4)) {
+			this_opt+=4;
+			pil = simple_strtol(this_opt, NULL, 0);
+			if (pil < 0 || pil > 2)
+				pil = 0;
+			continue;
+		}
+		if (!strncmp(this_opt, "rpu:", 4)) {
+			this_opt+=4;
+			rpu = simple_strtol(this_opt, NULL, 0);
+			if (rpu < 0 || rpu > 31)
+				rpu = 0;
+			continue;
+		}
+		if (!strncmp(this_opt, "pdd:", 4)) {
+			this_opt+=4;
+			pdd = simple_strtol(this_opt, NULL, 0);
+			if (pdd < 0 || pdd > 15)
+				pdd = 0;
+			continue;
+		}
+		if (!strncmp(this_opt, "delay:", 6)) {
+			this_opt+=6;
+			delay = simple_strtol(this_opt, NULL, 0);
+			if (delay < 0 || delay > 15)
+				delay = 4;
+			continue;
+		}
+		if (!strncmp(this_opt, "five_wire:", 10)) {
+			this_opt+=10;
+			five_wire = simple_strtol(this_opt, NULL, 0);
+			if (five_wire < 0 || five_wire > 1)
+				five_wire = 0;
+			continue;
+		}
+		if (!strncmp(this_opt, "mode:", 5)) {
+			this_opt+=5;
+			mode = simple_strtol(this_opt, NULL, 0);
+			if (mode < 0 || mode > 2)
+				mode = 0;
+			continue;
+		}
+	}
+	return 1;
+}
+
+__setup("wm97xx_ts=", wm97xx_ts_setup);
+
+#endif /* MODULE */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)