drm edp屏加载学习

  • 基本概念
    • 专业术语
    • 链路训练
      • 触发与能力读取
      • 时钟恢复
      • 通道均衡
      • 符号锁定与通道对齐
    • 加载流程
      • 硬件探测与面板识别
      • 内核探测
      • 面板准备
    • 模式设置触发与链路训练
      • 用户空间发起请求
      • 内核执行模式设置
      • 链路训练
    • 总结
  • dp-aux总线简介
    • 源文件位置
    • 总线的注册
    • dp-aux驱动的注册
    • dp-aux设备的注册
  • edp屏通用驱动
    • 源码位置
    • 驱动加载
    • panel_edp_platform_probe
    • panel_edp_dp_aux_ep_probe
    • panel_edp_probe
  • edp控制器加载
    • mtk平台bridge加载示例

专业术语

链路训练

触发与能力读取

屏幕上电后,通过HPD(热插拔检测)引脚通知显卡"我已就绪"。显卡随即通过AUX通道读取接收端的DPCD寄存器,了解它支持的最大通道数、速率、电压摆幅等能力。

时钟恢复 (Clock Recovery)

这是训练的第一步,也是基础。显卡会以最低的驱动设置,发送特定的训练模式1(TPS1)。接收端则尝试从数据流中恢复出同步时钟。显卡会通过AUX通道反复检查接收端的CR_DONE标志位,直到所有通道都成功锁定时钟。如果失败,会逐步提高电压摆幅和预加重,或降低链路速率重试。

通道均衡 (Channel Equalization)

时钟锁定后,显卡发送更复杂的训练模式(TPS2/3/4)。接收端根据信号失真情况,动态调整其内部均衡器参数来补偿传输损耗,并反馈EQ_DONE状态。这一步是为了确保信号在高速传输后仍能被清晰识别。

符号锁定与通道对齐

接收端需要准确识别编码符号的边界(符号锁定),并确保多条数据通道间的数据是同步对齐的。当所有步骤的状态标志都置位后,链路训练宣告成功,可以开始传输视频画面了。

加载流程

从开机到点亮屏幕整个流程可以清晰地分为二个阶段,而"先读EDID,后链路训练"的顺序

硬件探测与面板识别(链路空闲)

内核探测

系统启动,DRM驱动发现eDP面板。

面板准备

panel-edp驱动给面板上电,然后通过AUX通道读取EDID。这一步只是获取屏幕的"身份信息"(分辨率、ID等)并加载时序参数。

模式设置触发与链路训练(用户空间介入)

用户空间发起请求

显示服务器启动,提交模式设置(Modeset) 请求。

内核执行模式设置

内核收到请求并开始执行,进入commit_tail流程。

链路训练

在模式设置流程中,当处理到启用DP/eDP连接器的阶段时,驱动才正式执行链路训练。

总结

这里有一个关键点需要明确:panel-edp在"第一阶段"能读取EDID,是因为底层的DP控制器驱动已经完成了最基础的初始化,使得AUX通道可以工作。但这并不等于"完整的链路训练"。一个"半工作"的AUX通道,足以承载低速的EDID读取,但无法传输高速的视频数据。

⏱️ 延后训练的设计意图

之所以把链路训练延后到模式设置这一步,主要有两点考虑:

按需训练:如果系统启动到救援模式,用户空间从不发起模式设置,那就不必浪费电和初始化时间去训练链路。

参数决策:训练参数(速率、通道数)需要根据最终显示的分辨率和色深来定。只有用户空间知道"要显示什么",内核才能用最优参数去训练。

综合来看,三者实际发生的顺序是:

EDID读取(由内核在探测时自发完成,早于训练)

用户空间发起模式设置请求(触发整个训练流程的"扳机")

链路训练(作为模式设置流程的一部分,最后执行)

dp-aux总线简介

源文件位置

复制代码
kernel/drivers/gpu/drm/display/drm_dp_aux_bus.c

总线的注册

c 复制代码
static int __init dp_aux_bus_init(void)
{
	int ret;

	ret = bus_register(&dp_aux_bus_type);
	if (ret)
		return ret;

	return 0;
}

static struct bus_type dp_aux_bus_type = {
	.name		= "dp-aux",
	.match		= dp_aux_ep_match,
	.probe		= dp_aux_ep_probe,
	.remove		= dp_aux_ep_remove,
	.shutdown	= dp_aux_ep_shutdown,
};

dp-aux驱动的注册

c 复制代码
int __dp_aux_dp_driver_register(struct dp_aux_ep_driver *drv, struct module *owner)
{
	drv->driver.owner = owner;
	drv->driver.bus = &dp_aux_bus_type;

	return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__dp_aux_dp_driver_register);

dp-aux总线的驱动注册都是调用上面的函数,将驱动注册到总线的kset_driver链表上

dp-aux设备的注册

c 复制代码
int devm_of_dp_aux_populate_bus(struct drm_dp_aux *aux,
				int (*done_probing)(struct drm_dp_aux *aux))
{
	int ret;

	ret = of_dp_aux_populate_bus(aux, done_probing);
	if (ret)
		return ret;

	return devm_add_action_or_reset(aux->dev,
					of_dp_aux_depopulate_bus_void, aux);
}
EXPORT_SYMBOL_GPL(devm_of_dp_aux_populate_bus);

一般由dp-aux控制器驱动调用上面接口自动探测dp-aux device,然后device与driver按总线匹配规则完成匹配。这里就不举例了。

edp屏通用驱动

源码位置:

复制代码
kernel/drivers/gpu/drm/panel/panel-edp.c

驱动加载

c 复制代码
static int __init panel_edp_init(void)
{
	int err;

	err = platform_driver_register(&panel_edp_platform_driver);
	if (err < 0)
		return err;

	err = dp_aux_dp_driver_register(&panel_edp_dp_aux_ep_driver);
	if (err < 0)
		goto err_did_platform_register;

	return 0;

err_did_platform_register:
	platform_driver_unregister(&panel_edp_platform_driver);

	return err;
}
module_init(panel_edp_init);

这里可以看到有两个驱动的加载,一个是platform总线的驱动加载。一种是dp-aux总线的驱动加载,这两者的区别如下

这里需要注意的是这两种加载方式的匹配过程,这两种加载方式最终都是调用panel_edp_probe函数来完成

panel_edp_platform_probe

c 复制代码
static struct platform_driver panel_edp_platform_driver = {
	.driver = {
		.name = "panel-edp",
		.of_match_table = platform_of_match,
		.pm = &panel_edp_pm_ops,
	},
	.probe = panel_edp_platform_probe,
	.remove = panel_edp_platform_remove,
	.shutdown = panel_edp_platform_shutdown,
};

static int panel_edp_platform_probe(struct platform_device *pdev)
{
	const struct of_device_id *id;

	/* Skip one since "edp-panel" is only supported on DP AUX bus */
	id = of_match_node(platform_of_match + 1, pdev->dev.of_node);
	if (!id)
		return -ENODEV;

	return panel_edp_probe(&pdev->dev, id->data, NULL);
}

这里坱要注意的platform总线匹配是从table数组下标1开始的,edp-panel这个字段仅属性dp-aux总线

panel_edp_dp_aux_ep_probe

c 复制代码
static struct dp_aux_ep_driver panel_edp_dp_aux_ep_driver = {
	.driver = {
		.name = "panel-simple-dp-aux",
		.of_match_table = platform_of_match,	/* Same as platform one! */
		.pm = &panel_edp_pm_ops,
	},
	.probe = panel_edp_dp_aux_ep_probe,
	.remove = panel_edp_dp_aux_ep_remove,
	.shutdown = panel_edp_dp_aux_ep_shutdown,
};

static int panel_edp_dp_aux_ep_probe(struct dp_aux_ep_device *aux_ep)
{
	const struct of_device_id *id;

	id = of_match_node(platform_of_match, aux_ep->dev.of_node);
	if (!id)
		return -ENODEV;

	return panel_edp_probe(&aux_ep->dev, id->data, aux_ep->aux);
}

dp-aux table匹配是从数组下标为0开始的,刚好对应edp-panel字段

panel_edp_probe

此函数是edp核心驱动加载函数,这里重点来关注下

其panel核心回调函数如下

c 复制代码
static const struct drm_panel_funcs panel_edp_funcs = {
	.disable = panel_edp_disable,
	.unprepare = panel_edp_unprepare,
	.prepare = panel_edp_prepare,
	.enable = panel_edp_enable,
	.get_modes = panel_edp_get_modes,
	.get_orientation = panel_edp_get_orientation,
	.get_timings = panel_edp_get_timings,
	.debugfs_init = panel_edp_debugfs_init,
};

都是基于drm框架的回调函数。

edp控制器加载

mtk平台bridge加载示例

源码位置:

kernel/drivers/gpu/drm/mediatek/mtk_dp.c

控制器的驱动一般放在encoder或者bridge组件里。MTK放在brideg下进行处理,此驱动作为platform总线驱动由内核直接加载,这里只关注核心源码

其核心的回调函数如下:

c 复制代码
static const struct drm_bridge_funcs mtk_dp_bridge_funcs = {
	.atomic_check = mtk_dp_bridge_atomic_check,
	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
	.atomic_get_output_bus_fmts = mtk_dp_bridge_atomic_get_output_bus_fmts,
	.atomic_get_input_bus_fmts = mtk_dp_bridge_atomic_get_input_bus_fmts,
	.atomic_reset = drm_atomic_helper_bridge_reset,
	.attach = mtk_dp_bridge_attach,
	.detach = mtk_dp_bridge_detach,
	.atomic_enable = mtk_dp_bridge_atomic_enable,
	.atomic_disable = mtk_dp_bridge_atomic_disable,
	.mode_valid = mtk_dp_bridge_mode_valid,
	.get_edid = mtk_dp_get_edid,
	.detect = mtk_dp_bdg_detect,
};

在atomic_enable核心回调函数里面,就包含了edp链路训练的核心代码,截图如下:

atomic_enable触发调用的大体流程如下:

最终会调用到drm_atomic_helper_commit_modeset_enables,在这个函数里面会对crtc encoder,bridge进行enable操作,这里就会调用到edp核心的训练函数了

本篇到这里就结束了,水平有限如有错误敬请指出!

相关推荐
Linux-lucky2 小时前
36-Linux学习之旅之MySQL主从复制
linux·运维·学习·mysql·ubuntu
SatanII2 小时前
华为云ECS实践:从创建、镜像制作到弹性伸缩完整实操指南
linux·运维·服务器·学习·centos·华为云
留白_2 小时前
【tableau入门学习】1、数据预处理
学习
小弥儿2 小时前
GitHub今日热榜 | 2026-09-08:微软 markitdown 冲进前三
学习·microsoft·开源·github
是隼人2 小时前
buuctf-pwn bypwn(ret2shellcode)题解(学习过程持续更新)
c语言·学习·安全·pwn入门·ctf入门
kyrie_sakura2 小时前
MySQL数据库学习笔记3--关联(联合)查询
数据库·学习·mysql
辣知3 小时前
辣知·化智49 黄帝时期至夏朝的一千年
学习
是隼人3 小时前
buuctf-pwn PWN2(整数溢出)题解(学习过程持续更新)
c语言·学习·安全·pwn入门·ctf入门