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