gma tgl: Add connector programming

This patch adds support for enabling displays on both combo PHY ports
and Type-C ports over DP-Alt mode.

Verified eDP, HDMI (not Type-C),  and DP Alt mode on google/delbin.

Change-Id: I908e8bef8451d21eecde9ce6defddc2b3df7f738
Signed-off-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
Signed-off-by: Nico Huber <nico.huber@secunet.com>
Reviewed-on: https://review.sourcearcade.org/c/libgfxinit/+/469
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Tested-by: Nico Huber <nico.h@gmx.de>
diff --git a/common/tigerlake/hw-gfx-gma-connectors.adb b/common/tigerlake/hw-gfx-gma-connectors.adb
index 0870288..02a208b 100644
--- a/common/tigerlake/hw-gfx-gma-connectors.adb
+++ b/common/tigerlake/hw-gfx-gma-connectors.adb
@@ -12,34 +12,296 @@
 -- GNU General Public License for more details.
 --
 
+with HW.GFX.GMA.DP_Aux_Ch;
+with HW.GFX.DP_Training;
+with HW.GFX.GMA.Combo_Phy;
 with HW.GFX.GMA.Config;
+with HW.GFX.GMA.Config_Helpers;
+with HW.GFX.GMA.Connectors.TC;
+with HW.GFX.GMA.Connectors.Combo_Phy;
+with HW.GFX.GMA.DP_Aux_Request;
+with HW.GFX.GMA.DP_Info;
 with HW.GFX.GMA.Panel;
+with HW.GFX.GMA.Power_And_Clocks;
+with HW.GFX.GMA.Registers;
+with HW.GFX.GMA.Transcoder;
 
 with HW.Debug;
 with GNAT.Source_Info;
 
 package body HW.GFX.GMA.Connectors is
 
-   procedure Post_Reset_Off is
-   begin
-      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
-   end Post_Reset_Off;
+   type Pipe_Regs is array (Pipe_Index) of Registers.Registers_Index;
+   DP_TP_CTL : constant Pipe_Regs :=
+     (Primary   => Registers.TGL_DP_TP_CTL_A,
+      Secondary => Registers.TGL_DP_TP_CTL_B,
+      Tertiary  => Registers.TGL_DP_TP_CTL_C);
+   DP_TP_CTL_TRANSPORT_ENABLE       : constant := 1 * 2 ** 31;
+   DP_TP_CTL_MODE_SST               : constant := 0 * 2 ** 27;
+   DP_TP_CTL_MODE_MST               : constant := 1 * 2 ** 27;
+   DP_TP_CTL_FORCE_ACT              : constant := 1 * 2 ** 25;
+   DP_TP_CTL_ENHANCED_FRAME_ENABLE  : constant := 1 * 2 ** 18;
+   DP_TP_CTL_LINK_TRAIN_MASK        : constant := 7 * 2 **  8;
+   DP_TP_CTL_LINK_TRAIN_PAT1        : constant := 0 * 2 **  8;
+   DP_TP_CTL_LINK_TRAIN_PAT2        : constant := 1 * 2 **  8;
+   DP_TP_CTL_LINK_TRAIN_IDLE        : constant := 2 * 2 **  8;
+   DP_TP_CTL_LINK_TRAIN_NORMAL      : constant := 3 * 2 **  8;
+   DP_TP_CTL_LINK_TRAIN_PAT3        : constant := 4 * 2 **  8;
+   DP_TP_CTL_LINK_TRAIN_PAT4        : constant := 5 * 2 **  8;
 
-   procedure Initialize is
-   begin
-      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
-   end Initialize;
+   DP_TP_STATUS : constant Pipe_Regs :=
+     (Primary   => Registers.TGL_DP_TP_STATUS_A,
+      Secondary => Registers.TGL_DP_TP_STATUS_B,
+      Tertiary  => Registers.TGL_DP_TP_STATUS_C);
+   DP_TP_STATUS_MIN_IDLES_SENT      : constant := 1 * 2 ** 25;
+
+   DDI_BUF_CTL_BUFFER_ENABLE        : constant :=      1 * 2 ** 31;
+   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_PORT_REVERSAL        : constant :=      1 * 2 ** 16;
+   DDI_BUF_CTL_IDLE_STATUS          : constant :=      1 * 2 **  7;
+   DDI_BUF_CTL_TRANS_SELECT_MASK    : constant := 16#f#  * 2 ** 24;
+
+   DDI_CLK_SEL_SHIFT : constant array (Combo_Port) of Natural :=
+     (DIGI_A => 0,
+      DIGI_B => 2,
+      DIGI_C => 4);
+
+   function DDI_CLK_OFF (Port : TGL_Digital_Port) return Word32
+   is
+     (case Port is
+         when DIGI_A  => 1 * 2 ** 10,
+         when DIGI_B  => 1 * 2 ** 11,
+         when DIGI_C  => 1 * 2 ** 24,
+         when DDI_TC1 => 1 * 2 ** 12,
+         when DDI_TC2 => 1 * 2 ** 13,
+         when DDI_TC3 => 1 * 2 ** 14,
+         when DDI_TC4 => 1 * 2 ** 21,
+         when DDI_TC5 => 1 * 2 ** 22,
+         when DDI_TC6 => 1 * 2 ** 23,
+         when others  => 0);
+
+   function DDI_BUF_CTL (Port : TGL_Digital_Port) return Registers.Registers_Index
+   is
+     (case Port is
+         when DIGI_A  => Registers.DDI_BUF_CTL_A,
+         when DIGI_B  => Registers.DDI_BUF_CTL_B,
+         when DIGI_C  => Registers.DDI_BUF_CTL_C,
+         when DDI_TC1 => Registers.DDI_BUF_CTL_USBC1,
+         when DDI_TC2 => Registers.DDI_BUF_CTL_USBC2,
+         when DDI_TC3 => Registers.DDI_BUF_CTL_USBC3,
+         when DDI_TC4 => Registers.DDI_BUF_CTL_USBC4,
+         when DDI_TC5 => Registers.DDI_BUF_CTL_USBC5,
+         when DDI_TC6 => Registers.DDI_BUF_CTL_USBC6,
+         when others  => Registers.DDI_BUF_CTL_A);
+
+   DDI_CLK_SEL : constant array (USBC_Port) of Registers.Registers_Index :=
+     (DDI_TC1 => Registers.DDI_CLK_SEL_USBC1,
+      DDI_TC2 => Registers.DDI_CLK_SEL_USBC2,
+      DDI_TC3 => Registers.DDI_CLK_SEL_USBC3,
+      DDI_TC4 => Registers.DDI_CLK_SEL_USBC4,
+      DDI_TC5 => Registers.DDI_CLK_SEL_USBC5,
+      DDI_TC6 => Registers.DDI_CLK_SEL_USBC6);
+
+   type Training_Port_Info is record
+      Port  : TGL_Digital_Port;
+      Pipe  : Pipe_Index;
+      eDP   : Boolean;
+   end record;
+
+   function To_DP (Port_Info : Training_Port_Info) return DP_Port is
+     (case Port_Info.Port is
+      when DIGI_A  => DP_A,
+      when DIGI_B  => DP_B,
+      when DIGI_C  => DP_C,
+      when DDI_TC1 => DP_D,
+      when DDI_TC2 => DP_E,
+      when DDI_TC3 => DP_F,
+      when DDI_TC4 => DP_G,
+      when DDI_TC5 => DP_H,
+      when DDI_TC6 => DP_I,
+      when others  => DP_A);
 
    ---------------------------------------------------------------------
 
+   procedure Off (Pipe : Pipe_Index; Port : TGL_Digital_Port)
+   is
+      Enabled : Boolean;
+   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   => Enabled);
+
+      if Enabled then
+         Registers.Unset_Mask
+           (Register => DDI_BUF_CTL (Port),
+            Mask     => DDI_BUF_CTL_BUFFER_ENABLE);
+      end if;
+
+      Registers.Unset_Mask
+        (Register   => DP_TP_CTL (Pipe),
+         Mask       => DP_TP_CTL_TRANSPORT_ENABLE);
+
+      if Enabled then
+         Registers.Wait_Set_Mask
+           (Register => DDI_BUF_CTL (Port),
+            Mask     => DDI_BUF_CTL_IDLE_STATUS);
+      end if;
+
+     Registers.Set_Mask
+       (Register => Registers.DPCLKA_CFGCR0,
+        Mask     => DDI_CLK_OFF (Port));
+   end Off;
+
+   procedure Off (Port_Info : Training_Port_Info) is
+   begin
+      Off (Port_Info.Pipe, Port_Info.Port);
+   end Off;
+
+   ---------------------------------------------------------------------
+
+   procedure Set_TP_CTL
+     (Pipe : Pipe_Index;
+      Link : DP_Link;
+      Pattern : DP_Info.Training_Pattern)
+   is
+      type DP_TP_CTL_LINK_TRAIN_ARRAY is
+         array (DP_Info.Training_Pattern) of Word32;
+      DP_TP_CTL_LINK_TRAIN : constant DP_TP_CTL_LINK_TRAIN_ARRAY :=
+         DP_TP_CTL_LINK_TRAIN_ARRAY'
+        (DP_Info.TP_1    => DP_TP_CTL_LINK_TRAIN_PAT1,
+         DP_Info.TP_2    => DP_TP_CTL_LINK_TRAIN_PAT2,
+         DP_Info.TP_3    => DP_TP_CTL_LINK_TRAIN_PAT3,
+         DP_Info.TP_Idle => DP_TP_CTL_LINK_TRAIN_IDLE,
+         DP_Info.TP_None => DP_TP_CTL_LINK_TRAIN_NORMAL);
+
+      DP_TP_CTL_Enhanced_Frame : Word32 := 0;
+   begin
+      if Link.Enhanced_Framing then
+         DP_TP_CTL_Enhanced_Frame := DP_TP_CTL_ENHANCED_FRAME_ENABLE;
+      end if;
+
+      Registers.Write
+        (Register => DP_TP_CTL (Pipe),
+         Value    => DP_TP_CTL_TRANSPORT_ENABLE or
+                     DP_TP_CTL_Enhanced_Frame or
+                     DP_TP_CTL_MODE_SST or
+                     DP_TP_CTL_LINK_TRAIN (Pattern));
+      Registers.Posting_Read (DP_TP_CTL (Pipe));
+   end Set_TP_CTL;
+
+   procedure Set_Training_Pattern
+     (Port_Info   : Training_Port_Info;
+      Link        : DP_Link;
+      Pattern     : DP_Info.Training_Pattern)
+   is
+      use type DP_Info.Training_Pattern;
+      Pipe : Pipe_Index renames Port_Info.Pipe;
+   begin
+      if Pattern < DP_Info.TP_Idle then
+         Set_TP_CTL (Pipe, Link, Pattern);
+      else
+         Set_TP_CTL (Pipe, Link, DP_Info.TP_Idle);
+         Registers.Wait_Set_Mask
+           (Register => DP_TP_STATUS (Pipe),
+            Mask => DP_TP_STATUS_MIN_IDLES_SENT);
+         Set_TP_CTL (Pipe, Link, DP_Info.TP_None);
+      end if;
+   end Set_Training_Pattern;
+
+   ---------------------------------------------------------------------
+
+   pragma Warnings (GNATprove, Off, "unused variable ""Port_Info""",
+                    Reason => "Needed for a common interface");
+   function Max_V_Swing
+     (Port_Info : Training_Port_Info) return DP_Info.DP_Voltage_Swing
+   is
+     (DP_Info.VS_Level_3);
+
+   function Max_Pre_Emph
+     (Port_Info   : Training_Port_Info;
+      Train_Set   : DP_Info.Train_Set)
+      return DP_Info.DP_Pre_Emph
+   is
+   begin
+      return
+        (case Train_Set.Voltage_Swing is
+         when DP_Info.VS_Level_0 => DP_Info.Emph_Level_3,
+         when DP_Info.VS_Level_1 => DP_Info.Emph_Level_2,
+         when DP_Info.VS_Level_2 => DP_Info.Emph_Level_1,
+         when others             => DP_Info.Emph_Level_0);
+   end Max_Pre_Emph;
+   pragma Warnings (GNATprove, On, "unused variable ""Port_Info""");
+
+   ---------------------------------------------------------------------
+
+   procedure Set_Signal_Levels
+     (Port_Info   : Training_Port_Info;
+      Link        : DP_Link;
+      Train_Set   : DP_Info.Train_Set)
+   is
+      Port : TGL_Digital_Port renames Port_Info.Port;
+   begin
+      if Port in Combo_Port then
+         Combo_Phy.Set_Signal_Levels (Port, Port_Info.eDP, Link, Train_Set);
+      elsif Port in USBC_Port then
+         TC.Set_Signal_Levels (Port, Link, Train_Set);
+      end if;
+   end Set_Signal_Levels;
+
+   ---------------------------------------------------------------------
+
+   procedure Map_PLL_To_Port
+     (Port     : TGL_Digital_Port;
+      PLL_Hint : Word32)
+   is
+      CLOCK_SELECT_MASK  : constant := 16#f000_0000#;
+      CLOCK_SELECT_TYPEC : constant := 16#8000_0000#;
+      DDI_CLK_SEL_MASK : constant := 3;
+      Clk_Sel_Shift : Natural;
+   begin
+      if Port in USBC_Port then
+         Registers.Unset_And_Set_Mask
+           (Register   => DDI_CLK_SEL (Port),
+            Mask_Unset => CLOCK_SELECT_MASK,
+            Mask_Set   => CLOCK_SELECT_TYPEC);
+      end if;
+
+      if Port in Combo_Port then
+         Clk_Sel_Shift := DDI_CLK_SEL_SHIFT (Port);
+         Registers.Unset_And_Set_Mask
+           (Register   => Registers.DPCLKA_CFGCR0,
+            Mask_Unset => Shift_Left (DDI_CLK_SEL_MASK, Clk_Sel_Shift),
+            Mask_Set   => Shift_Left (PLL_Hint, Clk_Sel_Shift));
+         Registers.Posting_Read (Registers.DPCLKA_CFGCR0);
+      end if;
+   end Map_PLL_To_Port;
+
+   ---------------------------------------------------------------------
+
+   pragma Warnings (GNAT, Off, """Port_Cfg"" is not modified",
+                    Reason => "Needed for a common interface");
    procedure Prepare
      (Port     : in     Active_Port_Type;
       Port_Cfg : in out Port_Config;
       Success  :    out Boolean)
    is
    begin
-      Success := False;
-   end;
+      if Port_Cfg.Port in USBC_Port then
+         TC.Connect
+           (Port     => Port_Cfg.Port,
+            DP_Alt   => Port in Physical_USBC_Ports,
+            Lanes    => Port_Cfg.DP.Lane_Count,
+            Success  => Success);
+      else
+         Success := True;
+      end if;
+   end Prepare;
+   pragma Warnings (GNAT, On, """Port_Cfg"" is not modified");
 
    ---------------------------------------------------------------------
 
@@ -47,38 +309,96 @@
      (Pipe        : in     Pipe_Index;
       Port_Cfg    : in     Port_Config;
       PLL_Hint    : in     Word32;
-      Success     :    out Boolean) is
+      Success     :    out Boolean)
+   is
+      package Training is new DP_Training
+        (TPS3_Supported    => True,
+         T                 => Training_Port_Info,
+         Aux_T             => DP_Port,
+         Aux_Ch            => HW.GFX.GMA.DP_Aux_Ch,
+         DP_Info           => DP_Info,
+         To_Aux            => To_DP,
+         Max_V_Swing       => Max_V_Swing,
+         Max_Pre_Emph      => Max_Pre_Emph,
+         Set_Pattern       => Set_Training_Pattern,
+         Set_Signal_Levels => Set_Signal_Levels,
+         Off               => Off);
+      Port : TGL_Digital_Port;
    begin
       pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
-      Success := True;
+
+      if Port_Cfg.Port not in TGL_Digital_Port then
+         Success := False;
+         return;
+      end if;
+
+      Port := Port_Cfg.Port;
+      Map_PLL_To_Port (Port, PLL_Hint);
+
+      Registers.Unset_Mask
+        (Register => Registers.DPCLKA_CFGCR0,
+         Mask     => DDI_CLK_OFF (Port));
+
+      if Port_Cfg.Display = DP then
+         if Port in USBC_Port then
+            TC.Program_DP_Mode
+               (Port, Natural (Lane_Count_As_Integer (Port_Cfg.DP.Lane_Count)));
+         end if;
+         Transcoder.Enable_Pipe_Clock (Pipe, Port_Cfg);
+         Transcoder.Configure (Pipe, Port_Cfg, Scale => False);
+
+         Training.Train_DP
+           (Port    => (Port, Pipe, eDP => Port_Cfg.Is_eDP),
+            Link    => Port_Cfg.DP,
+            Success => Success);
+      elsif Port_Cfg.Display = HDMI then
+         Transcoder.Enable_Pipe_Clock (Pipe, Port_Cfg);
+         Transcoder.Configure (Pipe, Port_Cfg, Scale => False);
+
+         Success := True;
+      else
+         Success := False;
+      end if;
    end Pre_On;
 
    procedure Post_On
      (Pipe     : in     Pipe_Index;
       Port_Cfg : in     Port_Config;
       PLL_Hint : in     Word32;
-      Success  :    out Boolean) is
+      Success  :    out Boolean)
+   is
+      Port : GPU_Port renames Port_Cfg.Port;
    begin
-      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
-      Panel.Backlight_On (Port_Cfg.Panel);
       Success := True;
+
+      if Port_Cfg.Display = HDMI then
+         if Port in Combo_Port then
+            Combo_Phy.Enable_HDMI (Port);
+         elsif Port in USBC_Port then
+            TC.Enable_HDMI (Port);
+         end if;
+      end if;
+
+      Panel.Backlight_On (Port_Cfg.Panel);
    end Post_On;
 
+   ---------------------------------------------------------------------
+
    procedure Pre_Off (Pipe : Pipe_Index; Port_Cfg : Port_Config) is
    begin
-      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
       Panel.Backlight_Off (Port_Cfg.Panel);
       Panel.Off (Port_Cfg.Panel);
    end Pre_Off;
 
    procedure Post_Off (Pipe : Pipe_Index; Port_Cfg : Port_Config) is
    begin
-      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+      if Port_Cfg.Port in Combo_Port then
+         Off (Pipe, Port_Cfg.Port);
+      end if;
    end Post_Off;
 
    procedure Pre_All_Off  is
    begin
-      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
       for P in Valid_Panels loop
          Panel.Backlight_Off (P);
          Panel.Off (P);
@@ -87,7 +407,23 @@
 
    procedure Post_All_Off is
    begin
-      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+      for Port in Combo_Port loop
+         Off (Pipe_Index'First, Port); -- pipe index is arbitrary
+      end loop;
    end Post_All_Off;
 
+   procedure Post_Reset_Off is
+   begin
+      for Port in Combo_Port loop
+         Off (Pipe_Index'First, Port);
+      end loop;
+   end Post_Reset_Off;
+
+   procedure Initialize is
+   begin
+      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+
+      GMA.Combo_Phy.Initialize;
+   end Initialize;
+
 end HW.GFX.GMA.Connectors;