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