48.Linux IIC驱动多点电容屏

48.Linux IIC驱动多点电容屏

  1. 电容触摸屏,上报多点触摸信息,通过触摸芯片,比如FT5426,使用IIC驱动的。

  2. 触摸IC一般都有INT,当检测到触摸信息以后就会触发中断,那么就要在中断处理函数里面读取触摸点信息

  3. 得到触摸线信息,Linux系统如何使用,input设备,Linux系统下有触摸屏上报的流程,涉及到input子系统下触摸信息的上报------input子系统的多点电容触摸协议

    Documentation/input/multi-touch-protocol.txt

多点电容触摸(Multi-touch,简称 MT) MT 协议被分为两种类型, Type A 和 TypeB,

Type A:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(此类型在实际使用中非常少! )。这些信息需要系统去识别或追踪(只能软件追踪,需要实现算法)

Type B:适用于有硬件追踪 并能区分触摸点的触摸设备,此类型设备通过 slot 更新某一个触摸点的信息, FT5426 就属于此类型,一般的多点电容触摸屏 IC 都有此能力。

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

c 复制代码
#define ABS_MT_SLOT			0x2f	/* 正在修改的多点触控(MT)槽位 */
#define ABS_MT_TOUCH_MAJOR	0x30	/* 触摸椭圆的主轴 */
#define ABS_MT_TOUCH_MINOR	0x31	/* 触摸椭圆的副轴(如果是圆形则省略) */
#define ABS_MT_WIDTH_MAJOR	0x32	/* 接近中的椭圆主轴 */
#define ABS_MT_WIDTH_MINOR	0x33	/* 接近中的椭圆副轴(如果是圆形则省略) */
#define ABS_MT_ORIENTATION	0x34	/* 椭圆方向 */
#define ABS_MT_POSITION_X	0x35	/* 触摸中心的X坐标 */
#define ABS_MT_POSITION_Y	0x36	/* 触摸中心的Y坐标 */
#define ABS_MT_TOOL_TYPE	0x37	/* 触摸设备的类型 */
#define ABS_MT_BLOB_ID		0x38	/* 将一组数据包归为一组(一个blob) */
#define ABS_MT_TRACKING_ID	0x39	/* 初始接触点的唯一ID */
#define ABS_MT_PRESSURE		0x3a	/* 接触区域的压力 */
#define ABS_MT_DISTANCE		0x3b	/* 接触悬空距离 */
#define ABS_MT_TOOL_X		0x3c	/* 设备的中心X坐标 */
#define ABS_MT_TOOL_Y		0x3d	/* 设备的中心Y坐标 */

最常用的:ABS_MT_SLOT 、

ABS_MT_POSITION_X 、 ABS_MT_POSITION_Y (用来上 报触摸 点的 (X,Y) 坐标 信息 )和

ABS_MT_TRACKING_ID 。 (ABS_MT_SLOT 用 来 上 报 触 摸 点 ID )

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 函数来完成此工作。

对 于 Type B 类 型 的 设 备 , 需 要 用 到ABS_MT_TRACKING_ID 事件来区分触摸点。

对于 Type A 类型的设备,通过 input_mt_sync()函数来隔离不同的触摸点数据信息,此函数原型如下所示:

c 复制代码
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()函数原型如下所示:

c 复制代码
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。

Type A 触摸点信息上报时序

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

c 复制代码
 ABS_MT_POSITION_X x[0]  	//上报第一个触摸点的 X 坐标数据,通过input_report_abs 函数实现,下面同理。
 ABS_MT_POSITION_Y y[0]		//上报第一个触摸点的 Y 坐标数据。
 SYN_MT_REPORT				//通过调用 input_mt_sync 函数来实现。
 ABS_MT_POSITION_X x[1]		//上报第二个触摸点的 X 坐标数据。
 ABS_MT_POSITION_Y y[1]		//上报第二个触摸点的 Y 坐标数据。
 SYN_MT_REPORT				//通过调用 input_mt_sync 函数来实现。	
 SYN_REPORT					//通过调用 input_sync 函数实现。

简单实例:

c 复制代码
static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id)
 {
...
     ret = st1232_ts_read_data(ts);
     if (ret < 0)
     goto end;

     /* multi touch protocol */
     for (i = 0; i < MAX_FINGERS; i++) {
         if (!finger[i].is_valid)
         continue;

         input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t);
         input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x);
         input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y);
         input_mt_sync(input_dev);
         count++;
 	}
...

 /* SYN_REPORT */
     input_sync(input_dev);
 end:
 	return IRQ_HANDLED;
 }

st1232_ts_read_data获取所有触摸点信息。 通过for循环循环按照 Type A 类型轮流上报所有的触摸点坐标信息 每上报完一个触摸点坐标,都要调用 input_mt_sync 函数上报一个 SYN_MT_REPORT信息。 每上报完一轮触摸点信息就调用一次 input_sync 函数,也就是发送一个SYN_REPORT 事件

Type B 触摸点信息上报时序

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

c 复制代码
ABS_MT_SLOT 0			//上报 ABS_MT_SLOT 事件,触摸点对应的 SLOT
ABS_MT_TRACKING_ID 45	 //每个 SLOT 必须关联一个 ABS_MT_TRACKING_ID
ABS_MT_POSITION_X x[0]	 //上报触摸点 0 的 X 轴坐标,使用函数 input_report_abs 来完成。
ABS_MT_POSITION_Y y[0]	 //上报触摸点 0 的 Y 轴坐标,使用函数 input_report_abs 来完成。
    
ABS_MT_SLOT 1			//和第 1~4 行类似,只是换成了上报触摸点 0 的(X,Y)坐标信息
ABS_MT_TRACKING_ID 46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_REPORT				//当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件,使用 								input_sync函数来完成。

每次上报一个触摸点坐标之前要先使用input_mt_slot函数上报当前触摸点SLOT,触摸点的SLOT其实就是触摸点ID,需要由触摸 IC 提供。

通过修改 SLOT 关联的 ABS_MT_TRACKING_ID 来完成对触摸点的添加、替换或删除。

具体用到的函数就是 input_mt_report_slot_state,如果是添加一个新的触摸点,那么此函数的第三个参数active 要设置为 true, linux 内核会自动分配一个 ABS_MT_TRACKING_ID 值,不需要用户去指定具体的 ABS_MT_TRACKING_ID 值。

当一个触摸点移除以后,同样需要通过 SLOT 关联的 ABS_MT_TRACKING_ID 来处理,

c 复制代码
ABS_MT_TRACKING_ID -1	//当一个触摸点(SLOT)移除以后,需要通过 ABS_MT_TRACKING_ID 事件发送一个-1 给内核。方法很简单,同样使用 input_mt_report_slot_state 函数来完成,只需要将此函数的第三个参数 active 设置为 false 即可,不需要用户手动去设置-1。
SYN_REPORT		//当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件。

当要编写 Type B 类型的多点触摸驱动的时候就需要按照示例代码

Linux 内核里面有大量的 Type B 类型的多点触摸驱动程序,我们可以参考这些现成的驱动程序来编写自己的驱动代码。

这里就以 ili210x 这个触摸驱动 IC 为例,看看是 Type B 类型是 如 何 上 报 触 摸 点 坐 标 信 息 的 。

c 复制代码
static void ili210x_report_events(struct input_dev *input,
const struct touchdata *touchdata)
{
    int i;
    bool touch;
    unsigned int x, y;
    const struct finger *finger;

    for (i = 0; i < MAX_TOUCHES; i++) {		//使用 for 循环实现上报所有的触摸点坐标
        input_mt_slot(input, i);		    //调用 input_mt_slot 函数上 报 ABS_MT_SLOT 事 件 。

        finger = &touchdata->finger[i];

        touch = touchdata->status & (1 << i);
        input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);            
        //input_mt_report_slot_state 函 数 上 报ABS_MT_TRACKING_ID 事件也就是给 SLOT 关联一个 ABS_MT_TRACKING_ID
        if (touch) {
            x = finger->x_low | (finger->x_high << 8);
            y = finger->y_low | (finger->y_high << 8);
		   //使用 input_report_abs 函数上报触摸点对应的(X,Y)坐标值
            input_report_abs(input, ABS_MT_POSITION_X, x);	
            input_report_abs(input, ABS_MT_POSITION_Y, y);
        }
 }

    
     input_mt_report_pointer_emulation(input, false);
    //使用 input_sync 函数上报 SYN_REPORT 事件
     input_sync(input);
 }
MT 其他事件的使用

Linux 所支持所有 ABS_MT 事件 ,大家可以根据实际需求将 这 些 事 件 组 成 各 种 事 件 组 合 。

最 简 单 的 组 合 就 是 ABS_MT_POSITION_X 和ABS_MT_POSITION_Y,可以通过在这两个事件上报触摸点,如果设备支持的话,还可以使用ABS_MT_TOUCH_MAJOR 和 ABS_MT_WIDTH_MAJOR 这两个消息上报触摸面积信息,关于其他 ABS_MT 事件的具体含义大家可以查看 Linux 内核中的 multi-touch-protocol.txt 文档.

关于 Linux 系统下的多点触摸(MT)协议就讲解到这里,简单总结一下, MT 协议隶属于 linux的 input 子系统,驱动通过大量的 ABS_MT 事件向 linux 内核上报多点触摸坐标数据。根据触摸 IC 的不同,分为 Type A 和 Type B 两种类型,不同的类型其上报时序不同,目前使用最多的是 Type B 类型。接下来我们就根据前面学习过的 MT 协议来编写一个多点电容触摸驱动程序,本章节所使用的触摸屏是正点原子的 ATK7084(7 寸 800480)和 ATK7016(7 寸 1024600)这两款触摸屏,这两款触摸屏都使用 FT5426 这款触摸 IC,因此驱动程序是完全通用的。

多点触摸所使用到的 API 函数

简单介绍一下前面提到的一些函数,linux 下的多点触摸协议其实就是通过不同的事件来上报触摸点坐标信息,这些事件都是通过 Linux 内核提供的对应 API 函数实现的

1、 input_mt_init_slots 函数

2、 input_mt_slot 函数

3、 input_mt_report_slot_state 函数

4、 input_report_abs 函数

5、 input_mt_report_pointer_emulation 函数

多点电容触摸驱动框架

我们在编写驱动的时候需要注意一下几点

①、多点电容触摸芯片的接口,一般都为 I2C 接口,因此驱动主框架肯定是 I2C。

②、 linux 里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架。

③、多点电容触摸属于 input 子系统,因此还要用到 input 子系统框架。

④、在中断处理程序中按照 linux 的 MT 协议上报坐标信息。

c 复制代码
static int xxx_open (struct inode *inode, struct file *file){
    //初始化设备
}

static ssize_t ap3216c_read(struct file *file, char __user *buf, size_t size, loff_t *ppos){
    copy_to_user
}
static int ap3216c_close(struct inode *inode, struct file *file){
    
    
}
const struct file_operations ap3216c_opts={
    .owner = THIS_MODULE,
    .open  = xxx_open,
    .release = xxx_close,
    .read = xxx_read,
};

//中断服务函数
XXX_handler(){
    //上报信息给linux内核。
}



static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id){
    
     //搭建字符设备驱动框架
    //...
    cdev_init(&xxx.cdev,&xxx_opts);
    //申请中断绑定中断服务函数
    
}
static int ap3216c_remove(struct i2c_client *client){
    cdev_del(&ap3216c.cdev);
    unregister_chrdev_region(ap3216c.devid,AP3216C_CNT);
    device_destroy(ap3216c.class,ap3216c.devid);
    class_destroy(ap3216c.class);
    return 0;
}

/* 设备树匹配表 */
static const struct i2c_device_id xxx_ts_id[] = {
{ "xxx", 0, },
{ /* sentinel */ }
};

/* 设备树匹配表 */
static const struct of_device_id xxx_of_match[] = {
 .compatible = "xxx", },
{ /* sentinel */ }
};

/* i2c 驱动结构体 */
static struct i2c_driver ft5x06_ts_driver = {
    .driver = {
    .owner = THIS_MODULE,
        .name = "edt_ft5x06",
        .of_match_table = of_match_ptr(xxx_of_match),
    },
    .id_table = xxx_ts_id,
    .probe = xxx_ts_probe,
    .remove = xxx_ts_remove,
};

/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init xxx_init(void)
{
    int ret = 0;
    ret = i2c_add_driver(&xxx_ts_driver);
    return ret;
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit xxx_exit(void)
{
	i2c_del_driver(&ft5x06_ts_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

实战:

根据电路图修改设备树

对应线路图

INT ------ GPIO1_IO09 中断引脚作为普通GPIO使用

RST ------ SNVS_TAMPER9 SNVS的引脚都需要添加到iomuxsnvs下面

I2C_SDA ------ UART5_RXD 需要复用为i2c

I2C_SCL ------ UART5_RXD

设备树

  1. 添加pinctrl子节点
c 复制代码
&i2c2 {
	clock_frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c2>;
	status = "okay";


	//添加触摸屏
	ft5426: ft5426@38 {
		compatible = "edt,edt-ft5426";
		pinctrl-names = "default";
		pinctrl-0 = < &pinctrl_tsc
					  &pinctrl_tsc_reset>;
		reg = <0x1a>;
		interrupt-parent = <&gpio1>; //该中断属于GPIO1组
		interrupts = <9 0>;			//具体时GPIO1 IO09, IRQ_TYPE中的NONE
		reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
		interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
		status = "okay";

	};
};

&iomuxc{
		//电容触控
		pinctrl_tsc: tscgrp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO09__GPIO1_IO09	0xF080
			>;
		};
    
    	pinctrl_i2c2: i2c2grp {   //已经有了
			fsl,pins = <
				MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
				MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
			>;
		};
	}
	
&iomuxc_snvs {
    	
 	   	pinctrl_tsc_reset: tsc_resetgrp {
			fsl,pins = <
				MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09		0x10b0
			>;
		};	
}

SNVS相关的引脚需要在具体信号中的pinfunc-snvs中寻找。

  1. 查看是否有相同的pinctrl使用情况,检查引脚复用,比较多需要慢慢找

​ 包括GPIO1_IO09 GPIO5_IO09 gpio1 9 gpio5 9

  1. 编译,复制,启动

    c 复制代码
    cd /proc/device-tree/soc/aips-bus@02100000/i2c@021a4000/ft5426@38

    查看相关是否生效。

重点

添加设备结构体中input_dev

c 复制代码
struct input_dev *input;

使用input初始化外设之后

c 复制代码

上报输入事件的基本逻辑

  1. 读取数据

  2. input_mt_slot

  3. input_mt_report_slot_state

  4. input_report_abs(x)

    input_report_abs(y)

  5. input_mt_report_pointer_emulation -> 用于模拟出传统的单点触摸事件,考虑到兼容性

  6. input_sync

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/input/touchscreen.h>>
#include <linux/input/mt.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


#define ft5426_CNT      1
#define ft5426_NAME     "ft5426"

#define MAX_SUPPORT_POINTS		5			/* 5点触摸 	*/
#define TOUCH_EVENT_DOWN		0x00		/* 按下 	*/
#define TOUCH_EVENT_UP			0x01		/* 抬起 	*/
#define TOUCH_EVENT_ON			0x02		/* 接触 	*/
#define TOUCH_EVENT_RESERVED	0x03		/* 保留 	*/

/* FT5X06寄存器相关宏定义 */
#define FT5X06_TD_STATUS_REG	0X02		/*	状态寄存器地址 		*/
#define FT5x06_DEVICE_MODE_REG	0X00 		/* 模式寄存器 			*/
#define FT5426_IDG_MODE_REG		0XA4		/* 中断模式				*/
#define FT5X06_READLEN			29			/* 要读取的寄存器个数 	*/

struct ft5426_dev
{
    
    struct device_node *nd;
    int irq_pin,reset_pin;
    int irq_num;
    struct i2c_client *client;
    void *private_data;
    struct input_dev *input;
};
struct ft5426_dev ft5426;


//读取ft5426的N个寄存器的值
//从哪个设备的哪个reg读取多长的数据到哪个地址
static int ft5426_read_regs(struct ft5426_dev *dev,u8 reg,void *val,int len){
    
    struct i2c_msg ft5426_msg[2];
    struct i2c_client *client =  dev->client;
    //client中保存着大量的句柄,包括从机地址,i2c适配器...


    //1.想哪个从机发送什么数据
    ft5426_msg[0].addr =client->addr  ;  //丛机地址
    ft5426_msg[0].flags =0 ;             //表示发送
    ft5426_msg[0].buf =&reg ;          //发送的数据
    ft5426_msg[0].len =sizeof(reg) ;          //发送的长度



    //2.读取数据
    ft5426_msg[1].addr =client->addr  ;  //丛机地址
    ft5426_msg[1].flags =I2C_M_RD ;             //表示接受
    ft5426_msg[1].buf = val;          //接收的数据
    ft5426_msg[1].len = len;          //发送的长度
    
    return  i2c_transfer(client->adapter,ft5426_msg,2);
}


//想ft5426写入N个寄存器的值
static int ft5426_write_regs(struct ft5426_dev *dev,u8 reg,u8 *buf,u8 len){
    u8 b[256];
    struct i2c_msg msg;
    struct i2c_client *client =  dev->client;
    //client中保存着大量的句柄,包括从机地址,i2c适配器...
    b[0] = reg;

    memcpy(&b[1],buf,len);
    //1.想哪个从机发送什么数据
    msg.addr =client->addr  ;  //丛机地址
    msg.flags =0 ;             //表示发送
    msg.buf = b ;          //发送的数据
    msg.len =len+1 ;          //发送的长度

    
    return  i2c_transfer(client->adapter,&msg,1);
    
}


//读取单个寄存器
static unsigned char ft5426_read_reg(struct ft5426_dev *dev,u8 reg){
    u8 data = 0;
    ft5426_read_regs(dev,reg,&data,1);
    return data;
}
//写入单个寄存器
static void ft5426_write_reg(struct ft5426_dev *dev,u8 reg,u8 data){
    u8 buf = 0;
    buf = data;
    ft5426_write_regs(dev,reg,&buf,1);
}

//中断处理函数
static irqreturn_t ft5426_handler(int irq,void *dev_id){
    u8 rdbuf[29];
    int offset, tplen;
    offset = 1; 	/* 偏移1,也就是0X02+1=0x03,从0X03开始是触摸值 */
	tplen = 6;		/* 一个触摸点有6个寄存器来保存触摸值 */
    int i, type, x, y, id;

    bool down;
    printk("ft5426_handler\r\n");
    struct ft5426_dev *dev = dev_id;

    //从FT5426读取触摸点信息
    ft5426_read_regs(dev,FT5X06_TD_STATUS_REG,rdbuf,FT5X06_READLEN); //连续读取29个寄存器的内容就是触摸点的信息
    //处理数据
    for( i = 0; i < MAX_SUPPORT_POINTS; i++)
    {
        //计算每个触摸点的坐标
        u8 *buf = &rdbuf[i*tplen + offset];
        
        type = buf[0]>>6;
        if (type == TOUCH_EVENT_RESERVED)
            continue;
        /* 我们所使用的触摸屏和FT5X06是反过来的 */
		x = ((buf[2] << 8) | buf[3]) & 0x0fff;
		y = ((buf[0] << 8) | buf[1]) & 0x0fff;

        id = (buf[2] >> 4) & 0x0f;
		down = type != TOUCH_EVENT_UP;
        
        //向内核上报数据
        input_mt_slot(dev->input,id);
        input_mt_report_slot_state(dev->input,MT_TOOL_FINGER,down);

        if (!down)
        {
            continue;
        }
        input_report_abs(dev->input,ABS_MT_POSITION_X,x);
        input_report_abs(dev->input,ABS_MT_POSITION_Y,y);

    }
    input_mt_report_pointer_emulation(dev->input,true);
    input_sync(dev->input);



    return IRQ_HANDLED;
}


static int ft5426_reset(struct i2c_client *client,struct ft5426_dev *dev){
    int ret = 0;
    if (gpio_is_valid(dev->reset_pin))
    {
        ret =devm_gpio_request_one(&client->dev,dev->reset_pin,GPIOF_OUT_INIT_LOW,"Ft5426 Rset");
  
    if (ret)
    {
        return ret;
    }
    msleep(5);
    gpio_set_value(dev->reset_pin,1); 
    msleep(300);
    }
      return 0;
}

static int ft5426_irq(struct i2c_client *client,struct ft5426_dev *dev){
    int ret  = 0 ;
    printk("Init irq!\r\n");
    if (gpio_is_valid(dev->irq_pin))
    {
        ret  = devm_gpio_request_one(&client->dev,dev->irq_pin,GPIOF_IN,"ft5426 irq");
        if (ret)
        {
            dev_err(&client->dev,"Failed to request GPIO %d ,err %d",dev->irq_pin,ret);
            return ret;
        }
    }
    //申请中断,Client->irq就是IO中断
    ret = devm_request_threaded_irq(&client->dev,client->irq,NULL,ft5426_handler,IRQF_TRIGGER_FALLING|IRQF_ONESHOT,client->name,&ft5426);
     printk("irq num = %d!\r\n",client->irq);
    if (ret)
    {
        dev_err(&client->dev,"Unable to request touchscreen IRQ.\n");
        return ret;
    }
    return 0;
}




static int ft5426_open (struct inode *inode, struct file *file){
    file->private_data = &ft5426;
    printk("ft5426_open\r\n");   
    
    

    return 0; 
}  

static int ft5426_close(struct inode *inode, struct file *file){
    // struct ft5426_dev *dev = file->private_data;
    printk("ft5426_close\r\n");
    return 0;
}

static ssize_t ft5426_read(struct file *file, char __user *buf, size_t size, loff_t *ppos){
    unsigned char err;
    short data[3];
    // struct ft5426_dev *dev = file->private_data;
    
    printk("ft5426_read\r\n");
    return 0;
}
const struct file_operations ft5426_opts={
    .owner = THIS_MODULE,
    .open  = ft5426_open,
    .release = ft5426_close,
    .read = ft5426_read,
};

static int ft5426_probe(struct i2c_client *client, const struct i2c_device_id *id){

    printk("ft5426_probe\r\n");
    //搭建字符设备驱动框架

    int value;
    int ret = 0;
    //注册设备号
    

    ft5426.client =  client ;  //将i2c_client传递给设备结构体中的指针。里面保存着i2c的大量句柄
    //ft5426.irq_pin = of_get_named_gpio(nd,"interrupt-gpios",0); //通过这种方式不好找到gpio的节点了
    //使用下面这种方式
    ft5426.irq_pin = of_get_named_gpio(client->dev.of_node,"interrupt-gpios",0);
    ft5426.reset_pin = of_get_named_gpio(client->dev.of_node,"reset-gpios",0);

    //获取到设备树中:reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;  interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
    ft5426_reset(client,&ft5426);
    ft5426_irq(client,&ft5426);


    //初始化FT5426
    ft5426_write_reg(&ft5426,FT5x06_DEVICE_MODE_REG,0);	/* 进入正常模式 	*/
    ft5426_write_reg(&ft5426, FT5426_IDG_MODE_REG, 1);	/* FT5426中断模式	*/
    
    //读取
    value = ft5426_read_reg(&ft5426,FT5426_IDG_MODE_REG);
     printk("FT5426_IDG_MODE_REG  =  %x\r\n",value);

    //input框架
    ft5426.input = devm_input_allocate_device(&client->dev); //属于哪个设备
    //判断是否申请成功
    
    //初始化input_dev
    ft5426.input->name = client->name;
    ft5426.input->id.bustype = BUS_I2C;
    ft5426.input->dev.parent = &client->dev;

    //设置输入捕获事件
    __set_bit(EV_SYN,ft5426.input->evbit);
    __set_bit(EV_KEY,ft5426.input->evbit);
    __set_bit(EV_ABS,ft5426.input->evbit);
    __set_bit(BTN_TOUCH,ft5426.input->keybit);


    //单点触摸
    input_set_abs_params(ft5426.input,ABS_X,0,1024,0,0);
    input_set_abs_params(ft5426.input,ABS_Y,0,600,0,0);

    //多点触摸
    input_mt_init_slots(ft5426.input,MAX_SUPPORT_POINTS,0);
    input_set_abs_params(ft5426.input,ABS_MT_POSITION_X,0,1024,0,0);
    input_set_abs_params(ft5426.input,ABS_MT_POSITION_Y,0,600,0,0);

    ret = input_register_device(ft5426.input);
   
    return ret;

}

static int ft5426_remove(struct i2c_client *client){

    input_unregister_device(ft5426.input);
    return 0;

}
//设备树匹配
const struct of_device_id ft5426_of_match[]= {
    { .compatible = "edt,edt-ft5426"},
    {},
};

//传统匹配表
const struct i2c_device_id ft5426_id[] = {
    {"edt,edt-ft5426",0},
    {},
};
struct i2c_driver ft5426_driver= {
    .probe = ft5426_probe,
    .remove =  ft5426_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "ft5426",
        .of_match_table = ft5426_of_match,
    },
    .id_table = ft5426_id,

};
static int __init ft5426_init(void){
    int ret = 0;
    i2c_add_driver(&ft5426_driver);
    return ret;

}
static void __exit ft5426_exit(void){
    i2c_del_driver(&ft5426_driver);
}


module_init(ft5426_init);
module_exit(ft5426_exit);
//

MODULE_LICENSE("GPL");

优化代码:

1.出入口函数可以使用一个module_xxx_driver(xxx_driver)

2.重置gt9147芯片

gt9147_ts_reset

根据设置地址的时许来编写gt9147_ts_reset函数

c 复制代码
	gpio_set_value(dev->reset_pin, 0); /* 复位GT9147 */
    msleep(10);
    gpio_set_value(dev->reset_pin, 1); /* 停止复位GT9147 */
    msleep(10);
    gpio_set_value(dev->irq_pin, 0);    /* 拉低INT引脚 */
    msleep(50);
    gpio_direction_input(dev->irq_pin); /* INT引脚设置为输入 */

可以看到是完全按照下面的GT9147 的上电时序 编写程序

  1. 由于申请资源的函数只用了devm_的前缀函数

​ devm_input_allocate_device

​ devm_gpio_request_one

​ devm_request_threaded_irq

​ 因此不需要注销设备时再编写释放资源的函数

只需要释放未使用devm_开头去申请的资源

  1. 上报时序
c 复制代码
export TSLIB_TSDEVICE=/dev/input/event1
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0

如何将自己写的驱动添加到内核中

  1. 添加到对应目录中
c 复制代码
alientek@ubuntu16:~/Desktop/linux_kernel/drivers/input/touchscreen$ cp ~/Desktop/linux_driver/24.multitouch/gt9147.c ./
  1. 在改目录下的Makefile中编译该文件

添加最后一行

c 复制代码
obj-y += gt9147.o
  1. 重新编译内核(顶层Makefile中)
c 复制代码
make -j12
sudo cp arch/arm/boot/zImage ~/Desktop/tftp/ -f

重启开发板,查看内核启动信息

c 复制代码
input: gt9147 as /devices/platform/soc/2100000.aips-bus/21a4000.i2c/i2c-1/1-0014/input/input1

同时

c 复制代码
ls /dev/input/ -l
total 0
crw-rw----    1 root     0          13,  64 Jan  1 00:00 event0
crw-rw----    1 root     0          13,  65 Jan  1 00:00 event1
crw-rw----    1 root     0          13,  66 Jan  1 00:00 event2
crw-rw----    1 root     0          13,  63 Jan  1 00:00 mice
crw-rw----    1 root     0          13,  32 Jan  1 00:00 mouse0

可以看到默认有3个event事件被启动了,但此时屏幕输入不一定是event2

c 复制代码
# hexdump /dev/input/event2
^C
# hexdump /dev/input/event1
0000000 051b 0000 a41f 0007 0003 0039 000e 0000
0000010 051b 0000 a41f 0007 0003 0035 02c5 0000
0000020 051b 0000 a41f 0007 0003 0036 017a 0000
0000030 051b 0000 a41f 0007 0001 014a 0001 0000
0000040 051b 0000 a41f 0007 0003 0000 02c5 0000
0000050 051b 0000 a41f 0007 0003 0001 017a 0000
0000060 051b 0000 a41f 0007 0000 0000 0000 0000
0000070 051b 0000 06f2 0009 0003 0039 ffff ffff
0000080 051b 0000 06f2 0009 0001 014a 0000 0000
0000090 051b 0000 06f2 0009 0000 0000 0000 0000
^C

此时屏幕输入作为了event1

重新修改/etc/profile中event2-》event1

c 复制代码
#!bin/sh
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH

export TERM=vt100
export TERMINFO=/usr/share/terminfo

export TSLIB_TSDEVICE=/dev/input/event1
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0

重启开发板,就可以直接使用如下命令测试触摸屏输入,不需要加载模块驱动了

c 复制代码
ts_test_mt

使用linux内核自带的屏幕驱动

linux内核有自带的屏幕驱动,可以在make menuconfig进行配置,使能后即可使用

需要修改设备树!

本章主要学习了input子系统下面的触摸点上报协议

相关推荐
6***A66343 分钟前
SQL 插入数据详解
服务器·数据库·sql
有味道的男人44 分钟前
Jumia API
服务器·数据库·microsoft
南风~古草1 小时前
20252803《Linux内核原理与分析》第12周作业-Linux系统监控实战(系统安全实验)
linux·运维·系统安全
TsingtaoAI1 小时前
企业实训:AI运维工程师实训——某外资商业银行
运维·人工智能
未来会更好yes1 小时前
Alibaba Cloud Linux3.21.04 ModSecurity 3 + Nginx + OWASP CRS 完整安装流程
linux·waf·modsecurity
XH-hui1 小时前
【打靶日记】HackMyVm 之 Twisted
linux·网络安全·hackmyvm·hmv
wadesir1 小时前
Nginx安全加固指南(CentOS系统下的Web服务器安全配置实战)
服务器·nginx·安全
jinxinyuuuus1 小时前
局域网文件传输:P2P架构中的带宽测量与高效率文件分块传输
服务器·架构·p2p
Evan芙1 小时前
Rocky Linux 9 双网卡 bond0 绑定
linux·服务器·网络
保持低旋律节奏1 小时前
linux——vim编辑器
linux·编辑器·vim