当前位置: 首页 > news >正文

DRM驱动架构浅析-上(DRM基础概要与U-Boot阶段驱动解析)

一、背景

近期项目吃紧,接了不少调屏相关的需求,期间磕磕绊绊,但总算完成要求。回首过往,调试过多种屏幕,包括LVDS、EDP、MIPI、MI转EDP或是转LVDS、DP以及HDMI等常见屏。在Rockchip平台调外设也有段时间矣,尽管如此,对于整个DRM框架仍是一知半解,浮于表面。工作只停留于浅显的表面,长期如此,对于开发者而言是种恶性的发展趋势。

稍微花了些时间,以RK平台为主体,对整个DRM驱动架构做了系统性学习,尽管仍有大多部分没有真正理解,也于此记录,作为引子,也作为自我提升的一个鞭笞与激励。不定期更新,如有曲解的地方,还请见谅。

二、DRM架构简介

之所以主讲DRM,是因为在下从入行接触RK的芯片开始,能接触到的最早的系统是Android7.1的系统,这个时期的Android系统已经逐步应用新架构,从 Framebuffer 框架迁移到 DRM 上。根据Rockchip的文档记录中提到,从kernel4.4开始,RK的显示框架就逐渐迁移到DRM上。故而在下的调试历程中,基本就只在DRM驱动框架之下做显示开发。至于DRM和FM有啥具体的区别呢,下文也做了相关内容分析。

那么何为DRM框架?DRM的全称是Direct Rendering Manager,进行显示输出管理 、buffer分配、帧缓冲。对应的userspace 库为 libdrm,libdrm 库提供了一系列友 好的控制封装,使用户可以方便的进行显示的控制和 buffer 申请。DRM 的设备节点为 "/dev/dri/cardX", X 为 0-15 的数值,默认使用的是 /dev/dri/card0。以上则是官方的原话,按在下的理解,DRM就是Linux内核里面的一个子系统,负责管理图形硬件(比如GPU)和显示设备。在现代,DRM是Linux图形显示架构的核心部分,支持硬件加速、多个显示输出、模式设置等等高级的功能。

三、内容主体

 1、Rockchip 平台显示子系统(DSS) 概述     

这个部分我们直接上图:

VOP 1.0 显示子系统架构

VOP 2.0 显示子系统架构

显示子系统是 Rockchip 平台显示输出相关软硬件系统的统称。

这部分我们讲一讲两个显示子系统之间有什么具体的区别。如图所示,在Rockchip平台,当前存在两种VOP架构。它们之间的主要区别是对多显的支持方式不同。

a、VOP1.0

VOP 1.0 显示子系统架构,很明显可以看到其通过两个独立的VOP来支持不同的显示效果,即VOP0和VOP1,根据官方的说法,正常情况下,一个VOP在同一时刻只能输出一路独立的显示时序,驱动一个屏幕显示独立的内容。这是因为VOP内部通道通常只包含一套完整的时序控制逻辑和像素处理流水线,例如我们常配置的包括垂直同步(VSYNC)、水平同步(HSYNC)、行前肩(HFP)、场前肩(VFP)、行后肩(HBP)、场后肩(VBP)等等这些关键时序参数。举个例子,在下以往调试过RK3399 Android9.0项目的显示外设,在其VOP处理器中,分别通过VOPB和VOPL来同时输出一路完整的显示时序。因此输出的内容通常是镜像或者是扩展模式,而非是真正独立的显示内容。其VOP的定义在rk3399.dtsi中:

hdmi: hdmi@ff940000 {compatible = "rockchip,rk3399-dw-hdmi";reg = <0x0 0xff940000 0x0 0x20000>;pinctrl-names = "default";pinctrl-0 = <&hdmi_i2c_xfer>;interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH 0>;clocks = <&cru PCLK_HDMI_CTRL>,<&cru SCLK_HDMI_SFR>,<&cru PLL_VPLL>,<&cru PCLK_VIO_GRF>,<&cru SCLK_HDMI_CEC>;clock-names = "iahb", "isfr", "vpll", "grf", "cec";power-domains = <&power RK3399_PD_HDCP>;reg-io-width = <4>;rockchip,grf = <&grf>;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>;};};};};dsi: dsi@ff960000 {compatible = "rockchip,rk3399-mipi-dsi";reg = <0x0 0xff960000 0x0 0x8000>;interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH 0>;clocks = <&cru SCLK_DPHY_PLL>, <&cru PCLK_MIPI_DSI0>,<&cru SCLK_DPHY_TX0_CFG>;clock-names = "ref", "pclk", "phy_cfg";power-domains = <&power RK3399_PD_VIO>;resets = <&cru SRST_P_MIPI_DSI0>;reset-names = "apb";rockchip,grf = <&grf>;status = "disabled";#address-cells = <1>;#size-cells = <0>;ports {port {#address-cells = <1>;#size-cells = <0>;dsi_in_vopb: endpoint@0 {reg = <0>;remote-endpoint = <&vopb_out_dsi>;};dsi_in_vopl: endpoint@1 {reg = <1>;remote-endpoint = <&vopl_out_dsi>;};};};};dsi1: dsi@ff968000 {compatible = "rockchip,rk3399-mipi-dsi";reg = <0x0 0xff968000 0x0 0x8000>;interrupts = <GIC_SPI 46 IRQ_TYPE_LEVEL_HIGH 0>;clocks = <&cru SCLK_DPHY_PLL>, <&cru PCLK_MIPI_DSI1>,<&cru SCLK_DPHY_TX1RX1_CFG>;clock-names = "ref", "pclk", "phy_cfg";power-domains = <&power RK3399_PD_VIO>;resets = <&cru SRST_P_MIPI_DSI1>;reset-names = "apb";rockchip,grf = <&grf>;status = "disabled";#address-cells = <1>;#size-cells = <0>;ports {port {#address-cells = <1>;#size-cells = <0>;dsi1_in_vopb: endpoint@0 {reg = <0>;remote-endpoint = <&vopb_out_dsi1>;};dsi1_in_vopl: endpoint@1 {reg = <1>;remote-endpoint = <&vopl_out_dsi1>;};};};};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>;clock-names = "dp", "pclk";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>;};};};};

可以看到,不论是dsi转的还是EDP直出的,都是分别支持VOPB和VOPL两个通道,至于具体的通道组合,则需开发者应实际需求做选择配置。

b、VOP2.0

与前者相比,VOP 2.0 显示子系统架构则是整个SOC采用统一的一个VOP,但在VOP后端设计多路独立的video ports(VP),每个VP都能输出相互独立的显示时序。从Rockchip的官方文档上看是能够支持到三屏异显的。不过在下目前也仅调试过双屏异显的设备,较为复杂的三屏,要做到异显,可能实现过程会更繁琐一些。在VOP2.0的应用上,常见的芯片有RK3562、RK3566及RK3568系列芯片,例如在rk3568.dtsi中有VOP的定义如下:

	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: 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: 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: 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>;};};};};

从源码中可知该显示通络是以VP为主体衍射支持多类屏幕,且不同VP通道之间又有各自的差异所在,我们来看看下图演示:

结合上图到源码dtsi中我们可以知道,在显示接口的支持上,VP0和VP1支持的接口较多,VP2仅支持两种接口。其中也有一个规律,除了RGB/BT1120/BT656接口以外,几乎每个接口都有两个VP通道支输出。通道之间支持的分辨率亦有区别,VP0最高支持到4K显示,VP1最高支持2K显示,VP2最高支持1080P显示。所以平时在分配通道的时候就需要留意,看看需求要做到多高的分辨率,有多少种接口做兼容显示,这些都是调屏必须要考虑到的点。

2、DRM的结构

a、显示通路

显示通路包含很多内容,为了有个概念,这里直接上图,后续需反复回到此处做分析:

b、基本概念
基本概念说明
CRTC显示控制器,在 rockchip 平台是 SOC 内部 VOP(部分文档也称为 LCDC)模块或者 VOP2 中 Video Port 的抽象
Plane图层,在 rockchip 平台是 SOC 内部 VOP(LCDC)模块 win 图层的抽象
Encoder输出转换器,指 RGB、LVDS、DSI、eDP、DP、HDMI、CVBS、VGA 等显示接口
Connector连接器,指 encoder 和 panel 之间交互的接口部分
Bridge桥接设备,一般用于注册 encoder 后面另外再接的转换芯片,如 DSI2HDMI 转换芯片
Panel泛指屏,各种 LCD 显示设备的抽象
GEMDRM 下 buffer 管理和分配,类似 ION、DMA BUFFER

实际上再这一显示通路上在下也是有些疑惑之处,为何在前端也会有个framebuffer,在这一部分代表的又是什么东西?跟前面提过一嘴的framebuffer框架是不是同一个东西?

找了些资料,根据在下理解,从framebuffer到DRM plane之间,传递的内容包括像素数据与格式信息、内存地址与句柄以及转换和合成参数。framebuffer在这里是图形显示系统中的核心数据结构,用于存储待显示的图像数据,其本质是一块内存区域,包含像素的颜色、深度等信息,通常由应用程序或图形驱动填充。那么这个与前面说的FB框架有没有关联呢?

再次了解,FB框架是传统的显示框架,与DRM不同,FB框架相对简单,只能提供最基础的显示功能。相比之下,DRM则是更加面向现代化的显示硬件,能处理基于 3D 加速 的GPU 显卡,可以统一管理 GPU显示。

借博客Framebuffer基础知识(三十)-CSDN博客的一个图来讲讲,Framebuffer本质上是一片内存空间,而LCD中则有SRAM,CPU则通过内部的LCD控制器将Framebuffer中的数据拷贝到LCD的SRAM之中。图中也有解释,Framebuffer是逐个取出每个像素的颜色值并发送到LCD屏幕的。而讲到这里,在下发现这种数据传输的方式与上方显示通路的前端的framebuffer的工作方式差不多是相似的,这就奇怪了,难道DRM显示通路的是基于framebuffer框架来获取数据,而后对数据进行转换加工的吗?这是在下当前的一个疑惑,后续会了解清楚。

接下来简单过一下显示通路的基本概念:

CRTC,显示控制器,官方指明在Rockchip则代表为VOP1.0中的VOP0、VOP1这类通道,在VOP2.0中则可看成VP通道;

Plane,图层,VOP模块win图层的抽象,这个说法理解起来也很抽象,直白点,WIN图层(或者说窗口)在显示系统中扮演者重要角色,主要用于管理和显示图像数据。我们可以来看下方的打印出显示器的summary:

Esmart0-win0,状态为Active;

窗口ID为0;

像素格式为AB24 little-endian,即每个像素为32位;

颜色格式为SDR[0] color-encoding[BT.709] color-range[Full],细看发现与video port2格式相同;

rotate: xmirror: 0 ymirror: 0 rotate_90: 0 rotate_270: 0,这里是旋转个镜像的配置,全0的话,表示在方向上采用默认配置;

csc: y2r[0] r2y[0] csc mode[0],颜色空间配置,默认状态,不展开细讲;

zpos: 0,窗口的Z轴,不是很理解,0代表位于最底层;
src: pos[0, 0] rect[1920 x 1080],区域配置,起始坐标为(0,0),区域大小1920x1080;
dst: pos[0, 0] rect[1920 x 1080],目标区域配置,大小与上述相同;
buf[0]: addr: 0x00000000fe7eb000 pitch: 7680 offset: 0,缓冲区配置:地址,字节数,偏移量。

由上述的数据可以判断出来,在win图层部分,图像的数据被配置处理。这种图层的应用是复杂显示效果的基础,比如我们播放视频、看UI界面、做分屏等等,都会有用到Win图层来管理图像。

# cat /d/dri/0/summary
Video Port0: DISABLED
Video Port2: ACTIVEConnector:DP-1      Encoder: TMDS-405bus_format[1015]: RGB666_1X24_CPADHIoverlay_mode[0] output_mode[f] SDR[0] color-encoding[BT.709] color-range[Full]Display mode: 1920x1080p60dclk[141000 kHz] real_dclk[141000 kHz] aclk[500000 kHz] type[48] flag[a]H: 1920 2028 2076 2100V: 1080 1090 1100 1118Fixed H: 1920 2028 2076 2100Fixed V: 1080 1090 1100 1118Esmart0-win0: ACTIVEwin_id: 0format: AB24 little-endian (0x34324241) pixel_blend_mode[0] glb_alpha[0xff]color: SDR[0] color-encoding[BT.709] color-range[Full]rotate: xmirror: 0 ymirror: 0 rotate_90: 0 rotate_270: 0csc: y2r[0] r2y[0] csc mode[0]zpos: 0src: pos[0, 0] rect[1920 x 1080]dst: pos[0, 0] rect[1920 x 1080]buf[0]: addr: 0x00000000fe7eb000 pitch: 7680 offset: 0

Encoder,输出转换器,可理解为显示接口,包括涵盖EDP、LVDS、DSI、RGB、DP、HDMI等等显示接口;

Connector,连接器,在下理解为显示接口与屏之间的物理线材,不细讲;

Bridge,桥接设备,即注册在encoder显示接口之后,再另外接的转换芯片,这个东西就比较重要,比较常用。在下的调试历程中就涵盖了不少的转换芯片,例如IT6151 MIPI转EDP、IT6162 MIPI转HDMI、GM8775C MIPI转LVDS、TC3587775 mipi转lvds这类芯片。

在这一阶段,桥接芯片的主要任务是解决不同显示接口之间的协议差异,即兼容性问题。如何将不同显示设备的屏幕参数需求实现转换,则是桥接芯片的存在目的。不同的桥接芯片适配的方式各有差异,IT6151在适配上就需要利用到i2c bus 来实现屏幕参数的传输,这就需要独立的芯片驱动如it6151.c来实现参数的传输和转换;像IT6162、GM8775C以及TC358775这类芯片,在芯片出厂前厂家可能在芯片中已植入配置好的固件,或者出厂后开发者可主观能动性去修改芯片中固件的配置,那么这类芯片可绕过I2C BUS,而通过DSI驱动来直接做MIPI传参,只要参数上没有出现大的失误,大部分情况下都能够点亮屏幕。当然,不同转接芯片的上下电复位逻辑都有差异,需各自留意。除此之外,在支持I2C传参的情况下,这些带有独立固件的芯片也可以通过I2C BUS驱动的方式来转换参数。具体情况具体分析。

Panel,即指代屏幕,屏幕厂家五花八门,但调屏的原理多是千篇一律。按照在下一个前辈以往说过的一句话:在参数得到保证的条件之下,调屏无非就三大要素,电、时序和复位。搞清楚每款屏幕的规格系数,细致观察,大胆尝试,反复试错,总归要有个结果。

GEM,Graphics Execution Manager,DRM架构下负责内存的管理和释放。老实说这部分可能开发过程中直接调试GEM的几率非常低,但理解是要有的。问了下DeepSeek,说在GPU内存泄漏或者跨进程共享内存的时候,GEM的管理机制会成为问题的排查关键。

c、DRM 驱动和 libdrm 的交互过程

从上方的交互流程图来看,主要可分为三大板块:userspace、kernel space、hardware

(1)、userspace部分

可以再概括为两部分,应用程序和libdrm库,应用程序通过调用libdrm提供的API接口来访问DRM功能。我们看到上图中有hwc/gralloc、weston、xserver三个图形组件,通过资料了解:

hwc:即Hardware Composer,硬件合成器,通过libdrm与内核的DRM交互实现硬件合成,负责管理图层合成和显示输出。通过硬件加速优化图形合成,可减少GPU负载。

gralloc:图形内存分配器,通过libdrm的GBM(generic buffer management)接口创建和管理drm_bo(buffer object),提供跨平台的缓冲对象分配。

weston:即wayland compositor,即wayland合成器,一种能把图形界面里各个窗口啥的合成到一起,然后显示在屏幕上的程序。weston直接基于libdrm实现DRM后端,获取显示资源。

xserver:是X Windows System的核心组件,负责处理图形显示、输入设备和客户端应用的通信。它遵循“客户端-服务器”模型,客户端应用通过X协议与X Server交互,实现跨网络的图形显示。

应用层APP的功能实现与这几个图形组件密不可分,通过标准化接口,上层APP与底层图形组件交互,有hwc/gralloc管理资源,weston或xserver协调合成事件处理,实现图形输出的高效管道。

借博客Linux内核4.14版本——drm框架分析(1)——drm简介_linux drm-CSDN博客的一张逻辑图来展示,可以较为清晰地明晰libdrm、hw图形组件以及上层APP的关联性。

关于上述userspace部分的内容,在下理解得很浅,一是难以理解,二是对于上面的定义,在下认为对于底层的实际开发而言,开发调试接触到的几率较小,不作深入探讨,有个基本概念即可。

(2)、kernel space部分

其主体的构成实际上就是我们前面介绍过的显示通路组成。这部分需要提一嘴的为KMS的概念,kernel mode Setting,内核显示模式设置,主要的功能为显示参数和显示控制。KMS通过内核直接控制显示分辨率、刷新率、色彩深度等参数,替代了传统X Window System的用户空间管理模式。其作为DRM子系统的重要组成部分,实现了从系统启动阶段自动初始化显示硬件到运行时动态调整显示模式的全流程管理。

以上内容,又臭又长,说白了,KMS的显示参数和显示控制在DRM中的必要功能,是显示驱动必须具备的能力。故而在KMS之后衍生出crtc、plane、encoder/connector以及panel这些子模块。这些模块前面都有提及,不做赘述。

(3)、hardware部分

这部分我们可将其视为kernel space各模块的作用目标,如crtc作为显示控制器,直接作用目标为硬件通道;plane则是作用在硬件图层;encode/connector作为转换器和连接器这是与硬件接口直接关联; panel与实物屏息息相关;而DRM GEM在内存管理上直接与RAM挂钩。

3、软件驱动

a、U-Boot阶段

在RK文档中明确提出,在 U-Boot 阶段,显示驱动的主要作用是提供开机 logo 显示和充电界面的显示这两个功能。由于在下工作原因,接触的设备均为无电池类Android设备,故而关于充电界面显示这一部分并不了解。

简单提一下logo的显示,在RK平台上开机logo分两个阶段:u-boot logo 和 kernel logo。

两个logo图片默认存放于Linux kernel根目录之下,默认名称为logo.bmp 和 logo_kernel.bmp。

U-Boot 启动的时候会把这两个文件加载到内存中,U-Boot LOGO 在 U-Boot 阶段就开始显示,Kernel LOGO 在内存中的地址会被 U-Boot 传递给 Linux kernel,在 Linux Kernel 的 drm 驱动初始化阶段显示。

(1)、驱动文件

u-boot的驱动目录在 drivers/video/drm/ ,这里已RK3576 Android14 SDK为例,u-boot 显示驱动文件可罗列如下:

rk3576/u-boot/drivers/video/drm$ ls
analogix_dp.c      drm_of.h         inno_video_combo_phy.c             rk1000.c              rockchip_connector.h       rockchip_lvds.c         rockchip_vop2.c
analogix_dp.h      dw-dp.c          inno_video_phy.c                   rk1000.h              rockchip_crtc.c            rockchip_panel.c        rockchip_vop.c
analogix_dp_reg.c  dw_hdmi.c        Kconfig                            rk1000_tve.c          rockchip_crtc.h            rockchip_panel.h        rockchip_vop.h
bmp_helper.c       dw_hdmi.h        libnsbmp.c                         rk618.c               rockchip_display.c         rockchip_phy.c          rockchip_vop_reg.c
bmp_helper.h       dw_hdmi_qp.c     libnsbmp.h                         rk618_dsi.c           rockchip_display.h         rockchip_phy.h          rockchip_vop_reg.h
display-serdes     dw_hdmi_qp.h     Makefile                           rk618.h               rockchip_display_helper.c  rockchip_post_csc.c     rohm-bu18rl82.c
drm_dp_helper.c    dw_mipi_dsi2.c   max96745.c                         rk618_lvds.c          rockchip_dw_hdmi.c         rockchip_post_csc.h     rohm-bu18tl82.c
drm_dsc.c          dw_mipi_dsi.c    max96755f.c                        rk628                 rockchip_dw_hdmi.h         rockchip_rgb.c          samsung_mipi_dcphy.c
drm_mipi_dsi.c     inno_hdmi.c      panel-maxim-max96752f.c            rockchip_bridge.c     rockchip_dw_hdmi_qp.c      rockchip_spl_display.c  sii902x.c
drm_modes.c        inno_hdmi.h      panel-rohm-bu18rl82.c              rockchip_bridge.h     rockchip_dw_hdmi_qp.h      rockchip_tve.c
drm_of.c           inno_mipi_phy.c  phy-rockchip-samsung-hdptx-hdmi.c  rockchip_connector.c  rockchip-inno-hdmi-phy.c   rockchip_tve.h

在u-boot阶段,显示驱动的核心驱动部分为Core和VOP:

Core:
rockchip_display.c
rockchip_crtc.c
rockchip_connector.c
rockchip_phy.c
rockchip_panel.c
rockchip_bridge.cVOP:
rockchip_vop.c
rockchip_vop_reg.c
rockchip_vop2.c
rockchip_vop2_reg.c
(2)、接口部分
rockchip_display.c

以u-boot logo的显示功能来举例,其功能的实现通过rockchip_display.c文件中的相关接口来实现:

/** (C) Copyright 2008-2017 Fuzhou Rockchip Electronics Co., Ltd** SPDX-License-Identifier:	GPL-2.0+*/#include <asm/unaligned.h>
#include <boot_rkimg.h>
#include <config.h>
#include <common.h>
#include <errno.h>
#include <linux/libfdt.h>
#include <fdtdec.h>
#include <fdt_support.h>
#include <linux/hdmi.h>
#include <linux/list.h>
#include <linux/compat.h>
#include <linux/media-bus-format.h>
#include <malloc.h>
#include <memalign.h>
#include <video.h>
#include <video_rockchip.h>
#include <video_bridge.h>
#include <dm/device.h>
#include <dm/uclass-internal.h>
#include <asm/arch-rockchip/resource_img.h>
#include <asm/arch-rockchip/cpu.h>#include "bmp_helper.h"
#include "libnsbmp.h"
#include "rockchip_display.h"
#include "rockchip_crtc.h"
#include "rockchip_connector.h"
#include "rockchip_bridge.h"
#include "rockchip_phy.h"
#include "rockchip_panel.h"
#include <dm.h>
#include <dm/of_access.h>
#include <dm/ofnode.h>
#include <asm/io.h>#define DRIVER_VERSION	"v1.0.1"/************************************************************************  Rockchip UBOOT DRM driver version**  v1.0.0	: add basic version for rockchip drm driver(hjc)*  v1.0.1	: add much dsi update(hjc)***********************************************************************/#define RK_BLK_SIZE 512
#define BMP_PROCESSED_FLAG 8399
#define BYTES_PER_PIXEL sizeof(uint32_t)
#define MAX_IMAGE_BYTES (8 * 1024 * 1024)DECLARE_GLOBAL_DATA_PTR;
static LIST_HEAD(rockchip_display_list);
static LIST_HEAD(logo_cache_list);static unsigned long memory_start;
static unsigned long cubic_lut_memory_start;
static unsigned long memory_end;
static struct base2_info base_parameter;
static u32 align_size = PAGE_SIZE;/** the phy types are used by different connectors in public.* The current version only has inno hdmi phy for hdmi and tve.*/
enum public_use_phy {NONE,INNO_HDMI_PHY
};/* save public phy data */
struct public_phy_data {const struct rockchip_phy *phy_drv;int phy_node;int public_phy_type;bool phy_init;
};char* rockchip_get_output_if_name(u32 output_if, char *name)
{if (output_if & VOP_OUTPUT_IF_RGB)strcat(name, " RGB");if (output_if & VOP_OUTPUT_IF_BT1120)strcat(name, " BT1120");if (output_if & VOP_OUTPUT_IF_BT656)strcat(name, " BT656");if (output_if & VOP_OUTPUT_IF_LVDS0)strcat(name, " LVDS0");if (output_if & VOP_OUTPUT_IF_LVDS1)strcat(name, " LVDS1");if (output_if & VOP_OUTPUT_IF_MIPI0)strcat(name, " MIPI0");if (output_if & VOP_OUTPUT_IF_MIPI1)strcat(name, " MIPI1");if (output_if & VOP_OUTPUT_IF_eDP0)strcat(name, " eDP0");if (output_if & VOP_OUTPUT_IF_eDP1)strcat(name, " eDP1");if (output_if & VOP_OUTPUT_IF_DP0)strcat(name, " DP0");if (output_if & VOP_OUTPUT_IF_DP1)strcat(name, " DP1");if (output_if & VOP_OUTPUT_IF_HDMI0)strcat(name, " HDMI0");if (output_if & VOP_OUTPUT_IF_HDMI1)strcat(name, " HDMI1");return name;
}u32 rockchip_drm_get_cycles_per_pixel(u32 bus_format)
{switch (bus_format) {case MEDIA_BUS_FMT_RGB565_1X16:case MEDIA_BUS_FMT_RGB666_1X18:case MEDIA_BUS_FMT_RGB888_1X24:case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:return 1;case MEDIA_BUS_FMT_RGB565_2X8_LE:case MEDIA_BUS_FMT_BGR565_2X8_LE:return 2;case MEDIA_BUS_FMT_RGB666_3X6:case MEDIA_BUS_FMT_RGB888_3X8:case MEDIA_BUS_FMT_BGR888_3X8:return 3;case MEDIA_BUS_FMT_RGB888_DUMMY_4X8:case MEDIA_BUS_FMT_BGR888_DUMMY_4X8:return 4;default:return 1;}
}int rockchip_get_baseparameter(void)
{struct blk_desc *dev_desc;disk_partition_t part_info;int block_num;char *baseparameter_buf;int ret = 0;dev_desc = rockchip_get_bootdev();if (!dev_desc) {printf("%s: Could not find device\n", __func__);return -ENOENT;}if (part_get_info_by_name(dev_desc, "baseparameter", &part_info) < 0) {printf("Could not find baseparameter partition\n");return -ENOENT;}block_num = BLOCK_CNT(sizeof(base_parameter), dev_desc);baseparameter_buf = memalign(ARCH_DMA_MINALIGN, block_num * dev_desc->blksz);if (!baseparameter_buf) {printf("failed to alloc memory for baseparameter buffer\n");return -ENOMEM;}ret = blk_dread(dev_desc, part_info.start, block_num, (void *)baseparameter_buf);if (ret < 0) {printf("read baseparameter failed\n");goto out;}memcpy(&base_parameter, baseparameter_buf, sizeof(base_parameter));if (strncasecmp(base_parameter.head_flag, "BASP", 4)) {printf("warning: bad baseparameter\n");memset(&base_parameter, 0, sizeof(base_parameter));}rockchip_display_make_crc32_table();out:free(baseparameter_buf);return ret;
}struct base2_disp_info *rockchip_get_disp_info(int type, int id)
{struct base2_disp_info *disp_info;struct base2_disp_header *disp_header;int i = 0, offset = -1;u32 crc_val;u32 base2_length;void *base_parameter_addr = (void *)&base_parameter;for (i = 0; i < 8; i++) {disp_header = &base_parameter.disp_header[i];if (disp_header->connector_type == type &&disp_header->connector_id == id) {printf("disp info %d, type:%d, id:%d\n", i, type, id);offset = disp_header->offset;break;}}if (offset < 0)return NULL;disp_info = base_parameter_addr + offset;if (disp_info->screen_info[0].type != type ||disp_info->screen_info[0].id != id) {printf("base2_disp_info couldn't be found, screen_info type[%d] or id[%d] mismatched\n",disp_info->screen_info[0].type,disp_info->screen_info[0].id);return NULL;}if (strncasecmp(disp_info->disp_head_flag, "DISP", 4))return NULL;if (base_parameter.major_version == 3 && base_parameter.minor_version == 0) {crc_val = rockchip_display_crc32c_cal((unsigned char *)disp_info,sizeof(struct base2_disp_info) - 4);if (crc_val != disp_info->crc2) {printf("error: connector type[%d], id[%d] disp info crc2 check error\n",type, id);return NULL;}} else {base2_length = sizeof(struct base2_disp_info) - sizeof(struct csc_info) -sizeof(struct acm_data) - 10 * 1024 - 4;crc_val = rockchip_display_crc32c_cal((unsigned char *)disp_info, base2_length - 4);if (crc_val != disp_info->crc) {printf("error: connector type[%d], id[%d] disp info crc check error\n",type, id);return NULL;}}return disp_info;
}/* check which kind of public phy does connector use */
static int check_public_use_phy(struct rockchip_connector *conn)
{int ret = NONE;
#ifdef CONFIG_ROCKCHIP_INNO_HDMI_PHYif (!strncmp(dev_read_name(conn->dev), "tve", 3) ||!strncmp(dev_read_name(conn->dev), "hdmi", 4))ret = INNO_HDMI_PHY;
#endifreturn ret;
}/** get public phy driver and initialize it.* The current version only has inno hdmi phy for hdmi and tve.*/
static int get_public_phy(struct rockchip_connector *conn,struct public_phy_data *data)
{struct rockchip_phy *phy;struct udevice *dev;int ret = 0;switch (data->public_phy_type) {case INNO_HDMI_PHY:
#if defined(CONFIG_ROCKCHIP_RK3328)ret = uclass_get_device_by_name(UCLASS_PHY,"hdmiphy@ff430000", &dev);
#elif defined(CONFIG_ROCKCHIP_RK322X)ret = uclass_get_device_by_name(UCLASS_PHY,"hdmi-phy@12030000", &dev);
#elseret = -EINVAL;
#endifif (ret) {printf("Warn: can't find phy driver\n");return 0;}phy = (struct rockchip_phy *)dev_get_driver_data(dev);if (!phy) {printf("failed to get phy driver\n");return 0;}ret = rockchip_phy_init(phy);if (ret) {printf("failed to init phy driver\n");return ret;}conn->phy = phy;debug("inno hdmi phy init success, save it\n");data->phy_drv = conn->phy;data->phy_init = true;return 0;default:return -EINVAL;}
}static void init_display_buffer(ulong base)
{memory_start = ALIGN(base + DRM_ROCKCHIP_FB_SIZE, align_size);memory_end = memory_start;cubic_lut_memory_start = ALIGN(memory_start + MEMORY_POOL_SIZE, align_size);
}void *get_display_buffer(int size)
{unsigned long roundup_memory = roundup(memory_end, PAGE_SIZE);void *buf;if (roundup_memory + size > memory_start + MEMORY_POOL_SIZE) {printf("failed to alloc %dbyte memory to display\n", size);return NULL;}buf = (void *)roundup_memory;memory_end = roundup_memory + size;return buf;
}static unsigned long get_display_size(void)
{return memory_end - memory_start;
}static unsigned long get_single_cubic_lut_size(void)
{ulong cubic_lut_size;int cubic_lut_step = CONFIG_ROCKCHIP_CUBIC_LUT_SIZE;/* This is depend on IC designed */cubic_lut_size = (cubic_lut_step * cubic_lut_step * cubic_lut_step + 1) / 2 * 16;cubic_lut_size = roundup(cubic_lut_size, PAGE_SIZE);return cubic_lut_size;
}static unsigned long get_cubic_lut_offset(int crtc_id)
{return crtc_id * get_single_cubic_lut_size();
}unsigned long get_cubic_lut_buffer(int crtc_id)
{return cubic_lut_memory_start + crtc_id * get_single_cubic_lut_size();
}static unsigned long get_cubic_memory_size(void)
{/* Max support 4 cubic lut */return get_single_cubic_lut_size() * 4;
}bool can_direct_logo(int bpp)
{return bpp == 16 || bpp == 32;
}static int connector_phy_init(struct rockchip_connector *conn,struct public_phy_data *data)
{int type;/* does this connector use public phy with others */type = check_public_use_phy(conn);if (type == INNO_HDMI_PHY) {/* there is no public phy was initialized */if (!data->phy_init) {debug("start get public phy\n");data->public_phy_type = type;if (get_public_phy(conn, data)) {printf("can't find correct public phy type\n");free(data);return -EINVAL;}return 0;}/* if this phy has been initialized, get it directly */conn->phy = (struct rockchip_phy *)data->phy_drv;return 0;}return 0;
}int rockchip_ofnode_get_display_mode(ofnode node, struct drm_display_mode *mode, u32 *bus_flags)
{int hactive, vactive, pixelclock;int hfront_porch, hback_porch, hsync_len;int vfront_porch, vback_porch, vsync_len;int val, flags = 0;#define FDT_GET_BOOL(val, name) \val = ofnode_read_bool(node, name);#define FDT_GET_INT(val, name) \val = ofnode_read_s32_default(node, name, -1); \if (val < 0) { \printf("Can't get %s\n", name); \return -ENXIO; \}#define FDT_GET_INT_DEFAULT(val, name, default) \val = ofnode_read_s32_default(node, name, default);FDT_GET_INT(hactive, "hactive");FDT_GET_INT(vactive, "vactive");FDT_GET_INT(pixelclock, "clock-frequency");FDT_GET_INT(hsync_len, "hsync-len");FDT_GET_INT(hfront_porch, "hfront-porch");FDT_GET_INT(hback_porch, "hback-porch");FDT_GET_INT(vsync_len, "vsync-len");FDT_GET_INT(vfront_porch, "vfront-porch");FDT_GET_INT(vback_porch, "vback-porch");FDT_GET_INT(val, "hsync-active");flags |= val ? DRM_MODE_FLAG_PHSYNC : DRM_MODE_FLAG_NHSYNC;FDT_GET_INT(val, "vsync-active");flags |= val ? DRM_MODE_FLAG_PVSYNC : DRM_MODE_FLAG_NVSYNC;FDT_GET_BOOL(val, "interlaced");flags |= val ? DRM_MODE_FLAG_INTERLACE : 0;FDT_GET_BOOL(val, "doublescan");flags |= val ? DRM_MODE_FLAG_DBLSCAN : 0;FDT_GET_BOOL(val, "doubleclk");flags |= val ? DRM_MODE_FLAG_DBLCLK : 0;FDT_GET_INT(val, "de-active");*bus_flags |= val ? DRM_BUS_FLAG_DE_HIGH : DRM_BUS_FLAG_DE_LOW;FDT_GET_INT(val, "pixelclk-active");*bus_flags |= val ? DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE : DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;FDT_GET_INT_DEFAULT(val, "screen-rotate", 0);if (val == DRM_MODE_FLAG_XMIRROR) {flags |= DRM_MODE_FLAG_XMIRROR;} else if (val == DRM_MODE_FLAG_YMIRROR) {flags |= DRM_MODE_FLAG_YMIRROR;} else if (val == DRM_MODE_FLAG_XYMIRROR) {flags |= DRM_MODE_FLAG_XMIRROR;flags |= DRM_MODE_FLAG_YMIRROR;}mode->hdisplay = hactive;mode->hsync_start = mode->hdisplay + hfront_porch;mode->hsync_end = mode->hsync_start + hsync_len;mode->htotal = mode->hsync_end + hback_porch;mode->vdisplay = vactive;mode->vsync_start = mode->vdisplay + vfront_porch;mode->vsync_end = mode->vsync_start + vsync_len;mode->vtotal = mode->vsync_end + vback_porch;mode->clock = pixelclock / 1000;mode->flags = flags;mode->vrefresh = drm_mode_vrefresh(mode);return 0;
}static int display_get_force_timing_from_dts(ofnode node,struct drm_display_mode *mode,u32 *bus_flags)
{int ret = 0;ret = rockchip_ofnode_get_display_mode(node, mode, bus_flags);if (ret) {mode->clock = 74250;mode->flags = 0x5;mode->hdisplay = 1280;mode->hsync_start = 1390;mode->hsync_end = 1430;mode->htotal = 1650;mode->hskew = 0;mode->vdisplay = 720;mode->vsync_start = 725;mode->vsync_end = 730;mode->vtotal = 750;mode->vrefresh = 60;mode->picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9;mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;}printf("route node %s force_timing, use %dx%dp%d as default mode\n",ret ? "undefine" : "define", mode->hdisplay, mode->vdisplay,mode->vscan);return 0;
}static int display_get_timing_from_dts(struct rockchip_panel *panel,struct drm_display_mode *mode,u32 *bus_flags)
{struct ofnode_phandle_args args;ofnode dt, timing, mcu_panel;int ret;mcu_panel = dev_read_subnode(panel->dev, "mcu-panel");dt = dev_read_subnode(panel->dev, "display-timings");if (ofnode_valid(dt)) {ret = ofnode_parse_phandle_with_args(dt, "native-mode", NULL,0, 0, &args);if (ret)return ret;timing = args.node;} else if (ofnode_valid(mcu_panel)) {dt = ofnode_find_subnode(mcu_panel, "display-timings");ret = ofnode_parse_phandle_with_args(dt, "native-mode", NULL,0, 0, &args);if (ret)return ret;timing = args.node;} else {timing = dev_read_subnode(panel->dev, "panel-timing");}if (!ofnode_valid(timing)) {printf("failed to get display timings from DT\n");return -ENXIO;}rockchip_ofnode_get_display_mode(timing, mode, bus_flags);return 0;
}static int display_get_timing(struct display_state *state)
{struct connector_state *conn_state = &state->conn_state;struct drm_display_mode *mode = &conn_state->mode;const struct drm_display_mode *m;struct rockchip_panel *panel = conn_state->connector->panel;if (panel->funcs->get_mode)return panel->funcs->get_mode(panel, mode);if (dev_of_valid(panel->dev) &&!display_get_timing_from_dts(panel, mode, &conn_state->bus_flags)) {printf("Using display timing dts\n");return 0;}if (panel->data) {m = (const struct drm_display_mode *)panel->data;memcpy(mode, m, sizeof(*m));printf("Using display timing from compatible panel driver\n");return 0;}return -ENODEV;
}static int display_pre_init(void)
{struct display_state *state;int ret = 0;list_for_each_entry(state, &rockchip_display_list, head) {struct connector_state *conn_state = &state->conn_state;struct crtc_state *crtc_state = &state->crtc_state;struct rockchip_crtc *crtc = crtc_state->crtc;ret = rockchip_connector_pre_init(state);if (ret)printf("pre init conn error\n");crtc->vps[crtc_state->crtc_id].output_type = conn_state->type;}return ret;
}static int display_use_force_mode(struct display_state *state)
{struct connector_state *conn_state = &state->conn_state;struct drm_display_mode *mode = &conn_state->mode;conn_state->bpc = 8;memcpy(mode, &state->force_mode, sizeof(struct drm_display_mode));conn_state->bus_format = state->force_bus_format;return 0;
}static int display_get_edid_mode(struct display_state *state)
{int ret = 0;struct connector_state *conn_state = &state->conn_state;struct drm_display_mode *mode = &conn_state->mode;int bpc;ret = edid_get_drm_mode(conn_state->edid, sizeof(conn_state->edid), mode, &bpc);if (!ret) {conn_state->bpc = bpc;edid_print_info((void *)&conn_state->edid);} else {conn_state->bpc = 8;mode->clock = 74250;mode->flags = 0x5;mode->hdisplay = 1280;mode->hsync_start = 1390;mode->hsync_end = 1430;mode->htotal = 1650;mode->hskew = 0;mode->vdisplay = 720;mode->vsync_start = 725;mode->vsync_end = 730;mode->vtotal = 750;mode->vrefresh = 60;mode->picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9;mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;printf("error: %s get mode from edid failed, use 720p60 as default mode\n",state->conn_state.connector->dev->name);}return ret;
}static int display_mode_valid(struct display_state *state)
{struct connector_state *conn_state = &state->conn_state;struct rockchip_connector *conn = conn_state->connector;const struct rockchip_connector_funcs *conn_funcs = conn->funcs;struct crtc_state *crtc_state = &state->crtc_state;const struct rockchip_crtc *crtc = crtc_state->crtc;const struct rockchip_crtc_funcs *crtc_funcs = crtc->funcs;int ret;if (conn_funcs->mode_valid && state->enabled_at_spl == false) {ret = conn_funcs->mode_valid(conn, state);if (ret)return ret;}if (crtc_funcs->mode_valid) {ret = crtc_funcs->mode_valid(state);if (ret)return ret;}return 0;
}static int display_mode_fixup(struct display_state *state)
{struct crtc_state *crtc_state = &state->crtc_state;const struct rockchip_crtc *crtc = crtc_state->crtc;const struct rockchip_crtc_funcs *crtc_funcs = crtc->funcs;int ret;if (crtc_funcs->mode_fixup) {ret = crtc_funcs->mode_fixup(state);if (ret)return ret;}return 0;
}static int display_init(struct display_state *state)
{struct connector_state *conn_state = &state->conn_state;struct rockchip_connector *conn = conn_state->connector;struct crtc_state *crtc_state = &state->crtc_state;struct rockchip_crtc *crtc = crtc_state->crtc;const struct rockchip_crtc_funcs *crtc_funcs = crtc->funcs;struct drm_display_mode *mode = &conn_state->mode;const char *compatible;int ret = 0;static bool __print_once = false;
#ifdef CONFIG_SPL_BUILDstruct spl_display_info *spl_disp_info = (struct spl_display_info *)CONFIG_SPL_VIDEO_BUF;
#endifif (!__print_once) {__print_once = true;printf("Rockchip UBOOT DRM driver version: %s\n", DRIVER_VERSION);}if (state->is_init)return 0;if (!crtc_funcs) {printf("failed to find crtc functions\n");return -ENXIO;}#ifdef CONFIG_SPL_BUILDif (state->conn_state.type == DRM_MODE_CONNECTOR_HDMIA)state->enabled_at_spl = spl_disp_info->enabled == 1 ? true : false;if (state->enabled_at_spl)printf("HDMI enabled at SPL\n");
#endifif (crtc_state->crtc->active && !crtc_state->ports_node &&memcmp(&crtc_state->crtc->active_mode, &conn_state->mode,sizeof(struct drm_display_mode))) {printf("%s has been used for output type: %d, mode: %dx%dp%d\n",crtc_state->dev->name,crtc_state->crtc->active_mode.type,crtc_state->crtc->active_mode.hdisplay,crtc_state->crtc->active_mode.vdisplay,crtc_state->crtc->active_mode.vrefresh);return -ENODEV;}if (crtc_funcs->preinit) {ret = crtc_funcs->preinit(state);if (ret)return ret;}if (state->enabled_at_spl == false) {ret = rockchip_connector_init(state);if (ret)goto deinit;}/** support hotplug, but not connect;*/
#ifdef CONFIG_DRM_ROCKCHIP_TVEif (crtc->hdmi_hpd && conn_state->type == DRM_MODE_CONNECTOR_TV) {printf("hdmi plugin ,skip tve\n");goto deinit;}
#elif defined(CONFIG_DRM_ROCKCHIP_RK1000)if (crtc->hdmi_hpd && conn_state->type == DRM_MODE_CONNECTOR_LVDS) {printf("hdmi plugin ,skip tve\n");goto deinit;}
#endifret = rockchip_connector_detect(state);
#if defined(CONFIG_DRM_ROCKCHIP_TVE) || defined(CONFIG_DRM_ROCKCHIP_RK1000)if (conn_state->type == DRM_MODE_CONNECTOR_HDMIA)crtc->hdmi_hpd = ret;if (state->enabled_at_spl)crtc->hdmi_hpd = true;
#endifif (!ret && !state->force_output)goto deinit;ret = 0;if (state->enabled_at_spl == true) {
#ifdef CONFIG_SPL_BUILDstruct drm_display_mode *mode = &conn_state->mode;memcpy(mode, &spl_disp_info->mode,  sizeof(*mode));conn_state->bus_format = spl_disp_info->bus_format;printf("%s get display mode from spl:%dx%d, bus format:0x%x\n",conn->dev->name, mode->hdisplay, mode->vdisplay, conn_state->bus_format);
#endif} else if (conn->panel) {ret = display_get_timing(state);if (!ret)conn_state->bpc = conn->panel->bpc;
#if defined(CONFIG_I2C_EDID)if (ret < 0 && conn->funcs->get_edid) {rockchip_panel_prepare(conn->panel);ret = conn->funcs->get_edid(conn, state);if (!ret)display_get_edid_mode(state);}
#endif} else if (conn->bridge) {ret = video_bridge_read_edid(conn->bridge->dev,conn_state->edid, EDID_SIZE);if (ret > 0) {
#if defined(CONFIG_I2C_EDID)ret = display_get_edid_mode(state);
#endif} else {ret = video_bridge_get_timing(conn->bridge->dev);}} else if (conn->funcs->get_timing) {ret = conn->funcs->get_timing(conn, state);} else if (conn->funcs->get_edid) {ret = conn->funcs->get_edid(conn, state);
#if defined(CONFIG_I2C_EDID)if (!ret)display_get_edid_mode(state);
#endif}if (!ret && conn_state->secondary) {struct rockchip_connector *connector = conn_state->secondary;if (connector->panel) {if (connector->panel->funcs->get_mode) {struct drm_display_mode *_mode = drm_mode_create();ret = connector->panel->funcs->get_mode(connector->panel, _mode);if (!ret && !drm_mode_equal(_mode, mode))ret = -EINVAL;drm_mode_destroy(_mode);}}}if (ret && !state->force_output)goto deinit;if (state->force_output)display_use_force_mode(state);if (display_mode_valid(state))goto deinit;/* rk356x series drive mipi pixdata on posedge */compatible = dev_read_string(conn->dev, "compatible");if (compatible && !strcmp(compatible, "rockchip,rk3568-mipi-dsi")) {conn_state->bus_flags &= ~DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;conn_state->bus_flags |= DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE;}if (display_mode_fixup(state))goto deinit;if (conn->bridge)rockchip_bridge_mode_set(conn->bridge, &conn_state->mode);printf("%s: %s detailed mode clock %u kHz, flags[%x]\n""    H: %04d %04d %04d %04d\n""    V: %04d %04d %04d %04d\n""bus_format: %x\n",conn->dev->name,state->force_output ? "use force output" : "",mode->clock, mode->flags,mode->hdisplay, mode->hsync_start,mode->hsync_end, mode->htotal,mode->vdisplay, mode->vsync_start,mode->vsync_end, mode->vtotal,conn_state->bus_format);if (crtc_funcs->init && state->enabled_at_spl == false) {ret = crtc_funcs->init(state);if (ret)goto deinit;}state->is_init = 1;crtc_state->crtc->active = true;memcpy(&crtc_state->crtc->active_mode,&conn_state->mode, sizeof(struct drm_display_mode));return 0;deinit:rockchip_connector_deinit(state);return ret;
}int display_send_mcu_cmd(struct display_state *state, u32 type, u32 val)
{struct crtc_state *crtc_state = &state->crtc_state;const struct rockchip_crtc *crtc = crtc_state->crtc;const struct rockchip_crtc_funcs *crtc_funcs = crtc->funcs;int ret;if (!state->is_init)return -EINVAL;if (crtc_funcs->send_mcu_cmd) {ret = crtc_funcs->send_mcu_cmd(state, type, val);if (ret)return ret;}return 0;
}static int display_set_plane(struct display_state *state)
{struct crtc_state *crtc_state = &state->crtc_state;const struct rockchip_crtc *crtc = crtc_state->crtc;const struct rockchip_crtc_funcs *crtc_funcs = crtc->funcs;int ret;if (!state->is_init)return -EINVAL;if (crtc_funcs->set_plane) {ret = crtc_funcs->set_plane(state);if (ret)return ret;}return 0;
}static int display_enable(struct display_state *state)
{struct crtc_state *crtc_state = &state->crtc_state;const struct rockchip_crtc *crtc = crtc_state->crtc;const struct rockchip_crtc_funcs *crtc_funcs = crtc->funcs;if (!state->is_init)return -EINVAL;if (state->is_enable)return 0;if (crtc_funcs->prepare)crtc_funcs->prepare(state);if (state->enabled_at_spl == false)rockchip_connector_pre_enable(state);if (crtc_funcs->enable)crtc_funcs->enable(state);if (state->enabled_at_spl == false)rockchip_connector_enable(state);#ifdef CONFIG_DRM_ROCKCHIP_RK628/** trigger .probe helper of U_BOOT_DRIVER(rk628) in ./rk628/rk628.c*/struct udevice * dev;int phandle, ret;phandle = ofnode_read_u32_default(state->node, "bridge", -1);if (phandle < 0)printf("%s failed to find bridge phandle\n", ofnode_get_name(state->node));ret = uclass_get_device_by_phandle_id(UCLASS_I2C_GENERIC, phandle, &dev);if (ret && ret != -ENOENT)printf("%s:%d failed to get rk628 device ret:%d\n", __func__, __LINE__, ret);#endifif (crtc_state->soft_te)crtc_funcs->apply_soft_te(state);state->is_enable = true;return 0;
}static int display_disable(struct display_state *state)
{struct crtc_state *crtc_state = &state->crtc_state;const struct rockchip_crtc *crtc = crtc_state->crtc;const struct rockchip_crtc_funcs *crtc_funcs = crtc->funcs;if (!state->is_init)return 0;if (!state->is_enable)return 0;rockchip_connector_disable(state);if (crtc_funcs->disable)crtc_funcs->disable(state);rockchip_connector_post_disable(state);state->is_enable = 0;state->is_init = 0;return 0;
}static int display_check(struct display_state *state)
{struct connector_state *conn_state = &state->conn_state;struct rockchip_connector *conn = conn_state->connector;const struct rockchip_connector_funcs *conn_funcs = conn->funcs;struct crtc_state *crtc_state = &state->crtc_state;const struct rockchip_crtc *crtc = crtc_state->crtc;const struct rockchip_crtc_funcs *crtc_funcs = crtc->funcs;int ret;if (!state->is_init)return 0;if (conn_funcs->check) {ret = conn_funcs->check(conn, state);if (ret)goto check_fail;}if (crtc_funcs->check) {ret = crtc_funcs->check(state);if (ret)goto check_fail;}if (crtc_funcs->plane_check) {ret = crtc_funcs->plane_check(state);if (ret)goto check_fail;}return 0;check_fail:state->is_init = false;return ret;
}static int display_logo(struct display_state *state)
{struct crtc_state *crtc_state = &state->crtc_state;struct connector_state *conn_state = &state->conn_state;struct logo_info *logo = &state->logo;int hdisplay, vdisplay, ret;ret = display_init(state);if (!state->is_init || ret)return -ENODEV;switch (logo->bpp) {case 16:crtc_state->format = ROCKCHIP_FMT_RGB565;break;case 24:crtc_state->format = ROCKCHIP_FMT_RGB888;break;case 32:crtc_state->format = ROCKCHIP_FMT_ARGB8888;break;default:printf("can't support bmp bits[%d]\n", logo->bpp);return -EINVAL;}hdisplay = conn_state->mode.crtc_hdisplay;vdisplay = conn_state->mode.vdisplay;crtc_state->src_rect.w = logo->width;crtc_state->src_rect.h = logo->height;crtc_state->src_rect.x = 0;crtc_state->src_rect.y = 0;crtc_state->ymirror = logo->ymirror;crtc_state->rb_swap = 0;crtc_state->dma_addr = (u32)(unsigned long)logo->mem + logo->offset;crtc_state->xvir = ALIGN(crtc_state->src_rect.w * logo->bpp, 32) >> 5;if (state->logo_mode == ROCKCHIP_DISPLAY_FULLSCREEN) {crtc_state->crtc_rect.x = 0;crtc_state->crtc_rect.y = 0;crtc_state->crtc_rect.w = hdisplay;crtc_state->crtc_rect.h = vdisplay;} else {if (crtc_state->src_rect.w >= hdisplay) {crtc_state->crtc_rect.x = 0;crtc_state->crtc_rect.w = hdisplay;} else {crtc_state->crtc_rect.x = (hdisplay - crtc_state->src_rect.w) / 2;crtc_state->crtc_rect.w = crtc_state->src_rect.w;}if (crtc_state->src_rect.h >= vdisplay) {crtc_state->crtc_rect.y = 0;crtc_state->crtc_rect.h = vdisplay;} else {crtc_state->crtc_rect.y = (vdisplay - crtc_state->src_rect.h) / 2;crtc_state->crtc_rect.h = crtc_state->src_rect.h;}}display_check(state);ret = display_set_plane(state);if (ret)return ret;display_enable(state);return 0;
}static int get_crtc_id(ofnode connect, bool is_ports_node)
{struct device_node *port_node;struct device_node *remote;int phandle;int val;if (is_ports_node) {port_node = of_get_parent(connect.np);if (!port_node)goto err;val = ofnode_read_u32_default(np_to_ofnode(port_node), "reg", -1);if (val < 0)goto err;} else {phandle = ofnode_read_u32_default(connect, "remote-endpoint", -1);if (phandle < 0)goto err;remote = of_find_node_by_phandle(phandle);if (!remote)goto err;val = ofnode_read_u32_default(np_to_ofnode(remote), "reg", -1);if (val < 0)goto err;}return val;
err:printf("Can't get crtc id, default set to id = 0\n");return 0;
}static int get_crtc_mcu_mode(struct crtc_state *crtc_state, struct device_node *port_node,bool is_ports_node)
{ofnode mcu_node, vp_node;int total_pixel, cs_pst, cs_pend, rw_pst, rw_pend;if (is_ports_node) {vp_node = np_to_ofnode(port_node);mcu_node = ofnode_find_subnode(vp_node, "mcu-timing");if (!ofnode_valid(mcu_node))return -ENODEV;} else {mcu_node = dev_read_subnode(crtc_state->dev, "mcu-timing");if (!ofnode_valid(mcu_node))return -ENODEV;}#define FDT_GET_MCU_INT(val, name) \do { \val = ofnode_read_s32_default(mcu_node, name, -1); \if (val < 0) { \printf("Can't get %s\n", name); \return -ENXIO; \} \} while (0)FDT_GET_MCU_INT(total_pixel, "mcu-pix-total");FDT_GET_MCU_INT(cs_pst, "mcu-cs-pst");FDT_GET_MCU_INT(cs_pend, "mcu-cs-pend");FDT_GET_MCU_INT(rw_pst, "mcu-rw-pst");FDT_GET_MCU_INT(rw_pend, "mcu-rw-pend");crtc_state->mcu_timing.mcu_pix_total = total_pixel;crtc_state->mcu_timing.mcu_cs_pst = cs_pst;crtc_state->mcu_timing.mcu_cs_pend = cs_pend;crtc_state->mcu_timing.mcu_rw_pst = rw_pst;crtc_state->mcu_timing.mcu_rw_pend = rw_pend;return 0;
}struct rockchip_logo_cache *find_or_alloc_logo_cache(const char *bmp, int rotate)
{struct rockchip_logo_cache *tmp, *logo_cache = NULL;list_for_each_entry(tmp, &logo_cache_list, head) {if ((!strcmp(tmp->name, bmp) && rotate == tmp->logo_rotate) ||(soc_is_rk3566() && tmp->logo_rotate)) {logo_cache = tmp;break;}}if (!logo_cache) {logo_cache = malloc(sizeof(*logo_cache));if (!logo_cache) {printf("failed to alloc memory for logo cache\n");return NULL;}memset(logo_cache, 0, sizeof(*logo_cache));strcpy(logo_cache->name, bmp);INIT_LIST_HEAD(&logo_cache->head);list_add_tail(&logo_cache->head, &logo_cache_list);}return logo_cache;
}/* Note: used only for rkfb kernel driver */
static int load_kernel_bmp_logo(struct logo_info *logo, const char *bmp_name)
{
#ifdef CONFIG_ROCKCHIP_RESOURCE_IMAGEvoid *dst = NULL;int len, size;struct bmp_header *header;if (!logo || !bmp_name)return -EINVAL;header = malloc(RK_BLK_SIZE);if (!header)return -ENOMEM;len = rockchip_read_resource_file(header, bmp_name, 0, RK_BLK_SIZE);if (len != RK_BLK_SIZE) {free(header);return -EINVAL;}size = get_unaligned_le32(&header->file_size);dst = (void *)(memory_start + MEMORY_POOL_SIZE / 2);len = rockchip_read_resource_file(dst, bmp_name, 0, size);if (len != size) {printf("failed to load bmp %s\n", bmp_name);free(header);return -ENOENT;}logo->mem = dst;
#endifreturn 0;
}#ifdef BMP_DECODEER_LEGACY
static int load_bmp_logo_legacy(struct logo_info *logo, const char *bmp_name)
{
#ifdef CONFIG_ROCKCHIP_RESOURCE_IMAGEstruct rockchip_logo_cache *logo_cache;struct bmp_header *header;void *dst = NULL, *pdst;int size, len;int ret = 0;int reserved = 0;int dst_size;if (!logo || !bmp_name)return -EINVAL;logo_cache = find_or_alloc_logo_cache(bmp_name, logo->rotate);if (!logo_cache)return -ENOMEM;if (logo_cache->logo.mem) {memcpy(logo, &logo_cache->logo, sizeof(*logo));return 0;}header = malloc(RK_BLK_SIZE);if (!header)return -ENOMEM;len = rockchip_read_resource_file(header, bmp_name, 0, RK_BLK_SIZE);if (len != RK_BLK_SIZE) {ret = -EINVAL;goto free_header;}logo->bpp = get_unaligned_le16(&header->bit_count);logo->width = get_unaligned_le32(&header->width);logo->height = get_unaligned_le32(&header->height);dst_size = logo->width * logo->height * logo->bpp >> 3;reserved = get_unaligned_le32(&header->reserved);if (logo->height < 0)logo->height = -logo->height;size = get_unaligned_le32(&header->file_size);if (!can_direct_logo(logo->bpp)) {if (size > MEMORY_POOL_SIZE) {printf("failed to use boot buf as temp bmp buffer\n");ret = -ENOMEM;goto free_header;}pdst = get_display_buffer(size);} else {pdst = get_display_buffer(size);dst = pdst;}len = rockchip_read_resource_file(pdst, bmp_name, 0, size);if (len != size) {printf("failed to load bmp %s\n", bmp_name);ret = -ENOENT;goto free_header;}if (!can_direct_logo(logo->bpp)) {/** TODO: force use 16bpp if bpp less than 16;*/logo->bpp = (logo->bpp <= 16) ? 16 : logo->bpp;dst_size = logo->width * logo->height * logo->bpp >> 3;dst = get_display_buffer(dst_size);if (!dst) {ret = -ENOMEM;goto free_header;}if (bmpdecoder(pdst, dst, logo->bpp)) {printf("failed to decode bmp %s\n", bmp_name);ret = -EINVAL;goto free_header;}logo->offset = 0;logo->ymirror = 0;} else {logo->offset = get_unaligned_le32(&header->data_offset);if (reserved == BMP_PROCESSED_FLAG)logo->ymirror = 0;elselogo->ymirror = 1;}logo->mem = dst;memcpy(&logo_cache->logo, logo, sizeof(*logo));flush_dcache_range((ulong)dst, ALIGN((ulong)dst + dst_size, CONFIG_SYS_CACHELINE_SIZE));free_header:free(header);return ret;
#elsereturn -EINVAL;
#endif
}
#endifstatic void *bitmap_create(int width, int height, unsigned int state)
{/* Ensure a stupidly large bitmap is not created */if (width > 4096 || height > 4096)return NULL;return calloc(width * height, BYTES_PER_PIXEL);
}static unsigned char *bitmap_get_buffer(void *bitmap)
{return bitmap;
}static void bitmap_destroy(void *bitmap)
{free(bitmap);
}static void bmp_copy(void *dst, bmp_image *bmp)
{u16 row, col;u8 *image;u8 *pdst = (u8 *)dst;image = (u8 *)bmp->bitmap;for (row = 0; row != bmp->height; row++) {for (col = 0; col != bmp->width; col++) {size_t z = (row * bmp->width + col) * BYTES_PER_PIXEL;*pdst++ = image[z + 2];*pdst++ = image[z + 1];*pdst++ = image[z + 0];*pdst++ = image[z + 3];}}
}static void *rockchip_logo_rotate(struct logo_info *logo, void *src)
{void *dst_rotate;int width = logo->width;int height = logo->height;int width_rotate = logo->height & 0x3 ? (logo->height & ~0x3) + 4 : logo->height;int height_rotate = logo->width;int dst_size = width * height * logo->bpp >> 3;int dst_size_rotate = width_rotate * height_rotate * logo->bpp >> 3;int bytes_per_pixel = logo->bpp >> 3;int padded_width;int i, j;char *img_data;if (!(logo->rotate == 90 || logo->rotate == 180 || logo->rotate == 270)) {printf("Unsupported rotation angle\n");return NULL;}img_data = (char *)malloc(dst_size);if (!img_data) {printf("failed to alloc memory for image data\n");return NULL;}memcpy(img_data, src, dst_size);dst_rotate = get_display_buffer(dst_size_rotate);if (!dst_rotate)return NULL;memset(dst_rotate, 0, dst_size_rotate);switch (logo->rotate) {case 90:logo->width = width_rotate;logo->height = height_rotate;padded_width = height & 0x3 ? (height & ~0x3) + 4 : height;for (i = 0; i < height; i++) {for (j = 0; j < width; j++) {memcpy(dst_rotate + (j * padded_width * bytes_per_pixel) +(height - i - 1) * bytes_per_pixel,img_data + i * width * bytes_per_pixel + j * bytes_per_pixel,bytes_per_pixel);}}break;case 180:for (i = 0; i < height; i++) {for (j = 0; j < width; j++) {memcpy(dst_rotate + (height - i - 1) * width * bytes_per_pixel +(width - j - 1) * bytes_per_pixel,img_data + i * width * bytes_per_pixel + j * bytes_per_pixel,bytes_per_pixel);}}break;case 270:logo->width = width_rotate;logo->height = height_rotate;padded_width = height & 0x3 ? (height & ~0x3) + 4 : height;for (i = 0; i < height; i++) {for (j = 0; j < width; j++) {memcpy(dst_rotate + (width - j - 1) * padded_width * bytes_per_pixel +i * bytes_per_pixel,img_data + i * width * bytes_per_pixel + j * bytes_per_pixel,bytes_per_pixel);}}break;default:break;}free(img_data);return dst_rotate;
}static int load_bmp_logo(struct logo_info *logo, const char *bmp_name)
{
#ifdef CONFIG_ROCKCHIP_RESOURCE_IMAGEstruct rockchip_logo_cache *logo_cache;bmp_bitmap_callback_vt bitmap_callbacks = {bitmap_create,bitmap_destroy,bitmap_get_buffer,};bmp_result code;bmp_image bmp;void *bmp_data;void *dst = NULL;void *dst_rotate = NULL;int len, dst_size;int ret = 0;if (!logo || !bmp_name)return -EINVAL;logo_cache = find_or_alloc_logo_cache(bmp_name, logo->rotate);if (!logo_cache)return -ENOMEM;if (logo_cache->logo.mem) {memcpy(logo, &logo_cache->logo, sizeof(*logo));return 0;}bmp_data = malloc(MAX_IMAGE_BYTES);if (!bmp_data) {printf("failed to alloc bmp data\n");return -ENOMEM;}bmp_create(&bmp, &bitmap_callbacks);len = rockchip_read_resource_file(bmp_data, bmp_name, 0, MAX_IMAGE_BYTES);if (len < 0) {ret = -EINVAL;goto free_bmp_data;}/* analyse the BMP */code = bmp_analyse(&bmp, len, bmp_data);if (code != BMP_OK) {printf("failed to parse bmp:%s header\n", bmp_name);ret = -EINVAL;goto free_bmp_data;}if (bmp.buffer_size > MAX_IMAGE_BYTES) {printf("bmp[%s] data size[%dKB] is over the limitation MAX_IMAGE_BYTES[%dKB]\n",bmp_name, bmp.buffer_size / 1024, MAX_IMAGE_BYTES / 1024);ret = -EINVAL;goto free_bmp_data;}/* fix bpp to 32 */logo->bpp = 32;logo->offset = 0;logo->ymirror = 0;logo->width = get_unaligned_le32(&bmp.width);logo->height = get_unaligned_le32(&bmp.height);dst_size = logo->width * logo->height * logo->bpp >> 3;/* decode the image to RGBA8888 format */code = bmp_decode(&bmp);if (code != BMP_OK) {/* allow partially decoded images */if (code != BMP_INSUFFICIENT_DATA && code != BMP_DATA_ERROR) {printf("failed to allocate the buffer of bmp:%s\n", bmp_name);ret = -EINVAL;goto free_bmp_data;}}dst = get_display_buffer(dst_size);if (!dst) {ret = -ENOMEM;goto free_bmp_data;}bmp_copy(dst, &bmp);if (logo->rotate) {dst_rotate = rockchip_logo_rotate(logo, dst);if (dst_rotate) {dst = dst_rotate;dst_size = logo->width * logo->height * logo->bpp >> 3;}printf("logo ratate %d\n", logo->rotate);}logo->mem = dst;memcpy(&logo_cache->logo, logo, sizeof(*logo));logo_cache->logo_rotate = logo->rotate;flush_dcache_range((ulong)dst, ALIGN((ulong)dst + dst_size, CONFIG_SYS_CACHELINE_SIZE));
free_bmp_data:/* clean up */bmp_finalise(&bmp);free(bmp_data);return ret;
#elsereturn -EINVAL;
#endif
}void rockchip_show_fbbase(ulong fbbase)
{struct display_state *s;list_for_each_entry(s, &rockchip_display_list, head) {s->logo.mode = ROCKCHIP_DISPLAY_FULLSCREEN;s->logo.mem = (char *)fbbase;s->logo.width = DRM_ROCKCHIP_FB_WIDTH;s->logo.height = DRM_ROCKCHIP_FB_HEIGHT;s->logo.bpp = 32;s->logo.ymirror = 0;display_logo(s);}
}int rockchip_show_bmp(const char *bmp)    //显示指定的 bmp 图片,目前主要用于充电logo的显示
{struct display_state *s;int ret = 0;if (!bmp) {list_for_each_entry(s, &rockchip_display_list, head)display_disable(s);return -ENOENT;}list_for_each_entry(s, &rockchip_display_list, head) {s->logo.mode = s->charge_logo_mode;if (load_bmp_logo(&s->logo, bmp))continue;ret = display_logo(s);}return ret;
}int rockchip_show_logo(void)    //显示u-boot logo
{struct display_state *s;struct display_state *ms = NULL;int ret = 0;int count = 0;list_for_each_entry(s, &rockchip_display_list, head) {s->logo.mode = s->logo_mode;s->logo.rotate = s->logo_rotate;if (load_bmp_logo(&s->logo, s->ulogo_name)) {printf("failed to display uboot logo\n");} else {ret = display_logo(s);if (ret == -EAGAIN)ms = s;}/* Load kernel bmp in rockchip_display_fixup() later */}/** For rk3566, the mirror win must be enabled after the related* source win. If error code is EAGAIN, the mirror win may be* first enabled unexpectedly, and we will move the enabling process* as follows.*/if (ms) {while (count < 5) {ret = display_logo(ms);if (ret != -EAGAIN)break;mdelay(10);count++;}}return ret;
}int rockchip_vop_dump(const char *cmd)
{struct display_state *state;struct crtc_state *crtc_state;struct rockchip_crtc *crtc;const struct rockchip_crtc_funcs *crtc_funcs;int ret = -EINVAL;list_for_each_entry(state, &rockchip_display_list, head) {if (!state->is_init)continue;crtc_state = &state->crtc_state;crtc = crtc_state->crtc;crtc_funcs = crtc->funcs;if (!cmd)ret = crtc_funcs->active_regs_dump(state);else if (!strcmp(cmd, "a") || !strcmp(cmd, "all"))ret = crtc_funcs->regs_dump(state);if (!ret)break;}if (ret)ret = CMD_RET_USAGE;return ret;
}enum {PORT_DIR_IN,PORT_DIR_OUT,
};const struct device_node *rockchip_of_graph_get_port_by_id(ofnode node, int id)
{ofnode ports, port;u32 reg;ports = ofnode_find_subnode(node, "ports");if (!ofnode_valid(ports))return NULL;ofnode_for_each_subnode(port, ports) {if (ofnode_read_u32(port, "reg", &reg))continue;if (reg == id)break;}if (reg == id)return ofnode_to_np(port);return NULL;
}static const struct device_node *rockchip_of_graph_get_port_parent(ofnode port)
{ofnode parent;int is_ports_node;parent = ofnode_get_parent(port);is_ports_node = strstr(ofnode_to_np(parent)->full_name, "ports") ? 1 : 0;if (is_ports_node)parent = ofnode_get_parent(parent);return ofnode_to_np(parent);
}const struct device_node *
rockchip_of_graph_get_endpoint_by_regs(ofnode node, int port, int endpoint)
{const struct device_node *port_node;ofnode ep;u32 reg;port_node = rockchip_of_graph_get_port_by_id(node, port);if (!port_node)return NULL;ofnode_for_each_subnode(ep, np_to_ofnode(port_node)) {if (ofnode_read_u32(ep, "reg", &reg))break;if (reg == endpoint)break;}if (!ofnode_valid(ep))return NULL;return ofnode_to_np(ep);
}static const struct device_node *
rockchip_of_graph_get_remote_node(ofnode node, int port, int endpoint)
{const struct device_node *ep_node;ofnode ep;uint phandle;ep_node = rockchip_of_graph_get_endpoint_by_regs(node, port, endpoint);if (!ep_node)return NULL;if (ofnode_read_u32(np_to_ofnode(ep_node), "remote-endpoint", &phandle))return NULL;ep = ofnode_get_by_phandle(phandle);if (!ofnode_valid(ep))return NULL;return ofnode_to_np(ep);
}static int rockchip_of_find_panel(struct udevice *dev, struct rockchip_panel **panel)
{const struct device_node *ep_node, *panel_node;ofnode panel_ofnode, port;struct udevice *panel_dev;int ret = 0;*panel = NULL;panel_ofnode = dev_read_subnode(dev, "panel");if (ofnode_valid(panel_ofnode) && ofnode_is_available(panel_ofnode)) {ret = uclass_get_device_by_ofnode(UCLASS_PANEL, panel_ofnode,&panel_dev);if (!ret)goto found;}ep_node = rockchip_of_graph_get_remote_node(dev->node, PORT_DIR_OUT, 0);if (!ep_node)return -ENODEV;port = ofnode_get_parent(np_to_ofnode(ep_node));if (!ofnode_valid(port))return -ENODEV;panel_node = rockchip_of_graph_get_port_parent(port);if (!panel_node)return -ENODEV;ret = uclass_get_device_by_ofnode(UCLASS_PANEL, np_to_ofnode(panel_node), &panel_dev);if (!ret)goto found;return -ENODEV;found:*panel = (struct rockchip_panel *)dev_get_driver_data(panel_dev);return 0;
}static int rockchip_of_find_bridge(struct udevice *dev, struct rockchip_bridge **bridge)
{const struct device_node *ep_node, *bridge_node;ofnode port;struct udevice *bridge_dev;int ret = 0;ep_node = rockchip_of_graph_get_remote_node(dev->node, PORT_DIR_OUT, 0);if (!ep_node)return -ENODEV;port = ofnode_get_parent(np_to_ofnode(ep_node));if (!ofnode_valid(port))return -ENODEV;bridge_node = rockchip_of_graph_get_port_parent(port);if (!bridge_node)return -ENODEV;ret = uclass_get_device_by_ofnode(UCLASS_VIDEO_BRIDGE, np_to_ofnode(bridge_node),&bridge_dev);if (!ret)goto found;return -ENODEV;found:*bridge = (struct rockchip_bridge *)dev_get_driver_data(bridge_dev);return 0;
}static int rockchip_of_find_panel_or_bridge(struct udevice *dev, struct rockchip_panel **panel,struct rockchip_bridge **bridge)
{int ret = 0;if (*panel)return 0;*panel = NULL;*bridge = NULL;if (panel) {ret  = rockchip_of_find_panel(dev, panel);if (!ret)return 0;}if (ret) {ret = rockchip_of_find_bridge(dev, bridge);if (!ret)ret = rockchip_of_find_panel_or_bridge((*bridge)->dev, panel,&(*bridge)->next_bridge);}return ret;
}static struct rockchip_phy *rockchip_of_find_phy(struct udevice *dev)
{struct udevice *phy_dev;int ret;ret = uclass_get_device_by_phandle(UCLASS_PHY, dev, "phys", &phy_dev);if (ret)return NULL;return (struct rockchip_phy *)dev_get_driver_data(phy_dev);
}static struct udevice *rockchip_of_find_connector_device(ofnode endpoint)
{ofnode ep, port, ports, conn;uint phandle;struct udevice *dev;int ret;if (ofnode_read_u32(endpoint, "remote-endpoint", &phandle))return NULL;ep = ofnode_get_by_phandle(phandle);if (!ofnode_valid(ep) || !ofnode_is_available(ep))return NULL;port = ofnode_get_parent(ep);if (!ofnode_valid(port))return NULL;ports = ofnode_get_parent(port);if (!ofnode_valid(ports))return NULL;conn = ofnode_get_parent(ports);if (!ofnode_valid(conn) || !ofnode_is_available(conn))return NULL;ret = uclass_get_device_by_ofnode(UCLASS_DISPLAY, conn, &dev);if (ret)return NULL;return dev;
}static struct rockchip_connector *rockchip_of_get_connector(ofnode endpoint)
{struct rockchip_connector *conn;struct udevice *dev;int ret;dev = rockchip_of_find_connector_device(endpoint);if (!dev) {printf("Warn: can't find connect driver\n");return NULL;}conn = get_rockchip_connector_by_device(dev);if (!conn)return NULL;ret = rockchip_of_find_panel_or_bridge(dev, &conn->panel, &conn->bridge);if (ret)debug("Warn: no find panel or bridge\n");conn->phy = rockchip_of_find_phy(dev);return conn;
}static struct rockchip_connector *rockchip_get_split_connector(struct rockchip_connector *conn)
{char *conn_name;struct device_node *split_node;struct udevice *split_dev;struct rockchip_connector *split_conn;int ret;conn->split_mode = ofnode_read_bool(conn->dev->node, "split-mode");conn->dual_channel_mode = ofnode_read_bool(conn->dev->node, "dual-channel");if (!conn->split_mode && !conn->dual_channel_mode)return NULL;switch (conn->type) {case DRM_MODE_CONNECTOR_DisplayPort:conn_name = "dp";break;case DRM_MODE_CONNECTOR_eDP:conn_name = "edp";break;case DRM_MODE_CONNECTOR_HDMIA:conn_name = "hdmi";break;case DRM_MODE_CONNECTOR_LVDS:conn_name = "lvds";break;default:return NULL;}split_node = of_alias_get_dev(conn_name, !conn->id);if (!split_node || !of_device_is_available(split_node))return NULL;ret = uclass_get_device_by_ofnode(UCLASS_DISPLAY, np_to_ofnode(split_node), &split_dev);if (ret)return NULL;split_conn = get_rockchip_connector_by_device(split_dev);if (!split_conn)return NULL;ret = rockchip_of_find_panel_or_bridge(split_dev, &split_conn->panel, &split_conn->bridge);if (ret)debug("Warn: no find panel or bridge\n");split_conn->phy = rockchip_of_find_phy(split_dev);split_conn->split_mode = conn->split_mode;split_conn->dual_channel_mode = conn->dual_channel_mode;return split_conn;
}static bool rockchip_get_display_path_status(ofnode endpoint)
{ofnode ep;uint phandle;if (ofnode_read_u32(endpoint, "remote-endpoint", &phandle))return false;ep = ofnode_get_by_phandle(phandle);if (!ofnode_valid(ep) || !ofnode_is_available(ep))return false;return true;
}#if defined(CONFIG_ROCKCHIP_RK3568)
static int rockchip_display_fixup_dts(void *blob)
{ofnode route_node, route_subnode, conn_ep, conn_port;const struct device_node *route_sub_devnode;const struct device_node *ep_node, *conn_ep_dev_node;u32 phandle;int conn_ep_offset;const char *route_sub_path, *path;/* Don't go further if new variant after* reading PMUGRF_SOC_CON15*/if ((readl(0xfdc20100) & GENMASK(15, 14)))return 0;route_node = ofnode_path("/display-subsystem/route");if (!ofnode_valid(route_node))return -EINVAL;ofnode_for_each_subnode(route_subnode, route_node) {if (!ofnode_is_available(route_subnode))continue;route_sub_devnode = ofnode_to_np(route_subnode);route_sub_path = route_sub_devnode->full_name;if (!strstr(ofnode_get_name(route_subnode), "dsi") &&!strstr(ofnode_get_name(route_subnode), "edp"))return 0;phandle = ofnode_read_u32_default(route_subnode, "connect", -1);if (phandle < 0) {printf("Warn: can't find connect node's handle\n");continue;}ep_node = of_find_node_by_phandle(phandle);if (!ofnode_valid(np_to_ofnode(ep_node))) {printf("Warn: can't find endpoint node from phandle\n");continue;}ofnode_read_u32(np_to_ofnode(ep_node), "remote-endpoint", &phandle);conn_ep = ofnode_get_by_phandle(phandle);if (!ofnode_valid(conn_ep) || !ofnode_is_available(conn_ep))return -ENODEV;conn_port = ofnode_get_parent(conn_ep);if (!ofnode_valid(conn_port))return -ENODEV;ofnode_for_each_subnode(conn_ep, conn_port) {conn_ep_dev_node = ofnode_to_np(conn_ep);path = conn_ep_dev_node->full_name;ofnode_read_u32(conn_ep, "remote-endpoint", &phandle);conn_ep_offset = fdt_path_offset(blob, path);if (!ofnode_is_available(conn_ep) &&strstr(ofnode_get_name(conn_ep), "endpoint@0")) {do_fixup_by_path_u32(blob, route_sub_path,"connect", phandle, 1);fdt_status_okay(blob, conn_ep_offset);} else if (ofnode_is_available(conn_ep) &&strstr(ofnode_get_name(conn_ep), "endpoint@1")) {fdt_status_disabled(blob, conn_ep_offset);}}}return 0;
}
#endifstatic int rockchip_display_probe(struct udevice *dev)
{struct video_priv *uc_priv = dev_get_uclass_priv(dev);struct video_uc_platdata *plat = dev_get_uclass_platdata(dev);const void *blob = gd->fdt_blob;int phandle;struct udevice *crtc_dev;struct rockchip_crtc *crtc;struct rockchip_connector *conn, *split_conn;struct display_state *s;const char *name;int ret;ofnode node, route_node, timing_node;struct device_node *port_node, *vop_node, *ep_node, *port_parent_node;struct public_phy_data *data;bool is_ports_node = false;#if defined(CONFIG_ROCKCHIP_RK3568)rockchip_display_fixup_dts((void *)blob);
#endif/* Before relocation we don't need to do anything */if (!(gd->flags & GD_FLG_RELOC))return 0;data = malloc(sizeof(struct public_phy_data));if (!data) {printf("failed to alloc phy data\n");return -ENOMEM;}data->phy_init = false;init_display_buffer(plat->base);route_node = dev_read_subnode(dev, "route");if (!ofnode_valid(route_node))return -ENODEV;ofnode_for_each_subnode(node, route_node) {if (!ofnode_is_available(node))continue;phandle = ofnode_read_u32_default(node, "connect", -1);if (phandle < 0) {printf("Warn: can't find connect node's handle\n");continue;}ep_node = of_find_node_by_phandle(phandle);if (!ofnode_valid(np_to_ofnode(ep_node))) {printf("Warn: can't find endpoint node from phandle\n");continue;}port_node = of_get_parent(ep_node);if (!ofnode_valid(np_to_ofnode(port_node))) {printf("Warn: can't find port node from phandle\n");continue;}port_parent_node = of_get_parent(port_node);if (!ofnode_valid(np_to_ofnode(port_parent_node))) {printf("Warn: can't find port parent node from phandle\n");continue;}is_ports_node = strstr(port_parent_node->full_name, "ports") ? 1 : 0;if (is_ports_node) {vop_node = of_get_parent(port_parent_node);if (!ofnode_valid(np_to_ofnode(vop_node))) {printf("Warn: can't find crtc node from phandle\n");continue;}} else {vop_node = port_parent_node;}ret = uclass_get_device_by_ofnode(UCLASS_VIDEO_CRTC,np_to_ofnode(vop_node),&crtc_dev);if (ret) {printf("Warn: can't find crtc driver %d\n", ret);continue;}crtc = (struct rockchip_crtc *)dev_get_driver_data(crtc_dev);conn = rockchip_of_get_connector(np_to_ofnode(ep_node));if (!conn) {printf("Warn: can't get connect driver\n");continue;}split_conn = rockchip_get_split_connector(conn);s = malloc(sizeof(*s));if (!s)continue;memset(s, 0, sizeof(*s));INIT_LIST_HEAD(&s->head);ret = ofnode_read_string_index(node, "logo,uboot", 0, &name);if (!ret)memcpy(s->ulogo_name, name, strlen(name));ret = ofnode_read_string_index(node, "logo,kernel", 0, &name);if (!ret)memcpy(s->klogo_name, name, strlen(name));ret = ofnode_read_string_index(node, "logo,mode", 0, &name);if (!strcmp(name, "fullscreen"))s->logo_mode = ROCKCHIP_DISPLAY_FULLSCREEN;elses->logo_mode = ROCKCHIP_DISPLAY_CENTER;ret = ofnode_read_string_index(node, "charge_logo,mode", 0, &name);if (!strcmp(name, "fullscreen"))s->charge_logo_mode = ROCKCHIP_DISPLAY_FULLSCREEN;elses->charge_logo_mode = ROCKCHIP_DISPLAY_CENTER;s->logo_rotate = ofnode_read_u32_default(node, "logo,rotate", 0);s->force_output = ofnode_read_bool(node, "force-output");if (s->force_output) {timing_node = ofnode_find_subnode(node, "force_timing");ret = display_get_force_timing_from_dts(timing_node,&s->force_mode,&s->conn_state.bus_flags);if (ofnode_read_u32(node, "force-bus-format", &s->force_bus_format))s->force_bus_format = MEDIA_BUS_FMT_RGB888_1X24;}s->blob = blob;s->conn_state.connector = conn;s->conn_state.secondary = NULL;s->conn_state.type = conn->type;if (split_conn) {s->conn_state.secondary = split_conn;s->conn_state.output_flags |= ROCKCHIP_OUTPUT_DUAL_CHANNEL_LEFT_RIGHT_MODE;s->conn_state.output_flags |= conn->id ? ROCKCHIP_OUTPUT_DATA_SWAP : 0;}s->conn_state.overscan.left_margin = 100;s->conn_state.overscan.right_margin = 100;s->conn_state.overscan.top_margin = 100;s->conn_state.overscan.bottom_margin = 100;s->crtc_state.node = np_to_ofnode(vop_node);s->crtc_state.port_node = port_node;s->crtc_state.dev = crtc_dev;s->crtc_state.crtc = crtc;s->crtc_state.crtc_id = get_crtc_id(np_to_ofnode(ep_node), is_ports_node);s->node = node;if (is_ports_node) { /* only vop2 will get into here */ofnode vp_node = np_to_ofnode(port_node);static bool get_plane_mask_from_dts;s->crtc_state.ports_node = port_parent_node;if (!get_plane_mask_from_dts) {ofnode vp_sub_node;int vp_id = 0;bool vp_enable = false;ofnode_for_each_subnode(vp_node, np_to_ofnode(port_parent_node)) {int cursor_plane = -1;vp_id = ofnode_read_u32_default(vp_node, "reg", 0);s->crtc_state.crtc->vps[vp_id].xmirror_en =ofnode_read_bool(vp_node, "xmirror-enable");ret = ofnode_read_u32_default(vp_node, "rockchip,plane-mask", 0);cursor_plane = ofnode_read_u32_default(vp_node, "cursor-win-id", -1);s->crtc_state.crtc->vps[vp_id].cursor_plane = cursor_plane;if (ret) {s->crtc_state.crtc->vps[vp_id].plane_mask = ret;s->crtc_state.crtc->assign_plane |= true;s->crtc_state.crtc->vps[vp_id].primary_plane_id =ofnode_read_u32_default(vp_node, "rockchip,primary-plane", U8_MAX);printf("get vp%d plane mask:0x%x, primary id:%d, cursor_plane:%d, from dts\n",vp_id,s->crtc_state.crtc->vps[vp_id].plane_mask,s->crtc_state.crtc->vps[vp_id].primary_plane_id == U8_MAX ? -1 :s->crtc_state.crtc->vps[vp_id].primary_plane_id,cursor_plane);}/* To check current vp status */vp_enable = false;ofnode_for_each_subnode(vp_sub_node, vp_node)vp_enable |= rockchip_get_display_path_status(vp_sub_node);s->crtc_state.crtc->vps[vp_id].enable = vp_enable;}get_plane_mask_from_dts = true;}}get_crtc_mcu_mode(&s->crtc_state, port_node, is_ports_node);ret = ofnode_read_u32_default(s->crtc_state.node,"rockchip,dual-channel-swap", 0);s->crtc_state.dual_channel_swap = ret;if (connector_phy_init(conn, data)) {printf("Warn: Failed to init phy drivers\n");free(s);continue;}list_add_tail(&s->head, &rockchip_display_list);}if (list_empty(&rockchip_display_list)) {debug("Failed to found available display route\n");return -ENODEV;}rockchip_get_baseparameter();display_pre_init();uc_priv->xsize = DRM_ROCKCHIP_FB_WIDTH;uc_priv->ysize = DRM_ROCKCHIP_FB_HEIGHT;uc_priv->bpix = VIDEO_BPP32;#ifdef CONFIG_DRM_ROCKCHIP_VIDEO_FRAMEBUFFERrockchip_show_fbbase(plat->base);video_set_flush_dcache(dev, true);#endifreturn 0;
}void rockchip_display_fixup(void *blob)    //传递变量到内核
{const struct rockchip_connector_funcs *conn_funcs;const struct rockchip_crtc_funcs *crtc_funcs;struct rockchip_connector *conn;const struct rockchip_crtc *crtc;struct display_state *s;int offset;int ret;const struct device_node *np;const char *path;const char *cacm_header;u64 aligned_memory_size;if (fdt_node_offset_by_compatible(blob, 0, "rockchip,drm-logo") >= 0) {list_for_each_entry(s, &rockchip_display_list, head) {ret = load_bmp_logo(&s->logo, s->klogo_name);if (ret < 0) {s->is_klogo_valid = false;printf("VP%d fail to load kernel logo\n", s->crtc_state.crtc_id);} else {s->is_klogo_valid = true;}}if (!get_display_size())return;aligned_memory_size = (u64)ALIGN(get_display_size(), align_size);offset = fdt_update_reserved_memory(blob, "rockchip,drm-logo",(u64)memory_start,aligned_memory_size);if (offset < 0)printf("failed to reserve drm-loader-logo memory\n");if (get_cubic_memory_size()) {aligned_memory_size = (u64)ALIGN(get_cubic_memory_size(), align_size);offset = fdt_update_reserved_memory(blob, "rockchip,drm-cubic-lut",(u64)cubic_lut_memory_start,aligned_memory_size);if (offset < 0)printf("failed to reserve drm-cubic-lut memory\n");}} else {printf("can't found rockchip,drm-logo, use rockchip,fb-logo\n");/* Compatible with rkfb display, only need reserve memory */offset = fdt_update_reserved_memory(blob, "rockchip,fb-logo",(u64)memory_start,MEMORY_POOL_SIZE);if (offset < 0)printf("failed to reserve fb-loader-logo memory\n");elselist_for_each_entry(s, &rockchip_display_list, head)load_kernel_bmp_logo(&s->logo, s->klogo_name);return;}list_for_each_entry(s, &rockchip_display_list, head) {/** If plane mask is not set in dts, fixup dts to assign it* whether crtc is initialized or not.*/if (s->crtc_state.crtc->funcs->fixup_dts && !s->crtc_state.crtc->assign_plane)s->crtc_state.crtc->funcs->fixup_dts(s, blob);if (!s->is_init || !s->is_klogo_valid)continue;conn = s->conn_state.connector;if (!conn)continue;conn_funcs = conn->funcs;if (!conn_funcs) {printf("failed to get exist connector\n");continue;}crtc = s->crtc_state.crtc;if (!crtc)continue;crtc_funcs = crtc->funcs;if (!crtc_funcs) {printf("failed to get exist crtc\n");continue;}np = ofnode_to_np(s->node);path = np->full_name;fdt_increase_size(blob, 0x400);
#define FDT_SET_U32(name, val) \do_fixup_by_path_u32(blob, path, name, val, 1);offset = s->logo.offset + (u32)(unsigned long)s->logo.mem- memory_start;FDT_SET_U32("logo,offset", offset);FDT_SET_U32("logo,width", s->logo.width);FDT_SET_U32("logo,height", s->logo.height);FDT_SET_U32("logo,bpp", s->logo.bpp);FDT_SET_U32("logo,ymirror", s->logo.ymirror);FDT_SET_U32("video,clock", s->conn_state.mode.clock);FDT_SET_U32("video,hdisplay", s->conn_state.mode.hdisplay);FDT_SET_U32("video,vdisplay", s->conn_state.mode.vdisplay);FDT_SET_U32("video,crtc_hsync_end", s->conn_state.mode.crtc_hsync_end);FDT_SET_U32("video,crtc_vsync_end", s->conn_state.mode.crtc_vsync_end);FDT_SET_U32("video,vrefresh",drm_mode_vrefresh(&s->conn_state.mode));FDT_SET_U32("video,flags", s->conn_state.mode.flags);FDT_SET_U32("video,aspect_ratio", s->conn_state.mode.picture_aspect_ratio);FDT_SET_U32("overscan,left_margin", s->conn_state.overscan.left_margin);FDT_SET_U32("overscan,right_margin", s->conn_state.overscan.right_margin);FDT_SET_U32("overscan,top_margin", s->conn_state.overscan.top_margin);FDT_SET_U32("overscan,bottom_margin", s->conn_state.overscan.bottom_margin);if (s->conn_state.disp_info) {cacm_header = (const char*)&s->conn_state.disp_info->cacm_header;FDT_SET_U32("bcsh,brightness", s->conn_state.disp_info->bcsh_info.brightness);FDT_SET_U32("bcsh,contrast", s->conn_state.disp_info->bcsh_info.contrast);FDT_SET_U32("bcsh,saturation", s->conn_state.disp_info->bcsh_info.saturation);FDT_SET_U32("bcsh,hue", s->conn_state.disp_info->bcsh_info.hue);if (!strncasecmp(cacm_header, "CACM", 4)) {FDT_SET_U32("post-csc,hue",s->conn_state.disp_info->csc_info.hue);FDT_SET_U32("post-csc,saturation",s->conn_state.disp_info->csc_info.saturation);FDT_SET_U32("post-csc,contrast",s->conn_state.disp_info->csc_info.contrast);FDT_SET_U32("post-csc,brightness",s->conn_state.disp_info->csc_info.brightness);FDT_SET_U32("post-csc,r-gain",s->conn_state.disp_info->csc_info.r_gain);FDT_SET_U32("post-csc,g-gain",s->conn_state.disp_info->csc_info.g_gain);FDT_SET_U32("post-csc,b-gain",s->conn_state.disp_info->csc_info.b_gain);FDT_SET_U32("post-csc,r-offset",s->conn_state.disp_info->csc_info.r_offset);FDT_SET_U32("post-csc,g-offset",s->conn_state.disp_info->csc_info.g_offset);FDT_SET_U32("post-csc,b-offset",s->conn_state.disp_info->csc_info.b_offset);FDT_SET_U32("post-csc,enable",s->conn_state.disp_info->csc_info.csc_enable);}}if (s->conn_state.disp_info->cubic_lut_data.size &&CONFIG_ROCKCHIP_CUBIC_LUT_SIZE)FDT_SET_U32("cubic_lut,offset", get_cubic_lut_offset(s->crtc_state.crtc_id));#undef FDT_SET_U32}
}int rockchip_display_bind(struct udevice *dev)
{struct video_uc_platdata *plat = dev_get_uclass_platdata(dev);plat->size = DRM_ROCKCHIP_FB_SIZE + MEMORY_POOL_SIZE;return 0;
}static const struct udevice_id rockchip_display_ids[] = {{ .compatible = "rockchip,display-subsystem" },{ }
};U_BOOT_DRIVER(rockchip_display) = {.name	= "rockchip_display",.id	= UCLASS_VIDEO,.of_match = rockchip_display_ids,.bind	= rockchip_display_bind,.probe	= rockchip_display_probe,
};static int do_rockchip_logo_show(cmd_tbl_t *cmdtp, int flag, int argc,char *const argv[])
{if (argc != 1)return CMD_RET_USAGE;rockchip_show_logo();return 0;
}static int do_rockchip_show_bmp(cmd_tbl_t *cmdtp, int flag, int argc,char *const argv[])
{if (argc != 2)return CMD_RET_USAGE;rockchip_show_bmp(argv[1]);return 0;
}static int do_rockchip_vop_dump(cmd_tbl_t *cmdtp, int flag, int argc,char *const argv[])
{int ret;if (argc < 1 || argc > 2)return CMD_RET_USAGE;ret = rockchip_vop_dump(argv[1]);return ret;
}U_BOOT_CMD(rockchip_show_logo, 1, 1, do_rockchip_logo_show,"load and display log from resource partition",NULL
);U_BOOT_CMD(rockchip_show_bmp, 2, 1, do_rockchip_show_bmp,"load and display bmp from resource partition","    <bmp_name>"
);U_BOOT_CMD(vop_dump, 2, 1, do_rockchip_vop_dump,"dump vop regs"," [a/all]"
);

与logo相关的接口如下:

显示u-boot 接口:

void rockchip_show_logo(void)

显示指定的BMP图片:

void rockchip_show_bmp(const char *bmp)

将 U-Boot 中确定的一些变量通过修改 dtb 文件传递给内核,包括 kernel logo 的大小、地址、格式、输出扫描时序以及过扫描的配置等信息:

void rockchip_display_fixup(void *blob)

另外,在该驱动中也能找到显示驱动的初始化和配置,尤其是与DRM/KMS相关的部分,如下方是CRTC显示控制器的初始化,在驱动中可以看到被多次引用;

struct crtc_state *crtc_state = &state->crtc_state;
struct rockchip_crtc *crtc = crtc_state->crtc;
const struct rockchip_crtc_funcs *crtc_funcs = crtc->funcs;

输出模式的配置如下,如果启用强制输出模式,则从dts中获取强制时序信息,并设置强制总线格式。

		if (s->force_output) {timing_node = ofnode_find_subnode(node, "force_timing");ret = display_get_force_timing_from_dts(timing_node,&s->force_mode,&s->conn_state.bus_flags);if (ofnode_read_u32(node, "force-bus-format", &s->force_bus_format))s->force_bus_format = MEDIA_BUS_FMT_RGB888_1X24;}
dw_mipi_dsi2.c

接下来我们来看看针对特化接口的独立驱动,如dw_mipi_dsi2.c、dw-dp.c、dw_hdmi.c等这类驱动,这里以RK3576 DSI接口为例。在RK3576平台,采用dw_mipi_dsi2.c作为DSI接口的显示驱动。MIPI DSI-2 除了可以兼容 MIPI DSI 的所有协议功能外, 还增加支持 MIPI C-PHY。

/** (C) Copyright 2008-2017 Fuzhou Rockchip Electronics Co., Ltd** SPDX-License-Identifier:	GPL-2.0+** Author: Guochun Huang <hero.huang@rock-chips.com>*/#include <drm/drm_mipi_dsi.h>#include <config.h>
#include <common.h>
#include <errno.h>
#include <asm/unaligned.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <dm/device.h>
#include <dm/read.h>
#include <dm/of_access.h>
#include <regmap.h>
#include <syscon.h>
#include <asm/arch-rockchip/clock.h>
#include <linux/iopoll.h>
#include <asm/gpio.h>#include "rockchip_display.h"
#include "rockchip_crtc.h"
#include "rockchip_connector.h"
#include "rockchip_panel.h"
#include "rockchip_phy.h"#define UPDATE(v, h, l)		(((v) << (l)) & GENMASK((h), (l)))#define DSI2_PWR_UP			0x000c
#define RESET				0
#define POWER_UP			BIT(0)
#define CMD_TX_MODE(x)			UPDATE(x,  24,  24)
#define DSI2_SOFT_RESET			0x0010
#define SYS_RSTN			BIT(2)
#define PHY_RSTN			BIT(1)
#define IPI_RSTN			BIT(0)
#define INT_ST_MAIN			0x0014
#define DSI2_MODE_CTRL			0x0018
#define DSI2_MODE_STATUS		0x001c
#define DSI2_CORE_STATUS		0x0020
#define PRI_RD_DATA_AVAIL		BIT(26)
#define PRI_FIFOS_NOT_EMPTY		BIT(25)
#define PRI_BUSY			BIT(24)
#define CRI_RD_DATA_AVAIL		BIT(18)
#define CRT_FIFOS_NOT_EMPTY		BIT(17)
#define CRI_BUSY			BIT(16)
#define IPI_FIFOS_NOT_EMPTY		BIT(9)
#define IPI_BUSY			BIT(8)
#define CORE_FIFOS_NOT_EMPTY		BIT(1)
#define CORE_BUSY			BIT(0)
#define MANUAL_MODE_CFG			0x0024
#define MANUAL_MODE_EN			BIT(0)
#define DSI2_TIMEOUT_HSTX_CFG		0x0048
#define TO_HSTX(x)			UPDATE(x, 15, 0)
#define DSI2_TIMEOUT_HSTXRDY_CFG	0x004c
#define TO_HSTXRDY(x)			UPDATE(x, 15, 0)
#define DSI2_TIMEOUT_LPRX_CFG		0x0050
#define TO_LPRXRDY(x)			UPDATE(x, 15, 0)
#define DSI2_TIMEOUT_LPTXRDY_CFG	0x0054
#define TO_LPTXRDY(x)			UPDATE(x, 15, 0)
#define DSI2_TIMEOUT_LPTXTRIG_CFG	0x0058
#define TO_LPTXTRIG(x)			UPDATE(x, 15, 0)
#define DSI2_TIMEOUT_LPTXULPS_CFG	0x005c
#define TO_LPTXULPS(x)			UPDATE(x, 15, 0)
#define DSI2_TIMEOUT_BTA_CFG		0x60
#define TO_BTA(x)			UPDATE(x, 15, 0)#define DSI2_PHY_MODE_CFG		0x0100
#define PPI_WIDTH(x)			UPDATE(x, 9, 8)
#define PHY_LANES(x)			UPDATE(x - 1, 5, 4)
#define PHY_TYPE(x)			UPDATE(x, 0, 0)
#define DSI2_PHY_CLK_CFG		0X0104
#define PHY_LPTX_CLK_DIV(x)		UPDATE(x, 12, 8)
#define CLK_TYPE_MASK			BIT(0)
#define NON_CONTINUOUS_CLK		BIT(0)
#define CONTIUOUS_CLK			0
#define DSI2_PHY_LP2HS_MAN_CFG		0x010c
#define PHY_LP2HS_TIME(x)		UPDATE(x, 28, 0)
#define DSI2_PHY_HS2LP_MAN_CFG		0x0114
#define PHY_HS2LP_TIME(x)		UPDATE(x, 28, 0)
#define DSI2_PHY_MAX_RD_T_MAN_CFG	0x011c
#define PHY_MAX_RD_TIME(x)		UPDATE(x, 26, 0)
#define DSI2_PHY_ESC_CMD_T_MAN_CFG	0x0124
#define PHY_ESC_CMD_TIME(x)		UPDATE(x, 28, 0)
#define DSI2_PHY_ESC_BYTE_T_MAN_CFG	0x012c
#define PHY_ESC_BYTE_TIME(x)		UPDATE(x, 28, 0)#define DSI2_PHY_IPI_RATIO_MAN_CFG	0x0134
#define PHY_IPI_RATIO(x)		UPDATE(x, 21, 0)
#define DSI2_PHY_SYS_RATIO_MAN_CFG	0x013C
#define PHY_SYS_RATIO(x)		UPDATE(x, 16, 0)#define DSI2_DSI_GENERAL_CFG		0x0200
#define BTA_EN				BIT(1)
#define EOTP_TX_EN			BIT(0)
#define DSI2_DSI_VCID_CFG		0x0204
#define TX_VCID(x)			UPDATE(x, 1, 0)
#define DSI2_DSI_SCRAMBLING_CFG		0x0208
#define SCRAMBLING_SEED(x)		UPDATE(x, 31, 16)
#define SCRAMBLING_EN			BIT(0)
#define DSI2_DSI_VID_TX_CFG		0x020c
#define LPDT_DISPLAY_CMD_EN		BIT(20)
#define BLK_VFP_HS_EN			BIT(14)
#define BLK_VBP_HS_EN			BIT(13)
#define BLK_VSA_HS_EN			BIT(12)
#define BLK_HFP_HS_EN			BIT(6)
#define BLK_HBP_HS_EN			BIT(5)
#define BLK_HSA_HS_EN			BIT(4)
#define VID_MODE_TYPE(x)		UPDATE(x, 1, 0)
#define DSI2_CRI_TX_HDR			0x02c0
#define CMD_TX_MODE(x)			UPDATE(x, 24, 24)
#define DSI2_CRI_TX_PLD			0x02c4
#define DSI2_CRI_RX_HDR			0x02c8
#define DSI2_CRI_RX_PLD			0x02cc#define DSI2_IPI_COLOR_MAN_CFG		0x0300
#define IPI_DEPTH(x)			UPDATE(x, 7, 4)
#define IPI_DEPTH_5_6_5_BITS		0x02
#define IPI_DEPTH_6_BITS		0x03
#define IPI_DEPTH_8_BITS		0x05
#define IPI_DEPTH_10_BITS		0x06
#define IPI_FORMAT(x)			UPDATE(x, 3, 0)
#define IPI_FORMAT_RGB			0x0
#define IPI_FORMAT_DSC			0x0b
#define DSI2_IPI_VID_HSA_MAN_CFG	0x0304
#define VID_HSA_TIME(x)			UPDATE(x, 29, 0)
#define DSI2_IPI_VID_HBP_MAN_CFG	0x030c
#define VID_HBP_TIME(x)			UPDATE(x, 29, 0)
#define DSI2_IPI_VID_HACT_MAN_CFG	0x0314
#define VID_HACT_TIME(x)		UPDATE(x, 29, 0)
#define DSI2_IPI_VID_HLINE_MAN_CFG	0x031c
#define VID_HLINE_TIME(x)		UPDATE(x, 29, 0)
#define DSI2_IPI_VID_VSA_MAN_CFG	0x0324
#define VID_VSA_LINES(x)		UPDATE(x, 9, 0)
#define DSI2_IPI_VID_VBP_MAN_CFG	0X032C
#define VID_VBP_LINES(x)		UPDATE(x, 9, 0)
#define DSI2_IPI_VID_VACT_MAN_CFG	0X0334
#define VID_VACT_LINES(x)		UPDATE(x, 13, 0)
#define DSI2_IPI_VID_VFP_MAN_CFG	0X033C
#define VID_VFP_LINES(x)		UPDATE(x, 9, 0)
#define DSI2_IPI_PIX_PKT_CFG		0x0344
#define MAX_PIX_PKT(x)			UPDATE(x, 15, 0)#define DSI2_INT_ST_PHY			0x0400
#define DSI2_INT_MASK_PHY		0x0404
#define DSI2_INT_ST_TO			0x0410
#define DSI2_INT_MASK_TO		0x0414
#define DSI2_INT_ST_ACK			0x0420
#define DSI2_INT_MASK_ACK		0x0424
#define DSI2_INT_ST_IPI			0x0430
#define DSI2_INT_MASK_IPI		0x0434
#define DSI2_INT_ST_FIFO		0x0440
#define DSI2_INT_MASK_FIFO		0x0444
#define DSI2_INT_ST_PRI			0x0450
#define DSI2_INT_MASK_PRI		0x0454
#define DSI2_INT_ST_CRI			0x0460
#define DSI2_INT_MASK_CRI		0x0464
#define DSI2_INT_FORCE_CRI		0x0468
#define DSI2_MAX_REGISGER		DSI2_INT_FORCE_CRI#define CMD_PKT_STATUS_TIMEOUT_US	1000
#define MODE_STATUS_TIMEOUT_US		20000
#define PSEC_PER_SEC			1000000000000LL
#define USEC_PER_SEC			1000000L
#define MSEC_PER_SEC			1000L#define GRF_REG_FIELD(reg, lsb, msb)	(((reg) << 16) | ((lsb) << 8) | (msb))enum vid_mode_type {VID_MODE_TYPE_NON_BURST_SYNC_PULSES,VID_MODE_TYPE_NON_BURST_SYNC_EVENTS,VID_MODE_TYPE_BURST,
};enum mode_ctrl {IDLE_MODE,AUTOCALC_MODE,COMMAND_MODE,VIDEO_MODE,DATA_STREAM_MODE,VIDE_TEST_MODE,DATA_STREAM_TEST_MODE,
};enum grf_reg_fields {TXREQCLKHS_EN,GATING_EN,IPI_SHUTDN,IPI_COLORM,IPI_COLOR_DEPTH,IPI_FORMAT,MAX_FIELDS,
};enum phy_type {DPHY,CPHY,
};enum ppi_width {PPI_WIDTH_8_BITS,PPI_WIDTH_16_BITS,PPI_WIDTH_32_BITS,
};struct rockchip_cmd_header {u8 data_type;u8 delay_ms;u8 payload_length;
};struct dw_mipi_dsi2_plat_data {bool dsc;const u32 *dsi0_grf_reg_fields;const u32 *dsi1_grf_reg_fields;unsigned long long dphy_max_bit_rate_per_lane;unsigned long long cphy_max_symbol_rate_per_lane;
};struct mipi_dcphy {/* Non-SNPS PHY */struct rockchip_phy *phy;u16 input_div;u16 feedback_div;
};/*** struct mipi_dphy_configure - MIPI D-PHY configuration set** This structure is used to represent the configuration state of a* MIPI D-PHY phy.*/
struct mipi_dphy_configure {unsigned int		clk_miss;unsigned int		clk_post;unsigned int		clk_pre;unsigned int		clk_prepare;unsigned int		clk_settle;unsigned int		clk_term_en;unsigned int		clk_trail;unsigned int		clk_zero;unsigned int		d_term_en;unsigned int		eot;unsigned int		hs_exit;unsigned int		hs_prepare;unsigned int		hs_settle;unsigned int		hs_skip;unsigned int		hs_trail;unsigned int		hs_zero;unsigned int		init;unsigned int		lpx;unsigned int		ta_get;unsigned int		ta_go;unsigned int		ta_sure;unsigned int		wakeup;unsigned long		hs_clk_rate;unsigned long		lp_clk_rate;unsigned char		lanes;
};struct dw_mipi_dsi2 {struct rockchip_connector connector;struct udevice *dev;void *base;void *grf;int id;struct gpio_desc reset_gpio;struct gpio_desc enable_gpio;struct dw_mipi_dsi2 *master;struct dw_mipi_dsi2 *slave;bool prepared;bool disable_hold_mode;bool auto_calc_mode;bool c_option;bool dsc_enable;bool scrambling_en;unsigned int slice_width;unsigned int slice_height;u32 version_major;u32 version_minor;struct clk sys_clk;unsigned int lane_hs_rate; /* Kbps/Ksps per lane */u32 channel;u32 lanes;u32 format;u32 mode_flags;u64 mipi_pixel_rate;struct mipi_dcphy dcphy;struct drm_display_mode mode;bool data_swap;bool dual_channel;struct gpio_desc te_gpio;struct mipi_dsi_device *device;struct mipi_dphy_configure mipi_dphy_cfg;const struct dw_mipi_dsi2_plat_data *pdata;struct drm_dsc_picture_parameter_set *pps;
};static inline void dsi_write(struct dw_mipi_dsi2 *dsi2, u32 reg, u32 val)
{writel(val, dsi2->base + reg);
}static inline u32 dsi_read(struct dw_mipi_dsi2 *dsi2, u32 reg)
{return readl(dsi2->base + reg);
}static inline void dsi_update_bits(struct dw_mipi_dsi2 *dsi2,u32 reg, u32 mask, u32 val)
{u32 orig, tmp;orig = dsi_read(dsi2, reg);tmp = orig & ~mask;tmp |= val & mask;dsi_write(dsi2, reg, tmp);
}static void grf_field_write(struct dw_mipi_dsi2 *dsi2, enum grf_reg_fields index,unsigned int val)
{const u32 field = dsi2->id ? dsi2->pdata->dsi1_grf_reg_fields[index] :dsi2->pdata->dsi0_grf_reg_fields[index];u16 reg;u8 msb, lsb;if (!field)return;reg = (field >> 16) & 0xffff;lsb = (field >>  8) & 0xff;msb = (field >>  0) & 0xff;regmap_write(dsi2->grf, reg, GENMASK(msb, lsb) << 16 | val << lsb);
}static unsigned long dw_mipi_dsi2_get_lane_rate(struct dw_mipi_dsi2 *dsi2)
{const struct drm_display_mode *mode = &dsi2->mode;u64 max_lane_rate, lane_rate;unsigned int value;int bpp, lanes;u64 tmp;max_lane_rate = (dsi2->c_option) ?dsi2->pdata->cphy_max_symbol_rate_per_lane :dsi2->pdata->dphy_max_bit_rate_per_lane;/** optional override of the desired bandwidth* High-Speed mode: Differential and terminated: 80Mbps ~ 4500 Mbps*/value = dev_read_u32_default(dsi2->dev, "rockchip,lane-rate", 0);if (value >= 80000 && value <= 4500000)return value * MSEC_PER_SEC;else if (value >= 80 && value <= 4500)return value * USEC_PER_SEC;bpp = mipi_dsi_pixel_format_to_bpp(dsi2->format);if (bpp < 0)bpp = 24;lanes = dsi2->slave ? dsi2->lanes * 2 : dsi2->lanes;tmp = (u64)mode->crtc_clock * 1000 * bpp;do_div(tmp, lanes);if (dsi2->c_option)tmp = DIV_ROUND_CLOSEST(tmp * 100, 228);/* set BW a little larger only in video burst mode in* consideration of the protocol overhead and HS mode* switching to BLLP mode, take 1 / 0.9, since Mbps must* big than bandwidth of RGB*/if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) {tmp *= 10;do_div(tmp, 9);}if (tmp > max_lane_rate)lane_rate = max_lane_rate;elselane_rate = tmp;return lane_rate;
}static int cri_fifos_wait_avail(struct dw_mipi_dsi2 *dsi2)
{u32 sts, mask;int ret;mask = CRI_BUSY | CRT_FIFOS_NOT_EMPTY;ret = readl_poll_timeout(dsi2->base + DSI2_CORE_STATUS,sts, !(sts & mask),CMD_PKT_STATUS_TIMEOUT_US);if (ret < 0) {printf("command interface is busy: 0x%x\n", sts);return ret;}return 0;
}static int dw_mipi_dsi2_read_from_fifo(struct dw_mipi_dsi2 *dsi2,const struct mipi_dsi_msg *msg)
{u8 *payload = msg->rx_buf;u8 data_type;u16 wc;int i, j, ret, len = msg->rx_len;unsigned int vrefresh = drm_mode_vrefresh(&dsi2->mode);u32 val;ret = readl_poll_timeout(dsi2->base + DSI2_CORE_STATUS,val, val & CRI_RD_DATA_AVAIL,DIV_ROUND_UP(1000000, vrefresh));if (ret) {printf("CRI has no available read data\n");return ret;}val = dsi_read(dsi2, DSI2_CRI_RX_HDR);data_type = val & 0x3f;if (mipi_dsi_packet_format_is_short(data_type)) {for (i = 0; i < len && i < 2; i++)payload[i] = (val >> (8 * (i + 1))) & 0xff;return 0;}wc = (val >> 8) & 0xffff;/* Receive payload */for (i = 0; i < len && i < wc; i += 4) {val = dsi_read(dsi2, DSI2_CRI_RX_PLD);for (j = 0; j < 4 && j + i < len && j + i < wc; j++)payload[i + j] = val >> (8 * j);}return 0;
}static void dw_mipi_dsi2_clk_management(struct dw_mipi_dsi2 *dsi2)
{u32 clk_type;/** initial deskew calibration is send after phy_power_on,* then we can configure clk_type.*/if (dsi2->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)clk_type = NON_CONTINUOUS_CLK;elseclk_type = CONTIUOUS_CLK;dsi_update_bits(dsi2, DSI2_PHY_CLK_CFG, CLK_TYPE_MASK, clk_type);
}static ssize_t dw_mipi_dsi2_transfer(struct dw_mipi_dsi2 *dsi2,const struct mipi_dsi_msg *msg)
{struct mipi_dsi_packet packet;int ret;int val;u32 mode;dw_mipi_dsi2_clk_management(dsi2);dsi_update_bits(dsi2, DSI2_DSI_VID_TX_CFG, LPDT_DISPLAY_CMD_EN,msg->flags & MIPI_DSI_MSG_USE_LPM ?LPDT_DISPLAY_CMD_EN : 0);/* create a packet to the DSI protocol */ret = mipi_dsi_create_packet(&packet, msg);if (ret) {printf("failed to create packet: %d\n", ret);return ret;}/* check cri interface is not busy */ret = cri_fifos_wait_avail(dsi2);if (ret)return ret;/* Send payload */while (DIV_ROUND_UP(packet.payload_length, 4)) {if (packet.payload_length < 4) {/* send residu payload */val = 0;memcpy(&val, packet.payload, packet.payload_length);dsi_write(dsi2, DSI2_CRI_TX_PLD, val);packet.payload_length = 0;} else {val = get_unaligned_le32(packet.payload);dsi_write(dsi2, DSI2_CRI_TX_PLD, val);packet.payload += 4;packet.payload_length -= 4;}}/* Send packet header */mode = CMD_TX_MODE(msg->flags & MIPI_DSI_MSG_USE_LPM ? 1 : 0);val = get_unaligned_le32(packet.header);dsi_write(dsi2, DSI2_CRI_TX_HDR, mode | val);ret = cri_fifos_wait_avail(dsi2);if (ret)return ret;if (msg->rx_len) {ret = dw_mipi_dsi2_read_from_fifo(dsi2, msg);if (ret < 0)return ret;}if (dsi2->slave) {ret = dw_mipi_dsi2_transfer(dsi2->slave, msg);if (ret < 0)return ret;}return msg->rx_len ? msg->rx_len : msg->tx_len;
}static void dw_mipi_dsi2_ipi_color_coding_cfg(struct dw_mipi_dsi2 *dsi2)
{u32 val, color_depth;switch (dsi2->format) {case MIPI_DSI_FMT_RGB666:case MIPI_DSI_FMT_RGB666_PACKED:color_depth = IPI_DEPTH_6_BITS;break;case MIPI_DSI_FMT_RGB565:color_depth = IPI_DEPTH_5_6_5_BITS;break;case MIPI_DSI_FMT_RGB888:default:color_depth = IPI_DEPTH_8_BITS;break;}val = IPI_DEPTH(color_depth) |IPI_FORMAT(dsi2->dsc_enable ? IPI_FORMAT_DSC : IPI_FORMAT_RGB);dsi_write(dsi2, DSI2_IPI_COLOR_MAN_CFG, val);grf_field_write(dsi2, IPI_COLOR_DEPTH, color_depth);if (dsi2->dsc_enable)grf_field_write(dsi2, IPI_FORMAT, IPI_FORMAT_DSC);
}static void dw_mipi_dsi2_ipi_set(struct dw_mipi_dsi2 *dsi2)
{struct drm_display_mode *mode = &dsi2->mode;u32 hline, hsa, hbp, hact;u64 hline_time, hsa_time, hbp_time, hact_time, tmp;u64 pixel_clk, phy_hs_clk;u32 vact, vsa, vfp, vbp;u16 val;if (dsi2->slave || dsi2->master)val = mode->hdisplay / 2;elseval = mode->hdisplay;dsi_write(dsi2, DSI2_IPI_PIX_PKT_CFG, MAX_PIX_PKT(val));dw_mipi_dsi2_ipi_color_coding_cfg(dsi2);if (dsi2->auto_calc_mode)return;/** if the controller is intended to operate in data stream mode,* no more steps are required.*/if (!(dsi2->mode_flags & MIPI_DSI_MODE_VIDEO))return;vact = mode->vdisplay;vsa = mode->vsync_end - mode->vsync_start;vfp = mode->vsync_start - mode->vdisplay;vbp = mode->vtotal - mode->vsync_end;hact = mode->hdisplay;hsa = mode->hsync_end - mode->hsync_start;hbp = mode->htotal - mode->hsync_end;hline = mode->htotal;pixel_clk = mode->crtc_clock * MSEC_PER_SEC;if (dsi2->c_option)phy_hs_clk = DIV_ROUND_CLOSEST(dsi2->lane_hs_rate * MSEC_PER_SEC, 7);elsephy_hs_clk = DIV_ROUND_CLOSEST(dsi2->lane_hs_rate * MSEC_PER_SEC, 16);tmp = hsa * phy_hs_clk;hsa_time = DIV_ROUND_CLOSEST(tmp << 16, pixel_clk);dsi_write(dsi2, DSI2_IPI_VID_HSA_MAN_CFG, VID_HSA_TIME(hsa_time));tmp = hbp * phy_hs_clk;hbp_time = DIV_ROUND_CLOSEST(tmp << 16, pixel_clk);dsi_write(dsi2, DSI2_IPI_VID_HBP_MAN_CFG, VID_HBP_TIME(hbp_time));tmp = hact * phy_hs_clk;hact_time = DIV_ROUND_CLOSEST(tmp << 16, pixel_clk);dsi_write(dsi2, DSI2_IPI_VID_HACT_MAN_CFG, VID_HACT_TIME(hact_time));tmp = hline * phy_hs_clk;hline_time = DIV_ROUND_CLOSEST(tmp << 16, pixel_clk);dsi_write(dsi2, DSI2_IPI_VID_HLINE_MAN_CFG, VID_HLINE_TIME(hline_time));dsi_write(dsi2, DSI2_IPI_VID_VSA_MAN_CFG, VID_VSA_LINES(vsa));dsi_write(dsi2, DSI2_IPI_VID_VBP_MAN_CFG, VID_VBP_LINES(vbp));dsi_write(dsi2, DSI2_IPI_VID_VACT_MAN_CFG, VID_VACT_LINES(vact));dsi_write(dsi2, DSI2_IPI_VID_VFP_MAN_CFG, VID_VFP_LINES(vfp));
}static void dw_mipi_dsi2_set_vid_mode(struct dw_mipi_dsi2 *dsi2)
{u32 val = 0, mode;int ret;if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HFP)val |= BLK_HFP_HS_EN;if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HBP)val |= BLK_HBP_HS_EN;if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HSA)val |= BLK_HSA_HS_EN;if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)val |= VID_MODE_TYPE_BURST;else if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)val |= VID_MODE_TYPE_NON_BURST_SYNC_PULSES;elseval |= VID_MODE_TYPE_NON_BURST_SYNC_EVENTS;dsi_write(dsi2, DSI2_DSI_VID_TX_CFG, val);dsi_write(dsi2, DSI2_MODE_CTRL, VIDEO_MODE);ret = readl_poll_timeout(dsi2->base + DSI2_MODE_STATUS,mode, mode & VIDEO_MODE,MODE_STATUS_TIMEOUT_US);if (ret < 0)printf("failed to enter video mode\n");
}static void dw_mipi_dsi2_set_data_stream_mode(struct dw_mipi_dsi2 *dsi2)
{u32 mode;int ret;dsi_write(dsi2, DSI2_MODE_CTRL, DATA_STREAM_MODE);ret = readl_poll_timeout(dsi2->base + DSI2_MODE_STATUS,mode, mode & DATA_STREAM_MODE,MODE_STATUS_TIMEOUT_US);if (ret < 0)printf("failed to enter data stream mode\n");
}static void dw_mipi_dsi2_set_cmd_mode(struct dw_mipi_dsi2 *dsi2)
{u32 mode;int ret;dsi_write(dsi2, DSI2_MODE_CTRL, COMMAND_MODE);ret = readl_poll_timeout(dsi2->base + DSI2_MODE_STATUS,mode, mode & COMMAND_MODE,MODE_STATUS_TIMEOUT_US);if (ret < 0)printf("failed to enter cmd mode\n");
}static void dw_mipi_dsi2_enable(struct dw_mipi_dsi2 *dsi2)
{u32 mode;int ret;dw_mipi_dsi2_clk_management(dsi2);dw_mipi_dsi2_ipi_set(dsi2);if (dsi2->auto_calc_mode) {dsi_write(dsi2, DSI2_MODE_CTRL, AUTOCALC_MODE);ret = readl_poll_timeout(dsi2->base + DSI2_MODE_STATUS,mode, mode == IDLE_MODE,MODE_STATUS_TIMEOUT_US);if (ret < 0)printf("auto calculation training failed\n");}if (dsi2->mode_flags & MIPI_DSI_MODE_VIDEO)dw_mipi_dsi2_set_vid_mode(dsi2);elsedw_mipi_dsi2_set_data_stream_mode(dsi2);if (dsi2->slave)dw_mipi_dsi2_enable(dsi2->slave);
}static void dw_mipi_dsi2_disable(struct dw_mipi_dsi2 *dsi2)
{dsi_write(dsi2, DSI2_IPI_PIX_PKT_CFG, 0);dw_mipi_dsi2_set_cmd_mode(dsi2);if (dsi2->slave)dw_mipi_dsi2_disable(dsi2->slave);
}static void dw_mipi_dsi2_post_disable(struct dw_mipi_dsi2 *dsi2)
{if (!dsi2->prepared)return;dsi_write(dsi2, DSI2_PWR_UP, RESET);if (dsi2->dcphy.phy)rockchip_phy_power_off(dsi2->dcphy.phy);dsi2->prepared = false;if (dsi2->slave)dw_mipi_dsi2_post_disable(dsi2->slave);
}static int dw_mipi_dsi2_connector_pre_init(struct rockchip_connector *conn,struct display_state *state)
{struct connector_state *conn_state = &state->conn_state;struct dw_mipi_dsi2 *dsi2 = dev_get_priv(conn->dev);struct mipi_dsi_host *host = dev_get_platdata(dsi2->dev);struct mipi_dsi_device *device;char name[20];conn_state->type = DRM_MODE_CONNECTOR_DSI;if (conn->bridge) {device = dev_get_platdata(conn->bridge->dev);if (!device)return -ENODEV;device->host = host;sprintf(name, "%s.%d", host->dev->name, device->channel);device_set_name(conn->bridge->dev, name);mipi_dsi_attach(device);}return 0;
}static int dw_mipi_dsi2_get_dsc_params_from_sink(struct dw_mipi_dsi2 *dsi2)
{struct udevice *dev = dsi2->device->dev;struct rockchip_cmd_header *header;struct drm_dsc_picture_parameter_set *pps = NULL;u8 *dsc_packed_pps;const void *data;int len;dsi2->c_option = dev_read_bool(dev, "phy-c-option");dsi2->scrambling_en = dev_read_bool(dev, "scrambling-enable");dsi2->dsc_enable = dsi2->pdata->dsc ?dev_read_bool(dev, "compressed-data") : false;if (dsi2->slave) {dsi2->slave->c_option = dsi2->c_option;dsi2->slave->scrambling_en = dsi2->scrambling_en;dsi2->slave->dsc_enable = dsi2->dsc_enable;}if (!dsi2->dsc_enable)return 0;dsi2->slice_width = dev_read_u32_default(dev, "slice-width", 0);dsi2->slice_height = dev_read_u32_default(dev, "slice-height", 0);dsi2->version_major = dev_read_u32_default(dev, "version-major", 0);dsi2->version_minor = dev_read_u32_default(dev, "version-minor", 0);data = dev_read_prop(dev, "panel-init-sequence", &len);if (!data)return -EINVAL;while (len > sizeof(*header)) {header = (struct rockchip_cmd_header *)data;data += sizeof(*header);len -= sizeof(*header);if (header->payload_length > len)return -EINVAL;if (header->data_type == MIPI_DSI_PICTURE_PARAMETER_SET) {dsc_packed_pps = calloc(1, header->payload_length);if (!dsc_packed_pps)return -ENOMEM;memcpy(dsc_packed_pps, data, header->payload_length);pps = (struct drm_dsc_picture_parameter_set *)dsc_packed_pps;break;}data += header->payload_length;len -= header->payload_length;}if (!pps) {printf("not found dsc pps definition\n");return -EINVAL;}dsi2->pps = pps;if (dsi2->slave) {u16 pic_width = be16_to_cpu(pps->pic_width) / 2;dsi2->pps->pic_width = cpu_to_be16(pic_width);printf("dsc pic_width change from %d to %d\n", pic_width * 2, pic_width);}return 0;
}static void rockchip_dsi_external_bridge_power_off(struct dw_mipi_dsi2 *dsi)
{struct gpio_desc *enable_gpio = &dsi->enable_gpio;struct gpio_desc *reset_gpio = &dsi->reset_gpio;dm_gpio_set_value(reset_gpio, 1);dm_gpio_set_value(enable_gpio, 1);printf("uboot GM8775C power off \n");}static void rockchip_dsi_external_bridge_power_on(struct dw_mipi_dsi2 *dsi)
{struct gpio_desc *enable_gpio = &dsi->enable_gpio;struct gpio_desc *reset_gpio = &dsi->reset_gpio;dm_gpio_set_value(enable_gpio, 1);udelay(1000);dm_gpio_set_value(reset_gpio, 1);udelay(1000);printf("uboot GM8775C power on \n");
}static int dw_mipi_dsi2_connector_init(struct rockchip_connector *conn, struct display_state *state)
{struct connector_state *conn_state = &state->conn_state;struct crtc_state *cstate = &state->crtc_state;struct dw_mipi_dsi2 *dsi2 = dev_get_priv(conn->dev);struct rockchip_phy *phy = NULL;struct udevice *phy_dev;struct udevice *dev;int ret;conn_state->disp_info  = rockchip_get_disp_info(conn_state->type, dsi2->id);dsi2->dcphy.phy = conn->phy;conn_state->output_mode = ROCKCHIP_OUT_MODE_P888;conn_state->color_encoding = DRM_COLOR_YCBCR_BT709;conn_state->color_range = DRM_COLOR_YCBCR_FULL_RANGE;conn_state->output_if |=dsi2->id ? VOP_OUTPUT_IF_MIPI1 : VOP_OUTPUT_IF_MIPI0;if (!(dsi2->mode_flags & MIPI_DSI_MODE_VIDEO)) {conn_state->output_flags |= ROCKCHIP_OUTPUT_MIPI_DS_MODE;conn_state->hold_mode = dsi2->disable_hold_mode ? false : true;}if (dsi2->dual_channel) {ret = uclass_get_device_by_name(UCLASS_DISPLAY,"dsi@fde30000",&dev);if (ret)return ret;dsi2->slave = dev_get_priv(dev);if (!dsi2->slave)return -ENODEV;dsi2->slave->master = dsi2;dsi2->lanes /= 2;dsi2->slave->auto_calc_mode = dsi2->auto_calc_mode;dsi2->slave->lanes = dsi2->lanes;dsi2->slave->format = dsi2->format;dsi2->slave->mode_flags = dsi2->mode_flags;dsi2->slave->channel = dsi2->channel;conn_state->output_flags |=ROCKCHIP_OUTPUT_DUAL_CHANNEL_LEFT_RIGHT_MODE;if (dsi2->data_swap)conn_state->output_flags |= ROCKCHIP_OUTPUT_DATA_SWAP;conn_state->output_if |= VOP_OUTPUT_IF_MIPI1;ret = uclass_get_device_by_phandle(UCLASS_PHY, dev,"phys", &phy_dev);if (ret)return -ENODEV;phy = (struct rockchip_phy *)dev_get_driver_data(phy_dev);if (!phy)return -ENODEV;dsi2->slave->dcphy.phy = phy;if (phy->funcs && phy->funcs->init)return phy->funcs->init(phy);}dw_mipi_dsi2_get_dsc_params_from_sink(dsi2);if (dm_gpio_is_valid(&dsi2->te_gpio)) {cstate->soft_te = true;conn_state->te_gpio = &dsi2->te_gpio;}if (dsi2->dsc_enable) {cstate->dsc_enable = 1;cstate->dsc_sink_cap.version_major = dsi2->version_major;cstate->dsc_sink_cap.version_minor = dsi2->version_minor;cstate->dsc_sink_cap.slice_width = dsi2->slice_width;cstate->dsc_sink_cap.slice_height = dsi2->slice_height;/* only can support rgb888 panel now */cstate->dsc_sink_cap.target_bits_per_pixel_x16 = 8 << 4;cstate->dsc_sink_cap.native_420 = 0;memcpy(&cstate->pps, dsi2->pps, sizeof(struct drm_dsc_picture_parameter_set));}{struct udevice *dev;int ret;ret = uclass_get_device_by_name(UCLASS_DISPLAY, "dsi@27d80000",&dev);if (ret)return ret;ret = gpio_request_by_name(dev, "enable-gpios", 0, &dsi2->enable_gpio,GPIOD_IS_OUT);ret = gpio_request_by_name(dev, "reset-gpios", 0, &dsi2->reset_gpio,GPIOD_IS_OUT);rockchip_dsi_external_bridge_power_on(dsi2);}return 0;
}/** Minimum D-PHY timings based on MIPI D-PHY specification. Derived* from the valid ranges specified in Section 6.9, Table 14, Page 41* of the D-PHY specification (v2.1).*/
int mipi_dphy_get_default_config(unsigned long long hs_clk_rate,struct mipi_dphy_configure *cfg)
{unsigned long long ui;if (!cfg)return -EINVAL;ui = ALIGN(PSEC_PER_SEC, hs_clk_rate);do_div(ui, hs_clk_rate);cfg->clk_miss = 0;cfg->clk_post = 60000 + 52 * ui;cfg->clk_pre = 8000;cfg->clk_prepare = 38000;cfg->clk_settle = 95000;cfg->clk_term_en = 0;cfg->clk_trail = 60000;cfg->clk_zero = 262000;cfg->d_term_en = 0;cfg->eot = 0;cfg->hs_exit = 100000;cfg->hs_prepare = 40000 + 4 * ui;cfg->hs_zero = 105000 + 6 * ui;cfg->hs_settle = 85000 + 6 * ui;cfg->hs_skip = 40000;/** The MIPI D-PHY specification (Section 6.9, v1.2, Table 14, Page 40)* contains this formula as:**     T_HS-TRAIL = max(n * 8 * ui, 60 + n * 4 * ui)** where n = 1 for forward-direction HS mode and n = 4 for reverse-* direction HS mode. There's only one setting and this function does* not parameterize on anything other that ui, so this code will* assumes that reverse-direction HS mode is supported and uses n = 4.*/cfg->hs_trail = max(4 * 8 * ui, 60000 + 4 * 4 * ui);cfg->init = 100;cfg->lpx = 50000;cfg->ta_get = 5 * cfg->lpx;cfg->ta_go = 4 * cfg->lpx;cfg->ta_sure = cfg->lpx;cfg->wakeup = 1000;return 0;
}static void dw_mipi_dsi2_set_hs_clk(struct dw_mipi_dsi2 *dsi2, unsigned long rate)
{mipi_dphy_get_default_config(rate, &dsi2->mipi_dphy_cfg);if (!dsi2->c_option)rockchip_phy_set_mode(dsi2->dcphy.phy, PHY_MODE_MIPI_DPHY);rate = rockchip_phy_set_pll(dsi2->dcphy.phy, rate);dsi2->lane_hs_rate = DIV_ROUND_CLOSEST(rate, MSEC_PER_SEC);
}static void dw_mipi_dsi2_host_softrst(struct dw_mipi_dsi2 *dsi2)
{dsi_write(dsi2, DSI2_SOFT_RESET, 0X0);udelay(100);dsi_write(dsi2, DSI2_SOFT_RESET, SYS_RSTN | PHY_RSTN | IPI_RSTN);
}static void
dw_mipi_dsi2_work_mode(struct dw_mipi_dsi2 *dsi2, u32 mode)
{/** select controller work in Manual mode* Manual: MANUAL_MODE_EN* Automatic: 0*/dsi_write(dsi2, MANUAL_MODE_CFG, mode);
}static void dw_mipi_dsi2_phy_mode_cfg(struct dw_mipi_dsi2 *dsi2)
{u32 val = 0;/* PPI width is fixed to 16 bits in DCPHY */val |= PPI_WIDTH(PPI_WIDTH_16_BITS) | PHY_LANES(dsi2->lanes);val |= PHY_TYPE(dsi2->c_option ? CPHY : DPHY);dsi_write(dsi2, DSI2_PHY_MODE_CFG, val);
}static void dw_mipi_dsi2_phy_clk_mode_cfg(struct dw_mipi_dsi2 *dsi2)
{u32 sys_clk = clk_get_rate(&dsi2->sys_clk) / USEC_PER_SEC;u32 esc_clk_div;u32 val = 0;/** clk_type should be NON_CONTINUOUS_CLK before* initial deskew calibration be sent.*/val |= NON_CONTINUOUS_CLK;/* The Escape clock ranges from 1MHz to 20MHz. */esc_clk_div = DIV_ROUND_UP(sys_clk, 20 * 2);val |= PHY_LPTX_CLK_DIV(esc_clk_div);dsi_write(dsi2, DSI2_PHY_CLK_CFG, val);
}static void dw_mipi_dsi2_phy_ratio_cfg(struct dw_mipi_dsi2 *dsi2)
{u64 ipi_clk, phy_hsclk, tmp;u32 sys_clk = clk_get_rate(&dsi2->sys_clk);/** in DPHY mode, the phy_hstx_clk is exactly 1/16 the Lane high-speed* data rate; In CPHY mode, the phy_hstx_clk is exactly 1/7 the trio* high speed symbol rate.*/if (dsi2->c_option)phy_hsclk = DIV_ROUND_CLOSEST(dsi2->lane_hs_rate * MSEC_PER_SEC, 7);elsephy_hsclk = DIV_ROUND_CLOSEST(dsi2->lane_hs_rate * MSEC_PER_SEC, 16);/* IPI_RATIO_MAN_CFG = PHY_HSTX_CLK / IPI_CLK */ipi_clk = dsi2->mipi_pixel_rate;tmp = DIV_ROUND_CLOSEST(phy_hsclk << 16, ipi_clk);dsi_write(dsi2, DSI2_PHY_IPI_RATIO_MAN_CFG, PHY_IPI_RATIO(tmp));/* SYS_RATIO_MAN_CFG = MIPI_DCPHY_HSCLK_Freq / SYS_CLK */tmp = DIV_ROUND_CLOSEST(phy_hsclk << 16, sys_clk);dsi_write(dsi2, DSI2_PHY_SYS_RATIO_MAN_CFG, PHY_SYS_RATIO(tmp));
}static void dw_mipi_dsi2_lp2hs_or_hs2lp_cfg(struct dw_mipi_dsi2 *dsi2)
{struct mipi_dphy_configure *cfg = &dsi2->mipi_dphy_cfg;unsigned long long tmp, ui;unsigned long long hstx_clk;hstx_clk = DIV_ROUND_CLOSEST(dsi2->lane_hs_rate * MSEC_PER_SEC, 16);ui = ALIGN(PSEC_PER_SEC, hstx_clk);do_div(ui, hstx_clk);/* PHY_LP2HS_TIME = (TLPX + THS-PREPARE + THS-ZERO) / Tphy_hstx_clk */tmp = cfg->lpx + cfg->hs_prepare + cfg->hs_zero;tmp = DIV_ROUND_CLOSEST(tmp << 16, ui);dsi_write(dsi2, DSI2_PHY_LP2HS_MAN_CFG, PHY_LP2HS_TIME(tmp));/* PHY_HS2LP_TIME = (THS-TRAIL + THS-EXIT) / Tphy_hstx_clk */tmp = cfg->hs_trail + cfg->hs_exit;tmp = DIV_ROUND_CLOSEST(tmp << 16, ui);dsi_write(dsi2, DSI2_PHY_HS2LP_MAN_CFG, PHY_HS2LP_TIME(tmp));
}static void dw_mipi_dsi2_phy_init(struct dw_mipi_dsi2 *dsi2)
{dw_mipi_dsi2_phy_mode_cfg(dsi2);dw_mipi_dsi2_phy_clk_mode_cfg(dsi2);if (dsi2->auto_calc_mode)return;dw_mipi_dsi2_phy_ratio_cfg(dsi2);dw_mipi_dsi2_lp2hs_or_hs2lp_cfg(dsi2);/* phy configuration 8 - 10 */
}static void dw_mipi_dsi2_tx_option_set(struct dw_mipi_dsi2 *dsi2)
{u32 val;val = BTA_EN | EOTP_TX_EN;if (dsi2->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)val &= ~EOTP_TX_EN;dsi_write(dsi2, DSI2_DSI_GENERAL_CFG, val);dsi_write(dsi2, DSI2_DSI_VCID_CFG, TX_VCID(dsi2->channel));if (dsi2->scrambling_en)dsi_write(dsi2, DSI2_DSI_SCRAMBLING_CFG, SCRAMBLING_EN);
}static void dw_mipi_dsi2_irq_enable(struct dw_mipi_dsi2 *dsi2, bool enable)
{if (enable) {dsi_write(dsi2, DSI2_INT_MASK_PHY, 0x1);dsi_write(dsi2, DSI2_INT_MASK_TO, 0xf);dsi_write(dsi2, DSI2_INT_MASK_ACK, 0x1);dsi_write(dsi2, DSI2_INT_MASK_IPI, 0x1);dsi_write(dsi2, DSI2_INT_MASK_FIFO, 0x1);dsi_write(dsi2, DSI2_INT_MASK_PRI, 0x1);dsi_write(dsi2, DSI2_INT_MASK_CRI, 0x1);} else {dsi_write(dsi2, DSI2_INT_MASK_PHY, 0x0);dsi_write(dsi2, DSI2_INT_MASK_TO, 0x0);dsi_write(dsi2, DSI2_INT_MASK_ACK, 0x0);dsi_write(dsi2, DSI2_INT_MASK_IPI, 0x0);dsi_write(dsi2, DSI2_INT_MASK_FIFO, 0x0);dsi_write(dsi2, DSI2_INT_MASK_PRI, 0x0);dsi_write(dsi2, DSI2_INT_MASK_CRI, 0x0);};
}static void mipi_dcphy_power_on(struct dw_mipi_dsi2 *dsi2)
{if (!dsi2->dcphy.phy)return;rockchip_phy_power_on(dsi2->dcphy.phy);
}static void dw_mipi_dsi2_pre_enable(struct dw_mipi_dsi2 *dsi2)
{if (dsi2->prepared)return;dw_mipi_dsi2_host_softrst(dsi2);dsi_write(dsi2, DSI2_PWR_UP, RESET);dw_mipi_dsi2_work_mode(dsi2, dsi2->auto_calc_mode ? 0 : MANUAL_MODE_EN);dw_mipi_dsi2_phy_init(dsi2);dw_mipi_dsi2_tx_option_set(dsi2);dw_mipi_dsi2_irq_enable(dsi2, 0);mipi_dcphy_power_on(dsi2);dsi_write(dsi2, DSI2_PWR_UP, POWER_UP);dw_mipi_dsi2_set_cmd_mode(dsi2);dsi2->prepared = true;if (dsi2->slave)dw_mipi_dsi2_pre_enable(dsi2->slave);
}static void dw_mipi_dsi2_get_mipi_pixel_clk(struct dw_mipi_dsi2 *dsi2,struct crtc_state *s)
{struct drm_display_mode *mode = &dsi2->mode;u8 k = dsi2->slave ? 2 : 1;/* 1.When MIPI works in uncompressed mode:* (Video Timing Pixel Rate)/(4)=(MIPI Pixel ClockxK)=(dclk_out×K)=dclk_core* 2.When MIPI works in compressed mode:* MIPI Pixel Clock = cds_clk / 2* MIPI is configured as double channel display mode, K=2, otherwise K=1.*/if (dsi2->dsc_enable) {dsi2->mipi_pixel_rate = s->dsc_cds_clk_rate / 2;if (dsi2->slave)dsi2->slave->mipi_pixel_rate = dsi2->mipi_pixel_rate;return;}dsi2->mipi_pixel_rate = (mode->crtc_clock * MSEC_PER_SEC) / (4 * k);if (dsi2->slave)dsi2->slave->mipi_pixel_rate = dsi2->mipi_pixel_rate;
}static int dw_mipi_dsi2_connector_prepare(struct rockchip_connector *conn,struct display_state *state)
{struct dw_mipi_dsi2 *dsi2 = dev_get_priv(conn->dev);struct connector_state *conn_state = &state->conn_state;struct crtc_state *cstate = &state->crtc_state;unsigned long lane_rate;memcpy(&dsi2->mode, &conn_state->mode, sizeof(struct drm_display_mode));if (dsi2->slave)memcpy(&dsi2->slave->mode, &dsi2->mode,sizeof(struct drm_display_mode));dw_mipi_dsi2_get_mipi_pixel_clk(dsi2, cstate);lane_rate = dw_mipi_dsi2_get_lane_rate(dsi2);if (dsi2->dcphy.phy)dw_mipi_dsi2_set_hs_clk(dsi2, lane_rate);if (dsi2->slave && dsi2->slave->dcphy.phy)dw_mipi_dsi2_set_hs_clk(dsi2->slave, lane_rate);printf("final DSI-Link bandwidth: %u %s x %d\n",dsi2->lane_hs_rate, dsi2->c_option ? "Ksps" : "Kbps",dsi2->slave ? dsi2->lanes * 2 : dsi2->lanes);dw_mipi_dsi2_pre_enable(dsi2);return 0;
}static void dw_mipi_dsi2_connector_unprepare(struct rockchip_connector *conn,struct display_state *state)
{struct dw_mipi_dsi2 *dsi2 = dev_get_priv(conn->dev);dw_mipi_dsi2_post_disable(dsi2);
}static int dw_mipi_dsi2_connector_enable(struct rockchip_connector *conn,struct display_state *state)
{struct dw_mipi_dsi2 *dsi2 = dev_get_priv(conn->dev);dw_mipi_dsi2_enable(dsi2);return 0;
}static int dw_mipi_dsi2_connector_disable(struct rockchip_connector *conn,struct display_state *state)
{struct dw_mipi_dsi2 *dsi2 = dev_get_priv(conn->dev);dw_mipi_dsi2_disable(dsi2);rockchip_dsi_external_bridge_power_off(dsi2);return 0;
}static int dw_mipi_dsi2_connector_mode_valid(struct rockchip_connector *conn,struct display_state *state)
{struct dw_mipi_dsi2 *dsi2 = dev_get_priv(conn->dev);struct connector_state *conn_state = &state->conn_state;u8 min_pixels = dsi2->slave ? 8 : 4;struct videomode vm;drm_display_mode_to_videomode(&conn_state->mode, &vm);/** the minimum region size (HSA,HBP,HACT,HFP) is 4 pixels* which is the ip known issues and limitations.*/if (!(vm.hsync_len < min_pixels || vm.hback_porch < min_pixels ||vm.hfront_porch < min_pixels || vm.hactive < min_pixels))return MODE_OK;if (vm.hsync_len < min_pixels)vm.hsync_len = min_pixels;if (vm.hback_porch < min_pixels)vm.hback_porch = min_pixels;if (vm.hfront_porch < min_pixels)vm.hfront_porch = min_pixels;if (vm.hactive < min_pixels)vm.hactive = min_pixels;memset(&conn_state->mode, 0, sizeof(struct drm_display_mode));drm_display_mode_from_videomode(&vm, &conn_state->mode);conn_state->mode.vrefresh = drm_mode_vrefresh(&conn_state->mode);return MODE_OK;
}static const struct rockchip_connector_funcs dw_mipi_dsi2_connector_funcs = {.pre_init = dw_mipi_dsi2_connector_pre_init,.init = dw_mipi_dsi2_connector_init,.prepare = dw_mipi_dsi2_connector_prepare,.unprepare = dw_mipi_dsi2_connector_unprepare,.enable = dw_mipi_dsi2_connector_enable,.disable = dw_mipi_dsi2_connector_disable,.mode_valid = dw_mipi_dsi2_connector_mode_valid,
};static int dw_mipi_dsi2_probe(struct udevice *dev)
{struct dw_mipi_dsi2 *dsi2 = dev_get_priv(dev);const struct dw_mipi_dsi2_plat_data *pdata =(const struct dw_mipi_dsi2_plat_data *)dev_get_driver_data(dev);struct udevice *syscon;int id, ret;dsi2->base = dev_read_addr_ptr(dev);ret = uclass_get_device_by_phandle(UCLASS_SYSCON, dev, "rockchip,grf",&syscon);if (!ret) {dsi2->grf = syscon_get_regmap(syscon);if (!dsi2->grf)return -ENODEV;}id = of_alias_get_id(ofnode_to_np(dev->node), "dsi");if (id < 0)id = 0;ret = gpio_request_by_name(dev, "te-gpios", 0,&dsi2->te_gpio, GPIOD_IS_IN);if (ret && ret != -ENOENT) {printf("%s: Cannot get TE GPIO: %d\n", __func__, ret);return ret;}ret = clk_get_by_name(dev, "sys_clk", &dsi2->sys_clk);if (ret < 0) {printf("failed to get sys_clk: %d\n", ret);return ret;}dsi2->dev = dev;dsi2->pdata = pdata;dsi2->id = id;dsi2->dual_channel = dev_read_bool(dsi2->dev, "rockchip,dual-channel");dsi2->data_swap = dev_read_bool(dsi2->dev, "rockchip,data-swap");dsi2->auto_calc_mode = dev_read_bool(dsi2->dev, "auto-calculation-mode");dsi2->disable_hold_mode = dev_read_bool(dsi2->dev, "disable-hold-mode");rockchip_connector_bind(&dsi2->connector, dev, id, &dw_mipi_dsi2_connector_funcs, NULL,DRM_MODE_CONNECTOR_DSI);return 0;
}static const u32 rk3576_dsi_grf_reg_fields[MAX_FIELDS] = {[TXREQCLKHS_EN]		= GRF_REG_FIELD(0x0028,  1,  1),[GATING_EN]		= GRF_REG_FIELD(0x0028,  0,  0),[IPI_SHUTDN]		= GRF_REG_FIELD(0x0028,  3,  3),[IPI_COLORM]		= GRF_REG_FIELD(0x0028,  2,  2),[IPI_COLOR_DEPTH]	= GRF_REG_FIELD(0x0028,  8,  11),[IPI_FORMAT]		= GRF_REG_FIELD(0x0028,  4,  7),
};static const u32 rk3588_dsi0_grf_reg_fields[MAX_FIELDS] = {[TXREQCLKHS_EN]		= GRF_REG_FIELD(0x0000, 11, 11),[GATING_EN]		= GRF_REG_FIELD(0x0000, 10, 10),[IPI_SHUTDN]		= GRF_REG_FIELD(0x0000,  9,  9),[IPI_COLORM]		= GRF_REG_FIELD(0x0000,  8,  8),[IPI_COLOR_DEPTH]	= GRF_REG_FIELD(0x0000,  4,  7),[IPI_FORMAT]		= GRF_REG_FIELD(0x0000,  0,  3),
};static const u32 rk3588_dsi1_grf_reg_fields[MAX_FIELDS] = {[TXREQCLKHS_EN]		= GRF_REG_FIELD(0x0004, 11, 11),[GATING_EN]		= GRF_REG_FIELD(0x0004, 10, 10),[IPI_SHUTDN]		= GRF_REG_FIELD(0x0004,  9,  9),[IPI_COLORM]		= GRF_REG_FIELD(0x0004,  8,  8),[IPI_COLOR_DEPTH]	= GRF_REG_FIELD(0x0004,  4,  7),[IPI_FORMAT]		= GRF_REG_FIELD(0x0004,  0,  3),
};static const struct dw_mipi_dsi2_plat_data rk3576_mipi_dsi2_plat_data = {.dsc = false,.dsi0_grf_reg_fields = rk3576_dsi_grf_reg_fields,.dphy_max_bit_rate_per_lane = 2500000000ULL,.cphy_max_symbol_rate_per_lane = 1700000000ULL,
};static const struct dw_mipi_dsi2_plat_data rk3588_mipi_dsi2_plat_data = {.dsc = true,.dsi0_grf_reg_fields = rk3588_dsi0_grf_reg_fields,.dsi1_grf_reg_fields = rk3588_dsi1_grf_reg_fields,.dphy_max_bit_rate_per_lane = 4500000000ULL,.cphy_max_symbol_rate_per_lane = 2000000000ULL,
};static const struct udevice_id dw_mipi_dsi2_ids[] = {{.compatible = "rockchip,rk3576-mipi-dsi2",.data = (ulong)&rk3576_mipi_dsi2_plat_data,}, {.compatible = "rockchip,rk3588-mipi-dsi2",.data = (ulong)&rk3588_mipi_dsi2_plat_data,},{}
};static ssize_t dw_mipi_dsi2_host_transfer(struct mipi_dsi_host *host,const struct mipi_dsi_msg *msg)
{struct dw_mipi_dsi2 *dsi2 = dev_get_priv(host->dev);return dw_mipi_dsi2_transfer(dsi2, msg);
}static int dw_mipi_dsi2_host_attach(struct mipi_dsi_host *host,struct mipi_dsi_device *device)
{struct dw_mipi_dsi2 *dsi2 = dev_get_priv(host->dev);if (device->lanes < 1 || device->lanes > 8)return -EINVAL;dsi2->lanes = device->lanes;dsi2->channel = device->channel;dsi2->format = device->format;dsi2->mode_flags = device->mode_flags;dsi2->device = device;return 0;
}static const struct mipi_dsi_host_ops dw_mipi_dsi2_host_ops = {.attach = dw_mipi_dsi2_host_attach,.transfer = dw_mipi_dsi2_host_transfer,
};static int dw_mipi_dsi2_bind(struct udevice *dev)
{struct mipi_dsi_host *host = dev_get_platdata(dev);host->dev = dev;host->ops = &dw_mipi_dsi2_host_ops;return dm_scan_fdt_dev(dev);
}static int dw_mipi_dsi2_child_post_bind(struct udevice *dev)
{struct mipi_dsi_host *host = dev_get_platdata(dev->parent);struct mipi_dsi_device *device = dev_get_parent_platdata(dev);char name[20];sprintf(name, "%s.%d", host->dev->name, device->channel);device_set_name(dev, name);device->dev = dev;device->host = host;device->lanes = dev_read_u32_default(dev, "dsi,lanes", 4);device->format = dev_read_u32_default(dev, "dsi,format",MIPI_DSI_FMT_RGB888);device->mode_flags = dev_read_u32_default(dev, "dsi,flags",MIPI_DSI_MODE_VIDEO |MIPI_DSI_MODE_VIDEO_BURST |MIPI_DSI_MODE_VIDEO_NO_HBP |MIPI_DSI_MODE_LPM |MIPI_DSI_MODE_NO_EOT_PACKET);device->channel = dev_read_u32_default(dev, "reg", 0);return 0;
}static int dw_mipi_dsi2_child_pre_probe(struct udevice *dev)
{struct mipi_dsi_device *device = dev_get_parent_platdata(dev);int ret;ret = mipi_dsi_attach(device);if (ret) {dev_err(dev, "mipi_dsi_attach() failed: %d\n", ret);return ret;}return 0;
}U_BOOT_DRIVER(dw_mipi_dsi2) = {.name = "dw_mipi_dsi2",.id = UCLASS_DISPLAY,.of_match = dw_mipi_dsi2_ids,.probe = dw_mipi_dsi2_probe,.bind = dw_mipi_dsi2_bind,.priv_auto_alloc_size = sizeof(struct dw_mipi_dsi2),.per_child_platdata_auto_alloc_size = sizeof(struct mipi_dsi_device),.platdata_auto_alloc_size = sizeof(struct mipi_dsi_host),.child_post_bind = dw_mipi_dsi2_child_post_bind,.child_pre_probe = dw_mipi_dsi2_child_pre_probe,
};

这份驱动主要涉及MIPI DSI2接口的硬件配置,包括寄存器定义、模式控制、时钟配置、视频信号参数等。这里在下直接罗列出涉及到我们配置底层屏参时关联到的接口或内容。

首先,dsi节点的检索在dw_mipi_dsi2_probe函数开始后执行,通过"dsi"来识别设备别名;

	id = of_alias_get_id(ofnode_to_np(dev->node), "dsi");if (id < 0)id = 0;ret = gpio_request_by_name(dev, "te-gpios", 0,&dsi2->te_gpio, GPIOD_IS_IN);if (ret && ret != -ENOENT) {printf("%s: Cannot get TE GPIO: %d\n", __func__, ret);return ret;}ret = clk_get_by_name(dev, "sys_clk", &dsi2->sys_clk);if (ret < 0) {printf("failed to get sys_clk: %d\n", ret);return ret;}dsi2->dev = dev;dsi2->pdata = pdata;dsi2->id = id;dsi2->dual_channel = dev_read_bool(dsi2->dev, "rockchip,dual-channel");dsi2->data_swap = dev_read_bool(dsi2->dev, "rockchip,data-swap");dsi2->auto_calc_mode = dev_read_bool(dsi2->dev, "auto-calculation-mode");

其次是初始化参数的获取,通过dw_mipi_dsi2_get_dsc_params_from_sink接口来实现,从显示设备(sink)中获取 DSC(Display Stream Compression)参数,并将其配置到 MIPI DSI2 设备中:

static int dw_mipi_dsi2_get_dsc_params_from_sink(struct dw_mipi_dsi2 *dsi2)
{struct udevice *dev = dsi2->device->dev;struct rockchip_cmd_header *header;struct drm_dsc_picture_parameter_set *pps = NULL;u8 *dsc_packed_pps;const void *data;int len;dsi2->c_option = dev_read_bool(dev, "phy-c-option");dsi2->scrambling_en = dev_read_bool(dev, "scrambling-enable");dsi2->dsc_enable = dsi2->pdata->dsc ?dev_read_bool(dev, "compressed-data") : false;if (dsi2->slave) {dsi2->slave->c_option = dsi2->c_option;dsi2->slave->scrambling_en = dsi2->scrambling_en;dsi2->slave->dsc_enable = dsi2->dsc_enable;}if (!dsi2->dsc_enable)return 0;dsi2->slice_width = dev_read_u32_default(dev, "slice-width", 0);dsi2->slice_height = dev_read_u32_default(dev, "slice-height", 0);dsi2->version_major = dev_read_u32_default(dev, "version-major", 0);dsi2->version_minor = dev_read_u32_default(dev, "version-minor", 0);data = dev_read_prop(dev, "panel-init-sequence", &len);if (!data)return -EINVAL;while (len > sizeof(*header)) {header = (struct rockchip_cmd_header *)data;data += sizeof(*header);len -= sizeof(*header);if (header->payload_length > len)return -EINVAL;if (header->data_type == MIPI_DSI_PICTURE_PARAMETER_SET) {dsc_packed_pps = calloc(1, header->payload_length);if (!dsc_packed_pps)return -ENOMEM;memcpy(dsc_packed_pps, data, header->payload_length);pps = (struct drm_dsc_picture_parameter_set *)dsc_packed_pps;break;}data += header->payload_length;len -= header->payload_length;}if (!pps) {printf("not found dsc pps definition\n");return -EINVAL;}dsi2->pps = pps;if (dsi2->slave) {u16 pic_width = be16_to_cpu(pps->pic_width) / 2;dsi2->pps->pic_width = cpu_to_be16(pic_width);printf("dsc pic_width change from %d to %d\n", pic_width * 2, pic_width);}return 0;
}

再者,是dw_mipi_dsi2_host_attach、dw_mipi_dsi2_host_ops和dw_mipi_dsi2_bind的作用:

dw_mipi_dsi2_host_attach:用于将 MIPI DSI 设备附加到主机,并验证和保存设备的配置参数:

static int dw_mipi_dsi2_host_attach(struct mipi_dsi_host *host,struct mipi_dsi_device *device)
{struct dw_mipi_dsi2 *dsi2 = dev_get_priv(host->dev);if (device->lanes < 1 || device->lanes > 8)return -EINVAL;dsi2->lanes = device->lanes;dsi2->channel = device->channel;dsi2->format = device->format;dsi2->mode_flags = device->mode_flags;dsi2->device = device;return 0;
}

dw_mipi_dsi2_host_ops定义 MIPI DSI 主机的操作函数,包括附加设备和数据传输:

static const struct mipi_dsi_host_ops dw_mipi_dsi2_host_ops = {.attach = dw_mipi_dsi2_host_attach,.transfer = dw_mipi_dsi2_host_transfer,
};

dw_mipi_dsi2_bind在设备绑定到驱动时执行初始化操作,设置主机的设备指针和操作函数,并扫描设备树以绑定子设备。

其间有一点值得一讲的是,在下于驱动中增添了几个接口,如下展示:

static void rockchip_dsi_external_bridge_power_off(struct dw_mipi_dsi2 *dsi)
{struct gpio_desc *enable_gpio = &dsi->enable_gpio;struct gpio_desc *reset_gpio = &dsi->reset_gpio;dm_gpio_set_value(reset_gpio, 1);dm_gpio_set_value(enable_gpio, 1);printf("uboot GM8775C power off \n");}static void rockchip_dsi_external_bridge_power_on(struct dw_mipi_dsi2 *dsi)
{struct gpio_desc *enable_gpio = &dsi->enable_gpio;struct gpio_desc *reset_gpio = &dsi->reset_gpio;dm_gpio_set_value(enable_gpio, 1);udelay(1000);dm_gpio_set_value(reset_gpio, 1);udelay(1000);printf("uboot GM8775C power on \n");
}......{struct udevice *dev;int ret;ret = uclass_get_device_by_name(UCLASS_DISPLAY, "dsi@27d80000",&dev);if (ret)return ret;ret = gpio_request_by_name(dev, "enable-gpios", 0, &dsi2->enable_gpio,GPIOD_IS_OUT);ret = gpio_request_by_name(dev, "reset-gpios", 0, &dsi2->reset_gpio,GPIOD_IS_OUT);rockchip_dsi_external_bridge_power_on(dsi2);}......

这是过往调试GM8775C芯片时增添的一个补丁,为的则是在u-boot加载阶段也要控制GM8775C转换芯片的上电和复位的时序。

四、结语

由于篇幅过长,暂于u-boot驱动部分结束,作为上篇。后续内容,将继续探讨研究。

五、参考文献

1、Rockchip DRM Display Driver 开发指南(详见SDK Display模块文档)

2、Rockchip_DRM_Panel_Porting_Guide(详见SDK Display模块文档)

3、Rockchip DRM Direct Show 开发指南(详见SDK Display模块文档)

4、Rockchip MIPI DSI2 软件开发指南(详见SDK Display模块文档)

5、显示系统的主流框架:DRM和FB框架_drm框架-CSDN博客

6、Rockchip RK3399 - DRM framebuffer、plane基础知识 - 大奥特曼打小怪兽 - 博客园

7、Framebuffer基础知识(三十)-CSDN博客

8、【Linux应用编程】framebuffer设备应用编程实例_frambuffer例子程序-CSDN博客

9、Linux内核4.14版本——drm框架分析(1)——drm简介_linux drm-CSDN博客

10、【项目原理】DRM驱动概念、组成、框架、源码分析-CSDN博客

11、KMS(Linux内核显示配置管理技术)_百度百科

http://www.dtcms.com/a/340064.html

相关文章:

  • 基于 OpenMV 的矩形识别与 STM32 串口通信(电子设计大赛实用教程)
  • k8s运维实践:高可用Redis Cluster(三主三从)与Proxy部署方案
  • 使用 Docker 安装长安链管理平台 + 部署区块链与示例合约
  • daily notes[3]
  • Eigen中Dense 模块简要介绍和实战应用示例(最小二乘拟合直线、协方差矩阵计算和稀疏求解等)
  • 三极管驱动led灯搭配的电阻选取方法
  • 跟随广州AI导游深度探寻广州历史底蕴​
  • 如何做一次AIMD
  • 农田扫描提速37%!基于检测置信度的无人机“智能抽查”路径规划,Coovally一键加速模型落地
  • [OWASP]智能体应用安全保障指南
  • 英伟达显卡驱动怎么更新 详细步骤教程
  • MySQL练习题50题(附带详细教程)
  • Day13_【DataFrame数据组合concat连接】【案例】
  • C5.5:VDB及后面的电路讨论
  • 决策树(2)
  • Yum使用时报错
  • Spring Boot 全局异常处理
  • 快速了解Anaconda系统
  • 08.5【C++ 初阶】实现一个相对完整的日期类--附带源码
  • implement libtime on Windows
  • MyCAT基础概念
  • Python函数总结
  • week2-[一维数组]最大元素
  • 单细胞格式转换 rds 转成 h5ad
  • transformer模型初理解
  • Transformer、BERT、BEiT等模型相关八股及代码【自用】
  • HJ4 字符串分隔
  • 神经网络训练过程详解
  • 电流采样实现方法
  • JavaScript 代码保护与混淆