1 // SPDX-License-Identifier: MIT
2 /*
3  * Copyright © 2018 Intel Corporation
4  */
5 
6 #include "intel_combo_phy.h"
7 #include "intel_combo_phy_regs.h"
8 #include "intel_de.h"
9 #include "intel_display_types.h"
10 
11 #define for_each_combo_phy(__dev_priv, __phy) \
12 	for ((__phy) = PHY_A; (__phy) < I915_MAX_PHYS; (__phy)++)	\
13 		for_each_if(intel_phy_is_combo(__dev_priv, __phy))
14 
15 #define for_each_combo_phy_reverse(__dev_priv, __phy) \
16 	for ((__phy) = I915_MAX_PHYS; (__phy)-- > PHY_A;) \
17 		for_each_if(intel_phy_is_combo(__dev_priv, __phy))
18 
19 enum {
20 	PROCMON_0_85V_DOT_0,
21 	PROCMON_0_95V_DOT_0,
22 	PROCMON_0_95V_DOT_1,
23 	PROCMON_1_05V_DOT_0,
24 	PROCMON_1_05V_DOT_1,
25 };
26 
27 static const struct icl_procmon {
28 	u32 dw1, dw9, dw10;
29 } icl_procmon_values[] = {
30 	[PROCMON_0_85V_DOT_0] =
31 		{ .dw1 = 0x00000000, .dw9 = 0x62AB67BB, .dw10 = 0x51914F96, },
32 	[PROCMON_0_95V_DOT_0] =
33 		{ .dw1 = 0x00000000, .dw9 = 0x86E172C7, .dw10 = 0x77CA5EAB, },
34 	[PROCMON_0_95V_DOT_1] =
35 		{ .dw1 = 0x00000000, .dw9 = 0x93F87FE1, .dw10 = 0x8AE871C5, },
36 	[PROCMON_1_05V_DOT_0] =
37 		{ .dw1 = 0x00000000, .dw9 = 0x98FA82DD, .dw10 = 0x89E46DC1, },
38 	[PROCMON_1_05V_DOT_1] =
39 		{ .dw1 = 0x00440000, .dw9 = 0x9A00AB25, .dw10 = 0x8AE38FF1, },
40 };
41 
42 static const struct icl_procmon *
43 icl_get_procmon_ref_values(struct drm_i915_private *dev_priv, enum phy phy)
44 {
45 	const struct icl_procmon *procmon;
46 	u32 val;
47 
48 	val = intel_de_read(dev_priv, ICL_PORT_COMP_DW3(phy));
49 	switch (val & (PROCESS_INFO_MASK | VOLTAGE_INFO_MASK)) {
50 	default:
51 		MISSING_CASE(val);
52 		fallthrough;
53 	case VOLTAGE_INFO_0_85V | PROCESS_INFO_DOT_0:
54 		procmon = &icl_procmon_values[PROCMON_0_85V_DOT_0];
55 		break;
56 	case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_0:
57 		procmon = &icl_procmon_values[PROCMON_0_95V_DOT_0];
58 		break;
59 	case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_1:
60 		procmon = &icl_procmon_values[PROCMON_0_95V_DOT_1];
61 		break;
62 	case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_0:
63 		procmon = &icl_procmon_values[PROCMON_1_05V_DOT_0];
64 		break;
65 	case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_1:
66 		procmon = &icl_procmon_values[PROCMON_1_05V_DOT_1];
67 		break;
68 	}
69 
70 	return procmon;
71 }
72 
73 static void icl_set_procmon_ref_values(struct drm_i915_private *dev_priv,
74 				       enum phy phy)
75 {
76 	const struct icl_procmon *procmon;
77 	u32 val;
78 
79 	procmon = icl_get_procmon_ref_values(dev_priv, phy);
80 
81 	val = intel_de_read(dev_priv, ICL_PORT_COMP_DW1(phy));
82 	val &= ~((0xff << 16) | 0xff);
83 	val |= procmon->dw1;
84 	intel_de_write(dev_priv, ICL_PORT_COMP_DW1(phy), val);
85 
86 	intel_de_write(dev_priv, ICL_PORT_COMP_DW9(phy), procmon->dw9);
87 	intel_de_write(dev_priv, ICL_PORT_COMP_DW10(phy), procmon->dw10);
88 }
89 
90 static bool check_phy_reg(struct drm_i915_private *dev_priv,
91 			  enum phy phy, i915_reg_t reg, u32 mask,
92 			  u32 expected_val)
93 {
94 	u32 val = intel_de_read(dev_priv, reg);
95 
96 	if ((val & mask) != expected_val) {
97 		drm_dbg(&dev_priv->drm,
98 			"Combo PHY %c reg %08x state mismatch: "
99 			"current %08x mask %08x expected %08x\n",
100 			phy_name(phy),
101 			reg.reg, val, mask, expected_val);
102 		return false;
103 	}
104 
105 	return true;
106 }
107 
108 static bool icl_verify_procmon_ref_values(struct drm_i915_private *dev_priv,
109 					  enum phy phy)
110 {
111 	const struct icl_procmon *procmon;
112 	bool ret;
113 
114 	procmon = icl_get_procmon_ref_values(dev_priv, phy);
115 
116 	ret = check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW1(phy),
117 			    (0xff << 16) | 0xff, procmon->dw1);
118 	ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW9(phy),
119 			     -1U, procmon->dw9);
120 	ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW10(phy),
121 			     -1U, procmon->dw10);
122 
123 	return ret;
124 }
125 
126 static bool has_phy_misc(struct drm_i915_private *i915, enum phy phy)
127 {
128 	/*
129 	 * Some platforms only expect PHY_MISC to be programmed for PHY-A and
130 	 * PHY-B and may not even have instances of the register for the
131 	 * other combo PHY's.
132 	 *
133 	 * ADL-S technically has three instances of PHY_MISC, but only requires
134 	 * that we program it for PHY A.
135 	 */
136 
137 	if (IS_ALDERLAKE_S(i915))
138 		return phy == PHY_A;
139 	else if (IS_JSL_EHL(i915) ||
140 		 IS_ROCKETLAKE(i915) ||
141 		 IS_DG1(i915))
142 		return phy < PHY_C;
143 
144 	return true;
145 }
146 
147 static bool icl_combo_phy_enabled(struct drm_i915_private *dev_priv,
148 				  enum phy phy)
149 {
150 	/* The PHY C added by EHL has no PHY_MISC register */
151 	if (!has_phy_misc(dev_priv, phy))
152 		return intel_de_read(dev_priv, ICL_PORT_COMP_DW0(phy)) & COMP_INIT;
153 	else
154 		return !(intel_de_read(dev_priv, ICL_PHY_MISC(phy)) &
155 			 ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN) &&
156 			(intel_de_read(dev_priv, ICL_PORT_COMP_DW0(phy)) & COMP_INIT);
157 }
158 
159 static bool ehl_vbt_ddi_d_present(struct drm_i915_private *i915)
160 {
161 	bool ddi_a_present = intel_bios_is_port_present(i915, PORT_A);
162 	bool ddi_d_present = intel_bios_is_port_present(i915, PORT_D);
163 	bool dsi_present = intel_bios_is_dsi_present(i915, NULL);
164 
165 	/*
166 	 * VBT's 'dvo port' field for child devices references the DDI, not
167 	 * the PHY.  So if combo PHY A is wired up to drive an external
168 	 * display, we should see a child device present on PORT_D and
169 	 * nothing on PORT_A and no DSI.
170 	 */
171 	if (ddi_d_present && !ddi_a_present && !dsi_present)
172 		return true;
173 
174 	/*
175 	 * If we encounter a VBT that claims to have an external display on
176 	 * DDI-D _and_ an internal display on DDI-A/DSI leave an error message
177 	 * in the log and let the internal display win.
178 	 */
179 	if (ddi_d_present)
180 		drm_err(&i915->drm,
181 			"VBT claims to have both internal and external displays on PHY A.  Configuring for internal.\n");
182 
183 	return false;
184 }
185 
186 static bool phy_is_master(struct drm_i915_private *dev_priv, enum phy phy)
187 {
188 	/*
189 	 * Certain PHYs are connected to compensation resistors and act
190 	 * as masters to other PHYs.
191 	 *
192 	 * ICL,TGL:
193 	 *   A(master) -> B(slave), C(slave)
194 	 * RKL,DG1:
195 	 *   A(master) -> B(slave)
196 	 *   C(master) -> D(slave)
197 	 * ADL-S:
198 	 *   A(master) -> B(slave), C(slave)
199 	 *   D(master) -> E(slave)
200 	 *
201 	 * We must set the IREFGEN bit for any PHY acting as a master
202 	 * to another PHY.
203 	 */
204 	if (phy == PHY_A)
205 		return true;
206 	else if (IS_ALDERLAKE_S(dev_priv))
207 		return phy == PHY_D;
208 	else if (IS_DG1(dev_priv) || IS_ROCKETLAKE(dev_priv))
209 		return phy == PHY_C;
210 
211 	return false;
212 }
213 
214 static bool icl_combo_phy_verify_state(struct drm_i915_private *dev_priv,
215 				       enum phy phy)
216 {
217 	bool ret = true;
218 	u32 expected_val = 0;
219 
220 	if (!icl_combo_phy_enabled(dev_priv, phy))
221 		return false;
222 
223 	if (DISPLAY_VER(dev_priv) >= 12) {
224 		ret &= check_phy_reg(dev_priv, phy, ICL_PORT_TX_DW8_LN(0, phy),
225 				     ICL_PORT_TX_DW8_ODCC_CLK_SEL |
226 				     ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_MASK,
227 				     ICL_PORT_TX_DW8_ODCC_CLK_SEL |
228 				     ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_DIV2);
229 
230 		ret &= check_phy_reg(dev_priv, phy, ICL_PORT_PCS_DW1_LN(0, phy),
231 				     DCC_MODE_SELECT_MASK,
232 				     DCC_MODE_SELECT_CONTINUOSLY);
233 	}
234 
235 	ret &= icl_verify_procmon_ref_values(dev_priv, phy);
236 
237 	if (phy_is_master(dev_priv, phy)) {
238 		ret &= check_phy_reg(dev_priv, phy, ICL_PORT_COMP_DW8(phy),
239 				     IREFGEN, IREFGEN);
240 
241 		if (IS_JSL_EHL(dev_priv)) {
242 			if (ehl_vbt_ddi_d_present(dev_priv))
243 				expected_val = ICL_PHY_MISC_MUX_DDID;
244 
245 			ret &= check_phy_reg(dev_priv, phy, ICL_PHY_MISC(phy),
246 					     ICL_PHY_MISC_MUX_DDID,
247 					     expected_val);
248 		}
249 	}
250 
251 	ret &= check_phy_reg(dev_priv, phy, ICL_PORT_CL_DW5(phy),
252 			     CL_POWER_DOWN_ENABLE, CL_POWER_DOWN_ENABLE);
253 
254 	return ret;
255 }
256 
257 void intel_combo_phy_power_up_lanes(struct drm_i915_private *dev_priv,
258 				    enum phy phy, bool is_dsi,
259 				    int lane_count, bool lane_reversal)
260 {
261 	u8 lane_mask;
262 	u32 val;
263 
264 	if (is_dsi) {
265 		drm_WARN_ON(&dev_priv->drm, lane_reversal);
266 
267 		switch (lane_count) {
268 		case 1:
269 			lane_mask = PWR_DOWN_LN_3_1_0;
270 			break;
271 		case 2:
272 			lane_mask = PWR_DOWN_LN_3_1;
273 			break;
274 		case 3:
275 			lane_mask = PWR_DOWN_LN_3;
276 			break;
277 		default:
278 			MISSING_CASE(lane_count);
279 			fallthrough;
280 		case 4:
281 			lane_mask = PWR_UP_ALL_LANES;
282 			break;
283 		}
284 	} else {
285 		switch (lane_count) {
286 		case 1:
287 			lane_mask = lane_reversal ? PWR_DOWN_LN_2_1_0 :
288 						    PWR_DOWN_LN_3_2_1;
289 			break;
290 		case 2:
291 			lane_mask = lane_reversal ? PWR_DOWN_LN_1_0 :
292 						    PWR_DOWN_LN_3_2;
293 			break;
294 		default:
295 			MISSING_CASE(lane_count);
296 			fallthrough;
297 		case 4:
298 			lane_mask = PWR_UP_ALL_LANES;
299 			break;
300 		}
301 	}
302 
303 	val = intel_de_read(dev_priv, ICL_PORT_CL_DW10(phy));
304 	val &= ~PWR_DOWN_LN_MASK;
305 	val |= lane_mask;
306 	intel_de_write(dev_priv, ICL_PORT_CL_DW10(phy), val);
307 }
308 
309 static void icl_combo_phys_init(struct drm_i915_private *dev_priv)
310 {
311 	enum phy phy;
312 
313 	for_each_combo_phy(dev_priv, phy) {
314 		u32 val;
315 
316 		if (icl_combo_phy_verify_state(dev_priv, phy)) {
317 			drm_dbg(&dev_priv->drm,
318 				"Combo PHY %c already enabled, won't reprogram it.\n",
319 				phy_name(phy));
320 			continue;
321 		}
322 
323 		if (!has_phy_misc(dev_priv, phy))
324 			goto skip_phy_misc;
325 
326 		/*
327 		 * EHL's combo PHY A can be hooked up to either an external
328 		 * display (via DDI-D) or an internal display (via DDI-A or
329 		 * the DSI DPHY).  This is a motherboard design decision that
330 		 * can't be changed on the fly, so initialize the PHY's mux
331 		 * based on whether our VBT indicates the presence of any
332 		 * "internal" child devices.
333 		 */
334 		val = intel_de_read(dev_priv, ICL_PHY_MISC(phy));
335 		if (IS_JSL_EHL(dev_priv) && phy == PHY_A) {
336 			val &= ~ICL_PHY_MISC_MUX_DDID;
337 
338 			if (ehl_vbt_ddi_d_present(dev_priv))
339 				val |= ICL_PHY_MISC_MUX_DDID;
340 		}
341 
342 		val &= ~ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN;
343 		intel_de_write(dev_priv, ICL_PHY_MISC(phy), val);
344 
345 skip_phy_misc:
346 		if (DISPLAY_VER(dev_priv) >= 12) {
347 			val = intel_de_read(dev_priv, ICL_PORT_TX_DW8_LN(0, phy));
348 			val &= ~ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_MASK;
349 			val |= ICL_PORT_TX_DW8_ODCC_CLK_SEL;
350 			val |= ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_DIV2;
351 			intel_de_write(dev_priv, ICL_PORT_TX_DW8_GRP(phy), val);
352 
353 			val = intel_de_read(dev_priv, ICL_PORT_PCS_DW1_LN(0, phy));
354 			val &= ~DCC_MODE_SELECT_MASK;
355 			val |= DCC_MODE_SELECT_CONTINUOSLY;
356 			intel_de_write(dev_priv, ICL_PORT_PCS_DW1_GRP(phy), val);
357 		}
358 
359 		icl_set_procmon_ref_values(dev_priv, phy);
360 
361 		if (phy_is_master(dev_priv, phy)) {
362 			val = intel_de_read(dev_priv, ICL_PORT_COMP_DW8(phy));
363 			val |= IREFGEN;
364 			intel_de_write(dev_priv, ICL_PORT_COMP_DW8(phy), val);
365 		}
366 
367 		val = intel_de_read(dev_priv, ICL_PORT_COMP_DW0(phy));
368 		val |= COMP_INIT;
369 		intel_de_write(dev_priv, ICL_PORT_COMP_DW0(phy), val);
370 
371 		val = intel_de_read(dev_priv, ICL_PORT_CL_DW5(phy));
372 		val |= CL_POWER_DOWN_ENABLE;
373 		intel_de_write(dev_priv, ICL_PORT_CL_DW5(phy), val);
374 	}
375 }
376 
377 static void icl_combo_phys_uninit(struct drm_i915_private *dev_priv)
378 {
379 	enum phy phy;
380 
381 	for_each_combo_phy_reverse(dev_priv, phy) {
382 		u32 val;
383 
384 		if (phy == PHY_A &&
385 		    !icl_combo_phy_verify_state(dev_priv, phy)) {
386 			if (IS_TIGERLAKE(dev_priv) || IS_DG1(dev_priv)) {
387 				/*
388 				 * A known problem with old ifwi:
389 				 * https://gitlab.freedesktop.org/drm/intel/-/issues/2411
390 				 * Suppress the warning for CI. Remove ASAP!
391 				 */
392 				drm_dbg_kms(&dev_priv->drm,
393 					    "Combo PHY %c HW state changed unexpectedly\n",
394 					    phy_name(phy));
395 			} else {
396 				drm_warn(&dev_priv->drm,
397 					 "Combo PHY %c HW state changed unexpectedly\n",
398 					 phy_name(phy));
399 			}
400 		}
401 
402 		if (!has_phy_misc(dev_priv, phy))
403 			goto skip_phy_misc;
404 
405 		val = intel_de_read(dev_priv, ICL_PHY_MISC(phy));
406 		val |= ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN;
407 		intel_de_write(dev_priv, ICL_PHY_MISC(phy), val);
408 
409 skip_phy_misc:
410 		val = intel_de_read(dev_priv, ICL_PORT_COMP_DW0(phy));
411 		val &= ~COMP_INIT;
412 		intel_de_write(dev_priv, ICL_PORT_COMP_DW0(phy), val);
413 	}
414 }
415 
416 void intel_combo_phy_init(struct drm_i915_private *i915)
417 {
418 	icl_combo_phys_init(i915);
419 }
420 
421 void intel_combo_phy_uninit(struct drm_i915_private *i915)
422 {
423 	icl_combo_phys_uninit(i915);
424 }
425