Linux驱动.之 input i2c框架下,touch触摸屏驱动框架,以及两款芯片gslX680,ft5x0x,驱动源代码(一)

本文详细介绍了在S3C2440开发板上进行触摸屏驱动的实例开发,包括Linux输入子系统介绍、驱动实现步骤、硬件原理分析以及中断服务程序的编写。通过注册输入设备、初始化寄存器、申请中断等操作,实现了触摸屏的坐标转换和事件上报。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文链接:https://blog.youkuaiyun.com/weixin_55796564/article/details/120470202

一、touch触摸屏硬件原理
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1、裸机下,iic接口,驱动触摸屏。
本章实验在上一章例程的基础上完成,更改工程名字为“touchscreen”,用到的i2c接口,回去看,这里不展示,然后在bsp文件夹下创建名为“touchscreen”的文件。代码,只将如何设置驱动一个触摸屏。

在bsp/ touchscreen中新建bsp_ft5xx6.c和bsp_ft5xx6.h这两个文件,在bsp_ft5xx6.h中输入如下内容:

示例代码28.3.1 bsp_ft5xx6.h文件代码
1  #ifndef _FT5XX6_H
2  #define _FT5XX6_H
14 #include "imx6ul.h"
15 #include "bsp_gpio.h"
16 
17 /* 宏定义 */
18 #define FT5426_ADDR             	0X38	/* FT5426设备地址 	*/
19 
20 #define FT5426_DEVICE_MODE      	0X00  	/* 模式寄存器      	*/
21 #define FT5426_IDGLIB_VERSION   	0XA1  	/* 固件版本寄存器  	*/
22 #define FT5426_IDG_MODE         	0XA4   	/* 中断模式        	*/
23 #define FT5426_TD_STATUS        	0X02   	/* 触摸状态寄存器  	*/
24 #define FT5426_TOUCH1_XH        	0X03   	/* 触摸点坐标寄存器,
25                                                 * 一个触摸点用4个寄存器*/
26                                       
27 #define FT5426_XYCOORDREG_NUM  	30      /* 触摸点坐标寄存器数量 	*/
28 #define FT5426_INIT_FINISHED   	1       /* 触摸屏初始化完成     	*/
29 #define FT5426_INIT_NOTFINISHED	0       /* 触摸屏初始化未完成  	*/
30 
31 #define FT5426_TOUCH_EVENT_DOWN			0x00	/* 按下    	*/
32 #define FT5426_TOUCH_EVENT_UP   			0x01   	/* 释放   	*/
33 #define FT5426_TOUCH_EVENT_ON   			0x02   	/* 接触    	*/
34 #define FT5426_TOUCH_EVENT_RESERVED  	0x03   	/* 没有事件 	*/
35 
36 /* 触摸屏结构体 */
37 struct ft5426_dev_struc
38 {    
39  unsigned char initfalg;     	/* 触摸屏初始化状态 		*/
40  unsigned char intflag;      	/* 标记中断有没有发生 	*/
41  unsigned char point_num;    	/* 触摸点        		*/
42  unsigned short x[5];        	/* X轴坐标   			*/
43  unsigned short y[5];        	/* Y轴坐标   			*/
44 };
45 
46 extern struct ft5426_dev_struc ft5426_dev;
47 
48 /* 函数声明 */
49 void ft5426_init(void);
50 
51 void gpio1_io9_irqhandler(void);
52 unsigned char ft5426_write_byte(unsigned char addr,
unsigned char reg, 
unsigned char data);
53 unsigned char ft5426_read_byte(unsigned char addr,
unsigned char reg);
54 void ft5426_read_len(unsigned char addr,unsigned char reg,
unsigned char len,unsigned char *buf);
55 void ft5426_read_tpnum(void);
56 void ft5426_read_tpcoord(void);
57 #endif

文件bsp_ft5xx6.h文件中先是定义了FT5426的设备地址、寄存器地址和一些触摸点状态宏,然后在第37行定义了一个结构体ft5426_dev_struc,此结构体用来保存触摸信息,最后就是一些函数声明。接下来在bsp_ft5xx6.c中输入如下所示内容:

/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名   :  bsp_ft5xx6.c
作者     : 左忠凯
版本     : V1.0
描述     : 触摸屏驱动文件,触摸芯片为FT5xx6,
           包括FT5426和FT5406。
其他     : 无
论坛     : www.openedv.com
日志     : 初版V1.0 2019/1/21 左忠凯创建
***************************************************************/
1   #include "bsp_ft5xx6.h"
2   #include "bsp_i2c.h"
3   #include "bsp_int.h"
4   #include "bsp_delay.h"
5   #include "stdio.h"
6   
7   struct ft5426_dev_struc ft5426_dev;
8   
9   /*
10   * @description 	: 初始化触摸屏,其实就是初始化FT5426
11   * @param       	: 无
12   * @return      	: 无
13   */
14  void ft5426_init(void)
15  {
16      unsigned char reg_value[2];
17  
18      ft5426_dev.initfalg = FT5426_INIT_NOTFINISHED;
19  
20      /* 1、初始化IIC2 IO
21       * I2C2_SCL -> UART5_TXD
22       * I2C2_SDA -> UART5_RXD
23       */
24      IOMUXC_SetPinMux(IOMUXC_UART5_TX_DATA_I2C2_SCL, 1);
25      IOMUXC_SetPinMux(IOMUXC_UART5_RX_DATA_I2C2_SDA, 1);
26      IOMUXC_SetPinConfig(IOMUXC_UART5_TX_DATA_I2C2_SCL, 0x70B0);
27      IOMUXC_SetPinConfig(IOMUXC_UART5_RX_DATA_I2C2_SDA, 0X70B0);
28      
29      /* 2、初始化触摸屏中断IO和复位IO */
30      gpio_pin_config_t ctintpin_config;
31      IOMUXC_SetPinMux(IOMUXC_GPIO1_IO09_GPIO1_IO09,0); 
32      IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER9_GPIO5_IO09,0);
33      IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO09_GPIO1_IO09,0xF080);
34      IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER9_GPIO5_IO09,0X10B0);
35  
36      /* 中断IO初始化 */
37      ctintpin_config.direction = kGPIO_DigitalInput;
38      ctintpin_config.interruptMode = kGPIO_IntRisingOrFallingEdge;
39      gpio_init(GPIO1, 9, &ctintpin_config);
40  
41      GIC_EnableIRQ(GPIO1_Combined_0_15_IRQn); /* 使能GIC中对应的中断 */
42      system_register_irqhandler(GPIO1_Combined_0_15_IRQn, (system_irq_handler_t)gpio1_io9_irqhandler, NULL); /* 注册中断服务函数 */
43      gpio_enableint(GPIO1, 9);  /* 使能GPIO1_IO09的中断功能 */
44  
45      /* 复位IO初始化 */
46      ctintpin_config.direction=kGPIO_DigitalOutput;  
47      ctintpin_config.interruptMode=kGPIO_NoIntmode;  
48      ctintpin_config.outputLogic=1;                     
49      gpio_init(GPIO5, 9, &ctintpin_config); 
50  
51      /* 3、初始化I2C */
52      i2c_init(I2C2); 
53  
54      /* 4、初始化FT5426 */
55      gpio_pinwrite(GPIO5, 9, 0); 	/* 复位FT5426 		*/
56      delayms(20);
57      gpio_pinwrite(GPIO5, 9, 1); 	/* 停止复位FT5426 	*/
58      delayms(20);
59      ft5426_write_byte(FT5426_ADDR, FT5426_DEVICE_MODE, 0);
60      ft5426_write_byte(FT5426_ADDR, FT5426_IDG_MODE, 1);     
61      ft5426_read_len(FT5426_ADDR, FT5426_IDGLIB_VERSION, 2,reg_value);
62      printf("Touch Frimware Version:%#X\r\n", ((unsigned short)reg_value[0] << 8) + reg_value[1]);
63      ft5426_dev.initfalg = FT5426_INIT_FINISHED; /* 标记初始化完成 */
64      ft5426_dev.intflag = 0;
65  }
66  
67  /*
68   * @description 	: GPIO1_IO9最终的中断处理函数
69   * @param         	: 无
70   * @return       	: 无
71   */
72  void gpio1_io9_irqhandler(void)
73  { 
74      if(ft5426_dev.initfalg == FT5426_INIT_FINISHED)
75      {
76          //ft5426_dev.intflag = 1;
77          ft5426_read_tpcoord();
78      }
79      gpio_clearintflags(GPIO1, 9); /* 清除中断标志位 */
80  }
81  
82  /*
83   * @description 	: 向FT5426写入数据
84   * @param – addr	: 设备地址
85   * @param - reg 	: 要写入的寄存器
86   * @param – data	: 要写入的数据
87   * @return      	: 操作结果
88   */
89  unsigned char ft5426_write_byte(unsigned char addr,
unsigned char reg, 
unsigned char data)
90  {
91      unsigned char status=0;
92      unsigned char writedata=data;
93      struct i2c_transfer masterXfer;
94      
95      /* 配置I2C xfer结构体 */
96      masterXfer.slaveAddress = addr;   		/* 设备地址             	*/
97      masterXfer.direction = kI2C_Write;  	/* 写入数据             	*/
98      masterXfer.subaddress = reg;   			/* 要写入的寄存器地址  	*/
99      masterXfer.subaddressSize = 1;			/* 地址长度一个字节      */
100     masterXfer.data = &writedata;  			/* 要写入的数据        	*/
101     masterXfer.dataSize = 1;       			/* 写入数据长度1个字节	*/
102 
103     if(i2c_master_transfer(I2C2, &masterXfer))
104         status=1;
105         
106     return status;
107 }
108 
109 /*
110  * @description 	: 从FT5426读取一个字节的数据
111  * @param – addr	: 设备地址
112  * @param - reg 	: 要读取的寄存器
113  * @return      	: 读取到的数据。
114  */
115 unsigned char ft5426_read_byte(unsigned char addr,unsigned char reg)
116 {
117     unsigned char val=0;
118     
119     struct i2c_transfer masterXfer; 
120     masterXfer.slaveAddress = addr;     	/* 设备地址   			*/
121     masterXfer.direction = kI2C_Read;  		/* 读取数据           	*/
122     masterXfer.subaddress = reg;          	/* 要读取的寄存器地址   	*/
123     masterXfer.subaddressSize = 1;       	/* 地址长度一个字节     	*/
124     masterXfer.data = &val;                 	/* 接收数据缓冲区        */
125     masterXfer.dataSize = 1;                	/* 读取数据长度1个字节 	*/
126     i2c_master_transfer(I2C2, &masterXfer);
127     return val;
128 }
129 
130 /*
131  * @description 	: 从FT5429读取多个字节的数据
132  * @param – addr	: 设备地址
133  * @param - reg 	: 要读取的开始寄存器地址
134  * @param - len 	: 要读取的数据长度.
135  * @param - buf 	: 读取到的数据缓冲区
136  * @return      	: 无
137  */
138 void ft426_read_len(unsigned char addr,unsigned char reg,unsigned char len,unsigned char *buf)
139 {   
140     struct i2c_transfer masterXfer; 
141     
142     masterXfer.slaveAddress = addr;    		/* 设备地址        		*/
143     masterXfer.direction = kI2C_Read;    	/* 读取数据         		*/
144     masterXfer.subaddress = reg;          	/* 要读取的寄存器地址  	*/
145     masterXfer.subaddressSize = 1;       	/* 地址长度一个字节    	*/
146     masterXfer.data = buf;                 	/* 接收数据缓冲区        */
147     masterXfer.dataSize = len;             	/* 读取数据长度			*/
148     i2c_master_transfer(I2C2, &masterXfer);
149 } 
150 
151 /*
152  * @description 	: 读取当前触摸点个数
153  * @param       	: 无
154  * @return      	: 无
155  */
156 void ft5426_read_tpnum(void)
157 {
158     ft5426_dev.point_num = ft5426_read_byte(FT5426_ADDR, FT5426_TD_STATUS);
159 }
160 
161 /*
162  * @description 	: 读取当前所有触摸点的坐标
163  * @param       	: 无
164  * @return      	: 无
165  */
166 void ft5426_read_tpcoord(void)
167 {
168     unsigned char i = 0;
169     unsigned char type = 0;
170     //unsigned char id = 0;
171     unsigned char pointbuf[FT5426_XYCOORDREG_NUM];
172     
173     ft5426_dev.point_num = ft5426_read_byte(FT5426_ADDR, FT5426_TD_STATUS);
174 
175     /*
176      * 从寄存器FT5426_TOUCH1_XH开始,连续读取30个寄存器的值, 
177      * 这30个寄存器保存着5个点的触摸值,每个点占用6个寄存器。
178      */
179     ft5426_read_len(FT5426_ADDR, FT5426_TOUCH1_XH, FT5426_XYCOORDREG_NUM, pointbuf);
180     for(i = 0; i < ft5426_dev.point_num ; i++)
181     {
182         unsigned char *buf = &pointbuf[i * 6];

183         ft5426_dev.x[i] = ((buf[2] << 8) | buf[3]) & 0x0fff;
184         ft5426_dev.y[i] = ((buf[0] << 8) | buf[1]) & 0x0fff;
185         type = buf[0] >> 6; /* 获取触摸类型 */
186         //id = (buf[2] >> 4) & 0x0f;
187         if(type == FT5426_TOUCH_EVENT_DOWN || type == FT5426_TOUCH_EVENT_ON )/* 按下  */
188         {
189         
190         } else  {   /* 释放 */    
191             
192         }
193     }   
194 }

文件bsp_ft5xx6.c中有7个函数,我们依次来看一下这7个函数。第1个是函数ft5426_init,此函数是ft5426的初始化函数,此函数先初始化FT5426所使用的I2C2接口引脚、复位引脚和中断引脚。接下来使能了FT5426所使用的中断,并且注册了中断处理函数,最后初始化了I2C2和FT5426。第2个函数是gpio1_io9_irqhandler,这个是FT5426的中断引脚中断处理函数,在此函数中会读取FT5426内部的触摸数据。第3和第4个函数分别为ft5426_write_byte和ft5426_read_byte,函数ft5426_write_byte用于向FT5426的寄存器写入指定的值,函数ft5426_read_byte用于读取FT5426指定寄存器的值。第5个函数是ft5426_read_len,此函数也是从FT5426的指定寄存器读取数据,但是此函数是读取数个连续的寄存器。第6个函数是ft5426_read_tpnum,此函数用于获取FT5426当前有几个触摸点有效,也就是触摸点个数。最后一个函数是ft5426_read_tpcoord,此函数就是读取FT5426各个触摸点坐标值的。
最后在main.c中输入如下内容:

/**************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名   : main.c
作者     : 左忠凯
版本     : V1.0
描述     : I.MX6U开发板裸机实验20 触摸屏实验
其他     : I.MX6U-ALPHAL推荐使用正点原子-7寸LCD,此款LCD支持5点电容触摸,
            本节我们就来学习如何驱动LCD上的5点电容触摸屏。
论坛     : www.openedv.com
日志     : 初版V1.0 2019/1/21 左忠凯创建
**************************************************************/
1  #include "bsp_clk.h"
2  #include "bsp_delay.h"
3  #include "bsp_led.h"
4  #include "bsp_beep.h"
5  #include "bsp_key.h"
6  #include "bsp_int.h"
7  #include "bsp_uart.h"
8  #include "bsp_lcd.h"
9  #include "bsp_lcdapi.h"
10 #include "bsp_rtc.h"
11 #include "bsp_ft5xx6.h"
12 #include "stdio.h"
13 
14 /*
15  * @description 	: 使能I.MX6U的硬件NEON和FPU
16  * @param        	: 无
17  * @return       	: 无
18  */
19  void imx6ul_hardfpu_enable(void)
20 {
21  	uint32_t cpacr;
22  	uint32_t fpexc;
23 
24  	/* 使能NEON和FPU */
25  	cpacr = __get_CPACR();
26  	cpacr = (cpacr & ~(CPACR_ASEDIS_Msk | CPACR_D32DIS_Msk))
27        	 	|  (3UL << CPACR_cp10_Pos) | (3UL << CPACR_cp11_Pos);
28  	__set_CPACR(cpacr);
29  	fpexc = __get_FPEXC();
30  	fpexc |= 0x40000000UL;  
31  	__set_FPEXC(fpexc);
32 }
33 
34 /*
35  * @description  	: main函数
36  * @param        	: 无
37  * @return       	: 无
38  */
39 int main(void)
40 {
41      unsigned char i = 0;
42  	unsigned char state = OFF;
43 
44  	imx6ul_hardfpu_enable();  	/* 使能I.MX6U的硬件浮点	*/
45  	int_init();               	/* 初始化中断(一定要最先调用!) 	*/
46  	imx6u_clkinit();         	/* 初始化系统时钟              	*/
47  	delay_init();            	/* 初始化延时                  	*/
48  	clk_enable();            	/* 使能所有的时钟               	*/
49  	led_init();               	/* 初始化led                   	*/
50  	beep_init();              	/* 初始化beep                  	*/
51  	uart_init();              	/* 初始化串口,波特率115200 	*/
52  	lcd_init();               	/* 初始化LCD                   	*/      
53  	ft5426_init();           	/* 初始化触摸屏                 	*/ 
54  
55  	tftlcd_dev.forecolor = LCD_RED;
56  	lcd_show_string(50, 10, 400, 24, 24, 
(char*)"ALPHA-IMX6U TOUCH SCREEN TEST");  
57  	lcd_show_string(50, 40, 200, 16, 16, 
(char*)"TOUCH SCREEN TEST");  
58  	lcd_show_string(50, 60, 200, 16, 16, (char*)"ATOM@ALIENTEK");  
59  	lcd_show_string(50, 80, 200, 16, 16, (char*)"2019/3/27");  
60  	lcd_show_string(50, 110, 400, 16, 16,   (char*)"TP Num  :");  
61  	lcd_show_string(50, 130, 200, 16, 16,   (char*)"Point0 X:");  
62  	lcd_show_string(50, 150, 200, 16, 16,   (char*)"Point0 Y:");  
63  	lcd_show_string(50, 170, 200, 16, 16,   (char*)"Point1 X:");  
64 	 	lcd_show_string(50, 190, 200, 16, 16,   (char*)"Point1 Y:");  
65  	lcd_show_string(50, 210, 200, 16, 16,   (char*)"Point2 X:");  
66  	lcd_show_string(50, 230, 200, 16, 16,   (char*)"Point2 Y:");  
67  	lcd_show_string(50, 250, 200, 16, 16,   (char*)"Point3 X:");  
68  	lcd_show_string(50, 270, 200, 16, 16,   (char*)"Point3 Y:");  
69  	lcd_show_string(50, 290, 200, 16, 16,   (char*)"Point4 X:");  
70  	lcd_show_string(50, 310, 200, 16, 16,   (char*)"Point4 Y:");  
71  	tftlcd_dev.forecolor = LCD_BLUE;
72  	while(1)                    
73  	{
74      	lcd_shownum(50 + 72, 110, ft5426_dev.point_num , 1, 16);
75      	lcd_shownum(50 + 72, 130, ft5426_dev.x[0], 5, 16);
76      	lcd_shownum(50 + 72, 150, ft5426_dev.y[0], 5, 16);
77      	lcd_shownum(50 + 72, 170, ft5426_dev.x[1], 5, 16);
78      	lcd_shownum(50 + 72, 190, ft5426_dev.y[1], 5, 16);
79      	lcd_shownum(50 + 72, 210, ft5426_dev.x[2], 5, 16);
80      	lcd_shownum(50 + 72, 230, ft5426_dev.y[2], 5, 16);
81      	lcd_shownum(50 + 72, 250, ft5426_dev.x[3], 5, 16);
82      	lcd_shownum(50 + 72, 270, ft5426_dev.y[3], 5, 16);
83      	lcd_shownum(50 + 72, 290, ft5426_dev.x[4], 5, 16);
84      	lcd_shownum(50 + 72, 310, ft5426_dev.y[4], 5, 16);
85          
86      	delayms(10);
87      	i++;
88  
89      	if(i == 50)
90      	{	   
91          		i = 0;
92         	 	state = !state;
93          		led_switch(LED0,state); 
94      	}
95  	}
96  	return 0;
97 }

当我们用手指触摸屏幕的时候就会在LCD上显示出当前的触摸点和对应的触摸值,如图28.4.2.2所示:
在这里插入图片描述

二、Linux驱动touch触摸屏驱动框架

在这里插入图片描述
触摸点的信息通过一系列的ABS_MT事件(有的资料也叫消息)上报给linux内核,只有ABS_MT事件是用于多点触摸的,ABS_MT事件定义在文件include/uapi/linux/input.h中,相关事件如下所示:

示例代码64.1.1.1 ABS_MT事件
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()函数来隔离不同的触摸点数据信息,此函数原型如下所示:

void input_mt_sync(struct input_dev *dev)

此函数只要一个参数,类型为input_dev,用于指定具体的input_dev设备。input_mt_sync()函数会触发SYN_MT_REPORT事件,此事件会通知接收者获取当前触摸数据,并且准备接收下一个触摸点数据。

对于Type B类型的设备,上报触摸点信息的时候需要通过input_mt_slot()函数区分是哪一个触摸点,input_mt_slot()函数原型如下所示:

void input_mt_slot(struct input_dev *dev, int 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。

64.1.2 Type A触摸点信息上报时序
对于Type A类型的设备,
发送触摸点信息的时序如下所示,这里以2个触摸点为例:
示例代码64.1.2.1 Type A触摸点数据上报时序

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类型的多点触摸驱动的时候就需要按照示例代码64.1.2.1中的时序上报坐标信息。Linux内核里面也有Type A类型的多

示例代码64.1.2.2 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事件

64.1.3 Type B触摸点信息上报时序
对于Type B类型的设备,发送触摸点信息的时序如下所示,这里以2个触摸点为例:

示例代码64.1.3.1 Type B触摸点数据上报时序
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来完成。
第58行,和第14行类似,只是换成了上报触摸点0的(X,Y)坐标信息
第9行,当所有的触摸点坐标都上传完毕以后就得发送SYN_REPORT事件,使用input_sync函数来完成。
当一个触摸点移除以后,同样需要通过SLOT关联的ABS_MT_TRACKING_ID来处理,时序如下所示:

当要编写Type B类型的多点触摸驱动的时候就需要按照示例代码64.1.3.1中的时序上报坐标信息。Linux内核里面有大量的Type B类型的多点触摸驱动程序,我们可以参考这些现成的驱动程序来编写自己的驱动代码。这里就以ili210x这个触摸驱动IC为例,看看是Type B类型是如何上报触摸点坐标信息的。找到ili210x.c这个驱动文件,路径为drivers/input/touchscreen/ili210x.c,找到ili210x_report_events函数,此函数就是用于上报ili210x触摸坐标信息的,函数内容如下所示:

示例代码64.1.3.3 ili210x_report_events函数代码段
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事件。

64.1.4 MT其他事件的使用
在示例代码64.1.1.1中给出了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,因此驱动程序是完全通用的。

64.1.5 多点触摸所使用到的API函数
根据前面的讲解,我们知道linux下的多点触摸协议其实就是通过不同的事件来上报触摸点坐标信息,这些事件都是通过Linux内核提供的对应API函数实现的,本小节我们来看一下一些常见的API函数。
1、input_mt_init_slots函数
input_mt_init_slots函数用于初始化MT的输入slots,编写MT驱动的时候必须先调用此函数初始化slots,此函数定义在文件drivers/input/input-mt.c中,函数原型如下所示:

int input_mt_init_slots( struct input_dev dev,unsigned int num_slots,unsigned int flags)

函数参数和返回值含义如下:
dev: MT设备对应的input_dev,因为MT设备隶属于input_dev。
num_slots:设备要使用的SLOT数量,也就是触摸点的数量。
flags:其他一些flags信息,可设置的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_UNUSED 0x0004 / 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 */
可以采用‘|’运算来同时设置多个flags标识。
返回值:0,成功;负值,失败。

2、input_mt_slot函数
此函数用于Type B类型,此函数用于产生ABS_MT_SLOT事件,告诉内核当前上报的是哪个触摸点的坐标数据,此函数定义在文件include/linux/input/mt.h中,函数原型如下所示:

void input_mt_slot(struct input_dev *dev,int slot)

函数参数和返回值含义如下:
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中,此函数原型如下所示:

void input_mt_report_slot_state( struct input_dev *dev,unsigned int tool_type,bool active)

函数参数和返回值含义如下:
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中,函数原型如下所示:

void input_report_abs( struct input_dev *dev,unsigned int code,int value)

函数参数和返回值含义如下:
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中,函数原型如下:

void input_mt_report_pointer_emulation(struct input_dev *dev,bool use_count)

函数参数和返回值含义如下:
dev: MT设备对应的input_dev。
use_count:true,有效的触摸点数量;false,追踪到的触摸点数量多于当前上报的数量。
返回值:无。

64.1.6 多点电容触摸驱动框架
前面几小节已经详细的讲解了linux下多点触摸屏驱动原理,本小节我们来梳理一下linux下多点电容触摸驱动的编写框架和步骤。首先确定驱动需要用到哪些知识点,哪些框架?根据前面的分析,我们在编写驱动的时候需要注意一下几点:

①、多点电容触摸芯片的接口,一般都为I2C接口,因此驱动主框架肯定是I2C。
②、linux里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架。
③、多点电容触摸属于input子系统,因此还要用到input子系统框架。
④、在中断处理程序中按照linux的MT协议上报坐标信息。

1、I2C驱动框架
驱动总体采用I2C框架,参考框架代码如下所示

示例代码64.1.6.1 多点电容触摸驱动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");

I2C驱动框架已经在六十一章进行了详细的讲解,这里就不再赘述了。
当设备树中触摸IC的设备节点和驱动匹配以后,示例代码64.1.6.1中第21行的xxx_ts_probe函数就会执行,我们可以在此函数中初始化触摸IC,中断和input子系统等。

2、初始化触摸IC、中断和input子系统
初始化操作都是在xxx_ts_probe函数中完成,参考框架如下所示(以下代码中步骤顺序可以自行调整,不一定按照示例框架来):

示例代码64.1.6.2 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,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函数,也就是线程的意思。那么为什么要中断线程化呢?我们都是知道硬件中断具有最高优先级,不论什么时候只要硬件中断发生,那么内核都会终止当前正在执行的操作,转而去执行中断处理程序(不考虑关闭中断和中断优先级的情况),如果中断非常频繁的话那么内核将会频繁的执行中断处理程序,导致任务得不到及时的处理。中断线程化以后中断将作为内核线程运行,而且也可以被赋予不同的优先级,任务的优先级可能比中断线程的优先级高,这样做的目的就是保证高优先级的任务能被优先处理。大家可能会疑问,前面不是说可以将比较耗时的中断放到下半部(bottom half)处理吗?虽然下半部可以被延迟处理,但是依旧先于线程执行,中断线程化可以让这些比较耗时的下半部与进程进行公平竞争。
要注意,并不是所有的中断都可以被线程化,重要的中断就不能这么操作。对于触摸屏而言只要手指放到屏幕上,它可能就会一直产生中断(视具体芯片而定,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类型为例讲解一下上报过程,参考驱动框架如下所示:

示例代码64.1.6.3 xxx_handler中断处理程序
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类型的时序进行,这个已经在64.1.3小节进行详细的讲解,这里就不再赘述了。
第19行,每一轮触摸点坐标上报完毕以后就调用一次input_sync函数发送一个SYN_REPORT事件。
关于多点电容触摸驱动框架就讲解到这里,接下来我们就实际编写一个多点电容触摸驱动程序。

64.3 试验程序编写
本试验以正点原子的ATK7084(7寸800480分辨率)和ATK7016(7寸1024600分辨率)这两款屏幕所使用的FT5426触摸芯片为例,讲解如何编写多点电容触摸驱动。关于FT5426这颗触摸芯片的详细内容就不再赘述了,因为已经在裸机篇的28.1小节进行了详细的讲解。本实验对应的例程路径为:开发板光盘-> 2、Linux驱动例程-> 23_multitouch。

64.3.1 修改设备树
1、添加FT5426所使用的IO
FT5426触摸芯片用到了4个IO,一个复位IO、一个中断IO、I2C2的SCL和SDA,所以我们需要先在设备树中添加IO相关的信息。复位IO和中断IO是普通的GPIO,因此这两个IO可以放到同一个节点下去描述,I2C2的SCL和SDA属于I2C2,因此这两个要放到同一个节点下去描述。首先是复位IO和中断IO,imx6ull-alientek-emmc.dts文件里面默认有个名为“pinctrl_tsc”的节点,如果被删除了的话就自行创建,在此节点下添加触摸屏的中断引脚信息,修改以后的“pinctrl_tsc”节点内容如下所示:

示例代码64.3.1.1 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的子节点,然后在此子节点里面输入复位引脚配置信息即可,如下所示:

示例代码64.3.3.2 pinctrl_tsc_reset子节点内容
1 pinctrl_tsc_reset: tsc_reset {
2 		fsl,pins = <
3   		MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
4     	>;
5 };

继续添加I2C2的SCL和SDA这两个IO信息,imx6ull-alientek-emmc.dts里面默认就已经添加了I2C2的IO信息,这是NXP官方添加的,所以不需要我们去修改。找到“pinctrl_i2c2”节点,此节点就是用于描述I2C2的IO信息,节点内容如下所示:

示例代码64.3.1.3 pinctrl_i2c2节点信息
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 };

最后,一定要检查一下设备树,确保触摸屏所使用的IO没有被其他的外设使用,如果有的话就需要将其屏蔽掉,保证只有触摸屏用到了这四个IO。

2、添加FT5426节点
FT5426这个触摸IC挂载I2C2下,因此需要向I2C2节点下添加一个子节点,此子节点用于描述FT5426,添加完成以后的I2C2节点内容如下所示(省略掉其他挂载到I2C2下的设备):

示例代码64.3.1.4 ft5426节点信息
1  &i2c2 {
2   	clock_frequency = <100000>;
3   	pinctrl-names = "default";
4   	pinctrl-0 = <&pinctrl_i2c2>;
5   	status = "okay";
6 
7   	/****************************/
8   	/* 省略掉其他的设备节点 			*/
9   	/****************************/
10  
11  	/* zuozhongkai FT5406/FT5426 */
12  	ft5426: ft5426@38 {
13      	compatible = "edt,edt-ft5426";
14      	reg = <0x38>;
15      	pinctrl-names = "default";
16    		pinctrl-0 = <&pinctrl_tsc
17                    &pinctrl_tsc_reset >; 
18      	interrupt-parent = <&gpio1>; 
19      	interrupts = <9 0>; 
20      	reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;  
21      	interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; 
22  	};
23 };

第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。

64.3.2 编写多点电容触摸驱动
新建名为“23_multitouch”的文件夹,然后在23_multitouch文件夹里面创建vscode工程,工作区命名为“multitouch”。工程创建好以后新建ft5x06.c这个驱动文件,在里面输入如下所示内容(限于篇幅原因,部分内容省略掉了,完整的内容请查看驱动源码):

示例代码64.3.2.1 ft5x06.c文件内容(有省略)
1   #include <linux/module.h>
2   #include <linux/ratelimit.h>
......
15  #include <linux/i2c.h>
16  /***************************************************************
17  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
18  文件名  	: ft5x06.c
19  作者   	: 左忠凯
20  版本   	: V1.0
21  描述   	: FT5X06,包括FT5206、FT5426等触摸屏驱动程序
22  其他   	: 无
23  论坛   	: www.openedv.com
24  日志   	: 初版V1.0 2019/12/23 左忠凯创建个
25  ***************************************************************/
26  
27  #define MAX_SUPPORT_POINTS   	5      	/* 5点触摸 	*/
28  #define TOUCH_EVENT_DOWN      	0x00   	/* 按下 	*/
29  #define TOUCH_EVENT_UP        	0x01  	/* 抬起 	*/
30  #define TOUCH_EVENT_ON          	0x02   	/* 接触 	*/
31  #define TOUCH_EVENT_RESERVED  	0x03  	/* 保留 	*/
32  
33  /* FT5X06寄存器相关宏定义 */
34  #define FT5X06_TD_STATUS_REG  	0X02  	/*  状态寄存器地址 		*/
35  #define FT5x06_DEVICE_MODE_REG	0X00  	/* 模式寄存器      		*/
36  #define FT5426_IDG_MODE_REG    	0XA4   	/* 中断模式             	*/
37  #define FT5X06_READLEN          	29     	/* 要读取的寄存器个数 	*/
38  
39  struct ft5x06_dev {
40      struct device_node  *nd;        		/* 设备节点     		*/
41      int irq_pin,reset_pin;          		/* 中断和复位IO    	*/
42      int irqnum;                     		/* 中断号        	*/
43      void *private_data;             		/* 私有数据     		*/
44      struct input_dev *input;        		/* input结构体 	*/
45      struct i2c_client *client;      		/* I2C客户端 		*/
46  };
47  
48  static struct ft5x06_dev ft5x06;
49  
50  /*
51   * @description   	: 复位FT5X06
52   * @param - client 	: 要操作的i2c
53   * @param – multidev	: 自定义的multitouch设备
54   * @return          	: 0,成功;其他负值,失败
55   */
56  static int ft5x06_ts_reset(struct i2c_client *client, 
struct ft5x06_dev *dev)
57  {
58      int ret = 0;
59  
60      if (gpio_is_valid(dev->reset_pin)) {        /* 检查IO是否有效 */
61          /* 申请复位IO,并且默认输出低电平 */
62          ret = devm_gpio_request_one(&client->dev,   
63                      dev->reset_pin, GPIOF_OUT_INIT_LOW,
64                      "edt-ft5x06 reset");
65          if (ret) {
66              return ret;
67          }
68          msleep(5);
69          gpio_set_value(dev->reset_pin, 1);  /* 输出高电平,停止复位 */
70          msleep(300);
71      }
72      return 0;
73  }
74  
75  /*
76   * @description	: 从FT5X06读取多个寄存器数据
77   * @param – dev	: ft5x06设备
78   * @param – reg	: 要读取的寄存器首地址
79   * @param – val	: 读取到的数据
80   * @param – len	: 要读取的数据长度
81   * @return      	: 操作结果
82   */
83  static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg, 
void *val, int len)
84  {
85      int ret;
86      struct i2c_msg msg[2];
87      struct i2c_client *client = (struct i2c_client *)dev->client;
......
101     ret = i2c_transfer(client->adapter, msg, 2);
102     if(ret == 2) {
103         ret = 0;
104     } else {
105         ret = -EREMOTEIO;
106     }
107     return ret;
108 }
109 
110 /*
111  * @description 	: 向ft5x06多个寄存器写入数据
112  * @param – dev	: ft5x06设备
113  * @param – reg	: 要写入的寄存器首地址
114  * @param – val	: 要写入的数据缓冲区
115  * @param – len	: 要写入的数据长度
116  * @return    		: 操作结果
117  */
118 static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg, 
u8 *buf, u8 len)
119 {
120     u8 b[256];
121     struct i2c_msg msg;
122     struct i2c_client *client = (struct i2c_client *)dev->client;
123     
124     b[0] = reg;                 	/* 寄存器首地址 						*/
125     memcpy(&b[1],buf,len);      	/* 将要写入的数据拷贝到数组b里面 	*/
126         
127     msg.addr = client->addr;    	/* ft5x06地址 						*/
128     msg.flags = 0;              	/* 标记为写数据 						*/
129 
130     msg.buf = b;                	/* 要写入的数据缓冲区 				*/
131     msg.len = len + 1;          	/* 要写入的数据长度 					*/
132 
133     return i2c_transfer(client->adapter, &msg, 1);
134 }
135 
136 /*
137  * @description 	: 向ft5x06指定寄存器写入指定的值,写一个寄存器
138  * @param – dev	: ft5x06设备
139  * @param – reg	: 要写的寄存器
140  * @param – data	: 要写入的值
141  * @return   		: 无
142  */
143 static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg,
          u8 data)
144 {
145     u8 buf = 0;
146     buf = data;
147     ft5x06_write_regs(dev, reg, &buf, 1);
148 }
149 
150 /*
151  * @description   	: FT5X06中断服务函数
152  * @param - irq     	: 中断号 
153  * @param - dev_id 	: 设备结构。
154  * @return          	: 中断执行结果
155  */
156 static irqreturn_t ft5x06_handler(int irq, void *dev_id)
157 {
158     struct ft5x06_dev *multidata = dev_id;
159 
160     u8 rdbuf[29];
161     int i, type, x, y, id;
162     int offset, tplen;
163     int ret;
164     bool down;
165 
166     offset = 1;     /* 偏移1,也就是0X02+1=0x03,从0X03开始是触摸值 */
167     tplen = 6;      /* 一个触摸点有6个寄存器来保存触摸值 */
168 
169     memset(rdbuf, 0, sizeof(rdbuf));        /* 清除 */
170 
171     /* 读取FT5X06触摸点坐标从0X02寄存器开始,连续读取29个寄存器 */
172     ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, 
rdbuf, FT5X06_READLEN);
173     if (ret) {
174         goto fail;
175     }
176 
177     /* 上报每一个触摸点坐标 */
178     for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
179         u8 *buf = &rdbuf[i * tplen + offset];
180 
181         /* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0X03),各位描述如下:
182          * bit7:6  Event flag  0:按下 1:释放 2:接触 3:没有事件
183          * bit5:4  保留
184          * bit3:0  X轴触摸点的11~8位。
185          */
186         type = buf[0] >> 6;     /* 获取触摸类型 */
187         if (type == TOUCH_EVENT_RESERVED)
188             continue;
189  
190         /* 我们所使用的触摸屏和FT5X06是反过来的 */
191         x = ((buf[2] << 8) | buf[3]) & 0x0fff;
192         y = ((buf[0] << 8) | buf[1]) & 0x0fff;
193         
194         /* 以第一个触摸点为例,寄存器TOUCH1_YH(地址0X05),各位描述如下:
195          * bit7:4  Touch ID  触摸ID,表示是哪个触摸点
196          * bit3:0  Y轴触摸点的11~8位。
197          */
198         id = (buf[2] >> 4) & 0x0f;
199         down = type != TOUCH_EVENT_UP;
200 
201         input_mt_slot(multidata->input, id);
202         input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, 
down);
203 
204         if (!down)
205             continue;
206 
207         input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
208         input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
209     }
210 
211     input_mt_report_pointer_emulation(multidata->input, true);
212     input_sync(multidata->input);
213 
214 fail:
215     return IRQ_HANDLED;
216 
217 }
218 
219 /*
220  * @description    	: FT5x06中断初始化
221  * @param - client  	: 要操作的i2c
222  * @param – multidev	: 自定义的multitouch设备
223  * @return          	: 0,成功;其他负值,失败
224  */
225 static int ft5x06_ts_irq(struct i2c_client *client,
   struct ft5x06_dev *dev)
226 {
227     int ret = 0;
228 
229     /* 1,申请中断GPIO */
230     if (gpio_is_valid(dev->irq_pin)) {
231         ret = devm_gpio_request_one(&client->dev, dev->irq_pin,
232                     GPIOF_IN, "edt-ft5x06 irq");
233         if (ret) {
234             dev_err(&client->dev,
235                 "Failed to request GPIO %d, error %d\n",
236                 dev->irq_pin, ret);
237             return ret;
238         }
239     }
240 
241     /* 2,申请中断,client->irq就是IO中断, */
242     ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
243                   ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
244                   client->name, &ft5x06);
245     if (ret) {
246         dev_err(&client->dev, "Unable to request 
touchscreen IRQ.\n");
247         return ret;
248     }
249 
250     return 0;
251 }
252 
253  /*
254   * @description     	: i2c驱动的probe函数,当驱动与
255   *                        设备匹配以后此函数就会执行
256   * @param - client  	: i2c设备
257   * @param - id      	: i2c设备ID
258   * @return          	: 0,成功;其他负值,失败
259   */
260 static int ft5x06_ts_probe(struct i2c_client *client, 
const struct i2c_device_id *id)
261 {
262     int ret = 0;
263 
264     ft5x06.client = client;
265 
266     /* 1,获取设备树中的中断和复位引脚 */
267     ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, 
"interrupt-gpios", 0);
268     ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, 
"reset-gpios", 0);
269 
270     /* 2,复位FT5x06 */
271     ret = ft5x06_ts_reset(client, &ft5x06);
272     if(ret < 0) {
273         goto fail;
274     }
275 
276     /* 3,初始化中断 */
277     ret = ft5x06_ts_irq(client, &ft5x06);
278     if(ret < 0) {
279         goto fail;
280     }
281 
282     /* 4,初始化FT5X06 */
283     ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0);  
284     ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1);  
285 
286     /* 5,input设备注册 */
287     ft5x06.input = devm_input_allocate_device(&client->dev);
288     if (!ft5x06.input) {
289         ret = -ENOMEM;
290         goto fail;
291     }
292     ft5x06.input->name = client->name;
293     ft5x06.input->id.bustype = BUS_I2C;
294     ft5x06.input->dev.parent = &client->dev;
295 
296     __set_bit(EV_KEY, ft5x06.input->evbit);
297     __set_bit(EV_ABS, ft5x06.input->evbit);
298     __set_bit(BTN_TOUCH, ft5x06.input->keybit);
299 
300     input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);
301     input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);
302     input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X,
0, 1024, 0, 0);
303     input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y,
0, 600, 0, 0);      
304     ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);
305     if (ret) {
306         goto fail;
307     }
308 
309     ret = input_register_device(ft5x06.input);
310     if (ret)
311         goto fail;
312 
313     return 0;
314 
315 fail:
316     return ret;
317 }
318 
319 /*
320  * @description   : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
321  * @param – client	: i2c设备
322  * @return          	: 0,成功;其他负值,失败
323  */
324 static int ft5x06_ts_remove(struct i2c_client *client)
325 {   
326     /* 释放input_dev */
327     input_unregister_device(ft5x06.input);
328     return 0;
329 }
330 
331 
332 /*
333  *  传统驱动匹配表
334  */ 
335 static const struct i2c_device_id ft5x06_ts_id[] = {
336     { "edt-ft5206", 0, },
337     { "edt-ft5426", 0, },
338     { /* sentinel */ }
339 };
340 
341 /*
342  * 设备树匹配表 
343  */
344 static const struct of_device_id ft5x06_of_match[] = {
345     { .compatible = "edt,edt-ft5206", },
346     { .compatible = "edt,edt-ft5426", },
347     { /* sentinel */ }
348 };
349 
350 /* i2c驱动结构体 */    
351 static struct i2c_driver ft5x06_ts_driver = {
352     .driver = {
353         .owner = THIS_MODULE,
354         .name = "edt_ft5x06",
355         .of_match_table = of_match_ptr(ft5x06_of_match),
356     },
357     .id_table = ft5x06_ts_id,
358     .probe    = ft5x06_ts_probe,
359     .remove   = ft5x06_ts_remove,
360 };
361 
362 /*
363  * @description 	: 驱动入口函数
364  * @param       	: 无
365  * @return      	: 无
366  */
367 static int __init ft5x06_init(void)
368 {
369     int ret = 0;
370 
371     ret = i2c_add_driver(&ft5x06_ts_driver);
372 
373     return ret;
374 }
375 
376 /*
377  * @description 	: 驱动出口函数
378  * @param       	: 无
379  * @return      	: 无
380  */
381 static void __exit ft5x06_exit(void)
382 {
383     i2c_del_driver(&ft5x06_ts_driver);
384 }
385 
386 module_init(ft5x06_init);
387 module_exit(ft5x06_exit);
388 MODULE_LICENSE("GPL");
389 MODULE_AUTHOR("zuozhongkai");

第39~46行,定义一个设备结构体,存放多点电容触摸设备相关属性信息。
第48行,定义一个名为ft5x06的全局变量,变量类型就是上面定义的ft5x06_dev结构体。
第56~73行,ft5x06_ts_reset函数,用于初始化FT5426触摸芯片,其实就是设置FT5426的复位IO为高电平,防止芯片复位。注意在第62行使用devm_gpio_request_one函数来申请复位IO,关于“devm_”前缀的作用已经在64.1.6小节做了详细的讲解。使用“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。
第156217行,ft5x06_handler函数,触摸屏中断服务函数,触摸点坐标的上报就是在此函数中完成的。第172行通过ft5x06_read_regs函数读取FT5426的所有触摸点信息寄存器数据,从0X02这个地址开始,一共29个寄存器。第178209行的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。

第260317行,当I2C设备与驱动匹配以后此函数就会执行,一般在此函数中完成一些初始化工作。我们重点来看一下287309行是关于input_dev设备的初始化,第287294行申请并简单的初始化input_dev,这个我们在第五十八章已经讲解过了。第296和298行设置input_dev需要上报的事件为EV_KEY和EV_ABS,需要上报的按键码为BTN_TOUCH。EV_KEY是按键事件,用于上报触摸屏是否被按下,相当于把触摸屏当做一个按键。EV_ABS是触摸点坐标数据,BTN_TOUCH表示将触摸屏的按下和抬起用作BTN_TOUCH按键。第300303行调用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驱动框架那一套,已经在六十一章中进行了详细的讲解。

图64.4.2.1 驱动加载过程
驱动加载成功以后就会生成/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类型呈现的,这个同样在第58.4.2小节做了详细的介绍,这里我们重点来分析一下,在多点电容触摸屏上其所代表的具体含义,将图64.4.2.3中的数据进行整理,结果如下所示:

/input_event类型***/
/* 编号 / / tv_sec / / tv_usec / / type / / code / / value */
0000000 02bb 0000 9459 0007 0003 002f 0000 0000
0000010 02bb 0000 9459 0007 0003 0039 0005 0000
0000020 02bb 0000 9459 0007 0003 0035 03ec 0000
0000030 02bb 0000 9459 0007 0003 0036 0017 0000
0000040 02bb 0000 9459 0007 0001 014a 0001 0000
0000050 02bb 0000 9459 0007 0003 0000 03ec 0000
0000060 02bb 0000 9459 0007 0003 0001 0017 0000
0000070 02bb 0000 9459 0007 0000 0000 0000 0000
0000080 02bb 0000 e5f8 0008 0003 0039 ffff ffff
0000090 02bb 0000 e5f8 0008 0001 014a 0000 0000
00000a0 02bb 0000 e5f8 0008 0000 0000 0000 0000

第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类型设备一致。

64.5.1 tslib移植
tslib是一个开源的第三方库,用于触摸屏性能调试,使用电阻屏的时候一般使用tslib进行校准。虽然电容屏不需要校准,但是由于电容屏加工的原因,有的时候其不一定精准,因此有时候也需要进行校准。最主要的是tslib提供了一些其他软件,我们可以通过这些软件来测试触摸屏工作是否正常。最新版本的tslib已经支持了多点电容触摸屏,因此可以通过tslib来直观的测试多点电容触摸屏驱动,这个要比观看eventX原始数据方便的多。
tslib的移植很简单,步骤如下:
1、获取tslib源码
首先肯定是获取tslib的源码,git地址为https://github.com/kergoth/tslib,目前最新的版本是1.21。tslib源码已经放到开发板光盘中,路径为:1、例程源码-》7、第三方库源码-》tslib-1.21.tar.bz2。将压缩包发送到ubuntu中并解压,得到名为“tslib-1.21”的目录,此目录下就是tslib源码。
2、修改tslib源码所属用户
修改解压得到的tslib-1.21目录所属用户为当前用户,这一步一定要做!否则在稍后的编译中会遇到各种问题。我当前ubuntu的登录用户名为“zuozhongkai”,那么修改命令如下:
sudo chown zuozhongkai:zuozhongkai 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/zuozhongkai/linux/IMX6ULL/tool/tslib。
接下来输入如下命令配置并编译tslib:
cd tslib-1.21/ //进入tslib源码目录
./autogen.sh
./configure --host=arm-linux-gnueabihf --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/tslib
make //编译
make install //安装
注意,在使用./configure配置tslib的时候“–host”参数指定编译器,“–prefix”参数指定编译完成以后的tslib文件安装到哪里,这里肯定是安装到我们刚刚创建的“tslib”目录下。
完成以后tslib目录下的内容如图64.5.1.1所示:
在这里插入图片描述

图64.5.1.1 编译得到的文件
bin目录下是可执行文件,包括tslib的测试工具。etc目录下是tslib的配置文件,lib目录下是相关的库文件。将图64.5.1.1中的所有文件拷贝到开发板的根文件系统中,命令如下:
sudo cp * -rf /home/zuozhongkai/linux/nfs/rootfs
5、配置tslib
打开/etc/ts.conf文件,找到下面这一行:
module_raw input
如果上面这句前面有“#”的话就删除掉“#”。
打开/etc/profile文件,在里面加入如下内容:
示例代码64.5.1.1 /etc/profile文件添加的内容

1 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。
全部配置好以后重启开发板,然后就可以进行测试了。

64.5.2 tslib测试
电容屏可以不用校准,如果是电阻屏就要先进行校准!校准的话输入如下命令:
ts_calibrate
校准完成以后如果不满意,或者不小心对电容屏做了校准,那么直接删除掉/etc/pointercal文件即可。
最后我们使用ts_test_mt这个软件来测试触摸屏工作是否正常,以及多点触摸是否有效,执行如下所示命令:
ts_test_mt
此命令会打开一个触摸测试界面,如图64.5.2.1所示:
在这里插入图片描述

图64.5.2.1 ts_test_mt界面
在图64.5.2.1上有三个按钮“Drag”、“Draw”和“Quit”,这三个按钮的功能如下:
Drag:拖拽按钮,默认就是此功能,大家可以看到屏幕中间有一个十字光标,我们可以通过触摸屏幕来拖拽此光标。一个触摸点一个十字光标,对于5点电容触摸屏,如果5个手指都放到屏幕上,那么就有5个光标,一个手指一个。
Draw:绘制按钮,按下此按钮我们就可以在屏幕上进行简单的绘制,可以通过此功能检测多点触摸工作是否正常。
Quit:退出按钮,退出ts_test_mt测试软件。
点击“Draw”按钮,使用绘制功能,5个手指一起划过屏幕,如果多点电容屏工作正常的话就会在屏幕上留下5条线,如图64.5.2.2所示:

图64.5.2.2 5点触摸测试
从图64.5.2.2可以看出,屏幕上有5条线,说明5点电容触摸工作正常。这5跳线都是白色的,图64.5.2.2中由于拍照并处理的原因,导致5条线开起来不是白色的。

64.6 使用内核自带的驱动
Linux内核已经集成了很多电容触摸IC的驱动文件,比如本章实验我们所使用FT5426,本节我们就来学习一下,如何使用Linux内核自带的多点电容触摸驱动。在使用之前要先将前面我们自己添加到内核的ft5x06.c这个文件从内核中去除掉,只需要修改drivers/input/touchscreen/Makefile这个文件即可,将下面这一行删除掉:
obj-y += ft5x06.o
内核自带的FT5426的驱动文件为drivers/input/touchscreen/edt-ft5x06.c,此驱动文件不仅仅能够驱动FT5426,FT5206、FT5406这些都可以驱动。按照如下步骤来操作,学习如何使用此驱动。
1、修改edt-ft5x06.c
edt-ft5x06.c直接使用的话是不行的,需要对其做修改,由于此文件太大,这里就不一一指出来如何修改了。大家可以直接参考我们已经修改好的edt-ft5x06.c,修改好的edt-ft5x06.c放到了开发板光盘中,路径为:1、例程源码/2、Linux驱动例程/23_multitouch/edt-ft5x06.c,直接用我们提供的edt-ft5x06.c文件替换掉内核自带的edt-ft5x06.c即可。感兴趣的可以对比一下两个文件的差异,看一下我们修改了什么地方。
2、使能内核自带的FT5X06驱动
edt-ft5x06.c这个驱动默认是没有使能的,我们需要配置Linux内核,使能此驱动,通过图形化配置界面即可完成配置。进入linux内核源码目录,输入如下所示命令打开图形化配置界面:
make menuconfig
配置路径如下:
Location:
-> Device Drivers
-> Input device support
-> Generic input layer (needed for keyboard, mouse, …) (INPUT [=y])
-> Touchscreens (INPUT_TOUCHSCREEN [=y])
-> <*> EDT FocalTech FT5x06 I2C Touchscreen support
配置如图64.6.1所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最后,附上两个型号的触摸屏驱动源代码

gslX680.c

/*
 * drivers/input/touchscreen/gslX680.c
 *
 * Copyright (c) 2012 Shanghai Basewin
 *	Guan Yuwei<guanyuwei@basewin.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 */

#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/earlysuspend.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/ioc4.h>
#include <linux/io.h>

#include <linux/proc_fs.h>

#include <mach/gpio.h>gslX680.c
#include <mach/hardware.h>
#include <plat/gpio-cfg.h>
#include <plat/irqs.h>

#include "gslX680.h"

//#define GSL_DEBUG
//#define GSL_MONITOR
//#define HAVE_TOUCH_KEY
//#define SLEEP_CLEAR_POINT
//#define FILTER_POINT
#ifdef FILTER_POINT
#define FILTER_MAX	9
#endif

#define GSLX680_I2C_NAME 	"gslX680"
#define GSLX680_I2C_ADDR 	0x40
#define IRQ_PORT			IRQ_EINT(7)

#define GSL_DATA_REG		0x80
#define GSL_STATUS_REG		0xe0
#define GSL_PAGE_REG		0xf0

#define PRESS_MAX    		255
#define MAX_FINGERS 		1
#define MAX_CONTACTS 		1
#define DMA_TRANS_LEN		0x20

#ifdef GSL_MONITOR
static struct delayed_work gsl_monitor_work;
static struct workqueue_struct *gsl_monitor_workqueue = NULL;
static u8 int_1st[4] = {0};
static u8 int_2nd[4] = {0};
static char dac_counter = 0;
static char b0_counter = 0;
static char bc_counter = 0;
static char i2c_lock_flag = 0;
#endif 

static struct i2c_client *gsl_client = NULL;

#ifdef HAVE_TOUCH_KEY
static u16 key = 0;
static int key_state_flag = 0;
struct key_data {
	u16 key;
	u16 x_min;
	u16 x_max;
	u16 y_min;
	u16 y_max;	
};

const u16 key_array[]={
                                      KEY_BACK,
                                      KEY_HOME,
                                      KEY_MENU,
                                      KEY_SEARCH,
                                     }; 
#define MAX_KEY_NUM     (sizeof(key_array)/sizeof(key_array[0]))

struct key_data gsl_key_data[MAX_KEY_NUM] = {
	{KEY_BACK, 2048, 2048, 2048, 2048},
	{KEY_HOME, 2048, 2048, 2048, 2048},	
	{KEY_MENU, 2048, 2048, 2048, 2048},
	{KEY_SEARCH, 2048, 2048, 2048, 2048},
};
#endif

struct gsl_ts_data {
	u8 x_index;
	u8 y_index;
	u8 z_index;
	u8 id_index;
	u8 touch_index;
	u8 data_reg;
	u8 status_reg;
	u8 data_size;
	u8 touch_bytes;
	u8 update_data;
	u8 touch_meta_data;
	u8 finger_size;
};

static struct gsl_ts_data devices[] = {
	{
		.x_index = 6,
		.y_index = 4,
		.z_index = 5,
		.id_index = 7,
		.data_reg = GSL_DATA_REG,
		.status_reg = GSL_STATUS_REG,
		.update_data = 0x4,
		.touch_bytes = 4,
		.touch_meta_data = 4,
		.finger_size = 70,
	},
};

struct gsl_ts {
	struct i2c_client *client;
	struct input_dev *input;
	struct work_struct work;
	struct workqueue_struct *wq;
	struct gsl_ts_data *dd;
	u8 *touch_data;
	u8 device_id;
	int irq;
#if defined(CONFIG_HAS_EARLYSUSPEND)
	struct early_suspend early_suspend;
#endif
};

#ifdef GSL_DEBUG 
#define print_info(fmt, args...)   \
        do{                              \
                printk(fmt, ##args);     \
        }while(0)
#else
#define print_info(fmt, args...)
#endif


static u32 id_sign[MAX_CONTACTS+1] = {0};
static u8 id_state_flag[MAX_CONTACTS+1] = {0};
static u8 id_state_old_flag[MAX_CONTACTS+1] = {0};
static u16 x_old[MAX_CONTACTS+1] = {0};
static u16 y_old[MAX_CONTACTS+1] = {0};
static u16 x_new = 0;
static u16 y_new = 0;

static int gslX680_init(void)
{
	/* shutdown pin */
	s3c_gpio_setpull(S5PV210_GPH0(6), S3C_GPIO_PULL_UP);
	s3c_gpio_cfgpin(S5PV210_GPH0(6), S3C_GPIO_SFN(1));
	gpio_set_value(S5PV210_GPH0(6), 0);

	mdelay(50);
	gpio_set_value(S5PV210_GPH0(6), 1);

	/* config interrupt pin */
	s3c_gpio_cfgpin(S5PV210_GPH0(7), S3C_GPIO_SFN(0xf));
	s3c_gpio_setpull(S5PV210_GPH0(7), S3C_GPIO_PULL_NONE);
	set_irq_type(IRQ_EINT7, IRQ_TYPE_EDGE_RISING);

	return 0;
}

static int gslX680_shutdown_low(void)
{
	gpio_set_value(S5PV210_GPH0(6), 0);
	return 0;
}

static int gslX680_shutdown_high(void)
{
	gpio_set_value(S5PV210_GPH0(6), 1);
	return 0;
}

static inline u16 join_bytes(u8 a, u8 b)
{
	u16 ab = 0;
	ab = ab | a;
	ab = ab << 8 | b;
	return ab;
}

static u32 gsl_write_interface(struct i2c_client *client, const u8 reg, u8 *buf, u32 num)
{
	struct i2c_msg xfer_msg[1];

	buf[0] = reg;

	xfer_msg[0].addr = client->addr;
	xfer_msg[0].len = num + 1;
	xfer_msg[0].flags = client->flags & I2C_M_TEN;
	xfer_msg[0].buf = buf;

	return i2c_transfer(client->adapter, xfer_msg, 1) == 1 ? 0 : -EFAULT;
}

static int gsl_ts_write(struct i2c_client *client, u8 addr, u8 *pdata, int datalen)
{
	int ret = 0;
	u8 tmp_buf[128];
	unsigned int bytelen = 0;
	if (datalen > 125)
	{
		print_info("%s too big datalen = %d!\n", __func__, datalen);
		return -1;
	}
	
	tmp_buf[0] = addr;
	bytelen++;
	
	if (datalen != 0 && pdata != NULL)
	{
		memcpy(&tmp_buf[bytelen], pdata, datalen);
		bytelen += datalen;
	}
	
	ret = i2c_master_send(client, tmp_buf, bytelen);
	return ret;
}

static int gsl_ts_read(struct i2c_client *client, u8 addr, u8 *pdata, unsigned int datalen)
{
	int ret = 0;

	if (datalen > 126)
	{
		print_info("%s too big datalen = %d!\n", __func__, datalen);
		return -1;
	}

	ret = gsl_ts_write(client, addr, NULL, 0);
	if (ret < 0)
	{
		print_info("%s set data address fail!\n", __func__);
		return ret;
	}
	
	return i2c_master_recv(client, pdata, datalen);
}

static __inline__ void fw2buf(u8 *buf, const u32 *fw)
{
	u32 *u32_buf = (int *)buf;
	*u32_buf = *fw;
}

static void gsl_load_fw(struct i2c_client *client)
{
	u8 buf[DMA_TRANS_LEN*4 + 1] = {0};
	u8 send_flag = 1;
	u8 *cur = buf + 1;
	u32 source_line = 0;
	u32 source_len;
	struct fw_data *ptr_fw;
	
	print_info("=============gsl_load_fw start==============\n");

	ptr_fw = GSLX680_FW;
	source_len = ARRAY_SIZE(GSLX680_FW);

	for (source_line = 0; source_line < source_len; source_line++) 
	{
		/* init page trans, set the page val */
		if (GSL_PAGE_REG == ptr_fw[source_line].offset)
		{
			fw2buf(cur, &ptr_fw[source_line].val);
			gsl_write_interface(client, GSL_PAGE_REG, buf, 4);
			send_flag = 1;
		}
		else 
		{
			if (1 == send_flag % (DMA_TRANS_LEN < 0x20 ? DMA_TRANS_LEN : 0x20))
	    			buf[0] = (u8)ptr_fw[source_line].offset;

			fw2buf(cur, &ptr_fw[source_line].val);
			cur += 4;

			if (0 == send_flag % (DMA_TRANS_LEN < 0x20 ? DMA_TRANS_LEN : 0x20)) 
			{
	    			gsl_write_interface(client, buf[0], buf, cur - buf - 1);
	    			cur = buf + 1;
			}

			send_flag++;
		}
	}

	print_info("=============gsl_load_fw end==============\n");

}


static int test_i2c(struct i2c_client *client)
{
	u8 read_buf = 0;
	u8 write_buf = 0x12;
	int ret, rc = 1;
	
	ret = gsl_ts_read( client, 0xf0, &read_buf, sizeof(read_buf) );
	if  (ret  < 0)  
    		rc --;
	else
		print_info("I read reg 0xf0 is %x\n", read_buf);
	
	msleep(2);
	ret = gsl_ts_write(client, 0xf0, &write_buf, sizeof(write_buf));
	if(ret  >=  0 )
		print_info("I write reg 0xf0 0x12\n");
	
	msleep(2);
	ret = gsl_ts_read( client, 0xf0, &read_buf, sizeof(read_buf) );
	if(ret <  0 )
		rc --;
	else
		print_info("I read reg 0xf0 is 0x%x\n", read_buf);

	return rc;
}


static void startup_chip(struct i2c_client *client)
{
	u8 buf[4] = {0};
	buf[3] = 0x01;
	buf[2] = 0xfe;
	buf[1] = 0x0e;
	buf[0] = 0x02;
	gsl_write_interface(client,0xf0,buf,4);
	buf[3] = 0x0;
	buf[2] = 0xa;
	buf[1] = 0x0;
	buf[0] = 0x70;
	gsl_write_interface(client,0x4,buf,4);
	
	u8 tmp = 0x00;
	
#ifdef GSL_NOID_VERSION
	gsl_DataInit(gsl_config_data_id);
#endif
	gsl_ts_write(client, 0xe0, &tmp, 1);
	msleep(10);	
}

static void reset_chip(struct i2c_client *client)
{
	u8 tmp = 0x88;
	u8 buf[4] = {0x00};
	
	gsl_ts_write(client, 0xe0, &tmp, sizeof(tmp));
	msleep(20);
	tmp = 0x04;
	gsl_ts_write(client, 0xe4, &tmp, sizeof(tmp));
	msleep(10);
	gsl_ts_write(client, 0xbc, buf, sizeof(buf));
	msleep(10);
}

static void clr_reg(struct i2c_client *client)
{
	u8 write_buf[4]	= {0};

	write_buf[0] = 0x88;
	gsl_ts_write(client, 0xe0, &write_buf[0], 1); 	
	msleep(20);
	write_buf[0] = 0x03;
	gsl_ts_write(client, 0x80, &write_buf[0], 1); 	
	msleep(5);
	write_buf[0] = 0x04;
	gsl_ts_write(client, 0xe4, &write_buf[0], 1); 	
	msleep(5);
	write_buf[0] = 0x00;
	gsl_ts_write(client, 0xe0, &write_buf[0], 1); 	
	msleep(20);
}

static void init_chip(struct i2c_client *client)
{
	int rc;
	
	gslX680_shutdown_low();	
	msleep(20); 	
	gslX680_shutdown_high();	
	msleep(20); 		
	rc = test_i2c(client);
	if(rc < 0)
	{
		print_info("------gslX680 test_i2c error------\n");	
		return;
	}	
	clr_reg(client);
	reset_chip(client);
	gsl_load_fw(client);			
	startup_chip(client);	
	reset_chip(client);	
	startup_chip(client);	
}

static void check_mem_data(struct i2c_client *client)
{
	u8 read_buf[4]  = {0};
	
	msleep(30);
	gsl_ts_read(client,0xb0, read_buf, sizeof(read_buf));
	
	if (read_buf[3] != 0x5a || read_buf[2] != 0x5a || read_buf[1] != 0x5a || read_buf[0] != 0x5a)
	{
		print_info("#########check mem read 0xb0 = %x %x %x %x #########\n", read_buf[3], read_buf[2], read_buf[1], read_buf[0]);
		init_chip(client);
	}
}

#ifdef FILTER_POINT
static void filter_point(u16 x, u16 y , u8 id)
{
	u16 x_err =0;
	u16 y_err =0;
	u16 filter_step_x = 0, filter_step_y = 0;
	
	id_sign[id] = id_sign[id] + 1;
	if(id_sign[id] == 1)
	{
		x_old[id] = x;
		y_old[id] = y;
	}
	
	x_err = x > x_old[id] ? (x -x_old[id]) : (x_old[id] - x);
	y_err = y > y_old[id] ? (y -y_old[id]) : (y_old[id] - y);

	if( (x_err > FILTER_MAX && y_err > FILTER_MAX/3) || (x_err > FILTER_MAX/3 && y_err > FILTER_MAX) )
	{
		filter_step_x = x_err;
		filter_step_y = y_err;
	}
	else
	{
		if(x_err > FILTER_MAX)
			filter_step_x = x_err; 
		if(y_err> FILTER_MAX)
			filter_step_y = y_err;
	}

	if(x_err <= 2*FILTER_MAX && y_err <= 2*FILTER_MAX)
	{
		filter_step_x >>= 2; 
		filter_step_y >>= 2;
	}
	else if(x_err <= 3*FILTER_MAX && y_err <= 3*FILTER_MAX)
	{
		filter_step_x >>= 1; 
		filter_step_y >>= 1;
	}	
	else if(x_err <= 4*FILTER_MAX && y_err <= 4*FILTER_MAX)
	{
		filter_step_x = filter_step_x*3/4; 
		filter_step_y = filter_step_y*3/4;
	}	
	
	x_new = x > x_old[id] ? (x_old[id] + filter_step_x) : (x_old[id] - filter_step_x);
	y_new = y > y_old[id] ? (y_old[id] + filter_step_y) : (y_old[id] - filter_step_y);

	x_old[id] = x_new;
	y_old[id] = y_new;
}
#else
static void record_point(u16 x, u16 y , u8 id)
{
	u16 x_err =0;
	u16 y_err =0;

	id_sign[id]=id_sign[id]+1;
	
	if(id_sign[id]==1){
		x_old[id]=x;
		y_old[id]=y;
	}

	x = (x_old[id] + x)/2;
	y = (y_old[id] + y)/2;
		
	if(x>x_old[id]){
		x_err=x -x_old[id];
	}
	else{
		x_err=x_old[id]-x;
	}

	if(y>y_old[id]){
		y_err=y -y_old[id];
	}
	else{
		y_err=y_old[id]-y;
	}

	if( (x_err > 3 && y_err > 1) || (x_err > 1 && y_err > 3) ){
		x_new = x;     x_old[id] = x;
		y_new = y;     y_old[id] = y;
	}
	else{
		if(x_err > 3){
			x_new = x;     x_old[id] = x;
		}
		else
			x_new = x_old[id];
		if(y_err> 3){
			y_new = y;     y_old[id] = y;
		}
		else
			y_new = y_old[id];
	}

	if(id_sign[id]==1){
		x_new= x_old[id];
		y_new= y_old[id];
	}
	
}
#endif

#ifdef HAVE_TOUCH_KEY
static void report_key(struct gsl_ts *ts, u16 x, u16 y)
{
	u16 i = 0;

	for(i = 0; i < MAX_KEY_NUM; i++) 
	{
		if((gsl_key_data[i].x_min < x) && (x < gsl_key_data[i].x_max)&&(gsl_key_data[i].y_min < y) && (y < gsl_key_data[i].y_max))
		{
			key = gsl_key_data[i].key;	
			input_report_key(ts->input, key, 1);
			input_sync(ts->input); 		
			key_state_flag = 1;
			break;
		}
	}
}
#endif

static void report_data(struct gsl_ts *ts, u16 x, u16 y, u8 pressure, u8 id)
{
	swap(x, y);

	print_info("#####id=%d,x=%d,y=%d######\n",id,x,y);

	if(x > SCREEN_MAX_X || y > SCREEN_MAX_Y)
	{
	#ifdef HAVE_TOUCH_KEY
		report_key(ts,x,y);
	#endif
		return;
	}

	input_report_abs(ts->input, ABS_X, x);
	input_report_abs(ts->input, ABS_Y, y);
	if(pressure != 0)
	{
		input_report_key(ts->input, BTN_TOUCH, 1);
		input_report_abs(ts->input, ABS_PRESSURE, 1);
	}
	else
	{
		input_report_key(ts->input, BTN_TOUCH, 0);
		input_report_abs(ts->input, ABS_PRESSURE, 0);
	}
	input_sync(ts->input);
}

static void gslX680_ts_worker(struct work_struct *work)
{
	int rc, i;
	u8 id, touches;
	u16 x, y;

	struct gsl_ts *ts = container_of(work, struct gsl_ts,work);

	print_info("=====gslX680_ts_worker=====\n");				 

#ifdef GSL_MONITOR
	if(i2c_lock_flag != 0)
		goto i2c_lock_schedule;
	else
		i2c_lock_flag = 1;
#endif

#ifdef GSL_NOID_VERSION
	u32 tmp1;
	u8 buf[4] = {0};
	struct gsl_touch_info cinfo = {0};
#endif

	rc = gsl_ts_read(ts->client, 0x80, ts->touch_data, ts->dd->data_size);
	if (rc < 0) 
	{
		dev_err(&ts->client->dev, "read failed\n");
		goto schedule;
	}
		
	touches = ts->touch_data[ts->dd->touch_index];
	print_info("-----touches: %d -----\n", touches);		
#ifdef GSL_NOID_VERSION
	cinfo.finger_num = touches;
	print_info("tp-gsl  finger_num = %d\n",cinfo.finger_num);
	for(i = 0; i < (touches < MAX_CONTACTS ? touches : MAX_CONTACTS); i ++)
	{
		cinfo.x[i] = join_bytes( ( ts->touch_data[ts->dd->x_index  + 4 * i + 1] & 0xf),
				ts->touch_data[ts->dd->x_index + 4 * i]);
		cinfo.y[i] = join_bytes(ts->touch_data[ts->dd->y_index + 4 * i + 1],
				ts->touch_data[ts->dd->y_index + 4 * i ]);
		cinfo.id[i] = ((ts->touch_data[ts->dd->x_index  + 4 * i + 1] & 0xf0)>>4);
		print_info("tp-gsl  before: x[%d] = %d, y[%d] = %d, id[%d] = %d \n",i,cinfo.x[i],i,cinfo.y[i],i,cinfo.id[i]);
	}
	cinfo.finger_num=(ts->touch_data[3]<<24)|(ts->touch_data[2]<<16)
		|(ts->touch_data[1]<<8)|(ts->touch_data[0]);
	gsl_alg_id_main(&cinfo);
	tmp1=gsl_mask_tiaoping();
	print_info("[tp-gsl] tmp1=%x\n",tmp1);
	if(tmp1>0&&tmp1<0xffffffff)
	{
		buf[0]=0xa;buf[1]=0;buf[2]=0;buf[3]=0;
		gsl_ts_write(ts->client,0xf0,buf,4);
		buf[0]=(u8)(tmp1 & 0xff);
		buf[1]=(u8)((tmp1>>8) & 0xff);
		buf[2]=(u8)((tmp1>>16) & 0xff);
		buf[3]=(u8)((tmp1>>24) & 0xff);
		print_info("tmp1=%08x,buf[0]=%02x,buf[1]=%02x,buf[2]=%02x,buf[3]=%02x\n",
			tmp1,buf[0],buf[1],buf[2],buf[3]);
		gsl_ts_write(ts->client,0x8,buf,4);
	}
	touches = cinfo.finger_num;
#endif
	
	for(i = 1; i <= MAX_CONTACTS; i ++)
	{
		if(touches == 0)
			id_sign[i] = 0;	
		id_state_flag[i] = 0;
	}
	for(i= 0;i < (touches > MAX_FINGERS ? MAX_FINGERS : touches);i ++)
	{
	#ifdef GSL_NOID_VERSION
		id = cinfo.id[i];
		x =  cinfo.x[i];
		y =  cinfo.y[i];	
	#else
		x = join_bytes( ( ts->touch_data[ts->dd->x_index  + 4 * i + 1] & 0xf),
				ts->touch_data[ts->dd->x_index + 4 * i]);
		y = join_bytes(ts->touch_data[ts->dd->y_index + 4 * i + 1],
				ts->touch_data[ts->dd->y_index + 4 * i ]);
		id = ts->touch_data[ts->dd->id_index + 4 * i] >> 4;
	#endif

		if(1 <=id && id <= MAX_CONTACTS)
		{
		#ifdef FILTER_POINT
			filter_point(x, y ,id);
		#else
			record_point(x, y , id);
		#endif
			report_data(ts, x_new, y_new, 10, id);		
			id_state_flag[id] = 1;
		}
	}
	for(i = 1; i <= MAX_CONTACTS; i ++)
	{	
		if( (0 == touches) || ((0 != id_state_old_flag[i]) && (0 == id_state_flag[i])) )
		{
			report_data(ts, x_new, y_new, 0, i);
			id_sign[i]=0;
		}
		id_state_old_flag[i] = id_state_flag[i];
	}
	if(0 == touches)
	{
		input_mt_sync(ts->input);
	#ifdef HAVE_TOUCH_KEY
		if(key_state_flag)
		{
        		input_report_key(ts->input, key, 0);
			input_sync(ts->input);
			key_state_flag = 0;
		}
	#endif			
	}
	input_sync(ts->input);
	
schedule:
#ifdef GSL_MONITOR
	i2c_lock_flag = 0;
i2c_lock_schedule:
#endif
	enable_irq(ts->irq);
		
}

#ifdef GSL_MONITOR
static void gsl_monitor_worker(void)
{
	u8 write_buf[4] = {0};
	u8 read_buf[4]  = {0};
	char init_chip_flag = 0;
	
	print_info("----------------gsl_monitor_worker-----------------\n");	

	if(i2c_lock_flag != 0)
		goto queue_monitor_work;
	else
		i2c_lock_flag = 1;
	
	gsl_ts_read(gsl_client, 0xb0, read_buf, 4);
	if(read_buf[3] != 0x5a || read_buf[2] != 0x5a || read_buf[1] != 0x5a || read_buf[0] != 0x5a)
		b0_counter ++;
	else
		b0_counter = 0;

	if(b0_counter > 1)
	{
		print_info("======read 0xb0: %x %x %x %x ======\n",read_buf[3], read_buf[2], read_buf[1], read_buf[0]);
		init_chip_flag = 1;
		b0_counter = 0;
		goto queue_monitor_init_chip;
	}
	
	gsl_ts_read(gsl_client, 0xb4, read_buf, 4);	
	int_2nd[3] = int_1st[3];
	int_2nd[2] = int_1st[2];
	int_2nd[1] = int_1st[1];
	int_2nd[0] = int_1st[0];
	int_1st[3] = read_buf[3];
	int_1st[2] = read_buf[2];
	int_1st[1] = read_buf[1];
	int_1st[0] = read_buf[0];

	if(int_1st[3] == int_2nd[3] && int_1st[2] == int_2nd[2] &&int_1st[1] == int_2nd[1] && int_1st[0] == int_2nd[0]) 
	{
		print_info("======int_1st: %x %x %x %x , int_2nd: %x %x %x %x ======\n",int_1st[3], int_1st[2], int_1st[1], int_1st[0], int_2nd[3], int_2nd[2],int_2nd[1],int_2nd[0]);
		init_chip_flag = 1;
		goto queue_monitor_init_chip;
	}
	
#if 1 //version 1.4.0 or later than 1.4.0 read 0xbc for esd checking
	gsl_ts_read(gsl_client, 0xbc, read_buf, 4);
	if(read_buf[3] != 0 || read_buf[2] != 0 || read_buf[1] != 0 || read_buf[0] != 0)
		bc_counter++;
	else
		bc_counter = 0;
	if(bc_counter > 1)
	{
		print_info("======read 0xbc: %x %x %x %x======\n",read_buf[3], read_buf[2], read_buf[1], read_buf[0]);
		init_chip_flag = 1;
		bc_counter = 0;
	}
#else
	write_buf[3] = 0x01;
	write_buf[2] = 0xfe;
	write_buf[1] = 0x10;
	write_buf[0] = 0x00;
	gsl_ts_write(gsl_client, 0xf0, write_buf, 4);
	gsl_ts_read(gsl_client, 0x10, read_buf, 4);
	gsl_ts_read(gsl_client, 0x10, read_buf, 4);
	
	if(read_buf[3] < 10 && read_buf[2] < 10 && read_buf[1] < 10 && read_buf[0] < 10)
		dac_counter ++;
	else
		dac_counter = 0;

	if(dac_counter > 1) 
	{
		print_info("======read DAC1_0: %x %x %x %x ======\n",read_buf[3], read_buf[2], read_buf[1], read_buf[0]);
		init_chip_flag = 1;
		dac_counter = 0;
	}
#endif
queue_monitor_init_chip:
	if(init_chip_flag)
		init_chip(gsl_client);
	
	i2c_lock_flag = 0;
	
queue_monitor_work:	
	queue_delayed_work(gsl_monitor_workqueue, &gsl_monitor_work, 100);
}
#endif

static irqreturn_t gsl_ts_irq(int irq, void *dev_id)
{	
	struct gsl_ts *ts = dev_id;

	print_info("========gslX680 Interrupt=========\n");				 

	disable_irq_nosync(ts->irq);

	if (!work_pending(&ts->work)) 
	{
		queue_work(ts->wq, &ts->work);
	}
	
	return IRQ_HANDLED;

}

static int gslX680_ts_init(struct i2c_client *client, struct gsl_ts *ts)
{
	struct input_dev *input_device;
	int rc = 0;
	
	printk("[GSLX680] Enter %s\n", __func__);

	ts->dd = &devices[ts->device_id];

	if (ts->device_id == 0) {
		ts->dd->data_size = MAX_FINGERS * ts->dd->touch_bytes + ts->dd->touch_meta_data;
		ts->dd->touch_index = 0;
	}

	ts->touch_data = kzalloc(ts->dd->data_size, GFP_KERNEL);
	if (!ts->touch_data) {
		pr_err("%s: Unable to allocate memory\n", __func__);
		return -ENOMEM;
	}

	input_device = input_allocate_device();
	if (!input_device) {
		rc = -ENOMEM;
		goto error_alloc_dev;
	}

	ts->input = input_device;
	input_device->name = GSLX680_I2C_NAME;
	input_device->id.bustype = BUS_I2C;
	input_device->dev.parent = &client->dev;
	input_set_drvdata(input_device, ts);

	set_bit(EV_ABS, input_device->evbit);

	set_bit(BTN_TOUCH, input_device->keybit);
	set_bit(EV_ABS, input_device->evbit);
	set_bit(EV_KEY, input_device->evbit);
	input_set_abs_params(input_device, ABS_X, 0, SCREEN_MAX_X, 0, 0);
	input_set_abs_params(input_device, ABS_Y, 0, SCREEN_MAX_Y, 0, 0);
	input_set_abs_params(input_device, ABS_PRESSURE, 0, 1, 0, 0);
#ifdef HAVE_TOUCH_KEY
	input_device->evbit[0] = BIT_MASK(EV_KEY);
	//input_device->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
	for (i = 0; i < MAX_KEY_NUM; i++)
		set_bit(key_array[i], input_device->keybit);
#endif
	
	client->irq = IRQ_PORT;
	ts->irq = client->irq;

	ts->wq = create_singlethread_workqueue("kworkqueue_ts");
	if (!ts->wq) {
		dev_err(&client->dev, "Could not create workqueue\n");
		goto error_wq_create;
	}
	flush_workqueue(ts->wq);	

	INIT_WORK(&ts->work, gslX680_ts_worker);

	rc = input_register_device(input_device);
	if (rc)
		goto error_unreg_device;

	return 0;

error_unreg_device:
	destroy_workqueue(ts->wq);
error_wq_create:
	input_free_device(input_device);
error_alloc_dev:
	kfree(ts->touch_data);
	return rc;
}

static int gsl_ts_suspend(struct device *dev)
{
	struct gsl_ts *ts = dev_get_drvdata(dev);
//	int i;

  	print_info("I'am in gsl_ts_suspend() start\n");

#ifdef GSL_MONITOR
	print_info( "gsl_ts_suspend () : cancel gsl_monitor_work\n");
	cancel_delayed_work_sync(&gsl_monitor_work);
#endif
	
	disable_irq_nosync(ts->irq);	
		   
	gslX680_shutdown_low();

#ifdef SLEEP_CLEAR_POINT
	msleep(10); 			
	input_mt_sync(ts->input);
	input_sync(ts->input);
	msleep(10); 	
	report_data(ts, 1, 1, 10, 1);		
	input_sync(ts->input);	
#endif	

	return 0;
}

static int gsl_ts_resume(struct device *dev)
{
	struct gsl_ts *ts = dev_get_drvdata(dev);
//	int i;
	
  	print_info("I'am in gsl_ts_resume() start\n");

	gslX680_shutdown_high();
	msleep(20); 	
	reset_chip(ts->client);
	startup_chip(ts->client);
	check_mem_data(ts->client);

#ifdef SLEEP_CLEAR_POINT	
	input_mt_sync(ts->input);
	input_sync(ts->input);	
#endif
#ifdef GSL_MONITOR
	print_info( "gsl_ts_resume () : queue gsl_monitor_work\n");
	queue_delayed_work(gsl_monitor_workqueue, &gsl_monitor_work, 300);
#endif	
	
	enable_irq(ts->irq);

	return 0;
}

#ifdef CONFIG_HAS_EARLYSUSPEND
static void gsl_ts_early_suspend(struct early_suspend *h)
{
	struct gsl_ts *ts = container_of(h, struct gsl_ts, early_suspend);
	print_info("[GSLX680] Enter %s\n", __func__);
	gsl_ts_suspend(&ts->client->dev);
}

static void gsl_ts_late_resume(struct early_suspend *h)
{
	struct gsl_ts *ts = container_of(h, struct gsl_ts, early_suspend);
	print_info("[GSLX680] Enter %s\n", __func__);
	gsl_ts_resume(&ts->client->dev);
}
#endif

static int __devinit gsl_ts_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
	struct gsl_ts *ts;
	int rc;

	print_info("GSLX680 Enter %s\n", __func__);
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
		dev_err(&client->dev, "I2C functionality not supported\n");
		return -ENODEV;
	}
 
	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
	if (!ts)
		return -ENOMEM;
	print_info("==kzalloc success=\n");

	ts->client = client;
	i2c_set_clientdata(client, ts);
	ts->device_id = id->driver_data;

	rc = gslX680_ts_init(client, ts);
	if (rc < 0) {
		dev_err(&client->dev, "GSLX680 init failed\n");
		goto error_mutex_destroy;
	}	

	gsl_client = client;
	
	gslX680_init();
	init_chip(ts->client);
	check_mem_data(ts->client);
	
	rc=  request_irq(client->irq, gsl_ts_irq, IRQF_TRIGGER_RISING, client->name, ts);
	if (rc < 0) {
		print_info( "gsl_probe: request irq failed\n");
		goto error_req_irq_fail;
	}

	/* create debug attribute */
	//rc = device_create_file(&ts->input->dev, &dev_attr_debug_enable);

#ifdef CONFIG_HAS_EARLYSUSPEND
	ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
	//ts->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 1;
	ts->early_suspend.suspend = gsl_ts_early_suspend;
	ts->early_suspend.resume = gsl_ts_late_resume;
	register_early_suspend(&ts->early_suspend);
#endif


#ifdef GSL_MONITOR
	print_info( "gsl_ts_probe () : queue gsl_monitor_workqueue\n");

	INIT_DELAYED_WORK(&gsl_monitor_work, gsl_monitor_worker);
	gsl_monitor_workqueue = create_singlethread_workqueue("gsl_monitor_workqueue");
	queue_delayed_work(gsl_monitor_workqueue, &gsl_monitor_work, 1000);
#endif

	print_info("[GSLX680] End %s\n", __func__);

	return 0;

//exit_set_irq_mode:	
error_req_irq_fail:
    free_irq(ts->irq, ts);	

error_mutex_destroy:
	input_free_device(ts->input);
	kfree(ts);
	return rc;
}

static int __devexit gsl_ts_remove(struct i2c_client *client)
{
	struct gsl_ts *ts = i2c_get_clientdata(client);
	print_info("==gsl_ts_remove=\n");

#ifdef CONFIG_HAS_EARLYSUSPEND
	unregister_early_suspend(&ts->early_suspend);
#endif

#ifdef GSL_MONITOR
	cancel_delayed_work_sync(&gsl_monitor_work);
	destroy_workqueue(gsl_monitor_workqueue);
#endif

	device_init_wakeup(&client->dev, 0);
	cancel_work_sync(&ts->work);
	free_irq(ts->irq, ts);
	destroy_workqueue(ts->wq);
	input_unregister_device(ts->input);
	//device_remove_file(&ts->input->dev, &dev_attr_debug_enable);
	
	kfree(ts->touch_data);
	kfree(ts);

	return 0;
}

static const struct i2c_device_id gsl_ts_id[] = {
	{GSLX680_I2C_NAME, 0},
	{}
};
MODULE_DEVICE_TABLE(i2c, gsl_ts_id);

static struct i2c_driver gsl_ts_driver = {
	.driver = {
		.name = GSLX680_I2C_NAME,
		.owner = THIS_MODULE,
	},
#ifndef CONFIG_HAS_EARLYSUSPEND
	.suspend	= gsl_ts_suspend,
	.resume	= gsl_ts_resume,
#endif
	.probe		= gsl_ts_probe,
	.remove		= __devexit_p(gsl_ts_remove),
	.id_table	= gsl_ts_id,
};

static int __init gsl_ts_init(void)
{
    int ret;
	print_info("==gsl_ts_init==\n");
	ret = i2c_add_driver(&gsl_ts_driver);
	print_info("ret=%d\n",ret);
	return ret;
}
static void __exit gsl_ts_exit(void)
{
	print_info("==gsl_ts_exit==\n");
	i2c_del_driver(&gsl_ts_driver);
	return;
}

module_init(gsl_ts_init);
module_exit(gsl_ts_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("GSLX680 touchscreen controller driver");
MODULE_AUTHOR("Guan Yuwei, guanyuwei@basewin.com");
MODULE_ALIAS("platform:gsl_ts");

“gslX680.h”

#ifndef _GSLX680_H_
#define _GSLX680_H_


#define SCREEN_MAX_X 		1024
#define SCREEN_MAX_Y 		600

struct fw_data
{
    u32 offset : 8;
    u32 : 0;
    u32 val;
};

#define 	GSL_NOID_VERSION
#ifdef	GSL_NOID_VERSION
struct gsl_touch_info
{
	int x[10];
	int y[10];
	int id[10];
	int finger_num;	
};
extern unsigned int gsl_mask_tiaoping(void);
extern unsigned int gsl_version_id(void);
extern void gsl_alg_id_main(struct gsl_touch_info *cinfo);
extern void gsl_DataInit(int *ret);
static unsigned int gsl_config_data_id[]= 
{
	0x7b1bd8,  
	0x200,
	0,0,
	0,
	0,0,0,
	0,0,0,0,0,0,0,0xd887666b,


	0x900,0x5,0xa000f,0xa000f,0x2580400,0,0x5100,0x8e00,
	0,0x320014,0,0,0,0,0,0,
	0x8,0x4000,0x1000,0x10410005,0x10450008,0,0,0x1010101,
	0x1b6db688,0x190,0xc00014,0xb3001e,0xa60028,0x9a0032,0xc00014,0xb3001e,
	0xa60028,0x9a0032,0xc00014,0xb3001e,0xa60028,0x9a0032,0xc00014,0xb3001e,
	0xa60028,0x9a0032,0x804000,0x90040,0x90001,0,0,0,
	0,0,0,0x14012c,0xa003c,0xa0078,0x400,0x1081,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,

	0,//key_map
	0x3200384,0x64,0x503e8,//0
	0,0,0,//1
	0,0,0,//2
	0,0,0,//3
	0,0,0,//4
	0,0,0,//5
	0,0,0,//6
	0,0,0,//7

	0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,



#endif

另一款ft5x0x_ts驱动源代码

ft5x0x_ts.h

#ifndef __LINUX_FT5X0X_TS_H__
#define __LINUX_FT5X0X_TS_H__

#define SCREEN_MAX_X    800
#define SCREEN_MAX_Y    480
#define PRESS_MAX       255

#define FT5X0X_NAME	"ft5x0x_ts"//"synaptics_i2c_rmi"//"synaptics-rmi-ts"// 

struct ft5x0x_ts_platform_data{
	u16	intr;		/* irq number	*/
};

enum ft5x0x_ts_regs {
	FT5X0X_REG_PMODE	= 0xA5,	/* Power Consume Mode		*/	
};

//FT5X0X_REG_PMODE
#define PMODE_ACTIVE        0x00
#define PMODE_MONITOR       0x01
#define PMODE_STANDBY       0x02
#define PMODE_HIBERNATE     0x03


	#ifndef ABS_MT_TOUCH_MAJOR
	#define ABS_MT_TOUCH_MAJOR	0x30	/* touching ellipse */
	#define ABS_MT_TOUCH_MINOR	0x31	/* (omit if circular) */
	#define ABS_MT_WIDTH_MAJOR	0x32	/* approaching ellipse */
	#define ABS_MT_WIDTH_MINOR	0x33	/* (omit if circular) */
	#define ABS_MT_ORIENTATION	0x34	/* Ellipse orientation */
	#define ABS_MT_POSITION_X	0x35	/* Center X ellipse position */
	#define ABS_MT_POSITION_Y	0x36	/* Center Y ellipse position */
	#define ABS_MT_TOOL_TYPE	0x37	/* Type of touching device */
	#define ABS_MT_BLOB_ID		0x38	/* Group set of pkts as blob */
	#endif /* ABS_MT_TOUCH_MAJOR */


#endif

ft5x0x_ts.c

/* 
 * drivers/input/touchscreen/ft5x0x_ts.c
 *
 * FocalTech ft5x0x TouchScreen driver. 
 *
 * Copyright (c) 2010  Focal tech Ltd.
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 *
 *	note: only support mulititouch	Wenfs 2010-10-01
 */

#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/ft5x0x_ts.h>
#include <linux/earlysuspend.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
//#include <asm/jzsoc.h>

#ifndef __LINUX_FT5X0X_TS_H__
#define __LINUX_FT5X0X_TS_H__

#define SCREEN_MAX_X    800
#define SCREEN_MAX_Y    480
#define PRESS_MAX       255

#define FT5X0X_NAME	"ft5x0x_ts"//"synaptics_i2c_rmi"//"synaptics-rmi-ts"// 

struct ft5x0x_ts_platform_data{
	u16	intr;		/* irq number	*/
};

enum ft5x0x_ts_regs {
	FT5X0X_REG_PMODE	= 0xA5,	/* Power Consume Mode		*/	
};

//FT5X0X_REG_PMODE
#define PMODE_ACTIVE        0x00
#define PMODE_MONITOR       0x01
#define PMODE_STANDBY       0x02
#define PMODE_HIBERNATE     0x03


#ifndef ABS_MT_TOUCH_MAJOR
#define ABS_MT_TOUCH_MAJOR	0x30	/* touching ellipse */
#define ABS_MT_TOUCH_MINOR	0x31	/* (omit if circular) */
#define ABS_MT_WIDTH_MAJOR	0x32	/* approaching ellipse */
#define ABS_MT_WIDTH_MINOR	0x33	/* (omit if circular) */



static struct i2c_client *this_client;
static struct ft5x0x_ts_platform_data *pdata;

#define CONFIG_FT5X0X_MULTITOUCH 1

struct ts_event {
	u16	x1;
	u16	y1;
	u16	x2;
	u16	y2;
	u16	x3;
	u16	y3;
	u16	x4;
	u16	y4;
	u16	x5;
	u16	y5;
	u16	pressure;
    u8  touch_point;
};

struct ft5x0x_ts_data {
	struct input_dev	*input_dev;
	struct ts_event		event;
	struct work_struct 	pen_event_work;
	struct workqueue_struct *ts_workqueue;
	struct early_suspend	early_suspend;
};

static int ft5x0x_i2c_rxdata(char *rxdata, int length)
{
	int ret;

	struct i2c_msg msgs[] = {
		{
			.addr	= this_client->addr,
			.flags	= 0,
			.len	= 1,
			.buf	= rxdata,
		},
		{
			.addr	= this_client->addr,
			.flags	= I2C_M_RD,
			.len	= length,
			.buf	= rxdata,
		},
	};

    //msleep(1);
	ret = i2c_transfer(this_client->adapter, msgs, 2);
	if (ret < 0)
		pr_err("msg %s i2c read error: %d\n", __func__, ret);
	
	return ret;
}

static int ft5x0x_i2c_txdata(char *txdata, int length)
{
	int ret;

	struct i2c_msg msg[] = {
		{
			.addr	= this_client->addr,
			.flags	= 0,
			.len	= length,
			.buf	= txdata,
		},
	};

   	//msleep(1);
	ret = i2c_transfer(this_client->adapter, msg, 1);
	if (ret < 0)
		pr_err("%s i2c write error: %d\n", __func__, ret);

	return ret;
}

static int ft5x0x_set_reg(u8 addr, u8 para)
{
    u8 buf[3];
    int ret = -1;

    buf[0] = addr;
    buf[1] = para;
    ret = ft5x0x_i2c_txdata(buf, 2);
    if (ret < 0) {
        pr_err("write reg failed! %#x ret: %d", buf[0], ret);
        return -1;
    }
    
    return 0;
}

static void ft5x0x_ts_release(void)
{
	struct ft5x0x_ts_data *data = i2c_get_clientdata(this_client);
#ifdef CONFIG_FT5X0X_MULTITOUCH	
	input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, 0);
#else
	input_report_abs(data->input_dev, ABS_PRESSURE, 0);
	input_report_key(data->input_dev, BTN_TOUCH, 0);
#endif
	input_sync(data->input_dev);
}

static int ft5x0x_read_data(void)
{
	struct ft5x0x_ts_data *data = i2c_get_clientdata(this_client);
	struct ts_event *event = &data->event;
//	u8 buf[14] = {0};
	u8 buf[32] = {0};
	int ret = -1;

#ifdef CONFIG_FT5X0X_MULTITOUCH
//	ret = ft5x0x_i2c_rxdata(buf, 13);
	ret = ft5x0x_i2c_rxdata(buf, 31);
#else
    ret = ft5x0x_i2c_rxdata(buf, 7);
#endif
    if (ret < 0) {
		printk("%s read_data i2c_rxdata failed: %d\n", __func__, ret);
		return ret;
	}

	memset(event, 0, sizeof(struct ts_event));
//	event->touch_point = buf[2] & 0x03;// 0000 0011
	event->touch_point = buf[2] & 0x07;// 000 0111

    if (event->touch_point == 0) {
        ft5x0x_ts_release();
        return 1; 
    }

#ifdef CONFIG_FT5X0X_MULTITOUCH
    switch (event->touch_point) {
		case 5:
			event->x5 = (s16)(buf[0x1b] & 0x0F)<<8 | (s16)buf[0x1c];
			event->y5 = (s16)(buf[0x1d] & 0x0F)<<8 | (s16)buf[0x1e];
		case 4:
			event->x4 = (s16)(buf[0x15] & 0x0F)<<8 | (s16)buf[0x16];
			event->y4 = (s16)(buf[0x17] & 0x0F)<<8 | (s16)buf[0x18];
		case 3:
			event->x3 = (s16)(buf[0x0f] & 0x0F)<<8 | (s16)buf[0x10];
			event->y3 = (s16)(buf[0x11] & 0x0F)<<8 | (s16)buf[0x12];
		case 2:
			event->x2 = (s16)(buf[9] & 0x0F)<<8 | (s16)buf[10];
			event->y2 = (s16)(buf[11] & 0x0F)<<8 | (s16)buf[12];
		case 1:
			event->x1 = (s16)(buf[3] & 0x0F)<<8 | (s16)buf[4];
			event->y1 = (s16)(buf[5] & 0x0F)<<8 | (s16)buf[6];
            break;
		default:
		    return -1;
	}
#else
    if (event->touch_point == 1) {
    	event->x1 = (s16)(buf[3] & 0x0F)<<8 | (s16)buf[4];
		event->y1 = (s16)(buf[5] & 0x0F)<<8 | (s16)buf[6];
    }
#endif
    event->pressure = 200;

	dev_dbg(&this_client->dev, "%s: 1:%d %d 2:%d %d \n", __func__,
		event->x1, event->y1, event->x2, event->y2);
	//printk("%d (%d, %d), (%d, %d)\n", event->touch_point, event->x1, event->y1, event->x2, event->y2);

    return 0;
}

static void ft5x0x_report_value(void)
{
	struct ft5x0x_ts_data *data = i2c_get_clientdata(this_client);
	struct ts_event *event = &data->event;

//		printk("==ft5x0x_report_value =\n");
#ifdef CONFIG_FT5X0X_MULTITOUCH
	switch(event->touch_point) {
		case 5:
			input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure);
			input_report_abs(data->input_dev, ABS_MT_POSITION_X, event->x5);
			input_report_abs(data->input_dev, ABS_MT_POSITION_Y, event->y5);
			input_report_abs(data->input_dev, ABS_MT_WIDTH_MAJOR, 1);
			input_mt_sync(data->input_dev);
//			printk("===x2 = %d,y2 = %d ====\n",event->x2,event->y2);
		case 4:
			input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure);
			input_report_abs(data->input_dev, ABS_MT_POSITION_X, event->x4);
			input_report_abs(data->input_dev, ABS_MT_POSITION_Y, event->y4);
			input_report_abs(data->input_dev, ABS_MT_WIDTH_MAJOR, 1);
			input_mt_sync(data->input_dev);
//			printk("===x2 = %d,y2 = %d ====\n",event->x2,event->y2);
		case 3:
			input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure);
			input_report_abs(data->input_dev, ABS_MT_POSITION_X, event->x3);
			input_report_abs(data->input_dev, ABS_MT_POSITION_Y, event->y3);
			input_report_abs(data->input_dev, ABS_MT_WIDTH_MAJOR, 1);
			input_mt_sync(data->input_dev);
//			printk("===x2 = %d,y2 = %d ====\n",event->x2,event->y2);
		case 2:
			input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure);
			input_report_abs(data->input_dev, ABS_MT_POSITION_X, event->x2);
			input_report_abs(data->input_dev, ABS_MT_POSITION_Y, event->y2);
			input_report_abs(data->input_dev, ABS_MT_WIDTH_MAJOR, 1);
			input_mt_sync(data->input_dev);
//			printk("===x2 = %d,y2 = %d ====\n",event->x2,event->y2);
		case 1:
			input_report_abs(data->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure);
			input_report_abs(data->input_dev, ABS_MT_POSITION_X, event->x1);
			input_report_abs(data->input_dev, ABS_MT_POSITION_Y, event->y1);
			input_report_abs(data->input_dev, ABS_MT_WIDTH_MAJOR, 1);
			input_mt_sync(data->input_dev);
			printk("===x1 = %d,y1 = %d ====\n",event->x1,event->y1);
		default:
//			printk("==touch_point default =\n");
			break;
	}
#else	/* CONFIG_FT5X0X_MULTITOUCH*/
	if (event->touch_point == 1) {
		input_report_abs(data->input_dev, ABS_X, event->x1);
		input_report_abs(data->input_dev, ABS_Y, event->y1);
		input_report_abs(data->input_dev, ABS_PRESSURE, event->pressure);
	}
	input_report_key(data->input_dev, BTN_TOUCH, 1);
#endif	/* CONFIG_FT5X0X_MULTITOUCH*/
	input_sync(data->input_dev);

	dev_dbg(&this_client->dev, "%s: 1:%d %d 2:%d %d \n", __func__,
		event->x1, event->y1, event->x2, event->y2);
}	/*end ft5x0x_report_value*/

static void ft5x0x_ts_pen_irq_work(struct work_struct *work)
{
	int ret = -1;
//	printk("==work 1=\n");
	ret = ft5x0x_read_data();	
	if (ret == 0) {	
		ft5x0x_report_value();
	}
//	else printk("data package read error\n");
//	printk("==work 2=\n");
//    	msleep(1);
//    enable_irq(this_client->irq);
	enable_irq(IRQ_EINT(6));
}

static irqreturn_t ft5x0x_ts_interrupt(int irq, void *dev_id)
{
	struct ft5x0x_ts_data *ft5x0x_ts = dev_id;
//    	disable_irq(this_client->irq);		
	disable_irq(IRQ_EINT(6));
//	printk("==int=\n");
	if (!work_pending(&ft5x0x_ts->pen_event_work)) {
		queue_work(ft5x0x_ts->ts_workqueue, &ft5x0x_ts->pen_event_work);
	}

	return IRQ_HANDLED;
}

#ifdef CONFIG_HAS_EARLYSUSPEND
static void ft5x0x_ts_suspend(struct early_suspend *handler)
{
//	struct ft5x0x_ts_data *ts;
//	ts =  container_of(handler, struct ft5x0x_ts_data, early_suspend);

	printk("==ft5x0x_ts_suspend=\n");
//	disable_irq(this_client->irq);
//	disable_irq(IRQ_EINT(6));
//	cancel_work_sync(&ts->pen_event_work);
//	flush_workqueue(ts->ts_workqueue);
	// ==set mode ==, 
//    	ft5x0x_set_reg(FT5X0X_REG_PMODE, PMODE_HIBERNATE);
}

static void ft5x0x_ts_resume(struct early_suspend *handler)
{
	printk("==ft5x0x_ts_resume=\n");
	// wake the mode
//	__gpio_as_output(GPIO_FT5X0X_WAKE);		
//	__gpio_clear_pin(GPIO_FT5X0X_WAKE);		//set wake = 0,base on system
//	 msleep(100);
//	__gpio_set_pin(GPIO_FT5X0X_WAKE);			//set wake = 1,base on system
//	msleep(100);
//	enable_irq(this_client->irq);
//	enable_irq(IRQ_EINT(6));
}
#endif  //CONFIG_HAS_EARLYSUSPEND

static int 
ft5x0x_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct ft5x0x_ts_data *ft5x0x_ts;
	struct input_dev *input_dev;
	int err = 0;
	
	printk("==ft5x0x_ts_probe=\n");
	
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
		err = -ENODEV;
		goto exit_check_functionality_failed;
	}

	printk("==kzalloc=\n");
	ft5x0x_ts = kzalloc(sizeof(*ft5x0x_ts), GFP_KERNEL);
	if (!ft5x0x_ts)	{
		err = -ENOMEM;
		goto exit_alloc_data_failed;
	}

//	printk("==i2c_set_clientdata=\n");
	this_client = client;
	i2c_set_clientdata(client, ft5x0x_ts);
//	i2c_jz_setclk(client, 100*1000);

//	printk("==INIT_WORK=\n");
	INIT_WORK(&ft5x0x_ts->pen_event_work, ft5x0x_ts_pen_irq_work);
//	printk("==create_singlethread_workqueue=\n");
	ft5x0x_ts->ts_workqueue = create_singlethread_workqueue(dev_name(&client->dev));
	if (!ft5x0x_ts->ts_workqueue) {
		err = -ESRCH;
		goto exit_create_singlethread;
	}

//	pdata = client->dev.platform_data;
//	if (pdata == NULL) {
//		dev_err(&client->dev, "%s: platform data is null\n", __func__);
//		goto exit_platform_data_null;
//	}
	
//	printk("==request_irq=\n");
//	err = request_irq(client->irq, ft5x0x_ts_interrupt, IRQF_DISABLED, "ft5x0x_ts", ft5x0x_ts);
	err = request_irq(IRQ_EINT(6), ft5x0x_ts_interrupt, IRQF_TRIGGER_FALLING, "ft5x0x_ts", ft5x0x_ts);
	if (err < 0) {
		dev_err(&client->dev, "ft5x0x_probe: request irq failed\n");
		goto exit_irq_request_failed;
	}

//	__gpio_as_irq_fall_edge(pdata->intr);		//
//	disable_irq(this_client->irq);
	disable_irq(IRQ_EINT(6));

//	printk("==input_allocate_device=\n");
	input_dev = input_allocate_device();
	if (!input_dev) {
		err = -ENOMEM;
		dev_err(&client->dev, "failed to allocate input device\n");
		goto exit_input_dev_alloc_failed;
	}
	
	ft5x0x_ts->input_dev = input_dev;

#ifdef CONFIG_FT5X0X_MULTITOUCH
	set_bit(ABS_MT_TOUCH_MAJOR, input_dev->absbit);
	set_bit(ABS_MT_POSITION_X, input_dev->absbit);
	set_bit(ABS_MT_POSITION_Y, input_dev->absbit);
	set_bit(ABS_MT_WIDTH_MAJOR, input_dev->absbit);

	input_set_abs_params(input_dev,
			     ABS_MT_POSITION_X, 0, SCREEN_MAX_X, 0, 0);
	input_set_abs_params(input_dev,
			     ABS_MT_POSITION_Y, 0, SCREEN_MAX_Y, 0, 0);
	input_set_abs_params(input_dev,
			     ABS_MT_TOUCH_MAJOR, 0, PRESS_MAX, 0, 0);
	input_set_abs_params(input_dev,
			     ABS_MT_WIDTH_MAJOR, 0, 200, 0, 0);
#else
	set_bit(ABS_X, input_dev->absbit);
	set_bit(ABS_Y, input_dev->absbit);
	set_bit(ABS_PRESSURE, input_dev->absbit);
	set_bit(BTN_TOUCH, input_dev->keybit);

	input_set_abs_params(input_dev, ABS_X, 0, SCREEN_MAX_X, 0, 0);
	input_set_abs_params(input_dev, ABS_Y, 0, SCREEN_MAX_Y, 0, 0);
	input_set_abs_params(input_dev,
			     ABS_PRESSURE, 0, PRESS_MAX, 0 , 0);
#endif

	set_bit(EV_ABS, input_dev->evbit);
	set_bit(EV_KEY, input_dev->evbit);

	input_dev->name		= FT5X0X_NAME;		//dev_name(&client->dev)
	err = input_register_device(input_dev);
	if (err) {
		dev_err(&client->dev,
		"ft5x0x_ts_probe: failed to register input device: %s\n",
		dev_name(&client->dev));
		goto exit_input_register_device_failed;
	}

#ifdef CONFIG_HAS_EARLYSUSPEND
	printk("==register_early_suspend =\n");
	ft5x0x_ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
	ft5x0x_ts->early_suspend.suspend = ft5x0x_ts_suspend;
	ft5x0x_ts->early_suspend.resume	= ft5x0x_ts_resume;
	register_early_suspend(&ft5x0x_ts->early_suspend);
#endif
//wake the CTPM
//	__gpio_as_output(GPIO_FT5X0X_WAKE);		
//	__gpio_clear_pin(GPIO_FT5X0X_WAKE);		//set wake = 0,base on system
//	 msleep(100);
//	__gpio_set_pin(GPIO_FT5X0X_WAKE);			//set wake = 1,base on system
//	msleep(100);
//	ft5x0x_set_reg(0x88, 0x05); //5, 6,7,8
//	ft5x0x_set_reg(0x80, 30);
//	msleep(50);
//    	enable_irq(this_client->irq);
    	enable_irq(IRQ_EINT(6));

	printk("==probe over =\n");
    return 0;

exit_input_register_device_failed:
	input_free_device(input_dev);
exit_input_dev_alloc_failed:
//	free_irq(client->irq, ft5x0x_ts);
	free_irq(IRQ_EINT(6), ft5x0x_ts);
exit_irq_request_failed:
exit_platform_data_null:
	cancel_work_sync(&ft5x0x_ts->pen_event_work);
	destroy_workqueue(ft5x0x_ts->ts_workqueue);
exit_create_singlethread:
	printk("==singlethread error =\n");
	i2c_set_clientdata(client, NULL);
	kfree(ft5x0x_ts);
exit_alloc_data_failed:
exit_check_functionality_failed:
	return err;
}

static int __devexit ft5x0x_ts_remove(struct i2c_client *client)
{
	printk("==ft5x0x_ts_remove=\n");
	struct ft5x0x_ts_data *ft5x0x_ts = i2c_get_clientdata(client);
	unregister_early_suspend(&ft5x0x_ts->early_suspend);
//	free_irq(client->irq, ft5x0x_ts);
	free_irq(IRQ_EINT(6), ft5x0x_ts);
	input_unregister_device(ft5x0x_ts->input_dev);
	kfree(ft5x0x_ts);
	cancel_work_sync(&ft5x0x_ts->pen_event_work);
	destroy_workqueue(ft5x0x_ts->ts_workqueue);
	i2c_set_clientdata(client, NULL);
	return 0;
}

static const struct i2c_device_id ft5x0x_ts_id[] = {
	{ FT5X0X_NAME, 0 },{ }
};
MODULE_DEVICE_TABLE(i2c, ft5x0x_ts_id);

static struct i2c_driver ft5x0x_ts_driver = {
	.probe		= ft5x0x_ts_probe,
	.remove		= __devexit_p(ft5x0x_ts_remove),
	.id_table	= ft5x0x_ts_id,
	.driver	= {
		.name	= FT5X0X_NAME,
		.owner	= THIS_MODULE,
	},
};

static int __init ft5x0x_ts_init(void)
{
	return i2c_add_driver(&ft5x0x_ts_driver);
}

static void __exit ft5x0x_ts_exit(void)
{
	i2c_del_driver(&ft5x0x_ts_driver);
}

module_init(ft5x0x_ts_init);
module_exit(ft5x0x_ts_exit);

MODULE_AUTHOR("<wenfs@Focaltech-systems.com>");
MODULE_DESCRIPTION("FocalTech ft5x0x TouchScreen driver");
MODULE_LICENSE("GPL");

本书是根据相关的博客做的PDF格式的电子书,欢迎到原作者的博客去看看。 这个是目录: ·嵌入式Linux之我行——虚拟机中安装Linux ·嵌入式Linux之我行——虚拟机中实现Linux与Windows之间的文件传输 ·嵌入式Linux之我行——开发环境的建立与Eclipse的使用 ·嵌入式Linux之我行——配置内核时出现“ncurses-devel”错误 ·嵌入式Linux之我行——C+CGI+Ajax在S3C2440中的应用 ·嵌入式Linux之我行——嵌入式数据库sqlite在2440上的移植 ·嵌入式Linux之我行——嵌入式数据库sqlite在2440上的应用 ·嵌入式Linux之我行——Linux-2.6.30.4在2440上的移植之内核 ·嵌入式Linux之我行——Linux-2.6.30.4在2440上的移植之文件系统 ·嵌入式Linux之我行——Linux-2.6.30.4在2440上的移植之RTC时钟驱动 ·嵌入式Linux之我行——Linux-2.6.30.4在2440上的移植之DM9000E网卡驱动 ·嵌入式Linux之我行——Linux-2.6.30.4在2440上的移植之USB驱动 ·嵌入式Linux之我行——Linux-2.6.30.4在2440上的移植之MMC/SD卡驱动 ·嵌入式Linux之我行——Linux-2.6.30.4在2440上的移植之LCD驱动 ·嵌入式Linux之我行——Linux-2.6.30.4在2440上的移植之触摸屏驱动 ·嵌入式Linux之我行——Linux-2.6.30.4在2440上的移植之UDA1341声卡驱动 ·嵌入式Linux之我行——u-boot-2009.08在2440上的移植详解() ·嵌入式Linux之我行——u-boot-2009.08在2440上的移植详解(二) ·嵌入式Linux之我行——u-boot-2009.08在2440上的移植详解(三) ·嵌入式Linux之我行——u-boot-2009.08在2440上的移植详解(四) ·嵌入式Linux之我行——u-boot-2009.08在2440上的移植详解(五) ·嵌入式Linux之我行——u-boot-2009.08在2440上的移植详解(六) ·嵌入式Linux之我行——s3c2440的IO静态映射的分析 ·嵌入式Linux之我行——内核访问外设I/O资源的方式 ·嵌入式Linux之我行——深入理解DM9000在mini2440上的驱动 ·嵌入式Linux之我行——LCD背光驱动在2440上的实例开发 ·嵌入式Linux之我行——LED驱动在2440上的实例开发 ·Linux内核常用的些宏的收集 ·嵌入式Linux之我行——按键驱动在2440上的实例开发(带去抖动) ·嵌入式Linux之我行——ARM MMU工作原理剖析 ·嵌入式Linux之我行——设备文件系统剖析与使用 ·嵌入式Linux之我行——PWM在ARM Linux中的原理和蜂鸣器驱动实例开发 ·嵌入式Linux之我行——S3C2440上RTC时钟驱动开发实例讲解 ·嵌入式Linux之我行——S3C2440上看门狗(Watchdog)驱动开发实例讲解 ·嵌入式Linux之我行——S3C2440上ADC驱动实例开发讲解 ·嵌入式Linux之我行——S3C2440触摸屏驱动实例开发讲解 ·嵌入式Linux之我行——S3C2440上LCD驱动(FrameBuffer)实例开发讲解() ·嵌入式Linux之我行——S3C2440上LCD驱动(FrameBuffer)实例开发讲解(二) ·嵌入式Linux之我行——RamDisk块设备驱动实例开发讲解 ·嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解() ·嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解() ·嵌入式Linux之我行——内核通知链机制的原理及实现(转载) ·嵌入式Linux之我行——S3C2440上Flash驱动实例开发讲解()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值