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
ComputeHdmiDpll(int freq,int * Pdiv,int * Qdiv,int * Kdiv,float * bestdco)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
ComputeDisplayPortDpll(int freq,int * Pdiv,int * Qdiv,int * Kdiv,float * bestdco)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
ProgramPLL(int which,int Pdiv,int Qdiv,int Kdiv,float dco)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