summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/i915/display/intel_tc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/i915/display/intel_tc.c')
-rw-r--r--drivers/gpu/drm/i915/display/intel_tc.c322
1 files changed, 257 insertions, 65 deletions
diff --git a/drivers/gpu/drm/i915/display/intel_tc.c b/drivers/gpu/drm/i915/display/intel_tc.c
index f45328712bff..bd8c9df5f98f 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_ddi.h"
#include "intel_de.h"
#include "intel_display.h"
#include "intel_display_power_map.h"
@@ -118,6 +119,24 @@ assert_tc_cold_blocked(struct intel_digital_port *dig_port)
drm_WARN_ON(&i915->drm, !enabled);
}
+static enum intel_display_power_domain
+tc_port_power_domain(struct intel_digital_port *dig_port)
+{
+ struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev);
+ enum tc_port tc_port = intel_port_to_tc(i915, dig_port->base.port);
+
+ return POWER_DOMAIN_PORT_DDI_LANES_TC1 + tc_port - TC_PORT_1;
+}
+
+static void
+assert_tc_port_power_enabled(struct intel_digital_port *dig_port)
+{
+ struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev);
+
+ drm_WARN_ON(&i915->drm,
+ !intel_display_power_is_enabled(i915, tc_port_power_domain(dig_port)));
+}
+
u32 intel_tc_port_get_lane_mask(struct intel_digital_port *dig_port)
{
struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev);
@@ -418,9 +437,9 @@ static bool icl_tc_phy_is_owned(struct intel_digital_port *dig_port)
val = intel_de_read(i915, PORT_TX_DFLEXDPCSSS(dig_port->tc_phy_fia));
if (val == 0xffffffff) {
drm_dbg_kms(&i915->drm,
- "Port %s: PHY in TCCOLD, assume safe mode\n",
+ "Port %s: PHY in TCCOLD, assume not owned\n",
dig_port->tc_port_name);
- return true;
+ return false;
}
return val & DP_PHY_MODE_STATUS_NOT_SAFE(dig_port->tc_phy_fia_idx);
@@ -464,7 +483,8 @@ static void icl_tc_phy_connect(struct intel_digital_port *dig_port,
u32 live_status_mask;
int max_lanes;
- if (!tc_phy_status_complete(dig_port)) {
+ if (!tc_phy_status_complete(dig_port) &&
+ !drm_WARN_ON(&i915->drm, dig_port->tc_legacy_port)) {
drm_dbg_kms(&i915->drm, "Port %s: PHY not ready\n",
dig_port->tc_port_name);
goto out_set_tbt_alt_mode;
@@ -539,62 +559,171 @@ static void icl_tc_phy_disconnect(struct intel_digital_port *dig_port)
}
}
-static bool icl_tc_phy_is_connected(struct intel_digital_port *dig_port)
+static bool tc_phy_is_ready_and_owned(struct intel_digital_port *dig_port,
+ bool phy_is_ready, bool phy_is_owned)
{
struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev);
- if (!tc_phy_status_complete(dig_port)) {
- drm_dbg_kms(&i915->drm, "Port %s: PHY status not complete\n",
- dig_port->tc_port_name);
- return dig_port->tc_mode == TC_PORT_TBT_ALT;
- }
+ drm_WARN_ON(&i915->drm, phy_is_owned && !phy_is_ready);
+
+ return phy_is_ready && phy_is_owned;
+}
- /* On ADL-P the PHY complete flag is set in TBT mode as well. */
- if (IS_ALDERLAKE_P(i915) && dig_port->tc_mode == TC_PORT_TBT_ALT)
- return true;
+static bool tc_phy_is_connected(struct intel_digital_port *dig_port,
+ enum icl_port_dpll_id port_pll_type)
+{
+ struct intel_encoder *encoder = &dig_port->base;
+ struct drm_i915_private *i915 = to_i915(encoder->base.dev);
+ bool phy_is_ready = tc_phy_status_complete(dig_port);
+ bool phy_is_owned = tc_phy_is_owned(dig_port);
+ bool is_connected;
- if (!tc_phy_is_owned(dig_port)) {
- drm_dbg_kms(&i915->drm, "Port %s: PHY not owned\n",
- dig_port->tc_port_name);
+ if (tc_phy_is_ready_and_owned(dig_port, phy_is_ready, phy_is_owned))
+ is_connected = port_pll_type == ICL_PORT_DPLL_MG_PHY;
+ else
+ is_connected = port_pll_type == ICL_PORT_DPLL_DEFAULT;
- return false;
+ drm_dbg_kms(&i915->drm,
+ "Port %s: PHY connected: %s (ready: %s, owned: %s, pll_type: %s)\n",
+ dig_port->tc_port_name,
+ str_yes_no(is_connected),
+ str_yes_no(phy_is_ready),
+ str_yes_no(phy_is_owned),
+ port_pll_type == ICL_PORT_DPLL_DEFAULT ? "tbt" : "non-tbt");
+
+ return is_connected;
+}
+
+static void tc_phy_wait_for_ready(struct intel_digital_port *dig_port)
+{
+ struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev);
+
+ if (wait_for(tc_phy_status_complete(dig_port), 100))
+ drm_err(&i915->drm, "Port %s: timeout waiting for PHY ready\n",
+ dig_port->tc_port_name);
+}
+
+static enum tc_port_mode
+hpd_mask_to_tc_mode(u32 live_status_mask)
+{
+ if (live_status_mask)
+ return fls(live_status_mask) - 1;
+
+ return TC_PORT_DISCONNECTED;
+}
+
+static enum tc_port_mode
+tc_phy_hpd_live_mode(struct intel_digital_port *dig_port)
+{
+ u32 live_status_mask = tc_port_live_status_mask(dig_port);
+
+ return hpd_mask_to_tc_mode(live_status_mask);
+}
+
+static enum tc_port_mode
+get_tc_mode_in_phy_owned_state(struct intel_digital_port *dig_port,
+ enum tc_port_mode live_mode)
+{
+ switch (live_mode) {
+ case TC_PORT_LEGACY:
+ case TC_PORT_DP_ALT:
+ return live_mode;
+ default:
+ MISSING_CASE(live_mode);
+ fallthrough;
+ case TC_PORT_TBT_ALT:
+ case TC_PORT_DISCONNECTED:
+ if (dig_port->tc_legacy_port)
+ return TC_PORT_LEGACY;
+ else
+ return TC_PORT_DP_ALT;
}
+}
- return dig_port->tc_mode == TC_PORT_DP_ALT ||
- dig_port->tc_mode == TC_PORT_LEGACY;
+static enum tc_port_mode
+get_tc_mode_in_phy_not_owned_state(struct intel_digital_port *dig_port,
+ enum tc_port_mode live_mode)
+{
+ switch (live_mode) {
+ case TC_PORT_LEGACY:
+ return TC_PORT_DISCONNECTED;
+ case TC_PORT_DP_ALT:
+ case TC_PORT_TBT_ALT:
+ return TC_PORT_TBT_ALT;
+ default:
+ MISSING_CASE(live_mode);
+ fallthrough;
+ case TC_PORT_DISCONNECTED:
+ if (dig_port->tc_legacy_port)
+ return TC_PORT_DISCONNECTED;
+ else
+ return TC_PORT_TBT_ALT;
+ }
}
static enum tc_port_mode
intel_tc_port_get_current_mode(struct intel_digital_port *dig_port)
{
struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev);
- u32 live_status_mask = tc_port_live_status_mask(dig_port);
+ enum tc_port_mode live_mode = tc_phy_hpd_live_mode(dig_port);
+ bool phy_is_ready;
+ bool phy_is_owned;
enum tc_port_mode mode;
- if (!tc_phy_is_owned(dig_port) ||
- drm_WARN_ON(&i915->drm, !tc_phy_status_complete(dig_port)))
- return TC_PORT_TBT_ALT;
+ /*
+ * For legacy ports the IOM firmware initializes the PHY during boot-up
+ * and system resume whether or not a sink is connected. Wait here for
+ * the initialization to get ready.
+ */
+ if (dig_port->tc_legacy_port)
+ tc_phy_wait_for_ready(dig_port);
- mode = dig_port->tc_legacy_port ? TC_PORT_LEGACY : TC_PORT_DP_ALT;
- if (live_status_mask) {
- enum tc_port_mode live_mode = fls(live_status_mask) - 1;
+ phy_is_ready = tc_phy_status_complete(dig_port);
+ phy_is_owned = tc_phy_is_owned(dig_port);
- if (!drm_WARN_ON(&i915->drm, live_mode == TC_PORT_TBT_ALT))
- mode = live_mode;
+ if (!tc_phy_is_ready_and_owned(dig_port, phy_is_ready, phy_is_owned)) {
+ mode = get_tc_mode_in_phy_not_owned_state(dig_port, live_mode);
+ } else {
+ drm_WARN_ON(&i915->drm, live_mode == TC_PORT_TBT_ALT);
+ mode = get_tc_mode_in_phy_owned_state(dig_port, live_mode);
}
+ drm_dbg_kms(&i915->drm,
+ "Port %s: PHY mode: %s (ready: %s, owned: %s, HPD: %s)\n",
+ dig_port->tc_port_name,
+ tc_port_mode_name(mode),
+ str_yes_no(phy_is_ready),
+ str_yes_no(phy_is_owned),
+ tc_port_mode_name(live_mode));
+
return mode;
}
+static enum tc_port_mode default_tc_mode(struct intel_digital_port *dig_port)
+{
+ if (dig_port->tc_legacy_port)
+ return TC_PORT_LEGACY;
+
+ return TC_PORT_TBT_ALT;
+}
+
+static enum tc_port_mode
+hpd_mask_to_target_mode(struct intel_digital_port *dig_port, u32 live_status_mask)
+{
+ enum tc_port_mode mode = hpd_mask_to_tc_mode(live_status_mask);
+
+ if (mode != TC_PORT_DISCONNECTED)
+ return mode;
+
+ return default_tc_mode(dig_port);
+}
+
static enum tc_port_mode
intel_tc_port_get_target_mode(struct intel_digital_port *dig_port)
{
u32 live_status_mask = tc_port_live_status_mask(dig_port);
- if (live_status_mask)
- return fls(live_status_mask) - 1;
-
- return TC_PORT_TBT_ALT;
+ return hpd_mask_to_target_mode(dig_port, live_status_mask);
}
static void intel_tc_port_reset_mode(struct intel_digital_port *dig_port,
@@ -660,11 +789,24 @@ static void intel_tc_port_update_mode(struct intel_digital_port *dig_port,
tc_cold_unblock(dig_port, domain, wref);
}
-static void
-intel_tc_port_link_init_refcount(struct intel_digital_port *dig_port,
- int refcount)
+static void __intel_tc_port_get_link(struct intel_digital_port *dig_port)
+{
+ dig_port->tc_link_refcount++;
+}
+
+static void __intel_tc_port_put_link(struct intel_digital_port *dig_port)
+{
+ dig_port->tc_link_refcount--;
+}
+
+static bool tc_port_is_enabled(struct intel_digital_port *dig_port)
{
- dig_port->tc_link_refcount = refcount;
+ struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev);
+
+ assert_tc_port_power_enabled(dig_port);
+
+ return intel_de_read(i915, DDI_BUF_CTL(dig_port->base.port)) &
+ DDI_BUF_CTL_ENABLE;
}
/**
@@ -679,6 +821,7 @@ void intel_tc_port_init_mode(struct intel_digital_port *dig_port)
struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev);
intel_wakeref_t tc_cold_wref;
enum intel_display_power_domain domain;
+ bool update_mode = false;
mutex_lock(&dig_port->tc_lock);
@@ -689,63 +832,105 @@ void intel_tc_port_init_mode(struct intel_digital_port *dig_port)
tc_cold_wref = tc_cold_block(dig_port, &domain);
dig_port->tc_mode = intel_tc_port_get_current_mode(dig_port);
+ /*
+ * Save the initial mode for the state check in
+ * intel_tc_port_sanitize_mode().
+ */
+ dig_port->tc_init_mode = dig_port->tc_mode;
+ if (dig_port->tc_mode != TC_PORT_DISCONNECTED)
+ dig_port->tc_lock_wakeref =
+ tc_cold_block(dig_port, &dig_port->tc_lock_power_domain);
+
+ /*
+ * The PHY needs to be connected for AUX to work during HW readout and
+ * MST topology resume, but the PHY mode can only be changed if the
+ * port is disabled.
+ *
+ * An exception is the case where BIOS leaves the PHY incorrectly
+ * disconnected on an enabled legacy port. Work around that by
+ * connecting the PHY even though the port is enabled. This doesn't
+ * cause a problem as the PHY ownership state is ignored by the
+ * IOM/TCSS firmware (only display can own the PHY in that case).
+ */
+ if (!tc_port_is_enabled(dig_port)) {
+ update_mode = true;
+ } else if (dig_port->tc_mode == TC_PORT_DISCONNECTED) {
+ drm_WARN_ON(&i915->drm, !dig_port->tc_legacy_port);
+ drm_err(&i915->drm,
+ "Port %s: PHY disconnected on enabled port, connecting it\n",
+ dig_port->tc_port_name);
+ update_mode = true;
+ }
+
+ if (update_mode)
+ intel_tc_port_update_mode(dig_port, 1, false);
+
/* Prevent changing dig_port->tc_mode until intel_tc_port_sanitize_mode() is called. */
- intel_tc_port_link_init_refcount(dig_port, 1);
- dig_port->tc_lock_wakeref = tc_cold_block(dig_port, &dig_port->tc_lock_power_domain);
+ __intel_tc_port_get_link(dig_port);
tc_cold_unblock(dig_port, domain, tc_cold_wref);
- drm_dbg_kms(&i915->drm, "Port %s: init mode (%s)\n",
- dig_port->tc_port_name,
- tc_port_mode_name(dig_port->tc_mode));
-
mutex_unlock(&dig_port->tc_lock);
}
+static bool tc_port_has_active_links(struct intel_digital_port *dig_port,
+ const struct intel_crtc_state *crtc_state)
+{
+ struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev);
+ enum icl_port_dpll_id pll_type = ICL_PORT_DPLL_DEFAULT;
+ int active_links = 0;
+
+ if (dig_port->dp.is_mst) {
+ /* TODO: get the PLL type for MST, once HW readout is done for it. */
+ active_links = intel_dp_mst_encoder_active_links(dig_port);
+ } else if (crtc_state && crtc_state->hw.active) {
+ pll_type = intel_ddi_port_pll_type(&dig_port->base, crtc_state);
+ active_links = 1;
+ }
+
+ if (active_links && !tc_phy_is_connected(dig_port, pll_type))
+ drm_err(&i915->drm,
+ "Port %s: PHY disconnected with %d active link(s)\n",
+ dig_port->tc_port_name, active_links);
+
+ return active_links;
+}
+
/**
* intel_tc_port_sanitize_mode: Sanitize the given port's TypeC mode
* @dig_port: digital port
+ * @crtc_state: atomic state of CRTC connected to @dig_port
*
* Sanitize @dig_port's TypeC mode wrt. the encoder's state right after driver
* loading and system resume:
* If the encoder is enabled keep the TypeC mode/PHY connected state locked until
* the encoder is disabled.
* If the encoder is disabled make sure the PHY is disconnected.
+ * @crtc_state is valid if @dig_port is enabled, NULL otherwise.
*/
-void intel_tc_port_sanitize_mode(struct intel_digital_port *dig_port)
+void intel_tc_port_sanitize_mode(struct intel_digital_port *dig_port,
+ const struct intel_crtc_state *crtc_state)
{
struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev);
- struct intel_encoder *encoder = &dig_port->base;
- int active_links = 0;
mutex_lock(&dig_port->tc_lock);
- if (dig_port->dp.is_mst)
- active_links = intel_dp_mst_encoder_active_links(dig_port);
- else if (encoder->base.crtc)
- active_links = to_intel_crtc(encoder->base.crtc)->active;
-
drm_WARN_ON(&i915->drm, dig_port->tc_link_refcount != 1);
- intel_tc_port_link_init_refcount(dig_port, active_links);
-
- if (active_links) {
- if (!icl_tc_phy_is_connected(dig_port))
- drm_dbg_kms(&i915->drm,
- "Port %s: PHY disconnected with %d active link(s)\n",
- dig_port->tc_port_name, active_links);
- } else {
+ if (!tc_port_has_active_links(dig_port, crtc_state)) {
/*
* TBT-alt is the default mode in any case the PHY ownership is not
* held (regardless of the sink's connected live state), so
* we'll just switch to disconnected mode from it here without
* a note.
*/
- if (dig_port->tc_mode != TC_PORT_TBT_ALT)
+ if (dig_port->tc_init_mode != TC_PORT_TBT_ALT &&
+ dig_port->tc_init_mode != TC_PORT_DISCONNECTED)
drm_dbg_kms(&i915->drm,
"Port %s: PHY left in %s mode on disabled port, disconnecting it\n",
dig_port->tc_port_name,
- tc_port_mode_name(dig_port->tc_mode));
+ tc_port_mode_name(dig_port->tc_init_mode));
icl_tc_phy_disconnect(dig_port);
+ __intel_tc_port_put_link(dig_port);
tc_cold_unblock(dig_port, dig_port->tc_lock_power_domain,
fetch_and_zero(&dig_port->tc_lock_wakeref));
@@ -768,16 +953,23 @@ void intel_tc_port_sanitize_mode(struct intel_digital_port *dig_port)
* connected ports are usable, and avoids exposing to the users objects they
* can't really use.
*/
+bool intel_tc_port_connected_locked(struct intel_encoder *encoder)
+{
+ struct intel_digital_port *dig_port = enc_to_dig_port(encoder);
+ struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev);
+
+ drm_WARN_ON(&i915->drm, !intel_tc_port_ref_held(dig_port));
+
+ return tc_port_live_status_mask(dig_port) & BIT(dig_port->tc_mode);
+}
+
bool intel_tc_port_connected(struct intel_encoder *encoder)
{
struct intel_digital_port *dig_port = enc_to_dig_port(encoder);
bool is_connected;
intel_tc_port_lock(dig_port);
-
- is_connected = tc_port_live_status_mask(dig_port) &
- BIT(dig_port->tc_mode);
-
+ is_connected = intel_tc_port_connected_locked(encoder);
intel_tc_port_unlock(dig_port);
return is_connected;
@@ -857,14 +1049,14 @@ void intel_tc_port_get_link(struct intel_digital_port *dig_port,
int required_lanes)
{
__intel_tc_port_lock(dig_port, required_lanes);
- dig_port->tc_link_refcount++;
+ __intel_tc_port_get_link(dig_port);
intel_tc_port_unlock(dig_port);
}
void intel_tc_port_put_link(struct intel_digital_port *dig_port)
{
intel_tc_port_lock(dig_port);
- --dig_port->tc_link_refcount;
+ __intel_tc_port_put_link(dig_port);
intel_tc_port_unlock(dig_port);
/*