1 | /* |
2 | * Copyright 2022 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 | /* FILE POLICY AND INTENDED USAGE: |
27 | * This file implements DP HPD short pulse handling sequence according to DP |
28 | * specifications |
29 | * |
30 | */ |
31 | |
32 | #include "link_dp_irq_handler.h" |
33 | #include "link_dpcd.h" |
34 | #include "link_dp_training.h" |
35 | #include "link_dp_capability.h" |
36 | #include "link_edp_panel_control.h" |
37 | #include "link/accessories/link_dp_trace.h" |
38 | #include "link/link_dpms.h" |
39 | #include "dm_helpers.h" |
40 | |
41 | #define DC_LOGGER \ |
42 | link->ctx->logger |
43 | #define DC_LOGGER_INIT(logger) |
44 | |
45 | bool dp_parse_link_loss_status( |
46 | struct dc_link *link, |
47 | union hpd_irq_data *hpd_irq_dpcd_data) |
48 | { |
49 | uint8_t irq_reg_rx_power_state = 0; |
50 | enum dc_status dpcd_result = DC_ERROR_UNEXPECTED; |
51 | union lane_status lane_status; |
52 | uint32_t lane; |
53 | bool sink_status_changed; |
54 | bool return_code; |
55 | |
56 | sink_status_changed = false; |
57 | return_code = false; |
58 | |
59 | if (link->cur_link_settings.lane_count == 0) |
60 | return return_code; |
61 | |
62 | /*1. Check that Link Status changed, before re-training.*/ |
63 | |
64 | /*parse lane status*/ |
65 | for (lane = 0; lane < link->cur_link_settings.lane_count; lane++) { |
66 | /* check status of lanes 0,1 |
67 | * changed DpcdAddress_Lane01Status (0x202) |
68 | */ |
69 | lane_status.raw = dp_get_nibble_at_index( |
70 | buf: &hpd_irq_dpcd_data->bytes.lane01_status.raw, |
71 | index: lane); |
72 | |
73 | if (!lane_status.bits.CHANNEL_EQ_DONE_0 || |
74 | !lane_status.bits.CR_DONE_0 || |
75 | !lane_status.bits.SYMBOL_LOCKED_0) { |
76 | /* if one of the channel equalization, clock |
77 | * recovery or symbol lock is dropped |
78 | * consider it as (link has been |
79 | * dropped) dp sink status has changed |
80 | */ |
81 | sink_status_changed = true; |
82 | break; |
83 | } |
84 | } |
85 | |
86 | /* Check interlane align.*/ |
87 | if (link_dp_get_encoding_format(link_settings: &link->cur_link_settings) == DP_128b_132b_ENCODING && |
88 | (!hpd_irq_dpcd_data->bytes.lane_status_updated.bits.EQ_INTERLANE_ALIGN_DONE_128b_132b || |
89 | !hpd_irq_dpcd_data->bytes.lane_status_updated.bits.CDS_INTERLANE_ALIGN_DONE_128b_132b)) { |
90 | sink_status_changed = true; |
91 | } else if (!hpd_irq_dpcd_data->bytes.lane_status_updated.bits.INTERLANE_ALIGN_DONE) { |
92 | sink_status_changed = true; |
93 | } |
94 | |
95 | if (sink_status_changed) { |
96 | |
97 | DC_LOG_HW_HPD_IRQ("%s: Link Status changed.\n" , __func__); |
98 | |
99 | return_code = true; |
100 | |
101 | /*2. Check that we can handle interrupt: Not in FS DOS, |
102 | * Not in "Display Timeout" state, Link is trained. |
103 | */ |
104 | dpcd_result = core_link_read_dpcd(link, |
105 | DP_SET_POWER, |
106 | data: &irq_reg_rx_power_state, |
107 | size: sizeof(irq_reg_rx_power_state)); |
108 | |
109 | if (dpcd_result != DC_OK) { |
110 | DC_LOG_HW_HPD_IRQ("%s: DPCD read failed to obtain power state.\n" , |
111 | __func__); |
112 | } else { |
113 | if (irq_reg_rx_power_state != DP_SET_POWER_D0) |
114 | return_code = false; |
115 | } |
116 | } |
117 | |
118 | return return_code; |
119 | } |
120 | |
121 | static bool handle_hpd_irq_psr_sink(struct dc_link *link) |
122 | { |
123 | union dpcd_psr_configuration psr_configuration; |
124 | |
125 | if (!link->psr_settings.psr_feature_enabled) |
126 | return false; |
127 | |
128 | dm_helpers_dp_read_dpcd( |
129 | ctx: link->ctx, |
130 | link, |
131 | address: 368,/*DpcdAddress_PSR_Enable_Cfg*/ |
132 | data: &psr_configuration.raw, |
133 | size: sizeof(psr_configuration.raw)); |
134 | |
135 | if (psr_configuration.bits.ENABLE) { |
136 | unsigned char dpcdbuf[3] = {0}; |
137 | union psr_error_status psr_error_status; |
138 | union psr_sink_psr_status psr_sink_psr_status; |
139 | |
140 | dm_helpers_dp_read_dpcd( |
141 | ctx: link->ctx, |
142 | link, |
143 | address: 0x2006, /*DpcdAddress_PSR_Error_Status*/ |
144 | data: (unsigned char *) dpcdbuf, |
145 | size: sizeof(dpcdbuf)); |
146 | |
147 | /*DPCD 2006h ERROR STATUS*/ |
148 | psr_error_status.raw = dpcdbuf[0]; |
149 | /*DPCD 2008h SINK PANEL SELF REFRESH STATUS*/ |
150 | psr_sink_psr_status.raw = dpcdbuf[2]; |
151 | |
152 | if (psr_error_status.bits.LINK_CRC_ERROR || |
153 | psr_error_status.bits.RFB_STORAGE_ERROR || |
154 | psr_error_status.bits.VSC_SDP_ERROR) { |
155 | bool allow_active; |
156 | |
157 | /* Acknowledge and clear error bits */ |
158 | dm_helpers_dp_write_dpcd( |
159 | ctx: link->ctx, |
160 | link, |
161 | address: 8198,/*DpcdAddress_PSR_Error_Status*/ |
162 | data: &psr_error_status.raw, |
163 | size: sizeof(psr_error_status.raw)); |
164 | |
165 | /* PSR error, disable and re-enable PSR */ |
166 | if (link->psr_settings.psr_allow_active) { |
167 | allow_active = false; |
168 | edp_set_psr_allow_active(link, allow_active: &allow_active, wait: true, force_static: false, NULL); |
169 | allow_active = true; |
170 | edp_set_psr_allow_active(link, allow_active: &allow_active, wait: true, force_static: false, NULL); |
171 | } |
172 | |
173 | return true; |
174 | } else if (psr_sink_psr_status.bits.SINK_SELF_REFRESH_STATUS == |
175 | PSR_SINK_STATE_ACTIVE_DISPLAY_FROM_SINK_RFB){ |
176 | /* No error is detect, PSR is active. |
177 | * We should return with IRQ_HPD handled without |
178 | * checking for loss of sync since PSR would have |
179 | * powered down main link. |
180 | */ |
181 | return true; |
182 | } |
183 | } |
184 | return false; |
185 | } |
186 | |
187 | static void handle_hpd_irq_replay_sink(struct dc_link *link) |
188 | { |
189 | union dpcd_replay_configuration replay_configuration; |
190 | /*AMD Replay version reuse DP_PSR_ERROR_STATUS for REPLAY_ERROR status.*/ |
191 | union psr_error_status replay_error_status; |
192 | |
193 | if (!link->replay_settings.replay_feature_enabled) |
194 | return; |
195 | |
196 | dm_helpers_dp_read_dpcd( |
197 | ctx: link->ctx, |
198 | link, |
199 | DP_SINK_PR_REPLAY_STATUS, |
200 | data: &replay_configuration.raw, |
201 | size: sizeof(replay_configuration.raw)); |
202 | |
203 | dm_helpers_dp_read_dpcd( |
204 | ctx: link->ctx, |
205 | link, |
206 | DP_PSR_ERROR_STATUS, |
207 | data: &replay_error_status.raw, |
208 | size: sizeof(replay_error_status.raw)); |
209 | |
210 | link->replay_settings.config.replay_error_status.bits.LINK_CRC_ERROR = |
211 | replay_error_status.bits.LINK_CRC_ERROR; |
212 | link->replay_settings.config.replay_error_status.bits.DESYNC_ERROR = |
213 | replay_configuration.bits.DESYNC_ERROR_STATUS; |
214 | link->replay_settings.config.replay_error_status.bits.STATE_TRANSITION_ERROR = |
215 | replay_configuration.bits.STATE_TRANSITION_ERROR_STATUS; |
216 | |
217 | if (link->replay_settings.config.replay_error_status.bits.LINK_CRC_ERROR || |
218 | link->replay_settings.config.replay_error_status.bits.DESYNC_ERROR || |
219 | link->replay_settings.config.replay_error_status.bits.STATE_TRANSITION_ERROR) { |
220 | bool allow_active; |
221 | |
222 | if (link->replay_settings.config.replay_error_status.bits.DESYNC_ERROR) |
223 | link->replay_settings.config.received_desync_error_hpd = 1; |
224 | |
225 | if (link->replay_settings.config.force_disable_desync_error_check) |
226 | return; |
227 | |
228 | /* Acknowledge and clear configuration bits */ |
229 | dm_helpers_dp_write_dpcd( |
230 | ctx: link->ctx, |
231 | link, |
232 | DP_SINK_PR_REPLAY_STATUS, |
233 | data: &replay_configuration.raw, |
234 | size: sizeof(replay_configuration.raw)); |
235 | |
236 | /* Acknowledge and clear error bits */ |
237 | dm_helpers_dp_write_dpcd( |
238 | ctx: link->ctx, |
239 | link, |
240 | DP_PSR_ERROR_STATUS,/*DpcdAddress_REPLAY_Error_Status*/ |
241 | data: &replay_error_status.raw, |
242 | size: sizeof(replay_error_status.raw)); |
243 | |
244 | /* Replay error, disable and re-enable Replay */ |
245 | if (link->replay_settings.replay_allow_active) { |
246 | allow_active = false; |
247 | edp_set_replay_allow_active(dc_link: link, enable: &allow_active, wait: true, force_static: false, NULL); |
248 | allow_active = true; |
249 | edp_set_replay_allow_active(dc_link: link, enable: &allow_active, wait: true, force_static: false, NULL); |
250 | } |
251 | } |
252 | } |
253 | |
254 | void dp_handle_link_loss(struct dc_link *link) |
255 | { |
256 | struct pipe_ctx *pipes[MAX_PIPES]; |
257 | struct dc_state *state = link->dc->current_state; |
258 | uint8_t count; |
259 | int i; |
260 | |
261 | link_get_master_pipes_with_dpms_on(link, state, count: &count, pipes); |
262 | |
263 | for (i = 0; i < count; i++) |
264 | link_set_dpms_off(pipe_ctx: pipes[i]); |
265 | |
266 | for (i = count - 1; i >= 0; i--) { |
267 | // Always use max settings here for DP 1.4a LL Compliance CTS |
268 | if (link->skip_fallback_on_link_loss) { |
269 | pipes[i]->link_config.dp_link_settings.lane_count = |
270 | link->verified_link_cap.lane_count; |
271 | pipes[i]->link_config.dp_link_settings.link_rate = |
272 | link->verified_link_cap.link_rate; |
273 | pipes[i]->link_config.dp_link_settings.link_spread = |
274 | link->verified_link_cap.link_spread; |
275 | } |
276 | link_set_dpms_on(state: link->dc->current_state, pipe_ctx: pipes[i]); |
277 | } |
278 | } |
279 | |
280 | static void read_dpcd204h_on_irq_hpd(struct dc_link *link, union hpd_irq_data *irq_data) |
281 | { |
282 | enum dc_status retval; |
283 | union lane_align_status_updated dpcd_lane_status_updated; |
284 | |
285 | retval = core_link_read_dpcd( |
286 | link, |
287 | DP_LANE_ALIGN_STATUS_UPDATED, |
288 | data: &dpcd_lane_status_updated.raw, |
289 | size: sizeof(union lane_align_status_updated)); |
290 | |
291 | if (retval == DC_OK) { |
292 | irq_data->bytes.lane_status_updated.bits.EQ_INTERLANE_ALIGN_DONE_128b_132b = |
293 | dpcd_lane_status_updated.bits.EQ_INTERLANE_ALIGN_DONE_128b_132b; |
294 | irq_data->bytes.lane_status_updated.bits.CDS_INTERLANE_ALIGN_DONE_128b_132b = |
295 | dpcd_lane_status_updated.bits.CDS_INTERLANE_ALIGN_DONE_128b_132b; |
296 | } |
297 | } |
298 | |
299 | enum dc_status dp_read_hpd_rx_irq_data( |
300 | struct dc_link *link, |
301 | union hpd_irq_data *irq_data) |
302 | { |
303 | static enum dc_status retval; |
304 | |
305 | /* The HW reads 16 bytes from 200h on HPD, |
306 | * but if we get an AUX_DEFER, the HW cannot retry |
307 | * and this causes the CTS tests 4.3.2.1 - 3.2.4 to |
308 | * fail, so we now explicitly read 6 bytes which is |
309 | * the req from the above mentioned test cases. |
310 | * |
311 | * For DP 1.4 we need to read those from 2002h range. |
312 | */ |
313 | if (link->dpcd_caps.dpcd_rev.raw < DPCD_REV_14) |
314 | retval = core_link_read_dpcd( |
315 | link, |
316 | DP_SINK_COUNT, |
317 | data: irq_data->raw, |
318 | size: sizeof(union hpd_irq_data)); |
319 | else { |
320 | /* Read 14 bytes in a single read and then copy only the required fields. |
321 | * This is more efficient than doing it in two separate AUX reads. */ |
322 | |
323 | uint8_t tmp[DP_SINK_STATUS_ESI - DP_SINK_COUNT_ESI + 1]; |
324 | |
325 | retval = core_link_read_dpcd( |
326 | link, |
327 | DP_SINK_COUNT_ESI, |
328 | data: tmp, |
329 | size: sizeof(tmp)); |
330 | |
331 | if (retval != DC_OK) |
332 | return retval; |
333 | |
334 | irq_data->bytes.sink_cnt.raw = tmp[DP_SINK_COUNT_ESI - DP_SINK_COUNT_ESI]; |
335 | irq_data->bytes.device_service_irq.raw = tmp[DP_DEVICE_SERVICE_IRQ_VECTOR_ESI0 - DP_SINK_COUNT_ESI]; |
336 | irq_data->bytes.lane01_status.raw = tmp[DP_LANE0_1_STATUS_ESI - DP_SINK_COUNT_ESI]; |
337 | irq_data->bytes.lane23_status.raw = tmp[DP_LANE2_3_STATUS_ESI - DP_SINK_COUNT_ESI]; |
338 | irq_data->bytes.lane_status_updated.raw = tmp[DP_LANE_ALIGN_STATUS_UPDATED_ESI - DP_SINK_COUNT_ESI]; |
339 | irq_data->bytes.sink_status.raw = tmp[DP_SINK_STATUS_ESI - DP_SINK_COUNT_ESI]; |
340 | |
341 | /* |
342 | * This display doesn't have correct values in DPCD200Eh. |
343 | * Read and check DPCD204h instead. |
344 | */ |
345 | if (link->wa_flags.read_dpcd204h_on_irq_hpd) |
346 | read_dpcd204h_on_irq_hpd(link, irq_data); |
347 | } |
348 | |
349 | return retval; |
350 | } |
351 | |
352 | /*************************Short Pulse IRQ***************************/ |
353 | bool dp_should_allow_hpd_rx_irq(const struct dc_link *link) |
354 | { |
355 | /* |
356 | * Don't handle RX IRQ unless one of following is met: |
357 | * 1) The link is established (cur_link_settings != unknown) |
358 | * 2) We know we're dealing with a branch device, SST or MST |
359 | */ |
360 | |
361 | if ((link->cur_link_settings.lane_count != LANE_COUNT_UNKNOWN) || |
362 | is_dp_branch_device(link)) |
363 | return true; |
364 | |
365 | return false; |
366 | } |
367 | |
368 | bool dp_handle_hpd_rx_irq(struct dc_link *link, |
369 | union hpd_irq_data *out_hpd_irq_dpcd_data, bool *out_link_loss, |
370 | bool defer_handling, bool *has_left_work) |
371 | { |
372 | union hpd_irq_data hpd_irq_dpcd_data = {0}; |
373 | union device_service_irq device_service_clear = {0}; |
374 | enum dc_status result; |
375 | bool status = false; |
376 | |
377 | if (out_link_loss) |
378 | *out_link_loss = false; |
379 | |
380 | if (has_left_work) |
381 | *has_left_work = false; |
382 | /* For use cases related to down stream connection status change, |
383 | * PSR and device auto test, refer to function handle_sst_hpd_irq |
384 | * in DAL2.1*/ |
385 | |
386 | DC_LOG_HW_HPD_IRQ("%s: Got short pulse HPD on link %d\n" , |
387 | __func__, link->link_index); |
388 | |
389 | |
390 | /* All the "handle_hpd_irq_xxx()" methods |
391 | * should be called only after |
392 | * dal_dpsst_ls_read_hpd_irq_data |
393 | * Order of calls is important too |
394 | */ |
395 | result = dp_read_hpd_rx_irq_data(link, irq_data: &hpd_irq_dpcd_data); |
396 | if (out_hpd_irq_dpcd_data) |
397 | *out_hpd_irq_dpcd_data = hpd_irq_dpcd_data; |
398 | |
399 | if (result != DC_OK) { |
400 | DC_LOG_HW_HPD_IRQ("%s: DPCD read failed to obtain irq data\n" , |
401 | __func__); |
402 | return false; |
403 | } |
404 | |
405 | if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.AUTOMATED_TEST) { |
406 | // Workaround for DP 1.4a LL Compliance CTS as USB4 has to share encoders unlike DP and USBC |
407 | if (link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA) |
408 | link->skip_fallback_on_link_loss = true; |
409 | |
410 | device_service_clear.bits.AUTOMATED_TEST = 1; |
411 | core_link_write_dpcd( |
412 | link, |
413 | DP_DEVICE_SERVICE_IRQ_VECTOR, |
414 | data: &device_service_clear.raw, |
415 | size: sizeof(device_service_clear.raw)); |
416 | device_service_clear.raw = 0; |
417 | if (defer_handling && has_left_work) |
418 | *has_left_work = true; |
419 | else |
420 | dc_link_dp_handle_automated_test(link); |
421 | return false; |
422 | } |
423 | |
424 | if (!dp_should_allow_hpd_rx_irq(link)) { |
425 | DC_LOG_HW_HPD_IRQ("%s: skipping HPD handling on %d\n" , |
426 | __func__, link->link_index); |
427 | return false; |
428 | } |
429 | |
430 | if (handle_hpd_irq_psr_sink(link)) |
431 | /* PSR-related error was detected and handled */ |
432 | return true; |
433 | |
434 | handle_hpd_irq_replay_sink(link); |
435 | |
436 | /* If PSR-related error handled, Main link may be off, |
437 | * so do not handle as a normal sink status change interrupt. |
438 | */ |
439 | |
440 | if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY) { |
441 | if (defer_handling && has_left_work) |
442 | *has_left_work = true; |
443 | return true; |
444 | } |
445 | |
446 | /* check if we have MST msg and return since we poll for it */ |
447 | if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) { |
448 | if (defer_handling && has_left_work) |
449 | *has_left_work = true; |
450 | return false; |
451 | } |
452 | |
453 | /* For now we only handle 'Downstream port status' case. |
454 | * If we got sink count changed it means |
455 | * Downstream port status changed, |
456 | * then DM should call DC to do the detection. |
457 | * NOTE: Do not handle link loss on eDP since it is internal link*/ |
458 | if ((link->connector_signal != SIGNAL_TYPE_EDP) && |
459 | dp_parse_link_loss_status( |
460 | link, |
461 | hpd_irq_dpcd_data: &hpd_irq_dpcd_data)) { |
462 | /* Connectivity log: link loss */ |
463 | CONN_DATA_LINK_LOSS(link, |
464 | hpd_irq_dpcd_data.raw, |
465 | sizeof(hpd_irq_dpcd_data), |
466 | "Status: " ); |
467 | |
468 | if (defer_handling && has_left_work) |
469 | *has_left_work = true; |
470 | else |
471 | dp_handle_link_loss(link); |
472 | |
473 | status = false; |
474 | if (out_link_loss) |
475 | *out_link_loss = true; |
476 | |
477 | dp_trace_link_loss_increment(link); |
478 | } |
479 | |
480 | if (link->type == dc_connection_sst_branch && |
481 | hpd_irq_dpcd_data.bytes.sink_cnt.bits.SINK_COUNT |
482 | != link->dpcd_sink_count) |
483 | status = true; |
484 | |
485 | /* reasons for HPD RX: |
486 | * 1. Link Loss - ie Re-train the Link |
487 | * 2. MST sideband message |
488 | * 3. Automated Test - ie. Internal Commit |
489 | * 4. CP (copy protection) - (not interesting for DM???) |
490 | * 5. DRR |
491 | * 6. Downstream Port status changed |
492 | * -ie. Detect - this the only one |
493 | * which is interesting for DM because |
494 | * it must call dc_link_detect. |
495 | */ |
496 | return status; |
497 | } |
498 | |