xref: /haiku/src/add-ons/accelerants/radeon_hd/pll.cpp (revision 18027fff34af4a666c1e62254b462cbaeae1859e)
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