gma g45: Read CDClk and calculate dot-clock limits

Numbers are taken from `intel_cdclk.c` of Linux' i915 driver.

We introduce three new procedures to the `Power_And_Clocks` interface:

  o Limit_Dotclocks() limits the dot clocks of all pipe configs
    according to the maximum supported CDClk. It also reports if
    CDClk has to be switched for these configs.

  o Update_CDClk() performs the CDClk switch if necessary. It may
    further limit the dot clocks if the switch didn't succeed.

  o Enable_CDClk() ensures that the CDClk is running. This may be
    necessary to probe for DP displays when no pipes are active.

The latter two are no-ops for G45, as the CDClk runs at a fixed rate.
Dot clocks are limited to 90% of CDClk.

Change-Id: Ie50c0f8f51b3a0a6ed58c6461069c556cc92f51e
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.coreboot.org/c/libgfxinit/+/35715
Reviewed-by: Matt DeVillier <matt.devillier@gmail.com>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
diff --git a/common/g45/hw-gfx-gma-power_and_clocks.adb b/common/g45/hw-gfx-gma-power_and_clocks.adb
index d6ee9f9..0ecf09b 100644
--- a/common/g45/hw-gfx-gma-power_and_clocks.adb
+++ b/common/g45/hw-gfx-gma-power_and_clocks.adb
@@ -1,5 +1,6 @@
 --
 -- Copyright (C) 2016 secunet Security Networks AG
+-- Copyright (C) 2019 Nico Huber <nico.h@gmx.de>
 --
 -- 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
@@ -26,8 +27,91 @@
    CLKCFG_FSB_1067   : constant Frequency_Type := 266_666_666;
    CLKCFG_FSB_1333   : constant Frequency_Type := 333_333_333;
 
+   type Div_Array is array (0 .. 7) of Pos64;
+
+   procedure Get_VCO (VCO : out Int64; Divisors : out Div_Array)
+   is
+      G45_3200 : constant Div_Array := (12, 10,  8,  7,  5, 16, others => 1);
+      G45_4000 : constant Div_Array := (14, 12, 10,  8,  6, 20, others => 1);
+      G45_4800 : constant Div_Array := (20, 14, 12, 10,  8, 24, others => 1);
+      G45_5333 : constant Div_Array := (20, 16, 12, 12,  8, 28, others => 1);
+      G45_Divs : constant array (Natural range 0 .. 7) of Div_Array :=
+        (G45_3200, G45_4000, G45_5333, G45_4800, others => (others => 1));
+
+      GM45_2667 : constant Div_Array := (12,  8, others => 1);
+      GM45_3200 : constant Div_Array := (14, 10, others => 1);
+      GM45_4000 : constant Div_Array := (18, 12, others => 1);
+      GM45_5333 : constant Div_Array := (24, 16, others => 1);
+      GM45_Divs : constant array (Natural range 0 .. 7) of Div_Array :=
+        (0 => GM45_3200, 1 => GM45_4000, 2 => GM45_5333, 4 => GM45_2667,
+         others => (others => 1));
+
+      HPLLVCO : Word32;
+      VCO_Sel : Natural range 0 .. 7;
+   begin
+      if Config.Has_GMCH_Mobile_VCO then
+         Registers.Read (Registers.GMCH_HPLLVCO_MOBILE, HPLLVCO);
+         VCO_Sel := Natural (HPLLVCO and 7);
+         VCO :=
+           (case VCO_Sel is
+               when 0 => 3_200_000_000,
+               when 1 => 4_000_000_000,
+               when 2 => 5_333_333_333,
+               --when 3 => 6_400_000_000,
+               when 4 => 2_666_666_667,
+               --when 5 => 4_266_666_667,
+               when others => 0);
+         Divisors := GM45_Divs (VCO_Sel);
+      else
+         Registers.Read (Registers.GMCH_HPLLVCO, HPLLVCO);
+         VCO_Sel := Natural (HPLLVCO and 7);
+         VCO :=
+           (case VCO_Sel is
+               when 0 => 3_200_000_000,
+               when 1 => 4_000_000_000,
+               when 2 => 5_333_333_333,
+               when 3 => 4_800_000_000,
+               when others => 0);
+         Divisors := G45_Divs (VCO_Sel);
+      end if;
+   end Get_VCO;
+
+   procedure Get_CDClk (CDClk : out Config.CDClk_Range)
+   is
+      use type HW.Word16;
+
+      Tmp_Clk : Int64 := 0;
+
+      VCO : Int64;
+      Divisors : Div_Array;
+
+      GCFGC : Word16;
+      CDClk_Sel : Natural range 0 .. 7;
+   begin
+      if PCI_Usable then
+         Get_VCO (VCO, Divisors);
+         PCI_Read16 (GCFGC, 16#f0#);
+         if Config.Has_GMCH_Mobile_VCO then
+            CDClk_Sel := Natural (Shift_Right (GCFGC, 12) and 1);
+         else
+            CDClk_Sel := Natural (Shift_Right (GCFGC, 4) and 7);
+         end if;
+         Tmp_Clk := VCO / Divisors (CDClk_Sel);
+      end if;
+
+      if Tmp_Clk in Config.CDClk_Range then
+         CDClk := Tmp_Clk;
+      else
+         if Config.Has_GMCH_Mobile_VCO then
+            CDClk := 5_333_333_333 / 24;
+         else
+            CDClk := 5_333_333_333 / 28;
+         end if;
+      end if;
+   end Get_CDClk;
+
    -- The Raw Freq is 1/4 of the FSB freq
-   procedure Initialize
+   procedure Get_Raw_Clock (Raw_Clock : out Frequency_Type)
    is
       CLK_CFG : Word32;
       type Freq_Sel is new Natural range 0 .. 7;
@@ -36,15 +120,35 @@
         (Register => Registers.GMCH_CLKCFG,
          Value => CLK_CFG);
       case Freq_Sel (CLK_CFG and FSB_FREQ_SEL_MASK) is
-         when 0      => Config.Raw_Clock := CLKCFG_FSB_1067;
-         when 1      => Config.Raw_Clock := CLKCFG_FSB_533;
-         when 2      => Config.Raw_Clock := CLKCFG_FSB_800;
-         when 3      => Config.Raw_Clock := CLKCFG_FSB_667;
-         when 4      => Config.Raw_Clock := CLKCFG_FSB_1333;
-         when 5      => Config.Raw_Clock := CLKCFG_FSB_400;
-         when 6      => Config.Raw_Clock := CLKCFG_FSB_1067;
-         when 7      => Config.Raw_Clock := CLKCFG_FSB_1333;
+         when 0      => Raw_Clock := CLKCFG_FSB_1067;
+         when 1      => Raw_Clock := CLKCFG_FSB_533;
+         when 2      => Raw_Clock := CLKCFG_FSB_800;
+         when 3      => Raw_Clock := CLKCFG_FSB_667;
+         when 4      => Raw_Clock := CLKCFG_FSB_1333;
+         when 5      => Raw_Clock := CLKCFG_FSB_400;
+         when 6      => Raw_Clock := CLKCFG_FSB_1067;
+         when 7      => Raw_Clock := CLKCFG_FSB_1333;
       end case;
+   end Get_Raw_Clock;
+
+   procedure Initialize
+   is
+      CDClk : Config.CDClk_Range;
+   begin
+      Get_CDClk (CDClk);
+      Config.CDClk := CDClk;
+      Config.Max_CDClk := CDClk;
+
+      Get_Raw_Clock (Config.Raw_Clock);
    end Initialize;
 
+   procedure Limit_Dotclocks
+     (Configs        : in out Pipe_Configs;
+      CDClk_Switch   :    out Boolean)
+   is
+   begin
+      Config_Helpers.Limit_Dotclocks (Configs, Config.CDClk * 90 / 100);
+      CDClk_Switch := False;
+   end Limit_Dotclocks;
+
 end HW.GFX.GMA.Power_And_Clocks;