2019-06-04 10:11:33 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2017-10-14 10:10:53 -07:00
/*
* Driver for I2C connected EETI EXC3000 multiple touch controller
*
* Copyright ( C ) 2017 Ahmet Inan < inan @ distec . de >
*
* minimal implementation based on egalax_ts . c and egalax_i2c . c
*/
# include <linux/bitops.h>
# include <linux/device.h>
# include <linux/i2c.h>
# include <linux/input.h>
# include <linux/input/mt.h>
# include <linux/input/touchscreen.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/timer.h>
# include <asm/unaligned.h>
# define EXC3000_NUM_SLOTS 10
# define EXC3000_SLOTS_PER_FRAME 5
# define EXC3000_LEN_FRAME 66
# define EXC3000_LEN_POINT 10
# define EXC3000_MT_EVENT 6
# define EXC3000_TIMEOUT_MS 100
struct exc3000_data {
struct i2c_client * client ;
struct input_dev * input ;
struct touchscreen_properties prop ;
struct timer_list timer ;
u8 buf [ 2 * EXC3000_LEN_FRAME ] ;
} ;
static void exc3000_report_slots ( struct input_dev * input ,
struct touchscreen_properties * prop ,
const u8 * buf , int num )
{
for ( ; num - - ; buf + = EXC3000_LEN_POINT ) {
if ( buf [ 0 ] & BIT ( 0 ) ) {
input_mt_slot ( input , buf [ 1 ] ) ;
input_mt_report_slot_state ( input , MT_TOOL_FINGER , true ) ;
touchscreen_report_pos ( input , prop ,
get_unaligned_le16 ( buf + 2 ) ,
get_unaligned_le16 ( buf + 4 ) ,
true ) ;
}
}
}
static void exc3000_timer ( struct timer_list * t )
{
struct exc3000_data * data = from_timer ( data , t , timer ) ;
input_mt_sync_frame ( data - > input ) ;
input_sync ( data - > input ) ;
}
static int exc3000_read_frame ( struct i2c_client * client , u8 * buf )
{
int ret ;
ret = i2c_master_send ( client , " ' " , 2 ) ;
if ( ret < 0 )
return ret ;
if ( ret ! = 2 )
return - EIO ;
ret = i2c_master_recv ( client , buf , EXC3000_LEN_FRAME ) ;
if ( ret < 0 )
return ret ;
if ( ret ! = EXC3000_LEN_FRAME )
return - EIO ;
if ( get_unaligned_le16 ( buf ) ! = EXC3000_LEN_FRAME | |
buf [ 2 ] ! = EXC3000_MT_EVENT )
return - EINVAL ;
return 0 ;
}
static int exc3000_read_data ( struct i2c_client * client ,
u8 * buf , int * n_slots )
{
int error ;
error = exc3000_read_frame ( client , buf ) ;
if ( error )
return error ;
* n_slots = buf [ 3 ] ;
if ( ! * n_slots | | * n_slots > EXC3000_NUM_SLOTS )
return - EINVAL ;
if ( * n_slots > EXC3000_SLOTS_PER_FRAME ) {
/* Read 2nd frame to get the rest of the contacts. */
error = exc3000_read_frame ( client , buf + EXC3000_LEN_FRAME ) ;
if ( error )
return error ;
/* 2nd chunk must have number of contacts set to 0. */
if ( buf [ EXC3000_LEN_FRAME + 3 ] ! = 0 )
return - EINVAL ;
}
return 0 ;
}
static irqreturn_t exc3000_interrupt ( int irq , void * dev_id )
{
struct exc3000_data * data = dev_id ;
struct input_dev * input = data - > input ;
u8 * buf = data - > buf ;
int slots , total_slots ;
int error ;
error = exc3000_read_data ( data - > client , buf , & total_slots ) ;
if ( error ) {
/* Schedule a timer to release "stuck" contacts */
mod_timer ( & data - > timer ,
jiffies + msecs_to_jiffies ( EXC3000_TIMEOUT_MS ) ) ;
goto out ;
}
/*
* We read full state successfully , no contacts will be " stuck " .
*/
del_timer_sync ( & data - > timer ) ;
while ( total_slots > 0 ) {
slots = min ( total_slots , EXC3000_SLOTS_PER_FRAME ) ;
exc3000_report_slots ( input , & data - > prop , buf + 4 , slots ) ;
total_slots - = slots ;
buf + = EXC3000_LEN_FRAME ;
}
input_mt_sync_frame ( input ) ;
input_sync ( input ) ;
out :
return IRQ_HANDLED ;
}
static int exc3000_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct exc3000_data * data ;
struct input_dev * input ;
int error ;
data = devm_kzalloc ( & client - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > client = client ;
timer_setup ( & data - > timer , exc3000_timer , 0 ) ;
input = devm_input_allocate_device ( & client - > dev ) ;
if ( ! input )
return - ENOMEM ;
data - > input = input ;
input - > name = " EETI EXC3000 Touch Screen " ;
input - > id . bustype = BUS_I2C ;
input_set_abs_params ( input , ABS_MT_POSITION_X , 0 , 4095 , 0 , 0 ) ;
input_set_abs_params ( input , ABS_MT_POSITION_Y , 0 , 4095 , 0 , 0 ) ;
touchscreen_parse_properties ( input , true , & data - > prop ) ;
error = input_mt_init_slots ( input , EXC3000_NUM_SLOTS ,
INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED ) ;
if ( error )
return error ;
error = input_register_device ( input ) ;
if ( error )
return error ;
error = devm_request_threaded_irq ( & client - > dev , client - > irq ,
NULL , exc3000_interrupt , IRQF_ONESHOT ,
client - > name , data ) ;
if ( error )
return error ;
return 0 ;
}
static const struct i2c_device_id exc3000_id [ ] = {
{ " exc3000 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , exc3000_id ) ;
# ifdef CONFIG_OF
static const struct of_device_id exc3000_of_match [ ] = {
{ . compatible = " eeti,exc3000 " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , exc3000_of_match ) ;
# endif
static struct i2c_driver exc3000_driver = {
. driver = {
. name = " exc3000 " ,
. of_match_table = of_match_ptr ( exc3000_of_match ) ,
} ,
. id_table = exc3000_id ,
. probe = exc3000_probe ,
} ;
module_i2c_driver ( exc3000_driver ) ;
MODULE_AUTHOR ( " Ahmet Inan <inan@distec.de> " ) ;
MODULE_DESCRIPTION ( " I2C connected EETI EXC3000 multiple touch controller driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;