Linux驱动开发十八.触摸屏驱动——1.驱动构建
前面一章我们已经可以使用屏幕来显示需要的信息了,下面就要使用屏幕的触摸功能了。我们使用的触摸屏是在LCD屏幕上附加了一层玻璃,触摸功能是通过这层玻璃来实现的,其实LCD的显示和触摸其实是没有什么关系的,只是将硬件封装在一起。LCD是通过eLCDIF寄存器来控制的,而触摸效果是通过I2C接口实现的。按理说我们应该先看下I2C在Linux下是如何驱动的,但是由于项目需求要先实现触摸屏控制的效果(移植tslib),就把I2C章节跳过去,先实现触摸屏的功能,然后再回头看看I2C在内核里的驱动是如何工作的。
两种触摸协议
因为我们在做裸机驱动的时候没有看触摸屏的框架,所以在了解这个框架以前,我们先借助教程看一下触摸屏驱动是怎么个原理
- 触摸屏是基于I2C接口实现的,所以需要一个IC,我们现在使用的正点原子弹ATK7016,使用的IC是FT5426,至于这个IC的特点我们可以在半导小芯上查一下。所谓的这个电容屏触摸驱动实质上就是这个I2C设备驱动。
- 触摸屏一共有4个引脚,除了两个用来走I2C的时钟和数据外,还有一个初始化和一个中断。我们需要用这个中断引脚告诉SOC发生了触摸事件,进而处理相关事件
- 电容屏可以记录是否有被触摸事件发生,并且通过绝对坐标来记录位置信息
- 电容屏理论上不需要校准,但是如果触摸玻璃和LCD屏幕贴合的过程没有完全对齐,那么也是需要重新校准的。
通过上面几点触摸驱动的特性,我们可以总结出来,我们要做的触摸屏的驱动从大框架上来看就是个I2C的设备驱动,一旦屏幕被触摸,FT5426给SOC触发一个外部中断,中断处理函数就会从IC里获取到触摸的相关信息。我们前面讲过input子系统,触摸屏属于输入设备,必然也属于这个input子系统。所以我们需要通过input子系统按照Linux的内核规定的规则上报一个输入事件。然后内核通过相关的协议去分析触摸的信息。
由于我们现在使用的触摸都是多点触控(Muti-touch),所以我们要大致了解一下两种MT协议:TypeA和TypeB。注意这两种协议都是MT协议,即多点触控协议,早期的内核是不支持的,可以看帮助文档(Documentation/input/multi-touch-protocol.txt)
TypeA协议
TypeA协议下多个触摸点不能被区分或者追踪,IC上报的原始数据,需要内核去处理甄别各个触摸点,这种协议现在基本上很少用到了。在上报事件的时候需要用一个input_mt_sync的函数将各个点隔开。上报的过程大致是这样的。
ABS_MT_POSITION_X x[0] ABS_MT_POSITION_Y y[0] SYN_MT_REPORT ABS_MT_POSITION_X x[1] ABS_MT_POSITION_Y y[1] SYN_MT_REPORT SYN_REPORT
在把各个点的坐标上报完成后同步一下就可以了。我们可以在内核里搜索一下,只有一个驱动用到了这种协议(drivers/input/touchscreen/st1232.c)
1 static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id) 2 { 3 struct st1232_ts_data *ts = dev_id; 4 struct st1232_ts_finger *finger = ts->finger; 5 struct input_dev *input_dev = ts->input_dev; 6 int count = 0; 7 int i, ret; 8 9 ret = st1232_ts_read_data(ts); 10 if (ret < 0) 11 goto end; 12 13 /* multi touch protocol */ 14 for (i = 0; i < MAX_FINGERS; i++) { 15 if (!finger[i].is_valid) 16 continue; 17 18 input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t); 19 input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x); 20 input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y); 21 input_mt_sync(input_dev); 22 count++; 23 } 24 25 /* SYN_MT_REPORT only if no contact */ 26 if (!count) { 27 input_mt_sync(input_dev); 28 if (ts->low_latency_req.dev) { 29 dev_pm_qos_remove_request(&ts->low_latency_req); 30 ts->low_latency_req.dev = NULL; 31 } 32 } else if (!ts->low_latency_req.dev) { 33 /* First contact, request 100 us latency. */ 34 dev_pm_qos_add_ancestor_request(&ts->client->dev, 35 &ts->low_latency_req, 36 DEV_PM_QOS_RESUME_LATENCY, 100); 37 } 38 39 /* SYN_REPORT */ 40 input_sync(input_dev); 41 42 end: 43 return IRQ_HANDLED; 44 }
从上面这段代码可以看出来,触摸事件的上报输入事件是定在中断服务函数中的。在每次发生触摸事件以后会触发一个中断,中断服务函数里通过一个for循环上报了每个触摸点的信息,并且每个点通过一个input_mt_sync函数隔离开。最后用了一input_sync()函数进行同步作为上报的结束语句。这种协议由于无法分别描述各个触摸点,我们使用的范围不会特别多,大概了解一下就可以了。
TypeB协议
和TypeA协议不同,TypeB协议针对了有硬件追踪以及分辨触摸点的触摸设备。这种协议是通过slot来更新一个点的信息的。现在使用的大部分触摸屏都是基于这个协议工作的。TypeB设备驱动会给每一个被识别出来的slot分配一个slot,后面借由这个slot上报触摸点的信息。还可以通过TRACKING_ID来增加、替换或者删除触摸点。slot是一个整形数据,一个非负的整数就是一个ID,表示一个有效的触摸点,如果是-1表示未使用slot,一个新的ID表示这时一个新的触摸点。
在发生了触摸事件以后,还是触发了中断,然后中断服务函数会上报ABS_MT事件(或者说是消息)给Linux内核,只有ABS_MT里的一系列事件是多点触摸相关的。
1 #define ABS_MT_SLOT 0x2f /* MT slot being modified */ 2 #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */ 3 #define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */ 4 #define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */ 5 #define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */ 6 #define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */ 7 #define ABS_MT_POSITION_X 0x35 /* Center X touch position */ 8 #define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */ 9 #define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */ 10 #define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */ 11 #define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */ 12 #define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */ 13 #define ABS_MT_DISTANCE 0x3b /* Contact hover distance */ 14 #define ABS_MT_TOOL_X 0x3c /* Center X tool position */ 15 #define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */
在上面一系列ABS_MT的相关事件中,我们最常使用的就是SLOT、POSITION_X、POSITION_Y以及TRACKING_ID这几个。其中POSITION用来上报触摸点的绝对坐标位置,SLOT用来上报触摸点ID,TRACKING_ID可以用来区分触摸点。对于TypeB类型的设备,我们就要通过函数input_mt_slot上报事件
1 static inline void input_mt_slot(struct input_dev *dev, int slot) 2 { 3 input_event(dev, EV_ABS, ABS_MT_SLOT, slot); 4 }
函数有两个参数,第一个是input设备,另外一个就是slot,可以从函数定义看出来,最终还是上报了一个EV_ABS事件,并且通过slot参数告诉当前更新的是哪个触摸点,也就是slot的数据。事件上报完成以后都要使用input_sync()函数进行同步。两个协议最大的区别,就是TypeB可以区分出来触摸点,就可以发送较少的信息。硬件会接收到驱动分配的slot然后关联一个ID,然后监测是哪个ID对应的触摸点发生了变化并上报给驱动。驱动就改变这个slot的ABS_MT_TRACKING_ID然后失效对应的ID。比如我们要上报两个触摸点的信息
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
第一行上报了ABS_MT_SLOT事件,在上报坐标的时候要使用input_mt_slot函数上报当前触摸点的slot,触摸点的slot就是对应触摸点的ID,由触摸屏的IC提供。
第二行用函数将slot和ID进行对应,通过修改SLOT关联的ABS_MT_TRACKING_ID来完成触摸点的添加、替换或者删除。使用的函数是input_mt_report_slot_stat。
1 /** 2 * input_mt_report_slot_state() - report contact state 3 * @dev: input device with allocated MT slots 4 * @tool_type: the tool type to use in this slot 5 * @active: true if contact is active, false otherwise 6 * 7 * Reports a contact via ABS_MT_TRACKING_ID, and optionally 8 * ABS_MT_TOOL_TYPE. If active is true and the slot is currently 9 * inactive, or if the tool type is changed, a new tracking id is 10 * assigned to the slot. The tool type is only reported if the 11 * corresponding absbit field is set. 12 */ 13 void input_mt_report_slot_state(struct input_dev *dev, 14 unsigned int tool_type, bool active) 15 { 16 struct input_mt *mt = dev->mt; 17 struct input_mt_slot *slot; 18 int id; 19 20 if (!mt) 21 return; 22 23 slot = &mt->slots[mt->slot]; 24 slot->frame = mt->frame; 25 26 if (!active) { 27 input_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); 28 return; 29 } 30 31 id = input_mt_get_value(slot, ABS_MT_TRACKING_ID); 32 if (id < 0 || input_mt_get_value(slot, ABS_MT_TOOL_TYPE) != tool_type) 33 id = input_mt_new_trkid(mt); 34 35 input_event(dev, EV_ABS, ABS_MT_TRACKING_ID, id); 36 input_event(dev, EV_ABS, ABS_MT_TOOL_TYPE, tool_type); 37 } 38 EXPORT_SYMBOL(input_mt_report_slot_state);
函数的第三个参数active是个布尔量,true的时候一般表示当前是连续的触摸,Linux内核会自动分配一个ABS_MT_TRACKING_ID的值,Flase时候一般就是触摸点弹起,input子系统就给slot一个-1,表示触摸点移出。参数type_tool在内核中给我们一组宏可以使用,用来表示触摸屏幕是用的手指还是笔或者手掌什么的。
1 /* 2 * MT_TOOL types 3 */ 4 #define MT_TOOL_FINGER 0x00 5 #define MT_TOOL_PEN 0x01 6 #define MT_TOOL_PALM 0x02 7 #define MT_TOOL_DIAL 0x0a 8 #define MT_TOOL_MAX 0x0f
最后使用上报x、y的坐标位置就可以了(可以使用input_report_abs上报或者直接使用inpur_event)。然后上报第二个点的信息(slot 1)。过程有些不清楚,我们可以参考内核里已经有的驱动,看看是怎么做的(可以直接搜索input_mt_slot或者input_mt_report_slot_state函数,随便找个.c的驱动文件)。这里看一下/rivers/input/touchscreen/chipone_icn8318.c这个驱动文件,顺便可以按照这个思路完成我们的触摸屏驱动。
1 static irqreturn_t icn8318_irq(int irq, void *dev_id) 2 { 3 struct icn8318_data *data = dev_id; 4 struct device *dev = &data->client->dev; 5 struct icn8318_touch_data touch_data; 6 int i, ret, x, y; 7 8 ret = icn8318_read_touch_data(data->client, &touch_data); 9 if (ret < 0) { 10 dev_err(dev, "Error reading touch data: %d\n", ret); 11 return IRQ_HANDLED; 12 } 13 14 if (touch_data.softbutton) { 15 /* 16 * Other data is invalid when a softbutton is pressed. 17 * This needs some extra devicetree bindings to map the icn8318 18 * softbutton codes to evdev codes. Currently no known devices 19 * use this. 20 */ 21 return IRQ_HANDLED; 22 } 23 24 if (touch_data.touch_count > ICN8318_MAX_TOUCHES) { 25 dev_warn(dev, "Too much touches %d > %d\n", 26 touch_data.touch_count, ICN8318_MAX_TOUCHES); 27 touch_data.touch_count = ICN8318_MAX_TOUCHES; 28 } 29 30 for (i = 0; i < touch_data.touch_count; i++) { 31 struct icn8318_touch *touch = &touch_data.touches[i]; 32 bool act = icn8318_touch_active(touch->event); 33 34 input_mt_slot(data->input, touch->slot); 35 input_mt_report_slot_state(data->input, MT_TOOL_FINGER, act); 36 if (!act) 37 continue; 38 39 x = be16_to_cpu(touch->x); 40 y = be16_to_cpu(touch->y); 41 42 if (data->invert_x) 43 x = data->max_x - x; 44 45 if (data->invert_y) 46 y = data->max_y - y; 47 48 if (!data->swap_x_y) { 49 input_event(data->input, EV_ABS, ABS_MT_POSITION_X, x); 50 input_event(data->input, EV_ABS, ABS_MT_POSITION_Y, y); 51 } else { 52 input_event(data->input, EV_ABS, ABS_MT_POSITION_X, y); 53 input_event(data->input, EV_ABS, ABS_MT_POSITION_Y, x); 54 } 55 } 56 57 input_mt_sync_frame(data->input); 58 input_sync(data->input); 59 60 return IRQ_HANDLED; 61 }
和前面的TypeA协议一样,上报事件的过程也是在中断处理函数中。设备是I2C接口的,第8行从I2C接口从IC读取了数据。9-10行对读取I2C的过程进行判断,如果读取数据过程异常就退出中断服务函数。
14-22行应该和实际硬件结构有关系,当反生某些事件后处理相关事件,这个过程我们不用考虑
24-28行是判定一下读取到的触摸点数和最大的点数进行比较,如果超过了就进行相对应的处理
30-55行是我们着重要关注的地方,驱动通过一个for循环来对每个触摸点进行处理,先是第34行使用input_mt_slot上报要处理的触摸点,35行是判定触摸点的情况,由于不知道这个硬件的实际状态,我感觉如果触摸抬起时候act就成false了,在36行的if语句里进行非运算就跳出循环。如果是按下,就执行for循环的循环体。
39-47行是对每个按下的坐标进行分析,获取x和y的绝对坐标并对其进行判定是否符合实际硬件能力(肯定不能超过屏幕分辨率)。
48-53行是上报x和y的绝对坐标
for循环一共执行次数和按下的点数一致。循环体执行完毕后通过58行的input_sync同步。
构建自己的触摸屏驱动
下面我们就参考前面的驱动来构建自己的驱动!先回忆一下几个关键点:
触摸屏是通过FT5426和SOC进行I2C通讯的。硬件可以看下电路图
有我们要关注的就是I2C2_SDA/SCL,实现的I2C通讯。另外还有CT_INT用来产生中断还有个RESET进行复位。
触摸屏驱动也是个input驱动,属于platform驱动框架。所以我们要先完成我们的platform驱动框架
platform框架
platform框架很简单
1 static int ft5426_probe(struct i2c_client *client, 2 const struct i2c_device_id *id) 3 { 4 int ret = 0; 5 printk("ft5426 probe\r\n"); 6 7 return ret; 8 } 9 10 11 static int ft5426_remove(struct i2c_client *client) 12 { 13 return 0; 14 } 15 16 static struct i2c_device_id ft5426_id[] = { 17 {"edt-ft5426",0}, 18 {} 19 }; 20 21 static struct of_device_id ft5426_of_match[] = { 22 {.compatible = "edt,det-ft5426"}, 23 {} 24 }; 25 26 static struct i2c_driver ft5426_driver = { 27 .probe = ft5426_probe, 28 .remove = ft5426_remove, 29 .driver = { 30 .name = "edt_ft5426", 31 .owner = THIS_MODULE, 32 .of_match_table = of_match_ptr(ft5426_of_match), 33 }, 34 .id_table = ft5426_id, 35 }; 36 37 static int __init ft5426_init(void) 38 { 39 int ret = 0; 40 ret = i2c_add_driver(&ft5426_driver); 41 return ret; 42 } 43 44 static void __exit ft5426_exit(void) 45 { 46 i2c_del_driver(&ft5426_driver); 47 } 48 49 module_init(ft5426_init); 50 module_exit(ft5426_exit); 51 MODULE_LICENSE("GPL"); 52 MODULE_AUTHOR("AC");
前面的头文件这里就先不放了,整个驱动过程就是在init函数里(37-42行)里使用i2c_add_driver加载了platform驱动。platform驱动在加载的时候通过of_match_table去设备树里索引设备信息。这个过程前面一直在用,这里就不再说了。下面就要去根据硬件去设备树里完成设备信息。
设备树信息
下面就要根据实际情况去设备树里完善相对应的信息。因为我们的触摸屏是接在了I2C2接口下,我们就要在I2C2节点下去完成触摸屏对应的设备树信息。
1 &i2c2 { 2 clock_frequency = <100000>; 3 pinctrl-names = "default"; 4 pinctrl-0 = <&pinctrl_i2c2>; 5 status = "okay"; 6 7 /*触摸七寸,FT5426*/ 8 ft5426:ft5426@38 { 9 compatible = "edt,edt-ft5426"; 10 reg = <0x38>; 11 pinctrl-name = "default"; 12 pinctrl-0 = <&pinctrl_tsc 13 &pinctrl_tsc_reset>; 14 interrupt-parent = <&gpio1>; 15 interrupts = <9 0>; 16 reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; 17 interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; 18 status = "okay"; 19 };
上面的设备树信息就是FT5426对应的设备树信息。主要有几点要说一下:
- compatible里的值必须和驱动里的match_table里一致,才能在挂载驱动的时候和设备匹配
- pinctrl里要把中断和复位的pin设置好。然后要把gpio的信息设置好,特别是中断和复位的gpio相关信息。
- 设置好中断和复位gpio的gpio特性
pinctrl里的设置也要做好。
复位的GPIO是在GPIO5组里的,要在iomuxc_snvs组里
1 &iomuxc_snvs { 2 pinctrl-names = "default_snvs"; 3 pinctrl-0 = <&pinctrl_hog_2>; 4 imx6ul-evk { 5 6 /*触摸屏RST*/ 7 pinctrl_tsc_reset:tsc_reset{ 8 fsl,pins = < 9 MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0 10 >; 11 };
复位就是一个普通的IO作用,所以电气属性直接就给0x10B0就可以。
中断是GPIO1里的直接在iomuxc节点下设置就可以
1 &iomuxc { 2 pinctrl-names = "default"; 3 pinctrl-0 = <&pinctrl_hog_1>; 4 5 /*触摸屏INTpinctrl设置*/ 6 pinctrl_tsc: tscgrp { 7 fsl,pins = < 8 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080 9 >; 10 }; 11 . 12 . 13 .
在更新完了设备树一定要在整个文件中搜索一下GPIO有没有被使用的,防止冲突。编译完成以后用新的设备树文件启动,加载驱动模块
如果能通过probe函数打印出信息,,就说明整体框架是没问题的。
初始化
在完成了大致框架以后我们要对外部IC连接的GPIO进行初始化。由于I2C我们直接使用了内核的驱动,I2C相关的时钟以及数据线就不用管了,这里要做的就是中断以及reset的初始化。先看一下具体的代码构成
1 struct ft5426_dev { 2 struct device_node *nd; 3 int irq_pin,reset_pin; 4 int irq_num; 5 struct i2c_clinet *client; 6 void *private_data; 7 }; 8 9 static struct ft5426_dev ft5426_dev; 10 11 12 static int ft5426_read_regs(struct ft5426_dev *dev,u8 reg ,void *val , int len) 13 { 14 struct i2c_msg msg[2]; 15 struct i2c_client *client = (struct i2c_client*)dev->private_data; 16 return 0; 17 } 18 19 20 static int ft5426_write_regs(struct ft5426_dev *dev, u8 reg, u8 *buf, u8 len) 21 { 22 return 0; 23 } 24 25 static void ft5426_write_reg(struct ft5426_dev *dev, u8 reg, u8 data) 26 { 27 u8 buf = 0; 28 buf = data; 29 ft5426_write_regs(dev,reg,&buf,1); 30 } 31 32 33 //复位 34 static int ft5426_ts_reset(struct i2c_client *client, struct ft5426_dev *dev) 35 { 36 int ret = 0; 37 38 if(gpio_is_valid(dev->reset_pin)){ //检查IO是否有效 39 ret = devm_gpio_request_one(&client->dev, dev->reset_pin, 40 GPIOF_OUT_INIT_LOW, "edt-ft5426 reset"); 41 42 if(ret){ 43 return ret; 44 } 45 46 msleep(5); 47 gpio_set_value(dev->reset_pin,1); 48 msleep(300); 49 } 50 return 0; 51 } 52 53 54 //ft6426中断处理函数 55 static irqreturn_t ft5426_handler(int irq,void *dev) 56 { 57 printk("ft5426 handler\r\n"); 58 return IRQ_HANDLED; 59 } 60 61 62 63 //中断初始化 64 static int ft5426_ts_irq(struct i2c_client *client,struct ft5426_dev *dev) 65 { 66 int ret = 0; 67 68 //申请中断GPIO 69 if(gpio_is_valid(dev->irq_pin)){ 70 ret = devm_gpio_request_one(&client->dev, dev->irq_pin, 71 GPIOF_IN,"edt-ft5426 irq"); 72 if(ret){ 73 dev_err(&client->dev, 74 "failed to request GPIO %d,error%d\n",dev->irq_pin,ret); 75 return ret; 76 } 77 } 78 79 //申请中断,client->irq 就是IO中断 80 ret = devm_request_threaded_irq(&client->dev,client->irq,NULL, 81 ft5426_handler,IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 82 client->name,&ft5426_dev); 83 if(ret){ 84 dev_err(&client->dev, 85 "Unable to request touchscreen IRQ\n"); 86 return ret; 87 } 88 return 0; 89 } 90 91 static int ft5426_probe(struct i2c_client *client, 92 const struct i2c_device_id *id) 93 { 94 int ret = 0; 95 printk("ft5426 probe\r\n"); 96 ft5426_dev.client = client; 97 98 ft5426_dev.irq_pin = of_get_named_gpio(client->dev.of_node,"interrupt-gpios",0); 99 ft5426_dev.reset_pin = of_get_named_gpio(client->dev.of_node,"reset_gpios",0); 100 101 ft5426_ts_reset(client,&ft5426_dev); 102 ft5426_ts_irq(client,&ft5426_dev); 103 104 return ret; 105 } 106 107 108 static int ft5426_remove(struct i2c_client *client) 109 { 110 return 0; 111 } 112 113 static struct i2c_device_id ft5426_id[] = { 114 {"edt-ft5426",0}, 115 {} 116 }; 117 118 static struct of_device_id ft5426_of_match[] = { 119 {.compatible = "edt,det-ft5426"}, 120 {} 121 }; 122 123 static struct i2c_driver ft5426_driver = { 124 .probe = ft5426_probe, 125 .remove = ft5426_remove, 126 .driver = { 127 .name = "edt_ft5426", 128 .owner = THIS_MODULE, 129 .of_match_table = of_match_ptr(ft5426_of_match), 130 }, 131 .id_table = ft5426_id, 132 }; 133 134 static int __init ft5426_init(void) 135 { 136 int ret = 0; 137 ret = i2c_add_driver(&ft5426_driver); 138 return ret; 139 } 140 141 static void __exit ft5426_exit(void) 142 { 143 i2c_del_driver(&ft5426_driver); 144 }
上面的代码掐头去尾留下来中间的部分。
1-7行重新定义了设备结构体。
12-30行几个函数用来实现I2C的数据传输,函数只是放了个函数名,没有具体的函数内容,具体I2C数据传输这一部分可以先不考虑。
34-51行是reset信号执行的操作,主要内容就是先请求一个GPIO,并对GPIO进行判断,检查是否有效,没问题的话就在申请的时候就默认输出为低电平进行复位,低电平通过msleep函数保持300ms后拉高,停止复位。
63-89行是中断初始化的过程,函数前半部分是GPIO的初始化,后面通过一个devm_request_threaded_irq函数申请了中断,这个函数可以多说一句,和我们前面用到request_irq不太一样,两个函数的最终目的都是用来申请中断,先说一下这个多出来的threaded,顾名思义是和线程相关的,因为正常情况下外部中断的优先级都是比较高的,不论什么情况下只要有外部中断事件发生,内核都会将当前的任务停止然后去执行中断服务函数。如果中断的发生比较频繁,那么内核就会频繁的切换去处理中断事件,于是正常的任务就无法处理。我们使用的那个加了threaded的函数就是中断线程化,主要用来保证比较耗时的中断服务和正常的进程能够公平竞争(即便是下半部处理也是优于进程的,无法保证进程的正常运行)。
对于我们使用的这个IC外设来说,只要把手放在触摸屏上,就会产生一个外部中断,中断处理函数中会要求内核通过I2C协议去IC中获取触摸点相关信息然后上报输入事件。由于I2C的最大速率只有400KHz,不断的产生中断就会让处理器消耗较高的资源在处理相关中断服务上。而这个外部中断相对来说又没有那么重要,我们就可以把这个触摸事件线程化。可以看下内核里的触摸屏驱动,有好些公司都采用了这种方法来构建自己的触摸屏驱动。
函数还有个前缀devm_,在Linux中几乎所有的资源申请类的函数都是成对出现的,先是申请,最后释放。不管是GPIO还是中断还是什么的,都是这么个套路。但是通过加了devm_前缀的函数在申请资源以后是不需要手动卸载释放掉,那么久省略了很多需要借助goto完成的异常处理,能够简化代码的结构。
从91行开始就是probe函数了,在函数中我们先对设备结构体ft5426初始化,包括I2C的client、GPIO获取,然后再初始化中断、reset,就完成了基础的框架结构。
重新make一下,并加载新的驱动模块,看看效果:
加载完驱动以后,一旦触摸屏幕,就会进入中断服务函数然后打印出调试信息。
到此为止,驱动的框架已经完成了一半了!
I2C数据传输
下面就要我们在中断服务函数中对实际的触摸信息进行上报了。首先我们要获取到IC里的触摸信息,这需要I2C协议来帮我们完成。(这里不知道为啥有个问题)
首先是I2C相关的读取和写入,因为我们还没学习I2C在Linux下是如何驱动的,我们直接套用现有的程序模板
1 /* 2 * @description : 从FT5426读取多个寄存器数据 3 * @param - dev: ft5426设备 4 * @param - reg: 要读取的寄存器首地址 5 * @param - val: 读取到的数据 6 * @param - len: 要读取的数据长度 7 * @return : 操作结果 8 */ 9 static int ft5426_read_regs(struct ft5426_dev *dev, u8 reg, void *val, int len) 10 { 11 int ret; 12 int i = 0; 13 struct i2c_msg msg[2]; 14 struct i2c_client *client = (struct i2c_client *)dev->client; 15 16 /* msg[0]为发送要读取的首地址 */ 17 msg[0].addr = client->addr; /* ft5426地址 */ 18 msg[0].flags = 0; /* 标记为发送数据 */ 19 msg[0].buf = ® /* 读取的首地址 */ 20 msg[0].len = 1; /* reg长度*/ 21 22 /* msg[1]读取数据 */ 23 msg[1].addr = client->addr; /* ft5426地址 */ 24 msg[1].flags = I2C_M_RD; /* 标记为读取数据*/ 25 msg[1].buf = val; /* 读取数据缓冲区 */ 26 msg[1].len = len; /* 要读取的数据长度*/ 27 28 ret = i2c_transfer(client->adapter, msg, 2); 29 if(ret == 2) { 30 ret = 0; 31 } else { 32 ret = -EREMOTEIO; 33 } 34 return ret; 35 } 36 37 /* 38 * @description : 向ft5426多个寄存器写入数据 39 * @param - dev: ft5426设备 40 * @param - reg: 要写入的寄存器首地址 41 * @param - val: 要写入的数据缓冲区 42 * @param - len: 要写入的数据长度 43 * @return : 操作结果 44 */ 45 static s32 ft5426_write_regs(struct ft5426_dev *dev, u8 reg, u8 *buf, u8 len) 46 { 47 u8 b[256]; 48 struct i2c_msg msg; 49 struct i2c_client *client = (struct i2c_client *)dev->client; 50 51 b[0] = reg; /* 寄存器首地址 */ 52 memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */ 53 54 msg.addr = client->addr; /* ft5426地址 */ 55 msg.flags = 0; /* 标记为写数据 */ 56 57 msg.buf = b; /* 要写入的数据缓冲区 */ 58 msg.len = len + 1; /* 要写入的数据长度 */ 59 60 return i2c_transfer(client->adapter, &msg, 1); 61 } 62 63 /* 64 * @description : 向ft5426指定寄存器写入指定的值,写一个寄存器 65 * @param - dev: ft5426设备 66 * @param - reg: 要写的寄存器 67 * @param - data: 要写入的值 68 * @return : 无 69 */ 70 static void ft5426_write_reg(struct ft5426_dev *dev, u8 reg, u8 data) 71 { 72 u8 buf = 0; 73 buf = data; 74 ft5426_write_regs(dev, reg, &buf, 1); 75 } 76 77 static int ft5426_read_reg(struct ft5426_dev *dev, u8 reg) 78 { 79 u8 data = 0; 80 ft5426_read_regs(dev,reg,&data,1); 81 return data; 82 }
这里现在存在一个问题:
在初始化probe函数里,我们通过调用ft5426_write_reg和ft5426_read_reg对触摸屏控制IC写入或读取1个寄存器,但是在进行下面的操作以后
ft5426_ts_reset(client,&ft5426_dev); ft5426_ts_irq(client,&ft5426_dev); //ft5426初始化 ft5426_write_reg(&ft5426_dev,FT5x06_DEVICE_MODE_REG,0); //进入正常模式 ft5426_write_reg(&ft5426_dev,FT5426_IDG_MODE_REG,1); //FT5426中断模式 value = ft5426_read_reg(&ft5426_dev,FT5426_IDG_MODE_REG); printk("read ret=%#x\r\n",value);
我们把写入寄存器以后再重新读出来,虽然写入了1,但是打印出来的还是0
测试了下程序,在中断的过程中读第一个点的Y低8位坐标(0x06),是可以的,不知道是不是IC版本不一样这个FT5426_IDG_MODE_REG(0xA4)寄存器是不是不能读
并且驱动完成以后是没问题的。
input子系统初始化
input子系统的初始化也要在probe函数中(input注册完就完成了整个probe函数了)
1 static int ft5426_probe(struct i2c_client *client, 2 const struct i2c_device_id *id) 3 { 4 int ret = 0; 5 int value = 0; 6 ft5426_dev.client = client; 7 8 //of方法获取GPIO资源时候一定要注意!!!特别是名称里下划线和-一定要注意!!! 9 ft5426_dev.irq_pin = of_get_named_gpio(client->dev.of_node,"interrupt-gpios",0); 10 ft5426_dev.reset_pin = of_get_named_gpio(client->dev.of_node,"reset-gpios",0); 11 12 ft5426_ts_reset(client,&ft5426_dev); 13 ft5426_ts_irq(client,&ft5426_dev); 14 15 //ft5426初始化 16 ft5426_write_reg(&ft5426_dev,FT5x06_DEVICE_MODE_REG,0); //进入正常模式 17 ft5426_write_reg(&ft5426_dev,FT5426_IDG_MODE_REG,1); //FT5426中断模式 18 19 //input框架初始化 20 ret = ft5426_dev.input_dev = devm_input_allocate_device(&client->dev); 21 if(ret) 22 goto fail; 23 24 ft5426_dev.input_dev->name = client->name; 25 ft5426_dev.input_dev->id.bustype = BUS_I2C; 26 ft5426_dev.input_dev->dev.parent = &client->dev; 27 28 __set_bit(EV_SYN,ft5426_dev.input_dev->evbit); 29 __set_bit(EV_KEY,ft5426_dev.input_dev->evbit); 30 __set_bit(EV_ABS,ft5426_dev.input_dev->evbit); 31 __set_bit(BTN_TOUCH,ft5426_dev.input_dev->evbit); 32 33 //单点触摸 34 input_set_abs_params(ft5426_dev.input_dev,ABS_X,0,1024,0,0); //1024是x最大值 35 input_set_abs_params(ft5426_dev.input_dev,ABS_Y,0,600,0,0); //600是y最大值 36 37 //多点触摸 38 input_mt_init_slots(ft5426_dev.input_dev,MAX_SUPPORT_POINTS,0); 39 input_set_abs_params(ft5426_dev.input_dev,ABS_MT_POSITION_X,0,1024,0,0); 40 input_set_abs_params(ft5426_dev.input_dev,ABS_MT_POSITION_Y,0,600,0,0); 41 42 ret = input_register_device(ft5426_dev.input_dev); 43 44 return ret; 45 46 fail: 47 return ret; 48 }
input系统初始化的过程没什么可说的,主要就是为上报坐标提前定义了整个坐标系(34-40行),并且为了通用性我把单点的触摸和多点的触摸都定义好了。
input_dev对象在加载完成后要记得在remove函数中卸载!
static int ft5426_remove(struct i2c_client *client) { input_unregister_device(ft5426_dev.input_dev); return 0; }
注意我们定义的input_dev对象本身就是指针形式(忘记放修改后的设备dev结构体了)
1 struct ft5426_dev { 2 struct device_node *nd; 3 int irq_pin,reset_pin; 4 int irq_num; 5 struct i2c_clinet *client; 6 struct input_dev *input_dev; 7 void *private_data; 8 };
所以在使用input_register_device函数的时候传参的过程中没有加取址符。
上报事件
上面的整个过程没有什么复杂的,就是把platform框架、input子系统、中断、GPIO什么的整合到了一起,下面是我们这一章比较关注的内容:上报事件
我们先要看下FT5426这个IC里定义的寄存器结构
我们需要读取到就是从0x02开始到0x1E结束的所有寄存器,但是注意寄存器号是不相连的,整个地址结构是这样的(X、Y地址高位里面还包括了状态以及ID,这里省略了没写出来)
看下每个点和下一个点之间的数据寄存器的地址是不想连的,空了2个。所以我们读取寄存器的个数应该是0x1E-0x02+1=29个。假设我们用一个readbuf来缓存读取到的数据,一共5个点,我们可以用个for循环来分割这个readbuf
for(i=0;i<=触摸点数;i++) { u8 *buf = &readbuf[i*每个点的寄存器数(6) + 起始偏置(1)]; }
每个点使用了4个物理寄存器来保存信息,然后还空出来了2个,所以在循环体中我们让数组的指针往后移动了6个,然后由于我们是从0x02开始读取的数据,但是实际上第一个点的数据地址是0x03,所以有个值为1的偏置量。我们在循环体里对这个buf进行处理就可以分析出来每个点的信息了。
把整个中断处理函数的全部放出来,可以根据后面的注释看下上报过程
1 static irqreturn_t ft5426_handler(int irq,void *dev) 2 { 3 // printk("\r\n"); 4 // printk("ft5426 handler\r\n"); 5 //从ft5406读取触摸点信息 6 struct ft5426_dev *multidata = dev; 7 u8 readbuf[29]; 8 int i,type,x,y,id; 9 int offset,tplen; 10 bool down; 11 12 //for循环使用,每个触摸点由6个寄存器来保存触摸值(寄存器地址不连续,6个便于计算) 13 //偏移量为1,第一组从0x02开始,但第一个寄存器是保存是否按下状态的。 14 offset = 1; 15 tplen = 6; 16 17 memset(readbuf,0,sizeof(readbuf)); //清除readbuf里的数据 18 19 ft5426_read_regs(multidata,FT5X06_TD_STATUS_REG,readbuf,29); //从0x02开始读取29个寄存器 20 21 for(i=0;i < MAX_SUPPORT_POINTS;i++){ 22 //提取出来每个触摸点的坐标并上报 23 u8 *buf = &readbuf[i*tplen + offset]; //buf[0]就是每个点第一个寄存器, 24 25 /* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0X03),各位描述如下: 26 * bit7:6 Event flag 0:按下 1:释放 2:接触 3:没有事件 27 * bit5:4 保留 28 * bit3:0 X轴触摸点的11~8位。 29 */ 30 type = buf[0] >> 6; //按下类型 31 if(type == TOUCH_EVENT_RESERVED) //值为RESERVED,即非按下,跳出本次循环 32 continue; 33 34 /* 我们所使用的触摸屏和FT5X06是反过来的,下面的注释是按照定义上的XY描述的,但是赋值和注释里相反 */ 35 //buf[2]地址为0x05为Y高4位,左移8位后或buf[3]的8位,与0x0fff值保留buf[2]的低4位 36 x = ((buf[2] << 8) | buf[3]) & 0x0fff; 37 //buf[0]地址为0x03为X高4位,左移8位后或buf[1]的8位,与0x0fff值保留buf[0]的低4位 38 y = ((buf[0] << 8) | buf[1]) & 0x0fff; 39 40 id = (buf[2] >> 4) & 0x0f; 41 down = type != TOUCH_EVENT_UP; //type不等于EVENT_UP,只能是按下或接触 42 43 44 //上报数据 45 input_mt_slot(multidata->input_dev,id); //上报哪个ID下的触摸信息 46 input_mt_report_slot_state(multidata->input_dev, MT_TOOL_FINGER,down); 47 48 if(!down){ 49 //如果down是假的,非按下状态就是没按下,跳出本次循环 50 continue; 51 } 52 53 input_report_abs(multidata->input_dev, ABS_MT_POSITION_X, x); 54 input_report_abs(multidata->input_dev, ABS_MT_POSITION_Y, y); 55 } 56 57 input_mt_report_pointer_emulation(multidata->input_dev,true); 58 input_sync(multidata->input_dev); 59 60 return IRQ_HANDLED; 61 62 }
整个函数的注释已经很清楚了,这就不再解释了。
加载新的模块,可以看到在/dev/input下面多出来了一个event文件,可以用我们前面使用的hexdump指令测试一下我们的新驱动程序,看一下上报的原始数据。
数据倒数第3列就是上报的事件类型,讲TypeB协议的时候里面说了有各种宏定义来描述事件类型,这里我们就看几个
#define ABS_MT_SLOT 0x2f /* MT slot being modified */ #define ABS_MT_POSITION_X 0x35 /* Center X touch position */ #define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */
可以看下,0x35后面的值就是X坐标,0x36后面的就是Y坐标。我们就可以根据这个值去估摸一下上报的坐标点数据是否正常。
1 hexdump /dev/input/event4 2 . 3 . 4 . 5 0000090 11a4 0000 fd77 000b 0003 0035 03ec 0000 6 00000a0 11a4 0000 fd77 000b 0003 0036 0255 0000 7 . 8 . 9 . 10 11 00001c0 11ed 0000 63f2 0003 0003 0035 0004 0000 12 00001d0 11ed 0000 63f2 0003 0003 0036 000a 0000 13 . 14 . 15 .
第5、6行是上报的触摸右下角时候上报的坐标,0x35后面的是0x03ec=1004,0x36后面的0x0255=597,对应的就是1024*600的分辨率坐标。
第11、12行是上报的左上角时候的坐标,0x35上报X坐标0x0004,0x36后面是0x000a大约就是零位。说明坐标是没问题的。
最后把最终的驱动放在最后
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/types.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/irq.h> #include <linux/interrupt.h> #include <linux/input.h> #include <linux/input/mt.h> #include <linux/i2c.h> #include <linux/delay.h> #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 ft5426_dev { struct device_node *nd; int irq_pin,reset_pin; int irq_num; struct i2c_clinet *client; struct input_dev *input_dev; void *private_data; }; static struct ft5426_dev ft5426_dev; static int ft5426_read_regs(struct ft5426_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; } static int ft5426_write_regs(struct ft5426_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); } // 写一个寄存器 static void ft5426_write_reg(struct ft5426_dev *dev, u8 reg, u8 data) { u8 buf = 0; buf = data; ft5426_write_regs(dev,reg,&buf,1); } //读一个寄存器 static u8 ft5426_read_reg(struct ft5426_dev *dev, u8 reg) { u8 data =0 ; ft5426_read_regs(dev,reg,&data,1); return data; } //复位 static int ft5426_ts_reset(struct i2c_client *client, struct ft5426_dev *dev) { int ret = 0; if(gpio_is_valid(dev->reset_pin)){ //检查IO是否有效 ret = devm_gpio_request_one(&client->dev, dev->reset_pin, GPIOF_OUT_INIT_LOW, "edt-ft5426 reset"); if(ret){ return ret; } msleep(5); gpio_set_value(dev->reset_pin,1); msleep(300); } return 0; } //ft6426中断处理函数 static irqreturn_t ft5426_handler(int irq,void *dev) { //从ft5406读取触摸点信息 struct ft5426_dev *multidata = dev; u8 readbuf[29]; int i,type,x,y,id; int offset,tplen; bool down; //for循环使用,每个触摸点由6个寄存器来保存触摸值(寄存器地址不连续,6个便于计算) //偏移量为1,第一组从0x02开始,但第一个寄存器是保存是否按下状态的。 offset = 1; tplen = 6; memset(readbuf,0,sizeof(readbuf)); //清除readbuf里的数据 ft5426_read_regs(multidata,FT5X06_TD_STATUS_REG,readbuf,29); //从0x02开始读取29个寄存器 for(i=0;i < MAX_SUPPORT_POINTS;i++){ //提取出来每个触摸点的坐标并上报 u8 *buf = &readbuf[i*tplen + offset]; //buf[0]就是每个点第一个寄存器, /* 以第一个触摸点为例,寄存器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) //值为RESERVED,即非按下,跳出本次循环 continue; /* 我们所使用的触摸屏和FT5X06是反过来的,下面的注释是按照定义上的XY描述的,但是赋值和注释里相反 */ //buf[2]地址为0x05为Y高4位,左移8位后或buf[3]的8位,与0x0fff值保留buf[2]的低4位 x = ((buf[2] << 8) | buf[3]) & 0x0fff; //buf[0]地址为0x03为X高4位,左移8位后或buf[1]的8位,与0x0fff值保留buf[0]的低4位 y = ((buf[0] << 8) | buf[1]) & 0x0fff; id = (buf[2] >> 4) & 0x0f; down = type != TOUCH_EVENT_UP; //type不等于EVENT_UP,只能是按下或接触 //上报数据 input_mt_slot(multidata->input_dev,id); //上报哪个ID下的触摸信息 input_mt_report_slot_state(multidata->input_dev, MT_TOOL_FINGER,down); if(!down){ //如果down是假的,非按下状态就是没按下,跳出本次循环 continue; } input_report_abs(multidata->input_dev, ABS_MT_POSITION_X, x); input_report_abs(multidata->input_dev, ABS_MT_POSITION_Y, y); } input_mt_report_pointer_emulation(multidata->input_dev,true); input_sync(multidata->input_dev); return IRQ_HANDLED; } //中断初始化 static int ft5426_ts_irq(struct i2c_client *client,struct ft5426_dev *dev) { int ret = 0; //申请中断GPIO if(gpio_is_valid(dev->irq_pin)){ ret = devm_gpio_request_one(&client->dev, dev->irq_pin, GPIOF_IN,"edt-ft5426 irq"); if(ret){ dev_err(&client->dev, "failed to request GPIO %d,error%d\n",dev->irq_pin,ret); return ret; } } //申请中断,client->irq 就是IO中断 ret = devm_request_threaded_irq(&client->dev,client->irq,NULL, ft5426_handler,IRQF_TRIGGER_FALLING | IRQF_ONESHOT, client->name,&ft5426_dev); if(ret){ dev_err(&client->dev, "Unable to request touchscreen IRQ\n"); return ret; } return 0; } static int ft5426_probe(struct i2c_client *client, const struct i2c_device_id *id) { int ret = 0; int value = 0; ft5426_dev.client = client; //of方法获取GPIO资源时候一定要注意!!!特别是名称里下划线和-一定要注意!!! ft5426_dev.irq_pin = of_get_named_gpio(client->dev.of_node,"interrupt-gpios",0); ft5426_dev.reset_pin = of_get_named_gpio(client->dev.of_node,"reset-gpios",0); ft5426_ts_reset(client,&ft5426_dev); ft5426_ts_irq(client,&ft5426_dev); //ft5426初始化 ft5426_write_reg(&ft5426_dev,FT5x06_DEVICE_MODE_REG,3); //进入正常模式 ft5426_write_reg(&ft5426_dev,FT5426_IDG_MODE_REG,1); //FT5426中断模式 //input框架初始化 ret = ft5426_dev.input_dev = devm_input_allocate_device(&client->dev); printk("ret=%d\r\n",ret); ft5426_dev.input_dev->name = client->name; ft5426_dev.input_dev->id.bustype = BUS_I2C; ft5426_dev.input_dev->dev.parent = &client->dev; __set_bit(EV_SYN,ft5426_dev.input_dev->evbit); __set_bit(EV_KEY,ft5426_dev.input_dev->evbit); __set_bit(EV_ABS,ft5426_dev.input_dev->evbit); __set_bit(BTN_TOUCH,ft5426_dev.input_dev->evbit); //单点触摸 input_set_abs_params(ft5426_dev.input_dev,ABS_X,0,1024,0,0); //1024是x最大值 input_set_abs_params(ft5426_dev.input_dev,ABS_Y,0,600,0,0); //600是y最大值 // //多点触摸 input_mt_init_slots(ft5426_dev.input_dev,MAX_SUPPORT_POINTS,0); input_set_abs_params(ft5426_dev.input_dev,ABS_MT_POSITION_X,0,1024,0,0); input_set_abs_params(ft5426_dev.input_dev,ABS_MT_POSITION_Y,0,600,0,0); ret = input_register_device(ft5426_dev.input_dev); return ret; } static int ft5426_remove(struct i2c_client *client) { input_unregister_device(ft5426_dev.input_dev); return 0; } static struct i2c_device_id ft5426_id[] = { {"edt-ft5426",0}, {} }; static struct of_device_id ft5426_of_match[] = { {.compatible = "edt,det-ft5426"}, {} }; static struct i2c_driver ft5426_driver = { .probe = ft5426_probe, .remove = ft5426_remove, .driver = { .name = "edt_ft5426", .owner = THIS_MODULE, .of_match_table = of_match_ptr(ft5426_of_match), }, .id_table = ft5426_id, }; static int __init ft5426_init(void) { int ret = 0; ret = i2c_add_driver(&ft5426_driver); return ret; } static void __exit ft5426_exit(void) { i2c_del_driver(&ft5426_driver); } module_init(ft5426_init); module_exit(ft5426_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("AC");
驱动使用
在下一章我们会借助一个第三方库来使用这个触摸屏驱动。在测试完成后直接把这个驱动文件添加到内核里,可以直接使用了!