add idl4k kernel firmware version 1.13.0.105

This commit is contained in:
Jaroslav Kysela
2015-03-26 17:22:37 +01:00
parent 5194d2792e
commit e9070cdc77
31064 changed files with 12769984 additions and 0 deletions

View File

@@ -0,0 +1,533 @@
#
# 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_AD7877
tristate "AD7877 based touchscreens"
depends on SPI_MASTER
help
Say Y here if you have a touchscreen interface using the
AD7877 controller, and your board-specific initialization
code includes that in its table of SPI devices.
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 ad7877.
config TOUCHSCREEN_AD7879_I2C
tristate "AD7879 based touchscreens: AD7879-1 I2C Interface"
depends on I2C
select TOUCHSCREEN_AD7879
help
Say Y here if you have a touchscreen interface using the
AD7879-1/AD7889-1 controller, and your board-specific
initialization code includes that in its table of I2C devices.
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 ad7879.
config TOUCHSCREEN_AD7879_SPI
tristate "AD7879 based touchscreens: AD7879 SPI Interface"
depends on SPI_MASTER && TOUCHSCREEN_AD7879_I2C = n
select TOUCHSCREEN_AD7879
help
Say Y here if you have a touchscreen interface using the
AD7879/AD7889 controller, and your board-specific initialization
code includes that in its table of SPI devices.
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 ad7879.
config TOUCHSCREEN_AD7879
tristate
default n
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_DA9034
tristate "Touchscreen support for Dialog Semiconductor DA9034"
depends on PMIC_DA903X
default y
help
Say Y here to enable the support for the touchscreen found
on Dialog Semiconductor DA9034 PMIC.
config TOUCHSCREEN_EETI
tristate "EETI touchscreen panel support"
depends on I2C
help
Say Y here to enable support for I2C connected EETI touch panels.
To compile this driver as a module, choose M here: the
module will be called eeti_ts.
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_WACOM_W8001
tristate "Wacom W8001 penabled serial touchscreen"
select SERIO
help
Say Y here if you have an Wacom W8001 penabled 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 wacom_w8001.
config TOUCHSCREEN_MCS5000
tristate "MELFAS MCS-5000 touchscreen"
depends on I2C
help
Say Y here if you have the MELFAS MCS-5000 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 mcs5000_ts.
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_ATMEL
tristate "WM97xx Atmel accelerated touch"
depends on TOUCHSCREEN_WM97XX && (AVR32 || ARCH_AT91)
help
Say Y here for support for streaming mode with WM97xx touchscreens
on Atmel AT91 or AVR32 systems with an AC97C module.
Be aware that this will use channel B in the controller for
streaming data, this must not conflict with other AC97C drivers.
If unsure, say N.
To compile this driver as a module, choose M here: the module will
be called atmel-wm97xx.
config TOUCHSCREEN_WM97XX_MAINSTONE
tristate "WM97xx Mainstone/Palm accelerated touch"
depends on TOUCHSCREEN_WM97XX && ARCH_PXA
help
Say Y here for support for streaming mode with WM97xx touchscreens
on Mainstone, Palm Tungsten T5, TX and LifeDrive systems.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called mainstone-wm97xx.
config TOUCHSCREEN_WM97XX_ZYLONITE
tristate "Zylonite accelerated touch"
depends on TOUCHSCREEN_WM97XX && MACH_ZYLONITE
select TOUCHSCREEN_WM9713
help
Say Y here for support for streaming mode with the touchscreen
on Zylonite systems.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called zylonite-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
- JASTEC USB Touch Controller/DigiTech DTR-02U
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_USB_JASTEC
default y
bool "JASTEC/DigiTech DTR-02U USB touch controller device support" if EMBEDDED
depends on TOUCHSCREEN_USB_COMPOSITE
config TOUCHSCREEN_USB_E2I
default y
bool "e2i Touchscreen controller (e.g. from Mimo 740)"
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.
config TOUCHSCREEN_TSC2007
tristate "TSC2007 based touchscreens"
depends on I2C
help
Say Y here if you have a TSC2007 based touchscreen.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called tsc2007.
config TOUCHSCREEN_W90X900
tristate "W90P910 touchscreen driver"
depends on HAVE_CLK
help
Say Y here if you have a W90P910 based touchscreen.
To compile this driver as a module, choose M here: the
module will be called w90p910_ts.
config TOUCHSCREEN_PCAP
tristate "Motorola PCAP touchscreen"
depends on EZX_PCAP
help
Say Y here if you have a Motorola EZX telephone and
want to enable support for the built-in touchscreen.
To compile this driver as a module, choose M here: the
module will be called pcap_ts.
endif

View File

@@ -0,0 +1,44 @@
#
# Makefile for the touchscreen drivers.
#
# Each configuration option enables a list of files.
wm97xx-ts-y := wm97xx-core.o
obj-$(CONFIG_TOUCHSCREEN_AD7877) += ad7877.o
obj-$(CONFIG_TOUCHSCREEN_AD7879) += ad7879.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_EETI) += eeti_ts.o
obj-$(CONFIG_TOUCHSCREEN_ELO) += elo.o
obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o
obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o
obj-$(CONFIG_TOUCHSCREEN_MCS5000) += mcs5000_ts.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_TSC2007) += tsc2007.o
obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o
obj-$(CONFIG_TOUCHSCREEN_WACOM_W8001) += wacom_w8001.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o
obj-$(CONFIG_TOUCHSCREEN_DA9034) += da9034-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_ATMEL) += atmel-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o
obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o

View File

@@ -0,0 +1,845 @@
/*
* Copyright (C) 2006-2008 Michael Hennerich, Analog Devices Inc.
*
* Description: AD7877 based touchscreen, sensor (ADCs), DAC and GPIO driver
* Based on: ads7846.c
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* 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, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* History:
* 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
*/
#include <linux/device.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/spi/ad7877.h>
#include <asm/irq.h>
#define TS_PEN_UP_TIMEOUT msecs_to_jiffies(50)
#define MAX_SPI_FREQ_HZ 20000000
#define MAX_12BIT ((1<<12)-1)
#define AD7877_REG_ZEROS 0
#define AD7877_REG_CTRL1 1
#define AD7877_REG_CTRL2 2
#define AD7877_REG_ALERT 3
#define AD7877_REG_AUX1HIGH 4
#define AD7877_REG_AUX1LOW 5
#define AD7877_REG_BAT1HIGH 6
#define AD7877_REG_BAT1LOW 7
#define AD7877_REG_BAT2HIGH 8
#define AD7877_REG_BAT2LOW 9
#define AD7877_REG_TEMP1HIGH 10
#define AD7877_REG_TEMP1LOW 11
#define AD7877_REG_SEQ0 12
#define AD7877_REG_SEQ1 13
#define AD7877_REG_DAC 14
#define AD7877_REG_NONE1 15
#define AD7877_REG_EXTWRITE 15
#define AD7877_REG_XPLUS 16
#define AD7877_REG_YPLUS 17
#define AD7877_REG_Z2 18
#define AD7877_REG_aux1 19
#define AD7877_REG_aux2 20
#define AD7877_REG_aux3 21
#define AD7877_REG_bat1 22
#define AD7877_REG_bat2 23
#define AD7877_REG_temp1 24
#define AD7877_REG_temp2 25
#define AD7877_REG_Z1 26
#define AD7877_REG_GPIOCTRL1 27
#define AD7877_REG_GPIOCTRL2 28
#define AD7877_REG_GPIODATA 29
#define AD7877_REG_NONE2 30
#define AD7877_REG_NONE3 31
#define AD7877_SEQ_YPLUS_BIT (1<<11)
#define AD7877_SEQ_XPLUS_BIT (1<<10)
#define AD7877_SEQ_Z2_BIT (1<<9)
#define AD7877_SEQ_AUX1_BIT (1<<8)
#define AD7877_SEQ_AUX2_BIT (1<<7)
#define AD7877_SEQ_AUX3_BIT (1<<6)
#define AD7877_SEQ_BAT1_BIT (1<<5)
#define AD7877_SEQ_BAT2_BIT (1<<4)
#define AD7877_SEQ_TEMP1_BIT (1<<3)
#define AD7877_SEQ_TEMP2_BIT (1<<2)
#define AD7877_SEQ_Z1_BIT (1<<1)
enum {
AD7877_SEQ_YPOS = 0,
AD7877_SEQ_XPOS = 1,
AD7877_SEQ_Z2 = 2,
AD7877_SEQ_AUX1 = 3,
AD7877_SEQ_AUX2 = 4,
AD7877_SEQ_AUX3 = 5,
AD7877_SEQ_BAT1 = 6,
AD7877_SEQ_BAT2 = 7,
AD7877_SEQ_TEMP1 = 8,
AD7877_SEQ_TEMP2 = 9,
AD7877_SEQ_Z1 = 10,
AD7877_NR_SENSE = 11,
};
/* DAC Register Default RANGE 0 to Vcc, Volatge Mode, DAC On */
#define AD7877_DAC_CONF 0x1
/* If gpio3 is set AUX3/GPIO3 acts as GPIO Output */
#define AD7877_EXTW_GPIO_3_CONF 0x1C4
#define AD7877_EXTW_GPIO_DATA 0x200
/* Control REG 2 */
#define AD7877_TMR(x) ((x & 0x3) << 0)
#define AD7877_REF(x) ((x & 0x1) << 2)
#define AD7877_POL(x) ((x & 0x1) << 3)
#define AD7877_FCD(x) ((x & 0x3) << 4)
#define AD7877_PM(x) ((x & 0x3) << 6)
#define AD7877_ACQ(x) ((x & 0x3) << 8)
#define AD7877_AVG(x) ((x & 0x3) << 10)
/* Control REG 1 */
#define AD7877_SER (1 << 11) /* non-differential */
#define AD7877_DFR (0 << 11) /* differential */
#define AD7877_MODE_NOC (0) /* Do not convert */
#define AD7877_MODE_SCC (1) /* Single channel conversion */
#define AD7877_MODE_SEQ0 (2) /* Sequence 0 in Slave Mode */
#define AD7877_MODE_SEQ1 (3) /* Sequence 1 in Master Mode */
#define AD7877_CHANADD(x) ((x&0xF)<<7)
#define AD7877_READADD(x) ((x)<<2)
#define AD7877_WRITEADD(x) ((x)<<12)
#define AD7877_READ_CHAN(x) (AD7877_WRITEADD(AD7877_REG_CTRL1) | AD7877_SER | \
AD7877_MODE_SCC | AD7877_CHANADD(AD7877_REG_ ## x) | \
AD7877_READADD(AD7877_REG_ ## x))
#define AD7877_MM_SEQUENCE (AD7877_SEQ_YPLUS_BIT | AD7877_SEQ_XPLUS_BIT | \
AD7877_SEQ_Z2_BIT | AD7877_SEQ_Z1_BIT)
/*
* Non-touchscreen sensors only use single-ended conversions.
*/
struct ser_req {
u16 reset;
u16 ref_on;
u16 command;
u16 sample;
struct spi_message msg;
struct spi_transfer xfer[6];
};
struct ad7877 {
struct input_dev *input;
char phys[32];
struct spi_device *spi;
u16 model;
u16 vref_delay_usecs;
u16 x_plate_ohms;
u16 pressure_max;
u16 cmd_crtl1;
u16 cmd_crtl2;
u16 cmd_dummy;
u16 dac;
u8 stopacq_polarity;
u8 first_conversion_delay;
u8 acquisition_time;
u8 averaging;
u8 pen_down_acc_interval;
u16 conversion_data[AD7877_NR_SENSE];
struct spi_transfer xfer[AD7877_NR_SENSE + 2];
struct spi_message msg;
struct mutex mutex;
unsigned disabled:1; /* P: mutex */
unsigned gpio3:1; /* P: mutex */
unsigned gpio4:1; /* P: mutex */
spinlock_t lock;
struct timer_list timer; /* P: lock */
unsigned pending:1; /* P: lock */
};
static int gpio3;
module_param(gpio3, int, 0);
MODULE_PARM_DESC(gpio3, "If gpio3 is set to 1 AUX3 acts as GPIO3");
/*
* ad7877_read/write are only used for initial setup and for sysfs controls.
* The main traffic is done using spi_async() in the interrupt handler.
*/
static int ad7877_read(struct spi_device *spi, u16 reg)
{
struct ser_req *req;
int status, ret;
req = kzalloc(sizeof *req, GFP_KERNEL);
if (!req)
return -ENOMEM;
spi_message_init(&req->msg);
req->command = (u16) (AD7877_WRITEADD(AD7877_REG_CTRL1) |
AD7877_READADD(reg));
req->xfer[0].tx_buf = &req->command;
req->xfer[0].len = 2;
req->xfer[1].rx_buf = &req->sample;
req->xfer[1].len = 2;
spi_message_add_tail(&req->xfer[0], &req->msg);
spi_message_add_tail(&req->xfer[1], &req->msg);
status = spi_sync(spi, &req->msg);
ret = status ? : req->sample;
kfree(req);
return ret;
}
static int ad7877_write(struct spi_device *spi, u16 reg, u16 val)
{
struct ser_req *req;
int status;
req = kzalloc(sizeof *req, GFP_KERNEL);
if (!req)
return -ENOMEM;
spi_message_init(&req->msg);
req->command = (u16) (AD7877_WRITEADD(reg) | (val & MAX_12BIT));
req->xfer[0].tx_buf = &req->command;
req->xfer[0].len = 2;
spi_message_add_tail(&req->xfer[0], &req->msg);
status = spi_sync(spi, &req->msg);
kfree(req);
return status;
}
static int ad7877_read_adc(struct spi_device *spi, unsigned command)
{
struct ad7877 *ts = dev_get_drvdata(&spi->dev);
struct ser_req *req;
int status;
int sample;
int i;
req = kzalloc(sizeof *req, GFP_KERNEL);
if (!req)
return -ENOMEM;
spi_message_init(&req->msg);
/* activate reference, so it has time to settle; */
req->ref_on = AD7877_WRITEADD(AD7877_REG_CTRL2) |
AD7877_POL(ts->stopacq_polarity) |
AD7877_AVG(0) | AD7877_PM(2) | AD7877_TMR(0) |
AD7877_ACQ(ts->acquisition_time) | AD7877_FCD(0);
req->reset = AD7877_WRITEADD(AD7877_REG_CTRL1) | AD7877_MODE_NOC;
req->command = (u16) command;
req->xfer[0].tx_buf = &req->reset;
req->xfer[0].len = 2;
req->xfer[1].tx_buf = &req->ref_on;
req->xfer[1].len = 2;
req->xfer[1].delay_usecs = ts->vref_delay_usecs;
req->xfer[2].tx_buf = &req->command;
req->xfer[2].len = 2;
req->xfer[2].delay_usecs = ts->vref_delay_usecs;
req->xfer[3].rx_buf = &req->sample;
req->xfer[3].len = 2;
req->xfer[4].tx_buf = &ts->cmd_crtl2; /*REF OFF*/
req->xfer[4].len = 2;
req->xfer[5].tx_buf = &ts->cmd_crtl1; /*DEFAULT*/
req->xfer[5].len = 2;
/* group all the transfers together, so we can't interfere with
* reading touchscreen state; disable penirq while sampling
*/
for (i = 0; i < 6; i++)
spi_message_add_tail(&req->xfer[i], &req->msg);
status = spi_sync(spi, &req->msg);
sample = req->sample;
kfree(req);
return status ? : sample;
}
static void ad7877_rx(struct ad7877 *ts)
{
struct input_dev *input_dev = ts->input;
unsigned Rt;
u16 x, y, z1, z2;
x = ts->conversion_data[AD7877_SEQ_XPOS] & MAX_12BIT;
y = ts->conversion_data[AD7877_SEQ_YPOS] & MAX_12BIT;
z1 = ts->conversion_data[AD7877_SEQ_Z1] & MAX_12BIT;
z2 = ts->conversion_data[AD7877_SEQ_Z2] & MAX_12BIT;
/*
* The samples processed here are already preprocessed by the AD7877.
* The preprocessing function consists of an averaging filter.
* The combination of 'first conversion delay' and averaging provides a robust solution,
* discarding the spurious noise in the signal and keeping only the data of interest.
* The size of the averaging filter is programmable. (dev.platform_data, see linux/spi/ad7877.h)
* Other user-programmable conversion controls include variable acquisition time,
* and first conversion delay. Up to 16 averages can be taken per conversion.
*/
if (likely(x && z1)) {
/* compute touch pressure resistance using equation #1 */
Rt = (z2 - z1) * x * ts->x_plate_ohms;
Rt /= z1;
Rt = (Rt + 2047) >> 12;
input_report_abs(input_dev, ABS_X, x);
input_report_abs(input_dev, ABS_Y, y);
input_report_abs(input_dev, ABS_PRESSURE, Rt);
input_sync(input_dev);
}
}
static inline void ad7877_ts_event_release(struct ad7877 *ts)
{
struct input_dev *input_dev = ts->input;
input_report_abs(input_dev, ABS_PRESSURE, 0);
input_sync(input_dev);
}
static void ad7877_timer(unsigned long handle)
{
struct ad7877 *ts = (void *)handle;
ad7877_ts_event_release(ts);
}
static irqreturn_t ad7877_irq(int irq, void *handle)
{
struct ad7877 *ts = handle;
unsigned long flags;
int status;
/*
* The repeated conversion sequencer controlled by TMR kicked off
* too fast. We ignore the last and process the sample sequence
* currently in the queue. It can't be older than 9.4ms, and we
* need to avoid that ts->msg doesn't get issued twice while in work.
*/
spin_lock_irqsave(&ts->lock, flags);
if (!ts->pending) {
ts->pending = 1;
status = spi_async(ts->spi, &ts->msg);
if (status)
dev_err(&ts->spi->dev, "spi_sync --> %d\n", status);
}
spin_unlock_irqrestore(&ts->lock, flags);
return IRQ_HANDLED;
}
static void ad7877_callback(void *_ts)
{
struct ad7877 *ts = _ts;
spin_lock_irq(&ts->lock);
ad7877_rx(ts);
ts->pending = 0;
mod_timer(&ts->timer, jiffies + TS_PEN_UP_TIMEOUT);
spin_unlock_irq(&ts->lock);
}
static void ad7877_disable(struct ad7877 *ts)
{
mutex_lock(&ts->mutex);
if (!ts->disabled) {
ts->disabled = 1;
disable_irq(ts->spi->irq);
/* Wait for spi_async callback */
while (ts->pending)
msleep(1);
if (del_timer_sync(&ts->timer))
ad7877_ts_event_release(ts);
}
/* we know the chip's in lowpower mode since we always
* leave it that way after every request
*/
mutex_unlock(&ts->mutex);
}
static void ad7877_enable(struct ad7877 *ts)
{
mutex_lock(&ts->mutex);
if (ts->disabled) {
ts->disabled = 0;
enable_irq(ts->spi->irq);
}
mutex_unlock(&ts->mutex);
}
#define SHOW(name) static ssize_t \
name ## _show(struct device *dev, struct device_attribute *attr, char *buf) \
{ \
struct ad7877 *ts = dev_get_drvdata(dev); \
ssize_t v = ad7877_read_adc(ts->spi, \
AD7877_READ_CHAN(name)); \
if (v < 0) \
return v; \
return sprintf(buf, "%u\n", (unsigned) v); \
} \
static DEVICE_ATTR(name, S_IRUGO, name ## _show, NULL);
SHOW(aux1)
SHOW(aux2)
SHOW(aux3)
SHOW(bat1)
SHOW(bat2)
SHOW(temp1)
SHOW(temp2)
static ssize_t ad7877_disable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ad7877 *ts = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", ts->disabled);
}
static ssize_t ad7877_disable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ad7877 *ts = dev_get_drvdata(dev);
unsigned long val;
int error;
error = strict_strtoul(buf, 10, &val);
if (error)
return error;
if (val)
ad7877_disable(ts);
else
ad7877_enable(ts);
return count;
}
static DEVICE_ATTR(disable, 0664, ad7877_disable_show, ad7877_disable_store);
static ssize_t ad7877_dac_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ad7877 *ts = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", ts->dac);
}
static ssize_t ad7877_dac_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ad7877 *ts = dev_get_drvdata(dev);
unsigned long val;
int error;
error = strict_strtoul(buf, 10, &val);
if (error)
return error;
mutex_lock(&ts->mutex);
ts->dac = val & 0xFF;
ad7877_write(ts->spi, AD7877_REG_DAC, (ts->dac << 4) | AD7877_DAC_CONF);
mutex_unlock(&ts->mutex);
return count;
}
static DEVICE_ATTR(dac, 0664, ad7877_dac_show, ad7877_dac_store);
static ssize_t ad7877_gpio3_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ad7877 *ts = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", ts->gpio3);
}
static ssize_t ad7877_gpio3_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ad7877 *ts = dev_get_drvdata(dev);
unsigned long val;
int error;
error = strict_strtoul(buf, 10, &val);
if (error)
return error;
mutex_lock(&ts->mutex);
ts->gpio3 = !!val;
ad7877_write(ts->spi, AD7877_REG_EXTWRITE, AD7877_EXTW_GPIO_DATA |
(ts->gpio4 << 4) | (ts->gpio3 << 5));
mutex_unlock(&ts->mutex);
return count;
}
static DEVICE_ATTR(gpio3, 0664, ad7877_gpio3_show, ad7877_gpio3_store);
static ssize_t ad7877_gpio4_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ad7877 *ts = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", ts->gpio4);
}
static ssize_t ad7877_gpio4_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ad7877 *ts = dev_get_drvdata(dev);
unsigned long val;
int error;
error = strict_strtoul(buf, 10, &val);
if (error)
return error;
mutex_lock(&ts->mutex);
ts->gpio4 = !!val;
ad7877_write(ts->spi, AD7877_REG_EXTWRITE, AD7877_EXTW_GPIO_DATA |
(ts->gpio4 << 4) | (ts->gpio3 << 5));
mutex_unlock(&ts->mutex);
return count;
}
static DEVICE_ATTR(gpio4, 0664, ad7877_gpio4_show, ad7877_gpio4_store);
static struct attribute *ad7877_attributes[] = {
&dev_attr_temp1.attr,
&dev_attr_temp2.attr,
&dev_attr_aux1.attr,
&dev_attr_aux2.attr,
&dev_attr_bat1.attr,
&dev_attr_bat2.attr,
&dev_attr_disable.attr,
&dev_attr_dac.attr,
&dev_attr_gpio4.attr,
NULL
};
static const struct attribute_group ad7877_attr_group = {
.attrs = ad7877_attributes,
};
static void ad7877_setup_ts_def_msg(struct spi_device *spi, struct ad7877 *ts)
{
struct spi_message *m;
int i;
ts->cmd_crtl2 = AD7877_WRITEADD(AD7877_REG_CTRL2) |
AD7877_POL(ts->stopacq_polarity) |
AD7877_AVG(ts->averaging) | AD7877_PM(1) |
AD7877_TMR(ts->pen_down_acc_interval) |
AD7877_ACQ(ts->acquisition_time) |
AD7877_FCD(ts->first_conversion_delay);
ad7877_write(spi, AD7877_REG_CTRL2, ts->cmd_crtl2);
ts->cmd_crtl1 = AD7877_WRITEADD(AD7877_REG_CTRL1) |
AD7877_READADD(AD7877_REG_XPLUS-1) |
AD7877_MODE_SEQ1 | AD7877_DFR;
ad7877_write(spi, AD7877_REG_CTRL1, ts->cmd_crtl1);
ts->cmd_dummy = 0;
m = &ts->msg;
spi_message_init(m);
m->complete = ad7877_callback;
m->context = ts;
ts->xfer[0].tx_buf = &ts->cmd_crtl1;
ts->xfer[0].len = 2;
spi_message_add_tail(&ts->xfer[0], m);
ts->xfer[1].tx_buf = &ts->cmd_dummy; /* Send ZERO */
ts->xfer[1].len = 2;
spi_message_add_tail(&ts->xfer[1], m);
for (i = 0; i < 11; i++) {
ts->xfer[i + 2].rx_buf = &ts->conversion_data[AD7877_SEQ_YPOS + i];
ts->xfer[i + 2].len = 2;
spi_message_add_tail(&ts->xfer[i + 2], m);
}
}
static int __devinit ad7877_probe(struct spi_device *spi)
{
struct ad7877 *ts;
struct input_dev *input_dev;
struct ad7877_platform_data *pdata = spi->dev.platform_data;
int err;
u16 verify;
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 SPI CLK frequency */
if (spi->max_speed_hz > MAX_SPI_FREQ_HZ) {
dev_dbg(&spi->dev, "SPI CLK %d Hz?\n",spi->max_speed_hz);
return -EINVAL;
}
ts = kzalloc(sizeof(struct ad7877), GFP_KERNEL);
input_dev = input_allocate_device();
if (!ts || !input_dev) {
err = -ENOMEM;
goto err_free_mem;
}
dev_set_drvdata(&spi->dev, ts);
ts->spi = spi;
ts->input = input_dev;
setup_timer(&ts->timer, ad7877_timer, (unsigned long) ts);
mutex_init(&ts->mutex);
spin_lock_init(&ts->lock);
ts->model = pdata->model ? : 7877;
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;
ts->stopacq_polarity = pdata->stopacq_polarity;
ts->first_conversion_delay = pdata->first_conversion_delay;
ts->acquisition_time = pdata->acquisition_time;
ts->averaging = pdata->averaging;
ts->pen_down_acc_interval = pdata->pen_down_acc_interval;
snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(&spi->dev));
input_dev->name = "AD7877 Touchscreen";
input_dev->phys = ts->phys;
input_dev->dev.parent = &spi->dev;
__set_bit(EV_ABS, input_dev->evbit);
__set_bit(ABS_X, input_dev->absbit);
__set_bit(ABS_Y, input_dev->absbit);
__set_bit(ABS_PRESSURE, input_dev->absbit);
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);
ad7877_write(spi, AD7877_REG_SEQ1, AD7877_MM_SEQUENCE);
verify = ad7877_read(spi, AD7877_REG_SEQ1);
if (verify != AD7877_MM_SEQUENCE){
dev_err(&spi->dev, "%s: Failed to probe %s\n",
dev_name(&spi->dev), input_dev->name);
err = -ENODEV;
goto err_free_mem;
}
if (gpio3)
ad7877_write(spi, AD7877_REG_EXTWRITE, AD7877_EXTW_GPIO_3_CONF);
ad7877_setup_ts_def_msg(spi, ts);
/* Request AD7877 /DAV GPIO interrupt */
err = request_irq(spi->irq, ad7877_irq, IRQF_TRIGGER_FALLING,
spi->dev.driver->name, ts);
if (err) {
dev_dbg(&spi->dev, "irq %d busy?\n", spi->irq);
goto err_free_mem;
}
err = sysfs_create_group(&spi->dev.kobj, &ad7877_attr_group);
if (err)
goto err_free_irq;
err = device_create_file(&spi->dev,
gpio3 ? &dev_attr_gpio3 : &dev_attr_aux3);
if (err)
goto err_remove_attr_group;
err = input_register_device(input_dev);
if (err)
goto err_remove_attr;
return 0;
err_remove_attr:
device_remove_file(&spi->dev,
gpio3 ? &dev_attr_gpio3 : &dev_attr_aux3);
err_remove_attr_group:
sysfs_remove_group(&spi->dev.kobj, &ad7877_attr_group);
err_free_irq:
free_irq(spi->irq, ts);
err_free_mem:
input_free_device(input_dev);
kfree(ts);
dev_set_drvdata(&spi->dev, NULL);
return err;
}
static int __devexit ad7877_remove(struct spi_device *spi)
{
struct ad7877 *ts = dev_get_drvdata(&spi->dev);
sysfs_remove_group(&spi->dev.kobj, &ad7877_attr_group);
device_remove_file(&spi->dev,
gpio3 ? &dev_attr_gpio3 : &dev_attr_aux3);
ad7877_disable(ts);
free_irq(ts->spi->irq, ts);
input_unregister_device(ts->input);
kfree(ts);
dev_dbg(&spi->dev, "unregistered touchscreen\n");
dev_set_drvdata(&spi->dev, NULL);
return 0;
}
#ifdef CONFIG_PM
static int ad7877_suspend(struct spi_device *spi, pm_message_t message)
{
struct ad7877 *ts = dev_get_drvdata(&spi->dev);
ad7877_disable(ts);
return 0;
}
static int ad7877_resume(struct spi_device *spi)
{
struct ad7877 *ts = dev_get_drvdata(&spi->dev);
ad7877_enable(ts);
return 0;
}
#else
#define ad7877_suspend NULL
#define ad7877_resume NULL
#endif
static struct spi_driver ad7877_driver = {
.driver = {
.name = "ad7877",
.bus = &spi_bus_type,
.owner = THIS_MODULE,
},
.probe = ad7877_probe,
.remove = __devexit_p(ad7877_remove),
.suspend = ad7877_suspend,
.resume = ad7877_resume,
};
static int __init ad7877_init(void)
{
return spi_register_driver(&ad7877_driver);
}
module_init(ad7877_init);
static void __exit ad7877_exit(void)
{
spi_unregister_driver(&ad7877_driver);
}
module_exit(ad7877_exit);
MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
MODULE_DESCRIPTION("AD7877 touchscreen Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:ad7877");

View File

@@ -0,0 +1,784 @@
/*
* Copyright (C) 2008-2009 Michael Hennerich, Analog Devices Inc.
*
* Description: AD7879/AD7889 based touchscreen, and GPIO driver
* (I2C/SPI Interface)
*
* Bugs: Enter bugs at http://blackfin.uclinux.org/
*
* 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, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* History:
* 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
* - ad7877.c
* Copyright (C) 2006-2008 Analog Devices Inc.
*/
#include <linux/device.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/spi/spi.h>
#include <linux/i2c.h>
#include <linux/spi/ad7879.h>
#define AD7879_REG_ZEROS 0
#define AD7879_REG_CTRL1 1
#define AD7879_REG_CTRL2 2
#define AD7879_REG_CTRL3 3
#define AD7879_REG_AUX1HIGH 4
#define AD7879_REG_AUX1LOW 5
#define AD7879_REG_TEMP1HIGH 6
#define AD7879_REG_TEMP1LOW 7
#define AD7879_REG_XPLUS 8
#define AD7879_REG_YPLUS 9
#define AD7879_REG_Z1 10
#define AD7879_REG_Z2 11
#define AD7879_REG_AUXVBAT 12
#define AD7879_REG_TEMP 13
#define AD7879_REG_REVID 14
/* Control REG 1 */
#define AD7879_TMR(x) ((x & 0xFF) << 0)
#define AD7879_ACQ(x) ((x & 0x3) << 8)
#define AD7879_MODE_NOC (0 << 10) /* Do not convert */
#define AD7879_MODE_SCC (1 << 10) /* Single channel conversion */
#define AD7879_MODE_SEQ0 (2 << 10) /* Sequence 0 in Slave Mode */
#define AD7879_MODE_SEQ1 (3 << 10) /* Sequence 1 in Master Mode */
#define AD7879_MODE_INT (1 << 15) /* PENIRQ disabled INT enabled */
/* Control REG 2 */
#define AD7879_FCD(x) ((x & 0x3) << 0)
#define AD7879_RESET (1 << 4)
#define AD7879_MFS(x) ((x & 0x3) << 5)
#define AD7879_AVG(x) ((x & 0x3) << 7)
#define AD7879_SER (1 << 9) /* non-differential */
#define AD7879_DFR (0 << 9) /* differential */
#define AD7879_GPIOPOL (1 << 10)
#define AD7879_GPIODIR (1 << 11)
#define AD7879_GPIO_DATA (1 << 12)
#define AD7879_GPIO_EN (1 << 13)
#define AD7879_PM(x) ((x & 0x3) << 14)
#define AD7879_PM_SHUTDOWN (0)
#define AD7879_PM_DYN (1)
#define AD7879_PM_FULLON (2)
/* Control REG 3 */
#define AD7879_TEMPMASK_BIT (1<<15)
#define AD7879_AUXVBATMASK_BIT (1<<14)
#define AD7879_INTMODE_BIT (1<<13)
#define AD7879_GPIOALERTMASK_BIT (1<<12)
#define AD7879_AUXLOW_BIT (1<<11)
#define AD7879_AUXHIGH_BIT (1<<10)
#define AD7879_TEMPLOW_BIT (1<<9)
#define AD7879_TEMPHIGH_BIT (1<<8)
#define AD7879_YPLUS_BIT (1<<7)
#define AD7879_XPLUS_BIT (1<<6)
#define AD7879_Z1_BIT (1<<5)
#define AD7879_Z2_BIT (1<<4)
#define AD7879_AUX_BIT (1<<3)
#define AD7879_VBAT_BIT (1<<2)
#define AD7879_TEMP_BIT (1<<1)
enum {
AD7879_SEQ_XPOS = 0,
AD7879_SEQ_YPOS = 1,
AD7879_SEQ_Z1 = 2,
AD7879_SEQ_Z2 = 3,
AD7879_NR_SENSE = 4,
};
#define MAX_12BIT ((1<<12)-1)
#define TS_PEN_UP_TIMEOUT msecs_to_jiffies(50)
#if defined(CONFIG_TOUCHSCREEN_AD7879_SPI) || defined(CONFIG_TOUCHSCREEN_AD7879_SPI_MODULE)
#define AD7879_DEVID 0x7A
typedef struct spi_device bus_device;
#elif defined(CONFIG_TOUCHSCREEN_AD7879_I2C) || defined(CONFIG_TOUCHSCREEN_AD7879_I2C_MODULE)
#define AD7879_DEVID 0x79
typedef struct i2c_client bus_device;
#endif
struct ad7879 {
bus_device *bus;
struct input_dev *input;
struct work_struct work;
struct timer_list timer;
struct mutex mutex;
unsigned disabled:1; /* P: mutex */
#if defined(CONFIG_TOUCHSCREEN_AD7879_SPI) || defined(CONFIG_TOUCHSCREEN_AD7879_SPI_MODULE)
struct spi_message msg;
struct spi_transfer xfer[AD7879_NR_SENSE + 1];
u16 cmd;
#endif
u16 conversion_data[AD7879_NR_SENSE];
char phys[32];
u8 first_conversion_delay;
u8 acquisition_time;
u8 averaging;
u8 pen_down_acc_interval;
u8 median;
u16 x_plate_ohms;
u16 pressure_max;
u16 gpio_init;
u16 cmd_crtl1;
u16 cmd_crtl2;
u16 cmd_crtl3;
unsigned gpio:1;
};
static int ad7879_read(bus_device *, u8);
static int ad7879_write(bus_device *, u8, u16);
static void ad7879_collect(struct ad7879 *);
static void ad7879_report(struct ad7879 *ts)
{
struct input_dev *input_dev = ts->input;
unsigned Rt;
u16 x, y, z1, z2;
x = ts->conversion_data[AD7879_SEQ_XPOS] & MAX_12BIT;
y = ts->conversion_data[AD7879_SEQ_YPOS] & MAX_12BIT;
z1 = ts->conversion_data[AD7879_SEQ_Z1] & MAX_12BIT;
z2 = ts->conversion_data[AD7879_SEQ_Z2] & MAX_12BIT;
/*
* The samples processed here are already preprocessed by the AD7879.
* The preprocessing function consists of a median and an averaging filter.
* The combination of these two techniques provides a robust solution,
* discarding the spurious noise in the signal and keeping only the data of interest.
* The size of both filters is programmable. (dev.platform_data, see linux/spi/ad7879.h)
* Other user-programmable conversion controls include variable acquisition time,
* and first conversion delay. Up to 16 averages can be taken per conversion.
*/
if (likely(x && z1)) {
/* compute touch pressure resistance using equation #1 */
Rt = (z2 - z1) * x * ts->x_plate_ohms;
Rt /= z1;
Rt = (Rt + 2047) >> 12;
input_report_abs(input_dev, ABS_X, x);
input_report_abs(input_dev, ABS_Y, y);
input_report_abs(input_dev, ABS_PRESSURE, Rt);
input_sync(input_dev);
}
}
static void ad7879_work(struct work_struct *work)
{
struct ad7879 *ts = container_of(work, struct ad7879, work);
/* use keventd context to read the result registers */
ad7879_collect(ts);
ad7879_report(ts);
mod_timer(&ts->timer, jiffies + TS_PEN_UP_TIMEOUT);
}
static void ad7879_ts_event_release(struct ad7879 *ts)
{
struct input_dev *input_dev = ts->input;
input_report_abs(input_dev, ABS_PRESSURE, 0);
input_sync(input_dev);
}
static void ad7879_timer(unsigned long handle)
{
struct ad7879 *ts = (void *)handle;
ad7879_ts_event_release(ts);
}
static irqreturn_t ad7879_irq(int irq, void *handle)
{
struct ad7879 *ts = handle;
/* The repeated conversion sequencer controlled by TMR kicked off too fast.
* We ignore the last and process the sample sequence currently in the queue.
* It can't be older than 9.4ms
*/
if (!work_pending(&ts->work))
schedule_work(&ts->work);
return IRQ_HANDLED;
}
static void ad7879_setup(struct ad7879 *ts)
{
ts->cmd_crtl3 = AD7879_YPLUS_BIT |
AD7879_XPLUS_BIT |
AD7879_Z2_BIT |
AD7879_Z1_BIT |
AD7879_TEMPMASK_BIT |
AD7879_AUXVBATMASK_BIT |
AD7879_GPIOALERTMASK_BIT;
ts->cmd_crtl2 = AD7879_PM(AD7879_PM_DYN) | AD7879_DFR |
AD7879_AVG(ts->averaging) |
AD7879_MFS(ts->median) |
AD7879_FCD(ts->first_conversion_delay) |
ts->gpio_init;
ts->cmd_crtl1 = AD7879_MODE_INT | AD7879_MODE_SEQ1 |
AD7879_ACQ(ts->acquisition_time) |
AD7879_TMR(ts->pen_down_acc_interval);
ad7879_write(ts->bus, AD7879_REG_CTRL2, ts->cmd_crtl2);
ad7879_write(ts->bus, AD7879_REG_CTRL3, ts->cmd_crtl3);
ad7879_write(ts->bus, AD7879_REG_CTRL1, ts->cmd_crtl1);
}
static void ad7879_disable(struct ad7879 *ts)
{
mutex_lock(&ts->mutex);
if (!ts->disabled) {
ts->disabled = 1;
disable_irq(ts->bus->irq);
cancel_work_sync(&ts->work);
if (del_timer_sync(&ts->timer))
ad7879_ts_event_release(ts);
ad7879_write(ts->bus, AD7879_REG_CTRL2,
AD7879_PM(AD7879_PM_SHUTDOWN));
}
mutex_unlock(&ts->mutex);
}
static void ad7879_enable(struct ad7879 *ts)
{
mutex_lock(&ts->mutex);
if (ts->disabled) {
ad7879_setup(ts);
ts->disabled = 0;
enable_irq(ts->bus->irq);
}
mutex_unlock(&ts->mutex);
}
static ssize_t ad7879_disable_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ad7879 *ts = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", ts->disabled);
}
static ssize_t ad7879_disable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ad7879 *ts = dev_get_drvdata(dev);
unsigned long val;
int error;
error = strict_strtoul(buf, 10, &val);
if (error)
return error;
if (val)
ad7879_disable(ts);
else
ad7879_enable(ts);
return count;
}
static DEVICE_ATTR(disable, 0664, ad7879_disable_show, ad7879_disable_store);
static ssize_t ad7879_gpio_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ad7879 *ts = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", ts->gpio);
}
static ssize_t ad7879_gpio_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ad7879 *ts = dev_get_drvdata(dev);
unsigned long val;
int error;
error = strict_strtoul(buf, 10, &val);
if (error)
return error;
mutex_lock(&ts->mutex);
ts->gpio = !!val;
error = ad7879_write(ts->bus, AD7879_REG_CTRL2,
ts->gpio ?
ts->cmd_crtl2 & ~AD7879_GPIO_DATA :
ts->cmd_crtl2 | AD7879_GPIO_DATA);
mutex_unlock(&ts->mutex);
return error ? : count;
}
static DEVICE_ATTR(gpio, 0664, ad7879_gpio_show, ad7879_gpio_store);
static struct attribute *ad7879_attributes[] = {
&dev_attr_disable.attr,
&dev_attr_gpio.attr,
NULL
};
static const struct attribute_group ad7879_attr_group = {
.attrs = ad7879_attributes,
};
static int __devinit ad7879_construct(bus_device *bus, struct ad7879 *ts)
{
struct input_dev *input_dev;
struct ad7879_platform_data *pdata = bus->dev.platform_data;
int err;
u16 revid;
if (!bus->irq) {
dev_err(&bus->dev, "no IRQ?\n");
return -ENODEV;
}
if (!pdata) {
dev_err(&bus->dev, "no platform data?\n");
return -ENODEV;
}
input_dev = input_allocate_device();
if (!input_dev)
return -ENOMEM;
ts->input = input_dev;
setup_timer(&ts->timer, ad7879_timer, (unsigned long) ts);
INIT_WORK(&ts->work, ad7879_work);
mutex_init(&ts->mutex);
ts->x_plate_ohms = pdata->x_plate_ohms ? : 400;
ts->pressure_max = pdata->pressure_max ? : ~0;
ts->first_conversion_delay = pdata->first_conversion_delay;
ts->acquisition_time = pdata->acquisition_time;
ts->averaging = pdata->averaging;
ts->pen_down_acc_interval = pdata->pen_down_acc_interval;
ts->median = pdata->median;
if (pdata->gpio_output)
ts->gpio_init = AD7879_GPIO_EN |
(pdata->gpio_default ? 0 : AD7879_GPIO_DATA);
else
ts->gpio_init = AD7879_GPIO_EN | AD7879_GPIODIR;
snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(&bus->dev));
input_dev->name = "AD7879 Touchscreen";
input_dev->phys = ts->phys;
input_dev->dev.parent = &bus->dev;
__set_bit(EV_ABS, input_dev->evbit);
__set_bit(ABS_X, input_dev->absbit);
__set_bit(ABS_Y, input_dev->absbit);
__set_bit(ABS_PRESSURE, input_dev->absbit);
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);
err = ad7879_write(bus, AD7879_REG_CTRL2, AD7879_RESET);
if (err < 0) {
dev_err(&bus->dev, "Failed to write %s\n", input_dev->name);
goto err_free_mem;
}
revid = ad7879_read(bus, AD7879_REG_REVID);
if ((revid & 0xFF) != AD7879_DEVID) {
dev_err(&bus->dev, "Failed to probe %s\n", input_dev->name);
err = -ENODEV;
goto err_free_mem;
}
ad7879_setup(ts);
err = request_irq(bus->irq, ad7879_irq,
IRQF_TRIGGER_FALLING, bus->dev.driver->name, ts);
if (err) {
dev_err(&bus->dev, "irq %d busy?\n", bus->irq);
goto err_free_mem;
}
err = sysfs_create_group(&bus->dev.kobj, &ad7879_attr_group);
if (err)
goto err_free_irq;
err = input_register_device(input_dev);
if (err)
goto err_remove_attr;
dev_info(&bus->dev, "Rev.%d touchscreen, irq %d\n",
revid >> 8, bus->irq);
return 0;
err_remove_attr:
sysfs_remove_group(&bus->dev.kobj, &ad7879_attr_group);
err_free_irq:
free_irq(bus->irq, ts);
err_free_mem:
input_free_device(input_dev);
return err;
}
static int __devexit ad7879_destroy(bus_device *bus, struct ad7879 *ts)
{
ad7879_disable(ts);
sysfs_remove_group(&ts->bus->dev.kobj, &ad7879_attr_group);
free_irq(ts->bus->irq, ts);
input_unregister_device(ts->input);
dev_dbg(&bus->dev, "unregistered touchscreen\n");
return 0;
}
#ifdef CONFIG_PM
static int ad7879_suspend(bus_device *bus, pm_message_t message)
{
struct ad7879 *ts = dev_get_drvdata(&bus->dev);
ad7879_disable(ts);
return 0;
}
static int ad7879_resume(bus_device *bus)
{
struct ad7879 *ts = dev_get_drvdata(&bus->dev);
ad7879_enable(ts);
return 0;
}
#else
#define ad7879_suspend NULL
#define ad7879_resume NULL
#endif
#if defined(CONFIG_TOUCHSCREEN_AD7879_SPI) || defined(CONFIG_TOUCHSCREEN_AD7879_SPI_MODULE)
#define MAX_SPI_FREQ_HZ 5000000
#define AD7879_CMD_MAGIC 0xE000
#define AD7879_CMD_READ (1 << 10)
#define AD7879_WRITECMD(reg) (AD7879_CMD_MAGIC | (reg & 0xF))
#define AD7879_READCMD(reg) (AD7879_CMD_MAGIC | AD7879_CMD_READ | (reg & 0xF))
struct ser_req {
u16 command;
u16 data;
struct spi_message msg;
struct spi_transfer xfer[2];
};
/*
* ad7879_read/write are only used for initial setup and for sysfs controls.
* The main traffic is done in ad7879_collect().
*/
static int ad7879_read(struct spi_device *spi, u8 reg)
{
struct ser_req *req;
int status, ret;
req = kzalloc(sizeof *req, GFP_KERNEL);
if (!req)
return -ENOMEM;
spi_message_init(&req->msg);
req->command = (u16) AD7879_READCMD(reg);
req->xfer[0].tx_buf = &req->command;
req->xfer[0].len = 2;
req->xfer[1].rx_buf = &req->data;
req->xfer[1].len = 2;
spi_message_add_tail(&req->xfer[0], &req->msg);
spi_message_add_tail(&req->xfer[1], &req->msg);
status = spi_sync(spi, &req->msg);
ret = status ? : req->data;
kfree(req);
return ret;
}
static int ad7879_write(struct spi_device *spi, u8 reg, u16 val)
{
struct ser_req *req;
int status;
req = kzalloc(sizeof *req, GFP_KERNEL);
if (!req)
return -ENOMEM;
spi_message_init(&req->msg);
req->command = (u16) AD7879_WRITECMD(reg);
req->xfer[0].tx_buf = &req->command;
req->xfer[0].len = 2;
req->data = val;
req->xfer[1].tx_buf = &req->data;
req->xfer[1].len = 2;
spi_message_add_tail(&req->xfer[0], &req->msg);
spi_message_add_tail(&req->xfer[1], &req->msg);
status = spi_sync(spi, &req->msg);
kfree(req);
return status;
}
static void ad7879_collect(struct ad7879 *ts)
{
int status = spi_sync(ts->bus, &ts->msg);
if (status)
dev_err(&ts->bus->dev, "spi_sync --> %d\n", status);
}
static void ad7879_setup_ts_def_msg(struct ad7879 *ts)
{
struct spi_message *m;
int i;
ts->cmd = (u16) AD7879_READCMD(AD7879_REG_XPLUS);
m = &ts->msg;
spi_message_init(m);
ts->xfer[0].tx_buf = &ts->cmd;
ts->xfer[0].len = 2;
spi_message_add_tail(&ts->xfer[0], m);
for (i = 0; i < AD7879_NR_SENSE; i++) {
ts->xfer[i + 1].rx_buf = &ts->conversion_data[i];
ts->xfer[i + 1].len = 2;
spi_message_add_tail(&ts->xfer[i + 1], m);
}
}
static int __devinit ad7879_probe(struct spi_device *spi)
{
struct ad7879 *ts;
int error;
/* don't exceed max specified SPI CLK frequency */
if (spi->max_speed_hz > MAX_SPI_FREQ_HZ) {
dev_err(&spi->dev, "SPI CLK %d Hz?\n", spi->max_speed_hz);
return -EINVAL;
}
ts = kzalloc(sizeof(struct ad7879), GFP_KERNEL);
if (!ts)
return -ENOMEM;
dev_set_drvdata(&spi->dev, ts);
ts->bus = spi;
ad7879_setup_ts_def_msg(ts);
error = ad7879_construct(spi, ts);
if (error) {
dev_set_drvdata(&spi->dev, NULL);
kfree(ts);
}
return error;
}
static int __devexit ad7879_remove(struct spi_device *spi)
{
struct ad7879 *ts = dev_get_drvdata(&spi->dev);
ad7879_destroy(spi, ts);
dev_set_drvdata(&spi->dev, NULL);
kfree(ts);
return 0;
}
static struct spi_driver ad7879_driver = {
.driver = {
.name = "ad7879",
.bus = &spi_bus_type,
.owner = THIS_MODULE,
},
.probe = ad7879_probe,
.remove = __devexit_p(ad7879_remove),
.suspend = ad7879_suspend,
.resume = ad7879_resume,
};
static int __init ad7879_init(void)
{
return spi_register_driver(&ad7879_driver);
}
module_init(ad7879_init);
static void __exit ad7879_exit(void)
{
spi_unregister_driver(&ad7879_driver);
}
module_exit(ad7879_exit);
#elif defined(CONFIG_TOUCHSCREEN_AD7879_I2C) || defined(CONFIG_TOUCHSCREEN_AD7879_I2C_MODULE)
/* All registers are word-sized.
* AD7879 uses a high-byte first convention.
*/
static int ad7879_read(struct i2c_client *client, u8 reg)
{
return swab16(i2c_smbus_read_word_data(client, reg));
}
static int ad7879_write(struct i2c_client *client, u8 reg, u16 val)
{
return i2c_smbus_write_word_data(client, reg, swab16(val));
}
static void ad7879_collect(struct ad7879 *ts)
{
int i;
for (i = 0; i < AD7879_NR_SENSE; i++)
ts->conversion_data[i] = ad7879_read(ts->bus,
AD7879_REG_XPLUS + i);
}
static int __devinit ad7879_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct ad7879 *ts;
int error;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_WORD_DATA)) {
dev_err(&client->dev, "SMBUS Word Data not Supported\n");
return -EIO;
}
ts = kzalloc(sizeof(struct ad7879), GFP_KERNEL);
if (!ts)
return -ENOMEM;
i2c_set_clientdata(client, ts);
ts->bus = client;
error = ad7879_construct(client, ts);
if (error) {
i2c_set_clientdata(client, NULL);
kfree(ts);
}
return error;
}
static int __devexit ad7879_remove(struct i2c_client *client)
{
struct ad7879 *ts = dev_get_drvdata(&client->dev);
ad7879_destroy(client, ts);
i2c_set_clientdata(client, NULL);
kfree(ts);
return 0;
}
static const struct i2c_device_id ad7879_id[] = {
{ "ad7879", 0 },
{ "ad7889", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ad7879_id);
static struct i2c_driver ad7879_driver = {
.driver = {
.name = "ad7879",
.owner = THIS_MODULE,
},
.probe = ad7879_probe,
.remove = __devexit_p(ad7879_remove),
.suspend = ad7879_suspend,
.resume = ad7879_resume,
.id_table = ad7879_id,
};
static int __init ad7879_init(void)
{
return i2c_add_driver(&ad7879_driver);
}
module_init(ad7879_init);
static void __exit ad7879_exit(void)
{
i2c_del_driver(&ad7879_driver);
}
module_exit(ad7879_exit);
#endif
MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
MODULE_DESCRIPTION("AD7879(-1) touchscreen Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:ad7879");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,446 @@
/*
* Atmel AT91 and AVR32 continuous touch screen driver for Wolfson WM97xx AC97
* codecs.
*
* Copyright (C) 2008 - 2009 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/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/timer.h>
#include <linux/gpio.h>
#include <linux/io.h>
#define AC97C_ICA 0x10
#define AC97C_CBRHR 0x30
#define AC97C_CBSR 0x38
#define AC97C_CBMR 0x3c
#define AC97C_IER 0x54
#define AC97C_IDR 0x58
#define AC97C_RXRDY (1 << 4)
#define AC97C_OVRUN (1 << 5)
#define AC97C_CMR_SIZE_20 (0 << 16)
#define AC97C_CMR_SIZE_18 (1 << 16)
#define AC97C_CMR_SIZE_16 (2 << 16)
#define AC97C_CMR_SIZE_10 (3 << 16)
#define AC97C_CMR_CEM_LITTLE (1 << 18)
#define AC97C_CMR_CEM_BIG (0 << 18)
#define AC97C_CMR_CENA (1 << 21)
#define AC97C_INT_CBEVT (1 << 4)
#define AC97C_SR_CAEVT (1 << 3)
#define AC97C_CH_MASK(slot) \
(0x7 << (3 * (slot - 3)))
#define AC97C_CH_ASSIGN(slot, channel) \
(AC97C_CHANNEL_##channel << (3 * (slot - 3)))
#define AC97C_CHANNEL_NONE 0x0
#define AC97C_CHANNEL_B 0x2
#define ac97c_writel(chip, reg, val) \
__raw_writel((val), (chip)->regs + AC97C_##reg)
#define ac97c_readl(chip, reg) \
__raw_readl((chip)->regs + AC97C_##reg)
#ifdef CONFIG_CPU_AT32AP700X
#define ATMEL_WM97XX_AC97C_IOMEM (0xfff02800)
#define ATMEL_WM97XX_AC97C_IRQ (29)
#define ATMEL_WM97XX_GPIO_DEFAULT (32+16) /* Pin 16 on port B. */
#else
#error Unkown CPU, this driver only supports AT32AP700X CPUs.
#endif
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;
/*
* Pen sampling frequency (Hz) in continuous mode.
*/
static int cont_rate = 188;
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 = 1;
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");
/*
* GPIO line number.
*
* Set to GPIO number where the signal from the WM97xx device is hooked up.
*/
static int atmel_gpio_line = ATMEL_WM97XX_GPIO_DEFAULT;
module_param(atmel_gpio_line, int, 0);
MODULE_PARM_DESC(atmel_gpio_line, "GPIO line number connected to WM97xx");
struct atmel_wm97xx {
struct wm97xx *wm;
struct timer_list pen_timer;
void __iomem *regs;
unsigned long ac97c_irq;
unsigned long gpio_pen;
unsigned long gpio_irq;
unsigned short x;
unsigned short y;
};
static irqreturn_t atmel_wm97xx_channel_b_interrupt(int irq, void *dev_id)
{
struct atmel_wm97xx *atmel_wm97xx = dev_id;
struct wm97xx *wm = atmel_wm97xx->wm;
int status = ac97c_readl(atmel_wm97xx, CBSR);
irqreturn_t retval = IRQ_NONE;
if (status & AC97C_OVRUN) {
dev_dbg(&wm->touch_dev->dev, "AC97C overrun\n");
ac97c_readl(atmel_wm97xx, CBRHR);
retval = IRQ_HANDLED;
} else if (status & AC97C_RXRDY) {
u16 data;
u16 value;
u16 source;
u16 pen_down;
data = ac97c_readl(atmel_wm97xx, CBRHR);
value = data & 0x0fff;
source = data & WM97XX_ADCSRC_MASK;
pen_down = (data & WM97XX_PEN_DOWN) >> 8;
if (source == WM97XX_ADCSEL_X)
atmel_wm97xx->x = value;
if (source == WM97XX_ADCSEL_Y)
atmel_wm97xx->y = value;
if (!pressure && source == WM97XX_ADCSEL_Y) {
input_report_abs(wm->input_dev, ABS_X, atmel_wm97xx->x);
input_report_abs(wm->input_dev, ABS_Y, atmel_wm97xx->y);
input_report_key(wm->input_dev, BTN_TOUCH, pen_down);
input_sync(wm->input_dev);
} else if (pressure && source == WM97XX_ADCSEL_PRES) {
input_report_abs(wm->input_dev, ABS_X, atmel_wm97xx->x);
input_report_abs(wm->input_dev, ABS_Y, atmel_wm97xx->y);
input_report_abs(wm->input_dev, ABS_PRESSURE, value);
input_report_key(wm->input_dev, BTN_TOUCH, value);
input_sync(wm->input_dev);
}
retval = IRQ_HANDLED;
}
return retval;
}
static void atmel_wm97xx_acc_pen_up(struct wm97xx *wm)
{
struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(wm->touch_dev);
struct input_dev *input_dev = wm->input_dev;
int pen_down = gpio_get_value(atmel_wm97xx->gpio_pen);
if (pen_down != 0) {
mod_timer(&atmel_wm97xx->pen_timer,
jiffies + msecs_to_jiffies(1));
} else {
if (pressure)
input_report_abs(input_dev, ABS_PRESSURE, 0);
input_report_key(input_dev, BTN_TOUCH, 0);
input_sync(input_dev);
}
}
static void atmel_wm97xx_pen_timer(unsigned long data)
{
atmel_wm97xx_acc_pen_up((struct wm97xx *)data);
}
static int atmel_wm97xx_acc_startup(struct wm97xx *wm)
{
struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(wm->touch_dev);
int idx = 0;
if (wm->ac97 == NULL)
return -ENODEV;
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->touch_dev->dev, "atmel accelerated touchscreen driver, "
"%d samples/sec\n", cinfo[sp_idx].speed);
if (pen_int) {
unsigned long reg;
wm->pen_irq = atmel_wm97xx->gpio_irq;
switch (wm->id) {
case WM9712_ID2: /* Fall through. */
case WM9713_ID2:
/*
* Use GPIO 13 (PEN_DOWN) to assert GPIO line 3
* (PENDOWN).
*/
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_3, WM97XX_GPIO_OUT,
WM97XX_GPIO_POL_HIGH,
WM97XX_GPIO_NOTSTICKY,
WM97XX_GPIO_NOWAKE);
case WM9705_ID2: /* Fall through. */
/*
* Enable touch data slot in AC97 controller channel B.
*/
reg = ac97c_readl(atmel_wm97xx, ICA);
reg &= ~AC97C_CH_MASK(wm->acc_slot);
reg |= AC97C_CH_ASSIGN(wm->acc_slot, B);
ac97c_writel(atmel_wm97xx, ICA, reg);
/*
* Enable channel and interrupt for RXRDY and OVERRUN.
*/
ac97c_writel(atmel_wm97xx, CBMR, AC97C_CMR_CENA
| AC97C_CMR_CEM_BIG
| AC97C_CMR_SIZE_16
| AC97C_OVRUN
| AC97C_RXRDY);
/* Dummy read to empty RXRHR. */
ac97c_readl(atmel_wm97xx, CBRHR);
/*
* Enable interrupt for channel B in the AC97
* controller.
*/
ac97c_writel(atmel_wm97xx, IER, AC97C_INT_CBEVT);
break;
default:
dev_err(&wm->touch_dev->dev, "pen down irq not "
"supported on this device\n");
pen_int = 0;
break;
}
}
return 0;
}
static void atmel_wm97xx_acc_shutdown(struct wm97xx *wm)
{
if (pen_int) {
struct atmel_wm97xx *atmel_wm97xx =
platform_get_drvdata(wm->touch_dev);
unsigned long ica;
switch (wm->id & 0xffff) {
case WM9705_ID2: /* Fall through. */
case WM9712_ID2: /* Fall through. */
case WM9713_ID2:
/* Disable slot and turn off channel B interrupts. */
ica = ac97c_readl(atmel_wm97xx, ICA);
ica &= ~AC97C_CH_MASK(wm->acc_slot);
ac97c_writel(atmel_wm97xx, ICA, ica);
ac97c_writel(atmel_wm97xx, IDR, AC97C_INT_CBEVT);
ac97c_writel(atmel_wm97xx, CBMR, 0);
wm->pen_irq = 0;
break;
default:
dev_err(&wm->touch_dev->dev, "unknown codec\n");
break;
}
}
}
static void atmel_wm97xx_irq_enable(struct wm97xx *wm, int enable)
{
/* Intentionally left empty. */
}
static struct wm97xx_mach_ops atmel_mach_ops = {
.acc_enabled = 1,
.acc_pen_up = atmel_wm97xx_acc_pen_up,
.acc_startup = atmel_wm97xx_acc_startup,
.acc_shutdown = atmel_wm97xx_acc_shutdown,
.irq_enable = atmel_wm97xx_irq_enable,
.irq_gpio = WM97XX_GPIO_3,
};
static int __init atmel_wm97xx_probe(struct platform_device *pdev)
{
struct wm97xx *wm = platform_get_drvdata(pdev);
struct atmel_wm97xx *atmel_wm97xx;
int ret;
atmel_wm97xx = kzalloc(sizeof(struct atmel_wm97xx), GFP_KERNEL);
if (!atmel_wm97xx) {
dev_dbg(&pdev->dev, "out of memory\n");
return -ENOMEM;
}
atmel_wm97xx->wm = wm;
atmel_wm97xx->regs = (void *)ATMEL_WM97XX_AC97C_IOMEM;
atmel_wm97xx->ac97c_irq = ATMEL_WM97XX_AC97C_IRQ;
atmel_wm97xx->gpio_pen = atmel_gpio_line;
atmel_wm97xx->gpio_irq = gpio_to_irq(atmel_wm97xx->gpio_pen);
setup_timer(&atmel_wm97xx->pen_timer, atmel_wm97xx_pen_timer,
(unsigned long)wm);
ret = request_irq(atmel_wm97xx->ac97c_irq,
atmel_wm97xx_channel_b_interrupt,
IRQF_SHARED, "atmel-wm97xx-ch-b", atmel_wm97xx);
if (ret) {
dev_dbg(&pdev->dev, "could not request ac97c irq\n");
goto err;
}
platform_set_drvdata(pdev, atmel_wm97xx);
ret = wm97xx_register_mach_ops(wm, &atmel_mach_ops);
if (ret)
goto err_irq;
return ret;
err_irq:
free_irq(atmel_wm97xx->ac97c_irq, atmel_wm97xx);
err:
platform_set_drvdata(pdev, NULL);
kfree(atmel_wm97xx);
return ret;
}
static int __exit atmel_wm97xx_remove(struct platform_device *pdev)
{
struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(pdev);
struct wm97xx *wm = atmel_wm97xx->wm;
ac97c_writel(atmel_wm97xx, IDR, AC97C_INT_CBEVT);
free_irq(atmel_wm97xx->ac97c_irq, atmel_wm97xx);
del_timer_sync(&atmel_wm97xx->pen_timer);
wm97xx_unregister_mach_ops(wm);
platform_set_drvdata(pdev, NULL);
kfree(atmel_wm97xx);
return 0;
}
#ifdef CONFIG_PM
static int atmel_wm97xx_suspend(struct platform_device *pdev, pm_message_t msg)
{
struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(pdev);
ac97c_writel(atmel_wm97xx, IDR, AC97C_INT_CBEVT);
disable_irq(atmel_wm97xx->gpio_irq);
del_timer_sync(&atmel_wm97xx->pen_timer);
return 0;
}
static int atmel_wm97xx_resume(struct platform_device *pdev)
{
struct atmel_wm97xx *atmel_wm97xx = platform_get_drvdata(pdev);
struct wm97xx *wm = atmel_wm97xx->wm;
if (wm->input_dev->users) {
enable_irq(atmel_wm97xx->gpio_irq);
ac97c_writel(atmel_wm97xx, IER, AC97C_INT_CBEVT);
}
return 0;
}
#else
#define atmel_wm97xx_suspend NULL
#define atmel_wm97xx_resume NULL
#endif
static struct platform_driver atmel_wm97xx_driver = {
.remove = __exit_p(atmel_wm97xx_remove),
.driver = {
.name = "wm97xx-touch",
},
.suspend = atmel_wm97xx_suspend,
.resume = atmel_wm97xx_resume,
};
static int __init atmel_wm97xx_init(void)
{
return platform_driver_probe(&atmel_wm97xx_driver, atmel_wm97xx_probe);
}
module_init(atmel_wm97xx_init);
static void __exit atmel_wm97xx_exit(void)
{
platform_driver_unregister(&atmel_wm97xx_driver);
}
module_exit(atmel_wm97xx_exit);
MODULE_AUTHOR("Hans-Christian Egtvedt <hans-christian.egtvedt@atmel.com>");
MODULE_DESCRIPTION("wm97xx continuous touch driver for Atmel AT91 and AVR32");
MODULE_LICENSE("GPL");

View File

@@ -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, resource_size(res),
"atmel tsadcc regs")) {
dev_err(&pdev->dev, "resources is unavailable.\n");
err = -EBUSY;
goto err_free_dev;
}
tsc_base = ioremap(res->start, resource_size(res));
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", dev_name(&pdev->dev));
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, resource_size(res));
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, resource_size(res));
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>");

View File

@@ -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/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 __devinit 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 __devexit 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 = __devexit_p(corgits_remove),
.suspend = corgits_suspend,
.resume = corgits_resume,
.driver = {
.name = "corgi-ts",
.owner = THIS_MODULE,
},
};
static int __init 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");

View File

@@ -0,0 +1,397 @@
/*
* Touchscreen driver for Dialog Semiconductor DA9034
*
* Copyright (C) 2006-2008 Marvell International Ltd.
* Fengwei Yin <fengwei.yin@marvell.com>
* Bin Yang <bin.yang@marvell.com>
* Eric Miao <eric.miao@marvell.com>
*
* 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/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/workqueue.h>
#include <linux/mfd/da903x.h>
#define DA9034_MANUAL_CTRL 0x50
#define DA9034_LDO_ADC_EN (1 << 4)
#define DA9034_AUTO_CTRL1 0x51
#define DA9034_AUTO_CTRL2 0x52
#define DA9034_AUTO_TSI_EN (1 << 3)
#define DA9034_PEN_DETECT (1 << 4)
#define DA9034_TSI_CTRL1 0x53
#define DA9034_TSI_CTRL2 0x54
#define DA9034_TSI_X_MSB 0x6c
#define DA9034_TSI_Y_MSB 0x6d
#define DA9034_TSI_XY_LSB 0x6e
enum {
STATE_IDLE, /* wait for pendown */
STATE_BUSY, /* TSI busy sampling */
STATE_STOP, /* sample available */
STATE_WAIT, /* Wait to start next sample */
};
enum {
EVENT_PEN_DOWN,
EVENT_PEN_UP,
EVENT_TSI_READY,
EVENT_TIMEDOUT,
};
struct da9034_touch {
struct device *da9034_dev;
struct input_dev *input_dev;
struct delayed_work tsi_work;
struct notifier_block notifier;
int state;
int interval_ms;
int x_inverted;
int y_inverted;
int last_x;
int last_y;
};
static inline int is_pen_down(struct da9034_touch *touch)
{
return da903x_query_status(touch->da9034_dev, DA9034_STATUS_PEN_DOWN);
}
static inline int detect_pen_down(struct da9034_touch *touch, int on)
{
if (on)
return da903x_set_bits(touch->da9034_dev,
DA9034_AUTO_CTRL2, DA9034_PEN_DETECT);
else
return da903x_clr_bits(touch->da9034_dev,
DA9034_AUTO_CTRL2, DA9034_PEN_DETECT);
}
static int read_tsi(struct da9034_touch *touch)
{
uint8_t _x, _y, _v;
int ret;
ret = da903x_read(touch->da9034_dev, DA9034_TSI_X_MSB, &_x);
if (ret)
return ret;
ret = da903x_read(touch->da9034_dev, DA9034_TSI_Y_MSB, &_y);
if (ret)
return ret;
ret = da903x_read(touch->da9034_dev, DA9034_TSI_XY_LSB, &_v);
if (ret)
return ret;
touch->last_x = ((_x << 2) & 0x3fc) | (_v & 0x3);
touch->last_y = ((_y << 2) & 0x3fc) | ((_v & 0xc) >> 2);
return 0;
}
static inline int start_tsi(struct da9034_touch *touch)
{
return da903x_set_bits(touch->da9034_dev,
DA9034_AUTO_CTRL2, DA9034_AUTO_TSI_EN);
}
static inline int stop_tsi(struct da9034_touch *touch)
{
return da903x_clr_bits(touch->da9034_dev,
DA9034_AUTO_CTRL2, DA9034_AUTO_TSI_EN);
}
static inline void report_pen_down(struct da9034_touch *touch)
{
int x = touch->last_x;
int y = touch->last_y;
x &= 0xfff;
if (touch->x_inverted)
x = 1024 - x;
y &= 0xfff;
if (touch->y_inverted)
y = 1024 - y;
input_report_abs(touch->input_dev, ABS_X, x);
input_report_abs(touch->input_dev, ABS_Y, y);
input_report_key(touch->input_dev, BTN_TOUCH, 1);
input_sync(touch->input_dev);
}
static inline void report_pen_up(struct da9034_touch *touch)
{
input_report_key(touch->input_dev, BTN_TOUCH, 0);
input_sync(touch->input_dev);
}
static void da9034_event_handler(struct da9034_touch *touch, int event)
{
int err;
switch (touch->state) {
case STATE_IDLE:
if (event != EVENT_PEN_DOWN)
break;
/* Enable auto measurement of the TSI, this will
* automatically disable pen down detection
*/
err = start_tsi(touch);
if (err)
goto err_reset;
touch->state = STATE_BUSY;
break;
case STATE_BUSY:
if (event != EVENT_TSI_READY)
break;
err = read_tsi(touch);
if (err)
goto err_reset;
/* Disable auto measurement of the TSI, so that
* pen down status will be available
*/
err = stop_tsi(touch);
if (err)
goto err_reset;
touch->state = STATE_STOP;
/* FIXME: PEN_{UP/DOWN} events are expected to be
* available by stopping TSI, but this is found not
* always true, delay and simulate such an event
* here is more reliable
*/
mdelay(1);
da9034_event_handler(touch,
is_pen_down(touch) ? EVENT_PEN_DOWN :
EVENT_PEN_UP);
break;
case STATE_STOP:
if (event == EVENT_PEN_DOWN) {
report_pen_down(touch);
schedule_delayed_work(&touch->tsi_work,
msecs_to_jiffies(touch->interval_ms));
touch->state = STATE_WAIT;
}
if (event == EVENT_PEN_UP) {
report_pen_up(touch);
touch->state = STATE_IDLE;
}
break;
case STATE_WAIT:
if (event != EVENT_TIMEDOUT)
break;
if (is_pen_down(touch)) {
start_tsi(touch);
touch->state = STATE_BUSY;
} else {
report_pen_up(touch);
touch->state = STATE_IDLE;
}
break;
}
return;
err_reset:
touch->state = STATE_IDLE;
stop_tsi(touch);
detect_pen_down(touch, 1);
}
static void da9034_tsi_work(struct work_struct *work)
{
struct da9034_touch *touch =
container_of(work, struct da9034_touch, tsi_work.work);
da9034_event_handler(touch, EVENT_TIMEDOUT);
}
static int da9034_touch_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
struct da9034_touch *touch =
container_of(nb, struct da9034_touch, notifier);
if (event & DA9034_EVENT_TSI_READY)
da9034_event_handler(touch, EVENT_TSI_READY);
if ((event & DA9034_EVENT_PEN_DOWN) && touch->state == STATE_IDLE)
da9034_event_handler(touch, EVENT_PEN_DOWN);
return 0;
}
static int da9034_touch_open(struct input_dev *dev)
{
struct da9034_touch *touch = input_get_drvdata(dev);
int ret;
ret = da903x_register_notifier(touch->da9034_dev, &touch->notifier,
DA9034_EVENT_PEN_DOWN | DA9034_EVENT_TSI_READY);
if (ret)
return -EBUSY;
/* Enable ADC LDO */
ret = da903x_set_bits(touch->da9034_dev,
DA9034_MANUAL_CTRL, DA9034_LDO_ADC_EN);
if (ret)
return ret;
/* TSI_DELAY: 3 slots, TSI_SKIP: 3 slots */
ret = da903x_write(touch->da9034_dev, DA9034_TSI_CTRL1, 0x1b);
if (ret)
return ret;
ret = da903x_write(touch->da9034_dev, DA9034_TSI_CTRL2, 0x00);
if (ret)
return ret;
touch->state = STATE_IDLE;
detect_pen_down(touch, 1);
return 0;
}
static void da9034_touch_close(struct input_dev *dev)
{
struct da9034_touch *touch = input_get_drvdata(dev);
da903x_unregister_notifier(touch->da9034_dev, &touch->notifier,
DA9034_EVENT_PEN_DOWN | DA9034_EVENT_TSI_READY);
cancel_delayed_work_sync(&touch->tsi_work);
touch->state = STATE_IDLE;
stop_tsi(touch);
detect_pen_down(touch, 0);
/* Disable ADC LDO */
da903x_clr_bits(touch->da9034_dev,
DA9034_MANUAL_CTRL, DA9034_LDO_ADC_EN);
}
static int __devinit da9034_touch_probe(struct platform_device *pdev)
{
struct da9034_touch_pdata *pdata = pdev->dev.platform_data;
struct da9034_touch *touch;
struct input_dev *input_dev;
int ret;
touch = kzalloc(sizeof(struct da9034_touch), GFP_KERNEL);
if (touch == NULL) {
dev_err(&pdev->dev, "failed to allocate driver data\n");
return -ENOMEM;
}
touch->da9034_dev = pdev->dev.parent;
if (pdata) {
touch->interval_ms = pdata->interval_ms;
touch->x_inverted = pdata->x_inverted;
touch->y_inverted = pdata->y_inverted;
} else
/* fallback into default */
touch->interval_ms = 10;
INIT_DELAYED_WORK(&touch->tsi_work, da9034_tsi_work);
touch->notifier.notifier_call = da9034_touch_notifier;
input_dev = input_allocate_device();
if (!input_dev) {
dev_err(&pdev->dev, "failed to allocate input device\n");
ret = -ENOMEM;
goto err_free_touch;
}
input_dev->name = pdev->name;
input_dev->open = da9034_touch_open;
input_dev->close = da9034_touch_close;
input_dev->dev.parent = &pdev->dev;
__set_bit(EV_ABS, input_dev->evbit);
__set_bit(ABS_X, input_dev->absbit);
__set_bit(ABS_Y, input_dev->absbit);
input_set_abs_params(input_dev, ABS_X, 0, 1023, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 1023, 0, 0);
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(BTN_TOUCH, input_dev->keybit);
touch->input_dev = input_dev;
input_set_drvdata(input_dev, touch);
ret = input_register_device(input_dev);
if (ret)
goto err_free_input;
platform_set_drvdata(pdev, touch);
return 0;
err_free_input:
input_free_device(input_dev);
err_free_touch:
kfree(touch);
return ret;
}
static int __devexit da9034_touch_remove(struct platform_device *pdev)
{
struct da9034_touch *touch = platform_get_drvdata(pdev);
input_unregister_device(touch->input_dev);
kfree(touch);
return 0;
}
static struct platform_driver da9034_touch_driver = {
.driver = {
.name = "da9034-touch",
.owner = THIS_MODULE,
},
.probe = da9034_touch_probe,
.remove = __devexit_p(da9034_touch_remove),
};
static int __init da9034_touch_init(void)
{
return platform_driver_register(&da9034_touch_driver);
}
module_init(da9034_touch_init);
static void __exit da9034_touch_exit(void)
{
platform_driver_unregister(&da9034_touch_driver);
}
module_exit(da9034_touch_exit);
MODULE_DESCRIPTION("Touchscreen driver for Dialog Semiconductor DA9034");
MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>, Bin Yang <bin.yang@marvell.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:da9034-touch");

View File

@@ -0,0 +1,302 @@
/*
* Touch Screen driver for EETI's I2C connected touch screen panels
* Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
*
* See EETI's software guide for the protocol specification:
* http://home.eeti.com.tw/web20/eg/guide.htm
*
* Based on migor_ts.c
* Copyright (c) 2008 Magnus Damm
* Copyright (c) 2007 Ujjwal Pande <ujjwal@kenati.com>
*
* 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/moduleparam.h>
#include <linux/kernel.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/timer.h>
#include <linux/gpio.h>
#include <linux/input/eeti_ts.h>
static int flip_x;
module_param(flip_x, bool, 0644);
MODULE_PARM_DESC(flip_x, "flip x coordinate");
static int flip_y;
module_param(flip_y, bool, 0644);
MODULE_PARM_DESC(flip_y, "flip y coordinate");
struct eeti_ts_priv {
struct i2c_client *client;
struct input_dev *input;
struct work_struct work;
struct mutex mutex;
int irq, irq_active_high;
};
#define EETI_TS_BITDEPTH (11)
#define EETI_MAXVAL ((1 << (EETI_TS_BITDEPTH + 1)) - 1)
#define REPORT_BIT_PRESSED (1 << 0)
#define REPORT_BIT_AD0 (1 << 1)
#define REPORT_BIT_AD1 (1 << 2)
#define REPORT_BIT_HAS_PRESSURE (1 << 6)
#define REPORT_RES_BITS(v) (((v) >> 1) + EETI_TS_BITDEPTH)
static inline int eeti_ts_irq_active(struct eeti_ts_priv *priv)
{
return gpio_get_value(irq_to_gpio(priv->irq)) == priv->irq_active_high;
}
static void eeti_ts_read(struct work_struct *work)
{
char buf[6];
unsigned int x, y, res, pressed, to = 100;
struct eeti_ts_priv *priv =
container_of(work, struct eeti_ts_priv, work);
mutex_lock(&priv->mutex);
while (eeti_ts_irq_active(priv) && --to)
i2c_master_recv(priv->client, buf, sizeof(buf));
if (!to) {
dev_err(&priv->client->dev,
"unable to clear IRQ - line stuck?\n");
goto out;
}
/* drop non-report packets */
if (!(buf[0] & 0x80))
goto out;
pressed = buf[0] & REPORT_BIT_PRESSED;
res = REPORT_RES_BITS(buf[0] & (REPORT_BIT_AD0 | REPORT_BIT_AD1));
x = buf[2] | (buf[1] << 8);
y = buf[4] | (buf[3] << 8);
/* fix the range to 11 bits */
x >>= res - EETI_TS_BITDEPTH;
y >>= res - EETI_TS_BITDEPTH;
if (flip_x)
x = EETI_MAXVAL - x;
if (flip_y)
y = EETI_MAXVAL - y;
if (buf[0] & REPORT_BIT_HAS_PRESSURE)
input_report_abs(priv->input, ABS_PRESSURE, buf[5]);
input_report_abs(priv->input, ABS_X, x);
input_report_abs(priv->input, ABS_Y, y);
input_report_key(priv->input, BTN_TOUCH, !!pressed);
input_sync(priv->input);
out:
mutex_unlock(&priv->mutex);
}
static irqreturn_t eeti_ts_isr(int irq, void *dev_id)
{
struct eeti_ts_priv *priv = dev_id;
/* postpone I2C transactions as we are atomic */
schedule_work(&priv->work);
return IRQ_HANDLED;
}
static int eeti_ts_open(struct input_dev *dev)
{
struct eeti_ts_priv *priv = input_get_drvdata(dev);
enable_irq(priv->irq);
/* Read the events once to arm the IRQ */
eeti_ts_read(&priv->work);
return 0;
}
static void eeti_ts_close(struct input_dev *dev)
{
struct eeti_ts_priv *priv = input_get_drvdata(dev);
disable_irq(priv->irq);
cancel_work_sync(&priv->work);
}
static int __devinit eeti_ts_probe(struct i2c_client *client,
const struct i2c_device_id *idp)
{
struct eeti_ts_platform_data *pdata;
struct eeti_ts_priv *priv;
struct input_dev *input;
unsigned int irq_flags;
int err = -ENOMEM;
/* In contrast to what's described in the datasheet, there seems
* to be no way of probing the presence of that device using I2C
* commands. So we need to blindly believe it is there, and wait
* for interrupts to occur. */
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(&client->dev, "failed to allocate driver data\n");
goto err0;
}
mutex_init(&priv->mutex);
input = input_allocate_device();
if (!input) {
dev_err(&client->dev, "Failed to allocate input device.\n");
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, 0, EETI_MAXVAL, 0, 0);
input_set_abs_params(input, ABS_Y, 0, EETI_MAXVAL, 0, 0);
input_set_abs_params(input, ABS_PRESSURE, 0, 0xff, 0, 0);
input->name = client->name;
input->id.bustype = BUS_I2C;
input->dev.parent = &client->dev;
input->open = eeti_ts_open;
input->close = eeti_ts_close;
priv->client = client;
priv->input = input;
priv->irq = client->irq;
pdata = client->dev.platform_data;
if (pdata)
priv->irq_active_high = pdata->irq_active_high;
irq_flags = priv->irq_active_high ?
IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING;
INIT_WORK(&priv->work, eeti_ts_read);
i2c_set_clientdata(client, priv);
input_set_drvdata(input, priv);
err = input_register_device(input);
if (err)
goto err1;
err = request_irq(priv->irq, eeti_ts_isr, irq_flags,
client->name, priv);
if (err) {
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
goto err2;
}
/* Disable the irq for now. It will be enabled once the input device
* is opened. */
disable_irq(priv->irq);
device_init_wakeup(&client->dev, 0);
return 0;
err2:
input_unregister_device(input);
input = NULL; /* so we dont try to free it below */
err1:
input_free_device(input);
i2c_set_clientdata(client, NULL);
kfree(priv);
err0:
return err;
}
static int __devexit eeti_ts_remove(struct i2c_client *client)
{
struct eeti_ts_priv *priv = i2c_get_clientdata(client);
free_irq(priv->irq, priv);
input_unregister_device(priv->input);
i2c_set_clientdata(client, NULL);
kfree(priv);
return 0;
}
#ifdef CONFIG_PM
static int eeti_ts_suspend(struct i2c_client *client, pm_message_t mesg)
{
struct eeti_ts_priv *priv = i2c_get_clientdata(client);
if (device_may_wakeup(&client->dev))
enable_irq_wake(priv->irq);
return 0;
}
static int eeti_ts_resume(struct i2c_client *client)
{
struct eeti_ts_priv *priv = i2c_get_clientdata(client);
if (device_may_wakeup(&client->dev))
disable_irq_wake(priv->irq);
return 0;
}
#else
#define eeti_ts_suspend NULL
#define eeti_ts_resume NULL
#endif
static const struct i2c_device_id eeti_ts_id[] = {
{ "eeti_ts", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, eeti_ts_id);
static struct i2c_driver eeti_ts_driver = {
.driver = {
.name = "eeti_ts",
},
.probe = eeti_ts_probe,
.remove = __devexit_p(eeti_ts_remove),
.suspend = eeti_ts_suspend,
.resume = eeti_ts_resume,
.id_table = eeti_ts_id,
};
static int __init eeti_ts_init(void)
{
return i2c_add_driver(&eeti_ts_driver);
}
static void __exit eeti_ts_exit(void)
{
i2c_del_driver(&eeti_ts_driver);
}
MODULE_DESCRIPTION("EETI Touchscreen driver");
MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
MODULE_LICENSE("GPL");
module_init(eeti_ts_init);
module_exit(eeti_ts_exit);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -0,0 +1,491 @@
/*
* 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 */
serio_write(ts->serio, 1);
serio_write(ts->serio, pwr);
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: {
// 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);

View File

@@ -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");

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -0,0 +1,319 @@
/*
* 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 <linux/gpio.h>
#include <mach/regs-ac97.h>
#include <asm/mach-types.h>
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;
static int irq;
/*
* 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)
{
unsigned int count;
schedule_timeout_uninterruptible(1);
for (count = 0; count < 16; count++)
MODR;
}
#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_report_key(wm->input_dev, BTN_TOUCH, (p != 0));
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, ret = 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);
/* IRQ driven touchscreen is used on Palm hardware */
if (machine_is_palmt5() || machine_is_palmtx() || machine_is_palmld()) {
pen_int = 1;
irq = 27;
/* There is some obscure mutant of WM9712 interbred with WM9713
* used on Palm HW */
wm->variant = WM97xx_WM1613;
} else if (machine_is_mainstone() && pen_int)
irq = 4;
if (irq) {
ret = gpio_request(irq, "Touchscreen IRQ");
if (ret)
goto out;
ret = gpio_direction_input(irq);
if (ret) {
gpio_free(irq);
goto out;
}
wm->pen_irq = gpio_to_irq(irq);
set_irq_type(wm->pen_irq, IRQ_TYPE_EDGE_BOTH);
} else /* pen irq not supported */
pen_int = 0;
/* codec specific irq config */
if (pen_int) {
switch (wm->id) {
case WM9705_ID2:
break;
case WM9712_ID2:
case WM9713_ID2:
/* use PEN_DOWN GPIO 13 to assert IRQ on GPIO line 2 */
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;
}
}
out:
return ret;
}
static void wm97xx_acc_shutdown(struct wm97xx *wm)
{
/* codec specific deconfig */
if (pen_int) {
if (irq)
gpio_free(irq);
wm->pen_irq = 0;
}
}
static void wm97xx_irq_enable(struct wm97xx *wm, int enable)
{
if (enable)
enable_irq(wm->pen_irq);
else
disable_irq_nosync(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");

View File

@@ -0,0 +1,318 @@
/*
* mcs5000_ts.c - Touchscreen driver for MELFAS MCS-5000 controller
*
* Copyright (C) 2009 Samsung Electronics Co.Ltd
* Author: Joonyoung Shim <jy0922.shim@samsung.com>
*
* Based on wm97xx-core.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.
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/i2c/mcs5000_ts.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/irq.h>
/* Registers */
#define MCS5000_TS_STATUS 0x00
#define STATUS_OFFSET 0
#define STATUS_NO (0 << STATUS_OFFSET)
#define STATUS_INIT (1 << STATUS_OFFSET)
#define STATUS_SENSING (2 << STATUS_OFFSET)
#define STATUS_COORD (3 << STATUS_OFFSET)
#define STATUS_GESTURE (4 << STATUS_OFFSET)
#define ERROR_OFFSET 4
#define ERROR_NO (0 << ERROR_OFFSET)
#define ERROR_POWER_ON_RESET (1 << ERROR_OFFSET)
#define ERROR_INT_RESET (2 << ERROR_OFFSET)
#define ERROR_EXT_RESET (3 << ERROR_OFFSET)
#define ERROR_INVALID_REG_ADDRESS (8 << ERROR_OFFSET)
#define ERROR_INVALID_REG_VALUE (9 << ERROR_OFFSET)
#define MCS5000_TS_OP_MODE 0x01
#define RESET_OFFSET 0
#define RESET_NO (0 << RESET_OFFSET)
#define RESET_EXT_SOFT (1 << RESET_OFFSET)
#define OP_MODE_OFFSET 1
#define OP_MODE_SLEEP (0 << OP_MODE_OFFSET)
#define OP_MODE_ACTIVE (1 << OP_MODE_OFFSET)
#define GESTURE_OFFSET 4
#define GESTURE_DISABLE (0 << GESTURE_OFFSET)
#define GESTURE_ENABLE (1 << GESTURE_OFFSET)
#define PROXIMITY_OFFSET 5
#define PROXIMITY_DISABLE (0 << PROXIMITY_OFFSET)
#define PROXIMITY_ENABLE (1 << PROXIMITY_OFFSET)
#define SCAN_MODE_OFFSET 6
#define SCAN_MODE_INTERRUPT (0 << SCAN_MODE_OFFSET)
#define SCAN_MODE_POLLING (1 << SCAN_MODE_OFFSET)
#define REPORT_RATE_OFFSET 7
#define REPORT_RATE_40 (0 << REPORT_RATE_OFFSET)
#define REPORT_RATE_80 (1 << REPORT_RATE_OFFSET)
#define MCS5000_TS_SENS_CTL 0x02
#define MCS5000_TS_FILTER_CTL 0x03
#define PRI_FILTER_OFFSET 0
#define SEC_FILTER_OFFSET 4
#define MCS5000_TS_X_SIZE_UPPER 0x08
#define MCS5000_TS_X_SIZE_LOWER 0x09
#define MCS5000_TS_Y_SIZE_UPPER 0x0A
#define MCS5000_TS_Y_SIZE_LOWER 0x0B
#define MCS5000_TS_INPUT_INFO 0x10
#define INPUT_TYPE_OFFSET 0
#define INPUT_TYPE_NONTOUCH (0 << INPUT_TYPE_OFFSET)
#define INPUT_TYPE_SINGLE (1 << INPUT_TYPE_OFFSET)
#define INPUT_TYPE_DUAL (2 << INPUT_TYPE_OFFSET)
#define INPUT_TYPE_PALM (3 << INPUT_TYPE_OFFSET)
#define INPUT_TYPE_PROXIMITY (7 << INPUT_TYPE_OFFSET)
#define GESTURE_CODE_OFFSET 3
#define GESTURE_CODE_NO (0 << GESTURE_CODE_OFFSET)
#define MCS5000_TS_X_POS_UPPER 0x11
#define MCS5000_TS_X_POS_LOWER 0x12
#define MCS5000_TS_Y_POS_UPPER 0x13
#define MCS5000_TS_Y_POS_LOWER 0x14
#define MCS5000_TS_Z_POS 0x15
#define MCS5000_TS_WIDTH 0x16
#define MCS5000_TS_GESTURE_VAL 0x17
#define MCS5000_TS_MODULE_REV 0x20
#define MCS5000_TS_FIRMWARE_VER 0x21
/* Touchscreen absolute values */
#define MCS5000_MAX_XC 0x3ff
#define MCS5000_MAX_YC 0x3ff
enum mcs5000_ts_read_offset {
READ_INPUT_INFO,
READ_X_POS_UPPER,
READ_X_POS_LOWER,
READ_Y_POS_UPPER,
READ_Y_POS_LOWER,
READ_BLOCK_SIZE,
};
/* Each client has this additional data */
struct mcs5000_ts_data {
struct i2c_client *client;
struct input_dev *input_dev;
const struct mcs5000_ts_platform_data *platform_data;
};
static irqreturn_t mcs5000_ts_interrupt(int irq, void *dev_id)
{
struct mcs5000_ts_data *data = dev_id;
struct i2c_client *client = data->client;
u8 buffer[READ_BLOCK_SIZE];
int err;
int x;
int y;
err = i2c_smbus_read_i2c_block_data(client, MCS5000_TS_INPUT_INFO,
READ_BLOCK_SIZE, buffer);
if (err < 0) {
dev_err(&client->dev, "%s, err[%d]\n", __func__, err);
goto out;
}
switch (buffer[READ_INPUT_INFO]) {
case INPUT_TYPE_NONTOUCH:
input_report_key(data->input_dev, BTN_TOUCH, 0);
input_sync(data->input_dev);
break;
case INPUT_TYPE_SINGLE:
x = (buffer[READ_X_POS_UPPER] << 8) | buffer[READ_X_POS_LOWER];
y = (buffer[READ_Y_POS_UPPER] << 8) | buffer[READ_Y_POS_LOWER];
input_report_key(data->input_dev, BTN_TOUCH, 1);
input_report_abs(data->input_dev, ABS_X, x);
input_report_abs(data->input_dev, ABS_Y, y);
input_sync(data->input_dev);
break;
case INPUT_TYPE_DUAL:
/* TODO */
break;
case INPUT_TYPE_PALM:
/* TODO */
break;
case INPUT_TYPE_PROXIMITY:
/* TODO */
break;
default:
dev_err(&client->dev, "Unknown ts input type %d\n",
buffer[READ_INPUT_INFO]);
break;
}
out:
return IRQ_HANDLED;
}
static void mcs5000_ts_phys_init(struct mcs5000_ts_data *data)
{
const struct mcs5000_ts_platform_data *platform_data =
data->platform_data;
struct i2c_client *client = data->client;
/* Touch reset & sleep mode */
i2c_smbus_write_byte_data(client, MCS5000_TS_OP_MODE,
RESET_EXT_SOFT | OP_MODE_SLEEP);
/* Touch size */
i2c_smbus_write_byte_data(client, MCS5000_TS_X_SIZE_UPPER,
platform_data->x_size >> 8);
i2c_smbus_write_byte_data(client, MCS5000_TS_X_SIZE_LOWER,
platform_data->x_size & 0xff);
i2c_smbus_write_byte_data(client, MCS5000_TS_Y_SIZE_UPPER,
platform_data->y_size >> 8);
i2c_smbus_write_byte_data(client, MCS5000_TS_Y_SIZE_LOWER,
platform_data->y_size & 0xff);
/* Touch active mode & 80 report rate */
i2c_smbus_write_byte_data(data->client, MCS5000_TS_OP_MODE,
OP_MODE_ACTIVE | REPORT_RATE_80);
}
static int __devinit mcs5000_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct mcs5000_ts_data *data;
struct input_dev *input_dev;
int ret;
if (!client->dev.platform_data)
return -EINVAL;
data = kzalloc(sizeof(struct mcs5000_ts_data), GFP_KERNEL);
input_dev = input_allocate_device();
if (!data || !input_dev) {
dev_err(&client->dev, "Failed to allocate memory\n");
ret = -ENOMEM;
goto err_free_mem;
}
data->client = client;
data->input_dev = input_dev;
data->platform_data = client->dev.platform_data;
input_dev->name = "MELPAS MCS-5000 Touchscreen";
input_dev->id.bustype = BUS_I2C;
input_dev->dev.parent = &client->dev;
__set_bit(EV_ABS, input_dev->evbit);
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(BTN_TOUCH, input_dev->keybit);
input_set_abs_params(input_dev, ABS_X, 0, MCS5000_MAX_XC, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, MCS5000_MAX_YC, 0, 0);
input_set_drvdata(input_dev, data);
if (data->platform_data->cfg_pin)
data->platform_data->cfg_pin();
ret = request_threaded_irq(client->irq, NULL, mcs5000_ts_interrupt,
IRQF_TRIGGER_LOW | IRQF_ONESHOT, "mcs5000_ts", data);
if (ret < 0) {
dev_err(&client->dev, "Failed to register interrupt\n");
goto err_free_mem;
}
ret = input_register_device(data->input_dev);
if (ret < 0)
goto err_free_irq;
mcs5000_ts_phys_init(data);
i2c_set_clientdata(client, data);
return 0;
err_free_irq:
free_irq(client->irq, data);
err_free_mem:
input_free_device(input_dev);
kfree(data);
return ret;
}
static int __devexit mcs5000_ts_remove(struct i2c_client *client)
{
struct mcs5000_ts_data *data = i2c_get_clientdata(client);
free_irq(client->irq, data);
input_unregister_device(data->input_dev);
kfree(data);
i2c_set_clientdata(client, NULL);
return 0;
}
#ifdef CONFIG_PM
static int mcs5000_ts_suspend(struct i2c_client *client, pm_message_t mesg)
{
/* Touch sleep mode */
i2c_smbus_write_byte_data(client, MCS5000_TS_OP_MODE, OP_MODE_SLEEP);
return 0;
}
static int mcs5000_ts_resume(struct i2c_client *client)
{
struct mcs5000_ts_data *data = i2c_get_clientdata(client);
mcs5000_ts_phys_init(data);
return 0;
}
#else
#define mcs5000_ts_suspend NULL
#define mcs5000_ts_resume NULL
#endif
static const struct i2c_device_id mcs5000_ts_id[] = {
{ "mcs5000_ts", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, mcs5000_ts_id);
static struct i2c_driver mcs5000_ts_driver = {
.probe = mcs5000_ts_probe,
.remove = __devexit_p(mcs5000_ts_remove),
.suspend = mcs5000_ts_suspend,
.resume = mcs5000_ts_resume,
.driver = {
.name = "mcs5000_ts",
},
.id_table = mcs5000_ts_id,
};
static int __init mcs5000_ts_init(void)
{
return i2c_add_driver(&mcs5000_ts_driver);
}
static void __exit mcs5000_ts_exit(void)
{
i2c_del_driver(&mcs5000_ts_driver);
}
module_init(mcs5000_ts_init);
module_exit(mcs5000_ts_exit);
/* Module information */
MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>");
MODULE_DESCRIPTION("Touchscreen driver for MELFAS MCS-5000 controller");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,280 @@
/*
* 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;
}
device_init_wakeup(&client->dev, 1);
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 int migor_ts_suspend(struct i2c_client *client, pm_message_t mesg)
{
struct migor_ts_priv *priv = dev_get_drvdata(&client->dev);
if (device_may_wakeup(&client->dev))
enable_irq_wake(priv->irq);
return 0;
}
static int migor_ts_resume(struct i2c_client *client)
{
struct migor_ts_priv *priv = dev_get_drvdata(&client->dev);
if (device_may_wakeup(&client->dev))
disable_irq_wake(priv->irq);
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,
.suspend = migor_ts_suspend,
.resume = migor_ts_resume,
.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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -0,0 +1,271 @@
/*
* Driver for Motorola PCAP2 touchscreen as found in the EZX phone platform.
*
* Copyright (C) 2006 Harald Welte <laforge@openezx.org>
* Copyright (C) 2009 Daniel Ribeiro <drwyrm@gmail.com>
*
* 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/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/pm.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/mfd/ezx-pcap.h>
struct pcap_ts {
struct pcap_chip *pcap;
struct input_dev *input;
struct delayed_work work;
u16 x, y;
u16 pressure;
u8 read_state;
};
#define SAMPLE_DELAY 20 /* msecs */
#define X_AXIS_MIN 0
#define X_AXIS_MAX 1023
#define Y_AXIS_MAX X_AXIS_MAX
#define Y_AXIS_MIN X_AXIS_MIN
#define PRESSURE_MAX X_AXIS_MAX
#define PRESSURE_MIN X_AXIS_MIN
static void pcap_ts_read_xy(void *data, u16 res[2])
{
struct pcap_ts *pcap_ts = data;
switch (pcap_ts->read_state) {
case PCAP_ADC_TS_M_PRESSURE:
/* pressure reading is unreliable */
if (res[0] > PRESSURE_MIN && res[0] < PRESSURE_MAX)
pcap_ts->pressure = res[0];
pcap_ts->read_state = PCAP_ADC_TS_M_XY;
schedule_delayed_work(&pcap_ts->work, 0);
break;
case PCAP_ADC_TS_M_XY:
pcap_ts->y = res[0];
pcap_ts->x = res[1];
if (pcap_ts->x <= X_AXIS_MIN || pcap_ts->x >= X_AXIS_MAX ||
pcap_ts->y <= Y_AXIS_MIN || pcap_ts->y >= Y_AXIS_MAX) {
/* pen has been released */
input_report_abs(pcap_ts->input, ABS_PRESSURE, 0);
input_report_key(pcap_ts->input, BTN_TOUCH, 0);
pcap_ts->read_state = PCAP_ADC_TS_M_STANDBY;
schedule_delayed_work(&pcap_ts->work, 0);
} else {
/* pen is touching the screen */
input_report_abs(pcap_ts->input, ABS_X, pcap_ts->x);
input_report_abs(pcap_ts->input, ABS_Y, pcap_ts->y);
input_report_key(pcap_ts->input, BTN_TOUCH, 1);
input_report_abs(pcap_ts->input, ABS_PRESSURE,
pcap_ts->pressure);
/* switch back to pressure read mode */
pcap_ts->read_state = PCAP_ADC_TS_M_PRESSURE;
schedule_delayed_work(&pcap_ts->work,
msecs_to_jiffies(SAMPLE_DELAY));
}
input_sync(pcap_ts->input);
break;
default:
dev_warn(&pcap_ts->input->dev,
"pcap_ts: Warning, unhandled read_state %d\n",
pcap_ts->read_state);
break;
}
}
static void pcap_ts_work(struct work_struct *work)
{
struct delayed_work *dw = container_of(work, struct delayed_work, work);
struct pcap_ts *pcap_ts = container_of(dw, struct pcap_ts, work);
u8 ch[2];
pcap_set_ts_bits(pcap_ts->pcap,
pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
if (pcap_ts->read_state == PCAP_ADC_TS_M_STANDBY)
return;
/* start adc conversion */
ch[0] = PCAP_ADC_CH_TS_X1;
ch[1] = PCAP_ADC_CH_TS_Y1;
pcap_adc_async(pcap_ts->pcap, PCAP_ADC_BANK_1, 0, ch,
pcap_ts_read_xy, pcap_ts);
}
static irqreturn_t pcap_ts_event_touch(int pirq, void *data)
{
struct pcap_ts *pcap_ts = data;
if (pcap_ts->read_state == PCAP_ADC_TS_M_STANDBY) {
pcap_ts->read_state = PCAP_ADC_TS_M_PRESSURE;
schedule_delayed_work(&pcap_ts->work, 0);
}
return IRQ_HANDLED;
}
static int pcap_ts_open(struct input_dev *dev)
{
struct pcap_ts *pcap_ts = input_get_drvdata(dev);
pcap_ts->read_state = PCAP_ADC_TS_M_STANDBY;
schedule_delayed_work(&pcap_ts->work, 0);
return 0;
}
static void pcap_ts_close(struct input_dev *dev)
{
struct pcap_ts *pcap_ts = input_get_drvdata(dev);
cancel_delayed_work_sync(&pcap_ts->work);
pcap_ts->read_state = PCAP_ADC_TS_M_NONTS;
pcap_set_ts_bits(pcap_ts->pcap,
pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
}
static int __devinit pcap_ts_probe(struct platform_device *pdev)
{
struct input_dev *input_dev;
struct pcap_ts *pcap_ts;
int err = -ENOMEM;
pcap_ts = kzalloc(sizeof(*pcap_ts), GFP_KERNEL);
if (!pcap_ts)
return err;
pcap_ts->pcap = dev_get_drvdata(pdev->dev.parent);
platform_set_drvdata(pdev, pcap_ts);
input_dev = input_allocate_device();
if (!input_dev)
goto fail;
INIT_DELAYED_WORK(&pcap_ts->work, pcap_ts_work);
pcap_ts->read_state = PCAP_ADC_TS_M_NONTS;
pcap_set_ts_bits(pcap_ts->pcap,
pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
pcap_ts->input = input_dev;
input_set_drvdata(input_dev, pcap_ts);
input_dev->name = "pcap-touchscreen";
input_dev->phys = "pcap_ts/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->open = pcap_ts_open;
input_dev->close = pcap_ts_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, 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);
err = input_register_device(pcap_ts->input);
if (err)
goto fail_allocate;
err = request_irq(pcap_to_irq(pcap_ts->pcap, PCAP_IRQ_TS),
pcap_ts_event_touch, 0, "Touch Screen", pcap_ts);
if (err)
goto fail_register;
return 0;
fail_register:
input_unregister_device(input_dev);
goto fail;
fail_allocate:
input_free_device(input_dev);
fail:
kfree(pcap_ts);
return err;
}
static int __devexit pcap_ts_remove(struct platform_device *pdev)
{
struct pcap_ts *pcap_ts = platform_get_drvdata(pdev);
free_irq(pcap_to_irq(pcap_ts->pcap, PCAP_IRQ_TS), pcap_ts);
cancel_delayed_work_sync(&pcap_ts->work);
input_unregister_device(pcap_ts->input);
kfree(pcap_ts);
return 0;
}
#ifdef CONFIG_PM
static int pcap_ts_suspend(struct device *dev)
{
struct pcap_ts *pcap_ts = dev_get_drvdata(dev);
pcap_set_ts_bits(pcap_ts->pcap, PCAP_ADC_TS_REF_LOWPWR);
return 0;
}
static int pcap_ts_resume(struct device *dev)
{
struct pcap_ts *pcap_ts = dev_get_drvdata(dev);
pcap_set_ts_bits(pcap_ts->pcap,
pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
return 0;
}
static struct dev_pm_ops pcap_ts_pm_ops = {
.suspend = pcap_ts_suspend,
.resume = pcap_ts_resume,
};
#define PCAP_TS_PM_OPS (&pcap_ts_pm_ops)
#else
#define PCAP_TS_PM_OPS NULL
#endif
static struct platform_driver pcap_ts_driver = {
.probe = pcap_ts_probe,
.remove = __devexit_p(pcap_ts_remove),
.driver = {
.name = "pcap-ts",
.owner = THIS_MODULE,
.pm = PCAP_TS_PM_OPS,
},
};
static int __init pcap_ts_init(void)
{
return platform_driver_register(&pcap_ts_driver);
}
static void __exit pcap_ts_exit(void)
{
platform_driver_unregister(&pcap_ts_driver);
}
module_init(pcap_ts_init);
module_exit(pcap_ts_exit);
MODULE_DESCRIPTION("Motorola PCAP2 touchscreen driver");
MODULE_AUTHOR("Daniel Ribeiro / Harald Welte");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pcap_ts");

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -0,0 +1,393 @@
/*
* drivers/input/touchscreen/tsc2007.c
*
* Copyright (c) 2008 MtekVision Co., Ltd.
* Kwangwoo Lee <kwlee@mtekvision.com>
*
* Using code from:
* - ads7846.c
* Copyright (c) 2005 David Brownell
* Copyright (c) 2006 Nokia Corporation
* - 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/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/i2c/tsc2007.h>
#define TS_POLL_DELAY 1 /* ms delay between samples */
#define TS_POLL_PERIOD 1 /* ms delay between samples */
#define TSC2007_MEASURE_TEMP0 (0x0 << 4)
#define TSC2007_MEASURE_AUX (0x2 << 4)
#define TSC2007_MEASURE_TEMP1 (0x4 << 4)
#define TSC2007_ACTIVATE_XN (0x8 << 4)
#define TSC2007_ACTIVATE_YN (0x9 << 4)
#define TSC2007_ACTIVATE_YP_XN (0xa << 4)
#define TSC2007_SETUP (0xb << 4)
#define TSC2007_MEASURE_X (0xc << 4)
#define TSC2007_MEASURE_Y (0xd << 4)
#define TSC2007_MEASURE_Z1 (0xe << 4)
#define TSC2007_MEASURE_Z2 (0xf << 4)
#define TSC2007_POWER_OFF_IRQ_EN (0x0 << 2)
#define TSC2007_ADC_ON_IRQ_DIS0 (0x1 << 2)
#define TSC2007_ADC_OFF_IRQ_EN (0x2 << 2)
#define TSC2007_ADC_ON_IRQ_DIS1 (0x3 << 2)
#define TSC2007_12BIT (0x0 << 1)
#define TSC2007_8BIT (0x1 << 1)
#define MAX_12BIT ((1 << 12) - 1)
#define ADC_ON_12BIT (TSC2007_12BIT | TSC2007_ADC_ON_IRQ_DIS0)
#define READ_Y (ADC_ON_12BIT | TSC2007_MEASURE_Y)
#define READ_Z1 (ADC_ON_12BIT | TSC2007_MEASURE_Z1)
#define READ_Z2 (ADC_ON_12BIT | TSC2007_MEASURE_Z2)
#define READ_X (ADC_ON_12BIT | TSC2007_MEASURE_X)
#define PWRDOWN (TSC2007_12BIT | TSC2007_POWER_OFF_IRQ_EN)
struct ts_event {
u16 x;
u16 y;
u16 z1, z2;
};
struct tsc2007 {
struct input_dev *input;
char phys[32];
struct delayed_work work;
struct i2c_client *client;
u16 model;
u16 x_plate_ohms;
bool pendown;
int irq;
int (*get_pendown_state)(void);
void (*clear_penirq)(void);
};
static inline int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
{
s32 data;
u16 val;
data = i2c_smbus_read_word_data(tsc->client, cmd);
if (data < 0) {
dev_err(&tsc->client->dev, "i2c io error: %d\n", data);
return data;
}
/* The protocol and raw data format from i2c interface:
* S Addr Wr [A] Comm [A] S Addr Rd [A] [DataLow] A [DataHigh] NA P
* Where DataLow has [D11-D4], DataHigh has [D3-D0 << 4 | Dummy 4bit].
*/
val = swab16(data) >> 4;
dev_dbg(&tsc->client->dev, "data: 0x%x, val: 0x%x\n", data, val);
return val;
}
static void tsc2007_read_values(struct tsc2007 *tsc, struct ts_event *tc)
{
/* y- still on; turn on only y+ (and ADC) */
tc->y = tsc2007_xfer(tsc, READ_Y);
/* turn y- off, x+ on, then leave in lowpower */
tc->x = tsc2007_xfer(tsc, READ_X);
/* turn y+ off, x- on; we'll use formula #1 */
tc->z1 = tsc2007_xfer(tsc, READ_Z1);
tc->z2 = tsc2007_xfer(tsc, READ_Z2);
/* Prepare for next touch reading - power down ADC, enable PENIRQ */
tsc2007_xfer(tsc, PWRDOWN);
}
static u32 tsc2007_calculate_pressure(struct tsc2007 *tsc, struct ts_event *tc)
{
u32 rt = 0;
/* range filtering */
if (tc->x == MAX_12BIT)
tc->x = 0;
if (likely(tc->x && tc->z1)) {
/* compute touch pressure resistance using equation #1 */
rt = tc->z2 - tc->z1;
rt *= tc->x;
rt *= tsc->x_plate_ohms;
rt /= tc->z1;
rt = (rt + 2047) >> 12;
}
return rt;
}
static void tsc2007_send_up_event(struct tsc2007 *tsc)
{
struct input_dev *input = tsc->input;
dev_dbg(&tsc->client->dev, "UP\n");
input_report_key(input, BTN_TOUCH, 0);
input_report_abs(input, ABS_PRESSURE, 0);
input_sync(input);
}
static void tsc2007_work(struct work_struct *work)
{
struct tsc2007 *ts =
container_of(to_delayed_work(work), struct tsc2007, work);
struct ts_event tc;
u32 rt;
/*
* NOTE: We can't rely on the pressure to determine the pen down
* state, even though 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
* work function by reading the pen signal state (it's a GPIO
* and IRQ). Unfortunately such callback is not always available,
* in that case we have rely on the pressure anyway.
*/
if (ts->get_pendown_state) {
if (unlikely(!ts->get_pendown_state())) {
tsc2007_send_up_event(ts);
ts->pendown = false;
goto out;
}
dev_dbg(&ts->client->dev, "pen is still down\n");
}
tsc2007_read_values(ts, &tc);
rt = tsc2007_calculate_pressure(ts, &tc);
if (rt > MAX_12BIT) {
/*
* 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.
*/
dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt);
goto out;
}
if (rt) {
struct input_dev *input = ts->input;
if (!ts->pendown) {
dev_dbg(&ts->client->dev, "DOWN\n");
input_report_key(input, BTN_TOUCH, 1);
ts->pendown = true;
}
input_report_abs(input, ABS_X, tc.x);
input_report_abs(input, ABS_Y, tc.y);
input_report_abs(input, ABS_PRESSURE, rt);
input_sync(input);
dev_dbg(&ts->client->dev, "point(%4d,%4d), pressure (%4u)\n",
tc.x, tc.y, rt);
} else if (!ts->get_pendown_state && ts->pendown) {
/*
* We don't have callback to check pendown state, so we
* have to assume that since pressure reported is 0 the
* pen was lifted up.
*/
tsc2007_send_up_event(ts);
ts->pendown = false;
}
out:
if (ts->pendown)
schedule_delayed_work(&ts->work,
msecs_to_jiffies(TS_POLL_PERIOD));
else
enable_irq(ts->irq);
}
static irqreturn_t tsc2007_irq(int irq, void *handle)
{
struct tsc2007 *ts = handle;
if (!ts->get_pendown_state || likely(ts->get_pendown_state())) {
disable_irq_nosync(ts->irq);
schedule_delayed_work(&ts->work,
msecs_to_jiffies(TS_POLL_DELAY));
}
if (ts->clear_penirq)
ts->clear_penirq();
return IRQ_HANDLED;
}
static void tsc2007_free_irq(struct tsc2007 *ts)
{
free_irq(ts->irq, ts);
if (cancel_delayed_work_sync(&ts->work)) {
/*
* Work was pending, therefore we need to enable
* IRQ here to balance the disable_irq() done in the
* interrupt handler.
*/
enable_irq(ts->irq);
}
}
static int __devinit tsc2007_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct tsc2007 *ts;
struct tsc2007_platform_data *pdata = pdata = client->dev.platform_data;
struct input_dev *input_dev;
int err;
if (!pdata) {
dev_err(&client->dev, "platform data is required!\n");
return -EINVAL;
}
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_WORD_DATA))
return -EIO;
ts = kzalloc(sizeof(struct tsc2007), GFP_KERNEL);
input_dev = input_allocate_device();
if (!ts || !input_dev) {
err = -ENOMEM;
goto err_free_mem;
}
ts->client = client;
ts->irq = client->irq;
ts->input = input_dev;
INIT_DELAYED_WORK(&ts->work, tsc2007_work);
ts->model = pdata->model;
ts->x_plate_ohms = pdata->x_plate_ohms;
ts->get_pendown_state = pdata->get_pendown_state;
ts->clear_penirq = pdata->clear_penirq;
snprintf(ts->phys, sizeof(ts->phys),
"%s/input0", dev_name(&client->dev));
input_dev->name = "TSC2007 Touchscreen";
input_dev->phys = ts->phys;
input_dev->id.bustype = BUS_I2C;
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, MAX_12BIT, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, 0, 0);
input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, 0, 0);
if (pdata->init_platform_hw)
pdata->init_platform_hw();
err = request_irq(ts->irq, tsc2007_irq, 0,
client->dev.driver->name, ts);
if (err < 0) {
dev_err(&client->dev, "irq %d busy?\n", ts->irq);
goto err_free_mem;
}
/* Prepare for touch readings - power down ADC and enable PENIRQ */
err = tsc2007_xfer(ts, PWRDOWN);
if (err < 0)
goto err_free_irq;
err = input_register_device(input_dev);
if (err)
goto err_free_irq;
i2c_set_clientdata(client, ts);
return 0;
err_free_irq:
tsc2007_free_irq(ts);
if (pdata->exit_platform_hw)
pdata->exit_platform_hw();
err_free_mem:
input_free_device(input_dev);
kfree(ts);
return err;
}
static int __devexit tsc2007_remove(struct i2c_client *client)
{
struct tsc2007 *ts = i2c_get_clientdata(client);
struct tsc2007_platform_data *pdata = client->dev.platform_data;
tsc2007_free_irq(ts);
if (pdata->exit_platform_hw)
pdata->exit_platform_hw();
input_unregister_device(ts->input);
kfree(ts);
return 0;
}
static struct i2c_device_id tsc2007_idtable[] = {
{ "tsc2007", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tsc2007_idtable);
static struct i2c_driver tsc2007_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "tsc2007"
},
.id_table = tsc2007_idtable,
.probe = tsc2007_probe,
.remove = __devexit_p(tsc2007_remove),
};
static int __init tsc2007_init(void)
{
return i2c_add_driver(&tsc2007_driver);
}
static void __exit tsc2007_exit(void)
{
i2c_del_driver(&tsc2007_driver);
}
module_init(tsc2007_init);
module_exit(tsc2007_exit);
MODULE_AUTHOR("Kwangwoo Lee <kwlee@mtekvision.com>");
MODULE_DESCRIPTION("TSC2007 TouchScreen Driver");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,484 @@
/*
* 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_up(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_report_key(idev, BTN_TOUCH, 1);
input_sync(idev);
}
static void ucb1400_ts_event_release(struct input_dev *idev)
{
input_report_abs(idev, ABS_PRESSURE, 0);
input_report_key(idev, BTN_TOUCH, 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);
else
dev_dbg(&ucb->ts_idev->dev, "ucb1400: unexpected IE_STATUS = %#x\n", isr);
enable_irq(ucb->irq);
}
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, &param);
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_up(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_nosync(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;
u16 fcsr;
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) | BIT_MASK(EV_KEY);
ucb->ts_idev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
/*
* Enable ADC filter to prevent horrible jitter on Colibri.
* This also further reduces jitter on boards where ADCSYNC
* pin is connected.
*/
fcsr = ucb1400_reg_read(ucb->ac97, UCB_FCSR);
ucb1400_reg_write(ucb->ac97, UCB_FCSR, fcsr | UCB_FCSR_AVE);
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 = dev->dev.platform_data;
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");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,350 @@
/*
* Copyright (c) 2008 Nuvoton technology corporation.
*
* Wan ZongShun <mcuos.com@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation;version 2 of the License.
*
*/
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/input.h>
#include <linux/interrupt.h>
/* ADC controller bit defines */
#define ADC_DELAY 0xf00
#define ADC_DOWN 0x01
#define ADC_TSC_Y (0x01 << 8)
#define ADC_TSC_X (0x00 << 8)
#define TSC_FOURWIRE (~(0x03 << 1))
#define ADC_CLK_EN (0x01 << 28) /* ADC clock enable */
#define ADC_READ_CON (0x01 << 12)
#define ADC_CONV (0x01 << 13)
#define ADC_SEMIAUTO (0x01 << 14)
#define ADC_WAITTRIG (0x03 << 14)
#define ADC_RST1 (0x01 << 16)
#define ADC_RST0 (0x00 << 16)
#define ADC_EN (0x01 << 17)
#define ADC_INT (0x01 << 18)
#define WT_INT (0x01 << 20)
#define ADC_INT_EN (0x01 << 21)
#define LVD_INT_EN (0x01 << 22)
#define WT_INT_EN (0x01 << 23)
#define ADC_DIV (0x04 << 1) /* div = 6 */
enum ts_state {
TS_WAIT_NEW_PACKET, /* We are waiting next touch report */
TS_WAIT_X_COORD, /* We are waiting for ADC to report X coord */
TS_WAIT_Y_COORD, /* We are waiting for ADC to report Y coord */
TS_IDLE, /* Input device is closed, don't do anything */
};
struct w90p910_ts {
struct input_dev *input;
struct timer_list timer;
struct clk *clk;
int irq_num;
void __iomem *ts_reg;
spinlock_t lock;
enum ts_state state;
};
static void w90p910_report_event(struct w90p910_ts *w90p910_ts, bool down)
{
struct input_dev *dev = w90p910_ts->input;
if (down) {
input_report_abs(dev, ABS_X,
__raw_readl(w90p910_ts->ts_reg + 0x0c));
input_report_abs(dev, ABS_Y,
__raw_readl(w90p910_ts->ts_reg + 0x10));
}
input_report_key(dev, BTN_TOUCH, down);
input_sync(dev);
}
static void w90p910_prepare_x_reading(struct w90p910_ts *w90p910_ts)
{
unsigned long ctlreg;
__raw_writel(ADC_TSC_X, w90p910_ts->ts_reg + 0x04);
ctlreg = __raw_readl(w90p910_ts->ts_reg);
ctlreg &= ~(ADC_WAITTRIG | WT_INT | WT_INT_EN);
ctlreg |= ADC_SEMIAUTO | ADC_INT_EN | ADC_CONV;
__raw_writel(ctlreg, w90p910_ts->ts_reg);
w90p910_ts->state = TS_WAIT_X_COORD;
}
static void w90p910_prepare_y_reading(struct w90p910_ts *w90p910_ts)
{
unsigned long ctlreg;
__raw_writel(ADC_TSC_Y, w90p910_ts->ts_reg + 0x04);
ctlreg = __raw_readl(w90p910_ts->ts_reg);
ctlreg &= ~(ADC_WAITTRIG | ADC_INT | WT_INT_EN);
ctlreg |= ADC_SEMIAUTO | ADC_INT_EN | ADC_CONV;
__raw_writel(ctlreg, w90p910_ts->ts_reg);
w90p910_ts->state = TS_WAIT_Y_COORD;
}
static void w90p910_prepare_next_packet(struct w90p910_ts *w90p910_ts)
{
unsigned long ctlreg;
ctlreg = __raw_readl(w90p910_ts->ts_reg);
ctlreg &= ~(ADC_INT | ADC_INT_EN | ADC_SEMIAUTO | ADC_CONV);
ctlreg |= ADC_WAITTRIG | WT_INT_EN;
__raw_writel(ctlreg, w90p910_ts->ts_reg);
w90p910_ts->state = TS_WAIT_NEW_PACKET;
}
static irqreturn_t w90p910_ts_interrupt(int irq, void *dev_id)
{
struct w90p910_ts *w90p910_ts = dev_id;
unsigned long flags;
spin_lock_irqsave(&w90p910_ts->lock, flags);
switch (w90p910_ts->state) {
case TS_WAIT_NEW_PACKET:
/*
* The controller only generates interrupts when pen
* is down.
*/
del_timer(&w90p910_ts->timer);
w90p910_prepare_x_reading(w90p910_ts);
break;
case TS_WAIT_X_COORD:
w90p910_prepare_y_reading(w90p910_ts);
break;
case TS_WAIT_Y_COORD:
w90p910_report_event(w90p910_ts, true);
w90p910_prepare_next_packet(w90p910_ts);
mod_timer(&w90p910_ts->timer, jiffies + msecs_to_jiffies(100));
break;
case TS_IDLE:
break;
}
spin_unlock_irqrestore(&w90p910_ts->lock, flags);
return IRQ_HANDLED;
}
static void w90p910_check_pen_up(unsigned long data)
{
struct w90p910_ts *w90p910_ts = (struct w90p910_ts *) data;
unsigned long flags;
spin_lock_irqsave(&w90p910_ts->lock, flags);
if (w90p910_ts->state == TS_WAIT_NEW_PACKET &&
!(__raw_readl(w90p910_ts->ts_reg + 0x04) & ADC_DOWN)) {
w90p910_report_event(w90p910_ts, false);
}
spin_unlock_irqrestore(&w90p910_ts->lock, flags);
}
static int w90p910_open(struct input_dev *dev)
{
struct w90p910_ts *w90p910_ts = input_get_drvdata(dev);
unsigned long val;
/* enable the ADC clock */
clk_enable(w90p910_ts->clk);
__raw_writel(ADC_RST1, w90p910_ts->ts_reg);
msleep(1);
__raw_writel(ADC_RST0, w90p910_ts->ts_reg);
msleep(1);
/* set delay and screen type */
val = __raw_readl(w90p910_ts->ts_reg + 0x04);
__raw_writel(val & TSC_FOURWIRE, w90p910_ts->ts_reg + 0x04);
__raw_writel(ADC_DELAY, w90p910_ts->ts_reg + 0x08);
w90p910_ts->state = TS_WAIT_NEW_PACKET;
wmb();
/* set trigger mode */
val = __raw_readl(w90p910_ts->ts_reg);
val |= ADC_WAITTRIG | ADC_DIV | ADC_EN | WT_INT_EN;
__raw_writel(val, w90p910_ts->ts_reg);
return 0;
}
static void w90p910_close(struct input_dev *dev)
{
struct w90p910_ts *w90p910_ts = input_get_drvdata(dev);
unsigned long val;
/* disable trigger mode */
spin_lock_irq(&w90p910_ts->lock);
w90p910_ts->state = TS_IDLE;
val = __raw_readl(w90p910_ts->ts_reg);
val &= ~(ADC_WAITTRIG | ADC_DIV | ADC_EN | WT_INT_EN | ADC_INT_EN);
__raw_writel(val, w90p910_ts->ts_reg);
spin_unlock_irq(&w90p910_ts->lock);
/* Now that interrupts are shut off we can safely delete timer */
del_timer_sync(&w90p910_ts->timer);
/* stop the ADC clock */
clk_disable(w90p910_ts->clk);
}
static int __devinit w90x900ts_probe(struct platform_device *pdev)
{
struct w90p910_ts *w90p910_ts;
struct input_dev *input_dev;
struct resource *res;
int err;
w90p910_ts = kzalloc(sizeof(struct w90p910_ts), GFP_KERNEL);
input_dev = input_allocate_device();
if (!w90p910_ts || !input_dev) {
err = -ENOMEM;
goto fail1;
}
w90p910_ts->input = input_dev;
w90p910_ts->state = TS_IDLE;
spin_lock_init(&w90p910_ts->lock);
setup_timer(&w90p910_ts->timer, w90p910_check_pen_up,
(unsigned long)&w90p910_ts);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
err = -ENXIO;
goto fail1;
}
if (!request_mem_region(res->start, resource_size(res),
pdev->name)) {
err = -EBUSY;
goto fail1;
}
w90p910_ts->ts_reg = ioremap(res->start, resource_size(res));
if (!w90p910_ts->ts_reg) {
err = -ENOMEM;
goto fail2;
}
w90p910_ts->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(w90p910_ts->clk)) {
err = PTR_ERR(w90p910_ts->clk);
goto fail3;
}
input_dev->name = "W90P910 TouchScreen";
input_dev->phys = "w90p910ts/event0";
input_dev->id.bustype = BUS_HOST;
input_dev->id.vendor = 0x0005;
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0100;
input_dev->dev.parent = &pdev->dev;
input_dev->open = w90p910_open;
input_dev->close = w90p910_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, 0, 0x400, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 0x400, 0, 0);
input_set_drvdata(input_dev, w90p910_ts);
w90p910_ts->irq_num = platform_get_irq(pdev, 0);
if (request_irq(w90p910_ts->irq_num, w90p910_ts_interrupt,
IRQF_DISABLED, "w90p910ts", w90p910_ts)) {
err = -EBUSY;
goto fail4;
}
err = input_register_device(w90p910_ts->input);
if (err)
goto fail5;
platform_set_drvdata(pdev, w90p910_ts);
return 0;
fail5: free_irq(w90p910_ts->irq_num, w90p910_ts);
fail4: clk_put(w90p910_ts->clk);
fail3: iounmap(w90p910_ts->ts_reg);
fail2: release_mem_region(res->start, resource_size(res));
fail1: input_free_device(input_dev);
kfree(w90p910_ts);
return err;
}
static int __devexit w90x900ts_remove(struct platform_device *pdev)
{
struct w90p910_ts *w90p910_ts = platform_get_drvdata(pdev);
struct resource *res;
free_irq(w90p910_ts->irq_num, w90p910_ts);
del_timer_sync(&w90p910_ts->timer);
iounmap(w90p910_ts->ts_reg);
clk_put(w90p910_ts->clk);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
release_mem_region(res->start, resource_size(res));
input_unregister_device(w90p910_ts->input);
kfree(w90p910_ts);
platform_set_drvdata(pdev, NULL);
return 0;
}
static struct platform_driver w90x900ts_driver = {
.probe = w90x900ts_probe,
.remove = __devexit_p(w90x900ts_remove),
.driver = {
.name = "nuc900-ts",
.owner = THIS_MODULE,
},
};
static int __init w90x900ts_init(void)
{
return platform_driver_register(&w90x900ts_driver);
}
static void __exit w90x900ts_exit(void)
{
platform_driver_unregister(&w90x900ts_driver);
}
module_init(w90x900ts_init);
module_exit(w90x900ts_exit);
MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>");
MODULE_DESCRIPTION("w90p910 touch screen driver!");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:nuc900-ts");

View File

@@ -0,0 +1,284 @@
/*
* Wacom W8001 penabled serial touchscreen driver
*
* Copyright (c) 2008 Jaya Kumar
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*
* Layout based on Elo serial touchscreen driver by Vojtech Pavlik
*/
#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 "Wacom W8001 serial touchscreen driver"
MODULE_AUTHOR("Jaya Kumar <jayakumar.lkml@gmail.com>");
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
#define W8001_MAX_LENGTH 11
#define W8001_LEAD_MASK 0x80
#define W8001_LEAD_BYTE 0x80
#define W8001_TAB_MASK 0x40
#define W8001_TAB_BYTE 0x40
#define W8001_QUERY_PACKET 0x20
#define W8001_CMD_START '1'
#define W8001_CMD_QUERY '*'
struct w8001_coord {
u8 rdy;
u8 tsw;
u8 f1;
u8 f2;
u16 x;
u16 y;
u16 pen_pressure;
u8 tilt_x;
u8 tilt_y;
};
/*
* Per-touchscreen data.
*/
struct w8001 {
struct input_dev *dev;
struct serio *serio;
struct completion cmd_done;
int id;
int idx;
unsigned char response_type;
unsigned char response[W8001_MAX_LENGTH];
unsigned char data[W8001_MAX_LENGTH];
char phys[32];
};
static void parse_data(u8 *data, struct w8001_coord *coord)
{
memset(coord, 0, sizeof(*coord));
coord->rdy = data[0] & 0x20;
coord->tsw = data[0] & 0x01;
coord->f1 = data[0] & 0x02;
coord->f2 = data[0] & 0x04;
coord->x = (data[1] & 0x7F) << 9;
coord->x |= (data[2] & 0x7F) << 2;
coord->x |= (data[6] & 0x60) >> 5;
coord->y = (data[3] & 0x7F) << 9;
coord->y |= (data[4] & 0x7F) << 2;
coord->y |= (data[6] & 0x18) >> 3;
coord->pen_pressure = data[5] & 0x7F;
coord->pen_pressure |= (data[6] & 0x07) << 7 ;
coord->tilt_x = data[7] & 0x7F;
coord->tilt_y = data[8] & 0x7F;
}
static irqreturn_t w8001_interrupt(struct serio *serio,
unsigned char data, unsigned int flags)
{
struct w8001 *w8001 = serio_get_drvdata(serio);
struct input_dev *dev = w8001->dev;
struct w8001_coord coord;
unsigned char tmp;
w8001->data[w8001->idx] = data;
switch (w8001->idx++) {
case 0:
if ((data & W8001_LEAD_MASK) != W8001_LEAD_BYTE) {
pr_debug("w8001: unsynchronized data: 0x%02x\n", data);
w8001->idx = 0;
}
break;
case 8:
tmp = w8001->data[0] & W8001_TAB_MASK;
if (unlikely(tmp == W8001_TAB_BYTE))
break;
w8001->idx = 0;
parse_data(w8001->data, &coord);
input_report_abs(dev, ABS_X, coord.x);
input_report_abs(dev, ABS_Y, coord.y);
input_report_abs(dev, ABS_PRESSURE, coord.pen_pressure);
input_report_key(dev, BTN_TOUCH, coord.tsw);
input_sync(dev);
break;
case 10:
w8001->idx = 0;
memcpy(w8001->response, w8001->data, W8001_MAX_LENGTH);
w8001->response_type = W8001_QUERY_PACKET;
complete(&w8001->cmd_done);
break;
}
return IRQ_HANDLED;
}
static int w8001_command(struct w8001 *w8001, unsigned char command,
bool wait_response)
{
int rc;
w8001->response_type = 0;
init_completion(&w8001->cmd_done);
rc = serio_write(w8001->serio, command);
if (rc == 0 && wait_response) {
wait_for_completion_timeout(&w8001->cmd_done, HZ);
if (w8001->response_type != W8001_QUERY_PACKET)
rc = -EIO;
}
return rc;
}
static int w8001_setup(struct w8001 *w8001)
{
struct input_dev *dev = w8001->dev;
struct w8001_coord coord;
int error;
error = w8001_command(w8001, W8001_CMD_QUERY, true);
if (error)
return error;
parse_data(w8001->response, &coord);
input_set_abs_params(dev, ABS_X, 0, coord.x, 0, 0);
input_set_abs_params(dev, ABS_Y, 0, coord.y, 0, 0);
input_set_abs_params(dev, ABS_PRESSURE, 0, coord.pen_pressure, 0, 0);
input_set_abs_params(dev, ABS_TILT_X, 0, coord.tilt_x, 0, 0);
input_set_abs_params(dev, ABS_TILT_Y, 0, coord.tilt_y, 0, 0);
return w8001_command(w8001, W8001_CMD_START, false);
}
/*
* w8001_disconnect() is the opposite of w8001_connect()
*/
static void w8001_disconnect(struct serio *serio)
{
struct w8001 *w8001 = serio_get_drvdata(serio);
input_get_device(w8001->dev);
input_unregister_device(w8001->dev);
serio_close(serio);
serio_set_drvdata(serio, NULL);
input_put_device(w8001->dev);
kfree(w8001);
}
/*
* w8001_connect() is the routine that is called when someone adds a
* new serio device that supports the w8001 protocol and registers it as
* an input device.
*/
static int w8001_connect(struct serio *serio, struct serio_driver *drv)
{
struct w8001 *w8001;
struct input_dev *input_dev;
int err;
w8001 = kzalloc(sizeof(struct w8001), GFP_KERNEL);
input_dev = input_allocate_device();
if (!w8001 || !input_dev) {
err = -ENOMEM;
goto fail1;
}
w8001->serio = serio;
w8001->id = serio->id.id;
w8001->dev = input_dev;
init_completion(&w8001->cmd_done);
snprintf(w8001->phys, sizeof(w8001->phys), "%s/input0", serio->phys);
input_dev->name = "Wacom W8001 Penabled Serial TouchScreen";
input_dev->phys = w8001->phys;
input_dev->id.bustype = BUS_RS232;
input_dev->id.vendor = SERIO_W8001;
input_dev->id.product = w8001->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, w8001);
err = serio_open(serio, drv);
if (err)
goto fail2;
err = w8001_setup(w8001);
if (err)
goto fail3;
err = input_register_device(w8001->dev);
if (err)
goto fail3;
return 0;
fail3:
serio_close(serio);
fail2:
serio_set_drvdata(serio, NULL);
fail1:
input_free_device(input_dev);
kfree(w8001);
return err;
}
static struct serio_device_id w8001_serio_ids[] = {
{
.type = SERIO_RS232,
.proto = SERIO_W8001,
.id = SERIO_ANY,
.extra = SERIO_ANY,
},
{ 0 }
};
MODULE_DEVICE_TABLE(serio, w8001_serio_ids);
static struct serio_driver w8001_drv = {
.driver = {
.name = "w8001",
},
.description = DRIVER_DESC,
.id_table = w8001_serio_ids,
.interrupt = w8001_interrupt,
.connect = w8001_connect,
.disconnect = w8001_disconnect,
};
static int __init w8001_init(void)
{
return serio_register_driver(&w8001_drv);
}
static void __exit w8001_exit(void)
{
serio_unregister_driver(&w8001_drv);
}
module_init(w8001_init);
module_exit(w8001_exit);

View File

@@ -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");

View File

@@ -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");

View File

@@ -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");

View File

@@ -0,0 +1,837 @@
/*
* 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 && wm->variant != WM97xx_WM1613)
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 && wm->variant != WM97xx_WM1613)
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,
"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_report_key(wm->input_dev, BTN_TOUCH, 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_report_key(wm->input_dev, BTN_TOUCH, 1);
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;
struct wm97xx_pdata *pdata = dev->platform_data;
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_set_drvdata(dev, 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);
wm->variant = WM97xx_GENERIC;
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(EV_KEY, wm->input_dev->evbit);
__set_bit(BTN_TOUCH, wm->input_dev->keybit);
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;
wm->battery_dev->dev.platform_data = pdata;
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;
wm->touch_dev->dev.platform_data = pdata;
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");

View File

@@ -0,0 +1,240 @@
/*
* zylonite-wm97xx.c -- Zylonite Continuous Touch screen driver
*
* Copyright 2004, 2007, 2008 Wolfson Microelectronics PLC.
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
* 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 supporting interrupt driven
* and continuous operation on Marvell Zylonite development systems
* (which have a WM9713 on board).
*/
#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/io.h>
#include <linux/wm97xx.h>
#include <mach/hardware.h>
#include <mach/mfp.h>
#include <mach/regs-ac97.h>
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[] = {
{ 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;
/*
* 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)");
/*
* 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 machines */
static void wm97xx_acc_pen_up(struct wm97xx *wm)
{
int i;
msleep(1);
for (i = 0; i < 16; i++)
MODR;
}
static int wm97xx_acc_pen_down(struct wm97xx *wm)
{
u16 x, y, p = 0x100 | WM97XX_ADCSEL_PRES;
int reads = 0;
static u16 last, tries;
/* 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.
*/
msleep(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_report_key(wm->input_dev, BTN_TOUCH, (p != 0));
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;
/* 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,
"zylonite accelerated touchscreen driver, %d samples/sec\n",
cinfo[sp_idx].speed);
return 0;
}
static void wm97xx_irq_enable(struct wm97xx *wm, int enable)
{
if (enable)
enable_irq(wm->pen_irq);
else
disable_irq_nosync(wm->pen_irq);
}
static struct wm97xx_mach_ops zylonite_mach_ops = {
.acc_enabled = 1,
.acc_pen_up = wm97xx_acc_pen_up,
.acc_pen_down = wm97xx_acc_pen_down,
.acc_startup = wm97xx_acc_startup,
.irq_enable = wm97xx_irq_enable,
.irq_gpio = WM97XX_GPIO_2,
};
static int zylonite_wm97xx_probe(struct platform_device *pdev)
{
struct wm97xx *wm = platform_get_drvdata(pdev);
int gpio_touch_irq;
if (cpu_is_pxa320())
gpio_touch_irq = mfp_to_gpio(MFP_PIN_GPIO15);
else
gpio_touch_irq = mfp_to_gpio(MFP_PIN_GPIO26);
wm->pen_irq = IRQ_GPIO(gpio_touch_irq);
set_irq_type(IRQ_GPIO(gpio_touch_irq), IRQ_TYPE_EDGE_BOTH);
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);
return wm97xx_register_mach_ops(wm, &zylonite_mach_ops);
}
static int zylonite_wm97xx_remove(struct platform_device *pdev)
{
struct wm97xx *wm = platform_get_drvdata(pdev);
wm97xx_unregister_mach_ops(wm);
return 0;
}
static struct platform_driver zylonite_wm97xx_driver = {
.probe = zylonite_wm97xx_probe,
.remove = zylonite_wm97xx_remove,
.driver = {
.name = "wm97xx-touch",
},
};
static int __init zylonite_wm97xx_init(void)
{
return platform_driver_register(&zylonite_wm97xx_driver);
}
static void __exit zylonite_wm97xx_exit(void)
{
platform_driver_unregister(&zylonite_wm97xx_driver);
}
module_init(zylonite_wm97xx_init);
module_exit(zylonite_wm97xx_exit);
/* Module information */
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_DESCRIPTION("wm97xx continuous touch driver for Zylonite");
MODULE_LICENSE("GPL");