
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 返回的是有效模式数量。但有些显示器会报告一堆奇怪的分辨率比如640x48060Hz。我建议在驱动层做一个白名单过滤只保留常见的分辨率。核心思路显示模式获取的流程是EDID读取 → 解析 → 生成DRM Mode → 上报给用户空间。每一步都可能失败所以要做好降级处理。比如EDID读不到时至少给一个1080p的保底模式。8.5 实战中的几个坑最后我总结几个实战中容易踩的坑EDID缓存失效有些显示器在切换输入源后会更新EDID。我建议每次HOTPLUG事件都重新读取EDID不要迷信缓存。HPD去抖时间不同接口的去抖时间不一样。HDMI建议50msDP建议100ms。我习惯在设备树里配一个可调的参数。模式优先级多个显示器同时接入时要保证主屏优先获取最佳模式。我一般按物理端口顺序分配优先级。I2C总线竞争EDID读取时如果其他设备也在用同一根I2C总线会冲突。建议加mutex保护。好了Connector驱动的核心内容就这些。说白了它就是一个翻译官——把物理层的信号翻译成DRM能理解的mode。下一章咱们聊聊如何把这些Connector挂到Display Engine上实现真正的多屏输出。