如果我们需要编写一个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
创建 plane
、crtc
、encoder
、connector
这4个 drm_mode_object
;
(5) 调用drm_dev_register
注册drm_device
;
一、显示子系统概述
显示子系统是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 硬件框图
整个显示系统的硬件框架如下图所示:
从上面的框图可以看到,在整个显示通路的最后端,是由RGA
,GPU
、VPU
组成的显示图形加速模块,他们是专门针对图像处理优化设计的硬件IP
,能够高效的进行图像的⽣成和进一步处理(比如GPU
通过opengl
功能提供图像渲染功能,RGA
可以对图像数据进行缩放,旋转,合成等2D
处理,VPU
可以高效的进行视频解码),从而减轻CPU
负担。
经过这些图像加速模块处理后的数据会存放在DDR
中,然后由VOP
读取,根据应用需求进行Alpha
叠加,颜色空间转换,gamma
矫正,HDR
转换 等处理后,再发送到对应的显示接口模块(HDMI
/DP
/DSI
/RGB
/LVDS
), 这些接口模块会把接收到的数据转换成符合各⾃协议的数据流,发送到显示器或者屏幕上,呈现在最终用户眼前。
目前Rockchip
平台上存在两种VOP
架构:
VOP 1.0
:VOP 1.0
是用多VOP
的方式来实现多屏幕显示,即正常情况下,一个VOP
在同一时刻只能输出一路独立的显示时序,驱动一个屏幕显示独立的内容。如果需要实现双屏显示,则需要有两个VOP
来实现,所以在RK3288
,RK3399
,PX30
等⽀持双显的平台上,都有两个独立的VOP
;VOP 2.0
:VOP 2.0
采用了统一显示架构,即整个SoC
上只存在一个VOP
,但是在VOP
的后端设计了多路独立的Video Port
(简称VP
) 输出接口,这些VP
能够同时独立⼯作,并且输出相互独立的显示时序。比如在上面的VOP 2.0
框图中,有三个VP
,就能同时实现三屏异显;
1.1.1 RK3399
在一颗SoC上,可能有多个vop
、mipi
、lvds
、hdmi
、edp
、dp
模块,根据具体产品定义,一款产品可能只需要使用其中一部分模块组成显示通路。具体使用那些模块,以及这些模块之间如何衔接通过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
驱动是由一系列相关功能模块的驱动的结合,它包含了vop
、mipi
、lvds
、hdmi
、edp
、dp
、backlight
等等显示通路上的依赖模块。只有这些相互依赖的模块都加载完整,整个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 |
显示子系统各个模块驱动加载顺序如下图所示:
这里我们对驱动加载顺序图简单说明一下:
- 在各种
Encoder driver
和CRCT driver
的probe
函数中:通过component_add
将自己注册进系统; - 在
Rockchip DRM Master driver
的probe
函数中;- 通过
rockchip_drm_match_add
为每个component
(各种Encoder
和CRCT
)注册一个component_match_array
到component_match
; - 通过
component_master_add_with_match
触发各种Encoder
和CRCT
component
的bind
操作,例如vop_bind
、dw_hdmi_rockchip_bind
等;
- 通过
bind
的含义就是将DRM
框架里的组件关联在一起,以vop_bind
为例:VOP driver
对应CRCT driver
,CRTC
负责连接Plane
和Encoder
;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
设备的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.c
、 rockchip_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
、edp
、hdmi
、mipi1
、dp
五个显示接口连接。
每个endpoint
通过remote-endpoint
属性和对应的显示接口组成一个连接通路,例如vopb_out_hdmi
---> hdmi_in_vopb
。
设备节点vopl_out
同理,这里就不在介绍了。
2.2.1 vopb_out_hdmi
vopb_out_hdmi
通过hdmi_in_vopb
和hdmi
显示接口组成一个连接通路;
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
,分别连接到VOPL
和VOPB
;
因此可以得到有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_vopb
和edp
显示接口组成一个连接通路;
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
,分别连接到VOPL
和VOPB
; - 1个
output endpoint
连接到了epd panel
上(这个下面介绍);
- 2个
因此可以得到有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_edp
、backlight
:
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
、以及显示接口(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
编译到内核。
(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 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
支持的显示接口较多,我们不可以将lvds
、dp
、hdmi
、mipi 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