diff options
Diffstat (limited to 'drivers/input/touchscreen')
27 files changed, 9873 insertions, 0 deletions
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig new file mode 100644 index 0000000..3d1ab8f --- /dev/null +++ b/drivers/input/touchscreen/Kconfig @@ -0,0 +1,379 @@ +# +# Touchscreen driver configuration +# +menuconfig INPUT_TOUCHSCREEN + bool "Touchscreens" + help + Say Y here, and a list of supported touchscreens will be displayed. + This option doesn't affect the kernel. + + If unsure, say Y. + +if INPUT_TOUCHSCREEN + +config TOUCHSCREEN_ADS7846 + tristate "ADS7846/TSC2046 and ADS7843 based touchscreens" + depends on SPI_MASTER + depends on HWMON = n || HWMON + help + Say Y here if you have a touchscreen interface using the + ADS7846/TSC2046 or ADS7843 controller, and your board-specific + setup code includes that in its table of SPI devices. + + If HWMON is selected, and the driver is told the reference voltage + on your board, you will also get hwmon interfaces for the voltage + (and on ads7846/tsc2046, temperature) sensors of this chip. + + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called ads7846. + +config TOUCHSCREEN_BITSY + tristate "Compaq iPAQ H3600 (Bitsy) touchscreen" + depends on SA1100_BITSY + select SERIO + help + Say Y here if you have the h3600 (Bitsy) touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called h3600_ts_input. + +config TOUCHSCREEN_CORGI + tristate "SharpSL (Corgi and Spitz series) touchscreen driver (DEPRECATED)" + depends on PXA_SHARPSL + select CORGI_SSP_DEPRECATED + default y + help + Say Y here to enable the driver for the touchscreen on the + Sharp SL-C7xx and SL-Cxx00 series of PDAs. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called corgi_ts. + + NOTE: this driver is deprecated, try enable SPI and generic + ADS7846-based touchscreen driver. + +config TOUCHSCREEN_FUJITSU + tristate "Fujitsu serial touchscreen" + select SERIO + help + Say Y here if you have the Fujitsu touchscreen (such as one + installed in Lifebook P series laptop) connected to your + system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called fujitsu-ts. + +config TOUCHSCREEN_GUNZE + tristate "Gunze AHL-51S touchscreen" + select SERIO + help + Say Y here if you have the Gunze AHL-51 touchscreen connected to + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called gunze. + +config TOUCHSCREEN_ELO + tristate "Elo serial touchscreens" + select SERIO + help + Say Y here if you have an Elo serial touchscreen connected to + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called elo. + +config TOUCHSCREEN_MTOUCH + tristate "MicroTouch serial touchscreens" + select SERIO + help + Say Y here if you have a MicroTouch (3M) serial touchscreen connected to + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mtouch. + +config TOUCHSCREEN_INEXIO + tristate "iNexio serial touchscreens" + select SERIO + help + Say Y here if you have an iNexio serial touchscreen connected to + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called inexio. + +config TOUCHSCREEN_MK712 + tristate "ICS MicroClock MK712 touchscreen" + help + Say Y here if you have the ICS MicroClock MK712 touchscreen + controller chip in your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mk712. + +config TOUCHSCREEN_HP600 + tristate "HP Jornada 6xx touchscreen" + depends on SH_HP6XX && SH_ADC + help + Say Y here if you have a HP Jornada 620/660/680/690 and want to + support the built-in touchscreen. + + To compile this driver as a module, choose M here: the + module will be called hp680_ts_input. + +config TOUCHSCREEN_HP7XX + tristate "HP Jornada 7xx touchscreen" + depends on SA1100_JORNADA720_SSP + help + Say Y here if you have a HP Jornada 710/720/728 and want + to support the built-in touchscreen. + + To compile this driver as a module, choose M here: the + module will be called jornada720_ts. + +config TOUCHSCREEN_HTCPEN + tristate "HTC Shift X9500 touchscreen" + depends on ISA + help + Say Y here if you have an HTC Shift UMPC also known as HTC X9500 + Clio / Shangrila and want to support the built-in touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called htcpen. + +config TOUCHSCREEN_PENMOUNT + tristate "Penmount serial touchscreen" + select SERIO + help + Say Y here if you have a Penmount serial touchscreen connected to + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called penmount. + +config TOUCHSCREEN_MIGOR + tristate "Renesas MIGO-R touchscreen" + depends on SH_MIGOR && I2C + help + Say Y here to enable MIGO-R touchscreen support. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called migor_ts. + +config TOUCHSCREEN_TOUCHRIGHT + tristate "Touchright serial touchscreen" + select SERIO + help + Say Y here if you have a Touchright serial touchscreen connected to + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called touchright. + +config TOUCHSCREEN_TOUCHWIN + tristate "Touchwin serial touchscreen" + select SERIO + help + Say Y here if you have a Touchwin serial touchscreen connected to + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called touchwin. + +config TOUCHSCREEN_ATMEL_TSADCC + tristate "Atmel Touchscreen Interface" + depends on ARCH_AT91SAM9RL + help + Say Y here if you have a 4-wire touchscreen connected to the + ADC Controller on your Atmel SoC (such as the AT91SAM9RL). + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called atmel_tsadcc. + +config TOUCHSCREEN_UCB1400 + tristate "Philips UCB1400 touchscreen" + depends on AC97_BUS + depends on UCB1400_CORE + help + This enables support for the Philips UCB1400 touchscreen interface. + The UCB1400 is an AC97 audio codec. The touchscreen interface + will be initialized only after the ALSA subsystem has been + brought up and the UCB1400 detected. You therefore have to + configure ALSA support as well (either built-in or modular, + independently of whether this driver is itself built-in or + modular) for this driver to work. + + To compile this driver as a module, choose M here: the + module will be called ucb1400_ts. + +config TOUCHSCREEN_WM97XX + tristate "Support for WM97xx AC97 touchscreen controllers" + depends on AC97_BUS + help + Say Y here if you have a Wolfson Microelectronics WM97xx + touchscreen connected to your system. Note that this option + only enables core driver, you will also need to select + support for appropriate chip below. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called wm97xx-ts. + +config TOUCHSCREEN_WM9705 + bool "WM9705 Touchscreen interface support" + depends on TOUCHSCREEN_WM97XX + default y + help + Say Y here to enable support for the Wolfson Microelectronics + WM9705 touchscreen controller. + +config TOUCHSCREEN_WM9712 + bool "WM9712 Touchscreen interface support" + depends on TOUCHSCREEN_WM97XX + default y + help + Say Y here to enable support for the Wolfson Microelectronics + WM9712 touchscreen controller. + +config TOUCHSCREEN_WM9713 + bool "WM9713 Touchscreen interface support" + depends on TOUCHSCREEN_WM97XX + default y + help + Say Y here to enable support for the Wolfson Microelectronics + WM9713 touchscreen controller. + +config TOUCHSCREEN_WM97XX_MAINSTONE + tristate "WM97xx Mainstone accelerated touch" + depends on TOUCHSCREEN_WM97XX && ARCH_PXA + help + Say Y here for support for streaming mode with WM97xx touchscreens + on Mainstone systems. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called mainstone-wm97xx. + +config TOUCHSCREEN_USB_COMPOSITE + tristate "USB Touchscreen Driver" + depends on USB_ARCH_HAS_HCD + select USB + help + USB Touchscreen driver for: + - eGalax Touchkit USB (also includes eTurboTouch CT-410/510/700) + - PanJit TouchSet USB + - 3M MicroTouch USB (EX II series) + - ITM + - some other eTurboTouch + - Gunze AHL61 + - DMC TSC-10/25 + - IRTOUCHSYSTEMS/UNITOP + - IdealTEK URTC1000 + - GoTop Super_Q2/GogoPen/PenPower tablets + + Have a look at <http://linux.chapter7.ch/touchkit/> for + a usage description and the required user-space stuff. + + To compile this driver as a module, choose M here: the + module will be called usbtouchscreen. + +config TOUCHSCREEN_USB_EGALAX + default y + bool "eGalax, eTurboTouch CT-410/510/700 device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_PANJIT + default y + bool "PanJit device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_3M + default y + bool "3M/Microtouch EX II series device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_ITM + default y + bool "ITM device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_ETURBO + default y + bool "eTurboTouch (non-eGalax compatible) device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_GUNZE + default y + bool "Gunze AHL61 device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_DMC_TSC10 + default y + bool "DMC TSC-10/25 device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_IRTOUCH + default y + bool "IRTOUCHSYSTEMS/UNITOP device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_IDEALTEK + default y + bool "IdealTEK URTC1000 device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_GENERAL_TOUCH + default y + bool "GeneralTouch Touchscreen device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_USB_GOTOP + default y + bool "GoTop Super_Q2/GogoPen/PenPower tablet device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + +config TOUCHSCREEN_TOUCHIT213 + tristate "Sahara TouchIT-213 touchscreen" + select SERIO + help + Say Y here if you have a Sahara TouchIT-213 Tablet PC. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called touchit213. + +endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile new file mode 100644 index 0000000..15cf290 --- /dev/null +++ b/drivers/input/touchscreen/Makefile @@ -0,0 +1,33 @@ +# +# Makefile for the touchscreen drivers. +# + +# Each configuration option enables a list of files. + +wm97xx-ts-y := wm97xx-core.o + +obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846.o +obj-$(CONFIG_TOUCHSCREEN_ATMEL_TSADCC) += atmel_tsadcc.o +obj-$(CONFIG_TOUCHSCREEN_BITSY) += h3600_ts_input.o +obj-$(CONFIG_TOUCHSCREEN_CORGI) += corgi_ts.o +obj-$(CONFIG_TOUCHSCREEN_GUNZE) += gunze.o +obj-$(CONFIG_TOUCHSCREEN_ELO) += elo.o +obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o +obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o +obj-$(CONFIG_TOUCHSCREEN_MIGOR) += migor_ts.o +obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o +obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o +obj-$(CONFIG_TOUCHSCREEN_HP600) += hp680_ts_input.o +obj-$(CONFIG_TOUCHSCREEN_HP7XX) += jornada720_ts.o +obj-$(CONFIG_TOUCHSCREEN_HTCPEN) += htcpen.o +obj-$(CONFIG_TOUCHSCREEN_USB_COMPOSITE) += usbtouchscreen.o +obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o +obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o +obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o +obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o +obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o +obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o +wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705) += wm9705.o +wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o +wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713) += wm9713.o +obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c new file mode 100644 index 0000000..b9b7fc6 --- /dev/null +++ b/drivers/input/touchscreen/ads7846.c @@ -0,0 +1,1233 @@ +/* + * ADS7846 based touchscreen and sensor driver + * + * Copyright (c) 2005 David Brownell + * Copyright (c) 2006 Nokia Corporation + * Various changes: Imre Deak <imre.deak@nokia.com> + * + * Using code from: + * - corgi_ts.c + * Copyright (C) 2004-2005 Richard Purdie + * - omap_ts.[hc], ads7846.h, ts_osk.c + * Copyright (C) 2002 MontaVista Software + * Copyright (C) 2004 Texas Instruments + * Copyright (C) 2005 Dirk Behme + * + * 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. + */ +#include <linux/hwmon.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/spi/spi.h> +#include <linux/spi/ads7846.h> +#include <asm/irq.h> + + +/* + * This code has been heavily tested on a Nokia 770, and lightly + * tested on other ads7846 devices (OSK/Mistral, Lubbock). + * TSC2046 is just newer ads7846 silicon. + * Support for ads7843 tested on Atmel at91sam926x-EK. + * Support for ads7845 has only been stubbed in. + * + * IRQ handling needs a workaround because of a shortcoming in handling + * edge triggered IRQs on some platforms like the OMAP1/2. These + * platforms don't handle the ARM lazy IRQ disabling properly, thus we + * have to maintain our own SW IRQ disabled status. This should be + * removed as soon as the affected platform's IRQ handling is fixed. + * + * app note sbaa036 talks in more detail about accurate sampling... + * that ought to help in situations like LCDs inducing noise (which + * can also be helped by using synch signals) and more generally. + * This driver tries to utilize the measures described in the app + * note. The strength of filtering can be set in the board-* specific + * files. + */ + +#define TS_POLL_DELAY (1 * 1000000) /* ns delay before the first sample */ +#define TS_POLL_PERIOD (5 * 1000000) /* ns delay between samples */ + +/* this driver doesn't aim at the peak continuous sample rate */ +#define SAMPLE_BITS (8 /*cmd*/ + 16 /*sample*/ + 2 /* before, after */) + +struct ts_event { + /* For portability, we can't read 12 bit values using SPI (which + * would make the controller deliver them as native byteorder u16 + * with msbs zeroed). Instead, we read them as two 8-bit values, + * *** WHICH NEED BYTESWAPPING *** and range adjustment. + */ + u16 x; + u16 y; + u16 z1, z2; + int ignore; +}; + +/* + * We allocate this separately to avoid cache line sharing issues when + * driver is used with DMA-based SPI controllers (like atmel_spi) on + * systems where main memory is not DMA-coherent (most non-x86 boards). + */ +struct ads7846_packet { + u8 read_x, read_y, read_z1, read_z2, pwrdown; + u16 dummy; /* for the pwrdown read */ + struct ts_event tc; +}; + +struct ads7846 { + struct input_dev *input; + char phys[32]; + + struct spi_device *spi; + +#if defined(CONFIG_HWMON) || defined(CONFIG_HWMON_MODULE) + struct attribute_group *attr_group; + struct device *hwmon; +#endif + + u16 model; + u16 vref_mv; + u16 vref_delay_usecs; + u16 x_plate_ohms; + u16 pressure_max; + + struct ads7846_packet *packet; + + struct spi_transfer xfer[18]; + struct spi_message msg[5]; + struct spi_message *last_msg; + int msg_idx; + int read_cnt; + int read_rep; + int last_read; + + u16 debounce_max; + u16 debounce_tol; + u16 debounce_rep; + + u16 penirq_recheck_delay_usecs; + + spinlock_t lock; + struct hrtimer timer; + unsigned pendown:1; /* P: lock */ + unsigned pending:1; /* P: lock */ +// FIXME remove "irq_disabled" + unsigned irq_disabled:1; /* P: lock */ + unsigned disabled:1; + unsigned is_suspended:1; + + int (*filter)(void *data, int data_idx, int *val); + void *filter_data; + void (*filter_cleanup)(void *data); + int (*get_pendown_state)(void); + int gpio_pendown; +}; + +/* leave chip selected when we're done, for quicker re-select? */ +#if 0 +#define CS_CHANGE(xfer) ((xfer).cs_change = 1) +#else +#define CS_CHANGE(xfer) ((xfer).cs_change = 0) +#endif + +/*--------------------------------------------------------------------------*/ + +/* The ADS7846 has touchscreen and other sensors. + * Earlier ads784x chips are somewhat compatible. + */ +#define ADS_START (1 << 7) +#define ADS_A2A1A0_d_y (1 << 4) /* differential */ +#define ADS_A2A1A0_d_z1 (3 << 4) /* differential */ +#define ADS_A2A1A0_d_z2 (4 << 4) /* differential */ +#define ADS_A2A1A0_d_x (5 << 4) /* differential */ +#define ADS_A2A1A0_temp0 (0 << 4) /* non-differential */ +#define ADS_A2A1A0_vbatt (2 << 4) /* non-differential */ +#define ADS_A2A1A0_vaux (6 << 4) /* non-differential */ +#define ADS_A2A1A0_temp1 (7 << 4) /* non-differential */ +#define ADS_8_BIT (1 << 3) +#define ADS_12_BIT (0 << 3) +#define ADS_SER (1 << 2) /* non-differential */ +#define ADS_DFR (0 << 2) /* differential */ +#define ADS_PD10_PDOWN (0 << 0) /* lowpower mode + penirq */ +#define ADS_PD10_ADC_ON (1 << 0) /* ADC on */ +#define ADS_PD10_REF_ON (2 << 0) /* vREF on + penirq */ +#define ADS_PD10_ALL_ON (3 << 0) /* ADC + vREF on */ + +#define MAX_12BIT ((1<<12)-1) + +/* leave ADC powered up (disables penirq) between differential samples */ +#define READ_12BIT_DFR(x, adc, vref) (ADS_START | ADS_A2A1A0_d_ ## x \ + | ADS_12_BIT | ADS_DFR | \ + (adc ? ADS_PD10_ADC_ON : 0) | (vref ? ADS_PD10_REF_ON : 0)) + +#define READ_Y(vref) (READ_12BIT_DFR(y, 1, vref)) +#define READ_Z1(vref) (READ_12BIT_DFR(z1, 1, vref)) +#define READ_Z2(vref) (READ_12BIT_DFR(z2, 1, vref)) + +#define READ_X(vref) (READ_12BIT_DFR(x, 1, vref)) +#define PWRDOWN (READ_12BIT_DFR(y, 0, 0)) /* LAST */ + +/* single-ended samples need to first power up reference voltage; + * we leave both ADC and VREF powered + */ +#define READ_12BIT_SER(x) (ADS_START | ADS_A2A1A0_ ## x \ + | ADS_12_BIT | ADS_SER) + +#define REF_ON (READ_12BIT_DFR(x, 1, 1)) +#define REF_OFF (READ_12BIT_DFR(y, 0, 0)) + +/*--------------------------------------------------------------------------*/ + +/* + * Non-touchscreen sensors only use single-ended conversions. + * The range is GND..vREF. The ads7843 and ads7835 must use external vREF; + * ads7846 lets that pin be unconnected, to use internal vREF. + */ + +struct ser_req { + u8 ref_on; + u8 command; + u8 ref_off; + u16 scratch; + __be16 sample; + struct spi_message msg; + struct spi_transfer xfer[6]; +}; + +static void ads7846_enable(struct ads7846 *ts); +static void ads7846_disable(struct ads7846 *ts); + +static int device_suspended(struct device *dev) +{ + struct ads7846 *ts = dev_get_drvdata(dev); + return ts->is_suspended || ts->disabled; +} + +static int ads7846_read12_ser(struct device *dev, unsigned command) +{ + struct spi_device *spi = to_spi_device(dev); + struct ads7846 *ts = dev_get_drvdata(dev); + struct ser_req *req = kzalloc(sizeof *req, GFP_KERNEL); + int status; + int use_internal; + + if (!req) + return -ENOMEM; + + spi_message_init(&req->msg); + + /* FIXME boards with ads7846 might use external vref instead ... */ + use_internal = (ts->model == 7846); + + /* maybe turn on internal vREF, and let it settle */ + if (use_internal) { + req->ref_on = REF_ON; + req->xfer[0].tx_buf = &req->ref_on; + req->xfer[0].len = 1; + spi_message_add_tail(&req->xfer[0], &req->msg); + + req->xfer[1].rx_buf = &req->scratch; + req->xfer[1].len = 2; + + /* for 1uF, settle for 800 usec; no cap, 100 usec. */ + req->xfer[1].delay_usecs = ts->vref_delay_usecs; + spi_message_add_tail(&req->xfer[1], &req->msg); + } + + /* take sample */ + req->command = (u8) command; + req->xfer[2].tx_buf = &req->command; + req->xfer[2].len = 1; + spi_message_add_tail(&req->xfer[2], &req->msg); + + req->xfer[3].rx_buf = &req->sample; + req->xfer[3].len = 2; + spi_message_add_tail(&req->xfer[3], &req->msg); + + /* REVISIT: take a few more samples, and compare ... */ + + /* converter in low power mode & enable PENIRQ */ + req->ref_off = PWRDOWN; + req->xfer[4].tx_buf = &req->ref_off; + req->xfer[4].len = 1; + spi_message_add_tail(&req->xfer[4], &req->msg); + + req->xfer[5].rx_buf = &req->scratch; + req->xfer[5].len = 2; + CS_CHANGE(req->xfer[5]); + spi_message_add_tail(&req->xfer[5], &req->msg); + + ts->irq_disabled = 1; + disable_irq(spi->irq); + status = spi_sync(spi, &req->msg); + ts->irq_disabled = 0; + enable_irq(spi->irq); + + if (status == 0) { + /* on-wire is a must-ignore bit, a BE12 value, then padding */ + status = be16_to_cpu(req->sample); + status = status >> 3; + status &= 0x0fff; + } + + kfree(req); + return status; +} + +#if defined(CONFIG_HWMON) || defined(CONFIG_HWMON_MODULE) + +#define SHOW(name, var, adjust) static ssize_t \ +name ## _show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct ads7846 *ts = dev_get_drvdata(dev); \ + ssize_t v = ads7846_read12_ser(dev, \ + READ_12BIT_SER(var) | ADS_PD10_ALL_ON); \ + if (v < 0) \ + return v; \ + return sprintf(buf, "%u\n", adjust(ts, v)); \ +} \ +static DEVICE_ATTR(name, S_IRUGO, name ## _show, NULL); + + +/* Sysfs conventions report temperatures in millidegrees Celcius. + * ADS7846 could use the low-accuracy two-sample scheme, but can't do the high + * accuracy scheme without calibration data. For now we won't try either; + * userspace sees raw sensor values, and must scale/calibrate appropriately. + */ +static inline unsigned null_adjust(struct ads7846 *ts, ssize_t v) +{ + return v; +} + +SHOW(temp0, temp0, null_adjust) /* temp1_input */ +SHOW(temp1, temp1, null_adjust) /* temp2_input */ + + +/* sysfs conventions report voltages in millivolts. We can convert voltages + * if we know vREF. userspace may need to scale vAUX to match the board's + * external resistors; we assume that vBATT only uses the internal ones. + */ +static inline unsigned vaux_adjust(struct ads7846 *ts, ssize_t v) +{ + unsigned retval = v; + + /* external resistors may scale vAUX into 0..vREF */ + retval *= ts->vref_mv; + retval = retval >> 12; + return retval; +} + +static inline unsigned vbatt_adjust(struct ads7846 *ts, ssize_t v) +{ + unsigned retval = vaux_adjust(ts, v); + + /* ads7846 has a resistor ladder to scale this signal down */ + if (ts->model == 7846) + retval *= 4; + return retval; +} + +SHOW(in0_input, vaux, vaux_adjust) +SHOW(in1_input, vbatt, vbatt_adjust) + + +static struct attribute *ads7846_attributes[] = { + &dev_attr_temp0.attr, + &dev_attr_temp1.attr, + &dev_attr_in0_input.attr, + &dev_attr_in1_input.attr, + NULL, +}; + +static struct attribute_group ads7846_attr_group = { + .attrs = ads7846_attributes, +}; + +static struct attribute *ads7843_attributes[] = { + &dev_attr_in0_input.attr, + &dev_attr_in1_input.attr, + NULL, +}; + +static struct attribute_group ads7843_attr_group = { + .attrs = ads7843_attributes, +}; + +static struct attribute *ads7845_attributes[] = { + &dev_attr_in0_input.attr, + NULL, +}; + +static struct attribute_group ads7845_attr_group = { + .attrs = ads7845_attributes, +}; + +static int ads784x_hwmon_register(struct spi_device *spi, struct ads7846 *ts) +{ + struct device *hwmon; + int err; + + /* hwmon sensors need a reference voltage */ + switch (ts->model) { + case 7846: + if (!ts->vref_mv) { + dev_dbg(&spi->dev, "assuming 2.5V internal vREF\n"); + ts->vref_mv = 2500; + } + break; + case 7845: + case 7843: + if (!ts->vref_mv) { + dev_warn(&spi->dev, + "external vREF for ADS%d not specified\n", + ts->model); + return 0; + } + break; + } + + /* different chips have different sensor groups */ + switch (ts->model) { + case 7846: + ts->attr_group = &ads7846_attr_group; + break; + case 7845: + ts->attr_group = &ads7845_attr_group; + break; + case 7843: + ts->attr_group = &ads7843_attr_group; + break; + default: + dev_dbg(&spi->dev, "ADS%d not recognized\n", ts->model); + return 0; + } + + err = sysfs_create_group(&spi->dev.kobj, ts->attr_group); + if (err) + return err; + + hwmon = hwmon_device_register(&spi->dev); + if (IS_ERR(hwmon)) { + sysfs_remove_group(&spi->dev.kobj, ts->attr_group); + return PTR_ERR(hwmon); + } + + ts->hwmon = hwmon; + return 0; +} + +static void ads784x_hwmon_unregister(struct spi_device *spi, + struct ads7846 *ts) +{ + if (ts->hwmon) { + sysfs_remove_group(&spi->dev.kobj, ts->attr_group); + hwmon_device_unregister(ts->hwmon); + } +} + +#else +static inline int ads784x_hwmon_register(struct spi_device *spi, + struct ads7846 *ts) +{ + return 0; +} + +static inline void ads784x_hwmon_unregister(struct spi_device *spi, + struct ads7846 *ts) +{ +} +#endif + +static int is_pen_down(struct device *dev) +{ + struct ads7846 *ts = dev_get_drvdata(dev); + + return ts->pendown; +} + +static ssize_t ads7846_pen_down_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", is_pen_down(dev)); +} + +static DEVICE_ATTR(pen_down, S_IRUGO, ads7846_pen_down_show, NULL); + +static ssize_t ads7846_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ads7846 *ts = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", ts->disabled); +} + +static ssize_t ads7846_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ads7846 *ts = dev_get_drvdata(dev); + long i; + + if (strict_strtoul(buf, 10, &i)) + return -EINVAL; + + spin_lock_irq(&ts->lock); + + if (i) + ads7846_disable(ts); + else + ads7846_enable(ts); + + spin_unlock_irq(&ts->lock); + + return count; +} + +static DEVICE_ATTR(disable, 0664, ads7846_disable_show, ads7846_disable_store); + +static struct attribute *ads784x_attributes[] = { + &dev_attr_pen_down.attr, + &dev_attr_disable.attr, + NULL, +}; + +static struct attribute_group ads784x_attr_group = { + .attrs = ads784x_attributes, +}; + +/*--------------------------------------------------------------------------*/ + +static int get_pendown_state(struct ads7846 *ts) +{ + if (ts->get_pendown_state) + return ts->get_pendown_state(); + + return !gpio_get_value(ts->gpio_pendown); +} + +/* + * PENIRQ only kicks the timer. The timer only reissues the SPI transfer, + * to retrieve touchscreen status. + * + * The SPI transfer completion callback does the real work. It reports + * touchscreen events and reactivates the timer (or IRQ) as appropriate. + */ + +static void ads7846_rx(void *ads) +{ + struct ads7846 *ts = ads; + struct ads7846_packet *packet = ts->packet; + unsigned Rt; + u16 x, y, z1, z2; + + /* ads7846_rx_val() did in-place conversion (including byteswap) from + * on-the-wire format as part of debouncing to get stable readings. + */ + x = packet->tc.x; + y = packet->tc.y; + z1 = packet->tc.z1; + z2 = packet->tc.z2; + + /* range filtering */ + if (x == MAX_12BIT) + x = 0; + + if (ts->model == 7843) { + Rt = ts->pressure_max / 2; + } else if (likely(x && z1)) { + /* compute touch pressure resistance using equation #2 */ + Rt = z2; + Rt -= z1; + Rt *= x; + Rt *= ts->x_plate_ohms; + Rt /= z1; + Rt = (Rt + 2047) >> 12; + } else { + Rt = 0; + } + + /* Sample found inconsistent by debouncing or pressure is beyond + * the maximum. Don't report it to user space, repeat at least + * once more the measurement + */ + if (packet->tc.ignore || Rt > ts->pressure_max) { +#ifdef VERBOSE + pr_debug("%s: ignored %d pressure %d\n", + ts->spi->dev.bus_id, packet->tc.ignore, Rt); +#endif + hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_PERIOD), + HRTIMER_MODE_REL); + return; + } + + /* Maybe check the pendown state before reporting. This discards + * false readings when the pen is lifted. + */ + if (ts->penirq_recheck_delay_usecs) { + udelay(ts->penirq_recheck_delay_usecs); + if (!get_pendown_state(ts)) + Rt = 0; + } + + /* NOTE: We can't rely on the pressure to determine the pen down + * state, even this controller has a pressure sensor. The pressure + * value can fluctuate for quite a while after lifting the pen and + * in some cases may not even settle at the expected value. + * + * The only safe way to check for the pen up condition is in the + * timer by reading the pen signal state (it's a GPIO _and_ IRQ). + */ + if (Rt) { + struct input_dev *input = ts->input; + + if (!ts->pendown) { + input_report_key(input, BTN_TOUCH, 1); + ts->pendown = 1; +#ifdef VERBOSE + dev_dbg(&ts->spi->dev, "DOWN\n"); +#endif + } + input_report_abs(input, ABS_X, x); + input_report_abs(input, ABS_Y, y); + input_report_abs(input, ABS_PRESSURE, Rt); + + input_sync(input); +#ifdef VERBOSE + dev_dbg(&ts->spi->dev, "%4d/%4d/%4d\n", x, y, Rt); +#endif + } + + hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_PERIOD), + HRTIMER_MODE_REL); +} + +static int ads7846_debounce(void *ads, int data_idx, int *val) +{ + struct ads7846 *ts = ads; + + if (!ts->read_cnt || (abs(ts->last_read - *val) > ts->debounce_tol)) { + /* Start over collecting consistent readings. */ + ts->read_rep = 0; + /* Repeat it, if this was the first read or the read + * wasn't consistent enough. */ + if (ts->read_cnt < ts->debounce_max) { + ts->last_read = *val; + ts->read_cnt++; + return ADS7846_FILTER_REPEAT; + } else { + /* Maximum number of debouncing reached and still + * not enough number of consistent readings. Abort + * the whole sample, repeat it in the next sampling + * period. + */ + ts->read_cnt = 0; + return ADS7846_FILTER_IGNORE; + } + } else { + if (++ts->read_rep > ts->debounce_rep) { + /* Got a good reading for this coordinate, + * go for the next one. */ + ts->read_cnt = 0; + ts->read_rep = 0; + return ADS7846_FILTER_OK; + } else { + /* Read more values that are consistent. */ + ts->read_cnt++; + return ADS7846_FILTER_REPEAT; + } + } +} + +static int ads7846_no_filter(void *ads, int data_idx, int *val) +{ + return ADS7846_FILTER_OK; +} + +static void ads7846_rx_val(void *ads) +{ + struct ads7846 *ts = ads; + struct ads7846_packet *packet = ts->packet; + struct spi_message *m; + struct spi_transfer *t; + int val; + int action; + int status; + + m = &ts->msg[ts->msg_idx]; + t = list_entry(m->transfers.prev, struct spi_transfer, transfer_list); + + /* adjust: on-wire is a must-ignore bit, a BE12 value, then padding; + * built from two 8 bit values written msb-first. + */ + val = be16_to_cpup((__be16 *)t->rx_buf) >> 3; + + action = ts->filter(ts->filter_data, ts->msg_idx, &val); + switch (action) { + case ADS7846_FILTER_REPEAT: + break; + case ADS7846_FILTER_IGNORE: + packet->tc.ignore = 1; + /* Last message will contain ads7846_rx() as the + * completion function. + */ + m = ts->last_msg; + break; + case ADS7846_FILTER_OK: + *(u16 *)t->rx_buf = val; + packet->tc.ignore = 0; + m = &ts->msg[++ts->msg_idx]; + break; + default: + BUG(); + } + status = spi_async(ts->spi, m); + if (status) + dev_err(&ts->spi->dev, "spi_async --> %d\n", + status); +} + +static enum hrtimer_restart ads7846_timer(struct hrtimer *handle) +{ + struct ads7846 *ts = container_of(handle, struct ads7846, timer); + int status = 0; + + spin_lock_irq(&ts->lock); + + if (unlikely(!get_pendown_state(ts) || + device_suspended(&ts->spi->dev))) { + if (ts->pendown) { + struct input_dev *input = ts->input; + + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_PRESSURE, 0); + input_sync(input); + + ts->pendown = 0; +#ifdef VERBOSE + dev_dbg(&ts->spi->dev, "UP\n"); +#endif + } + + /* measurement cycle ended */ + if (!device_suspended(&ts->spi->dev)) { + ts->irq_disabled = 0; + enable_irq(ts->spi->irq); + } + ts->pending = 0; + } else { + /* pen is still down, continue with the measurement */ + ts->msg_idx = 0; + status = spi_async(ts->spi, &ts->msg[0]); + if (status) + dev_err(&ts->spi->dev, "spi_async --> %d\n", status); + } + + spin_unlock_irq(&ts->lock); + return HRTIMER_NORESTART; +} + +static irqreturn_t ads7846_irq(int irq, void *handle) +{ + struct ads7846 *ts = handle; + unsigned long flags; + + spin_lock_irqsave(&ts->lock, flags); + if (likely(get_pendown_state(ts))) { + if (!ts->irq_disabled) { + /* The ARM do_simple_IRQ() dispatcher doesn't act + * like the other dispatchers: it will report IRQs + * even after they've been disabled. We work around + * that here. (The "generic irq" framework may help...) + */ + ts->irq_disabled = 1; + disable_irq(ts->spi->irq); + ts->pending = 1; + hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_DELAY), + HRTIMER_MODE_REL); + } + } + spin_unlock_irqrestore(&ts->lock, flags); + + return IRQ_HANDLED; +} + +/*--------------------------------------------------------------------------*/ + +/* Must be called with ts->lock held */ +static void ads7846_disable(struct ads7846 *ts) +{ + if (ts->disabled) + return; + + ts->disabled = 1; + + /* are we waiting for IRQ, or polling? */ + if (!ts->pending) { + ts->irq_disabled = 1; + disable_irq(ts->spi->irq); + } else { + /* the timer will run at least once more, and + * leave everything in a clean state, IRQ disabled + */ + while (ts->pending) { + spin_unlock_irq(&ts->lock); + msleep(1); + spin_lock_irq(&ts->lock); + } + } + + /* we know the chip's in lowpower mode since we always + * leave it that way after every request + */ +} + +/* Must be called with ts->lock held */ +static void ads7846_enable(struct ads7846 *ts) +{ + if (!ts->disabled) + return; + + ts->disabled = 0; + ts->irq_disabled = 0; + enable_irq(ts->spi->irq); +} + +static int ads7846_suspend(struct spi_device *spi, pm_message_t message) +{ + struct ads7846 *ts = dev_get_drvdata(&spi->dev); + + spin_lock_irq(&ts->lock); + + ts->is_suspended = 1; + ads7846_disable(ts); + + spin_unlock_irq(&ts->lock); + + return 0; + +} + +static int ads7846_resume(struct spi_device *spi) +{ + struct ads7846 *ts = dev_get_drvdata(&spi->dev); + + spin_lock_irq(&ts->lock); + + ts->is_suspended = 0; + ads7846_enable(ts); + + spin_unlock_irq(&ts->lock); + + return 0; +} + +static int __devinit setup_pendown(struct spi_device *spi, struct ads7846 *ts) +{ + struct ads7846_platform_data *pdata = spi->dev.platform_data; + int err; + + /* REVISIT when the irq can be triggered active-low, or if for some + * reason the touchscreen isn't hooked up, we don't need to access + * the pendown state. + */ + if (!pdata->get_pendown_state && !gpio_is_valid(pdata->gpio_pendown)) { + dev_err(&spi->dev, "no get_pendown_state nor gpio_pendown?\n"); + return -EINVAL; + } + + if (pdata->get_pendown_state) { + ts->get_pendown_state = pdata->get_pendown_state; + return 0; + } + + err = gpio_request(pdata->gpio_pendown, "ads7846_pendown"); + if (err) { + dev_err(&spi->dev, "failed to request pendown GPIO%d\n", + pdata->gpio_pendown); + return err; + } + + ts->gpio_pendown = pdata->gpio_pendown; + return 0; +} + +static int __devinit ads7846_probe(struct spi_device *spi) +{ + struct ads7846 *ts; + struct ads7846_packet *packet; + struct input_dev *input_dev; + struct ads7846_platform_data *pdata = spi->dev.platform_data; + struct spi_message *m; + struct spi_transfer *x; + int vref; + int err; + + if (!spi->irq) { + dev_dbg(&spi->dev, "no IRQ?\n"); + return -ENODEV; + } + + if (!pdata) { + dev_dbg(&spi->dev, "no platform data?\n"); + return -ENODEV; + } + + /* don't exceed max specified sample rate */ + if (spi->max_speed_hz > (125000 * SAMPLE_BITS)) { + dev_dbg(&spi->dev, "f(sample) %d KHz?\n", + (spi->max_speed_hz/SAMPLE_BITS)/1000); + return -EINVAL; + } + + /* We'd set TX wordsize 8 bits and RX wordsize to 13 bits ... except + * that even if the hardware can do that, the SPI controller driver + * may not. So we stick to very-portable 8 bit words, both RX and TX. + */ + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + err = spi_setup(spi); + if (err < 0) + return err; + + ts = kzalloc(sizeof(struct ads7846), GFP_KERNEL); + packet = kzalloc(sizeof(struct ads7846_packet), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ts || !packet || !input_dev) { + err = -ENOMEM; + goto err_free_mem; + } + + dev_set_drvdata(&spi->dev, ts); + + ts->packet = packet; + ts->spi = spi; + ts->input = input_dev; + ts->vref_mv = pdata->vref_mv; + + hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ts->timer.function = ads7846_timer; + + spin_lock_init(&ts->lock); + + ts->model = pdata->model ? : 7846; + ts->vref_delay_usecs = pdata->vref_delay_usecs ? : 100; + ts->x_plate_ohms = pdata->x_plate_ohms ? : 400; + ts->pressure_max = pdata->pressure_max ? : ~0; + + if (pdata->filter != NULL) { + if (pdata->filter_init != NULL) { + err = pdata->filter_init(pdata, &ts->filter_data); + if (err < 0) + goto err_free_mem; + } + ts->filter = pdata->filter; + ts->filter_cleanup = pdata->filter_cleanup; + } else if (pdata->debounce_max) { + ts->debounce_max = pdata->debounce_max; + if (ts->debounce_max < 2) + ts->debounce_max = 2; + ts->debounce_tol = pdata->debounce_tol; + ts->debounce_rep = pdata->debounce_rep; + ts->filter = ads7846_debounce; + ts->filter_data = ts; + } else + ts->filter = ads7846_no_filter; + + err = setup_pendown(spi, ts); + if (err) + goto err_cleanup_filter; + + if (pdata->penirq_recheck_delay_usecs) + ts->penirq_recheck_delay_usecs = + pdata->penirq_recheck_delay_usecs; + + snprintf(ts->phys, sizeof(ts->phys), "%s/input0", spi->dev.bus_id); + + input_dev->name = "ADS784x Touchscreen"; + input_dev->phys = ts->phys; + input_dev->dev.parent = &spi->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, + pdata->x_min ? : 0, + pdata->x_max ? : MAX_12BIT, + 0, 0); + input_set_abs_params(input_dev, ABS_Y, + pdata->y_min ? : 0, + pdata->y_max ? : MAX_12BIT, + 0, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, + pdata->pressure_min, pdata->pressure_max, 0, 0); + + vref = pdata->keep_vref_on; + + /* set up the transfers to read touchscreen state; this assumes we + * use formula #2 for pressure, not #3. + */ + m = &ts->msg[0]; + x = ts->xfer; + + spi_message_init(m); + + /* y- still on; turn on only y+ (and ADC) */ + packet->read_y = READ_Y(vref); + x->tx_buf = &packet->read_y; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.y; + x->len = 2; + spi_message_add_tail(x, m); + + /* the first sample after switching drivers can be low quality; + * optionally discard it, using a second one after the signals + * have had enough time to stabilize. + */ + if (pdata->settle_delay_usecs) { + x->delay_usecs = pdata->settle_delay_usecs; + + x++; + x->tx_buf = &packet->read_y; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.y; + x->len = 2; + spi_message_add_tail(x, m); + } + + m->complete = ads7846_rx_val; + m->context = ts; + + m++; + spi_message_init(m); + + /* turn y- off, x+ on, then leave in lowpower */ + x++; + packet->read_x = READ_X(vref); + x->tx_buf = &packet->read_x; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.x; + x->len = 2; + spi_message_add_tail(x, m); + + /* ... maybe discard first sample ... */ + if (pdata->settle_delay_usecs) { + x->delay_usecs = pdata->settle_delay_usecs; + + x++; + x->tx_buf = &packet->read_x; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.x; + x->len = 2; + spi_message_add_tail(x, m); + } + + m->complete = ads7846_rx_val; + m->context = ts; + + /* turn y+ off, x- on; we'll use formula #2 */ + if (ts->model == 7846) { + m++; + spi_message_init(m); + + x++; + packet->read_z1 = READ_Z1(vref); + x->tx_buf = &packet->read_z1; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.z1; + x->len = 2; + spi_message_add_tail(x, m); + + /* ... maybe discard first sample ... */ + if (pdata->settle_delay_usecs) { + x->delay_usecs = pdata->settle_delay_usecs; + + x++; + x->tx_buf = &packet->read_z1; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.z1; + x->len = 2; + spi_message_add_tail(x, m); + } + + m->complete = ads7846_rx_val; + m->context = ts; + + m++; + spi_message_init(m); + + x++; + packet->read_z2 = READ_Z2(vref); + x->tx_buf = &packet->read_z2; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.z2; + x->len = 2; + spi_message_add_tail(x, m); + + /* ... maybe discard first sample ... */ + if (pdata->settle_delay_usecs) { + x->delay_usecs = pdata->settle_delay_usecs; + + x++; + x->tx_buf = &packet->read_z2; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->tc.z2; + x->len = 2; + spi_message_add_tail(x, m); + } + + m->complete = ads7846_rx_val; + m->context = ts; + } + + /* power down */ + m++; + spi_message_init(m); + + x++; + packet->pwrdown = PWRDOWN; + x->tx_buf = &packet->pwrdown; + x->len = 1; + spi_message_add_tail(x, m); + + x++; + x->rx_buf = &packet->dummy; + x->len = 2; + CS_CHANGE(*x); + spi_message_add_tail(x, m); + + m->complete = ads7846_rx; + m->context = ts; + + ts->last_msg = m; + + if (request_irq(spi->irq, ads7846_irq, IRQF_TRIGGER_FALLING, + spi->dev.driver->name, ts)) { + dev_dbg(&spi->dev, "irq %d busy?\n", spi->irq); + err = -EBUSY; + goto err_free_gpio; + } + + err = ads784x_hwmon_register(spi, ts); + if (err) + goto err_free_irq; + + dev_info(&spi->dev, "touchscreen, irq %d\n", spi->irq); + + /* take a first sample, leaving nPENIRQ active and vREF off; avoid + * the touchscreen, in case it's not connected. + */ + (void) ads7846_read12_ser(&spi->dev, + READ_12BIT_SER(vaux) | ADS_PD10_ALL_ON); + + err = sysfs_create_group(&spi->dev.kobj, &ads784x_attr_group); + if (err) + goto err_remove_hwmon; + + err = input_register_device(input_dev); + if (err) + goto err_remove_attr_group; + + return 0; + + err_remove_attr_group: + sysfs_remove_group(&spi->dev.kobj, &ads784x_attr_group); + err_remove_hwmon: + ads784x_hwmon_unregister(spi, ts); + err_free_irq: + free_irq(spi->irq, ts); + err_free_gpio: + if (ts->gpio_pendown != -1) + gpio_free(ts->gpio_pendown); + err_cleanup_filter: + if (ts->filter_cleanup) + ts->filter_cleanup(ts->filter_data); + err_free_mem: + input_free_device(input_dev); + kfree(packet); + kfree(ts); + return err; +} + +static int __devexit ads7846_remove(struct spi_device *spi) +{ + struct ads7846 *ts = dev_get_drvdata(&spi->dev); + + ads784x_hwmon_unregister(spi, ts); + input_unregister_device(ts->input); + + ads7846_suspend(spi, PMSG_SUSPEND); + + sysfs_remove_group(&spi->dev.kobj, &ads784x_attr_group); + + free_irq(ts->spi->irq, ts); + /* suspend left the IRQ disabled */ + enable_irq(ts->spi->irq); + + if (ts->gpio_pendown != -1) + gpio_free(ts->gpio_pendown); + + if (ts->filter_cleanup) + ts->filter_cleanup(ts->filter_data); + + kfree(ts->packet); + kfree(ts); + + dev_dbg(&spi->dev, "unregistered touchscreen\n"); + return 0; +} + +static struct spi_driver ads7846_driver = { + .driver = { + .name = "ads7846", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = ads7846_probe, + .remove = __devexit_p(ads7846_remove), + .suspend = ads7846_suspend, + .resume = ads7846_resume, +}; + +static int __init ads7846_init(void) +{ + return spi_register_driver(&ads7846_driver); +} +module_init(ads7846_init); + +static void __exit ads7846_exit(void) +{ + spi_unregister_driver(&ads7846_driver); +} +module_exit(ads7846_exit); + +MODULE_DESCRIPTION("ADS7846 TouchScreen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/atmel_tsadcc.c b/drivers/input/touchscreen/atmel_tsadcc.c new file mode 100644 index 0000000..a89a6a8 --- /dev/null +++ b/drivers/input/touchscreen/atmel_tsadcc.c @@ -0,0 +1,343 @@ +/* + * Atmel Touch Screen Driver + * + * Copyright (c) 2008 ATMEL + * Copyright (c) 2008 Dan Liang + * Copyright (c) 2008 TimeSys Corporation + * Copyright (c) 2008 Justin Waters + * + * Based on touchscreen code from Atmel Corporation. + * + * 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. + */ +#include <linux/init.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/io.h> + +/* Register definitions based on AT91SAM9RL64 preliminary draft datasheet */ + +#define ATMEL_TSADCC_CR 0x00 /* Control register */ +#define ATMEL_TSADCC_SWRST (1 << 0) /* Software Reset*/ +#define ATMEL_TSADCC_START (1 << 1) /* Start conversion */ + +#define ATMEL_TSADCC_MR 0x04 /* Mode register */ +#define ATMEL_TSADCC_TSAMOD (3 << 0) /* ADC mode */ +#define ATMEL_TSADCC_TSAMOD_ADC_ONLY_MODE (0x0) /* ADC Mode */ +#define ATMEL_TSADCC_TSAMOD_TS_ONLY_MODE (0x1) /* Touch Screen Only Mode */ +#define ATMEL_TSADCC_LOWRES (1 << 4) /* Resolution selection */ +#define ATMEL_TSADCC_SLEEP (1 << 5) /* Sleep mode */ +#define ATMEL_TSADCC_PENDET (1 << 6) /* Pen Detect selection */ +#define ATMEL_TSADCC_PRESCAL (0x3f << 8) /* Prescalar Rate Selection */ +#define ATMEL_TSADCC_STARTUP (0x7f << 16) /* Start Up time */ +#define ATMEL_TSADCC_SHTIM (0xf << 24) /* Sample & Hold time */ +#define ATMEL_TSADCC_PENDBC (0xf << 28) /* Pen Detect debouncing time */ + +#define ATMEL_TSADCC_TRGR 0x08 /* Trigger register */ +#define ATMEL_TSADCC_TRGMOD (7 << 0) /* Trigger mode */ +#define ATMEL_TSADCC_TRGMOD_NONE (0 << 0) +#define ATMEL_TSADCC_TRGMOD_EXT_RISING (1 << 0) +#define ATMEL_TSADCC_TRGMOD_EXT_FALLING (2 << 0) +#define ATMEL_TSADCC_TRGMOD_EXT_ANY (3 << 0) +#define ATMEL_TSADCC_TRGMOD_PENDET (4 << 0) +#define ATMEL_TSADCC_TRGMOD_PERIOD (5 << 0) +#define ATMEL_TSADCC_TRGMOD_CONTINUOUS (6 << 0) +#define ATMEL_TSADCC_TRGPER (0xffff << 16) /* Trigger period */ + +#define ATMEL_TSADCC_TSR 0x0C /* Touch Screen register */ +#define ATMEL_TSADCC_TSFREQ (0xf << 0) /* TS Frequency in Interleaved mode */ +#define ATMEL_TSADCC_TSSHTIM (0xf << 24) /* Sample & Hold time */ + +#define ATMEL_TSADCC_CHER 0x10 /* Channel Enable register */ +#define ATMEL_TSADCC_CHDR 0x14 /* Channel Disable register */ +#define ATMEL_TSADCC_CHSR 0x18 /* Channel Status register */ +#define ATMEL_TSADCC_CH(n) (1 << (n)) /* Channel number */ + +#define ATMEL_TSADCC_SR 0x1C /* Status register */ +#define ATMEL_TSADCC_EOC(n) (1 << ((n)+0)) /* End of conversion for channel N */ +#define ATMEL_TSADCC_OVRE(n) (1 << ((n)+8)) /* Overrun error for channel N */ +#define ATMEL_TSADCC_DRDY (1 << 16) /* Data Ready */ +#define ATMEL_TSADCC_GOVRE (1 << 17) /* General Overrun Error */ +#define ATMEL_TSADCC_ENDRX (1 << 18) /* End of RX Buffer */ +#define ATMEL_TSADCC_RXBUFF (1 << 19) /* TX Buffer full */ +#define ATMEL_TSADCC_PENCNT (1 << 20) /* Pen contact */ +#define ATMEL_TSADCC_NOCNT (1 << 21) /* No contact */ + +#define ATMEL_TSADCC_LCDR 0x20 /* Last Converted Data register */ +#define ATMEL_TSADCC_DATA (0x3ff << 0) /* Channel data */ + +#define ATMEL_TSADCC_IER 0x24 /* Interrupt Enable register */ +#define ATMEL_TSADCC_IDR 0x28 /* Interrupt Disable register */ +#define ATMEL_TSADCC_IMR 0x2C /* Interrupt Mask register */ +#define ATMEL_TSADCC_CDR0 0x30 /* Channel Data 0 */ +#define ATMEL_TSADCC_CDR1 0x34 /* Channel Data 1 */ +#define ATMEL_TSADCC_CDR2 0x38 /* Channel Data 2 */ +#define ATMEL_TSADCC_CDR3 0x3C /* Channel Data 3 */ +#define ATMEL_TSADCC_CDR4 0x40 /* Channel Data 4 */ +#define ATMEL_TSADCC_CDR5 0x44 /* Channel Data 5 */ + +#define ADC_CLOCK 1000000 + +struct atmel_tsadcc { + struct input_dev *input; + char phys[32]; + struct clk *clk; + int irq; + unsigned int prev_absx; + unsigned int prev_absy; + unsigned char bufferedmeasure; +}; + +static void __iomem *tsc_base; + +#define atmel_tsadcc_read(reg) __raw_readl(tsc_base + (reg)) +#define atmel_tsadcc_write(reg, val) __raw_writel((val), tsc_base + (reg)) + +static irqreturn_t atmel_tsadcc_interrupt(int irq, void *dev) +{ + struct atmel_tsadcc *ts_dev = (struct atmel_tsadcc *)dev; + struct input_dev *input_dev = ts_dev->input; + + unsigned int status; + unsigned int reg; + + status = atmel_tsadcc_read(ATMEL_TSADCC_SR); + status &= atmel_tsadcc_read(ATMEL_TSADCC_IMR); + + if (status & ATMEL_TSADCC_NOCNT) { + /* Contact lost */ + reg = atmel_tsadcc_read(ATMEL_TSADCC_MR) | ATMEL_TSADCC_PENDBC; + + atmel_tsadcc_write(ATMEL_TSADCC_MR, reg); + atmel_tsadcc_write(ATMEL_TSADCC_TRGR, ATMEL_TSADCC_TRGMOD_NONE); + atmel_tsadcc_write(ATMEL_TSADCC_IDR, + ATMEL_TSADCC_EOC(3) | ATMEL_TSADCC_NOCNT); + atmel_tsadcc_write(ATMEL_TSADCC_IER, ATMEL_TSADCC_PENCNT); + + input_report_key(input_dev, BTN_TOUCH, 0); + ts_dev->bufferedmeasure = 0; + input_sync(input_dev); + + } else if (status & ATMEL_TSADCC_PENCNT) { + /* Pen detected */ + reg = atmel_tsadcc_read(ATMEL_TSADCC_MR); + reg &= ~ATMEL_TSADCC_PENDBC; + + atmel_tsadcc_write(ATMEL_TSADCC_IDR, ATMEL_TSADCC_PENCNT); + atmel_tsadcc_write(ATMEL_TSADCC_MR, reg); + atmel_tsadcc_write(ATMEL_TSADCC_IER, + ATMEL_TSADCC_EOC(3) | ATMEL_TSADCC_NOCNT); + atmel_tsadcc_write(ATMEL_TSADCC_TRGR, + ATMEL_TSADCC_TRGMOD_PERIOD | (0x0FFF << 16)); + + } else if (status & ATMEL_TSADCC_EOC(3)) { + /* Conversion finished */ + + if (ts_dev->bufferedmeasure) { + /* Last measurement is always discarded, since it can + * be erroneous. + * Always report previous measurement */ + input_report_abs(input_dev, ABS_X, ts_dev->prev_absx); + input_report_abs(input_dev, ABS_Y, ts_dev->prev_absy); + input_report_key(input_dev, BTN_TOUCH, 1); + input_sync(input_dev); + } else + ts_dev->bufferedmeasure = 1; + + /* Now make new measurement */ + ts_dev->prev_absx = atmel_tsadcc_read(ATMEL_TSADCC_CDR3) << 10; + ts_dev->prev_absx /= atmel_tsadcc_read(ATMEL_TSADCC_CDR2); + + ts_dev->prev_absy = atmel_tsadcc_read(ATMEL_TSADCC_CDR1) << 10; + ts_dev->prev_absy /= atmel_tsadcc_read(ATMEL_TSADCC_CDR0); + } + + return IRQ_HANDLED; +} + +/* + * The functions for inserting/removing us as a module. + */ + +static int __devinit atmel_tsadcc_probe(struct platform_device *pdev) +{ + struct atmel_tsadcc *ts_dev; + struct input_dev *input_dev; + struct resource *res; + int err = 0; + unsigned int prsc; + unsigned int reg; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no mmio resource defined.\n"); + return -ENXIO; + } + + /* Allocate memory for device */ + ts_dev = kzalloc(sizeof(struct atmel_tsadcc), GFP_KERNEL); + if (!ts_dev) { + dev_err(&pdev->dev, "failed to allocate memory.\n"); + return -ENOMEM; + } + platform_set_drvdata(pdev, ts_dev); + + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&pdev->dev, "failed to allocate input device.\n"); + err = -EBUSY; + goto err_free_mem; + } + + ts_dev->irq = platform_get_irq(pdev, 0); + if (ts_dev->irq < 0) { + dev_err(&pdev->dev, "no irq ID is designated.\n"); + err = -ENODEV; + goto err_free_dev; + } + + if (!request_mem_region(res->start, res->end - res->start + 1, + "atmel tsadcc regs")) { + dev_err(&pdev->dev, "resources is unavailable.\n"); + err = -EBUSY; + goto err_free_dev; + } + + tsc_base = ioremap(res->start, res->end - res->start + 1); + if (!tsc_base) { + dev_err(&pdev->dev, "failed to map registers.\n"); + err = -ENOMEM; + goto err_release_mem; + } + + err = request_irq(ts_dev->irq, atmel_tsadcc_interrupt, IRQF_DISABLED, + pdev->dev.driver->name, ts_dev); + if (err) { + dev_err(&pdev->dev, "failed to allocate irq.\n"); + goto err_unmap_regs; + } + + ts_dev->clk = clk_get(&pdev->dev, "tsc_clk"); + if (IS_ERR(ts_dev->clk)) { + dev_err(&pdev->dev, "failed to get ts_clk\n"); + err = PTR_ERR(ts_dev->clk); + goto err_free_irq; + } + + ts_dev->input = input_dev; + ts_dev->bufferedmeasure = 0; + + snprintf(ts_dev->phys, sizeof(ts_dev->phys), + "%s/input0", pdev->dev.bus_id); + + input_dev->name = "atmel touch screen controller"; + input_dev->phys = ts_dev->phys; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(input_dev, ABS_X, 0, 0x3FF, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, 0x3FF, 0, 0); + + /* clk_enable() always returns 0, no need to check it */ + clk_enable(ts_dev->clk); + + prsc = clk_get_rate(ts_dev->clk); + dev_info(&pdev->dev, "Master clock is set at: %d Hz\n", prsc); + + prsc = prsc / ADC_CLOCK / 2 - 1; + + reg = ATMEL_TSADCC_TSAMOD_TS_ONLY_MODE | + ((0x00 << 5) & ATMEL_TSADCC_SLEEP) | /* Normal Mode */ + ((0x01 << 6) & ATMEL_TSADCC_PENDET) | /* Enable Pen Detect */ + ((prsc << 8) & ATMEL_TSADCC_PRESCAL) | /* PRESCAL */ + ((0x13 << 16) & ATMEL_TSADCC_STARTUP) | /* STARTUP */ + ((0x0F << 28) & ATMEL_TSADCC_PENDBC); /* PENDBC */ + + atmel_tsadcc_write(ATMEL_TSADCC_CR, ATMEL_TSADCC_SWRST); + atmel_tsadcc_write(ATMEL_TSADCC_MR, reg); + atmel_tsadcc_write(ATMEL_TSADCC_TRGR, ATMEL_TSADCC_TRGMOD_NONE); + atmel_tsadcc_write(ATMEL_TSADCC_TSR, (0x3 << 24) & ATMEL_TSADCC_TSSHTIM); + + atmel_tsadcc_read(ATMEL_TSADCC_SR); + atmel_tsadcc_write(ATMEL_TSADCC_IER, ATMEL_TSADCC_PENCNT); + + /* All went ok, so register to the input system */ + err = input_register_device(input_dev); + if (err) + goto err_fail; + + return 0; + +err_fail: + clk_disable(ts_dev->clk); + clk_put(ts_dev->clk); +err_free_irq: + free_irq(ts_dev->irq, ts_dev); +err_unmap_regs: + iounmap(tsc_base); +err_release_mem: + release_mem_region(res->start, res->end - res->start + 1); +err_free_dev: + input_free_device(ts_dev->input); +err_free_mem: + kfree(ts_dev); + return err; +} + +static int __devexit atmel_tsadcc_remove(struct platform_device *pdev) +{ + struct atmel_tsadcc *ts_dev = dev_get_drvdata(&pdev->dev); + struct resource *res; + + free_irq(ts_dev->irq, ts_dev); + + input_unregister_device(ts_dev->input); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + iounmap(tsc_base); + release_mem_region(res->start, res->end - res->start + 1); + + clk_disable(ts_dev->clk); + clk_put(ts_dev->clk); + + kfree(ts_dev); + + return 0; +} + +static struct platform_driver atmel_tsadcc_driver = { + .probe = atmel_tsadcc_probe, + .remove = __devexit_p(atmel_tsadcc_remove), + .driver = { + .name = "atmel_tsadcc", + }, +}; + +static int __init atmel_tsadcc_init(void) +{ + return platform_driver_register(&atmel_tsadcc_driver); +} + +static void __exit atmel_tsadcc_exit(void) +{ + platform_driver_unregister(&atmel_tsadcc_driver); +} + +module_init(atmel_tsadcc_init); +module_exit(atmel_tsadcc_exit); + + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atmel TouchScreen Driver"); +MODULE_AUTHOR("Dan Liang <dan.liang@atmel.com>"); + diff --git a/drivers/input/touchscreen/corgi_ts.c b/drivers/input/touchscreen/corgi_ts.c new file mode 100644 index 0000000..65202c9 --- /dev/null +++ b/drivers/input/touchscreen/corgi_ts.c @@ -0,0 +1,385 @@ +/* + * Touchscreen driver for Sharp SL-C7xx and SL-Cxx00 models + * + * Copyright (c) 2004-2005 Richard Purdie + * + * 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. + * + */ + + +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/irq.h> + +#include <mach/sharpsl.h> +#include <mach/hardware.h> +#include <mach/pxa-regs.h> +#include <mach/pxa2xx-gpio.h> + + +#define PWR_MODE_ACTIVE 0 +#define PWR_MODE_SUSPEND 1 + +#define X_AXIS_MAX 3830 +#define X_AXIS_MIN 150 +#define Y_AXIS_MAX 3830 +#define Y_AXIS_MIN 190 +#define PRESSURE_MIN 0 +#define PRESSURE_MAX 15000 + +struct ts_event { + short pressure; + short x; + short y; +}; + +struct corgi_ts { + struct input_dev *input; + struct timer_list timer; + struct ts_event tc; + int pendown; + int power_mode; + int irq_gpio; + struct corgits_machinfo *machinfo; +}; + +#ifdef CONFIG_PXA25x +#define CCNT(a) asm volatile ("mrc p14, 0, %0, C1, C0, 0" : "=r"(a)) +#define PMNC_GET(x) asm volatile ("mrc p14, 0, %0, C0, C0, 0" : "=r"(x)) +#define PMNC_SET(x) asm volatile ("mcr p14, 0, %0, C0, C0, 0" : : "r"(x)) +#endif +#ifdef CONFIG_PXA27x +#define CCNT(a) asm volatile ("mrc p14, 0, %0, C1, C1, 0" : "=r"(a)) +#define PMNC_GET(x) asm volatile ("mrc p14, 0, %0, C0, C1, 0" : "=r"(x)) +#define PMNC_SET(x) asm volatile ("mcr p14, 0, %0, C0, C1, 0" : : "r"(x)) +#endif + +/* ADS7846 Touch Screen Controller bit definitions */ +#define ADSCTRL_PD0 (1u << 0) /* PD0 */ +#define ADSCTRL_PD1 (1u << 1) /* PD1 */ +#define ADSCTRL_DFR (1u << 2) /* SER/DFR */ +#define ADSCTRL_MOD (1u << 3) /* Mode */ +#define ADSCTRL_ADR_SH 4 /* Address setting */ +#define ADSCTRL_STS (1u << 7) /* Start Bit */ + +/* External Functions */ +extern unsigned int get_clk_frequency_khz(int info); + +static unsigned long calc_waittime(struct corgi_ts *corgi_ts) +{ + unsigned long hsync_invperiod = corgi_ts->machinfo->get_hsync_invperiod(); + + if (hsync_invperiod) + return get_clk_frequency_khz(0)*1000/hsync_invperiod; + else + return 0; +} + +static int sync_receive_data_send_cmd(struct corgi_ts *corgi_ts, int doRecive, int doSend, + unsigned int address, unsigned long wait_time) +{ + unsigned long timer1 = 0, timer2, pmnc = 0; + int pos = 0; + + if (wait_time && doSend) { + PMNC_GET(pmnc); + if (!(pmnc & 0x01)) + PMNC_SET(0x01); + + /* polling HSync */ + corgi_ts->machinfo->wait_hsync(); + /* get CCNT */ + CCNT(timer1); + } + + if (doRecive) + pos = corgi_ssp_ads7846_get(); + + if (doSend) { + int cmd = ADSCTRL_PD0 | ADSCTRL_PD1 | (address << ADSCTRL_ADR_SH) | ADSCTRL_STS; + /* dummy command */ + corgi_ssp_ads7846_put(cmd); + corgi_ssp_ads7846_get(); + + if (wait_time) { + /* Wait after HSync */ + CCNT(timer2); + if (timer2-timer1 > wait_time) { + /* too slow - timeout, try again */ + corgi_ts->machinfo->wait_hsync(); + /* get CCNT */ + CCNT(timer1); + /* Wait after HSync */ + CCNT(timer2); + } + while (timer2 - timer1 < wait_time) + CCNT(timer2); + } + corgi_ssp_ads7846_put(cmd); + if (wait_time && !(pmnc & 0x01)) + PMNC_SET(pmnc); + } + return pos; +} + +static int read_xydata(struct corgi_ts *corgi_ts) +{ + unsigned int x, y, z1, z2; + unsigned long flags, wait_time; + + /* critical section */ + local_irq_save(flags); + corgi_ssp_ads7846_lock(); + wait_time = calc_waittime(corgi_ts); + + /* Y-axis */ + sync_receive_data_send_cmd(corgi_ts, 0, 1, 1u, wait_time); + + /* Y-axis */ + sync_receive_data_send_cmd(corgi_ts, 1, 1, 1u, wait_time); + + /* X-axis */ + y = sync_receive_data_send_cmd(corgi_ts, 1, 1, 5u, wait_time); + + /* Z1 */ + x = sync_receive_data_send_cmd(corgi_ts, 1, 1, 3u, wait_time); + + /* Z2 */ + z1 = sync_receive_data_send_cmd(corgi_ts, 1, 1, 4u, wait_time); + z2 = sync_receive_data_send_cmd(corgi_ts, 1, 0, 4u, wait_time); + + /* Power-Down Enable */ + corgi_ssp_ads7846_put((1u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + corgi_ssp_ads7846_get(); + + corgi_ssp_ads7846_unlock(); + local_irq_restore(flags); + + if (x== 0 || y == 0 || z1 == 0 || (x * (z2 - z1) / z1) >= 15000) { + corgi_ts->tc.pressure = 0; + return 0; + } + + corgi_ts->tc.x = x; + corgi_ts->tc.y = y; + corgi_ts->tc.pressure = (x * (z2 - z1)) / z1; + return 1; +} + +static void new_data(struct corgi_ts *corgi_ts) +{ + struct input_dev *dev = corgi_ts->input; + + if (corgi_ts->power_mode != PWR_MODE_ACTIVE) + return; + + if (!corgi_ts->tc.pressure && corgi_ts->pendown == 0) + return; + + input_report_abs(dev, ABS_X, corgi_ts->tc.x); + input_report_abs(dev, ABS_Y, corgi_ts->tc.y); + input_report_abs(dev, ABS_PRESSURE, corgi_ts->tc.pressure); + input_report_key(dev, BTN_TOUCH, corgi_ts->pendown); + input_sync(dev); +} + +static void ts_interrupt_main(struct corgi_ts *corgi_ts, int isTimer) +{ + if ((GPLR(IRQ_TO_GPIO(corgi_ts->irq_gpio)) & GPIO_bit(IRQ_TO_GPIO(corgi_ts->irq_gpio))) == 0) { + /* Disable Interrupt */ + set_irq_type(corgi_ts->irq_gpio, IRQ_TYPE_NONE); + if (read_xydata(corgi_ts)) { + corgi_ts->pendown = 1; + new_data(corgi_ts); + } + mod_timer(&corgi_ts->timer, jiffies + HZ / 100); + } else { + if (corgi_ts->pendown == 1 || corgi_ts->pendown == 2) { + mod_timer(&corgi_ts->timer, jiffies + HZ / 100); + corgi_ts->pendown++; + return; + } + + if (corgi_ts->pendown) { + corgi_ts->tc.pressure = 0; + new_data(corgi_ts); + } + + /* Enable Falling Edge */ + set_irq_type(corgi_ts->irq_gpio, IRQ_TYPE_EDGE_FALLING); + corgi_ts->pendown = 0; + } +} + +static void corgi_ts_timer(unsigned long data) +{ + struct corgi_ts *corgits_data = (struct corgi_ts *) data; + + ts_interrupt_main(corgits_data, 1); +} + +static irqreturn_t ts_interrupt(int irq, void *dev_id) +{ + struct corgi_ts *corgits_data = dev_id; + + ts_interrupt_main(corgits_data, 0); + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM +static int corgits_suspend(struct platform_device *dev, pm_message_t state) +{ + struct corgi_ts *corgi_ts = platform_get_drvdata(dev); + + if (corgi_ts->pendown) { + del_timer_sync(&corgi_ts->timer); + corgi_ts->tc.pressure = 0; + new_data(corgi_ts); + corgi_ts->pendown = 0; + } + corgi_ts->power_mode = PWR_MODE_SUSPEND; + + corgi_ssp_ads7846_putget((1u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + + return 0; +} + +static int corgits_resume(struct platform_device *dev) +{ + struct corgi_ts *corgi_ts = platform_get_drvdata(dev); + + corgi_ssp_ads7846_putget((4u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + /* Enable Falling Edge */ + set_irq_type(corgi_ts->irq_gpio, IRQ_TYPE_EDGE_FALLING); + corgi_ts->power_mode = PWR_MODE_ACTIVE; + + return 0; +} +#else +#define corgits_suspend NULL +#define corgits_resume NULL +#endif + +static int __init corgits_probe(struct platform_device *pdev) +{ + struct corgi_ts *corgi_ts; + struct input_dev *input_dev; + int err = -ENOMEM; + + corgi_ts = kzalloc(sizeof(struct corgi_ts), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!corgi_ts || !input_dev) + goto fail1; + + platform_set_drvdata(pdev, corgi_ts); + + corgi_ts->machinfo = pdev->dev.platform_data; + corgi_ts->irq_gpio = platform_get_irq(pdev, 0); + + if (corgi_ts->irq_gpio < 0) { + err = -ENODEV; + goto fail1; + } + + corgi_ts->input = input_dev; + + init_timer(&corgi_ts->timer); + corgi_ts->timer.data = (unsigned long) corgi_ts; + corgi_ts->timer.function = corgi_ts_timer; + + input_dev->name = "Corgi Touchscreen"; + input_dev->phys = "corgits/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0002; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN, PRESSURE_MAX, 0, 0); + + pxa_gpio_mode(IRQ_TO_GPIO(corgi_ts->irq_gpio) | GPIO_IN); + + /* Initiaize ADS7846 Difference Reference mode */ + corgi_ssp_ads7846_putget((1u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + mdelay(5); + corgi_ssp_ads7846_putget((3u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + mdelay(5); + corgi_ssp_ads7846_putget((4u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + mdelay(5); + corgi_ssp_ads7846_putget((5u << ADSCTRL_ADR_SH) | ADSCTRL_STS); + mdelay(5); + + if (request_irq(corgi_ts->irq_gpio, ts_interrupt, IRQF_DISABLED, "ts", corgi_ts)) { + err = -EBUSY; + goto fail1; + } + + err = input_register_device(corgi_ts->input); + if (err) + goto fail2; + + corgi_ts->power_mode = PWR_MODE_ACTIVE; + + /* Enable Falling Edge */ + set_irq_type(corgi_ts->irq_gpio, IRQ_TYPE_EDGE_FALLING); + + return 0; + + fail2: free_irq(corgi_ts->irq_gpio, corgi_ts); + fail1: input_free_device(input_dev); + kfree(corgi_ts); + return err; +} + +static int corgits_remove(struct platform_device *pdev) +{ + struct corgi_ts *corgi_ts = platform_get_drvdata(pdev); + + free_irq(corgi_ts->irq_gpio, corgi_ts); + del_timer_sync(&corgi_ts->timer); + corgi_ts->machinfo->put_hsync(); + input_unregister_device(corgi_ts->input); + kfree(corgi_ts); + return 0; +} + +static struct platform_driver corgits_driver = { + .probe = corgits_probe, + .remove = corgits_remove, + .suspend = corgits_suspend, + .resume = corgits_resume, + .driver = { + .name = "corgi-ts", + .owner = THIS_MODULE, + }, +}; + +static int __devinit corgits_init(void) +{ + return platform_driver_register(&corgits_driver); +} + +static void __exit corgits_exit(void) +{ + platform_driver_unregister(&corgits_driver); +} + +module_init(corgits_init); +module_exit(corgits_exit); + +MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>"); +MODULE_DESCRIPTION("Corgi TouchScreen Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:corgi-ts"); diff --git a/drivers/input/touchscreen/elo.c b/drivers/input/touchscreen/elo.c new file mode 100644 index 0000000..8f38c5e --- /dev/null +++ b/drivers/input/touchscreen/elo.c @@ -0,0 +1,408 @@ +/* + * Elo serial touchscreen driver + * + * Copyright (c) 2004 Vojtech Pavlik + */ + +/* + * 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. + */ + +/* + * This driver can handle serial Elo touchscreens using either the Elo standard + * 'E271-2210' 10-byte protocol, Elo legacy 'E281A-4002' 6-byte protocol, Elo + * legacy 'E271-140' 4-byte protocol and Elo legacy 'E261-280' 3-byte protocol. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/ctype.h> + +#define DRIVER_DESC "Elo serial touchscreen driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define ELO_MAX_LENGTH 10 + +#define ELO10_PACKET_LEN 8 +#define ELO10_TOUCH 0x03 +#define ELO10_PRESSURE 0x80 + +#define ELO10_LEAD_BYTE 'U' + +#define ELO10_ID_CMD 'i' + +#define ELO10_TOUCH_PACKET 'T' +#define ELO10_ACK_PACKET 'A' +#define ELI10_ID_PACKET 'I' + +/* + * Per-touchscreen data. + */ + +struct elo { + struct input_dev *dev; + struct serio *serio; + struct mutex cmd_mutex; + struct completion cmd_done; + int id; + int idx; + unsigned char expected_packet; + unsigned char csum; + unsigned char data[ELO_MAX_LENGTH]; + unsigned char response[ELO10_PACKET_LEN]; + char phys[32]; +}; + +static void elo_process_data_10(struct elo *elo, unsigned char data) +{ + struct input_dev *dev = elo->dev; + + elo->data[elo->idx] = data; + switch (elo->idx++) { + case 0: + elo->csum = 0xaa; + if (data != ELO10_LEAD_BYTE) { + pr_debug("elo: unsynchronized data: 0x%02x\n", data); + elo->idx = 0; + } + break; + + case 9: + elo->idx = 0; + if (data != elo->csum) { + pr_debug("elo: bad checksum: 0x%02x, expected 0x%02x\n", + data, elo->csum); + break; + } + if (elo->data[1] != elo->expected_packet) { + if (elo->data[1] != ELO10_TOUCH_PACKET) + pr_debug("elo: unexpected packet: 0x%02x\n", + elo->data[1]); + break; + } + if (likely(elo->data[1] == ELO10_TOUCH_PACKET)) { + input_report_abs(dev, ABS_X, (elo->data[4] << 8) | elo->data[3]); + input_report_abs(dev, ABS_Y, (elo->data[6] << 8) | elo->data[5]); + if (elo->data[2] & ELO10_PRESSURE) + input_report_abs(dev, ABS_PRESSURE, + (elo->data[8] << 8) | elo->data[7]); + input_report_key(dev, BTN_TOUCH, elo->data[2] & ELO10_TOUCH); + input_sync(dev); + } else if (elo->data[1] == ELO10_ACK_PACKET) { + if (elo->data[2] == '0') + elo->expected_packet = ELO10_TOUCH_PACKET; + complete(&elo->cmd_done); + } else { + memcpy(elo->response, &elo->data[1], ELO10_PACKET_LEN); + elo->expected_packet = ELO10_ACK_PACKET; + } + break; + } + elo->csum += data; +} + +static void elo_process_data_6(struct elo *elo, unsigned char data) +{ + struct input_dev *dev = elo->dev; + + elo->data[elo->idx] = data; + + switch (elo->idx++) { + + case 0: if ((data & 0xc0) != 0xc0) elo->idx = 0; break; + case 1: if ((data & 0xc0) != 0x80) elo->idx = 0; break; + case 2: if ((data & 0xc0) != 0x40) elo->idx = 0; break; + + case 3: + if (data & 0xc0) { + elo->idx = 0; + break; + } + + input_report_abs(dev, ABS_X, ((elo->data[0] & 0x3f) << 6) | (elo->data[1] & 0x3f)); + input_report_abs(dev, ABS_Y, ((elo->data[2] & 0x3f) << 6) | (elo->data[3] & 0x3f)); + + if (elo->id == 2) { + input_report_key(dev, BTN_TOUCH, 1); + input_sync(dev); + elo->idx = 0; + } + + break; + + case 4: + if (data) { + input_sync(dev); + elo->idx = 0; + } + break; + + case 5: + if ((data & 0xf0) == 0) { + input_report_abs(dev, ABS_PRESSURE, elo->data[5]); + input_report_key(dev, BTN_TOUCH, !!elo->data[5]); + } + input_sync(dev); + elo->idx = 0; + break; + } +} + +static void elo_process_data_3(struct elo *elo, unsigned char data) +{ + struct input_dev *dev = elo->dev; + + elo->data[elo->idx] = data; + + switch (elo->idx++) { + + case 0: + if ((data & 0x7f) != 0x01) + elo->idx = 0; + break; + case 2: + input_report_key(dev, BTN_TOUCH, !(elo->data[1] & 0x80)); + input_report_abs(dev, ABS_X, elo->data[1]); + input_report_abs(dev, ABS_Y, elo->data[2]); + input_sync(dev); + elo->idx = 0; + break; + } +} + +static irqreturn_t elo_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct elo *elo = serio_get_drvdata(serio); + + switch(elo->id) { + case 0: + elo_process_data_10(elo, data); + break; + + case 1: + case 2: + elo_process_data_6(elo, data); + break; + + case 3: + elo_process_data_3(elo, data); + break; + } + + return IRQ_HANDLED; +} + +static int elo_command_10(struct elo *elo, unsigned char *packet) +{ + int rc = -1; + int i; + unsigned char csum = 0xaa + ELO10_LEAD_BYTE; + + mutex_lock(&elo->cmd_mutex); + + serio_pause_rx(elo->serio); + elo->expected_packet = toupper(packet[0]); + init_completion(&elo->cmd_done); + serio_continue_rx(elo->serio); + + if (serio_write(elo->serio, ELO10_LEAD_BYTE)) + goto out; + + for (i = 0; i < ELO10_PACKET_LEN; i++) { + csum += packet[i]; + if (serio_write(elo->serio, packet[i])) + goto out; + } + + if (serio_write(elo->serio, csum)) + goto out; + + wait_for_completion_timeout(&elo->cmd_done, HZ); + + if (elo->expected_packet == ELO10_TOUCH_PACKET) { + /* We are back in reporting mode, the command was ACKed */ + memcpy(packet, elo->response, ELO10_PACKET_LEN); + rc = 0; + } + + out: + mutex_unlock(&elo->cmd_mutex); + return rc; +} + +static int elo_setup_10(struct elo *elo) +{ + static const char *elo_types[] = { "Accu", "Dura", "Intelli", "Carroll" }; + struct input_dev *dev = elo->dev; + unsigned char packet[ELO10_PACKET_LEN] = { ELO10_ID_CMD }; + + if (elo_command_10(elo, packet)) + return -1; + + dev->id.version = (packet[5] << 8) | packet[4]; + + input_set_abs_params(dev, ABS_X, 96, 4000, 0, 0); + input_set_abs_params(dev, ABS_Y, 96, 4000, 0, 0); + if (packet[3] & ELO10_PRESSURE) + input_set_abs_params(dev, ABS_PRESSURE, 0, 255, 0, 0); + + printk(KERN_INFO "elo: %sTouch touchscreen, fw: %02x.%02x, " + "features: 0x%02x, controller: 0x%02x\n", + elo_types[(packet[1] -'0') & 0x03], + packet[5], packet[4], packet[3], packet[7]); + + return 0; +} + +/* + * elo_disconnect() is the opposite of elo_connect() + */ + +static void elo_disconnect(struct serio *serio) +{ + struct elo *elo = serio_get_drvdata(serio); + + input_get_device(elo->dev); + input_unregister_device(elo->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(elo->dev); + kfree(elo); +} + +/* + * elo_connect() is the routine that is called when someone adds a + * new serio device that supports Gunze protocol and registers it as + * an input device. + */ + +static int elo_connect(struct serio *serio, struct serio_driver *drv) +{ + struct elo *elo; + struct input_dev *input_dev; + int err; + + elo = kzalloc(sizeof(struct elo), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!elo || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + elo->serio = serio; + elo->id = serio->id.id; + elo->dev = input_dev; + elo->expected_packet = ELO10_TOUCH_PACKET; + mutex_init(&elo->cmd_mutex); + init_completion(&elo->cmd_done); + snprintf(elo->phys, sizeof(elo->phys), "%s/input0", serio->phys); + + input_dev->name = "Elo Serial TouchScreen"; + input_dev->phys = elo->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_ELO; + input_dev->id.product = elo->id; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + serio_set_drvdata(serio, elo); + err = serio_open(serio, drv); + if (err) + goto fail2; + + switch (elo->id) { + + case 0: /* 10-byte protocol */ + if (elo_setup_10(elo)) + goto fail3; + + break; + + case 1: /* 6-byte protocol */ + input_set_abs_params(input_dev, ABS_PRESSURE, 0, 15, 0, 0); + + case 2: /* 4-byte protocol */ + input_set_abs_params(input_dev, ABS_X, 96, 4000, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 96, 4000, 0, 0); + break; + + case 3: /* 3-byte protocol */ + input_set_abs_params(input_dev, ABS_X, 0, 255, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, 255, 0, 0); + break; + } + + err = input_register_device(elo->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(elo); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id elo_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_ELO, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, elo_serio_ids); + +static struct serio_driver elo_drv = { + .driver = { + .name = "elo", + }, + .description = DRIVER_DESC, + .id_table = elo_serio_ids, + .interrupt = elo_interrupt, + .connect = elo_connect, + .disconnect = elo_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init elo_init(void) +{ + return serio_register_driver(&elo_drv); +} + +static void __exit elo_exit(void) +{ + serio_unregister_driver(&elo_drv); +} + +module_init(elo_init); +module_exit(elo_exit); diff --git a/drivers/input/touchscreen/fujitsu_ts.c b/drivers/input/touchscreen/fujitsu_ts.c new file mode 100644 index 0000000..80b2180 --- /dev/null +++ b/drivers/input/touchscreen/fujitsu_ts.c @@ -0,0 +1,189 @@ +/* + * Fujitsu serial touchscreen driver + * + * Copyright (c) Dmitry Torokhov <dtor@mail.ru> + */ + +/* + * 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. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Fujitsu serial touchscreen driver" + +MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define FUJITSU_LENGTH 5 + +/* + * Per-touchscreen data. + */ +struct fujitsu { + struct input_dev *dev; + struct serio *serio; + int idx; + unsigned char data[FUJITSU_LENGTH]; + char phys[32]; +}; + +/* + * Decode serial data (5 bytes per packet) + * First byte + * 1 C 0 0 R S S S + * Where C is 1 while in calibration mode (which we don't use) + * R is 1 when no coordinate corection was done. + * S are button state + */ +static irqreturn_t fujitsu_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct fujitsu *fujitsu = serio_get_drvdata(serio); + struct input_dev *dev = fujitsu->dev; + + if (fujitsu->idx == 0) { + /* resync skip until start of frame */ + if ((data & 0xf0) != 0x80) + return IRQ_HANDLED; + } else { + /* resync skip garbage */ + if (data & 0x80) { + fujitsu->idx = 0; + return IRQ_HANDLED; + } + } + + fujitsu->data[fujitsu->idx++] = data; + if (fujitsu->idx == FUJITSU_LENGTH) { + input_report_abs(dev, ABS_X, + (fujitsu->data[2] << 7) | fujitsu->data[1]); + input_report_abs(dev, ABS_Y, + (fujitsu->data[4] << 7) | fujitsu->data[3]); + input_report_key(dev, BTN_TOUCH, + (fujitsu->data[0] & 0x03) != 2); + input_sync(dev); + fujitsu->idx = 0; + } + + return IRQ_HANDLED; +} + +/* + * fujitsu_disconnect() is the opposite of fujitsu_connect() + */ +static void fujitsu_disconnect(struct serio *serio) +{ + struct fujitsu *fujitsu = serio_get_drvdata(serio); + + input_get_device(fujitsu->dev); + input_unregister_device(fujitsu->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(fujitsu->dev); + kfree(fujitsu); +} + +/* + * fujitsu_connect() is the routine that is called when someone adds a + * new serio device that supports the Fujitsu protocol and registers it + * as input device. + */ +static int fujitsu_connect(struct serio *serio, struct serio_driver *drv) +{ + struct fujitsu *fujitsu; + struct input_dev *input_dev; + int err; + + fujitsu = kzalloc(sizeof(struct fujitsu), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!fujitsu || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + fujitsu->serio = serio; + fujitsu->dev = input_dev; + snprintf(fujitsu->phys, sizeof(fujitsu->phys), + "%s/input0", serio->phys); + + input_dev->name = "Fujitsu Serial Touchscreen"; + input_dev->phys = fujitsu->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_FUJITSU; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(input_dev, ABS_X, 0, 4096, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, 4096, 0, 0); + serio_set_drvdata(serio, fujitsu); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(fujitsu->dev); + if (err) + goto fail3; + + return 0; + + fail3: + serio_close(serio); + fail2: + serio_set_drvdata(serio, NULL); + fail1: + input_free_device(input_dev); + kfree(fujitsu); + return err; +} + +/* + * The serio driver structure. + */ +static struct serio_device_id fujitsu_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_FUJITSU, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, fujitsu_serio_ids); + +static struct serio_driver fujitsu_drv = { + .driver = { + .name = "fujitsu_ts", + }, + .description = DRIVER_DESC, + .id_table = fujitsu_serio_ids, + .interrupt = fujitsu_interrupt, + .connect = fujitsu_connect, + .disconnect = fujitsu_disconnect, +}; + +static int __init fujitsu_init(void) +{ + return serio_register_driver(&fujitsu_drv); +} + +static void __exit fujitsu_exit(void) +{ + serio_unregister_driver(&fujitsu_drv); +} + +module_init(fujitsu_init); +module_exit(fujitsu_exit); diff --git a/drivers/input/touchscreen/gunze.c b/drivers/input/touchscreen/gunze.c new file mode 100644 index 0000000..a54f90e --- /dev/null +++ b/drivers/input/touchscreen/gunze.c @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2000-2001 Vojtech Pavlik + */ + +/* + * Gunze AHL-51S touchscreen driver for Linux + */ + +/* + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Gunze AHL-51S touchscreen driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define GUNZE_MAX_LENGTH 10 + +/* + * Per-touchscreen data. + */ + +struct gunze { + struct input_dev *dev; + struct serio *serio; + int idx; + unsigned char data[GUNZE_MAX_LENGTH]; + char phys[32]; +}; + +static void gunze_process_packet(struct gunze* gunze) +{ + struct input_dev *dev = gunze->dev; + + if (gunze->idx != GUNZE_MAX_LENGTH || gunze->data[5] != ',' || + (gunze->data[0] != 'T' && gunze->data[0] != 'R')) { + printk(KERN_WARNING "gunze.c: bad packet: >%.*s<\n", GUNZE_MAX_LENGTH, gunze->data); + return; + } + + input_report_abs(dev, ABS_X, simple_strtoul(gunze->data + 1, NULL, 10)); + input_report_abs(dev, ABS_Y, 1024 - simple_strtoul(gunze->data + 6, NULL, 10)); + input_report_key(dev, BTN_TOUCH, gunze->data[0] == 'T'); + input_sync(dev); +} + +static irqreturn_t gunze_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct gunze* gunze = serio_get_drvdata(serio); + + if (data == '\r') { + gunze_process_packet(gunze); + gunze->idx = 0; + } else { + if (gunze->idx < GUNZE_MAX_LENGTH) + gunze->data[gunze->idx++] = data; + } + return IRQ_HANDLED; +} + +/* + * gunze_disconnect() is the opposite of gunze_connect() + */ + +static void gunze_disconnect(struct serio *serio) +{ + struct gunze *gunze = serio_get_drvdata(serio); + + input_get_device(gunze->dev); + input_unregister_device(gunze->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(gunze->dev); + kfree(gunze); +} + +/* + * gunze_connect() is the routine that is called when someone adds a + * new serio device that supports Gunze protocol and registers it as + * an input device. + */ + +static int gunze_connect(struct serio *serio, struct serio_driver *drv) +{ + struct gunze *gunze; + struct input_dev *input_dev; + int err; + + gunze = kzalloc(sizeof(struct gunze), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!gunze || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + gunze->serio = serio; + gunze->dev = input_dev; + snprintf(gunze->phys, sizeof(serio->phys), "%s/input0", serio->phys); + + input_dev->name = "Gunze AHL-51S TouchScreen"; + input_dev->phys = gunze->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_GUNZE; + input_dev->id.product = 0x0051; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, 24, 1000, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 24, 1000, 0, 0); + + serio_set_drvdata(serio, gunze); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(gunze->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(gunze); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id gunze_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_GUNZE, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, gunze_serio_ids); + +static struct serio_driver gunze_drv = { + .driver = { + .name = "gunze", + }, + .description = DRIVER_DESC, + .id_table = gunze_serio_ids, + .interrupt = gunze_interrupt, + .connect = gunze_connect, + .disconnect = gunze_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init gunze_init(void) +{ + return serio_register_driver(&gunze_drv); +} + +static void __exit gunze_exit(void) +{ + serio_unregister_driver(&gunze_drv); +} + +module_init(gunze_init); +module_exit(gunze_exit); diff --git a/drivers/input/touchscreen/h3600_ts_input.c b/drivers/input/touchscreen/h3600_ts_input.c new file mode 100644 index 0000000..4d3139e --- /dev/null +++ b/drivers/input/touchscreen/h3600_ts_input.c @@ -0,0 +1,490 @@ +/* + * Copyright (c) 2001 "Crazy" James Simmons jsimmons@transvirtual.com + * + * Sponsored by Transvirtual Technology. + * + * Derived from the code in h3600_ts.[ch] by Charles Flynn + */ + +/* + * Driver for the h3600 Touch Screen and other Atmel controlled devices. + */ + +/* + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so by + * e-mail - mail your message to <jsimmons@transvirtual.com>. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/delay.h> + +/* SA1100 serial defines */ +#include <mach/hardware.h> +#include <mach/irqs.h> + +#define DRIVER_DESC "H3600 touchscreen driver" + +MODULE_AUTHOR("James Simmons <jsimmons@transvirtual.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +/* The start and end of frame characters SOF and EOF */ +#define CHAR_SOF 0x02 +#define CHAR_EOF 0x03 +#define FRAME_OVERHEAD 3 /* CHAR_SOF,CHAR_EOF,LENGTH = 3 */ + +/* + Atmel events and response IDs contained in frame. + Programmer has no control over these numbers. + TODO there are holes - specifically 1,7,0x0a +*/ +#define VERSION_ID 0 /* Get Version (request/respose) */ +#define KEYBD_ID 2 /* Keyboard (event) */ +#define TOUCHS_ID 3 /* Touch Screen (event)*/ +#define EEPROM_READ_ID 4 /* (request/response) */ +#define EEPROM_WRITE_ID 5 /* (request/response) */ +#define THERMAL_ID 6 /* (request/response) */ +#define NOTIFY_LED_ID 8 /* (request/response) */ +#define BATTERY_ID 9 /* (request/response) */ +#define SPI_READ_ID 0x0b /* ( request/response) */ +#define SPI_WRITE_ID 0x0c /* ( request/response) */ +#define FLITE_ID 0x0d /* backlight ( request/response) */ +#define STX_ID 0xa1 /* extension pack status (req/resp) */ + +#define MAX_ID 14 + +#define H3600_MAX_LENGTH 16 +#define H3600_KEY 0xf + +#define H3600_SCANCODE_RECORD 1 /* 1 -> record button */ +#define H3600_SCANCODE_CALENDAR 2 /* 2 -> calendar */ +#define H3600_SCANCODE_CONTACTS 3 /* 3 -> contact */ +#define H3600_SCANCODE_Q 4 /* 4 -> Q button */ +#define H3600_SCANCODE_START 5 /* 5 -> start menu */ +#define H3600_SCANCODE_UP 6 /* 6 -> up */ +#define H3600_SCANCODE_RIGHT 7 /* 7 -> right */ +#define H3600_SCANCODE_LEFT 8 /* 8 -> left */ +#define H3600_SCANCODE_DOWN 9 /* 9 -> down */ + +/* + * Per-touchscreen data. + */ +struct h3600_dev { + struct input_dev *dev; + struct serio *serio; + unsigned char event; /* event ID from packet */ + unsigned char chksum; + unsigned char len; + unsigned char idx; + unsigned char buf[H3600_MAX_LENGTH]; + char phys[32]; +}; + +static irqreturn_t action_button_handler(int irq, void *dev_id) +{ + int down = (GPLR & GPIO_BITSY_ACTION_BUTTON) ? 0 : 1; + struct input_dev *dev = dev_id; + + input_report_key(dev, KEY_ENTER, down); + input_sync(dev); + + return IRQ_HANDLED; +} + +static irqreturn_t npower_button_handler(int irq, void *dev_id) +{ + int down = (GPLR & GPIO_BITSY_NPOWER_BUTTON) ? 0 : 1; + struct input_dev *dev = dev_id; + + /* + * This interrupt is only called when we release the key. So we have + * to fake a key press. + */ + input_report_key(dev, KEY_SUSPEND, 1); + input_report_key(dev, KEY_SUSPEND, down); + input_sync(dev); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM + +static int flite_brightness = 25; + +enum flite_pwr { + FLITE_PWR_OFF = 0, + FLITE_PWR_ON = 1 +}; + +/* + * h3600_flite_power: enables or disables power to frontlight, using last bright */ +unsigned int h3600_flite_power(struct input_dev *dev, enum flite_pwr pwr) +{ + unsigned char brightness = (pwr == FLITE_PWR_OFF) ? 0 : flite_brightness; + struct h3600_dev *ts = input_get_drvdata(dev); + + /* Must be in this order */ + ts->serio->write(ts->serio, 1); + ts->serio->write(ts->serio, pwr); + ts->serio->write(ts->serio, brightness); + return 0; +} + +#endif + +/* + * This function translates the native event packets to linux input event + * packets. Some packets coming from serial are not touchscreen related. In + * this case we send them off to be processed elsewhere. + */ +static void h3600ts_process_packet(struct h3600_dev *ts) +{ + struct input_dev *dev = ts->dev; + static int touched = 0; + int key, down = 0; + + switch (ts->event) { + /* + Buttons - returned as a single byte + 7 6 5 4 3 2 1 0 + S x x x N N N N + + S switch state ( 0=pressed 1=released) + x Unused. + NNNN switch number 0-15 + + Note: This is true for non interrupt generated key events. + */ + case KEYBD_ID: + down = (ts->buf[0] & 0x80) ? 0 : 1; + + switch (ts->buf[0] & 0x7f) { + case H3600_SCANCODE_RECORD: + key = KEY_RECORD; + break; + case H3600_SCANCODE_CALENDAR: + key = KEY_PROG1; + break; + case H3600_SCANCODE_CONTACTS: + key = KEY_PROG2; + break; + case H3600_SCANCODE_Q: + key = KEY_Q; + break; + case H3600_SCANCODE_START: + key = KEY_PROG3; + break; + case H3600_SCANCODE_UP: + key = KEY_UP; + break; + case H3600_SCANCODE_RIGHT: + key = KEY_RIGHT; + break; + case H3600_SCANCODE_LEFT: + key = KEY_LEFT; + break; + case H3600_SCANCODE_DOWN: + key = KEY_DOWN; + break; + default: + key = 0; + } + if (key) + input_report_key(dev, key, down); + break; + /* + * Native touchscreen event data is formatted as shown below:- + * + * +-------+-------+-------+-------+ + * | Xmsb | Xlsb | Ymsb | Ylsb | + * +-------+-------+-------+-------+ + * byte 0 1 2 3 + */ + case TOUCHS_ID: + if (!touched) { + input_report_key(dev, BTN_TOUCH, 1); + touched = 1; + } + + if (ts->len) { + unsigned short x, y; + + x = ts->buf[0]; x <<= 8; x += ts->buf[1]; + y = ts->buf[2]; y <<= 8; y += ts->buf[3]; + + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + } else { + input_report_key(dev, BTN_TOUCH, 0); + touched = 0; + } + break; + default: + /* Send a non input event elsewhere */ + break; + } + + input_sync(dev); +} + +/* + * h3600ts_event() handles events from the input module. + */ +static int h3600ts_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ +#if 0 + struct h3600_dev *ts = input_get_drvdata(dev); + + switch (type) { + case EV_LED: { + // ts->serio->write(ts->serio, SOME_CMD); + return 0; + } + } + return -1; +#endif + return 0; +} + +/* + Frame format + byte 1 2 3 len + 4 + +-------+---------------+---------------+--=------------+ + |SOF |id |len | len bytes | Chksum | + +-------+---------------+---------------+--=------------+ + bit 0 7 8 11 12 15 16 + + +-------+---------------+-------+ + |SOF |id |0 |Chksum | - Note Chksum does not include SOF + +-------+---------------+-------+ + bit 0 7 8 11 12 15 16 + +*/ + +static int state; + +/* decode States */ +#define STATE_SOF 0 /* start of FRAME */ +#define STATE_ID 1 /* state where we decode the ID & len */ +#define STATE_DATA 2 /* state where we decode data */ +#define STATE_EOF 3 /* state where we decode checksum or EOF */ + +static irqreturn_t h3600ts_interrupt(struct serio *serio, unsigned char data, + unsigned int flags) +{ + struct h3600_dev *ts = serio_get_drvdata(serio); + + /* + * We have a new frame coming in. + */ + switch (state) { + case STATE_SOF: + if (data == CHAR_SOF) + state = STATE_ID; + break; + case STATE_ID: + ts->event = (data & 0xf0) >> 4; + ts->len = (data & 0xf); + ts->idx = 0; + if (ts->event >= MAX_ID) { + state = STATE_SOF; + break; + } + ts->chksum = data; + state = (ts->len > 0) ? STATE_DATA : STATE_EOF; + break; + case STATE_DATA: + ts->chksum += data; + ts->buf[ts->idx]= data; + if (++ts->idx == ts->len) + state = STATE_EOF; + break; + case STATE_EOF: + state = STATE_SOF; + if (data == CHAR_EOF || data == ts->chksum) + h3600ts_process_packet(ts); + break; + default: + printk("Error3\n"); + break; + } + + return IRQ_HANDLED; +} + +/* + * h3600ts_connect() is the routine that is called when someone adds a + * new serio device that supports H3600 protocol and registers it as + * an input device. + */ +static int h3600ts_connect(struct serio *serio, struct serio_driver *drv) +{ + struct h3600_dev *ts; + struct input_dev *input_dev; + int err; + + ts = kzalloc(sizeof(struct h3600_dev), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ts || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + ts->serio = serio; + ts->dev = input_dev; + snprintf(ts->phys, sizeof(ts->phys), "%s/input0", serio->phys); + + input_dev->name = "H3600 TouchScreen"; + input_dev->phys = ts->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_H3600; + input_dev->id.product = 0x0666; /* FIXME !!! We can ask the hardware */ + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_set_drvdata(input_dev, ts); + + input_dev->event = h3600ts_event; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) | + BIT_MASK(EV_LED) | BIT_MASK(EV_PWR); + input_dev->ledbit[0] = BIT_MASK(LED_SLEEP); + input_set_abs_params(input_dev, ABS_X, 60, 985, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 35, 1024, 0, 0); + + set_bit(KEY_RECORD, input_dev->keybit); + set_bit(KEY_Q, input_dev->keybit); + set_bit(KEY_PROG1, input_dev->keybit); + set_bit(KEY_PROG2, input_dev->keybit); + set_bit(KEY_PROG3, input_dev->keybit); + set_bit(KEY_UP, input_dev->keybit); + set_bit(KEY_RIGHT, input_dev->keybit); + set_bit(KEY_LEFT, input_dev->keybit); + set_bit(KEY_DOWN, input_dev->keybit); + set_bit(KEY_ENTER, input_dev->keybit); + set_bit(KEY_SUSPEND, input_dev->keybit); + set_bit(BTN_TOUCH, input_dev->keybit); + + /* Device specific stuff */ + set_GPIO_IRQ_edge(GPIO_BITSY_ACTION_BUTTON, GPIO_BOTH_EDGES); + set_GPIO_IRQ_edge(GPIO_BITSY_NPOWER_BUTTON, GPIO_RISING_EDGE); + + if (request_irq(IRQ_GPIO_BITSY_ACTION_BUTTON, action_button_handler, + IRQF_SHARED | IRQF_DISABLED, "h3600_action", &ts->dev)) { + printk(KERN_ERR "h3600ts.c: Could not allocate Action Button IRQ!\n"); + err = -EBUSY; + goto fail2; + } + + if (request_irq(IRQ_GPIO_BITSY_NPOWER_BUTTON, npower_button_handler, + IRQF_SHARED | IRQF_DISABLED, "h3600_suspend", &ts->dev)) { + printk(KERN_ERR "h3600ts.c: Could not allocate Power Button IRQ!\n"); + err = -EBUSY; + goto fail3; + } + + serio_set_drvdata(serio, ts); + + err = serio_open(serio, drv); + if (err) + return err; + + //h3600_flite_control(1, 25); /* default brightness */ + input_register_device(ts->dev); + + return 0; + +fail3: free_irq(IRQ_GPIO_BITSY_NPOWER_BUTTON, ts->dev); +fail2: free_irq(IRQ_GPIO_BITSY_ACTION_BUTTON, ts->dev); +fail1: serio_set_drvdata(serio, NULL); + input_free_device(input_dev); + kfree(ts); + return err; +} + +/* + * h3600ts_disconnect() is the opposite of h3600ts_connect() + */ + +static void h3600ts_disconnect(struct serio *serio) +{ + struct h3600_dev *ts = serio_get_drvdata(serio); + + free_irq(IRQ_GPIO_BITSY_ACTION_BUTTON, &ts->dev); + free_irq(IRQ_GPIO_BITSY_NPOWER_BUTTON, &ts->dev); + input_get_device(ts->dev); + input_unregister_device(ts->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(ts->dev); + kfree(ts); +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id h3600ts_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_H3600, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, h3600ts_serio_ids); + +static struct serio_driver h3600ts_drv = { + .driver = { + .name = "h3600ts", + }, + .description = DRIVER_DESC, + .id_table = h3600ts_serio_ids, + .interrupt = h3600ts_interrupt, + .connect = h3600ts_connect, + .disconnect = h3600ts_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init h3600ts_init(void) +{ + return serio_register_driver(&h3600ts_drv); +} + +static void __exit h3600ts_exit(void) +{ + serio_unregister_driver(&h3600ts_drv); +} + +module_init(h3600ts_init); +module_exit(h3600ts_exit); diff --git a/drivers/input/touchscreen/hp680_ts_input.c b/drivers/input/touchscreen/hp680_ts_input.c new file mode 100644 index 0000000..a89700e --- /dev/null +++ b/drivers/input/touchscreen/hp680_ts_input.c @@ -0,0 +1,129 @@ +#include <linux/input.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <asm/io.h> +#include <asm/delay.h> +#include <asm/adc.h> +#include <mach/hp6xx.h> + +#define MODNAME "hp680_ts_input" + +#define HP680_TS_ABS_X_MIN 40 +#define HP680_TS_ABS_X_MAX 950 +#define HP680_TS_ABS_Y_MIN 80 +#define HP680_TS_ABS_Y_MAX 910 + +#define PHDR 0xa400012e +#define SCPDR 0xa4000136 + +static void do_softint(struct work_struct *work); + +static struct input_dev *hp680_ts_dev; +static DECLARE_DELAYED_WORK(work, do_softint); + +static void do_softint(struct work_struct *work) +{ + int absx = 0, absy = 0; + u8 scpdr; + int touched = 0; + + if (ctrl_inb(PHDR) & PHDR_TS_PEN_DOWN) { + scpdr = ctrl_inb(SCPDR); + scpdr |= SCPDR_TS_SCAN_ENABLE; + scpdr &= ~SCPDR_TS_SCAN_Y; + ctrl_outb(scpdr, SCPDR); + udelay(30); + + absy = adc_single(ADC_CHANNEL_TS_Y); + + scpdr = ctrl_inb(SCPDR); + scpdr |= SCPDR_TS_SCAN_Y; + scpdr &= ~SCPDR_TS_SCAN_X; + ctrl_outb(scpdr, SCPDR); + udelay(30); + + absx = adc_single(ADC_CHANNEL_TS_X); + + scpdr = ctrl_inb(SCPDR); + scpdr |= SCPDR_TS_SCAN_X; + scpdr &= ~SCPDR_TS_SCAN_ENABLE; + ctrl_outb(scpdr, SCPDR); + udelay(100); + touched = ctrl_inb(PHDR) & PHDR_TS_PEN_DOWN; + } + + if (touched) { + input_report_key(hp680_ts_dev, BTN_TOUCH, 1); + input_report_abs(hp680_ts_dev, ABS_X, absx); + input_report_abs(hp680_ts_dev, ABS_Y, absy); + } else { + input_report_key(hp680_ts_dev, BTN_TOUCH, 0); + } + + input_sync(hp680_ts_dev); + enable_irq(HP680_TS_IRQ); +} + +static irqreturn_t hp680_ts_interrupt(int irq, void *dev) +{ + disable_irq_nosync(irq); + schedule_delayed_work(&work, HZ / 20); + + return IRQ_HANDLED; +} + +static int __init hp680_ts_init(void) +{ + int err; + + hp680_ts_dev = input_allocate_device(); + if (!hp680_ts_dev) + return -ENOMEM; + + hp680_ts_dev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); + hp680_ts_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(hp680_ts_dev, ABS_X, + HP680_TS_ABS_X_MIN, HP680_TS_ABS_X_MAX, 0, 0); + input_set_abs_params(hp680_ts_dev, ABS_Y, + HP680_TS_ABS_Y_MIN, HP680_TS_ABS_Y_MAX, 0, 0); + + hp680_ts_dev->name = "HP Jornada touchscreen"; + hp680_ts_dev->phys = "hp680_ts/input0"; + + if (request_irq(HP680_TS_IRQ, hp680_ts_interrupt, + IRQF_DISABLED, MODNAME, 0) < 0) { + printk(KERN_ERR "hp680_touchscreen.c: Can't allocate irq %d\n", + HP680_TS_IRQ); + err = -EBUSY; + goto fail1; + } + + err = input_register_device(hp680_ts_dev); + if (err) + goto fail2; + + return 0; + + fail2: free_irq(HP680_TS_IRQ, NULL); + cancel_delayed_work(&work); + flush_scheduled_work(); + fail1: input_free_device(hp680_ts_dev); + return err; +} + +static void __exit hp680_ts_exit(void) +{ + free_irq(HP680_TS_IRQ, NULL); + cancel_delayed_work(&work); + flush_scheduled_work(); + input_unregister_device(hp680_ts_dev); +} + +module_init(hp680_ts_init); +module_exit(hp680_ts_exit); + +MODULE_AUTHOR("Andriy Skulysh, askulysh@image.kiev.ua"); +MODULE_DESCRIPTION("HP Jornada 680 touchscreen driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/htcpen.c b/drivers/input/touchscreen/htcpen.c new file mode 100644 index 0000000..62811de --- /dev/null +++ b/drivers/input/touchscreen/htcpen.c @@ -0,0 +1,255 @@ +/* + * HTC Shift touchscreen driver + * + * Copyright (C) 2008 Pau Oliva Fora <pof@eslack.org> + * + * 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. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/isa.h> +#include <linux/ioport.h> +#include <linux/dmi.h> + +MODULE_AUTHOR("Pau Oliva Fora <pau@eslack.org>"); +MODULE_DESCRIPTION("HTC Shift touchscreen driver"); +MODULE_LICENSE("GPL"); + +#define HTCPEN_PORT_IRQ_CLEAR 0x068 +#define HTCPEN_PORT_INIT 0x06c +#define HTCPEN_PORT_INDEX 0x0250 +#define HTCPEN_PORT_DATA 0x0251 +#define HTCPEN_IRQ 3 + +#define DEVICE_ENABLE 0xa2 +#define DEVICE_DISABLE 0xa3 + +#define X_INDEX 3 +#define Y_INDEX 5 +#define TOUCH_INDEX 0xb +#define LSB_XY_INDEX 0xc +#define X_AXIS_MAX 2040 +#define Y_AXIS_MAX 2040 + +static int invert_x; +module_param(invert_x, bool, 0644); +MODULE_PARM_DESC(invert_x, "If set, X axis is inverted"); +static int invert_y; +module_param(invert_y, bool, 0644); +MODULE_PARM_DESC(invert_y, "If set, Y axis is inverted"); + +static struct pnp_device_id pnp_ids[] = { + { .id = "PNP0cc0" }, + { .id = "" } +}; +MODULE_DEVICE_TABLE(pnp, pnp_ids); + +static irqreturn_t htcpen_interrupt(int irq, void *handle) +{ + struct input_dev *htcpen_dev = handle; + unsigned short x, y, xy; + + /* 0 = press; 1 = release */ + outb_p(TOUCH_INDEX, HTCPEN_PORT_INDEX); + + if (inb_p(HTCPEN_PORT_DATA)) { + input_report_key(htcpen_dev, BTN_TOUCH, 0); + } else { + outb_p(X_INDEX, HTCPEN_PORT_INDEX); + x = inb_p(HTCPEN_PORT_DATA); + + outb_p(Y_INDEX, HTCPEN_PORT_INDEX); + y = inb_p(HTCPEN_PORT_DATA); + + outb_p(LSB_XY_INDEX, HTCPEN_PORT_INDEX); + xy = inb_p(HTCPEN_PORT_DATA); + + /* get high resolution value of X and Y using LSB */ + x = X_AXIS_MAX - ((x * 8) + ((xy >> 4) & 0xf)); + y = (y * 8) + (xy & 0xf); + if (invert_x) + x = X_AXIS_MAX - x; + if (invert_y) + y = Y_AXIS_MAX - y; + + if (x != X_AXIS_MAX && x != 0) { + input_report_key(htcpen_dev, BTN_TOUCH, 1); + input_report_abs(htcpen_dev, ABS_X, x); + input_report_abs(htcpen_dev, ABS_Y, y); + } + } + + input_sync(htcpen_dev); + + inb_p(HTCPEN_PORT_IRQ_CLEAR); + + return IRQ_HANDLED; +} + +static int htcpen_open(struct input_dev *dev) +{ + outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); + + return 0; +} + +static void htcpen_close(struct input_dev *dev) +{ + outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); + synchronize_irq(HTCPEN_IRQ); +} + +static int __devinit htcpen_isa_probe(struct device *dev, unsigned int id) +{ + struct input_dev *htcpen_dev; + int err = -EBUSY; + + if (!request_region(HTCPEN_PORT_IRQ_CLEAR, 1, "htcpen")) { + printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", + HTCPEN_PORT_IRQ_CLEAR); + goto request_region1_failed; + } + + if (!request_region(HTCPEN_PORT_INIT, 1, "htcpen")) { + printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", + HTCPEN_PORT_INIT); + goto request_region2_failed; + } + + if (!request_region(HTCPEN_PORT_INDEX, 2, "htcpen")) { + printk(KERN_ERR "htcpen: unable to get IO region 0x%x\n", + HTCPEN_PORT_INDEX); + goto request_region3_failed; + } + + htcpen_dev = input_allocate_device(); + if (!htcpen_dev) { + printk(KERN_ERR "htcpen: can't allocate device\n"); + err = -ENOMEM; + goto input_alloc_failed; + } + + htcpen_dev->name = "HTC Shift EC TouchScreen"; + htcpen_dev->id.bustype = BUS_ISA; + + htcpen_dev->evbit[0] = BIT_MASK(EV_ABS) | BIT_MASK(EV_KEY); + htcpen_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(htcpen_dev, ABS_X, 0, X_AXIS_MAX, 0, 0); + input_set_abs_params(htcpen_dev, ABS_Y, 0, Y_AXIS_MAX, 0, 0); + + htcpen_dev->open = htcpen_open; + htcpen_dev->close = htcpen_close; + + err = request_irq(HTCPEN_IRQ, htcpen_interrupt, 0, "htcpen", + htcpen_dev); + if (err) { + printk(KERN_ERR "htcpen: irq busy\n"); + goto request_irq_failed; + } + + inb_p(HTCPEN_PORT_IRQ_CLEAR); + + err = input_register_device(htcpen_dev); + if (err) + goto input_register_failed; + + dev_set_drvdata(dev, htcpen_dev); + + return 0; + + input_register_failed: + free_irq(HTCPEN_IRQ, htcpen_dev); + request_irq_failed: + input_free_device(htcpen_dev); + input_alloc_failed: + release_region(HTCPEN_PORT_INDEX, 2); + request_region3_failed: + release_region(HTCPEN_PORT_INIT, 1); + request_region2_failed: + release_region(HTCPEN_PORT_IRQ_CLEAR, 1); + request_region1_failed: + return err; +} + +static int __devexit htcpen_isa_remove(struct device *dev, unsigned int id) +{ + struct input_dev *htcpen_dev = dev_get_drvdata(dev); + + input_unregister_device(htcpen_dev); + + free_irq(HTCPEN_IRQ, htcpen_dev); + + release_region(HTCPEN_PORT_INDEX, 2); + release_region(HTCPEN_PORT_INIT, 1); + release_region(HTCPEN_PORT_IRQ_CLEAR, 1); + + dev_set_drvdata(dev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int htcpen_isa_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + outb_p(DEVICE_DISABLE, HTCPEN_PORT_INIT); + + return 0; +} + +static int htcpen_isa_resume(struct device *dev, unsigned int n) +{ + outb_p(DEVICE_ENABLE, HTCPEN_PORT_INIT); + + return 0; +} +#endif + +static struct isa_driver htcpen_isa_driver = { + .probe = htcpen_isa_probe, + .remove = __devexit_p(htcpen_isa_remove), +#ifdef CONFIG_PM + .suspend = htcpen_isa_suspend, + .resume = htcpen_isa_resume, +#endif + .driver = { + .owner = THIS_MODULE, + .name = "htcpen", + } +}; + +static struct dmi_system_id __initdata htcshift_dmi_table[] = { + { + .ident = "Shift", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "High Tech Computer Corp"), + DMI_MATCH(DMI_PRODUCT_NAME, "Shift"), + }, + }, + { } +}; + +static int __init htcpen_isa_init(void) +{ + if (!dmi_check_system(htcshift_dmi_table)) + return -ENODEV; + + return isa_register_driver(&htcpen_isa_driver, 1); +} + +static void __exit htcpen_isa_exit(void) +{ + isa_unregister_driver(&htcpen_isa_driver); +} + +module_init(htcpen_isa_init); +module_exit(htcpen_isa_exit); diff --git a/drivers/input/touchscreen/inexio.c b/drivers/input/touchscreen/inexio.c new file mode 100644 index 0000000..192ade0 --- /dev/null +++ b/drivers/input/touchscreen/inexio.c @@ -0,0 +1,207 @@ +/* + * iNexio serial touchscreen driver + * + * Copyright (c) 2008 Richard Lemon + * Based on the mtouch driver (c) Vojtech Pavlik and Dan Streetman + * + */ + +/* + * 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. + */ + +/* + * 2008/06/19 Richard Lemon <richard@codelemon.com> + * Copied mtouch.c and edited for iNexio protocol + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "iNexio serial touchscreen driver" + +MODULE_AUTHOR("Richard Lemon <richard@codelemon.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define INEXIO_FORMAT_TOUCH_BIT 0x01 +#define INEXIO_FORMAT_LENGTH 5 +#define INEXIO_RESPONSE_BEGIN_BYTE 0x80 + +/* todo: check specs for max length of all responses */ +#define INEXIO_MAX_LENGTH 16 + +#define INEXIO_MIN_XC 0 +#define INEXIO_MAX_XC 0x3fff +#define INEXIO_MIN_YC 0 +#define INEXIO_MAX_YC 0x3fff + +#define INEXIO_GET_XC(data) (((data[1])<<7) | data[2]) +#define INEXIO_GET_YC(data) (((data[3])<<7) | data[4]) +#define INEXIO_GET_TOUCHED(data) (INEXIO_FORMAT_TOUCH_BIT & data[0]) + +/* + * Per-touchscreen data. + */ + +struct inexio { + struct input_dev *dev; + struct serio *serio; + int idx; + unsigned char data[INEXIO_MAX_LENGTH]; + char phys[32]; +}; + +static void inexio_process_data(struct inexio *pinexio) +{ + struct input_dev *dev = pinexio->dev; + + if (INEXIO_FORMAT_LENGTH == ++pinexio->idx) { + input_report_abs(dev, ABS_X, INEXIO_GET_XC(pinexio->data)); + input_report_abs(dev, ABS_Y, INEXIO_GET_YC(pinexio->data)); + input_report_key(dev, BTN_TOUCH, INEXIO_GET_TOUCHED(pinexio->data)); + input_sync(dev); + + pinexio->idx = 0; + } +} + +static irqreturn_t inexio_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct inexio* pinexio = serio_get_drvdata(serio); + + pinexio->data[pinexio->idx] = data; + + if (INEXIO_RESPONSE_BEGIN_BYTE&pinexio->data[0]) + inexio_process_data(pinexio); + else + printk(KERN_DEBUG "inexio.c: unknown/unsynchronized data from device, byte %x\n",pinexio->data[0]); + + return IRQ_HANDLED; +} + +/* + * inexio_disconnect() is the opposite of inexio_connect() + */ + +static void inexio_disconnect(struct serio *serio) +{ + struct inexio* pinexio = serio_get_drvdata(serio); + + input_get_device(pinexio->dev); + input_unregister_device(pinexio->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(pinexio->dev); + kfree(pinexio); +} + +/* + * inexio_connect() is the routine that is called when someone adds a + * new serio device that supports iNexio protocol and registers it as + * an input device. This is usually accomplished using inputattach. + */ + +static int inexio_connect(struct serio *serio, struct serio_driver *drv) +{ + struct inexio *pinexio; + struct input_dev *input_dev; + int err; + + pinexio = kzalloc(sizeof(struct inexio), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!pinexio || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + pinexio->serio = serio; + pinexio->dev = input_dev; + snprintf(pinexio->phys, sizeof(pinexio->phys), "%s/input0", serio->phys); + + input_dev->name = "iNexio Serial TouchScreen"; + input_dev->phys = pinexio->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_INEXIO; + input_dev->id.product = 0; + input_dev->id.version = 0x0001; + input_dev->dev.parent = &serio->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(pinexio->dev, ABS_X, INEXIO_MIN_XC, INEXIO_MAX_XC, 0, 0); + input_set_abs_params(pinexio->dev, ABS_Y, INEXIO_MIN_YC, INEXIO_MAX_YC, 0, 0); + + serio_set_drvdata(serio, pinexio); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(pinexio->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(pinexio); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id inexio_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_INEXIO, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, inexio_serio_ids); + +static struct serio_driver inexio_drv = { + .driver = { + .name = "inexio", + }, + .description = DRIVER_DESC, + .id_table = inexio_serio_ids, + .interrupt = inexio_interrupt, + .connect = inexio_connect, + .disconnect = inexio_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init inexio_init(void) +{ + return serio_register_driver(&inexio_drv); +} + +static void __exit inexio_exit(void) +{ + serio_unregister_driver(&inexio_drv); +} + +module_init(inexio_init); +module_exit(inexio_exit); diff --git a/drivers/input/touchscreen/jornada720_ts.c b/drivers/input/touchscreen/jornada720_ts.c new file mode 100644 index 0000000..c8b7e8a --- /dev/null +++ b/drivers/input/touchscreen/jornada720_ts.c @@ -0,0 +1,186 @@ +/* + * drivers/input/touchscreen/jornada720_ts.c + * + * Copyright (C) 2007 Kristoffer Ericson <Kristoffer.Ericson@gmail.com> + * + * Copyright (C) 2006 Filip Zyzniewski <filip.zyzniewski@tefnet.pl> + * based on HP Jornada 56x touchscreen driver by Alex Lange <chicken@handhelds.org> + * + * 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. + * + * HP Jornada 710/720/729 Touchscreen Driver + */ + +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> + +#include <mach/hardware.h> +#include <mach/jornada720.h> + +MODULE_AUTHOR("Kristoffer Ericson <kristoffer.ericson@gmail.com>"); +MODULE_DESCRIPTION("HP Jornada 710/720/728 touchscreen driver"); +MODULE_LICENSE("GPL v2"); + +struct jornada_ts { + struct input_dev *dev; + int x_data[4]; /* X sample values */ + int y_data[4]; /* Y sample values */ +}; + +static void jornada720_ts_collect_data(struct jornada_ts *jornada_ts) +{ + + /* 3 low word X samples */ + jornada_ts->x_data[0] = jornada_ssp_byte(TXDUMMY); + jornada_ts->x_data[1] = jornada_ssp_byte(TXDUMMY); + jornada_ts->x_data[2] = jornada_ssp_byte(TXDUMMY); + + /* 3 low word Y samples */ + jornada_ts->y_data[0] = jornada_ssp_byte(TXDUMMY); + jornada_ts->y_data[1] = jornada_ssp_byte(TXDUMMY); + jornada_ts->y_data[2] = jornada_ssp_byte(TXDUMMY); + + /* combined x samples bits */ + jornada_ts->x_data[3] = jornada_ssp_byte(TXDUMMY); + + /* combined y samples bits */ + jornada_ts->y_data[3] = jornada_ssp_byte(TXDUMMY); +} + +static int jornada720_ts_average(int coords[4]) +{ + int coord, high_bits = coords[3]; + + coord = coords[0] | ((high_bits & 0x03) << 8); + coord += coords[1] | ((high_bits & 0x0c) << 6); + coord += coords[2] | ((high_bits & 0x30) << 4); + + return coord / 3; +} + +static irqreturn_t jornada720_ts_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct jornada_ts *jornada_ts = platform_get_drvdata(pdev); + struct input_dev *input = jornada_ts->dev; + int x, y; + + /* If GPIO_GPIO9 is set to high then report pen up */ + if (GPLR & GPIO_GPIO(9)) { + input_report_key(input, BTN_TOUCH, 0); + input_sync(input); + } else { + jornada_ssp_start(); + + /* proper reply to request is always TXDUMMY */ + if (jornada_ssp_inout(GETTOUCHSAMPLES) == TXDUMMY) { + jornada720_ts_collect_data(jornada_ts); + + x = jornada720_ts_average(jornada_ts->x_data); + y = jornada720_ts_average(jornada_ts->y_data); + + input_report_key(input, BTN_TOUCH, 1); + input_report_abs(input, ABS_X, x); + input_report_abs(input, ABS_Y, y); + input_sync(input); + } + + jornada_ssp_end(); + } + + return IRQ_HANDLED; +} + +static int __devinit jornada720_ts_probe(struct platform_device *pdev) +{ + struct jornada_ts *jornada_ts; + struct input_dev *input_dev; + int error; + + jornada_ts = kzalloc(sizeof(struct jornada_ts), GFP_KERNEL); + input_dev = input_allocate_device(); + + if (!jornada_ts || !input_dev) { + error = -ENOMEM; + goto fail1; + } + + platform_set_drvdata(pdev, jornada_ts); + + jornada_ts->dev = input_dev; + + input_dev->name = "HP Jornada 7xx Touchscreen"; + input_dev->phys = "jornadats/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, 270, 3900, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 180, 3700, 0, 0); + + error = request_irq(IRQ_GPIO9, + jornada720_ts_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_RISING, + "HP7XX Touchscreen driver", pdev); + if (error) { + printk(KERN_INFO "HP7XX TS : Unable to acquire irq!\n"); + goto fail1; + } + + error = input_register_device(jornada_ts->dev); + if (error) + goto fail2; + + return 0; + + fail2: + free_irq(IRQ_GPIO9, pdev); + fail1: + platform_set_drvdata(pdev, NULL); + input_free_device(input_dev); + kfree(jornada_ts); + return error; +} + +static int __devexit jornada720_ts_remove(struct platform_device *pdev) +{ + struct jornada_ts *jornada_ts = platform_get_drvdata(pdev); + + free_irq(IRQ_GPIO9, pdev); + platform_set_drvdata(pdev, NULL); + input_unregister_device(jornada_ts->dev); + kfree(jornada_ts); + + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:jornada_ts"); + +static struct platform_driver jornada720_ts_driver = { + .probe = jornada720_ts_probe, + .remove = __devexit_p(jornada720_ts_remove), + .driver = { + .name = "jornada_ts", + .owner = THIS_MODULE, + }, +}; + +static int __init jornada720_ts_init(void) +{ + return platform_driver_register(&jornada720_ts_driver); +} + +static void __exit jornada720_ts_exit(void) +{ + platform_driver_unregister(&jornada720_ts_driver); +} + +module_init(jornada720_ts_init); +module_exit(jornada720_ts_exit); diff --git a/drivers/input/touchscreen/mainstone-wm97xx.c b/drivers/input/touchscreen/mainstone-wm97xx.c new file mode 100644 index 0000000..ba64875 --- /dev/null +++ b/drivers/input/touchscreen/mainstone-wm97xx.c @@ -0,0 +1,300 @@ +/* + * mainstone-wm97xx.c -- Mainstone Continuous Touch screen driver for + * Wolfson WM97xx AC97 Codecs. + * + * Copyright 2004, 2007 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * + * 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. + * + * Notes: + * This is a wm97xx extended touch driver to capture touch + * data in a continuous manner on the Intel XScale archictecture + * + * Features: + * - codecs supported:- WM9705, WM9712, WM9713 + * - processors supported:- Intel XScale PXA25x, PXA26x, PXA27x + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/wm97xx.h> +#include <linux/io.h> +#include <mach/pxa-regs.h> + +#define VERSION "0.13" + +struct continuous { + u16 id; /* codec id */ + u8 code; /* continuous code */ + u8 reads; /* number of coord reads per read cycle */ + u32 speed; /* number of coords per second */ +}; + +#define WM_READS(sp) ((sp / HZ) + 1) + +static const struct continuous cinfo[] = { + {WM9705_ID2, 0, WM_READS(94), 94}, + {WM9705_ID2, 1, WM_READS(188), 188}, + {WM9705_ID2, 2, WM_READS(375), 375}, + {WM9705_ID2, 3, WM_READS(750), 750}, + {WM9712_ID2, 0, WM_READS(94), 94}, + {WM9712_ID2, 1, WM_READS(188), 188}, + {WM9712_ID2, 2, WM_READS(375), 375}, + {WM9712_ID2, 3, WM_READS(750), 750}, + {WM9713_ID2, 0, WM_READS(94), 94}, + {WM9713_ID2, 1, WM_READS(120), 120}, + {WM9713_ID2, 2, WM_READS(154), 154}, + {WM9713_ID2, 3, WM_READS(188), 188}, +}; + +/* continuous speed index */ +static int sp_idx; +static u16 last, tries; + +/* + * Pen sampling frequency (Hz) in continuous mode. + */ +static int cont_rate = 200; +module_param(cont_rate, int, 0); +MODULE_PARM_DESC(cont_rate, "Sampling rate in continuous mode (Hz)"); + +/* + * Pen down detection. + * + * This driver can either poll or use an interrupt to indicate a pen down + * event. If the irq request fails then it will fall back to polling mode. + */ +static int pen_int; +module_param(pen_int, int, 0); +MODULE_PARM_DESC(pen_int, "Pen down detection (1 = interrupt, 0 = polling)"); + +/* + * Pressure readback. + * + * Set to 1 to read back pen down pressure + */ +static int pressure; +module_param(pressure, int, 0); +MODULE_PARM_DESC(pressure, "Pressure readback (1 = pressure, 0 = no pressure)"); + +/* + * AC97 touch data slot. + * + * Touch screen readback data ac97 slot + */ +static int ac97_touch_slot = 5; +module_param(ac97_touch_slot, int, 0); +MODULE_PARM_DESC(ac97_touch_slot, "Touch screen data slot AC97 number"); + + +/* flush AC97 slot 5 FIFO on pxa machines */ +#ifdef CONFIG_PXA27x +static void wm97xx_acc_pen_up(struct wm97xx *wm) +{ + schedule_timeout_uninterruptible(1); + + while (MISR & (1 << 2)) + MODR; +} +#else +static void wm97xx_acc_pen_up(struct wm97xx *wm) +{ + int count = 16; + schedule_timeout_uninterruptible(1); + + while (count < 16) { + MODR; + count--; + } +} +#endif + +static int wm97xx_acc_pen_down(struct wm97xx *wm) +{ + u16 x, y, p = 0x100 | WM97XX_ADCSEL_PRES; + int reads = 0; + + /* When the AC97 queue has been drained we need to allow time + * to buffer up samples otherwise we end up spinning polling + * for samples. The controller can't have a suitably low + * threashold set to use the notifications it gives. + */ + schedule_timeout_uninterruptible(1); + + if (tries > 5) { + tries = 0; + return RC_PENUP; + } + + x = MODR; + if (x == last) { + tries++; + return RC_AGAIN; + } + last = x; + do { + if (reads) + x = MODR; + y = MODR; + if (pressure) + p = MODR; + + /* are samples valid */ + if ((x & WM97XX_ADCSRC_MASK) != WM97XX_ADCSEL_X || + (y & WM97XX_ADCSRC_MASK) != WM97XX_ADCSEL_Y || + (p & WM97XX_ADCSRC_MASK) != WM97XX_ADCSEL_PRES) + goto up; + + /* coordinate is good */ + tries = 0; + input_report_abs(wm->input_dev, ABS_X, x & 0xfff); + input_report_abs(wm->input_dev, ABS_Y, y & 0xfff); + input_report_abs(wm->input_dev, ABS_PRESSURE, p & 0xfff); + input_sync(wm->input_dev); + reads++; + } while (reads < cinfo[sp_idx].reads); +up: + return RC_PENDOWN | RC_AGAIN; +} + +static int wm97xx_acc_startup(struct wm97xx *wm) +{ + int idx = 0; + + /* check we have a codec */ + if (wm->ac97 == NULL) + return -ENODEV; + + /* Go you big red fire engine */ + for (idx = 0; idx < ARRAY_SIZE(cinfo); idx++) { + if (wm->id != cinfo[idx].id) + continue; + sp_idx = idx; + if (cont_rate <= cinfo[idx].speed) + break; + } + wm->acc_rate = cinfo[sp_idx].code; + wm->acc_slot = ac97_touch_slot; + dev_info(wm->dev, + "mainstone accelerated touchscreen driver, %d samples/sec\n", + cinfo[sp_idx].speed); + + /* codec specific irq config */ + if (pen_int) { + switch (wm->id) { + case WM9705_ID2: + wm->pen_irq = IRQ_GPIO(4); + set_irq_type(IRQ_GPIO(4), IRQ_TYPE_EDGE_BOTH); + break; + case WM9712_ID2: + case WM9713_ID2: + /* enable pen down interrupt */ + /* use PEN_DOWN GPIO 13 to assert IRQ on GPIO line 2 */ + wm->pen_irq = MAINSTONE_AC97_IRQ; + wm97xx_config_gpio(wm, WM97XX_GPIO_13, WM97XX_GPIO_IN, + WM97XX_GPIO_POL_HIGH, + WM97XX_GPIO_STICKY, + WM97XX_GPIO_WAKE); + wm97xx_config_gpio(wm, WM97XX_GPIO_2, WM97XX_GPIO_OUT, + WM97XX_GPIO_POL_HIGH, + WM97XX_GPIO_NOTSTICKY, + WM97XX_GPIO_NOWAKE); + break; + default: + dev_err(wm->dev, + "pen down irq not supported on this device\n"); + pen_int = 0; + break; + } + } + + return 0; +} + +static void wm97xx_acc_shutdown(struct wm97xx *wm) +{ + /* codec specific deconfig */ + if (pen_int) { + switch (wm->id & 0xffff) { + case WM9705_ID2: + wm->pen_irq = 0; + break; + case WM9712_ID2: + case WM9713_ID2: + /* disable interrupt */ + wm->pen_irq = 0; + break; + } + } +} + +static void wm97xx_irq_enable(struct wm97xx *wm, int enable) +{ + if (enable) + enable_irq(wm->pen_irq); + else + disable_irq(wm->pen_irq); +} + +static struct wm97xx_mach_ops mainstone_mach_ops = { + .acc_enabled = 1, + .acc_pen_up = wm97xx_acc_pen_up, + .acc_pen_down = wm97xx_acc_pen_down, + .acc_startup = wm97xx_acc_startup, + .acc_shutdown = wm97xx_acc_shutdown, + .irq_enable = wm97xx_irq_enable, + .irq_gpio = WM97XX_GPIO_2, +}; + +static int mainstone_wm97xx_probe(struct platform_device *pdev) +{ + struct wm97xx *wm = platform_get_drvdata(pdev); + + return wm97xx_register_mach_ops(wm, &mainstone_mach_ops); +} + +static int mainstone_wm97xx_remove(struct platform_device *pdev) +{ + struct wm97xx *wm = platform_get_drvdata(pdev); + + wm97xx_unregister_mach_ops(wm); + return 0; +} + +static struct platform_driver mainstone_wm97xx_driver = { + .probe = mainstone_wm97xx_probe, + .remove = mainstone_wm97xx_remove, + .driver = { + .name = "wm97xx-touch", + }, +}; + +static int __init mainstone_wm97xx_init(void) +{ + return platform_driver_register(&mainstone_wm97xx_driver); +} + +static void __exit mainstone_wm97xx_exit(void) +{ + platform_driver_unregister(&mainstone_wm97xx_driver); +} + +module_init(mainstone_wm97xx_init); +module_exit(mainstone_wm97xx_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("wm97xx continuous touch driver for mainstone"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/migor_ts.c b/drivers/input/touchscreen/migor_ts.c new file mode 100644 index 0000000..504ca11 --- /dev/null +++ b/drivers/input/touchscreen/migor_ts.c @@ -0,0 +1,257 @@ +/* + * Touch Screen driver for Renesas MIGO-R Platform + * + * Copyright (c) 2008 Magnus Damm + * Copyright (c) 2007 Ujjwal Pande <ujjwal@kenati.com>, + * Kenati Technologies Pvt Ltd. + * + * This file 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 file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <asm/io.h> +#include <linux/i2c.h> +#include <linux/timer.h> + +#define EVENT_PENDOWN 1 +#define EVENT_REPEAT 2 +#define EVENT_PENUP 3 + +struct migor_ts_priv { + struct i2c_client *client; + struct input_dev *input; + struct delayed_work work; + int irq; +}; + +static const u_int8_t migor_ts_ena_seq[17] = { 0x33, 0x22, 0x11, + 0x01, 0x06, 0x07, }; +static const u_int8_t migor_ts_dis_seq[17] = { }; + +static void migor_ts_poscheck(struct work_struct *work) +{ + struct migor_ts_priv *priv = container_of(work, + struct migor_ts_priv, + work.work); + unsigned short xpos, ypos; + unsigned char event; + u_int8_t buf[16]; + + memset(buf, 0, sizeof(buf)); + + /* Set Index 0 */ + buf[0] = 0; + if (i2c_master_send(priv->client, buf, 1) != 1) { + dev_err(&priv->client->dev, "Unable to write i2c index\n"); + goto out; + } + + /* Now do Page Read */ + if (i2c_master_recv(priv->client, buf, sizeof(buf)) != sizeof(buf)) { + dev_err(&priv->client->dev, "Unable to read i2c page\n"); + goto out; + } + + ypos = ((buf[9] & 0x03) << 8 | buf[8]); + xpos = ((buf[11] & 0x03) << 8 | buf[10]); + event = buf[12]; + + if (event == EVENT_PENDOWN || event == EVENT_REPEAT) { + input_report_key(priv->input, BTN_TOUCH, 1); + input_report_abs(priv->input, ABS_X, ypos); /*X-Y swap*/ + input_report_abs(priv->input, ABS_Y, xpos); + input_sync(priv->input); + } else if (event == EVENT_PENUP) { + input_report_key(priv->input, BTN_TOUCH, 0); + input_sync(priv->input); + } + out: + enable_irq(priv->irq); +} + +static irqreturn_t migor_ts_isr(int irq, void *dev_id) +{ + struct migor_ts_priv *priv = dev_id; + + /* the touch screen controller chip is hooked up to the cpu + * using i2c and a single interrupt line. the interrupt line + * is pulled low whenever someone taps the screen. to deassert + * the interrupt line we need to acknowledge the interrupt by + * communicating with the controller over the slow i2c bus. + * + * we can't acknowledge from interrupt context since the i2c + * bus controller may sleep, so we just disable the interrupt + * here and handle the acknowledge using delayed work. + */ + + disable_irq_nosync(irq); + schedule_delayed_work(&priv->work, HZ / 20); + + return IRQ_HANDLED; +} + + +static int migor_ts_open(struct input_dev *dev) +{ + struct migor_ts_priv *priv = input_get_drvdata(dev); + struct i2c_client *client = priv->client; + int count; + + /* enable controller */ + count = i2c_master_send(client, migor_ts_ena_seq, + sizeof(migor_ts_ena_seq)); + if (count != sizeof(migor_ts_ena_seq)) { + dev_err(&client->dev, "Unable to enable touchscreen.\n"); + return -ENXIO; + } + + return 0; +} + +static void migor_ts_close(struct input_dev *dev) +{ + struct migor_ts_priv *priv = input_get_drvdata(dev); + struct i2c_client *client = priv->client; + + disable_irq(priv->irq); + + /* cancel pending work and wait for migor_ts_poscheck() to finish */ + if (cancel_delayed_work_sync(&priv->work)) { + /* + * if migor_ts_poscheck was canceled we need to enable IRQ + * here to balance disable done in migor_ts_isr. + */ + enable_irq(priv->irq); + } + + /* disable controller */ + i2c_master_send(client, migor_ts_dis_seq, sizeof(migor_ts_dis_seq)); + + enable_irq(priv->irq); +} + +static int migor_ts_probe(struct i2c_client *client, + const struct i2c_device_id *idp) +{ + struct migor_ts_priv *priv; + struct input_dev *input; + int error; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&client->dev, "failed to allocate driver data\n"); + error = -ENOMEM; + goto err0; + } + + dev_set_drvdata(&client->dev, priv); + + input = input_allocate_device(); + if (!input) { + dev_err(&client->dev, "Failed to allocate input device.\n"); + error = -ENOMEM; + goto err1; + } + + input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(input, ABS_X, 95, 955, 0, 0); + input_set_abs_params(input, ABS_Y, 85, 935, 0, 0); + + input->name = client->name; + input->id.bustype = BUS_I2C; + input->dev.parent = &client->dev; + + input->open = migor_ts_open; + input->close = migor_ts_close; + + input_set_drvdata(input, priv); + + priv->client = client; + priv->input = input; + INIT_DELAYED_WORK(&priv->work, migor_ts_poscheck); + priv->irq = client->irq; + + error = input_register_device(input); + if (error) + goto err1; + + error = request_irq(priv->irq, migor_ts_isr, IRQF_TRIGGER_LOW, + client->name, priv); + if (error) { + dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); + goto err2; + } + + return 0; + + err2: + input_unregister_device(input); + input = NULL; /* so we dont try to free it below */ + err1: + input_free_device(input); + kfree(priv); + err0: + dev_set_drvdata(&client->dev, NULL); + return error; +} + +static int migor_ts_remove(struct i2c_client *client) +{ + struct migor_ts_priv *priv = dev_get_drvdata(&client->dev); + + free_irq(priv->irq, priv); + input_unregister_device(priv->input); + kfree(priv); + + dev_set_drvdata(&client->dev, NULL); + + return 0; +} + +static const struct i2c_device_id migor_ts_id[] = { + { "migor_ts", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, migor_ts); + +static struct i2c_driver migor_ts_driver = { + .driver = { + .name = "migor_ts", + }, + .probe = migor_ts_probe, + .remove = migor_ts_remove, + .id_table = migor_ts_id, +}; + +static int __init migor_ts_init(void) +{ + return i2c_add_driver(&migor_ts_driver); +} + +static void __exit migor_ts_exit(void) +{ + i2c_del_driver(&migor_ts_driver); +} + +MODULE_DESCRIPTION("MigoR Touchscreen driver"); +MODULE_AUTHOR("Magnus Damm <damm@opensource.se>"); +MODULE_LICENSE("GPL"); + +module_init(migor_ts_init); +module_exit(migor_ts_exit); diff --git a/drivers/input/touchscreen/mk712.c b/drivers/input/touchscreen/mk712.c new file mode 100644 index 0000000..efd3aeb --- /dev/null +++ b/drivers/input/touchscreen/mk712.c @@ -0,0 +1,219 @@ +/* + * ICS MK712 touchscreen controller driver + * + * Copyright (c) 1999-2002 Transmeta Corporation + * Copyright (c) 2005 Rick Koch <n1gp@hotmail.com> + * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> + */ + +/* + * 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. + */ + +/* + * This driver supports the ICS MicroClock MK712 TouchScreen controller, + * found in Gateway AOL Connected Touchpad computers. + * + * Documentation for ICS MK712 can be found at: + * http://www.icst.com/pdf/mk712.pdf + */ + +/* + * 1999-12-18: original version, Daniel Quinlan + * 1999-12-19: added anti-jitter code, report pen-up events, fixed mk712_poll + * to use queue_empty, Nathan Laredo + * 1999-12-20: improved random point rejection, Nathan Laredo + * 2000-01-05: checked in new anti-jitter code, changed mouse protocol, fixed + * queue code, added module options, other fixes, Daniel Quinlan + * 2002-03-15: Clean up for kernel merge <alan@redhat.com> + * Fixed multi open race, fixed memory checks, fixed resource + * allocation, fixed close/powerdown bug, switched to new init + * 2005-01-18: Ported to 2.6 from 2.4.28, Rick Koch + * 2005-02-05: Rewritten for the input layer, Vojtech Pavlik + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <asm/io.h> + +MODULE_AUTHOR("Daniel Quinlan <quinlan@pathname.com>, Vojtech Pavlik <vojtech@suse.cz>"); +MODULE_DESCRIPTION("ICS MicroClock MK712 TouchScreen driver"); +MODULE_LICENSE("GPL"); + +static unsigned int mk712_io = 0x260; /* Also 0x200, 0x208, 0x300 */ +module_param_named(io, mk712_io, uint, 0); +MODULE_PARM_DESC(io, "I/O base address of MK712 touchscreen controller"); + +static unsigned int mk712_irq = 10; /* Also 12, 14, 15 */ +module_param_named(irq, mk712_irq, uint, 0); +MODULE_PARM_DESC(irq, "IRQ of MK712 touchscreen controller"); + +/* eight 8-bit registers */ +#define MK712_STATUS 0 +#define MK712_X 2 +#define MK712_Y 4 +#define MK712_CONTROL 6 +#define MK712_RATE 7 + +/* status */ +#define MK712_STATUS_TOUCH 0x10 +#define MK712_CONVERSION_COMPLETE 0x80 + +/* control */ +#define MK712_ENABLE_INT 0x01 +#define MK712_INT_ON_CONVERSION_COMPLETE 0x02 +#define MK712_INT_ON_CHANGE_IN_TOUCH_STATUS 0x04 +#define MK712_ENABLE_PERIODIC_CONVERSIONS 0x10 +#define MK712_READ_ONE_POINT 0x20 +#define MK712_POWERUP 0x40 + +static struct input_dev *mk712_dev; +static DEFINE_SPINLOCK(mk712_lock); + +static irqreturn_t mk712_interrupt(int irq, void *dev_id) +{ + unsigned char status; + static int debounce = 1; + static unsigned short last_x; + static unsigned short last_y; + + spin_lock(&mk712_lock); + + status = inb(mk712_io + MK712_STATUS); + + if (~status & MK712_CONVERSION_COMPLETE) { + debounce = 1; + goto end; + } + + if (~status & MK712_STATUS_TOUCH) { + debounce = 1; + input_report_key(mk712_dev, BTN_TOUCH, 0); + goto end; + } + + if (debounce) { + debounce = 0; + goto end; + } + + input_report_key(mk712_dev, BTN_TOUCH, 1); + input_report_abs(mk712_dev, ABS_X, last_x); + input_report_abs(mk712_dev, ABS_Y, last_y); + + end: + last_x = inw(mk712_io + MK712_X) & 0x0fff; + last_y = inw(mk712_io + MK712_Y) & 0x0fff; + input_sync(mk712_dev); + spin_unlock(&mk712_lock); + return IRQ_HANDLED; +} + +static int mk712_open(struct input_dev *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&mk712_lock, flags); + + outb(0, mk712_io + MK712_CONTROL); /* Reset */ + + outb(MK712_ENABLE_INT | MK712_INT_ON_CONVERSION_COMPLETE | + MK712_INT_ON_CHANGE_IN_TOUCH_STATUS | + MK712_ENABLE_PERIODIC_CONVERSIONS | + MK712_POWERUP, mk712_io + MK712_CONTROL); + + outb(10, mk712_io + MK712_RATE); /* 187 points per second */ + + spin_unlock_irqrestore(&mk712_lock, flags); + + return 0; +} + +static void mk712_close(struct input_dev *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&mk712_lock, flags); + + outb(0, mk712_io + MK712_CONTROL); + + spin_unlock_irqrestore(&mk712_lock, flags); +} + +static int __init mk712_init(void) +{ + int err; + + if (!request_region(mk712_io, 8, "mk712")) { + printk(KERN_WARNING "mk712: unable to get IO region\n"); + return -ENODEV; + } + + outb(0, mk712_io + MK712_CONTROL); + + if ((inw(mk712_io + MK712_X) & 0xf000) || /* Sanity check */ + (inw(mk712_io + MK712_Y) & 0xf000) || + (inw(mk712_io + MK712_STATUS) & 0xf333)) { + printk(KERN_WARNING "mk712: device not present\n"); + err = -ENODEV; + goto fail1; + } + + mk712_dev = input_allocate_device(); + if (!mk712_dev) { + printk(KERN_ERR "mk712: not enough memory\n"); + err = -ENOMEM; + goto fail1; + } + + mk712_dev->name = "ICS MicroClock MK712 TouchScreen"; + mk712_dev->phys = "isa0260/input0"; + mk712_dev->id.bustype = BUS_ISA; + mk712_dev->id.vendor = 0x0005; + mk712_dev->id.product = 0x0001; + mk712_dev->id.version = 0x0100; + + mk712_dev->open = mk712_open; + mk712_dev->close = mk712_close; + + mk712_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + mk712_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(mk712_dev, ABS_X, 0, 0xfff, 88, 0); + input_set_abs_params(mk712_dev, ABS_Y, 0, 0xfff, 88, 0); + + if (request_irq(mk712_irq, mk712_interrupt, 0, "mk712", mk712_dev)) { + printk(KERN_WARNING "mk712: unable to get IRQ\n"); + err = -EBUSY; + goto fail1; + } + + err = input_register_device(mk712_dev); + if (err) + goto fail2; + + return 0; + + fail2: free_irq(mk712_irq, mk712_dev); + fail1: input_free_device(mk712_dev); + release_region(mk712_io, 8); + return err; +} + +static void __exit mk712_exit(void) +{ + input_unregister_device(mk712_dev); + free_irq(mk712_irq, mk712_dev); + release_region(mk712_io, 8); +} + +module_init(mk712_init); +module_exit(mk712_exit); diff --git a/drivers/input/touchscreen/mtouch.c b/drivers/input/touchscreen/mtouch.c new file mode 100644 index 0000000..9077228 --- /dev/null +++ b/drivers/input/touchscreen/mtouch.c @@ -0,0 +1,220 @@ +/* + * MicroTouch (3M) serial touchscreen driver + * + * Copyright (c) 2004 Vojtech Pavlik + */ + +/* + * 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. + */ + +/* + * 2005/02/19 Dan Streetman <ddstreet@ieee.org> + * Copied elo.c and edited for MicroTouch protocol + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "MicroTouch serial touchscreen driver" + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define MTOUCH_FORMAT_TABLET_STATUS_BIT 0x80 +#define MTOUCH_FORMAT_TABLET_TOUCH_BIT 0x40 +#define MTOUCH_FORMAT_TABLET_LENGTH 5 +#define MTOUCH_RESPONSE_BEGIN_BYTE 0x01 +#define MTOUCH_RESPONSE_END_BYTE 0x0d + +/* todo: check specs for max length of all responses */ +#define MTOUCH_MAX_LENGTH 16 + +#define MTOUCH_MIN_XC 0 +#define MTOUCH_MAX_XC 0x3fff +#define MTOUCH_MIN_YC 0 +#define MTOUCH_MAX_YC 0x3fff + +#define MTOUCH_GET_XC(data) (((data[2])<<7) | data[1]) +#define MTOUCH_GET_YC(data) (((data[4])<<7) | data[3]) +#define MTOUCH_GET_TOUCHED(data) (MTOUCH_FORMAT_TABLET_TOUCH_BIT & data[0]) + +/* + * Per-touchscreen data. + */ + +struct mtouch { + struct input_dev *dev; + struct serio *serio; + int idx; + unsigned char data[MTOUCH_MAX_LENGTH]; + char phys[32]; +}; + +static void mtouch_process_format_tablet(struct mtouch *mtouch) +{ + struct input_dev *dev = mtouch->dev; + + if (MTOUCH_FORMAT_TABLET_LENGTH == ++mtouch->idx) { + input_report_abs(dev, ABS_X, MTOUCH_GET_XC(mtouch->data)); + input_report_abs(dev, ABS_Y, MTOUCH_MAX_YC - MTOUCH_GET_YC(mtouch->data)); + input_report_key(dev, BTN_TOUCH, MTOUCH_GET_TOUCHED(mtouch->data)); + input_sync(dev); + + mtouch->idx = 0; + } +} + +static void mtouch_process_response(struct mtouch *mtouch) +{ + if (MTOUCH_RESPONSE_END_BYTE == mtouch->data[mtouch->idx++]) { + /* FIXME - process response */ + mtouch->idx = 0; + } else if (MTOUCH_MAX_LENGTH == mtouch->idx) { + printk(KERN_ERR "mtouch.c: too many response bytes\n"); + mtouch->idx = 0; + } +} + +static irqreturn_t mtouch_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct mtouch* mtouch = serio_get_drvdata(serio); + + mtouch->data[mtouch->idx] = data; + + if (MTOUCH_FORMAT_TABLET_STATUS_BIT & mtouch->data[0]) + mtouch_process_format_tablet(mtouch); + else if (MTOUCH_RESPONSE_BEGIN_BYTE == mtouch->data[0]) + mtouch_process_response(mtouch); + else + printk(KERN_DEBUG "mtouch.c: unknown/unsynchronized data from device, byte %x\n",mtouch->data[0]); + + return IRQ_HANDLED; +} + +/* + * mtouch_disconnect() is the opposite of mtouch_connect() + */ + +static void mtouch_disconnect(struct serio *serio) +{ + struct mtouch* mtouch = serio_get_drvdata(serio); + + input_get_device(mtouch->dev); + input_unregister_device(mtouch->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(mtouch->dev); + kfree(mtouch); +} + +/* + * mtouch_connect() is the routine that is called when someone adds a + * new serio device that supports MicroTouch (Format Tablet) protocol and registers it as + * an input device. + */ + +static int mtouch_connect(struct serio *serio, struct serio_driver *drv) +{ + struct mtouch *mtouch; + struct input_dev *input_dev; + int err; + + mtouch = kzalloc(sizeof(struct mtouch), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!mtouch || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + mtouch->serio = serio; + mtouch->dev = input_dev; + snprintf(mtouch->phys, sizeof(mtouch->phys), "%s/input0", serio->phys); + + input_dev->name = "MicroTouch Serial TouchScreen"; + input_dev->phys = mtouch->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_MICROTOUCH; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(mtouch->dev, ABS_X, MTOUCH_MIN_XC, MTOUCH_MAX_XC, 0, 0); + input_set_abs_params(mtouch->dev, ABS_Y, MTOUCH_MIN_YC, MTOUCH_MAX_YC, 0, 0); + + serio_set_drvdata(serio, mtouch); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(mtouch->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(mtouch); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id mtouch_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_MICROTOUCH, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, mtouch_serio_ids); + +static struct serio_driver mtouch_drv = { + .driver = { + .name = "mtouch", + }, + .description = DRIVER_DESC, + .id_table = mtouch_serio_ids, + .interrupt = mtouch_interrupt, + .connect = mtouch_connect, + .disconnect = mtouch_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init mtouch_init(void) +{ + return serio_register_driver(&mtouch_drv); +} + +static void __exit mtouch_exit(void) +{ + serio_unregister_driver(&mtouch_drv); +} + +module_init(mtouch_init); +module_exit(mtouch_exit); diff --git a/drivers/input/touchscreen/penmount.c b/drivers/input/touchscreen/penmount.c new file mode 100644 index 0000000..c7f9ceb --- /dev/null +++ b/drivers/input/touchscreen/penmount.c @@ -0,0 +1,182 @@ +/* + * Penmount serial touchscreen driver + * + * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com> + * + * Based on ELO driver (drivers/input/touchscreen/elo.c) + * Copyright (c) 2004 Vojtech Pavlik + */ + +/* + * 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. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Penmount serial touchscreen driver" + +MODULE_AUTHOR("Rick Koch <n1gp@hotmail.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define PM_MAX_LENGTH 5 + +/* + * Per-touchscreen data. + */ + +struct pm { + struct input_dev *dev; + struct serio *serio; + int idx; + unsigned char data[PM_MAX_LENGTH]; + char phys[32]; +}; + +static irqreturn_t pm_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct pm *pm = serio_get_drvdata(serio); + struct input_dev *dev = pm->dev; + + pm->data[pm->idx] = data; + + if (pm->data[0] & 0x80) { + if (PM_MAX_LENGTH == ++pm->idx) { + input_report_abs(dev, ABS_X, pm->data[2] * 128 + pm->data[1]); + input_report_abs(dev, ABS_Y, pm->data[4] * 128 + pm->data[3]); + input_report_key(dev, BTN_TOUCH, !!(pm->data[0] & 0x40)); + input_sync(dev); + pm->idx = 0; + } + } + + return IRQ_HANDLED; +} + +/* + * pm_disconnect() is the opposite of pm_connect() + */ + +static void pm_disconnect(struct serio *serio) +{ + struct pm *pm = serio_get_drvdata(serio); + + input_get_device(pm->dev); + input_unregister_device(pm->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(pm->dev); + kfree(pm); +} + +/* + * pm_connect() is the routine that is called when someone adds a + * new serio device that supports Gunze protocol and registers it as + * an input device. + */ + +static int pm_connect(struct serio *serio, struct serio_driver *drv) +{ + struct pm *pm; + struct input_dev *input_dev; + int err; + + pm = kzalloc(sizeof(struct pm), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!pm || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + pm->serio = serio; + pm->dev = input_dev; + snprintf(pm->phys, sizeof(pm->phys), "%s/input0", serio->phys); + + input_dev->name = "Penmount Serial TouchScreen"; + input_dev->phys = pm->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_PENMOUNT; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(pm->dev, ABS_X, 0, 0x3ff, 0, 0); + input_set_abs_params(pm->dev, ABS_Y, 0, 0x3ff, 0, 0); + + serio_set_drvdata(serio, pm); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(pm->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(pm); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id pm_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_PENMOUNT, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, pm_serio_ids); + +static struct serio_driver pm_drv = { + .driver = { + .name = "penmountlpc", + }, + .description = DRIVER_DESC, + .id_table = pm_serio_ids, + .interrupt = pm_interrupt, + .connect = pm_connect, + .disconnect = pm_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init pm_init(void) +{ + return serio_register_driver(&pm_drv); +} + +static void __exit pm_exit(void) +{ + serio_unregister_driver(&pm_drv); +} + +module_init(pm_init); +module_exit(pm_exit); diff --git a/drivers/input/touchscreen/touchit213.c b/drivers/input/touchscreen/touchit213.c new file mode 100644 index 0000000..d1297ba --- /dev/null +++ b/drivers/input/touchscreen/touchit213.c @@ -0,0 +1,234 @@ +/* + * Sahara TouchIT-213 serial touchscreen driver + * + * Copyright (c) 2007-2008 Claudio Nieder <private@claudio.ch> + * + * Based on Touchright driver (drivers/input/touchscreen/touchright.c) + * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com> + * Copyright (c) 2004 Vojtech Pavlik + * and Dan Streetman <ddstreet@ieee.org> + */ + +/* + * 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. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Sahara TouchIT-213 serial touchscreen driver" + +MODULE_AUTHOR("Claudio Nieder <private@claudio.ch>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +/* + * Data is received through COM1 at 9600bit/s,8bit,no parity in packets + * of 5 byte each. + * + * +--------+ +--------+ +--------+ +--------+ +--------+ + * |1000000p| |0xxxxxxx| |0xxxxxxx| |0yyyyyyy| |0yyyyyyy| + * +--------+ +--------+ +--------+ +--------+ +--------+ + * MSB LSB MSB LSB + * + * The value of p is 1 as long as the screen is touched and 0 when + * reporting the location where touching stopped, e.g. where the pen was + * lifted from the screen. + * + * When holding the screen in landscape mode as the BIOS text output is + * presented, x is the horizontal axis with values growing from left to + * right and y is the vertical axis with values growing from top to + * bottom. + * + * When holding the screen in portrait mode with the Sahara logo in its + * correct position, x ist the vertical axis with values growing from + * top to bottom and y is the horizontal axis with values growing from + * right to left. + */ + +#define T213_FORMAT_TOUCH_BIT 0x01 +#define T213_FORMAT_STATUS_BYTE 0x80 +#define T213_FORMAT_STATUS_MASK ~T213_FORMAT_TOUCH_BIT + +/* + * On my Sahara Touch-IT 213 I have observed x values from 0 to 0x7f0 + * and y values from 0x1d to 0x7e9, so the actual measurement is + * probably done with an 11 bit precision. + */ +#define T213_MIN_XC 0 +#define T213_MAX_XC 0x07ff +#define T213_MIN_YC 0 +#define T213_MAX_YC 0x07ff + +/* + * Per-touchscreen data. + */ + +struct touchit213 { + struct input_dev *dev; + struct serio *serio; + int idx; + unsigned char csum; + unsigned char data[5]; + char phys[32]; +}; + +static irqreturn_t touchit213_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct touchit213 *touchit213 = serio_get_drvdata(serio); + struct input_dev *dev = touchit213->dev; + + touchit213->data[touchit213->idx] = data; + + switch (touchit213->idx++) { + case 0: + if ((touchit213->data[0] & T213_FORMAT_STATUS_MASK) != + T213_FORMAT_STATUS_BYTE) { + pr_debug("unsynchronized data: 0x%02x\n", data); + touchit213->idx = 0; + } + break; + + case 4: + touchit213->idx = 0; + input_report_abs(dev, ABS_X, + (touchit213->data[1] << 7) | touchit213->data[2]); + input_report_abs(dev, ABS_Y, + (touchit213->data[3] << 7) | touchit213->data[4]); + input_report_key(dev, BTN_TOUCH, + touchit213->data[0] & T213_FORMAT_TOUCH_BIT); + input_sync(dev); + break; + } + + return IRQ_HANDLED; +} + +/* + * touchit213_disconnect() is the opposite of touchit213_connect() + */ + +static void touchit213_disconnect(struct serio *serio) +{ + struct touchit213 *touchit213 = serio_get_drvdata(serio); + + input_get_device(touchit213->dev); + input_unregister_device(touchit213->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(touchit213->dev); + kfree(touchit213); +} + +/* + * touchit213_connect() is the routine that is called when someone adds a + * new serio device that supports the Touchright protocol and registers it as + * an input device. + */ + +static int touchit213_connect(struct serio *serio, struct serio_driver *drv) +{ + struct touchit213 *touchit213; + struct input_dev *input_dev; + int err; + + touchit213 = kzalloc(sizeof(struct touchit213), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!touchit213 || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + touchit213->serio = serio; + touchit213->dev = input_dev; + snprintf(touchit213->phys, sizeof(touchit213->phys), + "%s/input0", serio->phys); + + input_dev->name = "Sahara Touch-iT213 Serial TouchScreen"; + input_dev->phys = touchit213->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_TOUCHIT213; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(touchit213->dev, ABS_X, + T213_MIN_XC, T213_MAX_XC, 0, 0); + input_set_abs_params(touchit213->dev, ABS_Y, + T213_MIN_YC, T213_MAX_YC, 0, 0); + + serio_set_drvdata(serio, touchit213); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(touchit213->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(touchit213); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id touchit213_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_TOUCHIT213, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, touchit213_serio_ids); + +static struct serio_driver touchit213_drv = { + .driver = { + .name = "touchit213", + }, + .description = DRIVER_DESC, + .id_table = touchit213_serio_ids, + .interrupt = touchit213_interrupt, + .connect = touchit213_connect, + .disconnect = touchit213_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init touchit213_init(void) +{ + return serio_register_driver(&touchit213_drv); +} + +static void __exit touchit213_exit(void) +{ + serio_unregister_driver(&touchit213_drv); +} + +module_init(touchit213_init); +module_exit(touchit213_exit); diff --git a/drivers/input/touchscreen/touchright.c b/drivers/input/touchscreen/touchright.c new file mode 100644 index 0000000..3a5c142 --- /dev/null +++ b/drivers/input/touchscreen/touchright.c @@ -0,0 +1,194 @@ +/* + * Touchright serial touchscreen driver + * + * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com> + * + * Based on MicroTouch driver (drivers/input/touchscreen/mtouch.c) + * Copyright (c) 2004 Vojtech Pavlik + * and Dan Streetman <ddstreet@ieee.org> + */ + +/* + * 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. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Touchright serial touchscreen driver" + +MODULE_AUTHOR("Rick Koch <n1gp@hotmail.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define TR_FORMAT_TOUCH_BIT 0x01 +#define TR_FORMAT_STATUS_BYTE 0x40 +#define TR_FORMAT_STATUS_MASK ~TR_FORMAT_TOUCH_BIT + +#define TR_LENGTH 5 + +#define TR_MIN_XC 0 +#define TR_MAX_XC 0x1ff +#define TR_MIN_YC 0 +#define TR_MAX_YC 0x1ff + +/* + * Per-touchscreen data. + */ + +struct tr { + struct input_dev *dev; + struct serio *serio; + int idx; + unsigned char data[TR_LENGTH]; + char phys[32]; +}; + +static irqreturn_t tr_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct tr *tr = serio_get_drvdata(serio); + struct input_dev *dev = tr->dev; + + tr->data[tr->idx] = data; + + if ((tr->data[0] & TR_FORMAT_STATUS_MASK) == TR_FORMAT_STATUS_BYTE) { + if (++tr->idx == TR_LENGTH) { + input_report_abs(dev, ABS_X, + (tr->data[1] << 5) | (tr->data[2] >> 1)); + input_report_abs(dev, ABS_Y, + (tr->data[3] << 5) | (tr->data[4] >> 1)); + input_report_key(dev, BTN_TOUCH, + tr->data[0] & TR_FORMAT_TOUCH_BIT); + input_sync(dev); + tr->idx = 0; + } + } + + return IRQ_HANDLED; +} + +/* + * tr_disconnect() is the opposite of tr_connect() + */ + +static void tr_disconnect(struct serio *serio) +{ + struct tr *tr = serio_get_drvdata(serio); + + input_get_device(tr->dev); + input_unregister_device(tr->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(tr->dev); + kfree(tr); +} + +/* + * tr_connect() is the routine that is called when someone adds a + * new serio device that supports the Touchright protocol and registers it as + * an input device. + */ + +static int tr_connect(struct serio *serio, struct serio_driver *drv) +{ + struct tr *tr; + struct input_dev *input_dev; + int err; + + tr = kzalloc(sizeof(struct tr), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!tr || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + tr->serio = serio; + tr->dev = input_dev; + snprintf(tr->phys, sizeof(tr->phys), "%s/input0", serio->phys); + + input_dev->name = "Touchright Serial TouchScreen"; + input_dev->phys = tr->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_TOUCHRIGHT; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(tr->dev, ABS_X, TR_MIN_XC, TR_MAX_XC, 0, 0); + input_set_abs_params(tr->dev, ABS_Y, TR_MIN_YC, TR_MAX_YC, 0, 0); + + serio_set_drvdata(serio, tr); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(tr->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(tr); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id tr_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_TOUCHRIGHT, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, tr_serio_ids); + +static struct serio_driver tr_drv = { + .driver = { + .name = "touchright", + }, + .description = DRIVER_DESC, + .id_table = tr_serio_ids, + .interrupt = tr_interrupt, + .connect = tr_connect, + .disconnect = tr_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init tr_init(void) +{ + return serio_register_driver(&tr_drv); +} + +static void __exit tr_exit(void) +{ + serio_unregister_driver(&tr_drv); +} + +module_init(tr_init); +module_exit(tr_exit); diff --git a/drivers/input/touchscreen/touchwin.c b/drivers/input/touchscreen/touchwin.c new file mode 100644 index 0000000..763a656 --- /dev/null +++ b/drivers/input/touchscreen/touchwin.c @@ -0,0 +1,201 @@ +/* + * Touchwindow serial touchscreen driver + * + * Copyright (c) 2006 Rick Koch <n1gp@hotmail.com> + * + * Based on MicroTouch driver (drivers/input/touchscreen/mtouch.c) + * Copyright (c) 2004 Vojtech Pavlik + * and Dan Streetman <ddstreet@ieee.org> + */ + +/* + * 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. + */ + +/* + * 2005/02/19 Rick Koch: + * The Touchwindow I used is made by Edmark Corp. and + * constantly outputs a stream of 0's unless it is touched. + * It then outputs 3 bytes: X, Y, and a copy of Y. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> + +#define DRIVER_DESC "Touchwindow serial touchscreen driver" + +MODULE_AUTHOR("Rick Koch <n1gp@hotmail.com>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +/* + * Definitions & global arrays. + */ + +#define TW_LENGTH 3 + +#define TW_MIN_XC 0 +#define TW_MAX_XC 0xff +#define TW_MIN_YC 0 +#define TW_MAX_YC 0xff + +/* + * Per-touchscreen data. + */ + +struct tw { + struct input_dev *dev; + struct serio *serio; + int idx; + int touched; + unsigned char data[TW_LENGTH]; + char phys[32]; +}; + +static irqreturn_t tw_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct tw *tw = serio_get_drvdata(serio); + struct input_dev *dev = tw->dev; + + if (data) { /* touch */ + tw->touched = 1; + tw->data[tw->idx++] = data; + /* verify length and that the two Y's are the same */ + if (tw->idx == TW_LENGTH && tw->data[1] == tw->data[2]) { + input_report_abs(dev, ABS_X, tw->data[0]); + input_report_abs(dev, ABS_Y, tw->data[1]); + input_report_key(dev, BTN_TOUCH, 1); + input_sync(dev); + tw->idx = 0; + } + } else if (tw->touched) { /* untouch */ + input_report_key(dev, BTN_TOUCH, 0); + input_sync(dev); + tw->idx = 0; + tw->touched = 0; + } + + return IRQ_HANDLED; +} + +/* + * tw_disconnect() is the opposite of tw_connect() + */ + +static void tw_disconnect(struct serio *serio) +{ + struct tw *tw = serio_get_drvdata(serio); + + input_get_device(tw->dev); + input_unregister_device(tw->dev); + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_put_device(tw->dev); + kfree(tw); +} + +/* + * tw_connect() is the routine that is called when someone adds a + * new serio device that supports the Touchwin protocol and registers it as + * an input device. + */ + +static int tw_connect(struct serio *serio, struct serio_driver *drv) +{ + struct tw *tw; + struct input_dev *input_dev; + int err; + + tw = kzalloc(sizeof(struct tw), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!tw || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + tw->serio = serio; + tw->dev = input_dev; + snprintf(tw->phys, sizeof(tw->phys), "%s/input0", serio->phys); + + input_dev->name = "Touchwindow Serial TouchScreen"; + input_dev->phys = tw->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_TOUCHWIN; + input_dev->id.product = 0; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &serio->dev; + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(tw->dev, ABS_X, TW_MIN_XC, TW_MAX_XC, 0, 0); + input_set_abs_params(tw->dev, ABS_Y, TW_MIN_YC, TW_MAX_YC, 0, 0); + + serio_set_drvdata(serio, tw); + + err = serio_open(serio, drv); + if (err) + goto fail2; + + err = input_register_device(tw->dev); + if (err) + goto fail3; + + return 0; + + fail3: serio_close(serio); + fail2: serio_set_drvdata(serio, NULL); + fail1: input_free_device(input_dev); + kfree(tw); + return err; +} + +/* + * The serio driver structure. + */ + +static struct serio_device_id tw_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_TOUCHWIN, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, tw_serio_ids); + +static struct serio_driver tw_drv = { + .driver = { + .name = "touchwin", + }, + .description = DRIVER_DESC, + .id_table = tw_serio_ids, + .interrupt = tw_interrupt, + .connect = tw_connect, + .disconnect = tw_disconnect, +}; + +/* + * The functions for inserting/removing us as a module. + */ + +static int __init tw_init(void) +{ + return serio_register_driver(&tw_drv); +} + +static void __exit tw_exit(void) +{ + serio_unregister_driver(&tw_drv); +} + +module_init(tw_init); +module_exit(tw_exit); diff --git a/drivers/input/touchscreen/ucb1400_ts.c b/drivers/input/touchscreen/ucb1400_ts.c new file mode 100644 index 0000000..5498662 --- /dev/null +++ b/drivers/input/touchscreen/ucb1400_ts.c @@ -0,0 +1,471 @@ +/* + * Philips UCB1400 touchscreen driver + * + * Author: Nicolas Pitre + * Created: September 25, 2006 + * Copyright: MontaVista Software, Inc. + * + * Spliting done by: Marek Vasut <marek.vasut@gmail.com> + * If something doesnt work and it worked before spliting, e-mail me, + * dont bother Nicolas please ;-) + * + * 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. + * + * This code is heavily based on ucb1x00-*.c copyrighted by Russell King + * covering the UCB1100, UCB1200 and UCB1300.. Support for the UCB1400 has + * been made separate from ucb1x00-core/ucb1x00-ts on Russell's request. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/suspend.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/freezer.h> +#include <linux/ucb1400.h> + +static int adcsync; +static int ts_delay = 55; /* us */ +static int ts_delay_pressure; /* us */ + +/* Switch to interrupt mode. */ +static inline void ucb1400_ts_mode_int(struct snd_ac97 *ac97) +{ + ucb1400_reg_write(ac97, UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_INT); +} + +/* + * Switch to pressure mode, and read pressure. We don't need to wait + * here, since both plates are being driven. + */ +static inline unsigned int ucb1400_ts_read_pressure(struct ucb1400_ts *ucb) +{ + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMX_POW | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_GND | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + udelay(ts_delay_pressure); + return ucb1400_adc_read(ucb->ac97, UCB_ADC_INP_TSPY, adcsync); +} + +/* + * Switch to X position mode and measure Y plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ucb1400_ts_read_xpos(struct ucb1400_ts *ucb) +{ + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(ts_delay); + + return ucb1400_adc_read(ucb->ac97, UCB_ADC_INP_TSPY, adcsync); +} + +/* + * Switch to Y position mode and measure X plate. We switch the plate + * configuration in pressure mode, then switch to position mode. This + * gives a faster response time. Even so, we need to wait about 55us + * for things to stabilise. + */ +static inline unsigned int ucb1400_ts_read_ypos(struct ucb1400_ts *ucb) +{ + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA); + + udelay(ts_delay); + + return ucb1400_adc_read(ucb->ac97, UCB_ADC_INP_TSPX, adcsync); +} + +/* + * Switch to X plate resistance mode. Set MX to ground, PX to + * supply. Measure current. + */ +static inline unsigned int ucb1400_ts_read_xres(struct ucb1400_ts *ucb) +{ + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + return ucb1400_adc_read(ucb->ac97, 0, adcsync); +} + +/* + * Switch to Y plate resistance mode. Set MY to ground, PY to + * supply. Measure current. + */ +static inline unsigned int ucb1400_ts_read_yres(struct ucb1400_ts *ucb) +{ + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, + UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW | + UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA); + return ucb1400_adc_read(ucb->ac97, 0, adcsync); +} + +static inline int ucb1400_ts_pen_down(struct snd_ac97 *ac97) +{ + unsigned short val = ucb1400_reg_read(ac97, UCB_TS_CR); + return val & (UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW); +} + +static inline void ucb1400_ts_irq_enable(struct snd_ac97 *ac97) +{ + ucb1400_reg_write(ac97, UCB_IE_CLEAR, UCB_IE_TSPX); + ucb1400_reg_write(ac97, UCB_IE_CLEAR, 0); + ucb1400_reg_write(ac97, UCB_IE_FAL, UCB_IE_TSPX); +} + +static inline void ucb1400_ts_irq_disable(struct snd_ac97 *ac97) +{ + ucb1400_reg_write(ac97, UCB_IE_FAL, 0); +} + +static void ucb1400_ts_evt_add(struct input_dev *idev, u16 pressure, u16 x, u16 y) +{ + input_report_abs(idev, ABS_X, x); + input_report_abs(idev, ABS_Y, y); + input_report_abs(idev, ABS_PRESSURE, pressure); + input_sync(idev); +} + +static void ucb1400_ts_event_release(struct input_dev *idev) +{ + input_report_abs(idev, ABS_PRESSURE, 0); + input_sync(idev); +} + +static void ucb1400_handle_pending_irq(struct ucb1400_ts *ucb) +{ + unsigned int isr; + + isr = ucb1400_reg_read(ucb->ac97, UCB_IE_STATUS); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, isr); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0); + + if (isr & UCB_IE_TSPX) { + ucb1400_ts_irq_disable(ucb->ac97); + enable_irq(ucb->irq); + } else + printk(KERN_ERR "ucb1400: unexpected IE_STATUS = %#x\n", isr); +} + +static int ucb1400_ts_thread(void *_ucb) +{ + struct ucb1400_ts *ucb = _ucb; + struct task_struct *tsk = current; + int valid = 0; + struct sched_param param = { .sched_priority = 1 }; + + sched_setscheduler(tsk, SCHED_FIFO, ¶m); + + set_freezable(); + while (!kthread_should_stop()) { + unsigned int x, y, p; + long timeout; + + ucb->ts_restart = 0; + + if (ucb->irq_pending) { + ucb->irq_pending = 0; + ucb1400_handle_pending_irq(ucb); + } + + ucb1400_adc_enable(ucb->ac97); + x = ucb1400_ts_read_xpos(ucb); + y = ucb1400_ts_read_ypos(ucb); + p = ucb1400_ts_read_pressure(ucb); + ucb1400_adc_disable(ucb->ac97); + + /* Switch back to interrupt mode. */ + ucb1400_ts_mode_int(ucb->ac97); + + msleep(10); + + if (ucb1400_ts_pen_down(ucb->ac97)) { + ucb1400_ts_irq_enable(ucb->ac97); + + /* + * If we spat out a valid sample set last time, + * spit out a "pen off" sample here. + */ + if (valid) { + ucb1400_ts_event_release(ucb->ts_idev); + valid = 0; + } + + timeout = MAX_SCHEDULE_TIMEOUT; + } else { + valid = 1; + ucb1400_ts_evt_add(ucb->ts_idev, p, x, y); + timeout = msecs_to_jiffies(10); + } + + wait_event_freezable_timeout(ucb->ts_wait, + ucb->irq_pending || ucb->ts_restart || + kthread_should_stop(), timeout); + } + + /* Send the "pen off" if we are stopping with the pen still active */ + if (valid) + ucb1400_ts_event_release(ucb->ts_idev); + + ucb->ts_task = NULL; + return 0; +} + +/* + * A restriction with interrupts exists when using the ucb1400, as + * the codec read/write routines may sleep while waiting for codec + * access completion and uses semaphores for access control to the + * AC97 bus. A complete codec read cycle could take anywhere from + * 60 to 100uSec so we *definitely* don't want to spin inside the + * interrupt handler waiting for codec access. So, we handle the + * interrupt by scheduling a RT kernel thread to run in process + * context instead of interrupt context. + */ +static irqreturn_t ucb1400_hard_irq(int irqnr, void *devid) +{ + struct ucb1400_ts *ucb = devid; + + if (irqnr == ucb->irq) { + disable_irq(ucb->irq); + ucb->irq_pending = 1; + wake_up(&ucb->ts_wait); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static int ucb1400_ts_open(struct input_dev *idev) +{ + struct ucb1400_ts *ucb = input_get_drvdata(idev); + int ret = 0; + + BUG_ON(ucb->ts_task); + + ucb->ts_task = kthread_run(ucb1400_ts_thread, ucb, "UCB1400_ts"); + if (IS_ERR(ucb->ts_task)) { + ret = PTR_ERR(ucb->ts_task); + ucb->ts_task = NULL; + } + + return ret; +} + +static void ucb1400_ts_close(struct input_dev *idev) +{ + struct ucb1400_ts *ucb = input_get_drvdata(idev); + + if (ucb->ts_task) + kthread_stop(ucb->ts_task); + + ucb1400_ts_irq_disable(ucb->ac97); + ucb1400_reg_write(ucb->ac97, UCB_TS_CR, 0); +} + +#ifndef NO_IRQ +#define NO_IRQ 0 +#endif + +/* + * Try to probe our interrupt, rather than relying on lots of + * hard-coded machine dependencies. + */ +static int ucb1400_ts_detect_irq(struct ucb1400_ts *ucb) +{ + unsigned long mask, timeout; + + mask = probe_irq_on(); + + /* Enable the ADC interrupt. */ + ucb1400_reg_write(ucb->ac97, UCB_IE_RIS, UCB_IE_ADC); + ucb1400_reg_write(ucb->ac97, UCB_IE_FAL, UCB_IE_ADC); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0xffff); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0); + + /* Cause an ADC interrupt. */ + ucb1400_reg_write(ucb->ac97, UCB_ADC_CR, UCB_ADC_ENA); + ucb1400_reg_write(ucb->ac97, UCB_ADC_CR, UCB_ADC_ENA | UCB_ADC_START); + + /* Wait for the conversion to complete. */ + timeout = jiffies + HZ/2; + while (!(ucb1400_reg_read(ucb->ac97, UCB_ADC_DATA) & + UCB_ADC_DAT_VALID)) { + cpu_relax(); + if (time_after(jiffies, timeout)) { + printk(KERN_ERR "ucb1400: timed out in IRQ probe\n"); + probe_irq_off(mask); + return -ENODEV; + } + } + ucb1400_reg_write(ucb->ac97, UCB_ADC_CR, 0); + + /* Disable and clear interrupt. */ + ucb1400_reg_write(ucb->ac97, UCB_IE_RIS, 0); + ucb1400_reg_write(ucb->ac97, UCB_IE_FAL, 0); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0xffff); + ucb1400_reg_write(ucb->ac97, UCB_IE_CLEAR, 0); + + /* Read triggered interrupt. */ + ucb->irq = probe_irq_off(mask); + if (ucb->irq < 0 || ucb->irq == NO_IRQ) + return -ENODEV; + + return 0; +} + +static int ucb1400_ts_probe(struct platform_device *dev) +{ + int error, x_res, y_res; + struct ucb1400_ts *ucb = dev->dev.platform_data; + + ucb->ts_idev = input_allocate_device(); + if (!ucb->ts_idev) { + error = -ENOMEM; + goto err; + } + + error = ucb1400_ts_detect_irq(ucb); + if (error) { + printk(KERN_ERR "UCB1400: IRQ probe failed\n"); + goto err_free_devs; + } + + init_waitqueue_head(&ucb->ts_wait); + + error = request_irq(ucb->irq, ucb1400_hard_irq, IRQF_TRIGGER_RISING, + "UCB1400", ucb); + if (error) { + printk(KERN_ERR "ucb1400: unable to grab irq%d: %d\n", + ucb->irq, error); + goto err_free_devs; + } + printk(KERN_DEBUG "UCB1400: found IRQ %d\n", ucb->irq); + + input_set_drvdata(ucb->ts_idev, ucb); + + ucb->ts_idev->dev.parent = &dev->dev; + ucb->ts_idev->name = "UCB1400 touchscreen interface"; + ucb->ts_idev->id.vendor = ucb1400_reg_read(ucb->ac97, + AC97_VENDOR_ID1); + ucb->ts_idev->id.product = ucb->id; + ucb->ts_idev->open = ucb1400_ts_open; + ucb->ts_idev->close = ucb1400_ts_close; + ucb->ts_idev->evbit[0] = BIT_MASK(EV_ABS); + + ucb1400_adc_enable(ucb->ac97); + x_res = ucb1400_ts_read_xres(ucb); + y_res = ucb1400_ts_read_yres(ucb); + ucb1400_adc_disable(ucb->ac97); + printk(KERN_DEBUG "UCB1400: x/y = %d/%d\n", x_res, y_res); + + input_set_abs_params(ucb->ts_idev, ABS_X, 0, x_res, 0, 0); + input_set_abs_params(ucb->ts_idev, ABS_Y, 0, y_res, 0, 0); + input_set_abs_params(ucb->ts_idev, ABS_PRESSURE, 0, 0, 0, 0); + + error = input_register_device(ucb->ts_idev); + if (error) + goto err_free_irq; + + return 0; + +err_free_irq: + free_irq(ucb->irq, ucb); +err_free_devs: + input_free_device(ucb->ts_idev); +err: + return error; + +} + +static int ucb1400_ts_remove(struct platform_device *dev) +{ + struct ucb1400_ts *ucb = dev->dev.platform_data; + + free_irq(ucb->irq, ucb); + input_unregister_device(ucb->ts_idev); + return 0; +} + +#ifdef CONFIG_PM +static int ucb1400_ts_resume(struct platform_device *dev) +{ + struct ucb1400_ts *ucb = platform_get_drvdata(dev); + + if (ucb->ts_task) { + /* + * Restart the TS thread to ensure the + * TS interrupt mode is set up again + * after sleep. + */ + ucb->ts_restart = 1; + wake_up(&ucb->ts_wait); + } + return 0; +} +#else +#define ucb1400_ts_resume NULL +#endif + +static struct platform_driver ucb1400_ts_driver = { + .probe = ucb1400_ts_probe, + .remove = ucb1400_ts_remove, + .resume = ucb1400_ts_resume, + .driver = { + .name = "ucb1400_ts", + }, +}; + +static int __init ucb1400_ts_init(void) +{ + return platform_driver_register(&ucb1400_ts_driver); +} + +static void __exit ucb1400_ts_exit(void) +{ + platform_driver_unregister(&ucb1400_ts_driver); +} + +module_param(adcsync, bool, 0444); +MODULE_PARM_DESC(adcsync, "Synchronize touch readings with ADCSYNC pin."); + +module_param(ts_delay, int, 0444); +MODULE_PARM_DESC(ts_delay, "Delay between panel setup and" + " position read. Default = 55us."); + +module_param(ts_delay_pressure, int, 0444); +MODULE_PARM_DESC(ts_delay_pressure, + "delay between panel setup and pressure read." + " Default = 0us."); + +module_init(ucb1400_ts_init); +module_exit(ucb1400_ts_exit); + +MODULE_DESCRIPTION("Philips UCB1400 touchscreen driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/usbtouchscreen.c b/drivers/input/touchscreen/usbtouchscreen.c new file mode 100644 index 0000000..fdd645c --- /dev/null +++ b/drivers/input/touchscreen/usbtouchscreen.c @@ -0,0 +1,1029 @@ +/****************************************************************************** + * usbtouchscreen.c + * Driver for USB Touchscreens, supporting those devices: + * - eGalax Touchkit + * includes eTurboTouch CT-410/510/700 + * - 3M/Microtouch EX II series + * - ITM + * - PanJit TouchSet + * - eTurboTouch + * - Gunze AHL61 + * - DMC TSC-10/25 + * - IRTOUCHSYSTEMS/UNITOP + * - IdealTEK URTC1000 + * - General Touch + * - GoTop Super_Q2/GogoPen/PenPower tablets + * + * Copyright (C) 2004-2007 by Daniel Ritz <daniel.ritz@gmx.ch> + * Copyright (C) by Todd E. Johnson (mtouchusb.c) + * + * 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * 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. + * + * Driver is based on touchkitusb.c + * - ITM parts are from itmtouch.c + * - 3M parts are from mtouchusb.c + * - PanJit parts are from an unmerged driver by Lanslott Gish + * - DMC TSC 10/25 are from Holger Schurig, with ideas from an unmerged + * driver from Marius Vollmer + * + *****************************************************************************/ + +//#define DEBUG + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/usb/input.h> +#include <linux/hid.h> + + +#define DRIVER_VERSION "v0.6" +#define DRIVER_AUTHOR "Daniel Ritz <daniel.ritz@gmx.ch>" +#define DRIVER_DESC "USB Touchscreen Driver" + +static int swap_xy; +module_param(swap_xy, bool, 0644); +MODULE_PARM_DESC(swap_xy, "If set X and Y axes are swapped."); + +/* device specifc data/functions */ +struct usbtouch_usb; +struct usbtouch_device_info { + int min_xc, max_xc; + int min_yc, max_yc; + int min_press, max_press; + int rept_size; + + void (*process_pkt) (struct usbtouch_usb *usbtouch, unsigned char *pkt, int len); + + /* + * used to get the packet len. possible return values: + * > 0: packet len + * = 0: skip one byte + * < 0: -return value more bytes needed + */ + int (*get_pkt_len) (unsigned char *pkt, int len); + + int (*read_data) (struct usbtouch_usb *usbtouch, unsigned char *pkt); + int (*init) (struct usbtouch_usb *usbtouch); +}; + +/* a usbtouch device */ +struct usbtouch_usb { + unsigned char *data; + dma_addr_t data_dma; + unsigned char *buffer; + int buf_len; + struct urb *irq; + struct usb_device *udev; + struct input_dev *input; + struct usbtouch_device_info *type; + char name[128]; + char phys[64]; + + int x, y; + int touch, press; +}; + + +/* device types */ +enum { + DEVTYPE_IGNORE = -1, + DEVTYPE_EGALAX, + DEVTYPE_PANJIT, + DEVTYPE_3M, + DEVTYPE_ITM, + DEVTYPE_ETURBO, + DEVTYPE_GUNZE, + DEVTYPE_DMC_TSC10, + DEVTYPE_IRTOUCH, + DEVTYPE_IDEALTEK, + DEVTYPE_GENERAL_TOUCH, + DEVTYPE_GOTOP, +}; + +#define USB_DEVICE_HID_CLASS(vend, prod) \ + .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS \ + | USB_DEVICE_ID_MATCH_DEVICE, \ + .idVendor = (vend), \ + .idProduct = (prod), \ + .bInterfaceClass = USB_INTERFACE_CLASS_HID, \ + .bInterfaceProtocol = USB_INTERFACE_PROTOCOL_MOUSE + +static struct usb_device_id usbtouch_devices[] = { +#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX + /* ignore the HID capable devices, handled by usbhid */ + {USB_DEVICE_HID_CLASS(0x0eef, 0x0001), .driver_info = DEVTYPE_IGNORE}, + {USB_DEVICE_HID_CLASS(0x0eef, 0x0002), .driver_info = DEVTYPE_IGNORE}, + + /* normal device IDs */ + {USB_DEVICE(0x3823, 0x0001), .driver_info = DEVTYPE_EGALAX}, + {USB_DEVICE(0x3823, 0x0002), .driver_info = DEVTYPE_EGALAX}, + {USB_DEVICE(0x0123, 0x0001), .driver_info = DEVTYPE_EGALAX}, + {USB_DEVICE(0x0eef, 0x0001), .driver_info = DEVTYPE_EGALAX}, + {USB_DEVICE(0x0eef, 0x0002), .driver_info = DEVTYPE_EGALAX}, + {USB_DEVICE(0x1234, 0x0001), .driver_info = DEVTYPE_EGALAX}, + {USB_DEVICE(0x1234, 0x0002), .driver_info = DEVTYPE_EGALAX}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_PANJIT + {USB_DEVICE(0x134c, 0x0001), .driver_info = DEVTYPE_PANJIT}, + {USB_DEVICE(0x134c, 0x0002), .driver_info = DEVTYPE_PANJIT}, + {USB_DEVICE(0x134c, 0x0003), .driver_info = DEVTYPE_PANJIT}, + {USB_DEVICE(0x134c, 0x0004), .driver_info = DEVTYPE_PANJIT}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_3M + {USB_DEVICE(0x0596, 0x0001), .driver_info = DEVTYPE_3M}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_ITM + {USB_DEVICE(0x0403, 0xf9e9), .driver_info = DEVTYPE_ITM}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_ETURBO + {USB_DEVICE(0x1234, 0x5678), .driver_info = DEVTYPE_ETURBO}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GUNZE + {USB_DEVICE(0x0637, 0x0001), .driver_info = DEVTYPE_GUNZE}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_DMC_TSC10 + {USB_DEVICE(0x0afa, 0x03e8), .driver_info = DEVTYPE_DMC_TSC10}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_IRTOUCH + {USB_DEVICE(0x595a, 0x0001), .driver_info = DEVTYPE_IRTOUCH}, + {USB_DEVICE(0x6615, 0x0001), .driver_info = DEVTYPE_IRTOUCH}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_IDEALTEK + {USB_DEVICE(0x1391, 0x1000), .driver_info = DEVTYPE_IDEALTEK}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH + {USB_DEVICE(0x0dfc, 0x0001), .driver_info = DEVTYPE_GENERAL_TOUCH}, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GOTOP + {USB_DEVICE(0x08f2, 0x007f), .driver_info = DEVTYPE_GOTOP}, + {USB_DEVICE(0x08f2, 0x00ce), .driver_info = DEVTYPE_GOTOP}, + {USB_DEVICE(0x08f2, 0x00f4), .driver_info = DEVTYPE_GOTOP}, +#endif + + {} +}; + + +/***************************************************************************** + * eGalax part + */ + +#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX + +#ifndef MULTI_PACKET +#define MULTI_PACKET +#endif + +#define EGALAX_PKT_TYPE_MASK 0xFE +#define EGALAX_PKT_TYPE_REPT 0x80 +#define EGALAX_PKT_TYPE_DIAG 0x0A + +static int egalax_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + if ((pkt[0] & EGALAX_PKT_TYPE_MASK) != EGALAX_PKT_TYPE_REPT) + return 0; + + dev->x = ((pkt[3] & 0x0F) << 7) | (pkt[4] & 0x7F); + dev->y = ((pkt[1] & 0x0F) << 7) | (pkt[2] & 0x7F); + dev->touch = pkt[0] & 0x01; + + return 1; +} + +static int egalax_get_pkt_len(unsigned char *buf, int len) +{ + switch (buf[0] & EGALAX_PKT_TYPE_MASK) { + case EGALAX_PKT_TYPE_REPT: + return 5; + + case EGALAX_PKT_TYPE_DIAG: + if (len < 2) + return -1; + + return buf[1] + 2; + } + + return 0; +} +#endif + + +/***************************************************************************** + * PanJit Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_PANJIT +static int panjit_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = ((pkt[2] & 0x0F) << 8) | pkt[1]; + dev->y = ((pkt[4] & 0x0F) << 8) | pkt[3]; + dev->touch = pkt[0] & 0x01; + + return 1; +} +#endif + + +/***************************************************************************** + * 3M/Microtouch Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_3M + +#define MTOUCHUSB_ASYNC_REPORT 1 +#define MTOUCHUSB_RESET 7 +#define MTOUCHUSB_REQ_CTRLLR_ID 10 + +static int mtouch_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = (pkt[8] << 8) | pkt[7]; + dev->y = (pkt[10] << 8) | pkt[9]; + dev->touch = (pkt[2] & 0x40) ? 1 : 0; + + return 1; +} + +static int mtouch_init(struct usbtouch_usb *usbtouch) +{ + int ret, i; + + ret = usb_control_msg(usbtouch->udev, usb_rcvctrlpipe(usbtouch->udev, 0), + MTOUCHUSB_RESET, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 1, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); + dbg("%s - usb_control_msg - MTOUCHUSB_RESET - bytes|err: %d", + __func__, ret); + if (ret < 0) + return ret; + msleep(150); + + for (i = 0; i < 3; i++) { + ret = usb_control_msg(usbtouch->udev, usb_rcvctrlpipe(usbtouch->udev, 0), + MTOUCHUSB_ASYNC_REPORT, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 1, 1, NULL, 0, USB_CTRL_SET_TIMEOUT); + dbg("%s - usb_control_msg - MTOUCHUSB_ASYNC_REPORT - bytes|err: %d", + __func__, ret); + if (ret >= 0) + break; + if (ret != -EPIPE) + return ret; + } + + return 0; +} +#endif + + +/***************************************************************************** + * ITM Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_ITM +static int itm_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + int touch; + /* + * ITM devices report invalid x/y data if not touched. + * if the screen was touched before but is not touched any more + * report touch as 0 with the last valid x/y data once. then stop + * reporting data until touched again. + */ + dev->press = ((pkt[2] & 0x01) << 7) | (pkt[5] & 0x7F); + + touch = ~pkt[7] & 0x20; + if (!touch) { + if (dev->touch) { + dev->touch = 0; + return 1; + } + + return 0; + } + + dev->x = ((pkt[0] & 0x1F) << 7) | (pkt[3] & 0x7F); + dev->y = ((pkt[1] & 0x1F) << 7) | (pkt[4] & 0x7F); + dev->touch = touch; + + return 1; +} +#endif + + +/***************************************************************************** + * eTurboTouch part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_ETURBO +#ifndef MULTI_PACKET +#define MULTI_PACKET +#endif +static int eturbo_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + unsigned int shift; + + /* packets should start with sync */ + if (!(pkt[0] & 0x80)) + return 0; + + shift = (6 - (pkt[0] & 0x03)); + dev->x = ((pkt[3] << 7) | pkt[4]) >> shift; + dev->y = ((pkt[1] << 7) | pkt[2]) >> shift; + dev->touch = (pkt[0] & 0x10) ? 1 : 0; + + return 1; +} + +static int eturbo_get_pkt_len(unsigned char *buf, int len) +{ + if (buf[0] & 0x80) + return 5; + if (buf[0] == 0x01) + return 3; + return 0; +} +#endif + + +/***************************************************************************** + * Gunze part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_GUNZE +static int gunze_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + if (!(pkt[0] & 0x80) || ((pkt[1] | pkt[2] | pkt[3]) & 0x80)) + return 0; + + dev->x = ((pkt[0] & 0x1F) << 7) | (pkt[2] & 0x7F); + dev->y = ((pkt[1] & 0x1F) << 7) | (pkt[3] & 0x7F); + dev->touch = pkt[0] & 0x20; + + return 1; +} +#endif + +/***************************************************************************** + * DMC TSC-10/25 Part + * + * Documentation about the controller and it's protocol can be found at + * http://www.dmccoltd.com/files/controler/tsc10usb_pi_e.pdf + * http://www.dmccoltd.com/files/controler/tsc25_usb_e.pdf + */ +#ifdef CONFIG_TOUCHSCREEN_USB_DMC_TSC10 + +/* supported data rates. currently using 130 */ +#define TSC10_RATE_POINT 0x50 +#define TSC10_RATE_30 0x40 +#define TSC10_RATE_50 0x41 +#define TSC10_RATE_80 0x42 +#define TSC10_RATE_100 0x43 +#define TSC10_RATE_130 0x44 +#define TSC10_RATE_150 0x45 + +/* commands */ +#define TSC10_CMD_RESET 0x55 +#define TSC10_CMD_RATE 0x05 +#define TSC10_CMD_DATA1 0x01 + +static int dmc_tsc10_init(struct usbtouch_usb *usbtouch) +{ + struct usb_device *dev = usbtouch->udev; + int ret = -ENOMEM; + unsigned char *buf; + + buf = kmalloc(2, GFP_KERNEL); + if (!buf) + goto err_nobuf; + /* reset */ + buf[0] = buf[1] = 0xFF; + ret = usb_control_msg(dev, usb_rcvctrlpipe (dev, 0), + TSC10_CMD_RESET, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, 0, buf, 2, USB_CTRL_SET_TIMEOUT); + if (ret < 0) + goto err_out; + if (buf[0] != 0x06 || buf[1] != 0x00) { + ret = -ENODEV; + goto err_out; + } + + /* set coordinate output rate */ + buf[0] = buf[1] = 0xFF; + ret = usb_control_msg(dev, usb_rcvctrlpipe (dev, 0), + TSC10_CMD_RATE, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + TSC10_RATE_150, 0, buf, 2, USB_CTRL_SET_TIMEOUT); + if (ret < 0) + goto err_out; + if ((buf[0] != 0x06 || buf[1] != 0x00) && + (buf[0] != 0x15 || buf[1] != 0x01)) { + ret = -ENODEV; + goto err_out; + } + + /* start sending data */ + ret = usb_control_msg(dev, usb_rcvctrlpipe (dev, 0), + TSC10_CMD_DATA1, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); +err_out: + kfree(buf); +err_nobuf: + return ret; +} + + +static int dmc_tsc10_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = ((pkt[2] & 0x03) << 8) | pkt[1]; + dev->y = ((pkt[4] & 0x03) << 8) | pkt[3]; + dev->touch = pkt[0] & 0x01; + + return 1; +} +#endif + + +/***************************************************************************** + * IRTOUCH Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_IRTOUCH +static int irtouch_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = (pkt[3] << 8) | pkt[2]; + dev->y = (pkt[5] << 8) | pkt[4]; + dev->touch = (pkt[1] & 0x03) ? 1 : 0; + + return 1; +} +#endif + + +/***************************************************************************** + * IdealTEK URTC1000 Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_IDEALTEK +#ifndef MULTI_PACKET +#define MULTI_PACKET +#endif +static int idealtek_get_pkt_len(unsigned char *buf, int len) +{ + if (buf[0] & 0x80) + return 5; + if (buf[0] == 0x01) + return len; + return 0; +} + +static int idealtek_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + switch (pkt[0] & 0x98) { + case 0x88: + /* touch data in IdealTEK mode */ + dev->x = (pkt[1] << 5) | (pkt[2] >> 2); + dev->y = (pkt[3] << 5) | (pkt[4] >> 2); + dev->touch = (pkt[0] & 0x40) ? 1 : 0; + return 1; + + case 0x98: + /* touch data in MT emulation mode */ + dev->x = (pkt[2] << 5) | (pkt[1] >> 2); + dev->y = (pkt[4] << 5) | (pkt[3] >> 2); + dev->touch = (pkt[0] & 0x40) ? 1 : 0; + return 1; + + default: + return 0; + } +} +#endif + +/***************************************************************************** + * General Touch Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH +static int general_touch_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = ((pkt[2] & 0x0F) << 8) | pkt[1] ; + dev->y = ((pkt[4] & 0x0F) << 8) | pkt[3] ; + dev->press = pkt[5] & 0xff; + dev->touch = pkt[0] & 0x01; + + return 1; +} +#endif + +/***************************************************************************** + * GoTop Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_GOTOP +static int gotop_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = ((pkt[1] & 0x38) << 4) | pkt[2]; + dev->y = ((pkt[1] & 0x07) << 7) | pkt[3]; + dev->touch = pkt[0] & 0x01; + return 1; +} +#endif + + +/***************************************************************************** + * the different device descriptors + */ +#ifdef MULTI_PACKET +static void usbtouch_process_multi(struct usbtouch_usb *usbtouch, + unsigned char *pkt, int len); +#endif + +static struct usbtouch_device_info usbtouch_dev_info[] = { +#ifdef CONFIG_TOUCHSCREEN_USB_EGALAX + [DEVTYPE_EGALAX] = { + .min_xc = 0x0, + .max_xc = 0x07ff, + .min_yc = 0x0, + .max_yc = 0x07ff, + .rept_size = 16, + .process_pkt = usbtouch_process_multi, + .get_pkt_len = egalax_get_pkt_len, + .read_data = egalax_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_PANJIT + [DEVTYPE_PANJIT] = { + .min_xc = 0x0, + .max_xc = 0x0fff, + .min_yc = 0x0, + .max_yc = 0x0fff, + .rept_size = 8, + .read_data = panjit_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_3M + [DEVTYPE_3M] = { + .min_xc = 0x0, + .max_xc = 0x4000, + .min_yc = 0x0, + .max_yc = 0x4000, + .rept_size = 11, + .read_data = mtouch_read_data, + .init = mtouch_init, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_ITM + [DEVTYPE_ITM] = { + .min_xc = 0x0, + .max_xc = 0x0fff, + .min_yc = 0x0, + .max_yc = 0x0fff, + .max_press = 0xff, + .rept_size = 8, + .read_data = itm_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_ETURBO + [DEVTYPE_ETURBO] = { + .min_xc = 0x0, + .max_xc = 0x07ff, + .min_yc = 0x0, + .max_yc = 0x07ff, + .rept_size = 8, + .process_pkt = usbtouch_process_multi, + .get_pkt_len = eturbo_get_pkt_len, + .read_data = eturbo_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GUNZE + [DEVTYPE_GUNZE] = { + .min_xc = 0x0, + .max_xc = 0x0fff, + .min_yc = 0x0, + .max_yc = 0x0fff, + .rept_size = 4, + .read_data = gunze_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_DMC_TSC10 + [DEVTYPE_DMC_TSC10] = { + .min_xc = 0x0, + .max_xc = 0x03ff, + .min_yc = 0x0, + .max_yc = 0x03ff, + .rept_size = 5, + .init = dmc_tsc10_init, + .read_data = dmc_tsc10_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_IRTOUCH + [DEVTYPE_IRTOUCH] = { + .min_xc = 0x0, + .max_xc = 0x0fff, + .min_yc = 0x0, + .max_yc = 0x0fff, + .rept_size = 8, + .read_data = irtouch_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_IDEALTEK + [DEVTYPE_IDEALTEK] = { + .min_xc = 0x0, + .max_xc = 0x0fff, + .min_yc = 0x0, + .max_yc = 0x0fff, + .rept_size = 8, + .process_pkt = usbtouch_process_multi, + .get_pkt_len = idealtek_get_pkt_len, + .read_data = idealtek_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH + [DEVTYPE_GENERAL_TOUCH] = { + .min_xc = 0x0, + .max_xc = 0x0500, + .min_yc = 0x0, + .max_yc = 0x0500, + .rept_size = 7, + .read_data = general_touch_read_data, + }, +#endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GOTOP + [DEVTYPE_GOTOP] = { + .min_xc = 0x0, + .max_xc = 0x03ff, + .min_yc = 0x0, + .max_yc = 0x03ff, + .rept_size = 4, + .read_data = gotop_read_data, + }, +#endif +}; + + +/***************************************************************************** + * Generic Part + */ +static void usbtouch_process_pkt(struct usbtouch_usb *usbtouch, + unsigned char *pkt, int len) +{ + struct usbtouch_device_info *type = usbtouch->type; + + if (!type->read_data(usbtouch, pkt)) + return; + + input_report_key(usbtouch->input, BTN_TOUCH, usbtouch->touch); + + if (swap_xy) { + input_report_abs(usbtouch->input, ABS_X, usbtouch->y); + input_report_abs(usbtouch->input, ABS_Y, usbtouch->x); + } else { + input_report_abs(usbtouch->input, ABS_X, usbtouch->x); + input_report_abs(usbtouch->input, ABS_Y, usbtouch->y); + } + if (type->max_press) + input_report_abs(usbtouch->input, ABS_PRESSURE, usbtouch->press); + input_sync(usbtouch->input); +} + + +#ifdef MULTI_PACKET +static void usbtouch_process_multi(struct usbtouch_usb *usbtouch, + unsigned char *pkt, int len) +{ + unsigned char *buffer; + int pkt_len, pos, buf_len, tmp; + + /* process buffer */ + if (unlikely(usbtouch->buf_len)) { + /* try to get size */ + pkt_len = usbtouch->type->get_pkt_len( + usbtouch->buffer, usbtouch->buf_len); + + /* drop? */ + if (unlikely(!pkt_len)) + goto out_flush_buf; + + /* need to append -pkt_len bytes before able to get size */ + if (unlikely(pkt_len < 0)) { + int append = -pkt_len; + if (unlikely(append > len)) + append = len; + if (usbtouch->buf_len + append >= usbtouch->type->rept_size) + goto out_flush_buf; + memcpy(usbtouch->buffer + usbtouch->buf_len, pkt, append); + usbtouch->buf_len += append; + + pkt_len = usbtouch->type->get_pkt_len( + usbtouch->buffer, usbtouch->buf_len); + if (pkt_len < 0) + return; + } + + /* append */ + tmp = pkt_len - usbtouch->buf_len; + if (usbtouch->buf_len + tmp >= usbtouch->type->rept_size) + goto out_flush_buf; + memcpy(usbtouch->buffer + usbtouch->buf_len, pkt, tmp); + usbtouch_process_pkt(usbtouch, usbtouch->buffer, pkt_len); + + buffer = pkt + tmp; + buf_len = len - tmp; + } else { + buffer = pkt; + buf_len = len; + } + + /* loop over the received packet, process */ + pos = 0; + while (pos < buf_len) { + /* get packet len */ + pkt_len = usbtouch->type->get_pkt_len(buffer + pos, + buf_len - pos); + + /* unknown packet: skip one byte */ + if (unlikely(!pkt_len)) { + pos++; + continue; + } + + /* full packet: process */ + if (likely((pkt_len > 0) && (pkt_len <= buf_len - pos))) { + usbtouch_process_pkt(usbtouch, buffer + pos, pkt_len); + } else { + /* incomplete packet: save in buffer */ + memcpy(usbtouch->buffer, buffer + pos, buf_len - pos); + usbtouch->buf_len = buf_len - pos; + return; + } + pos += pkt_len; + } + +out_flush_buf: + usbtouch->buf_len = 0; + return; +} +#endif + + +static void usbtouch_irq(struct urb *urb) +{ + struct usbtouch_usb *usbtouch = urb->context; + int retval; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ETIME: + /* this urb is timing out */ + dbg("%s - urb timed out - was the device unplugged?", + __func__); + return; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", + __func__, urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", + __func__, urb->status); + goto exit; + } + + usbtouch->type->process_pkt(usbtouch, usbtouch->data, urb->actual_length); + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + err("%s - usb_submit_urb failed with result: %d", + __func__, retval); +} + +static int usbtouch_open(struct input_dev *input) +{ + struct usbtouch_usb *usbtouch = input_get_drvdata(input); + + usbtouch->irq->dev = usbtouch->udev; + + if (usb_submit_urb(usbtouch->irq, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void usbtouch_close(struct input_dev *input) +{ + struct usbtouch_usb *usbtouch = input_get_drvdata(input); + + usb_kill_urb(usbtouch->irq); +} + + +static void usbtouch_free_buffers(struct usb_device *udev, + struct usbtouch_usb *usbtouch) +{ + usb_buffer_free(udev, usbtouch->type->rept_size, + usbtouch->data, usbtouch->data_dma); + kfree(usbtouch->buffer); +} + + +static int usbtouch_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usbtouch_usb *usbtouch; + struct input_dev *input_dev; + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct usb_device *udev = interface_to_usbdev(intf); + struct usbtouch_device_info *type; + int err = -ENOMEM; + + /* some devices are ignored */ + if (id->driver_info == DEVTYPE_IGNORE) + return -ENODEV; + + interface = intf->cur_altsetting; + endpoint = &interface->endpoint[0].desc; + + usbtouch = kzalloc(sizeof(struct usbtouch_usb), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!usbtouch || !input_dev) + goto out_free; + + type = &usbtouch_dev_info[id->driver_info]; + usbtouch->type = type; + if (!type->process_pkt) + type->process_pkt = usbtouch_process_pkt; + + usbtouch->data = usb_buffer_alloc(udev, type->rept_size, + GFP_KERNEL, &usbtouch->data_dma); + if (!usbtouch->data) + goto out_free; + + if (type->get_pkt_len) { + usbtouch->buffer = kmalloc(type->rept_size, GFP_KERNEL); + if (!usbtouch->buffer) + goto out_free_buffers; + } + + usbtouch->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!usbtouch->irq) { + dbg("%s - usb_alloc_urb failed: usbtouch->irq", __func__); + goto out_free_buffers; + } + + usbtouch->udev = udev; + usbtouch->input = input_dev; + + if (udev->manufacturer) + strlcpy(usbtouch->name, udev->manufacturer, sizeof(usbtouch->name)); + + if (udev->product) { + if (udev->manufacturer) + strlcat(usbtouch->name, " ", sizeof(usbtouch->name)); + strlcat(usbtouch->name, udev->product, sizeof(usbtouch->name)); + } + + if (!strlen(usbtouch->name)) + snprintf(usbtouch->name, sizeof(usbtouch->name), + "USB Touchscreen %04x:%04x", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + usb_make_path(udev, usbtouch->phys, sizeof(usbtouch->phys)); + strlcat(usbtouch->phys, "/input0", sizeof(usbtouch->phys)); + + input_dev->name = usbtouch->name; + input_dev->phys = usbtouch->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, usbtouch); + + input_dev->open = usbtouch_open; + input_dev->close = usbtouch_close; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, type->min_xc, type->max_xc, 0, 0); + input_set_abs_params(input_dev, ABS_Y, type->min_yc, type->max_yc, 0, 0); + if (type->max_press) + input_set_abs_params(input_dev, ABS_PRESSURE, type->min_press, + type->max_press, 0, 0); + + usb_fill_int_urb(usbtouch->irq, usbtouch->udev, + usb_rcvintpipe(usbtouch->udev, endpoint->bEndpointAddress), + usbtouch->data, type->rept_size, + usbtouch_irq, usbtouch, endpoint->bInterval); + + usbtouch->irq->dev = usbtouch->udev; + usbtouch->irq->transfer_dma = usbtouch->data_dma; + usbtouch->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* device specific init */ + if (type->init) { + err = type->init(usbtouch); + if (err) { + dbg("%s - type->init() failed, err: %d", __func__, err); + goto out_free_buffers; + } + } + + err = input_register_device(usbtouch->input); + if (err) { + dbg("%s - input_register_device failed, err: %d", __func__, err); + goto out_free_buffers; + } + + usb_set_intfdata(intf, usbtouch); + + return 0; + +out_free_buffers: + usbtouch_free_buffers(udev, usbtouch); +out_free: + input_free_device(input_dev); + kfree(usbtouch); + return err; +} + +static void usbtouch_disconnect(struct usb_interface *intf) +{ + struct usbtouch_usb *usbtouch = usb_get_intfdata(intf); + + dbg("%s - called", __func__); + + if (!usbtouch) + return; + + dbg("%s - usbtouch is initialized, cleaning up", __func__); + usb_set_intfdata(intf, NULL); + usb_kill_urb(usbtouch->irq); + input_unregister_device(usbtouch->input); + usb_free_urb(usbtouch->irq); + usbtouch_free_buffers(interface_to_usbdev(intf), usbtouch); + kfree(usbtouch); +} + +MODULE_DEVICE_TABLE(usb, usbtouch_devices); + +static struct usb_driver usbtouch_driver = { + .name = "usbtouchscreen", + .probe = usbtouch_probe, + .disconnect = usbtouch_disconnect, + .id_table = usbtouch_devices, +}; + +static int __init usbtouch_init(void) +{ + return usb_register(&usbtouch_driver); +} + +static void __exit usbtouch_cleanup(void) +{ + usb_deregister(&usbtouch_driver); +} + +module_init(usbtouch_init); +module_exit(usbtouch_cleanup); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("touchkitusb"); +MODULE_ALIAS("itmtouch"); +MODULE_ALIAS("mtouchusb"); diff --git a/drivers/input/touchscreen/wm9705.c b/drivers/input/touchscreen/wm9705.c new file mode 100644 index 0000000..6b5be74 --- /dev/null +++ b/drivers/input/touchscreen/wm9705.c @@ -0,0 +1,351 @@ +/* + * wm9705.c -- Codec driver for Wolfson WM9705 AC97 Codec. + * + * Copyright 2003, 2004, 2005, 2006, 2007 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * Russell King <rmk@arm.linux.org.uk> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/wm97xx.h> + +#define TS_NAME "wm97xx" +#define WM9705_VERSION "1.00" +#define DEFAULT_PRESSURE 0xb0c0 + +/* + * Module parameters + */ + +/* + * 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. + */ +static int pil; +module_param(pil, int, 0); +MODULE_PARM_DESC(pil, "Set current used for pressure measurement."); + +/* + * Set threshold for pressure measurement. + * + * Pen down pressure below threshold is ignored. + */ +static int pressure = DEFAULT_PRESSURE & 0xfff; +module_param(pressure, int, 0); +MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement."); + +/* + * 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. + */ +static int delay = 4; +module_param(delay, int, 0); +MODULE_PARM_DESC(delay, "Set adc sample delay."); + +/* + * 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. + */ +static int pdd = 8; +module_param(pdd, int, 0); +MODULE_PARM_DESC(pdd, "Set pen detect comparator threshold"); + +/* + * Set adc mask function. + * + * Sources of glitch noise, such as signals driving an LCD display, may feed + * through to the touch screen plates and affect measurement accuracy. In + * order to minimise this, a signal may be applied to the MASK pin to delay or + * synchronise the sampling. + * + * 0 = No delay or sync + * 1 = High on pin stops conversions + * 2 = Edge triggered, edge on pin delays conversion by delay param (above) + * 3 = Edge triggered, edge on pin starts conversion after delay param + */ +static int mask; +module_param(mask, int, 0); +MODULE_PARM_DESC(mask, "Set adc mask function."); + +/* + * ADC sample delay times in uS + */ +static const int delay_table[] = { + 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(int d) +{ + udelay(3 * AC97_LINK_FRAME + delay_table[d]); +} + +/* + * set up the physical settings of the WM9705 + */ +static void wm9705_phy_init(struct wm97xx *wm) +{ + u16 dig1 = 0, dig2 = WM97XX_RPR; + + /* + * mute VIDEO and AUX as they share X and Y touchscreen + * inputs on the WM9705 + */ + wm97xx_reg_write(wm, AC97_AUX, 0x8000); + wm97xx_reg_write(wm, AC97_VIDEO, 0x8000); + + /* touchpanel pressure current*/ + if (pil == 2) { + dig2 |= WM9705_PIL; + dev_dbg(wm->dev, + "setting pressure measurement current to 400uA."); + } else if (pil) + dev_dbg(wm->dev, + "setting pressure measurement current to 200uA."); + if (!pil) + pressure = 0; + + /* polling mode sample settling delay */ + if (delay != 4) { + if (delay < 0 || delay > 15) { + dev_dbg(wm->dev, "supplied delay out of range."); + delay = 4; + } + } + dig1 &= 0xff0f; + dig1 |= WM97XX_DELAY(delay); + dev_dbg(wm->dev, "setting adc sample delay to %d u Secs.", + delay_table[delay]); + + /* WM9705 pdd */ + dig2 |= (pdd & 0x000f); + dev_dbg(wm->dev, "setting pdd to Vmid/%d", 1 - (pdd & 0x000f)); + + /* mask */ + dig2 |= ((mask & 0x3) << 4); + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2); +} + +static void wm9705_dig_enable(struct wm97xx *wm, int enable) +{ + if (enable) { + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, + wm->dig[2] | WM97XX_PRP_DET_DIG); + wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */ + } else + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, + wm->dig[2] & ~WM97XX_PRP_DET_DIG); +} + +static void wm9705_aux_prepare(struct wm97xx *wm) +{ + memcpy(wm->dig_save, wm->dig, sizeof(wm->dig)); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, 0); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, WM97XX_PRP_DET_DIG); +} + +static void wm9705_dig_restore(struct wm97xx *wm) +{ + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, wm->dig_save[1]); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, wm->dig_save[2]); +} + +static inline int is_pden(struct wm97xx *wm) +{ + return wm->dig[2] & WM9705_PDEN; +} + +/* + * Read a sample from the WM9705 adc in polling mode. + */ +static int wm9705_poll_sample(struct wm97xx *wm, int adcsel, int *sample) +{ + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + if (adcsel & 0x8000) + adcsel = ((adcsel & 0x7fff) + 3) << 12; + + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(adcsel); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, + adcsel | WM97XX_POLL | WM97XX_DELAY(delay)); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay(delay); + + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL) + && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout == 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dev_dbg(wm->dev, "adc sample timeout"); + return RC_PENUP; + } + + *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(adcsel); + + /* check we have correct sample */ + if ((*sample & WM97XX_ADCSEL_MASK) != adcsel) { + dev_dbg(wm->dev, "adc wrong sample, read %x got %x", adcsel, + *sample & WM97XX_ADCSEL_MASK); + return RC_PENUP; + } + + if (!(*sample & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + + return RC_VALID; +} + +/* + * Sample the WM9705 touchscreen in polling mode + */ +static int wm9705_poll_touch(struct wm97xx *wm, struct wm97xx_data *data) +{ + int rc; + + rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_X, &data->x); + if (rc != RC_VALID) + return rc; + rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_Y, &data->y); + if (rc != RC_VALID) + return rc; + if (pil) { + rc = wm9705_poll_sample(wm, WM97XX_ADCSEL_PRES, &data->p); + if (rc != RC_VALID) + return rc; + } else + data->p = DEFAULT_PRESSURE; + + return RC_VALID; +} + +/* + * Enable WM9705 continuous mode, i.e. touch data is streamed across + * an AC97 slot + */ +static int wm9705_acc_enable(struct wm97xx *wm, int enable) +{ + u16 dig1, dig2; + int ret = 0; + + dig1 = wm->dig[1]; + dig2 = wm->dig[2]; + + if (enable) { + /* continous mode */ + if (wm->mach_ops->acc_startup && + (ret = wm->mach_ops->acc_startup(wm)) < 0) + return ret; + dig1 &= ~(WM97XX_CM_RATE_MASK | WM97XX_ADCSEL_MASK | + WM97XX_DELAY_MASK | WM97XX_SLT_MASK); + dig1 |= WM97XX_CTC | WM97XX_COO | WM97XX_SLEN | + WM97XX_DELAY(delay) | + WM97XX_SLT(wm->acc_slot) | + WM97XX_RATE(wm->acc_rate); + if (pil) + dig1 |= WM97XX_ADCSEL_PRES; + dig2 |= WM9705_PDEN; + } else { + dig1 &= ~(WM97XX_CTC | WM97XX_COO | WM97XX_SLEN); + dig2 &= ~WM9705_PDEN; + if (wm->mach_ops->acc_shutdown) + wm->mach_ops->acc_shutdown(wm); + } + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2); + + return ret; +} + +struct wm97xx_codec_drv wm9705_codec = { + .id = WM9705_ID2, + .name = "wm9705", + .poll_sample = wm9705_poll_sample, + .poll_touch = wm9705_poll_touch, + .acc_enable = wm9705_acc_enable, + .phy_init = wm9705_phy_init, + .dig_enable = wm9705_dig_enable, + .dig_restore = wm9705_dig_restore, + .aux_prepare = wm9705_aux_prepare, +}; +EXPORT_SYMBOL_GPL(wm9705_codec); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("WM9705 Touch Screen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/wm9712.c b/drivers/input/touchscreen/wm9712.c new file mode 100644 index 0000000..7490b05 --- /dev/null +++ b/drivers/input/touchscreen/wm9712.c @@ -0,0 +1,466 @@ +/* + * wm9712.c -- Codec driver for Wolfson WM9712 AC97 Codecs. + * + * Copyright 2003, 2004, 2005, 2006, 2007 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * Russell King <rmk@arm.linux.org.uk> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/wm97xx.h> + +#define TS_NAME "wm97xx" +#define WM9712_VERSION "1.00" +#define DEFAULT_PRESSURE 0xb0c0 + +/* + * Module parameters + */ + +/* + * 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 event. + */ +static int rpu = 8; +module_param(rpu, int, 0); +MODULE_PARM_DESC(rpu, "Set internal pull up resitor for pen detect."); + +/* + * 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. + */ +static int pil; +module_param(pil, int, 0); +MODULE_PARM_DESC(pil, "Set current used for pressure measurement."); + +/* + * Set threshold for pressure measurement. + * + * Pen down pressure below threshold is ignored. + */ +static int pressure = DEFAULT_PRESSURE & 0xfff; +module_param(pressure, int, 0); +MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement."); + +/* + * 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. + */ +static int delay = 3; +module_param(delay, int, 0); +MODULE_PARM_DESC(delay, "Set adc sample delay."); + +/* + * Set five_wire = 1 to use a 5 wire touchscreen. + * + * NOTE: Five wire mode does not allow for readback of pressure. + */ +static int five_wire; +module_param(five_wire, int, 0); +MODULE_PARM_DESC(five_wire, "Set to '1' to use 5-wire touchscreen."); + +/* + * Set adc mask function. + * + * Sources of glitch noise, such as signals driving an LCD display, may feed + * through to the touch screen plates and affect measurement accuracy. In + * order to minimise this, a signal may be applied to the MASK pin to delay or + * synchronise the sampling. + * + * 0 = No delay or sync + * 1 = High on pin stops conversions + * 2 = Edge triggered, edge on pin delays conversion by delay param (above) + * 3 = Edge triggered, edge on pin starts conversion after delay param + */ +static int mask; +module_param(mask, int, 0); +MODULE_PARM_DESC(mask, "Set adc mask function."); + +/* + * Coordinate Polling Enable. + * + * Set to 1 to enable coordinate polling. e.g. x,y[,p] is sampled together + * for every poll. + */ +static int coord; +module_param(coord, int, 0); +MODULE_PARM_DESC(coord, "Polling coordinate mode"); + +/* + * ADC sample delay times in uS + */ +static const int delay_table[] = { + 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(int d) +{ + udelay(3 * AC97_LINK_FRAME + delay_table[d]); +} + +/* + * set up the physical settings of the WM9712 + */ +static void wm9712_phy_init(struct wm97xx *wm) +{ + u16 dig1 = 0; + u16 dig2 = WM97XX_RPR | WM9712_RPU(1); + + /* WM9712 rpu */ + if (rpu) { + dig2 &= 0xffc0; + dig2 |= WM9712_RPU(rpu); + dev_dbg(wm->dev, "setting pen detect pull-up to %d Ohms", + 64000 / rpu); + } + + /* WM9712 five wire */ + if (five_wire) { + dig2 |= WM9712_45W; + dev_dbg(wm->dev, "setting 5-wire touchscreen mode."); + + if (pil) { + dev_warn(wm->dev, "pressure measurement is not " + "supported in 5-wire mode\n"); + pil = 0; + } + } + + /* touchpanel pressure current*/ + if (pil == 2) { + dig2 |= WM9712_PIL; + dev_dbg(wm->dev, + "setting pressure measurement current to 400uA."); + } else if (pil) + dev_dbg(wm->dev, + "setting pressure measurement current to 200uA."); + if (!pil) + pressure = 0; + + /* polling mode sample settling delay */ + if (delay < 0 || delay > 15) { + dev_dbg(wm->dev, "supplied delay out of range."); + delay = 4; + } + dig1 &= 0xff0f; + dig1 |= WM97XX_DELAY(delay); + dev_dbg(wm->dev, "setting adc sample delay to %d u Secs.", + delay_table[delay]); + + /* mask */ + dig2 |= ((mask & 0x3) << 6); + if (mask) { + u16 reg; + /* Set GPIO4 as Mask Pin*/ + reg = wm97xx_reg_read(wm, AC97_MISC_AFE); + wm97xx_reg_write(wm, AC97_MISC_AFE, reg | WM97XX_GPIO_4); + reg = wm97xx_reg_read(wm, AC97_GPIO_CFG); + wm97xx_reg_write(wm, AC97_GPIO_CFG, reg | WM97XX_GPIO_4); + } + + /* wait - coord mode */ + if (coord) + dig2 |= WM9712_WAIT; + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2); +} + +static void wm9712_dig_enable(struct wm97xx *wm, int enable) +{ + u16 dig2 = wm->dig[2]; + + if (enable) { + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, + dig2 | WM97XX_PRP_DET_DIG); + wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */ + } else + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, + dig2 & ~WM97XX_PRP_DET_DIG); +} + +static void wm9712_aux_prepare(struct wm97xx *wm) +{ + memcpy(wm->dig_save, wm->dig, sizeof(wm->dig)); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, 0); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, WM97XX_PRP_DET_DIG); +} + +static void wm9712_dig_restore(struct wm97xx *wm) +{ + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, wm->dig_save[1]); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, wm->dig_save[2]); +} + +static inline int is_pden(struct wm97xx *wm) +{ + return wm->dig[2] & WM9712_PDEN; +} + +/* + * Read a sample from the WM9712 adc in polling mode. + */ +static int wm9712_poll_sample(struct wm97xx *wm, int adcsel, int *sample) +{ + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + if (adcsel & 0x8000) + adcsel = ((adcsel & 0x7fff) + 3) << 12; + + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(adcsel); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, + adcsel | WM97XX_POLL | WM97XX_DELAY(delay)); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay(delay); + + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL) + && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dev_dbg(wm->dev, "adc sample timeout"); + return RC_PENUP; + } + + *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(adcsel); + + /* check we have correct sample */ + if ((*sample & WM97XX_ADCSEL_MASK) != adcsel) { + dev_dbg(wm->dev, "adc wrong sample, read %x got %x", adcsel, + *sample & WM97XX_ADCSEL_MASK); + return RC_PENUP; + } + + if (!(*sample & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + + return RC_VALID; +} + +/* + * Read a coord from the WM9712 adc in polling mode. + */ +static int wm9712_poll_coord(struct wm97xx *wm, struct wm97xx_data *data) +{ + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data_rd = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data_rd & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y); + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, + WM97XX_COO | WM97XX_POLL | WM97XX_DELAY(delay)); + + /* wait 3 AC97 time slots + delay for conversion and read x */ + poll_delay(delay); + data->x = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER1) & WM97XX_POLL) + && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dev_dbg(wm->dev, "adc sample timeout"); + return RC_PENUP; + } + + /* read back y data */ + data->y = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (pil) + data->p = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + else + data->p = DEFAULT_PRESSURE; + + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y); + + /* check we have correct sample */ + if (!(data->x & WM97XX_ADCSEL_X) || !(data->y & WM97XX_ADCSEL_Y)) + goto err; + if (pil && !(data->p & WM97XX_ADCSEL_PRES)) + goto err; + + if (!(data->x & WM97XX_PEN_DOWN) || !(data->y & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + return RC_VALID; +err: + return 0; +} + +/* + * Sample the WM9712 touchscreen in polling mode + */ +static int wm9712_poll_touch(struct wm97xx *wm, struct wm97xx_data *data) +{ + int rc; + + if (coord) { + rc = wm9712_poll_coord(wm, data); + if (rc != RC_VALID) + return rc; + } else { + rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_X, &data->x); + if (rc != RC_VALID) + return rc; + + rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_Y, &data->y); + if (rc != RC_VALID) + return rc; + + if (pil && !five_wire) { + rc = wm9712_poll_sample(wm, WM97XX_ADCSEL_PRES, + &data->p); + if (rc != RC_VALID) + return rc; + } else + data->p = DEFAULT_PRESSURE; + } + return RC_VALID; +} + +/* + * Enable WM9712 continuous mode, i.e. touch data is streamed across + * an AC97 slot + */ +static int wm9712_acc_enable(struct wm97xx *wm, int enable) +{ + u16 dig1, dig2; + int ret = 0; + + dig1 = wm->dig[1]; + dig2 = wm->dig[2]; + + if (enable) { + /* continous mode */ + if (wm->mach_ops->acc_startup) { + ret = wm->mach_ops->acc_startup(wm); + if (ret < 0) + return ret; + } + dig1 &= ~(WM97XX_CM_RATE_MASK | WM97XX_ADCSEL_MASK | + WM97XX_DELAY_MASK | WM97XX_SLT_MASK); + dig1 |= WM97XX_CTC | WM97XX_COO | WM97XX_SLEN | + WM97XX_DELAY(delay) | + WM97XX_SLT(wm->acc_slot) | + WM97XX_RATE(wm->acc_rate); + if (pil) + dig1 |= WM97XX_ADCSEL_PRES; + dig2 |= WM9712_PDEN; + } else { + dig1 &= ~(WM97XX_CTC | WM97XX_COO | WM97XX_SLEN); + dig2 &= ~WM9712_PDEN; + if (wm->mach_ops->acc_shutdown) + wm->mach_ops->acc_shutdown(wm); + } + + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER1, dig1); + wm97xx_reg_write(wm, AC97_WM97XX_DIGITISER2, dig2); + + return 0; +} + +struct wm97xx_codec_drv wm9712_codec = { + .id = WM9712_ID2, + .name = "wm9712", + .poll_sample = wm9712_poll_sample, + .poll_touch = wm9712_poll_touch, + .acc_enable = wm9712_acc_enable, + .phy_init = wm9712_phy_init, + .dig_enable = wm9712_dig_enable, + .dig_restore = wm9712_dig_restore, + .aux_prepare = wm9712_aux_prepare, +}; +EXPORT_SYMBOL_GPL(wm9712_codec); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("WM9712 Touch Screen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/wm9713.c b/drivers/input/touchscreen/wm9713.c new file mode 100644 index 0000000..238b513 --- /dev/null +++ b/drivers/input/touchscreen/wm9713.c @@ -0,0 +1,480 @@ +/* + * wm9713.c -- Codec touch driver for Wolfson WM9713 AC97 Codec. + * + * Copyright 2003, 2004, 2005, 2006, 2007, 2008 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * Russell King <rmk@arm.linux.org.uk> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/wm97xx.h> + +#define TS_NAME "wm97xx" +#define WM9713_VERSION "1.00" +#define DEFAULT_PRESSURE 0xb0c0 + +/* + * Module parameters + */ + +/* + * 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 event. + */ +static int rpu = 8; +module_param(rpu, int, 0); +MODULE_PARM_DESC(rpu, "Set internal pull up resitor for pen detect."); + +/* + * 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. + */ +static int pil; +module_param(pil, int, 0); +MODULE_PARM_DESC(pil, "Set current used for pressure measurement."); + +/* + * Set threshold for pressure measurement. + * + * Pen down pressure below threshold is ignored. + */ +static int pressure = DEFAULT_PRESSURE & 0xfff; +module_param(pressure, int, 0); +MODULE_PARM_DESC(pressure, "Set threshold for pressure measurement."); + +/* + * 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. + */ +static int delay = 4; +module_param(delay, int, 0); +MODULE_PARM_DESC(delay, "Set adc sample delay."); + +/* + * Set five_wire = 1 to use a 5 wire touchscreen. + * + * NOTE: Five wire mode does not allow for readback of pressure. + */ +static int five_wire; +module_param(five_wire, int, 0); +MODULE_PARM_DESC(five_wire, "Set to '1' to use 5-wire touchscreen."); + +/* + * Set adc mask function. + * + * Sources of glitch noise, such as signals driving an LCD display, may feed + * through to the touch screen plates and affect measurement accuracy. In + * order to minimise this, a signal may be applied to the MASK pin to delay or + * synchronise the sampling. + * + * 0 = No delay or sync + * 1 = High on pin stops conversions + * 2 = Edge triggered, edge on pin delays conversion by delay param (above) + * 3 = Edge triggered, edge on pin starts conversion after delay param + */ +static int mask; +module_param(mask, int, 0); +MODULE_PARM_DESC(mask, "Set adc mask function."); + +/* + * Coordinate Polling Enable. + * + * Set to 1 to enable coordinate polling. e.g. x,y[,p] is sampled together + * for every poll. + */ +static int coord; +module_param(coord, int, 0); +MODULE_PARM_DESC(coord, "Polling coordinate mode"); + +/* + * ADC sample delay times in uS + */ +static const int delay_table[] = { + 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(int d) +{ + udelay(3 * AC97_LINK_FRAME + delay_table[d]); +} + +/* + * set up the physical settings of the WM9713 + */ +static void wm9713_phy_init(struct wm97xx *wm) +{ + u16 dig1 = 0, dig2, dig3; + + /* default values */ + dig2 = WM97XX_DELAY(4) | WM97XX_SLT(5); + dig3 = WM9712_RPU(1); + + /* rpu */ + if (rpu) { + dig3 &= 0xffc0; + dig3 |= WM9712_RPU(rpu); + dev_info(wm->dev, "setting pen detect pull-up to %d Ohms\n", + 64000 / rpu); + } + + /* Five wire panel? */ + if (five_wire) { + dig3 |= WM9713_45W; + dev_info(wm->dev, "setting 5-wire touchscreen mode."); + + if (pil) { + dev_warn(wm->dev, + "Pressure measurement not supported in 5 " + "wire mode, disabling\n"); + pil = 0; + } + } + + /* touchpanel pressure */ + if (pil == 2) { + dig3 |= WM9712_PIL; + dev_info(wm->dev, + "setting pressure measurement current to 400uA."); + } else if (pil) + dev_info(wm->dev, + "setting pressure measurement current to 200uA."); + if (!pil) + pressure = 0; + + /* sample settling delay */ + if (delay < 0 || delay > 15) { + dev_info(wm->dev, "supplied delay out of range."); + delay = 4; + dev_info(wm->dev, "setting adc sample delay to %d u Secs.", + delay_table[delay]); + } + dig2 &= 0xff0f; + dig2 |= WM97XX_DELAY(delay); + + /* mask */ + dig3 |= ((mask & 0x3) << 4); + if (coord) + dig3 |= WM9713_WAIT; + + wm->misc = wm97xx_reg_read(wm, 0x5a); + + wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1); + wm97xx_reg_write(wm, AC97_WM9713_DIG2, dig2); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, dig3); + wm97xx_reg_write(wm, AC97_GPIO_STICKY, 0x0); +} + +static void wm9713_dig_enable(struct wm97xx *wm, int enable) +{ + u16 val; + + if (enable) { + val = wm97xx_reg_read(wm, AC97_EXTENDED_MID); + wm97xx_reg_write(wm, AC97_EXTENDED_MID, val & 0x7fff); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2] | + WM97XX_PRP_DET_DIG); + wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); /* dummy read */ + } else { + wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2] & + ~WM97XX_PRP_DET_DIG); + val = wm97xx_reg_read(wm, AC97_EXTENDED_MID); + wm97xx_reg_write(wm, AC97_EXTENDED_MID, val | 0x8000); + } +} + +static void wm9713_dig_restore(struct wm97xx *wm) +{ + wm97xx_reg_write(wm, AC97_WM9713_DIG1, wm->dig_save[0]); + wm97xx_reg_write(wm, AC97_WM9713_DIG2, wm->dig_save[1]); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig_save[2]); +} + +static void wm9713_aux_prepare(struct wm97xx *wm) +{ + memcpy(wm->dig_save, wm->dig, sizeof(wm->dig)); + wm97xx_reg_write(wm, AC97_WM9713_DIG1, 0); + wm97xx_reg_write(wm, AC97_WM9713_DIG2, 0); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, WM97XX_PRP_DET_DIG); +} + +static inline int is_pden(struct wm97xx *wm) +{ + return wm->dig[2] & WM9713_PDEN; +} + +/* + * Read a sample from the WM9713 adc in polling mode. + */ +static int wm9713_poll_sample(struct wm97xx *wm, int adcsel, int *sample) +{ + u16 dig1; + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 data = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(data & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + if (adcsel & 0x8000) + adcsel = 1 << ((adcsel & 0x7fff) + 3); + + dig1 = wm97xx_reg_read(wm, AC97_WM9713_DIG1); + dig1 &= ~WM9713_ADCSEL_MASK; + + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(adcsel); + wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1 | adcsel | WM9713_POLL); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay(delay); + + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM9713_DIG1) & WM9713_POLL) && + timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dev_dbg(wm->dev, "adc sample timeout"); + return RC_PENUP; + } + + *sample = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(adcsel); + + /* check we have correct sample */ + if ((*sample & WM97XX_ADCSRC_MASK) != ffs(adcsel >> 1) << 12) { + dev_dbg(wm->dev, "adc wrong sample, read %x got %x", adcsel, + *sample & WM97XX_ADCSRC_MASK); + return RC_PENUP; + } + + if (!(*sample & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + + return RC_VALID; +} + +/* + * Read a coordinate from the WM9713 adc in polling mode. + */ +static int wm9713_poll_coord(struct wm97xx *wm, struct wm97xx_data *data) +{ + u16 dig1; + int timeout = 5 * delay; + + if (!wm->pen_probably_down) { + u16 val = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (!(val & WM97XX_PEN_DOWN)) + return RC_PENUP; + wm->pen_probably_down = 1; + } + + /* set up digitiser */ + dig1 = wm97xx_reg_read(wm, AC97_WM9713_DIG1); + dig1 &= ~WM9713_ADCSEL_MASK; + if (pil) + dig1 |= WM9713_ADCSEL_PRES; + + if (wm->mach_ops && wm->mach_ops->pre_sample) + wm->mach_ops->pre_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y); + wm97xx_reg_write(wm, AC97_WM9713_DIG1, + dig1 | WM9713_POLL | WM9713_COO); + + /* wait 3 AC97 time slots + delay for conversion */ + poll_delay(delay); + data->x = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + /* wait for POLL to go low */ + while ((wm97xx_reg_read(wm, AC97_WM9713_DIG1) & WM9713_POLL) + && timeout) { + udelay(AC97_LINK_FRAME); + timeout--; + } + + if (timeout <= 0) { + /* If PDEN is set, we can get a timeout when pen goes up */ + if (is_pden(wm)) + wm->pen_probably_down = 0; + else + dev_dbg(wm->dev, "adc sample timeout"); + return RC_PENUP; + } + + /* read back data */ + data->y = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + if (pil) + data->p = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD); + else + data->p = DEFAULT_PRESSURE; + + if (wm->mach_ops && wm->mach_ops->post_sample) + wm->mach_ops->post_sample(WM97XX_ADCSEL_X | WM97XX_ADCSEL_Y); + + /* check we have correct sample */ + if (!(data->x & WM97XX_ADCSEL_X) || !(data->y & WM97XX_ADCSEL_Y)) + goto err; + if (pil && !(data->p & WM97XX_ADCSEL_PRES)) + goto err; + + if (!(data->x & WM97XX_PEN_DOWN) || !(data->y & WM97XX_PEN_DOWN)) { + wm->pen_probably_down = 0; + return RC_PENUP; + } + return RC_VALID; +err: + return 0; +} + +/* + * Sample the WM9713 touchscreen in polling mode + */ +static int wm9713_poll_touch(struct wm97xx *wm, struct wm97xx_data *data) +{ + int rc; + + if (coord) { + rc = wm9713_poll_coord(wm, data); + if (rc != RC_VALID) + return rc; + } else { + rc = wm9713_poll_sample(wm, WM9713_ADCSEL_X, &data->x); + if (rc != RC_VALID) + return rc; + rc = wm9713_poll_sample(wm, WM9713_ADCSEL_Y, &data->y); + if (rc != RC_VALID) + return rc; + if (pil) { + rc = wm9713_poll_sample(wm, WM9713_ADCSEL_PRES, + &data->p); + if (rc != RC_VALID) + return rc; + } else + data->p = DEFAULT_PRESSURE; + } + return RC_VALID; +} + +/* + * Enable WM9713 continuous mode, i.e. touch data is streamed across + * an AC97 slot + */ +static int wm9713_acc_enable(struct wm97xx *wm, int enable) +{ + u16 dig1, dig2, dig3; + int ret = 0; + + dig1 = wm->dig[0]; + dig2 = wm->dig[1]; + dig3 = wm->dig[2]; + + if (enable) { + /* continous mode */ + if (wm->mach_ops->acc_startup && + (ret = wm->mach_ops->acc_startup(wm)) < 0) + return ret; + + dig1 &= ~WM9713_ADCSEL_MASK; + dig1 |= WM9713_CTC | WM9713_COO | WM9713_ADCSEL_X | + WM9713_ADCSEL_Y; + if (pil) + dig1 |= WM9713_ADCSEL_PRES; + dig2 &= ~(WM97XX_DELAY_MASK | WM97XX_SLT_MASK | + WM97XX_CM_RATE_MASK); + dig2 |= WM97XX_SLEN | WM97XX_DELAY(delay) | + WM97XX_SLT(wm->acc_slot) | WM97XX_RATE(wm->acc_rate); + dig3 |= WM9713_PDEN; + } else { + dig1 &= ~(WM9713_CTC | WM9713_COO); + dig2 &= ~WM97XX_SLEN; + dig3 &= ~WM9713_PDEN; + if (wm->mach_ops->acc_shutdown) + wm->mach_ops->acc_shutdown(wm); + } + + wm97xx_reg_write(wm, AC97_WM9713_DIG1, dig1); + wm97xx_reg_write(wm, AC97_WM9713_DIG2, dig2); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, dig3); + + return ret; +} + +struct wm97xx_codec_drv wm9713_codec = { + .id = WM9713_ID2, + .name = "wm9713", + .poll_sample = wm9713_poll_sample, + .poll_touch = wm9713_poll_touch, + .acc_enable = wm9713_acc_enable, + .phy_init = wm9713_phy_init, + .dig_enable = wm9713_dig_enable, + .dig_restore = wm9713_dig_restore, + .aux_prepare = wm9713_aux_prepare, +}; +EXPORT_SYMBOL_GPL(wm9713_codec); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("WM9713 Touch Screen Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/wm97xx-core.c b/drivers/input/touchscreen/wm97xx-core.c new file mode 100644 index 0000000..d15aa11 --- /dev/null +++ b/drivers/input/touchscreen/wm97xx-core.c @@ -0,0 +1,828 @@ +/* + * wm97xx-core.c -- Touch screen driver core for Wolfson WM9705, WM9712 + * and WM9713 AC97 Codecs. + * + * Copyright 2003, 2004, 2005, 2006, 2007, 2008 Wolfson Microelectronics PLC. + * Author: Liam Girdwood <lrg@slimlogic.co.uk> + * Parts Copyright : Ian Molton <spyro@f2s.com> + * Andrew Zabolotny <zap@homelink.ru> + * Russell King <rmk@arm.linux.org.uk> + * + * 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. + * + * Notes: + * + * Features: + * - supports WM9705, WM9712, WM9713 + * - polling mode + * - continuous mode (arch-dependent) + * - adjustable rpu/dpp settings + * - adjustable pressure current + * - adjustable sample settle delay + * - 4 and 5 wire touchscreens (5 wire is WM9712 only) + * - pen down detection + * - battery monitor + * - sample AUX adcs + * - power management + * - codec GPIO + * - codec event notification + * Todo + * - Support for async sampling control for noisy LCDs. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/proc_fs.h> +#include <linux/pm.h> +#include <linux/interrupt.h> +#include <linux/bitops.h> +#include <linux/workqueue.h> +#include <linux/wm97xx.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#define TS_NAME "wm97xx" +#define WM_CORE_VERSION "1.00" +#define DEFAULT_PRESSURE 0xb0c0 + + +/* + * Touchscreen absolute values + * + * These parameters are used to help the input layer discard out of + * range readings and reduce jitter etc. + * + * o min, max:- indicate the min and max values your touch screen returns + * o fuzz:- use a higher number to reduce jitter + * + * The default values correspond to Mainstone II in QVGA mode + * + * Please read + * Documentation/input/input-programming.txt for more details. + */ + +static int abs_x[3] = {350, 3900, 5}; +module_param_array(abs_x, int, NULL, 0); +MODULE_PARM_DESC(abs_x, "Touchscreen absolute X min, max, fuzz"); + +static int abs_y[3] = {320, 3750, 40}; +module_param_array(abs_y, int, NULL, 0); +MODULE_PARM_DESC(abs_y, "Touchscreen absolute Y min, max, fuzz"); + +static int abs_p[3] = {0, 150, 4}; +module_param_array(abs_p, int, NULL, 0); +MODULE_PARM_DESC(abs_p, "Touchscreen absolute Pressure min, max, fuzz"); + +/* + * wm97xx IO access, all IO locking done by AC97 layer + */ +int wm97xx_reg_read(struct wm97xx *wm, u16 reg) +{ + if (wm->ac97) + return wm->ac97->bus->ops->read(wm->ac97, reg); + else + return -1; +} +EXPORT_SYMBOL_GPL(wm97xx_reg_read); + +void wm97xx_reg_write(struct wm97xx *wm, u16 reg, u16 val) +{ + /* cache digitiser registers */ + if (reg >= AC97_WM9713_DIG1 && reg <= AC97_WM9713_DIG3) + wm->dig[(reg - AC97_WM9713_DIG1) >> 1] = val; + + /* cache gpio regs */ + if (reg >= AC97_GPIO_CFG && reg <= AC97_MISC_AFE) + wm->gpio[(reg - AC97_GPIO_CFG) >> 1] = val; + + /* wm9713 irq reg */ + if (reg == 0x5a) + wm->misc = val; + + if (wm->ac97) + wm->ac97->bus->ops->write(wm->ac97, reg, val); +} +EXPORT_SYMBOL_GPL(wm97xx_reg_write); + +/** + * wm97xx_read_aux_adc - Read the aux adc. + * @wm: wm97xx device. + * @adcsel: codec ADC to be read + * + * Reads the selected AUX ADC. + */ + +int wm97xx_read_aux_adc(struct wm97xx *wm, u16 adcsel) +{ + int power_adc = 0, auxval; + u16 power = 0; + + /* get codec */ + mutex_lock(&wm->codec_mutex); + + /* When the touchscreen is not in use, we may have to power up + * the AUX ADC before we can use sample the AUX inputs-> + */ + if (wm->id == WM9713_ID2 && + (power = wm97xx_reg_read(wm, AC97_EXTENDED_MID)) & 0x8000) { + power_adc = 1; + wm97xx_reg_write(wm, AC97_EXTENDED_MID, power & 0x7fff); + } + + /* Prepare the codec for AUX reading */ + wm->codec->aux_prepare(wm); + + /* Turn polling mode on to read AUX ADC */ + wm->pen_probably_down = 1; + wm->codec->poll_sample(wm, adcsel, &auxval); + + if (power_adc) + wm97xx_reg_write(wm, AC97_EXTENDED_MID, power | 0x8000); + + wm->codec->dig_restore(wm); + + wm->pen_probably_down = 0; + + mutex_unlock(&wm->codec_mutex); + return auxval & 0xfff; +} +EXPORT_SYMBOL_GPL(wm97xx_read_aux_adc); + +/** + * wm97xx_get_gpio - Get the status of a codec GPIO. + * @wm: wm97xx device. + * @gpio: gpio + * + * Get the status of a codec GPIO pin + */ + +enum wm97xx_gpio_status wm97xx_get_gpio(struct wm97xx *wm, u32 gpio) +{ + u16 status; + enum wm97xx_gpio_status ret; + + mutex_lock(&wm->codec_mutex); + status = wm97xx_reg_read(wm, AC97_GPIO_STATUS); + + if (status & gpio) + ret = WM97XX_GPIO_HIGH; + else + ret = WM97XX_GPIO_LOW; + + mutex_unlock(&wm->codec_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(wm97xx_get_gpio); + +/** + * wm97xx_set_gpio - Set the status of a codec GPIO. + * @wm: wm97xx device. + * @gpio: gpio + * + * + * Set the status of a codec GPIO pin + */ + +void wm97xx_set_gpio(struct wm97xx *wm, u32 gpio, + enum wm97xx_gpio_status status) +{ + u16 reg; + + mutex_lock(&wm->codec_mutex); + reg = wm97xx_reg_read(wm, AC97_GPIO_STATUS); + + if (status & WM97XX_GPIO_HIGH) + reg |= gpio; + else + reg &= ~gpio; + + if (wm->id == WM9712_ID2) + wm97xx_reg_write(wm, AC97_GPIO_STATUS, reg << 1); + else + wm97xx_reg_write(wm, AC97_GPIO_STATUS, reg); + mutex_unlock(&wm->codec_mutex); +} +EXPORT_SYMBOL_GPL(wm97xx_set_gpio); + +/* + * Codec GPIO pin configuration, this sets pin direction, polarity, + * stickyness and wake up. + */ +void wm97xx_config_gpio(struct wm97xx *wm, u32 gpio, enum wm97xx_gpio_dir dir, + enum wm97xx_gpio_pol pol, enum wm97xx_gpio_sticky sticky, + enum wm97xx_gpio_wake wake) +{ + u16 reg; + + mutex_lock(&wm->codec_mutex); + reg = wm97xx_reg_read(wm, AC97_GPIO_POLARITY); + + if (pol == WM97XX_GPIO_POL_HIGH) + reg |= gpio; + else + reg &= ~gpio; + + wm97xx_reg_write(wm, AC97_GPIO_POLARITY, reg); + reg = wm97xx_reg_read(wm, AC97_GPIO_STICKY); + + if (sticky == WM97XX_GPIO_STICKY) + reg |= gpio; + else + reg &= ~gpio; + + wm97xx_reg_write(wm, AC97_GPIO_STICKY, reg); + reg = wm97xx_reg_read(wm, AC97_GPIO_WAKEUP); + + if (wake == WM97XX_GPIO_WAKE) + reg |= gpio; + else + reg &= ~gpio; + + wm97xx_reg_write(wm, AC97_GPIO_WAKEUP, reg); + reg = wm97xx_reg_read(wm, AC97_GPIO_CFG); + + if (dir == WM97XX_GPIO_IN) + reg |= gpio; + else + reg &= ~gpio; + + wm97xx_reg_write(wm, AC97_GPIO_CFG, reg); + mutex_unlock(&wm->codec_mutex); +} +EXPORT_SYMBOL_GPL(wm97xx_config_gpio); + +/* + * Configure the WM97XX_PRP value to use while system is suspended. + * If a value other than 0 is set then WM97xx pen detection will be + * left enabled in the configured mode while the system is in suspend, + * the device has users and suspend has not been disabled via the + * wakeup sysfs entries. + * + * @wm: WM97xx device to configure + * @mode: WM97XX_PRP value to configure while suspended + */ +void wm97xx_set_suspend_mode(struct wm97xx *wm, u16 mode) +{ + wm->suspend_mode = mode; + device_init_wakeup(&wm->input_dev->dev, mode != 0); +} +EXPORT_SYMBOL_GPL(wm97xx_set_suspend_mode); + +/* + * Handle a pen down interrupt. + */ +static void wm97xx_pen_irq_worker(struct work_struct *work) +{ + struct wm97xx *wm = container_of(work, struct wm97xx, pen_event_work); + int pen_was_down = wm->pen_is_down; + + /* do we need to enable the touch panel reader */ + if (wm->id == WM9705_ID2) { + if (wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER_RD) & + WM97XX_PEN_DOWN) + wm->pen_is_down = 1; + else + wm->pen_is_down = 0; + } else { + u16 status, pol; + mutex_lock(&wm->codec_mutex); + status = wm97xx_reg_read(wm, AC97_GPIO_STATUS); + pol = wm97xx_reg_read(wm, AC97_GPIO_POLARITY); + + if (WM97XX_GPIO_13 & pol & status) { + wm->pen_is_down = 1; + wm97xx_reg_write(wm, AC97_GPIO_POLARITY, pol & + ~WM97XX_GPIO_13); + } else { + wm->pen_is_down = 0; + wm97xx_reg_write(wm, AC97_GPIO_POLARITY, pol | + WM97XX_GPIO_13); + } + + if (wm->id == WM9712_ID2) + wm97xx_reg_write(wm, AC97_GPIO_STATUS, (status & + ~WM97XX_GPIO_13) << 1); + else + wm97xx_reg_write(wm, AC97_GPIO_STATUS, status & + ~WM97XX_GPIO_13); + mutex_unlock(&wm->codec_mutex); + } + + /* If the system is not using continuous mode or it provides a + * pen down operation then we need to schedule polls while the + * pen is down. Otherwise the machine driver is responsible + * for scheduling reads. + */ + if (!wm->mach_ops->acc_enabled || wm->mach_ops->acc_pen_down) { + if (wm->pen_is_down && !pen_was_down) { + /* Data is not availiable immediately on pen down */ + queue_delayed_work(wm->ts_workq, &wm->ts_reader, 1); + } + + /* Let ts_reader report the pen up for debounce. */ + if (!wm->pen_is_down && pen_was_down) + wm->pen_is_down = 1; + } + + if (!wm->pen_is_down && wm->mach_ops->acc_enabled) + wm->mach_ops->acc_pen_up(wm); + + wm->mach_ops->irq_enable(wm, 1); +} + +/* + * Codec PENDOWN irq handler + * + * We have to disable the codec interrupt in the handler because it + * can take upto 1ms to clear the interrupt source. We schedule a task + * in a work queue to do the actual interaction with the chip. The + * interrupt is then enabled again in the slow handler when the source + * has been cleared. + */ +static irqreturn_t wm97xx_pen_interrupt(int irq, void *dev_id) +{ + struct wm97xx *wm = dev_id; + + if (!work_pending(&wm->pen_event_work)) { + wm->mach_ops->irq_enable(wm, 0); + queue_work(wm->ts_workq, &wm->pen_event_work); + } + + return IRQ_HANDLED; +} + +/* + * initialise pen IRQ handler and workqueue + */ +static int wm97xx_init_pen_irq(struct wm97xx *wm) +{ + u16 reg; + + /* If an interrupt is supplied an IRQ enable operation must also be + * provided. */ + BUG_ON(!wm->mach_ops->irq_enable); + + if (request_irq(wm->pen_irq, wm97xx_pen_interrupt, + IRQF_SHARED | IRQF_SAMPLE_RANDOM, + "wm97xx-pen", wm)) { + dev_err(wm->dev, + "Failed to register pen down interrupt, polling"); + wm->pen_irq = 0; + return -EINVAL; + } + + /* Configure GPIO as interrupt source on WM971x */ + if (wm->id != WM9705_ID2) { + BUG_ON(!wm->mach_ops->irq_gpio); + reg = wm97xx_reg_read(wm, AC97_MISC_AFE); + wm97xx_reg_write(wm, AC97_MISC_AFE, + reg & ~(wm->mach_ops->irq_gpio)); + reg = wm97xx_reg_read(wm, 0x5a); + wm97xx_reg_write(wm, 0x5a, reg & ~0x0001); + } + + return 0; +} + +static int wm97xx_read_samples(struct wm97xx *wm) +{ + struct wm97xx_data data; + int rc; + + mutex_lock(&wm->codec_mutex); + + if (wm->mach_ops && wm->mach_ops->acc_enabled) + rc = wm->mach_ops->acc_pen_down(wm); + else + rc = wm->codec->poll_touch(wm, &data); + + if (rc & RC_PENUP) { + if (wm->pen_is_down) { + wm->pen_is_down = 0; + dev_dbg(wm->dev, "pen up\n"); + input_report_abs(wm->input_dev, ABS_PRESSURE, 0); + input_sync(wm->input_dev); + } else if (!(rc & RC_AGAIN)) { + /* We need high frequency updates only while + * pen is down, the user never will be able to + * touch screen faster than a few times per + * second... On the other hand, when the user + * is actively working with the touchscreen we + * don't want to lose the quick response. So we + * will slowly increase sleep time after the + * pen is up and quicky restore it to ~one task + * switch when pen is down again. + */ + if (wm->ts_reader_interval < HZ / 10) + wm->ts_reader_interval++; + } + + } else if (rc & RC_VALID) { + dev_dbg(wm->dev, + "pen down: x=%x:%d, y=%x:%d, pressure=%x:%d\n", + data.x >> 12, data.x & 0xfff, data.y >> 12, + data.y & 0xfff, data.p >> 12, data.p & 0xfff); + input_report_abs(wm->input_dev, ABS_X, data.x & 0xfff); + input_report_abs(wm->input_dev, ABS_Y, data.y & 0xfff); + input_report_abs(wm->input_dev, ABS_PRESSURE, data.p & 0xfff); + input_sync(wm->input_dev); + wm->pen_is_down = 1; + wm->ts_reader_interval = wm->ts_reader_min_interval; + } else if (rc & RC_PENDOWN) { + dev_dbg(wm->dev, "pen down\n"); + wm->pen_is_down = 1; + wm->ts_reader_interval = wm->ts_reader_min_interval; + } + + mutex_unlock(&wm->codec_mutex); + return rc; +} + +/* +* The touchscreen sample reader. +*/ +static void wm97xx_ts_reader(struct work_struct *work) +{ + int rc; + struct wm97xx *wm = container_of(work, struct wm97xx, ts_reader.work); + + BUG_ON(!wm->codec); + + do { + rc = wm97xx_read_samples(wm); + } while (rc & RC_AGAIN); + + if (wm->pen_is_down || !wm->pen_irq) + queue_delayed_work(wm->ts_workq, &wm->ts_reader, + wm->ts_reader_interval); +} + +/** + * wm97xx_ts_input_open - Open the touch screen input device. + * @idev: Input device to be opened. + * + * Called by the input sub system to open a wm97xx touchscreen device. + * Starts the touchscreen thread and touch digitiser. + */ +static int wm97xx_ts_input_open(struct input_dev *idev) +{ + struct wm97xx *wm = input_get_drvdata(idev); + + wm->ts_workq = create_singlethread_workqueue("kwm97xx"); + if (wm->ts_workq == NULL) { + dev_err(wm->dev, + "Failed to create workqueue\n"); + return -EINVAL; + } + + /* start digitiser */ + if (wm->mach_ops && wm->mach_ops->acc_enabled) + wm->codec->acc_enable(wm, 1); + wm->codec->dig_enable(wm, 1); + + INIT_DELAYED_WORK(&wm->ts_reader, wm97xx_ts_reader); + INIT_WORK(&wm->pen_event_work, wm97xx_pen_irq_worker); + + wm->ts_reader_min_interval = HZ >= 100 ? HZ / 100 : 1; + if (wm->ts_reader_min_interval < 1) + wm->ts_reader_min_interval = 1; + wm->ts_reader_interval = wm->ts_reader_min_interval; + + wm->pen_is_down = 0; + if (wm->pen_irq) + wm97xx_init_pen_irq(wm); + else + dev_err(wm->dev, "No IRQ specified\n"); + + /* If we either don't have an interrupt for pen down events or + * failed to acquire it then we need to poll. + */ + if (wm->pen_irq == 0) + queue_delayed_work(wm->ts_workq, &wm->ts_reader, + wm->ts_reader_interval); + + return 0; +} + +/** + * wm97xx_ts_input_close - Close the touch screen input device. + * @idev: Input device to be closed. + * + * Called by the input sub system to close a wm97xx touchscreen + * device. Kills the touchscreen thread and stops the touch + * digitiser. + */ + +static void wm97xx_ts_input_close(struct input_dev *idev) +{ + struct wm97xx *wm = input_get_drvdata(idev); + u16 reg; + + if (wm->pen_irq) { + /* Return the interrupt to GPIO usage (disabling it) */ + if (wm->id != WM9705_ID2) { + BUG_ON(!wm->mach_ops->irq_gpio); + reg = wm97xx_reg_read(wm, AC97_MISC_AFE); + wm97xx_reg_write(wm, AC97_MISC_AFE, + reg | wm->mach_ops->irq_gpio); + } + + free_irq(wm->pen_irq, wm); + } + + wm->pen_is_down = 0; + + /* Balance out interrupt disables/enables */ + if (cancel_work_sync(&wm->pen_event_work)) + wm->mach_ops->irq_enable(wm, 1); + + /* ts_reader rearms itself so we need to explicitly stop it + * before we destroy the workqueue. + */ + cancel_delayed_work_sync(&wm->ts_reader); + + destroy_workqueue(wm->ts_workq); + + /* stop digitiser */ + wm->codec->dig_enable(wm, 0); + if (wm->mach_ops && wm->mach_ops->acc_enabled) + wm->codec->acc_enable(wm, 0); +} + +static int wm97xx_probe(struct device *dev) +{ + struct wm97xx *wm; + int ret = 0, id = 0; + + wm = kzalloc(sizeof(struct wm97xx), GFP_KERNEL); + if (!wm) + return -ENOMEM; + mutex_init(&wm->codec_mutex); + + wm->dev = dev; + dev->driver_data = wm; + wm->ac97 = to_ac97_t(dev); + + /* check that we have a supported codec */ + id = wm97xx_reg_read(wm, AC97_VENDOR_ID1); + if (id != WM97XX_ID1) { + dev_err(dev, "Device with vendor %04x is not a wm97xx\n", id); + ret = -ENODEV; + goto alloc_err; + } + + wm->id = wm97xx_reg_read(wm, AC97_VENDOR_ID2); + + dev_info(wm->dev, "detected a wm97%02x codec\n", wm->id & 0xff); + + switch (wm->id & 0xff) { +#ifdef CONFIG_TOUCHSCREEN_WM9705 + case 0x05: + wm->codec = &wm9705_codec; + break; +#endif +#ifdef CONFIG_TOUCHSCREEN_WM9712 + case 0x12: + wm->codec = &wm9712_codec; + break; +#endif +#ifdef CONFIG_TOUCHSCREEN_WM9713 + case 0x13: + wm->codec = &wm9713_codec; + break; +#endif + default: + dev_err(wm->dev, "Support for wm97%02x not compiled in.\n", + wm->id & 0xff); + ret = -ENODEV; + goto alloc_err; + } + + /* set up physical characteristics */ + wm->codec->phy_init(wm); + + /* load gpio cache */ + wm->gpio[0] = wm97xx_reg_read(wm, AC97_GPIO_CFG); + wm->gpio[1] = wm97xx_reg_read(wm, AC97_GPIO_POLARITY); + wm->gpio[2] = wm97xx_reg_read(wm, AC97_GPIO_STICKY); + wm->gpio[3] = wm97xx_reg_read(wm, AC97_GPIO_WAKEUP); + wm->gpio[4] = wm97xx_reg_read(wm, AC97_GPIO_STATUS); + wm->gpio[5] = wm97xx_reg_read(wm, AC97_MISC_AFE); + + wm->input_dev = input_allocate_device(); + if (wm->input_dev == NULL) { + ret = -ENOMEM; + goto alloc_err; + } + + /* set up touch configuration */ + wm->input_dev->name = "wm97xx touchscreen"; + wm->input_dev->phys = "wm97xx"; + wm->input_dev->open = wm97xx_ts_input_open; + wm->input_dev->close = wm97xx_ts_input_close; + set_bit(EV_ABS, wm->input_dev->evbit); + set_bit(ABS_X, wm->input_dev->absbit); + set_bit(ABS_Y, wm->input_dev->absbit); + set_bit(ABS_PRESSURE, wm->input_dev->absbit); + input_set_abs_params(wm->input_dev, ABS_X, abs_x[0], abs_x[1], + abs_x[2], 0); + input_set_abs_params(wm->input_dev, ABS_Y, abs_y[0], abs_y[1], + abs_y[2], 0); + input_set_abs_params(wm->input_dev, ABS_PRESSURE, abs_p[0], abs_p[1], + abs_p[2], 0); + input_set_drvdata(wm->input_dev, wm); + wm->input_dev->dev.parent = dev; + ret = input_register_device(wm->input_dev); + if (ret < 0) + goto dev_alloc_err; + + /* register our battery device */ + wm->battery_dev = platform_device_alloc("wm97xx-battery", -1); + if (!wm->battery_dev) { + ret = -ENOMEM; + goto batt_err; + } + platform_set_drvdata(wm->battery_dev, wm); + wm->battery_dev->dev.parent = dev; + ret = platform_device_add(wm->battery_dev); + if (ret < 0) + goto batt_reg_err; + + /* register our extended touch device (for machine specific + * extensions) */ + wm->touch_dev = platform_device_alloc("wm97xx-touch", -1); + if (!wm->touch_dev) { + ret = -ENOMEM; + goto touch_err; + } + platform_set_drvdata(wm->touch_dev, wm); + wm->touch_dev->dev.parent = dev; + ret = platform_device_add(wm->touch_dev); + if (ret < 0) + goto touch_reg_err; + + return ret; + + touch_reg_err: + platform_device_put(wm->touch_dev); + touch_err: + platform_device_unregister(wm->battery_dev); + wm->battery_dev = NULL; + batt_reg_err: + platform_device_put(wm->battery_dev); + batt_err: + input_unregister_device(wm->input_dev); + wm->input_dev = NULL; + dev_alloc_err: + input_free_device(wm->input_dev); + alloc_err: + kfree(wm); + + return ret; +} + +static int wm97xx_remove(struct device *dev) +{ + struct wm97xx *wm = dev_get_drvdata(dev); + + platform_device_unregister(wm->battery_dev); + platform_device_unregister(wm->touch_dev); + input_unregister_device(wm->input_dev); + kfree(wm); + + return 0; +} + +#ifdef CONFIG_PM +static int wm97xx_suspend(struct device *dev, pm_message_t state) +{ + struct wm97xx *wm = dev_get_drvdata(dev); + u16 reg; + int suspend_mode; + + if (device_may_wakeup(&wm->input_dev->dev)) + suspend_mode = wm->suspend_mode; + else + suspend_mode = 0; + + if (wm->input_dev->users) + cancel_delayed_work_sync(&wm->ts_reader); + + /* Power down the digitiser (bypassing the cache for resume) */ + reg = wm97xx_reg_read(wm, AC97_WM97XX_DIGITISER2); + reg &= ~WM97XX_PRP_DET_DIG; + if (wm->input_dev->users) + reg |= suspend_mode; + wm->ac97->bus->ops->write(wm->ac97, AC97_WM97XX_DIGITISER2, reg); + + /* WM9713 has an additional power bit - turn it off if there + * are no users or if suspend mode is zero. */ + if (wm->id == WM9713_ID2 && + (!wm->input_dev->users || !suspend_mode)) { + reg = wm97xx_reg_read(wm, AC97_EXTENDED_MID) | 0x8000; + wm97xx_reg_write(wm, AC97_EXTENDED_MID, reg); + } + + return 0; +} + +static int wm97xx_resume(struct device *dev) +{ + struct wm97xx *wm = dev_get_drvdata(dev); + + /* restore digitiser and gpios */ + if (wm->id == WM9713_ID2) { + wm97xx_reg_write(wm, AC97_WM9713_DIG1, wm->dig[0]); + wm97xx_reg_write(wm, 0x5a, wm->misc); + if (wm->input_dev->users) { + u16 reg; + reg = wm97xx_reg_read(wm, AC97_EXTENDED_MID) & 0x7fff; + wm97xx_reg_write(wm, AC97_EXTENDED_MID, reg); + } + } + + wm97xx_reg_write(wm, AC97_WM9713_DIG2, wm->dig[1]); + wm97xx_reg_write(wm, AC97_WM9713_DIG3, wm->dig[2]); + + wm97xx_reg_write(wm, AC97_GPIO_CFG, wm->gpio[0]); + wm97xx_reg_write(wm, AC97_GPIO_POLARITY, wm->gpio[1]); + wm97xx_reg_write(wm, AC97_GPIO_STICKY, wm->gpio[2]); + wm97xx_reg_write(wm, AC97_GPIO_WAKEUP, wm->gpio[3]); + wm97xx_reg_write(wm, AC97_GPIO_STATUS, wm->gpio[4]); + wm97xx_reg_write(wm, AC97_MISC_AFE, wm->gpio[5]); + + if (wm->input_dev->users && !wm->pen_irq) { + wm->ts_reader_interval = wm->ts_reader_min_interval; + queue_delayed_work(wm->ts_workq, &wm->ts_reader, + wm->ts_reader_interval); + } + + return 0; +} + +#else +#define wm97xx_suspend NULL +#define wm97xx_resume NULL +#endif + +/* + * Machine specific operations + */ +int wm97xx_register_mach_ops(struct wm97xx *wm, + struct wm97xx_mach_ops *mach_ops) +{ + mutex_lock(&wm->codec_mutex); + if (wm->mach_ops) { + mutex_unlock(&wm->codec_mutex); + return -EINVAL; + } + wm->mach_ops = mach_ops; + mutex_unlock(&wm->codec_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(wm97xx_register_mach_ops); + +void wm97xx_unregister_mach_ops(struct wm97xx *wm) +{ + mutex_lock(&wm->codec_mutex); + wm->mach_ops = NULL; + mutex_unlock(&wm->codec_mutex); +} +EXPORT_SYMBOL_GPL(wm97xx_unregister_mach_ops); + +static struct device_driver wm97xx_driver = { + .name = "wm97xx-ts", + .bus = &ac97_bus_type, + .owner = THIS_MODULE, + .probe = wm97xx_probe, + .remove = wm97xx_remove, + .suspend = wm97xx_suspend, + .resume = wm97xx_resume, +}; + +static int __init wm97xx_init(void) +{ + return driver_register(&wm97xx_driver); +} + +static void __exit wm97xx_exit(void) +{ + driver_unregister(&wm97xx_driver); +} + +module_init(wm97xx_init); +module_exit(wm97xx_exit); + +/* Module information */ +MODULE_AUTHOR("Liam Girdwood <lrg@slimlogic.co.uk>"); +MODULE_DESCRIPTION("WM97xx Core - Touch Screen / AUX ADC / GPIO Driver"); +MODULE_LICENSE("GPL"); |