2023-04-01 14:21:43 -07:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Novatek i2c touchscreen controller as found on
* the Acer Iconia One 7 B1 - 750 tablet . The Touchscreen controller
* model - number is unknown . Android calls this a " NVT-ts " touchscreen ,
* but that may apply to other Novatek controller models too .
*
* Copyright ( c ) 2023 Hans de Goede < hdegoede @ redhat . com >
*/
# include <linux/delay.h>
# include <linux/gpio/consumer.h>
# include <linux/interrupt.h>
# include <linux/i2c.h>
# include <linux/input.h>
# include <linux/input/mt.h>
# include <linux/input/touchscreen.h>
# include <linux/module.h>
# include <asm/unaligned.h>
# define NVT_TS_TOUCH_START 0x00
# define NVT_TS_TOUCH_SIZE 6
# define NVT_TS_PARAMETERS_START 0x78
/* These are offsets from NVT_TS_PARAMETERS_START */
# define NVT_TS_PARAMS_WIDTH 0x04
# define NVT_TS_PARAMS_HEIGHT 0x06
# define NVT_TS_PARAMS_MAX_TOUCH 0x09
# define NVT_TS_PARAMS_MAX_BUTTONS 0x0a
# define NVT_TS_PARAMS_IRQ_TYPE 0x0b
# define NVT_TS_PARAMS_WAKE_TYPE 0x0c
# define NVT_TS_PARAMS_CHIP_ID 0x0e
# define NVT_TS_PARAMS_SIZE 0x0f
# define NVT_TS_SUPPORTED_WAKE_TYPE 0x05
# define NVT_TS_SUPPORTED_CHIP_ID 0x05
# define NVT_TS_MAX_TOUCHES 10
# define NVT_TS_MAX_SIZE 4096
# define NVT_TS_TOUCH_INVALID 0xff
# define NVT_TS_TOUCH_SLOT_SHIFT 3
# define NVT_TS_TOUCH_TYPE_MASK GENMASK(2, 0)
# define NVT_TS_TOUCH_NEW 1
# define NVT_TS_TOUCH_UPDATE 2
# define NVT_TS_TOUCH_RELEASE 3
static const int nvt_ts_irq_type [ 4 ] = {
IRQF_TRIGGER_RISING ,
IRQF_TRIGGER_FALLING ,
IRQF_TRIGGER_LOW ,
IRQF_TRIGGER_HIGH
} ;
struct nvt_ts_data {
struct i2c_client * client ;
struct input_dev * input ;
struct gpio_desc * reset_gpio ;
struct touchscreen_properties prop ;
int max_touches ;
u8 buf [ NVT_TS_TOUCH_SIZE * NVT_TS_MAX_TOUCHES ] ;
} ;
static int nvt_ts_read_data ( struct i2c_client * client , u8 reg , u8 * data , int count )
{
struct i2c_msg msg [ 2 ] = {
{
. addr = client - > addr ,
. len = 1 ,
. buf = & reg ,
} ,
{
. addr = client - > addr ,
. flags = I2C_M_RD ,
. len = count ,
. buf = data ,
}
} ;
int ret ;
ret = i2c_transfer ( client - > adapter , msg , ARRAY_SIZE ( msg ) ) ;
if ( ret ! = ARRAY_SIZE ( msg ) ) {
dev_err ( & client - > dev , " Error reading from 0x%02x: %d \n " , reg , ret ) ;
return ( ret < 0 ) ? ret : - EIO ;
}
return 0 ;
}
static irqreturn_t nvt_ts_irq ( int irq , void * dev_id )
{
struct nvt_ts_data * data = dev_id ;
struct device * dev = & data - > client - > dev ;
int i , error , slot , x , y ;
bool active ;
u8 * touch ;
error = nvt_ts_read_data ( data - > client , NVT_TS_TOUCH_START , data - > buf ,
data - > max_touches * NVT_TS_TOUCH_SIZE ) ;
if ( error )
return IRQ_HANDLED ;
for ( i = 0 ; i < data - > max_touches ; i + + ) {
touch = & data - > buf [ i * NVT_TS_TOUCH_SIZE ] ;
if ( touch [ 0 ] = = NVT_TS_TOUCH_INVALID )
continue ;
slot = touch [ 0 ] > > NVT_TS_TOUCH_SLOT_SHIFT ;
if ( slot < 1 | | slot > data - > max_touches ) {
dev_warn ( dev , " slot %d out of range, ignoring \n " , slot ) ;
continue ;
}
switch ( touch [ 0 ] & NVT_TS_TOUCH_TYPE_MASK ) {
case NVT_TS_TOUCH_NEW :
case NVT_TS_TOUCH_UPDATE :
active = true ;
break ;
case NVT_TS_TOUCH_RELEASE :
active = false ;
break ;
default :
dev_warn ( dev , " slot %d unknown state %d \n " , slot , touch [ 0 ] & 7 ) ;
continue ;
}
slot - - ;
x = ( touch [ 1 ] < < 4 ) | ( touch [ 3 ] > > 4 ) ;
y = ( touch [ 2 ] < < 4 ) | ( touch [ 3 ] & 0x0f ) ;
input_mt_slot ( data - > input , slot ) ;
input_mt_report_slot_state ( data - > input , MT_TOOL_FINGER , active ) ;
touchscreen_report_pos ( data - > input , & data - > prop , x , y , true ) ;
}
input_mt_sync_frame ( data - > input ) ;
input_sync ( data - > input ) ;
return IRQ_HANDLED ;
}
static int nvt_ts_start ( struct input_dev * dev )
{
struct nvt_ts_data * data = input_get_drvdata ( dev ) ;
enable_irq ( data - > client - > irq ) ;
gpiod_set_value_cansleep ( data - > reset_gpio , 0 ) ;
return 0 ;
}
static void nvt_ts_stop ( struct input_dev * dev )
{
struct nvt_ts_data * data = input_get_drvdata ( dev ) ;
disable_irq ( data - > client - > irq ) ;
gpiod_set_value_cansleep ( data - > reset_gpio , 1 ) ;
}
static int nvt_ts_suspend ( struct device * dev )
{
struct nvt_ts_data * data = i2c_get_clientdata ( to_i2c_client ( dev ) ) ;
mutex_lock ( & data - > input - > mutex ) ;
if ( input_device_enabled ( data - > input ) )
nvt_ts_stop ( data - > input ) ;
mutex_unlock ( & data - > input - > mutex ) ;
return 0 ;
}
static int nvt_ts_resume ( struct device * dev )
{
struct nvt_ts_data * data = i2c_get_clientdata ( to_i2c_client ( dev ) ) ;
mutex_lock ( & data - > input - > mutex ) ;
if ( input_device_enabled ( data - > input ) )
nvt_ts_start ( data - > input ) ;
mutex_unlock ( & data - > input - > mutex ) ;
return 0 ;
}
static DEFINE_SIMPLE_DEV_PM_OPS ( nvt_ts_pm_ops , nvt_ts_suspend , nvt_ts_resume ) ;
static int nvt_ts_probe ( struct i2c_client * client )
{
struct device * dev = & client - > dev ;
int error , width , height , irq_type ;
struct nvt_ts_data * data ;
struct input_dev * input ;
if ( ! client - > irq ) {
dev_err ( dev , " Error no irq specified \n " ) ;
return - EINVAL ;
}
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > client = client ;
i2c_set_clientdata ( client , data ) ;
data - > reset_gpio = devm_gpiod_get ( dev , " reset " , GPIOD_OUT_LOW ) ;
error = PTR_ERR_OR_ZERO ( data - > reset_gpio ) ;
if ( error ) {
dev_err ( dev , " failed to request reset GPIO: %d \n " , error ) ;
return error ;
}
/* Wait for controller to come out of reset before params read */
msleep ( 100 ) ;
error = nvt_ts_read_data ( data - > client , NVT_TS_PARAMETERS_START ,
data - > buf , NVT_TS_PARAMS_SIZE ) ;
gpiod_set_value_cansleep ( data - > reset_gpio , 1 ) ; /* Put back in reset */
if ( error )
return error ;
width = get_unaligned_be16 ( & data - > buf [ NVT_TS_PARAMS_WIDTH ] ) ;
height = get_unaligned_be16 ( & data - > buf [ NVT_TS_PARAMS_HEIGHT ] ) ;
data - > max_touches = data - > buf [ NVT_TS_PARAMS_MAX_TOUCH ] ;
irq_type = data - > buf [ NVT_TS_PARAMS_IRQ_TYPE ] ;
if ( width > NVT_TS_MAX_SIZE | | height > = NVT_TS_MAX_SIZE | |
data - > max_touches > NVT_TS_MAX_TOUCHES | |
irq_type > = ARRAY_SIZE ( nvt_ts_irq_type ) | |
data - > buf [ NVT_TS_PARAMS_WAKE_TYPE ] ! = NVT_TS_SUPPORTED_WAKE_TYPE | |
data - > buf [ NVT_TS_PARAMS_CHIP_ID ] ! = NVT_TS_SUPPORTED_CHIP_ID ) {
dev_err ( dev , " Unsupported touchscreen parameters: %*ph \n " ,
NVT_TS_PARAMS_SIZE , data - > buf ) ;
return - EIO ;
}
dev_dbg ( dev , " Detected %dx%d touchscreen with %d max touches \n " ,
width , height , data - > max_touches ) ;
if ( data - > buf [ NVT_TS_PARAMS_MAX_BUTTONS ] )
dev_warn ( dev , " Touchscreen buttons are not supported \n " ) ;
input = devm_input_allocate_device ( dev ) ;
if ( ! input )
return - ENOMEM ;
input - > name = client - > name ;
input - > id . bustype = BUS_I2C ;
input - > open = nvt_ts_start ;
input - > close = nvt_ts_stop ;
input_set_abs_params ( input , ABS_MT_POSITION_X , 0 , width - 1 , 0 , 0 ) ;
input_set_abs_params ( input , ABS_MT_POSITION_Y , 0 , height - 1 , 0 , 0 ) ;
touchscreen_parse_properties ( input , true , & data - > prop ) ;
error = input_mt_init_slots ( input , data - > max_touches ,
INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED ) ;
if ( error )
return error ;
data - > input = input ;
input_set_drvdata ( input , data ) ;
error = devm_request_threaded_irq ( dev , client - > irq , NULL , nvt_ts_irq ,
IRQF_ONESHOT | IRQF_NO_AUTOEN |
nvt_ts_irq_type [ irq_type ] ,
client - > name , data ) ;
if ( error ) {
dev_err ( dev , " failed to request irq: %d \n " , error ) ;
return error ;
}
error = input_register_device ( input ) ;
if ( error ) {
dev_err ( dev , " failed to request irq: %d \n " , error ) ;
return error ;
}
return 0 ;
}
static const struct i2c_device_id nvt_ts_i2c_id [ ] = {
{ " NVT-ts " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , nvt_ts_i2c_id ) ;
static struct i2c_driver nvt_ts_driver = {
. driver = {
. name = " novatek-nvt-ts " ,
. pm = pm_sleep_ptr ( & nvt_ts_pm_ops ) ,
} ,
2023-05-17 09:55:42 -07:00
. probe = nvt_ts_probe ,
2023-04-01 14:21:43 -07:00
. id_table = nvt_ts_i2c_id ,
} ;
module_i2c_driver ( nvt_ts_driver ) ;
MODULE_DESCRIPTION ( " Novatek NVT-ts touchscreen driver " ) ;
MODULE_AUTHOR ( " Hans de Goede <hdegoede@redhat.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;