blob: 314ca417832688457207a912841482916878d2f9 [file] [log] [blame]
/*
* 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 */