驱动注册加载过程
注册kernel-4.49/drivers/input/touchscreen/mediatek/目录下的具体驱动(如ft5x0x),入口module_init(tpd_driver_init)会调用tpd_get_dts_info()获取到dts 设备树中与 touch 相关配置项并保存到全局变量tpd_dts_data中,然后在tpd_driver_init函数里调用tpd_driver_add(&tpd_device_driver)
//tpd.h 定义相关结构体 struct tpd_dts_info tpd_dts_data; struct tpd_dts_info {
int tpd_resolution[2]; int touch_max_num; int use_tpd_button; int tpd_key_num; int tpd_key_local[4]; bool tpd_use_ext_gpio; int rst_ext_gpio_num; struct tpd_key_dim_local tpd_key_dim_local[4]; struct tpd_filter_t touch_filter; }; struct tpd_driver_t {
char *tpd_device_name; int (*tpd_local_init)(void); void (*suspend)(struct device *h); void (*resume)(struct device *h); int tpd_have_button; struct tpd_attrs attrs; }; //ft5x0x_driver.c static struct tpd_driver_t tpd_device_driver = {
.tpd_device_name = "FT5x0x", .tpd_local_init = tpd_local_init, .suspend = tpd_suspend, .resume = tpd_resume, .attrs = {
.attr = ft5x0x_attrs, .num
=
ARRAY_SIZE
(ft5x0x_attrs
)
,
}
,
}
;
/* called when loaded into kernel */
static
int __init
tpd_driver_init
(
void
)
{
TPD_DMESG
(
"MediaTek FT5x0x touch panel driver init\n"
)
;
//打印提示
tpd_get_dts_info
(
)
;
//调用tpd_get_dts_info函数,获取该驱动的dts文件中的配置信息,tpd_get_dts_info()函数将dts信息保存在tpd_dts_info类型结构体变量tpd_dts_data里
if
(
tpd_driver_add
(
&tpd_device_driver
)
<
0
)
//调用tpd_drive_add添加一个驱动,
//实则将这个驱动添加到静态数组tpd_driver_list中,添加成功返回1,失败返回-1
//tpd_device_driver为驱动的TP驱动结构体,里面保存了驱动的name等信息
TPD_DMESG
(
"add FT5x0x driver failed\n"
)
;
return
0
;
}
然后,在tpd_driver_add(&tpd_device_driver)中,会将这个TP驱动注册添加到 tpd_driver_list[] 全局数组中
//位置:kernel-4.9\drivers\input\touchscreen\mediatek\mtk_tpd.c
//添加驱动,将TP驱动添加到tpd_driver_list[]静态数组中去
int tpd_driver_add(struct tpd_driver_t *tpd_drv) //传入ft5x0x驱动的tpd_device_driver结构体
{
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; //从k65v1_64_bsp.dst文件内获取tp虚拟按键配置的个数
/* R-touch */
if (strcmp(tpd_drv->tpd_device_name, "generic") == 0) {
//判断是否为电阻屏,便将该驱动添加到静态数组tpd_driver_list[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++) {
//电容屏,将tp驱动添加到tpd_driver_list[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, //用添加后的list再次判断name是否一致
tpd_drv->tpd_device_name) == 0)
return 1; /* driver exist *///返回1,表示添加成功
}
return 0;
}
mtk_tpd的平台调用流程
在kernel-4.9\drivers\input\touchscreen\mediatek\mtk_tpd.c文件里,late_initcall(tpd_device_init)入口函数首先加载进入tpd_device_init(),代码如下:
1、tpd_device_init()函数:
-
调用tpd_init_work_callback函数
-
然后调用platform_driver_register(&tpd_driver)注册一个平台驱动driver
-
工作队列(workqueue)是另外一种将工作推后执行的形式.工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。最重要的就是工作队列允许被重新调度甚至是睡眠。
//位置:kernel-4.9\drivers\input\touchscreen\mediatek\mtk_tpd.c
static int __init tpd_device_init(void)
{
int res = 0;
tpd_init_workqueue = create_singlethread_workqueue("mtk-tpd"); //create_singlethread_workqueue用于创建workqueue工作队列,@name:workqueue名称
INIT_WORK(&tpd_init_work, tpd_init_work_callback); //初始化工作队列,调用tpd_init_work_callback函数用于注册一个对应的平台驱动,INIT_WORK只是定义了work和work对应的操作
res = queue_work(tpd_init_workqueue, &tpd_init_work); //queue_work激活时,执行对应work,即执行tpd_init_work_callback函数
if (!res)
pr_info("tpd : touch device init failed res:%d\n", res);
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) //调用platform_driver_register(&tpd_driver)注册一个平台驱动driver
TPD_DMESG("unable to register touch panel driver.\n");
}
2、平台驱动(platform driver)注册成功后,会调用 of_match_table = touch_of_match 的compatible = “mediatek,touch” 与 在设备树dts文件内注册设备的platform device touch的 compatible = “mediatek,touch” 进行匹配。compatible相同匹配成功,就执行tpd_probe函数。
//mtk_tpd.c内 分析tpd_probe函数
//执行
tpd_probe(struct platform_device *pdev)
1、注册杂项设备
//static struct miscdevice tpd_misc_device = {
// .minor = MISC_DYNAMIC_MINOR,
// .name = "touch",
// .fops = &tpd_fops,
2、获得 dts 相关引脚配置 pinctrltpd_get_gpio_info(pdev);
3. 分配 mtk 封装的 tp 数据结构
tpd = kmalloc(sizeof(struct tpd_device), GFP_KERNEL);
4. 分配输入子系统
tpd->dev = input_allocate_device();
5. 处理 touch panel 报点旋转
#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
6. 相关报点参数设置
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;
7. 设置输入设备 TP 上报事件类型
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);
8.调用第三方驱动注册的的 init 函数
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里面的所有的驱动,每个module touch IC 驱动都会添加到这个静态数组里面
tpd_driver_list[i].tpd_local_init(); //调用tpd_local_init()函数,主要注册 i2c设备驱动,并初始化TP使用的reglator:上电 2.8v调用。i2c_add_driver(&tpd_i2c_driver),注册i2c驱动成功会跟注册的device匹配,匹配通过(name一致)然后具体驱动的probe成功的话就会将tpd_load_status变量置1
/* msleep(1); */
if (tpd_load_status == 1) {
//此处的tpd_load_status 是在对应使用的ft5x0x驱动中的probe函数中设置的,TP驱动初始化成功后,会设置此位为1,通过这个值tpd_load_status判断注册是否成功
TPD_DMESG("tpd_probe, tpd_driver_name=%s\n",
tpd_driver_list[i].tpd_device_name);
g_tpd_drv = &tpd_driver_list[i]; //然后调用g_tpd_drv = &tpd_driver_list[i]去执行具体的TP ic驱动
break;
}
}
}
9. 如果没有注册 tp 驱动,则调用默认的 R 电阻屏驱动
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();
}
}
10.调用当前使用的 tp 驱动的 resume() 函数
touch_resume_workqueue = create_singlethread_workqueue("touch_resume");
INIT_WORK(&touch_resume_work, touch_resume_workqueue_callback);
11. 注册内核通知链,监听fb_notifier,应该会在相应的屏的驱动里面进行通知调用
//在这里进行 TP 的唤醒和休眠操作
tpd_fb_notifier.notifier_call = tpd_fb_notifier_callback;
tpd_fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data) //参数:谁调用谁传
// 注册通知链
fb_register_client(&tpd_fb_notifier)
// 设置输入设备 TP 上报事件
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)) //失败返回0
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);
3、在ft5x0x_driver.c驱动文件内tpd_local_init函数
static int tpd_local_init(void)
{
int retval;
#if defined(CONFIG_MACH_MT6765)
tpd->reg = regulator_get(tpd->tpd_dev, "vldo28"); //并初始化TP使用的reglator:上电 2.8v调用。
#else
tpd->reg = regulator_get(tpd->tpd_dev, "vgp1");
#endif
#if defined(CONFIG_MACH_MT6761)
tpd->reg = regulator_get(tpd->tpd_dev, "vldo28");
#endif
retval = regulator_set_voltage(tpd->reg, 2800000, 2800000);
if (retval != 0) {
TPD_DMESG("Failed to set reg-vgp6 voltage: %d\n", retval);
return -1;
}
if (i2c_add_driver(&tpd_i2c_driver) != 0) {
//调用i2c_add_driver(&tpd_i2c_driver),注册一个i2c设备驱动,并与注册的device name匹配,匹配一致后将执行tpd_device_driver里面的tpd_probe函数,即ft5x0x的probe侦测函数,然后具体驱动的probe成功的话就会将tpd_load_status变量置1
TPD_DMESG("unable to add i2c driver.\n");
return -1;
}
/* tpd_load_status = 1; */
if (tpd_dts_data.use_tpd_button) {
tpd_button_setting(tpd_dts_data.tpd_key_num, tpd_dts_data.tpd_key_local,
tpd_dts_data.tpd_key_dim_local);
}
4、中断部分
static int tpd_irq_registration(void)
{
struct device_node *node = NULL;
int ret = 0;
node = of_find_compatible_node(NULL, NULL, "mediatek,touch");//在设备树(mt6765.dts)中匹配节点,获取touch_of_match数组中的name字符串与此处的touch进行匹配,
if (node) {
/*touch_irq = gpio_to_irq(tpd_int_gpio_number);*/ //不用gpio_to_irq来使用中断
touch_irq = irq_of_parse_and_map(node, 0); //使用irq_of_parse_and_map函数,解析dts内容的中断信息给驱动使用,后边的这个 0表示的是偏移索引,返回值:成功返回中断号
TPD_DMESG("[%s] tpd request_irq irq=%d.", __func__,touch_irq);
/***************** request_irq(touch_irq, tpd_eint_interrupt_handler,IRQF_TRIGGER_FALLING, TPD_DEVICE, NULL)函数,用于申请中断, @touch_irq:为中断号; @tpd_eint_interrupt_handler:为中断处理函数,一个回调函数,中断发生时系统将会调用这个函数; @IRQF_TRIGGER_FALLING:为中断处理的属性; @TPD_DEVICE:中断名称,通常是设备驱动程序的名称,在mtk_tpd.c文件内设置; @返回值:返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享 *******************/
ret = request_irq(touch_irq, tpd_eint_interrupt_handler,
IRQF_TRIGGER_FALLING, TPD_DEVICE, NULL);
if (ret > 0)
TPD_DMESG("tpd request_irq IRQ LINE NOT AVAILABLE!.");
} else {
TPD_DMESG("[%s] tpd request_irq can not find touch eint device node!.", __func__);
}
return 0;
}
cust.dtsi设备树文件内
&i2c0 {
#address-cells = <1>;
#size-cells = <0>;
clock-frequency = <100000>;
mediatek,use-open-drain;
cap_touch_mtk:cap_touch@5D {
compatible = "mediatek,cap_touch"; //用于和驱动中的compatible相匹配
reg = <0x5D>; //reg是TP_IC的通信地址
status = "okay";
};
};
&touch {
interrupt-parent = <&pio>; //interrupt-parent 对应的是平台的中断控制器,里面应用的 pio对应的是mt6765.dtsi文件里面的中断控制器dts描述
interrupts = <0 IRQ_TYPE_EDGE_FALLING 0 0>; //interrupts 的第一个参数对应的是中断号,第二个参数对应的是中断的触发方式,第三个参数是GPIO号,&pio内设置,第四个是GPIO口的电平
status = "okay";
};