gma g45: Add support for Intel GM965 (Crestline)

Add GM965 as a new CPU type under the G45 generation. GM965 shares
the GMCH display architecture with G45/GM45 (no PCH, same connector
registers) but differs in PLL limits, VCO/CDClk tables, and register
field encoding.

Key differences from G45/GM45:
 - PLL limits: Uses i9xx limits (VCO 1.4-2.8 GHz) instead of g4x
   limits. Add I9XX_LVDS_Limits; refactor Calculate_Clock_Parameters
   to take Limits as a parameter, with a new Select_Limits function.
 - VCO: Uses Crestline (CL) VCO tables with different frequencies
   and divisors than GM45's Cantiga (CTG) tables.
 - CDClk: GCFGC register decoding uses bits 12:8 minus 1 (3 possible
   divisor selections), unlike GM45's single bit 12.
 - No native DisplayPort (SDVO B/C only).
 - No HD Audio (G4X_AUD_VID_DID reads as 0).
 - Has integrated LVDS transmitter (mobile platform).
 - PCI IDs: 0x2a02 (I965_GM), 0x2a12 (I965_GME).

All implementation details cross-referenced against the Linux kernel
i915 driver (intel_dpll.c, intel_cdclk.c, intel_display_device.c).

Change-Id: I0d5d698cc1c2aa84778f0fc6c2752cb5ce4f1cb2
Signed-off-by: Arthur Heymans <arthur@aheymans.xyz>
Reviewed-on: https://review.sourcearcade.org/c/libgfxinit/+/499
Reviewed-by: Nico Huber <nico.h@gmx.de>
Tested-by: Nico Huber <nico.h@gmx.de>
diff --git a/common/g45/hw-gfx-gma-plls.adb b/common/g45/hw-gfx-gma-plls.adb
index 5db76d6..fcc08ad 100644
--- a/common/g45/hw-gfx-gma-plls.adb
+++ b/common/g45/hw-gfx-gma-plls.adb
@@ -140,6 +140,18 @@
       P2_Fast      =>   5,           P2_Slow   =>  10,
       P2_Threshold =>   165_000_000,
       VCO_Lower    => 1_750_000_000, VCO_Upper => 3_500_000_000);
+   -- I9XX limits are used by GM965 (Crestline) and older non-G4x chips.
+   I9XX_LVDS_Limits : constant Limits_Type := Limits_Type'
+     (N_Lower      =>   3,           N_Upper   =>   8,
+      M_Lower      =>  70,           M_Upper   => 120,
+      M1_Lower     =>  10,           M1_Upper  =>  20,
+      M2_Lower     =>   5,           M2_Upper  =>   9,
+      P_Lower      =>   7,           P_Upper   =>  98,
+      P1_Lower     =>   1,           P1_Upper  =>   8,
+      P2_Fast      =>   7,           P2_Slow   =>  14,
+      P2_Threshold => 112_000_000,
+      VCO_Lower    => 1_400_000_000, VCO_Upper => 2_800_000_000);
+   -- The All_Other/I9XX_SDVO limits match Linux' intel_limits_i9xx_sdvo.
    All_Other_Limits : constant Limits_Type := Limits_Type'
      (N_Lower      =>   3,           N_Upper   =>   8,
       M_Lower      =>  70,           M_Upper   => 120,
@@ -279,9 +291,9 @@
    end Verify_Parameters;
 
    procedure Calculate_Clock_Parameters
-     (Display         : in     Display_Type;
-      Target_Dotclock : in     Clock_Range;
+     (Target_Dotclock : in     Clock_Range;
       Reference_Clock : in     Clock_Range;
+      Limits          : in     Limits_Type;
       Best_Clock      :    out Clock_Type;
       Valid           :    out Boolean)
    with
@@ -289,17 +301,6 @@
      Pre => True,
      Post => True
    is
-      Limits : constant Limits_Type :=
-      (case Display is
-          when LVDS   =>
-             (if Target_Dotclock >= Config.LVDS_Dual_Threshold then
-                LVDS_Dual_Limits
-              else
-                LVDS_Single_Limits),
-          when HDMI   => HDMI_Analog_Limits,
-          when VGA    => HDMI_Analog_Limits,
-          when others => All_Other_Limits); --TODO add SDVO type, needs other limits
-
       P2               : P2_Range;
       Best_Delta       : Int64 := Int64'Last;
       Current_Delta    : Int64;
@@ -420,6 +421,34 @@
                      Shift_Left (Encoded_P1, DPLL_P1_DIVIDER_SHIFT));
    end Program_DPLL;
 
+   function Select_Limits
+     (Display         : Display_Type;
+      Target_Dotclock : Clock_Range)
+      return Limits_Type
+   is
+   begin
+      if Config.GMCH_GM965 then
+         -- i9xx PLL limits for GM965 (Crestline)
+         case Display is
+            when LVDS   => return I9XX_LVDS_Limits;
+            when others => return All_Other_Limits;
+         end case;
+      else
+         -- G4x PLL limits for G45/GM45
+         case Display is
+            when LVDS   =>
+               if Target_Dotclock >= Config.LVDS_Dual_Threshold then
+                  return LVDS_Dual_Limits;
+               else
+                  return LVDS_Single_Limits;
+               end if;
+            when HDMI   => return HDMI_Analog_Limits;
+            when VGA    => return HDMI_Analog_Limits;
+            when others => return All_Other_Limits;
+         end case;
+      end if;
+   end Select_Limits;
+
    procedure On
      (PLL      : in     T;
       Port_Cfg : in     Port_Config;
@@ -459,10 +488,10 @@
             end case;
          elsif Target_Clock <= 340_000_000 then
             Calculate_Clock_Parameters
-              (Display           => Port_Cfg.Display,
-               Target_Dotclock   => Target_Clock,
+              (Target_Dotclock   => Target_Clock,
                -- should be, but doesn't has to be always the same:
                Reference_Clock   => 96_000_000,
+               Limits            => Select_Limits (Port_Cfg.Display, Target_Clock),
                Best_Clock        => Clk,
                Valid             => Success);
          else
diff --git a/common/g45/hw-gfx-gma-port_detect.adb b/common/g45/hw-gfx-gma-port_detect.adb
index 45ed2dd..6fdb527 100644
--- a/common/g45/hw-gfx-gma-port_detect.adb
+++ b/common/g45/hw-gfx-gma-port_detect.adb
@@ -74,25 +74,27 @@
 
    begin
       Config.Valid_Port (Analog) := True;
-      Config.Valid_Port (LVDS)   := Config.GMCH_GM45;
-      for HDMI_Port in GMCH_HDMI_Port loop
-         Registers.Is_Set_Mask
-           (Register => GMCH_HDMI (HDMI_Port),
-            Mask     => PORT_DETECTED,
-            Result   => Detected);
-         Config.Valid_Port (To_HDMI_Port (HDMI_Port)) := Detected;
-         hotplug_mask_set := hotplug_mask_set or
-           (if Detected then HDMI_PORT_HOTPLUG_EN (HDMI_Port) else 0);
-      end loop;
-      for DP_Port in GMCH_DP_Port loop
-         Registers.Is_Set_Mask
-           (Register => GMCH_DP (DP_Port),
-            Mask     => PORT_DETECTED,
-            Result   => Detected);
-         Config.Valid_Port (To_DP_Port (DP_Port)) := Detected;
-         hotplug_mask_set := hotplug_mask_set or
-           (if Detected then DP_PORT_HOTPLUG_EN (DP_Port) else 0);
-      end loop;
+      Config.Valid_Port (LVDS)   := Config.GMCH_GM45 or Config.GMCH_GM965;
+      if not Config.GMCH_GM965 then
+         for HDMI_Port in GMCH_HDMI_Port loop
+            Registers.Is_Set_Mask
+              (Register => GMCH_HDMI (HDMI_Port),
+               Mask     => PORT_DETECTED,
+               Result   => Detected);
+            Config.Valid_Port (To_HDMI_Port (HDMI_Port)) := Detected;
+            hotplug_mask_set := hotplug_mask_set or
+              (if Detected then HDMI_PORT_HOTPLUG_EN (HDMI_Port) else 0);
+         end loop;
+         for DP_Port in GMCH_DP_Port loop
+            Registers.Is_Set_Mask
+              (Register => GMCH_DP (DP_Port),
+               Mask     => PORT_DETECTED,
+               Result   => Detected);
+            Config.Valid_Port (To_DP_Port (DP_Port)) := Detected;
+            hotplug_mask_set := hotplug_mask_set or
+              (if Detected then DP_PORT_HOTPLUG_EN (DP_Port) else 0);
+         end loop;
+      end if;
       Registers.Write
         (Register => Registers.PORT_HOTPLUG_EN,
          Value => hotplug_mask_set);
diff --git a/common/g45/hw-gfx-gma-power_and_clocks.adb b/common/g45/hw-gfx-gma-power_and_clocks.adb
index c0bb683..e082a6d 100644
--- a/common/g45/hw-gfx-gma-power_and_clocks.adb
+++ b/common/g45/hw-gfx-gma-power_and_clocks.adb
@@ -46,10 +46,32 @@
         (0 => GM45_3200, 1 => GM45_4000, 2 => GM45_5333, 4 => GM45_2667,
          others => (others => 1));
 
+      -- Crestline (GM965) VCO divisor tables from Linux' i965gm_get_cdclk
+      CL_3200 : constant Div_Array := (16, 10,  8, others => 1);
+      CL_4000 : constant Div_Array := (20, 12, 10, others => 1);
+      CL_5333 : constant Div_Array := (24, 16, 14, others => 1);
+      CL_Divs : constant array (Natural range 0 .. 7) of Div_Array :=
+        (0 => CL_3200, 1 => CL_4000, 2 => CL_5333,
+         others => (others => 1));
+
       HPLLVCO : Word32;
       VCO_Sel : Natural range 0 .. 7;
    begin
-      if Config.Has_GMCH_Mobile_VCO then
+      if Config.GMCH_GM965 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 => 3_333_333_333,
+               when 5 => 3_566_666_667,
+               when 6 => 4_266_666_667,
+               when others => 0);
+         Divisors := CL_Divs (VCO_Sel);
+      elsif Config.Has_GMCH_Mobile_VCO then
          Registers.Read (Registers.GMCH_HPLLVCO_MOBILE, HPLLVCO);
          VCO_Sel := Natural (HPLLVCO and 7);
          VCO :=
@@ -91,7 +113,14 @@
       if PCI_Usable then
          Get_VCO (VCO, Divisors);
          PCI_Read16 (GCFGC, 16#f0#);
-         if Config.Has_GMCH_Mobile_VCO then
+         if Config.GMCH_GM965 then
+            -- Linux i965gm_get_cdclk: cdclk_sel = ((tmp >> 8) & 0x1f) - 1
+            if (Shift_Right (GCFGC, 8) and 16#1f#) in 1 .. 3 then
+               CDClk_Sel := Natural (Shift_Right (GCFGC, 8) and 16#1f#) - 1;
+            else
+               CDClk_Sel := Div_Array'Last;
+            end if;
+         elsif 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);
@@ -102,7 +131,9 @@
       if Tmp_Clk in Config.CDClk_Range then
          CDClk := Tmp_Clk;
       else
-         if Config.Has_GMCH_Mobile_VCO then
+         if Config.GMCH_GM965 then
+            CDClk := 4_000_000_000 / 20;  -- 200 MHz
+         elsif Config.Has_GMCH_Mobile_VCO then
             CDClk := 5_333_333_333 / 24;
          else
             CDClk := 5_333_333_333 / 28;