第3章:I2C触摸屏驱动移植
好,咱们今天聊聊触摸屏驱动移植。说实话,I2C触摸屏是我在AOSP移植工作中打交道最多的外设之一。你想想看,手机、平板、车载中控,哪个设备离得开触摸?而这里面,I2C接口的触摸屏又占了绝大多数。
我个人习惯把触摸屏驱动移植分成三个层次:I2C设备驱动模型、初始化序列、寄存器读写与固件升级。咱们一层层剥开来看。
3.1 I2C设备驱动模型:Linux下的标准玩法
Linux内核里,I2C设备驱动有一套固定的框架。说白了,就是三步走:注册驱动、匹配设备、绑定操作。
先看一个典型的驱动结构体:
static const struct i2c_device_id ft5x06_ts_id[] = {
{ "ft5x06", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ft5x06_ts_id);
static const struct of_device_id ft5x06_of_match[] = {
{ .compatible = "edt,ft5x06", },
{ }
};
MODULE_DEVICE_TABLE(of, ft5x06_of_match);
static struct i2c_driver ft5x06_ts_driver = {
.driver = {
.name = "ft5x06",
.of_match_table = ft5x06_of_match,
},
.probe = ft5x06_ts_probe,
.remove = ft5x06_ts_remove,
.id_table = ft5x06_ts_id,
};
module_i2c_driver(ft5x06_ts_driver);
这里有两个关键点:
- of_match_table:用于设备树匹配。你在dts里写
compatible = "edt,ft5x06",内核就能找到这个驱动。 - id_table:传统I2C设备ID匹配,用于非设备树场景。
probe函数是驱动的入口。它负责:
- 分配并初始化触摸屏数据结构
- 设置I2C通信参数(速率、地址等)
- 注册输入设备(input_dev)
- 注册中断处理函数
- 初始化触摸屏硬件(调用初始化序列)
3.2 触摸屏初始化序列:上电后的第一件事
触摸屏上电后,不是马上就能用的。它需要一套初始化序列。我遇到过不少问题,都是初始化顺序不对导致的。
典型的初始化流程是这样的:
static int ft5x06_ts_init(struct ft5x06_ts_data *data)
{
int ret;
/* 1. 软复位 */
ret = ft5x06_i2c_write_reg(data, REG_SOFT_RESET, 0x01);
if (ret < 0)
return ret;
msleep(20); // 等待复位完成
/* 2. 读取芯片ID,确认通信正常 */
ret = ft5x06_i2c_read_reg(data, REG_CHIP_ID, &chip_id);
if (ret < 0 || chip_id != EXPECTED_ID) {
dev_err(data->dev, "chip id mismatch\n");
return -ENODEV;
}
/* 3. 配置工作模式 */
ft5x06_i2c_write_reg(data, REG_MODE, MODE_ACTIVE);
/* 4. 设置触摸阈值 */
ft5x06_i2c_write_reg(data, REG_THRESHOLD, 0x28);
/* 5. 使能中断 */
ft5x06_i2c_write_reg(data, REG_INT_ENABLE, 0x01);
return 0;
}
这里有几个坑,我一个个说:
- 延时不能省:软复位后,芯片需要时间稳定。我见过有人把msleep(20)写成msleep(1),结果10次里有3次初始化失败。
- 芯片ID校验:这是确认I2C通信是否正常的关键步骤。如果读不到正确的ID,要么是地址错了,要么是硬件没上电。
- 阈值设置:不同尺寸的屏,触摸阈值不一样。7寸屏和10寸屏的参数完全不同。
3.3 寄存器读写:I2C通信的核心
I2C触摸屏的寄存器读写,说白了就是发两个字节:寄存器地址 + 数据。但不同厂商的协议略有差异。
以FT5x06为例,它的读写函数是这样的:
static int ft5x06_i2c_read_reg(struct ft5x06_ts_data *data,
u8 reg, u8 *value)
{
struct i2c_msg msg[2];
int ret;
/* 写寄存器地址 */
msg[0].addr = data->client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = ®
/* 读数据 */
msg[1].addr = data->client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = 1;
msg[1].buf = value;
ret = i2c_transfer(data->client->adapter, msg, 2);
if (ret == 2)
return 0;
else
return -EIO;
}
static int ft5x06_i2c_write_reg(struct ft5x06_ts_data *data,
u8 reg, u8 value)
{
u8 buf[2] = { reg, value };
int ret;
ret = i2c_master_send(data->client, buf, 2);
if (ret == 2)
return 0;
else
return -EIO;
}
这里有个细节:读操作用了重复起始条件(即msg[0]和msg[1]组合)。写操作则直接发送地址+数据。为什么?因为读操作需要先告诉芯片要读哪个寄存器,然后再接收数据。这两个操作之间不能有停止信号。
3.4 固件升级:给触摸屏“刷系统”
触摸屏也有固件,而且需要升级。为什么?因为触摸算法在持续优化,或者出厂时固件有bug。
固件升级的流程大致如下:
- 进入升级模式(通常通过写特定寄存器)
- 擦除Flash
- 按块写入固件数据
- 校验CRC或MD5
- 复位芯片,退出升级模式
我写过一个简单的固件升级函数:
static int ft5x06_firmware_update(struct ft5x06_ts_data *data,
const u8 *fw_data, size_t fw_size)
{
int ret, i;
size_t block_size = 64; // 每块64字节
/* 1. 进入升级模式 */
ft5x06_i2c_write_reg(data, REG_FW_MODE, 0xAA);
/* 2. 擦除 */
ft5x06_i2c_write_reg(data, REG_FW_ERASE, 0x01);
msleep(500); // 擦除需要时间
/* 3. 逐块写入 */
for (i = 0; i < fw_size; i += block_size) {
ret = ft5x06_i2c_write_block(data,
REG_FW_ADDR + i,
&fw_data[i],
min(block_size, fw_size - i));
if (ret < 0) {
dev_err(data->dev, "fw write failed at %d\n", i);
return ret;
}
msleep(10);
}
/* 4. 校验 */
ret = ft5x06_verify_fw(data, fw_data, fw_size);
if (ret < 0) {
dev_err(data->dev, "fw verification failed\n");
return ret;
}
/* 5. 退出升级模式 */
ft5x06_i2c_write_reg(data, REG_FW_MODE, 0x00);
return 0;
}
另外,固件文件通常以.bin或.iic格式提供。我建议把固件放在/lib/firmware/目录下,通过request_firmware()接口加载。这样驱动和固件分离,升级固件时只需要替换文件,不用重新编译内核。
3.5 避坑指南:我踩过的那些雷
做触摸屏驱动移植这些年,我总结了几条血泪教训:
- I2C地址别搞错:7位地址和8位地址的区别。很多芯片手册给的是7位地址,但Linux驱动里用的是8位(左移一位)。我见过有人直接拿7位地址去注册,结果设备都找不到。
- 中断触发方式:触摸屏通常用边沿触发(上升沿或下降沿)。但有些芯片要求电平触发。配错了,要么中断风暴,要么没中断。
- 电源管理:触摸屏的休眠和唤醒时序很重要。有些芯片休眠后需要重新初始化,有些则不需要。这个一定要看数据手册。
- 多点触摸协议:Linux内核支持两种多点触摸协议:Type A(无状态)和Type B(有状态)。现在主流都用Type B,但有些老驱动还在用Type A。移植时要注意匹配。
i2cdetect命令扫描总线,确认设备地址。再用i2cget和i2cset手动读写寄存器。这样能快速定位是硬件问题还是软件问题。
好了,这一章的内容就到这里。I2C触摸屏驱动移植,说难不难,说简单也不简单。关键是理解模型、吃透时序、注意细节。下一章咱们聊聊输入子系统的核心——input_dev的注册与事件上报。到时候见。