haswell: Make VGA on FDI work

Attempting to light up a 1920x1080 monitor through VGA with libgfxinit
on Haswell would either hang the system or show garbage on the monitor.
This was due to two different problems around FDI initialization code.

The system would only hang if libgfxinit was the first program to light
up a monitor on VGA. This is because no one had performed the required
FDI mPHY initialization that is described on the Haswell graphics PRMs.
Add it to libgfxinit alongside some code to disable bending CLKOUT_DP.

Even with the FDI mPHY initialization in place, the garbage would still
be present on the VGA monitor. Digital interfaces were not affected.
By carefully dumping and comparing the display registers of a good and
a bad case, it was determinted that the fault was due to a mismatched
link width for FDI. The FDI link between the iGPU and the Lynxpoint PCH
can operate in either x1 or x2 width, depending on the bandwidth needs
of the monitor on the PCH's VGA port. To drive a 1920x1080 VGA monitor,
it is necessary to use both FDI lanes. Moreover, both ends of the link
need to be configured to use the same link width. However, the wrong
link width was assumed when configuring the display pipe, because
`DP.Lane_Count` was used unconditionally instead of `FDI.Lane_Count`.

After fixing both issues, gfx_test is able to light up a 1920x1080 VGA
monitor on the Asrock B85M Pro4 successfully, even after a S3 resume.

Change-Id: Ieabe3b7f947be2ef488ddb57bfeae85fa055d360
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-on: https://review.coreboot.org/c/libgfxinit/+/41343
Tested-by: Nico Huber <nico.h@gmx.de>
Reviewed-by: Nico Huber <nico.h@gmx.de>
diff --git a/common/haswell/Makefile.inc b/common/haswell/Makefile.inc
index ef75dc4..32657a8 100644
--- a/common/haswell/Makefile.inc
+++ b/common/haswell/Makefile.inc
@@ -1,6 +1,8 @@
 gfxinit-y += hw-gfx-gma-connectors-ddi-buffers.adb
 gfxinit-y += hw-gfx-gma-connectors-ddi-buffers.ads
 gfxinit-y += hw-gfx-gma-ddi_phy.ads
+gfxinit-y += hw-gfx-gma-pch-lynxpoint.adb
+gfxinit-y += hw-gfx-gma-pch-lynxpoint.ads
 gfxinit-y += hw-gfx-gma-plls-lcpll.ads
 gfxinit-y += hw-gfx-gma-plls-wrpll.adb
 gfxinit-y += hw-gfx-gma-plls-wrpll.ads
diff --git a/common/haswell/hw-gfx-gma-pch-lynxpoint.adb b/common/haswell/hw-gfx-gma-pch-lynxpoint.adb
new file mode 100644
index 0000000..9a89a58
--- /dev/null
+++ b/common/haswell/hw-gfx-gma-pch-lynxpoint.adb
@@ -0,0 +1,177 @@
+--
+-- Copyright (C) 2020 Angel Pons <th3fanbus@gmail.com>
+--
+-- 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 HW.GFX.GMA.Config;
+with HW.GFX.GMA.Registers;
+with HW.GFX.GMA.PCH.Sideband;
+
+with HW.Debug;
+with GNAT.Source_Info;
+
+package body HW.GFX.GMA.PCH.Lynxpoint is
+
+   FDI_MPHY_IOSFSB_RESET_CTL     : constant := 1 * 2 ** 12;
+   FDI_MPHY_IOSFSB_RESET_STATUS  : constant := 1 * 2 ** 13;
+
+   procedure Reset_FDI_mPHY is
+   begin
+      Registers.Set_Mask
+        (Register => Registers.QUIRK_C2004,
+         Mask     => FDI_MPHY_IOSFSB_RESET_CTL);
+
+      Registers.Wait_Set_Mask
+        (Register => Registers.QUIRK_C2004,
+         Mask     => FDI_MPHY_IOSFSB_RESET_STATUS,
+         TOut_MS  => 1); -- 100 us
+
+      Registers.Unset_Mask
+        (Register => Registers.QUIRK_C2004,
+         Mask     => FDI_MPHY_IOSFSB_RESET_CTL);
+
+      Registers.Wait_Unset_Mask
+        (Register => Registers.QUIRK_C2004,
+         Mask     => FDI_MPHY_IOSFSB_RESET_STATUS,
+         TOut_MS  => 1); -- 100 us
+   end Reset_FDI_mPHY;
+
+   -- WaMPhyProgramming:hsw
+   procedure Program_FDI_mPHY
+   is
+      use Sideband;
+      subtype Bit_Index is Natural range 0 .. Word32'Size - 1;
+
+      procedure mPHY_Update_Field
+        (High_Bit : in Bit_Index;
+         Low_Bit  : in Bit_Index;
+         Value    : in Word32;
+         Register : in Register_Type)
+      with
+         Pre => High_Bit >= Low_Bit
+      is
+      begin
+         Unset_And_Set_Mask
+           (Dest       => SBI_MPHY,
+            Register   => Register,
+            Mask_Unset => 2 ** (High_Bit + 1) - 2 ** Low_Bit,
+            Mask_Set   => Value * 2 ** Low_Bit);
+      end mPHY_Update_Field;
+
+      procedure mPHY_Update_Lanes
+        (High_Bit    : in Bit_Index;
+         Low_Bit     : in Bit_Index;
+         Value       : in Word32;
+         Reg_Lane_0  : in Register_Type;
+         Reg_Lane_1  : in Register_Type)
+      with
+         Pre => High_Bit >= Low_Bit
+      is
+      begin
+         mPHY_Update_Field (High_Bit, Low_Bit, Value, Reg_Lane_0);
+         mPHY_Update_Field (High_Bit, Low_Bit, Value, Reg_Lane_1);
+      end mPHY_Update_Lanes;
+   begin
+      mPHY_Update_Field (31, 24, 16#12#, SBI_MPHY_8008);
+      mPHY_Update_Lanes (11, 11,      1, SBI_MPHY_2008, SBI_MPHY_2108);
+      mPHY_Update_Lanes (24, 24,      1, SBI_MPHY_206C, SBI_MPHY_216C);
+      mPHY_Update_Lanes (21, 21,      1, SBI_MPHY_206C, SBI_MPHY_216C);
+      mPHY_Update_Lanes (18, 18,      1, SBI_MPHY_206C, SBI_MPHY_216C);
+      mPHY_Update_Lanes (15, 13, 16#05#, SBI_MPHY_2080, SBI_MPHY_2180);
+      mPHY_Update_Lanes ( 7,  0, 16#1c#, SBI_MPHY_208C, SBI_MPHY_218C);
+      mPHY_Update_Lanes (23, 16, 16#1c#, SBI_MPHY_2098, SBI_MPHY_2198);
+      mPHY_Update_Lanes (27, 27,      1, SBI_MPHY_20C4, SBI_MPHY_21C4);
+      mPHY_Update_Lanes (31, 28, 16#04#, SBI_MPHY_20EC, SBI_MPHY_21EC);
+   end Program_FDI_mPHY;
+
+   ----------------------------------------------------------------------------
+
+   SBI_SSCCTL_DISABLE               : constant := 1 * 2 **  0;
+   SBI_SSCCTL_PATHALT               : constant := 1 * 2 **  3;
+   SBI_GEN0_CFG_BUFFENABLE_DISABLE  : constant := 1 * 2 **  0;
+
+   procedure Enable_Clkout_DP_And_FDI_mPHY is
+   begin
+      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+
+      Sideband.Unset_And_Set_Mask
+        (Dest        => Sideband.SBI_ICLK,
+         Register    => Sideband.SBI_SSCCTL,
+         Mask_Unset  => SBI_SSCCTL_DISABLE,
+         Mask_Set    => SBI_SSCCTL_PATHALT);
+
+      Time.U_Delay (24);
+
+      Sideband.Unset_Mask
+        (Dest     => Sideband.SBI_ICLK,
+         Register => Sideband.SBI_SSCCTL,
+         Mask     => SBI_SSCCTL_PATHALT);
+
+      Reset_FDI_mPHY;
+      Program_FDI_mPHY;
+
+      Sideband.Set_Mask
+        (Dest     => Sideband.SBI_ICLK,
+         Register => (if Config.Is_LP then Sideband.SBI_GEN0 else Sideband.SBI_DBUFF0),
+         Mask     => SBI_GEN0_CFG_BUFFENABLE_DISABLE);
+   end Enable_Clkout_DP_And_FDI_mPHY;
+
+   procedure Disable_Clkout_DP
+   is
+      function Is_Mask_Set (Value, Mask : Word32) return Boolean is
+        ((Value and Mask) = Mask);
+      SSC_Ctl  : Word32;
+   begin
+      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+
+      Sideband.Unset_Mask
+        (Dest     => Sideband.SBI_ICLK,
+         Register => (if Config.Is_LP then Sideband.SBI_GEN0 else Sideband.SBI_DBUFF0),
+         Mask     => SBI_GEN0_CFG_BUFFENABLE_DISABLE);
+
+      Sideband.Read
+        (Dest     => Sideband.SBI_ICLK,
+         Register => Sideband.SBI_SSCCTL,
+         Value    => SSC_Ctl);
+
+      if not Is_Mask_Set (SSC_Ctl, SBI_SSCCTL_DISABLE) then
+         if not Is_Mask_Set (SSC_Ctl, SBI_SSCCTL_PATHALT) then
+            Sideband.Set_Mask
+              (Dest     => Sideband.SBI_ICLK,
+               Register => Sideband.SBI_SSCCTL,
+               Mask     => SBI_SSCCTL_PATHALT);
+            Time.U_Delay (32);
+         end if;
+         Sideband.Set_Mask
+           (Dest     => Sideband.SBI_ICLK,
+            Register => Sideband.SBI_SSCCTL,
+            Mask     => SBI_SSCCTL_DISABLE);
+      end if;
+   end Disable_Clkout_DP;
+
+   procedure Unbend_Clkout_DP is
+   begin
+      pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
+
+      Sideband.Write
+        (Dest     => Sideband.SBI_ICLK,
+         Register => Sideband.SBI_SSCDITHPHASE,
+         Value    => 0);
+
+      Sideband.Unset_And_Set_Mask
+        (Dest        => Sideband.SBI_ICLK,
+         Register    => Sideband.SBI_SSCDIVINTPHASE,
+         Mask_Unset  => 16#ffff#,
+         Mask_Set    => 16#0025#);
+   end Unbend_Clkout_DP;
+
+end HW.GFX.GMA.PCH.Lynxpoint;
diff --git a/common/haswell/hw-gfx-gma-pch-lynxpoint.ads b/common/haswell/hw-gfx-gma-pch-lynxpoint.ads
new file mode 100644
index 0000000..5782683
--- /dev/null
+++ b/common/haswell/hw-gfx-gma-pch-lynxpoint.ads
@@ -0,0 +1,21 @@
+--
+-- Copyright (C) 2020 Angel Pons <th3fanbus@gmail.com>
+--
+-- 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.
+--
+
+package HW.GFX.GMA.PCH.Lynxpoint is
+
+   procedure Enable_Clkout_DP_And_FDI_mPHY;
+   procedure Disable_Clkout_DP;
+   procedure Unbend_Clkout_DP;
+
+end HW.GFX.GMA.PCH.Lynxpoint;
diff --git a/common/haswell/hw-gfx-gma-power_and_clocks_haswell.adb b/common/haswell/hw-gfx-gma-power_and_clocks_haswell.adb
index e261ace..fa52899 100644
--- a/common/haswell/hw-gfx-gma-power_and_clocks_haswell.adb
+++ b/common/haswell/hw-gfx-gma-power_and_clocks_haswell.adb
@@ -21,6 +21,7 @@
 with HW.GFX.GMA.PCode;
 with HW.GFX.GMA.Registers;
 with HW.GFX.GMA.Transcoder;
+with HW.GFX.GMA.PCH.Lynxpoint;
 
 package body HW.GFX.GMA.Power_And_Clocks_Haswell is
 
@@ -275,6 +276,13 @@
       Config.CDClk := CDClk;
    end Set_CDClk;
 
+   procedure Post_All_Off is
+   begin
+      -- Reset CLKOUT_DP to disabled state
+      PCH.Lynxpoint.Disable_Clkout_DP;
+      PCH.Lynxpoint.Unbend_Clkout_DP;
+   end Post_All_Off;
+
    procedure Initialize
    is
       CDClk : Config.CDClk_Range;
@@ -289,6 +297,11 @@
       Set_CDClk (Config.Default_CDClk_Freq);
 
       Config.Raw_Clock := Config.Default_RawClk_Freq;
+
+      -- Configure CLKOUT_DP for FDI
+      if Config.Has_DDI_E then
+         PCH.Lynxpoint.Enable_Clkout_DP_And_FDI_mPHY;
+      end if;
    end Initialize;
 
    procedure Limit_Dotclocks
diff --git a/common/haswell/hw-gfx-gma-power_and_clocks_haswell.ads b/common/haswell/hw-gfx-gma-power_and_clocks_haswell.ads
index 2665122..488b90c 100644
--- a/common/haswell/hw-gfx-gma-power_and_clocks_haswell.ads
+++ b/common/haswell/hw-gfx-gma-power_and_clocks_haswell.ads
@@ -17,7 +17,7 @@
 private package HW.GFX.GMA.Power_And_Clocks_Haswell is
 
    procedure Pre_All_Off;
-   procedure Post_All_Off is null;
+   procedure Post_All_Off;
 
    procedure Initialize;