在《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 dw_hdmi_plat_data rk3399_hdmi_drv_data = {
.mode_valid = dw_hdmi_rockchip_mode_valid,
.mpll_cfg = rockchip_mpll_cfg,
.cur_ctr = rockchip_cur_ctr,
.phy_config = rockchip_phy_config,
.phy_data = &rk3399_chip_data,
.use_drm_infoframe = true,
};
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;
// 动态分配内存,指向struct rockchip_hdmi
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);
// 分配内存,指向一个struct dw_hdmi_plat_data,并复制rk3399_hdmi_drv_data数据
plat_data = devm_kmemdup(&pdev->dev, match->data,
sizeof(*plat_data), GFP_KERNEL);
if (!plat_data)
return -ENOMEM;
// 设置device设备
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;
// 解析hdmi设备节点
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;
}
三、相关数据结构
3.1 struct drm_encoder
linux内
核使用struct drm_encoder
来表示一个编码器,用于连接CRT
控制器和显示设备。
3.2 struct dw_hdmi
struct dw_hdmi
定义在drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
;
struct dw_hdmi {
struct drm_connector connector;
struct drm_bridge bridge;
struct drm_bridge *next_bridge;
unsigned int version;
struct platform_device *audio;
struct platform_device *cec;
struct device *dev;
struct clk *isfr_clk;
struct clk *iahb_clk;
struct clk *cec_clk;
struct dw_hdmi_i2c *i2c;
struct hdmi_data_info hdmi_data;
const struct dw_hdmi_plat_data *plat_data;
int vic;
u8 edid[HDMI_EDID_LEN];
struct {
const struct dw_hdmi_phy_ops *ops;
const char *name;
void *data;
bool enabled;
} phy;
struct drm_display_mode previous_mode;
struct i2c_adapter *ddc;
void __iomem *regs;
bool sink_is_hdmi;
bool sink_has_audio;
struct pinctrl *pinctrl;
struct pinctrl_state *default_state;
struct pinctrl_state *unwedge_state;
struct mutex mutex; /* for state below and previous_mode */
enum drm_connector_force force; /* mutex-protected force state */
struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */
bool disabled; /* DRM has disabled our bridge */
bool bridge_is_on; /* indicates the bridge is on */
bool rxsense; /* rxsense state */
u8 phy_mask; /* desired phy int mask settings */
u8 mc_clkdis; /* clock disable register */
spinlock_t audio_lock;
struct mutex audio_mutex;
unsigned int sample_non_pcm;
unsigned int sample_width;
unsigned int sample_rate;
unsigned int channels;
unsigned int audio_cts;
unsigned int audio_n;
bool audio_enable;
unsigned int reg_shift;
struct regmap *regm;
void (*enable_audio)(struct dw_hdmi *hdmi);
void (*disable_audio)(struct dw_hdmi *hdmi);
struct mutex cec_notifier_mutex;
struct cec_notifier *cec_notifier;
hdmi_codec_plugged_cb plugged_cb;
struct device *codec_dev;
enum drm_connector_status last_connector_result;
};
3.3 struct dw_hdmi_plat_data
struct dw_hdmi_plat_data
定义在include/drm/bridge/dw_hdmi.h
;
struct dw_hdmi_plat_data {
struct regmap *regm;
unsigned int output_port;
unsigned long input_bus_encoding;
bool use_drm_infoframe;
bool ycbcr_420_allowed;
/*
* Private data passed to all the .mode_valid() and .configure_phy()
* callback functions.
*/
void *priv_data;
/* Platform-specific mode validation (optional). */
enum drm_mode_status (*mode_valid)(struct dw_hdmi *hdmi, void *data,
const struct drm_display_info *info,
const struct drm_display_mode *mode);
/* Platform-specific audio enable/disable (optional) */
void (*enable_audio)(struct dw_hdmi *hdmi, int channel,
int width, int rate, int non_pcm);
void (*disable_audio)(struct dw_hdmi *hdmi);
/* Vendor PHY support */
const struct dw_hdmi_phy_ops *phy_ops;
const char *phy_name;
void *phy_data;
unsigned int phy_force_vendor;
/* Synopsys PHY support */
const struct dw_hdmi_mpll_config *mpll_cfg;
const struct dw_hdmi_curr_ctrl *cur_ctr;
const struct dw_hdmi_phy_config *phy_config;
int (*configure_phy)(struct dw_hdmi *hdmi, void *data,
unsigned long mpixelclock);
unsigned int disable_cec : 1;
};
3.4 struct rockchip_hdmi
struct rockchip_hdmi
定义在drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
;
struct rockchip_hdmi {
struct device *dev;
struct regmap *regmap;
struct rockchip_encoder encoder;
const struct rockchip_hdmi_chip_data *chip_data;
struct clk *ref_clk;
struct clk *grf_clk;
struct dw_hdmi *hdmi;
struct regulator *avdd_0v9;
struct regulator *avdd_1v8;
struct phy *phy;
};
3.4.1 struct rockchip_hdmi_chip_data
/**
* struct rockchip_hdmi_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 HDMI
* @lcdsel_lit: reg value of selecting vop little for HDMI
*/
struct rockchip_hdmi_chip_data {
int lcdsel_grf_reg;
u32 lcdsel_big;
u32 lcdsel_lit;
};
参考文章
[1] [Rockchip HDMI
软件开发指南]