4. FT5x06 驱动框架:驱动入口、probe 函数、设备树解析、I2C 读写函数封装
FT5x06 系列电容触摸控制器(FT5206、FT5306、FT5406、FT5506、FT5606)在嵌入式 Linux 中通常通过 I2C 总线与主控通信。本章节深入剖析其驱动框架的四个核心模块:驱动入口与出口、probe 探测函数、设备树匹配与解析、I2C 读写封装。理解这些模块是后续进行多点触控性能调优的基础。
4.1 驱动入口与出口:module_init / module_exit
Linux 驱动通过 module_init 和 module_exit 宏向内核注册入口和出口函数。FT5x06 驱动采用标准的 I2C 驱动模型,入口函数执行 I2C 驱动注册,出口函数执行注销。
/* 驱动入口函数 */
static int __init ft5x06_ts_init(void)
{
int ret;
ret = i2c_add_driver(&ft5x06_ts_driver);
if (ret)
pr_err("FT5x06: Failed to register I2C driver, err=%d\n", ret);
else
pr_info("FT5x06: Touchscreen driver initialized\n");
return ret;
}
/* 驱动出口函数 */
static void __exit ft5x06_ts_exit(void)
{
i2c_del_driver(&ft5x06_ts_driver);
pr_info("FT5x06: Touchscreen driver removed\n");
}
module_init(ft5x06_ts_init);
module_exit(ft5x06_ts_exit);
MODULE_AUTHOR("Embedded Touch Expert");
MODULE_DESCRIPTION("FT5x06 Capacitive Touchscreen Driver");
MODULE_LICENSE("GPL v2");
i2c_add_driver() 会将驱动挂接到 I2C 核心的总线驱动链表上。当 I2C 核心检测到与 id_table 匹配的设备时,自动调用 probe 函数。出口函数必须确保所有资源已释放,否则可能导致内核 panic。
4.2 I2C 驱动结构体与 id_table
驱动核心结构体 struct i2c_driver 包含 probe、remove、id_table 等关键成员。id_table 用于设备树或 ACPI 匹配,是驱动与硬件绑定的桥梁。
| 成员 | 类型 | 说明 |
|---|---|---|
| probe | int (*probe)(struct i2c_client *, const struct i2c_device_id *) | 设备匹配成功后调用,完成硬件初始化、中断注册、输入设备注册 |
| remove | int (*remove)(struct i2c_client *) | 设备移除或驱动卸载时调用,释放资源 |
| id_table | const struct i2c_device_id * | 传统 I2C 设备 ID 匹配表,与设备树 compatible 属性配合使用 |
| driver | struct device_driver | 内嵌通用驱动结构,包含 name、of_match_table、pm 等 |
/* I2C 设备 ID 表 */
static const struct i2c_device_id ft5x06_ts_id[] = {
{ "ft5x06", 0 },
{ "ft5206", 0 },
{ "ft5306", 0 },
{ "ft5406", 0 },
{ "ft5506", 0 },
{ "ft5606", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ft5x06_ts_id);
/* 设备树匹配表 */
static const struct of_device_id ft5x06_of_match[] = {
{ .compatible = "focaltech,ft5x06", },
{ .compatible = "focaltech,ft5206", },
{ .compatible = "focaltech,ft5306", },
{ .compatible = "focaltech,ft5406", },
{ .compatible = "focaltech,ft5506", },
{ .compatible = "focaltech,ft5606", },
{ }
};
MODULE_DEVICE_TABLE(of, ft5x06_of_match);
/* I2C 驱动结构体 */
static struct i2c_driver ft5x06_ts_driver = {
.driver = {
.name = "ft5x06_ts",
.of_match_table = ft5x06_of_match,
.pm = &ft5x06_ts_pm_ops, /* 电源管理操作 */
},
.probe = ft5x06_ts_probe,
.remove = ft5x06_ts_remove,
.id_table = ft5x06_ts_id,
};
4.3 probe 函数:驱动初始化的核心战场
probe 函数是驱动与硬件第一次握手的场所。FT5x06 的 probe 流程包含:设备树属性解析、I2C 客户端初始化、硬件复位、固件版本校验、中断申请、输入设备注册、多点触控协议配置。
static int ft5x06_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct ft5x06_ts_data *ts;
struct input_dev *input;
int ret;
/* 1. 分配私有数据结构体 */
ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
if (!ts)
return -ENOMEM;
ts->client = client;
i2c_set_clientdata(client, ts);
/* 2. 设备树解析:获取 GPIO、最大触摸点数、交换坐标等参数 */
ret = ft5x06_parse_dt(&client->dev, ts);
if (ret)
return ret;
/* 3. 硬件复位:拉低复位引脚,等待芯片稳定 */
gpiod_set_value_cansleep(ts->reset_gpio, 0);
msleep(5);
gpiod_set_value_cansleep(ts->reset_gpio, 1);
msleep(300);
/* 4. 读取芯片版本,验证通信正常 */
ret = ft5x06_read_version(ts);
if (ret) {
dev_err(&client->dev, "Failed to read chip version\n");
return ret;
}
/* 5. 申请输入设备 */
input = devm_input_allocate_device(&client->dev);
if (!input)
return -ENOMEM;
input->name = "FT5x06 Touchscreen";
input->id.bustype = BUS_I2C;
input->dev.parent = &client->dev;
/* 6. 设置多点触控协议:MT Protocol B */
input_set_abs_params(input, ABS_MT_POSITION_X, 0, ts->max_x, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ts->max_y, 0, 0);
input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
input_set_abs_params(input, ABS_MT_TRACKING_ID, 0, ts->max_touch_num, 0, 0);
ts->input_dev = input;
/* 7. 申请中断 */
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, ft5x06_ts_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"ft5x06_ts", ts);
if (ret) {
dev_err(&client->dev, "Failed to request IRQ %d\n", client->irq);
return ret;
}
/* 8. 注册输入设备 */
ret = input_register_device(input);
if (ret) {
dev_err(&client->dev, "Failed to register input device\n");
return ret;
}
dev_info(&client->dev, "FT5x06 probe success, ver=%d, max_touch=%d\n",
ts->chip_ver, ts->max_touch_num);
return 0;
}
4.4 设备树解析:从 DTS 到驱动参数
设备树(Device Tree)是嵌入式 Linux 描述硬件拓扑的标准方式。FT5x06 驱动通过 of_property_read_* 系列 API 从设备树节点中提取配置参数。典型的设备树节点如下:
/* 设备树节点示例 */
&i2c2 {
ft5x06_ts@38 {
compatible = "focaltech,ft5x06";
reg = <0x38>;
interrupt-parent = <&gpio1>;
interrupts = <13 IRQ_TYPE_EDGE_FALLING>;
reset-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
touchscreen-size-x = <800>;
touchscreen-size-y = <480>;
touchscreen-max-touch-points = <5>;
focaltech,swap-xy;
focaltech,invert-x;
focaltech,invert-y;
};
};
驱动解析函数实现:
static int ft5x06_parse_dt(struct device *dev, struct ft5x06_ts_data *ts)
{
struct device_node *np = dev->of_node;
u32 val;
int ret;
if (!np)
return -ENODEV;
/* 解析复位 GPIO */
ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(ts->reset_gpio)) {
dev_err(dev, "Failed to get reset GPIO\n");
return PTR_ERR(ts->reset_gpio);
}
/* 解析触摸屏尺寸 */
ret = of_property_read_u32(np, "touchscreen-size-x", &val);
ts->max_x = ret ? DEFAULT_MAX_X : val;
ret = of_property_read_u32(np, "touchscreen-size-y", &val);
ts->max_y = ret ? DEFAULT_MAX_Y : val;
/* 解析最大触摸点数 */
ret = of_property_read_u32(np, "touchscreen-max-touch-points", &val);
ts->max_touch_num = ret ? DEFAULT_MAX_TOUCH : val;
if (ts->max_touch_num > FT5X06_MAX_TOUCH_SUPPORT)
ts->max_touch_num = FT5X06_MAX_TOUCH_SUPPORT;
/* 解析坐标变换标志 */
ts->swap_xy = of_property_read_bool(np, "focaltech,swap-xy");
ts->invert_x = of_property_read_bool(np, "focaltech,invert-x");
ts->invert_y = of_property_read_bool(np, "focaltech,invert-y");
dev_info(dev, "DT parsed: %dx%d, max_touch=%d, swap=%d, inv_x=%d, inv_y=%d\n",
ts->max_x, ts->max_y, ts->max_touch_num,
ts->swap_xy, ts->invert_x, ts->invert_y);
return 0;
}
dtc -I dtb -O dts 反编译 DTB 文件,检查节点属性是否正确传递。在驱动中可以通过 dev_dbg 打印解析结果,配合 dyndbg 动态调试开关查看。
4.5 I2C 读写函数封装:可靠通信的基石
FT5x06 的寄存器访问采用标准的 I2C 读写序列。驱动需要封装可靠的读写函数,处理 I2C 传输错误、重试机制和寄存器地址对齐。FT5x06 的寄存器地址为 8 位宽,数据为 8 位或 16 位。
| 函数 | 功能 | 参数 | 返回值 |
|---|---|---|---|
| ft5x06_i2c_read_reg | 读取单个 8 位寄存器 | client, reg_addr, *data | 0 成功,负值错误码 |
| ft5x06_i2c_write_reg | 写入单个 8 位寄存器 | client, reg_addr, data | 0 成功,负值错误码 |
| ft5x06_i2c_read_block | 连续读取多个寄存器 | client, reg_addr, len, *buf | 0 成功,负值错误码 |
| ft5x06_i2c_write_block | 连续写入多个寄存器 | client, reg_addr, len, *buf | 0 成功,负值错误码 |
/* 单字节读:先写寄存器地址,再读数据 */
static int ft5x06_i2c_read_reg(struct i2c_client *client,
u8 reg_addr, u8 *data)
{
struct i2c_msg msg[2];
int ret;
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = ®_addr;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = 1;
msg[1].buf = data;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret == 2)
return 0;
else if (ret < 0)
return ret;
else
return -EIO;
}
/* 单字节写:先写寄存器地址,再写数据 */
static int ft5x06_i2c_write_reg(struct i2c_client *client,
u8 reg_addr, u8 data)
{
u8 buf[2] = { reg_addr, data };
int ret;
ret = i2c_master_send(client, buf, 2);
if (ret == 2)
return 0;
else if (ret < 0)
return ret;
else
return -EIO;
}
/* 块读:从 reg_addr 开始连续读取 len 字节 */
static int ft5x06_i2c_read_block(struct i2c_client *client,
u8 reg_addr, int len, u8 *buf)
{
struct i2c_msg msg[2];
int ret;
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = ®_addr;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = len;
msg[1].buf = buf;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret == 2)
return 0;
else if (ret < 0)
return ret;
else
return -EIO;
}
/* 带重试机制的读封装 */
static int ft5x06_i2c_read_retry(struct i2c_client *client,
u8 reg_addr, u8 *data, int retries)
{
int ret;
int i;
for (i = 0; i < retries; i++) {
ret = ft5x06_i2c_read_reg(client, reg_addr, data);
if (ret == 0)
return 0;
msleep(1);
}
dev_err(&client->dev, "I2C read retry failed after %d attempts\n", retries);
return ret;
}
i2c_transfer 而非多次单字节读,以减少总线开销。
4.6 驱动框架整体架构图
下图展示了 FT5x06 驱动从内核模块加载到输入事件上报的完整架构流程:
4.7 驱动框架关键数据结构
私有数据结构体 ft5x06_ts_data 贯穿整个驱动生命周期,承载设备树参数、硬件状态、输入设备和中断上下文信息。
/* FT5x06 驱动私有数据结构体 */
struct ft5x06_ts_data {
struct i2c_client *client; /* I2C 客户端指针 */
struct input_dev *input_dev; /* 输入设备指针 */
struct gpio_desc *reset_gpio; /* 复位 GPIO 描述符 */
struct gpio_desc *irq_gpio; /* 中断 GPIO 描述符(可选) */
u16 max_x; /* X 轴最大分辨率 */
u16 max_y; /* Y 轴最大分辨率 */
u8 max_touch_num; /* 最大同时触摸点数 */
u8 chip_ver; /* 芯片版本号 */
bool swap_xy; /* 是否交换 XY 坐标 */
bool invert_x; /* 是否翻转 X 轴 */
bool invert_y; /* 是否翻转 Y 轴 */
/* 触摸点缓存,用于多点触控协议 */
struct ft5x06_touch_point points[FT5X06_MAX_TOUCH_SUPPORT];
/* 调试与统计 */
atomic_t irq_count; /* 中断计数 */
struct mutex lock; /* 并发访问锁 */
};
- 分层清晰: 驱动入口 → I2C 核心 → probe → 设备树 → 硬件操作 → 输入子系统,每层职责单一。
- 错误回滚: probe 中每个步骤失败时,必须释放之前已申请的所有资源,避免内存泄漏。
- 可配置性: 设备树属性使驱动无需重新编译即可适配不同硬件设计(如屏幕旋转、分辨率变化)。
- 鲁棒性: I2C 读写封装加入重试机制,应对触摸芯片在扫描期间的时钟拉伸或总线噪声。
4.8 本章小结
FT5x06 驱动框架遵循标准的 Linux I2C 驱动模型,核心流程为:module_init → i2c_add_driver → 设备树匹配 → probe → 设备树解析 → 硬件初始化 → 中断注册 → 输入设备注册。本章详细拆解了每个环节的实现要点,特别是设备树解析的灵活性和 I2C 读写封装的可靠性设计。这些基础框架代码将直接复用到后续的 GT911 驱动适配和性能调优章节中。