如今觸摸屏的使用越來(lái)越廣泛,從手機(jī)、平板到蜂巢取貨等場(chǎng)合,都是用了觸摸屏,觸摸屏的使用非常便捷高效。在本章就來(lái)學(xué)習(xí)一下如何在 Linux 下編寫電容觸摸屏驅(qū)動(dòng)。 54.1 Linux 下電容觸摸屏驅(qū)動(dòng)框架 54.1.1 多點(diǎn)觸摸協(xié)議詳解 在前面的裸板實(shí)驗(yàn)中,已經(jīng)詳細(xì)講解過(guò)了電容觸摸驅(qū)動(dòng)的基本原理,根據(jù)前面的實(shí)驗(yàn)可以總結(jié)出電容觸摸屏驅(qū)動(dòng)其實(shí)就是一下幾種 linux 驅(qū)動(dòng)框架的組合: ① IIC 設(shè)備驅(qū)動(dòng),因?yàn)殡娙萦|摸 IC 基本都是 IIC 接口的,因此大框架就是 IIC 設(shè)備驅(qū)動(dòng)。 ② 通過(guò)中斷引腳(INT)向 linux 內(nèi)核上報(bào)觸摸信息,因此需要用到 linux 中斷驅(qū)動(dòng)框架。坐標(biāo)的上報(bào)在中斷服務(wù)函數(shù)中完成。 ③ 觸摸屏的坐標(biāo)信息、屏幕按下和抬起信息都屬于 linux 的 input 子系統(tǒng),因此向 linux 內(nèi)核上報(bào)觸摸屏坐標(biāo)信息就得使用 input 子系統(tǒng)。只是,我們得按照 linux 內(nèi)核規(guī)定的規(guī)則來(lái)上報(bào)坐標(biāo)信息。 在上面的驅(qū)動(dòng)框架組合中我們發(fā)現(xiàn) I2C 驅(qū)動(dòng)、中斷驅(qū)動(dòng)、input 子系統(tǒng)都已經(jīng)學(xué)習(xí)了解過(guò)了,還沒(méi)有學(xué)習(xí)過(guò) input 子系統(tǒng)下的多點(diǎn)電容觸摸協(xié)議,這個(gè)就是本章學(xué)習(xí)的重點(diǎn),linux 內(nèi)核中有一份文檔詳細(xì)的講解了多點(diǎn)電容觸摸屏協(xié)議,文檔路徑為:Documentation/input/multitouch-protocol.txt。 MT 協(xié)議被分為兩種類型,TypeA 和 TypeB,這兩種類型的區(qū)別如下: TypeA:適用于觸摸點(diǎn)不能被區(qū)分或者追蹤,此類型的設(shè)備上報(bào)原始數(shù)據(jù)(此類型在實(shí)際使用中非常少!)。 Type B:適用于有硬件追蹤并能區(qū)分觸摸點(diǎn)的觸摸設(shè)備,此類型設(shè)備通過(guò) slot 更新某一個(gè)觸摸點(diǎn)的信息,F(xiàn)T5426 就屬于此類型,一般的多點(diǎn)電容觸摸屏 IC 都有此能力。 觸摸點(diǎn)的信息通過(guò)一系列的 ABS_MT 事件(有的資料也叫消息)上報(bào)給 linux 內(nèi)核,只有 ABS_MT 事件是用于多點(diǎn)觸摸的,ABS_MT 事件定義在文件 linux/input.h 中,相關(guān)事件如下所示: 852 #define ABS_MT_SLOT 0x2f /* MT slot being modified */ 853 #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */ 854 #define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */ 855 #define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */ 856 #define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */ 857 #define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */ 858 #define ABS_MT_POSITION_X 0x35 /* Center X touch position */ 859 #define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */ 860 #define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */ 861 #define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */ 862 #define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */ 863 #define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */ 864 #define ABS_MT_DISTANCE 0x3b /* Contact hover distance */ 865 #define ABS_MT_TOOL_X 0x3c /* Center X tool position */ 866 #define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */ 在上面這些眾多的 ABS_MT 事件中,我們最常用的就是 ABS_MT_SLOT 、 ABS_MT_POSITION_X 、ABS_MT_POSITION_Y 和 ABS_MT_TRACKING_ID 。其中 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 用來(lái)上報(bào)觸摸點(diǎn)的 (X,Y) 坐標(biāo)信息,ABS_MT_SLOT 用來(lái)上報(bào)觸摸點(diǎn) ID ,對(duì)于 Type B 類型的設(shè)備,需要用到ABS_MT_TRACKING_ID 事件來(lái)區(qū)分觸摸點(diǎn)。 對(duì)于 TypeA 類型的設(shè)備,通過(guò) input_mt_sync()函數(shù)來(lái)隔離不同的觸摸點(diǎn)數(shù)據(jù)信息,此函數(shù)原型如下所示: void input_mt_sync(struct input_dev *dev) 此函數(shù)只要一個(gè)參數(shù),類型為 input_dev,用于指定具體的 input_dev 設(shè)備。input_mt_sync()函數(shù)會(huì)觸發(fā) SYN_MT_REPORT 事件,此事件會(huì)通知接收者獲取當(dāng)前觸摸數(shù)據(jù),并且準(zhǔn)備接收下一個(gè)觸摸點(diǎn)數(shù)據(jù)。 對(duì)于 Type B 類型的設(shè)備,上報(bào)觸摸點(diǎn)信息的時(shí)候需要通過(guò) input_mt_slot()函數(shù)區(qū)分是哪一個(gè)觸摸點(diǎn),input_mt_slot()函數(shù)原型如下所示: void input_mt_slot(struct input_dev *dev, int slot) 此函數(shù)有兩個(gè)參數(shù),第一個(gè)參數(shù)是 input_dev 設(shè)備,第二個(gè)參數(shù) slot 用于指定當(dāng)前上報(bào)的是哪個(gè)觸摸點(diǎn)信息。input_mt_slot()函數(shù)會(huì)觸發(fā) ABS_MT_SLOT 事件,此事件會(huì)告訴接收者當(dāng)前正在更新的是哪個(gè)觸摸點(diǎn)(slot)的數(shù)據(jù)。 不管是哪個(gè)類型的設(shè)備,最終都要調(diào)用 input_sync()函數(shù)來(lái)標(biāo)識(shí)多點(diǎn)觸摸信息傳輸完成,告訴接收者處理之前累計(jì)的所有消息,并且準(zhǔn)備好下一次接收。Type B 和 Type A 相比最大的區(qū)別就是 Type B 可以區(qū)分出觸摸點(diǎn), 因此可以減少發(fā)送到用戶空間的數(shù)據(jù)。Type B 使用 slot 協(xié)議區(qū)分具體的觸摸點(diǎn),slot 需要用 到 ABS_MT_TRACKING_ID 消息,這個(gè) ID 需要硬件提供,或者通過(guò)原始數(shù)據(jù)計(jì)算出來(lái)。對(duì)于 TypeA 設(shè)備,內(nèi)核驅(qū)動(dòng)需要一次性將觸摸屏上所有的觸摸點(diǎn)信息全部上報(bào),每個(gè)觸摸點(diǎn)的信息在本次上報(bào)事件流中的順序不重要,因?yàn)槭录倪^(guò)濾和手指(觸摸點(diǎn))跟蹤是在內(nèi)核空間處理的。 Type B 設(shè)備驅(qū)動(dòng)需要給每個(gè)識(shí)別出來(lái)的觸摸點(diǎn)分配一個(gè) slot,后面使用這個(gè) slot 來(lái)上報(bào)觸摸點(diǎn)信息。可以通過(guò) slot 的 ABS_MT_TRACKING_ID 來(lái)新增、替換或刪除觸摸點(diǎn)。一個(gè)非負(fù)數(shù)的 ID 表示一個(gè)有效的觸摸點(diǎn),-1 這個(gè) ID 表示未使用 slot。一個(gè)以前不存在的 ID 表示這是一個(gè)新加的觸摸點(diǎn),一個(gè) ID 如果再也不存在了就表示刪除了。 有些設(shè)備識(shí)別或追蹤的觸摸點(diǎn)信息要比他上報(bào)的多,這些設(shè)備驅(qū)動(dòng)應(yīng)該給硬件上報(bào)的每個(gè)觸摸點(diǎn)分配一個(gè) Type B 的 slot。一旦檢測(cè)到某一個(gè) slot 關(guān)聯(lián)的觸摸點(diǎn) ID 發(fā)生了變化,驅(qū)動(dòng)就應(yīng)該改變這個(gè) slot 的ABS_MT_TRACKING_ID,使這個(gè) slot 失效。如果硬件設(shè)備追蹤到了比他正在上報(bào)的還要多的觸摸點(diǎn),那么驅(qū)動(dòng)程序應(yīng)該發(fā)送 BTN_TOOL_*TAP 消息,并且調(diào)用 input_mt_report_pointer_emulation()函數(shù),將此函數(shù)的第二個(gè)參數(shù) use_count 設(shè)置為 false。 54.1.2 Type A 觸摸點(diǎn)信息上報(bào)時(shí)序 對(duì)于 Type A 類型的設(shè)備,發(fā)送觸摸點(diǎn)信息的時(shí)序如下所示,這里以 2 個(gè)觸摸點(diǎn)為例: 1 ABS_MT_POSITION_X x[0] 2 ABS_MT_POSITION_Y y[0] 3 SYN_MT_REPORT 4 ABS_MT_POSITION_X x[1] 5 ABS_MT_POSITION_Y y[1] 6 SYN_MT_REPORT 7 SYN_REPORT 第 1 行,通過(guò) ABS_MT_POSITION_X 事件上報(bào)第一個(gè)觸摸點(diǎn)的 X 坐標(biāo)數(shù)據(jù),通過(guò) input_report_abs 函數(shù)實(shí)現(xiàn),下面同理。 第 2 行,通過(guò) ABS_MT_POSITION_Y 事件上報(bào)第一個(gè)觸摸點(diǎn)的 Y 坐標(biāo)數(shù)據(jù)。 第 3 行,上報(bào) SYN_MT_REPORT 事件,通過(guò)調(diào)用 input_mt_sync 函數(shù)來(lái)實(shí)現(xiàn)。 第 4 行,通過(guò) ABS_MT_POSITION_X 事件上報(bào)第二個(gè)觸摸點(diǎn)的 X 坐標(biāo)數(shù)據(jù)。 第 5 行,通過(guò) ABS_MT_POSITION_Y 事件上報(bào)第二個(gè)觸摸點(diǎn)的 Y 坐標(biāo)數(shù)據(jù)。 第 6 行,上報(bào) SYN_MT_REPORT 事件,通過(guò)調(diào)用 input_mt_sync 函數(shù)來(lái)實(shí)現(xiàn)。 第 7 行,上報(bào) SYN_REPORT 事件,通過(guò)調(diào)用 input_sync 函數(shù)實(shí)現(xiàn)。 我們?cè)诰帉?TypeA 類型的多點(diǎn)觸摸驅(qū)動(dòng)的時(shí)候就需要按照上述代碼中的時(shí)序上報(bào)坐標(biāo)信息。Linux 內(nèi)核 里 面 也 有 Type A 類 型 的 多 點(diǎn) 觸 摸 驅(qū) 動(dòng) , 找 到 st2332.c 這 個(gè) 驅(qū) 動(dòng) 文 件 , 路 徑 為drivers/input/touchscreen/st1232.c,找到 st1232_ts_irq_handler 函數(shù),此函數(shù)里面就是上報(bào)觸摸點(diǎn)坐標(biāo)信息的。 103 static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id) 104 { ...... 111 ret = st1232_ts_read_data(ts); 112 if (ret < 0) 113 goto end; 114 115 /* multi touch protocol */ 116 for (i = 0; i < MAX_FINGERS; i++) { 117 if (!finger.is_valid) 118 continue; 119 120 input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger.t); 121 input_report_abs(input_dev, ABS_MT_POSITION_X, finger.x); 122 input_report_abs(input_dev, ABS_MT_POSITION_Y, finger.y); 123 input_mt_sync(input_dev); 124 count++; 125 } ...... 140 141 /* SYN_REPORT */ 142 input_sync(input_dev); 143 144 end: 145 return IRQ_HANDLED; 146 } 第 111 行,獲取所有觸摸點(diǎn)信息。 第 116~125 行,按照 Type A 類型輪流上報(bào)所有的觸摸點(diǎn)坐標(biāo)信息,第 121 和 122 行分別上報(bào)觸摸點(diǎn)的(X,Y)軸坐標(biāo),也就是 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 事件。每上報(bào)完一個(gè)觸摸點(diǎn)坐標(biāo),都要在第 123 行調(diào)用 input_mt_sync 函數(shù)上報(bào)一個(gè) SYN_MT_REPORT 信息。 第 142 行,每上報(bào)完一輪觸摸點(diǎn)信息就調(diào)用一次 input_sync 函數(shù),也就是發(fā)送一個(gè) SYN_REPORT 事件 54.1.3 Type B 觸摸點(diǎn)信息上報(bào)時(shí)序 對(duì)于 Type B 類型的設(shè)備,發(fā)送觸摸點(diǎn)信息的時(shí)序如下所示,這里以 2 個(gè)觸摸點(diǎn)為例: 1 ABS_MT_SLOT 0 2 ABS_MT_TRACKING_ID 45 3 ABS_MT_POSITION_X x[0] 4 ABS_MT_POSITION_Y y[0] 5 ABS_MT_SLOT 1 6 ABS_MT_TRACKING_ID 46 7 ABS_MT_POSITION_X x[1] 8 ABS_MT_POSITION_Y y[1] 9 SYN_REPORT 第 1 行,上報(bào) ABS_MT_SLOT 事件,也就是觸摸點(diǎn)對(duì)應(yīng)的 SLOT。每次上報(bào)一個(gè)觸摸點(diǎn)坐標(biāo)之前要先使用 input_mt_slot 函數(shù)上報(bào)當(dāng)前觸摸點(diǎn) SLOT,觸摸點(diǎn)的 SLOT 其實(shí)就是觸摸點(diǎn) ID,需要由觸摸 IC 提供。 第 2 行,根據(jù) Type B 的要求,每個(gè) SLOT 必須關(guān)聯(lián)一個(gè) ABS_MT_TRACKING_ID,通過(guò)修改 SLOT 關(guān)聯(lián) 的 ABS_MT_TRACKING_ID 來(lái) 完 成 對(duì) 觸 摸 點(diǎn) 的 添 加 、 替 換 或 刪 除 。 具 體 用 到 的 函 數(shù) 就 是input_mt_report_slot_state,如果是添加一個(gè)新的觸摸點(diǎn),那么此函數(shù)的第三個(gè)參數(shù) active 要設(shè)置為 true,linux 內(nèi)核會(huì)自動(dòng)分配一個(gè) ABS_MT_TRACKING_ID 值,不需要用戶去指定具體的 ABS_MT_TRACKING_ID 值。 第 3 行,上報(bào)觸摸點(diǎn) 0 的 X 軸坐標(biāo),使用函數(shù) input_report_abs 來(lái)完成。 第 4 行,上報(bào)觸摸點(diǎn) 0 的 Y 軸坐標(biāo),使用函數(shù) input_report_abs 來(lái)完成。 第 5~8 行,和第 1~4 行類似,只是換成了上報(bào)觸摸點(diǎn) 0 的(X,Y)坐標(biāo)信息 第 9 行,當(dāng)所有的觸摸點(diǎn)坐標(biāo)都上傳完畢以后就得發(fā)送 SYN_REPORT 事件,使用 input_sync 函數(shù)來(lái)完成。 當(dāng)一個(gè)觸摸點(diǎn)移除以后,同樣需要通過(guò) SLOT 關(guān)聯(lián)的 ABS_MT_TRACKING_ID 來(lái)處理,時(shí)序如下所示: 1 ABS_MT_TRACKING_ID -1 2 SYN_REPORT 第 1 行,當(dāng)一個(gè)觸摸點(diǎn)(SLOT)移除以后,需要通過(guò) ABS_MT_TRACKING_ID 事件發(fā)送一個(gè)-1 給內(nèi)核。方法很簡(jiǎn)單,同樣使用 input_mt_report_slot_state 函數(shù)來(lái)完成,只需要將此函數(shù)的第三個(gè)參數(shù) active 設(shè)置為false 即可,不需要用戶手動(dòng)去設(shè)置-1。 第 2 行,當(dāng)所有的觸摸點(diǎn)坐標(biāo)都上傳完畢以后就得發(fā)送 SYN_REPORT 事件。 當(dāng)要編寫 Type B 類型的多點(diǎn)觸摸驅(qū)動(dòng)的時(shí)候就需要按照上述代碼中的時(shí)序上報(bào)坐標(biāo)信息。Linux 內(nèi)核里面有大量的 Type B 類型的多點(diǎn)觸摸驅(qū)動(dòng)程序,我們可以參考這些現(xiàn)成的驅(qū)動(dòng)程序來(lái)編寫自己的驅(qū)動(dòng)代碼。這里就以 ili210x 這個(gè)觸摸驅(qū)動(dòng) IC 為例,看看是 Type B 類型是如何上報(bào)觸摸點(diǎn)坐標(biāo)信息的。找到ili210x.c 這個(gè)驅(qū)動(dòng)文件,路徑 為 drivers/input/touchscreen/ili210x.c,找到 ili210x_report_events 函數(shù),此函數(shù)就是用于上報(bào) ili210x 觸摸坐標(biāo)信息的,函數(shù)內(nèi)容如下所示: 78 static void ili210x_report_events(struct input_dev *input, 79 const struct touchdata *touchdata) 80 { 81 int i; 82 bool touch; 83 unsigned int x, y; 84 const struct finger *finger; 85 86 for (i = 0; i < MAX_TOUCHES; i++) { 87 input_mt_slot(input, i); 88 89 finger = &touchdata->finger; 90 91 touch = touchdata->status & (1 << i); 92 input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); 93 if (touch) { 94 x = finger->x_low | (finger->x_high << 8); 95 y = finger->y_low | (finger->y_high << 8); 96 97 input_report_abs(input, ABS_MT_POSITION_X, x); 98 input_report_abs(input, ABS_MT_POSITION_Y, y); 99 } 100 } 101 102 input_mt_report_pointer_emulation(input, false); 103 input_sync(input); 104 } 第 86~100 行,使用 for 循環(huán)實(shí)現(xiàn)上報(bào)所有的觸摸點(diǎn)坐標(biāo),第 87 行調(diào)用 input_mt_slot 函數(shù)上報(bào)ABS_MT_SLOT 事件。第 92 行調(diào)用 input_mt_report_slot_state 函數(shù)上報(bào) ABS_MT_TRACKING_ID 事件,也就是給 SLOT 關(guān)聯(lián)一個(gè) ABS_MT_TRACKING_ID。第 97 和 98 行使用 input_report_abs 函數(shù)上報(bào)觸摸點(diǎn)對(duì)應(yīng)的(X,Y)坐標(biāo)值。 第 103 行,使用 input_sync 函數(shù)上報(bào) SYN_REPORT 事件。 54.1.4 MT 其他事件的使用 在 54.1.1 小節(jié)中給出了 Linux 所支持的所有 ABS_MT 事件,大家可以根據(jù)實(shí)際需求將這些事件組成各種事件組合。最簡(jiǎn)單的組合就是 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y,可以通過(guò)在這兩個(gè)事件上報(bào)觸摸點(diǎn),如果設(shè)備支持的話,還可以使用 ABS_MT_TOUCH_MAJOR 和 ABS_MT_WIDTH_MAJOR 這兩個(gè)消息 上 報(bào) 觸 摸 面 積 信 息 , 關(guān) 于 其 他 ABS_MT 事 件 的 具 體 含 義 大 家 可 以 查 看 Linux 內(nèi) 核 中 的multi-touch-protocol.txt 文檔,這里我們重點(diǎn)補(bǔ)充一下 ABS_MT_TOOL_TYPE 事件。 ABS_MT_TOOL_TYPE 事件用于上報(bào)觸摸工具類型,很多內(nèi)核驅(qū)動(dòng)都不能區(qū)分出觸摸設(shè)備類型,是手指還是觸摸筆?這種情況下,這個(gè)事件可以忽略掉。目前的協(xié)議支持 MT_TOOL_FINGER(手指)、MT_TOOL_PEN(筆)和 MT_TOOL_PALM(手掌)這三種觸摸設(shè)備類,于 Type B 類 型 ,此事件由 input 子系統(tǒng)內(nèi)核處理。如果驅(qū)動(dòng)程序需要上報(bào) ABS_MT_TOOL_TYPE 事件,那么可以使用 input_mt_report_slot_state 函數(shù)來(lái)完成此工作。 關(guān)于 Linux 系統(tǒng)下的多點(diǎn)觸摸(MT)協(xié)議就講解到這里,簡(jiǎn)單總結(jié)一下,MT 協(xié)議隸屬于 linux 的 input子系統(tǒng),驅(qū)動(dòng)通過(guò)大量的 ABS_MT 事件向 linux 內(nèi)核上報(bào)多點(diǎn)觸摸坐標(biāo)數(shù)據(jù)。根據(jù)觸摸 IC 的不同,分為TypeA 和 Type B 兩種類型,不同的類型其上報(bào)時(shí)序不同,目前使用最多的是 Type B 類型。 54.1.5 多點(diǎn)觸摸使用到的 API 函數(shù) 根據(jù)前面的講解,我們知道 linux 下的多點(diǎn)觸摸協(xié)議其實(shí)就是通過(guò)不同的事件來(lái)上報(bào)觸摸點(diǎn)坐標(biāo)信息,這些事件都是通過(guò) Linux 內(nèi)核提供的對(duì)應(yīng) API 函數(shù)實(shí)現(xiàn)的,本小節(jié)我們來(lái)看一下一些常見(jiàn)的 API 函數(shù)。 1 、input_mt_init_slots 函數(shù) input_mt_init_slots 函數(shù)用于初始化 MT 的輸入 slots,編寫 MT 驅(qū)動(dòng)的時(shí)候必須先調(diào)用此函數(shù)初始化slots,此函數(shù)定義在文件 drivers/input/input-mt.c 中,函數(shù)原型如下所示: int input_mt_init_slots( struct input_dev *dev, unsigned int num_slots, unsigned int flags) 函數(shù)參數(shù)和返回值含義如下: dev: MT 設(shè)備對(duì)應(yīng)的 input_dev,因?yàn)?MT 設(shè)備隸屬于 input_dev。 num_slots:設(shè)備要使用的 SLOT 數(shù)量,也就是觸摸點(diǎn)的數(shù)量。 flags:其他一些 flags 信息,可設(shè)置的 flags 如下所示: #define INPUT_MT_POINTER 0x0001 /* pointer device, e.g. trackpad */ #define INPUT_MT_DIRECT 0x0002 /* direct device, e.g. touchscreen */ #define INPUT_MT_DROP_UNUSED0x0004 /* drop contacts not seen in frame */ #define INPUT_MT_TRACK 0x0008 /* use in-kernel tracking */ #define INPUT_MT_SEMI_MT 0x0010 /* semi-mt device, finger count handled manually */ 可以采用‘|’運(yùn)算來(lái)同時(shí)設(shè)置多個(gè) flags 標(biāo)識(shí)。 返回值:0,成功;負(fù)值,失敗。 2 、input_mt_slot 函數(shù) 此函數(shù)用于 Type B 類型,此函數(shù)用于產(chǎn)生 ABS_MT_SLOT 事件,告訴內(nèi)核當(dāng)前上報(bào)的是哪個(gè)觸摸點(diǎn)的坐標(biāo)數(shù)據(jù),此函數(shù)定義在文件 include/linux/input/mt.h 中,函數(shù)原型如下所示: void input_mt_slot(struct input_dev *dev, int slot) 函數(shù)參數(shù)和返回值含義如下: dev: MT 設(shè)備對(duì)應(yīng)的 input_dev。 slot:當(dāng)前發(fā)送的是哪個(gè) slot 的坐標(biāo)信息,也就是哪個(gè)觸摸點(diǎn)。 返回值:無(wú)。 3 、input_mt_report_slot_state 函數(shù) 此 函 數(shù) 用 于 Type B 類 型 , 用 于 產(chǎn) 生 ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE 事 件 ,ABS_MT_TRACKING_ID 事件給 slot 關(guān)聯(lián)一個(gè) ABS_MT_TRACKING_ID , ABS_MT_TOOL_TYPE 事件指定觸摸類 型(是筆還是手指等)。此函數(shù)定義在文件 drivers/input/input-mt.c 中,此函數(shù)原型如下所示: void input_mt_report_slot_state( struct input_dev *dev, unsigned int tool_type, bool active) 函數(shù)參數(shù)和返回值含義如下: dev: MT 設(shè)備對(duì)應(yīng)的 input_dev。 tool_type:觸摸類型,可以選擇 MT_TOOL_FINGER(手指)、MT_TOOL_PEN(筆)或MT_TOOL_PALM(手掌),對(duì)于多點(diǎn)電容觸摸屏來(lái)說(shuō)一般都是手指。 active:true,連續(xù)觸摸,input 子系統(tǒng)內(nèi)核會(huì)自動(dòng)分配一個(gè) ABS_MT_TRACKING_ID 給 slot。 false,觸摸點(diǎn)抬起,表示某個(gè)觸摸點(diǎn)無(wú)效了,input 子系統(tǒng)內(nèi)核會(huì)分配一個(gè)-1 給 slot,表示觸摸點(diǎn)溢出。 返回值:無(wú)。 4 、input_report_abs 函數(shù) TypeA 和 Type B 類 型 都 使 用 此 函 數(shù) 上 報(bào) 觸 摸 點(diǎn) 坐 標(biāo) 信 息 , 通 過(guò) ABS_MT_POSITION_X 和ABS_MT_POSITION_Y 事件實(shí)現(xiàn) X 和 Y 軸坐標(biāo)信息上報(bào)。此函數(shù)定義在文件 include/linux/input.h 中,函數(shù) 原型如下所示: void input_report_abs( struct input_dev *dev, unsigned int code, int value) 函數(shù)參數(shù)和返回值含義如下: dev: MT 設(shè)備對(duì)應(yīng)的 input_dev。 code:要上報(bào)的是什么數(shù)據(jù),可以設(shè)置為 ABS_MT_POSITION_X 或 ABS_MT_POSITION_Y,也就是 X 軸或者 Y 軸坐標(biāo)數(shù)據(jù)。 value:具體的 X 軸或 Y 軸坐標(biāo)數(shù)據(jù)值。 返回值:無(wú)。 5 、input_mt_report_pointer_emulation 函數(shù) 如果追蹤到的觸摸點(diǎn)數(shù)量多于當(dāng)前上報(bào)的數(shù)量,驅(qū)動(dòng)程序使用 BTN_TOOL_TAP 事件來(lái)通知用戶空間當(dāng)前追蹤到的觸摸點(diǎn)總數(shù)量,然后調(diào)用 input_mt_report_pointer_emulation 函數(shù)將 use_count 參數(shù)設(shè)置為false。否則的話將 use_count 參數(shù)設(shè)置為 true,表示當(dāng)前的觸摸點(diǎn)數(shù)量(此函數(shù)會(huì)獲取到具體的觸摸點(diǎn)數(shù)量,不需要用戶給出),此函數(shù)定義在文件 drivers/input/input-mt.c 中,函數(shù)原型如下: void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count) 函數(shù)參數(shù)和返回值含義如下: dev: MT 設(shè)備對(duì)應(yīng)的 input_dev。 use_count:true,有效的觸摸點(diǎn)數(shù)量;false,追蹤到的觸摸點(diǎn)數(shù)量多于當(dāng)前上報(bào)的數(shù)量。 返回值:無(wú)。 54.1.6 多點(diǎn)電容觸摸驅(qū)動(dòng)框架 前面幾小節(jié)已經(jīng)詳細(xì)的講解了 linux 下多點(diǎn)觸摸屏驅(qū)動(dòng)原理,本小節(jié)我們來(lái)梳理一下 linux 下多點(diǎn)電容觸摸驅(qū)動(dòng)的編寫框架和步驟。首先確定驅(qū)動(dòng)需要用到哪些知識(shí)點(diǎn),哪些框架?根據(jù)前面的分析,我們?cè)诰帉戲?qū)動(dòng)的時(shí)候需要注意一下幾點(diǎn): ① 多點(diǎn)電容觸摸芯片的接口,一般都為 I2C 接口,因此驅(qū)動(dòng)主框架肯定是 I2C。 ② linux 里面一般都是通過(guò)中斷來(lái)上報(bào)觸摸點(diǎn)坐標(biāo)信息,因此需要用到中斷框架。 ③ 多點(diǎn)電容觸摸屬于 input 子系統(tǒng),因此還要用到 input 子系統(tǒng)框架。 ④ 在中斷處理程序中按照 linux 的 MT 協(xié)議上報(bào)坐標(biāo)信息。 根據(jù)上面的分析,多點(diǎn)電容觸摸驅(qū)動(dòng)編寫框架以及步驟如下: 1 、I2C 驅(qū)動(dòng)框架 驅(qū)動(dòng)總體采用 I2C 框架,參考框架代碼如下所示: 1 /* 設(shè)備樹匹配表 */ 2 static const struct i2c_device_id xxx_ts_id[] = { 3 { "xxx", 0, }, 4 { /* sentinel */ } 5 }; 6 7 /* 設(shè)備樹匹配表 */ 8 static const struct of_device_id xxx_of_match[] = { 9 { .compatible = "xxx", }, 10 { /* sentinel */ } 11 }; 12 13 /* i2c 驅(qū)動(dòng)結(jié)構(gòu)體 */ 14 static struct i2c_driver ft5x06_ts_driver = { 15 .driver = { 16 .owner = THIS_MODULE, 17 .name = "edt_ft5x06", 18 .of_match_table = of_match_ptr(xxx_of_match), 19 }, 20 .id_table = xxx_ts_id, 21 .probe = xxx_ts_probe, 22 .remove = xxx_ts_remove, 23 }; 24 25 /* 26 * @description : 驅(qū)動(dòng)入口函數(shù) 27 * @param : 無(wú) 28 * @return : 無(wú) 29 */ 30 static int __init xxx_init(void) 31 { 32 int ret = 0; 33 34 ret = i2c_add_driver(&xxx_ts_driver); 35 36 return ret; 37 } 38 39 /* 40 * @description : 驅(qū)動(dòng)出口函數(shù) 41 * @param : 無(wú) 42 * @return : 無(wú) 43 */ 44 static void __exit xxx_exit(void) 45 { 46 i2c_del_driver(&ft5x06_ts_driver); 47 } 48 49 module_init(xxx_init); 50 module_exit(xxx_exit); 51 MODULE_LICENSE("GPL"); 52 MODULE_AUTHOR("topeet"); 當(dāng)設(shè)備樹中觸摸 IC 的設(shè)備節(jié)點(diǎn)和驅(qū)動(dòng)匹配以后,第 21 行的 xxx_ts_probe 函數(shù)就會(huì)執(zhí)行,我們可以在此函數(shù)中初始化觸摸 IC,中斷和 input 子系統(tǒng)等。 2摸 、初始化觸摸 IC和 、中斷和 input 子系統(tǒng) 初始化操作都是在 xxx_ts_probe 函數(shù)中完成,參考框架如下所示: 1 static int xxx_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) 2 { 3 struct input_dev *input; 4 5 /* 1、初始化 I2C */ 6 ...... 7 8 /* 2,申請(qǐng)中斷, */ 9 devm_request_threaded_irq(&client->dev, client->irq, NULL, 10 xxx_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 11 client->name, &xxx); 12 ...... 13 14 /* 3,input 設(shè)備申請(qǐng)與初始化 */ 15 input = devm_input_allocate_device(&client->dev); 16 17 input->name = client->name; 18 input->id.bustype = BUS_I2C; 19 input->dev.parent = &client->dev; 20 ...... 21 22 /* 4,初始化 input 和 MT */ 23 __set_bit(EV_ABS, input->evbit); 24 __set_bit(BTN_TOUCH, input->keybit); 25 26 input_set_abs_params(input, ABS_X, 0, width, 0, 0); 27 input_set_abs_params(input, ABS_Y, 0, height, 0, 0); 28 input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0); 29 input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0); 30 input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0); 31 ...... 32 33 /* 5,注冊(cè) input_dev */ 34 input_register_device(input); 35 ...... 36 } 第 5~7 行,首先肯定是初始化觸摸芯片,包括芯片的相關(guān) IO,比如復(fù)位、中斷等 IO 引腳,然后就是芯片本身的初始化,也就是配置觸摸芯片的相關(guān)寄存器。 第 9 行,因?yàn)橐话阌|摸芯片都是通過(guò)中斷來(lái)向系統(tǒng)上報(bào)觸摸點(diǎn)坐標(biāo)信息的,因此我們需要初始化中斷。大家可能會(huì)發(fā)現(xiàn)第 9 行并沒(méi)有使用 request_irq 函數(shù)申請(qǐng)中斷,而是采用了 devm_request_threaded_irq 這個(gè)函數(shù),為什么使用這個(gè)函數(shù)呢?是不是 request_irq 函數(shù)不能使用?答案肯定不是的,這里用 request_irq函數(shù)是絕對(duì)沒(méi)問(wèn)題的。那為何要用 devm_request_threaded_irq 呢?這里我們就簡(jiǎn)單的介紹一下這個(gè) API 函數(shù),devm_request_threaded_irq 函數(shù)特點(diǎn)如下: ① 用于申請(qǐng)中斷,作用和 request_irq 函數(shù)類似。 ② 此函數(shù)的作用是中斷線程化,大家如果直接在網(wǎng)上搜索“devm_request_threaded_irq”會(huì)發(fā)現(xiàn)相關(guān)解釋很少。但是大家去搜索 request_threaded_irq 函數(shù)就會(huì)有很多講解的博客和帖子,這兩個(gè)函數(shù)在名字上的差別就是前者比后者多了個(gè)“devm_”前綴,“devm_”前綴稍后講解。大家應(yīng)該注意到了 “request_threaded_irq”相比“request_irq”多了個(gè) threaded 函數(shù),也就是線程的意思。那么為什么要中斷線程化呢?我們都是知道硬件中斷具有最高優(yōu)先級(jí),不論什么時(shí)候只要硬件中斷發(fā)生,那么內(nèi)核都會(huì)終止當(dāng)前正在執(zhí)行的操作,轉(zhuǎn)而去執(zhí)行中斷處理程序(不考慮關(guān)閉中斷和中斷優(yōu)先級(jí)的情況),如果中斷非常頻 繁的話那么內(nèi)核將會(huì)頻繁的執(zhí)行中斷處理程序,導(dǎo)致任務(wù)得不到及時(shí)的處理。中斷線程化以后中斷將作為內(nèi)核線程運(yùn)行,而且也可以被賦予不同的優(yōu)先級(jí),任務(wù)的優(yōu)先級(jí)可能比中斷線程的優(yōu)先級(jí)高,這樣做的目的就是保證高優(yōu)先級(jí)的任務(wù)能被優(yōu)先處理。大家可能會(huì)疑問(wèn),前面不是說(shuō)可以將比較耗時(shí)的中斷放到下半 部(bottom half)處理嗎?雖然下半部可以被延遲處理,但是依舊先于線程執(zhí)行,中斷線程化可以讓這些比較耗時(shí)的下半部與進(jìn)程進(jìn)行公平競(jìng)爭(zhēng)。 要注意,并不是所有的中斷都可以被線程化,重要的中斷就不能這么操作。對(duì)于觸摸屏而言只要手指放到屏幕上,它可能就會(huì)一直產(chǎn)生中斷(視具體芯片而定,F(xiàn)T5426 是這樣的),中斷處理程序里面需要通過(guò) I2C讀取觸摸信息并上報(bào)給內(nèi)核,I2C 的速度最大只有 400KHz,算是低速外設(shè)。不斷的產(chǎn)生中斷、讀取觸摸信 息、上報(bào)信息會(huì)導(dǎo)致處理器在觸摸中斷上花費(fèi)大量的時(shí)間,但是觸摸相對(duì)來(lái)說(shuō)不是那么重要的事件,因此可以將觸摸中斷線程化。如果你覺(jué)得觸摸中斷很重要,那么就可以不將其進(jìn)行線程化處理?傊灰獙⒁粋(gè)中斷進(jìn)行線程化處理是需要自己根據(jù)實(shí)際情況去衡量的。 ③ 最后來(lái)看一下“devm_”前綴,在 linux 內(nèi)核中有很多的申請(qǐng)資源類的 API 函數(shù)都有對(duì)應(yīng)的“devm_”前綴版本。比如 devm_request_irq 和 request_irq 這兩個(gè)函數(shù),這兩個(gè)函數(shù)都是申請(qǐng)中斷的,我們使用request_irq 函數(shù)申請(qǐng)中斷的時(shí)候,如果驅(qū)動(dòng)初始化失敗的話就要調(diào)用 free_irq 函數(shù)對(duì)申請(qǐng)成功的 irq 進(jìn)行 釋放,卸載驅(qū)動(dòng)的時(shí)候也需要我們手動(dòng)調(diào)用 free_irq 來(lái)釋放 irq。假如我們的驅(qū)動(dòng)里面申請(qǐng)了很多資源,比如:gpio、irq、input_dev,那么就需要添加很多goto 語(yǔ)句對(duì)其做處理,當(dāng)這樣的標(biāo)簽多了以后代碼看起來(lái)就不整潔了!癲evm_”函數(shù)就是為了處理這種情況而誕生的,“devm_”函數(shù)最大的作用就是: 使用“devm_”前綴的函數(shù)申請(qǐng)到的資源可以由系統(tǒng)自動(dòng)釋放,不需要我們手動(dòng)處理。 如果我們使用devm_request_threaded_irq 函數(shù)來(lái)申請(qǐng)中斷,那么就不需要我們?cè)僬{(diào)用 free_irq 函數(shù)對(duì)其進(jìn)行釋放。大家可以注意一下,帶有“devm_”前綴的都是一些和設(shè)備資源管理有關(guān)的函數(shù)。關(guān)于“devm_”函數(shù)的實(shí)現(xiàn)原理這里就不做詳細(xì)的講解了,我們的重點(diǎn)在于學(xué)會(huì)如何使用這些 API 函數(shù),感興趣的可以查閱一些其他文檔或者帖子來(lái)看一下“devm_”函數(shù)的實(shí)現(xiàn)原理。 第 15 行,接下來(lái)就是申請(qǐng) input_dev,因?yàn)槎帱c(diǎn)電容觸摸屬于 input 子系統(tǒng)。這里同樣使用devm_input_allocate_device 函數(shù)來(lái)申請(qǐng) input_dev,也就是我們前面講解的 input_allocate_device 函數(shù)加“devm_”前綴版本。申請(qǐng)到 input_dev 以后還需要對(duì)其進(jìn)行初始化操作。 第 23~24 行,設(shè)置 input_dev 需要上報(bào)的事件為 EV_ABS 和 BTN_TOUCH,因?yàn)槎帱c(diǎn)電容屏的觸摸坐標(biāo)為絕對(duì)值,因此需要上報(bào) EV_ABS 事件。觸摸屏有按下和抬起之分,因此需要上報(bào) BTN_TOUCH 按鍵。 第 26~29 行,調(diào)用 input_set_abs_params 函數(shù)設(shè)置 EV_ABS 事件需要上報(bào) ABS_X、ABS_Y、ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y。單點(diǎn)觸摸需要上報(bào) ABS_X 和 ABS_Y,對(duì)于多點(diǎn)觸摸需要上報(bào) ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y。 第 30 行,調(diào)用 input_mt_init_slots 函數(shù)初始化多點(diǎn)電容觸摸的 slots。 第 34 行,調(diào)用 input_register_device 函數(shù)系統(tǒng)注冊(cè)前面申請(qǐng)到的 input_dev。 3 、上報(bào)坐標(biāo)信息 最后就是在中斷服務(wù)程序中上報(bào)讀取到的坐標(biāo)信息,根據(jù)所使用的多點(diǎn)電容觸摸設(shè)備類型選擇使用TypeA 還是 Type B 時(shí)序。由于大多數(shù)的設(shè)備都是 Type B 類型,因此這里就以 Type B 類型為例講解一下上報(bào)過(guò)程,參考驅(qū)動(dòng)框架如下所示: 1 static irqreturn_t xxx_handler(int irq, void *dev_id) 2 { 3 4 int num; /* 觸摸點(diǎn)數(shù)量 */ 5 int x[n], y[n]; /* 保存坐標(biāo)值 */ 6 7 /* 1、從觸摸芯片獲取各個(gè)觸摸點(diǎn)坐標(biāo)值 */ 8 ...... 9 10 /* 2、上報(bào)每一個(gè)觸摸點(diǎn)坐標(biāo) */ 11 for (i = 0; i < num; i++) { 12 input_mt_slot(input, id); 13 input_mt_report_slot_state(input, MT_TOOL_FINGER, true); 14 input_report_abs(input, ABS_MT_POSITION_X, x); 15 input_report_abs(input, ABS_MT_POSITION_Y, y); 16 } 17 ...... 18 19 input_sync(input); 20 ...... 21 22 return IRQ_HANDLED; 23 } 進(jìn)入中斷處理程序以后首先肯定是從觸摸 IC 里面讀取觸摸坐標(biāo)以及觸摸點(diǎn)數(shù)量,假設(shè)觸摸點(diǎn)數(shù)量保存到 num 變量,觸摸點(diǎn)坐標(biāo)存放到 x,y 數(shù)組里面。 第 11~16 行,循環(huán)上報(bào)每一個(gè)觸摸點(diǎn)坐標(biāo),一定要按照 Type B 類型的時(shí)序進(jìn)行。 第 19 行,每一輪觸摸點(diǎn)坐標(biāo)上報(bào)完畢以后就調(diào)用一次 input_sync 函數(shù)發(fā)送一個(gè) SYN_REPORT 事件。 關(guān)于多點(diǎn)電容觸摸驅(qū)動(dòng)框架就講解到這里,接下來(lái)我們就實(shí)際編寫一個(gè)多點(diǎn)電容觸摸驅(qū)動(dòng)程序。 |