| /* |
| * 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 */ |