news 2026/9/7 9:31:48

8. Connector驱动实现:mtk_connector.c分析、EDID读取、热插拔检测、显示模式获取

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
8. Connector驱动实现:mtk_connector.c分析、EDID读取、热插拔检测、显示模式获取

8.1 mtk_connector.c 核心结构分析

先看文件结构。mtk_connector.c 是MTK自研的Connector抽象层。它不像DRM框架里那种标准Connector,而是做了很多定制化处理。说白了,MTK把物理接口的共性操作抽出来了。

核心数据结构就一个:

struct mtk_connector { struct drm_connector base; struct mtk_ddp_comp *ddp_comp; struct mtk_edid *edid; struct mutex lock; bool hpd_state; struct work_struct hpd_work; void (*hpd_cb)(void *priv); void *hpd_priv; };

这里我重点说几个字段:

  • ddp_comp:指向对应的显示通路组件。比如HDMI接口,它就指向HDMI的DDP组件。
  • edid:缓存解析后的EDID数据。我习惯在热插拔时重新读取,避免用旧数据。
  • hpd_work:热插拔的工作队列。注意,这里用的是workqueue,不是tasklet。为什么?因为EDID读取需要休眠等待I2C传输,workqueue最合适。

关键点:mtk_connector 不是简单的DRM Connector封装。它内部维护了HPD状态机、EDID缓存、以及回调机制。你想想看,如果每次查询模式都要重新读EDID,那性能得多差?

8.2 EDID读取:从I2C到解析

EDID读取,说白了就是通过I2C从显示器里把128字节(或扩展块)的数据读出来。MTK的做法是:

static int mtk_connector_read_edid(struct mtk_connector *connector) { struct i2c_adapter *adap; u8 buf[EDID_LENGTH]; int ret; adap = mtk_ddp_comp_get_i2c_adap(connector->ddp_comp); if (!adap) { dev_err(dev, "no i2c adapter\n"); return -ENODEV; } // 标准EDID读取流程 ret = i2c_transfer(adap, &msg, 1); if (ret != 1) { dev_warn(dev, "EDID read failed, ret=%d\n", ret); return -EIO; } // 校验EDID头 if (buf[0] != 0x00 || buf[1] != 0xFF) { dev_err(dev, "invalid EDID header\n"); return -EINVAL; } // 解析并缓存 connector->edid = mtk_edid_parse(buf); return 0; }

这里有个坑,我踩过。有些显示器EDID读取需要重试。为什么?因为显示器刚上电时,I2C总线还没准备好。我建议至少重试3次,每次间隔50ms。

我的经验:EDID读取失败时,别急着报错。先检查I2C地址是否正确(标准是0x50)。有些山寨显示器会把地址偏移一位,我遇到过两次这种情况。

8.3 热插拔检测:中断与轮询的博弈

热插拔检测,MTK支持两种模式:

  • 中断模式:硬件HPD引脚触发中断,实时性最好。
  • 轮询模式:定时检查HPD状态,适合没有中断引脚的场景。

代码里是这样处理的:

static irqreturn_t mtk_connector_hpd_irq_handler(int irq, void *dev_id) { struct mtk_connector *connector = dev_id; // 注意:中断上下文不能做耗时操作 schedule_work(&connector->hpd_work); return IRQ_HANDLED; } static void mtk_connector_hpd_work_handler(struct work_struct *work) { struct mtk_connector *connector; bool new_state; connector = container_of(work, struct mtk_connector, hpd_work); // 读取硬件HPD状态 new_state = mtk_ddp_comp_get_hpd_state(connector->ddp_comp); if (new_state != connector->hpd_state) { connector->hpd_state = new_state; if (new_state) { // 插入:重新读取EDID mtk_connector_read_edid(connector); } else { // 拔出:释放EDID缓存 mtk_edid_free(connector->edid); connector->edid = NULL; } // 通知上层 if (connector->hpd_cb) connector->hpd_cb(connector->hpd_priv); } }

嗯,这里要注意。HPD状态变化后,不能立即上报给上层。我建议加一个50ms的去抖延时。为什么?因为物理接触会有抖动,特别是HDMI接口,插拔瞬间会反复触发中断。

避坑指南:我曾经遇到一个案子,用户插拔HDMI时系统频繁重启。查了两天才发现是HPD中断处理里调用了drm_helper_hpd_irq_event,而这个函数内部会加锁,导致死锁。解决方案是用workqueue延迟处理。

8.4 显示模式获取:从EDID到DRM Mode

显示模式获取,说白了就是把EDID里的时序信息转换成DRM能识别的mode结构。MTK的做法是:

static int mtk_connector_get_modes(struct drm_connector *connector) { struct mtk_connector *mtk_conn = to_mtk_connector(connector); struct edid *edid; int count = 0; // 优先使用缓存的EDID if (mtk_conn->edid) { edid = (struct edid *)mtk_conn->edid->raw; } else { // 没有缓存则重新读取 if (mtk_connector_read_edid(mtk_conn)) return 0; edid = (struct edid *)mtk_conn->edid->raw; } // 调用DRM标准解析函数 drm_connector_update_edid_property(connector, edid); count = drm_add_edid_modes(connector, edid); // 添加默认模式(防止EDID为空) if (count == 0) { drm_mode_create(connector->dev); // 设置一个1080p60的默认模式 ... count = 1; } return count; }

这里有个细节。drm_add_edid_modes 返回的是有效模式数量。但有些显示器会报告一堆奇怪的分辨率,比如640x480@60Hz。我建议在驱动层做一个白名单过滤,只保留常见的分辨率。

核心思路:显示模式获取的流程是:EDID读取 → 解析 → 生成DRM Mode → 上报给用户空间。每一步都可能失败,所以要做好降级处理。比如EDID读不到时,至少给一个1080p的保底模式。

8.5 实战中的几个坑

最后,我总结几个实战中容易踩的坑:

  1. EDID缓存失效:有些显示器在切换输入源后会更新EDID。我建议每次HOTPLUG事件都重新读取EDID,不要迷信缓存。
  2. HPD去抖时间:不同接口的去抖时间不一样。HDMI建议50ms,DP建议100ms。我习惯在设备树里配一个可调的参数。
  3. 模式优先级:多个显示器同时接入时,要保证主屏优先获取最佳模式。我一般按物理端口顺序分配优先级。
  4. I2C总线竞争:EDID读取时,如果其他设备也在用同一根I2C总线,会冲突。建议加mutex保护。

好了,Connector驱动的核心内容就这些。说白了,它就是一个翻译官——把物理层的信号翻译成DRM能理解的mode。下一章咱们聊聊如何把这些Connector挂到Display Engine上,实现真正的多屏输出。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 9:28:51

battery-historian实战:Android电池耗电分析工具详解

简介:电池历史学家(Battery Historian)是Google开源的Android电量分析工具,该压缩包提供可直接运行的版本,面向Android开发者、性能优化和测试人员,用于解析bugreport或adb日志中的电池状态记录&#xff0c…

作者头像 李华
网站建设 2026/9/7 9:27:37

秋叶ComfyUI V9.5整合包评测:一键安装AI绘画节点工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 9:25:39

MFC全局钩子实践:跨窗口监听键盘与鼠标事件

简介:面向 Windows C 开发者的 MFC 全局钩子示例项目,演示如何结合 MFC 对话框程序与 HOOK.DLL,通过 SetWindowsHookEx 设置全局键盘/鼠标钩子,实时捕获按键码和鼠标位置等输入信息,并写入日志文件。资源包含完整 Visu…

作者头像 李华
网站建设 2026/9/7 9:25:25

WorkBuddy实战:从AI聊天到自动化工作流的完整配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 9:25:20

STM32F103C8T6串口IAP固件升级实践:Bootloader+App双区方案

简介:面向 STM32F103C8T6 开发者的串口 IAP 在线升级源码包,解决产品现场通过串口完成固件升级时 Bootloader 与 App 跳转的问题,适合有基础嵌入式开发经验、正在规划量产升级方案的工程师学习参考。资源共 430 个文件,压缩包约 2…

作者头像 李华