一、DRM
Linux下的DRM框架内容众多,结构复杂。本文将简单介绍下开发过程中用到的几个结构体。这几个结构体都在之前文章里面开发DRM驱动时用到的,未用到的暂不介绍。
DRM中的KMS包含Framebuffer、CRTC,ENCODER,CONNECTOR,PLANE,VBLANK,property。因此,开发DMR驱动也是围绕这几个部分展开。驱动的逻辑部分需要包含这些。开发DRM驱动可以简化为如下操作。
1、初始化结构体
2、注册
3、 给 driver_features 添加上 DRIVER_MODESET,告诉 DRM Core 当前驱动支持 modesetting 操作,创建plane、crtc、encoder、connector 这4个 drm_mode_object。
4、给 driver_features 添加上 DRIVER_GEM 标志位,告诉 DRM Core 该驱动支持 GEM 操作。添加 FB 和 GEM 支持:dumb_create 回调接口用于创建 gem object,并分配物理 buffer。这里直接使用 CMA helper 函数来实现;fb_create 回调接口用于创建 framebuffer object,并绑定 gem objects。这里直接使用 CMA helper 函数实现。fops 中的 mmap 接口,用于将 dumb buffer 映射到 userspace,它依赖 drm driver 中的 gem_vm_ops 实现。这里也直接使用 CMA helper 函数来实现。
DRIVER_MODESET:表示支持modesetting 操作
DRIVER_GEM:表示支持GEM 操作,用于操作对内存的分配、释放
DRIVER_ATOMIC:支持 Atomic 操作,用于操作各种属性
.fops = &drv_driver_fops,驱动的fops结构体
.dumb_create = stm_gem_cma_dumb_create,创建gem object,分配屋里buffer,可以使用CMA helper函数实现
其中,
1)xxx_funcs 必须有,xxx_helper_funcs 可以没有。
2)drm_xxx_init() 必须有,drm_xxx_helper_add() 可以没有。
3)只有当 xxx_funcs 采用 DRM 标准的 helper 函数实现时,才有可能 需要定义 xxx_helper_funcs 接口。
4)xxx_funcs.destroy() 接口必须实现
helper 函数的作用:drm_xxx_funcs 是 drm ioctl 操作的最终入口,但是对于大多数 SoC 厂商来说,它们的 drm_xxx_funcs 操作流程基本相同(你抄我,我抄你),只是在寄存器配置上存在差异,因此开发者们将那些 common 的操作流程做成了 helper 函数,而将那些厂商差异化的代码放到了 drm_xxx_helper_funcs 中去,由 SoC 厂商自己实现。
开发一个DRM驱动大致流程如上所述。总之,就是实现KMS中的各个部分。GEM中的部分可以使用CAM中的接口完成。
二、几个结构体
1、struct drm_plane_funcs结构体
plane是图层相关部分。结构体部分成员如下:
struct drm_plane_funcs 结构体成员:
1、 update_plane:为给定的CRTC和framebuffer启用并配置平面
2、 disable_plane:关闭plane
3、 destroy:清除plane所有的资源
4、 reset:复位软硬件状态为关闭
5、 set_property:更新plane的property
6、 atomic_duplicate_state:复制当前的plane状态并返回
7、 atomic_destroy_state:销毁当前的plane状态
8、 atomic_set/get_property:原子操作设置/获得property
9、 format_mod_supported:该可选挂钩用于DRM,以确定给定的格式/修饰符组合是否对平面有效
struct drm_plane_helper_funcs部分成员如下:
struct drm_plane_helper_funcs结构体成员:
1、 prepare_fb:该钩子通过固定其后备存储器或将其重新定位到连续的VRAM块,为扫描准备帧缓冲区。其他可能的准备工作包括冲洗缓存。
2、 cleanup_fb:清除在prepare_fb分配的给framebuffer和plane的资源
3、 atomic_check:检查该挂钩中的plane特定约束
4、 atomic_update:更新plane状态
5、 atomic_disable:关闭
6、 atomic_async_check:异步检查
7、 atomic_async_update:异步更新状态
使用示例:
static const struct drm_plane_funcs ltdc_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
.atomic_print_state = ltdc_plane_atomic_print_state,
.format_mod_supported = ltdc_plane_format_mod_supported,
};
static const struct drm_plane_helper_funcs ltdc_plane_helper_funcs = {
.prepare_fb = drm_gem_fb_prepare_fb, //准备分配资源
.atomic_check = ltdc_plane_atomic_check, //检查驱动
.atomic_update = ltdc_plane_atomic_update, //更新plane状态
.atomic_disable = ltdc_plane_atomic_disable, //关闭
};
2、struct drm_crtc_funcs结构体
部分结构体成员如下:
struct drm_crtc_funcs结构体成员:
1、 reset;复位,设置为关闭状态
2、 destroy:清除CRTC资源
3、 cursor_set:更新鼠标图像
4、 cursor_move:更新光标位置
5、 gamma_set:gamma设置
6、 set_config:修改配置
7、 page_flip:修改帧缓冲页,避免垂直消影期间用新的缓冲区替代时产生撕裂
8、 set_property:设置property
9、 atomic_duplicate_state:复制当前的状态
10、 atomic_destroy_state:销毁复制的状态
11、 atomic_get_property:获取atomic_get_property
12、 set_crc_source:更改CRC的来源
13、 get_vblank_counter 获取CRTC的vblank计数器
14、 disable_vblank:关闭消影
struct drm_crtc_helper_funcs结构体成员
1、 dpms:控制CRTC电源功率
2、 commit:提交新的模式
3、 mode_valid:用于检查特定模式在此crtc中是否有效
4、 mode_fixup:用于验证模式
5、 mode_set:设置模式
6、 mode_set_nofb:用于更新CRTC的显示模式,而不更改主平面配置的任何内容
7、 mode_set_base:设置新的帧缓冲区和扫描位置
8、 atomic_flush:完成CRTC上多个平面的更新
9、 atomic_enable/disable:打开/关闭
使用示例如下:
static const struct drm_crtc_funcs ltdc_crtc_funcs = {
.destroy = drm_crtc_cleanup, //清除CRTC资源
.set_config = drm_atomic_helper_set_config, //设置配置
.page_flip = drm_atomic_helper_page_flip, //修改帧缓冲页,避免垂直消影期间用新的缓冲区替代时产生撕裂
.reset = drm_atomic_helper_crtc_reset, // 复位,设置为关闭状态
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, // 复制当前状态
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, // 销毁当前状态
.enable_vblank = ltdc_crtc_enable_vblank, // 使能消影
.disable_vblank = ltdc_crtc_disable_vblank, // 关闭消影
.gamma_set = drm_atomic_helper_legacy_gamma_set, //gamma设置
};
struct drm_crtc_helper_funcs ltdc_crtc_helper_funcs = {
.mode_valid = ltdc_crtc_mode_valid, //用于检查特定模式红CRTC是否有效
.mode_fixup = ltdc_crtc_mode_fixup, //用于验证模式
.mode_set_nofb = ltdc_crtc_mode_set_nofb, //用于更新CRTC的显示模式,而不更改主平面配置的任何内容
.atomic_flush = ltdc_crtc_atomic_flush, //完成CRTC上多个平面的更新
.atomic_enable = ltdc_crtc_atomic_enable, // 打开
.atomic_disable = ltdc_crtc_atomic_disable, //关闭
};
3、struct drm_encoder_helper_funcs结构体:
static const struct drm_encoder_funcs ltdc_encoder_funcs = {
.destroy = drm_encoder_cleanup,
};
static const struct drm_encoder_helper_funcs ltdc_encoder_helper_funcs = {
.disable = ltdc_encoder_disable,
.enable = ltdc_encoder_enable,
.mode_set = ltdc_encoder_mode_set,
};
4、drm_panel相关
drm_panel这部分是在drivers\gpu\drm\panel\panel-simple.c添加。具体则是:
static const struct of_device_id platform_of_match[]下面添加一个自己的of_device_id。
{
.compatible = "alientek,lcd-rgb",
.data = &HTQ_alientek_desc,
},
HTQ_alientek_desc结构体描述的是所用屏幕的信息,包括显示模式、像素格式等。
static const struct panel_desc HTQ_alientek_desc = {
.modes = &HTQ_ATK7016_mode,
.num_modes = 1,
.bus_format = MEDIA_BUS_FMT_RGB888_1X24,
};
显示模式在HTQ_ATK7016_mode这个结构体里面具体描述,如时钟、分辨率、刷新率等等。
static const struct drm_display_mode HTQ_ATK7016_mode = {
.clock = 51200, /* LCD像素时钟,单位KHz */
.hdisplay = 1024, /* LCD X轴像素个数 */
.hsync_start = 1024 + 140, /* LCD X轴+hbp的像素个数 */
.hsync_end = 1024 + 140 + 20, /* LCD X轴+hbp+hspw的像素个数 */
.htotal = 1024 + 140 + 20 + 160,/* LCD X轴+hbp+hspw+hfp */
.vdisplay = 600, /* LCD Y轴像素个数 */
.vsync_start = 600 + 20, /* LCD Y轴+vbp的像素个数 */
.vsync_end = 600 + 20 + 3, /* LCD Y轴+vbp+vspw的像素个数 */
.vtotal = 600 + 20 + 3 + 12,/* LCD Y轴+vbp+vspw+vfp */
.vrefresh = 60, /* LCD的刷新频率为60HZ */ 12
};
添加上这些,就能将设备树上的屏幕信息和驱动对应上。DRM框架就能驱动屏幕。
以上几个结构体分别对应KMS里面的crtc、plane、encoder、panel几个部分。对应着写好代码即可。都比较简单。最后一个是drm_panel相关的,屏幕这部分可以使用已经定义好的struct panel_desc结构体去描述,struct panel_desc结构体里面有drm_panel,struct panel_desc可以看作是drm_panel的继承。
panel-simple.c里面实际上使用的是struct mipi_dsi_driver panel_simple_dsi_driver结构体作为驱动描述结构体。
static struct mipi_dsi_driver panel_simple_dsi_driver = {
.driver = {
.name = "panel-simple-dsi",
.of_match_table = dsi_of_match,
},
.probe = panel_simple_dsi_probe,
.remove = panel_simple_dsi_remove,
.shutdown = panel_simple_dsi_shutdown,
};
本质上依然是个platform驱动。在panel_simple_init函数里面可以看到
static int __init panel_simple_init(void)
{
int err;
err = platform_driver_register(&panel_simple_platform_driver);
if (err < 0)
return err;
if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) {
err = mipi_dsi_driver_register(&panel_simple_dsi_driver);
if (err < 0)
return err;
}
return 0;
}
module_init(panel_simple_init);
注册一个platform驱动,在panel_simple_dsi_probe函数中进行匹配,根据上面static const struct of_device_id platform_of_match[]中已经定义的数组数据。
三、总结
DRM驱动是目前Linux内核主流的显示框架,支持现代的显示技术,社区参与度、活跃度都很高,传统的framebuffer基本上已经处于半废弃状态。DRM驱动开发离不开KMS中的几个模块,按照定义好的结构体填充代码,完成开发,还算比较方面。
相关链接:
linux驱动系列学习之DRM(十)