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