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
29extern "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
38status_t
39pll_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
130status_t
131pll_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
171status_t
172pll_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
291void
292pll_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
335status_t
336pll_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
452void
453pll_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
502status_t
503pll_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
648status_t
649pll_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
854status_t
855pll_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
919void
920pll_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
940status_t
941pll_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