aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm
diff options
context:
space:
mode:
authorPaulo Zanoni <paulo.r.zanoni@intel.com>2018-08-01 13:34:41 -0400
committerPaulo Zanoni <paulo.r.zanoni@intel.com>2018-08-24 15:26:42 -0400
commit39d1e234e1e13f65f4d53715d34aadfb6249eeaf (patch)
tree40d2706a6c2d78e70695c4d758990d2fc4a4eda9 /drivers/gpu/drm
parent62d3a8deaa10b8346d979d0dabde56c33b742afa (diff)
drm/i915/icl: implement the tc/legacy HPD {dis,}connect flows
Unlike the other ports, TC ports are not available to use as soon as we get a hotplug. The TC PHYs can be shared between multiple controllers: display, USB, etc. As a result, handshaking through FIA is required around connect and disconnect to cleanly transfer ownership with the controller and set the type-C power state. This patch implements the flow sequences described by our specification. We opt to grab ownership of the ports as soon as we get the hotplugs in order to simplify the interactions and avoid surprises in the user space side. We may consider changing this in the future, once we improve our testing capabilities on this area. v2: * This unifies the DP and HDMI patches so we can discuss everything at once so people looking at random single patches can actually understand the direction. * I found out the spec was updated a while ago. There's a small difference in the connect flow and the patch was updated for that. * Our spec also now gives a good explanation on what is really happening. As a result, comments were added. * Add some more comments as requested by Rodrigo (Rodrigo). v3: * Downgrade a DRM_ERROR that shouldn't ever happen but we can't act on in case it does (Chris). BSpec: 21750, 4250. Cc: Animesh Manna <animesh.manna@intel.com> Cc: Rodrigo Vivi <rodrigo.vivi@intel.com> Reviewed-by: Rodrigo Vivi <rodrigo.vivi@intel.com> Signed-off-by: Paulo Zanoni <paulo.r.zanoni@intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20180801173441.9789-1-paulo.r.zanoni@intel.com
Diffstat (limited to 'drivers/gpu/drm')
-rw-r--r--drivers/gpu/drm/i915/i915_reg.h6
-rw-r--r--drivers/gpu/drm/i915/intel_dp.c110
-rw-r--r--drivers/gpu/drm/i915/intel_hdmi.c11
3 files changed, 123 insertions, 4 deletions
diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
index a338aaa2b313..8534f88a60f6 100644
--- a/drivers/gpu/drm/i915/i915_reg.h
+++ b/drivers/gpu/drm/i915/i915_reg.h
@@ -10691,4 +10691,10 @@ enum skl_power_gate {
10691#define DP_LANE_ASSIGNMENT_MASK(tc_port) (0xf << ((tc_port) * 8)) 10691#define DP_LANE_ASSIGNMENT_MASK(tc_port) (0xf << ((tc_port) * 8))
10692#define DP_LANE_ASSIGNMENT(tc_port, x) ((x) << ((tc_port) * 8)) 10692#define DP_LANE_ASSIGNMENT(tc_port, x) ((x) << ((tc_port) * 8))
10693 10693
10694#define PORT_TX_DFLEXDPPMS _MMIO(0x163890)
10695#define DP_PHY_MODE_STATUS_COMPLETED(tc_port) (1 << (tc_port))
10696
10697#define PORT_TX_DFLEXDPCSSS _MMIO(0x163894)
10698#define DP_PHY_MODE_STATUS_NOT_SAFE(tc_port) (1 << (tc_port))
10699
10694#endif /* _I915_REG_H_ */ 10700#endif /* _I915_REG_H_ */
diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c
index 8e0e14ba534f..b3f6f04c3c7d 100644
--- a/drivers/gpu/drm/i915/intel_dp.c
+++ b/drivers/gpu/drm/i915/intel_dp.c
@@ -4799,6 +4799,104 @@ static void icl_update_tc_port_type(struct drm_i915_private *dev_priv,
4799 type_str); 4799 type_str);
4800} 4800}
4801 4801
4802/*
4803 * This function implements the first part of the Connect Flow described by our
4804 * specification, Gen11 TypeC Programming chapter. The rest of the flow (reading
4805 * lanes, EDID, etc) is done as needed in the typical places.
4806 *
4807 * Unlike the other ports, type-C ports are not available to use as soon as we
4808 * get a hotplug. The type-C PHYs can be shared between multiple controllers:
4809 * display, USB, etc. As a result, handshaking through FIA is required around
4810 * connect and disconnect to cleanly transfer ownership with the controller and
4811 * set the type-C power state.
4812 *
4813 * We could opt to only do the connect flow when we actually try to use the AUX
4814 * channels or do a modeset, then immediately run the disconnect flow after
4815 * usage, but there are some implications on this for a dynamic environment:
4816 * things may go away or change behind our backs. So for now our driver is
4817 * always trying to acquire ownership of the controller as soon as it gets an
4818 * interrupt (or polls state and sees a port is connected) and only gives it
4819 * back when it sees a disconnect. Implementation of a more fine-grained model
4820 * will require a lot of coordination with user space and thorough testing for
4821 * the extra possible cases.
4822 */
4823static bool icl_tc_phy_connect(struct drm_i915_private *dev_priv,
4824 struct intel_digital_port *dig_port)
4825{
4826 enum tc_port tc_port = intel_port_to_tc(dev_priv, dig_port->base.port);
4827 u32 val;
4828
4829 if (dig_port->tc_type != TC_PORT_LEGACY &&
4830 dig_port->tc_type != TC_PORT_TYPEC)
4831 return true;
4832
4833 val = I915_READ(PORT_TX_DFLEXDPPMS);
4834 if (!(val & DP_PHY_MODE_STATUS_COMPLETED(tc_port))) {
4835 DRM_DEBUG_KMS("DP PHY for TC port %d not ready\n", tc_port);
4836 return false;
4837 }
4838
4839 /*
4840 * This function may be called many times in a row without an HPD event
4841 * in between, so try to avoid the write when we can.
4842 */
4843 val = I915_READ(PORT_TX_DFLEXDPCSSS);
4844 if (!(val & DP_PHY_MODE_STATUS_NOT_SAFE(tc_port))) {
4845 val |= DP_PHY_MODE_STATUS_NOT_SAFE(tc_port);
4846 I915_WRITE(PORT_TX_DFLEXDPCSSS, val);
4847 }
4848
4849 /*
4850 * Now we have to re-check the live state, in case the port recently
4851 * became disconnected. Not necessary for legacy mode.
4852 */
4853 if (dig_port->tc_type == TC_PORT_TYPEC &&
4854 !(I915_READ(PORT_TX_DFLEXDPSP) & TC_LIVE_STATE_TC(tc_port))) {
4855 DRM_DEBUG_KMS("TC PHY %d sudden disconnect.\n", tc_port);
4856 val = I915_READ(PORT_TX_DFLEXDPCSSS);
4857 val &= ~DP_PHY_MODE_STATUS_NOT_SAFE(tc_port);
4858 I915_WRITE(PORT_TX_DFLEXDPCSSS, val);
4859 return false;
4860 }
4861
4862 return true;
4863}
4864
4865/*
4866 * See the comment at the connect function. This implements the Disconnect
4867 * Flow.
4868 */
4869static void icl_tc_phy_disconnect(struct drm_i915_private *dev_priv,
4870 struct intel_digital_port *dig_port)
4871{
4872 enum tc_port tc_port = intel_port_to_tc(dev_priv, dig_port->base.port);
4873 u32 val;
4874
4875 if (dig_port->tc_type != TC_PORT_LEGACY &&
4876 dig_port->tc_type != TC_PORT_TYPEC)
4877 return;
4878
4879 /*
4880 * This function may be called many times in a row without an HPD event
4881 * in between, so try to avoid the write when we can.
4882 */
4883 val = I915_READ(PORT_TX_DFLEXDPCSSS);
4884 if (val & DP_PHY_MODE_STATUS_NOT_SAFE(tc_port)) {
4885 val &= ~DP_PHY_MODE_STATUS_NOT_SAFE(tc_port);
4886 I915_WRITE(PORT_TX_DFLEXDPCSSS, val);
4887 }
4888}
4889
4890/*
4891 * The type-C ports are different because even when they are connected, they may
4892 * not be available/usable by the graphics driver: see the comment on
4893 * icl_tc_phy_connect(). So in our driver instead of adding the additional
4894 * concept of "usable" and make everything check for "connected and usable" we
4895 * define a port as "connected" when it is not only connected, but also when it
4896 * is usable by the rest of the driver. That maintains the old assumption that
4897 * connected ports are usable, and avoids exposing to the users objects they
4898 * can't really use.
4899 */
4802static bool icl_tc_port_connected(struct drm_i915_private *dev_priv, 4900static bool icl_tc_port_connected(struct drm_i915_private *dev_priv,
4803 struct intel_digital_port *intel_dig_port) 4901 struct intel_digital_port *intel_dig_port)
4804{ 4902{
@@ -4817,12 +4915,17 @@ static bool icl_tc_port_connected(struct drm_i915_private *dev_priv,
4817 is_typec = dpsp & TC_LIVE_STATE_TC(tc_port); 4915 is_typec = dpsp & TC_LIVE_STATE_TC(tc_port);
4818 is_tbt = dpsp & TC_LIVE_STATE_TBT(tc_port); 4916 is_tbt = dpsp & TC_LIVE_STATE_TBT(tc_port);
4819 4917
4820 if (!is_legacy && !is_typec && !is_tbt) 4918 if (!is_legacy && !is_typec && !is_tbt) {
4919 icl_tc_phy_disconnect(dev_priv, intel_dig_port);
4821 return false; 4920 return false;
4921 }
4822 4922
4823 icl_update_tc_port_type(dev_priv, intel_dig_port, is_legacy, is_typec, 4923 icl_update_tc_port_type(dev_priv, intel_dig_port, is_legacy, is_typec,
4824 is_tbt); 4924 is_tbt);
4825 4925
4926 if (!icl_tc_phy_connect(dev_priv, intel_dig_port))
4927 return false;
4928
4826 return true; 4929 return true;
4827} 4930}
4828 4931
@@ -4850,6 +4953,11 @@ static bool icl_digital_port_connected(struct intel_encoder *encoder)
4850 * intel_digital_port_connected - is the specified port connected? 4953 * intel_digital_port_connected - is the specified port connected?
4851 * @encoder: intel_encoder 4954 * @encoder: intel_encoder
4852 * 4955 *
4956 * In cases where there's a connector physically connected but it can't be used
4957 * by our hardware we also return false, since the rest of the driver should
4958 * pretty much treat the port as disconnected. This is relevant for type-C
4959 * (starting on ICL) where there's ownership involved.
4960 *
4853 * Return %true if port is connected, %false otherwise. 4961 * Return %true if port is connected, %false otherwise.
4854 */ 4962 */
4855bool intel_digital_port_connected(struct intel_encoder *encoder) 4963bool intel_digital_port_connected(struct intel_encoder *encoder)
diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c
index a1799b5c12bb..02faa2cf4a85 100644
--- a/drivers/gpu/drm/i915/intel_hdmi.c
+++ b/drivers/gpu/drm/i915/intel_hdmi.c
@@ -1909,21 +1909,26 @@ intel_hdmi_set_edid(struct drm_connector *connector)
1909static enum drm_connector_status 1909static enum drm_connector_status
1910intel_hdmi_detect(struct drm_connector *connector, bool force) 1910intel_hdmi_detect(struct drm_connector *connector, bool force)
1911{ 1911{
1912 enum drm_connector_status status; 1912 enum drm_connector_status status = connector_status_disconnected;
1913 struct drm_i915_private *dev_priv = to_i915(connector->dev); 1913 struct drm_i915_private *dev_priv = to_i915(connector->dev);
1914 struct intel_hdmi *intel_hdmi = intel_attached_hdmi(connector);
1915 struct intel_encoder *encoder = &hdmi_to_dig_port(intel_hdmi)->base;
1914 1916
1915 DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", 1917 DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n",
1916 connector->base.id, connector->name); 1918 connector->base.id, connector->name);
1917 1919
1918 intel_display_power_get(dev_priv, POWER_DOMAIN_GMBUS); 1920 intel_display_power_get(dev_priv, POWER_DOMAIN_GMBUS);
1919 1921
1922 if (IS_ICELAKE(dev_priv) &&
1923 !intel_digital_port_connected(encoder))
1924 goto out;
1925
1920 intel_hdmi_unset_edid(connector); 1926 intel_hdmi_unset_edid(connector);
1921 1927
1922 if (intel_hdmi_set_edid(connector)) 1928 if (intel_hdmi_set_edid(connector))
1923 status = connector_status_connected; 1929 status = connector_status_connected;
1924 else
1925 status = connector_status_disconnected;
1926 1930
1931out:
1927 intel_display_power_put(dev_priv, POWER_DOMAIN_GMBUS); 1932 intel_display_power_put(dev_priv, POWER_DOMAIN_GMBUS);
1928 1933
1929 return status; 1934 return status;