/* * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * 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. * * VERSION DATE AUTHOR Note * 1.0 2013-7-16 Focaltech initial based on MTK platform * */ #include "tpd.h" #include #ifdef FTS_CTL_IIC #include "focaltech_ctl.h" /*create sysfs for debug*/ extern int fts_create_sysfs(struct i2c_client * client); extern void fts_release_sysfs(struct i2c_client * client); extern int ft5x0x_create_apk_debug_channel(struct i2c_client *client); extern void ft5x0x_release_apk_debug_channel(void); #endif #ifdef TPD_PROXIMITY #include #include #include #endif #ifdef VELOCITY_CUSTOM extern int tpd_v_magnify_x; extern int tpd_v_magnify_y; #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tpd_custom_ft5206.h" #include "focaltech_ex_fun.h" u8 *I2CDMABuf_va = NULL; dma_addr_t I2CDMABuf_pa = 0; #ifdef TPD_PROXIMITY #define APS_ERR(fmt,arg...) printk("<> "fmt"\n",##arg) #define TPD_PROXIMITY_DEBUG(fmt,arg...) printk("<> "fmt"\n",##arg) #define TPD_PROXIMITY_DMESG(fmt,arg...) printk("<> "fmt"\n",##arg) static u8 tpd_proximity_flag = 0; static u8 tpd_proximity_flag_one = 0; //add for tpd_proximity by wangdongfang static u8 tpd_proximity_detect = 1;//0-->close ; 1--> far away #endif extern struct tpd_device *tpd; struct i2c_client *i2c_client = NULL; struct task_struct *thread = NULL; #ifdef VELOCITY_CUSTOM extern int tpd_v_magnify_x; extern int tpd_v_magnify_y; #endif static DECLARE_WAIT_QUEUE_HEAD(waiter); static DEFINE_MUTEX(i2c_access); static void tpd_eint_interrupt_handler(void); static int tpd_probe(struct i2c_client *client, const struct i2c_device_id *id); static int tpd_detect (struct i2c_client *client, struct i2c_board_info *info); static int tpd_remove(struct i2c_client *client); static int touch_event_handler(void *unused); static int tpd_flag = 0; static int tpd_halt = 0; #define TPD_OK 0 #define TPD_RESET_ISSUE_WORKAROUND #define TPD_MAX_RESET_COUNT 3 //extern int tpd_mstar_status ; // compatible mstar and ft6306 chenzhecong #ifdef TPD_HAVE_BUTTON static int tpd_keys_local[TPD_KEY_COUNT] = TPD_KEYS; static int tpd_keys_dim_local[TPD_KEY_COUNT][4] = TPD_KEYS_DIM; #endif #if (defined(TPD_WARP_START) && defined(TPD_WARP_END)) static int tpd_wb_start_local[TPD_WARP_CNT] = TPD_WARP_START; static int tpd_wb_end_local[TPD_WARP_CNT] = TPD_WARP_END; #endif #if (defined(TPD_HAVE_CALIBRATION) && !defined(TPD_CUSTOM_CALIBRATION)) static int tpd_calmat_local[8] = TPD_CALIBRATION_MATRIX; static int tpd_def_calmat_local[8] = TPD_CALIBRATION_MATRIX; #endif #include #include #include struct touch_info { int y[10]; int x[10]; int p[10]; int id[10]; }; static const struct i2c_device_id ft5206_tpd_id[] = {{"mtk-focal",0},{}}; static struct i2c_board_info __initdata ft5206_i2c_tpd={ I2C_BOARD_INFO("mtk-focal", (0x70>>1))}; static struct i2c_driver tpd_i2c_driver = { .driver = { .name = "mtk-focal", }, .probe = tpd_probe, .remove = tpd_remove, .id_table = ft5206_tpd_id, .detect = tpd_detect, }; static void tpd_down(int x, int y, int id) { input_report_key(tpd->dev, BTN_TOUCH, 1); input_report_abs(tpd->dev, ABS_MT_TOUCH_MAJOR, 20); input_report_abs(tpd->dev, ABS_MT_POSITION_X, x); input_report_abs(tpd->dev, ABS_MT_POSITION_Y, y); input_report_abs(tpd->dev, ABS_MT_TRACKING_ID, id); input_mt_sync(tpd->dev); TPD_EM_PRINT(x, y, x, y, id-1, 1); if (FACTORY_BOOT == get_boot_mode()|| RECOVERY_BOOT == get_boot_mode()) { tpd_button(x, y, 1); } } static void tpd_up(int x, int y) { input_report_key(tpd->dev, BTN_TOUCH, 0); TPD_EM_PRINT(x, y, x, y, 0, 0); if (FACTORY_BOOT == get_boot_mode()|| RECOVERY_BOOT == get_boot_mode()) { tpd_button(x, y, 0); } } #ifdef TPD_PROXIMITY int tpd_read_ps(void) { tpd_proximity_detect; return 0; } static int tpd_get_ps_value(void) { return tpd_proximity_detect; } static int tpd_enable_ps(int enable) { u8 state; int ret = -1; i2c_smbus_read_i2c_block_data(i2c_client, 0xB0, 1, &state); printk("[proxi_5206]read: 999 0xb0's value is 0x%02X\n", state); if (enable){ state |= 0x01; tpd_proximity_flag = 1; TPD_PROXIMITY_DEBUG("[proxi_5206]ps function is on\n"); }else{ state &= 0x00; tpd_proximity_flag = 0; TPD_PROXIMITY_DEBUG("[proxi_5206]ps function is off\n"); } ret = i2c_smbus_write_i2c_block_data(i2c_client, 0xB0, 1, &state); TPD_PROXIMITY_DEBUG("[proxi_5206]write: 0xB0's value is 0x%02X\n", state); return 0; } int tpd_ps_operate(void* self, uint32_t command, void* buff_in, int size_in, void* buff_out, int size_out, int* actualout) { int err = 0; int value; hwm_sensor_data *sensor_data; TPD_DEBUG("[proxi_5206]command = 0x%02X\n", command); switch (command) { case SENSOR_DELAY: if((buff_in == NULL) || (size_in < sizeof(int))) { APS_ERR("Set delay parameter error!\n"); err = -EINVAL; } // Do nothing break; case SENSOR_ENABLE: if((buff_in == NULL) || (size_in < sizeof(int))) { APS_ERR("Enable sensor parameter error!\n"); err = -EINVAL; } else { value = *(int *)buff_in; if(value) { if((tpd_enable_ps(1) != 0)) { APS_ERR("enable ps fail: %d\n", err); return -1; } } else { if((tpd_enable_ps(0) != 0)) { APS_ERR("disable ps fail: %d\n", err); return -1; } } } break; case SENSOR_GET_DATA: if((buff_out == NULL) || (size_out< sizeof(hwm_sensor_data))) { APS_ERR("get sensor data parameter error!\n"); err = -EINVAL; } else { sensor_data = (hwm_sensor_data *)buff_out; if((err = tpd_read_ps())) { err = -1;; } else { sensor_data->values[0] = tpd_get_ps_value(); TPD_PROXIMITY_DEBUG("huang sensor_data->values[0] 1082 = %d\n", sensor_data->values[0]); sensor_data->value_divide = 1; sensor_data->status = SENSOR_STATUS_ACCURACY_MEDIUM; } } break; default: APS_ERR("proxmy sensor operate function no this parameter %d!\n", command); err = -1; break; } return err; } #endif static int tpd_i2c_read_bytes(struct i2c_client *client, u16 addr, u8 *rxbuf, int len) { u8 buffer[1]; u16 left = len; u16 offset = 0; struct i2c_msg msg[2] = { { .addr = ((client->addr &I2C_MASK_FLAG) | (I2C_ENEXT_FLAG)), .flags = 0, .buf = buffer, .len = 1, .timing = 300 }, { .addr = ((client->addr &I2C_MASK_FLAG) | (I2C_ENEXT_FLAG)), .flags = I2C_M_RD, .timing = 300 }, }; if (rxbuf == NULL) return -1; printk("i2c_read_bytes to device %02X address %04X len %d\n", client->addr, addr, len); while (left > 0) { buffer[0] = (addr + offset) & 0xFF; msg[1].buf = &rxbuf[offset]; if (left > 8) { msg[1].len = 8; left -= 8; offset += 8; } else { msg[1].len = left; left = 0; } if (i2c_transfer(client->adapter, &msg[0], 2) != 2) { printk("I2C read 0x%X length=%d failed\n", addr + offset, len); return -1; } } return 0; } static int tpd_touchinfo(struct touch_info *cinfo, struct touch_info *pinfo, int *point_num) { int i = 0; char data[128] = {0}; u16 high_byte,low_byte; u8 reg; mutex_lock(&i2c_access); if (tpd_halt) { mutex_unlock(&i2c_access); TPD_DMESG( "tpd_touchinfo return ..\n"); return false; } reg = 0x00; memset(data, 0, 32); #ifdef TPD_AUTO_UPGRADE fts_i2c_Read(i2c_client, ®, 1, data, 32); //tpd_i2c_read_bytes(i2c_client, 0x00, data, 32); #else i2c_smbus_read_i2c_block_data(i2c_client, 0x00, 8, &(data[0])); i2c_smbus_read_i2c_block_data(i2c_client, 0x08, 8, &(data[8])); i2c_smbus_read_i2c_block_data(i2c_client, 0x10, 8, &(data[16])); i2c_smbus_read_i2c_block_data(i2c_client, 0x18, 8, &(data[24])); #endif mutex_unlock(&i2c_access); //TPD_DEBUG("received raw data from touch panel as following:\n"); //TPD_DEBUG("[data[0]=%x,data[1]= %x ,data[2]=%x ,data[3]=%x ,data[4]=%x ,data[5]=%x]\n",data[0],data[1],data[2],data[3],data[4],data[5]); //TPD_DEBUG("[data[9]=%x,data[10]= %x ,data[11]=%x ,data[12]=%x]\n",data[9],data[10],data[11],data[12]); //TPD_DEBUG("[data[15]=%x,data[16]= %x ,data[17]=%x ,data[18]=%x]\n",data[15],data[16],data[17],data[18]); /*get the number of the touch points*/ *point_num= data[2] & 0x0f; //TPD_DEBUG("point_num =%d\n",point_num); for(i = 0; i < *point_num; i++) { //cinfo->p[i] = data[3+6*i] >> 6; //event flag cinfo->id[i] = data[3+6*i+2]>>4; //touch id /*get the X coordinate, 2 bytes*/ high_byte = data[3+6*i]; high_byte <<= 8; high_byte &= 0x0f00; low_byte = data[3+6*i + 1]; cinfo->x[i] = high_byte |low_byte; //cinfo->x[i] = cinfo->x[i] * 480 >> 11; //calibra /*get the Y coordinate, 2 bytes*/ high_byte = data[3+6*i+2]; high_byte <<= 8; high_byte &= 0x0f00; low_byte = data[3+6*i+3]; cinfo->y[i] = high_byte |low_byte; //cinfo->y[i]= cinfo->y[i] * 800 >> 11; } //TPD_DEBUG(" cinfo->x[0] = %d, cinfo->y[0] = %d, cinfo->id[0] = %d\n", cinfo->x[0], cinfo->y[0], cinfo->id[0]); //TPD_DEBUG(" cinfo->x[1] = %d, cinfo->y[1] = %d, cinfo->p[1] = %d\n", cinfo->x[1], cinfo->y[1], cinfo->p[1]); //TPD_DEBUG(" cinfo->x[2]= %d, cinfo->y[2]= %d, cinfo->p[2] = %d\n", cinfo->x[2], cinfo->y[2], cinfo->p[2]); return true; }; #ifdef TPD_AUTO_UPGRADE static int tpd_ft_update_fw(void *unused) { mt_eint_mask(CUST_EINT_TOUCH_PANEL_NUM); msleep(3000); printk("********************Enter CTP Auto Upgrade********************\n"); fts_ctpm_auto_upgrade(i2c_client); mt_eint_unmask(CUST_EINT_TOUCH_PANEL_NUM); return 0; } #endif static int touch_event_handler(void *unused) { struct touch_info cinfo, pinfo; int i=0, point_num=0; struct sched_param param = { .sched_priority = RTPM_PRIO_TPD }; #ifdef TPD_PROXIMITY int err; hwm_sensor_data sensor_data; u8 proximity_status; u8 state; #endif sched_setscheduler(current, SCHED_RR, ¶m); do { set_current_state(TASK_INTERRUPTIBLE); wait_event_interruptible(waiter,tpd_flag!=0); tpd_flag = 0; set_current_state(TASK_RUNNING); #ifdef TPD_PROXIMITY if (tpd_proximity_flag == 1) { i2c_smbus_read_i2c_block_data(i2c_client, 0xB0, 1, &state); TPD_PROXIMITY_DEBUG("proxi_5206 0xB0 state value is 1131 0x%02X\n", state); if(!(state&0x01)) { tpd_enable_ps(1); } i2c_smbus_read_i2c_block_data(i2c_client, 0x01, 1, &proximity_status); TPD_PROXIMITY_DEBUG("proxi_5206 0x01 value is 1139 0x%02X\n", proximity_status); if (proximity_status == 0xC0) { tpd_proximity_detect = 0; } else if(proximity_status == 0xE0) { tpd_proximity_detect = 1; } TPD_PROXIMITY_DEBUG("tpd_proximity_detect 1149 = %d\n", tpd_proximity_detect); if ((err = tpd_read_ps())) { TPD_PROXIMITY_DMESG("proxi_5206 read ps data 1156: %d\n", err); } sensor_data.values[0] = tpd_get_ps_value(); sensor_data.value_divide = 1; sensor_data.status = SENSOR_STATUS_ACCURACY_MEDIUM; if ((err = hwmsen_get_interrupt_data(ID_PROXIMITY, &sensor_data))) { TPD_PROXIMITY_DMESG(" proxi_5206 call hwmsen_get_interrupt_data failed= %d\n", err); } } #endif if (tpd_touchinfo(&cinfo, &pinfo, &point_num)) { //TPD_DEBUG("point_num = %d\n",point_num); TPD_DEBUG_SET_TIME; if(point_num >0) { for(i =0; idev); } else { tpd_up(cinfo.x[0], cinfo.y[0]); input_sync(tpd->dev); } } mt_eint_unmask(CUST_EINT_TOUCH_PANEL_NUM); }while(!kthread_should_stop()); return 0; } static int tpd_detect (struct i2c_client *client, struct i2c_board_info *info) { strcpy(info->type, TPD_DEVICE); return 0; } static void tpd_eint_interrupt_handler(void) { //TPD_DEBUG("TPD interrupt has been triggered\n"); TPD_DEBUG_PRINT_INT; tpd_flag = 1; wake_up_interruptible(&waiter); } static int tpd_probe(struct i2c_client *client, const struct i2c_device_id *id) { int retval = TPD_OK; char data; int reset_count = 0; client->timing = 300; i2c_client = client; reset_proc: //power on, need confirm with SA mt_set_gpio_mode(GPIO_CTP_RST_PIN, GPIO_CTP_RST_PIN_M_GPIO); mt_set_gpio_dir(GPIO_CTP_RST_PIN, GPIO_DIR_OUT); mt_set_gpio_out(GPIO_CTP_RST_PIN, GPIO_OUT_ZERO); msleep(5); TPD_DMESG(" fts ic reset\n"); #ifdef TPD_POWER_SOURCE_CUSTOM hwPowerOn(TPD_POWER_SOURCE_CUSTOM, VOL_2800, "TP"); #else hwPowerOn(MT65XX_POWER_LDO_VGP2, VOL_2800, "TP"); #endif #ifdef TPD_POWER_SOURCE_1800 hwPowerOn(TPD_POWER_SOURCE_1800, VOL_1800, "TP"); #endif mt_set_gpio_mode(GPIO_CTP_RST_PIN, GPIO_CTP_RST_PIN_M_GPIO); mt_set_gpio_dir(GPIO_CTP_RST_PIN, GPIO_DIR_OUT); mt_set_gpio_out(GPIO_CTP_RST_PIN, GPIO_OUT_ONE); msleep(200); if((i2c_smbus_read_i2c_block_data(i2c_client, 0x00, 1, &data))< 0) { TPD_DMESG("I2C transfer error, line: %d\n", __LINE__); #ifdef TPD_RESET_ISSUE_WORKAROUND if ( reset_count < TPD_MAX_RESET_COUNT ) { reset_count++; goto reset_proc; } #endif return -1; } mt_set_gpio_mode(GPIO_CTP_EINT_PIN, GPIO_CTP_EINT_PIN_M_EINT); mt_set_gpio_dir(GPIO_CTP_EINT_PIN, GPIO_DIR_IN); mt_set_gpio_pull_enable(GPIO_CTP_EINT_PIN, GPIO_PULL_ENABLE); mt_set_gpio_pull_select(GPIO_CTP_EINT_PIN, GPIO_PULL_UP); mt_eint_registration(CUST_EINT_TOUCH_PANEL_NUM, CUST_EINT_TOUCH_PANEL_TYPE, tpd_eint_interrupt_handler, 0); mt_eint_unmask(CUST_EINT_TOUCH_PANEL_NUM); #ifdef TPD_AUTO_UPGRADE #ifndef TPD_SYSFS_DEBUG I2CDMABuf_va = (u8 *)dma_alloc_coherent(NULL, FTS_DMA_BUF_SIZE, &I2CDMABuf_pa, GFP_KERNEL); if(!I2CDMABuf_va) { TPD_DMESG("%s Allocate DMA I2C Buffer failed!\n",__func__); return -EIO; } #endif #endif #ifdef VELOCITY_CUSTOM tpd_v_magnify_x = TPD_VELOCITY_CUSTOM_X; tpd_v_magnify_y = TPD_VELOCITY_CUSTOM_Y; #endif tpd_load_status = 1; #ifdef FTS_APK_DEBUG ft5x0x_create_apk_debug_channel(client); #endif #ifdef TPD_SYSFS_DEBUG fts_create_sysfs(i2c_client); #endif #ifdef FTS_CTL_IIC if (ft_rw_iic_drv_init(i2c_client) < 0) TPD_DMESG(TPD_DEVICE, "%s:[FTS] create fts control iic driver failed\n",__func__); #endif #ifdef TPD_AUTO_UPGRADE thread = kthread_run(tpd_ft_update_fw, 0, TPD_DEVICE); if (IS_ERR(thread)) { retval = PTR_ERR(thread); TPD_DMESG(TPD_DEVICE " failed to create kernel thread for update fw: %d\n", retval); } #endif thread = kthread_run(touch_event_handler, 0, TPD_DEVICE); if (IS_ERR(thread)) { retval = PTR_ERR(thread); TPD_DMESG(TPD_DEVICE " failed to create kernel thread: %d\n", retval); } TPD_DMESG("FTS Touch Panel Device Probe %s\n", (retval < TPD_OK) ? "FAIL" : "PASS"); #ifdef TPD_PROXIMITY struct hwmsen_object obj_ps; obj_ps.polling = 0;//interrupt mode obj_ps.sensor_operate = tpd_ps_operate; if((err = hwmsen_attach(ID_PROXIMITY, &obj_ps))) { APS_ERR("proxi_fts attach fail = %d\n", err); } else { APS_ERR("proxi_fts attach ok = %d\n", err); } #endif return 0; } static int tpd_remove(struct i2c_client *client) { #ifdef FTS_APK_DEBUG ft5x0x_release_apk_debug_channel(); #endif #ifdef TPD_SYSFS_DEBUG fts_release_sysfs(client); #endif #ifdef FTS_CTL_IIC ft_rw_iic_drv_exit(); #endif TPD_DEBUG("TPD removed\n"); return 0; } static int tpd_local_init(void) { TPD_DMESG("FTS I2C Touchscreen Driver (Built %s @ %s)\n", __DATE__, __TIME__); if(i2c_add_driver(&tpd_i2c_driver)!=0) { TPD_DMESG("FTS unable to add i2c driver.\n"); return -1; } if(tpd_load_status == 0) { TPD_DMESG("FTS add error touch panel driver.\n"); i2c_del_driver(&tpd_i2c_driver); return -1; } #ifdef TPD_HAVE_BUTTON tpd_button_setting(TPD_KEY_COUNT, tpd_keys_local, tpd_keys_dim_local);// initialize tpd button data #endif #if (defined(TPD_WARP_START) && defined(TPD_WARP_END)) TPD_DO_WARP = 1; memcpy(tpd_wb_start, tpd_wb_start_local, TPD_WARP_CNT*4); memcpy(tpd_wb_end, tpd_wb_start_local, TPD_WARP_CNT*4); #endif #if (defined(TPD_HAVE_CALIBRATION) && !defined(TPD_CUSTOM_CALIBRATION)) memcpy(tpd_calmat, tpd_def_calmat_local, 8*4); memcpy(tpd_def_calmat, tpd_def_calmat_local, 8*4); #endif TPD_DMESG("end %s, %d\n", __func__, __LINE__); tpd_type_cap = 1; return 0; } static void tpd_resume( struct early_suspend *h ) { #ifdef TPD_PROXIMITY if (tpd_proximity_flag == 1) { if(tpd_proximity_flag_one == 1) { tpd_proximity_flag_one = 0; TPD_DMESG(TPD_DEVICE " tpd_proximity_flag_one \n"); return; } } #endif TPD_DMESG("TPD wake up\n"); #ifdef TPD_CLOSE_POWER_IN_SLEEP #ifdef TPD_POWER_SOURCE_CUSTOM hwPowerOn(TPD_POWER_SOURCE_CUSTOM, VOL_2800, "TP"); #else hwPowerOn(MT65XX_POWER_LDO_VGP2, VOL_2800, "TP"); #endif #ifdef TPD_POWER_SOURCE_1800 hwPowerOn(TPD_POWER_SOURCE_1800, VOL_1800, "TP"); #endif #endif mt_set_gpio_mode(GPIO_CTP_RST_PIN, GPIO_CTP_RST_PIN_M_GPIO); mt_set_gpio_dir(GPIO_CTP_RST_PIN, GPIO_DIR_OUT); mt_set_gpio_out(GPIO_CTP_RST_PIN, GPIO_OUT_ONE); msleep(30); tpd_halt = 0; mt_eint_unmask(CUST_EINT_TOUCH_PANEL_NUM); tpd_up(0,0); input_sync(tpd->dev); TPD_DMESG("TPD wake up done\n"); } static void tpd_suspend( struct early_suspend *h ) { #ifndef TPD_CLOSE_POWER_IN_SLEEP static char data = 0x3; #endif #ifdef TPD_PROXIMITY if (tpd_proximity_flag == 1) { tpd_proximity_flag_one = 1; return; } #endif TPD_DMESG("TPD enter sleep\n"); mutex_lock(&i2c_access); tpd_halt = 1; mt_eint_mask(CUST_EINT_TOUCH_PANEL_NUM); mutex_unlock(&i2c_access); mt_set_gpio_mode(GPIO_CTP_RST_PIN, GPIO_CTP_RST_PIN_M_GPIO); mt_set_gpio_dir(GPIO_CTP_RST_PIN, GPIO_DIR_OUT); mt_set_gpio_out(GPIO_CTP_RST_PIN, GPIO_OUT_ZERO); msleep(1); #ifdef TPD_CLOSE_POWER_IN_SLEEP #ifdef TPD_POWER_SOURCE_CUSTOM hwPowerDown(TPD_POWER_SOURCE_CUSTOM, "TP"); #else hwPowerDown(MT65XX_POWER_LDO_VGP2, "TP"); #endif #ifdef TPD_POWER_SOURCE_1800 hwPowerDown(TPD_POWER_SOURCE_1800, "TP"); #endif #else i2c_smbus_write_i2c_block_data(i2c_client, 0xA5, 1, &data); //TP enter sleep mode #endif TPD_DMESG("TPD enter sleep done\n"); } static struct tpd_driver_t tpd_device_driver = { .tpd_device_name = "mtk-focal", .tpd_local_init = tpd_local_init, .suspend = tpd_suspend, .resume = tpd_resume, #ifdef TPD_HAVE_BUTTON .tpd_have_button = 1, #else .tpd_have_button = 0, #endif }; /* called when loaded into kernel */ static int __init tpd_driver_init(void) { printk("MediaTek FTS touch panel driver init\n"); i2c_register_board_info(TPD_I2C_NUMBER, &ft5206_i2c_tpd, 1); if(tpd_driver_add(&tpd_device_driver) < 0) TPD_DMESG("add FTS driver failed\n"); return 0; } /* should never be called */ static void __exit tpd_driver_exit(void) { TPD_DMESG("MediaTek FTS touch panel driver exit\n"); //input_unregister_device(tpd->dev); tpd_driver_remove(&tpd_device_driver); } module_init(tpd_driver_init); module_exit(tpd_driver_exit);