2009-06-11 08:08:39 -07:00
/*
* Synaptics touchpad with I2C interface
*
* Copyright ( C ) 2009 Compulab , Ltd .
* Mike Rapoport < mike @ compulab . co . il >
* Igor Grinberg < grinberg @ compulab . co . il >
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file COPYING in the main directory of this archive for
* more details .
*/
# include <linux/module.h>
# include <linux/i2c.h>
# include <linux/irq.h>
# include <linux/interrupt.h>
# include <linux/input.h>
# include <linux/delay.h>
# include <linux/workqueue.h>
# define DRIVER_NAME "synaptics_i2c"
/* maximum product id is 15 characters */
# define PRODUCT_ID_LENGTH 15
# define REGISTER_LENGTH 8
/*
* after soft reset , we should wait for 1 ms
* before the device becomes operational
*/
# define SOFT_RESET_DELAY_MS 3
/* and after hard reset, we should wait for max 500ms */
# define HARD_RESET_DELAY_MS 500
/* Registers by SMBus address */
# define PAGE_SEL_REG 0xff
# define DEVICE_STATUS_REG 0x09
/* Registers by RMI address */
# define DEV_CONTROL_REG 0x0000
# define INTERRUPT_EN_REG 0x0001
# define ERR_STAT_REG 0x0002
# define INT_REQ_STAT_REG 0x0003
# define DEV_COMMAND_REG 0x0004
# define RMI_PROT_VER_REG 0x0200
# define MANUFACT_ID_REG 0x0201
# define PHYS_INT_VER_REG 0x0202
# define PROD_PROPERTY_REG 0x0203
# define INFO_QUERY_REG0 0x0204
# define INFO_QUERY_REG1 (INFO_QUERY_REG0 + 1)
# define INFO_QUERY_REG2 (INFO_QUERY_REG0 + 2)
# define INFO_QUERY_REG3 (INFO_QUERY_REG0 + 3)
# define PRODUCT_ID_REG0 0x0210
# define PRODUCT_ID_REG1 (PRODUCT_ID_REG0 + 1)
# define PRODUCT_ID_REG2 (PRODUCT_ID_REG0 + 2)
# define PRODUCT_ID_REG3 (PRODUCT_ID_REG0 + 3)
# define PRODUCT_ID_REG4 (PRODUCT_ID_REG0 + 4)
# define PRODUCT_ID_REG5 (PRODUCT_ID_REG0 + 5)
# define PRODUCT_ID_REG6 (PRODUCT_ID_REG0 + 6)
# define PRODUCT_ID_REG7 (PRODUCT_ID_REG0 + 7)
# define PRODUCT_ID_REG8 (PRODUCT_ID_REG0 + 8)
# define PRODUCT_ID_REG9 (PRODUCT_ID_REG0 + 9)
# define PRODUCT_ID_REG10 (PRODUCT_ID_REG0 + 10)
# define PRODUCT_ID_REG11 (PRODUCT_ID_REG0 + 11)
# define PRODUCT_ID_REG12 (PRODUCT_ID_REG0 + 12)
# define PRODUCT_ID_REG13 (PRODUCT_ID_REG0 + 13)
# define PRODUCT_ID_REG14 (PRODUCT_ID_REG0 + 14)
# define PRODUCT_ID_REG15 (PRODUCT_ID_REG0 + 15)
# define DATA_REG0 0x0400
# define ABS_PRESSURE_REG 0x0401
# define ABS_MSB_X_REG 0x0402
# define ABS_LSB_X_REG (ABS_MSB_X_REG + 1)
# define ABS_MSB_Y_REG 0x0404
# define ABS_LSB_Y_REG (ABS_MSB_Y_REG + 1)
# define REL_X_REG 0x0406
# define REL_Y_REG 0x0407
# define DEV_QUERY_REG0 0x1000
# define DEV_QUERY_REG1 (DEV_QUERY_REG0 + 1)
# define DEV_QUERY_REG2 (DEV_QUERY_REG0 + 2)
# define DEV_QUERY_REG3 (DEV_QUERY_REG0 + 3)
# define DEV_QUERY_REG4 (DEV_QUERY_REG0 + 4)
# define DEV_QUERY_REG5 (DEV_QUERY_REG0 + 5)
# define DEV_QUERY_REG6 (DEV_QUERY_REG0 + 6)
# define DEV_QUERY_REG7 (DEV_QUERY_REG0 + 7)
# define DEV_QUERY_REG8 (DEV_QUERY_REG0 + 8)
# define GENERAL_2D_CONTROL_REG 0x1041
# define SENSOR_SENSITIVITY_REG 0x1044
# define SENS_MAX_POS_MSB_REG 0x1046
# define SENS_MAX_POS_LSB_REG (SENS_MAX_POS_UPPER_REG + 1)
/* Register bits */
/* Device Control Register Bits */
# define REPORT_RATE_1ST_BIT 6
/* Interrupt Enable Register Bits (INTERRUPT_EN_REG) */
# define F10_ABS_INT_ENA 0
# define F10_REL_INT_ENA 1
# define F20_INT_ENA 2
/* Interrupt Request Register Bits (INT_REQ_STAT_REG | DEVICE_STATUS_REG) */
# define F10_ABS_INT_REQ 0
# define F10_REL_INT_REQ 1
# define F20_INT_REQ 2
/* Device Status Register Bits (DEVICE_STATUS_REG) */
# define STAT_CONFIGURED 6
# define STAT_ERROR 7
/* Device Command Register Bits (DEV_COMMAND_REG) */
# define RESET_COMMAND 0x01
# define REZERO_COMMAND 0x02
/* Data Register 0 Bits (DATA_REG0) */
# define GESTURE 3
/* Device Query Registers Bits */
/* DEV_QUERY_REG3 */
# define HAS_PALM_DETECT 1
# define HAS_MULTI_FING 2
# define HAS_SCROLLER 4
# define HAS_2D_SCROLL 5
/* General 2D Control Register Bits (GENERAL_2D_CONTROL_REG) */
# define NO_DECELERATION 1
# define REDUCE_REPORTING 3
# define NO_FILTER 5
/* Function Masks */
/* Device Control Register Masks (DEV_CONTROL_REG) */
# define REPORT_RATE_MSK 0xc0
# define SLEEP_MODE_MSK 0x07
/* Device Sleep Modes */
# define FULL_AWAKE 0x0
# define NORMAL_OP 0x1
# define LOW_PWR_OP 0x2
# define VERY_LOW_PWR_OP 0x3
# define SENS_SLEEP 0x4
# define SLEEP_MOD 0x5
# define DEEP_SLEEP 0x6
# define HIBERNATE 0x7
/* Interrupt Register Mask */
/* (INT_REQ_STAT_REG | DEVICE_STATUS_REG | INTERRUPT_EN_REG) */
# define INT_ENA_REQ_MSK 0x07
# define INT_ENA_ABS_MSK 0x01
# define INT_ENA_REL_MSK 0x02
# define INT_ENA_F20_MSK 0x04
/* Device Status Register Masks (DEVICE_STATUS_REG) */
# define CONFIGURED_MSK 0x40
# define ERROR_MSK 0x80
/* Data Register 0 Masks */
# define FINGER_WIDTH_MSK 0xf0
# define GESTURE_MSK 0x08
# define SENSOR_STATUS_MSK 0x07
/*
* MSB Position Register Masks
* ABS_MSB_X_REG | ABS_MSB_Y_REG | SENS_MAX_POS_MSB_REG |
* DEV_QUERY_REG3 | DEV_QUERY_REG5
*/
# define MSB_POSITION_MSK 0x1f
/* Device Query Registers Masks */
/* DEV_QUERY_REG2 */
# define NUM_EXTRA_POS_MSK 0x07
/* When in IRQ mode read the device every THREAD_IRQ_SLEEP_SECS */
# define THREAD_IRQ_SLEEP_SECS 2
# define THREAD_IRQ_SLEEP_MSECS (THREAD_IRQ_SLEEP_SECS * MSEC_PER_SEC)
/*
* When in Polling mode and no data received for NO_DATA_THRES msecs
* reduce the polling rate to NO_DATA_SLEEP_MSECS
*/
# define NO_DATA_THRES (MSEC_PER_SEC)
# define NO_DATA_SLEEP_MSECS (MSEC_PER_SEC / 4)
/* Control touchpad's No Deceleration option */
static int no_decel = 1 ;
module_param ( no_decel , bool , 0644 ) ;
MODULE_PARM_DESC ( no_decel , " No Deceleration. Default = 1 (on) " ) ;
/* Control touchpad's Reduced Reporting option */
static int reduce_report ;
module_param ( reduce_report , bool , 0644 ) ;
MODULE_PARM_DESC ( reduce_report , " Reduced Reporting. Default = 0 (off) " ) ;
/* Control touchpad's No Filter option */
static int no_filter ;
module_param ( no_filter , bool , 0644 ) ;
MODULE_PARM_DESC ( no_filter , " No Filter. Default = 0 (off) " ) ;
/*
* touchpad Attention line is Active Low and Open Drain ,
* therefore should be connected to pulled up line
* and the irq configuration should be set to Falling Edge Trigger
*/
/* Control IRQ / Polling option */
2009-09-16 01:06:42 -07:00
static bool polling_req ;
2009-06-11 08:08:39 -07:00
module_param ( polling_req , bool , 0444 ) ;
MODULE_PARM_DESC ( polling_req , " Request Polling. Default = 0 (use irq) " ) ;
/* Control Polling Rate */
static int scan_rate = 80 ;
module_param ( scan_rate , int , 0644 ) ;
MODULE_PARM_DESC ( scan_rate , " Polling rate in times/sec. Default = 80 " ) ;
/* The main device structure */
struct synaptics_i2c {
struct i2c_client * client ;
struct input_dev * input ;
struct delayed_work dwork ;
2009-09-16 01:06:42 -07:00
spinlock_t lock ;
2009-06-11 08:08:39 -07:00
int no_data_count ;
int no_decel_param ;
int reduce_report_param ;
int no_filter_param ;
int scan_rate_param ;
int scan_ms ;
} ;
static inline void set_scan_rate ( struct synaptics_i2c * touch , int scan_rate )
{
touch - > scan_ms = MSEC_PER_SEC / scan_rate ;
touch - > scan_rate_param = scan_rate ;
}
/*
* Driver ' s initial design makes no race condition possible on i2c bus ,
* so there is no need in any locking .
* Keep it in mind , while playing with the code .
*/
static s32 synaptics_i2c_reg_get ( struct i2c_client * client , u16 reg )
{
int ret ;
ret = i2c_smbus_write_byte_data ( client , PAGE_SEL_REG , reg > > 8 ) ;
if ( ret = = 0 )
ret = i2c_smbus_read_byte_data ( client , reg & 0xff ) ;
return ret ;
}
static s32 synaptics_i2c_reg_set ( struct i2c_client * client , u16 reg , u8 val )
{
int ret ;
ret = i2c_smbus_write_byte_data ( client , PAGE_SEL_REG , reg > > 8 ) ;
if ( ret = = 0 )
ret = i2c_smbus_write_byte_data ( client , reg & 0xff , val ) ;
return ret ;
}
static s32 synaptics_i2c_word_get ( struct i2c_client * client , u16 reg )
{
int ret ;
ret = i2c_smbus_write_byte_data ( client , PAGE_SEL_REG , reg > > 8 ) ;
if ( ret = = 0 )
ret = i2c_smbus_read_word_data ( client , reg & 0xff ) ;
return ret ;
}
static int synaptics_i2c_config ( struct i2c_client * client )
{
int ret , control ;
u8 int_en ;
/* set Report Rate to Device Highest (>=80) and Sleep to normal */
ret = synaptics_i2c_reg_set ( client , DEV_CONTROL_REG , 0xc1 ) ;
if ( ret )
return ret ;
/* set Interrupt Disable to Func20 / Enable to Func10) */
int_en = ( polling_req ) ? 0 : INT_ENA_ABS_MSK | INT_ENA_REL_MSK ;
ret = synaptics_i2c_reg_set ( client , INTERRUPT_EN_REG , int_en ) ;
if ( ret )
return ret ;
control = synaptics_i2c_reg_get ( client , GENERAL_2D_CONTROL_REG ) ;
/* No Deceleration */
control | = no_decel ? 1 < < NO_DECELERATION : 0 ;
/* Reduced Reporting */
control | = reduce_report ? 1 < < REDUCE_REPORTING : 0 ;
/* No Filter */
control | = no_filter ? 1 < < NO_FILTER : 0 ;
ret = synaptics_i2c_reg_set ( client , GENERAL_2D_CONTROL_REG , control ) ;
if ( ret )
return ret ;
return 0 ;
}
static int synaptics_i2c_reset_config ( struct i2c_client * client )
{
int ret ;
/* Reset the Touchpad */
ret = synaptics_i2c_reg_set ( client , DEV_COMMAND_REG , RESET_COMMAND ) ;
if ( ret ) {
dev_err ( & client - > dev , " Unable to reset device \n " ) ;
} else {
msleep ( SOFT_RESET_DELAY_MS ) ;
ret = synaptics_i2c_config ( client ) ;
if ( ret )
dev_err ( & client - > dev , " Unable to config device \n " ) ;
}
return ret ;
}
static int synaptics_i2c_check_error ( struct i2c_client * client )
{
int status , ret = 0 ;
status = i2c_smbus_read_byte_data ( client , DEVICE_STATUS_REG ) &
( CONFIGURED_MSK | ERROR_MSK ) ;
if ( status ! = CONFIGURED_MSK )
ret = synaptics_i2c_reset_config ( client ) ;
return ret ;
}
static bool synaptics_i2c_get_input ( struct synaptics_i2c * touch )
{
struct input_dev * input = touch - > input ;
int xy_delta , gesture ;
s32 data ;
s8 x_delta , y_delta ;
/* Deal with spontanious resets and errors */
if ( synaptics_i2c_check_error ( touch - > client ) )
return 0 ;
/* Get Gesture Bit */
data = synaptics_i2c_reg_get ( touch - > client , DATA_REG0 ) ;
gesture = ( data > > GESTURE ) & 0x1 ;
/*
* Get Relative axes . we have to get them in one shot ,
* so we get 2 bytes starting from REL_X_REG .
*/
xy_delta = synaptics_i2c_word_get ( touch - > client , REL_X_REG ) & 0xffff ;
/* Separate X from Y */
x_delta = xy_delta & 0xff ;
y_delta = ( xy_delta > > REGISTER_LENGTH ) & 0xff ;
/* Report the button event */
input_report_key ( input , BTN_LEFT , gesture ) ;
/* Report the deltas */
input_report_rel ( input , REL_X , x_delta ) ;
input_report_rel ( input , REL_Y , - y_delta ) ;
input_sync ( input ) ;
return xy_delta | | gesture ;
}
2009-09-16 01:06:42 -07:00
static void synaptics_i2c_reschedule_work ( struct synaptics_i2c * touch ,
unsigned long delay )
2009-06-11 08:08:39 -07:00
{
2009-09-16 01:06:42 -07:00
unsigned long flags ;
spin_lock_irqsave ( & touch - > lock , flags ) ;
2009-06-11 08:08:39 -07:00
/*
2009-09-16 01:06:42 -07:00
* If work is already scheduled then subsequent schedules will not
* change the scheduled time that ' s why we have to cancel it first .
2009-06-11 08:08:39 -07:00
*/
2009-09-16 01:06:42 -07:00
__cancel_delayed_work ( & touch - > dwork ) ;
schedule_delayed_work ( & touch - > dwork , delay ) ;
spin_unlock_irqrestore ( & touch - > lock , flags ) ;
}
static irqreturn_t synaptics_i2c_irq ( int irq , void * dev_id )
{
struct synaptics_i2c * touch = dev_id ;
synaptics_i2c_reschedule_work ( touch , 0 ) ;
2009-06-11 08:08:39 -07:00
return IRQ_HANDLED ;
}
static void synaptics_i2c_check_params ( struct synaptics_i2c * touch )
{
bool reset = false ;
if ( scan_rate ! = touch - > scan_rate_param )
set_scan_rate ( touch , scan_rate ) ;
if ( no_decel ! = touch - > no_decel_param ) {
touch - > no_decel_param = no_decel ;
reset = true ;
}
if ( no_filter ! = touch - > no_filter_param ) {
touch - > no_filter_param = no_filter ;
reset = true ;
}
if ( reduce_report ! = touch - > reduce_report_param ) {
touch - > reduce_report_param = reduce_report ;
reset = true ;
}
if ( reset )
synaptics_i2c_reset_config ( touch - > client ) ;
}
/* Control the Device polling rate / Work Handler sleep time */
2009-11-02 21:57:40 -08:00
static unsigned long synaptics_i2c_adjust_delay ( struct synaptics_i2c * touch ,
bool have_data )
2009-06-11 08:08:39 -07:00
{
unsigned long delay , nodata_count_thres ;
if ( polling_req ) {
delay = touch - > scan_ms ;
if ( have_data ) {
touch - > no_data_count = 0 ;
} else {
nodata_count_thres = NO_DATA_THRES / touch - > scan_ms ;
if ( touch - > no_data_count < nodata_count_thres )
touch - > no_data_count + + ;
else
delay = NO_DATA_SLEEP_MSECS ;
}
return msecs_to_jiffies ( delay ) ;
} else {
delay = msecs_to_jiffies ( THREAD_IRQ_SLEEP_MSECS ) ;
return round_jiffies_relative ( delay ) ;
}
}
/* Work Handler */
static void synaptics_i2c_work_handler ( struct work_struct * work )
{
bool have_data ;
struct synaptics_i2c * touch =
container_of ( work , struct synaptics_i2c , dwork . work ) ;
unsigned long delay ;
synaptics_i2c_check_params ( touch ) ;
have_data = synaptics_i2c_get_input ( touch ) ;
delay = synaptics_i2c_adjust_delay ( touch , have_data ) ;
/*
* While interrupt driven , there is no real need to poll the device .
* But touchpads are very sensitive , so there could be errors
* related to physical environment and the attention line isn ' t
* neccesarily asserted . In such case we can lose the touchpad .
* We poll the device once in THREAD_IRQ_SLEEP_SECS and
* if error is detected , we try to reset and reconfigure the touchpad .
*/
2009-09-16 01:06:42 -07:00
synaptics_i2c_reschedule_work ( touch , delay ) ;
2009-06-11 08:08:39 -07:00
}
static int synaptics_i2c_open ( struct input_dev * input )
{
struct synaptics_i2c * touch = input_get_drvdata ( input ) ;
int ret ;
ret = synaptics_i2c_reset_config ( touch - > client ) ;
if ( ret )
return ret ;
if ( polling_req )
2009-09-16 01:06:42 -07:00
synaptics_i2c_reschedule_work ( touch ,
msecs_to_jiffies ( NO_DATA_SLEEP_MSECS ) ) ;
2009-06-11 08:08:39 -07:00
return 0 ;
}
static void synaptics_i2c_close ( struct input_dev * input )
{
struct synaptics_i2c * touch = input_get_drvdata ( input ) ;
if ( ! polling_req )
synaptics_i2c_reg_set ( touch - > client , INTERRUPT_EN_REG , 0 ) ;
cancel_delayed_work_sync ( & touch - > dwork ) ;
/* Save some power */
synaptics_i2c_reg_set ( touch - > client , DEV_CONTROL_REG , DEEP_SLEEP ) ;
}
static void synaptics_i2c_set_input_params ( struct synaptics_i2c * touch )
{
struct input_dev * input = touch - > input ;
input - > name = touch - > client - > name ;
input - > phys = touch - > client - > adapter - > name ;
input - > id . bustype = BUS_I2C ;
input - > id . version = synaptics_i2c_word_get ( touch - > client ,
INFO_QUERY_REG0 ) ;
input - > dev . parent = & touch - > client - > dev ;
input - > open = synaptics_i2c_open ;
input - > close = synaptics_i2c_close ;
input_set_drvdata ( input , touch ) ;
/* Register the device as mouse */
__set_bit ( EV_REL , input - > evbit ) ;
__set_bit ( REL_X , input - > relbit ) ;
__set_bit ( REL_Y , input - > relbit ) ;
/* Register device's buttons and keys */
__set_bit ( EV_KEY , input - > evbit ) ;
__set_bit ( BTN_LEFT , input - > keybit ) ;
}
2009-11-02 21:57:40 -08:00
static struct synaptics_i2c * synaptics_i2c_touch_create ( struct i2c_client * client )
2009-06-11 08:08:39 -07:00
{
struct synaptics_i2c * touch ;
touch = kzalloc ( sizeof ( struct synaptics_i2c ) , GFP_KERNEL ) ;
if ( ! touch )
return NULL ;
touch - > client = client ;
touch - > no_decel_param = no_decel ;
touch - > scan_rate_param = scan_rate ;
set_scan_rate ( touch , scan_rate ) ;
INIT_DELAYED_WORK ( & touch - > dwork , synaptics_i2c_work_handler ) ;
2009-09-16 01:06:42 -07:00
spin_lock_init ( & touch - > lock ) ;
2009-06-11 08:08:39 -07:00
return touch ;
}
static int __devinit synaptics_i2c_probe ( struct i2c_client * client ,
const struct i2c_device_id * dev_id )
{
int ret ;
struct synaptics_i2c * touch ;
touch = synaptics_i2c_touch_create ( client ) ;
if ( ! touch )
return - ENOMEM ;
ret = synaptics_i2c_reset_config ( client ) ;
if ( ret )
goto err_mem_free ;
if ( client - > irq < 1 )
2009-09-16 01:06:42 -07:00
polling_req = true ;
2009-06-11 08:08:39 -07:00
touch - > input = input_allocate_device ( ) ;
if ( ! touch - > input ) {
ret = - ENOMEM ;
goto err_mem_free ;
}
synaptics_i2c_set_input_params ( touch ) ;
if ( ! polling_req ) {
dev_dbg ( & touch - > client - > dev ,
" Requesting IRQ: %d \n " , touch - > client - > irq ) ;
ret = request_irq ( touch - > client - > irq , synaptics_i2c_irq ,
IRQF_DISABLED | IRQ_TYPE_EDGE_FALLING ,
DRIVER_NAME , touch ) ;
if ( ret ) {
dev_warn ( & touch - > client - > dev ,
" IRQ request failed: %d, "
" falling back to polling \n " , ret ) ;
2009-09-16 01:06:42 -07:00
polling_req = true ;
2009-06-11 08:08:39 -07:00
synaptics_i2c_reg_set ( touch - > client ,
INTERRUPT_EN_REG , 0 ) ;
}
}
if ( polling_req )
dev_dbg ( & touch - > client - > dev ,
" Using polling at rate: %d times/sec \n " , scan_rate ) ;
/* Register the device in input subsystem */
ret = input_register_device ( touch - > input ) ;
if ( ret ) {
dev_err ( & client - > dev ,
" Input device register failed: %d \n " , ret ) ;
goto err_input_free ;
}
2009-09-16 01:06:42 -07:00
i2c_set_clientdata ( client , touch ) ;
2009-06-11 08:08:39 -07:00
return 0 ;
err_input_free :
input_free_device ( touch - > input ) ;
err_mem_free :
kfree ( touch ) ;
return ret ;
}
static int __devexit synaptics_i2c_remove ( struct i2c_client * client )
{
struct synaptics_i2c * touch = i2c_get_clientdata ( client ) ;
if ( ! polling_req )
2009-09-16 01:06:42 -07:00
free_irq ( client - > irq , touch ) ;
2009-06-11 08:08:39 -07:00
input_unregister_device ( touch - > input ) ;
i2c_set_clientdata ( client , NULL ) ;
kfree ( touch ) ;
return 0 ;
}
# ifdef CONFIG_PM
static int synaptics_i2c_suspend ( struct i2c_client * client , pm_message_t mesg )
{
struct synaptics_i2c * touch = i2c_get_clientdata ( client ) ;
cancel_delayed_work_sync ( & touch - > dwork ) ;
/* Save some power */
synaptics_i2c_reg_set ( touch - > client , DEV_CONTROL_REG , DEEP_SLEEP ) ;
return 0 ;
}
static int synaptics_i2c_resume ( struct i2c_client * client )
{
int ret ;
struct synaptics_i2c * touch = i2c_get_clientdata ( client ) ;
ret = synaptics_i2c_reset_config ( client ) ;
if ( ret )
return ret ;
2009-09-16 01:06:42 -07:00
synaptics_i2c_reschedule_work ( touch ,
msecs_to_jiffies ( NO_DATA_SLEEP_MSECS ) ) ;
2009-06-11 08:08:39 -07:00
return 0 ;
}
# else
# define synaptics_i2c_suspend NULL
# define synaptics_i2c_resume NULL
# endif
static const struct i2c_device_id synaptics_i2c_id_table [ ] = {
{ " synaptics_i2c " , 0 } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( i2c , synaptics_i2c_id_table ) ;
static struct i2c_driver synaptics_i2c_driver = {
. driver = {
. name = DRIVER_NAME ,
. owner = THIS_MODULE ,
} ,
. probe = synaptics_i2c_probe ,
. remove = __devexit_p ( synaptics_i2c_remove ) ,
. suspend = synaptics_i2c_suspend ,
. resume = synaptics_i2c_resume ,
. id_table = synaptics_i2c_id_table ,
} ;
static int __init synaptics_i2c_init ( void )
{
return i2c_add_driver ( & synaptics_i2c_driver ) ;
}
static void __exit synaptics_i2c_exit ( void )
{
i2c_del_driver ( & synaptics_i2c_driver ) ;
}
module_init ( synaptics_i2c_init ) ;
module_exit ( synaptics_i2c_exit ) ;
MODULE_DESCRIPTION ( " Synaptics I2C touchpad driver " ) ;
MODULE_AUTHOR ( " Mike Rapoport, Igor Grinberg, Compulab " ) ;
MODULE_LICENSE ( " GPL " ) ;