gma i945: Add Pineview support

Add Pineview desktop and mobile variants to the existing i945/Gen3
code path. Pineview is close to i945, but Linux' i915 treats a few
pieces differently and those differences need explicit handling:

- add Pineview/Pineview_M CPU types and PCI IDs 0xa001/0xa011
- detect Pineview-M as a mobile Gen3 platform so LVDS is exposed
- decode Pineview GCFGC display clock selections for 133/167/200/
  267/333/444 MHz CDClk
- use the mobile/Pineview CLKCFG FSB encoding for HRAW clock
- keep the separate BAR3 Gen3 GTT layout, but use the i965/G33 style
  PTE encoding for Pineview's 36-bit DMA addresses
- add a separate Pineview DPLL calculation/programming path instead of
  widening the i945 PLL ranges; Pineview has a one-hot N encoding in
  the frequency-parameter (FP) register, no M1 divider, M = M2 + 2,
  and P1 at bit 15

The i945 PLL type ranges and calculation path remain unchanged.

Tested with:
  make clean && make DEBUG=1 cnf=configs/ironlake
  make clean && make DEBUG=1 cnf=configs/ironlake_edp
  make clean && make DEBUG=1 cnf=configs/i945

Tested with coreboot on Foxconn D41S.

Change-Id: I8bda5d27e1c071bd59bd9a8751dc1ea31b25614f
Signed-off-by: Arthur Heymans <arthur@aheymans.xyz>
Reviewed-on: https://review.sourcearcade.org/c/libgfxinit/+/515
Tested-by: Ada SPARK <gnatbot@sourcearcade.org>
Reviewed-by: Nico Huber <nico.h@gmx.de>
diff --git a/common/i945/hw-gfx-gma-plls.adb b/common/i945/hw-gfx-gma-plls.adb
index a5e272c..fde7f63 100644
--- a/common/i945/hw-gfx-gma-plls.adb
+++ b/common/i945/hw-gfx-gma-plls.adb
@@ -130,6 +130,75 @@
       P2_Threshold => 200_000_000,
       VCO_Lower    => 1_400_000_000, VCO_Upper => 2_800_000_000);
 
+   -- Pineview has a different Gen3 DPLL encoding. Its N divider is encoded
+   -- as a one-hot value in the frequency-parameter (FP) register, M1 is
+   -- reserved as 0, M = M2 + 2, and P1 starts at bit 15.
+
+   subtype PNV_N_Range     is Int64 range          3 ..          6;
+   subtype PNV_M_Range     is Int64 range          2 ..        256;
+   subtype PNV_M2_Range    is Int64 range          0 ..        254;
+   subtype PNV_P_Range     is Int64 range          5 ..        112;
+   subtype PNV_P1_Range    is Int64 range          1 ..          8;
+   subtype PNV_P2_Range    is Int64 range          5 ..         14;
+   subtype PNV_VCO_Range   is Int64 range 1700000000 .. 3500000000;
+
+   type PNV_Clock_Type is
+      record
+         N               : PNV_N_Range;
+         M2              : PNV_M2_Range;
+         P1              : PNV_P1_Range;
+         P2              : PNV_P2_Range;
+         M               : PNV_M_Range;
+         P               : PNV_P_Range;
+         VCO             : PNV_VCO_Range;
+         Reference_Clock : Clock_Range;
+         Dotclock        : Clock_Range;
+      end record;
+
+   Invalid_PNV_Clock : constant PNV_Clock_Type := PNV_Clock_Type'
+      (N               => PNV_N_Range'Last,
+       M2              => PNV_M2_Range'Last,
+       P1              => PNV_P1_Range'Last,
+       P2              => PNV_P2_Range'Last,
+       Reference_Clock => Clock_Range'Last,
+       M               => PNV_M_Range'Last,
+       P               => PNV_P_Range'Last,
+       VCO             => PNV_VCO_Range'Last,
+       Dotclock        => Clock_Range'Last);
+
+   type PNV_Limits_Type is
+      record
+         M_Lower      : PNV_M_Range;
+         M_Upper      : PNV_M_Range;
+         M2_Lower     : PNV_M2_Range;
+         M2_Upper     : PNV_M2_Range;
+         P_Lower      : PNV_P_Range;
+         P_Upper      : PNV_P_Range;
+         P1_Lower     : PNV_P1_Range;
+         P1_Upper     : PNV_P1_Range;
+         P2_Fast      : PNV_P2_Range;
+         P2_Slow      : PNV_P2_Range;
+         P2_Threshold : Clock_Range;
+      end record;
+
+   -- Pineview SDVO/DAC limits (from Linux pnv_limits_sdvo)
+   Pineview_SDVO_DAC_Limits : constant PNV_Limits_Type := PNV_Limits_Type'
+     (M_Lower      =>   2,           M_Upper   => 256,
+      M2_Lower     =>   0,           M2_Upper  => 254,
+      P_Lower      =>   5,           P_Upper   =>  80,
+      P1_Lower     =>   1,           P1_Upper  =>   8,
+      P2_Fast      =>   5,           P2_Slow   =>  10,
+      P2_Threshold => 200_000_000);
+
+   -- Pineview LVDS limits (from Linux pnv_limits_lvds)
+   Pineview_LVDS_Limits : constant PNV_Limits_Type := PNV_Limits_Type'
+     (M_Lower      =>   2,           M_Upper   => 256,
+      M2_Lower     =>   0,           M2_Upper  => 254,
+      P_Lower      =>   7,           P_Upper   => 112,
+      P1_Lower     =>   1,           P1_Upper  =>   8,
+      P2_Fast      =>  14,           P2_Slow   =>  14,
+      P2_Threshold => 112_000_000);
+
    ----------------------------------------------------------------------------
 
    type Regs is array (DPLLs) of Registers.Registers_Index;
@@ -139,7 +208,8 @@
    DPLL_VGA_MODE_DIS       : constant := 1 * 2 ** 28;
    DPLL_P2_10_OR_14        : constant := 0 * 2 ** 24;
    DPLL_P2_5_OR_7          : constant := 1 * 2 ** 24;
-   DPLL_P1_DIVIDER_SHIFT   : constant := 16;
+   DPLL_P1_DIVIDER_SHIFT          : constant := 16;
+   DPLL_P1_DIVIDER_SHIFT_PINEVIEW : constant := 15;
    DPLL_SDVOCLK            : constant := 2 * 2 ** 13;
 
    -- i945 does not use DPLL_PULSE_PHASE (bits 12:9, Gen4+ only)
@@ -348,6 +418,133 @@
 
    end Calculate_Clock_Parameters;
 
+   procedure Verify_Pineview_Parameters
+      (N               : in     PNV_N_Range;
+       M2              : in     PNV_M2_Range;
+       P1              : in     PNV_P1_Range;
+       P2              : in     PNV_P2_Range;
+       Reference_Clock : in     Clock_Range;
+       Current_Limits  : in     PNV_Limits_Type;
+       Result          :    out PNV_Clock_Type;
+       Valid           :    out Boolean)
+   with
+      Global => null,
+      Pre => True,
+      Post => True
+   is
+      M        : Int64;
+      P        : Int64;
+      VCO      : Int64;
+      Dotclock : Int64;
+   begin
+      pragma Debug (Debug_Clocks, Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+
+      M        := M2 + 2;
+      P        := P1 * P2;
+      VCO      := (Int64 (Reference_Clock) * M) / N;
+      Dotclock := VCO / P;
+
+      Valid :=
+         Current_Limits.P1_Lower  <= P1  and P1  <= Current_Limits.P1_Upper  and
+         (Current_Limits.P2_Fast   = P2   or P2   = Current_Limits.P2_Slow)  and
+         Current_Limits.P_Lower   <= P   and P   <= Current_Limits.P_Upper   and
+         Current_Limits.M2_Lower  <= M2  and M2  <= Current_Limits.M2_Upper  and
+         Current_Limits.M_Lower   <= M   and M   <= Current_Limits.M_Upper   and
+         PNV_VCO_Range'First      <= VCO and VCO <= PNV_VCO_Range'Last       and
+         Int64 (Clock_Range'First) <= Dotclock                               and
+         Dotclock <= Int64 (Clock_Range'Last);
+
+      if Valid
+      then
+         Result := PNV_Clock_Type'
+            (N               => N,
+             M2              => M2,
+             P1              => P1,
+             P2              => P2,
+             Reference_Clock => Reference_Clock,
+             M               => M,
+             P               => P,
+             VCO             => VCO,
+             Dotclock        => Clock_Range (Dotclock));
+      else
+         Result := Invalid_PNV_Clock;
+      end if;
+   end Verify_Pineview_Parameters;
+
+   procedure Calculate_Pineview_Clock_Parameters
+     (Display         : in     Display_Type;
+      Target_Dotclock : in     Clock_Range;
+      Reference_Clock : in     Clock_Range;
+      Best_Clock      :    out PNV_Clock_Type;
+      Valid           :    out Boolean)
+   with
+     Global => null,
+     Pre => True,
+     Post => True
+   is
+      Limits : constant PNV_Limits_Type :=
+      (case Display is
+          when LVDS   => Pineview_LVDS_Limits,
+          when others => Pineview_SDVO_DAC_Limits);
+
+      P2               : PNV_P2_Range;
+      Best_Delta       : Int64 := Int64'Last;
+      Current_Delta    : Int64;
+      Current_Clock    : PNV_Clock_Type;
+      Registers_Valid  : Boolean;
+   begin
+      pragma Debug (Debug_Clocks, Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+
+      Valid      := False;
+      Best_Clock := Invalid_PNV_Clock;
+
+      if Target_Dotclock <= Limits.P2_Threshold then
+         P2 := Limits.P2_Slow;
+      else
+         P2 := Limits.P2_Fast;
+      end if;
+
+      -- Use the newer libgfxinit search order (N, then M2, then P1) and
+      -- descending M2/P1 loops. Linux's Pineview code iterates M2 outermost
+      -- and ascending; this only affects ties between parameter sets with
+      -- the same delta.
+      for N in PNV_N_Range loop
+         for M2 in reverse PNV_M2_Range range Limits.M2_Lower .. Limits.M2_Upper
+         loop
+            pragma Loop_Invariant (True);
+            for P1 in reverse PNV_P1_Range range Limits.P1_Lower .. Limits.P1_Upper
+            loop
+               Verify_Pineview_Parameters
+                 (N               => N,
+                  M2              => M2,
+                  P1              => P1,
+                  P2              => P2,
+                  Reference_Clock => Reference_Clock,
+                  Current_Limits  => Limits,
+                  Result          => Current_Clock,
+                  Valid           => Registers_Valid);
+
+               if Registers_Valid
+               then
+                  if Current_Clock.Dotclock > Target_Dotclock
+                  then
+                     Current_Delta := Current_Clock.Dotclock - Target_Dotclock;
+                  else
+                     Current_Delta := Target_Dotclock - Current_Clock.Dotclock;
+                  end if;
+
+                  if Current_Delta < Best_Delta
+                  then
+                     Best_Delta := Current_Delta;
+                     Best_Clock := Current_Clock;
+                     Valid      := True;
+                  end if;
+               end if;
+            end loop;
+         end loop;
+      end loop;
+   end Calculate_Pineview_Clock_Parameters;
+
    procedure Program_DPLL
      (PLL      : DPLLs;
       Display  : Display_Type;
@@ -389,27 +586,87 @@
                      Shift_Left (Encoded_P1, DPLL_P1_DIVIDER_SHIFT));
    end Program_DPLL;
 
+   procedure Program_Pineview_DPLL
+     (PLL      : DPLLs;
+      Display  : Display_Type;
+      Clk      : PNV_Clock_Type)
+   with
+      Global => (In_Out => Registers.Register_State),
+      Pre => True,
+      Post => True
+   is
+      FP, Encoded_P1, Encoded_P2 : Word32;
+   begin
+      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+
+      -- Pineview frequency-parameter (FP) register: N is one-hot,
+      -- M1 is reserved, M2 is raw.
+      FP :=
+         Shift_Left (Shift_Left (Word32'(1), Natural (Clk.N)), FP_N_SHIFT) or
+         Shift_Left (Word32 (Clk.M2), FP_M2_SHIFT);
+
+      Registers.Write (FP0 (PLL), FP);
+      Registers.Write (FP1 (PLL), FP);
+
+      Encoded_P1 := Shift_Left (1, Natural (Clk.P1) - 1);
+
+      if Clk.P2 = 5 or Clk.P2 = 7
+      then
+         Encoded_P2 := DPLL_P2_5_OR_7;
+      else
+         Encoded_P2 := DPLL_P2_10_OR_14;
+      end if;
+
+      Registers.Write
+        (Register => DPLL (PLL),
+         Value    => DPLL_Mode (Display)                            or
+                     DPLL_VGA_MODE_DIS                              or
+                     Encoded_P2                                     or
+                     Shift_Left (Encoded_P1, DPLL_P1_DIVIDER_SHIFT_PINEVIEW));
+   end Program_Pineview_DPLL;
+
    procedure On
      (PLL      : in     T;
       Port_Cfg : in     Port_Config;
       Success  :    out Boolean)
    is
       Target_Clock : constant Frequency_Type := Port_Cfg.Mode.Dotclock;
-      Clk : Clock_Type;
    begin
       pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
 
       Success := PLL in DPLLs;
-      Clk := Invalid_Clock;
 
       if Success then
          if Target_Clock <= 400_000_000 then
-            Calculate_Clock_Parameters
-              (Display           => Port_Cfg.Display,
-               Target_Dotclock   => Target_Clock,
-               Reference_Clock   => 96_000_000,
-               Best_Clock        => Clk,
-               Valid             => Success);
+            if Config.CPU_Any_Pineview then
+               declare
+                  Clk : PNV_Clock_Type;
+               begin
+                  Calculate_Pineview_Clock_Parameters
+                    (Display           => Port_Cfg.Display,
+                     Target_Dotclock   => Target_Clock,
+                     Reference_Clock   => 96_000_000,
+                     Best_Clock        => Clk,
+                     Valid             => Success);
+                  if Success then
+                     Program_Pineview_DPLL (PLL, Port_Cfg.Display, Clk);
+                  end if;
+               end;
+            else
+               declare
+                  Clk : Clock_Type;
+               begin
+                  Calculate_Clock_Parameters
+                    (Display           => Port_Cfg.Display,
+                     Target_Dotclock   => Target_Clock,
+                     Reference_Clock   => 96_000_000,
+                     Best_Clock        => Clk,
+                     Valid             => Success);
+                  if Success then
+                     Program_DPLL (PLL, Port_Cfg.Display, Clk);
+                  end if;
+               end;
+            end if;
          else
             Success := False;
             pragma Debug (Debug.Put ("WARNING: Targeted clock too high: "));
@@ -422,8 +679,6 @@
       end if;
 
       if Success then
-         Program_DPLL (PLL, Port_Cfg.Display, Clk);
-
          Registers.Set_Mask (DPLL (PLL), DPLL_VCO_ENABLE);
          Registers.Posting_Read (DPLL (PLL));
          Time.U_Delay (150);
diff --git a/common/i945/hw-gfx-gma-port_detect.adb b/common/i945/hw-gfx-gma-port_detect.adb
index af82c17..f2b29f0 100644
--- a/common/i945/hw-gfx-gma-port_detect.adb
+++ b/common/i945/hw-gfx-gma-port_detect.adb
@@ -31,8 +31,8 @@
       -- i945: VGA (ADPA) is always present
       Config.Valid_Port (Analog) := True;
 
-      -- LVDS is only present on mobile (i945GM)
-      Config.Valid_Port (LVDS)   := Config.GMCH_I945GM;
+      -- LVDS is only present on mobile Gen3 parts (i945GM/Pineview-M)
+      Config.Valid_Port (LVDS)   := Config.GMCH_Gen3_Mobile;
 
       -- Enable CRT hotplug detection
       Registers.Write
diff --git a/common/i945/hw-gfx-gma-power_and_clocks.adb b/common/i945/hw-gfx-gma-power_and_clocks.adb
index 2f6404a..6bc4555 100644
--- a/common/i945/hw-gfx-gma-power_and_clocks.adb
+++ b/common/i945/hw-gfx-gma-power_and_clocks.adb
@@ -31,7 +31,7 @@
    HRAWCLK_333       : constant Frequency_Type := 333_333_333;  -- FSB 1333
    HRAWCLK_400       : constant Frequency_Type := 400_000_000;  -- FSB 1600 (desktop)
 
-   -- i945 CDClk values (from Linux kernel intel_cdclk.c):
+   -- i945/Pineview CDClk values (from Linux kernel intel_cdclk.c):
    --
    -- i945G (desktop): fixed 400 MHz CDClk
    --
@@ -39,6 +39,14 @@
    --   GC_LOW_FREQUENCY_ENABLE (bit 7) => 133 MHz
    --   GC_DISPLAY_CLOCK_190_200_MHZ (0 << 4) => 200 MHz (default)
    --   GC_DISPLAY_CLOCK_333_320_MHZ (4 << 4) => 320 MHz
+   --
+   -- Pineview: read from GCFGC PCI register (0xF0)
+   --   GC_DISPLAY_CLOCK_267_MHZ_PNV (0 << 4) => 267 MHz
+   --   GC_DISPLAY_CLOCK_333_MHZ_PNV (1 << 4) => 333 MHz
+   --   GC_DISPLAY_CLOCK_444_MHZ_PNV (2 << 4) => 444 MHz
+   --   GC_DISPLAY_CLOCK_200_MHZ_PNV (5 << 4) => 200 MHz
+   --   GC_DISPLAY_CLOCK_133_MHZ_PNV (6 << 4) => 133 MHz
+   --   GC_DISPLAY_CLOCK_167_MHZ_PNV (7 << 4) => 167 MHz
 
    procedure Get_CDClk (CDClk : out Config.CDClk_Range)
    is
@@ -47,10 +55,26 @@
       GC_LOW_FREQUENCY_ENABLE      : constant Word16 := 1 * 2 ** 7;
       GC_DISPLAY_CLOCK_MASK        : constant Word16 := 7 * 2 ** 4;
       GC_DISPLAY_CLOCK_320_MHZ     : constant Word16 := 4 * 2 ** 4;
+      GC_DISPLAY_CLOCK_267_MHZ_PNV : constant Word16 := 0 * 2 ** 4;
+      GC_DISPLAY_CLOCK_333_MHZ_PNV : constant Word16 := 1 * 2 ** 4;
+      GC_DISPLAY_CLOCK_444_MHZ_PNV : constant Word16 := 2 * 2 ** 4;
+      GC_DISPLAY_CLOCK_200_MHZ_PNV : constant Word16 := 5 * 2 ** 4;
+      GC_DISPLAY_CLOCK_133_MHZ_PNV : constant Word16 := 6 * 2 ** 4;
+      GC_DISPLAY_CLOCK_167_MHZ_PNV : constant Word16 := 7 * 2 ** 4;
 
       GCFGC : Word16;
    begin
-      if Config.GMCH_I945GM then
+      if Config.CPU_Any_Pineview then
+         PCI_Read16 (GCFGC, 16#f0#);
+         CDClk :=
+           (case GCFGC and GC_DISPLAY_CLOCK_MASK is
+               when GC_DISPLAY_CLOCK_267_MHZ_PNV => 266_666_667,
+               when GC_DISPLAY_CLOCK_333_MHZ_PNV => 333_333_333,
+               when GC_DISPLAY_CLOCK_444_MHZ_PNV => 444_444_444,
+               when GC_DISPLAY_CLOCK_200_MHZ_PNV => 200_000_000,
+               when GC_DISPLAY_CLOCK_167_MHZ_PNV => 166_666_667,
+               when others                       => 133_333_333);
+      elsif Config.GMCH_I945GM then
          PCI_Read16 (GCFGC, 16#f0#);
          if (GCFGC and GC_LOW_FREQUENCY_ENABLE) /= 0 then
             CDClk := 133_333_333;
@@ -66,7 +90,7 @@
    end Get_CDClk;
 
    -- hrawclk = FSB / 4. The CLKCFG FSB encoding differs between
-   -- mobile (i945GM) and desktop (i945G), see CLKCFG_FSB_MASK comment above.
+   -- mobile/Pineview and desktop (i945G), see CLKCFG_FSB_MASK comment above.
    procedure Get_Raw_Clock (Raw_Clock : out Frequency_Type)
    is
       CLK_CFG : Word32;
@@ -75,7 +99,7 @@
       Registers.Read
         (Register => Registers.GMCH_CLKCFG,
          Value => CLK_CFG);
-      if Config.GMCH_I945GM then
+      if Config.GMCH_Gen3_Mobile or Config.CPU_Any_Pineview then
          Raw_Clock := (case Freq_Sel (CLK_CFG and CLKCFG_FSB_MASK) is
             when 0      => HRAWCLK_100,  -- FSB 400
             when 1      => HRAWCLK_133,  -- FSB 533