MTK Linux DRM分析(十四)- Mediatek KMS实现mtk_drm_drv.c(Part.2)
一、MTK KMS分析
mtk_drm_kms_init 函数分析
mtk_drm_kms_init 是 MediaTek DRM 驱动程序中的一个静态函数(static int mtk_drm_kms_init(struct drm_device *drm)),位于 mtk_drm_drv.c 文件中。该函数的主要作用是初始化 DRM 设备的 Kernel Mode Setting (KMS) 子系统,包括配置显示模式、绑定硬件组件、创建 CRTC(Controller)、设置 DMA 参数、初始化各种辅助结构和工作队列等。它是驱动从硬件抽象层过渡到 DRM 框架的核心入口点,通常在组件绑定(component_bind_all)后调用,确保显示管道(display pipeline)准备就绪,支持用户空间的图形渲染和模式设置。
函数的输入是一个已分配的 drm_device 结构(包含 MediaTek 特定的私有数据 mtk_drm_private)。输出为 0(成功)或负错误码(失败)。整个函数逻辑严谨,包含多个错误处理路径(使用 goto 跳转清理资源)。下面我基于提供的代码,按步骤详细分析其执行流程、关键操作和潜在逻辑。
1. 准备阶段:获取私有数据和检查依赖
- struct mtk_drm_private *private = drm->dev_private;
- 从 drm_device 中提取 MediaTek 特定的私有数据结构 mtk_drm_private,其中包含设备树节点、寄存器映射、helper_opt 等信息(这些在 mtk_drm_probe 中已初始化)。
- 检查 M4U (Memory Management Unit) 支持:
- if (mtk_drm_helper_get_opt(private->helper_opt, MTK_DRM_OPT_USE_M4U)) { ... }
- 如果启用 M4U(IOMMU 相关的内存管理选项),检查 IOMMU 是否就绪(iommu_present(&platform_bus_type))。
- 如果 IOMMU 未准备好,返回 -EPROBE_DEFER,延迟驱动探测(probe),等待 IOMMU 驱动加载。这确保了 DMA 内存分配的安全性和连续性(防止内存碎片)。
- if (mtk_drm_helper_get_opt(private->helper_opt, MTK_DRM_OPT_USE_M4U)) { ... }
- 日志记录:DDPINFO("%s+\n", __func__); – 输出调试信息,表示函数开始。
2. 初始化 DRM Mode Config
- drm_mode_config_init(drm);
- 初始化 DRM 的模式配置结构(drm_mode_config),设置默认的模式设置回调(如 atomic_check、atomic_commit),启用原子模式设置支持。
- 设置默认分辨率限制:
- drm->mode_config.min_width = 1; drm->mode_config.min_height = 1;
- 最小分辨率设置为 1x1(默认最小值,避免无效模式)。
- drm->mode_config.max_width = 4096; drm->mode_config.max_height = 4096;
- 最大分辨率设置为 4096x4096(默认值,用于检查帧缓冲区大小限制,在 drm_mode_addfb 中使用)。
- drm->mode_config.min_width = 1; drm->mode_config.min_height = 1;
- 设置模式配置函数:drm->mode_config.funcs = &mtk_drm_mode_config_funcs;
- 关联 MediaTek 特定的模式配置函数(未在代码中显示,但可能是自定义的页面翻转、属性处理等)。
3. 绑定组件和初始化 VBlank
- ret = component_bind_all(drm->dev, drm);
- 绑定所有显示组件(通过 mtk_drm_ops 中的 bind 函数),这些组件在 mtk_drm_probe 中已添加到 match 列表中(如 OVL、RDMA、DSI 等)。
- 如果绑定失败,跳转到 err_config_cleanup 清理 mode config。
- ret = drm_vblank_init(drm, MAX_CRTC);
- 初始化 VBlank(垂直同步)支持,最多支持 MAX_CRTC 个 CRTC(通常为 3 或 4,根据 SoC)。
- VBlank 用于同步帧渲染和显示,避免撕裂(tearing)。失败则跳转到 err_component_unbind 解绑组件。
4. 创建 CRTC(显示控制器)
- ret = mtk_drm_crtc_create(drm, private->data->main_path_data);
- 创建主路径 CRTC(OVL0 -> COLOR0 -> AAL -> OD -> RDMA0 -> UFOE -> DSI0 等)。
- main_path_data 来自 SoC 特定的数据(如 mt6885_mmsys_driver_data)。
- 根据设备树属性和阶段(disp_helper_get_stage())创建扩展路径:
- 如果是正常阶段(DISP_HELPER_STAGE_NORMAL):
- 检查 "enable_ext_alter_path" 属性,如果启用,使用 ext_alter_path_data 创建扩展路径 CRTC(OVL1 -> COLOR1 -> GAMMA -> RDMA1 -> DPI0)。
- 否则,使用 ext_path_data。
- 创建第三个 CRTC(third_path_data 或变体,根据 "condition-num" 属性调整路径,如是否包含 TDSHP)。
- 创建第四个 CRTC(如果启用 "enable_secondary_path" 或 "enable_discrete_path",使用 fourth_path_data_secondary 或 fourth_path_data_discrete)。
- 如果是正常阶段(DISP_HELPER_STAGE_NORMAL):
- 这些 CRTC 代表不同的显示管道,支持多屏输出(如主屏、扩展屏)。
- 失败则跳转到 err_component_unbind。
- 注释:/*TODO: Need to check path rule*/ – 表示路径规则检查待实现,可能涉及硬件兼容性。
5. 设置 DMA 和内存分配
- 选择 OVL 设备作为 DMA 分配设备:
- 从主路径或扩展路径的第一个组件节点获取 OVL 设备(private->comp_node[...])。
- private->dma_dev = &pdev->dev;
- 使用 OVL 设备进行所有 DMA 内存分配,确保连续 IOVA(IO Virtual Address)用于 PRIME 缓冲区导入。
- 配置 DMA 参数:
- 如果 dma_dev->dma_parms 未分配,动态分配(devm_kzalloc)。
- ret = dma_set_max_seg_size(dma_dev, (unsigned int)DMA_BIT_MASK(32));
- 设置 DMA 段最大大小为 32-bit(4GB),确保 PRIME 缓冲区导入时的连续性。
- 失败则清理并返回。
- drm_mode_config_reset(drm);
- 重置模式配置到默认状态。