MTK中的TP代碼結構並不復雜,相比於其他的系統更為的簡單些。它使用的是input子系統,通過該系統來上報觸摸按鍵。
首先我們來看看TP的文件夾下的各代碼文件的功能。
文件名 |
具體功能 |
關系文件 |
tpd.h |
一些宏和extern 函數,外部使用 |
Mtk_tpd.c |
tpd_button.c |
關於實體按鍵的定義 |
|
Tpd_calibrate.c |
矯准,比如偏移,可能是MTK以前做電阻屏留下的架構 |
|
Tpd_calibrate.h |
矯准,一些宏和數據結構 |
|
Tpd_debug.c |
一些函數用於debug信息 |
|
Tpd_debug.h |
一些dubug信息和函數申明 |
|
Tpd_default.c |
函數似乎為完成,函數只有一個 |
|
Tpd_default.h |
似乎未完成 |
|
Tpd_init.c |
空文件夾 |
|
Tpd_misc.c |
導出一些符號,基本都是切換模式函數 |
|
Tpd_setting.c |
傳些參數至上層應用 |
|
MTK_tpd.c |
關鍵代碼區 |
|
met_ftrace_touch.h |
Ftrace工具,用來代碼內追蹤運行情況 |
|
以上代碼文件中較為重要的就是mtk_tpd.c,故重點分析該文件。
在此會有以下幾個關鍵的點:
1.mtk_tpd.c中的函數運行流程。
2.mtk_tpd.c中的input系統。
3.注冊i2C
4.tpd_driver_add函數
5.LDO芯片設置
6.通過設備數或者其他方式獲取RST,EINT引腳號
7.通過設備節點獲得中斷,並請求一個中斷號,和注冊中斷handle
8.運行線程開啟
9.中斷處理函數,喚醒線程,並上報鍵值
在此,我將這幾個分為一些幾部分:
一、注冊MTK下的TP框架,與建立I2C框架。
二、在probe函數中完成相應電源,設備數,硬件中斷,GPIO等設置。
三、建立一個上報框架分為如下:1)開啟一個線程2)申請一個中斷irq來綁定硬件中斷觸發3)irq_handle中喚醒work 4)work在喚醒wait事件5)開啟的線程等到wait事件執行上報關鍵代碼。,否則睡眠。
四、填充關鍵的TP上報代碼(input系統上報)。
1.mtk_tpd.c中的函數運行流程。
mtk_tpd.c中,我們最先查看init函數,也就是module添加。
1 static int tpd_remove(struct platform_device *pdev) 2 { 3 input_unregister_device(tpd->dev); 4 return 0; 5 } 6 7 /* called when loaded into kernel */ 8 static int __init tpd_device_init(void) 9 { 10 TPD_DEBUG("MediaTek touch panel driver init\n"); 11 if (platform_driver_register(&tpd_driver) != 0) { 12 TPD_DMESG("unable to register touch panel driver.\n"); 13 return -1; 14 } 15 return 0; 16 }
可能有些版本的代碼是使用了workqune去注冊的,使用這個是讓tpd的probe在所有子tp系統add之后再運行mtk_tpd中的probe。
看到上面代碼中,在init中建立一個workqueue,這樣使得tpd_init_work_callback中的platform_driver_register延遲工作,在被內核調度室才會進行注冊,注冊時的probe函數也隨之延后運行。(具體作用暫時不知,我們僅僅知道注冊了一個platform設備)。
接下來再看tpd_driver_add:
1 /* Add driver: if find TPD_TYPE_CAPACITIVE driver successfully, loading it */ 2 int tpd_driver_add(struct tpd_driver_t *tpd_drv) 3 { 4 int i; 5 6 if (g_tpd_drv != NULL) { 7 TPD_DMESG("touch driver exist\n"); 8 return -1; 9 } 10 /* check parameter */ 11 if (tpd_drv == NULL) 12 return -1; 13 14 //runyee zhou add device info,20161012 15 #if defined(CONFIG_RUNYEE_DEVICE_INFO_SUPPORT) 16 hct_touchpanel_device_add(tpd_drv,DEVICE_SUPPORTED); 17 #endif 18 19 20 tpd_drv->tpd_have_button = tpd_dts_data.use_tpd_button; 21 /* R-touch 電阻屏,geneic也就是list[0]為電阻屏*/ 22 if (strcmp(tpd_drv->tpd_device_name, "generic") == 0) { 23 tpd_driver_list[0].tpd_device_name = tpd_drv->tpd_device_name; 24 tpd_driver_list[0].tpd_local_init = tpd_drv->tpd_local_init; 25 tpd_driver_list[0].suspend = tpd_drv->suspend; 26 tpd_driver_list[0].resume = tpd_drv->resume; 27 tpd_driver_list[0].tpd_have_button = tpd_drv->tpd_have_button; 28 return 0; 29 } 30 for (i = 1; i < TP_DRV_MAX_COUNT; i++) { 31 /* add tpd driver into list 使用該函數就會被填入到list中*/ 32 if (tpd_driver_list[i].tpd_device_name == NULL) { 33 tpd_driver_list[i].tpd_device_name = tpd_drv->tpd_device_name; 34 tpd_driver_list[i].tpd_local_init = tpd_drv->tpd_local_init; 35 tpd_driver_list[i].suspend = tpd_drv->suspend; 36 tpd_driver_list[i].resume = tpd_drv->resume; 37 tpd_driver_list[i].tpd_have_button = tpd_drv->tpd_have_button; 38 tpd_driver_list[i].attrs = tpd_drv->attrs; 39 #if 0 40 if (tpd_drv->tpd_local_init() == 0) { 41 TPD_DMESG("load %s successfully\n", 42 tpd_driver_list[i].tpd_device_name); 43 g_tpd_drv = &tpd_driver_list[i]; 44 } 45 #endif 46 break; 47 } 48 if (strcmp(tpd_driver_list[i].tpd_device_name, tpd_drv->tpd_device_name) == 0) 49 return 1; /* driver exist */ 50 } 51 52 return 0; 53 }
在此函數中的tpd_driver_list是一個全局的tpd_driver_t結構體,里面的TP_DRV_MAX_COUNT是支持的最大tp設備數。
驅動中調用add來向tpd_driver_list添加一個新的設備,並在添加前檢查該名字是否為NULL,保證add在最末尾。
接着看probe函數,我們跳過probe前面部分,直接到:
1 /* save dev for regulator_get() before tpd_local_init() */ 2 tpd->tpd_dev = &pdev->dev; 3 for (i = 1; i < TP_DRV_MAX_COUNT; i++) { 4 /* add tpd driver into list */ 5 if (tpd_driver_list[i].tpd_device_name != NULL) { 6 tpd_driver_list[i].tpd_local_init();//執行具體TP驅動的local_init 7 /* msleep(1); */ 8 if (tpd_load_status == 1) { 9 TPD_DMESG("[mtk-tpd]tpd_probe, tpd_driver_name=%s\n", 10 tpd_driver_list[i].tpd_device_name); 11 g_tpd_drv = &tpd_driver_list[i]; 12 // 13 #if defined(CONFIG_RUNYEE_DEVICE_INFO_SUPPORT) 14 hct_set_touch_device_used(g_tpd_drv->tpd_device_name, 0); 15 #endif 16 break; 17 } 18 } 19 } 20 if (g_tpd_drv == NULL) { 21 if (tpd_driver_list[0].tpd_device_name != NULL) { 22 g_tpd_drv = &tpd_driver_list[0]; 23 /* touch_type:0: r-touch, 1: C-touch */ 24 touch_type = 0; 25 g_tpd_drv->tpd_local_init(); 26 TPD_DMESG("[mtk-tpd]Generic touch panel driver\n"); 27 } else { 28 TPD_DMESG("[mtk-tpd]cap touch and Generic touch both are not loaded!!\n"); 29 return 0; 30 } 31 }
在這里面我們可以看到,這里會遍歷tpd_driver_list這個表,並且執行表中所有的tpd_local_init函數,而tpd_local_init通常會注冊i2C驅動,在register時會執行i2c的probe函數,這樣在一個probe函數中將所有的TP注冊全部完成。
在最末尾,執行了input系統注冊:
1 /* use fb_notifier */ 2 tpd_fb_notifier.notifier_call = tpd_fb_notifier_callback;//和上層接收TP是否存在相關 3 if (fb_register_client(&tpd_fb_notifier)) 4 TPD_DMESG("register fb_notifier fail!\n"); 5 /* TPD_TYPE_CAPACITIVE handle */ 6 if (touch_type == 1) { 7 //正常的input設備注冊 8 set_bit(ABS_MT_TRACKING_ID, tpd->dev->absbit); 9 set_bit(ABS_MT_TOUCH_MAJOR, tpd->dev->absbit); 10 set_bit(ABS_MT_TOUCH_MINOR, tpd->dev->absbit); 11 set_bit(ABS_MT_POSITION_X, tpd->dev->absbit); 12 set_bit(ABS_MT_POSITION_Y, tpd->dev->absbit); 13 input_set_abs_params(tpd->dev, ABS_MT_POSITION_X, 0, TPD_RES_X, 0, 0); 14 input_set_abs_params(tpd->dev, ABS_MT_POSITION_Y, 0, TPD_RES_Y, 0, 0); 15 #if defined(CONFIG_MTK_S3320) || defined(CONFIG_MTK_S3320_47) \ 16 || defined(CONFIG_MTK_S3320_50) || defined(CONFIG_MTK_MIT200) \ 17 || defined(CONFIG_TOUCHSCREEN_SYNAPTICS_S3528) || defined(CONFIG_MTK_S7020) 18 input_set_abs_params(tpd->dev, ABS_MT_PRESSURE, 0, 255, 0, 0); 19 input_set_abs_params(tpd->dev, ABS_MT_WIDTH_MAJOR, 0, 15, 0, 0); 20 input_set_abs_params(tpd->dev, ABS_MT_WIDTH_MINOR, 0, 15, 0, 0); 21 input_mt_init_slots(tpd->dev, 10, 0); 22 #else 23 input_set_abs_params(tpd->dev, ABS_MT_TOUCH_MAJOR, 0, 100, 0, 0); 24 input_set_abs_params(tpd->dev, ABS_MT_TOUCH_MINOR, 0, 100, 0, 0); 25 #endif /* CONFIG_MTK_S3320 */ 26 TPD_DMESG("Cap touch panel driver\n"); 27 } 28 input_set_abs_params(tpd->dev, ABS_X, 0, TPD_RES_X, 0, 0); 29 input_set_abs_params(tpd->dev, ABS_Y, 0, TPD_RES_Y, 0, 0); 30 input_abs_set_res(tpd->dev, ABS_X, TPD_RES_X); 31 input_abs_set_res(tpd->dev, ABS_Y, TPD_RES_Y); 32 input_set_abs_params(tpd->dev, ABS_PRESSURE, 0, 255, 0, 0); 33 input_set_abs_params(tpd->dev, ABS_MT_TRACKING_ID, 0, 10, 0, 0); 34 35 if (input_register_device(tpd->dev)) 36 TPD_DMESG("input_register_device failed.(tpd)\n");
在此就不展開input_set_abs_params參數的意義,后面會繼續深入。
2.mtk_tpd.c中的input系統
要向用戶空間發送信息,且是中斷,少不了input系統,這里使用MTK中自帶的GT9XXTB_hotknot來分析:
1 static int tpd_history_x = 0, tpd_history_y; 2 static void tpd_down(s32 x, s32 y, s32 size, s32 id) 3 { 4 if ((!size) && (!id)) { 5 input_report_abs(tpd->dev, ABS_MT_PRESSURE, 100); 6 input_report_abs(tpd->dev, ABS_MT_TOUCH_MAJOR, 100); 7 } else { 8 input_report_abs(tpd->dev, ABS_MT_PRESSURE, size); 9 input_report_abs(tpd->dev, ABS_MT_TOUCH_MAJOR, size); 10 /* track id Start 0 */ 11 input_report_abs(tpd->dev, ABS_MT_TRACKING_ID, id); 12 } 13 14 input_report_key(tpd->dev, BTN_TOUCH, 1); 15 input_report_abs(tpd->dev, ABS_MT_POSITION_X, x); 16 input_report_abs(tpd->dev, ABS_MT_POSITION_Y, y); 17 input_mt_sync(tpd->dev); 18 TPD_DEBUG_SET_TIME; 19 TPD_EM_PRINT(x, y, x, y, id, 1); 20 tpd_history_x = x; 21 tpd_history_y = y; 22 /* MMProfileLogEx(MMP_TouchPanelEvent, MMProfileFlagPulse, 1, x+y); */ 23 if (tpd_dts_data.use_tpd_button) { 24 if (FACTORY_BOOT == get_boot_mode() || 25 RECOVERY_BOOT == get_boot_mode()) 26 tpd_button(x, y, 1); 27 } 28 }
這一段tdp_down是關鍵的上報代碼,也就是說最后從TP芯片來的坐標數據需要通過以上input_report_adbs等函數來上報,那先暫時記住該部分。其中的input其實是不需要再次去注冊的,因為在mtk_tpd.c中已經注冊,只需要:extern struct tpd_device *tpd; 例子中在include/tpd_gt9xx_common.h有定義。
那么我們大概清楚了,MTK下的TP其實就是我們自己寫驅動上報(我們需要做的事),其他的input系統注冊我們就不操心了。那么大多數TP都是i2c,我們則需要進行i2c注冊。
3.具體驅動中的i2c注冊
注冊I2C這里就簡略提一下,i2C可以使用各種方式注冊,在此實例使用的是i2c_board_info來注冊,同樣的先注冊device,然后在drivers注冊時會匹配進入probe,其中需要做以下事情:
static s32 tpd_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { //1.i2c_clien獲得,給read,write函數使用 //2.上電,因為有些TP可能是使用LDO去控制的,所以要開啟電源regulator_set_voltage或者mtk的hwPowerDown(MT65XX_POWER_LDO_VGP2, "TP"); //3.使用設備樹,或者直接設置RST,INT腳的狀態。 //4.簡易的test測試tp是否存在,比如讀取version,添加異常處理 //5.INT腳配置硬件中斷申請,並鏈接處理函數 //6.使用 kthread_run開啟一個可休眠線程,等待中斷喚醒處理坐標上報 //7.添加自己的input參數,比如壓力,按壓面的size //8.與芯片相關的其他操作 }
以上操作都可在代碼中發現,也是一些基本的必須的設置,調試時應該先以大體框架通過為主,大體框架就是,local_init->i2c_probe->相關函數設置(thread_run有沒有跑,中斷函數是否申請了,設備樹是否成功匹配),然后再去進行硬件調試:i2c通不通,中斷是否能正常觸發,能否讀取版本等。
4.tpd_driver_add函數
1 /* called when loaded into kernel */ 2 static int __init tpd_driver_init(void) 3 { 4 GTP_INFO("MediaTek gt91xx touch panel driver init\n"); 5 tpd_get_dts_info(); 6 if (tpd_driver_add(&tpd_device_driver) < 0) 7 GTP_INFO("add generic driver failed\n"); 8 9 return 0; 10 }
在init函數中加入tpd_driver_add 函數。這樣只要以下流程完成即可調試硬件。
至此,添加一個MTK TP的框架搭建完了,回顧下,主要是:
1. I2C框架,device與driver注冊,還有probe函數(接下來的工作)
2. 使用tpd_driver_add向MTK TP框架添加設備,完成local_tpd_init函數
總結下運行流程如下:
5.各操作實例
5.1.LDO芯片設置
有些硬件使用PMU來為TP供電,所以有可能需要使用代碼來設備如:
如果沒有的話就直接設置供電打開即可。
5.2通過設備樹或者其他方式獲取RST,EINT引腳號
有些平台使用的是設備樹來獲取的這兩個引腳,這里我的是MTK6755平台,如下:
也可以使用GPIO_request來獲取,只要能做到控制這兩個引腳即可。
5.3通過設備節點獲得中斷,並請求一個中斷號,和注冊中斷handle
因為上報事件應該定義為一個中斷事件,故應該為ENIT引腳分配一個中斷號,並申請中斷handle。在此使用的是通過設備節點來申請的中斷節點,也是使用的是MTK的設備樹來獲取的,所以可能需要修改dtsi。
紫色框中是MTK的注冊硬件IRQ handle 的方法,而這里使用的是設備樹獲取中斷硬件中斷號,並分配中斷向量號,之候使用request來注冊中斷handle。
5.4運行線程開啟
報點需要高響應,延遲低,所以為TP開啟一個線程來保證高效。具體代碼如下:
我們進入ilitek_irq_handle_thread
紅色框中是關鍵的TP坐標上報代碼,而在此之前這個線程是睡眠的,只是偶爾運行一次,該線程等待中斷事件打斷,並運行,而喚醒的代碼則在之前申請的硬件中斷的handle中的work會喚醒。
5.5中斷處理函數,喚醒線程,並上報鍵值
該函數則是喚醒wait,則在上節的線程會被喚醒,進行關鍵的上報代碼。
也就是說當你的手觸摸屏幕,屏幕的中斷引腳會產生中斷(拉低或者高),使得申請的硬件中斷handle運行,handle中喚醒wait,接着運行着的線程被叫醒,執行坐標上報關鍵代碼。
至此一個驅動大概框架就是這樣,后面再為大家帶來一些細致的講解