1 // SPDX-License-Identifier: MIT 2 /* 3 * Copyright © 2018 Intel Corporation 4 */ 5 6 #include "i915_reg.h" 7 #include "i915_utils.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(__display, __phy) \ 14 for ((__phy) = PHY_A; (__phy) < I915_MAX_PHYS; (__phy)++) \ 15 for_each_if(intel_phy_is_combo(__display, __phy)) 16 17 #define for_each_combo_phy_reverse(__display, __phy) \ 18 for ((__phy) = I915_MAX_PHYS; (__phy)-- > PHY_A;) \ 19 for_each_if(intel_phy_is_combo(__display, __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 intel_display *display, enum phy phy) 57 { 58 u32 val; 59 60 val = intel_de_read(display, 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 intel_display *display, 79 enum phy phy) 80 { 81 const struct icl_procmon *procmon; 82 83 procmon = icl_get_procmon_ref_values(display, phy); 84 85 intel_de_rmw(display, ICL_PORT_COMP_DW1(phy), 86 (0xff << 16) | 0xff, procmon->dw1); 87 88 intel_de_write(display, ICL_PORT_COMP_DW9(phy), procmon->dw9); 89 intel_de_write(display, ICL_PORT_COMP_DW10(phy), procmon->dw10); 90 } 91 92 static bool check_phy_reg(struct intel_display *display, 93 enum phy phy, i915_reg_t reg, u32 mask, 94 u32 expected_val) 95 { 96 u32 val = intel_de_read(display, reg); 97 98 if ((val & mask) != expected_val) { 99 drm_dbg_kms(display->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 intel_display *display, 111 enum phy phy) 112 { 113 const struct icl_procmon *procmon; 114 bool ret; 115 116 procmon = icl_get_procmon_ref_values(display, phy); 117 118 ret = check_phy_reg(display, phy, ICL_PORT_COMP_DW1(phy), 119 (0xff << 16) | 0xff, procmon->dw1); 120 ret &= check_phy_reg(display, phy, ICL_PORT_COMP_DW9(phy), 121 -1U, procmon->dw9); 122 ret &= check_phy_reg(display, phy, ICL_PORT_COMP_DW10(phy), 123 -1U, procmon->dw10); 124 125 return ret; 126 } 127 128 static bool has_phy_misc(struct intel_display *display, 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 (display->platform.alderlake_s) 140 return phy == PHY_A; 141 else if ((display->platform.jasperlake || display->platform.elkhartlake) || 142 display->platform.rocketlake || 143 display->platform.dg1) 144 return phy < PHY_C; 145 146 return true; 147 } 148 149 static bool icl_combo_phy_enabled(struct intel_display *display, 150 enum phy phy) 151 { 152 /* The PHY C added by EHL has no PHY_MISC register */ 153 if (!has_phy_misc(display, phy)) 154 return intel_de_read(display, ICL_PORT_COMP_DW0(phy)) & COMP_INIT; 155 else 156 return !(intel_de_read(display, ICL_PHY_MISC(phy)) & 157 ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN) && 158 (intel_de_read(display, ICL_PORT_COMP_DW0(phy)) & COMP_INIT); 159 } 160 161 static bool ehl_vbt_ddi_d_present(struct intel_display *display) 162 { 163 bool ddi_a_present = intel_bios_is_port_present(display, PORT_A); 164 bool ddi_d_present = intel_bios_is_port_present(display, PORT_D); 165 bool dsi_present = intel_bios_is_dsi_present(display, NULL); 166 167 /* 168 * VBT's 'dvo port' field for child devices references the DDI, not 169 * the PHY. So if combo PHY A is wired up to drive an external 170 * display, we should see a child device present on PORT_D and 171 * nothing on PORT_A and no DSI. 172 */ 173 if (ddi_d_present && !ddi_a_present && !dsi_present) 174 return true; 175 176 /* 177 * If we encounter a VBT that claims to have an external display on 178 * DDI-D _and_ an internal display on DDI-A/DSI leave an error message 179 * in the log and let the internal display win. 180 */ 181 if (ddi_d_present) 182 drm_err(display->drm, 183 "VBT claims to have both internal and external displays on PHY A. Configuring for internal.\n"); 184 185 return false; 186 } 187 188 static bool phy_is_master(struct intel_display *display, enum phy phy) 189 { 190 /* 191 * Certain PHYs are connected to compensation resistors and act 192 * as masters to other PHYs. 193 * 194 * ICL,TGL: 195 * A(master) -> B(slave), C(slave) 196 * RKL,DG1: 197 * A(master) -> B(slave) 198 * C(master) -> D(slave) 199 * ADL-S: 200 * A(master) -> B(slave), C(slave) 201 * D(master) -> E(slave) 202 * 203 * We must set the IREFGEN bit for any PHY acting as a master 204 * to another PHY. 205 */ 206 if (phy == PHY_A) 207 return true; 208 else if (display->platform.alderlake_s) 209 return phy == PHY_D; 210 else if (display->platform.dg1 || display->platform.rocketlake) 211 return phy == PHY_C; 212 213 return false; 214 } 215 216 static bool icl_combo_phy_verify_state(struct intel_display *display, 217 enum phy phy) 218 { 219 bool ret = true; 220 u32 expected_val = 0; 221 222 if (!icl_combo_phy_enabled(display, phy)) 223 return false; 224 225 if (DISPLAY_VER(display) >= 12) { 226 ret &= check_phy_reg(display, phy, ICL_PORT_TX_DW8_LN(0, phy), 227 ICL_PORT_TX_DW8_ODCC_CLK_SEL | 228 ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_MASK, 229 ICL_PORT_TX_DW8_ODCC_CLK_SEL | 230 ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_DIV2); 231 232 ret &= check_phy_reg(display, phy, ICL_PORT_PCS_DW1_LN(0, phy), 233 DCC_MODE_SELECT_MASK, RUN_DCC_ONCE); 234 } 235 236 ret &= icl_verify_procmon_ref_values(display, phy); 237 238 if (phy_is_master(display, phy)) { 239 ret &= check_phy_reg(display, phy, ICL_PORT_COMP_DW8(phy), 240 IREFGEN, IREFGEN); 241 242 if (display->platform.jasperlake || display->platform.elkhartlake) { 243 if (ehl_vbt_ddi_d_present(display)) 244 expected_val = ICL_PHY_MISC_MUX_DDID; 245 246 ret &= check_phy_reg(display, phy, ICL_PHY_MISC(phy), 247 ICL_PHY_MISC_MUX_DDID, 248 expected_val); 249 } 250 } 251 252 ret &= check_phy_reg(display, phy, ICL_PORT_CL_DW5(phy), 253 CL_POWER_DOWN_ENABLE, CL_POWER_DOWN_ENABLE); 254 255 return ret; 256 } 257 258 void intel_combo_phy_power_up_lanes(struct intel_display *display, 259 enum phy phy, bool is_dsi, 260 int lane_count, bool lane_reversal) 261 { 262 u8 lane_mask; 263 264 if (is_dsi) { 265 drm_WARN_ON(display->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 intel_de_rmw(display, ICL_PORT_CL_DW10(phy), 304 PWR_DOWN_LN_MASK, lane_mask); 305 } 306 307 static void icl_combo_phys_init(struct intel_display *display) 308 { 309 enum phy phy; 310 311 for_each_combo_phy(display, phy) { 312 const struct icl_procmon *procmon; 313 u32 val; 314 315 if (icl_combo_phy_verify_state(display, phy)) 316 continue; 317 318 procmon = icl_get_procmon_ref_values(display, phy); 319 320 drm_dbg_kms(display->drm, 321 "Initializing combo PHY %c (Voltage/Process Info : %s)\n", 322 phy_name(phy), procmon->name); 323 324 if (!has_phy_misc(display, phy)) 325 goto skip_phy_misc; 326 327 /* 328 * EHL's combo PHY A can be hooked up to either an external 329 * display (via DDI-D) or an internal display (via DDI-A or 330 * the DSI DPHY). This is a motherboard design decision that 331 * can't be changed on the fly, so initialize the PHY's mux 332 * based on whether our VBT indicates the presence of any 333 * "internal" child devices. 334 */ 335 val = intel_de_read(display, ICL_PHY_MISC(phy)); 336 if ((display->platform.jasperlake || display->platform.elkhartlake) && 337 phy == PHY_A) { 338 val &= ~ICL_PHY_MISC_MUX_DDID; 339 340 if (ehl_vbt_ddi_d_present(display)) 341 val |= ICL_PHY_MISC_MUX_DDID; 342 } 343 344 val &= ~ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN; 345 intel_de_write(display, ICL_PHY_MISC(phy), val); 346 347 skip_phy_misc: 348 if (DISPLAY_VER(display) >= 12) { 349 val = intel_de_read(display, ICL_PORT_TX_DW8_LN(0, phy)); 350 val &= ~ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_MASK; 351 val |= ICL_PORT_TX_DW8_ODCC_CLK_SEL; 352 val |= ICL_PORT_TX_DW8_ODCC_CLK_DIV_SEL_DIV2; 353 intel_de_write(display, ICL_PORT_TX_DW8_GRP(phy), val); 354 355 val = intel_de_read(display, ICL_PORT_PCS_DW1_LN(0, phy)); 356 val &= ~DCC_MODE_SELECT_MASK; 357 val |= RUN_DCC_ONCE; 358 intel_de_write(display, ICL_PORT_PCS_DW1_GRP(phy), val); 359 } 360 361 icl_set_procmon_ref_values(display, phy); 362 363 if (phy_is_master(display, phy)) 364 intel_de_rmw(display, ICL_PORT_COMP_DW8(phy), 365 0, IREFGEN); 366 367 intel_de_rmw(display, ICL_PORT_COMP_DW0(phy), 0, COMP_INIT); 368 intel_de_rmw(display, ICL_PORT_CL_DW5(phy), 369 0, CL_POWER_DOWN_ENABLE); 370 } 371 } 372 373 static void icl_combo_phys_uninit(struct intel_display *display) 374 { 375 enum phy phy; 376 377 for_each_combo_phy_reverse(display, phy) { 378 if (phy == PHY_A && 379 !icl_combo_phy_verify_state(display, phy)) { 380 if (display->platform.tigerlake || display->platform.dg1) { 381 /* 382 * A known problem with old ifwi: 383 * https://gitlab.freedesktop.org/drm/intel/-/issues/2411 384 * Suppress the warning for CI. Remove ASAP! 385 */ 386 drm_dbg_kms(display->drm, 387 "Combo PHY %c HW state changed unexpectedly\n", 388 phy_name(phy)); 389 } else { 390 drm_warn(display->drm, 391 "Combo PHY %c HW state changed unexpectedly\n", 392 phy_name(phy)); 393 } 394 } 395 396 if (!has_phy_misc(display, phy)) 397 goto skip_phy_misc; 398 399 intel_de_rmw(display, ICL_PHY_MISC(phy), 0, 400 ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN); 401 402 skip_phy_misc: 403 intel_de_rmw(display, ICL_PORT_COMP_DW0(phy), COMP_INIT, 0); 404 } 405 } 406 407 void intel_combo_phy_init(struct intel_display *display) 408 { 409 icl_combo_phys_init(display); 410 } 411 412 void intel_combo_phy_uninit(struct intel_display *display) 413 { 414 icl_combo_phys_uninit(display); 415 } 416