Rockchip RK3399 - DRM HDMI驱动程序

发布时间 2023-10-19 22:36:15作者: 大奥特曼打小怪兽

Rockchip RK3399 - DRM驱动程序》》我们已经介绍过了,RK3399有两个VOP,均可以支持HDMIeDPDPMIPI DSI0MIPI DSI1显示接口,本节我们选择HDMI作为分析的对象。

一、设备树配置

1.1 hdmi设备节点

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

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

hdmi: hdmi@ff940000 {
		compatible = "rockchip,rk3399-dw-hdmi";
		reg = <0x0 0xff940000 0x0 0x20000>;
		interrupts = <GIC_SPI 23 IRQ_TYPE_LEVEL_HIGH 0>;
		clocks = <&cru PCLK_HDMI_CTRL>,
				 <&cru SCLK_HDMI_SFR>,
				 <&cru SCLK_HDMI_CEC>,
				 <&cru PCLK_VIO_GRF>,
				 <&cru PLL_VPLL>;
		clock-names = "iahb", "isfr", "cec", "grf", "ref";
		power-domains = <&power RK3399_PD_HDCP>;
		reg-io-width = <4>;
		rockchip,grf = <&grf>;
		#sound-dai-cells = <0>;
		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>;
						};
				};
		};
};

其中:

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

因此可以得到有2条同理:

  • vopb_out_hdmi ---> hdmi_in_vopb
  • vopl_out_hdmi ---> hdmi_in_vopl

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

1.2 启用hdmi

如果我们希望hdmi连接在VOPB上,则需要在arch/arm64/boot/dts/rockchip/rk3399-evb.dts中为以下节点新增属性:

&i2c7 {
        status = "okay";
};

&display_subsystem {
         status = "okay";
};

&vopb {
        status = "okay";
};

&vopb_mmu {
        status = "okay";
};
     
&hdmi {     
        ddc-i2c-bus = <&i2c7>;
        pinctrl-names = "default";
        pinctrl-0 = <&hdmi_cec>;
        status = "okay";
};


&hdmi_in_vopb{
        status = "okay";
};

&hdmi_in_vopl{
        status = "disabled";
};

二、platfrom driver

rockchip_drm_init函数中调用:

ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
            CONFIG_ROCKCHIP_DW_HDMI);

会将dw_hdmi_rockchip_pltfm_driver保存到rockchip_sub_drivers数组中。

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

2.1 dw_hdmi_rockchip_pltfm_driver

dw_hdmi_rockchip_pltfm_driver定义在drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c

struct platform_driver dw_hdmi_rockchip_pltfm_driver = {
        .probe  = dw_hdmi_rockchip_probe,
        .remove = dw_hdmi_rockchip_remove,
        .driver = {
                .name = "dwhdmi-rockchip",
                .pm = &dw_hdmi_rockchip_pm,
                .of_match_table = dw_hdmi_rockchip_dt_ids, // 用于设备树匹配
        },
};
2.1.1 of_match_table

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

static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
        { .compatible = "rockchip,rk3228-dw-hdmi",
          .data = &rk3228_hdmi_drv_data
        },
        { .compatible = "rockchip,rk3288-dw-hdmi",
          .data = &rk3288_hdmi_drv_data
        },
        { .compatible = "rockchip,rk3328-dw-hdmi",
          .data = &rk3328_hdmi_drv_data
        },
        { .compatible = "rockchip,rk3399-dw-hdmi",
          .data = &rk3399_hdmi_drv_data
        },
        { .compatible = "rockchip,rk3568-dw-hdmi",
          .data = &rk3568_hdmi_drv_data
        },
        {},
};
2.1.2 probe

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

static const struct component_ops dw_hdmi_rockchip_ops = {
        .bind   = dw_hdmi_rockchip_bind,
        .unbind = dw_hdmi_rockchip_unbind,
};


static int dw_hdmi_rockchip_probe(struct platform_device *pdev)
{
        return component_add(&pdev->dev, &dw_hdmi_rockchip_ops);
}

这里代码很简单,就是为设备pdev->dev向系统注册一个component,其中组件可执行的初始化操作被设置为了dw_hdmi_rockchip_ops,我们需要重点关注bind函数的实现,这个我们单独小节介绍。

2.2 dw_hdmi_rockchip_bind

dw_hdmi_rockchip_bind函数定义在drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c,涉及到对hdmi设备节点的解析;

static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
                                 void *data)
{
        struct platform_device *pdev = to_platform_device(dev);
        struct dw_hdmi_plat_data *plat_data;
        const struct of_device_id *match;
        struct drm_device *drm = data;
        struct drm_encoder *encoder;
        struct rockchip_hdmi *hdmi;
        int ret;

        if (!pdev->dev.of_node)
                return -ENODEV;

        hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
        if (!hdmi)
                return -ENOMEM;

        match = of_match_node(dw_hdmi_rockchip_dt_ids, pdev->dev.of_node);
        plat_data = devm_kmemdup(&pdev->dev, match->data,
                                             sizeof(*plat_data), GFP_KERNEL);
        if (!plat_data)
                return -ENOMEM;

        hdmi->dev = &pdev->dev;
        hdmi->chip_data = plat_data->phy_data;
        plat_data->phy_data = hdmi;
        encoder = &hdmi->encoder.encoder;

        encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
        rockchip_drm_encoder_set_crtc_endpoint_id(&hdmi->encoder,
                                                  dev->of_node, 0, 0);

        /*
         * If we failed to find the CRTC(s) which this encoder is
         * supposed to be connected to, it's because the CRTC has
         * not been registered yet.  Defer probing, and hope that
         * the required CRTC is added later.
         */
        if (encoder->possible_crtcs == 0)
                return -EPROBE_DEFER;

        ret = rockchip_hdmi_parse_dt(hdmi);
        if (ret) {
                if (ret != -EPROBE_DEFER)
                        DRM_DEV_ERROR(hdmi->dev, "Unable to parse OF data\n");
                return ret;
        }

        hdmi->phy = devm_phy_optional_get(dev, "hdmi");
        if (IS_ERR(hdmi->phy)) {
                ret = PTR_ERR(hdmi->phy);
                if (ret != -EPROBE_DEFER)
                        DRM_DEV_ERROR(hdmi->dev, "failed to get phy\n");
                return ret;
        }

        ret = regulator_enable(hdmi->avdd_0v9);
        if (ret) {
                DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd0v9: %d\n", ret);
                goto err_avdd_0v9;
        }

        ret = regulator_enable(hdmi->avdd_1v8);
        if (ret) {
                DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd1v8: %d\n", ret);
                goto err_avdd_1v8;
        }

        ret = clk_prepare_enable(hdmi->ref_clk);
        if (ret) {
                DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI reference clock: %d\n",
                              ret);
                goto err_clk;
        }

        if (hdmi->chip_data == &rk3568_chip_data) {
                regmap_write(hdmi->regmap, RK3568_GRF_VO_CON1,
                             HIWORD_UPDATE(RK3568_HDMI_SDAIN_MSK |
                                           RK3568_HDMI_SCLIN_MSK,
                                           RK3568_HDMI_SDAIN_MSK |
                                           RK3568_HDMI_SCLIN_MSK));
        }

        drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs);
        drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);

        platform_set_drvdata(pdev, hdmi);

        hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);

        /*
         * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
         * which would have called the encoder cleanup.  Do it manually.
         */
        if (IS_ERR(hdmi->hdmi)) {
                ret = PTR_ERR(hdmi->hdmi);
                goto err_bind;
        }

        return 0;

err_bind:
        drm_encoder_cleanup(encoder);
        clk_disable_unprepare(hdmi->ref_clk);
err_clk:
        regulator_disable(hdmi->avdd_1v8);
err_avdd_1v8:
        regulator_disable(hdmi->avdd_0v9);
err_avdd_0v9:
        return ret;
}