1 /* 2 * Copyright 2006-2011, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Alexander von Gluck, kallisti5@unixzen.com 7 */ 8 9 10 #include "pll.h" 11 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 #include <math.h> 16 17 #include "accelerant_protos.h" 18 #include "accelerant.h" 19 #include "bios.h" 20 #include "connector.h" 21 #include "display.h" 22 #include "displayport.h" 23 #include "encoder.h" 24 #include "utility.h" 25 26 27 #define TRACE_PLL 28 #ifdef TRACE_PLL 29 extern "C" void _sPrintf(const char* format, ...); 30 # define TRACE(x...) _sPrintf("radeon_hd: " x) 31 #else 32 # define TRACE(x...) ; 33 #endif 34 35 #define ERROR(x...) _sPrintf("radeon_hd: " x) 36 37 // Pixel Clock Storage 38 // kHz Value Result 39 // Haiku: 104000 khz 104 Mhz 40 // Linux: 104000 khz 104 Mhz 41 // AtomBIOS: 10400 * 10 khz 104 Mhz 42 // Ghz 43 // Haiku: 162000 * 10 khz 1.62 Ghz 44 // Linux: 162000 * 10 khz 1.62 Ghz 45 // AtomBIOS: 16200 * 10 Khz 0.162 * 10 Ghz 46 47 48 /* The PLL allows to synthesize a clock signal with a range of frequencies 49 * based on a single input reference clock signal. It uses several dividers 50 * to create a rational factor multiple of the input frequency. 51 * 52 * The reference clock signal frequency is pll_info::referenceFreq (in kHz). 53 * It is then, one after another... 54 * (1) divided by the (integer) reference divider (pll_info::referenceDiv). 55 * (2) multiplied by the fractional feedback divider, which sits in the 56 * PLL's feedback loop and thus multiplies the frequency. It allows 57 * using a rational number factor of the form "x.y", with 58 * x = pll_info::feedbackDiv and y = pll_info::feedbackDivFrac. 59 * (3) divided by the (integer) post divider (pll_info::postDiv). 60 * Allowed ranges are given in the pll_info min/max values. 61 * 62 * The resulting output pixel clock frequency is then: 63 * 64 * feedbackDiv + (feedbackDivFrac/10) 65 * f_out = referenceFreq * ------------------------------------ 66 * referenceDiv * postDiv 67 */ 68 69 70 status_t 71 pll_limit_probe(pll_info* pll) 72 { 73 uint8 tableMajor; 74 uint8 tableMinor; 75 uint16 tableOffset; 76 77 int index = GetIndexIntoMasterTable(DATA, FirmwareInfo); 78 if (atom_parse_data_header(gAtomContext, index, NULL, 79 &tableMajor, &tableMinor, &tableOffset) != B_OK) { 80 ERROR("%s: Couldn't parse data header\n", __func__); 81 return B_ERROR; 82 } 83 84 TRACE("%s: table %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, 85 tableMajor, tableMinor); 86 87 union atomFirmwareInfo { 88 ATOM_FIRMWARE_INFO info; 89 ATOM_FIRMWARE_INFO_V1_2 info_12; 90 ATOM_FIRMWARE_INFO_V1_3 info_13; 91 ATOM_FIRMWARE_INFO_V1_4 info_14; 92 ATOM_FIRMWARE_INFO_V2_1 info_21; 93 ATOM_FIRMWARE_INFO_V2_2 info_22; 94 }; 95 union atomFirmwareInfo* firmwareInfo 96 = (union atomFirmwareInfo*)(gAtomContext->bios + tableOffset); 97 98 /* pixel clock limits */ 99 pll->referenceFreq 100 = B_LENDIAN_TO_HOST_INT16(firmwareInfo->info.usReferenceClock) * 10; 101 102 if (tableMinor < 2) { 103 pll->pllOutMin 104 = B_LENDIAN_TO_HOST_INT16( 105 firmwareInfo->info.usMinPixelClockPLL_Output) * 10; 106 } else { 107 pll->pllOutMin 108 = B_LENDIAN_TO_HOST_INT32( 109 firmwareInfo->info_12.ulMinPixelClockPLL_Output) * 10; 110 } 111 112 pll->pllOutMax 113 = B_LENDIAN_TO_HOST_INT32( 114 firmwareInfo->info.ulMaxPixelClockPLL_Output) * 10; 115 116 if (tableMinor >= 4) { 117 pll->lcdPllOutMin 118 = B_LENDIAN_TO_HOST_INT16( 119 firmwareInfo->info_14.usLcdMinPixelClockPLL_Output) * 1000; 120 121 if (pll->lcdPllOutMin == 0) 122 pll->lcdPllOutMin = pll->pllOutMin; 123 124 pll->lcdPllOutMax 125 = B_LENDIAN_TO_HOST_INT16( 126 firmwareInfo->info_14.usLcdMaxPixelClockPLL_Output) * 1000; 127 128 if (pll->lcdPllOutMax == 0) 129 pll->lcdPllOutMax = pll->pllOutMax; 130 131 } else { 132 pll->lcdPllOutMin = pll->pllOutMin; 133 pll->lcdPllOutMax = pll->pllOutMax; 134 } 135 136 if (pll->pllOutMin == 0) { 137 pll->pllOutMin = 64800 * 10; 138 // Avivo+ limit 139 } 140 141 pll->minPostDiv = POST_DIV_MIN; 142 pll->maxPostDiv = POST_DIV_LIMIT; 143 pll->minRefDiv = REF_DIV_MIN; 144 pll->maxRefDiv = REF_DIV_LIMIT; 145 pll->minFeedbackDiv = FB_DIV_MIN; 146 pll->maxFeedbackDiv = FB_DIV_LIMIT; 147 148 pll->pllInMin = B_LENDIAN_TO_HOST_INT16( 149 firmwareInfo->info.usMinPixelClockPLL_Input) * 10; 150 pll->pllInMax = B_LENDIAN_TO_HOST_INT16( 151 firmwareInfo->info.usMaxPixelClockPLL_Input) * 10; 152 153 TRACE("%s: referenceFreq: %" B_PRIu32 "; pllOutMin: %" B_PRIu32 "; " 154 " pllOutMax: %" B_PRIu32 "; pllInMin: %" B_PRIu32 ";" 155 "pllInMax: %" B_PRIu32 "\n", __func__, pll->referenceFreq, 156 pll->pllOutMin, pll->pllOutMax, pll->pllInMin, pll->pllInMax); 157 158 return B_OK; 159 } 160 161 162 status_t 163 pll_ppll_ss_probe(pll_info* pll, uint32 ssID) 164 { 165 uint8 tableMajor; 166 uint8 tableMinor; 167 uint16 headerOffset; 168 uint16 headerSize; 169 170 int index = GetIndexIntoMasterTable(DATA, PPLL_SS_Info); 171 if (atom_parse_data_header(gAtomContext, index, &headerSize, 172 &tableMajor, &tableMinor, &headerOffset) != B_OK) { 173 ERROR("%s: Couldn't parse data header\n", __func__); 174 return B_ERROR; 175 } 176 177 struct _ATOM_SPREAD_SPECTRUM_INFO *ss_info 178 = (struct _ATOM_SPREAD_SPECTRUM_INFO*)((uint16*)gAtomContext->bios 179 + headerOffset); 180 181 int indices = (headerSize - sizeof(ATOM_COMMON_TABLE_HEADER)) 182 / sizeof(ATOM_SPREAD_SPECTRUM_ASSIGNMENT); 183 184 int i; 185 for (i = 0; i < indices; i++) { 186 if (ss_info->asSS_Info[i].ucSS_Id == ssID) { 187 pll->ssPercentage = B_LENDIAN_TO_HOST_INT16( 188 ss_info->asSS_Info[i].usSpreadSpectrumPercentage); 189 pll->ssType = ss_info->asSS_Info[i].ucSpreadSpectrumType; 190 pll->ssStep = ss_info->asSS_Info[i].ucSS_Step; 191 pll->ssDelay = ss_info->asSS_Info[i].ucSS_Delay; 192 pll->ssRange = ss_info->asSS_Info[i].ucSS_Range; 193 pll->ssReferenceDiv 194 = ss_info->asSS_Info[i].ucRecommendedRef_Div; 195 return B_OK; 196 } 197 } 198 199 return B_ERROR; 200 } 201 202 203 status_t 204 pll_asic_ss_probe(pll_info* pll, uint32 ssID) 205 { 206 uint8 tableMajor; 207 uint8 tableMinor; 208 uint16 headerOffset; 209 uint16 headerSize; 210 211 int index = GetIndexIntoMasterTable(DATA, ASIC_InternalSS_Info); 212 if (atom_parse_data_header(gAtomContext, index, &headerSize, 213 &tableMajor, &tableMinor, &headerOffset) != B_OK) { 214 ERROR("%s: Couldn't parse data header\n", __func__); 215 return B_ERROR; 216 } 217 218 union asicSSInfo { 219 struct _ATOM_ASIC_INTERNAL_SS_INFO info; 220 struct _ATOM_ASIC_INTERNAL_SS_INFO_V2 info_2; 221 struct _ATOM_ASIC_INTERNAL_SS_INFO_V3 info_3; 222 }; 223 224 union asicSSInfo *ss_info 225 = (union asicSSInfo*)((uint16*)gAtomContext->bios + headerOffset); 226 227 int i; 228 int indices; 229 switch (tableMajor) { 230 case 1: 231 indices = (headerSize - sizeof(ATOM_COMMON_TABLE_HEADER)) 232 / sizeof(ATOM_ASIC_SS_ASSIGNMENT); 233 234 for (i = 0; i < indices; i++) { 235 if (ss_info->info.asSpreadSpectrum[i].ucClockIndication 236 != ssID) { 237 continue; 238 } 239 TRACE("%s: ss match found\n", __func__); 240 if (pll->pixelClock * 10 > B_LENDIAN_TO_HOST_INT32( 241 ss_info->info.asSpreadSpectrum[i].ulTargetClockRange)) { 242 TRACE("%s: pixelClock > targetClockRange!\n", __func__); 243 continue; 244 } 245 246 pll->ssPercentage = B_LENDIAN_TO_HOST_INT16( 247 ss_info->info.asSpreadSpectrum[i].usSpreadSpectrumPercentage 248 ); 249 250 pll->ssType 251 = ss_info->info.asSpreadSpectrum[i].ucSpreadSpectrumMode; 252 pll->ssRate = B_LENDIAN_TO_HOST_INT16( 253 ss_info->info.asSpreadSpectrum[i].usSpreadRateInKhz); 254 return B_OK; 255 } 256 break; 257 case 2: 258 indices = (headerSize - sizeof(ATOM_COMMON_TABLE_HEADER)) 259 / sizeof(ATOM_ASIC_SS_ASSIGNMENT_V2); 260 261 for (i = 0; i < indices; i++) { 262 if (ss_info->info_2.asSpreadSpectrum[i].ucClockIndication 263 != ssID) { 264 continue; 265 } 266 TRACE("%s: ss match found\n", __func__); 267 if (pll->pixelClock * 10 > B_LENDIAN_TO_HOST_INT32( 268 ss_info->info_2.asSpreadSpectrum[i].ulTargetClockRange)) { 269 TRACE("%s: pixelClock > targetClockRange!\n", __func__); 270 continue; 271 } 272 273 pll->ssPercentage = B_LENDIAN_TO_HOST_INT16( 274 ss_info 275 ->info_2.asSpreadSpectrum[i].usSpreadSpectrumPercentage 276 ); 277 278 pll->ssType 279 = ss_info->info_2.asSpreadSpectrum[i].ucSpreadSpectrumMode; 280 pll->ssRate = B_LENDIAN_TO_HOST_INT16( 281 ss_info->info_2.asSpreadSpectrum[i].usSpreadRateIn10Hz); 282 return B_OK; 283 } 284 break; 285 case 3: 286 indices = (headerSize - sizeof(ATOM_COMMON_TABLE_HEADER)) 287 / sizeof(ATOM_ASIC_SS_ASSIGNMENT_V3); 288 289 for (i = 0; i < indices; i++) { 290 if (ss_info->info_3.asSpreadSpectrum[i].ucClockIndication 291 != ssID) { 292 continue; 293 } 294 TRACE("%s: ss match found\n", __func__); 295 if (pll->pixelClock * 10 > B_LENDIAN_TO_HOST_INT32( 296 ss_info->info_3.asSpreadSpectrum[i].ulTargetClockRange)) { 297 TRACE("%s: pixelClock > targetClockRange!\n", __func__); 298 continue; 299 } 300 301 pll->ssPercentage = B_LENDIAN_TO_HOST_INT16( 302 ss_info 303 ->info_3.asSpreadSpectrum[i].usSpreadSpectrumPercentage 304 ); 305 306 pll->ssType 307 = ss_info->info_3.asSpreadSpectrum[i].ucSpreadSpectrumMode; 308 pll->ssRate = B_LENDIAN_TO_HOST_INT16( 309 ss_info->info_3.asSpreadSpectrum[i].usSpreadRateIn10Hz); 310 return B_OK; 311 } 312 break; 313 default: 314 ERROR("%s: Unknown SS table version!\n", __func__); 315 return B_ERROR; 316 } 317 318 ERROR("%s: No potential spread spectrum data found!\n", __func__); 319 return B_ERROR; 320 } 321 322 323 void 324 pll_compute_post_divider(pll_info* pll) 325 { 326 if ((pll->flags & PLL_USE_POST_DIV) != 0) { 327 TRACE("%s: using AtomBIOS post divider\n", __func__); 328 return; 329 } 330 331 uint32 vco; 332 if ((pll->flags & PLL_PREFER_MINM_OVER_MAXP) != 0) { 333 if ((pll->flags & PLL_IS_LCD) != 0) 334 vco = pll->lcdPllOutMin; 335 else 336 vco = pll->pllOutMax; 337 } else { 338 if ((pll->flags & PLL_IS_LCD) != 0) 339 vco = pll->lcdPllOutMax; 340 else 341 vco = pll->pllOutMin; 342 } 343 344 TRACE("%s: vco = %" B_PRIu32 "\n", __func__, vco); 345 346 uint32 postDivider = vco / pll->adjustedClock; 347 uint32 tmp = vco % pll->adjustedClock; 348 349 if ((pll->flags & PLL_PREFER_MINM_OVER_MAXP) != 0) { 350 if (tmp) 351 postDivider++; 352 } else { 353 if (!tmp) 354 postDivider--; 355 } 356 357 if (postDivider > pll->maxPostDiv) 358 postDivider = pll->maxPostDiv; 359 else if (postDivider < pll->minPostDiv) 360 postDivider = pll->minPostDiv; 361 362 pll->postDiv = postDivider; 363 TRACE("%s: postDiv = %" B_PRIu32 "\n", __func__, postDivider); 364 } 365 366 367 /*! Compute values for the fractional feedback divider to match the desired 368 * pixel clock frequency as closely as possible. Reference and post divider 369 * values are already filled in (if used). 370 */ 371 status_t 372 pll_compute(pll_info* pll) 373 { 374 pll_compute_post_divider(pll); 375 376 const uint32 targetClock = pll->adjustedClock; 377 378 pll->feedbackDiv = 0; 379 pll->feedbackDivFrac = 0; 380 const uint32 referenceFrequency = pll->referenceFreq; 381 382 if ((pll->flags & PLL_USE_REF_DIV) != 0) { 383 TRACE("%s: using AtomBIOS reference divider\n", __func__); 384 } else { 385 TRACE("%s: using minimum reference divider\n", __func__); 386 pll->referenceDiv = pll->minRefDiv; 387 } 388 389 if ((pll->flags & PLL_USE_FRAC_FB_DIV) != 0) { 390 TRACE("%s: using AtomBIOS fractional feedback divider\n", __func__); 391 392 const uint32 numerator = pll->postDiv * pll->referenceDiv 393 * targetClock; 394 pll->feedbackDiv = numerator / referenceFrequency; 395 pll->feedbackDivFrac = numerator % referenceFrequency; 396 397 if (pll->feedbackDiv > pll->maxFeedbackDiv) 398 pll->feedbackDiv = pll->maxFeedbackDiv; 399 else if (pll->feedbackDiv < pll->minFeedbackDiv) 400 pll->feedbackDiv = pll->minFeedbackDiv; 401 402 // Put first 2 digits after the decimal point into feedbackDivFrac 403 pll->feedbackDivFrac 404 = (100 * pll->feedbackDivFrac) / referenceFrequency; 405 406 // Now round it to one digit 407 if (pll->feedbackDivFrac >= 5) { 408 pll->feedbackDivFrac -= 5; 409 pll->feedbackDivFrac /= 10; 410 pll->feedbackDivFrac++; 411 } 412 if (pll->feedbackDivFrac >= 10) { 413 pll->feedbackDiv++; 414 pll->feedbackDivFrac = 0; 415 } 416 } else { 417 TRACE("%s: performing fractional feedback calculations\n", __func__); 418 419 while (pll->referenceDiv <= pll->maxRefDiv) { 420 // get feedback divider 421 uint32 retroEncabulator = pll->postDiv * pll->referenceDiv; 422 423 retroEncabulator *= targetClock; 424 pll->feedbackDiv = retroEncabulator / referenceFrequency; 425 pll->feedbackDivFrac 426 = retroEncabulator % referenceFrequency; 427 428 if (pll->feedbackDiv > pll->maxFeedbackDiv) 429 pll->feedbackDiv = pll->maxFeedbackDiv; 430 else if (pll->feedbackDiv < pll->minFeedbackDiv) 431 pll->feedbackDiv = pll->minFeedbackDiv; 432 433 if (pll->feedbackDivFrac >= (referenceFrequency / 2)) 434 pll->feedbackDiv++; 435 436 pll->feedbackDivFrac = 0; 437 438 if (pll->referenceDiv == 0 439 || pll->postDiv == 0 440 || targetClock == 0) { 441 TRACE("%s: Caught division by zero!\n", __func__); 442 TRACE("%s: referenceDiv %" B_PRIu32 "\n", 443 __func__, pll->referenceDiv); 444 TRACE("%s: postDiv %" B_PRIu32 "\n", 445 __func__, pll->postDiv); 446 TRACE("%s: targetClock %" B_PRIu32 "\n", 447 __func__, targetClock); 448 return B_ERROR; 449 } 450 uint32 tmp = (referenceFrequency * pll->feedbackDiv) 451 / (pll->postDiv * pll->referenceDiv); 452 tmp = (tmp * 1000) / targetClock; 453 454 if (tmp > (1000 + (MAX_TOLERANCE / 10))) 455 pll->referenceDiv++; 456 else if (tmp >= (1000 - (MAX_TOLERANCE / 10))) 457 break; 458 else 459 pll->referenceDiv++; 460 } 461 } 462 463 if (pll->referenceDiv == 0 || pll->postDiv == 0) { 464 TRACE("%s: Caught division by zero of post or reference divider\n", 465 __func__); 466 return B_ERROR; 467 } 468 469 uint32 calculatedClock 470 = ((referenceFrequency * pll->feedbackDiv * 10) 471 + (referenceFrequency * pll->feedbackDivFrac)) 472 / (pll->referenceDiv * pll->postDiv * 10); 473 474 TRACE("%s: Calculated pixel clock of %" B_PRIu32 " based on:\n", __func__, 475 calculatedClock); 476 TRACE("%s: referenceFrequency: %" B_PRIu32 "; " 477 "referenceDivider: %" B_PRIu32 "\n", __func__, referenceFrequency, 478 pll->referenceDiv); 479 TRACE("%s: feedbackDivider: %" B_PRIu32 "; " 480 "feedbackDividerFrac: %" B_PRIu32 "\n", __func__, pll->feedbackDiv, 481 pll->feedbackDivFrac); 482 TRACE("%s: postDivider: %" B_PRIu32 "\n", __func__, pll->postDiv); 483 484 if (pll->adjustedClock != calculatedClock) { 485 TRACE("%s: pixel clock %" B_PRIu32 " was changed to %" B_PRIu32 "\n", 486 __func__, pll->adjustedClock, calculatedClock); 487 pll->pixelClock = calculatedClock; 488 } 489 490 return B_OK; 491 } 492 493 494 void 495 pll_setup_flags(pll_info* pll, uint8 crtcID) 496 { 497 radeon_shared_info &info = *gInfo->shared_info; 498 uint32 connectorIndex = gDisplay[crtcID]->connectorIndex; 499 uint32 connectorFlags = gConnector[connectorIndex]->flags; 500 501 uint32 dceVersion = (info.dceMajor * 100) + info.dceMinor; 502 503 TRACE("%s: CRTC: %" B_PRIu8 ", PLL: %" B_PRIu8 "\n", __func__, 504 crtcID, pll->id); 505 506 if (dceVersion >= 302 && pll->pixelClock > 200000) 507 pll->flags |= PLL_PREFER_HIGH_FB_DIV; 508 else 509 pll->flags |= PLL_PREFER_LOW_REF_DIV; 510 511 if (info.chipsetID < RADEON_RV770) 512 pll->flags |= PLL_PREFER_MINM_OVER_MAXP; 513 514 if ((connectorFlags & ATOM_DEVICE_LCD_SUPPORT) != 0) { 515 pll->flags |= PLL_IS_LCD; 516 517 // use reference divider for spread spectrum 518 TRACE("%s: Spread Spectrum is %" B_PRIu32 "%%\n", __func__, 519 pll->ssPercentage); 520 if (pll->ssPercentage > 0) { 521 if (pll->ssReferenceDiv > 0) { 522 TRACE("%s: using Spread Spectrum reference divider. " 523 "refDiv was: %" B_PRIu32 ", now: %" B_PRIu32 "\n", 524 __func__, pll->referenceDiv, pll->ssReferenceDiv); 525 pll->flags |= PLL_USE_REF_DIV; 526 pll->referenceDiv = pll->ssReferenceDiv; 527 528 // TODO: IS AVIVO+? 529 pll->flags |= PLL_USE_FRAC_FB_DIV; 530 } 531 } 532 } 533 534 if ((connectorFlags & ATOM_DEVICE_TV_SUPPORT) != 0) 535 pll->flags |= PLL_PREFER_CLOSEST_LOWER; 536 537 if ((info.chipsetFlags & CHIP_APU) != 0) { 538 // Use fractional feedback on APU's 539 pll->flags |= PLL_USE_FRAC_FB_DIV; 540 } 541 } 542 543 544 status_t 545 pll_adjust(pll_info* pll, display_mode* mode, uint8 crtcID) 546 { 547 radeon_shared_info &info = *gInfo->shared_info; 548 549 uint32 pixelClock = pll->pixelClock; 550 // original as pixel_clock will be adjusted 551 552 uint32 connectorIndex = gDisplay[crtcID]->connectorIndex; 553 connector_info* connector = gConnector[connectorIndex]; 554 555 uint32 encoderID = connector->encoder.objectID; 556 uint32 encoderMode = display_get_encoder_mode(connectorIndex); 557 uint32 connectorFlags = connector->flags; 558 559 uint32 externalEncoderID = 0; 560 pll->adjustedClock = pll->pixelClock; 561 if (connector->encoderExternal.isDPBridge) 562 externalEncoderID = connector->encoderExternal.objectID; 563 564 if (info.dceMajor >= 3) { 565 566 uint8 tableMajor; 567 uint8 tableMinor; 568 569 int index = GetIndexIntoMasterTable(COMMAND, AdjustDisplayPll); 570 if (atom_parse_cmd_header(gAtomContext, index, &tableMajor, &tableMinor) 571 != B_OK) { 572 ERROR("%s: Couldn't find AtomBIOS PLL adjustment\n", __func__); 573 return B_ERROR; 574 } 575 576 TRACE("%s: table %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, 577 tableMajor, tableMinor); 578 579 // Prepare arguments for AtomBIOS call 580 union adjustPixelClock { 581 ADJUST_DISPLAY_PLL_PS_ALLOCATION v1; 582 ADJUST_DISPLAY_PLL_PS_ALLOCATION_V3 v3; 583 }; 584 union adjustPixelClock args; 585 memset(&args, 0, sizeof(args)); 586 587 switch (tableMajor) { 588 case 1: 589 switch (tableMinor) { 590 case 1: 591 case 2: 592 args.v1.usPixelClock 593 = B_HOST_TO_LENDIAN_INT16(pixelClock / 10); 594 args.v1.ucTransmitterID = encoderID; 595 args.v1.ucEncodeMode = encoderMode; 596 if (pll->ssPercentage > 0) { 597 args.v1.ucConfig 598 |= ADJUST_DISPLAY_CONFIG_SS_ENABLE; 599 } 600 601 atom_execute_table(gAtomContext, index, (uint32*)&args); 602 // get returned adjusted clock 603 pll->adjustedClock 604 = B_LENDIAN_TO_HOST_INT16(args.v1.usPixelClock); 605 pll->adjustedClock *= 10; 606 break; 607 case 3: 608 args.v3.sInput.usPixelClock 609 = B_HOST_TO_LENDIAN_INT16(pixelClock / 10); 610 args.v3.sInput.ucTransmitterID = encoderID; 611 args.v3.sInput.ucEncodeMode = encoderMode; 612 args.v3.sInput.ucDispPllConfig = 0; 613 if (pll->ssPercentage > 0) { 614 args.v3.sInput.ucDispPllConfig 615 |= DISPPLL_CONFIG_SS_ENABLE; 616 } 617 618 // Handle DP adjustments 619 if (encoderMode == ATOM_ENCODER_MODE_DP 620 || encoderMode == ATOM_ENCODER_MODE_DP_MST) { 621 TRACE("%s: encoderMode is DP\n", __func__); 622 args.v3.sInput.ucDispPllConfig 623 |= DISPPLL_CONFIG_COHERENT_MODE; 624 /* 162000 or 270000 */ 625 uint32 dpLinkSpeed 626 = dp_get_link_rate(connectorIndex, mode); 627 /* 16200 or 27000 */ 628 args.v3.sInput.usPixelClock 629 = B_HOST_TO_LENDIAN_INT16(dpLinkSpeed / 10); 630 } else if ((connectorFlags & ATOM_DEVICE_DFP_SUPPORT) 631 != 0) { 632 #if 0 633 if (encoderMode == ATOM_ENCODER_MODE_HDMI) { 634 /* deep color support */ 635 args.v3.sInput.usPixelClock = 636 cpu_to_le16((mode->clock * bpc / 8) / 10); 637 } 638 #endif 639 if (pixelClock > 165000) { 640 args.v3.sInput.ucDispPllConfig 641 |= DISPPLL_CONFIG_DUAL_LINK; 642 } 643 if (1) { // dig coherent mode? 644 args.v3.sInput.ucDispPllConfig 645 |= DISPPLL_CONFIG_COHERENT_MODE; 646 } 647 } 648 649 args.v3.sInput.ucExtTransmitterID = externalEncoderID; 650 651 atom_execute_table(gAtomContext, index, (uint32*)&args); 652 653 // get returned adjusted clock 654 pll->adjustedClock = B_LENDIAN_TO_HOST_INT32( 655 args.v3.sOutput.ulDispPllFreq); 656 pll->adjustedClock *= 10; 657 // convert to kHz for storage 658 659 if (args.v3.sOutput.ucRefDiv) { 660 pll->flags |= PLL_USE_FRAC_FB_DIV; 661 pll->flags |= PLL_USE_REF_DIV; 662 pll->referenceDiv = args.v3.sOutput.ucRefDiv; 663 } 664 if (args.v3.sOutput.ucPostDiv) { 665 pll->flags |= PLL_USE_FRAC_FB_DIV; 666 pll->flags |= PLL_USE_POST_DIV; 667 pll->postDiv = args.v3.sOutput.ucPostDiv; 668 } 669 break; 670 default: 671 TRACE("%s: ERROR: table version %" B_PRIu8 ".%" B_PRIu8 672 " unknown\n", __func__, tableMajor, tableMinor); 673 return B_ERROR; 674 } 675 break; 676 default: 677 TRACE("%s: ERROR: table version %" B_PRIu8 ".%" B_PRIu8 678 " unknown\n", __func__, tableMajor, tableMinor); 679 return B_ERROR; 680 } 681 } 682 683 TRACE("%s: was: %" B_PRIu32 ", now: %" B_PRIu32 "\n", __func__, 684 pixelClock, pll->adjustedClock); 685 686 return B_OK; 687 } 688 689 690 status_t 691 pll_set(display_mode* mode, uint8 crtcID) 692 { 693 uint32 connectorIndex = gDisplay[crtcID]->connectorIndex; 694 pll_info* pll = &gConnector[connectorIndex]->encoder.pll; 695 uint32 dp_clock = gConnector[connectorIndex]->dpInfo.linkRate; 696 bool ssEnabled = false; 697 698 pll->pixelClock = mode->timing.pixel_clock; 699 700 radeon_shared_info &info = *gInfo->shared_info; 701 702 // Probe for PLL spread spectrum info; 703 pll->ssPercentage = 0; 704 pll->ssType = 0; 705 pll->ssStep = 0; 706 pll->ssDelay = 0; 707 pll->ssRange = 0; 708 pll->ssReferenceDiv = 0; 709 710 switch (display_get_encoder_mode(connectorIndex)) { 711 case ATOM_ENCODER_MODE_DP_MST: 712 case ATOM_ENCODER_MODE_DP: 713 if (info.dceMajor >= 4) 714 pll_asic_ss_probe(pll, ASIC_INTERNAL_SS_ON_DP); 715 else { 716 if (dp_clock == 162000) { 717 ssEnabled = pll_ppll_ss_probe(pll, ATOM_DP_SS_ID2); 718 if (!ssEnabled) 719 // id2 failed, try id1 720 ssEnabled = pll_ppll_ss_probe(pll, ATOM_DP_SS_ID1); 721 } else 722 ssEnabled = pll_ppll_ss_probe(pll, ATOM_DP_SS_ID1); 723 } 724 break; 725 case ATOM_ENCODER_MODE_LVDS: 726 if (info.dceMajor >= 4) 727 ssEnabled = pll_asic_ss_probe(pll, gInfo->lvdsSpreadSpectrumID); 728 else 729 ssEnabled = pll_ppll_ss_probe(pll, gInfo->lvdsSpreadSpectrumID); 730 break; 731 case ATOM_ENCODER_MODE_DVI: 732 if (info.dceMajor >= 4) 733 ssEnabled = pll_asic_ss_probe(pll, ASIC_INTERNAL_SS_ON_TMDS); 734 break; 735 case ATOM_ENCODER_MODE_HDMI: 736 if (info.dceMajor >= 4) 737 ssEnabled = pll_asic_ss_probe(pll, ASIC_INTERNAL_SS_ON_HDMI); 738 break; 739 } 740 741 pll_setup_flags(pll, crtcID); 742 // set up any special flags 743 pll_adjust(pll, mode, crtcID); 744 // get any needed clock adjustments, set reference/post dividers 745 pll_compute(pll); 746 // compute dividers 747 748 display_crtc_ss(pll, ATOM_DISABLE); 749 // disable ss 750 751 uint8 tableMajor; 752 uint8 tableMinor; 753 754 int index = GetIndexIntoMasterTable(COMMAND, SetPixelClock); 755 atom_parse_cmd_header(gAtomContext, index, &tableMajor, &tableMinor); 756 757 TRACE("%s: table %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, 758 tableMajor, tableMinor); 759 760 uint32 bitsPerColor = 8; 761 // TODO: Digital Depth, EDID 1.4+ on digital displays 762 // isn't in Haiku edid common code? 763 764 // Prepare arguments for AtomBIOS call 765 union setPixelClock { 766 SET_PIXEL_CLOCK_PS_ALLOCATION base; 767 PIXEL_CLOCK_PARAMETERS v1; 768 PIXEL_CLOCK_PARAMETERS_V2 v2; 769 PIXEL_CLOCK_PARAMETERS_V3 v3; 770 PIXEL_CLOCK_PARAMETERS_V5 v5; 771 PIXEL_CLOCK_PARAMETERS_V6 v6; 772 }; 773 union setPixelClock args; 774 memset(&args, 0, sizeof(args)); 775 776 switch (tableMinor) { 777 case 1: 778 args.v1.usPixelClock 779 = B_HOST_TO_LENDIAN_INT16(pll->pixelClock / 10); 780 args.v1.usRefDiv = B_HOST_TO_LENDIAN_INT16(pll->referenceDiv); 781 args.v1.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); 782 args.v1.ucFracFbDiv = pll->feedbackDivFrac; 783 args.v1.ucPostDiv = pll->postDiv; 784 args.v1.ucPpll = pll->id; 785 args.v1.ucCRTC = crtcID; 786 args.v1.ucRefDivSrc = 1; 787 break; 788 case 2: 789 args.v2.usPixelClock 790 = B_HOST_TO_LENDIAN_INT16(pll->pixelClock / 10); 791 args.v2.usRefDiv = B_HOST_TO_LENDIAN_INT16(pll->referenceDiv); 792 args.v2.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); 793 args.v2.ucFracFbDiv = pll->feedbackDivFrac; 794 args.v2.ucPostDiv = pll->postDiv; 795 args.v2.ucPpll = pll->id; 796 args.v2.ucCRTC = crtcID; 797 args.v2.ucRefDivSrc = 1; 798 break; 799 case 3: 800 args.v3.usPixelClock 801 = B_HOST_TO_LENDIAN_INT16(pll->pixelClock / 10); 802 args.v3.usRefDiv = B_HOST_TO_LENDIAN_INT16(pll->referenceDiv); 803 args.v3.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); 804 args.v3.ucFracFbDiv = pll->feedbackDivFrac; 805 args.v3.ucPostDiv = pll->postDiv; 806 args.v3.ucPpll = pll->id; 807 args.v3.ucMiscInfo = (pll->id << 2); 808 if (pll->ssPercentage > 0 809 && (pll->ssType & ATOM_EXTERNAL_SS_MASK) != 0) { 810 args.v3.ucMiscInfo |= PIXEL_CLOCK_MISC_REF_DIV_SRC; 811 } 812 args.v3.ucTransmitterId 813 = gConnector[connectorIndex]->encoder.objectID; 814 args.v3.ucEncoderMode = display_get_encoder_mode(connectorIndex); 815 break; 816 case 5: 817 args.v5.ucCRTC = crtcID; 818 args.v5.usPixelClock 819 = B_HOST_TO_LENDIAN_INT16(pll->pixelClock / 10); 820 args.v5.ucRefDiv = pll->referenceDiv; 821 args.v5.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); 822 args.v5.ulFbDivDecFrac 823 = B_HOST_TO_LENDIAN_INT32(pll->feedbackDivFrac * 100000); 824 args.v5.ucPostDiv = pll->postDiv; 825 args.v5.ucMiscInfo = 0; /* HDMI depth, etc. */ 826 if (pll->ssPercentage > 0 827 && (pll->ssType & ATOM_EXTERNAL_SS_MASK) != 0) { 828 args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_REF_DIV_SRC; 829 } 830 switch (bitsPerColor) { 831 case 8: 832 default: 833 args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_HDMI_24BPP; 834 break; 835 case 10: 836 args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_HDMI_30BPP; 837 break; 838 } 839 args.v5.ucTransmitterID 840 = gConnector[connectorIndex]->encoder.objectID; 841 args.v5.ucEncoderMode 842 = display_get_encoder_mode(connectorIndex); 843 args.v5.ucPpll = pll->id; 844 break; 845 case 6: 846 args.v6.ulDispEngClkFreq 847 = B_HOST_TO_LENDIAN_INT32(crtcID << 24 | pll->pixelClock / 10); 848 args.v6.ucRefDiv = pll->referenceDiv; 849 args.v6.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); 850 args.v6.ulFbDivDecFrac 851 = B_HOST_TO_LENDIAN_INT32(pll->feedbackDivFrac * 100000); 852 args.v6.ucPostDiv = pll->postDiv; 853 args.v6.ucMiscInfo = 0; /* HDMI depth, etc. */ 854 if (pll->ssPercentage > 0 855 && (pll->ssType & ATOM_EXTERNAL_SS_MASK) != 0) { 856 args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_REF_DIV_SRC; 857 } 858 switch (bitsPerColor) { 859 case 8: 860 default: 861 args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_24BPP; 862 break; 863 case 10: 864 args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_30BPP; 865 break; 866 case 12: 867 args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_36BPP; 868 break; 869 case 16: 870 args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_48BPP; 871 break; 872 } 873 args.v6.ucTransmitterID 874 = gConnector[connectorIndex]->encoder.objectID; 875 args.v6.ucEncoderMode = display_get_encoder_mode(connectorIndex); 876 args.v6.ucPpll = pll->id; 877 break; 878 default: 879 TRACE("%s: ERROR: table version %" B_PRIu8 ".%" B_PRIu8 " TODO\n", 880 __func__, tableMajor, tableMinor); 881 return B_ERROR; 882 } 883 884 TRACE("%s: set adjusted pixel clock %" B_PRIu32 " (was %" B_PRIu32 ")\n", 885 __func__, pll->pixelClock, mode->timing.pixel_clock); 886 887 status_t result = atom_execute_table(gAtomContext, index, (uint32*)&args); 888 889 if (ssEnabled) 890 display_crtc_ss(pll, ATOM_ENABLE); 891 892 return result; 893 } 894 895 896 status_t 897 pll_external_set(uint32 clock) 898 { 899 TRACE("%s: set external pll clock to %" B_PRIu32 "\n", __func__, clock); 900 901 if (clock == 0) 902 ERROR("%s: Warning: default display clock is 0?\n", __func__); 903 904 // also known as PLL display engineering 905 uint8 tableMajor; 906 uint8 tableMinor; 907 908 int index = GetIndexIntoMasterTable(COMMAND, SetPixelClock); 909 atom_parse_cmd_header(gAtomContext, index, &tableMajor, &tableMinor); 910 911 TRACE("%s: table %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, 912 tableMajor, tableMinor); 913 914 union setPixelClock { 915 SET_PIXEL_CLOCK_PS_ALLOCATION base; 916 PIXEL_CLOCK_PARAMETERS v1; 917 PIXEL_CLOCK_PARAMETERS_V2 v2; 918 PIXEL_CLOCK_PARAMETERS_V3 v3; 919 PIXEL_CLOCK_PARAMETERS_V5 v5; 920 PIXEL_CLOCK_PARAMETERS_V6 v6; 921 }; 922 union setPixelClock args; 923 memset(&args, 0, sizeof(args)); 924 925 radeon_shared_info &info = *gInfo->shared_info; 926 uint32 dceVersion = (info.dceMajor * 100) + info.dceMinor; 927 switch (tableMajor) { 928 case 1: 929 switch(tableMinor) { 930 case 5: 931 // If the default DC PLL clock is specified, 932 // SetPixelClock provides the dividers. 933 args.v5.ucCRTC = ATOM_CRTC_INVALID; 934 args.v5.usPixelClock = B_HOST_TO_LENDIAN_INT16(clock / 10); 935 args.v5.ucPpll = ATOM_DCPLL; 936 break; 937 case 6: 938 // If the default DC PLL clock is specified, 939 // SetPixelClock provides the dividers. 940 args.v6.ulDispEngClkFreq 941 = B_HOST_TO_LENDIAN_INT32(clock / 10); 942 if (dceVersion == 601) 943 args.v6.ucPpll = ATOM_EXT_PLL1; 944 else if (dceVersion >= 600) 945 args.v6.ucPpll = ATOM_PPLL0; 946 else 947 args.v6.ucPpll = ATOM_DCPLL; 948 break; 949 default: 950 ERROR("%s: Unknown table version %" B_PRIu8 951 ".%" B_PRIu8 "\n", __func__, tableMajor, tableMinor); 952 } 953 break; 954 default: 955 ERROR("%s: Unknown table version %" B_PRIu8 956 ".%" B_PRIu8 "\n", __func__, tableMajor, tableMinor); 957 } 958 return B_OK; 959 } 960 961 962 void 963 pll_external_init() 964 { 965 radeon_shared_info &info = *gInfo->shared_info; 966 967 if (info.dceMajor >= 6) { 968 pll_external_set(gInfo->displayClockFrequency); 969 } else if (info.dceMajor >= 4) { 970 // Create our own pseudo pll 971 pll_info pll; 972 bool ssPresent = pll_asic_ss_probe(&pll, ASIC_INTERNAL_SS_ON_DCPLL) 973 == B_OK ? true : false; 974 if (ssPresent) 975 display_crtc_ss(&pll, ATOM_DISABLE); 976 pll_external_set(gInfo->displayClockFrequency); 977 if (ssPresent) 978 display_crtc_ss(&pll, ATOM_ENABLE); 979 } 980 } 981 982 983 status_t 984 pll_pick(uint32 connectorIndex) 985 { 986 pll_info* pll = &gConnector[connectorIndex]->encoder.pll; 987 radeon_shared_info &info = *gInfo->shared_info; 988 989 bool linkB = gConnector[connectorIndex]->encoder.linkEnumeration 990 == GRAPH_OBJECT_ENUM_ID2 ? true : false; 991 992 if (info.dceMajor == 6 && info.dceMinor == 1) { 993 // DCE 6.1 APU 994 if (gConnector[connectorIndex]->encoder.objectID 995 == ENCODER_OBJECT_ID_INTERNAL_UNIPHY && !linkB) { 996 pll->id = ATOM_PPLL2; 997 return B_OK; 998 } 999 // TODO: check for used PLL1 and use PLL2? 1000 pll->id = ATOM_PPLL1; 1001 return B_OK; 1002 } else if (info.dceMajor >= 4) { 1003 if (connector_is_dp(connectorIndex)) { 1004 if (gInfo->dpExternalClock) { 1005 pll->id = ATOM_PPLL_INVALID; 1006 return B_OK; 1007 } else if (info.dceMajor >= 6) { 1008 pll->id = ATOM_PPLL1; 1009 return B_OK; 1010 } else if (info.dceMajor >= 5) { 1011 pll->id = ATOM_DCPLL; 1012 return B_OK; 1013 } 1014 } 1015 pll->id = ATOM_PPLL1; 1016 return B_OK; 1017 } 1018 1019 // TODO: Should return the CRTCID here. 1020 pll->id = ATOM_PPLL1; 1021 return B_OK; 1022 } 1023