程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

Rockchip RK3399 - DRM驱动程序

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

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

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

(1) 定义struct drm_driver,并初始化成员namedescdatamajorminordriver_featuresfopsdumb_create等;

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

(3) 调用drm_mode_config_init初始化drm_devicemode_config结构体;

(4) 调用drm_xxx_init创建 framebufferplanecrtcencoderconnector 这5个 drm_mode_object

DRM子系统中是通过component框架完成各个功能模块的注册,比如在:

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

(5) 调用drm_dev_register注册drm_device

  • 创建drm设备节点/dev/dri/card%d
  • 注册planecrtcencoderconnector这4个drm_mode_object

一、显示子系统概述

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

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

  • 将在内存中的图像数据,转化为电信号送到显示设备称为显示控制器,比如早期的LCDC
  • 后面进行了拓展,可以处理一些简单的图像,比如缩放、旋转、合成等,如瑞芯的vop,高通的sde称为显示处理器;

显示处理器可以在没有CPU参与的情况下可以做一些简单的图像处理,比如:

  • 缩放,旋转等操作;
  • 支持多层,并可以进行合成,支持porter-duff
  • 支持多种显存格式(ARGB888,RGB565, 包括GPU输出的tile格式以及fbdc压缩格式)等;
  • 支持生成时序信号如tcon,送给如mipilvds等接口;
  • 支持多种分辨率;

1.1 硬件框图

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

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

从上面的框图可以看到,在整个显示通路的最后端,是由RGAGPUVPU组成的显示图形加速模块,他们是专门针对图像处理优化设计的硬件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.1 RK3399

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支持的最大输出分辨率和协议标准如下:

显示接口 最大输出 协议标准
eDP VOP 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 协议标准
HDMI VOP BIG::4096X2160@60hz
VOP LITE: 2560x1600@60hz
支持 HDMI 1.4a 和 2.0a 协议标准
DP VOP 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驱动为例对显示子系统的各个模块进行介绍;

驱动 文件清单
core drivers/gpu/drm/rockchip/rockchip_drm_drv.c
framebuffer drivers/gpu/drm/rockchip/rockchip_drm_fb.c
gem drivers/gpu/drm/rockchip/rockchip_drm_gem.c
vop drivers/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
lvds drivers/gpu/drm/rockchip/rockchip_lvds.c
rgb drivers/gpu/drm/rockchip/rockchip_rgb.c
mipi drivers/gpu/drm/drm_mipi_dsi.c
drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c
hdmi drivers/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(物理层驱动的文件)
edp drivers/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
dp drivers/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 drivercrtc driverprobe函数中:通过component_add将自己注册进系统;
  • Rockchip DRM Master driverprobe函数中;
    • 通过rockchip_drm_match_add为每个component(各种encodercrtc )注册一个component_match_arraycomponent_match
    • 通过component_master_add_with_match触发各种encodercrtc componentbind操作,例如vop_binddw_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设备的phandlevopl_outvopb_out对应着VOP_LITEVOP_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_edpvopb_out_mipivopb_out_hdmivopb_out_mipi1vopb_out_dp五个节点,说明vopb可以和mipi dsi0edphdmimipi dsi1dp五个显示接口连接。

每个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、以及显示接口(lvdsdphdmimipi 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、以及显示接口(lvdsdphdmimipi dsi)的驱动咱们在后面章节单独介绍。

(2)调用platform_register_drivers注册num_rockchip_sub_driversplatform 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

static struct platform_driver rockchip_drm_platform_driver = {
        .probe = rockchip_drm_platform_probe,
        .remove = rockchip_drm_platform_remove,
        .shutdown = rockchip_drm_platform_shutdown,
        .driver = {
                .name = "rockchip-drm",
                .of_match_table = rockchip_drm_dt_ids,
                .pm = &rockchip_drm_pm_ops,
        },
};
3.1.1 of_match_table

其中of_match_table用于设备树匹配,匹配设备树中compatible = "rockchip,display-subsystem"的设备节点;

static const struct of_device_id rockchip_drm_dt_ids[] = {
        { .compatible = "rockchip,display-subsystem", },
        { /* sentinel */ },
};
3.1.2 rockchip_drm_platform_probe

plaform总线设备驱动模型中,我们知道当内核中有platform设备和platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是rockchip_drm_platform_probe函数;

static const struct component_master_ops rockchip_drm_ops = {
        .bind = rockchip_drm_bind,
        .unbind = rockchip_drm_unbind,
};

// 校验display-subsystem设备节点的属性ports是否有效
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++) {
            	// 获取ports属性第i个元素指向的设备节点
                // 例如:ports = <&vopl_out>, <&vopb_out> 保存了指向vop设备的phandle,返回指向设备节点的指针
                port = of_parse_phandle(np, "ports", i);
                if (!port)
                        break;

            	// port的父设备节点存在,则不会进入
                if (!of_device_is_available(port->parent)) {
                        of_node_put(port);
                        continue;
                }

            	// 设置为true
                found = true;
                of_node_put(port);
        }

        if (i == 0) {
                DRM_DEV_ERROR(dev, "missing 'ports' property\n");
                return -ENODEV;
        }

        if (!found) {
                DRM_DEV_ERROR(dev,
                              "No available vop found for display-subsystem.\n");
                return -ENODEV;
        }

        return 0;
}


static int rockchip_drm_platform_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        struct component_match *match = NULL;
        int ret;


    	// 校验display-subsystem设备节点的属性ports是否有效
        ret = rockchip_drm_platform_of_probe(dev);
        if (ret)
                return ret;

    	// 构建一个带release函数的component_match
        match = rockchip_drm_match_add(dev);
        if (IS_ERR(match))
                return PTR_ERR(match);

    	// 向系统注册一个aggregate_device,触发执行rockchip_drm_ops.bind,即rockchip_drm_bind
        ret = component_master_add_with_match(dev, &rockchip_drm_ops, match);
        if (ret < 0) {
                rockchip_drm_match_remove(dev);
                return ret;
        }

        return 0;
}

(1) 这里代码很简单,首先使用rockchip_drm_match_add来构建一个带release函数的component_match

static struct component_match *rockchip_drm_match_add(struct device *dev)
{
        struct component_match *match = NULL;
        int i;
    
		// 循环遍历
        for (i = 0; i < num_rockchip_sub_drivers; i++) {
            	// 获取第i个platform_driver
                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注册
                        component_match_add(dev, &match, component_compare_dev, d);
                } while (true);
        }

        if (IS_ERR(match))
                rockchip_drm_match_remove(dev);

        return match ?: ERR_PTR(-ENODEV);
}

(2) 最后为设备pdev->dev向系统注册一个aggregate_device,其中系统可执行的初始化操作被设置为了rockchip_drm_ops,我们需要重点关注bind函数的实现,即rockchip_drm_bind,这个我们单独小节介绍。

3.2 rockchip_drm_bind

我们定位到drivers/gpu/drm/rockchip/rockchip_drm_drv.c文件的函数rockchip_drm_bind,该函数用于绑定设备并初始化DRM驱动;

static int rockchip_drm_bind(struct device *dev)
{
        struct drm_device *drm_dev;
        struct rockchip_drm_private *private;
        int ret;

        /* 1. Remove existing drivers that may own the framebuffer memory. */
        ret = drm_aperture_remove_framebuffers(false, &rockchip_drm_driver);
        if (ret) {
                DRM_DEV_ERROR(dev,
                              "Failed to remove existing framebuffers - %d.\n",
                              ret);
                return ret;
        }

	    // 2. 动态分配并初始化struct drm_device实例
        drm_dev = drm_dev_alloc(&rockchip_drm_driver, dev); 
        if (IS_ERR(drm_dev))
                return PTR_ERR(drm_dev);

        // 3. 设置drm设备驱动私有数据为drm设备
        dev_set_drvdata(dev, drm_dev); 

        // 4. 动态分配rockchip_drm_private
        private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL); 
        if (!private) {
                ret = -ENOMEM;
                goto err_free;
        }

        drm_dev->dev_private = private;  

    	// 5. 初始化drm_device中mode_config结构体
        ret = drmm_mode_config_init(drm_dev); 
        if (ret)
                goto err_free;

    	// 初始化drm_device中mode_config的中成员
        rockchip_drm_mode_config_init(drm_dev);

        /* 6. Try to bind all sub drivers. 执行按顺序执行显示子系统的各个组件的bind函数 */
        ret = component_bind_all(dev, drm_dev);
        if (ret)
                goto err_free;

    	// 7. 初始化IOMMU
        ret = rockchip_drm_init_iommu(drm_dev);
        if (ret)
                goto err_unbind_all;

	    // 8.初始化vblank
        ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
        if (ret)
                goto err_iommu_cleanup;

        // 9. 重置模式配置
        drm_mode_config_reset(drm_dev);

        /* 10.init kms poll for handling hpd */
        drm_kms_helper_poll_init(drm_dev);

	    // 11.注册drm设备
        ret = drm_dev_register(drm_dev, 0); 
        if (ret)
                goto err_kms_helper_poll_fini;

	    // 12.设置帧缓冲设备
        drm_fbdev_generic_setup(drm_dev, 0);

        return 0;
err_kms_helper_poll_fini:
        drm_kms_helper_poll_fini(drm_dev);
err_iommu_cleanup:
        rockchip_iommu_cleanup(drm_dev);
err_unbind_all:
        component_unbind_all(dev, drm_dev);
err_free:
        drm_dev_put(drm_dev);
        return ret;
}

函数主要步骤如下:

(1) 首先,通过调用drm_aperture_remove_framebuffers函数,移除可能拥有framebuffer memory的现有驱动程序;

为什么要执行这个操作呢?对于一个图形设备来说,可能有多个驱动程序提供支持,但在任何给定的事件只能有一个驱动程序处于活动状态。许多系统在引导过程的早期加载通用图形驱动程序,例如EFI-GOPVESA,在后续的引导阶段,它们会将通用驱动程序替换为专用的、针对具体硬件的驱动程序。为了接管该设备,专用驱动程序首先必须移除通用驱动程序。DRM aperture函数负责管理DRM framebuffer内存的所有权和驱动程序之间的交接;

更多内容可以参考:Managing Ownership of the Framebuffer Aperture

(2) 接下来,使用drm_dev_alloc函数为设备动态分配和初始化一个struct drm_device结构体,并将其保存在drm_dev指针中,其driver被设置为rockchip_drm_driver

drm_dev = drm_dev_alloc(&rockchip_drm_driver, dev); 

(3) 然后,使用dev_set_drvdata函数将设备的驱动私有数据设置为drm_dev

dev_set_drvdata(dev, drm_dev); 

(4) 使用devm_kzalloc函数为drm_dev动态分配一个rockchip_drm_private结构体,并将其保存在drm_dev->dev_private指针中;

private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL)

(5) 调用drmm_mode_config_init函数和rockchip_drm_mode_config_init函数对drm_dev进行模式配置的初始化;

(6) 调用component_bind_all函数,执行按顺序执行显示子系统的各个组件的bind函数;

(7) 调用rockchip_drm_init_iommu函数初始化IOMMU

(8) 调用drm_vblank_init函数初始化vblank

(9) 调用drm_mode_config_reset函数重置模式配置;

(10) 调用drm_kms_helper_poll_init函数初始化KMS轮询以处理HPD(Hot Plug Detect)

(11) 在所有准备工作都完成后,调用drm_dev_register函数注册DRM设备;

(12) 调用drm_fbdev_generic_setup函数设置帧缓冲设备;

3.2.1 初始化drm设备

一个drm驱动的设备由struct drm_device来表示,这里调用drm_dev_alloc进行分配和初始化一个struct drm_device实例,其driver被设置为rockchip_drm_driver,有关rockchip_drm_driver单独小节介绍。

drm_dev_alloc函数定义在drivers/gpu/drm/drm_drv.c

/**
 * drm_dev_alloc - Allocate new DRM device
 * @driver: DRM driver to allocate device for
 * @parent: Parent device object
 *
 * This is the deprecated version of devm_drm_dev_alloc(), which does not support
 * subclassing through embedding the struct &drm_device in a driver private
 * structure, and which does not support automatic cleanup through devres.
 *
 * RETURNS:
 * Pointer to new DRM device, or ERR_PTR on failure.
 */
struct drm_device *drm_dev_alloc(const struct drm_driver *driver,
                                 struct device *parent)
{
        struct drm_device *dev;
        int ret;

    	// 动态分配一个struct drm_device 
        dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        if (!dev)
                return ERR_PTR(-ENOMEM);

    	// 初始化drm_device
        ret = drm_dev_init(dev, driver, parent);
        if (ret) {
                kfree(dev);
                return ERR_PTR(ret);
        }

        drmm_add_final_kfree(dev, dev);

        return dev;
}

drm_dev_alloc函数时一个已废弃版本,它不支持在驱动程序的私有结构中嵌入struct drm_device来实现子类化,并且不支持通过devres进行自动清理。

drm_dev_alloc函数内部调用drm_dev_init去初始化struct drm_device实例。

3.2.2 模式配置初始化

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(2854)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示