| From 50fafc38c436b7d77cc2a0beba931f14f1347b7f Mon Sep 17 00:00:00 2001 |
| From: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com> |
| Date: Mon, 6 Mar 2017 01:35:39 +0200 |
| Subject: [PATCH 228/286] drm: bridge: dw-hdmi: Fix the PHY power down sequence |
| |
| The PHY requires us to wait for the PHY to switch to low power mode |
| after deasserting TXPWRON and before asserting PDDQ in the power down |
| sequence, otherwise power down will fail. |
| |
| The PHY power down can be monitored though the TX_READY bit, available |
| through I2C in the PHY registers, or the TX_PHY_LOCK bit, available |
| through the HDMI TX registers. As the two are equivalent, let's pick the |
| easier solution of polling the TX_PHY_LOCK bit. |
| |
| The power down code is currently duplicated in multiple places. To avoid |
| spreading multiple calls to a TX_PHY_LOCK poll function, we have to |
| refactor the power down code and group it all in a single function. |
| |
| Tests showed that one poll iteration was enough for TX_PHY_LOCK to |
| become low, without requiring any additional delay. Retrying the read |
| five times with a 1ms to 2ms delay between each attempt should thus be |
| more than enough. |
| |
| Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com> |
| Tested-by: Neil Armstrong <narmstrong@baylibre.com> |
| Reviewed-by: Jose Abreu <joabreu@synopsys.com> |
| Signed-off-by: Archit Taneja <architt@codeaurora.org> |
| Link: http://patchwork.freedesktop.org/patch/msgid/20170305233539.11898-1-laurent.pinchart+renesas@ideasonboard.com |
| (cherry picked from commit b0e583e5b6b90eed40456c394410c154a5160814) |
| Signed-off-by: Simon Horman <horms+renesas@verge.net.au> |
| --- |
| drivers/gpu/drm/bridge/dw-hdmi.c | 52 ++++++++++++++++++++++++++++++++------- |
| 1 file changed, 43 insertions(+), 9 deletions(-) |
| |
| --- a/drivers/gpu/drm/bridge/dw-hdmi.c |
| +++ b/drivers/gpu/drm/bridge/dw-hdmi.c |
| @@ -116,6 +116,7 @@ struct dw_hdmi_i2c { |
| struct dw_hdmi_phy_data { |
| enum dw_hdmi_phy_type type; |
| const char *name; |
| + unsigned int gen; |
| bool has_svsret; |
| }; |
| |
| @@ -914,6 +915,40 @@ static void dw_hdmi_phy_sel_interface_co |
| HDMI_PHY_CONF0_SELDIPIF_MASK); |
| } |
| |
| +static void dw_hdmi_phy_power_off(struct dw_hdmi *hdmi) |
| +{ |
| + const struct dw_hdmi_phy_data *phy = hdmi->phy; |
| + unsigned int i; |
| + u16 val; |
| + |
| + if (phy->gen == 1) { |
| + dw_hdmi_phy_enable_tmds(hdmi, 0); |
| + dw_hdmi_phy_enable_powerdown(hdmi, true); |
| + return; |
| + } |
| + |
| + dw_hdmi_phy_gen2_txpwron(hdmi, 0); |
| + |
| + /* |
| + * Wait for TX_PHY_LOCK to be deasserted to indicate that the PHY went |
| + * to low power mode. |
| + */ |
| + for (i = 0; i < 5; ++i) { |
| + val = hdmi_readb(hdmi, HDMI_PHY_STAT0); |
| + if (!(val & HDMI_PHY_TX_PHY_LOCK)) |
| + break; |
| + |
| + usleep_range(1000, 2000); |
| + } |
| + |
| + if (val & HDMI_PHY_TX_PHY_LOCK) |
| + dev_warn(hdmi->dev, "PHY failed to power down\n"); |
| + else |
| + dev_dbg(hdmi->dev, "PHY powered down in %u iterations\n", i); |
| + |
| + dw_hdmi_phy_gen2_pddq(hdmi, 1); |
| +} |
| + |
| static int hdmi_phy_configure(struct dw_hdmi *hdmi) |
| { |
| u8 val, msec; |
| @@ -946,11 +981,7 @@ static int hdmi_phy_configure(struct dw_ |
| return -EINVAL; |
| } |
| |
| - /* gen2 tx power off */ |
| - dw_hdmi_phy_gen2_txpwron(hdmi, 0); |
| - |
| - /* gen2 pddq */ |
| - dw_hdmi_phy_gen2_pddq(hdmi, 1); |
| + dw_hdmi_phy_power_off(hdmi); |
| |
| /* Leave low power consumption mode by asserting SVSRET. */ |
| if (hdmi->phy->has_svsret) |
| @@ -1025,8 +1056,6 @@ static int dw_hdmi_phy_init(struct dw_hd |
| for (i = 0; i < 2; i++) { |
| dw_hdmi_phy_sel_data_en_pol(hdmi, 1); |
| dw_hdmi_phy_sel_interface_control(hdmi, 0); |
| - dw_hdmi_phy_enable_tmds(hdmi, 0); |
| - dw_hdmi_phy_enable_powerdown(hdmi, true); |
| |
| ret = hdmi_phy_configure(hdmi); |
| if (ret) |
| @@ -1256,8 +1285,7 @@ static void dw_hdmi_phy_disable(struct d |
| if (!hdmi->phy_enabled) |
| return; |
| |
| - dw_hdmi_phy_enable_tmds(hdmi, 0); |
| - dw_hdmi_phy_enable_powerdown(hdmi, true); |
| + dw_hdmi_phy_power_off(hdmi); |
| |
| hdmi->phy_enabled = false; |
| } |
| @@ -1827,23 +1855,29 @@ static const struct dw_hdmi_phy_data dw_ |
| { |
| .type = DW_HDMI_PHY_DWC_HDMI_TX_PHY, |
| .name = "DWC HDMI TX PHY", |
| + .gen = 1, |
| }, { |
| .type = DW_HDMI_PHY_DWC_MHL_PHY_HEAC, |
| .name = "DWC MHL PHY + HEAC PHY", |
| + .gen = 2, |
| .has_svsret = true, |
| }, { |
| .type = DW_HDMI_PHY_DWC_MHL_PHY, |
| .name = "DWC MHL PHY", |
| + .gen = 2, |
| .has_svsret = true, |
| }, { |
| .type = DW_HDMI_PHY_DWC_HDMI_3D_TX_PHY_HEAC, |
| .name = "DWC HDMI 3D TX PHY + HEAC PHY", |
| + .gen = 2, |
| }, { |
| .type = DW_HDMI_PHY_DWC_HDMI_3D_TX_PHY, |
| .name = "DWC HDMI 3D TX PHY", |
| + .gen = 2, |
| }, { |
| .type = DW_HDMI_PHY_DWC_HDMI20_TX_PHY, |
| .name = "DWC HDMI 2.0 TX PHY", |
| + .gen = 2, |
| .has_svsret = true, |
| } |
| }; |