]>
Commit | Line | Data |
---|---|---|
dbe9e61b SS |
1 | /* |
2 | * Copyright © 2016 Intel Corporation | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice (including the next | |
12 | * paragraph) shall be included in all copies or substantial portions of the | |
13 | * Software. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
21 | * DEALINGS IN THE SOFTWARE. | |
22 | * | |
23 | * | |
24 | */ | |
25 | #include <drm/drm_edid.h> | |
26 | #include <drm/drm_atomic_helper.h> | |
27 | #include <drm/drm_dp_dual_mode_helper.h> | |
28 | #include "intel_drv.h" | |
29 | ||
a5d94b83 ID |
30 | static struct intel_dp *lspcon_to_intel_dp(struct intel_lspcon *lspcon) |
31 | { | |
32 | struct intel_digital_port *dig_port = | |
33 | container_of(lspcon, struct intel_digital_port, lspcon); | |
34 | ||
35 | return &dig_port->dp; | |
36 | } | |
37 | ||
357c0ae9 ID |
38 | static const char *lspcon_mode_name(enum drm_lspcon_mode mode) |
39 | { | |
40 | switch (mode) { | |
41 | case DRM_LSPCON_MODE_PCON: | |
42 | return "PCON"; | |
43 | case DRM_LSPCON_MODE_LS: | |
44 | return "LS"; | |
45 | case DRM_LSPCON_MODE_INVALID: | |
46 | return "INVALID"; | |
47 | default: | |
48 | MISSING_CASE(mode); | |
49 | return "INVALID"; | |
50 | } | |
51 | } | |
52 | ||
1dc16aac | 53 | static enum drm_lspcon_mode lspcon_get_current_mode(struct intel_lspcon *lspcon) |
dbe9e61b | 54 | { |
357c0ae9 | 55 | enum drm_lspcon_mode current_mode; |
a5d94b83 | 56 | struct i2c_adapter *adapter = &lspcon_to_intel_dp(lspcon)->aux.ddc; |
dbe9e61b | 57 | |
357c0ae9 | 58 | if (drm_lspcon_get_mode(adapter, ¤t_mode)) { |
d18aef0f | 59 | DRM_DEBUG_KMS("Error reading LSPCON mode\n"); |
357c0ae9 ID |
60 | return DRM_LSPCON_MODE_INVALID; |
61 | } | |
62 | return current_mode; | |
63 | } | |
64 | ||
65 | static enum drm_lspcon_mode lspcon_wait_mode(struct intel_lspcon *lspcon, | |
66 | enum drm_lspcon_mode mode) | |
67 | { | |
68 | enum drm_lspcon_mode current_mode; | |
69 | ||
70 | current_mode = lspcon_get_current_mode(lspcon); | |
d18aef0f | 71 | if (current_mode == mode) |
357c0ae9 ID |
72 | goto out; |
73 | ||
74 | DRM_DEBUG_KMS("Waiting for LSPCON mode %s to settle\n", | |
75 | lspcon_mode_name(mode)); | |
76 | ||
58916edc | 77 | wait_for((current_mode = lspcon_get_current_mode(lspcon)) == mode, 400); |
357c0ae9 | 78 | if (current_mode != mode) |
d18aef0f | 79 | DRM_ERROR("LSPCON mode hasn't settled\n"); |
357c0ae9 ID |
80 | |
81 | out: | |
82 | DRM_DEBUG_KMS("Current LSPCON mode %s\n", | |
83 | lspcon_mode_name(current_mode)); | |
84 | ||
dbe9e61b SS |
85 | return current_mode; |
86 | } | |
87 | ||
88 | static int lspcon_change_mode(struct intel_lspcon *lspcon, | |
90248800 | 89 | enum drm_lspcon_mode mode) |
dbe9e61b SS |
90 | { |
91 | int err; | |
92 | enum drm_lspcon_mode current_mode; | |
a5d94b83 | 93 | struct i2c_adapter *adapter = &lspcon_to_intel_dp(lspcon)->aux.ddc; |
dbe9e61b SS |
94 | |
95 | err = drm_lspcon_get_mode(adapter, ¤t_mode); | |
96 | if (err) { | |
97 | DRM_ERROR("Error reading LSPCON mode\n"); | |
98 | return err; | |
99 | } | |
100 | ||
101 | if (current_mode == mode) { | |
102 | DRM_DEBUG_KMS("Current mode = desired LSPCON mode\n"); | |
103 | return 0; | |
104 | } | |
105 | ||
106 | err = drm_lspcon_set_mode(adapter, mode); | |
107 | if (err < 0) { | |
108 | DRM_ERROR("LSPCON mode change failed\n"); | |
109 | return err; | |
110 | } | |
111 | ||
112 | lspcon->mode = mode; | |
113 | DRM_DEBUG_KMS("LSPCON mode changed done\n"); | |
114 | return 0; | |
115 | } | |
116 | ||
f2b667b6 ID |
117 | static bool lspcon_wake_native_aux_ch(struct intel_lspcon *lspcon) |
118 | { | |
119 | uint8_t rev; | |
120 | ||
121 | if (drm_dp_dpcd_readb(&lspcon_to_intel_dp(lspcon)->aux, DP_DPCD_REV, | |
122 | &rev) != 1) { | |
123 | DRM_DEBUG_KMS("Native AUX CH down\n"); | |
124 | return false; | |
125 | } | |
126 | ||
127 | DRM_DEBUG_KMS("Native AUX CH up, DPCD version: %d.%d\n", | |
128 | rev >> 4, rev & 0xf); | |
129 | ||
130 | return true; | |
131 | } | |
132 | ||
dbe9e61b SS |
133 | static bool lspcon_probe(struct intel_lspcon *lspcon) |
134 | { | |
a2fc4bd6 | 135 | int retry; |
dbe9e61b | 136 | enum drm_dp_dual_mode_type adaptor_type; |
a5d94b83 | 137 | struct i2c_adapter *adapter = &lspcon_to_intel_dp(lspcon)->aux.ddc; |
357c0ae9 | 138 | enum drm_lspcon_mode expected_mode; |
dbe9e61b | 139 | |
357c0ae9 ID |
140 | expected_mode = lspcon_wake_native_aux_ch(lspcon) ? |
141 | DRM_LSPCON_MODE_PCON : DRM_LSPCON_MODE_LS; | |
f2b667b6 | 142 | |
dbe9e61b | 143 | /* Lets probe the adaptor and check its type */ |
a2fc4bd6 SS |
144 | for (retry = 0; retry < 6; retry++) { |
145 | if (retry) | |
146 | usleep_range(500, 1000); | |
147 | ||
148 | adaptor_type = drm_dp_dual_mode_detect(adapter); | |
149 | if (adaptor_type == DRM_DP_DUAL_MODE_LSPCON) | |
150 | break; | |
151 | } | |
152 | ||
dbe9e61b SS |
153 | if (adaptor_type != DRM_DP_DUAL_MODE_LSPCON) { |
154 | DRM_DEBUG_KMS("No LSPCON detected, found %s\n", | |
a2fc4bd6 | 155 | drm_dp_get_dual_mode_type_name(adaptor_type)); |
dbe9e61b SS |
156 | return false; |
157 | } | |
158 | ||
159 | /* Yay ... got a LSPCON device */ | |
160 | DRM_DEBUG_KMS("LSPCON detected\n"); | |
357c0ae9 | 161 | lspcon->mode = lspcon_wait_mode(lspcon, expected_mode); |
dbe9e61b SS |
162 | lspcon->active = true; |
163 | return true; | |
164 | } | |
165 | ||
489375c8 ID |
166 | static void lspcon_resume_in_pcon_wa(struct intel_lspcon *lspcon) |
167 | { | |
168 | struct intel_dp *intel_dp = lspcon_to_intel_dp(lspcon); | |
390b4e00 ID |
169 | struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); |
170 | struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); | |
489375c8 ID |
171 | unsigned long start = jiffies; |
172 | ||
489375c8 | 173 | while (1) { |
f1239595 | 174 | if (intel_digital_port_connected(dev_priv, dig_port)) { |
489375c8 ID |
175 | DRM_DEBUG_KMS("LSPCON recovering in PCON mode after %u ms\n", |
176 | jiffies_to_msecs(jiffies - start)); | |
177 | return; | |
178 | } | |
179 | ||
180 | if (time_after(jiffies, start + msecs_to_jiffies(1000))) | |
181 | break; | |
182 | ||
183 | usleep_range(10000, 15000); | |
184 | } | |
185 | ||
186 | DRM_DEBUG_KMS("LSPCON DP descriptor mismatch after resume\n"); | |
187 | } | |
188 | ||
910530c0 SS |
189 | void lspcon_resume(struct intel_lspcon *lspcon) |
190 | { | |
357c0ae9 ID |
191 | enum drm_lspcon_mode expected_mode; |
192 | ||
193 | if (lspcon_wake_native_aux_ch(lspcon)) { | |
194 | expected_mode = DRM_LSPCON_MODE_PCON; | |
f2b667b6 | 195 | lspcon_resume_in_pcon_wa(lspcon); |
357c0ae9 ID |
196 | } else { |
197 | expected_mode = DRM_LSPCON_MODE_LS; | |
198 | } | |
199 | ||
200 | if (lspcon_wait_mode(lspcon, expected_mode) == DRM_LSPCON_MODE_PCON) | |
201 | return; | |
489375c8 | 202 | |
90248800 | 203 | if (lspcon_change_mode(lspcon, DRM_LSPCON_MODE_PCON)) |
910530c0 SS |
204 | DRM_ERROR("LSPCON resume failed\n"); |
205 | else | |
206 | DRM_DEBUG_KMS("LSPCON resume success\n"); | |
207 | } | |
208 | ||
357c0ae9 ID |
209 | void lspcon_wait_pcon_mode(struct intel_lspcon *lspcon) |
210 | { | |
211 | lspcon_wait_mode(lspcon, DRM_LSPCON_MODE_PCON); | |
212 | } | |
213 | ||
dbe9e61b SS |
214 | bool lspcon_init(struct intel_digital_port *intel_dig_port) |
215 | { | |
216 | struct intel_dp *dp = &intel_dig_port->dp; | |
217 | struct intel_lspcon *lspcon = &intel_dig_port->lspcon; | |
218 | struct drm_device *dev = intel_dig_port->base.base.dev; | |
219 | struct drm_i915_private *dev_priv = to_i915(dev); | |
220 | ||
acf58d4e RV |
221 | if (!HAS_LSPCON(dev_priv)) { |
222 | DRM_ERROR("LSPCON is not supported on this platform\n"); | |
dbe9e61b SS |
223 | return false; |
224 | } | |
225 | ||
226 | lspcon->active = false; | |
227 | lspcon->mode = DRM_LSPCON_MODE_INVALID; | |
dbe9e61b SS |
228 | |
229 | if (!lspcon_probe(lspcon)) { | |
230 | DRM_ERROR("Failed to probe lspcon\n"); | |
231 | return false; | |
232 | } | |
233 | ||
234 | /* | |
235 | * In the SW state machine, lets Put LSPCON in PCON mode only. | |
236 | * In this way, it will work with both HDMI 1.4 sinks as well as HDMI | |
237 | * 2.0 sinks. | |
238 | */ | |
239 | if (lspcon->active && lspcon->mode != DRM_LSPCON_MODE_PCON) { | |
90248800 | 240 | if (lspcon_change_mode(lspcon, DRM_LSPCON_MODE_PCON) < 0) { |
dbe9e61b SS |
241 | DRM_ERROR("LSPCON mode change to PCON failed\n"); |
242 | return false; | |
243 | } | |
244 | } | |
245 | ||
24e807e7 ID |
246 | if (!intel_dp_read_dpcd(dp)) { |
247 | DRM_ERROR("LSPCON DPCD read failed\n"); | |
248 | return false; | |
249 | } | |
250 | ||
84c36753 | 251 | drm_dp_read_desc(&dp->aux, &dp->desc, drm_dp_is_branch(dp->dpcd)); |
12a47a42 | 252 | |
dbe9e61b SS |
253 | DRM_DEBUG_KMS("Success: LSPCON init\n"); |
254 | return true; | |
255 | } |