gma tgl: Add support for allocating PLLs

This patch adds support for allocating both combo PHY PLLs and USB
Type-C PLLs for Tiger Lake.

Verified Combo PHY (HDMI, eDP) and Type-C (DP Alt mode) on
Google/delbin.

Squashed clean-ups:

  gma tgl: Clean up the code for gfx plls

  This patch improves code quality through various optimizations, such as
  replacing indexed array access with direct value access and simplifying
  variable declarations. It also simplifies function rewrites by using
  constructs like case-when instead of multiple range mappings or if else
  blocks. Additionally, the patch addresses minor typographical errors.
  These changes enhance the code's readability and maintainability
  without impacting functionality.

  Signed-off-by: Dinesh Gehlot <digehlot@google.com>

  gma tgl: Rename DKL PLL to Dekel_Phy

  This patch changes the name of a PLL "DKL" to "Dekel_Phy" to maintain
  consistency with the naming convention of other PHY PLLs

  Signed-off-by: Dinesh Gehlot <digehlot@google.com>

Change-Id: I9dd7e0d0180f70d73eb50d7e58718261e5e74071
Signed-off-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
Reviewed-on: https://review.sourcearcade.org/c/libgfxinit/+/465
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Tested-by: Nico Huber <nico.h@gmx.de>
diff --git a/common/tigerlake/hw-gfx-gma-plls-combo_phy.adb b/common/tigerlake/hw-gfx-gma-plls-combo_phy.adb
new file mode 100644
index 0000000..7424b98
--- /dev/null
+++ b/common/tigerlake/hw-gfx-gma-plls-combo_phy.adb
@@ -0,0 +1,377 @@
+--
+-- Copyright (C) 2022 Google, LLC
+--
+-- This program is free software; you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation; either version 2 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+
+with HW.GFX.GMA.Config;
+with HW.GFX.GMA.Registers;
+with HW.GFX.GMA.Power_And_Clocks;
+
+package body HW.GFX.GMA.PLLs.Combo_Phy is
+
+   subtype HDMI_Clock_Range is Frequency_Type range
+      25_000_000 .. Config.HDMI_Max_Clock_24bpp;
+   subtype DCO_Range is Pos64 range
+      7_998_000_000 .. 10_000_000_000;
+
+   type PLL_Regs_Record is record
+      DPLL_ENABLE : Registers.Registers_Index;
+      DPLL_CFGCR0 : Registers.Registers_Index;
+      DPLL_CFGCR1 : Registers.Registers_Index;
+      DPLL_SSC    : Registers.Registers_Index;
+   end record;
+   type PLL_Regs_Array is array (Combo_DPLLs) of PLL_Regs_Record;
+   PLL_Regs : constant PLL_Regs_Array :=
+      PLL_Regs_Array'
+     (DPLL0 =>
+        (Registers.DPLL_0_ENABLE,
+         Registers.DPLL_0_CFGCR0,
+         Registers.DPLL_0_CFGCR1,
+         Registers.DPLL_0_SSC),
+      DPLL1 =>
+        (Registers.DPLL_1_ENABLE,
+         Registers.DPLL_1_CFGCR0,
+         Registers.DPLL_1_CFGCR1,
+         Registers.DPLL_1_SSC));
+
+   DPLL_ENABLE_PLL_ENABLE   : constant := 1 * 2 ** 31;
+   DPLL_ENABLE_PLL_LOCK     : constant := 1 * 2 ** 30;
+   DPLL_ENABLE_POWER_ENABLE : constant := 1 * 2 ** 27;
+   DPLL_ENABLE_POWER_STATE  : constant := 1 * 2 ** 26;
+   DPLL_SSC_DP              : constant := 16#200#;
+
+   procedure Encode_DCO (DCO_Integer, DCO_Fraction : out Word32; DCO : DCO_Range)
+   is
+      Refclk_Freq : Power_And_Clocks.Refclk_Range;
+      Enc_DCO : Int64;
+   begin
+      Power_And_Clocks.Get_Refclk (Refclk_Freq);
+
+      -- DPLL will auto-divide by 2 if refclk is 38.4 MHz
+      if Refclk_Freq = 38_400_000 then
+         Refclk_Freq := 19_200_000;
+      end if;
+
+      Enc_DCO := (DCO / 1_000) * (2 ** 15) / (Refclk_Freq / 1_000);
+      DCO_Integer := Word32 (Enc_DCO / (2 ** 15));
+      DCO_Fraction := Word32 (Enc_DCO) and 16#7fff#;
+   end Encode_DCO;
+
+   subtype PDiv_Range is Positive range 2 .. 7
+   with
+      Static_Predicate => (PDiv_Range in 2 | 3 | 5 | 7);
+
+   type Encoded_PDiv is new Positive range 1 .. 8
+   with
+      Static_Predicate => (Encoded_PDiv in 1 | 2 | 4 | 8);
+
+   function Encode_PDiv (PDiv : PDiv_Range) return Encoded_PDiv
+   is
+     (case PDiv is
+         when 2 => 2#0001#,
+         when 3 => 2#0010#,
+         when 5 => 2#0100#,
+         when 7 => 2#1000#);
+
+   subtype QDiv_Range is Positive range 1 .. 255;
+
+   type Encoded_QDiv is new Natural range 0 .. QDiv_Range'Last;
+
+   function Encode_QDiv (QDiv : QDiv_Range) return Encoded_QDiv
+   is
+     (Encoded_QDiv (QDiv));
+
+   function QDiv_Mode (QDiv : Encoded_QDiv) return Natural
+   is
+     (if QDiv <= 1 then 0 else 1);
+
+   subtype KDiv_Range is Positive range 1 .. 3;
+
+   type Encoded_KDiv is new Positive range 1 .. 4
+   with
+      Static_Predicate => (Encoded_KDiv in 1 | 2 | 4);
+
+   function Encode_KDiv (KDiv : KDiv_Range) return Encoded_KDiv
+   is
+     (case KDiv is
+         when 1 => 2#001#,
+         when 2 => 2#010#,
+         when 3 => 2#100#);
+
+   type PLL_Params is record
+      DCO_Integer    : Word32;
+      DCO_Fraction   : Word32;
+      PDiv           : Encoded_PDiv;
+      KDiv           : Encoded_KDiv;
+      QDiv           : Encoded_QDiv;
+   end record;
+
+   type DP_PLL_Params_Array is array (DP_Bandwidth) of PLL_Params;
+
+   PLL_Params_19_2MHz : constant DP_PLL_Params_Array := DP_PLL_Params_Array'
+     (DP_Bandwidth_5_4 =>
+        (DCO_Integer  => 16#1a5#,
+         DCO_Fraction => 16#7000#,
+         PDiv         => 2,
+         KDiv         => 1,
+         QDiv         => 0),
+      DP_Bandwidth_2_7 =>
+        (DCO_Integer  => 16#1a5#,
+         DCO_Fraction => 16#7000#,
+         PDiv         => 2,
+         KDiv         => 2,
+         QDiv         => 0),
+      DP_Bandwidth_1_62 =>
+        (DCO_Integer  => 16#1a5#,
+         DCO_Fraction => 16#7000#,
+         PDiv         => 4,
+         KDiv         => 2,
+         QDiv         => 0));
+
+   PLL_Params_24MHz : constant DP_PLL_Params_Array := DP_PLL_Params_Array'
+     (DP_Bandwidth_5_4 =>
+        (DCO_Integer  => 16#151#,
+         DCO_Fraction => 16#4000#,
+         PDiv         => 2,
+         KDiv         => 1,
+         QDiv         => 0),
+      DP_Bandwidth_2_7 =>
+        (DCO_Integer  => 16#151#,
+         DCO_Fraction => 16#4000#,
+         PDiv         => 2,
+         KDiv         => 2,
+         QDiv         => 0),
+      DP_Bandwidth_1_62 =>
+        (DCO_Integer  => 16#151#,
+         DCO_Fraction => 16#4000#,
+         PDiv         => 4,
+         KDiv         => 2,
+         QDiv         => 0));
+
+   procedure Calc_DP_PLL_Dividers
+     (Bandwidth : in     DP_Bandwidth;
+      Params    :    out PLL_Params)
+   is
+      Refclk : Frequency_Type;
+   begin
+      Power_And_Clocks.Get_Refclk (Refclk);
+      if Refclk = 24_000_000 then
+         Params := PLL_Params_24MHz (Bandwidth);
+      else
+         Params := PLL_Params_19_2MHz (Bandwidth);
+      end if;
+   end Calc_DP_PLL_Dividers;
+
+   procedure Calc_HDMI_PLL_Dividers
+     (Dotclock : in     Frequency_Type;
+      Params   :    out PLL_Params;
+      Success  :    out Boolean)
+   is
+      subtype Div_Range is Pos64 range 2 .. 102;
+      subtype Candidate_Index is Positive range 1 .. 46;
+      type Candidate_Array is array (Candidate_Index) of Div_Range;
+      Candidates : constant Candidate_Array := Candidate_Array'
+        (2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30, 32, 36, 40, 42, 44,
+         48, 50, 52, 54, 56, 60, 64, 66, 68, 70, 72, 76, 78, 80, 84, 88, 90,
+         92, 96, 98, 100, 102, 3, 5, 7, 9, 15, 21);
+      AFE_Clk : constant Int64 := Dotclock * 5;
+      DCO_Mid : constant Int64 := (DCO_Range'First + DCO_Range'Last) / 2;
+      Best_DCO_Centrality : Int64 := Frequency_Type'Last;
+      Best_Div_Index : Candidate_Index := Candidate_Index'First;
+      Best_Div : Div_Range;
+      Best_DCO : DCO_Range := DCO_Range'First;
+      DCO_Found : Boolean := False;
+      PDiv : PDiv_Range;
+      QDiv : QDiv_Range;
+      KDiv : KDiv_Range;
+   begin
+      for Index in Candidate_Index loop
+         declare
+            DCO : constant Int64 := AFE_Clk * Candidates(Index);
+            DCO_Centrality : constant Int64 := abs (DCO - DCO_Mid);
+         begin
+            if DCO <= DCO_Range'Last and DCO >= DCO_Range'First and
+               DCO_Centrality < Best_DCO_Centrality
+            then
+               DCO_Found := True;
+               Best_DCO_Centrality := DCO_Centrality;
+               Best_Div_Index := Index;
+               Best_DCO := DCO;
+            end if;
+         end;
+      end loop;
+
+      if not DCO_Found then
+         Params := (DCO_Integer => 0,
+                    DCO_Fraction => 0,
+                    PDiv => Encoded_PDiv'First_Valid,
+                    KDiv => Encoded_KDiv'First_Valid,
+                    QDiv => Encoded_QDiv'First_Valid);
+         Success := False;
+         return;
+      end if;
+
+      Best_Div := Candidates (Best_Div_Index);
+      if Best_Div mod 2 = 0 then
+         if Best_Div = 2 then
+            PDiv := 2;
+            QDiv := 1;
+            KDiv := 1;
+         elsif Best_Div mod 4 = 0 then
+            PDiv := 2;
+            QDiv := QDiv_Range (Best_Div / 4);
+            KDiv := 2;
+         elsif Best_Div mod 6 = 0 then
+            PDiv := 3;
+            QDiv := QDiv_Range (Best_Div / 6);
+            KDiv := 2;
+         elsif Best_Div mod 5 = 0 then
+            PDiv := 5;
+            QDiv := QDiv_Range (Best_Div / 10);
+            KDiv := 2;
+         else
+            -- Use `else`, not `elsif`, to prove we covered all cases.
+            pragma Assert (Best_Div mod 14 = 0);
+            PDiv := 7;
+            QDiv := QDiv_Range (Best_Div / 14);
+            KDiv := 2;
+         end if;
+      else
+         if Best_Div = 3 or Best_Div = 5 or Best_Div = 7 then
+            PDiv := PDiv_Range (Best_Div);
+            QDiv := 1;
+            KDiv := 1;
+         else
+            pragma Assert (Best_Div mod 3 = 0);
+            PDiv := PDiv_Range (Best_Div / 3);
+            QDiv := 1;
+            KDiv := 3;
+         end if;
+      end if;
+
+      -- PRM: If Kdiv != 2, then Qdiv must be 1. Else Qdiv can be 1 to 255.
+      pragma Assert (if KDiv /= 2 then QDiv = 1);
+
+      Params.KDiv := Encode_KDiv (KDiv);
+      Params.PDiv := Encode_PDiv (PDiv);
+      Params.QDiv := Encode_QDiv (QDiv);
+      Encode_DCO (Params.DCO_Integer, Params.DCO_Fraction, Best_DCO);
+
+      Success := True;
+   end Calc_HDMI_PLL_Dividers;
+
+   procedure On
+     (PLL      : in     Combo_DPLLs;
+      Port_Cfg : in     Port_Config;
+      Success  :    out Boolean)
+   is
+      Params : PLL_Params;
+      Refclk : Frequency_Type;
+   begin
+      if Port_Cfg.Display = DP then
+         Calc_DP_PLL_Dividers (Port_Cfg.DP.Bandwidth, Params);
+         Success := True;
+      else
+         if Port_Cfg.Mode.Dotclock not in HDMI_Clock_Range then
+            Debug.Put_Line ("Unsupported HDMI Pixel clock");
+            Success := False;
+            return;
+         end if;
+         declare
+            Color_Depth : constant Int64 := Port_Cfg.Mode.BPC * 3;
+            Pll_Freq : constant Frequency_Type := Color_Depth * Port_Cfg.Mode.Dotclock / 24;
+         begin
+            Calc_HDMI_PLL_Dividers (Pll_Freq, Params, Success);
+         end;
+      end if;
+
+      if not Success then
+         Debug.Put_Line ("Failed to calculate PLL dividers!");
+         return;
+      end if;
+
+      -- Display WA #22010492432: ehl, tgl, adl-p
+      -- Program half of the nominal DCO divider fraction value
+      -- for 38.4 MHz refclk
+      Power_And_Clocks.Get_Refclk (Refclk);
+      if Refclk = 38_400_000 then
+         Params.DCO_Fraction := Shift_Right (Params.DCO_Fraction, 1);
+      end if;
+
+      Registers.Set_Mask
+        (Register => PLL_Regs (PLL).DPLL_ENABLE,
+         Mask     => DPLL_ENABLE_POWER_ENABLE);
+      Registers.Wait_Set_Mask
+        (Register => PLL_Regs (PLL).DPLL_ENABLE,
+         Mask     => DPLL_ENABLE_POWER_STATE,
+         Success  => Success);
+
+      if not Success then
+         Debug.Put_Line ("Failed to enable PLL!");
+         return;
+      end if;
+
+      -- Configure DPLL_SSC
+      Registers.Write
+        (Register => PLL_Regs (PLL).DPLL_SSC,
+         Value    => (if Port_Cfg.Display = DP then DPLL_SSC_DP else 0));
+
+      Registers.Write
+        (Register => PLL_Regs (PLL).DPLL_CFGCR0,
+         Value => Shift_Left (Params.DCO_Fraction, 10) or
+                  Params.DCO_Integer);
+
+      Registers.Write
+        (Register => PLL_Regs (PLL).DPLL_CFGCR1,
+         Value => Shift_Left (Word32 (Params.QDiv), 10) or
+                  Shift_Left (Word32 (QDiv_Mode (Params.QDiv)), 9) or
+                  Shift_left (Word32 (Params.KDiv), 6) or
+                  Shift_Left (Word32 (Params.PDiv), 2));
+      Registers.Posting_Read(PLL_Regs (PLL).DPLL_CFGCR1);
+
+      -- Enable DPLL
+      Registers.Set_Mask
+        (Register => PLL_Regs (PLL).DPLL_ENABLE,
+         Mask     => DPLL_ENABLE_PLL_ENABLE);
+      -- Wait for PLL Lock status
+      Registers.Wait_Set_Mask
+        (Register => PLL_Regs (PLL).DPLL_ENABLE,
+         Mask     => DPLL_ENABLE_PLL_LOCK,
+         Success  => Success);
+   end On;
+
+   procedure Free (PLL : Combo_DPLLs)
+   is
+   begin
+      Registers.Unset_Mask
+        (Register => PLL_Regs (PLL).DPLL_ENABLE,
+         Mask     => DPLL_ENABLE_PLL_ENABLE);
+      Registers.Wait_Unset_Mask
+        (Register => PLL_Regs (PLL).DPLL_ENABLE,
+         Mask     => DPLL_ENABLE_PLL_LOCK);
+
+      Registers.Unset_Mask
+        (Register => PLL_Regs (PLL).DPLL_ENABLE,
+         Mask     => DPLL_ENABLE_POWER_ENABLE);
+      Registers.Wait_Unset_Mask
+        (Register => PLL_Regs (PLL).DPLL_ENABLE,
+         Mask     => DPLL_ENABLE_POWER_STATE);
+   end Free;
+
+   procedure All_Off is
+   begin
+      for PLL in Combo_DPLLs loop
+         Free (PLL);
+      end loop;
+   end All_Off;
+
+end HW.GFX.GMA.PLLs.Combo_Phy;