第一次接触GT911触摸芯片是在为一个工业平板项目做驱动移植时遇到的。这款由Goodix公司生产的电容式触摸控制器,凭借其高精度和低功耗特性,在嵌入式领域应用广泛。与常见的ft5x06等触摸芯片相比,GT911最大的特点是支持最多5点触控,并且内置了196个可配置寄存器,能灵活适配不同尺寸的屏幕。
在Linux系统中驱动GT911主要涉及三个层面的工作:首先是设备树配置,需要正确描述芯片的I2C地址、中断引脚和复位引脚;其次是I2C通信驱动,要完成寄存器读写和初始化流程;最后是Input子系统集成,将触摸坐标转换为标准输入事件。我遇到过最典型的问题就是中断触发异常,后来发现是设备树中中断触发方式配置错误导致的。
整个开发流程中,设备树的修改往往是第一步,也是容易出错的地方。GT911通常采用I2C接口通信,标准模式下时钟频率为100kHz,快速模式下可达400kHz。芯片支持两个从机地址:0x28和0xBA(7位地址格式),实际使用时需要根据硬件设计选择对应的地址。在设备树中,我们不仅需要配置I2C总线参数,还要声明中断引脚和复位引脚的电平特性。
在i.MX6ULL平台上配置GT911时,我通常会先检查I2C控制器的状态。以常见的I2C2总线为例,首先要确保总线控制器已启用:
dts复制&i2c2 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";
};
这里有几个关键参数需要注意:
clock-frequency设置为100kHz,这是GT911的标准工作频率pinctrl-0指定了I2C引脚复用配置status必须设为"okay"才能使能控制器实际项目中遇到过总线无法通信的情况,后来发现是pinctrl配置中漏掉了SCL线的上拉电阻设置。正确的引脚控制配置应该包含如下内容:
dts复制pinctrl_i2c2: i2c2grp {
fsl,pins = <
MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
>;
};
在确认I2C总线正常工作后,就可以添加GT911的设备节点了。根据芯片手册,我们需要配置以下几个关键属性:
dts复制gt911@5d {
compatible = "goodix,gt911";
reg = <0x5d>; // 0xBA >> 1
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gt911>;
interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
reset-gpios = <&gpio5 2 GPIO_ACTIVE_LOW>;
irq-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
goodix,cfg-group0 = [
// 配置寄存器数据
00 20 03 E0 01 05 0D 00 01 08
28 0F 50 32 03 05 00 00 00 00
// ... 省略其他配置数据
];
};
这里有几个容易出错的地方:
reg地址是0xBA右移一位的结果,因为Linux I2C子系统使用7位地址IRQ_TYPE_EDGE_FALLING必须与硬件设计一致reset-gpios的有效电平要根据电路设计确定在i.MX6ULL平台上,中断和复位引脚通常需要单独配置。以下是一个典型的引脚控制组配置:
dts复制pinctrl_gt911: gt911grp {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER2__GPIO5_IO02 0x10B0 // 复位
MX6UL_PAD_GPIO1_IO05__GPIO1_IO05 0x10B0 // 中断
>;
};
引脚配置参数0x10B0的含义是:
曾经在一个项目中,触摸屏偶尔会出现误触,后来发现是中断引脚没有启用上拉导致的。因此建议始终为中断引脚启用上拉电阻。
GT911驱动的基础是I2C通信,Linux内核提供了完善的接口。首先需要定义设备ID和驱动结构:
c复制static const struct i2c_device_id gt911_id[] = {
{ "goodix,gt911", 0 },
{}
};
MODULE_DEVICE_TABLE(i2c, gt911_id);
static struct i2c_driver gt911_driver = {
.driver = {
.name = "gt911",
.of_match_table = of_match_ptr(gt911_of_match),
},
.probe = gt911_probe,
.remove = gt911_remove,
.id_table = gt911_id,
};
在probe函数中,我们需要完成以下关键操作:
一个常见的寄存器读写函数实现如下:
c复制static int gt911_write_reg(struct i2c_client *client, u16 reg, u8 val)
{
u8 buf[3];
struct i2c_msg msg = {
.addr = client->addr,
.flags = 0,
.len = 3,
.buf = buf,
};
buf[0] = reg >> 8;
buf[1] = reg & 0xFF;
buf[2] = val;
return i2c_transfer(client->adapter, &msg, 1);
}
GT911的完整初始化流程包括以下几个步骤:
硬件复位:
c复制gpiod_set_value(ts->reset_gpio, 0);
msleep(20);
gpiod_set_value(ts->reset_gpio, 1);
msleep(100);
软件复位:
向0x8040寄存器写入0x02,然后写入0x00结束复位:
c复制gt911_write_reg(client, 0x8040, 0x02);
msleep(100);
gt911_write_reg(client, 0x8040, 0x00);
配置寄存器组:
将设备树中的配置数组写入0x8047-0x8100地址范围:
c复制for (i = 0; i < config_len; i++) {
gt911_write_reg(client, 0x8047 + i, config_data[i]);
}
验证配置:
读取产品ID寄存器确认通信正常:
c复制gt911_read_reg(client, 0x8140, &id, 4);
if (id[0] != '9' || id[1] != '1' || id[2] != '4' || id[3] != '7') {
dev_err(&client->dev, "ID mismatch");
return -ENODEV;
}
在实际调试中,我发现配置完成后需要额外等待50ms以上才能读取坐标数据,否则首次触摸可能无法触发中断。
GT911的中断处理是整个驱动的核心,其工作流程如下:
典型的中断处理函数实现:
c复制static irqreturn_t gt911_irq_handler(int irq, void *dev_id)
{
struct gt911_data *ts = dev_id;
u8 status;
int i;
gt911_read_reg(ts->client, 0x814E, &status, 1);
if (!(status & 0x80))
return IRQ_NONE;
int touch_num = status & 0x0F;
for (i = 0; i < touch_num; i++) {
u8 data[6];
gt911_read_reg(ts->client, 0x8150 + i * 8, data, 6);
int x = (data[1] << 8) | data[0];
int y = (data[3] << 8) | data[2];
input_report_abs(ts->input, ABS_MT_POSITION_X, x);
input_report_abs(ts->input, ABS_MT_POSITION_Y, y);
input_mt_sync(ts->input);
}
input_sync(ts->input);
gt911_write_reg(ts->client, 0x814E, 0x00);
return IRQ_HANDLED;
}
需要注意的是,GT911的坐标寄存器采用小端格式存储,X坐标位于0x8150-0x8151,Y坐标位于0x8152-0x8153。对于多点触控,每个触点的坐标间隔8个寄存器。
当驱动无法正常通信时,可以按照以下步骤排查:
检查设备树:
硬件测量:
软件调试:
bash复制# 查看I2C总线探测情况
dmesg | grep i2c
# 使用i2c-tools手动测试
i2cdetect -y 2
i2cget -y 2 0x5d 0x8140 b
我曾经遇到过一个棘手的问题:驱动在加载时能正常通信,但运行一段时间后I2C就会卡死。后来发现是中断处理函数中未及时清除状态位,导致中断持续触发,最终造成I2C控制器死锁。
当触摸坐标出现跳点或不准时,可以考虑以下解决方案:
校准配置参数:
goodix,cfg-group0配置添加软件滤波:
c复制#define FILTER_DEPTH 3
static int gt911_filter_coord(int new_val)
{
static int buf[FILTER_DEPTH] = {0};
static int index = 0;
buf[index++] = new_val;
if (index >= FILTER_DEPTH)
index = 0;
return (buf[0] + buf[1] + buf[2]) / 3;
}
检查电源噪声:
对于需要高刷新率的应用场景,可以考虑以下优化措施:
提高I2C时钟频率:
dts复制clock-frequency = <400000>; // 400kHz
使用DMA传输:
减少中断延迟:
c复制// 在probe函数中设置中断特性
irq_set_irq_type(client->irq, IRQF_TRIGGER_FALLING | IRQF_ONESHOT);
批量读取坐标数据:
c复制u8 data[30];
gt911_read_reg(client, 0x814F, data, 30);
在为一个医疗设备项目优化触摸响应时,通过将I2C频率提升到400kHz并结合DMA传输,成功将触摸延迟从15ms降低到了5ms以内。