第二十二节 电容触摸驱动实验

本章配套源码、设备树以及更新固件位于“~/linux_driver/touch_screen_GTxxx”目录下。

触摸面板通过双面胶粘在显示屏上,他们在硬件上没有关联,通常情况下我们会设置触摸面板的“分辨率”与显示屏的分辨率一致。触摸芯片自动完成触摸信息的采集和处理,我们要做的就是配置触摸芯片、读取触点坐标。

常见的触摸接口原理图如下所示。

在这里插入图片描述

触摸芯片有四个引脚连接到了芯片,其中scl、sda 是I2C 通信引脚。RSTN 是触摸的复位引脚,配置触摸芯片时也会用到。INT 是中断引脚,默认高电平。发生触摸后该引脚会发送一个低电平的脉冲信号。

从硬件连接我们可以大致得知电容触摸驱动要用到I2C 设备驱动、中断驱动。由于电容触摸驱动要向应用层提交触摸消息所以还会用到输入子系统。

I2C 设备驱动、中断驱动和输入子系统在之前章节已经介绍,我们现在可以自己从零写一个触摸驱动,但是我们不这样做,因为大多情况下触摸芯片厂商已经编写好了触摸驱动,我们只需要稍加修改就可以使用。

我们使用的触摸芯片型号如下,4.3 寸使用的GT5688,5 寸使用的是GT917S 或GT9157,7 寸屏使用的是GT911。它们都属于GOODIX 公司生产的触摸芯片,厂商已经写好了触摸驱动并且添加默认添加到了内核中。驱动源码位于“~/drivers/input/touchscreen/goodix.c”这个驱动程序并不能完全适配我们使用的这些触摸型号,所以我们需要稍加修改才能使用。下面我们结合源码简单介绍驱动实现原理以及如何根据实际需要修改驱动。

添加设备树节点

设备树节点有两个任务,第一,添加触摸使用的引脚,第二,添加I2C 设备节点。介绍如下。

根据之前讲解,触摸芯片共占用4 个IO 口。这四个IO 如下所示。

引脚功能
PE12 引脚用作触摸芯片的irq 引脚,接收触摸中断
PZ4 引脚用作触摸芯片的复位引脚
PF15 引脚用作I2C1 的SCL 引脚
PF14 引脚用作I2C1 的SDA 引脚

知道了各个引脚的复用功能,在设备树中使用就很容易了。

我们先来看看I2C1 总线的设备树插件:

列表1: I2C1 设备树插件

// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
* Copyright (C) STMicroelectronics 2018 - All Rights Reserved
* Author: Alexandre Torgue <alexandre.torgue@st.com>.
*/

/dts-v1/;
/plugin/;
#include <dt-bindings/pinctrl/stm32-pinfunc.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/mfd/st,stpmic1.h>
#include <dt-bindings/gpio/gpio.h>

/{
	fragment@0{
		target=<&i2c1>;
		__overlay__{
			pinctrl-names = "default", "sleep";
			pinctrl-0 = <&i2c1_pins_a>;
			pinctrl-1 = <&i2c1_pins_sleep_a>;
			i2c-scl-rising-time-ns = <100>;
			i2c-scl-falling-time-ns = <7>;
			status = "okay";
			/delete-property/dmas;
			/delete-property/dma-names;
		};
	};
	fragment@1{
		target=<&pinctrl>;
		__overlay__{
			i2c1_pins_a: i2c1-0 {
				pins {
					pinmux = <STM32_PINMUX('F', 14, AF5)>, /* I2C1_SCL */
							<STM32_PINMUX('F', 15, AF5)>; /* I2C1_SDA */
					bias-disable;
					drive-open-drain;
					slew-rate = <0>;
				};
			};

			i2c1_pins_sleep_a: i2c1-1 {
				pins {
					pinmux = <STM32_PINMUX('F', 14, ANALOG)>, /* I2C1_SCL */
							<STM32_PINMUX('F', 15, ANALOG)>; /* I2C1_SDA */
				};
			};
		};
	};
};

触摸驱动作为一个I2C 设备挂载在I2C1 总线上,它和我们之前讲解的I2C 接口的MPU6050 驱动一样,需要在I2C1 设备节点下追加相应的子节点,不同的是这里用到的两个GPIO 和一个中断。

触摸芯片设备树插件源码如下:

列表2: 在设备树中添加信息

// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
* Copyright (C) STMicroelectronics 2018 - All Rights Reserved
* Author: Alexandre Torgue <alexandre.torgue@st.com>.
*/

/dts-v1/;
/plugin/;
#include <dt-bindings/pinctrl/stm32-pinfunc.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/mfd/st,stpmic1.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>

/{
	fragment@0{
		target=<&i2c1>;
		__overlay__{
			#address-cells = <1>;
			#size-cells = <0>;
			gtxx_tsc@5d {
				compatible = "goodix,gt917s";
				reg = <0x5d>;
				status = "okay";
				/*gpio*/
				reset-gpios = <&gpioe 12 GPIO_ACTIVE_LOW>;
				irq-gpios = <&gpioz 4 GPIO_ACTIVE_HIGH>;
				/*interrupt */
				interrupt-parent = <&gpioz>;
				interrupts = <4 IRQ_TYPE_EDGE_FALLING>;
				irq-flags = <2>; /*1:rising 2: falling*/
			};
		};

	};

};

结合以上内容简单介绍如下:

第一部分,添加一个设备节点gtxx_tsc,设备匹配驱动的属性是”goodix,gt917s”,21、23 行处代码是触摸芯片在I2C1 总线上的地址0x5d。

第二部分,添加使用的GPIO,触摸驱动要使用的中断引脚以及复位引脚,这里使用GPIO 子系统将触摸芯片使用的irq 引脚、rest 引脚复用为GPIO.

第三部分,添加中断相关内容,这里将触发方式设置为上升和下降沿触发,具体内容可参考中断章节,这里不再赘述。

goodix 官方触摸驱动讲解

由于goodix 官方触摸驱动稍复杂,这里只讲解实现方法,以及如何简单修改驱动以适配多种触摸芯片

修改设备树匹配信息

和其他驱动类似,打开官方驱动后首先要找到“设备树匹配”相关内容。如下所示。

列表3: 在I2C11 节点追加

static const struct of_device_id goodix_of_match[] = {
	{ .compatible = "goodix,gt1151" },
	{ .compatible = "goodix,gt911" },
	{ .compatible = "goodix,gt9110" },
	{ .compatible = "goodix,gt912" },
	{ .compatible = "goodix,gt927" },
	{ .compatible = "goodix,gt9271" },
	{ .compatible = "goodix,gt928" },
	{ .compatible = "goodix,gt967" },
	{ }
};
MODULE_DEVICE_TABLE(of, goodix_of_match);
#endif

static struct i2c_driver goodix_ts_driver = {
	.probe = goodix_ts_probe,
	.remove = goodix_ts_remove,
	// .id_table = goodix_ts_id,
	.driver = {
			.name = "Goodix-TS",
			.acpi_match_table = ACPI_PTR(goodix_acpi_match),
			.of_match_table = of_match_ptr(goodix_of_match),
			.pm = &goodix_pm_ops,
	},
};

结合以上代码介绍如下。

  • 第1-10 行:这里就是用于和设备树节点匹配的匹配值,我们将前面编写的设备树节点添加进去即可。
  • 第15-25 行:这个就是i2c 设备驱动结构体,它代表了一个I2C 设备。
  • 第18 行:传统的匹配配方式,我们不用可以屏蔽掉。
  • 第20 行:驱动的名字。

probe 函数实现

probe 函数完成初始化工作,此份代码在goodix 官方提供的驱动上做了部分修改,以适配goodix 的一些触摸驱动芯片型号,代码如下:

列表4: .probe 函数

static int goodix_ts_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
	struct goodix_ts_data *ts;
	struct device *dev = &client->dev;
	int error;

	dev_dbg(&client->dev, "I2C Address: 0x%02x\n", client->addr);

	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
		dev_err(&client->dev, "I2C check functionality failed.\n");
		return -ENXIO;
	}

	ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
	if (!ts)
		return -ENOMEM;

	ts->client = client;
	i2c_set_clientdata(client, ts);
	init_completion(&ts->firmware_loading_complete);
	/* modify by embedfire */
	if(of_property_read_bool(dev->of_node, "needed_invert"))
		ts->needed_invert = true;


	error = goodix_get_gpio_config(ts);
	if (error)
		return error;

	if (ts->gpiod_int && ts->gpiod_rst) {
	/* reset the controller */
		error = goodix_reset(ts);
		if (error) {
			dev_err(&client->dev, "Controller reset failed.\n");
			return error;
		}
	}

	error = goodix_i2c_test(client);
	if (error) {
		dev_err(&client->dev, "I2C communication failure: %d\n", error);
		return error;
	}

	error = goodix_read_version(ts);
	if (error) {
		dev_err(&client->dev, "Read version failed.\n");
		return error;
	}

	ts->chip = goodix_get_chip_data(ts->id);
	// disable update device config——by embedfire
#if 0
	if (ts->gpiod_int && ts->gpiod_rst) {
	/* update device config */
	ts->cfg_name = devm_kasprintf(&client->dev, GFP_KERNEL,
					"goodix_%d_cfg.bin", ts->id);
	if (!ts->cfg_name)
		return -ENOMEM;

	error = request_firmware_nowait(THIS_MODULE, true, ts->cfg_name,
					&client->dev, GFP_KERNEL, ts,
					goodix_config_cb);
	if (error) {
		dev_err(&client->dev,
			"Failed to invoke firmware loader: %d\n",
			error);
		return error;
	}

	return 0;
	} else {
		error = goodix_configure_dev(ts);
		if (error)
			return error;
	}
#endif
	error = goodix_configure_dev(ts);
	if (error)
		return error;
	return 0;
}

probe 函数较长,但是理解起来很简单,也没有什么需要修改的地方,结合源码简单介绍如下:

第一部分,进入.probe 函数后做了一些简单的检查,比如,打印触摸设备的i2c 地址,这个地址是在触摸的设备节点中设置的地址。以及检查是否支持I2C 功能。

第二部分,为goodix_ts_data 类型的结构体变量ts 申请空间并初始化,在驱动中使用goodix_ts_data结构体保存触摸驱动信息。结构体原型如下。

列表5: .goodix_ts_data 结构体

struct goodix_ts_data {
	struct i2c_client *client; //i2c 从设备结构体
	struct input_dev *input_dev; //输入设备结构体
	const struct goodix_chip_data *chip; //goodix 相关内容
	struct touchscreen_properties prop; //未知内容
	unsigned int max_touch_num; //做大支持的触摸点
	unsigned int int_trigger_type; //触摸类型
	struct gpio_desc *gpiod_int; //触摸中断引脚
	struct gpio_desc *gpiod_rst; //触摸芯片复位引脚
	u16 id; //触摸芯片
	u16 version; //版本
	const char *cfg_name; //名字
	struct completion firmware_loading_complete; //固件加载完成标志
	unsigned long irq_flags; //中断标记
};

后面的初始化将会使用这个结构体。结构体成员含义见注释,这里不再赘述。接着回到.probe 函数。

第三部分,完成一些基本的初始化。这部分内容调用一些以“goodix”开头的函数,这些函数是goodix 官方实现的一些函数,定义在该文件内,从函数名我们可以大致知道函数的功能,简单说明如下:

  • goodix_get_gpio_config:获取触摸芯片rst 和int 使用的GPIO。
  • goodix_reset:复位触摸芯片。
  • goodix_i2c_test:测试I2C,尝试与触摸芯片通信。
  • goodix_read_version:读取触摸芯片版本,后边会根据触摸芯片版本来初始化触摸芯片。
  • goodix_get_chip_data:根据goodix_read_version 函数获取的触摸芯片型号指定触摸的一些配置参数。

goodix_get_chip_data 函数原型如下所示:

列表6: goodix_get_chip_data 函数

/*------------------第一部分------------------*/
static const struct goodix_chip_data *goodix_get_chip_data(u16 id)
{
	switch (id) {
	case 1151:
		return &gt1x_chip_data;
	case 911:
	case 9271:
	case 9110:
	case 927:
	case 928:
		return &gt911_chip_data;

	case 912:
	case 9157: // fire 新增
		return &gt9x_chip_data;
	case 917: // fire 新增
		return &gt917_chip_data;
	case 5688: // fire 新增
		return &gt5688_chip_data;
	case 967:
		return &gt967_chip_data;

	default:
		return &gt9x_chip_data;
	}
}

/*------------------第二部分------------------*/


/* 结构体原型*/
struct goodix_chip_data {
	u16 config_addr;
	int config_len;
	int (*check_config)(struct goodix_ts_data *, const struct firmware *);
};

static const struct goodix_chip_data gt9x_chip_data = {
	.config_addr = GOODIX_GT9X_REG_CONFIG_DATA,
	.config_len = GOODIX_CONFIG_MAX_LENGTH,
	.check_config = goodix_check_cfg_8,
};

/*fire 新增----*/
static const struct goodix_chip_data gt917_chip_data = {
	.config_addr = GOODIX_GT917_REG_CONFIG_DATA,
	.config_len = GOODIX_CONFIG_917_LENGTH,
	.check_config = goodix_check_cfg_16,
};

/*fire 新增----*/
static const struct goodix_chip_data gt5688_chip_data = {
	.config_addr = GOODIX_GT917_REG_CONFIG_DATA,
	.config_len = GOODIX_CONFIG_5688_LENGTH,
	.check_config = goodix_check_cfg_16,
};


/*------------------第三部分------------------*/
#define GOODIX_CONFIG_917_LENGTH 242 //fire 新增
#define GOODIX_CONFIG_5688_LENGTH 242 //fire 新增

#define GOODIX_GT5688_REG_CONFIG_DATA 0x8050 //fire 新增
#define GOODIX_GT917_REG_CONFIG_DATA 0x8050 //fire 新增

结合源码介绍如下。

  • 第一部分:goodix_get_chip_data 函数实现很简单,仅仅根据不同的芯片ID 返回不同的
    goodix_chip_data 结构体地址。
  • 第二部分:goodix_chip_data 结构体原型以及初始化实例,该结构体共有三个参数,第一个用于指定触摸芯片的配置寄存器地址,这个地址是触摸芯片的内部地址,不同触摸芯片有所不同,查找触摸手册即可。第二个参数用于指定配置信息的最大长度,不同触摸芯片配置信息长度是不同的,根据手册设置即可,这里也通过宏定义指出,第三个参数是一个函数指针,用于指定“校验”配置信息的函数。再想触摸芯片写入配置信息之前要校验配置信息。根据触摸芯片的不同分为8 位校验和16 位校验。稍后会详细讲解校验函数。
  • 第三部分:宏定义。

接着回到.probe 函数。

第四部分,这部分内容是驱动程序的重点。从前面所述的Probe 三部分可知,到目前为止我们初始化了触摸芯片使用的引脚并且能够与触摸芯片通信了。这部分内容完成后续的中断的申请、输入设备的注册、触摸配置信息的读取与更新、触摸事件的上报工作。不过不必担心这部分呢内容几乎不需要我们去修改。

第四部分中主要是调用goodix_configure_dev 函数。在调用goodix_configure_dev 函数前,我们注释了大块代码,代码内容是使用devm_kasprintf 函数根据触摸芯片的ID 合成触摸配置文件的文件名(以下简称为固件)。例如我们使用的GT911,它的ID 为911,则触摸更新固件名为“goodix_911_cfg.bin”。

有关更新固件内容,我们使用单独的小节说明。

触摸固件说明

通常情况下我们从供应商那里买到的触摸板已经正确配置了固件,如果你是买的我们的屏幕(带触摸)默认也是配置好了触摸固件,无需进行修改,故此我们注释了此块内容。如果是公司用户批量生产,通常情况下也可以和触摸屏供应商沟通,让供应商按照你的要求提前烧写好固件。

在注释了的内容中,使用request_firmware_nowait 函数从用户空间获取固件。它的最后一个参数是一个函数指针,用于指定获取成功后的回调函数,回调函数原型如下所示:

列表7: firmware 回调函数goodix_config_cb

static void goodix_config_cb(const struct firmware *cfg, void *ctx)
{
	struct goodix_ts_data *ts = ctx;
	int error;
	if (cfg) {
		/* send device configuration to the firmware */
		/*------------------第一部分------------------*/
		error = goodix_send_cfg(ts, cfg);

		if (error)
			goto err_release_cfg;
	}
	/*------------------第二部分------------------*/
	goodix_configure_dev(ts);

err_release_cfg:
	release_firmware(cfg);
	complete_all(&ts->firmware_loading_complete);
}

从以上代码可以看出,正常情况下该函数只会执行两个以“goodix”开头的函数,这两个函数完成了后续的初始化。goodix_send_cfg 函数完成触摸芯片更新固件的读取、校验、写入工作。goodix_configure_dev 函数完成中断的申请、注册,输入设备的注册、设置上报事件等等工作。最终的触摸事件上报由中断服务函数完成。由于这部分内容较长,我们将这两个函数独立出来讲解,如下所示。

goodix_send_cfg 函数实现

函数原型如下所示:

列表8: goodix_send_cfg 函数实现

static int goodix_send_cfg(struct goodix_ts_data *ts,
					const struct firmware *cfg)
{
	int error;
	/*------------------第一部分------------------*/
	error = goodix_check_cfg(ts, cfg);
	if (error)
			return error;

	/*------------------第二部分------------------*/
	error = goodix_i2c_write(ts->client, ts->chip->config_addr, cfg->data,
							cfg->size);
	if (error) {
			dev_err(&ts->client->dev, "Failed to write config data: %d",
					error);
			return error;
	}
	dev_dbg(&ts->client->dev, "Config sent successfully.");
	/*------------------第三部分------------------*/
	/* Let the firmware reconfigure itself, so sleep for 10ms */
	usleep_range(10000, 11000);

	return 0;
}

以上函数功能是校验从应用空间读取得到的触摸更新固件,如果校验通过则调用第二部分的代码将固件写入触摸芯片,我们重点看第一部分的校验函数。函数实现如下所示。

列表9: 固件校验函数

static int goodix_check_cfg(struct goodix_ts_data *ts,
const struct firmware *cfg)
{
	/*------------------第一部分------------------*/
	if (cfg->size > GOODIX_CONFIG_MAX_LENGTH) {
			dev_err(&ts->client->dev,
					"The length of the config fw is not correct");
			return -EINVAL;
	}
	/*------------------第二部分------------------*/
	return ts->chip->check_config(ts, cfg);
}

第一部分,校验固件长度是否大于最大支持的固件长度。官方驱动中这里设置为240 由于GT917S和GT5688 的固件会超过这个最大值,这里要按照GT917S 和GT5688 的最大值来计算。最终结果是我们要将“GOODIX_CONFIG_MAX_LENGTH”宏定义的值重新定义为242。

第二部分,调用校验函数。在讲解.probe 函数的第三部分,我们根据触摸ID 指定了校验函数和地址信息。以GT911 为例,如下所示。

列表10: gt911 的goodix_chip_data 结构体

static const struct goodix_chip_data gt911_chip_data = {
	.config_addr = GOODIX_GT9X_REG_CONFIG_DATA,
	.config_len = GOODIX_CONFIG_911_LENGTH,
	.check_config = goodix_check_cfg_8,
};

可以看到“check_config”是一个函数指针,它指向了“goodix_check_cfg_8”函数,下面将会调用goodix_check_cfg_8 函数完成GT911 固件的校验工作,函数实现如下所示。

列表11: 固件校验函数

static int goodix_check_cfg_8(struct goodix_ts_data *ts,
const struct firmware *cfg)
{
	int i, raw_cfg_len = cfg->size - 2;
	u8 check_sum = 0;

	/*---------------第一部分---------------*/
	for (i = 0; i < raw_cfg_len; i++)
			check_sum += cfg->data[i];

	check_sum = (~check_sum) + 1;

	/*---------------第二部分---------------*/
	if (check_sum != cfg->data[raw_cfg_len]) {
			dev_err(&ts->client->dev,
					"The checksum of the config fw is not correct");
			return -EINVAL;
	}

	/*---------------第三部分---------------*/
	if (cfg->data[raw_cfg_len + 1] != 1) {
			dev_err(&ts->client->dev,
					"Config fw must have Config_Fresh register set");
			return -EINVAL;
	}
	return 0;
}

校验过程比较简单,与stm32 稍有差别。在stm32 中我们是计算出配置信息的校验和然后追加到配置信息,然后在最后面添加更新标志。这里读取出来的固件已经加上了校验和并且在固件的最后添加了更新标志,所以这里只需要重新计算校验和并比较是否一致即可代码的第三部分是检测是否有更新标志。

goodix_configure_dev 函数实现

在前面调用获取固件的函数指定的回调函数goodix_config_cb 中,函数最后会调用两个函数,一个是我们上面讲解的固件更新函数goodix_send_cfg,另外一个是我们这小节要讲解的goodix_configure_dev 函数。

但是在我们提供的代码中没有使用更新固件的功能,所以就直接调用goodix_configure_dev 函数。

总的来说,前述代码是一个I2C 设备驱动,它实现了通过I2C1 与触摸芯片的通信,但我们最终目标是检测到“按下”或“抬起”事件后通过输入子系统上报给应用层。goodix_configure_dev 函数就是用作完成这些后续工作,实现代码如下所示。

列表12: 完成设备初始化函数

static int goodix_configure_dev(struct goodix_ts_data *ts)
{
	int error;
	/*---------------第一部分---------------*/
	ts->int_trigger_type = GOODIX_INT_TRIGGER;
	ts->max_touch_num = GOODIX_MAX_CONTACTS;

	ts->input_dev = devm_input_allocate_device(&ts->client->dev);
	if (!ts->input_dev) {
			dev_err(&ts->client->dev, "Failed to allocate input device.");
			return -ENOMEM;
	}

	ts->input_dev->name = "Goodix Capacitive TouchScreen";
	ts->input_dev->phys = "input/ts";
	ts->input_dev->id.bustype = BUS_I2C;
	ts->input_dev->id.vendor = 0x0416;
	ts->input_dev->id.product = ts->id;
	ts->input_dev->id.version = ts->version;

	/*---------------第二部分---------------*/
	/* Capacitive Windows/Home button on some devices */
	input_set_capability(ts->input_dev, EV_KEY, KEY_LEFTMETA);

	input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_X);
	input_set_capability(ts->input_dev, EV_ABS, ABS_MT_POSITION_Y);
	input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
	input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);

	/* Read configuration and apply touchscreen parameters */
	goodix_read_config(ts);

	/* Try overriding touchscreen parameters via device properties */
	touchscreen_parse_properties(ts->input_dev, true, &ts->prop);

	/*---------------第三部分---------------*/
	if (!ts->prop.max_x || !ts->prop.max_y || !ts->max_touch_num) {
			dev_err(&ts->client->dev, "Invalid config, using defaults\n");
			ts->prop.max_x = GOODIX_MAX_WIDTH - 1;
			ts->prop.max_y = GOODIX_MAX_HEIGHT - 1;
			ts->max_touch_num = GOODIX_MAX_CONTACTS;
			input_abs_set_max(ts->input_dev,
							ABS_MT_POSITION_X, ts->prop.max_x);
			input_abs_set_max(ts->input_dev,
							ABS_MT_POSITION_Y, ts->prop.max_y);
	}


	if (dmi_check_system(rotated_screen)) {
			ts->prop.invert_x = true;
			ts->prop.invert_y = true;
			dev_dbg(&ts->client->dev,
					"Applying '180 degrees rotated screen' quirk\n");
	}

	/*---------------第四部分---------------*/
	error = input_mt_init_slots(ts->input_dev, ts->max_touch_num,
								INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
	if (error) {
			dev_err(&ts->client->dev,
					"Failed to initialize MT slots: %d", error);
			return error;
	}

	error = input_register_device(ts->input_dev);
	if (error) {
			dev_err(&ts->client->dev,
					"Failed to register input device: %d", error);
					return error;
	}
	/*---------------第五部分---------------*/
	ts->irq_flags = goodix_irq_flags[ts->int_trigger_type] | IRQF_ONESHOT;
	error = goodix_request_irq(ts);
	if (error) {
			dev_err(&ts->client->dev, "request IRQ failed: %d\n", error);
			return error;
	}
	return 0;
}

函数内容较多,但是这部分内容不需要我们修改,结合源码介绍如下:

第一部分,根据现有参数初始化输出设备结构体input_dev,一个输入设备结构体代表了一输入设备,再注册它之前需要初始化它的一些参数。

第二部分,同样是初始化输入设备结构体,这部分内容用于设置输出设备能够上报的事件类型以及上报事件。从这里部分代码可以看到上报事件类型有EV_KEY 按键事件、绝对坐标事件EV_ABS,绝对坐标事件的键值又分为X 坐标值和Y 坐标值。

函数goodix_read_config 用于从触摸芯片中读取触摸配置信息并用这些配置信息初始化输入设备。

第三部分,检查主要参数是否出错,如果出错则只用默认的参数配置输入设备。

第四部分,输入设备结构体初始化完成后调用input_register_device 函数注册输入设备,注册成功后我们就可以向应用层上报输入触摸事件了。

第五部分,调用goodix_request_irq 函数完成中断的申请,函数实现如下所示。

列表13: 中断申请函数

static int goodix_request_irq(struct goodix_ts_data *ts)
{
	return devm_request_threaded_irq(&ts->client->dev, ts->client->irq,
									NULL, goodix_ts_irq_handler,
									ts->irq_flags, ts->client->name, ts);
}

申请函数使用了devm_request_threaded_irq 函数,在驱动中我们会经常看到“devm”开头的函数,这些函数大多用于注册、申请工作,使用这一类函数注册、申请的内容无需我们手动注销,驱动退出之前系统会自动完成注销。这里我们重点关注中断的处理函数goodix_ts_irq_handler,触摸中断发生有将会在中断服务函数中上报触摸事件。

中断服务函数如下所示:

列表14: 中断服务函数

static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id)
{
	struct goodix_ts_data *ts = dev_id;

	goodix_process_events(ts);

	if (goodix_i2c_write_u8(ts->client, GOODIX_READ_COOR_ADDR, 0) < 0)
			dev_err(&ts->client->dev, "I2C write end_cmd error\n");

	return IRQ_HANDLED;
}

第5 行处的函数用于处理触摸事件,具体的处理过程这里不再介绍,读者可参考输入子系统章节自行阅读。至此,我们可以知道,当触摸中断发生后将会在中断服务函数中上报输入事件,而应用程序只需要从“/dev/input”目录下对应的节点读取状态即可。

驱动测试

我们已经将触摸的设备树插件及驱动程序修改好了,放在内核源码中,在编译内核时驱动程序会被默认配置为编译进内核。

如果大家想手动加载触摸的内核驱动模块,则需自行将内核编译选项中的goodix 的驱动编译选项选择为“M”,并重新编译、加载使用新内核,并手动insmod 驱动模块进行实验。

本节涉及的代码也保存在“/linux_driver/touch_screen_GTxxx”目录中。

出厂时触摸驱动已经默认被编译进内核,所以我们需要做的只是加载触摸的设备树插件,并通过hexdump 工具查看实验现象。

方法参考如下:

在这里插入图片描述

加载触摸设备树插件,操作如下:

在这里插入图片描述

dtoverlay=/usr/lib/linux-image-4.19.94-stm-r1/overlays/stm-fire-touch-capacitive-goodix.dtbo

加载后重启开发板。在uboot 启动过程中,可以看到触摸的设备树插件已经被加载。

在这里插入图片描述

在Linux 启动过程中,goodix 驱动已经匹配到了设备,并打印出了一些信息,说明驱动模块被加载。在进入系统后,我们可以测试一下驱动程序。

在这里插入图片描述

通过如下命令,我们可以查看到系统中的输入设备,如无意外会由event0 设备。

ls /dev/input/

下面我们使用hexdump 简单检测一下触摸功能。

hexdump /dev/input/event0

执行命令后,在屏幕上轻触,会打印出大量格式化的数据,这些就是触摸数据了。


参考资料:嵌入式Linux 驱动开发实战指南-基于STM32MP1 系列

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: RA8875是一种常见的电容触摸驱动程序,适用于7寸电容触摸屏。以下是一个300字的中文回答: RA8875是一种集成了电容触摸屏控制功能的驱动程序,适用于7寸电容触摸屏。它能够通过软件进行初始化和配置,然后通过驱动电容触摸屏来实现用户的触摸输入。 在使用RA8875的过程中,首先需要进行初始化,包括设置分辨率、触摸屏参数等。接下来,我们可以使用预定义的函数来实现触摸屏的各种功能,比如获取触摸点的坐标、检测触摸状态等。 RA8875驱动程序支持多点触控,使得用户可以同时使用多个手指进行操作。它还提供了丰富的触摸手势功能,包括滑动、缩放等。 此外,RA8875驱动程序还提供了丰富的图形绘制功能,能够实现直线、矩形、圆形等基本图形的绘制。它还支持图像显示,可以通过加载图像文件来显示图片。 RA8875驱动程序还提供了丰富的字体绘制功能,支持各种字体的显示和打印。用户可以根据自己的需要进行字体大小、颜色的设置。 总的来说,使用RA8875驱动程序可以方便快捷地实现7寸电容触摸屏的驱动与控制,以及图形和文字的绘制。它为用户提供了丰富的交互功能,能够满足各种应用场景的需求。 ### 回答2: RA8875是一款常见的7寸电容触摸驱动程序。作为一款专为电容触摸屏设计的显示控制器,RA8875具有广泛的应用领域,包括工业控制、医疗设备、嵌入式系统等。 RA8875驱动程序的功能丰富,可以实现多种显示和触摸操作。通过驱动程序,用户可以设置显示屏的分辨率、亮度、对比度等参数,还可以实现图像的显示和刷新。同时,驱动程序还支持多种接口,例如SPI、I2C等,方便与外部设备的连接和通信。 在操作方面,RA8875驱动程序提供了丰富的指令和函数,用户可以通过编程来控制触摸屏的触摸操作。例如,可以通过驱动程序来检测并响应触摸屏的点击、滑动等手势操作。此外,驱动程序还支持多点触控,可以同时检测和处理多个触摸点的操作。 在开发过程中,用户可以根据具体需求,对驱动程序进行定制和优化。RA8875驱动程序提供了丰富的配置选项,可以灵活地调整显示效果和触摸响应。用户还可以根据需要,添加额外的功能和扩展,实现更复杂的应用。 总之,7寸电容触摸屏RA8875驱动程序是一款强大且灵活的工具,能够帮助用户实现各种显示和触摸操作。无论是在工业控制还是嵌入式系统中,使用该驱动程序可以提升产品的用户体验和功能性。 ### 回答3: 7寸电容触摸屏RA8875是一种常见的显示屏,它能够通过触摸来进行操作。为了使该触摸屏正常工作,我们需要编写相应的驱动程序。 首先,我们需要通过连接相关引脚来将触摸屏与单片机或开发板进行连接。通常情况下,RA8875驱动程序需要调用SPI(串行外设接口)来进行通信,并且需要连接到触摸屏的X、Y轴和触摸数据线。 接下来,我们需要编写驱动程序来初始化RA8875显示屏。这涉及到设置显示区域、颜色模式、背光等参数。我们还可以设置触摸屏的校准参数,以确保触摸位置的准确性。 然后,我们需要编写代码来读取触摸屏的数据。触摸屏通常会以坐标的形式返回触摸点的位置。我们可以使用SPI通信协议来读取这些数据,并将其转换为屏幕上的像素位置。 最后,我们可以编写一个简单的用户界面程序来实现图形化的操作。我们可以通过读取触摸屏的数据来检测用户的操作,如点击、滑动等,并相应地进行相应的操作。 总之,编写7寸电容触摸屏RA8875的驱动程序需要考虑到与单片机或开发板的连接、初始化显示屏、读取触摸屏数据以及实现相应的操作等方面的问题。通过编写完整的驱动程序,我们可以使用触摸屏来实现交互式的操作和界面。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值