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