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-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;