| Tim Wawrzynczak | 68deeb4 | 2022-09-09 10:59:08 -0600 | [diff] [blame] | 1 | -- |
| 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 | |
| 15 | with HW.GFX.GMA.Config; |
| 16 | with HW.GFX.GMA.Registers; |
| 17 | with HW.GFX.GMA.Power_And_Clocks; |
| 18 | |
| 19 | package 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 | |
| 377 | end HW.GFX.GMA.PLLs.Combo_Phy; |