显示系统的主流框架:DRM和FB框架

前言:

显示系统的主流框架有哪些?有啥区别 ?各自有啥优缺点? 

    在 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 driverDRM 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.1 当注册了平台总线后会调用一下结构体,执行函数:

1.2 自动进行匹配

1.3 当匹配成功时候会自动执行probe函数,当解除时会自动执行remove函数进行空间释放。

 1.4 probe函数是正常的进行基本的device设备操作。

 1.5 和其他设备驱动一样,DRM 也分为 DRM deviceDRM drivedrm_device 结构体为 DRM

设备, drm_driver 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;
};
我们在编写 DRM 驱动的时候需要自行申请 drm_device 内存并且使用初始化,这个可以直
接通过 drm_dev_alloc 函数来完成,函数原型如下:
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屏幕。

如果有什么不对的欢迎大家指正。

  • 11
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值