/* * Copyright 2006-2011, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: * Alexander von Gluck, kallisti5@unixzen.com */ #include "pll.h" #include #include #include #include #include "accelerant_protos.h" #include "accelerant.h" #include "bios.h" #include "connector.h" #include "display.h" #include "displayport.h" #include "encoder.h" #include "utility.h" #define TRACE_PLL #ifdef TRACE_PLL extern "C" void _sPrintf(const char* format, ...); # define TRACE(x...) _sPrintf("radeon_hd: " x) #else # define TRACE(x...) ; #endif #define ERROR(x...) _sPrintf("radeon_hd: " x) // Pixel Clock Storage // kHz Value Result // Haiku: 104000 khz 104 Mhz // Linux: 104000 khz 104 Mhz // AtomBIOS: 10400 * 10 khz 104 Mhz // Ghz // Haiku: 162000 * 10 khz 1.62 Ghz // Linux: 162000 * 10 khz 1.62 Ghz // AtomBIOS: 16200 * 10 Khz 0.162 * 10 Ghz /* The PLL allows to synthesize a clock signal with a range of frequencies * based on a single input reference clock signal. It uses several dividers * to create a rational factor multiple of the input frequency. * * The reference clock signal frequency is pll_info::referenceFreq (in kHz). * It is then, one after another... * (1) divided by the (integer) reference divider (pll_info::referenceDiv). * (2) multiplied by the fractional feedback divider, which sits in the * PLL's feedback loop and thus multiplies the frequency. It allows * using a rational number factor of the form "x.y", with * x = pll_info::feedbackDiv and y = pll_info::feedbackDivFrac. * (3) divided by the (integer) post divider (pll_info::postDiv). * Allowed ranges are given in the pll_info min/max values. * * The resulting output pixel clock frequency is then: * * feedbackDiv + (feedbackDivFrac/10) * f_out = referenceFreq * ------------------------------------ * referenceDiv * postDiv */ status_t pll_limit_probe(pll_info* pll) { uint8 tableMajor; uint8 tableMinor; uint16 tableOffset; int index = GetIndexIntoMasterTable(DATA, FirmwareInfo); if (atom_parse_data_header(gAtomContext, index, NULL, &tableMajor, &tableMinor, &tableOffset) != B_OK) { ERROR("%s: Couldn't parse data header\n", __func__); return B_ERROR; } TRACE("%s: table %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, tableMajor, tableMinor); union atomFirmwareInfo { ATOM_FIRMWARE_INFO info; ATOM_FIRMWARE_INFO_V1_2 info_12; ATOM_FIRMWARE_INFO_V1_3 info_13; ATOM_FIRMWARE_INFO_V1_4 info_14; ATOM_FIRMWARE_INFO_V2_1 info_21; ATOM_FIRMWARE_INFO_V2_2 info_22; }; union atomFirmwareInfo* firmwareInfo = (union atomFirmwareInfo*)(gAtomContext->bios + tableOffset); /* pixel clock limits */ pll->referenceFreq = B_LENDIAN_TO_HOST_INT16(firmwareInfo->info.usReferenceClock) * 10; if (tableMinor < 2) { pll->pllOutMin = B_LENDIAN_TO_HOST_INT16( firmwareInfo->info.usMinPixelClockPLL_Output) * 10; } else { pll->pllOutMin = B_LENDIAN_TO_HOST_INT32( firmwareInfo->info_12.ulMinPixelClockPLL_Output) * 10; } pll->pllOutMax = B_LENDIAN_TO_HOST_INT32( firmwareInfo->info.ulMaxPixelClockPLL_Output) * 10; if (tableMinor >= 4) { pll->lcdPllOutMin = B_LENDIAN_TO_HOST_INT16( firmwareInfo->info_14.usLcdMinPixelClockPLL_Output) * 1000; if (pll->lcdPllOutMin == 0) pll->lcdPllOutMin = pll->pllOutMin; pll->lcdPllOutMax = B_LENDIAN_TO_HOST_INT16( firmwareInfo->info_14.usLcdMaxPixelClockPLL_Output) * 1000; if (pll->lcdPllOutMax == 0) pll->lcdPllOutMax = pll->pllOutMax; } else { pll->lcdPllOutMin = pll->pllOutMin; pll->lcdPllOutMax = pll->pllOutMax; } if (pll->pllOutMin == 0) { pll->pllOutMin = 64800 * 10; // Avivo+ limit } pll->minPostDiv = POST_DIV_MIN; pll->maxPostDiv = POST_DIV_LIMIT; pll->minRefDiv = REF_DIV_MIN; pll->maxRefDiv = REF_DIV_LIMIT; pll->minFeedbackDiv = FB_DIV_MIN; pll->maxFeedbackDiv = FB_DIV_LIMIT; pll->pllInMin = B_LENDIAN_TO_HOST_INT16( firmwareInfo->info.usMinPixelClockPLL_Input) * 10; pll->pllInMax = B_LENDIAN_TO_HOST_INT16( firmwareInfo->info.usMaxPixelClockPLL_Input) * 10; TRACE("%s: referenceFreq: %" B_PRIu32 "; pllOutMin: %" B_PRIu32 "; " " pllOutMax: %" B_PRIu32 "; pllInMin: %" B_PRIu32 ";" "pllInMax: %" B_PRIu32 "\n", __func__, pll->referenceFreq, pll->pllOutMin, pll->pllOutMax, pll->pllInMin, pll->pllInMax); return B_OK; } status_t pll_ppll_ss_probe(pll_info* pll, uint32 ssID) { uint8 tableMajor; uint8 tableMinor; uint16 headerOffset; uint16 headerSize; int index = GetIndexIntoMasterTable(DATA, PPLL_SS_Info); if (atom_parse_data_header(gAtomContext, index, &headerSize, &tableMajor, &tableMinor, &headerOffset) != B_OK) { ERROR("%s: Couldn't parse data header\n", __func__); pll->ssEnabled = false; return B_ERROR; } struct _ATOM_SPREAD_SPECTRUM_INFO *ss_info = (struct _ATOM_SPREAD_SPECTRUM_INFO*)((uint16*)gAtomContext->bios + headerOffset); int indices = (headerSize - sizeof(ATOM_COMMON_TABLE_HEADER)) / sizeof(ATOM_SPREAD_SPECTRUM_ASSIGNMENT); int i; for (i = 0; i < indices; i++) { if (ss_info->asSS_Info[i].ucSS_Id == ssID) { pll->ssPercentage = B_LENDIAN_TO_HOST_INT16( ss_info->asSS_Info[i].usSpreadSpectrumPercentage); pll->ssType = ss_info->asSS_Info[i].ucSpreadSpectrumType; pll->ssStep = ss_info->asSS_Info[i].ucSS_Step; pll->ssDelay = ss_info->asSS_Info[i].ucSS_Delay; pll->ssRange = ss_info->asSS_Info[i].ucSS_Range; pll->ssReferenceDiv = ss_info->asSS_Info[i].ucRecommendedRef_Div; pll->ssEnabled = true; return B_OK; } } pll->ssEnabled = false; return B_ERROR; } status_t pll_asic_ss_probe(pll_info* pll, uint32 ssID) { uint8 tableMajor; uint8 tableMinor; uint16 headerOffset; uint16 headerSize; int index = GetIndexIntoMasterTable(DATA, ASIC_InternalSS_Info); if (atom_parse_data_header(gAtomContext, index, &headerSize, &tableMajor, &tableMinor, &headerOffset) != B_OK) { ERROR("%s: Couldn't parse data header\n", __func__); pll->ssEnabled = false; return B_ERROR; } union asicSSInfo { struct _ATOM_ASIC_INTERNAL_SS_INFO info; struct _ATOM_ASIC_INTERNAL_SS_INFO_V2 info_2; struct _ATOM_ASIC_INTERNAL_SS_INFO_V3 info_3; }; union asicSSInfo *ss_info = (union asicSSInfo*)((uint16*)gAtomContext->bios + headerOffset); int i; int indices; switch (tableMajor) { case 1: indices = (headerSize - sizeof(ATOM_COMMON_TABLE_HEADER)) / sizeof(ATOM_ASIC_SS_ASSIGNMENT); for (i = 0; i < indices; i++) { if (ss_info->info.asSpreadSpectrum[i].ucClockIndication != ssID) { continue; } TRACE("%s: ss match found\n", __func__); if (pll->pixelClock / 10 > B_LENDIAN_TO_HOST_INT32( ss_info->info.asSpreadSpectrum[i].ulTargetClockRange)) { TRACE("%s: pixelClock > targetClockRange!\n", __func__); continue; } pll->ssPercentage = B_LENDIAN_TO_HOST_INT16( ss_info->info.asSpreadSpectrum[i].usSpreadSpectrumPercentage ); pll->ssType = ss_info->info.asSpreadSpectrum[i].ucSpreadSpectrumMode; pll->ssRate = B_LENDIAN_TO_HOST_INT16( ss_info->info.asSpreadSpectrum[i].usSpreadRateInKhz); pll->ssPercentageDiv = 100; pll->ssEnabled = true; return B_OK; } break; case 2: indices = (headerSize - sizeof(ATOM_COMMON_TABLE_HEADER)) / sizeof(ATOM_ASIC_SS_ASSIGNMENT_V2); for (i = 0; i < indices; i++) { if (ss_info->info_2.asSpreadSpectrum[i].ucClockIndication != ssID) { continue; } TRACE("%s: ss match found\n", __func__); if (pll->pixelClock / 10 > B_LENDIAN_TO_HOST_INT32( ss_info->info_2.asSpreadSpectrum[i].ulTargetClockRange)) { TRACE("%s: pixelClock > targetClockRange!\n", __func__); continue; } pll->ssPercentage = B_LENDIAN_TO_HOST_INT16( ss_info ->info_2.asSpreadSpectrum[i].usSpreadSpectrumPercentage ); pll->ssType = ss_info->info_2.asSpreadSpectrum[i].ucSpreadSpectrumMode; pll->ssRate = B_LENDIAN_TO_HOST_INT16( ss_info->info_2.asSpreadSpectrum[i].usSpreadRateIn10Hz); pll->ssPercentageDiv = 100; pll->ssEnabled = true; return B_OK; } break; case 3: indices = (headerSize - sizeof(ATOM_COMMON_TABLE_HEADER)) / sizeof(ATOM_ASIC_SS_ASSIGNMENT_V3); for (i = 0; i < indices; i++) { if (ss_info->info_3.asSpreadSpectrum[i].ucClockIndication != ssID) { continue; } TRACE("%s: ss match found\n", __func__); if (pll->pixelClock / 10 > B_LENDIAN_TO_HOST_INT32( ss_info->info_3.asSpreadSpectrum[i].ulTargetClockRange)) { TRACE("%s: pixelClock > targetClockRange!\n", __func__); continue; } pll->ssPercentage = B_LENDIAN_TO_HOST_INT16( ss_info ->info_3.asSpreadSpectrum[i].usSpreadSpectrumPercentage ); pll->ssType = ss_info->info_3.asSpreadSpectrum[i].ucSpreadSpectrumMode; pll->ssRate = B_LENDIAN_TO_HOST_INT16( ss_info->info_3.asSpreadSpectrum[i].usSpreadRateIn10Hz); if ((ss_info->info_3.asSpreadSpectrum[i].ucSpreadSpectrumMode & SS_MODE_V3_PERCENTAGE_DIV_BY_1000_MASK) != 0) pll->ssPercentageDiv = 1000; else pll->ssPercentageDiv = 100; if (ssID == ASIC_INTERNAL_ENGINE_SS || ssID == ASIC_INTERNAL_MEMORY_SS) pll->ssRate /= 100; pll->ssEnabled = true; return B_OK; } break; default: ERROR("%s: Unknown SS table version!\n", __func__); pll->ssEnabled = false; return B_ERROR; } ERROR("%s: No potential spread spectrum data found!\n", __func__); pll->ssEnabled = false; return B_ERROR; } void pll_compute_post_divider(pll_info* pll) { if ((pll->flags & PLL_USE_POST_DIV) != 0) { TRACE("%s: using AtomBIOS post divider\n", __func__); return; } uint32 vco; if ((pll->flags & PLL_PREFER_MINM_OVER_MAXP) != 0) { if ((pll->flags & PLL_IS_LCD) != 0) vco = pll->lcdPllOutMin; else vco = pll->pllOutMax; } else { if ((pll->flags & PLL_IS_LCD) != 0) vco = pll->lcdPllOutMax; else vco = pll->pllOutMin; } TRACE("%s: vco = %" B_PRIu32 "\n", __func__, vco); uint32 postDivider = vco / pll->adjustedClock; uint32 tmp = vco % pll->adjustedClock; if ((pll->flags & PLL_PREFER_MINM_OVER_MAXP) != 0) { if (tmp) postDivider++; } else { if (!tmp) postDivider--; } if (postDivider > pll->maxPostDiv) postDivider = pll->maxPostDiv; else if (postDivider < pll->minPostDiv) postDivider = pll->minPostDiv; pll->postDiv = postDivider; TRACE("%s: postDiv = %" B_PRIu32 "\n", __func__, postDivider); } /*! Compute values for the fractional feedback divider to match the desired * pixel clock frequency as closely as possible. Reference and post divider * values are already filled in (if used). */ status_t pll_compute(pll_info* pll) { radeon_shared_info &info = *gInfo->shared_info; pll_compute_post_divider(pll); const uint32 targetClock = pll->adjustedClock; pll->feedbackDiv = 0; pll->feedbackDivFrac = 0; if ((pll->flags & PLL_USE_REF_DIV) != 0) { TRACE("%s: using AtomBIOS reference divider\n", __func__); } else { TRACE("%s: using minimum reference divider\n", __func__); pll->referenceDiv = pll->minRefDiv; } if ((pll->flags & PLL_USE_FRAC_FB_DIV) != 0) { TRACE("%s: using AtomBIOS fractional feedback divider\n", __func__); const uint32 numerator = pll->postDiv * pll->referenceDiv * targetClock; pll->feedbackDiv = numerator / pll->referenceFreq; pll->feedbackDivFrac = numerator % pll->referenceFreq; if (pll->feedbackDiv > pll->maxFeedbackDiv) pll->feedbackDiv = pll->maxFeedbackDiv; else if (pll->feedbackDiv < pll->minFeedbackDiv) pll->feedbackDiv = pll->minFeedbackDiv; // Put first 2 digits after the decimal point into feedbackDivFrac pll->feedbackDivFrac = (100 * pll->feedbackDivFrac) / pll->referenceFreq; // Now round it to one digit if (pll->feedbackDivFrac >= 5) { pll->feedbackDivFrac -= 5; pll->feedbackDivFrac /= 10; pll->feedbackDivFrac++; } if (pll->feedbackDivFrac >= 10) { pll->feedbackDiv++; pll->feedbackDivFrac = 0; } } else { TRACE("%s: performing fractional feedback calculations\n", __func__); while (pll->referenceDiv <= pll->maxRefDiv) { // get feedback divider uint32 retroEncabulator = pll->postDiv * pll->referenceDiv; retroEncabulator *= targetClock; pll->feedbackDiv = retroEncabulator / pll->referenceFreq; pll->feedbackDivFrac = retroEncabulator % pll->referenceFreq; if (pll->feedbackDiv > pll->maxFeedbackDiv) pll->feedbackDiv = pll->maxFeedbackDiv; else if (pll->feedbackDiv < pll->minFeedbackDiv) pll->feedbackDiv = pll->minFeedbackDiv; if (pll->feedbackDivFrac >= (pll->referenceFreq / 2)) pll->feedbackDiv++; pll->feedbackDivFrac = 0; if (pll->referenceDiv == 0 || pll->postDiv == 0 || targetClock == 0) { TRACE("%s: Caught division by zero!\n", __func__); TRACE("%s: referenceDiv %" B_PRIu32 "\n", __func__, pll->referenceDiv); TRACE("%s: postDiv %" B_PRIu32 "\n", __func__, pll->postDiv); TRACE("%s: targetClock %" B_PRIu32 "\n", __func__, targetClock); return B_ERROR; } uint32 tmp = (pll->referenceFreq * pll->feedbackDiv) / (pll->postDiv * pll->referenceDiv); tmp = (tmp * 1000) / targetClock; if (tmp > (1000 + (MAX_TOLERANCE / 10))) pll->referenceDiv++; else if (tmp >= (1000 - (MAX_TOLERANCE / 10))) break; else pll->referenceDiv++; } } if (pll->referenceDiv == 0 || pll->postDiv == 0) { TRACE("%s: Caught division by zero of post or reference divider\n", __func__); return B_ERROR; } uint32 calculatedClock = ((pll->referenceFreq * pll->feedbackDiv * 10) + (pll->referenceFreq * pll->feedbackDivFrac)) / (pll->referenceDiv * pll->postDiv * 10); TRACE("%s: Calculated pixel clock of %" B_PRIu32 " based on:\n", __func__, calculatedClock); TRACE("%s: referenceFrequency: %" B_PRIu32 "; " "referenceDivider: %" B_PRIu32 "\n", __func__, pll->referenceFreq, pll->referenceDiv); TRACE("%s: feedbackDivider: %" B_PRIu32 "; " "feedbackDividerFrac: %" B_PRIu32 "\n", __func__, pll->feedbackDiv, pll->feedbackDivFrac); TRACE("%s: postDivider: %" B_PRIu32 "\n", __func__, pll->postDiv); if (pll->adjustedClock != calculatedClock) { TRACE("%s: pixel clock %" B_PRIu32 " was changed to %" B_PRIu32 "\n", __func__, pll->adjustedClock, calculatedClock); pll->pixelClock = calculatedClock; } // Calcuate needed SS data on DCE4 if (info.dceMajor >= 4 && pll->ssEnabled) { if (pll->ssPercentageDiv == 0) { // Avoid div by 0, shouldn't happen but be mindful of it TRACE("%s: ssPercentageDiv is less than 0, aborting SS calcualation", __func__); pll->ssEnabled = false; return B_OK; } uint32 amount = ((pll->feedbackDiv * 10) + pll->feedbackDivFrac); amount *= pll->ssPercentage; amount /= pll->ssPercentageDiv * 100; pll->ssAmount = (amount / 10) & ATOM_PPLL_SS_AMOUNT_V2_FBDIV_MASK; pll->ssAmount |= ((amount - (amount / 10)) << ATOM_PPLL_SS_AMOUNT_V2_NFRAC_SHIFT) & ATOM_PPLL_SS_AMOUNT_V2_NFRAC_MASK; uint32 centerSpreadMultiplier = 2; if ((pll->ssType & ATOM_PPLL_SS_TYPE_V2_CENTRE_SPREAD) != 0) centerSpreadMultiplier = 4; pll->ssStep = (centerSpreadMultiplier * amount * pll->referenceDiv * (pll->ssRate * 2048)) / (125 * 25 * pll->referenceFreq / 100); } return B_OK; } void pll_setup_flags(pll_info* pll, uint8 crtcID) { radeon_shared_info &info = *gInfo->shared_info; uint32 connectorIndex = gDisplay[crtcID]->connectorIndex; uint32 connectorFlags = gConnector[connectorIndex]->flags; uint32 dceVersion = (info.dceMajor * 100) + info.dceMinor; TRACE("%s: CRTC: %" B_PRIu8 ", PLL: %" B_PRIu8 "\n", __func__, crtcID, pll->id); if (dceVersion >= 302 && pll->pixelClock > 200000) pll->flags |= PLL_PREFER_HIGH_FB_DIV; else pll->flags |= PLL_PREFER_LOW_REF_DIV; if (info.chipsetID < RADEON_RV770) pll->flags |= PLL_PREFER_MINM_OVER_MAXP; if ((connectorFlags & ATOM_DEVICE_LCD_SUPPORT) != 0) { pll->flags |= PLL_IS_LCD; // use reference divider for spread spectrum TRACE("%s: Spread Spectrum is %" B_PRIu32 "%%\n", __func__, pll->ssPercentage); if (pll->ssPercentage > 0) { if (pll->ssReferenceDiv > 0) { TRACE("%s: using Spread Spectrum reference divider. " "refDiv was: %" B_PRIu32 ", now: %" B_PRIu32 "\n", __func__, pll->referenceDiv, pll->ssReferenceDiv); pll->flags |= PLL_USE_REF_DIV; pll->referenceDiv = pll->ssReferenceDiv; // TODO: IS AVIVO+? pll->flags |= PLL_USE_FRAC_FB_DIV; } } } if ((connectorFlags & ATOM_DEVICE_TV_SUPPORT) != 0) pll->flags |= PLL_PREFER_CLOSEST_LOWER; if ((info.chipsetFlags & CHIP_APU) != 0) { // Use fractional feedback on APU's pll->flags |= PLL_USE_FRAC_FB_DIV; } } /** * pll_adjust - Ask AtomBIOS if it wants to make adjustments to our pll * * Returns B_OK on successful execution. */ status_t pll_adjust(pll_info* pll, display_mode* mode, uint8 crtcID) { radeon_shared_info &info = *gInfo->shared_info; uint32 pixelClock = pll->pixelClock; // original as pixel_clock will be adjusted uint32 connectorIndex = gDisplay[crtcID]->connectorIndex; connector_info* connector = gConnector[connectorIndex]; uint32 encoderID = connector->encoder.objectID; uint32 encoderMode = display_get_encoder_mode(connectorIndex); uint32 connectorFlags = connector->flags; uint32 externalEncoderID = 0; pll->adjustedClock = pll->pixelClock; if (connector->encoderExternal.isDPBridge) externalEncoderID = connector->encoderExternal.objectID; if (info.dceMajor >= 3) { uint8 tableMajor; uint8 tableMinor; int index = GetIndexIntoMasterTable(COMMAND, AdjustDisplayPll); if (atom_parse_cmd_header(gAtomContext, index, &tableMajor, &tableMinor) != B_OK) { ERROR("%s: Couldn't find AtomBIOS PLL adjustment\n", __func__); return B_ERROR; } TRACE("%s: table %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, tableMajor, tableMinor); // Prepare arguments for AtomBIOS call union adjustPixelClock { ADJUST_DISPLAY_PLL_PS_ALLOCATION v1; ADJUST_DISPLAY_PLL_PS_ALLOCATION_V3 v3; }; union adjustPixelClock args; memset(&args, 0, sizeof(args)); switch (tableMajor) { case 1: switch (tableMinor) { case 1: case 2: args.v1.usPixelClock = B_HOST_TO_LENDIAN_INT16(pixelClock / 10); args.v1.ucTransmitterID = encoderID; args.v1.ucEncodeMode = encoderMode; if (pll->ssPercentage > 0) { args.v1.ucConfig |= ADJUST_DISPLAY_CONFIG_SS_ENABLE; } atom_execute_table(gAtomContext, index, (uint32*)&args); // get returned adjusted clock pll->adjustedClock = B_LENDIAN_TO_HOST_INT16(args.v1.usPixelClock); pll->adjustedClock *= 10; break; case 3: args.v3.sInput.usPixelClock = B_HOST_TO_LENDIAN_INT16(pixelClock / 10); args.v3.sInput.ucTransmitterID = encoderID; args.v3.sInput.ucEncodeMode = encoderMode; args.v3.sInput.ucDispPllConfig = 0; if (pll->ssPercentage > 0) { args.v3.sInput.ucDispPllConfig |= DISPPLL_CONFIG_SS_ENABLE; } // Handle DP adjustments if (encoderMode == ATOM_ENCODER_MODE_DP || encoderMode == ATOM_ENCODER_MODE_DP_MST) { TRACE("%s: encoderMode is DP\n", __func__); args.v3.sInput.ucDispPllConfig |= DISPPLL_CONFIG_COHERENT_MODE; /* 162000 or 270000 */ uint32 dpLinkSpeed = dp_get_link_rate(connectorIndex, mode); /* 16200 or 27000 */ args.v3.sInput.usPixelClock = B_HOST_TO_LENDIAN_INT16(dpLinkSpeed / 10); } else if ((connectorFlags & ATOM_DEVICE_DFP_SUPPORT) != 0) { #if 0 if (encoderMode == ATOM_ENCODER_MODE_HDMI) { /* deep color support */ args.v3.sInput.usPixelClock = cpu_to_le16((mode->clock * bpc / 8) / 10); } #endif if (pixelClock > 165000) { args.v3.sInput.ucDispPllConfig |= DISPPLL_CONFIG_DUAL_LINK; } if (1) { // dig coherent mode? args.v3.sInput.ucDispPllConfig |= DISPPLL_CONFIG_COHERENT_MODE; } } args.v3.sInput.ucExtTransmitterID = externalEncoderID; atom_execute_table(gAtomContext, index, (uint32*)&args); // get returned adjusted clock pll->adjustedClock = B_LENDIAN_TO_HOST_INT32( args.v3.sOutput.ulDispPllFreq); pll->adjustedClock *= 10; // convert to kHz for storage if (args.v3.sOutput.ucRefDiv) { pll->flags |= PLL_USE_FRAC_FB_DIV; pll->flags |= PLL_USE_REF_DIV; pll->referenceDiv = args.v3.sOutput.ucRefDiv; } if (args.v3.sOutput.ucPostDiv) { pll->flags |= PLL_USE_FRAC_FB_DIV; pll->flags |= PLL_USE_POST_DIV; pll->postDiv = args.v3.sOutput.ucPostDiv; } break; default: TRACE("%s: ERROR: table version %" B_PRIu8 ".%" B_PRIu8 " unknown\n", __func__, tableMajor, tableMinor); return B_ERROR; } break; default: TRACE("%s: ERROR: table version %" B_PRIu8 ".%" B_PRIu8 " unknown\n", __func__, tableMajor, tableMinor); return B_ERROR; } } TRACE("%s: was: %" B_PRIu32 ", now: %" B_PRIu32 "\n", __func__, pixelClock, pll->adjustedClock); return B_OK; } /* * pll_set - Calculate and set a pll on the crtc provided based on the mode. * * Returns B_OK on successful execution */ status_t pll_set(display_mode* mode, uint8 crtcID) { uint32 connectorIndex = gDisplay[crtcID]->connectorIndex; uint32 encoderMode = display_get_encoder_mode(connectorIndex); pll_info* pll = &gConnector[connectorIndex]->encoder.pll; uint32 dp_clock = gConnector[connectorIndex]->dpInfo.linkRate; pll->ssEnabled = false; pll->pixelClock = mode->timing.pixel_clock; radeon_shared_info &info = *gInfo->shared_info; // Probe for PLL spread spectrum info; pll->ssPercentage = 0; pll->ssType = 0; pll->ssStep = 0; pll->ssDelay = 0; pll->ssRange = 0; pll->ssReferenceDiv = 0; switch (encoderMode) { case ATOM_ENCODER_MODE_DP_MST: case ATOM_ENCODER_MODE_DP: if (info.dceMajor >= 4) pll_asic_ss_probe(pll, ASIC_INTERNAL_SS_ON_DP); else { if (dp_clock == 162000) { pll_ppll_ss_probe(pll, ATOM_DP_SS_ID2); if (!pll->ssEnabled) pll_ppll_ss_probe(pll, ATOM_DP_SS_ID1); } else pll_ppll_ss_probe(pll, ATOM_DP_SS_ID1); } break; case ATOM_ENCODER_MODE_LVDS: if (info.dceMajor >= 4) pll_asic_ss_probe(pll, gInfo->lvdsSpreadSpectrumID); else pll_ppll_ss_probe(pll, gInfo->lvdsSpreadSpectrumID); break; case ATOM_ENCODER_MODE_DVI: if (info.dceMajor >= 4) pll_asic_ss_probe(pll, ASIC_INTERNAL_SS_ON_TMDS); break; case ATOM_ENCODER_MODE_HDMI: if (info.dceMajor >= 4) pll_asic_ss_probe(pll, ASIC_INTERNAL_SS_ON_HDMI); break; } pll_setup_flags(pll, crtcID); // set up any special flags pll_adjust(pll, mode, crtcID); // get any needed clock adjustments, set reference/post dividers pll_compute(pll); // compute dividers and spread spectrum uint8 tableMajor; uint8 tableMinor; int index = GetIndexIntoMasterTable(COMMAND, SetPixelClock); atom_parse_cmd_header(gAtomContext, index, &tableMajor, &tableMinor); TRACE("%s: table %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, tableMajor, tableMinor); uint32 bitsPerColor = 8; // TODO: Digital Depth, EDID 1.4+ on digital displays // isn't in Haiku edid common code? // Prepare arguments for AtomBIOS call union setPixelClock { SET_PIXEL_CLOCK_PS_ALLOCATION base; PIXEL_CLOCK_PARAMETERS v1; PIXEL_CLOCK_PARAMETERS_V2 v2; PIXEL_CLOCK_PARAMETERS_V3 v3; PIXEL_CLOCK_PARAMETERS_V5 v5; PIXEL_CLOCK_PARAMETERS_V6 v6; PIXEL_CLOCK_PARAMETERS_V7 v7; }; union setPixelClock args; memset(&args, 0, sizeof(args)); switch (tableMinor) { case 1: args.v1.usPixelClock = B_HOST_TO_LENDIAN_INT16(pll->pixelClock / 10); args.v1.usRefDiv = B_HOST_TO_LENDIAN_INT16(pll->referenceDiv); args.v1.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); args.v1.ucFracFbDiv = pll->feedbackDivFrac; args.v1.ucPostDiv = pll->postDiv; args.v1.ucPpll = pll->id; args.v1.ucCRTC = crtcID; args.v1.ucRefDivSrc = 1; break; case 2: args.v2.usPixelClock = B_HOST_TO_LENDIAN_INT16(pll->pixelClock / 10); args.v2.usRefDiv = B_HOST_TO_LENDIAN_INT16(pll->referenceDiv); args.v2.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); args.v2.ucFracFbDiv = pll->feedbackDivFrac; args.v2.ucPostDiv = pll->postDiv; args.v2.ucPpll = pll->id; args.v2.ucCRTC = crtcID; args.v2.ucRefDivSrc = 1; break; case 3: args.v3.usPixelClock = B_HOST_TO_LENDIAN_INT16(pll->pixelClock / 10); args.v3.usRefDiv = B_HOST_TO_LENDIAN_INT16(pll->referenceDiv); args.v3.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); args.v3.ucFracFbDiv = pll->feedbackDivFrac; args.v3.ucPostDiv = pll->postDiv; args.v3.ucPpll = pll->id; args.v3.ucMiscInfo = (pll->id << 2); if (pll->ssPercentage > 0 && (pll->ssType & ATOM_EXTERNAL_SS_MASK) != 0) { args.v3.ucMiscInfo |= PIXEL_CLOCK_MISC_REF_DIV_SRC; } args.v3.ucTransmitterId = gConnector[connectorIndex]->encoder.objectID; args.v3.ucEncoderMode = encoderMode; break; case 5: args.v5.ucCRTC = crtcID; args.v5.usPixelClock = B_HOST_TO_LENDIAN_INT16(pll->pixelClock / 10); args.v5.ucRefDiv = pll->referenceDiv; args.v5.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); args.v5.ulFbDivDecFrac = B_HOST_TO_LENDIAN_INT32(pll->feedbackDivFrac * 100000); args.v5.ucPostDiv = pll->postDiv; args.v5.ucMiscInfo = 0; /* HDMI depth, etc. */ if (pll->ssPercentage > 0 && (pll->ssType & ATOM_EXTERNAL_SS_MASK) != 0) { args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_REF_DIV_SRC; } if (encoderMode == ATOM_ENCODER_MODE_HDMI) { switch (bitsPerColor) { case 8: default: args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_HDMI_24BPP; break; case 10: // AMD notes the atombios define is incorrect here args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_HDMI_32BPP; break; case 12: // AMD notes the atombios define is incorrect here args.v5.ucMiscInfo |= PIXEL_CLOCK_V5_MISC_HDMI_30BPP; break; } } args.v5.ucTransmitterID = gConnector[connectorIndex]->encoder.objectID; args.v5.ucEncoderMode = encoderMode; args.v5.ucPpll = pll->id; break; case 6: args.v6.ulDispEngClkFreq = B_HOST_TO_LENDIAN_INT32(crtcID << 24 | pll->pixelClock / 10); args.v6.ucRefDiv = pll->referenceDiv; args.v6.usFbDiv = B_HOST_TO_LENDIAN_INT16(pll->feedbackDiv); args.v6.ulFbDivDecFrac = B_HOST_TO_LENDIAN_INT32(pll->feedbackDivFrac * 100000); args.v6.ucPostDiv = pll->postDiv; args.v6.ucMiscInfo = 0; /* HDMI depth, etc. */ if (pll->ssPercentage > 0 && (pll->ssType & ATOM_EXTERNAL_SS_MASK) != 0) { args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_REF_DIV_SRC; } if (encoderMode == ATOM_ENCODER_MODE_HDMI) { switch (bitsPerColor) { case 8: default: args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_24BPP; break; case 10: args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_30BPP_V6; break; case 12: args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_36BPP_V6; break; case 16: args.v6.ucMiscInfo |= PIXEL_CLOCK_V6_MISC_HDMI_48BPP; break; } } args.v6.ucTransmitterID = gConnector[connectorIndex]->encoder.objectID; args.v6.ucEncoderMode = encoderMode; args.v6.ucPpll = pll->id; break; case 7: args.v7.ulPixelClock = B_HOST_TO_LENDIAN_INT32(pll->pixelClock / 10); args.v7.ucMiscInfo = 0; if (gConnector[connectorIndex]->type == VIDEO_CONNECTOR_DVID && pll->pixelClock > 165000) { args.v7.ucMiscInfo |= PIXEL_CLOCK_V7_MISC_DVI_DUALLINK_EN; } args.v7.ucCRTC = crtcID; if (encoderMode == ATOM_ENCODER_MODE_HDMI) { switch (bitsPerColor) { case 8: default: args.v7.ucDeepColorRatio = PIXEL_CLOCK_V7_DEEPCOLOR_RATIO_DIS; break; case 10: args.v7.ucDeepColorRatio = PIXEL_CLOCK_V7_DEEPCOLOR_RATIO_5_4; break; case 12: args.v7.ucDeepColorRatio = PIXEL_CLOCK_V7_DEEPCOLOR_RATIO_3_2; break; case 16: args.v7.ucDeepColorRatio = PIXEL_CLOCK_V7_DEEPCOLOR_RATIO_2_1; break; } } args.v7.ucTransmitterID = gConnector[connectorIndex]->encoder.objectID; args.v7.ucEncoderMode = encoderMode; args.v7.ucPpll = pll->id; break; default: TRACE("%s: ERROR: table version %" B_PRIu8 ".%" B_PRIu8 " TODO\n", __func__, tableMajor, tableMinor); return B_ERROR; } TRACE("%s: set adjusted pixel clock %" B_PRIu32 " (was %" B_PRIu32 ")\n", __func__, pll->pixelClock, mode->timing.pixel_clock); status_t result = atom_execute_table(gAtomContext, index, (uint32*)&args); if (pll->ssEnabled) display_crtc_ss(pll, ATOM_ENABLE); else display_crtc_ss(pll, ATOM_DISABLE); return result; } /** * pll_set_external - Sets external default pll via SetPixelClock * * Applies a clock frequency to card's external PLL clock. */ status_t pll_set_external(uint32 clock) { TRACE("%s: set external pll clock to %" B_PRIu32 "\n", __func__, clock); if (clock == 0) ERROR("%s: Warning: default display clock is 0?\n", __func__); // also known as PLL display engineering uint8 tableMajor; uint8 tableMinor; int index = GetIndexIntoMasterTable(COMMAND, SetPixelClock); atom_parse_cmd_header(gAtomContext, index, &tableMajor, &tableMinor); TRACE("%s: table %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, tableMajor, tableMinor); union setPixelClock { SET_PIXEL_CLOCK_PS_ALLOCATION base; PIXEL_CLOCK_PARAMETERS v1; PIXEL_CLOCK_PARAMETERS_V2 v2; PIXEL_CLOCK_PARAMETERS_V3 v3; PIXEL_CLOCK_PARAMETERS_V5 v5; PIXEL_CLOCK_PARAMETERS_V6 v6; PIXEL_CLOCK_PARAMETERS_V7 v7; }; union setPixelClock args; memset(&args, 0, sizeof(args)); radeon_shared_info &info = *gInfo->shared_info; uint32 dceVersion = (info.dceMajor * 100) + info.dceMinor; switch (tableMajor) { case 1: switch (tableMinor) { case 5: // If the default DC PLL clock is specified, // SetPixelClock provides the dividers. args.v5.ucCRTC = ATOM_CRTC_INVALID; args.v5.usPixelClock = B_HOST_TO_LENDIAN_INT16(clock / 10); args.v5.ucPpll = ATOM_DCPLL; break; case 6: // If the default DC PLL clock is specified, // SetPixelClock provides the dividers. args.v6.ulDispEngClkFreq = B_HOST_TO_LENDIAN_INT32(clock / 10); if (dceVersion == 601) args.v6.ucPpll = ATOM_EXT_PLL1; else if (dceVersion >= 600) args.v6.ucPpll = ATOM_PPLL0; else args.v6.ucPpll = ATOM_DCPLL; break; default: ERROR("%s: Unknown table version %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, tableMajor, tableMinor); } break; default: ERROR("%s: Unknown table version %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, tableMajor, tableMinor); } return atom_execute_table(gAtomContext, index, (uint32*)&args); } /** * pll_set_dce - Sets external default pll via DCE Clock Allocation * * Applies a clock frequency to card's external PLL clock via SetDCEClock * Used on Polaris. */ status_t pll_set_dce(uint32 clock, uint8 clockType, uint8 clockSource) { TRACE("%s: set external pll clock to %" B_PRIu32 "\n", __func__, clock); if (clock == 0) ERROR("%s: Warning: default display clock is 0?\n", __func__); uint8 tableMajor; uint8 tableMinor; int index = GetIndexIntoMasterTable(COMMAND, SetDCEClock); atom_parse_cmd_header(gAtomContext, index, &tableMajor, &tableMinor); TRACE("%s: table %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, tableMajor, tableMinor); union setDCEClock { SET_DCE_CLOCK_PS_ALLOCATION_V1_1 v1; SET_DCE_CLOCK_PS_ALLOCATION_V2_1 v2; }; union setDCEClock args; memset(&args, 0, sizeof(args)); switch (tableMajor) { case 2: switch (tableMinor) { case 1: args.v2.asParam.ulDCEClkFreq = B_HOST_TO_LENDIAN_INT32(clock / 10); args.v2.asParam.ucDCEClkType = clockType; args.v2.asParam.ucDCEClkSrc = clockSource; break; default: ERROR("%s: Unknown table version %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, tableMajor, tableMinor); return B_ERROR; } break; default: ERROR("%s: Unknown table version %" B_PRIu8 ".%" B_PRIu8 "\n", __func__, tableMajor, tableMinor); return B_ERROR; } return atom_execute_table(gAtomContext, index, (uint32*)&args); } /** * pll_external_init - Sets external default pll to sane value * * Takes the AtomBIOS ulDefaultDispEngineClkFreq and applies it * back to the card's external PLL clock via SetPixelClock */ void pll_external_init() { radeon_shared_info &info = *gInfo->shared_info; if (info.dceMajor >= 12) { pll_set_dce(gInfo->displayClockFrequency, DCE_CLOCK_TYPE_DISPCLK, ATOM_GCK_DFS); pll_set_dce(gInfo->displayClockFrequency, DCE_CLOCK_TYPE_DPREFCLK, ATOM_GCK_DFS); } else if (info.dceMajor >= 6) { pll_set_external(gInfo->displayClockFrequency); } else if (info.dceMajor >= 4) { // Create our own pseudo pll pll_info pll; pll.pixelClock = gInfo->displayClockFrequency; pll_asic_ss_probe(&pll, ASIC_INTERNAL_SS_ON_DCPLL); if (pll.ssEnabled) display_crtc_ss(&pll, ATOM_DISABLE); pll_set_external(pll.pixelClock); if (pll.ssEnabled) display_crtc_ss(&pll, ATOM_ENABLE); } } /** * pll_usage_mask - Calculate which PLL's are in use * * Returns the mask of which PLL's are in use */ uint32 pll_usage_mask() { uint32 pllMask = 0; for (uint32 id = 0; id < ATOM_MAX_SUPPORTED_DEVICE; id++) { if (gConnector[id]->valid == true) { pll_info* pll = &gConnector[id]->encoder.pll; if (pll->id != ATOM_PPLL_INVALID) pllMask |= (1 << pll->id); } } return pllMask; } /** * pll_usage_count - Find number of connectors attached to a PLL * * Returns the count of connectors using specified PLL */ uint32 pll_usage_count(uint32 pllID) { uint32 pllCount = 0; for (uint32 id = 0; id < ATOM_MAX_SUPPORTED_DEVICE; id++) { if (gConnector[id]->valid == true) { pll_info* pll = &gConnector[id]->encoder.pll; if (pll->id == pllID) pllCount++; } } return pllCount; } /** * pll_shared_dp - Find any existing PLL's used for DP connectors * * Returns the PLL shared by other DP connectors */ uint32 pll_shared_dp() { for (uint32 id = 0; id < ATOM_MAX_SUPPORTED_DEVICE; id++) { if (gConnector[id]->valid == true) { if (connector_is_dp(id)) { pll_info* pll = &gConnector[id]->encoder.pll; return pll->id; } } } return ATOM_PPLL_INVALID; } /** * pll_next_available - Find the next available PLL * * Returns the next available PLL */ uint32 pll_next_available() { radeon_shared_info &info = *gInfo->shared_info; uint32 dceVersion = (info.dceMajor * 100) + info.dceMinor; uint32 pllMask = pll_usage_mask(); if (dceVersion == 802 || dceVersion == 601) { if (!(pllMask & (1 << ATOM_PPLL0))) return ATOM_PPLL0; } if (!(pllMask & (1 << ATOM_PPLL1))) return ATOM_PPLL1; if (dceVersion != 601) { if (!(pllMask & (1 << ATOM_PPLL2))) return ATOM_PPLL2; } // TODO: If this starts happening, we likely need to // add the sharing of PLL's with identical clock rates // (see radeon_atom_pick_pll in drm) ERROR("%s: Unable to find a PLL! (0x%" B_PRIX32 ")\n", __func__, pllMask); return ATOM_PPLL_INVALID; } status_t pll_pick(uint32 connectorIndex) { pll_info* pll = &gConnector[connectorIndex]->encoder.pll; radeon_shared_info &info = *gInfo->shared_info; uint32 dceVersion = (info.dceMajor * 100) + info.dceMinor; bool linkB = gConnector[connectorIndex]->encoder.linkEnumeration == GRAPH_OBJECT_ENUM_ID2 ? true : false; pll->id = ATOM_PPLL_INVALID; // DCE 6.1 APU, UNIPHYA requires PLL2 if (gConnector[connectorIndex]->encoder.objectID == ENCODER_OBJECT_ID_INTERNAL_UNIPHY && !linkB) { pll->id = ATOM_PPLL2; return B_OK; } if (connector_is_dp(connectorIndex)) { // If DP external clock, set to invalid except on DCE 6.1 if (gInfo->dpExternalClock && !(dceVersion == 601)) { pll->id = ATOM_PPLL_INVALID; return B_OK; } // DCE 6.1+, we can share DP PLLs. See if any other DP connectors // have been assigned a PLL yet. if (dceVersion >= 601) { pll->id = pll_shared_dp(); if (pll->id != ATOM_PPLL_INVALID) return B_OK; // Continue through to pll_next_available } else if (dceVersion == 600) { pll->id = ATOM_PPLL0; return B_OK; } else if (info.dceMajor >= 5) { pll->id = ATOM_DCPLL; return B_OK; } } if (info.dceMajor >= 4) { pll->id = pll_next_available(); return B_OK; } // TODO: Should return the CRTCID here. pll->id = ATOM_PPLL1; return B_OK; }