在《Rockchip RK3399 - DRM驱动程序》》我们已经介绍过了,RK3399有两个VOP,均可以支持HDMI、eDP、DP、MIPI DSI0、MIPI 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,分别连接到VOPL和VOPB;也就是在rk3399上,hdmi可以和VOPL、VOPB连接;
因此可以得到有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;
}