RK3568 设置VP0与VP1内容到对应显示接口(DSI0/DSI1)


前言

项目上结构上使用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);
### 关于POST_BUF_EMPTY中断错误的原因和解决方案 #### 错误原因分析 POST_BUF_EMPTY中断错误通常发生在视频输出处理器(VOP)未能及时获取到缓冲区数据的情况下。具体来说,在Rockchip系列芯片中,当显示器刷新帧率超过系统能够提供新图像的速度时,就会触发此错误。 对于RK356X设备上运行Debian操作系统遇到的`rockchip-vop2 fe040000.vop: [drm:vop2_isr] ERROR POST_BUF_EMPTY irq err at vp1 dsi`问题[^1],以及类似的RK3399平台上的带宽不足引发的问题[^3],根本原因在于: - **硬件资源分配不当**:如果DTSI配置文件中的虚拟输出端口(VOP)设置实际连接的显示屏不符,则可能导致性能瓶颈或者无法正常工作。 - **DDR内存带宽限制**:特别是在高分辨率或高速度传输场景下,如4K显示模式,可能会超出可用的数据传输速率,从而影响到图形渲染过程中的缓存管理机制。 #### 解决策略建议 针对上述提到的不同情况下的POST_BUF_EMPTY错误,可以采取以下措施来尝试解决问题: ##### 修改设备树源(DTS) 通过调整设备树描述符(.dts/.dtsi),确保所使用的VOP实例对应的物理接口相匹配。例如,在涉及DSI屏幕时不启用不必要的VOP通道,并且正确指定状态属性为“okay”或“disabled”。以下是具体的修改示例: ```diff &dsi1 { status = "okay"; }; &dsi1_in_vp0 { status = "okay"; }; &dsi1_in_vp1 { status = "disabled"; }; &video_phy1 { status = "okay"; }; &route_dsi1 { status = "okay"; connect = <&vp0_out_dsi1>; }; ``` ##### 调整显示参数以适应带宽需求 考虑到DDR带宽可能是造成该类错误的一个重要因素之一,应当优化系统的显示配置选项,降低对存储子系统的压力。这包括但不限于减少色彩深度、关闭某些特效功能或是适当调低分辨率/刷新频率等操作。 另外,还可以考虑升级固件版本至最新稳定版,以便利用制造商提供的改进后的驱动程序和支持特性;同时查阅官方文档和技术支持渠道获得更详细的指导说明。 #### VP系统概述 VP (Virtual Output Port) 是指负责处理并发送图像信号给外部显示器或其他视觉终端的一组逻辑单元。不同的VP可能支持不同类型的接口标准,比如MIPIDSI、LVDS、EDP等。在设计产品时需注意合理规划各个VP之间的分工协作关系,避免冲突发生。例如,若要在一个项目里同时接入MIPI屏和HDMI输出,则应让VP0负责前者而由VP1承担后者的工作任务[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值