]>
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)) { |
dbe9e61b | 59 | DRM_ERROR("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); | |
71 | if (current_mode == mode || current_mode == DRM_LSPCON_MODE_INVALID) | |
72 | goto out; | |
73 | ||
74 | DRM_DEBUG_KMS("Waiting for LSPCON mode %s to settle\n", | |
75 | lspcon_mode_name(mode)); | |
76 | ||
77 | wait_for((current_mode = lspcon_get_current_mode(lspcon)) == mode || | |
78 | current_mode == DRM_LSPCON_MODE_INVALID, 100); | |
79 | if (current_mode != mode) | |
80 | DRM_DEBUG_KMS("LSPCON mode hasn't settled\n"); | |
81 | ||
82 | out: | |
83 | DRM_DEBUG_KMS("Current LSPCON mode %s\n", | |
84 | lspcon_mode_name(current_mode)); | |
85 | ||
dbe9e61b SS |
86 | return current_mode; |
87 | } | |
88 | ||
89 | static int lspcon_change_mode(struct intel_lspcon *lspcon, | |
90248800 | 90 | enum drm_lspcon_mode mode) |
dbe9e61b SS |
91 | { |
92 | int err; | |
93 | enum drm_lspcon_mode current_mode; | |
a5d94b83 | 94 | struct i2c_adapter *adapter = &lspcon_to_intel_dp(lspcon)->aux.ddc; |
dbe9e61b SS |
95 | |
96 | err = drm_lspcon_get_mode(adapter, ¤t_mode); | |
97 | if (err) { | |
98 | DRM_ERROR("Error reading LSPCON mode\n"); | |
99 | return err; | |
100 | } | |
101 | ||
102 | if (current_mode == mode) { | |
103 | DRM_DEBUG_KMS("Current mode = desired LSPCON mode\n"); | |
104 | return 0; | |
105 | } | |
106 | ||
107 | err = drm_lspcon_set_mode(adapter, mode); | |
108 | if (err < 0) { | |
109 | DRM_ERROR("LSPCON mode change failed\n"); | |
110 | return err; | |
111 | } | |
112 | ||
113 | lspcon->mode = mode; | |
114 | DRM_DEBUG_KMS("LSPCON mode changed done\n"); | |
115 | return 0; | |
116 | } | |
117 | ||
f2b667b6 ID |
118 | static bool lspcon_wake_native_aux_ch(struct intel_lspcon *lspcon) |
119 | { | |
120 | uint8_t rev; | |
121 | ||
122 | if (drm_dp_dpcd_readb(&lspcon_to_intel_dp(lspcon)->aux, DP_DPCD_REV, | |
123 | &rev) != 1) { | |
124 | DRM_DEBUG_KMS("Native AUX CH down\n"); | |
125 | return false; | |
126 | } | |
127 | ||
128 | DRM_DEBUG_KMS("Native AUX CH up, DPCD version: %d.%d\n", | |
129 | rev >> 4, rev & 0xf); | |
130 | ||
131 | return true; | |
132 | } | |
133 | ||
dbe9e61b SS |
134 | static bool lspcon_probe(struct intel_lspcon *lspcon) |
135 | { | |
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 SS |
143 | /* Lets probe the adaptor and check its type */ |
144 | adaptor_type = drm_dp_dual_mode_detect(adapter); | |
145 | if (adaptor_type != DRM_DP_DUAL_MODE_LSPCON) { | |
146 | DRM_DEBUG_KMS("No LSPCON detected, found %s\n", | |
147 | drm_dp_get_dual_mode_type_name(adaptor_type)); | |
148 | return false; | |
149 | } | |
150 | ||
151 | /* Yay ... got a LSPCON device */ | |
152 | DRM_DEBUG_KMS("LSPCON detected\n"); | |
357c0ae9 | 153 | lspcon->mode = lspcon_wait_mode(lspcon, expected_mode); |
dbe9e61b SS |
154 | lspcon->active = true; |
155 | return true; | |
156 | } | |
157 | ||
489375c8 ID |
158 | static void lspcon_resume_in_pcon_wa(struct intel_lspcon *lspcon) |
159 | { | |
160 | struct intel_dp *intel_dp = lspcon_to_intel_dp(lspcon); | |
390b4e00 ID |
161 | struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); |
162 | struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); | |
489375c8 ID |
163 | unsigned long start = jiffies; |
164 | ||
489375c8 | 165 | while (1) { |
f1239595 | 166 | if (intel_digital_port_connected(dev_priv, dig_port)) { |
489375c8 ID |
167 | DRM_DEBUG_KMS("LSPCON recovering in PCON mode after %u ms\n", |
168 | jiffies_to_msecs(jiffies - start)); | |
169 | return; | |
170 | } | |
171 | ||
172 | if (time_after(jiffies, start + msecs_to_jiffies(1000))) | |
173 | break; | |
174 | ||
175 | usleep_range(10000, 15000); | |
176 | } | |
177 | ||
178 | DRM_DEBUG_KMS("LSPCON DP descriptor mismatch after resume\n"); | |
179 | } | |
180 | ||
910530c0 SS |
181 | void lspcon_resume(struct intel_lspcon *lspcon) |
182 | { | |
357c0ae9 ID |
183 | enum drm_lspcon_mode expected_mode; |
184 | ||
185 | if (lspcon_wake_native_aux_ch(lspcon)) { | |
186 | expected_mode = DRM_LSPCON_MODE_PCON; | |
f2b667b6 | 187 | lspcon_resume_in_pcon_wa(lspcon); |
357c0ae9 ID |
188 | } else { |
189 | expected_mode = DRM_LSPCON_MODE_LS; | |
190 | } | |
191 | ||
192 | if (lspcon_wait_mode(lspcon, expected_mode) == DRM_LSPCON_MODE_PCON) | |
193 | return; | |
489375c8 | 194 | |
90248800 | 195 | if (lspcon_change_mode(lspcon, DRM_LSPCON_MODE_PCON)) |
910530c0 SS |
196 | DRM_ERROR("LSPCON resume failed\n"); |
197 | else | |
198 | DRM_DEBUG_KMS("LSPCON resume success\n"); | |
199 | } | |
200 | ||
357c0ae9 ID |
201 | void lspcon_wait_pcon_mode(struct intel_lspcon *lspcon) |
202 | { | |
203 | lspcon_wait_mode(lspcon, DRM_LSPCON_MODE_PCON); | |
204 | } | |
205 | ||
dbe9e61b SS |
206 | bool lspcon_init(struct intel_digital_port *intel_dig_port) |
207 | { | |
208 | struct intel_dp *dp = &intel_dig_port->dp; | |
209 | struct intel_lspcon *lspcon = &intel_dig_port->lspcon; | |
210 | struct drm_device *dev = intel_dig_port->base.base.dev; | |
211 | struct drm_i915_private *dev_priv = to_i915(dev); | |
212 | ||
acf58d4e RV |
213 | if (!HAS_LSPCON(dev_priv)) { |
214 | DRM_ERROR("LSPCON is not supported on this platform\n"); | |
dbe9e61b SS |
215 | return false; |
216 | } | |
217 | ||
218 | lspcon->active = false; | |
219 | lspcon->mode = DRM_LSPCON_MODE_INVALID; | |
dbe9e61b SS |
220 | |
221 | if (!lspcon_probe(lspcon)) { | |
222 | DRM_ERROR("Failed to probe lspcon\n"); | |
223 | return false; | |
224 | } | |
225 | ||
226 | /* | |
227 | * In the SW state machine, lets Put LSPCON in PCON mode only. | |
228 | * In this way, it will work with both HDMI 1.4 sinks as well as HDMI | |
229 | * 2.0 sinks. | |
230 | */ | |
231 | if (lspcon->active && lspcon->mode != DRM_LSPCON_MODE_PCON) { | |
90248800 | 232 | if (lspcon_change_mode(lspcon, DRM_LSPCON_MODE_PCON) < 0) { |
dbe9e61b SS |
233 | DRM_ERROR("LSPCON mode change to PCON failed\n"); |
234 | return false; | |
235 | } | |
236 | } | |
237 | ||
24e807e7 ID |
238 | if (!intel_dp_read_dpcd(dp)) { |
239 | DRM_ERROR("LSPCON DPCD read failed\n"); | |
240 | return false; | |
241 | } | |
242 | ||
84c36753 | 243 | drm_dp_read_desc(&dp->aux, &dp->desc, drm_dp_is_branch(dp->dpcd)); |
12a47a42 | 244 | |
dbe9e61b SS |
245 | DRM_DEBUG_KMS("Success: LSPCON init\n"); |
246 | return true; | |
247 | } |