blob: 74e2ad8d9b7607de125efb3802ac6ca358daab63 [file] [log] [blame]
Nico Huber8c45bcf2016-11-20 17:30:57 +01001--
2-- Copyright (C) 2015-2016 secunet Security Networks AG
Nico Huber4fc6dc22019-05-10 13:01:29 +02003-- Copyright (C) 2019 Nico Huber <nico.h@gmx.de>
Nico Huber8c45bcf2016-11-20 17:30:57 +01004--
5-- This program is free software; you can redistribute it and/or modify
6-- it under the terms of the GNU General Public License as published by
7-- the Free Software Foundation; either version 2 of the License, or
8-- (at your option) any later version.
9--
10-- This program is distributed in the hope that it will be useful,
11-- but WITHOUT ANY WARRANTY; without even the implied warranty of
12-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13-- GNU General Public License for more details.
14--
15
16with HW.GFX.I2C;
Nico Huber8c45bcf2016-11-20 17:30:57 +010017with HW.GFX.GMA.Config;
18with HW.GFX.GMA.Config_Helpers;
19with HW.GFX.GMA.I2C;
20with HW.GFX.GMA.DP_Aux_Ch;
21with HW.GFX.GMA.Panel;
22with HW.GFX.GMA.Power_And_Clocks;
23
24with HW.Debug;
25with GNAT.Source_Info;
26
27package body HW.GFX.GMA.Display_Probing
28is
29
Nico Huber5a3191f2018-06-04 14:42:13 +020030 function Port_Configured (Configs : Pipe_Configs; Port : Port_Type)
31 return Boolean is
32 (Configs (Primary).Port = Port or
33 Configs (Secondary).Port = Port or
34 Configs (Tertiary).Port = Port);
Nico Huber8c45bcf2016-11-20 17:30:57 +010035
36 -- DP and HDMI share physical pins.
Nico Huber5a3191f2018-06-04 14:42:13 +020037 function Sibling_Port (Port : Port_Type) return Port_Type is
38 (case Port is
39 when HDMI1 => DP1,
40 when HDMI2 => DP2,
41 when HDMI3 => DP3,
42 when DP1 => HDMI1,
43 when DP2 => HDMI2,
44 when DP3 => HDMI3,
45 when others => Disabled);
Nico Huber8c45bcf2016-11-20 17:30:57 +010046
Nico Huber5a3191f2018-06-04 14:42:13 +020047 function Has_Sibling_Port (Port : Port_Type) return Boolean is
48 (Sibling_Port (Port) /= Disabled);
Nico Huber8c45bcf2016-11-20 17:30:57 +010049
Nico Huber5a3191f2018-06-04 14:42:13 +020050 function Is_DVI_I (Port : Active_Port_Type) return Boolean is
51 (Config.Have_DVI_I and
52 (Port = Analog or
53 Config_Helpers.To_PCH_Port (Port) = Config.Analog_I2C_Port));
Nico Huber1bc496f2017-06-09 22:23:28 +020054
Nico Huber8c45bcf2016-11-20 17:30:57 +010055 procedure Read_EDID
56 (Raw_EDID : out EDID.Raw_EDID_Data;
57 Port : in Active_Port_Type;
58 Success : out Boolean)
Nico Huber8c45bcf2016-11-20 17:30:57 +010059 is
60 Raw_EDID_Length : GFX.I2C.Transfer_Length := Raw_EDID'Length;
61 begin
62 pragma Debug (Debug.Put_Line (GNAT.Source_Info.Enclosing_Entity));
63
64 for I in 1 .. 2 loop
65 if Config_Helpers.To_Display_Type (Port) = DP then
Nico Huberb0bbdbc2019-09-27 22:32:21 +020066 -- May need power and CDClk to read EDID
Nico Huber8c45bcf2016-11-20 17:30:57 +010067 declare
68 Temp_Configs : Pipe_Configs := Cur_Configs;
69 begin
70 Temp_Configs (Primary).Port := Port;
71 Power_And_Clocks.Power_Up (Cur_Configs, Temp_Configs);
Nico Huberb0bbdbc2019-09-27 22:32:21 +020072 Power_And_Clocks.Enable_CDClk;
Nico Huber8c45bcf2016-11-20 17:30:57 +010073 end;
74
75 declare
76 DP_Port : constant GMA.DP_Port :=
77 (case Port is
78 when Internal => DP_A,
79 when DP1 => DP_B,
80 when DP2 => DP_C,
81 when DP3 => DP_D,
82 when others => GMA.DP_Port'First);
83 begin
84 DP_Aux_Ch.I2C_Read
85 (Port => DP_Port,
86 Address => 16#50#,
87 Length => Raw_EDID_Length,
88 Data => Raw_EDID,
89 Success => Success);
90 end;
91 else
92 I2C.I2C_Read
93 (Port => (if Port = Analog
94 then Config.Analog_I2C_Port
95 else Config_Helpers.To_PCH_Port (Port)),
96 Address => 16#50#,
97 Length => Raw_EDID_Length,
98 Data => Raw_EDID,
99 Success => Success);
100 end if;
101 exit when not Success; -- don't retry if reading itself failed
102
103 pragma Debug (Debug.Put_Buffer ("EDID", Raw_EDID, Raw_EDID_Length));
104 EDID.Sanitize (Raw_EDID, Success);
105 exit when Success;
106 end loop;
107 end Read_EDID;
108
109 procedure Probe_Port
110 (Pipe_Cfg : in out Pipe_Config;
111 Port : in Active_Port_Type;
112 Success : out Boolean)
113 with Pre => True
114 is
115 Raw_EDID : EDID.Raw_EDID_Data := (others => 16#00#);
116 begin
117 Success := Config.Valid_Port (Port);
118
119 if Success then
120 if Port = Internal then
Nico Huber6f9a50d2016-11-21 23:21:14 +0100121 Panel.Wait_On;
Nico Huber8c45bcf2016-11-20 17:30:57 +0100122 end if;
123 Read_EDID (Raw_EDID, Port, Success);
124 end if;
125
126 if Success and then
Nico Huber1bc496f2017-06-09 22:23:28 +0200127 ((not Is_DVI_I (Port) or EDID.Compatible_Display
128 (Raw_EDID, Config_Helpers.To_Display_Type (Port))) and
Nico Huber8c45bcf2016-11-20 17:30:57 +0100129 EDID.Has_Preferred_Mode (Raw_EDID))
130 then
131 Pipe_Cfg.Port := Port;
132 Pipe_Cfg.Mode := EDID.Preferred_Mode (Raw_EDID);
133
134 pragma Warnings (GNATprove, Off, "unused assignment to ""Raw_EDID""",
135 Reason => "We just want to check if it's readable.");
136 if Has_Sibling_Port (Port) then
137 -- Probe sibling port too and bail out if something is detected.
138 -- This is a precaution for adapters that expose the pins of a
139 -- port for both HDMI/DVI and DP (like some ThinkPad docks). A
140 -- user might have attached both by accident and there are ru-
141 -- mors of displays that got fried by applying the wrong signal.
142 declare
143 Have_Sibling_EDID : Boolean;
144 begin
145 Read_EDID (Raw_EDID, Sibling_Port (Port), Have_Sibling_EDID);
146 if Have_Sibling_EDID then
147 Pipe_Cfg.Port := Disabled;
148 Success := False;
149 end if;
150 end;
151 end if;
152 pragma Warnings (GNATprove, On, "unused assignment to ""Raw_EDID""");
153 else
154 Success := False;
Nico Huber8c45bcf2016-11-20 17:30:57 +0100155 end if;
156 end Probe_Port;
157
158 procedure Scan_Ports
Nico Huber4c7356d2016-12-16 14:22:32 +0100159 (Configs : out Pipe_Configs;
160 Ports : in Port_List := All_Ports;
161 Max_Pipe : in Pipe_Index := Pipe_Index'Last;
162 Keep_Power : in Boolean := False)
Nico Huber8c45bcf2016-11-20 17:30:57 +0100163 is
Nico Huber6f9a50d2016-11-21 23:21:14 +0100164 Probe_Internal : Boolean := False;
165
Nico Huber8c45bcf2016-11-20 17:30:57 +0100166 Port_Idx : Port_List_Range := Port_List_Range'First;
167 Success : Boolean;
168 begin
169 Configs := (Pipe_Index =>
170 (Port => Disabled,
171 Mode => Invalid_Mode,
Nico Hubera02b2c62018-01-09 15:58:34 +0100172 Cursor => Default_Cursor,
Nico Huber8c45bcf2016-11-20 17:30:57 +0100173 Framebuffer => Default_FB));
174
Nico Huber6f9a50d2016-11-21 23:21:14 +0100175 -- Turn panel on early to probe other ports during the power on delay.
176 for Idx in Port_List_Range loop
177 exit when Ports (Idx) = Disabled;
178 if Ports (Idx) = Internal then
179 Panel.On (Wait => False);
180 Probe_Internal := True;
181 exit;
182 end if;
183 end loop;
184
Nico Huber8c45bcf2016-11-20 17:30:57 +0100185 for Pipe in Pipe_Index range
186 Pipe_Index'First .. Pipe_Index'Min (Max_Pipe, Config.Max_Pipe)
187 loop
Nico Huber4fc6dc22019-05-10 13:01:29 +0200188 Success := False;
Nico Huber8c45bcf2016-11-20 17:30:57 +0100189 while Ports (Port_Idx) /= Disabled loop
190 if not Port_Configured (Configs, Ports (Port_Idx)) and
191 (not Has_Sibling_Port (Ports (Port_Idx)) or
192 not Port_Configured (Configs, Sibling_Port (Ports (Port_Idx))))
193 then
194 Probe_Port (Configs (Pipe), Ports (Port_Idx), Success);
195 else
196 Success := False;
197 end if;
198
199 exit when Port_Idx = Port_List_Range'Last;
200 Port_Idx := Port_List_Range'Succ (Port_Idx);
201
202 exit when Success;
203 end loop;
Nico Huber4fc6dc22019-05-10 13:01:29 +0200204 exit when not Success;
Nico Huber8c45bcf2016-11-20 17:30:57 +0100205 end loop;
206
207 -- Restore power settings
Nico Huber4c7356d2016-12-16 14:22:32 +0100208 if not Keep_Power then
209 Power_And_Clocks.Power_Set_To (Cur_Configs);
210 end if;
Nico Huber6f9a50d2016-11-21 23:21:14 +0100211
212 -- Turn panel power off if probing failed.
213 if Probe_Internal and not Port_Configured (Configs, Internal) then
214 Panel.Off;
215 end if;
Nico Huber8c45bcf2016-11-20 17:30:57 +0100216 end Scan_Ports;
217
218end HW.GFX.GMA.Display_Probing;