MTK平台TP驱动主要包括两处文件:
1,kernel-3.18\drivers\input\touchscreen\mediatek\mtk_tpd.c
平台设备驱动,主要为了兼容多个不同的TP驱动IC,它会去遍历每一个编译并添加进 tpd_driver_list 中的IC驱动,直到其中一个初始化成功。
2,kernel-3.18\drivers\input\touchscreen\mediatek\XXX
XXX文件为具体的TP驱动文件,如:汇鼎,思立微,敦泰 等等,一般由IC厂提供,然后移植到各平台的代码中(移植方法后面会讲到),主要实现了对IC的上电和初始化,注册输入设备,获取并上报数据等。
首先在装载入口函数 tpd_device_init 中创建了一个工作队列,调用 tpd_init_work_callback 函数,用于注册平台总线。
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, //匹配设备树
},
};
/* 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_driver
TPD_DMESG("unable to register touch panel driver.\n");
}
int tpd_device_init(void)
{
int res = 0;
//创建并初始化一个工作队列,调用tpd_init_workqueue回调函数
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_err("tpd : touch device init failed res:%d\n", res);
return 0;
}
EXPORT_SYMBOL(tpd_device_init);
与设备树中的device匹配成功后,调用 tpd_probe 方法
static const struct file_operations tpd_fops = {
/* .owner = THIS_MODULE, */
.open = tpd_misc_open, //tp杂类设备open
.release = tpd_misc_release,//tp杂类设备注销
.unlocked_ioctl = tpd_unlocked_ioctl,//使用ioctl操作tpd杂类设备
#ifdef CONFIG_COMPAT
.compat_ioctl = tpd_compat_ioctl,//调用tpd_unlocked_ioctl中的TPD_GET_FILTER_PARA命令
#endif
};
static struct miscdevice tpd_misc_device = {
.minor = MISC_DYNAMIC_MINOR, //次设备号
.name = "touch", //设备名称
.fops = &tpd_fops, //杂类设备操作集合
};
/* 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)) //把tpd作为misc杂类设备进行注册,其实就是一个封装过的字符设备
pr_err("mtk_tpd: tpd_misc_device register failed\n");
tpd_get_gpio_info(pdev); //获取gpio配置信息
tpd = kmalloc(sizeof(struct tpd_device), GFP_KERNEL); //分配一个mtk_tpd_device
if (tpd == NULL)
return -ENOMEM;
memset(tpd, 0, sizeof(struct tpd_device));
/* allocate input device */
tpd->dev = input_allocate_device(); //分配一个input设备
if (tpd->dev == NULL) {
kfree(tpd);
return -ENOMEM;
}
.............................
在 tpd_probe 方法中,先选择触摸屏的种类和一些其他参数信息,然后把tpd作为misc设备进行注册,之后会从设备树获取gpio配置信息
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_err(&pdev->dev, "fwq Cannot find touch pinctrl1!\n");
return ret;
}
pins_default = pinctrl_lookup_state(pinctrl1, "default");//通过gpio子系统的函数,获取引脚的状态
..............................
TPD_DEBUG("[tpd%d] mt_tpd_pinctrl----------\n", pdev->id);
return 0;
}
回到tpd_probe中,之后会分配一个input设备,并进行初始化,然后将属性设置为 INPUT_PROP_DIRECT,即为触摸屏
/* allocate input device */
tpd->dev = input_allocate_device(); //分配一个input设备
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);
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; //设置tp的模式
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); // 设置绝对x
set_bit(ABS_Y, tpd->dev->absbit); // 设置绝对y
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); //设置输入属性,INPUT_PROP_DIRECT为触摸屏
/* save dev for regulator_get() before tpd_local_init() */
tpd->tpd_dev = &pdev->dev; //保存设置的值到tpd->tpd_dev结构体中,初始化tpd
然后会遍历mtk的 tpd_driver_list 里面的所有的驱动,当名字不为null时,调用具体TP驱动的 tpd_local_init 函数。
/* save dev for regulator_get() before tpd_local_init() */
tpd->tpd_dev = &pdev->dev;//保存设置的值到tpd->tpd_dev结构体中,初始化tpd
for (i = 1; i < TP_DRV_MAX_COUNT; i++) {
/* add tpd driver into list */
if (tpd_driver_list[i].tpd_device_name != NULL) {//这里是在遍历mtk的tpd_driver_list里面的所有的驱动,
//判断名字是否为NULL,每一个moduletouch IC 驱动都会添加到这个静态数组里面
tpd_driver_list[i].tpd_local_init();//调用具体TP驱动的tpd_local_init函数,
/* msleep(1); */
if (tpd_load_status == 1) {//这里我们会判断我们所遍历的每一个moduleIC驱动的初始化函数。
//成功的话就会将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;
}
}
}
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;
}
}
touch_resume_workqueue = create_singlethread_workqueue("touch_resume");
//创建一个工作队列并初始化,调用touch_resume_workqueue_callback来处理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)) //注册fb驱动
TPD_DMESG("register fb_notifier fail!\n");
/* TPD_TYPE_CAPACITIVE handle */
.............................................
if (input_register_device(tpd->dev)) //注册input设备
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); //创建tpd的属性
return 0;
}
按下power亮屏时,为了不卡住亮屏的时间,touch创建了一个工作队列用来处理 resume,最后注册fb驱动和input设备
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("tpd_fb_notifier_callback\n");
evdata = data;
/* If we aren't interested in this event, skip it immediately ... */
if (event != FB_EVENT_BLANK)
return 0;
/*
按下power亮屏,为了不卡住亮屏的时间,touch创建了一个工作队列用来处理resume
touchresume时间尽量要短,防止lcm亮了,touch短时间内不能使用
*/
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 touch_resume_workqueue failed\n");
return err;
}
}
break;
case FB_BLANK_POWERDOWN: //如果按下power按键灭屏,touch会run_supend,相当于挂起,休眠。
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 touch_resume_workqueue err = %d\n", err);
g_tpd_drv->suspend(NULL);
}
tpd_suspend_flag = 1;
break;
default:
break;
}
return 0;
}
首先,在 gslx680.c 文件的 tpd_driver_init 函数中,先获取dts中的配置信息,然后将 tpd_device_driver 添加到 tpd_driver_lis 数组里面,然后在 tpd_probe 方法中会回调 tpd_local_init,此过程正是上面讲到的 tpd_probe 里面 for 循环调用的过程。
/* called when loaded into kernel */
static int __init tpd_driver_init(void) {
printk("Sileadinc gslX680 touch panel driver init\n");
tpd_get_dts_info(); //获得dts中的配置信息
if(tpd_driver_add(&tpd_device_driver) < 0) //调用tpd_driver_add添加添加一个驱动
//其实就是将这个驱动添加到静态数组tpd_driver_lis里面。
printk("add gslX680 driver failed\n");
return 0;
}
在 tpd_driver_init 函数中可以添加判断系统是否开机,如果是关机充电进入内核,则返回一个错误码,这样可以在关机充电时不加载,防止关机充电时唤醒屏幕出现卡顿
在 tpd_local_init 函数中,先初始化ic并上电,然后注册i2c设备驱动,之后会调用tpd_i2c_probe
struct i2c_driver tpd_i2c_driver = {
.driver = {
.name = TPD_DEVICE,
.owner = THIS_MODULE,
.of_match_table = tpd_of_match,
},
.probe = tpd_i2c_probe,
.remove = tpd_i2c_remove,
.id_table = tpd_i2c_id,
.detect = tpd_i2c_detect,
};
int tpd_local_init(void)
{
printk("%s\n", __func__);
init_tp_power(tpd->tpd_dev, TP_POWER); //初始化ic并上电
tp_power_on(1);
if(i2c_add_driver(&tpd_i2c_driver) != 0) {// 对 tpd_i2c_driver 进行注册
printk("%s() unable to add i2c driver.\n", __func__);
return -1;
}
if (tpd_load_status == 0) {
printk("add error touch panel driver.\n");
i2c_del_driver(&tpd_i2c_driver);
return -1;
}
................
tpd_type_cap = 1;
printk("%s successfully\n", __func__);
return 0;
}
tpd_i2c_probe 中,会对IC进行复位操作,然后测试i2c是否连接成功,之后会创建一个内核线程,用于采集触摸数据
static int tpd_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int err = 0;
#ifdef TPD_PROXIMITY
struct hwmsen_object obj_ps;
#endif
printk("%s\n", __func__);
i2c_client = client;
/*
复位ic
*/
tpd_gpio_output(GTP_RST_PORT, 0);
msleep(100);
tpd_gpio_output(GTP_RST_PORT, 1);
tpd_gpio_output(GTP_INT_PORT, 0);
tpd_gpio_as_int(GTP_INT_PORT);
msleep(50);
mutex_init(&gsl_i2c_lock);
if(test_i2c(client) < 0) //测试I2C是否连接成功
{
printk("%s: i2c test error, so quit!!!\n", __func__);
return -ENODEV;
}
//创建一个内核中运行的线程,
//这个线程主要是实现触摸事件发生时,采集触摸点数
thread = kthread_run(touch_event_handler, 0, TPD_DEVICE);
if (IS_ERR(thread)) {
err = PTR_ERR(thread);
TPD_DMESG(TPD_DEVICE " failed to create kernel thread: %d\n", err);
}
printk("%s successfully\n", __func__);
tpd_load_status = 1;
return 0;
}
在线程执行函数 touch_event_handler 中,会申请中断,然后在一个do while循环里面设置线程休眠,当有触摸事件时会产生中断,在中断处理函数 tpd_eint_interrupt_handler 中将线程唤醒,然后执行 report_data_handle 函数获取并上报数据
//触摸中断事件处理函数
static irqreturn_t tpd_eint_interrupt_handler(int irq, void *dev_id)
{
TPD_DEBUG_PRINT_INT;
eint_flag = 1;
tpd_flag=1; //将标志位置位
wake_up_interruptible(&waiter); // 唤醒等待队列
return IRQ_HANDLED;
}
static int touch_event_handler(void *unused) //触摸事件 采集线程
{
struct sched_param param = { .sched_priority = 4 };
sched_setscheduler(current, SCHED_RR, ¶m);
// 调度策略和调度参数分别设置为param指向的sched_param结构中指定的policy和参数
gsl_post_init(); //中断申请
do
{
set_current_state(TASK_INTERRUPTIBLE); //设置当前线程睡眠
if (tpd_flag == 0) // 设置等待队列等待事件,tpd_flag 会在中断处理函数中置位
{
DEFINE_WAIT(wait);
prepare_to_wait(&waiter, &wait, TASK_INTERRUPTIBLsE);
schedule(); //让进程让出调度
finish_wait(&waiter, &wait);
}
// wait_event_interruptible(waiter, tpd_flag != 0);
tpd_flag = 0;
TPD_DEBUG_SET_TIME;
set_current_state(TASK_RUNNING);// 标记当前线程正执行
print_info("===touch_event_handler, task running===\n");
eint_flag = 0;
report_data_handle(); //获取并上报数据
} while (!kthread_should_stop());
return 0;
}
具体的触摸IC驱动就介绍到此,不同的IC厂家的代码基本都大同小异。
首先,将触摸IC厂提供的驱动文件XXX放入路径:kernel-3.18\drivers\input\touchscreen\mediatek
并在Makefile中加入编译规则,以 GSLX688 为例
obj-$(CONFIG_TOUCHSCREEN_GSLX688) += GSLX688/
上述添加方法使用宏控制的目的是为了方便选择编译触摸IC的驱动,需在Kconfig和项目的xxxdeconfig文件加入宏 CONFIG_TOUCHSCREEN_GSLX688 的配置,也可以不加宏控制直接编译,如下
obj-y += GSLX688/
之后配置dts文件,此处需查看原理图,确定触摸IC使用的I2C号,各引脚的GPIO号和中断号,在相应的I2C节点中加入配置
然后就是编译了,运气好可能一次性就编译通过了,但是大多数情况第一次都是会报错的,因为你拿到的驱动代码基本上是在MTK其他平台copy下来的,此处只有根据报错信息去做相应的修改,报错基本上是一些头文件和一些接口等 差异导致的
下面简单介绍一些TP的调试手段
1,如果触摸IC能正常工作,只是触摸点位不对等问题,找FAE更改峰位参数解决。
2,IC不能正常工作的情况
首先检查硬件是否有问题,如焊接是否有虚焊,供电是否正常,pin脚是否匹配等
其次是检查中断触发是否正常,触摸TP的时候用示波器测量中断脚,看是否有中断触发,还需检查触摸IC的IO电压是否和平台的pin脚电压相匹配(举例:中断触发的电压为1.8v,而中断脚需3.3v才能触发,此时IC端虽然有中断电平输出,但主控无法识别并产生中断事件),并确定触发方式是否正确,然后检查I2C通讯是否成功,查看串口log或使用adb命令:getevent 检查系统是否有接收到上报的数据。
TP的移植和调试相对简单,一般问题都出在中断和I2C上,很好解决,小编就介绍到此,非常感谢阅读。