]>
Commit | Line | Data |
---|---|---|
4562236b HW |
1 | /* |
2 | * Copyright 2012-15 Advanced Micro Devices, Inc. | |
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 shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
20 | * OTHER DEALINGS IN THE SOFTWARE. | |
21 | * | |
22 | * Authors: AMD | |
23 | * | |
24 | */ | |
25 | ||
26 | #include <linux/version.h> | |
27 | #include <drm/drm_atomic_helper.h> | |
28 | #include "dm_services.h" | |
29 | #include "amdgpu.h" | |
e7b07cee | 30 | #include "amdgpu_dm.h" |
4562236b HW |
31 | #include "amdgpu_dm_mst_types.h" |
32 | ||
33 | #include "dc.h" | |
34 | #include "dm_helpers.h" | |
35 | ||
46df790c AG |
36 | #include "dc_link_ddc.h" |
37 | ||
4562236b HW |
38 | /* #define TRACE_DPCD */ |
39 | ||
40 | #ifdef TRACE_DPCD | |
41 | #define SIDE_BAND_MSG(address) (address >= DP_SIDEBAND_MSG_DOWN_REQ_BASE && address < DP_SINK_COUNT_ESI) | |
42 | ||
43 | static inline char *side_band_msg_type_to_str(uint32_t address) | |
44 | { | |
45 | static char str[10] = {0}; | |
46 | ||
47 | if (address < DP_SIDEBAND_MSG_UP_REP_BASE) | |
48 | strcpy(str, "DOWN_REQ"); | |
49 | else if (address < DP_SIDEBAND_MSG_DOWN_REP_BASE) | |
50 | strcpy(str, "UP_REP"); | |
51 | else if (address < DP_SIDEBAND_MSG_UP_REQ_BASE) | |
52 | strcpy(str, "DOWN_REP"); | |
53 | else | |
54 | strcpy(str, "UP_REQ"); | |
55 | ||
56 | return str; | |
57 | } | |
58 | ||
6563617f AD |
59 | static void log_dpcd(uint8_t type, |
60 | uint32_t address, | |
61 | uint8_t *data, | |
62 | uint32_t size, | |
63 | bool res) | |
4562236b HW |
64 | { |
65 | DRM_DEBUG_KMS("Op: %s, addr: %04x, SideBand Msg: %s, Op res: %s\n", | |
66 | (type == DP_AUX_NATIVE_READ) || | |
67 | (type == DP_AUX_I2C_READ) ? | |
68 | "Read" : "Write", | |
69 | address, | |
70 | SIDE_BAND_MSG(address) ? | |
71 | side_band_msg_type_to_str(address) : "Nop", | |
72 | res ? "OK" : "Fail"); | |
73 | ||
74 | if (res) { | |
75 | print_hex_dump(KERN_INFO, "Body: ", DUMP_PREFIX_NONE, 16, 1, data, size, false); | |
76 | } | |
77 | } | |
78 | #endif | |
79 | ||
aaa6346d AD |
80 | static ssize_t dm_dp_aux_transfer(struct drm_dp_aux *aux, |
81 | struct drm_dp_aux_msg *msg) | |
4562236b | 82 | { |
aaa6346d AD |
83 | enum i2c_mot_mode mot = (msg->request & DP_AUX_I2C_MOT) ? |
84 | I2C_MOT_TRUE : I2C_MOT_FALSE; | |
46df790c | 85 | enum ddc_result res; |
4562236b | 86 | |
7c7f5b15 | 87 | switch (msg->request & ~DP_AUX_I2C_MOT) { |
4562236b | 88 | case DP_AUX_NATIVE_READ: |
46df790c AG |
89 | res = dal_ddc_service_read_dpcd_data( |
90 | TO_DM_AUX(aux)->ddc_service, | |
91 | false, | |
92 | I2C_MOT_UNDEF, | |
7c7f5b15 AG |
93 | msg->address, |
94 | msg->buffer, | |
95 | msg->size); | |
4562236b HW |
96 | break; |
97 | case DP_AUX_NATIVE_WRITE: | |
46df790c AG |
98 | res = dal_ddc_service_write_dpcd_data( |
99 | TO_DM_AUX(aux)->ddc_service, | |
100 | false, | |
101 | I2C_MOT_UNDEF, | |
7c7f5b15 AG |
102 | msg->address, |
103 | msg->buffer, | |
104 | msg->size); | |
105 | break; | |
106 | case DP_AUX_I2C_READ: | |
46df790c AG |
107 | res = dal_ddc_service_read_dpcd_data( |
108 | TO_DM_AUX(aux)->ddc_service, | |
109 | true, | |
7c7f5b15 AG |
110 | mot, |
111 | msg->address, | |
112 | msg->buffer, | |
113 | msg->size); | |
114 | break; | |
115 | case DP_AUX_I2C_WRITE: | |
46df790c AG |
116 | res = dal_ddc_service_write_dpcd_data( |
117 | TO_DM_AUX(aux)->ddc_service, | |
118 | true, | |
7c7f5b15 AG |
119 | mot, |
120 | msg->address, | |
121 | msg->buffer, | |
122 | msg->size); | |
4562236b HW |
123 | break; |
124 | default: | |
125 | return 0; | |
126 | } | |
127 | ||
128 | #ifdef TRACE_DPCD | |
129 | log_dpcd(msg->request, | |
aaa6346d AD |
130 | msg->address, |
131 | msg->buffer, | |
132 | msg->size, | |
133 | r == DDC_RESULT_SUCESSFULL); | |
4562236b HW |
134 | #endif |
135 | ||
136 | return msg->size; | |
137 | } | |
138 | ||
139 | static enum drm_connector_status | |
140 | dm_dp_mst_detect(struct drm_connector *connector, bool force) | |
141 | { | |
c84dec2f HW |
142 | struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); |
143 | struct amdgpu_dm_connector *master = aconnector->mst_port; | |
4562236b HW |
144 | |
145 | enum drm_connector_status status = | |
146 | drm_dp_mst_detect_port( | |
147 | connector, | |
148 | &master->mst_mgr, | |
149 | aconnector->port); | |
150 | ||
4562236b HW |
151 | return status; |
152 | } | |
153 | ||
154 | static void | |
155 | dm_dp_mst_connector_destroy(struct drm_connector *connector) | |
156 | { | |
c84dec2f HW |
157 | struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector); |
158 | struct amdgpu_encoder *amdgpu_encoder = amdgpu_dm_connector->mst_encoder; | |
4562236b HW |
159 | |
160 | drm_encoder_cleanup(&amdgpu_encoder->base); | |
161 | kfree(amdgpu_encoder); | |
162 | drm_connector_cleanup(connector); | |
c84dec2f | 163 | kfree(amdgpu_dm_connector); |
4562236b HW |
164 | } |
165 | ||
166 | static const struct drm_connector_funcs dm_dp_mst_connector_funcs = { | |
167 | .detect = dm_dp_mst_detect, | |
168 | .fill_modes = drm_helper_probe_single_connector_modes, | |
169 | .destroy = dm_dp_mst_connector_destroy, | |
170 | .reset = amdgpu_dm_connector_funcs_reset, | |
171 | .atomic_duplicate_state = amdgpu_dm_connector_atomic_duplicate_state, | |
172 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | |
80c62d3a AG |
173 | .atomic_set_property = amdgpu_dm_connector_atomic_set_property, |
174 | .atomic_get_property = amdgpu_dm_connector_atomic_get_property | |
4562236b HW |
175 | }; |
176 | ||
54427651 JZ |
177 | static int dm_connector_update_modes(struct drm_connector *connector, |
178 | struct edid *edid) | |
179 | { | |
180 | int ret; | |
181 | ||
182 | ret = drm_add_edid_modes(connector, edid); | |
183 | drm_edid_to_eld(connector, edid); | |
184 | ||
185 | return ret; | |
186 | } | |
187 | ||
becd0875 JFZ |
188 | void dm_dp_mst_dc_sink_create(struct drm_connector *connector) |
189 | { | |
190 | struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); | |
191 | struct edid *edid; | |
192 | struct dc_sink *dc_sink; | |
193 | struct dc_sink_init_data init_params = { | |
194 | .link = aconnector->dc_link, | |
195 | .sink_signal = SIGNAL_TYPE_DISPLAY_PORT_MST }; | |
196 | ||
197 | edid = drm_dp_mst_get_edid(connector, &aconnector->mst_port->mst_mgr, aconnector->port); | |
198 | ||
199 | if (!edid) { | |
200 | drm_mode_connector_update_edid_property( | |
201 | &aconnector->base, | |
202 | NULL); | |
203 | return; | |
204 | } | |
205 | ||
206 | aconnector->edid = edid; | |
207 | ||
208 | dc_sink = dc_link_add_remote_sink( | |
209 | aconnector->dc_link, | |
210 | (uint8_t *)aconnector->edid, | |
211 | (aconnector->edid->extensions + 1) * EDID_LENGTH, | |
212 | &init_params); | |
213 | ||
214 | dc_sink->priv = aconnector; | |
215 | aconnector->dc_sink = dc_sink; | |
216 | ||
217 | amdgpu_dm_add_sink_to_freesync_module( | |
218 | connector, aconnector->edid); | |
219 | ||
220 | drm_mode_connector_update_edid_property( | |
221 | &aconnector->base, aconnector->edid); | |
222 | } | |
223 | ||
4562236b HW |
224 | static int dm_dp_mst_get_modes(struct drm_connector *connector) |
225 | { | |
c84dec2f | 226 | struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); |
4562236b HW |
227 | int ret = 0; |
228 | ||
54427651 JZ |
229 | if (!aconnector) |
230 | return dm_connector_update_modes(connector, NULL); | |
231 | ||
232 | if (!aconnector->edid) { | |
233 | struct edid *edid; | |
234 | struct dc_sink *dc_sink; | |
235 | struct dc_sink_init_data init_params = { | |
236 | .link = aconnector->dc_link, | |
237 | .sink_signal = SIGNAL_TYPE_DISPLAY_PORT_MST }; | |
238 | edid = drm_dp_mst_get_edid(connector, &aconnector->mst_port->mst_mgr, aconnector->port); | |
239 | ||
240 | if (!edid) { | |
241 | drm_mode_connector_update_edid_property( | |
242 | &aconnector->base, | |
243 | NULL); | |
244 | return ret; | |
245 | } | |
246 | ||
247 | aconnector->edid = edid; | |
248 | ||
249 | dc_sink = dc_link_add_remote_sink( | |
250 | aconnector->dc_link, | |
251 | (uint8_t *)edid, | |
252 | (edid->extensions + 1) * EDID_LENGTH, | |
253 | &init_params); | |
254 | ||
255 | dc_sink->priv = aconnector; | |
256 | aconnector->dc_sink = dc_sink; | |
4562236b | 257 | |
54427651 JZ |
258 | if (aconnector->dc_sink) |
259 | amdgpu_dm_add_sink_to_freesync_module( | |
260 | connector, edid); | |
261 | ||
262 | drm_mode_connector_update_edid_property( | |
263 | &aconnector->base, edid); | |
264 | } | |
265 | ||
266 | ret = dm_connector_update_modes(connector, aconnector->edid); | |
4562236b HW |
267 | |
268 | return ret; | |
269 | } | |
270 | ||
271 | static struct drm_encoder *dm_mst_best_encoder(struct drm_connector *connector) | |
272 | { | |
c84dec2f | 273 | struct amdgpu_dm_connector *amdgpu_dm_connector = to_amdgpu_dm_connector(connector); |
4562236b | 274 | |
c84dec2f | 275 | return &amdgpu_dm_connector->mst_encoder->base; |
4562236b HW |
276 | } |
277 | ||
278 | static const struct drm_connector_helper_funcs dm_dp_mst_connector_helper_funcs = { | |
279 | .get_modes = dm_dp_mst_get_modes, | |
280 | .mode_valid = amdgpu_dm_connector_mode_valid, | |
281 | .best_encoder = dm_mst_best_encoder, | |
282 | }; | |
283 | ||
4fc6f659 JFZ |
284 | static void amdgpu_dm_encoder_destroy(struct drm_encoder *encoder) |
285 | { | |
286 | drm_encoder_cleanup(encoder); | |
287 | kfree(encoder); | |
288 | } | |
289 | ||
290 | static const struct drm_encoder_funcs amdgpu_dm_encoder_funcs = { | |
291 | .destroy = amdgpu_dm_encoder_destroy, | |
292 | }; | |
293 | ||
4562236b | 294 | static struct amdgpu_encoder * |
c84dec2f | 295 | dm_dp_create_fake_mst_encoder(struct amdgpu_dm_connector *connector) |
4562236b HW |
296 | { |
297 | struct drm_device *dev = connector->base.dev; | |
298 | struct amdgpu_device *adev = dev->dev_private; | |
299 | struct amdgpu_encoder *amdgpu_encoder; | |
300 | struct drm_encoder *encoder; | |
301 | const struct drm_connector_helper_funcs *connector_funcs = | |
302 | connector->base.helper_private; | |
303 | struct drm_encoder *enc_master = | |
304 | connector_funcs->best_encoder(&connector->base); | |
305 | ||
306 | DRM_DEBUG_KMS("enc master is %p\n", enc_master); | |
307 | amdgpu_encoder = kzalloc(sizeof(*amdgpu_encoder), GFP_KERNEL); | |
308 | if (!amdgpu_encoder) | |
309 | return NULL; | |
310 | ||
311 | encoder = &amdgpu_encoder->base; | |
312 | encoder->possible_crtcs = amdgpu_dm_get_encoder_crtc_mask(adev); | |
313 | ||
314 | drm_encoder_init( | |
315 | dev, | |
316 | &amdgpu_encoder->base, | |
4fc6f659 | 317 | &amdgpu_dm_encoder_funcs, |
4562236b HW |
318 | DRM_MODE_ENCODER_DPMST, |
319 | NULL); | |
320 | ||
321 | drm_encoder_helper_add(encoder, &amdgpu_dm_encoder_helper_funcs); | |
322 | ||
323 | return amdgpu_encoder; | |
324 | } | |
325 | ||
aaa6346d AD |
326 | static struct drm_connector * |
327 | dm_dp_add_mst_connector(struct drm_dp_mst_topology_mgr *mgr, | |
328 | struct drm_dp_mst_port *port, | |
329 | const char *pathprop) | |
4562236b | 330 | { |
c84dec2f | 331 | struct amdgpu_dm_connector *master = container_of(mgr, struct amdgpu_dm_connector, mst_mgr); |
4562236b HW |
332 | struct drm_device *dev = master->base.dev; |
333 | struct amdgpu_device *adev = dev->dev_private; | |
c84dec2f | 334 | struct amdgpu_dm_connector *aconnector; |
4562236b | 335 | struct drm_connector *connector; |
365c7c97 | 336 | struct drm_connector_list_iter conn_iter; |
4562236b | 337 | |
365c7c97 JZ |
338 | drm_connector_list_iter_begin(dev, &conn_iter); |
339 | drm_for_each_connector_iter(connector, &conn_iter) { | |
c84dec2f | 340 | aconnector = to_amdgpu_dm_connector(connector); |
4562236b HW |
341 | if (aconnector->mst_port == master |
342 | && !aconnector->port) { | |
343 | DRM_INFO("DM_MST: reusing connector: %p [id: %d] [master: %p]\n", | |
344 | aconnector, connector->base.id, aconnector->mst_port); | |
345 | ||
346 | aconnector->port = port; | |
347 | drm_mode_connector_set_path_property(connector, pathprop); | |
348 | ||
365c7c97 | 349 | drm_connector_list_iter_end(&conn_iter); |
becd0875 | 350 | aconnector->mst_connected = true; |
4562236b HW |
351 | return &aconnector->base; |
352 | } | |
353 | } | |
365c7c97 | 354 | drm_connector_list_iter_end(&conn_iter); |
4562236b HW |
355 | |
356 | aconnector = kzalloc(sizeof(*aconnector), GFP_KERNEL); | |
357 | if (!aconnector) | |
358 | return NULL; | |
359 | ||
360 | connector = &aconnector->base; | |
361 | aconnector->port = port; | |
362 | aconnector->mst_port = master; | |
363 | ||
364 | if (drm_connector_init( | |
365 | dev, | |
366 | connector, | |
367 | &dm_dp_mst_connector_funcs, | |
368 | DRM_MODE_CONNECTOR_DisplayPort)) { | |
369 | kfree(aconnector); | |
370 | return NULL; | |
371 | } | |
372 | drm_connector_helper_add(connector, &dm_dp_mst_connector_helper_funcs); | |
373 | ||
374 | amdgpu_dm_connector_init_helper( | |
375 | &adev->dm, | |
376 | aconnector, | |
377 | DRM_MODE_CONNECTOR_DisplayPort, | |
378 | master->dc_link, | |
379 | master->connector_id); | |
380 | ||
381 | aconnector->mst_encoder = dm_dp_create_fake_mst_encoder(master); | |
382 | ||
383 | /* | |
384 | * TODO: understand why this one is needed | |
385 | */ | |
386 | drm_object_attach_property( | |
387 | &connector->base, | |
388 | dev->mode_config.path_property, | |
389 | 0); | |
390 | drm_object_attach_property( | |
391 | &connector->base, | |
392 | dev->mode_config.tile_property, | |
393 | 0); | |
394 | ||
395 | drm_mode_connector_set_path_property(connector, pathprop); | |
396 | ||
397 | /* | |
398 | * Initialize connector state before adding the connectror to drm and | |
399 | * framebuffer lists | |
400 | */ | |
401 | amdgpu_dm_connector_funcs_reset(connector); | |
402 | ||
becd0875 JFZ |
403 | aconnector->mst_connected = true; |
404 | ||
4562236b HW |
405 | DRM_INFO("DM_MST: added connector: %p [id: %d] [master: %p]\n", |
406 | aconnector, connector->base.id, aconnector->mst_port); | |
407 | ||
408 | DRM_DEBUG_KMS(":%d\n", connector->base.id); | |
409 | ||
410 | return connector; | |
411 | } | |
412 | ||
aaa6346d AD |
413 | static void dm_dp_destroy_mst_connector(struct drm_dp_mst_topology_mgr *mgr, |
414 | struct drm_connector *connector) | |
4562236b | 415 | { |
c84dec2f | 416 | struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); |
4562236b HW |
417 | |
418 | DRM_INFO("DM_MST: Disabling connector: %p [id: %d] [master: %p]\n", | |
419 | aconnector, connector->base.id, aconnector->mst_port); | |
420 | ||
421 | aconnector->port = NULL; | |
422 | if (aconnector->dc_sink) { | |
423 | amdgpu_dm_remove_sink_from_freesync_module(connector); | |
424 | dc_link_remove_remote_sink(aconnector->dc_link, aconnector->dc_sink); | |
425 | dc_sink_release(aconnector->dc_sink); | |
426 | aconnector->dc_sink = NULL; | |
427 | } | |
428 | if (aconnector->edid) { | |
429 | kfree(aconnector->edid); | |
430 | aconnector->edid = NULL; | |
431 | } | |
432 | ||
433 | drm_mode_connector_update_edid_property( | |
434 | &aconnector->base, | |
435 | NULL); | |
becd0875 JFZ |
436 | |
437 | aconnector->mst_connected = false; | |
4562236b HW |
438 | } |
439 | ||
440 | static void dm_dp_mst_hotplug(struct drm_dp_mst_topology_mgr *mgr) | |
441 | { | |
c84dec2f | 442 | struct amdgpu_dm_connector *master = container_of(mgr, struct amdgpu_dm_connector, mst_mgr); |
4562236b | 443 | struct drm_device *dev = master->base.dev; |
4562236b | 444 | |
54427651 | 445 | drm_kms_helper_hotplug_event(dev); |
4562236b HW |
446 | } |
447 | ||
becd0875 JFZ |
448 | static void dm_dp_mst_link_status_reset(struct drm_connector *connector) |
449 | { | |
450 | mutex_lock(&connector->dev->mode_config.mutex); | |
451 | drm_mode_connector_set_link_status_property(connector, DRM_MODE_LINK_STATUS_BAD); | |
452 | mutex_unlock(&connector->dev->mode_config.mutex); | |
453 | } | |
454 | ||
4562236b HW |
455 | static void dm_dp_mst_register_connector(struct drm_connector *connector) |
456 | { | |
457 | struct drm_device *dev = connector->dev; | |
458 | struct amdgpu_device *adev = dev->dev_private; | |
becd0875 | 459 | struct amdgpu_dm_connector *aconnector = to_amdgpu_dm_connector(connector); |
4562236b | 460 | |
4bd29c34 | 461 | if (adev->mode_info.rfbdev) |
4562236b | 462 | drm_fb_helper_add_one_connector(&adev->mode_info.rfbdev->helper, connector); |
4562236b HW |
463 | else |
464 | DRM_ERROR("adev->mode_info.rfbdev is NULL\n"); | |
465 | ||
4562236b HW |
466 | drm_connector_register(connector); |
467 | ||
becd0875 JFZ |
468 | if (aconnector->mst_connected) |
469 | dm_dp_mst_link_status_reset(connector); | |
4562236b HW |
470 | } |
471 | ||
472 | static const struct drm_dp_mst_topology_cbs dm_mst_cbs = { | |
473 | .add_connector = dm_dp_add_mst_connector, | |
474 | .destroy_connector = dm_dp_destroy_mst_connector, | |
475 | .hotplug = dm_dp_mst_hotplug, | |
476 | .register_connector = dm_dp_mst_register_connector | |
477 | }; | |
478 | ||
aaa6346d AD |
479 | void amdgpu_dm_initialize_dp_connector(struct amdgpu_display_manager *dm, |
480 | struct amdgpu_dm_connector *aconnector) | |
4562236b HW |
481 | { |
482 | aconnector->dm_dp_aux.aux.name = "dmdc"; | |
483 | aconnector->dm_dp_aux.aux.dev = dm->adev->dev; | |
484 | aconnector->dm_dp_aux.aux.transfer = dm_dp_aux_transfer; | |
46df790c | 485 | aconnector->dm_dp_aux.ddc_service = aconnector->dc_link->ddc; |
4562236b HW |
486 | |
487 | drm_dp_aux_register(&aconnector->dm_dp_aux.aux); | |
488 | aconnector->mst_mgr.cbs = &dm_mst_cbs; | |
489 | drm_dp_mst_topology_mgr_init( | |
490 | &aconnector->mst_mgr, | |
491 | dm->adev->ddev, | |
492 | &aconnector->dm_dp_aux.aux, | |
493 | 16, | |
494 | 4, | |
495 | aconnector->connector_id); | |
496 | } | |
497 |