blob: 7424b98d7bcee6013fe10ca03564ebd5af36e16a [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#,
83 when 7 => 2#1000#);
84
85 subtype QDiv_Range is Positive range 1 .. 255;
86
87 type Encoded_QDiv is new Natural range 0 .. QDiv_Range'Last;
88
89 function Encode_QDiv (QDiv : QDiv_Range) return Encoded_QDiv
90 is
91 (Encoded_QDiv (QDiv));
92
93 function QDiv_Mode (QDiv : Encoded_QDiv) return Natural
94 is
95 (if QDiv <= 1 then 0 else 1);
96
97 subtype KDiv_Range is Positive range 1 .. 3;
98
99 type Encoded_KDiv is new Positive range 1 .. 4
100 with
101 Static_Predicate => (Encoded_KDiv in 1 | 2 | 4);
102
103 function Encode_KDiv (KDiv : KDiv_Range) return Encoded_KDiv
104 is
105 (case KDiv is
106 when 1 => 2#001#,
107 when 2 => 2#010#,
108 when 3 => 2#100#);
109
110 type PLL_Params is record
111 DCO_Integer : Word32;
112 DCO_Fraction : Word32;
113 PDiv : Encoded_PDiv;
114 KDiv : Encoded_KDiv;
115 QDiv : Encoded_QDiv;
116 end record;
117
118 type DP_PLL_Params_Array is array (DP_Bandwidth) of PLL_Params;
119
120 PLL_Params_19_2MHz : constant DP_PLL_Params_Array := DP_PLL_Params_Array'
121 (DP_Bandwidth_5_4 =>
122 (DCO_Integer => 16#1a5#,
123 DCO_Fraction => 16#7000#,
124 PDiv => 2,
125 KDiv => 1,
126 QDiv => 0),
127 DP_Bandwidth_2_7 =>
128 (DCO_Integer => 16#1a5#,
129 DCO_Fraction => 16#7000#,
130 PDiv => 2,
131 KDiv => 2,
132 QDiv => 0),
133 DP_Bandwidth_1_62 =>
134 (DCO_Integer => 16#1a5#,
135 DCO_Fraction => 16#7000#,
136 PDiv => 4,
137 KDiv => 2,
138 QDiv => 0));
139
140 PLL_Params_24MHz : constant DP_PLL_Params_Array := DP_PLL_Params_Array'
141 (DP_Bandwidth_5_4 =>
142 (DCO_Integer => 16#151#,
143 DCO_Fraction => 16#4000#,
144 PDiv => 2,
145 KDiv => 1,
146 QDiv => 0),
147 DP_Bandwidth_2_7 =>
148 (DCO_Integer => 16#151#,
149 DCO_Fraction => 16#4000#,
150 PDiv => 2,
151 KDiv => 2,
152 QDiv => 0),
153 DP_Bandwidth_1_62 =>
154 (DCO_Integer => 16#151#,
155 DCO_Fraction => 16#4000#,
156 PDiv => 4,
157 KDiv => 2,
158 QDiv => 0));
159
160 procedure Calc_DP_PLL_Dividers
161 (Bandwidth : in DP_Bandwidth;
162 Params : out PLL_Params)
163 is
164 Refclk : Frequency_Type;
165 begin
166 Power_And_Clocks.Get_Refclk (Refclk);
167 if Refclk = 24_000_000 then
168 Params := PLL_Params_24MHz (Bandwidth);
169 else
170 Params := PLL_Params_19_2MHz (Bandwidth);
171 end if;
172 end Calc_DP_PLL_Dividers;
173
174 procedure Calc_HDMI_PLL_Dividers
175 (Dotclock : in Frequency_Type;
176 Params : out PLL_Params;
177 Success : out Boolean)
178 is
179 subtype Div_Range is Pos64 range 2 .. 102;
180 subtype Candidate_Index is Positive range 1 .. 46;
181 type Candidate_Array is array (Candidate_Index) of Div_Range;
182 Candidates : constant Candidate_Array := Candidate_Array'
183 (2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30, 32, 36, 40, 42, 44,
184 48, 50, 52, 54, 56, 60, 64, 66, 68, 70, 72, 76, 78, 80, 84, 88, 90,
185 92, 96, 98, 100, 102, 3, 5, 7, 9, 15, 21);
186 AFE_Clk : constant Int64 := Dotclock * 5;
187 DCO_Mid : constant Int64 := (DCO_Range'First + DCO_Range'Last) / 2;
188 Best_DCO_Centrality : Int64 := Frequency_Type'Last;
189 Best_Div_Index : Candidate_Index := Candidate_Index'First;
190 Best_Div : Div_Range;
191 Best_DCO : DCO_Range := DCO_Range'First;
192 DCO_Found : Boolean := False;
193 PDiv : PDiv_Range;
194 QDiv : QDiv_Range;
195 KDiv : KDiv_Range;
196 begin
197 for Index in Candidate_Index loop
198 declare
199 DCO : constant Int64 := AFE_Clk * Candidates(Index);
200 DCO_Centrality : constant Int64 := abs (DCO - DCO_Mid);
201 begin
202 if DCO <= DCO_Range'Last and DCO >= DCO_Range'First and
203 DCO_Centrality < Best_DCO_Centrality
204 then
205 DCO_Found := True;
206 Best_DCO_Centrality := DCO_Centrality;
207 Best_Div_Index := Index;
208 Best_DCO := DCO;
209 end if;
210 end;
211 end loop;
212
213 if not DCO_Found then
214 Params := (DCO_Integer => 0,
215 DCO_Fraction => 0,
216 PDiv => Encoded_PDiv'First_Valid,
217 KDiv => Encoded_KDiv'First_Valid,
218 QDiv => Encoded_QDiv'First_Valid);
219 Success := False;
220 return;
221 end if;
222
223 Best_Div := Candidates (Best_Div_Index);
224 if Best_Div mod 2 = 0 then
225 if Best_Div = 2 then
226 PDiv := 2;
227 QDiv := 1;
228 KDiv := 1;
229 elsif Best_Div mod 4 = 0 then
230 PDiv := 2;
231 QDiv := QDiv_Range (Best_Div / 4);
232 KDiv := 2;
233 elsif Best_Div mod 6 = 0 then
234 PDiv := 3;
235 QDiv := QDiv_Range (Best_Div / 6);
236 KDiv := 2;
237 elsif Best_Div mod 5 = 0 then
238 PDiv := 5;
239 QDiv := QDiv_Range (Best_Div / 10);
240 KDiv := 2;
241 else
242 -- Use `else`, not `elsif`, to prove we covered all cases.
243 pragma Assert (Best_Div mod 14 = 0);
244 PDiv := 7;
245 QDiv := QDiv_Range (Best_Div / 14);
246 KDiv := 2;
247 end if;
248 else
249 if Best_Div = 3 or Best_Div = 5 or Best_Div = 7 then
250 PDiv := PDiv_Range (Best_Div);
251 QDiv := 1;
252 KDiv := 1;
253 else
254 pragma Assert (Best_Div mod 3 = 0);
255 PDiv := PDiv_Range (Best_Div / 3);
256 QDiv := 1;
257 KDiv := 3;
258 end if;
259 end if;
260
261 -- PRM: If Kdiv != 2, then Qdiv must be 1. Else Qdiv can be 1 to 255.
262 pragma Assert (if KDiv /= 2 then QDiv = 1);
263
264 Params.KDiv := Encode_KDiv (KDiv);
265 Params.PDiv := Encode_PDiv (PDiv);
266 Params.QDiv := Encode_QDiv (QDiv);
267 Encode_DCO (Params.DCO_Integer, Params.DCO_Fraction, Best_DCO);
268
269 Success := True;
270 end Calc_HDMI_PLL_Dividers;
271
272 procedure On
273 (PLL : in Combo_DPLLs;
274 Port_Cfg : in Port_Config;
275 Success : out Boolean)
276 is
277 Params : PLL_Params;
278 Refclk : Frequency_Type;
279 begin
280 if Port_Cfg.Display = DP then
281 Calc_DP_PLL_Dividers (Port_Cfg.DP.Bandwidth, Params);
282 Success := True;
283 else
284 if Port_Cfg.Mode.Dotclock not in HDMI_Clock_Range then
285 Debug.Put_Line ("Unsupported HDMI Pixel clock");
286 Success := False;
287 return;
288 end if;
289 declare
290 Color_Depth : constant Int64 := Port_Cfg.Mode.BPC * 3;
291 Pll_Freq : constant Frequency_Type := Color_Depth * Port_Cfg.Mode.Dotclock / 24;
292 begin
293 Calc_HDMI_PLL_Dividers (Pll_Freq, Params, Success);
294 end;
295 end if;
296
297 if not Success then
298 Debug.Put_Line ("Failed to calculate PLL dividers!");
299 return;
300 end if;
301
302 -- Display WA #22010492432: ehl, tgl, adl-p
303 -- Program half of the nominal DCO divider fraction value
304 -- for 38.4 MHz refclk
305 Power_And_Clocks.Get_Refclk (Refclk);
306 if Refclk = 38_400_000 then
307 Params.DCO_Fraction := Shift_Right (Params.DCO_Fraction, 1);
308 end if;
309
310 Registers.Set_Mask
311 (Register => PLL_Regs (PLL).DPLL_ENABLE,
312 Mask => DPLL_ENABLE_POWER_ENABLE);
313 Registers.Wait_Set_Mask
314 (Register => PLL_Regs (PLL).DPLL_ENABLE,
315 Mask => DPLL_ENABLE_POWER_STATE,
316 Success => Success);
317
318 if not Success then
319 Debug.Put_Line ("Failed to enable PLL!");
320 return;
321 end if;
322
323 -- Configure DPLL_SSC
324 Registers.Write
325 (Register => PLL_Regs (PLL).DPLL_SSC,
326 Value => (if Port_Cfg.Display = DP then DPLL_SSC_DP else 0));
327
328 Registers.Write
329 (Register => PLL_Regs (PLL).DPLL_CFGCR0,
330 Value => Shift_Left (Params.DCO_Fraction, 10) or
331 Params.DCO_Integer);
332
333 Registers.Write
334 (Register => PLL_Regs (PLL).DPLL_CFGCR1,
335 Value => Shift_Left (Word32 (Params.QDiv), 10) or
336 Shift_Left (Word32 (QDiv_Mode (Params.QDiv)), 9) or
337 Shift_left (Word32 (Params.KDiv), 6) or
338 Shift_Left (Word32 (Params.PDiv), 2));
339 Registers.Posting_Read(PLL_Regs (PLL).DPLL_CFGCR1);
340
341 -- Enable DPLL
342 Registers.Set_Mask
343 (Register => PLL_Regs (PLL).DPLL_ENABLE,
344 Mask => DPLL_ENABLE_PLL_ENABLE);
345 -- Wait for PLL Lock status
346 Registers.Wait_Set_Mask
347 (Register => PLL_Regs (PLL).DPLL_ENABLE,
348 Mask => DPLL_ENABLE_PLL_LOCK,
349 Success => Success);
350 end On;
351
352 procedure Free (PLL : Combo_DPLLs)
353 is
354 begin
355 Registers.Unset_Mask
356 (Register => PLL_Regs (PLL).DPLL_ENABLE,
357 Mask => DPLL_ENABLE_PLL_ENABLE);
358 Registers.Wait_Unset_Mask
359 (Register => PLL_Regs (PLL).DPLL_ENABLE,
360 Mask => DPLL_ENABLE_PLL_LOCK);
361
362 Registers.Unset_Mask
363 (Register => PLL_Regs (PLL).DPLL_ENABLE,
364 Mask => DPLL_ENABLE_POWER_ENABLE);
365 Registers.Wait_Unset_Mask
366 (Register => PLL_Regs (PLL).DPLL_ENABLE,
367 Mask => DPLL_ENABLE_POWER_STATE);
368 end Free;
369
370 procedure All_Off is
371 begin
372 for PLL in Combo_DPLLs loop
373 Free (PLL);
374 end loop;
375 end All_Off;
376
377end HW.GFX.GMA.PLLs.Combo_Phy;