前言:
显示系统的主流框架有哪些?有啥区别 ?各自有啥优缺点?
在 Linux 系统中,主流的显示框架有两种: DRM(Direct Rendering Module)框架和
FB(FrameBuffer)框架。FB 框架不能处理基于 3D 加速 GPU 显卡,DRM 是可以统一管理 GPU
显示,所以 DRM 相对于 FB 更能适应新的显示硬件。比如 DRM 支持多层合成、支持 VSYNC、
支持 DMA-BUF、支持 fence 机制等等。
一、FB框架:
FB框架是传统的显示框架,相对简单,但是只能提供最基础的显示功能;
无法满足当前上层应用和底层硬件的显示需求。
lcd显示器驱动其实争对是像素点,:整体概念如图:
FrameBuffer驱动是一个字符设备,设备节点是/dev/fbX,主设备号为29,次设备号递增,
frambuffer就是linux内核驱动申请的一片内存空间,然后lcd内有一片sram,
cpu内部有个lcd控制器,它有个单独的dma用来将frambuffer中的数据拷贝到lcd的sram中去
拷贝到lcd的sram中的数据就会显示在lcd上,LCD驱动负责把信号转成显示屏上的内容,
至于什么内容这就是应用层要处理的。
假设每个像素的颜色用16位来表示,那么一个LCD的所有像素点假设有xres * y res个,
需要的内存为:xres * yres * 16 / 8,也就是要设置所有像素的颜色,需要这么大小的内存。
这块内存就被称为framebuffer, framebuffer字符驱动就是将lcd屏幕显示内存中写入对应颜色内容。
二、FB应用程序操作步骤:
应用程序操作步骤:
1> 打开设备文件 /dev/fb0
2> 获取当前设备信息 #include <linux/fb.h>
3> mmap做映射
4> 填充framebuffer
FB驱动框架结构:
三、DRM框架
目前主流的显示方案;为了适应当前日益更新的显示硬件;软件上能支持更多高级的控制和
特性FB框架不能处理基于3D加速GPU显卡,DRM是可以统一管理GPU显示,
所以DRM相对于FB更能适应新的显示硬件
下图就是一个 DRM 驱动框架包括两部分:DRM core 和 DRM driver。DRM core 提供了一 个基本的 DRM 框架,DRM driver 就可以注册进 DRM 框架,同时为用户空间提供一组 ioctl。 libdrm 对底层接口(DRM driver 提供的 ioctl)进行封装,向上层提供统一的 API 接口。DRM driver 包含了 GEM 模块和 KMS 模块这两大模块,这两模块也分为好几个小模块。
图形执行管理器(GEM):全称 Graphics Execution Manager,这是一个内存管理器,主要负
责内存的分配和释放,可以调用 GPU。
DUMB:这是一个 dumb 缓冲区,主要负责一些简单的 buffer 显示,可以通过 CPU 直接渲
染 dumb,GPU 不会使用 dumb。
内核显示模式设置(KMS):全称 Kernel Mode Setting,主要负责显示的控制,包括屏幕分辨
率、屏幕刷新率和颜色深度等等。
CRTC:就是指显示控制器,在 DRM 里有多个显存,就可以通过操作 CRTC 来控制要显示
那个显存。
Encoder(编码器):负责从 CRTC 里输出的 timing 时序转换成外部设备所需要的信号的模块,同时也
负责控制 LCD 的显示。
Connector(连接器):连接物理显示设备的连接器,比如 DSI、HDMI 等等。
Plane(面板):负责获取显存,在输出到 CRTC 里,说明 CRTC 必须要有一个 Plane。每个图像拥有一个Planes,Planes的属性控制着图像的显示区域、图像翻转、色彩混合方式等, 最终图像经过Planes并通过CRTC,得到多个图像的混合显示或单独显示的等等功能
帧缓冲(FB):能够显示图层的 buffer。
1、DRM driver模块:
GEM和KMS连接如下:
蓝色框表示KMS里的模块,KMS:全称 Kernel Mode Setting,主要负责显示的控制,包括屏幕分辨率、
屏幕刷新率和颜色深度等等。
plane是连接crtc和framebuffer的纽带,而encoder是连接crtc和connector的纽带
GEM是负责和物理的buffer打交道不是framebuffer
plane把获取到显存输出到crtc里,crtc通过connector接口输出到显示器
2、DRM core模块:
提供了一个DRM框架:
在 Linux 系统中,DRM 驱动的核心主要就一个 drm_driver 结构体,驱动程序要初始化
drm_driver 结构体,然后调用 drm_dev_init 函数,将其注册到 DRM core。
我们可以通过“drivers/gpu/drm/stm/drv.c”查看drm源码:
#include <linux/component.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include "ltdc.h"
#define STM_MAX_FB_WIDTH 2048
#define STM_MAX_FB_HEIGHT 2048 /* same as width to handle orientation */
static const struct drm_mode_config_funcs drv_mode_config_funcs = {
.fb_create = drm_gem_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
static int stm_gem_cma_dumb_create(struct drm_file *file,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
unsigned int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
/*
* in order to optimize data transfer, pitch is aligned on
* 128 bytes, height is aligned on 4 bytes
*/
args->pitch = roundup(min_pitch, 128);
args->height = roundup(args->height, 4);
return drm_gem_cma_dumb_create_internal(file, dev, args);
}
DEFINE_DRM_GEM_CMA_FOPS(drv_driver_fops);
static struct drm_driver drv_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.name = "stm",
.desc = "STMicroelectronics SoC DRM",
.date = "20170330",
.major = 1,
.minor = 0,
.patchlevel = 0,
.fops = &drv_driver_fops,
.dumb_create = stm_gem_cma_dumb_create,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_free_object_unlocked = drm_gem_cma_free_object,
.gem_vm_ops = &drm_gem_cma_vm_ops,
.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
.gem_prime_vmap = drm_gem_cma_prime_vmap,
.gem_prime_vunmap = drm_gem_cma_prime_vunmap,
.gem_prime_mmap = drm_gem_cma_prime_mmap,
.get_scanout_position = ltdc_crtc_scanoutpos,
.get_vblank_timestamp = drm_calc_vbltimestamp_from_scanoutpos,
};
static int drv_load(struct drm_device *ddev)
{
struct platform_device *pdev = to_platform_device(ddev->dev);
struct ltdc_device *ldev;
int ret;
DRM_DEBUG("%s\n", __func__);
ldev = devm_kzalloc(ddev->dev, sizeof(*ldev), GFP_KERNEL);
if (!ldev)
return -ENOMEM;
ddev->dev_private = (void *)ldev;
drm_mode_config_init(ddev);
/*
* set max width and height as default value.
* this value would be used to check framebuffer size limitation
* at drm_mode_addfb().
*/
ddev->mode_config.min_width = 0;
ddev->mode_config.min_height = 0;
ddev->mode_config.max_width = STM_MAX_FB_WIDTH;
ddev->mode_config.max_height = STM_MAX_FB_HEIGHT;
ddev->mode_config.funcs = &drv_mode_config_funcs;
ret = ltdc_load(ddev);
if (ret)
goto err;
drm_mode_config_reset(ddev);
drm_kms_helper_poll_init(ddev);
platform_set_drvdata(pdev, ddev);
return 0;
err:
drm_mode_config_cleanup(ddev);
return ret;
}
static void drv_unload(struct drm_device *ddev)
{
DRM_DEBUG("%s\n", __func__);
drm_kms_helper_poll_fini(ddev);
ltdc_unload(ddev);
drm_mode_config_cleanup(ddev);
}
static __maybe_unused int drv_suspend(struct device *dev)
{
struct drm_device *ddev = dev_get_drvdata(dev);
struct ltdc_device *ldev = ddev->dev_private;
struct drm_atomic_state *state;
WARN_ON(ldev->suspend_state);
state = drm_atomic_helper_suspend(ddev);
if (IS_ERR(state))
return PTR_ERR(state);
ldev->suspend_state = state;
pm_runtime_force_suspend(dev);
return 0;
}
static __maybe_unused int drv_resume(struct device *dev)
{
struct drm_device *ddev = dev_get_drvdata(dev);
struct ltdc_device *ldev = ddev->dev_private;
int ret;
if (WARN_ON(!ldev->suspend_state))
return -ENOENT;
pm_runtime_force_resume(dev);
ret = drm_atomic_helper_resume(ddev, ldev->suspend_state);
if (ret)
pm_runtime_force_suspend(dev);
ldev->suspend_state = NULL;
return ret;
}
static __maybe_unused int drv_runtime_suspend(struct device *dev)
{
struct drm_device *ddev = dev_get_drvdata(dev);
DRM_DEBUG_DRIVER("\n");
ltdc_suspend(ddev);
return 0;
}
static __maybe_unused int drv_runtime_resume(struct device *dev)
{
struct drm_device *ddev = dev_get_drvdata(dev);
DRM_DEBUG_DRIVER("\n");
return ltdc_resume(ddev);
}
static const struct dev_pm_ops drv_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(drv_suspend, drv_resume)
SET_RUNTIME_PM_OPS(drv_runtime_suspend,
drv_runtime_resume, NULL)
};
static int stm_drm_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct drm_device *ddev;
int ret;
DRM_DEBUG("%s\n", __func__);
dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
ddev = drm_dev_alloc(&drv_driver, dev);
if (IS_ERR(ddev))
return PTR_ERR(ddev);
ret = drv_load(ddev);
if (ret)
goto err_put;
ret = drm_dev_register(ddev, 0);
if (ret)
goto err_put;
drm_fbdev_generic_setup(ddev, 16);
return 0;
err_put:
drm_dev_put(ddev);
return ret;
}
static int stm_drm_platform_remove(struct platform_device *pdev)
{
struct drm_device *ddev = platform_get_drvdata(pdev);
DRM_DEBUG("%s\n", __func__);
drm_dev_unregister(ddev);
drv_unload(ddev);
drm_dev_put(ddev);
return 0;
}
static const struct of_device_id drv_dt_ids[] = { //匹配条件
{ .compatible = "st,stm32-ltdc"},
{ /* end node */ },
};
MODULE_DEVICE_TABLE(of, drv_dt_ids);
static struct platform_driver stm_drm_platform_driver = {
.probe = stm_drm_platform_probe,
.remove = stm_drm_platform_remove,
.driver = {
.name = "stm32-display",
.of_match_table = drv_dt_ids,
.pm = &drv_pm_ops,
},
};
module_platform_driver(stm_drm_platform_driver);
MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>");
MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>");
MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>");
MODULE_AUTHOR("Mickael Reulier <mickael.reulier@st.com>");
MODULE_DESCRIPTION("STMicroelectronics ST DRM LTDC driver");
MODULE_LICENSE("GPL v2");
这是一个标准的平台总线文件:

1.2 自动进行匹配
1.3 当匹配成功时候会自动执行probe函数,当解除时会自动执行remove函数进行空间释放。
1.4 probe函数是正常的进行基本的device设备操作。
1.5 和其他设备驱动一样,DRM 也分为 DRM device和 DRM drive,drm_device 结构体为 DRM
1.5.1drm_device结构体
struct drm_device {
struct list_head legacy_dev_list;
int if_version;
struct kref ref;
u32 max_vblank_count;
....
struct list_head vblank_event_list;
spinlock_t event_lock;
struct drm_agp_head *agp;
struct pci_dev *pdev;
unsigned int num_crtcs;
struct drm_mode_config mode_config;
struct mutex object_name_lock;
struct idr object_name_idr;
struct drm_vma_offset_manager *vma_offset_manager;
struct drm_vram_mm *vram_mm;
enum switch_power_state switch_power_state;
struct drm_fb_helper *fb_helper;
};
struct drm_device *drm_dev_alloc(struct drm_driver *driver, struct device *parent)
此函数会先调用 kzalloc 为 drm_device 分配内存,然后调用 drm_dev_init 初始化 drm_device,
函数参数和返回值含义如下:
driver:drm_driver 结构体指针,也就是 DRM 设备对应的 DRM 驱动。
parent:父设备。
返回值:返回分配成功的新 DRM 设备,如果返回 ERR_PTR 的话就表示 drm_device 申请
失败。
drm_device 分配成功以后还需要使用 drm_dev_register 函数向内核注册,
函数原型如下:
int drm_dev_register(struct drm_device *dev, unsigned long flags)
函数参数和返回值含义如下:
dev:需要注册到内核的 drm_device。
flags:传递给驱动.load 函数的标志。
返回值:0,成功;负数,失败
1.5.2 drm_driver 结构体
Linux 内核为 DRM 驱动提供一个叫做 drm_driver 的结构体, drm_driver 结构体包含了 DRM驱动的完整属性和操作集合,因此每一个 DRM 驱动都必须有一个 drm_driver 。 drm_driver 结构 体定义在 include/drm/drm_drv.h 文件里,

drm_driver 结构体的成员变量很多,我们重点关注 driver_features 、 fops 和 dumb_create 。
driver_features 用来描述驱动特性,枚举类型 drm_driver_feature 定义了可以选择的驱动特性(如下):

前面我们说了,STM32MP1 的 LTDC 是个 platform 驱动,当设备和驱动匹配成功以后stm_drm_platform_probe 函数就会执行,函数内容如下:

drv_load 函数


这里我们要引入 drm_panel 结构体,此结构体作用是提供一堆控制回调函数。
比如屏幕参数回调函数,背光控制函数等等。ltdc_load函数是负责初始化 ltdc 接口 ( 同时 connector 和 encoder 一起初始化 ), connector 初始化的时候,就会调用 drm_panel 结构体里的获取屏幕 参数函数( 所以我们只需要提供一个屏的驱动就能正常显示了 ) 。通常 encoder 和 connector 是放 在同一个驱动初始化的,目的是为了方便驱动程序设计。![]()
要完成整个 DRM 驱动的正常初始化,前面的 GEM 和 KMS 这些模块 ST 官方已经提供给我们了,只需提供一个 drm_panel 对象就行了。打开“ include/drm/drm_panel.h ”文 件,找到如下内容所示:、

如果需要驱动rgb屏幕,则只需要提供一个drm_panel对象则可以了。如果需要驱动自己的屏幕只需要修改:
①、在根节点下提供一个 LCD 设备树,包含背光的节点和引用 ltdc 节点。
②、在 panel-simple.c 文件里的 platform_of_match 结构体里添加一组设备 ID,此设备 ID 对
应你所使用的屏幕,重点是屏幕参数 panel_desc 结构体。
以上是关于rgb lcd 需要使用到drm框架的分析,正常如果不是原厂都不会需要自己去写驱动,正常来说都是直接修改设备树,驱动lcd屏幕。
如果有什么不对的欢迎大家指正。