Nico Huber has submitted this change. ( https://review.coreboot.org/c/libgfxinit/+/51133 )
Change subject: Add support to switch LSPCON modes ......................................................................
Add support to switch LSPCON modes
LSPCON is a level shifter and protocol converter chip. It is principally used to convert DisplayPort into HDMI 2.0, since some Intel iGPUs do not have native HDMI 2.0 support. The chip defaults to LS mode, in which the display engine has to generate an HDMI-protocol signal, which the LSPCON converts from DisplayPort signal levels to HDMI signal levels.
Add code to detect LSPCON chips and put them in protocol-converter mode. This way, the LSPCON behaves like a regular DisplayPort sink. This needs to be done before any DPCD accesses, because the LSPCON does not respond to DPCD accesses in level-shifter mode.
Tested on Purism Librem Mini v1 (WHL-U), HDMI port can now light up.
Change-Id: Idfa9bdff47a6591000cd5092d64a4cd4f8dbdc8d Signed-off-by: Angel Pons th3fanbus@gmail.com Reviewed-on: https://review.coreboot.org/c/libgfxinit/+/51133 Reviewed-by: Matt DeVillier matt.devillier@gmail.com Reviewed-by: Nico Huber nico.h@gmx.de --- M common/Makefile.inc A common/hw-gfx-dp_dual_mode.adb A common/hw-gfx-dp_dual_mode.ads M common/hw-gfx-gma-connector_info.adb A common/hw-gfx-gma-dp_dual_mode.ads 5 files changed, 265 insertions(+), 0 deletions(-)
Approvals: Angel Pons: Verified Nico Huber: Looks good to me, approved Matt DeVillier: Looks good to me, but someone else must approve
diff --git a/common/Makefile.inc b/common/Makefile.inc index b1ed074..2d18703 100644 --- a/common/Makefile.inc +++ b/common/Makefile.inc @@ -1,6 +1,8 @@ gfxinit-y += hw-gfx-dp_aux_ch.adb gfxinit-y += hw-gfx-dp_aux_ch.ads gfxinit-y += hw-gfx-dp_defs.ads +gfxinit-y += hw-gfx-dp_dual_mode.adb +gfxinit-y += hw-gfx-dp_dual_mode.ads gfxinit-y += hw-gfx-dp_info.adb gfxinit-y += hw-gfx-dp_info.ads gfxinit-y += hw-gfx-dp_training.adb @@ -17,6 +19,7 @@ gfxinit-y += hw-gfx-gma-dp_aux_ch.ads gfxinit-y += hw-gfx-gma-dp_aux_request.adb gfxinit-y += hw-gfx-gma-dp_aux_request.ads +gfxinit-y += hw-gfx-gma-dp_dual_mode.ads gfxinit-y += hw-gfx-gma-dp_info.ads gfxinit-y += hw-gfx-gma-i2c.adb gfxinit-y += hw-gfx-gma-i2c.ads diff --git a/common/hw-gfx-dp_dual_mode.adb b/common/hw-gfx-dp_dual_mode.adb new file mode 100644 index 0000000..595f8f3 --- /dev/null +++ b/common/hw-gfx-dp_dual_mode.adb @@ -0,0 +1,210 @@ +-- +-- Copyright (C) 2021 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.Debug; +with HW.Time; +with GNAT.Source_Info; + +with HW.GFX.DP_Defs; +with HW.GFX.I2C; + +use type HW.Word8; + +package body HW.GFX.DP_Dual_Mode is + + DUAL_MODE_I2C_ADDR : constant := 16#40#; + + DUAL_MODE_ADAPTOR_ID : constant := 16#10#; + LSPCON_MODE_CHANGE : constant := 16#40#; + LSPCON_CURRENT_MODE : constant := 16#41#; + + LSPCON_MODE_LS : constant := 0; + LSPCON_MODE_PCON : constant := 1; + + procedure Read_LSPCON_Mode + (Port : in T; + Mode : out Word8; + Success : out Boolean) + is + begin + pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); + for I in 1 .. 6 loop + Aux_Ch.I2C_Read_Byte + (Port => Port, + Address => DUAL_MODE_I2C_ADDR, + Offset => LSPCON_CURRENT_MODE, + Value => Mode, + Success => Success); + exit when Success; + Time.U_Delay (500); + end loop; + end Read_LSPCON_Mode; + + procedure Write_LSPCON_Mode + (Port : in T; + Mode : in Word8; + Success : out Boolean) + is + begin + pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); + for I in 1 .. 6 loop + Aux_Ch.I2C_Write_Byte + (Port => Port, + Address => DUAL_MODE_I2C_ADDR, + Offset => LSPCON_MODE_CHANGE, + Value => Mode, + Success => Success); + exit when Success; + Time.U_Delay (500); + end loop; + end Write_LSPCON_Mode; + + pragma Warnings + (GNATprove, Off, "subprogram ""Dump_LSPCON_Mode"" has no effect", + Reason => "It's only used for debugging"); + procedure Dump_LSPCON_Mode (Mode : in Word8) + is + begin + pragma Debug (Debug.Put ("Current LSPCON mode: ")); + case Mode is + when LSPCON_MODE_LS => pragma Debug (Debug.Put_Line ("Level Shifter")); + when LSPCON_MODE_PCON => pragma Debug (Debug.Put_Line ("Protocol Converter")); + when others => + pragma Debug (Debug.Put ("Unknown (")); + pragma Debug (Debug.Put_Word8 (Mode)); + pragma Debug (Debug.Put_Line (")")); + end case; + end Dump_LSPCON_Mode; + + procedure Auto_Configure_LSPCON (Port : in T) + is + Wanted_Mode : constant Word8 := LSPCON_MODE_PCON; + Timeout : Time.T; + Timed_Out : Boolean; + Success : Boolean; + Mode : Word8; + begin + pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); + + Read_LSPCON_Mode (Port, Mode, Success); + + if not Success then + pragma Debug (Debug.Put_Line ("Could not determine LSPCON mode.")); + return; + end if; + + Dump_LSPCON_Mode (Mode); + + if Mode = Wanted_Mode then + pragma Debug (Debug.Put_Line ("LSPCON mode is good enough.")); + return; + end if; + + -- Change to PCON mode if necessary + Write_LSPCON_Mode (Port, Wanted_Mode, Success); + + if not Success then + pragma Debug (Debug.Put_Line ("Could not write new LSPCON mode.")); + return; + end if; + + Timeout := Time.MS_From_Now (2000); + loop + Read_LSPCON_Mode (Port, Mode, Success); + + if not Success then + pragma Debug (Debug.Put_Line ("Could not confirm LSPCON mode change.")); + return; + end if; + + if Mode = Wanted_Mode then + pragma Debug (Debug.Put_Line ("Successfully set LSPCON to PCON mode.")); + return; + end if; + + Timed_Out := Time.Timed_Out (Timeout); + exit when Timed_Out; + end loop; + + pragma Debug (Debug.Put_Line ("Timed out waiting for LSPCON mode change.")); + Dump_LSPCON_Mode (Mode); + end Auto_Configure_LSPCON; + + subtype HDMI_ID_Index is Natural range 0 .. 15; + subtype HDMI_ID_Data is Buffer (HDMI_ID_Index); + + procedure Read_Dual_Mode_HDMI_ID + (Port : in T; + HDMI_ID : out HDMI_ID_Data; + Success : out Boolean) + is + Length : I2C.Transfer_Length := HDMI_ID'Length; + Buffer : I2C.Transfer_Data; + begin + Aux_Ch.I2C_Read + (Port => Port, + Address => DUAL_MODE_I2C_ADDR, + Length => Length, + Data => Buffer, + Success => Success); + + Success := Success and then Length = HDMI_ID'Length; + if Success then + HDMI_ID := Buffer (HDMI_ID'Range); + else + HDMI_ID := HDMI_ID_Data'(others => 0); + end if; + end Read_Dual_Mode_HDMI_ID; + + function Is_HDMI_Adaptor (Actual_ID : in HDMI_ID_Data) return Boolean + is + -- "DP-HDMI ADAPTOR\x04" + Expected_ID : constant HDMI_ID_Data := + (16#44#, 16#50#, 16#2d#, 16#48#, 16#44#, 16#4d#, 16#49#, 16#20#, + 16#41#, 16#44#, 16#41#, 16#50#, 16#54#, 16#4f#, 16#52#, 16#04#); + begin + return Expected_ID = Actual_ID; + end Is_HDMI_Adaptor; + + procedure Detect_LSPCON (Port : in T; Success : out Boolean) + is + HDMI_ID : HDMI_ID_Data; + Adaptor_ID : Word8; + begin + Read_Dual_Mode_HDMI_ID (Port, HDMI_ID, Success); + Success := Success and then Is_HDMI_Adaptor (HDMI_ID); + if Success then + Aux_Ch.I2C_Read_Byte + (Port => Port, + Address => DUAL_MODE_I2C_ADDR, + Offset => DUAL_MODE_ADAPTOR_ID, + Value => Adaptor_ID, + Success => Success); + + Success := Success and then Adaptor_ID = 16#a8#; + end if; + end Detect_LSPCON; + + procedure Switch_LSPCON (Port : in T) + is + Has_LSPCON : Boolean; + begin + pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity)); + Detect_LSPCON (Port, Has_LSPCON); + if Has_LSPCON then + Auto_Configure_LSPCON (Port); + end if; + end Switch_LSPCON; + +end HW.GFX.DP_Dual_Mode; diff --git a/common/hw-gfx-dp_dual_mode.ads b/common/hw-gfx-dp_dual_mode.ads new file mode 100644 index 0000000..3f4dd45 --- /dev/null +++ b/common/hw-gfx-dp_dual_mode.ads @@ -0,0 +1,27 @@ +-- +-- Copyright (C) 2021 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.DP_Aux_Ch; + +private generic + + type T (<>) is limited private; + + with package Aux_Ch is new DP_Aux_Ch (T => T, others => <>); + +package HW.GFX.DP_Dual_Mode is + + procedure Switch_LSPCON (Port : in T); + +end HW.GFX.DP_Dual_Mode; diff --git a/common/hw-gfx-gma-connector_info.adb b/common/hw-gfx-gma-connector_info.adb index dcbed2e..d611309 100644 --- a/common/hw-gfx-gma-connector_info.adb +++ b/common/hw-gfx-gma-connector_info.adb @@ -14,6 +14,7 @@
with HW.GFX.GMA.Config; with HW.GFX.GMA.Panel; +with HW.GFX.GMA.DP_Dual_Mode; with HW.GFX.GMA.DP_Info;
with HW.Debug; @@ -44,6 +45,8 @@ Panel.On (Port_Cfg.Panel); end if;
+ DP_Dual_Mode.Switch_LSPCON (DP_Port); + DP_Info.Read_Caps (Link => Port_Cfg.DP, Port => DP_Port, diff --git a/common/hw-gfx-gma-dp_dual_mode.ads b/common/hw-gfx-gma-dp_dual_mode.ads new file mode 100644 index 0000000..6457928 --- /dev/null +++ b/common/hw-gfx-gma-dp_dual_mode.ads @@ -0,0 +1,22 @@ +-- +-- Copyright (C) 2021 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.DP_Dual_Mode; +pragma Elaborate_All (HW.GFX.DP_Dual_Mode); +with HW.GFX.GMA.DP_Aux_Ch; + +private package HW.GFX.GMA.DP_Dual_Mode + is new HW.GFX.DP_Dual_Mode + (T => DP_Port, + Aux_Ch => DP_Aux_Ch);