| 1 | // SPDX-License-Identifier: MIT |
| 2 | /* |
| 3 | * Copyright © 2021 Intel Corporation |
| 4 | */ |
| 5 | |
| 6 | #include <drm/drm_print.h> |
| 7 | |
| 8 | #include "i915_reg.h" |
| 9 | #include "intel_de.h" |
| 10 | #include "intel_display_regs.h" |
| 11 | #include "intel_display_types.h" |
| 12 | #include "intel_display_utils.h" |
| 13 | #include "intel_panel.h" |
| 14 | #include "intel_pch_refclk.h" |
| 15 | #include "intel_sbi.h" |
| 16 | #include "intel_sbi_regs.h" |
| 17 | |
| 18 | static void lpt_fdi_reset_mphy(struct intel_display *display) |
| 19 | { |
| 20 | int ret; |
| 21 | |
| 22 | intel_de_rmw(display, SOUTH_CHICKEN2, clear: 0, FDI_MPHY_IOSFSB_RESET_CTL); |
| 23 | |
| 24 | ret = intel_de_wait_for_set_us(display, SOUTH_CHICKEN2, |
| 25 | FDI_MPHY_IOSFSB_RESET_STATUS, timeout_us: 100); |
| 26 | if (ret) |
| 27 | drm_err(display->drm, "FDI mPHY reset assert timeout\n" ); |
| 28 | |
| 29 | intel_de_rmw(display, SOUTH_CHICKEN2, FDI_MPHY_IOSFSB_RESET_CTL, set: 0); |
| 30 | |
| 31 | ret = intel_de_wait_for_clear_us(display, SOUTH_CHICKEN2, |
| 32 | FDI_MPHY_IOSFSB_RESET_STATUS, timeout_us: 100); |
| 33 | if (ret) |
| 34 | drm_err(display->drm, "FDI mPHY reset de-assert timeout\n" ); |
| 35 | } |
| 36 | |
| 37 | /* WaMPhyProgramming:hsw */ |
| 38 | static void lpt_fdi_program_mphy(struct intel_display *display) |
| 39 | { |
| 40 | u32 tmp; |
| 41 | |
| 42 | lpt_fdi_reset_mphy(display); |
| 43 | |
| 44 | tmp = intel_sbi_read(display, reg: 0x8008, destination: SBI_MPHY); |
| 45 | tmp &= ~(0xFF << 24); |
| 46 | tmp |= (0x12 << 24); |
| 47 | intel_sbi_write(display, reg: 0x8008, value: tmp, destination: SBI_MPHY); |
| 48 | |
| 49 | tmp = intel_sbi_read(display, reg: 0x2008, destination: SBI_MPHY); |
| 50 | tmp |= (1 << 11); |
| 51 | intel_sbi_write(display, reg: 0x2008, value: tmp, destination: SBI_MPHY); |
| 52 | |
| 53 | tmp = intel_sbi_read(display, reg: 0x2108, destination: SBI_MPHY); |
| 54 | tmp |= (1 << 11); |
| 55 | intel_sbi_write(display, reg: 0x2108, value: tmp, destination: SBI_MPHY); |
| 56 | |
| 57 | tmp = intel_sbi_read(display, reg: 0x206C, destination: SBI_MPHY); |
| 58 | tmp |= (1 << 24) | (1 << 21) | (1 << 18); |
| 59 | intel_sbi_write(display, reg: 0x206C, value: tmp, destination: SBI_MPHY); |
| 60 | |
| 61 | tmp = intel_sbi_read(display, reg: 0x216C, destination: SBI_MPHY); |
| 62 | tmp |= (1 << 24) | (1 << 21) | (1 << 18); |
| 63 | intel_sbi_write(display, reg: 0x216C, value: tmp, destination: SBI_MPHY); |
| 64 | |
| 65 | tmp = intel_sbi_read(display, reg: 0x2080, destination: SBI_MPHY); |
| 66 | tmp &= ~(7 << 13); |
| 67 | tmp |= (5 << 13); |
| 68 | intel_sbi_write(display, reg: 0x2080, value: tmp, destination: SBI_MPHY); |
| 69 | |
| 70 | tmp = intel_sbi_read(display, reg: 0x2180, destination: SBI_MPHY); |
| 71 | tmp &= ~(7 << 13); |
| 72 | tmp |= (5 << 13); |
| 73 | intel_sbi_write(display, reg: 0x2180, value: tmp, destination: SBI_MPHY); |
| 74 | |
| 75 | tmp = intel_sbi_read(display, reg: 0x208C, destination: SBI_MPHY); |
| 76 | tmp &= ~0xFF; |
| 77 | tmp |= 0x1C; |
| 78 | intel_sbi_write(display, reg: 0x208C, value: tmp, destination: SBI_MPHY); |
| 79 | |
| 80 | tmp = intel_sbi_read(display, reg: 0x218C, destination: SBI_MPHY); |
| 81 | tmp &= ~0xFF; |
| 82 | tmp |= 0x1C; |
| 83 | intel_sbi_write(display, reg: 0x218C, value: tmp, destination: SBI_MPHY); |
| 84 | |
| 85 | tmp = intel_sbi_read(display, reg: 0x2098, destination: SBI_MPHY); |
| 86 | tmp &= ~(0xFF << 16); |
| 87 | tmp |= (0x1C << 16); |
| 88 | intel_sbi_write(display, reg: 0x2098, value: tmp, destination: SBI_MPHY); |
| 89 | |
| 90 | tmp = intel_sbi_read(display, reg: 0x2198, destination: SBI_MPHY); |
| 91 | tmp &= ~(0xFF << 16); |
| 92 | tmp |= (0x1C << 16); |
| 93 | intel_sbi_write(display, reg: 0x2198, value: tmp, destination: SBI_MPHY); |
| 94 | |
| 95 | tmp = intel_sbi_read(display, reg: 0x20C4, destination: SBI_MPHY); |
| 96 | tmp |= (1 << 27); |
| 97 | intel_sbi_write(display, reg: 0x20C4, value: tmp, destination: SBI_MPHY); |
| 98 | |
| 99 | tmp = intel_sbi_read(display, reg: 0x21C4, destination: SBI_MPHY); |
| 100 | tmp |= (1 << 27); |
| 101 | intel_sbi_write(display, reg: 0x21C4, value: tmp, destination: SBI_MPHY); |
| 102 | |
| 103 | tmp = intel_sbi_read(display, reg: 0x20EC, destination: SBI_MPHY); |
| 104 | tmp &= ~(0xF << 28); |
| 105 | tmp |= (4 << 28); |
| 106 | intel_sbi_write(display, reg: 0x20EC, value: tmp, destination: SBI_MPHY); |
| 107 | |
| 108 | tmp = intel_sbi_read(display, reg: 0x21EC, destination: SBI_MPHY); |
| 109 | tmp &= ~(0xF << 28); |
| 110 | tmp |= (4 << 28); |
| 111 | intel_sbi_write(display, reg: 0x21EC, value: tmp, destination: SBI_MPHY); |
| 112 | } |
| 113 | |
| 114 | void lpt_disable_iclkip(struct intel_display *display) |
| 115 | { |
| 116 | u32 temp; |
| 117 | |
| 118 | intel_de_write(display, PIXCLK_GATE, PIXCLK_GATE_GATE); |
| 119 | |
| 120 | intel_sbi_lock(display); |
| 121 | |
| 122 | temp = intel_sbi_read(display, SBI_SSCCTL6, destination: SBI_ICLK); |
| 123 | temp |= SBI_SSCCTL_DISABLE; |
| 124 | intel_sbi_write(display, SBI_SSCCTL6, value: temp, destination: SBI_ICLK); |
| 125 | |
| 126 | intel_sbi_unlock(display); |
| 127 | } |
| 128 | |
| 129 | struct iclkip_params { |
| 130 | u32 iclk_virtual_root_freq; |
| 131 | u32 iclk_pi_range; |
| 132 | u32 divsel, phaseinc, auxdiv, phasedir, desired_divisor; |
| 133 | }; |
| 134 | |
| 135 | static void iclkip_params_init(struct iclkip_params *p) |
| 136 | { |
| 137 | memset(p, 0, sizeof(*p)); |
| 138 | |
| 139 | p->iclk_virtual_root_freq = 172800 * 1000; |
| 140 | p->iclk_pi_range = 64; |
| 141 | } |
| 142 | |
| 143 | static int lpt_iclkip_freq(struct iclkip_params *p) |
| 144 | { |
| 145 | return DIV_ROUND_CLOSEST(p->iclk_virtual_root_freq, |
| 146 | p->desired_divisor << p->auxdiv); |
| 147 | } |
| 148 | |
| 149 | static void lpt_compute_iclkip(struct iclkip_params *p, int clock) |
| 150 | { |
| 151 | iclkip_params_init(p); |
| 152 | |
| 153 | /* The iCLK virtual clock root frequency is in MHz, |
| 154 | * but the adjusted_mode->crtc_clock in KHz. To get the |
| 155 | * divisors, it is necessary to divide one by another, so we |
| 156 | * convert the virtual clock precision to KHz here for higher |
| 157 | * precision. |
| 158 | */ |
| 159 | for (p->auxdiv = 0; p->auxdiv < 2; p->auxdiv++) { |
| 160 | p->desired_divisor = DIV_ROUND_CLOSEST(p->iclk_virtual_root_freq, |
| 161 | clock << p->auxdiv); |
| 162 | p->divsel = (p->desired_divisor / p->iclk_pi_range) - 2; |
| 163 | p->phaseinc = p->desired_divisor % p->iclk_pi_range; |
| 164 | |
| 165 | /* |
| 166 | * Near 20MHz is a corner case which is |
| 167 | * out of range for the 7-bit divisor |
| 168 | */ |
| 169 | if (p->divsel <= 0x7f) |
| 170 | break; |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | int lpt_iclkip(const struct intel_crtc_state *crtc_state) |
| 175 | { |
| 176 | struct iclkip_params p; |
| 177 | |
| 178 | lpt_compute_iclkip(p: &p, clock: crtc_state->hw.adjusted_mode.crtc_clock); |
| 179 | |
| 180 | return lpt_iclkip_freq(p: &p); |
| 181 | } |
| 182 | |
| 183 | /* Program iCLKIP clock to the desired frequency */ |
| 184 | void lpt_program_iclkip(const struct intel_crtc_state *crtc_state) |
| 185 | { |
| 186 | struct intel_display *display = to_intel_display(crtc_state); |
| 187 | int clock = crtc_state->hw.adjusted_mode.crtc_clock; |
| 188 | struct iclkip_params p; |
| 189 | u32 temp; |
| 190 | |
| 191 | lpt_disable_iclkip(display); |
| 192 | |
| 193 | lpt_compute_iclkip(p: &p, clock); |
| 194 | drm_WARN_ON(display->drm, lpt_iclkip_freq(&p) != clock); |
| 195 | |
| 196 | /* This should not happen with any sane values */ |
| 197 | drm_WARN_ON(display->drm, SBI_SSCDIVINTPHASE_DIVSEL(p.divsel) & |
| 198 | ~SBI_SSCDIVINTPHASE_DIVSEL_MASK); |
| 199 | drm_WARN_ON(display->drm, SBI_SSCDIVINTPHASE_DIR(p.phasedir) & |
| 200 | ~SBI_SSCDIVINTPHASE_INCVAL_MASK); |
| 201 | |
| 202 | drm_dbg_kms(display->drm, |
| 203 | "iCLKIP clock: found settings for %dKHz refresh rate: auxdiv=%x, divsel=%x, phasedir=%x, phaseinc=%x\n" , |
| 204 | clock, p.auxdiv, p.divsel, p.phasedir, p.phaseinc); |
| 205 | |
| 206 | intel_sbi_lock(display); |
| 207 | |
| 208 | /* Program SSCDIVINTPHASE6 */ |
| 209 | temp = intel_sbi_read(display, SBI_SSCDIVINTPHASE6, destination: SBI_ICLK); |
| 210 | temp &= ~SBI_SSCDIVINTPHASE_DIVSEL_MASK; |
| 211 | temp |= SBI_SSCDIVINTPHASE_DIVSEL(p.divsel); |
| 212 | temp &= ~SBI_SSCDIVINTPHASE_INCVAL_MASK; |
| 213 | temp |= SBI_SSCDIVINTPHASE_INCVAL(p.phaseinc); |
| 214 | temp |= SBI_SSCDIVINTPHASE_DIR(p.phasedir); |
| 215 | temp |= SBI_SSCDIVINTPHASE_PROPAGATE; |
| 216 | intel_sbi_write(display, SBI_SSCDIVINTPHASE6, value: temp, destination: SBI_ICLK); |
| 217 | |
| 218 | /* Program SSCAUXDIV */ |
| 219 | temp = intel_sbi_read(display, SBI_SSCAUXDIV6, destination: SBI_ICLK); |
| 220 | temp &= ~SBI_SSCAUXDIV_FINALDIV2SEL(1); |
| 221 | temp |= SBI_SSCAUXDIV_FINALDIV2SEL(p.auxdiv); |
| 222 | intel_sbi_write(display, SBI_SSCAUXDIV6, value: temp, destination: SBI_ICLK); |
| 223 | |
| 224 | /* Enable modulator and associated divider */ |
| 225 | temp = intel_sbi_read(display, SBI_SSCCTL6, destination: SBI_ICLK); |
| 226 | temp &= ~SBI_SSCCTL_DISABLE; |
| 227 | intel_sbi_write(display, SBI_SSCCTL6, value: temp, destination: SBI_ICLK); |
| 228 | |
| 229 | intel_sbi_unlock(display); |
| 230 | |
| 231 | /* Wait for initialization time */ |
| 232 | udelay(usec: 24); |
| 233 | |
| 234 | intel_de_write(display, PIXCLK_GATE, PIXCLK_GATE_UNGATE); |
| 235 | } |
| 236 | |
| 237 | int lpt_get_iclkip(struct intel_display *display) |
| 238 | { |
| 239 | struct iclkip_params p; |
| 240 | u32 temp; |
| 241 | |
| 242 | if ((intel_de_read(display, PIXCLK_GATE) & PIXCLK_GATE_UNGATE) == 0) |
| 243 | return 0; |
| 244 | |
| 245 | iclkip_params_init(p: &p); |
| 246 | |
| 247 | intel_sbi_lock(display); |
| 248 | |
| 249 | temp = intel_sbi_read(display, SBI_SSCCTL6, destination: SBI_ICLK); |
| 250 | if (temp & SBI_SSCCTL_DISABLE) { |
| 251 | intel_sbi_unlock(display); |
| 252 | return 0; |
| 253 | } |
| 254 | |
| 255 | temp = intel_sbi_read(display, SBI_SSCDIVINTPHASE6, destination: SBI_ICLK); |
| 256 | p.divsel = (temp & SBI_SSCDIVINTPHASE_DIVSEL_MASK) >> |
| 257 | SBI_SSCDIVINTPHASE_DIVSEL_SHIFT; |
| 258 | p.phaseinc = (temp & SBI_SSCDIVINTPHASE_INCVAL_MASK) >> |
| 259 | SBI_SSCDIVINTPHASE_INCVAL_SHIFT; |
| 260 | |
| 261 | temp = intel_sbi_read(display, SBI_SSCAUXDIV6, destination: SBI_ICLK); |
| 262 | p.auxdiv = (temp & SBI_SSCAUXDIV_FINALDIV2SEL_MASK) >> |
| 263 | SBI_SSCAUXDIV_FINALDIV2SEL_SHIFT; |
| 264 | |
| 265 | intel_sbi_unlock(display); |
| 266 | |
| 267 | p.desired_divisor = (p.divsel + 2) * p.iclk_pi_range + p.phaseinc; |
| 268 | |
| 269 | return lpt_iclkip_freq(p: &p); |
| 270 | } |
| 271 | |
| 272 | /* Implements 3 different sequences from BSpec chapter "Display iCLK |
| 273 | * Programming" based on the parameters passed: |
| 274 | * - Sequence to enable CLKOUT_DP |
| 275 | * - Sequence to enable CLKOUT_DP without spread |
| 276 | * - Sequence to enable CLKOUT_DP for FDI usage and configure PCH FDI I/O |
| 277 | */ |
| 278 | static void lpt_enable_clkout_dp(struct intel_display *display, |
| 279 | bool with_spread, bool with_fdi) |
| 280 | { |
| 281 | u32 reg, tmp; |
| 282 | |
| 283 | if (drm_WARN(display->drm, with_fdi && !with_spread, |
| 284 | "FDI requires downspread\n" )) |
| 285 | with_spread = true; |
| 286 | if (drm_WARN(display->drm, HAS_PCH_LPT_LP(display) && |
| 287 | with_fdi, "LP PCH doesn't have FDI\n" )) |
| 288 | with_fdi = false; |
| 289 | |
| 290 | intel_sbi_lock(display); |
| 291 | |
| 292 | tmp = intel_sbi_read(display, SBI_SSCCTL, destination: SBI_ICLK); |
| 293 | tmp &= ~SBI_SSCCTL_DISABLE; |
| 294 | tmp |= SBI_SSCCTL_PATHALT; |
| 295 | intel_sbi_write(display, SBI_SSCCTL, value: tmp, destination: SBI_ICLK); |
| 296 | |
| 297 | udelay(usec: 24); |
| 298 | |
| 299 | if (with_spread) { |
| 300 | tmp = intel_sbi_read(display, SBI_SSCCTL, destination: SBI_ICLK); |
| 301 | tmp &= ~SBI_SSCCTL_PATHALT; |
| 302 | intel_sbi_write(display, SBI_SSCCTL, value: tmp, destination: SBI_ICLK); |
| 303 | |
| 304 | if (with_fdi) |
| 305 | lpt_fdi_program_mphy(display); |
| 306 | } |
| 307 | |
| 308 | reg = HAS_PCH_LPT_LP(display) ? SBI_GEN0 : SBI_DBUFF0; |
| 309 | tmp = intel_sbi_read(display, reg, destination: SBI_ICLK); |
| 310 | tmp |= SBI_GEN0_CFG_BUFFENABLE_DISABLE; |
| 311 | intel_sbi_write(display, reg, value: tmp, destination: SBI_ICLK); |
| 312 | |
| 313 | intel_sbi_unlock(display); |
| 314 | } |
| 315 | |
| 316 | /* Sequence to disable CLKOUT_DP */ |
| 317 | void lpt_disable_clkout_dp(struct intel_display *display) |
| 318 | { |
| 319 | u32 reg, tmp; |
| 320 | |
| 321 | intel_sbi_lock(display); |
| 322 | |
| 323 | reg = HAS_PCH_LPT_LP(display) ? SBI_GEN0 : SBI_DBUFF0; |
| 324 | tmp = intel_sbi_read(display, reg, destination: SBI_ICLK); |
| 325 | tmp &= ~SBI_GEN0_CFG_BUFFENABLE_DISABLE; |
| 326 | intel_sbi_write(display, reg, value: tmp, destination: SBI_ICLK); |
| 327 | |
| 328 | tmp = intel_sbi_read(display, SBI_SSCCTL, destination: SBI_ICLK); |
| 329 | if (!(tmp & SBI_SSCCTL_DISABLE)) { |
| 330 | if (!(tmp & SBI_SSCCTL_PATHALT)) { |
| 331 | tmp |= SBI_SSCCTL_PATHALT; |
| 332 | intel_sbi_write(display, SBI_SSCCTL, value: tmp, destination: SBI_ICLK); |
| 333 | udelay(usec: 32); |
| 334 | } |
| 335 | tmp |= SBI_SSCCTL_DISABLE; |
| 336 | intel_sbi_write(display, SBI_SSCCTL, value: tmp, destination: SBI_ICLK); |
| 337 | } |
| 338 | |
| 339 | intel_sbi_unlock(display); |
| 340 | } |
| 341 | |
| 342 | #define BEND_IDX(steps) ((50 + (steps)) / 5) |
| 343 | |
| 344 | static const u16 sscdivintphase[] = { |
| 345 | [BEND_IDX( 50)] = 0x3B23, |
| 346 | [BEND_IDX( 45)] = 0x3B23, |
| 347 | [BEND_IDX( 40)] = 0x3C23, |
| 348 | [BEND_IDX( 35)] = 0x3C23, |
| 349 | [BEND_IDX( 30)] = 0x3D23, |
| 350 | [BEND_IDX( 25)] = 0x3D23, |
| 351 | [BEND_IDX( 20)] = 0x3E23, |
| 352 | [BEND_IDX( 15)] = 0x3E23, |
| 353 | [BEND_IDX( 10)] = 0x3F23, |
| 354 | [BEND_IDX( 5)] = 0x3F23, |
| 355 | [BEND_IDX( 0)] = 0x0025, |
| 356 | [BEND_IDX( -5)] = 0x0025, |
| 357 | [BEND_IDX(-10)] = 0x0125, |
| 358 | [BEND_IDX(-15)] = 0x0125, |
| 359 | [BEND_IDX(-20)] = 0x0225, |
| 360 | [BEND_IDX(-25)] = 0x0225, |
| 361 | [BEND_IDX(-30)] = 0x0325, |
| 362 | [BEND_IDX(-35)] = 0x0325, |
| 363 | [BEND_IDX(-40)] = 0x0425, |
| 364 | [BEND_IDX(-45)] = 0x0425, |
| 365 | [BEND_IDX(-50)] = 0x0525, |
| 366 | }; |
| 367 | |
| 368 | /* |
| 369 | * Bend CLKOUT_DP |
| 370 | * steps -50 to 50 inclusive, in steps of 5 |
| 371 | * < 0 slow down the clock, > 0 speed up the clock, 0 == no bend (135MHz) |
| 372 | * change in clock period = -(steps / 10) * 5.787 ps |
| 373 | */ |
| 374 | static void lpt_bend_clkout_dp(struct intel_display *display, int steps) |
| 375 | { |
| 376 | u32 tmp; |
| 377 | int idx = BEND_IDX(steps); |
| 378 | |
| 379 | if (drm_WARN_ON(display->drm, steps % 5 != 0)) |
| 380 | return; |
| 381 | |
| 382 | if (drm_WARN_ON(display->drm, idx >= ARRAY_SIZE(sscdivintphase))) |
| 383 | return; |
| 384 | |
| 385 | intel_sbi_lock(display); |
| 386 | |
| 387 | if (steps % 10 != 0) |
| 388 | tmp = 0xAAAAAAAB; |
| 389 | else |
| 390 | tmp = 0x00000000; |
| 391 | intel_sbi_write(display, SBI_SSCDITHPHASE, value: tmp, destination: SBI_ICLK); |
| 392 | |
| 393 | tmp = intel_sbi_read(display, SBI_SSCDIVINTPHASE, destination: SBI_ICLK); |
| 394 | tmp &= 0xffff0000; |
| 395 | tmp |= sscdivintphase[idx]; |
| 396 | intel_sbi_write(display, SBI_SSCDIVINTPHASE, value: tmp, destination: SBI_ICLK); |
| 397 | |
| 398 | intel_sbi_unlock(display); |
| 399 | } |
| 400 | |
| 401 | #undef BEND_IDX |
| 402 | |
| 403 | static bool spll_uses_pch_ssc(struct intel_display *display) |
| 404 | { |
| 405 | u32 fuse_strap = intel_de_read(display, FUSE_STRAP); |
| 406 | u32 ctl = intel_de_read(display, SPLL_CTL); |
| 407 | |
| 408 | if ((ctl & SPLL_PLL_ENABLE) == 0) |
| 409 | return false; |
| 410 | |
| 411 | if ((ctl & SPLL_REF_MASK) == SPLL_REF_MUXED_SSC && |
| 412 | (fuse_strap & HSW_CPU_SSC_ENABLE) == 0) |
| 413 | return true; |
| 414 | |
| 415 | if (display->platform.broadwell && |
| 416 | (ctl & SPLL_REF_MASK) == SPLL_REF_PCH_SSC_BDW) |
| 417 | return true; |
| 418 | |
| 419 | return false; |
| 420 | } |
| 421 | |
| 422 | static bool wrpll_uses_pch_ssc(struct intel_display *display, enum intel_dpll_id id) |
| 423 | { |
| 424 | u32 fuse_strap = intel_de_read(display, FUSE_STRAP); |
| 425 | u32 ctl = intel_de_read(display, WRPLL_CTL(id)); |
| 426 | |
| 427 | if ((ctl & WRPLL_PLL_ENABLE) == 0) |
| 428 | return false; |
| 429 | |
| 430 | if ((ctl & WRPLL_REF_MASK) == WRPLL_REF_PCH_SSC) |
| 431 | return true; |
| 432 | |
| 433 | if ((display->platform.broadwell || display->platform.haswell_ult) && |
| 434 | (ctl & WRPLL_REF_MASK) == WRPLL_REF_MUXED_SSC_BDW && |
| 435 | (fuse_strap & HSW_CPU_SSC_ENABLE) == 0) |
| 436 | return true; |
| 437 | |
| 438 | return false; |
| 439 | } |
| 440 | |
| 441 | static void lpt_init_pch_refclk(struct intel_display *display) |
| 442 | { |
| 443 | struct intel_encoder *encoder; |
| 444 | bool has_fdi = false; |
| 445 | |
| 446 | for_each_intel_encoder(display->drm, encoder) { |
| 447 | switch (encoder->type) { |
| 448 | case INTEL_OUTPUT_ANALOG: |
| 449 | has_fdi = true; |
| 450 | break; |
| 451 | default: |
| 452 | break; |
| 453 | } |
| 454 | } |
| 455 | |
| 456 | /* |
| 457 | * The BIOS may have decided to use the PCH SSC |
| 458 | * reference so we must not disable it until the |
| 459 | * relevant PLLs have stopped relying on it. We'll |
| 460 | * just leave the PCH SSC reference enabled in case |
| 461 | * any active PLL is using it. It will get disabled |
| 462 | * after runtime suspend if we don't have FDI. |
| 463 | * |
| 464 | * TODO: Move the whole reference clock handling |
| 465 | * to the modeset sequence proper so that we can |
| 466 | * actually enable/disable/reconfigure these things |
| 467 | * safely. To do that we need to introduce a real |
| 468 | * clock hierarchy. That would also allow us to do |
| 469 | * clock bending finally. |
| 470 | */ |
| 471 | display->dpll.pch_ssc_use = 0; |
| 472 | |
| 473 | if (spll_uses_pch_ssc(display)) { |
| 474 | drm_dbg_kms(display->drm, "SPLL using PCH SSC\n" ); |
| 475 | display->dpll.pch_ssc_use |= BIT(DPLL_ID_SPLL); |
| 476 | } |
| 477 | |
| 478 | if (wrpll_uses_pch_ssc(display, id: DPLL_ID_WRPLL1)) { |
| 479 | drm_dbg_kms(display->drm, "WRPLL1 using PCH SSC\n" ); |
| 480 | display->dpll.pch_ssc_use |= BIT(DPLL_ID_WRPLL1); |
| 481 | } |
| 482 | |
| 483 | if (wrpll_uses_pch_ssc(display, id: DPLL_ID_WRPLL2)) { |
| 484 | drm_dbg_kms(display->drm, "WRPLL2 using PCH SSC\n" ); |
| 485 | display->dpll.pch_ssc_use |= BIT(DPLL_ID_WRPLL2); |
| 486 | } |
| 487 | |
| 488 | if (display->dpll.pch_ssc_use) |
| 489 | return; |
| 490 | |
| 491 | if (has_fdi) { |
| 492 | lpt_bend_clkout_dp(display, steps: 0); |
| 493 | lpt_enable_clkout_dp(display, with_spread: true, with_fdi: true); |
| 494 | } else { |
| 495 | lpt_disable_clkout_dp(display); |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | static void ilk_init_pch_refclk(struct intel_display *display) |
| 500 | { |
| 501 | struct intel_encoder *encoder; |
| 502 | struct intel_dpll *pll; |
| 503 | int i; |
| 504 | u32 val, final; |
| 505 | bool has_lvds = false; |
| 506 | bool has_cpu_edp = false; |
| 507 | bool has_panel = false; |
| 508 | bool has_ck505 = false; |
| 509 | bool can_ssc = false; |
| 510 | bool using_ssc_source = false; |
| 511 | |
| 512 | /* We need to take the global config into account */ |
| 513 | for_each_intel_encoder(display->drm, encoder) { |
| 514 | switch (encoder->type) { |
| 515 | case INTEL_OUTPUT_LVDS: |
| 516 | has_panel = true; |
| 517 | has_lvds = true; |
| 518 | break; |
| 519 | case INTEL_OUTPUT_EDP: |
| 520 | has_panel = true; |
| 521 | if (encoder->port == PORT_A) |
| 522 | has_cpu_edp = true; |
| 523 | break; |
| 524 | default: |
| 525 | break; |
| 526 | } |
| 527 | } |
| 528 | |
| 529 | if (HAS_PCH_IBX(display)) { |
| 530 | has_ck505 = display->vbt.display_clock_mode; |
| 531 | can_ssc = has_ck505; |
| 532 | } else { |
| 533 | has_ck505 = false; |
| 534 | can_ssc = true; |
| 535 | } |
| 536 | |
| 537 | /* Check if any DPLLs are using the SSC source */ |
| 538 | for_each_dpll(display, pll, i) { |
| 539 | u32 temp; |
| 540 | |
| 541 | temp = intel_de_read(display, PCH_DPLL(pll->info->id)); |
| 542 | |
| 543 | if (!(temp & DPLL_VCO_ENABLE)) |
| 544 | continue; |
| 545 | |
| 546 | if ((temp & PLL_REF_INPUT_MASK) == |
| 547 | PLLB_REF_INPUT_SPREADSPECTRUMIN) { |
| 548 | using_ssc_source = true; |
| 549 | break; |
| 550 | } |
| 551 | } |
| 552 | |
| 553 | drm_dbg_kms(display->drm, |
| 554 | "has_panel %d has_lvds %d has_ck505 %d using_ssc_source %d\n" , |
| 555 | has_panel, has_lvds, has_ck505, using_ssc_source); |
| 556 | |
| 557 | /* Ironlake: try to setup display ref clock before DPLL |
| 558 | * enabling. This is only under driver's control after |
| 559 | * PCH B stepping, previous chipset stepping should be |
| 560 | * ignoring this setting. |
| 561 | */ |
| 562 | val = intel_de_read(display, PCH_DREF_CONTROL); |
| 563 | |
| 564 | /* As we must carefully and slowly disable/enable each source in turn, |
| 565 | * compute the final state we want first and check if we need to |
| 566 | * make any changes at all. |
| 567 | */ |
| 568 | final = val; |
| 569 | final &= ~DREF_NONSPREAD_SOURCE_MASK; |
| 570 | if (has_ck505) |
| 571 | final |= DREF_NONSPREAD_CK505_ENABLE; |
| 572 | else |
| 573 | final |= DREF_NONSPREAD_SOURCE_ENABLE; |
| 574 | |
| 575 | final &= ~DREF_SSC_SOURCE_MASK; |
| 576 | final &= ~DREF_CPU_SOURCE_OUTPUT_MASK; |
| 577 | final &= ~DREF_SSC1_ENABLE; |
| 578 | |
| 579 | if (has_panel) { |
| 580 | final |= DREF_SSC_SOURCE_ENABLE; |
| 581 | |
| 582 | if (intel_panel_use_ssc(display) && can_ssc) |
| 583 | final |= DREF_SSC1_ENABLE; |
| 584 | |
| 585 | if (has_cpu_edp) { |
| 586 | if (intel_panel_use_ssc(display) && can_ssc) |
| 587 | final |= DREF_CPU_SOURCE_OUTPUT_DOWNSPREAD; |
| 588 | else |
| 589 | final |= DREF_CPU_SOURCE_OUTPUT_NONSPREAD; |
| 590 | } else { |
| 591 | final |= DREF_CPU_SOURCE_OUTPUT_DISABLE; |
| 592 | } |
| 593 | } else if (using_ssc_source) { |
| 594 | final |= DREF_SSC_SOURCE_ENABLE; |
| 595 | final |= DREF_SSC1_ENABLE; |
| 596 | } |
| 597 | |
| 598 | if (final == val) |
| 599 | return; |
| 600 | |
| 601 | /* Always enable nonspread source */ |
| 602 | val &= ~DREF_NONSPREAD_SOURCE_MASK; |
| 603 | |
| 604 | if (has_ck505) |
| 605 | val |= DREF_NONSPREAD_CK505_ENABLE; |
| 606 | else |
| 607 | val |= DREF_NONSPREAD_SOURCE_ENABLE; |
| 608 | |
| 609 | if (has_panel) { |
| 610 | val &= ~DREF_SSC_SOURCE_MASK; |
| 611 | val |= DREF_SSC_SOURCE_ENABLE; |
| 612 | |
| 613 | /* SSC must be turned on before enabling the CPU output */ |
| 614 | if (intel_panel_use_ssc(display) && can_ssc) { |
| 615 | drm_dbg_kms(display->drm, "Using SSC on panel\n" ); |
| 616 | val |= DREF_SSC1_ENABLE; |
| 617 | } else { |
| 618 | val &= ~DREF_SSC1_ENABLE; |
| 619 | } |
| 620 | |
| 621 | /* Get SSC going before enabling the outputs */ |
| 622 | intel_de_write(display, PCH_DREF_CONTROL, val); |
| 623 | intel_de_posting_read(display, PCH_DREF_CONTROL); |
| 624 | udelay(usec: 200); |
| 625 | |
| 626 | val &= ~DREF_CPU_SOURCE_OUTPUT_MASK; |
| 627 | |
| 628 | /* Enable CPU source on CPU attached eDP */ |
| 629 | if (has_cpu_edp) { |
| 630 | if (intel_panel_use_ssc(display) && can_ssc) { |
| 631 | drm_dbg_kms(display->drm, |
| 632 | "Using SSC on eDP\n" ); |
| 633 | val |= DREF_CPU_SOURCE_OUTPUT_DOWNSPREAD; |
| 634 | } else { |
| 635 | val |= DREF_CPU_SOURCE_OUTPUT_NONSPREAD; |
| 636 | } |
| 637 | } else { |
| 638 | val |= DREF_CPU_SOURCE_OUTPUT_DISABLE; |
| 639 | } |
| 640 | |
| 641 | intel_de_write(display, PCH_DREF_CONTROL, val); |
| 642 | intel_de_posting_read(display, PCH_DREF_CONTROL); |
| 643 | udelay(usec: 200); |
| 644 | } else { |
| 645 | drm_dbg_kms(display->drm, "Disabling CPU source output\n" ); |
| 646 | |
| 647 | val &= ~DREF_CPU_SOURCE_OUTPUT_MASK; |
| 648 | |
| 649 | /* Turn off CPU output */ |
| 650 | val |= DREF_CPU_SOURCE_OUTPUT_DISABLE; |
| 651 | |
| 652 | intel_de_write(display, PCH_DREF_CONTROL, val); |
| 653 | intel_de_posting_read(display, PCH_DREF_CONTROL); |
| 654 | udelay(usec: 200); |
| 655 | |
| 656 | if (!using_ssc_source) { |
| 657 | drm_dbg_kms(display->drm, "Disabling SSC source\n" ); |
| 658 | |
| 659 | /* Turn off the SSC source */ |
| 660 | val &= ~DREF_SSC_SOURCE_MASK; |
| 661 | val |= DREF_SSC_SOURCE_DISABLE; |
| 662 | |
| 663 | /* Turn off SSC1 */ |
| 664 | val &= ~DREF_SSC1_ENABLE; |
| 665 | |
| 666 | intel_de_write(display, PCH_DREF_CONTROL, val); |
| 667 | intel_de_posting_read(display, PCH_DREF_CONTROL); |
| 668 | udelay(usec: 200); |
| 669 | } |
| 670 | } |
| 671 | |
| 672 | drm_WARN_ON(display->drm, val != final); |
| 673 | } |
| 674 | |
| 675 | /* |
| 676 | * Initialize reference clocks when the driver loads |
| 677 | */ |
| 678 | void intel_init_pch_refclk(struct intel_display *display) |
| 679 | { |
| 680 | if (HAS_PCH_IBX(display) || HAS_PCH_CPT(display)) |
| 681 | ilk_init_pch_refclk(display); |
| 682 | else if (HAS_PCH_LPT(display)) |
| 683 | lpt_init_pch_refclk(display); |
| 684 | } |
| 685 | |