文章目录
前言
项目上结构上使用DSI1接口做单屏标签,而DSI1是VP1内容的输出,我们需要将VP0内容输出,设置到DSI1上。
为什么要把VP0设置到DSI1上?下面会有一个章节进行讲解。
这一章就是讲解RK3568如何把VP0设置到DSI1上,因为主要的修改都在设备树上,所以,本文仅围绕设备树进行讲解。
同时把下图的含义搞明白,对修改也具有一定的帮助。
下图是RK的说明
下图是DRM的标准说明
VPx的输出路径
一、为什么要把VP0设置到DSI1上?
我们已知VP0的内容可以输出到DSI1或DSI0上,但是VP0的内容是从哪来的?这就涉及到VPx与图层plane的关系,在RK中会有多个图层,主要有如下
rockchip_vop.h
#define ROCKCHIP_VOP2_CLUSTER0 0
#define ROCKCHIP_VOP2_CLUSTER1 1
#define ROCKCHIP_VOP2_ESMART0 2
#define ROCKCHIP_VOP2_ESMART1 3
#define ROCKCHIP_VOP2_SMART0 4
#define ROCKCHIP_VOP2_SMART1 5
#define ROCKCHIP_VOP2_CLUSTER2 6
#define ROCKCHIP_VOP2_CLUSTER3 7
#define ROCKCHIP_VOP2_ESMART2 8
#define ROCKCHIP_VOP2_ESMART3 9
已知有CLUSTER 0/1 等,这里分主图层与镜像图层,主图层是0,镜像图层是1,可知主图层一般用于不进行热插拔的接口(DSI),镜像图像用于可热插拔的接口。我们再从以下log来看,主图层在VPx中的分配
可知VP0使用了CLUSTER0、ESMART0、SMART0,即VP0使用的都是主图层,VP1使用的都是镜像图层,因此,如果,我们想要DSI1做主屏(不热插拔),那么我们需要把DSI1连接到VP0,DSI0连接到VP1。
注意:客户的需求就是一个app,单双屏都可以使用,单屏机器的结构上DSI1为主屏(DSI0不使用)
二、设备树显示分析
2.1 display_subsystem
display_subsystem: display-subsystem {
compatible = "rockchip,display-subsystem";
...
//显示内容的输出接口,这个后面会说明
ports = <&vop_out>;
...
//路径
route {
route_dsi0: route-dsi0 {
...
//当前显示内容输出路径是vp0输出到dsi0(这部分内容可以从vop_out查看vp0所支持的显示接口)
connect = <&vp0_out_dsi0>;
};
route_dsi1: route-dsi1 {
...
//当前显示内容输出路径是vp0输出到dsi1(这部分内容可以从vop_out查看vp0所支持的显示接口)
connect = <&vp0_out_dsi1>;
};
...
};
};
由上所述,如果vp0做主屏内容输出,vp1做副屏内容输出,上述肯定是需要修改的。这里先做个标记,先不修改,先查看ports = <&vop_out>;的内容
2.2 vop_out
vop: vop@fe040000 {
compatible = "rockchip,rk3568-vop";
reg = <0x0 0xfe040000 0x0 0x3000>, <0x0 0xfe044000 0x0 0x1000>;
reg-names = "regs", "gamma_lut";
rockchip,grf = <&grf>;
interrupts = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru ACLK_VOP>, <&cru HCLK_VOP>, <&cru DCLK_VOP0>, <&cru DCLK_VOP1>, <&cru DCLK_VOP2>;
clock-names = "aclk_vop", "hclk_vop", "dclk_vp0", "dclk_vp1", "dclk_vp2";
iommus = <&vop_mmu>;
power-domains = <&power RK3568_PD_VO>;
status = "disabled";
//从这里分析
vop_out: ports {
#address-cells = <1>;
#size-cells = <0>;
//vp0,支持vp0_out_dsi0、vp0_out_dsi1、vp0_out_edp、vp0_out_hdmi
//里面的remote-endpoint dsi0_in_vp0、dsi1_in_vp0 会有一个对应的端口,下面会进行讲解
vp0: port@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
vp0_out_dsi0: endpoint@0 {
reg = <0>;
remote-endpoint = <&dsi0_in_vp0>;
};
vp0_out_dsi1: endpoint@1 {
reg = <1>;
remote-endpoint = <&dsi1_in_vp0>;
};
vp0_out_edp: endpoint@2 {
reg = <2>;
remote-endpoint = <&edp_in_vp0>;
};
vp0_out_hdmi: endpoint@3 {
reg = <3>;
remote-endpoint = <&hdmi_in_vp0>;
};
};
//vp1,支持vp1_out_dsi0、vp1_out_dsi1、vp1_out_edp、vp1_out_hdmi、vp1_out_lvds
vp1: port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
vp1_out_dsi0: endpoint@0 {
reg = <0>;
remote-endpoint = <&dsi0_in_vp1>;
};
vp1_out_dsi1: endpoint@1 {
reg = <1>;
remote-endpoint = <&dsi1_in_vp1>;
};
vp1_out_edp: endpoint@2 {
reg = <2>;
remote-endpoint = <&edp_in_vp1>;
};
vp1_out_hdmi: endpoint@3 {
reg = <3>;
remote-endpoint = <&hdmi_in_vp1>;
};
vp1_out_lvds: endpoint@4 {
reg = <4>;
remote-endpoint = <&lvds_in_vp1>;
};
};
//vp2,支持vp1_out_dsi0、vp1_out_dsi1、vp1_out_edp、vp1_out_hdmi、vp1_out_lvds
vp2: port@2 {
#address-cells = <1>;
#size-cells = <0>;
reg = <2>;
vp2_out_lvds: endpoint@0 {
reg = <0>;
remote-endpoint = <&lvds_in_vp2>;
};
vp2_out_rgb: endpoint@1 {
reg = <1>;
remote-endpoint = <&rgb_in_vp2>;
};
};
};
};
2.3 以dsi0、dsi1为例
//这个是DSI0的显示接口
dsi0: dsi@fe060000 {
...
ports {
#address-cells = <1>;
#size-cells = <0>;
//dsi0可以接收的输出有dsi0_in_vp0、dsi0_in_vp1
//其对应的remote-endpoint就是vp0_out_dsi0、vp1_out_dsi0
dsi0_in: port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
dsi0_in_vp0: endpoint@0 {
reg = <0>;
remote-endpoint = <&vp0_out_dsi0>;
status = "disabled";
};
dsi0_in_vp1: endpoint@1 {
reg = <1>;
remote-endpoint = <&vp1_out_dsi0>;
status = "disabled";
};
};
};
};
dsi1: dsi@fe070000 {
...
ports {
#address-cells = <1>;
#size-cells = <0>;
//dsi1可以接收的输出有dsi1_in_vp0、dsi1_in_vp1
//其对应的remote-endpoint就是vp0_out_dsi1、vp1_out_dsi1
dsi1_in: port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
dsi1_in_vp0: endpoint@0 {
reg = <0>;
remote-endpoint = <&vp0_out_dsi1>;
status = "disabled";
};
dsi1_in_vp1: endpoint@1 {
reg = <1>;
remote-endpoint = <&vp1_out_dsi1>;
status = "disabled";
};
};
};
};
综合1.1、1.2、1.3,结合代码中一些端口的使能与关闭
2.4 VP0->DSI0、VP1->DSI1
DSI0
//DSI0的显示路径为vp0_out_dsi0
&route_dsi0 {
status = "okay";
connect = <&vp0_out_dsi0>;
};
//如果使用上述显示路径,那么需要做如下设置
//打开dsi0_in_vp0的使能
&dsi0_in_vp0 {
status = "okay";
};
//关闭dsi0_in_vp1的使能
&dsi0_in_vp1 {
status = "disabled";
};
由上述可知,dsi0的显示路径route_dsi0中:connect指定了vp0_out_dsi0,而dsi设备树中断ports也使能了dsi0_in_vp0,关闭了dsi0_in_vp1,即dsi0_in_vp0与vp0_out_dsi0一一对应。
DSI1
//DSI1的显示路径为vp1_out_dsi1
&route_dsi1 {
status = "okay";
connect = <&vp1_out_dsi1>;
};
//如果使用上述显示路径,那么需要做如下设置
//打开dsi1_in_vp1的使能
&dsi1_in_vp1{
status = "okay";
};
//关闭dsi0_in_vp1的使能
&dsi1_in_vp0 {
status = "disabled";
};
同上。
上述内容是把VP0内容输出到DSI0,VP1内容输出到DSI1,根据前言,我们需要把VP0内容输出到DSI1,VP1内容输出到DSI0,见第二章修改
三、修改VP1->DSI0、VP0->DSI1
DSI0
//DSI0的显示路径为vp1_out_dsi0,这个定义见第一节中的vop设备树节点内的定义
&route_dsi0 {
status = "okay";
connect = <&vp1_out_dsi0>;
};
//如果使用上述显示路径,那么需要做如下设置
//关闭dsi0_in_vp0的使能
&dsi0_in_vp0 {
status = "disabled";
};
//打开dsi0_in_vp1的使能
&dsi0_in_vp1 {
status = "okay";
};
DSI1
//DSI1的显示路径为vp0_out_dsi1
&route_dsi1 {
status = "okay";
connect = <&vp0_out_dsi1>;
};
//如果使用上述显示路径,那么需要做如下设置
//关闭dsi1_in_vp1的使能
&dsi1_in_vp1{
status = "disabled";
};
//打开dsi0_in_vp1的使能
&dsi1_in_vp0 {
status = "okay";
};
四、验证
4.1 基于2.4小节的验证
基于1.4小节的修改对DSI0、DSI1进行一个热插拔测试,有如下结果
对DSI0、DSI1进行拔出
echo off > /sys/devices/platform/display-subsystem/drm/card0/card0-DSI-1/status
echo off > /sys/devices/platform/display-subsystem/drm/card0/card0-DSI-2/status
先插入DSI1,DSI1是没有显示内容的(但如果先插入DSI0,DSI0是可以显示的,无需DSI1先插入)
echo on > /sys/devices/platform/display-subsystem/drm/card0/card0-DSI-2/status
需要把DSI0也插入,DSI1才会显示内容
echo on > /sys/devices/platform/display-subsystem/drm/card0/card0-DSI-2/status
综上所述,连接VP0的输出的DSI0是可以独立显示的,无需依赖DSI1
也可以从如下指令获取这些信息
可以看到VP0 的 connector是DSI-1(实际是DSI-0),使用的图层是主图层。
可以看到VP1 的 connector是DSI-2(实际是DSI-1),使用的图层是镜像图层。
4.2 基于第二章的修改验证
基于1.4小节的修改对DSI0、DSI1进行一个热插拔测试,有如下结果
对DSI0、DSI1进行拔出
echo off > /sys/devices/platform/display-subsystem/drm/card0/card0-DSI-1/status
echo off > /sys/devices/platform/display-subsystem/drm/card0/card0-DSI-2/status
先插入DSI1,DSI1是可以显示内容的
echo on > /sys/devices/platform/display-subsystem/drm/card0/card0-DSI-2/status
需要把DSI0也插入,DSI0也会显示内容(但是先插入DSI0(DSI1没有插入),DSI是没有显示内容的)
echo on > /sys/devices/platform/display-subsystem/drm/card0/card0-DSI-2/status
综上所述,连接VP0的输出的DSI1是可以独立显示的,无需依赖DSI0
从也可以dump这些信息出来
五、使用双屏异显APP的一些信息
5.1 基于2.4小节的验证
5.2 基于第二章的修改验证
六、主副内容显示
对上述进行修改后,我们仅仅是驱动中把主图层分配给了DSI1,镜像图层分配给DSI0,但是,主显示内容是否写入到主图层中,这个是由DRM应用程序决定的,需要看上层的实现,因此,驱动改完后,还需要上层对这些进行一个修改。DRM应用程序具体可以查看《DRM 应用程序简单理解》
主副屏显示内容的修改可以修改以下属性
vendor.hwc.device.primary
vendor.hwc.device.extend
还可以直接修改代码
hardware/rockchip/hwcomposer/drmhwc2/drm/drmdevice.cpp
DrmConnector *primary = NULL;
//新增这部分内容,把DSI2给找出来(如果不清楚这个属性,可以先把这个给打印出来,在写代码)
for (auto &conn : connectors_) {
//这里把主显示设置在DSI1(物理)上(这里是DSI2)
const char* str1 = "DSI";
if (strcmp(connector_type_str(conn->type()), str1) == 0) {
ALOGE("zzh connect_type == DSI \n");
if (conn->type_id() == 2) {
ALOGE("zzh ++ connect id = %d \n", conn->type_id());
found_primary = true;
primary = conn.get();
break;
} else {
ALOGE("zzh -- connect id = %d \n", conn->type_id());
}
} else {
ALOGE("zzh connect_type != DSI \n");
}
}
//下面这部分需要注释
/*
for (auto &conn : connectors_) {
//ALOGE("zzh connect_type:%s, connect_id:%d \n", connector_type_str(conn->type()), conn->type_id());
if (!(conn->possible_displays() & HWC_DISPLAY_PRIMARY_BIT))
continue;
if (conn->internal())
continue;
if (conn->state() != DRM_MODE_CONNECTED)
continue;
found_primary = true;
if(NULL == primary){
primary = conn.get();
}else{
// High priority devices can become the primary
if(conn.get()->priority() < primary->priority()){
primary = conn.get();
}
}
}
*/
可以看到这个主显示内容与第五章的是不一样的。点击start activity,选择需要异显的应用,此时物理DSI0会显示异显应用(第五章全都是物理DSI1显示异显应用)
七、调转主副屏后,Recovery显示在副屏DSI0上
调转主副屏后,Recovery显示在副屏DSI0上,这个现象是我们不愿意看到的,
那么修改的方案就是要找到recovery的drm显示动画的代码。
bootable/recovery/minui/graphics_drm.cpp
然后connector需要选择DSI1,因此需要做如下修改
@@ -212,7 +212,7 @@ static drmModeConnector* find_used_connector_by_type(int fd, drmModeRes* resourc
drmModeConnector* connector = drmModeGetConnector(fd, resources->connectors[i]);
if (connector) {
if ((connector->connector_type == type) && (connector->connection == DRM_MODE_CONNECTED) &&
- (connector->count_modes > 0)) {
+ (connector->count_modes > 0) && connector->connector_type_id == 2) {
//主要是这个判断connector->connector_type_id == 2
//==2时,说明connector是DSI1,此时,返回connector即可
//==1时,connector应该是DS0,这个就是之前默认的
return connector;
}
drmModeFreeConnector(connector);