gma: Configure panel fitter / pipe scaler

If the framebuffer size is smaller than the display mode's resolution,
enable the panel fitter or pipe scaler (on Skylake+).

Change-Id: I0a648a7e7bf495a80636a589a74b698ecba7e7d5
Signed-off-by: Nico Huber <nico.huber@secunet.com>
Reviewed-on: https://review.coreboot.org/17263
Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
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 2d13a01..b902b77 100644
--- a/common/hw-gfx-gma-pipe_setup.adb
+++ b/common/hw-gfx-gma-pipe_setup.adb
@@ -64,11 +64,13 @@
    PIPECONF_ENABLE_DITHER   : constant := 1 * 2 **  4;
    PIPECONF_DITHER_TEMPORAL : constant := 1 * 2 **  2;
 
-   PF_CTL_1_ENABLE : constant Word32 := 1 * 2 ** 31;
+   PF_CTRL_ENABLE                         : constant := 1 * 2 ** 31;
+   PF_CTRL_PIPE_SELECT_MASK               : constant := 3 * 2 ** 29;
+   PF_CTRL_FILTER_MED                     : constant := 1 * 2 ** 23;
 
-   PS_CTRL_ENABLE_SCALER               : constant Word32 := 1 * 2 ** 31;
-   PS_CTRL_SCALER_MODE_7X5_EXTENDED    : constant Word32 := 1 * 2 ** 28;
-   PS_CTRL_FILTER_SELECT_MEDIUM_2      : constant Word32 := 1 * 2 ** 23;
+   PS_CTRL_ENABLE_SCALER                  : constant := 1 * 2 ** 31;
+   PS_CTRL_SCALER_MODE_7X5_EXTENDED       : constant := 1 * 2 ** 28;
+   PS_CTRL_FILTER_SELECT_MEDIUM_2         : constant := 1 * 2 ** 23;
 
    PIPE_DDI_FUNC_CTL_ENABLE               : constant := 1 * 2 ** 31;
    PIPE_DDI_FUNC_CTL_DDI_SELECT_MASK      : constant := 7 * 2 ** 28;
@@ -385,6 +387,148 @@
 
    ----------------------------------------------------------------------------
 
+   procedure Scale_Keep_Aspect
+     (Width       :    out Pos32;
+      Height      :    out Pos32;
+      Max_Width   : in     Pos32;
+      Max_Height  : in     Pos32;
+      Framebuffer : in     Framebuffer_Type)
+   with
+      Pre =>
+         Max_Width <= Pos32 (Pos16'Last) and
+         Max_Height <= Pos32 (Pos16'Last) and
+         Framebuffer.Width <= Max_Width and
+         Framebuffer.Height <= Max_Height,
+      Post =>
+         Width <= Max_Width and Height <= Max_Height
+   is
+   begin
+      if (Max_Width * Framebuffer.Height) / Framebuffer.Width <= Max_Height then
+         Width  := Max_Width;
+         Height := (Max_Width * Framebuffer.Height) / Framebuffer.Width;
+      else
+         Height := Max_Height;
+         Width  := Pos32'Min (Max_Width,  -- could prove, it's <= Max_Width
+            (Max_Height * Framebuffer.Width) / Framebuffer.Height);
+      end if;
+   end Scale_Keep_Aspect;
+
+   procedure Setup_Skylake_Pipe_Scaler
+     (Controller  : in     Controller_Type;
+      Mode        : in     HW.GFX.Mode_Type;
+      Framebuffer : in     HW.GFX.Framebuffer_Type)
+   with
+      Pre =>
+         Framebuffer.Width <= Pos32 (Mode.H_Visible) and
+         Framebuffer.Height <= Pos32 (Mode.V_Visible)
+   is
+      -- Enable 7x5 extended mode where possible:
+      Scaler_Mode : constant Word32 :=
+        (if Controller.PS_CTRL_2 /= Registers.Invalid_Register then
+            PS_CTRL_SCALER_MODE_7X5_EXTENDED else 0);
+
+      -- We can scale up to 2.99x horizontally:
+      Horizontal_Limit : constant Pos32 := ((Framebuffer.Width * 299) / 100);
+      -- The third scaler is limited to 1.99x
+      -- vertical scaling for source widths > 2048:
+      Vertical_Limit : constant Pos32 :=
+        (Framebuffer.Height *
+           (if Controller.PS_CTRL_2 = Registers.Invalid_Register and
+               Framebuffer.Width > 2048
+            then
+               199
+            else
+               299)) / 100;
+
+      Width, Height : Pos32;
+   begin
+      -- Writes to WIN_SZ arm the PS registers.
+
+      Scale_Keep_Aspect
+        (Width       => Width,
+         Height      => Height,
+         Max_Width   => Pos32'Min (Horizontal_Limit, Pos32 (Mode.H_Visible)),
+         Max_Height  => Pos32'Min (Vertical_Limit, Pos32 (Mode.V_Visible)),
+         Framebuffer => Framebuffer);
+
+      Registers.Write
+        (Register => Controller.PS_CTRL_1,
+         Value    => PS_CTRL_ENABLE_SCALER or Scaler_Mode);
+      Registers.Write
+        (Register => Controller.PS_WIN_POS_1,
+         Value    =>
+            Shift_Left (Word32 (Pos32 (Mode.H_Visible) - Width) / 2, 16) or
+            Word32 (Pos32 (Mode.V_Visible) - Height) / 2);
+      Registers.Write
+        (Register => Controller.PS_WIN_SZ_1,
+         Value    => Shift_Left (Word32 (Width), 16) or Word32 (Height));
+   end Setup_Skylake_Pipe_Scaler;
+
+   procedure Setup_Ironlake_Panel_Fitter
+     (Controller  : in     Controller_Type;
+      Mode        : in     HW.GFX.Mode_Type;
+      Framebuffer : in     HW.GFX.Framebuffer_Type)
+   with
+      Pre =>
+         Framebuffer.Width <= Pos32 (Mode.H_Visible) and
+         Framebuffer.Height <= Pos32 (Mode.V_Visible)
+   is
+      -- Force 1:1 mapping of panel fitter:pipe
+      PF_Ctrl_Pipe_Sel : constant Word32 :=
+        (if Config.Has_PF_Pipe_Select then
+           (case Controller.PF_CTRL is
+               when Registers.PFA_CTL_1 => 0 * 2 ** 29,
+               when Registers.PFB_CTL_1 => 1 * 2 ** 29,
+               when Registers.PFC_CTL_1 => 2 * 2 ** 29,
+               when others              => 0) else 0);
+
+      Width, Height : Pos32;
+   begin
+      -- Writes to WIN_SZ arm the PF registers.
+
+      Scale_Keep_Aspect
+        (Width       => Width,
+         Height      => Height,
+         Max_Width   => Pos32 (Mode.H_Visible),
+         Max_Height  => Pos32 (Mode.V_Visible),
+         Framebuffer => Framebuffer);
+
+      Registers.Write
+        (Register => Controller.PF_CTRL,
+         Value    => PF_CTRL_ENABLE or PF_Ctrl_Pipe_Sel or PF_CTRL_FILTER_MED);
+      Registers.Write
+        (Register => Controller.PF_WIN_POS,
+         Value    =>
+            Shift_Left (Word32 (Pos32 (Mode.H_Visible) - Width) / 2, 16) or
+            Word32 (Pos32 (Mode.V_Visible) - Height) / 2);
+      Registers.Write
+        (Register => Controller.PF_WIN_SZ,
+         Value    => Shift_Left (Word32 (Width), 16) or Word32 (Height));
+   end Setup_Ironlake_Panel_Fitter;
+
+   procedure Setup_Scaling
+     (Controller  : in     Controller_Type;
+      Mode        : in     HW.GFX.Mode_Type;
+      Framebuffer : in     HW.GFX.Framebuffer_Type)
+   with
+      Pre =>
+         Framebuffer.Width <= Pos32 (Mode.H_Visible) and
+         Framebuffer.Height <= Pos32 (Mode.V_Visible)
+   is
+   begin
+      if Framebuffer.Width /= Pos32 (Mode.H_Visible) or
+         Framebuffer.Height /= Pos32 (Mode.V_Visible)
+      then
+         if Config.Has_Plane_Control then
+            Setup_Skylake_Pipe_Scaler (Controller, Mode, Framebuffer);
+         else
+            Setup_Ironlake_Panel_Fitter (Controller, Mode, Framebuffer);
+         end if;
+      end if;
+   end Setup_Scaling;
+
+   ----------------------------------------------------------------------------
+
    procedure Setup_Head
      (Controller  : Controller_Type;
       Head        : Head_Type;
@@ -456,6 +600,8 @@
 
       Setup_Display (Controller, Head, Port_Cfg.Mode, Framebuffer);
 
+      Setup_Scaling (Controller, Port_Cfg.Mode, Framebuffer);
+
       Setup_Head (Controller, Head, Port_Cfg, Framebuffer);
    end On;
 
@@ -511,7 +657,7 @@
             Registers.Write (Controller.PS_WIN_SZ_2, 16#0000_0000#);
          end if;
       else
-         Registers.Unset_Mask (Controller.PF_CTL_1, PF_CTL_1_ENABLE);
+         Registers.Unset_Mask (Controller.PF_CTRL, PF_CTRL_ENABLE);
          Registers.Write (Controller.PF_WIN_SZ, 16#0000_0000#);
       end if;
    end Panel_Fitter_Off;