2016-07-28 14:28:46 -07:00
/* -------------------------------------------------------------------------
* Copyright ( C ) 2014 - 2015 , Intel Corporation
*
* Derived from :
* gslX68X . c
* Copyright ( C ) 2010 - 2015 , Shanghai Sileadinc Co . Ltd
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
# include <linux/i2c.h>
# include <linux/module.h>
# include <linux/acpi.h>
# include <linux/interrupt.h>
# include <linux/gpio/consumer.h>
# include <linux/delay.h>
# include <linux/firmware.h>
# include <linux/input.h>
# include <linux/input/mt.h>
# include <linux/input/touchscreen.h>
# include <linux/pm.h>
# include <linux/irq.h>
2016-11-16 09:37:08 -08:00
# include <linux/regulator/consumer.h>
2016-07-28 14:28:46 -07:00
# include <asm/unaligned.h>
# define SILEAD_TS_NAME "silead_ts"
# define SILEAD_REG_RESET 0xE0
# define SILEAD_REG_DATA 0x80
# define SILEAD_REG_TOUCH_NR 0x80
# define SILEAD_REG_POWER 0xBC
# define SILEAD_REG_CLOCK 0xE4
# define SILEAD_REG_STATUS 0xB0
# define SILEAD_REG_ID 0xFC
# define SILEAD_REG_MEM_CHECK 0xB0
# define SILEAD_STATUS_OK 0x5A5A5A5A
# define SILEAD_TS_DATA_LEN 44
# define SILEAD_CLOCK 0x04
# define SILEAD_CMD_RESET 0x88
# define SILEAD_CMD_START 0x00
# define SILEAD_POINT_DATA_LEN 0x04
# define SILEAD_POINT_Y_OFF 0x00
# define SILEAD_POINT_Y_MSB_OFF 0x01
# define SILEAD_POINT_X_OFF 0x02
# define SILEAD_POINT_X_MSB_OFF 0x03
2018-01-09 11:34:15 -08:00
# define SILEAD_EXTRA_DATA_MASK 0xF0
2016-07-28 14:28:46 -07:00
# define SILEAD_CMD_SLEEP_MIN 10000
# define SILEAD_CMD_SLEEP_MAX 20000
# define SILEAD_POWER_SLEEP 20
# define SILEAD_STARTUP_SLEEP 30
# define SILEAD_MAX_FINGERS 10
enum silead_ts_power {
SILEAD_POWER_ON = 1 ,
SILEAD_POWER_OFF = 0
} ;
struct silead_ts_data {
struct i2c_client * client ;
struct gpio_desc * gpio_power ;
struct input_dev * input ;
2016-11-16 09:37:08 -08:00
struct regulator_bulk_data regulators [ 2 ] ;
2016-07-28 14:28:46 -07:00
char fw_name [ 64 ] ;
struct touchscreen_properties prop ;
u32 max_fingers ;
u32 chip_id ;
struct input_mt_pos pos [ SILEAD_MAX_FINGERS ] ;
int slots [ SILEAD_MAX_FINGERS ] ;
int id [ SILEAD_MAX_FINGERS ] ;
} ;
struct silead_fw_data {
u32 offset ;
u32 val ;
} ;
static int silead_ts_request_input_dev ( struct silead_ts_data * data )
{
struct device * dev = & data - > client - > dev ;
int error ;
data - > input = devm_input_allocate_device ( dev ) ;
if ( ! data - > input ) {
dev_err ( dev ,
" Failed to allocate input device \n " ) ;
return - ENOMEM ;
}
input_set_abs_params ( data - > input , ABS_MT_POSITION_X , 0 , 4095 , 0 , 0 ) ;
input_set_abs_params ( data - > input , ABS_MT_POSITION_Y , 0 , 4095 , 0 , 0 ) ;
touchscreen_parse_properties ( data - > input , true , & data - > prop ) ;
input_mt_init_slots ( data - > input , data - > max_fingers ,
INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED |
INPUT_MT_TRACK ) ;
2018-01-09 11:34:15 -08:00
if ( device_property_read_bool ( dev , " silead,home-button " ) )
input_set_capability ( data - > input , EV_KEY , KEY_LEFTMETA ) ;
2016-07-28 14:28:46 -07:00
data - > input - > name = SILEAD_TS_NAME ;
data - > input - > phys = " input/ts " ;
data - > input - > id . bustype = BUS_I2C ;
error = input_register_device ( data - > input ) ;
if ( error ) {
dev_err ( dev , " Failed to register input device: %d \n " , error ) ;
return error ;
}
return 0 ;
}
static void silead_ts_set_power ( struct i2c_client * client ,
enum silead_ts_power state )
{
struct silead_ts_data * data = i2c_get_clientdata ( client ) ;
if ( data - > gpio_power ) {
gpiod_set_value_cansleep ( data - > gpio_power , state ) ;
msleep ( SILEAD_POWER_SLEEP ) ;
}
}
static void silead_ts_read_data ( struct i2c_client * client )
{
struct silead_ts_data * data = i2c_get_clientdata ( client ) ;
struct input_dev * input = data - > input ;
struct device * dev = & client - > dev ;
u8 * bufp , buf [ SILEAD_TS_DATA_LEN ] ;
2018-01-09 11:34:15 -08:00
int touch_nr , softbutton , error , i ;
bool softbutton_pressed = false ;
2016-07-28 14:28:46 -07:00
error = i2c_smbus_read_i2c_block_data ( client , SILEAD_REG_DATA ,
SILEAD_TS_DATA_LEN , buf ) ;
if ( error < 0 ) {
dev_err ( dev , " Data read error %d \n " , error ) ;
return ;
}
2018-01-09 11:34:15 -08:00
if ( buf [ 0 ] > data - > max_fingers ) {
2016-07-28 14:28:46 -07:00
dev_warn ( dev , " More touches reported then supported %d > %d \n " ,
2018-01-09 11:34:15 -08:00
buf [ 0 ] , data - > max_fingers ) ;
buf [ 0 ] = data - > max_fingers ;
2016-07-28 14:28:46 -07:00
}
2018-01-09 11:34:15 -08:00
touch_nr = 0 ;
2016-07-28 14:28:46 -07:00
bufp = buf + SILEAD_POINT_DATA_LEN ;
2018-01-09 11:34:15 -08:00
for ( i = 0 ; i < buf [ 0 ] ; i + + , bufp + = SILEAD_POINT_DATA_LEN ) {
softbutton = ( bufp [ SILEAD_POINT_Y_MSB_OFF ] &
SILEAD_EXTRA_DATA_MASK ) > > 4 ;
if ( softbutton ) {
/*
* For now only respond to softbutton = = 0x01 , some
* tablets * without * a capacative button send 0x04
* when crossing the edges of the screen .
*/
if ( softbutton = = 0x01 )
softbutton_pressed = true ;
continue ;
}
/*
* Bits 4 - 7 are the touch id , note not all models have
* hardware touch ids so atm we don ' t use these .
*/
data - > id [ touch_nr ] = ( bufp [ SILEAD_POINT_X_MSB_OFF ] &
SILEAD_EXTRA_DATA_MASK ) > > 4 ;
touchscreen_set_mt_pos ( & data - > pos [ touch_nr ] , & data - > prop ,
2016-07-28 14:28:46 -07:00
get_unaligned_le16 ( & bufp [ SILEAD_POINT_X_OFF ] ) & 0xfff ,
get_unaligned_le16 ( & bufp [ SILEAD_POINT_Y_OFF ] ) & 0xfff ) ;
2018-01-09 11:34:15 -08:00
touch_nr + + ;
2016-07-28 14:28:46 -07:00
}
input_mt_assign_slots ( input , data - > slots , data - > pos , touch_nr , 0 ) ;
for ( i = 0 ; i < touch_nr ; i + + ) {
input_mt_slot ( input , data - > slots [ i ] ) ;
input_mt_report_slot_state ( input , MT_TOOL_FINGER , true ) ;
input_report_abs ( input , ABS_MT_POSITION_X , data - > pos [ i ] . x ) ;
input_report_abs ( input , ABS_MT_POSITION_Y , data - > pos [ i ] . y ) ;
dev_dbg ( dev , " x=%d y=%d hw_id=%d sw_id=%d \n " , data - > pos [ i ] . x ,
data - > pos [ i ] . y , data - > id [ i ] , data - > slots [ i ] ) ;
}
input_mt_sync_frame ( input ) ;
2018-01-09 11:34:15 -08:00
input_report_key ( input , KEY_LEFTMETA , softbutton_pressed ) ;
2016-07-28 14:28:46 -07:00
input_sync ( input ) ;
}
static int silead_ts_init ( struct i2c_client * client )
{
struct silead_ts_data * data = i2c_get_clientdata ( client ) ;
int error ;
error = i2c_smbus_write_byte_data ( client , SILEAD_REG_RESET ,
SILEAD_CMD_RESET ) ;
if ( error )
goto i2c_write_err ;
usleep_range ( SILEAD_CMD_SLEEP_MIN , SILEAD_CMD_SLEEP_MAX ) ;
error = i2c_smbus_write_byte_data ( client , SILEAD_REG_TOUCH_NR ,
data - > max_fingers ) ;
if ( error )
goto i2c_write_err ;
usleep_range ( SILEAD_CMD_SLEEP_MIN , SILEAD_CMD_SLEEP_MAX ) ;
error = i2c_smbus_write_byte_data ( client , SILEAD_REG_CLOCK ,
SILEAD_CLOCK ) ;
if ( error )
goto i2c_write_err ;
usleep_range ( SILEAD_CMD_SLEEP_MIN , SILEAD_CMD_SLEEP_MAX ) ;
error = i2c_smbus_write_byte_data ( client , SILEAD_REG_RESET ,
SILEAD_CMD_START ) ;
if ( error )
goto i2c_write_err ;
usleep_range ( SILEAD_CMD_SLEEP_MIN , SILEAD_CMD_SLEEP_MAX ) ;
return 0 ;
i2c_write_err :
dev_err ( & client - > dev , " Registers clear error %d \n " , error ) ;
return error ;
}
static int silead_ts_reset ( struct i2c_client * client )
{
int error ;
error = i2c_smbus_write_byte_data ( client , SILEAD_REG_RESET ,
SILEAD_CMD_RESET ) ;
if ( error )
goto i2c_write_err ;
usleep_range ( SILEAD_CMD_SLEEP_MIN , SILEAD_CMD_SLEEP_MAX ) ;
error = i2c_smbus_write_byte_data ( client , SILEAD_REG_CLOCK ,
SILEAD_CLOCK ) ;
if ( error )
goto i2c_write_err ;
usleep_range ( SILEAD_CMD_SLEEP_MIN , SILEAD_CMD_SLEEP_MAX ) ;
error = i2c_smbus_write_byte_data ( client , SILEAD_REG_POWER ,
SILEAD_CMD_START ) ;
if ( error )
goto i2c_write_err ;
usleep_range ( SILEAD_CMD_SLEEP_MIN , SILEAD_CMD_SLEEP_MAX ) ;
return 0 ;
i2c_write_err :
dev_err ( & client - > dev , " Chip reset error %d \n " , error ) ;
return error ;
}
static int silead_ts_startup ( struct i2c_client * client )
{
int error ;
error = i2c_smbus_write_byte_data ( client , SILEAD_REG_RESET , 0x00 ) ;
if ( error ) {
dev_err ( & client - > dev , " Startup error %d \n " , error ) ;
return error ;
}
msleep ( SILEAD_STARTUP_SLEEP ) ;
return 0 ;
}
static int silead_ts_load_fw ( struct i2c_client * client )
{
struct device * dev = & client - > dev ;
struct silead_ts_data * data = i2c_get_clientdata ( client ) ;
unsigned int fw_size , i ;
const struct firmware * fw ;
struct silead_fw_data * fw_data ;
int error ;
dev_dbg ( dev , " Firmware file name: %s " , data - > fw_name ) ;
error = request_firmware ( & fw , data - > fw_name , dev ) ;
if ( error ) {
dev_err ( dev , " Firmware request error %d \n " , error ) ;
return error ;
}
fw_size = fw - > size / sizeof ( * fw_data ) ;
fw_data = ( struct silead_fw_data * ) fw - > data ;
for ( i = 0 ; i < fw_size ; i + + ) {
error = i2c_smbus_write_i2c_block_data ( client ,
fw_data [ i ] . offset ,
4 ,
( u8 * ) & fw_data [ i ] . val ) ;
if ( error ) {
dev_err ( dev , " Firmware load error %d \n " , error ) ;
break ;
}
}
release_firmware ( fw ) ;
return error ? : 0 ;
}
static u32 silead_ts_get_status ( struct i2c_client * client )
{
int error ;
__le32 status ;
error = i2c_smbus_read_i2c_block_data ( client , SILEAD_REG_STATUS ,
sizeof ( status ) , ( u8 * ) & status ) ;
if ( error < 0 ) {
dev_err ( & client - > dev , " Status read error %d \n " , error ) ;
return error ;
}
return le32_to_cpu ( status ) ;
}
static int silead_ts_get_id ( struct i2c_client * client )
{
struct silead_ts_data * data = i2c_get_clientdata ( client ) ;
__le32 chip_id ;
int error ;
error = i2c_smbus_read_i2c_block_data ( client , SILEAD_REG_ID ,
sizeof ( chip_id ) , ( u8 * ) & chip_id ) ;
if ( error < 0 ) {
dev_err ( & client - > dev , " Chip ID read error %d \n " , error ) ;
return error ;
}
data - > chip_id = le32_to_cpu ( chip_id ) ;
dev_info ( & client - > dev , " Silead chip ID: 0x%8X " , data - > chip_id ) ;
return 0 ;
}
static int silead_ts_setup ( struct i2c_client * client )
{
int error ;
u32 status ;
silead_ts_set_power ( client , SILEAD_POWER_OFF ) ;
silead_ts_set_power ( client , SILEAD_POWER_ON ) ;
error = silead_ts_get_id ( client ) ;
if ( error )
return error ;
error = silead_ts_init ( client ) ;
if ( error )
return error ;
error = silead_ts_reset ( client ) ;
if ( error )
return error ;
error = silead_ts_load_fw ( client ) ;
if ( error )
return error ;
error = silead_ts_startup ( client ) ;
if ( error )
return error ;
status = silead_ts_get_status ( client ) ;
if ( status ! = SILEAD_STATUS_OK ) {
dev_err ( & client - > dev ,
" Initialization error, status: 0x%X \n " , status ) ;
return - ENODEV ;
}
return 0 ;
}
static irqreturn_t silead_ts_threaded_irq_handler ( int irq , void * id )
{
struct silead_ts_data * data = id ;
struct i2c_client * client = data - > client ;
silead_ts_read_data ( client ) ;
return IRQ_HANDLED ;
}
static void silead_ts_read_props ( struct i2c_client * client )
{
struct silead_ts_data * data = i2c_get_clientdata ( client ) ;
struct device * dev = & client - > dev ;
const char * str ;
int error ;
error = device_property_read_u32 ( dev , " silead,max-fingers " ,
& data - > max_fingers ) ;
if ( error ) {
dev_dbg ( dev , " Max fingers read error %d \n " , error ) ;
data - > max_fingers = 5 ; /* Most devices handle up-to 5 fingers */
}
2016-09-07 19:25:37 -07:00
error = device_property_read_string ( dev , " firmware-name " , & str ) ;
2016-07-28 14:28:46 -07:00
if ( ! error )
2016-09-07 19:25:37 -07:00
snprintf ( data - > fw_name , sizeof ( data - > fw_name ) ,
" silead/%s " , str ) ;
2016-07-28 14:28:46 -07:00
else
dev_dbg ( dev , " Firmware file name read error. Using default. " ) ;
}
# ifdef CONFIG_ACPI
static int silead_ts_set_default_fw_name ( struct silead_ts_data * data ,
const struct i2c_device_id * id )
{
const struct acpi_device_id * acpi_id ;
struct device * dev = & data - > client - > dev ;
int i ;
if ( ACPI_HANDLE ( dev ) ) {
acpi_id = acpi_match_device ( dev - > driver - > acpi_match_table , dev ) ;
if ( ! acpi_id )
return - ENODEV ;
2016-09-07 19:32:14 -07:00
snprintf ( data - > fw_name , sizeof ( data - > fw_name ) ,
" silead/%s.fw " , acpi_id - > id ) ;
2016-07-28 14:28:46 -07:00
for ( i = 0 ; i < strlen ( data - > fw_name ) ; i + + )
data - > fw_name [ i ] = tolower ( data - > fw_name [ i ] ) ;
} else {
2016-09-07 19:32:14 -07:00
snprintf ( data - > fw_name , sizeof ( data - > fw_name ) ,
" silead/%s.fw " , id - > name ) ;
2016-07-28 14:28:46 -07:00
}
return 0 ;
}
# else
static int silead_ts_set_default_fw_name ( struct silead_ts_data * data ,
const struct i2c_device_id * id )
{
2016-09-07 19:32:14 -07:00
snprintf ( data - > fw_name , sizeof ( data - > fw_name ) ,
" silead/%s.fw " , id - > name ) ;
2016-07-28 14:28:46 -07:00
return 0 ;
}
# endif
2016-11-16 09:37:08 -08:00
static void silead_disable_regulator ( void * arg )
{
struct silead_ts_data * data = arg ;
regulator_bulk_disable ( ARRAY_SIZE ( data - > regulators ) , data - > regulators ) ;
}
2016-07-28 14:28:46 -07:00
static int silead_ts_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct silead_ts_data * data ;
struct device * dev = & client - > dev ;
int error ;
if ( ! i2c_check_functionality ( client - > adapter ,
I2C_FUNC_I2C |
I2C_FUNC_SMBUS_READ_I2C_BLOCK |
I2C_FUNC_SMBUS_WRITE_I2C_BLOCK ) ) {
dev_err ( dev , " I2C functionality check failed \n " ) ;
return - ENXIO ;
}
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
i2c_set_clientdata ( client , data ) ;
data - > client = client ;
error = silead_ts_set_default_fw_name ( data , id ) ;
if ( error )
return error ;
silead_ts_read_props ( client ) ;
/* We must have the IRQ provided by DT or ACPI subsytem */
if ( client - > irq < = 0 )
return - ENODEV ;
2016-11-16 09:37:08 -08:00
data - > regulators [ 0 ] . supply = " vddio " ;
data - > regulators [ 1 ] . supply = " avdd " ;
error = devm_regulator_bulk_get ( dev , ARRAY_SIZE ( data - > regulators ) ,
data - > regulators ) ;
if ( error )
return error ;
/*
* Enable regulators at probe and disable them at remove , we need
* to keep the chip powered otherwise it forgets its firmware .
*/
error = regulator_bulk_enable ( ARRAY_SIZE ( data - > regulators ) ,
data - > regulators ) ;
if ( error )
return error ;
error = devm_add_action_or_reset ( dev , silead_disable_regulator , data ) ;
if ( error )
return error ;
2016-07-28 14:28:46 -07:00
/* Power GPIO pin */
2016-08-22 13:49:59 -07:00
data - > gpio_power = devm_gpiod_get_optional ( dev , " power " , GPIOD_OUT_LOW ) ;
2016-07-28 14:28:46 -07:00
if ( IS_ERR ( data - > gpio_power ) ) {
if ( PTR_ERR ( data - > gpio_power ) ! = - EPROBE_DEFER )
dev_err ( dev , " Shutdown GPIO request failed \n " ) ;
return PTR_ERR ( data - > gpio_power ) ;
}
error = silead_ts_setup ( client ) ;
if ( error )
return error ;
error = silead_ts_request_input_dev ( data ) ;
if ( error )
return error ;
error = devm_request_threaded_irq ( dev , client - > irq ,
NULL , silead_ts_threaded_irq_handler ,
IRQF_ONESHOT , client - > name , data ) ;
if ( error ) {
if ( error ! = - EPROBE_DEFER )
dev_err ( dev , " IRQ request failed %d \n " , error ) ;
return error ;
}
return 0 ;
}
static int __maybe_unused silead_ts_suspend ( struct device * dev )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
2017-05-26 16:09:48 -07:00
disable_irq ( client - > irq ) ;
2016-07-28 14:28:46 -07:00
silead_ts_set_power ( client , SILEAD_POWER_OFF ) ;
return 0 ;
}
static int __maybe_unused silead_ts_resume ( struct device * dev )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
int error , status ;
silead_ts_set_power ( client , SILEAD_POWER_ON ) ;
error = silead_ts_reset ( client ) ;
if ( error )
return error ;
error = silead_ts_startup ( client ) ;
if ( error )
return error ;
status = silead_ts_get_status ( client ) ;
if ( status ! = SILEAD_STATUS_OK ) {
dev_err ( dev , " Resume error, status: 0x%02x \n " , status ) ;
return - ENODEV ;
}
2017-05-26 16:09:48 -07:00
enable_irq ( client - > irq ) ;
2016-07-28 14:28:46 -07:00
return 0 ;
}
static SIMPLE_DEV_PM_OPS ( silead_ts_pm , silead_ts_suspend , silead_ts_resume ) ;
static const struct i2c_device_id silead_ts_id [ ] = {
{ " gsl1680 " , 0 } ,
{ " gsl1688 " , 0 } ,
{ " gsl3670 " , 0 } ,
{ " gsl3675 " , 0 } ,
{ " gsl3692 " , 0 } ,
{ " mssl1680 " , 0 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( i2c , silead_ts_id ) ;
# ifdef CONFIG_ACPI
static const struct acpi_device_id silead_ts_acpi_match [ ] = {
{ " GSL1680 " , 0 } ,
{ " GSL1688 " , 0 } ,
{ " GSL3670 " , 0 } ,
{ " GSL3675 " , 0 } ,
{ " GSL3692 " , 0 } ,
{ " MSSL1680 " , 0 } ,
2018-03-14 09:55:24 -07:00
{ " MSSL0001 " , 0 } ,
2018-06-05 09:34:22 -07:00
{ " MSSL0002 " , 0 } ,
2016-07-28 14:28:46 -07:00
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , silead_ts_acpi_match ) ;
# endif
2017-02-23 00:44:28 -08:00
# ifdef CONFIG_OF
static const struct of_device_id silead_ts_of_match [ ] = {
{ . compatible = " silead,gsl1680 " } ,
{ . compatible = " silead,gsl1688 " } ,
{ . compatible = " silead,gsl3670 " } ,
{ . compatible = " silead,gsl3675 " } ,
{ . compatible = " silead,gsl3692 " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , silead_ts_of_match ) ;
# endif
2016-07-28 14:28:46 -07:00
static struct i2c_driver silead_ts_driver = {
. probe = silead_ts_probe ,
. id_table = silead_ts_id ,
. driver = {
. name = SILEAD_TS_NAME ,
. acpi_match_table = ACPI_PTR ( silead_ts_acpi_match ) ,
2017-02-23 00:44:28 -08:00
. of_match_table = of_match_ptr ( silead_ts_of_match ) ,
2016-07-28 14:28:46 -07:00
. pm = & silead_ts_pm ,
} ,
} ;
module_i2c_driver ( silead_ts_driver ) ;
MODULE_AUTHOR ( " Robert Dolca <robert.dolca@intel.com> " ) ;
MODULE_DESCRIPTION ( " Silead I2C touchscreen driver " ) ;
MODULE_LICENSE ( " GPL " ) ;