Index: include/linux/ac97_codec.h =================================================================== --- include/linux/ac97_codec.h (.../vendor/arm/linux) (revision 33) +++ linux/include/linux/ac97_codec.h (.../branches/development/linux) (revision 33) @@ -214,6 +214,9 @@ (CODEC)->supported_mixers & (1< + * + * 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 + * - continuous mode + * - adjustable rpu/dpp settings + * - adjustable pressure current + * - adjustable sample settle delay + * - 4 and 5 wire touchscreens (5 wire is WM9712 only) + * - pen down detection + * - power management + * + * TODO: + * - adjustable sample rate + * - AUX adc in coordinate / continous modes + * - Codec GPIO + * - battery monitor + * - sample AUX adc's + * + * Revision history + * 7th May 2003 Initial version. + * 6th June 2003 Added non module support and AC97 registration. + * 18th June 2003 Added AUX adc sampling. + * 23rd June 2003 Did some minimal reformatting, fixed a couple of + * locking bugs and noted a race to fix. + * 24th June 2003 Added power management and fixed race condition. + * 10th July 2003 Changed to a misc device. + * 31st July 2003 Moved TS_EVENT and TS_CAL to wm97xx.h + * 8th Aug 2003 Added option for read() calling wm97xx_sample_touch() + * because some ac97_read/ac_97_write call schedule() + * 7th Nov 2003 Added Input touch event interface, stanley.cai@intel.com + * 13th Nov 2003 Removed h3600 touch interface, added interrupt based + * pen down notification and implemented continous mode + * on XScale arch. + * 4th Dec 2003 Removed ADC src bits from sample data. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* get_user,copy_to_user */ +#include + +#include "wm97xx.h" + +#define TS_NAME "wm97xx" +#define TS_MINOR 16 +#define WM_TS_VERSION "0.10" +#define AC97_NUM_REG 64 + +/* + * Machine specific set up. + * + * This is for targets that can support a PEN down interrupt and/or + * streaming back touch data in an AC97 slot (not slot 1). The + * streaming touch data is read back via the targets AC97 FIFO's + */ +#if defined(CONFIG_ARCH_WMMX) +#include +#include +#define WM97XX_IRQ IRQ_AC97 +#define WM97XX_FIFO_HAS_DATA MISR & (1 << 2) +#define WM97XX_READ_FIFO MODR & (0xffff) +#endif + +#ifndef WM97XX_IRQ +#define WM97XX_IRQ 0 +#define WM97XX_FIFO_HAS_DATA 0 +#define WM97XX_READ_FIFO 0 +#endif + +/* + * Debug + */ +#define PFX TS_NAME +#define WM97XX_TS_DEBUG 0 + +#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; + +typedef struct { + int is_wm9712:1; /* are we a WM9705/12 */ + int is_registered:1; /* Is the driver AC97 registered */ + int adc_errs; /* sample read back errors */ + spinlock_t lock; + struct ac97_codec *codec; +#if defined(CONFIG_PROC_FS) + struct proc_dir_entry *wm97xx_ts_ps; +#endif +#if defined(CONFIG_PM) + struct pm_dev * pm; + int line_pgal:5; + int line_pgar:5; + int phone_pga:5; + int mic_pgal:5; + int mic_pgar:5; +#endif + struct input_dev *idev; + struct completion thread_init; + struct completion thread_exit; + struct task_struct *rtask; + struct semaphore sem; + int use_count; + int restart; +} wm97xx_ts_t; + +static inline void poll_delay (void); +static int __init wm97xx_ts_init_module(void); +static inline int pendown (wm97xx_ts_t *ts); +static int wm97xx_poll_read_adc (wm97xx_ts_t* ts, u16 adcsel, u16* sample); +static void init_wm97xx_phy(void); +static int wm97xx_probe(struct ac97_codec *codec, struct ac97_driver *driver); +static void wm97xx_remove(struct ac97_codec *codec, struct ac97_driver *driver); +static void wm97xx_ts_cleanup_module(void); +static int wm97xx_ts_evt_add(wm97xx_ts_t* ts, u16 pressure, u16 x, u16 y); +static int wm97xx_ts_evt_release(wm97xx_ts_t* ts); + +#if defined(CONFIG_PM) +static int wm97xx_pm_event(struct pm_dev *dev, pm_request_t rqst, void *data); +static void wm97xx_suspend(void); +static void wm97xx_resume(void); +static void wm9712_pga_save(wm97xx_ts_t* ts); +static void wm9712_pga_restore(wm97xx_ts_t* ts); +#endif + +/* we only support a single touchscreen */ +static wm97xx_ts_t wm97xx_ts; +static struct input_dev wm97xx_input; + +/* AC97 registration info */ +static struct ac97_driver wm9705_driver = { + codec_id: 0x574D4C05, + codec_mask: 0xFFFFFFFF, + name: "Wolfson WM9705 Touchscreen", + probe: wm97xx_probe, + remove: __devexit_p(wm97xx_remove), +}; + +static struct ac97_driver wm9712_driver = { + codec_id: 0x574D4C12, + codec_mask: 0xFFFFFFFF, + name: "Wolfson WM9712 Touchscreen", + probe: wm97xx_probe, + remove: __devexit_p(wm97xx_remove), +}; + + +/* + * 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); +} + + +/* + * 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; + } +} + +/* + * Sample the touchscreen in polling mode + */ +int wm97xx_poll_touch(wm97xx_ts_t *ts) +{ + u16 x, y, p; + + if (!wm97xx_poll_read_adc(ts, WM97XX_ADCSEL_X, &x)) { + info("x %d\n", x); + return -EIO; + } + if (!wm97xx_poll_read_adc(ts, WM97XX_ADCSEL_Y, &y)) { + info("y %d\n", y); + return -EIO; + } + if (pil && !five_wire) { + if (!wm97xx_poll_read_adc(ts, WM97XX_ADCSEL_PRES, &p)) { + info("p %d\n", p); + return -EIO; + } + } else { + p = 0xffff; + } + + wm97xx_ts_evt_add(ts, p, x, y); + return 1; +} + +/* + * Sample the touchscreen in polling coordinate mode + */ +int wm97xx_poll_coord_touch(wm97xx_ts_t *ts) +{ + u16 x, y, p; + + if (wm97xx_coord_read_adc(ts, &x, &y, &p)) { + wm97xx_ts_evt_add(ts, p, x, y); + return 1; + } else + return -EIO; +} + +/* + * Sample the touchscreen in continous mode + */ +int wm97xx_cont_touch(wm97xx_ts_t *ts) +{ + u16 x, y; + int count = 0; + + /* currently assuming x is followed by y from the FIFO */ + while (WM97XX_FIFO_HAS_DATA) { + x = WM97XX_READ_FIFO; + y = WM97XX_READ_FIFO; + wm97xx_ts_evt_add(ts, 0xffff, x, y); + count++; + } + return count; +} + +/* + * 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; +} + +#if defined(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); + + ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER1, dig1 | WM97XX_POLL); + poll_delay(); + + 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; + + len += sprintf (page+len, "Wolfson WM97xx Version %s\n", WM_TS_VERSION); + + 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"); + + 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"); + } + + 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" : ""); + } + + 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); + + len += sprintf(page+len, "\nErrors:\n%s%d\n", + " -coordinate errors ", ts->adc_errs); + + return len; +} + +#endif + +#if defined(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; + + 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(5); + if (ts->is_wm9712) + dig2 = WM9712_RPU(1); + else { + dig2 = 0x0; + + /* + * mute VIDEO and AUX as they share X and Y touchscreen + * inputs on the WM9705 + */ + 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"); + } + + /* continous mode */ + if (mode == 2) { + dig1 |= WM97XX_CTC | WM97XX_COO | WM97XX_CM_RATE_375 | WM97XX_SLEN | WM97XX_SLT(5); + info("using continous mode"); + if (ts->is_wm9712) + dig2 |= WM9712_PDEN; + else + dig2 |= WM9705_PDEN; + } + + /* WM9705 pdd */ + if (pdd && !ts->is_wm9712) { + dig2 |= (pdd & 0x000f); + info("setting pdd to Vmid/%d", 1 - (pdd & 0x000f)); + } + + ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER1, dig1); + ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER2, dig2); +} + + +/* + * 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; + + /* set up physical characteristics */ + init_wm97xx_phy(); + + ts->is_registered = 1; + spin_unlock_irqrestore(&ts->lock, flags); + return 0; +} + +/* + * Called by ac97_codec when it is unloaded. + */ +static void wm97xx_remove(struct ac97_codec *codec, struct ac97_driver *driver) +{ + unsigned long flags; + u16 dig1, dig2; + wm97xx_ts_t *ts = codec->driver_private; + + dbg("Unloading codec\n"); + spin_lock_irqsave(&ts->lock, flags); + + /* check that are registered */ + if (!ts->is_registered) { + err("double unregister"); + spin_unlock_irqrestore(&ts->lock, flags); + return; + } + + ts->is_registered = 0; + + /* 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); +} + +/* + * add a touch event + */ +static int wm97xx_ts_evt_add(wm97xx_ts_t* ts, u16 pressure, u16 x, u16 y) +{ + /* add event and remove adc src bits */ + //printk("x %d y %d p %d\n", x,y,pressure); + input_report_abs(ts->idev, ABS_X, x & 0xfff); + input_report_abs(ts->idev, ABS_Y, y & 0xfff); + input_report_abs(ts->idev, ABS_PRESSURE, pressure & 0xfff); + return 0; +} + +/* + * add a pen up event + */ +static int wm97xx_ts_evt_release(wm97xx_ts_t* ts) +{ +// dbg("release the stylus.\n"); + input_report_abs(ts->idev, ABS_PRESSURE, 0); + return 0; +} + +/* + * The touchscreen sample reader thread + */ +static int wm97xx_thread(void *_ts) +{ + wm97xx_ts_t *ts = (wm97xx_ts_t *)_ts; + struct task_struct *tsk = current; + int valid = 0; + + ts->rtask = tsk; + + /* set up thread context */ + daemonize(); + reparent_to_init(); + strcpy(tsk->comm, "ktsd"); + tsk->tty = NULL; + + /* we will die when we receive SIGKILL */ + spin_lock_irq(&tsk->sigmask_lock); + siginitsetinv(&tsk->blocked, sigmask(SIGKILL)); + recalc_sigpending(tsk); + spin_unlock_irq(&tsk->sigmask_lock); + + /* init is complete */ + complete(&ts->thread_init); + + /* touch reader loop */ + for (;;) { + ts->restart = 0; + if(signal_pending(tsk)) + break; + + if(pendown(ts)) { + switch (mode) { + case 0: + wm97xx_poll_touch(ts); + valid = 1; + break; + case 1: + wm97xx_poll_coord_touch(ts); + valid = 1; + break; + case 2: + wm97xx_cont_touch(ts); + valid = 1; + break; + } + } else { + if (valid) { + valid = 0; + wm97xx_ts_evt_release(ts); + } + } + + set_task_state(tsk, TASK_INTERRUPTIBLE); + if (HZ >= 100) + schedule_timeout(HZ/100); + else + schedule_timeout(1); + } + ts->rtask = NULL; + complete_and_exit(&ts->thread_exit, 0); + + return 0; +} + +/* + * Start the touchscreen thread and + * the touch digitiser. + */ +static int wm97xx_ts_input_open(struct input_dev *idev) +{ + wm97xx_ts_t *ts = (wm97xx_ts_t *) &wm97xx_ts; + u32 flags; + int ret, val; + + spin_lock_irqsave( &ts->lock, flags ); + if ( ts->use_count++ == 0 ) { + + /* start touchscreen thread */ + ts->idev = idev; + init_completion(&ts->thread_init); + ret = kernel_thread(wm97xx_thread, ts, 0); + if (ret >= 0) + wait_for_completion(&ts->thread_init); + + /* start digitiser */ + val = ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER2); + ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER2, + val | WM97XX_PRP_DET_DIG); + } + spin_unlock_irqrestore( &ts->lock, flags ); + return 0; +} + +/* + * Kill the touchscreen thread and stop + * the touch digitiser. + */ +static void wm97xx_ts_input_close(struct input_dev *idev) +{ + wm97xx_ts_t *ts = (wm97xx_ts_t *) &wm97xx_ts; + u32 flags; + int val; + + spin_lock_irqsave(&ts->lock, flags); + if (--ts->use_count == 0) { + /* kill thread */ + init_completion(&ts->thread_exit); + if (ts->rtask) { + send_sig(SIGKILL, ts->rtask, 1); + wait_for_completion(&ts->thread_exit); + } + + /* 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); +} + +static int __init wm97xx_ts_init_module(void) +{ + wm97xx_ts_t* ts = &wm97xx_ts; + 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)); + + /* tell input system what we events we accept and register */ + wm97xx_input.name = "wm97xx touchscreen"; + wm97xx_input.open = wm97xx_ts_input_open; + wm97xx_input.close = wm97xx_ts_input_close; + __set_bit(EV_ABS, wm97xx_input.evbit); + __set_bit(ABS_X, wm97xx_input.absbit); + __set_bit(ABS_Y, wm97xx_input.absbit); + __set_bit(ABS_PRESSURE, wm97xx_input.absbit); + input_register_device(&wm97xx_input); + + spin_lock_init(&ts->lock); + init_MUTEX(&ts->sem); + + /* register with the AC97 layer */ + ac97_register_driver(&wm9705_driver); + ac97_register_driver(&wm9712_driver); + +#if defined(CONFIG_PROC_FS) + sprintf(proc_str, "driver/%s", TS_NAME); + if ((ts->wm97xx_ts_ps = create_proc_read_entry (proc_str, 0, NULL, + wm97xx_read_proc, ts)) == 0) + err("could not register proc interface /proc/%s", proc_str); +#endif +#if defined(CONFIG_PM) + if ((ts->pm = pm_register(PM_UNKNOWN_DEV, PM_SYS_UNKNOWN, wm97xx_pm_event)) == 0) + err("could not register with power management"); +#endif + + return 0; +} + +static void wm97xx_ts_cleanup_module(void) +{ +#if defined(CONFIG_PM) + pm_unregister (wm97xx_ts.pm); +#endif + input_unregister_device(&wm97xx_input); +} + +/* 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=strsep(options, ","); + this_opt; this_opt=strsep(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 */ Index: drivers/sound/Config.in =================================================================== --- drivers/sound/Config.in (.../vendor/arm/linux) (revision 33) +++ linux/drivers/sound/Config.in (.../branches/development/linux) (revision 33) @@ -227,9 +227,15 @@ fi dep_tristate ' Netwinder WaveArtist' CONFIG_SOUND_WAVEARTIST $CONFIG_SOUND_OSS $CONFIG_ARCH_NETWINDER dep_tristate ' Intel PXA250/210 AC97 audio' CONFIG_SOUND_PXA_AC97 $CONFIG_ARCH_PXA $CONFIG_SOUND + dep_tristate ' Wolfson WM8753 audio' CONFIG_SOUND_PXA_WM8753 $CONFIG_ARCH_PXA $CONFIG_SOUND fi dep_tristate ' TV card (bt848) mixer support' CONFIG_SOUND_TVMIXER $CONFIG_SOUND $CONFIG_I2C +dep_tristate ' Wolfson Touchscreen/BMON plugin' CONFIG_SOUND_WM97XX $CONFIG_SOUND +dep_tristate ' Wolfson WM9713 Touchscreen/DAC plugin' CONFIG_SOUND_WM9713 $CONFIG_SOUND +if ["$CONFIG_SOUND_WM9713" = "y" -o "$CONFIG_SOUND_WM9713" = "m"]; then + define_bool CONFIG_SOUND_AC97_WARM y +fi # A cross directory dependence. The sound modules will need gameport.o compiled in, # but it resides in the drivers/char/joystick directory. This define_tristate takes Index: drivers/sound/ac97_codec.c =================================================================== --- drivers/sound/ac97_codec.c (.../vendor/arm/linux) (revision 33) +++ linux/drivers/sound/ac97_codec.c (.../branches/development/linux) (revision 33) @@ -1,4 +1,3 @@ - /* * ac97_codec.c: Generic AC97 mixer/modem module * @@ -31,6 +30,13 @@ ************************************************************************** * * History + * Feb 25, 2004 Liam Girdwood + * Added support for codecs that require a warm reset to power up. + * Support for WM9713 + * May 02, 2003 Liam Girdwood + * Removed non existant WM9700 + * Added support for WM9705, WM9708, WM9709, WM9710, WM9711 + * WM9712 and WM9717 * Mar 28, 2002 Randolph Bentson * corrections to support WM9707 in ViewPad 1000 * v0.4 Mar 15 2000 Ollie Lho @@ -45,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +59,8 @@ #include #include +#define CODEC_ID_BUFSZ 14 + static int ac97_read_mixer(struct ac97_codec *codec, int oss_channel); static void ac97_write_mixer(struct ac97_codec *codec, int oss_channel, unsigned int left, unsigned int right); @@ -61,9 +70,12 @@ static int ac97_init_mixer(struct ac97_codec *codec); -static int wolfson_init00(struct ac97_codec * codec); static int wolfson_init03(struct ac97_codec * codec); static int wolfson_init04(struct ac97_codec * codec); +static int wolfson_init05(struct ac97_codec * codec); +static int wolfson_init11(struct ac97_codec * codec); +static int wolfson_init13(struct ac97_codec * codec); +static int phillips_ucb1400_init(struct ac97_codec * codec); static int tritech_init(struct ac97_codec * codec); static int tritech_maestro_init(struct ac97_codec * codec); static int sigmatel_9708_init(struct ac97_codec *codec); @@ -71,7 +83,10 @@ static int sigmatel_9744_init(struct ac97_codec *codec); static int ad1886_init(struct ac97_codec *codec); static int eapd_control(struct ac97_codec *codec, int); -static int crystal_digital_control(struct ac97_codec *codec, int mode); +static int crystal_digital_control(struct ac97_codec *codec, int slots, int rate, int mode); +static int cmedia_init(struct ac97_codec * codec); +static int cmedia_digital_control(struct ac97_codec *codec, int slots, int rate, int mode); +static int generic_digital_control(struct ac97_codec *codec, int slots, int rate, int mode); /* @@ -92,9 +107,13 @@ static struct ac97_ops null_ops = { NULL, NULL, NULL }; static struct ac97_ops default_ops = { NULL, eapd_control, NULL }; -static struct ac97_ops wolfson_ops00 = { wolfson_init00, NULL, NULL }; +static struct ac97_ops default_digital_ops = { NULL, eapd_control, generic_digital_control}; static struct ac97_ops wolfson_ops03 = { wolfson_init03, NULL, NULL }; static struct ac97_ops wolfson_ops04 = { wolfson_init04, NULL, NULL }; +static struct ac97_ops wolfson_ops05 = { wolfson_init05, NULL, NULL }; +static struct ac97_ops wolfson_ops11 = { wolfson_init11, NULL, NULL }; +static struct ac97_ops wolfson_ops13 = { wolfson_init13, NULL, NULL }; +static struct ac97_ops phillips_ucb1400_ops = { phillips_ucb1400_init, NULL, NULL }; static struct ac97_ops tritech_ops = { tritech_init, NULL, NULL }; static struct ac97_ops tritech_m_ops = { tritech_maestro_init, NULL, NULL }; static struct ac97_ops sigmatel_9708_ops = { sigmatel_9708_init, NULL, NULL }; @@ -102,12 +121,15 @@ static struct ac97_ops sigmatel_9744_ops = { sigmatel_9744_init, NULL, NULL }; static struct ac97_ops crystal_digital_ops = { NULL, eapd_control, crystal_digital_control }; static struct ac97_ops ad1886_ops = { ad1886_init, eapd_control, NULL }; +static struct ac97_ops cmedia_ops = { NULL, eapd_control, NULL}; +static struct ac97_ops cmedia_digital_ops = { cmedia_init, eapd_control, cmedia_digital_control}; /* sorted by vendor/device id */ static const struct { u32 id; char *name; struct ac97_ops *ops; + int flags; } ac97_codec_ids[] = { {0x41445303, "Analog Devices AD1819", &null_ops}, {0x41445340, "Analog Devices AD1881", &null_ops}, @@ -119,8 +141,12 @@ {0x414B4D00, "Asahi Kasei AK4540", &null_ops}, {0x414B4D01, "Asahi Kasei AK4542", &null_ops}, {0x414B4D02, "Asahi Kasei AK4543", &null_ops}, + {0x414C4326, "ALC100P", &null_ops}, {0x414C4710, "ALC200/200P", &null_ops}, - {0x414C4720, "ALC650", &null_ops}, + {0x414C4720, "ALC650", &default_digital_ops}, + {0x434D4941, "CMedia", &cmedia_ops, AC97_NO_PCM_VOLUME }, + {0x434D4942, "CMedia", &cmedia_ops, AC97_NO_PCM_VOLUME }, + {0x434D4961, "CMedia", &cmedia_digital_ops, AC97_NO_PCM_VOLUME }, {0x43525900, "Cirrus Logic CS4297", &default_ops}, {0x43525903, "Cirrus Logic CS4297", &default_ops}, {0x43525913, "Cirrus Logic CS4297A rev A", &default_ops}, @@ -131,11 +157,12 @@ {0x43525931, "Cirrus Logic CS4299 rev A", &crystal_digital_ops}, {0x43525933, "Cirrus Logic CS4299 rev C", &crystal_digital_ops}, {0x43525934, "Cirrus Logic CS4299 rev D", &crystal_digital_ops}, - {0x4352594d, "Cirrus Logic CS4201" , &null_ops}, + {0x43585442, "CXT66", &default_ops, AC97_DELUDED_MODEM }, + {0x44543031, "Diamond Technology DT0893", &default_ops}, {0x45838308, "ESS Allegro ES1988", &null_ops}, {0x49434511, "ICE1232", &null_ops}, /* I hope --jk */ {0x4e534331, "National Semiconductor LM4549", &null_ops}, - {0x50534304, "Philips UCB1400", &default_ops}, + {0x50534304, "Philips UCB1400", &phillips_ucb1400_ops}, {0x53494c22, "Silicon Laboratory Si3036", &null_ops}, {0x53494c23, "Silicon Laboratory Si3038", &null_ops}, {0x545200FF, "TriTech TR?????", &tritech_m_ops}, @@ -144,15 +171,19 @@ {0x54524106, "TriTech TR28026", &null_ops}, {0x54524108, "TriTech TR28028", &tritech_ops}, {0x54524123, "TriTech TR A5", &null_ops}, - {0x574D4C00, "Wolfson WM9700A", &wolfson_ops00}, - {0x574D4C03, "Wolfson WM9703/WM9707", &wolfson_ops03}, + {0x574D4C03, "Wolfson WM9703/07/08/17", &wolfson_ops03}, {0x574D4C04, "Wolfson WM9704M/WM9704Q", &wolfson_ops04}, + {0x574D4C05, "Wolfson WM9705/WM9710", &wolfson_ops05}, + {0x574D4C09, "Wolfson WM9709", &null_ops}, + {0x574D4C12, "Wolfson WM9711/9712", &wolfson_ops11}, + {0x574D4C13, "Wolfson WM9713", &wolfson_ops13, AC97_DEFAULT_POWER_OFF}, {0x83847600, "SigmaTel STAC????", &null_ops}, {0x83847604, "SigmaTel STAC9701/3/4/5", &null_ops}, {0x83847605, "SigmaTel STAC9704", &null_ops}, {0x83847608, "SigmaTel STAC9708", &sigmatel_9708_ops}, {0x83847609, "SigmaTel STAC9721/23", &sigmatel_9721_ops}, {0x83847644, "SigmaTel STAC9744/45", &sigmatel_9744_ops}, + {0x83847652, "SigmaTel STAC9752/53", &default_ops}, {0x83847656, "SigmaTel STAC9756/57", &sigmatel_9744_ops}, {0x83847666, "SigmaTel STAC9750T", &sigmatel_9744_ops}, {0x83847684, "SigmaTel STAC9783/84?", &null_ops}, @@ -272,6 +303,10 @@ [SOUND_MIXER_PHONEIN] = AC97_REC_PHONE }; +static LIST_HEAD(codecs); +static LIST_HEAD(codec_drivers); +static DECLARE_MUTEX(codec_sem); + /* reads the given OSS mixer from the ac97 the caller must have insured that the ac97 knows about that given mixer, and should be holding a spinlock for the card */ static int ac97_read_mixer(struct ac97_codec *codec, int oss_channel) @@ -656,6 +691,98 @@ } /** + * codec_id - Turn id1/id2 into a PnP string + * @id1: Vendor ID1 + * @id2: Vendor ID2 + * @buf: CODEC_ID_BUFSZ byte buffer + * + * Fills buf with a zero terminated PnP ident string for the id1/id2 + * pair. For convenience the return is the passed in buffer pointer. + */ + +static char *codec_id(u16 id1, u16 id2, char *buf) +{ + if(id1&0x8080) { + snprintf(buf, CODEC_ID_BUFSZ, "0x%04x:0x%04x", id1, id2); + } else { + buf[0] = (id1 >> 8); + buf[1] = (id1 & 0xFF); + buf[2] = (id2 >> 8); + snprintf(buf+3, CODEC_ID_BUFSZ - 3, "%d", id2&0xFF); + } + return buf; +} + +/** + * ac97_check_modem - Check if the Codec is a modem + * @codec: codec to check + * + * Return true if the device is an AC97 1.0 or AC97 2.0 modem + */ + +static int ac97_check_modem(struct ac97_codec *codec) +{ + /* Check for an AC97 1.0 soft modem (ID1) */ + if(codec->codec_read(codec, AC97_RESET) & 2) + return 1; + /* Check for an AC97 2.x soft modem */ + codec->codec_write(codec, AC97_EXTENDED_MODEM_ID, 0L); + if(codec->codec_read(codec, AC97_EXTENDED_MODEM_ID) & 1) + return 1; + return 0; +} + + +/** + * ac97_alloc_codec - Allocate an AC97 codec + * + * Returns a new AC97 codec structure. AC97 codecs may become + * refcounted soon so this interface is needed. Returns with + * one reference taken. + */ + +struct ac97_codec *ac97_alloc_codec(void) +{ + struct ac97_codec *codec = kmalloc(sizeof(struct ac97_codec), GFP_KERNEL); + if(!codec) + return NULL; + + memset(codec, 0, sizeof(*codec)); + spin_lock_init(&codec->lock); + INIT_LIST_HEAD(&codec->list); + return codec; +} + +EXPORT_SYMBOL(ac97_alloc_codec); + +/** + * ac97_release_codec - Release an AC97 codec + * @codec: codec to release + * + * Release an allocated AC97 codec. This will be refcounted in + * time but for the moment is trivial. Calls the unregister + * handler if the codec is now defunct. + */ + +void ac97_release_codec(struct ac97_codec *codec) +{ + /* Remove from the list first, we don't want to be + "rediscovered" */ + down(&codec_sem); + list_del(&codec->list); + up(&codec_sem); + /* + * The driver needs to deal with internal + * locking to avoid accidents here. + */ + if(codec->driver) + codec->driver->remove(codec, codec->driver); + kfree(codec); +} + +EXPORT_SYMBOL(ac97_release_codec); + +/** * ac97_probe_codec - Initialize and setup AC97-compatible codec * @codec: (in/out) Kernel info for a single AC97 codec * @@ -675,59 +802,107 @@ * Currently codec_wait is used to wait for AC97 codec * reset to complete. * + * Some codecs will power down when a register reset is + * performed. We now check for such codecs. + * * Returns 1 (true) on success, or 0 (false) on failure. */ int ac97_probe_codec(struct ac97_codec *codec) { u16 id1, id2; - u16 audio, modem; + u16 audio; int i; - + char cidbuf[CODEC_ID_BUFSZ]; + u16 f; + struct list_head *l; + struct ac97_driver *d; + /* probing AC97 codec, AC97 2.0 says that bit 15 of register 0x00 (reset) should * be read zero. * * FIXME: is the following comment outdated? -jgarzik * Probing of AC97 in this way is not reliable, it is not even SAFE !! */ - codec->codec_write(codec, AC97_RESET, 0L); - /* also according to spec, we wait for codec-ready state */ + /* also according to spec, we wait for codec-ready state */ if (codec->codec_wait) codec->codec_wait(codec); else udelay(10); - - if ((audio = codec->codec_read(codec, AC97_RESET)) & 0x8000) { - printk(KERN_ERR "ac97_codec: %s ac97 codec not present\n", - codec->id ? "Secondary" : "Primary"); - return 0; - } - - /* probe for Modem Codec */ - codec->codec_write(codec, AC97_EXTENDED_MODEM_ID, 0L); - modem = codec->codec_read(codec, AC97_EXTENDED_MODEM_ID); - + + /* will the codec power down if register reset ? */ + id1 = codec->codec_read(codec, AC97_VENDOR_ID1); + id2 = codec->codec_read(codec, AC97_VENDOR_ID2); codec->name = NULL; codec->codec_ops = &null_ops; - - id1 = codec->codec_read(codec, AC97_VENDOR_ID1); - id2 = codec->codec_read(codec, AC97_VENDOR_ID2); for (i = 0; i < ARRAY_SIZE(ac97_codec_ids); i++) { if (ac97_codec_ids[i].id == ((id1 << 16) | id2)) { codec->type = ac97_codec_ids[i].id; codec->name = ac97_codec_ids[i].name; codec->codec_ops = ac97_codec_ids[i].ops; + codec->flags = ac97_codec_ids[i].flags; break; } } + codec->model = (id1 << 16) | id2; + if (codec->flags != AC97_DEFAULT_POWER_OFF) { + /* reset codec and wait for the ready bit before we continue */ + codec->codec_write(codec, AC97_RESET, 0L); + if (codec->codec_wait) + codec->codec_wait(codec); + else + udelay(10); + } + + if ((audio = codec->codec_read(codec, AC97_RESET)) & 0x8000) { + printk(KERN_ERR "ac97_codec: %s ac97 codec not present\n", + (codec->id & 0x2) ? (codec->id&1 ? "4th" : "Tertiary") + : (codec->id&1 ? "Secondary": "Primary")); + return 0; + } + + /* probe for Modem Codec */ + codec->modem = ac97_check_modem(codec); + + f = codec->codec_read(codec, AC97_EXTENDED_STATUS); + if(f & 4) + codec->codec_ops = &default_digital_ops; + + /* A device which thinks its a modem but isnt */ + if(codec->flags & AC97_DELUDED_MODEM) + codec->modem = 0; + if (codec->name == NULL) codec->name = "Unknown"; - printk(KERN_INFO "ac97_codec: AC97 %s codec, id: 0x%04x:" - "0x%04x (%s)\n", audio ? "Audio" : (modem ? "Modem" : ""), - id1, id2, codec->name); + printk(KERN_INFO "ac97_codec: AC97 %s codec, id: %s (%s)\n", + codec->modem ? "Modem" : (audio ? "Audio" : ""), + codec_id(id1, id2, cidbuf), codec->name); - return ac97_init_mixer(codec); + if(!ac97_init_mixer(codec)) + return 0; + + /* + * Attach last so the caller can override the mixer + * callbacks. + */ + + down(&codec_sem); + list_add(&codec->list, &codecs); + + list_for_each(l, &codec_drivers) { + d = list_entry(l, struct ac97_driver, list); + if ((codec->model ^ d->codec_id) & d->codec_mask) + continue; + if(d->probe(codec, d) == 0) + { + codec->driver = d; + break; + } + } + + up(&codec_sem); + return 1; } static int ac97_init_mixer(struct ac97_codec *codec) @@ -746,6 +921,7 @@ if (!(cap & 0x10)) codec->supported_mixers &= ~SOUND_MASK_ALTPCM; + /* detect bit resolution */ codec->codec_write(codec, AC97_MASTER_VOL_STEREO, 0x2020); if(codec->codec_read(codec, AC97_MASTER_VOL_STEREO) == 0x2020) @@ -759,11 +935,6 @@ codec->recmask_io = ac97_recmask_io; codec->mixer_ioctl = ac97_mixer_ioctl; - /* codec specific initialization for 4-6 channel output or secondary codec stuff */ - if (codec->codec_ops->init != NULL) { - codec->codec_ops->init(codec); - } - /* initialize mixer channel volumes */ for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { struct mixer_defaults *md = &mixer_defaults[i]; @@ -774,6 +945,20 @@ ac97_set_mixer(codec, md->mixer, md->value); } + /* codec specific initialization for 4-6 channel output or secondary codec stuff */ + if (codec->codec_ops->init != NULL) { + codec->codec_ops->init(codec); + } + + /* + * Volume is MUTE only on this device. We have to initialise + * it but its useless beyond that. + */ + if(codec->flags & AC97_NO_PCM_VOLUME) + { + codec->supported_mixers &= ~SOUND_MASK_PCM; + printk(KERN_WARNING "AC97 codec does not have proper volume support.\n"); + } return 1; } @@ -846,55 +1031,100 @@ return 0; } +static int cmedia_init(struct ac97_codec *codec) +{ + /* Initialise the CMedia 9739 */ + /* + We could set various options here + Register 0x20 bit 0x100 sets mic as center bass + Also do multi_channel_ctrl &=~0x3000 |=0x1000 + + For now we set up the GPIO and PC beep + */ + + u16 v; + + /* MIC */ + codec->codec_write(codec, 0x64, 0x3000); + v = codec->codec_read(codec, 0x64); + v &= ~0x8000; + codec->codec_write(codec, 0x64, v); + codec->codec_write(codec, 0x70, 0x0100); + codec->codec_write(codec, 0x72, 0x0020); + return 0; +} + +#define AC97_WM97XX_FMIXER_VOL 0x72 +#define AC97_WM97XX_RMIXER_VOL 0x74 +#define AC97_WM97XX_TEST 0x5a +#define AC97_WM9704_RPCM_VOL 0x70 +#define AC97_WM9711_OUT3VOL 0x16 -static int wolfson_init00(struct ac97_codec * codec) +static int wolfson_init03(struct ac97_codec * codec) { - /* This initialization is suspect, but not known to be wrong. - It was copied from the initialization for the WM9704Q, but - that same sequence is known to fail for the WM9707. Thus - this warning may help someone with hardware to test - this code. */ - codec->codec_write(codec, 0x72, 0x0808); - codec->codec_write(codec, 0x74, 0x0808); + /* this is known to work for the ViewSonic ViewPad 1000 */ + codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808); + codec->codec_write(codec, AC97_GENERAL_PURPOSE, 0x8000); + return 0; +} +static int wolfson_init04(struct ac97_codec * codec) +{ + codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808); + codec->codec_write(codec, AC97_WM97XX_RMIXER_VOL, 0x0808); + // patch for DVD noise - codec->codec_write(codec, 0x5a, 0x0200); + codec->codec_write(codec, AC97_WM97XX_TEST, 0x0200); // init vol as PCM vol - codec->codec_write(codec, 0x70, + codec->codec_write(codec, AC97_WM9704_RPCM_VOL, codec->codec_read(codec, AC97_PCMOUT_VOL)); + /* set rear surround volume */ codec->codec_write(codec, AC97_SURROUND_MASTER, 0x0000); return 0; } - -static int wolfson_init03(struct ac97_codec * codec) +/* WM9705, WM9710 */ +static int wolfson_init05(struct ac97_codec * codec) { - /* this is known to work for the ViewSonic ViewPad 1000 */ - codec->codec_write(codec, 0x72, 0x0808); - codec->codec_write(codec, 0x20, 0x8000); + /* set front mixer volume */ + codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808); return 0; } - -static int wolfson_init04(struct ac97_codec * codec) +/* WM9711, WM9712 */ +static int wolfson_init11(struct ac97_codec * codec) { - codec->codec_write(codec, 0x72, 0x0808); - codec->codec_write(codec, 0x74, 0x0808); + /* stop pop's during suspend/resume */ + codec->codec_write(codec, AC97_WM97XX_TEST, codec->codec_read(codec, AC97_WM97XX_TEST) & 0xffbf); - // patch for DVD noise - codec->codec_write(codec, 0x5a, 0x0200); + /* set out3 volume */ + codec->codec_write(codec, AC97_WM9711_OUT3VOL, 0x0808); + return 0; +} - // init vol as PCM vol - codec->codec_write(codec, 0x70, - codec->codec_read(codec, AC97_PCMOUT_VOL)); +/* WM9713 */ +static int wolfson_init13(struct ac97_codec * codec) +{ + codec->codec_write(codec, AC97_RECORD_GAIN, 0x00a0); + codec->codec_write(codec, AC97_POWER_CONTROL, 0x0000); + codec->codec_write(codec, AC97_EXTENDED_MODEM_ID, 0xDA00); + codec->codec_write(codec, AC97_EXTEND_MODEM_STAT, 0x3810); + codec->codec_write(codec, AC97_PHONE_VOL, 0x0808); + codec->codec_write(codec, AC97_PCBEEP_VOL, 0x0808); + return 0; +} - codec->codec_write(codec, AC97_SURROUND_MASTER, 0x0000); +/* UCB1400 */ +static int phillips_ucb1400_init(struct ac97_codec * codec) +{ + codec->codec_write(codec,AC97_EXTENDED_STATUS,1); + codec->codec_write(codec, 0x6a, 0x0050); + codec->codec_write(codec, 0x6c, 0x0030); return 0; } - static int tritech_init(struct ac97_codec * codec) { codec->codec_write(codec, 0x26, 0x0300); @@ -954,26 +1184,115 @@ return 0; } +static int generic_digital_control(struct ac97_codec *codec, int slots, int rate, int mode) +{ + u16 reg; + + reg = codec->codec_read(codec, AC97_SPDIF_CONTROL); + + switch(rate) + { + /* Off by default */ + default: + case 0: + reg = codec->codec_read(codec, AC97_EXTENDED_STATUS); + codec->codec_write(codec, AC97_EXTENDED_STATUS, (reg & ~AC97_EA_SPDIF)); + if(rate == 0) + return 0; + return -EINVAL; + case 1: + reg = (reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_48K; + break; + case 2: + reg = (reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_44K; + break; + case 3: + reg = (reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_32K; + break; + } + + reg &= ~AC97_SC_CC_MASK; + reg |= (mode & AUDIO_CCMASK) << 6; + + if(mode & AUDIO_DIGITAL) + reg |= 2; + if(mode & AUDIO_PRO) + reg |= 1; + if(mode & AUDIO_DRS) + reg |= 0x4000; + + codec->codec_write(codec, AC97_SPDIF_CONTROL, reg); + + reg = codec->codec_read(codec, AC97_EXTENDED_STATUS); + reg &= (AC97_EA_SLOT_MASK); + reg |= AC97_EA_VRA | AC97_EA_SPDIF | slots; + codec->codec_write(codec, AC97_EXTENDED_STATUS, reg); + + reg = codec->codec_read(codec, AC97_EXTENDED_STATUS); + if(!(reg & 0x0400)) + { + codec->codec_write(codec, AC97_EXTENDED_STATUS, reg & ~ AC97_EA_SPDIF); + return -EINVAL; + } + return 0; +} + /* - * Crystal digital audio control (CS4299 + * Crystal digital audio control (CS4299) */ -static int crystal_digital_control(struct ac97_codec *codec, int mode) +static int crystal_digital_control(struct ac97_codec *codec, int slots, int rate, int mode) { u16 cv; - switch(mode) + if(mode & AUDIO_DIGITAL) + return -EINVAL; + + switch(rate) { case 0: cv = 0x0; break; /* SPEN off */ - case 1: cv = 0x8004; break; /* 48KHz digital */ - case 2: cv = 0x8104; break; /* 44.1KHz digital */ + case 48000: cv = 0x8004; break; /* 48KHz digital */ + case 44100: cv = 0x8104; break; /* 44.1KHz digital */ + case 32768: /* 32Khz */ default: - return -1; /* Not supported yet(eg AC3) */ + return -EINVAL; } codec->codec_write(codec, 0x68, cv); return 0; } +/* + * CMedia digital audio control + * Needs more work. + */ + +static int cmedia_digital_control(struct ac97_codec *codec, int slots, int rate, int mode) +{ + u16 cv; + + if(mode & AUDIO_DIGITAL) + return -EINVAL; + + switch(rate) + { + case 0: cv = 0x0001; break; /* SPEN off */ + case 48000: cv = 0x0009; break; /* 48KHz digital */ + default: + return -EINVAL; + } + codec->codec_write(codec, 0x2A, 0x05c4); + codec->codec_write(codec, 0x6C, cv); + + /* Switch on mix to surround */ + cv = codec->codec_read(codec, 0x64); + cv &= ~0x0200; + if(mode) + cv |= 0x0200; + codec->codec_write(codec, 0x64, cv); + return 0; +} + + /* copied from drivers/sound/maestro.c */ #if 0 /* there has been 1 person on the planet with a pt101 that we know of. If they care, they can put this back in :) */ @@ -1111,4 +1430,67 @@ EXPORT_SYMBOL(ac97_restore_state); +/** + * ac97_register_driver - register a codec helper + * @driver: Driver handler + * + * Register a handler for codecs matching the codec id. The handler + * attach function is called for all present codecs and will be + * called when new codecs are discovered. + */ + +int ac97_register_driver(struct ac97_driver *driver) +{ + struct list_head *l; + struct ac97_codec *c; + + down(&codec_sem); + INIT_LIST_HEAD(&driver->list); + list_add(&driver->list, &codec_drivers); + + list_for_each(l, &codecs) + { + c = list_entry(l, struct ac97_codec, list); + if(c->driver != NULL || ((c->model ^ driver->codec_id) & driver->codec_mask)) + continue; + if(driver->probe(c, driver)) + continue; + c->driver = driver; + } + up(&codec_sem); + return 0; +} + +EXPORT_SYMBOL(ac97_register_driver); + +/** + * ac97_unregister_driver - unregister a codec helper + * @driver: Driver handler + * + * Register a handler for codecs matching the codec id. The handler + * attach function is called for all present codecs and will be + * called when new codecs are discovered. + */ + +void ac97_unregister_driver(struct ac97_driver *driver) +{ + struct list_head *l; + struct ac97_codec *c; + + down(&codec_sem); + list_del_init(&driver->list); + + list_for_each(l, &codecs) + { + c = list_entry(l, struct ac97_codec, list); + if(c->driver == driver) + driver->remove(c, driver); + c->driver = NULL; + } + + up(&codec_sem); +} + +EXPORT_SYMBOL(ac97_unregister_driver); + MODULE_LICENSE("GPL"); Index: drivers/sound/wm97xx.h =================================================================== --- drivers/sound/wm97xx.h (revision 0) +++ linux/drivers/sound/wm97xx.h (revision 33) @@ -0,0 +1,111 @@ + +/* + * Register bits for Wolfson WM97xx series of codecs + */ + +#ifndef _WM97XX_H_ +#define _WM97XX_H_ + +#include /* AC97 control layer */ + +/* + * WM97xx AC97 Touchscreen registers + */ +#define AC97_WM97XX_DIGITISER1 0x76 +#define AC97_WM97XX_DIGITISER2 0x78 +#define AC97_WM97XX_DIGITISER_RD 0x7a + +#define AC97_WM9713_DIG1 0x74 +#define AC97_WM9713_DIG2 AC97_WM97XX_DIGITISER1 +#define AC97_WM9713_DIG3 AC97_WM97XX_DIGITISER2 +#define AC97_WM9713_DIG_RD AC97_WM97XX_DIGITISER_RD + +/* + * WM97xx register bits + */ +#define WM97XX_POLL 0x8000 /* initiate a polling measurement */ +#define WM97XX_ADCSEL_X 0x1000 /* x coord measurement */ +#define WM97XX_ADCSEL_Y 0x2000 /* y coord measurement */ +#define WM97XX_ADCSEL_PRES 0x3000 /* pressure measurement */ +#define WM97XX_COO 0x0800 /* enable coordinate mode */ +#define WM97XX_CTC 0x0400 /* enable continuous mode */ +#define WM97XX_CM_RATE_93 0x0000 /* 93.75Hz continuous rate */ +#define WM97XX_CM_RATE_187 0x0100 /* 187.5Hz continuous rate */ +#define WM97XX_CM_RATE_375 0x0200 /* 375Hz continuous rate */ +#define WM97XX_CM_RATE_750 0x0300 /* 750Hz continuous rate */ +#define WM97XX_CM_RATE_8K 0x00f0 /* 8kHz continuous rate */ +#define WM97XX_CM_RATE_12K 0x01f0 /* 12kHz continuous rate */ +#define WM97XX_CM_RATE_24K 0x02f0 /* 24kHz continuous rate */ +#define WM97XX_CM_RATE_48K 0x03f0 /* 48kHz continuous rate */ +#define WM97XX_DELAY(i) ((i << 4) & 0x00f0) /* sample delay times */ +#define WM97XX_SLEN 0x0008 /* slot read back enable */ +#define WM97XX_SLT(i) ((i - 5) & 0x7) /* touchpanel slot selection (5-11) */ +#define WM97XX_PRP_DETW 0x4000 /* pen detect on, digitiser off, wake up */ +#define WM97XX_PRP_DET 0x8000 /* pen detect on, digitiser off, no wake up */ +#define WM97XX_PRP_DET_DIG 0xc000 /* pen detect on, digitiser on */ +#define WM97XX_RPR 0x2000 /* wake up on pen down */ +#define WM97XX_PEN_DOWN 0x8000 /* pen is down */ + +/* WM9712 Bits */ +#define WM9712_45W 0x1000 /* set for 5-wire touchscreen */ +#define WM9712_PDEN 0x0800 /* measure only when pen down */ +#define WM9712_WAIT 0x0200 /* wait until adc is read before next sample */ +#define WM9712_PIL 0x0100 /* current used for pressure measurement. set 400uA else 200uA */ +#define WM9712_MASK_HI 0x0040 /* hi on mask pin (47) stops conversions */ +#define WM9712_MASK_EDGE 0x0080 /* rising/falling edge on pin delays sample */ +#define WM9712_MASK_SYNC 0x00c0 /* rising/falling edge on mask initiates sample */ +#define WM9712_RPU(i) (i&0x3f) /* internal pull up on pen detect (64k / rpu) */ +#define WM9712_ADCSEL_COMP1 0x4000 /* COMP1/AUX1 measurement (pin29) */ +#define WM9712_ADCSEL_COMP2 0x5000 /* COMP2/AUX2 measurement (pin30) */ +#define WM9712_ADCSEL_BMON 0x6000 /* BMON/AUX3 measurement (pin31) */ +#define WM9712_ADCSEL_WIPER 0x7000 /* WIPER/AUX4 measurement (pin12) */ +#define WM9712_PD(i) (0x1 << i) /* power management */ + +/* WM9712 Registers */ +#define AC97_WM9712_POWER 0x24 +#define AC97_WM9712_REV 0x58 + +/* WM9705 Bits */ +#define WM9705_PDEN 0x1000 /* measure only when pen is down */ +#define WM9705_PINV 0x0800 /* inverts sense of pen down output */ +#define WM9705_BSEN 0x0400 /* BUSY flag enable, pin47 is 1 when busy */ +#define WM9705_BINV 0x0200 /* invert BUSY (pin47) output */ +#define WM9705_WAIT 0x0100 /* wait until adc is read before next sample */ +#define WM9705_PIL 0x0080 /* current used for pressure measurement. set 400uA else 200uA */ +#define WM9705_PHIZ 0x0040 /* set PHONE and PCBEEP inputs to high impedance */ +#define WM9705_MASK_HI 0x0010 /* hi on mask stops conversions */ +#define WM9705_MASK_EDGE 0x0020 /* rising/falling edge on pin delays sample */ +#define WM9705_MASK_SYNC 0x0030 /* rising/falling edge on mask initiates sample */ +#define WM9705_PDD(i) (i & 0x000f) /* pen detect comparator threshold */ +#define WM9705_ADCSEL_BMON 0x4000 /* BMON measurement */ +#define WM9705_ADCSEL_AUX 0x5000 /* AUX measurement */ +#define WM9705_ADCSEL_PHONE 0x6000 /* PHONE measurement */ +#define WM9705_ADCSEL_PCBEEP 0x7000 /* PCBEEP measurement */ + +/* WM9713 Bits */ +#define WM9713_PDPOL 0x0400 /* Pen down polarity */ +#define WM9713_POLL 0x0200 /* initiate a polling measurement */ +#define WM9713_CTC 0x0100 /* enable continuous mode */ +#define WM9713_ADCSEL_X 0x0002 /* X measurement */ +#define WM9713_ADCSEL_Y 0x0004 /* Y measurement */ +#define WM9713_ADCSEL_PRES 0x0008 /* Pressure measurement */ +#define WM9713_COO 0x0001 /* enable coordinate mode */ + +/* AUX ADC ID's */ +#define TS_COMP1 0x0 +#define TS_COMP2 0x1 +#define TS_BMON 0x2 +#define TS_WIPER 0x3 + +/* ID numbers */ +#define WM97XX_ID1 0x574d +#define WM9712_ID2 0x4c12 +#define WM9705_ID2 0x4c05 +#define WM9713_ID2 0x4c13 + +#define AC97_LINK_FRAME 21 /* time in uS for AC97 link frame */ + +void register_touchscreen_codec(struct ac97_codec *codec); +void unregister_touchscreen_codec(struct ac97_codec *codec); + +#endif Index: drivers/sound/Makefile =================================================================== --- drivers/sound/Makefile (.../vendor/arm/linux) (revision 33) +++ linux/drivers/sound/Makefile (.../branches/development/linux) (revision 33) @@ -86,6 +86,9 @@ obj-$(CONFIG_SOUND_RME96XX) += rme96xx.o obj-$(CONFIG_SOUND_BT878) += btaudio.o obj-$(CONFIG_SOUND_IT8172) += ite8172.o ac97_codec.o +obj-$(CONFIG_SOUND_WM97XX) += ac97_plugin_wm97xx.o +obj-$(CONFIG_SOUND_WM9713) += ac97_plugin_wm9713.o +obj-$(CONFIG_SOUND_PXA_WM8753) += pxa-wm8753.o pxa-audio.o ifeq ($(CONFIG_MIDI_EMU10K1),y) obj-$(CONFIG_SOUND_EMU10K1) += sound.o Index: drivers/sound/ac97_plugin_wm9713.c =================================================================== --- drivers/sound/ac97_plugin_wm9713.c (revision 0) +++ linux/drivers/sound/ac97_plugin_wm9713.c (revision 33) @@ -0,0 +1,1080 @@ +/* + * ac97_plugin_wm9713.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 + * parts (c) Ian Molton + * + * 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 WM9713 + * - polling mode + * - coordinate polling + * - continuous mode + * - adjustable rpu/dpp settings + * - adjustable pressure current + * - adjustable sample settle delay + * - 4 and 5 wire touchscreens + * - pen down detection + * - power management + * + * TODO: + * - adjustable sample rate + * + * Revision history + * 4th Feb 2004 Initial version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* get_user,copy_to_user */ +#include + +#include "wm97xx.h" + +#define TS_NAME "wm9713" +#define TS_MINOR 16 +#define WM_TS_VERSION "0.4" +#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 WM9713 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 WM9713 operation mode"); +static int mode = 0; + +/* + * - 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; + +/* + * 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; + +/* + * - 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; + +struct wm9713_pcm_device { + void * pcm_audio; /* machine specific private audio data */ + struct file_operations * pcm_fops; +}; + +typedef struct { + int is_registered:1; /* Is the driver AC97 registered */ + int adc_errs; /* sample read back errors */ + spinlock_t lock; + struct ac97_codec *codec; +#if defined(CONFIG_PROC_FS) + struct proc_dir_entry *wm9713_ts_ps; +#endif +#if defined(CONFIG_PM) + struct pm_dev * pm; + int line_pgal:5; + int line_pgar:5; + int phone_pga:5; + int mic_pgal:5; + int mic_pgar:5; +#endif + struct input_dev *idev; + struct completion thread_init; + struct completion thread_exit; + struct task_struct *rtask; + struct semaphore sem; + struct wm9713_pcm_device *pcm; + int (*cont_mode)(void * ts); + int use_count; + int restart; +} wm9713_ts_t; + +static inline void poll_delay (void); +static int __init wm9713_ts_init_module(void); +static inline int pendown (wm9713_ts_t *ts); +static int wm9713_poll_read_adc (wm9713_ts_t* ts, u16 adcsel, u16* sample); +static void init_wm9713_phy(void); +static int wm9713_probe(struct ac97_codec *codec, struct ac97_driver *driver); +static void wm9713_remove(struct ac97_codec *codec, struct ac97_driver *driver); +static void wm9713_ts_cleanup_module(void); +static int wm9713_ts_evt_add(wm9713_ts_t* ts, u16 pressure, u16 x, u16 y); +static int wm9713_ts_evt_release(wm9713_ts_t* ts); +static int wm9713_machine_init(wm9713_ts_t * ts); + +#if defined(CONFIG_PM) +static int wm9713_pm_event(struct pm_dev *dev, pm_request_t rqst, void *data); +static void wm9713_suspend(void); +static void wm9713_resume(void); +static void wm9713_pga_save(wm9713_ts_t* ts); +static void wm9713_pga_restore(wm9713_ts_t* ts); +#endif + +/* we only support a single touchscreen */ +static struct input_dev wm9713_input; +static wm9713_ts_t wm9713_ts; + +/* + * Machine specific set up. + * + * This is for targets that can support the PEN down interrupt, + * streaming back touch data in an AC97 slot (not slot 1) or the + * WM9713 PCM codec. The streaming touch data is read back via the targets + * AC97 FIFO's + */ +#if defined(CONFIG_ARCH_1WMMX) +#include +#include +#include "wmmx-audio.h" + +#define WM9713_IRQ IRQ_AC97 + +static audio_stream_t wm9713_pcm_audio_out = { + name: "WM9713 PCM audio out", + dcmd: (DCMD_INCSRCADDR|DCMD_FLOWTRG|DCMD_BURST16|DCMD_WIDTH2), + drcmr: &DRCMRTXSS2DR, + dev_addr: __PREG(SSDR_(SSP_PORT2)), +}; + +static audio_stream_t wm9713_pcm_audio_in = { + name: "WM9713 PCM audio in", + dcmd: (DCMD_INCTRGADDR|DCMD_FLOWSRC|DCMD_BURST16|DCMD_WIDTH2), + drcmr: &DRCMRRXSS2DR, + dev_addr: __PREG(SSDR_(SSP_PORT2)), +}; + +static audio_state_t wm9713_pcm = { + output_stream: &wm9713_pcm_audio_out, + input_stream: &wm9713_pcm_audio_in, + client_ioctl: wm9713_pcm_ioctl, + sem: __MUTEX_INITIALIZER(wm9713_pcm.sem), +}; + +/* + * Sample the touchscreen in continous mode for Bulverde + */ +static int wmmx_cont_touch(wm9713_ts_t *ts) +{ + u16 x, y; + int count = 0; + + /* currently assuming x is followed by y from the FIFO */ + while (MISR & (1 << 2)) { + x = MODR & (0xffff); + y = MODR & (0xffff); + wm9713_ts_evt_add(ts, 0xffff, x, y); + count++; + } + return count; +} + +static int wmmx_init(wm9713_ts_t * ts) +{ + ts->cont_mode = wmmx_cont_touch; + return 0; +} + +#endif + +/* AC97 registration info */ +static struct ac97_driver wm9713_driver = { + codec_id: 0x574D4C13, + codec_mask: 0xFFFFFFFF, + name: "Wolfson WM9713 Touchscreen and VDAC", + probe: wm9713_probe, + remove: __devexit_p(wm9713_remove), +}; + +/* + * 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 +}; + +static int wm9713_machine_init(wm9713_ts_t * ts) +{ + int ret = 0; +#if defined(CONFIG_ARCH_1WMMX) + ret = wmmx_init(ts); +#endif + return ret; +} + +/* + * 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); +} + +/* + * Wait for POLL to go low + */ +static inline int poll_wait(wm9713_ts_t* ts) +{ + int timeout = 5 * delay; + + while ((ts->codec->codec_read(ts->codec, AC97_WM9713_DIG1) & WM9713_POLL) && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + return timeout; +} + +/* + * Read a sample from the adc in polling mode. + */ +static int wm9713_poll_read_adc (wm9713_ts_t* ts, u16 adcsel, u16* sample) +{ + u16 dig1; + + /* set up digitiser */ + dig1 = ts->codec->codec_read(ts->codec, AC97_WM9713_DIG1); + ts->codec->codec_write(ts->codec, AC97_WM9713_DIG1, dig1 | adcsel | + WM9713_POLL); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay(); + + if (poll_wait(ts) > 0) + *sample = ts->codec->codec_read(ts->codec, AC97_WM9713_DIG_RD); + else { + ts->adc_errs++; + printk ("adc sample timeout\n"); + return 0; + } + return 1; +} + +/* + * Read a sample from the adc in coordinate mode. + */ +static int wm9713_coord_read_adc(wm9713_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_WM9713_DIG1); + ts->codec->codec_write(ts->codec, AC97_WM9713_DIG1, dig1 | WM9713_ADCSEL_PRES | + WM9713_POLL | WM9713_COO); + + /* 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_WM9713_DIG_RD); + udelay (AC97_LINK_FRAME + delay_table[delay]); + + /* read Y */ + *y = ts->codec->codec_read(ts->codec, AC97_WM9713_DIG_RD); + + /* wait for POLL to go low and then read pressure */ + while ((ts->codec->codec_read(ts->codec, AC97_WM9713_DIG1) & WM9713_POLL)&& timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + if (timeout > 0) + *pressure = ts->codec->codec_read(ts->codec, AC97_WM9713_DIG_RD); + else { + ts->adc_errs++; + err ("adc sample timeout"); + return 0; + } + return 1; +} + +/* + * Sample the touchscreen in polling mode + */ +int wm9713_poll_touch(wm9713_ts_t *ts) +{ + u16 x, y, p; + + if (!wm9713_poll_read_adc(ts, WM9713_ADCSEL_X, &x)) + return -EIO; + if (!wm9713_poll_read_adc(ts, WM9713_ADCSEL_Y, &y)) + return -EIO; + if (pil && !five_wire) { + if (!wm9713_poll_read_adc(ts, WM9713_ADCSEL_PRES, &p)) + return -EIO; + } else { + p = 0xffff; + } + + wm9713_ts_evt_add(ts, p, x, y); + return 1; +} + +/* + * Sample the touchscreen in polling coordinate mode + */ +int wm9713_poll_coord_touch(wm9713_ts_t *ts) +{ + u16 x, y, p; + + if (wm9713_coord_read_adc(ts, &x, &y, &p)) { + wm9713_ts_evt_add(ts, p, x, y); + return 1; + } else + return -EIO; +} + +/* + * Is the pen down ? + */ +static inline int pendown (wm9713_ts_t *ts) +{ + return ts->codec->codec_read(ts->codec, AC97_WM97XX_DIGITISER_RD) + & WM97XX_PEN_DOWN; +} + +#if defined(CONFIG_PROC_FS) +static int wm9713_read_proc (char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0, prpu, i; + u16 dig1, dig2, dig3, digrd, adcsel, adcsrc, slt, prp, rev; + unsigned long flags; + wm9713_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; + } + + for (i=0; i< 0x42; i+=2) + len += sprintf(page+len, "%x : %x\n", i, ts->codec->codec_read(ts->codec, i)); + + dig1 = ts->codec->codec_read(ts->codec, AC97_WM9713_DIG1); + dig2 = ts->codec->codec_read(ts->codec, AC97_WM9713_DIG2); + dig3 = ts->codec->codec_read(ts->codec, AC97_WM9713_DIG3); + + ts->codec->codec_write(ts->codec, AC97_WM97XX_DIGITISER1, dig1 | WM97XX_POLL); + poll_delay(); + + 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 & 0x00fe) >> 1; + adcsrc = digrd & 0x7000; + slt = (dig2 & 0x0007); + prp = dig3 & 0xc000; + prpu = dig3 & 0x003f; + + len += sprintf (page+len, "Wolfson WM9713 Version %s\n", WM_TS_VERSION); + + len += sprintf (page+len, "Settings :\n%s%s%s%s", + dig1 & WM97XX_POLL ? " -sampling adc data(poll)\n" : "", + adcsel == WM9713_ADCSEL_X ? " -adc set to X coordinate\n" : "", + adcsel == WM9713_ADCSEL_Y ? " -adc set to Y coordinate\n" : "", + adcsel == WM9713_ADCSEL_PRES ? " -adc set to pressure\n" : ""); + + 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" : ""); + + len += sprintf (page+len, "%s%s%s%s%s%s", + dig1 & WM9713_COO ? " -coordinate sampling\n" : " -individual sampling\n", + dig1 & WM9713_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" : "", + dig2 & WM97XX_SLEN ? " -read back using slot " : " -read back using AC97\n"); + + if ((dig2 & 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 (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"); + + len += sprintf(page+len, "\nADC data:\n%s%d\n%s%s\n", + " -adc value (decimal) : ", digrd & 0x0fff, + " -pen ", digrd & 0x8000 ? "Down" : "Up"); + + 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" : ""); + + 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); + + len += sprintf(page+len, "\nErrors:\n%s%d\n", + " -coordinate errors ", ts->adc_errs); + + return len; +} + +#endif + +#if defined(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 wm9713_pm_event(struct pm_dev *dev, pm_request_t rqst, void *data) +{ + switch(rqst) { + case PM_SUSPEND: + wm9713_suspend(); + break; + case PM_RESUME: + wm9713_resume(); + break; + } + return 0; +} + +/* + * Power down the codec + */ +static void wm9713_suspend(void) +{ + wm9713_ts_t* ts = &wm9713_ts; + u16 reg; + unsigned long flags; + + spin_lock_irqsave(&ts->lock, flags); + if (!ts->is_registered) { + spin_unlock_irqrestore(&ts->lock, flags); + return; + } + + /* save and mute the PGA's */ + wm9713_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); + + /* LIAM need 13 spec stuff here 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 wm9713_resume(void) +{ + wm9713_ts_t* ts = &wm9713_ts; + unsigned long flags; + + /* are we registered */ + spin_lock_irqsave(&ts->lock, flags); + if (!ts->is_registered) { + spin_unlock_irqrestore(&ts->lock, flags); + return; + } + + /* power up */ + ts->codec->codec_write(ts->codec, AC97_WM9712_POWER, 0x0); + + /* restore PGA state */ + wm9713_pga_restore(ts); + + spin_unlock_irqrestore(&ts->lock, flags); +} + +/* save state of wm9713 PGA's */ +static void wm9713_pga_save(wm9713_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 wm9713 PGA's */ +static void wm9713_pga_restore(wm9713_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_wm9713_phy(void) +{ + u16 dig1 = 0, dig2, dig3; + wm9713_ts_t *ts = &wm9713_ts; + + /* default values */ + dig2 = WM97XX_DELAY(4) | WM97XX_SLT(5); + dig3= WM9712_RPU(1); + + /* rpu */ + if (rpu) { + dig3 &= 0xffc0; + dig3 |= WM9712_RPU(rpu); + info("setting pen detect pull-up to %d Ohms",64000 / rpu); + } + + /* touchpanel pressure */ + if (pil == 2) { + dig3 |= WM9712_PIL; + info("setting pressure measurement current to 400uA."); + } else if (pil) + info ("setting pressure measurement current to 200uA."); + + /* five wire */ + if (five_wire) { + dig3 |= 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; + } + dig2 &= 0xff0f; + dig2 |= WM97XX_DELAY(delay); + info("setting adc sample delay to %d u Secs.", delay_table[delay]); + } + + /* coordinate mode */ + if (mode == 1) { + dig1 |= WM9713_CTC; + info("using coordinate mode"); + } + + /* continous mode */ + if (mode == 2) { + if (ts->cont_mode) { + dig2 |= WM97XX_CM_RATE_375 | WM97XX_SLEN | WM97XX_SLT(5); + dig1 |= WM9713_CTC | WM9713_COO; + info("using continous mode"); + dig3 |= WM9712_PDEN; + } else { + mode = 0; + err("continous mode not supported on this machine"); + } + } + + ts->codec->codec_write(ts->codec, AC97_WM9713_DIG1, dig1); + ts->codec->codec_write(ts->codec, AC97_WM9713_DIG2, dig2); + ts->codec->codec_write(ts->codec, AC97_WM9713_DIG3, dig3); +} + + +/* + * Called by the audio codec initialisation to register + * the touchscreen driver. + */ + +static int wm9713_probe(struct ac97_codec *codec, struct ac97_driver *driver) +{ + unsigned long flags; + u16 id1, id2; + wm9713_ts_t *ts = &wm9713_ts; +printk("here 1\n"); + spin_lock_irqsave(&ts->lock, flags); + + /* we only support 1 touchscreen at the moment */ + if (ts->is_registered) { + spin_unlock_irqrestore(&ts->lock, flags); + printk("here 2\n"); + return -1; + } + + /* + * We can only use a WM9713 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 WM9713 is found then get the codec read and write + * functions. + */ + + /* test for a WM9713 */ + id1 = codec->codec_read(codec, AC97_VENDOR_ID1); + id2 = codec->codec_read(codec, AC97_VENDOR_ID2); + if (id1 == WM97XX_ID1 && id2 == WM9713_ID2) { + info("registered a WM9713"); + } 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; + + /* set up physical characteristics */ + init_wm9713_phy(); + + ts->is_registered = 1; + spin_unlock_irqrestore(&ts->lock, flags); + return 0; +} + +/* + * Called by ac97_codec when it is unloaded. + */ +static void wm9713_remove(struct ac97_codec *codec, struct ac97_driver *driver) +{ + unsigned long flags; + u16 dig2, dig3; + wm9713_ts_t *ts = codec->driver_private; + + dbg("Unloading codec\n"); + spin_lock_irqsave(&ts->lock, flags); + + /* check that are registered */ + if (!ts->is_registered) { + err("double unregister"); + spin_unlock_irqrestore(&ts->lock, flags); + return; + } + + ts->is_registered = 0; + + /* restore default digitiser values */ + dig2 = WM97XX_DELAY(4) | WM97XX_SLT(6); + dig3 = WM9712_RPU(1); + + codec->codec_write(codec, AC97_WM9713_DIG2, dig2); + codec->codec_write(codec, AC97_WM9713_DIG3, dig3); + ts->codec = NULL; + + spin_unlock_irqrestore(&ts->lock, flags); +} + +/* + * add a touch event + */ +static int wm9713_ts_evt_add(wm9713_ts_t* ts, u16 pressure, u16 x, u16 y) +{ + /* add event and remove adc src bits */ + printk("x %d y %d p %d\n", x&0xfff,y&0xfff,pressure); + input_report_abs(ts->idev, ABS_X, x & 0xfff); + input_report_abs(ts->idev, ABS_Y, y & 0xfff); + input_report_abs(ts->idev, ABS_PRESSURE, pressure & 0xfff); + return 0; +} + +/* + * add a pen up event + */ +static int wm9713_ts_evt_release(wm9713_ts_t* ts) +{ +// dbg("release the stylus.\n"); + input_report_abs(ts->idev, ABS_PRESSURE, 0); + return 0; +} + +/* + * The touchscreen sample reader thread + */ +static int wm9713_thread(void *_ts) +{ + wm9713_ts_t *ts = (wm9713_ts_t *)_ts; + struct task_struct *tsk = current; + int valid = 0; + + ts->rtask = tsk; + + /* set up thread context */ + daemonize(); + reparent_to_init(); + strcpy(tsk->comm, "ktsd"); + tsk->tty = NULL; + + /* we will die when we receive SIGKILL */ + spin_lock_irq(&tsk->sigmask_lock); + siginitsetinv(&tsk->blocked, sigmask(SIGKILL)); + recalc_sigpending(tsk); + spin_unlock_irq(&tsk->sigmask_lock); + + /* init is complete */ + complete(&ts->thread_init); + + /* touch reader loop */ + for (;;) { + ts->restart = 0; + if(signal_pending(tsk)) + break; + + if(pendown(ts)) { + switch (mode) { + case 0: + wm9713_poll_touch(ts); + valid = 1; + break; + case 1: + wm9713_poll_coord_touch(ts); + valid = 1; + break; + case 2: + ts->cont_mode(ts); + valid = 1; + break; + } + } else { + if (valid) { + valid = 0; + wm9713_ts_evt_release(ts); + } + } + + set_task_state(tsk, TASK_INTERRUPTIBLE); + if (HZ >= 100) + schedule_timeout(HZ/100); + else + schedule_timeout(1); + } + ts->rtask = NULL; + complete_and_exit(&ts->thread_exit, 0); + + return 0; +} + +/* + * Start the touchscreen thread and + * the touch digitiser. + */ +static int wm9713_ts_input_open(struct input_dev *idev) +{ + wm9713_ts_t *ts = (wm9713_ts_t *) &wm9713_ts; + u32 flags; + int ret, val; + + spin_lock_irqsave( &ts->lock, flags ); + if ( ts->use_count++ == 0 ) { + + /* start touchscreen thread */ + ts->idev = idev; + init_completion(&ts->thread_init); + ret = kernel_thread(wm9713_thread, ts, 0); + if (ret >= 0) + wait_for_completion(&ts->thread_init); + + /* start digitiser */ + val = ts->codec->codec_read(ts->codec, AC97_EXTENDED_MODEM_ID); + ts->codec->codec_write(ts->codec, AC97_EXTENDED_MODEM_ID, + val & 0x7fff); + val = ts->codec->codec_read(ts->codec, AC97_WM9713_DIG3); + ts->codec->codec_write(ts->codec, AC97_WM9713_DIG3, + val | WM97XX_PRP_DET_DIG); + } + spin_unlock_irqrestore( &ts->lock, flags ); + return 0; +} + +/* + * Kill the touchscreen thread and stop + * the touch digitiser. + */ +static void wm9713_ts_input_close(struct input_dev *idev) +{ + wm9713_ts_t *ts = (wm9713_ts_t *) &wm9713_ts; + u32 flags; + int val; + + spin_lock_irqsave(&ts->lock, flags); + if (--ts->use_count == 0) { + /* kill thread */ + init_completion(&ts->thread_exit); + if (ts->rtask) { + send_sig(SIGKILL, ts->rtask, 1); + wait_for_completion(&ts->thread_exit); + } + + /* stop digitiser */ + val = ts->codec->codec_read(ts->codec, AC97_WM9713_DIG3); + ts->codec->codec_write(ts->codec, AC97_WM9713_DIG3, + val & ~WM97XX_PRP_DET_DIG); + val = ts->codec->codec_read(ts->codec, AC97_EXTENDED_MODEM_ID); + ts->codec->codec_write(ts->codec, AC97_EXTENDED_MODEM_ID, + val | 0x8000); + } + spin_unlock_irqrestore(&ts->lock, flags); +} + +static int __init wm9713_ts_init_module(void) +{ + wm9713_ts_t* ts = &wm9713_ts; + char proc_str[64]; + + info("Wolfson WM9713 Touchscreen and PCM DAC Controller"); + info("Version %s liam.girdwood@wolfsonmicro.com", WM_TS_VERSION); + + memset(ts, 0, sizeof(wm9713_ts_t)); + wm9713_machine_init(&wm9713_ts); + + /* tell input system what we events we accept and register */ + wm9713_input.name = "wm9713 touchscreen"; + wm9713_input.open = wm9713_ts_input_open; + wm9713_input.close = wm9713_ts_input_close; + __set_bit(EV_ABS, wm9713_input.evbit); + __set_bit(ABS_X, wm9713_input.absbit); + __set_bit(ABS_Y, wm9713_input.absbit); + __set_bit(ABS_PRESSURE, wm9713_input.absbit); + input_register_device(&wm9713_input); + + spin_lock_init(&ts->lock); + init_MUTEX(&ts->sem); + + /* register with the AC97 layer */ + ac97_register_driver(&wm9713_driver); + +#if defined(CONFIG_PROC_FS) + sprintf(proc_str, "driver/%s", TS_NAME); + if ((ts->wm9713_ts_ps = create_proc_read_entry (proc_str, 0, NULL, + wm9713_read_proc, ts)) == 0) + err("could not register proc interface /proc/%s", proc_str); +#endif +#if defined(CONFIG_PM) + if ((ts->pm = pm_register(PM_UNKNOWN_DEV, PM_SYS_UNKNOWN, wm9713_pm_event)) == 0) + err("could not register with power management"); +#endif + + return 0; +} + +static void wm9713_ts_cleanup_module(void) +{ +#if defined(CONFIG_PM) + pm_unregister (wm9713_ts.pm); +#endif + input_unregister_device(&wm9713_input); +} + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com"); +MODULE_DESCRIPTION("WM9713 Touch Screen / PCM DAC Driver"); +MODULE_LICENSE("GPL"); + +module_init(wm9713_ts_init_module); +module_exit(wm9713_ts_cleanup_module); + +#ifndef MODULE + +static int __init wm9713_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=strsep(options, ","); + this_opt; this_opt=strsep(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, "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("wm9713_ts=", wm9713_ts_setup); + +#endif /* MODULE */ Index: drivers/sound/pxa-ac97.c =================================================================== --- drivers/sound/pxa-ac97.c (.../vendor/arm/linux) (revision 33) +++ linux/drivers/sound/pxa-ac97.c (.../branches/development/linux) (revision 33) @@ -8,6 +8,13 @@ * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. + * + * AC97 GPIO Changes:- + * In order to read/write codec GPIO bits using AC97 link slot 12, + * all IO to AC97_GPIO_STATUS must be via the Xscale modem codec + * address space. This is currently broken in PXA270 A,B and C0 + * silicon, so we use the slower slot 1/2 method. + * Liam Girdwood */ #include @@ -72,13 +79,7 @@ ret = ac97_probe_codec(codec); if (ret != 1) printk(KERN_INFO "After resume AC97codec fail.\n"); - /* little hack for UCB1400 (should be moved elsewhere) */ - pxa_ac97_write(codec,AC97_EXTENDED_STATUS,1); - /* pxa_ac97_write(&pxa_ac97_codec, 0x6a, 0x1ff7); */ - pxa_ac97_write(codec, 0x6a, 0x0050); - pxa_ac97_write(codec, 0x6c, 0x0030); - } if (rqst == PM_SUSPEND) { /* @@ -103,7 +104,20 @@ down(&CAR_mutex); if (!(CAR & CAR_CAIP)) { - volatile u32 *reg_addr = (u32 *)&PAC_REG_BASE + (reg >> 1); + volatile u32 *reg_addr; + +#if 0 + /* if we are reading the GPIO status then this is cached + * in hardware so we don't need to read over the link. + * NOTE: This is broken in PXA27x A,B and C0 silicon + */ + if (reg == AC97_GPIO_STATUS) { + reg_addr = (u32 *)&PMC_REG_BASE + (reg >> 1); + return *reg_addr; + } +#endif + + reg_addr = (u32 *)&PAC_REG_BASE + (reg >> 1); waitingForMask=GSR_SDONE; @@ -132,8 +146,22 @@ { down(&CAR_mutex); if (!(CAR & CAR_CAIP)) { - volatile u32 *reg_addr = (u32 *)&PAC_REG_BASE + (reg >> 1); + volatile u32 *reg_addr; +#if 0 + /* if we are writing to the codec GPIO using slot 12 + * then we have to write to the modem register space + * NOTE: This is broken in PXA27x A,B and C0 silicon + */ + if (reg == AC97_GPIO_STATUS) { + reg_addr = (u32 *)&PMC_REG_BASE + (reg >> 1); + *reg_addr = val; + return; + } +#endif + + reg_addr = (u32 *)&PAC_REG_BASE + (reg >> 1); + waitingForMask=GSR_CDONE; init_completion(&CAR_completion); *reg_addr = val; @@ -174,6 +202,13 @@ return ret; CKEN |= CKEN2_AC97; +#if defined(CONFIG_ARCH_MAINSTONE) + /* enable MSII daughter card clock */ + GPDR(1) |= (1 << 13); + GAFR1_L &= 0xf3ffffff; + GAFR1_L |= (0x01 << 26); +#endif + set_GPIO_mode(GPIO31_SYNC_AC97_MD); set_GPIO_mode(GPIO30_SDATA_OUT_AC97_MD); set_GPIO_mode(GPIO28_BITCLK_AC97_MD); @@ -188,9 +223,20 @@ MST_MSCWR2 &= ~MSCWR2_LINE1; #endif +#if defined(CONFIG_SOUND_AC97_WARM) +#if defined(CONFIG_CPU_BULVERDE) + /* warm reset broken on Bulverde */ + GAFR3_U &= ~0xC; + GPSR3 = 0x20000 | GPLR3; +#endif + GCR |= GCR_COLD_RST|GCR_CDONE_IE|GCR_SDONE_IE; + udelay(10); + GCR |= GCR_WARM_RST|GCR_CDONE_IE|GCR_SDONE_IE; +#else GCR = 0; udelay(10); GCR = GCR_COLD_RST|GCR_CDONE_IE|GCR_SDONE_IE; +#endif while (!(GSR & GSR_PCR)) { schedule(); } @@ -202,12 +248,6 @@ CKEN &= ~CKEN2_AC97; return ret; } - - // need little hack for UCB1400 (should be moved elsewhere) - pxa_ac97_write(&pxa_ac97_codec,AC97_EXTENDED_STATUS,1); - //pxa_ac97_write(&pxa_ac97_codec, 0x6a, 0x1ff7); - pxa_ac97_write(&pxa_ac97_codec, 0x6a, 0x0050); - pxa_ac97_write(&pxa_ac97_codec, 0x6c, 0x0030); } pxa_ac97_refcount++; @@ -425,4 +465,3 @@ module_init(pxa_ac97_init); module_exit(pxa_ac97_exit); -