引脚 | 名称及作用 |
---|---|
VDD | TP供电 |
RESET | 复位引脚 |
EINT | 中断引脚 |
SCL、SDA | I2C接口 |
TP的工作方式比较简单:
描述 | 路径 | 文件 |
---|---|---|
系统设置 | 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 等 |
在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 = ;
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>;
};
};
文件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的设备驱动基本结构就是这些。
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, ¶m);
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驱动的核心代码就很清晰了。