编辑
2025-01-22
工作知识
0
请注意,本文编写于 135 天前,最后修改于 135 天前,其中某些信息可能已经过时。

目录

一、概念
二、代码梳理
2.1 drmoffindpanelor_bridge
2.2 component
2.3 设备树
三、逻辑总结
3.1 对于hdmi的驱动:
3.2 对于rk628_hdmirx的驱动:
3.3 对于rk628postprocess的驱动:
3.4 对于rk628_lvds驱动:
3.5 对于panel_simple驱动:
四、Openharmony的做法
五、个人建议
5.1 基于drm的显示(非hdf)
5.1.1 修改defconfig
5.1.2 修改plane图层

最近在调试openharmony的显示问题上面受阻,原因是众达的硬件上是hdmi接的rk628转接,然后再接的lvds显示器,而openharmony实现的显示panel比较简单,直接dsi panel。所以针对于此,需要将众达的显示panel相关驱动好好梳理出来

一、概念

针对众达设备的场景,我们知道其重点在于drm panel和drm bridge。这里解释一下:

  • drm:内核drm显示框架,用于抽象

  • panel:屏的意思,在drm内抽象用于表示这是一个屏幕,例如hdmi,lvds,edp等

  • bridge:桥的意思,很多总线概念(pcie)都有,桥接显示器的意思,这里hdmi转lvds接的屏,肯定有bridge的概念

对于众达rk3568而言,我这里图示如下:

image.png 对于我们而言,我们需要初始化hdmi驱动,并且把hdmi作为bridge使能,然后找到下一个bridge为rk628,然后在rk628上找到真正的panel。

二、代码梳理

2.1 drm_of_find_panel_or_bridge

对于实现此问题的场景,此函数应该重点讲解,它是内核提供的标准api,用于找到下一个panel或bridge。

函数的定义如下:

int drm_of_find_panel_or_bridge(struct device *dev, int port, int endpoint, struct drm_panel **panel, struct drm_bridge **bridge);

这里需要解释的是:

  1. 第一个参数是设备的指针
  2. 第二个参数是设备树port的编号
  3. 第三个参数是设备树endpoint的编号
  4. 第四个参数是找到的panel二级指针
  5. 第五个参数是找到的bridge二级指针

驱动通过此函数能够从设备树获得到下一个bridge或panel的节点,从而触发component_ops的bind回调

2.2 component

为了把事情说清楚,这里有必要简单介绍一下component,详细的介绍建议阅读代码学习,网上知识不多,我也是个人理解

component是组件框架,通常存在在比较大的内核框架中实现,component框架分master和component,两者没有明显区别,只是master会按照match来遍历所有组件,如果都注册了就触发bind。而组件是通过返回-517也就是-DPROBE_DEFER来延迟探测。

这里我们把显示框架"display-subsystem"作为了master,而其他的panel和bridge都以component来注册。示例如下:

ret = component_master_add_with_match(dev, &rockchip_drm_ops, match); component_add(&pdev->dev, &dw_hdmi_rockchip_ops);

2.3 设备树

对于设备树,我们针对drm_of_find_panel_or_bridge的函数,需要按照实际硬件连接的方式准备port和endpoint,我整理如下:

hdmi: port@1 { reg = <1>; hdmi_out_hdmirx: endpoint { remote-endpoint = <&hdmirx_in_hdmi>; status = "okay"; }; }; rk628_hdmirx: ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; hdmirx_in_hdmi: endpoint { remote-endpoint = <&hdmi_out_hdmirx>; }; }; port@1 { reg = <1>; hdmirx_out_post_process: endpoint { remote-endpoint = <&post_process_in_hdmirx>; }; }; }; 抽象rk628_post_process ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; post_process_in_hdmirx: endpoint { remote-endpoint = <&hdmirx_out_post_process>; }; }; port@1 { reg = <1>; post_process_out_lvds: endpoint { remote-endpoint = <&lvds_in_post_process>; }; }; }; rk628_lvds: ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; lvds_in_post_process: endpoint { remote-endpoint = <&post_process_out_lvds>; }; }; port@1 { reg = <1>; lvds_out_panel: endpoint { remote-endpoint = <&panel_in_lvds>; }; }; }; simple_panel: port { panel_in_lvds: endpoint { remote-endpoint = <&lvds_out_panel>; }; };

上面代码我梳理一下如下:

hdmi---->hdmirx_in_hdmi---->post_process_in_hdmirx---->lvds_in_post_process---->panel_in_lvds

三、逻辑总结

根据上面的代码梳理,我们可以知道了其调用流程,这里总结一下如下:

3.1 对于hdmi的驱动:

component_add(&pdev->dev, &dw_hdmi_rockchip_ops);

添加了组件,等待调用bind

dw_hdmi_bind中drm_bridge_attach到drm中,并通过of_drm_find_bridge找下一个bridge,这里下一个设备是hdmirx_in_hdmi

3.2 对于rk628_hdmirx的驱动:

drm_bridge_add添加bridge设备,attach执行回调

在attach中,调用

drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, &hdmirx→bridge)

然后drm_bridge_attach上

这里1和-1是指的从port1上遍历找endpoint,这里是post_process_in_hdmirx

3.3 对于rk628_post_process的驱动:

同样的

drm_bridge_add添加bridge设备,attach执行回调

在attach中,调用

drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, &pp→bridge)

然后drm_bridge_attach上

这里1和-1是指的从port1上遍历找endpoint,这里是lvds_in_post_process

3.4 对于rk628_lvds驱动:

这里直接在probe函数中寻找panel设备

drm_of_find_panel_or_bridge(dev->of_node, 1, -1, &lvds->panel, NULL)

然后调用drm_bridge_add添加即可。

3.5 对于panel_simple驱动:

因为这个panel_simple驱动就是drm panel的驱动,所以它负责初始化panel drm_panel_init,添加panel drm_panel_add

四、Openharmony的做法

根据Openharmony的display的驱动来看,通过ili9881_st_5p5.c驱动直接初始化了dsi的硬件,然后通过hdf_drm_panel.c来进行drm_panel_init和drm_panel_add

可以知道,Openharmony没有涉及bridge的概念,所以根据众达的显示panel流程,我们理想中的Openharmony的hdf实现应该是如下:

  1. 根据hdmi设备树实现HDMI的hdf驱动
  2. 根据drm bridge的概念来寻找hdmi下的bridge设备
  3. 根据bridge设备来初始化rk628驱动
  4. 根据rk628驱动继续寻找panel设备
  5. 实现simple_panel驱动,初始化panel设备

我们可以跳过1,2,3,4四个步骤,我们只实现步骤5,让驱动在component的bind的时候能够将drm bridge or panel路径走通。此方法可以参考文章《Openharmony-rk3568 移植panel_simple_common》

五、个人建议

根据上面的分析解剖,我们知道,如果想要完全按照hdf的思想来设计hdf驱动,我们针对rk628需要写很多个驱动才能实现hdf的基本功能。

我们再基于一个原理:

drm是显示框架的抽象,它有crtc,plane,connector,encoder,frambuffer,panel和bridge。通常情况下,我们通过libdrm下发调用,不会涉及到panel和bridge,它与crtc是割裂开的。所以我们基于drm的panel和bridge也不是一定需要用hdf实现,可以借用linux的实现方式。

而crtc及其他,根据代码追踪,hdf_disp并没有去实现drm的一整套功能。

所以hdf_disp的驱动实际上是不完整的,它只实现了panel,bridge甚至都没有实现,所以我的建议是display驱动可以暂时使用linux标准。

关于hdf_disp调用drm的参考文章《Openharmony-RK3588-drm_plane设置》

针对上面的文章《Openharmony-RK3588-drm_plane设置》分析可以知道,hdf的display并不关键,所以可以完全使用linux的实现,方法如下:

5.1 基于drm的显示(非hdf)

基于drm的显示我们需要如下操作

5.1.1 修改defconfig

对于已经编译的内核,我们修改arch/arm64/configs/rockchip_linux_defconfig文件,对于未编译的内核,我们修改kernel/linux/config/linux-5.10/arch/arm64/configs/rk3568_standard_defconfig

我们在最底下添加如下:

# CONFIG_DRIVERS_HDF_DISP is not set CONFIG_DRM_PANEL_SIMPLE=y

5.1.2 修改plane图层

对于设备树,我们参考《Openharmony RK3568图层设置》修改,添加如下:

&vp0 { rockchip,plane-mask = <(1 << ROCKCHIP_VOP2_CLUSTER0 | 1 << ROCKCHIP_VOP2_ESMART0 | 1 << ROCKCHIP_VOP2_SMART0)>; rockchip,primary-plane = <ROCKCHIP_VOP2_ESMART0>; cursor-win-id = <ROCKCHIP_VOP2_CLUSTER0>; };

至此,相关修改全部完成。编译内核即可验证,这样情况下基于openharmony的内核的众达rk3568设备已经可以正常显示了。