分享

Rockchip RK3399 - DRM驱动程序

 waston 2025-01-07 发布于上海

Rockchip RK3399 - DRM子系统

目录

从开始接触音频子系统到如今已经两个多月,说实话花费的时间的确有点长了。从今天起我们开始接触DRM,网上已经有很多优秀的关于DRM的文章了,因此我们学习直接去学习一些优秀的文章即可。后面有关DRM相关的文章我们会大量参考DRM (Direct Rendering Manager)

一、DRM介绍

1.1 DRM概述

linux内核中包含两类图形显示设备驱动框架:

  • FB设备:Framebuffer图形显示框架;

  • DRM:直接渲染管理器(Direct Rendering Manager),是linux目前主流的图形显示框架;

在实际场景中,具体选择哪一种图形设备驱动框架取决于我们自己的业务需求。

1.1.1 Frambebuffer驱动

Frambebuffer驱动具有以下特征:

  • 直接控制显卡的帧缓冲区,提供基本的显卡输出功能;

  • 使用一些内核数据结构和API来管理图形界面,并提供一组接口与用户空间的应用程序进行通信;

  • 相对简单,适合于嵌入式系统或者不需要高性能图形的应用场景。

1.1.2 DRM驱动

相比FB(Framebuffer)架构,DRM更能适应当前日益更新的显示硬件;

  • 提供一种分离的图形驱动架构,将硬件驱动程序、内核模块和用户空间驱动程序进行分离;

  • 支持多个应用程序同时访问显卡,并提供了更丰富的图形功能,例如硬件加速和3D加速;

  • 提供了一些内核接口,可以让用户空间应用程序与驱动程序进行交互;

  • 支持多显示器(Display)和多GPU的配置;

总之,一句话,DRM是Linux目前主流的图形显示框架,相比FB架构,DRM
更能适应当前日益更新的显示硬。尽管FB退出历史舞台,但是并未将其遗弃,而是集合到DRM中,供部分嵌入式设备使用。

有关DRM的发展历史可以参考这篇博客:DRM (Direct Rendering Manager) 的发展历史

1.2 DRM框架

我们来看一下DRM子系统的软件架构:

DRM框架从上到下依次为应用程序、libdrm、DRM driver、HW;

(1) 应用程序:上图中并没有画出;应用程序可以直接操纵DRM的ioctl进行显示相关操作,后来封装成了libdrm库,让用户可以更加方便的进行显示控制;

(2) libdrm:lbdrm是DRM框架提供的位于用户空间操作DRM的库,提供了DRM
驱动的用户空间接口;对底层接口进行封装,向上层应用程序提供通用的API接口,本质上是对各种ioctl接口进行封装;

(3) DRM core:DRM核心层,由GEM和KMS组成;

  • KMS:Kernel Mode Setting,所谓内核显示模式设置,其实说白了就两件事:更新画面和设置显示参数;

    • 更新画面:显示buffer的切换,多图层的合成方式,以及每个图层的显示位置;

    • 设置显示参数:包括分辨率、刷新率、电源状态(休眠唤醒)等;

  • GEM:Graphic Execution Manager(图形执行管理器),它提供了一种抽象的显存管理方式,使用户空间应用程序可以更方便地管理显存,而不需要了解底层硬件的细节;

    • 实际上,在DRM中包含两个内存管理器,TTM(Translation Table Manager)和GEM(Graphic Execution Manager),TTM是第一个开发的DRM内存管理器,关于TTM我们就不做过多的介绍了,知道有这么一个东西就好了。

(4) HW:硬件设备;

1.2.1 KMS

KMS主要负责显示相关功能,在DRM中将其进行抽象,包括:CRTC、ENCODER、CONNECTOR、PLANE、Framebuffer、VBLANK、property;它们之间的关系如下图所示:

以HDMI接口为例说明,Soc内部一般包含一个Display模块,通过总线连接到HDMI
接口上;

  • Display模块对应CRTC;

  • HDMI接口对应Connector;

  • Framebuffer对应的是显存部分;

  • Plane是对Framebuffer进行描述的部分;

  • Encoder是将像素转化为HDMI接口所需要的信号,一般Encoder和Connector放到一块初始化。

1.2.2 GEM

GEM主要负责显示buffer的分配和释放,在DRM中将其进行抽象,包括:DUMP、PRIME、fence;

1.2.3 元素介绍

学习DRM驱动其实就是学习上面各个元素的实现及用法,如果你能掌握这些知识点,那么在编写DRM驱动的时候就能游刃有余。

元素说明
CRTC从Framebuffer中读取待显示的图像,并按照响应的格式输出给encoder,其主要承担的作用为
(1)配置适合显示的显示模式、分辨率、刷新率等参数,并输出相应的时序;
(2)扫描Framebuffer发送到一个或多个显示器;
(3)更新Framebuffer;
概括下就是,对显示器进行扫描,产生时序信号的模块、负责帧切换、电源控制、色彩调整等等。
Encoder编码器。它的作用就是将内存的pixel像素编码(转换)为显示器所需要的信号。
简单理解就是,如果需要将画面显示到不同的设备(Display Device)上,需要将画面转化为不同的电信号,例如DVID、VGA、YPbPr、CVBS、MIPI、eDP等。
Encoder和CRTC之间的交互就是我们所说的Mode Setting,其中包含了前面提到的色彩模式、还有时序(Timing)等连接器。
Connector它常常对应于物理连接器 (例如VGA,DVI, FPD-Link, HDMI, DisplayPort, S-Video等) ,它不是指物理线。在DRM中,Connector 是一个抽象的数据结构,代表连接的显示设备,从Connector中可以得到当前物理连接的输出设备相关的信息 ;例如连接状态,EDID数据,DPMS状态、支持的视频模式等
Plane图层,实际输出的图像是多个图层叠加而成的,比如主图层、光标图层。其中有些图层由硬件加速模块生成,每个CRTC至少一个plane;
plane一共有三种,分别是:DRM_PLANE_TYPE_PRIMARY、DRM_PLANE_TYPE_OVERLAY、DRM_PLANE_TYPE_CURSOR。这是配置plane的三个枚举,标注主图层、覆盖图层、光标图层;
FramebufferFramebuffer,用于存储单个图层(Plane)要实现的内容它是一块内存区域,可以理解为一块画布,驱动程序和应用都能访问它。绘画前需要将它格式化,设定绘制的色彩模式(例如RGB888,YUV等)和画布的大小(分辨率)
Vblank软件和硬件的同步机制,RGB时序中的垂直消影区,软件通常使用硬件VSYNC
来实现任何你想设置的参数都可以做成
propertyproperty,是DRM驱动中最灵活、最方便的Mode setting机制
Dumb只支持连续物理内存,基于kernel中通用CMA API实现,多用于小分辨率简单场景
Prime连续、非连续物理内存都支持,基于DMA-BUF机制,可以实现buffer共享,多用于大内存复杂场景
fencebuffer同步机制,基于内核dma_fence机制实现,用于防止显示内容出现异步问题

1.3 目录结构

linux内核将DRM驱动相关的代码都放在drivers/gpu/drm目录下,这下面的文件还是比较多的,我们大概了解一下即可;

root@zhengyang:/work/sambashare/rk3399/linux-6.3# ls drivers/gpu/drm/ -I "*.o"
amd                         drm_fbdev_generic.c             drm_print.c                logicvc
arm                         drm_fb_dma_helper.c             drm_privacy_screen.c       Makefile
armada                      drm_fb_helper.c                 drm_privacy_screen_x86.c   mcde
aspeed                      drm_file.c                      drm_probe_helper.c         mediatek
ast                         drm_flip_work.c                 drm_property.c             meson
atmel-hlcdc                 drm_format_helper.c             drm_rect.c                 mgag200
bridge                      drm_fourcc.c                    drm_scatter.c              modules.order
built-in.a                  drm_framebuffer.c               drm_self_refresh_helper.c  msm
display                     drm_gem_atomic_helper.c         drm_shmem_helper.ko        mxsfb
drm_agpsupport.c            drm_gem.c                       drm_shmem_helper.mod       nouveau
drm_aperture.c              drm_gem_dma_helper.c            drm_shmem_helper.mod.c     omapdrm
drm_atomic.c                drm_gem_framebuffer_helper.c    drm_simple_kms_helper.c    panel
drm_atomic_helper.c         drm_gem_shmem_helper.c          drm_syncobj.c              panfrost
drm_atomic_state_helper.c   drm_gem_ttm_helper.c            drm_sysfs.c                pl111
drm_atomic_uapi.c           drm_gem_vram_helper.c           drm_trace.h                qxl
drm_auth.c                  drm_hashtab.c                   drm_trace_points.c         radeon
drm_blend.c                 drm_internal.h                  drm_ttm_helper.ko          rcar-du
drm_bridge.c                drm_ioc32.c                     drm_ttm_helper.mod         rockchip
drm_bridge_connector.c      drm_ioctl.c                     drm_ttm_helper.mod.c       scheduler
drm_buddy.c                 drm_irq.c                       drm_vblank.c               shmobile
drm_bufs.c                  drm_kms_helper_common.c         drm_vblank_work.c          solomon
drm_cache.c                 drm_lease.c                     drm_vma_manager.c          sprd
drm_client.c                drm_legacy.h                    drm_vm.c                   sti
drm_client_modeset.c        drm_legacy_misc.c               drm_vram_helper.ko         stm
drm_color_mgmt.c            drm_lock.c                      drm_vram_helper.mod        sun4i
drm_connector.c             drm_managed.c                   drm_vram_helper.mod.c      tegra
drm_context.c               drm_memory.c                    drm_writeback.c            tests
drm_crtc.c                  drm_mipi_dbi.c                  etnaviv                    tidss
drm_crtc_helper.c           drm_mipi_dsi.c                  exynos                     tilcdc
drm_crtc_helper_internal.h  drm_mm.c                        fsl-dcu                    tiny
drm_crtc_internal.h         drm_mode_config.c               gma500                     ttm
drm_damage_helper.c         drm_mode_object.c               gud                        tve200
drm_debugfs.c               drm_modes.c                     hisilicon                  udl
drm_debugfs_crc.c           drm_modeset_helper.c            hyperv                     v3d
drm_displayid.c             drm_modeset_lock.c              i2c                        vboxvideo
drm_dma.c                   drm_of.c                        i915                       vc4
drm_drv.c                   drm_panel.c                     imx                        vgem
drm_dumb_buffers.c          drm_panel_orientation_quirks.c  ingenic                    virtio
drm_edid.c                  drm_pci.c                       Kconfig                    vkms
drm_edid_load.c             drm_plane.c                     kmb                        vmwgfx
drm_encoder.c               drm_plane_helper.c              lib                        xen
drm_encoder_slave.c         drm_prime.c                     lima                       xlnx

其中:drm_drv.c:DRM core核心实现;

  • drm_gem.c:提供了GEM相关的API;

其中rockchip为Rockchip官方的实现代码:

root@zhengyang:/work/sambashare/rk3399/linux-6.3# ls drivers/gpu/drm/rockchip/ -I "*.o"
analogix_dp-rockchip.c  inno_hdmi.c         rockchip_drm_drv.h   rockchip_drm_vop.h
built-in.a              inno_hdmi.h         rockchip_drm_fb.c    rockchip_lvds.c
cdn-dp-core.c           Kconfig             rockchip_drm_fb.h    rockchip_lvds.h
cdn-dp-core.h           Makefile            rockchip_drm_gem.c   rockchip_rgb.c
cdn-dp-reg.c            modules.order       rockchip_drm_gem.h   rockchip_rgb.h
cdn-dp-reg.h            rk3066_hdmi.c       rockchip_drm_vop2.c  rockchip_vop2_reg.c
dw_hdmi-rockchip.c      rk3066_hdmi.h       rockchip_drm_vop2.h  rockchip_vop_reg.c
dw-mipi-dsi-rockchip.c  rockchip_drm_drv.c  rockchip_drm_vop.c   rockchip_vop_reg.h

二、硬件抽象

对于初学者来说,往往让人迷惑的不是DRM中objects的概念,而是如何去建立这些objects
与实际硬件的对应关系。因为并不是所有的Display硬件都能很好的对应上plane/crtc/encoder/connector这些objects。

在学如何去抽象显示硬件到具体的DRM object之前,我们先普及一下MIPI相关的知识。

MIPI(Mobile Industry Processor Interface)是2003年由ARM, Nokia, ST ,TI等公司成立的一个联盟,目的是把手机内部的接口如摄像头、显示屏接口、射频/基带接口等标准化,从而减少手机设计的复杂程度和增加设计灵活性。

MIPI联盟下面有不同的WorkGroup,分别定义了一系列的手机内部接口标准,比如:

  • 摄像头接口CSI(Camera Serial Interface);

  • 显示接口DSI(Display Serial Interface);

  • 射频接口DigRF;

  • 麦克风/喇叭接口SLIMbus等。

2.1 MIPI DSI 接口

下图为一个典型的MIPI DSI接口屏的硬件连接框图:

它在软件架构上与DRM object的对应关系如下图:

多余的细节不做介绍,这里只说明为何如此分配drm object:

object说明
crtcRGB timing的产生,以及显示数据的更新,都需要访问Dislay Controller硬件寄存器,因此放在Display Controller驱动中
plane对Overlay硬件的抽象,同样需要访问Display Controller寄存器,因此也放在Display Controller驱动中
encoder将RGB并行信号转换为DSI行信号,需要配置DSI
硬件寄存器,因此放在DSI Controller驱动中
connector可以通过drm_panel来获取LCD的mode信息,但是encoder在哪,connector
就在哪,因此放在DSI Controller驱动中
drm_panel用于获取LCD mode参数,并提供LCD休眠唤醒的回调接口,供encoder调用,因此放在LCD驱动中

驱动参考:https://elixir./linux/latest/source/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c。

有关MIPI DSI可以参考MIPI 系列之 DSI

2.2 MIPI DPI接口

DPI接口也就是我们常说的RGB并行接口,Video数据通过RGB并行总线传输,控制命令(如初始化、休眠、唤醒等)则通过SPI/I2C 总线传输,比如早期的S3C2440 SoC平台。下图为一个典型的MIPI DPI接口屏的硬件连接框图:

该硬件连接在软件架构上与DRM object的对应关系如下图:

多余的细节不做介绍,这里只说明为何如此分配drm object:

object说明
crtcRGB timing的产生,以及显示数据的更新,都需要访问LCD Controller硬件寄存器,因此放在LCD Controller驱动中
planeLCD Controller没有Overlay硬件,它只有一个数据源通道,被抽象为Primary Plane,同样需要访问 LCD Controller硬件寄存器,因此放在LCD Controller驱动中
encoder由于DPI接口本身不需要对RGB信号做任何转换,因此没有哪个硬件与之对应。但是drm objects又缺一不可,因此实现了一个虚拟的encoder object。至于为什么要放在LCD驱动中实现,纯粹只是为了省事而已,你也可以放在一个虚拟的平台驱动中去实现该encoder object
connectorencoder在哪,connector就在哪,没什么好说的了
drm_panel用于获取LCD mode参数,并提供LCD休眠唤醒的回调接口,供encoder
调用,因此放在LCD驱动中

驱动参考:https://elixir./linux/v5.0/source/drivers/gpu/drm/panel/panel-sitronix-st7789v.c。

2.3 MIPI DBI接口

DBI接口也就是我们平时常说的MCU或SPI接口屏,这类屏的VIDEO数据和控制命令都是通过同一总线接口(I80、SPI接口)进行传输,而且这类屏幕必须内置GRAM显存,否则屏幕无法维持正常显示。

下图为一个典型的DBI接口屏的硬件连接框图:

该硬件连接在软件架构上与DRM object的对应关系如下:

上图参考kernel4.19 tinydrm软件架构。

object说明
crtc这类硬件本身不需要任何RGB timing信号,因此也没有实际的硬件与之对应。但是drm objects缺一不可,需要实现一个虚拟的crtc object。由于更新图像数据的动作需要通过SPI总线发送命令才能完成,因此放在了LCD驱动中
plane没有实际的硬件与之对应,但crtc初始化时需要一个plane object作为参数传递,因此和crtc放在一起
encoder没有实际的硬件与之对应,使用虚拟的encoder object。因为这类硬件并不是将RGB信号转换为SPI信号,而是根本就没有RGB信号源,也就无从谈起encoder设备。但是为了通知LCD休眠唤醒,需要调用LCD驱动的相应接口,因此放在LCD驱动中
connector由于没有了drm_panel,需要调用LCD接口来获取mode参数,因此放在LCD
驱动中

驱动参考:https://elixir./linux/latest/source/drivers/gpu/drm/tinydrm/ili9341.c。

三、DRM Objects

在编写DRM驱动程序之前,我们先对DRM内部的objects进行一番介绍,因为这些objects
是DRM框架的核心,它们缺一不可。

上图蓝色部分则是对物理硬件的抽象,黄色部分则是对软件的抽象。虚线以上的为drm_mode_object(或者说是modset object),虚线以下为drm_gem_object(或者说是gem objec)。

这些objects之间的关系:

通过上图可以看到,plane是连接framebuffer和crtc的纽带,而encoder则是连接crtc
和connector的纽带。与物理buffer直接打交道的是gem而不是framebuffer。

个人理解:

buffer是硬件存储设备, 由gem分配和释放;

framebuffer用于描述分配的显存的信息(如format、pitch、size等);

plane用于描述图层信息,同一个crtc可以由多个plane组成;

  • crtc控制显卡输出图像信号;

  • encoder将crtc输出的图像信号转换成一定格式的数字信号,如HDMI、DisplayPort、MIPI等;

  • connector用于将encoder输出的信号传递给显示器,并与显示器建立连接;

需要注意的是,上图蓝色部分即使没有实际的硬件与之对应,在软件驱动中也需要实现这些objects,否则DRM子系统无法正常运行。

3.1 drm_pane

encoder驱动程序负责将图形数据转换为LCD显示器所需的视频信号,而connector驱动程序则负责将这些信号发送到正确的显示设备上。LCD驱动程序需要和encoder、connector这两个驱动程序进行交互,以完成图形输出的控制。

耦合的产生:

connector的主要作用就是获取显示参数,所以会在LCD驱动中去构造connector object。但是connector初始化时需要attach上一个encoder object,而这个encoder object往往是在另一个硬件驱动中生成的,为了访问该encoder object,势必会产生一部分耦合的代码;

  • encoder除了扮演信号转换的角色,还担任着通知显示设备休眠唤醒的角色。因此,当encoder通知LCD驱动执行相应的enable/disable操作时,就一定会调用LCD驱动导出的全局函数,这也必然会产生一部分的耦合代码;

为了解决该耦合的问题,DRM子系统为开发人员提供了drm_panel结构体,该结构体封装了connector & encoder对LCD访问的常用接口;

于是,原来的encoder驱动和LCD驱动之间的耦合,就转变成了上图中encoder驱动与drm_panel、drm_panel与LCD驱动之间的“耦合”,从而实现了encoder驱动与LCD驱动之间的解耦合。

drm_panel不属于objects的范畴,它只是一堆回调函数的集合。但它的存在降低了LCD驱动与encoder驱动之间的耦合度。

3.2 modeset object

对于plane、crtc、encoder、connector几个对象,它们有一个公共基类struct drm_mode_object,这几个对象都由此基类扩展而来(该类作为crtc等结构体的成员)。

事实上这个基类扩展出来的子类并不是只有上面提到的几种,只不过这四种比较常见。其定义在include/drm/drm_mode_object.h:

/**
 * struct drm_mode_object - base structure for modeset objects
 * @id: userspace visible identifier
 * @type: type of the object, one of DRM_MODE_OBJECT\_\*
 * @properties: properties attached to this object, including values
 * @refcount: reference count for objects which with dynamic lifetime
 * @free_cb: free function callback, only set for objects with dynamic lifetime
 *
 * Base structure for modeset objects visible to userspace. Objects can be
 * looked up using drm_mode_object_find(). Besides basic uapi interface
 * properties like @id and @type it provides two services:
 *
 * - It tracks attached properties and their values. This is used by &drm_crtc,
 *   &drm_plane and &drm_connector. Properties are attached by calling
 *   drm_object_attach_property() before the object is visible to userspace.
 *
 * - For objects with dynamic lifetimes (as indicated by a non-NULL @free_cb) it
 *   provides reference counting through drm_mode_object_get() and
 *   drm_mode_object_put(). This is used by &drm_framebuffer, &drm_connector
 *   and &drm_property_blob. These objects provide specialized reference
 *   counting wrappers.
 */
struct drm_mode_object {
        uint32_t id;
        uint32_t type;
        struct drm_object_properties *properties;
        struct kref refcount;
        void (*free_cb)(struct kref *kref);
};

包括以下成员:

  • id:用户空间可见的唯一标识标识符,基于idr算法分配得到的;

  • type:对象的类型,可以是DRM_MODE_OBJECT_*中的一个;

  • properties:附加到该对象的属性,包括属性的值;在DRM驱动中,每个对象都可以拥有一组属性(例如分辨率、刷新率等),并且可以动态地增加、删除或修改属性。这些属性可以被用户空间的应用程序或者其他驱动程序获取或者设置;

  • refcount:具有动态生命周期的对象的引用计数;指drm_mode_object对象在内核中的生命周期的管理,每个drm_mode_object对象都有一个引用计数;

    • 当一个对象被创建时,它的引用计数被初始化为1;

    • 每当一个新的引用指向该对象时,它的引用计数就会增加1;

    • 每当一个引用被释放时,它的引用计数就会减少1;

    • 当对象的引用计数降为0时,内核会自动释放该对象。

    • 这种方式确保了内核中不会存在不再使用的对象,从而避免了内存泄漏。

  • free_cb:释放函数回调,仅对具有动态生命周期的对象设置;

该结构体提供了用户空间可见的modeset objects的基本结构,可以通过drm_mode_object_find函数查找对象。

为了更加清晰的了解struct drm_mode_object、struct drm_object_properties、struct snd_jack_kctl数据结构的关系,我们绘制了如下关系图:

3.2.1 对象类型

type主要包含以下几种类型,定义在include/uapi/drm/drm_mode.h:

#define DRM_MODE_OBJECT_CRTC 0xcccccccc
#define DRM_MODE_OBJECT_CONNECTOR 0xc0c0c0c0
#define DRM_MODE_OBJECT_ENCODER 0xe0e0e0e0
#define DRM_MODE_OBJECT_MODE 0xdededede
#define DRM_MODE_OBJECT_PROPERTY 0xb0b0b0b0
#define DRM_MODE_OBJECT_FB 0xfbfbfbfb
#define DRM_MODE_OBJECT_BLOB 0xbbbbbbbb
#define DRM_MODE_OBJECT_PLANE 0xeeeeeeee
#define DRM_MODE_OBJECT_ANY 0
3.2.2 对象属性

struct drm_object_properties用于描述对象的属性,定义在include/drm/drm_mode_object.h:

/**
 * struct drm_object_properties - property tracking for &drm_mode_object
 */
struct drm_object_properties {
        /**
         * @count: number of valid properties, must be less than or equal to
         * DRM_OBJECT_MAX_PROPERTY.
         */

        int count;
        /**
         * @properties: Array of pointers to &drm_property.
         *
         * NOTE: if we ever start dynamically destroying properties (ie.
         * not at drm_mode_config_cleanup() time), then we'd have to do
         * a better job of detaching property from mode objects to avoid
         * dangling property pointers:
         */
        struct drm_property *properties[DRM_OBJECT_MAX_PROPERTY];

        /**
         * @values: Array to store the property values, matching @properties. Do
         * not read/write values directly, but use
         * drm_object_property_get_value() and drm_object_property_set_value().
         *
         * Note that atomic drivers do not store mutable properties in this
         * array, but only the decoded values in the corresponding state
         * structure. The decoding is done using the &drm_crtc.atomic_get_property and
         * &drm_crtc.atomic_set_property hooks for &struct drm_crtc. For
         * &struct drm_plane the hooks are &drm_plane_funcs.atomic_get_property and
         * &drm_plane_funcs.atomic_set_property. And for &struct drm_connector
         * the hooks are &drm_connector_funcs.atomic_get_property and
         * &drm_connector_funcs.atomic_set_property .
         *
         * Hence atomic drivers should not use drm_object_property_set_value()
         * and drm_object_property_get_value() on mutable objects, i.e. those
         * without the DRM_MODE_PROP_IMMUTABLE flag set.
         *
         * For atomic drivers the default value of properties is stored in this
         * array, so drm_object_property_get_default_value can be used to
         * retrieve it.
         */
        uint64_t values[DRM_OBJECT_MAX_PROPERTY];
};

该结构体包含以下字段:

  • count:properties数组长度,必须小于或等于DRM_OBJECT_MAX_PROPERTY(值为24);

  • properties:指向drm_property的指针数组;

  • values:用于存储属性值的数组,与properties匹配;

其中struct drm_property定义在include/drm/drm_property.h:

Hidden Code
/**
 * struct drm_property - modeset object property
 *
 * This structure represent a modeset object property. It combines both the name
 * of the property with the set of permissible values. This means that when a
 * driver wants to use a property with the same name on different objects, but
 * with different value ranges, then it must create property for each one. An
 * example would be rotation of &drm_plane, when e.g. the primary plane cannot
 * be rotated. But if both the name and the value range match, then the same
 * property structure can be instantiated multiple times for the same object.
 * Userspace must be able to cope with this and cannot assume that the same
 * symbolic property will have the same modeset object ID on all modeset
 * objects.
 *
 * Properties are created by one of the special functions, as explained in
 * detail in the @flags structure member.
 *
 * To actually expose a property it must be attached to each object using
 * drm_object_attach_property(). Currently properties can only be attached to
 * &drm_connector, &drm_crtc and &drm_plane.
 *
 * Properties are also used as the generic metadatatransport for the atomic
 * IOCTL. Everything that was set directly in structures in the legacy modeset
 * IOCTLs (like the plane source or destination windows, or e.g. the links to
 * the CRTC) is exposed as a property with the DRM_MODE_PROP_ATOMIC flag set.
 */
struct drm_property {
        /**
         * @head: per-device list of properties, for cleanup.
         */
        struct list_head head;

        /**
         * @base: base KMS object
         */
        struct drm_mode_object base;
    
        /**
         * @flags:
         *
         * Property flags and type. A property needs to be one of the following
         * types:
         *
         * DRM_MODE_PROP_RANGE
         *     Range properties report their minimum and maximum admissible unsigned values.
         *     The KMS core verifies that values set by application fit in that
         *     range. The range is unsigned. Range properties are created using
         *     drm_property_create_range().
         *
         * DRM_MODE_PROP_SIGNED_RANGE
         *     Range properties report their minimum and maximum admissible unsigned values.
         *     The KMS core verifies that values set by application fit in that
         *     range. The range is signed. Range properties are created using
         *     drm_property_create_signed_range().
         *
         * DRM_MODE_PROP_ENUM
         *     Enumerated properties take a numerical value that ranges from 0 to
         *     the number of enumerated values defined by the property minus one,
         *     and associate a free-formed string name to each value. Applications
         *     can retrieve the list of defined value-name pairs and use the
         *     numerical value to get and set property instance values. Enum
         *     properties are created using drm_property_create_enum().
         *
         * DRM_MODE_PROP_BITMASK
         *     Bitmask properties are enumeration properties that additionally
         *     restrict all enumerated values to the 0..63 range. Bitmask property
         *     instance values combine one or more of the enumerated bits defined
         *     by the property. Bitmask properties are created using
         *     drm_property_create_bitmask().
         *
         * DRM_MODE_PROP_OBJECT
         *     Object properties are used to link modeset objects. This is used
         *     extensively in the atomic support to create the display pipeline,
         *     by linking &drm_framebuffer to &drm_plane, &drm_plane to
         *     &drm_crtc and &drm_connector to &drm_crtc. An object property can
         *     only link to a specific type of &drm_mode_object, this limit is
         *     enforced by the core. Object properties are created using
         *     drm_property_create_object().
         *
         *     Object properties work like blob properties, but in a more
         *     general fashion. They are limited to atomic drivers and must have
         *     the DRM_MODE_PROP_ATOMIC flag set.
         * DRM_MODE_PROP_BLOB
         *     Blob properties store a binary blob without any format restriction.
         *     The binary blobs are created as KMS standalone objects, and blob
         *     property instance values store the ID of their associated blob
         *     object. Blob properties are created by calling
         *     drm_property_create() with DRM_MODE_PROP_BLOB as the type.
         *
         *     Actual blob objects to contain blob data are created using
         *     drm_property_create_blob(), or through the corresponding IOCTL.
         *
         *     Besides the built-in limit to only accept blob objects blob
         *     properties work exactly like object properties. The only reasons
         *     blob properties exist is backwards compatibility with existing
         *     userspace.
         *
         * In addition a property can have any combination of the below flags:
         *
         * DRM_MODE_PROP_ATOMIC
         *     Set for properties which encode atomic modeset state. Such
         *     properties are not exposed to legacy userspace.
         *
         * DRM_MODE_PROP_IMMUTABLE
         *     Set for properties whose values cannot be changed by
         *     userspace. The kernel is allowed to update the value of these
         *     properties. This is generally used to expose probe state to
         *     userspace, e.g. the EDID, or the connector path property on DP
         *     MST sinks. Kernel can update the value of an immutable property
         *     by calling drm_object_property_set_value().
         */
        uint32_t flags;
    
        /**
         * @name: symbolic name of the properties
         */
        char name[DRM_PROP_NAME_LEN];
    
        /**
         * @num_values: size of the @values array.
         */
        uint32_t num_values;
    
        /**
         * @values:
         *
         * Array with limits and values for the property. The
         * interpretation of these limits is dependent upon the type per @flags.
         */
        uint64_t *values;
    
        /**
         * @dev: DRM device
         */
        struct drm_device *dev;
    
        /**
         * @enum_list:
         *
         * List of &drm_prop_enum_list structures with the symbolic names for
         * enum and bitmask values.
         */
        struct list_head enum_list;
};

四、DRM核心数据结构

学习DRM驱动,首先要了解驱动框架涉及到的数据结构,知道每个数据结构以及成员的含义之后,再去看源码就容易了。

4.1 struct drm_device

linux内核使用struct drm_device数据结构来描述一个drm设备,定义在include/drm/drm_device.h:

Hidden Code
/**
 * struct drm_device - DRM device structure
 *
 * This structure represent a complete card that
 * may contain multiple heads.
 */
struct drm_device {
        /** @if_version: Highest interface version set */
        int if_version;

        /** @ref: Object ref-count */
        struct kref ref;
    
        /** @dev: Device structure of bus-device */
        struct device *dev;
    
        /**
         * @managed:
         *
         * Managed resources linked to the lifetime of this &drm_device as
         * tracked by @ref.
         */
        struct {
                /** @managed.resources: managed resources list */
                struct list_head resources;
                /** @managed.final_kfree: pointer for final kfree() call */
                void *final_kfree;
                /** @managed.lock: protects @managed.resources */
                spinlock_t lock;
        } managed;
    
        /** @driver: DRM driver managing the device */
        const struct drm_driver *driver;
    
        /**
         * @dev_private:
         *
         * DRM driver private data. This is deprecated and should be left set to
         * NULL.
         *
         * Instead of using this pointer it is recommended that drivers use
         * devm_drm_dev_alloc() and embed struct &drm_device in their larger
         * per-device structure.
         */
        void *dev_private;
    
        /**
         * @primary:
         *
         * Primary node. Drivers should not interact with this
         * directly. debugfs interfaces can be registered with
         * drm_debugfs_add_file(), and sysfs should be directly added on the
         * hardware (and not character device node) struct device @dev.
         */
        struct drm_minor *primary;
    
        /**
         * @render:
         *
         * Render node. Drivers should not interact with this directly ever.
         * Drivers should not expose any additional interfaces in debugfs or
         * sysfs on this node.
         */
        struct drm_minor *render;
    
        /** @accel: Compute Acceleration node */
        struct drm_minor *accel;
    
        /**
         * @registered:
         *
         * Internally used by drm_dev_register() and drm_connector_register().
         */
        bool registered;
    
        /**
         * @master:
         *
         * Currently active master for this device.
         * Protected by &master_mutex
         */
        struct drm_master *master;
    
        /**
         * @driver_features: per-device driver features
         *
         * Drivers can clear specific flags here to disallow
         * certain features on a per-device basis while still
         * sharing a single &struct drm_driver instance across
         * all devices.
         */
        u32 driver_features;
    
        /**
         * @unplugged:
         *
         * Flag to tell if the device has been unplugged.
         * See drm_dev_enter() and drm_dev_is_unplugged().
         */
        bool unplugged;
    
        /** @anon_inode: inode for private address-space */
        struct inode *anon_inode;
    
        /** @unique: Unique name of the device */
        char *unique;
    
        /**
         * @struct_mutex:
         *
         * Lock for others (not &drm_minor.master and &drm_file.is_master)
         *
         * WARNING:
         * Only drivers annotated with DRIVER_LEGACY should be using this.
         */
        struct mutex struct_mutex;
    
        /**
         * @master_mutex:
         *
         * Lock for &drm_minor.master and &drm_file.is_master
         */
        struct mutex master_mutex;
    
        /**
         * @open_count:
         *
         * Usage counter for outstanding files open,
         * protected by drm_global_mutex
         */
        atomic_t open_count;
    
        /** @filelist_mutex: Protects @filelist. */
        struct mutex filelist_mutex;
        /**
         * @filelist:
         *
         * List of userspace clients, linked through &drm_file.lhead.
         */
        struct list_head filelist;
    
        /**
         * @filelist_internal:
         *
         * List of open DRM files for in-kernel clients.
         * Protected by &filelist_mutex.
         */
        struct list_head filelist_internal;
    
        /**
         * @clientlist_mutex:
         *
         * Protects &clientlist access.
         */
        struct mutex clientlist_mutex;
    
        /**
         * @clientlist:
         *
         * List of in-kernel clients. Protected by &clientlist_mutex.
         */
        struct list_head clientlist;
    
        /**
         * @vblank_disable_immediate:
         *
         * If true, vblank interrupt will be disabled immediately when the
         * refcount drops to zero, as opposed to via the vblank disable
         * timer.
         *
         * This can be set to true it the hardware has a working vblank counter
         * with high-precision timestamping (otherwise there are races) and the
         * driver uses drm_crtc_vblank_on() and drm_crtc_vblank_off()
         * appropriately. See also @max_vblank_count and
         * &drm_crtc_funcs.get_vblank_counter.
         */
        bool vblank_disable_immediate;
    
        /**
         * @vblank:
         *
         * Array of vblank tracking structures, one per &struct drm_crtc. For
         * historical reasons (vblank support predates kernel modesetting) this
         * is free-standing and not part of &struct drm_crtc itself. It must be
         * initialized explicitly by calling drm_vblank_init().
         */
        struct drm_vblank_crtc *vblank;
    
        /**
         * @vblank_time_lock:
         *
         *  Protects vblank count and time updates during vblank enable/disable
         */
        spinlock_t vblank_time_lock;
        /**
         * @vbl_lock: Top-level vblank references lock, wraps the low-level
         * @vblank_time_lock.
         */
        spinlock_t vbl_lock;
    
        /**
         * @max_vblank_count:
         *
         * Maximum value of the vblank registers. This value +1 will result in a
         * wrap-around of the vblank register. It is used by the vblank core to
         * handle wrap-arounds.
         *
         * If set to zero the vblank core will try to guess the elapsed vblanks
         * between times when the vblank interrupt is disabled through
         * high-precision timestamps. That approach is suffering from small
         * races and imprecision over longer time periods, hence exposing a
         * hardware vblank counter is always recommended.
         *
         * This is the statically configured device wide maximum. The driver
         * can instead choose to use a runtime configurable per-crtc value
         * &drm_vblank_crtc.max_vblank_count, in which case @max_vblank_count
         * must be left at zero. See drm_crtc_set_max_vblank_count() on how
         * to use the per-crtc value.
         *
         * If non-zero, &drm_crtc_funcs.get_vblank_counter must be set.
         */
        u32 max_vblank_count;
        /** @vblank_event_list: List of vblank events */
        struct list_head vblank_event_list;
    
        /**
         * @event_lock:
         *
         * Protects @vblank_event_list and event delivery in
         * general. See drm_send_event() and drm_send_event_locked().
         */
        spinlock_t event_lock;
    
        /** @num_crtcs: Number of CRTCs on this device */
        unsigned int num_crtcs;
    
        /** @mode_config: Current mode config */
        struct drm_mode_config mode_config;
    
        /** @object_name_lock: GEM information */
        struct mutex object_name_lock;
    
        /** @object_name_idr: GEM information */
        struct idr object_name_idr;
    
        /** @vma_offset_manager: GEM information */
        struct drm_vma_offset_manager *vma_offset_manager;
    
        /** @vram_mm: VRAM MM memory manager */
        struct drm_vram_mm *vram_mm;
    
        /**
         * @switch_power_state:
         *
         * Power state of the client.
         * Used by drivers supporting the switcheroo driver.
         * The state is maintained in the
         * &vga_switcheroo_client_ops.set_gpu_state callback
         */
        enum switch_power_state switch_power_state;
    
        /**
         * @fb_helper:
         *
         * Pointer to the fbdev emulation structure.
         * Set by drm_fb_helper_init() and cleared by drm_fb_helper_fini().
         */
        struct drm_fb_helper *fb_helper;
    
        /**
         * @debugfs_mutex:
         *
         * Protects &debugfs_list access.
         */
        struct mutex debugfs_mutex;
        /**
         * @debugfs_list:
         *
         * List of debugfs files to be created by the DRM device. The files
         * must be added during drm_dev_register().
         */
        struct list_head debugfs_list;
    
        /* Everything below here is for legacy driver, never use! */
        /* private: */
#if IS_ENABLED(CONFIG_DRM_LEGACY)
        /* List of devices per driver for stealth attach cleanup */
        struct list_head legacy_dev_list;

#ifdef __alpha__
        /** @hose: PCI hose, only used on ALPHA platforms. */
        struct pci_controller *hose;
#endif

        /* AGP data */
        struct drm_agp_head *agp;
    
        /* Context handle management - linked list of context handles */
        struct list_head ctxlist;
    
        /* Context handle management - mutex for &ctxlist */
        struct mutex ctxlist_mutex;
    
        /* Context handle management */
        struct idr ctx_idr;
    
        /* Memory management - linked list of regions */
        struct list_head maplist;
    
        /* Memory management - user token hash table for maps */
        struct drm_open_hash map_hash;
    
        /* Context handle management - list of vmas (for debugging) */
        struct list_head vmalist;
    
        /* Optional pointer for DMA support */
        struct drm_device_dma *dma;
    
        /* Context swapping flag */
        __volatile__ long context_flag;
    
        /* Last current context */
        int last_context;
    
        /* Lock for &buf_use and a few other things. */
        spinlock_t buf_lock;
    
        /* Usage counter for buffers in use -- cannot alloc */
        int buf_use;
    
        /* Buffer allocation in progress */
        atomic_t buf_alloc;
        struct {
                int context;
                struct drm_hw_lock *lock;
        } sigdata;
    
        struct drm_local_map *agp_buffer_map;
        unsigned int agp_buffer_token;
    
        /* Scatter gather memory */
        struct drm_sg_mem *sg;
    
        /* IRQs */
        bool irq_enabled;
        int irq;
#endif
};

初识这个数据结构,我们发现这个数据结构包含的字段属实有点多,如果要将每个字段的含义都搞清楚,定然不是一件容易的事情,因此我们只关注如下字段即可:

  • ref:具有动态生命周期的对象的引用计数,对象的初始引用计数为1,使用drm_dev_get和drm_dev_put获取和释放进一步的引用计数;

  • dev:设备驱动模型中的device,可以将drm_device看做其子类;

  • driver:drm驱动;

  • registered:设备是否已注册;

  • unique:设备的唯一名称;

  • vblank_event_list:vblank事件链表;

  • num_crtcs:CRTC的数量;

  • debugfs_list:保存struct drm_debugfs_entry的链表;

  • mode_config:当前的显示模式配置,struct drm_mode_config类型。

4.1.1 struct drm_minor

在struct drm_device数据结构中,primary、render、accel字段都是struct drm_minor
类型。

这个数据结构和我们在ALSA中介绍的 struct snd_minor是非常相似的,其创建和注册分别通过drm_minor_alloc和drm_minor_register实现。

struct drm_minor定义在include/drm/drm_file.h,DRM core会根据driver_features来决定是否为drm_device中的primary、render、accel注册字符设备(同时在/dev/dri目录下创建相应的设备节点),比如/dev/dri/card0、/dev/dri/renderD128等;

/*
 * FIXME: Not sure we want to have drm_minor here in the end, but to avoid
 * header include loops we need it here for now.
 */

/* Note that the order of this enum is ABI (it determines
 * /dev/dri/renderD* numbers).
 *
 * Setting DRM_MINOR_ACCEL to 32 gives enough space for more drm minors to
 * be implemented before we hit any future
 */
enum drm_minor_type {
        DRM_MINOR_PRIMARY,
        DRM_MINOR_CONTROL,
        DRM_MINOR_RENDER,
        DRM_MINOR_ACCEL = 32,
};

/**
 * struct drm_minor - DRM device minor structure
 *
 * This structure represents a DRM minor number for device nodes in /dev.
 * Entirely opaque to drivers and should never be inspected directly by drivers.
 * Drivers instead should only interact with &struct drm_file and of course
 * &struct drm_device, which is also where driver-private data and resources can
 * be attached to.
 */
struct drm_minor {
        /* private: */
        int index;                      /* Minor device number */
        int type;                       /* Control or render or accel */
        struct device *kdev;            /* Linux device */
        struct drm_device *dev;

        struct dentry *debugfs_root;

        struct list_head debugfs_list;
        struct mutex debugfs_lock; /* Protects debugfs_list. */
};

其中:

  • index:次设备号;

  • type:drm设备类型;

  • kdev:设备驱动模型中的device;

  • dev:drm设备;

  • debugfs_root:deugfs目录项;

  • debugfs_list:与debugfs有关的链表,链表中存放的都是struct drm_debugfs_entry;

struct drm_debugfs_entry定义在include/drm/drm_debugfs.h;

/**
 * struct drm_debugfs_info - debugfs info list entry
 *
 * This structure represents a debugfs file to be created by the drm
 * core. 描述debugfs文件信息
 */
struct drm_debugfs_info {
        /** @name: File name */
        const char *name;

        /**
         * @show:
         *
         * Show callback. &seq_file->private will be set to the &struct
         * drm_debugfs_entry corresponding to the instance of this info
         * on a given &struct drm_device.
         */
        int (*show)(struct seq_file*, void*);

        /** @driver_features: Required driver features for this entry. */
        u32 driver_features;

        /** @data: Driver-private data, should not be device-specific. */
        void *data;
};


/**
 * struct drm_debugfs_entry - Per-device debugfs node structure
 *
 * This structure represents a debugfs file, as an instantiation of a &struct
 * drm_debugfs_info on a &struct drm_device.
 */
struct drm_debugfs_entry {
        /** @dev: &struct drm_device for this node. */
        struct drm_device *dev;

        /** @file: Template for this node. */
        struct drm_debugfs_info file;

        /** @list: Linked list of all device nodes. */
        struct list_head list;  // 链表节点,用于将当节点添加到drm设备的debugfs_list链表中
};

4.1.2 struct drm_mode_config

linux内核使用struct drm_mode_config来描述显示模式配置信息,drm_mode_config的主要功能之一是提供对显示器模式的管理和配置。这包括添加、删除、修改和查询显示器模式的能力。此外,drm_mode_config还提供了与模式相关的配置选项,例如色彩空间、刷新率、分辨率等等。

struct drm_mode_config定义在include/drm/drm_mode_config.h:

-----------------------------------------------------------------------------------------------

开发板 :NanoPC-T4开发板
eMMC16GBLPDDR34GB显示屏 :15.6英寸HDMI接口显示屏u-boot2023.04linux6.3
-----------------------------------------------------------------------------------------------

如果我们需要编写一个DRM驱动,我们应该怎么做呢?具体流程如下:

(1) 定义struct drm_driver,并初始化成员name、desc、data、major、minor、driver_features、fops、dumb_create等;

(2)调用drm_dev_alloc函数分配并初始化一个struct drm_device;

(3) 调用drm_mode_config_init初始化drm_device中mode_config结构体;

(4) 调用drm_xxx_init创建framebuffer、plane、crtc、encoder、connector这5个drm_mode_object;

在DRM子系统中是通过component框架完成各个功能模块的注册,比如在:CRTC驱动程序:包含了plane和crtc的初始化工作;

  • HDMI驱动程序:包含了encoderconnector的初始化工作;

  • edp驱动程序:包含了encoderconnector的初始化工作;

  • ......

(5) 调用drm_dev_register注册drm_device;

  • 创建drm设备节点/dev/dri/card%d;

  • 注册plane、crtc、encoder、connector这4个drm_mode_object;

一、显示子系统概述

显示子系统是Rockchip平台显示输出相关软硬件系统的统称,linux内核采用component
框架来构建显示子系统,一个显示子系统由显示处理器(vop,video output processor)、接口控制器(mipi,lvds,hdmi、edp、dp、rgb、BT1120、BT656、I8080(MCU显示接口)等)、液晶背光,电源等多个独立的功能模块构成。

那么问题来了,什么是显示处理器?

  • 将在内存中的图像数据,转化为电信号送到显示设备称为显示控制器,比如早期的LCDC;

  • 后面进行了拓展,可以处理一些简单的图像,比如缩放、旋转、合成等,如瑞芯的vop,高通的sde称为显示处理器;

显示处理器可以在没有CPU参与的情况下可以做一些简单的图像处理,比如:缩放,旋转等操作;

  • 支持多层,并可以进行合成,支持porter-duff;

  • 支持多种显存格式(ARGB888,RGB565, 包括GPU输出的tile格式以及fbdc压缩格式)等;

  • 支持生成时序信号如tcon,送给如mipi、lvds等接口;

  • 支持多种分辨率;

1.1 硬件框图

整个显示系统的硬件框架如下图所示:

VOP 1.0显示子系统架构
VOP 2.0显示子系统架构

从上面的框图可以看到,在整个显示通路的最后端,是由RGA,GPU、VPU组成的显示图形加速模块,他们是专门针对图像处理优化设计的硬件IP,能够高效的进行图像的⽣成和进一步处理(比如GPU通过opengl功能提供图像渲染功能,RGA可以对图像数据进行缩放,旋转,合成等2D处理,VPU可以高效的进行视频解码),从而减轻CPU负担。

经过这些图像加速模块处理后的数据会存放在DDR中,然后由VOP读取,根据应用需求进行Alpha叠加,颜色空间转换,gamma矫正,HDR转换 等处理后,再发送到对应的显示接口模块(HDMIeDP/DPDSIRGB/BT1120/BT656LVDS), 这些接口模块会把接收到的数据转换成符合各⾃协议的数据流,发送到显示器或者屏幕上,呈现在最终用户眼前。

目前Rockchip平台上存在两种VOP架构:

  • VOP 1.0VOP 1.0是用多VOP的方式来实现多屏幕显示,即正常情况下,一个VOP在同一时刻只能输出一路独立的显示时序,驱动一个屏幕显示独立的内容。如果需要实现双屏显示,则需要有两个VOP来实现,所以在RK3288RK3399PX30等⽀持双显的平台上,都有两个独立的VOP

  • VOP 2.0VOP 2.0采用了统一显示架构,即整个SoC上只存在一个VOP,但是在VOP的后端设计了多路独立的Video Port(简称VP) 输出接口,这些VP能够同时独立⼯作,并且输出相互独立的显示时序。比如在上面的VOP 2.0框图中,有三个VP,就能同时实现三屏异显;

1.1.1RK3399

RK3399有2个VOP

  • Video Output Processor(VOP_BIG):supports 4096x2160 with AFBC;

  • Video Output Processor(VOP_LIT):supports 2560x1600

支持的显示接口:

  • 双通道MIPI DSI(4线/通道)显示接口;

  • 1个eDP显示接口;

  • 1个DP显示接口;

  • 1个HDMI显示接口;

RK3399支持的最大输出分辨率和协议标准如下:

显示接口最大输出协议标准
eDPVOP BIG: 3840x2160@60hz
VOP LITE: 2560x1600@60hz
支持 DP1.2a 和 eDP1.3 协议标准
MIPI单通道:1920x1080@60hz
双通道:2560x1600@60hz
支持 DSI v1.1,DCS v1.1,DPHY v1.1 协议标准
HDMIVOP BIG::4096X2160@60hz
VOP LITE: 2560x1600@60hz
支持 HDMI 1.4a 和 2.0a 协议标准
DPVOP BIG::4096X2160@60hz
VOP LITE: 2560x1600@60hz
支持 DP 1.2 协议标准
1.1.2 NanoPC T4

我们所使用的的NanoPC T4开发板,视频输出支持:

  • LCD Interface: 一个eDP 1.3(4线,10.8Gbps), 一个或2个4线MIPI DSI

  • DP on Type-C:DisplayPort 1.2 Alt Mode on USB Type-C;

  • HDMI:HDMI 2.0a, 支持4K@60Hz显示,支持HDCP 1.4/2.2;

1.2 DRM加载顺序

DRM驱动是由一系列相关功能模块的驱动的结合,它包含了vopmipilvdshdmiedpdpbacklight等等显示通路上的依赖模块。只有这些相互依赖的模块都加载完整,整个drm系统才算启动完成。

在《DRM子系统》中我们介绍了如何去抽象显示硬件到具体的DRM object;这里我们结合Rockchip平台以MIPI DSI显示接口为例来介绍显示硬件到具体的DRM object抽象,

object说明
plane图层;对Overlay硬件的抽象,同样需要访问Display Controller寄存器,因此也放在Display Controller驱动中
在Rockchip平台里对应SoC内部vop模块的win图层
crtc显示控制器;RGB timing的产生,以及显示数据的更新,都需要访问Dislay Controller硬件寄存器,因此放在Display Controller驱动中
在Rockchip平台里对应SoC内部的vop模块
encoder编码器;将RGB并行信号转换为DSI行信号,需要配置DSI硬件寄存器,因此放在DSI Controller驱动中
connector连接器;可以通过drm_panel来获取LCD的mode信息,但是encoder在哪,connector就在哪,因此放在DSI Controller驱动中
drm_panel用于获取LCD mode参数,并提供LCD休眠唤醒的回调接口,供encoder调用,因此放在LCD驱动中
bridge桥接设备;一般用于注册encoder后面另外再接的转换芯片,如DSI2HDMI转换芯片

接下来我们将会以RK3399 DRM驱动为例对显示子系统的各个模块进行介绍;

驱动文件清单
coredrivers/gpu/drm/rockchip/rockchip_drm_drv.c
framebufferdrivers/gpu/drm/rockchip/rockchip_drm_fb.c
gemdrivers/gpu/drm/rockchip/rockchip_drm_gem.c
vopdrivers/gpu/drm/rockchip/rockchip_drm_vop.c
drivers/gpu/drm/rockchip/rockchip_vop_reg.c
drivers/gpu/drm/rockchip/rockchip_drm_vop2.c
drivers/gpu/drm/rockchip/rockchip_vop2_reg.c
lvdsdrivers/gpu/drm/rockchip/rockchip_lvds.c
rgbdrivers/gpu/drm/rockchip/rockchip_rgb.c
mipidrivers/gpu/drm/drm_mipi_dsi.c
drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
hdmidrivers/gpu/drm/rockchip/dw_hdmi-rockchip.c(特定于Rockchip平台,支持DesignWare (DW) HDMI 控制器,RK3399使用)
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c(支持通用的DesignWare HDMI控制器,集成了DesignWare HDMI PHY驱动;适用于多个平台,功能较为通用)
drivers/gpu/drm/rockchip/inno-hdmi.c( Inno HDMI 控制器,RK3036使用)
drivers/phy/rockchip/phy-rockchip-inno-hdmi.c(Inno HDMI PHY(物理层驱动的文件)
edpdrivers/gpu/drm/rockchip/analogix_dp-rockchip.c
drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c
dpdrivers/gpu/drm/rockchip/cdn-dp-core.c
drivers/gpu/drm/rockchip/cdn-dp-reg.c

可以看到驱动位于如下目录:

  • drivers/gpu/drm/rockchip/

  • drivers/gpu/drm/bridge/analogix/

  • drivers/gpu/drm/bridge/synopsys/

  • drivers/phy/rockchip/

显示子系统各个模块驱动加载顺序如下图所示:

img

这里我们对驱动加载顺序图简单说明一下:

  • 在各种encoder driver和crtc driver的probe函数中:通过component_add
    将自己注册进系统;

  • 在Rockchip DRM Master driver的probe函数中;通过rockchip_drm_match_add

    • 为每个component(各种encoder和crtc)注册一个component_match_array
      到component_match;

    • 通过component_master_add_with_match触发各种encoder和crtc component
      的bind操作,例如vop_bind、dw_hdmi_rockchip_bind等;

  • bind的含义就是将DRM框架里的组件关联在一起,以vop_bind为例:

    • VOP driver对应crtc drivercrtc负责连接planeencoder;

    • vop_create_crtc->drm_crtc_init_with_planes初始化crtc对象,并和plane关联在一起;

  • 剩下的就是边边角角的工作,例如注册framebuffer以兼容FBDEV,显示logo等。

因为这些复杂的依赖关系,在DRM系统初始化的过程中,可能会出现某个资源暂时未就绪,而导致某个模块暂时无法顺利加载的情况。

为了解决这种问题,DRM驱动利用了Linux驱动中的deferred probe机制,当发现某个依赖的资源未就绪的时候,驱动返回-EPROBE_DEFER(-517), 然后退出。Linux kernel会在稍后再次尝试加载这个驱动,直到依赖的资源就绪,驱动顺利加载为止。

二、设备树配置

在RK3399上,包含两个VOP、以及1个MIPI、1个DP、1个eDP、双通道MIPI DSI
(4线/通道)显示接口;根据不同的显示屏,我们选择不同的模块来组成显示通路。具体使用那些模块,以及这些模块之间如何衔接通过dts配置。

2.1 display_subsystem设备节点

在每⼀个⽀持DRM显⽰功能的SoC的核⼼设备树⾥⾯,都会有display_subsystem节点:所有的子设备信息都通过设备树描述关联起来,这样系统开机后,就能统一的管理各个设备。

display_subsystem设备节点定义在arch/arm64/boot/dts/rockchip/rk3399.dtsi

 display_subsystem: display-subsystem {
		compatible = "rockchip,display-subsystem";
		ports = <&vopl_out>, <&vopb_out>;
};

该节点描述的是Rockchip DRM主设备,也就是我们在component框架中介绍的aggregate_device,这是一个虚拟设备,用于列出组成图形子系统的所有vop
设备或其他显示接口节点。该设备节点对应的驱动代码位于drivers/gpu/drm/rockchip/rockchip_drm_drv.c。

其中ports属性描述vop硬件资源,列出了指向各个vop设备的phandle,vopl_out、vopb_out对应着VOP_LITE、VOP_BIG。

更多属性信息可以参考:

  • Documentation/devicetree/bindings/display/rockchip/rockchip-drm.yaml

  • Documentation/devicetree/bindings/display/rockchip/rockchip-vop.yaml

2.2 vop设备节点

vop设备节点描述了vop硬件资源,控制vop驱动的加载rockchip_drm_vop.crockchip_drm_vop2.c

以设备节点vopb_out为例,vopb_out设备节点定义在arch/arm64/boot/dts/rockchip/rk3399.dtsi;

vopb: vop@ff900000 {
		compatible = "rockchip,rk3399-vop-big";
		reg = <0x0 0xff900000 0x0 0x2000>, <0x0 0xff902000 0x0 0x1000>;
		interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH 0>;
		assigned-clocks = <&cru ACLK_VOP0>, <&cru HCLK_VOP0>;
		assigned-clock-rates = <400000000>, <100000000>;
		clocks = <&cru ACLK_VOP0>, <&cru DCLK_VOP0>, <&cru HCLK_VOP0>;
		clock-names = "aclk_vop", "dclk_vop", "hclk_vop";
		iommus = <&vopb_mmu>;
		power-domains = <&power RK3399_PD_VOPB>;
		resets = <&cru SRST_A_VOP0>, <&cru SRST_H_VOP0>, <&cru SRST_D_VOP0>;
		reset-names = "axi", "ahb", "dclk";
		status = "disabled";

		vopb_out: port {
				#address-cells = <1>;
				#size-cells = <0>;

				vopb_out_edp: endpoint@0 {
						reg = <0>;
						remote-endpoint = <&edp_in_vopb>;
				};

				vopb_out_mipi: endpoint@1 {
						reg = <1>;
						remote-endpoint = <&mipi_in_vopb>;
				};

				vopb_out_hdmi: endpoint@2 {
						reg = <2>;
						remote-endpoint = <&hdmi_in_vopb>;
				};

				vopb_out_mipi1: endpoint@3 {
						reg = <3>;
						remote-endpoint = <&mipi1_in_vopb>;
				};

				vopb_out_dp: endpoint@4 {
						reg = <4>;
						remote-endpoint = <&dp_in_vopb>;
				};
		};
};

子节点port下的endpoint描述的是vop和显示接口的连接关系,vopb_out节点下有vopb_out_edp,vopb_out_mipi,vopb_out_hdmi,vopb_out_mipi1、vopb_out_dp
五个节点,说明vopb可以和mipi dsi0、edp、hdmi、mipi dsi1、dp五个显示接口连接。

每个endpoint通过remote-endpoint属性和对应的显示接口组成一个连接通路,例如vopb_out_hdmi--->hdmi_in_vopb。

设备节点vopl_out同理,这里就不在介绍了;

2.3 内核配置

make menuconfig配置内核:

Device Drivers --->
  Graphics support --->
   	 <*> Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)  ---> 
  	 <*>  DRM Support for Rockchip (DRM_ROCKCHIP [=y])     
     [*]   Rockchip VOP driver
	 [ ]   Rockchip VOP2 driver
     [*]   Rockchip specific extensions for Analogix DP driver  (ROCKCHIP_ANALOGIX_DP [=y]) 
     [*]   Rockchip cdn DP
     [*]   Rockchip specific extensions for Synopsys DW HDMI 
     [*]   Rockchip specific extensions for Synopsys DW MIPI DSI
     [*]   Rockchip specific extensions for Innosilicon HDMI
     [*]   Rockchip LVDS support
     [ ]   Rockchip RGB support

三、 DRM驱动入口

DRM驱动模块入口函数为rockchip_drm_init,位于drivers/gpu/drm/rockchip/rockchip_drm_drv.c

#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;

        if (drm_firmware_drivers_only())
                return -ENODEV;

    	// 1. 根据配置来决定是否添加xxx_xxx_driver到数组rockchip_sub_drivers
        num_rockchip_sub_drivers = 0;
        ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_ROCKCHIP_VOP);
        ADD_ROCKCHIP_SUB_DRIVER(vop2_platform_driver, CONFIG_ROCKCHIP_VOP2);
        ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver,
                                CONFIG_ROCKCHIP_LVDS);
        ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,
                                CONFIG_ROCKCHIP_ANALOGIX_DP);
        ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP);
        ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
                                CONFIG_ROCKCHIP_DW_HDMI);
        ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi_rockchip_driver,
                                CONFIG_ROCKCHIP_DW_MIPI_DSI);
        ADD_ROCKCHIP_SUB_DRIVER(inno_hdmi_driver, CONFIG_ROCKCHIP_INNO_HDMI);
        ADD_ROCKCHIP_SUB_DRIVER(rk3066_hdmi_driver,
                                CONFIG_ROCKCHIP_RK3066_HDMI);

		// 2. 注册多个platform driver    
        ret = platform_register_drivers(rockchip_sub_drivers,
                                        num_rockchip_sub_drivers);
        if (ret)
                return ret;

    	// 3. 注册rockchip_drm_platform_driver
        ret = platform_driver_register(&rockchip_drm_platform_driver);
        if (ret)
                goto err_unreg_drivers;

        return 0;

err_unreg_drivers:
        platform_unregister_drivers(rockchip_sub_drivers,
                                    num_rockchip_sub_drivers);
        return ret;
}


module_init(rockchip_drm_init);

(1) 函数内部多次调用宏ADD_ROCKCHIP_SUB_DRIVER,完成vop、以及显示接口(lvds
、dp、hdmi、mipi dsi)的添加。

咱们以hdmi如下代码为例;

ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
            CONFIG_ROCKCHIP_DW_HDMI);

展开得到:

if (IS_ENABLED(CONFIG_ROCKCHIP_DW_HDMI) && 
   !WARN_ON(num_rockchip_sub_drivers >= MAX_ROCKCHIP_SUB_DRIVERS)) 
      rockchip_sub_drivers[num_rockchip_sub_drivers++] = &dw_hdmi_rockchip_pltfm_driver;

如果定义了CONFIG_ROCKCHIP_DW_HDMI,会将dw_hdmi_rockchip_pltfm_driver
保存到rockchip_sub_drivers数组中。

#define MAX_ROCKCHIP_SUB_DRIVERS 16
// 数组长度为16
static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS];

那么宏CONFIG_ROCKCHIP_DW_HDMI到底是什么呢?

root@zhengyang:/work/sambashare/rk3399/linux-6.3# grep "CONFIG_ROCKCHIP_DW_HDMI" drivers/gpu/* -nR
drivers/gpu/drm/rockchip/Makefile:13:rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o

可以看到宏CONFIG_ROCKCHIP_DW_HDMI决定了是否将dw_hdmi-rockchip.c
编译到内核。

有关vop、以及显示接口(lvds、dp、hdmi、mipi dsi)的驱动咱们在后面章节单独介绍。

(2)调用platform_register_drivers注册num_rockchip_sub_drivers个platform driver
;该函数内部遍历rockchip_sub_drivers数组,多次调用platform_driver_register
注册platform driver,函数定义在drivers/base/platform.c;

(3) 最后调用platform_driver_register注册rockchip_drm_platform_driver。

3.1 rockchip_drm_platform_driver

dw_hdmi_rockchip_pltfm_driver定义在drivers/gpu/drm/rockchip/rockchip_drm_drv.c

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多