Rockchip RK3399 - DRM驱动程序

如果我们需要编写一个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创建 planecrtcencoderconnector 这4个 drm_mode_object

(5) 调用drm_dev_register注册drm_device

一、显示子系统概述

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

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

  • 将在内存中的图像数据,转化为电信号送到显示设备称为显示控制器,比如早期的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转换 等处理后,再发送到对应的显示接口模块(HDMI/DP/DSI/RGB/LVDS), 这些接口模块会把接收到的数据转换成符合各⾃协议的数据流,发送到显示器或者屏幕上,呈现在最终用户眼前。

目前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

在一颗SoC上,可能有多个vopmipilvdshdmiedpdp模块,根据具体产品定义,一款产品可能只需要使用其中一部分模块组成显示通路。具体使用那些模块,以及这些模块之间如何衔接通过dts配置。

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/rockchip/dw-mipi-dsi-rockchip.c
hdmi drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
drivers/gpu/drm/rockchip/inno_hdmi.c
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

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

img

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

  • 在各种Encoder driverCRCT driverprobe函数中:通过component_add将自己注册进系统;
  • Rockchip DRM Master driverprobe函数中;
    • 通过rockchip_drm_match_add为每个component(各种EncoderCRCT)注册一个component_match_arraycomponent_match
    • 通过component_master_add_with_match触发各种EncoderCRCT componentbind操作,例如vop_binddw_hdmi_rockchip_bind等;
  • bind的含义就是将DRM框架里的组件关联在一起,以vop_bind为例:
    • VOP driver对应CRCT 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会在稍后再次尝试加载这个驱动,直到依赖的资源就绪,驱动顺利加载为止。

二、设备树配置

2.1 display_subsystem设备节点

在设备树中,有一个总的设备节点,也就是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可以和mipiedphdmimipi1dp五个显示接口连接。

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

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

2.2.1 vopb_out_hdmi

vopb_out_hdmi通过hdmi_in_vopbhdmi显示接口组成一个连接通路;

hdmi: hdmi@ff940000 {
		compatible = "rockchip,rk3399-dw-hdmi";
		reg = <0x0 0xff940000 0x0 0x20000>;
		interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH 0>;
		clocks = <&cru PCLK_HDMI_CTRL>,
				 <&cru SCLK_HDMI_SFR>,
				 <&cru SCLK_HDMI_CEC>,
				 <&cru PCLK_VIO_GRF>,
				 <&cru PLL_VPLL>;
		clock-names = "iahb", "isfr", "cec", "grf", "ref";
		power-domains = <&power RK3399_PD_HDCP>;
		reg-io-width = <4>;
		rockchip,grf = <&grf>;
		#sound-dai-cells = <0>;
		status = "disabled";

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

						hdmi_in_vopb: endpoint@0 {
								reg = <0>;
								remote-endpoint = <&vopb_out_hdmi>;
						};
						hdmi_in_vopl: endpoint@1 {
								reg = <1>;
								remote-endpoint = <&vopl_out_hdmi>;
						};
				};
		};
};

其中:

  • 子节点ports:包含2个input endpoint,分别连接到VOPLVOPB;

因此可以得到有2条连接:

  • vopb_out_hdmi ---> hdmi_in_vopb
  • vopl_out_hdmi ---> hdmi_in_vopl

希望hdmi连接在vopb上,则需要在arch/arm64/boot/dts/rockchip/rk3399-evb.dts中为以下节点新增属性:

&i2c7 {
        status = "okay";
};

&display_subsystem {
         status = "okay";
};

&vopb {
        status = "okay";
};

&vopb_mmu {
        status = "okay";
};
     
&hdmi {
        ddc-i2c-bus = <&i2c7>;
        pinctrl-names = "default";
        pinctrl-0 = <&hdmi_cec>;
        status = "okay";
};

&hdmi_in_vopb{
        status = "okay";
};

&hdmi_in_vopl{
        status = "disabled";
};
2.2.2 vopb_out_edp

vopb_out_edp通过edp_in_vopbedp显示接口组成一个连接通路;

edp: edp@ff970000 {
		compatible = "rockchip,rk3399-edp";
		reg = <0x0 0xff970000 0x0 0x8000>;
		interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH 0>;
		clocks = <&cru PCLK_EDP>, <&cru PCLK_EDP_CTRL>, <&cru PCLK_VIO_GRF>;
		clock-names = "dp", "pclk", "grf";
		pinctrl-names = "default";
		pinctrl-0 = <&edp_hpd>;
		power-domains = <&power RK3399_PD_EDP>;
		resets = <&cru SRST_P_EDP_CTRL>;
		reset-names = "dp";
		rockchip,grf = <&grf>;
		status = "disabled";

		ports {
				#address-cells = <1>;
				#size-cells = <0>;
				edp_in: port@0 {
						reg = <0>;
						#address-cells = <1>;
						#size-cells = <0>;

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

						edp_in_vopl: endpoint@1 {
								reg = <1>;
								remote-endpoint = <&vopl_out_edp>;
						};
				};
		};
};

其中:

  • 子节点ports;
    • 2个input endpoint,分别连接到VOPLVOPB;
    • 1个output endpoint连接到了epd panel上(这个下面介绍);

因此可以得到有3条连接:

  • vopb_out_edp ---> edp_in_vopb
  • vopl_out_edp ---> edp_in_vopl
  • edp_out_panel ---> panel_in_edp

如果希望edp连接在vopb上,则需要在arch/arm64/boot/dts/rockchip/rk3399-evb.dts中为以下节点新增属性:

&display_subsystem {
         status = "okay";
};

&vopb {
        status = "okay";
};

&vopb_mmu {
        status = "okay";
};
     
&edp {
        status = "okay";
        force-hpd;

        ports {
                edp_out: port@1 {
                        reg = <1>;
                        #address-cells = <1>;
                        #size-cells = <0>;

                        edp_out_panel: endpoint@0 {
                                reg = <0>;
                                remote-endpoint = <&panel_in_edp>;
                        };
                };
        };
};

&edp_in_vopl{
        status = "disabled";
};

&edp_in_vopb{
        status = "okay";
};

同时配置设备节点panel_in_edpbacklight

backlight: backlight {
		compatible = "pwm-backlight";
		brightness-levels = <
				  0   1   2   3   4   5   6   7
				  8   9  10  11  12  13  14  15
				 16  17  18  19  20  21  22  23
				 24  25  26  27  28  29  30  31
				 32  33  34  35  36  37  38  39
				 40  41  42  43  44  45  46  47
				 48  49  50  51  52  53  54  55
				 56  57  58  59  60  61  62  63
				 64  65  66  67  68  69  70  71
				 72  73  74  75  76  77  78  79
				 80  81  82  83  84  85  86  87
				 88  89  90  91  92  93  94  95
				 96  97  98  99 100 101 102 103
				104 105 106 107 108 109 110 111
				112 113 114 115 116 117 118 119
				120 121 122 123 124 125 126 127
				128 129 130 131 132 133 134 135
				136 137 138 139 140 141 142 143
				144 145 146 147 148 149 150 151
				152 153 154 155 156 157 158 159
				160 161 162 163 164 165 166 167
				168 169 170 171 172 173 174 175
				176 177 178 179 180 181 182 183
				184 185 186 187 188 189 190 191
				192 193 194 195 196 197 198 199
				200 201 202 203 204 205 206 207
				208 209 210 211 212 213 214 215
				216 217 218 219 220 221 222 223
				224 225 226 227 228 229 230 231
				232 233 234 235 236 237 238 239
				240 241 242 243 244 245 246 247
				248 249 250 251 252 253 254 255>;
		default-brightness-level = <200>;
		pwms = <&pwm0 0 25000 0>;
};

edp_panel: edp-panel {
		compatible = "lg,lp079qx1-sp0v";
		backlight = <&backlight>;
		enable-gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_HIGH>;
		power-supply = <&vcc3v3_s0>;

		port {
				panel_in_edp: endpoint {
						remote-endpoint = <&edp_out_panel>;
				};
		};
};

三、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编译到内核。

(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 vop_platform_driver

vop_platform_driver定义在drivers/gpu/drm/rockchip/rockchip_vop_reg.c

struct platform_driver vop_platform_driver = {
        .probe = vop_probe,
        .remove = vop_remove,
        .driver = {
                .name = "rockchip-vop",
                .of_match_table = vop_driver_dt_match,
        },
};
3.1.1 of_match_table

其中of_match_table用于设备树匹配,匹配设备树中compatible = "rockchip,rk3399-vop-big"或者compatible = "rockchip,rk3399-vop-lit"的设备节点;

static const struct of_device_id vop_driver_dt_match[] = {
        { .compatible = "rockchip,rk3036-vop",
          .data = &rk3036_vop },
        { .compatible = "rockchip,rk3126-vop",
          .data = &rk3126_vop },
        { .compatible = "rockchip,px30-vop-big",
          .data = &px30_vop_big },
        { .compatible = "rockchip,px30-vop-lit",
          .data = &px30_vop_lit },
        { .compatible = "rockchip,rk3066-vop",
          .data = &rk3066_vop },
        { .compatible = "rockchip,rk3188-vop",
          .data = &rk3188_vop },
        { .compatible = "rockchip,rk3288-vop",
          .data = &rk3288_vop },
        { .compatible = "rockchip,rk3368-vop",
          .data = &rk3368_vop },
        { .compatible = "rockchip,rk3366-vop",
          .data = &rk3366_vop },
        { .compatible = "rockchip,rk3399-vop-big",
          .data = &rk3399_vop_big },
        { .compatible = "rockchip,rk3399-vop-lit",
          .data = &rk3399_vop_lit },
        { .compatible = "rockchip,rk3228-vop",
          .data = &rk3228_vop },
        { .compatible = "rockchip,rk3328-vop",
          .data = &rk3328_vop },
        {},
};
3.1.2 probe

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

static int vop_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;

		// 未使用设备树 退出
        if (!dev->of_node) {
                DRM_DEV_ERROR(dev, "can't find vop devices\n");
                return -ENODEV;
        }

        return component_add(dev, &vop_component_ops);
}

这里代码很简单,就是为设备pdev->dev向系统注册一个component,其中组件可执行的初始化操作被设置为了vop_component_ops,其定义在drivers/gpu/drm/rockchip/rockchip_drm_vop.c

const struct component_ops vop_component_ops = {
        .bind = vop_bind,
        .unbind = vop_unbind,
};

我们需要重点关注bind函数的实现,这个函数内容较多我们单独小节介绍。

3.2 dw_hdmi_rockchip_pltfm_driver

由于vop支持的显示接口较多,我们不可以将lvdsdphdmimipi dsi介绍一遍,因此这里我挑选了hdmi作为分析的对象。

dw_hdmi_rockchip_pltfm_driver定义在drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c

struct platform_driver dw_hdmi_rockchip_pltfm_driver = {
        .probe  = dw_hdmi_rockchip_probe,
        .remove = dw_hdmi_rockchip_remove,
        .driver = {
                .name = "dwhdmi-rockchip",
                .pm = &dw_hdmi_rockchip_pm,
                .of_match_table = dw_hdmi_rockchip_dt_ids, // 用于设备树匹配
        },
};
3.2.1 of_match_table

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

static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
        { .compatible = "rockchip,rk3228-dw-hdmi",
          .data = &rk3228_hdmi_drv_data
        },
        { .compatible = "rockchip,rk3288-dw-hdmi",
          .data = &rk3288_hdmi_drv_data
        },
        { .compatible = "rockchip,rk3328-dw-hdmi",
          .data = &rk3328_hdmi_drv_data
        },
        { .compatible = "rockchip,rk3399-dw-hdmi",
          .data = &rk3399_hdmi_drv_data
        },
        { .compatible = "rockchip,rk3568-dw-hdmi",
          .data = &rk3568_hdmi_drv_data
        },
        {},
};
3.2.2 probe

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

static const struct component_ops dw_hdmi_rockchip_ops = {
        .bind   = dw_hdmi_rockchip_bind,
        .unbind = dw_hdmi_rockchip_unbind,
};


static int dw_hdmi_rockchip_probe(struct platform_device *pdev)
{
        return component_add(&pdev->dev, &dw_hdmi_rockchip_ops);
}

这里代码很简单,就是为设备pdev->dev向系统注册一个component,其中组件可执行的初始化操作被设置为了dw_hdmi_rockchip_ops,我们需要重点关注bind函数的实现,这个我们单独小节介绍。

3.3 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.3.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.3.2 probe

plaftrom总线设备驱动模型中,我们知道当内核中有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;

        match = rockchip_drm_match_add(dev);
        if (IS_ERR(match))
                return PTR_ERR(match);

        ret = component_master_add_with_match(dev, &rockchip_drm_ops, match);
        if (ret < 0) {
                rockchip_drm_match_remove(dev);
                return ret;
        }

        return 0;
}

这里代码很简单,首先使用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);
}

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

四、rockchip_drm_driver

接下来我们以源码rockchip_drm_drv.c中的rockchip_drm_driver全局变量作为切入点进行介绍;

static const struct drm_driver rockchip_drm_driver = {
        .driver_features        = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
        .dumb_create            = rockchip_gem_dumb_create,
        .prime_handle_to_fd     = drm_gem_prime_handle_to_fd,
        .prime_fd_to_handle     = drm_gem_prime_fd_to_handle,
        .gem_prime_import_sg_table      = rockchip_gem_prime_import_sg_table,
        .gem_prime_mmap         = drm_gem_prime_mmap,
        .fops                   = &rockchip_drm_driver_fops,
        .name   = DRIVER_NAME,
        .desc   = DRIVER_DESC,
        .date   = DRIVER_DATE,
        .major  = DRIVER_MAJOR,
        .minor  = DRIVER_MINOR,
};
4.1 driver_features
  • 添加上 DRIVER_GEM 标志位,告诉DRM Core该驱动支持GEM操作;
  • 添加上 DRIVER_MODESET 标志位,告诉DRM Core 该驱动支持kernel Mode Setting操作;
  • 添加上 DRIVER_ATOMIC 标志位,告诉 DRM Core该驱动支持Atomic操作。
4.2 宏变量
#define DRIVER_NAME     "rockchip"
#define DRIVER_DESC     "RockChip Soc DRM"
#define DRIVER_DATE     "20140818"
#define DRIVER_MAJOR    1
#define DRIVER_MINOR    0
4.3 dumb_create

其中dumb_create配置为rockchip_gem_dumb_create,该函数用于分配物理内存dumb buffer,函数定义在drivers/gpu/drm/rockchip/rockchip_drm_gem.c

/*
 * rockchip_gem_dumb_create - (struct drm_driver)->dumb_create callback
 * function
 *
 * This aligns the pitch and size arguments to the minimum required. wrap
 * this into your own function if you need bigger alignment.
 */
int rockchip_gem_dumb_create(struct drm_file *file_priv,
                             struct drm_device *dev,
                             struct drm_mode_create_dumb *args)
{
        struct rockchip_gem_object *rk_obj
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Graceful_scenery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值