10.2多点触摸屏驱动

发布时间:2023年12月24日

多点电容触摸(MT)协议

多点电容触摸(MT)协议是 input 子系统的一部分, MT 协议被分为两种类型,分别是 Type A 和 Type B :

  • Type A :适用于触摸点不能被区分或者追踪的设备(即硬件上没有触摸 ID)
  • Type B :适用于有硬件追踪并能区分触摸点的触摸设备
    触摸点的信息通过一系列的 ABS_MT_* 事件上报给 linux 内核,常用的 ABS_MT 事件有:
  • ABS_MT_SLOT :表示后面上报的是相应 slot 的触摸信息,用于分割 Type B 类型触摸点
  • ABS_MT_TRACKING_ID :上报触摸 ID 事件, slot 从松开到按下时系统自动分配 TRACKING_ID
  • ABS_MT_POSITION_X :上报绝对坐标X
  • ABS_MT_POSITION_Y :上报绝对坐标Y
  • SYN_MT_REPORT:表示前面已经上报完一个触摸点数据,用于分割 Type A 类型触摸点
    对于 Type B 类型采用 ABS_MT_SLOT 分割触摸点,并用 ABS_MT_TRACKING_ID 上报触摸点 ID,对于 Type A 类型则采用 SYN_MT_REPORT 事件来分割触摸点。

多点触摸所常用 API

/* 初始化 slots
 * dev :MT 设备对应的 input_dev
 * num_slots :设备要使用的 SLOT 数量,也就是触摸点的数量
 * flags :其他一些 flags 信息,如 INPUT_MT_POINTER 表示触摸板设备,INPUT_MT_DIRECT 表示触摸屏设备
 **/
int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots, unsigned int flags)
/* 通过 slot 上报 ABS_MT_SLOT 事件
 * dev :MT 设备对应的 input_dev
 * slot :指定产生 ABS_MT_SLOT 事件 slot
 **/
void input_mt_slot(struct input_dev *dev, int slot)
/* 上报触摸点的状态,用于产生 ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE 事件
 * dev :MT 设备对应的 input_dev
 * tool_type :触摸类型, MT_TOOL_FINGER (手指)、 MT_TOOL_PEN (笔)、 MT_TOOL_PALM (手掌)
 * active :触摸状态, true 表示按下,如果是第一次按下系统会为其分配一个 ID ,false 表示抬起
 **/
bool input_mt_report_slot_state(struct input_dev *dev, unsigned int tool_type, bool active)
/* 上报绝对坐标事件
 * dev :MT 设备对应的 input_dev
 * code :事件类型,ABS_MT_POSITION_X 或 ABS_MT_POSITION_Y 分别表示 X 轴和 Y 轴,
 * value : X 轴或 Y 轴坐标值
 **/
void input_report_abs(struct input_dev *dev,unsigned int code, int value)
/* 单点触摸模拟
 * dev :MT 设备对应的 input_dev
 * use_count :为 true 则要上报触摸点数
 **/
void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
/* 用于产生 SYN_MT_REPORT 事件
 * dev :MT 设备对应的 input_dev
 **/
void input_mt_sync(struct input_dev *dev)
/* 上报触摸坐标
 * input :MT 设备对应的 input_dev
 * prop :prop触摸屏特性
 * x,y :触摸坐标
 * multitouch :是否是多点触摸,true表示多点触摸
 **/
void touchscreen_report_pos(struct input_dev *input, const struct touchscreen_properties *prop, unsigned int x, unsigned int y, bool multitouch)
/* 根据设备树配置初始化触摸屏特性,根据input对象absbit的设置完成对absinfo的配置
 * input :MT 设备对应的 input_dev
 * multitouch :是否是多点触摸,true表示多点触摸
 * prop :返回触摸屏特性
 **/
void touchscreen_parse_properties(struct input_dev *input, bool multitouch, struct touchscreen_properties *prop)

上报触摸事件

Type A 上报顺序:

1. ABS_MT_POSITION_X        通过 ABS_MT_POSITION_X 上报第一个触摸点的 X 坐标数据,input_report_abs
2. ABS_MT_POSITION_Y        通过 ABS_MT_POSITION_Y 上报第一个触摸点的 Y 坐标数据,input_report_abs
3. SYN_MT_REPORT            上报 SYN_MT_REPORT 事件,input_mt_sync
4. ABS_MT_POSITION_X        通过 ABS_MT_POSITION_X 上报第二个触摸点的 X 坐标数据
5. ABS_MT_POSITION_Y        通过 ABS_MT_POSITION_Y 上报第二个触摸点的 Y 坐标数据
6. SYN_MT_REPORT            上报 SYN_MT_REPORT 事件
......                      继续上报剩余的坐标
7. SYN_REPORT               上报 SYN_REPORT 事件,input_sync

Type B 上报顺序:

1. ABS_MT_SLOT             上报第一个触摸点的 ABS_MT_SLOT 事件,input_mt_slot
2.  ABS_MT_TRACKING_ID     通过 ABS_MT_TRACKING_ID 上报第一个触摸点的 ID,input_mt_report_slot_state
3. ABS_MT_POSITION_X       通过 ABS_MT_POSITION_X 上报第一个触摸点的 X 坐标数据,input_report_abs
4. ABS_MT_POSITION_Y       通过 ABS_MT_POSITION_Y 上报第一个触摸点的 Y 坐标数据,input_report_abs
5. ABS_MT_SLOT             上报第二个触摸点的 ABS_MT_SLOT 事件
6. ABS_MT_TRACKING_ID	   通过 ABS_MT_TRACKING_ID 上报第二个触摸点的 ID
7. ABS_MT_POSITION_X       通过 ABS_MT_POSITION_X 上报第二个触摸点的 X 坐标数据
8. ABS_MT_POSITION_Y       通过 ABS_MT_POSITION_Y 上报第二个触摸点的 Y 坐标数据
......
9. SYN_REPORT             上报 SYN_REPORT 事件,input_sync

移植 tslib

tslib 是一个开源的用于操作触摸屏的库,它可以简化触摸屏编程,同时也集成了一些触摸屏测试工具,其移植过程如下:

基于 buildroot 移植

buildroot中集成了tslib,在编译时选择其即可

make menuconfig
-> Target packages 
	-> Libraries 
		-> Hardware handling 
			-> [*] tslib

基于源码移植

  1. 解压 tslib ,并进入源码目录
tar -vxf tslib-1.21.tar.xz
cd tslib-1.21/
  1. 配置 tslib
#--host=arm-none-linux-gnueabihf编译器前缀
#--prefix=/安装到目标机的目录
./configure --host=arm-none-linux-gnueabihf --prefix=/
  1. 编译 tslib
make
  1. 安装 tslib
#会在当前目录下创建一个tmp目录,并将此目录作为目标机的跟目录进行安装
make install DESTDIR=$PWD/tmp
  1. 将安装后得到的头文件和库文件拷贝到工具链中(如果不基于 tslib 开发应用程序可以忽略此步骤)
#/usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/usr/include是工具链头文件路径
cp ./tmp/include/* /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/usr/include
#/usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/usr/lib是工具链库文件路径
cp -d ./tmp/lib/*so* /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/usr/lib
  1. 将安装后得到的文件拷贝到目标板跟文件系统中
#../../../../rootfs/是nfs根文件系统目录
cp -r tmp/* ../../../../rootfs/

基于 tslib 开发应用程序

  1. 使用 ts_setup 打开触摸屏设备,并初始化 tslib
  2. 使用 ioctl(int ts_fd(struct tsdev *ts), EVIOCGABS(ABS_MT_SLOT), struct input_absinfo *slot) 获取触摸屏的 slot 信息,并计算触摸点数
  3. 使用 ts_read_mt 读取触摸屏数据
  4. 最后通过 ts_close 关闭触摸屏设备

tslib 常用函数

//ts_sample结构体
struct ts_sample {
	//X 坐标
	int x;
	//Y 坐标
	int y;
	//按压力大小
	unsigned int pressure;
	//时间
	struct timeval tv;
};
//ts_sample_mt结构体
struct ts_sample_mt {
	//X 坐标
	int x; 
	//Y 坐标
	int y; 
	//按压力大小
	unsigned int pressure;
	//触摸点 slot
	int slot;
	//ID
	int tracking_id;
	int tool_type;
	int tool_x;
	int tool_y;
	unsigned int touch_major;
	unsigned int width_major;
	unsigned int touch_minor;
	unsigned int width_minor;
	int orientation;
	int distance;
	int blob_id;
	//时间
	struct timeval tv;
	//BTN_TOUCH 的状态
	short pen_down;
	//此次样本是否有效标志 触摸点数据是否发生更新
	short valid;
};
/* 打开触摸屏设备,并初始化 tslib
 * dev_name 触摸设备名称,为NULL则会在系统中搜索
 * nonblock 是否阻塞
 * 成功返回tslib设备句柄
 **/
struct tsdev *ts_setup(const char *dev_name, int nonblock)
/* 读取多点触摸屏数据
 * ts tslib设备句柄
 * samp 指向一个struct ts_sample_mt *对象,用于返回触摸坐标
 * nr 表示采样次数,采样值依次存入samp[0][0] samp[0][1] ... samp[0][max_slots] samp[1][0] samp[1][1] ...
 *    samp[1][max_slots] ... 中
 * 成功返回实际采样次数
 **/
int ts_read_mt(struct tsdev *ts, struct ts_sample_mt **samp, int max_slots, int nr)
/* 读取单点触摸屏数据
 * ts tslib设备句柄
 * samp:指向一个 struct ts_sample 对象,用于返回触摸坐标
 *  nr :表示采样次数。
 * 成功返回实际采样次数
 **/
int ts_read(struct tsdev *ts, struct ts_sample *samp, int nr)
/* 关闭触摸屏设备,并释放tslib资源
 * ts tslib设备句柄
 * 成功返回0
 **/
int ts_close(struct tsdev *ts)

编写GT1151驱动

硬件原理图

在这里插入图片描述
在这里插入图片描述

GT1151寄存器简介

配置信息:
在这里插入图片描述
其中配置信息的byte1~4表示触摸屏尺寸:
在这里插入图片描述
产品ID:
在这里插入图片描述触摸坐标:
虽然一共有10组触摸点坐标寄存器组,但是它最多支持同时跟踪5个触摸点
在这里插入图片描述

驱动代码编写

设备树编写

在顶层设备树中引用i2c2节点,并加入如下内容:

&i2c2 {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&i2c2_pins_a>;
	pinctrl-1 = <&i2c2_pins_sleep_a>;
	status = "okay";

	gt91xx@14 {
		compatible = "alientek,gt91xx";
		reg = <0x14>;
		x-size = <800>;
		y-size = <480>;
		reset-gpios = <&gpioh 15 GPIO_ACTIVE_LOW>;
		irq-gpios = <&gpioi 1 GPIO_ACTIVE_LOW>;
		interrupt-parent = <&gpioi>;
		interrupts = <1 IRQ_TYPE_EDGE_FALLING>;
	};
};

在 stm32mp15-pinctrl.dtsi 的 &pinctrl 节点中修改 i2c2 的引脚配置为如下内容

	i2c2_pins_a: i2c2-0 {
		pins {
			pinmux = <STM32_PINMUX('H', 4, AF4)>, /* I2C2_SCL */
				 <STM32_PINMUX('H', 5, AF4)>; /* I2C2_SDA */
			bias-disable;
			drive-open-drain;
			slew-rate = <0>;
		};
	};

	i2c2_pins_sleep_a: i2c2-1 {
		pins {
			pinmux = <STM32_PINMUX('H', 4, ANALOG)>, /* I2C2_SCL */
				 <STM32_PINMUX('H', 5, ANALOG)>; /* I2C2_SDA */
		};
	};

注意:设备树的内容需要根据硬件实际连接进行修改
用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树,然后用新的.dtb文件启动系统

驱动代码编写

驱动代码主要包括以下几个部分:

  1. 初始化GT1151硬件
  2. 初始化并注册触摸屏输入设备
  3. 注册触摸屏中断
  4. 在触摸屏中断中上报触摸点信息
    完整的驱动代码如下所示:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>

#define GT1151_CONFIG_REG		0x8050		//配置寄存器起始
#define GT1151_PRODUCT_ID_REG	0x8140		//产品ID寄存器
#define GT1151Q_GSTID_REG		0x814E		//GT1151Q当前检测到的触摸情况
#define GT1151Q_TP1_REG			0x814F		//第1个触摸点数据地址
#define GT1151Q_TP2_REG			0x8157		//第2个触摸点数据地址
#define GT1151Q_TP3_REG			0x815F		//第3个触摸点数据地址
#define GT1151Q_TP4_REG			0x8167		//第4个触摸点数据地址
#define GT1151Q_TP5_REG			0x816F		//第5个触摸点数据地址

//配置数据长度
#define GT1151_HW_CONFIG_LEN	239
//支持的触摸点数量
#define GTP_MAX_TOUCH			5

typedef struct gt1151 {
	struct input_dev *inputdev;						/* 输入设备句柄 */
	struct i2c_client *client;						/* I2C client句柄 */
	int rst_pin;									/* 复位引脚 */
	int intr_pin;									/* 中断引脚 */
	uint32_t x_range;								/* 触摸屏宽度 */
	uint32_t y_range;								/* 触摸屏高度 */
	uint8_t last_touch_state[GTP_MAX_TOUCH];		/* 缓存各个触摸点上一次的触摸状态 */
	uint8_t write_buffer[512];						/* I2C写数据缓存 */
	uint8_t gt1151_config[GT1151_HW_CONFIG_LEN];	/* gt1151配置数据缓存 */
}gt1151_t;

static const uint16_t tp_reg[GTP_MAX_TOUCH] = {
#if GTP_MAX_TOUCH >= 1
	GT1151Q_TP1_REG,
#endif
#if GTP_MAX_TOUCH >= 2
	GT1151Q_TP2_REG,
#endif
#if GTP_MAX_TOUCH >= 3
	GT1151Q_TP3_REG,
#endif
#if GTP_MAX_TOUCH >= 4
	GT1151Q_TP4_REG,
#endif
#if GTP_MAX_TOUCH >= 5
	GT1151Q_TP5_REG,
#endif
};

static int gt1151_read(gt1151_t *gt1151, uint16_t reg, uint8_t *data, uint16_t lenght)
{
	uint8_t addr[2];
	struct i2c_msg msg[2];

	addr[0] = (uint8_t)((reg >> 8) & 0xFF);
	addr[1] = (uint8_t)(reg & 0xFF);

	//从机地址
	msg[0].addr = gt1151->client->addr;
	//表示写
	msg[0].flags = 0;
	//buf是一个指针,指向了要发送的数据
	msg[0].buf = addr;
	//msg[0].buf的数据长度
	msg[0].len = 2;

	msg[1].addr = gt1151->client->addr;
	//表示读
	msg[1].flags = I2C_M_RD;
	msg[1].buf = data;
	msg[1].len = lenght;
	if(i2c_transfer(gt1151->client->adapter, msg, 2)  == 2)
		return 0;
	else 
		return -EIO;
}

static int gt1151_write(gt1151_t *gt1151, uint16_t reg, uint8_t *data, uint16_t lenght)
{
	struct i2c_msg msg[1];
	
	if(lenght > sizeof(gt1151->write_buffer)-2)
		return -EIO;

	memset(gt1151->write_buffer, 0, sizeof(gt1151->write_buffer));
	//只能用一个msg发送,分多个msg时msg衔接的时候不会等待设备的ACK信号,可能会导致失败
	gt1151->write_buffer[0] = (uint8_t)((reg >> 8) & 0xFF);
	gt1151->write_buffer[1] = (uint8_t)(reg & 0xFF);
	memcpy(&gt1151->write_buffer[2], data, lenght);

	//从机地址
	msg[0].addr = gt1151->client->addr;
	//表示写
	msg[0].flags = 0;
	//buf是一个指针,指向了要发送的数据
	msg[0].buf = gt1151->write_buffer;
	//msg[0].buf的数据长度
	msg[0].len = 2 + lenght;
	if(i2c_transfer(gt1151->client->adapter, msg, 1)  == 1)
		return 0;
	else
		return -EIO;
}

static irqreturn_t gt1151_irq_thread(int irq, void *arg)
{
	uint8_t i;
	uint8_t clr_intr;
	uint8_t touch_number;
	uint8_t phy_id;
	uint16_t x_coordinate;
	uint16_t y_coordinate;
	int32_t result;
	uint8_t buffer[5];
	uint8_t now_touch_state[GTP_MAX_TOUCH];
	gt1151_t *gt1151 = (gt1151_t*)arg;

	//读取触摸状态寄存器
	result = gt1151_read(gt1151, GT1151Q_GSTID_REG, buffer, 1);
	if(result != 0)
	{
		printk("read reg failed\r\n");
		return IRQ_HANDLED;
	}

	//获取触摸点数
	touch_number = buffer[0] & 0x0F;

	//复位当前触摸状态
	memset(now_touch_state, 0, sizeof(now_touch_state));
	//依次处理触摸点
	for(i = 0; (i < touch_number) && (i < GTP_MAX_TOUCH); i++) 
	{
		//读取一个触摸点
		result = gt1151_read(gt1151, tp_reg[i], buffer, 5);
		if(result != 0)
		{
			printk("read reg failed\r\n");
			return IRQ_HANDLED;
		}

		//获取触摸点ID,这里通过物理ID来区分slot
		phy_id = buffer[0] & 0x0F;
		//ID不合法
		if(phy_id >= GTP_MAX_TOUCH)
			continue;

		//获取触摸坐标
		x_coordinate = buffer[1] | (buffer[2] << 8);
		y_coordinate = buffer[3] | (buffer[4] << 8);
		//记录当前触摸点的状态
		now_touch_state[phy_id] = 1;

		//通过第phy_id个slot上报ABS_MT_SLOT事件,表示即将上报第phy_id的触摸信息
		input_mt_slot(gt1151->inputdev, phy_id);
		//上报触摸状态
		input_mt_report_slot_state(gt1151->inputdev, MT_TOOL_FINGER, true);
		//上报触摸坐标
		input_report_abs(gt1151->inputdev, ABS_MT_POSITION_X, x_coordinate);
		input_report_abs(gt1151->inputdev, ABS_MT_POSITION_Y, y_coordinate);
	}

	//处理松开的点
	for(i = 0; i < GTP_MAX_TOUCH; i++)
	{
		//上一次处于按下状态而此次处于松开状态
		if((!now_touch_state[i]) && (gt1151->last_touch_state[i]))
		{
			//触摸ID
			phy_id = i;

			//上报触摸信息
			input_mt_slot(gt1151->inputdev, phy_id);
			input_mt_report_slot_state(gt1151->inputdev, MT_TOOL_FINGER, false);
		}
	}

	//记录此次触摸状态
	memcpy(gt1151->last_touch_state, now_touch_state, sizeof(gt1151->last_touch_state));

	//进行单点触摸模拟,并上报触摸点数
	input_mt_report_pointer_emulation(gt1151->inputdev, true);
	//上报完成,进行事件同步
	input_sync(gt1151->inputdev);

	//清除触摸屏中断标志
	clr_intr = 0x00;
	gt1151_write(gt1151, GT1151Q_GSTID_REG, &clr_intr, 1);

	//返回IRQ_HANDLED,表示中断被成功处理
	return IRQ_HANDLED;
}

static int gt1151_reset(gt1151_t *gt1151)
{
	uint8_t data;

	if(gt1151->client->addr != 0x14)
	{
		printk("address bad\r\n");
		return -EINVAL;
	}

	//进行软件复位
	data = 0x02;
	gt1151_write(gt1151, 0X8040, &data, 1);
	mdelay(100);
	data = 0x0;
	gt1151_write(gt1151, 0X8040, &data, 1);
	mdelay(100);

	return 0;
}

static int32_t gt1151_config(gt1151_t *gt1151)
{
	uint16_t checksum;
	int i;
	int result;
	uint8_t buffer[5];
	uint8_t *config = gt1151->gt1151_config;
	uint16_t cfg_len = GT1151_HW_CONFIG_LEN;

	//读取产品ID
	gt1151_read(gt1151, GT1151_PRODUCT_ID_REG, buffer, 4);
	buffer[4] = '\0';
	printk("gt1151 ID: %s\r\n", (char*)buffer);

	//加载配置数据
	result = gt1151_read(gt1151, GT1151_CONFIG_REG, config, cfg_len);
	if(result != 0)
	{
		printk("load config failed\r\n");
		return result;
	}

	//配置触摸屏尺寸
	config[1] = gt1151->x_range & 0xFF;
	config[2] = gt1151->x_range >> 8;
	config[3] = gt1151->y_range & 0xFF;
	config[4] = gt1151->y_range >> 8;

	//计算和校验
	checksum = 0;
	for (i = 0; i < (cfg_len - 3); i += 2) 
		checksum += ((uint16_t)config[i] << 8) + config[i+1];
	checksum = 0 - checksum;
	config[cfg_len-3] = (uint8_t)(checksum >> 8);
	config[cfg_len-2] = (uint8_t)(checksum & 0xFF);
	config[cfg_len-1] = 0x01;

	//更新配置数据
	return gt1151_write(gt1151, GT1151_CONFIG_REG, config, cfg_len);
}

static int gt1151_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int result;
	uint32_t irq_flags;
	gt1151_t *gt1151;

	printk("%s\r\n", __FUNCTION__);

	//分配gt1151设备
	gt1151 = devm_kmalloc(&client->dev, sizeof(gt1151_t), GFP_KERNEL);
	if(!gt1151)
	{
		printk("malloc mem failed\r\n");
		return -ENOMEM;
	}
	memset(gt1151, 0, sizeof(gt1151_t));

	//在client中设置驱动私有数据
	client->dev.driver_data = gt1151;

	gt1151->client = client;
	//尺寸默认800*480
	gt1151->x_range = 800;
	gt1151->y_range = 480;

	//获取GPIO号
	gt1151->rst_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);
	if(gt1151->rst_pin < 0)
	{
		printk("get reset gpio failed\r\n");
		return gt1151->rst_pin;
	}
	gt1151->intr_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
	if(gt1151->intr_pin < 0)
	{
		printk("get irq gpio failed\r\n");
		return gt1151->intr_pin;
	}

	//申请GPIO
	result = devm_gpio_request(&client->dev, gt1151->rst_pin, "gt1151-reset");
	if(result != 0)
	{
		printk("request reset gpio failed\r\n");
		return result;
	}
	result = devm_gpio_request(&client->dev, gt1151->intr_pin, "gt1151-irq");
	if(result != 0)
	{
		printk("request irq gpio failed\r\n");
		return result;
	}

	//获取尺寸
	of_property_read_u32(client->dev.of_node, "x-size", &gt1151->x_range);
	of_property_read_u32(client->dev.of_node, "y-size", &gt1151->y_range);
	printk("size:%d*%d\r\n", gt1151->x_range, gt1151->y_range);

	//复位gt1151
	result = gt1151_reset(gt1151);
	if(result != 0)
	{
		printk("reset failed\r\n");
		return result;
	}

	//配置gt1151
	result = gt1151_config(gt1151);
	if(result != 0)
	{
		printk("config failed\r\n");
		return result;
	}

	//分配输入设备
	gt1151->inputdev = devm_input_allocate_device(&client->dev);
	if(!gt1151->inputdev)
	{
		printk("alloc input device failed\r\n");
		return -ENOMEM;
	}

	//初始化输入设备
	gt1151->inputdev->name = client->name;
	gt1151->inputdev->id.bustype = BUS_I2C;
	// // input_mt_init_slots 函数的 flags 为 INPUT_MT_DIRECT 会设置此事件
	// __set_bit(EV_KEY, gt1151->inputdev->evbit);
	// // input_set_abs_params 函数会设置此事件
	// __set_bit(EV_ABS, gt1151->inputdev->evbit);
	// // input_mt_init_slots 函数的 flags 为 INPUT_MT_DIRECT 会设置此按键
	// __set_bit(BTN_TOUCH, gt1151->inputdev->keybit);
	//  // input_mt_init_slots 函数的 flags 为 INPUT_MT_DIRECT 会设置此位
	//__set_bit(INPUT_PROP_DIRECT, gt1151->inputdev->propbit);
	input_set_abs_params(gt1151->inputdev, ABS_X, 0, gt1151->x_range, 0, 0);
	input_set_abs_params(gt1151->inputdev, ABS_Y, 0, gt1151->y_range, 0, 0);
	input_set_abs_params(gt1151->inputdev, ABS_MT_POSITION_X, 0, gt1151->x_range, 0, 0);
	input_set_abs_params(gt1151->inputdev, ABS_MT_POSITION_Y, 0, gt1151->y_range, 0, 0);
	// result = input_mt_init_slots(gt1151->inputdev, GTP_MAX_TOUCH, 0);
	//flags为INPUT_MT_DIRECT会设置EV_KEY、BTN_TOUCH事件,并将ABS_MT_POSITION_X和ABS_MT_POSITION_Y拷贝到ABS_X和ABS_Y
	result = input_mt_init_slots(gt1151->inputdev, GTP_MAX_TOUCH, INPUT_MT_DIRECT);
	if(result != 0)
	{
		printk("mt init failed\r\n");
		return result;
	}

	//注册输入设备
	result = input_register_device(gt1151->inputdev);
	if(result != 0)
	{
		printk("input register failed\r\n");
		return result;
	}

	//设置中断引脚为输入
	gpio_direction_input(gt1151->intr_pin);
	//获取中断触发方式
	irq_flags = irq_get_trigger_type(gt1151->client->irq);
	if(irq_flags == IRQF_TRIGGER_NONE)
		irq_flags = IRQF_TRIGGER_FALLING;
	//中断处理过程中暂时关闭此中断
	irq_flags |= IRQF_ONESHOT;
	//注册中断处理函数
	result = devm_request_threaded_irq(&client->dev, client->irq, NULL, gt1151_irq_thread, irq_flags, client->name, gt1151);
	if(result != 0)
	{
		printk("request irq failed\r\n");
		input_unregister_device(gt1151->inputdev);
		return result;
	}

	return 0;
}

static int gt1151_remove(struct i2c_client *client)
{
	gt1151_t *gt1151;

	printk("%s\r\n", __FUNCTION__);

	gt1151 = client->dev.driver_data;
	input_unregister_device(gt1151->inputdev);

	return 0;
}

static const struct i2c_device_id gt1151_id[] = {
	//采用设备树匹配,这里可以为空
	{"atk-gt1151", 0},
	{ /* Sentinel */ }
};

static const struct of_device_id gt1151_of_match[] = {
	{ .compatible = "atk-gt1151"},
	{ /* Sentinel */ }
};

static struct i2c_driver gt1151_driver = {
	.probe = gt1151_probe,
	.remove = gt1151_remove,
	.driver = {
			.owner = THIS_MODULE,
			.name = "gt1151",
			.of_match_table = gt1151_of_match, 
		},
	//I2C驱动的此项参数不能为NULL
	.id_table = gt1151_id, 
};

static int __init gt1151_init(void)
{
	int result;

	printk("%s\r\n", __FUNCTION__);

	result = i2c_add_driver(&gt1151_driver);
	if(result != 0)
	{
		printk("add gt1151 driver failed\r\n");
		return result;
	}
	return 0;
}

static void __exit gt1151_exit(void)
{
	printk("%s\r\n", __FUNCTION__);

	i2c_del_driver(&gt1151_driver);
}

module_init(gt1151_init);
module_exit(gt1151_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("gt1151 driver");

驱动测试程序编写

驱动测试程序基于tslib进行编写,在编写驱动测试程序前需要完成对tslib的移植,确保编译工具链中有tslib的库文件和头文件,根文件系统中有tslib的库文件,在连接时还需要 -lts 去连接tslib的库文件。驱动测试程序的完整代码如下所示:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/time.h>
#include <getopt.h>
#include <errno.h>
#include <unistd.h>
#include <linux/input.h>
#include <sys/ioctl.h>
#include "tslib.h"

int main(void)
{
	int i;
	int result;
	int32_t max_slots;
	struct tsdev *ts;
	struct ts_sample_mt *samp_mt[1];
	struct input_absinfo slot;

	//打开触摸屏设备,并初始化tslib
	ts = ts_setup(NULL, 0);
	if(!ts)
	{
		printf("tslib setup failed\r\n");
		return -1;
	}
	//获取触摸屏信息
	if(ioctl(ts_fd(ts), EVIOCGABS(ABS_MT_SLOT), &slot) < 0)
	{
		ts_close(ts);
		printf("get device info failed\r\n");
		return -1;
	}
	max_slots = slot.maximum + 1 - slot.minimum;
	//根据触摸点数分配缓存触摸点的内存
	samp_mt[0] = malloc(max_slots*sizeof(struct ts_sample_mt));
	if(!samp_mt[0])
	{
		ts_close(ts);
		printf("alloc memory failed\r\n");
		return -1;
	}
	
	while(1)
	{
		result = ts_read_mt(ts, samp_mt, max_slots, 1);
		if (result < 0) {
			printf("ts_read_mt err\n");
			ts_close(ts);
			free(samp_mt[0]);
			return -1;
		}
		
		for (i = 0; i < max_slots; i++)
		{
			if (samp_mt[0][i].valid)
			{
				printf("slot %d\tid=%d\tx=%d\ty=%d\r\n",
				samp_mt[0][i].slot, samp_mt[0][i].tracking_id, samp_mt[0][i].x, samp_mt[0][i].y);
			}
		}
	}
}

上机测试

  1. 根据硬件原理图对设备树进行修改,然后用命令make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树,并用新的设备树启动目标板。
  2. 这里下载代码并进行编译,然后将编译出来的.ko文件和.out文件拷贝到目标板根文件系统的root目录中
  3. 执行命令insmod gt1151.ko加载触摸屏驱动
    在这里插入图片描述
  4. 执行命令./tslib_app.out运行测试程序,此时用手指触摸触摸屏,便可看到输出的触摸点信息在这里插入图片描述
文章来源:https://blog.csdn.net/lf282481431/article/details/135025602
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。