Android TP驱动分析


一、TP的硬件接口

引脚 名称及作用
VDD TP供电
RESET 复位引脚
EINT 中断引脚
SCL、SDA I2C接口

​ TP的工作方式比较简单:

  • 上电后通过RESET脚控制TP芯片复位;
  • 通过I2C接口给TP设置参数或读取TP数据;
  • TP有触摸操作时通过EINT脚通知主控;

二、代码路径

描述 路径 文件
系统设置 device\top\top6737t_36_a_m0
kernel-3.18\arch\arm\configs
ProjectConfig.mk
user版本和userdebug版本对应:
top6737t_36_a_m0_defconfig
debug版本对应:
top6737t_36_a_m0_debug_defconfig
接口设置 kernel-3.18\arch\arm\boot\dts top6737t_36_a_m0.dts
cust_i2c.dtsi
Kernel代码 kernel-3.18\drivers\input\touchscreen\mediatek
mtk_tpd.c

三、TP代码分析

1、硬件参数设置

​ 在dts文件中,对照硬件接口,修改对应GPIO设置,还有TP的虚拟键坐标、分辨率等参数信息:

// top6737t_36_a_m0.dts
......
&accdet {
	pinctrl-names = "default","state_eint_as_int";
	pinctrl-0 = <&ACCDET_pins_default>;
	pinctrl-1 = <&ACCDET_pins_eint_int>;
	status = "okay";
};
&pio {
       ACCDET_pins_default: eint6default {
       };
       ACCDET_pins_eint_int: eint@6 {
	 pins_cmd_dat {
	   pins = <PINMUX_GPIO6__FUNC_GPIO6>;
	   slew-rate = <0>;
	   bias-disable;
	 };
       };
};
&touch {
	tpd-resolution = <240 240>;
	use-tpd-button = <0>;
	tpd-key-num = <0>;
	tpd-key-local= <139 172 158 0>;
	tpd-key-dim-local = <90 883 100 40 230 883 100 40 370 883 100 40 0 0 0 0>;
	tpd-max-touch-num = <1>;
	tpd-filter-enable = <1>;
	tpd-filter-pixel-density = <124>;
	tpd-filter-custom-prameters = <0 0 0 0 0 0 0 0 0 0 0 0>;
	tpd-filter-custom-speed = <0 0 0>;
	pinctrl-names = "default", "state_eint_as_int", "state_eint_output0", "state_eint_output1",
		"state_rst_output0", "state_rst_output1";
	pinctrl-0 = <&CTP_pins_default>;
	pinctrl-1 = <&CTP_pins_eint_as_int>;
	pinctrl-2 = <&CTP_pins_eint_output0>;
	pinctrl-3 = <&CTP_pins_eint_output1>;
	pinctrl-4 = <&CTP_pins_rst_output0>;
	pinctrl-5 = <&CTP_pins_rst_output1>;
	status = "okay";
};
......

还有对应的I2C设置:

/* cust_i2c.dtsi */
&i2c1 {
	cap_touch@5d {
		compatible = "mediatek,cap_touch";
		reg = <0x5d>;
	};
};

2、TP设备驱动

​ 文件mtk_tpd.c是TP设备驱动的入口,可以通过分析本文件简单捋一下代码结构。

​ 首先,是通过platform_driver_register注册一个设备驱动:

//mtk_tpd.c
static int __init tpd_device_init(void)
{
    
    
	TPD_DEBUG("MediaTek touch panel driver init\n");
	if (platform_driver_register(&tpd_driver) != 0) {
    
    
		TPD_DMESG("unable to register touch panel driver.\n");
		return -1;
	}
	return 0;
}
//设备驱动结构体
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,
	},
};

我们看一下这个设备驱动的probe函数:

//mtk_tpd.c
static int tpd_probe(struct platform_device *pdev)
{
    
    
	......
	//首先,注册了一个杂项设备
	if (misc_register(&tpd_misc_device))
	{
    
    
		pr_err("mtk_tpd: tpd_misc_device register failed\n");
	}
	//读取dts中相关gpio等设置
	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));

	//在内存中为输入设备结构体分配一个空间,并对结构体成员进行初始化
	tpd->dev = input_allocate_device();
	if (tpd->dev == NULL) {
    
    
		kfree(tpd);
		return -ENOMEM;
	}
	
#ifdef CONFIG_MTK_LCM_PHYSICAL_ROTATION
	//看下是否存在横屏竖用,TP也要跟着LCD进行横竖转换
	if (0 == strncmp(CONFIG_MTK_LCM_PHYSICAL_ROTATION, "90", 2) || 0 == strncmp(CONFIG_MTK_LCM_PHYSICAL_ROTATION, "270", 3)) 
	{
    
    
	#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_MTK_FPGA
#ifdef CONFIG_MTK_FB	/*Fix build errors,as some projects  cannot support these apis while bring up*/
		TPD_RES_X = DISP_GetScreenWidth();
		TPD_RES_Y = DISP_GetScreenHeight();
#endif
#endif
#else
#ifdef CONFIG_LCM_WIDTH
		ret = kstrtoul(CONFIG_LCM_WIDTH, 0, &tpd_res_x);
		if (ret < 0) {
    
    
			pr_err("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_err("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_info("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;
	//初始化输入设备结构
	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)
	set_bit(BTN_TOUCH, tpd->dev->keybit);
#endif /* CONFIG_MTK_S3320 */
	set_bit(INPUT_PROP_DIRECT, tpd->dev->propbit);

	//将注册到的platform_device保存到创建的输入设备中;
	tpd->tpd_dev = &pdev->dev;
	//遍历tpd_driver_list,找到当前在用TP;
	for (i = 1; i < TP_DRV_MAX_COUNT; i++) 
	{
    
    
		if (tpd_driver_list[i].tpd_device_name != NULL) 
		{
    
    
			tpd_driver_list[i].tpd_local_init();
			if (tpd_load_status == 1) 
			{
    
    
				TPD_DMESG("[mtk-tpd]tpd_probe, tpd_driver_name=%s\n",tpd_driver_list[i].tpd_device_name);
				g_tpd_drv = &tpd_driver_list[i];
				break;
			}
		}
	}
	//如果tpd_driver_list中所有TP都初始化失败,强制使用index=0的TP;
	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("[mtk-tpd]Generic touch panel driver\n");
		} else {
    
    
			TPD_DMESG("[mtk-tpd]cap touch and Generic touch both are not loaded!!\n");
			return 0;
		}
	}
	//为TP工作创建一个工作队列;
	touch_resume_workqueue = create_singlethread_workqueue("touch_resume");
	INIT_WORK(&touch_resume_work, touch_resume_workqueue_callback);
	//设置背光通知的回调函数,以便在亮屏时唤醒TP,或者息屏后关闭TP;
	tpd_fb_notifier.notifier_call = tpd_fb_notifier_callback;
	if (fb_register_client(&tpd_fb_notifier))
		TPD_DMESG("register fb_notifier fail!\n");
		
	//设置TP相关参数;
	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)
		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;
	
	//如果TP支持虚拟按键,还要进行虚拟按键的初始化;
	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;
}

执行完probe函数,TP的设备节点就创建好了。

​ 在probe中,查找当前使用TP是通过遍历一个支持列表—tpd_driver_list实现的,这个列表的元素是TP操作的具体函数组成的结构体,定义在:

//tpd.h
struct tpd_driver_t {
    
    
	char *tpd_device_name; //TP名字
	int (*tpd_local_init)(void); //TP初始化
	void (*suspend)(struct device *h);//TP休眠
	void (*resume)(struct device *h);//TP唤醒
	int tpd_have_button;//是否有虚拟按键
	struct tpd_attrs attrs;//创建节点文件
};

不同的TP都是要实现这个结构体中的内容,完成TP应有的工作。

​ tpd_driver_list这个列表时通过调用函数tpd_driver_add来添加元素的:

//mtk_tpd.c
int tpd_driver_add(struct tpd_driver_t *tpd_drv)
{
    
    
	......
	tpd_drv->tpd_have_button = tpd_dts_data.use_tpd_button;
	//如果是电阻屏,强制放到列表index=0位置;
	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++) {
    
    
		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;
		}
        //若列表中已经有了这个TP,不再重复添加;
		if (strcmp(tpd_driver_list[i].tpd_device_name, tpd_drv->tpd_device_name) == 0)
			return 1;	
	}
	return 0;
}

​ 另外,probe函数中还注册了一个背光通知回调函数,用于控制TP随背光变化调整工作方式,回调函数如下:

//mtk_tpd.c
static int tpd_fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data)
{
    
    
	......
	switch (blank) {
    
    
    //背光被点亮通知
	case FB_BLANK_UNBLANK:
		TPD_DMESG("LCD ON Notify\n");
		if (g_tpd_drv && tpd_suspend_flag) {
    
    
            //通过工作队列方式,调用TP的唤醒函数
			err = queue_work(touch_resume_workqueue, &touch_resume_work);
			if (!err) {
    
    
				TPD_DMESG("start touch_resume_workqueue failed\n");
				return err;
			}
		}
		break;
    //背光被熄灭通知        
	case FB_BLANK_POWERDOWN:
		TPD_DMESG("LCD OFF Notify\n");
		if (g_tpd_drv)
        {
    
    
            //先取消工作队列,避免逻辑混乱
			err = cancel_work_sync(&touch_resume_work);
			if (!err)
				TPD_DMESG("cancel touch_resume_workqueue err = %d\n", err);  
            //直接调用TP的休眠函数
			g_tpd_drv->suspend(NULL);
		tpd_suspend_flag = 1;
        }
		break;
	default:
		break;
	}
	return 0;
}

​ TP的设备驱动基本结构就是这些。

3、TP模组驱动

​ TP设备驱动搭好了TP驱动的架构,不同的TP模组按照这个架构,主要是实现结构体 tpd_driver_t 中内容,然后通过函数 tpd_driver_add 加入到驱动列表中。下面就以一个实例—ST16XX,看一下TP模组的驱动代码。

​ 首先,通过 module_init 和 module_exit 实现模组的初始化和退出:

//ST16XX_driver.c
static int __init tpd_driver_init(void)
{
    
    
    //获取模组在dts中的参数信息,包括分辨率、虚拟按键等
    tpd_get_dts_info();
    TPD_DMESG("Sitronix st16xx touch panel driver init\n");
    //添加到 列表tpd_driver_t
    if(tpd_driver_add(&tpd_device_driver) < 0)
    TPD_DMESG("add generic driver failed\n");
    return 0;
}

static void __exit tpd_driver_exit(void)
{
    
    
    TPD_DMESG("MediaTek ST16XX touch panel driver exit\n");
    //从列表tpd_driver_t 移除
    tpd_driver_remove(&tpd_device_driver);
}


module_init(tpd_driver_init);
module_exit(tpd_driver_exit);

结构体 tpd_driver_t 的具体实现为:

//ST16XX_driver.c
static struct tpd_driver_t tpd_device_driver = {
    
    
    .tpd_device_name =CTP_NAME,  // TPD_DEVICE,
    .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 
};

其中,tpd_local_init 源码如下:

//ST16XX_driver.c
static int tpd_local_init(void) 
{
    
    
	//添加一个I2C驱动,用于和TP通信
    if(i2c_add_driver(&tpd_i2c_driver)!=0) {
    
    
        TPD_DMESG("unable to add i2c driver.\n");
        return -1;
    }
	//若添加I2C驱动时,并未成功初始化,则删除I2C驱动
    if (tpd_load_status == 0) {
    
    
        TPD_DMESG("add error touch panel driver.");
        i2c_del_driver(&tpd_i2c_driver);
        return -1;
    }
	......
	//电容屏
    tpd_type_cap = 1;
    return 0;
}

​ 可以看出,添加I2C驱动的操作,是 tpd_local_init 的重点,tpd_i2c_driver 实现如下:

//ST16XX_driver.c
static struct i2c_client *i2c_client = NULL;
static const struct i2c_device_id tpd_i2c_id[] ={
    
    {
    
    CTP_NAME,0},{
    
    }}; 
static const struct of_device_id tpd_of_match[] = {
    
    
    {
    
    .compatible = "mediatek,cap_touch"},
    {
    
    },
};
 static struct i2c_driver tpd_i2c_driver = {
    
    
    .driver = {
    
    
    	.name = CTP_NAME,
	#ifdef CONFIG_OF	 	
    	.of_match_table = tpd_of_match,
	#endif	
    	.owner = THIS_MODULE,
    },
    .probe = tpd_i2c_probe,   
    .remove = tpd_i2c_remove,
    .detect = tpd_i2c_detect,                           
    .driver.name = CTP_NAME,  
    .id_table = tpd_i2c_id,                                                  
}; 

还是要读一下probe函数:

//ST16XX_driver.c
static int tpd_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    
    
    ......
	//赋值I2C地址
    client->addr = 0x55; 
    i2c_client = client;
    TPD_DMESG("Sitronix st16xx touch panel i2c probe:%s\n", id->name);

    //给TP上电
    tpd->reg = regulator_get(tpd->tpd_dev, "vtouch");
    if (IS_ERR(tpd->reg))
    {
    
    
        TPD_DMESG("regulator_get() failed!\n");
    }
    err = regulator_set_voltage(tpd->reg, 2800000, 2800000);
    if (err)
    {
    
    
        TPD_DMESG("regulator_set_voltage() failed!\n");
    }
    err = regulator_enable(tpd->reg);
    if (err != 0)
    {
    
    
        TPD_DMESG("Failed to enable reg-vtouch: %d\n", err);
    }
	//控制RESET脚,使TP复位一下
    tpd_gpio_output(GTP_RST_PORT, 1); 
    msleep(10);
    tpd_gpio_output(GTP_RST_PORT, 0); 
    msleep(10);
    tpd_gpio_output(GTP_RST_PORT, 1); 
    msleep(300);
	//读取TP的ID
    retval = tpd_get_st16xx_id();
    if (retval < 0)
    {
    
    
        tpd_load_status = 0;
        TPD_DMESG("tpd_get_st16xx_id error, Maybe not ST16XX\n");
        return -1;
    }
	
#ifdef I2C_SUPPORT_RS_DMA
    //如果支持DMA的话,申请DMA内存;
    I2CDMABuf_va = (u8 *)dma_alloc_coherent(NULL, 4096, &I2CDMABuf_pa, GFP_KERNEL);
    if(!I2CDMABuf_va)
    {
    
    
        TPD_DMESG("st16xx Allocate Touch DMA I2C Buffer failed!\n");
        return -1;
    }
#endif
    //注册EINT脚中断
    tpd_irq_registration();
    msleep(100);
	//读取TP状态
    status =  st16xx_get_status(); 
    if(status != 6)
    {
    
    
        st16xx_print_version(); //读取TP固件版本
        tpd_halt = 1;
        // upgrade panel here
        tpd_halt = 0;
    }
  //TP 固件自动升级操作
#ifdef ST_UPGRADE_FIRMWARE
#ifdef ST_FIREWARE_FILE
    kthread_run(st_upgrade_fw, "Sitronix", "sitronix_update");
#else
    st_upgrade_fw();
#endif 
#endif 
	//创建一个线程,用于执行EINT中断下半部
    thread = kthread_run(touch_event_handler, 0, CTP_NAME);
    if (IS_ERR(thread)) {
    
     
        err = PTR_ERR(thread);
        TPD_DMESG(CTP_NAME " st16xx failed to create kernel thread: %d\n", err);
    }
	
    //初始化完成,TP加载完毕
    tpd_load_status = 1;  
    return 0;
}

​ 然后,我们看一下EINT中断相关的代码:

//ST16XX_driver.c
static int tpd_irq_registration(void)
{
    
    
    ......
	//查找指定节点,获取相关参数
    node = of_find_matching_node(node, touch_of_match);
    if (node) 
    {
    
    
        //防抖设置
        of_property_read_u32_array(node, "debounce", ints, ARRAY_SIZE(ints));
        gpio_set_debounce(ints[0], ints[1]);
	    //设置中断,包括中断号、中断回调、中断触发方式等;
        touch_irq = irq_of_parse_and_map(node, 0);
        ret = request_irq(touch_irq, tpd_eint_interrupt_handler, IRQF_TRIGGER_FALLING,
		"TOUCH_PANEL-eint", NULL); //IRQF_TRIGGER_FALLING  IRQF_TRIGGER_RISING
        if (ret > 0) {
    
    
            ret = -1;
            TPD_DEBUG("tpd request_irq IRQ LINE NOT AVAILABLE!.\n");
        }
    } 
    ......
    return ret;
}

看下中断回调函数:

//ST16XX_driver.c
static irqreturn_t tpd_eint_interrupt_handler(int irq, void *dev_id)
{
    
    
    tpd_flag = 1;
    wake_up_interruptible(&waiter);//唤醒等待队列
    return IRQ_HANDLED;
} 

可以看到,中断回调只是EINT中断的上半部,上半部只是唤醒了一个等待队列。probe函数中创建了一个线程,ENIT中断的下半部放在这个线程中执行:

//ST16XX_driver.c
static DECLARE_WAIT_QUEUE_HEAD(waiter); //声明并创建一个等待队列

static int touch_event_handler( void *unused )
{
    
    
    struct sched_param param = {
    
     .sched_priority = RTPM_PRIO_TPD }; 
    unsigned char buf[ST16XX_MAX_TOUCHES*4];
    int ret = 0,i=0;
    int x,y;
    u8 touchCount=0;
	//设置线程调度策略为:时间片轮转实时调度;
    sched_setscheduler(current, SCHED_RR, &param); 
    do
    {
    
    
        set_current_state(TASK_INTERRUPTIBLE);
        while (tpd_halt) {
    
    tpd_flag = 0; msleep(20);}
        //等待等待队列被唤醒;
        wait_event_interruptible(waiter, tpd_flag != 0);
        tpd_flag = 0;
        TPD_DEBUG_SET_TIME;
        set_current_state(TASK_RUNNING); 	
        touchCount=0;
        //通过I2C读取TP数据
        ret = tpd_i2c_read(i2c_client, buf, ST16XX_MAX_TOUCHES*4, 0x12);
	    //解析TP数据
        for(i=0;i<ST16XX_MAX_TOUCHES;i++)
        {
    
    
            if(buf[4*i] & 0x80)
            {
    
    
                x = (int)(buf[i*4] & 0x70) << 4 | buf[i * 4 + 1]; 
                y = (int)(buf[i*4] & 0x07) << 8 | buf[i * 4 + 2];
                touchCount++;
                tpd_down(0,0, x, y, i);
            }
        }
        if(touchCount == 0)
        {
    
    
            tpd_up(0,0, 0,0,0);
        }
		//上报TP数据
        input_sync(tpd->dev);
    } while (!kthread_should_stop()); 
	
    return 0;
}


看到这里,TP通过中断上报数据的流程就清晰了。

​ 最后,我们再看看TP的休眠和唤醒都做了什么:

//ST16XX_driver.c
//休眠函数
static void tpd_suspend(struct device *h)
{
    
    
    unsigned char buf[2];
    int status;
    int ret;

    TPD_DEBUG("ST16xx call suspend\n");    
	//通过I2C,控制TP进入休眠模式
    buf[0] = 0x02;
    buf[1] = 0x02;
    ret = tpd_i2c_write(i2c_client, buf, 2);
    msleep(100);
    //读取TP状态,看操作是否成功;
    status  = st16xx_get_status();
    if(status == 5)
        TPD_DEBUG("ST16xx go power down mode success\n");  
    else
        TPD_DEBUG("ST16xx go power down mode fail\n");  
   	//禁止EINT中断;
    disable_irq(touch_irq);
    tpd_halt = 1;
}
//唤醒函数
static void tpd_resume(struct device *h)
{
    
    
    TPD_DEBUG("ST16XX call resume\n");
    //控制RESET脚复位TP
    tpd_gpio_output(GTP_RST_PORT, 1); 
    msleep(10);
    tpd_gpio_output(GTP_RST_PORT, 0); 
    msleep(10);
    tpd_gpio_output(GTP_RST_PORT, 1); 
    msleep(300);
    //使能EINT中断 
    enable_irq(touch_irq);
    tpd_halt = 0;	
}

​ 至此,TP驱动的核心代码就很清晰了。

猜你喜欢

转载自blog.csdn.net/xiaocaohuyang/article/details/114120766