54.2 硬件原理圖 在本實驗中使用迅為的 7 寸屏為例,使用的是 FT5426 觸摸芯片。 ![]() 從原理圖中得知,7 寸屏使用 I2C2,觸摸屏復(fù)位引腳為 SNVS_TAMPER9,中斷引腳為 GPIO_9。 54.3 實驗程序編寫 54.3.1 修改設(shè)備樹文件 1 、添加 FT5426 的 的 pinctrl 信息 FT5426 觸摸芯片用到了 4 個 IO,一個復(fù)位 IO、一個中斷 IO、I2C2 的 SCL 和 SDA,所以我們需要先在設(shè)備樹中添加 IO 相關(guān)的信息。復(fù)位 IO 和中斷 IO 是普通的 GPIO,因此這兩個 IO 可以放到同一個節(jié)點下去描述,I2C2 的 SCL 和 SDA 屬于 I2C2,因此這兩個要放到同一個節(jié)點下去描述。首先是復(fù)位 IO 和中斷 IO,topeet_emmc_4_3.dts 文件里面默認有個名為“pinctrl_tsc”的節(jié)點,如果被刪除了的話就自行創(chuàng)建,在此節(jié)點下添加觸摸屏的復(fù)位 IO 和中斷 IO 信息,修改以后的“pinctrl_tsc”節(jié)點內(nèi)容如下所示: 1 pinctrl_tsc: tscgrp { 2 fsl,pins = < 3 MX6UL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0 /* TSC_RST */ 4 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080 /* TSC_INT */ 5 >; 6 }; 繼續(xù)添加 I2C2 的 SCL 和 SDA 這兩個 IO 信息,topeet_emmc_4_3.dts 里面默認就已經(jīng)添加了 I2C2的 IO 信息,這是 NXP 官方添加的,所以不需要我們?nèi)バ薷摹U业健皃inctrl_i2c1”節(jié)點,此節(jié)點就是用于描述 I2C2 的 IO 信息,節(jié)點內(nèi)容如下所示: 1 pinctrl_i2c2: i2c2grp { 2 fsl,pins = < 3 MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0 4 MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0 5 >; 6 }; 最后在檢查一下這四個引腳有沒有被其他外設(shè)使用。如果有的話就需要屏蔽掉。 2 、添加 FT5426 節(jié)點 FT5426 這個觸摸 IC 掛載 I2C2 下,因此需要向 I2C2 節(jié)點下添加一個子節(jié)點,此子節(jié)點用于描述FT5426,添加完成以后的 I2C2 節(jié)點內(nèi)容如下所示: 1 &i2c2 { 2 clock_frequency = <100000>; 3 pinctrl-names = "default"; 4 pinctrl-0 = <&pinctrl_i2c2>; 5 status = "okay"; 6 7 /****************************/ 8 /* 省略掉其他的設(shè)備節(jié)點 */ 9 /****************************/ 10 11 12 ft5426: ft5426@38 { 13 compatible = "edt,edt-ft5426"; 14 reg = <0x38>; 15 pinctrl-names = "default"; 16 pinctrl-0 = <&pinctrl_tsc>; 17 interrupt-parent = <&gpio1>; 18 interrupts = <9 0>; 19 reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; 20 interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; 21 }; 22 }; 第 12 行,觸摸屏所使用的 FT5426 芯片節(jié)點,掛載 I2C2 節(jié)點下,F(xiàn)T5426 的器件地址為 0X38。 第 14 行,reg 屬性描述 FT5426 的器件地址為 0x38。 第 16 行,pinctrl-0 屬性描述 FT5426 的復(fù)位 IO 和中斷 IO 所使用的節(jié)點為 pinctrl_tsc。 第 17 行,interrupt-parent 屬性描述中斷 IO 對應(yīng)的 GPIO 組為 GPIO1。 第 18 行,interrupts 屬性描述中斷 IO 對應(yīng)的是 GPIO1 組的 IOI09。 第 19 行,reset-gpios 屬性描述復(fù)位 IO 對應(yīng)的 GPIO 為 GPIO5_IO09。 第 20 行,interrupt-gpios 屬性描述中斷 IO 對應(yīng)的 GPIO 為 GPIO1_IO09。 54.3.2 編寫多點電容觸摸驅(qū)動 本實驗例程路徑:i.MX6UL 終結(jié)者光盤資料/06_Linux 驅(qū)動例程/20_multitouch 創(chuàng)建 ft5426.c 驅(qū)動文件,內(nèi)容如下: 1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 13 #include 14 #include 15 #include 16 17 #define MAX_SUPPORT_POINTS 5 /* 5 點觸摸 */ 18 #define TOUCH_EVENT_DOWN 0x00 /* 按下 */ 19 #define TOUCH_EVENT_UP 0x01 /* 抬起 */ 20 #define TOUCH_EVENT_ON 0x02 /* 接觸 */ 21 #define TOUCH_EVENT_RESERVED 0x03 /* 保留 */ 22 23 /* FT5X06 寄存器相關(guān)宏定義 */ 24 #define FT5X06_TD_STATUS_REG 0X02 /* 狀態(tài)寄存器地址 */ 25 #define FT5x06_DEVICE_MODE_REG 0X00 /* 模式寄存器 */ 26 #define FT5426_IDG_MODE_R 0XA4 /* 中斷模式 */ 27 #define FT5X06_READLEN 29 /* 要讀取的寄存器個數(shù) */ 28 29 struct ft5x06_dev { 30 struct device_node *nd; /* 設(shè)備節(jié)點 */ 31 int irq_pin,reset_pin; /* 中斷和復(fù)位 IO */ 32 int irqnum; /* 中斷號 */ 33 void *private_data; /* 私有數(shù)據(jù) */ 34 struct input_dev *input; /* input 結(jié)構(gòu)體 */ 35 struct i2c_client *client; /* I2C 客戶端 */ 36 }; 37 38 static struct ft5x06_dev ft5x06; 39 40 /* 41 * @description : 復(fù)位 FT5X06 42 * @param - client : 要操作的 i2c 43 * @param - multidev: 自定義的 multitouch 設(shè)備 44 * @return : 0,成功;其他負值,失敗 45 */ 46 static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev *dev) 47 { 48 int ret = 0; 49 50 if (gpio_is_valid(dev->reset_pin)) { /* 檢查 IO 是否有效 */ 51 /* 申請復(fù)位 IO,并且默認輸出低電平 */ 52 ret = devm_gpio_request_one(&client->dev, 53 dev->reset_pin, GPIOF_OUT_INIT_LOW, 54 "edt-ft5x06 reset"); 55 if (ret) { 56 return ret; 57 } 58 59 msleep(5); 60 gpio_set_value(dev->reset_pin, 1); /* 輸出高電平,停止復(fù)位 */ 61 msleep(300); 62 } 63 64 return 0; 65 } 66 67 /* 68 * @description : 從 FT5X06 讀取多個寄存器數(shù)據(jù) 69 * @param - dev: ft5x06 設(shè)備 70 * @param - reg: 要讀取的寄存器首地址 71 * @param - val: 讀取到的數(shù)據(jù) 72 * @param - len: 要讀取的數(shù)據(jù)長度 73 * @return : 操作結(jié)果 74 */ 75 static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg, void *val, int len) 76 { 77 int ret; 78 struct i2c_msg msg[2]; 79 struct i2c_client *client = (struct i2c_client *)dev->client; 80 81 /* msg[0]為發(fā)送要讀取的首地址 */ 82 msg[0].addr = client->addr; /* ft5x06 地址 */ 83 msg[0].flags = 0; /* 標記為發(fā)送數(shù)據(jù) */ 84 msg[0].buf = ® /* 讀取的首地址 */ 85 msg[0].len = 1; /* reg 長度*/ 86 87 /* msg[1]讀取數(shù)據(jù) */ 88 msg[1].addr = client->addr; /* ft5x06 地址 */ 89 msg[1].flags = I2C_M_RD; /* 標記為讀取數(shù)據(jù)*/ 90 msg[1].buf = val; /* 讀取數(shù)據(jù)緩沖區(qū) */ 91 msg[1].len = len; /* 要讀取的數(shù)據(jù)長度*/ 92 93 ret = i2c_transfer(client->adapter, msg, 2); 94 if(ret == 2) { 95 ret = 0; 96 } else { 97 ret = -EREMOTEIO; 98 } 99 return ret; 100 } 101 102 /* 103 * @description : 向 ft5x06 多個寄存器寫入數(shù)據(jù) 104 * @param - dev: ft5x06 設(shè)備 105 * @param - reg: 要寫入的寄存器首地址 106 * @param - val: 要寫入的數(shù)據(jù)緩沖區(qū) 107 * @param - len: 要寫入的數(shù)據(jù)長度 108 * @return : 操作結(jié)果 109 */ 110 static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg, u8 *buf, u8 len) 111 { 112 u8 b[256]; 113 struct i2c_msg msg; 114 struct i2c_client *client = (struct i2c_client *)dev->client; 115 116 b[0] = reg; /* 寄存器首地址 */ 117 memcpy(&b[1],buf,len); /* 將要寫入的數(shù)據(jù)拷貝到數(shù)組 b 里面 */ 118 119 msg.addr = client->addr; /* ft5x06 地址 */ 120 msg.flags = 0; /* 標記為寫數(shù)據(jù) */ 121 122 msg.buf = b; /* 要寫入的數(shù)據(jù)緩沖區(qū) */ 123 msg.len = len + 1; /* 要寫入的數(shù)據(jù)長度 */ 124 125 return i2c_transfer(client->adapter, &msg, 1); 126 } 127 128 /* 129 * @description : 向 ft5x06 指定寄存器寫入指定的值,寫一個寄存器 130 * @param - dev: ft5x06 設(shè)備 131 * @param - reg: 要寫的寄存器 132 * @param - data: 要寫入的值 133 * @return : 無 134 */ 135 static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data) 136 { 137 u8 buf = 0; 138 buf = data; 139 ft5x06_write_regs(dev, reg, &buf, 1); 140 } 141 142 /* 143 * @description : FT5X06 中斷服務(wù)函數(shù) 144 * @param - irq : 中斷號 145 * @param - dev_id : 設(shè)備結(jié)構(gòu)。 146 * @return : 中斷執(zhí)行結(jié)果 147 */ 148 static irqreturn_t ft5x06_handler(int irq, void *dev_id) 149 { 150 struct ft5x06_dev *multidata = dev_id; 151 152 u8 rdbuf[29]; 153 int i, type, x, y, id; 154 int offset, tplen; 155 int ret; 156 bool down; 157 158 offset = 1; /* 偏移 1,也就是 0X02+1=0x03,從 0X03 開始是觸摸值 */ 159 tplen = 6; /* 一個觸摸點有 6 個寄存器來保存觸摸值 */ 160 161 memset(rdbuf, 0, sizeof(rdbuf)); /* 清除 */ 162 163 /* 讀取 FT5X06 觸摸點坐標從 0X02 寄存器開始,連續(xù)讀取 29 個寄存器 */ 164 ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN); 165 if (ret) { 166 goto fail; 167 } 168 169 /* 上報每一個觸摸點坐標 */ 170 for (i = 0; i < MAX_SUPPORT_POINTS; i++) { 171 u8 *buf = &rdbuf[i * tplen + offset]; 172 173 /* 以第一個觸摸點為例,寄存器 TOUCH1_XH(地址 0X03),各位描述如下: 174 * bit7:6 Event flag 0:按下 1:釋放 2:接觸 3:沒有事件 175 * bit5:4 保留 176 * bit3:0 X 軸觸摸點的 11~8 位。 177 */ 178 type = buf[0] >> 6; /* 獲取觸摸類型 */ 179 if (type == TOUCH_EVENT_RESERVED) 180 continue; 181 182 /* 我們所使用的觸摸屏和 FT5X06 是反過來的 */ 183 x = ((buf[2] << 8) | buf[3]) & 0x0fff; 184 y = ((buf[0] << 8) | buf[1]) & 0x0fff; 185 186 /* 以第一個觸摸點為例,寄存器 TOUCH1_YH(地址 0X05),各位描述如下: 187 * bit7:4 Touch ID 觸摸 ID,表示是哪個觸摸點 188 * bit3:0 Y 軸觸摸點的 11~8 位。 189 */ 190 id = (buf[2] >> 4) & 0x0f; 191 down = type != TOUCH_EVENT_UP; 192 193 input_mt_slot(multidata->input, id); 194 input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down); 195 196 if (!down) 197 continue; 198 199 input_report_abs(multidata->input, ABS_MT_POSITION_X, x); 200 input_report_abs(multidata->input, ABS_MT_POSITION_Y, y); 201 } 202 203 input_mt_report_pointer_emulation(multidata->input, true); 204 input_sync(multidata->input); 205 206 fail: 207 return IRQ_HANDLED; 208 209 } 210 211 /* 212 * @description : FT5x06 中斷初始化 213 * @param - client : 要操作的 i2c 214 * @param - multidev: 自定義的 multitouch 設(shè)備 215 * @return : 0,成功;其他負值,失敗 216 */ 217 static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev *dev) 218 { 219 int ret = 0; 220 221 /* 1,申請中斷 GPIO */ 222 if (gpio_is_valid(dev->irq_pin)) { 223 ret = devm_gpio_request_one(&client->dev, dev->irq_pin, 224 GPIOF_IN, "edt-ft5x06 irq"); 225 if (ret) { 226 dev_err(&client->dev, 227 "Failed to request GPIO %d, error %d\n", 228 dev->irq_pin, ret); 229 return ret; 230 } 231 } 232 233 /* 2,申請中斷,client->irq 就是 IO 中斷, */ 234 ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, 235 ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 236 client->name, &ft5x06); 237 if (ret) { 238 dev_err(&client->dev, "Unable to request touchscreen IRQ.\n"); 239 return ret; 240 } 241 242 return 0; 243 } 244 245 /* 246 * @description : i2c 驅(qū)動的 probe 函數(shù),當驅(qū)動與 247 * 設(shè)備匹配以后此函數(shù)就會執(zhí)行 248 * @param - client : i2c 設(shè)備 249 * @param - id : i2c 設(shè)備 ID 250 * @return : 0,成功;其他負值,失敗 251 */ 252 static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) 253 { 254 int ret = 0; 255 256 ft5x06.client = client; 257 258 /* 1,獲取設(shè)備樹中的中斷和復(fù)位引腳 */ 259 ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0); 260 ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0); 261 262 /* 2,復(fù)位 FT5x06 */ 263 ret = ft5x06_ts_reset(client, &ft5x06); 264 if(ret < 0) { 265 goto fail; 266 } 267 268 /* 3,初始化中斷 */ 269 ret = ft5x06_ts_irq(client, &ft5x06); 270 if(ret < 0) { 271 goto fail; 272 } 273 274 /* 4,初始化 FT5X06 */ 275 ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0); /* 進入正常模式*/ 276 ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1); /* FT5426 中斷模式 */ 277 278 /* 5,input 設(shè)備注冊 */ 279 ft5x06.input = devm_input_allocate_device(&client->dev); 280 if (!ft5x06.input) { 281 ret = -ENOMEM; 282 goto fail; 283 } 284 ft5x06.input->name = client->name; 285 ft5x06.input->id.bustype = BUS_I2C; 286 ft5x06.input->dev.parent = &client->dev; 287 288 __set_bit(EV_KEY, ft5x06.input->evbit); 289 __set_bit(EV_ABS, ft5x06.input->evbit); 290 __set_bit(BTN_TOUCH, ft5x06.input->keybit); 291 292 input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0); 293 input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0); 294 input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X,0, 1024, 0, 0); 295 input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y,0, 600, 0, 0); 296 ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0); 297 if (ret) { 298 goto fail; 299 } 300 301 ret = input_register_device(ft5x06.input); 302 if (ret) 303 goto fail; 304 305 return 0; 306 307 fail: 308 return ret; 309 } 310 311 /* 312 * @description : i2c 驅(qū)動的 remove 函數(shù),移除 i2c 驅(qū)動的時候此函數(shù)會執(zhí)行 313 * @param - client : i2c 設(shè)備 314 * @return : 0,成功;其他負值,失敗 315 */ 316 static int ft5x06_ts_remove(struct i2c_client *client) 317 { 318 /* 釋放 input_dev */ 319 input_unregister_device(ft5x06.input); 320 return 0; 321 } 322 323 324 /* 325 * 傳統(tǒng)驅(qū)動匹配表 326 */ 327 static const struct i2c_device_id ft5x06_ts_id[] = { 328 { "edt-ft5206", 0, }, 329 { "edt-ft5426", 0, }, 330 { /* sentinel */ } 331 }; 332 333 /* 334 * 設(shè)備樹匹配表 335 */ 336 static const struct of_device_id ft5x06_of_match[] = { 337 { .compatible = "edt,edt-ft5206", }, 338 { .compatible = "edt,edt-ft5426", }, 339 { /* sentinel */ } 340 }; 341 342 /* i2c 驅(qū)動結(jié)構(gòu)體 */ 343 static struct i2c_driver ft5x06_ts_driver = { 344 .driver = { 345 .owner = THIS_MODULE, 346 .name = "edt_ft5x06", 347 .of_match_table = of_match_ptr(ft5x06_of_match), 348 }, 349 .id_table = ft5x06_ts_id, 350 .probe = ft5x06_ts_probe, 351 .remove = ft5x06_ts_remove, 352 }; 353 354 /* 355 * @description : 驅(qū)動入口函數(shù) 356 * @param : 無 357 * @return : 無 358 */ 359 static int __init ft5x06_init(void) 360 { 361 int ret = 0; 362 363 ret = i2c_add_driver(&ft5x06_ts_driver); 364 365 return ret; 366 } 367 368 /* 369 * @description : 驅(qū)動出口函數(shù) 370 * @param : 無 371 * @return : 無 372 */ 373 static void __exit ft5x06_exit(void) 374 { 375 i2c_del_driver(&ft5x06_ts_driver); 376 } 377 378 module_init(ft5x06_init); 379 module_exit(ft5x06_exit); 380 MODULE_LICENSE("GPL"); 381 MODULE_AUTHOR("topeet"); 第 29~36 行,定義一個設(shè)備結(jié)構(gòu)體,存放多點電容觸摸設(shè)備相關(guān)屬性信息。 第 38 行,定義一個名為 ft5x06 的全局變量,變量類型就是上面定義的 ft5x06_dev 結(jié)構(gòu)體。 第 46~63 行,ft5x06_ts_reset 函數(shù),用于初始化 FT5426 觸摸芯片,其實就是設(shè)置 FT5426 的復(fù)位 IO為高電平,防止芯片復(fù)位。注意在第 52 行使用 devm_gpio_request_one 函數(shù)來申請復(fù)位 IO,關(guān)于“devm_”前綴的作用已經(jīng)在前面做了詳細的講解。使用“devm_”前綴的 API 函數(shù)申請的資源不需要我們手動釋放,內(nèi)核會處理,所以這里使用 devm_gpio_request_one 函數(shù)申請 IO 以后不需要我們在卸載驅(qū)動的時候手動去釋放此 IO。 第 73~98 行,ft5x06_read_regs 函數(shù),用于連續(xù)的讀取 FT5426 內(nèi)部寄存器數(shù)據(jù),就是 I2C 讀取函數(shù)。 第 98~124 行,ft5x06_write_regs 函數(shù),用于向 FT5426 寄存器寫入連續(xù)的數(shù)據(jù),也就是 I2C 寫函數(shù)。 第 133~138 行,ft5x06_write_reg 函數(shù),對 ft5x06_write_regs 函數(shù)的簡單封裝,向 FT5426 指定寄存器寫入一個數(shù)據(jù),用于配置 FT5426。 第 146~207 行,ft5x06_handler 函數(shù),觸摸屏中斷服務(wù)函數(shù),觸摸點坐標的上報就是在此函數(shù)中完成的。第 162 行通過 ft5x06_read_regs 函數(shù)讀取 FT5426 的所有觸摸點信息寄存器數(shù)據(jù),從 0X02 這個地址開始,一共 29 個寄存器。第 168~199 行的 for 循環(huán)就是一個一個的上報觸摸點坐標數(shù)據(jù),使用 Type B 時序,這個我們已經(jīng)在前面說了很多次了。最后在 202 行通過 input_sync 函數(shù)上報 SYN_REPORT 事件。如果理解了前面講解的 Type B 時序,那么此函數(shù)就很好看懂。 第 215~241 行 , ft5x06_ts_irq 函數(shù),初始化 FT5426 的中斷 IO ,第 221 行使用devm_gpio_request_one 函數(shù)申請中斷 IO。第 232 行使用函數(shù) devm_request_threaded_irq 申請中斷,中斷處理函數(shù)為 ft5x06_handler。 第 250~307 行,當 I2C 設(shè)備與驅(qū)動匹配以后此函數(shù)就會執(zhí)行,一般在此函數(shù)中完成一些初始化工作。我們重點來看一下 277~299 行是關(guān)于 input_dev 設(shè)備的初始化,第 277~284 行申請并簡單的初始化input_dev。第 286 和 288 行設(shè)置 input_dev 需要上報的事件為 EV_KEY 和 EV_ABS,需要上報的按鍵碼為BTN_TOUCH。EV_KEY 是按鍵事件,用于上報觸摸屏是否被按下,相當于把觸摸屏當做一個按鍵。EV_ABS 是觸摸點坐標數(shù)據(jù),BTN_TOUCH 表示將觸摸屏的按下和抬起用作 BTN_TOUCH 按鍵。第 290~293 行調(diào)用input_set_abs_params 函 數(shù) 設(shè) 置 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。第 294 行調(diào)用 input_mt_init_slots 函數(shù)初始化 slots,也就是最大觸摸點數(shù)量,F(xiàn)T5426是個 5 點電容觸摸芯片,因此一共 5 個 slot。最后在 299 行調(diào)用 input_register_device 函數(shù)向系統(tǒng)注冊input_dev。 第 314~319 行,當卸載驅(qū)動的時候 ft5x06_ts_remove 函數(shù)就會執(zhí)行,因為前面很多資源我們都是用“devm_”前綴函數(shù)來申請的,因此不需要手動釋放。此函數(shù)只需要調(diào)用 input_unregister_device 來釋放掉前面添加到內(nèi)核中的 input_dev。 第 320 行~結(jié)束,剩下的就是 I2C 驅(qū)動框架那一套。 ![]() ![]() |