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/accessories/link_dp_trace.h"
37 #include "link/link_dpms.h"
38 #include "dm_helpers.h"
39 
40 #define DC_LOGGER_INIT(logger)
41 
dc_link_check_link_loss_status(struct dc_link * link,union hpd_irq_data * hpd_irq_dpcd_data)42 bool dc_link_check_link_loss_status(
43 	struct dc_link *link,
44 	union hpd_irq_data *hpd_irq_dpcd_data)
45 {
46 	uint8_t irq_reg_rx_power_state = 0;
47 	enum dc_status dpcd_result = DC_ERROR_UNEXPECTED;
48 	union lane_status lane_status;
49 	uint32_t lane;
50 	bool sink_status_changed;
51 	bool return_code;
52 
53 	sink_status_changed = false;
54 	return_code = false;
55 
56 	if (link->cur_link_settings.lane_count == 0)
57 		return return_code;
58 
59 	/*1. Check that Link Status changed, before re-training.*/
60 
61 	/*parse lane status*/
62 	for (lane = 0; lane < link->cur_link_settings.lane_count; lane++) {
63 		/* check status of lanes 0,1
64 		 * changed DpcdAddress_Lane01Status (0x202)
65 		 */
66 		lane_status.raw = dp_get_nibble_at_index(
67 			&hpd_irq_dpcd_data->bytes.lane01_status.raw,
68 			lane);
69 
70 		if (!lane_status.bits.CHANNEL_EQ_DONE_0 ||
71 			!lane_status.bits.CR_DONE_0 ||
72 			!lane_status.bits.SYMBOL_LOCKED_0) {
73 			/* if one of the channel equalization, clock
74 			 * recovery or symbol lock is dropped
75 			 * consider it as (link has been
76 			 * dropped) dp sink status has changed
77 			 */
78 			sink_status_changed = true;
79 			break;
80 		}
81 	}
82 
83 	/* Check interlane align.*/
84 	if (sink_status_changed ||
85 		!hpd_irq_dpcd_data->bytes.lane_status_updated.bits.INTERLANE_ALIGN_DONE) {
86 
87 		DC_LOG_HW_HPD_IRQ("%s: Link Status changed.\n", __func__);
88 
89 		return_code = true;
90 
91 		/*2. Check that we can handle interrupt: Not in FS DOS,
92 		 *  Not in "Display Timeout" state, Link is trained.
93 		 */
94 		dpcd_result = core_link_read_dpcd(link,
95 			DP_SET_POWER,
96 			&irq_reg_rx_power_state,
97 			sizeof(irq_reg_rx_power_state));
98 
99 		if (dpcd_result != DC_OK) {
100 			DC_LOG_HW_HPD_IRQ("%s: DPCD read failed to obtain power state.\n",
101 				__func__);
102 		} else {
103 			if (irq_reg_rx_power_state != DP_SET_POWER_D0)
104 				return_code = false;
105 		}
106 	}
107 
108 	return return_code;
109 }
110 
handle_hpd_irq_psr_sink(struct dc_link * link)111 static bool handle_hpd_irq_psr_sink(struct dc_link *link)
112 {
113 	union dpcd_psr_configuration psr_configuration;
114 
115 	if (!link->psr_settings.psr_feature_enabled)
116 		return false;
117 
118 	dm_helpers_dp_read_dpcd(
119 		link->ctx,
120 		link,
121 		368,/*DpcdAddress_PSR_Enable_Cfg*/
122 		&psr_configuration.raw,
123 		sizeof(psr_configuration.raw));
124 
125 	if (psr_configuration.bits.ENABLE) {
126 		unsigned char dpcdbuf[3] = {0};
127 		union psr_error_status psr_error_status;
128 		union psr_sink_psr_status psr_sink_psr_status;
129 
130 		dm_helpers_dp_read_dpcd(
131 			link->ctx,
132 			link,
133 			0x2006, /*DpcdAddress_PSR_Error_Status*/
134 			(unsigned char *) dpcdbuf,
135 			sizeof(dpcdbuf));
136 
137 		/*DPCD 2006h   ERROR STATUS*/
138 		psr_error_status.raw = dpcdbuf[0];
139 		/*DPCD 2008h   SINK PANEL SELF REFRESH STATUS*/
140 		psr_sink_psr_status.raw = dpcdbuf[2];
141 
142 		if (psr_error_status.bits.LINK_CRC_ERROR ||
143 				psr_error_status.bits.RFB_STORAGE_ERROR ||
144 				psr_error_status.bits.VSC_SDP_ERROR) {
145 			bool allow_active;
146 
147 			/* Acknowledge and clear error bits */
148 			dm_helpers_dp_write_dpcd(
149 				link->ctx,
150 				link,
151 				8198,/*DpcdAddress_PSR_Error_Status*/
152 				&psr_error_status.raw,
153 				sizeof(psr_error_status.raw));
154 
155 			/* PSR error, disable and re-enable PSR */
156 			if (link->psr_settings.psr_allow_active) {
157 				allow_active = false;
158 				dc_link_set_psr_allow_active(link, &allow_active, true, false, NULL);
159 				allow_active = true;
160 				dc_link_set_psr_allow_active(link, &allow_active, true, false, NULL);
161 			}
162 
163 			return true;
164 		} else if (psr_sink_psr_status.bits.SINK_SELF_REFRESH_STATUS ==
165 				PSR_SINK_STATE_ACTIVE_DISPLAY_FROM_SINK_RFB){
166 			/* No error is detect, PSR is active.
167 			 * We should return with IRQ_HPD handled without
168 			 * checking for loss of sync since PSR would have
169 			 * powered down main link.
170 			 */
171 			return true;
172 		}
173 	}
174 	return false;
175 }
176 
dc_link_dp_handle_link_loss(struct dc_link * link)177 void dc_link_dp_handle_link_loss(struct dc_link *link)
178 {
179 	struct pipe_ctx *pipes[MAX_PIPES];
180 	struct dc_state *state = link->dc->current_state;
181 	uint8_t count;
182 	int i;
183 
184 	link_get_master_pipes_with_dpms_on(link, state, &count, pipes);
185 
186 	for (i = 0; i < count; i++)
187 		link_set_dpms_off(pipes[i]);
188 
189 	for (i = count - 1; i >= 0; i--) {
190 		// Always use max settings here for DP 1.4a LL Compliance CTS
191 		if (link->is_automated) {
192 			pipes[i]->link_config.dp_link_settings.lane_count =
193 					link->verified_link_cap.lane_count;
194 			pipes[i]->link_config.dp_link_settings.link_rate =
195 					link->verified_link_cap.link_rate;
196 			pipes[i]->link_config.dp_link_settings.link_spread =
197 					link->verified_link_cap.link_spread;
198 		}
199 		link_set_dpms_on(link->dc->current_state, pipes[i]);
200 	}
201 }
202 
dc_link_dp_read_hpd_rx_irq_data(struct dc_link * link,union hpd_irq_data * irq_data)203 enum dc_status dc_link_dp_read_hpd_rx_irq_data(
204 	struct dc_link *link,
205 	union hpd_irq_data *irq_data)
206 {
207 	static enum dc_status retval;
208 
209 	/* The HW reads 16 bytes from 200h on HPD,
210 	 * but if we get an AUX_DEFER, the HW cannot retry
211 	 * and this causes the CTS tests 4.3.2.1 - 3.2.4 to
212 	 * fail, so we now explicitly read 6 bytes which is
213 	 * the req from the above mentioned test cases.
214 	 *
215 	 * For DP 1.4 we need to read those from 2002h range.
216 	 */
217 	if (link->dpcd_caps.dpcd_rev.raw < DPCD_REV_14)
218 		retval = core_link_read_dpcd(
219 			link,
220 			DP_SINK_COUNT,
221 			irq_data->raw,
222 			sizeof(union hpd_irq_data));
223 	else {
224 		/* Read 14 bytes in a single read and then copy only the required fields.
225 		 * This is more efficient than doing it in two separate AUX reads. */
226 
227 		uint8_t tmp[DP_SINK_STATUS_ESI - DP_SINK_COUNT_ESI + 1];
228 
229 		retval = core_link_read_dpcd(
230 			link,
231 			DP_SINK_COUNT_ESI,
232 			tmp,
233 			sizeof(tmp));
234 
235 		if (retval != DC_OK)
236 			return retval;
237 
238 		irq_data->bytes.sink_cnt.raw = tmp[DP_SINK_COUNT_ESI - DP_SINK_COUNT_ESI];
239 		irq_data->bytes.device_service_irq.raw = tmp[DP_DEVICE_SERVICE_IRQ_VECTOR_ESI0 - DP_SINK_COUNT_ESI];
240 		irq_data->bytes.lane01_status.raw = tmp[DP_LANE0_1_STATUS_ESI - DP_SINK_COUNT_ESI];
241 		irq_data->bytes.lane23_status.raw = tmp[DP_LANE2_3_STATUS_ESI - DP_SINK_COUNT_ESI];
242 		irq_data->bytes.lane_status_updated.raw = tmp[DP_LANE_ALIGN_STATUS_UPDATED_ESI - DP_SINK_COUNT_ESI];
243 		irq_data->bytes.sink_status.raw = tmp[DP_SINK_STATUS_ESI - DP_SINK_COUNT_ESI];
244 	}
245 
246 	return retval;
247 }
248 
249 /*************************Short Pulse IRQ***************************/
dc_link_dp_allow_hpd_rx_irq(const struct dc_link * link)250 bool dc_link_dp_allow_hpd_rx_irq(const struct dc_link *link)
251 {
252 	/*
253 	 * Don't handle RX IRQ unless one of following is met:
254 	 * 1) The link is established (cur_link_settings != unknown)
255 	 * 2) We know we're dealing with a branch device, SST or MST
256 	 */
257 
258 	if ((link->cur_link_settings.lane_count != LANE_COUNT_UNKNOWN) ||
259 		is_dp_branch_device(link))
260 		return true;
261 
262 	return false;
263 }
264 
dc_link_handle_hpd_rx_irq(struct dc_link * link,union hpd_irq_data * out_hpd_irq_dpcd_data,bool * out_link_loss,bool defer_handling,bool * has_left_work)265 bool dc_link_handle_hpd_rx_irq(struct dc_link *link, union hpd_irq_data *out_hpd_irq_dpcd_data, bool *out_link_loss,
266 							bool defer_handling, bool *has_left_work)
267 {
268 	union hpd_irq_data hpd_irq_dpcd_data = {0};
269 	union device_service_irq device_service_clear = {0};
270 	enum dc_status result;
271 	bool status = false;
272 
273 	if (out_link_loss)
274 		*out_link_loss = false;
275 
276 	if (has_left_work)
277 		*has_left_work = false;
278 	/* For use cases related to down stream connection status change,
279 	 * PSR and device auto test, refer to function handle_sst_hpd_irq
280 	 * in DAL2.1*/
281 
282 	DC_LOG_HW_HPD_IRQ("%s: Got short pulse HPD on link %d\n",
283 		__func__, link->link_index);
284 
285 
286 	 /* All the "handle_hpd_irq_xxx()" methods
287 		 * should be called only after
288 		 * dal_dpsst_ls_read_hpd_irq_data
289 		 * Order of calls is important too
290 		 */
291 	result = dc_link_dp_read_hpd_rx_irq_data(link, &hpd_irq_dpcd_data);
292 	if (out_hpd_irq_dpcd_data)
293 		*out_hpd_irq_dpcd_data = hpd_irq_dpcd_data;
294 
295 	if (result != DC_OK) {
296 		DC_LOG_HW_HPD_IRQ("%s: DPCD read failed to obtain irq data\n",
297 			__func__);
298 		return false;
299 	}
300 
301 	if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.AUTOMATED_TEST) {
302 		// Workaround for DP 1.4a LL Compliance CTS as USB4 has to share encoders unlike DP and USBC
303 		link->is_automated = true;
304 		device_service_clear.bits.AUTOMATED_TEST = 1;
305 		core_link_write_dpcd(
306 			link,
307 			DP_DEVICE_SERVICE_IRQ_VECTOR,
308 			&device_service_clear.raw,
309 			sizeof(device_service_clear.raw));
310 		device_service_clear.raw = 0;
311 		if (defer_handling && has_left_work)
312 			*has_left_work = true;
313 		else
314 			dc_link_dp_handle_automated_test(link);
315 		return false;
316 	}
317 
318 	if (!dc_link_dp_allow_hpd_rx_irq(link)) {
319 		DC_LOG_HW_HPD_IRQ("%s: skipping HPD handling on %d\n",
320 			__func__, link->link_index);
321 		return false;
322 	}
323 
324 	if (handle_hpd_irq_psr_sink(link))
325 		/* PSR-related error was detected and handled */
326 		return true;
327 
328 	/* If PSR-related error handled, Main link may be off,
329 	 * so do not handle as a normal sink status change interrupt.
330 	 */
331 
332 	if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.UP_REQ_MSG_RDY) {
333 		if (defer_handling && has_left_work)
334 			*has_left_work = true;
335 		return true;
336 	}
337 
338 	/* check if we have MST msg and return since we poll for it */
339 	if (hpd_irq_dpcd_data.bytes.device_service_irq.bits.DOWN_REP_MSG_RDY) {
340 		if (defer_handling && has_left_work)
341 			*has_left_work = true;
342 		return false;
343 	}
344 
345 	/* For now we only handle 'Downstream port status' case.
346 	 * If we got sink count changed it means
347 	 * Downstream port status changed,
348 	 * then DM should call DC to do the detection.
349 	 * NOTE: Do not handle link loss on eDP since it is internal link*/
350 	if ((link->connector_signal != SIGNAL_TYPE_EDP) &&
351 		dc_link_check_link_loss_status(
352 			link,
353 			&hpd_irq_dpcd_data)) {
354 		/* Connectivity log: link loss */
355 		CONN_DATA_LINK_LOSS(link,
356 					hpd_irq_dpcd_data.raw,
357 					sizeof(hpd_irq_dpcd_data),
358 					"Status: ");
359 
360 		if (defer_handling && has_left_work)
361 			*has_left_work = true;
362 		else
363 			dc_link_dp_handle_link_loss(link);
364 
365 		status = false;
366 		if (out_link_loss)
367 			*out_link_loss = true;
368 
369 		dp_trace_link_loss_increment(link);
370 	}
371 
372 	if (link->type == dc_connection_sst_branch &&
373 		hpd_irq_dpcd_data.bytes.sink_cnt.bits.SINK_COUNT
374 			!= link->dpcd_sink_count)
375 		status = true;
376 
377 	/* reasons for HPD RX:
378 	 * 1. Link Loss - ie Re-train the Link
379 	 * 2. MST sideband message
380 	 * 3. Automated Test - ie. Internal Commit
381 	 * 4. CP (copy protection) - (not interesting for DM???)
382 	 * 5. DRR
383 	 * 6. Downstream Port status changed
384 	 * -ie. Detect - this the only one
385 	 * which is interesting for DM because
386 	 * it must call dc_link_detect.
387 	 */
388 	return status;
389 }
390