diff mbox series

[10/13] drm/i915/mtl: Power up TCSS

Message ID 20230420124050.3617608-11-mika.kahola@intel.com (mailing list archive)
State New, archived
Headers show
Series drm/i915/mtl: Add support for C20 phy | expand

Commit Message

Mika Kahola April 20, 2023, 12:40 p.m. UTC
Add register writes to enable powering up Type-C subsystem i.e. TCSS.
For MeteorLake we need to request TCSS to power up and check the TCSS
power state after 500 us.

In addition, for PICA we need to set/clear the Type-C PHY ownnership
bit when Type-C device is connected/disconnected.

Signed-off-by: Mika Kahola <mika.kahola@intel.com>
Signed-off-by: Imre Deak <imre.deak@intel.com>
---
 drivers/gpu/drm/i915/display/intel_cx0_phy.c |  19 ++
 drivers/gpu/drm/i915/display/intel_cx0_phy.h |   4 +
 drivers/gpu/drm/i915/display/intel_ddi.c     |   1 +
 drivers/gpu/drm/i915/display/intel_display.c |   2 +-
 drivers/gpu/drm/i915/display/intel_tc.c      | 199 ++++++++++++++++++-
 5 files changed, 216 insertions(+), 9 deletions(-)

Comments

Matt Atwood April 27, 2023, 4:09 p.m. UTC | #1
On Thu, Apr 20, 2023 at 03:40:47PM +0300, Mika Kahola wrote:
> Add register writes to enable powering up Type-C subsystem i.e. TCSS.
> For MeteorLake we need to request TCSS to power up and check the TCSS
> power state after 500 us.
> 
> In addition, for PICA we need to set/clear the Type-C PHY ownnership
> bit when Type-C device is connected/disconnected.
> 
Reviewed-by: Matt Atwood <matthew.s.atwood@intel.com>
> Signed-off-by: Mika Kahola <mika.kahola@intel.com>
> Signed-off-by: Imre Deak <imre.deak@intel.com>
> ---
>  drivers/gpu/drm/i915/display/intel_cx0_phy.c |  19 ++
>  drivers/gpu/drm/i915/display/intel_cx0_phy.h |   4 +
>  drivers/gpu/drm/i915/display/intel_ddi.c     |   1 +
>  drivers/gpu/drm/i915/display/intel_display.c |   2 +-
>  drivers/gpu/drm/i915/display/intel_tc.c      | 199 ++++++++++++++++++-
>  5 files changed, 216 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/gpu/drm/i915/display/intel_cx0_phy.c b/drivers/gpu/drm/i915/display/intel_cx0_phy.c
> index dc0555505e61..97d80adb921f 100644
> --- a/drivers/gpu/drm/i915/display/intel_cx0_phy.c
> +++ b/drivers/gpu/drm/i915/display/intel_cx0_phy.c
> @@ -2884,6 +2884,25 @@ void intel_mtl_pll_disable(struct intel_encoder *encoder)
>  		intel_cx0pll_disable(encoder);
>  }
>  
> +enum icl_port_dpll_id
> +intel_mtl_port_pll_type(struct intel_encoder *encoder,
> +			const struct intel_crtc_state *crtc_state)
> +{
> +	struct drm_i915_private *i915 = to_i915(encoder->base.dev);
> +	/*
> +	 * TODO: Determine the PLL type from the SW state, once MTL PLL
> +	 * handling is done via the standard shared DPLL framework.
> +	 */
> +	u32 val = intel_de_read(i915, XELPDP_PORT_CLOCK_CTL(encoder->port));
> +	u32 clock = REG_FIELD_GET(XELPDP_DDI_CLOCK_SELECT_MASK, val);
> +
> +	if (clock == XELPDP_DDI_CLOCK_SELECT_MAXPCLK ||
> +	    clock == XELPDP_DDI_CLOCK_SELECT_DIV18CLK)
> +		return ICL_PORT_DPLL_MG_PHY;
> +	else
> +		return ICL_PORT_DPLL_DEFAULT;
> +}
> +
>  void intel_c10pll_state_verify(struct intel_atomic_state *state,
>  			       struct intel_crtc_state *new_crtc_state)
>  {
> diff --git a/drivers/gpu/drm/i915/display/intel_cx0_phy.h b/drivers/gpu/drm/i915/display/intel_cx0_phy.h
> index c1b8f7980f69..f99809af257d 100644
> --- a/drivers/gpu/drm/i915/display/intel_cx0_phy.h
> +++ b/drivers/gpu/drm/i915/display/intel_cx0_phy.h
> @@ -16,12 +16,16 @@
>  struct drm_i915_private;
>  struct intel_encoder;
>  struct intel_crtc_state;
> +enum icl_port_dpll_id;
>  enum phy;
>  
>  bool intel_is_c10phy(struct drm_i915_private *dev_priv, enum phy phy);
>  void intel_mtl_pll_enable(struct intel_encoder *encoder,
>  			  const struct intel_crtc_state *crtc_state);
>  void intel_mtl_pll_disable(struct intel_encoder *encoder);
> +enum icl_port_dpll_id
> +intel_mtl_port_pll_type(struct intel_encoder *encoder,
> +			const struct intel_crtc_state *crtc_state);
>  void intel_c10pll_readout_hw_state(struct intel_encoder *encoder, struct intel_c10pll_state *pll_state);
>  int intel_cx0pll_calc_state(struct intel_crtc_state *crtc_state, struct intel_encoder *encoder);
>  void intel_c10pll_dump_hw_state(struct drm_i915_private *dev_priv,
> diff --git a/drivers/gpu/drm/i915/display/intel_ddi.c b/drivers/gpu/drm/i915/display/intel_ddi.c
> index 8f0f858cde31..55f36d9d509c 100644
> --- a/drivers/gpu/drm/i915/display/intel_ddi.c
> +++ b/drivers/gpu/drm/i915/display/intel_ddi.c
> @@ -4784,6 +4784,7 @@ void intel_ddi_init(struct drm_i915_private *dev_priv, enum port port)
>  	if (DISPLAY_VER(dev_priv) >= 14) {
>  		encoder->enable_clock = intel_mtl_pll_enable;
>  		encoder->disable_clock = intel_mtl_pll_disable;
> +		encoder->port_pll_type = intel_mtl_port_pll_type;
>  		encoder->get_config = mtl_ddi_get_config;
>  	} else if (IS_DG2(dev_priv)) {
>  		encoder->enable_clock = intel_mpllb_enable;
> diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c
> index 1c264c17b6e4..e70bdf0e06f3 100644
> --- a/drivers/gpu/drm/i915/display/intel_display.c
> +++ b/drivers/gpu/drm/i915/display/intel_display.c
> @@ -1757,7 +1757,7 @@ bool intel_phy_is_tc(struct drm_i915_private *dev_priv, enum phy phy)
>  	if (IS_DG2(dev_priv))
>  		/* DG2's "TC1" output uses a SNPS PHY */
>  		return false;
> -	else if (IS_ALDERLAKE_P(dev_priv))
> +	else if (IS_ALDERLAKE_P(dev_priv) || IS_METEORLAKE(dev_priv))
>  		return phy >= PHY_F && phy <= PHY_I;
>  	else if (IS_TIGERLAKE(dev_priv))
>  		return phy >= PHY_D && phy <= PHY_I;
> diff --git a/drivers/gpu/drm/i915/display/intel_tc.c b/drivers/gpu/drm/i915/display/intel_tc.c
> index 3b60995e9dfb..951b12ac51dc 100644
> --- a/drivers/gpu/drm/i915/display/intel_tc.c
> +++ b/drivers/gpu/drm/i915/display/intel_tc.c
> @@ -5,6 +5,7 @@
>  
>  #include "i915_drv.h"
>  #include "i915_reg.h"
> +#include "intel_cx0_phy_regs.h"
>  #include "intel_ddi.h"
>  #include "intel_de.h"
>  #include "intel_display.h"
> @@ -59,6 +60,7 @@ static enum intel_display_power_domain
>  tc_phy_cold_off_domain(struct intel_tc_port *);
>  static u32 tc_phy_hpd_live_status(struct intel_tc_port *tc);
>  static bool tc_phy_is_ready(struct intel_tc_port *tc);
> +static bool tc_phy_wait_for_ready(struct intel_tc_port *tc);
>  static enum tc_port_mode tc_phy_get_current_mode(struct intel_tc_port *tc);
>  
>  static const char *tc_port_mode_name(enum tc_port_mode mode)
> @@ -141,15 +143,23 @@ bool intel_tc_port_in_legacy_mode(struct intel_digital_port *dig_port)
>   *
>   * POWER_DOMAIN_TC_COLD_OFF:
>   * -------------------------
> - * TGL/legacy, DP-alt modes:
> + * ICL/DP-alt, TBT mode:
> + *   - TCSS/TBT: block TC-cold power state for using the (direct or
> + *     TBT DP-IN) AUX and main lanes.
> + *
> + * TGL/all modes:
>   *   - TCSS/IOM,FIA access for PHY ready, owned and HPD live state
> - *   - TCSS/PHY: block TC-cold power state for using the PHY AUX and
> - *     main lanes.
> + *   - TCSS/PHY: block TC-cold power state for using the (direct or
> + *     TBT DP-IN) AUX and main lanes.
>   *
> - * ICL, TGL, ADLP/TBT mode:
> - *   - TCSS/IOM,FIA access for HPD live state
> + * ADLP/TBT mode:
>   *   - TCSS/TBT: block TC-cold power state for using the (TBT DP-IN)
>   *     AUX and main lanes.
> + *
> + * XELPDP+/all modes:
> + *   - TCSS/IOM,FIA access for PHY ready, owned state
> + *   - TCSS/PHY: block TC-cold power state for using the (direct or
> + *     TBT DP-IN) AUX and main lanes.
>   */
>  bool intel_tc_cold_requires_aux_pw(struct intel_digital_port *dig_port)
>  {
> @@ -872,6 +882,172 @@ static const struct intel_tc_phy_ops adlp_tc_phy_ops = {
>  	.init = adlp_tc_phy_init,
>  };
>  
> +/*
> + * XELPDP TC PHY handlers
> + * ----------------------
> + */
> +static bool
> +xelpdp_tc_phy_tcss_power_is_enabled(struct intel_tc_port *tc)
> +{
> +	struct drm_i915_private *i915 = tc_to_i915(tc);
> +	enum port port = tc->dig_port->base.port;
> +
> +	assert_tc_cold_blocked(tc);
> +
> +	return intel_de_read(i915, XELPDP_PORT_BUF_CTL1(port)) & XELPDP_TCSS_POWER_STATE;
> +}
> +
> +static bool
> +xelpdp_tc_phy_wait_for_tcss_power(struct intel_tc_port *tc, bool enabled)
> +{
> +	struct drm_i915_private *i915 = tc_to_i915(tc);
> +
> +	if (wait_for(xelpdp_tc_phy_tcss_power_is_enabled(tc) == enabled, 5)) {
> +		drm_dbg_kms(&i915->drm,
> +			    "Port %s: timeout waiting for TCSS power to get %s\n",
> +			    enabled ? "enabled" : "disabled",
> +			    tc->port_name);
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
> +static void __xelpdp_tc_phy_enable_tcss_power(struct intel_tc_port *tc, bool enable)
> +{
> +	struct drm_i915_private *i915 = tc_to_i915(tc);
> +	enum port port = tc->dig_port->base.port;
> +	u32 val;
> +
> +	assert_tc_cold_blocked(tc);
> +
> +	val = intel_de_read(i915, XELPDP_PORT_BUF_CTL1(port));
> +	if (enable)
> +		val |= XELPDP_TCSS_POWER_REQUEST;
> +	else
> +		val &= ~XELPDP_TCSS_POWER_REQUEST;
> +	intel_de_write(i915, XELPDP_PORT_BUF_CTL1(port), val);
> +}
> +
> +static bool xelpdp_tc_phy_enable_tcss_power(struct intel_tc_port *tc, bool enable)
> +{
> +	struct drm_i915_private *i915 = tc_to_i915(tc);
> +
> +	__xelpdp_tc_phy_enable_tcss_power(tc, enable);
> +
> +	if ((!tc_phy_wait_for_ready(tc) ||
> +	     !xelpdp_tc_phy_wait_for_tcss_power(tc, enable)) &&
> +	    !drm_WARN_ON(&i915->drm, tc->mode == TC_PORT_LEGACY)) {
> +		if (enable) {
> +			__xelpdp_tc_phy_enable_tcss_power(tc, false);
> +			xelpdp_tc_phy_wait_for_tcss_power(tc, false);
> +		}
> +
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
> +static void xelpdp_tc_phy_take_ownership(struct intel_tc_port *tc, bool take)
> +{
> +	struct drm_i915_private *i915 = tc_to_i915(tc);
> +	enum port port = tc->dig_port->base.port;
> +	u32 val;
> +
> +	assert_tc_cold_blocked(tc);
> +
> +	val = intel_de_read(i915, XELPDP_PORT_BUF_CTL1(port));
> +	if (take)
> +		val |= XELPDP_TC_PHY_OWNERSHIP;
> +	else
> +		val &= ~XELPDP_TC_PHY_OWNERSHIP;
> +	intel_de_write(i915, XELPDP_PORT_BUF_CTL1(port), val);
> +}
> +
> +static bool xelpdp_tc_phy_is_owned(struct intel_tc_port *tc)
> +{
> +	struct drm_i915_private *i915 = tc_to_i915(tc);
> +	enum port port = tc->dig_port->base.port;
> +
> +	assert_tc_cold_blocked(tc);
> +
> +	return intel_de_read(i915, XELPDP_PORT_BUF_CTL1(port)) & XELPDP_TC_PHY_OWNERSHIP;
> +}
> +
> +static void xelpdp_tc_phy_get_hw_state(struct intel_tc_port *tc)
> +{
> +	struct drm_i915_private *i915 = tc_to_i915(tc);
> +	intel_wakeref_t tc_cold_wref;
> +	enum intel_display_power_domain domain;
> +
> +	tc_cold_wref = __tc_cold_block(tc, &domain);
> +
> +	tc->mode = tc_phy_get_current_mode(tc);
> +	if (tc->mode != TC_PORT_DISCONNECTED)
> +		tc->lock_wakeref = tc_cold_block(tc);
> +
> +	drm_WARN_ON(&i915->drm,
> +		    (tc->mode == TC_PORT_DP_ALT || tc->mode == TC_PORT_LEGACY) &&
> +		    !xelpdp_tc_phy_tcss_power_is_enabled(tc));
> +
> +	__tc_cold_unblock(tc, domain, tc_cold_wref);
> +}
> +
> +static bool xelpdp_tc_phy_connect(struct intel_tc_port *tc, int required_lanes)
> +{
> +	tc->lock_wakeref = tc_cold_block(tc);
> +
> +	if (tc->mode == TC_PORT_TBT_ALT)
> +		return true;
> +
> +	if (!xelpdp_tc_phy_enable_tcss_power(tc, true))
> +		goto out_unblock_tccold;
> +
> +	xelpdp_tc_phy_take_ownership(tc, true);
> +
> +	if (!tc_phy_verify_legacy_or_dp_alt_mode(tc, required_lanes))
> +		goto out_release_phy;
> +
> +	return true;
> +
> +out_release_phy:
> +	xelpdp_tc_phy_take_ownership(tc, false);
> +	xelpdp_tc_phy_wait_for_tcss_power(tc, false);
> +
> +out_unblock_tccold:
> +	tc_cold_unblock(tc, fetch_and_zero(&tc->lock_wakeref));
> +
> +	return false;
> +}
> +
> +static void xelpdp_tc_phy_disconnect(struct intel_tc_port *tc)
> +{
> +	switch (tc->mode) {
> +	case TC_PORT_LEGACY:
> +	case TC_PORT_DP_ALT:
> +		xelpdp_tc_phy_take_ownership(tc, false);
> +		xelpdp_tc_phy_enable_tcss_power(tc, false);
> +		fallthrough;
> +	case TC_PORT_TBT_ALT:
> +		tc_cold_unblock(tc, fetch_and_zero(&tc->lock_wakeref));
> +		break;
> +	default:
> +		MISSING_CASE(tc->mode);
> +	}
> +}
> +
> +static const struct intel_tc_phy_ops xelpdp_tc_phy_ops = {
> +	.cold_off_domain = tgl_tc_phy_cold_off_domain,
> +	.hpd_live_status = adlp_tc_phy_hpd_live_status,
> +	.is_ready = adlp_tc_phy_is_ready,
> +	.is_owned = xelpdp_tc_phy_is_owned,
> +	.get_hw_state = xelpdp_tc_phy_get_hw_state,
> +	.connect = xelpdp_tc_phy_connect,
> +	.disconnect = xelpdp_tc_phy_disconnect,
> +	.init = adlp_tc_phy_init,
> +};
> +
>  /*
>   * Generic TC PHY handlers
>   * -----------------------
> @@ -945,13 +1121,18 @@ static bool tc_phy_is_connected(struct intel_tc_port *tc,
>  	return is_connected;
>  }
>  
> -static void tc_phy_wait_for_ready(struct intel_tc_port *tc)
> +static bool tc_phy_wait_for_ready(struct intel_tc_port *tc)
>  {
>  	struct drm_i915_private *i915 = tc_to_i915(tc);
>  
> -	if (wait_for(tc_phy_is_ready(tc), 100))
> +	if (wait_for(tc_phy_is_ready(tc), 500)) {
>  		drm_err(&i915->drm, "Port %s: timeout waiting for PHY ready\n",
>  			tc->port_name);
> +
> +		return false;
> +	}
> +
> +	return true;
>  }
>  
>  static enum tc_port_mode
> @@ -1442,7 +1623,9 @@ int intel_tc_port_init(struct intel_digital_port *dig_port, bool is_legacy)
>  	dig_port->tc = tc;
>  	tc->dig_port = dig_port;
>  
> -	if (DISPLAY_VER(i915) >= 13)
> +	if (DISPLAY_VER(i915) >= 14)
> +		tc->phy_ops = &xelpdp_tc_phy_ops;
> +	else if (DISPLAY_VER(i915) >= 13)
>  		tc->phy_ops = &adlp_tc_phy_ops;
>  	else if (DISPLAY_VER(i915) >= 12)
>  		tc->phy_ops = &tgl_tc_phy_ops;
> -- 
> 2.34.1
>
diff mbox series

Patch

diff --git a/drivers/gpu/drm/i915/display/intel_cx0_phy.c b/drivers/gpu/drm/i915/display/intel_cx0_phy.c
index dc0555505e61..97d80adb921f 100644
--- a/drivers/gpu/drm/i915/display/intel_cx0_phy.c
+++ b/drivers/gpu/drm/i915/display/intel_cx0_phy.c
@@ -2884,6 +2884,25 @@  void intel_mtl_pll_disable(struct intel_encoder *encoder)
 		intel_cx0pll_disable(encoder);
 }
 
+enum icl_port_dpll_id
+intel_mtl_port_pll_type(struct intel_encoder *encoder,
+			const struct intel_crtc_state *crtc_state)
+{
+	struct drm_i915_private *i915 = to_i915(encoder->base.dev);
+	/*
+	 * TODO: Determine the PLL type from the SW state, once MTL PLL
+	 * handling is done via the standard shared DPLL framework.
+	 */
+	u32 val = intel_de_read(i915, XELPDP_PORT_CLOCK_CTL(encoder->port));
+	u32 clock = REG_FIELD_GET(XELPDP_DDI_CLOCK_SELECT_MASK, val);
+
+	if (clock == XELPDP_DDI_CLOCK_SELECT_MAXPCLK ||
+	    clock == XELPDP_DDI_CLOCK_SELECT_DIV18CLK)
+		return ICL_PORT_DPLL_MG_PHY;
+	else
+		return ICL_PORT_DPLL_DEFAULT;
+}
+
 void intel_c10pll_state_verify(struct intel_atomic_state *state,
 			       struct intel_crtc_state *new_crtc_state)
 {
diff --git a/drivers/gpu/drm/i915/display/intel_cx0_phy.h b/drivers/gpu/drm/i915/display/intel_cx0_phy.h
index c1b8f7980f69..f99809af257d 100644
--- a/drivers/gpu/drm/i915/display/intel_cx0_phy.h
+++ b/drivers/gpu/drm/i915/display/intel_cx0_phy.h
@@ -16,12 +16,16 @@ 
 struct drm_i915_private;
 struct intel_encoder;
 struct intel_crtc_state;
+enum icl_port_dpll_id;
 enum phy;
 
 bool intel_is_c10phy(struct drm_i915_private *dev_priv, enum phy phy);
 void intel_mtl_pll_enable(struct intel_encoder *encoder,
 			  const struct intel_crtc_state *crtc_state);
 void intel_mtl_pll_disable(struct intel_encoder *encoder);
+enum icl_port_dpll_id
+intel_mtl_port_pll_type(struct intel_encoder *encoder,
+			const struct intel_crtc_state *crtc_state);
 void intel_c10pll_readout_hw_state(struct intel_encoder *encoder, struct intel_c10pll_state *pll_state);
 int intel_cx0pll_calc_state(struct intel_crtc_state *crtc_state, struct intel_encoder *encoder);
 void intel_c10pll_dump_hw_state(struct drm_i915_private *dev_priv,
diff --git a/drivers/gpu/drm/i915/display/intel_ddi.c b/drivers/gpu/drm/i915/display/intel_ddi.c
index 8f0f858cde31..55f36d9d509c 100644
--- a/drivers/gpu/drm/i915/display/intel_ddi.c
+++ b/drivers/gpu/drm/i915/display/intel_ddi.c
@@ -4784,6 +4784,7 @@  void intel_ddi_init(struct drm_i915_private *dev_priv, enum port port)
 	if (DISPLAY_VER(dev_priv) >= 14) {
 		encoder->enable_clock = intel_mtl_pll_enable;
 		encoder->disable_clock = intel_mtl_pll_disable;
+		encoder->port_pll_type = intel_mtl_port_pll_type;
 		encoder->get_config = mtl_ddi_get_config;
 	} else if (IS_DG2(dev_priv)) {
 		encoder->enable_clock = intel_mpllb_enable;
diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c
index 1c264c17b6e4..e70bdf0e06f3 100644
--- a/drivers/gpu/drm/i915/display/intel_display.c
+++ b/drivers/gpu/drm/i915/display/intel_display.c
@@ -1757,7 +1757,7 @@  bool intel_phy_is_tc(struct drm_i915_private *dev_priv, enum phy phy)
 	if (IS_DG2(dev_priv))
 		/* DG2's "TC1" output uses a SNPS PHY */
 		return false;
-	else if (IS_ALDERLAKE_P(dev_priv))
+	else if (IS_ALDERLAKE_P(dev_priv) || IS_METEORLAKE(dev_priv))
 		return phy >= PHY_F && phy <= PHY_I;
 	else if (IS_TIGERLAKE(dev_priv))
 		return phy >= PHY_D && phy <= PHY_I;
diff --git a/drivers/gpu/drm/i915/display/intel_tc.c b/drivers/gpu/drm/i915/display/intel_tc.c
index 3b60995e9dfb..951b12ac51dc 100644
--- a/drivers/gpu/drm/i915/display/intel_tc.c
+++ b/drivers/gpu/drm/i915/display/intel_tc.c
@@ -5,6 +5,7 @@ 
 
 #include "i915_drv.h"
 #include "i915_reg.h"
+#include "intel_cx0_phy_regs.h"
 #include "intel_ddi.h"
 #include "intel_de.h"
 #include "intel_display.h"
@@ -59,6 +60,7 @@  static enum intel_display_power_domain
 tc_phy_cold_off_domain(struct intel_tc_port *);
 static u32 tc_phy_hpd_live_status(struct intel_tc_port *tc);
 static bool tc_phy_is_ready(struct intel_tc_port *tc);
+static bool tc_phy_wait_for_ready(struct intel_tc_port *tc);
 static enum tc_port_mode tc_phy_get_current_mode(struct intel_tc_port *tc);
 
 static const char *tc_port_mode_name(enum tc_port_mode mode)
@@ -141,15 +143,23 @@  bool intel_tc_port_in_legacy_mode(struct intel_digital_port *dig_port)
  *
  * POWER_DOMAIN_TC_COLD_OFF:
  * -------------------------
- * TGL/legacy, DP-alt modes:
+ * ICL/DP-alt, TBT mode:
+ *   - TCSS/TBT: block TC-cold power state for using the (direct or
+ *     TBT DP-IN) AUX and main lanes.
+ *
+ * TGL/all modes:
  *   - TCSS/IOM,FIA access for PHY ready, owned and HPD live state
- *   - TCSS/PHY: block TC-cold power state for using the PHY AUX and
- *     main lanes.
+ *   - TCSS/PHY: block TC-cold power state for using the (direct or
+ *     TBT DP-IN) AUX and main lanes.
  *
- * ICL, TGL, ADLP/TBT mode:
- *   - TCSS/IOM,FIA access for HPD live state
+ * ADLP/TBT mode:
  *   - TCSS/TBT: block TC-cold power state for using the (TBT DP-IN)
  *     AUX and main lanes.
+ *
+ * XELPDP+/all modes:
+ *   - TCSS/IOM,FIA access for PHY ready, owned state
+ *   - TCSS/PHY: block TC-cold power state for using the (direct or
+ *     TBT DP-IN) AUX and main lanes.
  */
 bool intel_tc_cold_requires_aux_pw(struct intel_digital_port *dig_port)
 {
@@ -872,6 +882,172 @@  static const struct intel_tc_phy_ops adlp_tc_phy_ops = {
 	.init = adlp_tc_phy_init,
 };
 
+/*
+ * XELPDP TC PHY handlers
+ * ----------------------
+ */
+static bool
+xelpdp_tc_phy_tcss_power_is_enabled(struct intel_tc_port *tc)
+{
+	struct drm_i915_private *i915 = tc_to_i915(tc);
+	enum port port = tc->dig_port->base.port;
+
+	assert_tc_cold_blocked(tc);
+
+	return intel_de_read(i915, XELPDP_PORT_BUF_CTL1(port)) & XELPDP_TCSS_POWER_STATE;
+}
+
+static bool
+xelpdp_tc_phy_wait_for_tcss_power(struct intel_tc_port *tc, bool enabled)
+{
+	struct drm_i915_private *i915 = tc_to_i915(tc);
+
+	if (wait_for(xelpdp_tc_phy_tcss_power_is_enabled(tc) == enabled, 5)) {
+		drm_dbg_kms(&i915->drm,
+			    "Port %s: timeout waiting for TCSS power to get %s\n",
+			    enabled ? "enabled" : "disabled",
+			    tc->port_name);
+		return false;
+	}
+
+	return true;
+}
+
+static void __xelpdp_tc_phy_enable_tcss_power(struct intel_tc_port *tc, bool enable)
+{
+	struct drm_i915_private *i915 = tc_to_i915(tc);
+	enum port port = tc->dig_port->base.port;
+	u32 val;
+
+	assert_tc_cold_blocked(tc);
+
+	val = intel_de_read(i915, XELPDP_PORT_BUF_CTL1(port));
+	if (enable)
+		val |= XELPDP_TCSS_POWER_REQUEST;
+	else
+		val &= ~XELPDP_TCSS_POWER_REQUEST;
+	intel_de_write(i915, XELPDP_PORT_BUF_CTL1(port), val);
+}
+
+static bool xelpdp_tc_phy_enable_tcss_power(struct intel_tc_port *tc, bool enable)
+{
+	struct drm_i915_private *i915 = tc_to_i915(tc);
+
+	__xelpdp_tc_phy_enable_tcss_power(tc, enable);
+
+	if ((!tc_phy_wait_for_ready(tc) ||
+	     !xelpdp_tc_phy_wait_for_tcss_power(tc, enable)) &&
+	    !drm_WARN_ON(&i915->drm, tc->mode == TC_PORT_LEGACY)) {
+		if (enable) {
+			__xelpdp_tc_phy_enable_tcss_power(tc, false);
+			xelpdp_tc_phy_wait_for_tcss_power(tc, false);
+		}
+
+		return false;
+	}
+
+	return true;
+}
+
+static void xelpdp_tc_phy_take_ownership(struct intel_tc_port *tc, bool take)
+{
+	struct drm_i915_private *i915 = tc_to_i915(tc);
+	enum port port = tc->dig_port->base.port;
+	u32 val;
+
+	assert_tc_cold_blocked(tc);
+
+	val = intel_de_read(i915, XELPDP_PORT_BUF_CTL1(port));
+	if (take)
+		val |= XELPDP_TC_PHY_OWNERSHIP;
+	else
+		val &= ~XELPDP_TC_PHY_OWNERSHIP;
+	intel_de_write(i915, XELPDP_PORT_BUF_CTL1(port), val);
+}
+
+static bool xelpdp_tc_phy_is_owned(struct intel_tc_port *tc)
+{
+	struct drm_i915_private *i915 = tc_to_i915(tc);
+	enum port port = tc->dig_port->base.port;
+
+	assert_tc_cold_blocked(tc);
+
+	return intel_de_read(i915, XELPDP_PORT_BUF_CTL1(port)) & XELPDP_TC_PHY_OWNERSHIP;
+}
+
+static void xelpdp_tc_phy_get_hw_state(struct intel_tc_port *tc)
+{
+	struct drm_i915_private *i915 = tc_to_i915(tc);
+	intel_wakeref_t tc_cold_wref;
+	enum intel_display_power_domain domain;
+
+	tc_cold_wref = __tc_cold_block(tc, &domain);
+
+	tc->mode = tc_phy_get_current_mode(tc);
+	if (tc->mode != TC_PORT_DISCONNECTED)
+		tc->lock_wakeref = tc_cold_block(tc);
+
+	drm_WARN_ON(&i915->drm,
+		    (tc->mode == TC_PORT_DP_ALT || tc->mode == TC_PORT_LEGACY) &&
+		    !xelpdp_tc_phy_tcss_power_is_enabled(tc));
+
+	__tc_cold_unblock(tc, domain, tc_cold_wref);
+}
+
+static bool xelpdp_tc_phy_connect(struct intel_tc_port *tc, int required_lanes)
+{
+	tc->lock_wakeref = tc_cold_block(tc);
+
+	if (tc->mode == TC_PORT_TBT_ALT)
+		return true;
+
+	if (!xelpdp_tc_phy_enable_tcss_power(tc, true))
+		goto out_unblock_tccold;
+
+	xelpdp_tc_phy_take_ownership(tc, true);
+
+	if (!tc_phy_verify_legacy_or_dp_alt_mode(tc, required_lanes))
+		goto out_release_phy;
+
+	return true;
+
+out_release_phy:
+	xelpdp_tc_phy_take_ownership(tc, false);
+	xelpdp_tc_phy_wait_for_tcss_power(tc, false);
+
+out_unblock_tccold:
+	tc_cold_unblock(tc, fetch_and_zero(&tc->lock_wakeref));
+
+	return false;
+}
+
+static void xelpdp_tc_phy_disconnect(struct intel_tc_port *tc)
+{
+	switch (tc->mode) {
+	case TC_PORT_LEGACY:
+	case TC_PORT_DP_ALT:
+		xelpdp_tc_phy_take_ownership(tc, false);
+		xelpdp_tc_phy_enable_tcss_power(tc, false);
+		fallthrough;
+	case TC_PORT_TBT_ALT:
+		tc_cold_unblock(tc, fetch_and_zero(&tc->lock_wakeref));
+		break;
+	default:
+		MISSING_CASE(tc->mode);
+	}
+}
+
+static const struct intel_tc_phy_ops xelpdp_tc_phy_ops = {
+	.cold_off_domain = tgl_tc_phy_cold_off_domain,
+	.hpd_live_status = adlp_tc_phy_hpd_live_status,
+	.is_ready = adlp_tc_phy_is_ready,
+	.is_owned = xelpdp_tc_phy_is_owned,
+	.get_hw_state = xelpdp_tc_phy_get_hw_state,
+	.connect = xelpdp_tc_phy_connect,
+	.disconnect = xelpdp_tc_phy_disconnect,
+	.init = adlp_tc_phy_init,
+};
+
 /*
  * Generic TC PHY handlers
  * -----------------------
@@ -945,13 +1121,18 @@  static bool tc_phy_is_connected(struct intel_tc_port *tc,
 	return is_connected;
 }
 
-static void tc_phy_wait_for_ready(struct intel_tc_port *tc)
+static bool tc_phy_wait_for_ready(struct intel_tc_port *tc)
 {
 	struct drm_i915_private *i915 = tc_to_i915(tc);
 
-	if (wait_for(tc_phy_is_ready(tc), 100))
+	if (wait_for(tc_phy_is_ready(tc), 500)) {
 		drm_err(&i915->drm, "Port %s: timeout waiting for PHY ready\n",
 			tc->port_name);
+
+		return false;
+	}
+
+	return true;
 }
 
 static enum tc_port_mode
@@ -1442,7 +1623,9 @@  int intel_tc_port_init(struct intel_digital_port *dig_port, bool is_legacy)
 	dig_port->tc = tc;
 	tc->dig_port = dig_port;
 
-	if (DISPLAY_VER(i915) >= 13)
+	if (DISPLAY_VER(i915) >= 14)
+		tc->phy_ops = &xelpdp_tc_phy_ops;
+	else if (DISPLAY_VER(i915) >= 13)
 		tc->phy_ops = &adlp_tc_phy_ops;
 	else if (DISPLAY_VER(i915) >= 12)
 		tc->phy_ops = &tgl_tc_phy_ops;