blob: d74cf56b46eaafe2fb0ab1d717473d7cf7b3f5e4 [file] [log] [blame]
Tim Wawrzynczak68deeb42022-09-09 10:59:08 -06001--
2-- Copyright (C) 2022 Google, LLC
3--
4-- This program is free software; you can redistribute it and/or modify
5-- it under the terms of the GNU General Public License as published by
6-- the Free Software Foundation; either version 2 of the License, or
7-- (at your option) any later version.
8--
9-- This program is distributed in the hope that it will be useful,
10-- but WITHOUT ANY WARRANTY; without even the implied warranty of
11-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12-- GNU General Public License for more details.
13--
14
15with HW.GFX.GMA.Config;
16with HW.GFX.GMA.Registers;
17with HW.GFX.GMA.Power_And_Clocks;
18
19package body HW.GFX.GMA.PLLs.Combo_Phy is
20
21 subtype HDMI_Clock_Range is Frequency_Type range
22 25_000_000 .. Config.HDMI_Max_Clock_24bpp;
23 subtype DCO_Range is Pos64 range
24 7_998_000_000 .. 10_000_000_000;
25
26 type PLL_Regs_Record is record
27 DPLL_ENABLE : Registers.Registers_Index;
28 DPLL_CFGCR0 : Registers.Registers_Index;
29 DPLL_CFGCR1 : Registers.Registers_Index;
30 DPLL_SSC : Registers.Registers_Index;
31 end record;
32 type PLL_Regs_Array is array (Combo_DPLLs) of PLL_Regs_Record;
33 PLL_Regs : constant PLL_Regs_Array :=
34 PLL_Regs_Array'
35 (DPLL0 =>
36 (Registers.DPLL_0_ENABLE,
37 Registers.DPLL_0_CFGCR0,
38 Registers.DPLL_0_CFGCR1,
39 Registers.DPLL_0_SSC),
40 DPLL1 =>
41 (Registers.DPLL_1_ENABLE,
42 Registers.DPLL_1_CFGCR0,
43 Registers.DPLL_1_CFGCR1,
44 Registers.DPLL_1_SSC));
45
46 DPLL_ENABLE_PLL_ENABLE : constant := 1 * 2 ** 31;
47 DPLL_ENABLE_PLL_LOCK : constant := 1 * 2 ** 30;
48 DPLL_ENABLE_POWER_ENABLE : constant := 1 * 2 ** 27;
49 DPLL_ENABLE_POWER_STATE : constant := 1 * 2 ** 26;
50 DPLL_SSC_DP : constant := 16#200#;
51
52 procedure Encode_DCO (DCO_Integer, DCO_Fraction : out Word32; DCO : DCO_Range)
53 is
54 Refclk_Freq : Power_And_Clocks.Refclk_Range;
55 Enc_DCO : Int64;
56 begin
57 Power_And_Clocks.Get_Refclk (Refclk_Freq);
58
59 -- DPLL will auto-divide by 2 if refclk is 38.4 MHz
60 if Refclk_Freq = 38_400_000 then
61 Refclk_Freq := 19_200_000;
62 end if;
63
64 Enc_DCO := (DCO / 1_000) * (2 ** 15) / (Refclk_Freq / 1_000);
65 DCO_Integer := Word32 (Enc_DCO / (2 ** 15));
66 DCO_Fraction := Word32 (Enc_DCO) and 16#7fff#;
67 end Encode_DCO;
68
69 subtype PDiv_Range is Positive range 2 .. 7
70 with
71 Static_Predicate => (PDiv_Range in 2 | 3 | 5 | 7);
72
73 type Encoded_PDiv is new Positive range 1 .. 8
74 with
75 Static_Predicate => (Encoded_PDiv in 1 | 2 | 4 | 8);
76
77 function Encode_PDiv (PDiv : PDiv_Range) return Encoded_PDiv
78 is
79 (case PDiv is
80 when 2 => 2#0001#,
81 when 3 => 2#0010#,
82 when 5 => 2#0100#,
Nico Huber7e08e5d2026-06-05 14:55:41 +000083 when 7 => 2#1000#,
84 when others => 1);
Tim Wawrzynczak68deeb42022-09-09 10:59:08 -060085
86 subtype QDiv_Range is Positive range 1 .. 255;
87
88 type Encoded_QDiv is new Natural range 0 .. QDiv_Range'Last;
89
90 function Encode_QDiv (QDiv : QDiv_Range) return Encoded_QDiv
91 is
92 (Encoded_QDiv (QDiv));
93
94 function QDiv_Mode (QDiv : Encoded_QDiv) return Natural
95 is
96 (if QDiv <= 1 then 0 else 1);
97
98 subtype KDiv_Range is Positive range 1 .. 3;
99
100 type Encoded_KDiv is new Positive range 1 .. 4
101 with
102 Static_Predicate => (Encoded_KDiv in 1 | 2 | 4);
103
104 function Encode_KDiv (KDiv : KDiv_Range) return Encoded_KDiv
105 is
106 (case KDiv is
107 when 1 => 2#001#,
108 when 2 => 2#010#,
109 when 3 => 2#100#);
110
111 type PLL_Params is record
112 DCO_Integer : Word32;
113 DCO_Fraction : Word32;
114 PDiv : Encoded_PDiv;
115 KDiv : Encoded_KDiv;
116 QDiv : Encoded_QDiv;
117 end record;
118
119 type DP_PLL_Params_Array is array (DP_Bandwidth) of PLL_Params;
120
121 PLL_Params_19_2MHz : constant DP_PLL_Params_Array := DP_PLL_Params_Array'
122 (DP_Bandwidth_5_4 =>
123 (DCO_Integer => 16#1a5#,
124 DCO_Fraction => 16#7000#,
125 PDiv => 2,
126 KDiv => 1,
127 QDiv => 0),
128 DP_Bandwidth_2_7 =>
129 (DCO_Integer => 16#1a5#,
130 DCO_Fraction => 16#7000#,
131 PDiv => 2,
132 KDiv => 2,
133 QDiv => 0),
134 DP_Bandwidth_1_62 =>
135 (DCO_Integer => 16#1a5#,
136 DCO_Fraction => 16#7000#,
137 PDiv => 4,
138 KDiv => 2,
139 QDiv => 0));
140
141 PLL_Params_24MHz : constant DP_PLL_Params_Array := DP_PLL_Params_Array'
142 (DP_Bandwidth_5_4 =>
143 (DCO_Integer => 16#151#,
144 DCO_Fraction => 16#4000#,
145 PDiv => 2,
146 KDiv => 1,
147 QDiv => 0),
148 DP_Bandwidth_2_7 =>
149 (DCO_Integer => 16#151#,
150 DCO_Fraction => 16#4000#,
151 PDiv => 2,
152 KDiv => 2,
153 QDiv => 0),
154 DP_Bandwidth_1_62 =>
155 (DCO_Integer => 16#151#,
156 DCO_Fraction => 16#4000#,
157 PDiv => 4,
158 KDiv => 2,
159 QDiv => 0));
160
161 procedure Calc_DP_PLL_Dividers
162 (Bandwidth : in DP_Bandwidth;
163 Params : out PLL_Params)
164 is
165 Refclk : Frequency_Type;
166 begin
167 Power_And_Clocks.Get_Refclk (Refclk);
168 if Refclk = 24_000_000 then
169 Params := PLL_Params_24MHz (Bandwidth);
170 else
171 Params := PLL_Params_19_2MHz (Bandwidth);
172 end if;
173 end Calc_DP_PLL_Dividers;
174
175 procedure Calc_HDMI_PLL_Dividers
176 (Dotclock : in Frequency_Type;
177 Params : out PLL_Params;
178 Success : out Boolean)
179 is
180 subtype Div_Range is Pos64 range 2 .. 102;
181 subtype Candidate_Index is Positive range 1 .. 46;
182 type Candidate_Array is array (Candidate_Index) of Div_Range;
183 Candidates : constant Candidate_Array := Candidate_Array'
184 (2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30, 32, 36, 40, 42, 44,
185 48, 50, 52, 54, 56, 60, 64, 66, 68, 70, 72, 76, 78, 80, 84, 88, 90,
186 92, 96, 98, 100, 102, 3, 5, 7, 9, 15, 21);
187 AFE_Clk : constant Int64 := Dotclock * 5;
188 DCO_Mid : constant Int64 := (DCO_Range'First + DCO_Range'Last) / 2;
189 Best_DCO_Centrality : Int64 := Frequency_Type'Last;
190 Best_Div_Index : Candidate_Index := Candidate_Index'First;
191 Best_Div : Div_Range;
192 Best_DCO : DCO_Range := DCO_Range'First;
193 DCO_Found : Boolean := False;
194 PDiv : PDiv_Range;
195 QDiv : QDiv_Range;
196 KDiv : KDiv_Range;
197 begin
198 for Index in Candidate_Index loop
199 declare
200 DCO : constant Int64 := AFE_Clk * Candidates(Index);
201 DCO_Centrality : constant Int64 := abs (DCO - DCO_Mid);
202 begin
203 if DCO <= DCO_Range'Last and DCO >= DCO_Range'First and
204 DCO_Centrality < Best_DCO_Centrality
205 then
206 DCO_Found := True;
207 Best_DCO_Centrality := DCO_Centrality;
208 Best_Div_Index := Index;
209 Best_DCO := DCO;
210 end if;
211 end;
212 end loop;
213
214 if not DCO_Found then
215 Params := (DCO_Integer => 0,
216 DCO_Fraction => 0,
217 PDiv => Encoded_PDiv'First_Valid,
218 KDiv => Encoded_KDiv'First_Valid,
219 QDiv => Encoded_QDiv'First_Valid);
220 Success := False;
221 return;
222 end if;
223
224 Best_Div := Candidates (Best_Div_Index);
225 if Best_Div mod 2 = 0 then
226 if Best_Div = 2 then
227 PDiv := 2;
228 QDiv := 1;
229 KDiv := 1;
230 elsif Best_Div mod 4 = 0 then
231 PDiv := 2;
232 QDiv := QDiv_Range (Best_Div / 4);
233 KDiv := 2;
234 elsif Best_Div mod 6 = 0 then
235 PDiv := 3;
236 QDiv := QDiv_Range (Best_Div / 6);
237 KDiv := 2;
238 elsif Best_Div mod 5 = 0 then
239 PDiv := 5;
240 QDiv := QDiv_Range (Best_Div / 10);
241 KDiv := 2;
242 else
243 -- Use `else`, not `elsif`, to prove we covered all cases.
244 pragma Assert (Best_Div mod 14 = 0);
245 PDiv := 7;
246 QDiv := QDiv_Range (Best_Div / 14);
247 KDiv := 2;
248 end if;
249 else
250 if Best_Div = 3 or Best_Div = 5 or Best_Div = 7 then
251 PDiv := PDiv_Range (Best_Div);
252 QDiv := 1;
253 KDiv := 1;
254 else
255 pragma Assert (Best_Div mod 3 = 0);
256 PDiv := PDiv_Range (Best_Div / 3);
257 QDiv := 1;
258 KDiv := 3;
259 end if;
260 end if;
261
262 -- PRM: If Kdiv != 2, then Qdiv must be 1. Else Qdiv can be 1 to 255.
263 pragma Assert (if KDiv /= 2 then QDiv = 1);
264
265 Params.KDiv := Encode_KDiv (KDiv);
266 Params.PDiv := Encode_PDiv (PDiv);
267 Params.QDiv := Encode_QDiv (QDiv);
268 Encode_DCO (Params.DCO_Integer, Params.DCO_Fraction, Best_DCO);
269
270 Success := True;
271 end Calc_HDMI_PLL_Dividers;
272
273 procedure On
274 (PLL : in Combo_DPLLs;
275 Port_Cfg : in Port_Config;
276 Success : out Boolean)
277 is
278 Params : PLL_Params;
279 Refclk : Frequency_Type;
280 begin
281 if Port_Cfg.Display = DP then
282 Calc_DP_PLL_Dividers (Port_Cfg.DP.Bandwidth, Params);
283 Success := True;
284 else
285 if Port_Cfg.Mode.Dotclock not in HDMI_Clock_Range then
286 Debug.Put_Line ("Unsupported HDMI Pixel clock");
287 Success := False;
288 return;
289 end if;
290 declare
291 Color_Depth : constant Int64 := Port_Cfg.Mode.BPC * 3;
292 Pll_Freq : constant Frequency_Type := Color_Depth * Port_Cfg.Mode.Dotclock / 24;
293 begin
294 Calc_HDMI_PLL_Dividers (Pll_Freq, Params, Success);
295 end;
296 end if;
297
298 if not Success then
299 Debug.Put_Line ("Failed to calculate PLL dividers!");
300 return;
301 end if;
302
303 -- Display WA #22010492432: ehl, tgl, adl-p
304 -- Program half of the nominal DCO divider fraction value
305 -- for 38.4 MHz refclk
306 Power_And_Clocks.Get_Refclk (Refclk);
307 if Refclk = 38_400_000 then
308 Params.DCO_Fraction := Shift_Right (Params.DCO_Fraction, 1);
309 end if;
310
311 Registers.Set_Mask
312 (Register => PLL_Regs (PLL).DPLL_ENABLE,
313 Mask => DPLL_ENABLE_POWER_ENABLE);
314 Registers.Wait_Set_Mask
315 (Register => PLL_Regs (PLL).DPLL_ENABLE,
316 Mask => DPLL_ENABLE_POWER_STATE,
317 Success => Success);
318
319 if not Success then
320 Debug.Put_Line ("Failed to enable PLL!");
321 return;
322 end if;
323
324 -- Configure DPLL_SSC
325 Registers.Write
326 (Register => PLL_Regs (PLL).DPLL_SSC,
327 Value => (if Port_Cfg.Display = DP then DPLL_SSC_DP else 0));
328
329 Registers.Write
330 (Register => PLL_Regs (PLL).DPLL_CFGCR0,
331 Value => Shift_Left (Params.DCO_Fraction, 10) or
332 Params.DCO_Integer);
333
334 Registers.Write
335 (Register => PLL_Regs (PLL).DPLL_CFGCR1,
336 Value => Shift_Left (Word32 (Params.QDiv), 10) or
337 Shift_Left (Word32 (QDiv_Mode (Params.QDiv)), 9) or
338 Shift_left (Word32 (Params.KDiv), 6) or
339 Shift_Left (Word32 (Params.PDiv), 2));
340 Registers.Posting_Read(PLL_Regs (PLL).DPLL_CFGCR1);
341
342 -- Enable DPLL
343 Registers.Set_Mask
344 (Register => PLL_Regs (PLL).DPLL_ENABLE,
345 Mask => DPLL_ENABLE_PLL_ENABLE);
346 -- Wait for PLL Lock status
347 Registers.Wait_Set_Mask
348 (Register => PLL_Regs (PLL).DPLL_ENABLE,
349 Mask => DPLL_ENABLE_PLL_LOCK,
350 Success => Success);
351 end On;
352
353 procedure Free (PLL : Combo_DPLLs)
354 is
355 begin
356 Registers.Unset_Mask
357 (Register => PLL_Regs (PLL).DPLL_ENABLE,
358 Mask => DPLL_ENABLE_PLL_ENABLE);
359 Registers.Wait_Unset_Mask
360 (Register => PLL_Regs (PLL).DPLL_ENABLE,
361 Mask => DPLL_ENABLE_PLL_LOCK);
362
363 Registers.Unset_Mask
364 (Register => PLL_Regs (PLL).DPLL_ENABLE,
365 Mask => DPLL_ENABLE_POWER_ENABLE);
366 Registers.Wait_Unset_Mask
367 (Register => PLL_Regs (PLL).DPLL_ENABLE,
368 Mask => DPLL_ENABLE_POWER_STATE);
369 end Free;
370
371 procedure All_Off is
372 begin
373 for PLL in Combo_DPLLs loop
374 Free (PLL);
375 end loop;
376 end All_Off;
377
378end HW.GFX.GMA.PLLs.Combo_Phy;