gma: Add Intel i945 (Gen3) graphics init support

Add i945G (desktop) and i945GM (mobile) generation support, modeled
after the existing G45 generation code with hardware-specific
adaptations based on the Linux i915 DRM driver and coreboot.

Key hardware differences from G45 (Gen4):
- GTT on separate PCI BAR3 (not within BAR0)
- Simple 32-bit GTT PTEs (addr[31:12] | valid[0])
- No DSPSURF register (uses DSPADDR/DSPLINOFF instead)
- Gen3 fence registers: 32-bit at split 0x2000/0x3000 addresses
- Different PLL limits (VCO 1400-2800 MHz, 96 MHz refclk)
- SDVO multiplier in DPLL register bits[7:4]
- LVDS restricted to Pipe B (pre-i965 requirement)
- CDClk: fixed 400 MHz (desktop) or GCFGC-based (mobile)
- No HDMI/DP, only VGA, LVDS, and SDVO outputs
- PCI IDs: 0x2772 (I945G), 0x27a2/0x27ae (I945GM)

TESTED with thinkpad x60: LVDS & VGA works with a linear framebuffer.

Change-Id: Ib67b3d0ee5e06df427869dce4db926ba57a80fd8
Signed-off-by: Arthur Heymans <arthur@aheymans.xyz>
Reviewed-on: https://review.sourcearcade.org/c/libgfxinit/+/476
Reviewed-by: Nico Huber <nico.h@gmx.de>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Tested-by: Nico Huber <nico.h@gmx.de>
diff --git a/common/hw-gfx-gma-pipe_setup.adb b/common/hw-gfx-gma-pipe_setup.adb
index 82f5222..03aaf12 100644
--- a/common/hw-gfx-gma-pipe_setup.adb
+++ b/common/hw-gfx-gma-pipe_setup.adb
@@ -27,6 +27,8 @@
    DSPCNTR_ENABLE                      : constant :=  1 * 2 ** 31;
    DSPCNTR_GAMMA_CORRECTION            : constant :=  1 * 2 ** 30;
    DSPCNTR_FORMAT_MASK                 : constant := 15 * 2 ** 26;
+   DSPCNTR_PIPE_SEL_MASK               : constant :=  3 * 2 ** 24;
+   DSPCNTR_PIPE_B_SELECT               : constant :=  1 * 2 ** 24;
    DSPCNTR_DISABLE_TRICKLE_FEED        : constant :=  1 * 2 ** 14;
    DSPCNTR_TILED_SURFACE_LINEAR        : constant :=  0 * 2 ** 10;
    DSPCNTR_TILED_SURFACE_X_TILED       : constant :=  1 * 2 ** 10;
@@ -36,10 +38,14 @@
       X_Tiled  => DSPCNTR_TILED_SURFACE_X_TILED,
       Y_Tiled  => 0); -- unsupported
 
+   function DSPCNTR_PIPE_SEL (Pipe : Pipe_Index) return Word32 is
+     (if Pipe = Secondary then DSPCNTR_PIPE_B_SELECT else 0);
+
    DSPCNTR_MASK : constant Word32 :=
       DSPCNTR_ENABLE or
       DSPCNTR_GAMMA_CORRECTION or
       DSPCNTR_FORMAT_MASK or
+      DSPCNTR_PIPE_SEL_MASK or
       DSPCNTR_DISABLE_TRICKLE_FEED or
       DSPCNTR_TILED_SURFACE_X_TILED;
 
@@ -212,7 +218,7 @@
    is
       -- FIXME: setup correct format, based on framebuffer RGB format
       Format : constant Word32 := 6 * 2 ** 26;
-      PRI : Word32 := DSPCNTR_ENABLE or Format;
+      PRI : Word32 := Format;
    begin
       pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
 
@@ -245,11 +251,19 @@
             Registers.Write (Controller.PLANE_SURF, FB.Offset and 16#ffff_f000#);
          end;
       else
+         if Config.Has_DSPCNTR_Pipe_Select then
+            PRI := PRI or DSPCNTR_PIPE_SEL (Controller.Pipe);
+         end if;
          if Config.Disable_Trickle_Feed then
             PRI := PRI or DSPCNTR_DISABLE_TRICKLE_FEED;
          end if;
-         -- for now, just disable gamma LUT (can't do anything
-         -- useful without colorimetry information from display)
+
+         -- Write DSPCNTR *without* the enable bit first.  On pre-SKL
+         -- hardware the control register self-arms when the plane
+         -- transitions from disabled to enabled, latching whatever
+         -- stride/size/offset values happen to be in the registers at
+         -- that moment.  Programming format, pipe-select, and trickle-
+         -- feed now avoids a glitch with stale geometry values.
          Registers.Unset_And_Set_Mask
             (Register   => Controller.DSPCNTR,
              Mask_Unset => DSPCNTR_MASK,
@@ -257,6 +271,16 @@
 
          Registers.Write
            (Controller.DSPSTRIDE, Word32 (Pixel_To_Bytes (FB.Stride, FB)));
+
+         -- Gen3 (i945): program DSPSIZE and DSPPOS before the surface
+         -- address write that arms the double-buffered plane registers.
+         if Config.Gen_I945 then
+            Registers.Write
+              (Controller.DSPSIZE,
+               Encode (Rotated_Width (FB), Rotated_Height (FB)));
+            Registers.Write (Controller.DSPPOS, 16#0000_0000#);
+         end if;
+
          if Config.Has_DSP_Linoff and then FB.Tiling = Linear then
             pragma Assert_And_Cut (True);
             declare
@@ -265,19 +289,33 @@
             begin
                Registers.Write
                  (Register => Controller.DSPLINOFF,
-                  Value    => Word32 (Pixel_To_Bytes (Linear_Offset, FB)));
+                  Value    => (if Config.Has_DSPSURF
+                               then Word32 (Pixel_To_Bytes (Linear_Offset, FB))
+                               else (FB.Offset and 16#ffff_f000#) or
+                                    Word32 (Pixel_To_Bytes (Linear_Offset, FB))));
                Registers.Write (Controller.DSPTILEOFF, 0);
             end;
          else
             if Config.Has_DSP_Linoff then
-               Registers.Write (Controller.DSPLINOFF, 0);
+               Registers.Write (Controller.DSPLINOFF,
+                  (if Config.Has_DSPSURF then 0
+                   else FB.Offset and 16#ffff_f000#));
             end if;
             Registers.Write
               (Register => Controller.DSPTILEOFF,
                Value    => Shift_Left (Word32 (FB.Start_Y), 16) or
                            Word32 (FB.Start_X));
          end if;
-         Registers.Write (Controller.DSPSURF, FB.Offset and 16#ffff_f000#);
+         if Config.Has_DSPSURF then
+            Registers.Write (Controller.DSPSURF, FB.Offset and 16#ffff_f000#);
+         end if;
+
+         -- Now enable the plane.  All geometry registers are in place,
+         -- so the self-arm latches correct values.
+         Registers.Write
+           (Register => Controller.DSPCNTR,
+            Value    => DSPCNTR_ENABLE or PRI or
+                        DSPCNTR_TILED_SURFACE (FB.Tiling));
       end if;
    end Setup_Hires_Plane;
 
@@ -579,11 +617,17 @@
    is
       Used_For_Secondary : Boolean;
    begin
-      Registers.Is_Set_Mask
-        (Register => Registers.GMCH_PFIT_CONTROL,
-         Mask     => GMCH_PFIT_CONTROL_SELECT_PIPE_B,
-         Result   => Used_For_Secondary);
-      Pipe := (if Used_For_Secondary then Secondary else Primary);
+      if Config.Gen_I945 then
+         -- Gen3: panel fitter is hardwired to Pipe B (Secondary).
+         -- The PFIT_PIPE field (bits 30:29) does not exist on Gen3.
+         Pipe := Secondary;
+      else
+         Registers.Is_Set_Mask
+           (Register => Registers.GMCH_PFIT_CONTROL,
+            Mask     => GMCH_PFIT_CONTROL_SELECT_PIPE_B,
+            Result   => Used_For_Secondary);
+         Pipe := (if Used_For_Secondary then Secondary else Primary);
+      end if;
    end;
 
    procedure Panel_Fitter_Off (Controller : Controller_Type)
@@ -604,7 +648,10 @@
       elsif Config.Has_GMCH_PFIT_CONTROL then
          Gmch_Panel_Fitter_Pipe (Pipe_Using_PF);
          if Pipe_Using_PF = Controller.Pipe then
-            Registers.Unset_Mask (Registers.GMCH_PFIT_CONTROL, PF_CTRL_ENABLE);
+            -- Write 0 to clear all bits (enable, scaling mode, auto-scale,
+            -- interpolation).  Just clearing the enable bit can leave stale
+            -- Gen3 auto-scale bits that confuse the hardware.
+            Registers.Write (Registers.GMCH_PFIT_CONTROL, 16#0000_0000#);
          end if;
       else
          Registers.Unset_Mask (Controller.PF_CTRL, PF_CTRL_ENABLE);
@@ -758,7 +805,7 @@
 
       Legacy_VGA_Off;
 
-      for Pipe in Pipe_Index loop
+      for Pipe in Pipe_Index range Pipe_Index'First .. Config.Max_Pipe loop
          Planes_Off (Controllers (Pipe), Cursors (Pipe));
          Transcoder.Off (Pipe);
          Panel_Fitter_Off (Controllers (Pipe));