2019-05-19 13:08:20 +01:00
// SPDX-License-Identifier: GPL-2.0-only
2019-08-10 23:19:24 -07:00
# include <linux/delay.h>
# include <linux/gpio/consumer.h>
2012-03-16 23:57:09 -07:00
# include <linux/i2c.h>
# include <linux/input.h>
# include <linux/input/mt.h>
2019-02-09 08:49:38 -08:00
# include <linux/input/touchscreen.h>
2019-08-10 23:19:24 -07:00
# include <linux/interrupt.h>
# include <linux/module.h>
2019-02-06 22:02:02 -08:00
# include <linux/of_device.h>
2019-08-10 23:19:24 -07:00
# include <linux/slab.h>
2019-02-06 22:01:07 -08:00
# include <asm/unaligned.h>
2012-03-16 23:57:09 -07:00
2019-02-06 22:02:02 -08:00
# define ILI210X_TOUCHES 2
2019-08-11 09:16:37 -07:00
# define ILI211X_TOUCHES 10
2019-02-06 22:02:02 -08:00
# define ILI251X_TOUCHES 10
2019-08-10 23:19:24 -07:00
# define ILI2XXX_POLL_PERIOD 20
2012-03-16 23:57:09 -07:00
/* Touchscreen commands */
# define REG_TOUCHDATA 0x10
# define REG_PANEL_INFO 0x20
# define REG_FIRMWARE_VERSION 0x40
# define REG_CALIBRATE 0xcc
struct firmware_version {
u8 id ;
u8 major ;
u8 minor ;
} __packed ;
2019-02-06 22:02:02 -08:00
enum ili2xxx_model {
MODEL_ILI210X ,
2019-08-11 09:16:37 -07:00
MODEL_ILI211X ,
2019-02-06 22:02:02 -08:00
MODEL_ILI251X ,
} ;
2012-03-16 23:57:09 -07:00
struct ili210x {
struct i2c_client * client ;
struct input_dev * input ;
2019-02-06 21:55:26 -08:00
struct gpio_desc * reset_gpio ;
2019-02-09 08:49:38 -08:00
struct touchscreen_properties prop ;
2019-02-06 22:02:02 -08:00
enum ili2xxx_model model ;
unsigned int max_touches ;
2019-08-10 23:19:24 -07:00
bool stop ;
2012-03-16 23:57:09 -07:00
} ;
static int ili210x_read_reg ( struct i2c_client * client , u8 reg , void * buf ,
size_t len )
{
2019-02-06 22:02:02 -08:00
struct ili210x * priv = i2c_get_clientdata ( client ) ;
2012-03-16 23:57:09 -07:00
struct i2c_msg msg [ 2 ] = {
{
. addr = client - > addr ,
. flags = 0 ,
. len = 1 ,
. buf = & reg ,
} ,
{
. addr = client - > addr ,
. flags = I2C_M_RD ,
. len = len ,
. buf = buf ,
}
} ;
2019-02-06 22:02:02 -08:00
if ( priv - > model = = MODEL_ILI251X ) {
if ( i2c_transfer ( client - > adapter , msg , 1 ) ! = 1 ) {
dev_err ( & client - > dev , " i2c transfer failed \n " ) ;
return - EIO ;
}
usleep_range ( 5000 , 5500 ) ;
if ( i2c_transfer ( client - > adapter , msg + 1 , 1 ) ! = 1 ) {
dev_err ( & client - > dev , " i2c transfer failed \n " ) ;
return - EIO ;
}
} else {
if ( i2c_transfer ( client - > adapter , msg , 2 ) ! = 2 ) {
dev_err ( & client - > dev , " i2c transfer failed \n " ) ;
return - EIO ;
}
}
return 0 ;
}
static int ili210x_read ( struct i2c_client * client , void * buf , size_t len )
{
struct i2c_msg msg = {
. addr = client - > addr ,
. flags = I2C_M_RD ,
. len = len ,
. buf = buf ,
} ;
if ( i2c_transfer ( client - > adapter , & msg , 1 ) ! = 1 ) {
2012-03-16 23:57:09 -07:00
dev_err ( & client - > dev , " i2c transfer failed \n " ) ;
return - EIO ;
}
return 0 ;
}
2019-02-06 22:01:07 -08:00
static bool ili210x_touchdata_to_coords ( struct ili210x * priv , u8 * touchdata ,
unsigned int finger ,
unsigned int * x , unsigned int * y )
{
2019-02-06 22:02:02 -08:00
if ( finger > = ILI210X_TOUCHES )
2019-02-06 22:01:07 -08:00
return false ;
if ( touchdata [ 0 ] & BIT ( finger ) )
return false ;
* x = get_unaligned_be16 ( touchdata + 1 + ( finger * 4 ) + 0 ) ;
* y = get_unaligned_be16 ( touchdata + 1 + ( finger * 4 ) + 2 ) ;
return true ;
}
2019-08-11 09:16:37 -07:00
static bool ili211x_touchdata_to_coords ( struct ili210x * priv , u8 * touchdata ,
unsigned int finger ,
unsigned int * x , unsigned int * y )
{
u32 data ;
if ( finger > = ILI211X_TOUCHES )
return false ;
data = get_unaligned_be32 ( touchdata + 1 + ( finger * 4 ) + 0 ) ;
if ( data = = 0xffffffff ) /* Finger up */
return false ;
* x = ( ( touchdata [ 1 + ( finger * 4 ) + 0 ] & 0xf0 ) < < 4 ) |
touchdata [ 1 + ( finger * 4 ) + 1 ] ;
* y = ( ( touchdata [ 1 + ( finger * 4 ) + 0 ] & 0x0f ) < < 8 ) |
touchdata [ 1 + ( finger * 4 ) + 2 ] ;
return true ;
}
2019-02-06 22:02:02 -08:00
static bool ili251x_touchdata_to_coords ( struct ili210x * priv , u8 * touchdata ,
unsigned int finger ,
unsigned int * x , unsigned int * y )
{
if ( finger > = ILI251X_TOUCHES )
return false ;
* x = get_unaligned_be16 ( touchdata + 1 + ( finger * 5 ) + 0 ) ;
if ( ! ( * x & BIT ( 15 ) ) ) /* Touch indication */
return false ;
* x & = 0x3fff ;
* y = get_unaligned_be16 ( touchdata + 1 + ( finger * 5 ) + 2 ) ;
return true ;
}
2019-02-06 22:01:07 -08:00
static bool ili210x_report_events ( struct ili210x * priv , u8 * touchdata )
2012-03-16 23:57:09 -07:00
{
2019-02-06 22:01:07 -08:00
struct input_dev * input = priv - > input ;
2012-03-16 23:57:09 -07:00
int i ;
2019-02-06 22:02:02 -08:00
bool contact = false , touch = false ;
unsigned int x = 0 , y = 0 ;
2012-03-16 23:57:09 -07:00
2019-02-06 22:02:02 -08:00
for ( i = 0 ; i < priv - > max_touches ; i + + ) {
if ( priv - > model = = MODEL_ILI210X ) {
touch = ili210x_touchdata_to_coords ( priv , touchdata ,
i , & x , & y ) ;
2019-08-11 09:16:37 -07:00
} else if ( priv - > model = = MODEL_ILI211X ) {
touch = ili211x_touchdata_to_coords ( priv , touchdata ,
i , & x , & y ) ;
2019-02-06 22:02:02 -08:00
} else if ( priv - > model = = MODEL_ILI251X ) {
touch = ili251x_touchdata_to_coords ( priv , touchdata ,
i , & x , & y ) ;
if ( touch )
contact = true ;
}
2019-02-09 08:49:38 -08:00
input_mt_slot ( input , i ) ;
2012-03-16 23:57:09 -07:00
input_mt_report_slot_state ( input , MT_TOOL_FINGER , touch ) ;
2019-02-09 08:49:38 -08:00
if ( ! touch )
continue ;
touchscreen_report_pos ( input , & priv - > prop , x , y ,
true ) ;
2012-03-16 23:57:09 -07:00
}
input_mt_report_pointer_emulation ( input , false ) ;
input_sync ( input ) ;
2019-02-06 22:01:07 -08:00
2019-02-06 22:02:02 -08:00
if ( priv - > model = = MODEL_ILI210X )
contact = touchdata [ 0 ] & 0xf3 ;
return contact ;
2012-03-16 23:57:09 -07:00
}
2019-08-10 23:19:24 -07:00
static irqreturn_t ili210x_irq ( int irq , void * irq_data )
2012-03-16 23:57:09 -07:00
{
2019-08-10 23:19:24 -07:00
struct ili210x * priv = irq_data ;
2012-03-16 23:57:09 -07:00
struct i2c_client * client = priv - > client ;
2019-02-06 22:02:02 -08:00
u8 touchdata [ 64 ] = { 0 } ;
2019-08-11 09:16:37 -07:00
s16 sum = 0 ;
2019-02-06 22:01:07 -08:00
bool touch ;
2019-08-10 23:19:24 -07:00
int i ;
int error ;
do {
if ( priv - > model = = MODEL_ILI210X ) {
error = ili210x_read_reg ( client , REG_TOUCHDATA ,
touchdata , sizeof ( touchdata ) ) ;
} else if ( priv - > model = = MODEL_ILI211X ) {
error = ili210x_read ( client , touchdata , 43 ) ;
if ( ! error ) {
/*
* This chip uses custom checksum at the end
* of data .
*/
for ( i = 0 ; i < = 41 ; i + + )
sum = ( sum + touchdata [ i ] ) & 0xff ;
if ( ( - sum & 0xff ) ! = touchdata [ 42 ] ) {
dev_err ( & client - > dev ,
" CRC error (crc=0x%02x expected=0x%02x) \n " ,
sum , touchdata [ 42 ] ) ;
break ;
}
2019-08-11 09:16:37 -07:00
}
2019-08-10 23:19:24 -07:00
} else if ( priv - > model = = MODEL_ILI251X ) {
error = ili210x_read_reg ( client , REG_TOUCHDATA ,
touchdata , 31 ) ;
if ( ! error & & touchdata [ 0 ] = = 2 )
error = ili210x_read ( client ,
& touchdata [ 31 ] , 20 ) ;
2019-08-11 09:16:37 -07:00
}
2012-03-16 23:57:09 -07:00
2019-08-10 23:19:24 -07:00
if ( error ) {
dev_err ( & client - > dev ,
" Unable to get touchdata, err = %d \n " , error ) ;
break ;
}
2012-03-16 23:57:09 -07:00
2019-08-10 23:19:24 -07:00
touch = ili210x_report_events ( priv , touchdata ) ;
if ( touch )
msleep ( ILI2XXX_POLL_PERIOD ) ;
} while ( ! priv - > stop & & touch ) ;
2012-03-16 23:57:09 -07:00
return IRQ_HANDLED ;
}
static ssize_t ili210x_calibrate ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
struct ili210x * priv = i2c_get_clientdata ( client ) ;
unsigned long calibrate ;
int rc ;
u8 cmd = REG_CALIBRATE ;
if ( kstrtoul ( buf , 10 , & calibrate ) )
return - EINVAL ;
if ( calibrate > 1 )
return - EINVAL ;
if ( calibrate ) {
rc = i2c_master_send ( priv - > client , & cmd , sizeof ( cmd ) ) ;
if ( rc ! = sizeof ( cmd ) )
return - EIO ;
}
return count ;
}
2016-08-02 10:31:43 -07:00
static DEVICE_ATTR ( calibrate , S_IWUSR , NULL , ili210x_calibrate ) ;
2012-03-16 23:57:09 -07:00
static struct attribute * ili210x_attributes [ ] = {
& dev_attr_calibrate . attr ,
NULL ,
} ;
static const struct attribute_group ili210x_attr_group = {
. attrs = ili210x_attributes ,
} ;
2019-02-06 21:55:26 -08:00
static void ili210x_power_down ( void * data )
{
struct gpio_desc * reset_gpio = data ;
gpiod_set_value_cansleep ( reset_gpio , 1 ) ;
}
2019-08-10 23:19:24 -07:00
static void ili210x_stop ( void * data )
2019-02-06 22:00:44 -08:00
{
struct ili210x * priv = data ;
2019-08-10 23:19:24 -07:00
/* Tell ISR to quit even if there is a contact. */
priv - > stop = true ;
2019-02-06 22:00:44 -08:00
}
2012-11-23 21:38:25 -08:00
static int ili210x_i2c_probe ( struct i2c_client * client ,
2012-03-16 23:57:09 -07:00
const struct i2c_device_id * id )
{
struct device * dev = & client - > dev ;
struct ili210x * priv ;
2019-02-06 21:55:26 -08:00
struct gpio_desc * reset_gpio ;
2012-03-16 23:57:09 -07:00
struct input_dev * input ;
struct firmware_version firmware ;
2019-02-06 22:02:02 -08:00
enum ili2xxx_model model ;
2012-03-16 23:57:09 -07:00
int error ;
2019-02-06 22:02:02 -08:00
model = ( enum ili2xxx_model ) id - > driver_data ;
2012-03-16 23:57:09 -07:00
dev_dbg ( dev , " Probing for ILI210X I2C Touschreen driver " ) ;
if ( client - > irq < = 0 ) {
dev_err ( dev , " No IRQ! \n " ) ;
return - EINVAL ;
}
2019-02-06 21:55:26 -08:00
reset_gpio = devm_gpiod_get_optional ( dev , " reset " , GPIOD_OUT_HIGH ) ;
if ( IS_ERR ( reset_gpio ) )
return PTR_ERR ( reset_gpio ) ;
if ( reset_gpio ) {
error = devm_add_action_or_reset ( dev , ili210x_power_down ,
reset_gpio ) ;
if ( error )
return error ;
usleep_range ( 50 , 100 ) ;
gpiod_set_value_cansleep ( reset_gpio , 0 ) ;
msleep ( 100 ) ;
}
2019-02-06 22:01:30 -08:00
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
input = devm_input_allocate_device ( dev ) ;
if ( ! input )
return - ENOMEM ;
priv - > client = client ;
priv - > input = input ;
priv - > reset_gpio = reset_gpio ;
2019-02-06 22:02:02 -08:00
priv - > model = model ;
if ( model = = MODEL_ILI210X )
priv - > max_touches = ILI210X_TOUCHES ;
2019-08-11 09:16:37 -07:00
if ( model = = MODEL_ILI211X )
priv - > max_touches = ILI211X_TOUCHES ;
2019-02-06 22:02:02 -08:00
if ( model = = MODEL_ILI251X )
priv - > max_touches = ILI251X_TOUCHES ;
2019-02-06 22:01:30 -08:00
i2c_set_clientdata ( client , priv ) ;
2012-03-16 23:57:09 -07:00
/* Get firmware version */
error = ili210x_read_reg ( client , REG_FIRMWARE_VERSION ,
& firmware , sizeof ( firmware ) ) ;
if ( error ) {
dev_err ( dev , " Failed to get firmware version, err: %d \n " ,
error ) ;
return error ;
}
/* Setup input device */
input - > name = " ILI210x Touchscreen " ;
input - > id . bustype = BUS_I2C ;
input - > dev . parent = dev ;
/* Multi touch */
2019-02-09 08:49:38 -08:00
input_set_abs_params ( input , ABS_MT_POSITION_X , 0 , 0xffff , 0 , 0 ) ;
input_set_abs_params ( input , ABS_MT_POSITION_Y , 0 , 0xffff , 0 , 0 ) ;
touchscreen_parse_properties ( input , true , & priv - > prop ) ;
input_mt_init_slots ( input , priv - > max_touches , INPUT_MT_DIRECT ) ;
2012-03-16 23:57:09 -07:00
2019-08-10 23:19:24 -07:00
error = devm_request_threaded_irq ( dev , client - > irq , NULL , ili210x_irq ,
IRQF_ONESHOT , client - > name , priv ) ;
2012-03-16 23:57:09 -07:00
if ( error ) {
dev_err ( dev , " Unable to request touchscreen IRQ, err: %d \n " ,
error ) ;
2019-02-06 22:00:44 -08:00
return error ;
2012-03-16 23:57:09 -07:00
}
2019-08-10 23:19:24 -07:00
error = devm_add_action_or_reset ( dev , ili210x_stop , priv ) ;
if ( error )
return error ;
2019-02-06 22:19:42 -08:00
error = devm_device_add_group ( dev , & ili210x_attr_group ) ;
2012-03-16 23:57:09 -07:00
if ( error ) {
dev_err ( dev , " Unable to create sysfs attributes, err: %d \n " ,
error ) ;
2019-02-06 22:00:44 -08:00
return error ;
2012-03-16 23:57:09 -07:00
}
error = input_register_device ( priv - > input ) ;
if ( error ) {
2015-05-20 23:54:02 +09:00
dev_err ( dev , " Cannot register input device, err: %d \n " , error ) ;
2019-02-06 22:19:42 -08:00
return error ;
2012-03-16 23:57:09 -07:00
}
2017-01-21 23:46:47 -08:00
device_init_wakeup ( dev , 1 ) ;
2012-03-16 23:57:09 -07:00
dev_dbg ( dev ,
" ILI210x initialized (IRQ: %d), firmware version %d.%d.%d " ,
client - > irq , firmware . id , firmware . major , firmware . minor ) ;
return 0 ;
}
2014-11-02 00:04:14 -07:00
static int __maybe_unused ili210x_i2c_suspend ( struct device * dev )
2012-03-16 23:57:09 -07:00
{
struct i2c_client * client = to_i2c_client ( dev ) ;
if ( device_may_wakeup ( & client - > dev ) )
enable_irq_wake ( client - > irq ) ;
return 0 ;
}
2014-11-02 00:04:14 -07:00
static int __maybe_unused ili210x_i2c_resume ( struct device * dev )
2012-03-16 23:57:09 -07:00
{
struct i2c_client * client = to_i2c_client ( dev ) ;
if ( device_may_wakeup ( & client - > dev ) )
disable_irq_wake ( client - > irq ) ;
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( ili210x_i2c_pm ,
ili210x_i2c_suspend , ili210x_i2c_resume ) ;
static const struct i2c_device_id ili210x_i2c_id [ ] = {
2019-02-06 22:02:02 -08:00
{ " ili210x " , MODEL_ILI210X } ,
2019-08-11 09:16:37 -07:00
{ " ili2117 " , MODEL_ILI211X } ,
2019-02-06 22:02:02 -08:00
{ " ili251x " , MODEL_ILI251X } ,
2012-03-16 23:57:09 -07:00
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , ili210x_i2c_id ) ;
2019-02-06 22:01:51 -08:00
static const struct of_device_id ili210x_dt_ids [ ] = {
2019-02-06 22:02:02 -08:00
{ . compatible = " ilitek,ili210x " , . data = ( void * ) MODEL_ILI210X } ,
2019-08-11 09:16:37 -07:00
{ . compatible = " ilitek,ili2117 " , . data = ( void * ) MODEL_ILI211X } ,
2019-02-06 22:02:02 -08:00
{ . compatible = " ilitek,ili251x " , . data = ( void * ) MODEL_ILI251X } ,
2019-02-06 22:01:51 -08:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , ili210x_dt_ids ) ;
2012-03-16 23:57:09 -07:00
static struct i2c_driver ili210x_ts_driver = {
. driver = {
. name = " ili210x_i2c " ,
. pm = & ili210x_i2c_pm ,
2019-02-06 22:01:51 -08:00
. of_match_table = ili210x_dt_ids ,
2012-03-16 23:57:09 -07:00
} ,
. id_table = ili210x_i2c_id ,
. probe = ili210x_i2c_probe ,
} ;
module_i2c_driver ( ili210x_ts_driver ) ;
MODULE_AUTHOR ( " Olivier Sobrie <olivier@sobrie.be> " ) ;
MODULE_DESCRIPTION ( " ILI210X I2C Touchscreen Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;