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