--
-- 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.Config_Helpers;
with HW.GFX.GMA.DP_Info;

package body HW.GFX.GMA.Transcoder is

   type Default_Transcoder_Array is array (Pipe_Index) of Transcoder_Index;
   Default_Transcoder : constant Default_Transcoder_Array :=
     (Primary     => Trans_A,
      Secondary   => Trans_B,
      Tertiary    => Trans_C);

   function Get_Idx (Pipe : Pipe_Index; Port : GPU_Port) return Transcoder_Index
   is
   begin
      return
        (if Config.Has_EDP_Transcoder and then Port = DIGI_A then
            Trans_EDP
         else
            Default_Transcoder (Pipe));
   end Get_Idx;

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

   TRANS_CLK_SEL_PORT_NONE : constant := 0 * 2 ** 29;
   TRANS_CLK_SEL_MASK : constant := 16#f000_0000#;

   type TRANS_CLK_SEL_PORT_Array is
      array (Digital_Port) of Word32;
   TRANS_CLK_SEL_PORT : constant TRANS_CLK_SEL_PORT_Array :=
     (DIGI_A => 0 * 2 ** 29,   -- DDI A is not selectable
      DIGI_B => 2 * 2 ** 29,
      DIGI_C => 3 * 2 ** 29,
      DIGI_D => 4 * 2 ** 29,
      DIGI_E => 5 * 2 ** 29);

   function TGL_TRANS_CLK_SEL_PORT (Port : TGL_Digital_Port) return Word32 is
   (case Port is
      when DIGI_A  => 1 * 2 ** 28,
      when DIGI_B  => 2 * 2 ** 28,
      when DIGI_C  => 3 * 2 ** 28,
      when DDI_TC1 => 4 * 2 ** 28,
      when DDI_TC2 => 5 * 2 ** 28,
      when DDI_TC3 => 6 * 2 ** 28,
      when DDI_TC4 => 7 * 2 ** 28,
      when DDI_TC5 => 8 * 2 ** 28,
      when DDI_TC6 => 9 * 2 ** 28,
      when others  => 0);

   TRANS_CONF_ENABLE          : constant := 1 * 2 ** 31;
   TRANS_CONF_ENABLED_STATUS  : constant := 1 * 2 ** 30;
   TRANS_CONF_ENABLE_DITHER   : constant := 1 * 2 **  4;

   type BPC_Array is array (BPC_Type) of Word32;
   TRANS_CONF_BPC : constant BPC_Array :=
     (6        => 2 * 2 ** 5,
      8        => 0 * 2 ** 5,
      10       => 1 * 2 ** 5,
      12       => 3 * 2 ** 5,
      others   => 0 * 2 ** 5);   -- default to 8 BPC

   function BPC_Conf (BPC : BPC_Type; Dither : Boolean) return Word32 is
   begin
      return
        (if Config.Has_Pipeconf_BPC then TRANS_CONF_BPC (BPC) else 0) or
        (if Dither                  then TRANS_CONF_ENABLE_DITHER else 0);
   end BPC_Conf;

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

   DDI_FUNC_CTL_ENABLE                 : constant := 1 * 2 ** 31;
   DDI_FUNC_CTL_MODE_SELECT_MASK       : constant := 7 * 2 ** 24;
   DDI_FUNC_CTL_MODE_SELECT_HDMI       : constant := 0 * 2 ** 24;
   DDI_FUNC_CTL_MODE_SELECT_DVI        : constant := 1 * 2 ** 24;
   DDI_FUNC_CTL_MODE_SELECT_DP_SST     : constant := 2 * 2 ** 24;
   DDI_FUNC_CTL_MODE_SELECT_DP_MST     : constant := 3 * 2 ** 24;
   DDI_FUNC_CTL_MODE_SELECT_FDI        : constant := 4 * 2 ** 24;

   type DDI_Select_Array is array (Digital_Port) of Word32;
   DDI_FUNC_CTL_DDI_SELECT : constant DDI_Select_Array :=
     (DIGI_A => 0 * 2 ** 28,
      DIGI_B => 1 * 2 ** 28,
      DIGI_C => 2 * 2 ** 28,
      DIGI_D => 3 * 2 ** 28,
      DIGI_E => 4 * 2 ** 28);

   function TGL_DDI_FUNC_CTL_DDI_SELECT (Port : TGL_Digital_Port)
      return Word32
   is
     (case Port is
      when DIGI_A  => 1 * 2 ** 27,
      when DIGI_B  => 2 * 2 ** 27,
      when DIGI_C  => 3 * 2 ** 27,
      when DDI_TC1 => 4 * 2 ** 27,
      when DDI_TC2 => 5 * 2 ** 27,
      when DDI_TC3 => 6 * 2 ** 27,
      when DDI_TC4 => 7 * 2 ** 27,
      when DDI_TC5 => 8 * 2 ** 27,
      when DDI_TC6 => 9 * 2 ** 27,
      when others  => 0);

   type DDI_Mode_Array is array (Display_Type) of Word32;
   DDI_FUNC_CTL_MODE_SELECT : constant DDI_Mode_Array :=
     (VGA      => DDI_FUNC_CTL_MODE_SELECT_FDI,
      HDMI     => DDI_FUNC_CTL_MODE_SELECT_DVI,
      DP       => DDI_FUNC_CTL_MODE_SELECT_DP_SST,
      others   => 0);

   type HV_Sync_Array is array (Boolean) of Word32;
   DDI_FUNC_CTL_VSYNC : constant HV_Sync_Array :=
     (False => 0 * 2 ** 17,
      True  => 1 * 2 ** 17);
   DDI_FUNC_CTL_HSYNC : constant HV_Sync_Array :=
     (False => 0 * 2 ** 16,
      True  => 1 * 2 ** 16);

   DDI_FUNC_CTL_EDP_SELECT_MASK        : constant := 7 * 2 ** 12;
   DDI_FUNC_CTL_EDP_SELECT_ALWAYS_ON   : constant := 0 * 2 ** 12;
   DDI_FUNC_CTL_EDP_SELECT : constant array (Pipe_Index) of Word32 :=
     (Primary     => 4 * 2 ** 12,
      Secondary   => 5 * 2 ** 12,
      Tertiary    => 6 * 2 ** 12);

   type Port_Width_Array is array (DP_Lane_Count) of Word32;
   DDI_FUNC_CTL_PORT_WIDTH : constant Port_Width_Array :=
     (DP_Lane_Count_1 => 0 * 2 ** 1,
      DP_Lane_Count_2 => 1 * 2 ** 1,
      DP_Lane_Count_4 => 3 * 2 ** 1);

   DDI_FUNC_CTL_BPC : constant BPC_Array :=
     (6        => 2 * 2 ** 20,
      8        => 0 * 2 ** 20,
      10       => 1 * 2 ** 20,
      12       => 3 * 2 ** 20,
      others   => 0 * 2 ** 20);  -- default to 8 BPC

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

   TRANS_MSA_MISC_SYNC_CLK : constant := 1 * 2 ** 0;
   TRANS_MSA_MISC_BPC      : constant BPC_Array :=
     (6        => 0 * 2 ** 5,
      8        => 1 * 2 ** 5,
      10       => 2 * 2 ** 5,
      12       => 3 * 2 ** 5,
      16       => 4 * 2 ** 5,
      others   => 1 * 2 ** 5);   -- default to 8 BPC

   function TRANS_DATA_M_TU (Transfer_Unit : Positive) return Word32 is
   begin
      return Shift_Left (Word32 (Transfer_Unit - 1), 25);
   end TRANS_DATA_M_TU;

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

   function Encode (LSW, MSW : Pos32) return Word32 is
   begin
      return Shift_Left (Word32 (MSW - 1), 16) or Word32 (LSW - 1);
   end Encode;

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

   procedure Setup_Link
     (Trans : Transcoder_Regs;
      Link  : DP_Link;
      Mode  : Mode_Type)
   with
      Global => (In_Out => Registers.Register_State),
      Depends => (Registers.Register_State =>+ (Trans, Link, Mode))
   is
      Data_M, Link_M : DP_Info.M_Type;
      Data_N, Link_N : DP_Info.N_Type;
   begin
      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));

      DP_Info.Calculate_M_N
        (Link     => Link,
         Mode     => Mode,
         Data_M   => Data_M,
         Data_N   => Data_N,
         Link_M   => Link_M,
         Link_N   => Link_N);

      Registers.Write
        (Register => Trans.DATA_M1,
         Value    => TRANS_DATA_M_TU (64) or
                     Word32 (Data_M));
      Registers.Write
        (Register => Trans.DATA_N1,
         Value    => Word32 (Data_N));

      Registers.Write
        (Register => Trans.LINK_M1,
         Value    => Word32 (Link_M));
      Registers.Write
        (Register => Trans.LINK_N1,
         Value    => Word32 (Link_N));

      if Config.Has_Pipe_MSA_Misc then
         Registers.Write
           (Register => Trans.MSA_MISC,
            Value    => TRANS_MSA_MISC_SYNC_CLK or
                        TRANS_MSA_MISC_BPC (Mode.BPC));
      end if;
   end Setup_Link;

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

   procedure Setup
     (Pipe     : Pipe_Index;
      Port_Cfg : Port_Config)
   is
      use type HW.GFX.GMA.Registers.Registers_Invalid_Index;

      Trans : Transcoder_Regs renames
               Transcoders (Get_Idx (Pipe, Port_Cfg.Port));
      M : constant Mode_Type := Port_Cfg.Mode;
   begin
      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));

      if Config.Has_Trans_Clk_Sel and then
         not Config.Need_Early_Transcoder_Setup and then
         Trans.CLK_SEL /= Registers.Invalid_Register and then
         Port_Cfg.Port in Digital_Port
      then
         Registers.Write
           (Register => Trans.CLK_SEL,
            Value    => TRANS_CLK_SEL_PORT (Port_Cfg.Port));
      end if;

      if Port_Cfg.Is_FDI then
         Setup_Link (Trans, Port_Cfg.FDI, Port_Cfg.Mode);
      elsif Port_Cfg.Display = DP then
         Setup_Link (Trans, Port_Cfg.DP, Port_Cfg.Mode);
      end if;

      Registers.Write (Trans.HTOTAL,   Encode (M.H_Visible,    M.H_Total));
      Registers.Write (Trans.HBLANK,   Encode (M.H_Visible,    M.H_Total));
      Registers.Write (Trans.HSYNC,    Encode (M.H_Sync_Begin, M.H_Sync_End));
      Registers.Write (Trans.VTOTAL,   Encode (M.V_Visible,    M.V_Total));
      Registers.Write (Trans.VBLANK,   Encode (M.V_Visible,    M.V_Total));
      Registers.Write (Trans.VSYNC,    Encode (M.V_Sync_Begin, M.V_Sync_End));
   end Setup;

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

   procedure Enable_Pipe_Clock (Pipe : Pipe_Index; Port_Cfg : Port_Config)
   is
      use type HW.GFX.GMA.Registers.Registers_Invalid_Index;

      Trans : Transcoder_Regs renames
               Transcoders (Get_Idx (Pipe, Port_Cfg.Port));
   begin
      if Config.Need_Early_Transcoder_Setup and then
         Trans.CLK_SEL /= Registers.Invalid_Register and then
         Port_Cfg.Port in TGL_Digital_Port
      then
         Registers.Unset_And_Set_Mask
           (Register   => Trans.CLK_SEL,
            Mask_Unset => TRANS_CLK_SEL_MASK,
            Mask_Set   => TGL_TRANS_CLK_SEL_PORT (Port_Cfg.Port));
      end if;
   end Enable_Pipe_Clock;

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

   procedure Configure (Pipe : Pipe_Index; Port_Cfg : Port_Config; Scale : Boolean)
   is
      Trans : Transcoder_Regs renames
               Transcoders (Get_Idx (Pipe, Port_Cfg.Port));
      Lane_Count : constant DP_Lane_Count :=
        (if Port_Cfg.Is_FDI then Port_Cfg.FDI.Lane_Count else Port_Cfg.DP.Lane_Count);
      EDP_Select : constant Word32 :=
        (if Config.Has_TGL_DDI_Select
         then 0
         else
           (if Pipe = Primary and
            (not Config.Use_PDW_For_EDP_Scaling or else not Scale)
            then
               DDI_FUNC_CTL_EDP_SELECT_ALWAYS_ON
            else
               DDI_FUNC_CTL_EDP_SELECT (Pipe)));
      DDI_Select : constant Word32 :=
        (if Config.Has_TGL_DDI_Select and Port_Cfg.Port in TGL_Digital_Port then
            TGL_DDI_FUNC_CTL_DDI_SELECT (Port_Cfg.Port)
         else
            (if Port_Cfg.Port in Digital_Port
             then
                DDI_FUNC_CTL_DDI_SELECT (Port_Cfg.Port)
             else 0));
   begin
      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
      if Config.Has_Pipe_DDI_Func then
         if Is_Digital_Port (Port_Cfg.Port) then
            Registers.Write
              (Register => Trans.DDI_FUNC_CTL,
               Value    => DDI_Select or
                        DDI_FUNC_CTL_MODE_SELECT (Port_Cfg.Display) or
                        DDI_FUNC_CTL_BPC (Port_Cfg.Mode.BPC) or
                        DDI_FUNC_CTL_VSYNC (Port_Cfg.Mode.V_Sync_Active_High) or
                        DDI_FUNC_CTL_HSYNC (Port_Cfg.Mode.H_Sync_Active_High) or
                        EDP_Select or
                        DDI_FUNC_CTL_PORT_WIDTH (Lane_Count));
         end if;
      end if;
   end Configure;

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

   procedure On
     (Pipe     : Pipe_Index;
      Port_Cfg : Port_Config;
      Dither   : Boolean;
      Scale    : Boolean)
   is
      use type HW.GFX.GMA.Registers.Registers_Invalid_Index;
      Trans : Transcoder_Regs renames
               Transcoders (Get_Idx (Pipe, Port_Cfg.Port));
      PIPE_ARB_USE_PROG_SLOTS : constant := 1 * 2 ** 13;
   begin
      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
      if not Config.Need_Early_Transcoder_Setup then
         Configure (Pipe, Port_Cfg, Scale);
      end if;

      if Config.Has_Pipe_DDI_Func and Is_Digital_Port (Port_Cfg.Port) then
         Registers.Set_Mask
           (Register => Trans.DDI_FUNC_CTL,
            Mask     => DDI_FUNC_CTL_ENABLE);
      end if;

      if Config.Need_Pipe_Arb_Slots and then
         Trans.PIPE_ARB_CTL /= Registers.Invalid_Register
      then
         Registers.Set_Mask
           (Register => Trans.PIPE_ARB_CTL,
            Mask     => PIPE_ARB_USE_PROG_SLOTS);
      end if;

      Registers.Write
        (Register => Trans.CONF,
         Value    => TRANS_CONF_ENABLE or
                     (if not Config.Has_Pipeconf_Misc then
                        BPC_Conf (Port_Cfg.Mode.BPC, Dither) else 0));
      Registers.Posting_Read (Trans.CONF);
   end On;

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

   procedure Trans_Off (Trans : Transcoder_Regs)
   is
      Enabled : Boolean;
   begin
      Registers.Is_Set_Mask (Trans.CONF, TRANS_CONF_ENABLE, Enabled);

      if Enabled then
         Registers.Unset_Mask (Trans.CONF, TRANS_CONF_ENABLE);
      end if;

      -- Workaround for Broadwell:
      -- Status may be wrong if pipe hasn't been enabled since reset.
      if not Config.Pipe_Enabled_Workaround or else Enabled then
         -- synchronously wait until pipe is truly off
         Registers.Wait_Unset_Mask
           (Register => Trans.CONF,
            Mask     => TRANS_CONF_ENABLED_STATUS,
            TOut_MS  => 40);
      end if;

      if Config.Has_Pipe_DDI_Func then
         Registers.Is_Set_Mask (Trans.DDI_FUNC_CTL, DDI_FUNC_CTL_ENABLE, Enabled);
         if Enabled then
            Registers.Write (Trans.DDI_FUNC_CTL, 0);
         end if;
      end if;
   end Trans_Off;

   procedure Off (Pipe : Pipe_Index)
   is
      DDI_Func_Ctl : Word32;
   begin
      if Config.Has_EDP_Transcoder then
         Registers.Read (Registers.PIPE_EDP_DDI_FUNC_CTL, DDI_Func_Ctl);
         DDI_Func_Ctl := DDI_Func_Ctl and DDI_FUNC_CTL_EDP_SELECT_MASK;

         if (Pipe = Primary and
             DDI_Func_Ctl = DDI_FUNC_CTL_EDP_SELECT_ALWAYS_ON) or
            DDI_Func_Ctl = DDI_FUNC_CTL_EDP_SELECT (Pipe)
         then
            Trans_Off (Transcoders (Trans_EDP));
         end if;
      end if;

      Trans_Off (Transcoders (Default_Transcoder (Pipe)));
   end Off;

   procedure Clk_Off (Pipe : Pipe_Index)
   is
      use type Registers.Registers_Invalid_Index;

      Trans : Transcoder_Regs renames Transcoders (Default_Transcoder (Pipe));
   begin
      if Config.Has_Trans_Clk_Sel and then
         Trans.CLK_SEL /= Registers.Invalid_Register
      then
         Registers.Write (Trans.CLK_SEL, TRANS_CLK_SEL_PORT_NONE);
      end if;
   end Clk_Off;

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

   SRD_CTL_ENABLE          : constant := 1 * 2 ** 31;
   SRD_STATUS_STATE_MASK   : constant := 7 * 2 ** 29;

   type SRD_Regs is record
      CTL     : Registers.Registers_Index;
      STATUS  : Registers.Registers_Index;
   end record;
   type SRD_Per_Pipe_Regs is array (Transcoder_Index) of SRD_Regs;

   SRD : constant SRD_Per_Pipe_Regs := SRD_Per_Pipe_Regs'
     (Trans_EDP   => SRD_Regs'
        (CTL      => Registers.SRD_CTL_EDP,
         STATUS   => Registers.SRD_STATUS_EDP),
      Trans_A     => SRD_Regs'
        (CTL      => Registers.SRD_CTL_A,
         STATUS   => Registers.SRD_STATUS_A),
      Trans_B     => SRD_Regs'
        (CTL      => Registers.SRD_CTL_B,
         STATUS   => Registers.SRD_STATUS_B),
      Trans_C     => SRD_Regs'
        (CTL      => Registers.SRD_CTL_C,
         STATUS   => Registers.SRD_STATUS_C));

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

   procedure PSR_Off
   is
      Enabled : Boolean;
   begin
      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));

      if Config.Has_Per_Pipe_SRD then
         declare
            First_Transcoder : constant Transcoder_Index :=
              (if Config.Has_EDP_Transcoder then Trans_EDP else Trans_A);
         begin
            for P in Transcoder_Index range First_Transcoder .. Transcoder_Index'Last loop
               Registers.Is_Set_Mask (SRD (P).CTL, SRD_CTL_ENABLE, Enabled);
               if Enabled then
                  Registers.Unset_Mask (SRD (P).CTL, SRD_CTL_ENABLE);
                  Registers.Wait_Unset_Mask (SRD (P).STATUS, SRD_STATUS_STATE_MASK);

                  pragma Debug (Debug.Put_Line ("Disabled PSR."));
               end if;
            end loop;
         end;
      else
         Registers.Is_Set_Mask (Registers.SRD_CTL, SRD_CTL_ENABLE, Enabled);
         if Enabled then
            Registers.Unset_Mask (Registers.SRD_CTL, SRD_CTL_ENABLE);
            Registers.Wait_Unset_Mask (Registers.SRD_STATUS, SRD_STATUS_STATE_MASK);

            pragma Debug (Debug.Put_Line ("Disabled PSR."));
         end if;
      end if;
   end PSR_Off;

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

end HW.GFX.GMA.Transcoder;
