网站建设的职责seo就业指导
往期内容
本专栏往期内容:
- input子系统的框架和重要数据结构详解-CSDN博客
- input device和input handler的注册以及匹配过程解析-CSDN博客
- input device和input handler的注册以及匹配过程解析-CSDN博客
- 编写一个简单的Iinput_dev框架-CSDN博客
- GPIO按键驱动分析与使用:input_dev层-CSDN博客
I2C子系统专栏:
- 专栏地址:IIC子系统_憧憬一下的博客-CSDN博客
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有往期内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树_憧憬一下的博客-CSDN博客
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有往期内容观看顺序
目录
- 往期内容
- 前言
- 1. 驱动程序框架
- 2. 设备树示例
- 3. 驱动程序分析
- 3.1 分配/设置/注册input_dev
- 3.2 注册中断处理函数
- 3.3 中断处理函数分析
- 4.编写
前言
-
Linux 4.x内核
- Documentation\devicetree\bindings\input\touchscreen\goodix.txt
- drivers/input/touchscreen/gt9xx/gt9xx.c 📎gt9xx.c
-
设备树
- IMX6ULL:
Linux-4.9.88/arch/arm/boot/dts/100ask_imx6ull-14x14.dts
- IMX6ULL:
本节主要讲解内核中提供的gt9xx.c,来看看是如何通过input子系统来实现一个IIC协议的传输,之后在此基础上编写一个简单的input子系统的IIC驱动
1. 驱动程序框架
2. 设备树示例
&i2c2 {gt9xx@5d {compatible = "goodix,gt9xx";reg = <0x5d>;status = "okay";interrupt-parent = <&gpio1>;interrupts = <5 IRQ_TYPE_EDGE_FALLING>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_tsc_reset &pinctrl_touchscreen_int>;/*pinctrl-1 = <&pinctrl_tsc_irq>;*//*pinctrl-names = "default", "int-output-low", "int-output-high", "int-input";pinctrl-0 = <&ts_int_default>;pinctrl-1 = <&ts_int_output_low>;pinctrl-2 = <&ts_int_output_high>;pinctrl-3 = <&ts_int_input>;*/reset-gpios = <&gpio5 2 GPIO_ACTIVE_LOW>;irq-gpios = <&gpio1 5 IRQ_TYPE_EDGE_FALLING>;irq-flags = <2>; /*1:rising 2: falling*/touchscreen-max-id = <5>;touchscreen-size-x = <800>;touchscreen-size-y = <480>;touchscreen-max-w = <1024>;touchscreen-max-p = <1024>;/*touchscreen-key-map = <172>, <158>;*/ /*KEY_HOMEPAGE, KEY_BACK*/goodix,type-a-report = <0>;goodix,driver-send-cfg = <0>;goodix,create-wr-node = <1>;goodix,wakeup-with-reset = <0>;goodix,resume-in-workqueue = <0>;goodix,int-sync = <0>;goodix,swap-x2y = <0>;goodix,esd-protect = <0>;goodix,pen-suppress-finger = <0>;goodix,auto-update = <0>;goodix,auto-update-cfg = <0>;goodix,power-off-sleep = <0>;/* ...... */};
};
这段设备树(Device Tree)定义配置了一个基于 I2C 总线的 Goodix GT9xx 系列触摸屏设备,设备地址为 0x5d
。
1. 节点和基础属性
&i2c2 {gt9xx@5d {compatible = "goodix,gt9xx";reg = <0x5d>;
&i2c2
:指向 I2C 总线控制器的节点,这意味着该设备挂载在 I2C 总线 2 上。gt9xx@5d
:表示该触摸屏设备的 I2C 地址为0x5d
。compatible = "goodix,gt9xx";
:指定设备兼容字符串,匹配 Goodix 的 GT9xx 系列触摸屏设备驱动程序。reg = <0x5d>;
:设备的 I2C 地址,定义为0x5d
。
2. 中断与引脚控制配置
status = "okay";interrupt-parent = <&gpio1>;interrupts = <5 IRQ_TYPE_EDGE_FALLING>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_tsc_reset &pinctrl_touchscreen_int>;
status = "okay";
:设备状态,标记为启用。interrupt-parent = <&gpio1>;
:指定中断的父节点为gpio1
,即 GPIO 控制器 1。interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
:中断配置,使用 GPIO1 控制器的第 5 个引脚,触发模式为下降沿触发(IRQ_TYPE_EDGE_FALLING
)。pinctrl-names
和pinctrl-0
:配置引脚控制器,pinctrl-names
指定了配置的名字,pinctrl-0
则定义了相关引脚配置(pinctrl_tsc_reset
和pinctrl_touchscreen_int
),用于触摸屏重置和中断引脚初始化。
3. GPIO 和中断标志
reset-gpios = <&gpio5 2 GPIO_ACTIVE_LOW>;irq-gpios = <&gpio1 5 IRQ_TYPE_EDGE_FALLING>;irq-flags = <2>;
reset-gpios = <&gpio5 2 GPIO_ACTIVE_LOW>;
:触摸屏的复位引脚定义在gpio5
的第 2 引脚,信号低电平有效。irq-gpios = <&gpio1 5 IRQ_TYPE_EDGE_FALLING>;
:触摸屏的中断引脚配置为gpio1
控制器的第 5 引脚,设置为下降沿触发。irq-flags = <2>;
:中断触发标志,2
表示下降沿触发。
4. 触摸屏参数
touchscreen-max-id = <5>;touchscreen-size-x = <800>;touchscreen-size-y = <480>;touchscreen-max-w = <1024>;touchscreen-max-p = <1024>;
touchscreen-max-id = <5>;
:最大触摸点数为 5,表示最多支持 5 个手指的多点触控。touchscreen-size-x
和touchscreen-size-y
:触摸屏 X、Y 轴的分辨率,分别为 800 和 480。touchscreen-max-w
和touchscreen-max-p
:触控宽度和压力的最大值,均为 1024。
5. Goodix 驱动特性
goodix,type-a-report = <0>;goodix,driver-send-cfg = <0>;goodix,create-wr-node = <1>;goodix,wakeup-with-reset = <0>;goodix,resume-in-workqueue = <0>;goodix,int-sync = <0>;goodix,swap-x2y = <0>;goodix,esd-protect = <0>;goodix,pen-suppress-finger = <0>;goodix,auto-update = <0>;goodix,auto-update-cfg = <0>;goodix,power-off-sleep = <0>;
-
Goodix 特定属性:
goodix,xxx
属性定义了一些特性和行为,例如:goodix,type-a-report
和goodix,driver-send-cfg
:控制是否启用 A 类型报告和驱动是否发送配置信息。goodix,create-wr-node
:创建 WR(写)节点的标志。goodix,wakeup-with-reset
和goodix,power-off-sleep
:控制唤醒和电源管理的行为。- 其他 Goodix 触摸屏特性配置,按需要启用或禁用。
这些属性通过设备树配置,使系统能够正确初始化并管理触摸屏设备。
3. 驱动程序分析
gt9xx.c
3.1 分配/设置/注册input_dev
gtp_proberet = gtp_request_input_dev(ts);ts->input_dev = input_allocate_device();......ret = input_register_device(ts->input_dev);ret = gtp_request_irq(ts);
3.2 注册中断处理函数
ret = request_threaded_irq(ts->client->irq, NULL,gtp_irq_handler,ts->pdata->irq_flags | IRQF_ONESHOT,ts->client->name,ts);
3.3 中断处理函数分析
通过I2C函数( 2c_tansfer)读取数据、上报数据。
static irqreturn_t gtp_irq_handler(int irq, void *dev_id)
{struct goodix_ts_data *ts = dev_id;gtp_work_func(ts);return IRQ_HANDLED;
}static void gtp_work_func(struct goodix_ts_data *ts)
{u8 point_state = 0; // 保存触摸点的状态u8 key_value = 0; // 保存按键的状态s32 i = 0; // 循环变量,用于迭代按键事件s32 ret = -1; // 函数调用返回值static u8 pre_key; // 保存上一次的按键值,用于识别按键变化struct goodix_point_t points[GTP_MAX_TOUCH_ID]; // 存储触摸点信息// 检查是否正在执行复位操作,若是,则退出函数if (test_bit(PANEL_RESETTING, &ts->flags))return;// 检查工作线程是否启用,若未启用,则退出函数if (!test_bit(WORK_THREAD_ENABLED, &ts->flags))return;// 手势事件处理,检测滑动唤醒功能是否启用,且设备是否处于休眠模式if (ts->pdata->slide_wakeup && test_bit(DOZE_MODE, &ts->flags)) {// 调用手势处理函数 gtp_gesture_handlerret = gtp_gesture_handler(ts);if (ret) {// 若处理失败,记录错误日志dev_err(&ts->client->dev,"Failed handler gesture event %d\n", ret);}return; // 处理完成后退出函数}// 获取触摸点的状态及按键状态-----------(1)point_state = gtp_get_points(ts, points, &key_value); if (!point_state) {// 若未检测到有效的触摸点,记录调试日志dev_dbg(&ts->client->dev, "Invalid finger points\n");return; // 没有有效触摸点,直接返回}// 处理触摸按键事件if (key_value & 0xf0 || pre_key & 0xf0) { // 判断是否有按键事件// 处理手写笔的按键switch (key_value) {case 0x40: // 两个按键都按下input_report_key(ts->input_dev, GTP_PEN_BUTTON1, 1);input_report_key(ts->input_dev, GTP_PEN_BUTTON2, 1);break;case 0x10: // 按下第一个按键input_report_key(ts->input_dev, GTP_PEN_BUTTON1, 1);input_report_key(ts->input_dev, GTP_PEN_BUTTON2, 0);dev_dbg(&ts->client->dev, "pen button1 down\n");break;case 0x20: // 按下第二个按键input_report_key(ts->input_dev, GTP_PEN_BUTTON1, 0);input_report_key(ts->input_dev, GTP_PEN_BUTTON2, 1);break;default: // 没有按键按下,恢复初始状态input_report_key(ts->input_dev, GTP_PEN_BUTTON1, 0);input_report_key(ts->input_dev, GTP_PEN_BUTTON2, 0);dev_dbg(&ts->client->dev, "button1 up\n");break;}input_sync(ts->input_dev); // 同步输入事件pre_key = key_value; // 更新上次按键状态} else if (key_value & 0x0f || pre_key & 0x0f) { // 判断是否有面板按键事件// 遍历所有按键,检测是否有按键状态发生变化for (i = 0; i < ts->pdata->key_nums; i++) {if ((pre_key | key_value) & (0x01 << i))input_report_key(ts->input_dev,ts->pdata->key_map[i], // 具体按键映射key_value & (0x01 << i)); // 按键状态}input_sync(ts->input_dev); // 同步输入事件pre_key = key_value; // 更新上次按键状态}// 选择不同的报告格式进行触摸点的处理if (!ts->pdata->type_a_report)gtp_mt_slot_report(ts, point_state & 0x0f, points); // Slot方式----------------------(2)elsegtp_type_a_report(ts, point_state & 0x0f, points); // Type A方式
}
(1)point_state = gtp_get_points(ts, points, &key_value);
: 从 I2C 总线上读取触摸点的数据和触摸状态。提取坐标、压力值、工具类型等信息,若需要则交换 X/Y 坐标。 将手写笔和普通触摸点区分,按需要调整触摸点数据。完成数据读取后,通过发送结束命令结束当前触摸事件。 ret = gtp_i2c_read(ts->client, point_data, 12); //这里内部就调用到了i2c_transfer发起传输
static u8 gtp_get_points(struct goodix_ts_data *ts,struct goodix_point_t *points,u8 *key_value)
{int ret; // 保存 I2C 传输返回状态int i; // 循环变量u8 *coor_data = NULL; // 指向触摸坐标数据的指针u8 finger_state = 0; // 保存手指状态信息u8 touch_num = 0; // 保存当前触摸点的数量u8 end_cmd[3] = { // 用于向触摸屏发送 "结束" 命令GTP_READ_COOR_ADDR >> 8,GTP_READ_COOR_ADDR & 0xFF,0};u8 point_data[2 + 1 + 8 * GTP_MAX_TOUCH_ID + 1] = { // 存储触摸点的数据GTP_READ_COOR_ADDR >> 8,GTP_READ_COOR_ADDR & 0xFF};// 读取触摸点的坐标数据ret = gtp_i2c_read(ts->client, point_data, 12); //这里内部就调用到了i2c_transfer发起传输if (ret < 0) {dev_err(&ts->client->dev, "I2C transfer error. errno:%d\n ", ret);return 0; // 读取失败返回0}finger_state = point_data[GTP_ADDR_LENGTH]; // 获取触摸屏状态if (finger_state == 0x00) // 若无触摸点return 0;// 获取触摸点数量(低 4 位表示触摸点数量)touch_num = finger_state & 0x0f;// 检查触摸状态是否合法,如超出最大触摸数或没有触摸if ((finger_state & MASK_BIT_8) == 0 || touch_num > ts->pdata->max_touch_id) {dev_err(&ts->client->dev, "Invalid touch state: 0x%x", finger_state);finger_state = 0; // 若非法则重置触摸状态goto exit_get_point; // 跳转到退出点}// 若有多个触摸点,则读取后续触摸点数据if (touch_num > 1) {u8 buf[8 * GTP_MAX_TOUCH_ID] = {(GTP_READ_COOR_ADDR + 10) >> 8,(GTP_READ_COOR_ADDR + 10) & 0xff};ret = gtp_i2c_read(ts->client, buf, 2 + 8 * (touch_num - 1));if (ret < 0) {dev_err(&ts->client->dev, "I2C error. %d\n", ret);finger_state = 0;goto exit_get_point;}memcpy(&point_data[12], &buf[2], 8 * (touch_num - 1));}// 触摸按键事件,低 4 位标识按键事件*key_value = point_data[3 + 8 * touch_num];// 初始化触摸点信息数组memset(points, 0, sizeof(*points) * GTP_MAX_TOUCH_ID);for (i = 0; i < touch_num; i++) {coor_data = &point_data[i * 8 + 3]; // 获取触摸点的坐标数据points[i].id = coor_data[0]; // 触摸点 IDpoints[i].x = coor_data[1] | (coor_data[2] << 8); // 触摸点的 X 坐标points[i].y = coor_data[3] | (coor_data[4] << 8); // 触摸点的 Y 坐标points[i].w = coor_data[5] | (coor_data[6] << 8); // 触摸点的宽度points[i].p = coor_data[5] | (coor_data[6] << 8); // 压力值// 判断是否需要交换 X/Y 坐标if (ts->pdata->swap_x2y)GTP_SWAP(points[i].x, points[i].y);dev_dbg(&ts->client->dev, "[%d][%d %d %d]\n", points[i].id, points[i].x, points[i].y, points[i].p);// 判断是否为手写笔触摸点if (points[i].id & 0x80) {points[i].tool_type = GTP_TOOL_PEN;points[i].id = 10; // 手写笔 ID 固定为 10if (ts->pdata->pen_suppress_finger) {points[0] = points[i];memset(++points, 0, sizeof(*points) * (GTP_MAX_TOUCH_ID - 1));finger_state &= 0xf0;finger_state |= 0x01; // 设置第一个触摸点为手写笔break;}} else {points[i].tool_type = GTP_TOOL_FINGER;}}exit_get_point:// 若未处于 RAW 数据模式,写入结束命令以结束当前触摸事件if (!test_bit(RAW_DATA_MODE, &ts->flags)) {ret = gtp_i2c_write(ts->client, end_cmd, 3);if (ret < 0)dev_info(&ts->client->dev, "I2C write end_cmd error!");}return finger_state; // 返回触摸屏状态
}
(2)gtp_irq_handler中你的gtp_mt_slot_report(ts, point_state & 0x0f, points); // Slot方式
。
- 函数主要负责报告多点触控的触摸点状态,包括其位置、工具类型和压力等。
- 使用位图(
cur_touch
和pre_touch
)跟踪当前和先前的触摸 ID,从而确定哪些触摸点是新触摸、哪些是结束触摸。
static void gtp_mt_slot_report(struct goodix_ts_data *ts, u8 touch_num, struct goodix_point_t *points)
{int i; // 循环变量u16 cur_touch = 0; // 当前触摸 ID 位图static u16 pre_touch; // 上一帧的触摸 ID 位图static u8 pre_pen_id; // 上一次触摸的笔 ID// 遍历每个可能的触摸点,最大触摸 ID 由 ts->pdata->max_touch_id 决定for (i = 0; i < ts->pdata->max_touch_id; i++) {// 检查当前触摸点是否有效if (touch_num && i == points->id) {// 设置当前触摸点的槽(slot)input_mt_slot(ts->input_dev, points->id);// 判断工具类型if (points->tool_type == GTP_TOOL_PEN) {// 如果是笔工具,报告笔的状态为真input_mt_report_slot_state(ts->input_dev, MT_TOOL_PEN, true);pre_pen_id = points->id; // 更新上一次的笔 ID} else {// 否则为手指工具,报告状态为真input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true);}// 报告触摸点的 X 坐标input_report_abs(ts->input_dev, ABS_MT_POSITION_X, points->x);// 报告触摸点的 Y 坐标input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, points->y);// 报告触摸点的宽度input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, points->w);// 报告触摸点的压力input_report_abs(ts->input_dev, ABS_MT_PRESSURE, points->p);// 更新当前触摸 ID 位图cur_touch |= 0x01 << points->id;points++; // 移动到下一个触摸点} // 如果在上一帧中这个槽是活跃的else if (pre_touch & (0x01 << i)) {// 设置当前槽为 iinput_mt_slot(ts->input_dev, i);// 检查上次是否是笔 IDif (pre_pen_id == i) {// 如果是笔,报告笔的状态为假input_mt_report_slot_state(ts->input_dev, MT_TOOL_PEN, false);/* 有效的 ID 应小于 10,所以将 ID 设置为 0xff * 来表示无效状态*/pre_pen_id = 0xff; // 重置笔 ID} else {// 否则报告手指状态为假input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false);}}}// 更新上一帧的触摸 ID 位图为当前帧pre_touch = cur_touch;// 同步当前的触摸帧input_mt_sync_frame(ts->input_dev);// 同步输入设备状态input_sync(ts->input_dev);
}
gtp_irq_handlergtp_work_func(ts);point_state = gtp_get_points(ts, points, &key_value);gtp_i2c_readi2c_transfergtp_mt_slot_report(ts, point_state & 0x0f, points);input_mt_slotinput_mt_report_slot_stateinput_report_abs
有所疑问可以先看:
I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客
编写一个简单的Iinput_dev框架-CSDN博客
4.编写
这里主要是参考📎gpio_keys.c,但思路其实和上面介绍的差不多,设置Input_dev并注册、配置事件类型和支持的事件、请求中断注册中断等。
#define TOUCHSCREEN_POLL_TIME_MS 10 // 定义轮询时间,单位为毫秒// 定义模拟触摸屏硬件数据结构
struct qemu_ts_con {volatile unsigned int pressure; // 触摸压力,0表示未触摸,非0表示触摸volatile unsigned int x; // 触摸X坐标volatile unsigned int y; // 触摸Y坐标volatile unsigned int clean; // 清除标志
};// 全局变量定义
static struct input_dev *g_input_dev; // 输入设备
static int g_irq; // 中断号
static struct qemu_ts_con *ts_con; // 指向模拟触摸屏的结构体
struct timer_list ts_timer; // 定时器,用于轮询触摸状态// 定时器回调函数,用于定期轮询触摸状态
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));}
}// 中断服务函数,触发触摸事件处理
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);}return IRQ_HANDLED;
}// 设备初始化函数,负责资源分配和设备注册
static int input_dev_demo_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev;int error;struct resource *io;int gpio;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);// 获取GPIO编号gpio = of_get_gpio(pdev->dev.of_node, 0);// 分配并初始化input_dev结构体g_input_dev = devm_input_allocate_device(dev);if (!g_input_dev)return -ENOMEM;// 配置input_dev的基础信息g_input_dev->name = "input_dev_demo";g_input_dev->phys = "input_dev_demo";g_input_dev->dev.parent = dev;g_input_dev->id.bustype = BUS_HOST;g_input_dev->id.vendor = 0x0001;g_input_dev->id.product = 0x0001;g_input_dev->id.version = 0x0100;// 配置事件类型和支持的事件__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_bit(BTN_TOUCH, g_input_dev->keybit);__set_bit(ABS_X, g_input_dev->absbit);__set_bit(ABS_Y, g_input_dev->absbit);// 配置触摸屏的绝对坐标范围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);// 注册input_deverror = input_register_device(g_input_dev);if (error)return error;// 获取硬件寄存器的I/O资源并映射io = platform_get_resource(pdev, IORESOURCE_MEM, 0);ts_con = ioremap(io->start, resource_size(io));// 获取并请求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);if (error)return error;// 初始化定时器setup_timer(&ts_timer, ts_irq_timer, (unsigned long)NULL);return 0;
}// 设备卸载函数,释放资源
static int input_dev_demo_remove(struct platform_device *pdev)
{del_timer_sync(&ts_timer); // 停止定时器iounmap(ts_con); // 取消内存映射free_irq(g_irq, NULL); // 释放中断input_unregister_device(g_input_dev); // 注销设备return 0;
}// 设备树匹配表
static const struct of_device_id input_dev_demo_of_match[] = {{ .compatible = "100ask,input_dev_demo", },{ },
};// 平台驱动结构体定义
static struct platform_driver input_dev_demo_driver = {.probe = input_dev_demo_probe,.remove = input_dev_demo_remove,.driver = {.name = "input_dev_demo",.of_match_table = input_dev_demo_of_match,}
};// 模块初始化函数
static int __init input_dev_demo_init(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);return platform_driver_register(&input_dev_demo_driver);
}// 模块退出函数
static void __exit input_dev_demo_exit(void)
{platform_driver_unregister(&input_dev_demo_driver);
}module_init(input_dev_demo_init);
module_exit(input_dev_demo_exit);MODULE_LICENSE("GPL");