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