--
-- Copyright (C) 2015-2016, 2019 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.GFX.GMA.Connector_Info;
with HW.GFX.GMA.DP_Info;

with HW.Debug;

package body HW.GFX.GMA.Config_Helpers
is

   function To_GPU_Port
     (Pipe : Pipe_Index;
      Port : Active_Port_Type)
      return GPU_Port
   is
      function Combo_PHY (Port : Port_Type) return GPU_Port is
        (case Port is
            when HDMI1 | DP1  => DIGI_A,
            when HDMI2 | DP2  => DIGI_B,
            when HDMI3 | DP3  => DIGI_C,
            when others       => LVDS);   -- n/a, actually
   begin
      return
        (case Config.Gen is
            when I945 =>               -- LVDS, VGA, SDVO B/C on GMCH
              (case Port is
                  when LVDS         => LVDS,
                  when HDMI1 | DP1  => DIGI_B, -- SDVO B
                  when HDMI2 | DP2  => DIGI_C, -- SDVO C
                  when Analog       => VGA,
                  when others       => DIGI_A), -- n/a, actually
            when G45 =>                -- everything on GMCH
              (case Port is
                  when LVDS         => LVDS,
                  when eDP          => DIGI_A, -- n/a, actually
                  when HDMI1 | DP1  => DIGI_B,
                  when HDMI2 | DP2  => DIGI_C,
                  when HDMI3 | DP3  => DIGI_D,
                  when Analog       => VGA,
                  when others       => DIGI_A), -- n/a, actually
            when Ironlake =>           -- everything but eDP through FDI/PCH
              (if Port = eDP then
                  DIGI_A
               else
                 (case Pipe is   -- FDIs are fixed to the CPU pipe
                     when Primary   => DIGI_B,
                     when Secondary => DIGI_C,
                     when Tertiary  => DIGI_D)),
            when Tigerlake =>
                 (case Port is
                     when DP_TC1 | HDMI_TC1 | USBC1   => DDI_TC1,
                     when DP_TC2 | HDMI_TC2 | USBC2   => DDI_TC2,
                     when DP_TC3 | HDMI_TC3 | USBC3   => DDI_TC3,
                     when DP_TC4 | HDMI_TC4 | USBC4   => DDI_TC4,
                     when eDP =>
                        Combo_PHY (Config.Panel_Ports (Panel_1)),
                     when others =>
                        Combo_PHY (Port)),
            when others =>             -- everything but VGA directly on CPU
              (case Port is
                  when LVDS         => LVDS,    -- n/a, actually
                  when eDP          => DIGI_A,
                  when HDMI1 | DP1  => DIGI_B,
                  when HDMI2 | DP2  => DIGI_C,
                  when HDMI3 | DP3  => DIGI_D,
                  when Analog       => DIGI_E,
                  when others       => LVDS));  -- n/a, actually
   end To_GPU_Port;

   function To_PCH_Port (Port : Active_Port_Type) return PCH_Port
   is
   begin
      return
        (if Config.Has_Type_C_Ports
         then
           (case To_GPU_Port (Pipe_Index'First, Port) is
               when DIGI_A    => PCH_HDMI_A,
               when DIGI_B    => PCH_HDMI_B,
               when DIGI_C    => PCH_HDMI_C,
               when DDI_TC1   => PCH_TC1,
               when DDI_TC2   => PCH_TC2,
               when DDI_TC3   => PCH_TC3,
               when DDI_TC4   => PCH_TC4,
               when DDI_TC5   => PCH_TC5,
               when DDI_TC6   => PCH_TC6,
               when others    => PCH_LVDS)  -- n/a, actually
         else
           (case Port is
               when LVDS      => PCH_LVDS,
               when Analog    => PCH_DAC,
               when HDMI1     => PCH_HDMI_B,
               when HDMI2     => PCH_HDMI_C,
               when HDMI3     => PCH_HDMI_D,
               when DP1       => PCH_DP_B,
               when DP2       => PCH_DP_C,
               when DP3       => PCH_DP_D,
               when others    => PCH_LVDS)); -- n/a, actually
   end To_PCH_Port;

   function To_Display_Type (Port : Active_Port_Type) return Display_Type
   is
   begin
      return Display_Type'
        (case Port is
            when LVDS                        => LVDS,
            when eDP                         => DP,
            when Analog                      => VGA,
            when Physical_HDMI_Ports'Range   => HDMI,
            when Physical_DP_Ports'Range     => DP,
            when Physical_USBC_Ports'Range   => DP);
   end To_Display_Type;

   function To_Panel (Port : Active_Port_Type) return Panel_Control
   is
   begin
      for P in Valid_Panels loop
         if Port = Config.Panel_Ports (P) then
            return P;
         end if;
      end loop;
      return No_Panel;
   end To_Panel;

   function Highest_Dotclock (Configs : Pipe_Configs) return Frequency_Type
   is
      Max : Frequency_Type := Frequency_Type'First;
   begin
      for I in Pipe_Index loop
         if Configs (I).Port /= Disabled and
            Max < Configs (I).Mode.Dotclock
         then
            Max := Configs (I).Mode.Dotclock;
         end if;
      end loop;
      return Max;
   end Highest_Dotclock;

   procedure Limit_Dotclocks
     (Configs  : in out Pipe_Configs;
      Max      : in     Frequency_Type) is
   begin
      for I in Pipe_Index loop
         if Configs (I).Port /= Disabled and
            Max < Configs (I).Mode.Dotclock
         then
            Configs (I).Mode.Dotclock := Max;
         end if;
      end loop;
   end Limit_Dotclocks;

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

   -- Prepares link rate and lane count settings for an FDI connection.
   procedure Configure_FDI_Link
     (Port_Cfg : in out Port_Config;
      Success  :    out Boolean)
   with
      Post =>
         Port_Cfg.Mode.H_Visible = Port_Cfg'Old.Mode.H_Visible and
         Port_Cfg.Mode.V_Visible = Port_Cfg'Old.Mode.V_Visible
   is
   begin
      Port_Cfg.FDI.Receiver_Caps.Max_Link_Rate    := DP_Bandwidth_2_7;
      Port_Cfg.FDI.Receiver_Caps.Max_Lane_Count   :=
         Config.FDI_Lane_Count (Port_Cfg.Port);
      Port_Cfg.FDI.Receiver_Caps.Enhanced_Framing := True;

      DP_Info.Preferred_Link_Setting (Port_Cfg.FDI, Port_Cfg.Mode, Success);
   end Configure_FDI_Link;

   -- Derives an internal port config.
   --
   -- This is where the magic happens that hides the hardware details
   -- from libgfxinit's users. We have to map the pipe (Pipe_Index),
   -- the user visible port (Port_Type) and the modeline (Mode_Type)
   -- that we are supposed to output to an internal representation
   -- (Port_Config) that applies to the selected hardware generation
   -- (in GMA.Config).
   procedure Fill_Port_Config
     (Port_Cfg :    out Port_Config;
      Pipe     : in     Pipe_Index;
      Port     : in     Port_Type;
      Mode     : in     Mode_Type;
      Success  :    out Boolean)
   is
   begin
      Success :=
         (Config.Has_Tertiary_Pipe or Pipe <= Secondary) and then
         Config.Valid_Port (Port) and then
         Port /= Disabled; -- Valid_Port should already cover this, but the
                           -- array is writeable, so it's hard to prove this.

      if Success then
         Port_Cfg := Port_Config'
           (Port     => To_GPU_Port (Pipe, Port),
            PCH_Port => To_PCH_Port (Port),
            Display  => To_Display_Type (Port),
            Panel    => To_Panel (Port),
            Mode     => Mode,
            Is_FDI   => Config.Is_FDI_Port (Port),
            Is_eDP   => To_Display_Type (Port) = DP and
                        To_Panel (Port) /= No_Panel,
            FDI      => Default_DP,
            DP       => Default_DP);

         if Port_Cfg.Mode.BPC = Auto_BPC then
            Port_Cfg.Mode.BPC := Connector_Info.Default_BPC (Port_Cfg);
         end if;

         if Port_Cfg.Display = HDMI then
            declare
               pragma Assert (Config.HDMI_Max_Clock_24bpp * 8
                              / Port_Cfg.Mode.BPC >= Frequency_Type'First);
               Max_Dotclock : constant Frequency_Type :=
                  Config.HDMI_Max_Clock_24bpp * 8 / Port_Cfg.Mode.BPC;
            begin
               if Port_Cfg.Mode.Dotclock > Max_Dotclock then
                  pragma Debug (Debug.Put ("Dotclock "));
                  pragma Debug (Debug.Put_Int64 (Port_Cfg.Mode.Dotclock));
                  pragma Debug (Debug.Put (" too high, limiting to "));
                  pragma Debug (Debug.Put_Int64 (Max_Dotclock));
                  pragma Debug (Debug.Put_Line ("."));
                  Port_Cfg.Mode.Dotclock := Max_Dotclock;
               end if;
            end;
         end if;

         if Port_Cfg.Is_FDI then
            Configure_FDI_Link (Port_Cfg, Success);
         end if;
      else
         Port_Cfg := Port_Config'
           (Port     => GPU_Port'First,
            PCH_Port => PCH_Port'First,
            Display  => Display_Type'First,
            Panel    => No_Panel,
            Mode     => Invalid_Mode,
            Is_FDI   => False,
            Is_eDP   => False,
            FDI      => Default_DP,
            DP       => Default_DP);
      end if;
   end Fill_Port_Config;

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

   -- Validates that a given configuration should work with
   -- a given framebuffer.
   function Validate_Config
     (FB                : Framebuffer_Type;
      Mode              : Mode_Type;
      Pipe              : Pipe_Index)
      return Boolean
   is
   begin
      -- No downscaling
      -- Respect maximum scalable width
      -- VGA plane is only allowed on the primary pipe
      -- Only 32bpp RGB (ignored for VGA plane)
      -- Stride must be big enough and a multiple of 64 bytes or the tile size
      -- (ignored for VGA plane)
      -- Y-Tiling and rotation are only supported on newer generations (with
      -- Plane_Control)
      -- 90 degree rotations are only supported with Y-tiling
      return
         ((Rotated_Width (FB) = Mode.H_Visible and
           Rotated_Height (FB) = Mode.V_Visible) or
          (Rotated_Width (FB) <= Config.Maximum_Scalable_Width (Pipe) and
           Rotated_Width (FB) <= Mode.H_Visible and
           Rotated_Height (FB) <= Mode.V_Visible)) and
         (FB.Offset /= VGA_PLANE_FRAMEBUFFER_OFFSET or Pipe = Primary) and
         (FB.Offset = VGA_PLANE_FRAMEBUFFER_OFFSET or
          (FB.BPC = 8 and Valid_Stride (FB) and
           (Config.Has_Plane_Control or
            (FB.Tiling /= Y_Tiled and FB.Rotation = No_Rotation)) and
           (FB.Tiling = Y_Tiled or not Rotation_90 (FB))));
   end Validate_Config;

end HW.GFX.GMA.Config_Helpers;
