--
-- Copyright (C) 2015-2018 secunet Security Networks AG
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--

with HW.Debug;
with GNAT.Source_Info;

with HW.GFX.GMA.Transcoder;

package body HW.GFX.GMA.Pipe_Setup is

   ILK_DISPLAY_CHICKEN1_VGA_MASK       : constant := 7 * 2 ** 29;
   ILK_DISPLAY_CHICKEN1_VGA_ENABLE     : constant := 5 * 2 ** 29;
   ILK_DISPLAY_CHICKEN2_VGA_MASK       : constant := 1 * 2 ** 25;
   ILK_DISPLAY_CHICKEN2_VGA_ENABLE     : constant := 0 * 2 ** 25;

   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;

   DSPCNTR_TILED_SURFACE : constant array (Tiling_Type) of Word32 :=
     (Linear   => DSPCNTR_TILED_SURFACE_LINEAR,
      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;

   PLANE_COLOR_CTL_PLANE_GAMMA_DISABLE : constant := 1 * 2 ** 13;

   PLANE_CTL_PLANE_ENABLE              : constant := 1 * 2 ** 31;
   PLANE_CTL_SRC_PIX_FMT_RGB_32B_8888  : constant := 4 * 2 ** 24;
   PLANE_CTL_PLANE_GAMMA_DISABLE       : constant := 1 * 2 ** 13;
   PLANE_CTL_TILED_SURFACE_MASK        : constant := 7 * 2 ** 10;
   PLANE_CTL_TILED_SURFACE_LINEAR      : constant := 0 * 2 ** 10;
   PLANE_CTL_TILED_SURFACE_X_TILED     : constant := 1 * 2 ** 10;
   PLANE_CTL_TILED_SURFACE_Y_TILED     : constant := 4 * 2 ** 10;
   PLANE_CTL_TILED_SURFACE_YF_TILED    : constant := 5 * 2 ** 10;

   PLANE_CTL_TILED_SURFACE : constant array (Tiling_Type) of Word32 :=
     (Linear   => PLANE_CTL_TILED_SURFACE_LINEAR,
      X_Tiled  => PLANE_CTL_TILED_SURFACE_X_TILED,
      Y_Tiled  => PLANE_CTL_TILED_SURFACE_Y_TILED);

   PLANE_CTL_PLANE_ROTATION_MASK       : constant := 3 * 2 ** 0;
   PLANE_CTL_PLANE_ROTATION : constant array (Rotation_Type) of Word32 :=
     (No_Rotation => 0 * 2 ** 0,
      Rotated_90  => 1 * 2 ** 0,
      Rotated_180 => 2 * 2 ** 0,
      Rotated_270 => 3 * 2 ** 0);

   PLANE_WM_ENABLE                     : constant :=        1 * 2 ** 31;
   PLANE_WM_LINES_SHIFT                : constant :=                 14;
   PLANE_WM_LINES_MASK                 : constant := 16#001f# * 2 ** 14;
   PLANE_WM_BLOCKS_MASK                : constant :=
      (if Config.Has_Wide_Watermarks then 16#7ff# else 16#3ff#);

   PIPEMISC_HDR_MODE_PRECISION         : constant := 1 * 2 ** 23;
   PIPEMISC_PIXEL_ROUNDING_TRUNC       : constant := 1 * 2 **  8;

   VGA_SR_INDEX                        : constant :=   16#03c4#;
   VGA_SR_DATA                         : constant :=   16#03c5#;
   VGA_SR01                            : constant :=     16#01#;
   VGA_SR01_SCREEN_OFF                 : constant := 1 * 2 ** 5;

   VGA_CONTROL_VGA_DISPLAY_DISABLE     : constant :=        1 * 2 ** 31;
   VGA_CONTROL_BLINK_DUTY_CYCLE_MASK   : constant := 16#0003# * 2 **  6;
   VGA_CONTROL_BLINK_DUTY_CYCLE_50     : constant :=        2 * 2 **  6;
   VGA_CONTROL_VSYNC_BLINK_RATE_MASK   : constant := 16#003f# * 2 **  0;

   CUR_CTL_PIPE_SELECT : constant array (Pipe_Index) of Word32 :=
     (Primary     => 0 * 2 ** 28,
      Secondary   => 1 * 2 ** 28,
      Tertiary    => 2 * 2 ** 28);
   CUR_CTL_MODE : constant array (Cursor_Mode, Cursor_Size) of Word32 :=
     (No_Cursor   => (others => 16#00#),
      ARGB_Cursor =>
        (Cursor_64x64   => 16#27#,
         Cursor_128x128 => 16#22#,
         Cursor_256x256 => 16#23#));
   subtype ARB_Slots is Natural range 0 .. 7;
   function MCURSOR_ARB_SLOTS (N : ARB_Slots) return Word32 is
     (Shift_Left (Word32 (N), 28));

   function CUR_POS_Y (Y : Int32) return Word32 is
     ((if Y >= 0 then 0 else 1 * 2 ** 31) or Shift_Left (Word32 (abs Y), 16))
   with
      Pre => Y > Int32'First;
   function CUR_POS_X (X : Int32) return Word32 is
     ((if X >= 0 then 0 else 1 * 2 ** 15) or Word32 (abs X))
   with
      Pre => X > Int32'First;

   subtype VGA_Cycle_Count is Pos32 range 2 .. 128;
   function VGA_CONTROL_VSYNC_BLINK_RATE
     (Cycles : VGA_Cycle_Count)
      return Word32
   is
   begin
      return Word32 (Cycles) / 2 - 1;
   end VGA_CONTROL_VSYNC_BLINK_RATE;

   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 := 1 * 2 ** 31;
   PS_CTRL_SCALER_MODE_7X5_EXTENDED    : constant := 1 * 2 ** 28;
   PS_CTRL_FILTER_SELECT_MEDIUM_2      : constant := 1 * 2 ** 23;

   GMCH_PFIT_CONTROL_SELECT_MASK       : constant := 3 * 2 ** 29;
   GMCH_PFIT_CONTROL_SELECT_PIPE_A     : constant := 0 * 2 ** 29;
   GMCH_PFIT_CONTROL_SELECT_PIPE_B     : constant := 1 * 2 ** 29;
   GMCH_PFIT_CONTROL_SCALING_MASK      : constant := 3 * 2 ** 26;
   GMCH_PFIT_CONTROL_SCALING : constant array (Scaling_Aspect) of Word32 :=
     (Uniform     => 0 * 2 ** 26,
      Pillarbox   => 2 * 2 ** 26,
      Letterbox   => 3 * 2 ** 26);

   VGACNTRL_REG : constant Registers.Registers_Index :=
     (if Config.Has_GMCH_VGACNTRL then
         Registers.GMCH_VGACNTRL
      else Registers.CPU_VGACNTRL);

   ---------------------------------------------------------------------------

   function PLANE_WM_LINES (Lines : Natural) return Word32 is
   begin
      return Shift_Left (Word32 (Lines), PLANE_WM_LINES_SHIFT)
               and PLANE_WM_LINES_MASK;
   end PLANE_WM_LINES;

   function PLANE_WM_BLOCKS (Blocks : Natural) return Word32 is
   begin
      return Word32 (Blocks) and PLANE_WM_BLOCKS_MASK;
   end PLANE_WM_BLOCKS;

   ---------------------------------------------------------------------------

   function Encode_Size (LSW, MSW : Word32) return Word32
   is
     (Shift_Left (MSW - 1, 16) or (LSW - 1));

   function Prepare_Source_Width (Framebuffer : Framebuffer_Type) return Word32
   is
     (if Config.Needs_Even_Source_Width then
         Word32 (Rotated_Width (Framebuffer)) and not 1
      else
         Word32 (Rotated_Width (Framebuffer)));

   function Prepare_Source_Height (Framebuffer : Framebuffer_Type) return Word32
   is
     (Word32 (Rotated_Height (Framebuffer)));

   ----------------------------------------------------------------------------

   procedure Clear_Watermarks (Controller : Controller_Type) is
   begin
      Registers.Write (Controller.CUR_BUF_CFG, 16#0000_0000#);
      for Level in WM_Levels loop
         Registers.Write (Controller.CUR_WM (Level), 16#0000_0000#);
      end loop;
      Registers.Write (Controller.PLANE_BUF_CFG, 16#0000_0000#);
      for Level in WM_Levels loop
         Registers.Write (Controller.PLANE_WM (Level), 16#0000_0000#);
      end loop;
      Registers.Write (Controller.WM_LINETIME, 16#0000_0000#);
   end Clear_Watermarks;

   procedure Setup_Watermarks (Controller : Controller_Type)
   is
      type Per_Plane_Buffer_Range is array (Pipe_Index) of Word32;
      Cur_Buffer_Range : constant Per_Plane_Buffer_Range :=
        (Primary     => Shift_Left (  7, 16) or   0,
         Secondary   => Shift_Left (167, 16) or 160,
         Tertiary    => Shift_Left (327, 16) or 320);
      Plane_Buffer_Range : constant Per_Plane_Buffer_Range :=
        (Primary     => Shift_Left (159, 16) or   8,
         Secondary   => Shift_Left (319, 16) or 168,
         Tertiary    => Shift_Left (479, 16) or 328);
   begin
      Registers.Write
        (Register    => Controller.PLANE_BUF_CFG,
         Value       => Plane_Buffer_Range (Controller.Pipe));
      Registers.Write
        (Register    => Controller.PLANE_WM (0),
         Value       => PLANE_WM_ENABLE or
                        PLANE_WM_LINES (2) or
                        PLANE_WM_BLOCKS (152));
      Registers.Write
        (Register    => Controller.CUR_BUF_CFG,
         Value       => Cur_Buffer_Range (Controller.Pipe));
      Registers.Write
        (Register    => Controller.CUR_WM (0),
         Value       => PLANE_WM_ENABLE or
                        PLANE_WM_LINES (2) or
                        PLANE_WM_BLOCKS (8));
   end Setup_Watermarks;

   ----------------------------------------------------------------------------

   procedure Setup_Hires_Plane
     (Controller  : Controller_Type;
      FB          : HW.GFX.Framebuffer_Type)
   with
      Pre => FB.Height + FB.Start_Y <= FB.V_Stride
   is
      Width : constant Word32 := Prepare_Source_Width (FB);
      Height : constant Word32 := Prepare_Source_Height (FB);

      -- FIXME: setup correct format, based on framebuffer RGB format
      Format : constant Word32 := 6 * 2 ** 26;
      PRI : Word32 := Format;
   begin
      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));

      if Config.Has_Plane_Control then
         declare
            Stride, Offset : Word32;

            function PLANE_CTL_ARB_SLOTS (N : Word32) return Word32 is
              (if Config.Need_Pipe_Arb_Slots then Shift_Left (N, 28) else 0);

            -- TODO: Hard coded format and arbitration slots for now,
            --       for 4B-per-pixel XRGB, just like `Format` above.
            --       ARB_SLOTS(1) matches the 4B per pixel.
            Plane_Ctl : constant Word32 :=
               PLANE_CTL_PLANE_ENABLE or
               PLANE_CTL_TILED_SURFACE (FB.Tiling) or
               PLANE_CTL_PLANE_ROTATION (FB.Rotation) or
               PLANE_CTL_SRC_PIX_FMT_RGB_32B_8888 or
               PLANE_CTL_ARB_SLOTS (1) or
              (if not Config.Has_Plane_Color_Control
               then PLANE_CTL_PLANE_GAMMA_DISABLE
               else 0);
         begin
            if Rotation_90 (FB) then
               Stride   := Word32 (FB_Pitch (FB.V_Stride, FB));
               Offset   := Shift_Left (Word32 (FB.Start_X), 16) or
                           Word32 (FB.V_Stride - FB.Height - FB.Start_Y);
            else
               Stride   := Word32 (FB_Pitch (FB.Stride, FB));
               Offset   := Shift_Left (Word32 (FB.Start_Y), 16) or
                           Word32 (FB.Start_X);
            end if;

            if Config.Has_Plane_Color_Control then
               Registers.Write
                 (Register => Controller.PLANE_COLOR_CTL,
                  Value    => PLANE_COLOR_CTL_PLANE_GAMMA_DISABLE);
            end if;
            Registers.Write (Controller.PLANE_AUX_DIST, 0);
            Registers.Write (Controller.PLANE_CTL, Plane_Ctl);
            Registers.Write (Controller.PLANE_OFFSET, Offset);
            Registers.Write (Controller.PLANE_SIZE, Encode_Size (Width, Height));
            Registers.Write (Controller.PLANE_STRIDE, Stride);
            Registers.Write (Controller.PLANE_POS, 16#0000_0000#);
            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;

         -- 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,
             Mask_Set   => PRI or DSPCNTR_TILED_SURFACE (FB.Tiling));

         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_Size (Width, Height));
            Registers.Write (Controller.DSPPOS, 16#0000_0000#);
         end if;

         if Config.Has_DSP_Linoff and then FB.Tiling = Linear then
            pragma Assert_And_Cut
              (FB.Start_Y * FB.Stride + FB.Start_X in Pixel_Type);
            declare
               Linear_Offset : constant Pixel_Type :=
                  FB.Start_Y * FB.Stride + FB.Start_X;
            begin
               Registers.Write
                 (Register => Controller.DSPLINOFF,
                  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,
                  (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;
         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;

   procedure Setup_Display
     (Controller  : Controller_Type;
      Framebuffer : Framebuffer_Type;
      Dither_BPC  : BPC_Type;
      Dither      : Boolean)
   with
      Pre =>
         Framebuffer.Offset = VGA_PLANE_FRAMEBUFFER_OFFSET or
         Framebuffer.Height + Framebuffer.Start_Y <= Framebuffer.V_Stride
   is
      use type Word8;

      Reg8 : Word8;

      type BW_Credit is new Natural range 0 .. 3;
      function MBUS_DBOX_BW_CREDIT (C : BW_Credit) return Word32 is
        (Shift_Left (Word32 (C), 14));

      type B_Credit is new Natural range 0 .. 31;
      function MBUS_DBOX_B_CREDIT (C : B_Credit) return Word32 is
        (Shift_Left (Word32 (C), 8));

      type A_Credit is new Natural range 0 .. 15;
      function MBUS_DBOX_A_CREDIT (C : A_Credit) return Word32 is
        (Word32 (C));

      type B2B_Trans_Max is new Natural range 0 .. 31;
      function MBUS_DBOX_B2B_TRANSACTIONS_MAX (B : B2B_Trans_Max) return Word32 is
        (Shift_Left (Word32 (B), 20));

      type B2B_Trans_Delay is new Natural range 0 .. 7;
      function MBUS_DBOX_B2B_TRANSACTIONS_DELAY (B : B2B_Trans_Delay) return Word32 is
        (Shift_Left (Word32 (B), 17));
      MBUS_DBOX_REGULATE_B2B_TRANSACTIONS_EN : constant := 1 * 2 ** 16;

      procedure Program_Mbus_Dbox_Credits is
         Tmp : Word32;
      begin
         Tmp := MBUS_DBOX_B2B_TRANSACTIONS_MAX (16) or
                MBUS_DBOX_B2B_TRANSACTIONS_DELAY (1) or
                MBUS_DBOX_REGULATE_B2B_TRANSACTIONS_EN;

         if Config.Has_New_Mbus_Dbox_Credits then
            Tmp := Tmp or MBUS_DBOX_BW_CREDIT (2) or
                          MBUS_DBOX_B_CREDIT (8) or
                          MBUS_DBOX_A_CREDIT (4);  -- No joined MBus support,
                                                   -- hence always use 4 for now.
         else
            Tmp := Tmp or MBUS_DBOX_BW_CREDIT (2) or
                          MBUS_DBOX_B_CREDIT (12) or
                          MBUS_DBOX_A_CREDIT (2);
         end if;

         Registers.Write
           (Register => Controller.MBUS_DBOX_CTL,
            Value    => Tmp);
      end Program_Mbus_Dbox_Credits;

      -- Display WA # 1605353570: icl
      -- Set the pixel rounding bit to 1 for allowing
      -- passthrough of Frame buffer pixels unmodified
      -- across pipe
      PIXEL_ROUNDING_TRUNC_FB_PASSTHRU : constant := 1 * 2 ** 15;

      -- Display WA #1153: icl
      -- enable hardware to bypass the alpha math
      -- and rounding for per-pixel values 00 and 0xff
      PER_PIXEL_ALPHA_BYPASS_EN : constant := 1 * 2 ** 7;

      -- ADL_P requires that we disable underrun recovery when
      -- downscaling (or using the scaler for YUV420 pipe output),
      -- using DSC, or using PSR2.
      -- i915 always disables underrun recovery for gen 13+.
      UNDERRUN_RECOVERY_DISABLE : constant := 1 * 2 ** 30;

      Width : constant Word32 := Prepare_Source_Width (Framebuffer);
      Height : constant Word32 := Prepare_Source_Height (Framebuffer);
   begin
      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));

      if Config.Has_Type_C_Ports then
         Registers.Set_Mask
           (Register => Controller.PIPE_CHICKEN,
            Mask     => PER_PIXEL_ALPHA_BYPASS_EN or
                        PIXEL_ROUNDING_TRUNC_FB_PASSTHRU or
                        (if Config.Need_Underrun_Rec_Disable
                         then UNDERRUN_RECOVERY_DISABLE
                         else 0));
      end if;

      if Config.Has_Plane_Control then
         Setup_Watermarks (Controller);
      end if;

      if Config.Has_Mbus_Dbox_Credits then
         Program_Mbus_Dbox_Credits;
      end if;

      if Framebuffer.Offset = VGA_PLANE_FRAMEBUFFER_OFFSET then
         if Config.VGA_Plane_Workaround then
            Registers.Unset_And_Set_Mask
              (Register    => Registers.ILK_DISPLAY_CHICKEN1,
               Mask_Unset  => ILK_DISPLAY_CHICKEN1_VGA_MASK,
               Mask_Set    => ILK_DISPLAY_CHICKEN1_VGA_ENABLE);
            Registers.Unset_And_Set_Mask
              (Register    => Registers.ILK_DISPLAY_CHICKEN2,
               Mask_Unset  => ILK_DISPLAY_CHICKEN2_VGA_MASK,
               Mask_Set    => ILK_DISPLAY_CHICKEN2_VGA_ENABLE);
         end if;

         Registers.Unset_And_Set_Mask
           (Register    => VGACNTRL_REG,
            Mask_Unset  => VGA_CONTROL_VGA_DISPLAY_DISABLE or
                           VGA_CONTROL_BLINK_DUTY_CYCLE_MASK or
                           VGA_CONTROL_VSYNC_BLINK_RATE_MASK,
            Mask_Set    => VGA_CONTROL_BLINK_DUTY_CYCLE_50 or
                           VGA_CONTROL_VSYNC_BLINK_RATE (30));

         Port_IO.OutB (VGA_SR_INDEX, VGA_SR01);
         Port_IO.InB  (Reg8, VGA_SR_DATA);
         Port_IO.OutB (VGA_SR_DATA, Reg8 and not (VGA_SR01_SCREEN_OFF));
      else
         Setup_Hires_Plane (Controller, Framebuffer);
      end if;

      Registers.Write
        (Register => Controller.PIPESRC,
         Value    => Encode_Size (Height, Width));

      if Config.Has_Pipeconf_Misc then
         Registers.Write
           (Register => Controller.PIPEMISC,
            Value    => Transcoder.BPC_Conf (Dither_BPC, Dither) or
                        -- FIXME: Should we set these at all?
                        (if Config.Has_Plane_Color_Control then
                           (PIPEMISC_PIXEL_ROUNDING_TRUNC or PIPEMISC_HDR_MODE_PRECISION) else 0));
      end if;
   end Setup_Display;

   ----------------------------------------------------------------------------

   procedure Update_Cursor
     (Pipe     : Pipe_Index;
      FB       : Framebuffer_Type;
      Cursor   : Cursor_Type)
   is
   begin
      -- on some platforms writing CUR_CTL disables self-arming of CUR_POS
      -- so keep it first
      Registers.Write
        (Register => Cursors (Pipe).CTL,
         Value    => CUR_CTL_MODE (Cursor.Mode, Cursor.Size) or
                     (if Config.Need_Pipe_Arb_Slots
                      then MCURSOR_ARB_SLOTS (1)
                      else CUR_CTL_PIPE_SELECT (Pipe)));
      Place_Cursor (Pipe, FB, Cursor);
   end Update_Cursor;

   procedure Place_Cursor
     (Pipe     : Pipe_Index;
      FB       : Framebuffer_Type;
      Cursor   : Cursor_Type)
   is
      Width : constant Width_Type := Cursor_Width (Cursor.Size);
      X : Int32 := Cursor.Center_X - Width / 2;
      Y : Int32 := Cursor.Center_Y - Width / 2;
   begin
      -- off-screen cursor needs special care
      if X <= -Width or Y <= -Width or
         X >= Rotated_Width (FB) or Y >= Rotated_Height (FB) or
         X > Config.Maximum_Cursor_X or Y > Config.Maximum_Cursor_Y
      then
         X := -Width;
         Y := -Width;
      end if;
      Registers.Write
        (Register => Cursors (Pipe).POS,
         Value    => CUR_POS_Y (Y) or CUR_POS_X (X),
         Verbose  => False);
      -- write to CUR_BASE always arms other CUR_* registers
      Registers.Write
        (Register => Cursors (Pipe).BASE,
         Value    => Shift_Left (Word32 (Cursor.GTT_Offset), 12),
         Verbose  => False);
   end Place_Cursor;

   ----------------------------------------------------------------------------

   procedure Scale_Keep_Aspect
     (Width       :    out Width_Type;
      Height      :    out Height_Type;
      Max_Width   : in     Width_Type;
      Max_Height  : in     Height_Type;
      Framebuffer : in     Framebuffer_Type)
   with
      Pre =>
         Rotated_Width (Framebuffer) <= Max_Width and
         Rotated_Height (Framebuffer) <= Max_Height,
      Post =>
         Width <= Max_Width and Height <= Max_Height
   is
      Src_Width : constant Width_Type := Rotated_Width (Framebuffer);
      Src_Height : constant Height_Type := Rotated_Height (Framebuffer);
   begin
      case Scaling_Type (Src_Width, Src_Height, Max_Width, Max_Height) is
         when Letterbox =>
            Height := (Src_Height * Max_Width) / Src_Width;
            pragma Assert (Height <= Max_Height);
            Width  := Max_Width;
         when Pillarbox =>
            Width  := (Src_Width * Max_Height) / Src_Height;
            pragma Assert (Max_Height * Src_Width < Max_Width * Src_Height);
            pragma Assert ((Max_Height * Src_Width) / Src_Height < Max_Width);
            pragma Assert (Width <= Max_Width);
            Height := Max_Height;
         when Uniform =>
            Width  := Max_Width;
            Height := Max_Height;
      end case;
   end Scale_Keep_Aspect;

   procedure Scale
     (Width       :    out Width_Type;
      Height      :    out Height_Type;
      Scaling     : in     GMA.Scaling;
      Max_Width   : in     Width_Type;
      Max_Height  : in     Height_Type;
      Framebuffer : in     Framebuffer_Type)
   with
      Pre =>
         Rotated_Width (Framebuffer) <= Max_Width and
         Rotated_Height (Framebuffer) <= Max_Height,
      Post =>
         Width <= Max_Width and Height <= Max_Height
   is
   begin
      case Scaling is
         when None =>
            Width  := Rotated_Width (Framebuffer);
            Height := Rotated_Height (Framebuffer);
         when Fit =>
            Scale_Keep_Aspect (Width, Height, Max_Width, Max_Height, Framebuffer);
         when Stretch =>
            Width  := Max_Width;
            Height := Max_Height;
      end case;
   end Scale;

   procedure Align_Framebuffer
     (Pos_X    :    out Position_Type;
      Pos_Y    :    out Position_Type;
      Align    : in     Alignment;
      Width    : in     Width_Type;
      Height   : in     Height_Type;
      Mode     : in     Mode_Type)
   with
      Pre => Width <= Mode.H_Visible and Height <= Mode.V_Visible
   is
      Gap_X : constant Position_Type := Mode.H_Visible - Width;
      Gap_Y : constant Position_Type := Mode.V_Visible - Height;
   begin
      case Align is
         when Top_Left     => Pos_X := 0;          Pos_Y := 0;
         when Top          => Pos_X := Gap_X / 2;  Pos_Y := 0;
         when Top_Right    => Pos_X := Gap_X;      Pos_Y := 0;
         when Left         => Pos_X := 0;          Pos_Y := Gap_Y / 2;
         when Center       => Pos_X := Gap_X / 2;  Pos_Y := Gap_Y / 2;
         when Right        => Pos_X := Gap_X;      Pos_Y := Gap_Y / 2;
         when Bottom_Left  => Pos_X := 0;          Pos_Y := Gap_Y;
         when Bottom       => Pos_X := Gap_X / 2;  Pos_Y := Gap_Y;
         when Bottom_Right => Pos_X := Gap_X;      Pos_Y := Gap_Y;
      end case;
   end Align_Framebuffer;

   type Pipe_Scaler_Limit_Config is record
      Control     : Word32;
      Horizontal  : Pos32;
      Vertical    : Pos32;
   end record;

   function Skylake_Scaler_Limits
     (Controller  : Controller_Type;
      Width       : Width_Type;
      Height      : Height_Type)
      return Pipe_Scaler_Limit_Config
   with
      Post => Skylake_Scaler_Limits'Result.Horizontal >= Width
               and Skylake_Scaler_Limits'Result.Vertical >= Height
   is
      use type Registers.Registers_Invalid_Index;

      -- 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 := (Width * 299) / 100;
      -- The third scaler is limited to 1.99x
      -- vertical scaling for source widths > 2048:
      Vertical_Limit : constant Pos32 :=
        (Height *
           (if Controller.PS_CTRL_2 = Registers.Invalid_Register and
               Width > 2048
            then
               199
            else
               299)) / 100;
   begin
      return (Scaler_Mode, Horizontal_Limit, Vertical_Limit);
   end Skylake_Scaler_Limits;

   function Tigerlake_Scaler_Limits
     (Width       : Width_Type;
      Height      : Height_Type)
      return Pipe_Scaler_Limit_Config
   with
      Post => Tigerlake_Scaler_Limits'Result.Horizontal >= Width
               and Tigerlake_Scaler_Limits'Result.Vertical >= Height
   is
      Scaling : constant := 32_000; -- PRM says: Scaling * 2**15 >= 1.0
                                    -- so virtually unlimited
   begin
      return (Control => 0, Horizontal => Width * Scaling, Vertical => Height * Scaling);
   end Tigerlake_Scaler_Limits;

   procedure Setup_Skylake_Pipe_Scaler
     (Controller  : in     Controller_Type;
      Pipe_Cfg    : in     Pipe_Config;
      Mode        : in     HW.GFX.Mode_Type)
   with
      Pre =>
         Rotated_Width (Pipe_Cfg.Framebuffer) <= Mode.H_Visible and
         Rotated_Height (Pipe_Cfg.Framebuffer) <= Mode.V_Visible
   is
      Width_In    : constant Width_Type   := Rotated_Width (Pipe_Cfg.Framebuffer);
      Height_In   : constant Height_Type  := Rotated_Height (Pipe_Cfg.Framebuffer);
      Limits      : constant Pipe_Scaler_Limit_Config :=
        (if Config.Has_Skylake_Scaler_Limits then
            Skylake_Scaler_Limits (Controller, Width_In, Height_In)
         else
            Tigerlake_Scaler_Limits (Width_In, Height_In));

      Width : Width_Type;
      Height : Height_Type;
      Pos_X, Pos_Y : Position_Type;
   begin
      -- Writes to WIN_SZ arm the PS registers.

      Scale
        (Width       => Width,
         Height      => Height,
         Scaling     => Pipe_Cfg.Scaling,
         Max_Width   => Pos32'Min (Limits.Horizontal, Mode.H_Visible),
         Max_Height  => Pos32'Min (Limits.Vertical, Mode.V_Visible),
         Framebuffer => Pipe_Cfg.Framebuffer);

      Align_Framebuffer
        (Pos_X  => Pos_X,
         Pos_Y  => Pos_Y,
         Align  => Pipe_Cfg.Alignment,
         Width  => Width,
         Height => Height,
         Mode   => Mode);

      Registers.Write
        (Register => Controller.PS_CTRL_1,
         Value    => PS_CTRL_ENABLE_SCALER or Limits.Control);
      Registers.Write
        (Register => Controller.PS_WIN_POS_1,
         Value    => Shift_Left (Word32 (Pos_X), 16) or Word32 (Pos_Y));
      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;
      Pipe_Cfg    : in     Pipe_Config;
      Mode        : in     HW.GFX.Mode_Type)
   with
      Pre =>
         Rotated_Width (Pipe_Cfg.Framebuffer) <= Mode.H_Visible and
         Rotated_Height (Pipe_Cfg.Framebuffer) <= 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 : Width_Type;
      Height : Height_Type;
      Pos_X, Pos_Y : Position_Type;
   begin
      -- Writes to WIN_SZ arm the PF registers.

      Scale
        (Width       => Width,
         Height      => Height,
         Scaling     => Pipe_Cfg.Scaling,
         Max_Width   => Mode.H_Visible,
         Max_Height  => Mode.V_Visible,
         Framebuffer => Pipe_Cfg.Framebuffer);

      -- Do not scale to odd width (at least Haswell has trouble with this).
      if Width < Mode.H_Visible and Width mod 2 = 1 then
         Width := Width + 1;
      end if;
      -- Do not scale to odd height (at least Sandy Bridge makes trouble).
      if Height < Mode.V_Visible and Height mod 2 = 1 then
         Height := Height + 1;
      end if;

      Align_Framebuffer
        (Pos_X  => Pos_X,
         Pos_Y  => Pos_Y,
         Align  => Pipe_Cfg.Alignment,
         Width  => Width,
         Height => Height,
         Mode   => Mode);

      -- Hardware is picky about minimal horizontal gaps.
      if Mode.H_Visible - Width <= 3 then
         Width := Mode.H_Visible;
         Pos_X := 0;
      end if;

      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 (Pos_X), 16) or Word32 (Pos_Y));
      Registers.Write
        (Register => Controller.PF_WIN_SZ,
         Value    => Shift_Left (Word32 (Width), 16) or Word32 (Height));
   end Setup_Ironlake_Panel_Fitter;

   procedure Setup_Gmch_Panel_Fitter
     (Controller  : in     Controller_Type;
      Mode        : in     HW.GFX.Mode_Type;
      Framebuffer : in     HW.GFX.Framebuffer_Type)
   is
      PF_Ctrl_Pipe_Sel : constant Word32 :=
           (case Controller.Pipe is
               when Primary   => GMCH_PFIT_CONTROL_SELECT_PIPE_A,
               when Secondary => GMCH_PFIT_CONTROL_SELECT_PIPE_B,
               when others    => 0);

      -- Work around a quirk:
      -- In legacy VGA mode Pillarbox fails to display anything so just force
      -- 'auto' mode on all displays, which will the output stretched to
      -- fullscreen .
      PF_Ctrl_Scaling : constant Word32 :=
        (if Framebuffer.Offset = VGA_PLANE_FRAMEBUFFER_OFFSET then
           GMCH_PFIT_CONTROL_SCALING (Uniform)
         else
           GMCH_PFIT_CONTROL_SCALING (Scaling_Type (Framebuffer, Mode)));

      In_Use : Boolean;
   begin
      Registers.Is_Set_Mask
        (Register => Registers.GMCH_PFIT_CONTROL,
         Mask     => PF_CTRL_ENABLE,
         Result   => In_Use);

      if not In_Use then
         Registers.Write
           (Register => Registers.GMCH_PFIT_CONTROL,
            Value    => PF_CTRL_ENABLE or PF_Ctrl_Pipe_Sel or PF_Ctrl_Scaling);
      else
         pragma Debug (Debug.Put_Line
           ("GMCH Pannel fitter already in use, skipping..."));
      end if;
   end Setup_Gmch_Panel_Fitter;

   procedure Gmch_Panel_Fitter_Pipe (Pipe : out Pipe_Index)
   is
      Used_For_Secondary : Boolean;
   begin
      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 Gmch_Panel_Fitter_Pipe;

   procedure Panel_Fitter_Off (Controller : Controller_Type)
   is
      use type HW.GFX.GMA.Registers.Registers_Invalid_Index;
      Pipe_Using_PF : Pipe_Index;
   begin
      -- Writes to WIN_SZ arm the PS/PF registers.
      if Config.Has_Plane_Control then
         Registers.Unset_Mask (Controller.PS_CTRL_1, PS_CTRL_ENABLE_SCALER);
         Registers.Write (Controller.PS_WIN_SZ_1, 16#0000_0000#);
         if Controller.PS_CTRL_2 /= Registers.Invalid_Register and
            Controller.PS_WIN_SZ_2 /= Registers.Invalid_Register
         then
            Registers.Unset_Mask (Controller.PS_CTRL_2, PS_CTRL_ENABLE_SCALER);
            Registers.Write (Controller.PS_WIN_SZ_2, 16#0000_0000#);
         end if;
      elsif Config.Has_GMCH_PFIT_CONTROL then
         Gmch_Panel_Fitter_Pipe (Pipe_Using_PF);
         if Pipe_Using_PF = Controller.Pipe then
            -- 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);
         Registers.Write (Controller.PF_WIN_SZ, 16#0000_0000#);
      end if;
   end Panel_Fitter_Off;

   procedure Setup_Scaling
     (Controller  : in     Controller_Type;
      Pipe_Cfg    : in     Pipe_Config;
      Mode        : in     HW.GFX.Mode_Type)
   with
      Pre =>
         Rotated_Width (Pipe_Cfg.Framebuffer) <= Mode.H_Visible and
         Rotated_Height (Pipe_Cfg.Framebuffer) <= Mode.V_Visible
   is
   begin
      if Requires_Scaling (Pipe_Cfg.Framebuffer, Mode) then
         if Config.Has_Plane_Control then
            Setup_Skylake_Pipe_Scaler (Controller, Pipe_Cfg, Mode);
         elsif Config.Has_GMCH_PFIT_CONTROL then
            Setup_Gmch_Panel_Fitter (Controller, Mode, Pipe_Cfg.Framebuffer);
         else
            Setup_Ironlake_Panel_Fitter (Controller, Pipe_Cfg, Mode);
         end if;
      else
         Panel_Fitter_Off (Controller);
      end if;
   end Setup_Scaling;

   procedure Reserve_Scaler
     (Success     :    out Boolean;
      Reservation : in out Scaler_Reservation;
      Pipe        : in     Pipe_Index)
   is
      Pipe_Using_PF : Pipe_Index := Pipe_Index'First;
      PF_Enabled : Boolean;
   begin
      if Config.Has_GMCH_PFIT_CONTROL then
         if Reservation.Reserved then
            Success := Reservation.Pipe = Pipe;
            return;
         end if;

         Registers.Is_Set_Mask
           (Register => Registers.GMCH_PFIT_CONTROL,
            Mask     => PF_CTRL_ENABLE,
            Result   => PF_Enabled);
         if PF_Enabled then
            Gmch_Panel_Fitter_Pipe (Pipe_Using_PF);
         end if;

         Success := not PF_Enabled or Pipe_Using_PF = Pipe;
         if Success then
            Reservation.Reserved := True;
            Reservation.Pipe     := Pipe;
         end if;
      else
         Success := True;
      end if;
   end Reserve_Scaler;

   ----------------------------------------------------------------------------

   procedure Setup_FB
     (Pipe        : Pipe_Index;
      Pipe_Cfg    : Pipe_Config;
      Mode        : Mode_Type)
   is
      -- Enable dithering if framebuffer BPC differs from port BPC,
      -- as smooth gradients look really bad without.
      Dither : constant Boolean := Pipe_Cfg.Framebuffer.BPC /= Mode.BPC;
   begin
      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));

      -- Disable the cursor first.
      Update_Cursor (Pipe, Pipe_Cfg.Framebuffer, Default_Cursor);

      Setup_Display (Controllers (Pipe), Pipe_Cfg.Framebuffer, Mode.BPC, Dither);
      Setup_Scaling (Controllers (Pipe), Pipe_Cfg, Mode);
   end Setup_FB;

   procedure On
     (Pipe        : Pipe_Index;
      Pipe_Cfg    : Pipe_Config;
      Port_Cfg    : Port_Config)
   is
   begin
      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));

      Transcoder.Setup (Pipe, Port_Cfg);

      Setup_FB (Pipe, Pipe_Cfg, Port_Cfg.Mode);
      Update_Cursor (Pipe, Pipe_Cfg.Framebuffer, Pipe_Cfg.Cursor);

      Transcoder.On
        (Pipe     => Pipe,
         Port_Cfg => Port_Cfg,
         Dither   => Pipe_Cfg.Framebuffer.BPC /= Port_Cfg.Mode.BPC,
         Scale    => Requires_Scaling (Pipe_Cfg.Framebuffer, Port_Cfg.Mode));
   end On;

   ----------------------------------------------------------------------------

   procedure Planes_Off (Controller : Controller_Type; CUR : Cursor_Regs)
   is
      use type Registers.Registers_Invalid_Index;
   begin
      Registers.Write (CUR.CTL, 16#0000_0000#);
      if CUR.FBC_CTL /= Registers.Invalid_Register then
         Registers.Write (CUR.FBC_CTL, 16#0000_0000#);
      end if;
      Registers.Unset_Mask (Controller.SPCNTR, DSPCNTR_ENABLE);
      if Config.Has_Plane_Control then
         Clear_Watermarks (Controller);
         Registers.Unset_Mask (Controller.PLANE_CTL, PLANE_CTL_PLANE_ENABLE);
         Registers.Write (Controller.PLANE_SURF, 16#0000_0000#);
      else
         Registers.Unset_Mask (Controller.DSPCNTR, DSPCNTR_ENABLE);
      end if;
   end Planes_Off;

   procedure Off (Pipe : Pipe_Index)
   is
   begin
      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));

      Planes_Off (Controllers (Pipe), Cursors (Pipe));
      Transcoder.Off (Pipe);
      Panel_Fitter_Off (Controllers (Pipe));
      Transcoder.Clk_Off (Pipe);
   end Off;

   procedure Legacy_VGA_Off
   is
      use type HW.Word8;
      Reg8 : Word8;
   begin
      Port_IO.OutB (VGA_SR_INDEX, VGA_SR01);
      Port_IO.InB  (Reg8, VGA_SR_DATA);
      Port_IO.OutB (VGA_SR_DATA, Reg8 or VGA_SR01_SCREEN_OFF);
      Time.U_Delay (100); -- PRM says 100us, Linux does 300
      Registers.Set_Mask (VGACNTRL_REG, VGA_CONTROL_VGA_DISPLAY_DISABLE);
   end Legacy_VGA_Off;

   procedure All_Off
   is
   begin
      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));

      Legacy_VGA_Off;

      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));
         Transcoder.Clk_Off (Pipe);
      end loop;
   end All_Off;

end HW.GFX.GMA.Pipe_Setup;
