1 
2 /*
3  * Copyright 2022 Advanced Micro Devices, Inc.
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
19  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
20  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
21  * OTHER DEALINGS IN THE SOFTWARE.
22  *
23  * Authors: AMD
24  *
25  */
26 /*********************************************************************/
27 //				USB4 DPIA BANDWIDTH ALLOCATION LOGIC
28 /*********************************************************************/
29 #include "dc.h"
30 #include "dc_link.h"
31 #include "link_dp_dpia_bw.h"
32 #include "drm_dp_helper_dc.h"
33 #include "link_dpcd.h"
34 
35 #define Kbps_TO_Gbps (1000 * 1000)
36 
37 // ------------------------------------------------------------------
38 //					PRIVATE FUNCTIONS
39 // ------------------------------------------------------------------
40 /*
41  * Always Check the following:
42  *  - Is it USB4 link?
43  *  - Is HPD HIGH?
44  *  - Is BW Allocation Support Mode enabled on DP-Tx?
45  */
get_bw_alloc_proceed_flag(struct dc_link * tmp)46 static bool get_bw_alloc_proceed_flag(struct dc_link *tmp)
47 {
48 	return (tmp && DISPLAY_ENDPOINT_USB4_DPIA == tmp->ep_type
49 			&& tmp->hpd_status
50 			&& tmp->dpia_bw_alloc_config.bw_alloc_enabled);
51 }
reset_bw_alloc_struct(struct dc_link * link)52 static void reset_bw_alloc_struct(struct dc_link *link)
53 {
54 	link->dpia_bw_alloc_config.bw_alloc_enabled = false;
55 	link->dpia_bw_alloc_config.sink_verified_bw = 0;
56 	link->dpia_bw_alloc_config.sink_max_bw = 0;
57 	link->dpia_bw_alloc_config.estimated_bw = 0;
58 	link->dpia_bw_alloc_config.bw_granularity = 0;
59 	link->dpia_bw_alloc_config.response_ready = false;
60 }
get_bw_granularity(struct dc_link * link)61 static uint8_t get_bw_granularity(struct dc_link *link)
62 {
63 	uint8_t bw_granularity = 0;
64 
65 	core_link_read_dpcd(
66 			link,
67 			DP_BW_GRANULALITY,
68 			&bw_granularity,
69 			sizeof(uint8_t));
70 
71 	switch (bw_granularity & 0x3) {
72 	case 0:
73 		bw_granularity = 4;
74 		break;
75 	case 1:
76 	default:
77 		bw_granularity = 2;
78 		break;
79 	}
80 
81 	return bw_granularity;
82 }
get_estimated_bw(struct dc_link * link)83 static int get_estimated_bw(struct dc_link *link)
84 {
85 	uint8_t bw_estimated_bw = 0;
86 
87 	if (core_link_read_dpcd(
88 		link,
89 		ESTIMATED_BW,
90 		&bw_estimated_bw,
91 		sizeof(uint8_t)) != DC_OK)
92 		dm_output_to_console("%s: AUX W/R ERROR @ 0x%x\n", __func__, ESTIMATED_BW);
93 
94 	return bw_estimated_bw * (Kbps_TO_Gbps / link->dpia_bw_alloc_config.bw_granularity);
95 }
allocate_usb4_bw(int * stream_allocated_bw,int bw_needed,struct dc_link * link)96 static bool allocate_usb4_bw(int *stream_allocated_bw, int bw_needed, struct dc_link *link)
97 {
98 	if (bw_needed > 0)
99 		*stream_allocated_bw += bw_needed;
100 
101 	return true;
102 }
deallocate_usb4_bw(int * stream_allocated_bw,int bw_to_dealloc,struct dc_link * link)103 static bool deallocate_usb4_bw(int *stream_allocated_bw, int bw_to_dealloc, struct dc_link *link)
104 {
105 	bool ret = false;
106 
107 	if (*stream_allocated_bw > 0) {
108 		*stream_allocated_bw -= bw_to_dealloc;
109 		ret = true;
110 	} else {
111 		//Do nothing for now
112 		ret = true;
113 	}
114 
115 	// Unplug so reset values
116 	if (!link->hpd_status)
117 		reset_bw_alloc_struct(link);
118 
119 	return ret;
120 }
121 /*
122  * Read all New BW alloc configuration ex: estimated_bw, allocated_bw,
123  * granuality, Driver_ID, CM_Group, & populate the BW allocation structs
124  * for host router and dpia
125  */
init_usb4_bw_struct(struct dc_link * link)126 static void init_usb4_bw_struct(struct dc_link *link)
127 {
128 	// Init the known values
129 	link->dpia_bw_alloc_config.bw_granularity = get_bw_granularity(link);
130 	link->dpia_bw_alloc_config.estimated_bw = get_estimated_bw(link);
131 }
get_lowest_dpia_index(struct dc_link * link)132 static uint8_t get_lowest_dpia_index(struct dc_link *link)
133 {
134 	const struct dc *dc_struct = link->dc;
135 	uint8_t idx = 0xFF;
136 
137 	for (int i = 0; i < MAX_PIPES * 2; ++i) {
138 
139 		if (!dc_struct->links[i] ||
140 				dc_struct->links[i]->ep_type != DISPLAY_ENDPOINT_USB4_DPIA)
141 			continue;
142 
143 		if (idx > dc_struct->links[i]->link_index)
144 			idx = dc_struct->links[i]->link_index;
145 	}
146 
147 	return idx;
148 }
149 /*
150  * Get the Max Available BW or Max Estimated BW for each Host Router
151  *
152  * @link: pointer to the dc_link struct instance
153  * @type: ESTIMATD BW or MAX AVAILABLE BW
154  *
155  * return: response_ready flag from dc_link struct
156  */
get_host_router_total_bw(struct dc_link * link,uint8_t type)157 static int get_host_router_total_bw(struct dc_link *link, uint8_t type)
158 {
159 	const struct dc *dc_struct = link->dc;
160 	uint8_t lowest_dpia_index = get_lowest_dpia_index(link);
161 	uint8_t idx = (link->link_index - lowest_dpia_index) / 2, idx_temp = 0;
162 	struct dc_link *link_temp;
163 	int total_bw = 0;
164 
165 	for (int i = 0; i < MAX_PIPES * 2; ++i) {
166 
167 		if (!dc_struct->links[i] || dc_struct->links[i]->ep_type != DISPLAY_ENDPOINT_USB4_DPIA)
168 			continue;
169 
170 		link_temp = dc_struct->links[i];
171 		if (!link_temp || !link_temp->hpd_status)
172 			continue;
173 
174 		idx_temp = (link_temp->link_index - lowest_dpia_index) / 2;
175 
176 		if (idx_temp == idx) {
177 
178 			if (type == HOST_ROUTER_BW_ESTIMATED)
179 				total_bw += link_temp->dpia_bw_alloc_config.estimated_bw;
180 			else if (type == HOST_ROUTER_BW_ALLOCATED)
181 				total_bw += link_temp->dpia_bw_alloc_config.sink_allocated_bw;
182 		}
183 	}
184 
185 	return total_bw;
186 }
187 /*
188  * Cleanup function for when the dpia is unplugged to reset struct
189  * and perform any required clean up
190  *
191  * @link: pointer to the dc_link struct instance
192  *
193  * return: none
194  */
dpia_bw_alloc_unplug(struct dc_link * link)195 static bool dpia_bw_alloc_unplug(struct dc_link *link)
196 {
197 	bool ret = false;
198 
199 	if (!link)
200 		return true;
201 
202 	return deallocate_usb4_bw(&link->dpia_bw_alloc_config.sink_allocated_bw,
203 			link->dpia_bw_alloc_config.sink_allocated_bw, link);
204 }
dc_link_set_usb4_req_bw_req(struct dc_link * link,int req_bw)205 static void dc_link_set_usb4_req_bw_req(struct dc_link *link, int req_bw)
206 {
207 	uint8_t requested_bw;
208 	uint32_t temp;
209 
210 	// 1. Add check for this corner case #1
211 	if (req_bw > link->dpia_bw_alloc_config.estimated_bw)
212 		req_bw = link->dpia_bw_alloc_config.estimated_bw;
213 
214 	temp = req_bw * link->dpia_bw_alloc_config.bw_granularity;
215 	requested_bw = temp / Kbps_TO_Gbps;
216 
217 	// Always make sure to add more to account for floating points
218 	if (temp % Kbps_TO_Gbps)
219 		++requested_bw;
220 
221 	// 2. Add check for this corner case #2
222 	req_bw = requested_bw * (Kbps_TO_Gbps / link->dpia_bw_alloc_config.bw_granularity);
223 	if (req_bw == link->dpia_bw_alloc_config.sink_allocated_bw)
224 		return;
225 
226 	if (core_link_write_dpcd(
227 		link,
228 		REQUESTED_BW,
229 		&requested_bw,
230 		sizeof(uint8_t)) != DC_OK)
231 		dm_output_to_console("%s: AUX W/R ERROR @ 0x%x\n", __func__, REQUESTED_BW);
232 	else
233 		link->dpia_bw_alloc_config.response_ready = false; // Reset flag
234 }
235 /*
236  * Return the response_ready flag from dc_link struct
237  *
238  * @link: pointer to the dc_link struct instance
239  *
240  * return: response_ready flag from dc_link struct
241  */
get_cm_response_ready_flag(struct dc_link * link)242 static bool get_cm_response_ready_flag(struct dc_link *link)
243 {
244 	return link->dpia_bw_alloc_config.response_ready;
245 }
246 // ------------------------------------------------------------------
247 //					PUBLIC FUNCTIONS
248 // ------------------------------------------------------------------
set_dptx_usb4_bw_alloc_support(struct dc_link * link)249 bool set_dptx_usb4_bw_alloc_support(struct dc_link *link)
250 {
251 	bool ret = false;
252 	uint8_t response = 0,
253 			bw_support_dpia = 0,
254 			bw_support_cm = 0;
255 
256 	if (!(link->ep_type == DISPLAY_ENDPOINT_USB4_DPIA && link->hpd_status))
257 		goto out;
258 
259 	if (core_link_read_dpcd(
260 		link,
261 		DP_TUNNELING_CAPABILITIES,
262 		&response,
263 		sizeof(uint8_t)) != DC_OK)
264 		dm_output_to_console("%s: AUX W/R ERROR @ 0x%x\n", __func__, DP_TUNNELING_CAPABILITIES);
265 
266 	bw_support_dpia = (response >> 7) & 1;
267 
268 	if (core_link_read_dpcd(
269 		link,
270 		USB4_DRIVER_BW_CAPABILITY,
271 		&response,
272 		sizeof(uint8_t)) != DC_OK)
273 		dm_output_to_console("%s: AUX W/R ERROR @ 0x%x\n", __func__, DP_TUNNELING_CAPABILITIES);
274 
275 	bw_support_cm = (response >> 7) & 1;
276 
277 	/* Send request acknowledgment to Turn ON DPTX support */
278 	if (bw_support_cm && bw_support_dpia) {
279 
280 		response = 0x80;
281 		if (core_link_write_dpcd(
282 				link,
283 				DPTX_BW_ALLOCATION_MODE_CONTROL,
284 				&response,
285 				sizeof(uint8_t)) != DC_OK)
286 			dm_output_to_console("%s: AUX W/R ERROR @ 0x%x\n",
287 					"**** FAILURE Enabling DPtx BW Allocation Mode Support ***\n",
288 					__func__, DP_TUNNELING_CAPABILITIES);
289 		else {
290 
291 			// SUCCESS Enabled DPtx BW Allocation Mode Support
292 			link->dpia_bw_alloc_config.bw_alloc_enabled = true;
293 			dm_output_to_console("**** SUCCESS Enabling DPtx BW Allocation Mode Support ***\n");
294 
295 			ret = true;
296 			init_usb4_bw_struct(link);
297 		}
298 	}
299 
300 out:
301 	return ret;
302 }
dc_link_get_usb4_req_bw_resp(struct dc_link * link,uint8_t bw,uint8_t result)303 void dc_link_get_usb4_req_bw_resp(struct dc_link *link, uint8_t bw, uint8_t result)
304 {
305 	if (!get_bw_alloc_proceed_flag((link)))
306 		return;
307 
308 	switch (result) {
309 
310 	case DPIA_BW_REQ_FAILED:
311 
312 		dm_output_to_console("%s: *** *** BW REQ FAILURE for DP-TX Request *** ***\n", __func__);
313 
314 		// Update the new Estimated BW value updated by CM
315 		link->dpia_bw_alloc_config.estimated_bw =
316 				bw * (Kbps_TO_Gbps / link->dpia_bw_alloc_config.bw_granularity);
317 
318 		dc_link_set_usb4_req_bw_req(link, link->dpia_bw_alloc_config.estimated_bw);
319 		link->dpia_bw_alloc_config.response_ready = false;
320 
321 		/*
322 		 * If FAIL then it is either:
323 		 * 1. Due to DP-Tx trying to allocate more than available i.e. it failed locally
324 		 *    => get estimated and allocate that
325 		 * 2. Due to the fact that DP-Tx tried to allocated ESTIMATED BW and failed then
326 		 *    CM will have to update 0xE0023 with new ESTIMATED BW value.
327 		 */
328 		break;
329 
330 	case DPIA_BW_REQ_SUCCESS:
331 
332 		dm_output_to_console("%s: *** BW REQ SUCCESS for DP-TX Request ***\n", __func__);
333 
334 		// 1. SUCCESS 1st time before any Pruning is done
335 		// 2. SUCCESS after prev. FAIL before any Pruning is done
336 		// 3. SUCCESS after Pruning is done but before enabling link
337 
338 		int needed = bw * (Kbps_TO_Gbps / link->dpia_bw_alloc_config.bw_granularity);
339 
340 		// 1.
341 		if (!link->dpia_bw_alloc_config.sink_allocated_bw) {
342 
343 			allocate_usb4_bw(&link->dpia_bw_alloc_config.sink_allocated_bw, needed, link);
344 			link->dpia_bw_alloc_config.sink_verified_bw =
345 					link->dpia_bw_alloc_config.sink_allocated_bw;
346 
347 			// SUCCESS from first attempt
348 			if (link->dpia_bw_alloc_config.sink_allocated_bw >
349 			link->dpia_bw_alloc_config.sink_max_bw)
350 				link->dpia_bw_alloc_config.sink_verified_bw =
351 						link->dpia_bw_alloc_config.sink_max_bw;
352 		}
353 		// 3.
354 		else if (link->dpia_bw_alloc_config.sink_allocated_bw) {
355 
356 			// Find out how much do we need to de-alloc
357 			if (link->dpia_bw_alloc_config.sink_allocated_bw > needed)
358 				deallocate_usb4_bw(&link->dpia_bw_alloc_config.sink_allocated_bw,
359 						link->dpia_bw_alloc_config.sink_allocated_bw - needed, link);
360 			else
361 				allocate_usb4_bw(&link->dpia_bw_alloc_config.sink_allocated_bw,
362 						needed - link->dpia_bw_alloc_config.sink_allocated_bw, link);
363 		}
364 
365 		// 4. If this is the 2nd sink then any unused bw will be reallocated to master DPIA
366 		// => check if estimated_bw changed
367 
368 		link->dpia_bw_alloc_config.response_ready = true;
369 		break;
370 
371 	case DPIA_EST_BW_CHANGED:
372 
373 		dm_output_to_console("%s: *** ESTIMATED BW CHANGED for DP-TX Request ***\n", __func__);
374 
375 		int available = 0, estimated = bw * (Kbps_TO_Gbps / link->dpia_bw_alloc_config.bw_granularity);
376 		int host_router_total_estimated_bw = get_host_router_total_bw(link, HOST_ROUTER_BW_ESTIMATED);
377 
378 		// 1. If due to unplug of other sink
379 		if (estimated == host_router_total_estimated_bw) {
380 
381 			// First update the estimated & max_bw fields
382 			if (link->dpia_bw_alloc_config.estimated_bw < estimated) {
383 				available = estimated - link->dpia_bw_alloc_config.estimated_bw;
384 				link->dpia_bw_alloc_config.estimated_bw = estimated;
385 			}
386 		}
387 		// 2. If due to realloc bw btw 2 dpia due to plug OR realloc unused Bw
388 		else {
389 
390 			// We took from another unplugged/problematic sink to give to us
391 			if (link->dpia_bw_alloc_config.estimated_bw < estimated)
392 				available = estimated - link->dpia_bw_alloc_config.estimated_bw;
393 
394 			// We lost estimated bw usually due to plug event of other dpia
395 			link->dpia_bw_alloc_config.estimated_bw = estimated;
396 		}
397 		break;
398 
399 	case DPIA_BW_ALLOC_CAPS_CHANGED:
400 
401 		dm_output_to_console("%s: *** BW ALLOC CAPABILITY CHANGED for DP-TX Request ***\n", __func__);
402 		link->dpia_bw_alloc_config.bw_alloc_enabled = false;
403 		break;
404 	}
405 }
dc_link_dp_dpia_handle_usb4_bandwidth_allocation_for_link(struct dc_link * link,int peak_bw)406 int dc_link_dp_dpia_handle_usb4_bandwidth_allocation_for_link(struct dc_link *link, int peak_bw)
407 {
408 	int ret = 0;
409 	uint8_t timeout = 10;
410 
411 	if (!(link && DISPLAY_ENDPOINT_USB4_DPIA == link->ep_type
412 			&& link->dpia_bw_alloc_config.bw_alloc_enabled))
413 		goto out;
414 
415 	//1. Hot Plug
416 	if (link->hpd_status && peak_bw > 0) {
417 
418 		// If DP over USB4 then we need to check BW allocation
419 		link->dpia_bw_alloc_config.sink_max_bw = peak_bw;
420 		dc_link_set_usb4_req_bw_req(link, link->dpia_bw_alloc_config.sink_max_bw);
421 
422 		do {
423 			if (!timeout > 0)
424 				timeout--;
425 			else
426 				break;
427 			udelay(10 * 1000);
428 		} while (!get_cm_response_ready_flag(link));
429 
430 		if (!timeout)
431 			ret = 0;// ERROR TIMEOUT waiting for response for allocating bw
432 		else if (link->dpia_bw_alloc_config.sink_allocated_bw > 0)
433 			ret = get_host_router_total_bw(link, HOST_ROUTER_BW_ALLOCATED);
434 	}
435 	//2. Cold Unplug
436 	else if (!link->hpd_status)
437 		dpia_bw_alloc_unplug(link);
438 
439 out:
440 	return ret;
441 }
442