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