--
-- Copyright (C) 2022 Google, LLC
--
-- 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.Config;
with HW.GFX.GMA.Registers;
with HW.GFX.DP_Info;
with HW.GFX.GMA.PCode;

with HW.Debug;
with GNAT.Source_Info;

use type HW.Word64;

package body HW.GFX.GMA.Connectors.TC is

   function HIP_INDEX_REG (P : USBC_Port) return Registers.Registers_Index
   is
     (if P <= DDI_TC4
      then Registers.HIP_INDEX_REG0
      else Registers.HIP_INDEX_REG1);

   function HIP_INDEX_VAL (P : USBC_Port; Val : Word32) return Word32 is
     (Val * 2 ** (8 * ((GPU_Port'Pos (P) - GPU_Port'Pos (DDI_TC1)) mod 4)));

   type Port_Regs_Array is array (USBC_Port) of Registers.Registers_Index;
   DKL_DP_MODE : constant Port_Regs_Array :=
     (DDI_TC1 => Registers.DKL_DP_MODE_1,
      DDI_TC2 => Registers.DKL_DP_MODE_2,
      DDI_TC3 => Registers.DKL_DP_MODE_3,
      DDI_TC4 => Registers.DKL_DP_MODE_4,
      DDI_TC5 => Registers.DKL_DP_MODE_5,
      DDI_TC6 => Registers.DKL_DP_MODE_6);

   function DP_PIN_ASSIGNMENT_SHIFT (P : USBC_Port) return Natural is
     (case P is
      when DDI_TC1 => 0,
      when DDI_TC2 => 4,
      when DDI_TC3 => 8,
      when DDI_TC4 => 12,
      when DDI_TC5 => 16,
      when DDI_TC6 => 20);

   DP_PIN_ASSIGNMENT_MASK : constant := 16#f#;

   TGL_PCODE_TCCOLD         : constant := 16#26#;
   TCCOLD_BLOCK_REQ         : constant := 16#00#;
   TCCOLD_UNBLOCK_REQ       : constant := 16#01#;
   TCCOLD_BLOCK_RESULT_FAIL : constant := 16#01#;

   type Fia_Regs_Record is record
      PORT_TX_DFLEXDPMLE1 : Registers.Registers_Index;
      PORT_TX_DFLEXDPSP   : Registers.Registers_Index;
      PORT_TX_DFLEXDPPMS  : Registers.Registers_Index;
      PORT_TX_DFLEXDPCSSS : Registers.Registers_Index;
      PORT_TX_DFLEXPA1    : Registers.Registers_Index;
   end record;

   Fia_Regs : constant array (USBC_Port) of Fia_Regs_Record :=
     (DDI_TC1 =>
        (Registers.PORT_TX_DFLEXDPMLE1_FIA1,
         Registers.PORT_TX_DFLEXDPSP_FIA1,
         Registers.PORT_TX_DFLEXDPPMS_FIA1,
         Registers.PORT_TX_DFLEXDPCSSS_FIA1,
         Registers.PORT_TX_DFLEXPA1_FIA1),
      DDI_TC2 =>
        (Registers.PORT_TX_DFLEXDPMLE1_FIA1,
         Registers.PORT_TX_DFLEXDPSP_FIA1,
         Registers.PORT_TX_DFLEXDPPMS_FIA1,
         Registers.PORT_TX_DFLEXDPCSSS_FIA1,
         Registers.PORT_TX_DFLEXPA1_FIA1),
      DDI_TC3 =>
        (Registers.PORT_TX_DFLEXDPMLE1_FIA2,
         Registers.PORT_TX_DFLEXDPSP_FIA2,
         Registers.PORT_TX_DFLEXDPPMS_FIA2,
         Registers.PORT_TX_DFLEXDPCSSS_FIA2,
         Registers.PORT_TX_DFLEXPA1_FIA2),
      DDI_TC4 =>
        (Registers.PORT_TX_DFLEXDPMLE1_FIA2,
         Registers.PORT_TX_DFLEXDPSP_FIA2,
         Registers.PORT_TX_DFLEXDPPMS_FIA2,
         Registers.PORT_TX_DFLEXDPCSSS_FIA2,
         Registers.PORT_TX_DFLEXPA1_FIA2),
      DDI_TC5 =>
        (Registers.PORT_TX_DFLEXDPMLE1_FIA3,
         Registers.PORT_TX_DFLEXDPSP_FIA3,
         Registers.PORT_TX_DFLEXDPPMS_FIA3,
         Registers.PORT_TX_DFLEXDPCSSS_FIA3,
         Registers.PORT_TX_DFLEXPA1_FIA3),
      DDI_TC6 =>
        (Registers.PORT_TX_DFLEXDPMLE1_FIA3,
         Registers.PORT_TX_DFLEXDPSP_FIA3,
         Registers.PORT_TX_DFLEXDPPMS_FIA3,
         Registers.PORT_TX_DFLEXDPCSSS_FIA3,
         Registers.PORT_TX_DFLEXPA1_FIA3));

   function Fia_Index (Port : USBC_Port) return Natural
   is (case Port is
       when DDI_TC1 | DDI_TC3 | DDI_TC5 => 0,
       when DDI_TC2 | DDI_TC4 | DDI_TC6 => 1);

   function DFLEXDPMLE1_DPMLETC_MASK (Port : USBC_Port) return Word32 is
      (Shift_Left (15, 4 * Fia_Index (Port)));
   function DFLEXDPMLE1_DPMLETC_ML0 (Port : USBC_Port) return Word32 is
      (Shift_Left (1, 4 * Fia_Index (Port)));
   function DFLEXDPMLE1_DPMLETC_ML1_0 (Port : USBC_Port) return Word32 is
      (Shift_Left (3, 4 * Fia_Index (Port)));
   function DFLEXDPMLE1_DPMLETC_ML3 (Port : USBC_Port) return Word32 is
      (Shift_Left (8, 4 * Fia_Index (Port)));
   function DFLEXDPMLE1_DPMLETC_ML3_2 (Port : USBC_Port) return Word32 is
      (Shift_Left (12, 4 * Fia_Index (Port)));
   function DFLEXDPMLE1_DPMLETC_ML3_0 (Port : USBC_Port) return Word32 is
      (Shift_Left (15, 4 * Fia_Index (Port)));
   function DP_PHY_MODE_STATUS_COMPLETE (Port : USBC_Port) return Word32 is
      (Shift_Left (1, Fia_Index (Port)));
   function DP_PHY_MODE_STATUS_NOT_SAFE (Port : USBC_Port) return Word32 is
      (Shift_Left (1, Fia_Index (Port)));
   function TC_LIVE_STATE_TC (Port : USBC_Port) return Word32 is
      (Shift_Left (1, Fia_Index (Port) * 8 + 5));
   function DP_LANE_ASSIGNMENT_MASK (Port : USBC_Port) return Word32 is
      (Shift_Left (16#f#, Fia_Index (Port) * 8));
   function DP_LANE_ASSIGNMENT_SHIFT (Port : USBC_Port) return Natural is
      (Fia_Index (Port) * 8);

   DDI_BUF_CTL_BUFFER_ENABLE        : constant :=      1 * 2 ** 31;
   DDI_BUF_CTL_TRANS_SELECT_MASK    : constant :=  16#f# * 2 ** 24;
   DDI_BUF_CTL_PORT_REVERSAL        : constant :=      1 * 2 ** 16;
   DDI_BUF_CTL_PORT_WIDTH_MASK      : constant :=      7 * 2 **  1;
   DDI_BUF_CTL_PORT_WIDTH_1_LANE    : constant :=      0 * 2 **  1;
   DDI_BUF_CTL_PORT_WIDTH_2_LANES   : constant :=      1 * 2 **  1;
   DDI_BUF_CTL_PORT_WIDTH_4_LANES   : constant :=      3 * 2 **  1;
   DDI_BUF_CTL_IDLE_STATUS          : constant :=      1 * 2 **  7;

   DDI_BUF_CTL_PORT_WIDTH : constant array (DP_Lane_Count) of Word32 :=
     (HW.GFX.DP_Lane_Count_1 => DDI_BUF_CTL_PORT_WIDTH_1_LANE,
      HW.GFX.DP_Lane_Count_2 => DDI_BUF_CTL_PORT_WIDTH_2_LANES,
      HW.GFX.DP_Lane_Count_4 => DDI_BUF_CTL_PORT_WIDTH_4_LANES);
   DDI_BUF_CTL : constant Port_Regs_Array :=
     (DDI_TC1  => Registers.DDI_BUF_CTL_USBC1,
      DDI_TC2  => Registers.DDI_BUF_CTL_USBC2,
      DDI_TC3  => Registers.DDI_BUF_CTL_USBC3,
      DDI_TC4  => Registers.DDI_BUF_CTL_USBC4,
      DDI_TC5  => Registers.DDI_BUF_CTL_USBC5,
      DDI_TC6  => Registers.DDI_BUF_CTL_USBC6);

   type Buffer_Trans is record
      Vswing_Control     : Word32;
      Preshoot_Control   : Word32;
      Deemphasis_Control : Word32;
   end record;

   type Buffer_Trans_Range is new Natural range 0 .. 9;
   type Buffer_Trans_Array is array (Buffer_Trans_Range) of Buffer_Trans;
   TGL_Buffer_Trans_DP_HBR2 : constant Buffer_Trans_Array :=
     ((16#7#, 16#0#, 16#00#),
      (16#5#, 16#0#, 16#05#),
      (16#2#, 16#0#, 16#0B#),
      (16#0#, 16#0#, 16#19#),
      (16#5#, 16#0#, 16#00#),
      (16#2#, 16#0#, 16#08#),
      (16#0#, 16#0#, 16#14#),
      (16#2#, 16#0#, 16#00#),
      (16#0#, 16#0#, 16#0B#),
      (16#0#, 16#0#, 16#00#));
   TGL_Buffer_Trans_DP_HBR : constant Buffer_Trans_Array :=
     ((16#7#, 16#0#, 16#00#),
      (16#5#, 16#0#, 16#05#),
      (16#2#, 16#0#, 16#0B#),
      (16#0#, 16#0#, 16#18#),
      (16#5#, 16#0#, 16#00#),
      (16#2#, 16#0#, 16#08#),
      (16#0#, 16#0#, 16#14#),
      (16#2#, 16#0#, 16#00#),
      (16#0#, 16#0#, 16#0B#),
      (16#0#, 16#0#, 16#00#));
   Buffer_Trans_HDMI : constant Buffer_Trans_Array :=
     ((16#7#, 16#0#, 16#0#),
      (16#6#, 16#0#, 16#0#),
      (16#4#, 16#0#, 16#0#),
      (16#2#, 16#0#, 16#0#),
      (16#0#, 16#0#, 16#0#),
      (16#0#, 16#0#, 16#5#),
      (16#0#, 16#0#, 16#6#),
      (16#0#, 16#0#, 16#7#),
      (16#0#, 16#0#, 16#8#),
      (16#0#, 16#0#, 16#A#));
   ADL_Buffer_Trans_DP_HBR2 : constant Buffer_Trans_Array :=
     ((16#7#, 16#0#, 16#00#),
      (16#5#, 16#0#, 16#04#),
      (16#2#, 16#0#, 16#0a#),
      (16#0#, 16#0#, 16#18#),
      (16#5#, 16#0#, 16#00#),
      (16#2#, 16#0#, 16#06#),
      (16#0#, 16#0#, 16#14#),
      (16#2#, 16#0#, 16#00#),
      (16#0#, 16#0#, 16#09#),
      (16#0#, 16#0#, 16#00#));
   ADL_Buffer_Trans_DP_HBR : constant Buffer_Trans_Array :=
     ((16#7#, 16#0#, 16#01#),
      (16#5#, 16#0#, 16#06#),
      (16#2#, 16#0#, 16#0b#),
      (16#0#, 16#0#, 16#17#),
      (16#5#, 16#0#, 16#00#),
      (16#2#, 16#0#, 16#08#),
      (16#0#, 16#0#, 16#14#),
      (16#2#, 16#0#, 16#00#),
      (16#0#, 16#0#, 16#0b#),
      (16#0#, 16#0#, 16#00#));

   type Vswing_Regs_Record is record
      DKL_TX_PMD_LANE_SUS  : Registers.Registers_Index;
      DKL_TX_DPCNTL0       : Registers.Registers_Index;
      DKL_TX_DPCNTL1       : Registers.Registers_Index;
      DKL_TX_DPCNTL2       : Registers.Registers_Index;
   end record;

   Vswing_Regs : constant array (USBC_Port) of Vswing_Regs_Record :=
     (DDI_TC1 =>
        (Registers.DKL_TX_PMD_LANE_SUS_1,
         Registers.DKL_TX_DPCNTL0_1,
         Registers.DKL_TX_DPCNTL1_1,
         Registers.DKL_TX_DPCNTL2_1),
      DDI_TC2 =>
        (Registers.DKL_TX_PMD_LANE_SUS_2,
         Registers.DKL_TX_DPCNTL0_2,
         Registers.DKL_TX_DPCNTL1_2,
         Registers.DKL_TX_DPCNTL2_2),
      DDI_TC3 =>
        (Registers.DKL_TX_PMD_LANE_SUS_3,
         Registers.DKL_TX_DPCNTL0_3,
         Registers.DKL_TX_DPCNTL1_3,
         Registers.DKL_TX_DPCNTL2_3),
      DDI_TC4 =>
        (Registers.DKL_TX_PMD_LANE_SUS_4,
         Registers.DKL_TX_DPCNTL0_4,
         Registers.DKL_TX_DPCNTL1_4,
         Registers.DKL_TX_DPCNTL2_4),
      DDI_TC5 =>
        (Registers.DKL_TX_PMD_LANE_SUS_5,
         Registers.DKL_TX_DPCNTL0_5,
         Registers.DKL_TX_DPCNTL1_5,
         Registers.DKL_TX_DPCNTL2_5),
      DDI_TC6 =>
        (Registers.DKL_TX_PMD_LANE_SUS_6,
         Registers.DKL_TX_DPCNTL0_6,
         Registers.DKL_TX_DPCNTL1_6,
         Registers.DKL_TX_DPCNTL2_6));

   procedure Set_HIP_For_Port (P : USBC_Port; N : Natural) is
   begin
      Registers.Write (HIP_INDEX_REG (P), HIP_INDEX_VAL (P, Word32 (N)));
   end Set_HIP_For_Port;

   subtype Pin_Assignment_Type is Natural range 0 .. 6;
   procedure Get_Pin_Assignment
     (P          : in    USBC_Port;
      Assignment : out   Pin_Assignment_Type)
   is
      Tmp : Word32;
      A : Word32;
   begin
      Registers.Read (Fia_Regs (P).PORT_TX_DFLEXPA1, Tmp);
      A := Shift_Right (Tmp, DP_PIN_ASSIGNMENT_SHIFT (P));
      A := A and DP_PIN_ASSIGNMENT_MASK;

      if Natural (A) in Pin_Assignment_Type then
         Assignment := Pin_Assignment_Type (A);
      else
         Assignment := Pin_Assignment_Type'First;
      end if;
   end Get_Pin_Assignment;

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

   procedure Program_DP_Mode (P : USBC_Port; Lane_Count : Natural)
   is
      MG_DP_MODE_CFG_DP_X1_MODE : constant := 1 * 2 ** 6;
      MG_DP_MODE_CFG_DP_X2_MODE : constant := 1 * 2 ** 7;
      DP_X_MODE_MASK : constant Word32 :=
         MG_DP_MODE_CFG_DP_X1_MODE or MG_DP_MODE_CFG_DP_X2_MODE;
      Assignment : Pin_Assignment_Type;
      Ln0, Ln1 : Word32;
   begin
      Set_HIP_For_Port (P, 0);
      Registers.Read (DKL_DP_MODE (P), Ln0);
      Set_HIP_For_Port (P, 1);
      Registers.Read (DKL_DP_MODE (P), Ln1);

      Ln0 := Ln0 and not DP_X_MODE_MASK;
      Ln1 := Ln1 and not DP_X_MODE_MASK;

      Get_Pin_Assignment (P, Assignment);
      case Assignment is
      when 0 =>
         if Lane_Count = 1 then
            Ln1 := Ln1 or MG_DP_MODE_CFG_DP_X1_MODE;
         else
            Ln0 := Ln0 or MG_DP_MODE_CFG_DP_X2_MODE;
            Ln1 := Ln1 or MG_DP_MODE_CFG_DP_X2_MODE;
         end if;
      when 1 =>
         if Lane_Count = 4 then
            Ln0 := Ln0 or MG_DP_MODE_CFG_DP_X2_MODE;
            Ln1 := Ln1 or MG_DP_MODE_CFG_DP_X2_MODE;
         end if;
      when 2 =>
         if Lane_Count = 2 then
            Ln0 := Ln0 or MG_DP_MODE_CFG_DP_X2_MODE;
            Ln1 := Ln1 or MG_DP_MODE_CFG_DP_X2_MODE;
         end if;
      when 3 | 4 | 5 | 6 =>
         if Lane_Count = 1 then
            Ln0 := Ln0 or MG_DP_MODE_CFG_DP_X1_MODE;
            Ln1 := Ln1 or MG_DP_MODE_CFG_DP_X1_MODE;
         else
            Ln0 := Ln0 or MG_DP_MODE_CFG_DP_X2_MODE;
            Ln1 := Ln1 or MG_DP_MODE_CFG_DP_X2_MODE;
         end if;
      end case;

      Set_HIP_For_Port (P, 0);
      Registers.Unset_And_Set_Mask (DKL_DP_MODE (P), DP_X_MODE_MASK, Ln0);
      Set_HIP_For_Port (P, 1);
      Registers.Unset_And_Set_Mask (DKL_DP_MODE (P), DP_X_MODE_MASK, Ln1);
   end Program_DP_Mode;

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

   procedure TC_Cold_Request
     (Request : in     TC_Cold_Request_Type;
      Success :    out Boolean)
   is
      Result : Word64;
   begin
      for Try in 1 .. 3 loop
         PCode.Mailbox_Read
           (MBox       => TGL_PCODE_TCCOLD,
            Command    => (if Request = Block
                           then TCCOLD_BLOCK_REQ
                           else TCCOLD_UNBLOCK_REQ),
            Wait_Ready => True,
            Reply      => Result,
            Success    => Success);

         if Success then
            Success := (Result and TCCOLD_BLOCK_RESULT_FAIL) = 0;
         end if;

         exit when Success;

         -- Wait 1 millisecond and try again
         Time.U_Delay (1_000);
      end loop;
   end TC_Cold_Request;

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

   procedure Claim
     (Port     : in     USBC_Port;
      DP_Alt   : in     Boolean;
      Success  :    out Boolean)
   is
   begin
      -- For legacy ports, this is supposed to be
      -- initialized once during boot, hence wait.
      Registers.Wait_Set_Mask
        (Register => Fia_Regs (Port).PORT_TX_DFLEXDPPMS,
         Mask     => DP_PHY_MODE_STATUS_COMPLETE (Port),
         TOut_MS  => (if DP_Alt then 0 else 100),
         Success  => Success);
      if not Success then
         pragma Debug (Debug.Put_Line ("DP PHY mode status not complete"));
         return;
      end if;

      Registers.Set_Mask
        (Register => Fia_Regs (Port).PORT_TX_DFLEXDPCSSS,
         Mask     => DP_PHY_MODE_STATUS_NOT_SAFE (Port));
   end Claim;

   procedure Claimed (Port : USBC_Port; Is_Claimed : out Boolean) is
   begin
      if Port not in DDI_TC1 .. Config.Last_TC_Port then
         Is_Claimed := False;
         return;
      end if;

      Registers.Is_Set_Mask
        (Register => Fia_Regs (Port).PORT_TX_DFLEXDPCSSS,
         Mask     => DP_PHY_MODE_STATUS_NOT_SAFE (Port),
         Result   => Is_Claimed);
   end Claimed;

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

   procedure Connect
     (Port     : in     USBC_Port;
      DP_Alt   : in     Boolean;
      Lanes    : in     DP_Lane_Count;
      Success  :    out Boolean)
   is
      procedure Get_Lane_Assignment_Count (Lanes : out DP_Lane_Count)
      is
         Lane_Mask : Word32;
         Tmp : Word32;
      begin
         Registers.Read (Fia_Regs (Port).PORT_TX_DFLEXDPSP, Tmp);
         Lane_Mask := Shift_Right (Tmp and DP_LANE_ASSIGNMENT_MASK (Port),
                                   DP_LANE_ASSIGNMENT_SHIFT (Port));
         Lanes :=
           (case Lane_Mask is
               when 16#1# | 16#2# | 16#4# | 16#8#  => DP_Lane_Count_1,
               when 16#3# | 16#c#                  => DP_Lane_Count_2,
               when 16#f#                          => DP_Lane_Count_4,
               when others                         => DP_Lane_Count_1);
      end Get_Lane_Assignment_Count;

      procedure Set_Lane_Count (Lanes : DP_Lane_Count) is
      begin
         Registers.Unset_And_Set_Mask
           (Register   => Fia_Regs (Port).PORT_TX_DFLEXDPMLE1,
            Mask_Unset => DFLEXDPMLE1_DPMLETC_MASK (Port),
            Mask_Set   =>
              (case Lanes is
                  -- ML0 is not lane-reversed, ML3 is reverse
                  when DP_Lane_Count_1 => DFLEXDPMLE1_DPMLETC_ML0 (Port),
                  -- ML1_0 is not reversed, ML3_2 is reverse
                  when DP_Lane_Count_2 => DFLEXDPMLE1_DPMLETC_ML1_0 (Port),
                  -- symmetric
                  when DP_Lane_Count_4 => DFLEXDPMLE1_DPMLETC_ML3_0 (Port)));
      end Set_Lane_Count;

      Assigned_Lanes : DP_Lane_Count;
   begin
      Claimed (Port, Success);
      if not Success then
         pragma Debug (Debug.Put_Line ("Tried to connect to unclaimed port."));
         return;
      end if;

      if DP_Alt then
         Registers.Is_Set_Mask
           (Register => Fia_Regs (Port).PORT_TX_DFLEXDPSP,
            Mask     => TC_LIVE_STATE_TC (Port),
            Result   => Success);
         if not Success then
            pragma Debug (Debug.Put_Line ("DP-Alt is not connected."));
            return;
         end if;

         Get_Lane_Assignment_Count (Assigned_Lanes);
         Set_Lane_Count (Assigned_Lanes);
      else
         Set_Lane_Count (Lanes);
      end if;
   end Connect;

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

   procedure Disconnect (Port : USBC_Port) is
   begin
      if Port in DDI_TC1 .. Config.Last_TC_Port then
         Registers.Unset_Mask
           (Register => Fia_Regs (Port).PORT_TX_DFLEXDPCSSS,
            Mask     => DP_PHY_MODE_STATUS_NOT_SAFE (Port));
      end if;
   end Disconnect;

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

   procedure Set_Vswing_And_Deemphasis
      (Port      : USBC_Port;
       Buf_Trans : Buffer_Trans)
   is
      -- Preshoot Coeff, Deemphasis Coeff, VSwing Control,
      DPcnt_Mask : constant Word32 := 16#3_ff07#;
      DPcnt_Val : constant Word32 :=
         Buf_Trans.Vswing_Control or
         Shift_Left (Buf_Trans.Deemphasis_Control, 8) or
         Shift_Left (Buf_Trans.Preshoot_Control, 13);
      DKL_TX_DP20BITMODE : constant := 1 * 2 ** 2;
   begin
      for Lane in 0 .. 1 loop
         Set_HIP_For_Port (Port, Lane);
         Registers.Write (Vswing_Regs (Port).DKL_TX_PMD_LANE_SUS, 0);
         Registers.Unset_And_Set_Mask
            (Vswing_Regs (Port).DKL_TX_DPCNTL0, DPcnt_Mask, DPcnt_Val);
         Registers.Unset_And_Set_Mask
            (Vswing_Regs (Port).DKL_TX_DPCNTL1, DPcnt_Mask, DPcnt_Val);
         Registers.Unset_Mask
            (Vswing_Regs (Port).DKL_TX_DPCNTL2, DKL_TX_DP20BITMODE);
      end loop;

   end Set_Vswing_And_Deemphasis;

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

   procedure Set_Signal_Levels
     (Port        : USBC_Port;
      Link        : DP_Link;
      Train_Set   : DP_Info.Train_Set)
   is
      function To_Buf_Trans_Index
         (Set : DP_Info.Train_Set) return Buffer_Trans_Range
      is
      begin
         case Set.Voltage_Swing is
         when DP_Info.VS_Level_0 =>
            case Set.Pre_Emph is
            when DP_Info.Emph_Level_0 => return 0;
            when DP_Info.Emph_Level_1 => return 1;
            when DP_Info.Emph_Level_2 => return 2;
            when DP_Info.Emph_Level_3 => return 3;
            end case;
         when DP_Info.VS_Level_1 =>
            case Set.Pre_Emph is
            when DP_Info.Emph_Level_0 => return 4;
            when DP_Info.Emph_Level_1 => return 5;
            when DP_Info.Emph_Level_2 => return 6;
            when others =>               return 0;
            end case;
         when DP_Info.VS_Level_2 =>
            case Set.Pre_Emph is
            when DP_Info.Emph_Level_0 => return 7;
            when DP_Info.Emph_Level_1 => return 8;
            when others =>               return 0;
            end case;
         when DP_Info.VS_Level_3 =>
            case Set.Pre_Emph is
            when DP_Info.Emph_Level_0 => return 9;
            when others =>               return 0;
            end case;
         end case;
      end To_Buf_Trans_Index;

      Was_Enabled : Boolean;
      Buf_Trans : Buffer_Trans;
      Entry_Index : constant Buffer_Trans_Range :=
         To_Buf_Trans_Index (Train_Set);
   begin
      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));

      Registers.Is_Set_Mask
        (Register => DDI_BUF_CTL (Port),
         Mask     => DDI_BUF_CTL_BUFFER_ENABLE,
         Result   => Was_Enabled);

      if Config.Has_TGL_Buffer_Translations then
         if Link.Bandwidth > DP_Bandwidth_2_7 then
            Buf_Trans := TGL_Buffer_Trans_DP_HBR2 (Entry_Index);
         else
            Buf_Trans := TGL_Buffer_Trans_DP_HBR (Entry_Index);
         end if;
      else
         if Link.Bandwidth > DP_Bandwidth_2_7 then
            Buf_Trans := ADL_Buffer_Trans_DP_HBR2 (Entry_Index);
         else
            Buf_Trans := ADL_Buffer_Trans_DP_HBR (Entry_Index);
         end if;
      end if;

      Set_Vswing_And_Deemphasis (Port, Buf_Trans);

      Registers.Unset_And_Set_Mask
        (Register    => DDI_BUF_CTL (Port),
         Mask_Unset  => DDI_BUF_CTL_TRANS_SELECT_MASK or
                        DDI_BUF_CTL_PORT_REVERSAL or
                        DDI_BUF_CTL_PORT_WIDTH_MASK,
         Mask_Set    => DDI_BUF_CTL_BUFFER_ENABLE or
                        DDI_BUF_CTL_PORT_WIDTH (Link.Lane_Count));
      Registers.Posting_Read (DDI_BUF_CTL (Port));

      if not Was_Enabled then
         Registers.Wait_Unset_Mask (DDI_BUF_CTL (Port), DDI_BUF_CTL_IDLE_STATUS);
      end if;
   end Set_Signal_Levels;

   procedure Enable_HDMI (Port : USBC_Port)
   is
      HDMI_Lane_Count : constant := 4;
      Buf_Trans : constant Buffer_Trans :=
         Buffer_Trans_HDMI (Buffer_Trans_HDMI'Last);
   begin
      Program_DP_Mode (Port, HDMI_Lane_Count);
      Set_Vswing_And_Deemphasis (Port, Buf_Trans);

      Registers.Unset_And_Set_Mask
           (Register    => DDI_BUF_CTL (Port),
            Mask_Unset  => DDI_BUF_CTL_TRANS_SELECT_MASK or
                           DDI_BUF_CTL_PORT_REVERSAL or
                           DDI_BUF_CTL_PORT_WIDTH_MASK,
            Mask_Set    => DDI_BUF_CTL_BUFFER_ENABLE);

      Registers.Wait_Unset_Mask
        (Register => DDI_BUF_CTL (Port),
         Mask     => DDI_BUF_CTL_IDLE_STATUS,
         TOut_MS  => 1);
   end Enable_HDMI;

end HW.GFX.GMA.Connectors.TC;
