intel_tc.c 7.2 KB
Newer Older
1 2 3 4 5 6 7 8 9
// SPDX-License-Identifier: MIT
/*
 * Copyright © 2019 Intel Corporation
 */

#include "intel_display.h"
#include "i915_drv.h"
#include "intel_tc.h"

10
static const char *tc_port_mode_name(enum tc_port_mode mode)
11 12
{
	static const char * const names[] = {
13 14
		[TC_PORT_TBT_ALT] = "tbt-alt",
		[TC_PORT_DP_ALT] = "dp-alt",
15 16 17
		[TC_PORT_LEGACY] = "legacy",
	};

18 19
	if (WARN_ON(mode >= ARRAY_SIZE(names)))
		mode = TC_PORT_TBT_ALT;
20

21
	return names[mode];
22 23 24 25 26 27 28 29 30
}

int intel_tc_port_fia_max_lane_count(struct intel_digital_port *dig_port)
{
	struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev);
	enum tc_port tc_port = intel_port_to_tc(dev_priv, dig_port->base.port);
	intel_wakeref_t wakeref;
	u32 lane_info;

31
	if (dig_port->tc_mode != TC_PORT_DP_ALT)
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
		return 4;

	lane_info = 0;
	with_intel_display_power(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref)
		lane_info = (I915_READ(PORT_TX_DFLEXDPSP) &
			     DP_LANE_ASSIGNMENT_MASK(tc_port)) >>
				DP_LANE_ASSIGNMENT_SHIFT(tc_port);

	switch (lane_info) {
	default:
		MISSING_CASE(lane_info);
	case 1:
	case 2:
	case 4:
	case 8:
		return 1;
	case 3:
	case 12:
		return 2;
	case 15:
		return 4;
	}
}

/*
 * This function implements the first part of the Connect Flow described by our
 * specification, Gen11 TypeC Programming chapter. The rest of the flow (reading
 * lanes, EDID, etc) is done as needed in the typical places.
 *
 * Unlike the other ports, type-C ports are not available to use as soon as we
 * get a hotplug. The type-C 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.
 *
 * We could opt to only do the connect flow when we actually try to use the AUX
 * channels or do a modeset, then immediately run the disconnect flow after
 * usage, but there are some implications on this for a dynamic environment:
 * things may go away or change behind our backs. So for now our driver is
 * always trying to acquire ownership of the controller as soon as it gets an
 * interrupt (or polls state and sees a port is connected) and only gives it
 * back when it sees a disconnect. Implementation of a more fine-grained model
 * will require a lot of coordination with user space and thorough testing for
 * the extra possible cases.
 */
static bool icl_tc_phy_connect(struct intel_digital_port *dig_port)
{
	struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev);
	enum tc_port tc_port = intel_port_to_tc(dev_priv, dig_port->base.port);
	u32 val;

83 84
	if (dig_port->tc_mode != TC_PORT_LEGACY &&
	    dig_port->tc_mode != TC_PORT_DP_ALT)
85 86 87 88
		return true;

	val = I915_READ(PORT_TX_DFLEXDPPMS);
	if (!(val & DP_PHY_MODE_STATUS_COMPLETED(tc_port))) {
89 90
		DRM_DEBUG_KMS("Port %s: PHY not ready\n",
			      dig_port->tc_port_name);
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
		WARN_ON(dig_port->tc_legacy_port);
		return false;
	}

	/*
	 * This function may be called many times in a row without an HPD event
	 * in between, so try to avoid the write when we can.
	 */
	val = I915_READ(PORT_TX_DFLEXDPCSSS);
	if (!(val & DP_PHY_MODE_STATUS_NOT_SAFE(tc_port))) {
		val |= DP_PHY_MODE_STATUS_NOT_SAFE(tc_port);
		I915_WRITE(PORT_TX_DFLEXDPCSSS, val);
	}

	/*
	 * Now we have to re-check the live state, in case the port recently
	 * became disconnected. Not necessary for legacy mode.
	 */
109
	if (dig_port->tc_mode == TC_PORT_DP_ALT &&
110
	    !(I915_READ(PORT_TX_DFLEXDPSP) & TC_LIVE_STATE_TC(tc_port))) {
111 112
		DRM_DEBUG_KMS("Port %s: PHY sudden disconnect\n",
			      dig_port->tc_port_name);
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
		icl_tc_phy_disconnect(dig_port);
		return false;
	}

	return true;
}

/*
 * See the comment at the connect function. This implements the Disconnect
 * Flow.
 */
void icl_tc_phy_disconnect(struct intel_digital_port *dig_port)
{
	struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev);
	enum tc_port tc_port = intel_port_to_tc(dev_priv, dig_port->base.port);

	/*
	 * TBT disconnection flow is read the live status, what was done in
	 * caller.
	 */
133 134
	if (dig_port->tc_mode == TC_PORT_DP_ALT ||
	    dig_port->tc_mode == TC_PORT_LEGACY) {
135 136 137 138 139 140 141
		u32 val;

		val = I915_READ(PORT_TX_DFLEXDPCSSS);
		val &= ~DP_PHY_MODE_STATUS_NOT_SAFE(tc_port);
		I915_WRITE(PORT_TX_DFLEXDPCSSS, val);
	}

142 143
	DRM_DEBUG_KMS("Port %s: mode %s disconnected\n",
		      dig_port->tc_port_name,
144
		      tc_port_mode_name(dig_port->tc_mode));
145

146
	dig_port->tc_mode = TC_PORT_TBT_ALT;
147 148 149 150 151 152
}

static void icl_update_tc_port_type(struct drm_i915_private *dev_priv,
				    struct intel_digital_port *intel_dig_port,
				    bool is_legacy, bool is_typec, bool is_tbt)
{
153
	enum tc_port_mode old_mode = intel_dig_port->tc_mode;
154 155 156 157

	WARN_ON(is_legacy + is_typec + is_tbt != 1);

	if (is_legacy)
158
		intel_dig_port->tc_mode = TC_PORT_LEGACY;
159
	else if (is_typec)
160
		intel_dig_port->tc_mode = TC_PORT_DP_ALT;
161
	else if (is_tbt)
162
		intel_dig_port->tc_mode = TC_PORT_TBT_ALT;
163 164 165
	else
		return;

166
	if (old_mode != intel_dig_port->tc_mode)
167 168
		DRM_DEBUG_KMS("Port %s: port has mode %s\n",
			      intel_dig_port->tc_port_name,
169
			      tc_port_mode_name(intel_dig_port->tc_mode));
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
}

/*
 * The type-C ports are different because even when they are connected, they may
 * not be available/usable by the graphics driver: see the comment on
 * icl_tc_phy_connect(). So in our driver instead of adding the additional
 * concept of "usable" and make everything check for "connected and usable" we
 * define a port as "connected" when it is not only connected, but also when it
 * is usable by the rest of the driver. That maintains the old assumption that
 * connected ports are usable, and avoids exposing to the users objects they
 * can't really use.
 */
bool intel_tc_port_connected(struct intel_digital_port *dig_port)
{
	struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev);
	enum port port = dig_port->base.port;
	enum tc_port tc_port = intel_port_to_tc(dev_priv, port);
	bool is_legacy, is_typec, is_tbt;
	u32 dpsp;

	/*
	 * Complain if we got a legacy port HPD, but VBT didn't mark the port as
	 * legacy. Treat the port as legacy from now on.
	 */
	if (!dig_port->tc_legacy_port &&
	    I915_READ(SDEISR) & SDE_TC_HOTPLUG_ICP(tc_port)) {
196 197
		DRM_ERROR("Port %s: VBT incorrectly claims port is not TypeC legacy\n",
			  dig_port->tc_port_name);
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
		dig_port->tc_legacy_port = true;
	}
	is_legacy = dig_port->tc_legacy_port;

	/*
	 * The spec says we shouldn't be using the ISR bits for detecting
	 * between TC and TBT. We should use DFLEXDPSP.
	 */
	dpsp = I915_READ(PORT_TX_DFLEXDPSP);
	is_typec = dpsp & TC_LIVE_STATE_TC(tc_port);
	is_tbt = dpsp & TC_LIVE_STATE_TBT(tc_port);

	if (!is_legacy && !is_typec && !is_tbt) {
		icl_tc_phy_disconnect(dig_port);

		return false;
	}

	icl_update_tc_port_type(dev_priv, dig_port, is_legacy, is_typec,
				is_tbt);

	if (!icl_tc_phy_connect(dig_port))
		return false;

	return true;
}

225 226 227 228 229 230 231 232 233 234 235 236 237 238
void intel_tc_port_init(struct intel_digital_port *dig_port, bool is_legacy)
{
	struct drm_i915_private *i915 = to_i915(dig_port->base.base.dev);
	enum port port = dig_port->base.port;
	enum tc_port tc_port = intel_port_to_tc(i915, port);

	if (WARN_ON(tc_port == PORT_TC_NONE))
		return;

	snprintf(dig_port->tc_port_name, sizeof(dig_port->tc_port_name),
		 "%c/TC#%d", port_name(port), tc_port + 1);

	dig_port->tc_legacy_port = is_legacy;
}