一、基础介绍
1 DRM核心组件
黄色:对软件的抽象;
蓝色:对物理硬件的抽象;
2 组件关系
注意:上图蓝色部分即使没有实际的硬件与之对应,在软件驱动中也需要实现这些 objects,否则 DRM 子系统无法正常运行。
drm_panel 不属于 objects 的范畴,它只是一堆回调函数的集合。它降低了对显示设备驱动与encoder驱动之间的耦合度。
于是,原来的 Encoder 驱动和 LCD 驱动之间的耦合,就转变成了上图中 Encoder 驱动与 drm_panel、drm_panel 与 LCD 驱动之间的“耦合”,从而实现了 Encoder 驱动与 LCD 驱动之间的解耦合。
为了方便驱动程序设计,通常都将 encoder 与 connector 放在同一个驱动中初始化,即 encoder 在哪,connector 就在哪。
对于初学者来说,往往让他们迷惑的不是 DRM 中 objects 的概念,而是如何去建立这些 objects 与实际硬件的对应关系。因为并不是所有的 Display 硬件都能很好的对应上 plane/crtc/encoder/connector 这些 objects。下面我们就来一起学习,如何去抽象显示硬件到具体的 DRM object。
MIPI DSI 接口
下图为一个典型的 MIPI DSI 接口屏的硬件连接框图:
它在软件架构上与 DRM object 的对应关系如下图:
二、DRM框架示例剖析
1 引言
这里剖析rockchip的DRM实现。首先介绍下VOP这个硬件IP:全称 Visual Output Processor, 是Rockchip系列Soc的Display Controller(显示控制器), 用来将video memory中的image data传送到外部lcd 接口,如edp, dp, hdmi等。那么VOP在DRM中数据CRTC的概念了。
2 代码框架
可以看到左上角是rockchip实现的代码,外围是DRM的框架代码,继续展开关注rockchip的代码。
继续看下代码文件对应的框架图:
介绍下目录结构中的关键文件:
下面计划主要剖析下面几个文件
rockchip_drm_drv.c:这个文件包含了RockChip DRM(Direct Rendering Manager)驱动的核心部分,负责初始化和管理整个DRM子系统,包括显示模式设置、缓冲区管理等。
rockchip_drm_vop.c:这个文件实现了RockChip DRM中的VOP(Video Output Processor)部分的驱动代码。VOP用于控制视频输出,例如将图像发送到显示器。
rockchip_drm_gem.c:这个文件包含了RockChip DRM中用于管理GEM(Graphics Execution Manager)对象的代码。GEM是Linux内核用于管理图形资源(如显存)的机制。
rockchip_drm_fb.c:这个文件实现了RockChip DRM驱动中与帧缓冲(Framebuffer)相关的功能,包括帧缓冲的分配、映射以及与硬件的交互。
rockchip_drm_fbdev.c:这个文件包含RockChip DRM的Framebuffer设备(fbdev)支持代码,它允许用户空间应用程序通过标准的Linux Framebuffer接口与DRM子系统进行交互。
rockchip_rgb.c:包含了RockChip DRM中与RGB(红绿蓝)显示接口相关的驱动代码,用于与RGB显示器进行通信。
dw-mipi-dsi-rockchip.c:包含了RockChip DRM中与MIPI DSI(Display Serial Interface)控制器相关的驱动代码,用于与MIPI DSI显示器进行通信。
rk3066_hdmi.c:包含了RockChip DRM中与HDMI显示接口相关的驱动代码,用于与HDMI显示器进行通信。
DRM Driver
1. 注册平台设备驱动
/* 这里记录了rockchip的所支持的DRM显示设备 */
static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS];
#define ADD_ROCKCHIP_SUB_DRIVER(drv, cond) { \
if (IS_ENABLED(cond) && \
!WARN_ON(num_rockchip_sub_drivers >= MAX_ROCKCHIP_SUB_DRIVERS)) \
rockchip_sub_drivers[num_rockchip_sub_drivers++] = &drv; \
}
static int __init rockchip_drm_init(void)
{
int ret;
// 初始化子驱动数量为0
num_rockchip_sub_drivers = 0;
// 使用宏 ADD_ROCKCHIP_SUB_DRIVER 来添加RockChip子驱动
// 第一个参数 drv 是子驱动的名字,第二个参数 cond 是条件
// 如果条件满足(cond为真),则将子驱动添加到列表中
// 如果条件不满足,子驱动将被忽略
// 注意:该宏内部使用了 IS_ENABLED 宏来检查条件是否启用
// 添加 vop_platform_driver 子驱动(条件:CONFIG_DRM_ROCKCHIP 为真)
ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_DRM_ROCKCHIP);
// 添加 rockchip_lvds_driver 子驱动(条件:CONFIG_ROCKCHIP_LVDS 为真)
ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver, CONFIG_ROCKCHIP_LVDS);
// 添加 rockchip_dp_driver 子驱动(条件:CONFIG_ROCKCHIP_ANALOGIX_DP 为真)
ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver, CONFIG_ROCKCHIP_ANALOGIX_DP);
// 添加 cdn_dp_driver 子驱动(条件:CONFIG_ROCKCHIP_CDN_DP 为真)
ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP);
// 添加 dw_hdmi_rockchip_pltfm_driver 子驱动(条件:CONFIG_ROCKCHIP_DW_HDMI 为真)
ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver, CONFIG_ROCKCHIP_DW_HDMI);
// 添加 dw_mipi_dsi_rockchip_driver 子驱动(条件:CONFIG_ROCKCHIP_DW_MIPI_DSI 为真)
ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi_rockchip_driver, CONFIG_ROCKCHIP_DW_MIPI_DSI);
// 添加 inno_hdmi_driver 子驱动(条件:CONFIG_ROCKCHIP_INNO_HDMI 为真)
ADD_ROCKCHIP_SUB_DRIVER(inno_hdmi_driver, CONFIG_ROCKCHIP_INNO_HDMI);
// 添加 rk3066_hdmi_driver 子驱动(条件:CONFIG_ROCKCHIP_RK3066_HDMI 为真)
ADD_ROCKCHIP_SUB_DRIVER(rk3066_hdmi_driver, CONFIG_ROCKCHIP_RK3066_HDMI);
// 注册平台驱动程序,将所有子驱动程序注册到Linux平台设备中
ret = platform_register_drivers(rockchip_sub_drivers, num_rockchip_sub_drivers);
if (ret)
return ret;
// 注册RockChip DRM平台驱动程序
ret = platform_driver_register(&rockchip_drm_platform_driver);
if (ret)
goto err_unreg_drivers;
return 0;
err_unreg_drivers:
// 如果注册RockChip DRM平台驱动失败,卸载已注册的子驱动程序
platform_unregister_drivers(rockchip_sub_drivers, num_rockchip_sub_drivers);
return ret;
}
2 probe
// 定义文件操作相关的结构体,用于处理用户空间与驱动程序之间的文件操作
static const struct file_operations rockchip_drm_driver_fops = {
.owner = THIS_MODULE, // 设置模块为拥有者
.open = drm_open, // 打开文件时执行的函数
.mmap = rockchip_gem_mmap, // 内存映射操作的函数
.poll = drm_poll, // 轮询操作的函数
.read = drm_read, // 读取文件时执行的函数
.unlocked_ioctl = drm_ioctl, // 无锁 I/O 控制操作的函数
.compat_ioctl = drm_compat_ioctl, // 兼容性 IOCTL 控制操作的函数
.release = drm_release, // 释放文件时执行的函数
};
// 定义 Rockchip DRM 驱动程序的属性和操作
static struct drm_driver rockchip_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, // 驱动程序特性
.lastclose = drm_fb_helper_lastclose, // 最后一个文件句柄关闭时执行的函数
.gem_vm_ops = &drm_gem_cma_vm_ops, // GEM 内存管理的操作函数
.gem_free_object_unlocked = rockchip_gem_free_object, // 释放 GEM 对象的函数
.dumb_create = rockchip_gem_dumb_create, // 创建 Dumb Buffer 的函数
.prime_handle_to_fd = drm_gem_prime_handle_to_fd, // 处理 Prime 文件描述符的函数
.prime_fd_to_handle = drm_gem_prime_fd_to_handle, // 处理 Prime 文件描述符的函数
.gem_prime_get_sg_table = rockchip_gem_prime_get_sg_table, // 处理 GEM 对象的 scatter-gather 表的函数
.gem_prime_import_sg_table = rockchip_gem_prime_import_sg_table, // 处理 GEM 对象的 scatter-gather 表的函数
.gem_prime_vmap = rockchip_gem_prime_vmap, // 映射 GEM 对象的函数
.gem_prime_vunmap = rockchip_gem_prime_vunmap, // 取消映射 GEM 对象的函数
.gem_prime_mmap = rockchip_gem_mmap_buf, // GEM 对象的内存映射操作的函数
.fops = &rockchip_drm_driver_fops, // 文件操作相关的函数指针
.name = DRIVER_NAME, // 驱动程序的名称
.desc = DRIVER_DESC, // 驱动程序的描述
.date = DRIVER_DATE, // 驱动程序的日期
.major = DRIVER_MAJOR, // 驱动程序的主版本号
.minor = DRIVER_MINOR, // 驱动程序的次版本号
};
// rockchip_drm_unbind 函数用于解绑 Rockchip DRM 驱动。
static void rockchip_drm_unbind(struct device *dev)
{
struct drm_device *drm_dev = dev_get_drvdata(dev); // 获取与设备关联的 DRM 设备指针
// 注销 DRM 设备。
drm_dev_unregister(drm_dev);
// 结束 Rockchip DRM Framebuffer 设备。
rockchip_drm_fbdev_fini(drm_dev);
// 结束 DRM KMS Helper 轮询机制。
drm_kms_helper_poll_fini(drm_dev);
// 关闭 DRM 原子模式帮助器。
drm_atomic_helper_shutdown(drm_dev);
// 解绑所有子设备。
component_unbind_all(dev, drm_dev);
// 清理 Rockchip IOMMU。
rockchip_iommu_cleanup(drm_dev);
// 释放 DRM 设备。
drm_dev_put(drm_dev);
}
// rockchip_drm_bind 函数用于绑定 Rockchip DRM 驱动到设备。
static int rockchip_drm_bind(struct device *dev)
{
struct drm_device *drm_dev; // 用于表示 DRM 设备的结构体指针
struct rockchip_drm_private *private; // 指向 Rockchip DRM 驱动的私有数据结构的指针
int ret; // 函数返回值
// 为 DRM 设备分配内存,使用 rockchip_drm_driver 作为驱动程序并与给定的设备(dev)相关联。
drm_dev = drm_dev_alloc(&rockchip_drm_driver, dev);
if (IS_ERR(drm_dev))
return PTR_ERR(drm_dev);
// 设置设备的私有数据,以便稍后可以访问 Rockchip DRM 驱动的私有数据。
dev_set_drvdata(dev, drm_dev);
// 为 Rockchip DRM 驱动的私有数据分配内存。
private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL);
if (!private) {
ret = -ENOMEM;
goto err_free;
}
// 将 Rockchip DRM 驱动的私有数据指针与 DRM 设备关联。
drm_dev->dev_private = private;
// 初始化私有数据中的 psr_list 列表和 psr_list_lock 互斥锁。
INIT_LIST_HEAD(&private->psr_list);
mutex_init(&private->psr_list_lock);
// 初始化 IOMMU,如果启用的话。
ret = rockchip_drm_init_iommu(drm_dev);
if (ret)
goto err_free;
// 初始化 DRM 模式配置。
ret = drmm_mode_config_init(drm_dev);
if (ret)
goto err_iommu_cleanup;
// 初始化 Rockchip DRM 特定的模式配置。
rockchip_drm_mode_config_init(drm_dev);
// 尝试绑定所有子驱动。
ret = component_bind_all(dev, drm_dev);
if (ret)
goto err_iommu_cleanup;
// 初始化 DRM 垂直空白间隔(VBlank)机制。
ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
if (ret)
goto err_unbind_all;
// 重置 DRM 模式配置。
drm_mode_config_reset(drm_dev);
// 启用 DRM 中断模式,以便可以使用垂直空白间隔(VBlank)功能。
drm_dev->irq_enabled = true;
// 初始化 Rockchip DRM Framebuffer 设备。
ret = rockchip_drm_fbdev_init(drm_dev);
if (ret)
goto err_unbind_all;
// 初始化 KMS 轮询以处理热插拔事件(HPD)。
drm_kms_helper_poll_init(drm_dev);
// 注册 DRM 设备。
ret = drm_dev_register(drm_dev, 0);
if (ret)
goto err_kms_helper_poll_fini;
return 0;
err_kms_helper_poll_fini:
// 处理错误情况:清理并释放资源
drm_kms_helper_poll_fini(drm_dev);
rockchip_drm_fbdev_fini(drm_dev);
err_unbind_all:
component_unbind_all(dev, drm_dev);
err_iommu_cleanup:
// 清理 Rockchip IOMMU 并返回错误码。
rockchip_iommu_cleanup(drm_dev);
err_free:
// 释放资源并返回错误码。
drm_dev_put(drm_dev);
return ret;
}
// 定义组件主设备操作,包括绑定和解绑子设备的函数指针。
static const struct component_master_ops rockchip_drm_ops = {
.bind = rockchip_drm_bind, // 绑定子设备的函数指针
.unbind = rockchip_drm_unbind // 解绑子设备的函数指针
};
// 在设备上添加组件匹配,用于匹配子设备。
static struct component_match *rockchip_drm_match_add(struct device *dev)
{
struct component_match *match = NULL; // 用于存储组件匹配的结构体指针
int i;
// 遍历所有 Rockchip 子驱动程序,查找匹配的子设备。
for (i = 0; i < num_rockchip_sub_drivers; i++) {
struct platform_driver *drv = rockchip_sub_drivers[i]; // 获取当前的平台驱动程序
struct device *p = NULL, *d;
do {
// 使用平台驱动程序查找设备。
d = platform_find_device_by_driver(p, &drv->driver);
put_device(p);
p = d;
if (!d)
break;
// 将设备添加到设备链表中,并进行组件匹配。
device_link_add(dev, d, DL_FLAG_STATELESS);
component_match_add(dev, &match, compare_dev, d);
} while (true);
}
// 如果组件匹配失败,删除设备链接。
if (IS_ERR(match))
rockchip_drm_match_remove(dev);
// 返回匹配结果或错误码。
return match ?: ERR_PTR(-ENODEV);
}
// 检查设备的设备树节点以查找子设备和 IOMMU 设置。
static int rockchip_drm_platform_of_probe(struct device *dev)
{
struct device_node *np = dev->of_node; // 获取设备的设备树节点
struct device_node *port;
bool found = false;
int i;
// 如果设备没有设备树节点,返回错误。
if (!np)
return -ENODEV;
for (i = 0;; i++) {
struct device_node *iommu;
// 解析设备树节点以查找 "ports" 属性。
port = of_parse_phandle(np, "ports", i);
if (!port)
break;
// 检查父节点是否可用,如果不可用,则跳过。
if (!of_device_is_available(port->parent)) {
of_node_put(port);
continue;
}
// 解析 "iommus" 属性以查找 IOMMU 设置。
iommu = of_parse_phandle(port->parent, "iommus", 0);
if (!iommu || !of_device_is_available(iommu->parent)) {
DRM_DEV_DEBUG(dev,
"no iommu attached for %pOF, using non-iommu buffers\n",
port->parent);
/*
* 如果有不支持 IOMMU 的 CRTCs,强制将所有 CRTCs 设置为使用非 IOMMU 缓冲区。
*/
is_support_iommu = false;
}
found = true;
of_node_put(iommu);
of_node_put(port);
}
// 如果没有找到任何 "ports" 属性,报告错误。
if (i == 0) {
DRM_DEV_ERROR(dev, "missing 'ports' property\n");
return -ENODEV;
}
// 如果没有可用的 VOP(Video Output Processor),报告错误。
if (!found) {
DRM_DEV_ERROR(dev,
"No available vop found for display-subsystem.\n");
return -ENODEV;
}
return 0;
}
// rockchip_drm_platform_probe 函数用于在平台设备上进行 Rockchip DRM 驱动的探测。
static int rockchip_drm_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev; // 获取平台设备的设备结构体
struct component_match *match = NULL; // 用于存储组件匹配的结构体指针
int ret;
// 调用 rockchip_drm_platform_of_probe 函数进行设备树探测。
ret = rockchip_drm_platform_of_probe(dev);
if (ret)
return ret;
// 添加组件匹配。
match = rockchip_drm_match_add(dev);
if (IS_ERR(match))
return PTR_ERR(match);
// 使用组件匹配创建组件主设备,绑定函数在这里调用。
ret = component_master_add_with_match(dev, &rockchip_drm_ops, match);
if (ret < 0) {
rockchip_drm_match_remove(dev);
return ret;
}
return 0;
}
本文件实现的功能,可以用下图表示:
CRTC & Panel
/* 在rockchip_drm_drv.c里 */
const struct component_ops vop_component_ops = {
.bind = vop_bind,
.unbind = vop_unbind,
};
static void vop_unbind(struct device *dev, struct device *master, void *data)
{
struct vop *vop = dev_get_drvdata(dev);
if (vop->rgb)
rockchip_rgb_fini(vop->rgb);
pm_runtime_disable(dev);
vop_destroy_crtc(vop);
clk_unprepare(vop->aclk);
clk_unprepare(vop->hclk);
clk_unprepare(vop->dclk);
}
static int vop_bind(struct device *dev, struct device *master, void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm_dev = data;
irq = platform_get_irq(pdev, 0);
/* 这里创建CRTC设备 */
ret = vop_create_crtc(vop);
pm_runtime_enable(&pdev->dev);
/* 初始化VOP IP */
ret = vop_initial(vop);
ret = devm_request_irq(dev, vop->irq, vop_isr,
return ret;
}
// 初始化 drm_plane_helper_funcs 结构体
static const struct drm_plane_helper_funcs plane_helper_funcs = {
.atomic_check = vop_plane_atomic_check, // 在原子模式下检查平面状态,通常在显示模式变更前调用。
.atomic_update = vop_plane_atomic_update, // 在原子模式下更新平面状态,通常在页面翻转后调用。
.atomic_disable = vop_plane_atomic_disable, // 在原子模式下禁用平面,通常在页面翻转或显示模式变更前调用。
.atomic_async_check = vop_plane_atomic_async_check, // 异步检查平面状态,用于非垂直空白同步的平面更新。
.atomic_async_update = vop_plane_atomic_async_update, // 异步更新平面状态,用于非垂直空白同步的平面更新。
.prepare_fb = drm_gem_fb_prepare_fb, // 准备帧缓冲以供扫描输出,通常在页面翻转前调用。
};
// 初始化 drm_plane_funcs 结构体
static const struct drm_plane_funcs vop_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane, // 更新平面状态,通常在页面翻转后调用。
.disable_plane = drm_atomic_helper_disable_plane, // 禁用平面,通常在页面翻转前或显示模式变更时调用。
.destroy = vop_plane_destroy, // 销毁平面,通常在设备或模块被卸载或销毁时调用。
.reset = drm_atomic_helper_plane_reset, // 重置平面状态,通常在初始化或重新初始化平面时使用。
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, // 复制平面状态,通常与原子模式设置相关,用于备份当前状态。
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state, // 销毁平面状态,通常在不再需要状态时释放相关资源。
.format_mod_supported = rockchip_mod_supported, // 检查图像格式和修饰符的支持情况,通常在设置图像格式时调用。
};
// 初始化 drm_crtc_helper_funcs 结构体
static const struct drm_crtc_helper_funcs vop_crtc_helper_funcs = {
.mode_fixup = vop_crtc_mode_fixup, // 修复显示模式,通常用于调整显示模式以适应硬件要求。
.atomic_check = vop_crtc_atomic_check, // 原子检查 CRTC 状态是否合法,通常在显示模式变更前调用。
.atomic_begin = vop_crtc_atomic_begin, // 原子开始更新操作,通常在执行原子模式设置时调用。
.atomic_flush = vop_crtc_atomic_flush, // 原子刷新操作,通常在原子更新的提交时调用。
.atomic_enable = vop_crtc_atomic_enable, // 原子启用 CRTC 的显示,通常在显示模式变更后调用。
.atomic_disable = vop_crtc_atomic_disable, // 原子禁用 CRTC 的显示,通常在显示模式变更前或设备关闭时调用。
};
static const struct drm_crtc_funcs vop_crtc_funcs = {
.set_config = drm_atomic_helper_set_config, // 在配置CRTC的显示模式和帧缓冲时被调用,通常在显示模式变更时触发。
.page_flip = drm_atomic_helper_page_flip, // 处理页面翻转请求,即切换显示到新的帧缓冲。当用户请求切换到新的图像帧时,这个函数会被调用。通常与垂直空白中断(VBlank)相关。
.destroy = vop_crtc_destroy, // 用于销毁CRTC相关的资源和数据结构,通常在设备或模块被卸载或销毁时调用。
.reset = vop_crtc_reset, // 在需要将CRTC重置为其默认状态时被调用,通常在初始化或重新初始化CRTC时使用。
.atomic_duplicate_state = vop_crtc_duplicate_state, // 用于复制CRTC的状态,通常与原子模式设置相关。在执行原子模式设置时,需要复制CRTC的状态以备份当前状态。
.atomic_destroy_state = vop_crtc_destroy_state, // 用于销毁CRTC的状态,通常与原子模式设置相关。在不再需要状态时,需要释放相关资源。
.enable_vblank = vop_crtc_enable_vblank, // 用于启用垂直空白中断(VBlank),以便在显示垂直空白期间执行特定任务,如页面翻转同步。
.disable_vblank = vop_crtc_disable_vblank, // 用于禁用垂直空白中断(VBlank),停止在垂直空白期间执行特定任务。
.set_crc_source = vop_crtc_set_crc_source, // 配置CRC(Cyclic Redundancy Check)计算的数据源,通常用于在显示过程中检测图像数据的完整性。
.verify_crc_source = vop_crtc_verify_crc_source, // 验证CRC计算的数据源,用于确保CRC计算的数据是正确的。
.gamma_set = drm_atomic_helper_legacy_gamma_set, // 配置伽马校正,允许调整显示的颜色和亮度。通常用于色彩校正和图像质量调整。
};
static int vop_create_crtc(struct vop *vop)
{
const struct vop_data *vop_data = vop->data;
struct device *dev = vop->dev;
struct drm_device *drm_dev = vop->drm_dev;
struct drm_plane *primary = NULL, *cursor = NULL, *plane, *tmp;
struct drm_crtc *crtc = &vop->crtc;
struct device_node *port;
int ret;
int i;
/*
* 首先创建主要和光标平面的 drm_plane,因为需要将它们传递给
* drm_crtc_init_with_planes,该函数会设置 "possible_crtcs" 为
* 新初始化的 crtc。
*/
for (i = 0; i < vop_data->win_size; i++) {
struct vop_win *vop_win = &vop->win[i];
const struct vop_win_data *win_data = vop_win->data;
if (win_data->type != DRM_PLANE_TYPE_PRIMARY &&
win_data->type != DRM_PLANE_TYPE_CURSOR)
continue;
ret = drm_universal_plane_init(vop->drm_dev, &vop_win->base,
0, &vop_plane_funcs,
win_data->phy->data_formats,
win_data->phy->nformats,
win_data->phy->format_modifiers,
win_data->type, NULL);
if (ret) {
DRM_DEV_ERROR(vop->dev, "failed to init plane %d\n",
ret);
goto err_cleanup_planes;
}
plane = &vop_win->base;
/* 用于向 DRM(Direct Rendering Manager)平面添加平面帮助函数,这些帮助函数可以协助平面的各种操作和配置。 */
drm_plane_helper_add(plane, &plane_helper_funcs);
/* 自定义的函数,用于向平面对象添加特定于 vop 设备的属性。 */
vop_plane_add_properties(plane, win_data);
if (plane->type == DRM_PLANE_TYPE_PRIMARY)
primary = plane;
else if (plane->type == DRM_PLANE_TYPE_CURSOR)
cursor = plane;
}
/* 函数的作用是根据提供的参数,将 DRM CRTC 对象 crtc 初始化并与相关的平面(主要平面和光标平面)关联起来。
* 这些平面将被该 CRTC 控制和管理。初始化后,CRTC 就准备好接受显示数据,并根据配置开始渲染图形内容。
* CRTC 在图形显示管道中起着关键作用,负责生成图像信号,将图像传递到显示设备并控制显示属性。
* 通过调用 drm_crtc_init_with_planes,开发者可以将 CRTC 与平面对象关联,
* 使其能够在显示管道中正确地处理图形数据,包括选择适当的平面来显示图像内容。
* 这是图形驱动程序中非常重要的一步,确保图形渲染和显示正常工作。 */
ret = drm_crtc_init_with_planes(drm_dev, crtc, primary, cursor,
&vop_crtc_funcs, NULL);
if (ret)
goto err_cleanup_planes;
/* drm_crtc_helper_add 函数用于向 CRTC(显示控制器)对象添加帮助函数,
* 这些函数通常用于协助 CRTC 的配置和操作。
* crtc 是一个指向 drm_crtc 结构体的指针,表示要添加帮助函数的 CRTC 对象。
* &vop_crtc_helper_funcs 是一个指向 CRTC 帮助函数的函数指针结构体,
* 其中包含了各种与 CRTC 相关的操作的实现。这些操作包括模式设置、帧缓冲管理、显示控制等等。
* 通过调用 drm_crtc_helper_add,CRTC 对象 crtc 将获得一组通用的 CRTC 操作函数,
* 这些函数可用于协助 CRTC 的各种配置和操作,以便在显示过程中提供更灵活的管理和控制。
*/
drm_crtc_helper_add(crtc, &vop_crtc_helper_funcs);
if (vop->lut_regs) {
drm_mode_crtc_set_gamma_size(crtc, vop_data->lut_size);
drm_crtc_enable_color_mgmt(crtc, 0, false, vop_data->lut_size);
}
/*
* 创建 overlay 窗口的 drm_planes,并将 "possible_crtcs" 限制为
* 新创建的 crtc。
*/
for (i = 0; i < vop_data->win_size; i++) {
struct vop_win *vop_win = &vop->win[i];
const struct vop_win_data *win_data = vop_win->data;
unsigned long possible_crtcs = drm_crtc_mask(crtc);
if (win_data->type != DRM_PLANE_TYPE_OVERLAY)
continue;
/* 初始化plan */
ret = drm_universal_plane_init(vop->drm_dev, &vop_win->base,
possible_crtcs,
&vop_plane_funcs,
win_data->phy->data_formats,
win_data->phy->nformats,
win_data->phy->format_modifiers,
win_data->type, NULL);
if (ret) {
DRM_DEV_ERROR(vop->dev, "failed to init overlay %d\n",
ret);
goto err_cleanup_crtc;
}
/* 主要功能是将平面对象和相关的辅助函数连接起来,以便在使用平面时能够调用正确的操作函数 */
drm_plane_helper_add(&vop_win->base, &plane_helper_funcs);
/* 调用drm_plane_create_rotation_property 函数
* 是用于创建 DRM 平面(DRM Plane)的旋转属性的函数。
* 旋转属性允许用户或应用程序以动态方式控制平面上图像的旋转效果。
* 这对于支持旋转功能的显示设备非常有用,因为它们可以根据需要旋转图像,
* 以适应不同的方向或布局。 */
vop_plane_add_properties(&vop_win->base, win_data);
}
port = of_get_child_by_name(dev->of_node, "port");
if (!port) {
DRM_DEV_ERROR(vop->dev, "no port node found in %pOF\n",
dev->of_node);
ret = -ENOENT;
goto err_cleanup_crtc;
}
/* 函数是用于减少 CRTC(Cathode Ray Tube Controller)的垂直空白中断引用计数的函数。
* 在 DRM 中,CRTC 是一个用于控制显示输出的硬件块,它负责将图像数据发送到显示设备(例如液晶屏幕)。
* 垂直空白中断(VBlank)是显示设备在垂直方向上返回到屏幕顶部的时间点,通常在显示一个完整的帧之后。
* 当某个代码块或模块需要访问 CRTC 时,它会增加 CRTC 的垂直空白中断引用计数,
* 以确保在使用期间 CRTC 不会被释放。一旦完成对 CRTC 的操作,就需要通过调用 drm_crtc_vblank_put
* 函数来减少引用计数,以允许 CRTC 在不再被使用时被释放。
*
* drm_framebuffer_put 函数用于减少帧缓冲(Framebuffer)对象的引用计数。
* 在 DRM 中,帧缓冲对象用于存储要显示在屏幕上的图像数据。
* 应用程序或内核模块在使用帧缓冲对象时会增加其引用计数,以防止对象在使用期间被销毁。
* 当使用完帧缓冲对象后,需要调用 drm_framebuffer_put 函数来减少引用计数,
* 以允许对象在不再被引用时被释放。
*
* drm_flip_work_init 函数是用于初始化异步工作队列的函数,它与图形帧缓冲切换相关。
* 在 DRM 中,帧缓冲切换是指在屏幕上显示不同的图像或帧。
* 这个函数创建了一个异步工作队列项(drm_flip_work),用于在后台执行与帧缓冲对象释放相关的工作。
*/
drm_flip_work_init(&vop->fb_unref_work, "fb_unref",
vop_fb_unref_worker);
/* init_completion 函数是Linux内核中的一个同步原语,用于初始化一个 "完成" 结构体,
* 用于线程同步。这个结构体用于在线程之间进行等待和通知操作,以确保某个特定事件的发生。
*/
init_completion(&vop->dsp_hold_completion);
init_completion(&vop->line_flag_completion);
crtc->port = port;
/* 主要作用是初始化自刷新功能,并将其与 DRM CRTC 或显示管道相关联。自刷新低功耗? */
ret = drm_self_refresh_helper_init(crtc);
if (ret)
DRM_DEV_DEBUG_KMS(vop->dev,
"Failed to init %s with SR helpers %d, ignoring\n",
crtc->name, ret);
return 0;
err_cleanup_crtc:
drm_crtc_cleanup(crtc);
err_cleanup_planes:
list_for_each_entry_safe(plane, tmp, &drm_dev->mode_config.plane_list,
head)
drm_plane_cleanup(plane);
return ret;
}
GEM
/* 在rockchip_drm_drv.c文件中为rockchip_drm_driver赋值了hook函数,其中gem相关本文件实现 */
static struct drm_driver rockchip_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.lastclose = drm_fb_helper_lastclose,
.gem_vm_ops = &drm_gem_cma_vm_ops,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_free_object_unlocked = rockchip_gem_free_object,
.dumb_create = rockchip_gem_dumb_create,
.gem_prime_get_sg_table = rockchip_gem_prime_get_sg_table,
.gem_prime_import_sg_table = rockchip_gem_prime_import_sg_table,
.gem_prime_vmap = rockchip_gem_prime_vmap,
.gem_prime_vunmap = rockchip_gem_prime_vunmap,
.gem_prime_mmap = rockchip_gem_mmap_buf,
........
};
static const struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
.destroy = drm_gem_fb_destroy, // 注册帧缓冲销毁回调函数
.create_handle = drm_gem_fb_create_handle, // 注册帧缓冲创建句柄回调函数
.dirty = drm_atomic_helper_dirtyfb, // 注册帧缓冲标记为"脏"的回调函数
};
static struct drm_framebuffer *
rockchip_fb_alloc(struct drm_device *dev, const struct drm_mode_fb_cmd2 *mode_cmd,
struct drm_gem_object **obj, unsigned int num_planes)
{
struct drm_framebuffer *fb;
int ret;
int i;
fb = kzalloc(sizeof(*fb), GFP_KERNEL); // 分配帧缓冲结构内存
if (!fb)
return ERR_PTR(-ENOMEM); // 内存分配失败,返回错误码
drm_helper_mode_fill_fb_struct(dev, fb, mode_cmd); // 使用模式信息填充帧缓冲结构
for (i = 0; i < num_planes; i++)
fb->obj[i] = obj[i]; // 将 gem 对象与帧缓冲关联
ret = drm_framebuffer_init(dev, fb, &rockchip_drm_fb_funcs); // 初始化帧缓冲
if (ret) {
DRM_DEV_ERROR(dev->dev,
"Failed to initialize framebuffer: %d\n",
ret);
kfree(fb); // 初始化失败,释放内存
return ERR_PTR(ret); // 返回错误码
}
return fb; // 返回初始化成功的帧缓冲结构
}
static const struct drm_mode_config_helper_funcs rockchip_mode_config_helpers = {
.atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, // 注册 atomic commit 尾部回调函数
};
static struct drm_framebuffer *
rockchip_fb_create(struct drm_device *dev, struct drm_file *file,
const struct drm_mode_fb_cmd2 *mode_cmd)
{
struct drm_afbc_framebuffer *afbc_fb;
const struct drm_format_info *info;
int ret;
info = drm_get_format_info(dev, mode_cmd); // 获取格式信息
if (!info)
return ERR_PTR(-ENOMEM); // 格式信息获取失败,返回错误码
afbc_fb = kzalloc(sizeof(*afbc_fb), GFP_KERNEL); // 分配 AFBC 帧缓冲结构内存
if (!afbc_fb)
return ERR_PTR(-ENOMEM); // 内存分配失败,返回错误码
ret = drm_gem_fb_init_with_funcs(dev, &afbc_fb->base, file, mode_cmd,
&rockchip_drm_fb_funcs); // 使用回调函数初始化 AFBC 帧缓冲
if (ret) {
kfree(afbc_fb); // 初始化失败,释放内存
return ERR_PTR(ret); // 返回错误码
}
if (drm_is_afbc(mode_cmd->modifier[0])) {
int ret, i;
ret = drm_gem_fb_afbc_init(dev, mode_cmd, afbc_fb); // 初始化 AFBC 帧缓冲
if (ret) {
struct drm_gem_object **obj = afbc_fb->base.obj;
for (i = 0; i < info->num_planes; ++i)
drm_gem_object_put(obj[i]);
kfree(afbc_fb); // 初始化失败,释放内存
return ERR_PTR(ret); // 返回错误码
}
}
return &afbc_fb->base; // 返回初始化成功的 AFBC 帧缓冲结构
}
static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
.fb_create = rockchip_fb_create, // 注册创建帧缓冲的回调函数
.output_poll_changed = drm_fb_helper_output_poll_changed, // 注册输出轮询变更回调函数
.atomic_check = drm_atomic_helper_check, // 注册 atomic 检查回调函数
.atomic_commit = drm_atomic_helper_commit, // 注册 atomic 提交回调函数
};
struct drm_framebuffer *
rockchip_drm_framebuffer_init(struct drm_device *dev,
const struct drm_mode_fb_cmd2 *mode_cmd,
struct drm_gem_object *obj)
{
struct drm_framebuffer *fb;
fb = rockchip_fb_alloc(dev, mode_cmd, &obj, 1); // 分配并初始化帧缓冲
if (IS_ERR(fb))
return ERR_CAST(fb);
return fb; // 返回初始化成功的帧缓冲结构
}
void rockchip_drm_mode_config_init(struct drm_device *dev)
{
dev->mode_config.min_width = 0; // 设置最小宽度
dev->mode_config.min_height = 0; // 设置最小高度
/*
* 设置默认的最大宽度和高度(4096x4096)。
* 这个值将在 drm_mode_addfb() 中用于检查帧缓冲大小限制。
*/
dev->mode_config.max_width = 4096;
dev->mode_config.max_height = 4096;
dev->mode_config.funcs = &rockchip_drm_mode_config_funcs; // 设置模式配置函数
dev->mode_config.helper_private = &rockchip_mode_config_helpers; // 设置模式配置助手
}
``
static int rockchip_fbdev_mmap(struct fb_info *info,
struct vm_area_struct *vma)
{
struct drm_fb_helper *helper = info->par; // 获取 DRM 帧缓冲助手
struct rockchip_drm_private *private = to_drm_private(helper); // 获取 Rockchip DRM 私有数据
return rockchip_gem_mmap_buf(private->fbdev_bo, vma); // 调用 Rockchip DRM 驱动的 gem 内存映射函数
}
static const struct fb_ops rockchip_drm_fbdev_ops = {
.owner = THIS_MODULE, // 设置模块所有者
DRM_FB_HELPER_DEFAULT_OPS, // 设置 DRM 帧缓冲助手默认操作函数
.fb_mmap = rockchip_fbdev_mmap, // 设置内存映射回调函数
.fb_fillrect = drm_fb_helper_cfb_fillrect, // 设置填充矩形回调函数
.fb_copyarea = drm_fb_helper_cfb_copyarea, // 设置复制区域回调函数
.fb_imageblit = drm_fb_helper_cfb_imageblit, // 设置图像传输回调函数
};
static int rockchip_drm_fbdev_create(struct drm_fb_helper *helper,
struct drm_fb_helper_surface_size *sizes)
{
// 省略部分代码...
return 0;
}
static const struct drm_fb_helper_funcs rockchip_drm_fb_helper_funcs = {
.fb_probe = rockchip_drm_fbdev_create, // 设置帧缓冲探测回调函数
};
int rockchip_drm_fbdev_init(struct drm_device *dev)
{
struct rockchip_drm_private *private = dev->dev_private; // 获取 Rockchip DRM 私有数据
struct drm_fb_helper *helper;
int ret;
if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector)
return -EINVAL;
helper = &private->fbdev_helper; // 获取帧缓冲助手
drm_fb_helper_prepare(dev, helper, &rockchip_drm_fb_helper_funcs); // 初始化帧缓冲助手
ret = drm_fb_helper_init(dev, helper); // 初始化帧缓冲助手
if (ret < 0) {
DRM_DEV_ERROR(dev->dev,
"Failed to initialize drm fb helper - %d.\n",
ret);
return ret;
}
ret = drm_fb_helper_initial_config(helper, PREFERRED_BPP); // 设置初始硬件配置
if (ret < 0) {
DRM_DEV_ERROR(dev->dev,
"Failed to set initial hw config - %d.\n",
ret);
goto err_drm_fb_helper_fini;
}
return 0;
err_drm_fb_helper_fini:
drm_fb_helper_fini(helper); // 完成帧缓冲助手的清理工作
return ret;
}
void rockchip_drm_fbdev_fini(struct drm_device *dev)
{
struct rockchip_drm_private *private = dev->dev_private; // 获取 Rockchip DRM 私有数据
struct drm_fb_helper *helper;
helper = &private->fbdev_helper; // 获取帧缓冲助手
drm_fb_helper_unregister_fbi(helper); // 注销帧缓冲助手的 FBI
if (helper->fb)
drm_framebuffer_put(helper->fb); // 释放帧缓冲
drm_fb_helper_fini(helper); // 完成帧缓冲助手的清理工作
}
Encoder & Connector
HDMI
/* 由rockchip_drm_drv.c进行注册 */
struct platform_driver rk3066_hdmi_driver = {
.probe = rk3066_hdmi_probe,
.remove = rk3066_hdmi_remove,
.driver = {
.name = "rockchip-rk3066-hdmi",
.of_match_table = rk3066_hdmi_dt_ids,
},
};
/* 向DEV加入子组件 */
static int rk3066_hdmi_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &rk3066_hdmi_ops);
}
/* 同样实现bind和unbind回调函数 */
static const struct component_ops rk3066_hdmi_ops = {
.bind = rk3066_hdmi_bind,
.unbind = rk3066_hdmi_unbind,
};
static int rk3066_hdmi_bind(struct device *dev, struct device *master,
void *data)
{
/* 上面的平台设备驱动和硬件信息就不关心了 */
...............................................
/* 获取iic作为HDMI的DDC,用来读取edid */
hdmi->ddc = rk3066_hdmi_i2c_adapter(hdmi);
/* 硬件配置 */
rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_B);
usleep_range(999, 1000);
hdmi_writeb(hdmi, HDMI_INTR_MASK1, HDMI_INTR_HOTPLUG);
hdmi_writeb(hdmi, HDMI_INTR_MASK2, 0);
hdmi_writeb(hdmi, HDMI_INTR_MASK3, 0);
hdmi_writeb(hdmi, HDMI_INTR_MASK4, 0);
/* 同样也是硬件配置 */
rk3066_hdmi_set_power_mode(hdmi, HDMI_SYS_POWER_MODE_A);
/* 这里是很关键的DRM设备注册 */
ret = rk3066_hdmi_register(drm, hdmi);
if (ret)
goto err_disable_i2c;
dev_set_drvdata(dev, hdmi);
/* 申请注册中断,HDMI支持热插拔,中断上来后,通过DDC获取EDID,解析EDID,将显示器的分辨率等信息记录到DRM */
ret = devm_request_threaded_irq(dev, irq, rk3066_hdmi_hardirq,
rk3066_hdmi_irq, IRQF_SHARED,
dev_name(dev), hdmi);
return ret;
}
static int rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)
{
struct drm_encoder *encoder = &hdmi->encoder; // 获取HDMI设备的编码器
struct device *dev = hdmi->dev; // 获取HDMI设备的设备结构体指针
// 查找可能的CRTC(CRTC是视频显示控制器)并分配给编码器
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
/*
* 如果无法找到与此编码器应连接的CRTC,那是因为CRTC尚未注册。
* 推迟探测并希望所需的CRTC稍后会被添加。
*/
if (encoder->possible_crtcs == 0)
return -EPROBE_DEFER; // 推迟探测,等待CRTC注册
// 添加编码器的辅助函数和初始化编码器
drm_encoder_helper_add(encoder, &rk3066_hdmi_encoder_helper_funcs);
/* DRM_MODE_ENCODER_TMDS表示TMDS类编码 */
drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
// 配置HDMI连接器为通过HPD(Hot Plug Detect)来轮询连接状态
hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
// 添加HDMI连接器的辅助函数,初始化连接器并指定连接器类型为HDMI
drm_connector_helper_add(&hdmi->connector,
&rk3066_hdmi_connector_helper_funcs);
drm_connector_init_with_ddc(drm, &hdmi->connector,
&rk3066_hdmi_connector_funcs,
DRM_MODE_CONNECTOR_HDMIA,
hdmi->ddc);
// 将连接器附加到编码器
drm_connector_attach_encoder(&hdmi->connector, encoder);
return 0; // 返回成功
}
/* encoder的helper */
static const struct drm_encoder_helper_funcs rk3066_hdmi_encoder_helper_funcs = {
/* 硬件使能encoder */
.enable = rk3066_hdmi_encoder_enable,
/* 硬件失能encoder */
.disable = rk3066_hdmi_encoder_disable,
.mode_fixup = rk3066_hdmi_encoder_mode_fixup,
.mode_set = rk3066_hdmi_encoder_mode_set,
.atomic_check = rk3066_hdmi_encoder_atomic_check,
};
/* connector的funcs */
static const struct drm_connector_funcs rk3066_hdmi_connector_funcs = {
/* 里面调用了drm_helper_probe_single_connector_modes函数,
* drm_helper_probe_single_connector_modes 是Linux DRM(Direct Rendering Manager)框架中的一个函数,
* 用于帮助探测单个连接器(通常是显示设备的连接接口,如HDMI、VGA、DP等)支持的显示模式。
* 该函数的主要作用如下:
* 1. 探测连接器支持的显示模式:函数会尝试获取连接器的EDID(扩展显示标识数据),
* 这是一组描述连接器支持的显示模式、分辨率、刷新率等信息的数据。通过解析EDID数据,
* 函数可以确定连接器的最佳显示模式以及其他支持的备选显示模式。
* 2. 填充连接器的显示模式列表:一旦成功解析EDID数据,函数将根据EDID中的信息创建一个显示模式列表,
* 其中包含连接器支持的各种显示模式。这些显示模式可以包括不同的分辨率、刷新率、色深等。
* 3. 返回显示模式列表:函数返回填充好的显示模式列表,供DRM子系统和显示管理器使用。
* 这些显示模式可以用于配置和控制连接器,以便在显示设备上正确设置所需的显示模式。
*/
.fill_modes = rk3066_hdmi_probe_single_connector_modes,
/* 获取HDMI的链接状态 */
.detect = rk3066_hdmi_connector_detect,
.destroy = rk3066_hdmi_connector_destroy,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static const struct drm_connector_helper_funcs rk3066_hdmi_connector_helper_funcs = {
/* 根据EDID获取所支持的分辨率 */
.get_modes = rk3066_hdmi_connector_get_modes,
/* 根据CEA的标准,判断此分辨率是否有效 */
.mode_valid = rk3066_hdmi_connector_mode_valid,
.best_encoder = rk3066_hdmi_connector_best_encoder,
};
mipi
/* 由rockchip_drm_drv进行注册 */
struct platform_driver dw_mipi_dsi_rockchip_driver = {
.probe = dw_mipi_dsi_rockchip_probe,
.remove = dw_mipi_dsi_rockchip_remove,
.driver = {
.of_match_table = dw_mipi_dsi_rockchip_dt_ids,
.name = "dw-mipi-dsi-rockchip",
},
};
static const struct dw_mipi_dsi_host_ops dw_mipi_dsi_rockchip_host_ops = {
.attach = dw_mipi_dsi_rockchip_host_attach,
.detach = dw_mipi_dsi_rockchip_host_detach,
};
static const struct dw_mipi_dsi_phy_ops dw_mipi_dsi_rockchip_phy_ops = {
.init = dw_mipi_dsi_phy_init,
.power_on = dw_mipi_dsi_phy_power_on,
.power_off = dw_mipi_dsi_phy_power_off,
.get_lane_mbps = dw_mipi_dsi_get_lane_mbps,
.get_timing = dw_mipi_dsi_phy_get_timing,
};
static int dw_mipi_dsi_rockchip_probe(struct platform_device *pdev)
{
................................
/* 填充DSI相关的信息 */
dsi->dev = dev;
dsi->pdata.base = dsi->base;
dsi->pdata.max_data_lanes = dsi->cdata->max_data_lanes;
/* mipi phy的硬件操作相关函数 */
dsi->pdata.phy_ops = &dw_mipi_dsi_rockchip_phy_ops;
/* DRM的attach和detach操作,这里的回调函数将由DRM框架中的DSI函数调用 */
dsi->pdata.host_ops = &dw_mipi_dsi_rockchip_host_ops;
dsi->dmd = dw_mipi_dsi_probe(pdev, &dsi->pdata);
................................
return ret;
}
/**
* dw_mipi_dsi_rockchip_host_attach - 连接DSI主机设备
* @priv_data: 指向与主机设备关联的私有数据的指针。
* @device: 指向正在连接的MIPI DSI设备的指针。
*
* 此函数负责将MIPI DSI主机设备连接到驱动程序,并将其注册到组件框架中,以便根据需要对其进行电源管理。
*
* @return: 成功返回0,失败返回负数错误代码。
*/
static int dw_mipi_dsi_rockchip_host_attach(void *priv_data,
struct mipi_dsi_device *device)
{
struct dw_mipi_dsi_rockchip *dsi = priv_data; // 获取私有数据结构
struct device *second;
int ret;
// 使用组件框架将DSI主机设备注册
ret = component_add(dsi->dev, &dw_mipi_dsi_rockchip_ops);
if (ret) {
DRM_DEV_ERROR(dsi->dev, "组件注册失败: %d\n",
ret);
return ret;
}
// 查找第二个DSI主机设备(如果存在)
second = dw_mipi_dsi_rockchip_find_second(dsi);
if (IS_ERR(second))
return PTR_ERR(second);
if (second) {
// 使用组件框架将第二个DSI主机设备注册
ret = component_add(second, &dw_mipi_dsi_rockchip_ops);
if (ret) {
DRM_DEV_ERROR(second,
"组件注册失败: %d\n",
ret);
return ret;
}
}
return 0; // 成功
}
/**
* dw_mipi_dsi_rockchip_host_detach - 断开DSI主机设备的连接
* @priv_data: 指向与主机设备关联的私有数据的指针。
* @device: 指向正在断开连接的MIPI DSI设备的指针。
*
* 此函数负责断开与MIPI DSI主机设备的连接并从组件框架中注销它,以停止电源管理。
*
* @return: 成功返回0。
*/
static int dw_mipi_dsi_rockchip_host_detach(void *priv_data,
struct mipi_dsi_device *device)
{
struct dw_mipi_dsi_rockchip *dsi = priv_data; // 获取私有数据结构
struct device *second;
// 查找第二个DSI主机设备(如果存在)
second = dw_mipi_dsi_rockchip_find_second(dsi);
// 如果第二个DSI主机设备存在且未发生错误
if (second && !IS_ERR(second))
/* 关注下dw_mipi_dsi_rockchip_ops */
component_del(second, &dw_mipi_dsi_rockchip_ops); // 从组件框架中注销第二个DSI主机设备
component_del(dsi->dev, &dw_mipi_dsi_rockchip_ops); // 从组件框架中注销DSI主机设备
return 0; // 成功
}
static const struct dw_mipi_dsi_host_ops dw_mipi_dsi_rockchip_host_ops = {
.attach = dw_mipi_dsi_rockchip_host_attach,
.detach = dw_mipi_dsi_rockchip_host_detach,
};
/**
* dw_mipi_dsi_rockchip_bind - 绑定DSI主机设备到DRM子系统
* @dev: DSI主机设备的指针。
* @master: DSI主机设备的主设备。
* @data: 传递给函数的私有数据指针。
*
* 此函数用于将DSI主机设备绑定到DRM子系统,以便在显示系统中使用。
*
* @return: 成功返回0,失败返回错误代码。
*/
static int dw_mipi_dsi_rockchip_bind(struct device *dev,
struct device *master,
void *data)
{
struct dw_mipi_dsi_rockchip *dsi = dev_get_drvdata(dev); // 获取DSI主机设备的私有数据结构
struct drm_device *drm_dev = data;
struct device *second;
bool master1, master2;
int ret;
ret = clk_prepare_enable(dsi->pllref_clk); // 准备并启用DSI主机设备的PLL参考时钟
/*
* 启用GRF时钟后,写入不会立即改变的通道和双模配置。
* 如果我们等到 enable() 后再执行这些配置,那么像面板准备之类的操作将无法通过DSI发送命令。
*/
ret = clk_prepare_enable(dsi->grf_clk); // 准备并启用DSI主机设备的GRF时钟
// 执行DSI主机设备的配置
dw_mipi_dsi_rockchip_config(dsi);
// 如果存在从属(slave)设备,也执行其配置
if (dsi->slave)
dw_mipi_dsi_rockchip_config(dsi->slave);
clk_disable_unprepare(dsi->grf_clk); // 禁用并释放DSI主机设备的GRF时钟
// 初始化DRM编码器
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dsi->dev->of_node);
ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_DSI);
if (ret) {
DRM_ERROR("Failed to initialize encoder with drm\n");
return ret;
}
// 添加DRM编码器助手函数
/* 继续关注下dw_mipi_dsi_encoder_helper_funcs函数 */
drm_encoder_helper_add(encoder, &dw_mipi_dsi_encoder_helper_funcs);
// 绑定DSI主机设备
ret = dw_mipi_dsi_bind(dsi->dmd, &dsi->encoder);
return 0;
}
/**
* struct drm_encoder_helper_funcs - DRM编码器助手函数结构体
*
* 这个结构体包含了DRM编码器助手函数的指针,它们用于管理和控制编码器的操作。
* 这些函数提供了与DRM框架的交互,以确保正确的显示输出。
*/
static const struct drm_encoder_helper_funcs dw_mipi_dsi_encoder_helper_funcs = {
/**
* .atomic_check - 原子检查函数
*
* 在DRM原子模式下,用于检查编码器配置是否有效。
* 这个函数会在每次显示模式发生更改时自动调用,以确保配置的正确性。
* 如果配置无效,可以返回一个错误来阻止显示模式的切换。
*
* 调用时机:在每次显示模式发生更改时自动调用。
*
* @encoder: 要检查的编码器结构体。
* @crtc_state: 包含有关CRTC状态的信息。
* @conn_state: 包含有关连接器状态的信息。
*
* 返回值:0表示配置有效,否则返回错误代码。
*/
.atomic_check = dw_mipi_dsi_encoder_atomic_check,
/**
* .enable - 启用编码器函数
*
* 用于启用编码器并开始输出信号。当用户打开显示或者在初始化期间调用时,这个函数会被触发。
* 在这个函数中,你可以执行必要的操作来激活编码器,以便其开始输出信号。
*
* 调用时机:用户打开显示或者在初始化期间调用。
*
* @encoder: 要启用的编码器结构体。
*/
.enable = dw_mipi_dsi_encoder_enable,
/**
* .disable - 禁用编码器函数
*
* 用于禁用编码器并停止输出信号。当用户关闭显示或者在资源释放期间调用时,这个函数会被触发。
* 在这个函数中,你可以执行必要的操作来关闭编码器,停止输出信号。
*
* 调用时机:用户关闭显示或者在资源释放期间调用。
*
* @encoder: 要禁用的编码器结构体。
*/
.disable = dw_mipi_dsi_encoder_disable,
};
static int
dw_mipi_dsi_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
struct dw_mipi_dsi_rockchip *dsi = to_dsi(encoder);
switch (dsi->format) {
case MIPI_DSI_FMT_RGB888:
s->output_mode = ROCKCHIP_OUT_MODE_P888;
break;
case MIPI_DSI_FMT_RGB666:
s->output_mode = ROCKCHIP_OUT_MODE_P666;
break;
case MIPI_DSI_FMT_RGB565:
s->output_mode = ROCKCHIP_OUT_MODE_P565;
break;
default:
WARN_ON(1);
return -EINVAL;
}
s->output_type = DRM_MODE_CONNECTOR_DSI;
if (dsi->slave)
s->output_flags = ROCKCHIP_OUTPUT_DSI_DUAL;
return 0;
}
/**
* dw_mipi_dsi_encoder_enable - 启用DRM编码器
*
* 此函数用于启用DRM编码器,激活输出信号,以便在显示设备上显示图像。
* 在函数内部,它执行了一系列与硬件和电源管理相关的操作,以确保编码器能够正常工作。
*
* 调用时机:当用户打开显示或在初始化期间调用此函数。
*
* @encoder: 要启用的DRM编码器结构体。
*/
static void dw_mipi_dsi_encoder_enable(struct drm_encoder *encoder)
{
struct dw_mipi_dsi_rockchip *dsi = to_dsi(encoder);
int ret, mux;
// 从设备树获取活动端点的标识符(ID)
mux = drm_of_encoder_active_endpoint_id(dsi->dev->of_node, &dsi->encoder);
if (mux < 0)
return;
// 获取运行时PM(电源管理)引用以确保设备处于活动状态
pm_runtime_get_sync(dsi->dev);
if (dsi->slave)
pm_runtime_get_sync(dsi->slave->dev);
/*
* 对于RK3399,必须在写入GRF寄存器之前启用grf_clk时钟。
* 对于RK3288或其他SoC,grf_clk时钟必须为NULL,clk_prepare_enable将直接返回true。
*/
ret = clk_prepare_enable(dsi->grf_clk);
if (ret) {
DRM_DEV_ERROR(dsi->dev, "Failed to enable grf_clk: %d\n", ret);
return;
}
// 设置LCD选择器以选择活动的端点
dw_mipi_dsi_rockchip_set_lcdsel(dsi, mux);
if (dsi->slave)
dw_mipi_dsi_rockchip_set_lcdsel(dsi->slave, mux);
// 关闭并卸载grf_clk时钟
clk_disable_unprepare(dsi->grf_clk);
}
static void dw_mipi_dsi_encoder_disable(struct drm_encoder *encoder)
{
struct dw_mipi_dsi_rockchip *dsi = to_dsi(encoder);
if (dsi->slave)
pm_runtime_put(dsi->slave->dev);
pm_runtime_put(dsi->dev);
}
三、Linux component
#ifndef COMPONENT_H
#define COMPONENT_H
#include <linux/stddef.h>
/* 包含Linux内核的stddef.h标准头文件 */
struct device;
/**
* struct component_ops - component驱动程序的回调函数
*
* 使用component_add()注册组件,使用component_del()注销组件。
*/
struct component_ops {
/**
* @bind:
*
* 当聚合驱动程序准备好绑定整个驱动程序时调用。
*
* @param comp: 要绑定的组件设备
* @param master: 聚合驱动程序的主设备
* @param master_data: 主设备数据的不透明指针
*
* @return 返回0表示成功,负值表示错误。
*/
int (*bind)(struct device *comp, struct device *master,
void *master_data);
/**
* @unbind:
*
* 当聚合驱动程序准备好绑定整个驱动程序,或者当component_bind_all()在部分绑定组件失败时,需要解绑已经绑定的组件时调用。
*
* @param comp: 要解绑的组件设备
* @param master: 聚合驱动程序的主设备
* @param master_data: 主设备数据的不透明指针
*/
void (*unbind)(struct device *comp, struct device *master,
void *master_data);
};
/**
* component_add - 注册组件到聚合驱动程序
*
* @param dev: 组件设备
* @param ops: 对组件的操作函数集合
*
* @return 返回0表示成功,负值表示错误。
*/
int component_add(struct device *dev, const struct component_ops *ops);
/**
* component_add_typed - 注册带类型的组件到聚合驱动程序
*
* @param dev: 组件设备
* @param ops: 对组件的操作函数集合
* @param subcomponent: 子组件类型
*
* @return 返回0表示成功,负值表示错误。
*/
int component_add_typed(struct device *dev, const struct component_ops *ops,
int subcomponent);
/**
* component_del - 从聚合驱动程序中注销组件
*
* @param dev: 组件设备
* @param ops: 对组件的操作函数集合
*/
void component_del(struct device *dev, const struct component_ops *ops);
/**
* component_bind_all - 绑定所有组件到聚合驱动程序
*
* @param master: 聚合驱动程序的主设备
* @param master_data: 主设备数据的不透明指针
*/
int component_bind_all(struct device *master, void *master_data);
/**
* component_unbind_all - 解绑所有组件从聚合驱动程序
*
* @param master: 聚合驱动程序的主设备
* @param master_data: 主设备数据的不透明指针
*/
void component_unbind_all(struct device *master, void *master_data);
struct master;
/**
* struct component_master_ops - 聚合驱动程序的回调函数
*
* 使用component_master_add_with_match()注册聚合驱动程序,使用component_master_del()注销聚合驱动程序。
*/
struct component_master_ops {
/**
* @bind:
*
* 当匹配列表中指定的所有组件或聚合驱动程序都准备好时调用。通常绑定聚合驱动程序包括以下步骤:
*
* 1. 为聚合驱动程序分配一个结构体。
*
* 2. 通过调用component_bind_all()并将聚合驱动程序结构体作为不透明指针数据绑定所有组件。
*
* 3. 注册聚合驱动程序以发布其接口。
*
* 需要注意的是,聚合驱动程序的生命周期与底层的任何device实例不对齐。因此不能使用devm,必须在unbind回调中显式释放在此回调中获取或分配的所有资源。
*
* @param master: 聚合驱动程序的主设备
*
* @return 返回0表示成功,负值表示错误。
*/
int (*bind)(struct device *master);
/**
* @unbind:
*
* 当聚合驱动程序使用component_master_del()注销,或者其中一个组件使用component_del()注销时调用。
*
* @param master: 聚合驱动程序的主设备
*/
void (*unbind)(struct device *master);
};
/**
* component_master_del - 从聚合驱动程序中注销聚合驱动程序
*
* @param dev: 聚合驱动程序的主设备
* @param ops: 对聚合驱动程序的操作函数集合
*/
void component_master_del(struct device *dev,
const struct component_master_ops *ops);
struct component_match;
/**
* component_master_add_with_match - 注册带匹配的聚合驱动程序
*
* @param dev: 聚合驱动程序的主设备
* @param ops: 对聚合驱动程序的操作函数集合
* @param match: 匹配组件的条件
*
* @return 返回0表示成功,负值表示错误。
*/
int component_master_add_with_match(struct device *dev,
const struct component_master_ops *ops, struct component_match *match);
/**
* component_match_add_release - 添加一个组件匹配项,并指定释放函数
*
* @param master: 具有聚合驱动程序的设备
* @param matchptr: 指向组件匹配项列表的指针
* @param release: 释放函数,用于释放匹配项
* @param compare: 与所有组件匹配的比较函数
* @param compare_data: 传递给@compare函数的不透明指针
*/
void component_match_add_release(struct device *master,
struct component_match **matchptr,
void (*release)(struct device *, void *),
int (*compare)(struct device *, void *), void *compare_data);
/**
* component_match_add_typed - 添加一个带类型的组件匹配项
*
* @param master: 具有聚合驱动程序的设备
* @param matchptr: 指向组件匹配项列表的指针
* @param compare_typed: 与特定类型的组件匹配的比较函数
* @param compare_data: 传递给@compare_typed函数的不透明指针
*/
void component_match_add_typed(struct device *master,
struct component_match **matchptr,
int (*compare_typed)(struct device *, int, void *), void *compare_data);
/**
* component_match_add - 添加一个组件匹配项
*
* @param master: 具有聚合驱动程序的设备
* @param matchptr: 指向组件匹配项列表的指针
* @param compare: 与所有组件匹配的比较函数
* @param compare_data: 传递给@compare函数的不透明指针
*
* 添加新的组件匹配项到@matchptr中,聚合驱动程序需要它来正常工作。在添加第一个匹配项之前,必须将@matchptr指向的组件匹配项列表初始化为NULL。这仅匹配使用component_add()添加的组件。
*/
static inline void component_match_add(struct device *master,
struct component_match **matchptr,
int (*compare)(struct device *, void *), void *compare_data)
{
component_match_add_release(master, matchptr, NULL, compare,
compare_data);
}
#endif