intel_tc.c 8.1 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
u32 intel_tc_port_get_lane_mask(struct intel_digital_port *dig_port)
25 26 27
{
	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);
28 29 30 31 32 33 34 35 36 37 38
	u32 lane_mask;

	lane_mask = I915_READ(PORT_TX_DFLEXDPSP);

	return (lane_mask & DP_LANE_ASSIGNMENT_MASK(tc_port)) >>
	       DP_LANE_ASSIGNMENT_SHIFT(tc_port);
}

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);
39
	intel_wakeref_t wakeref;
40
	u32 lane_mask;
41

42
	if (dig_port->tc_mode != TC_PORT_DP_ALT)
43 44
		return 4;

45
	lane_mask = 0;
46
	with_intel_display_power(dev_priv, POWER_DOMAIN_DISPLAY_CORE, wakeref)
47
		lane_mask = intel_tc_port_get_lane_mask(dig_port);
48

49
	switch (lane_mask) {
50
	default:
51
		MISSING_CASE(lane_mask);
52 53 54 55 56 57 58 59 60 61 62 63 64
	case 1:
	case 2:
	case 4:
	case 8:
		return 1;
	case 3:
	case 12:
		return 2;
	case 15:
		return 4;
	}
}

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
static void tc_port_fixup_legacy_flag(struct intel_digital_port *dig_port,
				      u32 live_status_mask)
{
	u32 valid_hpd_mask;

	if (dig_port->tc_legacy_port)
		valid_hpd_mask = BIT(TC_PORT_LEGACY);
	else
		valid_hpd_mask = BIT(TC_PORT_DP_ALT) |
				 BIT(TC_PORT_TBT_ALT);

	if (!(live_status_mask & ~valid_hpd_mask))
		return;

	/* If live status mismatches the VBT flag, trust the live status. */
	DRM_ERROR("Port %s: live status %08x mismatches the legacy port flag, fix flag\n",
		  dig_port->tc_port_name, live_status_mask);

	dig_port->tc_legacy_port = !dig_port->tc_legacy_port;
}

static u32 tc_port_live_status_mask(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 mask = 0;
	u32 val;

	val = I915_READ(PORT_TX_DFLEXDPSP);

	if (val & TC_LIVE_STATE_TBT(tc_port))
		mask |= BIT(TC_PORT_TBT_ALT);
	if (val & TC_LIVE_STATE_TC(tc_port))
		mask |= BIT(TC_PORT_DP_ALT);

	if (I915_READ(SDEISR) & SDE_TC_HOTPLUG_ICP(tc_port))
		mask |= BIT(TC_PORT_LEGACY);

	/* The sink can be connected only in a single mode. */
	if (!WARN_ON(hweight32(mask) > 1))
		tc_port_fixup_legacy_flag(dig_port, mask);

	return mask;
}

static bool icl_tc_phy_status_complete(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);

	return I915_READ(PORT_TX_DFLEXDPPMS) &
	       DP_PHY_MODE_STATUS_COMPLETED(tc_port);
}

static void icl_tc_phy_set_safe_mode(struct intel_digital_port *dig_port,
				     bool enable)
{
	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;

	val = I915_READ(PORT_TX_DFLEXDPCSSS);

	val &= ~DP_PHY_MODE_STATUS_NOT_SAFE(tc_port);
	if (!enable)
		val |= DP_PHY_MODE_STATUS_NOT_SAFE(tc_port);

	I915_WRITE(PORT_TX_DFLEXDPCSSS, val);
133 134 135 136

	if (enable && wait_for(!icl_tc_phy_status_complete(dig_port), 10))
		DRM_DEBUG_KMS("Port %s: PHY complete clear timed out\n",
			      dig_port->tc_port_name);
137 138
}

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
/*
 * 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)
{
162
	u32 live_status_mask;
163

164 165
	if (dig_port->tc_mode != TC_PORT_LEGACY &&
	    dig_port->tc_mode != TC_PORT_DP_ALT)
166 167
		return true;

168
	if (!icl_tc_phy_status_complete(dig_port)) {
169 170
		DRM_DEBUG_KMS("Port %s: PHY not ready\n",
			      dig_port->tc_port_name);
171 172 173 174
		WARN_ON(dig_port->tc_legacy_port);
		return false;
	}

175 176 177 178 179 180
	icl_tc_phy_set_safe_mode(dig_port, false);

	if (dig_port->tc_mode == TC_PORT_LEGACY)
		return true;

	live_status_mask = tc_port_live_status_mask(dig_port);
181 182 183 184 185

	/*
	 * Now we have to re-check the live state, in case the port recently
	 * became disconnected. Not necessary for legacy mode.
	 */
186
	if (!(live_status_mask & BIT(TC_PORT_DP_ALT))) {
187 188
		DRM_DEBUG_KMS("Port %s: PHY sudden disconnect\n",
			      dig_port->tc_port_name);
189 190 191 192 193 194 195 196 197 198 199 200 201
		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)
{
202 203 204 205 206 207 208 209 210 211 212
	switch (dig_port->tc_mode) {
	case TC_PORT_LEGACY:
	case TC_PORT_DP_ALT:
		icl_tc_phy_set_safe_mode(dig_port, true);
		dig_port->tc_mode = TC_PORT_TBT_ALT;
		break;
	case TC_PORT_TBT_ALT:
		/* Nothing to do, we stay in TBT-alt mode */
		break;
	default:
		MISSING_CASE(dig_port->tc_mode);
213 214
	}

215 216
	DRM_DEBUG_KMS("Port %s: mode %s disconnected\n",
		      dig_port->tc_port_name,
217
		      tc_port_mode_name(dig_port->tc_mode));
218 219 220 221
}

static void icl_update_tc_port_type(struct drm_i915_private *dev_priv,
				    struct intel_digital_port *intel_dig_port,
222
				    u32 live_status_mask)
223
{
224
	enum tc_port_mode old_mode = intel_dig_port->tc_mode;
225

226
	if (!live_status_mask)
227 228
		return;

229 230
	intel_dig_port->tc_mode = fls(live_status_mask) - 1;

231
	if (old_mode != intel_dig_port->tc_mode)
232 233
		DRM_DEBUG_KMS("Port %s: port has mode %s\n",
			      intel_dig_port->tc_port_name,
234
			      tc_port_mode_name(intel_dig_port->tc_mode));
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
}

/*
 * 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);
250
	u32 live_status_mask = tc_port_live_status_mask(dig_port);
251 252 253 254 255

	/*
	 * The spec says we shouldn't be using the ISR bits for detecting
	 * between TC and TBT. We should use DFLEXDPSP.
	 */
256
	if (!live_status_mask && !dig_port->tc_legacy_port) {
257 258 259 260 261
		icl_tc_phy_disconnect(dig_port);

		return false;
	}

262
	icl_update_tc_port_type(dev_priv, dig_port, live_status_mask);
263 264 265 266 267 268
	if (!icl_tc_phy_connect(dig_port))
		return false;

	return true;
}

269 270 271 272 273 274 275 276 277 278 279 280 281 282
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;
}