gt9xx
gtp_init
- adapter = i2c_get_adapter(i2c_bus_num); adapter是一种结构,它用来标识一个物理总线,连同访问它必要的算法
- i2c_dev = i2c_new_client_device(adapter, >p_device_info); 向适配器里添加一个设备,i2c_client 代表一个连接到i2c_bus的从设备,i2c_client的信息通常通过 gtp_device_info 来填充.扮演的角色就是一个device
- ret = i2c_add_driver(&goodix_ts_driver); driver match device
gtp_probe
- i2c_check_functionality(client->adapter, I2C_FUNC_I2C) 检测I2C总线功能;
- devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL); 分配内存,实例化数据结构;
- devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); 分配内存,实例化数据结构;
- gtp_parse_dt(&client->dev, pdata); 解析设备树;
- i2c_set_clientdata(client, ts); 将驱动程序自定义的设备结构体指针,绑定并存储到 i2c_client 结构体中,可以将它理解为给这个 I2C 设备挂载一个“私有的数据背包;反之使用i2c_get_clientdata 从 struct i2c_client *client 读回data;
- regulator_get(&ts->client->dev, "vdd_ana"); 获取一个电压/电流调节器。在传统驱动中,可能会直接操作某个 GPIO 或 PMIC 寄存器来控制电源。而 regulator_get 的引入,将硬件的具体实现(这个电源是 GPIO 控制的?还是 PMIC 的某个 LDO?电压范围多少?)移到了设备树或板级配置中,驱动代码只需要知道“我需要一个叫 'vdd' 的电源”,具体的硬件细节由 kernel 的 regulator 框架去解析;
- regulator_enable(ts->vdd_ana); 使能电源,另外还有API regulator_set_voltage() 设置输出电压(uV);
- clear_bit(POWER_OFF_MODE, &ts->flags); & set_bit(POWER_OFF_MODE, &ts->flags); 对ts->flags的第POWER_OFF_MODE位置位或者清零,原子操作函数,用于将指定位清除为 0(即“清零”或“置 0”),是一个位操作函数,专门用于按位(bit-level)地修改内存中的值,并且保证操作的原子性——即使在多核 CPU 或多线程环境中,该操作也不会被中断或并发干扰;另外API test_bit 等待操作完成;
- gtp_pinctrl_init(ts); GPIO申请相关;
- gtp_request_io_port(ts); GPIO申请相关;
- gpio_direction_output(ts->pdata->rst_gpio, 0); GPIO设置相关;
- usleep_range(2000, 3000); 毫秒级(实际是微秒级)睡眠函数,用于让当前执行的线程主动让出 CPU,进入睡眠状态一段指定的时间,与简单的 udelay(忙等待)不同,usleep_range 会调度出去,让 CPU 可以执行其他任务,而不是空转浪费 CPU 资源,不精确但高效:实际睡眠时间在 [min, max] 之间,由调度器决定,不忙等:让出 CPU,其他任务可以运行,节能:CPU 可以进入低功耗状态;
Linux 内核提供了多个延迟函数,适用于不同场景:
| 函数 | 精度 | 是否让出CPU | 适用场景 | 最小延迟 |
|---|---|---|---|---|
udelay() |
微秒级 | ❌ 忙等待 | 极短延迟(<10μs)、原子上下文 | 1μs+ |
mdelay() |
毫秒级 | ❌ 忙等待 | 短延迟但无法睡眠(如中断) | 1ms+ |
usleep_range() |
微秒级 | ✅ 睡眠 | 10μs - 20ms,进程上下文 | ~10μs |
msleep() |
毫秒级 | ✅ 睡眠 | 20ms+,精度要求不高 | 20ms+ |
msleep_interruptible() |
毫秒级 | ✅ 可被中断 | 可被信号中断的长延迟 | 20ms+ |
- gtp_request_input_dev(ts); 申请输入设备
- mutex_init(&ts->lock); 互斥锁
- gtp_request_irq(ts); 申请中断
- gtp_work_control_enable(ts, false); 对应clear_bit
- gtp_create_file(struct goodix_ts_data *ts) 在/proc/文件系统里 创建文件 GT91XX_CONFIG_PROC_FILE
- gtp_work_control_enable(ts, true); 对应set_bit
gtp_request_input_dev
- ts->client->irq = gpio_to_irq(ts->pdata->irq_gpio); 获取gpio对应的中断号
- ret = request_threaded_irq(ts->client->irq, NULL,
gtp_irq_handler,
ts->pdata->irq_flags | IRQF_ONESHOT,
ts->client->name,
ts); 根据中断号申请中断,使用的是线程化中断(Threaded IRQ),这是 Linux 内核中实现中断下半部的一种现代方法。上半部在哪里?为 NULL,使用内核默认的 irq_default_primary_handler,gtp_irq_handler是下半部线程函数,在线程上下文中执行,IRQF_ONESHOT确保中断线在硬件中断处理完成后才重新使能防止中断处理期间再次触发中断,避免中断风暴。优势是下半部可以睡眠,适合 I2C/SPI 触摸屏等慢速设备。
线程化中断处理流程
硬件中断触发
↓
默认上半部 (irq_default_primary_handler)
- 立即返回 IRQ_WAKE_THREAD
- 唤醒下半部线程
↓
下半部线程 (gtp_irq_handler) 被调度执行
- 可以调用 msleep()、mutex_lock()
- 进行 I2C/SPI 读取(可能睡眠)
- 上报输入事件
- 清除硬件中断状态
↓
中断处理完成
中断下半部实现方式对比
| 类型 | 上下文 | 特点 | 适用场景 |
|---|---|---|---|
| 软中断 | 软中断上下文 | 性能最高,但不能睡眠,复杂 | 网络、块设备等高性能场景 |
| Tasklet | 软中断上下文 | 简单易用,但不能睡眠 | 大多数外设驱动 |
| 工作队列 | 进程上下文 | 可以睡眠,但开销较大 | 需要睡眠的操作 |
| 线程化中断 | 内核线程上下文 | 可以睡眠,专为每个中断独立线程 | I2C/SPI 等慢速设备,触摸屏 |
- hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); 初始化一个定时器
- ts->timer.function = gtp_timer_handler; 设置定时器函数
- hrtimer_start(&ts->timer, ktime_set(0, (GTP_POLL_TIME + 6) * 1000000), HRTIMER_MODE_REL); 启动定时器
gtp_request_irq
- ts->input_dev = input_allocate_device(); 分派一个输入设备
- ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); 设置输入子系统中一个输入设备(input device)的事件类型支持位图,告诉内核:这个输入设备支持哪些大类的输入事件;evbit[0]:事件类型位图数组的第一个元素(共 EV_MAX/8+1 个),BIT_MASK(x):宏,将 1 左移 x 位,即 (1 << x);EV_SYN 同步事件,用于分隔数据包;EV_KEY 按键事件(键盘按钮、触摸按键等);EV_ABS 绝对坐标事件(触摸屏位置、加速度计等),可以等效于:
__set_bit(EV_SYN, input_dev->evbit); // 同步事件
__set_bit(EV_KEY, input_dev->evbit); // 按键事件
__set_bit(EV_ABS, input_dev->evbit); // 绝对坐标事件
两种方式来设置 evbit:BIT_MASK 与 __set_bit
Type B
- input_mt_init_slots(ts->input_dev, 16, INPUT_MT_DIRECT); 初始化多点触控 (Multi-Touch, MT) 设备的核心函数,为指定的输入设备 (ts->input_dev) 分配并初始化 16 个 MT 触摸槽位 (slots),将设备标记为 INPUT_MT_DIRECT 类型,表明这是一个直接输入设备
Type A 已经过时
- __set_bit(INPUT_PROP_DIRECT, ts->input_dev->propbit); INPUT_PROP_DIRECT:这是一个输入设备属性标志,表示该设备是直接输入设备,propbit:输入设备结构体中的属性位图,用于记录设备的特殊属性。这个设备的输入坐标直接映射到屏幕坐标,无需进行转换或缩放。
__set_bit(BTN_TOUCH, ts->input_dev->keybit); 设置输入设备的按键能力位(key bit),BTN_TOUCH:这是一个按键事件代码,表示"触摸"事件,keybit:输入设备结构体中的按键位图,用于记录设备支持哪些按键/按钮。声明该设备能够产生触摸事件(手指按下/抬起)。当触摸屏被触摸时,驱动程序会发送 BTN_TOUCH 值为 1 的事件;当手指离开时,发送值为 0 的事件。
__set_bit vs set_bit 的区别
| 函数 | 特性 | 使用场景 |
|---|---|---|
__set_bit |
非原子操作,不进行内存屏障或中断锁定 | 设备初始化阶段(单线程,无并发访问) |
set_bit |
原子操作,有内存屏障保护 | 运行时,可能被中断或并发访问 |
在驱动的 probe 或初始化函数中使用 __set_bit 是安全的,因为此时设备还未注册到系统,不会有并发访问。
Type A vs. Type B 对比
| 特性 | Type A (匿名触点) | Type B (可识别触点) |
|---|---|---|
| 硬件要求 | 无需硬件追踪,仅上报原始触点数据 | 需要硬件能为每个触点提供唯一标识符 (Tracking ID) |
| 数据上报 | 全量上报:每一帧都上报所有触点的完整状态 | 增量上报:只上报发生变化的触点数据 |
| 驱动职责 | 简单:只管把原始数据一股脑发出去 | 复杂:需维护每个触点的“槽位”(Slot) 和状态 |
| 用户空间 | 繁重:需自己完成手指的追踪、匹配等所有复杂逻辑 | 轻松:直接拿到带ID的、明确的数据流 |
| 内核API | input_mt_sync() 发送 SYN_MT_REPORT 分隔触点 |
input_mt_slot() 配合 input_mt_report_slot_state() 操作槽位 |
| 事件流特点 | 事件流是顺序的触点列表,用户空间需自行区分 | 通过 ABS_MT_SLOT 切换当前操作的触点,用 ABS_MT_TRACKING_ID (≥0有效,-1表示抬起) 管理生命周期 |
上报示例
Type A 上报两个触点,每一次都要完整上报所有触点,并用 SYN_MT_REPORT 隔开
# 触点 A 的数据
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
SYN_MT_REPORT # 分隔符,准备下一个触点
# 触点 B 的数据
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_MT_REPORT # 分隔符
SYN_REPORT # 同步事件,表示一帧结束
Type A 上报两个触点,每个触点有固定“槽位”(0,1...),只需要在创建时指定ID,之后移动时无需再发ID
# 第一帧,创建两个触点
ABS_MT_SLOT 0
ABS_MT_TRACKING_ID 45 # 为槽位0分配ID 45,新触点
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 46 # 为槽位1分配ID 46,新触点
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_REPORT
# 第二帧,只有第一个触点移动了
ABS_MT_SLOT 0 # 先切换到要更新的槽位
ABS_MT_POSITION_X x[0]' # 上报变化的数据
SYN_REPORT # 此时第二个触点的状态由内核自动保持
- input_set_capability 声明你的输入设备能够产生或接收哪一种特定的事件;
- input_set_abs_params 专门为绝对坐标轴(EV_ABS 类型事件)设置详细物理参数;
- input_register_device 将一个已初始化完成的输入设备注册到系统中,使其可以被用户空间和其他内核模块使用;
- kthread_run 展开
#define kthread_run(threadfn, data, namefmt, ...) \
({ \
struct task_struct *__k \
= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
if (!IS_ERR(__k)) \
wake_up_process(__k); \
__k; \
})
gt9xx_update.c
- request_firmware 是 Linux 内核中用于从用户空间请求固件的核心函数。它允许内核驱动在运行时从文件系统中加载固件二进制文件到内核内存中,而不必将固件数据硬编码在驱动代码里。
posted on 2026-06-13 08:45 f1engmin11 阅读(7) 评论(0) 收藏 举报
浙公网安备 33010602011771号