1 // SPDX-License-Identifier: MIT 2 /* 3 * Copyright © 2018 Intel Corporation 4 */ 5 6 #include <drm/drm_print.h> 7 8 #include "i915_reg.h" 9 #include "i915_utils.h" 10 #include "intel_combo_phy.h" 11 #include "intel_combo_phy_regs.h" 12 #include "intel_de.h" 13 #include "intel_display_types.h" 14 15 #define for_each_combo_phy(__display, __phy) \ 16 for ((__phy) = PHY_A; (__phy) < I915_MAX_PHYS; (__phy)++) \ 17 for_each_if(intel_phy_is_combo(__display, __phy)) 18 19 #define for_each_combo_phy_reverse(__display, __phy) \ 20 for ((__phy) = I915_MAX_PHYS; (__phy)-- > PHY_A;) \ 21 for_each_if(intel_phy_is_combo(__display, __phy)) 22 23 enum { 24 PROCMON_0_85V_DOT_0, 25 PROCMON_0_95V_DOT_0, 26 PROCMON_0_95V_DOT_1, 27 PROCMON_1_05V_DOT_0, 28 PROCMON_1_05V_DOT_1, 29 }; 30 31 static const struct icl_procmon { 32 const char *name; 33 u32 dw1, dw9, dw10; 34 } icl_procmon_values[] = { 35 [PROCMON_0_85V_DOT_0] = { 36 .name = "0.85V dot0 (low-voltage)", 37 .dw1 = 0x00000000, .dw9 = 0x62AB67BB, .dw10 = 0x51914F96, 38 }, 39 [PROCMON_0_95V_DOT_0] = { 40 .name = "0.95V dot0", 41 .dw1 = 0x00000000, .dw9 = 0x86E172C7, .dw10 = 0x77CA5EAB, 42 }, 43 [PROCMON_0_95V_DOT_1] = { 44 .name = "0.95V dot1", 45 .dw1 = 0x00000000, .dw9 = 0x93F87FE1, .dw10 = 0x8AE871C5, 46 }, 47 [PROCMON_1_05V_DOT_0] = { 48 .name = "1.05V dot0", 49 .dw1 = 0x00000000, .dw9 = 0x98FA82DD, .dw10 = 0x89E46DC1, 50 }, 51 [PROCMON_1_05V_DOT_1] = { 52 .name = "1.05V dot1", 53 .dw1 = 0x00440000, .dw9 = 0x9A00AB25, .dw10 = 0x8AE38FF1, 54 }, 55 }; 56 57 static const struct icl_procmon * 58 icl_get_procmon_ref_values(struct intel_display *display, enum phy phy) 59 { 60 u32 val; 61 62 val = intel_de_read(display, ICL_PORT_COMP_DW3(phy)); 63 switch (val & (PROCESS_INFO_MASK | VOLTAGE_INFO_MASK)) { 64 default: 65 MISSING_CASE(val); 66 fallthrough; 67 case VOLTAGE_INFO_0_85V | PROCESS_INFO_DOT_0: 68 return &icl_procmon_values[PROCMON_0_85V_DOT_0]; 69 case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_0: 70 return &icl_procmon_values[PROCMON_0_95V_DOT_0]; 71 case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_1: 72 return &icl_procmon_values[PROCMON_0_95V_DOT_1]; 73 case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_0: 74 return &icl_procmon_values[PROCMON_1_05V_DOT_0]; 75 case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_1: 76 return &icl_procmon_values[PROCMON_1_05V_DOT_1]; 77 } 78 } 79 80 static void icl_set_procmon_ref_values(struct intel_display *display, 81 enum phy phy) 82 { 83 const struct icl_procmon *procmon; 84 85 procmon = icl_get_procmon_ref_values(display, phy); 86 87 intel_de_rmw(display, ICL_PORT_COMP_DW1(phy), 88 (0xff << 16) | 0xff, procmon->dw1); 89 90 intel_de_write(display, ICL_PORT_COMP_DW9(phy), procmon->dw9); 91 intel_de_write(display, ICL_PORT_COMP_DW10(phy), procmon->dw10); 92 } 93 94 static bool check_phy_reg(struct intel_display *display, 95 enum phy phy, i915_reg_t reg, u32 mask, 96 u32 expected_val) 97 { 98 u32 val = intel_de_read(display, reg); 99 100 if ((val & mask) != expected_val) { 101 drm_dbg_kms(display->drm, 102 "Combo PHY %c reg %08x state mismatch: " 103 "current %08x mask %08x expected %08x\n", 104 phy_name(phy), 105 reg.reg, val, mask, expected_val); 106 return false; 107 } 108 109 return true; 110 } 111 112 static bool icl_verify_procmon_ref_values(struct intel_display *display, 113 enum phy phy) 114 { 115 const struct icl_procmon *procmon; 116 bool ret; 117 118 procmon = icl_get_procmon_ref_values(display, phy); 119 120 ret = check_phy_reg(display, phy, ICL_PORT_COMP_DW1(phy), 121 (0xff << 16) | 0xff, procmon->dw1); 122 ret &= check_phy_reg(display, phy, ICL_PORT_COMP_DW9(phy), 123 -1U, procmon->dw9); 124 ret &= check_phy_reg(display, phy, ICL_PORT_COMP_DW10(phy), 125 -1U, procmon->dw10); 126 127 return ret; 128 } 129 130 static bool has_phy_misc(struct intel_display *display, enum phy phy) 131 { 132 /* 133 * Some platforms only expect PHY_MISC to be programmed for PHY-A and 134 * PHY-B and may not even have instances of the register for the 135 * other combo PHY's. 136 * 137 * ADL-S technically has three instances of PHY_MISC, but only requires 138 * that we program it for PHY A. 139 */ 140 141 if (display->platform.alderlake_s) 142 return phy == PHY_A; 143 else if ((display->platform.jasperlake || display->platform.elkhartlake) || 144 display->platform.rocketlake || 145 display->platform.dg1) 146 return phy < PHY_C; 147 148 return true; 149 } 150 151 static bool icl_combo_phy_enabled(struct intel_display *display, 152 enum phy phy) 153 { 154 /* The PHY C added by EHL has no PHY_MISC register */ 155 if (!has_phy_misc(display, phy)) 156 return intel_de_read(display, ICL_PORT_COMP_DW0(phy)) & COMP_INIT; 157 else 158 return !(intel_de_read(display, ICL_PHY_MISC(phy)) & 159 ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN) && 160 (intel_de_read(display, ICL_PORT_COMP_DW0(phy)) & COMP_INIT); 161 } 162 163 static bool ehl_vbt_ddi_d_present(struct intel_display *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(display->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 intel_display *display, 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 (display->platform.alderlake_s) 211 return phy == PHY_D; 212 else if (display->platform.dg1 || display->platform.rocketlake) 213 return phy == PHY_C; 214 215 return false; 216 } 217 218 static bool icl_combo_phy_verify_state(struct intel_display *display, 219 enum phy phy) 220 { 221 bool ret = true; 222 u32 expected_val = 0; 223 224 if (!icl_combo_phy_enabled(display, phy)) 225 return false; 226 227 if (DISPLAY_VER(display) >= 12) { 228 ret &= check_phy_reg(display, 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(display, 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(display, phy); 239 240 if (phy_is_master(display, phy)) { 241 ret &= check_phy_reg(display, phy, ICL_PORT_COMP_DW8(phy), 242 IREFGEN, IREFGEN); 243 244 if (display->platform.jasperlake || display->platform.elkhartlake) { 245 if (ehl_vbt_ddi_d_present(display)) 246 expected_val = ICL_PHY_MISC_MUX_DDID; 247 248 ret &= check_phy_reg(display, phy, ICL_PHY_MISC(phy), 249 ICL_PHY_MISC_MUX_DDID, 250 expected_val); 251 } 252 } 253 254 ret &= check_phy_reg(display, 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 intel_display *display, 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(display->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(display, ICL_PORT_CL_DW10(phy), 306 PWR_DOWN_LN_MASK, lane_mask); 307 } 308 309 static void icl_combo_phys_init(struct intel_display *display) 310 { 311 enum phy phy; 312 313 for_each_combo_phy(display, phy) { 314 const struct icl_procmon *procmon; 315 u32 val; 316 317 if (icl_combo_phy_verify_state(display, phy)) 318 continue; 319 320 procmon = icl_get_procmon_ref_values(display, phy); 321 322 drm_dbg_kms(display->drm, 323 "Initializing combo PHY %c (Voltage/Process Info : %s)\n", 324 phy_name(phy), procmon->name); 325 326 if (!has_phy_misc(display, 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(display, ICL_PHY_MISC(phy)); 338 if ((display->platform.jasperlake || display->platform.elkhartlake) && 339 phy == PHY_A) { 340 val &= ~ICL_PHY_MISC_MUX_DDID; 341 342 if (ehl_vbt_ddi_d_present(display)) 343 val |= ICL_PHY_MISC_MUX_DDID; 344 } 345 346 val &= ~ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN; 347 intel_de_write(display, ICL_PHY_MISC(phy), val); 348 349 skip_phy_misc: 350 if (DISPLAY_VER(display) >= 12) { 351 val = intel_de_read(display, 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(display, ICL_PORT_TX_DW8_GRP(phy), val); 356 357 val = intel_de_read(display, ICL_PORT_PCS_DW1_LN(0, phy)); 358 val &= ~DCC_MODE_SELECT_MASK; 359 val |= RUN_DCC_ONCE; 360 intel_de_write(display, ICL_PORT_PCS_DW1_GRP(phy), val); 361 } 362 363 icl_set_procmon_ref_values(display, phy); 364 365 if (phy_is_master(display, phy)) 366 intel_de_rmw(display, ICL_PORT_COMP_DW8(phy), 367 0, IREFGEN); 368 369 intel_de_rmw(display, ICL_PORT_COMP_DW0(phy), 0, COMP_INIT); 370 intel_de_rmw(display, ICL_PORT_CL_DW5(phy), 371 0, CL_POWER_DOWN_ENABLE); 372 } 373 } 374 375 static void icl_combo_phys_uninit(struct intel_display *display) 376 { 377 enum phy phy; 378 379 for_each_combo_phy_reverse(display, phy) { 380 if (phy == PHY_A && 381 !icl_combo_phy_verify_state(display, phy)) { 382 if (display->platform.tigerlake || display->platform.dg1) { 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(display->drm, 389 "Combo PHY %c HW state changed unexpectedly\n", 390 phy_name(phy)); 391 } else { 392 drm_warn(display->drm, 393 "Combo PHY %c HW state changed unexpectedly\n", 394 phy_name(phy)); 395 } 396 } 397 398 if (!has_phy_misc(display, phy)) 399 goto skip_phy_misc; 400 401 intel_de_rmw(display, ICL_PHY_MISC(phy), 0, 402 ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN); 403 404 skip_phy_misc: 405 intel_de_rmw(display, ICL_PORT_COMP_DW0(phy), COMP_INIT, 0); 406 } 407 } 408 409 void intel_combo_phy_init(struct intel_display *display) 410 { 411 icl_combo_phys_init(display); 412 } 413 414 void intel_combo_phy_uninit(struct intel_display *display) 415 { 416 icl_combo_phys_uninit(display); 417 } 418