关键数据结构
1. 触摸控制器寄存器结构
struct qemu_ts_con {
volatile unsigned int pressure; // 压力/触摸状态
volatile unsigned int x; // X 坐标
volatile unsigned int y; // Y 坐标
volatile unsigned int clean; // 清理标志
};
-
volatile
确保编译器不会优化对这些寄存器的访问 -
这个结构体映射到硬件的内存区域
2. 全局变量
static struct input_dev *g_input_dev; // 输入设备
static int g_irq; // 中断号
static struct qemu_ts_con *ts_con; // 映射的硬件寄存器
struct timer_list ts_timer; // 定时器
核心机制:中断 + 定时器
1. 定时器回调函数
static void ts_irq_timer(unsigned long _data)
{
if (ts_con->pressure) // 如果仍然处于按压状态
{
// 持续上报坐标
input_event(g_input_dev, EV_ABS, ABS_X, ts_con->x);
input_event(g_input_dev, EV_ABS, ABS_Y, ts_con->y);
input_sync(g_input_dev);
// 重新设置定时器,实现轮询
mod_timer(&ts_timer, jiffies + msecs_to_jiffies(TOUCHSCREEN_POLL_TIME_MS));
}
}
-
在触摸持续期间,定时器每 10ms 触发一次
-
持续上报触摸坐标,实现触摸移动的跟踪
为什么需要定时器轮询?
触摸移动的连续性:
-
中断只在触摸开始和结束时触发
-
但在触摸过程中,手指可能持续移动
-
定时器轮询可以捕获这些连续的坐标变化
时间间隔选择:10ms
TOUCHSCREEN_POLL_TIME_MS 10
的含义:
-
100Hz 采样率:每秒采样 100 次坐标
-
平衡性能与流畅度:
-
太频繁:CPU 占用率高
-
太稀疏:触摸轨迹不连贯
-
-
典型触摸屏的采样率在 60-120Hz 之间
内核定时器机制
定时器初始化:
setup_timer(&ts_timer, ts_irq_timer, (unsigned long)NULL);
时间计算:
jiffies + msecs_to_jiffies(10)
-
jiffies
: 当前时间(从系统启动开始的节拍数) -
msecs_to_jiffies(10)
: 将 10ms 转换为对应的节拍数 -
结果:定时器在"当前时间 + 10ms"后再次触发
与中断处理函数的协作
中断函数中的定时器启动:
// 在 input_dev_demo_isr 中:
if (ts_con->pressure) // 触摸按下
{
// ... 上报初始坐标 ...
/* start timer */
mod_timer(&ts_timer, jiffies + msecs_to_jiffies(TOUCHSCREEN_POLL_TIME_MS));
}
2. 中断服务程序
static irqreturn_t input_dev_demo_isr(int irq, void *dev_id)
{
if (ts_con->pressure) // 触摸按下
{
// 上报初始触摸坐标
input_event(g_input_dev, EV_ABS, ABS_X, ts_con->x);
input_event(g_input_dev, EV_ABS, ABS_Y, ts_con->y);
input_event(g_input_dev, EV_KEY, BTN_TOUCH, 1); // 触摸开始
input_sync(g_input_dev);
// 启动定时器进行持续轮询
mod_timer(&ts_timer, jiffies + msecs_to_jiffies(TOUCHSCREEN_POLL_TIME_MS));
}
else // 触摸释放
{
input_event(g_input_dev, EV_KEY, BTN_TOUCH, 0); // 触摸结束
input_sync(g_input_dev);
// 注意:这里应该停止定时器,但代码中缺失了 del_timer
}
return IRQ_HANDLED;
}
驱动probe函数详解
1. 设备信息获取
// 从设备树获取 GPIO
gpio = of_get_gpio(pdev->dev.of_node, 0);
// 获取内存映射资源
io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ts_con = ioremap(io->start, io->end - io->start + 1);
2. 输入设备设置
/* set 1: which type event ? */
__set_bit(EV_KEY, g_input_dev->evbit); // 按键事件
__set_bit(EV_ABS, g_input_dev->evbit); // 绝对坐标事件
__set_bit(INPUT_PROP_DIRECT, g_input_dev->propbit); // 直接输入设备
/* set 2: which event ? */
__set_bit(BTN_TOUCH, g_input_dev->keybit); // 触摸按键
__set_bit(ABS_X, g_input_dev->absbit); // X 坐标
__set_bit(ABS_Y, g_input_dev->absbit); // Y 坐标
/* set 3: event params ? */
input_set_abs_params(g_input_dev, ABS_X, 0, 0xffff, 0, 0);
input_set_abs_params(g_input_dev, ABS_Y, 0, 0xffff, 0, 0);
__set_bit(INPUT_PROP_DIRECT, g_input_dev->propbit);
用于设置输入设备的属性 ,表明这是一个直接输入设备。
__set_bit
宏:
// 内核中的位操作宏
#define __set_bit(nr, addr) set_bit(nr, addr)
-
作用:将位图中的特定位设置为 1
-
nr
:要设置的位号(这里是INPUT_PROP_DIRECT
) -
addr
:位图地址(这里是g_input_dev->propbit
)
参数说明:
-
INPUT_PROP_DIRECT
:输入设备属性常量,值为 0 -
g_input_dev->propbit
:输入设备的属性位图
INPUT_PROP_DIRECT 的含义
直接输入设备(Direct Input Device):
-
定义:用户直接在设备表面上操作,操作位置与光标位置直接对应
-
典型例子:
-
触摸屏(Touchscreen)
-
数字化仪(Graphics Tablet)
-
触摸板(Touchpad)
-
3. 硬件初始化
// 内存映射
io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
ts_con = ioremap(io->start, io->end - io->start + 1);
// GPIO 转中断号,并注册中断
g_irq = gpio_to_irq(gpio);
error = request_irq(g_irq, input_dev_demo_isr,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
"input_dev_demo_irq", NULL);
// 初始化定时器
setup_timer(&ts_timer, ts_irq_timer, (unsigned long)NULL);
工作流程
1. 触摸按下流程
GPIO 中断(上升沿) → 中断处理函数 → 上报触摸开始 → 启动定时器 → 定时器轮询坐标
2. 触摸移动流程
定时器每10ms触发 → 读取坐标 → 上报新位置 → 重置定时器
3. 触摸释放流程
GPIO 中断(下降沿) → 中断处理函数 → 上报触摸结束 → 停止定时器(代码中缺失)
设备树配置
这个驱动期望的设备树节点:
input_dev_demo: input_dev_demo {
compatible = "100ask,input_dev_demo";
reg = <0x12345000 0x1000>; // 内存映射区域
gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>; // GPIO 引脚
interrupt-parent = <&gpio1>;
interrupts = <1 IRQ_TYPE_EDGE_BOTH>; // 双边沿触发
};