400 lines
13 KiB
Ada
400 lines
13 KiB
Ada
--
|
|
-- Copyright (C) 2015-2016, 2019 secunet Security Networks AG
|
|
-- Copyright (C) 2017 Nico Huber <nico.h@gmx.de>
|
|
--
|
|
-- 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;
|