The HDMI TX controller support different PHYs whose programming interface can vary significantly, especially with vendor PHYs that are not provided by Synopsys. To support them, create a PHY operation structure that can be provided by the platform glue layer. The existing PHY handling code (limited to Synopsys PHY support) is refactored into a set of default PHY operations that are used automatically when the platform glue doesn't provide its own operations. Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@xxxxxxxxxxxxxxxx> Tested-by: Neil Armstrong <narmstrong@xxxxxxxxxxxx> Reviewed-by: Jose Abreu <joabreu@xxxxxxxxxxxx> --- drivers/gpu/drm/bridge/dw-hdmi.c | 91 ++++++++++++++++++++++++++++------------ include/drm/bridge/dw_hdmi.h | 18 +++++++- 2 files changed, 80 insertions(+), 29 deletions(-) diff --git a/drivers/gpu/drm/bridge/dw-hdmi.c b/drivers/gpu/drm/bridge/dw-hdmi.c index 0aa3ad404f77..cb2703862be2 100644 --- a/drivers/gpu/drm/bridge/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/dw-hdmi.c @@ -141,8 +141,12 @@ struct dw_hdmi { u8 edid[HDMI_EDID_LEN]; bool cable_plugin; - const struct dw_hdmi_phy_data *phy; - bool phy_enabled; + struct { + const struct dw_hdmi_phy_ops *ops; + const char *name; + void *data; + bool enabled; + } phy; struct drm_display_mode previous_mode; @@ -831,6 +835,10 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi) HDMI_VP_CONF); } +/* ----------------------------------------------------------------------------- + * Synopsys PHY Handling + */ + static inline void hdmi_phy_test_clear(struct dw_hdmi *hdmi, unsigned char bit) { @@ -987,6 +995,7 @@ static int dw_hdmi_phy_power_on(struct dw_hdmi *hdmi) static int hdmi_phy_configure(struct dw_hdmi *hdmi) { + const struct dw_hdmi_phy_data *phy = hdmi->phy.data; const struct dw_hdmi_plat_data *pdata = hdmi->plat_data; const struct dw_hdmi_mpll_config *mpll_config = pdata->mpll_cfg; const struct dw_hdmi_curr_ctrl *curr_ctrl = pdata->cur_ctr; @@ -1019,7 +1028,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi) dw_hdmi_phy_power_off(hdmi); /* Leave low power consumption mode by asserting SVSRET. */ - if (hdmi->phy->has_svsret) + if (phy->has_svsret) dw_hdmi_phy_enable_svsret(hdmi, 1); /* PHY reset. The reset signal is active high on Gen2 PHYs. */ @@ -1057,7 +1066,8 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi) return dw_hdmi_phy_power_on(hdmi); } -static int dw_hdmi_phy_init(struct dw_hdmi *hdmi) +static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data, + struct drm_display_mode *mode) { int i, ret; @@ -1071,10 +1081,31 @@ static int dw_hdmi_phy_init(struct dw_hdmi *hdmi) return ret; } - hdmi->phy_enabled = true; return 0; } +static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data) +{ + dw_hdmi_phy_power_off(hdmi); +} + +static enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi, + void *data) +{ + return hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD ? + connector_status_connected : connector_status_disconnected; +} + +static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = { + .init = dw_hdmi_phy_init, + .disable = dw_hdmi_phy_disable, + .read_hpd = dw_hdmi_phy_read_hpd, +}; + +/* ----------------------------------------------------------------------------- + * HDMI TX Setup + */ + static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi) { u8 de; @@ -1289,16 +1320,6 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, hdmi_writeb(hdmi, vsync_len, HDMI_FC_VSYNCINWIDTH); } -static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi) -{ - if (!hdmi->phy_enabled) - return; - - dw_hdmi_phy_power_off(hdmi); - - hdmi->phy_enabled = false; -} - /* HDMI Initialization Step B.4 */ static void dw_hdmi_enable_video_path(struct dw_hdmi *hdmi) { @@ -1431,9 +1452,10 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode) hdmi_av_composer(hdmi, mode); /* HDMI Initializateion Step B.2 */ - ret = dw_hdmi_phy_init(hdmi); + ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, &hdmi->previous_mode); if (ret) return ret; + hdmi->phy.enabled = true; /* HDMI Initialization Step B.3 */ dw_hdmi_enable_video_path(hdmi); @@ -1548,7 +1570,11 @@ static void dw_hdmi_poweron(struct dw_hdmi *hdmi) static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) { - dw_hdmi_phy_disable(hdmi); + if (hdmi->phy.enabled) { + hdmi->phy.ops->disable(hdmi, hdmi->phy.data); + hdmi->phy.enabled = false; + } + hdmi->bridge_is_on = false; } @@ -1611,8 +1637,7 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force) dw_hdmi_update_phy_mask(hdmi); mutex_unlock(&hdmi->mutex); - return hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD ? - connector_status_connected : connector_status_disconnected; + return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data); } static int dw_hdmi_connector_get_modes(struct drm_connector *connector) @@ -1898,19 +1923,31 @@ static int dw_hdmi_detect_phy(struct dw_hdmi *hdmi) phy_type = hdmi_readb(hdmi, HDMI_CONFIG2_ID); + if (phy_type == DW_HDMI_PHY_VENDOR_PHY) { + /* Vendor PHYs require support from the glue layer. */ + if (!hdmi->plat_data->phy_ops || !hdmi->plat_data->phy_name) { + dev_err(hdmi->dev, + "Vendor HDMI PHY not supported by glue layer\n"); + return -ENODEV; + } + + hdmi->phy.ops = hdmi->plat_data->phy_ops; + hdmi->phy.data = hdmi->plat_data->phy_data; + hdmi->phy.name = hdmi->plat_data->phy_name; + return 0; + } + + /* Synopsys PHYs are handled internally. */ for (i = 0; i < ARRAY_SIZE(dw_hdmi_phys); ++i) { if (dw_hdmi_phys[i].type == phy_type) { - hdmi->phy = &dw_hdmi_phys[i]; + hdmi->phy.ops = &dw_hdmi_synopsys_phy_ops; + hdmi->phy.name = dw_hdmi_phys[i].name; + hdmi->phy.data = (void *)&dw_hdmi_phys[i]; return 0; } } - if (phy_type == DW_HDMI_PHY_VENDOR_PHY) - dev_err(hdmi->dev, "Unsupported vendor HDMI PHY\n"); - else - dev_err(hdmi->dev, "Unsupported HDMI PHY type (%02x)\n", - phy_type); - + dev_err(hdmi->dev, "Unsupported HDMI PHY type (%02x)\n", phy_type); return -ENODEV; } @@ -2031,7 +2068,7 @@ __dw_hdmi_probe(struct platform_device *pdev, dev_info(dev, "Detected HDMI TX controller v%x.%03x %s HDCP (%s)\n", hdmi->version >> 12, hdmi->version & 0xfff, prod_id1 & HDMI_PRODUCT_ID1_HDCP ? "with" : "without", - hdmi->phy->name); + hdmi->phy.name); initialize_hdmi_ih_mutes(hdmi); diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index b080a171a23f..0f583ca7e66e 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -57,13 +57,27 @@ struct dw_hdmi_phy_config { u16 vlev_ctr; /* voltage level control */ }; +struct dw_hdmi_phy_ops { + int (*init)(struct dw_hdmi *hdmi, void *data, + struct drm_display_mode *mode); + void (*disable)(struct dw_hdmi *hdmi, void *data); + enum drm_connector_status (*read_hpd)(struct dw_hdmi *hdmi, void *data); +}; + struct dw_hdmi_plat_data { enum dw_hdmi_devtype dev_type; + enum drm_mode_status (*mode_valid)(struct drm_connector *connector, + struct drm_display_mode *mode); + + /* Vendor PHY support */ + const struct dw_hdmi_phy_ops *phy_ops; + const char *phy_name; + void *phy_data; + + /* 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; - enum drm_mode_status (*mode_valid)(struct drm_connector *connector, - struct drm_display_mode *mode); }; int dw_hdmi_probe(struct platform_device *pdev, -- Regards, Laurent Pinchart