程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

Rockchip RK3399 - DRM eDP驱动程序

----------------------------------------------------------------------------------------------------------------------------

开发板 :NanoPC-T4开发板
eMMC16GB
LPDDR34GB
显示屏 :15.6英寸HDMI接口显示屏
u-boot2023.04
linux6.3
----------------------------------------------------------------------------------------------------------------------------

在《Rockchip RK3399 - DRM eDP介绍》我们已经对eDP进行了详细的介绍,本节我们选择DRM eDP驱动程序作为分析的对象。

一、设备树配置

1.1 eDP设备节点

eDP驱动位于:

  • drivers/gpu/drm/rockchip/analogix_dp-rockchip.c
  • drivers/gpu/drm/bridge/analogix/analogix_dp_core.c
  • drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c
  • drivers/phy/rockchip/phy-rockchip-dp.c

设备节点的配置参考文档 :

  • Documentation/devicetree/bindings/display/bridge/analogix_dp.txt
  • Documentation/devicetree/bindings/display/rockchip/analogix_dp-rockchip.txt

设备节点vopb下的子节点vopb_out_edp通过edp_in_vopb(由remote-endpoint属性指定)和eDP显示接口组成一个连接通路;

设备节点vopl下的子节点vopl_out_edp通过edp_in_vopl(由remote-endpoint属性指定)和eDP显示接口组成一个连接通路;

arch/arm64/boot/dts/rockchip/rk3399.dtsi定义有edp设备节点:

edp: edp@ff970000 {
		compatible = "rockchip,rk3399-edp";
		reg = <0x0 0xff970000 0x0 0x8000>;
		interrupts = <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH 0>;
		clocks = <&cru PCLK_EDP>, <&cru PCLK_EDP_CTRL>, <&cru PCLK_VIO_GRF>;
		clock-names = "dp", "pclk", "grf";
		pinctrl-names = "default";
		pinctrl-0 = <&edp_hpd>;
		power-domains = <&power RK3399_PD_EDP>;
		resets = <&cru SRST_P_EDP_CTRL>;
		reset-names = "dp";
		rockchip,grf = <&grf>;
		status = "disabled";

		ports {
				#address-cells = <1>;
				#size-cells = <0>;
				edp_in: port@0 {
						reg = <0>;
						#address-cells = <1>;
						#size-cells = <0>;

						edp_in_vopb: endpoint@0 {
								reg = <0>;
								remote-endpoint = <&vopb_out_edp>;
						};

						edp_in_vopl: endpoint@1 {
								reg = <1>;
								remote-endpoint = <&vopl_out_edp>;
						};
				};
		};
};

可参考:Documentation/devicetree/bindings/display/rockchip/analogix_dp-rockchip.txt

1.1.1 子节点ports

子节点ports包含2个input endpoint,分别连接到VOPLVOPB;也就是在rk3399上,eDP可以和VOPLVOPB连接;

因此可以得到有2条通路:

  • vopb_out_edp ---> edp_in_vopb
  • vopl_out_edp ---> edp_in_vopl

需要注意的是,⼀个显⽰接口在同⼀个时刻只能和⼀个VOP连接,所以在具体的板级配置中,需要设备树中把要使⽤的通路打开,把不使⽤的通路设置为disabled状态。

1.1.2 interrupts

interrupts 属性的值为 <GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH 0>,含义如下:

  • GIC_SPI: 中断控制器的类型,这里指的是ARM通用中断控制器 (GIC);
  • 10: 中断的编号,这里是第10个中断。
  • IRQ_TYPE_LEVEL_HIGH: 中断的触发方式,这里是高电平触发;
  • 0: 附加参数,这里没有使用;
1.1.3 pinctrl-0

定义了eDP设备热插拔引脚的默认状态pinctrl-0 = <&edp_hpd>edp_hpd定义在arch/arm64/boot/dts/rockchip/rk3399.dtsi

pinctrl: pinctrl {
		......
		edp {
				edp_hpd: edp-hpd {
						rockchip,pins =
								<4 RK_PC7 2 &pcfg_pull_none>;
				};
		};
}

这里将GPIO4_C7引脚功能配置为功能2,实际上NanoPC-T4开发板eDP接线中并没有使用HDP引脚进行热插拔检测。

如果我想开启双屏,比如同时支持HDMIeDP,由于GPIO4_C7同时被HDMIeDP引用了,因此需要修改arch/arm64/boot/dts/rockchip/rk3399-evb.dtsi屏蔽掉 pinctrl-0 = <&hdmi_cec>;

&hdmi {
        ddc-i2c-bus = <&i2c7>;
        # pinctrl-names = "default"; 删除
        # pinctrl-0 = <&hdmi_cec>;  删除
        status = "okay";
};
1.1.4 resets

resets = <&cru SRST_P_EDP_CTRL>

1.2 HD702E触摸显示屏配置

1.2.1 backlight

PWM驱动位于drivers/video/backlight/pwm_bl.c,设备节点的配置参考文档 :

  • Documentation/devicetree/bindings/leds/backlight/pwm-backlight.yaml

arch/arm64/boot/dts/rockchip/rk3399-evb.dts中新增触摸显示屏背光配置设备节点,如下:

backlight: backlight {
		compatible = "pwm-backlight";
		brightness-levels = <
				  0   1   2   3   4   5   6   7
				  8   9  10  11  12  13  14  15
				 16  17  18  19  20  21  22  23
				 24  25  26  27  28  29  30  31
				 32  33  34  35  36  37  38  39
				 40  41  42  43  44  45  46  47
				 48  49  50  51  52  53  54  55
				 56  57  58  59  60  61  62  63
				 64  65  66  67  68  69  70  71
				 72  73  74  75  76  77  78  79
				 80  81  82  83  84  85  86  87
				 88  89  90  91  92  93  94  95
				 96  97  98  99 100 101 102 103
				104 105 106 107 108 109 110 111
				112 113 114 115 116 117 118 119
				120 121 122 123 124 125 126 127
				128 129 130 131 132 133 134 135
				136 137 138 139 140 141 142 143
				144 145 146 147 148 149 150 151
				152 153 154 155 156 157 158 159
				160 161 162 163 164 165 166 167
				168 169 170 171 172 173 174 175
				176 177 178 179 180 181 182 183
				184 185 186 187 188 189 190 191
				192 193 194 195 196 197 198 199
				200 201 202 203 204 205 206 207
				208 209 210 211 212 213 214 215
				216 217 218 219 220 221 222 223
				224 225 226 227 228 229 230 231
				232 233 234 235 236 237 238 239
				240 241 242 243 244 245 246 247
				248 249 250 251 252 253 254 255>;
		default-brightness-level = <200>;
		pwms = <&pwm0 0 25000 0>;
		status = "okay";
		enable-gpios = <&gpio4 RK_PD5 GPIO_ACTIVE_HIGH>;
};

其中:

  • pwms:配置PWM,这里指定为pwmphandle,后面有三个参数:
    • 参数1:表示index (per-chip index of the PWM to request),一般是0,因为Rockchip PWM每个
      chip只有一个;
    • 参数2:表示 PWM输出波形的时间周期,单位是ns;上面我们配置的25000就是表示想要得到的
      PWM输出周期是40KHz
    • 参数3:表示极性,为可选参数;
      • 配置为0,那么cooling-levels描述的是高电平的占空比;
      • 配置为PWM_POLARITY_INVERTED,那么cooling-levels描述的是低电平的占空比;
  • brightness-levels属性:配置背光亮度数组,一般以值255为一个scale
    • PWM设置为正极性时,从0~255表示背光为正极,占空比从0%~100%变化,255~0为负极性,占空比从100%~0%变化;
    • PWM设置为负极性时,反之;
  • default-brightness-level属性:开机时默认背光亮度,范围为0-255;
  • enable-gpios:背光使能脚,高电平有效,连接RK3399GPIO4_D5引脚,通过GPIO控制背光开关;
1.2.2 启用pwm0

PWM驱动位于drivers/pwm/pwm-rockchip.c,设备节点的配置参考文档 :

  • Documentation/devicetree/bindings/pwm/pwm.yaml
  • Documentation/devicetree/bindings/pwm/pwm.txt

arch/arm64/boot/dts/rockchip/rk3399-evb.dts中启用pwm0设备节点;

&pwm0 {
	status = "okay";
};

pwm0定义在arch/arm64/boot/dts/rockchip/rk3399.dtsi文件;

pwm0: pwm@ff420000 {
		compatible = "rockchip,rk3399-pwm", "rockchip,rk3288-pwm";
		reg = <0x0 0xff420000 0x0 0x10>;
		#pwm-cells = <3>;
		pinctrl-names = "default";
		pinctrl-0 = <&pwm0_pin>;
		clocks = <&pmucru PCLK_RKPWM_PMU>;
		status = "disabled";
};

pinctrl: pinctrl {
		......
		pwm0 {
				pwm0_pin: pwm0-pin {
						rockchip,pins =
								<4 RK_PC2 1 &pcfg_pull_none>;
				};

				pwm0_pin_pull_down: pwm0-pin-pull-down {
						rockchip,pins =
								<4 RK_PC2 1 &pcfg_pull_down>;
				};

				vop0_pwm_pin: vop0-pwm-pin {
						rockchip,pins =
								<4 RK_PC2 2 &pcfg_pull_none>;
				};

				vop1_pwm_pin: vop1-pwm-pin {
						rockchip,pins =
								<4 RK_PC2 3 &pcfg_pull_none>;
				};
		};
}

由于eDP接口PWM0_BL引脚接RK3399 GPIO4_C2/PWM0/VOP0_PWM/VOP1_PWM,这里配置GPIO4_C2引脚功能复用为pwm0_pin

具体可以参考GRF_GPIO4C_IOMUX寄存器配置:

更多PWM驱动相关内容参考:《Rockchip RK3399 - GPIO&PWM风扇调试》。

1.2.3 edp_panel

panel驱动位于drivers/gpu/drm/panel/panel-simple.c,设备节点的配置参考文档 :

  • Documentation/devicetree/bindings/display/panel/panel-simple.yaml

arch/arm64/boot/dts/rockchip/rk3399-evb.dts中新增edp_panel配置,如下:

edp_panel: edp-panel {		
	    compatible = "chunghwa,claa070wp03xg";
        status = "okay";
    
		backlight = <&backlight>;
		power-supply = <&vcc3v3_s0>;
        prepare-delay-ms = <20>;
        enable-delay-ms = <20>;

		port {
				panel_in_edp: endpoint {
						remote-endpoint = <&edp_out_panel>;
				};
		};
};

如果你参考官方提供的linux 5.10内核,你会发现compatible配置为panel-simple,这里我们为啥没有配置为simple-panel,主要是因为在最新的内核文件panel-simple.c中不再支持simple-panel

由于我们使用的友善7寸高清电容触摸显示屏(型号为HD702E)分辨率为800x1280,屏幕大小94.2(H)x150.72(V) (mm) (7-inch diagonal),推荐像素时钟频率为66.77MHz,因此我们搜索panel-simple.c文件,查找vdisplay = 1280字符串,可以定位到匹配的时序配置chunghwa_claa070wp03xg_mode

static const struct drm_display_mode chunghwa_claa070wp03xg_mode = {
        .clock = 66770,
        .hdisplay = 800,
        .hsync_start = 800 + 49,
        .hsync_end = 800 + 49 + 33,
        .htotal = 800 + 49 + 33 + 17,
        .vdisplay = 1280,
        .vsync_start = 1280 + 1,
        .vsync_end = 1280 + 1 + 7,
        .vtotal = 1280 + 1 + 7 + 15,
        .flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
};

static const struct panel_desc chunghwa_claa070wp03xg = {
        .modes = &chunghwa_claa070wp03xg_mode,
        .num_modes = 1,
        .bpc = 6,
        .size = {
                .width = 94,
                .height = 150,
        },
        .bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,
        .bus_flags = DRM_BUS_FLAG_DE_HIGH,
        .connector_type = DRM_MODE_CONNECTOR_LVDS,
};

static const struct of_device_id platform_of_match[] = {
    [...]
    }, {
      .compatible = "chunghwa,claa070wp03xg",
      .data = &chunghwa_claa070wp03xg,
    }, {
    [...]
}
1.2.4 gt9xx

HD702E友善7寸高清电容触摸显示屏搭载的触摸IC型号为GT9271GT9271驱动位于drivers/input/touchscreen/goodix.c,设备节点的配置参考文档 :

  • Documentation/devicetree/bindings/input/touchscreen/goodix.yaml

因此需要在arch/arm64/boot/dts/rockchip/rk3399-evb.dts中新增gt9xx设备节点,如下:

&i2c4 {
        status = "okay";
		......
        gt9xx: goodix_ts@5d {
                compatible = "goodix,gt9271"";
                reg = <0x5d>;
                interrupt-parent = <&gpio1>;
                interrupts = <RK_PC4 IRQ_TYPE_EDGE_FALLING>;
                AVDD28-supply = <&vcc3v3_sys>;
                VDDIO-supply = <&vcc3v3_sys>;
                irq-gpios = <&gpio1 RK_PC4 GPIO_ACTIVE_HIGH>;
                reset-gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_LOW>;
                status = "okay";
        };
}

AVDD28-supplyAVDD28引脚的模拟电源供应;

VDDIO-supplyVDDIO引脚的GPIO电源供应;

eDP接口的GPIO1_C4_TP_INT连接到RK3399GPIO1_C4引脚,默认高电平,发生触摸后该引脚会发送一个低电平的脉冲信号。因此中断配置为:

interrupt-parent = <&gpio1>;
interrupts = <RK_PC4 IRQ_TYPE_EDGE_FALLING>;
irq-gpios = <&gpio1 RK_PC4 GPIO_ACTIVE_HIGH>;

eDP接口的GPIO1_B5_TP_RST连接到RK3399GPIO1_B5引脚,触摸面板的复位引脚。因此reset-gpios配置为:

reset-gpios = <&gpio1 RK_PB5 GPIO_ACTIVE_HIGH>;

1.3 电源配置

eDP接口使用到得电源有VCC12V0_SYSVCC3V3_SYSVCC0V9_S3,对应得设备节点均定义在arch/arm64/boot/dts/rockchip/rk3399-evb.dts

1.3.1 VCC12V0_SYS
vcc12v0_sys: vcc12v0-sys {
		compatible = "regulator-fixed";
		regulator-always-on;
		regulator-boot-on;
		regulator-max-microvolt = <12000000>;
		regulator-min-microvolt = <12000000>;
		regulator-name = "vcc12v0_sys";
};
1.3.2 VCC3V3_SYS
vcc3v3_sys: vcc3v3-sys {
		compatible = "regulator-fixed";
		regulator-name = "vcc3v3_sys";
		regulator-always-on;
		regulator-boot-on;
		regulator-min-microvolt = <3300000>;
		regulator-max-microvolt = <3300000>;
		vin-supply = <&vcc12v0_sys>;
};
1.3.2 VCC0V9_S3
/*
 * Really, this is supplied by vcc_1v8, and vcc1v8_s3 only
 * drives the enable pin, but we can't quite model that.
 */
vcca0v9_s3: vcca0v9-s3 {
		compatible = "regulator-fixed";
		regulator-min-microvolt = <900000>;
		regulator-max-microvolt = <900000>;
		regulator-name = "vcca0v9_s3";
		vin-supply = <&vcc1v8_s3>;
};

1.4 启用eDP

由于我们之前已经将hdmi连接在vopb上,因此在这里我们希望eDP连接在vopl上,则需要在arch/arm64/boot/dts/rockchip/rk3399-evb.dts中为以下节点新增属性:

&display_subsystem {
         status = "okay";
};

&vopl {
        status = "okay";
};

&vopl_mmu {
        status = "okay";
};
     
&edp {     
        status = "okay";
        force-hpd;

        ports {
                edp_out: port@1 {
                        reg = <1>;
                        #address-cells = <1>;
                        #size-cells = <0>;

                        edp_out_panel: endpoint@0 {
                                reg = <0>;
                                remote-endpoint = <&panel_in_edp>;
                        };
                };
        };
};

&edp_in_vopl{
        status = "okay";
};

&edp_in_vopb{
        status = "disabled";
};

对于Embedded Connection,一般不需要HPD功能,需要加上force-hpd属性。

此外,edp新增1个output endpoint,连接到了epd panel上,因此可以得到1条通路:

  • edp_out_panel ---> panel_in_edp

二、Platform驱动

2.1 模块入口函数

drivers/gpu/drm/rockchip/rockchip_drm_drv.c文件rockchip_drm_init函数:

static int __init rockchip_drm_init(void)
{
        int ret;


        if (drm_firmware_drivers_only())
                return -ENODEV;

    	// 1. 根据配置来决定是否添加xxx_xxx_driver到数组rockchip_sub_drivers
        num_rockchip_sub_drivers = 0;
        ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,CONFIG_ROCKCHIP_ANALOGIX_DP);
        ......

		// 2. 注册多个platform driver    
        ret = platform_register_drivers(rockchip_sub_drivers,
                                        num_rockchip_sub_drivers);
        if (ret)
                return ret;

    	// 3. 注册rockchip_drm_platform_driver
        ret = platform_driver_register(&rockchip_drm_platform_driver);
        if (ret)
                goto err_unreg_drivers;

        return 0;
        ......
}

其中:

ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,CONFIG_ROCKCHIP_ANALOGIX_DP);

会将rockchip_dp_driver保存到rockchip_sub_drivers数组中。

并调用platform_register_drivers遍历rockchip_sub_drivers数组,多次调用platform_driver_register注册platform driver

2.2 rockchip_dp_driver

rockchip_dp_driver定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c

struct platform_driver rockchip_dp_driver = {
        .probe = rockchip_dp_probe,
        .remove = rockchip_dp_remove,
        .driver = {
                   .name = "rockchip-dp",
                   .pm = &rockchip_dp_pm_ops,
                   .of_match_table = rockchip_dp_dt_ids,  // 用于设备树匹配
        },
};

其中of_match_table用于设备树匹配,匹配设备树中compatible = "rockchip,rk3399-edp"的设备节点,即edp设备节点;

static const struct of_device_id rockchip_dp_dt_ids[] = {
        {.compatible = "rockchip,rk3288-dp", .data = &rk3288_dp },
        {.compatible = "rockchip,rk3399-edp", .data = &rk3399_edp },
        {}
};

2.3 rockchip_dp_probe

plaform总线设备驱动模型中,我们知道当内核中有platform设备和platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是rockchip_dp_probe函数;

static const struct rockchip_dp_chip_data rk3399_edp = {
        .lcdsel_grf_reg = RK3399_GRF_SOC_CON20,
        .lcdsel_big = HIWORD_UPDATE(0, RK3399_EDP_LCDC_SEL),
        .lcdsel_lit = HIWORD_UPDATE(RK3399_EDP_LCDC_SEL, RK3399_EDP_LCDC_SEL),
        .chip_type = RK3399_EDP,
};

static const struct component_ops rockchip_dp_component_ops = {
        .bind = rockchip_dp_bind,
        .unbind = rockchip_dp_unbind,
};

static int rockchip_dp_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        const struct rockchip_dp_chip_data *dp_data;
        struct drm_panel *panel = NULL;
        struct rockchip_dp_device *dp;
        int ret;

    	// 获取配置数据,这里是rk3399_edp
        dp_data = of_device_get_match_data(dev);
        if (!dp_data)
                return -ENODEV;

    	// 基于设备树中的信息,获取connector连接的panel
        ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, NULL);
        if (ret < 0)
                return ret;

    	// 动态分配数据结构,并进行成员初始化
        dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
        if (!dp)
                return -ENOMEM;

        dp->dev = dev;
        dp->adp = ERR_PTR(-ENODEV);
    	// 保存Rockchip平台数据
        dp->data = dp_data;
        dp->plat_data.panel = panel;
        // 此处设置为RK3399_EDP
        dp->plat_data.dev_type = dp->data->chip_type;
        dp->plat_data.power_on_start = rockchip_dp_poweron_start;
        dp->plat_data.power_off = rockchip_dp_powerdown;
        dp->plat_data.get_modes = rockchip_dp_get_modes;

        // Rockchip平台eDP设备探测函数,主要是初始化时钟成员
        ret = rockchip_dp_of_probe(dp);
        if (ret < 0)
                return ret;

    	// 设置驱动私有数据 pdev->dev.driver_data = dp
        platform_set_drvdata(pdev, dp);

    	// 初始化edp接口
        dp->adp = analogix_dp_probe(dev, &dp->plat_data);
        if (IS_ERR(dp->adp))
                return PTR_ERR(dp->adp);

    	// 为设备dev向系统注册一个component
        ret = component_add(dev, &rockchip_dp_component_ops);
        if (ret)
                goto err_dp_remove;

        return 0;

err_dp_remove:
        analogix_dp_remove(dp->adp);
        return ret;
}

rockchip_dp_probe函数的代码虽然看着那么多,实际上主要就做了以下几件事;

  • 调用of_device_get_match_data根据设备树获取匹配配置数据,这里是rk3399_edp
  • 调用drm_of_find_panel_or_bridge根据设备树中的信息,找到可能连接的面板或桥接器;
  • 调用devm_kzalloc在内核堆中动态分配内存,用于存储eDP设备相关的数据结构 rockchip_dp_device,接下来实对其成员的初始化;
  • 调用rockchip_dp_of_probe,即执行Rockchip eDP的设备特定探测函数,进一步初始化eDP设备;
  • 调用platform_set_drvdata设置驱动私有数据pdev->dev.driver_data = dp
  • 调用component_add为设备dev向系统注册一个component,其中组件可执行的初始化操作被设置为了rockchip_dp_component_ops
2.3.1 drm_of_find_panel_or_bridge

drm_of_find_panel_or_bridge函数定义在drivers/gpu/drm/drm_of.c;用于在设备树节点中查找与特定端口和端点关联的 面板或桥接器设备。它可以返回与指定的端口和端点关联的 drm_paneldrm_bridge 设备。

/**
 * drm_of_find_panel_or_bridge - return connected panel or bridge device
 * @np: device tree node containing encoder output ports
 * @port: port in the device tree node
 * @endpoint: endpoint in the device tree node
 * @panel: pointer to hold returned drm_panel
 * @bridge: pointer to hold returned drm_bridge
 *
 * Given a DT node's port and endpoint number, find the connected node and
 * return either the associated struct drm_panel or drm_bridge device. Either
 * @panel or @bridge must not be NULL.
 *
 * This function is deprecated and should not be used in new drivers. Use
 * devm_drm_of_get_bridge() instead.
 *
 * Returns zero if successful, or one of the standard error codes if it fails.
 */
int drm_of_find_panel_or_bridge(const struct device_node *np,  // 传入edp设备节点
                                int port, int endpoint,   // 1 0
                                struct drm_panel **panel,  // 传入&panel
                                struct drm_bridge **bridge)   // null
{
        int ret = -EPROBE_DEFER;
        struct device_node *remote;

        if (!panel && !bridge)
                return -EINVAL;
        if (panel)
                *panel = NULL;

        /*
         * of_graph_get_remote_node() produces a noisy error message if port
         * node isn't found and the absence of the port is a legit case here,
         * so at first we silently check whether graph presents in the
         * device-tree node. 
         * 检查设备节点np是否有port或者ports子节点,由于edp设备节点有子节点ports很显然成立
         */
        if (!of_graph_is_present(np))
                return -ENODEV;
    
        // 首先获取edp设备节点下port=1、endpoint=0的endpoint设备节点,这里匹配到的设备节点为edp_out_panel;然后获取该节点remote-endpoint属性指定的设备节点,即panel_in_edp; 
        remote = of_graph_get_remote_node(np, port, endpoint);
        if (!remote)
                return -ENODEV;

    	// 如果指定了panel,即查找panel
        if (panel) {
            	// 根据remote设备节点获取panel
                *panel = of_drm_find_panel(remote);
                if (!IS_ERR(*panel))
                        ret = 0;
                else
                        *panel = NULL;
        }

        /* No panel found yet, check for a bridge next. */
        if (bridge) {
                if (ret) {
                        *bridge = of_drm_find_bridge(remote);
                        if (*bridge)
                                ret = 0;
                } else {
                        *bridge = NULL;
                }

        }

        of_node_put(remote);
        return ret;
}

of_graph_get_remote_node在本节最后单独分析,其目的是通过获取edp设备节点下port=1、endpoint=0endpoint设备节点,这里匹配到的设备节点为edp_out_panel;然后获取该节点remote-endpoint属性指定的设备节点,即panel_in_edp

从而可以调用of_drm_find_panel函数根据panel设备节点获取到对应的panel对象,of_drm_find_panel函数定义在drivers/gpu/drm/drm_panel.c

/**
 * of_drm_find_panel - look up a panel using a device tree node
 * @np: device tree node of the panel
 *
 * Searches the set of registered panels for one that matches the given device
 * tree node. If a matching panel is found, return a pointer to it.
 *
 * Return: A pointer to the panel registered for the specified device tree
 * node or an ERR_PTR() if no panel matching the device tree node can be found.
 *
 * Possible error codes returned by this function:
 *
 * - EPROBE_DEFER: the panel device has not been probed yet, and the caller
 *   should retry later
 * - ENODEV: the device is not available (status != "okay" or "ok")
 */
struct drm_panel *of_drm_find_panel(const struct device_node *np)
{
        struct drm_panel *panel;

        if (!of_device_is_available(np))
                return ERR_PTR(-ENODEV);

        mutex_lock(&panel_lock);

    	// 遍历链表panel_list,查找设备节点为np的panel
        list_for_each_entry(panel, &panel_list, list) {
                if (panel->dev->of_node == np) {
                        mutex_unlock(&panel_lock);
                        return panel;
                }
        }

        mutex_unlock(&panel_lock);
        return ERR_PTR(-EPROBE_DEFER);
}
2.3.2 rockchip_dp_poweron_start

rockchip_dp_poweron_start函数定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c

static int rockchip_dp_pre_init(struct rockchip_dp_device *dp)
{
        reset_control_assert(dp->rst);
        usleep_range(10, 20);
        reset_control_deassert(dp->rst);

        return 0;
}

static int rockchip_dp_poweron_start(struct analogix_dp_plat_data *plat_data)
{
        struct rockchip_dp_device *dp = pdata_encoder_to_dp(plat_data);
        int ret;
		// 准备和使能pclk时钟
        ret = clk_prepare_enable(dp->pclk);
        if (ret < 0) {
                DRM_DEV_ERROR(dp->dev, "failed to enable pclk %d\n", ret);
                return ret;
        }

    	// 预初始化
        ret = rockchip_dp_pre_init(dp);
        if (ret < 0) {
                DRM_DEV_ERROR(dp->dev, "failed to dp pre init %d\n", ret);
                clk_disable_unprepare(dp->pclk);
                return ret;
        }

        return ret;
}
2.3.3 rockchip_dp_powerdown

rockchip_dp_powerdown函数定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c

static int rockchip_dp_powerdown(struct analogix_dp_plat_data *plat_data)
{
        struct rockchip_dp_device *dp = pdata_encoder_to_dp(plat_data);

    	// 禁用并关闭pclk时钟
        clk_disable_unprepare(dp->pclk);

        return 0;
}
2.3.4 rockchip_dp_get_modes

rockchip_dp_get_modes函数定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c, 用于获取connector的显示模式;

static int rockchip_dp_get_modes(struct analogix_dp_plat_data *plat_data,
                                 struct drm_connector *connector)
{
        struct drm_display_info *di = &connector->display_info;
        /* VOP couldn't output YUV video format for eDP rightly */
        u32 mask = DRM_COLOR_FORMAT_YCBCR444 | DRM_COLOR_FORMAT_YCBCR422;

    	// 如果颜色格式设置了DRM_COLOR_FORMAT_YCBCR444、DRM_COLOR_FORMAT_YCBCR422
        if ((di->color_formats & mask)) {
                DRM_DEBUG_KMS("Swapping display color format from YUV to RGB\n");
                di->color_formats &= ~mask;             // 屏蔽该颜色模式
                di->color_formats |= DRM_COLOR_FORMAT_RGB444;
                di->bpc = 8;
        }

        return 0;
}
2.3.5 rockchip_dp_of_probe

rockchip_dp_get_modes函数定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c

static int rockchip_dp_of_probe(struct rockchip_dp_device *dp)
{
        struct device *dev = dp->dev;
        struct device_node *np = dev->of_node;

      // 根据设备树节点中的rockchip,grf属性获取与GRF相关的寄存器映射  rockchip,grf = <&grf>;
        dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
        if (IS_ERR(dp->grf)) {
                DRM_DEV_ERROR(dev, "failed to get rockchip,grf property\n");
                return PTR_ERR(dp->grf);
        }

       // 获取grf相关的时钟 <&cru PCLK_VIO_GRF>
        dp->grfclk = devm_clk_get(dev, "grf");
        if (PTR_ERR(dp->grfclk) == -ENOENT) {
                dp->grfclk = NULL;
        } else if (PTR_ERR(dp->grfclk) == -EPROBE_DEFER) {
                return -EPROBE_DEFER;
        } else if (IS_ERR(dp->grfclk)) {
                DRM_DEV_ERROR(dev, "failed to get grf clock\n");
                return PTR_ERR(dp->grfclk);
        }

    	// 获取pclk相关的时钟 <&cru PCLK_EDP_CTRL>
        dp->pclk = devm_clk_get(dev, "pclk");
        if (IS_ERR(dp->pclk)) {
                DRM_DEV_ERROR(dev, "failed to get pclk property\n");
                return PTR_ERR(dp->pclk);
        }

    	// 复位控制 <&cru SRST_P_EDP_CTRL>
        dp->rst = devm_reset_control_get(dev, "dp");
        if (IS_ERR(dp->rst)) {
                DRM_DEV_ERROR(dev, "failed to get dp reset control\n");
                return PTR_ERR(dp->rst);
        }

        return 0;
}

2.4 of_graph_get_remote_node

of_graph_get_remote_node定义在drivers/of/property.c,函数第1个传入的是edp设备节点,第2个参数port传入1,第3个参数reg传入0。

这段代码首先获取edp设备节点下port=1endpoint=0endpoint设备节点,这里匹配到的设备节点为edp_out_panel

然后获取该节点remote-endpoint属性指定的设备节点,即panel_in_edp

/**
 * of_graph_get_remote_node() - get remote parent device_node for given port/endpoint
 * @node: pointer to parent device_node containing graph port/endpoint
 * @port: identifier (value of reg property) of the parent port node
 * @endpoint: identifier (value of reg property) of the endpoint node
 *
 * Return: Remote device node associated with remote endpoint node linked
 * to @node. Use of_node_put() on it when done.
 */
struct device_node *of_graph_get_remote_node(const struct device_node *node,  // 传入edp设备节点
                                             u32 port, u32 endpoint)  // 1 0
{
        struct device_node *endpoint_node, *remote;

    	// 通过遍历node设备节点的所有端点节点来查找符合指定port=1和endpoint=0的端点节点,这里返回的是edp_out_panel设备节点
        endpoint_node = of_graph_get_endpoint_by_regs(node, port, endpoint);
        if (!endpoint_node) {
                pr_debug("no valid endpoint (%d, %d) for node %pOF\n",
                         port, endpoint, node);
                return NULL;
        }

    	// 首先获取endpoint_node设备节点remote-endpoin属性指定的设备节点,即panel_in_edp设备节点,并向上查找父设备节点,直至找到edp_panel设备节点
        remote = of_graph_get_remote_port_parent(endpoint_node);
        of_node_put(endpoint_node);
        if (!remote) {
                pr_debug("no valid remote node\n");
                return NULL;
        }

        // 判断这个设备节点是否处于启用状态 即status = "ok"
        if (!of_device_is_available(remote)) {
                pr_debug("not available for remote node\n");
                of_node_put(remote);
                return NULL;
        }

        return remote;
}
2.4.1 of_graph_get_endpoint_by_regs

of_graph_get_endpoint_by_regs定义在drivers/of/property.c,其作用就是通过遍历parent设备节点的所有端点节点来查找符合指定 port_regreg 标识符的端点节点;

/**
 * of_graph_get_endpoint_by_regs() - get endpoint node of specific identifiers
 * @parent: pointer to the parent device node
 * @port_reg: identifier (value of reg property) of the parent port node
 * @reg: identifier (value of reg property) of the endpoint node
 *
 * Return: An 'endpoint' node pointer which is identified by reg and at the same
 * is the child of a port node identified by port_reg. reg and port_reg are
 * ignored when they are -1. Use of_node_put() on the pointer when done.
 */
struct device_node *of_graph_get_endpoint_by_regs(
        const struct device_node *parent, int port_reg, int reg)  // 传入edp设备节点 1 0
{
        struct of_endpoint endpoint;
        struct device_node *node = NULL;
		// 遍历parent结点下的每个endpoint结点
        for_each_endpoint_of_node(parent, node) {
            	// 解析端点的信息,并将结果存储在endpoint
                of_graph_parse_endpoint(node, &endpoint);
            	// 对比传入的port_reg和reg参数与当前端点节点的属性值,如果匹配则返回该端点节点的指针
                if (((port_reg == -1) || (endpoint.port == port_reg)) &&
                        ((reg == -1) || (endpoint.id == reg)))
                        return node;
        }

        return NULL;
}

比如我们的edp节点,当调用of_graph_get_endpoint_by_regs(node,1,0)首先查找reg=1port,即edp_out,然后查找reg=0的端点节点,也就是edp_out_panel设备节点;

edp: edp@ff970000 {
		......
		ports { 
				#address-cells = <1>;
				#size-cells = <0>;
				edp_in: port@0 {               # 该节点和port_reg=0匹配, 节点属性reg的值赋值给endpoint.port
						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>;
						};
				};
				
                edp_out: port@1 {            # 该节点和port_reg=1匹配, 节点属性reg的值赋值给endpoint.port
                        reg = <1>;
                        #address-cells = <1>;
                        #size-cells = <0>;

                        edp_out_panel: endpoint@0 {    # 属性reg的值赋值给endpoint.id
                                reg = <0>;
                                remote-endpoint = <&panel_in_edp>;
                        };
                };
		};
};
2.4.2 of_graph_parse_endpoint

of_graph_parse_endpoint定义在drivers/of/property.c,函数的作用是解析端点节点node的属性,并将解析结果存储到 endpoint中;

/**
 * of_graph_parse_endpoint() - parse common endpoint node properties
 * @node: pointer to endpoint device_node
 * @endpoint: pointer to the OF endpoint data structure
 *
 * The caller should hold a reference to @node.
 */
int of_graph_parse_endpoint(const struct device_node *node,
                            struct of_endpoint *endpoint)
{
    	// endpoint的父结点是port结点
        struct device_node *port_node = of_get_parent(node);

        WARN_ONCE(!port_node, "%s(): endpoint %pOF has no parent node\n",
                  __func__, node);

    	// 填充0
        memset(endpoint, 0, sizeof(*endpoint));

    	// 设置endpoint所属的port节点
        endpoint->local_node = node;
        /*
         * It doesn't matter whether the two calls below succeed.
         * If they don't then the default value 0 is used.         
         * port结点下的reg属性值是endpoint->port值
         * endpoint节点reg属性值是endpoint->id值
         */
        of_property_read_u32(port_node, "reg", &endpoint->port);
        of_property_read_u32(node, "reg", &endpoint->id);

        of_node_put(port_node);

        return 0;
}

edp_out_panel设备节点为例:

 edp_out: port@1 {     
     reg = <1>;           # 赋值给endpoint->port
     .....
	 edp_out_panel: endpoint@0  {
    	reg = <0>;        # 赋值给endpoint->id 
	    remote-endpoint = <&panel_in_edp>;
	};
 }

经过of_graph_parse_endpoint函数处理后:

  • endpoint->id = 0
  • endpoint->port= 1

三、edp相关数据结构

edp相关的数据结构分为两部分:

  • Analogix dp相关驱动定义:比如struct analogix_dp_devicestruct analogix_dp_plat_data
  • Rochchip edp相关驱动定义:比如struct rockchip_dp_devicestruct rockchip_dp_chip_data

3.1 Analogix dp

Analogix dp驱动相关代码位于drivers/gpu/drm/bridge/analogix目录;

root@zhengyang:/work/sambashare/rk3399/linux-6.3# ll drivers/gpu/drm/bridge/analogix_dp_*
-rw-rw-r-- 1 root root  48467 4月  24  2023 analogix_dp_core.c
-rw-rw-r-- 1 root root   7644 4月  24  2023 analogix_dp_core.h
-rw-rw-r-- 1 root root  30782 4月  24  2023 analogix_dp_reg.c
-rw-rw-r-- 1 root root  13200 4月  24  2023 analogix_dp_reg.h
3.1.1 struct analogix_dp_device

struct analogix_dp_device定义在drivers/gpu/drm/bridge/analogix/analogix_dp_core.h;

struct analogix_dp_device {
        struct drm_encoder      *encoder;
        struct device           *dev;
        struct drm_device       *drm_dev;
        struct drm_connector    connector;
        struct drm_bridge       *bridge;
        struct drm_dp_aux       aux;
        struct clk              *clock;
        unsigned int            irq;
        void __iomem            *reg_base;

        struct video_info       video_info;
        struct link_train       link_train;
        struct phy              *phy;
        int                     dpms_mode;
        struct gpio_desc        *hpd_gpiod;
        bool                    force_hpd;
        bool                    fast_train_enable;
        bool                    psr_supported;

        struct mutex            panel_lock;
        bool                    panel_is_modeset;

        struct analogix_dp_plat_data *plat_data;
};

其中:

  • encoderdrm_encoder
  • devedp设备;
  • connectordrm_connector
  • drm_devdrm设备;
  • bridge:桥接设备,一般用于注册encoder后面另外再接的转换芯片;
  • auxedp辅助通道结构;
  • clkedp设备时钟;
  • irqedp中断编号;
  • reg_baseedp相关寄存器基址的虚拟地址;
  • video_info:视频信息结构;
  • link_train:链路训练信息结构;
  • phy::指向eDP PHYstruct phy *phy指针;
  • dpms_mode:显示电源管理模式;
  • hpd_gpiod:指向GPIO描述符的指针,用于表示edp设备的热插拔检测GPIO
  • force_hpd:是否强制启用HPD
  • fast_train_enable:是否启用快速链路训练;
  • psr_supported:是否支持部分屏幕刷新(PSR)功能;
  • panel_lock:用于保护edp设备面板状态的互斥锁;
  • panel_is_modesetedp设备面板是否已经进行了模式设置;
  • plat_dataedp平台数据;
3.1.2 struct analogix_dp_plat_data

struct analogix_dp_plat_data定义在include/drm/bridge/analogix_dp.h;

struct analogix_dp_plat_data {
        enum analogix_dp_devtype dev_type;
        struct drm_panel *panel;
        struct drm_encoder *encoder;
        struct drm_connector *connector;
        bool skip_connector;

        int (*power_on_start)(struct analogix_dp_plat_data *);
        int (*power_on_end)(struct analogix_dp_plat_data *);
        int (*power_off)(struct analogix_dp_plat_data *);
        int (*attach)(struct analogix_dp_plat_data *, struct drm_bridge *,
                      struct drm_connector *);
        int (*get_modes)(struct analogix_dp_plat_data *,
                         struct drm_connector *);
};

其中:

  • dev_typeedp设备的类型,比如EXYNOS_DPRK3288_DPRK3399_EDP
  • paneledp设备面板,用于表示与edp接口相连接的显示面板;
  • encoderdrm_encoder
  • connectordrm_connector
  • skip_connector:是否跳过连接器初始化;
  • power_on_start:函数指针,在edp设备启动时调用;
  • power_on_end::函数指针,在edp设备启动完成调用;
  • power_off:函数指针,在edp设备关闭时调用;
  • attach:指向函数的指针,回调函数在桥接设备连接到连接器时被调用;
  • get_modes:指向函数的指针,用于获取edp设备支持的显示模式;

3.2 Rochchip edp

3.2.1 struct rockchip_dp_device

struct rockchip_dp_device定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c;这是Rockchip平台定义的edp结构体,其对struct analogix_dp_device进行了扩充,用于表示Rockchip平台上的edp设备。

struct rockchip_dp_device {
        struct drm_device        *drm_dev;
        struct device            *dev;
        struct rockchip_encoder  encoder;
        struct drm_display_mode  mode;

        struct clk               *pclk;
        struct clk               *grfclk;
        struct regmap            *grf;
        struct reset_control     *rst;

        const struct rockchip_dp_chip_data *data;

        struct analogix_dp_device *adp;
        struct analogix_dp_plat_data plat_data;
};

其中:

  • drm_dev:指向drm设备的struct drm_device的指针;
  • dev:指向设备的struct device指针;
  • encoderRockchip平台定义的encoder
  • mode:显示模式;
  • pclkpclk时钟;
  • grf_clkgrf时钟;
  • grf:指向寄存器映射的struct regmap的指针;
  • rst:复位控制;
  • data:指向Rockchip平台定义的平台数据;
  • adp:指向analogix定义的struct analogix_dp_device的指针;
  • plat_data:指向analogix定义的平台数据;
3.2.2 struct rockchip_dp_chip_data

struct rockchip_dp_chip_data定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c

/**
 * struct rockchip_dp_chip_data - splite the grf setting of kind of chips
 * @lcdsel_grf_reg: grf register offset of lcdc select
 * @lcdsel_big: reg value of selecting vop big for eDP
 * @lcdsel_lit: reg value of selecting vop little for eDP
 * @chip_type: specific chip type
 */
struct rockchip_dp_chip_data {
        u32     lcdsel_grf_reg;
        u32     lcdsel_big;
        u32     lcdsel_lit;
        u32     chip_type;
};

其中:

  • lcdsel_grf_reg:表示GRF寄存器中LCD控制器选择寄存器的偏移量,该寄存器用于选择使用哪个vop进行eDP输出;
  • lcdsel_big:表示在GRF寄存器中lcdsel_grf_reg偏移位置处设置的值,用于选择vopb进行eDP输出;
  • lcdsel_lit:表示在GRF寄存器中lcdsel_grf_reg偏移位置处设置的值,用于选择vopl进行eDP输出。
  • chip_type:表示芯片类型;

四、rockchip_dp_bind

rockchip_dp_bind函数定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c,该函数首先为edp设备创建encoder,然后调用analogix_dp_bind进入analogix启动;

static int rockchip_dp_bind(struct device *dev, struct device *master,
                            void *data)
{
    	// 获取Rockchip edp设备
        struct rockchip_dp_device *dp = dev_get_drvdata(dev);
        struct drm_device *drm_dev = data;
        int ret;

        dp->drm_dev = drm_dev;

    	// encoder初始化
        ret = rockchip_dp_drm_create_encoder(dp);
        if (ret) {
                DRM_ERROR("failed to create drm encoder\n");
                return ret;
        }

    	// 保存encoder
        dp->plat_data.encoder = &dp->encoder.encoder;

	    // 初始化edp接口
        ret = analogix_dp_bind(dp->adp, drm_dev);
        if (ret)
                goto err_cleanup_encoder;

        return 0;
err_cleanup_encoder:
        dp->encoder.encoder.funcs->destroy(&dp->encoder.encoder);
        return ret;
}

4.1 rockchip_dp_drm_create_encoder

rockchip_dp_drm_create_encoder函数定义在drivers/gpu/drm/rockchip/analogix_dp-rockchip.c

static struct drm_encoder_helper_funcs rockchip_dp_encoder_helper_funcs = {
        .mode_fixup = rockchip_dp_drm_encoder_mode_fixup,
        .mode_set = rockchip_dp_drm_encoder_mode_set,
        .atomic_enable = rockchip_dp_drm_encoder_enable,
        .atomic_disable = rockchip_dp_drm_encoder_disable,
        .atomic_check = rockchip_dp_drm_encoder_atomic_check,
};


static int rockchip_dp_drm_create_encoder(struct rockchip_dp_device *dp)
{      
        struct drm_encoder *encoder = &dp->encoder.encoder;
        struct drm_device *drm_dev = dp->drm_dev;
        struct device *dev = dp->dev;
        int ret;
    
		// 基于edp设备节点的信息,确定特定encoder端口可能连接的CRTC
        encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
                                                             dev->of_node);
    	// encoder初始化
        DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs);

    	// encoder初始化
        ret = drm_simple_encoder_init(drm_dev, encoder,
                                      DRM_MODE_ENCODER_TMDS);
        if (ret) {
                DRM_ERROR("failed to initialize encoder with drm\n");
                return ret;
        }

    	// 设置encoder的辅助函数helper_private为rockchip_dp_encoder_helper_funcs
        drm_encoder_helper_add(encoder, &rockchip_dp_encoder_helper_funcs);

        return 0;
}
4.1.1 drm_of_find_possible_crtcs

drm_of_find_possible_crtcs定义在drivers/gpu/drm/drm_of.c;这个函数的作用是基于edp设备节点中的信息,确定特定encoder端口可能连接的CRTC

/**
 * drm_of_find_possible_crtcs - find the possible CRTCs for an encoder port
 * @dev: DRM device
 * @port: encoder port to scan for endpoints
 *
 * Scan all endpoints attached to a port, locate their attached CRTCs,
 * and generate the DRM mask of CRTCs which may be attached to this
 * encoder.
 *
 * See Documentation/devicetree/bindings/graph.txt for the bindings.
 */
uint32_t drm_of_find_possible_crtcs(struct drm_device *dev,
                                    struct device_node *port) // edp设备节点
{
        struct device_node *remote_port, *ep;
        uint32_t possible_crtcs = 0;

    	// 遍历port结点下的每个endpoint节点,即edp_in_vopb、edp_in_vopl、edp_out_panel设备节点
        for_each_endpoint_of_node(port, ep) {
            	// 首先获取ep设备节点remote-endpoin属性指定的设备节点,并向上查找port设备节点,即vopl_out
                // 1. edp_in_vopb -> vopb_out_edp -> vopb
                // 2. edp_in_vopl -> vopl_out_edp -> vopl
                // 3. edp_out_panel -> panel_in_edp -> edp_panel
                remote_port = of_graph_get_remote_port(ep);
            	// 无效节点,进入
                if (!remote_port) {
                        of_node_put(ep);
                        return 0;
                }

            	// 下文介绍,根据remote_port设备节点,查找对应的crtc(这里即vopl)
                possible_crtcs |= drm_of_crtc_port_mask(dev, remote_port);

                of_node_put(remote_port);
        }

        return possible_crtcs;
}

edp节点为例,其有3个endpoint子节点;

edp: edp@ff970000 {
		......
		ports { 
				#address-cells = <1>;
				#size-cells = <0>;
				edp_in: port@0 {               # 该节点和port_reg=0匹配, 节点属性reg的值赋值给ep.port
						reg = <0>;
						#address-cells = <1>;
						#size-cells = <0>;

						edp_in_vopb: endpoint@0 {
								reg = <0>;            # 属性reg的值赋值给ep.id
								remote-endpoint = <&vopb_out_edp>;
						};

						edp_in_vopl: endpoint@1 {
								reg = <1>;             # 属性reg的值赋值给ep.id
								remote-endpoint = <&vopl_out_edp>;
						};
				};
				
                edp_out: port@1 {            # 该节点和port_reg=1匹配, 节点属性reg的值赋值给ep.port
                        reg = <1>;
                        #address-cells = <1>;
                        #size-cells = <0>;

                        edp_out_panel: endpoint@0 {    # 属性reg的值赋值给ep.id
                                reg = <0>;
                                remote-endpoint = <&panel_in_edp>;
                        };
                };
		};
};

第一次遍历时,drm_of_crtc_port_mask参数一传入的是drm设备,参数二传入的是vopl_out设备节点。

drm_of_crtc_port_mask定义如下:

/**
 * DOC: overview
 *
 * A set of helper functions to aid DRM drivers in parsing standard DT
 * properties.
 */

/**
 * drm_of_crtc_port_mask - find the mask of a registered CRTC by port OF node
 * @dev: DRM device
 * @port: port OF node
 *
 * Given a port OF node, return the possible mask of the corresponding
 * CRTC within a device's list of CRTCs.  Returns zero if not found.
 */
uint32_t drm_of_crtc_port_mask(struct drm_device *dev,
                            struct device_node *port) // vopl_out设备节点
{
        unsigned int index = 0;
        struct drm_crtc *tmp;

    	// list_for_each_entry(tmp, &(dev)->mode_config.crtc_list, head),遍历crtc链表,赋值给tmp
    	// 因此这里会依次遍历到vopb、vopl对应的crtc,其中vopl对应的crtc->port被设置为vopl_out设备节点
        drm_for_each_crtc(tmp, dev) {   
                if (tmp->port == port)  // tmp为vopl时匹配
                        return 1 << index;

                index++; 
        }

        return 0;
}
4.1.2 drm_simple_encoder_init

drm_simple_encoder_init定义在drivers/gpu/drm/drm_simple_kms_helper.c,内部调用drm_encoder_init函数初始化encoder对象;

/**
 * drm_simple_encoder_init - Initialize a preallocated encoder with
 *                           basic functionality.
 * @dev: drm device
 * @encoder: the encoder to initialize
 * @encoder_type: user visible type of the encoder
 *
 * Initialises a preallocated encoder that has no further functionality.
 * Settings for possible CRTC and clones are left to their initial values.
 * The encoder will be cleaned up automatically as part of the mode-setting
 * cleanup.
 *
 * The caller of drm_simple_encoder_init() is responsible for freeing
 * the encoder's memory after the encoder has been cleaned up. At the
 * moment this only works reliably if the encoder data structure is
 * stored in the device structure. Free the encoder's memory as part of
 * the device release function.
 *
 * Note: consider using drmm_simple_encoder_alloc() instead of
 * drm_simple_encoder_init() to let the DRM managed resource infrastructure
 * take care of cleanup and deallocation.
 *
 * Returns:
 * Zero on success, error code on failure.
 */
int drm_simple_encoder_init(struct drm_device *dev,
                            struct drm_encoder *encoder,
                            int encoder_type)
{
        return drm_encoder_init(dev, encoder,
                                &drm_simple_encoder_funcs_cleanup,
                                encoder_type, NULL);
}

4.2 analogix_dp_bind

analogix_dp_bind函数定义在drivers/gpu/drm/bridge/analogix/analogix_dp_core.c

int analogix_dp_bind(struct analogix_dp_device *dp, struct drm_device *drm_dev)
{
        int ret;

        dp->drm_dev = drm_dev;
        dp->encoder = dp->plat_data->encoder;

        dp->aux.name = "DP-AUX";
        dp->aux.transfer = analogix_dpaux_transfer;
        dp->aux.dev = dp->dev;
        dp->aux.drm_dev = drm_dev;

        ret = drm_dp_aux_register(&dp->aux);
        if (ret)
                return ret;

        pm_runtime_use_autosuspend(dp->dev);
        pm_runtime_set_autosuspend_delay(dp->dev, 100);
        pm_runtime_enable(dp->dev);

        ret = analogix_dp_create_bridge(drm_dev, dp);
        if (ret) {
                DRM_ERROR("failed to create bridge (%d)\n", ret);
                goto err_disable_pm_runtime;
        }

        return 0;

err_disable_pm_runtime:
        pm_runtime_dont_use_autosuspend(dp->dev);
        pm_runtime_disable(dp->dev);
        drm_dp_aux_unregister(&dp->aux);

        return ret;
}
4.2.1 analogix_dp_create_bridge
static int analogix_dp_create_bridge(struct drm_device *drm_dev,
                                     struct analogix_dp_device *dp)
{    
        struct drm_bridge *bridge;
		// 动态分配bridge
        bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge), GFP_KERNEL);
        if (!bridge) {
                DRM_ERROR("failed to allocate for drm bridge\n");
                return -ENOMEM;
        }

        dp->bridge = bridge;

    	// 初始化成员
        bridge->driver_private = dp;
    	// 初始化bridge的控制函数
        bridge->funcs = &analogix_dp_bridge_funcs;

    	// 将bridge连接到encoder的链中
        return drm_bridge_attach(dp->encoder, bridge, NULL, 0);
}
4.2.2 analogix_dp_bridge_funcs
static const struct drm_bridge_funcs analogix_dp_bridge_funcs = {
        .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
        .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
        .atomic_reset = drm_atomic_helper_bridge_reset,
        .atomic_pre_enable = analogix_dp_bridge_atomic_pre_enable,
        .atomic_enable = analogix_dp_bridge_atomic_enable,
        .atomic_disable = analogix_dp_bridge_atomic_disable,
        .atomic_post_disable = analogix_dp_bridge_atomic_post_disable,
        .mode_set = analogix_dp_bridge_mode_set,
        .attach = analogix_dp_bridge_attach,
};

参考文章

[1] 输入子系统–电容触摸驱动实验

[2] RK3568J edp屏幕点亮 时序调试总结

[3] AIO-3566JD4 LCD使用

[4] https://wiki.friendlyarm.com/wiki/images/4/4a/HD702_HD702E_WK_Drawing.pdf

[5] DRM Display Driver Guide

posted @ 2024-02-27 00:07  大奥特曼打小怪兽  阅读(477)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步