-- -- Copyright (C) 2015-2016, 2019 secunet Security Networks AG -- Copyright (C) 2017 Nico Huber -- -- 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 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 (Port : in T; Link : in DP_Link; Pattern : in DP_Info.Training_Pattern; Train_Set : in DP_Info.Train_Set; 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#); T_Set : constant Word8 := Training_Set (Port, Train_Set); Data : DP_Defs.Aux_Payload; Length : Positive := 1; begin pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); Data := (TP (Pattern), others => 0); if Pattern < DP_Info.TP_Idle then Length := Length + Lane_Count (Link); Data (1 .. Lane_Count (Link)) := (others => T_Set); end if; Aux_Ch.Aux_Write (Port => To_Aux (Port), 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); -- Voltage swing may be updated during channel equalization too. if Success and not EQ_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 too, now: There -- are sinks in the wild that need this. if Success 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 (Port, Link, DP_Info.TP_1, Train_Set, 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 (Port, Link, EQ_Pattern, Train_Set, Success); end if; if Success then for Tries in 1 .. 6 loop Time.U_Delay (EQ_Delay); Sink_Adjust_Training (Port, DP, Link, Train_Set, CR_Done, EQ_Done, Success); exit when EQ_Done or not Success; 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; -- Set_Pattern (TP_None) includes sending the Idle Pattern, -- so tell sink first. Sink_Set_Training_Pattern (Port, Link, DP_Info.TP_None, Train_Set, Success); Set_Pattern (Port, Link, DP_Info.TP_None); Success := Success and then EQ_Done; if not Success then Off (Port); end if; end Train_DP; end HW.GFX.DP_Training;