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 "accelerant_protos.h" 11 #include "accelerant.h" 12 #include "bios.h" 13 #include "display.h" 14 #include "utility.h" 15 #include "pll.h" 16 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <string.h> 20 #include <math.h> 21 22 23 #define TRACE_PLL 24 #ifdef TRACE_PLL 25 extern "C" void _sPrintf(const char *format, ...); 26 # define TRACE(x...) _sPrintf("radeon_hd: " x) 27 #else 28 # define TRACE(x...) ; 29 #endif 30 31 #define ERROR(x...) _sPrintf("radeon_hd: " x) 32 33 34 union firmware_info { 35 ATOM_FIRMWARE_INFO info; 36 ATOM_FIRMWARE_INFO_V1_2 info_12; 37 ATOM_FIRMWARE_INFO_V1_3 info_13; 38 ATOM_FIRMWARE_INFO_V1_4 info_14; 39 ATOM_FIRMWARE_INFO_V2_1 info_21; 40 ATOM_FIRMWARE_INFO_V2_2 info_22; 41 }; 42 43 44 status_t 45 pll_limit_probe(pll_info *pll) 46 { 47 int index = GetIndexIntoMasterTable(DATA, FirmwareInfo); 48 uint8 tableMajor; 49 uint8 tableMinor; 50 uint16 tableOffset; 51 52 if (atom_parse_data_header(gAtomContext, index, NULL, 53 &tableMajor, &tableMinor, &tableOffset) != B_OK) { 54 ERROR("%s: Couldn't parse data header\n", __func__); 55 return B_ERROR; 56 } 57 58 union firmware_info *firmwareInfo 59 = (union firmware_info *)(gAtomContext->bios + tableOffset); 60 61 /* pixel clock limits */ 62 pll->referenceFreq 63 = B_LENDIAN_TO_HOST_INT16(firmwareInfo->info.usReferenceClock) * 10; 64 65 if (tableMinor < 2) { 66 pll->pllOutMin 67 = B_LENDIAN_TO_HOST_INT16( 68 firmwareInfo->info.usMinPixelClockPLL_Output) * 10; 69 } else { 70 pll->pllOutMin 71 = B_LENDIAN_TO_HOST_INT32( 72 firmwareInfo->info_12.ulMinPixelClockPLL_Output) * 10; 73 } 74 75 pll->pllOutMax 76 = B_LENDIAN_TO_HOST_INT32( 77 firmwareInfo->info.ulMaxPixelClockPLL_Output) * 10; 78 79 if (tableMinor >= 4) { 80 pll->lcdPllOutMin 81 = B_LENDIAN_TO_HOST_INT16( 82 firmwareInfo->info_14.usLcdMinPixelClockPLL_Output) * 1000; 83 84 if (pll->lcdPllOutMin == 0) 85 pll->lcdPllOutMin = pll->pllOutMin; 86 87 pll->lcdPllOutMax 88 = B_LENDIAN_TO_HOST_INT16( 89 firmwareInfo->info_14.usLcdMaxPixelClockPLL_Output) * 1000; 90 91 if (pll->lcdPllOutMax == 0) 92 pll->lcdPllOutMax = pll->pllOutMax; 93 94 } else { 95 pll->lcdPllOutMin = pll->pllOutMin; 96 pll->lcdPllOutMax = pll->pllOutMax; 97 } 98 99 if (pll->pllOutMin == 0) { 100 pll->pllOutMin = 64800 * 10; 101 // Avivo+ limit 102 } 103 104 pll->minPostDiv = POST_DIV_MIN; 105 pll->maxPostDiv = POST_DIV_LIMIT; 106 pll->minRefDiv = REF_DIV_MIN; 107 pll->maxRefDiv = REF_DIV_LIMIT; 108 pll->minFeedbackDiv = FB_DIV_MIN; 109 pll->maxFeedbackDiv = FB_DIV_LIMIT; 110 111 pll->pllInMin = B_LENDIAN_TO_HOST_INT16( 112 firmwareInfo->info.usMinPixelClockPLL_Input) * 10; 113 pll->pllInMax = B_LENDIAN_TO_HOST_INT16( 114 firmwareInfo->info.usMaxPixelClockPLL_Input) * 10; 115 116 TRACE("%s: referenceFreq: %" B_PRIu16 "; pllOutMin: %" B_PRIu16 "; " 117 " pllOutMax: %" B_PRIu16 "; pllInMin: %" B_PRIu16 ";" 118 "pllInMax: %" B_PRIu16 "\n", __func__, pll->referenceFreq, 119 pll->pllOutMin, pll->pllOutMax, pll->pllInMin, pll->pllInMax); 120 121 return B_OK; 122 } 123 124 125 void 126 pll_compute_post_divider(pll_info *pll) 127 { 128 if ((pll->flags & PLL_USE_POST_DIV) != 0) { 129 TRACE("%s: using AtomBIOS post divider\n", __func__); 130 return; 131 } 132 133 uint32 vco; 134 if ((pll->flags & PLL_PREFER_MINM_OVER_MAXP) != 0) { 135 if ((pll->flags & PLL_IS_LCD) != 0) 136 vco = pll->lcdPllOutMin; 137 else 138 vco = pll->pllOutMin; 139 } else { 140 if ((pll->flags & PLL_IS_LCD) != 0) 141 vco = pll->lcdPllOutMax; 142 else 143 vco = pll->pllOutMin; 144 } 145 146 TRACE("%s: vco = %" B_PRIu32 "\n", __func__, vco); 147 148 uint32 postDivider = vco / pll->pixelClock; 149 uint32 tmp = vco % pll->pixelClock; 150 151 if ((pll->flags & PLL_PREFER_MINM_OVER_MAXP) != 0) { 152 if (tmp) 153 postDivider++; 154 } else { 155 if (!tmp) 156 postDivider--; 157 } 158 159 if (postDivider > pll->maxPostDiv) 160 postDivider = pll->maxPostDiv; 161 else if (postDivider < pll->minPostDiv) 162 postDivider = pll->minPostDiv; 163 164 pll->postDiv = postDivider; 165 TRACE("%s: postDiv = %" B_PRIu32 "\n", __func__, postDivider); 166 } 167 168 169 status_t 170 pll_compute(pll_info *pll) 171 { 172 pll_compute_post_divider(pll); 173 174 uint32 targetClock = pll->pixelClock; 175 176 pll->feedbackDiv = 0; 177 pll->feedbackDivFrac = 0; 178 uint32 referenceFrequency = pll->referenceFreq; 179 180 if ((pll->flags & PLL_USE_REF_DIV) != 0) { 181 TRACE("%s: using AtomBIOS reference divider\n", __func__); 182 return B_OK; 183 } else { 184 pll->referenceDiv = pll->minRefDiv; 185 } 186 187 if ((pll->flags & PLL_USE_FRAC_FB_DIV) != 0) { 188 TRACE("%s: using AtomBIOS fractional feedback divider\n", __func__); 189 190 uint32 tmp = pll->postDiv * pll->referenceDiv; 191 tmp *= targetClock; 192 pll->feedbackDiv = tmp / pll->referenceFreq; 193 pll->feedbackDivFrac = tmp % pll->referenceFreq; 194 195 if (pll->feedbackDiv > pll->maxFeedbackDiv) 196 pll->feedbackDiv = pll->maxFeedbackDiv; 197 else if (pll->feedbackDiv < pll->minFeedbackDiv) 198 pll->feedbackDiv = pll->minFeedbackDiv; 199 200 pll->feedbackDivFrac 201 = (100 * pll->feedbackDivFrac) / pll->referenceFreq; 202 203 if (pll->feedbackDivFrac >= 5) { 204 pll->feedbackDivFrac -= 5; 205 pll->feedbackDivFrac /= 10; 206 pll->feedbackDivFrac++; 207 } 208 if (pll->feedbackDivFrac >= 10) { 209 pll->feedbackDiv++; 210 pll->feedbackDivFrac = 0; 211 } 212 } else { 213 TRACE("%s: performing fractional feedback calculations\n", __func__); 214 215 while (pll->referenceDiv <= pll->maxRefDiv) { 216 // get feedback divider 217 uint32 retroEncabulator = pll->postDiv * pll->referenceDiv; 218 219 retroEncabulator *= targetClock; 220 pll->feedbackDiv = retroEncabulator / referenceFrequency; 221 pll->feedbackDivFrac 222 = retroEncabulator % referenceFrequency; 223 224 if (pll->feedbackDiv > pll->maxFeedbackDiv) 225 pll->feedbackDiv = pll->maxFeedbackDiv; 226 else if (pll->feedbackDiv < pll->minFeedbackDiv) 227 pll->feedbackDiv = pll->minFeedbackDiv; 228 229 if (pll->feedbackDivFrac >= (referenceFrequency / 2)) 230 pll->feedbackDiv++; 231 232 pll->feedbackDivFrac = 0; 233 234 if (pll->referenceDiv == 0 235 || pll->postDiv == 0 236 || targetClock == 0) { 237 TRACE("%s: Caught division by zero!\n", __func__); 238 TRACE("%s: referenceDiv %" B_PRIu32 "\n", 239 __func__, pll->referenceDiv); 240 TRACE("%s: postDiv %" B_PRIu32 "\n", 241 __func__, pll->postDiv); 242 TRACE("%s: targetClock %" B_PRIu32 "\n", 243 __func__, targetClock); 244 return B_ERROR; 245 } 246 uint32 tmp = (referenceFrequency * pll->feedbackDiv) 247 / (pll->postDiv * pll->referenceDiv); 248 tmp = (tmp * 1000) / targetClock; 249 250 if (tmp > (1000 + (MAX_TOLERANCE / 10))) 251 pll->referenceDiv++; 252 else if (tmp >= (1000 - (MAX_TOLERANCE / 10))) 253 break; 254 else 255 pll->referenceDiv++; 256 } 257 } 258 259 if (pll->referenceDiv == 0 || pll->postDiv == 0) { 260 TRACE("%s: Caught division by zero of post or reference divider\n", 261 __func__); 262 return B_ERROR; 263 } 264 265 uint32 calculatedClock 266 = ((referenceFrequency * pll->feedbackDiv * 10) 267 + (referenceFrequency * pll->feedbackDivFrac)) 268 / (pll->referenceDiv * pll->postDiv * 10); 269 270 TRACE("%s: pixel clock: %" B_PRIu32 " gives:" 271 " feedbackDivider = %" B_PRIu32 ".%" B_PRIu32 272 "; referenceDivider = %" B_PRIu32 "; postDivider = %" B_PRIu32 "\n", 273 __func__, pll->pixelClock, pll->feedbackDiv, pll->feedbackDivFrac, 274 pll->referenceDiv, pll->postDiv); 275 276 if (pll->pixelClock != calculatedClock) { 277 TRACE("%s: pixel clock %" B_PRIu32 " was changed to %" B_PRIu32 "\n", 278 __func__, pll->pixelClock, calculatedClock); 279 pll->pixelClock = calculatedClock; 280 } 281 282 return B_OK; 283 } 284 285 286 union adjust_pixel_clock { 287 ADJUST_DISPLAY_PLL_PS_ALLOCATION v1; 288 ADJUST_DISPLAY_PLL_PS_ALLOCATION_V3 v3; 289 }; 290 291 292 void 293 pll_setup_flags(pll_info *pll, uint8 crtcID) 294 { 295 radeon_shared_info &info = *gInfo->shared_info; 296 uint32 connectorIndex = gDisplay[crtcID]->connectorIndex; 297 uint32 encoderFlags = gConnector[connectorIndex]->encoder.flags; 298 299 if ((info.dceMajor >= 3 && info.dceMinor >= 2) 300 && pll->pixelClock > 200000) { 301 pll->flags |= PLL_PREFER_HIGH_FB_DIV; 302 } else 303 pll->flags |= PLL_PREFER_LOW_REF_DIV; 304 305 306 if (info.device_chipset < RADEON_R700) 307 pll->flags |= PLL_PREFER_MINM_OVER_MAXP; 308 309 310 if ((encoderFlags & ATOM_DEVICE_LCD_SUPPORT) != 0) { 311 pll->flags |= PLL_IS_LCD; 312 313 // TODO: Spread Spectrum PLL 314 // use reference divider for spread spectrum 315 if (0) { // SS enabled 316 if (0) { // if we have a SS reference divider 317 pll->flags |= PLL_USE_REF_DIV; 318 //pll->reference_div = ss->refdiv; 319 pll->flags |= PLL_USE_FRAC_FB_DIV; 320 } 321 } 322 } 323 324 if ((encoderFlags & ATOM_DEVICE_TV_SUPPORT) != 0) 325 pll->flags |= PLL_PREFER_CLOSEST_LOWER; 326 } 327 328 329 status_t 330 pll_adjust(pll_info *pll, uint8 crtcID) 331 { 332 // TODO: PLL flags 333 radeon_shared_info &info = *gInfo->shared_info; 334 335 uint32 pixelClock = pll->pixelClock; 336 // original as pixel_clock will be adjusted 337 338 uint32 connectorIndex = gDisplay[crtcID]->connectorIndex; 339 uint32 encoderID = gConnector[connectorIndex]->encoder.objectID; 340 uint32 encoderMode = display_get_encoder_mode(connectorIndex); 341 342 if (info.device_chipset >= (RADEON_R600 | 0x20)) { 343 union adjust_pixel_clock args; 344 345 uint8 tableMajor; 346 uint8 tableMinor; 347 348 int index = GetIndexIntoMasterTable(COMMAND, AdjustDisplayPll); 349 350 if (atom_parse_cmd_header(gAtomContext, index, &tableMajor, &tableMinor) 351 != B_OK) { 352 return B_ERROR; 353 } 354 355 memset(&args, 0, sizeof(args)); 356 switch (tableMajor) { 357 case 1: 358 switch (tableMinor) { 359 case 1: 360 case 2: 361 args.v1.usPixelClock 362 = B_HOST_TO_LENDIAN_INT16(pixelClock / 10); 363 args.v1.ucTransmitterID = encoderID; 364 args.v1.ucEncodeMode = encoderMode; 365 // TODO: SS and SS % > 0 366 if (0) { 367 args.v1.ucConfig 368 |= ADJUST_DISPLAY_CONFIG_SS_ENABLE; 369 } 370 371 atom_execute_table(gAtomContext, index, (uint32*)&args); 372 // get returned adjusted clock 373 pll->pixelClock 374 = B_LENDIAN_TO_HOST_INT16(args.v1.usPixelClock); 375 pll->pixelClock *= 10; 376 break; 377 case 3: 378 args.v3.sInput.usPixelClock 379 = B_HOST_TO_LENDIAN_INT16(pixelClock / 10); 380 args.v3.sInput.ucTransmitterID = encoderID; 381 args.v3.sInput.ucEncodeMode = encoderMode; 382 args.v3.sInput.ucDispPllConfig = 0; 383 // TODO: SS and SS % > 0 384 if (0) { 385 args.v3.sInput.ucDispPllConfig 386 |= DISPPLL_CONFIG_SS_ENABLE; 387 } 388 // TODO: if ATOM_DEVICE_DFP_SUPPORT 389 // TODO: display port DP 390 391 // TODO: is DP? 392 args.v3.sInput.ucExtTransmitterID = 0; 393 394 atom_execute_table(gAtomContext, index, (uint32*)&args); 395 // get returned adjusted clock 396 pll->pixelClock 397 = B_LENDIAN_TO_HOST_INT32( 398 args.v3.sOutput.ulDispPllFreq); 399 pll->pixelClock *= 10; 400 // convert to kHz for storage 401 402 if (args.v3.sOutput.ucRefDiv) { 403 pll->flags |= PLL_USE_FRAC_FB_DIV; 404 pll->flags |= PLL_USE_REF_DIV; 405 pll->referenceDiv = args.v3.sOutput.ucRefDiv; 406 } 407 if (args.v3.sOutput.ucPostDiv) { 408 pll->flags |= PLL_USE_FRAC_FB_DIV; 409 pll->flags |= PLL_USE_POST_DIV; 410 pll->postDiv = args.v3.sOutput.ucPostDiv; 411 } 412 break; 413 default: 414 TRACE("%s: ERROR: table version %" B_PRIu8 ".%" B_PRIu8 415 " unknown\n", __func__, tableMajor, tableMinor); 416 return B_ERROR; 417 } 418 break; 419 default: 420 TRACE("%s: ERROR: table version %" B_PRIu8 ".%" B_PRIu8 421 " unknown\n", __func__, tableMajor, tableMinor); 422 return B_ERROR; 423 } 424 } 425 426 TRACE("%s: was: %" B_PRIu32 ", now: %" B_PRIu32 "\n", __func__, 427 pixelClock, pll->pixelClock); 428 429 return B_OK; 430 } 431 432 433 union set_pixel_clock { 434 SET_PIXEL_CLOCK_PS_ALLOCATION base; 435 PIXEL_CLOCK_PARAMETERS v1; 436 PIXEL_CLOCK_PARAMETERS_V2 v2; 437 PIXEL_CLOCK_PARAMETERS_V3 v3; 438 PIXEL_CLOCK_PARAMETERS_V5 v5; 439 PIXEL_CLOCK_PARAMETERS_V6 v6; 440 }; 441 442 443 status_t 444 pll_set(uint8 pllID, uint32 pixelClock, uint8 crtcID) 445 { 446 uint32 connectorIndex = gDisplay[crtcID]->connectorIndex; 447 pll_info *pll = &gConnector[connectorIndex]->encoder.pll; 448 449 pll->pixelClock = pixelClock; 450 pll->id = pllID; 451 452 pll_setup_flags(pll, crtcID); 453 // set up any special flags 454 pll_adjust(pll, crtcID); 455 // get any needed clock adjustments, set reference/post dividers 456 pll_compute(pll); 457 // compute dividers 458 459 int index = GetIndexIntoMasterTable(COMMAND, SetPixelClock); 460 union set_pixel_clock args; 461 memset(&args, 0, sizeof(args)); 462 463 uint8 tableMajor; 464 uint8 tableMinor; 465 466 atom_parse_cmd_header(gAtomContext, index, &tableMajor, &tableMinor); 467 468 uint32 bitsPerChannel = 8; 469 // TODO: Digital Depth, EDID 1.4+ on digital displays 470 // isn't in Haiku edid common code? 471 472 switch (tableMinor) { 473 case 1: 474 args.v1.usPixelClock 475 = B_HOST_TO_LENDIAN_INT16(pll->pixelClock / 10); 476 args.v1.usRefDiv = B_HOST_TO_LENDIAN_INT16(pll->referenceDiv); 477 args.v1.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); 478 args.v1.ucFracFbDiv = pll->feedbackDivFrac; 479 args.v1.ucPostDiv = pll->postDiv; 480 args.v1.ucPpll = pll->id; 481 args.v1.ucCRTC = crtcID; 482 args.v1.ucRefDivSrc = 1; 483 break; 484 case 2: 485 args.v2.usPixelClock 486 = B_HOST_TO_LENDIAN_INT16(pll->pixelClock / 10); 487 args.v2.usRefDiv = B_HOST_TO_LENDIAN_INT16(pll->referenceDiv); 488 args.v2.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); 489 args.v2.ucFracFbDiv = pll->feedbackDivFrac; 490 args.v2.ucPostDiv = pll->postDiv; 491 args.v2.ucPpll = pll->id; 492 args.v2.ucCRTC = crtcID; 493 args.v2.ucRefDivSrc = 1; 494 break; 495 case 3: 496 args.v3.usPixelClock 497 = B_HOST_TO_LENDIAN_INT16(pll->pixelClock / 10); 498 args.v3.usRefDiv = B_HOST_TO_LENDIAN_INT16(pll->referenceDiv); 499 args.v3.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); 500 args.v3.ucFracFbDiv = pll->feedbackDivFrac; 501 args.v3.ucPostDiv = pll->postDiv; 502 args.v3.ucPpll = pll->id; 503 args.v3.ucMiscInfo = (pll->id << 2); 504 // if (ss_enabled && (ss->type & ATOM_EXTERNAL_SS_MASK)) 505 // args.v3.ucMiscInfo |= PIXEL_CLOCK_MISC_REF_DIV_SRC; 506 args.v3.ucTransmitterId 507 = gConnector[connectorIndex]->encoder.objectID; 508 args.v3.ucEncoderMode = display_get_encoder_mode(connectorIndex); 509 break; 510 case 5: 511 args.v5.ucCRTC = crtcID; 512 args.v5.usPixelClock 513 = B_HOST_TO_LENDIAN_INT16(pll->pixelClock / 10); 514 args.v5.ucRefDiv = pll->referenceDiv; 515 args.v5.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); 516 args.v5.ulFbDivDecFrac 517 = B_HOST_TO_LENDIAN_INT32(pll->feedbackDivFrac * 100000); 518 args.v5.ucPostDiv = pll->postDiv; 519 args.v5.ucMiscInfo = 0; /* HDMI depth, etc. */ 520 // if (ss_enabled && (ss->type & ATOM_EXTERNAL_SS_MASK)) 521 // args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_REF_DIV_SRC; 522 switch (bitsPerChannel) { 523 case 8: 524 default: 525 args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_HDMI_24BPP; 526 break; 527 case 10: 528 args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_HDMI_30BPP; 529 break; 530 } 531 args.v5.ucTransmitterID 532 = gConnector[connectorIndex]->encoder.objectID; 533 args.v5.ucEncoderMode 534 = display_get_encoder_mode(connectorIndex); 535 args.v5.ucPpll = pllID; 536 break; 537 case 6: 538 args.v6.ulDispEngClkFreq 539 = B_HOST_TO_LENDIAN_INT32(crtcID << 24 | pll->pixelClock / 10); 540 args.v6.ucRefDiv = pll->referenceDiv; 541 args.v6.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); 542 args.v6.ulFbDivDecFrac 543 = B_HOST_TO_LENDIAN_INT32(pll->feedbackDivFrac * 100000); 544 args.v6.ucPostDiv = pll->postDiv; 545 args.v6.ucMiscInfo = 0; /* HDMI depth, etc. */ 546 // if (ss_enabled && (ss->type & ATOM_EXTERNAL_SS_MASK)) 547 // args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_REF_DIV_SRC; 548 switch (bitsPerChannel) { 549 case 8: 550 default: 551 args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_24BPP; 552 break; 553 case 10: 554 args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_30BPP; 555 break; 556 case 12: 557 args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_36BPP; 558 break; 559 case 16: 560 args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_48BPP; 561 break; 562 } 563 args.v6.ucTransmitterID 564 = gConnector[connectorIndex]->encoder.objectID; 565 args.v6.ucEncoderMode = display_get_encoder_mode(connectorIndex); 566 args.v6.ucPpll = pllID; 567 break; 568 default: 569 TRACE("%s: ERROR: table version %" B_PRIu8 ".%" B_PRIu8 " TODO\n", 570 __func__, tableMajor, tableMinor); 571 return B_ERROR; 572 } 573 574 TRACE("%s: set adjusted pixel clock %" B_PRIu32 " (was %" B_PRIu32 ")\n", 575 __func__, pll->pixelClock, pixelClock); 576 577 return atom_execute_table(gAtomContext, index, (uint32*)&args); 578 } 579