Initial upstream commit

The history contained unlicensed code so everything got squashed, sorry.

Change-Id: I9f5775208f9df6fb29074bf3bc498f68cb17b3a0
Signed-off-by: Nico Huber <nico.huber@secunet.com>
diff --git a/common/hw-gfx-dp_training.adb b/common/hw-gfx-dp_training.adb
new file mode 100644
index 0000000..4ba548a
--- /dev/null
+++ b/common/hw-gfx-dp_training.adb
@@ -0,0 +1,412 @@
+--
+-- Copyright (C) 2015-2016 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; version 2 of the License.
+--
+-- 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 Ada.Unchecked_Conversion;
+
+with HW.Debug;
+with GNAT.Source_Info;
+
+with HW.Time;
+with HW.GFX.DP_Defs;
+
+package body HW.GFX.DP_Training is
+
+   pragma Warnings (GNATprove, Off, "unused initial value of ""Port""*",
+                    Reason => "Needed for a common interface");
+   function Training_Set
+     (Port        : T;
+      Train_Set   : DP_Info.Train_Set)
+      return Word8
+   is
+      use type DP_Info.DP_Voltage_Swing;
+      use type DP_Info.DP_Pre_Emph;
+      use type Word8;
+      Value : Word8;
+   begin
+      case Train_Set.Voltage_Swing is
+         when DP_Info.VS_Level_0   => Value := 16#00#;
+         when DP_Info.VS_Level_1   => Value := 16#01#;
+         when DP_Info.VS_Level_2   => Value := 16#02#;
+         when DP_Info.VS_Level_3   => Value := 16#03#;
+      end case;
+      if Train_Set.Voltage_Swing = Max_V_Swing (Port) then
+         Value := Value or 16#04#;
+      end if;
+
+      case Train_Set.Pre_Emph is
+         when DP_Info.Emph_Level_0 => Value := Value or 16#00#;
+         when DP_Info.Emph_Level_1 => Value := Value or 16#08#;
+         when DP_Info.Emph_Level_2 => Value := Value or 16#10#;
+         when DP_Info.Emph_Level_3 => Value := Value or 16#18#;
+      end case;
+      if Train_Set.Pre_Emph = Max_Pre_Emph (Port, Train_Set) then
+         Value := Value or 16#20#;
+      end if;
+
+      return Value;
+   end Training_Set;
+   pragma Warnings (GNATprove, On, "unused initial value of ""Port""*");
+
+   ----------------------------------------------------------------------------
+
+   function Lane_Count (Link : DP_Link) return Positive
+   with
+      Post => Lane_Count'Result <= 4
+   is
+   begin
+      return Positive (Lane_Count_As_Integer (Link.Lane_Count));
+   end Lane_Count;
+
+   procedure Sink_Init
+     (Port        : in     Aux_T;
+      Link        : in     DP_Link;
+      Success     :    out Boolean)
+   is
+      use type Word8;
+      function Link_Rate_As_Word8 is new Ada.Unchecked_Conversion
+        (Source => DP_Bandwidth, Target => Word8);
+      Data : DP_Defs.Aux_Payload;
+   begin
+      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+
+      Data :=
+        (0      => Link_Rate_As_Word8 (Link.Bandwidth),
+         1      => Word8 (Lane_Count (Link)),
+         others => 0);  -- Don't care
+
+      if Link.Enhanced_Framing then
+         Data (1) := Data (1) or 16#80#;
+      end if;
+
+      Aux_Ch.Aux_Write
+        (Port     => Port,
+         Address  => 16#00100#,     -- LINK_BW_SET, LANE_COUNT_SET
+         Length   => 2,
+         Data     => Data,
+         Success  => Success);
+      Success := Success or Link.Opportunistic_Training;
+
+      if Success then
+         Data (0) := 16#00#;  -- no downspread
+         Data (1) := 16#01#;  -- ANSI8B10B coding
+
+         Aux_Ch.Aux_Write
+           (Port     => Port,
+            Address  => 16#00107#,     -- DOWNSPREAD_CTRL,
+            Length   => 2,             -- MAIN_LINK_CHANNEL_CODING_SET
+            Data     => Data,
+            Success  => Success);
+         Success := Success or Link.Opportunistic_Training;
+      end if;
+   end Sink_Init;
+
+   procedure Sink_Set_Training_Pattern
+     (DP          : in     Aux_T;
+      Link        : in     DP_Link;
+      Pattern     : in     DP_Info.Training_Pattern;
+      Success     :    out Boolean)
+   is
+      use type DP_Info.Training_Pattern;
+
+      type TP_Array is array (DP_Info.Training_Pattern) of Word8;
+      TP : constant TP_Array := TP_Array'
+        (DP_Info.TP_1 => 16#21#, DP_Info.TP_2 => 16#22#, DP_Info.TP_3 => 16#23#,
+         DP_Info.TP_Idle => 16#00#, DP_Info.TP_None => 16#00#);
+
+      Data : DP_Defs.Aux_Payload;
+      Length : Positive := 1;
+   begin
+      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+
+      Data :=
+        (0      => TP (Pattern),
+         others => 0);  -- Don't care
+
+      if Pattern < DP_Info.TP_Idle then
+         Length := Length + Lane_Count (Link);
+      end if;
+      Aux_Ch.Aux_Write
+        (Port     => DP,
+         Address  => 16#00102#,     -- TRAINING_PATTERN_SET
+         Length   => Length,
+         Data     => Data,
+         Success  => Success);
+   end Sink_Set_Training_Pattern;
+
+   procedure Sink_Set_Signal_Levels
+     (Port        : in     T;
+      DP          : in     Aux_T;
+      Link        : in     DP_Link;
+      Train_Set   : in     DP_Info.Train_Set;
+      Success     :    out Boolean)
+   is
+      Data  : DP_Defs.Aux_Payload;
+      T_Set : constant Word8 := Training_Set (Port, Train_Set);
+   begin
+      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+
+      Data                                := (others => 0); -- Initialize
+      Data (0 .. Lane_Count (Link) - 1)   := (others => T_Set);
+
+      Aux_Ch.Aux_Write
+        (Port     => DP,
+         Address  => 16#00103#,     -- TRAINING_LANEx_SET
+         Length   => Lane_Count (Link),
+         Data     => Data,
+         Success  => Success);
+   end Sink_Set_Signal_Levels;
+
+   pragma Warnings (GNATprove, Off, "unused initial value of ""Port""*",
+                    Reason => "Needed for a common interface");
+   procedure Sink_Adjust_Training
+     (Port        : in     T;
+      DP          : in     Aux_T;
+      Link        : in     DP_Link;
+      Train_Set   : in out DP_Info.Train_Set;
+      CR_Done     : in out Boolean;
+      EQ_Done     :    out Boolean;
+      Success     :    out Boolean)
+   is
+      use type DP_Info.DP_Voltage_Swing;
+      use type DP_Info.DP_Pre_Emph;
+
+      Status : DP_Info.Link_Status;
+      CR_Was_Done : constant Boolean := CR_Done;
+
+      pragma Warnings
+        (GNATprove, Off, "subprogram ""Dump_Link_Status"" has no effect*",
+         Reason => "It's only used for debugging");
+      procedure Dump_Link_Status
+      is
+      begin
+         Debug.New_Line;
+         Debug.Put_Line ("Link Status:");
+
+         for Lane in DP_Info.Lane_Index range 0
+            .. DP_Info.Lane_Index (Lane_Count_As_Integer (Link.Lane_Count) - 1)
+         loop
+            Debug.Put ("  Lane");
+            Debug.Put_Int8 (Int8 (Lane));
+            Debug.Put_Line (":");
+
+            Debug.Put_Line ("    CR_Done        : " &
+              (if Status.Lanes (Lane).CR_Done         then "1" else "0"));
+            Debug.Put_Line ("    Channel_EQ_Done: " &
+              (if Status.Lanes (Lane).Channel_EQ_Done then "1" else "0"));
+            Debug.Put_Line ("    Symbol_Locked  : " &
+              (if Status.Lanes (Lane).Symbol_Locked   then "1" else "0"));
+         end loop;
+
+         Debug.Put_Line ("  Interlane_Align_Done: " &
+           (if Status.Interlane_Align_Done then "1" else "0"));
+
+         for Lane in DP_Info.Lane_Index range 0
+            .. DP_Info.Lane_Index (Lane_Count_As_Integer (Link.Lane_Count) - 1)
+         loop
+            Debug.Put ("  Adjust");
+            Debug.Put_Int8 (Int8 (Lane));
+            Debug.Put_Line (":");
+
+            Debug.Put ("    Voltage_Swing: ");
+            Debug.Put_Int8 (Int8 (DP_Info.DP_Voltage_Swing'Pos
+              (Status.Adjust_Requests (Lane).Voltage_Swing)));
+            Debug.New_Line;
+            Debug.Put ("    Pre_Emph     : ");
+            Debug.Put_Int8 (Int8 (DP_Info.DP_Pre_Emph'Pos
+              (Status.Adjust_Requests (Lane).Pre_Emph)));
+            Debug.New_Line;
+         end loop;
+
+         Debug.New_Line;
+      end Dump_Link_Status;
+   begin
+      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+
+      DP_Info.Read_Link_Status
+        (Port     => DP,
+         Status   => Status,
+         Success  => Success);
+
+      pragma Debug (Success, Dump_Link_Status);
+
+      CR_Done := Success and then DP_Info.All_CR_Done (Status, Link);
+      EQ_Done := Success and then DP_Info.All_EQ_Done (Status, Link);
+      Success := Success and then (CR_Done or not CR_Was_Done);
+
+      if Success and not CR_Done then
+         Train_Set.Voltage_Swing :=
+            DP_Info.Max_Requested_VS (Status, Link);
+         if Train_Set.Voltage_Swing > Max_V_Swing (Port)
+         then
+            Train_Set.Voltage_Swing := Max_V_Swing (Port);
+         end if;
+      end if;
+
+      -- According to DP spec, only change preemphasis during channel
+      -- equalization. What to do if sink requests it during clock recovery?
+      -- Linux always accepts new values from the sink, we don't, for now.
+      if Success and then (CR_Was_Done and not EQ_Done) then
+         Train_Set.Pre_Emph :=
+            DP_Info.Max_Requested_Emph (Status, Link);
+         if Train_Set.Pre_Emph > Max_Pre_Emph (Port, Train_Set)
+         then
+            Train_Set.Pre_Emph := Max_Pre_Emph (Port, Train_Set);
+         end if;
+      end if;
+   end Sink_Adjust_Training;
+   pragma Warnings (GNATprove, On, "unused initial value of ""Port""*");
+
+   ----------------------------------------------------------------------------
+
+   procedure Train_DP
+     (Port     : in     T;
+      Link     : in     DP_Link;
+      Success  :    out Boolean)
+   is
+      use type DP_Info.DP_Voltage_Swing;
+      use type DP_Info.DP_Pre_Emph;
+      use type Word8;
+
+      DP : constant Aux_T := To_Aux (Port);
+
+      Retries : Natural;
+      Max_Retry : constant := 4;
+      CR_Done, EQ_Done : Boolean := False;
+
+      EQ_Pattern : constant DP_Info.Training_Pattern :=
+        (if TPS3_Supported and Link.Receiver_Caps.TPS3_Supported then
+            DP_Info.TP_3
+         else
+            DP_Info.TP_2);
+
+      Train_Set, Last_Train_Set : DP_Info.Train_Set;
+
+      function CR_Delay return Natural is
+         Result : Natural := 100; -- DP spec: 100us
+      begin
+         if Link.Bandwidth = DP_Bandwidth_5_4 and
+            Link.Receiver_Caps.Aux_RD_Interval /= 0
+         then
+            Result := Natural (Link.Receiver_Caps.Aux_RD_Interval) * 4_000;
+         end if;
+         return Result;
+      end CR_Delay;
+
+      function EQ_Delay return Natural is
+         Result : Natural := 400; -- DP spec: 400us
+      begin
+         if Link.Bandwidth = DP_Bandwidth_5_4 and
+            Link.Receiver_Caps.Aux_RD_Interval /= 0
+         then
+            Result := Natural (Link.Receiver_Caps.Aux_RD_Interval) * 4_000;
+         end if;
+         return Result;
+      end EQ_Delay;
+   begin
+      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+
+      Train_Set.Voltage_Swing := DP_Info.DP_Voltage_Swing'First;
+      Train_Set.Pre_Emph      := DP_Info.DP_Pre_Emph'First;
+
+      Set_Pattern (Port, Link, DP_Info.TP_1);
+      Set_Signal_Levels (Port, Link, Train_Set);
+
+      pragma Warnings
+        (GNATprove, Off, """Success"" modified by call, but value overwritten*",
+         Reason => "Read first, then overwritten, looks like a false positive");
+      Sink_Init (DP, Link, Success);
+      pragma Warnings
+        (GNATprove, On, """Success"" modified by call, but value overwritten*");
+      if Success then
+         Sink_Set_Training_Pattern (DP, Link, DP_Info.TP_1, Success);
+      end if;
+
+      if Success then
+         Retries := 0;
+         for Tries in 1 .. 32 loop
+            pragma Loop_Invariant (Retries <= Max_Retry);
+
+            Time.U_Delay (CR_Delay);
+
+            Last_Train_Set := Train_Set;
+            Sink_Adjust_Training
+              (Port, DP, Link, Train_Set, CR_Done, EQ_Done, Success);
+            exit when CR_Done or not Success;
+
+            if Train_Set.Voltage_Swing = Last_Train_Set.Voltage_Swing then
+               exit when Retries = Max_Retry;
+               Retries := Retries + 1;
+            else
+               exit when Last_Train_Set.Voltage_Swing = Max_V_Swing (Port);
+               Retries := 0;
+            end if;
+
+            Set_Signal_Levels (Port, Link, Train_Set);
+            Sink_Set_Signal_Levels (Port, DP, Link, Train_Set, Success);
+            exit when not Success;
+         end loop;
+      end if;
+
+      Success := Success and CR_Done;
+
+      if Success then
+         Set_Pattern (Port, Link, EQ_Pattern);
+         Sink_Set_Training_Pattern (DP, Link, EQ_Pattern, Success);
+      end if;
+
+      if Success then
+         Retries := 0;
+         for Tries in 1 .. 32 loop
+            pragma Loop_Invariant (Retries <= Max_Retry);
+
+            Time.U_Delay (EQ_Delay);
+
+            Last_Train_Set := Train_Set;
+            Sink_Adjust_Training
+              (Port, DP, Link, Train_Set, CR_Done, EQ_Done, Success);
+            exit when EQ_Done or not Success;
+
+            if Train_Set.Pre_Emph = Last_Train_Set.Pre_Emph then
+               exit when Retries = Max_Retry;
+               Retries := Retries + 1;
+            else
+               exit when Last_Train_Set.Pre_Emph =
+                  Max_Pre_Emph (Port, Last_Train_Set);
+               Retries := 0;
+            end if;
+
+            Set_Signal_Levels (Port, Link, Train_Set);
+            Sink_Set_Signal_Levels (Port, DP, Link, Train_Set, Success);
+            exit when not Success;
+         end loop;
+      end if;
+
+      if Success then
+         if EQ_Done then
+            -- Set_Pattern (TP_None) includes sending the Idle Pattern,
+            -- so tell sink first.
+            Sink_Set_Training_Pattern
+              (DP, Link, DP_Info.TP_None, Success);
+         else
+            Success := False;
+         end if;
+      end if;
+
+      if Success then
+         Set_Pattern (Port, Link, DP_Info.TP_None);
+      else
+         Off (Port);
+      end if;
+   end Train_DP;
+
+end HW.GFX.DP_Training;