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 module implements functionality for training DPIA links.
28 */
29#include "link_dp_training_dpia.h"
30#include "dc.h"
31#include "inc/core_status.h"
32#include "dpcd_defs.h"
33
34#include "link_dp_dpia.h"
35#include "link_hwss.h"
36#include "dm_helpers.h"
37#include "dmub/inc/dmub_cmd.h"
38#include "link_dpcd.h"
39#include "link_dp_phy.h"
40#include "link_dp_training_8b_10b.h"
41#include "link_dp_capability.h"
42#include "dc_dmub_srv.h"
43#define DC_LOGGER \
44	link->ctx->logger
45
46/* The approximate time (us) it takes to transmit 9 USB4 DP clock sync packets. */
47#define DPIA_CLK_SYNC_DELAY 16000
48
49/* Extend interval between training status checks for manual testing. */
50#define DPIA_DEBUG_EXTENDED_AUX_RD_INTERVAL_US 60000000
51
52#define TRAINING_AUX_RD_INTERVAL 100 //us
53
54/* SET_CONFIG message types sent by driver. */
55enum dpia_set_config_type {
56	DPIA_SET_CFG_SET_LINK = 0x01,
57	DPIA_SET_CFG_SET_PHY_TEST_MODE = 0x05,
58	DPIA_SET_CFG_SET_TRAINING = 0x18,
59	DPIA_SET_CFG_SET_VSPE = 0x19
60};
61
62/* Training stages (TS) in SET_CONFIG(SET_TRAINING) message. */
63enum dpia_set_config_ts {
64	DPIA_TS_DPRX_DONE = 0x00, /* Done training DPRX. */
65	DPIA_TS_TPS1 = 0x01,
66	DPIA_TS_TPS2 = 0x02,
67	DPIA_TS_TPS3 = 0x03,
68	DPIA_TS_TPS4 = 0x07,
69	DPIA_TS_UFP_DONE = 0xff /* Done training DPTX-to-DPIA hop. */
70};
71
72/* SET_CONFIG message data associated with messages sent by driver. */
73union dpia_set_config_data {
74	struct {
75		uint8_t mode : 1;
76		uint8_t reserved : 7;
77	} set_link;
78	struct {
79		uint8_t stage;
80	} set_training;
81	struct {
82		uint8_t swing : 2;
83		uint8_t max_swing_reached : 1;
84		uint8_t pre_emph : 2;
85		uint8_t max_pre_emph_reached : 1;
86		uint8_t reserved : 2;
87	} set_vspe;
88	uint8_t raw;
89};
90
91
92/* Configure link as prescribed in link_setting; set LTTPR mode; and
93 * Initialize link training settings.
94 * Abort link training if sink unplug detected.
95 *
96 * @param link DPIA link being trained.
97 * @param[in] link_setting Lane count, link rate and downspread control.
98 * @param[out] lt_settings Link settings and drive settings (voltage swing and pre-emphasis).
99 */
100static enum link_training_result dpia_configure_link(
101		struct dc_link *link,
102		const struct link_resource *link_res,
103		const struct dc_link_settings *link_setting,
104		struct link_training_settings *lt_settings)
105{
106	enum dc_status status;
107	bool fec_enable;
108
109	DC_LOG_HW_LINK_TRAINING("%s\n DPIA(%d) configuring\n - LTTPR mode(%d)\n",
110		__func__,
111		link->link_id.enum_id - ENUM_ID_1,
112		lt_settings->lttpr_mode);
113
114	dp_decide_training_settings(
115		link,
116		link_setting,
117		lt_settings);
118
119	dp_get_lttpr_mode_override(link, &lt_settings->lttpr_mode);
120
121	status = dpcd_configure_channel_coding(link, lt_settings);
122	if (status != DC_OK && link->is_hpd_pending)
123		return LINK_TRAINING_ABORT;
124
125	/* Configure lttpr mode */
126	status = dpcd_configure_lttpr_mode(link, lt_settings);
127	if (status != DC_OK && link->is_hpd_pending)
128		return LINK_TRAINING_ABORT;
129
130	/* Set link rate, lane count and spread. */
131	status = dpcd_set_link_settings(link, lt_settings);
132	if (status != DC_OK && link->is_hpd_pending)
133		return LINK_TRAINING_ABORT;
134
135	if (link->preferred_training_settings.fec_enable != NULL)
136		fec_enable = *link->preferred_training_settings.fec_enable;
137	else
138		fec_enable = true;
139	status = dp_set_fec_ready(link, link_res, fec_enable);
140	if (status != DC_OK && link->is_hpd_pending)
141		return LINK_TRAINING_ABORT;
142
143	return LINK_TRAINING_SUCCESS;
144}
145
146static enum dc_status core_link_send_set_config(
147	struct dc_link *link,
148	uint8_t msg_type,
149	uint8_t msg_data)
150{
151	struct set_config_cmd_payload payload;
152	enum set_config_status set_config_result = SET_CONFIG_PENDING;
153
154	/* prepare set_config payload */
155	payload.msg_type = msg_type;
156	payload.msg_data = msg_data;
157
158	if (!link->ddc->ddc_pin && !link->aux_access_disabled &&
159			(dm_helpers_dmub_set_config_sync(link->ctx,
160			link, &payload, &set_config_result) == -1)) {
161		return DC_ERROR_UNEXPECTED;
162	}
163
164	/* set_config should return ACK if successful */
165	return (set_config_result == SET_CONFIG_ACK_RECEIVED) ? DC_OK : DC_ERROR_UNEXPECTED;
166}
167
168/* Build SET_CONFIG message data payload for specified message type. */
169static uint8_t dpia_build_set_config_data(
170		enum dpia_set_config_type type,
171		struct dc_link *link,
172		struct link_training_settings *lt_settings)
173{
174	union dpia_set_config_data data;
175
176	data.raw = 0;
177
178	switch (type) {
179	case DPIA_SET_CFG_SET_LINK:
180		data.set_link.mode = lt_settings->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT ? 1 : 0;
181		break;
182	case DPIA_SET_CFG_SET_PHY_TEST_MODE:
183		break;
184	case DPIA_SET_CFG_SET_VSPE:
185		/* Assume all lanes have same drive settings. */
186		data.set_vspe.swing = lt_settings->hw_lane_settings[0].VOLTAGE_SWING;
187		data.set_vspe.pre_emph = lt_settings->hw_lane_settings[0].PRE_EMPHASIS;
188		data.set_vspe.max_swing_reached =
189				lt_settings->hw_lane_settings[0].VOLTAGE_SWING == VOLTAGE_SWING_MAX_LEVEL ? 1 : 0;
190		data.set_vspe.max_pre_emph_reached =
191				lt_settings->hw_lane_settings[0].PRE_EMPHASIS == PRE_EMPHASIS_MAX_LEVEL ? 1 : 0;
192		break;
193	default:
194		ASSERT(false); /* Message type not supported by helper function. */
195		break;
196	}
197
198	return data.raw;
199}
200
201/* Convert DC training pattern to DPIA training stage. */
202static enum dc_status convert_trng_ptn_to_trng_stg(enum dc_dp_training_pattern tps, enum dpia_set_config_ts *ts)
203{
204	enum dc_status status = DC_OK;
205
206	switch (tps) {
207	case DP_TRAINING_PATTERN_SEQUENCE_1:
208		*ts = DPIA_TS_TPS1;
209		break;
210	case DP_TRAINING_PATTERN_SEQUENCE_2:
211		*ts = DPIA_TS_TPS2;
212		break;
213	case DP_TRAINING_PATTERN_SEQUENCE_3:
214		*ts = DPIA_TS_TPS3;
215		break;
216	case DP_TRAINING_PATTERN_SEQUENCE_4:
217		*ts = DPIA_TS_TPS4;
218		break;
219	case DP_TRAINING_PATTERN_VIDEOIDLE:
220		*ts = DPIA_TS_DPRX_DONE;
221		break;
222	default: /* TPS not supported by helper function. */
223		ASSERT(false);
224		*ts = DPIA_TS_DPRX_DONE;
225		status = DC_UNSUPPORTED_VALUE;
226		break;
227	}
228
229	return status;
230}
231
232/* Write training pattern to DPCD. */
233static enum dc_status dpcd_set_lt_pattern(
234	struct dc_link *link,
235	enum dc_dp_training_pattern pattern,
236	uint32_t hop)
237{
238	union dpcd_training_pattern dpcd_pattern = {0};
239	uint32_t dpcd_tps_offset = DP_TRAINING_PATTERN_SET;
240	enum dc_status status;
241
242	if (hop != DPRX)
243		dpcd_tps_offset = DP_TRAINING_PATTERN_SET_PHY_REPEATER1 +
244			((DP_REPEATER_CONFIGURATION_AND_STATUS_SIZE) * (hop - 1));
245
246	/* DpcdAddress_TrainingPatternSet */
247	dpcd_pattern.v1_4.TRAINING_PATTERN_SET =
248		dp_training_pattern_to_dpcd_training_pattern(link, pattern);
249
250	dpcd_pattern.v1_4.SCRAMBLING_DISABLE =
251		dp_initialize_scrambling_data_symbols(link, pattern);
252
253	if (hop != DPRX) {
254		DC_LOG_HW_LINK_TRAINING("%s\n LTTPR Repeater ID: %d\n 0x%X pattern = %x\n",
255			__func__,
256			hop,
257			dpcd_tps_offset,
258			dpcd_pattern.v1_4.TRAINING_PATTERN_SET);
259	} else {
260		DC_LOG_HW_LINK_TRAINING("%s\n 0x%X pattern = %x\n",
261			__func__,
262			dpcd_tps_offset,
263			dpcd_pattern.v1_4.TRAINING_PATTERN_SET);
264	}
265
266	status = core_link_write_dpcd(
267			link,
268			dpcd_tps_offset,
269			&dpcd_pattern.raw,
270			sizeof(dpcd_pattern.raw));
271
272	return status;
273}
274
275/* Execute clock recovery phase of link training for specified hop in display
276 * path.in non-transparent mode:
277 * - Driver issues both DPCD and SET_CONFIG transactions.
278 * - TPS1 is transmitted for any hops downstream of DPOA.
279 * - Drive (VS/PE) only transmitted for the hop immediately downstream of DPOA.
280 * - CR for the first hop (DPTX-to-DPIA) is assumed to be successful.
281 *
282 * @param link DPIA link being trained.
283 * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis).
284 * @param hop Hop in display path. DPRX = 0.
285 */
286static enum link_training_result dpia_training_cr_non_transparent(
287		struct dc_link *link,
288		const struct link_resource *link_res,
289		struct link_training_settings *lt_settings,
290		uint32_t hop)
291{
292	enum link_training_result result = LINK_TRAINING_CR_FAIL_LANE0;
293	uint8_t repeater_cnt = 0; /* Number of hops/repeaters in display path. */
294	enum dc_status status = DC_ERROR_UNEXPECTED;
295	uint32_t retries_cr = 0; /* Number of consecutive attempts with same VS or PE. */
296	uint32_t retry_count = 0;
297	uint32_t wait_time_microsec = TRAINING_AUX_RD_INTERVAL; /* From DP spec, CR read interval is always 100us. */
298	enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
299	union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = {0};
300	union lane_align_status_updated dpcd_lane_status_updated = {0};
301	union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0};
302	uint8_t set_cfg_data;
303	enum dpia_set_config_ts ts;
304
305	repeater_cnt = dp_parse_lttpr_repeater_count(link->dpcd_caps.lttpr_caps.phy_repeater_cnt);
306
307	/* Cap of LINK_TRAINING_MAX_CR_RETRY attempts at clock recovery.
308	 * Fix inherited from perform_clock_recovery_sequence() -
309	 * the DP equivalent of this function:
310	 * Required for Synaptics MST hub which can put the LT in
311	 * infinite loop by switching the VS between level 0 and level 1
312	 * continuously.
313	 */
314	while ((retries_cr < LINK_TRAINING_MAX_RETRY_COUNT) &&
315			(retry_count < LINK_TRAINING_MAX_CR_RETRY)) {
316
317		/* DPTX-to-DPIA */
318		if (hop == repeater_cnt) {
319			/* Send SET_CONFIG(SET_LINK:LC,LR,LTTPR) to notify DPOA that
320			 * non-transparent link training has started.
321			 * This also enables the transmission of clk_sync packets.
322			 */
323			set_cfg_data = dpia_build_set_config_data(
324					DPIA_SET_CFG_SET_LINK,
325					link,
326					lt_settings);
327			status = core_link_send_set_config(
328					link,
329					DPIA_SET_CFG_SET_LINK,
330					set_cfg_data);
331			/* CR for this hop is considered successful as long as
332			 * SET_CONFIG message is acknowledged by DPOA.
333			 */
334			if (status == DC_OK)
335				result = LINK_TRAINING_SUCCESS;
336			else
337				result = LINK_TRAINING_ABORT;
338			break;
339		}
340
341		/* DPOA-to-x */
342		/* Instruct DPOA to transmit TPS1 then update DPCD. */
343		if (retry_count == 0) {
344			status = convert_trng_ptn_to_trng_stg(lt_settings->pattern_for_cr, &ts);
345			if (status != DC_OK) {
346				result = LINK_TRAINING_ABORT;
347				break;
348			}
349			status = core_link_send_set_config(
350					link,
351					DPIA_SET_CFG_SET_TRAINING,
352					ts);
353			if (status != DC_OK) {
354				result = LINK_TRAINING_ABORT;
355				break;
356			}
357			status = dpcd_set_lt_pattern(link, lt_settings->pattern_for_cr, hop);
358			if (status != DC_OK) {
359				result = LINK_TRAINING_ABORT;
360				break;
361			}
362		}
363
364		/* Update DPOA drive settings then DPCD. DPOA does only adjusts
365		 * drive settings for hops immediately downstream.
366		 */
367		if (hop == repeater_cnt - 1) {
368			set_cfg_data = dpia_build_set_config_data(
369					DPIA_SET_CFG_SET_VSPE,
370					link,
371					lt_settings);
372			status = core_link_send_set_config(
373					link,
374					DPIA_SET_CFG_SET_VSPE,
375					set_cfg_data);
376			if (status != DC_OK) {
377				result = LINK_TRAINING_ABORT;
378				break;
379			}
380		}
381		status = dpcd_set_lane_settings(link, lt_settings, hop);
382		if (status != DC_OK) {
383			result = LINK_TRAINING_ABORT;
384			break;
385		}
386
387		dp_wait_for_training_aux_rd_interval(link, wait_time_microsec);
388
389		/* Read status and adjustment requests from DPCD. */
390		status = dp_get_lane_status_and_lane_adjust(
391				link,
392				lt_settings,
393				dpcd_lane_status,
394				&dpcd_lane_status_updated,
395				dpcd_lane_adjust,
396				hop);
397		if (status != DC_OK) {
398			result = LINK_TRAINING_ABORT;
399			break;
400		}
401
402		/* Check if clock recovery successful. */
403		if (dp_is_cr_done(lane_count, dpcd_lane_status)) {
404			DC_LOG_HW_LINK_TRAINING("%s: Clock recovery OK\n", __func__);
405			result = LINK_TRAINING_SUCCESS;
406			break;
407		}
408
409		result = dp_get_cr_failure(lane_count, dpcd_lane_status);
410
411		if (dp_is_max_vs_reached(lt_settings))
412			break;
413
414		/* Count number of attempts with same drive settings.
415		 * Note: settings are the same for all lanes,
416		 * so comparing first lane is sufficient.
417		 */
418		if ((lt_settings->dpcd_lane_settings[0].bits.VOLTAGE_SWING_SET ==
419				dpcd_lane_adjust[0].bits.VOLTAGE_SWING_LANE)
420				&& (lt_settings->dpcd_lane_settings[0].bits.PRE_EMPHASIS_SET ==
421						dpcd_lane_adjust[0].bits.PRE_EMPHASIS_LANE))
422			retries_cr++;
423		else
424			retries_cr = 0;
425
426		/* Update VS/PE. */
427		dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
428				lt_settings->hw_lane_settings,
429				lt_settings->dpcd_lane_settings);
430		retry_count++;
431	}
432
433	/* Abort link training if clock recovery failed due to HPD unplug. */
434	if (link->is_hpd_pending)
435		result = LINK_TRAINING_ABORT;
436
437	DC_LOG_HW_LINK_TRAINING(
438		"%s\n DPIA(%d) clock recovery\n -hop(%d)\n - result(%d)\n - retries(%d)\n - status(%d)\n",
439		__func__,
440		link->link_id.enum_id - ENUM_ID_1,
441		hop,
442		result,
443		retry_count,
444		status);
445
446	return result;
447}
448
449/* Execute clock recovery phase of link training in transparent LTTPR mode:
450 * - Driver only issues DPCD transactions and leaves USB4 tunneling (SET_CONFIG) messages to DPIA.
451 * - Driver writes TPS1 to DPCD to kick off training.
452 * - Clock recovery (CR) for link is handled by DPOA, which reports result to DPIA on completion.
453 * - DPIA communicates result to driver by updating CR status when driver reads DPCD.
454 *
455 * @param link DPIA link being trained.
456 * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis).
457 */
458static enum link_training_result dpia_training_cr_transparent(
459		struct dc_link *link,
460		const struct link_resource *link_res,
461		struct link_training_settings *lt_settings)
462{
463	enum link_training_result result = LINK_TRAINING_CR_FAIL_LANE0;
464	enum dc_status status;
465	uint32_t retries_cr = 0; /* Number of consecutive attempts with same VS or PE. */
466	uint32_t retry_count = 0;
467	uint32_t wait_time_microsec = lt_settings->cr_pattern_time;
468	enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
469	union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = {0};
470	union lane_align_status_updated dpcd_lane_status_updated = {0};
471	union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0};
472
473	/* Cap of LINK_TRAINING_MAX_CR_RETRY attempts at clock recovery.
474	 * Fix inherited from perform_clock_recovery_sequence() -
475	 * the DP equivalent of this function:
476	 * Required for Synaptics MST hub which can put the LT in
477	 * infinite loop by switching the VS between level 0 and level 1
478	 * continuously.
479	 */
480	while ((retries_cr < LINK_TRAINING_MAX_RETRY_COUNT) &&
481			(retry_count < LINK_TRAINING_MAX_CR_RETRY)) {
482
483		/* Write TPS1 (not VS or PE) to DPCD to start CR phase.
484		 * DPIA sends SET_CONFIG(SET_LINK) to notify DPOA to
485		 * start link training.
486		 */
487		if (retry_count == 0) {
488			status = dpcd_set_lt_pattern(link, lt_settings->pattern_for_cr, DPRX);
489			if (status != DC_OK) {
490				result = LINK_TRAINING_ABORT;
491				break;
492			}
493		}
494
495		dp_wait_for_training_aux_rd_interval(link, wait_time_microsec);
496
497		/* Read status and adjustment requests from DPCD. */
498		status = dp_get_lane_status_and_lane_adjust(
499				link,
500				lt_settings,
501				dpcd_lane_status,
502				&dpcd_lane_status_updated,
503				dpcd_lane_adjust,
504				DPRX);
505		if (status != DC_OK) {
506			result = LINK_TRAINING_ABORT;
507			break;
508		}
509
510		/* Check if clock recovery successful. */
511		if (dp_is_cr_done(lane_count, dpcd_lane_status)) {
512			DC_LOG_HW_LINK_TRAINING("%s: Clock recovery OK\n", __func__);
513			result = LINK_TRAINING_SUCCESS;
514			break;
515		}
516
517		result = dp_get_cr_failure(lane_count, dpcd_lane_status);
518
519		if (dp_is_max_vs_reached(lt_settings))
520			break;
521
522		/* Count number of attempts with same drive settings.
523		 * Note: settings are the same for all lanes,
524		 * so comparing first lane is sufficient.
525		 */
526		if ((lt_settings->dpcd_lane_settings[0].bits.VOLTAGE_SWING_SET ==
527				dpcd_lane_adjust[0].bits.VOLTAGE_SWING_LANE)
528				&& (lt_settings->dpcd_lane_settings[0].bits.PRE_EMPHASIS_SET ==
529						dpcd_lane_adjust[0].bits.PRE_EMPHASIS_LANE))
530			retries_cr++;
531		else
532			retries_cr = 0;
533
534		/* Update VS/PE. */
535		dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
536				lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
537		retry_count++;
538	}
539
540	/* Abort link training if clock recovery failed due to HPD unplug. */
541	if (link->is_hpd_pending)
542		result = LINK_TRAINING_ABORT;
543
544	DC_LOG_HW_LINK_TRAINING("%s\n DPIA(%d) clock recovery\n -hop(%d)\n - result(%d)\n - retries(%d)\n",
545		__func__,
546		link->link_id.enum_id - ENUM_ID_1,
547		DPRX,
548		result,
549		retry_count);
550
551	return result;
552}
553
554/* Execute clock recovery phase of link training for specified hop in display
555 * path.
556 *
557 * @param link DPIA link being trained.
558 * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis).
559 * @param hop Hop in display path. DPRX = 0.
560 */
561static enum link_training_result dpia_training_cr_phase(
562		struct dc_link *link,
563		const struct link_resource *link_res,
564		struct link_training_settings *lt_settings,
565		uint32_t hop)
566{
567	enum link_training_result result = LINK_TRAINING_CR_FAIL_LANE0;
568
569	if (lt_settings->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT)
570		result = dpia_training_cr_non_transparent(link, link_res, lt_settings, hop);
571	else
572		result = dpia_training_cr_transparent(link, link_res, lt_settings);
573
574	return result;
575}
576
577/* Return status read interval during equalization phase. */
578static uint32_t dpia_get_eq_aux_rd_interval(
579		const struct dc_link *link,
580		const struct link_training_settings *lt_settings,
581		uint32_t hop)
582{
583	uint32_t wait_time_microsec;
584
585	if (hop == DPRX)
586		wait_time_microsec = lt_settings->eq_pattern_time;
587	else
588		wait_time_microsec =
589				dp_translate_training_aux_read_interval(
590					link->dpcd_caps.lttpr_caps.aux_rd_interval[hop - 1]);
591
592	/* Check debug option for extending aux read interval. */
593	if (link->dc->debug.dpia_debug.bits.extend_aux_rd_interval)
594		wait_time_microsec = DPIA_DEBUG_EXTENDED_AUX_RD_INTERVAL_US;
595
596	return wait_time_microsec;
597}
598
599/* Execute equalization phase of link training for specified hop in display
600 * path in non-transparent mode:
601 * - driver issues both DPCD and SET_CONFIG transactions.
602 * - TPSx is transmitted for any hops downstream of DPOA.
603 * - Drive (VS/PE) only transmitted for the hop immediately downstream of DPOA.
604 * - EQ for the first hop (DPTX-to-DPIA) is assumed to be successful.
605 * - DPRX EQ only reported successful when both DPRX and DPIA requirements (clk sync packets sent) fulfilled.
606 *
607 * @param link DPIA link being trained.
608 * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis).
609 * @param hop Hop in display path. DPRX = 0.
610 */
611static enum link_training_result dpia_training_eq_non_transparent(
612		struct dc_link *link,
613		const struct link_resource *link_res,
614		struct link_training_settings *lt_settings,
615		uint32_t hop)
616{
617	enum link_training_result result = LINK_TRAINING_EQ_FAIL_EQ;
618	uint8_t repeater_cnt = 0; /* Number of hops/repeaters in display path. */
619	uint32_t retries_eq = 0;
620	enum dc_status status = DC_ERROR_UNEXPECTED;
621	enum dc_dp_training_pattern tr_pattern;
622	uint32_t wait_time_microsec = 0;
623	enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
624	union lane_align_status_updated dpcd_lane_status_updated = {0};
625	union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = {0};
626	union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0};
627	uint8_t set_cfg_data;
628	enum dpia_set_config_ts ts;
629
630	/* Training pattern is TPS4 for repeater;
631	 * TPS2/3/4 for DPRX depending on what it supports.
632	 */
633	if (hop == DPRX)
634		tr_pattern = lt_settings->pattern_for_eq;
635	else
636		tr_pattern = DP_TRAINING_PATTERN_SEQUENCE_4;
637
638	repeater_cnt = dp_parse_lttpr_repeater_count(link->dpcd_caps.lttpr_caps.phy_repeater_cnt);
639
640	for (retries_eq = 0; retries_eq < LINK_TRAINING_MAX_RETRY_COUNT; retries_eq++) {
641
642		/* DPTX-to-DPIA equalization always successful. */
643		if (hop == repeater_cnt) {
644			result = LINK_TRAINING_SUCCESS;
645			break;
646		}
647
648		/* Instruct DPOA to transmit TPSn then update DPCD. */
649		if (retries_eq == 0) {
650			status = convert_trng_ptn_to_trng_stg(tr_pattern, &ts);
651			if (status != DC_OK) {
652				result = LINK_TRAINING_ABORT;
653				break;
654			}
655			status = core_link_send_set_config(
656					link,
657					DPIA_SET_CFG_SET_TRAINING,
658					ts);
659			if (status != DC_OK) {
660				result = LINK_TRAINING_ABORT;
661				break;
662			}
663			status = dpcd_set_lt_pattern(link, tr_pattern, hop);
664			if (status != DC_OK) {
665				result = LINK_TRAINING_ABORT;
666				break;
667			}
668		}
669
670		/* Update DPOA drive settings then DPCD. DPOA only adjusts
671		 * drive settings for hop immediately downstream.
672		 */
673		if (hop == repeater_cnt - 1) {
674			set_cfg_data = dpia_build_set_config_data(
675					DPIA_SET_CFG_SET_VSPE,
676					link,
677					lt_settings);
678			status = core_link_send_set_config(
679					link,
680					DPIA_SET_CFG_SET_VSPE,
681					set_cfg_data);
682			if (status != DC_OK) {
683				result = LINK_TRAINING_ABORT;
684				break;
685			}
686		}
687		status = dpcd_set_lane_settings(link, lt_settings, hop);
688		if (status != DC_OK) {
689			result = LINK_TRAINING_ABORT;
690			break;
691		}
692
693		/* Extend wait time on second equalisation attempt on final hop to
694		 * ensure clock sync packets have been sent.
695		 */
696		if (hop == DPRX && retries_eq == 1)
697			wait_time_microsec = max(wait_time_microsec, (uint32_t) DPIA_CLK_SYNC_DELAY);
698		else
699			wait_time_microsec = dpia_get_eq_aux_rd_interval(link, lt_settings, hop);
700
701		dp_wait_for_training_aux_rd_interval(link, wait_time_microsec);
702
703		/* Read status and adjustment requests from DPCD. */
704		status = dp_get_lane_status_and_lane_adjust(
705				link,
706				lt_settings,
707				dpcd_lane_status,
708				&dpcd_lane_status_updated,
709				dpcd_lane_adjust,
710				hop);
711		if (status != DC_OK) {
712			result = LINK_TRAINING_ABORT;
713			break;
714		}
715
716		/* CR can still fail during EQ phase. Fail training if CR fails. */
717		if (!dp_is_cr_done(lane_count, dpcd_lane_status)) {
718			result = LINK_TRAINING_EQ_FAIL_CR;
719			break;
720		}
721
722		if (dp_is_ch_eq_done(lane_count, dpcd_lane_status) &&
723				dp_is_symbol_locked(link->cur_link_settings.lane_count, dpcd_lane_status) &&
724				dp_is_interlane_aligned(dpcd_lane_status_updated)) {
725			result =  LINK_TRAINING_SUCCESS;
726			break;
727		}
728
729		/* Update VS/PE. */
730		dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
731				lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
732	}
733
734	/* Abort link training if equalization failed due to HPD unplug. */
735	if (link->is_hpd_pending)
736		result = LINK_TRAINING_ABORT;
737
738	DC_LOG_HW_LINK_TRAINING(
739		"%s\n DPIA(%d) equalization\n - hop(%d)\n - result(%d)\n - retries(%d)\n - status(%d)\n",
740		__func__,
741		link->link_id.enum_id - ENUM_ID_1,
742		hop,
743		result,
744		retries_eq,
745		status);
746
747	return result;
748}
749
750/* Execute equalization phase of link training for specified hop in display
751 * path in transparent LTTPR mode:
752 * - driver only issues DPCD transactions leaves USB4 tunneling (SET_CONFIG) messages to DPIA.
753 * - driver writes TPSx to DPCD to notify DPIA that is in equalization phase.
754 * - equalization (EQ) for link is handled by DPOA, which reports result to DPIA on completion.
755 * - DPIA communicates result to driver by updating EQ status when driver reads DPCD.
756 *
757 * @param link DPIA link being trained.
758 * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis).
759 * @param hop Hop in display path. DPRX = 0.
760 */
761static enum link_training_result dpia_training_eq_transparent(
762		struct dc_link *link,
763		const struct link_resource *link_res,
764		struct link_training_settings *lt_settings)
765{
766	enum link_training_result result = LINK_TRAINING_EQ_FAIL_EQ;
767	uint32_t retries_eq = 0;
768	enum dc_status status;
769	enum dc_dp_training_pattern tr_pattern = lt_settings->pattern_for_eq;
770	uint32_t wait_time_microsec;
771	enum dc_lane_count lane_count = lt_settings->link_settings.lane_count;
772	union lane_align_status_updated dpcd_lane_status_updated = {0};
773	union lane_status dpcd_lane_status[LANE_COUNT_DP_MAX] = {0};
774	union lane_adjust dpcd_lane_adjust[LANE_COUNT_DP_MAX] = {0};
775
776	wait_time_microsec = dpia_get_eq_aux_rd_interval(link, lt_settings, DPRX);
777
778	for (retries_eq = 0; retries_eq < LINK_TRAINING_MAX_RETRY_COUNT; retries_eq++) {
779
780		if (retries_eq == 0) {
781			status = dpcd_set_lt_pattern(link, tr_pattern, DPRX);
782			if (status != DC_OK) {
783				result = LINK_TRAINING_ABORT;
784				break;
785			}
786		}
787
788		dp_wait_for_training_aux_rd_interval(link, wait_time_microsec);
789
790		/* Read status and adjustment requests from DPCD. */
791		status = dp_get_lane_status_and_lane_adjust(
792				link,
793				lt_settings,
794				dpcd_lane_status,
795				&dpcd_lane_status_updated,
796				dpcd_lane_adjust,
797				DPRX);
798		if (status != DC_OK) {
799			result = LINK_TRAINING_ABORT;
800			break;
801		}
802
803		/* CR can still fail during EQ phase. Fail training if CR fails. */
804		if (!dp_is_cr_done(lane_count, dpcd_lane_status)) {
805			result = LINK_TRAINING_EQ_FAIL_CR;
806			break;
807		}
808
809		if (dp_is_ch_eq_done(lane_count, dpcd_lane_status) &&
810				dp_is_symbol_locked(link->cur_link_settings.lane_count, dpcd_lane_status)) {
811			/* Take into consideration corner case for DP 1.4a LL Compliance CTS as USB4
812			 * has to share encoders unlike DP and USBC
813			 */
814			if (dp_is_interlane_aligned(dpcd_lane_status_updated) || (link->skip_fallback_on_link_loss && retries_eq)) {
815				result =  LINK_TRAINING_SUCCESS;
816				break;
817			}
818		}
819
820		/* Update VS/PE. */
821		dp_decide_lane_settings(lt_settings, dpcd_lane_adjust,
822				lt_settings->hw_lane_settings, lt_settings->dpcd_lane_settings);
823	}
824
825	/* Abort link training if equalization failed due to HPD unplug. */
826	if (link->is_hpd_pending)
827		result = LINK_TRAINING_ABORT;
828
829	DC_LOG_HW_LINK_TRAINING("%s\n DPIA(%d) equalization\n - hop(%d)\n - result(%d)\n - retries(%d)\n",
830		__func__,
831		link->link_id.enum_id - ENUM_ID_1,
832		DPRX,
833		result,
834		retries_eq);
835
836	return result;
837}
838
839/* Execute equalization phase of link training for specified hop in display
840 * path.
841 *
842 * @param link DPIA link being trained.
843 * @param lt_settings link_setting and drive settings (voltage swing and pre-emphasis).
844 * @param hop Hop in display path. DPRX = 0.
845 */
846static enum link_training_result dpia_training_eq_phase(
847		struct dc_link *link,
848		const struct link_resource *link_res,
849		struct link_training_settings *lt_settings,
850		uint32_t hop)
851{
852	enum link_training_result result;
853
854	if (lt_settings->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT)
855		result = dpia_training_eq_non_transparent(link, link_res, lt_settings, hop);
856	else
857		result = dpia_training_eq_transparent(link, link_res, lt_settings);
858
859	return result;
860}
861
862/* End training of specified hop in display path. */
863static enum dc_status dpcd_clear_lt_pattern(
864	struct dc_link *link,
865	uint32_t hop)
866{
867	union dpcd_training_pattern dpcd_pattern = {0};
868	uint32_t dpcd_tps_offset = DP_TRAINING_PATTERN_SET;
869	enum dc_status status;
870
871	if (hop != DPRX)
872		dpcd_tps_offset = DP_TRAINING_PATTERN_SET_PHY_REPEATER1 +
873			((DP_REPEATER_CONFIGURATION_AND_STATUS_SIZE) * (hop - 1));
874
875	status = core_link_write_dpcd(
876			link,
877			dpcd_tps_offset,
878			&dpcd_pattern.raw,
879			sizeof(dpcd_pattern.raw));
880
881	return status;
882}
883
884/* End training of specified hop in display path.
885 *
886 * In transparent LTTPR mode:
887 * - driver clears training pattern for the specified hop in DPCD.
888 * In non-transparent LTTPR mode:
889 * - in addition to clearing training pattern, driver issues USB4 tunneling
890 * (SET_CONFIG) messages to notify DPOA when training is done for first hop
891 * (DPTX-to-DPIA) and last hop (DPRX).
892 *
893 * @param link DPIA link being trained.
894 * @param hop Hop in display path. DPRX = 0.
895 */
896static enum link_training_result dpia_training_end(
897		struct dc_link *link,
898		struct link_training_settings *lt_settings,
899		uint32_t hop)
900{
901	enum link_training_result result = LINK_TRAINING_SUCCESS;
902	uint8_t repeater_cnt = 0; /* Number of hops/repeaters in display path. */
903	enum dc_status status;
904
905	if (lt_settings->lttpr_mode == LTTPR_MODE_NON_TRANSPARENT) {
906
907		repeater_cnt = dp_parse_lttpr_repeater_count(link->dpcd_caps.lttpr_caps.phy_repeater_cnt);
908
909		if (hop == repeater_cnt) { /* DPTX-to-DPIA */
910			/* Send SET_CONFIG(SET_TRAINING:0xff) to notify DPOA that
911			 * DPTX-to-DPIA hop trained. No DPCD write needed for first hop.
912			 */
913			status = core_link_send_set_config(
914					link,
915					DPIA_SET_CFG_SET_TRAINING,
916					DPIA_TS_UFP_DONE);
917			if (status != DC_OK)
918				result = LINK_TRAINING_ABORT;
919		} else { /* DPOA-to-x */
920			/* Write 0x0 to TRAINING_PATTERN_SET */
921			status = dpcd_clear_lt_pattern(link, hop);
922			if (status != DC_OK)
923				result = LINK_TRAINING_ABORT;
924		}
925
926		/* Notify DPOA that non-transparent link training of DPRX done. */
927		if (hop == DPRX && result != LINK_TRAINING_ABORT) {
928			status = core_link_send_set_config(
929					link,
930					DPIA_SET_CFG_SET_TRAINING,
931					DPIA_TS_DPRX_DONE);
932			if (status != DC_OK)
933				result = LINK_TRAINING_ABORT;
934		}
935
936	} else { /* non-LTTPR or transparent LTTPR. */
937
938		/* Write 0x0 to TRAINING_PATTERN_SET */
939		status = dpcd_clear_lt_pattern(link, hop);
940		if (status != DC_OK)
941			result = LINK_TRAINING_ABORT;
942
943	}
944
945	DC_LOG_HW_LINK_TRAINING("%s\n DPIA(%d) end\n - hop(%d)\n - result(%d)\n - LTTPR mode(%d)\n",
946		__func__,
947		link->link_id.enum_id - ENUM_ID_1,
948		hop,
949		result,
950		lt_settings->lttpr_mode);
951
952	return result;
953}
954
955/* When aborting training of specified hop in display path, clean up by:
956 * - Attempting to clear DPCD TRAINING_PATTERN_SET, LINK_BW_SET and LANE_COUNT_SET.
957 * - Sending SET_CONFIG(SET_LINK) with lane count and link rate set to 0.
958 *
959 * @param link DPIA link being trained.
960 * @param hop Hop in display path. DPRX = 0.
961 */
962static void dpia_training_abort(
963		struct dc_link *link,
964		struct link_training_settings *lt_settings,
965		uint32_t hop)
966{
967	uint8_t data = 0;
968	uint32_t dpcd_tps_offset = DP_TRAINING_PATTERN_SET;
969
970	DC_LOG_HW_LINK_TRAINING("%s\n DPIA(%d) aborting\n - LTTPR mode(%d)\n - HPD(%d)\n",
971		__func__,
972		link->link_id.enum_id - ENUM_ID_1,
973		lt_settings->lttpr_mode,
974		link->is_hpd_pending);
975
976	/* Abandon clean-up if sink unplugged. */
977	if (link->is_hpd_pending)
978		return;
979
980	if (hop != DPRX)
981		dpcd_tps_offset = DP_TRAINING_PATTERN_SET_PHY_REPEATER1 +
982			((DP_REPEATER_CONFIGURATION_AND_STATUS_SIZE) * (hop - 1));
983
984	core_link_write_dpcd(link, dpcd_tps_offset, &data, 1);
985	core_link_write_dpcd(link, DP_LINK_BW_SET, &data, 1);
986	core_link_write_dpcd(link, DP_LANE_COUNT_SET, &data, 1);
987	core_link_send_set_config(link, DPIA_SET_CFG_SET_LINK, data);
988}
989
990enum link_training_result dpia_perform_link_training(
991	struct dc_link *link,
992	const struct link_resource *link_res,
993	const struct dc_link_settings *link_setting,
994	bool skip_video_pattern)
995{
996	enum link_training_result result;
997	struct link_training_settings lt_settings = {0};
998	uint8_t repeater_cnt = 0; /* Number of hops/repeaters in display path. */
999	int8_t repeater_id; /* Current hop. */
1000
1001	struct dc_link_settings link_settings = *link_setting; // non-const copy to pass in
1002
1003	lt_settings.lttpr_mode = dp_decide_lttpr_mode(link, &link_settings);
1004
1005	/* Configure link as prescribed in link_setting and set LTTPR mode. */
1006	result = dpia_configure_link(link, link_res, link_setting, &lt_settings);
1007	if (result != LINK_TRAINING_SUCCESS)
1008		return result;
1009
1010	if (lt_settings.lttpr_mode == LTTPR_MODE_NON_TRANSPARENT)
1011		repeater_cnt = dp_parse_lttpr_repeater_count(link->dpcd_caps.lttpr_caps.phy_repeater_cnt);
1012
1013	/* Train each hop in turn starting with the one closest to DPTX.
1014	 * In transparent or non-LTTPR mode, train only the final hop (DPRX).
1015	 */
1016	for (repeater_id = repeater_cnt; repeater_id >= 0; repeater_id--) {
1017		/* Clock recovery. */
1018		result = dpia_training_cr_phase(link, link_res, &lt_settings, repeater_id);
1019		if (result != LINK_TRAINING_SUCCESS)
1020			break;
1021
1022		/* Equalization. */
1023		result = dpia_training_eq_phase(link, link_res, &lt_settings, repeater_id);
1024		if (result != LINK_TRAINING_SUCCESS)
1025			break;
1026
1027		/* Stop training hop. */
1028		result = dpia_training_end(link, &lt_settings, repeater_id);
1029		if (result != LINK_TRAINING_SUCCESS)
1030			break;
1031	}
1032
1033	/* Double-check link status if training successful; gracefully abort
1034	 * training of current hop if training failed due to message tunneling
1035	 * failure; end training of hop if training ended conventionally and
1036	 * falling back to lower bandwidth settings possible.
1037	 */
1038	if (result == LINK_TRAINING_SUCCESS) {
1039		fsleep(5000);
1040		if (!link->skip_fallback_on_link_loss)
1041			result = dp_check_link_loss_status(link, &lt_settings);
1042	} else if (result == LINK_TRAINING_ABORT)
1043		dpia_training_abort(link, &lt_settings, repeater_id);
1044	else
1045		dpia_training_end(link, &lt_settings, repeater_id);
1046
1047	return result;
1048}
1049