文章目錄
- 一、linux下電容觸摸屏驅動架構簡介
-
- 1. 多點觸摸(MT)協定詳解
- 2. Type A觸摸點資訊上報時序
- 3. Type B觸摸點資訊上報時序
- 4. MT其他事件的使用
- 5. 多點觸摸所使用的API函數
-
- 1. input_mt_init_slots 函數
- 2. input_mt_slot 函數
- 3. input_mt_report_slot_state 函數
- 4. input_report_abs 函數
- 5. input_mt_report_pointer_emulation 函數
- 6. 多點電容觸摸驅動架構
-
- 1. I2C驅動架構
- 2. 初始化觸摸 IC 、中斷和 input 子系統
- 3. 上報坐标資訊
- 二、硬體原理圖分析
- 三、實驗程式編寫
-
- 1. 修改裝置樹
-
- 1. 添加FT5426所使用的IO
- 2. 添加FT5426節點
- 2. 編寫多點電容觸摸驅動
- 四、運作測試
-
- 1. 編譯驅動程式
- 2. 運作測試
- 3. 将驅動添加到核心中
-
- 1. 将驅動檔案放到合适的位置
- 2. 修改對應的 Makefile
- 五、tslib移植與使用
-
- 1. tslib移植
-
- 1. 擷取 tslib 源碼
- 2. 修改 tslib 源碼所屬使用者
- 3. ubuntu 工具安裝
- 4. 編譯 tslib
- 5. 配置tslib
- 2. tslib測試
- 六、使用核心自帶的驅動
- 七、 4.3 寸屏觸摸驅動實驗
一、linux下電容觸摸屏驅動架構簡介
1. 多點觸摸(MT)協定詳解
電容觸摸驅動的基本原理我們已經在裸機中進行了詳細的講解,回顧一下幾個重要的知識點:
①、電容觸摸屏是 IIC 接口的,需要觸摸 IC,以正點原子的 ATK7016 為例,其所使用的觸摸屏控制 IC 為 FT5426,是以所謂的電容觸摸驅動就是 IIC 裝置驅動。
②、觸摸 IC 提供了中斷信号引腳(INT),可以通過中斷來擷取觸摸資訊。
③、電容觸摸屏得到的是觸摸位置絕對資訊以及觸摸屏是否有按下。
④、電容觸摸屏不需要校準,當然了,這隻是理論上的,如果電容觸摸屏品質比較差,或者觸摸玻璃和 TFT 之間沒有完全對齊,那麼也是需要校準的。
根據以上幾個知識點,我們可以得出電容觸摸屏驅動其實就是以下幾種 linux 驅動架構的組合:
①、IIC 裝置驅動,因為電容觸摸 IC 基本都是 IIC 接口的,是以大架構就是 IIC 裝置驅動。
②、通過中斷引腳(INT)向 linux 核心上報觸摸資訊,是以需要用到 linux 中斷驅動架構。坐标的上報在中斷服務函數中完成。
③、觸摸屏的坐标資訊、螢幕按下和擡起資訊都屬于 linux 的 input 子系統,是以向 linux 核心上報觸摸屏坐标資訊就得使用 input 子系統。隻是,我們得按照 linux 核心規定的規則來上報坐标資訊。
經過簡單的分析,我們發現 IIC 驅動、中斷驅動、input 子系統我們都已經在前面學過了,唯獨沒學過的就是 input 子系統下的多點電容觸摸協定,這個才是我們本章學習的重點。linux核心中有一份文檔詳細的講解了多點電容觸摸屏協定,文檔路徑為:Documentation/input/multi-touch-protocol.txt。
老版本的 linux 核心是不支援多點電容觸摸的(Multi-touch,簡稱 MT),MT 協定是後面加入的,是以如果使用 2.x 版本 linux 核心的話可能找不到 MT 協定。MT 協定被分為兩種類型,TypeA 和 TypeB,這兩種類型的差別如下:Type A:适用于觸摸點不能被區分或者追蹤,此類型的裝置上報原始資料(此類型在實際使用中非常少!)。
Type B:适用于有硬體追蹤并能區分觸摸點的觸摸裝置,此類型裝置通過 slot 更新某一個觸摸點的資訊,FT5426 就屬于此類型,一般的多點電容觸摸屏 IC 都有此能力。
觸摸點的資訊通過一系列的 ABS_MT 事件(有的資料也叫消息)上報給 linux 核心,隻有ABS_MT 事件是用于多點觸摸的,ABS_MT 事件定義在檔案 include/uapi/linux/input.h 中,相關事件如下所示:
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 用來上報觸摸點的(X,Y)坐标資訊,ABS_MT_SLOT 用 來 上 報 觸 摸 點 ID ,
對 于 Type B 類 型 的 設 備 , 需 要 用 到ABS_MT_TRACKING_ID 事件來區分觸摸點。
對于 Type A 類型的裝置,通過 input_mt_sync()函數來隔離不同的觸摸點資料資訊,此函數原型如下所示:
此函數隻要一個參數,類型為 input_dev,用于指定具體的 input_dev 裝置。input_mt_sync()函數會觸發 SYN_MT_REPORT 事件,此事件會通知接收者擷取目前觸摸資料,并且準備接收下一個觸摸點資料。
對于 Type B 類型的裝置,上報觸摸點資訊的時候需要通過input_mt_slot()函數區分是哪一個觸摸點,input_mt_slot()函數原型如下所示:
此函數有兩個參數,第一個參數是 input_dev 裝置,第二個參數 slot 用于指定目前上報的是哪個觸摸點資訊。input_mt_slot()函數會觸發 ABS_MT_SLOT 事件,此事件會告訴接收者目前正在更新的是哪個觸摸點(slot)的資料。
不管是哪個類型的裝置,最終都要調用 input_sync()函數來辨別多點觸摸資訊傳輸完成,告訴接收者處理之前累計的所有消息,并且準備好下一次接收。
Type B 和 Type A 相比最大的差別就是 Type B 可以區分出觸摸點, 是以可以減少發送到使用者空間的資料。Type B 使用 slot 協定區分具體的觸摸點,slot 需要用到 ABS_MT_TRACKING_ID 消息,這個 ID 需要硬體提供,或者通過原始資料計算出來。對于 Type A 裝置,核心驅動需要一次性将觸摸屏上所有的觸摸點資訊全部上報,每個觸摸點的資訊在本次上報事件流中的順序不重要,因為事件的過濾和手指(觸摸點)跟蹤是在核心空間處理的。
Type B 裝置驅動需要給每個識别出來的觸摸點配置設定一個 slot,後面使用這個 slot 來上報觸摸點資訊。可以通過 slot 的ABS_MT_TRACKING_ID 來新增、替換或删除觸摸點。一個非負數的 ID 表示一個有效的觸摸點,-1 這個 ID 表示未使用 slot。一個以前不存在的 ID 表示這是一個新加的觸摸點,一個 ID 如果再也不存在了就表示删除了。有些裝置識别或追蹤的觸摸點資訊要比他上報的多,這些裝置驅動應該給硬體上報的每個觸摸點配置設定一個 Type B 的 slot。一旦檢測到某一個 slot 關聯的觸摸點 ID 發生了變化,驅動就應該改變這個 slot 的 ABS_MT_TRACKING_ID,使這個 slot 失效。如果硬體裝置追蹤到了比他正在上報的還要多的觸摸點,那麼驅動程式應該發送 BTN_TOOL_*TAP 消息,并且調用input_mt_report_pointer_emulation()函數,将此函數的第二個參數 use_count 設定為 false。
2. Type A觸摸點資訊上報時序
對于 Type A 類型的裝置,發送觸摸點資訊的時序如下所示,這裡以 2 個觸摸點為例:
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 行,通過 ABS_MT_POSITION_X 事件上報第一個觸摸點的 X 坐标資料,通過input_report_abs 函數實作,下面同理。
第 2 行,通過 ABS_MT_POSITION_Y 事件上報第一個觸摸點的 Y 坐标資料。
第 3 行,上報 SYN_MT_REPORT 事件,通過調用 input_mt_sync 函數來實作。
第 4 行,通過 ABS_MT_POSITION_X 事件上報第二個觸摸點的 X 坐标資料。
第 5 行,通過 ABS_MT_POSITION_Y 事件上報第二個觸摸點的 Y 坐标資料。
第 6 行,上報 SYN_MT_REPORT 事件,通過調用 input_mt_sync 函數來實作。
第 7 行,上報 SYN_REPORT 事件,通過調用 input_sync 函數實作。
我們在編寫 Type A 類型的多點觸摸驅動的時候就需要按照示例代碼中的時序上報坐标資訊。
Linux 核心裡面也有 Type A 類型的多點觸摸驅動,找到 st2332.c 這個驅動檔案,路徑為 drivers/input/touchscreen/st1232.c,找到 st1232_ts_irq_handler 函數,此函數裡面就是上報觸摸點坐标資訊的。
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[i].is_valid)
118 continue;
119
120 input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t);
121 input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x);
122 input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].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 行,擷取所有觸摸點資訊。
第 116~125 行,按照 Type A 類型輪流上報所有的觸摸點坐标資訊,第 121 和 122 行分别上報觸摸點的(X,Y)軸坐标,也就是 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 事件。每上報完一個觸摸點坐标,都要在第 123 行調用 input_mt_sync 函數上報一個 SYN_MT_REPORT資訊。
第 142 行,每上報完一輪觸摸點資訊就調用一次 input_sync 函數,也就是發送一個SYN_REPORT 事件
3. Type B觸摸點資訊上報時序
對于 Type B 類型的裝置,發送觸摸點資訊的時序如下所示,這裡以 2 個觸摸點為例
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 行,上報 ABS_MT_SLOT 事件,也就是觸摸點對應的 SLOT。每次上報一個觸摸點坐标之前要先使用input_mt_slot函數上報目前觸摸點SLOT,觸摸點的SLOT其實就是觸摸點ID,需要由觸摸 IC 提供。
第 2 行,根據 Type B 的要求,每個 SLOT 必須關聯一個 ABS_MT_TRACKING_ID,通過修改 SLOT 關聯的 ABS_MT_TRACKING_ID 來完成對觸摸點的添加、替換或删除。具體用到的函數就是 input_mt_report_slot_state,如果是添加一個新的觸摸點,那麼此函數的第三個參數active 要設定為 true,linux 核心會自動配置設定一個 ABS_MT_TRACKING_ID 值,不需要使用者去指定具體的 ABS_MT_TRACKING_ID 值。
第 3 行,上報觸摸點 0 的 X 軸坐标,使用函數 input_report_abs 來完成。
第 4 行,上報觸摸點 0 的 Y 軸坐标,使用函數 input_report_abs 來完成。
第 5~8 行,和第 1~4 行類似,隻是換成了上報觸摸點 0 的(X,Y)坐标資訊
第9行,當所有的觸摸點坐标都上傳完畢以後就得發送SYN_REPORT事件,使用input_sync函數來完成。
當一個觸摸點移除以後,同樣需要通過 SLOT 關聯的ABS_MT_TRACKING_ID 來處理,時序如下所示:
1 ABS_MT_TRACKING_ID -1
2 SYN_REPORT
第 1 行,當一個觸摸點(SLOT)移除以後,需要通過 ABS_MT_TRACKING_ID 事件發送一個-1 給核心。方法很簡單,同樣使用 input_mt_report_slot_state 函數來完成,隻需要将此函數的第三個參數 active 設定為 false 即可,不需要使用者手動去設定-1。
第 2 行,當所有的觸摸點坐标都上傳完畢以後就得發送 SYN_REPORT 事件。當要編寫 Type B 類型的多點觸摸驅動的時候就需要按照示例代碼中的時序上報坐标資訊。
Linux 核心裡面有大量的 Type B 類型的多點觸摸驅動程式,我們可以參考這些現成的驅動程式來編寫自己的驅動代碼。這裡就以 ili210x 這個觸摸驅動 IC 為例,看看是 Type B 類型是 如 何 上 報 觸 摸 點 坐 标 信 息 的 。 找 到 ili210x.c 這 個 驅 動 文 件 , 路 徑 為drivers/input/touchscreen/ili210x.c,找到 ili210x_report_events 函數,此函數就是用于上報 ili210x觸摸坐标資訊的,函數内容如下所示:
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[i];
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 循環實作上報所有的觸摸點坐标,
第 87 行調用 input_mt_slot 函數上 報 ABS_MT_SLOT 事 件 。
第 92 行 調 用 input_mt_report_slot_state 函 數 上 報ABS_MT_TRACKING_ID 事件,也就是給 SLOT 關聯一個 ABS_MT_TRACKING_ID。
第 97 和98 行使用 input_report_abs 函數上報觸摸點對應的(X,Y)坐标值。
第 103 行,使用 input_sync 函數上報 SYN_REPORT 事件。
4. MT其他事件的使用
在示例代碼中給出了 Linux 所支援的所有 ABS_MT 事件,大家可以根據實際需求将 這 些 事 件 組 成 各 種 事 件 組 合 。 最 簡 單 的 組 合 就 是 ABS_MT_POSITION_X 和ABS_MT_POSITION_Y,可以通過在這兩個事件上報觸摸點,如果裝置支援的話,還可以使用
ABS_MT_TOUCH_MAJOR 和 ABS_MT_WIDTH_MAJOR 這兩個消息上報觸摸面積資訊,關于其他 ABS_MT 事件的具體含義大家可以檢視 Linux 核心中的 multi-touch-protocol.txt 文檔,
這裡我們重點補充一下 ABS_MT_TOOL_TYPE 事件。
ABS_MT_TOOL_TYPE 事件用于上報觸摸工具類型,很多核心驅動都不能區分出觸摸裝置類型,是手指還是觸摸筆?這種情況下,這個事件可以忽略掉。目前的協定支援MT_TOOL_FINGER(手指)、MT_TOOL_PEN(筆)和 MT_TOOL_PALM(手掌)這三種觸摸裝置類
型, 于 Type B 類型,此事件 由 input 子系統核心處理。
如果驅動程式需要上 報ABS_MT_TOOL_TYPE 事件,那麼可以使用 input_mt_report_slot_state 函數來完成此工作。
關于 Linux 系統下的多點觸摸(MT)協定就講解到這裡,簡單總結一下,MT 協定隸屬于 linux的 input 子系統,驅動通過大量的 ABS_MT 事件向 linux 核心上報多點觸摸坐标資料。根據觸摸 IC 的不同,分為 Type A 和 Type B 兩種類型,不同的類型其上報時序不同,目前使用最多的是 Type B 類型。
接下來我們就根據前面學習過的 MT 協定來編寫一個多點電容觸摸驅動程式,本章節所使用的觸摸屏是正點原子的 ATK7084(7 寸 800480)和 ATK7016(7 寸 1024600)這兩款觸摸屏,這兩款觸摸屏都使用 FT5426 這款觸摸 IC,是以驅動程式是完全通用的。
5. 多點觸摸所使用的API函數
根據前面的講解,我們知道 linux 下的多點觸摸協定其實就是通過不同的事件來上報觸摸點坐标資訊,這些事件都是通過 Linux 核心提供的對應 API 函數實作的,本小節我們來看一下一些常見的 API 函數。
1. input_mt_init_slots 函數
input_mt_init_slots 函數用于初始化 MT 的輸入 slots,編寫 MT 驅動的時候必須先調用此函數初始化 slots,此函數定義在檔案 drivers/input/input-mt.c 中,函數原型如下所示:
函數參數和傳回值含義如下:
dev : MT 裝置對應的 input_dev,因為 MT 裝置隸屬于 input_dev。
num_slots:裝置要使用的 SLOT 數量,也就是觸摸點的數量。
flags :其他一些 flags 資訊,可設定的 flags 如下所示:
#define INPUT_MT_POINTER 0x0001
可以采用‘|’運算來同時設定多個 flags 辨別。
傳回值:0,成功;負值,失敗。
2. input_mt_slot 函數
此函數用于 Type B 類型,此函數用于産生 ABS_MT_SLOT 事件,告訴核心目前上報的是哪個觸摸點的坐标資料,此函數定義在檔案 include/linux/input/mt.h 中,函數原型如下所示:
函數參數和傳回值含義如下:
dev : MT 裝置對應的 input_dev。
slot:目前發送的是哪個 slot 的坐标資訊,也就是哪個觸摸點。
傳回值:無。
3. input_mt_report_slot_state 函數
此函數用于 Type B 類型,用于産生 ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE事 件 , ABS_MT_TRACKING_ID 事 件 給 slot 關 聯 一 個 ABS_MT_TRACKING_ID ,ABS_MT_TOOL_TYPE 事件指定觸摸類型(是筆還是手指等)。此函數定義在檔案
drivers/input/input-mt.c 中,此函數原型如下所示:
函數參數和傳回值含義如下:
dev : MT 裝置對應的 input_dev。
tool_type:觸摸類型,可以選擇 MT_TOOL_FINGER(手指)、MT_TOOL_PEN(筆)或MT_TOOL_PALM(手掌),對于多點電容觸摸屏來說一般都是手指。
active:true,連續觸摸,input子系統核心會自動配置設定一個ABS_MT_TRACKING_ID 給 slot。
false,觸摸點擡起,表示某個觸摸點無效了,input 子系統核心會配置設定一個-1 給 slot,表示觸摸點溢出。
傳回值:無。
4. input_report_abs 函數
Type A 和 Type B 類型都使用此函數上報觸摸點坐标資訊,通過 ABS_MT_POSITION_X 和ABS_MT_POSITION_Y 事 件 實 現 X 和 Y 軸 坐 标 信 息 上 報 。 此 函 數 定 義 在 文 件include/linux/input.h 中,函數原型如下所示:
函數參數和傳回值含義如下:
dev : MT 裝置對應的 input_dev。
code:要上報的是什麼資料,可以設定為 ABS_MT_POSITION_X 或 ABS_MT_POSITION_Y,也就是 X 軸或者 Y 軸坐标資料。
value:具體的 X 軸或 Y 軸坐标資料值。
傳回值:無。
5. input_mt_report_pointer_emulation 函數
如果追蹤到的觸摸點數量多于目前上報的數量,驅動程式使用 BTN_TOOL_TAP 事件來通知使用者空間目前追蹤到的觸摸點總數量,然後調用 input_mt_report_pointer_emulation 函數将use_count 參數設定為 false。否則的話将 use_count 參數設定為 true,表示目前的觸摸點數量(此函數會擷取到具體的觸摸點數量,不需要使用者給出),此函數定義在檔案 drivers/input/input-mt.c中,函數原型如下:
函數參數和傳回值含義如下:
dev : MT 裝置對應的 input_dev。
use_count:true,有效的觸摸點數量;false,追蹤到的觸摸點數量多于目前上報的數量。
傳回值:無。
6. 多點電容觸摸驅動架構
我們來梳理一下 linux下多點電容觸摸驅動的編寫架構和步驟。首先确定驅動需要用到哪些知識點,哪些架構?根據前面的分析,我們在編寫驅動的時候需要注意一下幾點:
①、多點電容觸摸晶片的接口,一般都為 I2C 接口,是以驅動主架構肯定是 I2C。
②、linux 裡面一般都是通過中斷來上報觸摸點坐标資訊,是以需要用到中斷架構。
③、多點電容觸摸屬于 input 子系統,是以還要用到 input 子系統架構。
④、在中斷處理程式中按照 linux 的 MT 協定上報坐标資訊。
根據上面的分析,多點電容觸摸驅動編寫架構以及步驟如下:
1. I2C驅動架構
驅動總體采用 I2C 架構,參考架構代碼如下所示:
1 /* 裝置樹比對表 */
2 static const struct i2c_device_id xxx_ts_id[] = {
3 { "xxx", 0, },
4 { /* sentinel */ }
5 };
6
7 /* 裝置樹比對表 */
8 static const struct of_device_id xxx_of_match[] = {
9 { .compatible = "xxx", },
10 { /* sentinel */ }
11 };
12
13 /* i2c 驅動結構體 */
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 : 驅動入口函數
27 * @param : 無
28 * @return : 無
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 : 驅動出口函數
41 * @param : 無
42 * @return : 無
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("zuozhongkai");
當裝置樹中觸摸 IC的裝置節點和驅動比對以後,示例代碼中第 21 行的 xxx_ts_probe 函數就會執行,我們可以在此函數中初始化觸摸 IC,中斷和 input 子系統等。
2. 初始化觸摸 IC 、中斷和 input 子系統
初始化操作都是在 xxx_ts_probe 函數中完成,參考架構如下所示(以下代碼中步驟順序可以自行調整,不一定按照示例架構來):
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,申請中斷, */
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 裝置申請與初始化 */
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,注冊 input_dev */
34 input_register_device(input);
35 ......
36 }
第 5~7 行,首先肯定是初始化觸摸晶片,包括晶片的相關 IO,比如複位、中斷等 IO 引腳,然後就是晶片本身的初始化,也就是配置觸摸晶片的相關寄存器。
第 9 行,因為一般觸摸晶片都是通過中斷來向系統上報觸摸點坐标資訊的,是以我們需要初始化中斷。大家可能會發現第 9 行并沒有使用request_irq 函數申請中斷,而是采用了 devm_request_threaded_irq 這個函數,為什麼使用這個函數呢?是不是 request_irq 函數不能使用?答案肯定不是的,這裡用 request_irq 函數是絕對沒問題的。
那為何要用 devm_request_threaded_irq 呢?這裡我們就簡單的介紹一下這個 API 函數,devm_request_threaded_irq 函數特點如下:
①、用于申請中斷,作用和 request_irq 函數類似。
②、此函數的作用是中斷線程化,大家如果直接在網上搜尋“devm_request_threaded_irq”會發現相關解釋很少。但是大家去搜尋 request_threaded_irq 函數就會有很多講解的部落格和文章,這兩個函數在名字上的差别就是前者比後者多了個“devm_”字首,“devm_”字首稍後講解。大家應該注意到了“request_threaded_irq”相比“request_irq”多了個 threaded 函數,也就是線程的意思。那麼為什麼要中斷線程化呢?我們都是知道硬體中斷具有最高優先級,不論什麼時候隻要硬體中斷發生,那麼核心都會終止目前正在執行的操作,轉而去執行中斷處理程式(不考慮關閉中斷和中斷優先級的情況),如果中斷非常頻繁的話那麼核心将會頻繁的執行中斷處理程式,導緻任務得不到及時的處理。中斷線程化以後中斷将作為核心線程運作,而且也可以被賦予不同的優先級,任務的優先級可能比中斷線程的優先級高,這樣做的目的就是保證高優先級的任務能被優先處理。大家可能會疑問,前面不是說可以将比較耗時的中斷放到下半部(bottomhalf)處理嗎?雖然下半部可以被延遲處理,但是依舊先于線程執行,中斷線程化可以讓這些比較耗時的下半部與程序進行公平競争。要注意,并不是所有的中斷都可以被線程化,重要的中斷就不能這麼操作。對于觸摸屏而言隻要手指放到螢幕上,它可能就會一直産生中斷(視具體晶片而定,FT5426 是這樣的),中斷處理程式裡面需要通過 I2C 讀取觸摸資訊并上報給核心,I2C 的速度最大隻有 400KHz,算是低速外設。不斷的産生中斷、讀取觸摸資訊、上報資訊會導緻處理器在觸摸中斷上花費大量的時間,但是觸摸相對來說不是那麼重要的事件,是以可以将觸摸中斷線程化。如果你覺得觸摸中斷很重要,那麼就可以不将其進行線程化處理。總之,要不要将一個中斷進行線程化處理是需要自己根據實際情況去衡量的。linux 核心自帶的 goodix.c(彙頂科技)、mms114.c(MELFAS 公司)、zforce_ts.c(zForce 公司)等多點電容觸摸 IC 驅動程式都采用了中斷線程化,當然也有一些驅動沒有采用中斷線程化。
③、最後來看一下“devm_”字首,在 linux 核心中有很多的申請資源類的 API 函數都有對應的“devm_”字首版本。比如 devm_request_irq 和 request_irq 這兩個函數,這兩個函數都是申請中斷的,我們使用 request_irq 函數申請中斷的時候,如果驅動初始化失敗的話就要調用free_irq 函數對申請成功的 irq 進行釋放,解除安裝驅動的時候也需要我們手動調用 free_irq 來釋放irq。假如我們的驅動裡面申請了很多資源,比如:gpio、irq、input_dev,那麼就需要添加很多goto 語句對其做處理,當這樣的标簽多了以後代碼看起來就不整潔了。“devm_”函數就是為了處理這種情況而誕生的,“devm_”函數最大的作用就是:
使用“devm_ ”字首的函數申請到的資源可以由系統自動釋放,不需要我們手動處理。
如果我們使用 devm_request_threaded_irq 函數來申請中斷,那麼就不需要我們再調用free_irq 函數對其進行釋放。大家可以注意一下,帶有“devm_”字首的都是一些和裝置資源管理有關的函數。關于“devm_”函數的實作原理這裡就不做詳細的講解了,我們的重點在于學會如何使用這些 API 函數,感興趣的可以查閱一些其他文檔或者文章來看一下“devm_”函數的實作原理。
第 15 行,接下來就是申請 input_dev,因為多點電容觸摸屬于 input 子系統。這裡同樣使用devm_input_allocate_device 函數來申請 input_dev,也就是我們前面講解的 input_allocate_device函數加“devm_”字首版本。申請到 input_dev 以後還需要對其進行初始化操作。
第 23~24 行,設定 input_dev 需要上報的事件為 EV_ABS 和 BTN_TOUCH,因為多點電容屏的觸摸坐标為絕對值,是以需要上報 EV_ABS 事件。觸摸屏有按下和擡起之分,是以需要上報BTN_TOUCH 按鍵。
第 26~29 行,調用 input_set_abs_params 函數設定 EV_ABS 事件需要上報 ABS_X、ABS_Y、ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y。單點觸摸需要上報 ABS_X 和 ABS_Y,對
于多點觸摸需要上報 ABS_MT_POSITION_X 和ABS_MT_POSITION_Y。
第 30 行,調用 input_mt_init_slots 函數初始化多點電容觸摸的 slots。
第 34 行,調用 input_register_device 函數系統注冊前面申請到的 input_dev。
3. 上報坐标資訊
最後就是在中斷服務程式中上報讀取到的坐标資訊,根據所使用的多點電容觸摸裝置類型選擇使用 Type A 還是 Type B 時序。由于大多數的裝置都是 Type B 類型,是以這裡就以 Type B類型為例講解一下上報過程,參考驅動架構如下所示:
1 static irqreturn_t xxx_handler(int irq, void *dev_id)
2 {
3
4 int num; /* 觸摸點數量 */
5 int x[n], y[n]; /* 儲存坐标值 */
6
7 /* 1、從觸摸晶片擷取各個觸摸點坐标值 */
8 ......
9
10 /* 2、上報每一個觸摸點坐标 */
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[i]);
15 input_report_abs(input, ABS_MT_POSITION_Y, y[i]);
16 }
17 ......
18
19 input_sync(input);
20 ......
21
22 return IRQ_HANDLED;
23 }
進入中斷處理程式以後首先肯定是從觸摸 IC 裡面讀取觸摸坐标以及觸摸點數量,假設觸摸點數量儲存到 num 變量,觸摸點坐标存放到 x,y 數組裡面。
第 11~16 行,循環上報每一個觸摸點坐标,一定要按照 Type B 類型的時序進行。
第 19 行,每一輪觸摸點坐标上報完畢以後就調用一次 input_sync 函數發送一個SYN_REPORT 事件。
關于多點電容觸摸驅動架構就講解到這裡,接下來我們就實際編寫一個多點電容觸摸驅動程式。
二、硬體原理圖分析
三、實驗程式編寫
本試驗以正點原子的 ATK7084(7 寸 800480 分辨率)和 ATK7016(7 寸 1024600 分辨率)這兩款螢幕所使用的 FT5426 觸摸晶片為例,講解如何編寫多點電容觸摸驅動。
1. 修改裝置樹
1. 添加FT5426所使用的IO
FT5426 觸摸晶片用到了 4 個 IO,一個複位 IO、一個中斷 IO、I2C2 的 SCL 和 SDA,是以我們需要先在裝置樹中添加 IO 相關的資訊。
複位 IO 和中斷 IO 是普通的 GPIO,是以這兩個 IO可以放到同一個節點下去描述,I2C2 的 SCL 和 SDA 屬于 I2C2,是以這兩個要放到同一個節點下去描述。
首先是複位 IO 和中斷 IO,imx6ull-luatao-emmc.dts 檔案裡面預設有個名為“pinctrl_tsc”的節點,如果被删除了的話就自行建立,在此節點下添加觸摸屏的中斷引腳資訊,修改以後的“pinctrl_tsc”節點内容如下所示:
1 pinctrl_tsc: tscgrp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080 /* TSC_INT */
4 >;
5 };
觸摸屏複位引腳使用的是 SNVS_TAMPER9,是以複位引腳資訊要添加到 iomuxc_snvs 節點下,在 iomuxc_snvs 節點建立一個名為 pinctrl_tsc_reset 的子節點,然後在此子節點裡面輸入複位引腳配置資訊即可,如下所示:
pinctrl_tsc_reset: tsc_reset {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
>;
};
繼續添加 I2C2 的 SCL 和 SDA 這兩個 IO 資訊,imx6ull-luatao-emmc.dts 裡面預設就已經添加了 I2C2 的 IO 資訊,這是 NXP 官方添加的,是以不需要我們去修改。找到“pinctrl_i2c2”節點,此節點就是用于描述 I2C2 的 IO 資訊,節點内容如下所示:
最後,一定要檢查一下裝置樹,確定觸摸屏所使用的 IO 沒有被其他的外設使用,如果有的話就需要将其屏蔽掉,保證隻有觸摸屏用到了這四個 IO。
2. 添加FT5426節點
FT5426 這個觸摸 IC 挂載 I2C2 下,是以需要向 I2C2 節點下添加一個子節點,此子節點用于描述 FT5426,添加完成以後的 I2C2 節點内容如下所示(省略掉其他挂載到 I2C2 下的裝置):
/* 觸摸 */
/* FT5406/FT5426 */
ft5426: [email protected]38 {
compatible = "edt,edt-ft5426";
reg = <0x38>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tsc
&pinctrl_tsc_reset >;
interrupt-parent = <&gpio1>;
interrupts = <9 0>;
reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
};
第 12 行,觸摸屏所使用的 FT5426 晶片節點,挂載 I2C2 節點下,FT5426 的器件位址為0X38。
第 14 行,reg 屬性描述 FT5426 的器件位址為 0x38。
第 16 和 17 行,pinctrl-0 屬性描述 FT5426 的複位 IO 和中斷 IO 所使用的節點為 pinctrl_tsc和 pinctrl_tsc_reset。
第 18 行,interrupt-parent 屬性描述中斷 IO 對應的 GPIO 組為 GPIO1。
第 19 行,interrupts 屬性描述中斷 IO 對應的是 GPIO1 組的 IOI09。
第 20 行,reset-gpios 屬性描述複位 IO 對應的 GPIO 為 GPIO5_IO09。
第 21 行,interrupt-gpios 屬性描述中斷 IO 對應的 GPIO 為 GPIO1_IO09。
2. 編寫多點電容觸摸驅動
建立名為“23_multitouch”的檔案夾,然後在 23_multitouch 檔案夾裡面建立 vscode 工程,工作區命名為“multitouch”。工程建立好以後建立 ft5x06.c 這個驅動檔案,在裡面輸入如下所示内容:
#include <linux/module.h>
#include <linux/ratelimit.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>
#include <linux/input/edt-ft5x06.h>
#include <linux/i2c.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
檔案名 : ft5x06.c
作者 : 左忠凱
版本 : V1.0
描述 : FT5X06,包括FT5206、FT5426等觸摸屏驅動程式
其他 : 無
論壇 : www.openedv.com
日志 : 初版V1.0 2019/12/23 左忠凱建立個
***************************************************************/
#define MAX_SUPPORT_POINTS 5 /* 5點觸摸 */
#define TOUCH_EVENT_DOWN 0x00 /* 按下 */
#define TOUCH_EVENT_UP 0x01 /* 擡起 */
#define TOUCH_EVENT_ON 0x02 /* 接觸 */
#define TOUCH_EVENT_RESERVED 0x03 /* 保留 */
/* FT5X06寄存器相關宏定義 */
#define FT5X06_TD_STATUS_REG 0X02 /* 狀态寄存器位址 */
#define FT5x06_DEVICE_MODE_REG 0X00 /* 模式寄存器 */
#define FT5426_IDG_MODE_REG 0XA4 /* 中斷模式 */
#define FT5X06_READLEN 29 /* 要讀取的寄存器個數 */
struct ft5x06_dev {
struct device_node *nd; /* 裝置節點 */
int irq_pin,reset_pin; /* 中斷和複位IO */
int irqnum; /* 中斷号 */
void *private_data; /* 私有資料 */
struct input_dev *input; /* input結構體 */
struct i2c_client *client; /* I2C用戶端 */
};
static struct ft5x06_dev ft5x06;
/*
* @description : 複位FT5X06
* @param - client : 要操作的i2c
* @param - multidev: 自定義的multitouch裝置
* @return : 0,成功;其他負值,失敗
*/
static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev *dev)
{
int ret = 0;
if (gpio_is_valid(dev->reset_pin)) { /* 檢查IO是否有效 */
/* 申請複位IO,并且預設輸出低電平 */
ret = devm_gpio_request_one(&client->dev,
dev->reset_pin, GPIOF_OUT_INIT_LOW,
"edt-ft5x06 reset");
if (ret) {
return ret;
}
msleep(5);
gpio_set_value(dev->reset_pin, 1); /* 輸出高電平,停止複位 */
msleep(300);
}
return 0;
}
/*
* @description : 從FT5X06讀取多個寄存器資料
* @param - dev: ft5x06裝置
* @param - reg: 要讀取的寄存器首位址
* @param - val: 讀取到的資料
* @param - len: 要讀取的資料長度
* @return : 操作結果
*/
static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->client;
/* msg[0]為發送要讀取的首位址 */
msg[0].addr = client->addr; /* ft5x06位址 */
msg[0].flags = 0; /* 标記為發送資料 */
msg[0].buf = ® /* 讀取的首位址 */
msg[0].len = 1; /* reg長度*/
/* msg[1]讀取資料 */
msg[1].addr = client->addr; /* ft5x06位址 */
msg[1].flags = I2C_M_RD; /* 标記為讀取資料*/
msg[1].buf = val; /* 讀取資料緩沖區 */
msg[1].len = len; /* 要讀取的資料長度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
ret = -EREMOTEIO;
}
return ret;
}
/*
* @description : 向ft5x06多個寄存器寫入資料
* @param - dev: ft5x06裝置
* @param - reg: 要寫入的寄存器首位址
* @param - val: 要寫入的資料緩沖區
* @param - len: 要寫入的資料長度
* @return : 操作結果
*/
static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->client;
b[0] = reg; /* 寄存器首位址 */
memcpy(&b[1],buf,len); /* 将要寫入的資料拷貝到數組b裡面 */
msg.addr = client->addr; /* ft5x06位址 */
msg.flags = 0; /* 标記為寫資料 */
msg.buf = b; /* 要寫入的資料緩沖區 */
msg.len = len + 1; /* 要寫入的資料長度 */
return i2c_transfer(client->adapter, &msg, 1);
}
/*
* @description : 向ft5x06指定寄存器寫入指定的值,寫一個寄存器
* @param - dev: ft5x06裝置
* @param - reg: 要寫的寄存器
* @param - data: 要寫入的值
* @return : 無
*/
static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ft5x06_write_regs(dev, reg, &buf, 1);
}
/*
* @description : FT5X06中斷服務函數
* @param - irq : 中斷号
* @param - dev_id : 裝置結構。
* @return : 中斷執行結果
*/
static irqreturn_t ft5x06_handler(int irq, void *dev_id)
{
struct ft5x06_dev *multidata = dev_id;
u8 rdbuf[29];
int i, type, x, y, id;
int offset, tplen;
int ret;
bool down;
offset = 1; /* 偏移1,也就是0X02+1=0x03,從0X03開始是觸摸值 */
tplen = 6; /* 一個觸摸點有6個寄存器來儲存觸摸值 */
memset(rdbuf, 0, sizeof(rdbuf)); /* 清除 */
/* 讀取FT5X06觸摸點坐标從0X02寄存器開始,連續讀取29個寄存器 */
ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN);
if (ret) {
goto fail;
}
/* 上報每一個觸摸點坐标 */
for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
u8 *buf = &rdbuf[i * tplen + offset];
/* 以第一個觸摸點為例,寄存器TOUCH1_XH(位址0X03),各位描述如下:
* bit7:6 Event flag 0:按下 1:釋放 2:接觸 3:沒有事件
* bit5:4 保留
* bit3:0 X軸觸摸點的11~8位。
*/
type = buf[0] >> 6; /* 擷取觸摸類型 */
if (type == TOUCH_EVENT_RESERVED)
continue;
/* 我們所使用的觸摸屏和FT5X06是反過來的 */
x = ((buf[2] << 8) | buf[3]) & 0x0fff;
y = ((buf[0] << 8) | buf[1]) & 0x0fff;
/* 以第一個觸摸點為例,寄存器TOUCH1_YH(位址0X05),各位描述如下:
* bit7:4 Touch ID 觸摸ID,表示是哪個觸摸點
* bit3:0 Y軸觸摸點的11~8位。
*/
id = (buf[2] >> 4) & 0x0f;
down = type != TOUCH_EVENT_UP;
input_mt_slot(multidata->input, id);
input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);
if (!down)
continue;
input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
}
input_mt_report_pointer_emulation(multidata->input, true);
input_sync(multidata->input);
fail:
return IRQ_HANDLED;
}
/*
* @description : FT5x06中斷初始化
* @param - client : 要操作的i2c
* @param - multidev: 自定義的multitouch裝置
* @return : 0,成功;其他負值,失敗
*/
static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev *dev)
{
int ret = 0;
/* 1,申請中斷GPIO */
if (gpio_is_valid(dev->irq_pin)) {
ret = devm_gpio_request_one(&client->dev, dev->irq_pin,
GPIOF_IN, "edt-ft5x06 irq");
if (ret) {
dev_err(&client->dev,
"Failed to request GPIO %d, error %d\n",
dev->irq_pin, ret);
return ret;
}
}
/* 2,申請中斷,client->irq就是IO中斷, */
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
client->name, &ft5x06);
if (ret) {
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
return ret;
}
return 0;
}
/*
* @description : i2c驅動的probe函數,當驅動與
* 裝置比對以後此函數就會執行
* @param - client : i2c裝置
* @param - id : i2c裝置ID
* @return : 0,成功;其他負值,失敗
*/
static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
ft5x06.client = client;
/* 1,擷取裝置樹中的中斷和複位引腳 */
ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);
/* 2,複位FT5x06 */
ret = ft5x06_ts_reset(client, &ft5x06);
if(ret < 0) {
goto fail;
}
/* 3,初始化中斷 */
ret = ft5x06_ts_irq(client, &ft5x06);
if(ret < 0) {
goto fail;
}
/* 4,初始化FT5X06 */
ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0); /* 進入正常模式 */
ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1); /* FT5426中斷模式 */
/* 5,input裝置注冊 */
ft5x06.input = devm_input_allocate_device(&client->dev);
if (!ft5x06.input) {
ret = -ENOMEM;
goto fail;
}
ft5x06.input->name = client->name;
ft5x06.input->id.bustype = BUS_I2C;
ft5x06.input->dev.parent = &client->dev;
__set_bit(EV_KEY, ft5x06.input->evbit);
__set_bit(EV_ABS, ft5x06.input->evbit);
__set_bit(BTN_TOUCH, ft5x06.input->keybit);
input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);
input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);
input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X,0, 1024, 0, 0);
input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y,0, 600, 0, 0);
ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);
if (ret) {
goto fail;
}
ret = input_register_device(ft5x06.input);
if (ret)
goto fail;
return 0;
fail:
return ret;
}
/*
* @description : i2c驅動的remove函數,移除i2c驅動的時候此函數會執行
* @param - client : i2c裝置
* @return : 0,成功;其他負值,失敗
*/
static int ft5x06_ts_remove(struct i2c_client *client)
{
/* 釋放input_dev */
input_unregister_device(ft5x06.input);
return 0;
}
/*
* 傳統驅動比對表
*/
static const struct i2c_device_id ft5x06_ts_id[] = {
{ "edt-ft5206", 0, },
{ "edt-ft5426", 0, },
{ /* sentinel */ }
};
/*
* 裝置樹比對表
*/
static const struct of_device_id ft5x06_of_match[] = {
{ .compatible = "edt,edt-ft5206", },
{ .compatible = "edt,edt-ft5426", },
{ /* sentinel */ }
};
/* i2c驅動結構體 */
static struct i2c_driver ft5x06_ts_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "edt_ft5x06",
.of_match_table = of_match_ptr(ft5x06_of_match),
},
.id_table = ft5x06_ts_id,
.probe = ft5x06_ts_probe,
.remove = ft5x06_ts_remove,
};
/*
* @description : 驅動入口函數
* @param : 無
* @return : 無
*/
static int __init ft5x06_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ft5x06_ts_driver);
return ret;
}
/*
* @description : 驅動出口函數
* @param : 無
* @return : 無
*/
static void __exit ft5x06_exit(void)
{
i2c_del_driver(&ft5x06_ts_driver);
}
module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
第 39~46 行,定義一個裝置結構體,存放多點電容觸摸裝置相關屬性資訊。
第 48 行,定義一個名為 ft5x06 的全局變量,變量類型就是上面定義的 ft5x06_dev 結構體。
第 56~73 行,ft5x06_ts_reset 函數,用于初始化 FT5426 觸摸晶片,其實就是設定 FT5426的複位 IO 為高電平,防止晶片複位。注意在第 62 行使用 devm_gpio_request_one 函數來申請複位 IO,關于“devm_”字首的作用已經在小節做了詳細的講解。使用“devm_”字首的
API 函數申請的資源不需要我們手動釋放,核心會處理,是以這裡使用 devm_gpio_request_one函數申請 IO 以後不需要我們在解除安裝驅動的時候手動去釋放此 IO。
第 83~108 行,ft5x06_read_regs 函數,用于連續的讀取 FT5426 内部寄存器資料,就是 I2C讀取函數。
第 118~134 行,ft5x06_write_regs 函數,用于向 FT5426 寄存器寫入連續的資料,也就是 I2C寫函數。
第 143~148 行,ft5x06_write_reg 函數,對 ft5x06_write_regs 函數的簡單封裝,向 FT5426 指定寄存器寫入一個資料,用于配置FT5426。
第 156~217 行,ft5x06_handler 函數,觸摸屏中斷服務函數,觸摸點坐标的上報就是在此函數中完成的。
第 172 行通過 ft5x06_read_regs 函數讀取 FT5426 的所有觸摸點資訊寄存器資料,從 0X02 這個位址開始,一共 29 個寄存器。第 178~209 行的 for 循環就是一個一個的上報觸摸點坐标資料,使用Type B時序,這個我們已經在前面說了很多次了。
最後在212行通過input_sync函數上報 SYN_REPORT 事件。如果了解了前面講解的 Type B 時序,那麼此函數就很好看懂。
第 225~251 行,ft5x06_ts_irq 函數,初始化 FT5426 的中斷 IO,
第 231 行使用devm_gpio_request_one 函數申請中斷 IO。
第 242 行使用函數 devm_request_threaded_irq 申請中斷,中斷處理函數為 ft5x06_handler。
第 260~317 行,當 I2C 裝置與驅動比對以後此函數就會執行,一般在此函數中完成一些初始化工作。
我們重點來看一下 287~309 行是關于 input_dev 裝置的初始化,
第 287~294 行申請并簡單的初始化input_dev。
第296和298行設定input_dev需要上報的事件為 EV_KEY 和 EV_ABS,需要上報的按鍵碼為 BTN_TOUCH。EV_KEY 是按鍵事件,用于上報觸摸屏是否被按下,相當于把觸摸屏當做一個按鍵。EV_ABS 是觸摸點坐标資料,BTN_TOUCH 表示将觸摸屏的按下和擡起用作 BTN_TOUCH 按鍵。
第 300~303 行調用input_set_abs_params函數設定EV_ABS 事件需要上報ABS_X、ABS_Y、ABS_MT_POSITION_X和 ABS_MT_POSITION_Y。單點觸摸需要上報 ABS_X 和 ABS_Y,對于多點觸摸需要上報ABS_MT_POSITION_X 和ABS_MT_POSITION_Y。
第 304 行調用 input_mt_init_slots 函數初始化 slots,也就是最大觸摸點數量,FT5426 是個 5 點電容觸摸晶片,是以一共 5 個 slot。
最後在309 行調用 input_register_device 函數向系統注冊input_dev。
第 324~329 行,當解除安裝驅動的時候 ft5x06_ts_remove 函數就會執行,因為前面很多資源我們都是用“devm_”字首函數來申請的,是以不需要手動釋放。此函數隻需要調用input_unregister_device 來釋放掉前面添加到核心中的 input_dev。
第 330 行~結束,剩下的就是 I2C 驅動架構那一套。
四、運作測試
1. 編譯驅動程式
2. 運作測試
depmod //第一次加載驅動的時候需要運作此指令
modprobe ft5x06.ko //加載驅動子產品
當驅動子產品加載成功以後會有如圖
驅動加載成功以後就會生成/dev/input/eventX(X=1,2,3…),比如本實驗的多點電容觸摸驅動就會在我所使用的 ALPHA 開發闆平台下就會生成/dev/input/event2 這個檔案,如圖所示
不同的平台 event 序号不同,也可能是 event3,event4 等,一切以實際情況為準!輸入如下指令檢視 event2,也就是多點電容觸摸屏上報的原始資料:
hexdump /dev/input/event2
現在用一根手指觸摸螢幕的右上角,然後再擡起,理論坐标值為(1023,0),但是由于觸摸誤差的原因,大機率不會是絕對的(1023,0),應該是在此值附近的一個觸摸坐标值,實際的上報資料如圖所示:
上報的資訊是按照 input_event 類型呈現的,這個同樣在小節做了詳細的介紹,這裡我們重點來分析一下,在多點電容觸摸屏上其所代表的具體含義,将圖中的資料進行整理,結果如下所示:
第 1 行,type 為 0x3,說明是一個 EV_ABS 事件,code 為 0x2f,為 ABS_MT_SLOT,是以這一行就是 input_mt_slot 函數上報的 ABS_MT_SLOT 事件。value=0,說明接下來上報的是第一個觸摸點坐标。
第 2 行 , type 為 0x3 , 說 明 是 一 個 EV_ABS 事 件 , code 為 0x39 , 也 就 是ABS_MT_TRACKING_ID , 這 一 行 就 是input_mt_report_slot_state 函 數 上 報ABS_MT_TRACKING_ID 事件。value=5 說明給 SLOT0 配置設定的 ID 為 5。
第 3 行,type 為 0x3,是一個 EV_ABS 事件,code 為 0x35,為 ABS_MT_POSITION_X,這一行就是 input_report_abs 函數上報的 ABS_MT_POSITION_X 事件,也就是觸摸點的 X 軸坐标。value=0x03ec=1004,說明觸摸點 X 軸坐标為 1004,屬于螢幕右上角區域。
第 4 行,type 為 0x3,是一個 EV_ABS 事件,code 為 0x36,為 ABS_MT_POSITION_Y,這一行就是 input_mt_report_slot_state 函數上報的 ABS_MT_POSITION_Y 事件,也就是觸摸點的 Y 軸坐标。value=0x17=23,說明 Y 軸坐标為 23,由此可以看出本次觸摸的坐标為(1004,23),處于螢幕右上角區域。
第 5 行,type 為 0x1,是一個 EV_KEY 事件,code=0x14a,為 BTN_TOUCH,value=0x1 表示觸摸屏被按下。
第 6 行,type 為 0x3,是一個 EV_ABS 事件,code 為 0x0,為 ABS_X,用于單點觸摸的時候上報 X 軸坐标。在這裡和 ABS_MT_POSITION_X 相同,value 也為 0x3f0=1008。ABS_X 是
由 input_mt_report_pointer_emulation 函數上報的。
第 7 行,type 為 0x3,是一個 EV_ABS 事件,code 為 0x1,為 ABS_Y,用于單點觸摸的時候上報 Y 軸坐标。在這裡和 ABS_MT_POSITION_Y 相同,value 也為 0x29=41。ABS_Y 是由
input_mt_report_pointer_emulation 函數上報的。
第 8 行,type 為 0x0,是一個 EV_SYN 事件,由 input_sync 函數上報。
第9行,type為0x3,是一個EV_ABS事件,code為0x39,也就是ABS_MT_TRACKING_ID,value=0xffffffff=-1,說明觸摸點離開了螢幕。
第 10 行,type 為 0x1,是一個 EV_KEY 事件,code=0x14a,為 BTN_TOUCH,value=0x0表示手指離開觸摸屏,也就是觸摸屏沒有被按下了。
第 11 行,type 為 0x0,是一個 EV_SYN 事件,由 input_sync 函數上報。
以上就是一個觸摸點的坐标上報過程,和我們前面講解的 Type B 類型裝置一緻。
3. 将驅動添加到核心中
前面我們一直将觸摸驅動編譯為子產品,每次系統啟動以後在手動加載驅動子產品,這樣很不友善。當我們把驅動調試成功以後一般都會将其編譯到核心中,這樣核心啟動以後就會自動加載驅動,不需要我們再手動 modprobe 了。本節我們就來學習一下如何将 ft5x06.c 添加到 linux核心裡面,步驟如下所示:
1. 将驅動檔案放到合适的位置
首先肯定是在核心源碼中找個合适的位置将 ft5x06.c 放進去,ft5x06.c 是個觸摸屏驅動,是以我們需要查找一下 linux 核心裡面觸摸屏驅動放到了哪個目錄下。
linux 核心裡面将觸摸屏驅動放到了 drivers/input/touchscreen 目錄下,是以我們要将 ft5x06.c 拷貝到此目錄下,指令如下:
cp ft5x06.c (核心源碼目錄)/drivers/input/touchscreen/ -f
2. 修改對應的 Makefile
修改 drivers/input/touchscreen 目錄下的 Makefile,在最下面添加下面一行:
obj-y += ft5x06.o
修改完成以後重新編譯 linux 核心,然後用新的 zImage 啟動開發闆。如果驅動添加成功的話系統啟動的時候就會輸出如圖
可以看出,觸摸屏驅動已經啟動了,這個時候就會自動生成/dev/input/evenvtX。
在本實驗中将觸摸屏驅動添加到 linux 核心裡面以後觸摸屏對應的是 event1,而不是前面編譯為子產品對應的 event2,這一點一定要注意!輸入如下指令,檢視驅動工作是否正常:
可以看出,坐标資料上報正常,說明驅動工作沒問題
五、tslib移植與使用
1. tslib移植
tslib 是一個開源的第三方庫,用于觸摸屏性能調試,使用電阻屏的時候一般使用 tslib 進行校準。雖然電容屏不需要校準,但是由于電容屏加工的原因,有的時候其不一定精準,是以有時候也需要進行校準。最主要的是 tslib 提供了一些其他軟體,我們可以通過這些軟體來測試觸摸屏工作是否正常。
最新版本的 tslib 已經支援了多點電容觸摸屏,是以可以通過 tslib 來直覺的測試多點電容觸摸屏驅動,這個要比觀看 eventX 原始資料友善的多。
tslib 的移植很簡單,步驟如下:
1. 擷取 tslib 源碼
首先肯定是擷取 tslib 的源碼,git 位址為https://github.com/kergoth/tslib,目前最新的版本是1.21。tslib源碼已經放到開發闆CD光牒中,路徑為:1、例程源碼-》7、第三方庫源碼-》tslib-1.21.tar.bz2。
将壓縮包發送到 ubuntu 中并解壓,得到名為“tslib-1.21”的目錄,此目錄下就是 tslib 源碼。
2. 修改 tslib 源碼所屬使用者
修改解壓得到的 tslib-1.21 目錄所屬使用者為目前使用者,這一步一定要做!否則在稍後的編譯中會遇到各種問題。我目前 ubuntu 的登入使用者名為“luatao”,那麼修改指令如下:
sudo chown luatao:luatao tslib-1.21 -R
3. ubuntu 工具安裝
編譯 tslib 的時候需要先在 ubuntu 中安裝一些檔案,防止編譯 tslib 過程中出錯,指令如下所示:
sudo apt-get install autoconf
sudo apt-get install automake
sudo apt-get install libtool
4. 編譯 tslib
首先在 ubuntu 中建立一個名為“tslib”的目錄存放編譯結果,比如我們建立的 tslib 目錄全路徑為:/home/luatao/linux/tool/tslib
接下來輸入如下指令配置并編譯 tslib:
cd tslib-1.21/ //進入 tslib 源碼目錄
./autogen.sh
./configure --host=arm-linux-gnueabihf --prefix=/home/luatao/linux/tool/tslib
make //編譯
make install //安裝
注意,在使用./configure 配置 tslib 的時候“–host”參數指定編譯器,“–prefix”參數指定編譯完成以後的 tslib 檔案安裝到哪裡,這裡肯定是安裝到我們剛剛建立的“tslib”目錄下。完成以後 tslib 目錄下的内容如圖所示:
bin 目錄下是可執行檔案,包括 tslib 的測試工具。etc 目錄下是 tslib 的配置檔案,lib 目錄下是相關的庫檔案。将圖中的所有檔案拷貝到開發闆的根檔案系統中,指令如下:
sudo cp * -rf /home/luatao/linux/nfs/rootfs
5. 配置tslib
在檔案系統根目錄下打開/etc/ts.conf 檔案,找到下面這一行:
module_raw input
如果上面這句前面有“#”的話就删除掉“#”。
打開/etc/profile 檔案,在裡面加入如下内容:
export TSLIB_TSDEVICE=/dev/input/event1
2 export TSLIB_CALIBFILE=/etc/pointercal
3 export TSLIB_CONFFILE=/etc/ts.conf
4 export TSLIB_PLUGINDIR=/lib/ts
5 export TSLIB_CONSOLEDEVICE=none
6 export TSLIB_FBDEVICE=/dev/fb0
第 1 行,TSLIB_TSDEVICE 表示觸摸裝置檔案,這裡設定為/dev/input/event1,這個要根據具體情況設定,如果你的觸摸裝置檔案為event2那麼就應該設定為/dev/input/event2,以此類推。
第 2 行,TSLIB_CALIBFILE 表示校準檔案,如果進行螢幕校準的話校準結果就儲存在這個檔案中,這裡設定校準檔案為/etc/pointercal,此檔案可以不存在,校準的時候會自動生成。
第 3 行,TSLIB_CONFFILE 表示觸摸配置檔案,文為/etc/ts.conf,此檔案在移植 tslib 的時候會生成。
第 4 行,TSLIB_PLUGINDIR 表示 tslib 插件目錄位置,目錄為/lib/ts。
第 5 行,TSLIB_CONSOLEDEVICE 表示控制台設定,這裡不設定,是以為 none。
第 6 行,TSLIB_FBDEVICE 表示 FB 裝置,也就是螢幕,根據實際情況配置,我的螢幕檔案為/dev/fb0,是以這裡設定為/dev/fb0。
全部配置好以後重新開機開發闆,然後就可以進行測試了。
2. tslib測試
電容屏可以不用校準,如果是電阻屏就要先進行校準!校準的話輸入如下指令:
ts_calibrate
校準完成以後如果不滿意,或者不小心對電容屏做了校準,那麼直接删除掉/etc/pointercal檔案即可。
最後我們使用 ts_test_mt 這個軟體來測試觸摸屏工作是否正常,以及多點觸摸是否有效,執行如下所示指令:
ts_test_mt
此指令會打開一個觸摸測試界面
在圖上有三個按鈕“Drag”、“Draw”和“Quit”,這三個按鈕的功能如下:
Drag: :拖拽按鈕,預設就是此功能,大家可以看到螢幕中間有一個十字光标,我們可以通過觸摸螢幕來拖拽此光标。一個觸摸點一個十字光标,對于 5 點電容觸摸屏,如果 5 個手指都放到螢幕上,那麼就有 5 個光标,一個手指一個。
Draw: :繪制按鈕,按下此按鈕我們就可以在螢幕上進行簡單的繪制,可以通過此功能檢測多點觸摸工作是否正常。
Quit :退出按鈕,退出 ts_test_mt 測試軟體。
六、使用核心自帶的驅動
暫不寫
七、 4.3 寸屏觸摸驅動實驗
暫不寫