| 
									
										
										
										
											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"); | 
					
						
							|  |  |  | 
 |