xref: /haiku/src/add-ons/accelerants/intel_extreme/TigerLakePLL.cpp (revision 58bfdd0cb5b951c98b689abe845d4acda6cf0a46)
1*58bfdd0cSPulkoMandy /*
2*58bfdd0cSPulkoMandy  * Copyright 2024, Haiku, Inc. All Rights Reserved.
3*58bfdd0cSPulkoMandy  * Distributed under the terms of the MIT License.
4*58bfdd0cSPulkoMandy  */
5*58bfdd0cSPulkoMandy 
6*58bfdd0cSPulkoMandy #include "TigerLakePLL.h"
7*58bfdd0cSPulkoMandy 
8*58bfdd0cSPulkoMandy #include "accelerant.h"
9*58bfdd0cSPulkoMandy 
10*58bfdd0cSPulkoMandy #include <math.h>
11*58bfdd0cSPulkoMandy 
12*58bfdd0cSPulkoMandy 
13*58bfdd0cSPulkoMandy #undef TRACE
14*58bfdd0cSPulkoMandy #define TRACE_PLL
15*58bfdd0cSPulkoMandy #ifdef TRACE_PLL
16*58bfdd0cSPulkoMandy #   define TRACE(x...) _sPrintf("intel_extreme: " x)
17*58bfdd0cSPulkoMandy #else
18*58bfdd0cSPulkoMandy #   define TRACE(x...)
19*58bfdd0cSPulkoMandy #endif
20*58bfdd0cSPulkoMandy 
21*58bfdd0cSPulkoMandy #define ERROR(x...) _sPrintf("intel_extreme: " x)
22*58bfdd0cSPulkoMandy #define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
23*58bfdd0cSPulkoMandy 
24*58bfdd0cSPulkoMandy 
25*58bfdd0cSPulkoMandy /**
26*58bfdd0cSPulkoMandy  * Compute the best PLL parameters for a given symbol clock frequency for a DVI or HDMI port.
27*58bfdd0cSPulkoMandy  *
28*58bfdd0cSPulkoMandy  * This is the algorithm documented in Intel Documentation: IHD-OS-TGL-Vol 12-12.21, page 182
29*58bfdd0cSPulkoMandy  *
30*58bfdd0cSPulkoMandy  * The clock generation on Tiger Lake is in two steps: first, a DCO generates a fractional
31*58bfdd0cSPulkoMandy  * multiplication of the reference clock (in the GHz range). Then, 3 dividers bring this back into
32*58bfdd0cSPulkoMandy  * the symbol clock frequency range.
33*58bfdd0cSPulkoMandy  *
34*58bfdd0cSPulkoMandy  * Reference clock (24 or 19.2MHz, as defined in DSSM Reference Frequency register)
35*58bfdd0cSPulkoMandy  *             ||
36*58bfdd0cSPulkoMandy  *             vv
37*58bfdd0cSPulkoMandy  * DCO (multiply by non-integer value defined in DPLL_CFGCR0 register)
38*58bfdd0cSPulkoMandy  *             ||
39*58bfdd0cSPulkoMandy  *             vv
40*58bfdd0cSPulkoMandy  * "DCO frequency" in the range 7998 - 10000 MHz
41*58bfdd0cSPulkoMandy  *             ||
42*58bfdd0cSPulkoMandy  *             vv
43*58bfdd0cSPulkoMandy  * Divide by P, Q, and K
44*58bfdd0cSPulkoMandy  *             ||
45*58bfdd0cSPulkoMandy  *             vv
46*58bfdd0cSPulkoMandy  * AFE clock (PLL output)
47*58bfdd0cSPulkoMandy  *             ||
48*58bfdd0cSPulkoMandy  *             vv
49*58bfdd0cSPulkoMandy  * Divide by 5 (fixed)
50*58bfdd0cSPulkoMandy  *             ||
51*58bfdd0cSPulkoMandy  *             vv
52*58bfdd0cSPulkoMandy  * Symbol clock (same as Pixel clock for 24-bit RGB)
53*58bfdd0cSPulkoMandy  *
54*58bfdd0cSPulkoMandy  * The algorithm to configure this is:
55*58bfdd0cSPulkoMandy  * - Iterate over all allowed values for the divider obtained by P, Q and K
56*58bfdd0cSPulkoMandy  * - Determine the one that results in the DCO frequency being as close as possible to 8999MHz
57*58bfdd0cSPulkoMandy  * - Compute the corresponding values for P, Q and K and the DCO multiplier
58*58bfdd0cSPulkoMandy  *
59*58bfdd0cSPulkoMandy  * Since the DCO is a fractional multiplier (it can multiply by non-integer values), it will always
60*58bfdd0cSPulkoMandy  * be possible to set the DCO to a "close enough" value in its available range. The only constraint
61*58bfdd0cSPulkoMandy  * is getting it as close as possible to the midpoint (8999MHz), and at least have it in the
62*58bfdd0cSPulkoMandy  * allowed range (7998 to 10000MHz). If this is not possible (too low or too high pixel clock), a
63*58bfdd0cSPulkoMandy  * different video mode or setup will be needed (for example, enable dual link DVI to divide the
64*58bfdd0cSPulkoMandy  * clock by two).
65*58bfdd0cSPulkoMandy  *
66*58bfdd0cSPulkoMandy  * This also means that this algorithm is independant of the initial reference frequency: there
67*58bfdd0cSPulkoMandy  * will always be a way to setup the DCO so that it outputs the frequency computed here, no matter
68*58bfdd0cSPulkoMandy  * what the input clock is.
69*58bfdd0cSPulkoMandy  *
70*58bfdd0cSPulkoMandy  * Unlinke in previous hardware generations, there is no need to satisfy multiple constraints at
71*58bfdd0cSPulkoMandy  * the same time because of several stages of dividers and multipliers each with their own
72*58bfdd0cSPulkoMandy  * frequency range.
73*58bfdd0cSPulkoMandy  *
74*58bfdd0cSPulkoMandy  * DCO multiplier = DCO integer + DCO fraction / 2^15
75*58bfdd0cSPulkoMandy  * Symbol clock frequency = DCO multiplier * RefFreq in MHz / (5 * Pdiv * Qdiv * Kdiv)
76*58bfdd0cSPulkoMandy  *
77*58bfdd0cSPulkoMandy  * The symbol clock is the clock of the DVI/HDMI port. It defines how much time is needed to send
78*58bfdd0cSPulkoMandy  * one "symbol", which corresponds to 8 bits of useful data for each channel (Red, Green and Blue).
79*58bfdd0cSPulkoMandy  *
80*58bfdd0cSPulkoMandy  * In our case (8 bit RGB videomode), the symbol clock is equal to the pixel rate. It would need
81*58bfdd0cSPulkoMandy  * to be adjusted for 10 and 12-bit modes (more bits per pixel) as well as for YUV420 modes (the U
82*58bfdd0cSPulkoMandy  * and V parts are sent only for some pixels, reducing the total bandwidth).
83*58bfdd0cSPulkoMandy  *
84*58bfdd0cSPulkoMandy  * @param[in] freq Desired symbol clock frequency in kHz
85*58bfdd0cSPulkoMandy  * @param[out] Pdiv, Qdiv, Kdiv: dividers for the PLL
86*58bfdd0cSPulkoMandy  * @param[out] bestdco Required DCO frequency, in the range 7998 to 10000, in MHz
87*58bfdd0cSPulkoMandy  */
88*58bfdd0cSPulkoMandy bool
ComputeHdmiDpll(int freq,int * Pdiv,int * Qdiv,int * Kdiv,float * bestdco)89*58bfdd0cSPulkoMandy ComputeHdmiDpll(int freq, int* Pdiv, int* Qdiv, int* Kdiv, float* bestdco)
90*58bfdd0cSPulkoMandy {
91*58bfdd0cSPulkoMandy 	int bestdiv = 0;
92*58bfdd0cSPulkoMandy 	float dco = 0, dcocentrality = 0;
93*58bfdd0cSPulkoMandy 	float bestdcocentrality = 999999;
94*58bfdd0cSPulkoMandy 
95*58bfdd0cSPulkoMandy 	// The allowed values for the divider depending on the allowed values for P, Q, and K:
96*58bfdd0cSPulkoMandy 	// - P can be 2, 3, 5 or 7
97*58bfdd0cSPulkoMandy 	// - K can be 1, 2, or 3
98*58bfdd0cSPulkoMandy 	// - Q can be 1 to 255 if K = 2. Otherwise, Q must be 1.
99*58bfdd0cSPulkoMandy 	// Not all possible combinations are listed here, more can be added if needed to reach lower
100*58bfdd0cSPulkoMandy 	// resolutions and refresh rates (probably not so interesting, this already allows to reach
101*58bfdd0cSPulkoMandy 	// frequencies low enough for all practical uses in a standard setup).
102*58bfdd0cSPulkoMandy 	const int dividerlist[] = { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30, 32, 36,
103*58bfdd0cSPulkoMandy 		40, 42, 44, 48, 50, 52, 54, 56, 60, 64, 66, 68, 70, 72, 76, 78, 80, 84, 88, 90, 92,
104*58bfdd0cSPulkoMandy 		96, 98, 100, 102, 3, 5, 7, 9, 15, 21 };
105*58bfdd0cSPulkoMandy 	const float dcomin = 7998;
106*58bfdd0cSPulkoMandy 	const float dcomax = 10000;
107*58bfdd0cSPulkoMandy 	const float dcomid = (dcomin + dcomax) / 2;
108*58bfdd0cSPulkoMandy 
109*58bfdd0cSPulkoMandy 	float afeclk = (5 * freq) / 1000;
110*58bfdd0cSPulkoMandy 
111*58bfdd0cSPulkoMandy 	for (size_t i = 0; i < B_COUNT_OF(dividerlist); i++) {
112*58bfdd0cSPulkoMandy 		int div = dividerlist[i];
113*58bfdd0cSPulkoMandy 		dco = afeclk * div;
114*58bfdd0cSPulkoMandy 		if (dco <= dcomax && dco >= dcomin) {
115*58bfdd0cSPulkoMandy 			dcocentrality = fabs(dco - dcomid);
116*58bfdd0cSPulkoMandy 			if (dcocentrality < bestdcocentrality) {
117*58bfdd0cSPulkoMandy 				bestdcocentrality = dcocentrality;
118*58bfdd0cSPulkoMandy 				bestdiv = div;
119*58bfdd0cSPulkoMandy 				*bestdco = dco;
120*58bfdd0cSPulkoMandy 			}
121*58bfdd0cSPulkoMandy 		}
122*58bfdd0cSPulkoMandy 	}
123*58bfdd0cSPulkoMandy 
124*58bfdd0cSPulkoMandy 	if (bestdiv != 0) {
125*58bfdd0cSPulkoMandy 		// Good divider found
126*58bfdd0cSPulkoMandy 		if (bestdiv % 2 == 0) {
127*58bfdd0cSPulkoMandy 			// Divider is even
128*58bfdd0cSPulkoMandy 			if (bestdiv == 2) {
129*58bfdd0cSPulkoMandy 				*Pdiv = 2;
130*58bfdd0cSPulkoMandy 				*Qdiv = 1;
131*58bfdd0cSPulkoMandy 				*Kdiv = 1;
132*58bfdd0cSPulkoMandy 			} else if (bestdiv % 4 == 0) {
133*58bfdd0cSPulkoMandy 				*Pdiv = 2;
134*58bfdd0cSPulkoMandy 				*Qdiv = bestdiv / 4;
135*58bfdd0cSPulkoMandy 				*Kdiv = 2;
136*58bfdd0cSPulkoMandy 			} else if (bestdiv % 6 == 0) {
137*58bfdd0cSPulkoMandy 				*Pdiv = 3;
138*58bfdd0cSPulkoMandy 				*Qdiv = bestdiv / 6;
139*58bfdd0cSPulkoMandy 				*Kdiv = 2;
140*58bfdd0cSPulkoMandy 			} else if (bestdiv % 5 == 0) {
141*58bfdd0cSPulkoMandy 				*Pdiv = 5;
142*58bfdd0cSPulkoMandy 				*Qdiv = bestdiv / 10;
143*58bfdd0cSPulkoMandy 				*Kdiv = 2;
144*58bfdd0cSPulkoMandy 			} else if (bestdiv % 14 == 0) {
145*58bfdd0cSPulkoMandy 				*Pdiv = 7;
146*58bfdd0cSPulkoMandy 				*Qdiv = bestdiv / 14;
147*58bfdd0cSPulkoMandy 				*Kdiv = 2;
148*58bfdd0cSPulkoMandy 			}
149*58bfdd0cSPulkoMandy 		} else {
150*58bfdd0cSPulkoMandy 			// Divider is odd
151*58bfdd0cSPulkoMandy 			if (bestdiv == 3 || bestdiv == 5 || bestdiv == 7) {
152*58bfdd0cSPulkoMandy 				*Pdiv = bestdiv;
153*58bfdd0cSPulkoMandy 				*Qdiv = 1;
154*58bfdd0cSPulkoMandy 				*Kdiv = 1;
155*58bfdd0cSPulkoMandy 			} else {
156*58bfdd0cSPulkoMandy 				// Divider is 9, 15, or 21
157*58bfdd0cSPulkoMandy 				*Pdiv = bestdiv / 3;
158*58bfdd0cSPulkoMandy 				*Qdiv = 1;
159*58bfdd0cSPulkoMandy 				*Kdiv = 3;
160*58bfdd0cSPulkoMandy 			}
161*58bfdd0cSPulkoMandy 		}
162*58bfdd0cSPulkoMandy 
163*58bfdd0cSPulkoMandy 		// SUCCESS
164*58bfdd0cSPulkoMandy 		return true;
165*58bfdd0cSPulkoMandy 	} else {
166*58bfdd0cSPulkoMandy 		// No good divider found
167*58bfdd0cSPulkoMandy 		// FAIL, try a different frequency (different video mode)
168*58bfdd0cSPulkoMandy 		return false;
169*58bfdd0cSPulkoMandy 	}
170*58bfdd0cSPulkoMandy }
171*58bfdd0cSPulkoMandy 
172*58bfdd0cSPulkoMandy 
173*58bfdd0cSPulkoMandy /*! In the case of DisplayPort, the interface between the computer and the display is not just a
174*58bfdd0cSPulkoMandy  * stream of pixels, but instead a packetized link. This means the interface does not need to be
175*58bfdd0cSPulkoMandy  * running in sync with the pixel clock. Instead, a selection of well-defined frequencies are used.
176*58bfdd0cSPulkoMandy  *
177*58bfdd0cSPulkoMandy  * This also would allow to use a "spread spectrum" clock, reducing interferences without degrading
178*58bfdd0cSPulkoMandy  * picture quality.
179*58bfdd0cSPulkoMandy  *
180*58bfdd0cSPulkoMandy  * Here we just set it to the isecond lowest predefined frequency of 2.7GHz, which will be enough
181*58bfdd0cSPulkoMandy  * for displays up to full HD, and a little more.
182*58bfdd0cSPulkoMandy  *
183*58bfdd0cSPulkoMandy  * TODO decide when we have to use one of the higher frequencies. See "DisplayPort Mode PLL values"
184*58bfdd0cSPulkoMandy  * in IHD-OS-TGL-Vol 12-12.21, page 178. However, my machine uses a slightly different value than
185*58bfdd0cSPulkoMandy  * what's in Intel datasheets (with Intel values, bestdco should be 8100 and not 8090). I'm not
186*58bfdd0cSPulkoMandy  * sure why that is so, possibly they shift the fractional value in the CFGR0 register by 9 bits
187*58bfdd0cSPulkoMandy  * instead of 10? But replicating what my BIOS does here allows me to skip the PLL
188*58bfdd0cSPulkoMandy  * programming, a good idea, because it seems we don't yet know how to properly disable and
189*58bfdd0cSPulkoMandy  * re-train displayport once it is switched off.
190*58bfdd0cSPulkoMandy  */
191*58bfdd0cSPulkoMandy bool
ComputeDisplayPortDpll(int freq,int * Pdiv,int * Qdiv,int * Kdiv,float * bestdco)192*58bfdd0cSPulkoMandy ComputeDisplayPortDpll(int freq, int* Pdiv, int* Qdiv, int* Kdiv, float* bestdco)
193*58bfdd0cSPulkoMandy {
194*58bfdd0cSPulkoMandy 	*Pdiv = 3;
195*58bfdd0cSPulkoMandy 	*Qdiv = 1;
196*58bfdd0cSPulkoMandy 	*Kdiv = 2;
197*58bfdd0cSPulkoMandy 	*bestdco = 8090;
198*58bfdd0cSPulkoMandy 
199*58bfdd0cSPulkoMandy 	return true;
200*58bfdd0cSPulkoMandy }
201*58bfdd0cSPulkoMandy 
202*58bfdd0cSPulkoMandy 
203*58bfdd0cSPulkoMandy /*! Actually program the computed values (from the functions above) into the PLL, and start it.
204*58bfdd0cSPulkoMandy  *
205*58bfdd0cSPulkoMandy  * TODO: detect if the PLL is already running at the right frequency, and in that case, skip
206*58bfdd0cSPulkoMandy  * reprogramming it altogether. In the case of DisplayPort, most often there is no need to change
207*58bfdd0cSPulkoMandy  * anything once the clock has been initially set.
208*58bfdd0cSPulkoMandy  */
209*58bfdd0cSPulkoMandy status_t
ProgramPLL(int which,int Pdiv,int Qdiv,int Kdiv,float dco)210*58bfdd0cSPulkoMandy ProgramPLL(int which, int Pdiv, int Qdiv, int Kdiv, float dco)
211*58bfdd0cSPulkoMandy {
212*58bfdd0cSPulkoMandy 	// Set up the registers for PLL access for the requested PLL
213*58bfdd0cSPulkoMandy 	uint32 DPLL_CFGCR0;
214*58bfdd0cSPulkoMandy 	uint32 DPLL_CFGCR1;
215*58bfdd0cSPulkoMandy 	uint32 DPLL_ENABLE;
216*58bfdd0cSPulkoMandy 	uint32 DPLL_SPREAD_SPECTRUM;
217*58bfdd0cSPulkoMandy 
218*58bfdd0cSPulkoMandy 	switch (which) {
219*58bfdd0cSPulkoMandy 		case 0:
220*58bfdd0cSPulkoMandy 			DPLL_ENABLE = TGL_DPLL0_ENABLE;
221*58bfdd0cSPulkoMandy 			DPLL_SPREAD_SPECTRUM = TGL_DPLL0_SPREAD_SPECTRUM;
222*58bfdd0cSPulkoMandy 			DPLL_CFGCR0 = TGL_DPLL0_CFGCR0;
223*58bfdd0cSPulkoMandy 			DPLL_CFGCR1 = TGL_DPLL0_CFGCR1;
224*58bfdd0cSPulkoMandy 			break;
225*58bfdd0cSPulkoMandy 		case 1:
226*58bfdd0cSPulkoMandy 			DPLL_ENABLE = TGL_DPLL1_ENABLE;
227*58bfdd0cSPulkoMandy 			DPLL_SPREAD_SPECTRUM = TGL_DPLL1_SPREAD_SPECTRUM;
228*58bfdd0cSPulkoMandy 			DPLL_CFGCR0 = TGL_DPLL1_CFGCR0;
229*58bfdd0cSPulkoMandy 			DPLL_CFGCR1 = TGL_DPLL1_CFGCR1;
230*58bfdd0cSPulkoMandy 			break;
231*58bfdd0cSPulkoMandy 		case 4:
232*58bfdd0cSPulkoMandy 			DPLL_ENABLE = TGL_DPLL4_ENABLE;
233*58bfdd0cSPulkoMandy 			DPLL_SPREAD_SPECTRUM = TGL_DPLL4_SPREAD_SPECTRUM;
234*58bfdd0cSPulkoMandy 			DPLL_CFGCR0 = TGL_DPLL4_CFGCR0;
235*58bfdd0cSPulkoMandy 			DPLL_CFGCR1 = TGL_DPLL4_CFGCR1;
236*58bfdd0cSPulkoMandy 			break;
237*58bfdd0cSPulkoMandy 		default:
238*58bfdd0cSPulkoMandy 			return B_BAD_VALUE;
239*58bfdd0cSPulkoMandy 	}
240*58bfdd0cSPulkoMandy 
241*58bfdd0cSPulkoMandy 	// Find the reference frequency (24 or 19.2MHz)
242*58bfdd0cSPulkoMandy 	int ref_khz = gInfo->shared_info->pll_info.reference_frequency;
243*58bfdd0cSPulkoMandy 
244*58bfdd0cSPulkoMandy 	// There is an automatic divide-by-two in this case
245*58bfdd0cSPulkoMandy 	if (ref_khz == 38400)
246*58bfdd0cSPulkoMandy 		ref_khz = 19200;
247*58bfdd0cSPulkoMandy 
248*58bfdd0cSPulkoMandy 	float ref = ref_khz / 1000.0f;
249*58bfdd0cSPulkoMandy 
250*58bfdd0cSPulkoMandy 	// Compute the DCO divider integer and fractional parts
251*58bfdd0cSPulkoMandy 	uint32 dco_int = (uint32)floorf(dco / ref);
252*58bfdd0cSPulkoMandy 	uint32 dco_frac = (uint32)ceilf((dco / ref - dco_int) * (1 << 15));
253*58bfdd0cSPulkoMandy 
254*58bfdd0cSPulkoMandy 	int32 dco_reg = dco_int | (dco_frac << TGL_DPLL_DCO_FRACTION_SHIFT);
255*58bfdd0cSPulkoMandy 
256*58bfdd0cSPulkoMandy 	int32 dividers = 0;
257*58bfdd0cSPulkoMandy 	switch (Pdiv) {
258*58bfdd0cSPulkoMandy 		case 2:
259*58bfdd0cSPulkoMandy 			dividers |= TGL_DPLL_PDIV_2;
260*58bfdd0cSPulkoMandy 			break;
261*58bfdd0cSPulkoMandy 		case 3:
262*58bfdd0cSPulkoMandy 			dividers |= TGL_DPLL_PDIV_3;
263*58bfdd0cSPulkoMandy 			break;
264*58bfdd0cSPulkoMandy 		case 5:
265*58bfdd0cSPulkoMandy 			dividers |= TGL_DPLL_PDIV_5;
266*58bfdd0cSPulkoMandy 			break;
267*58bfdd0cSPulkoMandy 		case 7:
268*58bfdd0cSPulkoMandy 			dividers |= TGL_DPLL_PDIV_7;
269*58bfdd0cSPulkoMandy 			break;
270*58bfdd0cSPulkoMandy 		default:
271*58bfdd0cSPulkoMandy 			return B_BAD_VALUE;
272*58bfdd0cSPulkoMandy 	}
273*58bfdd0cSPulkoMandy 	switch (Kdiv) {
274*58bfdd0cSPulkoMandy 		case 1:
275*58bfdd0cSPulkoMandy 			dividers |= TGL_DPLL_KDIV_1;
276*58bfdd0cSPulkoMandy 			break;
277*58bfdd0cSPulkoMandy 		case 2:
278*58bfdd0cSPulkoMandy 			dividers |= TGL_DPLL_KDIV_2;
279*58bfdd0cSPulkoMandy 			break;
280*58bfdd0cSPulkoMandy 		case 3:
281*58bfdd0cSPulkoMandy 			dividers |= TGL_DPLL_KDIV_3;
282*58bfdd0cSPulkoMandy 			break;
283*58bfdd0cSPulkoMandy 		default:
284*58bfdd0cSPulkoMandy 			return B_BAD_VALUE;
285*58bfdd0cSPulkoMandy 	}
286*58bfdd0cSPulkoMandy 	if (Qdiv != 1)
287*58bfdd0cSPulkoMandy 		dividers |= (Qdiv << TGL_DPLL_QDIV_RATIO_SHIFT) | TGL_DPLL_QDIV_ENABLE;
288*58bfdd0cSPulkoMandy 
289*58bfdd0cSPulkoMandy 	int32 initialState = read32(DPLL_ENABLE);
290*58bfdd0cSPulkoMandy 	TRACE("DPLL_ENABLE(%" B_PRIx32 ") initial value = %" B_PRIx32 "\n", DPLL_ENABLE, initialState);
291*58bfdd0cSPulkoMandy 
292*58bfdd0cSPulkoMandy 	if (initialState & TGL_DPLL_LOCK) {
293*58bfdd0cSPulkoMandy 		int32 oldDCO = read32(DPLL_CFGCR0);
294*58bfdd0cSPulkoMandy 		int32 oldDividers = read32(DPLL_CFGCR1);
295*58bfdd0cSPulkoMandy 		TRACE("DPLL already locked, checking current settings: DCO %" B_PRIx32 " -> %" B_PRIx32
296*58bfdd0cSPulkoMandy 				", dividers %" B_PRIx32 " -> %" B_PRIx32 "\n",
297*58bfdd0cSPulkoMandy 			oldDCO, dco_reg, oldDividers, dividers);
298*58bfdd0cSPulkoMandy 
299*58bfdd0cSPulkoMandy 		if ((oldDCO == dco_reg) && (oldDividers == dividers)) {
300*58bfdd0cSPulkoMandy 			TRACE("DPLL already configured at the right frequency, no changes needed\n");
301*58bfdd0cSPulkoMandy 			return B_OK;
302*58bfdd0cSPulkoMandy 		}
303*58bfdd0cSPulkoMandy 	}
304*58bfdd0cSPulkoMandy 
305*58bfdd0cSPulkoMandy 	// Before we start, disable the PLL
306*58bfdd0cSPulkoMandy 	write32(DPLL_ENABLE, read32(DPLL_ENABLE) & ~TGL_DPLL_ENABLE);
307*58bfdd0cSPulkoMandy 	while ((read32(DPLL_ENABLE) & TGL_DPLL_LOCK) != 0);
308*58bfdd0cSPulkoMandy 	TRACE("PLL is unlocked\n");
309*58bfdd0cSPulkoMandy 
310*58bfdd0cSPulkoMandy 	// Enable PLL power
311*58bfdd0cSPulkoMandy 	write32(DPLL_ENABLE, read32(DPLL_ENABLE) | TGL_DPLL_POWER_ENABLE);
312*58bfdd0cSPulkoMandy 
313*58bfdd0cSPulkoMandy 	// Wait for PLL to be powered up
314*58bfdd0cSPulkoMandy 	while ((read32(DPLL_ENABLE) & TGL_DPLL_POWER_STATE) == 0);
315*58bfdd0cSPulkoMandy 	TRACE("PLL is powered on\n");
316*58bfdd0cSPulkoMandy 
317*58bfdd0cSPulkoMandy 	// Deactivate spread spectrum
318*58bfdd0cSPulkoMandy 	write32(DPLL_SPREAD_SPECTRUM, read32(DPLL_SPREAD_SPECTRUM) & ~TGL_DPLL_SSC_ENABLE);
319*58bfdd0cSPulkoMandy 
320*58bfdd0cSPulkoMandy 	// Configure DCO
321*58bfdd0cSPulkoMandy 	write32(DPLL_CFGCR0, dco_reg);
322*58bfdd0cSPulkoMandy 
323*58bfdd0cSPulkoMandy 	// Configure dividers
324*58bfdd0cSPulkoMandy 	write32(DPLL_CFGCR1, dividers);
325*58bfdd0cSPulkoMandy 	TRACE("DFGCR0(%" B_PRIx32 ") = %" B_PRIx32 ", CFGCR1(%" B_PRIx32 ") = %" B_PRIx32
326*58bfdd0cSPulkoMandy 			" (int = %" B_PRId32 ", frac = %" B_PRId32 ")\n", DPLL_CFGCR0, dco_reg,
327*58bfdd0cSPulkoMandy 		DPLL_CFGCR1, dividers, dco_int, dco_frac);
328*58bfdd0cSPulkoMandy 
329*58bfdd0cSPulkoMandy 	// Read to make sure all writes are flushed to the hardware
330*58bfdd0cSPulkoMandy 	read32(DPLL_CFGCR1);
331*58bfdd0cSPulkoMandy 
332*58bfdd0cSPulkoMandy 	// TODO Display voltage frequency switching?
333*58bfdd0cSPulkoMandy 
334*58bfdd0cSPulkoMandy 	// Enable PLL
335*58bfdd0cSPulkoMandy 	write32(DPLL_ENABLE, read32(DPLL_ENABLE) | TGL_DPLL_ENABLE);
336*58bfdd0cSPulkoMandy 	TRACE("DPLL_ENABLE(%" B_PRIx32 ") = %" B_PRIx32 "\n", DPLL_ENABLE, read32(DPLL_ENABLE));
337*58bfdd0cSPulkoMandy 
338*58bfdd0cSPulkoMandy 	// Wait for PLL to be enabled
339*58bfdd0cSPulkoMandy 	while ((read32(DPLL_ENABLE) & TGL_DPLL_LOCK) == 0);
340*58bfdd0cSPulkoMandy 	TRACE("PLL is locked\n");
341*58bfdd0cSPulkoMandy 
342*58bfdd0cSPulkoMandy 	// TODO Display voltage frequency switching?
343*58bfdd0cSPulkoMandy 
344*58bfdd0cSPulkoMandy 	return B_OK;
345*58bfdd0cSPulkoMandy }
346