2005-09-11 13:26:57 +04:00
/*
2005-09-11 13:28:00 +04:00
* Touchscreen driver for UCB1x00 - based touchscreens
2005-09-11 13:26:57 +04:00
*
* Copyright ( C ) 2001 Russell King , All Rights Reserved .
2005-09-11 13:28:00 +04:00
* Copyright ( C ) 2005 Pavel Machek
2005-09-11 13:26:57 +04:00
*
* 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 .
*
* 21 - Jan - 2002 < jco @ ict . es > :
*
* Added support for synchronous A / D mode . This mode is useful to
* avoid noise induced in the touchpanel by the LCD , provided that
* the UCB1x00 has a valid LCD sync signal routed to its ADCSYNC pin .
* It is important to note that the signal connected to the ADCSYNC
* pin should provide pulses even when the LCD is blanked , otherwise
* a pen touch needed to unblank the LCD will never be read .
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/init.h>
# include <linux/smp.h>
# include <linux/sched.h>
# include <linux/completion.h>
# include <linux/delay.h>
# include <linux/string.h>
# include <linux/input.h>
# include <linux/device.h>
2006-12-07 07:34:23 +03:00
# include <linux/freezer.h>
2005-09-11 13:26:57 +04:00
# include <linux/slab.h>
2005-09-11 13:28:00 +04:00
# include <linux/kthread.h>
2009-02-10 16:54:57 +03:00
# include <linux/mfd/ucb1x00.h>
2005-09-11 13:26:57 +04:00
2008-11-29 14:40:28 +03:00
# include <mach/dma.h>
2008-08-05 19:14:15 +04:00
# include <mach/collie.h>
2005-10-31 02:38:01 +03:00
# include <asm/mach-types.h>
2005-09-11 13:26:57 +04:00
struct ucb1x00_ts {
2005-09-15 11:01:48 +04:00
struct input_dev * idev ;
2005-09-11 13:26:57 +04:00
struct ucb1x00 * ucb ;
wait_queue_head_t irq_wait ;
struct task_struct * rtask ;
u16 x_res ;
u16 y_res ;
2005-09-24 13:24:37 +04:00
unsigned int restart : 1 ;
unsigned int adcsync : 1 ;
2005-09-11 13:26:57 +04:00
} ;
static int adcsync ;
static inline void ucb1x00_ts_evt_add ( struct ucb1x00_ts * ts , u16 pressure , u16 x , u16 y )
{
2005-12-12 11:37:36 +03:00
struct input_dev * idev = ts - > idev ;
2006-09-29 12:59:52 +04:00
2005-12-12 11:37:36 +03:00
input_report_abs ( idev , ABS_X , x ) ;
input_report_abs ( idev , ABS_Y , y ) ;
input_report_abs ( idev , ABS_PRESSURE , pressure ) ;
input_sync ( idev ) ;
2005-09-11 13:26:57 +04:00
}
static inline void ucb1x00_ts_event_release ( struct ucb1x00_ts * ts )
{
2005-12-12 11:37:36 +03:00
struct input_dev * idev = ts - > idev ;
2006-09-29 12:59:52 +04:00
2005-12-12 11:37:36 +03:00
input_report_abs ( idev , ABS_PRESSURE , 0 ) ;
input_sync ( idev ) ;
2005-09-11 13:26:57 +04:00
}
/*
* Switch to interrupt mode .
*/
static inline void ucb1x00_ts_mode_int ( struct ucb1x00_ts * ts )
{
ucb1x00_reg_write ( ts - > ucb , 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 ucb1x00_ts_read_pressure ( struct ucb1x00_ts * ts )
{
2005-10-31 02:38:01 +03:00
if ( machine_is_collie ( ) ) {
ucb1x00_io_write ( ts - > ucb , COLLIE_TC35143_GPIO_TBL_CHK , 0 ) ;
ucb1x00_reg_write ( ts - > ucb , UCB_TS_CR ,
UCB_TS_CR_TSPX_POW | UCB_TS_CR_TSMX_POW |
UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA ) ;
udelay ( 55 ) ;
return ucb1x00_adc_read ( ts - > ucb , UCB_ADC_INP_AD2 , ts - > adcsync ) ;
} else {
ucb1x00_reg_write ( ts - > ucb , 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 ) ;
return ucb1x00_adc_read ( ts - > ucb , UCB_ADC_INP_TSPY , ts - > adcsync ) ;
}
2005-09-11 13:26:57 +04:00
}
/*
* 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 55u s
* for things to stabilise .
*/
static inline unsigned int ucb1x00_ts_read_xpos ( struct ucb1x00_ts * ts )
{
2005-10-31 02:38:01 +03:00
if ( machine_is_collie ( ) )
ucb1x00_io_write ( ts - > ucb , 0 , COLLIE_TC35143_GPIO_TBL_CHK ) ;
else {
ucb1x00_reg_write ( ts - > ucb , UCB_TS_CR ,
UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |
UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA ) ;
ucb1x00_reg_write ( ts - > ucb , UCB_TS_CR ,
UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |
UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA ) ;
}
2005-09-11 13:26:57 +04:00
ucb1x00_reg_write ( ts - > ucb , UCB_TS_CR ,
UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |
UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA ) ;
udelay ( 55 ) ;
return ucb1x00_adc_read ( ts - > ucb , UCB_ADC_INP_TSPY , ts - > 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 55u s
* for things to stabilise .
*/
static inline unsigned int ucb1x00_ts_read_ypos ( struct ucb1x00_ts * ts )
{
2005-10-31 02:38:01 +03:00
if ( machine_is_collie ( ) )
ucb1x00_io_write ( ts - > ucb , 0 , COLLIE_TC35143_GPIO_TBL_CHK ) ;
else {
ucb1x00_reg_write ( ts - > ucb , UCB_TS_CR ,
UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |
UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA ) ;
ucb1x00_reg_write ( ts - > ucb , UCB_TS_CR ,
UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |
UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA ) ;
}
2005-09-11 13:26:57 +04:00
ucb1x00_reg_write ( ts - > ucb , UCB_TS_CR ,
UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |
UCB_TS_CR_MODE_POS | UCB_TS_CR_BIAS_ENA ) ;
udelay ( 55 ) ;
return ucb1x00_adc_read ( ts - > ucb , UCB_ADC_INP_TSPX , ts - > adcsync ) ;
}
/*
* Switch to X plate resistance mode . Set MX to ground , PX to
* supply . Measure current .
*/
static inline unsigned int ucb1x00_ts_read_xres ( struct ucb1x00_ts * ts )
{
ucb1x00_reg_write ( ts - > ucb , UCB_TS_CR ,
UCB_TS_CR_TSMX_GND | UCB_TS_CR_TSPX_POW |
UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA ) ;
return ucb1x00_adc_read ( ts - > ucb , 0 , ts - > adcsync ) ;
}
/*
* Switch to Y plate resistance mode . Set MY to ground , PY to
* supply . Measure current .
*/
static inline unsigned int ucb1x00_ts_read_yres ( struct ucb1x00_ts * ts )
{
ucb1x00_reg_write ( ts - > ucb , UCB_TS_CR ,
UCB_TS_CR_TSMY_GND | UCB_TS_CR_TSPY_POW |
UCB_TS_CR_MODE_PRES | UCB_TS_CR_BIAS_ENA ) ;
return ucb1x00_adc_read ( ts - > ucb , 0 , ts - > adcsync ) ;
}
2005-10-31 02:38:01 +03:00
static inline int ucb1x00_ts_pen_down ( struct ucb1x00_ts * ts )
{
unsigned int val = ucb1x00_reg_read ( ts - > ucb , UCB_TS_CR ) ;
2006-09-29 12:59:52 +04:00
2005-10-31 02:38:01 +03:00
if ( machine_is_collie ( ) )
return ( ! ( val & ( UCB_TS_CR_TSPX_LOW ) ) ) ;
else
return ( val & ( UCB_TS_CR_TSPX_LOW | UCB_TS_CR_TSMX_LOW ) ) ;
}
2005-09-11 13:26:57 +04:00
/*
* This is a RT kernel thread that handles the ADC accesses
* ( mainly so we can use semaphores in the UCB1200 core code
* to serialise accesses to the ADC ) .
*/
static int ucb1x00_thread ( void * _ts )
{
struct ucb1x00_ts * ts = _ts ;
2008-04-28 13:14:24 +04:00
DECLARE_WAITQUEUE ( wait , current ) ;
2007-05-15 07:52:22 +04:00
int valid = 0 ;
2005-09-11 13:26:57 +04:00
2007-07-17 15:03:35 +04:00
set_freezable ( ) ;
2005-09-11 13:26:57 +04:00
add_wait_queue ( & ts - > irq_wait , & wait ) ;
2005-09-11 13:28:00 +04:00
while ( ! kthread_should_stop ( ) ) {
2005-10-31 02:38:01 +03:00
unsigned int x , y , p ;
2005-09-11 13:26:57 +04:00
signed long timeout ;
ts - > restart = 0 ;
ucb1x00_adc_enable ( ts - > ucb ) ;
x = ucb1x00_ts_read_xpos ( ts ) ;
y = ucb1x00_ts_read_ypos ( ts ) ;
p = ucb1x00_ts_read_pressure ( ts ) ;
/*
* Switch back to interrupt mode .
*/
ucb1x00_ts_mode_int ( ts ) ;
ucb1x00_adc_disable ( ts - > ucb ) ;
2005-09-11 13:28:00 +04:00
msleep ( 10 ) ;
2005-09-11 13:26:57 +04:00
ucb1x00_enable ( ts - > ucb ) ;
2005-10-31 02:38:01 +03:00
if ( ucb1x00_ts_pen_down ( ts ) ) {
2008-04-28 13:14:24 +04:00
set_current_state ( TASK_INTERRUPTIBLE ) ;
2005-09-11 13:26:57 +04:00
2005-10-31 02:38:01 +03:00
ucb1x00_enable_irq ( ts - > ucb , UCB_IRQ_TSPX , machine_is_collie ( ) ? UCB_RISING : UCB_FALLING ) ;
2005-09-11 13:26:57 +04:00
ucb1x00_disable ( ts - > ucb ) ;
/*
* If we spat out a valid sample set last time ,
* spit out a " pen off " sample here .
*/
if ( valid ) {
ucb1x00_ts_event_release ( ts ) ;
valid = 0 ;
}
timeout = MAX_SCHEDULE_TIMEOUT ;
} else {
ucb1x00_disable ( ts - > ucb ) ;
/*
* Filtering is policy . Policy belongs in user
* space . We therefore leave it to user space
* to do any filtering they please .
*/
if ( ! ts - > restart ) {
ucb1x00_ts_evt_add ( ts , p , x , y ) ;
valid = 1 ;
}
2008-04-28 13:14:24 +04:00
set_current_state ( TASK_INTERRUPTIBLE ) ;
2005-09-11 13:26:57 +04:00
timeout = HZ / 100 ;
}
try_to_freeze ( ) ;
schedule_timeout ( timeout ) ;
}
remove_wait_queue ( & ts - > irq_wait , & wait ) ;
ts - > rtask = NULL ;
2005-09-11 13:28:00 +04:00
return 0 ;
2005-09-11 13:26:57 +04:00
}
/*
* We only detect touch screen _touches_ with this interrupt
* handler , and even then we just schedule our task .
*/
static void ucb1x00_ts_irq ( int idx , void * id )
{
struct ucb1x00_ts * ts = id ;
2006-09-29 12:59:52 +04:00
2005-09-11 13:26:57 +04:00
ucb1x00_disable_irq ( ts - > ucb , UCB_IRQ_TSPX , UCB_FALLING ) ;
wake_up ( & ts - > irq_wait ) ;
}
static int ucb1x00_ts_open ( struct input_dev * idev )
{
2007-05-11 09:16:12 +04:00
struct ucb1x00_ts * ts = input_get_drvdata ( idev ) ;
2005-09-11 13:26:57 +04:00
int ret = 0 ;
2005-09-11 13:28:00 +04:00
BUG_ON ( ts - > rtask ) ;
2005-09-11 13:26:57 +04:00
init_waitqueue_head ( & ts - > irq_wait ) ;
ret = ucb1x00_hook_irq ( ts - > ucb , UCB_IRQ_TSPX , ucb1x00_ts_irq , ts ) ;
if ( ret < 0 )
goto out ;
/*
* If we do this at all , we should allow the user to
* measure and read the X and Y resistance at any time .
*/
ucb1x00_adc_enable ( ts - > ucb ) ;
ts - > x_res = ucb1x00_ts_read_xres ( ts ) ;
ts - > y_res = ucb1x00_ts_read_yres ( ts ) ;
ucb1x00_adc_disable ( ts - > ucb ) ;
2005-09-11 13:28:00 +04:00
ts - > rtask = kthread_run ( ucb1x00_thread , ts , " ktsd " ) ;
if ( ! IS_ERR ( ts - > rtask ) ) {
2005-09-11 13:26:57 +04:00
ret = 0 ;
} else {
ucb1x00_free_irq ( ts - > ucb , UCB_IRQ_TSPX , ts ) ;
2005-09-11 13:28:00 +04:00
ts - > rtask = NULL ;
ret = - EFAULT ;
2005-09-11 13:26:57 +04:00
}
out :
return ret ;
}
/*
* Release touchscreen resources . Disable IRQs .
*/
static void ucb1x00_ts_close ( struct input_dev * idev )
{
2007-05-11 09:16:12 +04:00
struct ucb1x00_ts * ts = input_get_drvdata ( idev ) ;
2005-09-11 13:26:57 +04:00
2005-09-11 13:28:00 +04:00
if ( ts - > rtask )
kthread_stop ( ts - > rtask ) ;
2005-09-11 13:26:57 +04:00
2005-09-11 13:28:00 +04:00
ucb1x00_enable ( ts - > ucb ) ;
ucb1x00_free_irq ( ts - > ucb , UCB_IRQ_TSPX , ts ) ;
ucb1x00_reg_write ( ts - > ucb , UCB_TS_CR , 0 ) ;
ucb1x00_disable ( ts - > ucb ) ;
2005-09-11 13:26:57 +04:00
}
# ifdef CONFIG_PM
static int ucb1x00_ts_resume ( struct ucb1x00_dev * dev )
{
struct ucb1x00_ts * ts = dev - > priv ;
if ( ts - > rtask ! = NULL ) {
/*
* Restart the TS thread to ensure the
* TS interrupt mode is set up again
* after sleep .
*/
ts - > restart = 1 ;
wake_up ( & ts - > irq_wait ) ;
}
return 0 ;
}
# else
# define ucb1x00_ts_resume NULL
# endif
/*
* Initialisation .
*/
static int ucb1x00_ts_add ( struct ucb1x00_dev * dev )
{
struct ucb1x00_ts * ts ;
2006-09-29 12:59:52 +04:00
struct input_dev * idev ;
int err ;
2005-09-11 13:26:57 +04:00
2005-09-15 11:01:48 +04:00
ts = kzalloc ( sizeof ( struct ucb1x00_ts ) , GFP_KERNEL ) ;
2006-09-29 12:59:52 +04:00
idev = input_allocate_device ( ) ;
if ( ! ts | | ! idev ) {
err = - ENOMEM ;
goto fail ;
2005-09-15 11:01:48 +04:00
}
2005-09-11 13:26:57 +04:00
ts - > ucb = dev - > ucb ;
2006-09-29 12:59:52 +04:00
ts - > idev = idev ;
2005-09-11 13:26:57 +04:00
ts - > adcsync = adcsync ? UCB_SYNC : UCB_NOSYNC ;
2006-09-29 12:59:52 +04:00
idev - > name = " Touchscreen panel " ;
idev - > id . product = ts - > ucb - > id ;
idev - > open = ucb1x00_ts_open ;
idev - > close = ucb1x00_ts_close ;
2005-09-11 13:26:57 +04:00
2006-09-29 12:59:52 +04:00
__set_bit ( EV_ABS , idev - > evbit ) ;
__set_bit ( ABS_X , idev - > absbit ) ;
__set_bit ( ABS_Y , idev - > absbit ) ;
__set_bit ( ABS_PRESSURE , idev - > absbit ) ;
2005-09-11 13:26:57 +04:00
2007-05-11 09:16:12 +04:00
input_set_drvdata ( idev , ts ) ;
2006-09-29 12:59:52 +04:00
err = input_register_device ( idev ) ;
if ( err )
goto fail ;
2005-09-11 13:26:57 +04:00
dev - > priv = ts ;
return 0 ;
2006-09-29 12:59:52 +04:00
fail :
input_free_device ( idev ) ;
kfree ( ts ) ;
return err ;
2005-09-11 13:26:57 +04:00
}
static void ucb1x00_ts_remove ( struct ucb1x00_dev * dev )
{
struct ucb1x00_ts * ts = dev - > priv ;
2005-09-15 11:01:48 +04:00
input_unregister_device ( ts - > idev ) ;
2005-09-11 13:26:57 +04:00
kfree ( ts ) ;
}
static struct ucb1x00_driver ucb1x00_ts_driver = {
. add = ucb1x00_ts_add ,
. remove = ucb1x00_ts_remove ,
. resume = ucb1x00_ts_resume ,
} ;
static int __init ucb1x00_ts_init ( void )
{
return ucb1x00_register_driver ( & ucb1x00_ts_driver ) ;
}
static void __exit ucb1x00_ts_exit ( void )
{
ucb1x00_unregister_driver ( & ucb1x00_ts_driver ) ;
}
module_param ( adcsync , int , 0444 ) ;
module_init ( ucb1x00_ts_init ) ;
module_exit ( ucb1x00_ts_exit ) ;
MODULE_AUTHOR ( " Russell King <rmk@arm.linux.org.uk> " ) ;
MODULE_DESCRIPTION ( " UCB1x00 touchscreen driver " ) ;
MODULE_LICENSE ( " GPL " ) ;