// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include "tpd.h" #include #include #include #include #include #include #include #include #ifdef CONFIG_MTK_MT6306_GPIO_SUPPORT #include #endif #ifdef CONFIG_COMPAT #include #endif #if defined(CONFIG_MTK_S3320) || defined(CONFIG_MTK_S3320_50) \ || defined(CONFIG_MTK_S3320_47) || defined(CONFIG_MTK_MIT200) \ || defined(CONFIG_TOUCHSCREEN_SYNAPTICS_S3528) \ || defined(CONFIG_MTK_S7020) \ || defined(CONFIG_TOUCHSCREEN_MTK_SYNAPTICS_3320_50) #include #endif /* CONFIG_MTK_S3320 */ /* for magnify velocity******************************************** */ #define TOUCH_IOC_MAGIC 'A' #define TPD_GET_VELOCITY_CUSTOM_X _IO(TOUCH_IOC_MAGIC, 0) #define TPD_GET_VELOCITY_CUSTOM_Y _IO(TOUCH_IOC_MAGIC, 1) #define TPD_GET_FILTER_PARA _IOWR(TOUCH_IOC_MAGIC, 2, struct tpd_filter_t) #ifdef CONFIG_COMPAT #define COMPAT_TPD_GET_FILTER_PARA _IOWR(TOUCH_IOC_MAGIC, \ 2, struct tpd_filter_t) #endif struct tpd_filter_t tpd_filter; struct tpd_dts_info tpd_dts_data; struct pinctrl *pinctrl1; struct pinctrl_state *pins_default; struct pinctrl_state *eint_as_int, *eint_output0, *eint_output1, *rst_output0, *rst_output1; const struct of_device_id touch_of_match[] = { { .compatible = "goodix,touch", }, {}, }; void tpd_get_dts_info(void) { struct device_node *node1 = NULL; int key_dim_local[16], i; node1 = of_find_matching_node(node1, touch_of_match); if (node1) { of_property_read_u32(node1, "tpd-max-touch-num", &tpd_dts_data.touch_max_num); of_property_read_u32(node1, "use-tpd-button", &tpd_dts_data.use_tpd_button); pr_debug("[tpd]use-tpd-button = %d\n", tpd_dts_data.use_tpd_button); if (of_property_read_u32_array(node1, "tpd-resolution", tpd_dts_data.tpd_resolution, ARRAY_SIZE(tpd_dts_data.tpd_resolution))) { pr_debug("[tpd] resulution is %d %d", tpd_dts_data.tpd_resolution[0], tpd_dts_data.tpd_resolution[1]); } if (tpd_dts_data.use_tpd_button) { of_property_read_u32(node1, "tpd-key-num", &tpd_dts_data.tpd_key_num); if (of_property_read_u32_array(node1, "tpd-key-local", tpd_dts_data.tpd_key_local, ARRAY_SIZE(tpd_dts_data.tpd_key_local))) pr_debug("tpd-key-local: %d %d %d %d", tpd_dts_data.tpd_key_local[0], tpd_dts_data.tpd_key_local[1], tpd_dts_data.tpd_key_local[2], tpd_dts_data.tpd_key_local[3]); if (of_property_read_u32_array(node1, "tpd-key-dim-local", key_dim_local, ARRAY_SIZE(key_dim_local))) { memcpy(tpd_dts_data.tpd_key_dim_local, key_dim_local, sizeof(key_dim_local)); for (i = 0; i < 4; i++) { pr_debug("[tpd]key[%d].key_x = %d\n", i, tpd_dts_data .tpd_key_dim_local[i] .key_x); pr_debug("[tpd]key[%d].key_y = %d\n", i, tpd_dts_data .tpd_key_dim_local[i] .key_y); pr_debug("[tpd]key[%d].key_W = %d\n", i, tpd_dts_data .tpd_key_dim_local[i] .key_width); pr_debug("[tpd]key[%d].key_H = %d\n", i, tpd_dts_data .tpd_key_dim_local[i] .key_height); } } } of_property_read_u32(node1, "tpd-filter-enable", &tpd_dts_data.touch_filter.enable); if (tpd_dts_data.touch_filter.enable) { of_property_read_u32(node1, "tpd-filter-pixel-density", &tpd_dts_data.touch_filter.pixel_density); if (of_property_read_u32_array(node1, "tpd-filter-custom-prameters", (u32 *)tpd_dts_data.touch_filter.W_W, ARRAY_SIZE(tpd_dts_data.touch_filter.W_W))) pr_debug("get tpd-filter-custom-parameters"); if (of_property_read_u32_array(node1, "tpd-filter-custom-speed", tpd_dts_data.touch_filter.VECLOCITY_THRESHOLD, ARRAY_SIZE(tpd_dts_data .touch_filter .VECLOCITY_THRESHOLD))) pr_debug("get tpd-filter-custom-speed"); } memcpy(&tpd_filter, &tpd_dts_data.touch_filter, sizeof(tpd_filter)); pr_debug("[tpd]tpd-filter-enable = %d, pixel_density = %d\n", tpd_filter.enable, tpd_filter.pixel_density); tpd_dts_data.tpd_use_ext_gpio = of_property_read_bool(node1, "tpd-use-ext-gpio"); of_property_read_u32(node1, "tpd-rst-ext-gpio-num", &tpd_dts_data.rst_ext_gpio_num); } else { TPD_DMESG("can't find touch compatible custom node\n"); } } static DEFINE_MUTEX(tpd_set_gpio_mutex); void tpd_gpio_as_int(int pin) { mutex_lock(&tpd_set_gpio_mutex); TPD_DEBUG("[tpd]tpd gpio as int\n"); if (pin == 1) pinctrl_select_state(pinctrl1, eint_as_int); mutex_unlock(&tpd_set_gpio_mutex); } void tpd_gpio_output(int pin, int level) { mutex_lock(&tpd_set_gpio_mutex); TPD_DEBUG("gpio_output pin = %d, level = %d\n", pin, level); if (pin == 1) { if (level) pinctrl_select_state(pinctrl1, eint_output1); else pinctrl_select_state(pinctrl1, eint_output0); } else { if (tpd_dts_data.tpd_use_ext_gpio) { #ifdef CONFIG_MTK_MT6306_GPIO_SUPPORT mt6306_set_gpio_dir( tpd_dts_data.rst_ext_gpio_num, 1); mt6306_set_gpio_out( tpd_dts_data.rst_ext_gpio_num, level); #endif } else { if (level) pinctrl_select_state(pinctrl1, rst_output1); else pinctrl_select_state(pinctrl1, rst_output0); } } mutex_unlock(&tpd_set_gpio_mutex); } int tpd_get_gpio_info(struct platform_device *pdev) { int ret; TPD_DEBUG("[tpd %d] mt_tpd_pinctrl+++++++++++++++++\n", pdev->id); pinctrl1 = devm_pinctrl_get(&pdev->dev); if (IS_ERR(pinctrl1)) { ret = PTR_ERR(pinctrl1); dev_info(&pdev->dev, "fwq Cannot find pinctrl1!\n"); return ret; } pins_default = pinctrl_lookup_state(pinctrl1, "default"); if (IS_ERR(pins_default)) { ret = PTR_ERR(pins_default); TPD_DMESG("Cannot find pinctrl default %d!\n", ret); } eint_as_int = pinctrl_lookup_state(pinctrl1, "state_eint_as_int"); if (IS_ERR(eint_as_int)) { ret = PTR_ERR(eint_as_int); TPD_DMESG("Cannot find pinctrl state_eint_as_int!\n"); return ret; } eint_output0 = pinctrl_lookup_state(pinctrl1, "state_eint_output0"); if (IS_ERR(eint_output0)) { ret = PTR_ERR(eint_output0); TPD_DMESG("Cannot find pinctrl state_eint_output0!\n"); return ret; } eint_output1 = pinctrl_lookup_state(pinctrl1, "state_eint_output1"); if (IS_ERR(eint_output1)) { ret = PTR_ERR(eint_output1); TPD_DMESG("Cannot find pinctrl state_eint_output1!\n"); return ret; } if (tpd_dts_data.tpd_use_ext_gpio == false) { rst_output0 = pinctrl_lookup_state(pinctrl1, "state_rst_output0"); if (IS_ERR(rst_output0)) { ret = PTR_ERR(rst_output0); TPD_DMESG("Cannot find pinctrl state_rst_output0!\n"); return ret; } rst_output1 = pinctrl_lookup_state(pinctrl1, "state_rst_output1"); if (IS_ERR(rst_output1)) { ret = PTR_ERR(rst_output1); TPD_DMESG("Cannot find pinctrl state_rst_output1!\n"); return ret; } } TPD_DEBUG("[tpd%d] mt_tpd_pinctrl----------\n", pdev->id); return 0; } static int tpd_misc_open(struct inode *inode, struct file *file) { return nonseekable_open(inode, file); } static int tpd_misc_release(struct inode *inode, struct file *file) { return 0; } #ifdef CONFIG_COMPAT static long tpd_compat_ioctl( struct file *file, unsigned int cmd, unsigned long arg) { long ret; void __user *arg32 = compat_ptr(arg); if (!file->f_op || !file->f_op->unlocked_ioctl) return -ENOTTY; switch (cmd) { case COMPAT_TPD_GET_FILTER_PARA: if (arg32 == NULL) { pr_info("invalid argument."); return -EINVAL; } ret = file->f_op->unlocked_ioctl(file, TPD_GET_FILTER_PARA, (unsigned long)arg32); if (ret) { pr_info("TPD_GET_FILTER_PARA unlocked_ioctl failed."); return ret; } break; default: pr_info("tpd: unknown IOCTL: 0x%08x\n", cmd); ret = -ENOIOCTLCMD; break; } return ret; } #endif static long tpd_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { /* char strbuf[256]; */ void __user *data; long err = 0; if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); if (err) { pr_info("tpd: access error: %08X, (%2d, %2d)\n", cmd, _IOC_DIR(cmd), _IOC_SIZE(cmd)); return -EFAULT; } switch (cmd) { case TPD_GET_VELOCITY_CUSTOM_X: data = (void __user *)arg; if (data == NULL) { err = -EINVAL; break; } if (copy_to_user(data, &tpd_v_magnify_x, sizeof(tpd_v_magnify_x))) { err = -EFAULT; break; } break; case TPD_GET_VELOCITY_CUSTOM_Y: data = (void __user *)arg; if (data == NULL) { err = -EINVAL; break; } if (copy_to_user(data, &tpd_v_magnify_y, sizeof(tpd_v_magnify_y))) { err = -EFAULT; break; } break; case TPD_GET_FILTER_PARA: data = (void __user *) arg; if (data == NULL) { err = -EINVAL; TPD_DMESG("GET_FILTER_PARA: data is null\n"); break; } if (copy_to_user(data, &tpd_filter, sizeof(struct tpd_filter_t))) { TPD_DMESG("GET_FILTER_PARA: copy data error\n"); err = -EFAULT; break; } break; default: pr_info("tpd: unknown IOCTL: 0x%08x\n", cmd); err = -ENOIOCTLCMD; break; } return err; } static struct work_struct touch_resume_work; static struct workqueue_struct *touch_resume_workqueue; static const struct file_operations tpd_fops = { /* .owner = THIS_MODULE, */ .open = tpd_misc_open, .release = tpd_misc_release, .unlocked_ioctl = tpd_unlocked_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = tpd_compat_ioctl, #endif }; /*---------------------------------------------------------------------------*/ static struct miscdevice tpd_misc_device = { .minor = MISC_DYNAMIC_MINOR, .name = "touch", .fops = &tpd_fops, }; /* ********************************************** */ /* #endif */ /* function definitions */ static int __init tpd_device_init(void); static void __exit tpd_device_exit(void); static int tpd_probe(struct platform_device *pdev); static int tpd_remove(struct platform_device *pdev); static struct work_struct tpd_init_work; static struct workqueue_struct *tpd_init_workqueue; static int tpd_suspend_flag; int tpd_register_flag; /* global variable definitions */ struct tpd_device *tpd; static struct tpd_driver_t tpd_driver_list[TP_DRV_MAX_COUNT]; /* = {0}; */ struct platform_device tpd_device = { .name = TPD_DEVICE, .id = -1, }; const struct dev_pm_ops tpd_pm_ops = { .suspend = NULL, .resume = NULL, }; static struct platform_driver tpd_driver = { .remove = tpd_remove, .shutdown = NULL, .probe = tpd_probe, .driver = { .name = TPD_DEVICE, .pm = &tpd_pm_ops, .owner = THIS_MODULE, .of_match_table = touch_of_match, }, }; static struct tpd_driver_t *g_tpd_drv; /* hh: use fb_notifier */ static struct notifier_block tpd_fb_notifier; /* use fb_notifier */ static void touch_resume_workqueue_callback(struct work_struct *work) { TPD_DEBUG("GTP touch_resume_callback\n"); g_tpd_drv->resume(NULL); tpd_suspend_flag = 0; } static int tpd_fb_notifier_callback( struct notifier_block *self, unsigned long event, void *data) { struct fb_event *evdata = NULL; int blank; int err = 0; TPD_DEBUG("fb_notifier_callback\n"); evdata = data; /* If we aren't interested in this event, skip it immediately ... */ if (event != FB_EVENT_BLANK) return 0; blank = *(int *)evdata->data; TPD_DMESG("fb_notify(blank=%d)\n", blank); switch (blank) { case FB_BLANK_UNBLANK: TPD_DMESG("LCD ON Notify\n"); if (g_tpd_drv && tpd_suspend_flag) { err = queue_work(touch_resume_workqueue, &touch_resume_work); if (!err) { TPD_DMESG("start resume_workqueue failed\n"); return err; } } break; case FB_BLANK_POWERDOWN: TPD_DMESG("LCD OFF Notify\n"); if (g_tpd_drv && !tpd_suspend_flag) { err = cancel_work_sync(&touch_resume_work); if (!err) TPD_DMESG("cancel resume_workqueue failed\n"); g_tpd_drv->suspend(NULL); } tpd_suspend_flag = 1; break; default: break; } return 0; } /* Add driver: if find TPD_TYPE_CAPACITIVE driver successfully, loading it */ int tpd_driver_add(struct tpd_driver_t *tpd_drv) { int i; if (g_tpd_drv != NULL) { TPD_DMESG("touch driver exist\n"); return -1; } /* check parameter */ if (tpd_drv == NULL) return -1; tpd_drv->tpd_have_button = tpd_dts_data.use_tpd_button; /* R-touch */ if (strcmp(tpd_drv->tpd_device_name, "generic") == 0) { tpd_driver_list[0].tpd_device_name = tpd_drv->tpd_device_name; tpd_driver_list[0].tpd_local_init = tpd_drv->tpd_local_init; tpd_driver_list[0].suspend = tpd_drv->suspend; tpd_driver_list[0].resume = tpd_drv->resume; tpd_driver_list[0].tpd_have_button = tpd_drv->tpd_have_button; return 0; } for (i = 1; i < TP_DRV_MAX_COUNT; i++) { /* add tpd driver into list */ if (tpd_driver_list[i].tpd_device_name == NULL) { tpd_driver_list[i].tpd_device_name = tpd_drv->tpd_device_name; tpd_driver_list[i].tpd_local_init = tpd_drv->tpd_local_init; tpd_driver_list[i].suspend = tpd_drv->suspend; tpd_driver_list[i].resume = tpd_drv->resume; tpd_driver_list[i].tpd_have_button = tpd_drv->tpd_have_button; tpd_driver_list[i].attrs = tpd_drv->attrs; break; } if (strcmp(tpd_driver_list[i].tpd_device_name, tpd_drv->tpd_device_name) == 0) return 1; /* driver exist */ } return 0; } int tpd_driver_remove(struct tpd_driver_t *tpd_drv) { int i = 0; /* check parameter */ if (tpd_drv == NULL) return -1; for (i = 0; i < TP_DRV_MAX_COUNT; i++) { /* find it */ if (strcmp(tpd_driver_list[i].tpd_device_name, tpd_drv->tpd_device_name) == 0) { memset(&tpd_driver_list[i], 0, sizeof(struct tpd_driver_t)); break; } } return 0; } static void tpd_create_attributes(struct device *dev, struct tpd_attrs *attrs) { int num = attrs->num; for (; num > 0;) { if (device_create_file(dev, attrs->attr[--num])) pr_info("mtk_tpd: tpd create attributes file failed\n"); } } /* touch panel probe */ static int tpd_probe(struct platform_device *pdev) { int touch_type = 1; /* 0:R-touch, 1: Cap-touch */ int i = 0; #ifndef CONFIG_CUSTOM_LCM_X #ifdef CONFIG_LCM_WIDTH unsigned long tpd_res_x = 0, tpd_res_y = 0; int ret = 0; #endif #endif TPD_DMESG("enter %s, %d\n", __func__, __LINE__); if (misc_register(&tpd_misc_device)) pr_info("mtk_tpd: tpd_misc_device register failed\n"); tpd_get_gpio_info(pdev); tpd = kmalloc(sizeof(struct tpd_device), GFP_KERNEL); if (tpd == NULL) return -ENOMEM; memset(tpd, 0, sizeof(struct tpd_device)); /* allocate input device */ tpd->dev = input_allocate_device(); if (tpd->dev == NULL) { kfree(tpd); return -ENOMEM; } /* TPD_RES_X = simple_strtoul(LCM_WIDTH, NULL, 0); */ /* TPD_RES_Y = simple_strtoul(LCM_HEIGHT, NULL, 0); */ #ifdef CONFIG_MTK_LCM_PHYSICAL_ROTATION if (strncmp(CONFIG_MTK_LCM_PHYSICAL_ROTATION, "90", 2) == 0 || strncmp(CONFIG_MTK_LCM_PHYSICAL_ROTATION, "270", 3) == 0) { #ifdef CONFIG_MTK_FB /*Fix build errors,as some projects cannot support these apis while bring up*/ TPD_RES_Y = DISP_GetScreenWidth(); TPD_RES_X = DISP_GetScreenHeight(); #endif } else #endif { #ifdef CONFIG_CUSTOM_LCM_X #ifndef CONFIG_FPGA_EARLY_PORTING #if defined(CONFIG_MTK_FB) && defined(CONFIG_MTK_LCM) /*Fix build errors,as some projects cannot support these apis while bring up*/ TPD_RES_X = DISP_GetScreenWidth(); TPD_RES_Y = DISP_GetScreenHeight(); #else /*for some projects, we do not use mtk framebuffer*/ TPD_RES_X = tpd_dts_data.tpd_resolution[0]; TPD_RES_Y = tpd_dts_data.tpd_resolution[1]; #endif #endif #else #ifdef CONFIG_LCM_WIDTH ret = kstrtoul(CONFIG_LCM_WIDTH, 0, &tpd_res_x); if (ret < 0) { pr_info("Touch down get lcm_x failed"); return ret; } TPD_RES_X = tpd_res_x; ret = kstrtoul(CONFIG_LCM_HEIGHT, 0, &tpd_res_x); if (ret < 0) { pr_info("Touch down get lcm_y failed"); return ret; } TPD_RES_Y = tpd_res_y; #endif #endif } if (2560 == TPD_RES_X) TPD_RES_X = 2048; if (1600 == TPD_RES_Y) TPD_RES_Y = 1536; pr_debug("mtk_tpd: TPD_RES_X = %lu, TPD_RES_Y = %lu\n", TPD_RES_X, TPD_RES_Y); tpd_mode = TPD_MODE_NORMAL; tpd_mode_axis = 0; tpd_mode_min = TPD_RES_Y / 2; tpd_mode_max = TPD_RES_Y; tpd_mode_keypad_tolerance = TPD_RES_X * TPD_RES_X / 1600; /* struct input_dev dev initialization and registration */ tpd->dev->name = TPD_DEVICE; set_bit(EV_ABS, tpd->dev->evbit); set_bit(EV_KEY, tpd->dev->evbit); set_bit(ABS_X, tpd->dev->absbit); set_bit(ABS_Y, tpd->dev->absbit); set_bit(ABS_PRESSURE, tpd->dev->absbit); #if !defined(CONFIG_MTK_S3320) && !defined(CONFIG_MTK_S3320_47)\ && !defined(CONFIG_MTK_S3320_50) && !defined(CONFIG_MTK_MIT200) \ && !defined(CONFIG_TOUCHSCREEN_SYNAPTICS_S3528) \ && !defined(CONFIG_MTK_S7020) \ && !defined(CONFIG_TOUCHSCREEN_MTK_SYNAPTICS_3320_50) set_bit(BTN_TOUCH, tpd->dev->keybit); #endif /* CONFIG_MTK_S3320 */ set_bit(INPUT_PROP_DIRECT, tpd->dev->propbit); /* save dev for regulator_get() before tpd_local_init() */ tpd->tpd_dev = &pdev->dev; for (i = 1; i < TP_DRV_MAX_COUNT; i++) { /* add tpd driver into list */ if (tpd_driver_list[i].tpd_device_name != NULL) { tpd_driver_list[i].tpd_local_init(); /* msleep(1); */ if (tpd_load_status == 1) { TPD_DMESG("probe, tpd_driver_name=%s\n", tpd_driver_list[i].tpd_device_name); g_tpd_drv = &tpd_driver_list[i]; break; } } } if (g_tpd_drv == NULL) { if (tpd_driver_list[0].tpd_device_name != NULL) { g_tpd_drv = &tpd_driver_list[0]; /* touch_type:0: r-touch, 1: C-touch */ touch_type = 0; g_tpd_drv->tpd_local_init(); TPD_DMESG("Generic touch panel driver\n"); } else { TPD_DMESG("no touch driver is loaded!!\n"); return 0; } } touch_resume_workqueue = create_singlethread_workqueue("touch_resume"); INIT_WORK(&touch_resume_work, touch_resume_workqueue_callback); /* use fb_notifier */ tpd_fb_notifier.notifier_call = tpd_fb_notifier_callback; if (fb_register_client(&tpd_fb_notifier)) TPD_DMESG("register fb_notifier fail!\n"); /* TPD_TYPE_CAPACITIVE handle */ if (touch_type == 1) { set_bit(ABS_MT_TRACKING_ID, tpd->dev->absbit); set_bit(ABS_MT_TOUCH_MAJOR, tpd->dev->absbit); set_bit(ABS_MT_TOUCH_MINOR, tpd->dev->absbit); set_bit(ABS_MT_POSITION_X, tpd->dev->absbit); set_bit(ABS_MT_POSITION_Y, tpd->dev->absbit); input_set_abs_params(tpd->dev, ABS_MT_POSITION_X, 0, TPD_RES_X, 0, 0); input_set_abs_params(tpd->dev, ABS_MT_POSITION_Y, 0, TPD_RES_Y, 0, 0); #if defined(CONFIG_MTK_S3320) || defined(CONFIG_MTK_S3320_47) \ || defined(CONFIG_MTK_S3320_50) || defined(CONFIG_MTK_MIT200) \ || defined(CONFIG_TOUCHSCREEN_SYNAPTICS_S3528) \ || defined(CONFIG_MTK_S7020) \ || defined(CONFIG_TOUCHSCREEN_MTK_SYNAPTICS_3320_50) input_set_abs_params(tpd->dev, ABS_MT_PRESSURE, 0, 255, 0, 0); input_set_abs_params(tpd->dev, ABS_MT_WIDTH_MAJOR, 0, 15, 0, 0); input_set_abs_params(tpd->dev, ABS_MT_WIDTH_MINOR, 0, 15, 0, 0); input_mt_init_slots(tpd->dev, 10, 0); #else input_set_abs_params(tpd->dev, ABS_MT_TOUCH_MAJOR, 0, 100, 0, 0); input_set_abs_params(tpd->dev, ABS_MT_TOUCH_MINOR, 0, 100, 0, 0); #endif /* CONFIG_MTK_S3320 */ TPD_DMESG("Cap touch panel driver\n"); } input_set_abs_params(tpd->dev, ABS_X, 0, TPD_RES_X, 0, 0); input_set_abs_params(tpd->dev, ABS_Y, 0, TPD_RES_Y, 0, 0); input_abs_set_res(tpd->dev, ABS_X, TPD_RES_X); input_abs_set_res(tpd->dev, ABS_Y, TPD_RES_Y); input_set_abs_params(tpd->dev, ABS_PRESSURE, 0, 255, 0, 0); input_set_abs_params(tpd->dev, ABS_MT_TRACKING_ID, 0, 10, 0, 0); if (input_register_device(tpd->dev)) TPD_DMESG("input_register_device failed.(tpd)\n"); else tpd_register_flag = 1; if (g_tpd_drv->tpd_have_button) tpd_button_init(); if (g_tpd_drv->attrs.num) tpd_create_attributes(&pdev->dev, &g_tpd_drv->attrs); return 0; } static int tpd_remove(struct platform_device *pdev) { input_unregister_device(tpd->dev); return 0; } /* called when loaded into kernel */ static void tpd_init_work_callback(struct work_struct *work) { TPD_DEBUG("MediaTek touch panel driver init\n"); if (platform_driver_register(&tpd_driver) != 0) TPD_DMESG("unable to register touch panel driver.\n"); } static int __init tpd_device_init(void) { int res = 0; tpd_init_workqueue = create_singlethread_workqueue("mtk-tpd"); INIT_WORK(&tpd_init_work, tpd_init_work_callback); res = queue_work(tpd_init_workqueue, &tpd_init_work); if (!res) pr_info("tpd : touch device init failed res:%d\n", res); return 0; } /* should never be called */ static void __exit tpd_device_exit(void) { TPD_DMESG("MediaTek touch panel driver exit\n"); /* input_unregister_device(tpd->dev); */ platform_driver_unregister(&tpd_driver); } late_initcall(tpd_device_init); module_exit(tpd_device_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("MediaTek touch panel driver"); MODULE_AUTHOR("Kirby Wu");