libgfxinit/common/ironlake/hw-gfx-gma-connectors-fdi.adb

343 lines
12 KiB
Ada

--
-- Copyright (C) 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 HW.Time;
with HW.GFX.GMA.Config;
with HW.GFX.GMA.PCH.FDI;
with HW.GFX.GMA.Registers;
with HW.Debug;
with GNAT.Source_Info;
package body HW.GFX.GMA.Connectors.FDI
is
PCH_FDI_CHICKEN_B_AND_C : constant := 1 * 2 ** 12;
type TX_CTL_Regs is array (GPU_FDI_Port) of Registers.Registers_Index;
TX_CTL : constant TX_CTL_Regs :=
(DIGI_B => Registers.FDI_TX_CTL_A,
DIGI_C => Registers.FDI_TX_CTL_B,
DIGI_D => Registers.FDI_TX_CTL_C);
FDI_TX_CTL_FDI_TX_ENABLE : constant := 1 * 2 ** 31;
FDI_TX_CTL_VP_MASK : constant := 16#3f# * 2 ** 22;
FDI_TX_CTL_PORT_WIDTH_SEL_SHIFT : constant := 19;
FDI_TX_CTL_ENHANCED_FRAMING_ENABLE : constant := 1 * 2 ** 18;
FDI_TX_CTL_FDI_PLL_ENABLE : constant := 1 * 2 ** 14;
FDI_TX_CTL_COMPOSITE_SYNC_SELECT : constant := 1 * 2 ** 11;
FDI_TX_CTL_AUTO_TRAIN_ENABLE : constant := 1 * 2 ** 10;
FDI_TX_CTL_AUTO_TRAIN_DONE : constant := 1 * 2 ** 1;
TP_SHIFT : constant := (if Config.CPU <= Sandybridge then 28 else 8);
FDI_TX_CTL_TRAINING_PATTERN_MASK : constant := 3 * 2 ** TP_SHIFT;
FDI_TX_CTL_TRAINING_PATTERN_1 : constant := 0 * 2 ** TP_SHIFT;
FDI_TX_CTL_TRAINING_PATTERN_2 : constant := 1 * 2 ** TP_SHIFT;
FDI_TX_CTL_TRAINING_PATTERN_IDLE : constant := 2 * 2 ** TP_SHIFT;
FDI_TX_CTL_TRAINING_PATTERN_NORMAL : constant := 3 * 2 ** TP_SHIFT;
subtype FDI_TX_CTL_VP_T is Natural range 0 .. 3;
type Vswing_Preemph_Values is array (FDI_TX_CTL_VP_T) of Word32;
FDI_TX_CTL_VSWING_PREEMPH : constant Vswing_Preemph_Values :=
(0 => 16#00# * 2 ** 22,
1 => 16#3a# * 2 ** 22,
2 => 16#39# * 2 ** 22,
3 => 16#38# * 2 ** 22);
function FDI_TX_CTL_PORT_WIDTH_SEL (Lane_Count : DP_Lane_Count) return Word32
is
begin
return Shift_Left
(Word32 (Lane_Count_As_Integer (Lane_Count)) - 1,
FDI_TX_CTL_PORT_WIDTH_SEL_SHIFT);
end FDI_TX_CTL_PORT_WIDTH_SEL;
----------------------------------------------------------------------------
--
-- This is usually used with Ivy Bridge.
--
procedure Auto_Training
(Port_Cfg : in Port_Config;
Success : out Boolean)
with
Pre => Port_Cfg.Port in GPU_FDI_Port
is
PCH_FDI_Port : constant PCH.FDI_Port_Type := PCH_FDIs (Port_Cfg.Port);
begin
pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
-- try each preemph/voltage pair twice
for VP2 in Natural range 0 .. FDI_TX_CTL_VP_T'Last * 2 + 1
loop
Registers.Unset_And_Set_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask_Unset => FDI_TX_CTL_VP_MASK or
FDI_TX_CTL_TRAINING_PATTERN_MASK,
Mask_Set => FDI_TX_CTL_FDI_TX_ENABLE or
FDI_TX_CTL_VSWING_PREEMPH (VP2 / 2) or
FDI_TX_CTL_AUTO_TRAIN_ENABLE or
FDI_TX_CTL_TRAINING_PATTERN_1);
Registers.Posting_Read (TX_CTL (Port_Cfg.Port));
PCH.FDI.Auto_Train (PCH_FDI_Port);
-- read at least twice
for I in 0 .. 3 loop
Registers.Is_Set_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask => FDI_TX_CTL_AUTO_TRAIN_DONE,
Result => Success);
exit when Success or I = 3;
Time.U_Delay (1);
end loop;
exit when Success;
Registers.Unset_And_Set_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask_Unset => FDI_TX_CTL_FDI_TX_ENABLE or
FDI_TX_CTL_AUTO_TRAIN_ENABLE or
FDI_TX_CTL_TRAINING_PATTERN_MASK,
Mask_Set => FDI_TX_CTL_TRAINING_PATTERN_1);
PCH.FDI.Off (PCH_FDI_Port, PCH.FDI.Rx_Off);
end loop;
if Success then
PCH.FDI.Enable_EC (PCH_FDI_Port);
else
Registers.Unset_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask => FDI_TX_CTL_FDI_PLL_ENABLE);
PCH.FDI.Off (PCH_FDI_Port, PCH.FDI.Clock_Off);
end if;
end Auto_Training;
----------------------------------------------------------------------------
--
-- Used with Sandy Bridge (should work with Ivy Bridge too)
--
procedure Full_Training
(Port_Cfg : in Port_Config;
Success : out Boolean)
with
Pre => Port_Cfg.Port in GPU_FDI_Port
is
PCH_FDI_Port : constant PCH.FDI_Port_Type := PCH_FDIs (Port_Cfg.Port);
begin
pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
-- try each preemph/voltage pair twice
for VP2 in Natural range 0 .. FDI_TX_CTL_VP_T'Last * 2 + 1
loop
Registers.Unset_And_Set_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask_Unset => FDI_TX_CTL_VP_MASK or
FDI_TX_CTL_TRAINING_PATTERN_MASK,
Mask_Set => FDI_TX_CTL_FDI_TX_ENABLE or
FDI_TX_CTL_VSWING_PREEMPH (VP2 / 2) or
FDI_TX_CTL_TRAINING_PATTERN_1);
Registers.Posting_Read (TX_CTL (Port_Cfg.Port));
PCH.FDI.Train (PCH_FDI_Port, PCH.FDI.TP_1, Success);
if Success then
Registers.Unset_And_Set_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask_Unset => FDI_TX_CTL_TRAINING_PATTERN_MASK,
Mask_Set => FDI_TX_CTL_TRAINING_PATTERN_2);
Registers.Posting_Read (TX_CTL (Port_Cfg.Port));
PCH.FDI.Train (PCH_FDI_Port, PCH.FDI.TP_2, Success);
end if;
exit when Success;
Registers.Unset_And_Set_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask_Unset => FDI_TX_CTL_FDI_TX_ENABLE or
FDI_TX_CTL_TRAINING_PATTERN_MASK,
Mask_Set => FDI_TX_CTL_TRAINING_PATTERN_1);
PCH.FDI.Off (PCH_FDI_Port, PCH.FDI.Rx_Off);
end loop;
if Success then
Registers.Unset_And_Set_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask_Unset => FDI_TX_CTL_TRAINING_PATTERN_MASK,
Mask_Set => FDI_TX_CTL_TRAINING_PATTERN_NORMAL);
Registers.Posting_Read (TX_CTL (Port_Cfg.Port));
PCH.FDI.Train (PCH_FDI_Port, PCH.FDI.TP_None, Success);
else
Registers.Unset_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask => FDI_TX_CTL_FDI_PLL_ENABLE);
PCH.FDI.Off (PCH_FDI_Port, PCH.FDI.Clock_Off);
end if;
end Full_Training;
----------------------------------------------------------------------------
--
-- Used with original Ironlake (Nehalem CPU)
--
-- This is close to what Linux' i915 does. A comment in i915_reg.h
-- states that it uses only the lowest voltage / pre-emphasis levels
-- which is why we leave them at zero here and don't try different
-- values.
--
-- It's actually not clear from i915's code if the values really are
-- at zero or if it's just reusing what the Video BIOS set. Some code
-- in coreboot sets them to zero explicitly.
--
procedure Simple_Training
(Port_Cfg : in Port_Config;
Success : out Boolean)
with
Pre => Port_Cfg.Port in GPU_FDI_Port
is
PCH_FDI_Port : constant PCH.FDI_Port_Type := PCH_FDIs (Port_Cfg.Port);
begin
pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
Registers.Unset_And_Set_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask_Unset => FDI_TX_CTL_TRAINING_PATTERN_MASK,
Mask_Set => FDI_TX_CTL_FDI_TX_ENABLE or
FDI_TX_CTL_TRAINING_PATTERN_1);
Registers.Posting_Read (TX_CTL (Port_Cfg.Port));
PCH.FDI.Train (PCH_FDI_Port, PCH.FDI.TP_1, Success);
if Success then
Registers.Unset_And_Set_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask_Unset => FDI_TX_CTL_TRAINING_PATTERN_MASK,
Mask_Set => FDI_TX_CTL_TRAINING_PATTERN_2);
Registers.Posting_Read (TX_CTL (Port_Cfg.Port));
PCH.FDI.Train (PCH_FDI_Port, PCH.FDI.TP_2, Success);
end if;
if Success then
Registers.Unset_And_Set_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask_Unset => FDI_TX_CTL_TRAINING_PATTERN_MASK,
Mask_Set => FDI_TX_CTL_TRAINING_PATTERN_NORMAL);
Registers.Posting_Read (TX_CTL (Port_Cfg.Port));
PCH.FDI.Train (PCH_FDI_Port, PCH.FDI.TP_None, Success);
else
Registers.Unset_And_Set_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask_Unset => FDI_TX_CTL_FDI_TX_ENABLE or
FDI_TX_CTL_TRAINING_PATTERN_MASK,
Mask_Set => FDI_TX_CTL_TRAINING_PATTERN_1);
PCH.FDI.Off (PCH_FDI_Port, PCH.FDI.Rx_Off);
Registers.Unset_Mask
(Register => TX_CTL (Port_Cfg.Port),
Mask => FDI_TX_CTL_FDI_PLL_ENABLE);
PCH.FDI.Off (PCH_FDI_Port, PCH.FDI.Clock_Off);
end if;
end Simple_Training;
----------------------------------------------------------------------------
procedure Pre_On (Port_Cfg : Port_Config)
is
Composite_Sel : constant :=
(if Config.Has_FDI_Composite_Sel then
FDI_TX_CTL_COMPOSITE_SYNC_SELECT else 0);
begin
pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
-- The PCH_FDI_CHICKEN_B_AND_C bit may not be changed when any of
-- both ports is active. Bandwidth calculations before calling us
-- should ensure this.
if Config.Has_FDI_C then
if Port_Cfg.Port = DIGI_D or
(Port_Cfg.Port = DIGI_C and
Port_Cfg.FDI.Lane_Count <= DP_Lane_Count_2)
then
Registers.Set_Mask
(Register => Registers.PCH_FDI_CHICKEN_B_C,
Mask => PCH_FDI_CHICKEN_B_AND_C);
elsif Port_Cfg.Port = DIGI_C then
Registers.Unset_Mask
(Register => Registers.PCH_FDI_CHICKEN_B_C,
Mask => PCH_FDI_CHICKEN_B_AND_C);
end if;
end if;
PCH.FDI.Pre_Train (PCH_FDIs (Port_Cfg.Port), Port_Cfg);
Registers.Write
(Register => TX_CTL (Port_Cfg.Port),
Value => FDI_TX_CTL_PORT_WIDTH_SEL (Port_Cfg.FDI.Lane_Count) or
FDI_TX_CTL_ENHANCED_FRAMING_ENABLE or
FDI_TX_CTL_FDI_PLL_ENABLE or
Composite_Sel or
FDI_TX_CTL_TRAINING_PATTERN_1);
Registers.Posting_Read (TX_CTL (Port_Cfg.Port));
Time.U_Delay (100);
end Pre_On;
----------------------------------------------------------------------------
procedure Post_On
(Port_Cfg : in Port_Config;
Success : out Boolean)
is
begin
pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
case Config.FDI_Training is
when GMA.Simple_Training => Simple_Training (Port_Cfg, Success);
when GMA.Full_Training => Full_Training (Port_Cfg, Success);
when GMA.Auto_Training => Auto_Training (Port_Cfg, Success);
end case;
end Post_On;
----------------------------------------------------------------------------
procedure Off (Port : GPU_FDI_Port; OT : Off_Type)
is
PCH_FDI_Port : constant PCH.FDI_Port_Type := PCH_FDIs (Port);
begin
pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
Registers.Unset_And_Set_Mask
(Register => TX_CTL (Port),
Mask_Unset => FDI_TX_CTL_FDI_TX_ENABLE or
FDI_TX_CTL_AUTO_TRAIN_ENABLE or
FDI_TX_CTL_TRAINING_PATTERN_MASK,
Mask_Set => FDI_TX_CTL_TRAINING_PATTERN_1);
PCH.FDI.Off (PCH_FDI_Port, PCH.FDI.Rx_Off);
if OT >= Clock_Off then
Registers.Unset_Mask
(Register => TX_CTL (Port),
Mask => FDI_TX_CTL_FDI_PLL_ENABLE);
PCH.FDI.Off (PCH_FDI_Port, PCH.FDI.Clock_Off);
end if;
end Off;
end HW.GFX.GMA.Connectors.FDI;