目录
一、AT4384 RGB屏幕触摸芯片
使用正点原子屏幕的朋友一定要注意,以前原子的触摸屏的触摸芯片是gt9147的,教程里的也是gt9147的驱动,后来,大概在2021年5月份前后,原子的4.3寸屏更换了触摸芯片了,触摸芯片型号是gt1151的。所以,后期买屏幕的朋友可能拿到屏幕的芯片不是gt9147的了,所以按照教程或者前面的帖子来改的话,可能是无法触摸的。所以说,一定要先确定自己触摸屏幕的触摸芯片型号。
二、设备树修改
1、.dtsi文件修改
dtsi文件中添加如下内容,在pio结点下对本次实验所用到的管脚进行功能复用,所用到的功能为I2C通信功能、GPIO输出复位功能、中断检测功能。其中,I2C选择i2c0,也就是PE11、PE12,复位管脚选择PE4,中断检测管脚选择PE3,这是我们的硬件决定的。
pio: pinctrl@1c20800 {
compatible = "allwinner,suniv-f1c100s-pinctrl";
reg = <0x01c20800 0x400>;
interrupts = <38>, <39>, <40>;
clocks = <&ccu 37>, <&osc24M>, <&osc32k>;
clock-names = "apb", "hosc", "losc";
gpio-controller;
interrupt-controller;
#interrupt-cells = <3>;
#gpio-cells = <3>;
uart0_pe_pins: uart0-pe-pins {
pins = "PE0", "PE1";
function = "uart0";
};
// modify by kashine
lcd_rgb666_pins: lcd-rgb666-pins {
pins = "PD0", "PD1", "PD2", "PD3", "PD4",
"PD5", "PD6", "PD7", "PD8", "PD9",
"PD10", "PD11", "PD12", "PD13", "PD14",
"PD15", "PD16", "PD17", "PD18", "PD19",
"PD20", "PD21";
function = "lcd";
};
// modify by kashine
mmc0_pins: mmc0-pins {
pins = "PF0", "PF1", "PF2", "PF3", "PF4", "PF5";
function = "mmc0";
};
// modify by kashine 2
i2c0_pins: i2c0_pins {
pins = "PE11", "PE12";
function = "i2c0";
};
// modify by kashine 2
ts_reset_pin: ts_reset_pin {
pins = "PE4";
function = "gpio_out";
};
ts_int_pin: ts_int_pin {
pins = "PE3";
function = "gpio_in";
};
};
在soc结点下添加i2c0结点, 描述了i2c0对应的驱动、时钟和所用管脚等信息,在.dtsi文件中该结点的状态属性值为disabled,这意味着需要在.dts文件中使能该结点功能。
i2c0: i2c@1C27000 {
compatible = "allwinner,sun6i-a31-i2c";
reg = <0x01C27000 0x400>;
interrupts = <7>;
clocks = <&ccu CLK_BUS_I2C0>;
resets = <&ccu RST_BUS_I2C0>;
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
};
2、.dts文件修改
dts文件添加以下内容,gt1151触摸屏对应的驱动为goodix,gt1151ATK4384,对于该驱动的编写,我们将在下一节进行描述。注意:gt1151器件地址和gt9147相同,都是0x14。每个I2C器件都有一个设备地址,通过发送具体的设备地址来决定访问哪个I2C器件。I2C设备的读写地址和I2C设备地址不同,I2C设备的读写地址是一个8位的数据,其中高7位是设备地址,最后1位是读写位,为1的话表示这是一个读操作,为0的话表示这是一个写操作。 I2C设备的写地址 = I2C设备地址 << 1,I2C设备的读地址 = (I2C设备地址 << 1) + 1。
&i2c0 {
status = "okay";
gt1151: touchscreen@14 {
compatible = "goodix,gt1151ATK4384";// "goodix,gt1151";
reg = <0x14>;// gt1151器件地址,器件地址是I2C器件固有的地址编码,器件出厂时已经给定,不可更改
interrupt-parent = <&pio>;
interrupts = <4 3 IRQ_TYPE_EDGE_FALLING>; /* (PE3) */
pinctrl-names = "default";
pinctrl-0 = <&ts_int_pin>;
reset-gpios = <&pio 4 4 GPIO_ACTIVE_LOW>; /* RST (PE4) */
interrupt-gpios = <&pio 4 3 GPIO_ACTIVE_LOW>;
touchscreen-size-x = <800>;
touchscreen-size-y = <480>;
touchscreen-swapped-x-y;
};
};
三、驱动编写
1、驱动分析
驱动程序编写主要参考《正点原子开发指南》,在裸机开发中进行触摸屏的驱动,主要流程如下:
①、电容触摸屏是IIC接口的,需要触摸 IC,以正点原子的 ATK4384 为例,其所使用的触
摸屏控制 IC 为GT1151,因此所谓的电容触摸驱动就是 IIC设备驱动。
②、触摸IC提供了中断信号引脚(INT),可以通过中断来获取触摸信息。
③、电容触摸屏得到的是触摸位置绝对信息以及触摸屏是否有按下。
④、电容触摸屏不需要校准,当然了,这只是理论上的,如果电容触摸屏质量比较差,或
者触摸玻璃和 TFT 之间没有完全对齐,那么也是需要校准的。
那么电容触摸屏的Linux驱动主要需要以下几个驱动框架的组合:
①、IIC 设备驱动,因为电容触摸IC基本都是IIC接口的,因此大框架就是IIC设备驱动。
②、通过中断引脚(INT)向linux内核上报触摸信息,因此需要用到linux中断驱动框架。坐
标的上报在中断服务函数中完成。
③、触摸屏的坐标信息、屏幕按下和抬起信息都属于linux的input子系统,因此向 linux 内
核上报触摸屏坐标信息就得使用input子系统。
2、驱动代码
我们根据上面的分析编写了如下的gt1151的触摸屏驱动程序,至于代码的的详细解释,请参考正点原子官方文档,或者代码中注释,不在此处赘述。设备(设备树结点)和驱动匹配成功之后,gt1151probe函数就会执行,在该函数内部获取设备树终端和复位引脚、复位并初始化gt1151、获取设备信息后注册input设备,最后初始化中断。注意:设备树匹配表gt1151_of_match_table中的.compatible = "goodix,gt1151ATK4384"用来连接设备树和驱动,在设备树中的compatible一定要于此处完全相同!
// gt1151驱动代码
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/gpio/consumer.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/input/mt.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>
#include <linux/i2c.h>
#include <asm/unaligned.h>
#define GT_CTRL_REG 0X8040 /* GT1151控制寄存器 */
#define GT_MODSW_REG 0X804D /* GT1151模式切换寄存器 */
#define GT_9xx_CFGS_REG 0X8047 /* GT1151配置起始地址寄存器 */
#define GT_1xx_CFGS_REG 0X8050 /* GT1151配置起始地址寄存器 */
#define GT_CHECK_REG 0X80FF /* GT1151校验和寄存器 */
#define GT_PID_REG 0X8140 /* GT1151产品ID寄存器 */
#define GT_GSTID_REG 0X814E /* GT1151当前检测到的触摸情况 */
#define GT_TP1_REG 0X814F /* 第一个触摸点数据地址 */
#define GT_TP2_REG 0X8157 /* 第二个触摸点数据地址 */
#define GT_TP3_REG 0X815F /* 第三个触摸点数据地址 */
#define GT_TP4_REG 0X8167 /* 第四个触摸点数据地址 */
#define GT_TP5_REG 0X816F /* 第五个触摸点数据地址 */
#define MAX_SUPPORT_POINTS 5 /* 最多5点电容触摸 */
// 存放电容触摸设备相关属性信息
struct gt1151_dev {
int irq_pin,reset_pin; /* 中断和复位IO */
int irqnum; /* 中断号 */
int irqtype; /* 中断类型 */
int max_x; /* 最大横坐标 */
int max_y; /* 最大纵坐标 */
void *private_data; /* 私有数据 */
struct input_dev *input; /* input结构体 */
struct i2c_client *client; /* I2C客户端 */
};
struct gt1151_dev gt1151;
const u8 irq_table[] = {IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_LEVEL_LOW, IRQ_TYPE_LEVEL_HIGH}; /* 触发方式 */
// 复位GT1151
static int gt1151_ts_reset(struct i2c_client *client, struct gt1151_dev *dev)
{
int ret = 0;
/* 申请复位IO*/
if (gpio_is_valid(dev->reset_pin)) {
/* 申请复位IO,并且默认输出高电平 */
ret = devm_gpio_request_one(&client->dev,
dev->reset_pin, GPIOF_OUT_INIT_HIGH,
"gt1151 reset");
if (ret) {
return ret;
}
}
/* 初始化GT1151,要严格按照GT1151时序要求 */
gpio_set_value(dev->reset_pin, 0); /* 复位GT1151 */
msleep(10);
gpio_set_value(dev->reset_pin, 1); /* 停止复位GT1151 */
msleep(10);
// gpio_set_value(dev->irq_pin, 0); /* 拉低INT引脚 */
// msleep(50);
// gpio_direction_input(dev->irq_pin); /* INT引脚设置为输入 */
return 0;
}
// 从GT1151读取多个寄存器数据
static int gt1151_read_regs(struct gt1151_dev *dev, u16 reg, u8 *buf, int len)
{
int ret;
u8 regdata[2];
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->client;
/* GT1151寄存器长度为2个字节 */
regdata[0] = reg >> 8;
regdata[1] = reg & 0xFF;
// 别和SPI通信方式搞混了
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* gt1151地址 应该是设备树中的器件地址 */
msg[0].flags = !I2C_M_RD; /* 标记为发送数据 */
msg[0].buf = ®data[0]; /* 读取的首地址 */
msg[0].len = 2; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* gt1151地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = buf; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
ret = -EREMOTEIO;
}
return ret;
}
// 向GT1151多个寄存器写入数据
static s32 gt1151_write_regs(struct gt1151_dev *dev, u16 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->client;
b[0] = reg >> 8; /* 寄存器首地址低8位 */
b[1] = reg & 0XFF; /* 寄存器首地址高8位 */
memcpy(&b[2],buf,len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* gt1151地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 2; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
// 中断服务函数
static irqreturn_t gt1151_irq_handler(int irq, void *dev_id)
{
int touch_num = 0;
int input_x, input_y;
int id = 0;
int ret = 0;
u8 data;
u8 touch_data[5];
struct gt1151_dev *dev = dev_id;
// printk("enter irq handler!\r\n");
ret = gt1151_read_regs(dev, GT_GSTID_REG, &data, 1);// GT1151当前检测到的触摸情况
if (data == 0x00) { /* 没有触摸数据,直接返回 */
goto fail;
} else { /* 统计触摸点数据 */
touch_num = data & 0x0f;
}
/* 由于GT1151没有硬件检测每个触摸点按下和抬起,因此每个触摸点的抬起和按
* 下不好处理,尝试过一些方法,但是效果都不好,因此这里暂时使用单点触摸
*/
if(touch_num) { /* 单点触摸按下 */
gt1151_read_regs(dev, GT_TP1_REG, touch_data, 5);
id = touch_data[0] & 0x0F;
if(id == 0) { //读取成功
input_x = touch_data[1] | (touch_data[2] << 8);
input_y = touch_data[3] | (touch_data[4] << 8);
// 单点id一直等于0即可
input_mt_slot(dev->input, 0);
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, true);
input_report_abs(dev->input, ABS_MT_POSITION_X, input_x);
input_report_abs(dev->input, ABS_MT_POSITION_Y, input_y);
}
} else if(touch_num == 0){ /* 单点触摸释放 */
input_mt_slot(dev->input, id);
input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false);// 删除触摸点
}
input_mt_report_pointer_emulation(dev->input, true);// 没有出现硬件检测到的点比上报的触摸点多的情况
input_sync(dev->input);
data = 0x00; /* 向0X814E寄存器写0 */
gt1151_write_regs(dev, GT_GSTID_REG, &data, 1);
fail:
return IRQ_HANDLED;
}
// GT1151中断初始化
static int gt1151_ts_irq(struct i2c_client *client, struct gt1151_dev *dev)
{
int ret = 0;
// 由于触摸IC复位需要用到两个IO 在前面已经request了
/* 2,申请中断,client->irq就是IO中断, */
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
gt1151_irq_handler, irq_table[dev->irqtype] | IRQF_ONESHOT,
client->name, >1151);
if (ret) {
dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
return ret;
}else
{
printk("irq init!\r\n");
printk("dev->irqtype = %d\r\n", dev->irqtype);
}
return 0;
}
// GT1151读取固件
static int gt1151_read_firmware(struct i2c_client *client, struct gt1151_dev *dev)
{
int ret = 0, version = 0;
u16 id = 0;
u8 data[7]={0};
char id_str[5];
ret = gt1151_read_regs(dev, GT_PID_REG, data, 6);
if (ret) {
dev_err(&client->dev, "Unable to read PID.\n");
return ret;
}
memcpy(id_str, data, 4);
id_str[4] = 0;
if (kstrtou16(id_str, 10, &id))id = 0x1001;
version = get_unaligned_le16(&data[4]);
dev_info(&client->dev, "ID %d, version: %04x\n", id, version);
switch (id) { /* 由于不同的芯片配置寄存器地址不一样需要判断一下 */
case 1151:
case 1158:
case 5663:
case 5688: /* 读取固件里面的配置信息 */
ret = gt1151_read_regs(dev, GT_1xx_CFGS_REG, data, 7);
break;
default:
ret = gt1151_read_regs(dev, GT_1xx_CFGS_REG, data, 7);
break;
}
if (ret) {
dev_err(&client->dev, "Unable to read Firmware.\n");
return ret;
}
dev->max_x = (data[2] << 8) + data[1];
dev->max_y = (data[4] << 8) + data[3];
dev->irqtype = data[6] & 0x3;
printk("X_MAX: %d, Y_MAX: %d, TRIGGER: 0x%02x", dev->max_x, dev->max_y, dev->irqtype);
return 0;
}
// probe函数
int gt1151_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
u8 data, ret;
gt1151.client = client;
printk("Driver and device has mached!!!\r\n");
/* 1,获取设备树中的中断和复位引脚 */
gt1151.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
gt1151.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);
// printk("irq_pin=%d, reset_pin=%d\r\n", gt1151.irq_pin, gt1151.reset_pin);
/* 2,复位GT1151 */
ret = gt1151_ts_reset(client, >1151);
if(ret < 0) {
goto fail;
}
/* 3,初始化GT1151 */
data = 0x02;
gt1151_write_regs(>1151, GT_CTRL_REG, &data, 1); /* 软复位 */
mdelay(100);
data = 0x0;
gt1151_write_regs(>1151, GT_CTRL_REG, &data, 1); /* 停止软复位 */
mdelay(100);
/* 4,初始化GT1151,读取固件 */ // 应该是读触摸设备的信息
ret = gt1151_read_firmware(client, >1151);
if(ret != 0) {
printk("Fail !!! check !!\r\n");
goto fail;
}
/* 5,input设备注册 */
gt1151.input = devm_input_allocate_device(&client->dev);
if (!gt1151.input) {
ret = -ENOMEM;
goto fail;
}
gt1151.input->name = client->name;
gt1151.input->id.bustype = BUS_I2C;
gt1151.input->dev.parent = &client->dev;
__set_bit(EV_KEY, gt1151.input->evbit);
__set_bit(EV_ABS, gt1151.input->evbit);
__set_bit(BTN_TOUCH, gt1151.input->keybit);
input_set_abs_params(gt1151.input, ABS_X, 0, gt1151.max_x, 0, 0);
input_set_abs_params(gt1151.input, ABS_Y, 0, gt1151.max_y, 0, 0);
input_set_abs_params(gt1151.input, ABS_MT_POSITION_X,0, gt1151.max_x, 0, 0);
input_set_abs_params(gt1151.input, ABS_MT_POSITION_Y,0, gt1151.max_y, 0, 0);
ret = input_mt_init_slots(gt1151.input, MAX_SUPPORT_POINTS, 0);
if (ret) {
goto fail;
}
ret = input_register_device(gt1151.input);
if (ret)
goto fail;
/* 6,最后初始化中断 */
ret = gt1151_ts_irq(client, >1151);
if(ret < 0) {
goto fail;
}
return 0;
fail:
return ret;
}
// i2c驱动的remove函数
int gt1151_remove(struct i2c_client *client)
{
input_unregister_device(gt1151.input);
return 0;
}
// 传统驱动匹配表
const struct i2c_device_id gt1151_id_table[] = {
{ "goodix,gt1151ATK4384", 0, },
{ /* sentinel */ }
};
// 设备树匹配表
const struct of_device_id gt1151_of_match_table[] = {
{.compatible = "goodix,gt1151ATK4384" },
{ /* sentinel */ }
};
/* i2c驱动结构体 */
struct i2c_driver gt1151_i2c_driver = {
.driver = {
.name = "gt1151",
.owner = THIS_MODULE,
.of_match_table = gt1151_of_match_table,
},
.id_table = gt1151_id_table,
.probe = gt1151_probe,
.remove = gt1151_remove,
};
module_i2c_driver(gt1151_i2c_driver);// 展开后和module_init module_exit一样,类似于module_platform_driver
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kashine");
四、内核配置
1、IIC设备使能
首先使能IIC控制器和设备树对i2c设备的支持,使用make menuconfig命令打开图形化配置界面,打开以下路径的Marvell mv64xxx I2C Controller:
Device Drivers
-> I2C support
-> I2C Hardware Bus support
全志芯片使用的是marvell的i2c控制器(参考Documentation/devicetree/bindings/i2c/i2c-mv64xxx.txt),该选项默认处于使能状态。
打开以下路径中的I2C device interface选项:
Device Drivers
-> I2C support
2、内核驱动添加
将上一节编写的驱动代码命名为gt1151.c,并放在linux-5.7.1/drivers/input/touchscreen目录下,如下图所示,可以看到touchscreen内有很多屏幕的适配驱动。其实gt1151官方也有适配的驱动,就是gt1151.c上方的goodix.c,这是汇顶触摸IC对应的gt系列驱动,那为什么我们还要自己编写呢?因为对于本文所用的硬件接线以及正点原子屏幕的定义,导致使用该驱动对正点原子屏幕的识别出错,得到的x、y坐标是反着的,因此需要对goodix.c进行修改,那就不如自己写喽。
仅仅将驱动文件放到对应的文件夹下面并不可以,因为内核不知道有这么个tg1151驱动文件,我们需要在touchscreen文件夹下的Makfile进行修改,在最后方添加对gt1151.c的编译请求。
obj-$(CONFIG_TOUCHSCREEN_GT1151) += gt1151.o
我们都知道在make menuconfig图形化配置界面可以对驱动进行使能、关闭、编译为模块的配置,如果想要我们的gt1151驱动编译选项能够在make menuconfig调出的图形化配置界面中配置,还需要在touchscreen文件夹下的Kconfig文件中添加如下内容:
config TOUCHSCREEN_GT1151
tristate "GT1151 touchscreen controller"
depends on I2C
help
Say Y to enable support for the GT1151
family of trackpad/touchscreen controllers.
To compile this driver as a module, choose M here: the
module will be called gt1151.
endif #非添加内容!!!
3、gt1151驱动使能
添加完驱动,并提供了图形化界面配置信息后,我们还要在make menuconfig提供的图形化配置界面中使能我们添加的gt1151驱动。如下图所示,我们需要使能GT1151 touchscreen controller选项(将驱动编译进内核),也就是我们上面添加的选项。
我们自定义了gt1151的驱动,因此goodix.c中的驱动我们就不需要了,所以在相同路径中,失能goodix驱动选项Goodix I2C touchscreen,如下图所示:
五、触摸测试
1、启动与文件检查
经过前几节的配置,我们重新对内核和设备树进行编译,然后上电运行开发板,查看输出信息,发现内核启动时先后加载了i2c驱动和gt1151驱动,并且初始化中断功能,Debian系统启动后直接进入root用户空间。注意:输出Driver and device has mached!!!代表设备树设备和驱动匹配成功,这是由驱动probe函数中的printk决定的!
下面这张图是我们在 “Debian根文件系统制作”这一小节中安装的一些组件,其中evtest是为本节安装的触摸屏测试软件,当时大家可能比较疑惑,现在我们使用该软件测试触摸屏触摸是否正常,当然大家也可以使用hexdump、tslib(非常直观,我移植了两天,宣告失败,有兴趣的尝试尝试,做出来记得@我,F1C200s下tslib移植到Debian文件系统哦)。
进入root用户空间后,首先查看路径/dev/input/event0、/dev/fb0中是否存在对应的文件,其中event0代表我们的电容触摸屏,不同的平台 event 序号不同,也可能是 event3,event4 等,一切以实际情况为准!我的是event0,如下图所示。
如果不存在event0,或者不存在input文件夹,首先检查/driver/input/touchscreen文件夹下面有没有产生gt1151.o文件,如果没有说明没有编译,检查Kconfig、Makfile、图形化配置使能;如果有检查compatible属性值,或是检查启动log有无输出Driver and device has mached!!!。
2、触摸测试
很幸运你能看到这里,应该庆幸欣慰,给你点赞👍👍👍👍👍👍。在顺利完成上述操作后,我们使用evtest软件对触摸屏进行测试,使用如下命令进入测试:
evtest /dev/input/event0
从上面的图中可以看出,该软件正确识别到了我们的屏幕,分辨率为800*480,上报X坐标事件码为53,上报Y坐标事件码为54,现在我们来分别触摸屏幕的右下角、右上角、左下角、中间:
通过串口打印的坐标基本符合我们的触摸位置,至此,触摸IC驱动移植结束,恭喜完成本节,离offer又进一步! 🎉🎉🎉
六、主要参考内容
1.【f1c200s/f1c100s】FT5426触摸屏驱动适配_Liangtao`的博客-CSDN博客_ft5426;
2. linux设备适配触摸屏(gt1151)_星星-点灯的博客-CSDN博客;
3. 《正点原子驱动开发指南》