2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
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
*
* 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>
2012-01-21 18:58:28 +04:00
# include <linux/interrupt.h>
2005-09-11 13:26:57 +04:00
# include <linux/sched.h>
2012-01-21 18:58:28 +04:00
# include <linux/spinlock.h>
2005-09-11 13:26:57 +04:00
# 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-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 ;
2012-01-21 18:58:28 +04:00
spinlock_t irq_lock ;
unsigned irq_disabled ;
2005-09-11 13:26:57 +04:00
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 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 ) ;
2011-01-30 15:40:56 +03:00
input_report_key ( idev , BTN_TOUCH , 1 ) ;
2005-12-12 11:37:36 +03:00
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 ) ;
2011-01-30 15:40:56 +03:00
input_report_key ( idev , BTN_TOUCH , 0 ) ;
2005-12-12 11:37:36 +03:00
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 ) ;
2012-01-23 00:58:55 +04:00
bool frozen , ignore = false ;
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 ) ;
2012-01-23 00:58:55 +04:00
while ( ! kthread_freezable_should_stop ( & frozen ) ) {
2005-10-31 02:38:01 +03:00
unsigned int x , y , p ;
2005-09-11 13:26:57 +04:00
signed long timeout ;
2012-01-23 00:58:55 +04:00
if ( frozen )
ignore = true ;
2005-09-11 13:26:57 +04:00
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
2012-01-21 18:58:28 +04:00
spin_lock_irq ( & ts - > irq_lock ) ;
if ( ts - > irq_disabled ) {
ts - > irq_disabled = 0 ;
enable_irq ( ts - > ucb - > irq_base + UCB_IRQ_TSPX ) ;
}
spin_unlock_irq ( & ts - > irq_lock ) ;
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 .
*/
2012-01-23 00:58:55 +04:00
if ( ! ignore ) {
2005-09-11 13:26:57 +04:00
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 ;
}
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 .
*/
2012-01-21 18:58:28 +04:00
static irqreturn_t ucb1x00_ts_irq ( int irq , void * id )
2005-09-11 13:26:57 +04:00
{
struct ucb1x00_ts * ts = id ;
2006-09-29 12:59:52 +04:00
2012-01-21 18:58:28 +04:00
spin_lock ( & ts - > irq_lock ) ;
ts - > irq_disabled = 1 ;
disable_irq_nosync ( ts - > ucb - > irq_base + UCB_IRQ_TSPX ) ;
spin_unlock ( & ts - > irq_lock ) ;
2005-09-11 13:26:57 +04:00
wake_up ( & ts - > irq_wait ) ;
2012-01-21 18:58:28 +04:00
return IRQ_HANDLED ;
2005-09-11 13:26:57 +04:00
}
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 ) ;
2012-01-21 18:58:28 +04:00
unsigned long flags = 0 ;
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
2012-01-21 18:58:28 +04:00
if ( machine_is_collie ( ) )
flags = IRQF_TRIGGER_RISING ;
else
flags = IRQF_TRIGGER_FALLING ;
ts - > irq_disabled = 0 ;
2005-09-11 13:26:57 +04:00
init_waitqueue_head ( & ts - > irq_wait ) ;
2012-01-21 18:58:28 +04:00
ret = request_irq ( ts - > ucb - > irq_base + UCB_IRQ_TSPX , ucb1x00_ts_irq ,
flags , " ucb1x00-ts " , ts ) ;
2005-09-11 13:26:57 +04:00
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 {
2012-01-21 18:58:28 +04:00
free_irq ( ts - > ucb - > irq_base + 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 ) ;
2012-01-21 18:58:28 +04:00
free_irq ( ts - > ucb - > irq_base + UCB_IRQ_TSPX , ts ) ;
2005-09-11 13:28:00 +04:00
ucb1x00_reg_write ( ts - > ucb , UCB_TS_CR , 0 ) ;
ucb1x00_disable ( ts - > ucb ) ;
2005-09-11 13:26:57 +04:00
}
/*
* 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 ;
2012-01-21 18:58:28 +04:00
spin_lock_init ( & ts - > irq_lock ) ;
2005-09-11 13:26:57 +04:00
2006-09-29 12:59:52 +04:00
idev - > name = " Touchscreen panel " ;
2012-01-20 21:38:58 +04:00
idev - > id . product = ts - > ucb - > id ;
2006-09-29 12:59:52 +04:00
idev - > open = ucb1x00_ts_open ;
idev - > close = ucb1x00_ts_close ;
2012-01-21 13:30:27 +04:00
idev - > dev . parent = & ts - > ucb - > dev ;
2005-09-11 13:26:57 +04:00
2011-01-30 15:40:56 +03:00
idev - > evbit [ 0 ] = BIT_MASK ( EV_ABS ) | BIT_MASK ( EV_KEY ) ;
idev - > keybit [ BIT_WORD ( BTN_TOUCH ) ] = BIT_MASK ( BTN_TOUCH ) ;
2005-09-11 13:26:57 +04:00
2007-05-11 09:16:12 +04:00
input_set_drvdata ( idev , ts ) ;
2011-01-26 13:30:01 +03:00
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 ) ;
input_set_abs_params ( idev , ABS_X , 0 , ts - > x_res , 0 , 0 ) ;
input_set_abs_params ( idev , ABS_Y , 0 , ts - > y_res , 0 , 0 ) ;
input_set_abs_params ( idev , ABS_PRESSURE , 0 , 0 , 0 , 0 ) ;
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 ,
} ;
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 " ) ;